mycontext-cli 4.2.6 → 4.2.7

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 (37) hide show
  1. package/README.md +531 -144
  2. package/dist/README.md +531 -144
  3. package/dist/cli.js +14 -5
  4. package/dist/cli.js.map +1 -1
  5. package/dist/commands/generate.d.ts.map +1 -1
  6. package/dist/commands/generate.js +17 -4
  7. package/dist/commands/generate.js.map +1 -1
  8. package/dist/commands/init-interactive.d.ts +127 -0
  9. package/dist/commands/init-interactive.d.ts.map +1 -0
  10. package/dist/commands/init-interactive.js +754 -0
  11. package/dist/commands/init-interactive.js.map +1 -0
  12. package/dist/doctor/rules/index.d.ts +3 -1
  13. package/dist/doctor/rules/index.d.ts.map +1 -1
  14. package/dist/doctor/rules/index.js +7 -1
  15. package/dist/doctor/rules/index.js.map +1 -1
  16. package/dist/doctor/rules/instantdb-rules.d.ts +3 -0
  17. package/dist/doctor/rules/instantdb-rules.d.ts.map +1 -0
  18. package/dist/doctor/rules/instantdb-rules.js +335 -0
  19. package/dist/doctor/rules/instantdb-rules.js.map +1 -0
  20. package/dist/doctor/rules/typescript-rules.d.ts +3 -0
  21. package/dist/doctor/rules/typescript-rules.d.ts.map +1 -0
  22. package/dist/doctor/rules/typescript-rules.js +177 -0
  23. package/dist/doctor/rules/typescript-rules.js.map +1 -0
  24. package/dist/package.json +1 -1
  25. package/dist/services/InferenceEngine.d.ts +41 -0
  26. package/dist/services/InferenceEngine.d.ts.map +1 -0
  27. package/dist/services/InferenceEngine.js +307 -0
  28. package/dist/services/InferenceEngine.js.map +1 -0
  29. package/dist/services/Planner.d.ts +77 -0
  30. package/dist/services/Planner.d.ts.map +1 -0
  31. package/dist/services/Planner.js +828 -0
  32. package/dist/services/Planner.js.map +1 -0
  33. package/dist/types/asl.d.ts +387 -0
  34. package/dist/types/asl.d.ts.map +1 -0
  35. package/dist/types/asl.js +139 -0
  36. package/dist/types/asl.js.map +1 -0
  37. package/package.json +1 -1
