@getcoherent/cli 0.6.0 → 0.6.1

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.
@@ -54,16 +54,14 @@ Rules:
54
54
 
55
55
  Respond with valid JSON matching the schema.`;
56
56
  async function generateArchitecturePlan(pages, userMessage, aiProvider, layoutHint) {
57
- const userPrompt = `${PLAN_SYSTEM_PROMPT}
58
-
59
- Pages: ${pages.map((p) => `${p.name} (${p.route})`).join(", ")}
57
+ const userPrompt = `Pages: ${pages.map((p) => `${p.name} (${p.route})`).join(", ")}
60
58
 
61
59
  User's request: "${userMessage}"
62
60
 
63
61
  Navigation type requested: ${layoutHint || "auto-detect"}`;
64
62
  for (let attempt = 0; attempt < 2; attempt++) {
65
63
  try {
66
- const raw = await aiProvider.parseModification(userPrompt);
64
+ const raw = await aiProvider.generateJSON(PLAN_SYSTEM_PROMPT, userPrompt);
67
65
  const parsed = ArchitecturePlanSchema.safeParse(raw);
68
66
  if (parsed.success) return parsed.data;
69
67
  } catch {
@@ -73,9 +71,7 @@ Navigation type requested: ${layoutHint || "auto-detect"}`;
73
71
  return null;
74
72
  }
75
73
  async function updateArchitecturePlan(existingPlan, newPages, userMessage, aiProvider) {
76
- const prompt = `${PLAN_SYSTEM_PROMPT}
77
-
78
- Existing plan:
74
+ const userPrompt = `Existing plan:
79
75
  ${JSON.stringify(existingPlan, null, 2)}
80
76
 
81
77
  New pages to integrate: ${newPages.map((p) => `${p.name} (${p.route})`).join(", ")}
@@ -84,7 +80,7 @@ User's request: "${userMessage}"
84
80
 
85
81
  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
82
  try {
87
- const raw = await aiProvider.parseModification(prompt);
83
+ const raw = await aiProvider.generateJSON(PLAN_SYSTEM_PROMPT, userPrompt);
88
84
  const parsed = ArchitecturePlanSchema.safeParse(raw);
89
85
  if (parsed.success) return parsed.data;
90
86
  } catch {
@@ -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-4M2RBSYF.js";
8
+ } from "./chunk-INW3BQSX.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-FSXSVEYD.js");
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-RFHVT7RC.js");
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-FSXSVEYD.js");
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-RFHVT7RC.js");
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-sm".',
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-sm".',
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-sm".',
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-sm
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-sm
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-sm) > Card with form.
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
- const h1Matches = code.match(/<h1[\s>]/g);
6237
- if (!h1Matches || h1Matches.length === 0) {
6238
- issues.push({
6239
- line: 0,
6240
- type: "NO_H1",
6241
- message: "Page has no <h1> \u2014 every page should have exactly one h1 heading",
6242
- severity: "warning"
6243
- });
6244
- } else if (h1Matches.length > 1) {
6245
- issues.push({
6246
- line: 0,
6247
- type: "MULTIPLE_H1",
6248
- message: `Page has ${h1Matches.length} <h1> elements \u2014 use exactly one per page`,
6249
- severity: "warning"
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, '<$1 href="/"$2>');
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('added href="/" to <Link>/<a> missing href');
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 normalize = (route) => route.replace(/\/$/, "").replace(/s$/, "").replace(/ue$/, "");
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);
@@ -7234,8 +7282,8 @@ ${currentCode}
7234
7282
 
7235
7283
  // src/commands/chat/request-parser.ts
7236
7284
  var AUTH_FLOW_PATTERNS = {
7237
- "/login": ["/register", "/forgot-password"],
7238
- "/signin": ["/register", "/forgot-password"],
7285
+ "/login": ["/signup", "/forgot-password"],
7286
+ "/signin": ["/signup", "/forgot-password"],
7239
7287
  "/signup": ["/login"],
7240
7288
  "/register": ["/login"],
7241
7289
  "/forgot-password": ["/login", "/reset-password"],
@@ -7278,14 +7326,19 @@ function extractInternalLinks(code) {
7278
7326
  function inferRelatedPages(plannedPages) {
7279
7327
  const plannedRoutes = new Set(plannedPages.map((p) => p.route));
7280
7328
  const inferred = [];
7281
- for (const { route } of plannedPages) {
7329
+ const queue = [...plannedPages];
7330
+ let i = 0;
7331
+ while (i < queue.length) {
7332
+ const { route } = queue[i++];
7282
7333
  const authRelated = AUTH_FLOW_PATTERNS[route];
7283
7334
  if (authRelated) {
7284
7335
  for (const rel of authRelated) {
7285
7336
  if (!plannedRoutes.has(rel)) {
7286
7337
  const slug = rel.slice(1);
7287
7338
  const name = slug.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
7288
- inferred.push({ id: slug, name, route: rel });
7339
+ const page = { id: slug, name, route: rel };
7340
+ inferred.push(page);
7341
+ queue.push(page);
7289
7342
  plannedRoutes.add(rel);
7290
7343
  }
7291
7344
  }
@@ -7295,6 +7348,7 @@ function inferRelatedPages(plannedPages) {
7295
7348
  for (const rel of rule.related) {
7296
7349
  if (!plannedRoutes.has(rel.route)) {
7297
7350
  inferred.push(rel);
7351
+ queue.push(rel);
7298
7352
  plannedRoutes.add(rel.route);
7299
7353
  }
7300
7354
  }
@@ -7322,7 +7376,7 @@ function extractPageNamesFromMessage(message) {
7322
7376
  settings: "/settings",
7323
7377
  account: "/account",
7324
7378
  "personal account": "/account",
7325
- registration: "/registration",
7379
+ registration: "/signup",
7326
7380
  signup: "/signup",
7327
7381
  "sign up": "/signup",
7328
7382
  login: "/login",
@@ -7992,7 +8046,7 @@ async function splitGeneratePages(spinner, message, modCtx, provider, parseOpts)
7992
8046
  if (plan && plan.sharedComponents.length > 0) {
7993
8047
  spinner.start(`Phase 4.5/6 \u2014 Generating ${plan.sharedComponents.length} shared components from plan...`);
7994
8048
  try {
7995
- const { generateSharedComponentsFromPlan } = await import("./plan-generator-XKMZTEGK.js");
8049
+ const { generateSharedComponentsFromPlan } = await import("./plan-generator-IS3YDZUW.js");
7996
8050
  const generated = await generateSharedComponentsFromPlan(
7997
8051
  plan,
7998
8052
  styleContext,
@@ -8048,7 +8102,7 @@ async function splitGeneratePages(spinner, message, modCtx, provider, parseOpts)
8048
8102
  const isAuth = isAuthRoute(route) || isAuthRoute(name);
8049
8103
  const pageType = plan ? getPageType(route, plan) : inferPageTypeFromRoute(route);
8050
8104
  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-sm". Do NOT use section containers or full-width wrappers. The auth layout provides centering \u2014 just output the card content.' : void 0;
8105
+ 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
8106
  const prompt = [
8053
8107
  `Create ONE page called "${name}" at route "${route}".`,
8054
8108
  `Context: ${message}.`,
@@ -8747,7 +8801,9 @@ function showPreview(requests, results, config2, preflightInstalledNames) {
8747
8801
  const route = page.route || "/";
8748
8802
  console.log(chalk11.white(` \u2728 ${page.name || "Page"}`));
8749
8803
  console.log(chalk11.gray(` Route: ${route}`));
8750
- console.log(chalk11.gray(` Sections: ${page.sections?.length ?? 0}`));
8804
+ const configPage = config2.pages?.find((p) => p.id === page.id || p.route === (page.route || "/"));
8805
+ const sectionCount = configPage?.pageAnalysis?.sections?.length ?? page.sections?.length ?? 0;
8806
+ console.log(chalk11.gray(` Sections: ${sectionCount}`));
8751
8807
  });
8752
8808
  console.log("");
8753
8809
  }
@@ -9285,7 +9341,13 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
9285
9341
  isPage: true
9286
9342
  });
9287
9343
  let codeToWrite = fixedCode;
9288
- const { code: autoFixed, fixes: autoFixes } = await autoFixCode(codeToWrite);
9344
+ const currentPlan = projectRoot ? loadPlan(projectRoot) : null;
9345
+ const autoFixCtx = route ? {
9346
+ currentRoute: route,
9347
+ knownRoutes: dsm.getConfig().pages.map((p) => p.route).filter(Boolean),
9348
+ linkMap: currentPlan?.pageNotes[routeToKey(route)]?.links
9349
+ } : void 0;
9350
+ const { code: autoFixed, fixes: autoFixes } = await autoFixCode(codeToWrite, autoFixCtx);
9289
9351
  codeToWrite = autoFixed;
9290
9352
  const hasDashboardPage = dsm.getConfig().pages.some((p) => p.route === "/dashboard");
9291
9353
  if (!hasDashboardPage) {
@@ -9297,7 +9359,7 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
9297
9359
  }
9298
9360
  const { code: layoutStripped, stripped } = stripInlineLayoutElements(codeToWrite);
9299
9361
  codeToWrite = layoutStripped;
9300
- const currentPlan = projectRoot ? loadPlan(projectRoot) : null;
9362
+ const qualityPageType = currentPlan ? getPageType(route, currentPlan) : inferPageTypeFromRoute(route);
9301
9363
  const pageType = currentPlan ? getPageType(route, currentPlan) : isMarketingRoute(route) ? "marketing" : isAuth ? "auth" : "app";
9302
9364
  if (pageType === "app") {
9303
9365
  const { code: normalized, fixed: wrapperFixed } = normalizePageWrapper(codeToWrite);
@@ -9343,7 +9405,7 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
9343
9405
  layoutShared: manifestForAudit.shared.filter((c) => c.type === "layout"),
9344
9406
  allShared: manifestForAudit.shared
9345
9407
  });
9346
- let issues = validatePageQuality(codeToWrite);
9408
+ let issues = validatePageQuality(codeToWrite, void 0, qualityPageType);
9347
9409
  const errors = issues.filter((i) => i.severity === "error");
9348
9410
  if (errors.length >= 2 && aiProvider) {
9349
9411
  console.log(
@@ -9364,17 +9426,17 @@ Rules:
9364
9426
  - Keep all existing functionality and layout intact`;
9365
9427
  const fixedCode2 = await ai.editPageCode(codeToWrite, instruction, page.name || page.id || "Page");
9366
9428
  if (fixedCode2 && fixedCode2.length > 100 && /export\s+default/.test(fixedCode2)) {
9367
- const recheck = validatePageQuality(fixedCode2);
9429
+ const recheck = validatePageQuality(fixedCode2, void 0, qualityPageType);
9368
9430
  const recheckErrors = recheck.filter((i) => i.severity === "error");
9369
9431
  if (recheckErrors.length < errors.length) {
9370
9432
  codeToWrite = fixedCode2;
9371
- const { code: reFixed, fixes: reFixes } = await autoFixCode(codeToWrite);
9433
+ const { code: reFixed, fixes: reFixes } = await autoFixCode(codeToWrite, autoFixCtx);
9372
9434
  if (reFixes.length > 0) {
9373
9435
  codeToWrite = reFixed;
9374
9436
  postFixes.push(...reFixes);
9375
9437
  }
9376
9438
  await writeFile(filePath, codeToWrite);
9377
- issues = validatePageQuality(codeToWrite);
9439
+ issues = validatePageQuality(codeToWrite, void 0, qualityPageType);
9378
9440
  const finalErrors = issues.filter((i) => i.severity === "error").length;
9379
9441
  console.log(chalk12.green(` \u2714 Quality fix: ${errors.length} \u2192 ${finalErrors} errors`));
9380
9442
  }
@@ -9495,7 +9557,13 @@ ${pagesCtx}`
9495
9557
  isPage: true
9496
9558
  });
9497
9559
  let codeToWrite = fixedCode;
9498
- const { code: autoFixed, fixes: autoFixes } = await autoFixCode(codeToWrite);
9560
+ const currentPlan2 = projectRoot ? loadPlan(projectRoot) : null;
9561
+ const autoFixCtx2 = route ? {
9562
+ currentRoute: route,
9563
+ knownRoutes: dsm.getConfig().pages.map((p) => p.route).filter(Boolean),
9564
+ linkMap: currentPlan2?.pageNotes[routeToKey(route)]?.links
9565
+ } : void 0;
9566
+ const { code: autoFixed, fixes: autoFixes } = await autoFixCode(codeToWrite, autoFixCtx2);
9499
9567
  codeToWrite = autoFixed;
9500
9568
  const hasDashboardPage = dsm.getConfig().pages.some((p) => p.route === "/dashboard");
9501
9569
  if (!hasDashboardPage) {
@@ -9507,7 +9575,7 @@ ${pagesCtx}`
9507
9575
  }
9508
9576
  const { code: layoutStripped, stripped } = stripInlineLayoutElements(codeToWrite);
9509
9577
  codeToWrite = layoutStripped;
9510
- const currentPlan2 = projectRoot ? loadPlan(projectRoot) : null;
9578
+ const qualityPageType2 = currentPlan2 ? getPageType(route, currentPlan2) : inferPageTypeFromRoute(route);
9511
9579
  const pageType2 = currentPlan2 ? getPageType(route, currentPlan2) : isMarketingRoute(route) ? "marketing" : isAuth ? "auth" : "app";
9512
9580
  if (pageType2 === "app") {
9513
9581
  const { code: normalized, fixed: wrapperFixed } = normalizePageWrapper(codeToWrite);
@@ -9553,7 +9621,7 @@ ${pagesCtx}`
9553
9621
  allShared: manifestForAudit.shared,
9554
9622
  layoutShared: manifestForAudit.shared.filter((c) => c.type === "layout")
9555
9623
  });
9556
- const issues = validatePageQuality(codeToWrite);
9624
+ const issues = validatePageQuality(codeToWrite, void 0, qualityPageType2);
9557
9625
  const report = formatIssues(issues);
9558
9626
  if (report) {
9559
9627
  console.log(chalk12.yellow(`
@@ -9569,7 +9637,13 @@ ${pagesCtx}`
9569
9637
  } else {
9570
9638
  try {
9571
9639
  let code = await readFile(absPath);
9572
- const { code: fixed, fixes } = await autoFixCode(code);
9640
+ const currentPlanForElse = projectRoot ? loadPlan(projectRoot) : null;
9641
+ const autoFixCtxElse = route ? {
9642
+ currentRoute: route,
9643
+ knownRoutes: dsm.getConfig().pages.map((p) => p.route).filter(Boolean),
9644
+ linkMap: currentPlanForElse?.pageNotes[routeToKey(route)]?.links
9645
+ } : void 0;
9646
+ const { code: fixed, fixes } = await autoFixCode(code, autoFixCtxElse);
9573
9647
  if (fixes.length > 0) {
9574
9648
  code = fixed;
9575
9649
  await writeFile(absPath, code);
@@ -9588,7 +9662,9 @@ ${pagesCtx}`
9588
9662
  allShared: manifest.shared,
9589
9663
  layoutShared: manifest.shared.filter((c) => c.type === "layout")
9590
9664
  });
9591
- const issues = validatePageQuality(code);
9665
+ const currentPlanForQuality = currentPlanForElse;
9666
+ const pageTypeForQuality = currentPlanForQuality ? getPageType(route, currentPlanForQuality) : inferPageTypeFromRoute(route);
9667
+ const issues = validatePageQuality(code, void 0, pageTypeForQuality);
9592
9668
  const report = formatIssues(issues);
9593
9669
  if (report) {
9594
9670
  console.log(chalk12.yellow(`
@@ -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
  */
@@ -11,7 +11,7 @@ import {
11
11
  routeToKey,
12
12
  savePlan,
13
13
  updateArchitecturePlan
14
- } from "./chunk-4M2RBSYF.js";
14
+ } from "./chunk-INW3BQSX.js";
15
15
  import "./chunk-3RG5ZIWI.js";
16
16
  export {
17
17
  ArchitecturePlanSchema,
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.6.0",
6
+ "version": "0.6.1",
7
7
  "description": "CLI interface for Coherent Design Method",
8
8
  "type": "module",
9
9
  "main": "./dist/index.js",