@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+)
|
|
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
|
|
619
|
-
if (colorMap[
|
|
618
|
+
const bareNoOpacity = m.replace(statePrefix, "").replace(/\/\d+$/, "");
|
|
619
|
+
if (colorMap[bareNoOpacity]) {
|
|
620
620
|
changed = true;
|
|
621
|
-
return statePrefix + colorMap[
|
|
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+)
|
|
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
|
|
654
|
-
if (colorMap[
|
|
653
|
+
const bareNoOpacity = m.replace(statePrefix, "").replace(/\/\d+$/, "");
|
|
654
|
+
if (colorMap[bareNoOpacity]) {
|
|
655
655
|
changed = true;
|
|
656
|
-
return statePrefix + colorMap[
|
|
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
|
|
940
|
-
const
|
|
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
|
-
|
|
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-
|
|
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
|
|
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
|
-
|
|
6067
|
-
|
|
6068
|
-
|
|
6069
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 /©|©|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 ||
|
|
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
|
|
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
|
-
|
|
7523
|
-
|
|
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-
|
|
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}.
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "0.6.
|
|
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
|
+
}
|