@decantr/cli 1.2.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.
@@ -1,12 +1,20 @@
1
1
  import {
2
2
  RegistryClient,
3
+ composeArchetypes,
4
+ composeSections,
5
+ deriveTransitions,
6
+ deriveZones,
7
+ generateTopologySection,
8
+ refreshDerivedFiles,
9
+ scaffoldMinimal,
10
+ scaffoldProject,
3
11
  syncRegistry
4
- } from "./chunk-CT5XHG6I.js";
12
+ } from "./chunk-ZQ5FTYKG.js";
5
13
 
6
14
  // src/index.ts
7
- import { readFileSync as readFileSync15, existsSync as existsSync20, readdirSync as readdirSync6 } from "fs";
8
- import { join as join20, dirname as dirname2 } from "path";
9
- import { fileURLToPath as fileURLToPath2 } from "url";
15
+ import { readFileSync as readFileSync14, existsSync as existsSync19, readdirSync as readdirSync6 } from "fs";
16
+ import { join as join19, dirname } from "path";
17
+ import { fileURLToPath } from "url";
10
18
  import { validateEssence as validateEssence2, evaluateGuard, isV3 as isV36 } from "@decantr/essence-spec";
11
19
  import { RegistryAPIClient as RegistryAPIClient2 } from "@decantr/registry";
12
20
 
@@ -384,1801 +392,9 @@ async function runSimplifiedInit(blueprints) {
384
392
  return { choice: "default" };
385
393
  }
386
394
 
