@agentuity/cli 1.0.48 → 2.0.0-beta.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 (124) hide show
  1. package/dist/cmd/build/app-router-detector.d.ts +2 -5
  2. package/dist/cmd/build/app-router-detector.d.ts.map +1 -1
  3. package/dist/cmd/build/app-router-detector.js +130 -154
  4. package/dist/cmd/build/app-router-detector.js.map +1 -1
  5. package/dist/cmd/build/ids.d.ts +11 -0
  6. package/dist/cmd/build/ids.d.ts.map +1 -0
  7. package/dist/cmd/build/ids.js +18 -0
  8. package/dist/cmd/build/ids.js.map +1 -0
  9. package/dist/cmd/build/vite/agent-discovery.d.ts +8 -4
  10. package/dist/cmd/build/vite/agent-discovery.d.ts.map +1 -1
  11. package/dist/cmd/build/vite/agent-discovery.js +166 -487
  12. package/dist/cmd/build/vite/agent-discovery.js.map +1 -1
  13. package/dist/cmd/build/vite/bun-dev-server.d.ts +10 -16
  14. package/dist/cmd/build/vite/bun-dev-server.d.ts.map +1 -1
  15. package/dist/cmd/build/vite/bun-dev-server.js +67 -134
  16. package/dist/cmd/build/vite/bun-dev-server.js.map +1 -1
  17. package/dist/cmd/build/vite/docs-generator.d.ts.map +1 -1
  18. package/dist/cmd/build/vite/docs-generator.js +0 -2
  19. package/dist/cmd/build/vite/docs-generator.js.map +1 -1
  20. package/dist/cmd/build/vite/index.d.ts.map +1 -1
  21. package/dist/cmd/build/vite/index.js +0 -36
  22. package/dist/cmd/build/vite/index.js.map +1 -1
  23. package/dist/cmd/build/vite/lifecycle-generator.d.ts +10 -2
  24. package/dist/cmd/build/vite/lifecycle-generator.d.ts.map +1 -1
  25. package/dist/cmd/build/vite/lifecycle-generator.js +302 -23
  26. package/dist/cmd/build/vite/lifecycle-generator.js.map +1 -1
  27. package/dist/cmd/build/vite/route-discovery.d.ts +11 -38
  28. package/dist/cmd/build/vite/route-discovery.d.ts.map +1 -1
  29. package/dist/cmd/build/vite/route-discovery.js +97 -177
  30. package/dist/cmd/build/vite/route-discovery.js.map +1 -1
  31. package/dist/cmd/build/vite/server-bundler.js +1 -1
  32. package/dist/cmd/build/vite/server-bundler.js.map +1 -1
  33. package/dist/cmd/build/vite/static-renderer.d.ts.map +1 -1
  34. package/dist/cmd/build/vite/static-renderer.js +1 -9
  35. package/dist/cmd/build/vite/static-renderer.js.map +1 -1
  36. package/dist/cmd/build/vite/vite-asset-server-config.d.ts +6 -3
  37. package/dist/cmd/build/vite/vite-asset-server-config.d.ts.map +1 -1
  38. package/dist/cmd/build/vite/vite-asset-server-config.js +171 -21
  39. package/dist/cmd/build/vite/vite-asset-server-config.js.map +1 -1
  40. package/dist/cmd/build/vite/vite-asset-server.d.ts +8 -3
  41. package/dist/cmd/build/vite/vite-asset-server.d.ts.map +1 -1
  42. package/dist/cmd/build/vite/vite-asset-server.js +14 -13
  43. package/dist/cmd/build/vite/vite-asset-server.js.map +1 -1
  44. package/dist/cmd/build/vite/vite-builder.d.ts.map +1 -1
  45. package/dist/cmd/build/vite/vite-builder.js +6 -36
  46. package/dist/cmd/build/vite/vite-builder.js.map +1 -1
  47. package/dist/cmd/build/vite/ws-proxy.d.ts +53 -0
  48. package/dist/cmd/build/vite/ws-proxy.d.ts.map +1 -0
  49. package/dist/cmd/build/vite/ws-proxy.js +95 -0
  50. package/dist/cmd/build/vite/ws-proxy.js.map +1 -0
  51. package/dist/cmd/build/vite-bundler.d.ts.map +1 -1
  52. package/dist/cmd/build/vite-bundler.js +0 -3
  53. package/dist/cmd/build/vite-bundler.js.map +1 -1
  54. package/dist/cmd/cloud/deploy.d.ts.map +1 -1
  55. package/dist/cmd/cloud/deploy.js +0 -1
  56. package/dist/cmd/cloud/deploy.js.map +1 -1
  57. package/dist/cmd/dev/file-watcher.d.ts.map +1 -1
  58. package/dist/cmd/dev/file-watcher.js +2 -8
  59. package/dist/cmd/dev/file-watcher.js.map +1 -1
  60. package/dist/cmd/dev/index.d.ts.map +1 -1
  61. package/dist/cmd/dev/index.js +369 -720
  62. package/dist/cmd/dev/index.js.map +1 -1
  63. package/package.json +6 -8
  64. package/src/cmd/ai/prompt/agent.md +0 -1
  65. package/src/cmd/ai/prompt/api.md +0 -7
  66. package/src/cmd/ai/prompt/web.md +51 -213
  67. package/src/cmd/build/app-router-detector.ts +152 -182
  68. package/src/cmd/build/ids.ts +19 -0
  69. package/src/cmd/build/vite/agent-discovery.ts +208 -679
  70. package/src/cmd/build/vite/bun-dev-server.ts +78 -154
  71. package/src/cmd/build/vite/docs-generator.ts +0 -2
  72. package/src/cmd/build/vite/index.ts +1 -42
  73. package/src/cmd/build/vite/lifecycle-generator.ts +345 -21
  74. package/src/cmd/build/vite/route-discovery.ts +116 -274
  75. package/src/cmd/build/vite/server-bundler.ts +1 -1
  76. package/src/cmd/build/vite/static-renderer.ts +1 -11
  77. package/src/cmd/build/vite/vite-asset-server-config.ts +196 -23
  78. package/src/cmd/build/vite/vite-asset-server.ts +25 -15
  79. package/src/cmd/build/vite/vite-builder.ts +6 -53
  80. package/src/cmd/build/vite/ws-proxy.ts +126 -0
  81. package/src/cmd/build/vite-bundler.ts +0 -4
  82. package/src/cmd/cloud/deploy.ts +0 -1
  83. package/src/cmd/dev/file-watcher.ts +2 -9
  84. package/src/cmd/dev/index.ts +409 -832
  85. package/dist/cmd/build/ast.d.ts +0 -78
  86. package/dist/cmd/build/ast.d.ts.map +0 -1
  87. package/dist/cmd/build/ast.js +0 -2703
  88. package/dist/cmd/build/ast.js.map +0 -1
  89. package/dist/cmd/build/entry-generator.d.ts +0 -25
  90. package/dist/cmd/build/entry-generator.d.ts.map +0 -1
  91. package/dist/cmd/build/entry-generator.js +0 -695
  92. package/dist/cmd/build/entry-generator.js.map +0 -1
  93. package/dist/cmd/build/vite/api-mount-path.d.ts +0 -61
  94. package/dist/cmd/build/vite/api-mount-path.d.ts.map +0 -1
  95. package/dist/cmd/build/vite/api-mount-path.js +0 -83
  96. package/dist/cmd/build/vite/api-mount-path.js.map +0 -1
  97. package/dist/cmd/build/vite/registry-generator.d.ts +0 -19
  98. package/dist/cmd/build/vite/registry-generator.d.ts.map +0 -1
  99. package/dist/cmd/build/vite/registry-generator.js +0 -1108
  100. package/dist/cmd/build/vite/registry-generator.js.map +0 -1
  101. package/dist/cmd/build/vite/tailwind-source-plugin.d.ts +0 -13
  102. package/dist/cmd/build/vite/tailwind-source-plugin.d.ts.map +0 -1
  103. package/dist/cmd/build/vite/tailwind-source-plugin.js +0 -44
  104. package/dist/cmd/build/vite/tailwind-source-plugin.js.map +0 -1
  105. package/dist/cmd/build/webanalytics-generator.d.ts +0 -16
  106. package/dist/cmd/build/webanalytics-generator.d.ts.map +0 -1
  107. package/dist/cmd/build/webanalytics-generator.js +0 -178
  108. package/dist/cmd/build/webanalytics-generator.js.map +0 -1
  109. package/dist/cmd/build/workbench.d.ts +0 -7
  110. package/dist/cmd/build/workbench.d.ts.map +0 -1
  111. package/dist/cmd/build/workbench.js +0 -55
  112. package/dist/cmd/build/workbench.js.map +0 -1
  113. package/dist/utils/route-migration.d.ts +0 -62
  114. package/dist/utils/route-migration.d.ts.map +0 -1
  115. package/dist/utils/route-migration.js +0 -630
  116. package/dist/utils/route-migration.js.map +0 -1
  117. package/src/cmd/build/ast.ts +0 -3529
  118. package/src/cmd/build/entry-generator.ts +0 -760
  119. package/src/cmd/build/vite/api-mount-path.ts +0 -87
  120. package/src/cmd/build/vite/registry-generator.ts +0 -1267
  121. package/src/cmd/build/vite/tailwind-source-plugin.ts +0 -54
  122. package/src/cmd/build/webanalytics-generator.ts +0 -197
  123. package/src/cmd/build/workbench.ts +0 -58
  124. package/src/utils/route-migration.ts +0 -757
