@getcoherent/cli 0.6.49 → 0.6.51
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/README.md +6 -1
- package/dist/{chunk-VLBVBF6V.js → chunk-IFJK5OPI.js} +2 -2
- package/dist/{chunk-E23FJX2I.js → chunk-NLL3SHN3.js} +17 -19
- package/dist/{chunk-NOM47EB4.js → chunk-U5SNPHVU.js} +90 -4
- package/dist/{chunk-4I7ATX6G.js → chunk-UZGT5APM.js} +2 -2
- package/dist/{code-generator-YSGVHVNN.js → code-generator-IZ6XM6WG.js} +4 -4
- package/dist/{design-constraints-HGNEY3W3.js → design-constraints-PFZDW2XW.js} +1 -1
- package/dist/index.js +493 -441
- package/dist/{plan-generator-PIDLFRWZ.js → plan-generator-D2UBTVCN.js} +3 -3
- package/dist/{quality-validator-2KIT6QKA.js → quality-validator-IM6YFKLI.js} +3 -1
- package/package.json +10 -10
- package/LICENSE +0 -21
package/README.md
CHANGED
|
@@ -26,9 +26,14 @@ coherent preview
|
|
|
26
26
|
| Command | Description |
|
|
27
27
|
|---------|-------------|
|
|
28
28
|
| `coherent init` | Create project: config, app, design-system viewer, docs. Non-interactive (optional provider flag). |
|
|
29
|
-
| `coherent chat "<request>"` |
|
|
29
|
+
| `coherent chat "<request>"` | Generate or modify pages using natural language. Includes generation-time TypeScript validation. |
|
|
30
30
|
| `coherent preview` | Start Next.js dev server. |
|
|
31
|
+
| `coherent fix` | Auto-fix everything: TypeScript errors (deterministic + AI), missing components, CSS, raw colors, layouts. |
|
|
32
|
+
| `coherent check` | Show all problems: page quality, component integrity, broken links. |
|
|
33
|
+
| `coherent sync` | Sync Design System with code after manual edits. |
|
|
31
34
|
| `coherent export` | Production build; optional Vercel/Netlify config. |
|
|
35
|
+
| `coherent undo` | Restore project to state before the last `coherent chat`. |
|
|
36
|
+
| `coherent update` | Apply platform updates to an existing project. |
|
|
32
37
|
| `coherent status` | Print config summary (pages, components). |
|
|
33
38
|
| `coherent components` | List registered components. |
|
|
34
39
|
|
|
@@ -2,10 +2,10 @@ import {
|
|
|
2
2
|
CORE_CONSTRAINTS,
|
|
3
3
|
getDesignQualityForType,
|
|
4
4
|
inferPageTypeFromRoute
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-NLL3SHN3.js";
|
|
6
6
|
import {
|
|
7
7
|
autoFixCode
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-U5SNPHVU.js";
|
|
9
9
|
|
|
10
10
|
// src/commands/chat/plan-generator.ts
|
|
11
11
|
import { z } from "zod";
|
|
@@ -28,10 +28,8 @@ TYPOGRAPHY (most impactful rules):
|
|
|
28
28
|
- Create hierarchy through font WEIGHT (medium \u2192 semibold \u2192 bold), NOT font SIZE.
|
|
29
29
|
|
|
30
30
|
COLORS \u2014 ONLY SEMANTIC TOKENS (zero raw colors):
|
|
31
|
-
-
|
|
32
|
-
-
|
|
33
|
-
- Borders: border (no color suffix \u2014 uses CSS variable). border-b for headers.
|
|
34
|
-
- BANNED: bg-gray-*, bg-blue-*, bg-slate-*, text-gray-*, ANY raw Tailwind color. The validator REJECTS these.
|
|
31
|
+
- Allowed: bg-background, bg-muted, bg-muted/50, bg-card, bg-primary, bg-secondary, bg-destructive, bg-success, bg-warning. text-foreground, text-muted-foreground, text-primary-foreground, text-destructive, text-success. border (bare), border-border. Opacity modifiers OK (bg-primary/50). ring-*, shadow-*, fill-*, stroke-* with same token names.
|
|
32
|
+
- BANNED: ANY raw Tailwind color (bg-gray-*, text-blue-*, etc.), inline style colors, hex values, bg-white, bg-black. The validator REJECTS all of these.
|
|
35
33
|
|
|
36
34
|
SPACING (restricted palette \u2014 only multiples of 4px):
|
|
37
35
|
- Page content padding: p-4 lg:p-6. Gap between major sections: gap-6 md:gap-8.
|
|
@@ -115,7 +113,7 @@ var DESIGN_QUALITY_COMMON = `
|
|
|
115
113
|
|
|
116
114
|
### Visual Depth & Layers
|
|
117
115
|
- Cards: bg-card border border-border/15 rounded-xl (not rounded-md)
|
|
118
|
-
- Cards on dark pages: bg-
|
|
116
|
+
- Cards on dark pages: bg-card border-border/10 backdrop-blur-sm
|
|
119
117
|
- Cards MUST have hover state: hover:border-border/30 transition-colors
|
|
120
118
|
- Sections alternate between bg-background and bg-muted/5 for rhythm
|
|
121
119
|
- Section dividers: border-t border-border/10 (subtle, not heavy)
|
|
@@ -127,18 +125,18 @@ var DESIGN_QUALITY_COMMON = `
|
|
|
127
125
|
- CTA buttons: use the Button component, NEVER raw <button> or <a> styled as button
|
|
128
126
|
|
|
129
127
|
### Accent Color Discipline
|
|
130
|
-
- ONE accent color per page (primary
|
|
131
|
-
- Use for: CTAs,
|
|
132
|
-
- NEVER mix
|
|
128
|
+
- ONE accent color per page (primary)
|
|
129
|
+
- Use for: CTAs, check icons, feature icon backgrounds, active states
|
|
130
|
+
- NEVER mix multiple accent colors on same page
|
|
133
131
|
- Badge: outline style (border-border/30 bg-transparent) not filled color
|
|
134
|
-
- Status icons: text-
|
|
132
|
+
- Status icons: text-success for positive, text-destructive for negative
|
|
135
133
|
|
|
136
134
|
### Dark Theme Implementation
|
|
137
135
|
- html element: className="dark"
|
|
138
136
|
- Background: use CSS variables from globals.css dark section
|
|
139
137
|
- Text: text-foreground for primary, text-muted-foreground for secondary
|
|
140
138
|
- NEVER hardcode dark colors (bg-gray-900) \u2014 always use semantic tokens
|
|
141
|
-
- Cards and elevated elements: slightly lighter than background (bg-card
|
|
139
|
+
- Cards and elevated elements: slightly lighter than background (bg-card)
|
|
142
140
|
`;
|
|
143
141
|
var DESIGN_QUALITY_MARKETING = `
|
|
144
142
|
## DESIGN QUALITY \u2014 MARKETING PAGES
|
|
@@ -155,15 +153,15 @@ var DESIGN_QUALITY_MARKETING = `
|
|
|
155
153
|
|
|
156
154
|
### Icons in Feature Cards
|
|
157
155
|
- Wrap in colored container: bg-primary/10 rounded-lg p-2.5
|
|
158
|
-
- Icon color: text-primary
|
|
156
|
+
- Icon color: text-primary
|
|
159
157
|
- Icon size: h-5 w-5 inside the container
|
|
160
158
|
- NEVER bare icons floating in a card without container
|
|
161
159
|
|
|
162
160
|
### Terminal / Code Blocks
|
|
163
161
|
- Background: bg-zinc-950 (near-black, not gray)
|
|
164
162
|
- Border: border-border/10 rounded-xl
|
|
165
|
-
- Text: font-mono text-sm text-emerald-400
|
|
166
|
-
- Prompt: text-emerald-500 "$ " prefix (
|
|
163
|
+
- Text: font-mono text-sm text-emerald-400 (EXCEPTION: terminal blocks use raw green for aesthetics)
|
|
164
|
+
- Prompt: text-emerald-500 "$ " prefix (EXCEPTION: terminal prompt uses raw green)
|
|
167
165
|
- Title bar (optional): flex with 3 dots (bg-zinc-700 rounded-full w-2.5 h-2.5) + title text-zinc-500 text-[11px]
|
|
168
166
|
- Copy button: text-zinc-500 hover:text-zinc-300
|
|
169
167
|
|
|
@@ -177,8 +175,8 @@ var DESIGN_QUALITY_MARKETING = `
|
|
|
177
175
|
|
|
178
176
|
### Comparison Sections (before/after, with/without)
|
|
179
177
|
- Two cards side by side: grid md:grid-cols-2 gap-6
|
|
180
|
-
- Negative card: neutral border, items with X icon text-
|
|
181
|
-
- Positive card: accent border (border-
|
|
178
|
+
- Negative card: neutral border, items with X icon text-destructive
|
|
179
|
+
- Positive card: accent border (border-primary/20), items with Check icon text-success
|
|
182
180
|
- Header of each card: text-sm font-semibold uppercase tracking-wider
|
|
183
181
|
|
|
184
182
|
### Step/Process Sections
|
|
@@ -493,7 +491,7 @@ var RULES_DATA_DISPLAY = `
|
|
|
493
491
|
DATA DISPLAY RULES:
|
|
494
492
|
|
|
495
493
|
STAT / METRIC CARDS: See the Stats Grid reference pattern in DESIGN QUALITY \u2014 APP PAGES. Follow that exact pattern.
|
|
496
|
-
- Trend up: text-
|
|
494
|
+
- Trend up: text-success.
|
|
497
495
|
- Trend down: text-destructive.
|
|
498
496
|
- Trend icon: ArrowUp / ArrowDown className="size-3 inline mr-1".
|
|
499
497
|
- No actions on stat cards. Click entire card to drill down (if applicable).
|
|
@@ -530,12 +528,12 @@ MOCK DATA IN COMPONENTS:
|
|
|
530
528
|
- NEVER store display strings ("2 hours ago", "Yesterday") in data \u2014 always compute from ISO date.
|
|
531
529
|
|
|
532
530
|
STATUS INDICATORS (dot + text):
|
|
533
|
-
- Pattern: <div className="flex items-center gap-2"><div className="size-2 rounded-full bg-
|
|
534
|
-
- Colors: bg-
|
|
531
|
+
- Pattern: <div className="flex items-center gap-2"><div className="size-2 rounded-full bg-success" /><span className="text-sm">Active</span></div>
|
|
532
|
+
- Colors: bg-success (active/online), bg-destructive (error/offline), bg-warning (warning), bg-muted-foreground (inactive).
|
|
535
533
|
- Alternative: use Badge variants for status in tables/lists (preferred over dots).
|
|
536
534
|
|
|
537
535
|
TREND INDICATORS:
|
|
538
|
-
- Up (positive): <span className="text-sm text-
|
|
536
|
+
- Up (positive): <span className="text-sm text-success flex items-center"><ArrowUp className="size-3 mr-1" />12.5%</span>
|
|
539
537
|
- Down (negative): <span className="text-sm text-destructive flex items-center"><ArrowDown className="size-3 mr-1" />3.2%</span>
|
|
540
538
|
- Neutral: <span className="text-sm text-muted-foreground">0%</span>
|
|
541
539
|
- Always include arrow icon + sign (+ or -) + percentage.
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
// src/utils/quality-validator.ts
|
|
2
|
+
import { readdir, readFile } from "fs/promises";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
|
|
1
5
|
// src/utils/component-rules.ts
|
|
2
6
|
function extractJsxElementProps(code, openTagStart) {
|
|
3
7
|
let i = openTagStart;
|
|
@@ -155,7 +159,12 @@ function applyComponentRules(code) {
|
|
|
155
159
|
}
|
|
156
160
|
|
|
157
161
|
// src/utils/quality-validator.ts
|
|
158
|
-
var RAW_COLOR_RE = /(?:(?:
|
|
162
|
+
var RAW_COLOR_RE = /(?:(?:[a-z][a-z0-9-]*:)*)?(?:bg|text|border|ring|outline|shadow|from|to|via|divide|placeholder|decoration|caret|fill|stroke|accent)-(gray|blue|red|green|yellow|purple|pink|indigo|orange|slate|zinc|stone|neutral|emerald|teal|cyan|sky|violet|fuchsia|rose|amber|lime)-\d+/g;
|
|
163
|
+
var RAW_BW_COLOR_RE = /(?:(?:[a-z][a-z0-9-]*:)*)?(?:bg|text|border|ring|outline|shadow|divide|fill|stroke)-(white|black)\b/g;
|
|
164
|
+
var INLINE_STYLE_COLOR_RE = /style=\{[^}]*(color|background|backgroundColor|borderColor)\s*:\s*['"]?(#[0-9a-fA-F]{3,8}|rgb|hsl|red|blue|orange|green|purple|yellow|pink|white|black|gray|grey)\b/gi;
|
|
165
|
+
var ARBITRARY_COLOR_RE = /\b(?:bg|text|border|ring|shadow|fill|stroke|from|to|via)-\[(?:#[0-9a-fA-F]{3,8}|rgb|hsl|color-mix)/gi;
|
|
166
|
+
var SVG_COLOR_RE = /\b(?:fill|stroke)=["'](?!none|currentColor|url|inherit|transparent)([^"']+)["']/g;
|
|
167
|
+
var COLOR_PROP_RE = /\b(?:color|accentColor|iconColor|fillColor)=["']#[0-9a-fA-F]{3,8}["']/g;
|
|
159
168
|
var HEX_IN_CLASS_RE = /className="[^"]*#[0-9a-fA-F]{3,8}[^"]*"/g;
|
|
160
169
|
var TEXT_BASE_RE = /\btext-base\b/g;
|
|
161
170
|
var HEAVY_SHADOW_RE = /\bshadow-(md|lg|xl|2xl)\b/g;
|
|
@@ -251,6 +260,51 @@ function validatePageQuality(code, validRoutes, pageType) {
|
|
|
251
260
|
"error"
|
|
252
261
|
).filter((issue) => !isTerminalContext(issue.line))
|
|
253
262
|
);
|
|
263
|
+
issues.push(
|
|
264
|
+
...checkLines(
|
|
265
|
+
code,
|
|
266
|
+
RAW_BW_COLOR_RE,
|
|
267
|
+
"RAW_COLOR",
|
|
268
|
+
"Use semantic tokens (bg-background, text-foreground) instead of white/black",
|
|
269
|
+
"error"
|
|
270
|
+
)
|
|
271
|
+
);
|
|
272
|
+
issues.push(
|
|
273
|
+
...checkLines(
|
|
274
|
+
code,
|
|
275
|
+
INLINE_STYLE_COLOR_RE,
|
|
276
|
+
"inline-style-color",
|
|
277
|
+
"Use semantic Tailwind classes instead of inline style colors",
|
|
278
|
+
"error"
|
|
279
|
+
)
|
|
280
|
+
);
|
|
281
|
+
issues.push(
|
|
282
|
+
...checkLines(
|
|
283
|
+
code,
|
|
284
|
+
ARBITRARY_COLOR_RE,
|
|
285
|
+
"arbitrary-color",
|
|
286
|
+
"Use semantic tokens instead of arbitrary color values like bg-[#hex]",
|
|
287
|
+
"error"
|
|
288
|
+
)
|
|
289
|
+
);
|
|
290
|
+
issues.push(
|
|
291
|
+
...checkLines(
|
|
292
|
+
code,
|
|
293
|
+
SVG_COLOR_RE,
|
|
294
|
+
"svg-raw-color",
|
|
295
|
+
"Use currentColor or CSS variables for SVG fill/stroke, not raw colors",
|
|
296
|
+
"error"
|
|
297
|
+
)
|
|
298
|
+
);
|
|
299
|
+
issues.push(
|
|
300
|
+
...checkLines(
|
|
301
|
+
code,
|
|
302
|
+
COLOR_PROP_RE,
|
|
303
|
+
"color-prop",
|
|
304
|
+
"Use semantic color tokens instead of hex values in color props",
|
|
305
|
+
"error"
|
|
306
|
+
)
|
|
307
|
+
);
|
|
254
308
|
issues.push(
|
|
255
309
|
...checkLines(
|
|
256
310
|
code,
|
|
@@ -613,7 +667,7 @@ function resolveHref(linkText, context) {
|
|
|
613
667
|
function replaceRawColors(classes, colorMap) {
|
|
614
668
|
let changed = false;
|
|
615
669
|
let result = classes;
|
|
616
|
-
const accentColorRe = /\b((?:(?:
|
|
670
|
+
const accentColorRe = /\b((?:(?:[a-z][a-z0-9-]*:)*)?)(bg|text|border|ring|outline|shadow|from|to|via|divide|placeholder|decoration|caret|fill|stroke|accent)-(emerald|blue|violet|indigo|purple|teal|cyan|sky|rose|amber|red|green|yellow|pink|orange|fuchsia|lime)-(\d+)(?:\/\d+)?\b/g;
|
|
617
671
|
result = result.replace(accentColorRe, (m, statePrefix, prefix, color, shade) => {
|
|
618
672
|
const bareNoOpacity = m.replace(statePrefix, "").replace(/\/\d+$/, "");
|
|
619
673
|
if (colorMap[bareNoOpacity]) {
|
|
@@ -668,7 +722,7 @@ function replaceRawColors(classes, colorMap) {
|
|
|
668
722
|
}
|
|
669
723
|
return m;
|
|
670
724
|
});
|
|
671
|
-
const neutralColorRe = /\b((?:(?:
|
|
725
|
+
const neutralColorRe = /\b((?:(?:[a-z][a-z0-9-]*:)*)?)(bg|text|border|ring|outline|shadow|divide|placeholder|decoration|caret|fill|stroke|accent)-(zinc|slate|gray|neutral|stone)-(\d+)(?:\/\d+)?\b/g;
|
|
672
726
|
result = result.replace(neutralColorRe, (m, statePrefix, prefix, _color, shade) => {
|
|
673
727
|
const bareNoOpacity = m.replace(statePrefix, "").replace(/\/\d+$/, "");
|
|
674
728
|
if (colorMap[bareNoOpacity]) {
|
|
@@ -1041,6 +1095,15 @@ ${fixed}`;
|
|
|
1041
1095
|
return fullMatch;
|
|
1042
1096
|
});
|
|
1043
1097
|
if (hadColorFix) fixes.push("raw colors \u2192 semantic tokens");
|
|
1098
|
+
if (hadColorFix) {
|
|
1099
|
+
const postFixIssues = validatePageQuality(fixed);
|
|
1100
|
+
const postFixErrors = postFixIssues.filter(
|
|
1101
|
+
(i) => i.severity === "error" && ["raw-color", "inline-style-color", "arbitrary-color", "svg-raw-color", "color-prop"].includes(i.type)
|
|
1102
|
+
);
|
|
1103
|
+
if (postFixErrors.length > 0) {
|
|
1104
|
+
fixes.push(`post-fix re-validation found ${postFixErrors.length} remaining color issue(s)`);
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1044
1107
|
const selectRe = /<select\b[^>]*>([\s\S]*?)<\/select>/g;
|
|
1045
1108
|
let hadSelectFix = false;
|
|
1046
1109
|
fixed = fixed.replace(selectRe, (_match, inner) => {
|
|
@@ -1455,11 +1518,34 @@ function verifyIncrementalEdit(before, after) {
|
|
|
1455
1518
|
}
|
|
1456
1519
|
return issues;
|
|
1457
1520
|
}
|
|
1521
|
+
async function validateSharedComponents(projectRoot) {
|
|
1522
|
+
const sharedDir = join(projectRoot, "components", "shared");
|
|
1523
|
+
let files;
|
|
1524
|
+
try {
|
|
1525
|
+
const entries = await readdir(sharedDir);
|
|
1526
|
+
files = entries.filter((f) => f.endsWith(".tsx"));
|
|
1527
|
+
} catch {
|
|
1528
|
+
return [];
|
|
1529
|
+
}
|
|
1530
|
+
const allIssues = [];
|
|
1531
|
+
for (const file of files) {
|
|
1532
|
+
const code = await readFile(join(sharedDir, file), "utf-8");
|
|
1533
|
+
const issues = validatePageQuality(code);
|
|
1534
|
+
for (const issue of issues) {
|
|
1535
|
+
allIssues.push({
|
|
1536
|
+
...issue,
|
|
1537
|
+
message: `[shared/${file}] ${issue.message}`
|
|
1538
|
+
});
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
return allIssues;
|
|
1542
|
+
}
|
|
1458
1543
|
|
|
1459
1544
|
export {
|
|
1460
1545
|
validatePageQuality,
|
|
1461
1546
|
autoFixCode,
|
|
1462
1547
|
formatIssues,
|
|
1463
1548
|
checkDesignConsistency,
|
|
1464
|
-
verifyIncrementalEdit
|
|
1549
|
+
verifyIncrementalEdit,
|
|
1550
|
+
validateSharedComponents
|
|
1465
1551
|
};
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
getPageGroup,
|
|
7
7
|
installPackages,
|
|
8
8
|
sanitizeMetadataStrings
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-IFJK5OPI.js";
|
|
10
10
|
import {
|
|
11
11
|
toKebabCase
|
|
12
12
|
} from "./chunk-4TLYDTT3.js";
|
|
@@ -2147,7 +2147,7 @@ async function regeneratePage(pageId, config, projectRoot) {
|
|
|
2147
2147
|
const code = await generator.generate(page, appType);
|
|
2148
2148
|
const route = page.route || "/";
|
|
2149
2149
|
const isAuth = isAuthRoute(route) || isAuthRoute(page.name || page.id || "");
|
|
2150
|
-
const { loadPlan: loadPlanForPath } = await import("./plan-generator-
|
|
2150
|
+
const { loadPlan: loadPlanForPath } = await import("./plan-generator-D2UBTVCN.js");
|
|
2151
2151
|
const planForPath = loadPlanForPath(projectRoot);
|
|
2152
2152
|
const filePath = routeToFsPath(projectRoot, route, planForPath || isAuth);
|
|
2153
2153
|
await mkdir3(dirname3(filePath), { recursive: true });
|
|
@@ -12,11 +12,11 @@ import {
|
|
|
12
12
|
regeneratePage,
|
|
13
13
|
scanAndInstallSharedDeps,
|
|
14
14
|
validateAndFixGeneratedCode
|
|
15
|
-
} from "./chunk-
|
|
16
|
-
import "./chunk-
|
|
15
|
+
} from "./chunk-UZGT5APM.js";
|
|
16
|
+
import "./chunk-IFJK5OPI.js";
|
|
17
17
|
import "./chunk-4TLYDTT3.js";
|
|
18
|
-
import "./chunk-
|
|
19
|
-
import "./chunk-
|
|
18
|
+
import "./chunk-NLL3SHN3.js";
|
|
19
|
+
import "./chunk-U5SNPHVU.js";
|
|
20
20
|
import "./chunk-3RG5ZIWI.js";
|
|
21
21
|
export {
|
|
22
22
|
buildAppLayoutCode,
|