387
- // src/scaffold.ts
388
- import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync, appendFileSync } from "fs";
389
- import { join as join2, dirname } from "path";
390
- import { fileURLToPath } from "url";
391
- var __dirname = dirname(fileURLToPath(import.meta.url));
392
- function composeArchetypes(composeEntries, archetypeResults) {
393
- if (composeEntries.length === 0) {
394
- return {
395
- pages: [{ id: "home", layout: ["hero"] }],
396
- features: [],
397
- defaultShell: "sidebar-main"
398
- };
399
- }
400
- const allPages = [];
401
- const allFeatures = [];
402
- let defaultShell = "sidebar-main";
403
- for (let i = 0; i < composeEntries.length; i++) {
404
- const entry = composeEntries[i];
405
- const archetypeId = typeof entry === "string" ? entry : entry.archetype;
406
- const data = archetypeResults.get(archetypeId);
407
- if (!data?.pages) continue;
408
- const isPrimary = i === 0;
409
- if (isPrimary) {
410
- defaultShell = data.pages[0]?.shell || defaultShell;
411
- for (const page of data.pages) {
412
- allPages.push({
413
- id: page.id,
414
- layout: page.default_layout?.length ? page.default_layout : ["hero"],
415
- ...page.shell !== defaultShell ? { shell_override: page.shell } : {}
416
- });
417
- }
418
- } else {
419
- const prefix = typeof entry === "string" ? entry : entry.prefix;
420
- for (const page of data.pages) {
421
- allPages.push({
422
- id: `${prefix}-${page.id}`,
423
- layout: page.default_layout?.length ? page.default_layout : ["hero"],
424
- ...page.shell !== defaultShell ? { shell_override: page.shell } : {}
425
- });
426
- }
427
- }
428
- if (data.features) {
429
- allFeatures.push(...data.features);
430
- }
431
- }
432
- if (allPages.length === 0) {
433
- allPages.push({ id: "home", layout: ["hero"] });
434
- }
435
- return {
436
- pages: allPages,
437
- features: [...new Set(allFeatures)],
438
- defaultShell
439
- };
440
- }
441
- function composeSections(composeEntries, archetypeResults, overrides) {
442
- if (composeEntries.length === 0) {
443
- return {
444
- sections: [{
445
- id: "default",
446
- role: "primary",
447
- shell: "sidebar-main",
448
- features: [],
449
- description: "Default section",
450
- pages: [{ id: "home", layout: ["hero"] }]
451
- }],
452
- features: [],
453
- defaultShell: "sidebar-main"
454
- };
455
- }
456
- const sections = [];
457
- const allFeatures = [];
458
- let defaultShell = "sidebar-main";
459
- const pagesRemoveSet = new Set(overrides?.pages_remove ?? []);
460
- for (let i = 0; i < composeEntries.length; i++) {
461
- const entry = composeEntries[i];
462
- const archetypeId = typeof entry === "string" ? entry : entry.archetype;
463
- const data = archetypeResults.get(archetypeId);
464
- if (!data?.pages) continue;
465
- const isPrimary = i === 0;
466
- if (isPrimary) {
467
- defaultShell = data.pages[0]?.shell || defaultShell;
468
- }
469
- const pages = [];
470
- for (const page of data.pages) {
471
- if (pagesRemoveSet.has(page.id)) continue;
472
- const overriddenPage = overrides?.pages?.[page.id];
473
- pages.push({
474
- id: page.id,
475
- layout: page.default_layout?.length ? page.default_layout : ["hero"],
476
- ...overriddenPage
477
- });
478
- }
479
- sections.push({
480
- id: archetypeId,
481
- role: data.role ?? "primary",
482
- shell: data.pages[0]?.shell || "sidebar-main",
483
- features: data.features ?? [],
484
- description: data.description ?? "",
485
- pages
486
- });
487
- if (data.features) {
488
- allFeatures.push(...data.features);
489
- }
490
- }
491
- if (sections.length === 0) {
492
- sections.push({
493
- id: "default",
494
- role: "primary",
495
- shell: "sidebar-main",
496
- features: [],
497
- description: "Default section",
498
- pages: [{ id: "home", layout: ["hero"] }]
499
- });
500
- }
501
- let features = [...new Set(allFeatures)];
502
- if (overrides?.features_add) {
503
- for (const f of overrides.features_add) {
504
- if (!features.includes(f)) features.push(f);
505
- }
506
- }
507
- if (overrides?.features_remove) {
508
- const removeSet = new Set(overrides.features_remove);
509
- features = features.filter((f) => !removeSet.has(f));
510
- }
511
- return { sections, features, defaultShell };
512
- }
513
- var ZONE_ORDER = ["public", "gateway", "primary", "auxiliary"];
514
- function deriveZones(inputs) {
515
- const zoneMap = /* @__PURE__ */ new Map();
516
- for (const input of inputs) {
517
- const existing = zoneMap.get(input.role);
518
- if (existing) {
519
- existing.archetypes.push(input.archetypeId);
520
- existing.features.push(...input.features);
521
- existing.descriptions.push(input.description);
522
- } else {
523
- zoneMap.set(input.role, {
524
- role: input.role,
525
- archetypes: [input.archetypeId],
526
- shell: input.shell,
527
- features: [...input.features],
528
- descriptions: [input.description]
529
- });
530
- }
531
- }
532
- for (const zone of zoneMap.values()) {
533
- zone.features = [...new Set(zone.features)];
534
- }
535
- return ZONE_ORDER.filter((role) => zoneMap.has(role)).map((role) => zoneMap.get(role));
536
- }
537
- var GATEWAY_TRIGGER_MAP = {
538
- auth: "authentication",
539
- login: "authentication",
540
- mfa: "authentication",
541
- payment: "payment",
542
- subscription: "payment",
543
- checkout: "payment",
544
- onboarding: "onboarding",
545
- "setup-wizard": "onboarding",
546
- welcome: "onboarding",
547
- invite: "invitation",
548
- "access-code": "invitation"
549
- };
550
- function resolveGatewayTrigger(features) {
551
- for (const feature of features) {
552
- const trigger = GATEWAY_TRIGGER_MAP[feature];
553
- if (trigger) return trigger;
554
- }
555
- return "authentication";
556
- }
557
- function deriveTransitions(zones) {
558
- const transitions = [];
559
- const roles = new Set(zones.map((z) => z.role));
560
- const gateway = zones.find((z) => z.role === "gateway");
561
- const gatewayTrigger = gateway ? resolveGatewayTrigger(gateway.features) : "authentication";
562
- const hasApp = roles.has("primary") || roles.has("auxiliary");
563
- const hasGateway = roles.has("gateway");
564
- const hasPublic = roles.has("public");
565
- if (hasPublic && hasGateway) {
566
- transitions.push({ from: "public", to: "gateway", type: "conversion", trigger: gatewayTrigger });
567
- }
568
- if (hasPublic && hasApp && !hasGateway) {
569
- transitions.push({ from: "public", to: "app", type: "conversion", trigger: "navigation" });
570
- }
571
- if (hasGateway && hasApp) {
572
- transitions.push({ from: "gateway", to: "app", type: "gate-pass", trigger: gatewayTrigger });
573
- transitions.push({ from: "app", to: "gateway", type: "gate-return", trigger: gatewayTrigger });
574
- }
575
- if (hasApp && hasPublic) {
576
- transitions.push({ from: "app", to: "public", type: "navigation", trigger: "external" });
577
- }
578
- return transitions;
579
- }
580
- var ZONE_LABELS = {
581
- public: "Public",
582
- gateway: "Gateway",
583
- primary: "App",
584
- auxiliary: "App (auxiliary)"
585
- };
586
- function generateTopologySection(data, personality) {
587
- const lines = [];
588
- lines.push("## Composition Topology");
589
- lines.push("");
590
- lines.push(`**Intent:** ${data.intent}`);
591
- lines.push("");
592
- lines.push("### Zones");
593
- lines.push("");
594
- for (const zone of data.zones) {
595
- const label = ZONE_LABELS[zone.role] || zone.role;
596
- lines.push(`**${label}** \u2014 ${zone.shell} shell`);
597
- lines.push(` Archetypes: ${zone.archetypes.join(", ")}`);
598
- lines.push(` Purpose: ${zone.descriptions.join(" ")}`);
599
- if (personality.length > 0) {
600
- lines.push(` Tone: ${personality.join(", ")}`);
601
- }
602
- if (zone.features.length > 0) {
603
- lines.push(` Features: ${zone.features.join(", ")}`);
604
- }
605
- lines.push("");
606
- }
607
- if (data.transitions.length > 0) {
608
- lines.push("### Zone Transitions");
609
- lines.push("");
610
- for (const t of data.transitions) {
611
- const fromLabel = t.from.charAt(0).toUpperCase() + t.from.slice(1);
612
- const toLabel = t.to.charAt(0).toUpperCase() + t.to.slice(1);
613
- lines.push(` ${fromLabel} \u2192 ${toLabel}: ${t.type} (${t.trigger})`);
614
- }
615
- lines.push("");
616
- }
617
- lines.push("### Default Entry Points");
618
- lines.push("");
619
- lines.push(` Anonymous users enter: ${data.entryPoints.anonymous}`);
620
- lines.push(` Authenticated users enter: ${data.entryPoints.authenticated}`);
621
- lines.push(` Auth redirect target: ${data.entryPoints.authenticated}`);
622
- lines.push("");
623
- return lines.join("\n");
624
- }
625
- var CLI_VERSION = "1.0.0";
626
- function generateTokensCSS(themeData, mode) {
627
- if (!themeData) {
628
- return `/* No theme data available */
629
- :root {
630
- --d-primary: #6366f1;
631
- --d-secondary: #a1a1aa;
632
- --d-accent: #f59e0b;
633
- --d-bg: #18181b;
634
- --d-surface: #1f1f23;
635
- --d-surface-raised: #27272a;
636
- --d-border: #3f3f46;
637
- --d-text: #fafafa;
638
- --d-text-muted: #a1a1aa;
639
- }
640
- `;
641
- }
642
- const seed = themeData.seed || {};
643
- const palette = themeData.palette || {};
644
- const resolvedMode = mode === "auto" ? "dark" : mode;
645
- function buildTokens(tokenMode) {
646
- return {
647
- // Seed colors
648
- "--d-primary": seed.primary || "#6366f1",
649
- "--d-secondary": seed.secondary || "#a1a1aa",
650
- "--d-accent": seed.accent || "#f59e0b",
651
- // Palette colors (mode-aware)
652
- "--d-bg": palette.background?.[tokenMode] || "#18181b",
653
- "--d-surface": palette.surface?.[tokenMode] || "#1f1f23",
654
- "--d-surface-raised": palette["surface-raised"]?.[tokenMode] || "#27272a",
655
- "--d-border": palette.border?.[tokenMode] || "#3f3f46",
656
- "--d-text": palette.text?.[tokenMode] || "#fafafa",
657
- "--d-text-muted": palette["text-muted"]?.[tokenMode] || "#a1a1aa",
658
- "--d-primary-hover": palette["primary-hover"]?.[tokenMode] || seed.primary || "#6366f1",
659
- // Spacing scale
660
- "--d-gap-1": "0.25rem",
661
- "--d-gap-2": "0.5rem",
662
- "--d-gap-3": "0.75rem",
663
- "--d-gap-4": "1rem",
664
- "--d-gap-6": "1.5rem",
665
- "--d-gap-8": "2rem",
666
- "--d-gap-12": "3rem",
667
- // Radii
668
- "--d-radius": "0.5rem",
669
- "--d-radius-sm": "0.25rem",
670
- "--d-radius-lg": "0.75rem",
671
- "--d-radius-xl": "1rem",
672
- "--d-radius-full": "9999px",
673
- // Shadows
674
- "--d-shadow-sm": "0 1px 2px rgba(0,0,0,0.05)",
675
- "--d-shadow": "0 1px 3px rgba(0,0,0,0.1)",
676
- "--d-shadow-md": "0 4px 6px rgba(0,0,0,0.1)",
677
- "--d-shadow-lg": "0 10px 15px rgba(0,0,0,0.1)",
678
- // Status colors
679
- "--d-success": themeData.tokens?.base?.success || "#22c55e",
680
- "--d-error": themeData.tokens?.base?.danger || "#ef4444",
681
- "--d-warning": themeData.tokens?.base?.warning || "#f59e0b",
682
- "--d-info": "#3b82f6"
683
- };
684
- }
685
- const tokens = buildTokens(resolvedMode);
686
- const lines = Object.entries(tokens).map(([key, value]) => ` ${key}: ${value};`).join("\n");
687
- let css = `/* Generated by @decantr/cli */
688
- :root {
689
- ${lines}
690
- }
691
- `;
692
- if (mode === "auto") {
693
- const lightTokens = buildTokens("light");
694
- const paletteKeys = ["--d-bg", "--d-surface", "--d-surface-raised", "--d-border", "--d-text", "--d-text-muted", "--d-primary-hover"];
695
- const lightLines = Object.entries(lightTokens).filter(([key]) => paletteKeys.includes(key)).map(([key, value]) => ` ${key}: ${value};`).join("\n");
696
- css += `
697
- @media (prefers-color-scheme: light) {
698
- :root {
699
- ${lightLines}
700
- }
701
- }
702
- `;
703
- }
704
- return css;
705
- }
706
- function generateDecoratorsCSS(recipeData, themeName) {
707
- if (!recipeData?.decorators) {
708
- return `/* No recipe decorators available */`;
709
- }
710
- const decorators = recipeData.decorators;
711
- const css = [
712
- `/* Generated by @decantr/cli from recipe: ${themeName} */`,
713
- ""
714
- ];
715
- for (const [name, description] of Object.entries(decorators)) {
716
- css.push(generateDecoratorRule(name, description));
717
- css.push("");
718
- }
719
- css.push(`/* Animation keyframes */
720
- @keyframes decantr-fade-in {
721
- from { opacity: 0; transform: translateY(8px); }
722
- to { opacity: 1; transform: translateY(0); }
723
- }
724
-
725
- @keyframes decantr-pulse {
726
- 0%, 100% { opacity: 1; }
727
- 50% { opacity: 0.5; }
728
- }
729
- `);
730
- return css.join("\n");
731
- }
732
- function generateDecoratorRule(name, description) {
733
- const rules = [];
734
- const descLower = description.toLowerCase();
735
- if (descLower.includes("surface background") || descLower.includes("surface elevation")) {
736
- rules.push("background: var(--d-surface)");
737
- } else if (descLower.includes("background") && descLower.includes("theme")) {
738
- rules.push("background: var(--d-bg)");
739
- } else if (descLower.includes("primary-tinted") || descLower.includes("primary background")) {
740
- rules.push("background: color-mix(in srgb, var(--d-primary) 15%, var(--d-surface))");
741
- }
742
- if (descLower.includes("1px border") || descLower.includes("subtle border")) {
743
- rules.push("border: 1px solid var(--d-border)");
744
- } else if (descLower.includes("border") && !descLower.includes("radius")) {
745
- rules.push("border: 1px solid var(--d-border)");
746
- }
747
- const radiusMatch = descLower.match(/(\d+)px radius/);
748
- if (radiusMatch) {
749
- rules.push(`border-radius: ${radiusMatch[1]}px`);
750
- } else if (descLower.includes("radius") || descLower.includes("rounded")) {
751
- rules.push("border-radius: var(--d-radius)");
752
- }
753
- if (descLower.includes("hover shadow") || descLower.includes("shadow transition")) {
754
- rules.push("transition: box-shadow 0.15s ease");
755
- }
756
- if (descLower.includes("elevation") || descLower.includes("shadow")) {
757
- rules.push("box-shadow: var(--d-shadow)");
758
- }
759
- if (descLower.includes("entrance animation") || descLower.includes("fade")) {
760
- rules.push("animation: decantr-fade-in 0.2s ease-out");
761
- }
762
- if (descLower.includes("pulse animation") || descLower.includes("skeleton")) {
763
- rules.push("animation: decantr-pulse 1.5s ease-in-out infinite");
764
- }
765
- if (descLower.includes("blur") || descLower.includes("glass")) {
766
- rules.push("backdrop-filter: blur(8px)");
767
- }
768
- if (descLower.includes("right-aligned")) {
769
- rules.push("margin-left: auto");
770
- } else if (descLower.includes("left-aligned")) {
771
- rules.push("margin-right: auto");
772
- }
773
- if (descLower.includes("message bubble") || descLower.includes("bubble")) {
774
- rules.push("padding: var(--d-gap-3) var(--d-gap-4)");
775
- rules.push("border-radius: var(--d-radius-lg)");
776
- rules.push("max-width: 80%");
777
- }
778
- if (rules.length === 0) {
779
- return `/* .${name}: ${description} */`;
780
- }
781
- return `.${name} {
782
- ${rules.join(";\n ")};
783
- }`;
784
- }
785
- function serializeLayoutItem(item) {
786
- if (typeof item === "string") {
787
- return item;
788
- }
789
- if (typeof item === "object" && item !== null) {
790
- const obj = item;
791
- if (typeof obj.pattern === "string") {
792
- const preset = obj.preset ? ` (${obj.preset})` : "";
793
- const alias = obj.as ? ` as ${obj.as}` : "";
794
- return `${obj.pattern}${preset}${alias}`;
795
- }
796
- if (Array.isArray(obj.cols)) {
797
- const cols = obj.cols.map(serializeLayoutItem).join(" | ");
798
- const breakpoint = obj.at ? ` @${obj.at}` : "";
799
- return `[${cols}]${breakpoint}`;
800
- }
801
- }
802
- return "custom";
803
- }
804
- function extractPatternNames(item) {
805
- if (typeof item === "string") {
806
- return [item];
807
- }
808
- if (typeof item === "object" && item !== null) {
809
- const obj = item;
810
- if (typeof obj.pattern === "string") {
811
- return [obj.pattern];
812
- }
813
- if (Array.isArray(obj.cols)) {
814
- return obj.cols.flatMap(extractPatternNames);
815
- }
816
- }
817
- return [];
818
- }
819
- function loadTemplate(name) {
820
- const fromDist = join2(__dirname, "..", "src", "templates", name);
821
- if (existsSync2(fromDist)) {
822
- return readFileSync2(fromDist, "utf-8");
823
- }
824
- const fromSrc = join2(__dirname, "templates", name);
825
- if (existsSync2(fromSrc)) {
826
- return readFileSync2(fromSrc, "utf-8");
827
- }
828
- throw new Error(`Template not found: ${name}`);
829
- }
830
- function renderTemplate(template, vars) {
831
- let result = template;
832
- for (const [key, value] of Object.entries(vars)) {
833
- result = result.replace(new RegExp(`\\{\\{${key}\\}\\}`, "g"), value);
834
- }
835
- return result;
836
- }
837
- function resolvePatternAlias(item, patterns) {
838
- if (!patterns) return item;
839
- if (typeof item === "string") {
840
- const patternDef = patterns.find((p) => p.as === item);
841
- if (patternDef) {
842
- if (patternDef.preset) {
843
- return { pattern: patternDef.pattern, preset: patternDef.preset };
844
- }
845
- return patternDef.pattern;
846
- }
847
- return item;
848
- }
849
- if (typeof item === "object" && item !== null) {
850
- const obj = item;
851
- if (Array.isArray(obj.cols)) {
852
- return {
853
- ...obj,
854
- cols: obj.cols.map((col) => resolvePatternAlias(col, patterns))
855
- };
856
- }
857
- }
858
- return item;
859
- }
860
- function buildEssence(options, archetypeData) {
861
- let structure = [
862
- { id: "home", shell: options.shell, layout: ["hero"] }
863
- ];
864
- let features = options.features;
865
- if (archetypeData?.pages) {
866
- structure = archetypeData.pages.map((p) => {
867
- const resolvedLayout = (p.default_layout?.length ? p.default_layout : ["hero"]).map((item) => resolvePatternAlias(item, p.patterns));
868
- return {
869
- id: p.id,
870
- shell: p.shell || options.shell,
871
- layout: resolvedLayout
872
- };
873
- });
874
- }
875
- if (archetypeData?.features) {
876
- features = [.../* @__PURE__ */ new Set([...features, ...archetypeData.features])];
877
- }
878
- const contentGapMap = {
879
- compact: "_gap2",
880
- comfortable: "_gap4",
881
- spacious: "_gap6"
882
- };
883
- const archetype = options.archetype || "custom";
884
- const essence = {
885
- version: "2.0.0",
886
- archetype,
887
- theme: {
888
- style: options.theme,
889
- mode: options.mode,
890
- recipe: options.theme,
891
- // Recipe defaults to theme
892
- shape: options.shape
893
- },
894
- personality: options.personality,
895
- platform: {
896
- type: "spa",
897
- routing: "hash"
898
- },
899
- structure,
900
- features,
901
- guard: {
902
- enforce_style: true,
903
- enforce_recipe: true,
904
- mode: options.guard
905
- },
906
- density: {
907
- level: options.density,
908
- content_gap: contentGapMap[options.density] || "_gap4"
909
- },
910
- target: options.target
911
- };
912
- if (options.accessibility) {
913
- essence.accessibility = options.accessibility;
914
- }
915
- return essence;
916
- }
917
- function buildEssenceV3(options, archetypeData, themeHints, recipeHints) {
918
- let pages = [
919
- { id: "home", layout: ["hero"] }
920
- ];
921
- let features = options.features;
922
- let defaultShell = options.shell || "sidebar-main";
923
- if (archetypeData?.pages) {
924
- defaultShell = archetypeData.pages[0]?.shell || defaultShell;
925
- pages = archetypeData.pages.map((p) => {
926
- const resolvedLayout = (p.default_layout?.length ? p.default_layout : ["hero"]).map((item) => resolvePatternAlias(item, p.patterns));
927
- return {
928
- id: p.id,
929
- ...p.shell !== defaultShell ? { shell_override: p.shell } : {},
930
- layout: resolvedLayout
931
- };
932
- });
933
- }
934
- if (archetypeData?.features) {
935
- features = [.../* @__PURE__ */ new Set([...features, ...archetypeData.features])];
936
- }
937
- const densityLevelMap = {
938
- compact: "_gap2",
939
- comfortable: "_gap4",
940
- spacious: "_gap6"
941
- };
942
- const shapeRadiusMap = {
943
- pill: 12,
944
- rounded: 8,
945
- sharp: 2
946
- };
947
- const guardModeMap = {
948
- strict: { mode: "strict", dna_enforcement: "error", blueprint_enforcement: "warn" },
949
- guided: { mode: "guided", dna_enforcement: "error", blueprint_enforcement: "off" },
950
- creative: { mode: "creative", dna_enforcement: "off", blueprint_enforcement: "off" }
951
- };
952
- const dna = {
953
- theme: {
954
- style: options.theme,
955
- mode: options.mode,
956
- recipe: options.theme,
957
- shape: options.shape
958
- },
959
- spacing: {
960
- base_unit: 4,
961
- scale: "linear",
962
- density: options.density,
963
- content_gap: densityLevelMap[options.density] || "_gap4"
964
- },
965
- typography: {
966
- scale: themeHints?.typography_hints?.scale || "modular",
967
- heading_weight: themeHints?.typography_hints?.heading_weight || 600,
968
- body_weight: themeHints?.typography_hints?.body_weight || 400
969
- },
970
- color: {
971
- palette: "semantic",
972
- accent_count: 1,
973
- cvd_preference: options.accessibility?.cvd_preference || "auto"
974
- },
975
- radius: {
976
- philosophy: recipeHints?.radius_hints?.philosophy || options.shape,
977
- base: recipeHints?.radius_hints?.base || shapeRadiusMap[options.shape] || 8
978
- },
979
- elevation: {
980
- system: "layered",
981
- max_levels: 3
982
- },
983
- motion: {
984
- preference: recipeHints?.animation?.preference || themeHints?.motion_hints?.preference || "subtle",
985
- duration_scale: 1,
986
- reduce_motion: themeHints?.motion_hints?.reduce_motion_default ?? true
987
- },
988
- accessibility: {
989
- wcag_level: options.accessibility?.wcag_level || "AA",
990
- focus_visible: true,
991
- skip_nav: true
992
- },
993
- personality: options.personality
994
- };
995
- const blueprint = {
996
- shell: defaultShell,
997
- pages,
998
- features
999
- };
1000
- const meta = {
1001
- archetype: options.archetype || "custom",
1002
- target: options.target,
1003
- platform: {
1004
- type: "spa",
1005
- routing: "hash"
1006
- },
1007
- guard: guardModeMap[options.guard] || guardModeMap.guided
1008
- };
1009
- return {
1010
- version: "3.0.0",
1011
- dna,
1012
- blueprint,
1013
- meta
1014
- };
1015
- }
1016
- var CSS_APPROACH_CONTENT = `## CSS Implementation
1017
-
1018
- This project uses **@decantr/css** for layout atoms and the generated CSS files for theme tokens and recipe decorators.
1019
-
1020
- ### Setup
1021
-
1022
- \`\`\`javascript
1023
- // 1. Import the atoms runtime
1024
- import { css } from '@decantr/css';
1025
-
1026
- // 2. Import generated CSS files (created by decantr init)
1027
- import './styles/tokens.css'; // Theme tokens (--d-primary, --d-surface, etc.)
1028
- import './styles/decorators.css'; // Recipe decorators
1029
- \`\`\`
1030
-
1031
- ### Using Atoms
1032
-
1033
- The \`css()\` function processes atom strings and injects CSS at runtime:
1034
-
1035
- \`\`\`jsx
1036
- // Layout atoms
1037
- <div className={css('_flex _col _gap4 _p4')}>
1038
- <h1 className={css('_heading1')}>Title</h1>
1039
- <p className={css('_textsm _fgmuted')}>Description</p>
1040
- </div>
1041
-
1042
- // Responsive prefixes (mobile-first)
1043
- <div className={css('_gc1 _sm:gc2 _lg:gc4')}>
1044
- {/* 1 col -> 2 cols at 640px -> 4 cols at 1024px */}
1045
- </div>
1046
- \`\`\`
1047
-
1048
- ### Atom Reference
1049
-
1050
- #### Display
1051
- | Atom | CSS |
1052
- |------|-----|
1053
- | \`_flex\` | \`display:flex\` |
1054
- | \`_grid\` | \`display:grid\` |
1055
- | \`_block\` | \`display:block\` |
1056
- | \`_inline\` | \`display:inline\` |
1057
- | \`_inlineflex\` | \`display:inline-flex\` |
1058
- | \`_none\` | \`display:none\` |
1059
- | \`_contents\` | \`display:contents\` |
1060
-
1061
- #### Flexbox
1062
- | Atom | CSS |
1063
- |------|-----|
1064
- | \`_col\` | \`flex-direction:column\` |
1065
- | \`_row\` | \`flex-direction:row\` |
1066
- | \`_colrev\` | \`flex-direction:column-reverse\` |
1067
- | \`_wrap\` | \`flex-wrap:wrap\` |
1068
- | \`_nowrap\` | \`flex-wrap:nowrap\` |
1069
- | \`_flex1\` | \`flex:1\` |
1070
- | \`_flex0\` | \`flex:none\` |
1071
- | \`_flexauto\` | \`flex:auto\` |
1072
- | \`_grow\` | \`flex-grow:1\` |
1073
- | \`_grow0\` | \`flex-grow:0\` |
1074
- | \`_shrink0\` | \`flex-shrink:0\` |
1075
-
1076
- #### Alignment
1077
- | Atom | CSS |
1078
- |------|-----|
1079
- | \`_aic\` | \`align-items:center\` |
1080
- | \`_aifs\` | \`align-items:flex-start\` |
1081
- | \`_aife\` | \`align-items:flex-end\` |
1082
- | \`_aist\` | \`align-items:stretch\` |
1083
- | \`_aibl\` | \`align-items:baseline\` |
1084
- | \`_jcc\` | \`justify-content:center\` |
1085
- | \`_jcfs\` | \`justify-content:flex-start\` |
1086
- | \`_jcfe\` | \`justify-content:flex-end\` |
1087
- | \`_jcsb\` | \`justify-content:space-between\` |
1088
- | \`_jcsa\` | \`justify-content:space-around\` |
1089
- | \`_jcse\` | \`justify-content:space-evenly\` |
1090
- | \`_pic\` | \`place-items:center\` |
1091
- | \`_pcc\` | \`place-content:center\` |
1092
-
1093
- #### Spacing (scale: 0, 1, 2, 3, 4, 5, 6, 8, 10, 12, 16, 20, 24, ...)
1094
- | Atom | CSS | Notes |
1095
- |------|-----|-------|
1096
- | \`_gap{n}\` | \`gap:{scale}\` | e.g. \`_gap4\` = \`gap:1rem\` |
1097
- | \`_gx{n}\` | \`column-gap:{scale}\` | horizontal gap |
1098
- | \`_gy{n}\` | \`row-gap:{scale}\` | vertical gap |
1099
- | \`_p{n}\` | \`padding:{scale}\` | all sides |
1100
- | \`_pt{n}\`, \`_pr{n}\`, \`_pb{n}\`, \`_pl{n}\` | directional padding | top/right/bottom/left |
1101
- | \`_px{n}\` | \`padding-inline:{scale}\` | horizontal |
1102
- | \`_py{n}\` | \`padding-block:{scale}\` | vertical |
1103
- | \`_m{n}\` | \`margin:{scale}\` | same as padding variants |
1104
- | \`_mx{n}\`, \`_my{n}\` | inline/block margin | horizontal/vertical |
1105
-
1106
- #### Sizing
1107
- | Atom | CSS |
1108
- |------|-----|
1109
- | \`_wfull\` / \`_w100\` | \`width:100%\` |
1110
- | \`_hfull\` / \`_h100\` | \`height:100%\` |
1111
- | \`_wscreen\` | \`width:100vw\` |
1112
- | \`_hscreen\` | \`height:100vh\` |
1113
- | \`_wfit\` | \`width:fit-content\` |
1114
- | \`_hfit\` | \`height:fit-content\` |
1115
- | \`_wauto\` | \`width:auto\` |
1116
- | \`_minw0\` | \`min-width:0\` |
1117
- | \`_minh0\` | \`min-height:0\` |
1118
- | \`_w{n}\`, \`_h{n}\` | width/height from spacing scale |
1119
- | \`_minw{n}\`, \`_maxw{n}\` | min/max width from scale |
1120
-
1121
- #### Text Size
1122
- | Atom | Size | Line-height |
1123
- |------|------|-------------|
1124
- | \`_textxs\` | 0.75rem | 1rem |
1125
- | \`_textsm\` | 0.875rem | 1.25rem |
1126
- | \`_textbase\` | 1rem | 1.5rem |
1127
- | \`_textlg\` | 1.125rem | 1.75rem |
1128
- | \`_textxl\` | 1.25rem | 1.75rem |
1129
- | \`_text2xl\` | 1.5rem | 2rem |
1130
- | \`_text3xl\` | 1.875rem | 2.25rem |
1131
- | \`_heading1\`-\`_heading6\` | Heading presets (size + weight) |
1132
-
1133
- #### Text Style
1134
- | Atom | CSS |
1135
- |------|-----|
1136
- | \`_fontbold\` | \`font-weight:700\` |
1137
- | \`_fontsemi\` | \`font-weight:600\` |
1138
- | \`_fontmedium\` | \`font-weight:500\` |
1139
- | \`_fontlight\` | \`font-weight:300\` |
1140
- | \`_italic\` | \`font-style:italic\` |
1141
- | \`_underline\` | \`text-decoration:underline\` |
1142
- | \`_uppercase\` | \`text-transform:uppercase\` |
1143
- | \`_truncate\` | overflow ellipsis + nowrap |
1144
- | \`_textl\`, \`_textc\`, \`_textr\` | text-align left/center/right |
1145
-
1146
- #### Color (theme variable based)
1147
- | Atom | CSS |
1148
- |------|-----|
1149
- | \`_bgprimary\` | \`background:var(--d-primary)\` |
1150
- | \`_bgsurface\` | \`background:var(--d-surface)\` |
1151
- | \`_bgsurface0\`-\`_bgsurface2\` | surface elevation layers |
1152
- | \`_bgmuted\` | \`background:var(--d-muted)\` |
1153
- | \`_bgbg\` | \`background:var(--d-bg)\` |
1154
- | \`_bgsuccess\`, \`_bgerror\`, \`_bgwarning\`, \`_bginfo\` | status backgrounds |
1155
- | \`_fgprimary\` | \`color:var(--d-primary)\` |
1156
- | \`_fgtext\` | \`color:var(--d-text)\` |
1157
- | \`_fgmuted\` | \`color:var(--d-text-muted)\` |
1158
- | \`_fgsuccess\`, \`_fgerror\`, \`_fgwarning\`, \`_fginfo\` | status text |
1159
- | \`_bcborder\` | \`border-color:var(--d-border)\` |
1160
-
1161
- #### Overflow & Whitespace
1162
- | Atom | CSS |
1163
- |------|-----|
1164
- | \`_overhidden\` | \`overflow:hidden\` |
1165
- | \`_overauto\` | \`overflow:auto\` |
1166
- | \`_overscroll\` | \`overflow:scroll\` |
1167
- | \`_overxauto\`, \`_overyauto\` | axis-specific overflow |
1168
- | \`_nowraptext\` | \`white-space:nowrap\` |
1169
- | \`_prewrap\` | \`white-space:pre-wrap\` |
1170
- | \`_breakword\` | \`overflow-wrap:break-word\` |
1171
-
1172
- #### Cursor & Interaction
1173
- | Atom | CSS |
1174
- |------|-----|
1175
- | \`_pointer\` | \`cursor:pointer\` |
1176
- | \`_cursordefault\` | \`cursor:default\` |
1177
- | \`_notallowed\` | \`cursor:not-allowed\` |
1178
- | \`_grab\` | \`cursor:grab\` |
1179
- | \`_selectnone\` | \`user-select:none\` |
1180
- | \`_ptrnone\` | \`pointer-events:none\` |
1181
-
1182
- #### Position & Layout
1183
- | Atom | CSS |
1184
- |------|-----|
1185
- | \`_rel\` | \`position:relative\` |
1186
- | \`_abs\` | \`position:absolute\` |
1187
- | \`_fixed\` | \`position:fixed\` |
1188
- | \`_sticky\` | \`position:sticky\` |
1189
- | \`_inset0\` | \`inset:0\` |
1190
- | \`_top0\`, \`_right0\`, \`_bottom0\`, \`_left0\` | edge positioning |
1191
- | \`_z10\`-\`_z50\` | z-index scale |
1192
-
1193
- #### Grid
1194
- | Atom | CSS |
1195
- |------|-----|
1196
- | \`_gc1\`-\`_gc12\` | \`grid-template-columns:repeat(N,...)\` |
1197
- | \`_gr1\`-\`_gr6\` | \`grid-template-rows:repeat(N,...)\` |
1198
- | \`_span1\`-\`_span12\`, \`_spanfull\` | column span |
1199
- | \`_rowspan1\`-\`_rowspan6\` | row span |
1200
-
1201
- #### Visual
1202
- | Atom | CSS |
1203
- |------|-----|
1204
- | \`_rounded\` | \`border-radius:var(--d-radius)\` |
1205
- | \`_roundedfull\` | \`border-radius:9999px\` |
1206
- | \`_roundedsm\`, \`_roundedlg\`, \`_roundedxl\` | radius variants |
1207
- | \`_shadow\`, \`_shadowmd\`, \`_shadowlg\` | box-shadow presets |
1208
- | \`_bordernone\` | \`border:none\` |
1209
- | \`_bw{n}\` | \`border-width:{n}px\` |
1210
- | \`_op0\`-\`_op100\` | opacity (0, 25, 50, 75, 100) |
1211
- | \`_trans\` | \`transition:all 0.15s ease\` |
1212
- | \`_visible\`, \`_invisible\` | visibility |
1213
-
1214
- ### CSS Architecture
1215
-
1216
- The CSS is organized into two parts:
1217
-
1218
- 1. **Atoms (@decantr/css)** - Layout utilities injected at runtime into \`@layer d.atoms\`
1219
- 2. **Generated CSS files** - Theme tokens and recipe decorators created during scaffold
1220
-
1221
- \`\`\`
1222
- src/styles/
1223
- tokens.css # :root { --d-primary: #...; --d-surface: #...; }
1224
- decorators.css # .recipe-card { ... }
1225
- \`\`\`
1226
-
1227
- ### Variable Naming Convention
1228
-
1229
- | Prefix | Purpose | Example |
1230
- |--------|---------|---------|
1231
- | \`--d-\` | Core Decantr tokens | \`--d-primary\`, \`--d-bg\` |
1232
- | \`--d-gap-{n}\` | Spacing tokens | \`--d-gap-4\`, \`--d-gap-8\` |
1233
- | \`--d-radius\` | Border radius | \`--d-radius\`, \`--d-radius-lg\` |`;
1234
- function generateDecantrMdV31(guardMode, cssApproach) {
1235
- const template = loadTemplate("DECANTR.md.template");
1236
- return renderTemplate(template, {
1237
- GUARD_MODE: guardMode,
1238
- CSS_APPROACH: cssApproach
1239
- });
1240
- }
1241
- function generateProjectJson(detected, options, registrySource) {
1242
- const now = (/* @__PURE__ */ new Date()).toISOString();
1243
- const data = {
1244
- detected: {
1245
- framework: detected.framework,
1246
- version: detected.version || null,
1247
- packageManager: detected.packageManager,
1248
- hasTypeScript: detected.hasTypeScript,
1249
- hasTailwind: detected.hasTailwind,
1250
- existingRuleFiles: detected.existingRuleFiles
1251
- },
1252
- overrides: {
1253
- framework: options.target !== detected.framework ? options.target : null
1254
- },
1255
- sync: {
1256
- status: registrySource === "api" ? "synced" : "needs-sync",
1257
- lastSync: now,
1258
- registrySource,
1259
- cachedContent: {
1260
- archetypes: [],
1261
- patterns: [],
1262
- themes: [],
1263
- recipes: []
1264
- }
1265
- },
1266
- initialized: {
1267
- at: now,
1268
- via: "cli",
1269
- version: CLI_VERSION,
1270
- flags: buildFlagsString(options)
1271
- }
1272
- };
1273
- return JSON.stringify(data, null, 2);
1274
- }
1275
- function buildFlagsString(options) {
1276
- const flags = [];
1277
- if (options.blueprint) flags.push(`--blueprint=${options.blueprint}`);
1278
- if (options.theme) flags.push(`--theme=${options.theme}`);
1279
- if (options.mode) flags.push(`--mode=${options.mode}`);
1280
- if (options.guard) flags.push(`--guard=${options.guard}`);
1281
- return flags.join(" ");
1282
- }
1283
- function generateTaskContext(templateName, essence) {
1284
- const template = loadTemplate(templateName);
1285
- const defaultShell = essence.structure[0]?.shell || "sidebar-main";
1286
- const layout = essence.structure[0]?.layout.map(serializeLayoutItem).join(", ") || "none";
1287
- const scaffoldStructure = essence.structure.map((p) => {
1288
- const patterns = p.layout.length > 0 ? `
1289
- - Patterns: ${p.layout.map(serializeLayoutItem).join(", ")}` : "";
1290
- return `- **${p.id}** (${p.shell})${patterns}`;
1291
- }).join("\n");
1292
- const vars = {
1293
- TARGET: essence.target,
1294
- THEME_STYLE: essence.theme.style,
1295
- THEME_MODE: essence.theme.mode,
1296
- THEME_RECIPE: essence.theme.recipe,
1297
- DEFAULT_SHELL: defaultShell,
1298
- GUARD_MODE: essence.guard.mode,
1299
- LAYOUT: layout,
1300
- DENSITY: essence.density.level,
1301
- CONTENT_GAP: essence.density.content_gap,
1302
- SCAFFOLD_STRUCTURE: scaffoldStructure
1303
- };
1304
- return renderTemplate(template, vars);
1305
- }
1306
- function generateEssenceSummary(essence) {
1307
- const template = loadTemplate("essence-summary.md.template");
1308
- const pagesTable = `| Page | Shell | Layout |
1309
- |------|-------|--------|
1310
- ${essence.structure.map((p) => `| ${p.id} | ${p.shell} | ${p.layout.map(serializeLayoutItem).join(", ") || "none"} |`).join("\n")}`;
1311
- const featuresList = essence.features.length > 0 ? essence.features.map((f) => `- ${f}`).join("\n") : "- No features specified";
1312
- const dnaEnforcement = essence.guard.enforce_style ? "error" : "off";
1313
- const blueprintEnforcement = essence.guard.enforce_recipe ? "warn" : "off";
1314
- const vars = {
1315
- ARCHETYPE: essence.archetype || "custom",
1316
- BLUEPRINT: essence.blueprint || "none",
1317
- PERSONALITY: essence.personality.join(", "),
1318
- TARGET: essence.target,
1319
- THEME_STYLE: essence.theme.style,
1320
- THEME_MODE: essence.theme.mode,
1321
- THEME_RECIPE: essence.theme.recipe,
1322
- SHAPE: essence.theme.shape,
1323
- PAGES_TABLE: pagesTable,
1324
- FEATURES_LIST: featuresList,
1325
- GUARD_MODE: essence.guard.mode,
1326
- ENFORCE_STYLE: String(essence.guard.enforce_style),
1327
- ENFORCE_RECIPE: String(essence.guard.enforce_recipe),
1328
- DNA_ENFORCEMENT: dnaEnforcement,
1329
- BLUEPRINT_ENFORCEMENT: blueprintEnforcement,
1330
- DENSITY: essence.density.level,
1331
- CONTENT_GAP: essence.density.content_gap,
1332
- LAST_UPDATED: (/* @__PURE__ */ new Date()).toISOString()
1333
- };
1334
- return renderTemplate(template, vars);
1335
- }
1336
- function generateEssenceSummaryV3(essence) {
1337
- const template = loadTemplate("essence-summary.md.template");
1338
- const blueprint = essence.blueprint;
1339
- const sections = blueprint.sections || [];
1340
- const flatPages = blueprint.pages || [];
1341
- let pagesTable;
1342
- if (sections.length > 0) {
1343
- const rows = sections.flatMap(
1344
- (s) => s.pages.map((p) => `| ${p.id} | ${s.shell} | ${p.layout.map(serializeLayoutItem).join(", ") || "none"} |`)
1345
- );
1346
- pagesTable = `| Page | Shell | Layout |
1347
- |------|-------|--------|
1348
- ${rows.join("\n")}`;
1349
- } else {
1350
- const shell = blueprint.shell ?? "sidebar-main";
1351
- const rows = flatPages.map((p) => `| ${p.id} | ${shell} | ${p.layout.map(serializeLayoutItem).join(", ") || "none"} |`);
1352
- pagesTable = `| Page | Shell | Layout |
1353
- |------|-------|--------|
1354
- ${rows.join("\n")}`;
1355
- }
1356
- const features = blueprint.features || [];
1357
- const featuresList = features.length > 0 ? features.map((f) => `- ${f}`).join("\n") : "- No features specified";
1358
- const vars = {
1359
- ARCHETYPE: essence.meta.archetype || "custom",
1360
- BLUEPRINT: "",
1361
- PERSONALITY: (essence.dna.personality || []).join(", "),
1362
- TARGET: essence.meta.target ?? "",
1363
- THEME_STYLE: essence.dna.theme.style,
1364
- THEME_MODE: essence.dna.theme.mode,
1365
- THEME_RECIPE: essence.dna.theme.recipe ?? "",
1366
- SHAPE: essence.dna.theme.shape ?? "",
1367
- PAGES_TABLE: pagesTable,
1368
- FEATURES_LIST: featuresList,
1369
- GUARD_MODE: essence.meta.guard.mode,
1370
- ENFORCE_STYLE: essence.meta.guard.dna_enforcement || "error",
1371
- ENFORCE_RECIPE: essence.meta.guard.blueprint_enforcement || "warn",
1372
- DNA_ENFORCEMENT: essence.meta.guard.dna_enforcement || "error",
1373
- BLUEPRINT_ENFORCEMENT: essence.meta.guard.blueprint_enforcement || "warn",
1374
- DENSITY: essence.dna.spacing?.density || "comfortable",
1375
- CONTENT_GAP: essence.dna.spacing?.content_gap || "_gap4",
1376
- LAST_UPDATED: (/* @__PURE__ */ new Date()).toISOString()
1377
- };
1378
- return renderTemplate(template, vars);
1379
- }
1380
- function updateGitignore(projectRoot) {
1381
- const gitignorePath = join2(projectRoot, ".gitignore");
1382
- const cacheEntry = ".decantr/cache/";
1383
- if (existsSync2(gitignorePath)) {
1384
- const content = readFileSync2(gitignorePath, "utf-8");
1385
- if (!content.includes(cacheEntry)) {
1386
- appendFileSync(gitignorePath, `
1387
- # Decantr cache
1388
- ${cacheEntry}
1389
- `);
1390
- return true;
1391
- }
1392
- return false;
1393
- } else {
1394
- writeFileSync(gitignorePath, `# Decantr cache
1395
- ${cacheEntry}
1396
- `);
1397
- return true;
1398
- }
1399
- }
1400
- async function scaffoldProject(projectRoot, options, detected, registry, archetypeData, registrySource = "cache", themeData, recipeData, topologyMarkdown, composedSections, routeMap, patternSpecs, blueprintData) {
1401
- const essenceV3 = buildEssenceV3(options, archetypeData, themeData, recipeData);
1402
- const essence = buildEssence(options, archetypeData);
1403
- const decantrDir = join2(projectRoot, ".decantr");
1404
- const contextDir = join2(decantrDir, "context");
1405
- const cacheDir = join2(decantrDir, "cache");
1406
- mkdirSync(contextDir, { recursive: true });
1407
- mkdirSync(cacheDir, { recursive: true });
1408
- const essencePath = join2(projectRoot, "decantr.essence.json");
1409
- writeFileSync(essencePath, JSON.stringify(essenceV3, null, 2) + "\n");
1410
- const projectJsonPath = join2(decantrDir, "project.json");
1411
- writeFileSync(projectJsonPath, generateProjectJson(detected, options, registrySource));
1412
- const contextFiles = [];
1413
- const scaffoldPath = join2(contextDir, "task-scaffold.md");
1414
- writeFileSync(scaffoldPath, generateTaskContext("task-scaffold.md.template", essence));
1415
- contextFiles.push(scaffoldPath);
1416
- const addPagePath = join2(contextDir, "task-add-page.md");
1417
- writeFileSync(addPagePath, generateTaskContext("task-add-page.md.template", essence));
1418
- contextFiles.push(addPagePath);
1419
- const modifyPath = join2(contextDir, "task-modify.md");
1420
- writeFileSync(modifyPath, generateTaskContext("task-modify.md.template", essence));
1421
- contextFiles.push(modifyPath);
1422
- const summaryPath = join2(contextDir, "essence-summary.md");
1423
- writeFileSync(summaryPath, generateEssenceSummary(essence));
1424
- contextFiles.push(summaryPath);
1425
- if (composedSections) {
1426
- essenceV3.version = "3.1.0";
1427
- essenceV3.blueprint = {
1428
- sections: composedSections.sections,
1429
- features: composedSections.features,
1430
- routes: routeMap || {}
1431
- };
1432
- if (blueprintData?.personality?.length) {
1433
- essenceV3.dna.personality = blueprintData.personality;
1434
- }
1435
- if (blueprintData?.design_constraints) {
1436
- essenceV3.dna.constraints = blueprintData.design_constraints;
1437
- }
1438
- if (blueprintData?.seo_hints) {
1439
- essenceV3.meta.seo = blueprintData.seo_hints;
1440
- }
1441
- if (blueprintData?.navigation) {
1442
- essenceV3.meta.navigation = blueprintData.navigation;
1443
- }
1444
- writeFileSync(essencePath, JSON.stringify(essenceV3, null, 2) + "\n");
1445
- }
1446
- const refreshResult = await refreshDerivedFiles(projectRoot, essenceV3, registry);
1447
- contextFiles.push(...refreshResult.contextFiles);
1448
- const gitignoreUpdated = updateGitignore(projectRoot);
1449
- return {
1450
- essencePath,
1451
- decantrMdPath: refreshResult.decantrMdPath,
1452
- projectJsonPath,
1453
- contextFiles,
1454
- cssFiles: refreshResult.cssFiles,
1455
- gitignoreUpdated
1456
- };
1457
- }
1458
- function scaffoldMinimal(projectRoot) {
1459
- const decantrDir = join2(projectRoot, ".decantr");
1460
- const customDir = join2(decantrDir, "custom");
1461
- const contentTypes = ["patterns", "recipes", "themes", "blueprints", "archetypes", "shells"];
1462
- for (const type of contentTypes) {
1463
- mkdirSync(join2(customDir, type), { recursive: true });
1464
- }
1465
- const essence = {
1466
- version: "3.0.0",
1467
- dna: {
1468
- theme: {
1469
- style: "default",
1470
- mode: "dark",
1471
- recipe: "default",
1472
- shape: "rounded"
1473
- },
1474
- spacing: {
1475
- base_unit: 4,
1476
- scale: "linear",
1477
- density: "comfortable",
1478
- content_gap: "_gap4"
1479
- },
1480
- typography: {
1481
- scale: "modular",
1482
- heading_weight: 600,
1483
- body_weight: 400
1484
- },
1485
- color: {
1486
- palette: "semantic",
1487
- accent_count: 1,
1488
- cvd_preference: "auto"
1489
- },
1490
- radius: {
1491
- philosophy: "rounded",
1492
- base: 8
1493
- },
1494
- elevation: {
1495
- system: "layered",
1496
- max_levels: 3
1497
- },
1498
- motion: {
1499
- preference: "subtle",
1500
- duration_scale: 1,
1501
- reduce_motion: true
1502
- },
1503
- accessibility: {
1504
- wcag_level: "AA",
1505
- focus_visible: true,
1506
- skip_nav: true
1507
- },
1508
- personality: ["clean", "modern"]
1509
- },
1510
- blueprint: {
1511
- shell: "sidebar-main",
1512
- pages: [
1513
- { id: "home", layout: ["hero"] }
1514
- ],
1515
- features: []
1516
- },
1517
- meta: {
1518
- archetype: "custom",
1519
- target: "react",
1520
- platform: {
1521
- type: "spa",
1522
- routing: "hash"
1523
- },
1524
- guard: {
1525
- mode: "guided",
1526
- dna_enforcement: "error",
1527
- blueprint_enforcement: "off"
1528
- }
1529
- }
1530
- };
1531
- const essencePath = join2(projectRoot, "decantr.essence.json");
1532
- writeFileSync(essencePath, JSON.stringify(essence, null, 2) + "\n");
1533
- const now = (/* @__PURE__ */ new Date()).toISOString();
1534
- const projectJson = {
1535
- detected: {
1536
- framework: "unknown",
1537
- version: null,
1538
- packageManager: "npm",
1539
- hasTypeScript: false,
1540
- hasTailwind: false,
1541
- existingRuleFiles: []
1542
- },
1543
- overrides: {
1544
- framework: null
1545
- },
1546
- sync: {
1547
- status: "needs-sync",
1548
- lastSync: now,
1549
- registrySource: "cache",
1550
- cachedContent: {
1551
- archetypes: [],
1552
- patterns: [],
1553
- themes: [],
1554
- recipes: []
1555
- }
1556
- },
1557
- initialized: {
1558
- at: now,
1559
- via: "cli",
1560
- version: CLI_VERSION,
1561
- flags: "--offline --minimal"
1562
- }
1563
- };
1564
- const projectJsonPath = join2(decantrDir, "project.json");
1565
- writeFileSync(projectJsonPath, JSON.stringify(projectJson, null, 2));
1566
- const decantrMdPath = join2(projectRoot, "DECANTR.md");
1567
- const decantrMdContent = `# DECANTR.md
1568
-
1569
- > This file was generated by \`decantr init\` in offline/minimal mode.
1570
- > Run \`decantr upgrade\` when online to pull full registry content.
1571
-
1572
- ## Two-Layer Model
1573
-
1574
- This project uses the v3 Essence format with two layers:
1575
-
1576
- ### DNA (Immutable Design Axioms)
1577
- DNA defines the foundational design rules that must never be violated. DNA violations are **errors**.
1578
-
1579
- - **Theme:** default (dark mode)
1580
- - **Spacing:** comfortable density, _gap4
1581
- - **Radius:** rounded (8px base)
1582
- - **Accessibility:** WCAG AA
1583
- - **Personality:** clean, modern
1584
-
1585
- ### Blueprint (Structural Layout)
1586
- Blueprint defines pages, shells, and patterns. Blueprint deviations are **warnings**.
1587
-
1588
- | Page | Shell | Layout |
1589
- |------|-------|--------|
1590
- | home | sidebar-main | hero |
1591
-
1592
- ## Guard Mode: guided
1593
-
1594
- - **DNA enforcement:** error (violations block generation)
1595
- - **Blueprint enforcement:** off (deviations are advisory)
1596
-
1597
- ## MCP Tools
1598
-
1599
- When available, use these tools:
1600
- - \`decantr_read_essence\` \u2014 Read the current essence
1601
- - \`decantr_check_drift\` \u2014 Check for guard violations
1602
- - \`decantr_accept_drift\` \u2014 Accept a detected drift as intentional
1603
- - \`decantr_update_essence\` \u2014 Update the essence spec
1604
-
1605
- ## Quick Start
1606
-
1607
- 1. Edit \`decantr.essence.json\` to define your project structure.
1608
- 2. Run \`decantr sync\` when online to fetch registry content.
1609
- 3. Use \`decantr create <type> <name>\` to create custom content.
1610
- 4. Use \`decantr validate\` to check your essence file.
1611
-
1612
- ## Commands
1613
-
1614
- - \`decantr init\` \u2014 Initialize a new Decantr project
1615
- - \`decantr status\` \u2014 Project health and DNA/Blueprint overview
1616
- - \`decantr sync\` \u2014 Sync registry content
1617
- - \`decantr audit\` \u2014 Audit project for issues
1618
- - \`decantr migrate\` \u2014 Migrate v2 essence to v3
1619
- - \`decantr check\` \u2014 Detect drift issues
1620
- - \`decantr sync-drift\` \u2014 Review and resolve drift entries
1621
- - \`decantr validate\` \u2014 Validate essence file
1622
- - \`decantr search\` \u2014 Search the registry
1623
- - \`decantr suggest\` \u2014 Suggest patterns or alternatives
1624
- - \`decantr get\` \u2014 Get full details of a registry item
1625
- - \`decantr list\` \u2014 List items by type
1626
- - \`decantr theme\` \u2014 Manage custom themes
1627
- - \`decantr create\` \u2014 Create custom content items
1628
- - \`decantr publish\` \u2014 Publish custom content to registry
1629
- - \`decantr login\` \u2014 Authenticate with registry
1630
- - \`decantr logout\` \u2014 Remove stored credentials
1631
- - \`decantr upgrade\` \u2014 Check for content updates
1632
-
1633
- ---
1634
-
1635
- *Generated by @decantr/cli v${CLI_VERSION}*
1636
- `;
1637
- writeFileSync(decantrMdPath, decantrMdContent);
1638
- const gitignoreUpdated = updateGitignore(projectRoot);
1639
- return {
1640
- essencePath,
1641
- decantrMdPath,
1642
- projectJsonPath,
1643
- contextFiles: [],
1644
- cssFiles: [],
1645
- gitignoreUpdated
1646
- };
1647
- }
1648
- async function refreshDerivedFiles(projectRoot, essence, registry) {
1649
- const decantrDir = join2(projectRoot, ".decantr");
1650
- const contextDir = join2(decantrDir, "context");
1651
- mkdirSync(contextDir, { recursive: true });
1652
- const themeName = essence.dna.theme.style;
1653
- const recipeName = essence.dna.theme.recipe ?? themeName;
1654
- const mode = essence.dna.theme.mode;
1655
- const guardMode = essence.meta.guard.mode;
1656
- const guardConfig = {
1657
- mode: guardMode,
1658
- dna_enforcement: essence.meta.guard.dna_enforcement || "error",
1659
- blueprint_enforcement: essence.meta.guard.blueprint_enforcement || "warn"
1660
- };
1661
- const personality = essence.dna.personality || [];
1662
- let themeData;
1663
- let recipeData;
1664
- try {
1665
- const themeResult = await registry.fetchTheme(themeName);
1666
- if (themeResult?.data) {
1667
- const t = themeResult.data;
1668
- themeData = {
1669
- seed: t.seed,
1670
- palette: t.palette,
1671
- cvd_support: t.cvd_support,
1672
- tokens: t.tokens,
1673
- typography_hints: t.typography_hints,
1674
- motion_hints: t.motion_hints
1675
- };
1676
- }
1677
- } catch {
1678
- }
1679
- try {
1680
- const recipeResult = await registry.fetchRecipe(recipeName);
1681
- if (recipeResult?.data) {
1682
- const r = recipeResult.data;
1683
- recipeData = {
1684
- decorators: r.decorators,
1685
- spatial_hints: r.spatial_hints,
1686
- radius_hints: r.radius_hints
1687
- };
1688
- }
1689
- } catch {
1690
- }
1691
- const stylesDir = join2(projectRoot, "src", "styles");
1692
- mkdirSync(stylesDir, { recursive: true });
1693
- const tokensPath = join2(stylesDir, "tokens.css");
1694
- const hasRealThemeData = themeData?.seed?.primary || themeData?.palette?.background;
1695
- if (hasRealThemeData || !existsSync2(tokensPath)) {
1696
- writeFileSync(tokensPath, generateTokensCSS(themeData, mode));
1697
- }
1698
- const decoratorsPath = join2(stylesDir, "decorators.css");
1699
- const hasRealRecipeData = recipeData?.decorators && Object.keys(recipeData.decorators).length > 0;
1700
- if (hasRealRecipeData || !existsSync2(decoratorsPath)) {
1701
- writeFileSync(decoratorsPath, generateDecoratorsCSS(recipeData, themeName));
1702
- }
1703
- const cssFiles = [tokensPath, decoratorsPath];
1704
- const decantrMdPath = join2(projectRoot, "DECANTR.md");
1705
- writeFileSync(decantrMdPath, generateDecantrMdV31(guardMode, CSS_APPROACH_CONTENT));
1706
- const summaryPath = join2(contextDir, "essence-summary.md");
1707
- writeFileSync(summaryPath, generateEssenceSummaryV3(essence));
1708
- const contextFiles = [summaryPath];
1709
- const blueprint = essence.blueprint;
1710
- const sections = blueprint.sections && blueprint.sections.length > 0 ? blueprint.sections : [];
1711
- if (sections.length > 0) {
1712
- const patternSpecs = {};
1713
- const seenPatterns = /* @__PURE__ */ new Set();
1714
- for (const section of sections) {
1715
- for (const page of section.pages) {
1716
- for (const item of page.layout) {
1717
- const names = extractPatternNames(item);
1718
- for (const name of names) {
1719
- if (!seenPatterns.has(name)) {
1720
- seenPatterns.add(name);
1721
- try {
1722
- const patResult = await registry.fetchPattern(name);
1723
- if (patResult?.data) {
1724
- const raw = patResult.data;
1725
- const inner = raw.data ?? raw;
1726
- const defaultPreset = inner.default_preset || "standard";
1727
- const preset = inner.presets?.[defaultPreset];
1728
- patternSpecs[name] = {
1729
- description: inner.description || "",
1730
- components: inner.components || [],
1731
- slots: preset?.layout?.slots || {}
1732
- };
1733
- }
1734
- } catch {
1735
- }
1736
- }
1737
- }
1738
- }
1739
- }
1740
- }
1741
- const zoneInputs = sections.map((s) => ({
1742
- archetypeId: s.id,
1743
- role: s.role,
1744
- shell: s.shell,
1745
- features: s.features,
1746
- description: s.description
1747
- }));
1748
- const zones = deriveZones(zoneInputs);
1749
- const transitions = deriveTransitions(zones);
1750
- const hasPublic = zones.some((z) => z.role === "public");
1751
- const hasPrimary = zones.some((z) => z.role === "primary");
1752
- const topologyData = {
1753
- intent: sections.map((s) => s.id).join(" + "),
1754
- zones,
1755
- transitions,
1756
- entryPoints: {
1757
- anonymous: hasPublic ? "public zone" : "gateway",
1758
- authenticated: hasPrimary ? "primary zone" : "first section"
1759
- }
1760
- };
1761
- const topologyMarkdown = generateTopologySection(topologyData, personality);
1762
- const themeTokensCss = existsSync2(tokensPath) ? readFileSync2(tokensPath, "utf-8") : "";
1763
- const decoratorList = [];
1764
- if (recipeData?.decorators) {
1765
- for (const [name, desc] of Object.entries(recipeData.decorators)) {
1766
- decoratorList.push({ name, description: desc });
1767
- }
1768
- } else if (existsSync2(decoratorsPath)) {
1769
- const decoratorsCss = readFileSync2(decoratorsPath, "utf-8");
1770
- const classRegex = /\/\*\s*(.+?)\s*\*\/\s*\n\s*\.([\w-]+)\s*\{/g;
1771
- let match;
1772
- while ((match = classRegex.exec(decoratorsCss)) !== null) {
1773
- decoratorList.push({ name: match[2], description: match[1] });
1774
- }
1775
- if (decoratorList.length === 0) {
1776
- const simpleClassRegex = /^\.([\w-]+)\s*\{/gm;
1777
- while ((match = simpleClassRegex.exec(decoratorsCss)) !== null) {
1778
- decoratorList.push({ name: match[1], description: "" });
1779
- }
1780
- }
1781
- }
1782
- const shellInfoCache = {};
1783
- const seenShells = /* @__PURE__ */ new Set();
1784
- for (const section of sections) {
1785
- const shellId = section.shell;
1786
- if (!seenShells.has(shellId)) {
1787
- seenShells.add(shellId);
1788
- try {
1789
- const shellResult = await registry.fetchShell(shellId);
1790
- if (shellResult?.data) {
1791
- const raw = shellResult.data;
1792
- const inner = raw.data ?? raw;
1793
- shellInfoCache[shellId] = {
1794
- description: inner.description || "",
1795
- regions: inner.config?.regions || [],
1796
- layout: inner.layout || void 0
1797
- };
1798
- }
1799
- } catch {
1800
- }
1801
- }
1802
- }
1803
- for (const section of sections) {
1804
- const zoneLabel = section.role === "primary" || section.role === "auxiliary" ? "App" : section.role.charAt(0).toUpperCase() + section.role.slice(1);
1805
- const zoneContext = `This section is in the **${zoneLabel}** zone (${section.shell} shell).` + (topologyMarkdown ? "\n\n" + topologyMarkdown : "");
1806
- const sectionPatterns = {};
1807
- for (const page of section.pages) {
1808
- for (const item of page.layout) {
1809
- const names = extractPatternNames(item);
1810
- for (const name of names) {
1811
- if (patternSpecs[name]) {
1812
- sectionPatterns[name] = patternSpecs[name];
1813
- }
1814
- }
1815
- }
1816
- }
1817
- const contextContent = generateSectionContext({
1818
- section,
1819
- themeTokens: themeTokensCss,
1820
- decorators: decoratorList,
1821
- guardConfig,
1822
- personality,
1823
- themeName,
1824
- recipeName,
1825
- zoneContext,
1826
- patternSpecs: sectionPatterns,
1827
- constraints: essence.dna.constraints,
1828
- shellInfo: shellInfoCache[section.shell]
1829
- });
1830
- const sectionContextPath = join2(contextDir, `section-${section.id}.md`);
1831
- writeFileSync(sectionContextPath, contextContent);
1832
- contextFiles.push(sectionContextPath);
1833
- }
1834
- const routes = blueprint.routes || {};
1835
- const scaffoldContent = generateScaffoldContext({
1836
- appName: essence.meta.archetype || "Application",
1837
- blueprintId: "",
1838
- themeName,
1839
- recipeName,
1840
- personality,
1841
- topologyMarkdown,
1842
- sections,
1843
- routes,
1844
- constraints: essence.dna.constraints,
1845
- seo: essence.meta.seo,
1846
- navigation: essence.meta.navigation
1847
- });
1848
- const scaffoldMdPath = join2(contextDir, "scaffold.md");
1849
- writeFileSync(scaffoldMdPath, scaffoldContent);
1850
- contextFiles.push(scaffoldMdPath);
1851
- } else {
1852
- const pages = blueprint.pages || [{ id: "home", layout: ["hero"] }];
1853
- const shell = blueprint.shell ?? "sidebar-main";
1854
- const syntheticSection = {
1855
- id: essence.meta.archetype || "default",
1856
- role: "primary",
1857
- shell,
1858
- features: blueprint.features || [],
1859
- description: `${essence.meta.archetype || "Application"} section`,
1860
- pages
1861
- };
1862
- const patternSpecs = {};
1863
- const seenPatterns = /* @__PURE__ */ new Set();
1864
- for (const page of pages) {
1865
- for (const item of page.layout) {
1866
- const names = extractPatternNames(item);
1867
- for (const name of names) {
1868
- if (!seenPatterns.has(name)) {
1869
- seenPatterns.add(name);
1870
- try {
1871
- const patResult = await registry.fetchPattern(name);
1872
- if (patResult?.data) {
1873
- const raw = patResult.data;
1874
- const inner = raw.data ?? raw;
1875
- const defaultPreset = inner.default_preset || "standard";
1876
- const preset = inner.presets?.[defaultPreset];
1877
- patternSpecs[name] = {
1878
- description: inner.description || "",
1879
- components: inner.components || [],
1880
- slots: preset?.layout?.slots || {}
1881
- };
1882
- }
1883
- } catch {
1884
- }
1885
- }
1886
- }
1887
- }
1888
- }
1889
- const themeTokensCss = existsSync2(tokensPath) ? readFileSync2(tokensPath, "utf-8") : "";
1890
- const decoratorList = [];
1891
- if (recipeData?.decorators) {
1892
- for (const [name, desc] of Object.entries(recipeData.decorators)) {
1893
- decoratorList.push({ name, description: desc });
1894
- }
1895
- } else if (existsSync2(decoratorsPath)) {
1896
- const decoratorsCss = readFileSync2(decoratorsPath, "utf-8");
1897
- const classRegex = /\/\*\s*(.+?)\s*\*\/\s*\n\s*\.([\w-]+)\s*\{/g;
1898
- let match;
1899
- while ((match = classRegex.exec(decoratorsCss)) !== null) {
1900
- decoratorList.push({ name: match[2], description: match[1] });
1901
- }
1902
- if (decoratorList.length === 0) {
1903
- const simpleClassRegex = /^\.([\w-]+)\s*\{/gm;
1904
- while ((match = simpleClassRegex.exec(decoratorsCss)) !== null) {
1905
- decoratorList.push({ name: match[1], description: "" });
1906
- }
1907
- }
1908
- }
1909
- let v30ShellInfo;
1910
- try {
1911
- const shellResult = await registry.fetchShell(shell);
1912
- if (shellResult?.data) {
1913
- const raw = shellResult.data;
1914
- const inner = raw.data ?? raw;
1915
- v30ShellInfo = {
1916
- description: inner.description || "",
1917
- regions: inner.config?.regions || [],
1918
- layout: inner.layout || void 0
1919
- };
1920
- }
1921
- } catch {
1922
- }
1923
- const contextContent = generateSectionContext({
1924
- section: syntheticSection,
1925
- themeTokens: themeTokensCss,
1926
- decorators: decoratorList,
1927
- guardConfig,
1928
- personality,
1929
- themeName,
1930
- recipeName,
1931
- zoneContext: `This is the primary section (${shell} shell).`,
1932
- patternSpecs,
1933
- constraints: essence.dna.constraints,
1934
- shellInfo: v30ShellInfo
1935
- });
1936
- const sectionContextPath = join2(contextDir, `section-${syntheticSection.id}.md`);
1937
- writeFileSync(sectionContextPath, contextContent);
1938
- contextFiles.push(sectionContextPath);
1939
- }
1940
- return {
1941
- decantrMdPath,
1942
- contextFiles,
1943
- cssFiles
1944
- };
1945
- }
1946
- function generateSectionContext(input) {
1947
- const { section, themeTokens, decorators, guardConfig, personality, themeName, recipeName, zoneContext, patternSpecs, recipeHints, constraints, shellInfo } = input;
1948
- const lines = [];
1949
- lines.push(`# Section: ${section.id}`);
1950
- lines.push("");
1951
- lines.push(`**Role:** ${section.role} | **Shell:** ${section.shell} | **Archetype:** ${section.id}`);
1952
- lines.push(`**Description:** ${section.description}`);
1953
- if (shellInfo) {
1954
- lines.push(`**Shell structure:** ${shellInfo.description}`);
1955
- lines.push(`**Regions:** ${shellInfo.regions.join(", ")}`);
1956
- }
1957
- lines.push("");
1958
- lines.push("---");
1959
- lines.push("");
1960
- lines.push("## Guard Rules");
1961
- lines.push("");
1962
- lines.push(`| Rule | Scope | Severity | Description |`);
1963
- lines.push(`|------|-------|----------|-------------|`);
1964
- lines.push(`| Style guard | DNA | ${guardConfig.dna_enforcement} | Code must use the ${themeName} theme |`);
1965
- lines.push(`| Recipe guard | DNA | ${guardConfig.dna_enforcement} | Visual recipe must match ${recipeName} |`);
1966
- lines.push(`| Density guard | DNA | ${guardConfig.dna_enforcement} | Content gap must match essence density |`);
1967
- lines.push(`| Accessibility guard | DNA | ${guardConfig.dna_enforcement} | Must meet WCAG level from essence |`);
1968
- lines.push(`| Structure guard | Blueprint | ${guardConfig.blueprint_enforcement} | Pages must exist in essence structure |`);
1969
- lines.push(`| Layout guard | Blueprint | ${guardConfig.blueprint_enforcement} | Pattern order must match essence layout |`);
1970
- lines.push(`| Pattern existence | Blueprint | ${guardConfig.blueprint_enforcement} | All patterns must exist in registry |`);
1971
- lines.push("");
1972
- lines.push(`**Guard mode:** ${guardConfig.mode}`);
1973
- lines.push("");
1974
- lines.push("---");
1975
- lines.push("");
1976
- lines.push(`## Theme: ${themeName}`);
1977
- lines.push("");
1978
- lines.push("```css");
1979
- lines.push(themeTokens);
1980
- lines.push("```");
1981
- lines.push("");
1982
- lines.push("---");
1983
- lines.push("");
1984
- lines.push(`## Decorators (${recipeName} recipe)`);
1985
- lines.push("");
1986
- if (decorators.length > 0) {
1987
- lines.push("| Decorator | Description |");
1988
- lines.push("|-----------|-------------|");
1989
- for (const dec of decorators) {
1990
- lines.push(`| ${dec.name} | ${dec.description} |`);
1991
- }
1992
- } else {
1993
- lines.push("No decorators defined.");
1994
- }
1995
- lines.push("");
1996
- if (recipeHints) {
1997
- if (recipeHints.preferred && recipeHints.preferred.length > 0) {
1998
- lines.push(`**Preferred:** ${recipeHints.preferred.join(", ")}`);
1999
- }
2000
- if (recipeHints.compositions) {
2001
- lines.push(`**Compositions:** ${recipeHints.compositions}`);
2002
- }
2003
- if (recipeHints.spatialHints) {
2004
- lines.push(`**Spatial hints:** ${recipeHints.spatialHints}`);
2005
- }
2006
- lines.push("");
2007
- }
2008
- lines.push("---");
2009
- lines.push("");
2010
- if (zoneContext) {
2011
- lines.push("## Zone Context");
2012
- lines.push("");
2013
- lines.push(zoneContext);
2014
- lines.push("");
2015
- lines.push("---");
2016
- lines.push("");
2017
- }
2018
- if (section.features.length > 0) {
2019
- lines.push("## Features");
2020
- lines.push("");
2021
- lines.push(section.features.join(", "));
2022
- lines.push("");
2023
- lines.push("---");
2024
- lines.push("");
2025
- }
2026
- if (personality.length > 0) {
2027
- lines.push("## Personality");
2028
- lines.push("");
2029
- lines.push(personality.join(", "));
2030
- lines.push("");
2031
- lines.push("---");
2032
- lines.push("");
2033
- }
2034
- if (constraints && Object.keys(constraints).length > 0) {
2035
- lines.push("## Constraints");
2036
- lines.push("");
2037
- for (const [key, value] of Object.entries(constraints)) {
2038
- lines.push(`- **${key}:** ${typeof value === "object" && value !== null ? JSON.stringify(value) : value}`);
2039
- }
2040
- lines.push("");
2041
- lines.push("---");
2042
- lines.push("");
2043
- }
2044
- lines.push("## Pages");
2045
- lines.push("");
2046
- for (const page of section.pages) {
2047
- const route = page.route || `/${section.id}/${page.id}`;
2048
- const layoutStr = page.layout.map(serializeLayoutItem).join(" \u2192 ");
2049
- lines.push(`### ${page.id} (${route})`);
2050
- lines.push("");
2051
- lines.push(`Layout: ${layoutStr}`);
2052
- lines.push("");
2053
- if (page.patterns && page.patterns.length > 0) {
2054
- lines.push("**Pattern mapping:**");
2055
- lines.push("| Alias | Pattern | Preset |");
2056
- lines.push("|-------|---------|--------|");
2057
- for (const ref of page.patterns) {
2058
- const alias = ref.as || ref.pattern;
2059
- const preset = ref.preset || "standard";
2060
- lines.push(`| ${alias} | ${ref.pattern} | ${preset} |`);
2061
- }
2062
- lines.push("");
2063
- }
2064
- const patternNames = page.layout.flatMap(extractPatternNames);
2065
- for (const patternName of patternNames) {
2066
- const spec = patternSpecs[patternName];
2067
- if (!spec) continue;
2068
- lines.push(`#### Pattern: ${patternName}`);
2069
- lines.push("");
2070
- lines.push(spec.description);
2071
- lines.push("");
2072
- lines.push(`**Components:** ${spec.components.join(", ")}`);
2073
- lines.push("");
2074
- lines.push("**Layout slots:**");
2075
- for (const [slot, desc] of Object.entries(spec.slots)) {
2076
- lines.push(`- \`${slot}\`: ${desc}`);
2077
- }
2078
- lines.push("");
2079
- }
2080
- }
2081
- return lines.join("\n");
2082
- }
2083
- function generateScaffoldContext(input) {
2084
- const { appName, blueprintId, themeName, recipeName, personality, topologyMarkdown, sections, routes, constraints, seo, navigation } = input;
2085
- const lines = [];
2086
- lines.push(`# Scaffold: ${appName}`);
2087
- lines.push("");
2088
- lines.push(`**Blueprint:** ${blueprintId}`);
2089
- lines.push(`**Theme:** ${themeName} | **Recipe:** ${recipeName}`);
2090
- lines.push(`**Personality:** ${personality.join(", ")}`);
2091
- lines.push("**Guard mode:** creative (no enforcement during initial scaffolding)");
2092
- lines.push("");
2093
- lines.push("## App Topology");
2094
- lines.push("");
2095
- lines.push(topologyMarkdown);
2096
- lines.push("");
2097
- lines.push("## Sections Overview");
2098
- lines.push("");
2099
- lines.push("| Section | Role | Shell | Pages | Features |");
2100
- lines.push("|---------|------|-------|-------|----------|");
2101
- for (const section of sections) {
2102
- const pageIds = section.pages.map((p) => p.id).join(", ");
2103
- const feats = section.features.join(", ") || "none";
2104
- lines.push(`| ${section.id} | ${section.role} | ${section.shell} | ${pageIds} | ${feats} |`);
2105
- }
2106
- lines.push("");
2107
- lines.push("## Route Map");
2108
- lines.push("");
2109
- lines.push("| Route | Section | Page |");
2110
- lines.push("|-------|---------|------|");
2111
- for (const [route, entry] of Object.entries(routes)) {
2112
- lines.push(`| ${route} | ${entry.section} | ${entry.page} |`);
2113
- }
2114
- lines.push("");
2115
- lines.push("## Section Contexts");
2116
- lines.push("");
2117
- lines.push("For detailed pattern specs per section, read:");
2118
- for (const section of sections) {
2119
- lines.push(`- .decantr/context/section-${section.id}.md`);
2120
- }
2121
- lines.push("");
2122
- const patternUsage = {};
2123
- for (const section of sections) {
2124
- for (const page of section.pages) {
2125
- const names = page.layout.flatMap(extractPatternNames);
2126
- for (const name of names) {
2127
- if (!patternUsage[name]) patternUsage[name] = [];
2128
- const pageLabel = sections.length > 1 ? `${section.id}/${page.id}` : page.id;
2129
- patternUsage[name].push(pageLabel);
2130
- }
2131
- }
2132
- }
2133
- const sharedPatterns = Object.entries(patternUsage).filter(([, pages]) => pages.length >= 2);
2134
- if (sharedPatterns.length > 0) {
2135
- lines.push("## Shared Components");
2136
- lines.push("");
2137
- lines.push("These patterns appear on multiple pages. Consider creating shared components:");
2138
- lines.push("");
2139
- lines.push("| Pattern | Used by |");
2140
- lines.push("|---------|---------|");
2141
- for (const [pattern, pages] of sharedPatterns) {
2142
- lines.push(`| ${pattern} | ${pages.join(", ")} |`);
2143
- }
2144
- lines.push("");
2145
- }
2146
- if (constraints && Object.keys(constraints).length > 0) {
2147
- lines.push("## Design Constraints");
2148
- lines.push("");
2149
- for (const [key, value] of Object.entries(constraints)) {
2150
- lines.push(`- **${key}:** ${typeof value === "object" && value !== null ? JSON.stringify(value) : value}`);
2151
- }
2152
- lines.push("");
2153
- }
2154
- if (seo && (seo.schema_org?.length || seo.meta_priorities?.length)) {
2155
- lines.push("## SEO Hints");
2156
- lines.push("");
2157
- if (seo.schema_org && seo.schema_org.length > 0) {
2158
- lines.push(`**Schema.org types:** ${seo.schema_org.join(", ")}`);
2159
- }
2160
- if (seo.meta_priorities && seo.meta_priorities.length > 0) {
2161
- lines.push(`**Meta priorities:** ${seo.meta_priorities.join(", ")}`);
2162
- }
2163
- lines.push("");
2164
- }
2165
- if (navigation && (navigation.hotkeys?.length || navigation.command_palette)) {
2166
- lines.push("## Navigation");
2167
- lines.push("");
2168
- if (navigation.command_palette) {
2169
- lines.push("- Command palette: enabled");
2170
- }
2171
- if (navigation.hotkeys && navigation.hotkeys.length > 0) {
2172
- lines.push(`- Hotkeys: ${navigation.hotkeys.length} configured`);
2173
- }
2174
- lines.push("");
2175
- }
2176
- return lines.join("\n");
2177
- }
2178
-
2179
395
  // src/theme-commands.ts
2180
- import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2, readdirSync, rmSync } from "fs";
2181
- import { join as join3 } from "path";
396
+ import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync, readdirSync, rmSync } from "fs";
397
+ import { join as join2 } from "path";
2182
398
 
2183
399
  // src/theme-templates.ts
2184
400
  function getThemeSkeleton(id, name) {
@@ -2296,20 +512,20 @@ function validateCustomTheme(theme) {
2296
512
  };
2297
513
  }
2298
514
  function createTheme(projectRoot, id, name) {
2299
- const customThemesDir = join3(projectRoot, ".decantr", "custom", "themes");
2300
- const themePath = join3(customThemesDir, `${id}.json`);
2301
- const howToPath = join3(customThemesDir, "how-to-theme.md");
2302
- mkdirSync2(customThemesDir, { recursive: true });
2303
- if (existsSync3(themePath)) {
515
+ const customThemesDir = join2(projectRoot, ".decantr", "custom", "themes");
516
+ const themePath = join2(customThemesDir, `${id}.json`);
517
+ const howToPath = join2(customThemesDir, "how-to-theme.md");
518
+ mkdirSync(customThemesDir, { recursive: true });
519
+ if (existsSync2(themePath)) {
2304
520
  return {
2305
521
  success: false,
2306
522
  error: `Theme "${id}" already exists at ${themePath}`
2307
523
  };
2308
524
  }
2309
525
  const skeleton = getThemeSkeleton(id, name);
2310
- writeFileSync2(themePath, JSON.stringify(skeleton, null, 2));
2311
- if (!existsSync3(howToPath)) {
2312
- writeFileSync2(howToPath, getHowToThemeDoc());
526
+ writeFileSync(themePath, JSON.stringify(skeleton, null, 2));
527
+ if (!existsSync2(howToPath)) {
528
+ writeFileSync(howToPath, getHowToThemeDoc());
2313
529
  }
2314
530
  return {
2315
531
  success: true,
@@ -2317,17 +533,17 @@ function createTheme(projectRoot, id, name) {
2317
533
  };
2318
534
  }
2319
535
  function listCustomThemes(projectRoot) {
2320
- const customThemesDir = join3(projectRoot, ".decantr", "custom", "themes");
2321
- if (!existsSync3(customThemesDir)) {
536
+ const customThemesDir = join2(projectRoot, ".decantr", "custom", "themes");
537
+ if (!existsSync2(customThemesDir)) {
2322
538
  return [];
2323
539
  }
2324
540
  const themes = [];
2325
541
  try {
2326
542
  const files = readdirSync(customThemesDir).filter((f) => f.endsWith(".json"));
2327
543
  for (const file of files) {
2328
- const filePath = join3(customThemesDir, file);
544
+ const filePath = join2(customThemesDir, file);
2329
545
  try {
2330
- const data = JSON.parse(readFileSync3(filePath, "utf-8"));
546
+ const data = JSON.parse(readFileSync2(filePath, "utf-8"));
2331
547
  themes.push({
2332
548
  id: data.id || file.replace(".json", ""),
2333
549
  name: data.name || data.id,
@@ -2342,8 +558,8 @@ function listCustomThemes(projectRoot) {
2342
558
  return themes;
2343
559
  }
2344
560
  function deleteTheme(projectRoot, id) {
2345
- const themePath = join3(projectRoot, ".decantr", "custom", "themes", `${id}.json`);
2346
- if (!existsSync3(themePath)) {
561
+ const themePath = join2(projectRoot, ".decantr", "custom", "themes", `${id}.json`);
562
+ if (!existsSync2(themePath)) {
2347
563
  return {
2348
564
  success: false,
2349
565
  error: `Theme "${id}" not found at ${themePath}`
@@ -2360,7 +576,7 @@ function deleteTheme(projectRoot, id) {
2360
576
  }
2361
577
  }
2362
578
  function importTheme(projectRoot, sourcePath) {
2363
- if (!existsSync3(sourcePath)) {
579
+ if (!existsSync2(sourcePath)) {
2364
580
  return {
2365
581
  success: false,
2366
582
  errors: [`Source file not found: ${sourcePath}`]
@@ -2368,7 +584,7 @@ function importTheme(projectRoot, sourcePath) {
2368
584
  }
2369
585
  let theme;
2370
586
  try {
2371
- theme = JSON.parse(readFileSync3(sourcePath, "utf-8"));
587
+ theme = JSON.parse(readFileSync2(sourcePath, "utf-8"));
2372
588
  } catch (e) {
2373
589
  return {
2374
590
  success: false,
@@ -2384,14 +600,14 @@ function importTheme(projectRoot, sourcePath) {
2384
600
  }
2385
601
  theme.source = "custom";
2386
602
  const id = theme.id;
2387
- const customThemesDir = join3(projectRoot, ".decantr", "custom", "themes");
2388
- const destPath = join3(customThemesDir, `${id}.json`);
2389
- mkdirSync2(customThemesDir, { recursive: true });
2390
- const howToPath = join3(customThemesDir, "how-to-theme.md");
2391
- if (!existsSync3(howToPath)) {
2392
- writeFileSync2(howToPath, getHowToThemeDoc());
2393
- }
2394
- writeFileSync2(destPath, JSON.stringify(theme, null, 2));
603
+ const customThemesDir = join2(projectRoot, ".decantr", "custom", "themes");
604
+ const destPath = join2(customThemesDir, `${id}.json`);
605
+ mkdirSync(customThemesDir, { recursive: true });
606
+ const howToPath = join2(customThemesDir, "how-to-theme.md");
607
+ if (!existsSync2(howToPath)) {
608
+ writeFileSync(howToPath, getHowToThemeDoc());
609
+ }
610
+ writeFileSync(destPath, JSON.stringify(theme, null, 2));
2395
611
  return {
2396
612
  success: true,
2397
613
  path: destPath
@@ -2399,25 +615,25 @@ function importTheme(projectRoot, sourcePath) {
2399
615
  }
2400
616
 
2401
617
  // src/auth.ts
2402
- import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync3, rmSync as rmSync2 } from "fs";
2403
- import { join as join4 } from "path";
618
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2, rmSync as rmSync2 } from "fs";
619
+ import { join as join3 } from "path";
2404
620
  import { homedir } from "os";
2405
- var CONFIG_DIR = join4(homedir(), ".config", "decantr");
2406
- var AUTH_FILE = join4(CONFIG_DIR, "auth.json");
621
+ var CONFIG_DIR = join3(homedir(), ".config", "decantr");
622
+ var AUTH_FILE = join3(CONFIG_DIR, "auth.json");
2407
623
  function getCredentials() {
2408
- if (!existsSync4(AUTH_FILE)) return null;
624
+ if (!existsSync3(AUTH_FILE)) return null;
2409
625
  try {
2410
- return JSON.parse(readFileSync4(AUTH_FILE, "utf-8"));
626
+ return JSON.parse(readFileSync3(AUTH_FILE, "utf-8"));
2411
627
  } catch {
2412
628
  return null;
2413
629
  }
2414
630
  }
2415
631
  function saveCredentials(creds) {
2416
- mkdirSync3(CONFIG_DIR, { recursive: true });
2417
- writeFileSync3(AUTH_FILE, JSON.stringify(creds, null, 2));
632
+ mkdirSync2(CONFIG_DIR, { recursive: true });
633
+ writeFileSync2(AUTH_FILE, JSON.stringify(creds, null, 2));
2418
634
  }
2419
635
  function clearCredentials() {
2420
- if (existsSync4(AUTH_FILE)) {
636
+ if (existsSync3(AUTH_FILE)) {
2421
637
  rmSync2(AUTH_FILE);
2422
638
  }
2423
639
  }
@@ -2428,8 +644,8 @@ function getApiKeyOrToken() {
2428
644
  }
2429
645
 
2430
646
  // src/commands/publish.ts
2431
- import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
2432
- import { join as join5 } from "path";
647
+ import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
648
+ import { join as join4 } from "path";
2433
649
  import { RegistryAPIClient } from "@decantr/registry";
2434
650
  var PLURAL_TO_SINGULAR = {
2435
651
  patterns: "pattern",
@@ -2456,8 +672,8 @@ async function cmdPublish(type, name, projectRoot = process.cwd()) {
2456
672
  }
2457
673
  const singularType = PLURAL_TO_SINGULAR[type] || type;
2458
674
  const pluralType = SINGULAR_TO_PLURAL[type] || SINGULAR_TO_PLURAL[singularType] || `${type}s`;
2459
- const customPath = join5(projectRoot, ".decantr", "custom", pluralType, `${name}.json`);
2460
- if (!existsSync5(customPath)) {
675
+ const customPath = join4(projectRoot, ".decantr", "custom", pluralType, `${name}.json`);
676
+ if (!existsSync4(customPath)) {
2461
677
  console.error(`Custom ${singularType} "${name}" not found at ${customPath}`);
2462
678
  console.error(`Create one first: decantr create ${singularType} ${name}`);
2463
679
  process.exitCode = 1;
@@ -2465,7 +681,7 @@ async function cmdPublish(type, name, projectRoot = process.cwd()) {
2465
681
  }
2466
682
  let data;
2467
683
  try {
2468
- data = JSON.parse(readFileSync5(customPath, "utf-8"));
684
+ data = JSON.parse(readFileSync4(customPath, "utf-8"));
2469
685
  } catch {
2470
686
  console.error(`Failed to parse ${customPath}`);
2471
687
  process.exitCode = 1;
@@ -2492,8 +708,8 @@ async function cmdPublish(type, name, projectRoot = process.cwd()) {
2492
708
  }
2493
709
 
2494
710
  // src/commands/create.ts
2495
- import { existsSync as existsSync6, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "fs";
2496
- import { join as join6 } from "path";
711
+ import { existsSync as existsSync5, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "fs";
712
+ import { join as join5 } from "path";
2497
713
  var CONTENT_TYPES = ["pattern", "recipe", "theme", "blueprint", "archetype", "shell"];
2498
714
  var PLURAL = {
2499
715
  pattern: "patterns",
@@ -2533,23 +749,23 @@ function cmdCreate(type, name, projectRoot = process.cwd()) {
2533
749
  return;
2534
750
  }
2535
751
  const plural = PLURAL[type];
2536
- const customDir = join6(projectRoot, ".decantr", "custom", plural);
2537
- const filePath = join6(customDir, `${name}.json`);
2538
- if (existsSync6(filePath)) {
752
+ const customDir = join5(projectRoot, ".decantr", "custom", plural);
753
+ const filePath = join5(customDir, `${name}.json`);
754
+ if (existsSync5(filePath)) {
2539
755
  console.error(`${type} "${name}" already exists at ${filePath}`);
2540
756
  process.exitCode = 1;
2541
757
  return;
2542
758
  }
2543
- mkdirSync4(customDir, { recursive: true });
759
+ mkdirSync3(customDir, { recursive: true });
2544
760
  const skeleton = getSkeleton(type, name, name.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()));
2545
- writeFileSync4(filePath, JSON.stringify(skeleton, null, 2));
761
+ writeFileSync3(filePath, JSON.stringify(skeleton, null, 2));
2546
762
  console.log(`Created ${type} "${name}" at ${filePath}`);
2547
763
  console.log(`Edit it, then publish with: decantr publish ${type} ${name}`);
2548
764
  }
2549
765
 
2550
766
  // src/commands/migrate.ts
2551
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, existsSync as existsSync7, copyFileSync } from "fs";
2552
- import { join as join7 } from "path";
767
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync6, copyFileSync } from "fs";
768
+ import { join as join6 } from "path";
2553
769
  import { validateEssence, migrateV2ToV3, isV3 } from "@decantr/essence-spec";
2554
770
  var GREEN2 = "\x1B[32m";
2555
771
  var RED = "\x1B[31m";
@@ -2557,12 +773,12 @@ var YELLOW2 = "\x1B[33m";
2557
773
  var RESET2 = "\x1B[0m";
2558
774
  var DIM2 = "\x1B[2m";
2559
775
  function migrateEssenceFile(essencePath) {
2560
- if (!existsSync7(essencePath)) {
776
+ if (!existsSync6(essencePath)) {
2561
777
  return { success: false, error: `File not found: ${essencePath}` };
2562
778
  }
2563
779
  let raw;
2564
780
  try {
2565
- raw = readFileSync6(essencePath, "utf-8");
781
+ raw = readFileSync5(essencePath, "utf-8");
2566
782
  } catch (e) {
2567
783
  return { success: false, error: `Could not read ${essencePath}: ${e.message}` };
2568
784
  }
@@ -2603,15 +819,15 @@ function migrateEssenceFile(essencePath) {
2603
819
  };
2604
820
  }
2605
821
  try {
2606
- writeFileSync5(essencePath, JSON.stringify(v3, null, 2) + "\n");
822
+ writeFileSync4(essencePath, JSON.stringify(v3, null, 2) + "\n");
2607
823
  } catch (e) {
2608
824
  return { success: false, backupPath, error: `Could not write migrated file: ${e.message}` };
2609
825
  }
2610
826
  return { success: true, backupPath };
2611
827
  }
2612
828
  async function cmdMigrate(projectRoot = process.cwd()) {
2613
- const essencePath = join7(projectRoot, "decantr.essence.json");
2614
- if (!existsSync7(essencePath)) {
829
+ const essencePath = join6(projectRoot, "decantr.essence.json");
830
+ if (!existsSync6(essencePath)) {
2615
831
  console.error(`${RED}No decantr.essence.json found. Run \`decantr init\` first.${RESET2}`);
2616
832
  process.exitCode = 1;
2617
833
  return;
@@ -2636,8 +852,8 @@ async function cmdMigrate(projectRoot = process.cwd()) {
2636
852
  }
2637
853
 
2638
854
  // src/commands/sync-drift.ts
2639
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync6, existsSync as existsSync8 } from "fs";
2640
- import { join as join8 } from "path";
855
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, existsSync as existsSync7 } from "fs";
856
+ import { join as join7 } from "path";
2641
857
  var GREEN3 = "\x1B[32m";
2642
858
  var RED2 = "\x1B[31m";
2643
859
  var YELLOW3 = "\x1B[33m";
@@ -2646,15 +862,15 @@ var DIM3 = "\x1B[2m";
2646
862
  var BOLD2 = "\x1B[1m";
2647
863
  var CYAN2 = "\x1B[36m";
2648
864
  async function cmdSyncDrift(projectRoot = process.cwd()) {
2649
- const driftLogPath = join8(projectRoot, ".decantr", "drift-log.json");
2650
- if (!existsSync8(driftLogPath)) {
865
+ const driftLogPath = join7(projectRoot, ".decantr", "drift-log.json");
866
+ if (!existsSync7(driftLogPath)) {
2651
867
  console.log(`${GREEN3}No drift log found \u2014 no drift recorded.${RESET3}`);
2652
868
  console.log(`${DIM3}Drift is logged when guard violations are detected during development.${RESET3}`);
2653
869
  return;
2654
870
  }
2655
871
  let entries;
2656
872
  try {
2657
- entries = JSON.parse(readFileSync7(driftLogPath, "utf-8"));
873
+ entries = JSON.parse(readFileSync6(driftLogPath, "utf-8"));
2658
874
  } catch {
2659
875
  console.error(`${RED2}Could not parse drift-log.json${RESET3}`);
2660
876
  process.exitCode = 1;
@@ -2698,13 +914,13 @@ ${BOLD2}Unresolved Drift Entries (${unresolved.length})${RESET3}
2698
914
  console.log("");
2699
915
  }
2700
916
  function resolveDriftEntries(projectRoot, options) {
2701
- const driftLogPath = join8(projectRoot, ".decantr", "drift-log.json");
2702
- if (!existsSync8(driftLogPath)) {
917
+ const driftLogPath = join7(projectRoot, ".decantr", "drift-log.json");
918
+ if (!existsSync7(driftLogPath)) {
2703
919
  return { success: true };
2704
920
  }
2705
921
  if (options.clear) {
2706
922
  try {
2707
- writeFileSync6(driftLogPath, "[]");
923
+ writeFileSync5(driftLogPath, "[]");
2708
924
  return { success: true };
2709
925
  } catch (e) {
2710
926
  return { success: false, error: `Could not clear drift log: ${e.message}` };
@@ -2712,7 +928,7 @@ function resolveDriftEntries(projectRoot, options) {
2712
928
  }
2713
929
  let entries;
2714
930
  try {
2715
- entries = JSON.parse(readFileSync7(driftLogPath, "utf-8"));
931
+ entries = JSON.parse(readFileSync6(driftLogPath, "utf-8"));
2716
932
  } catch {
2717
933
  return { success: false, error: "Could not parse drift-log.json" };
2718
934
  }
@@ -2731,7 +947,7 @@ function resolveDriftEntries(projectRoot, options) {
2731
947
  }
2732
948
  }
2733
949
  try {
2734
- writeFileSync6(driftLogPath, JSON.stringify(entries, null, 2));
950
+ writeFileSync5(driftLogPath, JSON.stringify(entries, null, 2));
2735
951
  return { success: true };
2736
952
  } catch (e) {
2737
953
  return { success: false, error: `Could not write drift log: ${e.message}` };
@@ -2739,23 +955,23 @@ function resolveDriftEntries(projectRoot, options) {
2739
955
  }
2740
956
 
2741
957
  // src/commands/refresh.ts
2742
- import { readFileSync as readFileSync8, existsSync as existsSync9 } from "fs";
2743
- import { join as join9 } from "path";
958
+ import { readFileSync as readFileSync7, existsSync as existsSync8 } from "fs";
959
+ import { join as join8 } from "path";
2744
960
  import { isV3 as isV32 } from "@decantr/essence-spec";
2745
961
  var GREEN4 = "\x1B[32m";
2746
962
  var RED3 = "\x1B[31m";
2747
963
  var DIM4 = "\x1B[2m";
2748
964
  var RESET4 = "\x1B[0m";
2749
965
  async function cmdRefresh(projectRoot = process.cwd()) {
2750
- const essencePath = join9(projectRoot, "decantr.essence.json");
2751
- if (!existsSync9(essencePath)) {
966
+ const essencePath = join8(projectRoot, "decantr.essence.json");
967
+ if (!existsSync8(essencePath)) {
2752
968
  console.error(`${RED3}No decantr.essence.json found. Run \`decantr init\` first.${RESET4}`);
2753
969
  process.exitCode = 1;
2754
970
  return;
2755
971
  }
2756
972
  let essence;
2757
973
  try {
2758
- const raw = readFileSync8(essencePath, "utf-8");
974
+ const raw = readFileSync7(essencePath, "utf-8");
2759
975
  const parsed = JSON.parse(raw);
2760
976
  if (!isV32(parsed)) {
2761
977
  console.error(`${RED3}Essence is not v3. Run \`decantr migrate\` first.${RESET4}`);
@@ -2769,7 +985,7 @@ async function cmdRefresh(projectRoot = process.cwd()) {
2769
985
  return;
2770
986
  }
2771
987
  const registryClient = new RegistryClient({
2772
- cacheDir: join9(projectRoot, ".decantr", "cache")
988
+ cacheDir: join8(projectRoot, ".decantr", "cache")
2773
989
  });
2774
990
  console.log("Regenerating derived files...\n");
2775
991
  const result = await refreshDerivedFiles(projectRoot, essence, registryClient);
@@ -2788,23 +1004,23 @@ async function cmdRefresh(projectRoot = process.cwd()) {
2788
1004
  }
2789
1005
 
2790
1006
  // src/commands/add.ts
2791
- import { readFileSync as readFileSync9, writeFileSync as writeFileSync7, existsSync as existsSync10 } from "fs";
2792
- import { join as join10 } from "path";
1007
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync9 } from "fs";
1008
+ import { join as join9 } from "path";
2793
1009
  import { isV3 as isV33, migrateV30ToV31 } from "@decantr/essence-spec";
2794
1010
  var GREEN5 = "\x1B[32m";
2795
1011
  var RED4 = "\x1B[31m";
2796
1012
  var DIM5 = "\x1B[2m";
2797
1013
  var RESET5 = "\x1B[0m";
2798
1014
  function readAndMigrate(projectRoot) {
2799
- const essencePath = join10(projectRoot, "decantr.essence.json");
2800
- if (!existsSync10(essencePath)) {
1015
+ const essencePath = join9(projectRoot, "decantr.essence.json");
1016
+ if (!existsSync9(essencePath)) {
2801
1017
  console.error(`${RED4}No decantr.essence.json found. Run \`decantr init\` first.${RESET5}`);
2802
1018
  process.exitCode = 1;
2803
1019
  return null;
2804
1020
  }
2805
1021
  let parsed;
2806
1022
  try {
2807
- parsed = JSON.parse(readFileSync9(essencePath, "utf-8"));
1023
+ parsed = JSON.parse(readFileSync8(essencePath, "utf-8"));
2808
1024
  } catch (e) {
2809
1025
  console.error(`${RED4}Could not read essence: ${e.message}${RESET5}`);
2810
1026
  process.exitCode = 1;
@@ -2819,7 +1035,7 @@ function readAndMigrate(projectRoot) {
2819
1035
  return { essence, essencePath };
2820
1036
  }
2821
1037
  function writeEssence(essencePath, essence) {
2822
- writeFileSync7(essencePath, JSON.stringify(essence, null, 2) + "\n");
1038
+ writeFileSync6(essencePath, JSON.stringify(essence, null, 2) + "\n");
2823
1039
  }
2824
1040
  async function cmdAddSection(archetypeId, args, projectRoot = process.cwd()) {
2825
1041
  if (!archetypeId) {
@@ -2838,7 +1054,7 @@ async function cmdAddSection(archetypeId, args, projectRoot = process.cwd()) {
2838
1054
  return;
2839
1055
  }
2840
1056
  const registryClient = new RegistryClient({
2841
- cacheDir: join10(projectRoot, ".decantr", "cache")
1057
+ cacheDir: join9(projectRoot, ".decantr", "cache")
2842
1058
  });
2843
1059
  const result = await registryClient.fetchArchetype(archetypeId);
2844
1060
  if (!result) {
@@ -2902,7 +1118,7 @@ async function cmdAddPage(path, args, projectRoot = process.cwd()) {
2902
1118
  writeEssence(essencePath, essence);
2903
1119
  console.log(`${GREEN5}Added page "${pageId}" to section "${sectionId}".${RESET5}`);
2904
1120
  const registryClient = new RegistryClient({
2905
- cacheDir: join10(projectRoot, ".decantr", "cache")
1121
+ cacheDir: join9(projectRoot, ".decantr", "cache")
2906
1122
  });
2907
1123
  await refreshDerivedFiles(projectRoot, essence, registryClient);
2908
1124
  console.log(`${GREEN5}Derived files refreshed.${RESET5}`);
@@ -2941,30 +1157,30 @@ async function cmdAddFeature(feature, args, projectRoot = process.cwd()) {
2941
1157
  const target = sectionId ? `section "${sectionId}" and global` : "global";
2942
1158
  console.log(`${GREEN5}Added feature "${feature}" to ${target} features.${RESET5}`);
2943
1159
  const registryClient = new RegistryClient({
2944
- cacheDir: join10(projectRoot, ".decantr", "cache")
1160
+ cacheDir: join9(projectRoot, ".decantr", "cache")
2945
1161
  });
2946
1162
  await refreshDerivedFiles(projectRoot, essence, registryClient);
2947
1163
  console.log(`${GREEN5}Derived files refreshed.${RESET5}`);
2948
1164
  }
2949
1165
 
2950
1166
  // src/commands/remove.ts
2951
- import { readFileSync as readFileSync10, writeFileSync as writeFileSync8, existsSync as existsSync11, rmSync as rmSync3 } from "fs";
2952
- import { join as join11 } from "path";
1167
+ import { readFileSync as readFileSync9, writeFileSync as writeFileSync7, existsSync as existsSync10, rmSync as rmSync3 } from "fs";
1168
+ import { join as join10 } from "path";
2953
1169
  import { isV3 as isV34, migrateV30ToV31 as migrateV30ToV312 } from "@decantr/essence-spec";
2954
1170
  var GREEN6 = "\x1B[32m";
2955
1171
  var RED5 = "\x1B[31m";
2956
1172
  var DIM6 = "\x1B[2m";
2957
1173
  var RESET6 = "\x1B[0m";
2958
1174
  function readAndMigrate2(projectRoot) {
2959
- const essencePath = join11(projectRoot, "decantr.essence.json");
2960
- if (!existsSync11(essencePath)) {
1175
+ const essencePath = join10(projectRoot, "decantr.essence.json");
1176
+ if (!existsSync10(essencePath)) {
2961
1177
  console.error(`${RED5}No decantr.essence.json found. Run \`decantr init\` first.${RESET6}`);
2962
1178
  process.exitCode = 1;
2963
1179
  return null;
2964
1180
  }
2965
1181
  let parsed;
2966
1182
  try {
2967
- parsed = JSON.parse(readFileSync10(essencePath, "utf-8"));
1183
+ parsed = JSON.parse(readFileSync9(essencePath, "utf-8"));
2968
1184
  } catch (e) {
2969
1185
  console.error(`${RED5}Could not read essence: ${e.message}${RESET6}`);
2970
1186
  process.exitCode = 1;
@@ -2979,7 +1195,7 @@ function readAndMigrate2(projectRoot) {
2979
1195
  return { essence, essencePath };
2980
1196
  }
2981
1197
  function writeEssence2(essencePath, essence) {
2982
- writeFileSync8(essencePath, JSON.stringify(essence, null, 2) + "\n");
1198
+ writeFileSync7(essencePath, JSON.stringify(essence, null, 2) + "\n");
2983
1199
  }
2984
1200
  function recomputeGlobalFeatures(essence) {
2985
1201
  const all = /* @__PURE__ */ new Set();
@@ -3021,14 +1237,14 @@ async function cmdRemoveSection(sectionId, args, projectRoot = process.cwd()) {
3021
1237
  sections.splice(idx, 1);
3022
1238
  recomputeGlobalFeatures(essence);
3023
1239
  removeRoutes(essence, sectionId);
3024
- const contextFile = join11(projectRoot, ".decantr", "context", `${sectionId}.md`);
3025
- if (existsSync11(contextFile)) {
1240
+ const contextFile = join10(projectRoot, ".decantr", "context", `${sectionId}.md`);
1241
+ if (existsSync10(contextFile)) {
3026
1242
  rmSync3(contextFile);
3027
1243
  }
3028
1244
  writeEssence2(essencePath, essence);
3029
1245
  console.log(`${GREEN6}Removed section "${sectionId}".${RESET6}`);
3030
1246
  const registryClient = new RegistryClient({
3031
- cacheDir: join11(projectRoot, ".decantr", "cache")
1247
+ cacheDir: join10(projectRoot, ".decantr", "cache")
3032
1248
  });
3033
1249
  await refreshDerivedFiles(projectRoot, essence, registryClient);
3034
1250
  console.log(`${GREEN6}Derived files refreshed.${RESET6}`);
@@ -3064,7 +1280,7 @@ async function cmdRemovePage(path, args, projectRoot = process.cwd()) {
3064
1280
  writeEssence2(essencePath, essence);
3065
1281
  console.log(`${GREEN6}Removed page "${pageId}" from section "${sectionId}".${RESET6}`);
3066
1282
  const registryClient = new RegistryClient({
3067
- cacheDir: join11(projectRoot, ".decantr", "cache")
1283
+ cacheDir: join10(projectRoot, ".decantr", "cache")
3068
1284
  });
3069
1285
  await refreshDerivedFiles(projectRoot, essence, registryClient);
3070
1286
  console.log(`${GREEN6}Derived files refreshed.${RESET6}`);
@@ -3105,15 +1321,15 @@ async function cmdRemoveFeature(feature, args, projectRoot = process.cwd()) {
3105
1321
  const target = sectionId ? `section "${sectionId}" and global` : "global";
3106
1322
  console.log(`${GREEN6}Removed feature "${feature}" from ${target} features.${RESET6}`);
3107
1323
  const registryClient = new RegistryClient({
3108
- cacheDir: join11(projectRoot, ".decantr", "cache")
1324
+ cacheDir: join10(projectRoot, ".decantr", "cache")
3109
1325
  });
3110
1326
  await refreshDerivedFiles(projectRoot, essence, registryClient);
3111
1327
  console.log(`${GREEN6}Derived files refreshed.${RESET6}`);
3112
1328
  }
3113
1329
 
3114
1330
  // src/commands/theme-switch.ts
3115
- import { readFileSync as readFileSync11, writeFileSync as writeFileSync9, existsSync as existsSync12 } from "fs";
3116
- import { join as join12 } from "path";
1331
+ import { readFileSync as readFileSync10, writeFileSync as writeFileSync8, existsSync as existsSync11 } from "fs";
1332
+ import { join as join11 } from "path";
3117
1333
  import { isV3 as isV35, migrateV30ToV31 as migrateV30ToV313 } from "@decantr/essence-spec";
3118
1334
  var GREEN7 = "\x1B[32m";
3119
1335
  var RED6 = "\x1B[31m";
@@ -3126,15 +1342,15 @@ async function cmdThemeSwitch(themeName, args, projectRoot = process.cwd()) {
3126
1342
  process.exitCode = 1;
3127
1343
  return;
3128
1344
  }
3129
- const essencePath = join12(projectRoot, "decantr.essence.json");
3130
- if (!existsSync12(essencePath)) {
1345
+ const essencePath = join11(projectRoot, "decantr.essence.json");
1346
+ if (!existsSync11(essencePath)) {
3131
1347
  console.error(`${RED6}No decantr.essence.json found. Run \`decantr init\` first.${RESET7}`);
3132
1348
  process.exitCode = 1;
3133
1349
  return;
3134
1350
  }
3135
1351
  let parsed;
3136
1352
  try {
3137
- parsed = JSON.parse(readFileSync11(essencePath, "utf-8"));
1353
+ parsed = JSON.parse(readFileSync10(essencePath, "utf-8"));
3138
1354
  } catch (e) {
3139
1355
  console.error(`${RED6}Could not read essence: ${e.message}${RESET7}`);
3140
1356
  process.exitCode = 1;
@@ -3179,7 +1395,7 @@ async function cmdThemeSwitch(themeName, args, projectRoot = process.cwd()) {
3179
1395
  essence.dna.theme.mode = mode;
3180
1396
  }
3181
1397
  const registryClient = new RegistryClient({
3182
- cacheDir: join12(projectRoot, ".decantr", "cache")
1398
+ cacheDir: join11(projectRoot, ".decantr", "cache")
3183
1399
  });
3184
1400
  const recipeName = essence.dna.theme.recipe;
3185
1401
  try {
@@ -3197,7 +1413,7 @@ async function cmdThemeSwitch(themeName, args, projectRoot = process.cwd()) {
3197
1413
  }
3198
1414
  } catch {
3199
1415
  }
3200
- writeFileSync9(essencePath, JSON.stringify(essence, null, 2) + "\n");
1416
+ writeFileSync8(essencePath, JSON.stringify(essence, null, 2) + "\n");
3201
1417
  console.log(`${GREEN7}Switched theme: ${oldStyle} \u2192 ${themeName}${RESET7}`);
3202
1418
  if (recipe) console.log(` ${DIM7}Recipe: ${recipe}${RESET7}`);
3203
1419
  if (shape) console.log(` ${DIM7}Shape: ${shape}${RESET7}`);
@@ -3208,12 +1424,12 @@ async function cmdThemeSwitch(themeName, args, projectRoot = process.cwd()) {
3208
1424
  }
3209
1425
 
3210
1426
  // src/commands/analyze.ts
3211
- import { existsSync as existsSync19, mkdirSync as mkdirSync5, writeFileSync as writeFileSync10 } from "fs";
3212
- import { join as join19 } from "path";
1427
+ import { existsSync as existsSync18, mkdirSync as mkdirSync4, writeFileSync as writeFileSync9 } from "fs";
1428
+ import { join as join18 } from "path";
3213
1429
 
3214
1430
  // src/analyzers/routes.ts
3215
- import { existsSync as existsSync13, readdirSync as readdirSync2, statSync } from "fs";
3216
- import { join as join13, relative } from "path";
1431
+ import { existsSync as existsSync12, readdirSync as readdirSync2, statSync } from "fs";
1432
+ import { join as join12, relative } from "path";
3217
1433
  var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".next", ".git", "api", "_app", "_document"]);
3218
1434
  function shouldSkipDir(name) {
3219
1435
  return name.startsWith("_") || name.startsWith(".") || SKIP_DIRS.has(name);
@@ -3249,13 +1465,13 @@ function walkAppDir(dir, baseDir, segments) {
3249
1465
  const pageFile = entries.find((e) => e.startsWith("page."));
3250
1466
  routes.push({
3251
1467
  path: routePath || "/",
3252
- file: relative(baseDir, join13(dir, pageFile)),
1468
+ file: relative(baseDir, join12(dir, pageFile)),
3253
1469
  hasLayout
3254
1470
  });
3255
1471
  }
3256
1472
  for (const entry of entries) {
3257
1473
  if (shouldSkipDir(entry)) continue;
3258
- const fullPath = join13(dir, entry);
1474
+ const fullPath = join12(dir, entry);
3259
1475
  try {
3260
1476
  if (!statSync(fullPath).isDirectory()) continue;
3261
1477
  } catch {
@@ -3277,7 +1493,7 @@ function walkPagesDir(dir, baseDir, segments) {
3277
1493
  }
3278
1494
  for (const entry of entries) {
3279
1495
  if (shouldSkipDir(entry)) continue;
3280
- const fullPath = join13(dir, entry);
1496
+ const fullPath = join12(dir, entry);
3281
1497
  try {
3282
1498
  const stat = statSync(fullPath);
3283
1499
  if (stat.isDirectory()) {
@@ -3305,11 +1521,11 @@ function walkPagesDir(dir, baseDir, segments) {
3305
1521
  }
3306
1522
  function scanRoutes(projectRoot) {
3307
1523
  const appDirs = [
3308
- join13(projectRoot, "src", "app"),
3309
- join13(projectRoot, "app")
1524
+ join12(projectRoot, "src", "app"),
1525
+ join12(projectRoot, "app")
3310
1526
  ];
3311
1527
  for (const appDir of appDirs) {
3312
- if (existsSync13(appDir)) {
1528
+ if (existsSync12(appDir)) {
3313
1529
  const routes = walkAppDir(appDir, projectRoot, []);
3314
1530
  if (routes.length > 0) {
3315
1531
  return { strategy: "app-router", routes };
@@ -3317,11 +1533,11 @@ function scanRoutes(projectRoot) {
3317
1533
  }
3318
1534
  }
3319
1535
  const pagesDirs = [
3320
- join13(projectRoot, "src", "pages"),
3321
- join13(projectRoot, "pages")
1536
+ join12(projectRoot, "src", "pages"),
1537
+ join12(projectRoot, "pages")
3322
1538
  ];
3323
1539
  for (const pagesDir of pagesDirs) {
3324
- if (existsSync13(pagesDir)) {
1540
+ if (existsSync12(pagesDir)) {
3325
1541
  const routes = walkPagesDir(pagesDir, projectRoot, []);
3326
1542
  if (routes.length > 0) {
3327
1543
  return { strategy: "pages-router", routes };
@@ -3332,8 +1548,8 @@ function scanRoutes(projectRoot) {
3332
1548
  }
3333
1549
 
3334
1550
  // src/analyzers/components.ts
3335
- import { existsSync as existsSync14, readdirSync as readdirSync3, statSync as statSync2 } from "fs";
3336
- import { join as join14 } from "path";
1551
+ import { existsSync as existsSync13, readdirSync as readdirSync3, statSync as statSync2 } from "fs";
1552
+ import { join as join13 } from "path";
3337
1553
  var PAGE_EXTENSIONS = /* @__PURE__ */ new Set([".tsx", ".ts", ".jsx", ".js"]);
3338
1554
  function countFilesRecursive(dir, extensions) {
3339
1555
  let count = 0;
@@ -3345,7 +1561,7 @@ function countFilesRecursive(dir, extensions) {
3345
1561
  }
3346
1562
  for (const entry of entries) {
3347
1563
  if (entry.startsWith(".") || entry === "node_modules") continue;
3348
- const fullPath = join14(dir, entry);
1564
+ const fullPath = join13(dir, entry);
3349
1565
  try {
3350
1566
  const stat = statSync2(fullPath);
3351
1567
  if (stat.isDirectory()) {
@@ -3372,7 +1588,7 @@ function countPageFiles(dir) {
3372
1588
  }
3373
1589
  for (const entry of entries) {
3374
1590
  if (entry.startsWith(".") || entry === "node_modules") continue;
3375
- const fullPath = join14(dir, entry);
1591
+ const fullPath = join13(dir, entry);
3376
1592
  try {
3377
1593
  const stat = statSync2(fullPath);
3378
1594
  if (stat.isDirectory()) {
@@ -3392,32 +1608,32 @@ function scanComponents(projectRoot) {
3392
1608
  let pageCount = 0;
3393
1609
  const componentDirs = [];
3394
1610
  const appDirs = [
3395
- join14(projectRoot, "src", "app"),
3396
- join14(projectRoot, "app")
1611
+ join13(projectRoot, "src", "app"),
1612
+ join13(projectRoot, "app")
3397
1613
  ];
3398
1614
  for (const dir of appDirs) {
3399
- if (existsSync14(dir)) {
1615
+ if (existsSync13(dir)) {
3400
1616
  pageCount += countPageFiles(dir);
3401
1617
  }
3402
1618
  }
3403
1619
  const pagesDirs = [
3404
- join14(projectRoot, "src", "pages"),
3405
- join14(projectRoot, "pages")
1620
+ join13(projectRoot, "src", "pages"),
1621
+ join13(projectRoot, "pages")
3406
1622
  ];
3407
1623
  for (const dir of pagesDirs) {
3408
- if (existsSync14(dir)) {
1624
+ if (existsSync13(dir)) {
3409
1625
  pageCount += countFilesRecursive(dir, PAGE_EXTENSIONS);
3410
1626
  }
3411
1627
  }
3412
1628
  let componentCount = 0;
3413
1629
  const componentPaths = [
3414
- join14(projectRoot, "src", "components"),
3415
- join14(projectRoot, "components"),
3416
- join14(projectRoot, "src", "ui"),
3417
- join14(projectRoot, "ui")
1630
+ join13(projectRoot, "src", "components"),
1631
+ join13(projectRoot, "components"),
1632
+ join13(projectRoot, "src", "ui"),
1633
+ join13(projectRoot, "ui")
3418
1634
  ];
3419
1635
  for (const dir of componentPaths) {
3420
- if (existsSync14(dir)) {
1636
+ if (existsSync13(dir)) {
3421
1637
  const count = countFilesRecursive(dir, PAGE_EXTENSIONS);
3422
1638
  if (count > 0) {
3423
1639
  componentCount += count;
@@ -3434,8 +1650,8 @@ function scanComponents(projectRoot) {
3434
1650
  }
3435
1651
 
3436
1652
  // src/analyzers/styling.ts
3437
- import { existsSync as existsSync15, readFileSync as readFileSync12 } from "fs";
3438
- import { join as join15 } from "path";
1653
+ import { existsSync as existsSync14, readFileSync as readFileSync11 } from "fs";
1654
+ import { join as join14 } from "path";
3439
1655
  var TAILWIND_CONFIGS = [
3440
1656
  "tailwind.config.js",
3441
1657
  "tailwind.config.ts",
@@ -3479,10 +1695,10 @@ function detectDarkMode(projectRoot, cssContent) {
3479
1695
  "app/layout.jsx"
3480
1696
  ];
3481
1697
  for (const rel of layoutPaths) {
3482
- const fullPath = join15(projectRoot, rel);
3483
- if (existsSync15(fullPath)) {
1698
+ const fullPath = join14(projectRoot, rel);
1699
+ if (existsSync14(fullPath)) {
3484
1700
  try {
3485
- const layoutContent = readFileSync12(fullPath, "utf-8");
1701
+ const layoutContent = readFileSync11(fullPath, "utf-8");
3486
1702
  if (layoutContent.includes('className="dark"') || layoutContent.includes("className='dark'") || layoutContent.includes('class="dark"')) {
3487
1703
  return true;
3488
1704
  }
@@ -3490,10 +1706,10 @@ function detectDarkMode(projectRoot, cssContent) {
3490
1706
  }
3491
1707
  }
3492
1708
  }
3493
- const pkgPath = join15(projectRoot, "package.json");
3494
- if (existsSync15(pkgPath)) {
1709
+ const pkgPath = join14(projectRoot, "package.json");
1710
+ if (existsSync14(pkgPath)) {
3495
1711
  try {
3496
- const pkg = JSON.parse(readFileSync12(pkgPath, "utf-8"));
1712
+ const pkg = JSON.parse(readFileSync11(pkgPath, "utf-8"));
3497
1713
  const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
3498
1714
  if (allDeps["next-themes"] || allDeps["theme-toggle"] || allDeps["use-dark-mode"]) {
3499
1715
  return true;
@@ -3507,17 +1723,17 @@ function scanStyling(projectRoot) {
3507
1723
  let approach = "unknown";
3508
1724
  let configFile;
3509
1725
  for (const cfg of TAILWIND_CONFIGS) {
3510
- if (existsSync15(join15(projectRoot, cfg))) {
1726
+ if (existsSync14(join14(projectRoot, cfg))) {
3511
1727
  approach = "tailwind";
3512
1728
  configFile = cfg;
3513
1729
  break;
3514
1730
  }
3515
1731
  }
3516
1732
  if (approach === "unknown") {
3517
- const pkgPath = join15(projectRoot, "package.json");
3518
- if (existsSync15(pkgPath)) {
1733
+ const pkgPath = join14(projectRoot, "package.json");
1734
+ if (existsSync14(pkgPath)) {
3519
1735
  try {
3520
- const pkg = JSON.parse(readFileSync12(pkgPath, "utf-8"));
1736
+ const pkg = JSON.parse(readFileSync11(pkgPath, "utf-8"));
3521
1737
  const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
3522
1738
  if (allDeps.tailwindcss || allDeps["@tailwindcss/postcss"] || allDeps["@tailwindcss/vite"]) {
3523
1739
  approach = "tailwind";
@@ -3528,10 +1744,10 @@ function scanStyling(projectRoot) {
3528
1744
  }
3529
1745
  let cssContent = null;
3530
1746
  for (const rel of GLOBALS_CSS_PATHS) {
3531
- const fullPath = join15(projectRoot, rel);
3532
- if (existsSync15(fullPath)) {
1747
+ const fullPath = join14(projectRoot, rel);
1748
+ if (existsSync14(fullPath)) {
3533
1749
  try {
3534
- cssContent = readFileSync12(fullPath, "utf-8");
1750
+ cssContent = readFileSync11(fullPath, "utf-8");
3535
1751
  } catch {
3536
1752
  }
3537
1753
  break;
@@ -3555,8 +1771,8 @@ function scanStyling(projectRoot) {
3555
1771
  }
3556
1772
 
3557
1773
  // src/analyzers/layout.ts
3558
- import { existsSync as existsSync16, readFileSync as readFileSync13, readdirSync as readdirSync4 } from "fs";
3559
- import { join as join16 } from "path";
1774
+ import { existsSync as existsSync15, readFileSync as readFileSync12, readdirSync as readdirSync4 } from "fs";
1775
+ import { join as join15 } from "path";
3560
1776
  var SIDEBAR_PATTERNS = ["sidebar", "side-bar", "sidenav", "side-nav", "drawer", "aside"];
3561
1777
  var NAV_PATTERNS = ["nav", "navbar", "header", "top-bar", "topbar", "app-bar", "appbar"];
3562
1778
  var FOOTER_PATTERNS = ["footer", "bottom-bar", "bottombar"];
@@ -3567,12 +1783,12 @@ function containsPattern(text, patterns) {
3567
1783
  function checkComponentDirs(projectRoot) {
3568
1784
  const result = { sidebar: false, nav: false, footer: false };
3569
1785
  const componentDirs = [
3570
- join16(projectRoot, "src", "components"),
3571
- join16(projectRoot, "components"),
3572
- join16(projectRoot, "src", "ui")
1786
+ join15(projectRoot, "src", "components"),
1787
+ join15(projectRoot, "components"),
1788
+ join15(projectRoot, "src", "ui")
3573
1789
  ];
3574
1790
  for (const dir of componentDirs) {
3575
- if (!existsSync16(dir)) continue;
1791
+ if (!existsSync15(dir)) continue;
3576
1792
  let entries;
3577
1793
  try {
3578
1794
  entries = readdirSync4(dir);
@@ -3591,16 +1807,16 @@ function checkComponentDirs(projectRoot) {
3591
1807
  function checkLayoutFiles(projectRoot) {
3592
1808
  const result = { sidebar: false, nav: false, footer: false };
3593
1809
  const layoutPaths = [
3594
- join16(projectRoot, "src", "app", "layout.tsx"),
3595
- join16(projectRoot, "src", "app", "layout.jsx"),
3596
- join16(projectRoot, "app", "layout.tsx"),
3597
- join16(projectRoot, "app", "layout.jsx")
1810
+ join15(projectRoot, "src", "app", "layout.tsx"),
1811
+ join15(projectRoot, "src", "app", "layout.jsx"),
1812
+ join15(projectRoot, "app", "layout.tsx"),
1813
+ join15(projectRoot, "app", "layout.jsx")
3598
1814
  ];
3599
1815
  for (const layoutPath of layoutPaths) {
3600
- if (!existsSync16(layoutPath)) continue;
1816
+ if (!existsSync15(layoutPath)) continue;
3601
1817
  let content;
3602
1818
  try {
3603
- content = readFileSync13(layoutPath, "utf-8");
1819
+ content = readFileSync12(layoutPath, "utf-8");
3604
1820
  } catch {
3605
1821
  continue;
3606
1822
  }
@@ -3611,16 +1827,16 @@ function checkLayoutFiles(projectRoot) {
3611
1827
  const subLayoutDirs = ["dashboard", "admin", "app"];
3612
1828
  for (const sub of subLayoutDirs) {
3613
1829
  const paths = [
3614
- join16(projectRoot, "src", "app", sub, "layout.tsx"),
3615
- join16(projectRoot, "src", "app", sub, "layout.jsx"),
3616
- join16(projectRoot, "app", sub, "layout.tsx"),
3617
- join16(projectRoot, "app", sub, "layout.jsx")
1830
+ join15(projectRoot, "src", "app", sub, "layout.tsx"),
1831
+ join15(projectRoot, "src", "app", sub, "layout.jsx"),
1832
+ join15(projectRoot, "app", sub, "layout.tsx"),
1833
+ join15(projectRoot, "app", sub, "layout.jsx")
3618
1834
  ];
3619
1835
  for (const layoutPath of paths) {
3620
- if (!existsSync16(layoutPath)) continue;
1836
+ if (!existsSync15(layoutPath)) continue;
3621
1837
  let content;
3622
1838
  try {
3623
- content = readFileSync13(layoutPath, "utf-8");
1839
+ content = readFileSync12(layoutPath, "utf-8");
3624
1840
  } catch {
3625
1841
  continue;
3626
1842
  }
@@ -3656,8 +1872,8 @@ function scanLayout(projectRoot) {
3656
1872
  }
3657
1873
 
3658
1874
  // src/analyzers/features.ts
3659
- import { existsSync as existsSync17, readdirSync as readdirSync5, statSync as statSync3 } from "fs";
3660
- import { join as join17 } from "path";
1875
+ import { existsSync as existsSync16, readdirSync as readdirSync5, statSync as statSync3 } from "fs";
1876
+ import { join as join16 } from "path";
3661
1877
  var FEATURE_PATTERNS = {
3662
1878
  auth: ["login", "signin", "sign-in", "signup", "sign-up", "register", "auth", "callback", "signout", "sign-out", "logout", "forgot-password", "reset-password", "verify-email"],
3663
1879
  dashboard: ["dashboard", "overview", "analytics", "metrics", "stats"],
@@ -3685,7 +1901,7 @@ function collectPaths(dir, baseDir, depth = 0) {
3685
1901
  }
3686
1902
  for (const entry of entries) {
3687
1903
  if (entry.startsWith(".") || entry.startsWith("_") || entry === "node_modules" || entry === "api") continue;
3688
- const fullPath = join17(dir, entry);
1904
+ const fullPath = join16(dir, entry);
3689
1905
  const relPath = fullPath.slice(baseDir.length + 1);
3690
1906
  paths.push(relPath);
3691
1907
  try {
@@ -3702,16 +1918,16 @@ function scanFeatures(projectRoot) {
3702
1918
  const detected = [];
3703
1919
  const evidence = {};
3704
1920
  const scanDirs = [
3705
- join17(projectRoot, "src", "app"),
3706
- join17(projectRoot, "app"),
3707
- join17(projectRoot, "src", "pages"),
3708
- join17(projectRoot, "pages"),
3709
- join17(projectRoot, "src", "components"),
3710
- join17(projectRoot, "components")
1921
+ join16(projectRoot, "src", "app"),
1922
+ join16(projectRoot, "app"),
1923
+ join16(projectRoot, "src", "pages"),
1924
+ join16(projectRoot, "pages"),
1925
+ join16(projectRoot, "src", "components"),
1926
+ join16(projectRoot, "components")
3711
1927
  ];
3712
1928
  const allPaths = [];
3713
1929
  for (const dir of scanDirs) {
3714
- if (existsSync17(dir)) {
1930
+ if (existsSync16(dir)) {
3715
1931
  allPaths.push(...collectPaths(dir, projectRoot));
3716
1932
  }
3717
1933
  }
@@ -3735,8 +1951,8 @@ function scanFeatures(projectRoot) {
3735
1951
  }
3736
1952
 
3737
1953
  // src/analyzers/dependencies.ts
3738
- import { existsSync as existsSync18, readFileSync as readFileSync14 } from "fs";
3739
- import { join as join18 } from "path";
1954
+ import { existsSync as existsSync17, readFileSync as readFileSync13 } from "fs";
1955
+ import { join as join17 } from "path";
3740
1956
  var CATEGORIES = {
3741
1957
  ui: [
3742
1958
  "react",
@@ -3884,11 +2100,11 @@ function scanDependencies(projectRoot) {
3884
2100
  styling: [],
3885
2101
  other: []
3886
2102
  };
3887
- const pkgPath = join18(projectRoot, "package.json");
3888
- if (!existsSync18(pkgPath)) return result;
2103
+ const pkgPath = join17(projectRoot, "package.json");
2104
+ if (!existsSync17(pkgPath)) return result;
3889
2105
  let pkg;
3890
2106
  try {
3891
- pkg = JSON.parse(readFileSync14(pkgPath, "utf-8"));
2107
+ pkg = JSON.parse(readFileSync13(pkgPath, "utf-8"));
3892
2108
  } catch {
3893
2109
  return result;
3894
2110
  }
@@ -3949,12 +2165,12 @@ ${BOLD3}Analyzing project...${RESET8}
3949
2165
  features,
3950
2166
  dependencies
3951
2167
  };
3952
- const decantrDir = join19(projectRoot, ".decantr");
3953
- if (!existsSync19(decantrDir)) {
3954
- mkdirSync5(decantrDir, { recursive: true });
2168
+ const decantrDir = join18(projectRoot, ".decantr");
2169
+ if (!existsSync18(decantrDir)) {
2170
+ mkdirSync4(decantrDir, { recursive: true });
3955
2171
  }
3956
- const outputPath = join19(decantrDir, "analysis.json");
3957
- writeFileSync10(outputPath, JSON.stringify(analysis, null, 2) + "\n", "utf-8");
2172
+ const outputPath = join18(decantrDir, "analysis.json");
2173
+ writeFileSync9(outputPath, JSON.stringify(analysis, null, 2) + "\n", "utf-8");
3958
2174
  console.log(`
3959
2175
  ${GREEN8}Analysis complete.${RESET8}
3960
2176
  `);
@@ -4143,25 +2359,25 @@ async function cmdGet(type, id) {
4143
2359
  };
4144
2360
  const apiType = typeMap[type];
4145
2361
  const registryClient = new RegistryClient({
4146
- cacheDir: join20(process.cwd(), ".decantr", "cache")
2362
+ cacheDir: join19(process.cwd(), ".decantr", "cache")
4147
2363
  });
4148
2364
  const result = await registryClient.fetchContentItem(apiType, id);
4149
2365
  if (result) {
4150
2366
  console.log(JSON.stringify(result.data, null, 2));
4151
2367
  return;
4152
2368
  }
4153
- const currentDir = dirname2(fileURLToPath2(import.meta.url));
2369
+ const currentDir = dirname(fileURLToPath(import.meta.url));
4154
2370
  const bundledCandidates = [
4155
- join20(currentDir, "bundled", apiType, `${id}.json`),
2371
+ join19(currentDir, "bundled", apiType, `${id}.json`),
4156
2372
  // Running from src/
4157
- join20(currentDir, "..", "src", "bundled", apiType, `${id}.json`),
2373
+ join19(currentDir, "..", "src", "bundled", apiType, `${id}.json`),
4158
2374
  // Running from dist/
4159
- join20(currentDir, "..", "bundled", apiType, `${id}.json`)
2375
+ join19(currentDir, "..", "bundled", apiType, `${id}.json`)
4160
2376
  // Alternative dist layout
4161
2377
  ];
4162
- const bundledPath = bundledCandidates.find((p) => existsSync20(p)) || null;
2378
+ const bundledPath = bundledCandidates.find((p) => existsSync19(p)) || null;
4163
2379
  if (bundledPath) {
4164
- const data = JSON.parse(readFileSync15(bundledPath, "utf-8"));
2380
+ const data = JSON.parse(readFileSync14(bundledPath, "utf-8"));
4165
2381
  console.log(JSON.stringify(data, null, 2));
4166
2382
  return;
4167
2383
  }
@@ -4173,13 +2389,13 @@ function buildRegistryContext() {
4173
2389
  const themeRegistry = /* @__PURE__ */ new Map();
4174
2390
  const patternRegistry = /* @__PURE__ */ new Map();
4175
2391
  const projectRoot = process.cwd();
4176
- const cacheDir = join20(projectRoot, ".decantr", "cache");
4177
- const customDir = join20(projectRoot, ".decantr", "custom");
4178
- const cachedThemesDir = join20(cacheDir, "@official", "themes");
2392
+ const cacheDir = join19(projectRoot, ".decantr", "cache");
2393
+ const customDir = join19(projectRoot, ".decantr", "custom");
2394
+ const cachedThemesDir = join19(cacheDir, "@official", "themes");
4179
2395
  try {
4180
- if (existsSync20(cachedThemesDir)) {
2396
+ if (existsSync19(cachedThemesDir)) {
4181
2397
  for (const f of readdirSync6(cachedThemesDir).filter((f2) => f2.endsWith(".json") && f2 !== "index.json")) {
4182
- const data = JSON.parse(readFileSync15(join20(cachedThemesDir, f), "utf-8"));
2398
+ const data = JSON.parse(readFileSync14(join19(cachedThemesDir, f), "utf-8"));
4183
2399
  if (data.id && !themeRegistry.has(data.id)) {
4184
2400
  themeRegistry.set(data.id, { modes: data.modes || ["light", "dark"] });
4185
2401
  }
@@ -4187,11 +2403,11 @@ function buildRegistryContext() {
4187
2403
  }
4188
2404
  } catch {
4189
2405
  }
4190
- const customThemesDir = join20(customDir, "themes");
2406
+ const customThemesDir = join19(customDir, "themes");
4191
2407
  try {
4192
- if (existsSync20(customThemesDir)) {
2408
+ if (existsSync19(customThemesDir)) {
4193
2409
  for (const f of readdirSync6(customThemesDir).filter((f2) => f2.endsWith(".json"))) {
4194
- const data = JSON.parse(readFileSync15(join20(customThemesDir, f), "utf-8"));
2410
+ const data = JSON.parse(readFileSync14(join19(customThemesDir, f), "utf-8"));
4195
2411
  if (data.id) {
4196
2412
  themeRegistry.set(`custom:${data.id}`, { modes: data.modes || ["light", "dark"] });
4197
2413
  }
@@ -4199,11 +2415,11 @@ function buildRegistryContext() {
4199
2415
  }
4200
2416
  } catch {
4201
2417
  }
4202
- const cachedPatternsDir = join20(cacheDir, "@official", "patterns");
2418
+ const cachedPatternsDir = join19(cacheDir, "@official", "patterns");
4203
2419
  try {
4204
- if (existsSync20(cachedPatternsDir)) {
2420
+ if (existsSync19(cachedPatternsDir)) {
4205
2421
  for (const f of readdirSync6(cachedPatternsDir).filter((f2) => f2.endsWith(".json") && f2 !== "index.json")) {
4206
- const data = JSON.parse(readFileSync15(join20(cachedPatternsDir, f), "utf-8"));
2422
+ const data = JSON.parse(readFileSync14(join19(cachedPatternsDir, f), "utf-8"));
4207
2423
  if (data.id && !patternRegistry.has(data.id)) {
4208
2424
  patternRegistry.set(data.id, data);
4209
2425
  }
@@ -4214,10 +2430,10 @@ function buildRegistryContext() {
4214
2430
  return { themeRegistry, patternRegistry };
4215
2431
  }
4216
2432
  async function cmdValidate(path) {
4217
- const essencePath = path || join20(process.cwd(), "decantr.essence.json");
2433
+ const essencePath = path || join19(process.cwd(), "decantr.essence.json");
4218
2434
  let raw;
4219
2435
  try {
4220
- raw = readFileSync15(essencePath, "utf-8");
2436
+ raw = readFileSync14(essencePath, "utf-8");
4221
2437
  } catch {
4222
2438
  console.error(error(`Could not read ${essencePath}`));
4223
2439
  process.exitCode = 1;
@@ -4272,7 +2488,7 @@ async function cmdList(type) {
4272
2488
  return;
4273
2489
  }
4274
2490
  const registryClient = new RegistryClient({
4275
- cacheDir: join20(process.cwd(), ".decantr", "cache")
2491
+ cacheDir: join19(process.cwd(), ".decantr", "cache")
4276
2492
  });
4277
2493
  const result = await registryClient.fetchContentList(type);
4278
2494
  const items = result.data.items;
@@ -4319,7 +2535,7 @@ async function cmdInit(args) {
4319
2535
  }
4320
2536
  }
4321
2537
  const registryClient = new RegistryClient({
4322
- cacheDir: join20(projectRoot, ".decantr", "cache"),
2538
+ cacheDir: join19(projectRoot, ".decantr", "cache"),
4323
2539
  apiUrl: args.registry,
4324
2540
  offline: args.offline
4325
2541
  });
@@ -4598,7 +2814,7 @@ ${YELLOW6}You're offline. Scaffolding Decantr default.${RESET9}`);
4598
2814
  console.log(` ${cyan("decantr upgrade")} Update to latest patterns`);
4599
2815
  console.log(` ${cyan("decantr check")} Detect drift issues`);
4600
2816
  console.log(` ${cyan("decantr migrate")} Migrate v2 essence to v3`);
4601
- const essenceContent = readFileSync15(result.essencePath, "utf-8");
2817
+ const essenceContent = readFileSync14(result.essencePath, "utf-8");
4602
2818
  const essence = JSON.parse(essenceContent);
4603
2819
  const validation = validateEssence2(essence);
4604
2820
  if (!validation.valid) {
@@ -4636,16 +2852,16 @@ Validation warnings: ${validation.errors.join(", ")}`));
4636
2852
  }
4637
2853
  async function cmdStatus() {
4638
2854
  const projectRoot = process.cwd();
4639
- const essencePath = join20(projectRoot, "decantr.essence.json");
4640
- const projectJsonPath = join20(projectRoot, ".decantr", "project.json");
2855
+ const essencePath = join19(projectRoot, "decantr.essence.json");
2856
+ const projectJsonPath = join19(projectRoot, ".decantr", "project.json");
4641
2857
  console.log(heading("Decantr Project Status"));
4642
- if (!existsSync20(essencePath)) {
2858
+ if (!existsSync19(essencePath)) {
4643
2859
  console.log(`${RED7}No decantr.essence.json found.${RESET9}`);
4644
2860
  console.log(dim('Run "decantr init" to create one.'));
4645
2861
  return;
4646
2862
  }
4647
2863
  try {
4648
- const essence = JSON.parse(readFileSync15(essencePath, "utf-8"));
2864
+ const essence = JSON.parse(readFileSync14(essencePath, "utf-8"));
4649
2865
  const validation = validateEssence2(essence);
4650
2866
  const essenceVersion = isV36(essence) ? "v3" : "v2";
4651
2867
  console.log(`${BOLD4}Essence:${RESET9}`);
@@ -4687,9 +2903,9 @@ async function cmdStatus() {
4687
2903
  }
4688
2904
  console.log("");
4689
2905
  console.log(`${BOLD4}Sync Status:${RESET9}`);
4690
- if (existsSync20(projectJsonPath)) {
2906
+ if (existsSync19(projectJsonPath)) {
4691
2907
  try {
4692
- const projectJson = JSON.parse(readFileSync15(projectJsonPath, "utf-8"));
2908
+ const projectJson = JSON.parse(readFileSync14(projectJsonPath, "utf-8"));
4693
2909
  const syncStatus = projectJson.sync?.status || "unknown";
4694
2910
  const lastSync = projectJson.sync?.lastSync || "never";
4695
2911
  const source = projectJson.sync?.registrySource || "unknown";
@@ -4707,7 +2923,7 @@ async function cmdStatus() {
4707
2923
  }
4708
2924
  async function cmdSync() {
4709
2925
  const projectRoot = process.cwd();
4710
- const cacheDir = join20(projectRoot, ".decantr", "cache");
2926
+ const cacheDir = join19(projectRoot, ".decantr", "cache");
4711
2927
  console.log(heading("Syncing registry content..."));
4712
2928
  const result = await syncRegistry(cacheDir);
4713
2929
  if (result.synced.length > 0) {
@@ -4725,15 +2941,15 @@ async function cmdSync() {
4725
2941
  }
4726
2942
  async function cmdAudit() {
4727
2943
  const projectRoot = process.cwd();
4728
- const essencePath = join20(projectRoot, "decantr.essence.json");
2944
+ const essencePath = join19(projectRoot, "decantr.essence.json");
4729
2945
  console.log(heading("Auditing project..."));
4730
- if (!existsSync20(essencePath)) {
2946
+ if (!existsSync19(essencePath)) {
4731
2947
  console.log(`${RED7}No decantr.essence.json found.${RESET9}`);
4732
2948
  process.exitCode = 1;
4733
2949
  return;
4734
2950
  }
4735
2951
  try {
4736
- const essence = JSON.parse(readFileSync15(essencePath, "utf-8"));
2952
+ const essence = JSON.parse(readFileSync14(essencePath, "utf-8"));
4737
2953
  const validation = validateEssence2(essence);
4738
2954
  if (!validation.valid) {
4739
2955
  console.log(`${RED7}Essence validation failed:${RESET9}`);
@@ -4833,14 +3049,14 @@ ${BOLD4}Examples:${RESET9}
4833
3049
  process.exitCode = 1;
4834
3050
  return;
4835
3051
  }
4836
- const themePath = join20(projectRoot, ".decantr", "custom", "themes", `${name}.json`);
4837
- if (!existsSync20(themePath)) {
3052
+ const themePath = join19(projectRoot, ".decantr", "custom", "themes", `${name}.json`);
3053
+ if (!existsSync19(themePath)) {
4838
3054
  console.error(error(`Theme "${name}" not found at ${themePath}`));
4839
3055
  process.exitCode = 1;
4840
3056
  return;
4841
3057
  }
4842
3058
  try {
4843
- const theme = JSON.parse(readFileSync15(themePath, "utf-8"));
3059
+ const theme = JSON.parse(readFileSync14(themePath, "utf-8"));
4844
3060
  const result = validateCustomTheme(theme);
4845
3061
  if (result.valid) {
4846
3062
  console.log(success(`Custom theme "${name}" is valid`));
@@ -5025,8 +3241,9 @@ async function main() {
5025
3241
  break;
5026
3242
  }
5027
3243
  case "upgrade": {
5028
- const { cmdUpgrade } = await import("./upgrade-ZSLT32KN.js");
5029
- await cmdUpgrade(process.cwd());
3244
+ const { cmdUpgrade } = await import("./upgrade-EHMDEZGC.js");
3245
+ const applyFlag = args.includes("--apply");
3246
+ await cmdUpgrade(process.cwd(), { apply: applyFlag });
5030
3247
  break;
5031
3248
  }
5032
3249
  case "check":