@decantr/cli 1.10.0 → 2.1.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.
@@ -958,7 +958,9 @@ ${themeBody}
958
958
  lines.push(' .d-shell[data-layout="copilot-overlay"] .d-shell-copilot {');
959
959
  lines.push(" display: none;");
960
960
  lines.push(" }");
961
- lines.push(' .d-shell[data-layout="copilot-overlay"] .d-shell-copilot[data-mobile-open="true"] {');
961
+ lines.push(
962
+ ' .d-shell[data-layout="copilot-overlay"] .d-shell-copilot[data-mobile-open="true"] {'
963
+ );
962
964
  lines.push(" display: flex;");
963
965
  lines.push(" position: fixed;");
964
966
  lines.push(" inset: 0 0 0 auto;");
@@ -1354,7 +1356,10 @@ ${themeBody}
1354
1356
  ["border-bottom", "1px solid var(--d-border)"],
1355
1357
  ["background", "color-mix(in srgb, var(--d-surface) 82%, transparent)"],
1356
1358
  ["color", "var(--d-text-muted)"],
1357
- ["transition", "background var(--d-motion-fast, 150ms) ease, box-shadow var(--d-motion-fast, 150ms) ease, color var(--d-motion-fast, 150ms) ease"]
1359
+ [
1360
+ "transition",
1361
+ "background var(--d-motion-fast, 150ms) ease, box-shadow var(--d-motion-fast, 150ms) ease, color var(--d-motion-fast, 150ms) ease"
1362
+ ]
1358
1363
  ]);
1359
1364
  emitRule(".d-palette-search:focus-within", [
1360
1365
  ["background", "var(--d-surface)"],
@@ -1471,10 +1476,7 @@ ${themeBody}
1471
1476
  ["animation", "d-pulse-ring 1.5s var(--d-motion-ease-out, cubic-bezier(0,0,0.2,1)) infinite"]
1472
1477
  ]);
1473
1478
  emitRule(".d-shimmer", [
1474
- [
1475
- "background",
1476
- "linear-gradient(90deg, transparent, var(--d-surface-raised) 50%, transparent)"
1477
- ],
1479
+ ["background", "linear-gradient(90deg, transparent, var(--d-surface-raised) 50%, transparent)"],
1478
1480
  ["background-size", "200% 100%"],
1479
1481
  ["animation", "d-shimmer 1.5s linear infinite"]
1480
1482
  ]);
@@ -1521,10 +1523,7 @@ ${themeBody}
1521
1523
  ["background", "radial-gradient(circle, currentColor 10%, transparent 10.01%)"],
1522
1524
  ["opacity", "0"],
1523
1525
  ["transform", "scale(0)"],
1524
- [
1525
- "transition",
1526
- "transform var(--d-motion-slow, 400ms), opacity var(--d-motion-slow, 400ms)"
1527
- ],
1526
+ ["transition", "transform var(--d-motion-slow, 400ms), opacity var(--d-motion-slow, 400ms)"],
1528
1527
  ["pointer-events", "none"]
1529
1528
  ]);
