@getcoherent/cli 0.5.4 → 0.5.6

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 (3) hide show
  1. package/dist/index.js +209 -96
  2. package/package.json +10 -10
  3. 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
- return { requests: requestsArray2, uxRecommendations: void 0 };
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;
@@ -4339,16 +4341,25 @@ User Request: "${message}"
4339
4341
 
4340
4342
  Return ONLY a JSON object with this structure (no pageCode, no sections, no content):
4341
4343
  {
4344
+ "appName": "Extracted App Name",
4342
4345
  "requests": [
4343
4346
  { "type": "add-page", "target": "new", "changes": { "id": "page-id", "name": "Page Name", "route": "/page-route" } }
4344
- ]
4347
+ ],
4348
+ "navigation": {
4349
+ "type": "header"
4350
+ }
4345
4351
  }
4346
4352
 
4347
4353
  Rules:
4354
+ - appName: Extract the app/product name from the user's request if mentioned (e.g. "app called TaskFlow" \u2192 "TaskFlow", "build a CRM" \u2192 "CRM"). If no name is mentioned, omit this field.
4348
4355
  - Use kebab-case for id and route
4349
4356
  - Route must start with /
4350
4357
  - Keep response under 500 tokens
4351
4358
  - Do NOT include pageCode, sections, or any other fields
4359
+ - Navigation type: Detect from user's request and include in response:
4360
+ * "sidebar" \u2014 if user mentions sidebar, side menu, left panel, admin panel, or app has 6+ main sections
4361
+ * "header" \u2014 if user mentions top navigation, header nav, or app is simple (< 6 sections). This is the default.
4362
+ * "both" \u2014 if complex multi-level app needs both header and sidebar navigation
4352
4363
  - Include ALL pages the user explicitly requested
4353
4364
  - ALSO include logically related pages that a real app would need. For example:
4354
4365
  * If there is a catalog/listing page, add a detail page (e.g. /products \u2192 /products/[id])
@@ -4487,6 +4498,7 @@ LAYOUT CONTRACT (CRITICAL \u2014 prevents duplicate navigation and footer):
4487
4498
  - The app has a root layout (app/layout.tsx) that renders a shared Header and Footer.
4488
4499
  - Pages are rendered INSIDE this layout, between the Header and Footer.
4489
4500
  - 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).
4501
+ - 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
4502
  - 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
4503
  - Do NOT add any navigation bars, logo headers, site-wide menus, or site footers to pages. The layout provides all of these.
4492
4504
 
@@ -4503,7 +4515,7 @@ PAGE CONTENT (CRITICAL \u2014 prevents empty or duplicate pages):
4503
4515
  - Every page MUST have substantial content. NEVER generate a page with only metadata and an empty <main> element.
4504
4516
  - 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
4517
  - 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 "/") should be a simple redirect using next/navigation redirect('/dashboard') \u2014 OR a standalone landing page. NEVER a multi-view SPA.
4518
+ - 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
4519
  - Landing pages should link to app pages via <Link href="/dashboard">, NOT via useState toggles that render inline content.
4508
4520
 
4509
4521
  pageCode rules (shadcn/ui blocks quality):
@@ -4515,6 +4527,7 @@ pageCode rules (shadcn/ui blocks quality):
4515
4527
  - No placeholders: real contextual copy only. Use the EXACT text, language, and content from the user's request.
4516
4528
  - 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
4529
  - 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.
4530
+ - 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
4531
  - Hover/focus on every interactive element (hover:bg-muted, focus-visible:ring-2 focus-visible:ring-ring).
4519
4532
  - LANGUAGE: Match the language of the user's request. English request \u2192 English page. Russian request \u2192 Russian page. Never switch languages.
4520
4533
  - NEVER use native HTML <select> or <option>. Always use Select, SelectTrigger, SelectValue, SelectContent, SelectItem from @/components/ui/select.
