@getcoherent/cli 0.6.14 → 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.
- package/dist/{ai-provider-HUQO64P3.js → ai-provider-CGSIYFZT.js} +1 -1
- package/dist/{chunk-WRDWFCQJ.js → chunk-PVJJ2YXP.js} +7 -5
- package/dist/{chunk-25JRF5MA.js → chunk-SPQZBQYY.js} +4 -4
- package/dist/{claude-BZ3HSBD3.js → claude-K5B5ITAT.js} +1 -1
- package/dist/index.js +494 -81
- package/dist/{openai-provider-XUI7ZHUR.js → openai-provider-4KGARVC3.js} +1 -1
- package/dist/{plan-generator-H55WEIY2.js → plan-generator-ITHYNYJI.js} +1 -1
- package/package.json +10 -10
- package/LICENSE +0 -21
|
@@ -253,7 +253,7 @@ Style context: ${styleContext || "default"}
|
|
|
253
253
|
${designRules}
|
|
254
254
|
|
|
255
255
|
Requirements:
|
|
256
|
-
- Each component MUST
|
|
256
|
+
- Each component MUST use a NAMED export: \`export function ComponentName\` (NOT export default)
|
|
257
257
|
- Use shadcn/ui imports from @/components/ui/*
|
|
258
258
|
- Use Tailwind CSS classes matching the style context
|
|
259
259
|
- TypeScript with proper props interface
|
|
@@ -269,9 +269,10 @@ Return JSON with { requests: [{ type: "add-page", changes: { name: "ComponentNam
|
|
|
269
269
|
(r) => r.type === "add-page" && r.changes?.name === comp.name
|
|
270
270
|
);
|
|
271
271
|
const code = match?.changes?.pageCode;
|
|
272
|
-
if (code && code.includes("export default")) {
|
|
272
|
+
if (code && (code.includes("export function") || code.includes("export default"))) {
|
|
273
|
+
const fixedCode = code.replace(/export default function (\w+)/g, "export function $1");
|
|
273
274
|
const file = `components/shared/${toKebabCase(comp.name)}.tsx`;
|
|
274
|
-
results.push({ name: comp.name, code, file });
|
|
275
|
+
results.push({ name: comp.name, code: fixedCode, file });
|
|
275
276
|
}
|
|
276
277
|
}
|
|
277
278
|
} catch {
|
|
@@ -284,9 +285,10 @@ Return JSON with { requests: [{ type: "add-page", changes: { name: "ComponentNam
|
|
|
284
285
|
(r) => r.type === "add-page" && r.changes?.name === comp.name
|
|
285
286
|
);
|
|
286
287
|
const code = match?.changes?.pageCode;
|
|
287
|
-
if (code && code.includes("export default")) {
|
|
288
|
+
if (code && (code.includes("export function") || code.includes("export default"))) {
|
|
289
|
+
const fixedCode = code.replace(/export default function (\w+)/g, "export function $1");
|
|
288
290
|
const file = `components/shared/${toKebabCase(comp.name)}.tsx`;
|
|
289
|
-
results.push({ name: comp.name, code, file });
|
|
291
|
+
results.push({ name: comp.name, code: fixedCode, file });
|
|
290
292
|
}
|
|
291
293
|
} catch {
|
|
292
294
|
}
|
|
@@ -68,7 +68,7 @@ Please set ${envVar} in your environment or .env file.`);
|
|
|
68
68
|
}
|
|
69
69
|
if (preferredProvider === "openai") {
|
|
70
70
|
try {
|
|
71
|
-
const { OpenAIClient } = await import("./openai-provider-
|
|
71
|
+
const { OpenAIClient } = await import("./openai-provider-4KGARVC3.js");
|
|
72
72
|
return await OpenAIClient.create(apiKey2, config?.model);
|
|
73
73
|
} catch (error) {
|
|
74
74
|
if (error.message?.includes("not installed")) {
|
|
@@ -82,7 +82,7 @@ Error: ${error.message}`
|
|
|
82
82
|
);
|
|
83
83
|
}
|
|
84
84
|
} else {
|
|
85
|
-
const { ClaudeClient } = await import("./claude-
|
|
85
|
+
const { ClaudeClient } = await import("./claude-K5B5ITAT.js");
|
|
86
86
|
return ClaudeClient.create(apiKey2, config?.model);
|
|
87
87
|
}
|
|
88
88
|
}
|
|
@@ -95,7 +95,7 @@ Error: ${error.message}`
|
|
|
95
95
|
switch (provider) {
|
|
96
96
|
case "openai":
|
|
97
97
|
try {
|
|
98
|
-
const { OpenAIClient } = await import("./openai-provider-
|
|
98
|
+
const { OpenAIClient } = await import("./openai-provider-4KGARVC3.js");
|
|
99
99
|
return await OpenAIClient.create(apiKey, config?.model);
|
|
100
100
|
} catch (error) {
|
|
101
101
|
if (error.message?.includes("not installed")) {
|
|
@@ -109,7 +109,7 @@ Error: ${error.message}`
|
|
|
109
109
|
);
|
|
110
110
|
}
|
|
111
111
|
case "claude":
|
|
112
|
-
const { ClaudeClient } = await import("./claude-
|
|
112
|
+
const { ClaudeClient } = await import("./claude-K5B5ITAT.js");
|
|
113
113
|
return ClaudeClient.create(apiKey, config?.model);
|
|
114
114
|
default:
|
|
115
115
|
throw new Error(`Unsupported AI provider: ${provider}`);
|
|
@@ -399,7 +399,7 @@ Rules:
|
|
|
399
399
|
- Do NOT use these names (already shared): ${existingSharedNames.join(", ")}
|
|
400
400
|
- Look for: cards with icon+title+description, pricing tiers, testimonial blocks, stat displays, CTA sections
|
|
401
401
|
|
|
402
|
-
Each component object: "name" (PascalCase), "type" ("section"|"widget"), "description", "propsInterface", "code" (full TSX module as string)
|
|
402
|
+
Each component object: "name" (PascalCase), "type" ("layout"|"navigation"|"data-display"|"form"|"feedback"|"section"|"widget"), "description", "propsInterface", "code" (full TSX module as string)
|
|
403
403
|
|
|
404
404
|
If no repeating patterns found: { "components": [] }`
|
|
405
405
|
}
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createAIProvider
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-SPQZBQYY.js";
|
|
4
4
|
import {
|
|
5
5
|
autoFixCode,
|
|
6
6
|
checkDesignConsistency,
|
|
@@ -14,8 +14,9 @@ import {
|
|
|
14
14
|
getPageType,
|
|
15
15
|
loadPlan,
|
|
16
16
|
routeToKey,
|
|
17
|
-
savePlan
|
|
18
|
-
|
|
17
|
+
savePlan,
|
|
18
|
+
updateArchitecturePlan
|
|
19
|
+
} from "./chunk-PVJJ2YXP.js";
|
|
19
20
|
import {
|
|
20
21
|
CORE_CONSTRAINTS,
|
|
21
22
|
DESIGN_QUALITY,
|
|
@@ -2464,7 +2465,7 @@ import { loadManifest } from "@getcoherent/core";
|
|
|
2464
2465
|
import { DesignSystemManager } from "@getcoherent/core";
|
|
2465
2466
|
function buildSharedComponentsListForClaude(manifest) {
|
|
2466
2467
|
if (!manifest.shared || manifest.shared.length === 0) {
|
|
2467
|
-
return "No shared components yet. Register with: coherent components shared add <name> --type layout|section|widget";
|
|
2468
|
+
return "No shared components yet. Register with: coherent components shared add <name> --type layout|navigation|data-display|form|feedback|section|widget";
|
|
2468
2469
|
}
|
|
2469
2470
|
const order = {
|
|
2470
2471
|
layout: 0,
|
|
@@ -2959,7 +2960,7 @@ function buildSharedComponentsList(manifest) {
|
|
|
2959
2960
|
if (!manifest.shared || manifest.shared.length === 0) {
|
|
2960
2961
|
return `No shared components registered yet.
|
|
2961
2962
|
When you create reusable blocks (headers, footers, repeated sections),
|
|
2962
|
-
register them: coherent components shared add <Name> --type layout|section|widget`;
|
|
2963
|
+
register them: coherent components shared add <Name> --type layout|navigation|data-display|form|feedback|section|widget`;
|
|
2963
2964
|
}
|
|
2964
2965
|
const typeOrder = {
|
|
2965
2966
|
layout: 0,
|
|
@@ -3857,7 +3858,7 @@ import {
|
|
|
3857
3858
|
CLI_VERSION as CLI_VERSION2,
|
|
3858
3859
|
getTemplateForPageType as getTemplateForPageType2,
|
|
3859
3860
|
loadManifest as loadManifest8,
|
|
3860
|
-
saveManifest as
|
|
3861
|
+
saveManifest as saveManifest3,
|
|
3861
3862
|
updateEntry
|
|
3862
3863
|
} from "@getcoherent/core";
|
|
3863
3864
|
|
|
@@ -4101,7 +4102,7 @@ function detectPageType(pageName) {
|
|
|
4101
4102
|
const normalized = pageName.toLowerCase();
|
|
4102
4103
|
if (/dashboard|admin|overview/.test(normalized)) return "dashboard";
|
|
4103
4104
|
if (/login|signin|sign-in/.test(normalized)) return "login";
|
|
4104
|
-
if (/
|
|
4105
|
+
if (/regist(?:er|ration)|signup|sign.?up/.test(normalized)) return "register";
|
|
4105
4106
|
if (/pricing|plans|subscription/.test(normalized)) return "pricing";
|
|
4106
4107
|
if (/about|company/.test(normalized)) return "about";
|
|
4107
4108
|
if (/contact|support|help/.test(normalized)) return "contact";
|
|
@@ -4171,7 +4172,8 @@ async function parseModification(message, context, provider = "auto", options) {
|
|
|
4171
4172
|
const prompt = buildModificationPrompt(enhancedMessage, context.config, componentRegistry, {
|
|
4172
4173
|
isExpandedPageRequest,
|
|
4173
4174
|
sharedComponentsSummary: options?.sharedComponentsSummary,
|
|
4174
|
-
tieredComponentsPrompt: options?.tieredComponentsPrompt
|
|
4175
|
+
tieredComponentsPrompt: options?.tieredComponentsPrompt,
|
|
4176
|
+
reusePlanDirective: options?.reusePlanDirective
|
|
4175
4177
|
});
|
|
4176
4178
|
const raw = await ai.parseModification(prompt);
|
|
4177
4179
|
const requestsArray = Array.isArray(raw) ? raw : raw?.requests ?? [];
|
|
@@ -4237,7 +4239,14 @@ Rules:
|
|
|
4237
4239
|
function buildModificationPrompt(message, config2, componentRegistry, options) {
|
|
4238
4240
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4239
4241
|
const expandedHint = options?.isExpandedPageRequest === true ? "\nIMPORTANT: The user request has been expanded with best practices. Use ALL the details provided when generating sections and content.\n\n" : "";
|
|
4240
|
-
const sharedSection = options?.
|
|
4242
|
+
const sharedSection = options?.reusePlanDirective ? `
|
|
4243
|
+
|
|
4244
|
+
## COMPONENT REUSE DIRECTIVE
|
|
4245
|
+
|
|
4246
|
+
${options.reusePlanDirective}
|
|
4247
|
+
|
|
4248
|
+
For editing an existing shared component use type "modify-layout-block" with target "CID-XXX" or name.
|
|
4249
|
+
` : options?.tieredComponentsPrompt ? `
|
|
4241
4250
|
|
|
4242
4251
|
## SHARED COMPONENTS (MANDATORY REUSE)
|
|
4243
4252
|
|
|
@@ -4520,9 +4529,9 @@ Return valid JSON only, no markdown code fence. Use this shape:
|
|
|
4520
4529
|
{ "requests": [ ... array of ModificationRequest ... ], "uxRecommendations": "optional markdown or omit key" }
|
|
4521
4530
|
Legacy: returning only a JSON array of requests is still accepted.`;
|
|
4522
4531
|
}
|
|
4523
|
-
function buildLightweightPagePrompt(pageName, route, styleContext, sharedComponentsSummary, pageType, tieredComponentsPrompt) {
|
|
4532
|
+
function buildLightweightPagePrompt(pageName, route, styleContext, sharedComponentsSummary, pageType, tieredComponentsPrompt, reusePlanDirective) {
|
|
4524
4533
|
const designConstraints = pageType ? getDesignQualityForType(pageType) : "";
|
|
4525
|
-
const sharedNote = tieredComponentsPrompt || (sharedComponentsSummary ? `Available shared components:
|
|
4534
|
+
const sharedNote = reusePlanDirective || tieredComponentsPrompt || (sharedComponentsSummary ? `Available shared components:
|
|
4526
4535
|
${sharedComponentsSummary}` : "");
|
|
4527
4536
|
return [
|
|
4528
4537
|
`Generate complete pageCode for a page called "${pageName}" at route "${route}".`,
|
|
@@ -5050,10 +5059,10 @@ function extractComponentIdsFromCode(code) {
|
|
|
5050
5059
|
return ids;
|
|
5051
5060
|
}
|
|
5052
5061
|
async function warnInlineDuplicates(projectRoot, pageName, route, pageCode, manifest, plan) {
|
|
5053
|
-
const
|
|
5054
|
-
if (
|
|
5062
|
+
const reusable = manifest.shared.filter((e) => e.type !== "layout");
|
|
5063
|
+
if (reusable.length === 0) return;
|
|
5055
5064
|
const plannedForPage = plan ? new Set(plan.sharedComponents.filter((c) => c.usedBy.includes(route)).map((c) => c.name)) : null;
|
|
5056
|
-
for (const e of
|
|
5065
|
+
for (const e of reusable) {
|
|
5057
5066
|
if (plannedForPage && !plannedForPage.has(e.name)) continue;
|
|
5058
5067
|
const kebab = e.file.replace(/^components\/shared\//, "").replace(/\.tsx$/, "");
|
|
5059
5068
|
const hasImport = pageCode.includes(`@/components/shared/${kebab}`);
|
|
@@ -5298,6 +5307,52 @@ function extractPageNamesFromMessage(message) {
|
|
|
5298
5307
|
}
|
|
5299
5308
|
return pages;
|
|
5300
5309
|
}
|
|
5310
|
+
function detectExplicitRootPage(message, pageNames) {
|
|
5311
|
+
const lower = message.toLowerCase();
|
|
5312
|
+
const w = "[a-z\u0430-\u044F\u0451A-Z\u0410-\u042F\u04010-9_]";
|
|
5313
|
+
const patterns = [
|
|
5314
|
+
new RegExp(
|
|
5315
|
+
`(?:main|start|home|root|first|entry|primary|\u0441\u0442\u0430\u0440\u0442\u043E\u0432${w}*|\u0433\u043B\u0430\u0432\u043D${w}*|\u043D\u0430\u0447\u0430\u043B\u044C\u043D${w}*)\\s*(?:page|screen|view|\u0441\u0442\u0440\u0430\u043D\u0438\u0446${w}*|\u044D\u043A\u0440\u0430\u043D${w}*)\\s*(?:is|should be|:|\u2014|\u2013|-|\u0431\u0443\u0434\u0435\u0442|\u044D\u0442\u043E)\\s*([a-z\u0430-\u044F\u0451A-Z\u0410-\u042F\u0401\\s]+)`,
|
|
5316
|
+
"i"
|
|
5317
|
+
),
|
|
5318
|
+
new RegExp(
|
|
5319
|
+
`([a-z\u0430-\u044F\u0451A-Z\u0410-\u042F\u0401\\s]+)\\s*(?:as|for|\u043A\u0430\u043A|\u0432 \u043A\u0430\u0447\u0435\u0441\u0442\u0432\u0435)\\s*(?:the\\s+)?(?:main|start|home|root|primary|\u0441\u0442\u0430\u0440\u0442\u043E\u0432${w}*|\u0433\u043B\u0430\u0432\u043D${w}*)\\s*(?:page|screen|\u0441\u0442\u0440\u0430\u043D\u0438\u0446${w}*)`,
|
|
5320
|
+
"i"
|
|
5321
|
+
),
|
|
5322
|
+
new RegExp(
|
|
5323
|
+
`(?:start|begin|\u043D\u0430\u0447\u0430\u0442${w}*|\u043D\u0430\u0447\u0438\u043D${w}*)\\s*(?:with|from|\u0441|\u0441\u043E)\\s*(?:a\\s+|an\\s+)?([a-z\u0430-\u044F\u0451A-Z\u0410-\u042F\u0401\\s]+?)(?:\\s+page|\\s+screen|\\s+form|\\s+\u0441\u0442\u0440\u0430\u043D\u0438\u0446${w}*|\\s+\u0444\u043E\u0440\u043C${w}*)?(?:\\s*$|[,.])`,
|
|
5324
|
+
"i"
|
|
5325
|
+
)
|
|
5326
|
+
];
|
|
5327
|
+
for (const pattern of patterns) {
|
|
5328
|
+
const match = lower.match(pattern);
|
|
5329
|
+
if (match) {
|
|
5330
|
+
const keyword = match[1].trim();
|
|
5331
|
+
const found = pageNames.find(
|
|
5332
|
+
(p) => p.name.toLowerCase().includes(keyword) || keyword.includes(p.name.toLowerCase()) || p.id.includes(keyword.replace(/\s+/g, "-"))
|
|
5333
|
+
);
|
|
5334
|
+
if (found) return found.id;
|
|
5335
|
+
}
|
|
5336
|
+
}
|
|
5337
|
+
return null;
|
|
5338
|
+
}
|
|
5339
|
+
var NON_MARKETING_ROUTES = /* @__PURE__ */ new Set([
|
|
5340
|
+
"/login",
|
|
5341
|
+
"/signin",
|
|
5342
|
+
"/signup",
|
|
5343
|
+
"/register",
|
|
5344
|
+
"/forgot-password",
|
|
5345
|
+
"/reset-password",
|
|
5346
|
+
"/dashboard",
|
|
5347
|
+
"/settings",
|
|
5348
|
+
"/account",
|
|
5349
|
+
"/tasks",
|
|
5350
|
+
"/profile"
|
|
5351
|
+
]);
|
|
5352
|
+
function isAppOnlyRequest(pageNames) {
|
|
5353
|
+
if (pageNames.length === 0) return false;
|
|
5354
|
+
return pageNames.every((p) => NON_MARKETING_ROUTES.has(p.route) || p.route.startsWith("/dashboard"));
|
|
5355
|
+
}
|
|
5301
5356
|
function normalizeRequest(request, config2) {
|
|
5302
5357
|
const changes = request.changes;
|
|
5303
5358
|
const VALID_TYPES = [
|
|
@@ -5567,7 +5622,9 @@ import { existsSync as existsSync14, readFileSync as readFileSync9, readdirSync
|
|
|
5567
5622
|
import { resolve as resolve6 } from "path";
|
|
5568
5623
|
import { z } from "zod";
|
|
5569
5624
|
import {
|
|
5625
|
+
SharedComponentTypeSchema,
|
|
5570
5626
|
loadManifest as loadManifest5,
|
|
5627
|
+
saveManifest,
|
|
5571
5628
|
generateSharedComponent as generateSharedComponent2
|
|
5572
5629
|
} from "@getcoherent/core";
|
|
5573
5630
|
|
|
@@ -5688,6 +5745,140 @@ async function pMap(items, fn, concurrency = 3) {
|
|
|
5688
5745
|
|
|
5689
5746
|
// src/commands/chat/split-generator.ts
|
|
5690
5747
|
import chalk8 from "chalk";
|
|
5748
|
+
|
|
5749
|
+
// src/utils/reuse-planner.ts
|
|
5750
|
+
var SECTION_TYPE_MAP = {
|
|
5751
|
+
stats: ["data-display", "widget"],
|
|
5752
|
+
metrics: ["data-display", "widget"],
|
|
5753
|
+
kpi: ["data-display", "widget"],
|
|
5754
|
+
list: ["data-display"],
|
|
5755
|
+
table: ["data-display"],
|
|
5756
|
+
items: ["data-display"],
|
|
5757
|
+
form: ["form"],
|
|
5758
|
+
filter: ["form"],
|
|
5759
|
+
search: ["form"],
|
|
5760
|
+
nav: ["navigation"],
|
|
5761
|
+
menu: ["navigation"],
|
|
5762
|
+
tabs: ["navigation"],
|
|
5763
|
+
card: ["widget", "data-display"],
|
|
5764
|
+
grid: ["widget", "data-display"],
|
|
5765
|
+
chart: ["data-display"],
|
|
5766
|
+
graph: ["data-display"],
|
|
5767
|
+
alert: ["feedback"],
|
|
5768
|
+
toast: ["feedback"],
|
|
5769
|
+
banner: ["feedback"]
|
|
5770
|
+
};
|
|
5771
|
+
function sectionToComponentTypes(section) {
|
|
5772
|
+
const lower = section.toLowerCase();
|
|
5773
|
+
const types = /* @__PURE__ */ new Set();
|
|
5774
|
+
for (const [keyword, componentTypes] of Object.entries(SECTION_TYPE_MAP)) {
|
|
5775
|
+
if (lower.includes(keyword)) {
|
|
5776
|
+
componentTypes.forEach((t) => types.add(t));
|
|
5777
|
+
}
|
|
5778
|
+
}
|
|
5779
|
+
return [...types];
|
|
5780
|
+
}
|
|
5781
|
+
function componentFilenameToImportPath(file) {
|
|
5782
|
+
const withoutExt = file.replace(/\.tsx?$/, "");
|
|
5783
|
+
return `@/${withoutExt}`;
|
|
5784
|
+
}
|
|
5785
|
+
function buildReusePlan(input) {
|
|
5786
|
+
const { pageName, sections, manifest } = input;
|
|
5787
|
+
const reuse = [];
|
|
5788
|
+
const usedComponents = /* @__PURE__ */ new Set();
|
|
5789
|
+
for (const section of sections) {
|
|
5790
|
+
const matchingTypes = sectionToComponentTypes(section);
|
|
5791
|
+
if (matchingTypes.length === 0) continue;
|
|
5792
|
+
for (const entry of manifest.shared) {
|
|
5793
|
+
if (usedComponents.has(entry.id)) continue;
|
|
5794
|
+
if (!matchingTypes.includes(entry.type)) continue;
|
|
5795
|
+
reuse.push({
|
|
5796
|
+
component: entry.name,
|
|
5797
|
+
targetSection: section,
|
|
5798
|
+
reason: entry.usedIn.length > 0 ? `Used on ${entry.usedIn.length} page(s)` : `Matches section type (${entry.type})`,
|
|
5799
|
+
importPath: componentFilenameToImportPath(entry.file),
|
|
5800
|
+
usageExample: entry.usageExample || `<${entry.name} />`
|
|
5801
|
+
});
|
|
5802
|
+
usedComponents.add(entry.id);
|
|
5803
|
+
break;
|
|
5804
|
+
}
|
|
5805
|
+
}
|
|
5806
|
+
const reusePatterns = extractCodePatterns(input.existingPageCode, sections);
|
|
5807
|
+
return { pageName, reuse, createNew: [], reusePatterns };
|
|
5808
|
+
}
|
|
5809
|
+
function extractCodePatterns(existingPageCode, _sections) {
|
|
5810
|
+
const patterns = [];
|
|
5811
|
+
const gridPatterns = /* @__PURE__ */ new Map();
|
|
5812
|
+
for (const [route, code] of Object.entries(existingPageCode)) {
|
|
5813
|
+
const gridMatches = code.match(/className="[^"]*grid[^"]*"/g) || [];
|
|
5814
|
+
for (const match of gridMatches) {
|
|
5815
|
+
const cls = match.replace(/className="|"/g, "");
|
|
5816
|
+
if (!gridPatterns.has(cls)) gridPatterns.set(cls, []);
|
|
5817
|
+
gridPatterns.get(cls).push(route);
|
|
5818
|
+
}
|
|
5819
|
+
}
|
|
5820
|
+
for (const [pattern, pages] of gridPatterns) {
|
|
5821
|
+
if (pages.length >= 1 && pattern.includes("grid-cols")) {
|
|
5822
|
+
patterns.push({
|
|
5823
|
+
pattern,
|
|
5824
|
+
sourcePages: pages,
|
|
5825
|
+
targetSection: "Layout grid"
|
|
5826
|
+
});
|
|
5827
|
+
}
|
|
5828
|
+
}
|
|
5829
|
+
return patterns;
|
|
5830
|
+
}
|
|
5831
|
+
function buildReusePlanDirective(plan) {
|
|
5832
|
+
if (plan.reuse.length === 0 && plan.createNew.length === 0 && plan.reusePatterns.length === 0) {
|
|
5833
|
+
return "";
|
|
5834
|
+
}
|
|
5835
|
+
const lines = [`COMPONENT REUSE PLAN FOR THIS PAGE:`];
|
|
5836
|
+
if (plan.reuse.length > 0) {
|
|
5837
|
+
lines.push("", "MUST USE (import these \u2014 do NOT re-implement):");
|
|
5838
|
+
for (const r of plan.reuse) {
|
|
5839
|
+
lines.push(` - ${r.component} from ${r.importPath} \u2014 for "${r.targetSection}" section`);
|
|
5840
|
+
if (r.usageExample) {
|
|
5841
|
+
lines.push(` Example: ${r.usageExample}`);
|
|
5842
|
+
}
|
|
5843
|
+
}
|
|
5844
|
+
}
|
|
5845
|
+
if (plan.createNew.length > 0) {
|
|
5846
|
+
lines.push("", "CREATE NEW (no existing match):");
|
|
5847
|
+
for (const c of plan.createNew) {
|
|
5848
|
+
lines.push(` - ${c.name} \u2014 ${c.reason} (suggest type: ${c.suggestedType})`);
|
|
5849
|
+
}
|
|
5850
|
+
}
|
|
5851
|
+
if (plan.reusePatterns.length > 0) {
|
|
5852
|
+
lines.push("", "LAYOUT PATTERNS (copy from existing pages for visual consistency):");
|
|
5853
|
+
for (const p of plan.reusePatterns) {
|
|
5854
|
+
lines.push(` - ${p.targetSection}: className="${p.pattern}"`);
|
|
5855
|
+
lines.push(` (source: ${p.sourcePages.join(", ")})`);
|
|
5856
|
+
}
|
|
5857
|
+
}
|
|
5858
|
+
return lines.join("\n");
|
|
5859
|
+
}
|
|
5860
|
+
function verifyReusePlan(generatedCode, plan) {
|
|
5861
|
+
const passed = [];
|
|
5862
|
+
const missed = [];
|
|
5863
|
+
for (const entry of plan.reuse) {
|
|
5864
|
+
const isImported = generatedCode.includes(entry.importPath) || generatedCode.includes(`{ ${entry.component} }`) || generatedCode.includes(`{ ${entry.component},`);
|
|
5865
|
+
if (isImported) {
|
|
5866
|
+
passed.push(entry);
|
|
5867
|
+
} else {
|
|
5868
|
+
missed.push(entry);
|
|
5869
|
+
}
|
|
5870
|
+
}
|
|
5871
|
+
let retryDirective;
|
|
5872
|
+
if (missed.length > 0) {
|
|
5873
|
+
const lines = missed.map(
|
|
5874
|
+
(m) => `CRITICAL: Your previous output failed to import ${m.component} from ${m.importPath}. You MUST import and use this component for the "${m.targetSection}" section. Do NOT re-implement it inline.`
|
|
5875
|
+
);
|
|
5876
|
+
retryDirective = lines.join("\n");
|
|
5877
|
+
}
|
|
5878
|
+
return { passed, missed, retryDirective };
|
|
5879
|
+
}
|
|
5880
|
+
|
|
5881
|
+
// src/commands/chat/split-generator.ts
|
|
5691
5882
|
function buildExistingPagesContext(config2) {
|
|
5692
5883
|
const pages = config2.pages || [];
|
|
5693
5884
|
const analyzed = pages.filter((p) => p.pageAnalysis);
|
|
@@ -5882,6 +6073,52 @@ function readExistingAppPageForReference(projectRoot, plan) {
|
|
|
5882
6073
|
}
|
|
5883
6074
|
return null;
|
|
5884
6075
|
}
|
|
6076
|
+
function buildLayoutNote(layoutType) {
|
|
6077
|
+
switch (layoutType) {
|
|
6078
|
+
case "sidebar":
|
|
6079
|
+
return "This page uses a SIDEBAR layout. The sidebar navigation is already rendered by the group layout. Do NOT create your own sidebar or side navigation. Start with the main content area directly. The page content appears to the right of the sidebar.";
|
|
6080
|
+
case "both":
|
|
6081
|
+
return "This page has both a sidebar and a header rendered by the group layout. Do NOT include any site-wide header, nav, sidebar, or footer in this page. Start with the main content directly.";
|
|
6082
|
+
case "none":
|
|
6083
|
+
return "This page has no shared navigation from the layout. Include any needed navigation within the page itself.";
|
|
6084
|
+
default:
|
|
6085
|
+
return "Header and Footer are shared components rendered by the root layout. Do NOT include any site-wide <header>, <nav>, or <footer> in this page. Start with the main content directly.";
|
|
6086
|
+
}
|
|
6087
|
+
}
|
|
6088
|
+
function buildAnchorPagePrompt(homePage, message, allPagesList, allRoutes, plan) {
|
|
6089
|
+
const pageType = detectPageType(homePage.name) || detectPageType(homePage.route);
|
|
6090
|
+
const authPageTypes = /* @__PURE__ */ new Set(["login", "register", "reset-password"]);
|
|
6091
|
+
const isAuth = isAuthRoute(homePage.route) || isAuthRoute(homePage.name) || authPageTypes.has(pageType || "");
|
|
6092
|
+
if (isAuth) {
|
|
6093
|
+
return `Create ONE page called "${homePage.name}" at route "${homePage.route}". Context: ${message}. This is the application's entry point \u2014 a clean, centered authentication form. Generate complete pageCode. Do NOT include site-wide <header>, <nav>, or <footer> \u2014 this page has its own minimal layout. Make it visually polished with proper form validation UI \u2014 this page sets the design direction for the entire site. Do not generate other pages.`;
|
|
6094
|
+
}
|
|
6095
|
+
const groupLayout = plan?.groups.find((g) => g.pages.includes(homePage.route))?.layout;
|
|
6096
|
+
if (groupLayout === "sidebar" || pageType === "dashboard") {
|
|
6097
|
+
return `Create ONE page called "${homePage.name}" at route "${homePage.route}". Context: ${message}. This REPLACES the default placeholder page \u2014 generate a complete application page. Generate complete pageCode. Do NOT include a sidebar or top navigation \u2014 these are handled by the layout. Focus on the main content area. Make it visually polished \u2014 this page sets the design direction for the entire site. Do not generate other pages.`;
|
|
6098
|
+
}
|
|
6099
|
+
return `Create ONE page called "${homePage.name}" at route "${homePage.route}". Context: ${message}. This REPLACES the default placeholder page \u2014 generate a complete, content-rich landing page for the project described above. Generate complete pageCode. Include a branded site-wide <header> with navigation links to ALL these pages: ${allPagesList}. Use these EXACT routes in navigation: ${allRoutes}. Include a <footer> at the bottom. Make it visually polished \u2014 this page sets the design direction for the entire site. Do not generate other pages.`;
|
|
6100
|
+
}
|
|
6101
|
+
function getGroupLayoutForRoute(route, plan) {
|
|
6102
|
+
if (!plan) return void 0;
|
|
6103
|
+
const group = plan.groups.find((g) => g.pages.includes(route));
|
|
6104
|
+
return group?.layout;
|
|
6105
|
+
}
|
|
6106
|
+
var manifestLock = Promise.resolve();
|
|
6107
|
+
async function updateManifestSafe(projectRoot, fn) {
|
|
6108
|
+
const timeoutMs = 5e3;
|
|
6109
|
+
const update = manifestLock.then(async () => {
|
|
6110
|
+
const m = await loadManifest5(projectRoot);
|
|
6111
|
+
const updated = fn(m);
|
|
6112
|
+
await saveManifest(projectRoot, updated);
|
|
6113
|
+
});
|
|
6114
|
+
manifestLock = update.catch(() => {
|
|
6115
|
+
});
|
|
6116
|
+
await Promise.race([
|
|
6117
|
+
update,
|
|
6118
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error("manifest sync timeout")), timeoutMs))
|
|
6119
|
+
]).catch(() => {
|
|
6120
|
+
});
|
|
6121
|
+
}
|
|
5885
6122
|
async function splitGeneratePages(spinner, message, modCtx, provider, parseOpts) {
|
|
5886
6123
|
let pageNames = [];
|
|
5887
6124
|
spinner.start("Phase 1/6 \u2014 Planning pages...");
|
|
@@ -5924,7 +6161,11 @@ async function splitGeneratePages(spinner, message, modCtx, provider, parseOpts)
|
|
|
5924
6161
|
(p) => p.id !== "home" && p.id !== "new" && p.route !== "/"
|
|
5925
6162
|
);
|
|
5926
6163
|
const isFreshProject = userPages.length === 0;
|
|
5927
|
-
|
|
6164
|
+
const explicitRootId = detectExplicitRootPage(message, pageNames);
|
|
6165
|
+
if (explicitRootId) {
|
|
6166
|
+
const rootPage = pageNames.find((p) => p.id === explicitRootId);
|
|
6167
|
+
if (rootPage) rootPage.route = "/";
|
|
6168
|
+
} else if (!isAppOnlyRequest(pageNames) && (isFreshProject || impliesFullWebsite(message))) {
|
|
5928
6169
|
pageNames.unshift({ name: "Home", id: "home", route: "/" });
|
|
5929
6170
|
}
|
|
5930
6171
|
}
|
|
@@ -5943,14 +6184,16 @@ async function splitGeneratePages(spinner, message, modCtx, provider, parseOpts)
|
|
|
5943
6184
|
spinner.start("Phase 2/6 \u2014 Generating architecture plan...");
|
|
5944
6185
|
try {
|
|
5945
6186
|
const ai = await createAIProvider(provider ?? "auto");
|
|
5946
|
-
const
|
|
5947
|
-
|
|
5948
|
-
|
|
5949
|
-
message,
|
|
5950
|
-
|
|
5951
|
-
layoutHint
|
|
5952
|
-
|
|
5953
|
-
|
|
6187
|
+
const cachedPlan = loadPlan(parseOpts.projectRoot);
|
|
6188
|
+
let planWarnings = [];
|
|
6189
|
+
if (cachedPlan) {
|
|
6190
|
+
plan = await updateArchitecturePlan(cachedPlan, pageNames, message, ai);
|
|
6191
|
+
} else {
|
|
6192
|
+
const layoutHint = modCtx.config.navigation?.type || null;
|
|
6193
|
+
const result = await generateArchitecturePlan(pageNames, message, ai, layoutHint);
|
|
6194
|
+
plan = result.plan;
|
|
6195
|
+
planWarnings = result.warnings;
|
|
6196
|
+
}
|
|
5954
6197
|
if (plan) {
|
|
5955
6198
|
const groupsSummary = plan.groups.map((g) => `${g.id} (${g.layout}, ${g.pages.length} pages)`).join(", ");
|
|
5956
6199
|
const sharedSummary = plan.sharedComponents.length > 0 ? plan.sharedComponents.map((c) => `${c.name} \u2192 ${c.usedBy.join(", ")}`).join(" | ") : "";
|
|
@@ -6001,12 +6244,8 @@ async function splitGeneratePages(spinner, message, modCtx, provider, parseOpts)
|
|
|
6001
6244
|
if (!reusedExistingAnchor) {
|
|
6002
6245
|
spinner.start(`Phase 3/6 \u2014 Generating ${homePage.name} page (sets design direction)...`);
|
|
6003
6246
|
try {
|
|
6004
|
-
const
|
|
6005
|
-
|
|
6006
|
-
modCtx,
|
|
6007
|
-
provider,
|
|
6008
|
-
parseOpts
|
|
6009
|
-
);
|
|
6247
|
+
const anchorPrompt = buildAnchorPagePrompt(homePage, message, allPagesList, allRoutes, plan);
|
|
6248
|
+
const homeResult = await parseModification(anchorPrompt, modCtx, provider, parseOpts);
|
|
6010
6249
|
const codePage = homeResult.requests.find((r) => r.type === "add-page");
|
|
6011
6250
|
if (codePage) {
|
|
6012
6251
|
homeRequest = codePage;
|
|
@@ -6036,7 +6275,7 @@ async function splitGeneratePages(spinner, message, modCtx, provider, parseOpts)
|
|
|
6036
6275
|
if (plan && plan.sharedComponents.length > 0) {
|
|
6037
6276
|
spinner.start(`Phase 4.5/6 \u2014 Generating ${plan.sharedComponents.length} shared components from plan...`);
|
|
6038
6277
|
try {
|
|
6039
|
-
const { generateSharedComponentsFromPlan } = await import("./plan-generator-
|
|
6278
|
+
const { generateSharedComponentsFromPlan } = await import("./plan-generator-ITHYNYJI.js");
|
|
6040
6279
|
const generated = await generateSharedComponentsFromPlan(
|
|
6041
6280
|
plan,
|
|
6042
6281
|
styleContext,
|
|
@@ -6078,7 +6317,6 @@ async function splitGeneratePages(spinner, message, modCtx, provider, parseOpts)
|
|
|
6078
6317
|
return { requests: homeRequest ? [homeRequest] : [], plan };
|
|
6079
6318
|
}
|
|
6080
6319
|
spinner.start(`Phase 5/6 \u2014 Generating ${remainingPages.length} pages in parallel...`);
|
|
6081
|
-
const sharedLayoutNote = "Header and Footer are shared components rendered by the root layout. Do NOT include any site-wide <header>, <nav>, or <footer> in this page. Start with the main content directly.";
|
|
6082
6320
|
const sharedComponentsNote = buildSharedComponentsNote(parseOpts.sharedComponentsSummary);
|
|
6083
6321
|
const currentManifest = projectRoot ? await loadManifest5(projectRoot) : null;
|
|
6084
6322
|
const routeNote = `EXISTING ROUTES in this project: ${allRoutes}. All internal links MUST point to one of these routes. If a target doesn't exist, use href="#".`;
|
|
@@ -6092,6 +6330,23 @@ ${existingAppPageCode}
|
|
|
6092
6330
|
\`\`\`
|
|
6093
6331
|
` : "";
|
|
6094
6332
|
const existingPagesContext = buildExistingPagesContext(modCtx.config);
|
|
6333
|
+
const existingPageCode = {};
|
|
6334
|
+
if (projectRoot) {
|
|
6335
|
+
const appDir = resolve6(projectRoot, "app");
|
|
6336
|
+
if (existsSync14(appDir)) {
|
|
6337
|
+
const pageFiles = readdirSync2(appDir, { recursive: true }).filter(
|
|
6338
|
+
(f) => typeof f === "string" && f.endsWith("page.tsx")
|
|
6339
|
+
);
|
|
6340
|
+
for (const pf of pageFiles) {
|
|
6341
|
+
try {
|
|
6342
|
+
const code = readFileSync9(resolve6(appDir, pf), "utf-8");
|
|
6343
|
+
const route = "/" + pf.replace(/\/page\.tsx$/, "").replace(/\(.*?\)\//g, "");
|
|
6344
|
+
existingPageCode[route === "/" ? "/" : route] = code;
|
|
6345
|
+
} catch {
|
|
6346
|
+
}
|
|
6347
|
+
}
|
|
6348
|
+
}
|
|
6349
|
+
}
|
|
6095
6350
|
const AI_CONCURRENCY = 3;
|
|
6096
6351
|
let phase5Done = 0;
|
|
6097
6352
|
const remainingRequests = await pMap(
|
|
@@ -6101,15 +6356,45 @@ ${existingAppPageCode}
|
|
|
6101
6356
|
const pageType = plan ? getPageType(route, plan) : inferPageTypeFromRoute(route);
|
|
6102
6357
|
const designConstraints = getDesignQualityForType(pageType);
|
|
6103
6358
|
const authNote = isAuth ? 'For this auth page: the auth layout already provides centering (flex items-center justify-center min-h-svh). Do NOT add your own centering wrapper or min-h-svh. Just output a div with className="w-full max-w-md" containing the Card. Do NOT use section containers or full-width wrappers.' : void 0;
|
|
6359
|
+
const layoutForPage = getGroupLayoutForRoute(route, plan);
|
|
6360
|
+
const layoutNote = buildLayoutNote(layoutForPage);
|
|
6104
6361
|
const tieredNote = currentManifest ? buildTieredComponentsPrompt(currentManifest, pageType) : void 0;
|
|
6362
|
+
const pageKey = route.replace(/^\//, "") || "home";
|
|
6363
|
+
const pageSections = plan?.pageNotes?.[pageKey]?.sections || [];
|
|
6364
|
+
let reusePlanDirective = "";
|
|
6365
|
+
let currentReusePlan = null;
|
|
6366
|
+
if (currentManifest && currentManifest.shared.length > 0) {
|
|
6367
|
+
try {
|
|
6368
|
+
currentReusePlan = buildReusePlan({
|
|
6369
|
+
pageName: name,
|
|
6370
|
+
pageType,
|
|
6371
|
+
sections: pageSections,
|
|
6372
|
+
manifest: currentManifest,
|
|
6373
|
+
existingPageCode,
|
|
6374
|
+
userRequest: message
|
|
6375
|
+
});
|
|
6376
|
+
reusePlanDirective = buildReusePlanDirective(currentReusePlan);
|
|
6377
|
+
if (currentReusePlan.reuse.length > 0 || currentReusePlan.createNew.length > 0) {
|
|
6378
|
+
const parts = [];
|
|
6379
|
+
if (currentReusePlan.reuse.length > 0)
|
|
6380
|
+
parts.push(`REUSE: ${currentReusePlan.reuse.map((r) => r.component).join(", ")}`);
|
|
6381
|
+
if (currentReusePlan.createNew.length > 0)
|
|
6382
|
+
parts.push(`CREATE: ${currentReusePlan.createNew.map((c) => c.name).join(", ")}`);
|
|
6383
|
+
if (currentReusePlan.reusePatterns.length > 0)
|
|
6384
|
+
parts.push(`${currentReusePlan.reusePatterns.length} pattern(s)`);
|
|
6385
|
+
console.log(chalk8.dim(` \u{1F504} Reuse Plan for "${name}": ${parts.join(" | ")}`));
|
|
6386
|
+
}
|
|
6387
|
+
} catch {
|
|
6388
|
+
}
|
|
6389
|
+
}
|
|
6105
6390
|
const prompt = [
|
|
6106
6391
|
`Create ONE page called "${name}" at route "${route}".`,
|
|
6107
6392
|
`Context: ${message}.`,
|
|
6108
6393
|
`Generate complete pageCode for this single page only. Do not generate other pages.`,
|
|
6109
6394
|
`PAGE TYPE: ${pageType}`,
|
|
6110
6395
|
designConstraints,
|
|
6111
|
-
|
|
6112
|
-
tieredNote || sharedComponentsNote,
|
|
6396
|
+
layoutNote,
|
|
6397
|
+
reusePlanDirective || tieredNote || sharedComponentsNote,
|
|
6113
6398
|
routeNote,
|
|
6114
6399
|
alignmentNote,
|
|
6115
6400
|
authNote,
|
|
@@ -6122,7 +6407,47 @@ ${existingAppPageCode}
|
|
|
6122
6407
|
const result = await parseModification(prompt, modCtx, provider, parseOpts);
|
|
6123
6408
|
phase5Done++;
|
|
6124
6409
|
spinner.text = `Phase 5/6 \u2014 ${phase5Done}/${remainingPages.length} pages generated...`;
|
|
6125
|
-
|
|
6410
|
+
let codePage = result.requests.find((r) => r.type === "add-page");
|
|
6411
|
+
if (currentReusePlan && currentReusePlan.reuse.length > 0 && codePage) {
|
|
6412
|
+
const pageCode = codePage.changes?.pageCode;
|
|
6413
|
+
if (pageCode) {
|
|
6414
|
+
const verification = verifyReusePlan(pageCode, currentReusePlan);
|
|
6415
|
+
if (verification.passed.length > 0) {
|
|
6416
|
+
console.log(
|
|
6417
|
+
chalk8.dim(` \u2713 Reuse verified for "${name}": ${verification.passed.map((p) => p.component).join(", ")}`)
|
|
6418
|
+
);
|
|
6419
|
+
}
|
|
6420
|
+
if (verification.missed.length > 0 && verification.retryDirective) {
|
|
6421
|
+
console.log(
|
|
6422
|
+
chalk8.yellow(
|
|
6423
|
+
` \u26A0 Missed reuse in "${name}": ${verification.missed.map((m) => m.component).join(", ")} \u2014 retrying...`
|
|
6424
|
+
)
|
|
6425
|
+
);
|
|
6426
|
+
try {
|
|
6427
|
+
const retryPrompt = [prompt, verification.retryDirective].join("\n\n");
|
|
6428
|
+
const retryResult = await parseModification(retryPrompt, modCtx, provider, parseOpts);
|
|
6429
|
+
const retryPage = retryResult.requests.find((r) => r.type === "add-page");
|
|
6430
|
+
if (retryPage) codePage = retryPage;
|
|
6431
|
+
} catch {
|
|
6432
|
+
}
|
|
6433
|
+
}
|
|
6434
|
+
}
|
|
6435
|
+
}
|
|
6436
|
+
if (projectRoot && codePage && currentManifest) {
|
|
6437
|
+
const finalPageCode = codePage.changes?.pageCode;
|
|
6438
|
+
if (finalPageCode) {
|
|
6439
|
+
await updateManifestSafe(projectRoot, (m) => {
|
|
6440
|
+
const updatedShared = m.shared.map((entry) => {
|
|
6441
|
+
const isUsed = finalPageCode.includes(`{ ${entry.name} }`) || finalPageCode.includes(`{ ${entry.name},`);
|
|
6442
|
+
if (isUsed && !entry.usedIn.includes(route)) {
|
|
6443
|
+
return { ...entry, usedIn: [...entry.usedIn, route] };
|
|
6444
|
+
}
|
|
6445
|
+
return entry;
|
|
6446
|
+
});
|
|
6447
|
+
return { ...m, shared: updatedShared };
|
|
6448
|
+
});
|
|
6449
|
+
}
|
|
6450
|
+
}
|
|
6126
6451
|
return codePage || { type: "add-page", target: "new", changes: { id, name, route } };
|
|
6127
6452
|
} catch {
|
|
6128
6453
|
phase5Done++;
|
|
@@ -6170,7 +6495,7 @@ ${existingAppPageCode}
|
|
|
6170
6495
|
}
|
|
6171
6496
|
var SharedExtractionItemSchema = z.object({
|
|
6172
6497
|
name: z.string().min(2).max(50),
|
|
6173
|
-
type:
|
|
6498
|
+
type: SharedComponentTypeSchema,
|
|
6174
6499
|
description: z.string().max(200).default(""),
|
|
6175
6500
|
propsInterface: z.string().default("{}"),
|
|
6176
6501
|
code: z.string()
|
|
@@ -6216,7 +6541,8 @@ async function extractSharedComponents(homePageCode, projectRoot, aiProvider) {
|
|
|
6216
6541
|
const provider = getComponentProvider();
|
|
6217
6542
|
for (const item of filtered) {
|
|
6218
6543
|
try {
|
|
6219
|
-
|
|
6544
|
+
let { code: fixedCode } = await autoFixCode(item.code);
|
|
6545
|
+
fixedCode = fixedCode.replace(/export default function (\w+)/g, "export function $1");
|
|
6220
6546
|
const shadcnImports = [...fixedCode.matchAll(/from\s+["']@\/components\/ui\/(.+?)["']/g)];
|
|
6221
6547
|
for (const match of shadcnImports) {
|
|
6222
6548
|
await provider.installComponent(match[1], projectRoot);
|
|
@@ -6284,7 +6610,7 @@ import chalk11 from "chalk";
|
|
|
6284
6610
|
import {
|
|
6285
6611
|
getTemplateForPageType,
|
|
6286
6612
|
loadManifest as loadManifest6,
|
|
6287
|
-
saveManifest,
|
|
6613
|
+
saveManifest as saveManifest2,
|
|
6288
6614
|
updateUsedIn,
|
|
6289
6615
|
findSharedComponentByIdOrName,
|
|
6290
6616
|
generateSharedComponent as generateSharedComponent4
|
|
@@ -6409,7 +6735,9 @@ async function canOverwriteShared(projectRoot, componentFile, storedHashes) {
|
|
|
6409
6735
|
}
|
|
6410
6736
|
return !edited;
|
|
6411
6737
|
}
|
|
6412
|
-
async function regenerateLayout(config2, projectRoot, options = {
|
|
6738
|
+
async function regenerateLayout(config2, projectRoot, options = {
|
|
6739
|
+
navChanged: false
|
|
6740
|
+
}) {
|
|
6413
6741
|
const appType = config2.settings.appType || "multi-page";
|
|
6414
6742
|
const generator = new PageGenerator(config2);
|
|
6415
6743
|
const initialized = config2.settings.initialized !== false;
|
|
@@ -6465,7 +6793,7 @@ async function regenerateLayout(config2, projectRoot, options = { navChanged: fa
|
|
|
6465
6793
|
try {
|
|
6466
6794
|
await integrateSharedLayoutIntoRootLayout2(projectRoot);
|
|
6467
6795
|
await ensureAuthRouteGroup(projectRoot);
|
|
6468
|
-
await ensureAppRouteGroupLayout(projectRoot, config2.navigation?.type, options.navChanged);
|
|
6796
|
+
await ensureAppRouteGroupLayout(projectRoot, config2.navigation?.type, options.navChanged, options.groupLayouts);
|
|
6469
6797
|
} catch (err) {
|
|
6470
6798
|
if (process.env.COHERENT_DEBUG === "1") {
|
|
6471
6799
|
console.log(chalk9.dim("Layout integration warning:", err));
|
|
@@ -6494,12 +6822,13 @@ async function scanAndInstallSharedDeps(projectRoot) {
|
|
|
6494
6822
|
}
|
|
6495
6823
|
return [...new Set(installed)];
|
|
6496
6824
|
}
|
|
6497
|
-
async function ensureAppRouteGroupLayout(projectRoot, navType, forceUpdate = false) {
|
|
6825
|
+
async function ensureAppRouteGroupLayout(projectRoot, navType, forceUpdate = false, groupLayouts) {
|
|
6826
|
+
const effectiveNavType = groupLayouts?.["app"] || navType;
|
|
6498
6827
|
const layoutPath = resolve7(projectRoot, "app", "(app)", "layout.tsx");
|
|
6499
6828
|
if (existsSync15(layoutPath) && !forceUpdate) return;
|
|
6500
6829
|
const { mkdir: mkdirAsync } = await import("fs/promises");
|
|
6501
6830
|
await mkdirAsync(resolve7(projectRoot, "app", "(app)"), { recursive: true });
|
|
6502
|
-
const code = buildAppLayoutCode(
|
|
6831
|
+
const code = buildAppLayoutCode(effectiveNavType);
|
|
6503
6832
|
await writeFile(layoutPath, code);
|
|
6504
6833
|
}
|
|
6505
6834
|
function buildAppLayoutCode(navType) {
|
|
@@ -6583,15 +6912,32 @@ export default function GroupLayout({
|
|
|
6583
6912
|
}
|
|
6584
6913
|
`;
|
|
6585
6914
|
}
|
|
6586
|
-
async function ensurePlanGroupLayouts(projectRoot, plan) {
|
|
6915
|
+
async function ensurePlanGroupLayouts(projectRoot, plan, storedHashes = {}, config2) {
|
|
6587
6916
|
const { mkdir: mkdirAsync } = await import("fs/promises");
|
|
6917
|
+
const { createHash: createHash2 } = await import("crypto");
|
|
6588
6918
|
for (const group of plan.groups) {
|
|
6589
6919
|
const groupDir = resolve7(projectRoot, "app", `(${group.id})`);
|
|
6590
6920
|
await mkdirAsync(groupDir, { recursive: true });
|
|
6591
6921
|
const layoutPath = resolve7(groupDir, "layout.tsx");
|
|
6922
|
+
const relPath = `app/(${group.id})/layout.tsx`;
|
|
6923
|
+
if (existsSync15(layoutPath)) {
|
|
6924
|
+
const currentContent = readFileSync10(layoutPath, "utf-8");
|
|
6925
|
+
const currentHash = createHash2("md5").update(currentContent).digest("hex");
|
|
6926
|
+
const storedHash = storedHashes[relPath];
|
|
6927
|
+
if (storedHash && storedHash !== currentHash) {
|
|
6928
|
+
continue;
|
|
6929
|
+
}
|
|
6930
|
+
}
|
|
6592
6931
|
const code = buildGroupLayoutCode(group.layout, group.pages);
|
|
6593
6932
|
await writeFile(layoutPath, code);
|
|
6594
6933
|
}
|
|
6934
|
+
if (config2) {
|
|
6935
|
+
const layouts = {};
|
|
6936
|
+
for (const group of plan.groups) {
|
|
6937
|
+
layouts[group.id] = group.layout;
|
|
6938
|
+
}
|
|
6939
|
+
config2.groupLayouts = layouts;
|
|
6940
|
+
}
|
|
6595
6941
|
}
|
|
6596
6942
|
async function regenerateFiles(modified, config2, projectRoot, options = { navChanged: false }) {
|
|
6597
6943
|
const componentIds = /* @__PURE__ */ new Set();
|
|
@@ -7093,7 +7439,7 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
|
|
|
7093
7439
|
const filePathRel = routePath ? `app/${routePath}/page.tsx` : "app/page.tsx";
|
|
7094
7440
|
if (!usedIn.includes(filePathRel)) {
|
|
7095
7441
|
const nextManifest = updateUsedIn(manifest, resolved.id, [...usedIn, filePathRel]);
|
|
7096
|
-
await
|
|
7442
|
+
await saveManifest2(projectRoot, nextManifest);
|
|
7097
7443
|
}
|
|
7098
7444
|
printLinkSharedReport({
|
|
7099
7445
|
sharedId: resolved.id,
|
|
@@ -7194,7 +7540,7 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
|
|
|
7194
7540
|
}
|
|
7195
7541
|
const manifest = await loadManifest6(projectRoot);
|
|
7196
7542
|
const nextManifest = updateUsedIn(manifest, created.id, usedInFiles);
|
|
7197
|
-
await
|
|
7543
|
+
await saveManifest2(projectRoot, nextManifest);
|
|
7198
7544
|
printPromoteAndLinkReport({
|
|
7199
7545
|
id: created.id,
|
|
7200
7546
|
name: created.name,
|
|
@@ -7413,44 +7759,55 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
|
|
|
7413
7759
|
});
|
|
7414
7760
|
let issues = validatePageQuality(codeToWrite, void 0, qualityPageType);
|
|
7415
7761
|
const errors = issues.filter((i) => i.severity === "error");
|
|
7416
|
-
|
|
7762
|
+
const MAX_QUALITY_FIX_ATTEMPTS = 2;
|
|
7763
|
+
let currentErrors = errors;
|
|
7764
|
+
for (let attempt = 0; attempt < MAX_QUALITY_FIX_ATTEMPTS && currentErrors.length > 0; attempt++) {
|
|
7765
|
+
if (!aiProvider) break;
|
|
7417
7766
|
console.log(
|
|
7418
|
-
chalk11.yellow(
|
|
7419
|
-
|
|
7767
|
+
chalk11.yellow(
|
|
7768
|
+
`
|
|
7769
|
+
\u{1F504} ${currentErrors.length} quality errors \u2014 attempting AI fix${attempt > 0 ? ` (retry ${attempt + 1})` : ""} for ${page.name || page.id}...`
|
|
7770
|
+
)
|
|
7420
7771
|
);
|
|
7421
7772
|
try {
|
|
7422
7773
|
const ai = await createAIProvider(aiProvider);
|
|
7423
|
-
if (ai.editPageCode)
|
|
7424
|
-
|
|
7425
|
-
|
|
7774
|
+
if (!ai.editPageCode) break;
|
|
7775
|
+
const errorList = currentErrors.map((e) => `Line ${e.line}: [${e.type}] ${e.message}`).join("\n");
|
|
7776
|
+
const instruction = `Fix these quality issues:
|
|
7426
7777
|
${errorList}
|
|
7427
7778
|
|
|
7428
7779
|
Rules:
|
|
7429
7780
|
- Replace raw Tailwind colors (bg-emerald-500, text-zinc-400, etc.) with semantic tokens (bg-primary, text-muted-foreground, bg-muted, etc.)
|
|
7781
|
+
- Replace placeholder content ("Lorem ipsum", "John Doe", "user@example.com") with realistic contextual content
|
|
7430
7782
|
- Ensure heading hierarchy (h1 \u2192 h2 \u2192 h3, no skipping)
|
|
7431
7783
|
- Add Label components for form inputs
|
|
7432
7784
|
- Keep all existing functionality and layout intact`;
|
|
7433
|
-
|
|
7434
|
-
|
|
7435
|
-
|
|
7436
|
-
|
|
7437
|
-
|
|
7438
|
-
|
|
7439
|
-
|
|
7440
|
-
|
|
7441
|
-
|
|
7442
|
-
|
|
7443
|
-
}
|
|
7444
|
-
await writeFile(filePath, codeToWrite);
|
|
7445
|
-
issues = validatePageQuality(codeToWrite, void 0, qualityPageType);
|
|
7446
|
-
const finalErrors = issues.filter((i) => i.severity === "error").length;
|
|
7447
|
-
console.log(chalk11.green(` \u2714 Quality fix: ${errors.length} \u2192 ${finalErrors} errors`));
|
|
7785
|
+
const fixedCode2 = await ai.editPageCode(codeToWrite, instruction, page.name || page.id || "Page");
|
|
7786
|
+
if (fixedCode2 && fixedCode2.length > 100 && /export\s+default/.test(fixedCode2)) {
|
|
7787
|
+
const recheck = validatePageQuality(fixedCode2, void 0, qualityPageType);
|
|
7788
|
+
const recheckErrors = recheck.filter((i) => i.severity === "error");
|
|
7789
|
+
if (recheckErrors.length < currentErrors.length) {
|
|
7790
|
+
codeToWrite = fixedCode2;
|
|
7791
|
+
const { code: reFixed, fixes: reFixes } = await autoFixCode(codeToWrite, autoFixCtx);
|
|
7792
|
+
if (reFixes.length > 0) {
|
|
7793
|
+
codeToWrite = reFixed;
|
|
7794
|
+
postFixes.push(...reFixes);
|
|
7448
7795
|
}
|
|
7796
|
+
await writeFile(filePath, codeToWrite);
|
|
7797
|
+
currentErrors = recheckErrors;
|
|
7798
|
+
console.log(chalk11.green(` \u2714 Quality fix: ${errors.length} \u2192 ${currentErrors.length} errors`));
|
|
7799
|
+
if (currentErrors.length === 0) break;
|
|
7800
|
+
} else {
|
|
7801
|
+
break;
|
|
7449
7802
|
}
|
|
7803
|
+
} else {
|
|
7804
|
+
break;
|
|
7450
7805
|
}
|
|
7451
7806
|
} catch {
|
|
7807
|
+
break;
|
|
7452
7808
|
}
|
|
7453
7809
|
}
|
|
7810
|
+
issues = validatePageQuality(codeToWrite, void 0, qualityPageType);
|
|
7454
7811
|
const report = formatIssues(issues);
|
|
7455
7812
|
if (report) {
|
|
7456
7813
|
console.log(chalk11.yellow(`
|
|
@@ -8056,7 +8413,7 @@ async function chatCommand(message, options) {
|
|
|
8056
8413
|
if (options.newComponent) {
|
|
8057
8414
|
const componentName = options.newComponent;
|
|
8058
8415
|
spinner.start(`Creating shared component: ${componentName}...`);
|
|
8059
|
-
const { createAIProvider: createAIProvider2 } = await import("./ai-provider-
|
|
8416
|
+
const { createAIProvider: createAIProvider2 } = await import("./ai-provider-CGSIYFZT.js");
|
|
8060
8417
|
const { generateSharedComponent: generateSharedComponent7 } = await import("@getcoherent/core");
|
|
8061
8418
|
const { autoFixCode: autoFixCode2 } = await import("./quality-validator-3K5BMJSR.js");
|
|
8062
8419
|
const { extractPropsInterface, extractDependencies } = await import("./component-extractor-VYJLT5NR.js");
|
|
@@ -8102,7 +8459,7 @@ Return JSON: { "requests": [{ "type": "add-page", "changes": { "name": "${compon
|
|
|
8102
8459
|
type: classifications[0].type,
|
|
8103
8460
|
description: classifications[0].description || message
|
|
8104
8461
|
});
|
|
8105
|
-
await
|
|
8462
|
+
await saveManifest3(projectRoot, manifest2);
|
|
8106
8463
|
}
|
|
8107
8464
|
} catch {
|
|
8108
8465
|
}
|
|
@@ -8171,7 +8528,7 @@ Return JSON: { "requests": [{ "type": "add-page", "changes": { "name": "${compon
|
|
|
8171
8528
|
if (validShared.length !== manifest.shared.length) {
|
|
8172
8529
|
const cleaned = manifest.shared.length - validShared.length;
|
|
8173
8530
|
manifest = { ...manifest, shared: validShared };
|
|
8174
|
-
await
|
|
8531
|
+
await saveManifest3(project.root, manifest);
|
|
8175
8532
|
if (DEBUG4) {
|
|
8176
8533
|
console.log(chalk13.dim(`[pre-gen] Cleaned ${cleaned} orphaned component(s) from manifest`));
|
|
8177
8534
|
}
|
|
@@ -8184,7 +8541,7 @@ Return JSON: { "requests": [{ "type": "add-page", "changes": { "name": "${compon
|
|
|
8184
8541
|
let uxRecommendations;
|
|
8185
8542
|
const SPLIT_THRESHOLD = 4;
|
|
8186
8543
|
const parseOpts = { sharedComponentsSummary, projectRoot };
|
|
8187
|
-
const modCtx = { config:
|
|
8544
|
+
const modCtx = { config: dsm.getConfig(), componentManager: cm };
|
|
8188
8545
|
const multiPageHint = /\b(pages?|sections?)\s*[:]\s*\w/i.test(message) || (message.match(
|
|
8189
8546
|
/\b(?:registration|about|catal|account|contact|pricing|dashboard|settings|login|sign.?up|blog|portfolio|features)\b/gi
|
|
8190
8547
|
) || []).length >= SPLIT_THRESHOLD;
|
|
@@ -8194,7 +8551,7 @@ Return JSON: { "requests": [{ "type": "add-page", "changes": { "name": "${compon
|
|
|
8194
8551
|
requests = splitResult.requests;
|
|
8195
8552
|
if (splitResult.plan && projectRoot) {
|
|
8196
8553
|
savePlan(projectRoot, splitResult.plan);
|
|
8197
|
-
await ensurePlanGroupLayouts(projectRoot, splitResult.plan);
|
|
8554
|
+
await ensurePlanGroupLayouts(projectRoot, splitResult.plan, storedHashes, dsm.getConfig());
|
|
8198
8555
|
}
|
|
8199
8556
|
uxRecommendations = void 0;
|
|
8200
8557
|
} catch {
|
|
@@ -8233,8 +8590,24 @@ Return JSON: { "requests": [{ "type": "add-page", "changes": { "name": "${compon
|
|
|
8233
8590
|
}
|
|
8234
8591
|
}
|
|
8235
8592
|
} else {
|
|
8593
|
+
let reusePlanDirective;
|
|
8236
8594
|
try {
|
|
8237
|
-
const
|
|
8595
|
+
const singlePageManifest = await loadManifest8(projectRoot);
|
|
8596
|
+
if (singlePageManifest.shared.length > 0) {
|
|
8597
|
+
const reusePlan = buildReusePlan({
|
|
8598
|
+
pageName: "page",
|
|
8599
|
+
pageType: inferPageTypeFromRoute("/"),
|
|
8600
|
+
sections: [],
|
|
8601
|
+
manifest: singlePageManifest,
|
|
8602
|
+
existingPageCode: {},
|
|
8603
|
+
userRequest: message
|
|
8604
|
+
});
|
|
8605
|
+
reusePlanDirective = buildReusePlanDirective(reusePlan) || void 0;
|
|
8606
|
+
}
|
|
8607
|
+
} catch {
|
|
8608
|
+
}
|
|
8609
|
+
try {
|
|
8610
|
+
const result = await parseModification(message, modCtx, provider, { ...parseOpts, reusePlanDirective });
|
|
8238
8611
|
requests = result.requests;
|
|
8239
8612
|
uxRecommendations = result.uxRecommendations;
|
|
8240
8613
|
const pagesWithoutCode = requests.filter(
|
|
@@ -8272,7 +8645,7 @@ Return JSON: { "requests": [{ "type": "add-page", "changes": { "name": "${compon
|
|
|
8272
8645
|
requests = splitResult.requests;
|
|
8273
8646
|
if (splitResult.plan && projectRoot) {
|
|
8274
8647
|
savePlan(projectRoot, splitResult.plan);
|
|
8275
|
-
await ensurePlanGroupLayouts(projectRoot, splitResult.plan);
|
|
8648
|
+
await ensurePlanGroupLayouts(projectRoot, splitResult.plan, storedHashes, dsm.getConfig());
|
|
8276
8649
|
}
|
|
8277
8650
|
uxRecommendations = void 0;
|
|
8278
8651
|
} catch {
|
|
@@ -8798,7 +9171,7 @@ Return JSON: { "requests": [{ "type": "add-page", "changes": { "name": "${compon
|
|
|
8798
9171
|
}
|
|
8799
9172
|
}
|
|
8800
9173
|
if (manifestChanged) {
|
|
8801
|
-
await
|
|
9174
|
+
await saveManifest3(projectRoot, currentManifest);
|
|
8802
9175
|
if (DEBUG4) console.log(chalk13.dim("[auto-sync] Manifest updated"));
|
|
8803
9176
|
}
|
|
8804
9177
|
} catch {
|
|
@@ -8969,7 +9342,7 @@ import { DesignSystemManager as DesignSystemManager8, ComponentGenerator as Comp
|
|
|
8969
9342
|
// src/utils/file-watcher.ts
|
|
8970
9343
|
import { readFileSync as readFileSync14, writeFileSync as writeFileSync9, existsSync as existsSync19 } from "fs";
|
|
8971
9344
|
import { relative as relative4, join as join13 } from "path";
|
|
8972
|
-
import { loadManifest as loadManifest9, saveManifest as
|
|
9345
|
+
import { loadManifest as loadManifest9, saveManifest as saveManifest4 } from "@getcoherent/core";
|
|
8973
9346
|
|
|
8974
9347
|
// src/utils/component-integrity.ts
|
|
8975
9348
|
import { existsSync as existsSync18, readFileSync as readFileSync13, readdirSync as readdirSync5 } from "fs";
|
|
@@ -9357,7 +9730,7 @@ async function handleFileDelete(projectRoot, filePath) {
|
|
|
9357
9730
|
...manifest,
|
|
9358
9731
|
shared: manifest.shared.filter((s) => s.id !== orphaned.id)
|
|
9359
9732
|
};
|
|
9360
|
-
await
|
|
9733
|
+
await saveManifest4(projectRoot, cleaned);
|
|
9361
9734
|
console.log(chalk33.cyan(`
|
|
9362
9735
|
\u{1F5D1} Auto-removed ${orphaned.id} (${orphaned.name}) \u2014 file deleted`));
|
|
9363
9736
|
await writeCursorRules(projectRoot);
|
|
@@ -9594,6 +9967,41 @@ async function fixMissingComponentExports(projectRoot) {
|
|
|
9594
9967
|
}
|
|
9595
9968
|
}
|
|
9596
9969
|
}
|
|
9970
|
+
const neededSharedExports = /* @__PURE__ */ new Map();
|
|
9971
|
+
for (const file of pages) {
|
|
9972
|
+
const content = readFileSync15(file, "utf-8");
|
|
9973
|
+
const sharedImportRe = /import\s*\{([^}]+)\}\s*from\s*['"]@\/components\/shared\/([^'"]+)['"]/g;
|
|
9974
|
+
let sm;
|
|
9975
|
+
while ((sm = sharedImportRe.exec(content)) !== null) {
|
|
9976
|
+
const names = sm[1].split(",").map((s) => s.trim()).filter(Boolean);
|
|
9977
|
+
const componentId = sm[2];
|
|
9978
|
+
if (!neededSharedExports.has(componentId)) neededSharedExports.set(componentId, /* @__PURE__ */ new Set());
|
|
9979
|
+
for (const name of names) neededSharedExports.get(componentId).add(name);
|
|
9980
|
+
}
|
|
9981
|
+
}
|
|
9982
|
+
for (const [componentId, needed] of neededSharedExports) {
|
|
9983
|
+
const componentFile = join14(sharedDir, `${componentId}.tsx`);
|
|
9984
|
+
if (!existsSync20(componentFile)) continue;
|
|
9985
|
+
let content = readFileSync15(componentFile, "utf-8");
|
|
9986
|
+
const exportRe = /export\s+(?:const|function|class)\s+(\w+)|export\s*\{([^}]+)\}/g;
|
|
9987
|
+
const existingExports = /* @__PURE__ */ new Set();
|
|
9988
|
+
let em;
|
|
9989
|
+
while ((em = exportRe.exec(content)) !== null) {
|
|
9990
|
+
if (em[1]) existingExports.add(em[1]);
|
|
9991
|
+
if (em[2])
|
|
9992
|
+
em[2].split(",").map(
|
|
9993
|
+
(s) => s.trim().split(/\s+as\s+/).pop()
|
|
9994
|
+
).filter(Boolean).forEach((n) => existingExports.add(n));
|
|
9995
|
+
}
|
|
9996
|
+
const missing = [...needed].filter((n) => !existingExports.has(n));
|
|
9997
|
+
if (missing.length === 0) continue;
|
|
9998
|
+
const defaultExportMatch = content.match(/export\s+default\s+function\s+(\w+)/);
|
|
9999
|
+
if (defaultExportMatch && missing.includes(defaultExportMatch[1])) {
|
|
10000
|
+
content = content.replace(/export\s+default\s+function\s+(\w+)/, "export function $1");
|
|
10001
|
+
writeFileSync10(componentFile, content, "utf-8");
|
|
10002
|
+
console.log(chalk14.dim(` \u2714 Fixed export in ${componentId}.tsx (default \u2192 named)`));
|
|
10003
|
+
}
|
|
10004
|
+
}
|
|
9597
10005
|
}
|
|
9598
10006
|
async function backfillPageAnalysis(projectRoot) {
|
|
9599
10007
|
const configPath = join14(projectRoot, "design-system.config.ts");
|
|
@@ -10318,7 +10726,7 @@ import {
|
|
|
10318
10726
|
PageManager as PageManager4,
|
|
10319
10727
|
ComponentGenerator as ComponentGenerator4,
|
|
10320
10728
|
loadManifest as loadManifest10,
|
|
10321
|
-
saveManifest as
|
|
10729
|
+
saveManifest as saveManifest5
|
|
10322
10730
|
} from "@getcoherent/core";
|
|
10323
10731
|
function extractComponentIdsFromCode2(code) {
|
|
10324
10732
|
const ids = /* @__PURE__ */ new Set();
|
|
@@ -10587,7 +10995,7 @@ async function fixCommand(opts = {}) {
|
|
|
10587
10995
|
manifestModified = true;
|
|
10588
10996
|
}
|
|
10589
10997
|
if (manifestModified && !dryRun) {
|
|
10590
|
-
await
|
|
10998
|
+
await saveManifest5(project.root, manifest);
|
|
10591
10999
|
fixes.push("Shared component manifest updated");
|
|
10592
11000
|
}
|
|
10593
11001
|
for (const entry of manifest.shared) {
|
|
@@ -11124,10 +11532,15 @@ function createComponentsCommand() {
|
|
|
11124
11532
|
});
|
|
11125
11533
|
console.log(chalk25.cyan("\u{1F4A1} Modify by ID:"), chalk25.white('coherent chat "in CID-001 add a search button"\n'));
|
|
11126
11534
|
});
|
|
11127
|
-
sharedCmd.command("add <name>").description("Create a shared component (layout/section/widget) and register in manifest").option(
|
|
11535
|
+
sharedCmd.command("add <name>").description("Create a shared component (layout/section/widget) and register in manifest").option(
|
|
11536
|
+
"-t, --type <type>",
|
|
11537
|
+
"Type: layout | navigation | data-display | form | feedback | section | widget",
|
|
11538
|
+
"layout"
|
|
11539
|
+
).option("-d, --description <desc>", "Description").action(async (name, opts) => {
|
|
11128
11540
|
const project = findConfig();
|
|
11129
11541
|
if (!project) exitNotCoherent();
|
|
11130
|
-
const
|
|
11542
|
+
const validTypes = ["layout", "navigation", "data-display", "form", "feedback", "section", "widget"];
|
|
11543
|
+
const type = validTypes.includes(opts.type ?? "") ? opts.type : "layout";
|
|
11131
11544
|
const result = await generateSharedComponent5(project.root, {
|
|
11132
11545
|
name: name.trim(),
|
|
11133
11546
|
type,
|
|
@@ -11803,7 +12216,7 @@ import { existsSync as existsSync27, readFileSync as readFileSync19 } from "fs";
|
|
|
11803
12216
|
import { join as join20, relative as relative5, dirname as dirname10 } from "path";
|
|
11804
12217
|
import { readdir as readdir4, readFile as readFile7 } from "fs/promises";
|
|
11805
12218
|
import { DesignSystemManager as DesignSystemManager16 } from "@getcoherent/core";
|
|
11806
|
-
import { loadManifest as loadManifest13, saveManifest as
|
|
12219
|
+
import { loadManifest as loadManifest13, saveManifest as saveManifest6, findSharedComponent } from "@getcoherent/core";
|
|
11807
12220
|
function extractTokensFromProject(projectRoot) {
|
|
11808
12221
|
const lightColors = {};
|
|
11809
12222
|
const darkColors = {};
|
|
@@ -12099,7 +12512,7 @@ async function syncCommand(options = {}) {
|
|
|
12099
12512
|
const { manifest: reconciledManifest, result: rr } = reconcileComponents(project.root, manifest);
|
|
12100
12513
|
reconcileResult = rr;
|
|
12101
12514
|
if (!dryRun) {
|
|
12102
|
-
await
|
|
12515
|
+
await saveManifest6(project.root, reconciledManifest);
|
|
12103
12516
|
}
|
|
12104
12517
|
detectedComponents = await detectCustomComponents(project.root, allPageCode);
|
|
12105
12518
|
for (const comp of detectedComponents) {
|
|
@@ -365,7 +365,7 @@ Rules:
|
|
|
365
365
|
- Do NOT use these names (already shared): ${existingSharedNames.join(", ")}
|
|
366
366
|
- Look for: cards with icon+title+description, pricing tiers, testimonial blocks, stat displays, CTA sections
|
|
367
367
|
|
|
368
|
-
Each component object: "name" (PascalCase), "type" ("section"|"widget"), "description", "propsInterface", "code" (full TSX module as string)
|
|
368
|
+
Each component object: "name" (PascalCase), "type" ("layout"|"navigation"|"data-display"|"form"|"feedback"|"section"|"widget"), "description", "propsInterface", "code" (full TSX module as string)
|
|
369
369
|
|
|
370
370
|
If no repeating patterns found: { "components": [] }`
|
|
371
371
|
}
|
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.15",
|
|
7
7
|
"description": "CLI interface for Coherent Design Method",
|
|
8
8
|
"type": "module",
|
|
9
9
|
"main": "./dist/index.js",
|
|
@@ -33,8 +33,15 @@
|
|
|
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
|
+
},
|
|
36
42
|
"dependencies": {
|
|
37
43
|
"@anthropic-ai/sdk": "^0.32.0",
|
|
44
|
+
"@getcoherent/core": "workspace:*",
|
|
38
45
|
"chalk": "^5.3.0",
|
|
39
46
|
"chokidar": "^4.0.1",
|
|
40
47
|
"commander": "^11.1.0",
|
|
@@ -42,19 +49,12 @@
|
|
|
42
49
|
"open": "^10.1.0",
|
|
43
50
|
"ora": "^7.0.1",
|
|
44
51
|
"prompts": "^2.4.2",
|
|
45
|
-
"zod": "^3.22.4"
|
|
46
|
-
"@getcoherent/core": "0.6.14"
|
|
52
|
+
"zod": "^3.22.4"
|
|
47
53
|
},
|
|
48
54
|
"devDependencies": {
|
|
49
55
|
"@types/node": "^20.11.0",
|
|
50
56
|
"@types/prompts": "^2.4.9",
|
|
51
57
|
"tsup": "^8.0.1",
|
|
52
58
|
"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
|
+
}
|
package/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
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.
|