@agentuity/migrate 2.0.11 → 3.0.0-alpha.1

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 (52) hide show
  1. package/bin/migrate.ts +93 -10
  2. package/dist/detect-v3.d.ts +92 -0
  3. package/dist/detect-v3.d.ts.map +1 -0
  4. package/dist/detect-v3.js +675 -0
  5. package/dist/detect-v3.js.map +1 -0
  6. package/dist/index.d.ts +2 -0
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +4 -0
  9. package/dist/index.js.map +1 -1
  10. package/dist/migrate-v3.d.ts +38 -0
  11. package/dist/migrate-v3.d.ts.map +1 -0
  12. package/dist/migrate-v3.js +448 -0
  13. package/dist/migrate-v3.js.map +1 -0
  14. package/dist/report.d.ts +3 -0
  15. package/dist/report.d.ts.map +1 -1
  16. package/dist/report.js +64 -0
  17. package/dist/report.js.map +1 -1
  18. package/dist/transforms/v3/agents.d.ts +33 -0
  19. package/dist/transforms/v3/agents.d.ts.map +1 -0
  20. package/dist/transforms/v3/agents.js +335 -0
  21. package/dist/transforms/v3/agents.js.map +1 -0
  22. package/dist/transforms/v3/dev-setup.d.ts +27 -0
  23. package/dist/transforms/v3/dev-setup.d.ts.map +1 -0
  24. package/dist/transforms/v3/dev-setup.js +103 -0
  25. package/dist/transforms/v3/dev-setup.js.map +1 -0
  26. package/dist/transforms/v3/entry-point.d.ts +23 -0
  27. package/dist/transforms/v3/entry-point.d.ts.map +1 -0
  28. package/dist/transforms/v3/entry-point.js +67 -0
  29. package/dist/transforms/v3/entry-point.js.map +1 -0
  30. package/dist/transforms/v3/package-json.d.ts +28 -0
  31. package/dist/transforms/v3/package-json.d.ts.map +1 -0
  32. package/dist/transforms/v3/package-json.js +151 -0
  33. package/dist/transforms/v3/package-json.js.map +1 -0
  34. package/dist/transforms/v3/routes.d.ts +37 -0
  35. package/dist/transforms/v3/routes.d.ts.map +1 -0
  36. package/dist/transforms/v3/routes.js +146 -0
  37. package/dist/transforms/v3/routes.js.map +1 -0
  38. package/dist/transforms/v3/services.d.ts +19 -0
  39. package/dist/transforms/v3/services.d.ts.map +1 -0
  40. package/dist/transforms/v3/services.js +61 -0
  41. package/dist/transforms/v3/services.js.map +1 -0
  42. package/package.json +10 -5
  43. package/src/detect-v3.ts +867 -0
  44. package/src/index.ts +13 -0
  45. package/src/migrate-v3.ts +539 -0
  46. package/src/report.ts +86 -0
  47. package/src/transforms/v3/agents.ts +434 -0
  48. package/src/transforms/v3/dev-setup.ts +137 -0
  49. package/src/transforms/v3/entry-point.ts +90 -0
  50. package/src/transforms/v3/package-json.ts +183 -0
  51. package/src/transforms/v3/routes.ts +185 -0
  52. package/src/transforms/v3/services.ts +76 -0
