@decantr/cli 1.7.24 → 1.7.26

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 {
@@ -40,29 +40,63 @@ import {
40
40
  } from "@decantr/verifier";
41
41
 
42
42
  // src/auth.ts
43
- import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
43
+ import {
44
+ chmodSync,
45
+ existsSync,
46
+ mkdirSync,
47
+ readFileSync,
48
+ rmSync,
49
+ statSync,
50
+ writeFileSync
51
+ } from "fs";
44
52
  import { homedir } from "os";
45
53
  import { join } from "path";
46
- var CONFIG_DIR = join(homedir(), ".config", "decantr");
47
- var AUTH_FILE = join(CONFIG_DIR, "auth.json");
54
+ var CONFIG_DIR_MODE = 448;
55
+ var AUTH_FILE_MODE = 384;
56
+ function getConfigDir() {
57
+ return process.env.DECANTR_CONFIG_DIR || join(homedir(), ".config", "decantr");
58
+ }
59
+ function getAuthFile() {
60
+ return join(getConfigDir(), "auth.json");
61
+ }
62
+ function chmodIfNeeded(path, mode) {
63
+ try {
64
+ if ((statSync(path).mode & 511) !== mode) {
65
+ chmodSync(path, mode);
66
+ }
67
+ } catch {
68
+ }
69
+ }
70
+ function ensureConfigDir() {
71
+ const configDir = getConfigDir();
72
+ mkdirSync(configDir, { recursive: true, mode: CONFIG_DIR_MODE });
73
+ chmodIfNeeded(configDir, CONFIG_DIR_MODE);
74
+ }
48
75
  function getCredentials() {
49
- if (!existsSync(AUTH_FILE)) return null;
76
+ const authFile = getAuthFile();
77
+ if (!existsSync(authFile)) return null;
50
78
  try {
51
- return JSON.parse(readFileSync(AUTH_FILE, "utf-8"));
79
+ chmodIfNeeded(authFile, AUTH_FILE_MODE);
80
+ return JSON.parse(readFileSync(authFile, "utf-8"));
52
81
  } catch {
53
82
  return null;
54
83
  }
55
84
  }
56
85
  function saveCredentials(creds) {
57
- mkdirSync(CONFIG_DIR, { recursive: true });
58
- writeFileSync(AUTH_FILE, JSON.stringify(creds, null, 2));
86
+ ensureConfigDir();
87
+ const authFile = getAuthFile();
88
+ writeFileSync(authFile, JSON.stringify(creds, null, 2), { mode: AUTH_FILE_MODE });
89
+ chmodIfNeeded(authFile, AUTH_FILE_MODE);
59
90
  }
60
91
  function clearCredentials() {
61
- if (existsSync(AUTH_FILE)) {
62
- rmSync(AUTH_FILE);
92
+ const authFile = getAuthFile();
93
+ if (existsSync(authFile)) {
94
+ rmSync(authFile);
63
95
  }
64
96
  }
65
97
  function getApiKeyOrToken() {
98
+ const envKey = process.env.DECANTR_API_KEY?.trim();
99
+ if (envKey) return envKey;
66
100
  const creds = getCredentials();
67
101
  if (!creds) return null;
68
102
  return creds.api_key || creds.access_token || null;
@@ -229,13 +263,120 @@ async function cmdAddFeature(feature, args, projectRoot = process.cwd()) {
229
263
  console.log(`${GREEN}Derived files refreshed.${RESET}`);
230
264
  }
231
265
 
266
+ // src/assistant-bridge.ts
267
+ import { appendFileSync, existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
268
+ import { dirname, join as join3 } from "path";
269
+ var START = "<!-- decantr:assistant-bridge:start -->";
270
+ var END = "<!-- decantr:assistant-bridge:end -->";
271
+ function buildAssistantBridgeContent(input) {
272
+ const lines = [];
273
+ lines.push("# Decantr Assistant Bridge");
274
+ lines.push("");
275
+ lines.push(
276
+ "Use this bridge when an AI assistant works in a repository that already has local rules."
277
+ );
278
+ lines.push("");
279
+ lines.push(`- Workflow mode: ${input.workflowMode}`);
280
+ lines.push(`- Detected framework: ${input.detected.framework}`);
281
+ lines.push(`- Package manager: ${input.detected.packageManager}`);
282
+ lines.push(
283
+ `- Existing rule files: ${input.detected.existingRuleFiles.length > 0 ? input.detected.existingRuleFiles.join(", ") : "none detected"}`
284
+ );
285
+ lines.push(`- Bridge mode: ${input.assistantBridge}`);
286
+ lines.push("");
287
+ lines.push("## Suggested Rule Block");
288
+ lines.push("");
289
+ lines.push(START);
290
+ lines.push(
291
+ "Before implementing Decantr-scoped work, read `DECANTR.md`, `decantr.essence.json`, and the compiled packs in `.decantr/context/`."
292
+ );
293
+ lines.push(
294
+ "For brownfield adoption, preserve existing framework, routing, styling, package manager, and build conventions unless the Decantr contract explicitly requires a reviewed change."
295
+ );
296
+ lines.push(
297
+ "Use `.decantr/context/scaffold-pack.md` as the primary compact contract, then read matching section/page packs for the surface being changed."
298
+ );
299
+ lines.push(
300
+ "Do not install `@decantr/css` or rewrite styling unless the project adoption mode says `decantr-css` or the task explicitly asks for it."
301
+ );
302
+ lines.push(END);
303
+ lines.push("");
304
+ return `${lines.join("\n")}
305
+ `;
306
+ }
307
+ function writeAssistantBridgePreview(input) {
308
+ const contextDir = join3(input.projectRoot, ".decantr", "context");
309
+ mkdirSync2(contextDir, { recursive: true });
310
+ const bridgePath = join3(contextDir, "assistant-bridge.md");
311
+ writeFileSync3(bridgePath, buildAssistantBridgeContent(input));
312
+ return bridgePath;
313
+ }
314
+ function bridgeBlock() {
315
+ return [
316
+ START,
317
+ "Before implementing Decantr-scoped work, read `DECANTR.md`, `decantr.essence.json`, and `.decantr/context/scaffold-pack.md` first.",
318
+ "For brownfield adoption, preserve existing framework, routing, styling, package manager, and build conventions unless the Decantr contract explicitly requires a reviewed change.",
319
+ "Do not install `@decantr/css` or rewrite styling unless the project adoption mode says `decantr-css` or the task explicitly asks for it.",
320
+ END,
321
+ ""
322
+ ].join("\n");
323
+ }
324
+ function upsertMarkdownBlock(path) {
325
+ const block = bridgeBlock();
326
+ if (!existsSync3(path)) {
327
+ mkdirSync2(dirname(path), { recursive: true });
328
+ writeFileSync3(path, `# Project Rules
329
+
330
+ ${block}`);
331
+ return true;
332
+ }
333
+ const content = readFileSync3(path, "utf-8");
334
+ if (content.includes(START) && content.includes(END)) return false;
335
+ appendFileSync(path, `
336
+
337
+ ${block}`);
338
+ return true;
339
+ }
340
+ function writeCursorRule(projectRoot) {
341
+ const path = join3(projectRoot, ".cursor", "rules", "decantr.mdc");
342
+ const content = `---
343
+ description: Decantr project contract and brownfield adoption bridge
344
+ alwaysApply: true
345
+ ---
346
+
347
+ ${bridgeBlock()}`;
348
+ if (existsSync3(path) && readFileSync3(path, "utf-8") === content) return false;
349
+ mkdirSync2(dirname(path), { recursive: true });
350
+ writeFileSync3(path, content);
351
+ return true;
352
+ }
353
+ function applyAssistantBridge(projectRoot, detected) {
354
+ const updated = [];
355
+ const markdownTargets = ["CLAUDE.md", "AGENTS.md", "GEMINI.md", "copilot-instructions.md"];
356
+ for (const target of markdownTargets) {
357
+ if (detected.existingRuleFiles.includes(target) && upsertMarkdownBlock(join3(projectRoot, target))) {
358
+ updated.push(target);
359
+ }
360
+ }
361
+ if (detected.existingRuleFiles.includes(".cursorrules")) {
362
+ if (upsertMarkdownBlock(join3(projectRoot, ".cursorrules"))) updated.push(".cursorrules");
363
+ }
364
+ if (detected.existingRuleFiles.includes(".cursor/rules")) {
365
+ if (writeCursorRule(projectRoot)) updated.push(".cursor/rules/decantr.mdc");
366
+ }
367
+ if (updated.length === 0 && detected.existingRuleFiles.length === 0) {
368
+ if (writeCursorRule(projectRoot)) updated.push(".cursor/rules/decantr.mdc");
369
+ }
370
+ return updated;
371
+ }
372
+
232
373
  // src/commands/analyze.ts
233
- import { existsSync as existsSync11, mkdirSync as mkdirSync2, writeFileSync as writeFileSync3 } from "fs";
234
- import { join as join11 } from "path";
374
+ import { existsSync as existsSync12, mkdirSync as mkdirSync3, writeFileSync as writeFileSync4 } from "fs";
375
+ import { join as join12 } from "path";
235
376
 
236
377
  // src/analyzers/components.ts
237
- import { existsSync as existsSync3, readdirSync, statSync } from "fs";
238
- import { join as join3 } from "path";
378
+ import { existsSync as existsSync4, readdirSync, statSync as statSync2 } from "fs";
379
+ import { join as join4 } from "path";
239
380
  var PAGE_EXTENSIONS = /* @__PURE__ */ new Set([".tsx", ".ts", ".jsx", ".js"]);
240
381
  var ROOT_COMPONENT_CANDIDATES = [
241
382
  "src/App.tsx",
@@ -257,9 +398,9 @@ function countFilesRecursive(dir, extensions) {
257
398
  }
258
399
  for (const entry of entries) {
259
400
  if (entry.startsWith(".") || entry === "node_modules") continue;
260
- const fullPath = join3(dir, entry);
401
+ const fullPath = join4(dir, entry);
261
402
  try {
262
- const stat = statSync(fullPath);
403
+ const stat = statSync2(fullPath);
263
404
  if (stat.isDirectory()) {
264
405
  count += countFilesRecursive(fullPath, extensions);
265
406
  } else if (stat.isFile()) {
@@ -283,9 +424,9 @@ function countPageFiles(dir) {
283
424
  }
284
425
  for (const entry of entries) {
285
426
  if (entry.startsWith(".") || entry === "node_modules") continue;
286
- const fullPath = join3(dir, entry);
427
+ const fullPath = join4(dir, entry);
287
428
  try {
288
- const stat = statSync(fullPath);
429
+ const stat = statSync2(fullPath);
289
430
  if (stat.isDirectory()) {
290
431
  count += countPageFiles(fullPath);
291
432
  } else if (stat.isFile()) {
@@ -301,27 +442,27 @@ function countPageFiles(dir) {
301
442
  function scanComponents(projectRoot) {
302
443
  let pageCount = 0;
303
444
  const componentDirs = [];
304
- const appDirs = [join3(projectRoot, "src", "app"), join3(projectRoot, "app")];
445
+ const appDirs = [join4(projectRoot, "src", "app"), join4(projectRoot, "app")];
305
446
  for (const dir of appDirs) {
306
- if (existsSync3(dir)) {
447
+ if (existsSync4(dir)) {
307
448
  pageCount += countPageFiles(dir);
308
449
  }
309
450
  }
310
- const pagesDirs = [join3(projectRoot, "src", "pages"), join3(projectRoot, "pages")];
451
+ const pagesDirs = [join4(projectRoot, "src", "pages"), join4(projectRoot, "pages")];
311
452
  for (const dir of pagesDirs) {
312
- if (existsSync3(dir)) {
453
+ if (existsSync4(dir)) {
313
454
  pageCount += countFilesRecursive(dir, PAGE_EXTENSIONS);
314
455
  }
315
456
  }
316
457
  let componentCount = 0;
317
458
  const componentPaths = [
318
- join3(projectRoot, "src", "components"),
319
- join3(projectRoot, "components"),
320
- join3(projectRoot, "src", "ui"),
321
- join3(projectRoot, "ui")
459
+ join4(projectRoot, "src", "components"),
460
+ join4(projectRoot, "components"),
461
+ join4(projectRoot, "src", "ui"),
462
+ join4(projectRoot, "ui")
322
463
  ];
323
464
  for (const dir of componentPaths) {
324
- if (existsSync3(dir)) {
465
+ if (existsSync4(dir)) {
325
466
  const count = countFilesRecursive(dir, PAGE_EXTENSIONS);
326
467
  if (count > 0) {
327
468
  componentCount += count;
@@ -331,7 +472,7 @@ function scanComponents(projectRoot) {
331
472
  }
332
473
  }
333
474
  const hasRootAppComponent = ROOT_COMPONENT_CANDIDATES.some(
334
- (relativePath) => existsSync3(join3(projectRoot, relativePath))
475
+ (relativePath) => existsSync4(join4(projectRoot, relativePath))
335
476
  );
336
477
  if (pageCount === 0 && hasRootAppComponent) {
337
478
  pageCount = 1;
@@ -350,8 +491,8 @@ function scanComponents(projectRoot) {
350
491
  }
351
492
 
352
493
  // src/analyzers/dependencies.ts
353
- import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
354
- import { join as join4 } from "path";
494
+ import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
495
+ import { join as join5 } from "path";
355
496
  var CATEGORIES = {
356
497
  ui: [
357
498
  "react",
@@ -501,11 +642,11 @@ function scanDependencies(projectRoot) {
501
642
  styling: [],
502
643
  other: []
503
644
  };
504
- const pkgPath = join4(projectRoot, "package.json");
505
- if (!existsSync4(pkgPath)) return result;
645
+ const pkgPath = join5(projectRoot, "package.json");
646
+ if (!existsSync5(pkgPath)) return result;
506
647
  let pkg;
507
648
  try {
508
- pkg = JSON.parse(readFileSync3(pkgPath, "utf-8"));
649
+ pkg = JSON.parse(readFileSync4(pkgPath, "utf-8"));
509
650
  } catch {
510
651
  return result;
511
652
  }
@@ -525,8 +666,8 @@ function scanDependencies(projectRoot) {
525
666
  }
526
667
 
527
668
  // src/analyzers/features.ts
528
- import { existsSync as existsSync5, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
529
- import { join as join5 } from "path";
669
+ import { existsSync as existsSync6, readdirSync as readdirSync2, statSync as statSync3 } from "fs";
670
+ import { join as join6 } from "path";
530
671
  var FEATURE_PATTERNS = {
531
672
  auth: [
532
673
  "login",
@@ -579,11 +720,11 @@ function collectPaths(dir, baseDir, depth = 0) {
579
720
  for (const entry of entries) {
580
721
  if (entry.startsWith(".") || entry.startsWith("_") || entry === "node_modules" || entry === "api")
581
722
  continue;
582
- const fullPath = join5(dir, entry);
723
+ const fullPath = join6(dir, entry);
583
724
  const relPath = fullPath.slice(baseDir.length + 1);
584
725
  paths.push(relPath);
585
726
  try {
586
- if (statSync2(fullPath).isDirectory()) {
727
+ if (statSync3(fullPath).isDirectory()) {
587
728
  paths.push(...collectPaths(fullPath, baseDir, depth + 1));
588
729
  }
589
730
  } catch {
@@ -595,16 +736,16 @@ function scanFeatures(projectRoot) {
595
736
  const detected = [];
596
737
  const evidence = {};
597
738
  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")
739
+ join6(projectRoot, "src", "app"),
740
+ join6(projectRoot, "app"),
741
+ join6(projectRoot, "src", "pages"),
742
+ join6(projectRoot, "pages"),
743
+ join6(projectRoot, "src", "components"),
744
+ join6(projectRoot, "components")
604
745
  ];
605
746
  const allPaths = [];
606
747
  for (const dir of scanDirs) {
607
- if (existsSync5(dir)) {
748
+ if (existsSync6(dir)) {
608
749
  allPaths.push(...collectPaths(dir, projectRoot));
609
750
  }
610
751
  }
@@ -628,8 +769,8 @@ function scanFeatures(projectRoot) {
628
769
  }
629
770
 
630
771
  // src/analyzers/layout.ts
631
- import { existsSync as existsSync6, readdirSync as readdirSync3, readFileSync as readFileSync4 } from "fs";
632
- import { join as join6 } from "path";
772
+ import { existsSync as existsSync7, readdirSync as readdirSync3, readFileSync as readFileSync5 } from "fs";
773
+ import { join as join7 } from "path";
633
774
  var SIDEBAR_PATTERNS = ["sidebar", "side-bar", "sidenav", "side-nav", "drawer", "aside"];
634
775
  var NAV_PATTERNS = ["nav", "navbar", "header", "top-bar", "topbar", "app-bar", "appbar"];
635
776
  var FOOTER_PATTERNS = ["footer", "bottom-bar", "bottombar"];
@@ -640,12 +781,12 @@ function containsPattern(text, patterns) {
640
781
  function checkComponentDirs(projectRoot) {
641
782
  const result = { sidebar: false, nav: false, footer: false };
642
783
  const componentDirs = [
643
- join6(projectRoot, "src", "components"),
644
- join6(projectRoot, "components"),
645
- join6(projectRoot, "src", "ui")
784
+ join7(projectRoot, "src", "components"),
785
+ join7(projectRoot, "components"),
786
+ join7(projectRoot, "src", "ui")
646
787
  ];
647
788
  for (const dir of componentDirs) {
648
- if (!existsSync6(dir)) continue;
789
+ if (!existsSync7(dir)) continue;
649
790
  let entries;
650
791
  try {
651
792
  entries = readdirSync3(dir);
@@ -664,16 +805,16 @@ function checkComponentDirs(projectRoot) {
664
805
  function checkLayoutFiles(projectRoot) {
665
806
  const result = { sidebar: false, nav: false, footer: false };
666
807
  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")
808
+ join7(projectRoot, "src", "app", "layout.tsx"),
809
+ join7(projectRoot, "src", "app", "layout.jsx"),
810
+ join7(projectRoot, "app", "layout.tsx"),
811
+ join7(projectRoot, "app", "layout.jsx")
671
812
  ];
672
813
  for (const layoutPath of layoutPaths) {
673
- if (!existsSync6(layoutPath)) continue;
814
+ if (!existsSync7(layoutPath)) continue;
674
815
  let content;
675
816
  try {
676
- content = readFileSync4(layoutPath, "utf-8");
817
+ content = readFileSync5(layoutPath, "utf-8");
677
818
  } catch {
678
819
  continue;
679
820
  }
@@ -684,16 +825,16 @@ function checkLayoutFiles(projectRoot) {
684
825
  const subLayoutDirs = ["dashboard", "admin", "app"];
685
826
  for (const sub of subLayoutDirs) {
686
827
  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")
828
+ join7(projectRoot, "src", "app", sub, "layout.tsx"),
829
+ join7(projectRoot, "src", "app", sub, "layout.jsx"),
830
+ join7(projectRoot, "app", sub, "layout.tsx"),
831
+ join7(projectRoot, "app", sub, "layout.jsx")
691
832
  ];
692
833
  for (const layoutPath of paths) {
693
- if (!existsSync6(layoutPath)) continue;
834
+ if (!existsSync7(layoutPath)) continue;
694
835
  let content;
695
836
  try {
696
- content = readFileSync4(layoutPath, "utf-8");
837
+ content = readFileSync5(layoutPath, "utf-8");
697
838
  } catch {
698
839
  continue;
699
840
  }
@@ -715,10 +856,10 @@ function inferShellPattern(hasSidebar, hasTopNav, hasFooter) {
715
856
  return "main-only";
716
857
  }
717
858
  function inferShellPatternFromDecantrContract(projectRoot) {
718
- const essencePath = join6(projectRoot, "decantr.essence.json");
719
- if (!existsSync6(essencePath)) return null;
859
+ const essencePath = join7(projectRoot, "decantr.essence.json");
860
+ if (!existsSync7(essencePath)) return null;
720
861
  try {
721
- const essence = JSON.parse(readFileSync4(essencePath, "utf-8"));
862
+ const essence = JSON.parse(readFileSync5(essencePath, "utf-8"));
722
863
  const sectionShells = essence.blueprint?.sections?.map((section) => section.shell).filter((shell) => typeof shell === "string" && shell.length > 0) ?? [];
723
864
  if (sectionShells.length === 0 && essence.blueprint?.shell) {
724
865
  return `${essence.blueprint.shell} (contract)`;
@@ -754,8 +895,8 @@ function scanLayout(projectRoot) {
754
895
  }
755
896
 
756
897
  // 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";
898
+ import { existsSync as existsSync8, readdirSync as readdirSync4, readFileSync as readFileSync6, statSync as statSync4 } from "fs";
899
+ import { join as join8, relative } from "path";
759
900
  var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".next", ".git", "api", "_app", "_document"]);
760
901
  function shouldSkipDir(name) {
761
902
  return name.startsWith("_") || name.startsWith(".") || SKIP_DIRS.has(name);
@@ -795,15 +936,15 @@ function walkAppDir(dir, baseDir, segments) {
795
936
  const pageFile = entries.find((e) => e.startsWith("page."));
796
937
  routes.push({
797
938
  path: routePath || "/",
798
- file: relative(baseDir, join7(dir, pageFile)),
939
+ file: relative(baseDir, join8(dir, pageFile)),
799
940
  hasLayout
800
941
  });
801
942
  }
802
943
  for (const entry of entries) {
803
944
  if (shouldSkipDir(entry)) continue;
804
- const fullPath = join7(dir, entry);
945
+ const fullPath = join8(dir, entry);
805
946
  try {
806
- if (!statSync3(fullPath).isDirectory()) continue;
947
+ if (!statSync4(fullPath).isDirectory()) continue;
807
948
  } catch {
808
949
  continue;
809
950
  }
@@ -823,9 +964,9 @@ function walkPagesDir(dir, baseDir, segments) {
823
964
  }
824
965
  for (const entry of entries) {
825
966
  if (shouldSkipDir(entry)) continue;
826
- const fullPath = join7(dir, entry);
967
+ const fullPath = join8(dir, entry);
827
968
  try {
828
- const stat = statSync3(fullPath);
969
+ const stat = statSync4(fullPath);
829
970
  if (stat.isDirectory()) {
830
971
  const routeSegment = segmentToRoute(entry);
831
972
  const nextSegments = routeSegment === null ? [...segments] : [...segments, routeSegment];
@@ -859,9 +1000,9 @@ function collectRouteCandidateFiles(dir, files, depth = 0) {
859
1000
  }
860
1001
  for (const entry of entries) {
861
1002
  if (entry.startsWith(".") || entry === "node_modules") continue;
862
- const fullPath = join7(dir, entry);
1003
+ const fullPath = join8(dir, entry);
863
1004
  try {
864
- const stat = statSync3(fullPath);
1005
+ const stat = statSync4(fullPath);
865
1006
  if (stat.isDirectory()) {
866
1007
  collectRouteCandidateFiles(fullPath, files, depth + 1);
867
1008
  } else if (stat.isFile()) {
@@ -875,16 +1016,16 @@ function collectRouteCandidateFiles(dir, files, depth = 0) {
875
1016
  }
876
1017
  }
877
1018
  function scanReactRouter(projectRoot) {
878
- const candidateDirs = [join7(projectRoot, "src"), projectRoot];
1019
+ const candidateDirs = [join8(projectRoot, "src"), projectRoot];
879
1020
  const candidateFiles = [];
880
1021
  for (const dir of candidateDirs) {
881
- if (existsSync7(dir)) collectRouteCandidateFiles(dir, candidateFiles);
1022
+ if (existsSync8(dir)) collectRouteCandidateFiles(dir, candidateFiles);
882
1023
  }
883
1024
  const routeMap = /* @__PURE__ */ new Map();
884
1025
  for (const absolutePath of candidateFiles) {
885
1026
  let content;
886
1027
  try {
887
- content = readFileSync5(absolutePath, "utf-8");
1028
+ content = readFileSync6(absolutePath, "utf-8");
888
1029
  } catch {
889
1030
  continue;
890
1031
  }
@@ -914,18 +1055,18 @@ function scanReactRouter(projectRoot) {
914
1055
  return [...routeMap.values()];
915
1056
  }
916
1057
  function scanRoutes(projectRoot) {
917
- const appDirs = [join7(projectRoot, "src", "app"), join7(projectRoot, "app")];
1058
+ const appDirs = [join8(projectRoot, "src", "app"), join8(projectRoot, "app")];
918
1059
  for (const appDir of appDirs) {
919
- if (existsSync7(appDir)) {
1060
+ if (existsSync8(appDir)) {
920
1061
  const routes = walkAppDir(appDir, projectRoot, []);
921
1062
  if (routes.length > 0) {
922
1063
  return { strategy: "app-router", routes };
923
1064
  }
924
1065
  }
925
1066
  }
926
- const pagesDirs = [join7(projectRoot, "src", "pages"), join7(projectRoot, "pages")];
1067
+ const pagesDirs = [join8(projectRoot, "src", "pages"), join8(projectRoot, "pages")];
927
1068
  for (const pagesDir of pagesDirs) {
928
- if (existsSync7(pagesDir)) {
1069
+ if (existsSync8(pagesDir)) {
929
1070
  const routes = walkPagesDir(pagesDir, projectRoot, []);
930
1071
  if (routes.length > 0) {
931
1072
  return { strategy: "pages-router", routes };
@@ -940,8 +1081,8 @@ function scanRoutes(projectRoot) {
940
1081
  }
941
1082
 
942
1083
  // src/analyzers/styling.ts
943
- import { existsSync as existsSync8, readFileSync as readFileSync6 } from "fs";
944
- import { join as join8 } from "path";
1084
+ import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
1085
+ import { join as join9 } from "path";
945
1086
  var TAILWIND_CONFIGS = [
946
1087
  "tailwind.config.js",
947
1088
  "tailwind.config.ts",
@@ -1003,10 +1144,10 @@ function detectDarkMode(projectRoot, cssContents) {
1003
1144
  "app/layout.jsx"
1004
1145
  ];
1005
1146
  for (const rel of layoutPaths) {
1006
- const fullPath = join8(projectRoot, rel);
1007
- if (existsSync8(fullPath)) {
1147
+ const fullPath = join9(projectRoot, rel);
1148
+ if (existsSync9(fullPath)) {
1008
1149
  try {
1009
- const layoutContent = readFileSync6(fullPath, "utf-8");
1150
+ const layoutContent = readFileSync7(fullPath, "utf-8");
1010
1151
  if (layoutContent.includes('className="dark"') || layoutContent.includes("className='dark'") || layoutContent.includes('class="dark"')) {
1011
1152
  return true;
1012
1153
  }
@@ -1014,10 +1155,10 @@ function detectDarkMode(projectRoot, cssContents) {
1014
1155
  }
1015
1156
  }
1016
1157
  }
1017
- const pkgPath = join8(projectRoot, "package.json");
1018
- if (existsSync8(pkgPath)) {
1158
+ const pkgPath = join9(projectRoot, "package.json");
1159
+ if (existsSync9(pkgPath)) {
1019
1160
  try {
1020
- const pkg = JSON.parse(readFileSync6(pkgPath, "utf-8"));
1161
+ const pkg = JSON.parse(readFileSync7(pkgPath, "utf-8"));
1021
1162
  const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
1022
1163
  if (allDeps["next-themes"] || allDeps["theme-toggle"] || allDeps["use-dark-mode"]) {
1023
1164
  return true;
@@ -1025,10 +1166,10 @@ function detectDarkMode(projectRoot, cssContents) {
1025
1166
  } catch {
1026
1167
  }
1027
1168
  }
1028
- const essencePath = join8(projectRoot, "decantr.essence.json");
1029
- if (existsSync8(essencePath)) {
1169
+ const essencePath = join9(projectRoot, "decantr.essence.json");
1170
+ if (existsSync9(essencePath)) {
1030
1171
  try {
1031
- const essence = JSON.parse(readFileSync6(essencePath, "utf-8"));
1172
+ const essence = JSON.parse(readFileSync7(essencePath, "utf-8"));
1032
1173
  const mode = essence.dna?.theme?.mode;
1033
1174
  if (mode === "dark" || mode === "auto") {
1034
1175
  return true;
@@ -1042,17 +1183,17 @@ function scanStyling(projectRoot) {
1042
1183
  let approach = "unknown";
1043
1184
  let configFile;
1044
1185
  for (const cfg of TAILWIND_CONFIGS) {
1045
- if (existsSync8(join8(projectRoot, cfg))) {
1186
+ if (existsSync9(join9(projectRoot, cfg))) {
1046
1187
  approach = "tailwind";
1047
1188
  configFile = cfg;
1048
1189
  break;
1049
1190
  }
1050
1191
  }
1051
1192
  if (approach === "unknown") {
1052
- const pkgPath = join8(projectRoot, "package.json");
1053
- if (existsSync8(pkgPath)) {
1193
+ const pkgPath = join9(projectRoot, "package.json");
1194
+ if (existsSync9(pkgPath)) {
1054
1195
  try {
1055
- const pkg = JSON.parse(readFileSync6(pkgPath, "utf-8"));
1196
+ const pkg = JSON.parse(readFileSync7(pkgPath, "utf-8"));
1056
1197
  const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
1057
1198
  if (allDeps["@decantr/css"]) {
1058
1199
  approach = "decantr-css";
@@ -1065,27 +1206,27 @@ function scanStyling(projectRoot) {
1065
1206
  }
1066
1207
  }
1067
1208
  }
1068
- const decantrStyleFiles = DECANTR_STYLE_PATHS.filter((rel) => existsSync8(join8(projectRoot, rel)));
1209
+ const decantrStyleFiles = DECANTR_STYLE_PATHS.filter((rel) => existsSync9(join9(projectRoot, rel)));
1069
1210
  if (decantrStyleFiles.length >= 2) {
1070
1211
  approach = "decantr-css";
1071
1212
  configFile = decantrStyleFiles.join(" + ");
1072
1213
  }
1073
1214
  const cssContents = [];
1074
1215
  for (const rel of GLOBALS_CSS_PATHS) {
1075
- const fullPath = join8(projectRoot, rel);
1076
- if (existsSync8(fullPath)) {
1216
+ const fullPath = join9(projectRoot, rel);
1217
+ if (existsSync9(fullPath)) {
1077
1218
  try {
1078
- cssContents.push(readFileSync6(fullPath, "utf-8"));
1219
+ cssContents.push(readFileSync7(fullPath, "utf-8"));
1079
1220
  } catch {
1080
1221
  }
1081
1222
  }
1082
1223
  }
1083
1224
  for (const rel of DECANTR_STYLE_PATHS) {
1084
1225
  if (GLOBALS_CSS_PATHS.includes(rel)) continue;
1085
- const fullPath = join8(projectRoot, rel);
1086
- if (existsSync8(fullPath)) {
1226
+ const fullPath = join9(projectRoot, rel);
1227
+ if (existsSync9(fullPath)) {
1087
1228
  try {
1088
- cssContents.push(readFileSync6(fullPath, "utf-8"));
1229
+ cssContents.push(readFileSync7(fullPath, "utf-8"));
1089
1230
  } catch {
1090
1231
  }
1091
1232
  }
@@ -1101,7 +1242,7 @@ function scanStyling(projectRoot) {
1101
1242
  const darkMode = detectDarkMode(projectRoot, cssContents);
1102
1243
  if (approach === "unknown" && cssContents.length > 0) {
1103
1244
  approach = "css";
1104
- configFile = GLOBALS_CSS_PATHS.find((rel) => existsSync8(join8(projectRoot, rel)));
1245
+ configFile = GLOBALS_CSS_PATHS.find((rel) => existsSync9(join9(projectRoot, rel)));
1105
1246
  }
1106
1247
  return {
1107
1248
  approach,
@@ -1113,8 +1254,8 @@ function scanStyling(projectRoot) {
1113
1254
  }
1114
1255
 
1115
1256
  // src/detect.ts
1116
- import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
1117
- import { join as join9 } from "path";
1257
+ import { existsSync as existsSync10, readFileSync as readFileSync8 } from "fs";
1258
+ import { join as join10 } from "path";
1118
1259
  var RULE_FILES = [
1119
1260
  "CLAUDE.md",
1120
1261
  ".cursorrules",
@@ -1133,52 +1274,52 @@ function detectProject(projectRoot = process.cwd()) {
1133
1274
  existingEssence: false,
1134
1275
  projectRoot
1135
1276
  };
1136
- result.existingEssence = existsSync9(join9(projectRoot, "decantr.essence.json"));
1277
+ result.existingEssence = existsSync10(join10(projectRoot, "decantr.essence.json"));
1137
1278
  for (const ruleFile of RULE_FILES) {
1138
- if (existsSync9(join9(projectRoot, ruleFile))) {
1279
+ if (existsSync10(join10(projectRoot, ruleFile))) {
1139
1280
  result.existingRuleFiles.push(ruleFile);
1140
1281
  }
1141
1282
  }
1142
- if (existsSync9(join9(projectRoot, "pnpm-lock.yaml"))) {
1283
+ if (existsSync10(join10(projectRoot, "pnpm-lock.yaml"))) {
1143
1284
  result.packageManager = "pnpm";
1144
- } else if (existsSync9(join9(projectRoot, "yarn.lock"))) {
1285
+ } else if (existsSync10(join10(projectRoot, "yarn.lock"))) {
1145
1286
  result.packageManager = "yarn";
1146
- } else if (existsSync9(join9(projectRoot, "bun.lockb"))) {
1287
+ } else if (existsSync10(join10(projectRoot, "bun.lockb"))) {
1147
1288
  result.packageManager = "bun";
1148
- } else if (existsSync9(join9(projectRoot, "package-lock.json"))) {
1289
+ } else if (existsSync10(join10(projectRoot, "package-lock.json"))) {
1149
1290
  result.packageManager = "npm";
1150
1291
  }
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"))) {
1292
+ result.hasTypeScript = existsSync10(join10(projectRoot, "tsconfig.json"));
1293
+ 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"));
1294
+ if (existsSync10(join10(projectRoot, "next.config.js")) || existsSync10(join10(projectRoot, "next.config.ts")) || existsSync10(join10(projectRoot, "next.config.mjs"))) {
1154
1295
  result.framework = "nextjs";
1155
1296
  result.version = getPackageVersion(projectRoot, "next");
1156
1297
  return result;
1157
1298
  }
1158
- if (existsSync9(join9(projectRoot, "nuxt.config.js")) || existsSync9(join9(projectRoot, "nuxt.config.ts"))) {
1299
+ if (existsSync10(join10(projectRoot, "nuxt.config.js")) || existsSync10(join10(projectRoot, "nuxt.config.ts"))) {
1159
1300
  result.framework = "nuxt";
1160
1301
  result.version = getPackageVersion(projectRoot, "nuxt");
1161
1302
  return result;
1162
1303
  }
1163
- if (existsSync9(join9(projectRoot, "astro.config.mjs")) || existsSync9(join9(projectRoot, "astro.config.ts"))) {
1304
+ if (existsSync10(join10(projectRoot, "astro.config.mjs")) || existsSync10(join10(projectRoot, "astro.config.ts"))) {
1164
1305
  result.framework = "astro";
1165
1306
  result.version = getPackageVersion(projectRoot, "astro");
1166
1307
  return result;
1167
1308
  }
1168
- if (existsSync9(join9(projectRoot, "svelte.config.js")) || existsSync9(join9(projectRoot, "svelte.config.ts"))) {
1309
+ if (existsSync10(join10(projectRoot, "svelte.config.js")) || existsSync10(join10(projectRoot, "svelte.config.ts"))) {
1169
1310
  result.framework = "svelte";
1170
1311
  result.version = getPackageVersion(projectRoot, "svelte");
1171
1312
  return result;
1172
1313
  }
1173
- if (existsSync9(join9(projectRoot, "angular.json"))) {
1314
+ if (existsSync10(join10(projectRoot, "angular.json"))) {
1174
1315
  result.framework = "angular";
1175
1316
  result.version = getPackageVersion(projectRoot, "@angular/core");
1176
1317
  return result;
1177
1318
  }
1178
- const packageJsonPath = join9(projectRoot, "package.json");
1179
- if (existsSync9(packageJsonPath)) {
1319
+ const packageJsonPath = join10(projectRoot, "package.json");
1320
+ if (existsSync10(packageJsonPath)) {
1180
1321
  try {
1181
- const packageJson = JSON.parse(readFileSync7(packageJsonPath, "utf-8"));
1322
+ const packageJson = JSON.parse(readFileSync8(packageJsonPath, "utf-8"));
1182
1323
  const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
1183
1324
  if (deps.next) {
1184
1325
  result.framework = "nextjs";
@@ -1205,18 +1346,18 @@ function detectProject(projectRoot = process.cwd()) {
1205
1346
  } catch {
1206
1347
  }
1207
1348
  }
1208
- if (result.framework === "unknown" && !existsSync9(packageJsonPath)) {
1209
- if (existsSync9(join9(projectRoot, "index.html"))) {
1349
+ if (result.framework === "unknown" && !existsSync10(packageJsonPath)) {
1350
+ if (existsSync10(join10(projectRoot, "index.html"))) {
1210
1351
  result.framework = "html";
1211
1352
  }
1212
1353
  }
1213
1354
  return result;
1214
1355
  }
1215
1356
  function getPackageVersion(projectRoot, packageName) {
1216
- const packageJsonPath = join9(projectRoot, "package.json");
1217
- if (!existsSync9(packageJsonPath)) return void 0;
1357
+ const packageJsonPath = join10(projectRoot, "package.json");
1358
+ if (!existsSync10(packageJsonPath)) return void 0;
1218
1359
  try {
1219
- const packageJson = JSON.parse(readFileSync7(packageJsonPath, "utf-8"));
1360
+ const packageJson = JSON.parse(readFileSync8(packageJsonPath, "utf-8"));
1220
1361
  const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
1221
1362
  const version = deps[packageName];
1222
1363
  return version?.replace(/^\^|~/, "");
@@ -1249,8 +1390,8 @@ function formatDetection(detected) {
1249
1390
  }
1250
1391
 
1251
1392
  // src/workflow-model.ts
1252
- import { existsSync as existsSync10, readFileSync as readFileSync8 } from "fs";
1253
- import { join as join10 } from "path";
1393
+ import { existsSync as existsSync11, readFileSync as readFileSync9 } from "fs";
1394
+ import { join as join11 } from "path";
1254
1395
  function inferSuggestedShell(layout) {
1255
1396
  if (layout.hasSidebar) return "sidebar-main";
1256
1397
  if (layout.hasTopNav) return "top-nav-main";
@@ -1259,6 +1400,58 @@ function inferSuggestedShell(layout) {
1259
1400
  function hasExistingProjectFootprint(detected) {
1260
1401
  return detected.framework !== "unknown" || detected.packageManager !== "unknown" || detected.hasTypeScript || detected.hasTailwind || detected.existingRuleFiles.length > 0;
1261
1402
  }
1403
+ function normalizeWorkflowFlag(value) {
1404
+ if (value === "greenfield" || value === "brownfield" || value === "hybrid") return value;
1405
+ return void 0;
1406
+ }
1407
+ function normalizeAdoptionMode(value) {
1408
+ if (value === "contract-only" || value === "style-bridge" || value === "decantr-css") {
1409
+ return value;
1410
+ }
1411
+ return void 0;
1412
+ }
1413
+ function normalizeAssistantBridge(value) {
1414
+ if (value === "none" || value === "preview" || value === "apply") return value;
1415
+ return void 0;
1416
+ }
1417
+ function resolveWorkflowPolicy(input) {
1418
+ const requestedWorkflow = normalizeWorkflowFlag(input.requestedWorkflow);
1419
+ const requestedAdoption = normalizeAdoptionMode(input.requestedAdoption);
1420
+ const requestedAssistantBridge = normalizeAssistantBridge(input.requestedAssistantBridge);
1421
+ const hasRegistryContent = Boolean(
1422
+ input.requestedBlueprint || input.requestedArchetype || input.requestedTheme
1423
+ );
1424
+ const hasAnalysisArtifacts = Boolean(input.workflowSeed);
1425
+ const existingFootprint = hasExistingProjectFootprint(input.detected);
1426
+ let workflowMode;
1427
+ if (requestedWorkflow === "hybrid") {
1428
+ workflowMode = "hybrid-compose";
1429
+ } else if (requestedWorkflow === "brownfield" || input.explicitExisting || input.workflowSeed) {
1430
+ workflowMode = "brownfield-attach";
1431
+ } else if (requestedWorkflow === "greenfield") {
1432
+ workflowMode = hasRegistryContent ? "greenfield-scaffold" : "greenfield-contract-only";
1433
+ } else if (input.command === "new") {
1434
+ workflowMode = hasRegistryContent ? "greenfield-scaffold" : "greenfield-contract-only";
1435
+ } else if (existingFootprint && hasRegistryContent) {
1436
+ workflowMode = "hybrid-compose";
1437
+ } else if (existingFootprint && !hasRegistryContent) {
1438
+ workflowMode = "brownfield-attach";
1439
+ } else {
1440
+ workflowMode = hasRegistryContent ? "greenfield-scaffold" : "greenfield-contract-only";
1441
+ }
1442
+ const adoptionMode = requestedAdoption ?? input.workflowSeed?.adoptionMode ?? (workflowMode === "brownfield-attach" ? "contract-only" : workflowMode === "hybrid-compose" ? "contract-only" : workflowMode === "greenfield-contract-only" ? "contract-only" : "decantr-css");
1443
+ const contentSource = hasRegistryContent ? input.offline ? "cache" : "official" : "none";
1444
+ const assistantBridge = requestedAssistantBridge ?? input.workflowSeed?.assistantBridge ?? (workflowMode === "brownfield-attach" && input.detected.existingRuleFiles.length > 0 ? "preview" : "none");
1445
+ return {
1446
+ workflowMode,
1447
+ adoptionMode,
1448
+ contentSource,
1449
+ assistantBridge,
1450
+ projectScope: input.projectScope ?? "single-app",
1451
+ hasAnalysisArtifacts,
1452
+ registryRequired: hasRegistryContent
1453
+ };
1454
+ }
1262
1455
  function createBrownfieldInitSeed(detected, layout, styling) {
1263
1456
  return {
1264
1457
  version: 1,
@@ -1266,6 +1459,10 @@ function createBrownfieldInitSeed(detected, layout, styling) {
1266
1459
  contractOnly: true,
1267
1460
  registryOptional: true,
1268
1461
  workflowMode: "brownfield-attach",
1462
+ adoptionMode: "contract-only",
1463
+ contentSource: "none",
1464
+ assistantBridge: detected.existingRuleFiles.length > 0 ? "preview" : "none",
1465
+ projectScope: "single-app",
1269
1466
  target: detected.framework !== "unknown" ? detected.framework : "react",
1270
1467
  shell: inferSuggestedShell(layout),
1271
1468
  guard: "guided",
@@ -1281,12 +1478,12 @@ function createBrownfieldInitSeed(detected, layout, styling) {
1281
1478
  };
1282
1479
  }
1283
1480
  function readBrownfieldInitSeed(projectRoot) {
1284
- const seedPath = join10(projectRoot, ".decantr", "init-seed.json");
1285
- if (!existsSync10(seedPath)) {
1481
+ const seedPath = join11(projectRoot, ".decantr", "init-seed.json");
1482
+ if (!existsSync11(seedPath)) {
1286
1483
  return null;
1287
1484
  }
1288
1485
  try {
1289
- const parsed = JSON.parse(readFileSync8(seedPath, "utf-8"));
1486
+ const parsed = JSON.parse(readFileSync9(seedPath, "utf-8"));
1290
1487
  if (parsed.workflow !== "brownfield-adoption") {
1291
1488
  return null;
1292
1489
  }
@@ -1303,7 +1500,7 @@ var RESET2 = "\x1B[0m";
1303
1500
  var GREEN2 = "\x1B[32m";
1304
1501
  var CYAN = "\x1B[36m";
1305
1502
  var YELLOW = "\x1B[33m";
1306
- function cmdAnalyze(projectRoot = process.cwd()) {
1503
+ function cmdAnalyze(projectRoot = process.cwd(), workspace) {
1307
1504
  console.log(`
1308
1505
  ${BOLD}Analyzing project...${RESET2}
1309
1506
  `);
@@ -1322,6 +1519,7 @@ ${BOLD}Analyzing project...${RESET2}
1322
1519
  console.log(`${DIM2}Scanning dependencies...${RESET2}`);
1323
1520
  const dependencies = scanDependencies(projectRoot);
1324
1521
  const initSeed = createBrownfieldInitSeed(project, layout, styling);
1522
+ initSeed.projectScope = workspace?.projectScope ?? "single-app";
1325
1523
  const analysis = {
1326
1524
  version: 1,
1327
1525
  analyzedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -1330,7 +1528,11 @@ ${BOLD}Analyzing project...${RESET2}
1330
1528
  frameworkVersion: project.version ?? null,
1331
1529
  packageManager: project.packageManager,
1332
1530
  hasTypeScript: project.hasTypeScript,
1333
- hasTailwind: project.hasTailwind
1531
+ hasTailwind: project.hasTailwind,
1532
+ existingRuleFiles: project.existingRuleFiles,
1533
+ workspaceRoot: workspace?.workspaceRoot ?? projectRoot,
1534
+ appRoot: workspace?.appRoot ?? projectRoot,
1535
+ projectScope: workspace?.projectScope ?? "single-app"
1334
1536
  },
1335
1537
  routes,
1336
1538
  components,
@@ -1344,8 +1546,9 @@ ${BOLD}Analyzing project...${RESET2}
1344
1546
  attach: {
1345
1547
  entrypoint: "decantr analyze",
1346
1548
  contractOnly: true,
1549
+ adoptionMode: "contract-only",
1347
1550
  initSeedPath: ".decantr/init-seed.json",
1348
- recommendedCommand: "decantr init --existing --yes"
1551
+ recommendedCommand: "decantr init --existing --yes --adoption=contract-only"
1349
1552
  },
1350
1553
  hybrid: {
1351
1554
  ownerCommands: [
@@ -1356,16 +1559,31 @@ ${BOLD}Analyzing project...${RESET2}
1356
1559
  "decantr upgrade"
1357
1560
  ]
1358
1561
  }
1562
+ },
1563
+ retrofitPlan: {
1564
+ recommendedWorkflowMode: "brownfield-attach",
1565
+ recommendedAdoptionMode: "contract-only",
1566
+ assistantBridge: project.existingRuleFiles.length > 0 ? "preview existing rule files before applying" : "none detected",
1567
+ routeAnchors: routes.routes.map((route) => route.path),
1568
+ stylingAnchors: [styling.configFile].filter(Boolean),
1569
+ ruleFiles: project.existingRuleFiles,
1570
+ preserve: [
1571
+ "framework",
1572
+ "package manager",
1573
+ "router",
1574
+ "build tooling",
1575
+ "existing styling system"
1576
+ ]
1359
1577
  }
1360
1578
  };
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");
1579
+ const decantrDir = join12(projectRoot, ".decantr");
1580
+ if (!existsSync12(decantrDir)) {
1581
+ mkdirSync3(decantrDir, { recursive: true });
1582
+ }
1583
+ const outputPath = join12(decantrDir, "analysis.json");
1584
+ const initSeedPath = join12(decantrDir, "init-seed.json");
1585
+ writeFileSync4(outputPath, JSON.stringify(analysis, null, 2) + "\n", "utf-8");
1586
+ writeFileSync4(initSeedPath, JSON.stringify(initSeed, null, 2) + "\n", "utf-8");
1369
1587
  console.log(`
1370
1588
  ${GREEN2}Analysis complete.${RESET2}
1371
1589
  `);
@@ -1399,14 +1617,14 @@ ${DIM2}Written to:${RESET2} ${outputPath}`);
1399
1617
  console.log(`${DIM2}Init seed:${RESET2} ${initSeedPath}`);
1400
1618
  console.log(
1401
1619
  `
1402
- ${YELLOW}Next step:${RESET2} Run ${BOLD}decantr init --existing --yes${RESET2} to attach Decantr using the generated brownfield seed.
1620
+ ${YELLOW}Next step:${RESET2} Run ${BOLD}decantr init --existing --yes --adoption=contract-only${RESET2} to attach Decantr using the generated brownfield seed.
1403
1621
  `
1404
1622
  );
1405
1623
  }
1406
1624
 
1407
1625
  // src/commands/create.ts
1408
- import { existsSync as existsSync12, mkdirSync as mkdirSync3, writeFileSync as writeFileSync4 } from "fs";
1409
- import { join as join12 } from "path";
1626
+ import { existsSync as existsSync13, mkdirSync as mkdirSync4, writeFileSync as writeFileSync5 } from "fs";
1627
+ import { join as join13 } from "path";
1410
1628
  import {
1411
1629
  CONTENT_TYPE_TO_API_CONTENT_TYPE,
1412
1630
  CONTENT_TYPES
@@ -1602,23 +1820,23 @@ function cmdCreate(type, name, projectRoot = process.cwd()) {
1602
1820
  }
1603
1821
  const contentType = type;
1604
1822
  const plural = PLURAL[contentType];
1605
- const customDir = join12(projectRoot, ".decantr", "custom", plural);
1606
- const filePath = join12(customDir, `${name}.json`);
1607
- if (existsSync12(filePath)) {
1823
+ const customDir = join13(projectRoot, ".decantr", "custom", plural);
1824
+ const filePath = join13(customDir, `${name}.json`);
1825
+ if (existsSync13(filePath)) {
1608
1826
  console.error(`${type} "${name}" already exists at ${filePath}`);
1609
1827
  process.exitCode = 1;
1610
1828
  return;
1611
1829
  }
1612
- mkdirSync3(customDir, { recursive: true });
1830
+ mkdirSync4(customDir, { recursive: true });
1613
1831
  const skeleton = getSkeleton(contentType, name, humanizeId(name));
1614
- writeFileSync4(filePath, JSON.stringify(skeleton, null, 2));
1832
+ writeFileSync5(filePath, JSON.stringify(skeleton, null, 2));
1615
1833
  console.log(`Created ${type} "${name}" at ${filePath}`);
1616
1834
  console.log(`Edit it, then publish with: decantr publish ${type} ${name}`);
1617
1835
  }
1618
1836
 
1619
1837
  // 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";
1838
+ import { existsSync as existsSync14, mkdirSync as mkdirSync5, readFileSync as readFileSync10, writeFileSync as writeFileSync6 } from "fs";
1839
+ import { dirname as dirname2, join as join14 } from "path";
1622
1840
  var GREEN3 = "\x1B[32m";
1623
1841
  var RED2 = "\x1B[31m";
1624
1842
  var DIM3 = "\x1B[2m";
@@ -1768,21 +1986,21 @@ function generateCSSVars(tokens) {
1768
1986
  return lines.join("\n");
1769
1987
  }
1770
1988
  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)) {
1989
+ const essencePath = join14(projectRoot, "decantr.essence.json");
1990
+ const tokensPath = join14(projectRoot, "src", "styles", "tokens.css");
1991
+ if (!existsSync14(essencePath)) {
1774
1992
  console.error(`${RED2}No decantr.essence.json found. Run \`decantr init\` first.${RESET3}`);
1775
1993
  process.exitCode = 1;
1776
1994
  return;
1777
1995
  }
1778
- if (!existsSync13(tokensPath)) {
1996
+ if (!existsSync14(tokensPath)) {
1779
1997
  console.error(
1780
1998
  `${RED2}No src/styles/tokens.css found. Run \`decantr refresh\` to generate tokens.${RESET3}`
1781
1999
  );
1782
2000
  process.exitCode = 1;
1783
2001
  return;
1784
2002
  }
1785
- const tokensCSS = readFileSync9(tokensPath, "utf-8");
2003
+ const tokensCSS = readFileSync10(tokensPath, "utf-8");
1786
2004
  const tokens = parseTokensCSS(tokensCSS);
1787
2005
  if (tokens.size === 0) {
1788
2006
  console.error(`${RED2}No --d-* tokens found in tokens.css.${RESET3}`);
@@ -1791,28 +2009,28 @@ async function cmdExport(target, projectRoot, options = {}) {
1791
2009
  }
1792
2010
  switch (target) {
1793
2011
  case "shadcn": {
1794
- const cssOut = options.output ?? join13(projectRoot, "src", "styles", "shadcn-theme.css");
1795
- const jsonOut = join13(projectRoot, "components.json");
2012
+ const cssOut = options.output ?? join14(projectRoot, "src", "styles", "shadcn-theme.css");
2013
+ const jsonOut = join14(projectRoot, "components.json");
1796
2014
  ensureDir(cssOut);
1797
- writeFileSync5(cssOut, generateShadcnCSS(tokens), "utf-8");
1798
- writeFileSync5(jsonOut, generateShadcnComponentsJSON(), "utf-8");
2015
+ writeFileSync6(cssOut, generateShadcnCSS(tokens), "utf-8");
2016
+ writeFileSync6(jsonOut, generateShadcnComponentsJSON(), "utf-8");
1799
2017
  console.log(`${GREEN3}Exported shadcn theme:${RESET3}`);
1800
2018
  console.log(` ${DIM3}CSS:${RESET3} ${cssOut}`);
1801
2019
  console.log(` ${DIM3}JSON:${RESET3} ${jsonOut}`);
1802
2020
  break;
1803
2021
  }
1804
2022
  case "tailwind": {
1805
- const out = options.output ?? join13(projectRoot, "tailwind.decantr.config.ts");
2023
+ const out = options.output ?? join14(projectRoot, "tailwind.decantr.config.ts");
1806
2024
  ensureDir(out);
1807
- writeFileSync5(out, generateTailwindConfig(tokens), "utf-8");
2025
+ writeFileSync6(out, generateTailwindConfig(tokens), "utf-8");
1808
2026
  console.log(`${GREEN3}Exported Tailwind config:${RESET3}`);
1809
2027
  console.log(` ${DIM3}File:${RESET3} ${out}`);
1810
2028
  break;
1811
2029
  }
1812
2030
  case "css-vars": {
1813
- const out = options.output ?? join13(projectRoot, "decantr-tokens.css");
2031
+ const out = options.output ?? join14(projectRoot, "decantr-tokens.css");
1814
2032
  ensureDir(out);
1815
- writeFileSync5(out, generateCSSVars(tokens), "utf-8");
2033
+ writeFileSync6(out, generateCSSVars(tokens), "utf-8");
1816
2034
  console.log(`${GREEN3}Exported CSS variables:${RESET3}`);
1817
2035
  console.log(` ${DIM3}File:${RESET3} ${out}`);
1818
2036
  break;
@@ -1820,16 +2038,16 @@ async function cmdExport(target, projectRoot, options = {}) {
1820
2038
  }
1821
2039
  }
1822
2040
  function ensureDir(filePath) {
1823
- const dir = dirname(filePath);
1824
- if (!existsSync13(dir)) {
1825
- mkdirSync4(dir, { recursive: true });
2041
+ const dir = dirname2(filePath);
2042
+ if (!existsSync14(dir)) {
2043
+ mkdirSync5(dir, { recursive: true });
1826
2044
  }
1827
2045
  }
1828
2046
 
1829
2047
  // src/commands/magic.ts
1830
- import { existsSync as existsSync14 } from "fs";
2048
+ import { existsSync as existsSync15 } from "fs";
1831
2049
  import * as fs from "fs/promises";
1832
- import { join as join14 } from "path";
2050
+ import { join as join15 } from "path";
1833
2051
  var BOLD2 = "\x1B[1m";
1834
2052
  var DIM4 = "\x1B[2m";
1835
2053
  var RESET4 = "\x1B[0m";
@@ -1979,7 +2197,8 @@ async function resolveTheme(intent, registryClient) {
1979
2197
  const hints = intent.themeHints;
1980
2198
  if (registryClient) {
1981
2199
  try {
1982
- const themes = await registryClient.fetchThemes();
2200
+ const themesResult = await registryClient.fetchThemes();
2201
+ const themes = Array.isArray(themesResult) ? themesResult : Array.isArray(themesResult?.data?.items) ? themesResult.data.items : [];
1983
2202
  if (themes && themes.length > 0) {
1984
2203
  const scored = themes.map((t) => {
1985
2204
  let score = 0;
@@ -1994,7 +2213,7 @@ async function resolveTheme(intent, registryClient) {
1994
2213
  return { id: t.id || t.slug, score, modes: t.modes };
1995
2214
  }).sort((a, b) => b.score - a.score);
1996
2215
  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";
2216
+ 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
2217
  return { id: scored[0].id, mode: mode2 };
1999
2218
  }
2000
2219
  }
@@ -2056,8 +2275,8 @@ async function cmdMagic(prompt, projectRoot, options) {
2056
2275
  console.log(` Archetype: ${intent.archetype}`);
2057
2276
  }
2058
2277
  console.log("");
2059
- const essencePath = join14(projectRoot, "decantr.essence.json");
2060
- if (existsSync14(essencePath)) {
2278
+ const essencePath = join15(projectRoot, "decantr.essence.json");
2279
+ if (existsSync15(essencePath)) {
2061
2280
  console.log(error(" decantr.essence.json already exists in this directory."));
2062
2281
  console.log(dim(" Remove it first or use a different directory."));
2063
2282
  process.exitCode = 1;
@@ -2080,7 +2299,7 @@ async function cmdMagic(prompt, projectRoot, options) {
2080
2299
  return;
2081
2300
  }
2082
2301
  const registryClient = new RegistryClient({
2083
- cacheDir: join14(projectRoot, ".decantr", "cache"),
2302
+ cacheDir: join15(projectRoot, ".decantr", "cache"),
2084
2303
  apiUrl: options.registry,
2085
2304
  offline: options.offline
2086
2305
  });
@@ -2351,14 +2570,14 @@ async function cmdMagic(prompt, projectRoot, options) {
2351
2570
  if (result.gitignoreUpdated) {
2352
2571
  console.log(` ${dim(".gitignore updated")}`);
2353
2572
  }
2354
- const contextDir = join14(projectRoot, ".decantr", "context");
2573
+ const contextDir = join15(projectRoot, ".decantr", "context");
2355
2574
  let sectionCount = 0;
2356
2575
  try {
2357
2576
  const files = await fs.readdir(contextDir);
2358
2577
  sectionCount = files.filter((f) => f.startsWith("section-")).length;
2359
2578
  } catch {
2360
2579
  }
2361
- const treatmentsPath = join14(projectRoot, "src", "styles", "treatments.css");
2580
+ const treatmentsPath = join15(projectRoot, "src", "styles", "treatments.css");
2362
2581
  let hasLayers = false;
2363
2582
  try {
2364
2583
  const css = await fs.readFile(treatmentsPath, "utf-8");
@@ -2376,27 +2595,23 @@ ${GREEN4}${BOLD2}Quality summary:${RESET4}`);
2376
2595
  );
2377
2596
  console.log("");
2378
2597
  console.log(`${BOLD2} Ready!${RESET4} Next steps:`);
2379
- console.log(` 1. Read ${cyan("DECANTR.md")} for guard rules, CSS approach, and workflow`);
2380
- console.log(
2381
- ` 2. Read ${cyan(".decantr/context/scaffold-pack.md")} first as the primary compiled contract`
2382
- );
2383
2598
  console.log(
2384
- ` 3. Read ${cyan(".decantr/context/scaffold.md")} second for broader topology and voice guidance`
2599
+ ` 1. Read ${cyan(".decantr/context/scaffold-pack.md")} first as the primary compiled contract`
2385
2600
  );
2386
2601
  console.log(
2387
- ` 4. Read the matching ${cyan(".decantr/context/section-*-pack.md")} and ${cyan(".decantr/context/section-*.md")} files before section work`
2602
+ ` 2. Read the matching ${cyan(".decantr/context/section-*-pack.md")} and ${cyan(".decantr/context/page-*-pack.md")} files before section or route work`
2388
2603
  );
2389
2604
  console.log(
2390
- ` 5. Read the matching ${cyan(".decantr/context/page-*-pack.md")} file before route work`
2605
+ ` 3. Use ${cyan("DECANTR.md")} as a lookup reference for guard rules, CSS atoms, treatments, decorators, and workflow`
2391
2606
  );
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`);
2607
+ console.log(` 4. Build the shell and route structure first, then implement each page`);
2608
+ console.log(` 5. Run ${cyan("decantr check")} and ${cyan("decantr audit")} before you ship`);
2394
2609
  console.log("");
2395
2610
  }
2396
2611
 
2397
2612
  // 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";
2613
+ import { copyFileSync, existsSync as existsSync16, readFileSync as readFileSync11, writeFileSync as writeFileSync7 } from "fs";
2614
+ import { join as join16 } from "path";
2400
2615
  import { isV3 as isV32, migrateV2ToV3, validateEssence } from "@decantr/essence-spec";
2401
2616
  var GREEN5 = "\x1B[32m";
2402
2617
  var RED4 = "\x1B[31m";
@@ -2404,12 +2619,12 @@ var YELLOW3 = "\x1B[33m";
2404
2619
  var RESET5 = "\x1B[0m";
2405
2620
  var DIM5 = "\x1B[2m";
2406
2621
  function migrateEssenceFile(essencePath) {
2407
- if (!existsSync15(essencePath)) {
2622
+ if (!existsSync16(essencePath)) {
2408
2623
  return { success: false, error: `File not found: ${essencePath}` };
2409
2624
  }
2410
2625
  let raw;
2411
2626
  try {
2412
- raw = readFileSync10(essencePath, "utf-8");
2627
+ raw = readFileSync11(essencePath, "utf-8");
2413
2628
  } catch (e) {
2414
2629
  return { success: false, error: `Could not read ${essencePath}: ${e.message}` };
2415
2630
  }
@@ -2450,7 +2665,7 @@ function migrateEssenceFile(essencePath) {
2450
2665
  };
2451
2666
  }
2452
2667
  try {
2453
- writeFileSync6(essencePath, JSON.stringify(v3, null, 2) + "\n");
2668
+ writeFileSync7(essencePath, JSON.stringify(v3, null, 2) + "\n");
2454
2669
  } catch (e) {
2455
2670
  return {
2456
2671
  success: false,
@@ -2461,8 +2676,8 @@ function migrateEssenceFile(essencePath) {
2461
2676
  return { success: true, backupPath };
2462
2677
  }
2463
2678
  async function cmdMigrate(projectRoot = process.cwd()) {
2464
- const essencePath = join15(projectRoot, "decantr.essence.json");
2465
- if (!existsSync15(essencePath)) {
2679
+ const essencePath = join16(projectRoot, "decantr.essence.json");
2680
+ if (!existsSync16(essencePath)) {
2466
2681
  console.error(`${RED4}No decantr.essence.json found. Run \`decantr init\` first.${RESET5}`);
2467
2682
  process.exitCode = 1;
2468
2683
  return;
@@ -2487,20 +2702,38 @@ async function cmdMigrate(projectRoot = process.cwd()) {
2487
2702
  }
2488
2703
 
2489
2704
  // src/commands/new-project.ts
2490
- 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";
2705
+ import { spawnSync } from "child_process";
2706
+ import { existsSync as existsSync18, mkdirSync as mkdirSync8 } from "fs";
2707
+ import { join as join19, resolve as resolve2 } from "path";
2493
2708
  import { fileURLToPath } from "url";
2494
2709
 
2495
2710
  // src/bootstrap.ts
2496
- import { mkdirSync as mkdirSync5, readFileSync as readFileSync11, writeFileSync as writeFileSync7 } from "fs";
2497
- import { basename, join as join16 } from "path";
2711
+ import { mkdirSync as mkdirSync6, readFileSync as readFileSync12, writeFileSync as writeFileSync8 } from "fs";
2712
+ import { basename, join as join17 } from "path";
2498
2713
  import { resolvePackAdapter } from "@decantr/core";
2499
2714
  var reactViteBootstrapAdapter = {
2500
2715
  id: "react-vite",
2501
2716
  label: "React + Vite starter",
2717
+ target: "react",
2718
+ packAdapter: "react-vite",
2719
+ capabilities: {
2720
+ bootstrap: true,
2721
+ attach: true,
2722
+ styling: true,
2723
+ verify: true
2724
+ },
2725
+ attach: {
2726
+ routeRoots: ["src/App.tsx", "src/routes", "src/pages"],
2727
+ layoutFiles: ["src/App.tsx", "src/main.tsx"],
2728
+ componentRoots: ["src/components", "src/pages", "src/routes"]
2729
+ },
2730
+ verify: {
2731
+ devCommand: "npm run dev",
2732
+ buildCommand: "npm run build",
2733
+ distDir: "dist"
2734
+ },
2502
2735
  writeProjectFiles(projectDir, title, routingMode) {
2503
- const srcDir = join16(projectDir, "src");
2736
+ const srcDir = join17(projectDir, "src");
2504
2737
  const routerImport = routingMode === "hash" ? "HashRouter" : "BrowserRouter";
2505
2738
  const packageJson = {
2506
2739
  name: basename(projectDir) || "decantr-app",
@@ -2532,7 +2765,7 @@ var reactViteBootstrapAdapter = {
2532
2765
  vite: "^6.0.0"
2533
2766
  }
2534
2767
  };
2535
- writeFileSync7(join16(projectDir, "package.json"), JSON.stringify(packageJson, null, 2) + "\n");
2768
+ writeFileSync8(join17(projectDir, "package.json"), JSON.stringify(packageJson, null, 2) + "\n");
2536
2769
  const viteConfig = `import { defineConfig } from 'vite';
2537
2770
  import react from '@vitejs/plugin-react';
2538
2771
 
@@ -2540,7 +2773,7 @@ export default defineConfig({
2540
2773
  plugins: [react()],
2541
2774
  });
2542
2775
  `;
2543
- writeFileSync7(join16(projectDir, "vite.config.ts"), viteConfig);
2776
+ writeFileSync8(join17(projectDir, "vite.config.ts"), viteConfig);
2544
2777
  const tsconfig = {
2545
2778
  compilerOptions: {
2546
2779
  target: "ES2020",
@@ -2562,7 +2795,7 @@ export default defineConfig({
2562
2795
  },
2563
2796
  include: ["src"]
2564
2797
  };
2565
- writeFileSync7(join16(projectDir, "tsconfig.json"), JSON.stringify(tsconfig, null, 2) + "\n");
2798
+ writeFileSync8(join17(projectDir, "tsconfig.json"), JSON.stringify(tsconfig, null, 2) + "\n");
2566
2799
  const tsconfigApp = {
2567
2800
  compilerOptions: {
2568
2801
  tsBuildInfoFile: "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
@@ -2585,8 +2818,8 @@ export default defineConfig({
2585
2818
  },
2586
2819
  include: ["src"]
2587
2820
  };
2588
- writeFileSync7(
2589
- join16(projectDir, "tsconfig.app.json"),
2821
+ writeFileSync8(
2822
+ join17(projectDir, "tsconfig.app.json"),
2590
2823
  JSON.stringify(tsconfigApp, null, 2) + "\n"
2591
2824
  );
2592
2825
  const indexHtml = `<!doctype html>
@@ -2602,8 +2835,8 @@ export default defineConfig({
2602
2835
  </body>
2603
2836
  </html>
2604
2837
  `;
2605
- writeFileSync7(join16(projectDir, "index.html"), indexHtml);
2606
- mkdirSync5(srcDir, { recursive: true });
2838
+ writeFileSync8(join17(projectDir, "index.html"), indexHtml);
2839
+ mkdirSync6(srcDir, { recursive: true });
2607
2840
  const mainTsx = `import { StrictMode } from 'react';
2608
2841
  import { createRoot } from 'react-dom/client';
2609
2842
  import { ${routerImport} } from 'react-router-dom';
@@ -2620,7 +2853,7 @@ createRoot(document.getElementById('root')!).render(
2620
2853
  </StrictMode>,
2621
2854
  );
2622
2855
  `;
2623
- writeFileSync7(join16(srcDir, "main.tsx"), mainTsx);
2856
+ writeFileSync8(join17(srcDir, "main.tsx"), mainTsx);
2624
2857
  const appTsx = `import { css } from '@decantr/css';
2625
2858
  import { Routes, Route } from 'react-router-dom';
2626
2859
 
@@ -2634,7 +2867,7 @@ function WelcomePage() {
2634
2867
  <p className="d-label" data-anchor>Decantr starter</p>
2635
2868
  <h1 className={css('_heading2')}>${title}</h1>
2636
2869
  <p className={css('_textsm _fgmuted _mw[32rem]')}>
2637
- Scaffolded with Decantr. Read DECANTR.md and the compiled packs in .decantr/context before building routes.
2870
+ Scaffolded with Decantr. Read .decantr/context/scaffold-pack.md first, then use DECANTR.md as a lookup reference.
2638
2871
  </p>
2639
2872
  <div className={css('_flex _gap3 _wrap _jcc')}>
2640
2873
  <span className="d-annotation" data-status="info">Runtime: @decantr/css</span>
@@ -2655,34 +2888,184 @@ export function App() {
2655
2888
  );
2656
2889
  }
2657
2890
  `;
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 });
2891
+ writeFileSync8(join17(srcDir, "App.tsx"), appTsx);
2892
+ writeFileSync8(join17(srcDir, "vite-env.d.ts"), '/// <reference types="vite/client" />\n');
2893
+ mkdirSync6(join17(srcDir, "styles"), { recursive: true });
2661
2894
  }
2662
2895
  };
2663
- var BOOTSTRAP_ADAPTERS = {
2664
- "react-vite": reactViteBootstrapAdapter
2896
+ var nextAppAdapter = {
2897
+ id: "next-app",
2898
+ label: "Next.js App Router starter",
2899
+ target: "nextjs",
2900
+ packAdapter: "nextjs",
2901
+ capabilities: {
2902
+ bootstrap: true,
2903
+ attach: true,
2904
+ styling: true,
2905
+ verify: true
2906
+ },
2907
+ attach: {
2908
+ routeRoots: ["app", "pages"],
2909
+ layoutFiles: ["app/layout.tsx", "pages/_app.tsx"],
2910
+ componentRoots: ["components", "src/components", "app"]
2911
+ },
2912
+ verify: {
2913
+ devCommand: "npm run dev",
2914
+ buildCommand: "npm run build",
2915
+ distDir: ".next"
2916
+ },
2917
+ writeProjectFiles(projectDir, title) {
2918
+ const appDir = join17(projectDir, "app");
2919
+ const stylesDir = join17(projectDir, "src", "styles");
2920
+ const packageJson = {
2921
+ name: basename(projectDir) || "decantr-next-app",
2922
+ private: true,
2923
+ version: "0.0.0",
2924
+ type: "module",
2925
+ scripts: {
2926
+ dev: "next dev",
2927
+ build: "next build",
2928
+ start: "next start"
2929
+ },
2930
+ dependencies: {
2931
+ "@decantr/css": "^1.0.4",
2932
+ "lucide-react": "^0.468.0",
2933
+ next: "^16.0.0",
2934
+ react: "^19.0.0",
2935
+ "react-dom": "^19.0.0"
2936
+ },
2937
+ devDependencies: {
2938
+ "@types/node": "^20.0.0",
2939
+ "@types/react": "^19.0.0",
2940
+ "@types/react-dom": "^19.0.0",
2941
+ typescript: "^5.7.0"
2942
+ }
2943
+ };
2944
+ writeFileSync8(join17(projectDir, "package.json"), JSON.stringify(packageJson, null, 2) + "\n");
2945
+ writeFileSync8(join17(projectDir, "next.config.ts"), 'import type { NextConfig } from "next";\n\nconst nextConfig: NextConfig = {};\n\nexport default nextConfig;\n');
2946
+ 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');
2947
+ writeFileSync8(
2948
+ join17(projectDir, "tsconfig.json"),
2949
+ JSON.stringify(
2950
+ {
2951
+ compilerOptions: {
2952
+ target: "ES2017",
2953
+ lib: ["dom", "dom.iterable", "esnext"],
2954
+ allowJs: true,
2955
+ skipLibCheck: true,
2956
+ strict: true,
2957
+ noEmit: true,
2958
+ esModuleInterop: true,
2959
+ module: "esnext",
2960
+ moduleResolution: "bundler",
2961
+ resolveJsonModule: true,
2962
+ isolatedModules: true,
2963
+ jsx: "preserve",
2964
+ incremental: true,
2965
+ plugins: [{ name: "next" }]
2966
+ },
2967
+ include: ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
2968
+ exclude: ["node_modules"]
2969
+ },
2970
+ null,
2971
+ 2
2972
+ ) + "\n"
2973
+ );
2974
+ mkdirSync6(appDir, { recursive: true });
2975
+ mkdirSync6(stylesDir, { recursive: true });
2976
+ writeFileSync8(
2977
+ join17(appDir, "layout.tsx"),
2978
+ `import type { Metadata } from 'next';
2979
+ import '../src/styles/global.css';
2980
+ import '../src/styles/tokens.css';
2981
+ import '../src/styles/treatments.css';
2982
+
2983
+ export const metadata: Metadata = {
2984
+ title: '${title}',
2985
+ description: 'Scaffolded with Decantr',
2986
+ };
2987
+
2988
+ export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) {
2989
+ return (
2990
+ <html lang="en">
2991
+ <body>{children}</body>
2992
+ </html>
2993
+ );
2994
+ }
2995
+ `
2996
+ );
2997
+ writeFileSync8(
2998
+ join17(appDir, "page.tsx"),
2999
+ `import { css } from '@decantr/css';
3000
+
3001
+ export default function HomePage() {
3002
+ return (
3003
+ <main id="main-content" className={css('_minh[100vh] _flex _col _aic _jcc _p6 _gap4')}>
3004
+ <section className={css('_wfull _mw[42rem]') + ' d-section'} data-density="comfortable">
3005
+ <div className={css('_flex _col _aic _gap4 _textc') + ' d-surface'} data-elevation="raised">
3006
+ <p className="d-label" data-anchor>Decantr starter</p>
3007
+ <h1 className={css('_heading2')}>${title}</h1>
3008
+ <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>
3009
+ <span className="d-annotation" data-status="info">Runtime: Next.js App Router</span>
3010
+ </div>
3011
+ </section>
3012
+ </main>
3013
+ );
3014
+ }
3015
+ `
3016
+ );
3017
+ }
3018
+ };
3019
+ var genericWebAdapter = {
3020
+ id: "generic-web",
3021
+ label: "Generic web contract adapter",
3022
+ target: "generic",
3023
+ packAdapter: "generic-web",
3024
+ capabilities: {
3025
+ bootstrap: false,
3026
+ attach: true,
3027
+ styling: true,
3028
+ verify: true
3029
+ },
3030
+ attach: {
3031
+ routeRoots: ["src", "app", "pages"],
3032
+ layoutFiles: ["index.html", "src/main.ts", "src/main.tsx"],
3033
+ componentRoots: ["src/components", "components"]
3034
+ },
3035
+ verify: {
3036
+ devCommand: "npm run dev",
3037
+ buildCommand: "npm run build",
3038
+ distDir: "dist"
3039
+ }
3040
+ };
3041
+ var DECANTR_ADAPTERS = {
3042
+ "react-vite": reactViteBootstrapAdapter,
3043
+ "next-app": nextAppAdapter,
3044
+ "generic-web": genericWebAdapter
2665
3045
  };
2666
3046
  function resolveBootstrapTarget(target) {
2667
3047
  const normalizedTarget = (target || "react").toLowerCase();
2668
- const platformType = "spa";
3048
+ const platformType = normalizedTarget === "nextjs" ? "ssr" : "spa";
2669
3049
  const packAdapter = resolvePackAdapter(normalizedTarget, platformType);
3050
+ const adapterId = normalizedTarget === "nextjs" ? "next-app" : DECANTR_ADAPTERS[packAdapter] ? packAdapter : "generic-web";
2670
3051
  return {
2671
3052
  target: normalizedTarget,
2672
3053
  platformType,
2673
3054
  packAdapter,
2674
- bootstrapAdapterId: BOOTSTRAP_ADAPTERS[packAdapter]?.id ?? null
3055
+ adapterId,
3056
+ bootstrapAdapterId: DECANTR_ADAPTERS[adapterId]?.capabilities.bootstrap && DECANTR_ADAPTERS[adapterId]?.writeProjectFiles ? adapterId : null
2675
3057
  };
2676
3058
  }
2677
3059
  function getBootstrapAdapter(resolution) {
2678
3060
  if (!resolution.bootstrapAdapterId) {
2679
3061
  return null;
2680
3062
  }
2681
- return BOOTSTRAP_ADAPTERS[resolution.bootstrapAdapterId] ?? null;
3063
+ const adapter = DECANTR_ADAPTERS[resolution.bootstrapAdapterId];
3064
+ return adapter?.writeProjectFiles ? adapter : null;
2682
3065
  }
2683
3066
  function detectRoutingMode(projectDir) {
2684
3067
  try {
2685
- const essence = JSON.parse(readFileSync11(join16(projectDir, "decantr.essence.json"), "utf-8"));
3068
+ const essence = JSON.parse(readFileSync12(join17(projectDir, "decantr.essence.json"), "utf-8"));
2686
3069
  const routing = essence.meta?.platform?.routing;
2687
3070
  if (routing === "history" || routing === "pathname") {
2688
3071
  return routing;
@@ -2694,45 +3077,45 @@ function detectRoutingMode(projectDir) {
2694
3077
  }
2695
3078
 
2696
3079
  // src/offline-content.ts
2697
- import { cpSync, existsSync as existsSync16, mkdirSync as mkdirSync6 } from "fs";
2698
- import { join as join17, resolve } from "path";
3080
+ import { cpSync, existsSync as existsSync17, mkdirSync as mkdirSync7 } from "fs";
3081
+ import { join as join18, resolve } from "path";
2699
3082
  var CONTENT_TYPES2 = ["archetypes", "blueprints", "patterns", "themes", "shells"];
2700
3083
  function copyIfExists(source, target) {
2701
- if (!existsSync16(source)) return false;
3084
+ if (!existsSync17(source)) return false;
2702
3085
  if (resolve(source) === resolve(target)) return true;
2703
3086
  cpSync(source, target, { recursive: true });
2704
3087
  return true;
2705
3088
  }
2706
3089
  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 });
3090
+ if (!existsSync17(contentRoot)) return false;
3091
+ const customRoot = join18(projectDir, ".decantr", "custom");
3092
+ const cacheRoot = join18(projectDir, ".decantr", "cache", "@official");
3093
+ mkdirSync7(customRoot, { recursive: true });
3094
+ mkdirSync7(cacheRoot, { recursive: true });
2712
3095
  let copiedAny = false;
2713
3096
  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 });
3097
+ const sourceDir = join18(contentRoot, type);
3098
+ if (!existsSync17(sourceDir)) continue;
3099
+ cpSync(sourceDir, join18(customRoot, type), { recursive: true });
3100
+ cpSync(sourceDir, join18(cacheRoot, type), { recursive: true });
2718
3101
  copiedAny = true;
2719
3102
  }
2720
3103
  return copiedAny;
2721
3104
  }
2722
3105
  function seedOfflineRegistry(projectDir, workspaceRoot) {
2723
- const projectDecantrRoot = join17(projectDir, ".decantr");
2724
- mkdirSync6(projectDecantrRoot, { recursive: true });
3106
+ const projectDecantrRoot = join18(projectDir, ".decantr");
3107
+ mkdirSync7(projectDecantrRoot, { recursive: true });
2725
3108
  const configuredContentRoot = process.env.DECANTR_CONTENT_DIR ? resolve(process.env.DECANTR_CONTENT_DIR) : null;
2726
3109
  if (configuredContentRoot && hydrateContentRoot(projectDir, configuredContentRoot)) {
2727
3110
  return { seeded: true, strategy: "configured-content-root" };
2728
3111
  }
2729
3112
  const copiedCache = copyIfExists(
2730
- join17(workspaceRoot, ".decantr", "cache"),
2731
- join17(projectDecantrRoot, "cache")
3113
+ join18(workspaceRoot, ".decantr", "cache"),
3114
+ join18(projectDecantrRoot, "cache")
2732
3115
  );
2733
3116
  const copiedCustom = copyIfExists(
2734
- join17(workspaceRoot, ".decantr", "custom"),
2735
- join17(projectDecantrRoot, "custom")
3117
+ join18(workspaceRoot, ".decantr", "custom"),
3118
+ join18(projectDecantrRoot, "custom")
2736
3119
  );
2737
3120
  if (copiedCache || copiedCustom) {
2738
3121
  return { seeded: true, strategy: "workspace-cache" };
@@ -2769,12 +3152,80 @@ function dim2(text) {
2769
3152
  function cyan2(text) {
2770
3153
  return `${CYAN3}${text}${RESET6}`;
2771
3154
  }
3155
+ function validatePassThroughFlagValue(flag, value) {
3156
+ if (value.length === 0) {
3157
+ throw new Error(`--${flag} cannot be empty.`);
3158
+ }
3159
+ if (value.length > 512) {
3160
+ throw new Error(`--${flag} is too long.`);
3161
+ }
3162
+ if (/[\u0000-\u001f\u007f]/.test(value)) {
3163
+ throw new Error(`--${flag} contains unsupported control characters.`);
3164
+ }
3165
+ return value;
3166
+ }
3167
+ function pushPassThroughFlag(flags, flag, value) {
3168
+ if (value == null) return;
3169
+ flags.push(`--${flag}=${validatePassThroughFlagValue(flag, value)}`);
3170
+ }
3171
+ function buildNewProjectInitArgs(options, inferredAdoption) {
3172
+ const initFlags = [
3173
+ "--yes",
3174
+ "--workflow=greenfield",
3175
+ `--adoption=${validatePassThroughFlagValue("mode", inferredAdoption)}`
3176
+ ];
3177
+ pushPassThroughFlag(initFlags, "blueprint", options.blueprint);
3178
+ pushPassThroughFlag(initFlags, "archetype", options.archetype);
3179
+ pushPassThroughFlag(initFlags, "theme", options.theme);
3180
+ pushPassThroughFlag(initFlags, "mode", options.mode);
3181
+ pushPassThroughFlag(initFlags, "shape", options.shape);
3182
+ pushPassThroughFlag(initFlags, "target", options.target);
3183
+ if (options.offline) initFlags.push("--offline");
3184
+ pushPassThroughFlag(initFlags, "registry", options.registry);
3185
+ pushPassThroughFlag(initFlags, "assistant-bridge", options.assistantBridge);
3186
+ return initFlags;
3187
+ }
3188
+ function commandForPlatform(command) {
3189
+ if (process.platform !== "win32") {
3190
+ return command;
3191
+ }
3192
+ return /^(?:npm|pnpm|yarn|bun|npx)$/.test(command) ? `${command}.cmd` : command;
3193
+ }
3194
+ function runArgvCommand(command, args, cwd) {
3195
+ const result = spawnSync(commandForPlatform(command), args, {
3196
+ cwd,
3197
+ stdio: "inherit",
3198
+ shell: false
3199
+ });
3200
+ if (result.error) {
3201
+ throw result.error;
3202
+ }
3203
+ if (result.status !== 0) {
3204
+ throw new Error(`Command failed: ${command} ${args.join(" ")}`);
3205
+ }
3206
+ }
3207
+ function resolveInitCommand(initFlags) {
3208
+ const bundledCliEntrypoint = fileURLToPath(new URL("./bin.js", import.meta.url));
3209
+ const cliEntrypoint = existsSync18(bundledCliEntrypoint) ? bundledCliEntrypoint : process.argv[1] && existsSync18(process.argv[1]) ? process.argv[1] : null;
3210
+ if (cliEntrypoint) {
3211
+ return {
3212
+ command: process.execPath,
3213
+ args: [cliEntrypoint, "init", ...initFlags]
3214
+ };
3215
+ }
3216
+ return {
3217
+ command: "npx",
3218
+ args: ["decantr", "init", ...initFlags]
3219
+ };
3220
+ }
2772
3221
  async function cmdNewProject(projectName, options) {
2773
3222
  const workspaceRoot = process.cwd();
2774
3223
  const projectDir = resolve2(workspaceRoot, projectName);
2775
3224
  const bootstrapTarget = resolveBootstrapTarget(options.target);
2776
3225
  const bootstrapAdapter = getBootstrapAdapter(bootstrapTarget);
2777
- const hasRunnableBootstrap = Boolean(bootstrapAdapter);
3226
+ const registryBackedScaffold = Boolean(options.blueprint || options.archetype);
3227
+ const inferredAdoption = options.adoption || (registryBackedScaffold ? "decantr-css" : "contract-only");
3228
+ const shouldBootstrapRuntime = Boolean(bootstrapAdapter && inferredAdoption === "decantr-css");
2778
3229
  if (!/^[a-z0-9][a-z0-9._-]*$/i.test(projectName)) {
2779
3230
  console.error(
2780
3231
  error2("Invalid project name. Use alphanumeric characters, hyphens, dots, or underscores.")
@@ -2782,18 +3233,32 @@ async function cmdNewProject(projectName, options) {
2782
3233
  process.exitCode = 1;
2783
3234
  return;
2784
3235
  }
2785
- if (existsSync17(projectDir)) {
3236
+ if (existsSync18(projectDir)) {
2786
3237
  console.error(error2(`Directory "${projectName}" already exists.`));
2787
3238
  process.exitCode = 1;
2788
3239
  return;
2789
3240
  }
3241
+ let initFlags;
3242
+ try {
3243
+ initFlags = buildNewProjectInitArgs(options, inferredAdoption);
3244
+ } catch (err) {
3245
+ console.error(error2(err.message));
3246
+ process.exitCode = 1;
3247
+ return;
3248
+ }
2790
3249
  console.log(heading(`Creating ${projectName}...`));
2791
- mkdirSync7(projectDir, { recursive: true });
3250
+ mkdirSync8(projectDir, { recursive: true });
2792
3251
  console.log(dim2(` Created ${projectName}/`));
2793
3252
  const title = projectName.replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
2794
- if (bootstrapAdapter) {
3253
+ if (shouldBootstrapRuntime && bootstrapAdapter) {
2795
3254
  bootstrapAdapter.writeProjectFiles(projectDir, title, "hash");
2796
3255
  console.log(dim2(` Bootstrapped ${bootstrapAdapter.label}`));
3256
+ } else if (bootstrapAdapter) {
3257
+ console.log(
3258
+ dim2(
3259
+ ` Skipping runtime bootstrap for adoption=${inferredAdoption}; creating a Decantr contract-only workspace.`
3260
+ )
3261
+ );
2797
3262
  } else {
2798
3263
  console.log(
2799
3264
  `${YELLOW4} No greenfield bootstrap adapter is available yet for target "${bootstrapTarget.target}" (${bootstrapTarget.packAdapter}).${RESET6}`
@@ -2805,10 +3270,10 @@ async function cmdNewProject(projectName, options) {
2805
3270
  );
2806
3271
  }
2807
3272
  const packageManager = detectPackageManager();
2808
- if (hasRunnableBootstrap) {
3273
+ if (shouldBootstrapRuntime) {
2809
3274
  console.log(heading("Installing dependencies..."));
2810
3275
  try {
2811
- execSync(`${packageManager} install`, { cwd: projectDir, stdio: "inherit" });
3276
+ runArgvCommand(packageManager, ["install"], projectDir);
2812
3277
  } catch {
2813
3278
  console.log(
2814
3279
  `
@@ -2845,21 +3310,10 @@ ${YELLOW4}Dependency install failed. Run \`${packageManager} install\` manually.
2845
3310
  return;
2846
3311
  }
2847
3312
  console.log(heading("Initializing Decantr..."));
2848
- const initFlags = ["--yes", "--existing"];
2849
- if (options.blueprint) initFlags.push(`--blueprint=${options.blueprint}`);
2850
- if (options.archetype) initFlags.push(`--archetype=${options.archetype}`);
2851
- if (options.theme) initFlags.push(`--theme=${options.theme}`);
2852
- if (options.mode) initFlags.push(`--mode=${options.mode}`);
2853
- if (options.shape) initFlags.push(`--shape=${options.shape}`);
2854
- if (options.target) initFlags.push(`--target=${options.target}`);
2855
- if (options.offline) initFlags.push("--offline");
2856
- if (options.registry) initFlags.push(`--registry=${options.registry}`);
2857
3313
  try {
2858
- 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;
2860
- const cliPath = cliEntrypoint ? `"${process.execPath}" "${cliEntrypoint}"` : "npx decantr";
2861
- execSync(`${cliPath} init ${initFlags.join(" ")}`, { cwd: projectDir, stdio: "inherit" });
2862
- if (bootstrapAdapter) {
3314
+ const initCommand = resolveInitCommand(initFlags);
3315
+ runArgvCommand(initCommand.command, initCommand.args, projectDir);
3316
+ if (shouldBootstrapRuntime && bootstrapAdapter) {
2863
3317
  bootstrapAdapter.writeProjectFiles(projectDir, title, detectRoutingMode(projectDir));
2864
3318
  }
2865
3319
  } catch {
@@ -2872,33 +3326,33 @@ ${YELLOW4}Decantr init encountered issues. Run \`decantr init\` manually inside
2872
3326
  \u2713 Project "${projectName}" created!
2873
3327
  `));
2874
3328
  console.log(` ${cyan2("cd " + projectName)}`);
2875
- if (bootstrapAdapter) {
3329
+ if (shouldBootstrapRuntime) {
2876
3330
  console.log(` ${cyan2(packageManager + " run dev")}`);
2877
3331
  } else {
2878
3332
  console.log(
2879
3333
  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.`
3334
+ ` 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
3335
  )
2882
3336
  );
2883
3337
  }
2884
3338
  console.log("");
2885
3339
  }
2886
3340
  function detectPackageManager() {
2887
- if (existsSync17(join18(process.cwd(), "pnpm-lock.yaml")) || existsSync17(join18(process.cwd(), "pnpm-workspace.yaml"))) {
3341
+ if (existsSync18(join19(process.cwd(), "pnpm-lock.yaml")) || existsSync18(join19(process.cwd(), "pnpm-workspace.yaml"))) {
2888
3342
  return "pnpm";
2889
3343
  }
2890
- if (existsSync17(join18(process.cwd(), "yarn.lock"))) {
3344
+ if (existsSync18(join19(process.cwd(), "yarn.lock"))) {
2891
3345
  return "yarn";
2892
3346
  }
2893
- if (existsSync17(join18(process.cwd(), "bun.lockb")) || existsSync17(join18(process.cwd(), "bun.lock"))) {
3347
+ if (existsSync18(join19(process.cwd(), "bun.lockb")) || existsSync18(join19(process.cwd(), "bun.lock"))) {
2894
3348
  return "bun";
2895
3349
  }
2896
3350
  return "npm";
2897
3351
  }
2898
3352
 
2899
3353
  // src/commands/publish.ts
2900
- import { existsSync as existsSync18, readFileSync as readFileSync12 } from "fs";
2901
- import { join as join19 } from "path";
3354
+ import { existsSync as existsSync19, readFileSync as readFileSync13 } from "fs";
3355
+ import { join as join20 } from "path";
2902
3356
  import {
2903
3357
  API_CONTENT_TYPE_TO_CONTENT_TYPE,
2904
3358
  CONTENT_TYPE_TO_API_CONTENT_TYPE as CONTENT_TYPE_TO_API_CONTENT_TYPE2,
@@ -2914,8 +3368,8 @@ async function cmdPublish(type, name, projectRoot = process.cwd()) {
2914
3368
  }
2915
3369
  const singularType = API_CONTENT_TYPE_TO_CONTENT_TYPE[type] || type;
2916
3370
  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)) {
3371
+ const customPath = join20(projectRoot, ".decantr", "custom", pluralType, `${name}.json`);
3372
+ if (!existsSync19(customPath)) {
2919
3373
  console.error(`Custom ${singularType} "${name}" not found at ${customPath}`);
2920
3374
  console.error(`Create one first: decantr create ${singularType} ${name}`);
2921
3375
  process.exitCode = 1;
@@ -2923,7 +3377,7 @@ async function cmdPublish(type, name, projectRoot = process.cwd()) {
2923
3377
  }
2924
3378
  let data;
2925
3379
  try {
2926
- data = JSON.parse(readFileSync12(customPath, "utf-8"));
3380
+ data = JSON.parse(readFileSync13(customPath, "utf-8"));
2927
3381
  } catch {
2928
3382
  console.error(`Failed to parse ${customPath}`);
2929
3383
  process.exitCode = 1;
@@ -2961,23 +3415,23 @@ async function cmdPublish(type, name, projectRoot = process.cwd()) {
2961
3415
  }
2962
3416
 
2963
3417
  // src/commands/refresh.ts
2964
- import { existsSync as existsSync19, readFileSync as readFileSync13 } from "fs";
2965
- import { join as join20 } from "path";
3418
+ import { existsSync as existsSync20, readFileSync as readFileSync14 } from "fs";
3419
+ import { join as join21 } from "path";
2966
3420
  import { isV3 as isV33 } from "@decantr/essence-spec";
2967
3421
  var GREEN7 = "\x1B[32m";
2968
3422
  var RED6 = "\x1B[31m";
2969
3423
  var DIM7 = "\x1B[2m";
2970
3424
  var RESET7 = "\x1B[0m";
2971
3425
  async function cmdRefresh(projectRoot = process.cwd(), options = {}) {
2972
- const essencePath = join20(projectRoot, "decantr.essence.json");
2973
- if (!existsSync19(essencePath)) {
3426
+ const essencePath = join21(projectRoot, "decantr.essence.json");
3427
+ if (!existsSync20(essencePath)) {
2974
3428
  console.error(`${RED6}No decantr.essence.json found. Run \`decantr init\` first.${RESET7}`);
2975
3429
  process.exitCode = 1;
2976
3430
  return;
2977
3431
  }
2978
3432
  let essence;
2979
3433
  try {
2980
- const raw = readFileSync13(essencePath, "utf-8");
3434
+ const raw = readFileSync14(essencePath, "utf-8");
2981
3435
  const parsed = JSON.parse(raw);
2982
3436
  if (!isV33(parsed)) {
2983
3437
  console.error(`${RED6}Essence is not v3. Run \`decantr migrate\` first.${RESET7}`);
@@ -2991,7 +3445,7 @@ async function cmdRefresh(projectRoot = process.cwd(), options = {}) {
2991
3445
  return;
2992
3446
  }
2993
3447
  const registryClient = new RegistryClient({
2994
- cacheDir: join20(projectRoot, ".decantr", "cache"),
3448
+ cacheDir: join21(projectRoot, ".decantr", "cache"),
2995
3449
  offline: options.offline
2996
3450
  });
2997
3451
  console.log("Regenerating derived files...\n");
@@ -3011,8 +3465,8 @@ async function cmdRefresh(projectRoot = process.cwd(), options = {}) {
3011
3465
  }
3012
3466
 
3013
3467
  // src/commands/registry-mirror.ts
3014
- import { mkdirSync as mkdirSync8, writeFileSync as writeFileSync8 } from "fs";
3015
- import { join as join21 } from "path";
3468
+ import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync9 } from "fs";
3469
+ import { join as join22 } from "path";
3016
3470
  import { API_CONTENT_TYPES, RegistryAPIClient as RegistryAPIClient2 } from "@decantr/registry";
3017
3471
  var GREEN8 = "\x1B[32m";
3018
3472
  var RED7 = "\x1B[31m";
@@ -3040,7 +3494,7 @@ async function cmdRegistryMirror(projectRoot, options = {}) {
3040
3494
  process.exitCode = 1;
3041
3495
  return;
3042
3496
  }
3043
- const cacheDir = join21(projectRoot, ".decantr", "cache");
3497
+ const cacheDir = join22(projectRoot, ".decantr", "cache");
3044
3498
  const counts = {};
3045
3499
  const failed = [];
3046
3500
  console.log(`
@@ -3050,19 +3504,19 @@ Mirroring registry content to ${DIM8}.decantr/cache/${RESET8}
3050
3504
  try {
3051
3505
  const result = await apiClient.listContent(type, { namespace: "@official" });
3052
3506
  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));
3507
+ const typeDir = join22(cacheDir, "@official", type);
3508
+ mkdirSync9(typeDir, { recursive: true });
3509
+ writeFileSync9(join22(typeDir, "index.json"), JSON.stringify(result, null, 2));
3056
3510
  let itemCount = 0;
3057
3511
  for (const item of items) {
3058
3512
  const slug = item.slug || item.id;
3059
3513
  if (!slug) continue;
3060
3514
  try {
3061
3515
  const fullItem = await apiClient.getContent(type, "@official", slug);
3062
- writeFileSync8(join21(typeDir, `${slug}.json`), JSON.stringify(fullItem, null, 2));
3516
+ writeFileSync9(join22(typeDir, `${slug}.json`), JSON.stringify(fullItem, null, 2));
3063
3517
  itemCount++;
3064
3518
  } catch {
3065
- writeFileSync8(join21(typeDir, `${slug}.json`), JSON.stringify(item, null, 2));
3519
+ writeFileSync9(join22(typeDir, `${slug}.json`), JSON.stringify(item, null, 2));
3066
3520
  itemCount++;
3067
3521
  }
3068
3522
  }
@@ -3077,8 +3531,8 @@ Mirroring registry content to ${DIM8}.decantr/cache/${RESET8}
3077
3531
  mirrored_at: (/* @__PURE__ */ new Date()).toISOString(),
3078
3532
  counts
3079
3533
  };
3080
- mkdirSync8(join21(cacheDir), { recursive: true });
3081
- writeFileSync8(join21(cacheDir, "mirror-manifest.json"), JSON.stringify(manifest, null, 2));
3534
+ mkdirSync9(join22(cacheDir), { recursive: true });
3535
+ writeFileSync9(join22(cacheDir, "mirror-manifest.json"), JSON.stringify(manifest, null, 2));
3082
3536
  const totalItems = Object.values(counts).reduce((a, b) => a + b, 0);
3083
3537
  console.log("");
3084
3538
  if (failed.length > 0) {
@@ -3095,23 +3549,23 @@ Mirroring registry content to ${DIM8}.decantr/cache/${RESET8}
3095
3549
  }
3096
3550
 
3097
3551
  // 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";
3552
+ import { existsSync as existsSync22, readFileSync as readFileSync15, rmSync as rmSync2, writeFileSync as writeFileSync10 } from "fs";
3553
+ import { join as join23 } from "path";
3100
3554
  import { isV3 as isV34, migrateV30ToV31 as migrateV30ToV312 } from "@decantr/essence-spec";
3101
3555
  var GREEN9 = "\x1B[32m";
3102
3556
  var RED8 = "\x1B[31m";
3103
3557
  var DIM9 = "\x1B[2m";
3104
3558
  var RESET9 = "\x1B[0m";
3105
3559
  function readAndMigrate2(projectRoot) {
3106
- const essencePath = join22(projectRoot, "decantr.essence.json");
3107
- if (!existsSync21(essencePath)) {
3560
+ const essencePath = join23(projectRoot, "decantr.essence.json");
3561
+ if (!existsSync22(essencePath)) {
3108
3562
  console.error(`${RED8}No decantr.essence.json found. Run \`decantr init\` first.${RESET9}`);
3109
3563
  process.exitCode = 1;
3110
3564
  return null;
3111
3565
  }
3112
3566
  let parsed;
3113
3567
  try {
3114
- parsed = JSON.parse(readFileSync14(essencePath, "utf-8"));
3568
+ parsed = JSON.parse(readFileSync15(essencePath, "utf-8"));
3115
3569
  } catch (e) {
3116
3570
  console.error(`${RED8}Could not read essence: ${e.message}${RESET9}`);
3117
3571
  process.exitCode = 1;
@@ -3126,7 +3580,7 @@ function readAndMigrate2(projectRoot) {
3126
3580
  return { essence, essencePath };
3127
3581
  }
3128
3582
  function writeEssence2(essencePath, essence) {
3129
- writeFileSync9(essencePath, JSON.stringify(essence, null, 2) + "\n");
3583
+ writeFileSync10(essencePath, JSON.stringify(essence, null, 2) + "\n");
3130
3584
  }
3131
3585
  function recomputeGlobalFeatures(essence) {
3132
3586
  const all = /* @__PURE__ */ new Set();
@@ -3168,14 +3622,14 @@ async function cmdRemoveSection(sectionId, args, projectRoot = process.cwd()) {
3168
3622
  sections.splice(idx, 1);
3169
3623
  recomputeGlobalFeatures(essence);
3170
3624
  removeRoutes(essence, sectionId);
3171
- const contextFile = join22(projectRoot, ".decantr", "context", `${sectionId}.md`);
3172
- if (existsSync21(contextFile)) {
3625
+ const contextFile = join23(projectRoot, ".decantr", "context", `${sectionId}.md`);
3626
+ if (existsSync22(contextFile)) {
3173
3627
  rmSync2(contextFile);
3174
3628
  }
3175
3629
  writeEssence2(essencePath, essence);
3176
3630
  console.log(`${GREEN9}Removed section "${sectionId}".${RESET9}`);
3177
3631
  const registryClient = new RegistryClient({
3178
- cacheDir: join22(projectRoot, ".decantr", "cache")
3632
+ cacheDir: join23(projectRoot, ".decantr", "cache")
3179
3633
  });
3180
3634
  await refreshDerivedFiles(projectRoot, essence, registryClient);
3181
3635
  console.log(`${GREEN9}Derived files refreshed.${RESET9}`);
@@ -3211,7 +3665,7 @@ async function cmdRemovePage(path, args, projectRoot = process.cwd()) {
3211
3665
  writeEssence2(essencePath, essence);
3212
3666
  console.log(`${GREEN9}Removed page "${pageId}" from section "${sectionId}".${RESET9}`);
3213
3667
  const registryClient = new RegistryClient({
3214
- cacheDir: join22(projectRoot, ".decantr", "cache")
3668
+ cacheDir: join23(projectRoot, ".decantr", "cache")
3215
3669
  });
3216
3670
  await refreshDerivedFiles(projectRoot, essence, registryClient);
3217
3671
  console.log(`${GREEN9}Derived files refreshed.${RESET9}`);
@@ -3252,15 +3706,15 @@ async function cmdRemoveFeature(feature, args, projectRoot = process.cwd()) {
3252
3706
  const target = sectionId ? `section "${sectionId}" and global` : "global";
3253
3707
  console.log(`${GREEN9}Removed feature "${feature}" from ${target} features.${RESET9}`);
3254
3708
  const registryClient = new RegistryClient({
3255
- cacheDir: join22(projectRoot, ".decantr", "cache")
3709
+ cacheDir: join23(projectRoot, ".decantr", "cache")
3256
3710
  });
3257
3711
  await refreshDerivedFiles(projectRoot, essence, registryClient);
3258
3712
  console.log(`${GREEN9}Derived files refreshed.${RESET9}`);
3259
3713
  }
3260
3714
 
3261
3715
  // 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";
3716
+ import { existsSync as existsSync23, readFileSync as readFileSync16, writeFileSync as writeFileSync11 } from "fs";
3717
+ import { join as join24 } from "path";
3264
3718
  var GREEN10 = "\x1B[32m";
3265
3719
  var RED9 = "\x1B[31m";
3266
3720
  var YELLOW6 = "\x1B[33m";
@@ -3269,8 +3723,8 @@ var DIM10 = "\x1B[2m";
3269
3723
  var BOLD4 = "\x1B[1m";
3270
3724
  var CYAN5 = "\x1B[36m";
3271
3725
  async function cmdSyncDrift(projectRoot = process.cwd()) {
3272
- const driftLogPath = join23(projectRoot, ".decantr", "drift-log.json");
3273
- if (!existsSync22(driftLogPath)) {
3726
+ const driftLogPath = join24(projectRoot, ".decantr", "drift-log.json");
3727
+ if (!existsSync23(driftLogPath)) {
3274
3728
  console.log(`${GREEN10}No drift log found \u2014 no drift recorded.${RESET10}`);
3275
3729
  console.log(
3276
3730
  `${DIM10}Drift is logged when guard violations are detected during development.${RESET10}`
@@ -3279,7 +3733,7 @@ async function cmdSyncDrift(projectRoot = process.cwd()) {
3279
3733
  }
3280
3734
  let entries;
3281
3735
  try {
3282
- entries = JSON.parse(readFileSync15(driftLogPath, "utf-8"));
3736
+ entries = JSON.parse(readFileSync16(driftLogPath, "utf-8"));
3283
3737
  } catch {
3284
3738
  console.error(`${RED9}Could not parse drift-log.json${RESET10}`);
3285
3739
  process.exitCode = 1;
@@ -3325,13 +3779,13 @@ ${BOLD4}Unresolved Drift Entries (${unresolved.length})${RESET10}
3325
3779
  console.log("");
3326
3780
  }
3327
3781
  function resolveDriftEntries(projectRoot, options) {
3328
- const driftLogPath = join23(projectRoot, ".decantr", "drift-log.json");
3329
- if (!existsSync22(driftLogPath)) {
3782
+ const driftLogPath = join24(projectRoot, ".decantr", "drift-log.json");
3783
+ if (!existsSync23(driftLogPath)) {
3330
3784
  return { success: true };
3331
3785
  }
3332
3786
  if (options.clear) {
3333
3787
  try {
3334
- writeFileSync10(driftLogPath, "[]");
3788
+ writeFileSync11(driftLogPath, "[]");
3335
3789
  return { success: true };
3336
3790
  } catch (e) {
3337
3791
  return { success: false, error: `Could not clear drift log: ${e.message}` };
@@ -3339,7 +3793,7 @@ function resolveDriftEntries(projectRoot, options) {
3339
3793
  }
3340
3794
  let entries;
3341
3795
  try {
3342
- entries = JSON.parse(readFileSync15(driftLogPath, "utf-8"));
3796
+ entries = JSON.parse(readFileSync16(driftLogPath, "utf-8"));
3343
3797
  } catch {
3344
3798
  return { success: false, error: "Could not parse drift-log.json" };
3345
3799
  }
@@ -3358,7 +3812,7 @@ function resolveDriftEntries(projectRoot, options) {
3358
3812
  }
3359
3813
  }
3360
3814
  try {
3361
- writeFileSync10(driftLogPath, JSON.stringify(entries, null, 2));
3815
+ writeFileSync11(driftLogPath, JSON.stringify(entries, null, 2));
3362
3816
  return { success: true };
3363
3817
  } catch (e) {
3364
3818
  return { success: false, error: `Could not write drift log: ${e.message}` };
@@ -3366,8 +3820,8 @@ function resolveDriftEntries(projectRoot, options) {
3366
3820
  }
3367
3821
 
3368
3822
  // 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";
3823
+ import { existsSync as existsSync24, readFileSync as readFileSync17, writeFileSync as writeFileSync12 } from "fs";
3824
+ import { join as join25 } from "path";
3371
3825
  import { isV3 as isV35, migrateV30ToV31 as migrateV30ToV313 } from "@decantr/essence-spec";
3372
3826
  var GREEN11 = "\x1B[32m";
3373
3827
  var RED10 = "\x1B[31m";
@@ -3384,15 +3838,15 @@ async function cmdThemeSwitch(themeName, args, projectRoot = process.cwd()) {
3384
3838
  process.exitCode = 1;
3385
3839
  return;
3386
3840
  }
3387
- const essencePath = join24(projectRoot, "decantr.essence.json");
3388
- if (!existsSync23(essencePath)) {
3841
+ const essencePath = join25(projectRoot, "decantr.essence.json");
3842
+ if (!existsSync24(essencePath)) {
3389
3843
  console.error(`${RED10}No decantr.essence.json found. Run \`decantr init\` first.${RESET11}`);
3390
3844
  process.exitCode = 1;
3391
3845
  return;
3392
3846
  }
3393
3847
  let parsed;
3394
3848
  try {
3395
- parsed = JSON.parse(readFileSync16(essencePath, "utf-8"));
3849
+ parsed = JSON.parse(readFileSync17(essencePath, "utf-8"));
3396
3850
  } catch (e) {
3397
3851
  console.error(`${RED10}Could not read essence: ${e.message}${RESET11}`);
3398
3852
  process.exitCode = 1;
@@ -3441,7 +3895,7 @@ async function cmdThemeSwitch(themeName, args, projectRoot = process.cwd()) {
3441
3895
  essence.dna.theme.mode = mode;
3442
3896
  }
3443
3897
  const registryClient = new RegistryClient({
3444
- cacheDir: join24(projectRoot, ".decantr", "cache")
3898
+ cacheDir: join25(projectRoot, ".decantr", "cache")
3445
3899
  });
3446
3900
  try {
3447
3901
  const themeResult = await registryClient.fetchTheme(themeName);
@@ -3456,7 +3910,7 @@ async function cmdThemeSwitch(themeName, args, projectRoot = process.cwd()) {
3456
3910
  }
3457
3911
  } catch {
3458
3912
  }
3459
- writeFileSync11(essencePath, JSON.stringify(essence, null, 2) + "\n");
3913
+ writeFileSync12(essencePath, JSON.stringify(essence, null, 2) + "\n");
3460
3914
  console.log(`${GREEN11}Switched theme: ${oldThemeId} \u2192 ${themeName}${RESET11}`);
3461
3915
  if (shape) console.log(` ${DIM11}Shape: ${shape}${RESET11}`);
3462
3916
  if (mode) console.log(` ${DIM11}Mode: ${mode}${RESET11}`);
@@ -3478,10 +3932,10 @@ var CYAN6 = "\x1B[36m";
3478
3932
  function ask(question, defaultValue) {
3479
3933
  const rl = createInterface({ input: process.stdin, output: process.stdout });
3480
3934
  const prompt = defaultValue ? `${question} ${DIM12}(${defaultValue})${RESET12}: ` : `${question}: `;
3481
- return new Promise((resolve4) => {
3935
+ return new Promise((resolve5) => {
3482
3936
  rl.question(prompt, (answer) => {
3483
3937
  rl.close();
3484
- resolve4(answer.trim() || defaultValue || "");
3938
+ resolve5(answer.trim() || defaultValue || "");
3485
3939
  });
3486
3940
  });
3487
3941
  }
@@ -3663,7 +4117,11 @@ async function runInteractivePrompts(detected, archetypes, blueprints, themes, w
3663
4117
  personality: ["professional"],
3664
4118
  features: [],
3665
4119
  existing: workflowSeed?.existing || detected.existingEssence,
3666
- workflowMode: workflowSeed?.workflowMode
4120
+ workflowMode: workflowSeed?.workflowMode,
4121
+ adoptionMode: workflowSeed?.adoptionMode,
4122
+ contentSource: workflowSeed?.contentSource,
4123
+ assistantBridge: workflowSeed?.assistantBridge,
4124
+ projectScope: workflowSeed?.projectScope
3667
4125
  };
3668
4126
  }
3669
4127
  function parseFlags(args, detected) {
@@ -3685,6 +4143,12 @@ function parseFlags(args, detected) {
3685
4143
  if (typeof args.features === "string")
3686
4144
  options.features = args.features.split(",").map((s) => s.trim());
3687
4145
  if (args.existing === true) options.existing = true;
4146
+ if (args.adoption === "contract-only" || args.adoption === "style-bridge" || args.adoption === "decantr-css") {
4147
+ options.adoptionMode = args.adoption;
4148
+ }
4149
+ if (args["assistant-bridge"] === "none" || args["assistant-bridge"] === "preview" || args["assistant-bridge"] === "apply") {
4150
+ options.assistantBridge = args["assistant-bridge"];
4151
+ }
3688
4152
  return options;
3689
4153
  }
3690
4154
  function mergeWithDefaults(flags, detected, workflowSeed) {
@@ -3701,12 +4165,16 @@ function mergeWithDefaults(flags, detected, workflowSeed) {
3701
4165
  personality: flags.personality || ["professional"],
3702
4166
  features: flags.features || [],
3703
4167
  existing: flags.existing || workflowSeed?.existing || detected.existingEssence,
3704
- workflowMode: flags.workflowMode || workflowSeed?.workflowMode
4168
+ workflowMode: flags.workflowMode || workflowSeed?.workflowMode,
4169
+ adoptionMode: flags.adoptionMode || workflowSeed?.adoptionMode,
4170
+ contentSource: flags.contentSource || workflowSeed?.contentSource,
4171
+ assistantBridge: flags.assistantBridge || workflowSeed?.assistantBridge,
4172
+ projectScope: flags.projectScope || workflowSeed?.projectScope
3705
4173
  };
3706
4174
  }
3707
4175
  async function runSimplifiedInit(blueprints) {
3708
4176
  const rl = createInterface({ input: process.stdin, output: process.stdout });
3709
- const question = (q) => new Promise((resolve4) => rl.question(q, resolve4));
4177
+ const question = (q) => new Promise((resolve5) => rl.question(q, resolve5));
3710
4178
  console.log("\n? What blueprint would you like to scaffold?\n");
3711
4179
  console.log(" 1. Decantr default (recommended)");
3712
4180
  console.log(" 2. Search registry...\n");
@@ -3738,8 +4206,8 @@ async function runSimplifiedInit(blueprints) {
3738
4206
  }
3739
4207
 
3740
4208
  // 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";
4209
+ import { existsSync as existsSync25, mkdirSync as mkdirSync10, readdirSync as readdirSync5, readFileSync as readFileSync18, rmSync as rmSync3, writeFileSync as writeFileSync13 } from "fs";
4210
+ import { join as join26 } from "path";
3743
4211
  var REQUIRED_FIELDS = [
3744
4212
  "$schema",
3745
4213
  "id",
@@ -3799,20 +4267,20 @@ function validateCustomTheme(theme) {
3799
4267
  };
3800
4268
  }
3801
4269
  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)) {
4270
+ const customThemesDir = join26(projectRoot, ".decantr", "custom", "themes");
4271
+ const themePath = join26(customThemesDir, `${id}.json`);
4272
+ const howToPath = join26(customThemesDir, "how-to-theme.md");
4273
+ mkdirSync10(customThemesDir, { recursive: true });
4274
+ if (existsSync25(themePath)) {
3807
4275
  return {
3808
4276
  success: false,
3809
4277
  error: `Theme "${id}" already exists at ${themePath}`
3810
4278
  };
3811
4279
  }
3812
4280
  const skeleton = getThemeSkeleton(id, name);
3813
- writeFileSync12(themePath, JSON.stringify(skeleton, null, 2));
3814
- if (!existsSync24(howToPath)) {
3815
- writeFileSync12(howToPath, getHowToThemeDoc());
4281
+ writeFileSync13(themePath, JSON.stringify(skeleton, null, 2));
4282
+ if (!existsSync25(howToPath)) {
4283
+ writeFileSync13(howToPath, getHowToThemeDoc());
3816
4284
  }
3817
4285
  return {
3818
4286
  success: true,
@@ -3820,17 +4288,17 @@ function createTheme(projectRoot, id, name) {
3820
4288
  };
3821
4289
  }
3822
4290
  function listCustomThemes(projectRoot) {
3823
- const customThemesDir = join25(projectRoot, ".decantr", "custom", "themes");
3824
- if (!existsSync24(customThemesDir)) {
4291
+ const customThemesDir = join26(projectRoot, ".decantr", "custom", "themes");
4292
+ if (!existsSync25(customThemesDir)) {
3825
4293
  return [];
3826
4294
  }
3827
4295
  const themes = [];
3828
4296
  try {
3829
4297
  const files = readdirSync5(customThemesDir).filter((f) => f.endsWith(".json"));
3830
4298
  for (const file of files) {
3831
- const filePath = join25(customThemesDir, file);
4299
+ const filePath = join26(customThemesDir, file);
3832
4300
  try {
3833
- const data = JSON.parse(readFileSync17(filePath, "utf-8"));
4301
+ const data = JSON.parse(readFileSync18(filePath, "utf-8"));
3834
4302
  themes.push({
3835
4303
  id: data.id || file.replace(".json", ""),
3836
4304
  name: data.name || data.id,
@@ -3845,8 +4313,8 @@ function listCustomThemes(projectRoot) {
3845
4313
  return themes;
3846
4314
  }
3847
4315
  function deleteTheme(projectRoot, id) {
3848
- const themePath = join25(projectRoot, ".decantr", "custom", "themes", `${id}.json`);
3849
- if (!existsSync24(themePath)) {
4316
+ const themePath = join26(projectRoot, ".decantr", "custom", "themes", `${id}.json`);
4317
+ if (!existsSync25(themePath)) {
3850
4318
  return {
3851
4319
  success: false,
3852
4320
  error: `Theme "${id}" not found at ${themePath}`
@@ -3863,7 +4331,7 @@ function deleteTheme(projectRoot, id) {
3863
4331
  }
3864
4332
  }
3865
4333
  function importTheme(projectRoot, sourcePath) {
3866
- if (!existsSync24(sourcePath)) {
4334
+ if (!existsSync25(sourcePath)) {
3867
4335
  return {
3868
4336
  success: false,
3869
4337
  errors: [`Source file not found: ${sourcePath}`]
@@ -3871,7 +4339,7 @@ function importTheme(projectRoot, sourcePath) {
3871
4339
  }
3872
4340
  let theme;
3873
4341
  try {
3874
- theme = JSON.parse(readFileSync17(sourcePath, "utf-8"));
4342
+ theme = JSON.parse(readFileSync18(sourcePath, "utf-8"));
3875
4343
  } catch (e) {
3876
4344
  return {
3877
4345
  success: false,
@@ -3887,20 +4355,90 @@ function importTheme(projectRoot, sourcePath) {
3887
4355
  }
3888
4356
  theme.source = "custom";
3889
4357
  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));
4358
+ const customThemesDir = join26(projectRoot, ".decantr", "custom", "themes");
4359
+ const destPath = join26(customThemesDir, `${id}.json`);
4360
+ mkdirSync10(customThemesDir, { recursive: true });
4361
+ const howToPath = join26(customThemesDir, "how-to-theme.md");
4362
+ if (!existsSync25(howToPath)) {
4363
+ writeFileSync13(howToPath, getHowToThemeDoc());
4364
+ }
4365
+ writeFileSync13(destPath, JSON.stringify(theme, null, 2));
3898
4366
  return {
3899
4367
  success: true,
3900
4368
  path: destPath
3901
4369
  };
3902
4370
  }
3903
4371
 
4372
+ // src/workspace.ts
4373
+ import { existsSync as existsSync26, readdirSync as readdirSync6, readFileSync as readFileSync19 } from "fs";
4374
+ import { dirname as dirname3, join as join27, resolve as resolve3 } from "path";
4375
+ function readPackageJson(dir) {
4376
+ const path = join27(dir, "package.json");
4377
+ if (!existsSync26(path)) return null;
4378
+ try {
4379
+ return JSON.parse(readFileSync19(path, "utf-8"));
4380
+ } catch {
4381
+ return null;
4382
+ }
4383
+ }
4384
+ function hasWorkspaceMarker(dir) {
4385
+ if (existsSync26(join27(dir, "pnpm-workspace.yaml")) || existsSync26(join27(dir, "turbo.json")) || existsSync26(join27(dir, "nx.json"))) {
4386
+ return true;
4387
+ }
4388
+ const pkg = readPackageJson(dir);
4389
+ return Boolean(pkg?.workspaces);
4390
+ }
4391
+ function findWorkspaceRoot(startDir) {
4392
+ let current = resolve3(startDir);
4393
+ while (true) {
4394
+ if (hasWorkspaceMarker(current)) return current;
4395
+ const parent = dirname3(current);
4396
+ if (parent === current) return null;
4397
+ current = parent;
4398
+ }
4399
+ }
4400
+ function looksLikeApp(dir) {
4401
+ 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"))) {
4402
+ return true;
4403
+ }
4404
+ const pkg = readPackageJson(dir);
4405
+ const deps = { ...pkg?.dependencies, ...pkg?.devDependencies };
4406
+ return Boolean(
4407
+ deps.react || deps.next || deps.vue || deps.svelte || deps["@angular/core"] || deps.astro || deps.nuxt
4408
+ );
4409
+ }
4410
+ function listWorkspaceApps(workspaceRoot) {
4411
+ const candidates = [];
4412
+ for (const base of ["apps", "packages"]) {
4413
+ const baseDir = join27(workspaceRoot, base);
4414
+ if (!existsSync26(baseDir)) continue;
4415
+ for (const entry of readdirSync6(baseDir, { withFileTypes: true })) {
4416
+ if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
4417
+ const candidate = join27(baseDir, entry.name);
4418
+ if (looksLikeApp(candidate)) {
4419
+ candidates.push(`${base}/${entry.name}`);
4420
+ }
4421
+ }
4422
+ }
4423
+ return candidates.sort();
4424
+ }
4425
+ function resolveWorkspaceInfo(cwd, projectArg) {
4426
+ const absoluteCwd = resolve3(cwd);
4427
+ const workspaceRoot = findWorkspaceRoot(absoluteCwd) ?? absoluteCwd;
4428
+ const appRoot = projectArg ? resolve3(absoluteCwd, projectArg) : absoluteCwd;
4429
+ const appCandidates = listWorkspaceApps(workspaceRoot);
4430
+ const projectScope = workspaceRoot !== appRoot || appCandidates.length > 0 ? "workspace-app" : "single-app";
4431
+ const requiresProjectSelection = !projectArg && workspaceRoot === absoluteCwd && appCandidates.length > 1;
4432
+ return {
4433
+ cwd: absoluteCwd,
4434
+ workspaceRoot,
4435
+ appRoot,
4436
+ projectScope,
4437
+ appCandidates,
4438
+ requiresProjectSelection
4439
+ };
4440
+ }
4441
+
3904
4442
  // src/index.ts
3905
4443
  var BOLD6 = "\x1B[1m";
3906
4444
  var DIM13 = "\x1B[2m";
@@ -3997,8 +4535,19 @@ function extractPatternName(item) {
3997
4535
  }
3998
4536
  function generateGreenfieldPrompt(ctx) {
3999
4537
  const lines = [];
4538
+ const usesDecantrCss = ctx.adoptionMode === "decantr-css" || !ctx.adoptionMode;
4000
4539
  lines.push("Build this greenfield application using the Decantr design system.");
4001
4540
  lines.push("");
4541
+ if (ctx.blueprint) lines.push(`Blueprint: ${ctx.blueprint}`);
4542
+ if (ctx.archetype) lines.push(`Primary archetype: ${ctx.archetype}`);
4543
+ if (ctx.theme) lines.push(`Theme: ${ctx.theme}${ctx.mode ? ` (${ctx.mode})` : ""}`);
4544
+ if (ctx.target) lines.push(`Target: ${ctx.target}`);
4545
+ if (ctx.pages.length > 0) {
4546
+ lines.push(
4547
+ `Routes/pages: ${ctx.pages.map((page) => `${page.sectionId ? `${page.sectionId}/` : ""}${page.id}:${page.shell}`).join(", ")}`
4548
+ );
4549
+ }
4550
+ lines.push("");
4002
4551
  lines.push(
4003
4552
  "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
4553
  );
@@ -4013,210 +4562,201 @@ function generateGreenfieldPrompt(ctx) {
4013
4562
  lines.push("");
4014
4563
  lines.push("Read in this order:");
4015
4564
  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.'
4565
+ "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
4566
  );
4018
4567
  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.'
4568
+ "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
4569
  );
4021
4570
  lines.push(
4022
- "3. .decantr/context/scaffold.md for the broader app overview, topology, route map, and voice guidance."
4571
+ "3. Before route work, read the matching .decantr/context/page-*-pack.md file. Its pattern layout and interaction checklists are contract."
4023
4572
  );
4024
4573
  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).'
4574
+ "4. .decantr/context/scaffold.md for broader topology, route map, and voice guidance after the compact packs are understood."
4026
4575
  );
4027
4576
  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.'
4577
+ "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
4578
  );
4030
4579
  lines.push("");
4031
4580
  lines.push("\u2550\u2550\u2550 INTERACTIONS ARE CONTRACT, NOT GUIDANCE \u2550\u2550\u2550");
4032
4581
  lines.push("");
4033
4582
  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"
4583
+ '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.'
4038
4584
  );
4039
- lines.push("- status-pulse \u2192 d-pulse class on the indicator element");
4040
4585
  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"
4586
+ "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."
4060
4587
  );
4061
4588
  lines.push("");
4062
4589
  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."
4590
+ "`decantr check --strict` fails when a declared interaction has no matching implementation."
4064
4591
  );
4065
4592
  lines.push("");
4066
- lines.push("\u2550\u2550\u2550 ATOMS-FIRST FOR LAYOUT \u2014 DO NOT INLINE-STYLE \u2550\u2550\u2550");
4593
+ lines.push("\u2550\u2550\u2550 STYLING ADOPTION \u2550\u2550\u2550");
4067
4594
  lines.push("");
4068
- 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", ... }}`.'
4070
- );
4071
- 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
- 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")}'
4099
- );
4100
- 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
- );
4104
- lines.push("");
4105
- lines.push(
4106
- 'Combine atoms with treatment / decorator strings: `className={css("_flex _col _gap4") + " d-card clean-card"}`.'
4107
- );
4595
+ if (ctx.adoptionMode === "contract-only") {
4596
+ lines.push(
4597
+ "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."
4598
+ );
4599
+ lines.push(
4600
+ "Do not install @decantr/css or add Decantr style files unless the adoption mode changes."
4601
+ );
4602
+ } else if (ctx.adoptionMode === "style-bridge") {
4603
+ lines.push(
4604
+ "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."
4605
+ );
4606
+ } else {
4607
+ lines.push(
4608
+ "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."
4609
+ );
4610
+ }
4611
+ if (usesDecantrCss) {
4612
+ lines.push("");
4613
+ lines.push("Use these canonical compact atom shapes:");
4614
+ lines.push(
4615
+ "- Layout: _flex, _col, _aic, _jcc, _jcsb, _grid, _gc3, _gc[2fr_1fr], _gap4, _wrap"
4616
+ );
4617
+ lines.push(
4618
+ "- Spacing/sizing: _p4, _py4, _px6, _wfull, _maxw[40rem], _mxauto, _h[20rem]"
4619
+ );
4620
+ lines.push(
4621
+ "- Position/type: _rel, _abs, _sticky, _top0, _text2xl, _textlg, _fgmuted"
4622
+ );
4623
+ lines.push("- Responsive: _sm:gc2, _lg:gc3, _mdmax:p4, _lg:gc[1.05fr_1fr]");
4624
+ lines.push("");
4625
+ lines.push(
4626
+ "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]`."
4627
+ );
4628
+ lines.push("");
4629
+ lines.push(
4630
+ 'Combine atoms with treatment / decorator strings: `className={css("_flex _col _gap4") + " d-card clean-card"}`.'
4631
+ );
4632
+ } else {
4633
+ lines.push("");
4634
+ lines.push(
4635
+ "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."
4636
+ );
4637
+ }
4108
4638
  lines.push("");
4109
4639
  lines.push("Inline `style={{...}}` is ONLY acceptable for:");
4110
4640
  lines.push(
4111
- ' 1. CSS custom-property writes the contract REQUIRES (`style={{ "--d-stagger-index": i }}`, `style={{ "--lum-card-color": accent }}`, etc.)'
4641
+ " 1. CSS custom-property writes the contract requires (`--d-stagger-index`, theme color vars, etc.)"
4112
4642
  );
4113
4643
  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)."
4644
+ " 2. Truly dynamic geometry no atom can express (computed transforms, drag positions, live chart geometry)."
4115
4645
  );
4116
4646
  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
- );
4647
+ if (usesDecantrCss) {
4648
+ lines.push(
4649
+ "If a component accumulates static inline visual styles, migrate them to atoms, treatments, decorators, or CSS vars. `decantr check` flags inline-style drift."
4650
+ );
4651
+ } else {
4652
+ lines.push(
4653
+ "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."
4654
+ );
4655
+ }
4120
4656
  lines.push("");
4121
4657
  lines.push("\u2550\u2550\u2550 TREATMENT SURFACE \u2014 USE WHAT EXISTS \u2550\u2550\u2550");
4122
4658
  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
- );
4659
+ if (usesDecantrCss) {
4660
+ lines.push(
4661
+ "60+ treatment classes ship in src/styles/treatments.css. Reach for these before inventing CSS:"
4662
+ );
4663
+ lines.push(
4664
+ "- Shells: d-shell + data-layout, d-shell-sidebar/main/aside/header/body/footer, d-shell-mobile-trigger/backdrop"
4665
+ );
4666
+ lines.push(
4667
+ "- Core UI: d-interactive, d-icon-btn, d-nav-link, d-step-chip, d-control, d-card, d-data, d-label, d-annotation"
4668
+ );
4669
+ lines.push(
4670
+ "- Overlays: d-modal, d-modal-backdrop, d-modal-panel, d-palette, d-tooltip, d-popover"
4671
+ );
4672
+ lines.push(
4673
+ "- 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"
4674
+ );
4675
+ } else {
4676
+ lines.push(
4677
+ "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."
4678
+ );
4679
+ }
4155
4680
  lines.push("");
4156
4681
  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."
4682
+ "Consult DECANTR.md only when you need the full table or exact data-* attributes."
4158
4683
  );
4159
4684
  lines.push("");
4160
4685
  lines.push("\u2550\u2550\u2550 THEME DECORATOR CONTRACT \u2014 APPLY OR THE THEME DOES NOT LAND \u2550\u2550\u2550");
4161
4686
  lines.push("");
4162
4687
  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."'
4688
+ '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
4689
  );
4165
4690
  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).'
4691
+ "Section packs may point back to the scaffold-pack table; scaffold-pack.md is authoritative."
4167
4692
  );
4168
4693
  lines.push("");
4169
4694
  lines.push("\u2550\u2550\u2550 HARD RULES (NON-NEGOTIABLE) \u2550\u2550\u2550");
4170
4695
  lines.push("");
4696
+ if (usesDecantrCss) {
4697
+ lines.push(
4698
+ '- Auth pages use `d-shell[data-layout="centered"]` with `d-shell-centered-card` around the form.'
4699
+ );
4700
+ lines.push(
4701
+ '- Command palette uses `d-modal[data-align="top"]` + `d-modal-backdrop` + `d-palette`; rows include Lucide icon, label, and d-kbd hotkey.'
4702
+ );
4703
+ } else {
4704
+ lines.push("- Auth pages use a centered shell with a focused centered-card form surface.");
4705
+ lines.push(
4706
+ "- Command palette uses an accessible modal/palette structure; rows include Lucide icon, label, and keyboard hint where the product contract calls for it."
4707
+ );
4708
+ }
4171
4709
  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.'
4710
+ "- 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
4711
  );
4174
4712
  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].'
4713
+ "- Section Directives in section packs are execution rules for layout proportions, treatment stacks, copy conventions, and pattern fitness."
4176
4714
  );
4177
4715
  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."
4716
+ "- Filter chip rows / tab strips use `d-step-chip[data-step-state]`, not bare `d-interactive` buttons."
4179
4717
  );
4180
4718
  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."
4719
+ "- 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
4720
  );
4183
4721
  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."
4722
+ "- Prevent layout collisions: hero content, CTA banners, cards, footers, and sticky chrome must not overlap or clip at desktop or mobile widths."
4185
4723
  );
4186
4724
  lines.push("");
4187
4725
  lines.push("\u2550\u2550\u2550 IMPLEMENTATION RULES \u2550\u2550\u2550");
4188
4726
  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."
4727
+ "- Do not invent routes, sections, shells, themes, or features beyond the compiled packs."
4193
4728
  );
4729
+ lines.push("- Prefer scaffold-pack, section-pack, and page-pack guidance over narrative docs.");
4194
4730
  lines.push(
4195
4731
  "- Start with the shell layouts and route structure first, then build section pages route by route."
4196
4732
  );
4733
+ if (ctx.adoptionMode === "decantr-css") {
4734
+ lines.push("- Import src/styles/global.css, src/styles/tokens.css, and src/styles/treatments.css.");
4735
+ } else if (ctx.adoptionMode === "style-bridge") {
4736
+ lines.push("- Import src/styles/tokens.css and src/styles/decantr-bridge.css where appropriate.");
4737
+ } else {
4738
+ lines.push("- Keep styling imports aligned with the selected runtime; Decantr does not own CSS here.");
4739
+ }
4197
4740
  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."
4741
+ 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
4742
  );
4203
4743
  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."
4744
+ "- If package.json, app entry files, or router/runtime files are absent, create them for the declared target."
4205
4745
  );
4206
4746
  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."
4747
+ 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
4748
  );
4209
4749
  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."
4750
+ "- Let shells own spacing, centering, and scroll containers unless the route contract says otherwise."
4211
4751
  );
4212
4752
  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."
4753
+ "- If command_palette or hotkeys are declared, implement them as real features rather than visible copy."
4214
4754
  );
4215
4755
  lines.push(
4216
4756
  "- 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
4757
  );
4218
4758
  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."
4759
+ "- If a required decorator class is missing from generated CSS, report the contract gap instead of inventing a parallel system."
4220
4760
  );
4221
4761
  lines.push(
4222
4762
  "- Do not modify generated context files unless the task is explicitly to regenerate or refresh Decantr context."
@@ -4226,7 +4766,7 @@ function generateGreenfieldPrompt(ctx) {
4226
4766
  lines.push("- Build the shell and shared layout first.");
4227
4767
  lines.push("- Then implement each section's pages using the matching section and page packs.");
4228
4768
  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."
4769
+ "- After implementation, run `decantr check` (primary gate) and `decantr audit` (supplementary diagnostics)."
4230
4770
  );
4231
4771
  lines.push("- Fix all violations until `decantr check` exits 0.");
4232
4772
  lines.push(
@@ -4236,14 +4776,32 @@ function generateGreenfieldPrompt(ctx) {
4236
4776
  }
4237
4777
  function generateBrownfieldPrompt(ctx) {
4238
4778
  const lines = [];
4239
- lines.push("Attach Decantr to this existing application without rebuilding it from scratch.");
4779
+ lines.push(
4780
+ 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."
4781
+ );
4782
+ lines.push("");
4783
+ if (ctx.blueprint) lines.push(`Blueprint: ${ctx.blueprint}`);
4784
+ if (ctx.archetype) lines.push(`Primary archetype: ${ctx.archetype}`);
4785
+ if (ctx.theme) lines.push(`Theme: ${ctx.theme}${ctx.mode ? ` (${ctx.mode})` : ""}`);
4786
+ if (ctx.target) lines.push(`Target: ${ctx.target}`);
4787
+ if (ctx.pages.length > 0) {
4788
+ lines.push(
4789
+ `Routes/pages: ${ctx.pages.map((page) => `${page.sectionId ? `${page.sectionId}/` : ""}${page.id}:${page.shell}`).join(", ")}`
4790
+ );
4791
+ }
4240
4792
  lines.push("");
4241
4793
  lines.push(
4242
4794
  "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
4795
  );
4244
4796
  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.");
4797
+ if (ctx.analysisArtifacts) {
4798
+ lines.push("Treat .decantr/analysis.json as the factual inventory of the current app.");
4799
+ lines.push("Treat .decantr/init-seed.json as the recommended Decantr attach defaults.");
4800
+ } else {
4801
+ lines.push(
4802
+ "No Decantr analysis seed is present. Start by inventorying the app before changing runtime files."
4803
+ );
4804
+ }
4247
4805
  lines.push(
4248
4806
  "Treat the compiled execution-pack files as the Decantr contract you are layering onto the app."
4249
4807
  );
@@ -4252,20 +4810,34 @@ function generateBrownfieldPrompt(ctx) {
4252
4810
  );
4253
4811
  lines.push("");
4254
4812
  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
- );
4813
+ if (ctx.analysisArtifacts) {
4814
+ lines.push(
4815
+ "1. .decantr/analysis.json for the detected framework, routes, styling, layout, and dependencies."
4816
+ );
4817
+ lines.push("2. .decantr/init-seed.json for the intended attach defaults and workflow lane.");
4818
+ lines.push("3. DECANTR.md for guard rules, CSS expectations, and Decantr operating rules.");
4819
+ lines.push(
4820
+ "4. .decantr/context/scaffold-pack.md for the compact compiled shell, theme, feature, and route contract."
4821
+ );
4822
+ lines.push(
4823
+ "5. .decantr/context/scaffold.md for broader topology, route map, and voice guidance."
4824
+ );
4825
+ lines.push(
4826
+ "6. The matching section and page pack files only when you are working on those specific surfaces."
4827
+ );
4828
+ } else {
4829
+ lines.push("1. Inventory existing framework, routes, styling, layout, rule files, and dependencies.");
4830
+ lines.push("2. DECANTR.md for guard rules, adoption mode, and Decantr operating rules.");
4831
+ lines.push(
4832
+ "3. .decantr/context/scaffold-pack.md for the compact compiled shell, theme, feature, and route contract."
4833
+ );
4834
+ lines.push(
4835
+ "4. .decantr/context/scaffold.md for broader topology, route map, and voice guidance."
4836
+ );
4837
+ lines.push(
4838
+ "5. The matching section and page pack files only when you are working on those specific surfaces."
4839
+ );
4840
+ }
4269
4841
  lines.push("");
4270
4842
  lines.push("Implementation rules:");
4271
4843
  lines.push(
@@ -4277,21 +4849,33 @@ function generateBrownfieldPrompt(ctx) {
4277
4849
  lines.push(
4278
4850
  "- If package.json, router files, or style files already exist, extend them deliberately instead of replacing them with a different starter shape."
4279
4851
  );
4852
+ if (ctx.adoptionMode === "decantr-css") {
4853
+ lines.push(
4854
+ "- 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."
4855
+ );
4856
+ lines.push(
4857
+ "- Use the existing Decantr tokens, treatments, and decorators instead of inventing a parallel visual system."
4858
+ );
4859
+ } else if (ctx.adoptionMode === "style-bridge") {
4860
+ lines.push(
4861
+ "- Use Decantr bridge files as a mapping layer onto the existing styling system; do not install @decantr/css unless explicitly requested."
4862
+ );
4863
+ } else {
4864
+ lines.push(
4865
+ "- Keep the existing styling system. Do not add Decantr CSS files or @decantr/css unless the adoption mode changes."
4866
+ );
4867
+ }
4280
4868
  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."
4869
+ 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
4870
  );
4289
4871
  lines.push(
4290
4872
  "- Do not invent routes, sections, shells, themes, or features that are not present in the compiled packs."
4291
4873
  );
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
- );
4874
+ if (ctx.adoptionMode === "decantr-css") {
4875
+ lines.push(
4876
+ "- 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."
4877
+ );
4878
+ }
4295
4879
  lines.push(
4296
4880
  "- Let shells own spacing, centering, and scroll containers. Preserve app structure, but remove duplicated shell responsibilities when the contract makes them explicit."
4297
4881
  );
@@ -4322,7 +4906,7 @@ function generateBrownfieldPrompt(ctx) {
4322
4906
  return lines.join("\n");
4323
4907
  }
4324
4908
  function generateCuratedPrompt(ctx) {
4325
- return ctx.workflow === "brownfield-attach" ? generateBrownfieldPrompt(ctx) : generateGreenfieldPrompt(ctx);
4909
+ return ctx.workflow === "brownfield-attach" || ctx.workflow === "hybrid-compose" ? generateBrownfieldPrompt(ctx) : generateGreenfieldPrompt(ctx);
4326
4910
  }
4327
4911
  function getAPIClient() {
4328
4912
  return new RegistryAPIClient3({
@@ -4336,7 +4920,7 @@ function getPublicAPIClient() {
4336
4920
  });
4337
4921
  }
4338
4922
  function resolveUserPath(inputPath, cwd = process.cwd()) {
4339
- return isAbsolute(inputPath) ? inputPath : resolve3(cwd, inputPath);
4923
+ return isAbsolute(inputPath) ? inputPath : resolve4(cwd, inputPath);
4340
4924
  }
4341
4925
  function extractHostedAssetPaths(indexHtml) {
4342
4926
  const assetPaths = /* @__PURE__ */ new Set();
@@ -4349,18 +4933,18 @@ function extractHostedAssetPaths(indexHtml) {
4349
4933
  return [...assetPaths];
4350
4934
  }
4351
4935
  function readHostedDistSnapshot(distPath) {
4352
- const resolvedDistPath = distPath ? resolveUserPath(distPath) : join26(process.cwd(), "dist");
4353
- const indexPath = join26(resolvedDistPath, "index.html");
4354
- if (!existsSync25(indexPath)) {
4936
+ const resolvedDistPath = distPath ? resolveUserPath(distPath) : join28(process.cwd(), "dist");
4937
+ const indexPath = join28(resolvedDistPath, "index.html");
4938
+ if (!existsSync27(indexPath)) {
4355
4939
  return void 0;
4356
4940
  }
4357
- const indexHtml = readFileSync18(indexPath, "utf-8");
4941
+ const indexHtml = readFileSync20(indexPath, "utf-8");
4358
4942
  const assetPaths = extractHostedAssetPaths(indexHtml);
4359
4943
  const assets = {};
4360
4944
  for (const assetPath of assetPaths) {
4361
- const assetFilePath = join26(resolvedDistPath, assetPath.replace(/^[/\\]+/, ""));
4362
- if (existsSync25(assetFilePath)) {
4363
- assets[assetPath] = readFileSync18(assetFilePath, "utf-8");
4945
+ const assetFilePath = join28(resolvedDistPath, assetPath.replace(/^[/\\]+/, ""));
4946
+ if (existsSync27(assetFilePath)) {
4947
+ assets[assetPath] = readFileSync20(assetFilePath, "utf-8");
4364
4948
  }
4365
4949
  }
4366
4950
  return {
@@ -4375,7 +4959,7 @@ function isHostedSourceSnapshotFile(path) {
4375
4959
  function readHostedSourceSnapshot(sourcePath) {
4376
4960
  if (!sourcePath) return void 0;
4377
4961
  const resolvedSourcePath = resolveUserPath(sourcePath);
4378
- if (!existsSync25(resolvedSourcePath)) {
4962
+ if (!existsSync27(resolvedSourcePath)) {
4379
4963
  return void 0;
4380
4964
  }
4381
4965
  const files = {};
@@ -4389,17 +4973,17 @@ function readHostedSourceSnapshot(sourcePath) {
4389
4973
  ]);
4390
4974
  const rootPrefix = basename2(resolvedSourcePath);
4391
4975
  const walk = (absoluteDir, relativeDir) => {
4392
- for (const entry of readdirSync6(absoluteDir, { withFileTypes: true })) {
4976
+ for (const entry of readdirSync7(absoluteDir, { withFileTypes: true })) {
4393
4977
  if (ignoredDirNames.has(entry.name)) continue;
4394
- const absolutePath = join26(absoluteDir, entry.name);
4395
- const relativePath = join26(relativeDir, entry.name).replace(/\\/g, "/");
4978
+ const absolutePath = join28(absoluteDir, entry.name);
4979
+ const relativePath = join28(relativeDir, entry.name).replace(/\\/g, "/");
4396
4980
  if (entry.isDirectory()) {
4397
4981
  walk(absolutePath, relativePath);
4398
4982
  continue;
4399
4983
  }
4400
4984
  if (!entry.isFile()) continue;
4401
4985
  if (!isHostedSourceSnapshotFile(relativePath)) continue;
4402
- files[relativePath] = readFileSync18(absolutePath, "utf-8");
4986
+ files[relativePath] = readFileSync20(absolutePath, "utf-8");
4403
4987
  }
4404
4988
  };
4405
4989
  walk(resolvedSourcePath, rootPrefix);
@@ -4550,16 +5134,16 @@ async function printRegistryIntelligenceSummary(namespace, jsonOutput = false) {
4550
5134
  }
4551
5135
  async function printHostedExecutionPackBundle(essencePath, namespace, jsonOutput = false, writeContext = false) {
4552
5136
  const client = getPublicAPIClient();
4553
- const resolvedPath = essencePath ? resolveUserPath(essencePath) : join26(process.cwd(), "decantr.essence.json");
4554
- if (!existsSync25(resolvedPath)) {
5137
+ const resolvedPath = essencePath ? resolveUserPath(essencePath) : join28(process.cwd(), "decantr.essence.json");
5138
+ if (!existsSync27(resolvedPath)) {
4555
5139
  throw new Error(`Essence file not found at ${resolvedPath}`);
4556
5140
  }
4557
- const essence = JSON.parse(readFileSync18(resolvedPath, "utf-8"));
5141
+ const essence = JSON.parse(readFileSync20(resolvedPath, "utf-8"));
4558
5142
  const bundle = await client.compileExecutionPacks(essence, namespace ? { namespace } : void 0);
4559
5143
  let writtenContextPaths = [];
4560
5144
  if (writeContext) {
4561
- const contextDir = join26(process.cwd(), ".decantr", "context");
4562
- mkdirSync10(contextDir, { recursive: true });
5145
+ const contextDir = join28(process.cwd(), ".decantr", "context");
5146
+ mkdirSync11(contextDir, { recursive: true });
4563
5147
  const written = writeExecutionPackBundleArtifacts(
4564
5148
  contextDir,
4565
5149
  bundle
@@ -4584,26 +5168,27 @@ async function printHostedExecutionPackBundle(essencePath, namespace, jsonOutput
4584
5168
  console.log(` Sections: ${typedBundle.sections.length}`);
4585
5169
  console.log(` Mutations: ${typedBundle.mutations.length}`);
4586
5170
  if (writeContext) {
4587
- console.log(` Context bundle: ${join26(process.cwd(), ".decantr", "context")}`);
5171
+ console.log(` Context bundle: ${join28(process.cwd(), ".decantr", "context")}`);
4588
5172
  console.log(` Files written: ${writtenContextPaths.length}`);
4589
5173
  }
4590
5174
  console.log("");
4591
5175
  console.log(`${BOLD6}Route Plan:${RESET13}`);
4592
5176
  for (const route of typedBundle.scaffold.data.routes) {
4593
5177
  const patterns = route.patternIds.length > 0 ? route.patternIds.join(", ") : "none";
4594
- console.log(` ${cyan3(route.path)} -> ${route.pageId} [${patterns}]`);
5178
+ const pageLabel = route.sectionId ? `${route.sectionId}/${route.pageId}` : route.pageId;
5179
+ console.log(` ${cyan3(route.path)} -> ${pageLabel} [${patterns}]`);
4595
5180
  }
4596
5181
  }
4597
5182
  async function printHostedSelectedExecutionPack(packType, id, essencePath, namespace, jsonOutput = false, writeContext = false) {
4598
5183
  const client = getPublicAPIClient();
4599
- const resolvedPath = essencePath ? resolveUserPath(essencePath) : join26(process.cwd(), "decantr.essence.json");
4600
- if (!existsSync25(resolvedPath)) {
5184
+ const resolvedPath = essencePath ? resolveUserPath(essencePath) : join28(process.cwd(), "decantr.essence.json");
5185
+ if (!existsSync27(resolvedPath)) {
4601
5186
  throw new Error(`Essence file not found at ${resolvedPath}`);
4602
5187
  }
4603
5188
  if ((packType === "section" || packType === "page" || packType === "mutation") && !id) {
4604
5189
  throw new Error(`Pack type "${packType}" requires an id.`);
4605
5190
  }
4606
- const essence = JSON.parse(readFileSync18(resolvedPath, "utf-8"));
5191
+ const essence = JSON.parse(readFileSync20(resolvedPath, "utf-8"));
4607
5192
  const selected = await client.selectExecutionPack(
4608
5193
  {
4609
5194
  essence,
@@ -4614,17 +5199,17 @@ async function printHostedSelectedExecutionPack(packType, id, essencePath, names
4614
5199
  );
4615
5200
  let writtenContextDir = null;
4616
5201
  if (writeContext) {
4617
- const contextDir = join26(process.cwd(), ".decantr", "context");
4618
- mkdirSync10(contextDir, { recursive: true });
4619
- writeFileSync13(
4620
- join26(contextDir, "pack-manifest.json"),
5202
+ const contextDir = join28(process.cwd(), ".decantr", "context");
5203
+ mkdirSync11(contextDir, { recursive: true });
5204
+ writeFileSync14(
5205
+ join28(contextDir, "pack-manifest.json"),
4621
5206
  JSON.stringify(selected.manifest, null, 2) + "\n"
4622
5207
  );
4623
5208
  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
5209
  const markdownFile = manifestEntry?.markdown ?? `${selected.selector.packType}${selected.selector.id ? `-${selected.selector.id}` : ""}-pack.md`;
4625
5210
  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");
5211
+ writeFileSync14(join28(contextDir, markdownFile), selected.pack.renderedMarkdown);
5212
+ writeFileSync14(join28(contextDir, jsonFile), JSON.stringify(selected.pack, null, 2) + "\n");
4628
5213
  writtenContextDir = contextDir;
4629
5214
  }
4630
5215
  if (jsonOutput) {
@@ -4649,20 +5234,20 @@ async function printHostedSelectedExecutionPack(packType, id, essencePath, names
4649
5234
  }
4650
5235
  async function printHostedExecutionPackManifest(essencePath, namespace, jsonOutput = false, writeContext = false) {
4651
5236
  const client = getPublicAPIClient();
4652
- const resolvedPath = essencePath ? resolveUserPath(essencePath) : join26(process.cwd(), "decantr.essence.json");
4653
- if (!existsSync25(resolvedPath)) {
5237
+ const resolvedPath = essencePath ? resolveUserPath(essencePath) : join28(process.cwd(), "decantr.essence.json");
5238
+ if (!existsSync27(resolvedPath)) {
4654
5239
  throw new Error(`Essence file not found at ${resolvedPath}`);
4655
5240
  }
4656
- const essence = JSON.parse(readFileSync18(resolvedPath, "utf-8"));
5241
+ const essence = JSON.parse(readFileSync20(resolvedPath, "utf-8"));
4657
5242
  const manifest = await client.getExecutionPackManifest(
4658
5243
  essence,
4659
5244
  namespace ? { namespace } : void 0
4660
5245
  );
4661
5246
  let writtenContextDir = null;
4662
5247
  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");
5248
+ const contextDir = join28(process.cwd(), ".decantr", "context");
5249
+ mkdirSync11(contextDir, { recursive: true });
5250
+ writeFileSync14(join28(contextDir, "pack-manifest.json"), JSON.stringify(manifest, null, 2) + "\n");
4666
5251
  writtenContextDir = contextDir;
4667
5252
  }
4668
5253
  if (jsonOutput) {
@@ -4683,14 +5268,14 @@ async function printHostedExecutionPackManifest(essencePath, namespace, jsonOutp
4683
5268
  }
4684
5269
  }
4685
5270
  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)) {
5271
+ const contextDir = join28(projectRoot, ".decantr", "context");
5272
+ const reviewPackPath = join28(contextDir, "review-pack.json");
5273
+ const manifestPath = join28(contextDir, "pack-manifest.json");
5274
+ if (existsSync27(reviewPackPath) && existsSync27(manifestPath)) {
4690
5275
  return { attempted: false, hydrated: false };
4691
5276
  }
4692
- const essencePath = join26(projectRoot, "decantr.essence.json");
4693
- if (!existsSync25(essencePath)) {
5277
+ const essencePath = join28(projectRoot, "decantr.essence.json");
5278
+ if (!existsSync27(essencePath)) {
4694
5279
  return { attempted: false, hydrated: false };
4695
5280
  }
4696
5281
  const reviewHydration = await hydrateHostedReviewPackIfMissing(projectRoot, namespace);
@@ -4699,9 +5284,9 @@ async function hydrateHostedExecutionPacksIfMissing(projectRoot, namespace = "@o
4699
5284
  }
4700
5285
  try {
4701
5286
  const client = getPublicAPIClient();
4702
- const essence = JSON.parse(readFileSync18(essencePath, "utf-8"));
5287
+ const essence = JSON.parse(readFileSync20(essencePath, "utf-8"));
4703
5288
  const bundle = await client.compileExecutionPacks(essence, { namespace });
4704
- mkdirSync10(contextDir, { recursive: true });
5289
+ mkdirSync11(contextDir, { recursive: true });
4705
5290
  writeExecutionPackBundleArtifacts(contextDir, bundle);
4706
5291
  return { attempted: true, hydrated: true, scope: "bundle" };
4707
5292
  } catch {
@@ -4709,19 +5294,19 @@ async function hydrateHostedExecutionPacksIfMissing(projectRoot, namespace = "@o
4709
5294
  }
4710
5295
  }
4711
5296
  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)) {
5297
+ const contextDir = join28(projectRoot, ".decantr", "context");
5298
+ const reviewPackPath = join28(contextDir, "review-pack.json");
5299
+ const manifestPath = join28(contextDir, "pack-manifest.json");
5300
+ if (existsSync27(reviewPackPath) && existsSync27(manifestPath)) {
4716
5301
  return { attempted: false, hydrated: false };
4717
5302
  }
4718
- const essencePath = join26(projectRoot, "decantr.essence.json");
4719
- if (!existsSync25(essencePath)) {
5303
+ const essencePath = join28(projectRoot, "decantr.essence.json");
5304
+ if (!existsSync27(essencePath)) {
4720
5305
  return { attempted: false, hydrated: false };
4721
5306
  }
4722
5307
  try {
4723
5308
  const client = getPublicAPIClient();
4724
- const essence = JSON.parse(readFileSync18(essencePath, "utf-8"));
5309
+ const essence = JSON.parse(readFileSync20(essencePath, "utf-8"));
4725
5310
  const selected = await client.selectExecutionPack(
4726
5311
  {
4727
5312
  essence,
@@ -4729,14 +5314,14 @@ async function hydrateHostedReviewPackIfMissing(projectRoot, namespace = "@offic
4729
5314
  },
4730
5315
  { namespace }
4731
5316
  );
4732
- mkdirSync10(contextDir, { recursive: true });
4733
- writeFileSync13(join26(contextDir, "review-pack.md"), selected.pack.renderedMarkdown);
4734
- writeFileSync13(
4735
- join26(contextDir, "review-pack.json"),
5317
+ mkdirSync11(contextDir, { recursive: true });
5318
+ writeFileSync14(join28(contextDir, "review-pack.md"), selected.pack.renderedMarkdown);
5319
+ writeFileSync14(
5320
+ join28(contextDir, "review-pack.json"),
4736
5321
  JSON.stringify(selected.pack, null, 2) + "\n"
4737
5322
  );
4738
- if (!existsSync25(manifestPath)) {
4739
- writeFileSync13(manifestPath, JSON.stringify(selected.manifest, null, 2) + "\n");
5323
+ if (!existsSync27(manifestPath)) {
5324
+ writeFileSync14(manifestPath, JSON.stringify(selected.manifest, null, 2) + "\n");
4740
5325
  }
4741
5326
  return { attempted: true, hydrated: true, scope: "review" };
4742
5327
  } catch {
@@ -4746,17 +5331,17 @@ async function hydrateHostedReviewPackIfMissing(projectRoot, namespace = "@offic
4746
5331
  async function printHostedFileCritique(sourcePath, namespace, jsonOutput = false, essencePath, treatmentsPath) {
4747
5332
  const client = getPublicAPIClient();
4748
5333
  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)) {
5334
+ const resolvedEssencePath = essencePath ? resolveUserPath(essencePath) : join28(process.cwd(), "decantr.essence.json");
5335
+ const resolvedTreatmentsPath = treatmentsPath ? resolveUserPath(treatmentsPath) : join28(process.cwd(), "src", "styles", "treatments.css");
5336
+ if (!existsSync27(resolvedSourcePath)) {
4752
5337
  throw new Error(`Source file not found at ${resolvedSourcePath}`);
4753
5338
  }
4754
- if (!existsSync25(resolvedEssencePath)) {
5339
+ if (!existsSync27(resolvedEssencePath)) {
4755
5340
  throw new Error(`Essence file not found at ${resolvedEssencePath}`);
4756
5341
  }
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;
5342
+ const code = readFileSync20(resolvedSourcePath, "utf-8");
5343
+ const essence = JSON.parse(readFileSync20(resolvedEssencePath, "utf-8"));
5344
+ const treatmentsCss = existsSync27(resolvedTreatmentsPath) ? readFileSync20(resolvedTreatmentsPath, "utf-8") : void 0;
4760
5345
  const report = await client.critiqueFile(
4761
5346
  {
4762
5347
  essence,
@@ -4780,11 +5365,11 @@ async function printHostedFileCritique(sourcePath, namespace, jsonOutput = false
4780
5365
  }
4781
5366
  async function printHostedProjectAudit(namespace, jsonOutput = false, essencePath, distPath, sourcesPath) {
4782
5367
  const client = getPublicAPIClient();
4783
- const resolvedEssencePath = essencePath ? resolveUserPath(essencePath) : join26(process.cwd(), "decantr.essence.json");
4784
- if (!existsSync25(resolvedEssencePath)) {
5368
+ const resolvedEssencePath = essencePath ? resolveUserPath(essencePath) : join28(process.cwd(), "decantr.essence.json");
5369
+ if (!existsSync27(resolvedEssencePath)) {
4785
5370
  throw new Error(`Essence file not found at ${resolvedEssencePath}`);
4786
5371
  }
4787
- const essence = JSON.parse(readFileSync18(resolvedEssencePath, "utf-8"));
5372
+ const essence = JSON.parse(readFileSync20(resolvedEssencePath, "utf-8"));
4788
5373
  const dist = readHostedDistSnapshot(distPath);
4789
5374
  const sources = readHostedSourceSnapshot(sourcesPath);
4790
5375
  const report = await client.auditProject(
@@ -4802,7 +5387,7 @@ async function printHostedProjectAudit(namespace, jsonOutput = false, essencePat
4802
5387
  console.log(heading2("Hosted Project Audit"));
4803
5388
  console.log(` Essence: ${resolvedEssencePath}`);
4804
5389
  console.log(
4805
- ` Dist snapshot: ${dist ? distPath ? resolveUserPath(distPath) : join26(process.cwd(), "dist") : "none"}`
5390
+ ` Dist snapshot: ${dist ? distPath ? resolveUserPath(distPath) : join28(process.cwd(), "dist") : "none"}`
4806
5391
  );
4807
5392
  console.log(
4808
5393
  ` Source snapshot: ${sources && sourcesPath ? resolveUserPath(sourcesPath) : "none"}`
@@ -4883,25 +5468,25 @@ async function cmdGet(type, id) {
4883
5468
  }
4884
5469
  const apiType = CONTENT_TYPE_TO_API_CONTENT_TYPE3[type];
4885
5470
  const registryClient = new RegistryClient({
4886
- cacheDir: join26(process.cwd(), ".decantr", "cache")
5471
+ cacheDir: join28(process.cwd(), ".decantr", "cache")
4887
5472
  });
4888
5473
  const result = await registryClient.fetchContentItem(apiType, id);
4889
5474
  if (result) {
4890
5475
  console.log(JSON.stringify(result.data, null, 2));
4891
5476
  return;
4892
5477
  }
4893
- const currentDir = dirname2(fileURLToPath2(import.meta.url));
5478
+ const currentDir = dirname4(fileURLToPath2(import.meta.url));
4894
5479
  const bundledCandidates = [
4895
- join26(currentDir, "bundled", apiType, `${id}.json`),
5480
+ join28(currentDir, "bundled", apiType, `${id}.json`),
4896
5481
  // Running from src/
4897
- join26(currentDir, "..", "src", "bundled", apiType, `${id}.json`),
5482
+ join28(currentDir, "..", "src", "bundled", apiType, `${id}.json`),
4898
5483
  // Running from dist/
4899
- join26(currentDir, "..", "bundled", apiType, `${id}.json`)
5484
+ join28(currentDir, "..", "bundled", apiType, `${id}.json`)
4900
5485
  // Alternative dist layout
4901
5486
  ];
4902
- const bundledPath = bundledCandidates.find((p) => existsSync25(p)) || null;
5487
+ const bundledPath = bundledCandidates.find((p) => existsSync27(p)) || null;
4903
5488
  if (bundledPath) {
4904
- const data = JSON.parse(readFileSync18(bundledPath, "utf-8"));
5489
+ const data = JSON.parse(readFileSync20(bundledPath, "utf-8"));
4905
5490
  console.log(JSON.stringify(data, null, 2));
4906
5491
  return;
4907
5492
  }
@@ -4910,10 +5495,10 @@ async function cmdGet(type, id) {
4910
5495
  return;
4911
5496
  }
4912
5497
  async function cmdValidate(path) {
4913
- const essencePath = path || join26(process.cwd(), "decantr.essence.json");
5498
+ const essencePath = path || join28(process.cwd(), "decantr.essence.json");
4914
5499
  let raw;
4915
5500
  try {
4916
- raw = readFileSync18(essencePath, "utf-8");
5501
+ raw = readFileSync20(essencePath, "utf-8");
4917
5502
  } catch {
4918
5503
  console.error(error3(`Could not read ${essencePath}`));
4919
5504
  process.exitCode = 1;
@@ -4982,7 +5567,7 @@ async function cmdList(type, sort, recommended, intelligenceSource) {
4982
5567
  return;
4983
5568
  }
4984
5569
  const registryClient = new RegistryClient({
4985
- cacheDir: join26(process.cwd(), ".decantr", "cache")
5570
+ cacheDir: join28(process.cwd(), ".decantr", "cache")
4986
5571
  });
4987
5572
  const result = await registryClient.fetchContentList(
4988
5573
  type,
@@ -5029,11 +5614,17 @@ async function cmdList(type, sort, recommended, intelligenceSource) {
5029
5614
  }
5030
5615
  }
5031
5616
  async function cmdInit(args) {
5032
- const projectRoot = process.cwd();
5617
+ const workspaceInfo = resolveWorkspaceInfo(process.cwd(), args.project);
5618
+ if (args.yes && workspaceInfo.requiresProjectSelection) {
5619
+ console.log(error3("This looks like a workspace root with multiple app candidates."));
5620
+ console.log(dim3(`Use --project=<path>. Candidates: ${workspaceInfo.appCandidates.join(", ")}`));
5621
+ process.exitCode = 1;
5622
+ return;
5623
+ }
5624
+ const projectRoot = workspaceInfo.appRoot;
5033
5625
  console.log(heading2("Decantr Project Setup"));
5034
5626
  const detected = detectProject(projectRoot);
5035
5627
  const workflowSeed = readBrownfieldInitSeed(projectRoot);
5036
- const brownfieldAttach = Boolean(workflowSeed) || hasExistingProjectFootprint(detected);
5037
5628
  if (workflowSeed) {
5038
5629
  console.log(dim3(" Found .decantr/init-seed.json brownfield guidance."));
5039
5630
  }
@@ -5048,11 +5639,27 @@ async function cmdInit(args) {
5048
5639
  const requestedBlueprint = Boolean(args.blueprint);
5049
5640
  const requestedArchetype = Boolean(args.archetype);
5050
5641
  const requestedTheme = Boolean(args.theme);
5642
+ const policy = resolveWorkflowPolicy({
5643
+ command: "init",
5644
+ detected,
5645
+ workflowSeed,
5646
+ requestedWorkflow: args.workflow,
5647
+ requestedAdoption: args.adoption,
5648
+ requestedAssistantBridge: args["assistant-bridge"],
5649
+ requestedBlueprint,
5650
+ requestedArchetype,
5651
+ requestedTheme,
5652
+ explicitExisting: args.existing,
5653
+ offline: args.offline,
5654
+ projectScope: workspaceInfo.projectScope
5655
+ });
5656
+ const preferContractOnly = policy.contentSource === "none" && (policy.workflowMode === "brownfield-attach" || policy.workflowMode === "greenfield-contract-only");
5657
+ const shouldUseRegistry = !preferContractOnly || policy.registryRequired;
5051
5658
  let offlineSeed = {
5052
5659
  seeded: false,
5053
5660
  strategy: null
5054
5661
  };
5055
- if (args.offline) {
5662
+ if (args.offline && shouldUseRegistry) {
5056
5663
  offlineSeed = seedOfflineRegistry(projectRoot, projectRoot);
5057
5664
  if (offlineSeed.seeded) {
5058
5665
  console.log(dim3(` Seeded offline registry content from ${offlineSeed.strategy}.`));
@@ -5070,11 +5677,12 @@ async function cmdInit(args) {
5070
5677
  }
5071
5678
  }
5072
5679
  const registryClient = new RegistryClient({
5073
- cacheDir: join26(projectRoot, ".decantr", "cache"),
5680
+ cacheDir: join28(projectRoot, ".decantr", "cache"),
5074
5681
  apiUrl: args.registry,
5075
- offline: args.offline
5682
+ offline: args.offline,
5683
+ projectRoot
5076
5684
  });
5077
- const apiAvailable = await registryClient.checkApiAvailability();
5685
+ const apiAvailable = shouldUseRegistry ? await registryClient.checkApiAvailability() : false;
5078
5686
  if (!apiAvailable && !args.offline && (requestedBlueprint || requestedArchetype)) {
5079
5687
  const fallbackSeed = seedOfflineRegistry(projectRoot, projectRoot);
5080
5688
  if (fallbackSeed.seeded) {
@@ -5084,18 +5692,21 @@ async function cmdInit(args) {
5084
5692
  }
5085
5693
  let selectedBlueprint = "default";
5086
5694
  let registrySource = "cache";
5087
- const preferContractOnly = brownfieldAttach && !requestedBlueprint && !requestedArchetype;
5088
- const workflowMode = workflowSeed || brownfieldAttach && !requestedBlueprint && !requestedArchetype ? "brownfield-attach" : "greenfield-scaffold";
5089
5695
  if (args.yes) {
5090
5696
  selectedBlueprint = args.blueprint || "default";
5091
- } else if (!apiAvailable) {
5697
+ } else if (shouldUseRegistry && !apiAvailable) {
5092
5698
  if (!args.blueprint) {
5093
5699
  console.log(`
5094
5700
  ${YELLOW9}You're offline. Scaffolding minimal Decantr project.${RESET13}`);
5095
5701
  console.log(
5096
5702
  dim3("Run `decantr sync` or `decantr upgrade` when online to pull full registry content.\n")
5097
5703
  );
5098
- const result2 = scaffoldMinimal(projectRoot);
5704
+ const result2 = scaffoldMinimal(projectRoot, {
5705
+ workflowMode: policy.workflowMode,
5706
+ adoptionMode: policy.adoptionMode,
5707
+ contentSource: policy.contentSource,
5708
+ assistantBridge: policy.assistantBridge
5709
+ });
5099
5710
  console.log(success3("\nProject scaffolded (minimal/offline)!\n"));
5100
5711
  console.log(" Files created:");
5101
5712
  console.log(` ${cyan3("decantr.essence.json")} Design specification`);
@@ -5111,7 +5722,7 @@ ${YELLOW9}You're offline. Scaffolding minimal Decantr project.${RESET13}`);
5111
5722
  ` 2. Run ${cyan3("decantr refresh")} after syncing to generate scaffold, section, and page packs`
5112
5723
  );
5113
5724
  console.log(
5114
- ` 3. Read ${cyan3("DECANTR.md")} and the generated ${cyan3(".decantr/context/*")} files before implementation`
5725
+ ` 3. Read ${cyan3(".decantr/context/scaffold-pack.md")} first, then use ${cyan3("DECANTR.md")} as a lookup reference`
5115
5726
  );
5116
5727
  console.log(
5117
5728
  ` 4. Use ${cyan3("decantr create <type> <name>")} to create custom content if needed`
@@ -5136,22 +5747,22 @@ ${YELLOW9}You're offline. Scaffolding minimal Decantr project.${RESET13}`);
5136
5747
  ${YELLOW9}You're offline. Scaffolding Decantr default.${RESET13}`);
5137
5748
  console.log(dim3("Run `decantr upgrade` when online, or visit decantr.ai/registry\n"));
5138
5749
  selectedBlueprint = "default";
5139
- } else if (!preferContractOnly) {
5750
+ } else if (shouldUseRegistry) {
5140
5751
  console.log(dim3("Fetching registry content..."));
5141
5752
  const blueprintsResult2 = await registryClient.fetchBlueprints();
5142
5753
  registrySource = blueprintsResult2.source.type === "api" ? "api" : "cache";
5143
5754
  const { selectedBlueprint: selected } = await runSimplifiedInit(blueprintsResult2.data.items);
5144
5755
  selectedBlueprint = selected || "default";
5145
5756
  }
5146
- const archetypesResult = await registryClient.fetchArchetypes();
5147
- const blueprintsResult = await registryClient.fetchBlueprints();
5148
- const themesResult = await registryClient.fetchThemes();
5149
- if (archetypesResult.source.type === "api") {
5757
+ const archetypesResult = shouldUseRegistry ? await registryClient.fetchArchetypes() : null;
5758
+ const blueprintsResult = shouldUseRegistry ? await registryClient.fetchBlueprints() : null;
5759
+ const themesResult = shouldUseRegistry ? await registryClient.fetchThemes() : null;
5760
+ if (archetypesResult?.source.type === "api") {
5150
5761
  registrySource = "api";
5151
5762
  }
5152
- const archetypes = archetypesResult.data.items;
5153
- const blueprints = blueprintsResult.data.items;
5154
- const themes = themesResult.data.items;
5763
+ const archetypes = archetypesResult?.data.items ?? [];
5764
+ const blueprints = blueprintsResult?.data.items ?? [];
5765
+ const themes = themesResult?.data.items ?? [];
5155
5766
  let options;
5156
5767
  const userExplicit = {
5157
5768
  theme: Boolean(args.theme),
@@ -5159,7 +5770,10 @@ ${YELLOW9}You're offline. Scaffolding Decantr default.${RESET13}`);
5159
5770
  shape: Boolean(args.shape),
5160
5771
  personality: Boolean(args.personality)
5161
5772
  };
5162
- if (args.yes || selectedBlueprint !== "default") {
5773
+ if (preferContractOnly) {
5774
+ const flags = parseFlags(args, detected);
5775
+ options = mergeWithDefaults(flags, detected, workflowSeed ?? void 0);
5776
+ } else if (args.yes || selectedBlueprint !== "default") {
5163
5777
  const flags = parseFlags(args, detected);
5164
5778
  flags.blueprint = selectedBlueprint !== "default" ? selectedBlueprint : flags.blueprint;
5165
5779
  options = mergeWithDefaults(flags, detected, workflowSeed ?? void 0);
@@ -5176,14 +5790,22 @@ ${YELLOW9}You're offline. Scaffolding Decantr default.${RESET13}`);
5176
5790
  userExplicit.shape = true;
5177
5791
  userExplicit.personality = true;
5178
5792
  }
5179
- options.workflowMode = workflowMode;
5793
+ options.workflowMode = policy.workflowMode;
5794
+ options.adoptionMode = policy.adoptionMode;
5795
+ options.contentSource = policy.contentSource;
5796
+ options.assistantBridge = policy.assistantBridge;
5797
+ options.projectScope = policy.projectScope;
5798
+ options.workspaceRoot = workspaceInfo.workspaceRoot;
5799
+ options.appRoot = workspaceInfo.appRoot;
5800
+ options.analysisArtifacts = policy.hasAnalysisArtifacts;
5801
+ options.adapterId = resolveBootstrapTarget(options.target).adapterId;
5180
5802
  let topologyMarkdown = "";
5181
5803
  let archetypeData;
5182
5804
  let composedSections;
5183
5805
  let routeMap;
5184
5806
  let patternSpecs;
5185
5807
  let blueprintData;
5186
- if (options.blueprint) {
5808
+ if (shouldUseRegistry && options.blueprint) {
5187
5809
  const blueprintResult = await registryClient.fetchBlueprint(options.blueprint);
5188
5810
  if (blueprintResult) {
5189
5811
  const blueprint = blueprintResult.data;
@@ -5306,7 +5928,7 @@ ${YELLOW9}You're offline. Scaffolding Decantr default.${RESET13}`);
5306
5928
  `${YELLOW9} Warning: Could not fetch blueprint "${options.blueprint}". Using defaults.${RESET13}`
5307
5929
  );
5308
5930
  }
5309
- } else if (options.archetype) {
5931
+ } else if (shouldUseRegistry && options.archetype) {
5310
5932
  const archetypeResult = await registryClient.fetchArchetype(options.archetype);
5311
5933
  if (archetypeResult) {
5312
5934
  archetypeData = mapRegistryArchetypeToArchetypeData(archetypeResult.data);
@@ -5323,7 +5945,7 @@ ${YELLOW9}You're offline. Scaffolding Decantr default.${RESET13}`);
5323
5945
  }
5324
5946
  }
5325
5947
  let themeData;
5326
- if (options.theme) {
5948
+ if (shouldUseRegistry && options.theme) {
5327
5949
  const themeResult = await registryClient.fetchTheme(options.theme);
5328
5950
  if (themeResult) {
5329
5951
  themeData = mapRegistryThemeToThemeData(themeResult.data);
@@ -5355,6 +5977,19 @@ ${YELLOW9}You're offline. Scaffolding Decantr default.${RESET13}`);
5355
5977
  patternSpecs,
5356
5978
  blueprintData
5357
5979
  );
5980
+ let assistantBridgePath = null;
5981
+ let appliedRuleFiles = [];
5982
+ if (policy.assistantBridge === "preview" || policy.assistantBridge === "apply") {
5983
+ assistantBridgePath = writeAssistantBridgePreview({
5984
+ projectRoot,
5985
+ detected,
5986
+ workflowMode: policy.workflowMode,
5987
+ assistantBridge: policy.assistantBridge
5988
+ });
5989
+ }
5990
+ if (policy.assistantBridge === "apply") {
5991
+ appliedRuleFiles = applyAssistantBridge(projectRoot, detected);
5992
+ }
5358
5993
  console.log(success3("\nProject scaffolded!\n"));
5359
5994
  console.log(" Files created:");
5360
5995
  console.log(` ${cyan3("decantr.essence.json")} Design specification`);
@@ -5363,7 +5998,13 @@ ${YELLOW9}You're offline. Scaffolding Decantr default.${RESET13}`);
5363
5998
  if (result.gitignoreUpdated) {
5364
5999
  console.log(` ${dim3(".gitignore updated")}`);
5365
6000
  }
5366
- if (!existsSync25(join26(projectRoot, "package.json"))) {
6001
+ if (assistantBridgePath) {
6002
+ console.log(` ${cyan3(".decantr/context/assistant-bridge.md")} Assistant bridge preview`);
6003
+ }
6004
+ if (appliedRuleFiles.length > 0) {
6005
+ console.log(` ${dim3(`Rule bridge applied: ${appliedRuleFiles.join(", ")}`)}`);
6006
+ }
6007
+ if (!existsSync27(join28(projectRoot, "package.json"))) {
5367
6008
  console.log("");
5368
6009
  console.log(
5369
6010
  dim3(` Note: ${cyan3("decantr init")} created Decantr contract/context files only.`)
@@ -5376,14 +6017,15 @@ ${YELLOW9}You're offline. Scaffolding Decantr default.${RESET13}`);
5376
6017
  }
5377
6018
  console.log("");
5378
6019
  console.log(" Next steps:");
5379
- console.log(" 1. Read DECANTR.md for methodology, CSS approach, and guard rules");
6020
+ console.log(" 1. Read .decantr/context/scaffold-pack.md first as the primary compiled contract");
5380
6021
  console.log(
5381
- " 2. Read .decantr/context/scaffold-pack.md first, then .decantr/context/scaffold.md"
6022
+ " 2. Read .decantr/context/scaffold.md for broader topology, route map, and voice guidance"
5382
6023
  );
5383
6024
  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");
6025
+ console.log(" 4. Use DECANTR.md as a lookup reference for atoms, treatments, and guard rules");
6026
+ console.log(" 5. Build the shell and route structure first, then implement the pages");
6027
+ console.log(" 6. Run decantr check and decantr audit after implementation");
6028
+ console.log(" 7. Explore more at decantr.ai/registry");
5387
6029
  console.log("");
5388
6030
  console.log(" Commands:");
5389
6031
  console.log(` ${cyan3("decantr status")} Project health`);
@@ -5393,7 +6035,7 @@ ${YELLOW9}You're offline. Scaffolding Decantr default.${RESET13}`);
5393
6035
  console.log(` ${cyan3("decantr upgrade")} Update to latest patterns`);
5394
6036
  console.log(` ${cyan3("decantr check")} Detect drift issues`);
5395
6037
  console.log(` ${cyan3("decantr migrate")} Migrate v2 essence to v3`);
5396
- const essenceContent = readFileSync18(result.essencePath, "utf-8");
6038
+ const essenceContent = readFileSync20(result.essencePath, "utf-8");
5397
6039
  const essence = JSON.parse(essenceContent);
5398
6040
  if (essence.version !== "3.1.0") {
5399
6041
  const validation = validateEssence2(essence);
@@ -5406,11 +6048,12 @@ Validation warnings: ${validation.errors.join(", ")}`));
5406
6048
  let promptPages;
5407
6049
  if (isV36(essence)) {
5408
6050
  const allPages = essence.blueprint.sections ? essence.blueprint.sections.flatMap(
5409
- (s) => s.pages.map((p) => ({ ...p, _shell: s.shell }))
6051
+ (s) => s.pages.map((p) => ({ ...p, _sectionId: s.id, _shell: s.shell }))
5410
6052
  ) : essence.blueprint.pages || [];
5411
6053
  promptPages = allPages.map(
5412
6054
  (p) => ({
5413
6055
  id: p.id,
6056
+ sectionId: p._sectionId,
5414
6057
  shell: p.shell_override ?? p._shell ?? essence.blueprint.shell,
5415
6058
  layout: (p.layout || []).map(
5416
6059
  (item) => typeof item === "string" ? item : extractPatternName(item)
@@ -5421,7 +6064,9 @@ Validation warnings: ${validation.errors.join(", ")}`));
5421
6064
  promptPages = essence.structure || [{ id: "home", shell: options.shell, layout: ["hero"] }];
5422
6065
  }
5423
6066
  const promptCtx = {
5424
- workflow: options.workflowMode === "brownfield-attach" ? "brownfield-attach" : "greenfield-scaffold",
6067
+ workflow: options.workflowMode || "greenfield-scaffold",
6068
+ adoptionMode: options.adoptionMode,
6069
+ analysisArtifacts: options.analysisArtifacts,
5425
6070
  archetype: options.archetype || "custom",
5426
6071
  blueprint: options.blueprint,
5427
6072
  theme: options.theme,
@@ -5447,16 +6092,16 @@ Validation warnings: ${validation.errors.join(", ")}`));
5447
6092
  }
5448
6093
  async function cmdStatus() {
5449
6094
  const projectRoot = process.cwd();
5450
- const essencePath = join26(projectRoot, "decantr.essence.json");
5451
- const projectJsonPath = join26(projectRoot, ".decantr", "project.json");
6095
+ const essencePath = join28(projectRoot, "decantr.essence.json");
6096
+ const projectJsonPath = join28(projectRoot, ".decantr", "project.json");
5452
6097
  console.log(heading2("Decantr Project Status"));
5453
- if (!existsSync25(essencePath)) {
6098
+ if (!existsSync27(essencePath)) {
5454
6099
  console.log(`${RED11}No decantr.essence.json found.${RESET13}`);
5455
6100
  console.log(dim3('Run "decantr init" to create one.'));
5456
6101
  return;
5457
6102
  }
5458
6103
  try {
5459
- const essence = JSON.parse(readFileSync18(essencePath, "utf-8"));
6104
+ const essence = JSON.parse(readFileSync20(essencePath, "utf-8"));
5460
6105
  const validation = validateEssence2(essence);
5461
6106
  const essenceVersion = isV36(essence) ? "v3" : "v2";
5462
6107
  console.log(`${BOLD6}Essence:${RESET13}`);
@@ -5513,9 +6158,9 @@ async function cmdStatus() {
5513
6158
  }
5514
6159
  console.log("");
5515
6160
  console.log(`${BOLD6}Sync Status:${RESET13}`);
5516
- if (existsSync25(projectJsonPath)) {
6161
+ if (existsSync27(projectJsonPath)) {
5517
6162
  try {
5518
- const projectJson = JSON.parse(readFileSync18(projectJsonPath, "utf-8"));
6163
+ const projectJson = JSON.parse(readFileSync20(projectJsonPath, "utf-8"));
5519
6164
  const syncStatus = projectJson.sync?.status || "unknown";
5520
6165
  const lastSync = projectJson.sync?.lastSync || "never";
5521
6166
  const source = projectJson.sync?.registrySource || "unknown";
@@ -5533,7 +6178,7 @@ async function cmdStatus() {
5533
6178
  }
5534
6179
  async function cmdSync() {
5535
6180
  const projectRoot = process.cwd();
5536
- const cacheDir = join26(projectRoot, ".decantr", "cache");
6181
+ const cacheDir = join28(projectRoot, ".decantr", "cache");
5537
6182
  console.log(heading2("Syncing registry content..."));
5538
6183
  const result = await syncRegistry(cacheDir);
5539
6184
  if (result.synced.length > 0) {
@@ -5728,14 +6373,14 @@ ${BOLD6}Examples:${RESET13}
5728
6373
  process.exitCode = 1;
5729
6374
  return;
5730
6375
  }
5731
- const themePath = join26(projectRoot, ".decantr", "custom", "themes", `${name}.json`);
5732
- if (!existsSync25(themePath)) {
6376
+ const themePath = join28(projectRoot, ".decantr", "custom", "themes", `${name}.json`);
6377
+ if (!existsSync27(themePath)) {
5733
6378
  console.error(error3(`Theme "${name}" not found at ${themePath}`));
5734
6379
  process.exitCode = 1;
5735
6380
  return;
5736
6381
  }
5737
6382
  try {
5738
- const theme = JSON.parse(readFileSync18(themePath, "utf-8"));
6383
+ const theme = JSON.parse(readFileSync20(themePath, "utf-8"));
5739
6384
  const result = validateCustomTheme(theme);
5740
6385
  if (result.valid) {
5741
6386
  console.log(success3(`Custom theme "${name}" is valid`));
@@ -5808,7 +6453,7 @@ function cmdHelp() {
5808
6453
  ${BOLD6}decantr${RESET13} \u2014 Design intelligence for AI-generated UI
5809
6454
 
5810
6455
  ${BOLD6}Usage:${RESET13}
5811
- decantr new <name> [--blueprint=X] [--archetype=X] [--theme=X]
6456
+ decantr new <name> [--blueprint=X] [--archetype=X] [--theme=X] [--workflow=greenfield] [--adoption=decantr-css]
5812
6457
  decantr magic <prompt> [--dry-run]
5813
6458
  decantr init [options]
5814
6459
  decantr status
@@ -5827,6 +6472,7 @@ ${BOLD6}Usage:${RESET13}
5827
6472
  decantr registry get-pack <manifest|scaffold|review|section|page|mutation> [id] [--namespace <namespace>] [--json] [--essence <path>] [--write-context]
5828
6473
  decantr registry critique-file <file> [--namespace <namespace>] [--json] [--essence <path>] [--treatments <path>]
5829
6474
  decantr registry audit-project [--namespace <namespace>] [--json] [--essence <path>] [--dist <path>] [--sources <dir>]
6475
+ decantr rules apply [--project=<path>]
5830
6476
  decantr validate [path]
5831
6477
  decantr theme <subcommand>
5832
6478
  decantr create <type> <name>
@@ -5845,6 +6491,10 @@ ${BOLD6}Init Options:${RESET13}
5845
6491
  --guard Guard mode: creative | guided | strict
5846
6492
  --density Spacing: compact | comfortable | spacious
5847
6493
  --shell Default shell layout
6494
+ --workflow Workflow: greenfield | brownfield | hybrid
6495
+ --adoption Adoption: contract-only | style-bridge | decantr-css
6496
+ --assistant-bridge Assistant rules: none | preview | apply
6497
+ --project App path inside a workspace/monorepo
5848
6498
  --existing Initialize in existing project
5849
6499
  --offline Force offline mode
5850
6500
  --yes, -y Accept defaults, skip confirmations
@@ -5874,6 +6524,7 @@ ${BOLD6}Commands:${RESET13}
5874
6524
  ${cyan3("analyze")} Brownfield entrypoint: scan an existing project and emit attach guidance
5875
6525
  ${cyan3("export")} Export design tokens to framework format (shadcn, tailwind, css-vars)
5876
6526
  ${cyan3("registry")} Registry management and intelligence summary
6527
+ ${cyan3("rules")} Preview/apply Decantr assistant bridge blocks to repo rule files
5877
6528
  ${cyan3("upgrade")} Check for content updates from registry
5878
6529
  ${cyan3("help")} Show this help
5879
6530
 
@@ -5881,7 +6532,11 @@ ${BOLD6}Examples:${RESET13}
5881
6532
  decantr new my-app --blueprint=carbon-ai-portal
5882
6533
  decantr magic "AI chatbot with dark cyber theme \u2014 bold and futuristic"
5883
6534
  decantr init
5884
- decantr init --existing --blueprint=saas-dashboard --theme=luminarum --yes
6535
+ decantr init --existing --adoption=contract-only --yes
6536
+ decantr init --existing --adoption=style-bridge --assistant-bridge=preview
6537
+ decantr init --workflow=greenfield --adoption=contract-only
6538
+ decantr init --project=apps/web --yes
6539
+ decantr rules apply
5885
6540
  decantr status
5886
6541
  decantr audit
5887
6542
  decantr audit src/pages/HomePage.tsx
@@ -5904,13 +6559,14 @@ ${BOLD6}Examples:${RESET13}
5904
6559
  decantr create pattern my-card
5905
6560
 
5906
6561
  ${BOLD6}Workflow Model:${RESET13}
5907
- ${cyan3("Greenfield blueprint")} decantr new / decantr magic
5908
- ${cyan3("Brownfield adoption")} decantr analyze -> decantr init --existing
6562
+ ${cyan3("Greenfield blueprint")} decantr new my-app --blueprint=X --workflow=greenfield --adoption=decantr-css
6563
+ ${cyan3("Greenfield contract")} decantr init --workflow=greenfield --adoption=contract-only
6564
+ ${cyan3("Brownfield adoption")} decantr analyze -> decantr init --existing --adoption=contract-only
5909
6565
  ${cyan3("Hybrid composition")} decantr add/remove, decantr theme switch, decantr registry, decantr upgrade
5910
6566
 
5911
6567
  ${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.
6568
+ Runnable starter adapters: ${cyan3("react-vite")}, ${cyan3("next-app")}
6569
+ Unsupported targets resolve through ${cyan3("generic-web")} contract-only mode until their starter adapters land.
5914
6570
  `);
5915
6571
  }
5916
6572
  async function main() {
@@ -5922,14 +6578,14 @@ async function main() {
5922
6578
  }
5923
6579
  if (command === "--version" || command === "-v" || command === "version") {
5924
6580
  try {
5925
- const here = dirname2(fileURLToPath2(import.meta.url));
6581
+ const here = dirname4(fileURLToPath2(import.meta.url));
5926
6582
  const candidates = [
5927
- join26(here, "..", "package.json"),
5928
- join26(here, "..", "..", "package.json")
6583
+ join28(here, "..", "package.json"),
6584
+ join28(here, "..", "..", "package.json")
5929
6585
  ];
5930
6586
  for (const candidate of candidates) {
5931
- if (existsSync25(candidate)) {
5932
- const pkg = JSON.parse(readFileSync18(candidate, "utf-8"));
6587
+ if (existsSync27(candidate)) {
6588
+ const pkg = JSON.parse(readFileSync20(candidate, "utf-8"));
5933
6589
  if (pkg.version) {
5934
6590
  console.log(pkg.version);
5935
6591
  return;
@@ -5976,7 +6632,10 @@ async function main() {
5976
6632
  shape: newOpts.shape,
5977
6633
  target: newOpts.target,
5978
6634
  offline: newOpts.offline === true,
5979
- registry: newOpts.registry
6635
+ registry: newOpts.registry,
6636
+ workflow: newOpts.workflow,
6637
+ adoption: newOpts.adoption,
6638
+ assistantBridge: newOpts["assistant-bridge"]
5980
6639
  });
5981
6640
  break;
5982
6641
  }
@@ -6015,7 +6674,7 @@ async function main() {
6015
6674
  break;
6016
6675
  }
6017
6676
  case "upgrade": {
6018
- const { cmdUpgrade } = await import("./upgrade-7LHT3S7E.js");
6677
+ const { cmdUpgrade } = await import("./upgrade-KG42WK5C.js");
6019
6678
  const applyFlag = args.includes("--apply");
6020
6679
  await cmdUpgrade(process.cwd(), { apply: applyFlag });
6021
6680
  break;
@@ -6407,7 +7066,47 @@ async function main() {
6407
7066
  break;
6408
7067
  }
6409
7068
  case "analyze": {
6410
- cmdAnalyze(process.cwd());
7069
+ let projectArg;
7070
+ for (let i = 1; i < args.length; i++) {
7071
+ if (args[i].startsWith("--project=")) {
7072
+ projectArg = args[i].split("=")[1];
7073
+ } else if (args[i] === "--project" && args[i + 1]) {
7074
+ projectArg = args[++i];
7075
+ }
7076
+ }
7077
+ const workspaceInfo = resolveWorkspaceInfo(process.cwd(), projectArg);
7078
+ if (workspaceInfo.requiresProjectSelection) {
7079
+ console.log(error3("This looks like a workspace root with multiple app candidates."));
7080
+ console.log(dim3(`Use --project=<path>. Candidates: ${workspaceInfo.appCandidates.join(", ")}`));
7081
+ process.exitCode = 1;
7082
+ break;
7083
+ }
7084
+ cmdAnalyze(workspaceInfo.appRoot, workspaceInfo);
7085
+ break;
7086
+ }
7087
+ case "rules": {
7088
+ const subcommand = args[1];
7089
+ if (subcommand !== "apply") {
7090
+ console.error(error3("Usage: decantr rules apply [--project=<path>]"));
7091
+ process.exitCode = 1;
7092
+ break;
7093
+ }
7094
+ let projectArg;
7095
+ for (let i = 2; i < args.length; i++) {
7096
+ if (args[i].startsWith("--project=")) {
7097
+ projectArg = args[i].split("=")[1];
7098
+ } else if (args[i] === "--project" && args[i + 1]) {
7099
+ projectArg = args[++i];
7100
+ }
7101
+ }
7102
+ const workspaceInfo = resolveWorkspaceInfo(process.cwd(), projectArg);
7103
+ const detected = detectProject(workspaceInfo.appRoot);
7104
+ const updated = applyAssistantBridge(workspaceInfo.appRoot, detected);
7105
+ if (updated.length === 0) {
7106
+ console.log(dim3("Assistant bridge rule files are already up to date."));
7107
+ } else {
7108
+ console.log(success3(`Applied Decantr assistant bridge to ${updated.join(", ")}.`));
7109
+ }
6411
7110
  break;
6412
7111
  }
6413
7112
  case "magic": {