@@ -0,0 +1,828 @@
1
+ "use strict";
2
+ /**
3
+ * Planner Service
4
+ *
5
+ * The "Planner Layer" in the MyContext compiler pipeline.
6
+ * Responsible for:
7
+ * 1. Validating ASL for 100% completeness
8
+ * 2. Generating clarifying questions for gaps
9
+ * 3. Creating diff previews before generation
10
+ * 4. Task decomposition with confidence scoring (NEW)
11
+ * 5. Auto-inference for high-confidence tasks (NEW)
12
+ *
13
+ * The Planner ensures NO code is generated until the specification is perfect.
14
+ */
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.Planner = void 0;
17
+ const asl_1 = require("../types/asl");
18
+ const AICore_1 = require("../core/ai/AICore");
19
+ const InferenceEngine_1 = require("./InferenceEngine");
20
+ class Planner {
21
+ constructor() {
22
+ this.inferenceEngine = new InferenceEngine_1.InferenceEngine();
23
+ this.aiCore = AICore_1.AICore.getInstance({
24
+ fallbackEnabled: true,
25
+ workingDirectory: process.cwd(),
26
+ });
27
+ this.state = {
28
+ tasks: [],
29
+ completedTasks: [],
30
+ pendingTasks: [],
31
+ revealedContext: [],
32
+ confidenceScore: 0,
33
+ learningContext: {
34
+ corrections: [],
35
+ preferences: {},
36
+ patterns: [],
37
+ },
38
+ checkpoints: [],
39
+ };
40
+ }
41
+ // ============================================================================
42
+ // NEW: TASK DECOMPOSITION & INFERENCE
43
+ // ============================================================================
44
+ /**
45
+ * Decompose initial input into inference tasks with confidence scores
46
+ */
47
+ async decompose(initialInput) {
48
+ const prompt = this.buildDecompositionPrompt(initialInput);
49
+ const response = await this.aiCore.generateText(prompt, {
50
+ temperature: 0.3,
51
+ });
52
+ let parsedTasks;
53
+ try {
54
+ // Strip markdown code blocks if present
55
+ let cleanedResponse = response.trim();
56
+ if (cleanedResponse.startsWith('```json')) {
57
+ cleanedResponse = cleanedResponse.replace(/^```json\s*/m, '').replace(/\s*```$/m, '');
58
+ }
59
+ else if (cleanedResponse.startsWith('```')) {
60
+ cleanedResponse = cleanedResponse.replace(/^```\s*/m, '').replace(/\s*```$/m, '');
61
+ }
62
+ parsedTasks = JSON.parse(cleanedResponse);
63
+ }
64
+ catch (error) {
65
+ throw new Error(`Failed to parse task decomposition: ${response}`);
66
+ }
67
+ // Convert to InferenceTask objects
68
+ const tasks = parsedTasks.tasks.map((t, idx) => ({
69
+ id: `task-${idx + 1}`,
70
+ description: t.description,
71
+ category: t.category,
72
+ confidence: t.confidence,
73
+ dependencies: t.dependencies || [],
74
+ autoInfer: t.confidence >= 90,
75
+ needsConfirmation: t.confidence >= 70 && t.confidence < 90,
76
+ needsUserInput: t.confidence < 70,
77
+ completed: false,
78
+ }));
79
+ this.state.tasks = tasks;
80
+ this.state.pendingTasks = tasks.map(t => t.id);
81
+ return tasks;
82
+ }
83
+ /**
84
+ * Select next task to execute based on dependencies and confidence
85
+ */
86
+ selectNextTask() {
87
+ const availableTasks = this.state.tasks.filter(task => {
88
+ // Must be pending
89
+ if (task.completed)
90
+ return false;
91
+ // All dependencies must be completed
92
+ const dependenciesMet = task.dependencies.every(depId => this.state.completedTasks.includes(depId));
93
+ return dependenciesMet;
94
+ });
95
+ if (availableTasks.length === 0)
96
+ return null;
97
+ // Prioritize by confidence (highest first)
98
+ availableTasks.sort((a, b) => b.confidence - a.confidence);
99
+ return availableTasks[0] || null;
100
+ }
101
+ /**
102
+ * Mark task as completed and update state
103
+ */
104
+ markTaskComplete(taskId) {
105
+ const task = this.state.tasks.find(t => t.id === taskId);
106
+ if (!task)
107
+ return;
108
+ task.completed = true;
109
+ task.completedAt = new Date();
110
+ this.state.completedTasks.push(taskId);
111
+ this.state.pendingTasks = this.state.pendingTasks.filter(id => id !== taskId);
112
+ // Update overall confidence score
113
+ this.updateConfidenceScore();
114
+ }
115
+ /**
116
+ * Update dependent tasks after a task completes
117
+ */
118
+ updateDependentTasks(completedTask) {
119
+ this.state.tasks = this.inferenceEngine.feedToNextTasks(completedTask, this.state.tasks.filter(t => !t.completed));
120
+ }
121
+ /**
122
+ * Reveal context to user (what was inferred and why)
123
+ */
124
+ revealContext(task, inference) {
125
+ const revelations = [];
126
+ // Generate human-readable explanations
127
+ if (inference.entities) {
128
+ Object.keys(inference.entities).forEach(entityName => {
129
+ revelations.push({
130
+ taskId: task.id,
131
+ message: `${entityName} entity (${task.reasoning || 'inferred from context'})`,
132
+ confidence: task.confidence,
133
+ timestamp: new Date(),
134
+ category: task.category,
135
+ });
136
+ });
137
+ }
138
+ if (inference.auth) {
139
+ revelations.push({
140
+ taskId: task.id,
141
+ message: `Authentication: ${inference.auth.provider} (${task.reasoning || 'inferred from context'})`,
142
+ confidence: task.confidence,
143
+ timestamp: new Date(),
144
+ category: task.category,
145
+ });
146
+ }
147
+ if (inference.pages) {
148
+ inference.pages.forEach(page => {
149
+ revelations.push({
150
+ taskId: task.id,
151
+ message: `Page: ${page.path} (${task.reasoning || 'inferred from context'})`,
152
+ confidence: task.confidence,
153
+ timestamp: new Date(),
154
+ category: task.category,
155
+ });
156
+ });
157
+ }
158
+ this.state.revealedContext.push(...revelations);
159
+ return revelations;
160
+ }
161
+ /**
162
+ * Create checkpoint summary before proceeding
163
+ */
164
+ createCheckpoint(autoInferredTasks) {
165
+ const entitiesCreated = [];
166
+ const rolesCreated = [];
167
+ const pagesCreated = [];
168
+ let fieldsAdded = 0;
169
+ let permissionsAdded = 0;
170
+ autoInferredTasks.forEach(task => {
171
+ if (task.inference?.entities) {
172
+ Object.keys(task.inference.entities).forEach(entityName => {
173
+ entitiesCreated.push(entityName);
174
+ const entity = task.inference?.entities?.[entityName];
175
+ if (entity) {
176
+ fieldsAdded += entity.fields?.length || 0;
177
+ }
178
+ });
179
+ }
180
+ if (task.inference?.auth?.roles) {
181
+ rolesCreated.push(...task.inference.auth.roles.map(r => r.name));
182
+ }
183
+ if (task.inference?.permissions) {
184
+ permissionsAdded += task.inference.permissions.length;
185
+ }
186
+ if (task.inference?.pages) {
187
+ pagesCreated.push(...task.inference.pages.map(p => p.path));
188
+ }
189
+ });
190
+ const totalConfidence = autoInferredTasks.reduce((sum, t) => sum + t.confidence, 0) /
191
+ (autoInferredTasks.length || 1);
192
+ const summary = {
193
+ entitiesCreated,
194
+ fieldsAdded,
195
+ rolesCreated,
196
+ permissionsAdded,
197
+ pagesCreated,
198
+ totalConfidence: Math.round(totalConfidence),
199
+ };
200
+ const checkpoint = {
201
+ id: `checkpoint-${this.state.checkpoints.length + 1}`,
202
+ timestamp: new Date(),
203
+ autoInferredTasks,
204
+ summary,
205
+ approved: false,
206
+ };
207
+ this.state.checkpoints.push(checkpoint);
208
+ return checkpoint;
209
+ }
210
+ /**
211
+ * Get current planner state
212
+ */
213
+ getState() {
214
+ return this.state;
215
+ }
216
+ /**
217
+ * Update overall confidence score
218
+ */
219
+ updateConfidenceScore() {
220
+ if (this.state.tasks.length === 0) {
221
+ this.state.confidenceScore = 0;
222
+ return;
223
+ }
224
+ const totalConfidence = this.state.tasks
225
+ .filter(t => t.completed)
226
+ .reduce((sum, task) => sum + task.confidence, 0);
227
+ const totalTasks = this.state.tasks.length;
228
+ this.state.confidenceScore = Math.round(totalConfidence / totalTasks);
229
+ }
230
+ /**
231
+ * Build decomposition prompt
232
+ */
233
+ buildDecompositionPrompt(initialInput) {
234
+ return `You are an expert software architect helping to break down a project description into actionable inference tasks.
235
+
236
+ ## User Input
237
+ "${initialInput}"
238
+
239
+ ## Your Task
240
+ Break this down into specific inference tasks that can be completed sequentially. Each task should:
241
+ 1. Have a clear description
242
+ 2. Be assignable to a category (project, entities, auth, permissions, pages, design)
243
+ 3. Have a confidence score (0-100) indicating how confidently it can be inferred
244
+ 4. List dependencies on other tasks (by task number)
245
+
246
+ Consider:
247
+ - What entities (data models) are needed?
248
+ - What authentication/authorization is needed?
249
+ - What pages/routes are needed?
250
+ - What relationships exist between entities?
251
+ - What can be inferred with high confidence vs. what needs user input?
252
+
253
+ Return JSON:
254
+ \`\`\`json
255
+ {
256
+ "tasks": [
257
+ {
258
+ "description": "Infer core entities from 'blog' context",
259
+ "category": "entities",
260
+ "confidence": 95,
261
+ "dependencies": []
262
+ },
263
+ {
264
+ "description": "Infer User entity fields",
265
+ "category": "entities",
266
+ "confidence": 90,
267
+ "dependencies": [1]
268
+ },
269
+ {
270
+ "description": "Infer authentication requirements",
271
+ "category": "auth",
272
+ "confidence": 85,
273
+ "dependencies": [1]
274
+ }
275
+ ]
276
+ }
277
+ \`\`\`
278
+
279
+ Confidence guidelines:
280
+ - 95-100%: Extremely certain (e.g., blog → needs Post entity)
281
+ - 85-94%: Very confident (e.g., blog → likely needs Comment entity)
282
+ - 70-84%: Moderately confident (e.g., auth method could be email or OAuth)
283
+ - Below 70%: Too ambiguous, user input needed
284
+
285
+ Only return JSON, no other text.`;
286
+ }
287
+ // ============================================================================
288
+ // EXISTING METHODS (KEEP AS-IS)
289
+ // ============================================================================
290
+ /**
291
+ * Validate ASL for completeness and correctness
292
+ */
293
+ validate(asl) {
294
+ const errors = [];
295
+ const warnings = [];
296
+ // 1. Check required top-level fields
297
+ if (!asl.version) {
298
+ errors.push({
299
+ path: "version",
300
+ message: "ASL version is required",
301
+ severity: "error",
302
+ fix: 'Set version to "1.0"',
303
+ });
304
+ }
305
+ if (!asl.project) {
306
+ errors.push({
307
+ path: "project",
308
+ message: "Project specification is required",
309
+ severity: "error",
310
+ });
311
+ }
312
+ else {
313
+ // Validate project fields
314
+ if (!asl.project.name) {
315
+ errors.push({
316
+ path: "project.name",
317
+ message: "Project name is required",
318
+ severity: "error",
319
+ });
320
+ }
321
+ if (!asl.project.framework) {
322
+ errors.push({
323
+ path: "project.framework",
324
+ message: "Framework must be specified",
325
+ severity: "error",
326
+ fix: 'Set framework to "nextjs"',
327
+ });
328
+ }
329
+ if (!asl.project.backend) {
330
+ errors.push({
331
+ path: "project.backend",
332
+ message: "Backend must be specified",
333
+ severity: "error",
334
+ fix: 'Set backend to "instantdb"',
335
+ });
336
+ }
337
+ }
338
+ // 2. Check entities
339
+ if (!asl.entities || Object.keys(asl.entities).length === 0) {
340
+ errors.push({
341
+ path: "entities",
342
+ message: "At least one entity must be defined",
343
+ severity: "error",
344
+ });
345
+ }
346
+ else {
347
+ // Validate each entity
348
+ Object.entries(asl.entities).forEach(([entityName, entity], idx) => {
349
+ if (!entity.fields || entity.fields.length === 0) {
350
+ errors.push({
351
+ path: `entities.${entityName}.fields`,
352
+ message: `Entity "${entityName}" must have at least one field`,
353
+ severity: "error",
354
+ });
355
+ }
356
+ // Check field types
357
+ entity.fields?.forEach((field, fieldIdx) => {
358
+ if (!field.name) {
359
+ errors.push({
360
+ path: `entities.${entityName}.fields[${fieldIdx}].name`,
361
+ message: "Field name is required",
362
+ severity: "error",
363
+ });
364
+ }
365
+ if (!field.type) {
366
+ errors.push({
367
+ path: `entities.${entityName}.fields[${fieldIdx}].type`,
368
+ message: `Field "${field.name}" must have a type`,
369
+ severity: "error",
370
+ });
371
+ }
372
+ // Warn about missing descriptions
373
+ if (!field.description) {
374
+ warnings.push({
375
+ path: `entities.${entityName}.fields[${fieldIdx}].description`,
376
+ message: `Field "${field.name}" missing description`,
377
+ severity: "warning",
378
+ suggestion: "Add description for better documentation",
379
+ });
380
+ }
381
+ });
382
+ });
383
+ }
384
+ // 3. Check pages
385
+ if (!asl.pages || asl.pages.length === 0) {
386
+ errors.push({
387
+ path: "pages",
388
+ message: "At least one page must be defined",
389
+ severity: "error",
390
+ });
391
+ }
392
+ else {
393
+ asl.pages.forEach((page, idx) => {
394
+ if (!page.path) {
395
+ errors.push({
396
+ path: `pages[${idx}].path`,
397
+ message: "Page path is required",
398
+ severity: "error",
399
+ });
400
+ }
401
+ if (!page.name) {
402
+ errors.push({
403
+ path: `pages[${idx}].name`,
404
+ message: "Page name (component name) is required",
405
+ severity: "error",
406
+ });
407
+ }
408
+ // Check if page requires auth but no auth is configured
409
+ if (!page.public && !asl.auth) {
410
+ warnings.push({
411
+ path: `pages[${idx}]`,
412
+ message: `Page "${page.path}" requires authentication but no auth is configured`,
413
+ severity: "warning",
414
+ suggestion: "Add auth configuration or mark page as public",
415
+ });
416
+ }
417
+ });
418
+ }
419
+ // 4. Check auth if provided
420
+ if (asl.auth) {
421
+ if (!asl.auth.provider) {
422
+ errors.push({
423
+ path: "auth.provider",
424
+ message: "Auth provider must be specified",
425
+ severity: "error",
426
+ });
427
+ }
428
+ if (!asl.auth.roles || asl.auth.roles.length === 0) {
429
+ warnings.push({
430
+ path: "auth.roles",
431
+ message: "No roles defined",
432
+ severity: "warning",
433
+ suggestion: "Define at least one role (e.g., 'user', 'admin')",
434
+ });
435
+ }
436
+ }
437
+ // 5. Validate entity references in pages
438
+ const refErrors = (0, asl_1.validateEntityReferences)(asl);
439
+ refErrors.forEach(err => {
440
+ const parts = err.split(":");
441
+ errors.push({
442
+ path: parts[0] || "",
443
+ message: parts[1] || err,
444
+ severity: "error",
445
+ });
446
+ });
447
+ // 6. Validate permission references
448
+ const permErrors = (0, asl_1.validatePermissionReferences)(asl);
449
+ permErrors.forEach(err => {
450
+ const parts = err.split(":");
451
+ errors.push({
452
+ path: parts[0] || "",
453
+ message: parts[1] || err,
454
+ severity: "error",
455
+ });
456
+ });
457
+ // 7. Calculate completeness
458
+ const completeness = (0, asl_1.calculateCompleteness)(asl);
459
+ return {
460
+ valid: errors.length === 0,
461
+ errors,
462
+ warnings,
463
+ completeness,
464
+ };
465
+ }
466
+ /**
467
+ * Generate clarifying questions for gaps in ASL
468
+ */
469
+ generateQuestions(asl) {
470
+ const questions = [];
471
+ const missing = (0, asl_1.getMissingSections)(asl);
472
+ // 1. Project questions
473
+ if (!asl.project) {
474
+ questions.push({
475
+ id: "project.name",
476
+ type: "text",
477
+ text: "What is the name of your project?",
478
+ context: "This will be used as the package name and directory name.",
479
+ validation: {
480
+ required: true,
481
+ pattern: "^[a-z][a-z0-9-]*$",
482
+ },
483
+ category: "project",
484
+ });
485
+ questions.push({
486
+ id: "project.description",
487
+ type: "text",
488
+ text: "Describe your project in one sentence:",
489
+ context: "This helps the AI understand your project's purpose.",
490
+ validation: {
491
+ required: true,
492
+ minLength: 10,
493
+ },
494
+ category: "project",
495
+ });
496
+ }
497
+ // 2. Entity questions
498
+ if (!asl.entities || Object.keys(asl.entities).length === 0) {
499
+ questions.push({
500
+ id: "entities.list",
501
+ type: "text",
502
+ text: "What are the main entities (data models) in your app?",
503
+ context: "Examples: User, Post, Comment, Product, Order. Separate with commas.",
504
+ validation: {
505
+ required: true,
506
+ },
507
+ category: "entities",
508
+ });
509
+ }
510
+ else {
511
+ // For each entity, check if fields are complete
512
+ Object.entries(asl.entities).forEach(([entityName, entity]) => {
513
+ if (!entity.fields || entity.fields.length === 0) {
514
+ questions.push({
515
+ id: `entities.${entityName}.fields`,
516
+ type: "entity-builder",
517
+ text: `What fields should the "${entityName}" entity have?`,
518
+ context: `Define the data structure for ${entityName}. Examples: title (string), price (number), published (boolean).`,
519
+ validation: {
520
+ required: true,
521
+ },
522
+ category: "entities",
523
+ });
524
+ }
525
+ });
526
+ }
527
+ // 3. Auth questions
528
+ if (!asl.auth) {
529
+ questions.push({
530
+ id: "auth.needed",
531
+ type: "boolean",
532
+ text: "Does your app need user authentication?",
533
+ context: "Most apps with user-specific data need authentication.",
534
+ category: "auth",
535
+ });
536
+ }
537
+ else {
538
+ if (!asl.auth.provider) {
539
+ questions.push({
540
+ id: "auth.provider",
541
+ type: "select",
542
+ text: "Which authentication method do you want to use?",
543
+ options: [
544
+ {
545
+ value: "email",
546
+ label: "Email/Password",
547
+ description: "Traditional email and password authentication",
548
+ },
549
+ {
550
+ value: "magic-link",
551
+ label: "Magic Link",
552
+ description: "Passwordless login via email link",
553
+ },
554
+ {
555
+ value: "oauth-github",
556
+ label: "OAuth (GitHub)",
557
+ description: "Login with GitHub account",
558
+ },
559
+ {
560
+ value: "oauth-google",
561
+ label: "OAuth (Google)",
562
+ description: "Login with Google account",
563
+ },
564
+ ],
565
+ validation: {
566
+ required: true,
567
+ },
568
+ category: "auth",
569
+ });
570
+ }
571
+ if (!asl.auth.roles || asl.auth.roles.length === 0) {
572
+ questions.push({
573
+ id: "auth.roles",
574
+ type: "multi-select",
575
+ text: "What user roles do you need?",
576
+ context: "Roles define different permission levels (e.g., admin can do everything, user can only edit their own content).",
577
+ options: [
578
+ { value: "admin", label: "Admin", description: "Full system access" },
579
+ { value: "user", label: "User", description: "Regular user" },
580
+ { value: "moderator", label: "Moderator", description: "Content moderation" },
581
+ { value: "guest", label: "Guest", description: "Read-only access" },
582
+ ],
583
+ validation: {
584
+ required: true,
585
+ },
586
+ category: "auth",
587
+ });
588
+ }
589
+ }
590
+ // 4. Permission questions
591
+ if (asl.auth && (!asl.permissions || asl.permissions.length === 0)) {
592
+ questions.push({
593
+ id: "permissions.needed",
594
+ type: "boolean",
595
+ text: "Do you need role-based access control (RBAC)?",
596
+ context: "RBAC controls who can create, read, update, or delete specific resources.",
597
+ dependsOn: ["auth.needed"],
598
+ category: "permissions",
599
+ });
600
+ }
601
+ // 5. Page questions
602
+ if (!asl.pages || asl.pages.length === 0) {
603
+ questions.push({
604
+ id: "pages.list",
605
+ type: "text",
606
+ text: "What are the main pages/routes in your app?",
607
+ context: "Examples: Home (/), Posts (/posts), Profile (/profile). Separate with commas.",
608
+ validation: {
609
+ required: true,
610
+ },
611
+ category: "pages",
612
+ });
613
+ }
614
+ else {
615
+ // Check if pages need more details
616
+ asl.pages.forEach((page, idx) => {
617
+ if (!page.type) {
618
+ questions.push({
619
+ id: `pages[${idx}].type`,
620
+ type: "select",
621
+ text: `What type of page is "${page.path}"?`,
622
+ options: [
623
+ { value: "page", label: "Regular Page" },
624
+ { value: "layout", label: "Layout" },
625
+ { value: "route-group", label: "Route Group" },
626
+ ],
627
+ validation: {
628
+ required: true,
629
+ },
630
+ category: "pages",
631
+ });
632
+ }
633
+ if (page.public === undefined) {
634
+ questions.push({
635
+ id: `pages[${idx}].public`,
636
+ type: "boolean",
637
+ text: `Should "${page.path}" be publicly accessible (no login required)?`,
638
+ category: "pages",
639
+ });
640
+ }
641
+ });
642
+ }
643
+ // 6. Design questions
644
+ if (!asl.design) {
645
+ questions.push({
646
+ id: "design.theme",
647
+ type: "select",
648
+ text: "What theme do you prefer?",
649
+ options: [
650
+ { value: "light", label: "Light", description: "Light color scheme" },
651
+ { value: "dark", label: "Dark", description: "Dark color scheme" },
652
+ { value: "system", label: "System", description: "Follow system preference" },
653
+ ],
654
+ validation: {
655
+ required: false,
656
+ },
657
+ category: "design",
658
+ });
659
+ }
660
+ return questions;
661
+ }
662
+ /**
663
+ * Generate diff preview showing what will be generated
664
+ */
665
+ generateDiff(asl) {
666
+ const files = [];
667
+ const registries = [];
668
+ const warnings = [];
669
+ // 1. Schema file
670
+ files.push({
671
+ path: "instant.schema.ts",
672
+ action: "create",
673
+ type: "schema",
674
+ preview: this.generateSchemaPreview(asl),
675
+ size: this.estimateSize(asl.entities),
676
+ });
677
+ // 2. Types file
678
+ files.push({
679
+ path: "src/types/schema.ts",
680
+ action: "create",
681
+ type: "type",
682
+ preview: this.generateTypesPreview(asl),
683
+ size: this.estimateSize(asl.entities) * 2,
684
+ });
685
+ // 3. Page files
686
+ asl.pages.forEach(page => {
687
+ const pagePath = this.pageSpecToPath(page);
688
+ files.push({
689
+ path: pagePath,
690
+ action: "create",
691
+ type: "page",
692
+ preview: `export default function ${page.name}() { ... }`,
693
+ size: 500,
694
+ });
695
+ });
696
+ // 4. Component files (estimate based on entities)
697
+ const componentCount = Object.keys(asl.entities).length * 3; // Card, List, Form per entity
698
+ for (let i = 0; i < componentCount; i++) {
699
+ files.push({
700
+ path: `src/components/.../${i}.tsx`,
701
+ action: "create",
702
+ type: "component",
703
+ size: 300,
704
+ });
705
+ }
706
+ // 5. Action files
707
+ Object.keys(asl.entities).forEach(entityName => {
708
+ files.push({
709
+ path: `src/actions/${entityName.toLowerCase()}.ts`,
710
+ action: "create",
711
+ type: "action",
712
+ preview: `'use server';\n\nexport async function create${entityName}(...) { ... }`,
713
+ size: 800,
714
+ });
715
+ });
716
+ // 6. Auth files (if auth is configured)
717
+ if (asl.auth) {
718
+ files.push({
719
+ path: "src/lib/guards.ts",
720
+ action: "create",
721
+ type: "config",
722
+ preview: "export function withAuthGuard(...) { ... }",
723
+ size: 600,
724
+ }, {
725
+ path: "src/lib/permissions.ts",
726
+ action: "create",
727
+ type: "config",
728
+ preview: "export function hasPermission(...) { ... }",
729
+ size: 400,
730
+ }, {
731
+ path: "middleware.ts",
732
+ action: "create",
733
+ type: "config",
734
+ preview: "export function middleware(request: NextRequest) { ... }",
735
+ size: 500,
736
+ });
737
+ }
738
+ // 7. Registry previews
739
+ registries.push({
740
+ type: "components",
741
+ added: Object.keys(asl.entities).flatMap(e => [
742
+ `${e}Card`,
743
+ `${e}List`,
744
+ `${e}Form`,
745
+ ]),
746
+ modified: [],
747
+ removed: [],
748
+ });
749
+ registries.push({
750
+ type: "types",
751
+ added: Object.keys(asl.entities).flatMap(e => [e, `${e}Insert`, `${e}WithRelations`]),
752
+ modified: [],
753
+ removed: [],
754
+ });
755
+ if (asl.permissions) {
756
+ registries.push({
757
+ type: "permissions",
758
+ added: asl.permissions.map(p => `${p.role}:${p.resource}:${(p.actions || []).join(",")}`),
759
+ modified: [],
760
+ removed: [],
761
+ });
762
+ }
763
+ // 8. Warnings
764
+ if (!asl.auth && asl.pages.some(p => !p.public)) {
765
+ warnings.push("Some pages require authentication but no auth is configured");
766
+ }
767
+ // 9. Summary
768
+ const summary = {
769
+ totalFiles: files.length,
770
+ newFiles: files.length,
771
+ modifiedFiles: 0,
772
+ deletedFiles: 0,
773
+ linesAdded: files.reduce((sum, f) => sum + (f.size || 0) / 50, 0), // Rough estimate
774
+ linesRemoved: 0,
775
+ };
776
+ return {
777
+ summary,
778
+ files,
779
+ registries,
780
+ warnings,
781
+ };
782
+ }
783
+ /**
784
+ * Check if ASL is complete enough for generation
785
+ */
786
+ isComplete(asl) {
787
+ return (0, asl_1.isASLComplete)(asl);
788
+ }
789
+ // ============================================================================
790
+ // HELPER METHODS
791
+ // ============================================================================
792
+ generateSchemaPreview(asl) {
793
+ const entityNames = Object.keys(asl.entities).slice(0, 2).join(", ");
794
+ const entityCount = Object.keys(asl.entities).length;
795
+ const more = entityCount > 2 ? ` + ${entityCount - 2} more` : "";
796
+ return `import { i } from "@instantdb/core";
797
+
798
+ const schema = i.schema({
799
+ entities: {
800
+ ${entityNames}${more} ...
801
+ }
802
+ });`;
803
+ }
804
+ generateTypesPreview(asl) {
805
+ const firstEntity = Object.keys(asl.entities)[0];
806
+ return `export interface ${firstEntity} {
807
+ id: string;
808
+ ...
809
+ }
810
+
811
+ export type ${firstEntity}Insert = Omit<${firstEntity}, "id">;`;
812
+ }
813
+ pageSpecToPath(page) {
814
+ // Convert page spec to Next.js file path
815
+ const basePath = page.path === "/" ? "" : page.path;
816
+ return `src/app${basePath}/page.tsx`;
817
+ }
818
+ estimateSize(entities) {
819
+ // Rough estimate: 100 bytes per field
820
+ let total = 0;
821
+ Object.values(entities).forEach(entity => {
822
+ total += (entity.fields?.length || 0) * 100;
823
+ });
824
+ return total;
825
+ }
826
+ }
827
+ exports.Planner = Planner;
828
+ //# sourceMappingURL=Planner.js.map