@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 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>"` | Parse NL, update config, regenerate pages/components/nav (e.g. add page, change tokens). |
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-E23FJX2I.js";
5
+ } from "./chunk-NLL3SHN3.js";
6
6
  import {
7
7
  autoFixCode
8
- } from "./chunk-NOM47EB4.js";
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
- - Backgrounds: bg-background, bg-muted, bg-muted/50, bg-card, bg-primary, bg-secondary, bg-destructive.
32
- - Text: text-foreground (default), text-muted-foreground, text-primary-foreground.
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-zinc-900/50 border-border/10 backdrop-blur-sm
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 or emerald-400)
131
- - Use for: CTAs, terminal text, check icons, feature icon backgrounds, active states
132
- - NEVER mix blue + purple + emerald on same page
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-emerald-400 for positive, text-red-400 for negative
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 or bg-zinc-900/50)
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 (or text-emerald-400 for dark themes)
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 (green dollar sign)
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-red-400
181
- - Positive card: accent border (border-emerald-500/20), items with Check icon text-emerald-400
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-emerald-600 (light) / text-emerald-400 (dark) \u2014 exception to semantic-only rule for trend indicators.
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-emerald-500" /><span className="text-sm">Active</span></div>
534
- - Colors: bg-emerald-500 (active/online), bg-destructive (error/offline), bg-yellow-500 (warning), bg-muted-foreground (inactive).
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-emerald-600 flex items-center"><ArrowUp className="size-3 mr-1" />12.5%</span>
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 = /(?:(?:hover|focus|active|group-hover|focus-visible|focus-within):)?(?:bg|text|border|ring|outline|shadow|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;
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((?:(?:hover|focus|active|group-hover|focus-visible|focus-within):)?)(bg|text|border|ring|outline|shadow|from|to|via)-(emerald|blue|violet|indigo|purple|teal|cyan|sky|rose|amber|red|green|yellow|pink|orange|fuchsia|lime)-(\d+)(?:\/\d+)?\b/g;
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((?:(?:hover|focus|active|group-hover|focus-visible|focus-within):)?)(bg|text|border|ring|outline|shadow)-(zinc|slate|gray|neutral|stone)-(\d+)(?:\/\d+)?\b/g;
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-VLBVBF6V.js";
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-PIDLFRWZ.js");
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-4I7ATX6G.js";
16
- import "./chunk-VLBVBF6V.js";
15
+ } from "./chunk-UZGT5APM.js";
16
+ import "./chunk-IFJK5OPI.js";
17
17
  import "./chunk-4TLYDTT3.js";
18
- import "./chunk-E23FJX2I.js";
19
- import "./chunk-NOM47EB4.js";
18
+ import "./chunk-NLL3SHN3.js";
19
+ import "./chunk-U5SNPHVU.js";
20
20
  import "./chunk-3RG5ZIWI.js";
21
21
  export {
22
22
  buildAppLayoutCode,
@@ -18,7 +18,7 @@ import {
18
18
  getDesignQualityForType,
19
19
  inferPageTypeFromRoute,
20
20
  selectContextualRules
21
- } from "./chunk-E23FJX2I.js";
21
+ } from "./chunk-NLL3SHN3.js";
22
22
  import "./chunk-3RG5ZIWI.js";
23
23
  export {
24
24
  CORE_CONSTRAINTS,