@decantr/cli 1.7.24 → 1.7.25

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,15 +14,15 @@ import {
14
14
  scaffoldProject,
15
15
  syncRegistry,
16
16
  writeExecutionPackBundleArtifacts
17
- } from "./chunk-RH6IQ7YX.js";
17
+ } from "./chunk-3K6HWLD5.js";
18
18
  import {
19
19
  buildGuardRegistryContext,
20
20
  scanProjectInteractions
21
21
  } from "./chunk-QRQCPD3C.js";
22
22
 
23
23
  // src/index.ts
24
- import { existsSync as existsSync25, mkdirSync as mkdirSync10, readdirSync as readdirSync6, readFileSync as readFileSync18, writeFileSync as writeFileSync13 } from "fs";
25
- import { basename as basename2, dirname as dirname2, isAbsolute, join as join26, resolve as resolve3 } from "path";
24
+ import { existsSync as existsSync27, mkdirSync as mkdirSync11, readdirSync as readdirSync7, readFileSync as readFileSync20, writeFileSync as writeFileSync14 } from "fs";
25
+ import { basename as basename2, dirname as dirname4, isAbsolute, join as join28, resolve as resolve4 } from "path";
26
26
  import { fileURLToPath as fileURLToPath2 } from "url";
27
27
  import { evaluateGuard, isV3 as isV36, validateEssence as validateEssence2 } from "@decantr/essence-spec";
