@claudetools/tools 0.9.0 → 0.9.2

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 (85) hide show
  1. package/dist/cli.js +9 -1
  2. package/dist/codedna/__tests__/examples/mongoose-example.d.ts +6 -0
  3. package/dist/codedna/__tests__/examples/mongoose-example.js +163 -0
  4. package/dist/codedna/__tests__/fixtures/typeorm-production-test.d.ts +1 -0
  5. package/dist/codedna/__tests__/fixtures/typeorm-production-test.js +231 -0
  6. package/dist/codedna/__tests__/fixtures/typeorm-test.d.ts +1 -0
  7. package/dist/codedna/__tests__/fixtures/typeorm-test.js +124 -0
  8. package/dist/codedna/__tests__/laravel-output-review.d.ts +1 -0
  9. package/dist/codedna/__tests__/laravel-output-review.js +249 -0
  10. package/dist/codedna/__tests__/mongoose-output-test.d.ts +1 -0
  11. package/dist/codedna/__tests__/mongoose-output-test.js +178 -0
  12. package/dist/codedna/examples/radix-example.d.ts +2 -0
  13. package/dist/codedna/examples/radix-example.js +259 -0
  14. package/dist/codedna/index.d.ts +5 -3
  15. package/dist/codedna/index.js +6 -3
  16. package/dist/codedna/kappa-ast.d.ts +143 -5
  17. package/dist/codedna/kappa-drizzle-generator.js +8 -5
  18. package/dist/codedna/kappa-gofiber-generator.d.ts +65 -0
  19. package/dist/codedna/kappa-gofiber-generator.js +587 -0
  20. package/dist/codedna/kappa-laravel-generator.d.ts +68 -0
  21. package/dist/codedna/kappa-laravel-generator.js +741 -0
  22. package/dist/codedna/kappa-lexer.d.ts +44 -0
  23. package/dist/codedna/kappa-lexer.js +124 -0
  24. package/dist/codedna/kappa-mantine-generator.d.ts +65 -0
  25. package/dist/codedna/kappa-mantine-generator.js +518 -0
  26. package/dist/codedna/kappa-mongoose-generator.d.ts +44 -0
  27. package/dist/codedna/kappa-mongoose-generator.js +442 -0
  28. package/dist/codedna/kappa-parser.d.ts +43 -1
  29. package/dist/codedna/kappa-parser.js +601 -0
  30. package/dist/codedna/kappa-radix-generator.d.ts +61 -0
  31. package/dist/codedna/kappa-radix-generator.js +566 -0
  32. package/dist/codedna/kappa-typeorm-generator.d.ts +59 -0
  33. package/dist/codedna/kappa-typeorm-generator.js +723 -0
  34. package/dist/codedna/kappa-vitest-generator.d.ts +85 -0
  35. package/dist/codedna/kappa-vitest-generator.js +739 -0
  36. package/dist/codedna/parser.js +26 -1
  37. package/dist/codegen/cloud-client.d.ts +160 -0
  38. package/dist/codegen/cloud-client.js +195 -0
  39. package/dist/codegen/codegen-tool.d.ts +35 -0
  40. package/dist/codegen/codegen-tool.js +312 -0
  41. package/dist/codegen/field-inference.d.ts +24 -0
  42. package/dist/codegen/field-inference.js +101 -0
  43. package/dist/codegen/form-parser.d.ts +13 -0
  44. package/dist/codegen/form-parser.js +186 -0
  45. package/dist/codegen/index.d.ts +2 -0
  46. package/dist/codegen/index.js +4 -0
  47. package/dist/codegen/natural-parser.d.ts +50 -0
  48. package/dist/codegen/natural-parser.js +769 -0
  49. package/dist/handlers/codedna-handlers.d.ts +1 -1
  50. package/dist/handlers/codegen-handlers.d.ts +20 -0
  51. package/dist/handlers/codegen-handlers.js +60 -0
  52. package/dist/handlers/kappa-handlers.d.ts +97 -0
  53. package/dist/handlers/kappa-handlers.js +408 -0
  54. package/dist/handlers/tool-handlers.js +124 -221
  55. package/dist/helpers/api-client.js +48 -3
  56. package/dist/helpers/compact-formatter.d.ts +9 -2
  57. package/dist/helpers/compact-formatter.js +26 -2
  58. package/dist/helpers/config.d.ts +7 -2
  59. package/dist/helpers/config.js +25 -10
  60. package/dist/helpers/session-validation.d.ts +1 -1
  61. package/dist/helpers/session-validation.js +2 -4
  62. package/dist/helpers/tasks.d.ts +21 -0
  63. package/dist/helpers/tasks.js +52 -0
  64. package/dist/helpers/workers.d.ts +1 -1
  65. package/dist/helpers/workers.js +19 -19
  66. package/dist/setup.d.ts +1 -0
  67. package/dist/setup.js +228 -3
  68. package/dist/templates/claude-md.d.ts +1 -1
  69. package/dist/templates/claude-md.js +37 -152
  70. package/dist/templates/orchestrator-prompt.d.ts +2 -2
  71. package/dist/templates/orchestrator-prompt.js +31 -38
  72. package/dist/templates/self-critique.d.ts +50 -0
  73. package/dist/templates/self-critique.js +209 -0
  74. package/dist/templates/worker-prompt.d.ts +3 -3
  75. package/dist/templates/worker-prompt.js +18 -18
  76. package/dist/tools.js +77 -413
  77. package/docs/codedna/generator-testing-summary.md +205 -0
  78. package/docs/codedna/radix-ui-generator.md +478 -0
  79. package/docs/kappa-gofiber-generator.md +274 -0
  80. package/docs/kappa-laravel-fixes.md +172 -0
  81. package/docs/kappa-mongoose-generator.md +322 -0
  82. package/docs/kappa-vitest-generator.md +337 -0
  83. package/package.json +1 -1
  84. package/dist/context/deduplication.test.d.ts +0 -6
  85. package/dist/context/deduplication.test.js +0 -84