@@ -1,15 +1,15 @@
1
1
  /**
2
- * Route Discovery - READ-ONLY AST analysis
2
+ * Route Discovery Explicit Routing Only
3
3
  *
4
- * Discovers routes by scanning src/api/**\/*.ts files or by following
5
- * explicit router mounts from createApp({ router }).
6
- * Extracts route definitions WITHOUT mutating source files.
4
+ * Discovers routes from `createApp({ router })` by importing the router
5
+ * module at build time and reading `router.routes` from the Hono instance.
6
+ *
7
+ * File-based routing (scanning src/api/**) is no longer supported.
7
8
  */
8
9
 
9
10
  import { join, relative } from 'node:path';
10
- import { existsSync } from 'node:fs';
11
+ import { createHash } from 'node:crypto';
11
12
  import type { Logger } from '../../../types';
12
- import { parseRoute } from '../ast';
13
13
  import { toForwardSlash } from '../../../utils/normalize-path';
14
14
  import { detectExplicitRouter, type AppRouterDetection } from '../app-router-detector';
15
15
 
@@ -28,34 +28,6 @@ export interface RouteMetadata {
28
28
  };
29
29
  }
30
30
 
31
- export interface RouteInfo {
32
- method: string;
33
- path: string;
34
- filename: string;
35
- hasValidator: boolean;
36
- routeType: 'api' | 'sms' | 'email' | 'cron' | 'websocket' | 'sse' | 'stream';
37
- agentVariable?: string;
38
- agentImportPath?: string;
39
- agentName?: string;
40
- agentDescription?: string;
41
- inputSchemaVariable?: string;
42
- outputSchemaVariable?: string;
43
- inputSchemaImportPath?: string;
44
- inputSchemaImportedName?: string;
45
- outputSchemaImportPath?: string;
46
- outputSchemaImportedName?: string;
47
- inputSchemaCode?: string;
48
- outputSchemaCode?: string;
49
- stream?: boolean;
50
- pathParams?: string[];
51
- /**
52
- * When a route is mounted via .route(), its filename is set to the parent file
53
- * (for dedup filtering). schemaSourceFile preserves the actual file where the
54
- * route's schema variables are defined/exported, so registry imports resolve correctly.
55
- */
56
- schemaSourceFile?: string;
57
- }
58
-
59
31
  /**
60
32
  * Extract path parameters from a route path.
61
33
  * Matches patterns like :id, :userId, :id?, *path, etc.
@@ -74,283 +46,154 @@ export function extractPathParams(path: string): string[] {
74
46
  }
75
47
 
76
48
  /**
77
- * Discover all routes tries explicit router detection first, falls back to file-based.
78
- *
79
- * When `createApp({ router })` is detected in app.ts, routes are discovered by
80
- * following the router imports with code-derived mount paths. Otherwise, falls back
81
- * to scanning src/api/**\/*.ts with filesystem-derived paths.
49
+ * Generate a deterministic route ID from project/deployment/path/method.
82
50
  */