@@ -0,0 +1,675 @@
1
+ /**
2
+ * V2 → V3 pattern detection.
3
+ *
4
+ * Analyses a project directory and returns a structured report of every v2
5
+ * artefact that needs to be migrated to v3 (framework-agnostic Hono).
6
+ * No files are modified here.
7
+ */
8
+ import { existsSync, readdirSync } from 'node:fs';
9
+ import { join, relative, resolve } from 'node:path';
10
+ import ts from 'typescript';
11
+ // ---------------------------------------------------------------------------
12
+ // Known service names
13
+ // ---------------------------------------------------------------------------
14
+ const SERVICE_NAMES = [
15
+ 'kv',
16
+ 'vector',
17
+ 'stream',
18
+ 'queue',
19
+ 'email',
20
+ 'task',
21
+ 'schedule',
22
+ 'sandbox',
23
+ 'logger',
24
+ ];
25
+ /** Map from service name to package */
26
+ export const SERVICE_PACKAGE_MAP = {
27
+ kv: { pkg: '@agentuity/keyvalue', client: 'KeyValueClient' },
28
+ vector: { pkg: '@agentuity/vector', client: 'VectorClient' },
29
+ stream: { pkg: '@agentuity/stream', client: 'StreamClient' },
30
+ queue: { pkg: '@agentuity/queue', client: 'QueueClient' },
31
+ email: { pkg: '@agentuity/email', client: 'EmailClient' },
32
+ task: { pkg: '@agentuity/task', client: 'TaskClient' },
33
+ schedule: { pkg: '@agentuity/schedule', client: 'ScheduleClient' },
34
+ sandbox: { pkg: '@agentuity/sandbox', client: 'SandboxClient' },
35
+ };
36
+ // ---------------------------------------------------------------------------
37
+ // Helpers
38
+ // ---------------------------------------------------------------------------
39
+ function rel(projectDir, abs) {
40
+ return relative(projectDir, abs);
41
+ }
42
+ function* walkFiles(dir, exts) {
43
+ if (!existsSync(dir))
44
+ return;
45
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
46
+ const full = join(dir, entry.name);
47
+ if (entry.isDirectory()) {
48
+ if (['node_modules', 'dist', '.agentuity', '.git'].includes(entry.name))
49
+ continue;
50
+ yield* walkFiles(full, exts);
51
+ }
52
+ else if (entry.isFile() && exts.some((e) => entry.name.endsWith(e))) {
53
+ yield full;
54
+ }
55
+ }
56
+ }
57
+ async function parseTs(filePath) {
58
+ const src = await Bun.file(filePath).text();
59
+ return ts.createSourceFile(filePath, src, ts.ScriptTarget.ESNext, true);
60
+ }
61
+ // ---------------------------------------------------------------------------
62
+ // AST analysis helpers
63
+ // ---------------------------------------------------------------------------
64
+ /**
65
+ * Get properties passed to createApp() call.
66
+ */
67
+ function getCreateAppProps(sourceFile) {
68
+ const props = [];
69
+ function visit(node) {
70
+ if (ts.isCallExpression(node) &&
71
+ ts.isIdentifier(node.expression) &&
72
+ node.expression.text === 'createApp') {
73
+ const arg = node.arguments[0];
74
+ if (arg && ts.isObjectLiteralExpression(arg)) {
75
+ for (const prop of arg.properties) {
76
+ if ((ts.isPropertyAssignment(prop) || ts.isShorthandPropertyAssignment(prop)) &&
77
+ ts.isIdentifier(prop.name)) {
78
+ props.push(prop.name.text);
79
+ }
80
+ }
81
+ }
82
+ }
83
+ ts.forEachChild(node, visit);
84
+ }
85
+ visit(sourceFile);
86
+ return props;
87
+ }
88
+ /**
89
+ * Check if a source file imports createApp from @agentuity/runtime.
90
+ */
91
+ function hasCreateAppImport(sourceFile) {
92
+ let found = false;
93
+ function visit(node) {
94
+ if (found)
95
+ return;
96
+ if (ts.isImportDeclaration(node)) {
97
+ const moduleSpecifier = node.moduleSpecifier.text;
98
+ if (moduleSpecifier === '@agentuity/runtime') {
99
+ const namedBindings = node.importClause?.namedBindings;
100
+ if (namedBindings && ts.isNamedImports(namedBindings)) {
101
+ for (const element of namedBindings.elements) {
102
+ if (element.name.text === 'createApp') {
103
+ found = true;
104
+ return;
105
+ }
106
+ }
107
+ }
108
+ }
109
+ }
110
+ ts.forEachChild(node, visit);
111
+ }
112
+ visit(sourceFile);
113
+ return found;
114
+ }
115
+ /**
116
+ * Analyse an agent file for complexity classification.
117
+ */
118
+ function analyseAgentFile(sourceFile) {
119
+ let agentName = null;
120
+ let hasSchema = false;
121
+ let hasSetup = false;
122
+ let hasShutdown = false;
123
+ let hasOnEvent = false;
124
+ let hasModuleLevelCode = false;
125
+ let hasConfigAccess = false;
126
+ let hasAppAccess = false;
127
+ const ctxServices = new Set();
128
+ // Track what's at module level vs inside createAgent
129
+ let insideCreateAgent = false;
130
+ function visitCreateAgentConfig(node) {
131
+ // Inside the config object of createAgent
132
+ if (ts.isPropertyAssignment(node) && ts.isIdentifier(node.name)) {
133
+ const propName = node.name.text;
134
+ if (propName === 'schema')
135
+ hasSchema = true;
136
+ if (propName === 'setup')
137
+ hasSetup = true;
138
+ if (propName === 'shutdown')
139
+ hasShutdown = true;
140
+ if (propName === 'on' ||
141
+ propName === 'onStarted' ||
142
+ propName === 'onCompleted' ||
143
+ propName === 'onErrored') {
144
+ hasOnEvent = true;
145
+ }
146
+ }
147
+ // Look for ctx.kv, ctx.vector, etc. inside handler
148
+ if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.name)) {
149
+ const serviceName = node.name.text;
150
+ if (SERVICE_NAMES.includes(serviceName)) {
151
+ // Check if accessing on a parameter-like identifier (ctx, context, c)
152
+ if (ts.isIdentifier(node.expression)) {
153
+ const objName = node.expression.text;
154
+ if (['ctx', 'context', 'c'].includes(objName)) {
155
+ ctxServices.add(serviceName);
156
+ }
157
+ }
158
+ }
159
+ // Check for ctx.config access
160
+ if (node.name.text === 'config' && ts.isIdentifier(node.expression)) {
161
+ if (['ctx', 'context'].includes(node.expression.text)) {
162
+ hasConfigAccess = true;
163
+ }
164
+ }
165
+ // Check for ctx.app access
166
+ if (node.name.text === 'app' && ts.isIdentifier(node.expression)) {
167
+ if (['ctx', 'context'].includes(node.expression.text)) {
168
+ hasAppAccess = true;
169
+ }
170
+ }
171
+ }
172
+ ts.forEachChild(node, visitCreateAgentConfig);
173
+ }
174
+ function visit(node) {
175
+ // Detect createAgent() call
176
+ if (ts.isCallExpression(node) &&
177
+ ts.isIdentifier(node.expression) &&
178
+ node.expression.text === 'createAgent') {
179
+ // First arg is the name
180
+ if (node.arguments[0] && ts.isStringLiteral(node.arguments[0])) {
181
+ agentName = node.arguments[0].text;
182
+ }
183
+ // Second arg is the config
184
+ if (node.arguments[1]) {
185
+ insideCreateAgent = true;
186
+ visitCreateAgentConfig(node.arguments[1]);
187
+ insideCreateAgent = false;
188
+ }
189
+ return; // Don't recurse into createAgent children again
190
+ }
191
+ // Check for module-level statements that aren't just imports/exports/type declarations
192
+ if (!insideCreateAgent && isModuleLevelCode(node, sourceFile)) {
193
+ hasModuleLevelCode = true;
194
+ }
195
+ ts.forEachChild(node, visit);
196
+ }
197
+ visit(sourceFile);
198
+ // Classify complexity
199
+ const complexityReasons = [];
200
+ if (hasSetup)
201
+ complexityReasons.push('has setup() lifecycle hook');
202
+ if (hasShutdown)
203
+ complexityReasons.push('has shutdown() lifecycle hook');
204
+ if (hasOnEvent)
205
+ complexityReasons.push('has event listeners');
206
+ if (hasConfigAccess)
207
+ complexityReasons.push('accesses ctx.config (from setup)');
208
+ if (hasAppAccess)
209
+ complexityReasons.push('accesses ctx.app (app state)');
210
+ if (hasModuleLevelCode)
211
+ complexityReasons.push('has module-level code beyond imports/exports');
212
+ const complexity = complexityReasons.length > 0 ? 'complex' : 'simple';
213
+ return {
214
+ name: agentName,
215
+ complexity,
216
+ complexityReason: complexityReasons.length > 0 ? complexityReasons.join('; ') : undefined,
217
+ hasSchema,
218
+ ctxServices: [...ctxServices],
219
+ };
220
+ }
221
+ /**
222
+ * Check if a node is "module-level code" (not an import, export, type, or interface).
223
+ */
224
+ function isModuleLevelCode(node, sourceFile) {
225
+ // Only check top-level statements
226
+ if (node.parent !== sourceFile)
227
+ return false;
228
+ // End-of-file token is not code
229
+ if (node.kind === ts.SyntaxKind.EndOfFileToken)
230
+ return false;
231
+ // Imports are fine
232
+ if (ts.isImportDeclaration(node))
233
+ return false;
234
+ // Type-only declarations are fine
235
+ if (ts.isTypeAliasDeclaration(node))
236
+ return false;
237
+ if (ts.isInterfaceDeclaration(node))
238
+ return false;
239
+ // Export default createAgent(...) is fine
240
+ if (ts.isExportAssignment(node))
241
+ return false;
242
+ // Export declarations (re-exports) are fine
243
+ if (ts.isExportDeclaration(node))
244
+ return false;
245
+ // Variable statements
246
+ if (ts.isVariableStatement(node)) {
247
+ // `const` declarations are fine — they're just data, schemas, or config.
248
+ // They'll be preserved alongside the extracted function.
249
+ if (node.declarationList.flags & ts.NodeFlags.Const ||
250
+ node.declarationList.flags & ts.NodeFlags.Using) {
251
+ return false;
252
+ }
253
+ // `let`/`var` with createAgent() is fine
254
+ const declarations = node.declarationList.declarations;
255
+ if (declarations.length === 1) {
256
+ const decl = declarations[0];
257
+ const init = decl?.initializer;
258
+ if (init &&
259
+ ts.isCallExpression(init) &&
260
+ ts.isIdentifier(init.expression) &&
261
+ init.expression.text === 'createAgent') {
262
+ return false;
263
+ }
264
+ if (init &&
265
+ ts.isAwaitExpression(init) &&
266
+ ts.isCallExpression(init.expression) &&
267
+ ts.isIdentifier(init.expression.expression) &&
268
+ init.expression.expression.text === 'createAgent') {
269
+ return false;
270
+ }
271
+ }
272
+ // `let`/`var` with mutable state → complex
273
+ return true;
274
+ }
275
+ // Enum declarations are fine
276
+ if (ts.isEnumDeclaration(node))
277
+ return false;
278
+ // Function declarations are fine — they're just helper functions
279
+ // that will be preserved alongside the extracted agent function.
280
+ if (ts.isFunctionDeclaration(node))
281
+ return false;
282
+ // Expression statements that are just createAgent() exports are fine
283
+ if (ts.isExpressionStatement(node)) {
284
+ const expr = node.expression;
285
+ if (ts.isCallExpression(expr) &&
286
+ ts.isIdentifier(expr.expression) &&
287
+ expr.expression.text === 'createAgent') {
288
+ return false;
289
+ }
290
+ }
291
+ // Everything else is module-level code that indicates complexity
292
+ return true;
293
+ }
294
+ /**
295
+ * Scan a source file for service usage patterns: c.var.kv, c.var.vector, etc.
296
+ */
297
+ function scanServiceUsageInRouteFile(sourceFile) {
298
+ const services = new Set();
299
+ function visit(node) {
300
+ // Match c.var.kv, c.var.vector, etc.
301
+ if (ts.isPropertyAccessExpression(node) &&
302
+ ts.isIdentifier(node.name) &&
303
+ SERVICE_NAMES.includes(node.name.text)) {
304
+ // Check it's *.var.service
305
+ if (ts.isPropertyAccessExpression(node.expression) &&
306
+ ts.isIdentifier(node.expression.name) &&
307
+ node.expression.name.text === 'var') {
308
+ services.add(node.name.text);
309
+ }
310
+ }
311
+ ts.forEachChild(node, visit);
312
+ }
313
+ visit(sourceFile);
314
+ return [...services];
315
+ }
316
+ // ---------------------------------------------------------------------------
317
+ // Main detector
318
+ // ---------------------------------------------------------------------------
319
+ export async function detectV3(projectDir) {
320
+ const absDir = resolve(projectDir);
321
+ const findings = [];
322
+ const result = {
323
+ projectDir: absDir,
324
+ findings,
325
+ hasRuntimeDep: false,
326
+ hasCreateApp: false,
327
+ createAppProps: [],
328
+ agentFiles: [],
329
+ hasAgentBarrel: false,
330
+ serviceUsages: [],
331
+ allServicesUsed: [],
332
+ hasFrontend: false,
333
+ hasAgentuityConfig: false,
334
+ hasViteConfig: false,
335
+ outdatedPackages: [],
336
+ hasReactPackage: false,
337
+ hasFrontendPackage: false,
338
+ };
339
+ // ── 1. package.json analysis ────────────────────────────────────────────
340
+ const packageJsonPath = join(absDir, 'package.json');
341
+ if (existsSync(packageJsonPath)) {
342
+ try {
343
+ const packageJson = JSON.parse(await Bun.file(packageJsonPath).text());
344
+ for (const section of ['dependencies', 'devDependencies']) {
345
+ const deps = packageJson[section];
346
+ if (!deps || typeof deps !== 'object')
347
+ continue;
348
+ for (const [name, version] of Object.entries(deps)) {
349
+ if (name === '@agentuity/runtime') {
350
+ result.hasRuntimeDep = true;
351
+ result.runtimeVersion = String(version);
352
+ }
353
+ if (name === '@agentuity/react') {
354
+ result.hasReactPackage = true;
355
+ }
356
+ if (name === '@agentuity/frontend') {
357
+ result.hasFrontendPackage = true;
358
+ }
359
+ // Check for outdated packages
360
+ if (name.startsWith('@agentuity/')) {
361
+ const versionStr = String(version);
362
+ const needsUpdate = versionStr === 'latest' ||
363
+ versionStr === '*' ||
364
+ versionStr.startsWith('workspace:') ||
365
+ /^[~^]?[12]\./.test(versionStr);
366
+ if (needsUpdate) {
367
+ result.outdatedPackages.push({
368
+ name,
369
+ currentVersion: versionStr,
370
+ section,
371
+ });
372
+ }
373
+ }
374
+ }
375
+ }
376
+ }
377
+ catch {
378
+ // Ignore parse errors
379
+ }
380
+ }
381
+ if (result.hasRuntimeDep) {
382
+ findings.push({
383
+ id: 'v3-runtime-dep',
384
+ severity: 'auto',
385
+ message: `@agentuity/runtime@${result.runtimeVersion} found — will be removed`,
386
+ file: 'package.json',
387
+ hint: 'v3 is framework-agnostic. @agentuity/runtime is replaced by:\n' +
388
+ ' • hono — the web framework\n' +
389
+ ' • @agentuity/hono — middleware for telemetry + services\n' +
390
+ ' • Individual service packages (@agentuity/keyvalue, etc.)',
391
+ });
392
+ }
393
+ if (result.outdatedPackages.length > 0) {
394
+ const packageList = result.outdatedPackages
395
+ .map((p) => `${p.name}@${p.currentVersion}`)
396
+ .join(', ');
397
+ findings.push({
398
+ id: 'v3-outdated-packages',
399
+ severity: 'auto',
400
+ message: `Outdated @agentuity/* packages: ${packageList}`,
401
+ file: 'package.json',
402
+ hint: 'All @agentuity/* packages will be updated to their v3 versions.',
403
+ });
404
+ }
405
+ // ── 2. app.ts / entry point ─────────────────────────────────────────────
406
+ const appTsPath = join(absDir, 'app.ts');
407
+ if (existsSync(appTsPath)) {
408
+ result.appTsPath = appTsPath;
409
+ const sourceFile = await parseTs(appTsPath);
410
+ result.hasCreateApp = hasCreateAppImport(sourceFile);
411
+ if (result.hasCreateApp) {
412
+ result.createAppProps = getCreateAppProps(sourceFile);
413
+ findings.push({
414
+ id: 'v3-createapp',
415
+ severity: 'auto',
416
+ message: 'app.ts uses createApp() from @agentuity/runtime',
417
+ file: 'app.ts',
418
+ hint: 'Will be rewritten to a plain Hono app at src/index.ts:\n' +
419
+ '\n' +
420
+ " import { Hono } from 'hono';\n" +
421
+ " import { agentuity } from '@agentuity/hono';\n" +
422
+ '\n' +
423
+ ' const app = new Hono();\n' +
424
+ " app.use('*', agentuity());\n" +
425
+ '\n' +
426
+ ' export default app;',
427
+ });
428
+ // Check for cors config
429
+ if (result.createAppProps.includes('cors')) {
430
+ findings.push({
431
+ id: 'v3-cors-config',
432
+ severity: 'guided',
433
+ message: 'createApp() has cors configuration',
434
+ file: 'app.ts',
435
+ hint: 'CORS config will be migrated to hono/cors middleware.\n' +
436
+ "Note: Agentuity-specific options like 'sameOrigin' are not available\n" +
437
+ 'in hono/cors. You may need to configure allowedOrigins manually.',
438
+ });
439
+ }
440
+ // Check for agents reference
441
+ if (result.createAppProps.includes('agents')) {
442
+ findings.push({
443
+ id: 'v3-agents-in-createapp',
444
+ severity: 'auto',
445
+ message: 'createApp() passes agents array — concept removed in v3',
446
+ file: 'app.ts',
447
+ hint: 'Agents are converted to plain functions. The agents import will be removed.',
448
+ });
449
+ }
450
+ }
451
+ // Also scan app.ts for service usage
452
+ const appServices = scanServiceUsageInRouteFile(sourceFile);
453
+ if (appServices.length > 0) {
454
+ result.serviceUsages.push({
455
+ path: appTsPath,
456
+ relativePath: 'app.ts',
457
+ services: appServices,
458
+ accessPattern: 'c.var',
459
+ });
460
+ }
461
+ }
462
+ // ── 3. Agent files ──────────────────────────────────────────────────────
463
+ const agentDir = join(absDir, 'src', 'agent');
464
+ if (existsSync(agentDir)) {
465
+ for (const file of walkFiles(agentDir, ['.ts', '.tsx'])) {
466
+ const base = file.split('/').pop() ?? '';
467
+ // Skip index.ts barrel
468
+ if (base === 'index.ts' || base === 'index.tsx')
469
+ continue;
470
+ const sourceFile = await parseTs(file);
471
+ const src = await Bun.file(file).text();
472
+ // Check if this file uses createAgent
473
+ if (!src.includes('createAgent'))
474
+ continue;
475
+ const analysis = analyseAgentFile(sourceFile);
476
+ if (!analysis.name)
477
+ continue;
478
+ const relPath = rel(absDir, file);
479
+ const agentFile = {
480
+ path: file,
481
+ relativePath: relPath,
482
+ name: analysis.name,
483
+ complexity: analysis.complexity,
484
+ complexityReason: analysis.complexityReason,
485
+ hasSchema: analysis.hasSchema,
486
+ ctxServices: analysis.ctxServices,
487
+ };
488
+ result.agentFiles.push(agentFile);
489
+ if (analysis.complexity === 'simple') {
490
+ findings.push({
491
+ id: `v3-agent-simple:${relPath}`,
492
+ severity: 'auto',
493
+ message: `Agent "${analysis.name}" is simple — will be converted to plain function`,
494
+ file: relPath,
495
+ hint: 'The createAgent() wrapper will be removed. The handler becomes a plain\n' +
496
+ 'exported async function.' +
497
+ (analysis.hasSchema
498
+ ? ' Schema validation will be preserved in the function.'
499
+ : ''),
500
+ });
501
+ }
502
+ else {
503
+ findings.push({
504
+ id: `v3-agent-complex:${relPath}`,
505
+ severity: 'manual',
506
+ message: `Agent "${analysis.name}" is complex — requires manual migration`,
507
+ file: relPath,
508
+ hint: `Complexity: ${analysis.complexityReason}\n` +
509
+ '\n' +
510
+ 'This agent uses features beyond a simple handler and cannot be\n' +
511
+ 'automatically converted. You need to:\n' +
512
+ ' 1. Extract the handler into a plain async function\n' +
513
+ ' 2. Move setup logic to module-level initialization\n' +
514
+ ' 3. Replace ctx.config/ctx.app with direct imports\n' +
515
+ ' 4. Remove event listeners (use your own event patterns)',
516
+ });
517
+ }
518
+ // Track service usage from agents
519
+ if (analysis.ctxServices.length > 0) {
520
+ result.serviceUsages.push({
521
+ path: file,
522
+ relativePath: relPath,
523
+ services: analysis.ctxServices,
524
+ accessPattern: 'ctx',
525
+ });
526
+ }
527
+ }
528
+ }
529
+ // Agent barrel
530
+ const agentBarrelPath = join(absDir, 'src', 'agent', 'index.ts');
531
+ result.hasAgentBarrel = existsSync(agentBarrelPath);
532
+ if (result.hasAgentBarrel) {
533
+ findings.push({
534
+ id: 'v3-agent-barrel',
535
+ severity: 'auto',
536
+ message: 'src/agent/index.ts barrel — will be removed',
537
+ file: 'src/agent/index.ts',
538
+ hint: 'The agents barrel exported an array of agents for createApp().\n' +
539
+ 'In v3, there is no agents concept — functions are imported directly\n' +
540
+ 'where needed.',
541
+ });
542
+ }
543
+ // ── 4. Route files — service usage ──────────────────────────────────────
544
+ const apiDir = join(absDir, 'src', 'api');
545
+ if (existsSync(apiDir)) {
546
+ for (const file of walkFiles(apiDir, ['.ts', '.tsx'])) {
547
+ const sourceFile = await parseTs(file);
548
+ const services = scanServiceUsageInRouteFile(sourceFile);
549
+ if (services.length > 0) {
550
+ const relPath = rel(absDir, file);
551
+ result.serviceUsages.push({
552
+ path: file,
553
+ relativePath: relPath,
554
+ services,
555
+ accessPattern: 'c.var',
556
+ });
557
+ }
558
+ }
559
+ }
560
+ // Also scan src/ root-level TS files
561
+ const srcDir = join(absDir, 'src');
562
+ if (existsSync(srcDir)) {
563
+ for (const entry of readdirSync(srcDir, { withFileTypes: true })) {
564
+ if (entry.isFile() &&
565
+ (entry.name.endsWith('.ts') || entry.name.endsWith('.tsx')) &&
566
+ entry.name !== 'services.ts' // Don't scan our own generated file
567
+ ) {
568
+ const file = join(srcDir, entry.name);
569
+ const sourceFile = await parseTs(file);
570
+ const services = scanServiceUsageInRouteFile(sourceFile);
571
+ if (services.length > 0) {
572
+ const relPath = rel(absDir, file);
573
+ result.serviceUsages.push({
574
+ path: file,
575
+ relativePath: relPath,
576
+ services,
577
+ accessPattern: 'c.var',
578
+ });
579
+ }
580
+ }
581
+ }
582
+ }
583
+ // Compute all services used
584
+ const allServices = new Set();
585
+ for (const usage of result.serviceUsages) {
586
+ for (const svc of usage.services) {
587
+ allServices.add(svc);
588
+ }
589
+ }
590
+ result.allServicesUsed = [...allServices].sort();
591
+ if (result.serviceUsages.length > 0) {
592
+ const ctxUsages = result.serviceUsages.filter((u) => u.accessPattern === 'ctx');
593
+ const cVarUsages = result.serviceUsages.filter((u) => u.accessPattern === 'c.var');
594
+ if (ctxUsages.length > 0) {
595
+ findings.push({
596
+ id: 'v3-service-agent-ctx',
597
+ severity: 'guided',
598
+ message: `${ctxUsages.length} file(s) access services via ctx.* (agent context)`,
599
+ hint: 'Service access through agent context (ctx.kv, ctx.vector, etc.) is removed\n' +
600
+ 'in v3. A shared src/services.ts will be generated with singleton clients.\n' +
601
+ '\n' +
602
+ " import { kv } from './services'; // or '../services'\n" +
603
+ " const data = await kv.get('namespace', 'key');",
604
+ });
605
+ }
606
+ if (cVarUsages.length > 0) {
607
+ findings.push({
608
+ id: 'v3-service-route-ctx',
609
+ severity: 'guided',
610
+ message: `${cVarUsages.length} file(s) access services via c.var.* (Hono context)`,
611
+ hint: 'Service access through Hono context variables (c.var.kv, etc.) is replaced\n' +
612
+ 'by direct imports from a shared services module.\n' +
613
+ '\n' +
614
+ " import { kv } from './services';\n" +
615
+ " const data = await kv.get('namespace', 'key');",
616
+ });
617
+ }
618
+ }
619
+ // ── 5. Frontend / SPA ───────────────────────────────────────────────────
620
+ const webDir = join(absDir, 'src', 'web');
621
+ result.hasFrontend = existsSync(webDir);
622
+ if (result.hasFrontend) {
623
+ findings.push({
624
+ id: 'v3-spa-detected',
625
+ severity: 'guided',
626
+ message: 'src/web/ frontend detected',
627
+ hint: 'In v3, SPAs are served by the Agentuity buildpack. For local development,\n' +
628
+ "use your framework's dev server (e.g., vite dev). For production, the\n" +
629
+ 'buildpack detects static assets and injects a file server automatically.\n' +
630
+ '\n' +
631
+ 'If you want to serve static files from your Hono app directly:\n' +
632
+ '\n' +
633
+ " import { serveStatic } from 'hono/bun';\n" +
634
+ " app.use('/assets/*', serveStatic({ root: './src/web/dist' }));",
635
+ });
636
+ }
637
+ // ── 6. agentuity.config.ts ──────────────────────────────────────────────
638
+ const configPath = join(absDir, 'agentuity.config.ts');
639
+ result.hasAgentuityConfig = existsSync(configPath);
640
+ if (result.hasAgentuityConfig) {
641
+ findings.push({
642
+ id: 'v3-config-file',
643
+ severity: 'auto',
644
+ message: 'agentuity.config.ts exists — will be deleted',
645
+ file: 'agentuity.config.ts',
646
+ hint: 'v3 uses standard framework configuration (vite.config.ts, etc.).\n' +
647
+ 'The agentuity.config.ts file is no longer used.',
648
+ });
649
+ }
650
+ // Vite config
651
+ const viteConfigPath = join(absDir, 'vite.config.ts');
652
+ result.hasViteConfig = existsSync(viteConfigPath);
653
+ // ── 7. @agentuity/react deprecation ─────────────────────────────────────
654
+ if (result.hasReactPackage) {
655
+ findings.push({
656
+ id: 'v3-react-deprecated',
657
+ severity: 'manual',
658
+ message: '@agentuity/react is deprecated and will be removed',
659
+ file: 'package.json',
660
+ hint: '@agentuity/react is fully deprecated in v3. Replace with:\n' +
661
+ '\n' +
662
+ ' • AgentuityProvider/useAuth → Your auth provider directly (better-auth, Clerk, etc.)\n' +
663
+ ' • useAPI/createAPIClient → Hono RPC client (hc from hono/client)\n' +
664
+ ' • useAnalytics → getAnalytics() from @agentuity/analytics\n' +
665
+ ' • useWebRTCCall → WebRTCManager from @agentuity/frontend\n' +
666
+ '\n' +
667
+ 'Remove @agentuity/react from package.json after migrating all imports.',
668
+ });
669
+ }
670
+ // Sort: auto first, guided second, manual last
671
+ const order = { auto: 0, guided: 1, manual: 2 };
672
+ findings.sort((a, b) => order[a.severity] - order[b.severity]);
673
+ return result;
674
+ }
675
+ //# sourceMappingURL=detect-v3.js.map