@@ -0,0 +1,741 @@
1
+ // =============================================================================
2
+ // Kappa v2.5 PHP/Laravel Generator
3
+ // =============================================================================
4
+ //
5
+ // Generates Laravel code from Kappa specifications.
6
+ // Supports: Controllers, Models, Migrations, Form Requests.
7
+ //
8
+ // =============================================================================
9
+ // Type Mappings
10
+ // =============================================================================
11
+ const PRIMITIVE_PHP_MAP = {
12
+ string: 'string',
13
+ int: 'int',
14
+ float: 'float',
15
+ bool: 'bool',
16
+ timestamp: '\\Carbon\\Carbon',
17
+ date: '\\Carbon\\Carbon',
18
+ time: '\\Carbon\\Carbon',
19
+ duration: 'int',
20
+ uuid: 'string',
21
+ email: 'string',
22
+ url: 'string',
23
+ phone: 'string',
24
+ slug: 'string',
25
+ markdown: 'string',
26
+ json: 'array',
27
+ };
28
+ const PRIMITIVE_MIGRATION_MAP = {
29
+ string: 'string',
30
+ int: 'integer',
31
+ float: 'decimal',
32
+ bool: 'boolean',
33
+ timestamp: 'timestamp',
34
+ date: 'date',
35
+ time: 'time',
36
+ duration: 'integer',
37
+ uuid: 'uuid',
38
+ email: 'string',
39
+ url: 'string',
40
+ phone: 'string',
41
+ slug: 'string',
42
+ markdown: 'text',
43
+ json: 'json',
44
+ };
45
+ const PRIMITIVE_VALIDATION_MAP = {
46
+ string: ['string'],
47
+ int: ['integer'],
48
+ float: ['numeric'],
49
+ bool: ['boolean'],
50
+ timestamp: ['date'],
51
+ date: ['date'],
52
+ time: ['date_format:H:i:s'],
53
+ duration: ['integer'],
54
+ uuid: ['uuid'],
55
+ email: ['email'],
56
+ url: ['url'],
57
+ phone: ['string'],
58
+ slug: ['string', 'regex:/^[a-z0-9-]+$/'],
59
+ markdown: ['string'],
60
+ json: ['array'],
61
+ };
62
+ const CRUD_METHOD_MAP = {
63
+ create: 'post',
64
+ read: 'get',
65
+ update: 'patch',
66
+ delete: 'delete',
67
+ list: 'get',
68
+ };
69
+ const CRUD_ACTION_MAP = {
70
+ create: 'store',
71
+ read: 'show',
72
+ update: 'update',
73
+ delete: 'destroy',
74
+ list: 'index',
75
+ };
76
+ // =============================================================================
77
+ // Generator Class
78
+ // =============================================================================
79
+ export class KappaLaravelGenerator {
80
+ modelNamespace;
81
+ controllerNamespace;
82
+ provenance;
83
+ migrations;
84
+ formRequests;
85
+ apiResources;
86
+ basePath;
87
+ entities;
88
+ constructor(options = {}) {
89
+ this.modelNamespace = options.modelNamespace ?? 'App\\Models';
90
+ this.controllerNamespace = options.controllerNamespace ?? 'App\\Http\\Controllers';
91
+ this.provenance = options.provenance ?? true;
92
+ this.migrations = options.migrations ?? true;
93
+ this.formRequests = options.formRequests ?? true;
94
+ this.apiResources = options.apiResources ?? true;
95
+ this.basePath = options.basePath ?? '/api';
96
+ this.entities = new Map();
97
+ }
98
+ // ===========================================================================
99
+ // Main Generation Methods
100
+ // ===========================================================================
101
+ generateFromAPIs(apis) {
102
+ const routes = this.extractRoutes(apis);
103
+ const controllers = {};
104
+ const formRequests = {};
105
+ // Group routes by controller
106
+ const controllerGroups = this.groupByController(routes);
107
+ for (const [controllerName, controllerRoutes] of Object.entries(controllerGroups)) {
108
+ controllers[controllerName] = this.generateController(controllerName, controllerRoutes);
109
+ if (this.formRequests) {
110
+ for (const route of controllerRoutes) {
111
+ if (route.method === 'post' || route.method === 'put' || route.method === 'patch') {
112
+ const requestName = `${this.toPascalCase(route.operationName)}Request`;
113
+ formRequests[requestName] = this.generateFormRequest(requestName, route);
114
+ }
115
+ }
116
+ }
117
+ }
118
+ return {
119
+ routes: this.generateRoutes(apis),
120
+ controllers,
121
+ models: {},
122
+ formRequests: this.formRequests ? formRequests : undefined,
123
+ };
124
+ }
125
+ generateFromEntities(entities) {
126
+ const models = {};
127
+ const migrations = {};
128
+ const resources = {};
129
+ for (const entity of entities) {
130
+ models[entity.name] = this.generateModel(entity);
131
+ if (this.migrations) {
132
+ const migrationName = `create_${this.toSnakeCase(entity.name)}s_table`;
133
+ migrations[migrationName] = this.generateMigration(entity);
134
+ }
135
+ if (this.apiResources) {
136
+ resources[`${entity.name}Resource`] = this.generateResource(entity);
137
+ }
138
+ }
139
+ return {
140
+ routes: '',
141
+ controllers: {},
142
+ models,
143
+ migrations: this.migrations ? migrations : undefined,
144
+ resources: this.apiResources ? resources : undefined,
145
+ };
146
+ }
147
+ generateAll(apis, entities) {
148
+ // Store entities for validation rule generation
149
+ for (const entity of entities) {
150
+ this.entities.set(entity.name, entity);
151
+ }
152
+ const apiResult = this.generateFromAPIs(apis);
153
+ const entityResult = this.generateFromEntities(entities);
154
+ return {
155
+ routes: apiResult.routes,
156
+ controllers: apiResult.controllers,
157
+ models: entityResult.models,
158
+ migrations: entityResult.migrations,
159
+ formRequests: apiResult.formRequests,
160
+ resources: entityResult.resources,
161
+ };
162
+ }
163
+ // ===========================================================================
164
+ // Route Generation
165
+ // ===========================================================================
166
+ generateRoutes(apis) {
167
+ const lines = [];
168
+ const routes = this.extractRoutes(apis);
169
+ // Header
170
+ lines.push('<?php');
171
+ lines.push('');
172
+ if (this.provenance) {
173
+ lines.push('// Generated by Kappa v2.5 CodeDNA');
174
+ lines.push('// Framework: Laravel');
175
+ lines.push('');
176
+ }
177
+ lines.push("use Illuminate\\Support\\Facades\\Route;");
178
+ // Collect controller imports
179
+ const controllers = new Set();
180
+ for (const route of routes) {
181
+ controllers.add(`use ${this.controllerNamespace}\\${route.controller};`);
182
+ }
183
+ for (const controller of controllers) {
184
+ lines.push(controller);
185
+ }
186
+ lines.push('');
187
+ // API routes
188
+ lines.push("Route::prefix('api')->group(function () {");
189
+ // Group by auth requirement
190
+ const authRoutes = routes.filter((r) => r.requiresAuth);
191
+ const publicRoutes = routes.filter((r) => !r.requiresAuth);
192
+ if (publicRoutes.length > 0) {
193
+ lines.push(' // Public routes');
194
+ for (const route of publicRoutes) {
195
+ lines.push(this.generateRouteDefinition(route));
196
+ }
197
+ lines.push('');
198
+ }
199
+ if (authRoutes.length > 0) {
200
+ lines.push(" Route::middleware(['auth:sanctum'])->group(function () {");
201
+ for (const route of authRoutes) {
202
+ lines.push(' ' + this.generateRouteDefinition(route));
203
+ }
204
+ lines.push(' });');
205
+ }
206
+ lines.push('});');
207
+ lines.push('');
208
+ return lines.join('\n');
209
+ }
210
+ generateRouteDefinition(route) {
211
+ const middleware = route.middleware.length > 0
212
+ ? `->middleware([${route.middleware.map((m) => `'${m}'`).join(', ')}])`
213
+ : '';
214
+ return ` Route::${route.method}('${route.path}', [${route.controller}::class, '${route.action}'])${middleware};`;
215
+ }
216
+ extractRoutes(apis) {
217
+ const routes = [];
218
+ for (const api of apis) {
219
+ const apiPath = `/${this.toKebabCase(api.name)}`;
220
+ const controllerName = `${api.name}Controller`;
221
+ for (const crud of api.crud || []) {
222
+ routes.push(...this.crudToRoutes(crud, apiPath, controllerName));
223
+ }
224
+ if (api.operations) {
225
+ for (const op of api.operations) {
226
+ routes.push(this.operationToRoute(op, apiPath, controllerName));
227
+ }
228
+ }
229
+ }
230
+ return routes;
231
+ }
232
+ operationToRoute(op, apiPath, controller) {
233
+ const method = this.inferMethod(op.name);
234
+ const path = `${apiPath}/${this.toKebabCase(op.name)}`;
235
+ const middleware = [];
236
+ if (op.rateLimit) {
237
+ middleware.push(`throttle:${op.rateLimit}`);
238
+ }
239
+ return {
240
+ method,
241
+ path,
242
+ controller,
243
+ action: this.toCamelCase(op.name),
244
+ operationName: op.name,
245
+ effects: op.effects,
246
+ requiresAuth: op.effects.includes('Auth'),
247
+ middleware,
248
+ };
249
+ }
250
+ crudToRoutes(crud, apiPath, controller) {
251
+ const routes = [];
252
+ const entityPath = `${apiPath}/${this.toKebabCase(crud.entity)}s`;
253
+ for (const action of crud.actions) {
254
+ const method = CRUD_METHOD_MAP[action.action] ?? 'get';
255
+ const laravelAction = CRUD_ACTION_MAP[action.action] ?? action.action;
256
+ const pathSuffix = action.action === 'read' || action.action === 'update' || action.action === 'delete'
257
+ ? '/{id}'
258
+ : '';
259
+ routes.push({
260
+ method,
261
+ path: `${entityPath}${pathSuffix}`,
262
+ controller,
263
+ action: laravelAction,
264
+ operationName: `${action.action}${crud.entity}`,
265
+ effects: action.effects,
266
+ requiresAuth: action.effects.includes('Auth'),
267
+ middleware: [],
268
+ });
269
+ }
270
+ return routes;
271
+ }
272
+ inferMethod(name) {
273
+ const lower = name.toLowerCase();
274
+ if (lower.startsWith('get') ||
275
+ lower.startsWith('list') ||
276
+ lower.startsWith('find') ||
277
+ lower.startsWith('fetch')) {
278
+ return 'get';
279
+ }
280
+ if (lower.startsWith('create') || lower.startsWith('add') || lower.startsWith('register')) {
281
+ return 'post';
282
+ }
283
+ if (lower.startsWith('update') || lower.startsWith('edit')) {
284
+ return 'patch';
285
+ }
286
+ if (lower.startsWith('delete') || lower.startsWith('remove')) {
287
+ return 'delete';
288
+ }
289
+ return 'post';
290
+ }
291
+ groupByController(routes) {
292
+ const groups = {};
293
+ for (const route of routes) {
294
+ if (!groups[route.controller]) {
295
+ groups[route.controller] = [];
296
+ }
297
+ groups[route.controller].push(route);
298
+ }
299
+ return groups;
300
+ }
301
+ // ===========================================================================
302
+ // Controller Generation
303
+ // ===========================================================================
304
+ generateController(name, routes) {
305
+ const lines = [];
306
+ lines.push('<?php');
307
+ lines.push('');
308
+ lines.push(`namespace ${this.controllerNamespace};`);
309
+ lines.push('');
310
+ if (this.provenance) {
311
+ lines.push('// Generated by Kappa v2.5 CodeDNA');
312
+ lines.push('');
313
+ }
314
+ lines.push('use Illuminate\\Http\\JsonResponse;');
315
+ lines.push('use Illuminate\\Http\\Request;');
316
+ lines.push('');
317
+ lines.push(`class ${name} extends Controller`);
318
+ lines.push('{');
319
+ for (const route of routes) {
320
+ lines.push(this.generateControllerMethod(route));
321
+ lines.push('');
322
+ }
323
+ lines.push('}');
324
+ lines.push('');
325
+ return lines.join('\n');
326
+ }
327
+ generateControllerMethod(route) {
328
+ const lines = [];
329
+ const paramType = route.method === 'post' || route.method === 'put' || route.method === 'patch'
330
+ ? `${this.toPascalCase(route.operationName)}Request`
331
+ : 'Request';
332
+ lines.push(' /**');
333
+ lines.push(` * ${route.operationName}`);
334
+ if (route.effects.length > 0) {
335
+ lines.push(` * Effects: ${route.effects.join(', ')}`);
336
+ }
337
+ lines.push(' */');
338
+ lines.push(` public function ${route.action}(${paramType} $request): JsonResponse`);
339
+ lines.push(' {');
340
+ lines.push(` // TODO: Implement ${route.operationName}`);
341
+ lines.push(' return response()->json([');
342
+ lines.push(" 'message' => 'Not implemented',");
343
+ lines.push(' ], 501);');
344
+ lines.push(' }');
345
+ return lines.join('\n');
346
+ }
347
+ // ===========================================================================
348
+ // Model Generation
349
+ // ===========================================================================
350
+ generateModel(entity) {
351
+ const lines = [];
352
+ lines.push('<?php');
353
+ lines.push('');
354
+ lines.push(`namespace ${this.modelNamespace};`);
355
+ lines.push('');
356
+ if (this.provenance) {
357
+ lines.push('// Generated by Kappa v2.5 CodeDNA');
358
+ lines.push('');
359
+ }
360
+ lines.push('use Illuminate\\Database\\Eloquent\\Factories\\HasFactory;');
361
+ lines.push('use Illuminate\\Database\\Eloquent\\Model;');
362
+ // Check for UUID
363
+ const hasUUID = entity.fields.some((f) => f.type.kind === 'primitive' && f.type.type === 'uuid');
364
+ if (hasUUID) {
365
+ lines.push('use Illuminate\\Database\\Eloquent\\Concerns\\HasUuids;');
366
+ }
367
+ lines.push('');
368
+ lines.push(`class ${entity.name} extends Model`);
369
+ lines.push('{');
370
+ lines.push(' use HasFactory;');
371
+ if (hasUUID) {
372
+ lines.push(' use HasUuids;');
373
+ }
374
+ lines.push('');
375
+ // Table name
376
+ lines.push(` protected $table = '${this.toSnakeCase(entity.name)}s';`);
377
+ lines.push('');
378
+ // Fillable
379
+ const fillable = entity.fields
380
+ .filter((f) => !f.modifiers.includes('primary') && !f.modifiers.includes('auto'))
381
+ .map((f) => `'${this.toSnakeCase(f.name)}'`);
382
+ lines.push(' protected $fillable = [');
383
+ lines.push(` ${fillable.join(',\n ')},`);
384
+ lines.push(' ];');
385
+ lines.push('');
386
+ // Hidden
387
+ const hidden = entity.fields
388
+ .filter((f) => f.modifiers.includes('hashed') || f.name.toLowerCase().includes('password'))
389
+ .map((f) => `'${this.toSnakeCase(f.name)}'`);
390
+ if (hidden.length > 0) {
391
+ lines.push(' protected $hidden = [');
392
+ lines.push(` ${hidden.join(',\n ')},`);
393
+ lines.push(' ];');
394
+ lines.push('');
395
+ }
396
+ // Casts
397
+ const casts = this.generateCasts(entity.fields);
398
+ if (Object.keys(casts).length > 0) {
399
+ lines.push(' protected $casts = [');
400
+ for (const [field, type] of Object.entries(casts)) {
401
+ lines.push(` '${field}' => '${type}',`);
402
+ }
403
+ lines.push(' ];');
404
+ }
405
+ lines.push('}');
406
+ lines.push('');
407
+ return lines.join('\n');
408
+ }
409
+ generateCasts(fields) {
410
+ const casts = {};
411
+ for (const field of fields) {
412
+ const fieldName = this.toSnakeCase(field.name);
413
+ if (field.type.kind === 'primitive') {
414
+ switch (field.type.type) {
415
+ case 'timestamp':
416
+ case 'date':
417
+ casts[fieldName] = 'datetime';
418
+ break;
419
+ case 'bool':
420
+ casts[fieldName] = 'boolean';
421
+ break;
422
+ case 'int':
423
+ casts[fieldName] = 'integer';
424
+ break;
425
+ case 'float':
426
+ casts[fieldName] = 'float';
427
+ break;
428
+ case 'json':
429
+ casts[fieldName] = 'array';
430
+ break;
431
+ }
432
+ }
433
+ else if (field.type.kind === 'array') {
434
+ casts[fieldName] = 'array';
435
+ }
436
+ // Handle hashed modifier for password-like fields
437
+ if (field.modifiers.includes('hashed')) {
438
+ casts[fieldName] = 'hashed';
439
+ }
440
+ }
441
+ return casts;
442
+ }
443
+ // ===========================================================================
444
+ // Migration Generation
445
+ // ===========================================================================
446
+ generateMigration(entity) {
447
+ const lines = [];
448
+ const tableName = `${this.toSnakeCase(entity.name)}s`;
449
+ lines.push('<?php');
450
+ lines.push('');
451
+ if (this.provenance) {
452
+ lines.push('// Generated by Kappa v2.5 CodeDNA');
453
+ lines.push('');
454
+ }
455
+ lines.push('use Illuminate\\Database\\Migrations\\Migration;');
456
+ lines.push('use Illuminate\\Database\\Schema\\Blueprint;');
457
+ lines.push('use Illuminate\\Support\\Facades\\Schema;');
458
+ lines.push('');
459
+ lines.push('return new class extends Migration');
460
+ lines.push('{');
461
+ lines.push(' public function up(): void');
462
+ lines.push(' {');
463
+ lines.push(` Schema::create('${tableName}', function (Blueprint $table) {`);
464
+ // Check for custom primary key
465
+ const hasPrimary = entity.fields.some((f) => f.modifiers.includes('primary'));
466
+ const hasUUID = entity.fields.some((f) => f.type.kind === 'primitive' && f.type.type === 'uuid' && f.modifiers.includes('primary'));
467
+ if (!hasPrimary) {
468
+ lines.push(' $table->id();');
469
+ }
470
+ for (const field of entity.fields) {
471
+ const migration = this.generateMigrationColumn(field);
472
+ if (migration) {
473
+ lines.push(` ${migration}`);
474
+ }
475
+ }
476
+ lines.push(' $table->timestamps();');
477
+ lines.push(' });');
478
+ lines.push(' }');
479
+ lines.push('');
480
+ lines.push(' public function down(): void');
481
+ lines.push(' {');
482
+ lines.push(` Schema::dropIfExists('${tableName}');`);
483
+ lines.push(' }');
484
+ lines.push('};');
485
+ lines.push('');
486
+ return lines.join('\n');
487
+ }
488
+ generateMigrationColumn(field) {
489
+ const columnName = this.toSnakeCase(field.name);
490
+ let type;
491
+ let modifiers = [];
492
+ if (field.type.kind === 'primitive') {
493
+ type = PRIMITIVE_MIGRATION_MAP[field.type.type] ?? 'string';
494
+ if (field.type.type === 'uuid' && field.modifiers.includes('primary')) {
495
+ return `$table->uuid('${columnName}')->primary();`;
496
+ }
497
+ }
498
+ else if (field.type.kind === 'enum') {
499
+ const values = field.type.values.map((v) => `'${v}'`).join(', ');
500
+ return `$table->enum('${columnName}', [${values}]);`;
501
+ }
502
+ else if (field.type.kind === 'array') {
503
+ type = 'json';
504
+ }
505
+ else if (field.type.kind === 'reference') {
506
+ // Foreign key - use foreignUuid if the referenced entity uses UUID
507
+ const refTable = `${this.toSnakeCase(field.type.entity)}s`;
508
+ // For now, assume UUID foreign keys (most common in modern Laravel)
509
+ // TODO: Could enhance to detect if referenced entity uses UUID or int
510
+ return `$table->foreignUuid('${columnName}')->constrained('${refTable}');`;
511
+ }
512
+ else {
513
+ return null;
514
+ }
515
+ // Apply modifiers
516
+ if (field.modifiers.includes('unique')) {
517
+ modifiers.push('unique()');
518
+ }
519
+ if (field.modifiers.includes('optional')) {
520
+ modifiers.push('nullable()');
521
+ }
522
+ if (field.defaultValue !== undefined) {
523
+ // Handle boolean literals correctly
524
+ if (field.type.kind === 'primitive' && field.type.type === 'bool') {
525
+ modifiers.push(`default(${field.defaultValue})`);
526
+ }
527
+ else if (typeof field.defaultValue === 'number') {
528
+ modifiers.push(`default(${field.defaultValue})`);
529
+ }
530
+ else {
531
+ modifiers.push(`default('${field.defaultValue}')`);
532
+ }
533
+ }
534
+ const modifierStr = modifiers.length > 0 ? '->' + modifiers.join('->') : '';
535
+ return `$table->${type}('${columnName}')${modifierStr};`;
536
+ }
537
+ // ===========================================================================
538
+ // Form Request Generation
539
+ // ===========================================================================
540
+ generateFormRequest(name, route) {
541
+ const lines = [];
542
+ lines.push('<?php');
543
+ lines.push('');
544
+ lines.push('namespace App\\Http\\Requests;');
545
+ lines.push('');
546
+ if (this.provenance) {
547
+ lines.push('// Generated by Kappa v2.5 CodeDNA');
548
+ lines.push('');
549
+ }
550
+ lines.push('use Illuminate\\Foundation\\Http\\FormRequest;');
551
+ lines.push('');
552
+ lines.push(`class ${name} extends FormRequest`);
553
+ lines.push('{');
554
+ lines.push(' public function authorize(): bool');
555
+ lines.push(' {');
556
+ lines.push(` return ${route.requiresAuth ? 'auth()->check()' : 'true'};`);
557
+ lines.push(' }');
558
+ lines.push('');
559
+ lines.push(' public function rules(): array');
560
+ lines.push(' {');
561
+ lines.push(' return [');
562
+ // Try to extract entity name from route to generate validation rules
563
+ const validationRules = this.generateValidationRules(route);
564
+ if (validationRules.length > 0) {
565
+ for (const rule of validationRules) {
566
+ lines.push(` ${rule}`);
567
+ }
568
+ }
569
+ else {
570
+ lines.push(' // TODO: Add validation rules');
571
+ }
572
+ lines.push(' ];');
573
+ lines.push(' }');
574
+ lines.push('}');
575
+ lines.push('');
576
+ return lines.join('\n');
577
+ }
578
+ generateValidationRules(route) {
579
+ const rules = [];
580
+ // Try to infer entity from route operation name
581
+ // e.g., "createPost" -> "Post", "updateUser" -> "User"
582
+ let entityName = null;
583
+ for (const [name] of this.entities) {
584
+ if (route.operationName.toLowerCase().includes(name.toLowerCase())) {
585
+ entityName = name;
586
+ break;
587
+ }
588
+ }
589
+ if (!entityName) {
590
+ return rules;
591
+ }
592
+ const entity = this.entities.get(entityName);
593
+ if (!entity) {
594
+ return rules;
595
+ }
596
+ const isUpdate = route.action === 'update' || route.operationName.toLowerCase().includes('update');
597
+ for (const field of entity.fields) {
598
+ // Skip primary keys and auto-generated fields
599
+ if (field.modifiers.includes('primary') || field.modifiers.includes('auto')) {
600
+ continue;
601
+ }
602
+ const fieldName = this.toSnakeCase(field.name);
603
+ const fieldRules = [];
604
+ // Required/optional
605
+ if (isUpdate) {
606
+ fieldRules.push('sometimes');
607
+ }
608
+ else if (!field.modifiers.includes('optional')) {
609
+ fieldRules.push('required');
610
+ }
611
+ else {
612
+ fieldRules.push('nullable');
613
+ }
614
+ // Type-specific validation
615
+ if (field.type.kind === 'primitive') {
616
+ const primitiveRules = PRIMITIVE_VALIDATION_MAP[field.type.type] || [];
617
+ fieldRules.push(...primitiveRules);
618
+ }
619
+ else if (field.type.kind === 'enum') {
620
+ fieldRules.push(`in:${field.type.values.join(',')}`);
621
+ }
622
+ else if (field.type.kind === 'reference') {
623
+ const refTable = `${this.toSnakeCase(field.type.entity)}s`;
624
+ fieldRules.push(`exists:${refTable},id`);
625
+ }
626
+ // Modifiers
627
+ if (field.modifiers.includes('unique')) {
628
+ const tableName = `${this.toSnakeCase(entityName)}s`;
629
+ if (isUpdate) {
630
+ // For updates, we need to exclude the current record from unique check
631
+ // This will be concatenated in the rules array, not interpolated here
632
+ rules.push(`'${fieldName}' => ['${fieldRules.join('\', \'')}', 'unique:${tableName},${fieldName},' . $this->route('id')],`);
633
+ continue; // Skip the normal rule addition at the end
634
+ }
635
+ else {
636
+ fieldRules.push(`unique:${tableName},${fieldName}`);
637
+ }
638
+ }
639
+ // Validators from field type range (if primitive with range constraint)
640
+ if (field.type.kind === 'primitive' && 'range' in field.type && field.type.range) {
641
+ const range = field.type.range;
642
+ if (range.min !== undefined) {
643
+ fieldRules.push(`min:${range.min}`);
644
+ }
645
+ if (range.max !== undefined) {
646
+ fieldRules.push(`max:${range.max}`);
647
+ }
648
+ }
649
+ rules.push(`'${fieldName}' => '${fieldRules.join('|')}',`);
650
+ }
651
+ return rules;
652
+ }
653
+ // ===========================================================================
654
+ // Resource Generation
655
+ // ===========================================================================
656
+ generateResource(entity) {
657
+ const lines = [];
658
+ lines.push('<?php');
659
+ lines.push('');
660
+ lines.push('namespace App\\Http\\Resources;');
661
+ lines.push('');
662
+ if (this.provenance) {
663
+ lines.push('// Generated by Kappa v2.5 CodeDNA');
664
+ lines.push('');
665
+ }
666
+ lines.push('use Illuminate\\Http\\Request;');
667
+ lines.push('use Illuminate\\Http\\Resources\\Json\\JsonResource;');
668
+ lines.push('');
669
+ lines.push(`class ${entity.name}Resource extends JsonResource`);
670
+ lines.push('{');
671
+ lines.push(' public function toArray(Request $request): array');
672
+ lines.push(' {');
673
+ lines.push(' return [');
674
+ const addedFields = new Set();
675
+ for (const field of entity.fields) {
676
+ const fieldName = this.toSnakeCase(field.name);
677
+ // Skip password fields in resources
678
+ if (field.modifiers.includes('hashed') || field.name.toLowerCase().includes('password')) {
679
+ continue;
680
+ }
681
+ // Skip auto timestamp fields (created_at, updated_at) - we'll add them at the end
682
+ if (field.modifiers.includes('auto') && (fieldName === 'created_at' || fieldName === 'updated_at')) {
683
+ continue;
684
+ }
685
+ lines.push(` '${fieldName}' => $this->${fieldName},`);
686
+ addedFields.add(fieldName);
687
+ }
688
+ // Add timestamps if not already added
689
+ if (!addedFields.has('created_at')) {
690
+ lines.push(" 'created_at' => $this->created_at,");
691
+ }
692
+ if (!addedFields.has('updated_at')) {
693
+ lines.push(" 'updated_at' => $this->updated_at,");
694
+ }
695
+ lines.push(' ];');
696
+ lines.push(' }');
697
+ lines.push('}');
698
+ lines.push('');
699
+ return lines.join('\n');
700
+ }
701
+ // ===========================================================================
702
+ // Utility Methods
703
+ // ===========================================================================
704
+ toPascalCase(str) {
705
+ return str
706
+ .replace(/[-_](.)/g, (_, c) => c.toUpperCase())
707
+ .replace(/^(.)/, (_, c) => c.toUpperCase());
708
+ }
709
+ toCamelCase(str) {
710
+ return str
711
+ .replace(/[-_](.)/g, (_, c) => c.toUpperCase())
712
+ .replace(/^(.)/, (_, c) => c.toLowerCase());
713
+ }
714
+ toKebabCase(str) {
715
+ return str
716
+ .replace(/([a-z])([A-Z])/g, '$1-$2')
717
+ .replace(/[_\s]+/g, '-')
718
+ .toLowerCase();
719
+ }
720
+ toSnakeCase(str) {
721
+ return str
722
+ .replace(/([a-z])([A-Z])/g, '$1_$2')
723
+ .replace(/[-\s]+/g, '_')
724
+ .toLowerCase();
725
+ }
726
+ }
727
+ // =============================================================================
728
+ // Convenience Functions
729
+ // =============================================================================
730
+ export function generateLaravelAPI(apis, options) {
731
+ const generator = new KappaLaravelGenerator(options);
732
+ return generator.generateFromAPIs(apis);
733
+ }
734
+ export function generateLaravelModels(entities, options) {
735
+ const generator = new KappaLaravelGenerator(options);
736
+ return generator.generateFromEntities(entities);
737
+ }
738
+ export function generateLaravel(apis, entities, options) {
739
+ const generator = new KappaLaravelGenerator(options);
740
+ return generator.generateAll(apis, entities);
741
+ }