83
- export async function discoverRoutes(
84
- srcDir: string,
51
+ function generateRouteId(
85
52
  projectId: string,
86
53
  deploymentId: string,
87
- logger: Logger
88
- ): Promise<{
89
- routes: RouteMetadata[];
90
- routeInfoList: RouteInfo[];
91
- /** Whether explicit router was detected (vs file-based fallback) */
92
- explicitRouter?: AppRouterDetection;
93
- }> {
94
- const rootDir = join(srcDir, '..');
54
+ path: string,
55
+ method: string
56
+ ): string {
57
+ const hash = createHash('sha256')
58
+ .update(`${projectId}:${deploymentId}:${path}:${method}`)
59
+ .digest('hex')
60
+ .substring(0, 16);
61
+ return `routeid_${hash}`;
62
+ }
95
63
 
96
- // Try explicit router detection first
97
- const detection = await detectExplicitRouter(rootDir, logger);
98
- if (detection.detected && detection.mounts.length > 0) {
99
- logger.debug(
100
- 'Using explicit router detection (%d mount(s) from createApp)',
101
- detection.mounts.length
102
- );
103
- const result = await discoverExplicitRoutes(
104
- rootDir,
105
- srcDir,
106
- projectId,
107
- deploymentId,
108
- detection,
109
- logger
110
- );
111
- return { ...result, explicitRouter: detection };
64
+ /**
65
+ * Generate a version hash from file contents.
66
+ */
67
+ async function generateFileVersion(filePath: string): Promise<string> {
68
+ try {
69
+ const content = await Bun.file(filePath).text();
70
+ return createHash('sha256').update(content).digest('hex').substring(0, 16);
71
+ } catch {
72
+ return 'unknown';
112
73
  }
113
-
114
- // Fall back to file-based discovery
115
- return discoverFileBasedRoutes(srcDir, projectId, deploymentId, logger);
116
74
  }
117
75
 
118
76
  /**
119
- * Discover routes from explicit router mounts detected in app.ts.
120
- * Parses each router file with its code-derived mount prefix.
77
+ * Detect route type from handler metadata or method.
78
+ * Checks for route-meta symbol stamped by handler wrappers (websocket, sse, stream, cron).
121
79
  */
122
- async function discoverExplicitRoutes(
123
- rootDir: string,
124
- srcDir: string,
125
- projectId: string,
126
- deploymentId: string,
127
- detection: AppRouterDetection,
128
- logger: Logger
129
- ): Promise<{ routes: RouteMetadata[]; routeInfoList: RouteInfo[] }> {
130
- const routes: RouteMetadata[] = [];
131
- const routeInfoList: RouteInfo[] = [];
132
- const visited = new Set<string>();
133
- const mountedSubrouters = new Set<string>();
134
-
135
- for (const mount of detection.mounts) {
136
- try {
137
- const parsedRoutes = await parseRoute(rootDir, mount.routerFile, projectId, deploymentId, {
138
- visitedFiles: visited,
139
- mountedSubrouters,
140
- mountPrefix: mount.path,
141
- });
142
-
143
- if (parsedRoutes.length > 0) {
144
- const relFile = './' + toForwardSlash(relative(srcDir, mount.routerFile));
145
- logger.trace(
146
- 'Discovered %d route(s) from explicit mount at %s (%s)',
147
- parsedRoutes.length,
148
- mount.path,
149
- relFile
150
- );
151
- routes.push(...parsedRoutes);
152
-
153
- for (const route of parsedRoutes) {
154
- const pathParams = extractPathParams(route.path);
155
- routeInfoList.push({
156
- method: route.method.toUpperCase(),
157
- path: route.path,
158
- filename: route.filename,
159
- hasValidator: route.config?.hasValidator === true,
160
- routeType: route.type || 'api',
161
- agentVariable: route.config?.agentVariable as string | undefined,
162
- agentImportPath: route.config?.agentImportPath as string | undefined,
163
- inputSchemaVariable: route.config?.inputSchemaVariable as string | undefined,
164
- outputSchemaVariable: route.config?.outputSchemaVariable as string | undefined,
165
- inputSchemaImportPath: route.config?.inputSchemaImportPath as string | undefined,
166
- inputSchemaImportedName: route.config?.inputSchemaImportedName as
167
- | string
168
- | undefined,
169
- outputSchemaImportPath: route.config?.outputSchemaImportPath as
170
- | string
171
- | undefined,
172
- outputSchemaImportedName: route.config?.outputSchemaImportedName as
173
- | string
174
- | undefined,
175
- stream:
176
- route.config?.stream !== undefined && route.config.stream !== null
177
- ? Boolean(route.config.stream)
178
- : route.type === 'stream'
179
- ? true
180
- : undefined,
181
- pathParams: pathParams.length > 0 ? pathParams : undefined,
182
- schemaSourceFile: route.config?.schemaSourceFile as string | undefined,
183
- });
184
- }
185
- }
186
- } catch (error) {
187
- logger.warn(
188
- 'Failed to parse explicit router at %s: %s',
189
- mount.routerFile,
190
- error instanceof Error ? error.message : String(error)
191
- );
80
+ function detectRouteType(handler: unknown): 'api' | 'websocket' | 'sse' | 'stream' | 'cron' {
81
+ // Check for route-meta symbol (future: handler wrappers will tag this)
82
+ if (typeof handler === 'function') {
83
+ const meta = (handler as any)[Symbol.for('agentuity:route-meta')];
84
+ if (meta?.type) {
85
+ return meta.type;
192
86
  }
193
87
  }
194
88
 
195
- logger.debug('Discovered %d route(s) via explicit router detection', routes.length);
196
-
197
- // Check for route conflicts
198
- const conflicts = detectRouteConflicts(routeInfoList);
199
- if (conflicts.length > 0) {
200
- logger.error('Route conflicts detected:');
201
- for (const conflict of conflicts) {
202
- logger.error(' %s', conflict.message);
203
- for (const route of conflict.routes) {
204
- logger.error(' - %s %s in %s', route.method, route.path, route.filename);
205
- }
206
- }
207
- throw new Error(
208
- `Found ${conflicts.length} route conflict(s). Fix the conflicts and try again.`
209
- );
89
+ // Heuristic: upgradeWebSocket handler has a specific name
90
+ if (typeof handler === 'function' && handler.name === 'upgradeWebSocket') {
91
+ return 'websocket';
210
92
  }
211
93
 
212
- return { routes, routeInfoList };
94
+ return 'api';
213
95
  }
214
96
 
215
97
  /**
216
- * Discover routes by scanning src/api directory (original file-based approach).
98
+ * Discover all routes from explicit router mounts in createApp({ router }).
99
+ *
100
+ * Imports each router module at build time and reads `router.routes` from
101
+ * the Hono instance to extract method, path, and route type.
102
+ *
103
+ * @throws If no explicit router is detected in app.ts
217
104
  */
218
- async function discoverFileBasedRoutes(
105
+ export async function discoverRoutes(
219
106
  srcDir: string,
220
107
  projectId: string,
221
108
  deploymentId: string,
222
109
  logger: Logger
223
- ): Promise<{ routes: RouteMetadata[]; routeInfoList: RouteInfo[] }> {
224
- const apiDir = join(srcDir, 'api');
225
- const routes: RouteMetadata[] = [];
226
- const routeInfoList: RouteInfo[] = [];
110
+ ): Promise<{
111
+ routes: RouteMetadata[];
112
+ explicitRouter?: AppRouterDetection;
113
+ }> {
114
+ const rootDir = join(srcDir, '..');
227
115
 
228
- // Check if API directory exists
229
- if (!existsSync(apiDir)) {
230
- logger.trace('No api directory found at %s', apiDir);
231
- return { routes, routeInfoList };
116
+ const detection = await detectExplicitRouter(rootDir, logger);
117
+ if (!detection.detected || detection.mounts.length === 0) {
118
+ logger.debug('No explicit router detected in createApp() — no routes to discover');
119
+ return { routes: [] };
232
120
  }
233
121
 
234
- const transpiler = new Bun.Transpiler({ loader: 'ts', target: 'bun' });
122
+ logger.debug(
123
+ 'Using explicit router detection (%d mount(s) from createApp)',
124
+ detection.mounts.length
125
+ );
235
126
 
236
- // Track files that are mounted as sub-routers via .route()
237
- // These files will be parsed standalone AND via .route() — we need to deduplicate
238
- const mountedSubrouters = new Set<string>();
127
+ const routes: RouteMetadata[] = [];
239
128
 
240
- // Scan all .ts files in api directory
241
- const glob = new Bun.Glob('**/*.ts');
242
- for await (const file of glob.scan(apiDir)) {
243
- const filePath = join(apiDir, file);
129
+ const seenRoutes = new Set<string>();
244
130
 
131
+ for (const mount of detection.mounts) {
245
132
  try {
246
- const source = await Bun.file(filePath).text();
247
- const contents = transpiler.transformSync(source);
248
-
249
- // Check if file has createRouter or Hono
250
- if (!contents.includes('createRouter') && !contents.includes('new Hono')) {
251
- logger.trace('Skipping %s (no router)', file);
133
+ // Import the router module at build time
134
+ const routerModule = await import(mount.routerFile);
135
+ const router = routerModule.default ?? routerModule;
136
+
137
+ // Validate it's a Hono instance with routes
138
+ if (!router || !Array.isArray(router.routes)) {
139
+ logger.warn(
140
+ 'Router module at %s does not export a Hono instance with routes',
141
+ mount.routerFile
142
+ );
252
143
  continue;
253
144
  }
254
145
 
255
- const rootDir = join(srcDir, '..');
256
- const relativeFilename = './' + toForwardSlash(relative(srcDir, filePath));
146
+ const relFile = './' + toForwardSlash(relative(srcDir, mount.routerFile));
147
+ const version = await generateFileVersion(mount.routerFile);
257
148
 
258
- try {
259
- const parsedRoutes = await parseRoute(
260
- rootDir,
261
- filePath,
262
- projectId,
263
- deploymentId,
264
- undefined,
265
- mountedSubrouters
266
- );
267
-
268
- if (parsedRoutes.length > 0) {
269
- logger.trace('Discovered %d route(s) in %s', parsedRoutes.length, relativeFilename);
270
- routes.push(...parsedRoutes);
149
+ // Filter to actual route handlers (not middleware — middleware uses '*' or ALL method)
150
+ const routeEntries = router.routes.filter(
151
+ (r: any) => r.method !== 'ALL' && r.path !== '*'
152
+ );
271
153
 
272
- // Convert to RouteInfo for registry
273
- for (const route of parsedRoutes) {
274
- const pathParams = extractPathParams(route.path);
275
- routeInfoList.push({
276
- method: route.method.toUpperCase(),
277
- path: route.path,
278
- filename: route.filename,
279
- hasValidator: route.config?.hasValidator === true,
280
- routeType: route.type || 'api',
281
- agentVariable: route.config?.agentVariable as string | undefined,
282
- agentImportPath: route.config?.agentImportPath as string | undefined,
283
- inputSchemaVariable: route.config?.inputSchemaVariable as string | undefined,
284
- outputSchemaVariable: route.config?.outputSchemaVariable as string | undefined,
285
- inputSchemaImportPath: route.config?.inputSchemaImportPath as
286
- | string
287
- | undefined,
288
- inputSchemaImportedName: route.config?.inputSchemaImportedName as
289
- | string
290
- | undefined,
291
- outputSchemaImportPath: route.config?.outputSchemaImportPath as
292
- | string
293
- | undefined,
294
- outputSchemaImportedName: route.config?.outputSchemaImportedName as
295
- | string
296
- | undefined,
297
- stream:
298
- route.config?.stream !== undefined && route.config.stream !== null
299
- ? Boolean(route.config.stream)
300
- : route.type === 'stream'
301
- ? true
302
- : undefined,
303
- pathParams: pathParams.length > 0 ? pathParams : undefined,
304
- schemaSourceFile: route.config?.schemaSourceFile as string | undefined,
305
- });
306
- }
307
- }
308
- } catch (error) {
309
- // Skip files that don't have proper router setup
310
- if (error instanceof Error) {
311
- if (
312
- error.message.includes('could not find default export') ||
313
- error.message.includes('could not find an proper createRouter')
314
- ) {
315
- logger.trace('Skipping %s: %s', file, error.message);
316
- } else {
317
- throw error;
318
- }
319
- } else {
320
- throw error;
154
+ for (const route of routeEntries) {
155
+ const method = String(route.method).toUpperCase();
156
+ // Combine mount path with route path
157
+ let fullPath = route.path;
158
+ if (mount.path !== '/' && !fullPath.startsWith(mount.path)) {
159
+ fullPath = mount.path + (fullPath.startsWith('/') ? fullPath : '/' + fullPath);
321
160
  }
161
+
162
+ // Deduplicate (Hono may register same route multiple times for middleware)
163
+ const routeKey = `${method} ${fullPath}`;
164
+ if (seenRoutes.has(routeKey)) continue;
165
+ seenRoutes.add(routeKey);
166
+
167
+ const routeType = detectRouteType(route.handler);
168
+ const id = generateRouteId(projectId, deploymentId, fullPath, method);
169
+
170
+ routes.push({
171
+ id,
172
+ filename: toForwardSlash(relative(rootDir, mount.routerFile)),
173
+ path: fullPath,
174
+ method: method.toLowerCase(),
175
+ version,
176
+ type: routeType,
177
+ });
322
178
  }
323
- } catch (error) {
324
- logger.warn(`Failed to parse route file ${filePath}: ${error}`);
325
- }
326
- }
327
179
 
328
- // Filter out routes from standalone-parsed sub-router files
329
- // When a file is mounted via .route(), its standalone routes have wrong prefixes
330
- // Only the .route()-prefixed routes (attached to the parent file) are correct
331
- if (mountedSubrouters.size > 0) {
332
- const rootDir = join(srcDir, '..');
333
- const subrouterRelPaths = new Set<string>();
334
- for (const absPath of mountedSubrouters) {
335
- subrouterRelPaths.add(toForwardSlash(relative(rootDir, absPath)));
180
+ logger.trace(
181
+ 'Discovered %d route(s) from explicit mount at %s (%s)',
182
+ routeEntries.length,
183
+ mount.path,
184
+ relFile
185
+ );
186
+ } catch (error) {
187
+ logger.warn(
188
+ 'Failed to import router at %s: %s',
189
+ mount.routerFile,
190
+ error instanceof Error ? error.message : String(error)
191
+ );
336
192
  }
337
-
338
- // Remove routes whose filename matches a sub-router file
339
- // (these are the incorrectly-prefixed standalone routes)
340
- const filteredRoutes = routes.filter((r) => !subrouterRelPaths.has(r.filename));
341
- const filteredRouteInfoList = routeInfoList.filter((r) => !subrouterRelPaths.has(r.filename));
342
-
343
- // Replace arrays in-place
344
- routes.length = 0;
345
- routes.push(...filteredRoutes);
346
- routeInfoList.length = 0;
347
- routeInfoList.push(...filteredRouteInfoList);
348
193
  }
349
194
 
350
- logger.debug('Discovered %d route(s)', routes.length);
351
-
352
195
  // Check for route conflicts
353
- const conflicts = detectRouteConflicts(routeInfoList);
196
+ const conflicts = detectRouteConflicts(routes);
354
197
  if (conflicts.length > 0) {
355
198
  logger.error('Route conflicts detected:');
356
199
  for (const conflict of conflicts) {
@@ -364,7 +207,8 @@ async function discoverFileBasedRoutes(
364
207
  );
365
208
  }
366
209
 
367
- return { routes, routeInfoList };
210
+ logger.debug('Discovered %d route(s) via explicit router detection', routes.length);
211
+ return { routes, explicitRouter: detection };
368
212
  }
369
213
 
370
214
  export interface RouteConflict {
@@ -374,14 +218,13 @@ export interface RouteConflict {
374
218
  }
375
219
 
376
220
  /**
377
- * Detect conflicts between routes
221
+ * Detect conflicts between routes.
378
222
  */
379
223
  export function detectRouteConflicts(
380
224
  routes: Array<{ method: string; path: string; filename: string }>
381
225
  ): RouteConflict[] {
382
226
  const conflicts: RouteConflict[] = [];
383
227
 
384
- // Group routes by method+path
385
228
  const methodPathMap = new Map<string, Array<{ path: string; filename: string }>>();
386
229
 
387
230
  for (const route of routes) {
@@ -392,7 +235,6 @@ export function detectRouteConflicts(
392
235
  methodPathMap.get(key)!.push({ path: route.path, filename: route.filename });
393
236
  }
394
237
 
395
- // Check for exact duplicates
396
238
  for (const [methodPath, routeList] of methodPathMap.entries()) {
397
239
  if (routeList.length > 1) {
398
240
  const [method = 'UNKNOWN'] = methodPath.split(' ', 2);
@@ -56,7 +56,7 @@ export async function installExternalsAndBuild(options: ServerBundleOptions): Pr
56
56
 
57
57
  logger.debug('[server-bundler] Starting server bundle process');
58
58
 
59
- const entryPath = join(rootDir, 'src/generated/app.ts');
59
+ const entryPath = join(rootDir, 'app.ts');
60
60
  const outDir = join(rootDir, '.agentuity');
61
61
 
62
62
  logger.debug(`[server-bundler] Entry: ${entryPath}, OutDir: ${outDir}`);
@@ -109,16 +109,6 @@ export async function runStaticRender(options: StaticRenderOptions): Promise<Sta
109
109
  );
110
110
  }
111
111
 
112
- const isViteDebug =
113
- process.env.AGENTUITY_VITE_DEBUG === '1' || process.env.AGENTUITY_VITE_DEBUG === 'true';
114
- if (isViteDebug) {
115
- logger.debug('Vite debug logging enabled via AGENTUITY_VITE_DEBUG');
116
- const existing = process.env.DEBUG || '';
117
- if (!existing.includes('vite:')) {
118
- process.env.DEBUG = existing ? `${existing},vite:*` : 'vite:*';
119
- }
120
- }
121
-
122
112
  // Step 1: Vite SSR build
123
113
  // This resolves import.meta.glob, MDX imports, and other Vite-specific APIs
124
114
  logger.debug('Running Vite SSR build for static rendering...');
@@ -160,7 +150,7 @@ export async function runStaticRender(options: StaticRenderOptions): Promise<Sta
160
150
  // resolved at build time. Node built-ins are still externalized.
161
151
  noExternal: true,
162
152
  },
163
- logLevel: isViteDebug ? 'info' : 'warn',
153
+ logLevel: 'warn',
164
154
  });
165
155
 
166
156
  // Steps 2–4: wrapped in try-finally so SSR artifacts are always cleaned up,