28
28
  import {
@@ -229,13 +229,120 @@ async function cmdAddFeature(feature, args, projectRoot = process.cwd()) {
229
229
  console.log(`${GREEN}Derived files refreshed.${RESET}`);
230
230
  }
231
231
 
232
+ // src/assistant-bridge.ts
233
+ import { appendFileSync, existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
234
+ import { dirname, join as join3 } from "path";
235
+ var START = "<!-- decantr:assistant-bridge:start -->";
236
+ var END = "<!-- decantr:assistant-bridge:end -->";
237
+ function buildAssistantBridgeContent(input) {
238
+ const lines = [];
239
+ lines.push("# Decantr Assistant Bridge");
240
+ lines.push("");
241
+ lines.push(
242
+ "Use this bridge when an AI assistant works in a repository that already has local rules."
243
+ );
244
+ lines.push("");
245
+ lines.push(`- Workflow mode: ${input.workflowMode}`);
246
+ lines.push(`- Detected framework: ${input.detected.framework}`);
247
+ lines.push(`- Package manager: ${input.detected.packageManager}`);
248
+ lines.push(
249
+ `- Existing rule files: ${input.detected.existingRuleFiles.length > 0 ? input.detected.existingRuleFiles.join(", ") : "none detected"}`
250
+ );
251
+ lines.push(`- Bridge mode: ${input.assistantBridge}`);
252
+ lines.push("");
253
+ lines.push("## Suggested Rule Block");
254
+ lines.push("");
255
+ lines.push(START);
256
+ lines.push(
257
+ "Before implementing Decantr-scoped work, read `DECANTR.md`, `decantr.essence.json`, and the compiled packs in `.decantr/context/`."
258
+ );
259
+ lines.push(
260
+ "For brownfield adoption, preserve existing framework, routing, styling, package manager, and build conventions unless the Decantr contract explicitly requires a reviewed change."
261
+ );
262
+ lines.push(
263
+ "Use `.decantr/context/scaffold-pack.md` as the primary compact contract, then read matching section/page packs for the surface being changed."
264
+ );
265
+ lines.push(
266
+ "Do not install `@decantr/css` or rewrite styling unless the project adoption mode says `decantr-css` or the task explicitly asks for it."
267
+ );
268
+ lines.push(END);
269
+ lines.push("");
270
+ return `${lines.join("\n")}
271
+ `;
272
+ }
273
+ function writeAssistantBridgePreview(input) {
274
+ const contextDir = join3(input.projectRoot, ".decantr", "context");
275
+ mkdirSync2(contextDir, { recursive: true });
276
+ const bridgePath = join3(contextDir, "assistant-bridge.md");
277
+ writeFileSync3(bridgePath, buildAssistantBridgeContent(input));
278
+ return bridgePath;
279
+ }
280
+ function bridgeBlock() {
281
+ return [
282
+ START,
283
+ "Before implementing Decantr-scoped work, read `DECANTR.md`, `decantr.essence.json`, and `.decantr/context/scaffold-pack.md` first.",
284
+ "For brownfield adoption, preserve existing framework, routing, styling, package manager, and build conventions unless the Decantr contract explicitly requires a reviewed change.",
285
+ "Do not install `@decantr/css` or rewrite styling unless the project adoption mode says `decantr-css` or the task explicitly asks for it.",
286
+ END,
287
+ ""
288
+ ].join("\n");
289
+ }
290
+ function upsertMarkdownBlock(path) {
291
+ const block = bridgeBlock();
292
+ if (!existsSync3(path)) {
293
+ mkdirSync2(dirname(path), { recursive: true });
294
+ writeFileSync3(path, `# Project Rules
295
+
296
+ ${block}`);
297
+ return true;
298
+ }
299
+ const content = readFileSync3(path, "utf-8");
300
+ if (content.includes(START) && content.includes(END)) return false;
301
+ appendFileSync(path, `
302
+
303
+ ${block}`);
304
+ return true;
305
+ }
306
+ function writeCursorRule(projectRoot) {
307
+ const path = join3(projectRoot, ".cursor", "rules", "decantr.mdc");
308
+ const content = `---
309
+ description: Decantr project contract and brownfield adoption bridge
310
+ alwaysApply: true
311
+ ---
312
+
313
+ ${bridgeBlock()}`;
314
+ if (existsSync3(path) && readFileSync3(path, "utf-8") === content) return false;
315
+ mkdirSync2(dirname(path), { recursive: true });
316
+ writeFileSync3(path, content);
317
+ return true;
318
+ }
319
+ function applyAssistantBridge(projectRoot, detected) {
320
+ const updated = [];
321
+ const markdownTargets = ["CLAUDE.md", "AGENTS.md", "GEMINI.md", "copilot-instructions.md"];
322
+ for (const target of markdownTargets) {
323
+ if (detected.existingRuleFiles.includes(target) && upsertMarkdownBlock(join3(projectRoot, target))) {
324
+ updated.push(target);
325
+ }
326
+ }
327
+ if (detected.existingRuleFiles.includes(".cursorrules")) {
328
+ if (upsertMarkdownBlock(join3(projectRoot, ".cursorrules"))) updated.push(".cursorrules");
329
+ }
330
+ if (detected.existingRuleFiles.includes(".cursor/rules")) {
331
+ if (writeCursorRule(projectRoot)) updated.push(".cursor/rules/decantr.mdc");
332
+ }
333
+ if (updated.length === 0 && detected.existingRuleFiles.length === 0) {
334
+ if (writeCursorRule(projectRoot)) updated.push(".cursor/rules/decantr.mdc");
335
+ }
336
+ return updated;
337
+ }
338
+
232
339
  // src/commands/analyze.ts
233
- import { existsSync as existsSync11, mkdirSync as mkdirSync2, writeFileSync as writeFileSync3 } from "fs";
234
- import { join as join11 } from "path";
340
+ import { existsSync as existsSync12, mkdirSync as mkdirSync3, writeFileSync as writeFileSync4 } from "fs";
341
+ import { join as join12 } from "path";
235
342
 
236
343
  // src/analyzers/components.ts
237
- import { existsSync as existsSync3, readdirSync, statSync } from "fs";
238
- import { join as join3 } from "path";
344
+ import { existsSync as existsSync4, readdirSync, statSync } from "fs";
345
+ import { join as join4 } from "path";
239
346
  var PAGE_EXTENSIONS = /* @__PURE__ */ new Set([".tsx", ".ts", ".jsx", ".js"]);
240
347
  var ROOT_COMPONENT_CANDIDATES = [
241
348
  "src/App.tsx",
@@ -257,7 +364,7 @@ function countFilesRecursive(dir, extensions) {
257
364
  }
258
365
  for (const entry of entries) {
259
366
  if (entry.startsWith(".") || entry === "node_modules") continue;
260
- const fullPath = join3(dir, entry);
367
+ const fullPath = join4(dir, entry);
261
368
  try {
262
369
  const stat = statSync(fullPath);
263
370
  if (stat.isDirectory()) {
@@ -283,7 +390,7 @@ function countPageFiles(dir) {
283
390
  }
284
391
  for (const entry of entries) {
285
392
  if (entry.startsWith(".") || entry === "node_modules") continue;
286
- const fullPath = join3(dir, entry);
393
+ const fullPath = join4(dir, entry);
287
394
  try {
288
395
  const stat = statSync(fullPath);
289
396
  if (stat.isDirectory()) {
@@ -301,27 +408,27 @@ function countPageFiles(dir) {
301
408
  function scanComponents(projectRoot) {
302
409
  let pageCount = 0;
303
410
  const componentDirs = [];
304
- const appDirs = [join3(projectRoot, "src", "app"), join3(projectRoot, "app")];
411
+ const appDirs = [join4(projectRoot, "src", "app"), join4(projectRoot, "app")];
305
412
  for (const dir of appDirs) {
306
- if (existsSync3(dir)) {
413
+ if (existsSync4(dir)) {
307
414
  pageCount += countPageFiles(dir);
308
415
  }
309
416
  }
310
- const pagesDirs = [join3(projectRoot, "src", "pages"), join3(projectRoot, "pages")];
417
+ const pagesDirs = [join4(projectRoot, "src", "pages"), join4(projectRoot, "pages")];
311
418
  for (const dir of pagesDirs) {
312
- if (existsSync3(dir)) {
419
+ if (existsSync4(dir)) {
313
420
  pageCount += countFilesRecursive(dir, PAGE_EXTENSIONS);
314
421
  }
315
422
  }
316
423
  let componentCount = 0;
317
424
  const componentPaths = [
318
- join3(projectRoot, "src", "components"),
319
- join3(projectRoot, "components"),
320
- join3(projectRoot, "src", "ui"),
321
- join3(projectRoot, "ui")
425
+ join4(projectRoot, "src", "components"),
426
+ join4(projectRoot, "components"),
427
+ join4(projectRoot, "src", "ui"),
428
+ join4(projectRoot, "ui")
322
429
  ];
323
430
  for (const dir of componentPaths) {
324
- if (existsSync3(dir)) {
431
+ if (existsSync4(dir)) {
325
432
  const count = countFilesRecursive(dir, PAGE_EXTENSIONS);
326
433
  if (count > 0) {
327
434
  componentCount += count;
@@ -331,7 +438,7 @@ function scanComponents(projectRoot) {
331
438
  }
332
439
  }
333
440
  const hasRootAppComponent = ROOT_COMPONENT_CANDIDATES.some(
334
- (relativePath) => existsSync3(join3(projectRoot, relativePath))
441
+ (relativePath) => existsSync4(join4(projectRoot, relativePath))
335
442
  );
336
443
  if (pageCount === 0 && hasRootAppComponent) {
337
444
  pageCount = 1;
@@ -350,8 +457,8 @@ function scanComponents(projectRoot) {
350
457
  }
351
458
 
352
459
  // src/analyzers/dependencies.ts
353
- import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
354
- import { join as join4 } from "path";
460
+ import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
461
+ import { join as join5 } from "path";
355
462
  var CATEGORIES = {
356
463
  ui: [
357
464
  "react",
@@ -501,11 +608,11 @@ function scanDependencies(projectRoot) {
501
608
  styling: [],
502
609
  other: []
503
610
  };
504
- const pkgPath = join4(projectRoot, "package.json");
505
- if (!existsSync4(pkgPath)) return result;
611
+ const pkgPath = join5(projectRoot, "package.json");
612
+ if (!existsSync5(pkgPath)) return result;
506
613
  let pkg;
507
614
  try {
508
- pkg = JSON.parse(readFileSync3(pkgPath, "utf-8"));
615
+ pkg = JSON.parse(readFileSync4(pkgPath, "utf-8"));
509
616
  } catch {
510
617
  return result;
511
618
  }
@@ -525,8 +632,8 @@ function scanDependencies(projectRoot) {
525
632
  }
526
633
 
527
634
  // src/analyzers/features.ts
528
- import { existsSync as existsSync5, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
529
- import { join as join5 } from "path";
635
+ import { existsSync as existsSync6, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
636
+ import { join as join6 } from "path";
530
637
  var FEATURE_PATTERNS = {
531
638
  auth: [
532
639
  "login",
@@ -579,7 +686,7 @@ function collectPaths(dir, baseDir, depth = 0) {
579
686
  for (const entry of entries) {
580
687
  if (entry.startsWith(".") || entry.startsWith("_") || entry === "node_modules" || entry === "api")
581
688
  continue;
582
- const fullPath = join5(dir, entry);
689
+ const fullPath = join6(dir, entry);
583
690
  const relPath = fullPath.slice(baseDir.length + 1);
584
691
  paths.push(relPath);
585
692
  try {
@@ -595,16 +702,16 @@ function scanFeatures(projectRoot) {
595
702
  const detected = [];
596
703
  const evidence = {};
597
704
  const scanDirs = [
598
- join5(projectRoot, "src", "app"),
599
- join5(projectRoot, "app"),
600
- join5(projectRoot, "src", "pages"),
601
- join5(projectRoot, "pages"),
602
- join5(projectRoot, "src", "components"),
603
- join5(projectRoot, "components")
705
+ join6(projectRoot, "src", "app"),
706
+ join6(projectRoot, "app"),
707
+ join6(projectRoot, "src", "pages"),
708
+ join6(projectRoot, "pages"),
709
+ join6(projectRoot, "src", "components"),
710
+ join6(projectRoot, "components")
604
711
  ];
605
712
  const allPaths = [];
606
713
  for (const dir of scanDirs) {
607
- if (existsSync5(dir)) {
714
+ if (existsSync6(dir)) {
608
715
  allPaths.push(...collectPaths(dir, projectRoot));
609
716
  }
610
717
  }
@@ -628,8 +735,8 @@ function scanFeatures(projectRoot) {
628
735
  }
629
736
 
630
737
  // src/analyzers/layout.ts
631
- import { existsSync as existsSync6, readdirSync as readdirSync3, readFileSync as readFileSync4 } from "fs";
632
- import { join as join6 } from "path";
738
+ import { existsSync as existsSync7, readdirSync as readdirSync3, readFileSync as readFileSync5 } from "fs";
739
+ import { join as join7 } from "path";
633
740
  var SIDEBAR_PATTERNS = ["sidebar", "side-bar", "sidenav", "side-nav", "drawer", "aside"];
634
741
  var NAV_PATTERNS = ["nav", "navbar", "header", "top-bar", "topbar", "app-bar", "appbar"];
635
742
  var FOOTER_PATTERNS = ["footer", "bottom-bar", "bottombar"];
@@ -640,12 +747,12 @@ function containsPattern(text, patterns) {
640
747
  function checkComponentDirs(projectRoot) {
641
748
  const result = { sidebar: false, nav: false, footer: false };
642
749
  const componentDirs = [
643
- join6(projectRoot, "src", "components"),
644
- join6(projectRoot, "components"),
645
- join6(projectRoot, "src", "ui")
750
+ join7(projectRoot, "src", "components"),
751
+ join7(projectRoot, "components"),
752
+ join7(projectRoot, "src", "ui")
646
753
  ];
647
754
  for (const dir of componentDirs) {
648
- if (!existsSync6(dir)) continue;
755
+ if (!existsSync7(dir)) continue;
649
756
  let entries;
650
757
  try {
651
758
  entries = readdirSync3(dir);
@@ -664,16 +771,16 @@ function checkComponentDirs(projectRoot) {
664
771
  function checkLayoutFiles(projectRoot) {
665
772
  const result = { sidebar: false, nav: false, footer: false };
666
773
  const layoutPaths = [
667
- join6(projectRoot, "src", "app", "layout.tsx"),
668
- join6(projectRoot, "src", "app", "layout.jsx"),
669
- join6(projectRoot, "app", "layout.tsx"),
670
- join6(projectRoot, "app", "layout.jsx")
774
+ join7(projectRoot, "src", "app", "layout.tsx"),
775
+ join7(projectRoot, "src", "app", "layout.jsx"),
776
+ join7(projectRoot, "app", "layout.tsx"),
777
+ join7(projectRoot, "app", "layout.jsx")
671
778
  ];
672
779
  for (const layoutPath of layoutPaths) {
673
- if (!existsSync6(layoutPath)) continue;
780
+ if (!existsSync7(layoutPath)) continue;
674
781
  let content;
675
782
  try {
676
- content = readFileSync4(layoutPath, "utf-8");
783
+ content = readFileSync5(layoutPath, "utf-8");
677
784
  } catch {
678
785
  continue;
679
786
  }
@@ -684,16 +791,16 @@ function checkLayoutFiles(projectRoot) {
684
791
  const subLayoutDirs = ["dashboard", "admin", "app"];
685
792
  for (const sub of subLayoutDirs) {
686
793
  const paths = [
687
- join6(projectRoot, "src", "app", sub, "layout.tsx"),
688
- join6(projectRoot, "src", "app", sub, "layout.jsx"),
689
- join6(projectRoot, "app", sub, "layout.tsx"),
690
- join6(projectRoot, "app", sub, "layout.jsx")
794
+ join7(projectRoot, "src", "app", sub, "layout.tsx"),
795
+ join7(projectRoot, "src", "app", sub, "layout.jsx"),
796
+ join7(projectRoot, "app", sub, "layout.tsx"),
797
+ join7(projectRoot, "app", sub, "layout.jsx")
691
798
  ];
692
799
  for (const layoutPath of paths) {
693
- if (!existsSync6(layoutPath)) continue;
800
+ if (!existsSync7(layoutPath)) continue;
694
801
  let content;
695
802
  try {
696
- content = readFileSync4(layoutPath, "utf-8");
803
+ content = readFileSync5(layoutPath, "utf-8");
697
804
  } catch {
698
805
  continue;
699
806
  }
@@ -715,10 +822,10 @@ function inferShellPattern(hasSidebar, hasTopNav, hasFooter) {
715
822
  return "main-only";
716
823
  }
717
824
  function inferShellPatternFromDecantrContract(projectRoot) {
718
- const essencePath = join6(projectRoot, "decantr.essence.json");
719
- if (!existsSync6(essencePath)) return null;
825
+ const essencePath = join7(projectRoot, "decantr.essence.json");
826
+ if (!existsSync7(essencePath)) return null;
720
827
  try {
721
- const essence = JSON.parse(readFileSync4(essencePath, "utf-8"));
828
+ const essence = JSON.parse(readFileSync5(essencePath, "utf-8"));
722
829
  const sectionShells = essence.blueprint?.sections?.map((section) => section.shell).filter((shell) => typeof shell === "string" && shell.length > 0) ?? [];
723
830
  if (sectionShells.length === 0 && essence.blueprint?.shell) {
724
831
  return `${essence.blueprint.shell} (contract)`;
@@ -754,8 +861,8 @@ function scanLayout(projectRoot) {
754
861
  }
755
862
 
756
863
  // src/analyzers/routes.ts
757
- import { existsSync as existsSync7, readdirSync as readdirSync4, readFileSync as readFileSync5, statSync as statSync3 } from "fs";
758
- import { join as join7, relative } from "path";
864
+ import { existsSync as existsSync8, readdirSync as readdirSync4, readFileSync as readFileSync6, statSync as statSync3 } from "fs";
865
+ import { join as join8, relative } from "path";
759
866
  var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".next", ".git", "api", "_app", "_document"]);
760
867
  function shouldSkipDir(name) {
761
868
  return name.startsWith("_") || name.startsWith(".") || SKIP_DIRS.has(name);
@@ -795,13 +902,13 @@ function walkAppDir(dir, baseDir, segments) {
795
902
  const pageFile = entries.find((e) => e.startsWith("page."));
796
903
  routes.push({
797
904
  path: routePath || "/",
798
- file: relative(baseDir, join7(dir, pageFile)),
905
+ file: relative(baseDir, join8(dir, pageFile)),
799
906
  hasLayout
800
907
  });
801
908
  }
802
909
  for (const entry of entries) {
803
910
  if (shouldSkipDir(entry)) continue;
804
- const fullPath = join7(dir, entry);
911
+ const fullPath = join8(dir, entry);
805
912
  try {
806
913
  if (!statSync3(fullPath).isDirectory()) continue;
807
914
  } catch {
@@ -823,7 +930,7 @@ function walkPagesDir(dir, baseDir, segments) {
823
930
  }
824
931
  for (const entry of entries) {
825
932
  if (shouldSkipDir(entry)) continue;
826
- const fullPath = join7(dir, entry);
933
+ const fullPath = join8(dir, entry);
827
934
  try {
828
935
  const stat = statSync3(fullPath);
829
936
  if (stat.isDirectory()) {
@@ -859,7 +966,7 @@ function collectRouteCandidateFiles(dir, files, depth = 0) {
859
966
  }
860
967
  for (const entry of entries) {
861
968
  if (entry.startsWith(".") || entry === "node_modules") continue;
862
- const fullPath = join7(dir, entry);
969
+ const fullPath = join8(dir, entry);
863
970
  try {
864
971
  const stat = statSync3(fullPath);
865
972
  if (stat.isDirectory()) {
@@ -875,16 +982,16 @@ function collectRouteCandidateFiles(dir, files, depth = 0) {
875
982
  }
876
983
  }
877
984
  function scanReactRouter(projectRoot) {
878
- const candidateDirs = [join7(projectRoot, "src"), projectRoot];
985
+ const candidateDirs = [join8(projectRoot, "src"), projectRoot];
879
986
  const candidateFiles = [];
880
987
  for (const dir of candidateDirs) {
881
- if (existsSync7(dir)) collectRouteCandidateFiles(dir, candidateFiles);
988
+ if (existsSync8(dir)) collectRouteCandidateFiles(dir, candidateFiles);
882
989
  }
883
990
  const routeMap = /* @__PURE__ */ new Map();
884
991
  for (const absolutePath of candidateFiles) {
885
992
  let content;
886
993
  try {
887
- content = readFileSync5(absolutePath, "utf-8");
994
+ content = readFileSync6(absolutePath, "utf-8");
888
995
  } catch {
889
996
  continue;
890
997
  }
@@ -914,18 +1021,18 @@ function scanReactRouter(projectRoot) {
914
1021
  return [...routeMap.values()];
915
1022
  }
916
1023
  function scanRoutes(projectRoot) {
917
- const appDirs = [join7(projectRoot, "src", "app"), join7(projectRoot, "app")];
1024
+ const appDirs = [join8(projectRoot, "src", "app"), join8(projectRoot, "app")];
918
1025
  for (const appDir of appDirs) {
919
- if (existsSync7(appDir)) {
1026
+ if (existsSync8(appDir)) {
920
1027
  const routes = walkAppDir(appDir, projectRoot, []);
921
1028
  if (routes.length > 0) {
922
1029
  return { strategy: "app-router", routes };
923
1030
  }
924
1031
  }
925
1032
  }
926
- const pagesDirs = [join7(projectRoot, "src", "pages"), join7(projectRoot, "pages")];
1033
+ const pagesDirs = [join8(projectRoot, "src", "pages"), join8(projectRoot, "pages")];
927
1034
  for (const pagesDir of pagesDirs) {
928
- if (existsSync7(pagesDir)) {
1035
+ if (existsSync8(pagesDir)) {
929
1036
  const routes = walkPagesDir(pagesDir, projectRoot, []);
930
1037
  if (routes.length > 0) {
931
1038
  return { strategy: "pages-router", routes };
@@ -940,8 +1047,8 @@ function scanRoutes(projectRoot) {
940
1047
  }
941
1048
 
942
1049
  // src/analyzers/styling.ts
943
- import { existsSync as existsSync8, readFileSync as readFileSync6 } from "fs";
944
- import { join as join8 } from "path";
1050
+ import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
1051
+ import { join as join9 } from "path";
945
1052
  var TAILWIND_CONFIGS = [
946
1053
  "tailwind.config.js",
947
1054
  "tailwind.config.ts",
@@ -1003,10 +1110,10 @@ function detectDarkMode(projectRoot, cssContents) {
1003
1110
  "app/layout.jsx"
1004
1111
  ];
1005
1112
  for (const rel of layoutPaths) {
1006
- const fullPath = join8(projectRoot, rel);
1007
- if (existsSync8(fullPath)) {
1113
+ const fullPath = join9(projectRoot, rel);
1114
+ if (existsSync9(fullPath)) {
1008
1115
  try {
1009
- const layoutContent = readFileSync6(fullPath, "utf-8");
1116
+ const layoutContent = readFileSync7(fullPath, "utf-8");
1010
1117
  if (layoutContent.includes('className="dark"') || layoutContent.includes("className='dark'") || layoutContent.includes('class="dark"')) {
1011
1118
  return true;
1012
1119
  }
@@ -1014,10 +1121,10 @@ function detectDarkMode(projectRoot, cssContents) {
1014
1121
  }
1015
1122
  }
1016
1123
  }
1017
- const pkgPath = join8(projectRoot, "package.json");
1018
- if (existsSync8(pkgPath)) {
1124
+ const pkgPath = join9(projectRoot, "package.json");
1125
+ if (existsSync9(pkgPath)) {
1019
1126
  try {
1020
- const pkg = JSON.parse(readFileSync6(pkgPath, "utf-8"));
1127
+ const pkg = JSON.parse(readFileSync7(pkgPath, "utf-8"));
1021
1128
  const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
1022
1129
  if (allDeps["next-themes"] || allDeps["theme-toggle"] || allDeps["use-dark-mode"]) {
1023
1130
  return true;
@@ -1025,10 +1132,10 @@ function detectDarkMode(projectRoot, cssContents) {
1025
1132
  } catch {
1026
1133
  }
1027
1134
  }
1028
- const essencePath = join8(projectRoot, "decantr.essence.json");
1029
- if (existsSync8(essencePath)) {
1135
+ const essencePath = join9(projectRoot, "decantr.essence.json");
1136
+ if (existsSync9(essencePath)) {
1030
1137
  try {
1031
- const essence = JSON.parse(readFileSync6(essencePath, "utf-8"));
1138
+ const essence = JSON.parse(readFileSync7(essencePath, "utf-8"));
1032
1139
  const mode = essence.dna?.theme?.mode;
1033
1140
  if (mode === "dark" || mode === "auto") {
1034
1141
  return true;
@@ -1042,17 +1149,17 @@ function scanStyling(projectRoot) {
1042
1149
  let approach = "unknown";
1043
1150
  let configFile;
1044
1151
  for (const cfg of TAILWIND_CONFIGS) {
1045
- if (existsSync8(join8(projectRoot, cfg))) {
1152
+ if (existsSync9(join9(projectRoot, cfg))) {
1046
1153
  approach = "tailwind";
1047
1154
  configFile = cfg;
1048
1155
  break;
1049
1156
  }
1050
1157
  }
1051
1158
  if (approach === "unknown") {
1052
- const pkgPath = join8(projectRoot, "package.json");
1053
- if (existsSync8(pkgPath)) {
1159
+ const pkgPath = join9(projectRoot, "package.json");
1160
+ if (existsSync9(pkgPath)) {
1054
1161
  try {
1055
- const pkg = JSON.parse(readFileSync6(pkgPath, "utf-8"));
1162
+ const pkg = JSON.parse(readFileSync7(pkgPath, "utf-8"));
1056
1163
  const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
1057
1164
  if (allDeps["@decantr/css"]) {
1058
1165
  approach = "decantr-css";
@@ -1065,27 +1172,27 @@ function scanStyling(projectRoot) {
1065
1172
  }
1066
1173
  }
1067
1174
  }
1068
- const decantrStyleFiles = DECANTR_STYLE_PATHS.filter((rel) => existsSync8(join8(projectRoot, rel)));
1175
+ const decantrStyleFiles = DECANTR_STYLE_PATHS.filter((rel) => existsSync9(join9(projectRoot, rel)));
1069
1176
  if (decantrStyleFiles.length >= 2) {
1070
1177
  approach = "decantr-css";
1071
1178
  configFile = decantrStyleFiles.join(" + ");
1072
1179
  }
1073
1180
  const cssContents = [];
1074
1181
  for (const rel of GLOBALS_CSS_PATHS) {
1075
- const fullPath = join8(projectRoot, rel);
1076
- if (existsSync8(fullPath)) {
1182
+ const fullPath = join9(projectRoot, rel);
1183
+ if (existsSync9(fullPath)) {
1077
1184
  try {
1078
- cssContents.push(readFileSync6(fullPath, "utf-8"));
1185
+ cssContents.push(readFileSync7(fullPath, "utf-8"));
1079
1186
  } catch {
1080
1187
  }
1081
1188
  }
1082
1189
  }
1083
1190
  for (const rel of DECANTR_STYLE_PATHS) {
1084
1191
  if (GLOBALS_CSS_PATHS.includes(rel)) continue;
1085
- const fullPath = join8(projectRoot, rel);
1086
- if (existsSync8(fullPath)) {
1192
+ const fullPath = join9(projectRoot, rel);
1193
+ if (existsSync9(fullPath)) {
1087
1194
  try {
1088
- cssContents.push(readFileSync6(fullPath, "utf-8"));
1195
+ cssContents.push(readFileSync7(fullPath, "utf-8"));
1089
1196
  } catch {
1090
1197
  }
1091
1198
  }
@@ -1101,7 +1208,7 @@ function scanStyling(projectRoot) {
1101
1208
  const darkMode = detectDarkMode(projectRoot, cssContents);
1102
1209
  if (approach === "unknown" && cssContents.length > 0) {
1103
1210
  approach = "css";
1104
- configFile = GLOBALS_CSS_PATHS.find((rel) => existsSync8(join8(projectRoot, rel)));
1211
+ configFile = GLOBALS_CSS_PATHS.find((rel) => existsSync9(join9(projectRoot, rel)));
1105
1212
  }
1106
1213
  return {
1107
1214
  approach,
@@ -1113,8 +1220,8 @@ function scanStyling(projectRoot) {
1113
1220
  }
1114
1221
 
1115
1222
  // src/detect.ts
1116
- import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
1117
- import { join as join9 } from "path";
1223
+ import { existsSync as existsSync10, readFileSync as readFileSync8 } from "fs";
1224
+ import { join as join10 } from "path";
1118
1225
  var RULE_FILES = [
1119
1226
  "CLAUDE.md",
1120
1227
  ".cursorrules",
@@ -1133,52 +1240,52 @@ function detectProject(projectRoot = process.cwd()) {
1133
1240
  existingEssence: false,
1134
1241
  projectRoot
1135
1242
  };
1136
- result.existingEssence = existsSync9(join9(projectRoot, "decantr.essence.json"));
1243
+ result.existingEssence = existsSync10(join10(projectRoot, "decantr.essence.json"));
1137
1244
  for (const ruleFile of RULE_FILES) {
1138
- if (existsSync9(join9(projectRoot, ruleFile))) {
1245
+ if (existsSync10(join10(projectRoot, ruleFile))) {
1139
1246
  result.existingRuleFiles.push(ruleFile);
1140
1247
  }
1141
1248
  }
1142
- if (existsSync9(join9(projectRoot, "pnpm-lock.yaml"))) {
1249
+ if (existsSync10(join10(projectRoot, "pnpm-lock.yaml"))) {
1143
1250
  result.packageManager = "pnpm";
1144
- } else if (existsSync9(join9(projectRoot, "yarn.lock"))) {
1251
+ } else if (existsSync10(join10(projectRoot, "yarn.lock"))) {
1145
1252
  result.packageManager = "yarn";
1146
- } else if (existsSync9(join9(projectRoot, "bun.lockb"))) {
1253
+ } else if (existsSync10(join10(projectRoot, "bun.lockb"))) {
1147
1254
  result.packageManager = "bun";
1148
- } else if (existsSync9(join9(projectRoot, "package-lock.json"))) {
1255
+ } else if (existsSync10(join10(projectRoot, "package-lock.json"))) {
1149
1256
  result.packageManager = "npm";
1150
1257
  }
1151
- result.hasTypeScript = existsSync9(join9(projectRoot, "tsconfig.json"));
1152
- result.hasTailwind = existsSync9(join9(projectRoot, "tailwind.config.js")) || existsSync9(join9(projectRoot, "tailwind.config.ts")) || existsSync9(join9(projectRoot, "tailwind.config.mjs")) || existsSync9(join9(projectRoot, "tailwind.config.cjs"));
1153
- if (existsSync9(join9(projectRoot, "next.config.js")) || existsSync9(join9(projectRoot, "next.config.ts")) || existsSync9(join9(projectRoot, "next.config.mjs"))) {
1258
+ result.hasTypeScript = existsSync10(join10(projectRoot, "tsconfig.json"));
1259
+ result.hasTailwind = existsSync10(join10(projectRoot, "tailwind.config.js")) || existsSync10(join10(projectRoot, "tailwind.config.ts")) || existsSync10(join10(projectRoot, "tailwind.config.mjs")) || existsSync10(join10(projectRoot, "tailwind.config.cjs"));
1260
+ if (existsSync10(join10(projectRoot, "next.config.js")) || existsSync10(join10(projectRoot, "next.config.ts")) || existsSync10(join10(projectRoot, "next.config.mjs"))) {
1154
1261
  result.framework = "nextjs";
1155
1262
  result.version = getPackageVersion(projectRoot, "next");
1156
1263
  return result;
1157
1264
  }
1158
- if (existsSync9(join9(projectRoot, "nuxt.config.js")) || existsSync9(join9(projectRoot, "nuxt.config.ts"))) {
1265
+ if (existsSync10(join10(projectRoot, "nuxt.config.js")) || existsSync10(join10(projectRoot, "nuxt.config.ts"))) {
1159
1266
  result.framework = "nuxt";
1160
1267
  result.version = getPackageVersion(projectRoot, "nuxt");
1161
1268
  return result;
1162
1269
  }
1163
- if (existsSync9(join9(projectRoot, "astro.config.mjs")) || existsSync9(join9(projectRoot, "astro.config.ts"))) {
1270
+ if (existsSync10(join10(projectRoot, "astro.config.mjs")) || existsSync10(join10(projectRoot, "astro.config.ts"))) {
1164
1271
  result.framework = "astro";
1165
1272
  result.version = getPackageVersion(projectRoot, "astro");
1166
1273
  return result;
1167
1274
  }
1168
- if (existsSync9(join9(projectRoot, "svelte.config.js")) || existsSync9(join9(projectRoot, "svelte.config.ts"))) {
1275
+ if (existsSync10(join10(projectRoot, "svelte.config.js")) || existsSync10(join10(projectRoot, "svelte.config.ts"))) {
1169
1276
  result.framework = "svelte";
1170
1277
  result.version = getPackageVersion(projectRoot, "svelte");
1171
1278
  return result;
1172
1279
  }
1173
- if (existsSync9(join9(projectRoot, "angular.json"))) {
1280
+ if (existsSync10(join10(projectRoot, "angular.json"))) {
1174
1281
  result.framework = "angular";
1175
1282
  result.version = getPackageVersion(projectRoot, "@angular/core");
1176
1283
  return result;
1177
1284
  }
1178
- const packageJsonPath = join9(projectRoot, "package.json");
1179
- if (existsSync9(packageJsonPath)) {
1285
+ const packageJsonPath = join10(projectRoot, "package.json");
1286
+ if (existsSync10(packageJsonPath)) {
1180
1287
  try {
1181
- const packageJson = JSON.parse(readFileSync7(packageJsonPath, "utf-8"));
1288
+ const packageJson = JSON.parse(readFileSync8(packageJsonPath, "utf-8"));
1182
1289
  const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
1183
1290
  if (deps.next) {
1184
1291
  result.framework = "nextjs";
@@ -1205,18 +1312,18 @@ function detectProject(projectRoot = process.cwd()) {
1205
1312
  } catch {
1206
1313
  }
1207
1314
  }
1208
- if (result.framework === "unknown" && !existsSync9(packageJsonPath)) {
1209
- if (existsSync9(join9(projectRoot, "index.html"))) {
1315
+ if (result.framework === "unknown" && !existsSync10(packageJsonPath)) {
1316
+ if (existsSync10(join10(projectRoot, "index.html"))) {
1210
1317
  result.framework = "html";
1211
1318
  }
1212
1319
  }
1213
1320
  return result;
1214
1321
  }
1215
1322
  function getPackageVersion(projectRoot, packageName) {
1216
- const packageJsonPath = join9(projectRoot, "package.json");
1217
- if (!existsSync9(packageJsonPath)) return void 0;
1323
+ const packageJsonPath = join10(projectRoot, "package.json");
1324
+ if (!existsSync10(packageJsonPath)) return void 0;
1218
1325
  try {
1219
- const packageJson = JSON.parse(readFileSync7(packageJsonPath, "utf-8"));
1326
+ const packageJson = JSON.parse(readFileSync8(packageJsonPath, "utf-8"));
1220
1327
  const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
1221
1328
  const version = deps[packageName];
1222
1329
  return version?.replace(/^\^|~/, "");
@@ -1249,8 +1356,8 @@ function formatDetection(detected) {
1249
1356
  }
1250
1357
 
1251
1358
  // src/workflow-model.ts
1252
- import { existsSync as existsSync10, readFileSync as readFileSync8 } from "fs";
1253
- import { join as join10 } from "path";
1359
+ import { existsSync as existsSync11, readFileSync as readFileSync9 } from "fs";
1360
+ import { join as join11 } from "path";
1254
1361
  function inferSuggestedShell(layout) {
1255
1362
  if (layout.hasSidebar) return "sidebar-main";
1256
1363
  if (layout.hasTopNav) return "top-nav-main";
@@ -1259,6 +1366,58 @@ function inferSuggestedShell(layout) {
1259
1366
  function hasExistingProjectFootprint(detected) {
1260
1367
  return detected.framework !== "unknown" || detected.packageManager !== "unknown" || detected.hasTypeScript || detected.hasTailwind || detected.existingRuleFiles.length > 0;
1261
1368
  }
1369
+ function normalizeWorkflowFlag(value) {
1370
+ if (value === "greenfield" || value === "brownfield" || value === "hybrid") return value;
1371
+ return void 0;
1372
+ }
1373
+ function normalizeAdoptionMode(value) {
1374
+ if (value === "contract-only" || value === "style-bridge" || value === "decantr-css") {
1375
+ return value;
1376
+ }
1377
+ return void 0;
1378
+ }
1379
+ function normalizeAssistantBridge(value) {
1380
+ if (value === "none" || value === "preview" || value === "apply") return value;
1381
+ return void 0;
1382
+ }
1383
+ function resolveWorkflowPolicy(input) {
1384
+ const requestedWorkflow = normalizeWorkflowFlag(input.requestedWorkflow);
1385
+ const requestedAdoption = normalizeAdoptionMode(input.requestedAdoption);
1386
+ const requestedAssistantBridge = normalizeAssistantBridge(input.requestedAssistantBridge);
1387
+ const hasRegistryContent = Boolean(
1388
+ input.requestedBlueprint || input.requestedArchetype || input.requestedTheme
1389
+ );
1390
+ const hasAnalysisArtifacts = Boolean(input.workflowSeed);
1391
+ const existingFootprint = hasExistingProjectFootprint(input.detected);
1392
+ let workflowMode;
1393
+ if (requestedWorkflow === "hybrid") {
1394
+ workflowMode = "hybrid-compose";
1395
+ } else if (requestedWorkflow === "brownfield" || input.explicitExisting || input.workflowSeed) {
1396
+ workflowMode = "brownfield-attach";
1397
+ } else if (requestedWorkflow === "greenfield") {
1398
+ workflowMode = hasRegistryContent ? "greenfield-scaffold" : "greenfield-contract-only";
1399
+ } else if (input.command === "new") {
1400
+ workflowMode = hasRegistryContent ? "greenfield-scaffold" : "greenfield-contract-only";
1401
+ } else if (existingFootprint && hasRegistryContent) {
1402
+ workflowMode = "hybrid-compose";
1403
+ } else if (existingFootprint && !hasRegistryContent) {
1404
+ workflowMode = "brownfield-attach";
1405
+ } else {
1406
+ workflowMode = hasRegistryContent ? "greenfield-scaffold" : "greenfield-contract-only";
1407
+ }
1408
+ const adoptionMode = requestedAdoption ?? input.workflowSeed?.adoptionMode ?? (workflowMode === "brownfield-attach" ? "contract-only" : workflowMode === "hybrid-compose" ? "contract-only" : workflowMode === "greenfield-contract-only" ? "contract-only" : "decantr-css");
1409
+ const contentSource = hasRegistryContent ? input.offline ? "cache" : "official" : "none";
1410
+ const assistantBridge = requestedAssistantBridge ?? input.workflowSeed?.assistantBridge ?? (workflowMode === "brownfield-attach" && input.detected.existingRuleFiles.length > 0 ? "preview" : "none");
1411
+ return {
1412
+ workflowMode,
1413
+ adoptionMode,
1414
+ contentSource,
1415
+ assistantBridge,
1416
+ projectScope: input.projectScope ?? "single-app",
1417
+ hasAnalysisArtifacts,
1418
+ registryRequired: hasRegistryContent
1419
+ };
1420
+ }
1262
1421
  function createBrownfieldInitSeed(detected, layout, styling) {
1263
1422
  return {
1264
1423
  version: 1,
@@ -1266,6 +1425,10 @@ function createBrownfieldInitSeed(detected, layout, styling) {
1266
1425
  contractOnly: true,
1267
1426
  registryOptional: true,
1268
1427
  workflowMode: "brownfield-attach",
1428
+ adoptionMode: "contract-only",
1429
+ contentSource: "none",
1430
+ assistantBridge: detected.existingRuleFiles.length > 0 ? "preview" : "none",
1431
+ projectScope: "single-app",
1269
1432
  target: detected.framework !== "unknown" ? detected.framework : "react",
1270
1433
  shell: inferSuggestedShell(layout),
1271
1434
  guard: "guided",
@@ -1281,12 +1444,12 @@ function createBrownfieldInitSeed(detected, layout, styling) {
1281
1444
  };
1282
1445
  }
1283
1446
  function readBrownfieldInitSeed(projectRoot) {
1284
- const seedPath = join10(projectRoot, ".decantr", "init-seed.json");
1285
- if (!existsSync10(seedPath)) {
1447
+ const seedPath = join11(projectRoot, ".decantr", "init-seed.json");
1448
+ if (!existsSync11(seedPath)) {
1286
1449
  return null;
1287
1450
  }
1288
1451
  try {
1289
- const parsed = JSON.parse(readFileSync8(seedPath, "utf-8"));
1452
+ const parsed = JSON.parse(readFileSync9(seedPath, "utf-8"));
1290
1453
  if (parsed.workflow !== "brownfield-adoption") {
1291
1454
  return null;
1292
1455
  }
@@ -1303,7 +1466,7 @@ var RESET2 = "\x1B[0m";
1303
1466
  var GREEN2 = "\x1B[32m";
1304
1467
  var CYAN = "\x1B[36m";
1305
1468
  var YELLOW = "\x1B[33m";
1306
- function cmdAnalyze(projectRoot = process.cwd()) {
1469
+ function cmdAnalyze(projectRoot = process.cwd(), workspace) {
1307
1470
  console.log(`
1308
1471
  ${BOLD}Analyzing project...${RESET2}
1309
1472
  `);
@@ -1322,6 +1485,7 @@ ${BOLD}Analyzing project...${RESET2}
1322
1485
  console.log(`${DIM2}Scanning dependencies...${RESET2}`);
1323
1486
  const dependencies = scanDependencies(projectRoot);
1324
1487
  const initSeed = createBrownfieldInitSeed(project, layout, styling);
1488
+ initSeed.projectScope = workspace?.projectScope ?? "single-app";
1325
1489
  const analysis = {
1326
1490
  version: 1,
1327
1491
  analyzedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -1330,7 +1494,11 @@ ${BOLD}Analyzing project...${RESET2}
1330
1494
  frameworkVersion: project.version ?? null,
1331
1495
  packageManager: project.packageManager,
1332
1496
  hasTypeScript: project.hasTypeScript,
1333
- hasTailwind: project.hasTailwind
1497
+ hasTailwind: project.hasTailwind,
1498
+ existingRuleFiles: project.existingRuleFiles,
1499
+ workspaceRoot: workspace?.workspaceRoot ?? projectRoot,
1500
+ appRoot: workspace?.appRoot ?? projectRoot,
1501
+ projectScope: workspace?.projectScope ?? "single-app"
1334
1502
  },
1335
1503
  routes,
1336
1504
  components,
@@ -1344,8 +1512,9 @@ ${BOLD}Analyzing project...${RESET2}
1344
1512
  attach: {
1345
1513
  entrypoint: "decantr analyze",
1346
1514
  contractOnly: true,
1515
+ adoptionMode: "contract-only",
1347
1516
  initSeedPath: ".decantr/init-seed.json",
1348
- recommendedCommand: "decantr init --existing --yes"
1517
+ recommendedCommand: "decantr init --existing --yes --adoption=contract-only"
1349
1518
  },
1350
1519
  hybrid: {
1351
1520
  ownerCommands: [
@@ -1356,16 +1525,31 @@ ${BOLD}Analyzing project...${RESET2}
1356
1525
  "decantr upgrade"
1357
1526
  ]
1358
1527
  }
1528
+ },
1529
+ retrofitPlan: {
1530
+ recommendedWorkflowMode: "brownfield-attach",
1531
+ recommendedAdoptionMode: "contract-only",
1532
+ assistantBridge: project.existingRuleFiles.length > 0 ? "preview existing rule files before applying" : "none detected",
1533
+ routeAnchors: routes.routes.map((route) => route.path),
1534
+ stylingAnchors: [styling.configFile].filter(Boolean),
1535
+ ruleFiles: project.existingRuleFiles,
1536
+ preserve: [
1537
+ "framework",
1538
+ "package manager",
1539
+ "router",
1540
+ "build tooling",
1541
+ "existing styling system"
1542
+ ]
1359
1543
  }
1360
1544
  };
1361
- const decantrDir = join11(projectRoot, ".decantr");
1362
- if (!existsSync11(decantrDir)) {
1363
- mkdirSync2(decantrDir, { recursive: true });
1364
- }
1365
- const outputPath = join11(decantrDir, "analysis.json");
1366
- const initSeedPath = join11(decantrDir, "init-seed.json");
1367
- writeFileSync3(outputPath, JSON.stringify(analysis, null, 2) + "\n", "utf-8");
1368
- writeFileSync3(initSeedPath, JSON.stringify(initSeed, null, 2) + "\n", "utf-8");
1545
+ const decantrDir = join12(projectRoot, ".decantr");
1546
+ if (!existsSync12(decantrDir)) {
1547
+ mkdirSync3(decantrDir, { recursive: true });
1548
+ }
1549
+ const outputPath = join12(decantrDir, "analysis.json");
1550
+ const initSeedPath = join12(decantrDir, "init-seed.json");
1551
+ writeFileSync4(outputPath, JSON.stringify(analysis, null, 2) + "\n", "utf-8");
1552
+ writeFileSync4(initSeedPath, JSON.stringify(initSeed, null, 2) + "\n", "utf-8");
1369
1553
  console.log(`
1370
1554
  ${GREEN2}Analysis complete.${RESET2}
1371
1555
  `);
@@ -1399,14 +1583,14 @@ ${DIM2}Written to:${RESET2} ${outputPath}`);
1399
1583
  console.log(`${DIM2}Init seed:${RESET2} ${initSeedPath}`);
1400
1584
  console.log(
1401
1585
  `
1402
- ${YELLOW}Next step:${RESET2} Run ${BOLD}decantr init --existing --yes${RESET2} to attach Decantr using the generated brownfield seed.
1586
+ ${YELLOW}Next step:${RESET2} Run ${BOLD}decantr init --existing --yes --adoption=contract-only${RESET2} to attach Decantr using the generated brownfield seed.
1403
1587
  `
1404
1588
  );
1405
1589
  }
1406
1590
 
1407
1591
  // src/commands/create.ts
1408
- import { existsSync as existsSync12, mkdirSync as mkdirSync3, writeFileSync as writeFileSync4 } from "fs";
1409
- import { join as join12 } from "path";
1592
+ import { existsSync as existsSync13, mkdirSync as mkdirSync4, writeFileSync as writeFileSync5 } from "fs";
1593
+ import { join as join13 } from "path";
1410
1594
  import {
1411
1595
  CONTENT_TYPE_TO_API_CONTENT_TYPE,
1412
1596
  CONTENT_TYPES
@@ -1602,23 +1786,23 @@ function cmdCreate(type, name, projectRoot = process.cwd()) {
1602
1786
  }
1603
1787
  const contentType = type;
1604
1788
  const plural = PLURAL[contentType];
1605
- const customDir = join12(projectRoot, ".decantr", "custom", plural);
1606
- const filePath = join12(customDir, `${name}.json`);
1607
- if (existsSync12(filePath)) {
1789
+ const customDir = join13(projectRoot, ".decantr", "custom", plural);
1790
+ const filePath = join13(customDir, `${name}.json`);
1791
+ if (existsSync13(filePath)) {
1608
1792
  console.error(`${type} "${name}" already exists at ${filePath}`);
1609
1793
  process.exitCode = 1;
1610
1794
  return;
1611
1795
  }
1612
- mkdirSync3(customDir, { recursive: true });
1796
+ mkdirSync4(customDir, { recursive: true });
1613
1797
  const skeleton = getSkeleton(contentType, name, humanizeId(name));
1614
- writeFileSync4(filePath, JSON.stringify(skeleton, null, 2));
1798
+ writeFileSync5(filePath, JSON.stringify(skeleton, null, 2));
1615
1799
  console.log(`Created ${type} "${name}" at ${filePath}`);
1616
1800
  console.log(`Edit it, then publish with: decantr publish ${type} ${name}`);
1617
1801
  }
1618
1802
 
1619
1803
  // src/commands/export.ts
1620
- import { existsSync as existsSync13, mkdirSync as mkdirSync4, readFileSync as readFileSync9, writeFileSync as writeFileSync5 } from "fs";
1621
- import { dirname, join as join13 } from "path";
1804
+ import { existsSync as existsSync14, mkdirSync as mkdirSync5, readFileSync as readFileSync10, writeFileSync as writeFileSync6 } from "fs";
1805
+ import { dirname as dirname2, join as join14 } from "path";
1622
1806
  var GREEN3 = "\x1B[32m";
1623
1807
  var RED2 = "\x1B[31m";
1624
1808
  var DIM3 = "\x1B[2m";
@@ -1768,21 +1952,21 @@ function generateCSSVars(tokens) {
1768
1952
  return lines.join("\n");
1769
1953
  }
1770
1954
  async function cmdExport(target, projectRoot, options = {}) {
1771
- const essencePath = join13(projectRoot, "decantr.essence.json");
1772
- const tokensPath = join13(projectRoot, "src", "styles", "tokens.css");
1773
- if (!existsSync13(essencePath)) {
1955
+ const essencePath = join14(projectRoot, "decantr.essence.json");
1956
+ const tokensPath = join14(projectRoot, "src", "styles", "tokens.css");
1957
+ if (!existsSync14(essencePath)) {
1774
1958
  console.error(`${RED2}No decantr.essence.json found. Run \`decantr init\` first.${RESET3}`);
1775
1959
  process.exitCode = 1;
1776
1960
  return;
1777
1961
  }
1778
- if (!existsSync13(tokensPath)) {
1962
+ if (!existsSync14(tokensPath)) {
1779
1963
  console.error(
1780
1964
  `${RED2}No src/styles/tokens.css found. Run \`decantr refresh\` to generate tokens.${RESET3}`
1781
1965
  );
1782
1966
  process.exitCode = 1;
1783
1967
  return;
1784
1968
  }
1785
- const tokensCSS = readFileSync9(tokensPath, "utf-8");
1969
+ const tokensCSS = readFileSync10(tokensPath, "utf-8");
1786
1970
  const tokens = parseTokensCSS(tokensCSS);
1787
1971
  if (tokens.size === 0) {
1788
1972
  console.error(`${RED2}No --d-* tokens found in tokens.css.${RESET3}`);
@@ -1791,28 +1975,28 @@ async function cmdExport(target, projectRoot, options = {}) {
1791
1975
  }
1792
1976
  switch (target) {
1793
1977
  case "shadcn": {
1794
- const cssOut = options.output ?? join13(projectRoot, "src", "styles", "shadcn-theme.css");
1795
- const jsonOut = join13(projectRoot, "components.json");
1978
+ const cssOut = options.output ?? join14(projectRoot, "src", "styles", "shadcn-theme.css");
1979
+ const jsonOut = join14(projectRoot, "components.json");
1796
1980
  ensureDir(cssOut);
1797
- writeFileSync5(cssOut, generateShadcnCSS(tokens), "utf-8");
1798
- writeFileSync5(jsonOut, generateShadcnComponentsJSON(), "utf-8");
1981
+ writeFileSync6(cssOut, generateShadcnCSS(tokens), "utf-8");
1982
+ writeFileSync6(jsonOut, generateShadcnComponentsJSON(), "utf-8");
1799
1983
  console.log(`${GREEN3}Exported shadcn theme:${RESET3}`);
1800
1984
  console.log(` ${DIM3}CSS:${RESET3} ${cssOut}`);
1801
1985
  console.log(` ${DIM3}JSON:${RESET3} ${jsonOut}`);
1802
1986
  break;
1803
1987
  }
1804
1988
  case "tailwind": {
1805
- const out = options.output ?? join13(projectRoot, "tailwind.decantr.config.ts");
1989
+ const out = options.output ?? join14(projectRoot, "tailwind.decantr.config.ts");
1806
1990
  ensureDir(out);
1807
- writeFileSync5(out, generateTailwindConfig(tokens), "utf-8");
1991
+ writeFileSync6(out, generateTailwindConfig(tokens), "utf-8");
1808
1992
  console.log(`${GREEN3}Exported Tailwind config:${RESET3}`);
1809
1993
  console.log(` ${DIM3}File:${RESET3} ${out}`);
1810
1994
  break;
1811
1995
  }
1812
1996
  case "css-vars": {
1813
- const out = options.output ?? join13(projectRoot, "decantr-tokens.css");
1997
+ const out = options.output ?? join14(projectRoot, "decantr-tokens.css");
1814
1998
  ensureDir(out);
1815
- writeFileSync5(out, generateCSSVars(tokens), "utf-8");
1999
+ writeFileSync6(out, generateCSSVars(tokens), "utf-8");
1816
2000
  console.log(`${GREEN3}Exported CSS variables:${RESET3}`);
1817
2001
  console.log(` ${DIM3}File:${RESET3} ${out}`);
1818
2002
  break;
@@ -1820,16 +2004,16 @@ async function cmdExport(target, projectRoot, options = {}) {
1820
2004
  }
1821
2005
  }
1822
2006
  function ensureDir(filePath) {
1823
- const dir = dirname(filePath);
1824
- if (!existsSync13(dir)) {
1825
- mkdirSync4(dir, { recursive: true });
2007
+ const dir = dirname2(filePath);
2008
+ if (!existsSync14(dir)) {
2009
+ mkdirSync5(dir, { recursive: true });
1826
2010
  }
1827
2011
  }
1828
2012
 
1829
2013
  // src/commands/magic.ts
1830
- import { existsSync as existsSync14 } from "fs";
2014
+ import { existsSync as existsSync15 } from "fs";
1831
2015
  import * as fs from "fs/promises";
1832
- import { join as join14 } from "path";
2016
+ import { join as join15 } from "path";
1833
2017
  var BOLD2 = "\x1B[1m";
1834
2018
  var DIM4 = "\x1B[2m";
1835
2019
  var RESET4 = "\x1B[0m";
@@ -1979,7 +2163,8 @@ async function resolveTheme(intent, registryClient) {
1979
2163
  const hints = intent.themeHints;
1980
2164
  if (registryClient) {
1981
2165
  try {
1982
- const themes = await registryClient.fetchThemes();
2166
+ const themesResult = await registryClient.fetchThemes();
2167
+ const themes = Array.isArray(themesResult) ? themesResult : Array.isArray(themesResult?.data?.items) ? themesResult.data.items : [];
1983
2168
  if (themes && themes.length > 0) {
1984
2169
  const scored = themes.map((t) => {
1985
2170
  let score = 0;
@@ -1994,7 +2179,7 @@ async function resolveTheme(intent, registryClient) {
1994
2179
  return { id: t.id || t.slug, score, modes: t.modes };
1995
2180
  }).sort((a, b) => b.score - a.score);
1996
2181
  if (scored[0] && scored[0].score > 0) {
1997
- const mode2 = intent.themeHints.includes("light") ? "light" : intent.themeHints.includes("dark") ? "dark" : scored[0].modes?.includes("dark") ? "dark" : "light";
2182
+ const mode2 = intent.themeHints.includes("light") ? "light" : intent.themeHints.includes("dark") ? "dark" : scored[0].modes?.includes("light") ? "light" : scored[0].modes?.includes("dark") ? "dark" : "light";
1998
2183
  return { id: scored[0].id, mode: mode2 };
1999
2184
  }
2000
2185
  }
@@ -2056,8 +2241,8 @@ async function cmdMagic(prompt, projectRoot, options) {
2056
2241
  console.log(` Archetype: ${intent.archetype}`);
2057
2242
  }
2058
2243
  console.log("");
2059
- const essencePath = join14(projectRoot, "decantr.essence.json");
2060
- if (existsSync14(essencePath)) {
2244
+ const essencePath = join15(projectRoot, "decantr.essence.json");
2245
+ if (existsSync15(essencePath)) {
2061
2246
  console.log(error(" decantr.essence.json already exists in this directory."));
2062
2247
  console.log(dim(" Remove it first or use a different directory."));
2063
2248
  process.exitCode = 1;
@@ -2080,7 +2265,7 @@ async function cmdMagic(prompt, projectRoot, options) {
2080
2265
  return;
2081
2266
  }
2082
2267
  const registryClient = new RegistryClient({
2083
- cacheDir: join14(projectRoot, ".decantr", "cache"),
2268
+ cacheDir: join15(projectRoot, ".decantr", "cache"),
2084
2269
  apiUrl: options.registry,
2085
2270
  offline: options.offline
2086
2271
  });
@@ -2351,14 +2536,14 @@ async function cmdMagic(prompt, projectRoot, options) {
2351
2536
  if (result.gitignoreUpdated) {
2352
2537
  console.log(` ${dim(".gitignore updated")}`);
2353
2538
  }
2354
- const contextDir = join14(projectRoot, ".decantr", "context");
2539
+ const contextDir = join15(projectRoot, ".decantr", "context");
2355
2540
  let sectionCount = 0;
2356
2541
  try {
2357
2542
  const files = await fs.readdir(contextDir);
2358
2543
  sectionCount = files.filter((f) => f.startsWith("section-")).length;
2359
2544
  } catch {
2360
2545
  }
2361
- const treatmentsPath = join14(projectRoot, "src", "styles", "treatments.css");
2546
+ const treatmentsPath = join15(projectRoot, "src", "styles", "treatments.css");
2362
2547
  let hasLayers = false;
2363
2548
  try {
2364
2549
  const css = await fs.readFile(treatmentsPath, "utf-8");
@@ -2376,27 +2561,23 @@ ${GREEN4}${BOLD2}Quality summary:${RESET4}`);
2376
2561
  );
2377
2562
  console.log("");
2378
2563
  console.log(`${BOLD2} Ready!${RESET4} Next steps:`);
2379
- console.log(` 1. Read ${cyan("DECANTR.md")} for guard rules, CSS approach, and workflow`);
2380
2564
  console.log(
2381
- ` 2. Read ${cyan(".decantr/context/scaffold-pack.md")} first as the primary compiled contract`
2565
+ ` 1. Read ${cyan(".decantr/context/scaffold-pack.md")} first as the primary compiled contract`
2382
2566
  );
2383
2567
  console.log(
2384
- ` 3. Read ${cyan(".decantr/context/scaffold.md")} second for broader topology and voice guidance`
2568
+ ` 2. Read the matching ${cyan(".decantr/context/section-*-pack.md")} and ${cyan(".decantr/context/page-*-pack.md")} files before section or route work`
2385
2569
  );
2386
2570
  console.log(
2387
- ` 4. Read the matching ${cyan(".decantr/context/section-*-pack.md")} and ${cyan(".decantr/context/section-*.md")} files before section work`
2571
+ ` 3. Use ${cyan("DECANTR.md")} as a lookup reference for guard rules, CSS atoms, treatments, decorators, and workflow`
2388
2572
  );
2389
- console.log(
2390
- ` 5. Read the matching ${cyan(".decantr/context/page-*-pack.md")} file before route work`
2391
- );
2392
- console.log(` 6. Build the shell and route structure first, then implement each page`);
2393
- console.log(` 7. Run ${cyan("decantr check")} and ${cyan("decantr audit")} before you ship`);
2573
+ console.log(` 4. Build the shell and route structure first, then implement each page`);
2574
+ console.log(` 5. Run ${cyan("decantr check")} and ${cyan("decantr audit")} before you ship`);
2394
2575
  console.log("");
2395
2576
  }
2396
2577
 
2397
2578
  // src/commands/migrate.ts
2398
- import { copyFileSync, existsSync as existsSync15, readFileSync as readFileSync10, writeFileSync as writeFileSync6 } from "fs";
2399
- import { join as join15 } from "path";
2579
+ import { copyFileSync, existsSync as existsSync16, readFileSync as readFileSync11, writeFileSync as writeFileSync7 } from "fs";
2580
+ import { join as join16 } from "path";
2400
2581
  import { isV3 as isV32, migrateV2ToV3, validateEssence } from "@decantr/essence-spec";
2401
2582
  var GREEN5 = "\x1B[32m";
2402
2583
  var RED4 = "\x1B[31m";
@@ -2404,12 +2585,12 @@ var YELLOW3 = "\x1B[33m";
2404
2585
  var RESET5 = "\x1B[0m";
2405
2586
  var DIM5 = "\x1B[2m";
2406
2587
  function migrateEssenceFile(essencePath) {
2407
- if (!existsSync15(essencePath)) {
2588
+ if (!existsSync16(essencePath)) {
2408
2589
  return { success: false, error: `File not found: ${essencePath}` };
2409
2590
  }
2410
2591
  let raw;
2411
2592
  try {
2412
- raw = readFileSync10(essencePath, "utf-8");
2593
+ raw = readFileSync11(essencePath, "utf-8");
2413
2594
  } catch (e) {
2414
2595
  return { success: false, error: `Could not read ${essencePath}: ${e.message}` };
2415
2596
  }
@@ -2450,7 +2631,7 @@ function migrateEssenceFile(essencePath) {
2450
2631
  };
2451
2632
  }
2452
2633
  try {
2453
- writeFileSync6(essencePath, JSON.stringify(v3, null, 2) + "\n");
2634
+ writeFileSync7(essencePath, JSON.stringify(v3, null, 2) + "\n");
2454
2635
  } catch (e) {
2455
2636
  return {
2456
2637
  success: false,
@@ -2461,8 +2642,8 @@ function migrateEssenceFile(essencePath) {
2461
2642
  return { success: true, backupPath };
2462
2643
  }
2463
2644
  async function cmdMigrate(projectRoot = process.cwd()) {
2464
- const essencePath = join15(projectRoot, "decantr.essence.json");
2465
- if (!existsSync15(essencePath)) {
2645
+ const essencePath = join16(projectRoot, "decantr.essence.json");
2646
+ if (!existsSync16(essencePath)) {
2466
2647
  console.error(`${RED4}No decantr.essence.json found. Run \`decantr init\` first.${RESET5}`);
2467
2648
  process.exitCode = 1;
2468
2649
  return;
@@ -2488,19 +2669,37 @@ async function cmdMigrate(projectRoot = process.cwd()) {
2488
2669
 
2489
2670
  // src/commands/new-project.ts
2490
2671
  import { execSync } from "child_process";
2491
- import { existsSync as existsSync17, mkdirSync as mkdirSync7 } from "fs";
2492
- import { join as join18, resolve as resolve2 } from "path";
2672
+ import { existsSync as existsSync18, mkdirSync as mkdirSync8 } from "fs";
2673
+ import { join as join19, resolve as resolve2 } from "path";
2493
2674
  import { fileURLToPath } from "url";
2494
2675
 
2495
2676
  // src/bootstrap.ts
2496
- import { mkdirSync as mkdirSync5, readFileSync as readFileSync11, writeFileSync as writeFileSync7 } from "fs";
2497
- import { basename, join as join16 } from "path";
2677
+ import { mkdirSync as mkdirSync6, readFileSync as readFileSync12, writeFileSync as writeFileSync8 } from "fs";
2678
+ import { basename, join as join17 } from "path";
2498
2679
  import { resolvePackAdapter } from "@decantr/core";
2499
2680
  var reactViteBootstrapAdapter = {
2500
2681
  id: "react-vite",
2501
2682
  label: "React + Vite starter",
2683
+ target: "react",
2684
+ packAdapter: "react-vite",
2685
+ capabilities: {
2686
+ bootstrap: true,
2687
+ attach: true,
2688
+ styling: true,
2689
+ verify: true
2690
+ },
2691
+ attach: {
2692
+ routeRoots: ["src/App.tsx", "src/routes", "src/pages"],
2693
+ layoutFiles: ["src/App.tsx", "src/main.tsx"],
2694
+ componentRoots: ["src/components", "src/pages", "src/routes"]
2695
+ },
2696
+ verify: {
2697
+ devCommand: "npm run dev",
2698
+ buildCommand: "npm run build",
2699
+ distDir: "dist"
2700
+ },
2502
2701
  writeProjectFiles(projectDir, title, routingMode) {
2503
- const srcDir = join16(projectDir, "src");
2702
+ const srcDir = join17(projectDir, "src");
2504
2703
  const routerImport = routingMode === "hash" ? "HashRouter" : "BrowserRouter";
2505
2704
  const packageJson = {
2506
2705
  name: basename(projectDir) || "decantr-app",
@@ -2532,7 +2731,7 @@ var reactViteBootstrapAdapter = {
2532
2731
  vite: "^6.0.0"
2533
2732
  }
2534
2733
  };
2535
- writeFileSync7(join16(projectDir, "package.json"), JSON.stringify(packageJson, null, 2) + "\n");
2734
+ writeFileSync8(join17(projectDir, "package.json"), JSON.stringify(packageJson, null, 2) + "\n");
2536
2735
  const viteConfig = `import { defineConfig } from 'vite';
2537
2736
  import react from '@vitejs/plugin-react';
2538
2737
 
@@ -2540,7 +2739,7 @@ export default defineConfig({
2540
2739
  plugins: [react()],
2541
2740
  });
2542
2741
  `;
2543
- writeFileSync7(join16(projectDir, "vite.config.ts"), viteConfig);
2742
+ writeFileSync8(join17(projectDir, "vite.config.ts"), viteConfig);
2544
2743
  const tsconfig = {
2545
2744
  compilerOptions: {
2546
2745
  target: "ES2020",
@@ -2562,7 +2761,7 @@ export default defineConfig({
2562
2761
  },
2563
2762
  include: ["src"]
2564
2763
  };
2565
- writeFileSync7(join16(projectDir, "tsconfig.json"), JSON.stringify(tsconfig, null, 2) + "\n");
2764
+ writeFileSync8(join17(projectDir, "tsconfig.json"), JSON.stringify(tsconfig, null, 2) + "\n");
2566
2765
  const tsconfigApp = {
2567
2766
  compilerOptions: {
2568
2767
  tsBuildInfoFile: "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
@@ -2585,8 +2784,8 @@ export default defineConfig({
2585
2784
  },
2586
2785
  include: ["src"]
2587
2786
  };
2588
- writeFileSync7(
2589
- join16(projectDir, "tsconfig.app.json"),
2787
+ writeFileSync8(
2788
+ join17(projectDir, "tsconfig.app.json"),
2590
2789
  JSON.stringify(tsconfigApp, null, 2) + "\n"
2591
2790
  );
2592
2791
  const indexHtml = `<!doctype html>
@@ -2602,8 +2801,8 @@ export default defineConfig({
2602
2801
  </body>
2603
2802
  </html>
2604
2803
  `;
2605
- writeFileSync7(join16(projectDir, "index.html"), indexHtml);
2606
- mkdirSync5(srcDir, { recursive: true });
2804
+ writeFileSync8(join17(projectDir, "index.html"), indexHtml);
2805
+ mkdirSync6(srcDir, { recursive: true });
2607
2806
  const mainTsx = `import { StrictMode } from 'react';
2608
2807
  import { createRoot } from 'react-dom/client';
2609
2808
  import { ${routerImport} } from 'react-router-dom';
@@ -2620,7 +2819,7 @@ createRoot(document.getElementById('root')!).render(
2620
2819
  </StrictMode>,
2621
2820
  );
2622
2821
  `;
2623
- writeFileSync7(join16(srcDir, "main.tsx"), mainTsx);
2822
+ writeFileSync8(join17(srcDir, "main.tsx"), mainTsx);
2624
2823
  const appTsx = `import { css } from '@decantr/css';
2625
2824
  import { Routes, Route } from 'react-router-dom';
2626
2825
 
@@ -2634,7 +2833,7 @@ function WelcomePage() {
2634
2833
  <p className="d-label" data-anchor>Decantr starter</p>
2635
2834
  <h1 className={css('_heading2')}>${title}</h1>
2636
2835
  <p className={css('_textsm _fgmuted _mw[32rem]')}>
2637
- Scaffolded with Decantr. Read DECANTR.md and the compiled packs in .decantr/context before building routes.
2836
+ Scaffolded with Decantr. Read .decantr/context/scaffold-pack.md first, then use DECANTR.md as a lookup reference.
2638
2837
  </p>
2639
2838
  <div className={css('_flex _gap3 _wrap _jcc')}>
2640
2839
  <span className="d-annotation" data-status="info">Runtime: @decantr/css</span>
@@ -2655,34 +2854,184 @@ export function App() {
2655
2854
  );
2656
2855
  }
2657
2856
  `;
2658
- writeFileSync7(join16(srcDir, "App.tsx"), appTsx);
2659
- writeFileSync7(join16(srcDir, "vite-env.d.ts"), '/// <reference types="vite/client" />\n');
2660
- mkdirSync5(join16(srcDir, "styles"), { recursive: true });
2857
+ writeFileSync8(join17(srcDir, "App.tsx"), appTsx);
2858
+ writeFileSync8(join17(srcDir, "vite-env.d.ts"), '/// <reference types="vite/client" />\n');
2859
+ mkdirSync6(join17(srcDir, "styles"), { recursive: true });
2661
2860
  }
2662
2861
  };
2663
- var BOOTSTRAP_ADAPTERS = {
2664
- "react-vite": reactViteBootstrapAdapter
2862
+ var nextAppAdapter = {
2863
+ id: "next-app",
2864
+ label: "Next.js App Router starter",
2865
+ target: "nextjs",
2866
+ packAdapter: "nextjs",
2867
+ capabilities: {
2868
+ bootstrap: true,
2869
+ attach: true,
2870
+ styling: true,
2871
+ verify: true
2872
+ },
2873
+ attach: {
2874
+ routeRoots: ["app", "pages"],
2875
+ layoutFiles: ["app/layout.tsx", "pages/_app.tsx"],
2876
+ componentRoots: ["components", "src/components", "app"]
2877
+ },
2878
+ verify: {
2879
+ devCommand: "npm run dev",
2880
+ buildCommand: "npm run build",
2881
+ distDir: ".next"
2882
+ },
2883
+ writeProjectFiles(projectDir, title) {
2884
+ const appDir = join17(projectDir, "app");
2885
+ const stylesDir = join17(projectDir, "src", "styles");
2886
+ const packageJson = {
2887
+ name: basename(projectDir) || "decantr-next-app",
2888
+ private: true,
2889
+ version: "0.0.0",
2890
+ type: "module",
2891
+ scripts: {
2892
+ dev: "next dev",
2893
+ build: "next build",
2894
+ start: "next start"
2895
+ },
2896
+ dependencies: {
2897
+ "@decantr/css": "^1.0.4",
2898
+ "lucide-react": "^0.468.0",
2899
+ next: "^16.0.0",
2900
+ react: "^19.0.0",
2901
+ "react-dom": "^19.0.0"
2902
+ },
2903
+ devDependencies: {
2904
+ "@types/node": "^20.0.0",
2905
+ "@types/react": "^19.0.0",
2906
+ "@types/react-dom": "^19.0.0",
2907
+ typescript: "^5.7.0"
2908
+ }
2909
+ };
2910
+ writeFileSync8(join17(projectDir, "package.json"), JSON.stringify(packageJson, null, 2) + "\n");
2911
+ writeFileSync8(join17(projectDir, "next.config.ts"), 'import type { NextConfig } from "next";\n\nconst nextConfig: NextConfig = {};\n\nexport default nextConfig;\n');
2912
+ writeFileSync8(join17(projectDir, "next-env.d.ts"), '/// <reference types="next" />\n/// <reference types="next/image-types/global" />\n\n// This file is generated by Next.js.\n');
2913
+ writeFileSync8(
2914
+ join17(projectDir, "tsconfig.json"),
2915
+ JSON.stringify(
2916
+ {
2917
+ compilerOptions: {
2918
+ target: "ES2017",
2919
+ lib: ["dom", "dom.iterable", "esnext"],
2920
+ allowJs: true,
2921
+ skipLibCheck: true,
2922
+ strict: true,
2923
+ noEmit: true,
2924
+ esModuleInterop: true,
2925
+ module: "esnext",
2926
+ moduleResolution: "bundler",
2927
+ resolveJsonModule: true,
2928
+ isolatedModules: true,
2929
+ jsx: "preserve",
2930
+ incremental: true,
2931
+ plugins: [{ name: "next" }]
2932
+ },
2933
+ include: ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
2934
+ exclude: ["node_modules"]
2935
+ },
2936
+ null,
2937
+ 2
2938
+ ) + "\n"
2939
+ );
2940
+ mkdirSync6(appDir, { recursive: true });
2941
+ mkdirSync6(stylesDir, { recursive: true });
2942
+ writeFileSync8(
2943
+ join17(appDir, "layout.tsx"),
2944
+ `import type { Metadata } from 'next';
2945
+ import '../src/styles/global.css';
2946
+ import '../src/styles/tokens.css';
2947
+ import '../src/styles/treatments.css';
2948
+
2949
+ export const metadata: Metadata = {
2950
+ title: '${title}',
2951
+ description: 'Scaffolded with Decantr',
2952
+ };
2953
+
2954
+ export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) {
2955
+ return (
2956
+ <html lang="en">
2957
+ <body>{children}</body>
2958
+ </html>
2959
+ );
2960
+ }
2961
+ `
2962
+ );
2963
+ writeFileSync8(
2964
+ join17(appDir, "page.tsx"),
2965
+ `import { css } from '@decantr/css';
2966
+
2967
+ export default function HomePage() {
2968
+ return (
2969
+ <main id="main-content" className={css('_minh[100vh] _flex _col _aic _jcc _p6 _gap4')}>
2970
+ <section className={css('_wfull _mw[42rem]') + ' d-section'} data-density="comfortable">
2971
+ <div className={css('_flex _col _aic _gap4 _textc') + ' d-surface'} data-elevation="raised">
2972
+ <p className="d-label" data-anchor>Decantr starter</p>
2973
+ <h1 className={css('_heading2')}>${title}</h1>
2974
+ <p className={css('_textsm _fgmuted _mw[32rem]')}>Scaffolded with Decantr. Read .decantr/context/scaffold-pack.md first, then use DECANTR.md as a lookup reference.</p>
2975
+ <span className="d-annotation" data-status="info">Runtime: Next.js App Router</span>
2976
+ </div>
2977
+ </section>
2978
+ </main>
2979
+ );
2980
+ }
2981
+ `
2982
+ );
2983
+ }
2984
+ };
2985
+ var genericWebAdapter = {
2986
+ id: "generic-web",
2987
+ label: "Generic web contract adapter",
2988
+ target: "generic",
2989
+ packAdapter: "generic-web",
2990
+ capabilities: {
2991
+ bootstrap: false,
2992
+ attach: true,
2993
+ styling: true,
2994
+ verify: true
2995
+ },
2996
+ attach: {
2997
+ routeRoots: ["src", "app", "pages"],
2998
+ layoutFiles: ["index.html", "src/main.ts", "src/main.tsx"],
2999
+ componentRoots: ["src/components", "components"]
3000
+ },
3001
+ verify: {
3002
+ devCommand: "npm run dev",
3003
+ buildCommand: "npm run build",
3004
+ distDir: "dist"
3005
+ }
3006
+ };
3007
+ var DECANTR_ADAPTERS = {
3008
+ "react-vite": reactViteBootstrapAdapter,
3009
+ "next-app": nextAppAdapter,
3010
+ "generic-web": genericWebAdapter
2665
3011
  };
2666
3012
  function resolveBootstrapTarget(target) {
2667
3013
  const normalizedTarget = (target || "react").toLowerCase();
2668
- const platformType = "spa";
3014
+ const platformType = normalizedTarget === "nextjs" ? "ssr" : "spa";
2669
3015
  const packAdapter = resolvePackAdapter(normalizedTarget, platformType);
3016
+ const adapterId = normalizedTarget === "nextjs" ? "next-app" : DECANTR_ADAPTERS[packAdapter] ? packAdapter : "generic-web";
2670
3017
  return {
2671
3018
  target: normalizedTarget,
2672
3019
  platformType,
2673
3020
  packAdapter,
2674
- bootstrapAdapterId: BOOTSTRAP_ADAPTERS[packAdapter]?.id ?? null
3021
+ adapterId,
3022
+ bootstrapAdapterId: DECANTR_ADAPTERS[adapterId]?.capabilities.bootstrap && DECANTR_ADAPTERS[adapterId]?.writeProjectFiles ? adapterId : null
2675
3023
  };
2676
3024
  }
2677
3025
  function getBootstrapAdapter(resolution) {
2678
3026
  if (!resolution.bootstrapAdapterId) {
2679
3027
  return null;
2680
3028
  }
2681
- return BOOTSTRAP_ADAPTERS[resolution.bootstrapAdapterId] ?? null;
3029
+ const adapter = DECANTR_ADAPTERS[resolution.bootstrapAdapterId];
3030
+ return adapter?.writeProjectFiles ? adapter : null;
2682
3031
  }
2683
3032
  function detectRoutingMode(projectDir) {
2684
3033
  try {
2685
- const essence = JSON.parse(readFileSync11(join16(projectDir, "decantr.essence.json"), "utf-8"));
3034
+ const essence = JSON.parse(readFileSync12(join17(projectDir, "decantr.essence.json"), "utf-8"));
2686
3035
  const routing = essence.meta?.platform?.routing;
2687
3036
  if (routing === "history" || routing === "pathname") {
2688
3037
  return routing;
@@ -2694,45 +3043,45 @@ function detectRoutingMode(projectDir) {
2694
3043
  }
2695
3044
 
2696
3045
  // src/offline-content.ts
2697
- import { cpSync, existsSync as existsSync16, mkdirSync as mkdirSync6 } from "fs";
2698
- import { join as join17, resolve } from "path";
3046
+ import { cpSync, existsSync as existsSync17, mkdirSync as mkdirSync7 } from "fs";
3047
+ import { join as join18, resolve } from "path";
2699
3048
  var CONTENT_TYPES2 = ["archetypes", "blueprints", "patterns", "themes", "shells"];
2700
3049
  function copyIfExists(source, target) {
2701
- if (!existsSync16(source)) return false;
3050
+ if (!existsSync17(source)) return false;
2702
3051
  if (resolve(source) === resolve(target)) return true;
2703
3052
  cpSync(source, target, { recursive: true });
2704
3053
  return true;
2705
3054
  }
2706
3055
  function hydrateContentRoot(projectDir, contentRoot) {
2707
- if (!existsSync16(contentRoot)) return false;
2708
- const customRoot = join17(projectDir, ".decantr", "custom");
2709
- const cacheRoot = join17(projectDir, ".decantr", "cache", "@official");
2710
- mkdirSync6(customRoot, { recursive: true });
2711
- mkdirSync6(cacheRoot, { recursive: true });
3056
+ if (!existsSync17(contentRoot)) return false;
3057
+ const customRoot = join18(projectDir, ".decantr", "custom");
3058
+ const cacheRoot = join18(projectDir, ".decantr", "cache", "@official");
3059
+ mkdirSync7(customRoot, { recursive: true });
3060
+ mkdirSync7(cacheRoot, { recursive: true });
2712
3061
  let copiedAny = false;
2713
3062
  for (const type of CONTENT_TYPES2) {
2714
- const sourceDir = join17(contentRoot, type);
2715
- if (!existsSync16(sourceDir)) continue;
2716
- cpSync(sourceDir, join17(customRoot, type), { recursive: true });
2717
- cpSync(sourceDir, join17(cacheRoot, type), { recursive: true });
3063
+ const sourceDir = join18(contentRoot, type);
3064
+ if (!existsSync17(sourceDir)) continue;
3065
+ cpSync(sourceDir, join18(customRoot, type), { recursive: true });
3066
+ cpSync(sourceDir, join18(cacheRoot, type), { recursive: true });
2718
3067
  copiedAny = true;
2719
3068
  }
2720
3069
  return copiedAny;
2721
3070
  }
2722
3071
  function seedOfflineRegistry(projectDir, workspaceRoot) {
2723
- const projectDecantrRoot = join17(projectDir, ".decantr");
2724
- mkdirSync6(projectDecantrRoot, { recursive: true });
3072
+ const projectDecantrRoot = join18(projectDir, ".decantr");
3073
+ mkdirSync7(projectDecantrRoot, { recursive: true });
2725
3074
  const configuredContentRoot = process.env.DECANTR_CONTENT_DIR ? resolve(process.env.DECANTR_CONTENT_DIR) : null;
2726
3075
  if (configuredContentRoot && hydrateContentRoot(projectDir, configuredContentRoot)) {
2727
3076
  return { seeded: true, strategy: "configured-content-root" };
2728
3077
  }
2729
3078
  const copiedCache = copyIfExists(
2730
- join17(workspaceRoot, ".decantr", "cache"),
2731
- join17(projectDecantrRoot, "cache")
3079
+ join18(workspaceRoot, ".decantr", "cache"),
3080
+ join18(projectDecantrRoot, "cache")
2732
3081
  );
2733
3082
  const copiedCustom = copyIfExists(
2734
- join17(workspaceRoot, ".decantr", "custom"),
2735
- join17(projectDecantrRoot, "custom")
3083
+ join18(workspaceRoot, ".decantr", "custom"),
3084
+ join18(projectDecantrRoot, "custom")
2736
3085
  );
2737
3086
  if (copiedCache || copiedCustom) {
2738
3087
  return { seeded: true, strategy: "workspace-cache" };
@@ -2774,7 +3123,9 @@ async function cmdNewProject(projectName, options) {
2774
3123
  const projectDir = resolve2(workspaceRoot, projectName);
2775
3124
  const bootstrapTarget = resolveBootstrapTarget(options.target);
2776
3125
  const bootstrapAdapter = getBootstrapAdapter(bootstrapTarget);
2777
- const hasRunnableBootstrap = Boolean(bootstrapAdapter);
3126
+ const registryBackedScaffold = Boolean(options.blueprint || options.archetype);
3127
+ const inferredAdoption = options.adoption || (registryBackedScaffold ? "decantr-css" : "contract-only");
3128
+ const shouldBootstrapRuntime = Boolean(bootstrapAdapter && inferredAdoption === "decantr-css");
2778
3129
  if (!/^[a-z0-9][a-z0-9._-]*$/i.test(projectName)) {
2779
3130
  console.error(
2780
3131
  error2("Invalid project name. Use alphanumeric characters, hyphens, dots, or underscores.")
@@ -2782,18 +3133,24 @@ async function cmdNewProject(projectName, options) {
2782
3133
  process.exitCode = 1;
2783
3134
  return;
2784
3135
  }
2785
- if (existsSync17(projectDir)) {
3136
+ if (existsSync18(projectDir)) {
2786
3137
  console.error(error2(`Directory "${projectName}" already exists.`));
2787
3138
  process.exitCode = 1;
2788
3139
  return;
2789
3140
  }
2790
3141
  console.log(heading(`Creating ${projectName}...`));
2791
- mkdirSync7(projectDir, { recursive: true });
3142
+ mkdirSync8(projectDir, { recursive: true });
2792
3143
  console.log(dim2(` Created ${projectName}/`));
2793
3144
  const title = projectName.replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
2794
- if (bootstrapAdapter) {
3145
+ if (shouldBootstrapRuntime && bootstrapAdapter) {
2795
3146
  bootstrapAdapter.writeProjectFiles(projectDir, title, "hash");
2796
3147
  console.log(dim2(` Bootstrapped ${bootstrapAdapter.label}`));
3148
+ } else if (bootstrapAdapter) {
3149
+ console.log(
3150
+ dim2(
3151
+ ` Skipping runtime bootstrap for adoption=${inferredAdoption}; creating a Decantr contract-only workspace.`
3152
+ )
3153
+ );
2797
3154
  } else {
2798
3155
  console.log(
2799
3156
  `${YELLOW4} No greenfield bootstrap adapter is available yet for target "${bootstrapTarget.target}" (${bootstrapTarget.packAdapter}).${RESET6}`
@@ -2805,7 +3162,7 @@ async function cmdNewProject(projectName, options) {
2805
3162
  );
2806
3163
  }
2807
3164
  const packageManager = detectPackageManager();
2808
- if (hasRunnableBootstrap) {
3165
+ if (shouldBootstrapRuntime) {
2809
3166
  console.log(heading("Installing dependencies..."));
2810
3167
  try {
2811
3168
  execSync(`${packageManager} install`, { cwd: projectDir, stdio: "inherit" });
@@ -2845,7 +3202,7 @@ ${YELLOW4}Dependency install failed. Run \`${packageManager} install\` manually.
2845
3202
  return;
2846
3203
  }
2847
3204
  console.log(heading("Initializing Decantr..."));
2848
- const initFlags = ["--yes", "--existing"];
3205
+ const initFlags = ["--yes", "--workflow=greenfield", `--adoption=${inferredAdoption}`];
2849
3206
  if (options.blueprint) initFlags.push(`--blueprint=${options.blueprint}`);
2850
3207
  if (options.archetype) initFlags.push(`--archetype=${options.archetype}`);
2851
3208
  if (options.theme) initFlags.push(`--theme=${options.theme}`);
@@ -2854,12 +3211,13 @@ ${YELLOW4}Dependency install failed. Run \`${packageManager} install\` manually.
2854
3211
  if (options.target) initFlags.push(`--target=${options.target}`);
2855
3212
  if (options.offline) initFlags.push("--offline");
2856
3213
  if (options.registry) initFlags.push(`--registry=${options.registry}`);
3214
+ if (options.assistantBridge) initFlags.push(`--assistant-bridge=${options.assistantBridge}`);
2857
3215
  try {
2858
3216
  const bundledCliEntrypoint = fileURLToPath(new URL("./bin.js", import.meta.url));
2859
- const cliEntrypoint = existsSync17(bundledCliEntrypoint) ? bundledCliEntrypoint : process.argv[1] && existsSync17(process.argv[1]) ? process.argv[1] : null;
3217
+ const cliEntrypoint = existsSync18(bundledCliEntrypoint) ? bundledCliEntrypoint : process.argv[1] && existsSync18(process.argv[1]) ? process.argv[1] : null;
2860
3218
  const cliPath = cliEntrypoint ? `"${process.execPath}" "${cliEntrypoint}"` : "npx decantr";
2861
3219
  execSync(`${cliPath} init ${initFlags.join(" ")}`, { cwd: projectDir, stdio: "inherit" });
2862
- if (bootstrapAdapter) {
3220
+ if (shouldBootstrapRuntime && bootstrapAdapter) {
2863
3221
  bootstrapAdapter.writeProjectFiles(projectDir, title, detectRoutingMode(projectDir));
2864
3222
  }
2865
3223
  } catch {
@@ -2872,33 +3230,33 @@ ${YELLOW4}Decantr init encountered issues. Run \`decantr init\` manually inside
2872
3230
  \u2713 Project "${projectName}" created!
2873
3231
  `));
2874
3232
  console.log(` ${cyan2("cd " + projectName)}`);
2875
- if (bootstrapAdapter) {
3233
+ if (shouldBootstrapRuntime) {
2876
3234
  console.log(` ${cyan2(packageManager + " run dev")}`);
2877
3235
  } else {
2878
3236
  console.log(
2879
3237
  dim2(
2880
- ` Contract-only mode for target ${bootstrapTarget.target}. Bring your own runtime, or rerun ${cyan2(`decantr new ${projectName} --target=react`)} for the current starter adapter.`
3238
+ ` Contract-only mode for target ${bootstrapTarget.target}. Bring your own runtime, or rerun with ${cyan2("--adoption=decantr-css")} for a runnable Decantr CSS starter adapter.`
2881
3239
  )
2882
3240
  );
2883
3241
  }
2884
3242
  console.log("");
2885
3243
  }
2886
3244
  function detectPackageManager() {
2887
- if (existsSync17(join18(process.cwd(), "pnpm-lock.yaml")) || existsSync17(join18(process.cwd(), "pnpm-workspace.yaml"))) {
3245
+ if (existsSync18(join19(process.cwd(), "pnpm-lock.yaml")) || existsSync18(join19(process.cwd(), "pnpm-workspace.yaml"))) {
2888
3246
  return "pnpm";
2889
3247
  }
2890
- if (existsSync17(join18(process.cwd(), "yarn.lock"))) {
3248
+ if (existsSync18(join19(process.cwd(), "yarn.lock"))) {
2891
3249
  return "yarn";
2892
3250
  }
2893
- if (existsSync17(join18(process.cwd(), "bun.lockb")) || existsSync17(join18(process.cwd(), "bun.lock"))) {
3251
+ if (existsSync18(join19(process.cwd(), "bun.lockb")) || existsSync18(join19(process.cwd(), "bun.lock"))) {
2894
3252
  return "bun";
2895
3253
  }
2896
3254
  return "npm";
2897
3255
  }
2898
3256
 
2899
3257
  // src/commands/publish.ts
2900
- import { existsSync as existsSync18, readFileSync as readFileSync12 } from "fs";
2901
- import { join as join19 } from "path";
3258
+ import { existsSync as existsSync19, readFileSync as readFileSync13 } from "fs";
3259
+ import { join as join20 } from "path";
2902
3260
  import {
2903
3261
  API_CONTENT_TYPE_TO_CONTENT_TYPE,
2904
3262
  CONTENT_TYPE_TO_API_CONTENT_TYPE as CONTENT_TYPE_TO_API_CONTENT_TYPE2,
@@ -2914,8 +3272,8 @@ async function cmdPublish(type, name, projectRoot = process.cwd()) {
2914
3272
  }
2915
3273
  const singularType = API_CONTENT_TYPE_TO_CONTENT_TYPE[type] || type;
2916
3274
  const pluralType = CONTENT_TYPE_TO_API_CONTENT_TYPE2[type] || CONTENT_TYPE_TO_API_CONTENT_TYPE2[singularType] || `${type}s`;
2917
- const customPath = join19(projectRoot, ".decantr", "custom", pluralType, `${name}.json`);
2918
- if (!existsSync18(customPath)) {
3275
+ const customPath = join20(projectRoot, ".decantr", "custom", pluralType, `${name}.json`);
3276
+ if (!existsSync19(customPath)) {
2919
3277
  console.error(`Custom ${singularType} "${name}" not found at ${customPath}`);
2920
3278
  console.error(`Create one first: decantr create ${singularType} ${name}`);
2921
3279
  process.exitCode = 1;
@@ -2923,7 +3281,7 @@ async function cmdPublish(type, name, projectRoot = process.cwd()) {
2923
3281
  }
2924
3282
  let data;
2925
3283
  try {
2926
- data = JSON.parse(readFileSync12(customPath, "utf-8"));
3284
+ data = JSON.parse(readFileSync13(customPath, "utf-8"));
2927
3285
  } catch {
2928
3286
  console.error(`Failed to parse ${customPath}`);
2929
3287
  process.exitCode = 1;
@@ -2961,23 +3319,23 @@ async function cmdPublish(type, name, projectRoot = process.cwd()) {
2961
3319
  }
2962
3320
 
2963
3321
  // src/commands/refresh.ts
2964
- import { existsSync as existsSync19, readFileSync as readFileSync13 } from "fs";
2965
- import { join as join20 } from "path";
3322
+ import { existsSync as existsSync20, readFileSync as readFileSync14 } from "fs";
3323
+ import { join as join21 } from "path";
2966
3324
  import { isV3 as isV33 } from "@decantr/essence-spec";
2967
3325
  var GREEN7 = "\x1B[32m";
2968
3326
  var RED6 = "\x1B[31m";
2969
3327
  var DIM7 = "\x1B[2m";
2970
3328
  var RESET7 = "\x1B[0m";
2971
3329
  async function cmdRefresh(projectRoot = process.cwd(), options = {}) {
2972
- const essencePath = join20(projectRoot, "decantr.essence.json");
2973
- if (!existsSync19(essencePath)) {
3330
+ const essencePath = join21(projectRoot, "decantr.essence.json");
3331
+ if (!existsSync20(essencePath)) {
2974
3332
  console.error(`${RED6}No decantr.essence.json found. Run \`decantr init\` first.${RESET7}`);
2975
3333
  process.exitCode = 1;
2976
3334
  return;
2977
3335
  }
2978
3336
  let essence;
2979
3337
  try {
2980
- const raw = readFileSync13(essencePath, "utf-8");
3338
+ const raw = readFileSync14(essencePath, "utf-8");
2981
3339
  const parsed = JSON.parse(raw);
2982
3340
  if (!isV33(parsed)) {
2983
3341
  console.error(`${RED6}Essence is not v3. Run \`decantr migrate\` first.${RESET7}`);
@@ -2991,7 +3349,7 @@ async function cmdRefresh(projectRoot = process.cwd(), options = {}) {
2991
3349
  return;
2992
3350
  }
2993
3351
  const registryClient = new RegistryClient({
2994
- cacheDir: join20(projectRoot, ".decantr", "cache"),
3352
+ cacheDir: join21(projectRoot, ".decantr", "cache"),
2995
3353
  offline: options.offline
2996
3354
  });
2997
3355
  console.log("Regenerating derived files...\n");
@@ -3011,8 +3369,8 @@ async function cmdRefresh(projectRoot = process.cwd(), options = {}) {
3011
3369
  }
3012
3370
 
3013
3371
  // src/commands/registry-mirror.ts
3014
- import { mkdirSync as mkdirSync8, writeFileSync as writeFileSync8 } from "fs";
3015
- import { join as join21 } from "path";
3372
+ import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync9 } from "fs";
3373
+ import { join as join22 } from "path";
3016
3374
  import { API_CONTENT_TYPES, RegistryAPIClient as RegistryAPIClient2 } from "@decantr/registry";
3017
3375
  var GREEN8 = "\x1B[32m";
3018
3376
  var RED7 = "\x1B[31m";
@@ -3040,7 +3398,7 @@ async function cmdRegistryMirror(projectRoot, options = {}) {
3040
3398
  process.exitCode = 1;
3041
3399
  return;
3042
3400
  }
3043
- const cacheDir = join21(projectRoot, ".decantr", "cache");
3401
+ const cacheDir = join22(projectRoot, ".decantr", "cache");
3044
3402
  const counts = {};
3045
3403
  const failed = [];
3046
3404
  console.log(`
@@ -3050,19 +3408,19 @@ Mirroring registry content to ${DIM8}.decantr/cache/${RESET8}
3050
3408
  try {
3051
3409
  const result = await apiClient.listContent(type, { namespace: "@official" });
3052
3410
  const items = result.items;
3053
- const typeDir = join21(cacheDir, "@official", type);
3054
- mkdirSync8(typeDir, { recursive: true });
3055
- writeFileSync8(join21(typeDir, "index.json"), JSON.stringify(result, null, 2));
3411
+ const typeDir = join22(cacheDir, "@official", type);
3412
+ mkdirSync9(typeDir, { recursive: true });
3413
+ writeFileSync9(join22(typeDir, "index.json"), JSON.stringify(result, null, 2));
3056
3414
  let itemCount = 0;
3057
3415
  for (const item of items) {
3058
3416
  const slug = item.slug || item.id;
3059
3417
  if (!slug) continue;
3060
3418
  try {
3061
3419
  const fullItem = await apiClient.getContent(type, "@official", slug);
3062
- writeFileSync8(join21(typeDir, `${slug}.json`), JSON.stringify(fullItem, null, 2));
3420
+ writeFileSync9(join22(typeDir, `${slug}.json`), JSON.stringify(fullItem, null, 2));
3063
3421
  itemCount++;
3064
3422
  } catch {
3065
- writeFileSync8(join21(typeDir, `${slug}.json`), JSON.stringify(item, null, 2));
3423
+ writeFileSync9(join22(typeDir, `${slug}.json`), JSON.stringify(item, null, 2));
3066
3424
  itemCount++;
3067
3425
  }
3068
3426
  }
@@ -3077,8 +3435,8 @@ Mirroring registry content to ${DIM8}.decantr/cache/${RESET8}
3077
3435
  mirrored_at: (/* @__PURE__ */ new Date()).toISOString(),
3078
3436
  counts
3079
3437
  };
3080
- mkdirSync8(join21(cacheDir), { recursive: true });
3081
- writeFileSync8(join21(cacheDir, "mirror-manifest.json"), JSON.stringify(manifest, null, 2));
3438
+ mkdirSync9(join22(cacheDir), { recursive: true });
3439
+ writeFileSync9(join22(cacheDir, "mirror-manifest.json"), JSON.stringify(manifest, null, 2));
3082
3440
  const totalItems = Object.values(counts).reduce((a, b) => a + b, 0);
3083
3441
  console.log("");
3084
3442
  if (failed.length > 0) {
@@ -3095,23 +3453,23 @@ Mirroring registry content to ${DIM8}.decantr/cache/${RESET8}
3095
3453
  }
3096
3454
 
3097
3455
  // src/commands/remove.ts
3098
- import { existsSync as existsSync21, readFileSync as readFileSync14, rmSync as rmSync2, writeFileSync as writeFileSync9 } from "fs";
3099
- import { join as join22 } from "path";
3456
+ import { existsSync as existsSync22, readFileSync as readFileSync15, rmSync as rmSync2, writeFileSync as writeFileSync10 } from "fs";
3457
+ import { join as join23 } from "path";
3100
3458
  import { isV3 as isV34, migrateV30ToV31 as migrateV30ToV312 } from "@decantr/essence-spec";
3101
3459
  var GREEN9 = "\x1B[32m";
3102
3460
  var RED8 = "\x1B[31m";
3103
3461
  var DIM9 = "\x1B[2m";
3104
3462
  var RESET9 = "\x1B[0m";
3105
3463
  function readAndMigrate2(projectRoot) {
3106
- const essencePath = join22(projectRoot, "decantr.essence.json");
3107
- if (!existsSync21(essencePath)) {
3464
+ const essencePath = join23(projectRoot, "decantr.essence.json");
3465
+ if (!existsSync22(essencePath)) {
3108
3466
  console.error(`${RED8}No decantr.essence.json found. Run \`decantr init\` first.${RESET9}`);
3109
3467
  process.exitCode = 1;
3110
3468
  return null;
3111
3469
  }
3112
3470
  let parsed;
3113
3471
  try {
3114
- parsed = JSON.parse(readFileSync14(essencePath, "utf-8"));
3472
+ parsed = JSON.parse(readFileSync15(essencePath, "utf-8"));
3115
3473
  } catch (e) {
3116
3474
  console.error(`${RED8}Could not read essence: ${e.message}${RESET9}`);
3117
3475
  process.exitCode = 1;
@@ -3126,7 +3484,7 @@ function readAndMigrate2(projectRoot) {
3126
3484
  return { essence, essencePath };
3127
3485
  }
3128
3486
  function writeEssence2(essencePath, essence) {
3129
- writeFileSync9(essencePath, JSON.stringify(essence, null, 2) + "\n");
3487
+ writeFileSync10(essencePath, JSON.stringify(essence, null, 2) + "\n");
3130
3488
  }
3131
3489
  function recomputeGlobalFeatures(essence) {
3132
3490
  const all = /* @__PURE__ */ new Set();
@@ -3168,14 +3526,14 @@ async function cmdRemoveSection(sectionId, args, projectRoot = process.cwd()) {
3168
3526
  sections.splice(idx, 1);
3169
3527
  recomputeGlobalFeatures(essence);
3170
3528
  removeRoutes(essence, sectionId);
3171
- const contextFile = join22(projectRoot, ".decantr", "context", `${sectionId}.md`);
3172
- if (existsSync21(contextFile)) {
3529
+ const contextFile = join23(projectRoot, ".decantr", "context", `${sectionId}.md`);
3530
+ if (existsSync22(contextFile)) {
3173
3531
  rmSync2(contextFile);
3174
3532
  }
3175
3533
  writeEssence2(essencePath, essence);
3176
3534
  console.log(`${GREEN9}Removed section "${sectionId}".${RESET9}`);
3177
3535
  const registryClient = new RegistryClient({
3178
- cacheDir: join22(projectRoot, ".decantr", "cache")
3536
+ cacheDir: join23(projectRoot, ".decantr", "cache")
3179
3537
  });
3180
3538
  await refreshDerivedFiles(projectRoot, essence, registryClient);
3181
3539
  console.log(`${GREEN9}Derived files refreshed.${RESET9}`);
@@ -3211,7 +3569,7 @@ async function cmdRemovePage(path, args, projectRoot = process.cwd()) {
3211
3569
  writeEssence2(essencePath, essence);
3212
3570
  console.log(`${GREEN9}Removed page "${pageId}" from section "${sectionId}".${RESET9}`);
3213
3571
  const registryClient = new RegistryClient({
3214
- cacheDir: join22(projectRoot, ".decantr", "cache")
3572
+ cacheDir: join23(projectRoot, ".decantr", "cache")
3215
3573
  });
3216
3574
  await refreshDerivedFiles(projectRoot, essence, registryClient);
3217
3575
  console.log(`${GREEN9}Derived files refreshed.${RESET9}`);
@@ -3252,15 +3610,15 @@ async function cmdRemoveFeature(feature, args, projectRoot = process.cwd()) {
3252
3610
  const target = sectionId ? `section "${sectionId}" and global` : "global";
3253
3611
  console.log(`${GREEN9}Removed feature "${feature}" from ${target} features.${RESET9}`);
3254
3612
  const registryClient = new RegistryClient({
3255
- cacheDir: join22(projectRoot, ".decantr", "cache")
3613
+ cacheDir: join23(projectRoot, ".decantr", "cache")
3256
3614
  });
3257
3615
  await refreshDerivedFiles(projectRoot, essence, registryClient);
3258
3616
  console.log(`${GREEN9}Derived files refreshed.${RESET9}`);
3259
3617
  }
3260
3618
 
3261
3619
  // src/commands/sync-drift.ts
3262
- import { existsSync as existsSync22, readFileSync as readFileSync15, writeFileSync as writeFileSync10 } from "fs";
3263
- import { join as join23 } from "path";
3620
+ import { existsSync as existsSync23, readFileSync as readFileSync16, writeFileSync as writeFileSync11 } from "fs";
3621
+ import { join as join24 } from "path";
3264
3622
  var GREEN10 = "\x1B[32m";
3265
3623
  var RED9 = "\x1B[31m";
3266
3624
  var YELLOW6 = "\x1B[33m";
@@ -3269,8 +3627,8 @@ var DIM10 = "\x1B[2m";
3269
3627
  var BOLD4 = "\x1B[1m";
3270
3628
  var CYAN5 = "\x1B[36m";
3271
3629
  async function cmdSyncDrift(projectRoot = process.cwd()) {
3272
- const driftLogPath = join23(projectRoot, ".decantr", "drift-log.json");
3273
- if (!existsSync22(driftLogPath)) {
3630
+ const driftLogPath = join24(projectRoot, ".decantr", "drift-log.json");
3631
+ if (!existsSync23(driftLogPath)) {
3274
3632
  console.log(`${GREEN10}No drift log found \u2014 no drift recorded.${RESET10}`);
3275
3633
  console.log(
3276
3634
  `${DIM10}Drift is logged when guard violations are detected during development.${RESET10}`
@@ -3279,7 +3637,7 @@ async function cmdSyncDrift(projectRoot = process.cwd()) {
3279
3637
  }
3280
3638
  let entries;
3281
3639
  try {
3282
- entries = JSON.parse(readFileSync15(driftLogPath, "utf-8"));
3640
+ entries = JSON.parse(readFileSync16(driftLogPath, "utf-8"));
3283
3641
  } catch {
3284
3642
  console.error(`${RED9}Could not parse drift-log.json${RESET10}`);
3285
3643
  process.exitCode = 1;
@@ -3325,13 +3683,13 @@ ${BOLD4}Unresolved Drift Entries (${unresolved.length})${RESET10}
3325
3683
  console.log("");
3326
3684
  }
3327
3685
  function resolveDriftEntries(projectRoot, options) {
3328
- const driftLogPath = join23(projectRoot, ".decantr", "drift-log.json");
3329
- if (!existsSync22(driftLogPath)) {
3686
+ const driftLogPath = join24(projectRoot, ".decantr", "drift-log.json");
3687
+ if (!existsSync23(driftLogPath)) {
3330
3688
  return { success: true };
3331
3689
  }
3332
3690
  if (options.clear) {
3333
3691
  try {
3334
- writeFileSync10(driftLogPath, "[]");
3692
+ writeFileSync11(driftLogPath, "[]");
3335
3693
  return { success: true };
3336
3694
  } catch (e) {
3337
3695
  return { success: false, error: `Could not clear drift log: ${e.message}` };
@@ -3339,7 +3697,7 @@ function resolveDriftEntries(projectRoot, options) {
3339
3697
  }
3340
3698
  let entries;
3341
3699
  try {
3342
- entries = JSON.parse(readFileSync15(driftLogPath, "utf-8"));
3700
+ entries = JSON.parse(readFileSync16(driftLogPath, "utf-8"));
3343
3701
  } catch {
3344
3702
  return { success: false, error: "Could not parse drift-log.json" };
3345
3703
  }
@@ -3358,7 +3716,7 @@ function resolveDriftEntries(projectRoot, options) {
3358
3716
  }
3359
3717
  }
3360
3718
  try {
3361
- writeFileSync10(driftLogPath, JSON.stringify(entries, null, 2));
3719
+ writeFileSync11(driftLogPath, JSON.stringify(entries, null, 2));
3362
3720
  return { success: true };
3363
3721
  } catch (e) {
3364
3722
  return { success: false, error: `Could not write drift log: ${e.message}` };
@@ -3366,8 +3724,8 @@ function resolveDriftEntries(projectRoot, options) {
3366
3724
  }
3367
3725
 
3368
3726
  // src/commands/theme-switch.ts
3369
- import { existsSync as existsSync23, readFileSync as readFileSync16, writeFileSync as writeFileSync11 } from "fs";
3370
- import { join as join24 } from "path";
3727
+ import { existsSync as existsSync24, readFileSync as readFileSync17, writeFileSync as writeFileSync12 } from "fs";
3728
+ import { join as join25 } from "path";
3371
3729
  import { isV3 as isV35, migrateV30ToV31 as migrateV30ToV313 } from "@decantr/essence-spec";
3372
3730
  var GREEN11 = "\x1B[32m";
3373
3731
  var RED10 = "\x1B[31m";
@@ -3384,15 +3742,15 @@ async function cmdThemeSwitch(themeName, args, projectRoot = process.cwd()) {
3384
3742
  process.exitCode = 1;
3385
3743
  return;
3386
3744
  }
3387
- const essencePath = join24(projectRoot, "decantr.essence.json");
3388
- if (!existsSync23(essencePath)) {
3745
+ const essencePath = join25(projectRoot, "decantr.essence.json");
3746
+ if (!existsSync24(essencePath)) {
3389
3747
  console.error(`${RED10}No decantr.essence.json found. Run \`decantr init\` first.${RESET11}`);
3390
3748
  process.exitCode = 1;
3391
3749
  return;
3392
3750
  }
3393
3751
  let parsed;
3394
3752
  try {
3395
- parsed = JSON.parse(readFileSync16(essencePath, "utf-8"));
3753
+ parsed = JSON.parse(readFileSync17(essencePath, "utf-8"));
3396
3754
  } catch (e) {
3397
3755
  console.error(`${RED10}Could not read essence: ${e.message}${RESET11}`);
3398
3756
  process.exitCode = 1;
@@ -3441,7 +3799,7 @@ async function cmdThemeSwitch(themeName, args, projectRoot = process.cwd()) {
3441
3799
  essence.dna.theme.mode = mode;
3442
3800
  }
3443
3801
  const registryClient = new RegistryClient({
3444
- cacheDir: join24(projectRoot, ".decantr", "cache")
3802
+ cacheDir: join25(projectRoot, ".decantr", "cache")
3445
3803
  });
3446
3804
  try {
3447
3805
  const themeResult = await registryClient.fetchTheme(themeName);
@@ -3456,7 +3814,7 @@ async function cmdThemeSwitch(themeName, args, projectRoot = process.cwd()) {
3456
3814
  }
3457
3815
  } catch {
3458
3816
  }
3459
- writeFileSync11(essencePath, JSON.stringify(essence, null, 2) + "\n");
3817
+ writeFileSync12(essencePath, JSON.stringify(essence, null, 2) + "\n");
3460
3818
  console.log(`${GREEN11}Switched theme: ${oldThemeId} \u2192 ${themeName}${RESET11}`);
3461
3819
  if (shape) console.log(` ${DIM11}Shape: ${shape}${RESET11}`);
3462
3820
  if (mode) console.log(` ${DIM11}Mode: ${mode}${RESET11}`);
@@ -3478,10 +3836,10 @@ var CYAN6 = "\x1B[36m";
3478
3836
  function ask(question, defaultValue) {
3479
3837
  const rl = createInterface({ input: process.stdin, output: process.stdout });
3480
3838
  const prompt = defaultValue ? `${question} ${DIM12}(${defaultValue})${RESET12}: ` : `${question}: `;
3481
- return new Promise((resolve4) => {
3839
+ return new Promise((resolve5) => {
3482
3840
  rl.question(prompt, (answer) => {
3483
3841
  rl.close();
3484
- resolve4(answer.trim() || defaultValue || "");
3842
+ resolve5(answer.trim() || defaultValue || "");
3485
3843
  });
3486
3844
  });
3487
3845
  }
@@ -3663,7 +4021,11 @@ async function runInteractivePrompts(detected, archetypes, blueprints, themes, w
3663
4021
  personality: ["professional"],
3664
4022
  features: [],
3665
4023
  existing: workflowSeed?.existing || detected.existingEssence,
3666
- workflowMode: workflowSeed?.workflowMode
4024
+ workflowMode: workflowSeed?.workflowMode,
4025
+ adoptionMode: workflowSeed?.adoptionMode,
4026
+ contentSource: workflowSeed?.contentSource,
4027
+ assistantBridge: workflowSeed?.assistantBridge,
4028
+ projectScope: workflowSeed?.projectScope
3667
4029
  };
3668
4030
  }
3669
4031
  function parseFlags(args, detected) {
@@ -3685,6 +4047,12 @@ function parseFlags(args, detected) {
3685
4047
  if (typeof args.features === "string")
3686
4048
  options.features = args.features.split(",").map((s) => s.trim());
3687
4049
  if (args.existing === true) options.existing = true;
4050
+ if (args.adoption === "contract-only" || args.adoption === "style-bridge" || args.adoption === "decantr-css") {
4051
+ options.adoptionMode = args.adoption;
4052
+ }
4053
+ if (args["assistant-bridge"] === "none" || args["assistant-bridge"] === "preview" || args["assistant-bridge"] === "apply") {
4054
+ options.assistantBridge = args["assistant-bridge"];
4055
+ }
3688
4056
  return options;
3689
4057
  }
3690
4058
  function mergeWithDefaults(flags, detected, workflowSeed) {
@@ -3701,12 +4069,16 @@ function mergeWithDefaults(flags, detected, workflowSeed) {
3701
4069
  personality: flags.personality || ["professional"],
3702
4070
  features: flags.features || [],
3703
4071
  existing: flags.existing || workflowSeed?.existing || detected.existingEssence,
3704
- workflowMode: flags.workflowMode || workflowSeed?.workflowMode
4072
+ workflowMode: flags.workflowMode || workflowSeed?.workflowMode,
4073
+ adoptionMode: flags.adoptionMode || workflowSeed?.adoptionMode,
4074
+ contentSource: flags.contentSource || workflowSeed?.contentSource,
4075
+ assistantBridge: flags.assistantBridge || workflowSeed?.assistantBridge,
4076
+ projectScope: flags.projectScope || workflowSeed?.projectScope
3705
4077
  };
3706
4078
  }
3707
4079
  async function runSimplifiedInit(blueprints) {
3708
4080
  const rl = createInterface({ input: process.stdin, output: process.stdout });
3709
- const question = (q) => new Promise((resolve4) => rl.question(q, resolve4));
4081
+ const question = (q) => new Promise((resolve5) => rl.question(q, resolve5));
3710
4082
  console.log("\n? What blueprint would you like to scaffold?\n");
3711
4083
  console.log(" 1. Decantr default (recommended)");
3712
4084
  console.log(" 2. Search registry...\n");
@@ -3738,8 +4110,8 @@ async function runSimplifiedInit(blueprints) {
3738
4110
  }
3739
4111
 
3740
4112
  // src/theme-commands.ts
3741
- import { existsSync as existsSync24, mkdirSync as mkdirSync9, readdirSync as readdirSync5, readFileSync as readFileSync17, rmSync as rmSync3, writeFileSync as writeFileSync12 } from "fs";
3742
- import { join as join25 } from "path";
4113
+ import { existsSync as existsSync25, mkdirSync as mkdirSync10, readdirSync as readdirSync5, readFileSync as readFileSync18, rmSync as rmSync3, writeFileSync as writeFileSync13 } from "fs";
4114
+ import { join as join26 } from "path";
3743
4115
  var REQUIRED_FIELDS = [
3744
4116
  "$schema",
3745
4117
  "id",
@@ -3799,20 +4171,20 @@ function validateCustomTheme(theme) {
3799
4171
  };
3800
4172
  }
3801
4173
  function createTheme(projectRoot, id, name) {
3802
- const customThemesDir = join25(projectRoot, ".decantr", "custom", "themes");
3803
- const themePath = join25(customThemesDir, `${id}.json`);
3804
- const howToPath = join25(customThemesDir, "how-to-theme.md");
3805
- mkdirSync9(customThemesDir, { recursive: true });
3806
- if (existsSync24(themePath)) {
4174
+ const customThemesDir = join26(projectRoot, ".decantr", "custom", "themes");
4175
+ const themePath = join26(customThemesDir, `${id}.json`);
4176
+ const howToPath = join26(customThemesDir, "how-to-theme.md");
4177
+ mkdirSync10(customThemesDir, { recursive: true });
4178
+ if (existsSync25(themePath)) {
3807
4179
  return {
3808
4180
  success: false,
3809
4181
  error: `Theme "${id}" already exists at ${themePath}`
3810
4182
  };
3811
4183
  }
3812
4184
  const skeleton = getThemeSkeleton(id, name);
3813
- writeFileSync12(themePath, JSON.stringify(skeleton, null, 2));
3814
- if (!existsSync24(howToPath)) {
3815
- writeFileSync12(howToPath, getHowToThemeDoc());
4185
+ writeFileSync13(themePath, JSON.stringify(skeleton, null, 2));
4186
+ if (!existsSync25(howToPath)) {
4187
+ writeFileSync13(howToPath, getHowToThemeDoc());
3816
4188
  }
3817
4189
  return {
3818
4190
  success: true,
@@ -3820,17 +4192,17 @@ function createTheme(projectRoot, id, name) {
3820
4192
  };
3821
4193
  }
3822
4194
  function listCustomThemes(projectRoot) {
3823
- const customThemesDir = join25(projectRoot, ".decantr", "custom", "themes");
3824
- if (!existsSync24(customThemesDir)) {
4195
+ const customThemesDir = join26(projectRoot, ".decantr", "custom", "themes");
4196
+ if (!existsSync25(customThemesDir)) {
3825
4197
  return [];
3826
4198
  }
3827
4199
  const themes = [];
3828
4200
  try {
3829
4201
  const files = readdirSync5(customThemesDir).filter((f) => f.endsWith(".json"));
3830
4202
  for (const file of files) {
3831
- const filePath = join25(customThemesDir, file);
4203
+ const filePath = join26(customThemesDir, file);
3832
4204
  try {
3833
- const data = JSON.parse(readFileSync17(filePath, "utf-8"));
4205
+ const data = JSON.parse(readFileSync18(filePath, "utf-8"));
3834
4206
  themes.push({
3835
4207
  id: data.id || file.replace(".json", ""),
3836
4208
  name: data.name || data.id,
@@ -3845,8 +4217,8 @@ function listCustomThemes(projectRoot) {
3845
4217
  return themes;
3846
4218
  }
3847
4219
  function deleteTheme(projectRoot, id) {
3848
- const themePath = join25(projectRoot, ".decantr", "custom", "themes", `${id}.json`);
3849
- if (!existsSync24(themePath)) {
4220
+ const themePath = join26(projectRoot, ".decantr", "custom", "themes", `${id}.json`);
4221
+ if (!existsSync25(themePath)) {
3850
4222
  return {
3851
4223
  success: false,
3852
4224
  error: `Theme "${id}" not found at ${themePath}`
@@ -3863,7 +4235,7 @@ function deleteTheme(projectRoot, id) {
3863
4235
  }
3864
4236
  }
3865
4237
  function importTheme(projectRoot, sourcePath) {
3866
- if (!existsSync24(sourcePath)) {
4238
+ if (!existsSync25(sourcePath)) {
3867
4239
  return {
3868
4240
  success: false,
3869
4241
  errors: [`Source file not found: ${sourcePath}`]
@@ -3871,7 +4243,7 @@ function importTheme(projectRoot, sourcePath) {
3871
4243
  }
3872
4244
  let theme;
3873
4245
  try {
3874
- theme = JSON.parse(readFileSync17(sourcePath, "utf-8"));
4246
+ theme = JSON.parse(readFileSync18(sourcePath, "utf-8"));
3875
4247
  } catch (e) {
3876
4248
  return {
3877
4249
  success: false,
@@ -3887,20 +4259,90 @@ function importTheme(projectRoot, sourcePath) {
3887
4259
  }
3888
4260
  theme.source = "custom";
3889
4261
  const id = theme.id;
3890
- const customThemesDir = join25(projectRoot, ".decantr", "custom", "themes");
3891
- const destPath = join25(customThemesDir, `${id}.json`);
3892
- mkdirSync9(customThemesDir, { recursive: true });
3893
- const howToPath = join25(customThemesDir, "how-to-theme.md");
3894
- if (!existsSync24(howToPath)) {
3895
- writeFileSync12(howToPath, getHowToThemeDoc());
3896
- }
3897
- writeFileSync12(destPath, JSON.stringify(theme, null, 2));
4262
+ const customThemesDir = join26(projectRoot, ".decantr", "custom", "themes");
4263
+ const destPath = join26(customThemesDir, `${id}.json`);
4264
+ mkdirSync10(customThemesDir, { recursive: true });
4265
+ const howToPath = join26(customThemesDir, "how-to-theme.md");
4266
+ if (!existsSync25(howToPath)) {
4267
+ writeFileSync13(howToPath, getHowToThemeDoc());
4268
+ }
4269
+ writeFileSync13(destPath, JSON.stringify(theme, null, 2));
3898
4270
  return {
3899
4271
  success: true,
3900
4272
  path: destPath
3901
4273
  };
3902
4274
  }
3903
4275
 
4276
+ // src/workspace.ts
4277
+ import { existsSync as existsSync26, readdirSync as readdirSync6, readFileSync as readFileSync19 } from "fs";
4278
+ import { dirname as dirname3, join as join27, resolve as resolve3 } from "path";
4279
+ function readPackageJson(dir) {
4280
+ const path = join27(dir, "package.json");
4281
+ if (!existsSync26(path)) return null;
4282
+ try {
4283
+ return JSON.parse(readFileSync19(path, "utf-8"));
4284
+ } catch {
4285
+ return null;
4286
+ }
4287
+ }
4288
+ function hasWorkspaceMarker(dir) {
4289
+ if (existsSync26(join27(dir, "pnpm-workspace.yaml")) || existsSync26(join27(dir, "turbo.json")) || existsSync26(join27(dir, "nx.json"))) {
4290
+ return true;
4291
+ }
4292
+ const pkg = readPackageJson(dir);
4293
+ return Boolean(pkg?.workspaces);
4294
+ }
4295
+ function findWorkspaceRoot(startDir) {
4296
+ let current = resolve3(startDir);
4297
+ while (true) {
4298
+ if (hasWorkspaceMarker(current)) return current;
4299
+ const parent = dirname3(current);
4300
+ if (parent === current) return null;
4301
+ current = parent;
4302
+ }
4303
+ }
4304
+ function looksLikeApp(dir) {
4305
+ if (existsSync26(join27(dir, "next.config.js")) || existsSync26(join27(dir, "next.config.ts")) || existsSync26(join27(dir, "next.config.mjs")) || existsSync26(join27(dir, "vite.config.ts")) || existsSync26(join27(dir, "vite.config.js")) || existsSync26(join27(dir, "angular.json")) || existsSync26(join27(dir, "svelte.config.js")) || existsSync26(join27(dir, "svelte.config.ts")) || existsSync26(join27(dir, "astro.config.mjs")) || existsSync26(join27(dir, "src")) || existsSync26(join27(dir, "app")) || existsSync26(join27(dir, "pages"))) {
4306
+ return true;
4307
+ }
4308
+ const pkg = readPackageJson(dir);
4309
+ const deps = { ...pkg?.dependencies, ...pkg?.devDependencies };
4310
+ return Boolean(
4311
+ deps.react || deps.next || deps.vue || deps.svelte || deps["@angular/core"] || deps.astro || deps.nuxt
4312
+ );
4313
+ }
4314
+ function listWorkspaceApps(workspaceRoot) {
4315
+ const candidates = [];
4316
+ for (const base of ["apps", "packages"]) {
4317
+ const baseDir = join27(workspaceRoot, base);
4318
+ if (!existsSync26(baseDir)) continue;
4319
+ for (const entry of readdirSync6(baseDir, { withFileTypes: true })) {
4320
+ if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
4321
+ const candidate = join27(baseDir, entry.name);
4322
+ if (looksLikeApp(candidate)) {
4323
+ candidates.push(`${base}/${entry.name}`);
4324
+ }
4325
+ }
4326
+ }
4327
+ return candidates.sort();
4328
+ }
4329
+ function resolveWorkspaceInfo(cwd, projectArg) {
4330
+ const absoluteCwd = resolve3(cwd);
4331
+ const workspaceRoot = findWorkspaceRoot(absoluteCwd) ?? absoluteCwd;
4332
+ const appRoot = projectArg ? resolve3(absoluteCwd, projectArg) : absoluteCwd;
4333
+ const appCandidates = listWorkspaceApps(workspaceRoot);
4334
+ const projectScope = workspaceRoot !== appRoot || appCandidates.length > 0 ? "workspace-app" : "single-app";
4335
+ const requiresProjectSelection = !projectArg && workspaceRoot === absoluteCwd && appCandidates.length > 1;
4336
+ return {
4337
+ cwd: absoluteCwd,
4338
+ workspaceRoot,
4339
+ appRoot,
4340
+ projectScope,
4341
+ appCandidates,
4342
+ requiresProjectSelection
4343
+ };
4344
+ }
4345
+
3904
4346
  // src/index.ts
3905
4347
  var BOLD6 = "\x1B[1m";
3906
4348
  var DIM13 = "\x1B[2m";
@@ -3997,8 +4439,19 @@ function extractPatternName(item) {
3997
4439
  }
3998
4440
  function generateGreenfieldPrompt(ctx) {
3999
4441
  const lines = [];
4442
+ const usesDecantrCss = ctx.adoptionMode === "decantr-css" || !ctx.adoptionMode;
4000
4443
  lines.push("Build this greenfield application using the Decantr design system.");
4001
4444
  lines.push("");
4445
+ if (ctx.blueprint) lines.push(`Blueprint: ${ctx.blueprint}`);
4446
+ if (ctx.archetype) lines.push(`Primary archetype: ${ctx.archetype}`);
4447
+ if (ctx.theme) lines.push(`Theme: ${ctx.theme}${ctx.mode ? ` (${ctx.mode})` : ""}`);
4448
+ if (ctx.target) lines.push(`Target: ${ctx.target}`);
4449
+ if (ctx.pages.length > 0) {
4450
+ lines.push(
4451
+ `Routes/pages: ${ctx.pages.map((page) => `${page.sectionId ? `${page.sectionId}/` : ""}${page.id}:${page.shell}`).join(", ")}`
4452
+ );
4453
+ }
4454
+ lines.push("");
4002
4455
  lines.push(
4003
4456
  "This workspace is a new Decantr scaffold. Use the contract to create or extend the runtime deliberately, not to reverse-engineer a hidden starter."
4004
4457
  );
@@ -4013,210 +4466,201 @@ function generateGreenfieldPrompt(ctx) {
4013
4466
  lines.push("");
4014
4467
  lines.push("Read in this order:");
4015
4468
  lines.push(
4016
- '1. DECANTR.md for the design spec, "Atoms in 5 minutes" walkthrough, treatment catalog, motion/typography/elevation tables, and the Interaction Requirements canonical-implementation table.'
4469
+ "1. .decantr/context/scaffold-pack.md \u2014 the canonical compiled contract. Contains route plan, shell layouts, navigation, Required Theme Decorators, and project-wide execution rules."
4017
4470
  );
4018
4471
  lines.push(
4019
- '2. .decantr/context/scaffold-pack.md \u2014 the canonical compiled contract. Contains the route plan, shell layouts, navigation, AND the project-wide "Required Theme Decorators" table (class | intent | apply-to). This table is the authoritative decorator contract; section packs reference it.'
4472
+ "2. Before section work, read the matching .decantr/context/section-*-pack.md first, then .decantr/context/section-*.md only for extra slot/layout detail."
4020
4473
  );
4021
4474
  lines.push(
4022
- "3. .decantr/context/scaffold.md for the broader app overview, topology, route map, and voice guidance."
4475
+ "3. Before route work, read the matching .decantr/context/page-*-pack.md file. Its pattern layout and interaction checklists are contract."
4023
4476
  );
4024
4477
  lines.push(
4025
- '4. Before working on any section, read its matching .decantr/context/section-*-pack.md (compact contract \u2014 pay close attention to the "Section Directives" block; those are non-negotiable execution rules) and then .decantr/context/section-*.md (long-form context with shell internal_layout + slot guidance).'
4478
+ "4. .decantr/context/scaffold.md for broader topology, route map, and voice guidance after the compact packs are understood."
4026
4479
  );
4027
4480
  lines.push(
4028
- '5. Before working on any route/page, read its matching .decantr/context/page-*-pack.md file. The "Interactions (MUST implement each)" checklist per pattern is contract \u2014 see the next section.'
4481
+ "5. DECANTR.md as a lookup reference for atoms, treatments, decorators, interaction implementations, and guard rules. Do not let narrative docs override compiled packs."
4029
4482
  );
4030
4483
  lines.push("");
4031
4484
  lines.push("\u2550\u2550\u2550 INTERACTIONS ARE CONTRACT, NOT GUIDANCE \u2550\u2550\u2550");
4032
4485
  lines.push("");
4033
4486
  lines.push(
4034
- 'Each page pack lists an "Interactions (MUST implement each)" checkbox list per pattern. Treat each checkbox as a contract item, NOT a suggestion. The DECANTR.md "Interaction Requirements" table maps each canonical interaction to its required implementation:'
4035
- );
4036
- lines.push(
4037
- "- drag-nodes \u2192 onPointerDown/Move handlers with 4px threshold + cursor: grab/grabbing"
4038
- );
4039
- lines.push("- status-pulse \u2192 d-pulse class on the indicator element");
4040
- lines.push(
4041
- "- animate-on-mount \u2192 d-enter-fade / d-enter-slide-up / d-enter-scale on the pattern root"
4042
- );
4043
- lines.push(
4044
- '- stagger-children \u2192 d-stagger-children parent + style={{ "--d-stagger-index": i }} on each child'
4045
- );
4046
- lines.push(
4047
- "- pan-background \u2192 pointer handlers on canvas BACKGROUND only (not nodes); translate viewport transform"
4048
- );
4049
- lines.push("- zoom-scroll \u2192 onWheel adjusting a scale transform, clamped [0.25, 4]");
4050
- lines.push("- keyboard-navigation \u2192 onKeyDown arrow handlers + tabIndex={0}");
4051
- lines.push("- focus-trap \u2192 Tab cycle inside dialog; restore focus on close");
4052
- lines.push("- hover-tooltip \u2192 data-tooltip + onMouseEnter handler showing popover");
4053
- lines.push(
4054
- "- real-time-updates \u2192 setInterval (2-8s) updating mock state with d-pulse on changed elements"
4055
- );
4056
- lines.push("- scroll-reveal \u2192 IntersectionObserver (once: true) triggering d-enter-fade");
4057
- lines.push("- inline-edit \u2192 controlled <input> on click; commit on blur or Enter");
4058
- lines.push(
4059
- "- lift-hover / scale-hover / glow-hover \u2192 matching d-*-hover treatment classes"
4060
- );
4061
- lines.push("");
4062
- lines.push(
4063
- "`decantr check --strict` FAILS the build when a declared interaction has no matching implementation. The full canonical-implementation table is in DECANTR.md."
4487
+ 'Each page pack lists "Interactions (MUST implement each)" per pattern. Implement the actual behavior, not visible text saying it exists. Use DECANTR.md only to look up the canonical implementation shape when needed.'
4064
4488
  );
4065
- lines.push("");
4066
- lines.push("\u2550\u2550\u2550 ATOMS-FIRST FOR LAYOUT \u2014 DO NOT INLINE-STYLE \u2550\u2550\u2550");
4067
- lines.push("");
4068
4489
  lines.push(
4069
- 'Decantr ships @decantr/css as a runtime atom system (already in package.json). Use atoms via `css(...)` for ALL layout, spacing, sizing, flex/grid, position, and typography sizing \u2014 wherever you would otherwise reach for `style={{ display: "flex", gap: "1rem", padding: "1rem", ... }}`.'
4490
+ "Examples: pointer handlers for dragging/panning, onWheel for zoom, onKeyDown + tabIndex for keyboard navigation, IntersectionObserver for scroll reveal, state updates for real-time indicators, and d-* motion classes where the contract calls for animation."
4070
4491
  );
4071
4492
  lines.push("");
4072
- lines.push("Mandatory translation (NOT optional). USE THESE EXACT ATOM NAMES:");
4073
- lines.push(
4074
- ' \u274C style={{ display: "flex", gap: "1rem" }} \u2192 \u2705 className={css("_flex _gap4")}'
4075
- );
4076
- lines.push(
4077
- ' \u274C style={{ flexDirection: "column", alignItems: "center" }} \u2192 \u2705 className={css("_col _aic")}'
4078
- );
4079
- lines.push(
4080
- ' \u274C style={{ justifyContent: "space-between" }} \u2192 \u2705 className={css("_jcsb")}'
4081
- );
4082
- lines.push(
4083
- ' \u274C style={{ padding: "1rem 1.5rem" }} \u2192 \u2705 className={css("_py4 _px6")}'
4084
- );
4085
- lines.push(
4086
- ' \u274C style={{ gridTemplateColumns: "repeat(3, 1fr)" }} \u2192 \u2705 className={css("_grid _gc3")}'
4087
- );
4088
- lines.push(
4089
- ' \u274C style={{ position: "sticky", top: 0 }} \u2192 \u2705 className={css("_sticky _top0")}'
4090
- );
4091
- lines.push(
4092
- ' \u274C style={{ width: "100%", maxWidth: "40rem" }} \u2192 \u2705 className={css("_wfull _maxw[40rem]")}'
4093
- );
4094
4493
  lines.push(
4095
- ' \u274C style={{ marginInline: "auto" }} \u2192 \u2705 className={css("_mxauto")}'
4096
- );
4097
- lines.push(
4098
- ' \u274C style={{ fontSize: "1.5rem" }} \u2192 \u2705 className={css("_text2xl")}'
4494
+ "`decantr check --strict` fails when a declared interaction has no matching implementation."
4099
4495
  );
4100
4496
  lines.push("");
4101
- lines.push(
4102
- 'Atom naming convention: compact prefix-spelling \u2014 `_aic` (align-items:center) not `_items-center`, `_jcsb` (justify-content:space-between) not `_justify-between`, `_wfull` not `_w-full`, `_top0` not `_t0`. The runtime accepts hyphenated Tailwind-style aliases as fallback BUT compact prefix is canonical, shorter, and what every existing scaffold uses. Do not invent your own atom shapes \u2014 if you need an arbitrary value (e.g. `maxWidth: "72rem"`), use the brackets form `_maxw[72rem]`.'
4103
- );
4497
+ lines.push("\u2550\u2550\u2550 STYLING ADOPTION \u2550\u2550\u2550");
4104
4498
  lines.push("");
4105
- lines.push(
4106
- 'Combine atoms with treatment / decorator strings: `className={css("_flex _col _gap4") + " d-card clean-card"}`.'
4107
- );
4499
+ if (ctx.adoptionMode === "contract-only") {
4500
+ lines.push(
4501
+ "This project is contract-only. Use Decantr packs for design intent and governance, but implement through the app runtime and styling system already present or explicitly chosen for this project."
4502
+ );
4503
+ lines.push(
4504
+ "Do not install @decantr/css or add Decantr style files unless the adoption mode changes."
4505
+ );
4506
+ } else if (ctx.adoptionMode === "style-bridge") {
4507
+ lines.push(
4508
+ "This project uses Decantr style-bridge mode. Use generated bridge tokens as a mapping layer onto the selected styling system; @decantr/css is not required."
4509
+ );
4510
+ } else {
4511
+ lines.push(
4512
+ "Use @decantr/css atoms via `css(...)` for layout, spacing, sizing, flex/grid, position, and typography sizing. Static visual values should not live in inline style props."
4513
+ );
4514
+ }
4515
+ if (usesDecantrCss) {
4516
+ lines.push("");
4517
+ lines.push("Use these canonical compact atom shapes:");
4518
+ lines.push(
4519
+ "- Layout: _flex, _col, _aic, _jcc, _jcsb, _grid, _gc3, _gc[2fr_1fr], _gap4, _wrap"
4520
+ );
4521
+ lines.push(
4522
+ "- Spacing/sizing: _p4, _py4, _px6, _wfull, _maxw[40rem], _mxauto, _h[20rem]"
4523
+ );
4524
+ lines.push(
4525
+ "- Position/type: _rel, _abs, _sticky, _top0, _text2xl, _textlg, _fgmuted"
4526
+ );
4527
+ lines.push("- Responsive: _sm:gc2, _lg:gc3, _mdmax:p4, _lg:gc[1.05fr_1fr]");
4528
+ lines.push("");
4529
+ lines.push(
4530
+ "Use compact atom names: `_aic` not `_items-center`, `_jcsb` not `_justify-between`, `_wfull` not `_w-full`, `_top0` not `_t0`. For arbitrary values, use brackets such as `_maxw[72rem]`."
4531
+ );
4532
+ lines.push("");
4533
+ lines.push(
4534
+ 'Combine atoms with treatment / decorator strings: `className={css("_flex _col _gap4") + " d-card clean-card"}`.'
4535
+ );
4536
+ } else {
4537
+ lines.push("");
4538
+ lines.push(
4539
+ "When packs mention atoms, treatments, decorators, or shell class names, treat them as Decantr vocabulary and map the intent into the selected runtime. Keep the literal class names only if this project has a compatible implementation."
4540
+ );
4541
+ }
4108
4542
  lines.push("");
4109
4543
  lines.push("Inline `style={{...}}` is ONLY acceptable for:");
4110
4544
  lines.push(
4111
- ' 1. CSS custom-property writes the contract REQUIRES (`style={{ "--d-stagger-index": i }}`, `style={{ "--lum-card-color": accent }}`, etc.)'
4545
+ " 1. CSS custom-property writes the contract requires (`--d-stagger-index`, theme color vars, etc.)"
4112
4546
  );
4113
4547
  lines.push(
4114
- " 2. Truly dynamic geometry no atom can express (computed pan/zoom transforms, draggable-node positions calculated from data, real-time gradient hue interpolation)."
4548
+ " 2. Truly dynamic geometry no atom can express (computed transforms, drag positions, live chart geometry)."
4115
4549
  );
4116
4550
  lines.push("");
4117
- lines.push(
4118
- 'If you find yourself writing >5 inline styles in a component for static visual values, STOP and migrate to atoms. The full atom reference is in DECANTR.md ("Atoms in 5 minutes" + "Atom Reference"). `decantr check` flags inline-style attributes as a contract violation.'
4119
- );
4551
+ if (usesDecantrCss) {
4552
+ lines.push(
4553
+ "If a component accumulates static inline visual styles, migrate them to atoms, treatments, decorators, or CSS vars. `decantr check` flags inline-style drift."
4554
+ );
4555
+ } else {
4556
+ lines.push(
4557
+ "If a component accumulates static inline visual styles, migrate them into the project styling system or mapped Decantr bridge variables. `decantr check` flags inline-style drift."
4558
+ );
4559
+ }
4120
4560
  lines.push("");
4121
4561
  lines.push("\u2550\u2550\u2550 TREATMENT SURFACE \u2014 USE WHAT EXISTS \u2550\u2550\u2550");
4122
4562
  lines.push("");
4123
- lines.push(
4124
- "60+ treatment classes ship in src/styles/treatments.css. Reach for these BEFORE inventing equivalent CSS. Families:"
4125
- );
4126
- lines.push(
4127
- "- Core surfaces: d-interactive (data-variant + data-size), d-surface, d-data + d-data-row/cell/header, d-control, d-section, d-annotation, d-label"
4128
- );
4129
- lines.push(
4130
- '- Common UI: d-link, d-icon-btn (data-size + data-variant), d-nav-link, d-step-chip (data-step-state="pending|active|done"), d-divider-{top,bottom,left,right}'
4131
- );
4132
- lines.push(
4133
- "- Utility primitives (1.7.21): d-tooltip (data-position), d-empty-state, d-breadcrumb + d-breadcrumb-item (data-current) + d-breadcrumb-separator, d-avatar (data-size), d-icon-well (data-size), d-toggle (data-on), d-toc + d-toc-item (data-current), d-popover"
4134
- );
4135
- lines.push(
4136
- '- Spatial / graph: d-agent-node, d-port (data-side, data-active); d-cta-banner; d-interactive[data-variant="dark"]'
4137
- );
4138
- lines.push(
4139
- '- Shells (14 layouts shipped): d-shell + data-layout="sidebar-main | centered | top-nav-footer | sidebar-aside | top-nav-main | minimal-header | full-bleed | recipefork-top-nav | canvas-overlay | chat-portal | copilot-overlay | terminal-split | three-column-browser | workspace-aside". Child regions: d-shell-sidebar, d-shell-main, d-shell-aside, d-shell-header, d-shell-body, d-shell-footer, d-shell-centered-card, d-shell-list (three-column-browser), d-shell-copilot (copilot-overlay), d-shell-status-bar + d-shell-hotkey-bar (terminal-split), d-shell-overlay (canvas-overlay, with data-corner). d-shell-mobile-trigger + d-shell-mobile-backdrop close the mobile drawer loop.'
4140
- );
4141
- lines.push(
4142
- '- Modal / palette: d-modal (data-align="top"), d-modal-backdrop, d-modal-panel (data-size="sm|lg"), d-palette + d-palette-input/list/row/section, d-kbd, d-hotkey-indicator'
4143
- );
4144
- lines.push("- Composite card: d-card, d-card-header, d-card-body, d-card-footer");
4145
- lines.push(
4146
- "- Motion: d-enter-fade, d-enter-slide-up, d-enter-scale, d-stagger-children, d-pulse, d-pulse-ring, d-shimmer, d-float, d-glow-hover, d-scale-hover, d-lift-hover, d-ripple"
4147
- );
4148
- lines.push(
4149
- "- Typography: d-display, d-headline, d-title, d-subtitle, d-prose, d-body, d-caption, d-eyebrow, d-numeric, d-mono-text"
4150
- );
4151
- lines.push('- Elevation: d-elevate[data-level="0..5"]');
4152
- lines.push(
4153
- "- Data-viz: d-timeline-rail + d-timeline-dot[data-state], d-sparkline + d-sparkline-path/area, d-intent-radar + d-intent-radar-ring/axis, d-waveform, d-qr-placeholder, d-conic-ring (--d-conic-value 0..1), d-heatmap-cell"
4154
- );
4563
+ if (usesDecantrCss) {
4564
+ lines.push(
4565
+ "60+ treatment classes ship in src/styles/treatments.css. Reach for these before inventing CSS:"
4566
+ );
4567
+ lines.push(
4568
+ "- Shells: d-shell + data-layout, d-shell-sidebar/main/aside/header/body/footer, d-shell-mobile-trigger/backdrop"
4569
+ );
4570
+ lines.push(
4571
+ "- Core UI: d-interactive, d-icon-btn, d-nav-link, d-step-chip, d-control, d-card, d-data, d-label, d-annotation"
4572
+ );
4573
+ lines.push(
4574
+ "- Overlays: d-modal, d-modal-backdrop, d-modal-panel, d-palette, d-tooltip, d-popover"
4575
+ );
4576
+ lines.push(
4577
+ "- Motion/type/data-viz: d-enter-*, d-stagger-children, d-pulse, d-lift-hover, d-display/headline/title, d-sparkline, d-conic-ring, d-heatmap-cell"
4578
+ );
4579
+ } else {
4580
+ lines.push(
4581
+ "The treatment names in the packs describe reusable UI roles. Map shells, cards, controls, overlays, motion, typography, and data-viz roles into the project styling system instead of inventing unrelated component language."
4582
+ );
4583
+ }
4155
4584
  lines.push("");
4156
4585
  lines.push(
4157
- "Token scales are tunable via CSS vars: --d-motion-{instant,fast,base,slow,slower}, --d-text-{xs..6xl}, --d-elevation-{1..5}, --d-tracking-*, --d-leading-*, --d-weight-*. Themes override per-blueprint."
4586
+ "Consult DECANTR.md only when you need the full table or exact data-* attributes."
4158
4587
  );
4159
4588
  lines.push("");
4160
4589
  lines.push("\u2550\u2550\u2550 THEME DECORATOR CONTRACT \u2014 APPLY OR THE THEME DOES NOT LAND \u2550\u2550\u2550");
4161
4590
  lines.push("");
4162
4591
  lines.push(
4163
- 'Each theme ships 5-15 namespaced decorator classes (`clean-card`, `lum-glass`, `carbon-canvas`, `paper-card`, etc.). The full Class | Intent | Apply-to contract for the active theme is in scaffold-pack.md under "Required Theme Decorators". Apply them as additive classes alongside d-* treatments \u2014 that is what makes the theme look like the theme rather than "themed colors only."'
4592
+ 'Each theme ships namespaced decorator classes (`clean-card`, `lum-glass`, `carbon-canvas`, `paper-card`, etc.). Apply the scaffold-pack.md "Required Theme Decorators" as additive classes alongside d-* treatments so the theme lands as more than token colors.'
4164
4593
  );
4165
4594
  lines.push(
4166
- 'Section packs ship a one-line pointer to the scaffold-pack table; the canonical contract is in scaffold-pack.md (also mirrored in DECANTR.md "Decorator Quick Reference" with intent + apply-to + key CSS).'
4595
+ "Section packs may point back to the scaffold-pack table; scaffold-pack.md is authoritative."
4167
4596
  );
4168
4597
  lines.push("");
4169
4598
  lines.push("\u2550\u2550\u2550 HARD RULES (NON-NEGOTIABLE) \u2550\u2550\u2550");
4170
4599
  lines.push("");
4600
+ if (usesDecantrCss) {
4601
+ lines.push(
4602
+ '- Auth pages use `d-shell[data-layout="centered"]` with `d-shell-centered-card` around the form.'
4603
+ );
4604
+ lines.push(
4605
+ '- Command palette uses `d-modal[data-align="top"]` + `d-modal-backdrop` + `d-palette`; rows include Lucide icon, label, and d-kbd hotkey.'
4606
+ );
4607
+ } else {
4608
+ lines.push("- Auth pages use a centered shell with a focused centered-card form surface.");
4609
+ lines.push(
4610
+ "- Command palette uses an accessible modal/palette structure; rows include Lucide icon, label, and keyboard hint where the product contract calls for it."
4611
+ );
4612
+ }
4171
4613
  lines.push(
4172
- '- Auth pages MUST wrap content in `<div className="d-shell" data-layout="centered"><div className="d-shell-centered-card">{form}</div></div>`. Skipping d-shell-centered-card produces a full-viewport-width card.'
4614
+ "- Use lucide-react for ALL iconography (already in package.json). Pick semantic icons (Bot, Activity, Database, Search) over generic ones. Do NOT inline SVGs for icons that have Lucide equivalents."
4173
4615
  );
4174
4616
  lines.push(
4175
- '- Command palette MUST wrap `<div className="d-palette">` inside `<div className="d-modal" data-align="top"><div className="d-modal-backdrop" onClick={close} />{palette}</div>`. Without the modal+backdrop wrap, the palette renders as a full-width strip pinned to viewport top. Group commands via d-palette-section eyebrow rows (honor blueprint\'s commands[].section). Each row anatomy: [Lucide icon | label | d-kbd hotkey].'
4617
+ "- Section Directives in section packs are execution rules for layout proportions, treatment stacks, copy conventions, and pattern fitness."
4176
4618
  );
4177
4619
  lines.push(
4178
- "- Use lucide-react for ALL iconography (already in package.json). Pick semantic icons (Bot, Activity, Database, Search) over generic ones. Do NOT inline SVGs for icons that have Lucide equivalents."
4620
+ "- Filter chip rows / tab strips use `d-step-chip[data-step-state]`, not bare `d-interactive` buttons."
4179
4621
  );
4180
4622
  lines.push(
4181
- "- Section Directives (when present in section-*-pack.md) are non-negotiable execution rules. They define layout proportions, treatment stacks (e.g., card-grid uses d-card + d-elevate[1] + d-lift-hover + theme card decorator + d-stagger-children), copy conventions, and pattern-fitness rules per section. Honor them exactly \u2014 they encode product-quality decisions the registry author made for this archetype."
4623
+ "- Do not render Decantr guard prose, implementation notes, keyboard shortcut hints, or treatment/debug labels as product UI unless a route/shell contract explicitly declares that text as user-facing."
4182
4624
  );
4183
4625
  lines.push(
4184
- "- Filter chip rows / tab strips MUST use d-step-chip with data-step-state, NOT bare d-interactive. d-step-chip ships proper active-fill, hover-tint, and chip-row sizing. Bare buttons make filter rows look like generic SaaS form controls."
4626
+ "- Prevent layout collisions: hero content, CTA banners, cards, footers, and sticky chrome must not overlap or clip at desktop or mobile widths."
4185
4627
  );
4186
4628
  lines.push("");
4187
4629
  lines.push("\u2550\u2550\u2550 IMPLEMENTATION RULES \u2550\u2550\u2550");
4188
4630
  lines.push(
4189
- "- Do not invent routes, sections, shells, themes, or features that are not present in the compiled packs."
4190
- );
4191
- lines.push(
4192
- "- Prefer scaffold-pack, section-pack, and page-pack guidance over broader narrative docs when they differ."
4631
+ "- Do not invent routes, sections, shells, themes, or features beyond the compiled packs."
4193
4632
  );
4633
+ lines.push("- Prefer scaffold-pack, section-pack, and page-pack guidance over narrative docs.");
4194
4634
  lines.push(
4195
4635
  "- Start with the shell layouts and route structure first, then build section pages route by route."
4196
4636
  );
4637
+ if (ctx.adoptionMode === "decantr-css") {
4638
+ lines.push("- Import src/styles/global.css, src/styles/tokens.css, and src/styles/treatments.css.");
4639
+ } else if (ctx.adoptionMode === "style-bridge") {
4640
+ lines.push("- Import src/styles/tokens.css and src/styles/decantr-bridge.css where appropriate.");
4641
+ } else {
4642
+ lines.push("- Keep styling imports aligned with the selected runtime; Decantr does not own CSS here.");
4643
+ }
4197
4644
  lines.push(
4198
- "- Import src/styles/global.css, src/styles/tokens.css, and src/styles/treatments.css."
4199
- );
4200
- lines.push(
4201
- "- Use the existing Decantr tokens, treatments, and decorators instead of inventing a new visual system."
4645
+ usesDecantrCss ? "- Use the existing Decantr tokens, treatments, and decorators instead of inventing a new visual system." : "- Map Decantr tokens, treatments, and decorators into the selected runtime instead of inventing an unrelated visual system."
4202
4646
  );
4203
4647
  lines.push(
4204
- "- 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."
4648
+ "- If package.json, app entry files, or router/runtime files are absent, create them for the declared target."
4205
4649
  );
4206
4650
  lines.push(
4207
- "- 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 (computed positions, CSS custom properties like --d-stagger-index, dynamic gradient hues) that cannot be expressed through the contract."
4651
+ usesDecantrCss ? "- Colors, spacing, borders, shadows, gradients, and transitions should come from atoms, treatments, decorators, or CSS variables." : "- Colors, spacing, borders, shadows, gradients, and transitions should come from the project styling system or mapped Decantr variables."
4208
4652
  );
4209
4653
  lines.push(
4210
- "- 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."
4654
+ "- Let shells own spacing, centering, and scroll containers unless the route contract says otherwise."
4211
4655
  );
4212
4656
  lines.push(
4213
- "- 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."
4657
+ "- If command_palette or hotkeys are declared, implement them as real features rather than visible copy."
4214
4658
  );
4215
4659
  lines.push(
4216
4660
  "- 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."
4217
4661
  );
4218
4662
  lines.push(
4219
- "- 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."
4663
+ "- If a required decorator class is missing from generated CSS, report the contract gap instead of inventing a parallel system."
4220
4664
  );
4221
4665
  lines.push(
4222
4666
  "- Do not modify generated context files unless the task is explicitly to regenerate or refresh Decantr context."
@@ -4226,7 +4670,7 @@ function generateGreenfieldPrompt(ctx) {
4226
4670
  lines.push("- Build the shell and shared layout first.");
4227
4671
  lines.push("- Then implement each section's pages using the matching section and page packs.");
4228
4672
  lines.push(
4229
- "- After implementation, run `decantr check` (primary gate \u2014 8 guard rules including the experiential interactions guard). It fails strict-mode builds when declared interactions[] are not implemented; the suggestions point at the canonical implementation. Run `decantr audit` (supplementary \u2014 surfaces inline-style counts, security hygiene findings, accessibility / auth-flow advisories) for deeper diagnostics."
4673
+ "- After implementation, run `decantr check` (primary gate) and `decantr audit` (supplementary diagnostics)."
4230
4674
  );
4231
4675
  lines.push("- Fix all violations until `decantr check` exits 0.");
4232
4676
  lines.push(
@@ -4236,14 +4680,32 @@ function generateGreenfieldPrompt(ctx) {
4236
4680
  }
4237
4681
  function generateBrownfieldPrompt(ctx) {
4238
4682
  const lines = [];
4239
- lines.push("Attach Decantr to this existing application without rebuilding it from scratch.");
4683
+ lines.push(
4684
+ ctx.workflow === "hybrid-compose" ? "Compose the requested Decantr registry contract into this existing application without rebuilding it from scratch." : "Attach Decantr to this existing application without rebuilding it from scratch."
4685
+ );
4686
+ lines.push("");
4687
+ if (ctx.blueprint) lines.push(`Blueprint: ${ctx.blueprint}`);
4688
+ if (ctx.archetype) lines.push(`Primary archetype: ${ctx.archetype}`);
4689
+ if (ctx.theme) lines.push(`Theme: ${ctx.theme}${ctx.mode ? ` (${ctx.mode})` : ""}`);
4690
+ if (ctx.target) lines.push(`Target: ${ctx.target}`);
4691
+ if (ctx.pages.length > 0) {
4692
+ lines.push(
4693
+ `Routes/pages: ${ctx.pages.map((page) => `${page.sectionId ? `${page.sectionId}/` : ""}${page.id}:${page.shell}`).join(", ")}`
4694
+ );
4695
+ }
4240
4696
  lines.push("");
4241
4697
  lines.push(
4242
4698
  "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."
4243
4699
  );
4244
4700
  lines.push("");
4245
- lines.push("Treat .decantr/analysis.json as the factual inventory of the current app.");
4246
- lines.push("Treat .decantr/init-seed.json as the recommended Decantr attach defaults.");
4701
+ if (ctx.analysisArtifacts) {
4702
+ lines.push("Treat .decantr/analysis.json as the factual inventory of the current app.");
4703
+ lines.push("Treat .decantr/init-seed.json as the recommended Decantr attach defaults.");
4704
+ } else {
4705
+ lines.push(
4706
+ "No Decantr analysis seed is present. Start by inventorying the app before changing runtime files."
4707
+ );
4708
+ }
4247
4709
  lines.push(
4248
4710
  "Treat the compiled execution-pack files as the Decantr contract you are layering onto the app."
4249
4711
  );
@@ -4252,20 +4714,34 @@ function generateBrownfieldPrompt(ctx) {
4252
4714
  );
4253
4715
  lines.push("");
4254
4716
  lines.push("Read in this order:");
4255
- lines.push(
4256
- "1. .decantr/analysis.json for the detected framework, routes, styling, layout, and dependencies."
4257
- );
4258
- lines.push("2. .decantr/init-seed.json for the intended attach defaults and workflow lane.");
4259
- lines.push("3. DECANTR.md for guard rules, CSS expectations, and Decantr operating rules.");
4260
- lines.push(
4261
- "4. .decantr/context/scaffold-pack.md for the compact compiled shell, theme, feature, and route contract."
4262
- );
4263
- lines.push(
4264
- "5. .decantr/context/scaffold.md for broader topology, route map, and voice guidance."
4265
- );
4266
- lines.push(
4267
- "6. The matching section and page pack files only when you are working on those specific surfaces."
4268
- );
4717
+ if (ctx.analysisArtifacts) {
4718
+ lines.push(
4719
+ "1. .decantr/analysis.json for the detected framework, routes, styling, layout, and dependencies."
4720
+ );
4721
+ lines.push("2. .decantr/init-seed.json for the intended attach defaults and workflow lane.");
4722
+ lines.push("3. DECANTR.md for guard rules, CSS expectations, and Decantr operating rules.");
4723
+ lines.push(
4724
+ "4. .decantr/context/scaffold-pack.md for the compact compiled shell, theme, feature, and route contract."
4725
+ );
4726
+ lines.push(
4727
+ "5. .decantr/context/scaffold.md for broader topology, route map, and voice guidance."
4728
+ );
4729
+ lines.push(
4730
+ "6. The matching section and page pack files only when you are working on those specific surfaces."
4731
+ );
4732
+ } else {
4733
+ lines.push("1. Inventory existing framework, routes, styling, layout, rule files, and dependencies.");
4734
+ lines.push("2. DECANTR.md for guard rules, adoption mode, and Decantr operating rules.");
4735
+ lines.push(
4736
+ "3. .decantr/context/scaffold-pack.md for the compact compiled shell, theme, feature, and route contract."
4737
+ );
4738
+ lines.push(
4739
+ "4. .decantr/context/scaffold.md for broader topology, route map, and voice guidance."
4740
+ );
4741
+ lines.push(
4742
+ "5. The matching section and page pack files only when you are working on those specific surfaces."
4743
+ );
4744
+ }
4269
4745
  lines.push("");
4270
4746
  lines.push("Implementation rules:");
4271
4747
  lines.push(
@@ -4277,21 +4753,33 @@ function generateBrownfieldPrompt(ctx) {
4277
4753
  lines.push(
4278
4754
  "- If package.json, router files, or style files already exist, extend them deliberately instead of replacing them with a different starter shape."
4279
4755
  );
4756
+ if (ctx.adoptionMode === "decantr-css") {
4757
+ lines.push(
4758
+ "- 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."
4759
+ );
4760
+ lines.push(
4761
+ "- Use the existing Decantr tokens, treatments, and decorators instead of inventing a parallel visual system."
4762
+ );
4763
+ } else if (ctx.adoptionMode === "style-bridge") {
4764
+ lines.push(
4765
+ "- Use Decantr bridge files as a mapping layer onto the existing styling system; do not install @decantr/css unless explicitly requested."
4766
+ );
4767
+ } else {
4768
+ lines.push(
4769
+ "- Keep the existing styling system. Do not add Decantr CSS files or @decantr/css unless the adoption mode changes."
4770
+ );
4771
+ }
4280
4772
  lines.push(
4281
- "- 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."
4282
- );
4283
- lines.push(
4284
- "- Use the existing Decantr tokens, treatments, and decorators instead of inventing a parallel visual system."
4285
- );
4286
- lines.push(
4287
- "- Registry content is optional in this workflow unless the task explicitly asks for blueprint/theme/pattern enrichment."
4773
+ ctx.workflow === "hybrid-compose" ? "- Registry content is part of this task. Layer it onto the current app through existing route/component anchors before creating new runtime structure." : "- Registry content is optional in this workflow unless the task explicitly asks for blueprint/theme/pattern enrichment."
4288
4774
  );
4289
4775
  lines.push(
4290
4776
  "- Do not invent routes, sections, shells, themes, or features that are not present in the compiled packs."
4291
4777
  );
4292
- lines.push(
4293
- "- 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."
4294
- );
4778
+ if (ctx.adoptionMode === "decantr-css") {
4779
+ lines.push(
4780
+ "- 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."
4781
+ );
4782
+ }
4295
4783
  lines.push(
4296
4784
  "- Let shells own spacing, centering, and scroll containers. Preserve app structure, but remove duplicated shell responsibilities when the contract makes them explicit."
4297
4785
  );
@@ -4322,7 +4810,7 @@ function generateBrownfieldPrompt(ctx) {
4322
4810
  return lines.join("\n");
4323
4811
  }
4324
4812
  function generateCuratedPrompt(ctx) {
4325
- return ctx.workflow === "brownfield-attach" ? generateBrownfieldPrompt(ctx) : generateGreenfieldPrompt(ctx);
4813
+ return ctx.workflow === "brownfield-attach" || ctx.workflow === "hybrid-compose" ? generateBrownfieldPrompt(ctx) : generateGreenfieldPrompt(ctx);
4326
4814
  }
4327
4815
  function getAPIClient() {
4328
4816
  return new RegistryAPIClient3({
@@ -4336,7 +4824,7 @@ function getPublicAPIClient() {
4336
4824
  });
4337
4825
  }
4338
4826
  function resolveUserPath(inputPath, cwd = process.cwd()) {
4339
- return isAbsolute(inputPath) ? inputPath : resolve3(cwd, inputPath);
4827
+ return isAbsolute(inputPath) ? inputPath : resolve4(cwd, inputPath);
4340
4828
  }
4341
4829
  function extractHostedAssetPaths(indexHtml) {
4342
4830
  const assetPaths = /* @__PURE__ */ new Set();
@@ -4349,18 +4837,18 @@ function extractHostedAssetPaths(indexHtml) {
4349
4837
  return [...assetPaths];
4350
4838
  }
4351
4839
  function readHostedDistSnapshot(distPath) {
4352
- const resolvedDistPath = distPath ? resolveUserPath(distPath) : join26(process.cwd(), "dist");
4353
- const indexPath = join26(resolvedDistPath, "index.html");
4354
- if (!existsSync25(indexPath)) {
4840
+ const resolvedDistPath = distPath ? resolveUserPath(distPath) : join28(process.cwd(), "dist");
4841
+ const indexPath = join28(resolvedDistPath, "index.html");
4842
+ if (!existsSync27(indexPath)) {
4355
4843
  return void 0;
4356
4844
  }
4357
- const indexHtml = readFileSync18(indexPath, "utf-8");
4845
+ const indexHtml = readFileSync20(indexPath, "utf-8");
4358
4846
  const assetPaths = extractHostedAssetPaths(indexHtml);
4359
4847
  const assets = {};
4360
4848
  for (const assetPath of assetPaths) {
4361
- const assetFilePath = join26(resolvedDistPath, assetPath.replace(/^[/\\]+/, ""));
4362
- if (existsSync25(assetFilePath)) {
4363
- assets[assetPath] = readFileSync18(assetFilePath, "utf-8");
4849
+ const assetFilePath = join28(resolvedDistPath, assetPath.replace(/^[/\\]+/, ""));
4850
+ if (existsSync27(assetFilePath)) {
4851
+ assets[assetPath] = readFileSync20(assetFilePath, "utf-8");
4364
4852
  }
4365
4853
  }
4366
4854
  return {
@@ -4375,7 +4863,7 @@ function isHostedSourceSnapshotFile(path) {
4375
4863
  function readHostedSourceSnapshot(sourcePath) {
4376
4864
  if (!sourcePath) return void 0;
4377
4865
  const resolvedSourcePath = resolveUserPath(sourcePath);
4378
- if (!existsSync25(resolvedSourcePath)) {
4866
+ if (!existsSync27(resolvedSourcePath)) {
4379
4867
  return void 0;
4380
4868
  }
4381
4869
  const files = {};
@@ -4389,17 +4877,17 @@ function readHostedSourceSnapshot(sourcePath) {
4389
4877
  ]);
4390
4878
  const rootPrefix = basename2(resolvedSourcePath);
4391
4879
  const walk = (absoluteDir, relativeDir) => {
4392
- for (const entry of readdirSync6(absoluteDir, { withFileTypes: true })) {
4880
+ for (const entry of readdirSync7(absoluteDir, { withFileTypes: true })) {
4393
4881
  if (ignoredDirNames.has(entry.name)) continue;
4394
- const absolutePath = join26(absoluteDir, entry.name);
4395
- const relativePath = join26(relativeDir, entry.name).replace(/\\/g, "/");
4882
+ const absolutePath = join28(absoluteDir, entry.name);
4883
+ const relativePath = join28(relativeDir, entry.name).replace(/\\/g, "/");
4396
4884
  if (entry.isDirectory()) {
4397
4885
  walk(absolutePath, relativePath);
4398
4886
  continue;
4399
4887
  }
4400
4888
  if (!entry.isFile()) continue;
4401
4889
  if (!isHostedSourceSnapshotFile(relativePath)) continue;
4402
- files[relativePath] = readFileSync18(absolutePath, "utf-8");
4890
+ files[relativePath] = readFileSync20(absolutePath, "utf-8");
4403
4891
  }
4404
4892
  };
4405
4893
  walk(resolvedSourcePath, rootPrefix);
@@ -4550,16 +5038,16 @@ async function printRegistryIntelligenceSummary(namespace, jsonOutput = false) {
4550
5038
  }
4551
5039
  async function printHostedExecutionPackBundle(essencePath, namespace, jsonOutput = false, writeContext = false) {
4552
5040
  const client = getPublicAPIClient();
4553
- const resolvedPath = essencePath ? resolveUserPath(essencePath) : join26(process.cwd(), "decantr.essence.json");
4554
- if (!existsSync25(resolvedPath)) {
5041
+ const resolvedPath = essencePath ? resolveUserPath(essencePath) : join28(process.cwd(), "decantr.essence.json");
5042
+ if (!existsSync27(resolvedPath)) {
4555
5043
  throw new Error(`Essence file not found at ${resolvedPath}`);
4556
5044
  }
4557
- const essence = JSON.parse(readFileSync18(resolvedPath, "utf-8"));
5045
+ const essence = JSON.parse(readFileSync20(resolvedPath, "utf-8"));
4558
5046
  const bundle = await client.compileExecutionPacks(essence, namespace ? { namespace } : void 0);
4559
5047
  let writtenContextPaths = [];
4560
5048
  if (writeContext) {
4561
- const contextDir = join26(process.cwd(), ".decantr", "context");
4562
- mkdirSync10(contextDir, { recursive: true });
5049
+ const contextDir = join28(process.cwd(), ".decantr", "context");
5050
+ mkdirSync11(contextDir, { recursive: true });
4563
5051
  const written = writeExecutionPackBundleArtifacts(
4564
5052
  contextDir,
4565
5053
  bundle
@@ -4584,26 +5072,27 @@ async function printHostedExecutionPackBundle(essencePath, namespace, jsonOutput
4584
5072
  console.log(` Sections: ${typedBundle.sections.length}`);
4585
5073
  console.log(` Mutations: ${typedBundle.mutations.length}`);
4586
5074
  if (writeContext) {
4587
- console.log(` Context bundle: ${join26(process.cwd(), ".decantr", "context")}`);
5075
+ console.log(` Context bundle: ${join28(process.cwd(), ".decantr", "context")}`);
4588
5076
  console.log(` Files written: ${writtenContextPaths.length}`);
4589
5077
  }
4590
5078
  console.log("");
4591
5079
  console.log(`${BOLD6}Route Plan:${RESET13}`);
4592
5080
  for (const route of typedBundle.scaffold.data.routes) {
4593
5081
  const patterns = route.patternIds.length > 0 ? route.patternIds.join(", ") : "none";
4594
- console.log(` ${cyan3(route.path)} -> ${route.pageId} [${patterns}]`);
5082
+ const pageLabel = route.sectionId ? `${route.sectionId}/${route.pageId}` : route.pageId;
5083
+ console.log(` ${cyan3(route.path)} -> ${pageLabel} [${patterns}]`);
4595
5084
  }
4596
5085
  }
4597
5086
  async function printHostedSelectedExecutionPack(packType, id, essencePath, namespace, jsonOutput = false, writeContext = false) {
4598
5087
  const client = getPublicAPIClient();
4599
- const resolvedPath = essencePath ? resolveUserPath(essencePath) : join26(process.cwd(), "decantr.essence.json");
4600
- if (!existsSync25(resolvedPath)) {
5088
+ const resolvedPath = essencePath ? resolveUserPath(essencePath) : join28(process.cwd(), "decantr.essence.json");
5089
+ if (!existsSync27(resolvedPath)) {
4601
5090
  throw new Error(`Essence file not found at ${resolvedPath}`);
4602
5091
  }
4603
5092
  if ((packType === "section" || packType === "page" || packType === "mutation") && !id) {
4604
5093
  throw new Error(`Pack type "${packType}" requires an id.`);
4605
5094
  }
4606
- const essence = JSON.parse(readFileSync18(resolvedPath, "utf-8"));
5095
+ const essence = JSON.parse(readFileSync20(resolvedPath, "utf-8"));
4607
5096
  const selected = await client.selectExecutionPack(
4608
5097
  {
4609
5098
  essence,
@@ -4614,17 +5103,17 @@ async function printHostedSelectedExecutionPack(packType, id, essencePath, names
4614
5103
  );
4615
5104
  let writtenContextDir = null;
4616
5105
  if (writeContext) {
4617
- const contextDir = join26(process.cwd(), ".decantr", "context");
4618
- mkdirSync10(contextDir, { recursive: true });
4619
- writeFileSync13(
4620
- join26(contextDir, "pack-manifest.json"),
5106
+ const contextDir = join28(process.cwd(), ".decantr", "context");
5107
+ mkdirSync11(contextDir, { recursive: true });
5108
+ writeFileSync14(
5109
+ join28(contextDir, "pack-manifest.json"),
4621
5110
  JSON.stringify(selected.manifest, null, 2) + "\n"
4622
5111
  );
4623
5112
  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);
4624
5113
  const markdownFile = manifestEntry?.markdown ?? `${selected.selector.packType}${selected.selector.id ? `-${selected.selector.id}` : ""}-pack.md`;
4625
5114
  const jsonFile = manifestEntry?.json ?? `${selected.selector.packType}${selected.selector.id ? `-${selected.selector.id}` : ""}-pack.json`;
4626
- writeFileSync13(join26(contextDir, markdownFile), selected.pack.renderedMarkdown);
4627
- writeFileSync13(join26(contextDir, jsonFile), JSON.stringify(selected.pack, null, 2) + "\n");
5115
+ writeFileSync14(join28(contextDir, markdownFile), selected.pack.renderedMarkdown);
5116
+ writeFileSync14(join28(contextDir, jsonFile), JSON.stringify(selected.pack, null, 2) + "\n");
4628
5117
  writtenContextDir = contextDir;
4629
5118
  }
4630
5119
  if (jsonOutput) {
@@ -4649,20 +5138,20 @@ async function printHostedSelectedExecutionPack(packType, id, essencePath, names
4649
5138
  }
4650
5139
  async function printHostedExecutionPackManifest(essencePath, namespace, jsonOutput = false, writeContext = false) {
4651
5140
  const client = getPublicAPIClient();
4652
- const resolvedPath = essencePath ? resolveUserPath(essencePath) : join26(process.cwd(), "decantr.essence.json");
4653
- if (!existsSync25(resolvedPath)) {
5141
+ const resolvedPath = essencePath ? resolveUserPath(essencePath) : join28(process.cwd(), "decantr.essence.json");
5142
+ if (!existsSync27(resolvedPath)) {
4654
5143
  throw new Error(`Essence file not found at ${resolvedPath}`);
4655
5144
  }
4656
- const essence = JSON.parse(readFileSync18(resolvedPath, "utf-8"));
5145
+ const essence = JSON.parse(readFileSync20(resolvedPath, "utf-8"));
4657
5146
  const manifest = await client.getExecutionPackManifest(
4658
5147
  essence,
4659
5148
  namespace ? { namespace } : void 0
4660
5149
  );
4661
5150
  let writtenContextDir = null;
4662
5151
  if (writeContext) {
4663
- const contextDir = join26(process.cwd(), ".decantr", "context");
4664
- mkdirSync10(contextDir, { recursive: true });
4665
- writeFileSync13(join26(contextDir, "pack-manifest.json"), JSON.stringify(manifest, null, 2) + "\n");
5152
+ const contextDir = join28(process.cwd(), ".decantr", "context");
5153
+ mkdirSync11(contextDir, { recursive: true });
5154
+ writeFileSync14(join28(contextDir, "pack-manifest.json"), JSON.stringify(manifest, null, 2) + "\n");
4666
5155
  writtenContextDir = contextDir;
4667
5156
  }
4668
5157
  if (jsonOutput) {
@@ -4683,14 +5172,14 @@ async function printHostedExecutionPackManifest(essencePath, namespace, jsonOutp
4683
5172
  }
4684
5173
  }
4685
5174
  async function hydrateHostedExecutionPacksIfMissing(projectRoot, namespace = "@official") {
4686
- const contextDir = join26(projectRoot, ".decantr", "context");
4687
- const reviewPackPath = join26(contextDir, "review-pack.json");
4688
- const manifestPath = join26(contextDir, "pack-manifest.json");
4689
- if (existsSync25(reviewPackPath) && existsSync25(manifestPath)) {
5175
+ const contextDir = join28(projectRoot, ".decantr", "context");
5176
+ const reviewPackPath = join28(contextDir, "review-pack.json");
5177
+ const manifestPath = join28(contextDir, "pack-manifest.json");
5178
+ if (existsSync27(reviewPackPath) && existsSync27(manifestPath)) {
4690
5179
  return { attempted: false, hydrated: false };
4691
5180
  }
4692
- const essencePath = join26(projectRoot, "decantr.essence.json");
4693
- if (!existsSync25(essencePath)) {
5181
+ const essencePath = join28(projectRoot, "decantr.essence.json");
5182
+ if (!existsSync27(essencePath)) {
4694
5183
  return { attempted: false, hydrated: false };
4695
5184
  }
4696
5185
  const reviewHydration = await hydrateHostedReviewPackIfMissing(projectRoot, namespace);
@@ -4699,9 +5188,9 @@ async function hydrateHostedExecutionPacksIfMissing(projectRoot, namespace = "@o
4699
5188
  }
4700
5189
  try {
4701
5190
  const client = getPublicAPIClient();
4702
- const essence = JSON.parse(readFileSync18(essencePath, "utf-8"));
5191
+ const essence = JSON.parse(readFileSync20(essencePath, "utf-8"));
4703
5192
  const bundle = await client.compileExecutionPacks(essence, { namespace });
4704
- mkdirSync10(contextDir, { recursive: true });
5193
+ mkdirSync11(contextDir, { recursive: true });
4705
5194
  writeExecutionPackBundleArtifacts(contextDir, bundle);
4706
5195
  return { attempted: true, hydrated: true, scope: "bundle" };
4707
5196
  } catch {
@@ -4709,19 +5198,19 @@ async function hydrateHostedExecutionPacksIfMissing(projectRoot, namespace = "@o
4709
5198
  }
4710
5199
  }
4711
5200
  async function hydrateHostedReviewPackIfMissing(projectRoot, namespace = "@official") {
4712
- const contextDir = join26(projectRoot, ".decantr", "context");
4713
- const reviewPackPath = join26(contextDir, "review-pack.json");
4714
- const manifestPath = join26(contextDir, "pack-manifest.json");
4715
- if (existsSync25(reviewPackPath) && existsSync25(manifestPath)) {
5201
+ const contextDir = join28(projectRoot, ".decantr", "context");
5202
+ const reviewPackPath = join28(contextDir, "review-pack.json");
5203
+ const manifestPath = join28(contextDir, "pack-manifest.json");
5204
+ if (existsSync27(reviewPackPath) && existsSync27(manifestPath)) {
4716
5205
  return { attempted: false, hydrated: false };
4717
5206
  }
4718
- const essencePath = join26(projectRoot, "decantr.essence.json");
4719
- if (!existsSync25(essencePath)) {
5207
+ const essencePath = join28(projectRoot, "decantr.essence.json");
5208
+ if (!existsSync27(essencePath)) {
4720
5209
  return { attempted: false, hydrated: false };
4721
5210
  }
4722
5211
  try {
4723
5212
  const client = getPublicAPIClient();
4724
- const essence = JSON.parse(readFileSync18(essencePath, "utf-8"));
5213
+ const essence = JSON.parse(readFileSync20(essencePath, "utf-8"));
4725
5214
  const selected = await client.selectExecutionPack(
4726
5215
  {
4727
5216
  essence,
@@ -4729,14 +5218,14 @@ async function hydrateHostedReviewPackIfMissing(projectRoot, namespace = "@offic
4729
5218
  },
4730
5219
  { namespace }
4731
5220
  );
4732
- mkdirSync10(contextDir, { recursive: true });
4733
- writeFileSync13(join26(contextDir, "review-pack.md"), selected.pack.renderedMarkdown);
4734
- writeFileSync13(
4735
- join26(contextDir, "review-pack.json"),
5221
+ mkdirSync11(contextDir, { recursive: true });
5222
+ writeFileSync14(join28(contextDir, "review-pack.md"), selected.pack.renderedMarkdown);
5223
+ writeFileSync14(
5224
+ join28(contextDir, "review-pack.json"),
4736
5225
  JSON.stringify(selected.pack, null, 2) + "\n"
4737
5226
  );
4738
- if (!existsSync25(manifestPath)) {
4739
- writeFileSync13(manifestPath, JSON.stringify(selected.manifest, null, 2) + "\n");
5227
+ if (!existsSync27(manifestPath)) {
5228
+ writeFileSync14(manifestPath, JSON.stringify(selected.manifest, null, 2) + "\n");
4740
5229
  }
4741
5230
  return { attempted: true, hydrated: true, scope: "review" };
4742
5231
  } catch {
@@ -4746,17 +5235,17 @@ async function hydrateHostedReviewPackIfMissing(projectRoot, namespace = "@offic
4746
5235
  async function printHostedFileCritique(sourcePath, namespace, jsonOutput = false, essencePath, treatmentsPath) {
4747
5236
  const client = getPublicAPIClient();
4748
5237
  const resolvedSourcePath = resolveUserPath(sourcePath);
4749
- const resolvedEssencePath = essencePath ? resolveUserPath(essencePath) : join26(process.cwd(), "decantr.essence.json");
4750
- const resolvedTreatmentsPath = treatmentsPath ? resolveUserPath(treatmentsPath) : join26(process.cwd(), "src", "styles", "treatments.css");
4751
- if (!existsSync25(resolvedSourcePath)) {
5238
+ const resolvedEssencePath = essencePath ? resolveUserPath(essencePath) : join28(process.cwd(), "decantr.essence.json");
5239
+ const resolvedTreatmentsPath = treatmentsPath ? resolveUserPath(treatmentsPath) : join28(process.cwd(), "src", "styles", "treatments.css");
5240
+ if (!existsSync27(resolvedSourcePath)) {
4752
5241
  throw new Error(`Source file not found at ${resolvedSourcePath}`);
4753
5242
  }
4754
- if (!existsSync25(resolvedEssencePath)) {
5243
+ if (!existsSync27(resolvedEssencePath)) {
4755
5244
  throw new Error(`Essence file not found at ${resolvedEssencePath}`);
4756
5245
  }
4757
- const code = readFileSync18(resolvedSourcePath, "utf-8");
4758
- const essence = JSON.parse(readFileSync18(resolvedEssencePath, "utf-8"));
4759
- const treatmentsCss = existsSync25(resolvedTreatmentsPath) ? readFileSync18(resolvedTreatmentsPath, "utf-8") : void 0;
5246
+ const code = readFileSync20(resolvedSourcePath, "utf-8");
5247
+ const essence = JSON.parse(readFileSync20(resolvedEssencePath, "utf-8"));
5248
+ const treatmentsCss = existsSync27(resolvedTreatmentsPath) ? readFileSync20(resolvedTreatmentsPath, "utf-8") : void 0;
4760
5249
  const report = await client.critiqueFile(
4761
5250
  {
4762
5251
  essence,
@@ -4780,11 +5269,11 @@ async function printHostedFileCritique(sourcePath, namespace, jsonOutput = false
4780
5269
  }
4781
5270
  async function printHostedProjectAudit(namespace, jsonOutput = false, essencePath, distPath, sourcesPath) {
4782
5271
  const client = getPublicAPIClient();
4783
- const resolvedEssencePath = essencePath ? resolveUserPath(essencePath) : join26(process.cwd(), "decantr.essence.json");
4784
- if (!existsSync25(resolvedEssencePath)) {
5272
+ const resolvedEssencePath = essencePath ? resolveUserPath(essencePath) : join28(process.cwd(), "decantr.essence.json");
5273
+ if (!existsSync27(resolvedEssencePath)) {
4785
5274
  throw new Error(`Essence file not found at ${resolvedEssencePath}`);
4786
5275
  }
4787
- const essence = JSON.parse(readFileSync18(resolvedEssencePath, "utf-8"));
5276
+ const essence = JSON.parse(readFileSync20(resolvedEssencePath, "utf-8"));
4788
5277
  const dist = readHostedDistSnapshot(distPath);
4789
5278
  const sources = readHostedSourceSnapshot(sourcesPath);
4790
5279
  const report = await client.auditProject(
@@ -4802,7 +5291,7 @@ async function printHostedProjectAudit(namespace, jsonOutput = false, essencePat
4802
5291
  console.log(heading2("Hosted Project Audit"));
4803
5292
  console.log(` Essence: ${resolvedEssencePath}`);
4804
5293
  console.log(
4805
- ` Dist snapshot: ${dist ? distPath ? resolveUserPath(distPath) : join26(process.cwd(), "dist") : "none"}`
5294
+ ` Dist snapshot: ${dist ? distPath ? resolveUserPath(distPath) : join28(process.cwd(), "dist") : "none"}`
4806
5295
  );
4807
5296
  console.log(
4808
5297
  ` Source snapshot: ${sources && sourcesPath ? resolveUserPath(sourcesPath) : "none"}`
@@ -4883,25 +5372,25 @@ async function cmdGet(type, id) {
4883
5372
  }
4884
5373
  const apiType = CONTENT_TYPE_TO_API_CONTENT_TYPE3[type];
4885
5374
  const registryClient = new RegistryClient({
4886
- cacheDir: join26(process.cwd(), ".decantr", "cache")
5375
+ cacheDir: join28(process.cwd(), ".decantr", "cache")
4887
5376
  });
4888
5377
  const result = await registryClient.fetchContentItem(apiType, id);
4889
5378
  if (result) {
4890
5379
  console.log(JSON.stringify(result.data, null, 2));
4891
5380
  return;
4892
5381
  }
4893
- const currentDir = dirname2(fileURLToPath2(import.meta.url));
5382
+ const currentDir = dirname4(fileURLToPath2(import.meta.url));
4894
5383
  const bundledCandidates = [
4895
- join26(currentDir, "bundled", apiType, `${id}.json`),
5384
+ join28(currentDir, "bundled", apiType, `${id}.json`),
4896
5385
  // Running from src/
4897
- join26(currentDir, "..", "src", "bundled", apiType, `${id}.json`),
5386
+ join28(currentDir, "..", "src", "bundled", apiType, `${id}.json`),
4898
5387
  // Running from dist/
4899
- join26(currentDir, "..", "bundled", apiType, `${id}.json`)
5388
+ join28(currentDir, "..", "bundled", apiType, `${id}.json`)
4900
5389
  // Alternative dist layout
4901
5390
  ];
4902
- const bundledPath = bundledCandidates.find((p) => existsSync25(p)) || null;
5391
+ const bundledPath = bundledCandidates.find((p) => existsSync27(p)) || null;
4903
5392
  if (bundledPath) {
4904
- const data = JSON.parse(readFileSync18(bundledPath, "utf-8"));
5393
+ const data = JSON.parse(readFileSync20(bundledPath, "utf-8"));
4905
5394
  console.log(JSON.stringify(data, null, 2));
4906
5395
  return;
4907
5396
  }
@@ -4910,10 +5399,10 @@ async function cmdGet(type, id) {
4910
5399
  return;
4911
5400
  }
4912
5401
  async function cmdValidate(path) {
4913
- const essencePath = path || join26(process.cwd(), "decantr.essence.json");
5402
+ const essencePath = path || join28(process.cwd(), "decantr.essence.json");
4914
5403
  let raw;
4915
5404
  try {
4916
- raw = readFileSync18(essencePath, "utf-8");
5405
+ raw = readFileSync20(essencePath, "utf-8");
4917
5406
  } catch {
4918
5407
  console.error(error3(`Could not read ${essencePath}`));
4919
5408
  process.exitCode = 1;
@@ -4982,7 +5471,7 @@ async function cmdList(type, sort, recommended, intelligenceSource) {
4982
5471
  return;
4983
5472
  }
4984
5473
  const registryClient = new RegistryClient({
4985
- cacheDir: join26(process.cwd(), ".decantr", "cache")
5474
+ cacheDir: join28(process.cwd(), ".decantr", "cache")
4986
5475
  });
4987
5476
  const result = await registryClient.fetchContentList(
4988
5477
  type,
@@ -5029,11 +5518,17 @@ async function cmdList(type, sort, recommended, intelligenceSource) {
5029
5518
  }
5030
5519
  }
5031
5520
  async function cmdInit(args) {
5032
- const projectRoot = process.cwd();
5521
+ const workspaceInfo = resolveWorkspaceInfo(process.cwd(), args.project);
5522
+ if (args.yes && workspaceInfo.requiresProjectSelection) {
5523
+ console.log(error3("This looks like a workspace root with multiple app candidates."));
5524
+ console.log(dim3(`Use --project=<path>. Candidates: ${workspaceInfo.appCandidates.join(", ")}`));
5525
+ process.exitCode = 1;
5526
+ return;
5527
+ }
5528
+ const projectRoot = workspaceInfo.appRoot;
5033
5529
  console.log(heading2("Decantr Project Setup"));
5034
5530
  const detected = detectProject(projectRoot);
5035
5531
  const workflowSeed = readBrownfieldInitSeed(projectRoot);
5036
- const brownfieldAttach = Boolean(workflowSeed) || hasExistingProjectFootprint(detected);
5037
5532
  if (workflowSeed) {
5038
5533
  console.log(dim3(" Found .decantr/init-seed.json brownfield guidance."));
5039
5534
  }
@@ -5048,11 +5543,27 @@ async function cmdInit(args) {
5048
5543
  const requestedBlueprint = Boolean(args.blueprint);
5049
5544
  const requestedArchetype = Boolean(args.archetype);
5050
5545
  const requestedTheme = Boolean(args.theme);
5546
+ const policy = resolveWorkflowPolicy({
5547
+ command: "init",
5548
+ detected,
5549
+ workflowSeed,
5550
+ requestedWorkflow: args.workflow,
5551
+ requestedAdoption: args.adoption,
5552
+ requestedAssistantBridge: args["assistant-bridge"],
5553
+ requestedBlueprint,
5554
+ requestedArchetype,
5555
+ requestedTheme,
5556
+ explicitExisting: args.existing,
5557
+ offline: args.offline,
5558
+ projectScope: workspaceInfo.projectScope
5559
+ });
5560
+ const preferContractOnly = policy.contentSource === "none" && (policy.workflowMode === "brownfield-attach" || policy.workflowMode === "greenfield-contract-only");
5561
+ const shouldUseRegistry = !preferContractOnly || policy.registryRequired;
5051
5562
  let offlineSeed = {
5052
5563
  seeded: false,
5053
5564
  strategy: null
5054
5565
  };
5055
- if (args.offline) {
5566
+ if (args.offline && shouldUseRegistry) {
5056
5567
  offlineSeed = seedOfflineRegistry(projectRoot, projectRoot);
5057
5568
  if (offlineSeed.seeded) {
5058
5569
  console.log(dim3(` Seeded offline registry content from ${offlineSeed.strategy}.`));
@@ -5070,11 +5581,12 @@ async function cmdInit(args) {
5070
5581
  }
5071
5582
  }
5072
5583
  const registryClient = new RegistryClient({
5073
- cacheDir: join26(projectRoot, ".decantr", "cache"),
5584
+ cacheDir: join28(projectRoot, ".decantr", "cache"),
5074
5585
  apiUrl: args.registry,
5075
- offline: args.offline
5586
+ offline: args.offline,
5587
+ projectRoot
5076
5588
  });
5077
- const apiAvailable = await registryClient.checkApiAvailability();
5589
+ const apiAvailable = shouldUseRegistry ? await registryClient.checkApiAvailability() : false;
5078
5590
  if (!apiAvailable && !args.offline && (requestedBlueprint || requestedArchetype)) {
5079
5591
  const fallbackSeed = seedOfflineRegistry(projectRoot, projectRoot);
5080
5592
  if (fallbackSeed.seeded) {
@@ -5084,18 +5596,21 @@ async function cmdInit(args) {
5084
5596
  }
5085
5597
  let selectedBlueprint = "default";
5086
5598
  let registrySource = "cache";
5087
- const preferContractOnly = brownfieldAttach && !requestedBlueprint && !requestedArchetype;
5088
- const workflowMode = workflowSeed || brownfieldAttach && !requestedBlueprint && !requestedArchetype ? "brownfield-attach" : "greenfield-scaffold";
5089
5599
  if (args.yes) {
5090
5600
  selectedBlueprint = args.blueprint || "default";
5091
- } else if (!apiAvailable) {
5601
+ } else if (shouldUseRegistry && !apiAvailable) {
5092
5602
  if (!args.blueprint) {
5093
5603
  console.log(`
5094
5604
  ${YELLOW9}You're offline. Scaffolding minimal Decantr project.${RESET13}`);
5095
5605
  console.log(
5096
5606
  dim3("Run `decantr sync` or `decantr upgrade` when online to pull full registry content.\n")
5097
5607
  );
5098
- const result2 = scaffoldMinimal(projectRoot);
5608
+ const result2 = scaffoldMinimal(projectRoot, {
5609
+ workflowMode: policy.workflowMode,
5610
+ adoptionMode: policy.adoptionMode,
5611
+ contentSource: policy.contentSource,
5612
+ assistantBridge: policy.assistantBridge
5613
+ });
5099
5614
  console.log(success3("\nProject scaffolded (minimal/offline)!\n"));
5100
5615
  console.log(" Files created:");
5101
5616
  console.log(` ${cyan3("decantr.essence.json")} Design specification`);
@@ -5111,7 +5626,7 @@ ${YELLOW9}You're offline. Scaffolding minimal Decantr project.${RESET13}`);
5111
5626
  ` 2. Run ${cyan3("decantr refresh")} after syncing to generate scaffold, section, and page packs`
5112
5627
  );
5113
5628
  console.log(
5114
- ` 3. Read ${cyan3("DECANTR.md")} and the generated ${cyan3(".decantr/context/*")} files before implementation`
5629
+ ` 3. Read ${cyan3(".decantr/context/scaffold-pack.md")} first, then use ${cyan3("DECANTR.md")} as a lookup reference`
5115
5630
  );
5116
5631
  console.log(
5117
5632
  ` 4. Use ${cyan3("decantr create <type> <name>")} to create custom content if needed`
@@ -5136,22 +5651,22 @@ ${YELLOW9}You're offline. Scaffolding minimal Decantr project.${RESET13}`);
5136
5651
  ${YELLOW9}You're offline. Scaffolding Decantr default.${RESET13}`);
5137
5652
  console.log(dim3("Run `decantr upgrade` when online, or visit decantr.ai/registry\n"));
5138
5653
  selectedBlueprint = "default";
5139
- } else if (!preferContractOnly) {
5654
+ } else if (shouldUseRegistry) {
5140
5655
  console.log(dim3("Fetching registry content..."));
5141
5656
  const blueprintsResult2 = await registryClient.fetchBlueprints();
5142
5657
  registrySource = blueprintsResult2.source.type === "api" ? "api" : "cache";
5143
5658
  const { selectedBlueprint: selected } = await runSimplifiedInit(blueprintsResult2.data.items);
5144
5659
  selectedBlueprint = selected || "default";
5145
5660
  }
5146
- const archetypesResult = await registryClient.fetchArchetypes();
5147
- const blueprintsResult = await registryClient.fetchBlueprints();
5148
- const themesResult = await registryClient.fetchThemes();
5149
- if (archetypesResult.source.type === "api") {
5661
+ const archetypesResult = shouldUseRegistry ? await registryClient.fetchArchetypes() : null;
5662
+ const blueprintsResult = shouldUseRegistry ? await registryClient.fetchBlueprints() : null;
5663
+ const themesResult = shouldUseRegistry ? await registryClient.fetchThemes() : null;
5664
+ if (archetypesResult?.source.type === "api") {
5150
5665
  registrySource = "api";
5151
5666
  }
5152
- const archetypes = archetypesResult.data.items;
5153
- const blueprints = blueprintsResult.data.items;
5154
- const themes = themesResult.data.items;
5667
+ const archetypes = archetypesResult?.data.items ?? [];
5668
+ const blueprints = blueprintsResult?.data.items ?? [];
5669
+ const themes = themesResult?.data.items ?? [];
5155
5670
  let options;
5156
5671
  const userExplicit = {
5157
5672
  theme: Boolean(args.theme),
@@ -5159,7 +5674,10 @@ ${YELLOW9}You're offline. Scaffolding Decantr default.${RESET13}`);
5159
5674
  shape: Boolean(args.shape),
5160
5675
  personality: Boolean(args.personality)
5161
5676
  };
5162
- if (args.yes || selectedBlueprint !== "default") {
5677
+ if (preferContractOnly) {
5678
+ const flags = parseFlags(args, detected);
5679
+ options = mergeWithDefaults(flags, detected, workflowSeed ?? void 0);
5680
+ } else if (args.yes || selectedBlueprint !== "default") {
5163
5681
  const flags = parseFlags(args, detected);
5164
5682
  flags.blueprint = selectedBlueprint !== "default" ? selectedBlueprint : flags.blueprint;
5165
5683
  options = mergeWithDefaults(flags, detected, workflowSeed ?? void 0);
@@ -5176,14 +5694,22 @@ ${YELLOW9}You're offline. Scaffolding Decantr default.${RESET13}`);
5176
5694
  userExplicit.shape = true;
5177
5695
  userExplicit.personality = true;
5178
5696
  }
5179
- options.workflowMode = workflowMode;
5697
+ options.workflowMode = policy.workflowMode;
5698
+ options.adoptionMode = policy.adoptionMode;
5699
+ options.contentSource = policy.contentSource;
5700
+ options.assistantBridge = policy.assistantBridge;
5701
+ options.projectScope = policy.projectScope;
5702
+ options.workspaceRoot = workspaceInfo.workspaceRoot;
5703
+ options.appRoot = workspaceInfo.appRoot;
5704
+ options.analysisArtifacts = policy.hasAnalysisArtifacts;
5705
+ options.adapterId = resolveBootstrapTarget(options.target).adapterId;
5180
5706
  let topologyMarkdown = "";
5181
5707
  let archetypeData;
5182
5708
  let composedSections;
5183
5709
  let routeMap;
5184
5710
  let patternSpecs;
5185
5711
  let blueprintData;
5186
- if (options.blueprint) {
5712
+ if (shouldUseRegistry && options.blueprint) {
5187
5713
  const blueprintResult = await registryClient.fetchBlueprint(options.blueprint);
5188
5714
  if (blueprintResult) {
5189
5715
  const blueprint = blueprintResult.data;
@@ -5306,7 +5832,7 @@ ${YELLOW9}You're offline. Scaffolding Decantr default.${RESET13}`);
5306
5832
  `${YELLOW9} Warning: Could not fetch blueprint "${options.blueprint}". Using defaults.${RESET13}`
5307
5833
  );
5308
5834
  }
5309
- } else if (options.archetype) {
5835
+ } else if (shouldUseRegistry && options.archetype) {
5310
5836
  const archetypeResult = await registryClient.fetchArchetype(options.archetype);
5311
5837
  if (archetypeResult) {
5312
5838
  archetypeData = mapRegistryArchetypeToArchetypeData(archetypeResult.data);
@@ -5323,7 +5849,7 @@ ${YELLOW9}You're offline. Scaffolding Decantr default.${RESET13}`);
5323
5849
  }
5324
5850
  }
5325
5851
  let themeData;
5326
- if (options.theme) {
5852
+ if (shouldUseRegistry && options.theme) {
5327
5853
  const themeResult = await registryClient.fetchTheme(options.theme);
5328
5854
  if (themeResult) {
5329
5855
  themeData = mapRegistryThemeToThemeData(themeResult.data);
@@ -5355,6 +5881,19 @@ ${YELLOW9}You're offline. Scaffolding Decantr default.${RESET13}`);
5355
5881
  patternSpecs,
5356
5882
  blueprintData
5357
5883
  );
5884
+ let assistantBridgePath = null;
5885
+ let appliedRuleFiles = [];
5886
+ if (policy.assistantBridge === "preview" || policy.assistantBridge === "apply") {
5887
+ assistantBridgePath = writeAssistantBridgePreview({
5888
+ projectRoot,
5889
+ detected,
5890
+ workflowMode: policy.workflowMode,
5891
+ assistantBridge: policy.assistantBridge
5892
+ });
5893
+ }
5894
+ if (policy.assistantBridge === "apply") {
5895
+ appliedRuleFiles = applyAssistantBridge(projectRoot, detected);
5896
+ }
5358
5897
  console.log(success3("\nProject scaffolded!\n"));
5359
5898
  console.log(" Files created:");
5360
5899
  console.log(` ${cyan3("decantr.essence.json")} Design specification`);
@@ -5363,7 +5902,13 @@ ${YELLOW9}You're offline. Scaffolding Decantr default.${RESET13}`);
5363
5902
  if (result.gitignoreUpdated) {
5364
5903
  console.log(` ${dim3(".gitignore updated")}`);
5365
5904
  }
5366
- if (!existsSync25(join26(projectRoot, "package.json"))) {
5905
+ if (assistantBridgePath) {
5906
+ console.log(` ${cyan3(".decantr/context/assistant-bridge.md")} Assistant bridge preview`);
5907
+ }
5908
+ if (appliedRuleFiles.length > 0) {
5909
+ console.log(` ${dim3(`Rule bridge applied: ${appliedRuleFiles.join(", ")}`)}`);
5910
+ }
5911
+ if (!existsSync27(join28(projectRoot, "package.json"))) {
5367
5912
  console.log("");
5368
5913
  console.log(
5369
5914
  dim3(` Note: ${cyan3("decantr init")} created Decantr contract/context files only.`)
@@ -5376,14 +5921,15 @@ ${YELLOW9}You're offline. Scaffolding Decantr default.${RESET13}`);
5376
5921
  }
5377
5922
  console.log("");
5378
5923
  console.log(" Next steps:");
5379
- console.log(" 1. Read DECANTR.md for methodology, CSS approach, and guard rules");
5924
+ console.log(" 1. Read .decantr/context/scaffold-pack.md first as the primary compiled contract");
5380
5925
  console.log(
5381
- " 2. Read .decantr/context/scaffold-pack.md first, then .decantr/context/scaffold.md"
5926
+ " 2. Read .decantr/context/scaffold.md for broader topology, route map, and voice guidance"
5382
5927
  );
5383
5928
  console.log(" 3. Read the matching section and page packs before implementing each route");
5384
- console.log(" 4. Build the shell and route structure first, then implement the pages");
5385
- console.log(" 5. Run decantr check and decantr audit after implementation");
5386
- console.log(" 6. Explore more at decantr.ai/registry");
5929
+ console.log(" 4. Use DECANTR.md as a lookup reference for atoms, treatments, and guard rules");
5930
+ console.log(" 5. Build the shell and route structure first, then implement the pages");
5931
+ console.log(" 6. Run decantr check and decantr audit after implementation");
5932
+ console.log(" 7. Explore more at decantr.ai/registry");
5387
5933
  console.log("");
5388
5934
  console.log(" Commands:");
5389
5935
  console.log(` ${cyan3("decantr status")} Project health`);
@@ -5393,7 +5939,7 @@ ${YELLOW9}You're offline. Scaffolding Decantr default.${RESET13}`);
5393
5939
  console.log(` ${cyan3("decantr upgrade")} Update to latest patterns`);
5394
5940
  console.log(` ${cyan3("decantr check")} Detect drift issues`);
5395
5941
  console.log(` ${cyan3("decantr migrate")} Migrate v2 essence to v3`);
5396
- const essenceContent = readFileSync18(result.essencePath, "utf-8");
5942
+ const essenceContent = readFileSync20(result.essencePath, "utf-8");
5397
5943
  const essence = JSON.parse(essenceContent);
5398
5944
  if (essence.version !== "3.1.0") {
5399
5945
  const validation = validateEssence2(essence);
@@ -5406,11 +5952,12 @@ Validation warnings: ${validation.errors.join(", ")}`));
5406
5952
  let promptPages;
5407
5953
  if (isV36(essence)) {
5408
5954
  const allPages = essence.blueprint.sections ? essence.blueprint.sections.flatMap(
5409
- (s) => s.pages.map((p) => ({ ...p, _shell: s.shell }))
5955
+ (s) => s.pages.map((p) => ({ ...p, _sectionId: s.id, _shell: s.shell }))
5410
5956
  ) : essence.blueprint.pages || [];
5411
5957
  promptPages = allPages.map(
5412
5958
  (p) => ({
5413
5959
  id: p.id,
5960
+ sectionId: p._sectionId,
5414
5961
  shell: p.shell_override ?? p._shell ?? essence.blueprint.shell,
5415
5962
  layout: (p.layout || []).map(
5416
5963
  (item) => typeof item === "string" ? item : extractPatternName(item)
@@ -5421,7 +5968,9 @@ Validation warnings: ${validation.errors.join(", ")}`));
5421
5968
  promptPages = essence.structure || [{ id: "home", shell: options.shell, layout: ["hero"] }];
5422
5969
  }
5423
5970
  const promptCtx = {
5424
- workflow: options.workflowMode === "brownfield-attach" ? "brownfield-attach" : "greenfield-scaffold",
5971
+ workflow: options.workflowMode || "greenfield-scaffold",
5972
+ adoptionMode: options.adoptionMode,
5973
+ analysisArtifacts: options.analysisArtifacts,
5425
5974
  archetype: options.archetype || "custom",
5426
5975
  blueprint: options.blueprint,
5427
5976
  theme: options.theme,
@@ -5447,16 +5996,16 @@ Validation warnings: ${validation.errors.join(", ")}`));
5447
5996
  }
5448
5997
  async function cmdStatus() {
5449
5998
  const projectRoot = process.cwd();
5450
- const essencePath = join26(projectRoot, "decantr.essence.json");
5451
- const projectJsonPath = join26(projectRoot, ".decantr", "project.json");
5999
+ const essencePath = join28(projectRoot, "decantr.essence.json");
6000
+ const projectJsonPath = join28(projectRoot, ".decantr", "project.json");
5452
6001
  console.log(heading2("Decantr Project Status"));
5453
- if (!existsSync25(essencePath)) {
6002
+ if (!existsSync27(essencePath)) {
5454
6003
  console.log(`${RED11}No decantr.essence.json found.${RESET13}`);
5455
6004
  console.log(dim3('Run "decantr init" to create one.'));
5456
6005
  return;
5457
6006
  }
5458
6007
  try {
5459
- const essence = JSON.parse(readFileSync18(essencePath, "utf-8"));
6008
+ const essence = JSON.parse(readFileSync20(essencePath, "utf-8"));
5460
6009
  const validation = validateEssence2(essence);
5461
6010
  const essenceVersion = isV36(essence) ? "v3" : "v2";
5462
6011
  console.log(`${BOLD6}Essence:${RESET13}`);
@@ -5513,9 +6062,9 @@ async function cmdStatus() {
5513
6062
  }
5514
6063
  console.log("");
5515
6064
  console.log(`${BOLD6}Sync Status:${RESET13}`);
5516
- if (existsSync25(projectJsonPath)) {
6065
+ if (existsSync27(projectJsonPath)) {
5517
6066
  try {
5518
- const projectJson = JSON.parse(readFileSync18(projectJsonPath, "utf-8"));
6067
+ const projectJson = JSON.parse(readFileSync20(projectJsonPath, "utf-8"));
5519
6068
  const syncStatus = projectJson.sync?.status || "unknown";
5520
6069
  const lastSync = projectJson.sync?.lastSync || "never";
5521
6070
  const source = projectJson.sync?.registrySource || "unknown";
@@ -5533,7 +6082,7 @@ async function cmdStatus() {
5533
6082
  }
5534
6083
  async function cmdSync() {
5535
6084
  const projectRoot = process.cwd();
5536
- const cacheDir = join26(projectRoot, ".decantr", "cache");
6085
+ const cacheDir = join28(projectRoot, ".decantr", "cache");
5537
6086
  console.log(heading2("Syncing registry content..."));
5538
6087
  const result = await syncRegistry(cacheDir);
5539
6088
  if (result.synced.length > 0) {
@@ -5728,14 +6277,14 @@ ${BOLD6}Examples:${RESET13}
5728
6277
  process.exitCode = 1;
5729
6278
  return;
5730
6279
  }
5731
- const themePath = join26(projectRoot, ".decantr", "custom", "themes", `${name}.json`);
5732
- if (!existsSync25(themePath)) {
6280
+ const themePath = join28(projectRoot, ".decantr", "custom", "themes", `${name}.json`);
6281
+ if (!existsSync27(themePath)) {
5733
6282
  console.error(error3(`Theme "${name}" not found at ${themePath}`));
5734
6283
  process.exitCode = 1;
5735
6284
  return;
5736
6285
  }
5737
6286
  try {
5738
- const theme = JSON.parse(readFileSync18(themePath, "utf-8"));
6287
+ const theme = JSON.parse(readFileSync20(themePath, "utf-8"));
5739
6288
  const result = validateCustomTheme(theme);
5740
6289
  if (result.valid) {
5741
6290
  console.log(success3(`Custom theme "${name}" is valid`));
@@ -5808,7 +6357,7 @@ function cmdHelp() {
5808
6357
  ${BOLD6}decantr${RESET13} \u2014 Design intelligence for AI-generated UI
5809
6358
 
5810
6359
  ${BOLD6}Usage:${RESET13}
5811
- decantr new <name> [--blueprint=X] [--archetype=X] [--theme=X]
6360
+ decantr new <name> [--blueprint=X] [--archetype=X] [--theme=X] [--workflow=greenfield] [--adoption=decantr-css]
5812
6361
  decantr magic <prompt> [--dry-run]
5813
6362
  decantr init [options]
5814
6363
  decantr status
@@ -5827,6 +6376,7 @@ ${BOLD6}Usage:${RESET13}
5827
6376
  decantr registry get-pack <manifest|scaffold|review|section|page|mutation> [id] [--namespace <namespace>] [--json] [--essence <path>] [--write-context]
5828
6377
  decantr registry critique-file <file> [--namespace <namespace>] [--json] [--essence <path>] [--treatments <path>]
5829
6378
  decantr registry audit-project [--namespace <namespace>] [--json] [--essence <path>] [--dist <path>] [--sources <dir>]
6379
+ decantr rules apply [--project=<path>]
5830
6380
  decantr validate [path]
5831
6381
  decantr theme <subcommand>
5832
6382
  decantr create <type> <name>
@@ -5845,6 +6395,10 @@ ${BOLD6}Init Options:${RESET13}
5845
6395
  --guard Guard mode: creative | guided | strict
5846
6396
  --density Spacing: compact | comfortable | spacious
5847
6397
  --shell Default shell layout
6398
+ --workflow Workflow: greenfield | brownfield | hybrid
6399
+ --adoption Adoption: contract-only | style-bridge | decantr-css
6400
+ --assistant-bridge Assistant rules: none | preview | apply
6401
+ --project App path inside a workspace/monorepo
5848
6402
  --existing Initialize in existing project
5849
6403
  --offline Force offline mode
5850
6404
  --yes, -y Accept defaults, skip confirmations
@@ -5874,6 +6428,7 @@ ${BOLD6}Commands:${RESET13}
5874
6428
  ${cyan3("analyze")} Brownfield entrypoint: scan an existing project and emit attach guidance
5875
6429
  ${cyan3("export")} Export design tokens to framework format (shadcn, tailwind, css-vars)
5876
6430
  ${cyan3("registry")} Registry management and intelligence summary
6431
+ ${cyan3("rules")} Preview/apply Decantr assistant bridge blocks to repo rule files
5877
6432
  ${cyan3("upgrade")} Check for content updates from registry
5878
6433
  ${cyan3("help")} Show this help
5879
6434
 
@@ -5881,7 +6436,11 @@ ${BOLD6}Examples:${RESET13}
5881
6436
  decantr new my-app --blueprint=carbon-ai-portal
5882
6437
  decantr magic "AI chatbot with dark cyber theme \u2014 bold and futuristic"
5883
6438
  decantr init
5884
- decantr init --existing --blueprint=saas-dashboard --theme=luminarum --yes
6439
+ decantr init --existing --adoption=contract-only --yes
6440
+ decantr init --existing --adoption=style-bridge --assistant-bridge=preview
6441
+ decantr init --workflow=greenfield --adoption=contract-only
6442
+ decantr init --project=apps/web --yes
6443
+ decantr rules apply
5885
6444
  decantr status
5886
6445
  decantr audit
5887
6446
  decantr audit src/pages/HomePage.tsx
@@ -5904,13 +6463,14 @@ ${BOLD6}Examples:${RESET13}
5904
6463
  decantr create pattern my-card
5905
6464
 
5906
6465
  ${BOLD6}Workflow Model:${RESET13}
5907
- ${cyan3("Greenfield blueprint")} decantr new / decantr magic
5908
- ${cyan3("Brownfield adoption")} decantr analyze -> decantr init --existing
6466
+ ${cyan3("Greenfield blueprint")} decantr new my-app --blueprint=X --workflow=greenfield --adoption=decantr-css
6467
+ ${cyan3("Greenfield contract")} decantr init --workflow=greenfield --adoption=contract-only
6468
+ ${cyan3("Brownfield adoption")} decantr analyze -> decantr init --existing --adoption=contract-only
5909
6469
  ${cyan3("Hybrid composition")} decantr add/remove, decantr theme switch, decantr registry, decantr upgrade
5910
6470
 
5911
6471
  ${BOLD6}Bootstrap adapters:${RESET13}
5912
- Current runnable starter adapter: ${cyan3("react-vite")}
5913
- Other contract targets stay framework-agnostic, but currently initialize in contract-only mode until their starter adapters land.
6472
+ Runnable starter adapters: ${cyan3("react-vite")}, ${cyan3("next-app")}
6473
+ Unsupported targets resolve through ${cyan3("generic-web")} contract-only mode until their starter adapters land.
5914
6474
  `);
5915
6475
  }
5916
6476
  async function main() {
@@ -5922,14 +6482,14 @@ async function main() {
5922
6482
  }
5923
6483
  if (command === "--version" || command === "-v" || command === "version") {
5924
6484
  try {
5925
- const here = dirname2(fileURLToPath2(import.meta.url));
6485
+ const here = dirname4(fileURLToPath2(import.meta.url));
5926
6486
  const candidates = [
5927
- join26(here, "..", "package.json"),
5928
- join26(here, "..", "..", "package.json")
6487
+ join28(here, "..", "package.json"),
6488
+ join28(here, "..", "..", "package.json")
5929
6489
  ];
5930
6490
  for (const candidate of candidates) {
5931
- if (existsSync25(candidate)) {
5932
- const pkg = JSON.parse(readFileSync18(candidate, "utf-8"));
6491
+ if (existsSync27(candidate)) {
6492
+ const pkg = JSON.parse(readFileSync20(candidate, "utf-8"));
5933
6493
  if (pkg.version) {
5934
6494
  console.log(pkg.version);
5935
6495
  return;
@@ -5976,7 +6536,10 @@ async function main() {
5976
6536
  shape: newOpts.shape,
5977
6537
  target: newOpts.target,
5978
6538
  offline: newOpts.offline === true,
5979
- registry: newOpts.registry
6539
+ registry: newOpts.registry,
6540
+ workflow: newOpts.workflow,
6541
+ adoption: newOpts.adoption,
6542
+ assistantBridge: newOpts["assistant-bridge"]
5980
6543
  });
5981
6544
  break;
5982
6545
  }
@@ -6015,7 +6578,7 @@ async function main() {
6015
6578
  break;
6016
6579
  }
6017
6580
  case "upgrade": {
6018
- const { cmdUpgrade } = await import("./upgrade-7LHT3S7E.js");
6581
+ const { cmdUpgrade } = await import("./upgrade-KG42WK5C.js");
6019
6582
  const applyFlag = args.includes("--apply");
6020
6583
  await cmdUpgrade(process.cwd(), { apply: applyFlag });
6021
6584
  break;
@@ -6407,7 +6970,47 @@ async function main() {
6407
6970
  break;
6408
6971
  }
6409
6972
  case "analyze": {
6410
- cmdAnalyze(process.cwd());
6973
+ let projectArg;
6974
+ for (let i = 1; i < args.length; i++) {
6975
+ if (args[i].startsWith("--project=")) {
6976
+ projectArg = args[i].split("=")[1];
6977
+ } else if (args[i] === "--project" && args[i + 1]) {
6978
+ projectArg = args[++i];
6979
+ }
6980
+ }
6981
+ const workspaceInfo = resolveWorkspaceInfo(process.cwd(), projectArg);
6982
+ if (workspaceInfo.requiresProjectSelection) {
6983
+ console.log(error3("This looks like a workspace root with multiple app candidates."));
6984
+ console.log(dim3(`Use --project=<path>. Candidates: ${workspaceInfo.appCandidates.join(", ")}`));
6985
+ process.exitCode = 1;
6986
+ break;
6987
+ }
6988
+ cmdAnalyze(workspaceInfo.appRoot, workspaceInfo);
6989
+ break;
6990
+ }
6991
+ case "rules": {
6992
+ const subcommand = args[1];
6993
+ if (subcommand !== "apply") {
6994
+ console.error(error3("Usage: decantr rules apply [--project=<path>]"));
6995
+ process.exitCode = 1;
6996
+ break;
6997
+ }
6998
+ let projectArg;
6999
+ for (let i = 2; i < args.length; i++) {
7000
+ if (args[i].startsWith("--project=")) {
7001
+ projectArg = args[i].split("=")[1];
7002
+ } else if (args[i] === "--project" && args[i + 1]) {
7003
+ projectArg = args[++i];
7004
+ }
7005
+ }
7006
+ const workspaceInfo = resolveWorkspaceInfo(process.cwd(), projectArg);
7007
+ const detected = detectProject(workspaceInfo.appRoot);
7008
+ const updated = applyAssistantBridge(workspaceInfo.appRoot, detected);
7009
+ if (updated.length === 0) {
7010
+ console.log(dim3("Assistant bridge rule files are already up to date."));
7011
+ } else {
7012
+ console.log(success3(`Applied Decantr assistant bridge to ${updated.join(", ")}.`));
7013
+ }
6411
7014
  break;
6412
7015
  }
6413
7016
  case "magic": {