@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,312 @@
1
+ // =============================================================================
2
+ // Unified Codegen Tool
3
+ // =============================================================================
4
+ //
5
+ // ONE tool to replace 20+ broken CodeDNA/Kappa tools.
6
+ // Natural language in → generated code out.
7
+ //
8
+ import { parseNaturalLanguage, parsePageDescription, parseComponentDescription } from './natural-parser.js';
9
+ import { parseFormDescription } from './form-parser.js';
10
+ import { generateDrizzleSchema } from '../codedna/kappa-drizzle-generator.js';
11
+ import { generateZodSchemas } from '../codedna/kappa-zod-generator.js';
12
+ import { generateTypeScriptTypes } from '../codedna/kappa-types-generator.js';
13
+ import { generateAPI } from '../codedna/kappa-api-generator.js';
14
+ import { generateEntityTests } from '../codedna/kappa-vitest-generator.js';
15
+ import { generateRadixComponents } from '../codedna/kappa-radix-generator.js';
16
+ import { generatePages } from '../codedna/kappa-page-generator.js';
17
+ import { generateForms } from '../codedna/kappa-form-generator.js';
18
+ export function detectStack(packageJson) {
19
+ const deps = {
20
+ ...(packageJson?.dependencies || {}),
21
+ ...(packageJson?.devDependencies || {}),
22
+ };
23
+ const stack = {
24
+ orm: null,
25
+ api: null,
26
+ ui: null,
27
+ db: null,
28
+ framework: null,
29
+ lang: 'typescript' in deps || '@types/node' in deps ? 'typescript' : 'javascript',
30
+ };
31
+ // Detect ORM
32
+ if ('drizzle-orm' in deps)
33
+ stack.orm = 'drizzle';
34
+ else if ('@prisma/client' in deps)
35
+ stack.orm = 'prisma';
36
+ else if ('typeorm' in deps)
37
+ stack.orm = 'typeorm';
38
+ else if ('mongoose' in deps)
39
+ stack.orm = 'mongoose';
40
+ // Detect API framework
41
+ if ('hono' in deps)
42
+ stack.api = 'hono';
43
+ else if ('express' in deps)
44
+ stack.api = 'express';
45
+ else if ('@trpc/server' in deps)
46
+ stack.api = 'trpc';
47
+ else if ('fastify' in deps)
48
+ stack.api = 'fastify';
49
+ // Detect UI
50
+ if ('@radix-ui/react-dialog' in deps || '@radix-ui/themes' in deps)
51
+ stack.ui = 'radix';
52
+ else if ('@mantine/core' in deps)
53
+ stack.ui = 'mantine';
54
+ else if ('@chakra-ui/react' in deps)
55
+ stack.ui = 'chakra';
56
+ // shadcn doesn't have a package, detected by tailwind + radix primitives
57
+ // Detect DB
58
+ if ('better-sqlite3' in deps || '@libsql/client' in deps)
59
+ stack.db = 'sqlite';
60
+ else if ('pg' in deps || '@neondatabase/serverless' in deps)
61
+ stack.db = 'postgres';
62
+ else if ('mysql2' in deps)
63
+ stack.db = 'mysql';
64
+ else if ('mongodb' in deps || 'mongoose' in deps)
65
+ stack.db = 'mongodb';
66
+ // Detect Framework
67
+ if ('@tanstack/react-router' in deps || '@tanstack/start' in deps)
68
+ stack.framework = 'tanstack-start';
69
+ else if ('next' in deps)
70
+ stack.framework = 'nextjs';
71
+ return stack;
72
+ }
73
+ // =============================================================================
74
+ // Main Codegen Function
75
+ // =============================================================================
76
+ export function codegen(input, packageJson) {
77
+ // Parse all types from natural language
78
+ const parsed = parseNaturalLanguage(input.describe);
79
+ const formParsed = parseFormDescription(input.describe);
80
+ const componentParsed = parseComponentDescription(input.describe);
81
+ const pagesParsed = parsePageDescription(input.describe);
82
+ // Check if we parsed anything useful
83
+ const hasEntities = parsed.success && parsed.entities.length > 0;
84
+ const hasForms = formParsed.success && formParsed.forms.length > 0;
85
+ const hasComponents = componentParsed.success && componentParsed.components.length > 0;
86
+ const hasPages = pagesParsed.length > 0;
87
+ if (!hasEntities && !hasForms && !hasComponents && !hasPages) {
88
+ return {
89
+ success: false,
90
+ files: [],
91
+ entities: [],
92
+ summary: 'Failed to parse description',
93
+ errors: ['Could not understand the description. Try:\n Entity: "User with email, password, role (admin/user)"\n Form: "Login form with email, password"\n Component: "Button with label, onClick, variant (primary/secondary)"\n Page: "Dashboard page at /dashboard, requires auth"'],
94
+ };
95
+ }
96
+ // Detect or use provided stack
97
+ const detected = detectStack(packageJson);
98
+ const stack = {
99
+ orm: input.stack?.orm || detected.orm || 'drizzle',
100
+ api: input.stack?.api || detected.api || 'hono',
101
+ db: input.stack?.db || detected.db || 'sqlite',
102
+ ui: input.stack?.ui || detected.ui || 'radix',
103
+ framework: detected.framework || 'tanstack-start',
104
+ lang: detected.lang,
105
+ };
106
+ const files = [];
107
+ const toGenerate = input.generate || ['schema', 'types', 'validators'];
108
+ const generateAll = toGenerate.includes('all');
109
+ // Generate schema (Drizzle)
110
+ if (generateAll || toGenerate.includes('schema')) {
111
+ if (stack.orm === 'drizzle') {
112
+ // Map 'postgres' to 'postgresql' for Drizzle dialect
113
+ const dialectMap = {
114
+ sqlite: 'sqlite',
115
+ postgres: 'postgresql',
116
+ postgresql: 'postgresql',
117
+ mysql: 'mysql',
118
+ };
119
+ const drizzle = generateDrizzleSchema(parsed.entities, {
120
+ dialect: dialectMap[stack.db] || 'sqlite',
121
+ });
122
+ files.push({
123
+ path: 'src/db/schema.ts',
124
+ content: drizzle.schema,
125
+ description: 'Drizzle ORM schema',
126
+ });
127
+ if (drizzle.relations) {
128
+ files.push({
129
+ path: 'src/db/relations.ts',
130
+ content: drizzle.relations,
131
+ description: 'Drizzle relations',
132
+ });
133
+ }
134
+ if (drizzle.types) {
135
+ files.push({
136
+ path: 'src/db/types.ts',
137
+ content: drizzle.types,
138
+ description: 'Drizzle inferred types',
139
+ });
140
+ }
141
+ }
142
+ // TODO: Add prisma, typeorm, mongoose
143
+ }
144
+ // Generate TypeScript types
145
+ if (generateAll || toGenerate.includes('types')) {
146
+ const types = generateTypeScriptTypes(parsed.entities);
147
+ files.push({
148
+ path: 'src/types/entities.ts',
149
+ content: types.types,
150
+ description: 'TypeScript entity types',
151
+ });
152
+ }
153
+ // Generate Zod validators
154
+ if (generateAll || toGenerate.includes('validators')) {
155
+ const zod = generateZodSchemas(parsed.entities);
156
+ files.push({
157
+ path: 'src/validators/schemas.ts',
158
+ content: zod.schemas,
159
+ description: 'Zod validation schemas',
160
+ });
161
+ }
162
+ // Generate API routes
163
+ if (generateAll || toGenerate.includes('api')) {
164
+ const apis = createAPIsFromEntities(parsed.entities);
165
+ const api = generateAPI(apis, { framework: stack.api });
166
+ files.push({
167
+ path: 'src/routes/index.ts',
168
+ content: api.routes,
169
+ description: `${stack.api} API routes`,
170
+ });
171
+ if (api.validation) {
172
+ files.push({
173
+ path: 'src/routes/validation.ts',
174
+ content: api.validation,
175
+ description: 'Route validation schemas',
176
+ });
177
+ }
178
+ }
179
+ // Generate Vitest tests
180
+ if (generateAll || toGenerate.includes('tests')) {
181
+ for (const entity of parsed.entities) {
182
+ const tests = generateEntityTests(entity, {
183
+ factories: true,
184
+ mocks: true,
185
+ provenance: true,
186
+ });
187
+ const entityName = entity.name.toLowerCase();
188
+ files.push({
189
+ path: `__tests__/${entityName}.test.ts`,
190
+ content: tests.tests,
191
+ description: `Vitest unit tests for ${entity.name}`,
192
+ });
193
+ if (tests.factories) {
194
+ files.push({
195
+ path: `__tests__/${entityName}.factory.ts`,
196
+ content: tests.factories,
197
+ description: `Test factory for ${entity.name}`,
198
+ });
199
+ }
200
+ if (tests.mocks) {
201
+ files.push({
202
+ path: `__tests__/${entityName}.mock.ts`,
203
+ content: tests.mocks,
204
+ description: `Mock repository for ${entity.name}`,
205
+ });
206
+ }
207
+ }
208
+ }
209
+ // Generate React components (Radix UI)
210
+ if (generateAll || toGenerate.includes('components')) {
211
+ if (hasComponents) {
212
+ const radixResult = generateRadixComponents(componentParsed.components, {
213
+ typescript: true,
214
+ provenance: true,
215
+ a11yDocs: true,
216
+ });
217
+ for (const component of radixResult.components) {
218
+ files.push({
219
+ path: component.path,
220
+ content: component.content,
221
+ description: `Radix UI ${component.primitive} component`,
222
+ });
223
+ }
224
+ }
225
+ // TODO: Add shadcn, mantine generation when available
226
+ }
227
+ // Generate Pages (TanStack Start / Next.js)
228
+ if (generateAll || toGenerate.includes('pages')) {
229
+ if (hasPages) {
230
+ const framework = stack.framework || 'tanstack-start';
231
+ const pagesResult = generatePages(pagesParsed, {
232
+ framework: framework,
233
+ typescript: stack.lang === 'typescript',
234
+ provenance: true,
235
+ });
236
+ for (const page of pagesResult.pages) {
237
+ files.push({
238
+ path: page.path,
239
+ content: page.content,
240
+ description: `${framework} page: ${page.path}`,
241
+ });
242
+ if (page.loader) {
243
+ files.push({
244
+ path: page.loader.path,
245
+ content: page.loader.content,
246
+ description: `Page loader for ${page.path}`,
247
+ });
248
+ }
249
+ }
250
+ if (pagesResult.layouts) {
251
+ for (const layout of pagesResult.layouts) {
252
+ files.push({
253
+ path: layout.path,
254
+ content: layout.content,
255
+ description: `Shared layout component`,
256
+ });
257
+ }
258
+ }
259
+ }
260
+ }
261
+ // Generate forms (React Hook Form + Zod)
262
+ if (generateAll || toGenerate.includes('forms')) {
263
+ if (hasForms) {
264
+ const ui = stack.ui === 'shadcn' || stack.ui === 'radix' ? 'shadcn' : 'plain';
265
+ const formsResult = generateForms(formParsed.forms, {
266
+ ui,
267
+ typescript: true,
268
+ provenance: true,
269
+ zod: true,
270
+ });
271
+ for (const form of formsResult.forms) {
272
+ files.push({
273
+ path: form.path,
274
+ content: form.content,
275
+ description: `React Hook Form component`,
276
+ });
277
+ }
278
+ }
279
+ }
280
+ const entityNames = parsed.entities.map(e => e.name);
281
+ const formNames = formParsed.forms.map(f => f.name);
282
+ const allNames = [...entityNames, ...formNames.map(n => `${n} form`)];
283
+ return {
284
+ success: true,
285
+ files,
286
+ entities: entityNames,
287
+ summary: `Generated ${files.length} files for ${allNames.join(', ')}`,
288
+ };
289
+ }
290
+ /**
291
+ * Create API blocks from entities (CRUD for each)
292
+ */
293
+ function createAPIsFromEntities(entities) {
294
+ return entities.map(entity => ({
295
+ kind: 'APIBlock',
296
+ name: `${entity.name}API`,
297
+ operations: [],
298
+ crud: [{
299
+ kind: 'CRUDShorthand',
300
+ entity: entity.name,
301
+ actions: [
302
+ { action: 'list', effects: ['DB'] },
303
+ { action: 'create', effects: ['DB'] },
304
+ { action: 'read', effects: ['DB'] },
305
+ { action: 'update', effects: ['DB'] },
306
+ { action: 'delete', effects: ['DB'] },
307
+ ],
308
+ loc: { startLine: 1, startColumn: 1, endLine: 1, endColumn: 1, startOffset: 0, endOffset: 0 },
309
+ }],
310
+ loc: { startLine: 1, startColumn: 1, endLine: 1, endColumn: 1, startOffset: 0, endOffset: 0 },
311
+ }));
312
+ }
@@ -0,0 +1,24 @@
1
+ export type InferredType = {
2
+ type: string;
3
+ modifiers: string[];
4
+ };
5
+ /**
6
+ * Infer field type from field name
7
+ */
8
+ export declare function inferFieldType(fieldName: string): InferredType;
9
+ /**
10
+ * Parse explicit type annotation if present
11
+ * Examples: "role:enum(admin,user)", "age:int", "bio:text:optional"
12
+ */
13
+ export declare function parseExplicitType(spec: string): {
14
+ name: string;
15
+ type: InferredType;
16
+ } | null;
17
+ /**
18
+ * Parse enum from parenthetical notation
19
+ * Example: "role (admin, user, guest)" → { name: 'role', values: ['admin', 'user', 'guest'] }
20
+ */
21
+ export declare function parseInlineEnum(text: string): {
22
+ name: string;
23
+ values: string[];
24
+ } | null;
@@ -0,0 +1,101 @@
1
+ // =============================================================================
2
+ // Smart Field Inference
3
+ // =============================================================================
4
+ //
5
+ // Maps common field names to appropriate types automatically.
6
+ // No DSL syntax needed - just say "User with email, password, createdAt"
7
+ //
8
+ // Field name patterns → type mappings
9
+ const FIELD_PATTERNS = [
10
+ // Identity - always uuid primary
11
+ [/^id$/i, { type: 'uuid', modifiers: ['primary', 'auto'] }],
12
+ [/^uuid$/i, { type: 'uuid', modifiers: ['primary', 'auto'] }],
13
+ // Email fields
14
+ [/email/i, { type: 'email', modifiers: ['unique'] }],
15
+ // Password - always hashed
16
+ [/^password$/i, { type: 'string', modifiers: ['hashed'] }],
17
+ [/^passwordHash$/i, { type: 'string', modifiers: [] }],
18
+ // URLs
19
+ [/^url$|Url$|^link$|Link$|^href$/i, { type: 'url', modifiers: [] }],
20
+ [/^avatar$|^image$|^photo$|^picture$/i, { type: 'url', modifiers: ['optional'] }],
21
+ // Phone
22
+ [/phone|mobile|cell/i, { type: 'phone', modifiers: ['optional'] }],
23
+ // Slugs
24
+ [/^slug$/i, { type: 'slug', modifiers: ['unique'] }],
25
+ // Timestamps - auto-managed
26
+ [/^createdAt$|^created_at$/i, { type: 'timestamp', modifiers: ['auto'] }],
27
+ [/^updatedAt$|^updated_at$/i, { type: 'timestamp', modifiers: ['auto'] }],
28
+ [/^deletedAt$|^deleted_at$/i, { type: 'timestamp', modifiers: ['optional'] }],
29
+ [/^publishedAt$|^published_at$/i, { type: 'timestamp', modifiers: ['optional'] }],
30
+ [/At$|_at$/i, { type: 'timestamp', modifiers: ['optional'] }],
31
+ // Booleans - common patterns
32
+ [/^is[A-Z]|^has[A-Z]|^can[A-Z]|^should[A-Z]/i, { type: 'bool', modifiers: [] }],
33
+ [/^active$|^enabled$|^published$|^verified$|^approved$/i, { type: 'bool', modifiers: [] }],
34
+ // Counts/numbers
35
+ [/count$|Count$|^total|Total$|^num|Num$|^amount|Amount$/i, { type: 'int', modifiers: [] }],
36
+ [/^age$|^order$|^priority$|^position$|^rank$/i, { type: 'int', modifiers: [] }],
37
+ [/^price$|^cost$|^amount$|^balance$|^rate$/i, { type: 'float', modifiers: [] }],
38
+ // Long text content
39
+ [/^content$|^body$|^text$|^description$|^summary$/i, { type: 'markdown', modifiers: [] }],
40
+ [/^bio$|^about$|^notes$/i, { type: 'markdown', modifiers: ['optional'] }],
41
+ // JSON data
42
+ [/^data$|^meta$|^metadata$|^settings$|^config$|^options$/i, { type: 'json', modifiers: ['optional'] }],
43
+ ];
44
+ // Default: string
45
+ const DEFAULT_TYPE = { type: 'string', modifiers: [] };
46
+ /**
47
+ * Infer field type from field name
48
+ */
49
+ export function inferFieldType(fieldName) {
50
+ for (const [pattern, inferredType] of FIELD_PATTERNS) {
51
+ if (pattern.test(fieldName)) {
52
+ return inferredType;
53
+ }
54
+ }
55
+ return DEFAULT_TYPE;
56
+ }
57
+ /**
58
+ * Parse explicit type annotation if present
59
+ * Examples: "role:enum(admin,user)", "age:int", "bio:text:optional"
60
+ */
61
+ export function parseExplicitType(spec) {
62
+ // Clean up: remove trailing punctuation and whitespace
63
+ const cleaned = spec.trim().replace(/[.,;!?]+$/, '');
64
+ // Match: fieldName:type or fieldName:type(args) or fieldName:type:modifier
65
+ const match = cleaned.match(/^(\w+):(\w+)(?:\(([^)]+)\))?(?::(\w+))?$/);
66
+ if (!match)
67
+ return null;
68
+ const [, name, type, args, modifier] = match;
69
+ const modifiers = modifier ? [modifier] : [];
70
+ // Handle enum
71
+ if (type === 'enum' && args) {
72
+ return {
73
+ name,
74
+ type: {
75
+ type: `enum:${args}`,
76
+ modifiers,
77
+ },
78
+ };
79
+ }
80
+ return {
81
+ name,
82
+ type: {
83
+ type,
84
+ modifiers,
85
+ },
86
+ };
87
+ }
88
+ /**
89
+ * Parse enum from parenthetical notation
90
+ * Example: "role (admin, user, guest)" → { name: 'role', values: ['admin', 'user', 'guest'] }
91
+ */
92
+ export function parseInlineEnum(text) {
93
+ const match = text.match(/^(\w+)\s*\(([^)]+)\)$/);
94
+ if (!match)
95
+ return null;
96
+ const [, name, valuesStr] = match;
97
+ const values = valuesStr.split(/[,\/|]/).map(v => v.trim()).filter(Boolean);
98
+ if (values.length < 2)
99
+ return null; // Not an enum, probably a function
100
+ return { name, values };
101
+ }
@@ -0,0 +1,13 @@
1
+ import type { FormBlock } from '../codedna/kappa-ast.js';
2
+ export interface ParsedForm {
3
+ form: FormBlock;
4
+ }
5
+ export interface FormParseResult {
6
+ success: boolean;
7
+ forms: FormBlock[];
8
+ errors: string[];
9
+ }
10
+ /**
11
+ * Parse natural language form description into FormBlock
12
+ */
13
+ export declare function parseFormDescription(description: string): FormParseResult;
@@ -0,0 +1,186 @@
1
+ // =============================================================================
2
+ // Natural Language → Form AST Parser
3
+ // =============================================================================
4
+ //
5
+ // Converts natural language form descriptions into Kappa FormBlock AST.
6
+ // Examples:
7
+ // "Login form with email, password"
8
+ // "User registration with name, email, password, confirm password"
9
+ // "Contact form with name, email, subject, message"
10
+ //
11
+ const defaultLoc = {
12
+ startLine: 1,
13
+ startColumn: 1,
14
+ endLine: 1,
15
+ endColumn: 1,
16
+ startOffset: 0,
17
+ endOffset: 0,
18
+ };
19
+ /**
20
+ * Parse natural language form description into FormBlock
21
+ */
22
+ export function parseFormDescription(description) {
23
+ const errors = [];
24
+ const forms = [];
25
+ try {
26
+ // Extract form name (e.g., "Login form", "User registration", "Contact form")
27
+ const nameMatch = description.match(/^([A-Za-z\s]+?)(?:\s+form|\s+with)/i);
28
+ if (!nameMatch) {
29
+ throw new Error('Could not extract form name. Try: "Login form with email, password"');
30
+ }
31
+ const formName = nameMatch[1].trim()
32
+ .split(/\s+/)
33
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1))
34
+ .join('');
35
+ // Extract fields (after "with")
36
+ const fieldsMatch = description.match(/with\s+(.+)$/i);
37
+ if (!fieldsMatch) {
38
+ throw new Error('Could not find fields. Use format: "FormName with field1, field2"');
39
+ }
40
+ const fieldsStr = fieldsMatch[1];
41
+ const fields = parseFormFields(fieldsStr);
42
+ // Determine submit button text based on form type
43
+ const submitText = inferSubmitText(formName, description);
44
+ const form = {
45
+ kind: 'FormBlock',
46
+ name: formName,
47
+ fields,
48
+ submit: {
49
+ kind: 'FormSubmit',
50
+ action: 'submit',
51
+ button: submitText,
52
+ loading: `${submitText.replace(/^(.*?)(?:ing)?$/, '$1ing')}...`,
53
+ onSuccess: 'console.log("Form submitted successfully")',
54
+ onError: 'console.error("Form submission failed", error)',
55
+ loc: defaultLoc,
56
+ },
57
+ layout: [],
58
+ loc: defaultLoc,
59
+ };
60
+ forms.push(form);
61
+ }
62
+ catch (e) {
63
+ errors.push(e instanceof Error ? e.message : String(e));
64
+ }
65
+ return {
66
+ success: errors.length === 0 && forms.length > 0,
67
+ forms,
68
+ errors,
69
+ };
70
+ }
71
+ /**
72
+ * Parse comma-separated form fields
73
+ */
74
+ function parseFormFields(fieldsStr) {
75
+ const fields = [];
76
+ const parts = fieldsStr.split(',').map(s => s.trim());
77
+ for (const part of parts) {
78
+ if (!part)
79
+ continue;
80
+ const field = createFormField(part);
81
+ fields.push(field);
82
+ }
83
+ return fields;
84
+ }
85
+ /**
86
+ * Create a FormField from a field name
87
+ */
88
+ function createFormField(fieldName) {
89
+ const normalizedName = fieldName.replace(/\s+/g, '');
90
+ const label = fieldName
91
+ .split(/\s+/)
92
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1))
93
+ .join(' ');
94
+ // Infer field type from name
95
+ const { type, required, placeholder } = inferFormFieldType(normalizedName);
96
+ return {
97
+ kind: 'FormField',
98
+ name: normalizedName,
99
+ label,
100
+ type,
101
+ required,
102
+ placeholder,
103
+ options: type === 'select' ? [] : undefined,
104
+ loc: defaultLoc,
105
+ };
106
+ }
107
+ /**
108
+ * Infer form field type from field name
109
+ */
110
+ function inferFormFieldType(fieldName) {
111
+ const lower = fieldName.toLowerCase();
112
+ // Email
113
+ if (lower.includes('email')) {
114
+ return { type: 'email', required: true, placeholder: 'your@email.com' };
115
+ }
116
+ // Password
117
+ if (lower.includes('password')) {
118
+ return { type: 'password', required: true, placeholder: '••••••••' };
119
+ }
120
+ // Number/Age
121
+ if (lower.includes('age') || lower.includes('count') || lower.includes('quantity')) {
122
+ return { type: 'number', required: false };
123
+ }
124
+ // Date
125
+ if (lower.includes('date') || lower.includes('birthday') || lower.includes('dob')) {
126
+ return { type: 'date', required: false };
127
+ }
128
+ // Time
129
+ if (lower.includes('time')) {
130
+ return { type: 'time', required: false };
131
+ }
132
+ // Textarea (for longer content)
133
+ if (lower.includes('message') ||
134
+ lower.includes('description') ||
135
+ lower.includes('bio') ||
136
+ lower.includes('comment') ||
137
+ lower.includes('content')) {
138
+ return { type: 'textarea', required: false, placeholder: 'Enter your message...' };
139
+ }
140
+ // Checkbox
141
+ if (lower.includes('agree') ||
142
+ lower.includes('accept') ||
143
+ lower.includes('consent') ||
144
+ lower.includes('subscribe')) {
145
+ return { type: 'checkbox', required: false };
146
+ }
147
+ // Phone
148
+ if (lower.includes('phone') || lower.includes('mobile')) {
149
+ return { type: 'text', required: false, placeholder: '+1 (555) 123-4567' };
150
+ }
151
+ // URL
152
+ if (lower.includes('website') || lower.includes('url')) {
153
+ return { type: 'text', required: false, placeholder: 'https://example.com' };
154
+ }
155
+ // Default to text
156
+ return { type: 'text', required: false };
157
+ }
158
+ /**
159
+ * Infer submit button text from form name/description
160
+ */
161
+ function inferSubmitText(formName, description) {
162
+ const lower = formName.toLowerCase();
163
+ const descLower = description.toLowerCase();
164
+ if (lower.includes('login') || lower.includes('signin')) {
165
+ return 'Sign In';
166
+ }
167
+ if (lower.includes('register') || lower.includes('signup') || descLower.includes('registration')) {
168
+ return 'Create Account';
169
+ }
170
+ if (lower.includes('contact')) {
171
+ return 'Send Message';
172
+ }
173
+ if (lower.includes('search')) {
174
+ return 'Search';
175
+ }
176
+ if (lower.includes('update') || lower.includes('edit')) {
177
+ return 'Update';
178
+ }
179
+ if (lower.includes('delete')) {
180
+ return 'Delete';
181
+ }
182
+ if (lower.includes('reset')) {
183
+ return 'Reset Password';
184
+ }
185
+ return 'Submit';
186
+ }
@@ -0,0 +1,2 @@
1
+ export { cloudCodegen, CloudCodegenClient, defaultClient as cloudClient, } from './cloud-client.js';
2
+ export type { CloudCodegenConfig, CodegenInput, CodegenResult, GeneratorInfo, CategoryInfo, FrameworkInfo, } from './cloud-client.js';
@@ -0,0 +1,4 @@
1
+ // =============================================================================
2
+ // Codegen Module - Cloud Only
3
+ // =============================================================================
4
+ export { cloudCodegen, CloudCodegenClient, defaultClient as cloudClient, } from './cloud-client.js';