1530
1529
  emitRule(".d-ripple:active::after", [
@@ -1891,9 +1890,7 @@ ${themeBody}
1891
1890
  lines.push("}");
1892
1891
  lines.push("");
1893
1892
  lines.push("@keyframes d-pulse-ring {");
1894
- lines.push(
1895
- " 0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--d-primary) 40%, transparent); }"
1896
- );
1893
+ lines.push(" 0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--d-primary) 40%, transparent); }");
1897
1894
  lines.push(" 100% { box-shadow: 0 0 0 12px transparent; }");
1898
1895
  lines.push("}");
1899
1896
  lines.push("");
@@ -1909,9 +1906,7 @@ ${themeBody}
1909
1906
  lines.push("");
1910
1907
  lines.push("/* Respect user motion preferences \u2014 disable all declarative motion */");
1911
1908
  lines.push("@media (prefers-reduced-motion: reduce) {");
1912
- lines.push(
1913
- " .d-enter-fade, .d-enter-slide-up, .d-enter-scale, .d-stagger-children > *,"
1914
- );
1909
+ lines.push(" .d-enter-fade, .d-enter-slide-up, .d-enter-scale, .d-stagger-children > *,");
1915
1910
  lines.push(" .d-pulse, .d-pulse-ring, .d-shimmer, .d-float {");
1916
1911
  lines.push(" animation: none !important;");
1917
1912
  lines.push(" }");
@@ -2363,7 +2358,10 @@ function generateTopologySection(data, personality) {
2363
2358
  return lines.join("\n");
2364
2359
  }
2365
2360
  function readCliVersion() {
2366
- for (const candidate of [join2(__dirname, "..", "package.json"), join2(__dirname, "..", "..", "package.json")]) {
2361
+ for (const candidate of [
2362
+ join2(__dirname, "..", "package.json"),
2363
+ join2(__dirname, "..", "..", "package.json")
2364
+ ]) {
2367
2365
  try {
2368
2366
  const pkg = JSON.parse(readFileSync2(candidate, "utf-8"));
2369
2367
  if (pkg.version) return pkg.version;
@@ -2456,7 +2454,9 @@ function generateTokensCSS(themeData, mode, spatialTokens, options) {
2456
2454
  return entry?.[tokenMode] || entry?.[tokenModeKey] || entry?.dark || entry?.light;
2457
2455
  };
2458
2456
  const extendedPaletteTokens = Object.fromEntries(
2459
- Object.keys(palette).filter((key) => !CORE_PALETTE_KEYS.has(key)).map((key) => [`--d-${key.replace(/[^a-zA-Z0-9-]/g, "-")}`, pickPalette(key)]).filter((entry) => typeof entry[1] === "string" && entry[1].length > 0)
2457
+ Object.keys(palette).filter((key) => !CORE_PALETTE_KEYS.has(key)).map((key) => [`--d-${key.replace(/[^a-zA-Z0-9-]/g, "-")}`, pickPalette(key)]).filter(
2458
+ (entry) => typeof entry[1] === "string" && entry[1].length > 0
2459
+ )
2460
2460
  );
2461
2461
  return {
2462
2462
  // Seed colors
@@ -2503,7 +2503,7 @@ function generateTokensCSS(themeData, mode, spatialTokens, options) {
2503
2503
  "--d-shadow": tokenMode === "light" ? "0 1px 3px rgba(0,0,0,0.1)" : "0 1px 3px rgba(0,0,0,0.25)",
2504
2504
  "--d-shadow-md": tokenMode === "light" ? "0 4px 6px rgba(0,0,0,0.1)" : "0 4px 6px rgba(0,0,0,0.3)",
2505
2505
  "--d-shadow-lg": tokenMode === "light" ? "0 10px 15px rgba(0,0,0,0.1)" : "0 10px 15px rgba(0,0,0,0.4)",
2506
- // Elevation scale (v2.1 Tier B3). Formal cross-theme depth system.
2506
+ // Formal cross-theme depth system.
2507
2507
  // .d-elevate[data-level="N"] reads these. Dark themes need stronger
2508
2508
  // alpha to register on dark backgrounds.
2509
2509
  "--d-elevation-0": "none",
@@ -2519,7 +2519,7 @@ function generateTokensCSS(themeData, mode, spatialTokens, options) {
2519
2519
  "--d-info": "#3b82f6",
2520
2520
  "--d-danger": "var(--d-error)",
2521
2521
  "--d-destructive": "var(--d-error)",
2522
- // Motion scale (v2.1 Tier B1). Canonical durations + easings.
2522
+ // Canonical motion durations + easings.
2523
2523
  // d-enter-fade, d-pulse, d-glow-hover, etc. all read these.
2524
2524
  // Themes can override via theme.motion.* and this picks them up
2525
2525
  // below. Defaults here ensure treatments work even without theme.
@@ -2537,7 +2537,7 @@ function generateTokensCSS(themeData, mode, spatialTokens, options) {
2537
2537
  "--d-duration-entrance": "var(--d-motion-base)",
2538
2538
  "--d-easing": "var(--d-motion-ease-out)",
2539
2539
  "--d-accent-glow": "color-mix(in srgb, var(--d-accent) 24%, transparent)",
2540
- // Typography scale (v2.1 Tier B2). Canonical sizes + weights +
2540
+ // Canonical typography sizes + weights +
2541
2541
  // tracking + leading. d-display, d-headline, d-title, d-prose,
2542
2542
  // d-caption, d-eyebrow read these. Themes override via
2543
2543
  // theme.typography.* below.
@@ -2864,7 +2864,7 @@ function resolvePatternAlias(item, patterns) {
2864
2864
  }
2865
2865
  return item;
2866
2866
  }
2867
- function buildEssenceV3(options, archetypeData, themeHints) {
2867
+ function buildEssenceV4(options, archetypeData, themeHints) {
2868
2868
  const isBrownfieldAttach = options.workflowMode === "brownfield-attach";
2869
2869
  let pages = isBrownfieldAttach ? [{ id: "observed-app", layout: ["existing-surface"] }] : [{ id: "home", layout: ["hero"] }];
2870
2870
  let features = options.features;
@@ -2926,7 +2926,7 @@ function buildEssenceV3(options, archetypeData, themeHints) {
2926
2926
  },
2927
2927
  typography: {
2928
2928
  // Coerce: 5 historical themes stored typography.scale as a numeric
2929
- // ratio (e.g., 1.15) instead of a string enum, which fails the v3
2929
+ // ratio (e.g., 1.15) instead of a string enum, which fails the v4
2930
2930
  // essence schema (`/dna/typography/scale: must be string`). Treat
2931
2931
  // any non-string as missing and fall back to the canonical 'modular'.
2932
2932
  scale: typeof themeHints?.typography?.scale === "string" ? themeHints.typography.scale : "modular",
@@ -2958,10 +2958,32 @@ function buildEssenceV3(options, archetypeData, themeHints) {
2958
2958
  },
2959
2959
  personality: options.personality
2960
2960
  };
2961
+ const sectionId = options.archetype || archetypeData?.id || "custom";
2962
+ const sectionPages = pages.map((page, index) => ({
2963
+ ...page,
2964
+ route: page.route ?? (page.id === "home" || index === 0 ? "/" : `/${page.id}`)
2965
+ }));
2966
+ const section = {
2967
+ id: sectionId,
2968
+ role: archetypeData?.role || "primary",
2969
+ shell: defaultShell,
2970
+ features,
2971
+ description: archetypeData?.description || `${sectionId} primary section`,
2972
+ pages: sectionPages,
2973
+ ...archetypeData?.navigation_items?.length ? { navigation_items: archetypeData.navigation_items } : {},
2974
+ ...archetypeData?.directives?.length ? { directives: archetypeData.directives } : {}
2975
+ };
2976
+ const routes = {};
2977
+ for (const page of sectionPages) {
2978
+ if (page.route) {
2979
+ routes[page.route] = { section: section.id, page: page.id };
2980
+ }
2981
+ }
2961
2982
  const blueprint = {
2962
2983
  shell: defaultShell,
2963
- pages,
2964
- features
2984
+ sections: [section],
2985
+ features,
2986
+ routes
2965
2987
  };
2966
2988
  const meta = {
2967
2989
  archetype: options.archetype || "custom",
@@ -2970,7 +2992,7 @@ function buildEssenceV3(options, archetypeData, themeHints) {
2970
2992
  guard: guardModeMap[options.guard] || guardModeMap.guided
2971
2993
  };
2972
2994
  return {
2973
- version: "3.0.0",
2995
+ version: "4.0.0",
2974
2996
  dna,
2975
2997
  blueprint,
2976
2998
  meta
@@ -3702,7 +3724,7 @@ function getCssApproachContent(adoptionMode) {
3702
3724
  if (adoptionMode === "style-bridge") return STYLE_BRIDGE_CSS_APPROACH;
3703
3725
  return CSS_APPROACH_CONTENT;
3704
3726
  }
3705
- function generateDecantrMdV31(params) {
3727
+ function generateDecantrMdV4(params) {
3706
3728
  const template = loadTemplate("DECANTR.md.template");
3707
3729
  const body = renderTemplate(template, {
3708
3730
  GUARD_MODE: params.guardMode,
@@ -3740,9 +3762,7 @@ Start implementation from the shell layouts and shared route structure before fi
3740
3762
  briefLines.push(`- **Blueprint:** ${params.blueprintId || "custom"}`);
3741
3763
  const themeDesc = `${params.themeName || "default"} (${params.themeMode || "dark"} mode${params.themeShape ? `, ${params.themeShape} shape` : ""})`;
3742
3764
  briefLines.push(`- **Theme:** ${themeDesc}`);
3743
- briefLines.push(
3744
- `- **Workflow:** ${params.workflowMode || "greenfield-scaffold"}`
3745
- );
3765
+ briefLines.push(`- **Workflow:** ${params.workflowMode || "greenfield-scaffold"}`);
3746
3766
  briefLines.push(`- **Adoption mode:** ${params.adoptionMode || "decantr-css"}`);
3747
3767
  if (params.personality && params.personality.length > 0) {
3748
3768
  briefLines.push(`- **Personality:** ${params.personality.join(". ")}`);
@@ -3890,10 +3910,10 @@ function buildFlagsString(options) {
3890
3910
  }
3891
3911
  return flags.join(" ");
3892
3912
  }
3893
- function generateTaskContextV3(templateName, essence) {
3913
+ function generateTaskContextV4(templateName, essence) {
3894
3914
  const template = loadTemplate(templateName);
3895
- const sections = essence.blueprint.sections && essence.blueprint.sections.length > 0 ? essence.blueprint.sections : [];
3896
- const pages = sections.length > 0 ? sections.flatMap((s) => s.pages) : essence.blueprint.pages || [];
3915
+ const sections = essence.blueprint.sections;
3916
+ const pages = sections.flatMap((s) => s.pages);
3897
3917
  const defaultShell = sections[0]?.shell || essence.blueprint.shell || "sidebar-main";
3898
3918
  const layout = pages[0]?.layout?.map(serializeLayoutItem).join(", ") || "none";
3899
3919
  const pageShellMap = /* @__PURE__ */ new Map();
@@ -3940,7 +3960,7 @@ ${entries.map((entry) => `- ${entry}`).join("\n")}`;
3940
3960
  }
3941
3961
  function generateScaffoldTaskContext(essence, scaffoldPack, manifest) {
3942
3962
  if (!scaffoldPack) {
3943
- return generateTaskContextV3("task-scaffold.md.template", essence);
3963
+ return generateTaskContextV4("task-scaffold.md.template", essence);
3944
3964
  }
3945
3965
  const themeShape = scaffoldPack.data.theme.shape || "default";
3946
3966
  const features = scaffoldPack.data.features.length > 0 ? scaffoldPack.data.features.join(", ") : "none";
@@ -3999,7 +4019,7 @@ Post-scaffold enforcement mode: **${essence.meta.guard.mode.toUpperCase()}**.
3999
4019
  }
4000
4020
  function generateAddPageTaskContext(essence, scaffoldPack, manifest) {
4001
4021
  if (!scaffoldPack) {
4002
- return generateTaskContextV3("task-add-page.md.template", essence);
4022
+ return generateTaskContextV4("task-add-page.md.template", essence);
4003
4023
  }
4004
4024
  const routePlan = scaffoldPack.data.routes.length > 0 ? scaffoldPack.data.routes.map((route) => {
4005
4025
  const patternSummary = route.patternIds.length > 0 ? route.patternIds.join(", ") : "none";
@@ -4056,7 +4076,7 @@ ${renderPackReferenceList("Page Packs", pageRefs, "No page packs were generated
4056
4076
  }
4057
4077
  function generateModifyTaskContext(essence, scaffoldPack, manifest) {
4058
4078
  if (!scaffoldPack) {
4059
- return generateTaskContextV3("task-modify.md.template", essence);
4079
+ return generateTaskContextV4("task-modify.md.template", essence);
4060
4080
  }
4061
4081
  const routePlan = scaffoldPack.data.routes.length > 0 ? scaffoldPack.data.routes.map((route) => {
4062
4082
  const patternSummary = route.patternIds.length > 0 ? route.patternIds.join(", ") : "none";
@@ -4100,53 +4120,6 @@ ${successChecks}
4100
4120
 
4101
4121
  *Task context generated from Decantr execution packs*`;
4102
4122
  }
4103
- function generateEssenceSummaryV3(essence) {
4104
- const template = loadTemplate("essence-summary.md.template");
4105
- const blueprint = essence.blueprint;
4106
- const sections = blueprint.sections || [];
4107
- const flatPages = blueprint.pages || [];
4108
- let pagesTable;
4109
- if (sections.length > 0) {
4110
- const rows = sections.flatMap(
4111
- (s) => s.pages.map(
4112
- (p) => `| ${p.id} | ${s.shell} | ${p.layout.map(serializeLayoutItem).join(", ") || "none"} |`
4113
- )
4114
- );
4115
- pagesTable = `| Page | Shell | Layout |
4116
- |------|-------|--------|
4117
- ${rows.join("\n")}`;
4118
- } else {
4119
- const shell = blueprint.shell ?? "sidebar-main";
4120
- const rows = flatPages.map(
4121
- (p) => `| ${p.id} | ${shell} | ${p.layout.map(serializeLayoutItem).join(", ") || "none"} |`
4122
- );
4123
- pagesTable = `| Page | Shell | Layout |
4124
- |------|-------|--------|
4125
- ${rows.join("\n")}`;
4126
- }
4127
- const features = blueprint.features || [];
4128
- const featuresList = features.length > 0 ? features.map((f) => `- ${f}`).join("\n") : "- No features specified";
4129
- const vars = {
4130
- ARCHETYPE: essence.meta.archetype || "custom",
4131
- BLUEPRINT: "",
4132
- PERSONALITY: (essence.dna.personality || []).join(", "),
4133
- TARGET: essence.meta.target ?? "",
4134
- THEME_ID: essence.dna.theme.id ?? "",
4135
- THEME_MODE: essence.dna.theme.mode,
4136
- SHAPE: essence.dna.theme.shape ?? "",
4137
- PAGES_TABLE: pagesTable,
4138
- FEATURES_LIST: featuresList,
4139
- GUARD_MODE: essence.meta.guard.mode,
4140
- ENFORCE_STYLE: essence.meta.guard.dna_enforcement || "error",
4141
- ENFORCE_RECIPE: essence.meta.guard.blueprint_enforcement || "warn",
4142
- DNA_ENFORCEMENT: essence.meta.guard.dna_enforcement || "error",
4143
- BLUEPRINT_ENFORCEMENT: essence.meta.guard.blueprint_enforcement || "warn",
4144
- DENSITY: essence.dna.spacing?.density || "comfortable",
4145
- CONTENT_GAP: essence.dna.spacing?.content_gap || "_gap4",
4146
- LAST_UPDATED: (/* @__PURE__ */ new Date()).toISOString()
4147
- };
4148
- return renderTemplate(template, vars);
4149
- }
4150
4123
  function updateGitignore(projectRoot) {
4151
4124
  const gitignorePath = join2(projectRoot, ".gitignore");
4152
4125
  const cacheEntry = ".decantr/cache/";
@@ -4168,14 +4141,14 @@ ${cacheEntry}
4168
4141
  }
4169
4142
  }
4170
4143
  async function scaffoldProject(projectRoot, options, detected, registry, archetypeData, registrySource = "cache", themeData, topologyMarkdown, composedSections, routeMap, patternSpecs, blueprintData) {
4171
- const essenceV3 = buildEssenceV3(options, archetypeData, themeData);
4144
+ const essenceV4 = buildEssenceV4(options, archetypeData, themeData);
4172
4145
  const decantrDir = join2(projectRoot, ".decantr");
4173
4146
  const contextDir = join2(decantrDir, "context");
4174
4147
  const cacheDir = join2(decantrDir, "cache");
4175
4148
  mkdirSync2(contextDir, { recursive: true });
4176
4149
  mkdirSync2(cacheDir, { recursive: true });
4177
4150
  const essencePath = join2(projectRoot, "decantr.essence.json");
4178
- writeFileSync2(essencePath, JSON.stringify(essenceV3, null, 2) + "\n");
4151
+ writeFileSync2(essencePath, JSON.stringify(essenceV4, null, 2) + "\n");
4179
4152
  const projectJsonPath = join2(decantrDir, "project.json");
4180
4153
  const projectJsonStr = generateProjectJson(detected, options, registrySource);
4181
4154
  const projectJsonObj = JSON.parse(projectJsonStr);
@@ -4185,27 +4158,27 @@ async function scaffoldProject(projectRoot, options, detected, registry, archety
4185
4158
  writeFileSync2(projectJsonPath, JSON.stringify(projectJsonObj, null, 2));
4186
4159
  const contextFiles = [];
4187
4160
  if (composedSections) {
4188
- essenceV3.version = "3.1.0";
4189
- essenceV3.blueprint = {
4161
+ essenceV4.version = "4.0.0";
4162
+ essenceV4.blueprint = {
4190
4163
  sections: composedSections.sections,
4191
4164
  features: composedSections.features,
4192
4165
  routes: routeMap || {}
4193
4166
  };
4194
4167
  if (blueprintData?.personality?.length) {
4195
- essenceV3.dna.personality = typeof blueprintData.personality === "string" ? [blueprintData.personality] : blueprintData.personality;
4168
+ essenceV4.dna.personality = typeof blueprintData.personality === "string" ? [blueprintData.personality] : blueprintData.personality;
4196
4169
  }
4197
4170
  if (blueprintData?.design_constraints) {
4198
- essenceV3.dna.constraints = blueprintData.design_constraints;
4171
+ essenceV4.dna.constraints = blueprintData.design_constraints;
4199
4172
  }
4200
4173
  if (blueprintData?.seo_hints) {
4201
- essenceV3.meta.seo = blueprintData.seo_hints;
4174
+ essenceV4.meta.seo = blueprintData.seo_hints;
4202
4175
  }
4203
4176
  if (blueprintData?.navigation) {
4204
- essenceV3.meta.navigation = blueprintData.navigation;
4177
+ essenceV4.meta.navigation = blueprintData.navigation;
4205
4178
  }
4206
- writeFileSync2(essencePath, JSON.stringify(essenceV3, null, 2) + "\n");
4179
+ writeFileSync2(essencePath, JSON.stringify(essenceV4, null, 2) + "\n");
4207
4180
  }
4208
- const refreshResult = await refreshDerivedFiles(projectRoot, essenceV3, registry, themeData, {
4181
+ const refreshResult = await refreshDerivedFiles(projectRoot, essenceV4, registry, themeData, {
4209
4182
  isInitialScaffold: true,
4210
4183
  patternSpecs,
4211
4184
  workflowMode: options.workflowMode,
@@ -4231,7 +4204,7 @@ function scaffoldMinimal(projectRoot, options = {}) {
4231
4204
  mkdirSync2(join2(customDir, type), { recursive: true });
4232
4205
  }
4233
4206
  const essence = {
4234
- version: "3.0.0",
4207
+ version: "4.0.0",
4235
4208
  dna: {
4236
4209
  theme: {
4237
4210
  id: "default",
@@ -4276,8 +4249,18 @@ function scaffoldMinimal(projectRoot, options = {}) {
4276
4249
  },
4277
4250
  blueprint: {
4278
4251
  shell: "sidebar-main",
4279
- pages: [{ id: "home", layout: ["hero"] }],
4280
- features: []
4252
+ sections: [
4253
+ {
4254
+ id: "custom",
4255
+ role: "primary",
4256
+ shell: "sidebar-main",
4257
+ features: [],
4258
+ description: "custom primary section",
4259
+ pages: [{ id: "home", route: "/", layout: ["hero"] }]
4260
+ }
4261
+ ],
4262
+ features: [],
4263
+ routes: { "/": { section: "custom", page: "home" } }
4281
4264
  },
4282
4265
  meta: {
4283
4266
  archetype: "custom",
@@ -4338,7 +4321,7 @@ function scaffoldMinimal(projectRoot, options = {}) {
4338
4321
 
4339
4322
  ## Two-Layer Model
4340
4323
 
4341
- This project uses the v3 Essence format with two layers:
4324
+ This project uses the Essence v4 format with two layers:
4342
4325
 
4343
4326
  ### DNA (Immutable Design Axioms)
4344
4327
  DNA defines the foundational design rules that must never be violated. DNA violations are **errors**.
@@ -4382,7 +4365,7 @@ When available, use these tools:
4382
4365
  - \`decantr status\` \u2014 Project health and DNA/Blueprint overview
4383
4366
  - \`decantr sync\` \u2014 Sync registry content
4384
4367
  - \`decantr audit\` \u2014 Audit project for issues
4385
- - \`decantr migrate\` \u2014 Migrate v2 essence to v3
4368
+ - \`decantr migrate --to v4\` \u2014 Migrate older essence files to v4
4386
4369
  - \`decantr check\` \u2014 Detect drift issues
4387
4370
  - \`decantr sync-drift\` \u2014 Review and resolve drift entries
4388
4371
  - \`decantr validate\` \u2014 Validate essence file
@@ -4442,10 +4425,7 @@ function writeExecutionPackBundleArtifacts(contextDir, bundle) {
4442
4425
  for (const [index, pagePack] of bundle.pages.entries()) {
4443
4426
  const manifestPage = bundle.manifest.pages[index];
4444
4427
  const pageBaseName = manifestPage?.markdown?.endsWith(".md") ? manifestPage.markdown.slice(0, -".md".length) : `page-${pagePack.data.pageId}-pack`;
4445
- const pagePackPath = writeExecutionPackArtifacts(
4446
- join2(contextDir, pageBaseName),
4447
- pagePack
4448
- );
4428
+ const pagePackPath = writeExecutionPackArtifacts(join2(contextDir, pageBaseName), pagePack);
4449
4429
  outputPaths.push(pagePackPath);
4450
4430
  }
4451
4431
  for (const mutationPack of bundle.mutations) {
@@ -4688,7 +4668,10 @@ async function refreshDerivedFiles(projectRoot, essence, registry, prefetchedThe
4688
4668
  }
4689
4669
  const features = essence.blueprint?.features ?? [];
4690
4670
  const hasThemeToggle = features.includes("theme-toggle") || features.includes("theme_toggle");
4691
- writeFileSync2(tokensPath, generateTokensCSS(themeData, mode, spatialTokens, { hasThemeToggle }));
4671
+ writeFileSync2(
4672
+ tokensPath,
4673
+ generateTokensCSS(themeData, mode, spatialTokens, { hasThemeToggle })
4674
+ );
4692
4675
  }
4693
4676
  const treatmentsPath = join2(stylesDir, "treatments.css");
4694
4677
  if (effectiveAdoptionMode === "decantr-css") {
@@ -4744,7 +4727,7 @@ async function refreshDerivedFiles(projectRoot, essence, registry, prefetchedThe
4744
4727
  const decantrMdPath = join2(projectRoot, "DECANTR.md");
4745
4728
  writeFileSync2(
4746
4729
  decantrMdPath,
4747
- generateDecantrMdV31({
4730
+ generateDecantrMdV4({
4748
4731
  guardMode,
4749
4732
  cssApproach: getCssApproachContent(effectiveAdoptionMode),
4750
4733
  workflowMode: effectiveWorkflowMode,
@@ -4761,13 +4744,7 @@ async function refreshDerivedFiles(projectRoot, essence, registry, prefetchedThe
4761
4744
  decoratorDefinitions: themeData?.decorator_definitions
4762
4745
  })
4763
4746
  );
4764
- const hasSections = essence.blueprint.sections && essence.blueprint.sections.length > 0;
4765
4747
  const contextFiles = [];
4766
- if (!hasSections) {
4767
- const summaryPath = join2(contextDir, "essence-summary.md");
4768
- writeFileSync2(summaryPath, generateEssenceSummaryV3(essence));
4769
- contextFiles.push(summaryPath);
4770
- }
4771
4748
  const packContexts = await generatePackContexts(projectRoot, contextDir, essence);
4772
4749
  const scaffoldTaskPath = join2(contextDir, "task-scaffold.md");
4773
4750
  writeFileSync2(
@@ -4790,217 +4767,149 @@ async function refreshDerivedFiles(projectRoot, essence, registry, prefetchedThe
4790
4767
  contextFiles.push(modifyPath);
4791
4768
  }
4792
4769
  const blueprint = essence.blueprint;
4793
- const sections = blueprint.sections && blueprint.sections.length > 0 ? blueprint.sections : [];
4794
- if (sections.length > 0) {
4795
- const primarySectionShell = sections.find((s) => s.role === "primary")?.shell || "sidebar-main";
4796
- for (const section of sections) {
4797
- if (section.shell === "inherit") {
4798
- section.shell = primarySectionShell;
4799
- }
4770
+ const sections = blueprint.sections;
4771
+ if (sections.length === 0) {
4772
+ throw new Error(
4773
+ "Essence v4 requires blueprint.sections. Run `decantr migrate --to v4` for older essence files."
4774
+ );
4775
+ }
4776
+ const primarySectionShell = sections.find((s) => s.role === "primary")?.shell || "sidebar-main";
4777
+ for (const section of sections) {
4778
+ if (section.shell === "inherit") {
4779
+ section.shell = primarySectionShell;
4800
4780
  }
4801
- const prefetchedSpecs = options?.patternSpecs;
4802
- const patternSpecs = {};
4803
- const seenPatterns = /* @__PURE__ */ new Set();
4804
- for (const section of sections) {
4805
- for (const page of section.pages) {
4806
- for (const item of page.layout) {
4807
- const names = extractPatternNames(item);
4808
- for (const name of names) {
4809
- if (!seenPatterns.has(name)) {
4810
- seenPatterns.add(name);
4811
- const spec = await resolvePatternSpec(name, registry, prefetchedSpecs?.[name], true);
4812
- if (spec) patternSpecs[name] = spec;
4813
- }
4781
+ }
4782
+ const prefetchedSpecs = options?.patternSpecs;
4783
+ const patternSpecs = {};
4784
+ const seenPatterns = /* @__PURE__ */ new Set();
4785
+ for (const section of sections) {
4786
+ for (const page of section.pages) {
4787
+ for (const item of page.layout) {
4788
+ const names = extractPatternNames(item);
4789
+ for (const name of names) {
4790
+ if (!seenPatterns.has(name)) {
4791
+ seenPatterns.add(name);
4792
+ const spec = await resolvePatternSpec(name, registry, prefetchedSpecs?.[name], true);
4793
+ if (spec) patternSpecs[name] = spec;
4814
4794
  }
4815
4795
  }
4816
4796
  }
4817
4797
  }
4818
- const zoneInputs = sections.map((s) => ({
4819
- archetypeId: s.id,
4820
- role: s.role,
4821
- shell: s.shell,
4822
- features: s.features,
4823
- description: s.description
4824
- }));
4825
- const zones = deriveZones(zoneInputs);
4826
- const transitions = deriveTransitions(zones);
4827
- const hasPublic = zones.some((z) => z.role === "public");
4828
- const hasPrimary = zones.some((z) => z.role === "primary");
4829
- const topologyData = {
4830
- intent: sections.map((s) => s.id).join(" + "),
4831
- zones,
4832
- transitions,
4833
- entryPoints: {
4834
- anonymous: hasPublic ? "public zone" : "gateway",
4835
- authenticated: hasPrimary ? "primary zone" : "first section"
4836
- }
4837
- };
4838
- const topologyMarkdown = generateTopologySection(topologyData, personality);
4839
- const themeTokensCss = existsSync2(tokensPath) ? readFileSync2(tokensPath, "utf-8") : "";
4840
- const decoratorList = [];
4841
- if (themeData?.decorators) {
4842
- for (const [name, desc] of Object.entries(themeData.decorators)) {
4843
- decoratorList.push({ name, description: desc });
4844
- }
4798
+ }
4799
+ const zoneInputs = sections.map((s) => ({
4800
+ archetypeId: s.id,
4801
+ role: s.role,
4802
+ shell: s.shell,
4803
+ features: s.features,
4804
+ description: s.description
4805
+ }));
4806
+ const zones = deriveZones(zoneInputs);
4807
+ const transitions = deriveTransitions(zones);
4808
+ const hasPublic = zones.some((z) => z.role === "public");
4809
+ const hasPrimary = zones.some((z) => z.role === "primary");
4810
+ const topologyData = {
4811
+ intent: sections.map((s) => s.id).join(" + "),
4812
+ zones,
4813
+ transitions,
4814
+ entryPoints: {
4815
+ anonymous: hasPublic ? "public zone" : "gateway",
4816
+ authenticated: hasPrimary ? "primary zone" : "first section"
4845
4817
  }
4846
- const shellInfoCache = {};
4847
- const seenShells = /* @__PURE__ */ new Set();
4848
- for (const section of sections) {
4849
- const shellId = section.shell;
4850
- if (!seenShells.has(shellId)) {
4851
- seenShells.add(shellId);
4852
- try {
4853
- const shellResult = await registry.fetchShell(shellId);
4854
- if (shellResult?.data) {
4855
- shellInfoCache[shellId] = mapRegistryShellToShellInfo(shellResult.data);
4856
- }
4857
- } catch {
4858
- }
4859
- }
4818
+ };
4819
+ const topologyMarkdown = generateTopologySection(topologyData, personality);
4820
+ const themeTokensCss = existsSync2(tokensPath) ? readFileSync2(tokensPath, "utf-8") : "";
4821
+ const decoratorList = [];
4822
+ if (themeData?.decorators) {
4823
+ for (const [name, desc] of Object.entries(themeData.decorators)) {
4824
+ decoratorList.push({ name, description: desc });
4860
4825
  }
4861
- for (const section of sections) {
4862
- const zoneLabel = section.role === "primary" || section.role === "auxiliary" ? "App" : section.role === "gateway" ? "Gateway" : "Public";
4863
- let zoneContext = `**Zone:** ${zoneLabel} (${section.role}) \u2014 ${section.shell} shell`;
4864
- if (section.role === "gateway") {
4865
- zoneContext += "\nAuth success \u2192 enters App zone. Sign out returns here.";
4866
- } else if (section.role === "primary") {
4867
- zoneContext += "\nAuthenticated users land here. Sign out \u2192 Gateway (/login).";
4868
- } else if (section.role === "public") {
4869
- zoneContext += "\nAnonymous visitors. CTAs lead to Gateway (/login, /register).";
4870
- } else if (section.role === "auxiliary") {
4871
- zoneContext += "\nSupporting section within App zone. Shares navigation with primary.";
4872
- }
4873
- const sectionPatterns = {};
4874
- for (const page of section.pages) {
4875
- for (const item of page.layout) {
4876
- const names = extractPatternNames(item);
4877
- for (const name of names) {
4878
- if (patternSpecs[name]) {
4879
- sectionPatterns[name] = patternSpecs[name];
4880
- }
4881
- }
4826
+ }
4827
+ const shellInfoCache = {};
4828
+ const seenShells = /* @__PURE__ */ new Set();
4829
+ for (const section of sections) {
4830
+ const shellId = section.shell;
4831
+ if (!seenShells.has(shellId)) {
4832
+ seenShells.add(shellId);
4833
+ try {
4834
+ const shellResult = await registry.fetchShell(shellId);
4835
+ if (shellResult?.data) {
4836
+ shellInfoCache[shellId] = mapRegistryShellToShellInfo(shellResult.data);
4882
4837
  }
4838
+ } catch {
4883
4839
  }
4884
- const sectionSpatialHints = themeData?.spatial ? {
4885
- section_padding: themeData.spatial.section_padding ?? void 0,
4886
- density_bias: typeof themeData.spatial.density_bias === "number" ? themeData.spatial.density_bias : void 0,
4887
- content_gap_shift: themeData.spatial.content_gap_shift,
4888
- label_content_gap: themeData.spatial.label_content_gap ?? void 0
4889
- } : void 0;
4890
- const contextContent = generateSectionContext({
4891
- section,
4892
- themeTokens: themeTokensCss,
4893
- decorators: decoratorList,
4894
- guardConfig,
4895
- personality,
4896
- themeName,
4897
- zoneContext,
4898
- patternSpecs: sectionPatterns,
4899
- themeHints: themeData ? {
4900
- preferred: themeData.pattern_preferences?.prefer,
4901
- compositions: themeData.compositions ? Object.entries(themeData.compositions).map(([k, v]) => `**${k}:** ${v.description || v}`).join("\n") : void 0,
4902
- spatialHints: themeData.spatial ? `Density bias: ${themeData.spatial.density_bias || "none"}. Section padding: ${themeData.spatial.section_padding || "default"}. Card wrapping: ${themeData.spatial.card_wrapping || "default"}.` : void 0
4903
- } : void 0,
4904
- constraints: essence.dna.constraints,
4905
- shellInfo: shellInfoCache[section.shell],
4906
- themeData,
4907
- themeMode: mode,
4908
- voiceTone: storedVoice?.tone ? storedVoice.tone.split(".")[0] + "." : void 0,
4909
- spatialHints: sectionSpatialHints
4910
- });
4911
- const sectionContextPath = join2(contextDir, `section-${section.id}.md`);
4912
- writeFileSync2(sectionContextPath, contextContent);
4913
- contextFiles.push(sectionContextPath);
4914
4840
  }
4915
- const routes = blueprint.routes || {};
4916
- const scaffoldContent = generateScaffoldContext({
4917
- appName: essence.meta.archetype || "Application",
4918
- blueprintId: storedBlueprintId || getLegacyBlueprintId(essence.meta) || "",
4919
- themeName,
4920
- personality,
4921
- topologyMarkdown,
4922
- sections,
4923
- routes,
4924
- constraints: essence.dna.constraints,
4925
- seo: essence.meta.seo,
4926
- navigation: essence.meta.navigation,
4927
- voice: storedVoice
4928
- });
4929
- const scaffoldMdPath = join2(contextDir, "scaffold.md");
4930
- writeFileSync2(scaffoldMdPath, scaffoldContent);
4931
- contextFiles.push(scaffoldMdPath);
4932
- } else {
4933
- const pages = blueprint.pages || [{ id: "home", layout: ["hero"] }];
4934
- const shell = blueprint.shell ?? "sidebar-main";
4935
- const syntheticSection = {
4936
- id: essence.meta.archetype || "default",
4937
- role: "primary",
4938
- shell,
4939
- features: blueprint.features || [],
4940
- description: `${essence.meta.archetype || "Application"} section`,
4941
- pages
4942
- };
4943
- const prefetchedSpecs = options?.patternSpecs;
4944
- const patternSpecs = {};
4945
- const seenPatterns = /* @__PURE__ */ new Set();
4946
- for (const page of pages) {
4841
+ }
4842
+ for (const section of sections) {
4843
+ const zoneLabel = section.role === "primary" || section.role === "auxiliary" ? "App" : section.role === "gateway" ? "Gateway" : "Public";
4844
+ let zoneContext = `**Zone:** ${zoneLabel} (${section.role}) \u2014 ${section.shell} shell`;
4845
+ if (section.role === "gateway") {
4846
+ zoneContext += "\nAuth success \u2192 enters App zone. Sign out returns here.";
4847
+ } else if (section.role === "primary") {
4848
+ zoneContext += "\nAuthenticated users land here. Sign out \u2192 Gateway (/login).";
4849
+ } else if (section.role === "public") {
4850
+ zoneContext += "\nAnonymous visitors. CTAs lead to Gateway (/login, /register).";
4851
+ } else if (section.role === "auxiliary") {
4852
+ zoneContext += "\nSupporting section within App zone. Shares navigation with primary.";
4853
+ }
4854
+ const sectionPatterns = {};
4855
+ for (const page of section.pages) {
4947
4856
  for (const item of page.layout) {
4948
4857
  const names = extractPatternNames(item);
4949
4858
  for (const name of names) {
4950
- if (!seenPatterns.has(name)) {
4951
- seenPatterns.add(name);
4952
- const spec = await resolvePatternSpec(name, registry, prefetchedSpecs?.[name], false);
4953
- if (spec) patternSpecs[name] = spec;
4859
+ if (patternSpecs[name]) {
4860
+ sectionPatterns[name] = patternSpecs[name];
4954
4861
  }
4955
4862
  }
4956
4863
  }
4957
4864
  }
4958
- const themeTokensCss = existsSync2(tokensPath) ? readFileSync2(tokensPath, "utf-8") : "";
4959
- const decoratorList = [];
4960
- if (themeData?.decorators) {
4961
- for (const [name, desc] of Object.entries(themeData.decorators)) {
4962
- decoratorList.push({ name, description: desc });
4963
- }
4964
- }
4965
- let v30ShellInfo;
4966
- try {
4967
- const shellResult = await registry.fetchShell(shell);
4968
- if (shellResult?.data) {
4969
- v30ShellInfo = mapRegistryShellToShellInfo(shellResult.data);
4970
- }
4971
- } catch {
4972
- }
4973
- const v30SpatialHints = themeData?.spatial ? {
4865
+ const sectionSpatialHints = themeData?.spatial ? {
4974
4866
  section_padding: themeData.spatial.section_padding ?? void 0,
4975
4867
  density_bias: typeof themeData.spatial.density_bias === "number" ? themeData.spatial.density_bias : void 0,
4976
4868
  content_gap_shift: themeData.spatial.content_gap_shift,
4977
4869
  label_content_gap: themeData.spatial.label_content_gap ?? void 0
4978
4870
  } : void 0;
4979
4871
  const contextContent = generateSectionContext({
4980
- section: syntheticSection,
4872
+ section,
4981
4873
  themeTokens: themeTokensCss,
4982
4874
  decorators: decoratorList,
4983
4875
  guardConfig,
4984
4876
  personality,
4985
4877
  themeName,
4986
- zoneContext: `This is the primary section (${shell} shell).`,
4987
- patternSpecs,
4878
+ zoneContext,
4879
+ patternSpecs: sectionPatterns,
4988
4880
  themeHints: themeData ? {
4989
4881
  preferred: themeData.pattern_preferences?.prefer,
4990
4882
  compositions: themeData.compositions ? Object.entries(themeData.compositions).map(([k, v]) => `**${k}:** ${v.description || v}`).join("\n") : void 0,
4991
4883
  spatialHints: themeData.spatial ? `Density bias: ${themeData.spatial.density_bias || "none"}. Section padding: ${themeData.spatial.section_padding || "default"}. Card wrapping: ${themeData.spatial.card_wrapping || "default"}.` : void 0
4992
4884
  } : void 0,
4993
4885
  constraints: essence.dna.constraints,
4994
- shellInfo: v30ShellInfo,
4886
+ shellInfo: shellInfoCache[section.shell],
4995
4887
  themeData,
4996
4888
  themeMode: mode,
4997
4889
  voiceTone: storedVoice?.tone ? storedVoice.tone.split(".")[0] + "." : void 0,
4998
- spatialHints: v30SpatialHints
4890
+ spatialHints: sectionSpatialHints
4999
4891
  });
5000
- const sectionContextPath = join2(contextDir, `section-${syntheticSection.id}.md`);
4892
+ const sectionContextPath = join2(contextDir, `section-${section.id}.md`);
5001
4893
  writeFileSync2(sectionContextPath, contextContent);
5002
4894
  contextFiles.push(sectionContextPath);
5003
4895
  }
4896
+ const routes = blueprint.routes || {};
4897
+ const scaffoldContent = generateScaffoldContext({
4898
+ appName: essence.meta.archetype || "Application",
4899
+ blueprintId: storedBlueprintId || getLegacyBlueprintId(essence.meta) || "",
4900
+ themeName,
4901
+ personality,
4902
+ topologyMarkdown,
4903
+ sections,
4904
+ routes,
4905
+ constraints: essence.dna.constraints,
4906
+ seo: essence.meta.seo,
4907
+ navigation: essence.meta.navigation,
4908
+ voice: storedVoice
4909
+ });
4910
+ const scaffoldMdPath = join2(contextDir, "scaffold.md");
4911
+ writeFileSync2(scaffoldMdPath, scaffoldContent);
4912
+ contextFiles.push(scaffoldMdPath);
5004
4913
  if (packContexts.paths.length > 0) {
5005
4914
  contextFiles.push(...packContexts.paths);
5006
4915
  }
@@ -5265,9 +5174,7 @@ function generateQuickStart(input) {
5265
5174
  if (pLower.includes("neon") || pLower.includes("glow")) personalityUtils.push("neon-glow");
5266
5175
  if (pLower.includes("mono") || pLower.includes("monospace")) personalityUtils.push("mono-data");
5267
5176
  if (personalityUtils.length > 0) {
5268
- lines.push(
5269
- `**Personality utilities:** ${personalityUtils.map((c) => `\`.${c}\``).join(", ")}`
5270
- );
5177
+ lines.push(`**Personality utilities:** ${personalityUtils.map((c) => `\`.${c}\``).join(", ")}`);
5271
5178
  }
5272
5179
  const density = section.dna_overrides?.density || "comfortable";
5273
5180
  lines.push(`**Density:** ${density}`);