@@ -4705,7 +4718,7 @@ var AUTH_LAYOUT = `export default function AuthLayout({
4705
4718
  children: React.ReactNode
4706
4719
  }) {
4707
4720
  return (
4708
- <div className="min-h-svh bg-muted">
4721
+ <div className="min-h-svh bg-muted flex items-center justify-center p-4">
4709
4722
  {children}
4710
4723
  </div>
4711
4724
  )
@@ -5044,7 +5057,7 @@ function fixGlobalsCss(projectRoot, config2) {
5044
5057
  }
5045
5058
 
5046
5059
  // src/utils/quality-validator.ts
5047
- var RAW_COLOR_RE = /(?:bg|text|border)-(gray|blue|red|green|yellow|purple|pink|indigo|orange|slate|zinc|stone|neutral|emerald|teal|cyan|sky|violet|fuchsia|rose|amber|lime)-\d+/g;
5060
+ var RAW_COLOR_RE = /(?:(?:hover|focus|active|group-hover|focus-visible|focus-within):)?(?:bg|text|border|ring|outline|from|to|via)-(gray|blue|red|green|yellow|purple|pink|indigo|orange|slate|zinc|stone|neutral|emerald|teal|cyan|sky|violet|fuchsia|rose|amber|lime)-\d+/g;
5048
5061
  var HEX_IN_CLASS_RE = /className="[^"]*#[0-9a-fA-F]{3,8}[^"]*"/g;
5049
5062
  var TEXT_BASE_RE = /\btext-base\b/g;
5050
5063
  var HEAVY_SHADOW_RE = /\bshadow-(md|lg|xl|2xl)\b/g;
@@ -5308,13 +5321,14 @@ function validatePageQuality(code, validRoutes) {
5308
5321
  });
5309
5322
  }
5310
5323
  const headingLevels = [...code.matchAll(/<h([1-6])[\s>]/g)].map((m) => parseInt(m[1]));
5324
+ const hasCardContext = /\bCard\b|\bCardTitle\b|\bCardHeader\b/.test(code);
5311
5325
  for (let i = 1; i < headingLevels.length; i++) {
5312
5326
  if (headingLevels[i] > headingLevels[i - 1] + 1) {
5313
5327
  issues.push({
5314
5328
  line: 0,
5315
5329
  type: "SKIPPED_HEADING",
5316
5330
  message: `Heading level skipped: h${headingLevels[i - 1]} \u2192 h${headingLevels[i]} \u2014 don't skip levels`,
5317
- severity: "warning"
5331
+ severity: hasCardContext ? "info" : "warning"
5318
5332
  });
5319
5333
  break;
5320
5334
  }
@@ -5441,8 +5455,122 @@ function validatePageQuality(code, validRoutes) {
5441
5455
  }
5442
5456
  }
5443
5457
  }
5458
+ const linkBlockRe = /<(?:Link|a)\b[^>]*>[\s\S]*?<\/(?:Link|a)>/g;
5459
+ let linkMatch;
5460
+ while ((linkMatch = linkBlockRe.exec(code)) !== null) {
5461
+ const block = linkMatch[0];
5462
+ if (/<(?:Button|button)\b/.test(block) && !/asChild/.test(block)) {
5463
+ issues.push({
5464
+ line: 0,
5465
+ type: "NESTED_INTERACTIVE",
5466
+ message: "Button inside Link without asChild \u2014 causes DOM nesting error. Use <Button asChild><Link>...</Link></Button> instead",
5467
+ severity: "error"
5468
+ });
5469
+ break;
5470
+ }
5471
+ }
5472
+ const nestedAnchorRe = /<a\b[^>]*>[\s\S]*?<a\b/;
5473
+ if (nestedAnchorRe.test(code)) {
5474
+ issues.push({
5475
+ line: 0,
5476
+ type: "NESTED_INTERACTIVE",
5477
+ message: "Nested <a> tags \u2014 causes DOM nesting error. Remove inner anchor or restructure",
5478
+ severity: "error"
5479
+ });
5480
+ }
5444
5481
  return issues;
5445
5482
  }
