@getcoherent/cli 0.5.4 → 0.5.5
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/dist/index.js +195 -95
- package/package.json +10 -10
- package/LICENSE +0 -21
package/dist/index.js
CHANGED
|
@@ -3909,6 +3909,7 @@ DROPDOWN MENU:
|
|
|
3909
3909
|
- Item text: text-sm. Icon before text: size-4 mr-2.
|
|
3910
3910
|
- Group related items with DropdownMenuSeparator.
|
|
3911
3911
|
- Destructive item: className="text-destructive" at bottom, separated.
|
|
3912
|
+
- NON-DESTRUCTIVE items: NEVER apply text color classes. Use default text-foreground. No text-amber, text-orange, text-yellow on menu items.
|
|
3912
3913
|
- Keyboard shortcut hint: <DropdownMenuShortcut>\u2318K</DropdownMenuShortcut>.
|
|
3913
3914
|
- Max items without scroll: 8. For more, use Command palette.
|
|
3914
3915
|
- Trigger: Button variant="ghost" size="icon" for icon-only, variant="outline" for labeled.
|
|
@@ -4284,7 +4285,8 @@ async function parseModification(message, context, provider = "auto", options) {
|
|
|
4284
4285
|
const prompt2 = buildPlanOnlyPrompt(message, context.config);
|
|
4285
4286
|
const raw2 = await ai.parseModification(prompt2);
|
|
4286
4287
|
const requestsArray2 = Array.isArray(raw2) ? raw2 : raw2?.requests ?? [];
|
|
4287
|
-
|
|
4288
|
+
const navigation = !Array.isArray(raw2) && raw2?.navigation ? raw2.navigation : void 0;
|
|
4289
|
+
return { requests: requestsArray2, uxRecommendations: void 0, navigation };
|
|
4288
4290
|
}
|
|
4289
4291
|
const componentRegistry = buildComponentRegistry(context.componentManager);
|
|
4290
4292
|
let enhancedMessage = message;
|
|
@@ -4341,7 +4343,10 @@ Return ONLY a JSON object with this structure (no pageCode, no sections, no cont
|
|
|
4341
4343
|
{
|
|
4342
4344
|
"requests": [
|
|
4343
4345
|
{ "type": "add-page", "target": "new", "changes": { "id": "page-id", "name": "Page Name", "route": "/page-route" } }
|
|
4344
|
-
]
|
|
4346
|
+
],
|
|
4347
|
+
"navigation": {
|
|
4348
|
+
"type": "header"
|
|
4349
|
+
}
|
|
4345
4350
|
}
|
|
4346
4351
|
|
|
4347
4352
|
Rules:
|
|
@@ -4349,6 +4354,10 @@ Rules:
|
|
|
4349
4354
|
- Route must start with /
|
|
4350
4355
|
- Keep response under 500 tokens
|
|
4351
4356
|
- Do NOT include pageCode, sections, or any other fields
|
|
4357
|
+
- Navigation type: Detect from user's request and include in response:
|
|
4358
|
+
* "sidebar" \u2014 if user mentions sidebar, side menu, left panel, admin panel, or app has 6+ main sections
|
|
4359
|
+
* "header" \u2014 if user mentions top navigation, header nav, or app is simple (< 6 sections). This is the default.
|
|
4360
|
+
* "both" \u2014 if complex multi-level app needs both header and sidebar navigation
|
|
4352
4361
|
- Include ALL pages the user explicitly requested
|
|
4353
4362
|
- ALSO include logically related pages that a real app would need. For example:
|
|
4354
4363
|
* If there is a catalog/listing page, add a detail page (e.g. /products \u2192 /products/[id])
|
|
@@ -4487,6 +4496,7 @@ LAYOUT CONTRACT (CRITICAL \u2014 prevents duplicate navigation and footer):
|
|
|
4487
4496
|
- The app has a root layout (app/layout.tsx) that renders a shared Header and Footer.
|
|
4488
4497
|
- Pages are rendered INSIDE this layout, between the Header and Footer.
|
|
4489
4498
|
- NEVER include <header>, <nav>, or <footer> elements in pageCode. Also do NOT add a footer-like section at the bottom (no "\xA9 2024", no site links, no logo + nav links at the bottom).
|
|
4499
|
+
- NEVER generate a sidebar panel, navigation column, or left-side navigation inside pageCode. Sidebar navigation is handled by the layout system via shared Sidebar component. If the user mentions "sidebar", it will be rendered by app/(app)/layout.tsx \u2014 do NOT recreate it in page code.
|
|
4490
4500
|
- If the page needs sub-navigation (tabs, breadcrumbs, sidebar nav), use elements like <div role="tablist"> or <aside> \u2014 NOT <header>, <nav>, or <footer>.
|
|
4491
4501
|
- Do NOT add any navigation bars, logo headers, site-wide menus, or site footers to pages. The layout provides all of these.
|
|
4492
4502
|
|
|
@@ -4503,7 +4513,7 @@ PAGE CONTENT (CRITICAL \u2014 prevents empty or duplicate pages):
|
|
|
4503
4513
|
- Every page MUST have substantial content. NEVER generate a page with only metadata and an empty <main> element.
|
|
4504
4514
|
- NEVER create an inline preview/demo of another page (e.g., embedding a "dashboard view" inside the landing page with a toggle). Each page should be its own route.
|
|
4505
4515
|
- NEVER create a single-page app (SPA) that renders multiple views via useState. Each view must be a separate Next.js page with its own route.
|
|
4506
|
-
- The home page (route "/")
|
|
4516
|
+
- The home page (route "/"): When BOTH "/" and "/dashboard" exist, "/" MUST be a full landing page with hero section, features, pricing, and CTA buttons linking to /login and /register \u2014 NOT a redirect. When "/" is the ONLY main page, it can be a redirect to /dashboard. NEVER a multi-view SPA.
|
|
4507
4517
|
- Landing pages should link to app pages via <Link href="/dashboard">, NOT via useState toggles that render inline content.
|
|
4508
4518
|
|
|
4509
4519
|
pageCode rules (shadcn/ui blocks quality):
|
|
@@ -4515,6 +4525,7 @@ pageCode rules (shadcn/ui blocks quality):
|
|
|
4515
4525
|
- No placeholders: real contextual copy only. Use the EXACT text, language, and content from the user's request.
|
|
4516
4526
|
- IMAGES: For avatar/profile photos, use https://i.pravatar.cc/150?u=<unique-seed> (e.g. ?u=sarah.johnson). For hero/product images, use https://picsum.photos/800/400?random=N. Use standard <img> tags with className, NOT Next.js <Image>. Always provide alt text.
|
|
4517
4527
|
- BUTTON + LINK: The Button component supports asChild prop. To make a button that navigates, use <Button asChild><Link href="/path"><Plus className="size-4" /> Label</Link></Button>. Never nest <button> inside <Link> or vice versa without asChild.
|
|
4528
|
+
- DOM NESTING: NEVER nest interactive elements. No <Button> inside <Link>, no <a> inside <a>, no <button> inside <button>. For clickable cards containing internal buttons, use onClick on the card wrapper \u2014 NOT <Link> wrapping the entire card.
|
|
4518
4529
|
- Hover/focus on every interactive element (hover:bg-muted, focus-visible:ring-2 focus-visible:ring-ring).
|
|
4519
4530
|
- LANGUAGE: Match the language of the user's request. English request \u2192 English page. Russian request \u2192 Russian page. Never switch languages.
|
|
4520
4531
|
- NEVER use native HTML <select> or <option>. Always use Select, SelectTrigger, SelectValue, SelectContent, SelectItem from @/components/ui/select.
|
|
@@ -4705,7 +4716,7 @@ var AUTH_LAYOUT = `export default function AuthLayout({
|
|
|
4705
4716
|
children: React.ReactNode
|
|
4706
4717
|
}) {
|
|
4707
4718
|
return (
|
|
4708
|
-
<div className="min-h-svh bg-muted">
|
|
4719
|
+
<div className="min-h-svh bg-muted flex items-center justify-center p-4">
|
|
4709
4720
|
{children}
|
|
4710
4721
|
</div>
|
|
4711
4722
|
)
|
|
@@ -5308,13 +5319,14 @@ function validatePageQuality(code, validRoutes) {
|
|
|
5308
5319
|
});
|
|
5309
5320
|
}
|
|
5310
5321
|
const headingLevels = [...code.matchAll(/<h([1-6])[\s>]/g)].map((m) => parseInt(m[1]));
|
|
5322
|
+
const hasCardContext = /\bCard\b|\bCardTitle\b|\bCardHeader\b/.test(code);
|
|
5311
5323
|
for (let i = 1; i < headingLevels.length; i++) {
|
|
5312
5324
|
if (headingLevels[i] > headingLevels[i - 1] + 1) {
|
|
5313
5325
|
issues.push({
|
|
5314
5326
|
line: 0,
|
|
5315
5327
|
type: "SKIPPED_HEADING",
|
|
5316
5328
|
message: `Heading level skipped: h${headingLevels[i - 1]} \u2192 h${headingLevels[i]} \u2014 don't skip levels`,
|
|
5317
|
-
severity: "warning"
|
|
5329
|
+
severity: hasCardContext ? "info" : "warning"
|
|
5318
5330
|
});
|
|
5319
5331
|
break;
|
|
5320
5332
|
}
|
|
@@ -5441,8 +5453,115 @@ function validatePageQuality(code, validRoutes) {
|
|
|
5441
5453
|
}
|
|
5442
5454
|
}
|
|
5443
5455
|
}
|
|
5456
|
+
const linkBlockRe = /<(?:Link|a)\b[^>]*>[\s\S]*?<\/(?:Link|a)>/g;
|
|
5457
|
+
let linkMatch;
|
|
5458
|
+
while ((linkMatch = linkBlockRe.exec(code)) !== null) {
|
|
5459
|
+
const block = linkMatch[0];
|
|
5460
|
+
if (/<(?:Button|button)\b/.test(block) && !/asChild/.test(block)) {
|
|
5461
|
+
issues.push({
|
|
5462
|
+
line: 0,
|
|
5463
|
+
type: "NESTED_INTERACTIVE",
|
|
5464
|
+
message: "Button inside Link without asChild \u2014 causes DOM nesting error. Use <Button asChild><Link>...</Link></Button> instead",
|
|
5465
|
+
severity: "error"
|
|
5466
|
+
});
|
|
5467
|
+
break;
|
|
5468
|
+
}
|
|
5469
|
+
}
|
|
5470
|
+
const nestedAnchorRe = /<a\b[^>]*>[\s\S]*?<a\b/;
|
|
5471
|
+
if (nestedAnchorRe.test(code)) {
|
|
5472
|
+
issues.push({
|
|
5473
|
+
line: 0,
|
|
5474
|
+
type: "NESTED_INTERACTIVE",
|
|
5475
|
+
message: "Nested <a> tags \u2014 causes DOM nesting error. Remove inner anchor or restructure",
|
|
5476
|
+
severity: "error"
|
|
5477
|
+
});
|
|
5478
|
+
}
|
|
5444
5479
|
return issues;
|
|
5445
5480
|
}
|
|
5481
|
+
function replaceRawColors(classes, colorMap) {
|
|
5482
|
+
let changed = false;
|
|
5483
|
+
let result = classes;
|
|
5484
|
+
const accentColorRe = /\b(bg|text|border)-(emerald|blue|violet|indigo|purple|teal|cyan|sky|rose|amber|red|green|yellow|pink|orange|fuchsia|lime)-(\d+)\b/g;
|
|
5485
|
+
result = result.replace(accentColorRe, (m, prefix, color, shade) => {
|
|
5486
|
+
if (colorMap[m]) {
|
|
5487
|
+
changed = true;
|
|
5488
|
+
return colorMap[m];
|
|
5489
|
+
}
|
|
5490
|
+
const n = parseInt(shade);
|
|
5491
|
+
const isDestructive = color === "red";
|
|
5492
|
+
if (prefix === "bg") {
|
|
5493
|
+
if (n >= 500 && n <= 700) {
|
|
5494
|
+
changed = true;
|
|
5495
|
+
return isDestructive ? "bg-destructive" : "bg-primary";
|
|
5496
|
+
}
|
|
5497
|
+
if (n >= 100 && n <= 200) {
|
|
5498
|
+
changed = true;
|
|
5499
|
+
return isDestructive ? "bg-destructive/10" : "bg-primary/10";
|
|
5500
|
+
}
|
|
5501
|
+
if (n >= 300 && n <= 400) {
|
|
5502
|
+
changed = true;
|
|
5503
|
+
return isDestructive ? "bg-destructive/20" : "bg-primary/20";
|
|
5504
|
+
}
|
|
5505
|
+
if (n >= 800) {
|
|
5506
|
+
changed = true;
|
|
5507
|
+
return "bg-muted";
|
|
5508
|
+
}
|
|
5509
|
+
}
|
|
5510
|
+
if (prefix === "text") {
|
|
5511
|
+
if (n >= 400 && n <= 600) {
|
|
5512
|
+
changed = true;
|
|
5513
|
+
return isDestructive ? "text-destructive" : "text-primary";
|
|
5514
|
+
}
|
|
5515
|
+
if (n >= 100 && n <= 300) {
|
|
5516
|
+
changed = true;
|
|
5517
|
+
return "text-foreground";
|
|
5518
|
+
}
|
|
5519
|
+
if (n >= 700) {
|
|
5520
|
+
changed = true;
|
|
5521
|
+
return "text-foreground";
|
|
5522
|
+
}
|
|
5523
|
+
}
|
|
5524
|
+
if (prefix === "border") {
|
|
5525
|
+
changed = true;
|
|
5526
|
+
return isDestructive ? "border-destructive" : "border-primary";
|
|
5527
|
+
}
|
|
5528
|
+
return m;
|
|
5529
|
+
});
|
|
5530
|
+
const neutralColorRe = /\b(bg|text|border)-(zinc|slate|gray|neutral|stone)-(\d+)\b/g;
|
|
5531
|
+
result = result.replace(neutralColorRe, (m, prefix, _color, shade) => {
|
|
5532
|
+
if (colorMap[m]) {
|
|
5533
|
+
changed = true;
|
|
5534
|
+
return colorMap[m];
|
|
5535
|
+
}
|
|
5536
|
+
const n = parseInt(shade);
|
|
5537
|
+
if (prefix === "bg") {
|
|
5538
|
+
if (n >= 800) {
|
|
5539
|
+
changed = true;
|
|
5540
|
+
return "bg-background";
|
|
5541
|
+
}
|
|
5542
|
+
if (n >= 100 && n <= 300) {
|
|
5543
|
+
changed = true;
|
|
5544
|
+
return "bg-muted";
|
|
5545
|
+
}
|
|
5546
|
+
}
|
|
5547
|
+
if (prefix === "text") {
|
|
5548
|
+
if (n >= 100 && n <= 300) {
|
|
5549
|
+
changed = true;
|
|
5550
|
+
return "text-foreground";
|
|
5551
|
+
}
|
|
5552
|
+
if (n >= 400 && n <= 600) {
|
|
5553
|
+
changed = true;
|
|
5554
|
+
return "text-muted-foreground";
|
|
5555
|
+
}
|
|
5556
|
+
}
|
|
5557
|
+
if (prefix === "border") {
|
|
5558
|
+
changed = true;
|
|
5559
|
+
return "border-border";
|
|
5560
|
+
}
|
|
5561
|
+
return m;
|
|
5562
|
+
});
|
|
5563
|
+
return { result, changed };
|
|
5564
|
+
}
|
|
5446
5565
|
async function autoFixCode(code) {
|
|
5447
5566
|
const fixes = [];
|
|
5448
5567
|
let fixed = code;
|
|
@@ -5596,89 +5715,34 @@ ${fixed}`;
|
|
|
5596
5715
|
fixed = fixed.replace(/className="([^"]*)"/g, (fullMatch, classes, offset) => {
|
|
5597
5716
|
if (isCodeContext(classes)) return fullMatch;
|
|
5598
5717
|
if (isInsideTerminalBlock(offset)) return fullMatch;
|
|
5599
|
-
|
|
5600
|
-
|
|
5601
|
-
result = result.replace(accentColorRe, (m, prefix, color, shade) => {
|
|
5602
|
-
if (colorMap[m]) {
|
|
5603
|
-
hadColorFix = true;
|
|
5604
|
-
return colorMap[m];
|
|
5605
|
-
}
|
|
5606
|
-
const n = parseInt(shade);
|
|
5607
|
-
const isDestructive = color === "red";
|
|
5608
|
-
if (prefix === "bg") {
|
|
5609
|
-
if (n >= 500 && n <= 700) {
|
|
5610
|
-
hadColorFix = true;
|
|
5611
|
-
return isDestructive ? "bg-destructive" : "bg-primary";
|
|
5612
|
-
}
|
|
5613
|
-
if (n >= 100 && n <= 200) {
|
|
5614
|
-
hadColorFix = true;
|
|
5615
|
-
return isDestructive ? "bg-destructive/10" : "bg-primary/10";
|
|
5616
|
-
}
|
|
5617
|
-
if (n >= 300 && n <= 400) {
|
|
5618
|
-
hadColorFix = true;
|
|
5619
|
-
return isDestructive ? "bg-destructive/20" : "bg-primary/20";
|
|
5620
|
-
}
|
|
5621
|
-
if (n >= 800) {
|
|
5622
|
-
hadColorFix = true;
|
|
5623
|
-
return "bg-muted";
|
|
5624
|
-
}
|
|
5625
|
-
}
|
|
5626
|
-
if (prefix === "text") {
|
|
5627
|
-
if (n >= 400 && n <= 600) {
|
|
5628
|
-
hadColorFix = true;
|
|
5629
|
-
return isDestructive ? "text-destructive" : "text-primary";
|
|
5630
|
-
}
|
|
5631
|
-
if (n >= 100 && n <= 300) {
|
|
5632
|
-
hadColorFix = true;
|
|
5633
|
-
return "text-foreground";
|
|
5634
|
-
}
|
|
5635
|
-
if (n >= 700) {
|
|
5636
|
-
hadColorFix = true;
|
|
5637
|
-
return "text-foreground";
|
|
5638
|
-
}
|
|
5639
|
-
}
|
|
5640
|
-
if (prefix === "border") {
|
|
5641
|
-
hadColorFix = true;
|
|
5642
|
-
return isDestructive ? "border-destructive" : "border-primary";
|
|
5643
|
-
}
|
|
5644
|
-
return m;
|
|
5645
|
-
});
|
|
5646
|
-
const neutralColorRe = /\b(bg|text|border)-(zinc|slate|gray|neutral|stone)-(\d+)\b/g;
|
|
5647
|
-
result = result.replace(neutralColorRe, (m, prefix, _color, shade) => {
|
|
5648
|
-
if (colorMap[m]) {
|
|
5649
|
-
hadColorFix = true;
|
|
5650
|
-
return colorMap[m];
|
|
5651
|
-
}
|
|
5652
|
-
const n = parseInt(shade);
|
|
5653
|
-
if (prefix === "bg") {
|
|
5654
|
-
if (n >= 800) {
|
|
5655
|
-
hadColorFix = true;
|
|
5656
|
-
return "bg-background";
|
|
5657
|
-
}
|
|
5658
|
-
if (n >= 100 && n <= 300) {
|
|
5659
|
-
hadColorFix = true;
|
|
5660
|
-
return "bg-muted";
|
|
5661
|
-
}
|
|
5662
|
-
}
|
|
5663
|
-
if (prefix === "text") {
|
|
5664
|
-
if (n >= 100 && n <= 300) {
|
|
5665
|
-
hadColorFix = true;
|
|
5666
|
-
return "text-foreground";
|
|
5667
|
-
}
|
|
5668
|
-
if (n >= 400 && n <= 600) {
|
|
5669
|
-
hadColorFix = true;
|
|
5670
|
-
return "text-muted-foreground";
|
|
5671
|
-
}
|
|
5672
|
-
}
|
|
5673
|
-
if (prefix === "border") {
|
|
5674
|
-
hadColorFix = true;
|
|
5675
|
-
return "border-border";
|
|
5676
|
-
}
|
|
5677
|
-
return m;
|
|
5678
|
-
});
|
|
5718
|
+
const { result, changed } = replaceRawColors(classes, colorMap);
|
|
5719
|
+
if (changed) hadColorFix = true;
|
|
5679
5720
|
if (result !== classes) return `className="${result}"`;
|
|
5680
5721
|
return fullMatch;
|
|
5681
5722
|
});
|
|
5723
|
+
fixed = fixed.replace(/(?:cn|clsx|cva)\(([^()]*(?:\([^()]*\)[^()]*)*)\)/g, (fullMatch, args) => {
|
|
5724
|
+
const replaced = args.replace(/"([^"]*)"/g, (_qm, inner) => {
|
|
5725
|
+
const { result, changed } = replaceRawColors(inner, colorMap);
|
|
5726
|
+
if (changed) hadColorFix = true;
|
|
5727
|
+
return `"${result}"`;
|
|
5728
|
+
});
|
|
5729
|
+
if (replaced !== args) return fullMatch.replace(args, replaced);
|
|
5730
|
+
return fullMatch;
|
|
5731
|
+
});
|
|
5732
|
+
fixed = fixed.replace(/className='([^']*)'/g, (fullMatch, classes, offset) => {
|
|
5733
|
+
if (isCodeContext(classes)) return fullMatch;
|
|
5734
|
+
if (isInsideTerminalBlock(offset)) return fullMatch;
|
|
5735
|
+
const { result, changed } = replaceRawColors(classes, colorMap);
|
|
5736
|
+
if (changed) hadColorFix = true;
|
|
5737
|
+
if (result !== classes) return `className='${result}'`;
|
|
5738
|
+
return fullMatch;
|
|
5739
|
+
});
|
|
5740
|
+
fixed = fixed.replace(/className=\{`([^`]*)`\}/g, (fullMatch, inner) => {
|
|
5741
|
+
const { result, changed } = replaceRawColors(inner, colorMap);
|
|
5742
|
+
if (changed) hadColorFix = true;
|
|
5743
|
+
if (result !== inner) return `className={\`${result}\`}`;
|
|
5744
|
+
return fullMatch;
|
|
5745
|
+
});
|
|
5682
5746
|
if (hadColorFix) fixes.push("raw colors \u2192 semantic tokens");
|
|
5683
5747
|
const selectRe = /<select\b[^>]*>([\s\S]*?)<\/select>/g;
|
|
5684
5748
|
let hadSelectFix = false;
|
|
@@ -5812,6 +5876,17 @@ ${selectImport}`
|
|
|
5812
5876
|
}
|
|
5813
5877
|
}
|
|
5814
5878
|
}
|
|
5879
|
+
const linkWithButtonRe = /(<Link\b[^>]*>)\s*(<Button\b(?![^>]*asChild)[^>]*>)([\s\S]*?)<\/Button>\s*<\/Link>/g;
|
|
5880
|
+
const beforeLinkFix = fixed;
|
|
5881
|
+
fixed = fixed.replace(linkWithButtonRe, (_match, linkOpen, buttonOpen, inner) => {
|
|
5882
|
+
const hrefMatch = linkOpen.match(/href="([^"]*)"/);
|
|
5883
|
+
const href = hrefMatch ? hrefMatch[1] : "/";
|
|
5884
|
+
const buttonWithAsChild = buttonOpen.replace("<Button", "<Button asChild");
|
|
5885
|
+
return `${buttonWithAsChild}<Link href="${href}">${inner.trim()}</Link></Button>`;
|
|
5886
|
+
});
|
|
5887
|
+
if (fixed !== beforeLinkFix) {
|
|
5888
|
+
fixes.push("Link>Button \u2192 Button asChild>Link (DOM nesting fix)");
|
|
5889
|
+
}
|
|
5815
5890
|
fixed = fixed.replace(/className="([^"]*)"/g, (_match, inner) => {
|
|
5816
5891
|
const cleaned = inner.replace(/\s{2,}/g, " ").trim();
|
|
5817
5892
|
return `className="${cleaned}"`;
|
|
@@ -6662,6 +6737,14 @@ function extractStyleContext(pageCode) {
|
|
|
6662
6737
|
return `STYLE CONTEXT (match these patterns exactly for visual consistency with the Home page):
|
|
6663
6738
|
${lines.map((l) => ` - ${l}`).join("\n")}`;
|
|
6664
6739
|
}
|
|
6740
|
+
var VALID_NAV_TYPES = /* @__PURE__ */ new Set(["header", "sidebar", "both"]);
|
|
6741
|
+
function parseNavTypeFromPlan(planResult) {
|
|
6742
|
+
const nav = planResult.navigation;
|
|
6743
|
+
if (nav && typeof nav.type === "string" && VALID_NAV_TYPES.has(nav.type)) {
|
|
6744
|
+
return nav.type;
|
|
6745
|
+
}
|
|
6746
|
+
return "header";
|
|
6747
|
+
}
|
|
6665
6748
|
async function splitGeneratePages(spinner, message, modCtx, provider, parseOpts) {
|
|
6666
6749
|
let pageNames = [];
|
|
6667
6750
|
spinner.start("Phase 1/4 \u2014 Planning pages...");
|
|
@@ -6675,6 +6758,10 @@ async function splitGeneratePages(spinner, message, modCtx, provider, parseOpts)
|
|
|
6675
6758
|
const route = c.route || `/${id}`;
|
|
6676
6759
|
return { name, id, route };
|
|
6677
6760
|
});
|
|
6761
|
+
const detectedNavType = parseNavTypeFromPlan(planResult);
|
|
6762
|
+
if (detectedNavType !== "header" && modCtx.config.navigation) {
|
|
6763
|
+
modCtx.config.navigation.type = detectedNavType;
|
|
6764
|
+
}
|
|
6678
6765
|
} catch {
|
|
6679
6766
|
spinner.text = "AI plan failed \u2014 extracting pages from your request...";
|
|
6680
6767
|
}
|
|
@@ -7796,10 +7883,13 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
|
|
|
7796
7883
|
let codeToWrite = fixedCode;
|
|
7797
7884
|
const { code: autoFixed, fixes: autoFixes } = await autoFixCode(codeToWrite);
|
|
7798
7885
|
codeToWrite = autoFixed;
|
|
7799
|
-
const
|
|
7800
|
-
if (
|
|
7801
|
-
|
|
7802
|
-
|
|
7886
|
+
const hasDashboardPage = dsm.getConfig().pages.some((p) => p.route === "/dashboard");
|
|
7887
|
+
if (!hasDashboardPage) {
|
|
7888
|
+
const { code: spaFixed, fixed: spaWasFixed } = detectAndFixSpaHomePage(codeToWrite, route);
|
|
7889
|
+
if (spaWasFixed) {
|
|
7890
|
+
codeToWrite = spaFixed;
|
|
7891
|
+
autoFixes.push("replaced SPA-style home page with redirect to /dashboard");
|
|
7892
|
+
}
|
|
7803
7893
|
}
|
|
7804
7894
|
const { code: layoutStripped, stripped } = stripInlineLayoutElements(codeToWrite);
|
|
7805
7895
|
codeToWrite = layoutStripped;
|
|
@@ -7839,7 +7929,7 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
|
|
|
7839
7929
|
layoutShared: manifestForAudit.shared.filter((c) => c.type === "layout"),
|
|
7840
7930
|
allShared: manifestForAudit.shared
|
|
7841
7931
|
});
|
|
7842
|
-
|
|
7932
|
+
let issues = validatePageQuality(codeToWrite);
|
|
7843
7933
|
const errors = issues.filter((i) => i.severity === "error");
|
|
7844
7934
|
if (errors.length >= 2 && aiProvider) {
|
|
7845
7935
|
console.log(
|
|
@@ -7864,8 +7954,15 @@ Rules:
|
|
|
7864
7954
|
const recheckErrors = recheck.filter((i) => i.severity === "error");
|
|
7865
7955
|
if (recheckErrors.length < errors.length) {
|
|
7866
7956
|
codeToWrite = fixedCode2;
|
|
7957
|
+
const { code: reFixed, fixes: reFixes } = await autoFixCode(codeToWrite);
|
|
7958
|
+
if (reFixes.length > 0) {
|
|
7959
|
+
codeToWrite = reFixed;
|
|
7960
|
+
postFixes.push(...reFixes);
|
|
7961
|
+
}
|
|
7867
7962
|
await writeFile(filePath, codeToWrite);
|
|
7868
|
-
|
|
7963
|
+
issues = validatePageQuality(codeToWrite);
|
|
7964
|
+
const finalErrors = issues.filter((i) => i.severity === "error").length;
|
|
7965
|
+
console.log(chalk11.green(` \u2714 Quality fix: ${errors.length} \u2192 ${finalErrors} errors`));
|
|
7869
7966
|
}
|
|
7870
7967
|
}
|
|
7871
7968
|
}
|
|
@@ -7986,10 +8083,13 @@ ${pagesCtx}`
|
|
|
7986
8083
|
let codeToWrite = fixedCode;
|
|
7987
8084
|
const { code: autoFixed, fixes: autoFixes } = await autoFixCode(codeToWrite);
|
|
7988
8085
|
codeToWrite = autoFixed;
|
|
7989
|
-
const
|
|
7990
|
-
if (
|
|
7991
|
-
|
|
7992
|
-
|
|
8086
|
+
const hasDashboardPage = dsm.getConfig().pages.some((p) => p.route === "/dashboard");
|
|
8087
|
+
if (!hasDashboardPage) {
|
|
8088
|
+
const { code: spaFixed, fixed: spaWasFixed } = detectAndFixSpaHomePage(codeToWrite, route);
|
|
8089
|
+
if (spaWasFixed) {
|
|
8090
|
+
codeToWrite = spaFixed;
|
|
8091
|
+
autoFixes.push("replaced SPA-style home page with redirect to /dashboard");
|
|
8092
|
+
}
|
|
7993
8093
|
}
|
|
7994
8094
|
const { code: layoutStripped, stripped } = stripInlineLayoutElements(codeToWrite);
|
|
7995
8095
|
codeToWrite = layoutStripped;
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "0.5.
|
|
6
|
+
"version": "0.5.5",
|
|
7
7
|
"description": "CLI interface for Coherent Design Method",
|
|
8
8
|
"type": "module",
|
|
9
9
|
"main": "./dist/index.js",
|
|
@@ -33,8 +33,15 @@
|
|
|
33
33
|
],
|
|
34
34
|
"author": "Coherent Design Method",
|
|
35
35
|
"license": "MIT",
|
|
36
|
+
"scripts": {
|
|
37
|
+
"dev": "tsup --watch",
|
|
38
|
+
"build": "tsup",
|
|
39
|
+
"typecheck": "tsc --noEmit",
|
|
40
|
+
"test": "vitest"
|
|
41
|
+
},
|
|
36
42
|
"dependencies": {
|
|
37
43
|
"@anthropic-ai/sdk": "^0.32.0",
|
|
44
|
+
"@getcoherent/core": "workspace:*",
|
|
38
45
|
"chalk": "^5.3.0",
|
|
39
46
|
"chokidar": "^4.0.1",
|
|
40
47
|
"commander": "^11.1.0",
|
|
@@ -42,19 +49,12 @@
|
|
|
42
49
|
"open": "^10.1.0",
|
|
43
50
|
"ora": "^7.0.1",
|
|
44
51
|
"prompts": "^2.4.2",
|
|
45
|
-
"zod": "^3.22.4"
|
|
46
|
-
"@getcoherent/core": "0.5.4"
|
|
52
|
+
"zod": "^3.22.4"
|
|
47
53
|
},
|
|
48
54
|
"devDependencies": {
|
|
49
55
|
"@types/node": "^20.11.0",
|
|
50
56
|
"@types/prompts": "^2.4.9",
|
|
51
57
|
"tsup": "^8.0.1",
|
|
52
58
|
"typescript": "^5.3.3"
|
|
53
|
-
},
|
|
54
|
-
"scripts": {
|
|
55
|
-
"dev": "tsup --watch",
|
|
56
|
-
"build": "tsup",
|
|
57
|
-
"typecheck": "tsc --noEmit",
|
|
58
|
-
"test": "vitest"
|
|
59
59
|
}
|
|
60
|
-
}
|
|
60
|
+
}
|
package/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2025 Sergei Kovtun
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|