@getcoherent/cli 0.6.19 → 0.6.21

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.
@@ -613,12 +613,12 @@ function resolveHref(linkText, context) {
613
613
  function replaceRawColors(classes, colorMap) {
614
614
  let changed = false;
615
615
  let result = classes;
616
- 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;
616
+ 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+)(?:\/\d+)?\b/g;
617
617
  result = result.replace(accentColorRe, (m, statePrefix, prefix, color, shade) => {
618
- const bare = m.replace(statePrefix, "");
619
- if (colorMap[bare]) {
618
+ const bareNoOpacity = m.replace(statePrefix, "").replace(/\/\d+$/, "");
619
+ if (colorMap[bareNoOpacity]) {
620
620
  changed = true;
621
- return statePrefix + colorMap[bare];
621
+ return statePrefix + colorMap[bareNoOpacity];
622
622
  }
623
623
  const n = parseInt(shade);
624
624
  const isDestructive = color === "red";
@@ -648,12 +648,12 @@ function replaceRawColors(classes, colorMap) {
648
648
  }
649
649
  return m;
650
650
  });
651
- 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;
651
+ const neutralColorRe = /\b((?:(?:hover|focus|active|group-hover|focus-visible|focus-within):)?)(bg|text|border|ring|outline)-(zinc|slate|gray|neutral|stone)-(\d+)(?:\/\d+)?\b/g;
652
652
  result = result.replace(neutralColorRe, (m, statePrefix, prefix, _color, shade) => {
653
- const bare = m.replace(statePrefix, "");
654
- if (colorMap[bare]) {
653
+ const bareNoOpacity = m.replace(statePrefix, "").replace(/\/\d+$/, "");
654
+ if (colorMap[bareNoOpacity]) {
655
655
  changed = true;
656
- return statePrefix + colorMap[bare];
656
+ return statePrefix + colorMap[bareNoOpacity];
657
657
  }
658
658
  const n = parseInt(shade);
659
659
  if (prefix === "bg") {
@@ -936,11 +936,19 @@ ${selectImport}`
936
936
  for (const m of fixed.matchAll(/import\s*\{([^}]+)\}\s*from\s*["'](?!lucide-react)([^"']+)["']/g)) {
937
937
  m[1].split(",").map((s) => s.trim()).filter(Boolean).forEach((n) => nonLucideImports.add(n));
938
938
  }
939
- const iconNames = lucideImportMatch[1].split(",").map((s) => s.trim()).filter(Boolean);
940
- const duplicates = iconNames.filter((name) => nonLucideImports.has(name));
939
+ const rawIconEntries = lucideImportMatch[1].split(",").map((s) => s.trim()).filter(Boolean);
940
+ const iconNames = rawIconEntries.map((entry) => {
941
+ const parts = entry.split(/\s+as\s+/);
942
+ return parts[0].trim();
943
+ });
944
+ const duplicates = rawIconEntries.filter((entry) => {
945
+ const alias = entry.split(/\s+as\s+/).pop().trim();
946
+ return nonLucideImports.has(alias);
947
+ });
941
948
  let newImport = lucideImportMatch[1];
942
949
  for (const dup of duplicates) {
943
- newImport = newImport.replace(new RegExp(`\\b${dup}\\b,?\\s*`), "");
950
+ const escaped = dup.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
951
+ newImport = newImport.replace(new RegExp(`${escaped},?\\s*`), "");
944
952
  fixes.push(`removed ${dup} from lucide import (conflicts with UI component import)`);
945
953
  }
946
954
  const ICON_ALIASES = {
package/dist/index.js CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  formatIssues,
8
8
  validatePageQuality,
9
9
  verifyIncrementalEdit
10
- } from "./chunk-CF4377B7.js";
10
+ } from "./chunk-VKGZRKWD.js";
11
11
  import {
12
12
  generateArchitecturePlan,
13
13
  getPageGroup,
@@ -5879,11 +5879,13 @@ function verifyReusePlan(generatedCode, plan) {
5879
5879
  }
5880
5880
 
5881
5881
  // src/commands/chat/split-generator.ts
5882
+ var MAX_EXISTING_PAGES_CONTEXT = 10;
5882
5883
  function buildExistingPagesContext(config2) {
5883
5884
  const pages = config2.pages || [];
5884
5885
  const analyzed = pages.filter((p) => p.pageAnalysis);
5885
5886
  if (analyzed.length === 0) return "";
5886
- const lines = analyzed.map((p) => {
5887
+ const capped = analyzed.slice(0, MAX_EXISTING_PAGES_CONTEXT);
5888
+ const lines = capped.map((p) => {
5887
5889
  return summarizePageAnalysis(p.name || p.id, p.route, p.pageAnalysis);
5888
5890
  });
5889
5891
  let ctx = `EXISTING PAGES CONTEXT:
@@ -5983,12 +5985,15 @@ var RELEVANT_TYPES = {
5983
5985
  function buildTieredComponentsPrompt(manifest, pageType) {
5984
5986
  if (manifest.shared.length === 0) return void 0;
5985
5987
  const relevantTypes = new Set(RELEVANT_TYPES[pageType] || RELEVANT_TYPES.app);
5986
- const level1Lines = manifest.shared.map((e) => {
5988
+ const level1Lines = manifest.shared.slice(0, 20).map((e) => {
5987
5989
  const desc = e.description ? ` \u2014 ${e.description}` : "";
5988
5990
  return `- ${e.id} ${e.name} (${e.type})${desc}`;
5989
5991
  });
5992
+ if (manifest.shared.length > 20) {
5993
+ level1Lines.push(`- ... and ${manifest.shared.length - 20} more (import by name)`);
5994
+ }
5990
5995
  const relevantComponents = manifest.shared.filter((e) => relevantTypes.has(e.type));
5991
- const level2Blocks = relevantComponents.filter((e) => e.propsInterface || e.usageExample).map((e) => {
5996
+ const level2Blocks = relevantComponents.filter((e) => e.propsInterface || e.usageExample).slice(0, 8).map((e) => {
5992
5997
  const importPath = e.file.replace(/^components\/shared\//, "").replace(/\.tsx$/, "");
5993
5998
  const lines = [`### ${e.name} (${e.id})`];
5994
5999
  if (e.propsInterface) lines.push(`Props: ${e.propsInterface}`);
@@ -6063,10 +6068,14 @@ function readExistingAppPageForReference(projectRoot, plan) {
6063
6068
  if (note.type !== "app") continue;
6064
6069
  for (const group of ["(app)", "(admin)", "(dashboard)"]) {
6065
6070
  const filePath = resolve6(projectRoot, "app", group, key, "page.tsx");
6066
- if (existsSync14(filePath)) {
6067
- const code = readFileSync9(filePath, "utf-8");
6068
- const lines = code.split("\n");
6069
- return lines.slice(0, 60).join("\n");
6071
+ try {
6072
+ if (existsSync14(filePath)) {
6073
+ const code = readFileSync9(filePath, "utf-8");
6074
+ const lines = code.split("\n");
6075
+ return lines.slice(0, 60).join("\n");
6076
+ }
6077
+ } catch {
6078
+ continue;
6070
6079
  }
6071
6080
  }
6072
6081
  }
@@ -6281,7 +6290,11 @@ async function splitGeneratePages(spinner, message, modCtx, provider, parseOpts)
6281
6290
  changes: { id: homePage.id, name: homePage.name, route: homePage.route }
6282
6291
  };
6283
6292
  }
6284
- spinner.succeed(`Phase 3/6 \u2014 ${homePage.name} page generated`);
6293
+ if (homePageCode) {
6294
+ spinner.succeed(`Phase 3/6 \u2014 ${homePage.name} page generated`);
6295
+ } else {
6296
+ spinner.warn(`Phase 3/6 \u2014 ${homePage.name} page generated (no code \u2014 AI may have failed)`);
6297
+ }
6285
6298
  }
6286
6299
  spinner.start("Phase 4/6 \u2014 Extracting design patterns...");
6287
6300
  const styleContext = homePageCode ? extractStyleContext(homePageCode) : "";
@@ -6411,6 +6424,7 @@ ${existingAppPageCode}
6411
6424
  `Create ONE page called "${name}" at route "${route}".`,
6412
6425
  `Context: ${message}.`,
6413
6426
  `Generate complete pageCode for this single page only. Do not generate other pages.`,
6427
+ `FORBIDDEN in pageCode: <header>, <nav>, <footer>, site-wide navigation, copyright footers. The layout provides all of these.`,
6414
6428
  `PAGE TYPE: ${pageType}`,
6415
6429
  designConstraints,
6416
6430
  layoutNote,
@@ -6521,7 +6535,11 @@ Keep all existing functionality. Only add imports and replace inline duplicates.
6521
6535
  }
6522
6536
  }
6523
6537
  const withCode = allRequests.filter((r) => r.changes?.pageCode).length;
6524
- spinner.succeed(`Phase 5/6 \u2014 Generated ${allRequests.length} pages (${withCode} with full code)`);
6538
+ if (withCode === 0) {
6539
+ spinner.warn(`Phase 5/6 \u2014 Generated ${allRequests.length} pages (0 with full code \u2014 AI may have failed)`);
6540
+ } else {
6541
+ spinner.succeed(`Phase 5/6 \u2014 Generated ${allRequests.length} pages (${withCode} with full code)`);
6542
+ }
6525
6543
  return { requests: allRequests, plan };
6526
6544
  }
6527
6545
  var SharedExtractionItemSchema = z.object({
@@ -7083,7 +7101,8 @@ function printPostGenerationReport(opts) {
7083
7101
  if (inCodeShared.length > 0) {
7084
7102
  console.log(chalk10.dim(` Shared: ${inCodeShared.map((s) => `${s.id} (${s.name})`).join(", ")}`));
7085
7103
  }
7086
- if (layoutShared.length > 0) {
7104
+ const isAuthPage = route && (/^\/(login|signin|signup|register|forgot-password|reset-password)\b/.test(route) || filePath.includes("(auth)"));
7105
+ if (layoutShared.length > 0 && !isAuthPage) {
7087
7106
  console.log(chalk10.dim(` Layout: ${layoutShared.map((l) => `${l.id} (${l.name})`).join(", ")} via layout.tsx`));
7088
7107
  }
7089
7108
  if (iconCount > 0) {
@@ -7287,21 +7306,30 @@ function getChangeDescription(request, config2) {
7287
7306
 
7288
7307
  // src/commands/chat/modification-handler.ts
7289
7308
  var DEBUG2 = process.env.COHERENT_DEBUG === "1";
7309
+ function isSiteWideHeader(block) {
7310
+ return /<Link\b/.test(block) || /<a\s+href/.test(block) || /<nav\b/.test(block);
7311
+ }
7312
+ function isSiteWideFooter(block) {
7313
+ return /©|&copy;|All rights|<Link\b/.test(block) || /<a\s+href/.test(block) && block.split("<a ").length >= 3;
7314
+ }
7315
+ function isSiteWideNav(block) {
7316
+ return /<Link\b/.test(block) || /<a\s+href/.test(block) && block.split(/<a\s+href/).length >= 3;
7317
+ }
7290
7318
  function stripInlineLayoutElements(code) {
7291
7319
  let result = code;
7292
7320
  const stripped = [];
7293
7321
  const headerBlock = extractBalancedTag(result, "header");
7294
- if (headerBlock) {
7322
+ if (headerBlock && isSiteWideHeader(headerBlock)) {
7295
7323
  result = result.replace(headerBlock, "");
7296
7324
  stripped.push("header");
7297
7325
  }
7298
7326
  const navBlock = extractBalancedTag(result, "nav");
7299
- if (navBlock) {
7327
+ if (navBlock && isSiteWideNav(navBlock)) {
7300
7328
  result = result.replace(navBlock, "");
7301
7329
  stripped.push("nav");
7302
7330
  }
7303
7331
  const footerBlock = extractBalancedTag(result, "footer");
7304
- if (footerBlock) {
7332
+ if (footerBlock && isSiteWideFooter(footerBlock)) {
7305
7333
  result = result.replace(footerBlock, "");
7306
7334
  stripped.push("footer");
7307
7335
  }
@@ -7444,7 +7472,7 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
7444
7472
  };
7445
7473
  }
7446
7474
  const readPlan = projectRoot ? loadPlan(projectRoot) : null;
7447
- const pageFilePath = routeToFsPath(projectRoot, route, readPlan || false);
7475
+ const pageFilePath = routeToFsPath(projectRoot, route, readPlan || isAuthRoute(route));
7448
7476
  let pageCode;
7449
7477
  try {
7450
7478
  pageCode = await readFile(pageFilePath);
@@ -7475,8 +7503,7 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
7475
7503
  await writeFile(pageFilePath, fixedCode);
7476
7504
  const manifest = await loadManifest6(projectRoot);
7477
7505
  const usedIn = manifest.shared.find((e) => e.id === resolved.id)?.usedIn ?? [];
7478
- const routePath = route.replace(/^\//, "");
7479
- const filePathRel = routePath ? `app/${routePath}/page.tsx` : "app/page.tsx";
7506
+ const filePathRel = routeToRelPath(route, readPlan || isAuthRoute(route));
7480
7507
  if (!usedIn.includes(filePathRel)) {
7481
7508
  const nextManifest = updateUsedIn(manifest, resolved.id, [...usedIn, filePathRel]);
7482
7509
  await saveManifest2(projectRoot, nextManifest);
@@ -7516,13 +7543,11 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
7516
7543
  };
7517
7544
  }
7518
7545
  const allPagesToLink = [sourcePageName, ...targetPages];
7546
+ const promotePlan = projectRoot ? loadPlan(projectRoot) : null;
7519
7547
  const routeToPath = (nameOrRoute) => {
7520
- if (nameOrRoute.startsWith("/")) {
7521
- return routeToRelPath(nameOrRoute, false);
7522
- }
7523
- const p = config2.pages.find((x) => x.name?.toLowerCase() === nameOrRoute.toLowerCase() || x.id === nameOrRoute);
7524
- if (!p?.route) return null;
7525
- return routeToRelPath(p.route, false);
7548
+ const route = nameOrRoute.startsWith("/") ? nameOrRoute : config2.pages.find((x) => x.name?.toLowerCase() === nameOrRoute.toLowerCase() || x.id === nameOrRoute)?.route;
7549
+ if (!route) return null;
7550
+ return routeToRelPath(route, promotePlan || isAuthRoute(route));
7526
7551
  };
7527
7552
  const sourcePath = routeToPath(sourcePageName);
7528
7553
  if (!sourcePath) {
@@ -8503,7 +8528,7 @@ async function chatCommand(message, options) {
8503
8528
  spinner.start(`Creating shared component: ${componentName}...`);
8504
8529
  const { createAIProvider: createAIProvider2 } = await import("./ai-provider-CGSIYFZT.js");
8505
8530
  const { generateSharedComponent: generateSharedComponent7 } = await import("@getcoherent/core");
8506
- const { autoFixCode: autoFixCode2 } = await import("./quality-validator-BX3T5X6Z.js");
8531
+ const { autoFixCode: autoFixCode2 } = await import("./quality-validator-P572ZUW5.js");
8507
8532
  const { extractPropsInterface, extractDependencies } = await import("./component-extractor-VYJLT5NR.js");
8508
8533
  const aiProvider = await createAIProvider2(provider ?? "auto");
8509
8534
  const prompt = `Generate a React component called "${componentName}". Description: ${message}.
@@ -4,7 +4,7 @@ import {
4
4
  formatIssues,
5
5
  validatePageQuality,
6
6
  verifyIncrementalEdit
7
- } from "./chunk-CF4377B7.js";
7
+ } from "./chunk-VKGZRKWD.js";
8
8
  import "./chunk-3RG5ZIWI.js";
9
9
  export {
10
10
  autoFixCode,
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.6.19",
6
+ "version": "0.6.21",
7
7
  "description": "CLI interface for Coherent Design Method",
8
8
  "type": "module",
9
9
  "main": "./dist/index.js",