mycontext-cli 0.4.7 → 0.4.9
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 +261 -67
- package/dist/agents/communication/AgentCommunicationManager.d.ts +27 -0
- package/dist/agents/communication/AgentCommunicationManager.d.ts.map +1 -0
- package/dist/agents/communication/AgentCommunicationManager.js +293 -0
- package/dist/agents/communication/AgentCommunicationManager.js.map +1 -0
- package/dist/agents/evolution/CodeEvolutionEngine.d.ts +92 -0
- package/dist/agents/evolution/CodeEvolutionEngine.d.ts.map +1 -0
- package/dist/agents/evolution/CodeEvolutionEngine.js +639 -0
- package/dist/agents/evolution/CodeEvolutionEngine.js.map +1 -0
- package/dist/agents/implementations/ArchitectAgent.d.ts +39 -0
- package/dist/agents/implementations/ArchitectAgent.d.ts.map +1 -0
- package/dist/agents/implementations/ArchitectAgent.js +345 -0
- package/dist/agents/implementations/ArchitectAgent.js.map +1 -0
- package/dist/agents/implementations/CodeGenSubAgent.d.ts +12 -0
- package/dist/agents/implementations/CodeGenSubAgent.d.ts.map +1 -1
- package/dist/agents/implementations/CodeGenSubAgent.js +303 -58
- package/dist/agents/implementations/CodeGenSubAgent.js.map +1 -1
- package/dist/agents/implementations/PromptConstructorAgent.d.ts +50 -0
- package/dist/agents/implementations/PromptConstructorAgent.d.ts.map +1 -0
- package/dist/agents/implementations/PromptConstructorAgent.js +481 -0
- package/dist/agents/implementations/PromptConstructorAgent.js.map +1 -0
- package/dist/agents/implementations/SecurityAgent.d.ts +31 -0
- package/dist/agents/implementations/SecurityAgent.d.ts.map +1 -0
- package/dist/agents/implementations/SecurityAgent.js +453 -0
- package/dist/agents/implementations/SecurityAgent.js.map +1 -0
- package/dist/agents/intelligence/ProjectIntelligence.d.ts +127 -0
- package/dist/agents/intelligence/ProjectIntelligence.d.ts.map +1 -0
- package/dist/agents/intelligence/ProjectIntelligence.js +456 -0
- package/dist/agents/intelligence/ProjectIntelligence.js.map +1 -0
- package/dist/agents/interfaces/AgentCommunication.d.ts +65 -0
- package/dist/agents/interfaces/AgentCommunication.d.ts.map +1 -0
- package/dist/agents/interfaces/AgentCommunication.js +13 -0
- package/dist/agents/interfaces/AgentCommunication.js.map +1 -0
- package/dist/agents/learning/CrossProjectLearning.d.ts +99 -0
- package/dist/agents/learning/CrossProjectLearning.d.ts.map +1 -0
- package/dist/agents/learning/CrossProjectLearning.js +517 -0
- package/dist/agents/learning/CrossProjectLearning.js.map +1 -0
- package/dist/cli.js +49 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/agent-flow.d.ts +21 -0
- package/dist/commands/agent-flow.d.ts.map +1 -0
- package/dist/commands/agent-flow.js +225 -0
- package/dist/commands/agent-flow.js.map +1 -0
- package/dist/commands/generate-components.d.ts +0 -13
- package/dist/commands/generate-components.d.ts.map +1 -1
- package/dist/commands/generate-components.js +42 -422
- package/dist/commands/generate-components.js.map +1 -1
- package/dist/commands/generate.d.ts +5 -1
- package/dist/commands/generate.d.ts.map +1 -1
- package/dist/commands/generate.js +381 -23
- package/dist/commands/generate.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +12 -6
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/predict.d.ts +36 -0
- package/dist/commands/predict.d.ts.map +1 -0
- package/dist/commands/predict.js +539 -0
- package/dist/commands/predict.js.map +1 -0
- package/dist/utils/clean.d.ts +6 -0
- package/dist/utils/clean.d.ts.map +1 -0
- package/dist/utils/clean.js +220 -0
- package/dist/utils/clean.js.map +1 -0
- package/dist/utils/githubModelsClient.d.ts.map +1 -1
- package/dist/utils/githubModelsClient.js +33 -16
- package/dist/utils/githubModelsClient.js.map +1 -1
- package/dist/utils/hybridAIClient.d.ts +1 -0
- package/dist/utils/hybridAIClient.d.ts.map +1 -1
- package/dist/utils/hybridAIClient.js +23 -9
- package/dist/utils/hybridAIClient.js.map +1 -1
- package/dist/utils/ollamaClient.d.ts.map +1 -1
- package/dist/utils/ollamaClient.js +6 -4
- package/dist/utils/ollamaClient.js.map +1 -1
- package/package.json +2 -1
|
@@ -88,6 +88,9 @@ class GenerateCommand {
|
|
|
88
88
|
case "components-list":
|
|
89
89
|
result = await this.generateComponentList(projectContext, options);
|
|
90
90
|
break;
|
|
91
|
+
case "project-structure":
|
|
92
|
+
result = await this.generateProjectStructure(projectContext, options);
|
|
93
|
+
break;
|
|
91
94
|
case "all": {
|
|
92
95
|
// Run sequentially with chaining: PRD -> Types(from PRD) -> (optional Brand) -> Components List(from Types + optional brand)
|
|
93
96
|
// PRD
|
|
@@ -129,6 +132,20 @@ class GenerateCommand {
|
|
|
129
132
|
throw new Error(compListRes.error || "Components list generation failed");
|
|
130
133
|
const compListPath = await this.saveGeneratedContent("components-list", compListRes.content, options);
|
|
131
134
|
console.log(chalk_1.default.green(`✅ Generated components-list saved to: ${compListPath}`));
|
|
135
|
+
// Project Structure (use all context artifacts)
|
|
136
|
+
const structureRes = await this.generateProjectStructure(projectContext, {
|
|
137
|
+
...options,
|
|
138
|
+
contextFile: brandPath
|
|
139
|
+
? `${prdPath},${typesPath},${brandPath},${compListPath}`
|
|
140
|
+
: `${prdPath},${typesPath},${compListPath}`,
|
|
141
|
+
});
|
|
142
|
+
if (structureRes.success && structureRes.content) {
|
|
143
|
+
const structurePath = await this.saveGeneratedContent("project-structure", structureRes.content, options);
|
|
144
|
+
console.log(chalk_1.default.green(`✅ Generated project structure saved to: ${structurePath}`));
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
console.log(chalk_1.default.yellow(`⚠️ Project structure generation skipped or failed: ${structureRes.error || "unknown"}`));
|
|
148
|
+
}
|
|
132
149
|
this.spinner.success({ text: "All artifacts generated" });
|
|
133
150
|
return;
|
|
134
151
|
}
|
|
@@ -145,19 +162,37 @@ class GenerateCommand {
|
|
|
145
162
|
try {
|
|
146
163
|
const coreStatePath = path_1.default.join(process.cwd(), ".mycontext", "core.json");
|
|
147
164
|
const hasCoreState = await fs.pathExists(coreStatePath);
|
|
148
|
-
const coreState = hasCoreState
|
|
165
|
+
const coreState = hasCoreState
|
|
166
|
+
? await fs.readJson(coreStatePath)
|
|
167
|
+
: {};
|
|
149
168
|
if (!coreState?.name) {
|
|
150
169
|
const raw = await fs.readFile(outputPath, "utf8");
|
|
151
170
|
const json = JSON.parse(raw);
|
|
152
171
|
const groups = Array.isArray(json.groups) ? json.groups : [];
|
|
172
|
+
const metaCandidates = Array.isArray(json?.metadata?.coreCandidates)
|
|
173
|
+
? json.metadata.coreCandidates
|
|
174
|
+
: [];
|
|
175
|
+
const candidateKeys = new Set(metaCandidates.map((c) => `${c.group}::${c.name}`));
|
|
153
176
|
const flat = [];
|
|
154
177
|
for (const g of groups)
|
|
155
178
|
for (const c of g.components || [])
|
|
156
179
|
flat.push({ name: c.name, group: g.name, tags: c.tags });
|
|
157
180
|
if (flat.length) {
|
|
158
|
-
|
|
159
|
-
const
|
|
160
|
-
|
|
181
|
+
// Preferred by metadata first; then heuristic-based; then rest
|
|
182
|
+
const byKey = new Map(flat.map((c) => [`${c.group}::${c.name}`, c]));
|
|
183
|
+
const metaList = metaCandidates
|
|
184
|
+
.map((c) => byKey.get(`${c.group}::${c.name}`))
|
|
185
|
+
.filter(Boolean);
|
|
186
|
+
const heuristic = flat.filter((c) => (/core|canvas|layout|board/i.test(c.name) ||
|
|
187
|
+
(Array.isArray(c.tags) &&
|
|
188
|
+
c.tags.some((t) => /core|canvas/i.test(String(t))))) &&
|
|
189
|
+
!candidateKeys.has(`${c.group}::${c.name}`));
|
|
190
|
+
const rest = flat.filter((c) => !candidateKeys.has(`${c.group}::${c.name}`) &&
|
|
191
|
+
!heuristic.includes(c));
|
|
192
|
+
const ordered = [...metaList, ...heuristic, ...rest];
|
|
193
|
+
const choices = ordered.map((c) => ({
|
|
194
|
+
title: `${c.group} / ${c.name}` +
|
|
195
|
+
(candidateKeys.has(`${c.group}::${c.name}`) ? " ⭐" : ""),
|
|
161
196
|
value: `${c.group}::${c.name}`,
|
|
162
197
|
}));
|
|
163
198
|
const pickNow = await (0, prompts_1.default)({
|
|
@@ -170,14 +205,24 @@ class GenerateCommand {
|
|
|
170
205
|
});
|
|
171
206
|
if (pickNow?.do) {
|
|
172
207
|
const ans = await (0, prompts_1.default)({
|
|
173
|
-
type: "
|
|
208
|
+
type: "autocomplete",
|
|
174
209
|
name: "pick",
|
|
175
210
|
message: "Pick the core component (design anchor)",
|
|
176
211
|
choices,
|
|
212
|
+
suggest: (input, choicesArg) => {
|
|
213
|
+
const q = (input || "").toLowerCase();
|
|
214
|
+
return Promise.resolve(choicesArg.filter((c) => String(c.title).toLowerCase().includes(q)));
|
|
215
|
+
},
|
|
177
216
|
});
|
|
178
217
|
if (ans?.pick) {
|
|
179
218
|
const [g, n] = String(ans.pick).split("::");
|
|
180
|
-
const state = {
|
|
219
|
+
const state = {
|
|
220
|
+
name: n,
|
|
221
|
+
group: g || "core",
|
|
222
|
+
ready: false,
|
|
223
|
+
updatedAt: new Date().toISOString(),
|
|
224
|
+
refinements: [],
|
|
225
|
+
};
|
|
181
226
|
await fs.ensureDir(path_1.default.dirname(coreStatePath));
|
|
182
227
|
await fs.writeJson(coreStatePath, state, { spaces: 2 });
|
|
183
228
|
console.log(chalk_1.default.green(`✅ Core set to ${n} (group: ${g}).`));
|
|
@@ -313,14 +358,87 @@ class GenerateCommand {
|
|
|
313
358
|
return await this.getProjectContext();
|
|
314
359
|
}
|
|
315
360
|
async generateContext(projectContext, options) {
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
361
|
+
// If user provided --prd-file, import it as-is and return without AI
|
|
362
|
+
try {
|
|
363
|
+
const prdFile = options.prdFile ||
|
|
364
|
+
options.prdfile ||
|
|
365
|
+
options["prd-file"]; // accept variants
|
|
366
|
+
if (prdFile) {
|
|
367
|
+
const abs = path_1.default.isAbsolute(prdFile)
|
|
368
|
+
? prdFile
|
|
369
|
+
: path_1.default.join(process.cwd(), prdFile);
|
|
370
|
+
if (await fs.pathExists(abs)) {
|
|
371
|
+
const content = await fs.readFile(abs, "utf8");
|
|
372
|
+
return {
|
|
373
|
+
success: true,
|
|
374
|
+
content,
|
|
375
|
+
provider: "hybrid",
|
|
376
|
+
metadata: { imported: true },
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
catch { }
|
|
382
|
+
const prompt = [
|
|
383
|
+
`[mycontext] Plan: plan → generate → QA → docs → preview (→ checks)`,
|
|
384
|
+
`Create a production-grade PRD for: ${projectContext.description || "MyContext project"}`,
|
|
385
|
+
"",
|
|
386
|
+
"Assume default stack unless user specifies otherwise:",
|
|
387
|
+
"- Frontend: Next.js (App Router) with Shadcn UI",
|
|
388
|
+
"- Auth/DB/File storage: InstantDB (auth, data, file persistence)",
|
|
389
|
+
"- Accessibility: Radix UI principles (keyboard nav, ARIA, focus mgmt)",
|
|
390
|
+
"- Testing: Jest + React Testing Library",
|
|
391
|
+
"",
|
|
392
|
+
"Follow this step-by-step process first, then structure the PRD (avoid generic statements):",
|
|
393
|
+
"",
|
|
394
|
+
"Process:",
|
|
395
|
+
"1) Problem Statement: restate the project description from the user's perspective (who is impacted, what problem exists today, why now)",
|
|
396
|
+
"2) Desired Outcomes & Success Metrics: what users want to eliminate/improve/create; define measurable outcomes",
|
|
397
|
+
"3) Primary Users & Roles: identify roles and their responsibilities/permissions",
|
|
398
|
+
"4) Current State & Pain Points: briefly describe how it works today (or assumptions) and pain points",
|
|
399
|
+
"5) Improvement Opportunities: what to eliminate/automate/simplify; list key constraints/assumptions",
|
|
400
|
+
"6) Solution Approach & Scope: MVP vs later; boundaries and tradeoffs",
|
|
401
|
+
"7) User Journeys & Flows: derive end-to-end flows per role (happy path + alternatives + edge cases)",
|
|
402
|
+
"8) Detailed User Stories: action-oriented stories with context and goals",
|
|
403
|
+
"9) Acceptance Criteria: Given/When/Then per critical story",
|
|
404
|
+
"10) Entity Model & Relationships: core entities and associations",
|
|
405
|
+
"11) Information Architecture: pages/routes and the shadcn/ui primitives likely used",
|
|
406
|
+
"12) Technical Requirements: SSR/ISR, server actions, data fetching, security, telemetry",
|
|
407
|
+
"13) Non-Functional Requirements: performance budgets, accessibility, i18n, reliability",
|
|
408
|
+
"14) Risks & Open Questions: what could derail the plan and what needs decisions",
|
|
409
|
+
"",
|
|
410
|
+
"Then, produce the PRD with the following sections:",
|
|
411
|
+
"Structure the PRD with depth (avoid generic statements):",
|
|
412
|
+
"1) Overview: goal, non-goals, success metrics",
|
|
413
|
+
"2) User Roles & Responsibilities: distinct roles with responsibilities and permissions",
|
|
414
|
+
"3) Personas (optional): brief traits that influence UX",
|
|
415
|
+
"4) User Journeys & Flows: end-to-end scenarios per role (step-by-step)",
|
|
416
|
+
"5) Detailed User Stories: action-oriented; include negative flows and edge cases",
|
|
417
|
+
"6) Entity Model & Relationships: main entities and associations",
|
|
418
|
+
"7) Information Architecture: pages/routes, key UI patterns (shadcn components)",
|
|
419
|
+
"8) Technical Requirements: SSR/ISR, data fetching, server actions, security, telemetry",
|
|
420
|
+
"9) Non-Functional Requirements: performance budgets, accessibility, internationalization",
|
|
421
|
+
"10) Acceptance Criteria: Gherkin-style Given/When/Then per critical story",
|
|
422
|
+
"11) Risks & Open Questions",
|
|
423
|
+
"",
|
|
424
|
+
"Include two diagrams in fenced code blocks:",
|
|
425
|
+
"- Sequence diagram of a critical flow (e.g., auth → gameplay → result)",
|
|
426
|
+
"- (Optional) Flowchart for a user journey",
|
|
427
|
+
"",
|
|
428
|
+
"```mermaid",
|
|
429
|
+
"sequenceDiagram",
|
|
430
|
+
" autonumber",
|
|
431
|
+
" participant User",
|
|
432
|
+
" participant App as Next.js App",
|
|
433
|
+
" participant DB as InstantDB",
|
|
434
|
+
" User->>App: Perform primary action",
|
|
435
|
+
" App->>DB: Persist/fetch data",
|
|
436
|
+
" DB-->>App: Data OK",
|
|
437
|
+
" App-->>User: Updated UI",
|
|
438
|
+
"```",
|
|
439
|
+
"",
|
|
440
|
+
"Write concretely. Ground content in the provided description. No placeholders.",
|
|
441
|
+
].join("\n");
|
|
324
442
|
try {
|
|
325
443
|
this.spinner.updateText(`Generating PRD with AI (${await this.ai.getActiveProviderName()}/${await this.ai.getActiveTextModelName()})...`);
|
|
326
444
|
const { text, provider } = await this.ai.generateText(prompt, {
|
|
@@ -377,6 +495,7 @@ Include: data models, API interfaces, component props, utility types. Keep it co
|
|
|
377
495
|
};
|
|
378
496
|
}
|
|
379
497
|
}
|
|
498
|
+
// specs flow removed; PRD is the requirements and Brand is the design.
|
|
380
499
|
sanitizeTypeScriptOutput(raw) {
|
|
381
500
|
// Prefer fenced code block content if present
|
|
382
501
|
const block = raw.match(/```(?:ts|tsx|typescript|js)?\n([\s\S]*?)```/);
|
|
@@ -437,6 +556,56 @@ Include: identity, colors, typography, visual elements, brand voice. Keep it con
|
|
|
437
556
|
};
|
|
438
557
|
}
|
|
439
558
|
}
|
|
559
|
+
async generateProjectStructure(projectContext, options) {
|
|
560
|
+
this.spinner.updateText("Generating project structure...");
|
|
561
|
+
try {
|
|
562
|
+
// Read existing context artifacts
|
|
563
|
+
const contextArtifacts = await this.readContextArtifacts();
|
|
564
|
+
const prd = contextArtifacts.prd;
|
|
565
|
+
const types = contextArtifacts.types;
|
|
566
|
+
// Read component list specifically
|
|
567
|
+
const compListPath = path_1.default.join(process.cwd(), ".mycontext", "04-component-list.json");
|
|
568
|
+
let compList = null;
|
|
569
|
+
if (await fs.pathExists(compListPath)) {
|
|
570
|
+
try {
|
|
571
|
+
const compListRaw = await fs.readFile(compListPath, "utf8");
|
|
572
|
+
compList = JSON.parse(compListRaw);
|
|
573
|
+
}
|
|
574
|
+
catch {
|
|
575
|
+
// Ignore parse errors
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
if (!compList?.groups?.length) {
|
|
579
|
+
throw new Error("Component list is required for project structure generation. Run: mycontext generate components-list");
|
|
580
|
+
}
|
|
581
|
+
const structurePrompt = this.buildProjectStructurePrompt(projectContext.description || "", prd, types, compList);
|
|
582
|
+
const candidates = this.getModelCandidates(options);
|
|
583
|
+
const result = await this.ai.generateText(structurePrompt, {
|
|
584
|
+
modelCandidates: candidates,
|
|
585
|
+
temperature: 0.3,
|
|
586
|
+
});
|
|
587
|
+
if (!result?.text) {
|
|
588
|
+
throw new Error("Failed to generate project structure");
|
|
589
|
+
}
|
|
590
|
+
const sanitized = this.sanitizeMarkdownOutput(result.text);
|
|
591
|
+
return {
|
|
592
|
+
success: true,
|
|
593
|
+
content: sanitized,
|
|
594
|
+
provider: result.provider ||
|
|
595
|
+
"hybrid",
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
catch (error) {
|
|
599
|
+
console.error(chalk_1.default.red(`Project structure generation failed: ${error.message}`));
|
|
600
|
+
// Fallback: Generate basic structure based on component list
|
|
601
|
+
const fallback = this.buildFallbackProjectStructure(projectContext);
|
|
602
|
+
return {
|
|
603
|
+
success: true,
|
|
604
|
+
content: fallback,
|
|
605
|
+
provider: "hybrid",
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
}
|
|
440
609
|
async generateComponentList(projectContext, options) {
|
|
441
610
|
const ctx = await this.readContextArtifacts();
|
|
442
611
|
// Gate on core readiness if core.json exists
|
|
@@ -500,12 +669,21 @@ Include: identity, colors, typography, visual elements, brand voice. Keep it con
|
|
|
500
669
|
" ]",
|
|
501
670
|
" }",
|
|
502
671
|
" ]",
|
|
672
|
+
" ,",
|
|
673
|
+
' "metadata": {',
|
|
674
|
+
' "coreCandidates": [',
|
|
675
|
+
' { "name": string, "group": string, "reason": string },',
|
|
676
|
+
' { "name": string, "group": string, "reason": string },',
|
|
677
|
+
' { "name": string, "group": string, "reason": string }',
|
|
678
|
+
" ]",
|
|
679
|
+
" }",
|
|
503
680
|
"}",
|
|
504
681
|
"",
|
|
505
682
|
"Rules:",
|
|
506
683
|
"- No code fences, no comments, no trailing commas.",
|
|
507
684
|
"- Use only the fields above.",
|
|
508
685
|
"- Ensure arrays/objects have no extra commas.",
|
|
686
|
+
"- Provide up to 3 'coreCandidates' that best represent the design anchor (canvas/layout), with a short reason.",
|
|
509
687
|
].join("\n");
|
|
510
688
|
try {
|
|
511
689
|
this.spinner.updateText(`Generating component list with AI (${await this.ai.getActiveProviderName()}/${await this.ai.getActiveTextModelName()})...`);
|
|
@@ -650,7 +828,31 @@ Include: identity, colors, typography, visual elements, brand voice. Keep it con
|
|
|
650
828
|
switch (type) {
|
|
651
829
|
case "context": {
|
|
652
830
|
outputPath = path_1.default.join(outputDir, "01-prd.md");
|
|
653
|
-
|
|
831
|
+
const preserve = Boolean(options.preservePrd);
|
|
832
|
+
if (preserve && (await fs.pathExists(outputPath))) {
|
|
833
|
+
// Preserve existing PRD; write a copy next to it for reference
|
|
834
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
835
|
+
const alt = path_1.default.join(outputDir, `01-prd.imported-${timestamp}.md`);
|
|
836
|
+
await fs.writeFile(alt, content);
|
|
837
|
+
}
|
|
838
|
+
else {
|
|
839
|
+
await fs.writeFile(outputPath, content);
|
|
840
|
+
}
|
|
841
|
+
// Also write split artifacts for improved DX (idempotent)
|
|
842
|
+
try {
|
|
843
|
+
const sections = this.splitPrdContent(content);
|
|
844
|
+
if (sections.brief.trim().length > 0) {
|
|
845
|
+
await fs.writeFile(path_1.default.join(outputDir, "01a-brief.md"), sections.brief);
|
|
846
|
+
}
|
|
847
|
+
if (sections.requirements.trim().length > 0) {
|
|
848
|
+
await fs.writeFile(path_1.default.join(outputDir, "01b-requirements.md"), sections.requirements);
|
|
849
|
+
}
|
|
850
|
+
if (sections.flows.trim().length > 0) {
|
|
851
|
+
await fs.writeFile(path_1.default.join(outputDir, "01c-flows.md"), sections.flows);
|
|
852
|
+
}
|
|
853
|
+
console.log(chalk_1.default.gray(" ↳ Also wrote split context: 01a-brief.md, 01b-requirements.md, 01c-flows.md"));
|
|
854
|
+
}
|
|
855
|
+
catch { }
|
|
654
856
|
break;
|
|
655
857
|
}
|
|
656
858
|
case "types": {
|
|
@@ -690,6 +892,11 @@ Include: identity, colors, typography, visual elements, brand voice. Keep it con
|
|
|
690
892
|
}
|
|
691
893
|
break;
|
|
692
894
|
}
|
|
895
|
+
case "project-structure": {
|
|
896
|
+
outputPath = path_1.default.join(outputDir, "05-project-structure.md");
|
|
897
|
+
await fs.writeFile(outputPath, content);
|
|
898
|
+
break;
|
|
899
|
+
}
|
|
693
900
|
default: {
|
|
694
901
|
// Fallback: keep timestamped file for unknown types
|
|
695
902
|
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
@@ -749,11 +956,40 @@ Include: identity, colors, typography, visual elements, brand voice. Keep it con
|
|
|
749
956
|
return Array.from(out.values());
|
|
750
957
|
}
|
|
751
958
|
sanitizeMarkdownOutput(raw) {
|
|
752
|
-
//
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
959
|
+
// Preserve fenced code blocks (e.g., ```mermaid, ```ts) while stripping stray outer fences only
|
|
960
|
+
// If the model wrapped the whole document in a single fence, unwrap once; otherwise keep inner fences.
|
|
961
|
+
const singleFence = raw.match(/^```[a-zA-Z]*\n[\s\S]*\n```\s*$/);
|
|
962
|
+
if (singleFence) {
|
|
963
|
+
// unwrap outer fence only
|
|
964
|
+
return raw
|
|
965
|
+
.replace(/^```[a-zA-Z]*\n/, "")
|
|
966
|
+
.replace(/\n```\s*$/, "")
|
|
967
|
+
.trim();
|
|
968
|
+
}
|
|
969
|
+
return raw.trim();
|
|
970
|
+
}
|
|
971
|
+
splitPrdContent(raw) {
|
|
972
|
+
try {
|
|
973
|
+
const text = String(raw);
|
|
974
|
+
// Heuristic: look for section anchors
|
|
975
|
+
const idxReq = text.search(/###\s+Requirements|###\s+Detailed User Stories|###\s+Acceptance Criteria/i);
|
|
976
|
+
const idxFlows = text.search(/###\s+Flows|###\s+User Journeys|```mermaid/i);
|
|
977
|
+
let brief = text;
|
|
978
|
+
let requirements = "";
|
|
979
|
+
let flows = "";
|
|
980
|
+
if (idxReq !== -1) {
|
|
981
|
+
brief = text.slice(0, idxReq).trim();
|
|
982
|
+
const secondCut = idxFlows !== -1 && idxFlows > idxReq ? idxFlows : text.length;
|
|
983
|
+
requirements = text.slice(idxReq, secondCut).trim();
|
|
984
|
+
if (idxFlows !== -1) {
|
|
985
|
+
flows = text.slice(idxFlows).trim();
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
return { brief, requirements, flows };
|
|
989
|
+
}
|
|
990
|
+
catch {
|
|
991
|
+
return { brief: raw, requirements: "", flows: "" };
|
|
992
|
+
}
|
|
757
993
|
}
|
|
758
994
|
buildSkeleton(type, projectContext) {
|
|
759
995
|
switch (type) {
|
|
@@ -856,7 +1092,26 @@ Include: identity, colors, typography, visual elements, brand voice. Keep it con
|
|
|
856
1092
|
const readOrEmpty = async (rel) => (await fs.pathExists(path_1.default.join(cwd, rel)))
|
|
857
1093
|
? await fs.readFile(path_1.default.join(cwd, rel), "utf8")
|
|
858
1094
|
: "";
|
|
859
|
-
|
|
1095
|
+
// Prefer split files if present
|
|
1096
|
+
let prd = "";
|
|
1097
|
+
const brief = (await readOrEmpty(".mycontext/01a-brief.md")) ||
|
|
1098
|
+
(await readOrEmpty(".mycontext/01-brief.md"));
|
|
1099
|
+
const reqs = (await readOrEmpty(".mycontext/01b-requirements.md")) ||
|
|
1100
|
+
(await readOrEmpty(".mycontext/02-requirements.md"));
|
|
1101
|
+
const flows = (await readOrEmpty(".mycontext/01c-flows.md")) ||
|
|
1102
|
+
(await readOrEmpty(".mycontext/03-flows.md"));
|
|
1103
|
+
if (brief || reqs || flows) {
|
|
1104
|
+
prd = [
|
|
1105
|
+
brief ? "## Brief\n\n" + brief : "",
|
|
1106
|
+
reqs ? "\n\n## Requirements\n\n" + reqs : "",
|
|
1107
|
+
flows ? "\n\n## Flows\n\n" + flows : "",
|
|
1108
|
+
]
|
|
1109
|
+
.filter(Boolean)
|
|
1110
|
+
.join("");
|
|
1111
|
+
}
|
|
1112
|
+
if (!prd) {
|
|
1113
|
+
prd = await readOrEmpty(".mycontext/01-prd.md");
|
|
1114
|
+
}
|
|
860
1115
|
const types = await readOrEmpty(".mycontext/02-types.ts");
|
|
861
1116
|
const brand = await readOrEmpty(".mycontext/03-branding.md");
|
|
862
1117
|
// discover shadcn/ui primitives from components/ui
|
|
@@ -966,11 +1221,106 @@ Include: identity, colors, typography, visual elements, brand voice. Keep it con
|
|
|
966
1221
|
repairJson(raw) {
|
|
967
1222
|
// Remove any fenced code blocks markers
|
|
968
1223
|
let s = raw.replace(/```json\n?|```/gi, "");
|
|
969
|
-
// Remove stray
|
|
1224
|
+
// Remove stray tokens and trailing commas before ] or }
|
|
970
1225
|
s = s.replace(/\s*" \" ",?\s*/g, "");
|
|
971
1226
|
s = s.replace(/,\s*(\]|\})/g, "$1");
|
|
1227
|
+
// Quote bare identifiers for "name" fields, e.g., { "name": Dashboard } → { "name": "Dashboard" }
|
|
1228
|
+
// Handles simple PascalCase/word identifiers without quotes
|
|
1229
|
+
s = s.replace(/("name"\s*:\s*)([A-Za-z_][A-Za-z0-9_]*)/g, (_m, p1, p2) => `${p1}"${p2}"`);
|
|
972
1230
|
return s;
|
|
973
1231
|
}
|
|
1232
|
+
buildProjectStructurePrompt(description, prd, types, compList) {
|
|
1233
|
+
const groups = compList.groups || [];
|
|
1234
|
+
const componentSummary = groups
|
|
1235
|
+
.map((g) => `${g.name} (${g.components?.length || 0} components): ${g.description}`)
|
|
1236
|
+
.join("\n");
|
|
1237
|
+
return `Generate a Next.js project structure for: ${description}
|
|
1238
|
+
|
|
1239
|
+
DESIGN PRINCIPLES:
|
|
1240
|
+
- Prioritize dialogs for details/forms/interactions (reduce page count)
|
|
1241
|
+
- Use datatables for reports and data metrics
|
|
1242
|
+
- Minimize standalone pages - favor modal workflows
|
|
1243
|
+
- Follow Next.js App Router patterns
|
|
1244
|
+
|
|
1245
|
+
EXISTING CONTEXT:
|
|
1246
|
+
## PRD Summary:
|
|
1247
|
+
${prd.substring(0, 500)}...
|
|
1248
|
+
|
|
1249
|
+
## Component Groups:
|
|
1250
|
+
${componentSummary}
|
|
1251
|
+
|
|
1252
|
+
## Available Types:
|
|
1253
|
+
${types.substring(0, 300)}...
|
|
1254
|
+
|
|
1255
|
+
PROJECT STRUCTURE REQUIREMENTS:
|
|
1256
|
+
1. Generate a realistic Next.js 13+ app directory structure
|
|
1257
|
+
2. Create minimal pages - favor dialogs and datatables
|
|
1258
|
+
3. Include API routes for each data entity
|
|
1259
|
+
4. Structure for the specific app type and complexity
|
|
1260
|
+
5. Show how components will be organized
|
|
1261
|
+
6. Include middleware, layouts, and error boundaries
|
|
1262
|
+
7. Plan for state management and data fetching
|
|
1263
|
+
|
|
1264
|
+
Format as a markdown file with:
|
|
1265
|
+
- Clear directory tree structure
|
|
1266
|
+
- Explanation of each major directory/file
|
|
1267
|
+
- Component placement strategy
|
|
1268
|
+
- API route organization
|
|
1269
|
+
- State management approach
|
|
1270
|
+
|
|
1271
|
+
Generate the complete project structure now:`;
|
|
1272
|
+
}
|
|
1273
|
+
buildFallbackProjectStructure(projectContext) {
|
|
1274
|
+
const appType = projectContext.description?.toLowerCase() || "general";
|
|
1275
|
+
const isEcommerce = appType.includes("ecommerce") || appType.includes("shop");
|
|
1276
|
+
const isDashboard = appType.includes("dashboard") || appType.includes("admin");
|
|
1277
|
+
return `# Project Structure
|
|
1278
|
+
|
|
1279
|
+
## Core Next.js Structure
|
|
1280
|
+
\`\`\`
|
|
1281
|
+
${projectContext.name || "project"}/
|
|
1282
|
+
├── src/
|
|
1283
|
+
│ ├── app/
|
|
1284
|
+
│ │ ├── (auth)/
|
|
1285
|
+
│ │ │ ├── login/page.tsx
|
|
1286
|
+
│ │ │ └── register/page.tsx
|
|
1287
|
+
│ │ ├── dashboard/
|
|
1288
|
+
│ │ │ ├── page.tsx # Main dashboard with dialogs
|
|
1289
|
+
│ │ │ └── loading.tsx
|
|
1290
|
+
│ │ ├── api/
|
|
1291
|
+
│ │ │ ├── auth/route.ts
|
|
1292
|
+
${isEcommerce
|
|
1293
|
+
? "│ │ │ ├── products/route.ts\n│ │ │ ├── orders/route.ts\n│ │ │ └── cart/route.ts"
|
|
1294
|
+
: "│ │ │ ├── users/route.ts\n│ │ │ └── data/route.ts"}
|
|
1295
|
+
│ │ ├── globals.css
|
|
1296
|
+
│ │ ├── layout.tsx
|
|
1297
|
+
│ │ └── page.tsx # Landing page
|
|
1298
|
+
│ ├── components/
|
|
1299
|
+
│ │ ├── ui/ # shadcn/ui components
|
|
1300
|
+
│ │ ├── dialogs/ # Modal dialogs for forms/details
|
|
1301
|
+
│ │ │ ├── CreateDialog.tsx
|
|
1302
|
+
│ │ │ ├── EditDialog.tsx
|
|
1303
|
+
│ │ │ └── DetailsDialog.tsx
|
|
1304
|
+
│ │ ├── tables/ # Data tables for reports
|
|
1305
|
+
│ │ │ ├── DataTable.tsx
|
|
1306
|
+
│ │ │ └── ReportsTable.tsx
|
|
1307
|
+
│ │ └── forms/ # Reusable form components
|
|
1308
|
+
│ └── lib/
|
|
1309
|
+
│ ├── utils.ts
|
|
1310
|
+
│ ├── validations.ts
|
|
1311
|
+
│ └── api.ts
|
|
1312
|
+
├── components.json
|
|
1313
|
+
└── package.json
|
|
1314
|
+
\`\`\`
|
|
1315
|
+
|
|
1316
|
+
## Design Strategy
|
|
1317
|
+
- **Minimal Pages**: Use dialogs for forms and detail views
|
|
1318
|
+
- **Datatables**: Centralized data display with inline actions
|
|
1319
|
+
- **Modal-First**: Most interactions happen in dialogs
|
|
1320
|
+
- **API-Driven**: Each entity has dedicated API routes
|
|
1321
|
+
- **Component Organization**: Group by function, not feature
|
|
1322
|
+
`;
|
|
1323
|
+
}
|
|
974
1324
|
printNextStepsAfterGenerate(type) {
|
|
975
1325
|
try {
|
|
976
1326
|
switch (type) {
|
|
@@ -983,12 +1333,20 @@ Include: identity, colors, typography, visual elements, brand voice. Keep it con
|
|
|
983
1333
|
console.log(chalk_1.default.gray(" mycontext generate components-list # alias: component-list"));
|
|
984
1334
|
break;
|
|
985
1335
|
case "components-list":
|
|
986
|
-
console.log(chalk_1.default.blue("\n➡️ Next: Generate
|
|
1336
|
+
console.log(chalk_1.default.blue("\n➡️ Next: Generate project structure"));
|
|
1337
|
+
console.log(chalk_1.default.gray(" mycontext generate project-structure"));
|
|
1338
|
+
console.log(chalk_1.default.blue("\n💡 Then: Generate components"));
|
|
987
1339
|
console.log(chalk_1.default.gray(" mycontext generate-components all --with-tests # optional tests"));
|
|
988
1340
|
console.log(chalk_1.default.blue("➡️ Preview:"));
|
|
989
1341
|
console.log(chalk_1.default.gray(" Visit /preview (dev server)"));
|
|
990
1342
|
console.log(chalk_1.default.gray(" mycontext normalize preview # optional final layout"));
|
|
991
1343
|
break;
|
|
1344
|
+
case "project-structure":
|
|
1345
|
+
console.log(chalk_1.default.blue("\n➡️ Next: Generate components"));
|
|
1346
|
+
console.log(chalk_1.default.gray(" mycontext generate-components all --with-tests # optional tests"));
|
|
1347
|
+
console.log(chalk_1.default.blue("➡️ Preview:"));
|
|
1348
|
+
console.log(chalk_1.default.gray(" Visit /preview (dev server)"));
|
|
1349
|
+
break;
|
|
992
1350
|
default:
|
|
993
1351
|
break;
|
|
994
1352
|
}
|