@getcoherent/cli 0.6.13 → 0.6.15

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.
@@ -0,0 +1,42 @@
1
+ import "./chunk-3RG5ZIWI.js";
2
+
3
+ // src/utils/ai-classifier.ts
4
+ import { z } from "zod";
5
+ var VALID_TYPES = ["layout", "navigation", "data-display", "form", "feedback", "section", "widget"];
6
+ function buildClassificationPrompt(components) {
7
+ const specs = components.map((c, i) => `${i + 1}. ${c.name}: ${c.signature}`).join("\n");
8
+ return `Classify these React components into one of these types: ${VALID_TYPES.join(", ")}.
9
+
10
+ ${specs}
11
+
12
+ Return JSON array: [{ "name": "...", "type": "...", "description": "one sentence" }]`;
13
+ }
14
+ var ClassificationSchema = z.array(
15
+ z.object({
16
+ name: z.string(),
17
+ type: z.string(),
18
+ description: z.string().default("")
19
+ })
20
+ );
21
+ function parseClassificationResponse(response) {
22
+ const jsonMatch = response.match(/\[[\s\S]*\]/);
23
+ if (!jsonMatch) return [];
24
+ const parsed = ClassificationSchema.safeParse(JSON.parse(jsonMatch[0]));
25
+ if (!parsed.success) return [];
26
+ return parsed.data.map((item) => ({
27
+ name: item.name,
28
+ type: VALID_TYPES.includes(item.type) ? item.type : "section",
29
+ description: item.description
30
+ }));
31
+ }
32
+ async function classifyComponents(components, aiCall) {
33
+ if (components.length === 0) return [];
34
+ const prompt = buildClassificationPrompt(components);
35
+ const response = await aiCall(prompt);
36
+ return parseClassificationResponse(response);
37
+ }
38
+ export {
39
+ buildClassificationPrompt,
40
+ classifyComponents,
41
+ parseClassificationResponse
42
+ };
@@ -0,0 +1,13 @@
1
+ import {
2
+ createAIProvider,
3
+ detectAIProvider,
4
+ getAPIKey,
5
+ hasAnyAPIKey
6
+ } from "./chunk-SPQZBQYY.js";
7
+ import "./chunk-3RG5ZIWI.js";
8
+ export {
9
+ createAIProvider,
10
+ detectAIProvider,
11
+ getAPIKey,
12
+ hasAnyAPIKey
13
+ };
@@ -1,10 +1,3 @@
1
- // src/commands/chat/plan-generator.ts
2
- import { z } from "zod";
3
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
4
- import { dirname, resolve } from "path";
5
- import { mkdir, writeFile } from "fs/promises";
6
- import chalk from "chalk";
7
-
8
1
  // src/agents/design-constraints.ts
9
2
  var DESIGN_THINKING = `
10
3
  ## DESIGN THINKING (answer internally BEFORE writing code)
@@ -1026,279 +1019,6 @@ ${RULES_CONTENT}
1026
1019
  ${RULES_CARDS_LAYOUT}
1027
1020
  ${RULES_COMPONENTS_MISC}`;
1028
1021
 
