mycontext-cli 4.2.5 ā 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.
- package/README.md +531 -144
- package/dist/README.md +531 -144
- package/dist/cli.js +14 -5
- package/dist/cli.js.map +1 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +2 -0
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/generate.d.ts.map +1 -1
- package/dist/commands/generate.js +17 -4
- package/dist/commands/generate.js.map +1 -1
- package/dist/commands/init-interactive.d.ts +127 -0
- package/dist/commands/init-interactive.d.ts.map +1 -0
- package/dist/commands/init-interactive.js +754 -0
- package/dist/commands/init-interactive.js.map +1 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +22 -31
- package/dist/commands/init.js.map +1 -1
- package/dist/doctor/DoctorEngine.d.ts.map +1 -1
- package/dist/doctor/DoctorEngine.js +9 -3
- package/dist/doctor/DoctorEngine.js.map +1 -1
- package/dist/doctor/rules/dead-code-rules.d.ts.map +1 -1
- package/dist/doctor/rules/dead-code-rules.js +29 -3
- package/dist/doctor/rules/dead-code-rules.js.map +1 -1
- package/dist/doctor/rules/index.d.ts +3 -1
- package/dist/doctor/rules/index.d.ts.map +1 -1
- package/dist/doctor/rules/index.js +7 -1
- package/dist/doctor/rules/index.js.map +1 -1
- package/dist/doctor/rules/instantdb-rules.d.ts +3 -0
- package/dist/doctor/rules/instantdb-rules.d.ts.map +1 -0
- package/dist/doctor/rules/instantdb-rules.js +335 -0
- package/dist/doctor/rules/instantdb-rules.js.map +1 -0
- package/dist/doctor/rules/typescript-rules.d.ts +3 -0
- package/dist/doctor/rules/typescript-rules.d.ts.map +1 -0
- package/dist/doctor/rules/typescript-rules.js +177 -0
- package/dist/doctor/rules/typescript-rules.js.map +1 -0
- package/dist/doctor/types.d.ts +1 -0
- package/dist/doctor/types.d.ts.map +1 -1
- package/dist/package.json +1 -1
- package/dist/services/InferenceEngine.d.ts +41 -0
- package/dist/services/InferenceEngine.d.ts.map +1 -0
- package/dist/services/InferenceEngine.js +307 -0
- package/dist/services/InferenceEngine.js.map +1 -0
- package/dist/services/Planner.d.ts +77 -0
- package/dist/services/Planner.d.ts.map +1 -0
- package/dist/services/Planner.js +828 -0
- package/dist/services/Planner.js.map +1 -0
- package/dist/services/ProjectScanner.d.ts.map +1 -1
- package/dist/services/ProjectScanner.js +7 -3
- package/dist/services/ProjectScanner.js.map +1 -1
- package/dist/types/asl.d.ts +387 -0
- package/dist/types/asl.d.ts.map +1 -0
- package/dist/types/asl.js +139 -0
- package/dist/types/asl.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,754 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Init Interactive Command
|
|
4
|
+
*
|
|
5
|
+
* The user-facing entry point for deterministic compilation with AI-powered inference.
|
|
6
|
+
* This command implements the "Self-Organizing Planner" where the AI:
|
|
7
|
+
* 1. Decomposes the project into inference tasks
|
|
8
|
+
* 2. Auto-infers high-confidence tasks (ā„90%)
|
|
9
|
+
* 3. Self-critiques its work
|
|
10
|
+
* 4. Learns from user corrections
|
|
11
|
+
* 5. Only prompts for ambiguous items
|
|
12
|
+
*
|
|
13
|
+
* Flow:
|
|
14
|
+
* 1. Ask: "What are you building?"
|
|
15
|
+
* 2. Decompose into tasks with confidence scores
|
|
16
|
+
* 3. Auto-infer high-confidence tasks
|
|
17
|
+
* 4. Show checkpoints for review
|
|
18
|
+
* 5. Prompt for low-confidence items
|
|
19
|
+
* 6. Show final diff preview
|
|
20
|
+
* 7. Get user approval
|
|
21
|
+
* 8. Save ASL to .mycontext/asl.json
|
|
22
|
+
*/
|
|
23
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
24
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
25
|
+
};
|
|
26
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
27
|
+
exports.InitInteractiveCommand = void 0;
|
|
28
|
+
const inquirer_1 = __importDefault(require("inquirer"));
|
|
29
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
30
|
+
const ora_1 = __importDefault(require("ora"));
|
|
31
|
+
const promises_1 = __importDefault(require("fs/promises"));
|
|
32
|
+
const path_1 = __importDefault(require("path"));
|
|
33
|
+
const Planner_1 = require("../services/Planner");
|
|
34
|
+
const InferenceEngine_1 = require("../services/InferenceEngine");
|
|
35
|
+
const AICore_1 = require("../core/ai/AICore");
|
|
36
|
+
class InitInteractiveCommand {
|
|
37
|
+
constructor() {
|
|
38
|
+
this.planner = new Planner_1.Planner();
|
|
39
|
+
this.inferenceEngine = new InferenceEngine_1.InferenceEngine();
|
|
40
|
+
this.ai = AICore_1.AICore.getInstance({
|
|
41
|
+
fallbackEnabled: true,
|
|
42
|
+
workingDirectory: process.cwd(),
|
|
43
|
+
});
|
|
44
|
+
this.asl = { version: "1.0" };
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Main entry point - NEW INFERENCE-BASED FLOW
|
|
48
|
+
*/
|
|
49
|
+
async execute() {
|
|
50
|
+
console.log(chalk_1.default.bold.cyan("\nšÆ MyContext Interactive Setup\n"));
|
|
51
|
+
console.log(chalk_1.default.dim("Describe your project and I'll build a complete specification with minimal questions.\n"));
|
|
52
|
+
try {
|
|
53
|
+
// Step 1: Get initial input
|
|
54
|
+
const initialInput = await this.askInitialQuestion();
|
|
55
|
+
// Step 2: Decompose into tasks
|
|
56
|
+
console.log(chalk_1.default.cyan("\nš¤ Breaking down into tasks...\n"));
|
|
57
|
+
const tasks = await this.planner.decompose(initialInput);
|
|
58
|
+
this.displayTaskDecomposition(tasks);
|
|
59
|
+
// Step 3: Recursive inference loop
|
|
60
|
+
console.log(chalk_1.default.cyan("\nš¤ Auto-inferring high-confidence tasks...\n"));
|
|
61
|
+
await this.recursiveInferenceLoop();
|
|
62
|
+
// Step 4: Show checkpoints and get approval
|
|
63
|
+
await this.showCheckpointsAndConfirm();
|
|
64
|
+
// Step 5: Show final diff preview
|
|
65
|
+
await this.showDiffPreview();
|
|
66
|
+
// Step 6: Get final approval
|
|
67
|
+
const approved = await this.getApproval();
|
|
68
|
+
if (approved) {
|
|
69
|
+
// Step 7: Save ASL
|
|
70
|
+
await this.saveASL();
|
|
71
|
+
console.log(chalk_1.default.green("\nā Specification complete!\n"));
|
|
72
|
+
console.log(chalk_1.default.cyan("š Summary:"));
|
|
73
|
+
console.log(chalk_1.default.gray(` Auto-inferred: ${this.getAutoInferredCount()} items`));
|
|
74
|
+
console.log(chalk_1.default.gray(` User prompted: ${this.getUserPromptedCount()} items`));
|
|
75
|
+
console.log(chalk_1.default.gray(` Overall confidence: ${this.planner.getState().confidenceScore}%\n`));
|
|
76
|
+
console.log(chalk_1.default.cyan("Next step: Run ") + chalk_1.default.bold("mycontext scaffold --from-manifest\n"));
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
console.log(chalk_1.default.yellow("\nā Setup cancelled\n"));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
console.error(chalk_1.default.red("\nā Setup failed:"), error);
|
|
84
|
+
throw error;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
// ============================================================================
|
|
88
|
+
// NEW INFERENCE-BASED METHODS
|
|
89
|
+
// ============================================================================
|
|
90
|
+
/**
|
|
91
|
+
* Ask single initial question
|
|
92
|
+
*/
|
|
93
|
+
async askInitialQuestion() {
|
|
94
|
+
const answers = await inquirer_1.default.prompt([
|
|
95
|
+
{
|
|
96
|
+
type: "input",
|
|
97
|
+
name: "description",
|
|
98
|
+
message: "What are you building?",
|
|
99
|
+
validate: (input) => {
|
|
100
|
+
if (!input || input.length < 10) {
|
|
101
|
+
return "Please provide at least 10 characters describing your project";
|
|
102
|
+
}
|
|
103
|
+
return true;
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
]);
|
|
107
|
+
return answers.description;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Display task decomposition
|
|
111
|
+
*/
|
|
112
|
+
displayTaskDecomposition(tasks) {
|
|
113
|
+
console.log(chalk_1.default.bold("š Task decomposition:\n"));
|
|
114
|
+
tasks.forEach((task, idx) => {
|
|
115
|
+
const confidenceColor = task.confidence >= 90 ? chalk_1.default.green :
|
|
116
|
+
task.confidence >= 70 ? chalk_1.default.yellow :
|
|
117
|
+
chalk_1.default.red;
|
|
118
|
+
console.log(chalk_1.default.gray(` ${idx + 1}.`) +
|
|
119
|
+
` ${task.description} - ` +
|
|
120
|
+
confidenceColor(`${task.confidence}% confidence`));
|
|
121
|
+
});
|
|
122
|
+
console.log(chalk_1.default.gray(`\n Total: ${tasks.length} tasks\n`));
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Recursive inference loop
|
|
126
|
+
*/
|
|
127
|
+
async recursiveInferenceLoop() {
|
|
128
|
+
const state = this.planner.getState();
|
|
129
|
+
const completedTasks = [];
|
|
130
|
+
const autoInferredTasks = [];
|
|
131
|
+
while (true) {
|
|
132
|
+
// Select next task
|
|
133
|
+
const task = this.planner.selectNextTask();
|
|
134
|
+
if (!task)
|
|
135
|
+
break;
|
|
136
|
+
if (task.autoInfer) {
|
|
137
|
+
// Auto-infer with spinner
|
|
138
|
+
const spinner = (0, ora_1.default)(`Inferring: ${task.description}`).start();
|
|
139
|
+
try {
|
|
140
|
+
// Run inference
|
|
141
|
+
const inference = await this.inferenceEngine.infer(task, this.asl, completedTasks);
|
|
142
|
+
// Self-critique
|
|
143
|
+
const critique = await this.inferenceEngine.selfCritique(inference, this.asl);
|
|
144
|
+
spinner.stop();
|
|
145
|
+
if (critique.confidence >= 90) {
|
|
146
|
+
// Accept inference
|
|
147
|
+
this.asl = { ...this.asl, ...inference.result };
|
|
148
|
+
task.inference = inference.result;
|
|
149
|
+
task.reasoning = inference.reasoning;
|
|
150
|
+
// Reveal context
|
|
151
|
+
const revelations = this.planner.revealContext(task, inference.result);
|
|
152
|
+
revelations.forEach(rev => {
|
|
153
|
+
console.log(chalk_1.default.green("ā") +
|
|
154
|
+
` ${rev.message} ` +
|
|
155
|
+
chalk_1.default.gray(`(${rev.confidence}% confidence)`));
|
|
156
|
+
});
|
|
157
|
+
autoInferredTasks.push(task);
|
|
158
|
+
this.planner.markTaskComplete(task.id);
|
|
159
|
+
completedTasks.push(task);
|
|
160
|
+
// Update dependent tasks
|
|
161
|
+
this.planner.updateDependentTasks(task);
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
// Drop to confirmation
|
|
165
|
+
task.needsConfirmation = true;
|
|
166
|
+
await this.confirmInference(task, inference, critique);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
spinner.fail(`Failed to infer: ${task.description}`);
|
|
171
|
+
console.error(chalk_1.default.red(error));
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
else if (task.needsConfirmation) {
|
|
175
|
+
// Suggest with confirmation
|
|
176
|
+
await this.confirmInference(task, null);
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
// Direct user prompt
|
|
180
|
+
await this.promptUser(task);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
// Create checkpoint if we auto-inferred anything
|
|
184
|
+
if (autoInferredTasks.length > 0) {
|
|
185
|
+
this.planner.createCheckpoint(autoInferredTasks);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Confirm inference with user
|
|
190
|
+
*/
|
|
191
|
+
async confirmInference(task, inference, critique) {
|
|
192
|
+
console.log(chalk_1.default.yellow("\nā Need confirmation:\n"));
|
|
193
|
+
console.log(chalk_1.default.dim(` ${task.description}\n`));
|
|
194
|
+
if (inference) {
|
|
195
|
+
console.log(chalk_1.default.dim(" Suggested:"));
|
|
196
|
+
console.log(chalk_1.default.dim(` ${JSON.stringify(inference.result, null, 2)}\n`));
|
|
197
|
+
if (critique && critique.issues.length > 0) {
|
|
198
|
+
console.log(chalk_1.default.yellow(" ā Potential issues:"));
|
|
199
|
+
critique.issues.forEach((issue) => {
|
|
200
|
+
console.log(chalk_1.default.yellow(` ⢠${issue.message}`));
|
|
201
|
+
});
|
|
202
|
+
console.log();
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
const { confirmed } = await inquirer_1.default.prompt([
|
|
206
|
+
{
|
|
207
|
+
type: "confirm",
|
|
208
|
+
name: "confirmed",
|
|
209
|
+
message: "Accept this inference?",
|
|
210
|
+
default: true,
|
|
211
|
+
},
|
|
212
|
+
]);
|
|
213
|
+
if (confirmed && inference) {
|
|
214
|
+
this.asl = { ...this.asl, ...inference.result };
|
|
215
|
+
task.inference = inference.result;
|
|
216
|
+
this.planner.markTaskComplete(task.id);
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
// Fall back to direct prompt
|
|
220
|
+
await this.promptUser(task);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Prompt user directly for a task
|
|
225
|
+
*/
|
|
226
|
+
async promptUser(task) {
|
|
227
|
+
console.log(chalk_1.default.cyan(`\nā ${task.description}\n`));
|
|
228
|
+
// Generic text input for now
|
|
229
|
+
// TODO: Add type-specific prompts (select, multi-select, etc.)
|
|
230
|
+
const { answer } = await inquirer_1.default.prompt([
|
|
231
|
+
{
|
|
232
|
+
type: "input",
|
|
233
|
+
name: "answer",
|
|
234
|
+
message: "Your answer:",
|
|
235
|
+
},
|
|
236
|
+
]);
|
|
237
|
+
// Parse answer with AI
|
|
238
|
+
const spinner = (0, ora_1.default)("Processing your answer...").start();
|
|
239
|
+
try {
|
|
240
|
+
const parsedInference = await this.inferenceEngine.infer({
|
|
241
|
+
...task,
|
|
242
|
+
description: `${task.description}. User said: "${answer}"`,
|
|
243
|
+
}, this.asl, []);
|
|
244
|
+
this.asl = { ...this.asl, ...parsedInference.result };
|
|
245
|
+
task.inference = parsedInference.result;
|
|
246
|
+
spinner.succeed("Answer processed");
|
|
247
|
+
this.planner.markTaskComplete(task.id);
|
|
248
|
+
}
|
|
249
|
+
catch (error) {
|
|
250
|
+
spinner.fail("Failed to process answer");
|
|
251
|
+
console.error(chalk_1.default.red(error));
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Show checkpoints and get confirmation
|
|
256
|
+
*/
|
|
257
|
+
async showCheckpointsAndConfirm() {
|
|
258
|
+
const state = this.planner.getState();
|
|
259
|
+
if (state.checkpoints.length === 0)
|
|
260
|
+
return;
|
|
261
|
+
console.log(chalk_1.default.cyan("\nš” Checkpoint - Review Auto-Inferred Items:\n"));
|
|
262
|
+
state.checkpoints.forEach(checkpoint => {
|
|
263
|
+
console.log(chalk_1.default.bold(` Checkpoint ${checkpoint.id}:`));
|
|
264
|
+
console.log(chalk_1.default.gray(` ā ${checkpoint.summary.entitiesCreated.length} entities`));
|
|
265
|
+
console.log(chalk_1.default.gray(` ā ${checkpoint.summary.fieldsAdded} fields`));
|
|
266
|
+
console.log(chalk_1.default.gray(` ā ${checkpoint.summary.rolesCreated.length} roles`));
|
|
267
|
+
console.log(chalk_1.default.gray(` ā ${checkpoint.summary.permissionsAdded} permissions`));
|
|
268
|
+
console.log(chalk_1.default.gray(` ā ${checkpoint.summary.pagesCreated.length} pages`));
|
|
269
|
+
console.log(chalk_1.default.gray(` Overall confidence: ${checkpoint.summary.totalConfidence}%\n`));
|
|
270
|
+
});
|
|
271
|
+
const { action } = await inquirer_1.default.prompt([
|
|
272
|
+
{
|
|
273
|
+
type: "list",
|
|
274
|
+
name: "action",
|
|
275
|
+
message: "What would you like to do?",
|
|
276
|
+
choices: [
|
|
277
|
+
{ name: "Continue", value: "continue" },
|
|
278
|
+
{ name: "Edit inferences", value: "edit" },
|
|
279
|
+
{ name: "Cancel", value: "cancel" },
|
|
280
|
+
],
|
|
281
|
+
},
|
|
282
|
+
]);
|
|
283
|
+
if (action === "cancel") {
|
|
284
|
+
throw new Error("User cancelled");
|
|
285
|
+
}
|
|
286
|
+
if (action === "edit") {
|
|
287
|
+
// TODO: Implement interactive editing
|
|
288
|
+
console.log(chalk_1.default.yellow("\nā Interactive editing coming soon. For now, you can edit in the final diff.\n"));
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Get counts for summary
|
|
293
|
+
*/
|
|
294
|
+
getAutoInferredCount() {
|
|
295
|
+
const state = this.planner.getState();
|
|
296
|
+
return state.tasks.filter(t => t.autoInfer && t.completed).length;
|
|
297
|
+
}
|
|
298
|
+
getUserPromptedCount() {
|
|
299
|
+
const state = this.planner.getState();
|
|
300
|
+
return state.tasks.filter(t => !t.autoInfer && t.completed).length;
|
|
301
|
+
}
|
|
302
|
+
// ============================================================================
|
|
303
|
+
// OLD METHODS (KEEP FOR COMPATIBILITY)
|
|
304
|
+
// ============================================================================
|
|
305
|
+
/**
|
|
306
|
+
* OLD: Recursive clarification loop (legacy, not used in new flow)
|
|
307
|
+
*/
|
|
308
|
+
async recursiveClarificationLoop_OLD() {
|
|
309
|
+
let iteration = 0;
|
|
310
|
+
const maxIterations = 20; // Safety limit
|
|
311
|
+
while (!this.planner.isComplete(this.asl) && iteration < maxIterations) {
|
|
312
|
+
iteration++;
|
|
313
|
+
// Validate current ASL
|
|
314
|
+
const validation = this.planner.validate(this.asl);
|
|
315
|
+
console.log(chalk_1.default.cyan(`\nš Completeness: ${chalk_1.default.bold(validation.completeness + "%")}\n`));
|
|
316
|
+
// Generate questions for gaps
|
|
317
|
+
const questions = this.planner.generateQuestions(this.asl);
|
|
318
|
+
if (questions.length === 0) {
|
|
319
|
+
// No more questions, but still not complete
|
|
320
|
+
// This shouldn't happen, but handle gracefully
|
|
321
|
+
console.log(chalk_1.default.yellow("ā No more questions, but specification may be incomplete"));
|
|
322
|
+
break;
|
|
323
|
+
}
|
|
324
|
+
// Ask questions in batches by category
|
|
325
|
+
const categorized = this.categorizeQuestions(questions);
|
|
326
|
+
for (const [category, categoryQuestions] of Object.entries(categorized)) {
|
|
327
|
+
await this.askQuestionBatch(category, categoryQuestions);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
if (iteration >= maxIterations) {
|
|
331
|
+
console.log(chalk_1.default.yellow("\nā Reached maximum iterations. Proceeding with current state.\n"));
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Ask a batch of questions for a specific category
|
|
336
|
+
*/
|
|
337
|
+
async askQuestionBatch(category, questions) {
|
|
338
|
+
console.log(chalk_1.default.bold.cyan(`\nš ${this.categoryLabel(category)}\n`));
|
|
339
|
+
for (const question of questions) {
|
|
340
|
+
const answer = await this.askQuestion(question);
|
|
341
|
+
await this.processAnswer(question, answer);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Ask a single question
|
|
346
|
+
*/
|
|
347
|
+
async askQuestion(question) {
|
|
348
|
+
const inquirerQuestion = {
|
|
349
|
+
type: this.mapQuestionType(question.type),
|
|
350
|
+
name: "answer",
|
|
351
|
+
message: question.text,
|
|
352
|
+
};
|
|
353
|
+
if (question.options) {
|
|
354
|
+
inquirerQuestion.choices = question.options.map(opt => ({
|
|
355
|
+
name: opt.description ? `${opt.label} - ${opt.description}` : opt.label,
|
|
356
|
+
value: opt.value,
|
|
357
|
+
}));
|
|
358
|
+
}
|
|
359
|
+
if (question.validation?.required) {
|
|
360
|
+
inquirerQuestion.validate = (input) => {
|
|
361
|
+
if (!input)
|
|
362
|
+
return "This field is required";
|
|
363
|
+
return true;
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
const { answer } = await inquirer_1.default.prompt([inquirerQuestion]);
|
|
367
|
+
return answer;
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Process answer and update ASL
|
|
371
|
+
*/
|
|
372
|
+
async processAnswer(question, answer) {
|
|
373
|
+
const [section, ...rest] = question.id.split(".");
|
|
374
|
+
switch (section) {
|
|
375
|
+
case "project":
|
|
376
|
+
this.updateProject(rest.join("."), answer);
|
|
377
|
+
break;
|
|
378
|
+
case "entities":
|
|
379
|
+
await this.updateEntities(rest, answer);
|
|
380
|
+
break;
|
|
381
|
+
case "auth":
|
|
382
|
+
this.updateAuth(rest.join("."), answer);
|
|
383
|
+
break;
|
|
384
|
+
case "permissions":
|
|
385
|
+
this.updatePermissions(rest.join("."), answer);
|
|
386
|
+
break;
|
|
387
|
+
case "pages":
|
|
388
|
+
await this.updatePages(rest, answer);
|
|
389
|
+
break;
|
|
390
|
+
case "design":
|
|
391
|
+
this.updateDesign(rest.join("."), answer);
|
|
392
|
+
break;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* Update project section
|
|
397
|
+
*/
|
|
398
|
+
updateProject(field, value) {
|
|
399
|
+
if (!this.asl.project) {
|
|
400
|
+
this.asl.project = {
|
|
401
|
+
name: "",
|
|
402
|
+
description: "",
|
|
403
|
+
framework: "nextjs",
|
|
404
|
+
backend: "instantdb",
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
this.asl.project[field] = value;
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Update entities section (uses AI to parse)
|
|
411
|
+
*/
|
|
412
|
+
async updateEntities(path, value) {
|
|
413
|
+
if (!this.asl.entities)
|
|
414
|
+
this.asl.entities = {};
|
|
415
|
+
if (path[0] === "list") {
|
|
416
|
+
// Parse entity list
|
|
417
|
+
const entityNames = value.split(",").map((e) => e.trim());
|
|
418
|
+
entityNames.forEach((name) => {
|
|
419
|
+
if (!this.asl.entities[name]) {
|
|
420
|
+
this.asl.entities[name] = {
|
|
421
|
+
name,
|
|
422
|
+
fields: [],
|
|
423
|
+
timestamps: true,
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
else if (path[1] === "fields") {
|
|
429
|
+
// Parse fields for specific entity
|
|
430
|
+
const entityName = path[0];
|
|
431
|
+
if (!entityName)
|
|
432
|
+
return;
|
|
433
|
+
const fields = await this.parseFieldsWithAI(entityName, value);
|
|
434
|
+
if (this.asl.entities && this.asl.entities[entityName]) {
|
|
435
|
+
this.asl.entities[entityName].fields = fields;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Use AI to parse field definitions from natural language
|
|
441
|
+
*/
|
|
442
|
+
async parseFieldsWithAI(entityName, userInput) {
|
|
443
|
+
const spinner = (0, ora_1.default)(`Parsing fields for ${entityName}...`).start();
|
|
444
|
+
try {
|
|
445
|
+
const prompt = `Parse the following field definitions for entity "${entityName}" into structured JSON.
|
|
446
|
+
|
|
447
|
+
User input: ${userInput}
|
|
448
|
+
|
|
449
|
+
Return a JSON array of fields with this structure:
|
|
450
|
+
{
|
|
451
|
+
"fields": [
|
|
452
|
+
{
|
|
453
|
+
"name": "fieldName",
|
|
454
|
+
"type": "string" | "number" | "boolean" | "date" | "json" | "ref",
|
|
455
|
+
"required": true | false,
|
|
456
|
+
"description": "field description"
|
|
457
|
+
}
|
|
458
|
+
]
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
Examples:
|
|
462
|
+
- "title (string, required), content (string), published (boolean)"
|
|
463
|
+
- "email (unique string), age (number), bio (optional string)"
|
|
464
|
+
|
|
465
|
+
Return only valid JSON, no markdown.`;
|
|
466
|
+
const response = await this.ai.generateText(prompt, {
|
|
467
|
+
temperature: 0.1,
|
|
468
|
+
maxTokens: 1000,
|
|
469
|
+
});
|
|
470
|
+
const parsed = JSON.parse(response);
|
|
471
|
+
spinner.succeed(`Parsed ${parsed.fields.length} fields for ${entityName}`);
|
|
472
|
+
return parsed.fields;
|
|
473
|
+
}
|
|
474
|
+
catch (error) {
|
|
475
|
+
spinner.fail("Failed to parse fields");
|
|
476
|
+
console.error(error);
|
|
477
|
+
return [];
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
/**
|
|
481
|
+
* Update auth section
|
|
482
|
+
*/
|
|
483
|
+
updateAuth(field, value) {
|
|
484
|
+
if (!this.asl.auth) {
|
|
485
|
+
this.asl.auth = {
|
|
486
|
+
provider: "email",
|
|
487
|
+
roles: [],
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
if (field === "provider") {
|
|
491
|
+
this.asl.auth.provider = value;
|
|
492
|
+
}
|
|
493
|
+
else if (field === "roles") {
|
|
494
|
+
// Convert array of role names to RoleSpec[]
|
|
495
|
+
if (Array.isArray(value)) {
|
|
496
|
+
this.asl.auth.roles = value.map(name => ({
|
|
497
|
+
name,
|
|
498
|
+
description: `${name} role`,
|
|
499
|
+
}));
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
else if (field === "needed") {
|
|
503
|
+
if (!value) {
|
|
504
|
+
delete this.asl.auth;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
/**
|
|
509
|
+
* Update permissions section
|
|
510
|
+
*/
|
|
511
|
+
updatePermissions(field, value) {
|
|
512
|
+
if (!this.asl.permissions)
|
|
513
|
+
this.asl.permissions = [];
|
|
514
|
+
if (field === "needed" && value) {
|
|
515
|
+
// Generate default permissions based on roles and entities
|
|
516
|
+
if (this.asl.auth?.roles && this.asl.entities) {
|
|
517
|
+
this.asl.auth.roles.forEach(role => {
|
|
518
|
+
Object.keys(this.asl.entities).forEach(entity => {
|
|
519
|
+
if (role.name === "admin") {
|
|
520
|
+
// Admins get full access
|
|
521
|
+
this.asl.permissions.push({
|
|
522
|
+
role: role.name,
|
|
523
|
+
resource: entity,
|
|
524
|
+
actions: ["create", "read", "update", "delete"],
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
else {
|
|
528
|
+
// Regular users get limited access
|
|
529
|
+
this.asl.permissions.push({
|
|
530
|
+
role: role.name,
|
|
531
|
+
resource: entity,
|
|
532
|
+
actions: ["read"],
|
|
533
|
+
condition: { type: "own", field: "user_id" },
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
});
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
/**
|
|
542
|
+
* Update pages section (uses AI to parse)
|
|
543
|
+
*/
|
|
544
|
+
async updatePages(path, value) {
|
|
545
|
+
if (!this.asl.pages)
|
|
546
|
+
this.asl.pages = [];
|
|
547
|
+
if (path[0] === "list") {
|
|
548
|
+
// Parse page list
|
|
549
|
+
const pages = await this.parsePagesWithAI(value);
|
|
550
|
+
this.asl.pages = pages;
|
|
551
|
+
}
|
|
552
|
+
else {
|
|
553
|
+
// Update specific page field
|
|
554
|
+
const match = path[0]?.match(/\d+/);
|
|
555
|
+
const pageIndex = parseInt(match?.[0] || "0");
|
|
556
|
+
const field = path[1];
|
|
557
|
+
if (!this.asl.pages || !this.asl.pages[pageIndex] || !field)
|
|
558
|
+
return;
|
|
559
|
+
this.asl.pages[pageIndex][field] = value;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* Use AI to parse page definitions
|
|
564
|
+
*/
|
|
565
|
+
async parsePagesWithAI(userInput) {
|
|
566
|
+
const spinner = (0, ora_1.default)("Parsing pages...").start();
|
|
567
|
+
try {
|
|
568
|
+
const prompt = `Parse the following page definitions into structured JSON.
|
|
569
|
+
|
|
570
|
+
User input: ${userInput}
|
|
571
|
+
|
|
572
|
+
Return a JSON array of pages with this structure:
|
|
573
|
+
{
|
|
574
|
+
"pages": [
|
|
575
|
+
{
|
|
576
|
+
"path": "/path",
|
|
577
|
+
"name": "PageName",
|
|
578
|
+
"type": "page",
|
|
579
|
+
"title": "Page Title",
|
|
580
|
+
"public": true | false
|
|
581
|
+
}
|
|
582
|
+
]
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
Examples:
|
|
586
|
+
- "Home (/), Posts (/posts), Profile (/profile)"
|
|
587
|
+
- "Landing page (public), Dashboard (/dashboard, protected), Settings"
|
|
588
|
+
|
|
589
|
+
Return only valid JSON, no markdown.`;
|
|
590
|
+
const response = await this.ai.generateText(prompt, {
|
|
591
|
+
temperature: 0.1,
|
|
592
|
+
maxTokens: 1000,
|
|
593
|
+
});
|
|
594
|
+
const parsed = JSON.parse(response);
|
|
595
|
+
spinner.succeed(`Parsed ${parsed.pages.length} pages`);
|
|
596
|
+
return parsed.pages;
|
|
597
|
+
}
|
|
598
|
+
catch (error) {
|
|
599
|
+
spinner.fail("Failed to parse pages");
|
|
600
|
+
console.error(error);
|
|
601
|
+
return [];
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
/**
|
|
605
|
+
* Update design section
|
|
606
|
+
*/
|
|
607
|
+
updateDesign(field, value) {
|
|
608
|
+
if (!this.asl.design) {
|
|
609
|
+
this.asl.design = {
|
|
610
|
+
theme: "light",
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
this.asl.design[field] = value;
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* Show diff preview
|
|
617
|
+
*/
|
|
618
|
+
async showDiffPreview() {
|
|
619
|
+
console.log(chalk_1.default.bold.cyan("\nš Generation Preview\n"));
|
|
620
|
+
const diff = this.planner.generateDiff(this.asl);
|
|
621
|
+
// Summary
|
|
622
|
+
console.log(chalk_1.default.bold("Summary:"));
|
|
623
|
+
console.log(chalk_1.default.green(` ā ${diff.summary.totalFiles} files will be generated`));
|
|
624
|
+
console.log(chalk_1.default.cyan(` ā ~${Math.round(diff.summary.linesAdded)} lines of code`));
|
|
625
|
+
console.log();
|
|
626
|
+
// File breakdown
|
|
627
|
+
console.log(chalk_1.default.bold("Files:"));
|
|
628
|
+
const filesByType = this.groupFilesByType(diff.files);
|
|
629
|
+
Object.entries(filesByType).forEach(([type, files]) => {
|
|
630
|
+
console.log(chalk_1.default.cyan(` ${this.fileTypeLabel(type)}: ${files.length}`));
|
|
631
|
+
});
|
|
632
|
+
console.log();
|
|
633
|
+
// Registries
|
|
634
|
+
if (diff.registries.length > 0) {
|
|
635
|
+
console.log(chalk_1.default.bold("Registries:"));
|
|
636
|
+
diff.registries.forEach(reg => {
|
|
637
|
+
console.log(chalk_1.default.cyan(` ${reg.type}: ${reg.added.length} items`));
|
|
638
|
+
});
|
|
639
|
+
console.log();
|
|
640
|
+
}
|
|
641
|
+
// Warnings
|
|
642
|
+
if (diff.warnings && diff.warnings.length > 0) {
|
|
643
|
+
console.log(chalk_1.default.bold.yellow("ā Warnings:"));
|
|
644
|
+
diff.warnings.forEach(warning => {
|
|
645
|
+
console.log(chalk_1.default.yellow(` - ${warning}`));
|
|
646
|
+
});
|
|
647
|
+
console.log();
|
|
648
|
+
}
|
|
649
|
+
// Show sample files
|
|
650
|
+
console.log(chalk_1.default.bold("Sample files:\n"));
|
|
651
|
+
diff.files.slice(0, 5).forEach(file => {
|
|
652
|
+
console.log(chalk_1.default.dim(` ${file.path}`));
|
|
653
|
+
if (file.preview) {
|
|
654
|
+
console.log(chalk_1.default.dim(` ${file.preview.split("\n").slice(0, 2).join("\n ")}`));
|
|
655
|
+
}
|
|
656
|
+
});
|
|
657
|
+
if (diff.files.length > 5) {
|
|
658
|
+
console.log(chalk_1.default.dim(` ... and ${diff.files.length - 5} more files`));
|
|
659
|
+
}
|
|
660
|
+
console.log();
|
|
661
|
+
}
|
|
662
|
+
/**
|
|
663
|
+
* Get user approval
|
|
664
|
+
*/
|
|
665
|
+
async getApproval() {
|
|
666
|
+
const { approve } = await inquirer_1.default.prompt([
|
|
667
|
+
{
|
|
668
|
+
type: "confirm",
|
|
669
|
+
name: "approve",
|
|
670
|
+
message: "Generate these files?",
|
|
671
|
+
default: true,
|
|
672
|
+
},
|
|
673
|
+
]);
|
|
674
|
+
return approve;
|
|
675
|
+
}
|
|
676
|
+
/**
|
|
677
|
+
* Save ASL to .mycontext/asl.json
|
|
678
|
+
*/
|
|
679
|
+
async saveASL() {
|
|
680
|
+
const spinner = (0, ora_1.default)("Saving manifest...").start();
|
|
681
|
+
try {
|
|
682
|
+
// Ensure .mycontext directory exists
|
|
683
|
+
const mycontextDir = path_1.default.join(process.cwd(), ".mycontext");
|
|
684
|
+
await promises_1.default.mkdir(mycontextDir, { recursive: true });
|
|
685
|
+
// Save ASL
|
|
686
|
+
const aslPath = path_1.default.join(mycontextDir, "asl.json");
|
|
687
|
+
await promises_1.default.writeFile(aslPath, JSON.stringify(this.asl, null, 2), "utf-8");
|
|
688
|
+
spinner.succeed(`Manifest saved to ${chalk_1.default.cyan(".mycontext/asl.json")}`);
|
|
689
|
+
}
|
|
690
|
+
catch (error) {
|
|
691
|
+
spinner.fail("Failed to save manifest");
|
|
692
|
+
throw error;
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
// ============================================================================
|
|
696
|
+
// HELPER METHODS
|
|
697
|
+
// ============================================================================
|
|
698
|
+
categorizeQuestions(questions) {
|
|
699
|
+
const categorized = {};
|
|
700
|
+
questions.forEach(q => {
|
|
701
|
+
if (!categorized[q.category]) {
|
|
702
|
+
categorized[q.category] = [];
|
|
703
|
+
}
|
|
704
|
+
categorized[q.category].push(q);
|
|
705
|
+
});
|
|
706
|
+
return categorized;
|
|
707
|
+
}
|
|
708
|
+
categoryLabel(category) {
|
|
709
|
+
const labels = {
|
|
710
|
+
project: "Project Information",
|
|
711
|
+
entities: "Data Models (Entities)",
|
|
712
|
+
auth: "Authentication",
|
|
713
|
+
permissions: "Permissions (RBAC)",
|
|
714
|
+
pages: "Pages & Routes",
|
|
715
|
+
design: "Design & Styling",
|
|
716
|
+
};
|
|
717
|
+
return labels[category] || category;
|
|
718
|
+
}
|
|
719
|
+
mapQuestionType(type) {
|
|
720
|
+
const mapping = {
|
|
721
|
+
text: "input",
|
|
722
|
+
number: "number",
|
|
723
|
+
boolean: "confirm",
|
|
724
|
+
select: "list",
|
|
725
|
+
"multi-select": "checkbox",
|
|
726
|
+
"entity-builder": "input",
|
|
727
|
+
"field-builder": "input",
|
|
728
|
+
};
|
|
729
|
+
return mapping[type] || "input";
|
|
730
|
+
}
|
|
731
|
+
groupFilesByType(files) {
|
|
732
|
+
const grouped = {};
|
|
733
|
+
files.forEach(file => {
|
|
734
|
+
if (!grouped[file.type]) {
|
|
735
|
+
grouped[file.type] = [];
|
|
736
|
+
}
|
|
737
|
+
grouped[file.type].push(file);
|
|
738
|
+
});
|
|
739
|
+
return grouped;
|
|
740
|
+
}
|
|
741
|
+
fileTypeLabel(type) {
|
|
742
|
+
const labels = {
|
|
743
|
+
schema: "Schema",
|
|
744
|
+
type: "Types",
|
|
745
|
+
page: "Pages",
|
|
746
|
+
component: "Components",
|
|
747
|
+
action: "Server Actions",
|
|
748
|
+
config: "Configuration",
|
|
749
|
+
};
|
|
750
|
+
return labels[type] || type;
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
exports.InitInteractiveCommand = InitInteractiveCommand;
|
|
754
|
+
//# sourceMappingURL=init-interactive.js.map
|