@decantr/cli 1.7.6 → 1.7.8

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.
@@ -14,14 +14,14 @@ import {
14
14
  scaffoldProject,
15
15
  syncRegistry,
16
16
  writeExecutionPackBundleArtifacts
17
- } from "./chunk-H4H3IQJK.js";
17
+ } from "./chunk-74G2RQDO.js";
18
18
  import {
19
19
  buildGuardRegistryContext
20
20
  } from "./chunk-KUDAVJOR.js";
21
21
 
22
22
  // src/index.ts
23
- import { mkdirSync as mkdirSync9, readFileSync as readFileSync16, writeFileSync as writeFileSync13, existsSync as existsSync24, readdirSync as readdirSync6 } from "fs";
24
- import { basename, join as join24, dirname as dirname2, isAbsolute, resolve as resolve3 } from "path";
23
+ import { mkdirSync as mkdirSync10, readFileSync as readFileSync18, writeFileSync as writeFileSync13, existsSync as existsSync25, readdirSync as readdirSync6 } from "fs";
24
+ import { basename as basename2, join as join26, dirname as dirname2, isAbsolute, resolve as resolve3 } from "path";
25
25
  import { fileURLToPath as fileURLToPath2 } from "url";
26
26
  import { validateEssence as validateEssence2, evaluateGuard, isV3 as isV36 } from "@decantr/essence-spec";
