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