5483
+ function replaceRawColors(classes, colorMap) {
5484
+ let changed = false;
5485
+ let result = classes;
5486
+ const accentColorRe = /\b((?:(?:hover|focus|active|group-hover|focus-visible|focus-within):)?)(bg|text|border|ring|outline|from|to|via)-(emerald|blue|violet|indigo|purple|teal|cyan|sky|rose|amber|red|green|yellow|pink|orange|fuchsia|lime)-(\d+)\b/g;
5487
+ result = result.replace(accentColorRe, (m, statePrefix, prefix, color, shade) => {
5488
+ const bare = m.replace(statePrefix, "");
5489
+ if (colorMap[bare]) {
5490
+ changed = true;
5491
+ return statePrefix + colorMap[bare];
5492
+ }
5493
+ const n = parseInt(shade);
5494
+ const isDestructive = color === "red";
5495
+ if (prefix === "bg") {
5496
+ if (n >= 500 && n <= 700) {
5497
+ changed = true;
5498
+ return statePrefix + (isDestructive ? "bg-destructive" : "bg-primary");
5499
+ }
5500
+ if (n >= 100 && n <= 200) {
5501
+ changed = true;
5502
+ return statePrefix + (isDestructive ? "bg-destructive/10" : "bg-primary/10");
5503
+ }
5504
+ if (n >= 300 && n <= 400) {
5505
+ changed = true;
5506
+ return statePrefix + (isDestructive ? "bg-destructive/20" : "bg-primary/20");
5507
+ }
5508
+ if (n >= 800) {
5509
+ changed = true;
5510
+ return statePrefix + "bg-muted";
5511
+ }
5512
+ }
5513
+ if (prefix === "text") {
5514
+ if (n >= 400 && n <= 600) {
5515
+ changed = true;
5516
+ return statePrefix + (isDestructive ? "text-destructive" : "text-primary");
5517
+ }
5518
+ if (n >= 100 && n <= 300) {
5519
+ changed = true;
5520
+ return statePrefix + "text-foreground";
5521
+ }
5522
+ if (n >= 700) {
5523
+ changed = true;
5524
+ return statePrefix + "text-foreground";
5525
+ }
5526
+ }
5527
+ if (prefix === "border" || prefix === "ring" || prefix === "outline") {
5528
+ changed = true;
5529
+ return statePrefix + (isDestructive ? `${prefix}-destructive` : `${prefix}-primary`);
5530
+ }
5531
+ if (prefix === "from" || prefix === "to" || prefix === "via") {
5532
+ changed = true;
5533
+ if (n >= 100 && n <= 300) return statePrefix + (isDestructive ? `${prefix}-destructive/20` : `${prefix}-primary/20`);
5534
+ return statePrefix + (isDestructive ? `${prefix}-destructive` : `${prefix}-primary`);
5535
+ }
5536
+ return m;
5537
+ });
5538
+ const neutralColorRe = /\b((?:(?:hover|focus|active|group-hover|focus-visible|focus-within):)?)(bg|text|border|ring|outline)-(zinc|slate|gray|neutral|stone)-(\d+)\b/g;
5539
+ result = result.replace(neutralColorRe, (m, statePrefix, prefix, _color, shade) => {
5540
+ const bare = m.replace(statePrefix, "");
5541
+ if (colorMap[bare]) {
5542
+ changed = true;
5543
+ return statePrefix + colorMap[bare];
5544
+ }
5545
+ const n = parseInt(shade);
5546
+ if (prefix === "bg") {
5547
+ if (n >= 800) {
5548
+ changed = true;
5549
+ return statePrefix + "bg-background";
5550
+ }
5551
+ if (n >= 100 && n <= 300) {
5552
+ changed = true;
5553
+ return statePrefix + "bg-muted";
5554
+ }
5555
+ }
5556
+ if (prefix === "text") {
5557
+ if (n >= 100 && n <= 300) {
5558
+ changed = true;
5559
+ return statePrefix + "text-foreground";
5560
+ }
5561
+ if (n >= 400 && n <= 600) {
5562
+ changed = true;
5563
+ return statePrefix + "text-muted-foreground";
5564
+ }
5565
+ }
5566
+ if (prefix === "border" || prefix === "ring" || prefix === "outline") {
5567
+ changed = true;
5568
+ return statePrefix + `${prefix === "border" ? "border-border" : `${prefix}-ring`}`;
5569
+ }
5570
+ return m;
5571
+ });
5572
+ return { result, changed };
5573
+ }
5446
5574
  async function autoFixCode(code) {
5447
5575
  const fixes = [];
5448
5576
  let fixed = code;
@@ -5596,89 +5724,34 @@ ${fixed}`;
5596
5724
  fixed = fixed.replace(/className="([^"]*)"/g, (fullMatch, classes, offset) => {
5597
5725
  if (isCodeContext(classes)) return fullMatch;
5598
5726
  if (isInsideTerminalBlock(offset)) return fullMatch;
5599
- let result = classes;
5600
- 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;
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
- });
5727
+ const { result, changed } = replaceRawColors(classes, colorMap);
5728
+ if (changed) hadColorFix = true;
5679
5729
  if (result !== classes) return `className="${result}"`;
5680
5730
  return fullMatch;
5681
5731
  });
5732
+ fixed = fixed.replace(/(?:cn|clsx|cva)\(([^()]*(?:\([^()]*\)[^()]*)*)\)/g, (fullMatch, args) => {
5733
+ const replaced = args.replace(/"([^"]*)"/g, (_qm, inner) => {
5734
+ const { result, changed } = replaceRawColors(inner, colorMap);
5735
+ if (changed) hadColorFix = true;
5736
+ return `"${result}"`;
5737
+ });
5738
+ if (replaced !== args) return fullMatch.replace(args, replaced);
5739
+ return fullMatch;
5740
+ });
5741
+ fixed = fixed.replace(/className='([^']*)'/g, (fullMatch, classes, offset) => {
5742
+ if (isCodeContext(classes)) return fullMatch;
5743
+ if (isInsideTerminalBlock(offset)) return fullMatch;
5744
+ const { result, changed } = replaceRawColors(classes, colorMap);
5745
+ if (changed) hadColorFix = true;
5746
+ if (result !== classes) return `className='${result}'`;
5747
+ return fullMatch;
5748
+ });
5749
+ fixed = fixed.replace(/className=\{`([^`]*)`\}/g, (fullMatch, inner) => {
5750
+ const { result, changed } = replaceRawColors(inner, colorMap);
5751
+ if (changed) hadColorFix = true;
5752
+ if (result !== inner) return `className={\`${result}\`}`;
5753
+ return fullMatch;
5754
+ });
5682
5755
  if (hadColorFix) fixes.push("raw colors \u2192 semantic tokens");
5683
5756
  const selectRe = /<select\b[^>]*>([\s\S]*?)<\/select>/g;
5684
5757
  let hadSelectFix = false;
@@ -5812,6 +5885,17 @@ ${selectImport}`
5812
5885
  }
5813
5886
  }
5814
5887
  }
5888
+ const linkWithButtonRe = /(<Link\b[^>]*>)\s*(<Button\b(?![^>]*asChild)[^>]*>)([\s\S]*?)<\/Button>\s*<\/Link>/g;
5889
+ const beforeLinkFix = fixed;
5890
+ fixed = fixed.replace(linkWithButtonRe, (_match, linkOpen, buttonOpen, inner) => {
5891
+ const hrefMatch = linkOpen.match(/href="([^"]*)"/);
5892
+ const href = hrefMatch ? hrefMatch[1] : "/";
5893
+ const buttonWithAsChild = buttonOpen.replace("<Button", "<Button asChild");
5894
+ return `${buttonWithAsChild}<Link href="${href}">${inner.trim()}</Link></Button>`;
5895
+ });
5896
+ if (fixed !== beforeLinkFix) {
5897
+ fixes.push("Link>Button \u2192 Button asChild>Link (DOM nesting fix)");
5898
+ }
5815
5899
  fixed = fixed.replace(/className="([^"]*)"/g, (_match, inner) => {
5816
5900
  const cleaned = inner.replace(/\s{2,}/g, " ").trim();
5817
5901
  return `className="${cleaned}"`;
@@ -6662,6 +6746,14 @@ function extractStyleContext(pageCode) {
6662
6746
  return `STYLE CONTEXT (match these patterns exactly for visual consistency with the Home page):
6663
6747
  ${lines.map((l) => ` - ${l}`).join("\n")}`;
6664
6748
  }
6749
+ var VALID_NAV_TYPES = /* @__PURE__ */ new Set(["header", "sidebar", "both"]);
6750
+ function parseNavTypeFromPlan(planResult) {
6751
+ const nav = planResult.navigation;
6752
+ if (nav && typeof nav.type === "string" && VALID_NAV_TYPES.has(nav.type)) {
6753
+ return nav.type;
6754
+ }
6755
+ return "header";
6756
+ }
6665
6757
  async function splitGeneratePages(spinner, message, modCtx, provider, parseOpts) {
6666
6758
  let pageNames = [];
6667
6759
  spinner.start("Phase 1/4 \u2014 Planning pages...");
@@ -6675,6 +6767,14 @@ async function splitGeneratePages(spinner, message, modCtx, provider, parseOpts)
6675
6767
  const route = c.route || `/${id}`;
6676
6768
  return { name, id, route };
6677
6769
  });
6770
+ const detectedNavType = parseNavTypeFromPlan(planResult);
6771
+ if (detectedNavType !== "header" && modCtx.config.navigation) {
6772
+ modCtx.config.navigation.type = detectedNavType;
6773
+ }
6774
+ const planRaw = planResult;
6775
+ if (typeof planRaw.appName === "string" && planRaw.appName && modCtx.config.name === "My App") {
6776
+ modCtx.config.name = planRaw.appName;
6777
+ }
6678
6778
  } catch {
6679
6779
  spinner.text = "AI plan failed \u2014 extracting pages from your request...";
6680
6780
  }
@@ -7796,10 +7896,13 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
7796
7896
  let codeToWrite = fixedCode;
7797
7897
  const { code: autoFixed, fixes: autoFixes } = await autoFixCode(codeToWrite);
7798
7898
  codeToWrite = autoFixed;
7799
- const { code: spaFixed, fixed: spaWasFixed } = detectAndFixSpaHomePage(codeToWrite, route);
7800
- if (spaWasFixed) {
7801
- codeToWrite = spaFixed;
7802
- autoFixes.push("replaced SPA-style home page with redirect to /dashboard");
7899
+ const hasDashboardPage = dsm.getConfig().pages.some((p) => p.route === "/dashboard");
7900
+ if (!hasDashboardPage) {
7901
+ const { code: spaFixed, fixed: spaWasFixed } = detectAndFixSpaHomePage(codeToWrite, route);
7902
+ if (spaWasFixed) {
7903
+ codeToWrite = spaFixed;
7904
+ autoFixes.push("replaced SPA-style home page with redirect to /dashboard");
7905
+ }
7803
7906
  }
7804
7907
  const { code: layoutStripped, stripped } = stripInlineLayoutElements(codeToWrite);
7805
7908
  codeToWrite = layoutStripped;
@@ -7839,7 +7942,7 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
7839
7942
  layoutShared: manifestForAudit.shared.filter((c) => c.type === "layout"),
7840
7943
  allShared: manifestForAudit.shared
7841
7944
  });
7842
- const issues = validatePageQuality(codeToWrite);
7945
+ let issues = validatePageQuality(codeToWrite);
7843
7946
  const errors = issues.filter((i) => i.severity === "error");
7844
7947
  if (errors.length >= 2 && aiProvider) {
7845
7948
  console.log(
@@ -7864,8 +7967,15 @@ Rules:
7864
7967
  const recheckErrors = recheck.filter((i) => i.severity === "error");
7865
7968
  if (recheckErrors.length < errors.length) {
7866
7969
  codeToWrite = fixedCode2;
7970
+ const { code: reFixed, fixes: reFixes } = await autoFixCode(codeToWrite);
7971
+ if (reFixes.length > 0) {
7972
+ codeToWrite = reFixed;
7973
+ postFixes.push(...reFixes);
7974
+ }
7867
7975
  await writeFile(filePath, codeToWrite);
7868
- console.log(chalk11.green(` \u2714 Quality fix: ${errors.length} \u2192 ${recheckErrors.length} errors`));
7976
+ issues = validatePageQuality(codeToWrite);
7977
+ const finalErrors = issues.filter((i) => i.severity === "error").length;
7978
+ console.log(chalk11.green(` \u2714 Quality fix: ${errors.length} \u2192 ${finalErrors} errors`));
7869
7979
  }
7870
7980
  }
7871
7981
  }
@@ -7986,10 +8096,13 @@ ${pagesCtx}`
7986
8096
  let codeToWrite = fixedCode;
7987
8097
  const { code: autoFixed, fixes: autoFixes } = await autoFixCode(codeToWrite);
7988
8098
  codeToWrite = autoFixed;
7989
- const { code: spaFixed, fixed: spaWasFixed } = detectAndFixSpaHomePage(codeToWrite, route);
7990
- if (spaWasFixed) {
7991
- codeToWrite = spaFixed;
7992
- autoFixes.push("replaced SPA-style home page with redirect to /dashboard");
8099
+ const hasDashboardPage = dsm.getConfig().pages.some((p) => p.route === "/dashboard");
8100
+ if (!hasDashboardPage) {
8101
+ const { code: spaFixed, fixed: spaWasFixed } = detectAndFixSpaHomePage(codeToWrite, route);
8102
+ if (spaWasFixed) {
8103
+ codeToWrite = spaFixed;
8104
+ autoFixes.push("replaced SPA-style home page with redirect to /dashboard");
8105
+ }
7993
8106
  }
7994
8107
  const { code: layoutStripped, stripped } = stripInlineLayoutElements(codeToWrite);
7995
8108
  codeToWrite = layoutStripped;
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.5.4",
6
+ "version": "0.5.6",
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.