27
27
  import {
@@ -238,7 +238,7 @@ ${CYAN}Detected project configuration:${RESET}`);
238
238
  console.log(` Existing essence: ${YELLOW}yes${RESET}`);
239
239
  }
240
240
  }
241
- async function runInteractivePrompts(detected, archetypes, blueprints, themes) {
241
+ async function runInteractivePrompts(detected, archetypes, blueprints, themes, workflowSeed) {
242
242
  showDetection(detected);
243
243
  const blueprintOptions = [
244
244
  { value: "none", label: "none", description: "Start from scratch (blank canvas)" },
@@ -255,7 +255,8 @@ async function runInteractivePrompts(detected, archetypes, blueprints, themes) {
255
255
  label: t.id,
256
256
  description: t.description
257
257
  }));
258
- const defaultThemeIdx = themeOptions.findIndex((t) => t.value === "luminarum") || 0;
258
+ const desiredTheme = workflowSeed?.theme || "luminarum";
259
+ const defaultThemeIdx = Math.max(0, themeOptions.findIndex((t) => t.value === desiredTheme));
259
260
  const theme = await select("Choose a theme", themeOptions, Math.max(0, defaultThemeIdx), true);
260
261
  const mode = await select(
261
262
  "Color mode",
@@ -264,7 +265,7 @@ async function runInteractivePrompts(detected, archetypes, blueprints, themes) {
264
265
  { value: "light", label: "light", description: "Light background" },
265
266
  { value: "auto", label: "auto", description: "Follow system preference" }
266
267
  ],
267
- 0
268
+ workflowSeed?.mode === "light" ? 1 : workflowSeed?.mode === "auto" ? 2 : 0
268
269
  );
269
270
  const shape = await select(
270
271
  "Border shape",
@@ -286,7 +287,7 @@ async function runInteractivePrompts(detected, archetypes, blueprints, themes) {
286
287
  { value: "angular", label: "angular", description: "Angular" },
287
288
  { value: "html", label: "html", description: "Plain HTML/CSS/JS" }
288
289
  ];
289
- let defaultFrameworkIdx = frameworkOptions.findIndex((f) => f.value === detected.framework);
290
+ let defaultFrameworkIdx = frameworkOptions.findIndex((f) => f.value === (workflowSeed?.target || detected.framework));
290
291
  if (defaultFrameworkIdx < 0) defaultFrameworkIdx = 0;
291
292
  const target = await select("Target framework", frameworkOptions, defaultFrameworkIdx, true);
292
293
  if (detected.framework !== "unknown" && target !== detected.framework) {
@@ -303,8 +304,7 @@ async function runInteractivePrompts(detected, archetypes, blueprints, themes) {
303
304
  { value: "guided", label: "guided", description: "Style, structure, density enforced" },
304
305
  { value: "strict", label: "strict", description: "All 5 rules enforced exactly" }
305
306
  ],
306
- detected.existingEssence ? 1 : 2
307
- // Default to guided for existing, strict for new
307
+ workflowSeed?.guard === "creative" ? 0 : workflowSeed?.guard === "strict" ? 2 : workflowSeed?.guard === "guided" ? 1 : detected.existingEssence ? 1 : 2
308
308
  );
309
309
  const density = await select(
310
310
  "Spacing density",
@@ -313,7 +313,7 @@ async function runInteractivePrompts(detected, archetypes, blueprints, themes) {
313
313
  { value: "comfortable", label: "comfortable", description: "Balanced spacing" },
314
314
  { value: "spacious", label: "spacious", description: "Generous whitespace" }
315
315
  ],
316
- 1
316
+ workflowSeed?.density === "compact" ? 0 : workflowSeed?.density === "spacious" ? 2 : 1
317
317
  );
318
318
  const shellOptions = [
319
319
  { value: "sidebar-main", label: "sidebar-main", description: "Collapsible sidebar with main content" },
@@ -322,8 +322,11 @@ async function runInteractivePrompts(detected, archetypes, blueprints, themes) {
322
322
  { value: "full-bleed", label: "full-bleed", description: "No persistent nav (landing pages)" },
323
323
  { value: "minimal-header", label: "minimal-header", description: "Slim header with centered content" }
324
324
  ];
325
- let defaultShellIdx = 0;
326
- if (["nextjs", "nuxt", "astro"].includes(target)) {
325
+ let defaultShellIdx = shellOptions.findIndex((s) => s.value === workflowSeed?.shell);
326
+ if (defaultShellIdx < 0) {
327
+ defaultShellIdx = 0;
328
+ }
329
+ if (defaultShellIdx === 0 && ["nextjs", "nuxt", "astro"].includes(target)) {
327
330
  defaultShellIdx = shellOptions.findIndex((s) => s.value === "top-nav-main");
328
331
  }
329
332
  const shell = await select("Default page shell (layout)", shellOptions, Math.max(0, defaultShellIdx), true);
@@ -340,7 +343,8 @@ async function runInteractivePrompts(detected, archetypes, blueprints, themes) {
340
343
  shell,
341
344
  personality: ["professional"],
342
345
  features: [],
343
- existing: detected.existingEssence
346
+ existing: workflowSeed?.existing || detected.existingEssence,
347
+ workflowMode: workflowSeed?.workflowMode
344
348
  };
345
349
  }
346
350
  function parseFlags(args, detected) {
@@ -359,20 +363,21 @@ function parseFlags(args, detected) {
359
363
  if (args.existing === true) options.existing = true;
360
364
  return options;
361
365
  }
362
- function mergeWithDefaults(flags, detected) {
366
+ function mergeWithDefaults(flags, detected, workflowSeed) {
363
367
  return {
364
368
  blueprint: flags.blueprint,
365
369
  archetype: flags.archetype,
366
- theme: flags.theme || "luminarum",
367
- mode: flags.mode || "dark",
370
+ theme: flags.theme || workflowSeed?.theme || "luminarum",
371
+ mode: flags.mode || workflowSeed?.mode || "dark",
368
372
  shape: flags.shape || "rounded",
369
- target: flags.target || (detected.framework !== "unknown" ? detected.framework : "react"),
370
- guard: flags.guard || (detected.existingEssence ? "guided" : "strict"),
371
- density: flags.density || "comfortable",
372
- shell: flags.shell || "sidebar-main",
373
+ target: flags.target || workflowSeed?.target || (detected.framework !== "unknown" ? detected.framework : "react"),
374
+ guard: flags.guard || workflowSeed?.guard || (detected.existingEssence ? "guided" : "strict"),
375
+ density: flags.density || workflowSeed?.density || "comfortable",
376
+ shell: flags.shell || workflowSeed?.shell || "sidebar-main",
373
377
  personality: flags.personality || ["professional"],
374
378
  features: flags.features || [],
375
- existing: flags.existing || detected.existingEssence
379
+ existing: flags.existing || workflowSeed?.existing || detected.existingEssence,
380
+ workflowMode: flags.workflowMode || workflowSeed?.workflowMode
376
381
  };
377
382
  }
378
383
  async function runSimplifiedInit(blueprints) {
@@ -1532,11 +1537,11 @@ async function cmdThemeSwitch(themeName, args, projectRoot = process.cwd()) {
1532
1537
  }
1533
1538
 
1534
1539
  // src/commands/analyze.ts
1535
- import { existsSync as existsSync18, mkdirSync as mkdirSync4, writeFileSync as writeFileSync9 } from "fs";
1536
- import { join as join18 } from "path";
1540
+ import { existsSync as existsSync19, mkdirSync as mkdirSync4, writeFileSync as writeFileSync9 } from "fs";
1541
+ import { join as join19 } from "path";
1537
1542
 
1538
1543
  // src/analyzers/routes.ts
1539
- import { existsSync as existsSync12, readdirSync as readdirSync2, statSync } from "fs";
1544
+ import { existsSync as existsSync12, readFileSync as readFileSync11, readdirSync as readdirSync2, statSync } from "fs";
1540
1545
  import { join as join12, relative } from "path";
1541
1546
  var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".next", ".git", "api", "_app", "_document"]);
1542
1547
  function shouldSkipDir(name) {
@@ -1627,6 +1632,75 @@ function walkPagesDir(dir, baseDir, segments) {
1627
1632
  }
1628
1633
  return routes;
1629
1634
  }
1635
+ var ROUTER_FILE_EXTENSIONS = /* @__PURE__ */ new Set([".tsx", ".ts", ".jsx", ".js"]);
1636
+ function collectRouteCandidateFiles(dir, files, depth = 0) {
1637
+ if (depth > 5) return;
1638
+ let entries;
1639
+ try {
1640
+ entries = readdirSync2(dir);
1641
+ } catch {
1642
+ return;
1643
+ }
1644
+ for (const entry of entries) {
1645
+ if (entry.startsWith(".") || entry === "node_modules") continue;
1646
+ const fullPath = join12(dir, entry);
1647
+ try {
1648
+ const stat = statSync(fullPath);
1649
+ if (stat.isDirectory()) {
1650
+ collectRouteCandidateFiles(fullPath, files, depth + 1);
1651
+ } else if (stat.isFile()) {
1652
+ const ext = entry.slice(entry.lastIndexOf("."));
1653
+ if (ROUTER_FILE_EXTENSIONS.has(ext)) {
1654
+ files.push(fullPath);
1655
+ }
1656
+ }
1657
+ } catch {
1658
+ continue;
1659
+ }
1660
+ }
1661
+ }
1662
+ function scanReactRouter(projectRoot) {
1663
+ const candidateDirs = [
1664
+ join12(projectRoot, "src"),
1665
+ projectRoot
1666
+ ];
1667
+ const candidateFiles = [];
1668
+ for (const dir of candidateDirs) {
1669
+ if (existsSync12(dir)) collectRouteCandidateFiles(dir, candidateFiles);
1670
+ }
1671
+ const routeMap = /* @__PURE__ */ new Map();
1672
+ for (const absolutePath of candidateFiles) {
1673
+ let content;
1674
+ try {
1675
+ content = readFileSync11(absolutePath, "utf-8");
1676
+ } catch {
1677
+ continue;
1678
+ }
1679
+ const isReactRouterFile = content.includes("react-router-dom") || content.includes("react-router") || content.includes("<Routes") || content.includes("createBrowserRouter") || content.includes("createHashRouter") || content.includes("RouterProvider") || content.includes("HashRouter") || content.includes("BrowserRouter");
1680
+ if (!isReactRouterFile) continue;
1681
+ const relativePath = relative(projectRoot, absolutePath);
1682
+ const pathMatches = /* @__PURE__ */ new Set();
1683
+ for (const match of content.matchAll(/<Route\b[^>]*\bpath=["'`]([^"'`]+)["'`]/g)) {
1684
+ pathMatches.add(match[1]);
1685
+ }
1686
+ for (const match of content.matchAll(/\bpath\s*:\s*["'`]([^"'`]+)["'`]/g)) {
1687
+ pathMatches.add(match[1]);
1688
+ }
1689
+ if (pathMatches.size === 0 && (content.includes("<Routes") || content.includes("RouterProvider"))) {
1690
+ pathMatches.add("/");
1691
+ }
1692
+ for (const path of pathMatches) {
1693
+ if (!routeMap.has(path)) {
1694
+ routeMap.set(path, {
1695
+ path,
1696
+ file: relativePath,
1697
+ hasLayout: false
1698
+ });
1699
+ }
1700
+ }
1701
+ }
1702
+ return [...routeMap.values()];
1703
+ }
1630
1704
  function scanRoutes(projectRoot) {
1631
1705
  const appDirs = [
1632
1706
  join12(projectRoot, "src", "app"),
@@ -1652,6 +1726,10 @@ function scanRoutes(projectRoot) {
1652
1726
  }
1653
1727
  }
1654
1728
  }
1729
+ const reactRouterRoutes = scanReactRouter(projectRoot);
1730
+ if (reactRouterRoutes.length > 0) {
1731
+ return { strategy: "react-router", routes: reactRouterRoutes };
1732
+ }
1655
1733
  return { strategy: "none", routes: [] };
1656
1734
  }
1657
1735
 
@@ -1659,6 +1737,16 @@ function scanRoutes(projectRoot) {
1659
1737
  import { existsSync as existsSync13, readdirSync as readdirSync3, statSync as statSync2 } from "fs";
1660
1738
  import { join as join13 } from "path";
1661
1739
  var PAGE_EXTENSIONS = /* @__PURE__ */ new Set([".tsx", ".ts", ".jsx", ".js"]);
1740
+ var ROOT_COMPONENT_CANDIDATES = [
1741
+ "src/App.tsx",
1742
+ "src/App.ts",
1743
+ "src/App.jsx",
1744
+ "src/App.js",
1745
+ "App.tsx",
1746
+ "App.ts",
1747
+ "App.jsx",
1748
+ "App.js"
1749
+ ];
1662
1750
  function countFilesRecursive(dir, extensions) {
1663
1751
  let count = 0;
1664
1752
  let entries;
@@ -1750,6 +1838,18 @@ function scanComponents(projectRoot) {
1750
1838
  }
1751
1839
  }
1752
1840
  }
1841
+ const hasRootAppComponent = ROOT_COMPONENT_CANDIDATES.some(
1842
+ (relativePath) => existsSync13(join13(projectRoot, relativePath))
1843
+ );
1844
+ if (pageCount === 0 && hasRootAppComponent) {
1845
+ pageCount = 1;
1846
+ }
1847
+ if (componentCount === 0 && hasRootAppComponent) {
1848
+ componentCount = 1;
1849
+ if (!componentDirs.includes("src")) {
1850
+ componentDirs.push("src");
1851
+ }
1852
+ }
1753
1853
  return {
1754
1854
  pageCount,
1755
1855
  componentCount,
@@ -1758,7 +1858,7 @@ function scanComponents(projectRoot) {
1758
1858
  }
1759
1859
 
1760
1860
  // src/analyzers/styling.ts
1761
- import { existsSync as existsSync14, readFileSync as readFileSync11 } from "fs";
1861
+ import { existsSync as existsSync14, readFileSync as readFileSync12 } from "fs";
1762
1862
  import { join as join14 } from "path";
1763
1863
  var TAILWIND_CONFIGS = [
1764
1864
  "tailwind.config.js",
@@ -1769,11 +1869,17 @@ var TAILWIND_CONFIGS = [
1769
1869
  var GLOBALS_CSS_PATHS = [
1770
1870
  "src/app/globals.css",
1771
1871
  "app/globals.css",
1872
+ "src/styles/global.css",
1772
1873
  "src/styles/globals.css",
1773
1874
  "styles/globals.css",
1774
1875
  "src/index.css",
1775
1876
  "src/global.css"
1776
1877
  ];
1878
+ var DECANTR_STYLE_PATHS = [
1879
+ "src/styles/tokens.css",
1880
+ "src/styles/treatments.css",
1881
+ "src/styles/global.css"
1882
+ ];
1777
1883
  function extractCSSVariables(content) {
1778
1884
  const colors = {};
1779
1885
  const variables = [];
@@ -1790,8 +1896,8 @@ function extractCSSVariables(content) {
1790
1896
  }
1791
1897
  return { colors, variables };
1792
1898
  }
1793
- function detectDarkMode(projectRoot, cssContent) {
1794
- if (cssContent) {
1899
+ function detectDarkMode(projectRoot, cssContents) {
1900
+ for (const cssContent of cssContents) {
1795
1901
  if (cssContent.includes(".dark") || cssContent.includes('[data-theme="dark"]') || cssContent.includes("prefers-color-scheme: dark") || cssContent.includes("color-scheme: dark")) {
1796
1902
  return true;
1797
1903
  }
@@ -1806,7 +1912,7 @@ function detectDarkMode(projectRoot, cssContent) {
1806
1912
  const fullPath = join14(projectRoot, rel);
1807
1913
  if (existsSync14(fullPath)) {
1808
1914
  try {
1809
- const layoutContent = readFileSync11(fullPath, "utf-8");
1915
+ const layoutContent = readFileSync12(fullPath, "utf-8");
1810
1916
  if (layoutContent.includes('className="dark"') || layoutContent.includes("className='dark'") || layoutContent.includes('class="dark"')) {
1811
1917
  return true;
1812
1918
  }
@@ -1817,7 +1923,7 @@ function detectDarkMode(projectRoot, cssContent) {
1817
1923
  const pkgPath = join14(projectRoot, "package.json");
1818
1924
  if (existsSync14(pkgPath)) {
1819
1925
  try {
1820
- const pkg = JSON.parse(readFileSync11(pkgPath, "utf-8"));
1926
+ const pkg = JSON.parse(readFileSync12(pkgPath, "utf-8"));
1821
1927
  const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
1822
1928
  if (allDeps["next-themes"] || allDeps["theme-toggle"] || allDeps["use-dark-mode"]) {
1823
1929
  return true;
@@ -1825,6 +1931,17 @@ function detectDarkMode(projectRoot, cssContent) {
1825
1931
  } catch {
1826
1932
  }
1827
1933
  }
1934
+ const essencePath = join14(projectRoot, "decantr.essence.json");
1935
+ if (existsSync14(essencePath)) {
1936
+ try {
1937
+ const essence = JSON.parse(readFileSync12(essencePath, "utf-8"));
1938
+ const mode = essence.dna?.theme?.mode;
1939
+ if (mode === "dark" || mode === "auto") {
1940
+ return true;
1941
+ }
1942
+ } catch {
1943
+ }
1944
+ }
1828
1945
  return false;
1829
1946
  }
1830
1947
  function scanStyling(projectRoot) {
@@ -1841,8 +1958,12 @@ function scanStyling(projectRoot) {
1841
1958
  const pkgPath = join14(projectRoot, "package.json");
1842
1959
  if (existsSync14(pkgPath)) {
1843
1960
  try {
1844
- const pkg = JSON.parse(readFileSync11(pkgPath, "utf-8"));
1961
+ const pkg = JSON.parse(readFileSync12(pkgPath, "utf-8"));
1845
1962
  const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
1963
+ if (allDeps["@decantr/css"]) {
1964
+ approach = "decantr-css";
1965
+ configFile = "src/styles/tokens.css";
1966
+ }
1846
1967
  if (allDeps.tailwindcss || allDeps["@tailwindcss/postcss"] || allDeps["@tailwindcss/vite"]) {
1847
1968
  approach = "tailwind";
1848
1969
  }
@@ -1850,25 +1971,44 @@ function scanStyling(projectRoot) {
1850
1971
  }
1851
1972
  }
1852
1973
  }
1853
- let cssContent = null;
1974
+ const decantrStyleFiles = DECANTR_STYLE_PATHS.filter((rel) => existsSync14(join14(projectRoot, rel)));
1975
+ if (decantrStyleFiles.length >= 2) {
1976
+ approach = "decantr-css";
1977
+ configFile = decantrStyleFiles.join(" + ");
1978
+ }
1979
+ const cssContents = [];
1854
1980
  for (const rel of GLOBALS_CSS_PATHS) {
1855
1981
  const fullPath = join14(projectRoot, rel);
1856
1982
  if (existsSync14(fullPath)) {
1857
1983
  try {
1858
- cssContent = readFileSync11(fullPath, "utf-8");
1984
+ cssContents.push(readFileSync12(fullPath, "utf-8"));
1985
+ } catch {
1986
+ }
1987
+ }
1988
+ }
1989
+ for (const rel of DECANTR_STYLE_PATHS) {
1990
+ if (GLOBALS_CSS_PATHS.includes(rel)) continue;
1991
+ const fullPath = join14(projectRoot, rel);
1992
+ if (existsSync14(fullPath)) {
1993
+ try {
1994
+ cssContents.push(readFileSync12(fullPath, "utf-8"));
1859
1995
  } catch {
1860
1996
  }
1861
- break;
1862
1997
  }
1863
1998
  }
1864
1999
  let colors = {};
1865
2000
  let cssVariables = [];
1866
- if (cssContent) {
2001
+ for (const cssContent of cssContents) {
1867
2002
  const extracted = extractCSSVariables(cssContent);
1868
- colors = extracted.colors;
1869
- cssVariables = extracted.variables;
2003
+ colors = { ...colors, ...extracted.colors };
2004
+ cssVariables.push(...extracted.variables);
2005
+ }
2006
+ cssVariables = [...new Set(cssVariables)];
2007
+ const darkMode = detectDarkMode(projectRoot, cssContents);
2008
+ if (approach === "unknown" && cssContents.length > 0) {
2009
+ approach = "css";
2010
+ configFile = GLOBALS_CSS_PATHS.find((rel) => existsSync14(join14(projectRoot, rel)));
1870
2011
  }
1871
- const darkMode = detectDarkMode(projectRoot, cssContent);
1872
2012
  return {
1873
2013
  approach,
1874
2014
  configFile,
@@ -1879,7 +2019,7 @@ function scanStyling(projectRoot) {
1879
2019
  }
1880
2020
 
1881
2021
  // src/analyzers/layout.ts
1882
- import { existsSync as existsSync15, readFileSync as readFileSync12, readdirSync as readdirSync4 } from "fs";
2022
+ import { existsSync as existsSync15, readFileSync as readFileSync13, readdirSync as readdirSync4 } from "fs";
1883
2023
  import { join as join15 } from "path";
1884
2024
  var SIDEBAR_PATTERNS = ["sidebar", "side-bar", "sidenav", "side-nav", "drawer", "aside"];
1885
2025
  var NAV_PATTERNS = ["nav", "navbar", "header", "top-bar", "topbar", "app-bar", "appbar"];
@@ -1924,7 +2064,7 @@ function checkLayoutFiles(projectRoot) {
1924
2064
  if (!existsSync15(layoutPath)) continue;
1925
2065
  let content;
1926
2066
  try {
1927
- content = readFileSync12(layoutPath, "utf-8");
2067
+ content = readFileSync13(layoutPath, "utf-8");
1928
2068
  } catch {
1929
2069
  continue;
1930
2070
  }
@@ -1944,7 +2084,7 @@ function checkLayoutFiles(projectRoot) {
1944
2084
  if (!existsSync15(layoutPath)) continue;
1945
2085
  let content;
1946
2086
  try {
1947
- content = readFileSync12(layoutPath, "utf-8");
2087
+ content = readFileSync13(layoutPath, "utf-8");
1948
2088
  } catch {
1949
2089
  continue;
1950
2090
  }
@@ -1965,17 +2105,40 @@ function inferShellPattern(hasSidebar, hasTopNav, hasFooter) {
1965
2105
  if (hasFooter) return "main-footer";
1966
2106
  return "main-only";
1967
2107
  }
2108
+ function inferShellPatternFromDecantrContract(projectRoot) {
2109
+ const essencePath = join15(projectRoot, "decantr.essence.json");
2110
+ if (!existsSync15(essencePath)) return null;
2111
+ try {
2112
+ const essence = JSON.parse(readFileSync13(essencePath, "utf-8"));
2113
+ const sectionShells = essence.blueprint?.sections?.map((section) => section.shell).filter((shell) => typeof shell === "string" && shell.length > 0) ?? [];
2114
+ if (sectionShells.length === 0 && essence.blueprint?.shell) {
2115
+ return `${essence.blueprint.shell} (contract)`;
2116
+ }
2117
+ const uniqueShells = [...new Set(sectionShells)];
2118
+ if (uniqueShells.length === 0) return null;
2119
+ if (uniqueShells.length === 1) return `${uniqueShells[0]} (contract)`;
2120
+ const primarySection = essence.blueprint?.sections?.find((section) => section.role === "primary" && section.shell);
2121
+ if (primarySection?.shell) {
2122
+ return `${primarySection.shell} (primary contract)`;
2123
+ }
2124
+ return `${uniqueShells[0]} (+${uniqueShells.length - 1} shells, contract)`;
2125
+ } catch {
2126
+ return null;
2127
+ }
2128
+ }
1968
2129
  function scanLayout(projectRoot) {
1969
2130
  const fromComponents = checkComponentDirs(projectRoot);
1970
2131
  const fromLayouts = checkLayoutFiles(projectRoot);
1971
2132
  const hasSidebar = fromComponents.sidebar || fromLayouts.sidebar;
1972
2133
  const hasTopNav = fromComponents.nav || fromLayouts.nav;
1973
2134
  const hasFooter = fromComponents.footer || fromLayouts.footer;
2135
+ const runtimeShellPattern = inferShellPattern(hasSidebar, hasTopNav, hasFooter);
2136
+ const contractShellPattern = inferShellPatternFromDecantrContract(projectRoot);
1974
2137
  return {
1975
2138
  hasSidebar,
1976
2139
  hasTopNav,
1977
2140
  hasFooter,
1978
- shellPattern: inferShellPattern(hasSidebar, hasTopNav, hasFooter)
2141
+ shellPattern: runtimeShellPattern === "main-only" && contractShellPattern ? contractShellPattern : runtimeShellPattern
1979
2142
  };
1980
2143
  }
1981
2144
 
@@ -2059,12 +2222,14 @@ function scanFeatures(projectRoot) {
2059
2222
  }
2060
2223
 
2061
2224
  // src/analyzers/dependencies.ts
2062
- import { existsSync as existsSync17, readFileSync as readFileSync13 } from "fs";
2225
+ import { existsSync as existsSync17, readFileSync as readFileSync14 } from "fs";
2063
2226
  import { join as join17 } from "path";
2064
2227
  var CATEGORIES = {
2065
2228
  ui: [
2066
2229
  "react",
2067
2230
  "react-dom",
2231
+ "react-router",
2232
+ "react-router-dom",
2068
2233
  "vue",
2069
2234
  "svelte",
2070
2235
  "@angular/core",
@@ -2212,7 +2377,7 @@ function scanDependencies(projectRoot) {
2212
2377
  if (!existsSync17(pkgPath)) return result;
2213
2378
  let pkg;
2214
2379
  try {
2215
- pkg = JSON.parse(readFileSync13(pkgPath, "utf-8"));
2380
+ pkg = JSON.parse(readFileSync14(pkgPath, "utf-8"));
2216
2381
  } catch {
2217
2382
  return result;
2218
2383
  }
@@ -2231,6 +2396,54 @@ function scanDependencies(projectRoot) {
2231
2396
  return result;
2232
2397
  }
2233
2398
 
2399
+ // src/workflow-model.ts
2400
+ import { existsSync as existsSync18, readFileSync as readFileSync15 } from "fs";
2401
+ import { join as join18 } from "path";
2402
+ function inferSuggestedShell(layout) {
2403
+ if (layout.hasSidebar) return "sidebar-main";
2404
+ if (layout.hasTopNav) return "top-nav-main";
2405
+ return "full-bleed";
2406
+ }
2407
+ function hasExistingProjectFootprint(detected) {
2408
+ return detected.framework !== "unknown" || detected.packageManager !== "unknown" || detected.hasTypeScript || detected.hasTailwind || detected.existingRuleFiles.length > 0;
2409
+ }
2410
+ function createBrownfieldInitSeed(detected, layout, styling) {
2411
+ return {
2412
+ version: 1,
2413
+ workflow: "brownfield-adoption",
2414
+ contractOnly: true,
2415
+ registryOptional: true,
2416
+ workflowMode: "brownfield-attach",
2417
+ target: detected.framework !== "unknown" ? detected.framework : "react",
2418
+ shell: inferSuggestedShell(layout),
2419
+ guard: "guided",
2420
+ density: "comfortable",
2421
+ theme: "luminarum",
2422
+ mode: styling.darkMode ? "dark" : "auto",
2423
+ existing: true,
2424
+ notes: [
2425
+ "Use decantr init --existing to attach Decantr contract and context files to this project.",
2426
+ "Registry content is optional during brownfield adoption.",
2427
+ "Use decantr add/remove, decantr theme switch, and registry commands later for hybrid composition."
2428
+ ]
2429
+ };
2430
+ }
2431
+ function readBrownfieldInitSeed(projectRoot) {
2432
+ const seedPath = join18(projectRoot, ".decantr", "init-seed.json");
2433
+ if (!existsSync18(seedPath)) {
2434
+ return null;
2435
+ }
2436
+ try {
2437
+ const parsed = JSON.parse(readFileSync15(seedPath, "utf-8"));
2438
+ if (parsed.workflow !== "brownfield-adoption") {
2439
+ return null;
2440
+ }
2441
+ return parsed;
2442
+ } catch {
2443
+ return null;
2444
+ }
2445
+ }
2446
+
2234
2447
  // src/commands/analyze.ts
2235
2448
  var BOLD3 = "\x1B[1m";
2236
2449
  var DIM8 = "\x1B[2m";
@@ -2256,6 +2469,7 @@ ${BOLD3}Analyzing project...${RESET8}
2256
2469
  const features = scanFeatures(projectRoot);
2257
2470
  console.log(`${DIM8}Scanning dependencies...${RESET8}`);
2258
2471
  const dependencies = scanDependencies(projectRoot);
2472
+ const initSeed = createBrownfieldInitSeed(project, layout, styling);
2259
2473
  const analysis = {
2260
2474
  version: 1,
2261
2475
  analyzedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -2271,14 +2485,29 @@ ${BOLD3}Analyzing project...${RESET8}
2271
2485
  styling,
2272
2486
  layout,
2273
2487
  features,
2274
- dependencies
2488
+ dependencies,
2489
+ decantr: {
2490
+ workflow: "brownfield-adoption",
2491
+ registryOptional: true,
2492
+ attach: {
2493
+ entrypoint: "decantr analyze",
2494
+ contractOnly: true,
2495
+ initSeedPath: ".decantr/init-seed.json",
2496
+ recommendedCommand: "decantr init --existing --yes"
2497
+ },
2498
+ hybrid: {
2499
+ ownerCommands: ["decantr add", "decantr remove", "decantr theme switch", "decantr registry", "decantr upgrade"]
2500
+ }
2501
+ }
2275
2502
  };
2276
- const decantrDir = join18(projectRoot, ".decantr");
2277
- if (!existsSync18(decantrDir)) {
2503
+ const decantrDir = join19(projectRoot, ".decantr");
2504
+ if (!existsSync19(decantrDir)) {
2278
2505
  mkdirSync4(decantrDir, { recursive: true });
2279
2506
  }
2280
- const outputPath = join18(decantrDir, "analysis.json");
2507
+ const outputPath = join19(decantrDir, "analysis.json");
2508
+ const initSeedPath = join19(decantrDir, "init-seed.json");
2281
2509
  writeFileSync9(outputPath, JSON.stringify(analysis, null, 2) + "\n", "utf-8");
2510
+ writeFileSync9(initSeedPath, JSON.stringify(initSeed, null, 2) + "\n", "utf-8");
2282
2511
  console.log(`
2283
2512
  ${GREEN8}Analysis complete.${RESET8}
2284
2513
  `);
@@ -2303,14 +2532,15 @@ ${GREEN8}Analysis complete.${RESET8}
2303
2532
  console.log(` Dependencies: ${depCounts || "none categorized"}`);
2304
2533
  console.log(`
2305
2534
  ${DIM8}Written to:${RESET8} ${outputPath}`);
2535
+ console.log(`${DIM8}Init seed:${RESET8} ${initSeedPath}`);
2306
2536
  console.log(`
2307
- ${YELLOW5}Next step:${RESET8} Ask your AI assistant to read ${BOLD3}.decantr/analysis.json${RESET8} and set up Decantr
2537
+ ${YELLOW5}Next step:${RESET8} Run ${BOLD3}decantr init --existing --yes${RESET8} to attach Decantr using the generated brownfield seed.
2308
2538
  `);
2309
2539
  }
2310
2540
 
2311
2541
  // src/commands/magic.ts
2312
- import { join as join19 } from "path";
2313
- import { existsSync as existsSync19 } from "fs";
2542
+ import { join as join20 } from "path";
2543
+ import { existsSync as existsSync20 } from "fs";
2314
2544
  import * as fs from "fs/promises";
2315
2545
  var BOLD4 = "\x1B[1m";
2316
2546
  var DIM9 = "\x1B[2m";
@@ -2538,15 +2768,25 @@ async function cmdMagic(prompt, projectRoot, options) {
2538
2768
  console.log(` Archetype: ${intent.archetype}`);
2539
2769
  }
2540
2770
  console.log("");
2541
- const essencePath = join19(projectRoot, "decantr.essence.json");
2542
- if (existsSync19(essencePath)) {
2771
+ const essencePath = join20(projectRoot, "decantr.essence.json");
2772
+ if (existsSync20(essencePath)) {
2543
2773
  console.log(error(" decantr.essence.json already exists in this directory."));
2544
2774
  console.log(dim(" Remove it first or use a different directory."));
2545
2775
  process.exitCode = 1;
2546
2776
  return;
2547
2777
  }
2778
+ const detected = detectProject(projectRoot);
2779
+ if (hasExistingProjectFootprint(detected)) {
2780
+ console.log(`${YELLOW6} Existing project detected.${RESET9}`);
2781
+ console.log(dim(" decantr magic stays greenfield-first and will not silently bootstrap over an existing app."));
2782
+ console.log(dim(" Running brownfield analysis instead so you can attach Decantr deliberately.\n"));
2783
+ cmdAnalyze(projectRoot);
2784
+ console.log(`${BOLD4}Recommended next step:${RESET9} ${cyan("decantr init --existing --yes")}`);
2785
+ console.log("");
2786
+ return;
2787
+ }
2548
2788
  const registryClient = new RegistryClient({
2549
- cacheDir: join19(projectRoot, ".decantr", "cache"),
2789
+ cacheDir: join20(projectRoot, ".decantr", "cache"),
2550
2790
  apiUrl: options.registry,
2551
2791
  offline: options.offline
2552
2792
  });
@@ -2647,7 +2887,6 @@ async function cmdMagic(prompt, projectRoot, options) {
2647
2887
  return;
2648
2888
  }
2649
2889
  console.log("");
2650
- const detected = detectProject(projectRoot);
2651
2890
  let archetypeData;
2652
2891
  let composedSections;
2653
2892
  let routeMap;
@@ -2809,14 +3048,14 @@ async function cmdMagic(prompt, projectRoot, options) {
2809
3048
  if (result.gitignoreUpdated) {
2810
3049
  console.log(` ${dim(".gitignore updated")}`);
2811
3050
  }
2812
- const contextDir = join19(projectRoot, ".decantr", "context");
3051
+ const contextDir = join20(projectRoot, ".decantr", "context");
2813
3052
  let sectionCount = 0;
2814
3053
  try {
2815
3054
  const files = await fs.readdir(contextDir);
2816
3055
  sectionCount = files.filter((f) => f.startsWith("section-")).length;
2817
3056
  } catch {
2818
3057
  }
2819
- const treatmentsPath = join19(projectRoot, "src", "styles", "treatments.css");
3058
+ const treatmentsPath = join20(projectRoot, "src", "styles", "treatments.css");
2820
3059
  let hasLayers = false;
2821
3060
  try {
2822
3061
  const css = await fs.readFile(treatmentsPath, "utf-8");
@@ -2841,8 +3080,8 @@ ${GREEN9}${BOLD4}Quality summary:${RESET9}`);
2841
3080
  }
2842
3081
 
2843
3082
  // src/commands/export.ts
2844
- import { readFileSync as readFileSync14, writeFileSync as writeFileSync10, existsSync as existsSync20, mkdirSync as mkdirSync5 } from "fs";
2845
- import { join as join20, dirname } from "path";
3083
+ import { readFileSync as readFileSync16, writeFileSync as writeFileSync10, existsSync as existsSync21, mkdirSync as mkdirSync5 } from "fs";
3084
+ import { join as join21, dirname } from "path";
2846
3085
  var GREEN10 = "\x1B[32m";
2847
3086
  var RED8 = "\x1B[31m";
2848
3087
  var DIM10 = "\x1B[2m";
@@ -2988,19 +3227,19 @@ function generateCSSVars(tokens) {
2988
3227
  return lines.join("\n");
2989
3228
  }
2990
3229
  async function cmdExport(target, projectRoot, options = {}) {
2991
- const essencePath = join20(projectRoot, "decantr.essence.json");
2992
- const tokensPath = join20(projectRoot, "src", "styles", "tokens.css");
2993
- if (!existsSync20(essencePath)) {
3230
+ const essencePath = join21(projectRoot, "decantr.essence.json");
3231
+ const tokensPath = join21(projectRoot, "src", "styles", "tokens.css");
3232
+ if (!existsSync21(essencePath)) {
2994
3233
  console.error(`${RED8}No decantr.essence.json found. Run \`decantr init\` first.${RESET10}`);
2995
3234
  process.exitCode = 1;
2996
3235
  return;
2997
3236
  }
2998
- if (!existsSync20(tokensPath)) {
3237
+ if (!existsSync21(tokensPath)) {
2999
3238
  console.error(`${RED8}No src/styles/tokens.css found. Run \`decantr refresh\` to generate tokens.${RESET10}`);
3000
3239
  process.exitCode = 1;
3001
3240
  return;
3002
3241
  }
3003
- const tokensCSS = readFileSync14(tokensPath, "utf-8");
3242
+ const tokensCSS = readFileSync16(tokensPath, "utf-8");
3004
3243
  const tokens = parseTokensCSS(tokensCSS);
3005
3244
  if (tokens.size === 0) {
3006
3245
  console.error(`${RED8}No --d-* tokens found in tokens.css.${RESET10}`);
@@ -3009,8 +3248,8 @@ async function cmdExport(target, projectRoot, options = {}) {
3009
3248
  }
3010
3249
  switch (target) {
3011
3250
  case "shadcn": {
3012
- const cssOut = options.output ?? join20(projectRoot, "src", "styles", "shadcn-theme.css");
3013
- const jsonOut = join20(projectRoot, "components.json");
3251
+ const cssOut = options.output ?? join21(projectRoot, "src", "styles", "shadcn-theme.css");
3252
+ const jsonOut = join21(projectRoot, "components.json");
3014
3253
  ensureDir(cssOut);
3015
3254
  writeFileSync10(cssOut, generateShadcnCSS(tokens), "utf-8");
3016
3255
  writeFileSync10(jsonOut, generateShadcnComponentsJSON(), "utf-8");
@@ -3020,7 +3259,7 @@ async function cmdExport(target, projectRoot, options = {}) {
3020
3259
  break;
3021
3260
  }
3022
3261
  case "tailwind": {
3023
- const out = options.output ?? join20(projectRoot, "tailwind.decantr.config.ts");
3262
+ const out = options.output ?? join21(projectRoot, "tailwind.decantr.config.ts");
3024
3263
  ensureDir(out);
3025
3264
  writeFileSync10(out, generateTailwindConfig(tokens), "utf-8");
3026
3265
  console.log(`${GREEN10}Exported Tailwind config:${RESET10}`);
@@ -3028,7 +3267,7 @@ async function cmdExport(target, projectRoot, options = {}) {
3028
3267
  break;
3029
3268
  }
3030
3269
  case "css-vars": {
3031
- const out = options.output ?? join20(projectRoot, "decantr-tokens.css");
3270
+ const out = options.output ?? join21(projectRoot, "decantr-tokens.css");
3032
3271
  ensureDir(out);
3033
3272
  writeFileSync10(out, generateCSSVars(tokens), "utf-8");
3034
3273
  console.log(`${GREEN10}Exported CSS variables:${RESET10}`);
@@ -3039,14 +3278,14 @@ async function cmdExport(target, projectRoot, options = {}) {
3039
3278
  }
3040
3279
  function ensureDir(filePath) {
3041
3280
  const dir = dirname(filePath);
3042
- if (!existsSync20(dir)) {
3281
+ if (!existsSync21(dir)) {
3043
3282
  mkdirSync5(dir, { recursive: true });
3044
3283
  }
3045
3284
  }
3046
3285
 
3047
3286
  // src/commands/registry-mirror.ts
3048
3287
  import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync11 } from "fs";
3049
- import { join as join21 } from "path";
3288
+ import { join as join22 } from "path";
3050
3289
  import { RegistryAPIClient as RegistryAPIClient2, API_CONTENT_TYPES } from "@decantr/registry";
3051
3290
  var GREEN11 = "\x1B[32m";
3052
3291
  var RED9 = "\x1B[31m";
@@ -3074,7 +3313,7 @@ async function cmdRegistryMirror(projectRoot, options = {}) {
3074
3313
  process.exitCode = 1;
3075
3314
  return;
3076
3315
  }
3077
- const cacheDir = join21(projectRoot, ".decantr", "cache");
3316
+ const cacheDir = join22(projectRoot, ".decantr", "cache");
3078
3317
  const counts = {};
3079
3318
  const failed = [];
3080
3319
  console.log(`
@@ -3084,19 +3323,19 @@ Mirroring registry content to ${DIM11}.decantr/cache/${RESET11}
3084
3323
  try {
3085
3324
  const result = await apiClient.listContent(type, { namespace: "@official" });
3086
3325
  const items = result.items;
3087
- const typeDir = join21(cacheDir, "@official", type);
3326
+ const typeDir = join22(cacheDir, "@official", type);
3088
3327
  mkdirSync6(typeDir, { recursive: true });
3089
- writeFileSync11(join21(typeDir, "index.json"), JSON.stringify(result, null, 2));
3328
+ writeFileSync11(join22(typeDir, "index.json"), JSON.stringify(result, null, 2));
3090
3329
  let itemCount = 0;
3091
3330
  for (const item of items) {
3092
3331
  const slug = item.slug || item.id;
3093
3332
  if (!slug) continue;
3094
3333
  try {
3095
3334
  const fullItem = await apiClient.getContent(type, "@official", slug);
3096
- writeFileSync11(join21(typeDir, `${slug}.json`), JSON.stringify(fullItem, null, 2));
3335
+ writeFileSync11(join22(typeDir, `${slug}.json`), JSON.stringify(fullItem, null, 2));
3097
3336
  itemCount++;
3098
3337
  } catch {
3099
- writeFileSync11(join21(typeDir, `${slug}.json`), JSON.stringify(item, null, 2));
3338
+ writeFileSync11(join22(typeDir, `${slug}.json`), JSON.stringify(item, null, 2));
3100
3339
  itemCount++;
3101
3340
  }
3102
3341
  }
@@ -3111,8 +3350,8 @@ Mirroring registry content to ${DIM11}.decantr/cache/${RESET11}
3111
3350
  mirrored_at: (/* @__PURE__ */ new Date()).toISOString(),
3112
3351
  counts
3113
3352
  };
3114
- mkdirSync6(join21(cacheDir), { recursive: true });
3115
- writeFileSync11(join21(cacheDir, "mirror-manifest.json"), JSON.stringify(manifest, null, 2));
3353
+ mkdirSync6(join22(cacheDir), { recursive: true });
3354
+ writeFileSync11(join22(cacheDir, "mirror-manifest.json"), JSON.stringify(manifest, null, 2));
3116
3355
  const totalItems = Object.values(counts).reduce((a, b) => a + b, 0);
3117
3356
  console.log("");
3118
3357
  if (failed.length > 0) {
@@ -3125,98 +3364,160 @@ Mirroring registry content to ${DIM11}.decantr/cache/${RESET11}
3125
3364
  }
3126
3365
 
3127
3366
  // src/commands/new-project.ts
3128
- import { existsSync as existsSync23, mkdirSync as mkdirSync8, readFileSync as readFileSync15, writeFileSync as writeFileSync12 } from "fs";
3129
- import { join as join23, resolve as resolve2 } from "path";
3367
+ import { existsSync as existsSync24, mkdirSync as mkdirSync9 } from "fs";
3368
+ import { join as join25, resolve as resolve2 } from "path";
3130
3369
  import { execSync } from "child_process";
3131
3370
  import { fileURLToPath } from "url";
3132
3371
 
3133
3372
  // src/offline-content.ts
3134
- import { cpSync, existsSync as existsSync22, mkdirSync as mkdirSync7 } from "fs";
3135
- import { join as join22, resolve } from "path";
3373
+ import { cpSync, existsSync as existsSync23, mkdirSync as mkdirSync7 } from "fs";
3374
+ import { join as join23, resolve } from "path";
3136
3375
  var CONTENT_TYPES2 = ["archetypes", "blueprints", "patterns", "themes", "shells"];
3137
3376
  function copyIfExists(source, target) {
3138
- if (!existsSync22(source)) return false;
3377
+ if (!existsSync23(source)) return false;
3139
3378
  if (resolve(source) === resolve(target)) return true;
3140
3379
  cpSync(source, target, { recursive: true });
3141
3380
  return true;
3142
3381
  }
3143
3382
  function hydrateContentRoot(projectDir, contentRoot) {
3144
- if (!existsSync22(contentRoot)) return false;
3145
- const customRoot = join22(projectDir, ".decantr", "custom");
3146
- const cacheRoot = join22(projectDir, ".decantr", "cache", "@official");
3383
+ if (!existsSync23(contentRoot)) return false;
3384
+ const customRoot = join23(projectDir, ".decantr", "custom");
3385
+ const cacheRoot = join23(projectDir, ".decantr", "cache", "@official");
3147
3386
  mkdirSync7(customRoot, { recursive: true });
3148
3387
  mkdirSync7(cacheRoot, { recursive: true });
3149
3388
  let copiedAny = false;
3150
3389
  for (const type of CONTENT_TYPES2) {
3151
- const sourceDir = join22(contentRoot, type);
3152
- if (!existsSync22(sourceDir)) continue;
3153
- cpSync(sourceDir, join22(customRoot, type), { recursive: true });
3154
- cpSync(sourceDir, join22(cacheRoot, type), { recursive: true });
3390
+ const sourceDir = join23(contentRoot, type);
3391
+ if (!existsSync23(sourceDir)) continue;
3392
+ cpSync(sourceDir, join23(customRoot, type), { recursive: true });
3393
+ cpSync(sourceDir, join23(cacheRoot, type), { recursive: true });
3155
3394
  copiedAny = true;
3156
3395
  }
3157
3396
  return copiedAny;
3158
3397
  }
3159
3398
  function seedOfflineRegistry(projectDir, workspaceRoot) {
3160
- const projectDecantrRoot = join22(projectDir, ".decantr");
3399
+ const projectDecantrRoot = join23(projectDir, ".decantr");
3161
3400
  mkdirSync7(projectDecantrRoot, { recursive: true });
3162
- const copiedCache = copyIfExists(join22(workspaceRoot, ".decantr", "cache"), join22(projectDecantrRoot, "cache"));
3163
- const copiedCustom = copyIfExists(join22(workspaceRoot, ".decantr", "custom"), join22(projectDecantrRoot, "custom"));
3401
+ const configuredContentRoot = process.env.DECANTR_CONTENT_DIR ? resolve(process.env.DECANTR_CONTENT_DIR) : null;
3402
+ if (configuredContentRoot && hydrateContentRoot(projectDir, configuredContentRoot)) {
3403
+ return { seeded: true, strategy: "configured-content-root" };
3404
+ }
3405
+ const copiedCache = copyIfExists(join23(workspaceRoot, ".decantr", "cache"), join23(projectDecantrRoot, "cache"));
3406
+ const copiedCustom = copyIfExists(join23(workspaceRoot, ".decantr", "custom"), join23(projectDecantrRoot, "custom"));
3164
3407
  if (copiedCache || copiedCustom) {
3165
3408
  return { seeded: true, strategy: "workspace-cache" };
3166
3409
  }
3167
- const configuredContentRoot = process.env.DECANTR_CONTENT_DIR ? resolve(process.env.DECANTR_CONTENT_DIR) : null;
3168
3410
  const siblingContentRoot = resolve(workspaceRoot, "..", "decantr-content");
3169
- const contentRoot = configuredContentRoot && existsSync22(configuredContentRoot) ? configuredContentRoot : siblingContentRoot;
3170
- if (hydrateContentRoot(projectDir, contentRoot)) {
3171
- return {
3172
- seeded: true,
3173
- strategy: configuredContentRoot && existsSync22(configuredContentRoot) ? "configured-content-root" : "sibling-content-root"
3174
- };
3411
+ if (hydrateContentRoot(projectDir, siblingContentRoot)) {
3412
+ return { seeded: true, strategy: "sibling-content-root" };
3175
3413
  }
3176
3414
  return { seeded: false, strategy: null };
3177
3415
  }
3178
3416
 
3179
- // src/commands/new-project.ts
3180
- var BOLD5 = "\x1B[1m";
3181
- var DIM12 = "\x1B[2m";
3182
- var RESET12 = "\x1B[0m";
3183
- var RED10 = "\x1B[31m";
3184
- var GREEN12 = "\x1B[32m";
3185
- var CYAN6 = "\x1B[36m";
3186
- var YELLOW8 = "\x1B[33m";
3187
- function heading(text) {
3188
- return `
3189
- ${BOLD5}${text}${RESET12}
3417
+ // src/bootstrap.ts
3418
+ import { mkdirSync as mkdirSync8, readFileSync as readFileSync17, writeFileSync as writeFileSync12 } from "fs";
3419
+ import { basename, join as join24 } from "path";
3420
+ import { resolvePackAdapter } from "@decantr/core";
3421
+ var reactViteBootstrapAdapter = {
3422
+ id: "react-vite",
3423
+ label: "React + Vite starter",
3424
+ writeProjectFiles(projectDir, title, routingMode) {
3425
+ const srcDir = join24(projectDir, "src");
3426
+ const routerImport = routingMode === "hash" ? "HashRouter" : "BrowserRouter";
3427
+ const packageJson = {
3428
+ name: basename(projectDir) || "decantr-app",
3429
+ private: true,
3430
+ version: "0.0.0",
3431
+ type: "module",
3432
+ scripts: {
3433
+ dev: "vite",
3434
+ build: "tsc -b && vite build",
3435
+ preview: "vite preview"
3436
+ },
3437
+ dependencies: {
3438
+ react: "^19.0.0",
3439
+ "react-dom": "^19.0.0",
3440
+ "react-router-dom": "^7.0.0",
3441
+ "@decantr/css": "^1.0.0"
3442
+ },
3443
+ devDependencies: {
3444
+ "@types/react": "^19.0.0",
3445
+ "@types/react-dom": "^19.0.0",
3446
+ "@vitejs/plugin-react": "^4.0.0",
3447
+ typescript: "^5.7.0",
3448
+ vite: "^6.0.0"
3449
+ }
3450
+ };
3451
+ writeFileSync12(join24(projectDir, "package.json"), JSON.stringify(packageJson, null, 2) + "\n");
3452
+ const viteConfig = `import { defineConfig } from 'vite';
3453
+ import react from '@vitejs/plugin-react';
3454
+
3455
+ export default defineConfig({
3456
+ plugins: [react()],
3457
+ });
3190
3458
  `;
3191
- }
3192
- function success2(text) {
3193
- return `${GREEN12}${text}${RESET12}`;
3194
- }
3195
- function error2(text) {
3196
- return `${RED10}${text}${RESET12}`;
3197
- }
3198
- function dim2(text) {
3199
- return `${DIM12}${text}${RESET12}`;
3200
- }
3201
- function cyan2(text) {
3202
- return `${CYAN6}${text}${RESET12}`;
3203
- }
3204
- function detectRoutingMode(projectDir) {
3205
- try {
3206
- const essence = JSON.parse(readFileSync15(join23(projectDir, "decantr.essence.json"), "utf-8"));
3207
- const routing = essence.meta?.platform?.routing;
3208
- if (routing === "history" || routing === "pathname") {
3209
- return routing;
3210
- }
3211
- return "hash";
3212
- } catch {
3213
- return "hash";
3214
- }
3215
- }
3216
- function writeStarterRuntimeFiles(projectDir, title, routingMode) {
3217
- const srcDir = join23(projectDir, "src");
3218
- const routerImport = routingMode === "hash" ? "HashRouter" : "BrowserRouter";
3219
- const mainTsx = `import { StrictMode } from 'react';
3459
+ writeFileSync12(join24(projectDir, "vite.config.ts"), viteConfig);
3460
+ const tsconfig = {
3461
+ compilerOptions: {
3462
+ target: "ES2020",
3463
+ useDefineForClassFields: true,
3464
+ lib: ["ES2020", "DOM", "DOM.Iterable"],
3465
+ module: "ESNext",
3466
+ skipLibCheck: true,
3467
+ moduleResolution: "bundler",
3468
+ allowImportingTsExtensions: true,
3469
+ isolatedModules: true,
3470
+ moduleDetection: "force",
3471
+ noEmit: true,
3472
+ jsx: "react-jsx",
3473
+ strict: true,
3474
+ noUnusedLocals: true,
3475
+ noUnusedParameters: true,
3476
+ noFallthroughCasesInSwitch: true,
3477
+ noUncheckedSideEffectImports: true
3478
+ },
3479
+ include: ["src"]
3480
+ };
3481
+ writeFileSync12(join24(projectDir, "tsconfig.json"), JSON.stringify(tsconfig, null, 2) + "\n");
3482
+ const tsconfigApp = {
3483
+ compilerOptions: {
3484
+ tsBuildInfoFile: "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
3485
+ target: "ES2020",
3486
+ useDefineForClassFields: true,
3487
+ lib: ["ES2020", "DOM", "DOM.Iterable"],
3488
+ module: "ESNext",
3489
+ skipLibCheck: true,
3490
+ moduleResolution: "bundler",
3491
+ allowImportingTsExtensions: true,
3492
+ isolatedModules: true,
3493
+ moduleDetection: "force",
3494
+ noEmit: true,
3495
+ jsx: "react-jsx",
3496
+ strict: true,
3497
+ noUnusedLocals: true,
3498
+ noUnusedParameters: true,
3499
+ noFallthroughCasesInSwitch: true,
3500
+ noUncheckedSideEffectImports: true
3501
+ },
3502
+ include: ["src"]
3503
+ };
3504
+ writeFileSync12(join24(projectDir, "tsconfig.app.json"), JSON.stringify(tsconfigApp, null, 2) + "\n");
3505
+ const indexHtml = `<!doctype html>
3506
+ <html lang="en">
3507
+ <head>
3508
+ <meta charset="UTF-8" />
3509
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
3510
+ <title>${title}</title>
3511
+ </head>
3512
+ <body>
3513
+ <div id="root"></div>
3514
+ <script type="module" src="/src/main.tsx"></script>
3515
+ </body>
3516
+ </html>
3517
+ `;
3518
+ writeFileSync12(join24(projectDir, "index.html"), indexHtml);
3519
+ mkdirSync8(srcDir, { recursive: true });
3520
+ const mainTsx = `import { StrictMode } from 'react';
3220
3521
  import { createRoot } from 'react-dom/client';
3221
3522
  import { ${routerImport} } from 'react-router-dom';
3222
3523
  import { App } from './App';
@@ -3232,8 +3533,8 @@ createRoot(document.getElementById('root')!).render(
3232
3533
  </StrictMode>,
3233
3534
  );
3234
3535
  `;
3235
- writeFileSync12(join23(srcDir, "main.tsx"), mainTsx);
3236
- const appTsx = `import { css } from '@decantr/css';
3536
+ writeFileSync12(join24(srcDir, "main.tsx"), mainTsx);
3537
+ const appTsx = `import { css } from '@decantr/css';
3237
3538
  import { Routes, Route } from 'react-router-dom';
3238
3539
 
3239
3540
  function WelcomePage() {
@@ -3267,137 +3568,105 @@ export function App() {
3267
3568
  );
3268
3569
  }
3269
3570
  `;
3270
- writeFileSync12(join23(srcDir, "App.tsx"), appTsx);
3571
+ writeFileSync12(join24(srcDir, "App.tsx"), appTsx);
3572
+ writeFileSync12(join24(srcDir, "vite-env.d.ts"), '/// <reference types="vite/client" />\n');
3573
+ mkdirSync8(join24(srcDir, "styles"), { recursive: true });
3574
+ }
3575
+ };
3576
+ var BOOTSTRAP_ADAPTERS = {
3577
+ "react-vite": reactViteBootstrapAdapter
3578
+ };
3579
+ function resolveBootstrapTarget(target) {
3580
+ const normalizedTarget = (target || "react").toLowerCase();
3581
+ const platformType = "spa";
3582
+ const packAdapter = resolvePackAdapter(normalizedTarget, platformType);
3583
+ return {
3584
+ target: normalizedTarget,
3585
+ platformType,
3586
+ packAdapter,
3587
+ bootstrapAdapterId: BOOTSTRAP_ADAPTERS[packAdapter]?.id ?? null
3588
+ };
3589
+ }
3590
+ function getBootstrapAdapter(resolution) {
3591
+ if (!resolution.bootstrapAdapterId) {
3592
+ return null;
3593
+ }
3594
+ return BOOTSTRAP_ADAPTERS[resolution.bootstrapAdapterId] ?? null;
3595
+ }
3596
+ function detectRoutingMode(projectDir) {
3597
+ try {
3598
+ const essence = JSON.parse(readFileSync17(join24(projectDir, "decantr.essence.json"), "utf-8"));
3599
+ const routing = essence.meta?.platform?.routing;
3600
+ if (routing === "history" || routing === "pathname") {
3601
+ return routing;
3602
+ }
3603
+ return "hash";
3604
+ } catch {
3605
+ return "hash";
3606
+ }
3607
+ }
3608
+
3609
+ // src/commands/new-project.ts
3610
+ var BOLD5 = "\x1B[1m";
3611
+ var DIM12 = "\x1B[2m";
3612
+ var RESET12 = "\x1B[0m";
3613
+ var RED10 = "\x1B[31m";
3614
+ var GREEN12 = "\x1B[32m";
3615
+ var CYAN6 = "\x1B[36m";
3616
+ var YELLOW8 = "\x1B[33m";
3617
+ function heading(text) {
3618
+ return `
3619
+ ${BOLD5}${text}${RESET12}
3620
+ `;
3621
+ }
3622
+ function success2(text) {
3623
+ return `${GREEN12}${text}${RESET12}`;
3624
+ }
3625
+ function error2(text) {
3626
+ return `${RED10}${text}${RESET12}`;
3271
3627
  }
3272
- function getTargetRoutingMode(target) {
3273
- return (target || "react").toLowerCase() === "nextjs" ? "pathname" : "hash";
3628
+ function dim2(text) {
3629
+ return `${DIM12}${text}${RESET12}`;
3630
+ }
3631
+ function cyan2(text) {
3632
+ return `${CYAN6}${text}${RESET12}`;
3274
3633
  }
3275
3634
  async function cmdNewProject(projectName, options) {
3276
3635
  const workspaceRoot = process.cwd();
3277
3636
  const projectDir = resolve2(workspaceRoot, projectName);
3637
+ const bootstrapTarget = resolveBootstrapTarget(options.target);
3638
+ const bootstrapAdapter = getBootstrapAdapter(bootstrapTarget);
3639
+ const hasRunnableBootstrap = Boolean(bootstrapAdapter);
3278
3640
  if (!/^[a-z0-9][a-z0-9._-]*$/i.test(projectName)) {
3279
3641
  console.error(error2("Invalid project name. Use alphanumeric characters, hyphens, dots, or underscores."));
3280
3642
  process.exitCode = 1;
3281
3643
  return;
3282
3644
  }
3283
- if (existsSync23(projectDir)) {
3645
+ if (existsSync24(projectDir)) {
3284
3646
  console.error(error2(`Directory "${projectName}" already exists.`));
3285
3647
  process.exitCode = 1;
3286
3648
  return;
3287
3649
  }
3288
3650
  console.log(heading(`Creating ${projectName}...`));
3289
- mkdirSync8(projectDir, { recursive: true });
3651
+ mkdirSync9(projectDir, { recursive: true });
3290
3652
  console.log(dim2(` Created ${projectName}/`));
3291
- const packageJson = {
3292
- name: projectName,
3293
- private: true,
3294
- version: "0.0.0",
3295
- type: "module",
3296
- scripts: {
3297
- dev: "vite",
3298
- build: "tsc -b && vite build",
3299
- preview: "vite preview"
3300
- },
3301
- dependencies: {
3302
- "react": "^19.0.0",
3303
- "react-dom": "^19.0.0",
3304
- "react-router-dom": "^7.0.0",
3305
- "@decantr/css": "^1.0.0"
3306
- },
3307
- devDependencies: {
3308
- "@types/react": "^19.0.0",
3309
- "@types/react-dom": "^19.0.0",
3310
- "@vitejs/plugin-react": "^4.0.0",
3311
- "typescript": "^5.7.0",
3312
- "vite": "^6.0.0"
3313
- }
3314
- };
3315
- writeFileSync12(join23(projectDir, "package.json"), JSON.stringify(packageJson, null, 2) + "\n");
3316
- console.log(dim2(" Created package.json"));
3317
- const viteConfig = `import { defineConfig } from 'vite';
3318
- import react from '@vitejs/plugin-react';
3319
-
3320
- export default defineConfig({
3321
- plugins: [react()],
3322
- });
3323
- `;
3324
- writeFileSync12(join23(projectDir, "vite.config.ts"), viteConfig);
3325
- console.log(dim2(" Created vite.config.ts"));
3326
- const tsconfig = {
3327
- compilerOptions: {
3328
- target: "ES2020",
3329
- useDefineForClassFields: true,
3330
- lib: ["ES2020", "DOM", "DOM.Iterable"],
3331
- module: "ESNext",
3332
- skipLibCheck: true,
3333
- moduleResolution: "bundler",
3334
- allowImportingTsExtensions: true,
3335
- isolatedModules: true,
3336
- moduleDetection: "force",
3337
- noEmit: true,
3338
- jsx: "react-jsx",
3339
- strict: true,
3340
- noUnusedLocals: true,
3341
- noUnusedParameters: true,
3342
- noFallthroughCasesInSwitch: true,
3343
- noUncheckedSideEffectImports: true
3344
- },
3345
- include: ["src"]
3346
- };
3347
- writeFileSync12(join23(projectDir, "tsconfig.json"), JSON.stringify(tsconfig, null, 2) + "\n");
3348
- const tsconfigApp = {
3349
- compilerOptions: {
3350
- tsBuildInfoFile: "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
3351
- target: "ES2020",
3352
- useDefineForClassFields: true,
3353
- lib: ["ES2020", "DOM", "DOM.Iterable"],
3354
- module: "ESNext",
3355
- skipLibCheck: true,
3356
- moduleResolution: "bundler",
3357
- allowImportingTsExtensions: true,
3358
- isolatedModules: true,
3359
- moduleDetection: "force",
3360
- noEmit: true,
3361
- jsx: "react-jsx",
3362
- strict: true,
3363
- noUnusedLocals: true,
3364
- noUnusedParameters: true,
3365
- noFallthroughCasesInSwitch: true,
3366
- noUncheckedSideEffectImports: true
3367
- },
3368
- include: ["src"]
3369
- };
3370
- writeFileSync12(join23(projectDir, "tsconfig.app.json"), JSON.stringify(tsconfigApp, null, 2) + "\n");
3371
- console.log(dim2(" Created tsconfig.json"));
3372
3653
  const title = projectName.replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
3373
- const indexHtml = `<!doctype html>
3374
- <html lang="en">
3375
- <head>
3376
- <meta charset="UTF-8" />
3377
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
3378
- <title>${title}</title>
3379
- </head>
3380
- <body>
3381
- <div id="root"></div>
3382
- <script type="module" src="/src/main.tsx"></script>
3383
- </body>
3384
- </html>
3385
- `;
3386
- writeFileSync12(join23(projectDir, "index.html"), indexHtml);
3387
- console.log(dim2(" Created index.html"));
3388
- const srcDir = join23(projectDir, "src");
3389
- mkdirSync8(srcDir, { recursive: true });
3390
- writeStarterRuntimeFiles(projectDir, title, getTargetRoutingMode(options.target));
3391
- writeFileSync12(join23(srcDir, "vite-env.d.ts"), '/// <reference types="vite/client" />\n');
3392
- mkdirSync8(join23(srcDir, "styles"), { recursive: true });
3393
- console.log(dim2(" Created src/"));
3394
- console.log(heading("Installing dependencies..."));
3654
+ if (bootstrapAdapter) {
3655
+ bootstrapAdapter.writeProjectFiles(projectDir, title, "hash");
3656
+ console.log(dim2(` Bootstrapped ${bootstrapAdapter.label}`));
3657
+ } else {
3658
+ console.log(`${YELLOW8} No greenfield bootstrap adapter is available yet for target "${bootstrapTarget.target}" (${bootstrapTarget.packAdapter}).${RESET12}`);
3659
+ console.log(dim2(" Continuing with a contract-only Decantr workspace so the command stays target-honest instead of writing the wrong runtime."));
3660
+ }
3395
3661
  const packageManager = detectPackageManager();
3396
- try {
3397
- execSync(`${packageManager} install`, { cwd: projectDir, stdio: "inherit" });
3398
- } catch {
3399
- console.log(`
3662
+ if (hasRunnableBootstrap) {
3663
+ console.log(heading("Installing dependencies..."));
3664
+ try {
3665
+ execSync(`${packageManager} install`, { cwd: projectDir, stdio: "inherit" });
3666
+ } catch {
3667
+ console.log(`
3400
3668
  ${YELLOW8}Dependency install failed. Run \`${packageManager} install\` manually.${RESET12}`);
3669
+ }
3401
3670
  }
3402
3671
  const requiresOfflineContent = Boolean(options.offline && (options.blueprint || options.archetype));
3403
3672
  const seeded = options.offline ? seedOfflineRegistry(projectDir, workspaceRoot) : { seeded: false, strategy: null };
@@ -3429,10 +3698,12 @@ ${YELLOW8}Dependency install failed. Run \`${packageManager} install\` manually.
3429
3698
  if (options.registry) initFlags.push(`--registry=${options.registry}`);
3430
3699
  try {
3431
3700
  const bundledCliEntrypoint = fileURLToPath(new URL("./bin.js", import.meta.url));
3432
- const cliEntrypoint = existsSync23(bundledCliEntrypoint) ? bundledCliEntrypoint : process.argv[1] && existsSync23(process.argv[1]) ? process.argv[1] : null;
3701
+ const cliEntrypoint = existsSync24(bundledCliEntrypoint) ? bundledCliEntrypoint : process.argv[1] && existsSync24(process.argv[1]) ? process.argv[1] : null;
3433
3702
  const cliPath = cliEntrypoint ? `"${process.execPath}" "${cliEntrypoint}"` : "npx decantr";
3434
3703
  execSync(`${cliPath} init ${initFlags.join(" ")}`, { cwd: projectDir, stdio: "inherit" });
3435
- writeStarterRuntimeFiles(projectDir, title, detectRoutingMode(projectDir));
3704
+ if (bootstrapAdapter) {
3705
+ bootstrapAdapter.writeProjectFiles(projectDir, title, detectRoutingMode(projectDir));
3706
+ }
3436
3707
  } catch {
3437
3708
  console.log(`
3438
3709
  ${YELLOW8}Decantr init encountered issues. Run \`decantr init\` manually inside ${projectName}/.${RESET12}`);
@@ -3441,17 +3712,21 @@ ${YELLOW8}Decantr init encountered issues. Run \`decantr init\` manually inside
3441
3712
  \u2713 Project "${projectName}" created!
3442
3713
  `));
3443
3714
  console.log(` ${cyan2("cd " + projectName)}`);
3444
- console.log(` ${cyan2(packageManager + " run dev")}`);
3715
+ if (bootstrapAdapter) {
3716
+ console.log(` ${cyan2(packageManager + " run dev")}`);
3717
+ } else {
3718
+ console.log(dim2(` Contract-only mode for target ${bootstrapTarget.target}. Bring your own runtime, or rerun ${cyan2(`decantr new ${projectName} --target=react`)} for the current starter adapter.`));
3719
+ }
3445
3720
  console.log("");
3446
3721
  }
3447
3722
  function detectPackageManager() {
3448
- if (existsSync23(join23(process.cwd(), "pnpm-lock.yaml")) || existsSync23(join23(process.cwd(), "pnpm-workspace.yaml"))) {
3723
+ if (existsSync24(join25(process.cwd(), "pnpm-lock.yaml")) || existsSync24(join25(process.cwd(), "pnpm-workspace.yaml"))) {
3449
3724
  return "pnpm";
3450
3725
  }
3451
- if (existsSync23(join23(process.cwd(), "yarn.lock"))) {
3726
+ if (existsSync24(join25(process.cwd(), "yarn.lock"))) {
3452
3727
  return "yarn";
3453
3728
  }
3454
- if (existsSync23(join23(process.cwd(), "bun.lockb")) || existsSync23(join23(process.cwd(), "bun.lock"))) {
3729
+ if (existsSync24(join25(process.cwd(), "bun.lockb")) || existsSync24(join25(process.cwd(), "bun.lock"))) {
3455
3730
  return "bun";
3456
3731
  }
3457
3732
  return "npm";
@@ -3551,9 +3826,11 @@ function extractPatternName(item) {
3551
3826
  }
3552
3827
  return "custom";
3553
3828
  }
3554
- function generateCuratedPrompt(ctx) {
3829
+ function generateGreenfieldPrompt(ctx) {
3555
3830
  const lines = [];
3556
- lines.push("Build this application using the Decantr design system.");
3831
+ lines.push("Build this greenfield application using the Decantr design system.");
3832
+ lines.push("");
3833
+ lines.push("This workspace is a new Decantr scaffold. Use the contract to create or extend the runtime deliberately, not to reverse-engineer a hidden starter.");
3557
3834
  lines.push("");
3558
3835
  lines.push("Treat the compiled execution-pack files as the primary source of truth.");
3559
3836
  lines.push("Use narrative docs only as secondary explanation when the compiled packs are not enough.");
@@ -3572,6 +3849,12 @@ function generateCuratedPrompt(ctx) {
3572
3849
  lines.push("- Start with the shell layouts and route structure first, then build section pages route by route.");
3573
3850
  lines.push("- Import src/styles/global.css, src/styles/tokens.css, and src/styles/treatments.css.");
3574
3851
  lines.push("- Use the existing Decantr tokens, treatments, and decorators instead of inventing a new visual system.");
3852
+ lines.push("- If package.json, app entry files, or router/runtime files are absent, create them explicitly for the declared target instead of assuming a hidden starter already exists in the workspace.");
3853
+ lines.push("- Do not use inline visual style values or component-scoped <style> tags as the primary styling path. Colors, spacing, borders, shadows, gradients, and transitions should come from atoms, treatments, decorators, or CSS variables. Inline styles are only acceptable for truly dynamic geometry that cannot be expressed through the contract.");
3854
+ lines.push("- Let shells own spacing, centering, and scroll containers. Pages should not duplicate shell responsibilities with extra full-height wrappers, max-width wrappers, or page-local padding unless the route contract explicitly requires it.");
3855
+ lines.push("- If command_palette or hotkeys are declared in the generated context, implement them as real features. Do not merely acknowledge them in copy or comments.");
3856
+ lines.push("- Treat declared hotkeys as interaction bindings by default, not visible navigation label text, unless the shell or route contract explicitly calls for shown shortcut hints.");
3857
+ lines.push("- If a required decorator class is referenced in the contract but missing from generated CSS, report the contract gap instead of inventing a parallel visual system.");
3575
3858
  lines.push("- Do not modify generated context files unless the task is explicitly to regenerate or refresh Decantr context.");
3576
3859
  lines.push("");
3577
3860
  lines.push("Execution flow:");
@@ -3581,6 +3864,50 @@ function generateCuratedPrompt(ctx) {
3581
3864
  lines.push("- If a required context file is missing or inconsistent, stop and report exactly which file is missing before continuing.");
3582
3865
  return lines.join("\n");
3583
3866
  }
3867
+ function generateBrownfieldPrompt(ctx) {
3868
+ const lines = [];
3869
+ lines.push("Attach Decantr to this existing application without rebuilding it from scratch.");
3870
+ lines.push("");
3871
+ lines.push("Preserve the current framework, package manager, router, build tooling, and working runtime structure unless the generated Decantr contract gives you a reviewed reason to change them.");
3872
+ lines.push("");
3873
+ lines.push("Treat .decantr/analysis.json as the factual inventory of the current app.");
3874
+ lines.push("Treat .decantr/init-seed.json as the recommended Decantr attach defaults.");
3875
+ lines.push("Treat the compiled execution-pack files as the Decantr contract you are layering onto the app.");
3876
+ lines.push("Use only files present in this workspace as the source of truth. If the runtime and contract disagree, call out the drift explicitly instead of improvising a rewrite.");
3877
+ lines.push("");
3878
+ lines.push("Read in this order:");
3879
+ lines.push("1. .decantr/analysis.json for the detected framework, routes, styling, layout, and dependencies.");
3880
+ lines.push("2. .decantr/init-seed.json for the intended attach defaults and workflow lane.");
3881
+ lines.push("3. DECANTR.md for guard rules, CSS expectations, and Decantr operating rules.");
3882
+ lines.push("4. .decantr/context/scaffold-pack.md for the compact compiled shell, theme, feature, and route contract.");
3883
+ lines.push("5. .decantr/context/scaffold.md for broader topology, route map, and voice guidance.");
3884
+ lines.push("6. The matching section and page pack files only when you are working on those specific surfaces.");
3885
+ lines.push("");
3886
+ lines.push("Implementation rules:");
3887
+ lines.push("- Preserve existing files and working flows whenever possible. Prefer incremental attachment over whole-app rewrites.");
3888
+ lines.push("- Map existing routes and components onto the declared Decantr sections/pages before creating new files.");
3889
+ lines.push("- If package.json, router files, or style files already exist, extend them deliberately instead of replacing them with a different starter shape.");
3890
+ lines.push("- If Decantr style files are absent, add src/styles/global.css, src/styles/tokens.css, and src/styles/treatments.css in a way that fits the current app structure.");
3891
+ lines.push("- Use the existing Decantr tokens, treatments, and decorators instead of inventing a parallel visual system.");
3892
+ lines.push("- Registry content is optional in this workflow unless the task explicitly asks for blueprint/theme/pattern enrichment.");
3893
+ lines.push("- Do not invent routes, sections, shells, themes, or features that are not present in the compiled packs.");
3894
+ lines.push("- Do not use inline visual style values or component-scoped <style> tags as the primary styling path. Colors, spacing, borders, shadows, gradients, and transitions should come from atoms, treatments, decorators, or CSS variables.");
3895
+ lines.push("- Let shells own spacing, centering, and scroll containers. Preserve app structure, but remove duplicated shell responsibilities when the contract makes them explicit.");
3896
+ lines.push("- If command_palette or hotkeys are declared in the generated context, implement them as real features.");
3897
+ lines.push("- If a required decorator class is referenced in the contract but missing from generated CSS, report the contract gap instead of inventing a parallel visual system.");
3898
+ lines.push("- Do not modify generated context files unless the task is explicitly to regenerate or refresh Decantr context.");
3899
+ lines.push("");
3900
+ lines.push("Execution flow:");
3901
+ lines.push("- Start by inventorying the current runtime and identifying the safest route/component anchors for attachment.");
3902
+ lines.push("- Align the shared shell and route structure incrementally instead of replacing the app shell wholesale.");
3903
+ lines.push("- Then attach or refine section pages using the matching section and page packs.");
3904
+ lines.push("- After implementation, run decantr check and decantr audit and fix contract or drift issues.");
3905
+ lines.push("- If a required context file or runtime anchor is missing, stop and report exactly what is missing before continuing.");
3906
+ return lines.join("\n");
3907
+ }
3908
+ function generateCuratedPrompt(ctx) {
3909
+ return ctx.workflow === "brownfield-attach" ? generateBrownfieldPrompt(ctx) : generateGreenfieldPrompt(ctx);
3910
+ }
3584
3911
  function getAPIClient() {
3585
3912
  return new RegistryAPIClient3({
3586
3913
  baseUrl: process.env.DECANTR_API_URL || void 0,
@@ -3606,18 +3933,18 @@ function extractHostedAssetPaths(indexHtml) {
3606
3933
  return [...assetPaths];
3607
3934
  }
3608
3935
  function readHostedDistSnapshot(distPath) {
3609
- const resolvedDistPath = distPath ? resolveUserPath(distPath) : join24(process.cwd(), "dist");
3610
- const indexPath = join24(resolvedDistPath, "index.html");
3611
- if (!existsSync24(indexPath)) {
3936
+ const resolvedDistPath = distPath ? resolveUserPath(distPath) : join26(process.cwd(), "dist");
3937
+ const indexPath = join26(resolvedDistPath, "index.html");
3938
+ if (!existsSync25(indexPath)) {
3612
3939
  return void 0;
3613
3940
  }
3614
- const indexHtml = readFileSync16(indexPath, "utf-8");
3941
+ const indexHtml = readFileSync18(indexPath, "utf-8");
3615
3942
  const assetPaths = extractHostedAssetPaths(indexHtml);
3616
3943
  const assets = {};
3617
3944
  for (const assetPath of assetPaths) {
3618
- const assetFilePath = join24(resolvedDistPath, assetPath.replace(/^[/\\]+/, ""));
3619
- if (existsSync24(assetFilePath)) {
3620
- assets[assetPath] = readFileSync16(assetFilePath, "utf-8");
3945
+ const assetFilePath = join26(resolvedDistPath, assetPath.replace(/^[/\\]+/, ""));
3946
+ if (existsSync25(assetFilePath)) {
3947
+ assets[assetPath] = readFileSync18(assetFilePath, "utf-8");
3621
3948
  }
3622
3949
  }
3623
3950
  return {
@@ -3632,24 +3959,24 @@ function isHostedSourceSnapshotFile(path) {
3632
3959
  function readHostedSourceSnapshot(sourcePath) {
3633
3960
  if (!sourcePath) return void 0;
3634
3961
  const resolvedSourcePath = resolveUserPath(sourcePath);
3635
- if (!existsSync24(resolvedSourcePath)) {
3962
+ if (!existsSync25(resolvedSourcePath)) {
3636
3963
  return void 0;
3637
3964
  }
3638
3965
  const files = {};
3639
3966
  const ignoredDirNames = /* @__PURE__ */ new Set(["node_modules", ".git", ".decantr", "dist", "build", "coverage"]);
3640
- const rootPrefix = basename(resolvedSourcePath);
3967
+ const rootPrefix = basename2(resolvedSourcePath);
3641
3968
  const walk = (absoluteDir, relativeDir) => {
3642
3969
  for (const entry of readdirSync6(absoluteDir, { withFileTypes: true })) {
3643
3970
  if (ignoredDirNames.has(entry.name)) continue;
3644
- const absolutePath = join24(absoluteDir, entry.name);
3645
- const relativePath = join24(relativeDir, entry.name).replace(/\\/g, "/");
3971
+ const absolutePath = join26(absoluteDir, entry.name);
3972
+ const relativePath = join26(relativeDir, entry.name).replace(/\\/g, "/");
3646
3973
  if (entry.isDirectory()) {
3647
3974
  walk(absolutePath, relativePath);
3648
3975
  continue;
3649
3976
  }
3650
3977
  if (!entry.isFile()) continue;
3651
3978
  if (!isHostedSourceSnapshotFile(relativePath)) continue;
3652
- files[relativePath] = readFileSync16(absolutePath, "utf-8");
3979
+ files[relativePath] = readFileSync18(absolutePath, "utf-8");
3653
3980
  }
3654
3981
  };
3655
3982
  walk(resolvedSourcePath, rootPrefix);
@@ -3766,19 +4093,19 @@ async function printRegistryIntelligenceSummary(namespace, jsonOutput = false) {
3766
4093
  }
3767
4094
  async function printHostedExecutionPackBundle(essencePath, namespace, jsonOutput = false, writeContext = false) {
3768
4095
  const client = getPublicAPIClient();
3769
- const resolvedPath = essencePath ? resolveUserPath(essencePath) : join24(process.cwd(), "decantr.essence.json");
3770
- if (!existsSync24(resolvedPath)) {
4096
+ const resolvedPath = essencePath ? resolveUserPath(essencePath) : join26(process.cwd(), "decantr.essence.json");
4097
+ if (!existsSync25(resolvedPath)) {
3771
4098
  throw new Error(`Essence file not found at ${resolvedPath}`);
3772
4099
  }
3773
- const essence = JSON.parse(readFileSync16(resolvedPath, "utf-8"));
4100
+ const essence = JSON.parse(readFileSync18(resolvedPath, "utf-8"));
3774
4101
  const bundle = await client.compileExecutionPacks(
3775
4102
  essence,
3776
4103
  namespace ? { namespace } : void 0
3777
4104
  );
3778
4105
  let writtenContextPaths = [];
3779
4106
  if (writeContext) {
3780
- const contextDir = join24(process.cwd(), ".decantr", "context");
3781
- mkdirSync9(contextDir, { recursive: true });
4107
+ const contextDir = join26(process.cwd(), ".decantr", "context");
4108
+ mkdirSync10(contextDir, { recursive: true });
3782
4109
  const written = writeExecutionPackBundleArtifacts(
3783
4110
  contextDir,
3784
4111
  bundle
@@ -3801,7 +4128,7 @@ async function printHostedExecutionPackBundle(essencePath, namespace, jsonOutput
3801
4128
  console.log(` Sections: ${typedBundle.sections.length}`);
3802
4129
  console.log(` Mutations: ${typedBundle.mutations.length}`);
3803
4130
  if (writeContext) {
3804
- console.log(` Context bundle: ${join24(process.cwd(), ".decantr", "context")}`);
4131
+ console.log(` Context bundle: ${join26(process.cwd(), ".decantr", "context")}`);
3805
4132
  console.log(` Files written: ${writtenContextPaths.length}`);
3806
4133
  }
3807
4134
  console.log("");
@@ -3813,14 +4140,14 @@ async function printHostedExecutionPackBundle(essencePath, namespace, jsonOutput
3813
4140
  }
3814
4141
  async function printHostedSelectedExecutionPack(packType, id, essencePath, namespace, jsonOutput = false, writeContext = false) {
3815
4142
  const client = getPublicAPIClient();
3816
- const resolvedPath = essencePath ? resolveUserPath(essencePath) : join24(process.cwd(), "decantr.essence.json");
3817
- if (!existsSync24(resolvedPath)) {
4143
+ const resolvedPath = essencePath ? resolveUserPath(essencePath) : join26(process.cwd(), "decantr.essence.json");
4144
+ if (!existsSync25(resolvedPath)) {
3818
4145
  throw new Error(`Essence file not found at ${resolvedPath}`);
3819
4146
  }
3820
4147
  if ((packType === "section" || packType === "page" || packType === "mutation") && !id) {
3821
4148
  throw new Error(`Pack type "${packType}" requires an id.`);
3822
4149
  }
3823
- const essence = JSON.parse(readFileSync16(resolvedPath, "utf-8"));
4150
+ const essence = JSON.parse(readFileSync18(resolvedPath, "utf-8"));
3824
4151
  const selected = await client.selectExecutionPack(
3825
4152
  {
3826
4153
  essence,
@@ -3831,14 +4158,14 @@ async function printHostedSelectedExecutionPack(packType, id, essencePath, names
3831
4158
  );
3832
4159
  let writtenContextDir = null;
3833
4160
  if (writeContext) {
3834
- const contextDir = join24(process.cwd(), ".decantr", "context");
3835
- mkdirSync9(contextDir, { recursive: true });
3836
- writeFileSync13(join24(contextDir, "pack-manifest.json"), JSON.stringify(selected.manifest, null, 2) + "\n");
4161
+ const contextDir = join26(process.cwd(), ".decantr", "context");
4162
+ mkdirSync10(contextDir, { recursive: true });
4163
+ writeFileSync13(join26(contextDir, "pack-manifest.json"), JSON.stringify(selected.manifest, null, 2) + "\n");
3837
4164
  const manifestEntry = selected.selector.packType === "scaffold" ? selected.manifest.scaffold : selected.selector.packType === "review" ? selected.manifest.review : selected.selector.packType === "section" ? selected.manifest.sections.find((entry) => entry.id === selected.selector.id) : selected.selector.packType === "page" ? selected.manifest.pages.find((entry) => entry.id === selected.selector.id) : selected.manifest.mutations.find((entry) => entry.id === selected.selector.id);
3838
4165
  const markdownFile = manifestEntry?.markdown ?? `${selected.selector.packType}${selected.selector.id ? `-${selected.selector.id}` : ""}-pack.md`;
3839
4166
  const jsonFile = manifestEntry?.json ?? `${selected.selector.packType}${selected.selector.id ? `-${selected.selector.id}` : ""}-pack.json`;
3840
- writeFileSync13(join24(contextDir, markdownFile), selected.pack.renderedMarkdown);
3841
- writeFileSync13(join24(contextDir, jsonFile), JSON.stringify(selected.pack, null, 2) + "\n");
4167
+ writeFileSync13(join26(contextDir, markdownFile), selected.pack.renderedMarkdown);
4168
+ writeFileSync13(join26(contextDir, jsonFile), JSON.stringify(selected.pack, null, 2) + "\n");
3842
4169
  writtenContextDir = contextDir;
3843
4170
  }
3844
4171
  if (jsonOutput) {
@@ -3863,20 +4190,20 @@ async function printHostedSelectedExecutionPack(packType, id, essencePath, names
3863
4190
  }
3864
4191
  async function printHostedExecutionPackManifest(essencePath, namespace, jsonOutput = false, writeContext = false) {
3865
4192
  const client = getPublicAPIClient();
3866
- const resolvedPath = essencePath ? resolveUserPath(essencePath) : join24(process.cwd(), "decantr.essence.json");
3867
- if (!existsSync24(resolvedPath)) {
4193
+ const resolvedPath = essencePath ? resolveUserPath(essencePath) : join26(process.cwd(), "decantr.essence.json");
4194
+ if (!existsSync25(resolvedPath)) {
3868
4195
  throw new Error(`Essence file not found at ${resolvedPath}`);
3869
4196
  }
3870
- const essence = JSON.parse(readFileSync16(resolvedPath, "utf-8"));
4197
+ const essence = JSON.parse(readFileSync18(resolvedPath, "utf-8"));
3871
4198
  const manifest = await client.getExecutionPackManifest(
3872
4199
  essence,
3873
4200
  namespace ? { namespace } : void 0
3874
4201
  );
3875
4202
  let writtenContextDir = null;
3876
4203
  if (writeContext) {
3877
- const contextDir = join24(process.cwd(), ".decantr", "context");
3878
- mkdirSync9(contextDir, { recursive: true });
3879
- writeFileSync13(join24(contextDir, "pack-manifest.json"), JSON.stringify(manifest, null, 2) + "\n");
4204
+ const contextDir = join26(process.cwd(), ".decantr", "context");
4205
+ mkdirSync10(contextDir, { recursive: true });
4206
+ writeFileSync13(join26(contextDir, "pack-manifest.json"), JSON.stringify(manifest, null, 2) + "\n");
3880
4207
  writtenContextDir = contextDir;
3881
4208
  }
3882
4209
  if (jsonOutput) {
@@ -3897,14 +4224,14 @@ async function printHostedExecutionPackManifest(essencePath, namespace, jsonOutp
3897
4224
  }
3898
4225
  }
3899
4226
  async function hydrateHostedExecutionPacksIfMissing(projectRoot, namespace = "@official") {
3900
- const contextDir = join24(projectRoot, ".decantr", "context");
3901
- const reviewPackPath = join24(contextDir, "review-pack.json");
3902
- const manifestPath = join24(contextDir, "pack-manifest.json");
3903
- if (existsSync24(reviewPackPath) && existsSync24(manifestPath)) {
4227
+ const contextDir = join26(projectRoot, ".decantr", "context");
4228
+ const reviewPackPath = join26(contextDir, "review-pack.json");
4229
+ const manifestPath = join26(contextDir, "pack-manifest.json");
4230
+ if (existsSync25(reviewPackPath) && existsSync25(manifestPath)) {
3904
4231
  return { attempted: false, hydrated: false };
3905
4232
  }
3906
- const essencePath = join24(projectRoot, "decantr.essence.json");
3907
- if (!existsSync24(essencePath)) {
4233
+ const essencePath = join26(projectRoot, "decantr.essence.json");
4234
+ if (!existsSync25(essencePath)) {
3908
4235
  return { attempted: false, hydrated: false };
3909
4236
  }
3910
4237
  const reviewHydration = await hydrateHostedReviewPackIfMissing(projectRoot, namespace);
@@ -3913,9 +4240,9 @@ async function hydrateHostedExecutionPacksIfMissing(projectRoot, namespace = "@o
3913
4240
  }
3914
4241
  try {
3915
4242
  const client = getPublicAPIClient();
3916
- const essence = JSON.parse(readFileSync16(essencePath, "utf-8"));
4243
+ const essence = JSON.parse(readFileSync18(essencePath, "utf-8"));
3917
4244
  const bundle = await client.compileExecutionPacks(essence, { namespace });
3918
- mkdirSync9(contextDir, { recursive: true });
4245
+ mkdirSync10(contextDir, { recursive: true });
3919
4246
  writeExecutionPackBundleArtifacts(
3920
4247
  contextDir,
3921
4248
  bundle
@@ -3926,19 +4253,19 @@ async function hydrateHostedExecutionPacksIfMissing(projectRoot, namespace = "@o
3926
4253
  }
3927
4254
  }
3928
4255
  async function hydrateHostedReviewPackIfMissing(projectRoot, namespace = "@official") {
3929
- const contextDir = join24(projectRoot, ".decantr", "context");
3930
- const reviewPackPath = join24(contextDir, "review-pack.json");
3931
- const manifestPath = join24(contextDir, "pack-manifest.json");
3932
- if (existsSync24(reviewPackPath) && existsSync24(manifestPath)) {
4256
+ const contextDir = join26(projectRoot, ".decantr", "context");
4257
+ const reviewPackPath = join26(contextDir, "review-pack.json");
4258
+ const manifestPath = join26(contextDir, "pack-manifest.json");
4259
+ if (existsSync25(reviewPackPath) && existsSync25(manifestPath)) {
3933
4260
  return { attempted: false, hydrated: false };
3934
4261
  }
3935
- const essencePath = join24(projectRoot, "decantr.essence.json");
3936
- if (!existsSync24(essencePath)) {
4262
+ const essencePath = join26(projectRoot, "decantr.essence.json");
4263
+ if (!existsSync25(essencePath)) {
3937
4264
  return { attempted: false, hydrated: false };
3938
4265
  }
3939
4266
  try {
3940
4267
  const client = getPublicAPIClient();
3941
- const essence = JSON.parse(readFileSync16(essencePath, "utf-8"));
4268
+ const essence = JSON.parse(readFileSync18(essencePath, "utf-8"));
3942
4269
  const selected = await client.selectExecutionPack(
3943
4270
  {
3944
4271
  essence,
@@ -3946,10 +4273,10 @@ async function hydrateHostedReviewPackIfMissing(projectRoot, namespace = "@offic
3946
4273
  },
3947
4274
  { namespace }
3948
4275
  );
3949
- mkdirSync9(contextDir, { recursive: true });
3950
- writeFileSync13(join24(contextDir, "review-pack.md"), selected.pack.renderedMarkdown);
3951
- writeFileSync13(join24(contextDir, "review-pack.json"), JSON.stringify(selected.pack, null, 2) + "\n");
3952
- if (!existsSync24(manifestPath)) {
4276
+ mkdirSync10(contextDir, { recursive: true });
4277
+ writeFileSync13(join26(contextDir, "review-pack.md"), selected.pack.renderedMarkdown);
4278
+ writeFileSync13(join26(contextDir, "review-pack.json"), JSON.stringify(selected.pack, null, 2) + "\n");
4279
+ if (!existsSync25(manifestPath)) {
3953
4280
  writeFileSync13(manifestPath, JSON.stringify(selected.manifest, null, 2) + "\n");
3954
4281
  }
3955
4282
  return { attempted: true, hydrated: true, scope: "review" };
@@ -3960,17 +4287,17 @@ async function hydrateHostedReviewPackIfMissing(projectRoot, namespace = "@offic
3960
4287
  async function printHostedFileCritique(sourcePath, namespace, jsonOutput = false, essencePath, treatmentsPath) {
3961
4288
  const client = getPublicAPIClient();
3962
4289
  const resolvedSourcePath = resolveUserPath(sourcePath);
3963
- const resolvedEssencePath = essencePath ? resolveUserPath(essencePath) : join24(process.cwd(), "decantr.essence.json");
3964
- const resolvedTreatmentsPath = treatmentsPath ? resolveUserPath(treatmentsPath) : join24(process.cwd(), "src", "styles", "treatments.css");
3965
- if (!existsSync24(resolvedSourcePath)) {
4290
+ const resolvedEssencePath = essencePath ? resolveUserPath(essencePath) : join26(process.cwd(), "decantr.essence.json");
4291
+ const resolvedTreatmentsPath = treatmentsPath ? resolveUserPath(treatmentsPath) : join26(process.cwd(), "src", "styles", "treatments.css");
4292
+ if (!existsSync25(resolvedSourcePath)) {
3966
4293
  throw new Error(`Source file not found at ${resolvedSourcePath}`);
3967
4294
  }
3968
- if (!existsSync24(resolvedEssencePath)) {
4295
+ if (!existsSync25(resolvedEssencePath)) {
3969
4296
  throw new Error(`Essence file not found at ${resolvedEssencePath}`);
3970
4297
  }
3971
- const code = readFileSync16(resolvedSourcePath, "utf-8");
3972
- const essence = JSON.parse(readFileSync16(resolvedEssencePath, "utf-8"));
3973
- const treatmentsCss = existsSync24(resolvedTreatmentsPath) ? readFileSync16(resolvedTreatmentsPath, "utf-8") : void 0;
4298
+ const code = readFileSync18(resolvedSourcePath, "utf-8");
4299
+ const essence = JSON.parse(readFileSync18(resolvedEssencePath, "utf-8"));
4300
+ const treatmentsCss = existsSync25(resolvedTreatmentsPath) ? readFileSync18(resolvedTreatmentsPath, "utf-8") : void 0;
3974
4301
  const report = await client.critiqueFile(
3975
4302
  {
3976
4303
  essence,
@@ -3994,11 +4321,11 @@ async function printHostedFileCritique(sourcePath, namespace, jsonOutput = false
3994
4321
  }
3995
4322
  async function printHostedProjectAudit(namespace, jsonOutput = false, essencePath, distPath, sourcesPath) {
3996
4323
  const client = getPublicAPIClient();
3997
- const resolvedEssencePath = essencePath ? resolveUserPath(essencePath) : join24(process.cwd(), "decantr.essence.json");
3998
- if (!existsSync24(resolvedEssencePath)) {
4324
+ const resolvedEssencePath = essencePath ? resolveUserPath(essencePath) : join26(process.cwd(), "decantr.essence.json");
4325
+ if (!existsSync25(resolvedEssencePath)) {
3999
4326
  throw new Error(`Essence file not found at ${resolvedEssencePath}`);
4000
4327
  }
4001
- const essence = JSON.parse(readFileSync16(resolvedEssencePath, "utf-8"));
4328
+ const essence = JSON.parse(readFileSync18(resolvedEssencePath, "utf-8"));
4002
4329
  const dist = readHostedDistSnapshot(distPath);
4003
4330
  const sources = readHostedSourceSnapshot(sourcesPath);
4004
4331
  const report = await client.auditProject(
@@ -4015,7 +4342,7 @@ async function printHostedProjectAudit(namespace, jsonOutput = false, essencePat
4015
4342
  }
4016
4343
  console.log(heading2("Hosted Project Audit"));
4017
4344
  console.log(` Essence: ${resolvedEssencePath}`);
4018
- console.log(` Dist snapshot: ${dist ? distPath ? resolveUserPath(distPath) : join24(process.cwd(), "dist") : "none"}`);
4345
+ console.log(` Dist snapshot: ${dist ? distPath ? resolveUserPath(distPath) : join26(process.cwd(), "dist") : "none"}`);
4019
4346
  console.log(` Source snapshot: ${sources && sourcesPath ? resolveUserPath(sourcesPath) : "none"}`);
4020
4347
  printProjectAuditReport(report);
4021
4348
  }
@@ -4093,7 +4420,7 @@ async function cmdGet(type, id) {
4093
4420
  }
4094
4421
  const apiType = CONTENT_TYPE_TO_API_CONTENT_TYPE3[type];
4095
4422
  const registryClient = new RegistryClient({
4096
- cacheDir: join24(process.cwd(), ".decantr", "cache")
4423
+ cacheDir: join26(process.cwd(), ".decantr", "cache")
4097
4424
  });
4098
4425
  const result = await registryClient.fetchContentItem(apiType, id);
4099
4426
  if (result) {
@@ -4102,16 +4429,16 @@ async function cmdGet(type, id) {
4102
4429
  }
4103
4430
  const currentDir = dirname2(fileURLToPath2(import.meta.url));
4104
4431
  const bundledCandidates = [
4105
- join24(currentDir, "bundled", apiType, `${id}.json`),
4432
+ join26(currentDir, "bundled", apiType, `${id}.json`),
4106
4433
  // Running from src/
4107
- join24(currentDir, "..", "src", "bundled", apiType, `${id}.json`),
4434
+ join26(currentDir, "..", "src", "bundled", apiType, `${id}.json`),
4108
4435
  // Running from dist/
4109
- join24(currentDir, "..", "bundled", apiType, `${id}.json`)
4436
+ join26(currentDir, "..", "bundled", apiType, `${id}.json`)
4110
4437
  // Alternative dist layout
4111
4438
  ];
4112
- const bundledPath = bundledCandidates.find((p) => existsSync24(p)) || null;
4439
+ const bundledPath = bundledCandidates.find((p) => existsSync25(p)) || null;
4113
4440
  if (bundledPath) {
4114
- const data = JSON.parse(readFileSync16(bundledPath, "utf-8"));
4441
+ const data = JSON.parse(readFileSync18(bundledPath, "utf-8"));
4115
4442
  console.log(JSON.stringify(data, null, 2));
4116
4443
  return;
4117
4444
  }
@@ -4120,10 +4447,10 @@ async function cmdGet(type, id) {
4120
4447
  return;
4121
4448
  }
4122
4449
  async function cmdValidate(path) {
4123
- const essencePath = path || join24(process.cwd(), "decantr.essence.json");
4450
+ const essencePath = path || join26(process.cwd(), "decantr.essence.json");
4124
4451
  let raw;
4125
4452
  try {
4126
- raw = readFileSync16(essencePath, "utf-8");
4453
+ raw = readFileSync18(essencePath, "utf-8");
4127
4454
  } catch {
4128
4455
  console.error(error3(`Could not read ${essencePath}`));
4129
4456
  process.exitCode = 1;
@@ -4177,7 +4504,7 @@ async function cmdList(type, sort, recommended, intelligenceSource) {
4177
4504
  return;
4178
4505
  }
4179
4506
  const registryClient = new RegistryClient({
4180
- cacheDir: join24(process.cwd(), ".decantr", "cache")
4507
+ cacheDir: join26(process.cwd(), ".decantr", "cache")
4181
4508
  });
4182
4509
  const result = await registryClient.fetchContentList(
4183
4510
  type,
@@ -4227,6 +4554,11 @@ async function cmdInit(args) {
4227
4554
  const projectRoot = process.cwd();
4228
4555
  console.log(heading2("Decantr Project Setup"));
4229
4556
  const detected = detectProject(projectRoot);
4557
+ const workflowSeed = readBrownfieldInitSeed(projectRoot);
4558
+ const brownfieldAttach = Boolean(workflowSeed) || hasExistingProjectFootprint(detected);
4559
+ if (workflowSeed) {
4560
+ console.log(dim3(" Found .decantr/init-seed.json brownfield guidance."));
4561
+ }
4230
4562
  if (detected.existingEssence && !args.existing) {
4231
4563
  console.log(`${YELLOW9}Warning: decantr.essence.json already exists.${RESET13}`);
4232
4564
  const overwrite = await confirm("Overwrite existing configuration?", false);
@@ -4251,7 +4583,7 @@ async function cmdInit(args) {
4251
4583
  }
4252
4584
  }
4253
4585
  const registryClient = new RegistryClient({
4254
- cacheDir: join24(projectRoot, ".decantr", "cache"),
4586
+ cacheDir: join26(projectRoot, ".decantr", "cache"),
4255
4587
  apiUrl: args.registry,
4256
4588
  offline: args.offline
4257
4589
  });
@@ -4265,6 +4597,8 @@ async function cmdInit(args) {
4265
4597
  }
4266
4598
  let selectedBlueprint = "default";
4267
4599
  let registrySource = "cache";
4600
+ const preferContractOnly = brownfieldAttach && !requestedBlueprint && !requestedArchetype;
4601
+ const workflowMode = workflowSeed || brownfieldAttach && !requestedBlueprint && !requestedArchetype ? "brownfield-attach" : "greenfield-scaffold";
4268
4602
  if (args.yes) {
4269
4603
  selectedBlueprint = args.blueprint || "default";
4270
4604
  } else if (!apiAvailable) {
@@ -4299,7 +4633,7 @@ ${YELLOW9}You're offline. Scaffolding minimal Decantr project.${RESET13}`);
4299
4633
  ${YELLOW9}You're offline. Scaffolding Decantr default.${RESET13}`);
4300
4634
  console.log(dim3("Run `decantr upgrade` when online, or visit decantr.ai/registry\n"));
4301
4635
  selectedBlueprint = "default";
4302
- } else {
4636
+ } else if (!preferContractOnly) {
4303
4637
  console.log(dim3("Fetching registry content..."));
4304
4638
  const blueprintsResult2 = await registryClient.fetchBlueprints();
4305
4639
  registrySource = blueprintsResult2.source.type === "api" ? "api" : "cache";
@@ -4327,14 +4661,15 @@ ${YELLOW9}You're offline. Scaffolding Decantr default.${RESET13}`);
4327
4661
  if (args.yes || selectedBlueprint !== "default") {
4328
4662
  const flags = parseFlags(args, detected);
4329
4663
  flags.blueprint = selectedBlueprint !== "default" ? selectedBlueprint : flags.blueprint;
4330
- options = mergeWithDefaults(flags, detected);
4664
+ options = mergeWithDefaults(flags, detected, workflowSeed ?? void 0);
4331
4665
  } else {
4332
- options = await runInteractivePrompts(detected, archetypes, blueprints, themes);
4666
+ options = await runInteractivePrompts(detected, archetypes, blueprints, themes, workflowSeed ?? void 0);
4333
4667
  userExplicit.theme = true;
4334
4668
  userExplicit.mode = true;
4335
4669
  userExplicit.shape = true;
4336
4670
  userExplicit.personality = true;
4337
4671
  }
4672
+ options.workflowMode = workflowMode;
4338
4673
  let topologyMarkdown = "";
4339
4674
  let archetypeData;
4340
4675
  let composedSections;
@@ -4512,6 +4847,11 @@ ${YELLOW9}You're offline. Scaffolding Decantr default.${RESET13}`);
4512
4847
  if (result.gitignoreUpdated) {
4513
4848
  console.log(` ${dim3(".gitignore updated")}`);
4514
4849
  }
4850
+ if (!existsSync25(join26(projectRoot, "package.json"))) {
4851
+ console.log("");
4852
+ console.log(dim3(` Note: ${cyan3("decantr init")} created Decantr contract/context files only.`));
4853
+ console.log(dim3(` For a runnable starter in a new directory, prefer ${cyan3("decantr new <name> --blueprint=...")}.`));
4854
+ }
4515
4855
  console.log("");
4516
4856
  console.log(" Next steps:");
4517
4857
  console.log(" 1. Read DECANTR.md for methodology, CSS approach, and guard rules");
@@ -4529,7 +4869,7 @@ ${YELLOW9}You're offline. Scaffolding Decantr default.${RESET13}`);
4529
4869
  console.log(` ${cyan3("decantr upgrade")} Update to latest patterns`);
4530
4870
  console.log(` ${cyan3("decantr check")} Detect drift issues`);
4531
4871
  console.log(` ${cyan3("decantr migrate")} Migrate v2 essence to v3`);
4532
- const essenceContent = readFileSync16(result.essencePath, "utf-8");
4872
+ const essenceContent = readFileSync18(result.essencePath, "utf-8");
4533
4873
  const essence = JSON.parse(essenceContent);
4534
4874
  if (essence.version !== "3.1.0") {
4535
4875
  const validation = validateEssence2(essence);
@@ -4551,6 +4891,7 @@ Validation warnings: ${validation.errors.join(", ")}`));
4551
4891
  promptPages = essence.structure || [{ id: "home", shell: options.shell, layout: ["hero"] }];
4552
4892
  }
4553
4893
  const promptCtx = {
4894
+ workflow: options.workflowMode === "brownfield-attach" ? "brownfield-attach" : "greenfield-scaffold",
4554
4895
  archetype: options.archetype || "custom",
4555
4896
  blueprint: options.blueprint,
4556
4897
  theme: options.theme,
@@ -4576,16 +4917,16 @@ Validation warnings: ${validation.errors.join(", ")}`));
4576
4917
  }
4577
4918
  async function cmdStatus() {
4578
4919
  const projectRoot = process.cwd();
4579
- const essencePath = join24(projectRoot, "decantr.essence.json");
4580
- const projectJsonPath = join24(projectRoot, ".decantr", "project.json");
4920
+ const essencePath = join26(projectRoot, "decantr.essence.json");
4921
+ const projectJsonPath = join26(projectRoot, ".decantr", "project.json");
4581
4922
  console.log(heading2("Decantr Project Status"));
4582
- if (!existsSync24(essencePath)) {
4923
+ if (!existsSync25(essencePath)) {
4583
4924
  console.log(`${RED11}No decantr.essence.json found.${RESET13}`);
4584
4925
  console.log(dim3('Run "decantr init" to create one.'));
4585
4926
  return;
4586
4927
  }
4587
4928
  try {
4588
- const essence = JSON.parse(readFileSync16(essencePath, "utf-8"));
4929
+ const essence = JSON.parse(readFileSync18(essencePath, "utf-8"));
4589
4930
  const validation = validateEssence2(essence);
4590
4931
  const essenceVersion = isV36(essence) ? "v3" : "v2";
4591
4932
  console.log(`${BOLD6}Essence:${RESET13}`);
@@ -4634,9 +4975,9 @@ async function cmdStatus() {
4634
4975
  }
4635
4976
  console.log("");
4636
4977
  console.log(`${BOLD6}Sync Status:${RESET13}`);
4637
- if (existsSync24(projectJsonPath)) {
4978
+ if (existsSync25(projectJsonPath)) {
4638
4979
  try {
4639
- const projectJson = JSON.parse(readFileSync16(projectJsonPath, "utf-8"));
4980
+ const projectJson = JSON.parse(readFileSync18(projectJsonPath, "utf-8"));
4640
4981
  const syncStatus = projectJson.sync?.status || "unknown";
4641
4982
  const lastSync = projectJson.sync?.lastSync || "never";
4642
4983
  const source = projectJson.sync?.registrySource || "unknown";
@@ -4654,7 +4995,7 @@ async function cmdStatus() {
4654
4995
  }
4655
4996
  async function cmdSync() {
4656
4997
  const projectRoot = process.cwd();
4657
- const cacheDir = join24(projectRoot, ".decantr", "cache");
4998
+ const cacheDir = join26(projectRoot, ".decantr", "cache");
4658
4999
  console.log(heading2("Syncing registry content..."));
4659
5000
  const result = await syncRegistry(cacheDir);
4660
5001
  if (result.synced.length > 0) {
@@ -4843,14 +5184,14 @@ ${BOLD6}Examples:${RESET13}
4843
5184
  process.exitCode = 1;
4844
5185
  return;
4845
5186
  }
4846
- const themePath = join24(projectRoot, ".decantr", "custom", "themes", `${name}.json`);
4847
- if (!existsSync24(themePath)) {
5187
+ const themePath = join26(projectRoot, ".decantr", "custom", "themes", `${name}.json`);
5188
+ if (!existsSync25(themePath)) {
4848
5189
  console.error(error3(`Theme "${name}" not found at ${themePath}`));
4849
5190
  process.exitCode = 1;
4850
5191
  return;
4851
5192
  }
4852
5193
  try {
4853
- const theme = JSON.parse(readFileSync16(themePath, "utf-8"));
5194
+ const theme = JSON.parse(readFileSync18(themePath, "utf-8"));
4854
5195
  const result = validateCustomTheme(theme);
4855
5196
  if (result.valid) {
4856
5197
  console.log(success3(`Custom theme "${name}" is valid`));
@@ -4966,9 +5307,9 @@ ${BOLD6}Init Options:${RESET13}
4966
5307
  --registry Custom registry URL
4967
5308
 
4968
5309
  ${BOLD6}Commands:${RESET13}
4969
- ${cyan3("new")} Create a new project with Vite + React + Decantr
4970
- ${cyan3("magic")} One-liner scaffold from a natural language prompt
4971
- ${cyan3("init")} Initialize Decantr in an existing project (v3 essence by default)
5310
+ ${cyan3("new")} Create a new greenfield workspace and bootstrap the available starter adapter
5311
+ ${cyan3("magic")} Greenfield-first intent flow; steers existing apps into analyze + init
5312
+ ${cyan3("init")} Attach Decantr contract/context files to an existing project or empty workspace
4972
5313
  ${cyan3("status")} Show project status, DNA axioms, and blueprint info
4973
5314
  ${cyan3("sync")} Sync registry content from API
4974
5315
  ${cyan3("audit")} Audit the project or critique a specific file against compiled packs
@@ -4986,7 +5327,7 @@ ${BOLD6}Commands:${RESET13}
4986
5327
  ${cyan3("publish")} Publish a custom content item to the community registry
4987
5328
  ${cyan3("login")} Authenticate with the Decantr registry
4988
5329
  ${cyan3("logout")} Remove stored credentials
4989
- ${cyan3("analyze")} Scan existing project and produce analysis report
5330
+ ${cyan3("analyze")} Brownfield entrypoint: scan an existing project and emit attach guidance
4990
5331
  ${cyan3("export")} Export design tokens to framework format (shadcn, tailwind, css-vars)
4991
5332
  ${cyan3("registry")} Registry management and intelligence summary
4992
5333
  ${cyan3("upgrade")} Check for content updates from registry
@@ -4996,7 +5337,7 @@ ${BOLD6}Examples:${RESET13}
4996
5337
  decantr new my-app --blueprint=carbon-ai-portal
4997
5338
  decantr magic "AI chatbot with dark cyber theme \u2014 bold and futuristic"
4998
5339
  decantr init
4999
- decantr init --blueprint=saas-dashboard --theme=luminarum --yes
5340
+ decantr init --existing --blueprint=saas-dashboard --theme=luminarum --yes
5000
5341
  decantr status
5001
5342
  decantr audit
5002
5343
  decantr audit src/pages/HomePage.tsx
@@ -5017,6 +5358,15 @@ ${BOLD6}Examples:${RESET13}
5017
5358
  decantr registry audit-project --namespace @official --json
5018
5359
  decantr registry audit-project --namespace @official --dist dist --sources src
5019
5360
  decantr create pattern my-card
5361
+
5362
+ ${BOLD6}Workflow Model:${RESET13}
5363
+ ${cyan3("Greenfield blueprint")} decantr new / decantr magic
5364
+ ${cyan3("Brownfield adoption")} decantr analyze -> decantr init --existing
5365
+ ${cyan3("Hybrid composition")} decantr add/remove, decantr theme switch, decantr registry, decantr upgrade
5366
+
5367
+ ${BOLD6}Bootstrap adapters:${RESET13}
5368
+ Current runnable starter adapter: ${cyan3("react-vite")}
5369
+ Other contract targets stay framework-agnostic, but currently initialize in contract-only mode until their starter adapters land.
5020
5370
  `);
5021
5371
  }
5022
5372
  async function main() {
@@ -5095,7 +5445,7 @@ async function main() {
5095
5445
  break;
5096
5446
  }
5097
5447
  case "upgrade": {
5098
- const { cmdUpgrade } = await import("./upgrade-XNUAON3G.js");
5448
+ const { cmdUpgrade } = await import("./upgrade-UXY2WUJH.js");
5099
5449
  const applyFlag = args.includes("--apply");
5100
5450
  await cmdUpgrade(process.cwd(), { apply: applyFlag });
5101
5451
  break;