@getcoherent/cli 0.6.20 → 0.6.22

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/LICENSE ADDED
@@ -0,0 +1,21 @@
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.
@@ -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) : "";
@@ -6522,7 +6535,11 @@ Keep all existing functionality. Only add imports and replace inline duplicates.
6522
6535
  }
6523
6536
  }
6524
6537
  const withCode = allRequests.filter((r) => r.changes?.pageCode).length;
6525
- 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
+ }
6526
6543
  return { requests: allRequests, plan };
6527
6544
  }
6528
6545
  var SharedExtractionItemSchema = z.object({
@@ -7289,21 +7306,30 @@ function getChangeDescription(request, config2) {
7289
7306
 
7290
7307
  // src/commands/chat/modification-handler.ts
7291
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
+ }
7292
7318
  function stripInlineLayoutElements(code) {
7293
7319
  let result = code;
7294
7320
  const stripped = [];
7295
7321
  const headerBlock = extractBalancedTag(result, "header");
7296
- if (headerBlock) {
7322
+ if (headerBlock && isSiteWideHeader(headerBlock)) {
7297
7323
  result = result.replace(headerBlock, "");
7298
7324
  stripped.push("header");
7299
7325
  }
7300
7326
  const navBlock = extractBalancedTag(result, "nav");
7301
- if (navBlock) {
7327
+ if (navBlock && isSiteWideNav(navBlock)) {
7302
7328
  result = result.replace(navBlock, "");
7303
7329
  stripped.push("nav");
7304
7330
  }
7305
7331
  const footerBlock = extractBalancedTag(result, "footer");
7306
- if (footerBlock) {
7332
+ if (footerBlock && isSiteWideFooter(footerBlock)) {
7307
7333
  result = result.replace(footerBlock, "");
7308
7334
  stripped.push("footer");
7309
7335
  }
@@ -7446,7 +7472,7 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
7446
7472
  };
7447
7473
  }
7448
7474
  const readPlan = projectRoot ? loadPlan(projectRoot) : null;
7449
- const pageFilePath = routeToFsPath(projectRoot, route, readPlan || false);
7475
+ const pageFilePath = routeToFsPath(projectRoot, route, readPlan || isAuthRoute(route));
7450
7476
  let pageCode;
7451
7477
  try {
7452
7478
  pageCode = await readFile(pageFilePath);
@@ -7477,8 +7503,7 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
7477
7503
  await writeFile(pageFilePath, fixedCode);
7478
7504
  const manifest = await loadManifest6(projectRoot);
7479
7505
  const usedIn = manifest.shared.find((e) => e.id === resolved.id)?.usedIn ?? [];
7480
- const routePath = route.replace(/^\//, "");
7481
- const filePathRel = routePath ? `app/${routePath}/page.tsx` : "app/page.tsx";
7506
+ const filePathRel = routeToRelPath(route, readPlan || isAuthRoute(route));
7482
7507
  if (!usedIn.includes(filePathRel)) {
7483
7508
  const nextManifest = updateUsedIn(manifest, resolved.id, [...usedIn, filePathRel]);
7484
7509
  await saveManifest2(projectRoot, nextManifest);
@@ -7518,13 +7543,11 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
7518
7543
  };
7519
7544
  }
7520
7545
  const allPagesToLink = [sourcePageName, ...targetPages];
7546
+ const promotePlan = projectRoot ? loadPlan(projectRoot) : null;
7521
7547
  const routeToPath = (nameOrRoute) => {
7522
- if (nameOrRoute.startsWith("/")) {
7523
- return routeToRelPath(nameOrRoute, false);
7524
- }
7525
- const p = config2.pages.find((x) => x.name?.toLowerCase() === nameOrRoute.toLowerCase() || x.id === nameOrRoute);
7526
- if (!p?.route) return null;
7527
- 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));
7528
7551
  };
7529
7552
  const sourcePath = routeToPath(sourcePageName);
7530
7553
  if (!sourcePath) {
@@ -8505,7 +8528,7 @@ async function chatCommand(message, options) {
8505
8528
  spinner.start(`Creating shared component: ${componentName}...`);
8506
8529
  const { createAIProvider: createAIProvider2 } = await import("./ai-provider-CGSIYFZT.js");
8507
8530
  const { generateSharedComponent: generateSharedComponent7 } = await import("@getcoherent/core");
8508
- const { autoFixCode: autoFixCode2 } = await import("./quality-validator-BX3T5X6Z.js");
8531
+ const { autoFixCode: autoFixCode2 } = await import("./quality-validator-P572ZUW5.js");
8509
8532
  const { extractPropsInterface, extractDependencies } = await import("./component-extractor-VYJLT5NR.js");
8510
8533
  const aiProvider = await createAIProvider2(provider ?? "auto");
8511
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.20",
6
+ "version": "0.6.22",
7
7
  "description": "CLI interface for Coherent Design Method",
8
8
  "type": "module",
9
9
  "main": "./dist/index.js",
@@ -33,15 +33,8 @@
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
- },
42
36
  "dependencies": {
43
37
  "@anthropic-ai/sdk": "^0.32.0",
44
- "@getcoherent/core": "workspace:*",
45
38
  "chalk": "^5.3.0",
46
39
  "chokidar": "^4.0.1",
47
40
  "commander": "^11.1.0",
@@ -49,7 +42,8 @@
49
42
  "open": "^10.1.0",
50
43
  "ora": "^7.0.1",
51
44
  "prompts": "^2.4.2",
52
- "zod": "^3.22.4"
45
+ "zod": "^3.22.4",
46
+ "@getcoherent/core": "0.6.22"
53
47
  },
54
48
  "devDependencies": {
55
49
  "@types/node": "^20.11.0",
@@ -58,5 +52,11 @@
58
52
  "react": "^19.2.4",
59
53
  "tsup": "^8.0.1",
60
54
  "typescript": "^5.3.3"
55
+ },
56
+ "scripts": {
57
+ "dev": "tsup --watch",
58
+ "build": "tsup",
59
+ "typecheck": "tsc --noEmit",
60
+ "test": "vitest"
61
61
  }
62
- }
62
+ }