@decantr/cli 1.7.6 → 1.7.7

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-4D5V6GQU.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 readFileSync17, 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,7 @@ 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
344
347
  };
345
348
  }
346
349
  function parseFlags(args, detected) {
@@ -359,20 +362,20 @@ function parseFlags(args, detected) {
359
362
  if (args.existing === true) options.existing = true;
360
363
  return options;
361
364
  }
362
- function mergeWithDefaults(flags, detected) {
365
+ function mergeWithDefaults(flags, detected, workflowSeed) {
363
366
  return {
364
367
  blueprint: flags.blueprint,
365
368
  archetype: flags.archetype,
366
- theme: flags.theme || "luminarum",
367
- mode: flags.mode || "dark",
369
+ theme: flags.theme || workflowSeed?.theme || "luminarum",
370
+ mode: flags.mode || workflowSeed?.mode || "dark",
368
371
  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",
372
+ target: flags.target || workflowSeed?.target || (detected.framework !== "unknown" ? detected.framework : "react"),
373
+ guard: flags.guard || workflowSeed?.guard || (detected.existingEssence ? "guided" : "strict"),
374
+ density: flags.density || workflowSeed?.density || "comfortable",
375
+ shell: flags.shell || workflowSeed?.shell || "sidebar-main",
373
376
  personality: flags.personality || ["professional"],
374
377
  features: flags.features || [],
375
- existing: flags.existing || detected.existingEssence
378
+ existing: flags.existing || workflowSeed?.existing || detected.existingEssence
376
379
  };
377
380
  }
378
381
  async function runSimplifiedInit(blueprints) {
@@ -1532,8 +1535,8 @@ async function cmdThemeSwitch(themeName, args, projectRoot = process.cwd()) {
1532
1535
  }
1533
1536
 
1534
1537
  // src/commands/analyze.ts
1535
- import { existsSync as existsSync18, mkdirSync as mkdirSync4, writeFileSync as writeFileSync9 } from "fs";
1536
- import { join as join18 } from "path";
1538
+ import { existsSync as existsSync19, mkdirSync as mkdirSync4, writeFileSync as writeFileSync9 } from "fs";
1539
+ import { join as join19 } from "path";
1537
1540
 
1538
1541
  // src/analyzers/routes.ts
1539
1542
  import { existsSync as existsSync12, readdirSync as readdirSync2, statSync } from "fs";
@@ -2231,6 +2234,53 @@ function scanDependencies(projectRoot) {
2231
2234
  return result;
2232
2235
  }
2233
2236
 
2237
+ // src/workflow-model.ts
2238
+ import { existsSync as existsSync18, readFileSync as readFileSync14 } from "fs";
2239
+ import { join as join18 } from "path";
2240
+ function inferSuggestedShell(layout) {
2241
+ if (layout.hasSidebar) return "sidebar-main";
2242
+ if (layout.hasTopNav) return "top-nav-main";
2243
+ return "full-bleed";
2244
+ }
2245
+ function hasExistingProjectFootprint(detected) {
2246
+ return detected.framework !== "unknown" || detected.packageManager !== "unknown" || detected.hasTypeScript || detected.hasTailwind || detected.existingRuleFiles.length > 0;
2247
+ }
2248
+ function createBrownfieldInitSeed(detected, layout, styling) {
2249
+ return {
2250
+ version: 1,
2251
+ workflow: "brownfield-adoption",
2252
+ contractOnly: true,
2253
+ registryOptional: true,
2254
+ target: detected.framework !== "unknown" ? detected.framework : "react",
2255
+ shell: inferSuggestedShell(layout),
2256
+ guard: "guided",
2257
+ density: "comfortable",
2258
+ theme: "luminarum",
2259
+ mode: styling.darkMode ? "dark" : "auto",
2260
+ existing: true,
2261
+ notes: [
2262
+ "Use decantr init --existing to attach Decantr contract and context files to this project.",
2263
+ "Registry content is optional during brownfield adoption.",
2264
+ "Use decantr add/remove, decantr theme switch, and registry commands later for hybrid composition."
2265
+ ]
2266
+ };
2267
+ }
2268
+ function readBrownfieldInitSeed(projectRoot) {
2269
+ const seedPath = join18(projectRoot, ".decantr", "init-seed.json");
2270
+ if (!existsSync18(seedPath)) {
2271
+ return null;
2272
+ }
2273
+ try {
2274
+ const parsed = JSON.parse(readFileSync14(seedPath, "utf-8"));
2275
+ if (parsed.workflow !== "brownfield-adoption") {
2276
+ return null;
2277
+ }
2278
+ return parsed;
2279
+ } catch {
2280
+ return null;
2281
+ }
2282
+ }
2283
+
2234
2284
  // src/commands/analyze.ts
2235
2285
  var BOLD3 = "\x1B[1m";
2236
2286
  var DIM8 = "\x1B[2m";
@@ -2256,6 +2306,7 @@ ${BOLD3}Analyzing project...${RESET8}
2256
2306
  const features = scanFeatures(projectRoot);
2257
2307
  console.log(`${DIM8}Scanning dependencies...${RESET8}`);
2258
2308
  const dependencies = scanDependencies(projectRoot);
2309
+ const initSeed = createBrownfieldInitSeed(project, layout, styling);
2259
2310
  const analysis = {
2260
2311
  version: 1,
2261
2312
  analyzedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -2271,14 +2322,29 @@ ${BOLD3}Analyzing project...${RESET8}
2271
2322
  styling,
2272
2323
  layout,
2273
2324
  features,
2274
- dependencies
2325
+ dependencies,
2326
+ decantr: {
2327
+ workflow: "brownfield-adoption",
2328
+ registryOptional: true,
2329
+ attach: {
2330
+ entrypoint: "decantr analyze",
2331
+ contractOnly: true,
2332
+ initSeedPath: ".decantr/init-seed.json",
2333
+ recommendedCommand: "decantr init --existing --yes"
2334
+ },
2335
+ hybrid: {
2336
+ ownerCommands: ["decantr add", "decantr remove", "decantr theme switch", "decantr registry", "decantr upgrade"]
2337
+ }
2338
+ }
2275
2339
  };
2276
- const decantrDir = join18(projectRoot, ".decantr");
2277
- if (!existsSync18(decantrDir)) {
2340
+ const decantrDir = join19(projectRoot, ".decantr");
2341
+ if (!existsSync19(decantrDir)) {
2278
2342
  mkdirSync4(decantrDir, { recursive: true });
2279
2343
  }
2280
- const outputPath = join18(decantrDir, "analysis.json");
2344
+ const outputPath = join19(decantrDir, "analysis.json");
2345
+ const initSeedPath = join19(decantrDir, "init-seed.json");
2281
2346
  writeFileSync9(outputPath, JSON.stringify(analysis, null, 2) + "\n", "utf-8");
2347
+ writeFileSync9(initSeedPath, JSON.stringify(initSeed, null, 2) + "\n", "utf-8");
2282
2348
  console.log(`
2283
2349
  ${GREEN8}Analysis complete.${RESET8}
2284
2350
  `);
@@ -2303,14 +2369,15 @@ ${GREEN8}Analysis complete.${RESET8}
2303
2369
  console.log(` Dependencies: ${depCounts || "none categorized"}`);
2304
2370
  console.log(`
2305
2371
  ${DIM8}Written to:${RESET8} ${outputPath}`);
2372
+ console.log(`${DIM8}Init seed:${RESET8} ${initSeedPath}`);
2306
2373
  console.log(`
2307
- ${YELLOW5}Next step:${RESET8} Ask your AI assistant to read ${BOLD3}.decantr/analysis.json${RESET8} and set up Decantr
2374
+ ${YELLOW5}Next step:${RESET8} Run ${BOLD3}decantr init --existing --yes${RESET8} to attach Decantr using the generated brownfield seed.
2308
2375
  `);
2309
2376
  }
2310
2377
 
2311
2378
  // src/commands/magic.ts
2312
- import { join as join19 } from "path";
2313
- import { existsSync as existsSync19 } from "fs";
2379
+ import { join as join20 } from "path";
2380
+ import { existsSync as existsSync20 } from "fs";
2314
2381
  import * as fs from "fs/promises";
2315
2382
  var BOLD4 = "\x1B[1m";
2316
2383
  var DIM9 = "\x1B[2m";
@@ -2538,15 +2605,25 @@ async function cmdMagic(prompt, projectRoot, options) {
2538
2605
  console.log(` Archetype: ${intent.archetype}`);
2539
2606
  }
2540
2607
  console.log("");
2541
- const essencePath = join19(projectRoot, "decantr.essence.json");
2542
- if (existsSync19(essencePath)) {
2608
+ const essencePath = join20(projectRoot, "decantr.essence.json");
2609
+ if (existsSync20(essencePath)) {
2543
2610
  console.log(error(" decantr.essence.json already exists in this directory."));
2544
2611
  console.log(dim(" Remove it first or use a different directory."));
2545
2612
  process.exitCode = 1;
2546
2613
  return;
2547
2614
  }
2615
+ const detected = detectProject(projectRoot);
2616
+ if (hasExistingProjectFootprint(detected)) {
2617
+ console.log(`${YELLOW6} Existing project detected.${RESET9}`);
2618
+ console.log(dim(" decantr magic stays greenfield-first and will not silently bootstrap over an existing app."));
2619
+ console.log(dim(" Running brownfield analysis instead so you can attach Decantr deliberately.\n"));
2620
+ cmdAnalyze(projectRoot);
2621
+ console.log(`${BOLD4}Recommended next step:${RESET9} ${cyan("decantr init --existing --yes")}`);
2622
+ console.log("");
2623
+ return;
2624
+ }
2548
2625
  const registryClient = new RegistryClient({
2549
- cacheDir: join19(projectRoot, ".decantr", "cache"),
2626
+ cacheDir: join20(projectRoot, ".decantr", "cache"),
2550
2627
  apiUrl: options.registry,
2551
2628
  offline: options.offline
2552
2629
  });
@@ -2647,7 +2724,6 @@ async function cmdMagic(prompt, projectRoot, options) {
2647
2724
  return;
2648
2725
  }
2649
2726
  console.log("");
2650
- const detected = detectProject(projectRoot);
2651
2727
  let archetypeData;
2652
2728
  let composedSections;
2653
2729
  let routeMap;
@@ -2809,14 +2885,14 @@ async function cmdMagic(prompt, projectRoot, options) {
2809
2885
  if (result.gitignoreUpdated) {
2810
2886
  console.log(` ${dim(".gitignore updated")}`);
2811
2887
  }
2812
- const contextDir = join19(projectRoot, ".decantr", "context");
2888
+ const contextDir = join20(projectRoot, ".decantr", "context");
2813
2889
  let sectionCount = 0;
2814
2890
  try {
2815
2891
  const files = await fs.readdir(contextDir);
2816
2892
  sectionCount = files.filter((f) => f.startsWith("section-")).length;
2817
2893
  } catch {
2818
2894
  }
2819
- const treatmentsPath = join19(projectRoot, "src", "styles", "treatments.css");
2895
+ const treatmentsPath = join20(projectRoot, "src", "styles", "treatments.css");
2820
2896
  let hasLayers = false;
2821
2897
  try {
2822
2898
  const css = await fs.readFile(treatmentsPath, "utf-8");
@@ -2841,8 +2917,8 @@ ${GREEN9}${BOLD4}Quality summary:${RESET9}`);
2841
2917
  }
2842
2918
 
2843
2919
  // 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";
2920
+ import { readFileSync as readFileSync15, writeFileSync as writeFileSync10, existsSync as existsSync21, mkdirSync as mkdirSync5 } from "fs";
2921
+ import { join as join21, dirname } from "path";
2846
2922
  var GREEN10 = "\x1B[32m";
2847
2923
  var RED8 = "\x1B[31m";
2848
2924
  var DIM10 = "\x1B[2m";
@@ -2988,19 +3064,19 @@ function generateCSSVars(tokens) {
2988
3064
  return lines.join("\n");
2989
3065
  }
2990
3066
  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)) {
3067
+ const essencePath = join21(projectRoot, "decantr.essence.json");
3068
+ const tokensPath = join21(projectRoot, "src", "styles", "tokens.css");
3069
+ if (!existsSync21(essencePath)) {
2994
3070
  console.error(`${RED8}No decantr.essence.json found. Run \`decantr init\` first.${RESET10}`);
2995
3071
  process.exitCode = 1;
2996
3072
  return;
2997
3073
  }
2998
- if (!existsSync20(tokensPath)) {
3074
+ if (!existsSync21(tokensPath)) {
2999
3075
  console.error(`${RED8}No src/styles/tokens.css found. Run \`decantr refresh\` to generate tokens.${RESET10}`);
3000
3076
  process.exitCode = 1;
3001
3077
  return;
3002
3078
  }
3003
- const tokensCSS = readFileSync14(tokensPath, "utf-8");
3079
+ const tokensCSS = readFileSync15(tokensPath, "utf-8");
3004
3080
  const tokens = parseTokensCSS(tokensCSS);
3005
3081
  if (tokens.size === 0) {
3006
3082
  console.error(`${RED8}No --d-* tokens found in tokens.css.${RESET10}`);
@@ -3009,8 +3085,8 @@ async function cmdExport(target, projectRoot, options = {}) {
3009
3085
  }
3010
3086
  switch (target) {
3011
3087
  case "shadcn": {
3012
- const cssOut = options.output ?? join20(projectRoot, "src", "styles", "shadcn-theme.css");
3013
- const jsonOut = join20(projectRoot, "components.json");
3088
+ const cssOut = options.output ?? join21(projectRoot, "src", "styles", "shadcn-theme.css");
3089
+ const jsonOut = join21(projectRoot, "components.json");
3014
3090
  ensureDir(cssOut);
3015
3091
  writeFileSync10(cssOut, generateShadcnCSS(tokens), "utf-8");
3016
3092
  writeFileSync10(jsonOut, generateShadcnComponentsJSON(), "utf-8");
@@ -3020,7 +3096,7 @@ async function cmdExport(target, projectRoot, options = {}) {
3020
3096
  break;
3021
3097
  }
3022
3098
  case "tailwind": {
3023
- const out = options.output ?? join20(projectRoot, "tailwind.decantr.config.ts");
3099
+ const out = options.output ?? join21(projectRoot, "tailwind.decantr.config.ts");
3024
3100
  ensureDir(out);
3025
3101
  writeFileSync10(out, generateTailwindConfig(tokens), "utf-8");
3026
3102
  console.log(`${GREEN10}Exported Tailwind config:${RESET10}`);
@@ -3028,7 +3104,7 @@ async function cmdExport(target, projectRoot, options = {}) {
3028
3104
  break;
3029
3105
  }
3030
3106
  case "css-vars": {
3031
- const out = options.output ?? join20(projectRoot, "decantr-tokens.css");
3107
+ const out = options.output ?? join21(projectRoot, "decantr-tokens.css");
3032
3108
  ensureDir(out);
3033
3109
  writeFileSync10(out, generateCSSVars(tokens), "utf-8");
3034
3110
  console.log(`${GREEN10}Exported CSS variables:${RESET10}`);
@@ -3039,14 +3115,14 @@ async function cmdExport(target, projectRoot, options = {}) {
3039
3115
  }
3040
3116
  function ensureDir(filePath) {
3041
3117
  const dir = dirname(filePath);
3042
- if (!existsSync20(dir)) {
3118
+ if (!existsSync21(dir)) {
3043
3119
  mkdirSync5(dir, { recursive: true });
3044
3120
  }
3045
3121
  }
3046
3122
 
3047
3123
  // src/commands/registry-mirror.ts
3048
3124
  import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync11 } from "fs";
3049
- import { join as join21 } from "path";
3125
+ import { join as join22 } from "path";
3050
3126
  import { RegistryAPIClient as RegistryAPIClient2, API_CONTENT_TYPES } from "@decantr/registry";
3051
3127
  var GREEN11 = "\x1B[32m";
3052
3128
  var RED9 = "\x1B[31m";
@@ -3074,7 +3150,7 @@ async function cmdRegistryMirror(projectRoot, options = {}) {
3074
3150
  process.exitCode = 1;
3075
3151
  return;
3076
3152
  }
3077
- const cacheDir = join21(projectRoot, ".decantr", "cache");
3153
+ const cacheDir = join22(projectRoot, ".decantr", "cache");
3078
3154
  const counts = {};
3079
3155
  const failed = [];
3080
3156
  console.log(`
@@ -3084,19 +3160,19 @@ Mirroring registry content to ${DIM11}.decantr/cache/${RESET11}
3084
3160
  try {
3085
3161
  const result = await apiClient.listContent(type, { namespace: "@official" });
3086
3162
  const items = result.items;
3087
- const typeDir = join21(cacheDir, "@official", type);
3163
+ const typeDir = join22(cacheDir, "@official", type);
3088
3164
  mkdirSync6(typeDir, { recursive: true });
3089
- writeFileSync11(join21(typeDir, "index.json"), JSON.stringify(result, null, 2));
3165
+ writeFileSync11(join22(typeDir, "index.json"), JSON.stringify(result, null, 2));
3090
3166
  let itemCount = 0;
3091
3167
  for (const item of items) {
3092
3168
  const slug = item.slug || item.id;
3093
3169
  if (!slug) continue;
3094
3170
  try {
3095
3171
  const fullItem = await apiClient.getContent(type, "@official", slug);
3096
- writeFileSync11(join21(typeDir, `${slug}.json`), JSON.stringify(fullItem, null, 2));
3172
+ writeFileSync11(join22(typeDir, `${slug}.json`), JSON.stringify(fullItem, null, 2));
3097
3173
  itemCount++;
3098
3174
  } catch {
3099
- writeFileSync11(join21(typeDir, `${slug}.json`), JSON.stringify(item, null, 2));
3175
+ writeFileSync11(join22(typeDir, `${slug}.json`), JSON.stringify(item, null, 2));
3100
3176
  itemCount++;
3101
3177
  }
3102
3178
  }
@@ -3111,8 +3187,8 @@ Mirroring registry content to ${DIM11}.decantr/cache/${RESET11}
3111
3187
  mirrored_at: (/* @__PURE__ */ new Date()).toISOString(),
3112
3188
  counts
3113
3189
  };
3114
- mkdirSync6(join21(cacheDir), { recursive: true });
3115
- writeFileSync11(join21(cacheDir, "mirror-manifest.json"), JSON.stringify(manifest, null, 2));
3190
+ mkdirSync6(join22(cacheDir), { recursive: true });
3191
+ writeFileSync11(join22(cacheDir, "mirror-manifest.json"), JSON.stringify(manifest, null, 2));
3116
3192
  const totalItems = Object.values(counts).reduce((a, b) => a + b, 0);
3117
3193
  console.log("");
3118
3194
  if (failed.length > 0) {
@@ -3125,98 +3201,160 @@ Mirroring registry content to ${DIM11}.decantr/cache/${RESET11}
3125
3201
  }
3126
3202
 
3127
3203
  // 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";
3204
+ import { existsSync as existsSync24, mkdirSync as mkdirSync9 } from "fs";
3205
+ import { join as join25, resolve as resolve2 } from "path";
3130
3206
  import { execSync } from "child_process";
3131
3207
  import { fileURLToPath } from "url";
3132
3208
 
3133
3209
  // src/offline-content.ts
3134
- import { cpSync, existsSync as existsSync22, mkdirSync as mkdirSync7 } from "fs";
3135
- import { join as join22, resolve } from "path";
3210
+ import { cpSync, existsSync as existsSync23, mkdirSync as mkdirSync7 } from "fs";
3211
+ import { join as join23, resolve } from "path";
3136
3212
  var CONTENT_TYPES2 = ["archetypes", "blueprints", "patterns", "themes", "shells"];
3137
3213
  function copyIfExists(source, target) {
3138
- if (!existsSync22(source)) return false;
3214
+ if (!existsSync23(source)) return false;
3139
3215
  if (resolve(source) === resolve(target)) return true;
3140
3216
  cpSync(source, target, { recursive: true });
3141
3217
  return true;
3142
3218
  }
3143
3219
  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");
3220
+ if (!existsSync23(contentRoot)) return false;
3221
+ const customRoot = join23(projectDir, ".decantr", "custom");
3222
+ const cacheRoot = join23(projectDir, ".decantr", "cache", "@official");
3147
3223
  mkdirSync7(customRoot, { recursive: true });
3148
3224
  mkdirSync7(cacheRoot, { recursive: true });
3149
3225
  let copiedAny = false;
3150
3226
  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 });
3227
+ const sourceDir = join23(contentRoot, type);
3228
+ if (!existsSync23(sourceDir)) continue;
3229
+ cpSync(sourceDir, join23(customRoot, type), { recursive: true });
3230
+ cpSync(sourceDir, join23(cacheRoot, type), { recursive: true });
3155
3231
  copiedAny = true;
3156
3232
  }
3157
3233
  return copiedAny;
3158
3234
  }
3159
3235
  function seedOfflineRegistry(projectDir, workspaceRoot) {
3160
- const projectDecantrRoot = join22(projectDir, ".decantr");
3236
+ const projectDecantrRoot = join23(projectDir, ".decantr");
3161
3237
  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"));
3238
+ const configuredContentRoot = process.env.DECANTR_CONTENT_DIR ? resolve(process.env.DECANTR_CONTENT_DIR) : null;
3239
+ if (configuredContentRoot && hydrateContentRoot(projectDir, configuredContentRoot)) {
3240
+ return { seeded: true, strategy: "configured-content-root" };
3241
+ }
3242
+ const copiedCache = copyIfExists(join23(workspaceRoot, ".decantr", "cache"), join23(projectDecantrRoot, "cache"));
3243
+ const copiedCustom = copyIfExists(join23(workspaceRoot, ".decantr", "custom"), join23(projectDecantrRoot, "custom"));
3164
3244
  if (copiedCache || copiedCustom) {
3165
3245
  return { seeded: true, strategy: "workspace-cache" };
3166
3246
  }
3167
- const configuredContentRoot = process.env.DECANTR_CONTENT_DIR ? resolve(process.env.DECANTR_CONTENT_DIR) : null;
3168
3247
  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
- };
3248
+ if (hydrateContentRoot(projectDir, siblingContentRoot)) {
3249
+ return { seeded: true, strategy: "sibling-content-root" };
3175
3250
  }
3176
3251
  return { seeded: false, strategy: null };
3177
3252
  }
3178
3253
 
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}
3254
+ // src/bootstrap.ts
3255
+ import { mkdirSync as mkdirSync8, readFileSync as readFileSync16, writeFileSync as writeFileSync12 } from "fs";
3256
+ import { basename, join as join24 } from "path";
3257
+ import { resolvePackAdapter } from "@decantr/core";
3258
+ var reactViteBootstrapAdapter = {
3259
+ id: "react-vite",
3260
+ label: "React + Vite starter",
3261
+ writeProjectFiles(projectDir, title, routingMode) {
3262
+ const srcDir = join24(projectDir, "src");
3263
+ const routerImport = routingMode === "hash" ? "HashRouter" : "BrowserRouter";
3264
+ const packageJson = {
3265
+ name: basename(projectDir) || "decantr-app",
3266
+ private: true,
3267
+ version: "0.0.0",
3268
+ type: "module",
3269
+ scripts: {
3270
+ dev: "vite",
3271
+ build: "tsc -b && vite build",
3272
+ preview: "vite preview"
3273
+ },
3274
+ dependencies: {
3275
+ react: "^19.0.0",
3276
+ "react-dom": "^19.0.0",
3277
+ "react-router-dom": "^7.0.0",
3278
+ "@decantr/css": "^1.0.0"
3279
+ },
3280
+ devDependencies: {
3281
+ "@types/react": "^19.0.0",
3282
+ "@types/react-dom": "^19.0.0",
3283
+ "@vitejs/plugin-react": "^4.0.0",
3284
+ typescript: "^5.7.0",
3285
+ vite: "^6.0.0"
3286
+ }
3287
+ };
3288
+ writeFileSync12(join24(projectDir, "package.json"), JSON.stringify(packageJson, null, 2) + "\n");
3289
+ const viteConfig = `import { defineConfig } from 'vite';
3290
+ import react from '@vitejs/plugin-react';
3291
+
3292
+ export default defineConfig({
3293
+ plugins: [react()],
3294
+ });
3190
3295
  `;
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';
3296
+ writeFileSync12(join24(projectDir, "vite.config.ts"), viteConfig);
3297
+ const tsconfig = {
3298
+ compilerOptions: {
3299
+ target: "ES2020",
3300
+ useDefineForClassFields: true,
3301
+ lib: ["ES2020", "DOM", "DOM.Iterable"],
3302
+ module: "ESNext",
3303
+ skipLibCheck: true,
3304
+ moduleResolution: "bundler",
3305
+ allowImportingTsExtensions: true,
3306
+ isolatedModules: true,
3307
+ moduleDetection: "force",
3308
+ noEmit: true,
3309
+ jsx: "react-jsx",
3310
+ strict: true,
3311
+ noUnusedLocals: true,
3312
+ noUnusedParameters: true,
3313
+ noFallthroughCasesInSwitch: true,
3314
+ noUncheckedSideEffectImports: true
3315
+ },
3316
+ include: ["src"]
3317
+ };
3318
+ writeFileSync12(join24(projectDir, "tsconfig.json"), JSON.stringify(tsconfig, null, 2) + "\n");
3319
+ const tsconfigApp = {
3320
+ compilerOptions: {
3321
+ tsBuildInfoFile: "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
3322
+ target: "ES2020",
3323
+ useDefineForClassFields: true,
3324
+ lib: ["ES2020", "DOM", "DOM.Iterable"],
3325
+ module: "ESNext",
3326
+ skipLibCheck: true,
3327
+ moduleResolution: "bundler",
3328
+ allowImportingTsExtensions: true,
3329
+ isolatedModules: true,
3330
+ moduleDetection: "force",
3331
+ noEmit: true,
3332
+ jsx: "react-jsx",
3333
+ strict: true,
3334
+ noUnusedLocals: true,
3335
+ noUnusedParameters: true,
3336
+ noFallthroughCasesInSwitch: true,
3337
+ noUncheckedSideEffectImports: true
3338
+ },
3339
+ include: ["src"]
3340
+ };
3341
+ writeFileSync12(join24(projectDir, "tsconfig.app.json"), JSON.stringify(tsconfigApp, null, 2) + "\n");
3342
+ const indexHtml = `<!doctype html>
3343
+ <html lang="en">
3344
+ <head>
3345
+ <meta charset="UTF-8" />
3346
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
3347
+ <title>${title}</title>
3348
+ </head>
3349
+ <body>
3350
+ <div id="root"></div>
3351
+ <script type="module" src="/src/main.tsx"></script>
3352
+ </body>
3353
+ </html>
3354
+ `;
3355
+ writeFileSync12(join24(projectDir, "index.html"), indexHtml);
3356
+ mkdirSync8(srcDir, { recursive: true });
3357
+ const mainTsx = `import { StrictMode } from 'react';
3220
3358
  import { createRoot } from 'react-dom/client';
3221
3359
  import { ${routerImport} } from 'react-router-dom';
3222
3360
  import { App } from './App';
@@ -3232,8 +3370,8 @@ createRoot(document.getElementById('root')!).render(
3232
3370
  </StrictMode>,
3233
3371
  );
3234
3372
  `;
3235
- writeFileSync12(join23(srcDir, "main.tsx"), mainTsx);
3236
- const appTsx = `import { css } from '@decantr/css';
3373
+ writeFileSync12(join24(srcDir, "main.tsx"), mainTsx);
3374
+ const appTsx = `import { css } from '@decantr/css';
3237
3375
  import { Routes, Route } from 'react-router-dom';
3238
3376
 
3239
3377
  function WelcomePage() {
@@ -3267,137 +3405,105 @@ export function App() {
3267
3405
  );
3268
3406
  }
3269
3407
  `;
3270
- writeFileSync12(join23(srcDir, "App.tsx"), appTsx);
3408
+ writeFileSync12(join24(srcDir, "App.tsx"), appTsx);
3409
+ writeFileSync12(join24(srcDir, "vite-env.d.ts"), '/// <reference types="vite/client" />\n');
3410
+ mkdirSync8(join24(srcDir, "styles"), { recursive: true });
3411
+ }
3412
+ };
3413
+ var BOOTSTRAP_ADAPTERS = {
3414
+ "react-vite": reactViteBootstrapAdapter
3415
+ };
3416
+ function resolveBootstrapTarget(target) {
3417
+ const normalizedTarget = (target || "react").toLowerCase();
3418
+ const platformType = "spa";
3419
+ const packAdapter = resolvePackAdapter(normalizedTarget, platformType);
3420
+ return {
3421
+ target: normalizedTarget,
3422
+ platformType,
3423
+ packAdapter,
3424
+ bootstrapAdapterId: BOOTSTRAP_ADAPTERS[packAdapter]?.id ?? null
3425
+ };
3426
+ }
3427
+ function getBootstrapAdapter(resolution) {
3428
+ if (!resolution.bootstrapAdapterId) {
3429
+ return null;
3430
+ }
3431
+ return BOOTSTRAP_ADAPTERS[resolution.bootstrapAdapterId] ?? null;
3432
+ }
3433
+ function detectRoutingMode(projectDir) {
3434
+ try {
3435
+ const essence = JSON.parse(readFileSync16(join24(projectDir, "decantr.essence.json"), "utf-8"));
3436
+ const routing = essence.meta?.platform?.routing;
3437
+ if (routing === "history" || routing === "pathname") {
3438
+ return routing;
3439
+ }
3440
+ return "hash";
3441
+ } catch {
3442
+ return "hash";
3443
+ }
3444
+ }
3445
+
3446
+ // src/commands/new-project.ts
3447
+ var BOLD5 = "\x1B[1m";
3448
+ var DIM12 = "\x1B[2m";
3449
+ var RESET12 = "\x1B[0m";
3450
+ var RED10 = "\x1B[31m";
3451
+ var GREEN12 = "\x1B[32m";
3452
+ var CYAN6 = "\x1B[36m";
3453
+ var YELLOW8 = "\x1B[33m";
3454
+ function heading(text) {
3455
+ return `
3456
+ ${BOLD5}${text}${RESET12}
3457
+ `;
3458
+ }
3459
+ function success2(text) {
3460
+ return `${GREEN12}${text}${RESET12}`;
3271
3461
  }
3272
- function getTargetRoutingMode(target) {
3273
- return (target || "react").toLowerCase() === "nextjs" ? "pathname" : "hash";
3462
+ function error2(text) {
3463
+ return `${RED10}${text}${RESET12}`;
3464
+ }
3465
+ function dim2(text) {
3466
+ return `${DIM12}${text}${RESET12}`;
3467
+ }
3468
+ function cyan2(text) {
3469
+ return `${CYAN6}${text}${RESET12}`;
3274
3470
  }
3275
3471
  async function cmdNewProject(projectName, options) {
3276
3472
  const workspaceRoot = process.cwd();
3277
3473
  const projectDir = resolve2(workspaceRoot, projectName);
3474
+ const bootstrapTarget = resolveBootstrapTarget(options.target);
3475
+ const bootstrapAdapter = getBootstrapAdapter(bootstrapTarget);
3476
+ const hasRunnableBootstrap = Boolean(bootstrapAdapter);
3278
3477
  if (!/^[a-z0-9][a-z0-9._-]*$/i.test(projectName)) {
3279
3478
  console.error(error2("Invalid project name. Use alphanumeric characters, hyphens, dots, or underscores."));
3280
3479
  process.exitCode = 1;
3281
3480
  return;
3282
3481
  }
3283
- if (existsSync23(projectDir)) {
3482
+ if (existsSync24(projectDir)) {
3284
3483
  console.error(error2(`Directory "${projectName}" already exists.`));
3285
3484
  process.exitCode = 1;
3286
3485
  return;
3287
3486
  }
3288
3487
  console.log(heading(`Creating ${projectName}...`));
3289
- mkdirSync8(projectDir, { recursive: true });
3488
+ mkdirSync9(projectDir, { recursive: true });
3290
3489
  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
3490
  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..."));
3491
+ if (bootstrapAdapter) {
3492
+ bootstrapAdapter.writeProjectFiles(projectDir, title, "hash");
3493
+ console.log(dim2(` Bootstrapped ${bootstrapAdapter.label}`));
3494
+ } else {
3495
+ console.log(`${YELLOW8} No greenfield bootstrap adapter is available yet for target "${bootstrapTarget.target}" (${bootstrapTarget.packAdapter}).${RESET12}`);
3496
+ console.log(dim2(" Continuing with a contract-only Decantr workspace so the command stays target-honest instead of writing the wrong runtime."));
3497
+ }
3395
3498
  const packageManager = detectPackageManager();
3396
- try {
3397
- execSync(`${packageManager} install`, { cwd: projectDir, stdio: "inherit" });
3398
- } catch {
3399
- console.log(`
3499
+ if (hasRunnableBootstrap) {
3500
+ console.log(heading("Installing dependencies..."));
3501
+ try {
3502
+ execSync(`${packageManager} install`, { cwd: projectDir, stdio: "inherit" });
3503
+ } catch {
3504
+ console.log(`
3400
3505
  ${YELLOW8}Dependency install failed. Run \`${packageManager} install\` manually.${RESET12}`);
3506
+ }
3401
3507
  }
3402
3508
  const requiresOfflineContent = Boolean(options.offline && (options.blueprint || options.archetype));
3403
3509
  const seeded = options.offline ? seedOfflineRegistry(projectDir, workspaceRoot) : { seeded: false, strategy: null };
@@ -3429,10 +3535,12 @@ ${YELLOW8}Dependency install failed. Run \`${packageManager} install\` manually.
3429
3535
  if (options.registry) initFlags.push(`--registry=${options.registry}`);
3430
3536
  try {
3431
3537
  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;
3538
+ const cliEntrypoint = existsSync24(bundledCliEntrypoint) ? bundledCliEntrypoint : process.argv[1] && existsSync24(process.argv[1]) ? process.argv[1] : null;
3433
3539
  const cliPath = cliEntrypoint ? `"${process.execPath}" "${cliEntrypoint}"` : "npx decantr";
3434
3540
  execSync(`${cliPath} init ${initFlags.join(" ")}`, { cwd: projectDir, stdio: "inherit" });
3435
- writeStarterRuntimeFiles(projectDir, title, detectRoutingMode(projectDir));
3541
+ if (bootstrapAdapter) {
3542
+ bootstrapAdapter.writeProjectFiles(projectDir, title, detectRoutingMode(projectDir));
3543
+ }
3436
3544
  } catch {
3437
3545
  console.log(`
3438
3546
  ${YELLOW8}Decantr init encountered issues. Run \`decantr init\` manually inside ${projectName}/.${RESET12}`);
@@ -3441,17 +3549,21 @@ ${YELLOW8}Decantr init encountered issues. Run \`decantr init\` manually inside
3441
3549
  \u2713 Project "${projectName}" created!
3442
3550
  `));
3443
3551
  console.log(` ${cyan2("cd " + projectName)}`);
3444
- console.log(` ${cyan2(packageManager + " run dev")}`);
3552
+ if (bootstrapAdapter) {
3553
+ console.log(` ${cyan2(packageManager + " run dev")}`);
3554
+ } else {
3555
+ 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.`));
3556
+ }
3445
3557
  console.log("");
3446
3558
  }
3447
3559
  function detectPackageManager() {
3448
- if (existsSync23(join23(process.cwd(), "pnpm-lock.yaml")) || existsSync23(join23(process.cwd(), "pnpm-workspace.yaml"))) {
3560
+ if (existsSync24(join25(process.cwd(), "pnpm-lock.yaml")) || existsSync24(join25(process.cwd(), "pnpm-workspace.yaml"))) {
3449
3561
  return "pnpm";
3450
3562
  }
3451
- if (existsSync23(join23(process.cwd(), "yarn.lock"))) {
3563
+ if (existsSync24(join25(process.cwd(), "yarn.lock"))) {
3452
3564
  return "yarn";
3453
3565
  }
3454
- if (existsSync23(join23(process.cwd(), "bun.lockb")) || existsSync23(join23(process.cwd(), "bun.lock"))) {
3566
+ if (existsSync24(join25(process.cwd(), "bun.lockb")) || existsSync24(join25(process.cwd(), "bun.lock"))) {
3455
3567
  return "bun";
3456
3568
  }
3457
3569
  return "npm";
@@ -3572,6 +3684,12 @@ function generateCuratedPrompt(ctx) {
3572
3684
  lines.push("- Start with the shell layouts and route structure first, then build section pages route by route.");
3573
3685
  lines.push("- Import src/styles/global.css, src/styles/tokens.css, and src/styles/treatments.css.");
3574
3686
  lines.push("- Use the existing Decantr tokens, treatments, and decorators instead of inventing a new visual system.");
3687
+ 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.");
3688
+ 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.");
3689
+ 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.");
3690
+ 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.");
3691
+ 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.");
3692
+ 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
3693
  lines.push("- Do not modify generated context files unless the task is explicitly to regenerate or refresh Decantr context.");
3576
3694
  lines.push("");
3577
3695
  lines.push("Execution flow:");
@@ -3606,18 +3724,18 @@ function extractHostedAssetPaths(indexHtml) {
3606
3724
  return [...assetPaths];
3607
3725
  }
3608
3726
  function readHostedDistSnapshot(distPath) {
3609
- const resolvedDistPath = distPath ? resolveUserPath(distPath) : join24(process.cwd(), "dist");
3610
- const indexPath = join24(resolvedDistPath, "index.html");
3611
- if (!existsSync24(indexPath)) {
3727
+ const resolvedDistPath = distPath ? resolveUserPath(distPath) : join26(process.cwd(), "dist");
3728
+ const indexPath = join26(resolvedDistPath, "index.html");
3729
+ if (!existsSync25(indexPath)) {
3612
3730
  return void 0;
3613
3731
  }
3614
- const indexHtml = readFileSync16(indexPath, "utf-8");
3732
+ const indexHtml = readFileSync17(indexPath, "utf-8");
3615
3733
  const assetPaths = extractHostedAssetPaths(indexHtml);
3616
3734
  const assets = {};
3617
3735
  for (const assetPath of assetPaths) {
3618
- const assetFilePath = join24(resolvedDistPath, assetPath.replace(/^[/\\]+/, ""));
3619
- if (existsSync24(assetFilePath)) {
3620
- assets[assetPath] = readFileSync16(assetFilePath, "utf-8");
3736
+ const assetFilePath = join26(resolvedDistPath, assetPath.replace(/^[/\\]+/, ""));
3737
+ if (existsSync25(assetFilePath)) {
3738
+ assets[assetPath] = readFileSync17(assetFilePath, "utf-8");
3621
3739
  }
3622
3740
  }
3623
3741
  return {
@@ -3632,24 +3750,24 @@ function isHostedSourceSnapshotFile(path) {
3632
3750
  function readHostedSourceSnapshot(sourcePath) {
3633
3751
  if (!sourcePath) return void 0;
3634
3752
  const resolvedSourcePath = resolveUserPath(sourcePath);
3635
- if (!existsSync24(resolvedSourcePath)) {
3753
+ if (!existsSync25(resolvedSourcePath)) {
3636
3754
  return void 0;
3637
3755
  }
3638
3756
  const files = {};
3639
3757
  const ignoredDirNames = /* @__PURE__ */ new Set(["node_modules", ".git", ".decantr", "dist", "build", "coverage"]);
3640
- const rootPrefix = basename(resolvedSourcePath);
3758
+ const rootPrefix = basename2(resolvedSourcePath);
3641
3759
  const walk = (absoluteDir, relativeDir) => {
3642
3760
  for (const entry of readdirSync6(absoluteDir, { withFileTypes: true })) {
3643
3761
  if (ignoredDirNames.has(entry.name)) continue;
3644
- const absolutePath = join24(absoluteDir, entry.name);
3645
- const relativePath = join24(relativeDir, entry.name).replace(/\\/g, "/");
3762
+ const absolutePath = join26(absoluteDir, entry.name);
3763
+ const relativePath = join26(relativeDir, entry.name).replace(/\\/g, "/");
3646
3764
  if (entry.isDirectory()) {
3647
3765
  walk(absolutePath, relativePath);
3648
3766
  continue;
3649
3767
  }
3650
3768
  if (!entry.isFile()) continue;
3651
3769
  if (!isHostedSourceSnapshotFile(relativePath)) continue;
3652
- files[relativePath] = readFileSync16(absolutePath, "utf-8");
3770
+ files[relativePath] = readFileSync17(absolutePath, "utf-8");
3653
3771
  }
3654
3772
  };
3655
3773
  walk(resolvedSourcePath, rootPrefix);
@@ -3766,19 +3884,19 @@ async function printRegistryIntelligenceSummary(namespace, jsonOutput = false) {
3766
3884
  }
3767
3885
  async function printHostedExecutionPackBundle(essencePath, namespace, jsonOutput = false, writeContext = false) {
3768
3886
  const client = getPublicAPIClient();
3769
- const resolvedPath = essencePath ? resolveUserPath(essencePath) : join24(process.cwd(), "decantr.essence.json");
3770
- if (!existsSync24(resolvedPath)) {
3887
+ const resolvedPath = essencePath ? resolveUserPath(essencePath) : join26(process.cwd(), "decantr.essence.json");
3888
+ if (!existsSync25(resolvedPath)) {
3771
3889
  throw new Error(`Essence file not found at ${resolvedPath}`);
3772
3890
  }
3773
- const essence = JSON.parse(readFileSync16(resolvedPath, "utf-8"));
3891
+ const essence = JSON.parse(readFileSync17(resolvedPath, "utf-8"));
3774
3892
  const bundle = await client.compileExecutionPacks(
3775
3893
  essence,
3776
3894
  namespace ? { namespace } : void 0
3777
3895
  );
3778
3896
  let writtenContextPaths = [];
3779
3897
  if (writeContext) {
3780
- const contextDir = join24(process.cwd(), ".decantr", "context");
3781
- mkdirSync9(contextDir, { recursive: true });
3898
+ const contextDir = join26(process.cwd(), ".decantr", "context");
3899
+ mkdirSync10(contextDir, { recursive: true });
3782
3900
  const written = writeExecutionPackBundleArtifacts(
3783
3901
  contextDir,
3784
3902
  bundle
@@ -3801,7 +3919,7 @@ async function printHostedExecutionPackBundle(essencePath, namespace, jsonOutput
3801
3919
  console.log(` Sections: ${typedBundle.sections.length}`);
3802
3920
  console.log(` Mutations: ${typedBundle.mutations.length}`);
3803
3921
  if (writeContext) {
3804
- console.log(` Context bundle: ${join24(process.cwd(), ".decantr", "context")}`);
3922
+ console.log(` Context bundle: ${join26(process.cwd(), ".decantr", "context")}`);
3805
3923
  console.log(` Files written: ${writtenContextPaths.length}`);
3806
3924
  }
3807
3925
  console.log("");
@@ -3813,14 +3931,14 @@ async function printHostedExecutionPackBundle(essencePath, namespace, jsonOutput
3813
3931
  }
3814
3932
  async function printHostedSelectedExecutionPack(packType, id, essencePath, namespace, jsonOutput = false, writeContext = false) {
3815
3933
  const client = getPublicAPIClient();
3816
- const resolvedPath = essencePath ? resolveUserPath(essencePath) : join24(process.cwd(), "decantr.essence.json");
3817
- if (!existsSync24(resolvedPath)) {
3934
+ const resolvedPath = essencePath ? resolveUserPath(essencePath) : join26(process.cwd(), "decantr.essence.json");
3935
+ if (!existsSync25(resolvedPath)) {
3818
3936
  throw new Error(`Essence file not found at ${resolvedPath}`);
3819
3937
  }
3820
3938
  if ((packType === "section" || packType === "page" || packType === "mutation") && !id) {
3821
3939
  throw new Error(`Pack type "${packType}" requires an id.`);
3822
3940
  }
3823
- const essence = JSON.parse(readFileSync16(resolvedPath, "utf-8"));
3941
+ const essence = JSON.parse(readFileSync17(resolvedPath, "utf-8"));
3824
3942
  const selected = await client.selectExecutionPack(
3825
3943
  {
3826
3944
  essence,
@@ -3831,14 +3949,14 @@ async function printHostedSelectedExecutionPack(packType, id, essencePath, names
3831
3949
  );
3832
3950
  let writtenContextDir = null;
3833
3951
  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");
3952
+ const contextDir = join26(process.cwd(), ".decantr", "context");
3953
+ mkdirSync10(contextDir, { recursive: true });
3954
+ writeFileSync13(join26(contextDir, "pack-manifest.json"), JSON.stringify(selected.manifest, null, 2) + "\n");
3837
3955
  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
3956
  const markdownFile = manifestEntry?.markdown ?? `${selected.selector.packType}${selected.selector.id ? `-${selected.selector.id}` : ""}-pack.md`;
3839
3957
  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");
3958
+ writeFileSync13(join26(contextDir, markdownFile), selected.pack.renderedMarkdown);
3959
+ writeFileSync13(join26(contextDir, jsonFile), JSON.stringify(selected.pack, null, 2) + "\n");
3842
3960
  writtenContextDir = contextDir;
3843
3961
  }
3844
3962
  if (jsonOutput) {
@@ -3863,20 +3981,20 @@ async function printHostedSelectedExecutionPack(packType, id, essencePath, names
3863
3981
  }
3864
3982
  async function printHostedExecutionPackManifest(essencePath, namespace, jsonOutput = false, writeContext = false) {
3865
3983
  const client = getPublicAPIClient();
3866
- const resolvedPath = essencePath ? resolveUserPath(essencePath) : join24(process.cwd(), "decantr.essence.json");
3867
- if (!existsSync24(resolvedPath)) {
3984
+ const resolvedPath = essencePath ? resolveUserPath(essencePath) : join26(process.cwd(), "decantr.essence.json");
3985
+ if (!existsSync25(resolvedPath)) {
3868
3986
  throw new Error(`Essence file not found at ${resolvedPath}`);
3869
3987
  }
3870
- const essence = JSON.parse(readFileSync16(resolvedPath, "utf-8"));
3988
+ const essence = JSON.parse(readFileSync17(resolvedPath, "utf-8"));
3871
3989
  const manifest = await client.getExecutionPackManifest(
3872
3990
  essence,
3873
3991
  namespace ? { namespace } : void 0
3874
3992
  );
3875
3993
  let writtenContextDir = null;
3876
3994
  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");
3995
+ const contextDir = join26(process.cwd(), ".decantr", "context");
3996
+ mkdirSync10(contextDir, { recursive: true });
3997
+ writeFileSync13(join26(contextDir, "pack-manifest.json"), JSON.stringify(manifest, null, 2) + "\n");
3880
3998
  writtenContextDir = contextDir;
3881
3999
  }
3882
4000
  if (jsonOutput) {
@@ -3897,14 +4015,14 @@ async function printHostedExecutionPackManifest(essencePath, namespace, jsonOutp
3897
4015
  }
3898
4016
  }
3899
4017
  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)) {
4018
+ const contextDir = join26(projectRoot, ".decantr", "context");
4019
+ const reviewPackPath = join26(contextDir, "review-pack.json");
4020
+ const manifestPath = join26(contextDir, "pack-manifest.json");
4021
+ if (existsSync25(reviewPackPath) && existsSync25(manifestPath)) {
3904
4022
  return { attempted: false, hydrated: false };
3905
4023
  }
3906
- const essencePath = join24(projectRoot, "decantr.essence.json");
3907
- if (!existsSync24(essencePath)) {
4024
+ const essencePath = join26(projectRoot, "decantr.essence.json");
4025
+ if (!existsSync25(essencePath)) {
3908
4026
  return { attempted: false, hydrated: false };
3909
4027
  }
3910
4028
  const reviewHydration = await hydrateHostedReviewPackIfMissing(projectRoot, namespace);
@@ -3913,9 +4031,9 @@ async function hydrateHostedExecutionPacksIfMissing(projectRoot, namespace = "@o
3913
4031
  }
3914
4032
  try {
3915
4033
  const client = getPublicAPIClient();
3916
- const essence = JSON.parse(readFileSync16(essencePath, "utf-8"));
4034
+ const essence = JSON.parse(readFileSync17(essencePath, "utf-8"));
3917
4035
  const bundle = await client.compileExecutionPacks(essence, { namespace });
3918
- mkdirSync9(contextDir, { recursive: true });
4036
+ mkdirSync10(contextDir, { recursive: true });
3919
4037
  writeExecutionPackBundleArtifacts(
3920
4038
  contextDir,
3921
4039
  bundle
@@ -3926,19 +4044,19 @@ async function hydrateHostedExecutionPacksIfMissing(projectRoot, namespace = "@o
3926
4044
  }
3927
4045
  }
3928
4046
  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)) {
4047
+ const contextDir = join26(projectRoot, ".decantr", "context");
4048
+ const reviewPackPath = join26(contextDir, "review-pack.json");
4049
+ const manifestPath = join26(contextDir, "pack-manifest.json");
4050
+ if (existsSync25(reviewPackPath) && existsSync25(manifestPath)) {
3933
4051
  return { attempted: false, hydrated: false };
3934
4052
  }
3935
- const essencePath = join24(projectRoot, "decantr.essence.json");
3936
- if (!existsSync24(essencePath)) {
4053
+ const essencePath = join26(projectRoot, "decantr.essence.json");
4054
+ if (!existsSync25(essencePath)) {
3937
4055
  return { attempted: false, hydrated: false };
3938
4056
  }
3939
4057
  try {
3940
4058
  const client = getPublicAPIClient();
3941
- const essence = JSON.parse(readFileSync16(essencePath, "utf-8"));
4059
+ const essence = JSON.parse(readFileSync17(essencePath, "utf-8"));
3942
4060
  const selected = await client.selectExecutionPack(
3943
4061
  {
3944
4062
  essence,
@@ -3946,10 +4064,10 @@ async function hydrateHostedReviewPackIfMissing(projectRoot, namespace = "@offic
3946
4064
  },
3947
4065
  { namespace }
3948
4066
  );
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)) {
4067
+ mkdirSync10(contextDir, { recursive: true });
4068
+ writeFileSync13(join26(contextDir, "review-pack.md"), selected.pack.renderedMarkdown);
4069
+ writeFileSync13(join26(contextDir, "review-pack.json"), JSON.stringify(selected.pack, null, 2) + "\n");
4070
+ if (!existsSync25(manifestPath)) {
3953
4071
  writeFileSync13(manifestPath, JSON.stringify(selected.manifest, null, 2) + "\n");
3954
4072
  }
3955
4073
  return { attempted: true, hydrated: true, scope: "review" };
@@ -3960,17 +4078,17 @@ async function hydrateHostedReviewPackIfMissing(projectRoot, namespace = "@offic
3960
4078
  async function printHostedFileCritique(sourcePath, namespace, jsonOutput = false, essencePath, treatmentsPath) {
3961
4079
  const client = getPublicAPIClient();
3962
4080
  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)) {
4081
+ const resolvedEssencePath = essencePath ? resolveUserPath(essencePath) : join26(process.cwd(), "decantr.essence.json");
4082
+ const resolvedTreatmentsPath = treatmentsPath ? resolveUserPath(treatmentsPath) : join26(process.cwd(), "src", "styles", "treatments.css");
4083
+ if (!existsSync25(resolvedSourcePath)) {
3966
4084
  throw new Error(`Source file not found at ${resolvedSourcePath}`);
3967
4085
  }
3968
- if (!existsSync24(resolvedEssencePath)) {
4086
+ if (!existsSync25(resolvedEssencePath)) {
3969
4087
  throw new Error(`Essence file not found at ${resolvedEssencePath}`);
3970
4088
  }
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;
4089
+ const code = readFileSync17(resolvedSourcePath, "utf-8");
4090
+ const essence = JSON.parse(readFileSync17(resolvedEssencePath, "utf-8"));
4091
+ const treatmentsCss = existsSync25(resolvedTreatmentsPath) ? readFileSync17(resolvedTreatmentsPath, "utf-8") : void 0;
3974
4092
  const report = await client.critiqueFile(
3975
4093
  {
3976
4094
  essence,
@@ -3994,11 +4112,11 @@ async function printHostedFileCritique(sourcePath, namespace, jsonOutput = false
3994
4112
  }
3995
4113
  async function printHostedProjectAudit(namespace, jsonOutput = false, essencePath, distPath, sourcesPath) {
3996
4114
  const client = getPublicAPIClient();
3997
- const resolvedEssencePath = essencePath ? resolveUserPath(essencePath) : join24(process.cwd(), "decantr.essence.json");
3998
- if (!existsSync24(resolvedEssencePath)) {
4115
+ const resolvedEssencePath = essencePath ? resolveUserPath(essencePath) : join26(process.cwd(), "decantr.essence.json");
4116
+ if (!existsSync25(resolvedEssencePath)) {
3999
4117
  throw new Error(`Essence file not found at ${resolvedEssencePath}`);
4000
4118
  }
4001
- const essence = JSON.parse(readFileSync16(resolvedEssencePath, "utf-8"));
4119
+ const essence = JSON.parse(readFileSync17(resolvedEssencePath, "utf-8"));
4002
4120
  const dist = readHostedDistSnapshot(distPath);
4003
4121
  const sources = readHostedSourceSnapshot(sourcesPath);
4004
4122
  const report = await client.auditProject(
@@ -4015,7 +4133,7 @@ async function printHostedProjectAudit(namespace, jsonOutput = false, essencePat
4015
4133
  }
4016
4134
  console.log(heading2("Hosted Project Audit"));
4017
4135
  console.log(` Essence: ${resolvedEssencePath}`);
4018
- console.log(` Dist snapshot: ${dist ? distPath ? resolveUserPath(distPath) : join24(process.cwd(), "dist") : "none"}`);
4136
+ console.log(` Dist snapshot: ${dist ? distPath ? resolveUserPath(distPath) : join26(process.cwd(), "dist") : "none"}`);
4019
4137
  console.log(` Source snapshot: ${sources && sourcesPath ? resolveUserPath(sourcesPath) : "none"}`);
4020
4138
  printProjectAuditReport(report);
4021
4139
  }
@@ -4093,7 +4211,7 @@ async function cmdGet(type, id) {
4093
4211
  }
4094
4212
  const apiType = CONTENT_TYPE_TO_API_CONTENT_TYPE3[type];
4095
4213
  const registryClient = new RegistryClient({
4096
- cacheDir: join24(process.cwd(), ".decantr", "cache")
4214
+ cacheDir: join26(process.cwd(), ".decantr", "cache")
4097
4215
  });
4098
4216
  const result = await registryClient.fetchContentItem(apiType, id);
4099
4217
  if (result) {
@@ -4102,16 +4220,16 @@ async function cmdGet(type, id) {
4102
4220
  }
4103
4221
  const currentDir = dirname2(fileURLToPath2(import.meta.url));
4104
4222
  const bundledCandidates = [
4105
- join24(currentDir, "bundled", apiType, `${id}.json`),
4223
+ join26(currentDir, "bundled", apiType, `${id}.json`),
4106
4224
  // Running from src/
4107
- join24(currentDir, "..", "src", "bundled", apiType, `${id}.json`),
4225
+ join26(currentDir, "..", "src", "bundled", apiType, `${id}.json`),
4108
4226
  // Running from dist/
4109
- join24(currentDir, "..", "bundled", apiType, `${id}.json`)
4227
+ join26(currentDir, "..", "bundled", apiType, `${id}.json`)
4110
4228
  // Alternative dist layout
4111
4229
  ];
4112
- const bundledPath = bundledCandidates.find((p) => existsSync24(p)) || null;
4230
+ const bundledPath = bundledCandidates.find((p) => existsSync25(p)) || null;
4113
4231
  if (bundledPath) {
4114
- const data = JSON.parse(readFileSync16(bundledPath, "utf-8"));
4232
+ const data = JSON.parse(readFileSync17(bundledPath, "utf-8"));
4115
4233
  console.log(JSON.stringify(data, null, 2));
4116
4234
  return;
4117
4235
  }
@@ -4120,10 +4238,10 @@ async function cmdGet(type, id) {
4120
4238
  return;
4121
4239
  }
4122
4240
  async function cmdValidate(path) {
4123
- const essencePath = path || join24(process.cwd(), "decantr.essence.json");
4241
+ const essencePath = path || join26(process.cwd(), "decantr.essence.json");
4124
4242
  let raw;
4125
4243
  try {
4126
- raw = readFileSync16(essencePath, "utf-8");
4244
+ raw = readFileSync17(essencePath, "utf-8");
4127
4245
  } catch {
4128
4246
  console.error(error3(`Could not read ${essencePath}`));
4129
4247
  process.exitCode = 1;
@@ -4177,7 +4295,7 @@ async function cmdList(type, sort, recommended, intelligenceSource) {
4177
4295
  return;
4178
4296
  }
4179
4297
  const registryClient = new RegistryClient({
4180
- cacheDir: join24(process.cwd(), ".decantr", "cache")
4298
+ cacheDir: join26(process.cwd(), ".decantr", "cache")
4181
4299
  });
4182
4300
  const result = await registryClient.fetchContentList(
4183
4301
  type,
@@ -4227,6 +4345,11 @@ async function cmdInit(args) {
4227
4345
  const projectRoot = process.cwd();
4228
4346
  console.log(heading2("Decantr Project Setup"));
4229
4347
  const detected = detectProject(projectRoot);
4348
+ const workflowSeed = readBrownfieldInitSeed(projectRoot);
4349
+ const brownfieldAttach = Boolean(workflowSeed) || hasExistingProjectFootprint(detected);
4350
+ if (workflowSeed) {
4351
+ console.log(dim3(" Found .decantr/init-seed.json brownfield guidance."));
4352
+ }
4230
4353
  if (detected.existingEssence && !args.existing) {
4231
4354
  console.log(`${YELLOW9}Warning: decantr.essence.json already exists.${RESET13}`);
4232
4355
  const overwrite = await confirm("Overwrite existing configuration?", false);
@@ -4251,7 +4374,7 @@ async function cmdInit(args) {
4251
4374
  }
4252
4375
  }
4253
4376
  const registryClient = new RegistryClient({
4254
- cacheDir: join24(projectRoot, ".decantr", "cache"),
4377
+ cacheDir: join26(projectRoot, ".decantr", "cache"),
4255
4378
  apiUrl: args.registry,
4256
4379
  offline: args.offline
4257
4380
  });
@@ -4265,6 +4388,7 @@ async function cmdInit(args) {
4265
4388
  }
4266
4389
  let selectedBlueprint = "default";
4267
4390
  let registrySource = "cache";
4391
+ const preferContractOnly = brownfieldAttach && !requestedBlueprint && !requestedArchetype;
4268
4392
  if (args.yes) {
4269
4393
  selectedBlueprint = args.blueprint || "default";
4270
4394
  } else if (!apiAvailable) {
@@ -4299,7 +4423,7 @@ ${YELLOW9}You're offline. Scaffolding minimal Decantr project.${RESET13}`);
4299
4423
  ${YELLOW9}You're offline. Scaffolding Decantr default.${RESET13}`);
4300
4424
  console.log(dim3("Run `decantr upgrade` when online, or visit decantr.ai/registry\n"));
4301
4425
  selectedBlueprint = "default";
4302
- } else {
4426
+ } else if (!preferContractOnly) {
4303
4427
  console.log(dim3("Fetching registry content..."));
4304
4428
  const blueprintsResult2 = await registryClient.fetchBlueprints();
4305
4429
  registrySource = blueprintsResult2.source.type === "api" ? "api" : "cache";
@@ -4327,9 +4451,9 @@ ${YELLOW9}You're offline. Scaffolding Decantr default.${RESET13}`);
4327
4451
  if (args.yes || selectedBlueprint !== "default") {
4328
4452
  const flags = parseFlags(args, detected);
4329
4453
  flags.blueprint = selectedBlueprint !== "default" ? selectedBlueprint : flags.blueprint;
4330
- options = mergeWithDefaults(flags, detected);
4454
+ options = mergeWithDefaults(flags, detected, workflowSeed ?? void 0);
4331
4455
  } else {
4332
- options = await runInteractivePrompts(detected, archetypes, blueprints, themes);
4456
+ options = await runInteractivePrompts(detected, archetypes, blueprints, themes, workflowSeed ?? void 0);
4333
4457
  userExplicit.theme = true;
4334
4458
  userExplicit.mode = true;
4335
4459
  userExplicit.shape = true;
@@ -4512,6 +4636,11 @@ ${YELLOW9}You're offline. Scaffolding Decantr default.${RESET13}`);
4512
4636
  if (result.gitignoreUpdated) {
4513
4637
  console.log(` ${dim3(".gitignore updated")}`);
4514
4638
  }
4639
+ if (!existsSync25(join26(projectRoot, "package.json"))) {
4640
+ console.log("");
4641
+ console.log(dim3(` Note: ${cyan3("decantr init")} created Decantr contract/context files only.`));
4642
+ console.log(dim3(` For a runnable starter in a new directory, prefer ${cyan3("decantr new <name> --blueprint=...")}.`));
4643
+ }
4515
4644
  console.log("");
4516
4645
  console.log(" Next steps:");
4517
4646
  console.log(" 1. Read DECANTR.md for methodology, CSS approach, and guard rules");
@@ -4529,7 +4658,7 @@ ${YELLOW9}You're offline. Scaffolding Decantr default.${RESET13}`);
4529
4658
  console.log(` ${cyan3("decantr upgrade")} Update to latest patterns`);
4530
4659
  console.log(` ${cyan3("decantr check")} Detect drift issues`);
4531
4660
  console.log(` ${cyan3("decantr migrate")} Migrate v2 essence to v3`);
4532
- const essenceContent = readFileSync16(result.essencePath, "utf-8");
4661
+ const essenceContent = readFileSync17(result.essencePath, "utf-8");
4533
4662
  const essence = JSON.parse(essenceContent);
4534
4663
  if (essence.version !== "3.1.0") {
4535
4664
  const validation = validateEssence2(essence);
@@ -4576,16 +4705,16 @@ Validation warnings: ${validation.errors.join(", ")}`));
4576
4705
  }
4577
4706
  async function cmdStatus() {
4578
4707
  const projectRoot = process.cwd();
4579
- const essencePath = join24(projectRoot, "decantr.essence.json");
4580
- const projectJsonPath = join24(projectRoot, ".decantr", "project.json");
4708
+ const essencePath = join26(projectRoot, "decantr.essence.json");
4709
+ const projectJsonPath = join26(projectRoot, ".decantr", "project.json");
4581
4710
  console.log(heading2("Decantr Project Status"));
4582
- if (!existsSync24(essencePath)) {
4711
+ if (!existsSync25(essencePath)) {
4583
4712
  console.log(`${RED11}No decantr.essence.json found.${RESET13}`);
4584
4713
  console.log(dim3('Run "decantr init" to create one.'));
4585
4714
  return;
4586
4715
  }
4587
4716
  try {
4588
- const essence = JSON.parse(readFileSync16(essencePath, "utf-8"));
4717
+ const essence = JSON.parse(readFileSync17(essencePath, "utf-8"));
4589
4718
  const validation = validateEssence2(essence);
4590
4719
  const essenceVersion = isV36(essence) ? "v3" : "v2";
4591
4720
  console.log(`${BOLD6}Essence:${RESET13}`);
@@ -4634,9 +4763,9 @@ async function cmdStatus() {
4634
4763
  }
4635
4764
  console.log("");
4636
4765
  console.log(`${BOLD6}Sync Status:${RESET13}`);
4637
- if (existsSync24(projectJsonPath)) {
4766
+ if (existsSync25(projectJsonPath)) {
4638
4767
  try {
4639
- const projectJson = JSON.parse(readFileSync16(projectJsonPath, "utf-8"));
4768
+ const projectJson = JSON.parse(readFileSync17(projectJsonPath, "utf-8"));
4640
4769
  const syncStatus = projectJson.sync?.status || "unknown";
4641
4770
  const lastSync = projectJson.sync?.lastSync || "never";
4642
4771
  const source = projectJson.sync?.registrySource || "unknown";
@@ -4654,7 +4783,7 @@ async function cmdStatus() {
4654
4783
  }
4655
4784
  async function cmdSync() {
4656
4785
  const projectRoot = process.cwd();
4657
- const cacheDir = join24(projectRoot, ".decantr", "cache");
4786
+ const cacheDir = join26(projectRoot, ".decantr", "cache");
4658
4787
  console.log(heading2("Syncing registry content..."));
4659
4788
  const result = await syncRegistry(cacheDir);
4660
4789
  if (result.synced.length > 0) {
@@ -4843,14 +4972,14 @@ ${BOLD6}Examples:${RESET13}
4843
4972
  process.exitCode = 1;
4844
4973
  return;
4845
4974
  }
4846
- const themePath = join24(projectRoot, ".decantr", "custom", "themes", `${name}.json`);
4847
- if (!existsSync24(themePath)) {
4975
+ const themePath = join26(projectRoot, ".decantr", "custom", "themes", `${name}.json`);
4976
+ if (!existsSync25(themePath)) {
4848
4977
  console.error(error3(`Theme "${name}" not found at ${themePath}`));
4849
4978
  process.exitCode = 1;
4850
4979
  return;
4851
4980
  }
4852
4981
  try {
4853
- const theme = JSON.parse(readFileSync16(themePath, "utf-8"));
4982
+ const theme = JSON.parse(readFileSync17(themePath, "utf-8"));
4854
4983
  const result = validateCustomTheme(theme);
4855
4984
  if (result.valid) {
4856
4985
  console.log(success3(`Custom theme "${name}" is valid`));
@@ -4966,9 +5095,9 @@ ${BOLD6}Init Options:${RESET13}
4966
5095
  --registry Custom registry URL
4967
5096
 
4968
5097
  ${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)
5098
+ ${cyan3("new")} Create a new greenfield workspace and bootstrap the available starter adapter
5099
+ ${cyan3("magic")} Greenfield-first intent flow; steers existing apps into analyze + init
5100
+ ${cyan3("init")} Attach Decantr contract/context files to an existing project or empty workspace
4972
5101
  ${cyan3("status")} Show project status, DNA axioms, and blueprint info
4973
5102
  ${cyan3("sync")} Sync registry content from API
4974
5103
  ${cyan3("audit")} Audit the project or critique a specific file against compiled packs
@@ -4986,7 +5115,7 @@ ${BOLD6}Commands:${RESET13}
4986
5115
  ${cyan3("publish")} Publish a custom content item to the community registry
4987
5116
  ${cyan3("login")} Authenticate with the Decantr registry
4988
5117
  ${cyan3("logout")} Remove stored credentials
4989
- ${cyan3("analyze")} Scan existing project and produce analysis report
5118
+ ${cyan3("analyze")} Brownfield entrypoint: scan an existing project and emit attach guidance
4990
5119
  ${cyan3("export")} Export design tokens to framework format (shadcn, tailwind, css-vars)
4991
5120
  ${cyan3("registry")} Registry management and intelligence summary
4992
5121
  ${cyan3("upgrade")} Check for content updates from registry
@@ -4996,7 +5125,7 @@ ${BOLD6}Examples:${RESET13}
4996
5125
  decantr new my-app --blueprint=carbon-ai-portal
4997
5126
  decantr magic "AI chatbot with dark cyber theme \u2014 bold and futuristic"
4998
5127
  decantr init
4999
- decantr init --blueprint=saas-dashboard --theme=luminarum --yes
5128
+ decantr init --existing --blueprint=saas-dashboard --theme=luminarum --yes
5000
5129
  decantr status
5001
5130
  decantr audit
5002
5131
  decantr audit src/pages/HomePage.tsx
@@ -5017,6 +5146,15 @@ ${BOLD6}Examples:${RESET13}
5017
5146
  decantr registry audit-project --namespace @official --json
5018
5147
  decantr registry audit-project --namespace @official --dist dist --sources src
5019
5148
  decantr create pattern my-card
5149
+
5150
+ ${BOLD6}Workflow Model:${RESET13}
5151
+ ${cyan3("Greenfield blueprint")} decantr new / decantr magic
5152
+ ${cyan3("Brownfield adoption")} decantr analyze -> decantr init --existing
5153
+ ${cyan3("Hybrid composition")} decantr add/remove, decantr theme switch, decantr registry, decantr upgrade
5154
+
5155
+ ${BOLD6}Bootstrap adapters:${RESET13}
5156
+ Current runnable starter adapter: ${cyan3("react-vite")}
5157
+ Other contract targets stay framework-agnostic, but currently initialize in contract-only mode until their starter adapters land.
5020
5158
  `);
5021
5159
  }
5022
5160
  async function main() {
@@ -5095,7 +5233,7 @@ async function main() {
5095
5233
  break;
5096
5234
  }
5097
5235
  case "upgrade": {
5098
- const { cmdUpgrade } = await import("./upgrade-XNUAON3G.js");
5236
+ const { cmdUpgrade } = await import("./upgrade-WE7Y7ZOE.js");
5099
5237
  const applyFlag = args.includes("--apply");
5100
5238
  await cmdUpgrade(process.cwd(), { apply: applyFlag });
5101
5239
  break;