@getcoherent/cli 0.6.0 → 0.6.3
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 +21 -0
- package/dist/{chunk-4M2RBSYF.js → chunk-IKHAW6OI.js} +80 -22
- package/dist/{claude-RFHVT7RC.js → claude-BZ3HSBD3.js} +16 -0
- package/dist/index.js +153 -58
- package/dist/{openai-provider-FSXSVEYD.js → openai-provider-XUI7ZHUR.js} +20 -0
- package/dist/{plan-generator-XKMZTEGK.js → plan-generator-R72I6RNM.js} +1 -1
- package/package.json +10 -10
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.
|
|
@@ -3,29 +3,83 @@ import { z } from "zod";
|
|
|
3
3
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
4
4
|
import { dirname, resolve } from "path";
|
|
5
5
|
import { mkdir, writeFile } from "fs/promises";
|
|
6
|
+
import chalk from "chalk";
|
|
7
|
+
var LAYOUT_SYNONYMS = {
|
|
8
|
+
horizontal: "header",
|
|
9
|
+
top: "header",
|
|
10
|
+
nav: "header",
|
|
11
|
+
navbar: "header",
|
|
12
|
+
topbar: "header",
|
|
13
|
+
"top-bar": "header",
|
|
14
|
+
vertical: "sidebar",
|
|
15
|
+
left: "sidebar",
|
|
16
|
+
side: "sidebar",
|
|
17
|
+
drawer: "sidebar",
|
|
18
|
+
full: "both",
|
|
19
|
+
combined: "both",
|
|
20
|
+
empty: "none",
|
|
21
|
+
minimal: "none",
|
|
22
|
+
clean: "none"
|
|
23
|
+
};
|
|
24
|
+
var PAGE_TYPE_SYNONYMS = {
|
|
25
|
+
landing: "marketing",
|
|
26
|
+
public: "marketing",
|
|
27
|
+
home: "marketing",
|
|
28
|
+
website: "marketing",
|
|
29
|
+
static: "marketing",
|
|
30
|
+
application: "app",
|
|
31
|
+
dashboard: "app",
|
|
32
|
+
admin: "app",
|
|
33
|
+
panel: "app",
|
|
34
|
+
console: "app",
|
|
35
|
+
authentication: "auth",
|
|
36
|
+
login: "auth",
|
|
37
|
+
"log-in": "auth",
|
|
38
|
+
register: "auth",
|
|
39
|
+
signin: "auth",
|
|
40
|
+
"sign-in": "auth",
|
|
41
|
+
signup: "auth",
|
|
42
|
+
"sign-up": "auth"
|
|
43
|
+
};
|
|
44
|
+
var COMPONENT_TYPE_SYNONYMS = {
|
|
45
|
+
component: "widget",
|
|
46
|
+
ui: "widget",
|
|
47
|
+
element: "widget",
|
|
48
|
+
block: "widget",
|
|
49
|
+
"page-section": "section",
|
|
50
|
+
hero: "section",
|
|
51
|
+
feature: "section",
|
|
52
|
+
area: "section"
|
|
53
|
+
};
|
|
54
|
+
function normalizeEnum(synonyms) {
|
|
55
|
+
return (v) => {
|
|
56
|
+
const trimmed = v.trim().toLowerCase();
|
|
57
|
+
return synonyms[trimmed] ?? trimmed;
|
|
58
|
+
};
|
|
59
|
+
}
|
|
6
60
|
var RouteGroupSchema = z.object({
|
|
7
61
|
id: z.string(),
|
|
8
|
-
layout: z.enum(["header", "sidebar", "both", "none"]),
|
|
62
|
+
layout: z.string().transform(normalizeEnum(LAYOUT_SYNONYMS)).pipe(z.enum(["header", "sidebar", "both", "none"])),
|
|
9
63
|
pages: z.array(z.string())
|
|
10
64
|
});
|
|
11
65
|
var PlannedComponentSchema = z.object({
|
|
12
66
|
name: z.string(),
|
|
13
|
-
description: z.string(),
|
|
14
|
-
props: z.string(),
|
|
15
|
-
usedBy: z.array(z.string()),
|
|
16
|
-
type: z.enum(["section", "widget"]),
|
|
67
|
+
description: z.string().default(""),
|
|
68
|
+
props: z.string().default("{}"),
|
|
69
|
+
usedBy: z.array(z.string()).default([]),
|
|
70
|
+
type: z.string().transform(normalizeEnum(COMPONENT_TYPE_SYNONYMS)).pipe(z.enum(["section", "widget"])),
|
|
17
71
|
shadcnDeps: z.array(z.string()).default([])
|
|
18
72
|
});
|
|
19
73
|
var PageNoteSchema = z.object({
|
|
20
|
-
type: z.enum(["marketing", "app", "auth"]),
|
|
21
|
-
sections: z.array(z.string()),
|
|
74
|
+
type: z.string().transform(normalizeEnum(PAGE_TYPE_SYNONYMS)).pipe(z.enum(["marketing", "app", "auth"])),
|
|
75
|
+
sections: z.array(z.string()).default([]),
|
|
22
76
|
links: z.record(z.string()).optional()
|
|
23
77
|
});
|
|
24
78
|
var ArchitecturePlanSchema = z.object({
|
|
25
79
|
appName: z.string().optional(),
|
|
26
80
|
groups: z.array(RouteGroupSchema),
|
|
27
|
-
sharedComponents: z.array(PlannedComponentSchema).max(8),
|
|
28
|
-
pageNotes: z.record(z.string(), PageNoteSchema)
|
|
81
|
+
sharedComponents: z.array(PlannedComponentSchema).max(8).default([]),
|
|
82
|
+
pageNotes: z.record(z.string(), PageNoteSchema).default({})
|
|
29
83
|
});
|
|
30
84
|
function routeToKey(route) {
|
|
31
85
|
return route.replace(/^\//, "") || "home";
|
|
@@ -54,28 +108,29 @@ Rules:
|
|
|
54
108
|
|
|
55
109
|
Respond with valid JSON matching the schema.`;
|
|
56
110
|
async function generateArchitecturePlan(pages, userMessage, aiProvider, layoutHint) {
|
|
57
|
-
const userPrompt = `${
|
|
58
|
-
|
|
59
|
-
Pages: ${pages.map((p) => `${p.name} (${p.route})`).join(", ")}
|
|
111
|
+
const userPrompt = `Pages: ${pages.map((p) => `${p.name} (${p.route})`).join(", ")}
|
|
60
112
|
|
|
61
113
|
User's request: "${userMessage}"
|
|
62
114
|
|
|
63
115
|
Navigation type requested: ${layoutHint || "auto-detect"}`;
|
|
116
|
+
const warnings = [];
|
|
64
117
|
for (let attempt = 0; attempt < 2; attempt++) {
|
|
65
118
|
try {
|
|
66
|
-
const raw = await aiProvider.
|
|
119
|
+
const raw = await aiProvider.generateJSON(PLAN_SYSTEM_PROMPT, userPrompt);
|
|
67
120
|
const parsed = ArchitecturePlanSchema.safeParse(raw);
|
|
68
|
-
if (parsed.success) return parsed.data;
|
|
69
|
-
|
|
70
|
-
|
|
121
|
+
if (parsed.success) return { plan: parsed.data, warnings };
|
|
122
|
+
warnings.push(
|
|
123
|
+
`Validation (attempt ${attempt + 1}): ${parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ")}`
|
|
124
|
+
);
|
|
125
|
+
} catch (err) {
|
|
126
|
+
warnings.push(`Error (attempt ${attempt + 1}): ${err instanceof Error ? err.message : String(err)}`);
|
|
127
|
+
if (attempt === 1) return { plan: null, warnings };
|
|
71
128
|
}
|
|
72
129
|
}
|
|
73
|
-
return null;
|
|
130
|
+
return { plan: null, warnings };
|
|
74
131
|
}
|
|
75
132
|
async function updateArchitecturePlan(existingPlan, newPages, userMessage, aiProvider) {
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
Existing plan:
|
|
133
|
+
const userPrompt = `Existing plan:
|
|
79
134
|
${JSON.stringify(existingPlan, null, 2)}
|
|
80
135
|
|
|
81
136
|
New pages to integrate: ${newPages.map((p) => `${p.name} (${p.route})`).join(", ")}
|
|
@@ -84,10 +139,13 @@ User's request: "${userMessage}"
|
|
|
84
139
|
|
|
85
140
|
Update the existing plan to include these new pages. Keep all existing groups, components, and pageNotes. Add the new pages to appropriate groups and add pageNotes for them.`;
|
|
86
141
|
try {
|
|
87
|
-
const raw = await aiProvider.
|
|
142
|
+
const raw = await aiProvider.generateJSON(PLAN_SYSTEM_PROMPT, userPrompt);
|
|
88
143
|
const parsed = ArchitecturePlanSchema.safeParse(raw);
|
|
89
144
|
if (parsed.success) return parsed.data;
|
|
90
|
-
|
|
145
|
+
const issues = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
|
|
146
|
+
console.warn(chalk.dim(` Plan update validation failed: ${issues}`));
|
|
147
|
+
} catch (err) {
|
|
148
|
+
console.warn(chalk.dim(` Plan update error: ${err instanceof Error ? err.message : String(err)}`));
|
|
91
149
|
}
|
|
92
150
|
const merged = structuredClone(existingPlan);
|
|
93
151
|
const largestGroup = merged.groups.reduce(
|
|
@@ -129,6 +129,22 @@ Return ONLY the JSON object, no markdown, no code blocks, no explanations.`;
|
|
|
129
129
|
}
|
|
130
130
|
return jsonText.trim();
|
|
131
131
|
}
|
|
132
|
+
async generateJSON(systemPrompt, userPrompt) {
|
|
133
|
+
const response = await this.client.messages.create({
|
|
134
|
+
model: this.defaultModel,
|
|
135
|
+
max_tokens: 16384,
|
|
136
|
+
system: systemPrompt,
|
|
137
|
+
messages: [{ role: "user", content: userPrompt }]
|
|
138
|
+
});
|
|
139
|
+
if (response.stop_reason === "max_tokens") {
|
|
140
|
+
const err = new Error("AI response truncated (max_tokens reached)");
|
|
141
|
+
err.code = "RESPONSE_TRUNCATED";
|
|
142
|
+
throw err;
|
|
143
|
+
}
|
|
144
|
+
const content = response.content[0];
|
|
145
|
+
if (content.type !== "text") throw new Error("Unexpected response type from Claude API");
|
|
146
|
+
return JSON.parse(this.extractJSON(content.text));
|
|
147
|
+
}
|
|
132
148
|
/**
|
|
133
149
|
* Test API connection
|
|
134
150
|
*/
|
package/dist/index.js
CHANGED
|
@@ -3,8 +3,9 @@ import {
|
|
|
3
3
|
getPageGroup,
|
|
4
4
|
getPageType,
|
|
5
5
|
loadPlan,
|
|
6
|
+
routeToKey,
|
|
6
7
|
savePlan
|
|
7
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-IKHAW6OI.js";
|
|
8
9
|
import {
|
|
9
10
|
__require
|
|
10
11
|
} from "./chunk-3RG5ZIWI.js";
|
|
@@ -3839,7 +3840,7 @@ Please set ${envVar} in your environment or .env file.`);
|
|
|
3839
3840
|
}
|
|
3840
3841
|
if (preferredProvider === "openai") {
|
|
3841
3842
|
try {
|
|
3842
|
-
const { OpenAIClient } = await import("./openai-provider-
|
|
3843
|
+
const { OpenAIClient } = await import("./openai-provider-XUI7ZHUR.js");
|
|
3843
3844
|
return await OpenAIClient.create(apiKey2, config2?.model);
|
|
3844
3845
|
} catch (error) {
|
|
3845
3846
|
if (error.message?.includes("not installed")) {
|
|
@@ -3853,7 +3854,7 @@ Error: ${error.message}`
|
|
|
3853
3854
|
);
|
|
3854
3855
|
}
|
|
3855
3856
|
} else {
|
|
3856
|
-
const { ClaudeClient } = await import("./claude-
|
|
3857
|
+
const { ClaudeClient } = await import("./claude-BZ3HSBD3.js");
|
|
3857
3858
|
return ClaudeClient.create(apiKey2, config2?.model);
|
|
3858
3859
|
}
|
|
3859
3860
|
}
|
|
@@ -3866,7 +3867,7 @@ Error: ${error.message}`
|
|
|
3866
3867
|
switch (provider) {
|
|
3867
3868
|
case "openai":
|
|
3868
3869
|
try {
|
|
3869
|
-
const { OpenAIClient } = await import("./openai-provider-
|
|
3870
|
+
const { OpenAIClient } = await import("./openai-provider-XUI7ZHUR.js");
|
|
3870
3871
|
return await OpenAIClient.create(apiKey, config2?.model);
|
|
3871
3872
|
} catch (error) {
|
|
3872
3873
|
if (error.message?.includes("not installed")) {
|
|
@@ -3880,7 +3881,7 @@ Error: ${error.message}`
|
|
|
3880
3881
|
);
|
|
3881
3882
|
}
|
|
3882
3883
|
case "claude":
|
|
3883
|
-
const { ClaudeClient } = await import("./claude-
|
|
3884
|
+
const { ClaudeClient } = await import("./claude-BZ3HSBD3.js");
|
|
3884
3885
|
return ClaudeClient.create(apiKey, config2?.model);
|
|
3885
3886
|
default:
|
|
3886
3887
|
throw new Error(`Unsupported AI provider: ${provider}`);
|
|
@@ -3901,7 +3902,7 @@ var PAGE_TEMPLATES = {
|
|
|
3901
3902
|
login: {
|
|
3902
3903
|
description: "Login page with centered card form",
|
|
3903
3904
|
sections: [
|
|
3904
|
-
'Centered layout: outer div className="flex min-h-svh flex-col items-center justify-center p-6 md:p-10". Inner div className="w-full max-w-
|
|
3905
|
+
'Centered layout: outer div className="flex min-h-svh flex-col items-center justify-center p-6 md:p-10". Inner div className="w-full max-w-md".',
|
|
3905
3906
|
'Card with CardHeader: CardTitle "Sign in" (text-2xl font-bold), CardDescription "Enter your credentials to access your account" (text-sm text-muted-foreground).',
|
|
3906
3907
|
'CardContent with form: email Input (type="email", placeholder="you@example.com"), password Input (type="password"), a "Forgot password?" link (text-sm text-muted-foreground hover:text-foreground), and a Button "Sign in" (w-full).',
|
|
3907
3908
|
`CardFooter: text "Don't have an account?" with a Sign up link. All text is text-sm text-muted-foreground.`,
|
|
@@ -3922,7 +3923,7 @@ var PAGE_TEMPLATES = {
|
|
|
3922
3923
|
register: {
|
|
3923
3924
|
description: "Registration page with centered card form",
|
|
3924
3925
|
sections: [
|
|
3925
|
-
'Centered layout: outer div className="flex min-h-svh flex-col items-center justify-center p-6 md:p-10". Inner div className="w-full max-w-
|
|
3926
|
+
'Centered layout: outer div className="flex min-h-svh flex-col items-center justify-center p-6 md:p-10". Inner div className="w-full max-w-md".',
|
|
3926
3927
|
'Card with CardHeader: CardTitle "Create an account" (text-2xl font-bold), CardDescription "Enter your details to get started" (text-sm text-muted-foreground).',
|
|
3927
3928
|
'CardContent with form: name Input, email Input (type="email"), password Input (type="password"), confirm password Input (type="password"), and a Button "Create account" (w-full).',
|
|
3928
3929
|
'CardFooter: text "Already have an account?" with a Sign in link. All text is text-sm text-muted-foreground.',
|
|
@@ -4096,7 +4097,7 @@ var PAGE_TEMPLATES = {
|
|
|
4096
4097
|
"reset-password": {
|
|
4097
4098
|
description: "Reset password page with centered card form",
|
|
4098
4099
|
sections: [
|
|
4099
|
-
'Centered layout: outer div className="flex min-h-svh flex-col items-center justify-center p-6 md:p-10". Inner div className="w-full max-w-
|
|
4100
|
+
'Centered layout: outer div className="flex min-h-svh flex-col items-center justify-center p-6 md:p-10". Inner div className="w-full max-w-md".',
|
|
4100
4101
|
'Card with CardHeader: CardTitle "Reset Password" (text-xl), CardDescription.',
|
|
4101
4102
|
'CardContent with form: new password Input (type="password"), confirm password Input (type="password"), Button "Reset password" (w-full).',
|
|
4102
4103
|
'Footer text: "Remember your password?" with Sign in link.',
|
|
@@ -4205,7 +4206,7 @@ LAYOUT PATTERNS:
|
|
|
4205
4206
|
- Stats/KPI grid: grid gap-4 md:grid-cols-2 lg:grid-cols-4
|
|
4206
4207
|
- Card grid (3 col): grid gap-4 md:grid-cols-3
|
|
4207
4208
|
- Full-height page: min-h-svh (not min-h-screen)
|
|
4208
|
-
- Centered form (login/signup): flex min-h-svh flex-col items-center justify-center p-6 md:p-10 \u2192 div w-full max-w-
|
|
4209
|
+
- Centered form (login/signup): flex min-h-svh flex-col items-center justify-center p-6 md:p-10 \u2192 div w-full max-w-md
|
|
4209
4210
|
- Page content wrapper: flex flex-1 flex-col gap-4 p-4 lg:p-6
|
|
4210
4211
|
- Responsive: primary md: and lg:. Use sm:/xl: only when genuinely needed. Avoid 2xl:. NEVER arbitrary like min-[800px].
|
|
4211
4212
|
|
|
@@ -4362,6 +4363,12 @@ var DESIGN_QUALITY_APP = `
|
|
|
4362
4363
|
- Stats grid: grid gap-4 md:grid-cols-2 lg:grid-cols-4
|
|
4363
4364
|
- Content: functional, scannable, data-dense
|
|
4364
4365
|
|
|
4366
|
+
### Toolbars & Filters
|
|
4367
|
+
- Filter row: flex flex-wrap items-center gap-2
|
|
4368
|
+
- Search input: MUST use flex-1 to fill remaining horizontal space
|
|
4369
|
+
- Filters/selects: fixed width (w-[180px] or auto), do NOT flex-grow
|
|
4370
|
+
- On mobile (sm:): search full width, filters wrap to next line
|
|
4371
|
+
|
|
4365
4372
|
NEVER include marketing sections (hero, pricing, testimonials) on app pages.
|
|
4366
4373
|
`;
|
|
4367
4374
|
var DESIGN_QUALITY_AUTH = `
|
|
@@ -4369,21 +4376,29 @@ var DESIGN_QUALITY_AUTH = `
|
|
|
4369
4376
|
|
|
4370
4377
|
### Layout
|
|
4371
4378
|
- Centered card: flex min-h-svh items-center justify-center p-6 md:p-10
|
|
4372
|
-
- Card width: w-full max-w-
|
|
4379
|
+
- Card width: w-full max-w-md
|
|
4373
4380
|
- No navigation, no section containers, no sidebar
|
|
4374
4381
|
- Single focused form with clear CTA
|
|
4375
4382
|
- Card \u2192 CardHeader (title + description) \u2192 CardContent (form) \u2192 CardFooter (link to other auth page)
|
|
4376
4383
|
|
|
4377
4384
|
NEVER include navigation bars, sidebars, or multi-section layouts on auth pages.
|
|
4378
4385
|
`;
|
|
4386
|
+
var DESIGN_QUALITY_CRITICAL = `
|
|
4387
|
+
## CRITICAL CODE RULES (violations will be auto-corrected)
|
|
4388
|
+
- Every lucide-react icon MUST have className="... shrink-0" to prevent flex squishing
|
|
4389
|
+
- Button with asChild wrapping Link: the inner element MUST have className="inline-flex items-center gap-2"
|
|
4390
|
+
- NEVER use raw Tailwind colors (bg-blue-500, text-gray-600). ONLY semantic tokens: bg-primary, text-muted-foreground, etc.
|
|
4391
|
+
- <Link> and <a> MUST always have an href attribute. Never omit href.
|
|
4392
|
+
- CardTitle: NEVER add text-xl, text-2xl, text-lg. CardTitle is text-sm font-medium by default.
|
|
4393
|
+
`;
|
|
4379
4394
|
function getDesignQualityForType(type) {
|
|
4380
4395
|
switch (type) {
|
|
4381
4396
|
case "marketing":
|
|
4382
|
-
return DESIGN_QUALITY_MARKETING;
|
|
4397
|
+
return DESIGN_QUALITY_MARKETING + DESIGN_QUALITY_CRITICAL;
|
|
4383
4398
|
case "app":
|
|
4384
|
-
return DESIGN_QUALITY_APP;
|
|
4399
|
+
return DESIGN_QUALITY_APP + DESIGN_QUALITY_CRITICAL;
|
|
4385
4400
|
case "auth":
|
|
4386
|
-
return DESIGN_QUALITY_AUTH;
|
|
4401
|
+
return DESIGN_QUALITY_AUTH + DESIGN_QUALITY_CRITICAL;
|
|
4387
4402
|
}
|
|
4388
4403
|
}
|
|
4389
4404
|
function inferPageTypeFromRoute(route) {
|
|
@@ -5281,7 +5296,7 @@ pageCode rules (shadcn/ui blocks quality):
|
|
|
5281
5296
|
- Full Next.js App Router page. Imports from '@/components/ui/...' for registry components.
|
|
5282
5297
|
- Follow ALL design constraints above: text-sm base, semantic colors only, restricted spacing, weight-based hierarchy.
|
|
5283
5298
|
- Stat card pattern: Card > CardHeader(flex flex-row items-center justify-between space-y-0 pb-2) > CardTitle(text-sm font-medium) + Icon(size-4 text-muted-foreground) ; CardContent > metric(text-2xl font-bold) + change(text-xs text-muted-foreground).
|
|
5284
|
-
- Login/form pattern: outer div(flex min-h-svh flex-col items-center justify-center p-6 md:p-10) > inner div(w-full max-w-
|
|
5299
|
+
- Login/form pattern: outer div(flex min-h-svh flex-col items-center justify-center p-6 md:p-10) > inner div(w-full max-w-md) > Card with form.
|
|
5285
5300
|
- Dashboard pattern: div(space-y-6) > page header(h1 text-2xl font-bold tracking-tight + p text-sm text-muted-foreground) > stats grid(grid gap-4 md:grid-cols-2 lg:grid-cols-4) > content cards. No <main> wrapper \u2014 the layout provides it.
|
|
5286
5301
|
- No placeholders: real contextual copy only. Use the EXACT text, language, and content from the user's request.
|
|
5287
5302
|
- IMAGES: For avatar/profile photos, use https://i.pravatar.cc/150?u=<unique-seed> (e.g. ?u=sarah.johnson). For hero/product images, use https://picsum.photos/800/400?random=N. Use standard <img> tags with className, NOT Next.js <Image>. Always provide alt text.
|
|
@@ -6063,7 +6078,7 @@ function checkLines(code, pattern, type, message, severity, skipCommentsAndStrin
|
|
|
6063
6078
|
}
|
|
6064
6079
|
return issues;
|
|
6065
6080
|
}
|
|
6066
|
-
function validatePageQuality(code, validRoutes) {
|
|
6081
|
+
function validatePageQuality(code, validRoutes, pageType) {
|
|
6067
6082
|
const issues = [];
|
|
6068
6083
|
const allLines = code.split("\n");
|
|
6069
6084
|
const isTerminalContext = (lineNum) => {
|
|
@@ -6233,21 +6248,23 @@ function validatePageQuality(code, validRoutes) {
|
|
|
6233
6248
|
"warning"
|
|
6234
6249
|
)
|
|
6235
6250
|
);
|
|
6236
|
-
|
|
6237
|
-
|
|
6238
|
-
|
|
6239
|
-
|
|
6240
|
-
|
|
6241
|
-
|
|
6242
|
-
|
|
6243
|
-
|
|
6244
|
-
|
|
6245
|
-
|
|
6246
|
-
|
|
6247
|
-
|
|
6248
|
-
|
|
6249
|
-
|
|
6250
|
-
|
|
6251
|
+
if (pageType !== "auth") {
|
|
6252
|
+
const h1Matches = code.match(/<h1[\s>]/g);
|
|
6253
|
+
if (!h1Matches || h1Matches.length === 0) {
|
|
6254
|
+
issues.push({
|
|
6255
|
+
line: 0,
|
|
6256
|
+
type: "NO_H1",
|
|
6257
|
+
message: "Page has no <h1> \u2014 every page should have exactly one h1 heading",
|
|
6258
|
+
severity: "warning"
|
|
6259
|
+
});
|
|
6260
|
+
} else if (h1Matches.length > 1) {
|
|
6261
|
+
issues.push({
|
|
6262
|
+
line: 0,
|
|
6263
|
+
type: "MULTIPLE_H1",
|
|
6264
|
+
message: `Page has ${h1Matches.length} <h1> elements \u2014 use exactly one per page`,
|
|
6265
|
+
severity: "warning"
|
|
6266
|
+
});
|
|
6267
|
+
}
|
|
6251
6268
|
}
|
|
6252
6269
|
const headingLevels = [...code.matchAll(/<h([1-6])[\s>]/g)].map((m) => parseInt(m[1]));
|
|
6253
6270
|
const hasCardContext = /\bCard\b|\bCardTitle\b|\bCardHeader\b/.test(code);
|
|
@@ -6421,6 +6438,24 @@ function validatePageQuality(code, validRoutes) {
|
|
|
6421
6438
|
issues.push(...detectComponentIssues(code));
|
|
6422
6439
|
return issues;
|
|
6423
6440
|
}
|
|
6441
|
+
function resolveHref(linkText, context) {
|
|
6442
|
+
if (!context) return "/";
|
|
6443
|
+
const text = linkText.trim().toLowerCase();
|
|
6444
|
+
if (context.linkMap) {
|
|
6445
|
+
for (const [label, route] of Object.entries(context.linkMap)) {
|
|
6446
|
+
if (label.toLowerCase() === text) return route;
|
|
6447
|
+
}
|
|
6448
|
+
}
|
|
6449
|
+
if (context.knownRoutes) {
|
|
6450
|
+
const cleaned = text.replace(/^(back\s+to|go\s+to|view\s+all|see\s+all|return\s+to)\s+/i, "").trim();
|
|
6451
|
+
for (const route of context.knownRoutes) {
|
|
6452
|
+
const slug = route.split("/").filter(Boolean).pop() || "";
|
|
6453
|
+
const routeName = slug.replace(/[-_]/g, " ");
|
|
6454
|
+
if (routeName && cleaned === routeName) return route;
|
|
6455
|
+
}
|
|
6456
|
+
}
|
|
6457
|
+
return "/";
|
|
6458
|
+
}
|
|
6424
6459
|
function replaceRawColors(classes, colorMap) {
|
|
6425
6460
|
let changed = false;
|
|
6426
6461
|
let result = classes;
|
|
@@ -6513,7 +6548,7 @@ function replaceRawColors(classes, colorMap) {
|
|
|
6513
6548
|
});
|
|
6514
6549
|
return { result, changed };
|
|
6515
6550
|
}
|
|
6516
|
-
async function autoFixCode(code) {
|
|
6551
|
+
async function autoFixCode(code, context) {
|
|
6517
6552
|
const fixes = [];
|
|
6518
6553
|
let fixed = code;
|
|
6519
6554
|
const beforeQuoteFix = fixed;
|
|
@@ -6900,9 +6935,14 @@ ${selectImport}`
|
|
|
6900
6935
|
fixes.push("added inline-flex to Button asChild children (base-ui compat)");
|
|
6901
6936
|
}
|
|
6902
6937
|
const beforeLinkHrefFix = fixed;
|
|
6903
|
-
fixed = fixed.replace(/<(Link|a)\b(?![^>]*\bhref\s*=)([^>]*)>/g,
|
|
6938
|
+
fixed = fixed.replace(/<(Link|a)\b(?![^>]*\bhref\s*=)([^>]*)>([\s\S]*?)<\/\1>/g, (_match, tag, attrs, children) => {
|
|
6939
|
+
const textContent = children.replace(/<[^>]*>/g, "").trim();
|
|
6940
|
+
const href = resolveHref(textContent, context);
|
|
6941
|
+
return `<${tag} href="${href}"${attrs}>${children}</${tag}>`;
|
|
6942
|
+
});
|
|
6943
|
+
fixed = fixed.replace(/<(Link|a)\b(?![^>]*\bhref\s*=)([^>]*)\/?>/g, '<$1 href="/"$2>');
|
|
6904
6944
|
if (fixed !== beforeLinkHrefFix) {
|
|
6905
|
-
fixes.push(
|
|
6945
|
+
fixes.push("added href to <Link>/<a> missing href");
|
|
6906
6946
|
}
|
|
6907
6947
|
const { code: fixedByRules, fixes: ruleFixes } = applyComponentRules(fixed);
|
|
6908
6948
|
if (ruleFixes.length > 0) {
|
|
@@ -7084,8 +7124,16 @@ function routeToRelPath(route, isAuthOrPlan) {
|
|
|
7084
7124
|
if (isMarketingRoute(route)) return `app/${slug}/page.tsx`;
|
|
7085
7125
|
return `app/(app)/${slug}/page.tsx`;
|
|
7086
7126
|
}
|
|
7127
|
+
var AUTH_SYNONYMS = {
|
|
7128
|
+
"/register": "/signup",
|
|
7129
|
+
"/registration": "/signup",
|
|
7130
|
+
"/sign-up": "/signup",
|
|
7131
|
+
"/signin": "/login",
|
|
7132
|
+
"/sign-in": "/login"
|
|
7133
|
+
};
|
|
7087
7134
|
function deduplicatePages(pages) {
|
|
7088
|
-
const
|
|
7135
|
+
const canonicalize = (route) => AUTH_SYNONYMS[route] || route;
|
|
7136
|
+
const normalize = (route) => canonicalize(route).replace(/\/$/, "").replace(/s$/, "").replace(/ue$/, "");
|
|
7089
7137
|
const seen = /* @__PURE__ */ new Map();
|
|
7090
7138
|
return pages.filter((page, idx) => {
|
|
7091
7139
|
const norm = normalize(page.route);
|
|
@@ -7143,7 +7191,8 @@ async function warnInlineDuplicates(projectRoot, pageName, route, pageCode, mani
|
|
|
7143
7191
|
for (const t of sharedTokens) {
|
|
7144
7192
|
if (pageTokens.includes(t)) overlap++;
|
|
7145
7193
|
}
|
|
7146
|
-
|
|
7194
|
+
const overlapRatio = sharedTokens.size > 0 ? overlap / sharedTokens.size : 0;
|
|
7195
|
+
if (overlap >= 20 && overlapRatio >= 0.6) {
|
|
7147
7196
|
console.log(
|
|
7148
7197
|
chalk8.yellow(
|
|
7149
7198
|
`
|
|
@@ -7234,8 +7283,8 @@ ${currentCode}
|
|
|
7234
7283
|
|
|
7235
7284
|
// src/commands/chat/request-parser.ts
|
|
7236
7285
|
var AUTH_FLOW_PATTERNS = {
|
|
7237
|
-
"/login": ["/
|
|
7238
|
-
"/signin": ["/
|
|
7286
|
+
"/login": ["/signup", "/forgot-password"],
|
|
7287
|
+
"/signin": ["/signup", "/forgot-password"],
|
|
7239
7288
|
"/signup": ["/login"],
|
|
7240
7289
|
"/register": ["/login"],
|
|
7241
7290
|
"/forgot-password": ["/login", "/reset-password"],
|
|
@@ -7278,14 +7327,19 @@ function extractInternalLinks(code) {
|
|
|
7278
7327
|
function inferRelatedPages(plannedPages) {
|
|
7279
7328
|
const plannedRoutes = new Set(plannedPages.map((p) => p.route));
|
|
7280
7329
|
const inferred = [];
|
|
7281
|
-
|
|
7330
|
+
const queue = [...plannedPages];
|
|
7331
|
+
let i = 0;
|
|
7332
|
+
while (i < queue.length) {
|
|
7333
|
+
const { route } = queue[i++];
|
|
7282
7334
|
const authRelated = AUTH_FLOW_PATTERNS[route];
|
|
7283
7335
|
if (authRelated) {
|
|
7284
7336
|
for (const rel of authRelated) {
|
|
7285
7337
|
if (!plannedRoutes.has(rel)) {
|
|
7286
7338
|
const slug = rel.slice(1);
|
|
7287
7339
|
const name = slug.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
7288
|
-
|
|
7340
|
+
const page = { id: slug, name, route: rel };
|
|
7341
|
+
inferred.push(page);
|
|
7342
|
+
queue.push(page);
|
|
7289
7343
|
plannedRoutes.add(rel);
|
|
7290
7344
|
}
|
|
7291
7345
|
}
|
|
@@ -7295,6 +7349,7 @@ function inferRelatedPages(plannedPages) {
|
|
|
7295
7349
|
for (const rel of rule.related) {
|
|
7296
7350
|
if (!plannedRoutes.has(rel.route)) {
|
|
7297
7351
|
inferred.push(rel);
|
|
7352
|
+
queue.push(rel);
|
|
7298
7353
|
plannedRoutes.add(rel.route);
|
|
7299
7354
|
}
|
|
7300
7355
|
}
|
|
@@ -7322,7 +7377,7 @@ function extractPageNamesFromMessage(message) {
|
|
|
7322
7377
|
settings: "/settings",
|
|
7323
7378
|
account: "/account",
|
|
7324
7379
|
"personal account": "/account",
|
|
7325
|
-
registration: "/
|
|
7380
|
+
registration: "/signup",
|
|
7326
7381
|
signup: "/signup",
|
|
7327
7382
|
"sign up": "/signup",
|
|
7328
7383
|
login: "/login",
|
|
@@ -7909,7 +7964,13 @@ async function splitGeneratePages(spinner, message, modCtx, provider, parseOpts)
|
|
|
7909
7964
|
try {
|
|
7910
7965
|
const ai = await createAIProvider(provider ?? "auto");
|
|
7911
7966
|
const layoutHint = modCtx.config.navigation?.type || null;
|
|
7912
|
-
plan = await generateArchitecturePlan(
|
|
7967
|
+
const { plan: generatedPlan, warnings: planWarnings } = await generateArchitecturePlan(
|
|
7968
|
+
pageNames,
|
|
7969
|
+
message,
|
|
7970
|
+
ai,
|
|
7971
|
+
layoutHint
|
|
7972
|
+
);
|
|
7973
|
+
plan = generatedPlan;
|
|
7913
7974
|
if (plan) {
|
|
7914
7975
|
const groupsSummary = plan.groups.map((g) => `${g.id} (${g.layout}, ${g.pages.length} pages)`).join(", ");
|
|
7915
7976
|
const sharedSummary = plan.sharedComponents.length > 0 ? plan.sharedComponents.map((c) => `${c.name} \u2192 ${c.usedBy.join(", ")}`).join(" | ") : "";
|
|
@@ -7933,6 +7994,9 @@ async function splitGeneratePages(spinner, message, modCtx, provider, parseOpts)
|
|
|
7933
7994
|
} else {
|
|
7934
7995
|
spinner.warn("Phase 2/6 \u2014 Plan generation failed (continuing without plan)");
|
|
7935
7996
|
}
|
|
7997
|
+
for (const w of planWarnings) {
|
|
7998
|
+
console.log(chalk9.dim(` ${w}`));
|
|
7999
|
+
}
|
|
7936
8000
|
} catch {
|
|
7937
8001
|
spinner.warn("Phase 2/6 \u2014 Plan generation failed (continuing without plan)");
|
|
7938
8002
|
}
|
|
@@ -7992,7 +8056,7 @@ async function splitGeneratePages(spinner, message, modCtx, provider, parseOpts)
|
|
|
7992
8056
|
if (plan && plan.sharedComponents.length > 0) {
|
|
7993
8057
|
spinner.start(`Phase 4.5/6 \u2014 Generating ${plan.sharedComponents.length} shared components from plan...`);
|
|
7994
8058
|
try {
|
|
7995
|
-
const { generateSharedComponentsFromPlan } = await import("./plan-generator-
|
|
8059
|
+
const { generateSharedComponentsFromPlan } = await import("./plan-generator-R72I6RNM.js");
|
|
7996
8060
|
const generated = await generateSharedComponentsFromPlan(
|
|
7997
8061
|
plan,
|
|
7998
8062
|
styleContext,
|
|
@@ -8048,7 +8112,7 @@ async function splitGeneratePages(spinner, message, modCtx, provider, parseOpts)
|
|
|
8048
8112
|
const isAuth = isAuthRoute(route) || isAuthRoute(name);
|
|
8049
8113
|
const pageType = plan ? getPageType(route, plan) : inferPageTypeFromRoute(route);
|
|
8050
8114
|
const designConstraints = getDesignQualityForType(pageType);
|
|
8051
|
-
const authNote = isAuth ? 'For this auth page: use centered card layout with outer div className="flex min-h-svh flex-col items-center justify-center p-6 md:p-10" and inner div className="w-full max-w-
|
|
8115
|
+
const authNote = isAuth ? 'For this auth page: use centered card layout with outer div className="flex min-h-svh flex-col items-center justify-center p-6 md:p-10" and inner div className="w-full max-w-md". Do NOT use section containers or full-width wrappers. The auth layout provides centering \u2014 just output the card content.' : void 0;
|
|
8052
8116
|
const prompt = [
|
|
8053
8117
|
`Create ONE page called "${name}" at route "${route}".`,
|
|
8054
8118
|
`Context: ${message}.`,
|
|
@@ -8747,7 +8811,9 @@ function showPreview(requests, results, config2, preflightInstalledNames) {
|
|
|
8747
8811
|
const route = page.route || "/";
|
|
8748
8812
|
console.log(chalk11.white(` \u2728 ${page.name || "Page"}`));
|
|
8749
8813
|
console.log(chalk11.gray(` Route: ${route}`));
|
|
8750
|
-
|
|
8814
|
+
const configPage = config2.pages?.find((p) => p.id === page.id || p.route === (page.route || "/"));
|
|
8815
|
+
const sectionCount = configPage?.pageAnalysis?.sections?.length ?? page.sections?.length ?? 0;
|
|
8816
|
+
console.log(chalk11.gray(` Sections: ${sectionCount}`));
|
|
8751
8817
|
});
|
|
8752
8818
|
console.log("");
|
|
8753
8819
|
}
|
|
@@ -9285,7 +9351,13 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
|
|
|
9285
9351
|
isPage: true
|
|
9286
9352
|
});
|
|
9287
9353
|
let codeToWrite = fixedCode;
|
|
9288
|
-
const
|
|
9354
|
+
const currentPlan = projectRoot ? loadPlan(projectRoot) : null;
|
|
9355
|
+
const autoFixCtx = route ? {
|
|
9356
|
+
currentRoute: route,
|
|
9357
|
+
knownRoutes: dsm.getConfig().pages.map((p) => p.route).filter(Boolean),
|
|
9358
|
+
linkMap: currentPlan?.pageNotes[routeToKey(route)]?.links
|
|
9359
|
+
} : void 0;
|
|
9360
|
+
const { code: autoFixed, fixes: autoFixes } = await autoFixCode(codeToWrite, autoFixCtx);
|
|
9289
9361
|
codeToWrite = autoFixed;
|
|
9290
9362
|
const hasDashboardPage = dsm.getConfig().pages.some((p) => p.route === "/dashboard");
|
|
9291
9363
|
if (!hasDashboardPage) {
|
|
@@ -9297,7 +9369,7 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
|
|
|
9297
9369
|
}
|
|
9298
9370
|
const { code: layoutStripped, stripped } = stripInlineLayoutElements(codeToWrite);
|
|
9299
9371
|
codeToWrite = layoutStripped;
|
|
9300
|
-
const
|
|
9372
|
+
const qualityPageType = currentPlan ? getPageType(route, currentPlan) : inferPageTypeFromRoute(route);
|
|
9301
9373
|
const pageType = currentPlan ? getPageType(route, currentPlan) : isMarketingRoute(route) ? "marketing" : isAuth ? "auth" : "app";
|
|
9302
9374
|
if (pageType === "app") {
|
|
9303
9375
|
const { code: normalized, fixed: wrapperFixed } = normalizePageWrapper(codeToWrite);
|
|
@@ -9343,7 +9415,7 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
|
|
|
9343
9415
|
layoutShared: manifestForAudit.shared.filter((c) => c.type === "layout"),
|
|
9344
9416
|
allShared: manifestForAudit.shared
|
|
9345
9417
|
});
|
|
9346
|
-
let issues = validatePageQuality(codeToWrite);
|
|
9418
|
+
let issues = validatePageQuality(codeToWrite, void 0, qualityPageType);
|
|
9347
9419
|
const errors = issues.filter((i) => i.severity === "error");
|
|
9348
9420
|
if (errors.length >= 2 && aiProvider) {
|
|
9349
9421
|
console.log(
|
|
@@ -9364,17 +9436,17 @@ Rules:
|
|
|
9364
9436
|
- Keep all existing functionality and layout intact`;
|
|
9365
9437
|
const fixedCode2 = await ai.editPageCode(codeToWrite, instruction, page.name || page.id || "Page");
|
|
9366
9438
|
if (fixedCode2 && fixedCode2.length > 100 && /export\s+default/.test(fixedCode2)) {
|
|
9367
|
-
const recheck = validatePageQuality(fixedCode2);
|
|
9439
|
+
const recheck = validatePageQuality(fixedCode2, void 0, qualityPageType);
|
|
9368
9440
|
const recheckErrors = recheck.filter((i) => i.severity === "error");
|
|
9369
9441
|
if (recheckErrors.length < errors.length) {
|
|
9370
9442
|
codeToWrite = fixedCode2;
|
|
9371
|
-
const { code: reFixed, fixes: reFixes } = await autoFixCode(codeToWrite);
|
|
9443
|
+
const { code: reFixed, fixes: reFixes } = await autoFixCode(codeToWrite, autoFixCtx);
|
|
9372
9444
|
if (reFixes.length > 0) {
|
|
9373
9445
|
codeToWrite = reFixed;
|
|
9374
9446
|
postFixes.push(...reFixes);
|
|
9375
9447
|
}
|
|
9376
9448
|
await writeFile(filePath, codeToWrite);
|
|
9377
|
-
issues = validatePageQuality(codeToWrite);
|
|
9449
|
+
issues = validatePageQuality(codeToWrite, void 0, qualityPageType);
|
|
9378
9450
|
const finalErrors = issues.filter((i) => i.severity === "error").length;
|
|
9379
9451
|
console.log(chalk12.green(` \u2714 Quality fix: ${errors.length} \u2192 ${finalErrors} errors`));
|
|
9380
9452
|
}
|
|
@@ -9495,7 +9567,13 @@ ${pagesCtx}`
|
|
|
9495
9567
|
isPage: true
|
|
9496
9568
|
});
|
|
9497
9569
|
let codeToWrite = fixedCode;
|
|
9498
|
-
const
|
|
9570
|
+
const currentPlan2 = projectRoot ? loadPlan(projectRoot) : null;
|
|
9571
|
+
const autoFixCtx2 = route ? {
|
|
9572
|
+
currentRoute: route,
|
|
9573
|
+
knownRoutes: dsm.getConfig().pages.map((p) => p.route).filter(Boolean),
|
|
9574
|
+
linkMap: currentPlan2?.pageNotes[routeToKey(route)]?.links
|
|
9575
|
+
} : void 0;
|
|
9576
|
+
const { code: autoFixed, fixes: autoFixes } = await autoFixCode(codeToWrite, autoFixCtx2);
|
|
9499
9577
|
codeToWrite = autoFixed;
|
|
9500
9578
|
const hasDashboardPage = dsm.getConfig().pages.some((p) => p.route === "/dashboard");
|
|
9501
9579
|
if (!hasDashboardPage) {
|
|
@@ -9507,7 +9585,7 @@ ${pagesCtx}`
|
|
|
9507
9585
|
}
|
|
9508
9586
|
const { code: layoutStripped, stripped } = stripInlineLayoutElements(codeToWrite);
|
|
9509
9587
|
codeToWrite = layoutStripped;
|
|
9510
|
-
const
|
|
9588
|
+
const qualityPageType2 = currentPlan2 ? getPageType(route, currentPlan2) : inferPageTypeFromRoute(route);
|
|
9511
9589
|
const pageType2 = currentPlan2 ? getPageType(route, currentPlan2) : isMarketingRoute(route) ? "marketing" : isAuth ? "auth" : "app";
|
|
9512
9590
|
if (pageType2 === "app") {
|
|
9513
9591
|
const { code: normalized, fixed: wrapperFixed } = normalizePageWrapper(codeToWrite);
|
|
@@ -9553,7 +9631,7 @@ ${pagesCtx}`
|
|
|
9553
9631
|
allShared: manifestForAudit.shared,
|
|
9554
9632
|
layoutShared: manifestForAudit.shared.filter((c) => c.type === "layout")
|
|
9555
9633
|
});
|
|
9556
|
-
const issues = validatePageQuality(codeToWrite);
|
|
9634
|
+
const issues = validatePageQuality(codeToWrite, void 0, qualityPageType2);
|
|
9557
9635
|
const report = formatIssues(issues);
|
|
9558
9636
|
if (report) {
|
|
9559
9637
|
console.log(chalk12.yellow(`
|
|
@@ -9569,7 +9647,13 @@ ${pagesCtx}`
|
|
|
9569
9647
|
} else {
|
|
9570
9648
|
try {
|
|
9571
9649
|
let code = await readFile(absPath);
|
|
9572
|
-
const
|
|
9650
|
+
const currentPlanForElse = projectRoot ? loadPlan(projectRoot) : null;
|
|
9651
|
+
const autoFixCtxElse = route ? {
|
|
9652
|
+
currentRoute: route,
|
|
9653
|
+
knownRoutes: dsm.getConfig().pages.map((p) => p.route).filter(Boolean),
|
|
9654
|
+
linkMap: currentPlanForElse?.pageNotes[routeToKey(route)]?.links
|
|
9655
|
+
} : void 0;
|
|
9656
|
+
const { code: fixed, fixes } = await autoFixCode(code, autoFixCtxElse);
|
|
9573
9657
|
if (fixes.length > 0) {
|
|
9574
9658
|
code = fixed;
|
|
9575
9659
|
await writeFile(absPath, code);
|
|
@@ -9588,7 +9672,9 @@ ${pagesCtx}`
|
|
|
9588
9672
|
allShared: manifest.shared,
|
|
9589
9673
|
layoutShared: manifest.shared.filter((c) => c.type === "layout")
|
|
9590
9674
|
});
|
|
9591
|
-
const
|
|
9675
|
+
const currentPlanForQuality = currentPlanForElse;
|
|
9676
|
+
const pageTypeForQuality = currentPlanForQuality ? getPageType(route, currentPlanForQuality) : inferPageTypeFromRoute(route);
|
|
9677
|
+
const issues = validatePageQuality(code, void 0, pageTypeForQuality);
|
|
9592
9678
|
const report = formatIssues(issues);
|
|
9593
9679
|
if (report) {
|
|
9594
9680
|
console.log(chalk12.yellow(`
|
|
@@ -10380,8 +10466,16 @@ async function chatCommand(message, options) {
|
|
|
10380
10466
|
if (authRelated) authRelated.forEach((l) => allLinkedRoutes.add(l));
|
|
10381
10467
|
}
|
|
10382
10468
|
const existingRoutes = new Set(currentConfig.pages.map((p) => p.route).filter(Boolean));
|
|
10469
|
+
const expandedExisting = new Set(existingRoutes);
|
|
10470
|
+
for (const route of existingRoutes) {
|
|
10471
|
+
const canonical = AUTH_SYNONYMS[route] ?? route;
|
|
10472
|
+
expandedExisting.add(canonical);
|
|
10473
|
+
for (const [syn, can] of Object.entries(AUTH_SYNONYMS)) {
|
|
10474
|
+
if (can === canonical) expandedExisting.add(syn);
|
|
10475
|
+
}
|
|
10476
|
+
}
|
|
10383
10477
|
const missingRoutes = [...allLinkedRoutes].filter((route) => {
|
|
10384
|
-
if (
|
|
10478
|
+
if (expandedExisting.has(route)) return false;
|
|
10385
10479
|
if (existsSync16(routeToFsPath(projectRoot, route, false))) return false;
|
|
10386
10480
|
if (existsSync16(routeToFsPath(projectRoot, route, true))) return false;
|
|
10387
10481
|
return true;
|
|
@@ -10594,8 +10688,9 @@ async function chatCommand(message, options) {
|
|
|
10594
10688
|
const preflightNames = preflightInstalledIds.map((id) => updatedConfig.components.find((c) => c.id === id)?.name).filter(Boolean);
|
|
10595
10689
|
showPreview(normalizedRequests, results, updatedConfig, preflightNames);
|
|
10596
10690
|
if (scaffoldedPages.length > 0) {
|
|
10691
|
+
const uniqueScaffolded = [...new Map(scaffoldedPages.map((s) => [s.route, s])).values()];
|
|
10597
10692
|
console.log(chalk14.cyan("\u{1F517} Auto-scaffolded linked pages:"));
|
|
10598
|
-
|
|
10693
|
+
uniqueScaffolded.forEach(({ route, name }) => {
|
|
10599
10694
|
console.log(chalk14.white(` \u2728 ${name} \u2192 ${route}`));
|
|
10600
10695
|
});
|
|
10601
10696
|
console.log("");
|
|
@@ -11528,7 +11623,7 @@ async function previewCommand() {
|
|
|
11528
11623
|
}
|
|
11529
11624
|
console.log(chalk15.green("\n\u2705 Dependencies installed\n"));
|
|
11530
11625
|
} else {
|
|
11531
|
-
spinner.succeed("
|
|
11626
|
+
spinner.succeed("Dependencies installed");
|
|
11532
11627
|
}
|
|
11533
11628
|
if (needsGlobalsFix(projectRoot)) {
|
|
11534
11629
|
spinner.text = "Fixing globals.css...";
|
|
@@ -122,6 +122,26 @@ Please check your API key and try again.`
|
|
|
122
122
|
throw new Error("Unknown error occurred while parsing modification");
|
|
123
123
|
}
|
|
124
124
|
}
|
|
125
|
+
async generateJSON(systemPrompt, userPrompt) {
|
|
126
|
+
const response = await this.client.chat.completions.create({
|
|
127
|
+
model: this.defaultModel,
|
|
128
|
+
messages: [
|
|
129
|
+
{ role: "system", content: systemPrompt },
|
|
130
|
+
{ role: "user", content: userPrompt }
|
|
131
|
+
],
|
|
132
|
+
response_format: { type: "json_object" },
|
|
133
|
+
temperature: 0.3,
|
|
134
|
+
max_tokens: 16384
|
|
135
|
+
});
|
|
136
|
+
if (response.choices[0]?.finish_reason === "length") {
|
|
137
|
+
const err = new Error("AI response truncated (max_tokens reached)");
|
|
138
|
+
err.code = "RESPONSE_TRUNCATED";
|
|
139
|
+
throw err;
|
|
140
|
+
}
|
|
141
|
+
const content = response.choices[0]?.message?.content;
|
|
142
|
+
if (!content) throw new Error("Empty response from OpenAI API");
|
|
143
|
+
return JSON.parse(this.extractJSON(content));
|
|
144
|
+
}
|
|
125
145
|
/**
|
|
126
146
|
* Test API connection
|
|
127
147
|
*/
|
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.3",
|
|
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,12 +42,19 @@
|
|
|
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.3"
|
|
53
47
|
},
|
|
54
48
|
"devDependencies": {
|
|
55
49
|
"@types/node": "^20.11.0",
|
|
56
50
|
"@types/prompts": "^2.4.9",
|
|
57
51
|
"tsup": "^8.0.1",
|
|
58
52
|
"typescript": "^5.3.3"
|
|
53
|
+
},
|
|
54
|
+
"scripts": {
|
|
55
|
+
"dev": "tsup --watch",
|
|
56
|
+
"build": "tsup",
|
|
57
|
+
"typecheck": "tsc --noEmit",
|
|
58
|
+
"test": "vitest"
|
|
59
59
|
}
|
|
60
|
-
}
|
|
60
|
+
}
|