@decantr/cli 1.3.0 → 1.5.0

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,2249 @@
1
+ // src/scaffold.ts
2
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, appendFileSync } from "fs";
3
+ import { join, dirname } from "path";
4
+ import { fileURLToPath } from "url";
5
+ var __dirname = dirname(fileURLToPath(import.meta.url));
6
+ function composeArchetypes(composeEntries, archetypeResults) {
7
+ if (composeEntries.length === 0) {
8
+ return {
9
+ pages: [{ id: "home", layout: ["hero"] }],
10
+ features: [],
11
+ defaultShell: "sidebar-main"
12
+ };
13
+ }
14
+ const allPages = [];
15
+ const allFeatures = [];
16
+ let defaultShell = "sidebar-main";
17
+ for (let i = 0; i < composeEntries.length; i++) {
18
+ const entry = composeEntries[i];
19
+ const archetypeId = typeof entry === "string" ? entry : entry.archetype;
20
+ const data = archetypeResults.get(archetypeId);
21
+ if (!data?.pages) continue;
22
+ const isPrimary = i === 0;
23
+ if (isPrimary) {
24
+ defaultShell = data.pages[0]?.shell || defaultShell;
25
+ for (const page of data.pages) {
26
+ allPages.push({
27
+ id: page.id,
28
+ layout: page.default_layout?.length ? page.default_layout : ["hero"],
29
+ ...page.shell !== defaultShell ? { shell_override: page.shell } : {}
30
+ });
31
+ }
32
+ } else {
33
+ const prefix = typeof entry === "string" ? entry : entry.prefix;
34
+ for (const page of data.pages) {
35
+ allPages.push({
36
+ id: `${prefix}-${page.id}`,
37
+ layout: page.default_layout?.length ? page.default_layout : ["hero"],
38
+ ...page.shell !== defaultShell ? { shell_override: page.shell } : {}
39
+ });
40
+ }
41
+ }
42
+ if (data.features) {
43
+ allFeatures.push(...data.features);
44
+ }
45
+ }
46
+ if (allPages.length === 0) {
47
+ allPages.push({ id: "home", layout: ["hero"] });
48
+ }
49
+ return {
50
+ pages: allPages,
51
+ features: [...new Set(allFeatures)],
52
+ defaultShell
53
+ };
54
+ }
55
+ function composeSections(composeEntries, archetypeResults, overrides) {
56
+ if (composeEntries.length === 0) {
57
+ return {
58
+ sections: [{
59
+ id: "default",
60
+ role: "primary",
61
+ shell: "sidebar-main",
62
+ features: [],
63
+ description: "Default section",
64
+ pages: [{ id: "home", layout: ["hero"] }]
65
+ }],
66
+ features: [],
67
+ defaultShell: "sidebar-main"
68
+ };
69
+ }
70
+ const sections = [];
71
+ const allFeatures = [];
72
+ let defaultShell = "sidebar-main";
73
+ const pagesRemoveSet = new Set(overrides?.pages_remove ?? []);
74
+ for (let i = 0; i < composeEntries.length; i++) {
75
+ const entry = composeEntries[i];
76
+ const archetypeId = typeof entry === "string" ? entry : entry.archetype;
77
+ const data = archetypeResults.get(archetypeId);
78
+ if (!data?.pages) continue;
79
+ const isPrimary = i === 0;
80
+ if (isPrimary) {
81
+ defaultShell = data.pages[0]?.shell || defaultShell;
82
+ }
83
+ const pages = [];
84
+ for (const page of data.pages) {
85
+ if (pagesRemoveSet.has(page.id)) continue;
86
+ const overriddenPage = overrides?.pages?.[page.id];
87
+ pages.push({
88
+ id: page.id,
89
+ layout: page.default_layout?.length ? page.default_layout : ["hero"],
90
+ ...overriddenPage
91
+ });
92
+ }
93
+ sections.push({
94
+ id: archetypeId,
95
+ role: data.role ?? "primary",
96
+ shell: data.pages[0]?.shell || "sidebar-main",
97
+ features: data.features ?? [],
98
+ description: data.description ?? "",
99
+ pages
100
+ });
101
+ if (data.features) {
102
+ allFeatures.push(...data.features);
103
+ }
104
+ }
105
+ if (sections.length === 0) {
106
+ sections.push({
107
+ id: "default",
108
+ role: "primary",
109
+ shell: "sidebar-main",
110
+ features: [],
111
+ description: "Default section",
112
+ pages: [{ id: "home", layout: ["hero"] }]
113
+ });
114
+ }
115
+ if (overrides?.section_dna_overrides) {
116
+ for (const section of sections) {
117
+ const sectionOverrides = overrides.section_dna_overrides[section.id];
118
+ if (sectionOverrides) {
119
+ section.dna_overrides = { ...section.dna_overrides, ...sectionOverrides };
120
+ }
121
+ }
122
+ }
123
+ const primaryShell = sections.find((s) => s.role === "primary")?.shell || defaultShell;
124
+ for (const section of sections) {
125
+ if (section.shell === "inherit") {
126
+ section.shell = primaryShell;
127
+ }
128
+ }
129
+ let features = [...new Set(allFeatures)];
130
+ if (overrides?.features_add) {
131
+ for (const f of overrides.features_add) {
132
+ if (!features.includes(f)) features.push(f);
133
+ }
134
+ }
135
+ if (overrides?.features_remove) {
136
+ const removeSet = new Set(overrides.features_remove);
137
+ features = features.filter((f) => !removeSet.has(f));
138
+ }
139
+ return { sections, features, defaultShell };
140
+ }
141
+ var ZONE_ORDER = ["public", "gateway", "primary", "auxiliary"];
142
+ function deriveZones(inputs) {
143
+ const zoneMap = /* @__PURE__ */ new Map();
144
+ for (const input of inputs) {
145
+ const existing = zoneMap.get(input.role);
146
+ if (existing) {
147
+ existing.archetypes.push(input.archetypeId);
148
+ existing.features.push(...input.features);
149
+ existing.descriptions.push(input.description);
150
+ } else {
151
+ zoneMap.set(input.role, {
152
+ role: input.role,
153
+ archetypes: [input.archetypeId],
154
+ shell: input.shell,
155
+ features: [...input.features],
156
+ descriptions: [input.description]
157
+ });
158
+ }
159
+ }
160
+ for (const zone of zoneMap.values()) {
161
+ zone.features = [...new Set(zone.features)];
162
+ }
163
+ return ZONE_ORDER.filter((role) => zoneMap.has(role)).map((role) => zoneMap.get(role));
164
+ }
165
+ var GATEWAY_TRIGGER_MAP = {
166
+ auth: "authentication",
167
+ login: "authentication",
168
+ mfa: "authentication",
169
+ payment: "payment",
170
+ subscription: "payment",
171
+ checkout: "payment",
172
+ onboarding: "onboarding",
173
+ "setup-wizard": "onboarding",
174
+ welcome: "onboarding",
175
+ invite: "invitation",
176
+ "access-code": "invitation"
177
+ };
178
+ function resolveGatewayTrigger(features) {
179
+ for (const feature of features) {
180
+ const trigger = GATEWAY_TRIGGER_MAP[feature];
181
+ if (trigger) return trigger;
182
+ }
183
+ return "authentication";
184
+ }
185
+ function deriveTransitions(zones) {
186
+ const transitions = [];
187
+ const roles = new Set(zones.map((z) => z.role));
188
+ const gateway = zones.find((z) => z.role === "gateway");
189
+ const gatewayTrigger = gateway ? resolveGatewayTrigger(gateway.features) : "authentication";
190
+ const hasApp = roles.has("primary") || roles.has("auxiliary");
191
+ const hasGateway = roles.has("gateway");
192
+ const hasPublic = roles.has("public");
193
+ if (hasPublic && hasGateway) {
194
+ transitions.push({ from: "public", to: "gateway", type: "conversion", trigger: gatewayTrigger });
195
+ }
196
+ if (hasPublic && hasApp && !hasGateway) {
197
+ transitions.push({ from: "public", to: "app", type: "conversion", trigger: "navigation" });
198
+ }
199
+ if (hasGateway && hasApp) {
200
+ transitions.push({ from: "gateway", to: "app", type: "gate-pass", trigger: gatewayTrigger });
201
+ transitions.push({ from: "app", to: "gateway", type: "gate-return", trigger: gatewayTrigger });
202
+ }
203
+ if (hasApp && hasPublic) {
204
+ transitions.push({ from: "app", to: "public", type: "navigation", trigger: "external" });
205
+ }
206
+ return transitions;
207
+ }
208
+ var ZONE_LABELS = {
209
+ public: "Public",
210
+ gateway: "Gateway",
211
+ primary: "App",
212
+ auxiliary: "App (auxiliary)"
213
+ };
214
+ function generateTopologySection(data, personality) {
215
+ const lines = [];
216
+ lines.push("## Composition Topology");
217
+ lines.push("");
218
+ lines.push(`**Intent:** ${data.intent}`);
219
+ lines.push("");
220
+ lines.push("### Zones");
221
+ lines.push("");
222
+ for (const zone of data.zones) {
223
+ const label = ZONE_LABELS[zone.role] || zone.role;
224
+ lines.push(`**${label}** \u2014 ${zone.shell} shell`);
225
+ lines.push(` Archetypes: ${zone.archetypes.join(", ")}`);
226
+ lines.push(` Purpose: ${zone.descriptions.join(" ")}`);
227
+ if (personality.length > 0) {
228
+ lines.push(` Tone: ${personality.join(", ")}`);
229
+ }
230
+ if (zone.features.length > 0) {
231
+ lines.push(` Features: ${zone.features.join(", ")}`);
232
+ }
233
+ lines.push("");
234
+ }
235
+ if (data.transitions.length > 0) {
236
+ lines.push("### Zone Transitions");
237
+ lines.push("");
238
+ for (const t of data.transitions) {
239
+ const fromLabel = t.from.charAt(0).toUpperCase() + t.from.slice(1);
240
+ const toLabel = t.to.charAt(0).toUpperCase() + t.to.slice(1);
241
+ lines.push(` ${fromLabel} \u2192 ${toLabel}: ${t.type} (${t.trigger})`);
242
+ }
243
+ lines.push("");
244
+ }
245
+ lines.push("### Default Entry Points");
246
+ lines.push("");
247
+ lines.push(` Anonymous users enter: ${data.entryPoints.anonymous}`);
248
+ lines.push(` Authenticated users enter: ${data.entryPoints.authenticated}`);
249
+ lines.push(` Auth redirect target: ${data.entryPoints.authenticated}`);
250
+ lines.push("");
251
+ return lines.join("\n");
252
+ }
253
+ var CLI_VERSION = "1.0.0";
254
+ function generateTokensCSS(themeData, mode) {
255
+ if (!themeData) {
256
+ return `/* No theme data available */
257
+ :root {
258
+ --d-primary: #6366f1;
259
+ --d-secondary: #a1a1aa;
260
+ --d-accent: #f59e0b;
261
+ --d-bg: #18181b;
262
+ --d-surface: #1f1f23;
263
+ --d-surface-raised: #27272a;
264
+ --d-border: #3f3f46;
265
+ --d-text: #fafafa;
266
+ --d-text-muted: #a1a1aa;
267
+ }
268
+ `;
269
+ }
270
+ const seed = themeData.seed || {};
271
+ const palette = themeData.palette || {};
272
+ const resolvedMode = mode === "auto" ? "dark" : mode;
273
+ function buildTokens(tokenMode) {
274
+ return {
275
+ // Seed colors
276
+ "--d-primary": seed.primary || "#6366f1",
277
+ "--d-secondary": seed.secondary || "#a1a1aa",
278
+ "--d-accent": seed.accent || "#f59e0b",
279
+ // Palette colors (mode-aware)
280
+ "--d-bg": palette.background?.[tokenMode] || "#18181b",
281
+ "--d-surface": palette.surface?.[tokenMode] || "#1f1f23",
282
+ "--d-surface-raised": palette["surface-raised"]?.[tokenMode] || "#27272a",
283
+ "--d-border": palette.border?.[tokenMode] || "#3f3f46",
284
+ "--d-text": palette.text?.[tokenMode] || "#fafafa",
285
+ "--d-text-muted": palette["text-muted"]?.[tokenMode] || "#a1a1aa",
286
+ "--d-primary-hover": palette["primary-hover"]?.[tokenMode] || seed.primary || "#6366f1",
287
+ // Spacing scale
288
+ "--d-gap-1": "0.25rem",
289
+ "--d-gap-2": "0.5rem",
290
+ "--d-gap-3": "0.75rem",
291
+ "--d-gap-4": "1rem",
292
+ "--d-gap-6": "1.5rem",
293
+ "--d-gap-8": "2rem",
294
+ "--d-gap-12": "3rem",
295
+ // Radii
296
+ "--d-radius": "0.5rem",
297
+ "--d-radius-sm": "0.25rem",
298
+ "--d-radius-lg": "0.75rem",
299
+ "--d-radius-xl": "1rem",
300
+ "--d-radius-full": "9999px",
301
+ // Shadows
302
+ "--d-shadow-sm": "0 1px 2px rgba(0,0,0,0.05)",
303
+ "--d-shadow": "0 1px 3px rgba(0,0,0,0.1)",
304
+ "--d-shadow-md": "0 4px 6px rgba(0,0,0,0.1)",
305
+ "--d-shadow-lg": "0 10px 15px rgba(0,0,0,0.1)",
306
+ // Status colors
307
+ "--d-success": themeData.tokens?.base?.success || "#22c55e",
308
+ "--d-error": themeData.tokens?.base?.danger || "#ef4444",
309
+ "--d-warning": themeData.tokens?.base?.warning || "#f59e0b",
310
+ "--d-info": "#3b82f6"
311
+ };
312
+ }
313
+ const tokens = buildTokens(resolvedMode);
314
+ const lines = Object.entries(tokens).map(([key, value]) => ` ${key}: ${value};`).join("\n");
315
+ let css = `/* Generated by @decantr/cli */
316
+ :root {
317
+ ${lines}
318
+ }
319
+ `;
320
+ if (mode === "auto") {
321
+ const lightTokens = buildTokens("light");
322
+ const paletteKeys = ["--d-bg", "--d-surface", "--d-surface-raised", "--d-border", "--d-text", "--d-text-muted", "--d-primary-hover"];
323
+ const lightLines = Object.entries(lightTokens).filter(([key]) => paletteKeys.includes(key)).map(([key, value]) => ` ${key}: ${value};`).join("\n");
324
+ css += `
325
+ @media (prefers-color-scheme: light) {
326
+ :root {
327
+ ${lightLines}
328
+ }
329
+ }
330
+ `;
331
+ }
332
+ return css;
333
+ }
334
+ function generateDecoratorsCSS(recipeData, themeName) {
335
+ if (!recipeData?.decorators) {
336
+ return `/* No recipe decorators available */`;
337
+ }
338
+ const decorators = recipeData.decorators;
339
+ const css = [
340
+ `/* Generated by @decantr/cli from recipe: ${themeName} */`,
341
+ ""
342
+ ];
343
+ for (const [name, description] of Object.entries(decorators)) {
344
+ css.push(generateDecoratorRule(name, description));
345
+ css.push("");
346
+ }
347
+ css.push(`/* Animation keyframes */
348
+ @keyframes decantr-fade-in {
349
+ from { opacity: 0; transform: translateY(8px); }
350
+ to { opacity: 1; transform: translateY(0); }
351
+ }
352
+
353
+ @keyframes decantr-pulse {
354
+ 0%, 100% { opacity: 1; }
355
+ 50% { opacity: 0.5; }
356
+ }
357
+ `);
358
+ return css.join("\n");
359
+ }
360
+ function generateDecoratorsContext(recipeData, recipeName) {
361
+ const lines = [];
362
+ lines.push(`# Recipe Decorators: ${recipeName}`);
363
+ lines.push("");
364
+ lines.push("## Available Classes");
365
+ lines.push("");
366
+ if (recipeData?.decorators && Object.keys(recipeData.decorators).length > 0) {
367
+ lines.push("| Decorator | Description |");
368
+ lines.push("|-----------|-------------|");
369
+ for (const [name, description] of Object.entries(recipeData.decorators)) {
370
+ lines.push(`| ${name} | ${description} |`);
371
+ }
372
+ } else {
373
+ lines.push("No decorators defined.");
374
+ }
375
+ lines.push("");
376
+ lines.push("## Usage");
377
+ lines.push("");
378
+ lines.push("Decorators are plain CSS class names from `src/styles/decorators.css`. Combine with atoms:");
379
+ lines.push("");
380
+ lines.push("```tsx");
381
+ lines.push("<div className={css('_flex _col _gap4') + ' " + recipeName + "-card'}>");
382
+ lines.push(" <pre className={css('_p3') + ' " + recipeName + "-code'}>{code}</pre>");
383
+ lines.push("</div>");
384
+ lines.push("```");
385
+ lines.push("");
386
+ lines.push("Atoms use `css()` function. Decorators are plain class strings. Combined via string concatenation.");
387
+ lines.push("");
388
+ return lines.join("\n");
389
+ }
390
+ function generateDecoratorRule(name, description) {
391
+ const rules = [];
392
+ const descLower = description.toLowerCase();
393
+ if (descLower.includes("monospace") || descLower.includes("mono font")) {
394
+ rules.push("font-family: ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, Consolas, 'DejaVu Sans Mono', monospace");
395
+ }
396
+ if (descLower.includes("surface-raised") || descLower.includes("surface raised")) {
397
+ rules.push("background: var(--d-surface-raised)");
398
+ } else if (descLower.includes("surface background") || descLower.includes("surface elevation")) {
399
+ rules.push("background: var(--d-surface)");
400
+ } else if (descLower.includes("background") && descLower.includes("theme")) {
401
+ rules.push("background: var(--d-bg)");
402
+ } else if (descLower.includes("primary-tinted") || descLower.includes("primary background")) {
403
+ rules.push("background: color-mix(in srgb, var(--d-primary) 15%, var(--d-surface))");
404
+ }
405
+ const leftBorderMatch = descLower.match(/(\d+)px\s+left\s+border/);
406
+ if (leftBorderMatch) {
407
+ rules.push(`border-left: ${leftBorderMatch[1]}px solid var(--d-primary)`);
408
+ } else if (descLower.includes("left border")) {
409
+ rules.push("border-left: 3px solid var(--d-primary)");
410
+ } else if (descLower.includes("1px border") || descLower.includes("subtle border")) {
411
+ rules.push("border: 1px solid var(--d-border)");
412
+ } else if (descLower.includes("border") && !descLower.includes("radius")) {
413
+ rules.push("border: 1px solid var(--d-border)");
414
+ }
415
+ const radiusMatch = descLower.match(/(\d+)px radius/);
416
+ if (radiusMatch) {
417
+ rules.push(`border-radius: ${radiusMatch[1]}px`);
418
+ } else if (descLower.includes("radius") || descLower.includes("rounded")) {
419
+ rules.push("border-radius: var(--d-radius)");
420
+ }
421
+ if (descLower.includes("hover shadow") || descLower.includes("shadow transition")) {
422
+ rules.push("transition: box-shadow 0.15s ease");
423
+ }
424
+ if (descLower.includes("elevation") || descLower.includes("shadow")) {
425
+ rules.push("box-shadow: var(--d-shadow)");
426
+ }
427
+ if (descLower.includes("entrance animation") || descLower.includes("fade")) {
428
+ rules.push("animation: decantr-fade-in 0.2s ease-out");
429
+ }
430
+ if (descLower.includes("pulse animation") || descLower.includes("skeleton")) {
431
+ rules.push("animation: decantr-pulse 1.5s ease-in-out infinite");
432
+ }
433
+ if (descLower.includes("blur") || descLower.includes("glass")) {
434
+ rules.push("backdrop-filter: blur(8px)");
435
+ }
436
+ if (descLower.includes("right-aligned")) {
437
+ rules.push("margin-left: auto");
438
+ } else if (descLower.includes("left-aligned")) {
439
+ rules.push("margin-right: auto");
440
+ }
441
+ if (descLower.includes("message bubble") || descLower.includes("bubble")) {
442
+ rules.push("padding: var(--d-gap-3) var(--d-gap-4)");
443
+ rules.push("border-radius: var(--d-radius-lg)");
444
+ rules.push("max-width: 80%");
445
+ }
446
+ if (descLower.includes("monospace") || descLower.includes("code")) {
447
+ if (!rules.some((r) => r.startsWith("padding"))) {
448
+ rules.push("padding: 0.75rem 1rem");
449
+ }
450
+ if (!rules.some((r) => r.startsWith("border-radius"))) {
451
+ rules.push("border-radius: var(--d-radius-sm)");
452
+ }
453
+ rules.push("overflow-x: auto");
454
+ }
455
+ if (rules.length === 0) {
456
+ return `/* .${name}: ${description} */`;
457
+ }
458
+ return `.${name} {
459
+ ${rules.join(";\n ")};
460
+ }`;
461
+ }
462
+ function serializeLayoutItem(item) {
463
+ if (typeof item === "string") {
464
+ return item;
465
+ }
466
+ if (typeof item === "object" && item !== null) {
467
+ const obj = item;
468
+ if (typeof obj.pattern === "string") {
469
+ const preset = obj.preset ? ` (${obj.preset})` : "";
470
+ const alias = obj.as ? ` as ${obj.as}` : "";
471
+ return `${obj.pattern}${preset}${alias}`;
472
+ }
473
+ if (Array.isArray(obj.cols)) {
474
+ const cols = obj.cols.map(serializeLayoutItem).join(" | ");
475
+ const breakpoint = obj.at ? ` @${obj.at}` : "";
476
+ return `[${cols}]${breakpoint}`;
477
+ }
478
+ }
479
+ return "custom";
480
+ }
481
+ function extractPatternNames(item) {
482
+ if (typeof item === "string") {
483
+ return [item];
484
+ }
485
+ if (typeof item === "object" && item !== null) {
486
+ const obj = item;
487
+ if (typeof obj.pattern === "string") {
488
+ return [obj.pattern];
489
+ }
490
+ if (Array.isArray(obj.cols)) {
491
+ return obj.cols.flatMap(extractPatternNames);
492
+ }
493
+ }
494
+ return [];
495
+ }
496
+ function loadTemplate(name) {
497
+ const fromDist = join(__dirname, "..", "src", "templates", name);
498
+ if (existsSync(fromDist)) {
499
+ return readFileSync(fromDist, "utf-8");
500
+ }
501
+ const fromSrc = join(__dirname, "templates", name);
502
+ if (existsSync(fromSrc)) {
503
+ return readFileSync(fromSrc, "utf-8");
504
+ }
505
+ throw new Error(`Template not found: ${name}`);
506
+ }
507
+ function renderTemplate(template, vars) {
508
+ let result = template;
509
+ for (const [key, value] of Object.entries(vars)) {
510
+ result = result.replace(new RegExp(`\\{\\{${key}\\}\\}`, "g"), value);
511
+ }
512
+ return result;
513
+ }
514
+ function resolvePatternAlias(item, patterns) {
515
+ if (!patterns) return item;
516
+ if (typeof item === "string") {
517
+ const patternDef = patterns.find((p) => p.as === item);
518
+ if (patternDef) {
519
+ if (patternDef.preset) {
520
+ return { pattern: patternDef.pattern, preset: patternDef.preset };
521
+ }
522
+ return patternDef.pattern;
523
+ }
524
+ return item;
525
+ }
526
+ if (typeof item === "object" && item !== null) {
527
+ const obj = item;
528
+ if (Array.isArray(obj.cols)) {
529
+ return {
530
+ ...obj,
531
+ cols: obj.cols.map((col) => resolvePatternAlias(col, patterns))
532
+ };
533
+ }
534
+ }
535
+ return item;
536
+ }
537
+ function buildEssence(options, archetypeData) {
538
+ let structure = [
539
+ { id: "home", shell: options.shell, layout: ["hero"] }
540
+ ];
541
+ let features = options.features;
542
+ if (archetypeData?.pages) {
543
+ structure = archetypeData.pages.map((p) => {
544
+ const resolvedLayout = (p.default_layout?.length ? p.default_layout : ["hero"]).map((item) => resolvePatternAlias(item, p.patterns));
545
+ return {
546
+ id: p.id,
547
+ shell: p.shell || options.shell,
548
+ layout: resolvedLayout
549
+ };
550
+ });
551
+ }
552
+ if (archetypeData?.features) {
553
+ features = [.../* @__PURE__ */ new Set([...features, ...archetypeData.features])];
554
+ }
555
+ const contentGapMap = {
556
+ compact: "_gap2",
557
+ comfortable: "_gap4",
558
+ spacious: "_gap6"
559
+ };
560
+ const archetype = options.archetype || "custom";
561
+ const essence = {
562
+ version: "2.0.0",
563
+ archetype,
564
+ theme: {
565
+ style: options.theme,
566
+ mode: options.mode,
567
+ recipe: options.theme,
568
+ // Recipe defaults to theme
569
+ shape: options.shape
570
+ },
571
+ personality: options.personality,
572
+ platform: {
573
+ type: "spa",
574
+ routing: "hash"
575
+ },
576
+ structure,
577
+ features,
578
+ guard: {
579
+ enforce_style: true,
580
+ enforce_recipe: true,
581
+ mode: options.guard
582
+ },
583
+ density: {
584
+ level: options.density,
585
+ content_gap: contentGapMap[options.density] || "_gap4"
586
+ },
587
+ target: options.target
588
+ };
589
+ if (options.accessibility) {
590
+ essence.accessibility = options.accessibility;
591
+ }
592
+ return essence;
593
+ }
594
+ function buildEssenceV3(options, archetypeData, themeHints, recipeHints) {
595
+ let pages = [
596
+ { id: "home", layout: ["hero"] }
597
+ ];
598
+ let features = options.features;
599
+ let defaultShell = options.shell || "sidebar-main";
600
+ if (archetypeData?.pages) {
601
+ defaultShell = archetypeData.pages[0]?.shell || defaultShell;
602
+ pages = archetypeData.pages.map((p) => {
603
+ const resolvedLayout = (p.default_layout?.length ? p.default_layout : ["hero"]).map((item) => resolvePatternAlias(item, p.patterns));
604
+ return {
605
+ id: p.id,
606
+ ...p.shell !== defaultShell ? { shell_override: p.shell } : {},
607
+ layout: resolvedLayout
608
+ };
609
+ });
610
+ }
611
+ if (archetypeData?.features) {
612
+ features = [.../* @__PURE__ */ new Set([...features, ...archetypeData.features])];
613
+ }
614
+ const densityLevelMap = {
615
+ compact: "_gap2",
616
+ comfortable: "_gap4",
617
+ spacious: "_gap6"
618
+ };
619
+ const shapeRadiusMap = {
620
+ pill: 12,
621
+ rounded: 8,
622
+ sharp: 2
623
+ };
624
+ const guardModeMap = {
625
+ strict: { mode: "strict", dna_enforcement: "error", blueprint_enforcement: "warn" },
626
+ guided: { mode: "guided", dna_enforcement: "error", blueprint_enforcement: "off" },
627
+ creative: { mode: "creative", dna_enforcement: "off", blueprint_enforcement: "off" }
628
+ };
629
+ const dna = {
630
+ theme: {
631
+ style: options.theme,
632
+ mode: options.mode,
633
+ recipe: options.theme,
634
+ shape: options.shape
635
+ },
636
+ spacing: {
637
+ base_unit: 4,
638
+ scale: "linear",
639
+ density: options.density,
640
+ content_gap: densityLevelMap[options.density] || "_gap4"
641
+ },
642
+ typography: {
643
+ scale: themeHints?.typography_hints?.scale || "modular",
644
+ heading_weight: themeHints?.typography_hints?.heading_weight || 600,
645
+ body_weight: themeHints?.typography_hints?.body_weight || 400
646
+ },
647
+ color: {
648
+ palette: "semantic",
649
+ accent_count: 1,
650
+ cvd_preference: options.accessibility?.cvd_preference || "auto"
651
+ },
652
+ radius: {
653
+ philosophy: recipeHints?.radius_hints?.philosophy || options.shape,
654
+ base: recipeHints?.radius_hints?.base || shapeRadiusMap[options.shape] || 8
655
+ },
656
+ elevation: {
657
+ system: "layered",
658
+ max_levels: 3
659
+ },
660
+ motion: {
661
+ preference: recipeHints?.animation?.preference || themeHints?.motion_hints?.preference || "subtle",
662
+ duration_scale: 1,
663
+ reduce_motion: themeHints?.motion_hints?.reduce_motion_default ?? true
664
+ },
665
+ accessibility: {
666
+ wcag_level: options.accessibility?.wcag_level || "AA",
667
+ focus_visible: true,
668
+ skip_nav: true
669
+ },
670
+ personality: options.personality
671
+ };
672
+ const blueprint = {
673
+ shell: defaultShell,
674
+ pages,
675
+ features
676
+ };
677
+ const meta = {
678
+ archetype: options.archetype || "custom",
679
+ target: options.target,
680
+ platform: {
681
+ type: "spa",
682
+ routing: "hash"
683
+ },
684
+ guard: guardModeMap[options.guard] || guardModeMap.guided
685
+ };
686
+ return {
687
+ version: "3.0.0",
688
+ dna,
689
+ blueprint,
690
+ meta
691
+ };
692
+ }
693
+ var CSS_APPROACH_CONTENT = `## CSS Implementation
694
+
695
+ This project uses **@decantr/css** for layout atoms and the generated CSS files for theme tokens and recipe decorators.
696
+
697
+ ### Setup
698
+
699
+ \`\`\`javascript
700
+ // 1. Import the atoms runtime
701
+ import { css } from '@decantr/css';
702
+
703
+ // 2. Import generated CSS files (created by decantr init)
704
+ import './styles/tokens.css'; // Theme tokens (--d-primary, --d-surface, etc.)
705
+ import './styles/decorators.css'; // Recipe decorators
706
+ \`\`\`
707
+
708
+ ### Using Atoms
709
+
710
+ The \`css()\` function processes atom strings and injects CSS at runtime:
711
+
712
+ \`\`\`jsx
713
+ // Layout atoms
714
+ <div className={css('_flex _col _gap4 _p4')}>
715
+ <h1 className={css('_heading1')}>Title</h1>
716
+ <p className={css('_textsm _fgmuted')}>Description</p>
717
+ </div>
718
+
719
+ // Responsive prefixes (mobile-first)
720
+ <div className={css('_gc1 _sm:gc2 _lg:gc4')}>
721
+ {/* 1 col -> 2 cols at 640px -> 4 cols at 1024px */}
722
+ </div>
723
+ \`\`\`
724
+
725
+ ### Atom Reference
726
+
727
+ #### Display
728
+ | Atom | CSS |
729
+ |------|-----|
730
+ | \`_flex\` | \`display:flex\` |
731
+ | \`_grid\` | \`display:grid\` |
732
+ | \`_block\` | \`display:block\` |
733
+ | \`_inline\` | \`display:inline\` |
734
+ | \`_inlineflex\` | \`display:inline-flex\` |
735
+ | \`_none\` | \`display:none\` |
736
+ | \`_contents\` | \`display:contents\` |
737
+
738
+ #### Flexbox
739
+ | Atom | CSS |
740
+ |------|-----|
741
+ | \`_col\` | \`flex-direction:column\` |
742
+ | \`_row\` | \`flex-direction:row\` |
743
+ | \`_colrev\` | \`flex-direction:column-reverse\` |
744
+ | \`_wrap\` | \`flex-wrap:wrap\` |
745
+ | \`_nowrap\` | \`flex-wrap:nowrap\` |
746
+ | \`_flex1\` | \`flex:1\` |
747
+ | \`_flex0\` | \`flex:none\` |
748
+ | \`_flexauto\` | \`flex:auto\` |
749
+ | \`_grow\` | \`flex-grow:1\` |
750
+ | \`_grow0\` | \`flex-grow:0\` |
751
+ | \`_shrink0\` | \`flex-shrink:0\` |
752
+
753
+ #### Alignment
754
+ | Atom | CSS |
755
+ |------|-----|
756
+ | \`_aic\` | \`align-items:center\` |
757
+ | \`_aifs\` | \`align-items:flex-start\` |
758
+ | \`_aife\` | \`align-items:flex-end\` |
759
+ | \`_aist\` | \`align-items:stretch\` |
760
+ | \`_aibl\` | \`align-items:baseline\` |
761
+ | \`_jcc\` | \`justify-content:center\` |
762
+ | \`_jcfs\` | \`justify-content:flex-start\` |
763
+ | \`_jcfe\` | \`justify-content:flex-end\` |
764
+ | \`_jcsb\` | \`justify-content:space-between\` |
765
+ | \`_jcsa\` | \`justify-content:space-around\` |
766
+ | \`_jcse\` | \`justify-content:space-evenly\` |
767
+ | \`_pic\` | \`place-items:center\` |
768
+ | \`_pcc\` | \`place-content:center\` |
769
+
770
+ #### Spacing (scale: 0, 1, 2, 3, 4, 5, 6, 8, 10, 12, 16, 20, 24, ...)
771
+ | Atom | CSS | Notes |
772
+ |------|-----|-------|
773
+ | \`_gap{n}\` | \`gap:{scale}\` | e.g. \`_gap4\` = \`gap:1rem\` |
774
+ | \`_gx{n}\` | \`column-gap:{scale}\` | horizontal gap |
775
+ | \`_gy{n}\` | \`row-gap:{scale}\` | vertical gap |
776
+ | \`_p{n}\` | \`padding:{scale}\` | all sides |
777
+ | \`_pt{n}\`, \`_pr{n}\`, \`_pb{n}\`, \`_pl{n}\` | directional padding | top/right/bottom/left |
778
+ | \`_px{n}\` | \`padding-inline:{scale}\` | horizontal |
779
+ | \`_py{n}\` | \`padding-block:{scale}\` | vertical |
780
+ | \`_m{n}\` | \`margin:{scale}\` | same as padding variants |
781
+ | \`_mx{n}\`, \`_my{n}\` | inline/block margin | horizontal/vertical |
782
+
783
+ #### Sizing
784
+ | Atom | CSS |
785
+ |------|-----|
786
+ | \`_wfull\` / \`_w100\` | \`width:100%\` |
787
+ | \`_hfull\` / \`_h100\` | \`height:100%\` |
788
+ | \`_wscreen\` | \`width:100vw\` |
789
+ | \`_hscreen\` | \`height:100vh\` |
790
+ | \`_wfit\` | \`width:fit-content\` |
791
+ | \`_hfit\` | \`height:fit-content\` |
792
+ | \`_wauto\` | \`width:auto\` |
793
+ | \`_minw0\` | \`min-width:0\` |
794
+ | \`_minh0\` | \`min-height:0\` |
795
+ | \`_w{n}\`, \`_h{n}\` | width/height from spacing scale |
796
+ | \`_minw{n}\`, \`_maxw{n}\` | min/max width from scale |
797
+
798
+ #### Text Size
799
+ | Atom | Size | Line-height |
800
+ |------|------|-------------|
801
+ | \`_textxs\` | 0.75rem | 1rem |
802
+ | \`_textsm\` | 0.875rem | 1.25rem |
803
+ | \`_textbase\` | 1rem | 1.5rem |
804
+ | \`_textlg\` | 1.125rem | 1.75rem |
805
+ | \`_textxl\` | 1.25rem | 1.75rem |
806
+ | \`_text2xl\` | 1.5rem | 2rem |
807
+ | \`_text3xl\` | 1.875rem | 2.25rem |
808
+ | \`_heading1\`-\`_heading6\` | Heading presets (size + weight) |
809
+
810
+ #### Text Style
811
+ | Atom | CSS |
812
+ |------|-----|
813
+ | \`_fontbold\` | \`font-weight:700\` |
814
+ | \`_fontsemi\` | \`font-weight:600\` |
815
+ | \`_fontmedium\` | \`font-weight:500\` |
816
+ | \`_fontlight\` | \`font-weight:300\` |
817
+ | \`_italic\` | \`font-style:italic\` |
818
+ | \`_underline\` | \`text-decoration:underline\` |
819
+ | \`_uppercase\` | \`text-transform:uppercase\` |
820
+ | \`_truncate\` | overflow ellipsis + nowrap |
821
+ | \`_textl\`, \`_textc\`, \`_textr\` | text-align left/center/right |
822
+
823
+ #### Color (theme variable based)
824
+ | Atom | CSS |
825
+ |------|-----|
826
+ | \`_bgprimary\` | \`background:var(--d-primary)\` |
827
+ | \`_bgsurface\` | \`background:var(--d-surface)\` |
828
+ | \`_bgsurface0\`-\`_bgsurface2\` | surface elevation layers |
829
+ | \`_bgmuted\` | \`background:var(--d-muted)\` |
830
+ | \`_bgbg\` | \`background:var(--d-bg)\` |
831
+ | \`_bgsuccess\`, \`_bgerror\`, \`_bgwarning\`, \`_bginfo\` | status backgrounds |
832
+ | \`_fgprimary\` | \`color:var(--d-primary)\` |
833
+ | \`_fgtext\` | \`color:var(--d-text)\` |
834
+ | \`_fgmuted\` | \`color:var(--d-text-muted)\` |
835
+ | \`_fgsuccess\`, \`_fgerror\`, \`_fgwarning\`, \`_fginfo\` | status text |
836
+ | \`_bcborder\` | \`border-color:var(--d-border)\` |
837
+
838
+ #### Overflow & Whitespace
839
+ | Atom | CSS |
840
+ |------|-----|
841
+ | \`_overhidden\` | \`overflow:hidden\` |
842
+ | \`_overauto\` | \`overflow:auto\` |
843
+ | \`_overscroll\` | \`overflow:scroll\` |
844
+ | \`_overxauto\`, \`_overyauto\` | axis-specific overflow |
845
+ | \`_nowraptext\` | \`white-space:nowrap\` |
846
+ | \`_prewrap\` | \`white-space:pre-wrap\` |
847
+ | \`_breakword\` | \`overflow-wrap:break-word\` |
848
+
849
+ #### Cursor & Interaction
850
+ | Atom | CSS |
851
+ |------|-----|
852
+ | \`_pointer\` | \`cursor:pointer\` |
853
+ | \`_cursordefault\` | \`cursor:default\` |
854
+ | \`_notallowed\` | \`cursor:not-allowed\` |
855
+ | \`_grab\` | \`cursor:grab\` |
856
+ | \`_selectnone\` | \`user-select:none\` |
857
+ | \`_ptrnone\` | \`pointer-events:none\` |
858
+
859
+ #### Position & Layout
860
+ | Atom | CSS |
861
+ |------|-----|
862
+ | \`_rel\` | \`position:relative\` |
863
+ | \`_abs\` | \`position:absolute\` |
864
+ | \`_fixed\` | \`position:fixed\` |
865
+ | \`_sticky\` | \`position:sticky\` |
866
+ | \`_inset0\` | \`inset:0\` |
867
+ | \`_top0\`, \`_right0\`, \`_bottom0\`, \`_left0\` | edge positioning |
868
+ | \`_z10\`-\`_z50\` | z-index scale |
869
+
870
+ #### Grid
871
+ | Atom | CSS |
872
+ |------|-----|
873
+ | \`_gc1\`-\`_gc12\` | \`grid-template-columns:repeat(N,...)\` |
874
+ | \`_gr1\`-\`_gr6\` | \`grid-template-rows:repeat(N,...)\` |
875
+ | \`_span1\`-\`_span12\`, \`_spanfull\` | column span |
876
+ | \`_rowspan1\`-\`_rowspan6\` | row span |
877
+
878
+ #### Visual
879
+ | Atom | CSS |
880
+ |------|-----|
881
+ | \`_rounded\` | \`border-radius:var(--d-radius)\` |
882
+ | \`_roundedfull\` | \`border-radius:9999px\` |
883
+ | \`_roundedsm\`, \`_roundedlg\`, \`_roundedxl\` | radius variants |
884
+ | \`_shadow\`, \`_shadowmd\`, \`_shadowlg\` | box-shadow presets |
885
+ | \`_bordernone\` | \`border:none\` |
886
+ | \`_bw{n}\` | \`border-width:{n}px\` |
887
+ | \`_op0\`-\`_op100\` | opacity (0, 25, 50, 75, 100) |
888
+ | \`_trans\` | \`transition:all 0.15s ease\` |
889
+ | \`_visible\`, \`_invisible\` | visibility |
890
+
891
+ ### Using Recipe Decorators
892
+
893
+ Recipe decorators (from \`src/styles/decorators.css\`) are regular CSS class names, NOT atoms. They are applied directly as class names and combined with atoms using string concatenation:
894
+
895
+ \`\`\`tsx
896
+ // Atoms use css() function, decorators are plain class names
897
+ <div className={css('_flex _col _gap4') + ' carbon-card'}>
898
+ <div className={css('_p4') + ' carbon-glass'}>
899
+ <pre className={css('_p3') + ' carbon-code'}>{code}</pre>
900
+ </div>
901
+ </div>
902
+ \`\`\`
903
+
904
+ **Key difference:**
905
+ - Atoms: \`css('_flex _col _gap4')\` \u2014 processed by @decantr/css runtime
906
+ - Decorators: \`'carbon-card'\`, \`'carbon-glass'\` \u2014 plain CSS classes from decorators.css
907
+ - Combined: \`css('_flex _col') + ' carbon-card'\`
908
+
909
+ ### Routing
910
+
911
+ Check \`decantr.essence.json\` \u2192 \`meta.platform.routing\` for the routing strategy:
912
+ - \`"hash"\` \u2192 use \`HashRouter\` (e.g., for static hosting, GitHub Pages)
913
+ - \`"history"\` \u2192 use \`BrowserRouter\` (e.g., for server-rendered apps)
914
+
915
+ Routes are defined in \`decantr.essence.json\` \u2192 \`blueprint.routes\` and listed in \`.decantr/context/scaffold.md\`.
916
+
917
+ ### CSS Architecture
918
+
919
+ The CSS is organized into two parts:
920
+
921
+ 1. **Atoms (@decantr/css)** - Layout utilities injected at runtime into \`@layer d.atoms\`
922
+ 2. **Generated CSS files** - Theme tokens and recipe decorators created during scaffold
923
+
924
+ \`\`\`
925
+ src/styles/
926
+ tokens.css # :root { --d-primary: #...; --d-surface: #...; }
927
+ decorators.css # .recipe-card { ... }
928
+ \`\`\`
929
+
930
+ ### Variable Naming Convention
931
+
932
+ | Prefix | Purpose | Example |
933
+ |--------|---------|---------|
934
+ | \`--d-\` | Core Decantr tokens | \`--d-primary\`, \`--d-bg\` |
935
+ | \`--d-gap-{n}\` | Spacing tokens | \`--d-gap-4\`, \`--d-gap-8\` |
936
+ | \`--d-radius\` | Border radius | \`--d-radius\`, \`--d-radius-lg\` |`;
937
+ function generateDecantrMdV31(guardMode, cssApproach) {
938
+ const template = loadTemplate("DECANTR.md.template");
939
+ return renderTemplate(template, {
940
+ GUARD_MODE: guardMode,
941
+ CSS_APPROACH: cssApproach
942
+ });
943
+ }
944
+ function generateProjectJson(detected, options, registrySource) {
945
+ const now = (/* @__PURE__ */ new Date()).toISOString();
946
+ const data = {
947
+ detected: {
948
+ framework: detected.framework,
949
+ version: detected.version || null,
950
+ packageManager: detected.packageManager,
951
+ hasTypeScript: detected.hasTypeScript,
952
+ hasTailwind: detected.hasTailwind,
953
+ existingRuleFiles: detected.existingRuleFiles
954
+ },
955
+ overrides: {
956
+ framework: options.target !== detected.framework ? options.target : null
957
+ },
958
+ sync: {
959
+ status: registrySource === "api" ? "synced" : "needs-sync",
960
+ lastSync: now,
961
+ registrySource,
962
+ cachedContent: {
963
+ archetypes: [],
964
+ patterns: [],
965
+ themes: [],
966
+ recipes: []
967
+ }
968
+ },
969
+ initialized: {
970
+ at: now,
971
+ via: "cli",
972
+ version: CLI_VERSION,
973
+ flags: buildFlagsString(options)
974
+ }
975
+ };
976
+ return JSON.stringify(data, null, 2);
977
+ }
978
+ function buildFlagsString(options) {
979
+ const flags = [];
980
+ if (options.blueprint) flags.push(`--blueprint=${options.blueprint}`);
981
+ if (options.theme) flags.push(`--theme=${options.theme}`);
982
+ if (options.mode) flags.push(`--mode=${options.mode}`);
983
+ if (options.guard) flags.push(`--guard=${options.guard}`);
984
+ return flags.join(" ");
985
+ }
986
+ function generateTaskContext(templateName, essence) {
987
+ const template = loadTemplate(templateName);
988
+ const defaultShell = essence.structure[0]?.shell || "sidebar-main";
989
+ const layout = essence.structure[0]?.layout.map(serializeLayoutItem).join(", ") || "none";
990
+ const scaffoldStructure = essence.structure.map((p) => {
991
+ const patterns = p.layout.length > 0 ? `
992
+ - Patterns: ${p.layout.map(serializeLayoutItem).join(", ")}` : "";
993
+ return `- **${p.id}** (${p.shell})${patterns}`;
994
+ }).join("\n");
995
+ const vars = {
996
+ TARGET: essence.target,
997
+ THEME_STYLE: essence.theme.style,
998
+ THEME_MODE: essence.theme.mode,
999
+ THEME_RECIPE: essence.theme.recipe,
1000
+ DEFAULT_SHELL: defaultShell,
1001
+ GUARD_MODE: essence.guard.mode,
1002
+ LAYOUT: layout,
1003
+ DENSITY: essence.density.level,
1004
+ CONTENT_GAP: essence.density.content_gap,
1005
+ SCAFFOLD_STRUCTURE: scaffoldStructure
1006
+ };
1007
+ return renderTemplate(template, vars);
1008
+ }
1009
+ function generateEssenceSummary(essence) {
1010
+ const template = loadTemplate("essence-summary.md.template");
1011
+ const pagesTable = `| Page | Shell | Layout |
1012
+ |------|-------|--------|
1013
+ ${essence.structure.map((p) => `| ${p.id} | ${p.shell} | ${p.layout.map(serializeLayoutItem).join(", ") || "none"} |`).join("\n")}`;
1014
+ const featuresList = essence.features.length > 0 ? essence.features.map((f) => `- ${f}`).join("\n") : "- No features specified";
1015
+ const dnaEnforcement = essence.guard.enforce_style ? "error" : "off";
1016
+ const blueprintEnforcement = essence.guard.enforce_recipe ? "warn" : "off";
1017
+ const vars = {
1018
+ ARCHETYPE: essence.archetype || "custom",
1019
+ BLUEPRINT: essence.blueprint || "none",
1020
+ PERSONALITY: essence.personality.join(", "),
1021
+ TARGET: essence.target,
1022
+ THEME_STYLE: essence.theme.style,
1023
+ THEME_MODE: essence.theme.mode,
1024
+ THEME_RECIPE: essence.theme.recipe,
1025
+ SHAPE: essence.theme.shape,
1026
+ PAGES_TABLE: pagesTable,
1027
+ FEATURES_LIST: featuresList,
1028
+ GUARD_MODE: essence.guard.mode,
1029
+ ENFORCE_STYLE: String(essence.guard.enforce_style),
1030
+ ENFORCE_RECIPE: String(essence.guard.enforce_recipe),
1031
+ DNA_ENFORCEMENT: dnaEnforcement,
1032
+ BLUEPRINT_ENFORCEMENT: blueprintEnforcement,
1033
+ DENSITY: essence.density.level,
1034
+ CONTENT_GAP: essence.density.content_gap,
1035
+ LAST_UPDATED: (/* @__PURE__ */ new Date()).toISOString()
1036
+ };
1037
+ return renderTemplate(template, vars);
1038
+ }
1039
+ function generateEssenceSummaryV3(essence) {
1040
+ const template = loadTemplate("essence-summary.md.template");
1041
+ const blueprint = essence.blueprint;
1042
+ const sections = blueprint.sections || [];
1043
+ const flatPages = blueprint.pages || [];
1044
+ let pagesTable;
1045
+ if (sections.length > 0) {
1046
+ const rows = sections.flatMap(
1047
+ (s) => s.pages.map((p) => `| ${p.id} | ${s.shell} | ${p.layout.map(serializeLayoutItem).join(", ") || "none"} |`)
1048
+ );
1049
+ pagesTable = `| Page | Shell | Layout |
1050
+ |------|-------|--------|
1051
+ ${rows.join("\n")}`;
1052
+ } else {
1053
+ const shell = blueprint.shell ?? "sidebar-main";
1054
+ const rows = flatPages.map((p) => `| ${p.id} | ${shell} | ${p.layout.map(serializeLayoutItem).join(", ") || "none"} |`);
1055
+ pagesTable = `| Page | Shell | Layout |
1056
+ |------|-------|--------|
1057
+ ${rows.join("\n")}`;
1058
+ }
1059
+ const features = blueprint.features || [];
1060
+ const featuresList = features.length > 0 ? features.map((f) => `- ${f}`).join("\n") : "- No features specified";
1061
+ const vars = {
1062
+ ARCHETYPE: essence.meta.archetype || "custom",
1063
+ BLUEPRINT: "",
1064
+ PERSONALITY: (essence.dna.personality || []).join(", "),
1065
+ TARGET: essence.meta.target ?? "",
1066
+ THEME_STYLE: essence.dna.theme.style,
1067
+ THEME_MODE: essence.dna.theme.mode,
1068
+ THEME_RECIPE: essence.dna.theme.recipe ?? "",
1069
+ SHAPE: essence.dna.theme.shape ?? "",
1070
+ PAGES_TABLE: pagesTable,
1071
+ FEATURES_LIST: featuresList,
1072
+ GUARD_MODE: essence.meta.guard.mode,
1073
+ ENFORCE_STYLE: essence.meta.guard.dna_enforcement || "error",
1074
+ ENFORCE_RECIPE: essence.meta.guard.blueprint_enforcement || "warn",
1075
+ DNA_ENFORCEMENT: essence.meta.guard.dna_enforcement || "error",
1076
+ BLUEPRINT_ENFORCEMENT: essence.meta.guard.blueprint_enforcement || "warn",
1077
+ DENSITY: essence.dna.spacing?.density || "comfortable",
1078
+ CONTENT_GAP: essence.dna.spacing?.content_gap || "_gap4",
1079
+ LAST_UPDATED: (/* @__PURE__ */ new Date()).toISOString()
1080
+ };
1081
+ return renderTemplate(template, vars);
1082
+ }
1083
+ function updateGitignore(projectRoot) {
1084
+ const gitignorePath = join(projectRoot, ".gitignore");
1085
+ const cacheEntry = ".decantr/cache/";
1086
+ if (existsSync(gitignorePath)) {
1087
+ const content = readFileSync(gitignorePath, "utf-8");
1088
+ if (!content.includes(cacheEntry)) {
1089
+ appendFileSync(gitignorePath, `
1090
+ # Decantr cache
1091
+ ${cacheEntry}
1092
+ `);
1093
+ return true;
1094
+ }
1095
+ return false;
1096
+ } else {
1097
+ writeFileSync(gitignorePath, `# Decantr cache
1098
+ ${cacheEntry}
1099
+ `);
1100
+ return true;
1101
+ }
1102
+ }
1103
+ async function scaffoldProject(projectRoot, options, detected, registry, archetypeData, registrySource = "cache", themeData, recipeData, topologyMarkdown, composedSections, routeMap, patternSpecs, blueprintData) {
1104
+ const essenceV3 = buildEssenceV3(options, archetypeData, themeData, recipeData);
1105
+ const essence = buildEssence(options, archetypeData);
1106
+ const decantrDir = join(projectRoot, ".decantr");
1107
+ const contextDir = join(decantrDir, "context");
1108
+ const cacheDir = join(decantrDir, "cache");
1109
+ mkdirSync(contextDir, { recursive: true });
1110
+ mkdirSync(cacheDir, { recursive: true });
1111
+ const essencePath = join(projectRoot, "decantr.essence.json");
1112
+ writeFileSync(essencePath, JSON.stringify(essenceV3, null, 2) + "\n");
1113
+ const projectJsonPath = join(decantrDir, "project.json");
1114
+ writeFileSync(projectJsonPath, generateProjectJson(detected, options, registrySource));
1115
+ const contextFiles = [];
1116
+ const scaffoldPath = join(contextDir, "task-scaffold.md");
1117
+ writeFileSync(scaffoldPath, generateTaskContext("task-scaffold.md.template", essence));
1118
+ contextFiles.push(scaffoldPath);
1119
+ const addPagePath = join(contextDir, "task-add-page.md");
1120
+ writeFileSync(addPagePath, generateTaskContext("task-add-page.md.template", essence));
1121
+ contextFiles.push(addPagePath);
1122
+ const modifyPath = join(contextDir, "task-modify.md");
1123
+ writeFileSync(modifyPath, generateTaskContext("task-modify.md.template", essence));
1124
+ contextFiles.push(modifyPath);
1125
+ const summaryPath = join(contextDir, "essence-summary.md");
1126
+ writeFileSync(summaryPath, generateEssenceSummary(essence));
1127
+ contextFiles.push(summaryPath);
1128
+ if (composedSections) {
1129
+ essenceV3.version = "3.1.0";
1130
+ essenceV3.blueprint = {
1131
+ sections: composedSections.sections,
1132
+ features: composedSections.features,
1133
+ routes: routeMap || {}
1134
+ };
1135
+ if (blueprintData?.personality?.length) {
1136
+ essenceV3.dna.personality = blueprintData.personality;
1137
+ }
1138
+ if (blueprintData?.design_constraints) {
1139
+ essenceV3.dna.constraints = blueprintData.design_constraints;
1140
+ }
1141
+ if (blueprintData?.seo_hints) {
1142
+ essenceV3.meta.seo = blueprintData.seo_hints;
1143
+ }
1144
+ if (blueprintData?.navigation) {
1145
+ essenceV3.meta.navigation = blueprintData.navigation;
1146
+ }
1147
+ writeFileSync(essencePath, JSON.stringify(essenceV3, null, 2) + "\n");
1148
+ }
1149
+ const refreshResult = await refreshDerivedFiles(projectRoot, essenceV3, registry, themeData, recipeData);
1150
+ contextFiles.push(...refreshResult.contextFiles);
1151
+ const gitignoreUpdated = updateGitignore(projectRoot);
1152
+ return {
1153
+ essencePath,
1154
+ decantrMdPath: refreshResult.decantrMdPath,
1155
+ projectJsonPath,
1156
+ contextFiles,
1157
+ cssFiles: refreshResult.cssFiles,
1158
+ gitignoreUpdated
1159
+ };
1160
+ }
1161
+ function scaffoldMinimal(projectRoot) {
1162
+ const decantrDir = join(projectRoot, ".decantr");
1163
+ const customDir = join(decantrDir, "custom");
1164
+ const contentTypes = ["patterns", "recipes", "themes", "blueprints", "archetypes", "shells"];
1165
+ for (const type of contentTypes) {
1166
+ mkdirSync(join(customDir, type), { recursive: true });
1167
+ }
1168
+ const essence = {
1169
+ version: "3.0.0",
1170
+ dna: {
1171
+ theme: {
1172
+ style: "default",
1173
+ mode: "dark",
1174
+ recipe: "default",
1175
+ shape: "rounded"
1176
+ },
1177
+ spacing: {
1178
+ base_unit: 4,
1179
+ scale: "linear",
1180
+ density: "comfortable",
1181
+ content_gap: "_gap4"
1182
+ },
1183
+ typography: {
1184
+ scale: "modular",
1185
+ heading_weight: 600,
1186
+ body_weight: 400
1187
+ },
1188
+ color: {
1189
+ palette: "semantic",
1190
+ accent_count: 1,
1191
+ cvd_preference: "auto"
1192
+ },
1193
+ radius: {
1194
+ philosophy: "rounded",
1195
+ base: 8
1196
+ },
1197
+ elevation: {
1198
+ system: "layered",
1199
+ max_levels: 3
1200
+ },
1201
+ motion: {
1202
+ preference: "subtle",
1203
+ duration_scale: 1,
1204
+ reduce_motion: true
1205
+ },
1206
+ accessibility: {
1207
+ wcag_level: "AA",
1208
+ focus_visible: true,
1209
+ skip_nav: true
1210
+ },
1211
+ personality: ["clean", "modern"]
1212
+ },
1213
+ blueprint: {
1214
+ shell: "sidebar-main",
1215
+ pages: [
1216
+ { id: "home", layout: ["hero"] }
1217
+ ],
1218
+ features: []
1219
+ },
1220
+ meta: {
1221
+ archetype: "custom",
1222
+ target: "react",
1223
+ platform: {
1224
+ type: "spa",
1225
+ routing: "hash"
1226
+ },
1227
+ guard: {
1228
+ mode: "guided",
1229
+ dna_enforcement: "error",
1230
+ blueprint_enforcement: "off"
1231
+ }
1232
+ }
1233
+ };
1234
+ const essencePath = join(projectRoot, "decantr.essence.json");
1235
+ writeFileSync(essencePath, JSON.stringify(essence, null, 2) + "\n");
1236
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1237
+ const projectJson = {
1238
+ detected: {
1239
+ framework: "unknown",
1240
+ version: null,
1241
+ packageManager: "npm",
1242
+ hasTypeScript: false,
1243
+ hasTailwind: false,
1244
+ existingRuleFiles: []
1245
+ },
1246
+ overrides: {
1247
+ framework: null
1248
+ },
1249
+ sync: {
1250
+ status: "needs-sync",
1251
+ lastSync: now,
1252
+ registrySource: "cache",
1253
+ cachedContent: {
1254
+ archetypes: [],
1255
+ patterns: [],
1256
+ themes: [],
1257
+ recipes: []
1258
+ }
1259
+ },
1260
+ initialized: {
1261
+ at: now,
1262
+ via: "cli",
1263
+ version: CLI_VERSION,
1264
+ flags: "--offline --minimal"
1265
+ }
1266
+ };
1267
+ const projectJsonPath = join(decantrDir, "project.json");
1268
+ writeFileSync(projectJsonPath, JSON.stringify(projectJson, null, 2));
1269
+ const decantrMdPath = join(projectRoot, "DECANTR.md");
1270
+ const decantrMdContent = `# DECANTR.md
1271
+
1272
+ > This file was generated by \`decantr init\` in offline/minimal mode.
1273
+ > Run \`decantr upgrade\` when online to pull full registry content.
1274
+
1275
+ ## Two-Layer Model
1276
+
1277
+ This project uses the v3 Essence format with two layers:
1278
+
1279
+ ### DNA (Immutable Design Axioms)
1280
+ DNA defines the foundational design rules that must never be violated. DNA violations are **errors**.
1281
+
1282
+ - **Theme:** default (dark mode)
1283
+ - **Spacing:** comfortable density, _gap4
1284
+ - **Radius:** rounded (8px base)
1285
+ - **Accessibility:** WCAG AA
1286
+ - **Personality:** clean, modern
1287
+
1288
+ ### Blueprint (Structural Layout)
1289
+ Blueprint defines pages, shells, and patterns. Blueprint deviations are **warnings**.
1290
+
1291
+ | Page | Shell | Layout |
1292
+ |------|-------|--------|
1293
+ | home | sidebar-main | hero |
1294
+
1295
+ ## Guard Mode: guided
1296
+
1297
+ - **DNA enforcement:** error (violations block generation)
1298
+ - **Blueprint enforcement:** off (deviations are advisory)
1299
+
1300
+ ## MCP Tools
1301
+
1302
+ When available, use these tools:
1303
+ - \`decantr_read_essence\` \u2014 Read the current essence
1304
+ - \`decantr_check_drift\` \u2014 Check for guard violations
1305
+ - \`decantr_accept_drift\` \u2014 Accept a detected drift as intentional
1306
+ - \`decantr_update_essence\` \u2014 Update the essence spec
1307
+
1308
+ ## Quick Start
1309
+
1310
+ 1. Edit \`decantr.essence.json\` to define your project structure.
1311
+ 2. Run \`decantr sync\` when online to fetch registry content.
1312
+ 3. Use \`decantr create <type> <name>\` to create custom content.
1313
+ 4. Use \`decantr validate\` to check your essence file.
1314
+
1315
+ ## Commands
1316
+
1317
+ - \`decantr init\` \u2014 Initialize a new Decantr project
1318
+ - \`decantr status\` \u2014 Project health and DNA/Blueprint overview
1319
+ - \`decantr sync\` \u2014 Sync registry content
1320
+ - \`decantr audit\` \u2014 Audit project for issues
1321
+ - \`decantr migrate\` \u2014 Migrate v2 essence to v3
1322
+ - \`decantr check\` \u2014 Detect drift issues
1323
+ - \`decantr sync-drift\` \u2014 Review and resolve drift entries
1324
+ - \`decantr validate\` \u2014 Validate essence file
1325
+ - \`decantr search\` \u2014 Search the registry
1326
+ - \`decantr suggest\` \u2014 Suggest patterns or alternatives
1327
+ - \`decantr get\` \u2014 Get full details of a registry item
1328
+ - \`decantr list\` \u2014 List items by type
1329
+ - \`decantr theme\` \u2014 Manage custom themes
1330
+ - \`decantr create\` \u2014 Create custom content items
1331
+ - \`decantr publish\` \u2014 Publish custom content to registry
1332
+ - \`decantr login\` \u2014 Authenticate with registry
1333
+ - \`decantr logout\` \u2014 Remove stored credentials
1334
+ - \`decantr upgrade\` \u2014 Check for content updates
1335
+
1336
+ ---
1337
+
1338
+ *Generated by @decantr/cli v${CLI_VERSION}*
1339
+ `;
1340
+ writeFileSync(decantrMdPath, decantrMdContent);
1341
+ const gitignoreUpdated = updateGitignore(projectRoot);
1342
+ return {
1343
+ essencePath,
1344
+ decantrMdPath,
1345
+ projectJsonPath,
1346
+ contextFiles: [],
1347
+ cssFiles: [],
1348
+ gitignoreUpdated
1349
+ };
1350
+ }
1351
+ async function refreshDerivedFiles(projectRoot, essence, registry, prefetchedThemeData, prefetchedRecipeData) {
1352
+ const decantrDir = join(projectRoot, ".decantr");
1353
+ const contextDir = join(decantrDir, "context");
1354
+ mkdirSync(contextDir, { recursive: true });
1355
+ const themeName = essence.dna.theme.style;
1356
+ const recipeName = essence.dna.theme.recipe ?? themeName;
1357
+ const mode = essence.dna.theme.mode;
1358
+ const guardMode = essence.meta.guard.mode;
1359
+ const guardConfig = {
1360
+ mode: guardMode,
1361
+ dna_enforcement: essence.meta.guard.dna_enforcement || "error",
1362
+ blueprint_enforcement: essence.meta.guard.blueprint_enforcement || "warn"
1363
+ };
1364
+ const personality = essence.dna.personality || [];
1365
+ let themeData = prefetchedThemeData;
1366
+ let recipeData = prefetchedRecipeData;
1367
+ if (!themeData) try {
1368
+ const themeResult = await registry.fetchTheme(themeName);
1369
+ if (themeResult?.data) {
1370
+ const raw = themeResult.data;
1371
+ const t = raw.data ?? raw;
1372
+ themeData = {
1373
+ seed: t.seed,
1374
+ palette: t.palette,
1375
+ cvd_support: t.cvd_support,
1376
+ tokens: t.tokens,
1377
+ typography_hints: t.typography_hints,
1378
+ motion_hints: t.motion_hints
1379
+ };
1380
+ }
1381
+ } catch {
1382
+ }
1383
+ if (!recipeData) try {
1384
+ const recipeResult = await registry.fetchRecipe(recipeName);
1385
+ if (recipeResult?.data) {
1386
+ const raw = recipeResult.data;
1387
+ const r = raw.data ?? raw;
1388
+ recipeData = {
1389
+ decorators: r.decorators,
1390
+ spatial_hints: r.spatial_hints,
1391
+ radius_hints: r.radius_hints
1392
+ };
1393
+ if (!recipeData.decorators && raw.data) {
1394
+ const inner = raw.data;
1395
+ if (inner.decorators) {
1396
+ recipeData.decorators = inner.decorators;
1397
+ recipeData.spatial_hints = inner.spatial_hints;
1398
+ recipeData.radius_hints = inner.radius_hints;
1399
+ }
1400
+ }
1401
+ }
1402
+ } catch {
1403
+ }
1404
+ if (!recipeData?.decorators || Object.keys(recipeData.decorators).length === 0) {
1405
+ try {
1406
+ const apiUrl = registry.apiUrl || "https://api.decantr.ai/v1";
1407
+ const resp = await fetch(`${apiUrl}/recipes/@official/${recipeName}`);
1408
+ if (resp.ok) {
1409
+ const apiData = await resp.json();
1410
+ const inner = apiData.data ?? apiData;
1411
+ if (inner.decorators && Object.keys(inner.decorators).length > 0) {
1412
+ recipeData = {
1413
+ decorators: inner.decorators,
1414
+ spatial_hints: inner.spatial_hints,
1415
+ radius_hints: inner.radius_hints
1416
+ };
1417
+ }
1418
+ }
1419
+ } catch {
1420
+ }
1421
+ }
1422
+ if (!themeData?.seed?.primary) {
1423
+ try {
1424
+ const apiUrl = registry.apiUrl || "https://api.decantr.ai/v1";
1425
+ const resp = await fetch(`${apiUrl}/themes/@official/${themeName}`);
1426
+ if (resp.ok) {
1427
+ const apiData = await resp.json();
1428
+ const inner = apiData.data ?? apiData;
1429
+ if (inner.seed) {
1430
+ themeData = {
1431
+ seed: inner.seed,
1432
+ palette: inner.palette,
1433
+ cvd_support: inner.cvd_support,
1434
+ tokens: inner.tokens,
1435
+ typography_hints: inner.typography_hints,
1436
+ motion_hints: inner.motion_hints
1437
+ };
1438
+ }
1439
+ }
1440
+ } catch {
1441
+ }
1442
+ }
1443
+ const stylesDir = join(projectRoot, "src", "styles");
1444
+ mkdirSync(stylesDir, { recursive: true });
1445
+ const tokensPath = join(stylesDir, "tokens.css");
1446
+ const hasRealThemeData = themeData?.seed?.primary || themeData?.palette?.background;
1447
+ if (hasRealThemeData || !existsSync(tokensPath)) {
1448
+ writeFileSync(tokensPath, generateTokensCSS(themeData, mode));
1449
+ }
1450
+ const decoratorsPath = join(stylesDir, "decorators.css");
1451
+ const hasRealRecipeData = recipeData?.decorators && Object.keys(recipeData.decorators).length > 0;
1452
+ if (hasRealRecipeData || !existsSync(decoratorsPath)) {
1453
+ writeFileSync(decoratorsPath, generateDecoratorsCSS(recipeData, themeName));
1454
+ }
1455
+ const cssFiles = [tokensPath, decoratorsPath];
1456
+ const decoratorsMdPath = join(contextDir, "decorators.md");
1457
+ writeFileSync(decoratorsMdPath, generateDecoratorsContext(recipeData, recipeName));
1458
+ const decantrMdPath = join(projectRoot, "DECANTR.md");
1459
+ writeFileSync(decantrMdPath, generateDecantrMdV31(guardMode, CSS_APPROACH_CONTENT));
1460
+ const summaryPath = join(contextDir, "essence-summary.md");
1461
+ writeFileSync(summaryPath, generateEssenceSummaryV3(essence));
1462
+ const contextFiles = [decoratorsMdPath, summaryPath];
1463
+ const blueprint = essence.blueprint;
1464
+ const sections = blueprint.sections && blueprint.sections.length > 0 ? blueprint.sections : [];
1465
+ if (sections.length > 0) {
1466
+ const primarySectionShell = sections.find((s) => s.role === "primary")?.shell || "sidebar-main";
1467
+ for (const section of sections) {
1468
+ if (section.shell === "inherit") {
1469
+ section.shell = primarySectionShell;
1470
+ }
1471
+ }
1472
+ const patternSpecs = {};
1473
+ const seenPatterns = /* @__PURE__ */ new Set();
1474
+ for (const section of sections) {
1475
+ for (const page of section.pages) {
1476
+ for (const item of page.layout) {
1477
+ const names = extractPatternNames(item);
1478
+ for (const name of names) {
1479
+ if (!seenPatterns.has(name)) {
1480
+ seenPatterns.add(name);
1481
+ try {
1482
+ const patResult = await registry.fetchPattern(name);
1483
+ if (patResult?.data) {
1484
+ const raw = patResult.data;
1485
+ const inner = raw.data ?? raw;
1486
+ const defaultPreset = inner.default_preset || "standard";
1487
+ const preset = inner.presets?.[defaultPreset];
1488
+ let slots = preset?.layout?.slots || {};
1489
+ if (Object.keys(slots).length === 0) {
1490
+ const synthetic = generateSyntheticSlots(name, inner.description || "");
1491
+ if (Object.keys(synthetic).length > 0) {
1492
+ slots = synthetic;
1493
+ }
1494
+ }
1495
+ patternSpecs[name] = {
1496
+ description: inner.description || "",
1497
+ components: inner.components || [],
1498
+ slots
1499
+ };
1500
+ } else {
1501
+ const synthetic = generateSyntheticSlots(name, "");
1502
+ if (Object.keys(synthetic).length > 0) {
1503
+ patternSpecs[name] = { description: "", components: [], slots: synthetic };
1504
+ }
1505
+ }
1506
+ } catch {
1507
+ const synthetic = generateSyntheticSlots(name, "");
1508
+ if (Object.keys(synthetic).length > 0) {
1509
+ patternSpecs[name] = { description: "", components: [], slots: synthetic };
1510
+ }
1511
+ }
1512
+ }
1513
+ }
1514
+ }
1515
+ }
1516
+ }
1517
+ const zoneInputs = sections.map((s) => ({
1518
+ archetypeId: s.id,
1519
+ role: s.role,
1520
+ shell: s.shell,
1521
+ features: s.features,
1522
+ description: s.description
1523
+ }));
1524
+ const zones = deriveZones(zoneInputs);
1525
+ const transitions = deriveTransitions(zones);
1526
+ const hasPublic = zones.some((z) => z.role === "public");
1527
+ const hasPrimary = zones.some((z) => z.role === "primary");
1528
+ const topologyData = {
1529
+ intent: sections.map((s) => s.id).join(" + "),
1530
+ zones,
1531
+ transitions,
1532
+ entryPoints: {
1533
+ anonymous: hasPublic ? "public zone" : "gateway",
1534
+ authenticated: hasPrimary ? "primary zone" : "first section"
1535
+ }
1536
+ };
1537
+ const topologyMarkdown = generateTopologySection(topologyData, personality);
1538
+ const themeTokensCss = existsSync(tokensPath) ? readFileSync(tokensPath, "utf-8") : "";
1539
+ const decoratorList = [];
1540
+ if (recipeData?.decorators) {
1541
+ for (const [name, desc] of Object.entries(recipeData.decorators)) {
1542
+ decoratorList.push({ name, description: desc });
1543
+ }
1544
+ } else if (existsSync(decoratorsPath)) {
1545
+ const decoratorsCss = readFileSync(decoratorsPath, "utf-8");
1546
+ const classRegex = /\/\*\s*(.+?)\s*\*\/\s*\n\s*\.([\w-]+)\s*\{/g;
1547
+ let match;
1548
+ while ((match = classRegex.exec(decoratorsCss)) !== null) {
1549
+ decoratorList.push({ name: match[2], description: match[1] });
1550
+ }
1551
+ if (decoratorList.length === 0) {
1552
+ const simpleClassRegex = /^\.([\w-]+)\s*\{/gm;
1553
+ while ((match = simpleClassRegex.exec(decoratorsCss)) !== null) {
1554
+ decoratorList.push({ name: match[1], description: "" });
1555
+ }
1556
+ }
1557
+ }
1558
+ const shellInfoCache = {};
1559
+ const seenShells = /* @__PURE__ */ new Set();
1560
+ for (const section of sections) {
1561
+ const shellId = section.shell;
1562
+ if (!seenShells.has(shellId)) {
1563
+ seenShells.add(shellId);
1564
+ try {
1565
+ const shellResult = await registry.fetchShell(shellId);
1566
+ if (shellResult?.data) {
1567
+ const raw = shellResult.data;
1568
+ const inner = raw.data ?? raw;
1569
+ shellInfoCache[shellId] = {
1570
+ description: inner.description || "",
1571
+ regions: inner.config?.regions || [],
1572
+ layout: inner.layout || void 0
1573
+ };
1574
+ }
1575
+ } catch {
1576
+ }
1577
+ }
1578
+ }
1579
+ for (const section of sections) {
1580
+ const zoneLabel = section.role === "primary" || section.role === "auxiliary" ? "App" : section.role === "gateway" ? "Gateway" : "Public";
1581
+ let zoneContext = `**Zone:** ${zoneLabel} (${section.role}) \u2014 ${section.shell} shell`;
1582
+ if (section.role === "gateway") {
1583
+ zoneContext += "\nAuth success \u2192 enters App zone. Sign out returns here.";
1584
+ } else if (section.role === "primary") {
1585
+ zoneContext += "\nAuthenticated users land here. Sign out \u2192 Gateway (/login).";
1586
+ } else if (section.role === "public") {
1587
+ zoneContext += "\nAnonymous visitors. CTAs lead to Gateway (/login, /register).";
1588
+ } else if (section.role === "auxiliary") {
1589
+ zoneContext += "\nSupporting section within App zone. Shares navigation with primary.";
1590
+ }
1591
+ const sectionPatterns = {};
1592
+ for (const page of section.pages) {
1593
+ for (const item of page.layout) {
1594
+ const names = extractPatternNames(item);
1595
+ for (const name of names) {
1596
+ if (patternSpecs[name]) {
1597
+ sectionPatterns[name] = patternSpecs[name];
1598
+ }
1599
+ }
1600
+ }
1601
+ }
1602
+ const contextContent = generateSectionContext({
1603
+ section,
1604
+ themeTokens: themeTokensCss,
1605
+ decorators: decoratorList,
1606
+ guardConfig,
1607
+ personality,
1608
+ themeName,
1609
+ recipeName,
1610
+ zoneContext,
1611
+ patternSpecs: sectionPatterns,
1612
+ constraints: essence.dna.constraints,
1613
+ shellInfo: shellInfoCache[section.shell]
1614
+ });
1615
+ const sectionContextPath = join(contextDir, `section-${section.id}.md`);
1616
+ writeFileSync(sectionContextPath, contextContent);
1617
+ contextFiles.push(sectionContextPath);
1618
+ }
1619
+ const routes = blueprint.routes || {};
1620
+ const scaffoldContent = generateScaffoldContext({
1621
+ appName: essence.meta.archetype || "Application",
1622
+ blueprintId: "",
1623
+ themeName,
1624
+ recipeName,
1625
+ personality,
1626
+ topologyMarkdown,
1627
+ sections,
1628
+ routes,
1629
+ constraints: essence.dna.constraints,
1630
+ seo: essence.meta.seo,
1631
+ navigation: essence.meta.navigation
1632
+ });
1633
+ const scaffoldMdPath = join(contextDir, "scaffold.md");
1634
+ writeFileSync(scaffoldMdPath, scaffoldContent);
1635
+ contextFiles.push(scaffoldMdPath);
1636
+ } else {
1637
+ const pages = blueprint.pages || [{ id: "home", layout: ["hero"] }];
1638
+ const shell = blueprint.shell ?? "sidebar-main";
1639
+ const syntheticSection = {
1640
+ id: essence.meta.archetype || "default",
1641
+ role: "primary",
1642
+ shell,
1643
+ features: blueprint.features || [],
1644
+ description: `${essence.meta.archetype || "Application"} section`,
1645
+ pages
1646
+ };
1647
+ const patternSpecs = {};
1648
+ const seenPatterns = /* @__PURE__ */ new Set();
1649
+ for (const page of pages) {
1650
+ for (const item of page.layout) {
1651
+ const names = extractPatternNames(item);
1652
+ for (const name of names) {
1653
+ if (!seenPatterns.has(name)) {
1654
+ seenPatterns.add(name);
1655
+ try {
1656
+ const patResult = await registry.fetchPattern(name);
1657
+ if (patResult?.data) {
1658
+ const raw = patResult.data;
1659
+ const inner = raw.data ?? raw;
1660
+ const defaultPreset = inner.default_preset || "standard";
1661
+ const preset = inner.presets?.[defaultPreset];
1662
+ let slots = preset?.layout?.slots || {};
1663
+ if (Object.keys(slots).length === 0) {
1664
+ const synthetic = generateSyntheticSlots(name, inner.description || "");
1665
+ if (Object.keys(synthetic).length > 0) {
1666
+ slots = synthetic;
1667
+ }
1668
+ }
1669
+ patternSpecs[name] = {
1670
+ description: inner.description || "",
1671
+ components: inner.components || [],
1672
+ slots
1673
+ };
1674
+ } else {
1675
+ const synthetic = generateSyntheticSlots(name, "");
1676
+ if (Object.keys(synthetic).length > 0) {
1677
+ patternSpecs[name] = { description: "", components: [], slots: synthetic };
1678
+ }
1679
+ }
1680
+ } catch {
1681
+ const synthetic = generateSyntheticSlots(name, "");
1682
+ if (Object.keys(synthetic).length > 0) {
1683
+ patternSpecs[name] = { description: "", components: [], slots: synthetic };
1684
+ }
1685
+ }
1686
+ }
1687
+ }
1688
+ }
1689
+ }
1690
+ const themeTokensCss = existsSync(tokensPath) ? readFileSync(tokensPath, "utf-8") : "";
1691
+ const decoratorList = [];
1692
+ if (recipeData?.decorators) {
1693
+ for (const [name, desc] of Object.entries(recipeData.decorators)) {
1694
+ decoratorList.push({ name, description: desc });
1695
+ }
1696
+ } else if (existsSync(decoratorsPath)) {
1697
+ const decoratorsCss = readFileSync(decoratorsPath, "utf-8");
1698
+ const classRegex = /\/\*\s*(.+?)\s*\*\/\s*\n\s*\.([\w-]+)\s*\{/g;
1699
+ let match;
1700
+ while ((match = classRegex.exec(decoratorsCss)) !== null) {
1701
+ decoratorList.push({ name: match[2], description: match[1] });
1702
+ }
1703
+ if (decoratorList.length === 0) {
1704
+ const simpleClassRegex = /^\.([\w-]+)\s*\{/gm;
1705
+ while ((match = simpleClassRegex.exec(decoratorsCss)) !== null) {
1706
+ decoratorList.push({ name: match[1], description: "" });
1707
+ }
1708
+ }
1709
+ }
1710
+ let v30ShellInfo;
1711
+ try {
1712
+ const shellResult = await registry.fetchShell(shell);
1713
+ if (shellResult?.data) {
1714
+ const raw = shellResult.data;
1715
+ const inner = raw.data ?? raw;
1716
+ v30ShellInfo = {
1717
+ description: inner.description || "",
1718
+ regions: inner.config?.regions || [],
1719
+ layout: inner.layout || void 0
1720
+ };
1721
+ }
1722
+ } catch {
1723
+ }
1724
+ const contextContent = generateSectionContext({
1725
+ section: syntheticSection,
1726
+ themeTokens: themeTokensCss,
1727
+ decorators: decoratorList,
1728
+ guardConfig,
1729
+ personality,
1730
+ themeName,
1731
+ recipeName,
1732
+ zoneContext: `This is the primary section (${shell} shell).`,
1733
+ patternSpecs,
1734
+ constraints: essence.dna.constraints,
1735
+ shellInfo: v30ShellInfo
1736
+ });
1737
+ const sectionContextPath = join(contextDir, `section-${syntheticSection.id}.md`);
1738
+ writeFileSync(sectionContextPath, contextContent);
1739
+ contextFiles.push(sectionContextPath);
1740
+ }
1741
+ return {
1742
+ decantrMdPath,
1743
+ contextFiles,
1744
+ cssFiles
1745
+ };
1746
+ }
1747
+ function generateSyntheticSlots(patternId, description) {
1748
+ const desc = description.toLowerCase();
1749
+ const syntheticSlots = {};
1750
+ if (patternId.includes("feature") || desc.includes("feature")) {
1751
+ syntheticSlots["grid"] = "Grid of feature cards (icon + title + description)";
1752
+ syntheticSlots["feature-card"] = "Individual feature with icon, heading, and description text";
1753
+ }
1754
+ if (patternId.includes("pricing") || desc.includes("pricing")) {
1755
+ syntheticSlots["tiers"] = "Pricing tier cards (name, price, features list, CTA button)";
1756
+ syntheticSlots["toggle"] = "Monthly/annual billing toggle (optional)";
1757
+ }
1758
+ if (patternId.includes("testimonial") || desc.includes("testimonial")) {
1759
+ syntheticSlots["quotes"] = "Testimonial cards (quote text, author name, role, avatar)";
1760
+ }
1761
+ if (patternId.includes("cta") || desc.includes("call-to-action") || desc.includes("call to action")) {
1762
+ syntheticSlots["headline"] = "CTA headline text";
1763
+ syntheticSlots["description"] = "Supporting description text";
1764
+ syntheticSlots["actions"] = "CTA button(s)";
1765
+ }
1766
+ if (patternId.includes("how-it-works") || desc.includes("how it works") || desc.includes("timeline") || desc.includes("steps")) {
1767
+ syntheticSlots["steps"] = "Numbered steps (step number, title, description)";
1768
+ }
1769
+ if (patternId.includes("team") || desc.includes("team")) {
1770
+ syntheticSlots["members"] = "Team member cards (avatar, name, role)";
1771
+ }
1772
+ if (patternId.includes("story") || desc.includes("story") || desc.includes("about")) {
1773
+ syntheticSlots["content"] = "Story/about narrative text content";
1774
+ }
1775
+ if (patternId.includes("values") || desc.includes("values")) {
1776
+ syntheticSlots["values"] = "Value cards (icon/emoji, title, description)";
1777
+ }
1778
+ if (patternId.includes("form") || desc.includes("form") || desc.includes("contact")) {
1779
+ syntheticSlots["fields"] = "Form fields (name, email, message, etc.)";
1780
+ syntheticSlots["submit"] = "Submit button";
1781
+ }
1782
+ if (patternId.includes("content") || desc.includes("legal") || desc.includes("privacy") || desc.includes("policy")) {
1783
+ syntheticSlots["body"] = "Long-form text content with headings and paragraphs";
1784
+ syntheticSlots["toc"] = "Table of contents sidebar (optional)";
1785
+ }
1786
+ if (patternId.includes("settings") || desc.includes("settings") || desc.includes("preferences")) {
1787
+ syntheticSlots["sections"] = "Settings sections (label, description, input/toggle)";
1788
+ }
1789
+ if (patternId.includes("security") || desc.includes("security") || desc.includes("password")) {
1790
+ syntheticSlots["sections"] = "Security sections (password change, MFA toggle, session list)";
1791
+ }
1792
+ if (patternId.includes("session") || desc.includes("session")) {
1793
+ syntheticSlots["list"] = "Active sessions list (device, location, last active, revoke button)";
1794
+ }
1795
+ if (patternId.includes("message") || desc.includes("message") || desc.includes("chat")) {
1796
+ syntheticSlots["messages"] = "Message bubbles (user/assistant, content, timestamp)";
1797
+ }
1798
+ if (patternId.includes("input") && desc.includes("chat")) {
1799
+ syntheticSlots["textarea"] = "Auto-expanding message input";
1800
+ syntheticSlots["actions"] = "Attach file button, send button";
1801
+ }
1802
+ if (patternId.includes("empty") || desc.includes("empty")) {
1803
+ syntheticSlots["illustration"] = "Empty state illustration or icon";
1804
+ syntheticSlots["message"] = "Welcome/empty state message";
1805
+ syntheticSlots["suggestions"] = "Suggested actions or prompts";
1806
+ }
1807
+ if (patternId.includes("header") && desc.includes("chat")) {
1808
+ syntheticSlots["title"] = "Conversation title or model name";
1809
+ syntheticSlots["actions"] = "Header action buttons (new chat, settings)";
1810
+ }
1811
+ return syntheticSlots;
1812
+ }
1813
+ function generateSectionContext(input) {
1814
+ const { section, decorators, guardConfig, personality, themeName, recipeName, zoneContext, patternSpecs, recipeHints, constraints, shellInfo } = input;
1815
+ const lines = [];
1816
+ lines.push(`# Section: ${section.id}`);
1817
+ lines.push("");
1818
+ lines.push(`**Role:** ${section.role} | **Shell:** ${section.shell} | **Archetype:** ${section.id}`);
1819
+ lines.push(`**Description:** ${section.description}`);
1820
+ if (shellInfo) {
1821
+ lines.push(`**Shell structure:** ${shellInfo.description}`);
1822
+ lines.push(`**Regions:** ${shellInfo.regions.join(", ")}`);
1823
+ }
1824
+ if (section.dna_overrides) {
1825
+ const parts = [];
1826
+ if (section.dna_overrides.density) parts.push(`density: ${section.dna_overrides.density}`);
1827
+ if (section.dna_overrides.mode) parts.push(`mode: ${section.dna_overrides.mode}`);
1828
+ if (parts.length > 0) {
1829
+ lines.push(`**DNA Overrides:** ${parts.join(", ")}`);
1830
+ }
1831
+ }
1832
+ lines.push("");
1833
+ lines.push("---");
1834
+ lines.push("");
1835
+ lines.push(`**Guard:** ${guardConfig.mode} mode | DNA violations = ${guardConfig.dna_enforcement} | Blueprint violations = ${guardConfig.blueprint_enforcement}`);
1836
+ lines.push("");
1837
+ lines.push(`**Theme tokens:** see \`src/styles/tokens.css\` \u2014 use \`var(--d-primary)\`, \`var(--d-bg)\`, etc.`);
1838
+ lines.push("");
1839
+ if (decorators.length > 0) {
1840
+ const names = decorators.map((d) => d.name).join(", ");
1841
+ lines.push(`**Decorators:** see \`src/styles/decorators.css\` \u2014 available classes: ${names}`);
1842
+ } else {
1843
+ lines.push("**Decorators:** none defined.");
1844
+ }
1845
+ lines.push("");
1846
+ if (recipeHints) {
1847
+ if (recipeHints.preferred && recipeHints.preferred.length > 0) {
1848
+ lines.push(`**Preferred:** ${recipeHints.preferred.join(", ")}`);
1849
+ }
1850
+ if (recipeHints.compositions) {
1851
+ lines.push(`**Compositions:** ${recipeHints.compositions}`);
1852
+ }
1853
+ if (recipeHints.spatialHints) {
1854
+ lines.push(`**Spatial hints:** ${recipeHints.spatialHints}`);
1855
+ }
1856
+ lines.push("");
1857
+ }
1858
+ lines.push("---");
1859
+ lines.push("");
1860
+ if (zoneContext) {
1861
+ lines.push(zoneContext);
1862
+ lines.push("For full app topology, see `.decantr/context/scaffold.md`");
1863
+ lines.push("");
1864
+ }
1865
+ if (section.features.length > 0) {
1866
+ lines.push("## Features");
1867
+ lines.push("");
1868
+ lines.push(section.features.join(", "));
1869
+ lines.push("");
1870
+ lines.push("---");
1871
+ lines.push("");
1872
+ }
1873
+ if (personality.length > 0) {
1874
+ lines.push("## Personality");
1875
+ lines.push("");
1876
+ lines.push(personality.join(", "));
1877
+ lines.push("");
1878
+ lines.push("---");
1879
+ lines.push("");
1880
+ }
1881
+ if (constraints && Object.keys(constraints).length > 0) {
1882
+ lines.push("## Constraints");
1883
+ lines.push("");
1884
+ for (const [key, value] of Object.entries(constraints)) {
1885
+ lines.push(`- **${key}:** ${typeof value === "object" && value !== null ? JSON.stringify(value) : value}`);
1886
+ }
1887
+ lines.push("");
1888
+ lines.push("---");
1889
+ lines.push("");
1890
+ }
1891
+ lines.push("## Pages");
1892
+ lines.push("");
1893
+ for (const page of section.pages) {
1894
+ const route = page.route || `/${section.id}/${page.id}`;
1895
+ const layoutStr = page.layout.map(serializeLayoutItem).join(" \u2192 ");
1896
+ lines.push(`### ${page.id} (${route})`);
1897
+ lines.push("");
1898
+ lines.push(`Layout: ${layoutStr}`);
1899
+ lines.push("");
1900
+ if (page.patterns && page.patterns.length > 0) {
1901
+ lines.push("**Pattern mapping:**");
1902
+ lines.push("| Alias | Pattern | Preset |");
1903
+ lines.push("|-------|---------|--------|");
1904
+ for (const ref of page.patterns) {
1905
+ const alias = ref.as || ref.pattern;
1906
+ const preset = ref.preset || "standard";
1907
+ lines.push(`| ${alias} | ${ref.pattern} | ${preset} |`);
1908
+ }
1909
+ lines.push("");
1910
+ }
1911
+ const patternNames = page.layout.flatMap(extractPatternNames);
1912
+ for (const patternName of patternNames) {
1913
+ const spec = patternSpecs[patternName];
1914
+ if (!spec) continue;
1915
+ lines.push(`#### Pattern: ${patternName}`);
1916
+ lines.push("");
1917
+ lines.push(spec.description);
1918
+ lines.push("");
1919
+ lines.push(`**Components:** ${spec.components.join(", ")}`);
1920
+ lines.push("");
1921
+ lines.push("**Layout slots:**");
1922
+ for (const [slot, desc] of Object.entries(spec.slots)) {
1923
+ lines.push(`- \`${slot}\`: ${desc}`);
1924
+ }
1925
+ lines.push("");
1926
+ }
1927
+ }
1928
+ return lines.join("\n");
1929
+ }
1930
+ function generateScaffoldContext(input) {
1931
+ const { appName, blueprintId, themeName, recipeName, personality, topologyMarkdown, sections, routes, constraints, seo, navigation } = input;
1932
+ const lines = [];
1933
+ lines.push(`# Scaffold: ${appName}`);
1934
+ lines.push("");
1935
+ lines.push(`**Blueprint:** ${blueprintId}`);
1936
+ lines.push(`**Theme:** ${themeName} | **Recipe:** ${recipeName}`);
1937
+ lines.push(`**Personality:** ${personality.join(", ")}`);
1938
+ lines.push("**Guard mode:** creative (no enforcement during initial scaffolding)");
1939
+ lines.push("");
1940
+ lines.push("## App Topology");
1941
+ lines.push("");
1942
+ lines.push(topologyMarkdown);
1943
+ lines.push("");
1944
+ lines.push("## Sections Overview");
1945
+ lines.push("");
1946
+ lines.push("| Section | Role | Shell | Pages | Features |");
1947
+ lines.push("|---------|------|-------|-------|----------|");
1948
+ for (const section of sections) {
1949
+ const pageIds = section.pages.map((p) => p.id).join(", ");
1950
+ const feats = section.features.join(", ") || "none";
1951
+ lines.push(`| ${section.id} | ${section.role} | ${section.shell} | ${pageIds} | ${feats} |`);
1952
+ }
1953
+ lines.push("");
1954
+ lines.push("## Route Map");
1955
+ lines.push("");
1956
+ lines.push("| Route | Section | Page |");
1957
+ lines.push("|-------|---------|------|");
1958
+ for (const [route, entry] of Object.entries(routes)) {
1959
+ lines.push(`| ${route} | ${entry.section} | ${entry.page} |`);
1960
+ }
1961
+ lines.push("");
1962
+ lines.push("## Section Contexts");
1963
+ lines.push("");
1964
+ lines.push("For detailed pattern specs per section, read:");
1965
+ for (const section of sections) {
1966
+ lines.push(`- .decantr/context/section-${section.id}.md`);
1967
+ }
1968
+ lines.push("");
1969
+ const patternUsage = {};
1970
+ for (const section of sections) {
1971
+ for (const page of section.pages) {
1972
+ const names = page.layout.flatMap(extractPatternNames);
1973
+ for (const name of names) {
1974
+ if (!patternUsage[name]) patternUsage[name] = [];
1975
+ const pageLabel = sections.length > 1 ? `${section.id}/${page.id}` : page.id;
1976
+ patternUsage[name].push(pageLabel);
1977
+ }
1978
+ }
1979
+ }
1980
+ const sharedPatterns = Object.entries(patternUsage).filter(([, pages]) => pages.length >= 2);
1981
+ if (sharedPatterns.length > 0) {
1982
+ lines.push("## Shared Components");
1983
+ lines.push("");
1984
+ lines.push("These patterns appear on multiple pages. Consider creating shared components:");
1985
+ lines.push("");
1986
+ lines.push("| Pattern | Used by |");
1987
+ lines.push("|---------|---------|");
1988
+ for (const [pattern, pages] of sharedPatterns) {
1989
+ lines.push(`| ${pattern} | ${pages.join(", ")} |`);
1990
+ }
1991
+ lines.push("");
1992
+ }
1993
+ if (constraints && Object.keys(constraints).length > 0) {
1994
+ lines.push("## Design Constraints");
1995
+ lines.push("");
1996
+ for (const [key, value] of Object.entries(constraints)) {
1997
+ lines.push(`- **${key}:** ${typeof value === "object" && value !== null ? JSON.stringify(value) : value}`);
1998
+ }
1999
+ lines.push("");
2000
+ }
2001
+ if (seo && (seo.schema_org?.length || seo.meta_priorities?.length)) {
2002
+ lines.push("## SEO Hints");
2003
+ lines.push("");
2004
+ if (seo.schema_org && seo.schema_org.length > 0) {
2005
+ lines.push(`**Schema.org types:** ${seo.schema_org.join(", ")}`);
2006
+ }
2007
+ if (seo.meta_priorities && seo.meta_priorities.length > 0) {
2008
+ lines.push(`**Meta priorities:** ${seo.meta_priorities.join(", ")}`);
2009
+ }
2010
+ lines.push("");
2011
+ }
2012
+ if (navigation && (navigation.hotkeys?.length || navigation.command_palette)) {
2013
+ lines.push("## Navigation");
2014
+ lines.push("");
2015
+ if (navigation.command_palette) {
2016
+ lines.push("- Command palette: enabled");
2017
+ }
2018
+ if (navigation.hotkeys && navigation.hotkeys.length > 0) {
2019
+ lines.push(`- Hotkeys: ${navigation.hotkeys.length} configured`);
2020
+ }
2021
+ lines.push("");
2022
+ }
2023
+ return lines.join("\n");
2024
+ }
2025
+
2026
+ // src/registry.ts
2027
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2, readdirSync } from "fs";
2028
+ import { join as join2 } from "path";
2029
+ import { RegistryAPIClient } from "@decantr/registry";
2030
+ var DEFAULT_API_URL = "https://api.decantr.ai/v1";
2031
+ var ALL_CONTENT_TYPES = ["themes", "patterns", "recipes", "blueprints", "archetypes", "shells"];
2032
+ function loadFromCache(cacheDir, contentType, id, namespace) {
2033
+ const nsDir = namespace ? join2(cacheDir, namespace) : cacheDir;
2034
+ const cachePath = id ? join2(nsDir, contentType, `${id}.json`) : join2(nsDir, contentType, "index.json");
2035
+ if (!existsSync2(cachePath)) return null;
2036
+ try {
2037
+ const data = JSON.parse(readFileSync2(cachePath, "utf-8"));
2038
+ return { data, source: { type: "cache" } };
2039
+ } catch {
2040
+ return null;
2041
+ }
2042
+ }
2043
+ function saveToCache(cacheDir, contentType, id, data, namespace = "@official") {
2044
+ const dir = join2(cacheDir, namespace, contentType);
2045
+ mkdirSync2(dir, { recursive: true });
2046
+ const cachePath = id ? join2(dir, `${id}.json`) : join2(dir, "index.json");
2047
+ writeFileSync2(cachePath, JSON.stringify(data, null, 2));
2048
+ }
2049
+ var RegistryClient = class {
2050
+ cacheDir;
2051
+ apiUrl;
2052
+ offline;
2053
+ projectRoot;
2054
+ apiClient;
2055
+ constructor(options = {}) {
2056
+ this.projectRoot = options.projectRoot || process.cwd();
2057
+ this.cacheDir = options.cacheDir || join2(this.projectRoot, ".decantr", "cache");
2058
+ this.apiUrl = options.apiUrl || DEFAULT_API_URL;
2059
+ this.offline = options.offline || false;
2060
+ this.apiClient = new RegistryAPIClient({
2061
+ baseUrl: this.apiUrl,
2062
+ apiKey: options.apiKey
2063
+ });
2064
+ }
2065
+ /**
2066
+ * Load content from .decantr/custom/{contentType}/{id}.json
2067
+ * Works for ALL content types, not just themes.
2068
+ */
2069
+ loadCustomContent(contentType, id) {
2070
+ const customPath = join2(
2071
+ this.projectRoot,
2072
+ ".decantr",
2073
+ "custom",
2074
+ contentType,
2075
+ `${id}.json`
2076
+ );
2077
+ if (!existsSync2(customPath)) return null;
2078
+ try {
2079
+ const data = JSON.parse(readFileSync2(customPath, "utf-8"));
2080
+ return { data, source: { type: "custom", path: customPath } };
2081
+ } catch {
2082
+ return null;
2083
+ }
2084
+ }
2085
+ /**
2086
+ * List all custom content of a given type from .decantr/custom/{type}/
2087
+ */
2088
+ listCustomContent(contentType) {
2089
+ const dir = join2(this.projectRoot, ".decantr", "custom", contentType);
2090
+ if (!existsSync2(dir)) return [];
2091
+ try {
2092
+ return readdirSync(dir).filter((f) => f.endsWith(".json")).map((f) => {
2093
+ const data = JSON.parse(readFileSync2(join2(dir, f), "utf-8"));
2094
+ return { id: data.id || f.replace(".json", ""), ...data };
2095
+ });
2096
+ } catch {
2097
+ return [];
2098
+ }
2099
+ }
2100
+ /**
2101
+ * Unified fetch for a content list.
2102
+ * Resolution: API -> Cache. Custom items are merged into the list.
2103
+ */
2104
+ async fetchContentList(contentType, namespace) {
2105
+ let apiItems = [];
2106
+ let source = { type: "cache" };
2107
+ if (!this.offline) {
2108
+ try {
2109
+ const apiResult = await this.apiClient.listContent(contentType, { namespace });
2110
+ apiItems = apiResult.items;
2111
+ source = { type: "api", url: this.apiUrl };
2112
+ saveToCache(this.cacheDir, contentType, null, apiResult, namespace || "@official");
2113
+ } catch {
2114
+ }
2115
+ }
2116
+ if (apiItems.length === 0) {
2117
+ const cacheResult = loadFromCache(
2118
+ this.cacheDir,
2119
+ contentType,
2120
+ void 0,
2121
+ namespace
2122
+ );
2123
+ if (cacheResult) {
2124
+ apiItems = cacheResult.data.items;
2125
+ source = { type: "cache" };
2126
+ }
2127
+ }
2128
+ const customItems = this.listCustomContent(contentType);
2129
+ const allItems = [...customItems, ...apiItems];
2130
+ return {
2131
+ data: { items: allItems, total: allItems.length },
2132
+ source
2133
+ };
2134
+ }
2135
+ /**
2136
+ * Unified fetch for a single content item.
2137
+ * Resolution: Custom -> API -> Cache
2138
+ */
2139
+ async fetchContentItem(contentType, id, namespace = "@official") {
2140
+ const customId = id.startsWith("custom:") ? id.slice(7) : id;
2141
+ const customResult = this.loadCustomContent(contentType, customId);
2142
+ if (customResult) return customResult;
2143
+ if (id.startsWith("custom:")) return null;
2144
+ if (!this.offline) {
2145
+ for (let attempt = 0; attempt < 2; attempt++) {
2146
+ try {
2147
+ const data = await this.apiClient.getContent(contentType, namespace, id);
2148
+ saveToCache(this.cacheDir, contentType, id, data, namespace);
2149
+ return { data, source: { type: "api", url: this.apiUrl } };
2150
+ } catch (e) {
2151
+ if (process.env.DECANTR_DEBUG) {
2152
+ console.error(` [debug] API fetch ${attempt === 0 ? "failed" : "retry failed"} for ${contentType}/${namespace}/${id}: ${e.message}`);
2153
+ }
2154
+ if (attempt === 0) {
2155
+ await new Promise((r) => setTimeout(r, 500));
2156
+ }
2157
+ }
2158
+ }
2159
+ } else if (process.env.DECANTR_DEBUG) {
2160
+ console.error(` [debug] Skipping API (offline mode) for ${contentType}/${namespace}/${id}`);
2161
+ }
2162
+ return loadFromCache(this.cacheDir, contentType, id, namespace);
2163
+ }
2164
+ // ── Convenience methods (delegate to unified fetch) ──
2165
+ async fetchArchetypes() {
2166
+ return this.fetchContentList("archetypes");
2167
+ }
2168
+ async fetchArchetype(id) {
2169
+ return this.fetchContentItem("archetypes", id);
2170
+ }
2171
+ async fetchBlueprints() {
2172
+ return this.fetchContentList("blueprints");
2173
+ }
2174
+ async fetchBlueprint(id) {
2175
+ return this.fetchContentItem("blueprints", id);
2176
+ }
2177
+ async fetchThemes() {
2178
+ return this.fetchContentList("themes");
2179
+ }
2180
+ async fetchTheme(id) {
2181
+ return this.fetchContentItem("themes", id);
2182
+ }
2183
+ async fetchPatterns() {
2184
+ return this.fetchContentList("patterns");
2185
+ }
2186
+ async fetchPattern(id) {
2187
+ return this.fetchContentItem("patterns", id);
2188
+ }
2189
+ async fetchShells() {
2190
+ return this.fetchContentList("shells");
2191
+ }
2192
+ async fetchShell(id) {
2193
+ return this.fetchContentItem("shells", id);
2194
+ }
2195
+ async fetchRecipes() {
2196
+ return this.fetchContentList("recipes");
2197
+ }
2198
+ async fetchRecipe(id) {
2199
+ return this.fetchContentItem("recipes", id);
2200
+ }
2201
+ /**
2202
+ * Check if API is available.
2203
+ */
2204
+ async checkApiAvailability() {
2205
+ if (this.offline) return false;
2206
+ return this.apiClient.checkHealth();
2207
+ }
2208
+ };
2209
+ async function syncRegistry(cacheDir, apiUrl = DEFAULT_API_URL) {
2210
+ const apiClient = new RegistryAPIClient({ baseUrl: apiUrl });
2211
+ const synced = [];
2212
+ const failed = [];
2213
+ const healthy = await apiClient.checkHealth();
2214
+ if (!healthy) {
2215
+ return { synced: [], failed: ["API unavailable"] };
2216
+ }
2217
+ for (const type of ALL_CONTENT_TYPES) {
2218
+ try {
2219
+ const result = await apiClient.listContent(type, { namespace: "@official" });
2220
+ saveToCache(cacheDir, type, null, result, "@official");
2221
+ for (const item of result.items) {
2222
+ const slug = item.slug;
2223
+ const data = item.data;
2224
+ const innerSlug = data?.id || data?.slug;
2225
+ const cacheKey = slug || innerSlug || item.id;
2226
+ if (cacheKey) {
2227
+ saveToCache(cacheDir, type, cacheKey, item, "@official");
2228
+ }
2229
+ }
2230
+ synced.push(type);
2231
+ } catch {
2232
+ failed.push(type);
2233
+ }
2234
+ }
2235
+ return { synced, failed };
2236
+ }
2237
+
2238
+ export {
2239
+ composeArchetypes,
2240
+ composeSections,
2241
+ deriveZones,
2242
+ deriveTransitions,
2243
+ generateTopologySection,
2244
+ scaffoldProject,
2245
+ scaffoldMinimal,
2246
+ refreshDerivedFiles,
2247
+ RegistryClient,
2248
+ syncRegistry
2249
+ };