1029
- // src/commands/chat/plan-generator.ts
1030
- var LAYOUT_SYNONYMS = {
1031
- horizontal: "header",
1032
- top: "header",
1033
- nav: "header",
1034
- navbar: "header",
1035
- topbar: "header",
1036
- "top-bar": "header",
1037
- vertical: "sidebar",
1038
- left: "sidebar",
1039
- side: "sidebar",
1040
- drawer: "sidebar",
1041
- full: "both",
1042
- combined: "both",
1043
- empty: "none",
1044
- minimal: "none",
1045
- clean: "none"
1046
- };
1047
- var PAGE_TYPE_SYNONYMS = {
1048
- landing: "marketing",
1049
- public: "marketing",
1050
- home: "marketing",
1051
- website: "marketing",
1052
- static: "marketing",
1053
- application: "app",
1054
- dashboard: "app",
1055
- admin: "app",
1056
- panel: "app",
1057
- console: "app",
1058
- authentication: "auth",
1059
- login: "auth",
1060
- "log-in": "auth",
1061
- register: "auth",
1062
- signin: "auth",
1063
- "sign-in": "auth",
1064
- signup: "auth",
1065
- "sign-up": "auth"
1066
- };
1067
- var COMPONENT_TYPE_SYNONYMS = {
1068
- component: "widget",
1069
- ui: "widget",
1070
- element: "widget",
1071
- block: "widget",
1072
- "page-section": "section",
1073
- hero: "section",
1074
- feature: "section",
1075
- area: "section"
1076
- };
1077
- function normalizeEnum(synonyms) {
1078
- return (v) => {
1079
- const trimmed = v.trim().toLowerCase();
1080
- return synonyms[trimmed] ?? trimmed;
1081
- };
1082
- }
1083
- var RouteGroupSchema = z.object({
1084
- id: z.string(),
1085
- layout: z.string().transform(normalizeEnum(LAYOUT_SYNONYMS)).pipe(z.enum(["header", "sidebar", "both", "none"])),
1086
- pages: z.array(z.string())
1087
- });
1088
- var PlannedComponentSchema = z.object({
1089
- name: z.string(),
1090
- description: z.string().default(""),
1091
- props: z.string().default("{}"),
1092
- usedBy: z.array(z.string()).default([]),
1093
- type: z.string().transform(normalizeEnum(COMPONENT_TYPE_SYNONYMS)).pipe(z.enum(["section", "widget"])).catch("widget"),
1094
- shadcnDeps: z.array(z.string()).default([])
1095
- });
1096
- var PageNoteSchema = z.object({
1097
- type: z.string().transform(normalizeEnum(PAGE_TYPE_SYNONYMS)).pipe(z.enum(["marketing", "app", "auth"])),
1098
- sections: z.array(z.string()).default([]),
1099
- links: z.record(z.string()).optional()
1100
- });
1101
- var ArchitecturePlanSchema = z.object({
1102
- appName: z.string().optional(),
1103
- groups: z.array(RouteGroupSchema),
1104
- sharedComponents: z.array(PlannedComponentSchema).max(8).default([]),
1105
- pageNotes: z.record(z.string(), PageNoteSchema).default({})
1106
- });
1107
- function routeToKey(route) {
1108
- return route.replace(/^\//, "") || "home";
1109
- }
1110
- function getPageGroup(route, plan) {
1111
- return plan.groups.find((g) => g.pages.includes(route));
1112
- }
1113
- function getPageType(route, plan) {
1114
- return plan.pageNotes[routeToKey(route)]?.type ?? inferPageTypeFromRoute(route);
1115
- }
1116
- var PLAN_SYSTEM_PROMPT = `You are a UI architect. Given a list of pages for a web application, create a Component Architecture Plan as JSON.
1117
-
1118
- Your task:
1119
- 1. Group pages by navigation context (e.g., public marketing pages, authenticated app pages, auth flows)
1120
- 2. Identify reusable UI components that appear on 2+ pages
1121
- 3. Describe each page's sections and cross-page links
1122
-
1123
- Rules:
1124
- - Each group gets a layout type: "header" (horizontal nav), "sidebar" (vertical nav), "both", or "none" (no nav)
1125
- - Shared components must be genuinely reusable (appear on 2+ pages). Do NOT create a shared component for patterns used on only one page.
1126
- - Page types: "marketing" (landing, features, pricing \u2014 spacious, section-based), "app" (dashboard, settings \u2014 compact, data-dense), "auth" (login, register \u2014 centered card form)
1127
- - Component props should be a TypeScript-like interface string
1128
- - shadcnDeps lists the shadcn/ui atoms the component will need (e.g., "card", "badge", "avatar")
1129
- - Cross-page links: map link labels to target routes (e.g., {"Sign in": "/login"})
1130
- - Maximum 8 shared components
1131
-
1132
- Respond with EXACTLY this JSON structure (use these exact field names):
1133
-
1134
- {
1135
- "appName": "MyApp",
1136
- "groups": [
1137
- { "id": "public", "layout": "header", "pages": ["/", "/pricing"] },
1138
- { "id": "app", "layout": "sidebar", "pages": ["/dashboard", "/settings"] },
1139
- { "id": "auth", "layout": "none", "pages": ["/login", "/register"] }
1140
- ],
1141
- "sharedComponents": [
1142
- {
1143
- "name": "StatCard",
1144
- "description": "Displays a single metric with label and value",
1145
- "props": "{ label: string; value: string; icon?: React.ReactNode }",
1146
- "usedBy": ["/dashboard", "/projects"],
1147
- "type": "widget",
1148
- "shadcnDeps": ["card"]
1149
- }
1150
- ],
1151
- "pageNotes": {
1152
- "home": { "type": "marketing", "sections": ["Hero", "Features", "Pricing"], "links": { "Sign in": "/login" } },
1153
- "dashboard": { "type": "app", "sections": ["Stats row", "Recent tasks", "Activity feed"] },
1154
- "login": { "type": "auth", "sections": ["Login form"] }
1155
- }
1156
- }`;
1157
- async function generateArchitecturePlan(pages, userMessage, aiProvider, layoutHint) {
1158
- const userPrompt = `Pages: ${pages.map((p) => `${p.name} (${p.route})`).join(", ")}
1159
-
1160
- User's request: "${userMessage}"
1161
-
1162
- Navigation type requested: ${layoutHint || "auto-detect"}`;
1163
- const warnings = [];
1164
- for (let attempt = 0; attempt < 2; attempt++) {
1165
- try {
1166
- const raw = await aiProvider.generateJSON(PLAN_SYSTEM_PROMPT, userPrompt);
1167
- const parsed = ArchitecturePlanSchema.safeParse(raw);
1168
- if (parsed.success) return { plan: parsed.data, warnings };
1169
- warnings.push(
1170
- `Validation (attempt ${attempt + 1}): ${parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ")}`
1171
- );
1172
- } catch (err) {
1173
- warnings.push(`Error (attempt ${attempt + 1}): ${err instanceof Error ? err.message : String(err)}`);
1174
- if (attempt === 1) return { plan: null, warnings };
1175
- }
1176
- }
1177
- return { plan: null, warnings };
1178
- }
1179
- async function updateArchitecturePlan(existingPlan, newPages, userMessage, aiProvider) {
1180
- const userPrompt = `Existing plan:
1181
- ${JSON.stringify(existingPlan, null, 2)}
1182
-
1183
- New pages to integrate: ${newPages.map((p) => `${p.name} (${p.route})`).join(", ")}
1184
-
1185
- User's request: "${userMessage}"
1186
-
1187
- 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.`;
1188
- try {
1189
- const raw = await aiProvider.generateJSON(PLAN_SYSTEM_PROMPT, userPrompt);
1190
- const parsed = ArchitecturePlanSchema.safeParse(raw);
1191
- if (parsed.success) return parsed.data;
1192
- const issues = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
1193
- console.warn(chalk.dim(` Plan update validation failed: ${issues}`));
1194
- } catch (err) {
1195
- console.warn(chalk.dim(` Plan update error: ${err instanceof Error ? err.message : String(err)}`));
1196
- }
1197
- const merged = structuredClone(existingPlan);
1198
- const largestGroup = merged.groups.reduce(
1199
- (best, g) => g.pages.length > (best?.pages.length ?? 0) ? g : best,
1200
- merged.groups[0]
1201
- );
1202
- for (const page of newPages) {
1203
- const alreadyPlaced = merged.groups.some((g) => g.pages.includes(page.route));
1204
- if (!alreadyPlaced && largestGroup) {
1205
- largestGroup.pages.push(page.route);
1206
- }
1207
- const key = routeToKey(page.route);
1208
- if (!merged.pageNotes[key]) {
1209
- merged.pageNotes[key] = { type: "app", sections: [] };
1210
- }
1211
- }
1212
- return merged;
1213
- }
1214
- var cachedPlan = null;
1215
- function savePlan(projectRoot, plan) {
1216
- cachedPlan = null;
1217
- const dir = resolve(projectRoot, ".coherent");
1218
- mkdirSync(dir, { recursive: true });
1219
- writeFileSync(resolve(dir, "plan.json"), JSON.stringify(plan, null, 2));
1220
- }
1221
- function loadPlan(projectRoot) {
1222
- const planPath = resolve(projectRoot, ".coherent", "plan.json");
1223
- if (cachedPlan?.path === planPath) return cachedPlan.plan;
1224
- if (!existsSync(planPath)) return null;
1225
- try {
1226
- const raw = JSON.parse(readFileSync(planPath, "utf-8"));
1227
- const parsed = ArchitecturePlanSchema.safeParse(raw);
1228
- if (!parsed.success) return null;
1229
- cachedPlan = { path: planPath, plan: parsed.data };
1230
- return parsed.data;
1231
- } catch {
1232
- return null;
1233
- }
1234
- }
1235
- function toKebabCase(name) {
1236
- return name.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
1237
- }
1238
- async function generateSharedComponentsFromPlan(plan, styleContext, projectRoot, aiProvider) {
1239
- if (plan.sharedComponents.length === 0) return [];
1240
- const componentSpecs = plan.sharedComponents.map(
1241
- (c) => `- ${c.name}: ${c.description}. Props: ${c.props}. Type: ${c.type}. shadcn deps: ${c.shadcnDeps.join(", ") || "none"}`
1242
- ).join("\n");
1243
- const designRules = `${CORE_CONSTRAINTS}
1244
- ${getDesignQualityForType("app")}`;
1245
- const prompt = `Generate React components as separate files. For EACH component below, return an add-page request with name and pageCode fields.
1246
-
1247
- Components to generate:
1248
- ${componentSpecs}
1249
-
1250
- Style context: ${styleContext || "default"}
1251
-
1252
- ${designRules}
1253
-
1254
- Requirements:
1255
- - Each component MUST have \`export default function ComponentName\`
1256
- - Use shadcn/ui imports from @/components/ui/*
1257
- - Use Tailwind CSS classes matching the style context
1258
- - TypeScript with proper props interface
1259
- - Each component is a standalone file
1260
-
1261
- Return JSON with { requests: [{ type: "add-page", changes: { name: "ComponentName", pageCode: "..." } }, ...] }`;
1262
- const results = [];
1263
- try {
1264
- const raw = await aiProvider.parseModification(prompt);
1265
- const requests = Array.isArray(raw) ? raw : raw?.requests ?? [];
1266
- for (const comp of plan.sharedComponents) {
1267
- const match = requests.find(
1268
- (r) => r.type === "add-page" && r.changes?.name === comp.name
1269
- );
1270
- const code = match?.changes?.pageCode;
1271
- if (code && code.includes("export default")) {
1272
- const file = `components/shared/${toKebabCase(comp.name)}.tsx`;
1273
- results.push({ name: comp.name, code, file });
1274
- }
1275
- }
1276
- } catch {
1277
- for (const comp of plan.sharedComponents) {
1278
- try {
1279
- const singlePrompt = `Generate a React component: ${comp.name} \u2014 ${comp.description}. Props: ${comp.props}. shadcn deps: ${comp.shadcnDeps.join(", ") || "none"}. Style: ${styleContext || "default"}. Return { requests: [{ type: "add-page", changes: { name: "${comp.name}", pageCode: "..." } }] }`;
1280
- const raw = await aiProvider.parseModification(singlePrompt);
1281
- const requests = Array.isArray(raw) ? raw : raw?.requests ?? [];
1282
- const match = requests.find(
1283
- (r) => r.type === "add-page" && r.changes?.name === comp.name
1284
- );
1285
- const code = match?.changes?.pageCode;
1286
- if (code && code.includes("export default")) {
1287
- const file = `components/shared/${toKebabCase(comp.name)}.tsx`;
1288
- results.push({ name: comp.name, code, file });
1289
- }
1290
- } catch {
1291
- }
1292
- }
1293
- }
1294
- for (const comp of results) {
1295
- const fullPath = resolve(projectRoot, comp.file);
1296
- await mkdir(dirname(fullPath), { recursive: true });
1297
- await writeFile(fullPath, comp.code, "utf-8");
1298
- }
1299
- return results;
1300
- }
1301
-
1302
1022
  export {
1303
1023
  DESIGN_THINKING,
1304
1024
  CORE_CONSTRAINTS,
@@ -1307,18 +1027,16 @@ export {
1307
1027
  inferPageTypeFromRoute,
1308
1028
  DESIGN_QUALITY,
1309
1029
  VISUAL_DEPTH,
1030
+ RULES_FORMS,
1031
+ RULES_DATA_DISPLAY,
1032
+ RULES_NAVIGATION,
1033
+ RULES_OVERLAYS,
1034
+ RULES_FEEDBACK,
1035
+ RULES_CONTENT,
1036
+ RULES_CARDS_LAYOUT,
1037
+ RULES_SHADCN_APIS,
1038
+ RULES_COMPONENTS_MISC,
1310
1039
  INTERACTION_PATTERNS,
1311
1040
  selectContextualRules,
1312
- RouteGroupSchema,
1313
- PlannedComponentSchema,
1314
- PageNoteSchema,
1315
- ArchitecturePlanSchema,
1316
- routeToKey,
1317
- getPageGroup,
1318
- getPageType,
1319
- generateArchitecturePlan,
1320
- updateArchitecturePlan,
1321
- savePlan,
1322
- loadPlan,
1323
- generateSharedComponentsFromPlan
1041
+ DESIGN_CONSTRAINTS
1324
1042
  };