@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.
- package/LICENSE +661 -0
- package/README.md +407 -0
- package/lib/AutoViewAgent.d.ts +109 -0
- package/lib/AutoViewAgent.js +123 -0
- package/lib/AutoViewAgent.js.map +1 -0
- package/lib/agent/emitMcpServer.d.ts +15 -0
- package/lib/agent/emitMcpServer.js +157 -0
- package/lib/agent/emitMcpServer.js.map +1 -0
- package/lib/agent/emitReport.d.ts +14 -0
- package/lib/agent/emitReport.js +85 -0
- package/lib/agent/emitReport.js.map +1 -0
- package/lib/agent/toolSurface.d.ts +130 -0
- package/lib/agent/toolSurface.js +342 -0
- package/lib/agent/toolSurface.js.map +1 -0
- package/lib/agent/verifyAgentTasks.d.ts +87 -0
- package/lib/agent/verifyAgentTasks.js +126 -0
- package/lib/agent/verifyAgentTasks.js.map +1 -0
- package/lib/cli/main.d.ts +2 -0
- package/lib/cli/main.js +295 -0
- package/lib/cli/main.js.map +1 -0
- package/lib/compiler/AutoViewInterfaceCompiler.d.ts +27 -0
- package/lib/compiler/AutoViewInterfaceCompiler.js +68 -0
- package/lib/compiler/AutoViewInterfaceCompiler.js.map +1 -0
- package/lib/constants/AutoViewFrontendTemplate.d.ts +1 -0
- package/lib/constants/AutoViewFrontendTemplate.js +46 -0
- package/lib/constants/AutoViewFrontendTemplate.js.map +1 -0
- package/lib/constants/AutoViewSystemPromptConstant.d.ts +5 -0
- package/lib/constants/AutoViewSystemPromptConstant.js +4 -0
- package/lib/constants/AutoViewSystemPromptConstant.js.map +1 -0
- package/lib/context/IAutoViewAgentContext.d.ts +60 -0
- package/lib/context/IAutoViewAgentContext.js +3 -0
- package/lib/context/IAutoViewAgentContext.js.map +1 -0
- package/lib/fromSwagger.d.ts +53 -0
- package/lib/fromSwagger.js +513 -0
- package/lib/fromSwagger.js.map +1 -0
- package/lib/generateDeterministic.d.ts +26 -0
- package/lib/generateDeterministic.js +75 -0
- package/lib/generateDeterministic.js.map +1 -0
- package/lib/index.d.ts +15 -0
- package/lib/index.js +41 -0
- package/lib/index.js.map +1 -0
- package/lib/orchestrate/orchestrateAutoView.d.ts +17 -0
- package/lib/orchestrate/orchestrateAutoView.js +491 -0
- package/lib/orchestrate/orchestrateAutoView.js.map +1 -0
- package/lib/orchestrate/orchestrateAutoViewProductPlan.d.ts +37 -0
- package/lib/orchestrate/orchestrateAutoViewProductPlan.js +109 -0
- package/lib/orchestrate/orchestrateAutoViewProductPlan.js.map +1 -0
- package/lib/orchestrate/orchestrateAutoViewRender.d.ts +133 -0
- package/lib/orchestrate/orchestrateAutoViewRender.js +943 -0
- package/lib/orchestrate/orchestrateAutoViewRender.js.map +1 -0
- package/lib/orchestrate/orchestrateAutoViewRenderDeterministic.d.ts +24 -0
- package/lib/orchestrate/orchestrateAutoViewRenderDeterministic.js +92 -0
- package/lib/orchestrate/orchestrateAutoViewRenderDeterministic.js.map +1 -0
- package/lib/orchestrate/orchestrateAutoViewReview.d.ts +48 -0
- package/lib/orchestrate/orchestrateAutoViewReview.js +328 -0
- package/lib/orchestrate/orchestrateAutoViewReview.js.map +1 -0
- package/lib/orchestrate/orchestrateAutoViewScaffold.d.ts +45 -0
- package/lib/orchestrate/orchestrateAutoViewScaffold.js +586 -0
- package/lib/orchestrate/orchestrateAutoViewScaffold.js.map +1 -0
- package/lib/orchestrate/orchestrateAutoViewSdkStudy.d.ts +26 -0
- package/lib/orchestrate/orchestrateAutoViewSdkStudy.js +85 -0
- package/lib/orchestrate/orchestrateAutoViewSdkStudy.js.map +1 -0
- package/lib/orchestrate/structures/IAutoViewProductPlan.d.ts +96 -0
- package/lib/orchestrate/structures/IAutoViewProductPlan.js +3 -0
- package/lib/orchestrate/structures/IAutoViewProductPlan.js.map +1 -0
- package/lib/orchestrate/structures/IAutoViewProductPlanApplication.d.ts +38 -0
- package/lib/orchestrate/structures/IAutoViewProductPlanApplication.js +3 -0
- package/lib/orchestrate/structures/IAutoViewProductPlanApplication.js.map +1 -0
- package/lib/orchestrate/structures/IAutoViewRenderApplication.d.ts +38 -0
- package/lib/orchestrate/structures/IAutoViewRenderApplication.js +3 -0
- package/lib/orchestrate/structures/IAutoViewRenderApplication.js.map +1 -0
- package/lib/orchestrate/structures/IAutoViewReviewApplication.d.ts +40 -0
- package/lib/orchestrate/structures/IAutoViewReviewApplication.js +3 -0
- package/lib/orchestrate/structures/IAutoViewReviewApplication.js.map +1 -0
- package/lib/orchestrate/structures/IAutoViewSdkMap.d.ts +63 -0
- package/lib/orchestrate/structures/IAutoViewSdkMap.js +3 -0
- package/lib/orchestrate/structures/IAutoViewSdkMap.js.map +1 -0
- package/lib/orchestrate/structures/IAutoViewSdkStudyApplication.d.ts +37 -0
- package/lib/orchestrate/structures/IAutoViewSdkStudyApplication.js +3 -0
- package/lib/orchestrate/structures/IAutoViewSdkStudyApplication.js.map +1 -0
- package/lib/orchestrate/utils/HistoryMessage.d.ts +10 -0
- package/lib/orchestrate/utils/HistoryMessage.js +25 -0
- package/lib/orchestrate/utils/HistoryMessage.js.map +1 -0
- package/lib/orchestrate/utils/auditFrontendRuntime.d.ts +53 -0
- package/lib/orchestrate/utils/auditFrontendRuntime.js +362 -0
- package/lib/orchestrate/utils/auditFrontendRuntime.js.map +1 -0
- package/lib/orchestrate/utils/buildDeterministicPlan.d.ts +4 -0
- package/lib/orchestrate/utils/buildDeterministicPlan.js +233 -0
- package/lib/orchestrate/utils/buildDeterministicPlan.js.map +1 -0
- package/lib/orchestrate/utils/buildDeterministicSdkMap.d.ts +22 -0
- package/lib/orchestrate/utils/buildDeterministicSdkMap.js +154 -0
- package/lib/orchestrate/utils/buildDeterministicSdkMap.js.map +1 -0
- package/lib/orchestrate/utils/cacheNodeModules.d.ts +31 -0
- package/lib/orchestrate/utils/cacheNodeModules.js +134 -0
- package/lib/orchestrate/utils/cacheNodeModules.js.map +1 -0
- package/lib/orchestrate/utils/describeEndpointPropsShape.d.ts +37 -0
- package/lib/orchestrate/utils/describeEndpointPropsShape.js +192 -0
- package/lib/orchestrate/utils/describeEndpointPropsShape.js.map +1 -0
- package/lib/orchestrate/utils/describeEndpointRequestBodyShape.d.ts +22 -0
- package/lib/orchestrate/utils/describeEndpointRequestBodyShape.js +29 -0
- package/lib/orchestrate/utils/describeEndpointRequestBodyShape.js.map +1 -0
- package/lib/orchestrate/utils/describeEndpointResponseShape.d.ts +19 -0
- package/lib/orchestrate/utils/describeEndpointResponseShape.js +30 -0
- package/lib/orchestrate/utils/describeEndpointResponseShape.js.map +1 -0
- package/lib/orchestrate/utils/executeCachedBatch.d.ts +22 -0
- package/lib/orchestrate/utils/executeCachedBatch.js +64 -0
- package/lib/orchestrate/utils/executeCachedBatch.js.map +1 -0
- package/lib/orchestrate/utils/loadShoppingFixture.d.ts +33 -0
- package/lib/orchestrate/utils/loadShoppingFixture.js +17 -0
- package/lib/orchestrate/utils/loadShoppingFixture.js.map +1 -0
- package/lib/orchestrate/utils/normalizeProductPlanPaths.d.ts +24 -0
- package/lib/orchestrate/utils/normalizeProductPlanPaths.js +77 -0
- package/lib/orchestrate/utils/normalizeProductPlanPaths.js.map +1 -0
- package/lib/orchestrate/utils/renderJsonSchema.d.ts +23 -0
- package/lib/orchestrate/utils/renderJsonSchema.js +122 -0
- package/lib/orchestrate/utils/renderJsonSchema.js.map +1 -0
- package/lib/orchestrate/utils/renderResourcePage.d.ts +36 -0
- package/lib/orchestrate/utils/renderResourcePage.js +1415 -0
- package/lib/orchestrate/utils/renderResourcePage.js.map +1 -0
- package/lib/orchestrate/utils/validateFrontendTypecheck.d.ts +109 -0
- package/lib/orchestrate/utils/validateFrontendTypecheck.js +274 -0
- package/lib/orchestrate/utils/validateFrontendTypecheck.js.map +1 -0
- package/lib/preview/renderPreview.d.ts +22 -0
- package/lib/preview/renderPreview.js +198 -0
- package/lib/preview/renderPreview.js.map +1 -0
- package/lib/typings/compiler.d.ts +39 -0
- package/lib/typings/compiler.js +3 -0
- package/lib/typings/compiler.js.map +1 -0
- package/lib/typings/events.d.ts +106 -0
- package/lib/typings/events.js +3 -0
- package/lib/typings/events.js.map +1 -0
- package/lib/typings/index.d.ts +10 -0
- package/lib/typings/index.js +27 -0
- package/lib/typings/index.js.map +1 -0
- package/lib/typings/misc.d.ts +78 -0
- package/lib/typings/misc.js +3 -0
- package/lib/typings/misc.js.map +1 -0
- package/lib/utils/ArrayUtil.d.ts +8 -0
- package/lib/utils/ArrayUtil.js +30 -0
- package/lib/utils/ArrayUtil.js.map +1 -0
- package/lib/utils/StringUtil.d.ts +11 -0
- package/lib/utils/StringUtil.js +28 -0
- package/lib/utils/StringUtil.js.map +1 -0
- package/lib/utils/classifyEndpoints.d.ts +62 -0
- package/lib/utils/classifyEndpoints.js +216 -0
- package/lib/utils/classifyEndpoints.js.map +1 -0
- package/lib/utils/endpointFilter.d.ts +26 -0
- package/lib/utils/endpointFilter.js +0 -0
- package/lib/utils/endpointFilter.js.map +1 -0
- package/lib/utils/extractFields.d.ts +85 -0
- package/lib/utils/extractFields.js +231 -0
- package/lib/utils/extractFields.js.map +1 -0
- package/lib/utils/index.d.ts +13 -0
- package/lib/utils/index.js +30 -0
- package/lib/utils/index.js.map +1 -0
- package/lib/utils/normalizeForNestia.d.ts +34 -0
- package/lib/utils/normalizeForNestia.js +133 -0
- package/lib/utils/normalizeForNestia.js.map +1 -0
- package/lib/utils/resourcePlan.d.ts +39 -0
- package/lib/utils/resourcePlan.js +95 -0
- package/lib/utils/resourcePlan.js.map +1 -0
- package/lib/utils/sliceDocument.d.ts +17 -0
- package/lib/utils/sliceDocument.js +114 -0
- package/lib/utils/sliceDocument.js.map +1 -0
- package/lib/utils/toEndpoints.d.ts +90 -0
- package/lib/utils/toEndpoints.js +227 -0
- package/lib/utils/toEndpoints.js.map +1 -0
- package/lib/verify/runWorkflows.d.ts +25 -0
- package/lib/verify/runWorkflows.js +366 -0
- package/lib/verify/runWorkflows.js.map +1 -0
- package/lib/verify/workflows.d.ts +53 -0
- package/lib/verify/workflows.js +107 -0
- package/lib/verify/workflows.js.map +1 -0
- package/package.json +82 -0
- package/prompts/AUTOVIEW_RENDER.md +398 -0
- package/prompts/AUTOVIEW_REVIEW.md +60 -0
- package/prompts/AUTOVIEW_SDK_STUDY.md +89 -0
- package/src/AutoViewAgent.ts +222 -0
- package/src/agent/emitMcpServer.integration.test.ts +168 -0
- package/src/agent/emitMcpServer.test.ts +51 -0
- package/src/agent/emitMcpServer.ts +178 -0
- package/src/agent/emitReport.ts +117 -0
- package/src/agent/toolSurface.test.ts +243 -0
- package/src/agent/toolSurface.ts +501 -0
- package/src/agent/verifyAgentTasks.test.ts +106 -0
- package/src/agent/verifyAgentTasks.ts +171 -0
- package/src/cli/main.ts +363 -0
- package/src/compiler/AutoViewInterfaceCompiler.ts +69 -0
- package/src/constants/AutoViewFrontendTemplate.ts +42 -0
- package/src/constants/AutoViewSystemPromptConstant.ts +6 -0
- package/src/context/IAutoViewAgentContext.ts +84 -0
- package/src/fromSwagger.test.ts +269 -0
- package/src/fromSwagger.ts +500 -0
- package/src/generateDeterministic.test.ts +39 -0
- package/src/generateDeterministic.ts +77 -0
- package/src/index.ts +30 -0
- package/src/orchestrate/orchestrateAutoView.ts +590 -0
- package/src/orchestrate/orchestrateAutoViewProductPlan.ts +121 -0
- package/src/orchestrate/orchestrateAutoViewRender.ts +1117 -0
- package/src/orchestrate/orchestrateAutoViewRenderDeterministic.ts +101 -0
- package/src/orchestrate/orchestrateAutoViewReview.ts +272 -0
- package/src/orchestrate/orchestrateAutoViewScaffold.ts +627 -0
- package/src/orchestrate/orchestrateAutoViewSdkStudy.ts +90 -0
- package/src/orchestrate/renderNavTs.test.ts +74 -0
- package/src/orchestrate/structures/IAutoViewProductPlan.ts +119 -0
- package/src/orchestrate/structures/IAutoViewProductPlanApplication.ts +41 -0
- package/src/orchestrate/structures/IAutoViewRenderApplication.ts +40 -0
- package/src/orchestrate/structures/IAutoViewReviewApplication.ts +42 -0
- package/src/orchestrate/structures/IAutoViewSdkMap.ts +72 -0
- package/src/orchestrate/structures/IAutoViewSdkStudyApplication.ts +40 -0
- package/src/orchestrate/utils/HistoryMessage.ts +41 -0
- package/src/orchestrate/utils/auditFrontendRuntime.test.ts +18 -0
- package/src/orchestrate/utils/auditFrontendRuntime.ts +454 -0
- package/src/orchestrate/utils/buildDeterministicPlan.test.ts +170 -0
- package/src/orchestrate/utils/buildDeterministicPlan.ts +289 -0
- package/src/orchestrate/utils/buildDeterministicSdkMap.test.ts +90 -0
- package/src/orchestrate/utils/buildDeterministicSdkMap.ts +169 -0
- package/src/orchestrate/utils/cacheNodeModules.ts +136 -0
- package/src/orchestrate/utils/describeEndpointPropsShape.test.ts +86 -0
- package/src/orchestrate/utils/describeEndpointPropsShape.ts +202 -0
- package/src/orchestrate/utils/describeEndpointRequestBodyShape.test.ts +87 -0
- package/src/orchestrate/utils/describeEndpointRequestBodyShape.ts +31 -0
- package/src/orchestrate/utils/describeEndpointResponseShape.test.ts +70 -0
- package/src/orchestrate/utils/describeEndpointResponseShape.ts +32 -0
- package/src/orchestrate/utils/executeCachedBatch.ts +59 -0
- package/src/orchestrate/utils/loadShoppingFixture.ts +52 -0
- package/src/orchestrate/utils/normalizeProductPlanPaths.ts +92 -0
- package/src/orchestrate/utils/renderJsonSchema.test.ts +162 -0
- package/src/orchestrate/utils/renderJsonSchema.ts +133 -0
- package/src/orchestrate/utils/renderResourcePage.test.ts +468 -0
- package/src/orchestrate/utils/renderResourcePage.ts +1624 -0
- package/src/orchestrate/utils/validateFrontendTypecheck.test.ts +32 -0
- package/src/orchestrate/utils/validateFrontendTypecheck.ts +335 -0
- package/src/preview/renderPreview.ts +273 -0
- package/src/typings/compiler.ts +47 -0
- package/src/typings/events.ts +155 -0
- package/src/typings/index.ts +10 -0
- package/src/typings/misc.ts +93 -0
- package/src/utils/ArrayUtil.ts +16 -0
- package/src/utils/StringUtil.ts +29 -0
- package/src/utils/classifyEndpoints.test.ts +86 -0
- package/src/utils/classifyEndpoints.ts +291 -0
- package/src/utils/endpointFilter.test.ts +50 -0
- package/src/utils/endpointFilter.ts +0 -0
- package/src/utils/extractFields.test.ts +82 -0
- package/src/utils/extractFields.ts +306 -0
- package/src/utils/index.ts +13 -0
- package/src/utils/normalizeForNestia.test.ts +93 -0
- package/src/utils/normalizeForNestia.ts +139 -0
- package/src/utils/resourcePlan.test.ts +104 -0
- package/src/utils/resourcePlan.ts +180 -0
- package/src/utils/sliceDocument.test.ts +85 -0
- package/src/utils/sliceDocument.ts +119 -0
- package/src/utils/toEndpoints.test.ts +251 -0
- package/src/utils/toEndpoints.ts +343 -0
- package/src/verify/runWorkflows.ts +403 -0
- package/src/verify/workflows.test.ts +117 -0
- package/src/verify/workflows.ts +154 -0
- package/template/CLAUDE.md +140 -0
- package/template/Dockerfile +31 -0
- package/template/PROMPT.md +80 -0
- package/template/SANDBOX.md +70 -0
- package/template/app/api/health/route.ts +10 -0
- package/template/app/globals.css +97 -0
- package/template/app/layout.tsx +30 -0
- package/template/app/page.tsx +19 -0
- package/template/components/AppShell.tsx +114 -0
- package/template/components/auto/CatalogGrid.tsx +159 -0
- package/template/components/auto/ConfirmButton.tsx +67 -0
- package/template/components/auto/EmbeddedCollection.tsx +144 -0
- package/template/components/auto/ResourceDashboard.tsx +104 -0
- package/template/components/auto/ResourceDetail.tsx +93 -0
- package/template/components/auto/ResourceForm.tsx +235 -0
- package/template/components/auto/ResourceIcon.tsx +88 -0
- package/template/components/auto/ResourceLanding.tsx +155 -0
- package/template/components/auto/ResourceTable.tsx +223 -0
- package/template/components/auto/formatValue.tsx +186 -0
- package/template/components/auto/types.ts +42 -0
- package/template/components/ui/badge.tsx +40 -0
- package/template/components/ui/button.tsx +57 -0
- package/template/components/ui/card.tsx +86 -0
- package/template/components/ui/dialog.tsx +119 -0
- package/template/components/ui/input.tsx +23 -0
- package/template/components/ui/label.tsx +24 -0
- package/template/components/ui/pagination.tsx +117 -0
- package/template/components/ui/select.tsx +92 -0
- package/template/components/ui/sheet.tsx +135 -0
- package/template/components/ui/skeleton.tsx +15 -0
- package/template/components/ui/table.tsx +120 -0
- package/template/components/ui/tabs.tsx +55 -0
- package/template/lib/utils.ts +35 -0
- package/template/next.config.mjs +52 -0
- package/template/package.json +46 -0
- package/template/postcss.config.js +6 -0
- package/template/scripts/start-shopping-backend.sh +56 -0
- package/template/tailwind.config.ts +96 -0
- 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
|
+
}
|