@decantr/cli 1.7.27 → 1.7.29

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,18 +14,23 @@ import {
14
14
  scaffoldProject,
15
15
  syncRegistry,
16
16
  writeExecutionPackBundleArtifacts
17
- } from "./chunk-GCDFX7UE.js";
17
+ } from "./chunk-HULA6E2D.js";
18
18
  import {
19
19
  buildGuardRegistryContext,
20
+ createDoctrineMap,
21
+ scanAmbientContext,
20
22
  scanProjectInteractions,
21
- sendCliCommandTelemetry
22
- } from "./chunk-RSIOCKZF.js";
23
+ scanRoutes,
24
+ scanStyling,
25
+ sendCliCommandTelemetry,
26
+ writeDoctrineMap
27
+ } from "./chunk-DI2PLOJ6.js";
23
28
 
24
29
  // src/index.ts
25
- import { existsSync as existsSync27, mkdirSync as mkdirSync11, readdirSync as readdirSync7, readFileSync as readFileSync20, writeFileSync as writeFileSync14 } from "fs";
26
- import { basename as basename2, dirname as dirname4, isAbsolute, join as join28, resolve as resolve4 } from "path";
30
+ import { existsSync as existsSync26, mkdirSync as mkdirSync12, readdirSync as readdirSync6, readFileSync as readFileSync19, writeFileSync as writeFileSync15 } from "fs";
31
+ import { basename as basename2, dirname as dirname4, isAbsolute, join as join27, resolve as resolve4 } from "path";
27
32
  import { fileURLToPath as fileURLToPath2 } from "url";
28
- import { evaluateGuard, isV3 as isV36, validateEssence as validateEssence2 } from "@decantr/essence-spec";
33
+ import { evaluateGuard, isV3 as isV37, validateEssence as validateEssence2 } from "@decantr/essence-spec";
29
34
  import {
30
35
  CONTENT_TYPE_TO_API_CONTENT_TYPE as CONTENT_TYPE_TO_API_CONTENT_TYPE3,
31
36
  CONTENT_TYPES as GET_CONTENT_TYPES,
@@ -274,7 +279,7 @@ function buildAssistantBridgeContent(input) {
274
279
  lines.push("# Decantr Assistant Bridge");
275
280
  lines.push("");
276
281
  lines.push(
277
- "Use this bridge when an AI assistant works in a repository that already has local rules."
282
+ "Use this bridge when an AI assistant works in a repository that already has local rules and Decantr has compiled them into a brownfield contract."
278
283
  );
279
284
  lines.push("");
280
285
  lines.push(`- Workflow mode: ${input.workflowMode}`);
@@ -289,13 +294,13 @@ function buildAssistantBridgeContent(input) {
289
294
  lines.push("");
290
295
  lines.push(START);
291
296
  lines.push(
292
- "Before implementing Decantr-scoped work, read `DECANTR.md`, `decantr.essence.json`, and the compiled packs in `.decantr/context/`."
297
+ "Before implementing Decantr-scoped work, read `decantr.essence.json`, `.decantr/brownfield-report.md`, `.decantr/doctrine-map.json`, `.decantr/ambient-context.json`, and the compiled packs in `.decantr/context/`."
293
298
  );
294
299
  lines.push(
295
- "For brownfield adoption, preserve existing framework, routing, styling, package manager, and build conventions unless the Decantr contract explicitly requires a reviewed change."
300
+ "Treat Decantr as the reconciled contract layer and the original project docs/rules as cited evidence; if they conflict, stop and report the conflict instead of guessing."
296
301
  );
297
302
  lines.push(
298
- "Use `.decantr/context/scaffold-pack.md` as the primary compact contract, then read matching section/page packs for the surface being changed."
303
+ "For brownfield adoption, preserve existing framework, routing, styling, package manager, data boundaries, and build conventions unless the Decantr contract explicitly records a reviewed change."
299
304
  );
300
305
  lines.push(
301
306
  "Do not install `@decantr/css` or rewrite styling unless the project adoption mode says `decantr-css` or the task explicitly asks for it."
@@ -315,8 +320,9 @@ function writeAssistantBridgePreview(input) {
315
320
  function bridgeBlock() {
316
321
  return [
317
322
  START,
318
- "Before implementing Decantr-scoped work, read `DECANTR.md`, `decantr.essence.json`, and `.decantr/context/scaffold-pack.md` first.",
319
- "For brownfield adoption, preserve existing framework, routing, styling, package manager, and build conventions unless the Decantr contract explicitly requires a reviewed change.",
323
+ "Before implementing Decantr-scoped work, read `decantr.essence.json`, `.decantr/brownfield-report.md`, `.decantr/doctrine-map.json`, `.decantr/ambient-context.json`, and `.decantr/context/scaffold-pack.md` first.",
324
+ "Treat Decantr as the reconciled contract layer and the original project docs/rules as cited evidence; if they conflict, stop and report the conflict instead of guessing.",
325
+ "For brownfield adoption, preserve existing framework, routing, styling, package manager, data boundaries, and build conventions unless the Decantr contract explicitly records a reviewed change.",
320
326
  "Do not install `@decantr/css` or rewrite styling unless the project adoption mode says `decantr-css` or the task explicitly asks for it.",
321
327
  END,
322
328
  ""
@@ -345,6 +351,16 @@ description: Decantr project contract and brownfield adoption bridge
345
351
  alwaysApply: true
346
352
  ---
347
353
 
354
+ ${bridgeBlock()}`;
355
+ if (existsSync3(path) && readFileSync3(path, "utf-8") === content) return false;
356
+ mkdirSync2(dirname(path), { recursive: true });
357
+ writeFileSync3(path, content);
358
+ return true;
359
+ }
360
+ function writeClaudeRule(projectRoot) {
361
+ const path = join3(projectRoot, ".claude", "rules", "decantr.md");
362
+ const content = `# Decantr Brownfield Bridge
363
+
348
364
  ${bridgeBlock()}`;
349
365
  if (existsSync3(path) && readFileSync3(path, "utf-8") === content) return false;
350
366
  mkdirSync2(dirname(path), { recursive: true });
@@ -353,7 +369,13 @@ ${bridgeBlock()}`;
353
369
  }
354
370
  function applyAssistantBridge(projectRoot, detected) {
355
371
  const updated = [];
356
- const markdownTargets = ["CLAUDE.md", "AGENTS.md", "GEMINI.md", "copilot-instructions.md"];
372
+ const markdownTargets = [
373
+ "CLAUDE.md",
374
+ "AGENTS.md",
375
+ "GEMINI.md",
376
+ "copilot-instructions.md",
377
+ ".github/copilot-instructions.md"
378
+ ];
357
379
  for (const target of markdownTargets) {
358
380
  if (detected.existingRuleFiles.includes(target) && upsertMarkdownBlock(join3(projectRoot, target))) {
359
381
  updated.push(target);
@@ -362,6 +384,12 @@ function applyAssistantBridge(projectRoot, detected) {
362
384
  if (detected.existingRuleFiles.includes(".cursorrules")) {
363
385
  if (upsertMarkdownBlock(join3(projectRoot, ".cursorrules"))) updated.push(".cursorrules");
364
386
  }
387
+ if (detected.existingRuleFiles.includes(".windsurfrules")) {
388
+ if (upsertMarkdownBlock(join3(projectRoot, ".windsurfrules"))) updated.push(".windsurfrules");
389
+ }
390
+ if (detected.existingRuleFiles.includes(".claude/rules")) {
391
+ if (writeClaudeRule(projectRoot)) updated.push(".claude/rules/decantr.md");
392
+ }
365
393
  if (detected.existingRuleFiles.includes(".cursor/rules")) {
366
394
  if (writeCursorRule(projectRoot)) updated.push(".cursor/rules/decantr.mdc");
367
395
  }
@@ -371,13 +399,540 @@ function applyAssistantBridge(projectRoot, detected) {
371
399
  return updated;
372
400
  }
373
401
 
402
+ // src/brownfield-proposal.ts
403
+ import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
404
+ import { join as join4 } from "path";
405
+ import { isV3 as isV32 } from "@decantr/essence-spec";
406
+ function slugify(value, fallback) {
407
+ const slug = value.replace(/^\//, "").replace(/:[^/]+/g, "param").replace(/\*/g, "all").replace(/[^a-zA-Z0-9]+/g, "-").replace(/^-+|-+$/g, "").toLowerCase();
408
+ return slug || fallback;
409
+ }
410
+ var ROUTE_DOMAINS = [
411
+ {
412
+ sectionId: "observed-auth",
413
+ role: "gateway",
414
+ label: "Authentication",
415
+ description: "Observed authentication, sign-in, sign-up, callback, and account-entry surfaces.",
416
+ featureHints: ["auth"],
417
+ priority: 10
418
+ },
419
+ {
420
+ sectionId: "observed-rbac",
421
+ role: "auxiliary",
422
+ label: "RBAC and User Administration",
423
+ description: "Observed role, permission, user-management, and access-control administration surfaces.",
424
+ featureHints: ["admin", "team", "settings", "auth"],
425
+ priority: 30
426
+ },
427
+ {
428
+ sectionId: "observed-billing",
429
+ role: "auxiliary",
430
+ label: "Billing",
431
+ description: "Observed billing, subscription, plan, payment, invoice, and checkout surfaces.",
432
+ featureHints: ["billing", "admin"],
433
+ priority: 34
434
+ },
435
+ {
436
+ sectionId: "observed-admin",
437
+ role: "auxiliary",
438
+ label: "Administration",
439
+ description: "Observed administrative and operational management surfaces.",
440
+ featureHints: ["admin", "team"],
441
+ priority: 36
442
+ },
443
+ {
444
+ sectionId: "observed-settings",
445
+ role: "auxiliary",
446
+ label: "Settings",
447
+ description: "Observed account, profile, client-settings, organization, and user-preference surfaces.",
448
+ featureHints: ["settings", "profile", "team"],
449
+ priority: 38
450
+ },
451
+ {
452
+ sectionId: "observed-api-access",
453
+ role: "auxiliary",
454
+ label: "API Access",
455
+ description: "Observed developer, API key, token, and credential-management surfaces.",
456
+ featureHints: ["api-keys", "admin"],
457
+ priority: 40
458
+ },
459
+ {
460
+ sectionId: "observed-reporting",
461
+ role: "primary",
462
+ label: "Reporting",
463
+ description: "Observed reporting, analytics, metrics, dashboard, and compliance surfaces.",
464
+ featureHints: ["dashboard", "admin"],
465
+ priority: 58
466
+ },
467
+ {
468
+ sectionId: "observed-facilities",
469
+ role: "primary",
470
+ label: "Facilities",
471
+ description: "Observed facility, location, bin, lifecycle, and operational surfaces.",
472
+ featureHints: ["file-upload", "dashboard"],
473
+ priority: 60
474
+ },
475
+ {
476
+ sectionId: "observed-lab-results",
477
+ role: "primary",
478
+ label: "Lab Results",
479
+ description: "Observed lab result, report detail, compliance, and evidence-review surfaces.",
480
+ featureHints: ["dashboard", "file-upload"],
481
+ priority: 62
482
+ },
483
+ {
484
+ sectionId: "observed-ai-insights",
485
+ role: "primary",
486
+ label: "AI Insights",
487
+ description: "Observed AI insight, assistant, intelligence, recommendation, and automation surfaces.",
488
+ featureHints: ["dashboard", "chat"],
489
+ priority: 64
490
+ },
491
+ {
492
+ sectionId: "observed-communications",
493
+ role: "primary",
494
+ label: "Communications",
495
+ description: "Observed chat, message, inbox, notification, and announcement surfaces.",
496
+ featureHints: ["chat", "notifications"],
497
+ priority: 66
498
+ },
499
+ {
500
+ sectionId: "observed-brand-portal",
501
+ role: "primary",
502
+ label: "Brand Portal",
503
+ description: "Observed brand, asset, media, gallery, and upload surfaces.",
504
+ featureHints: ["file-upload", "content"],
505
+ priority: 68
506
+ },
507
+ {
508
+ sectionId: "observed-content",
509
+ role: "primary",
510
+ label: "Content",
511
+ description: "Observed CMS, editor, article, post, catalog, and content-management surfaces.",
512
+ featureHints: ["content", "search"],
513
+ priority: 70
514
+ },
515
+ {
516
+ sectionId: "observed-dashboard",
517
+ role: "primary",
518
+ label: "Dashboard",
519
+ description: "Observed dashboard, overview, workspace, and primary application surfaces.",
520
+ featureHints: ["dashboard", "search"],
521
+ priority: 72
522
+ },
523
+ {
524
+ sectionId: "observed-public",
525
+ role: "public",
526
+ label: "Public",
527
+ description: "Observed public-facing, marketing, docs, blog, legal, contact, and home routes.",
528
+ featureHints: ["content", "search"],
529
+ priority: 0
530
+ },
531
+ {
532
+ sectionId: "observed-primary",
533
+ role: "primary",
534
+ label: "Primary Application",
535
+ description: "Observed primary application routes from the existing application.",
536
+ featureHints: [],
537
+ priority: 90
538
+ }
539
+ ];
540
+ function routeDomain(path) {
541
+ const lower = path.toLowerCase();
542
+ if (/\/(login|signin|sign-in|signup|sign-up|register|auth|callback|reset-password|forgot-password)/.test(
543
+ lower
544
+ )) {
545
+ return ROUTE_DOMAINS[0];
546
+ }
547
+ if (/\/(rbac|roles?|permissions?|manage-rbac|manage-users|user-management|access-control)\b/.test(lower)) {
548
+ return ROUTE_DOMAINS.find((domain) => domain.sectionId === "observed-rbac");
549
+ }
550
+ if (/\/(billing|subscription|subscriptions|pricing|plans|checkout|payment|payments|stripe|invoice|invoices)\b/.test(lower)) {
551
+ return ROUTE_DOMAINS.find((domain) => domain.sectionId === "observed-billing");
552
+ }
553
+ if (/\/(admin|moderation|moderate|system-logs)\b/.test(lower)) {
554
+ return ROUTE_DOMAINS.find((domain) => domain.sectionId === "observed-admin");
555
+ }
556
+ if (/\/(settings|profile|account|client-settings|user-settings|preferences|organization|org|team|members)\b/.test(lower)) {
557
+ return ROUTE_DOMAINS.find((domain) => domain.sectionId === "observed-settings");
558
+ }
559
+ if (/\/(api-keys?|api-key-management|developer|developers|tokens?|credentials?)\b/.test(lower)) {
560
+ return ROUTE_DOMAINS.find((domain) => domain.sectionId === "observed-api-access");
561
+ }
562
+ if (/\/(reports?|analytics|metrics|stats|compliance|environmental|operations)\b/.test(lower)) {
563
+ return ROUTE_DOMAINS.find((domain) => domain.sectionId === "observed-reporting");
564
+ }
565
+ if (/\/(facilities|facility|locations?|bins?|lifecycle)\b/.test(lower)) {
566
+ return ROUTE_DOMAINS.find((domain) => domain.sectionId === "observed-facilities");
567
+ }
568
+ if (/\/(lab-results?|lab|results?)\b/.test(lower)) {
569
+ return ROUTE_DOMAINS.find((domain) => domain.sectionId === "observed-lab-results");
570
+ }
571
+ if (/\/(ai-insights?|insights?|ai|assistant|automation|recommendations?)\b/.test(lower)) {
572
+ return ROUTE_DOMAINS.find((domain) => domain.sectionId === "observed-ai-insights");
573
+ }
574
+ if (/\/(chat|messages?|messaging|conversations?|inbox|notifications?|alerts?)\b/.test(lower)) {
575
+ return ROUTE_DOMAINS.find((domain) => domain.sectionId === "observed-communications");
576
+ }
577
+ if (/\/(brand|brand-portal|assets?|media|gallery|uploads?)\b/.test(lower)) {
578
+ return ROUTE_DOMAINS.find((domain) => domain.sectionId === "observed-brand-portal");
579
+ }
580
+ if (/\/(content|cms|editor|posts?|articles?|catalog|library|registry|marketplace)\b/.test(lower)) {
581
+ return ROUTE_DOMAINS.find((domain) => domain.sectionId === "observed-content");
582
+ }
583
+ if (path === "/" || /\/(about|contact|pricing|blog|docs|legal|privacy|terms|marketing)\b/.test(lower)) {
584
+ return ROUTE_DOMAINS.find((domain) => domain.sectionId === "observed-public");
585
+ }
586
+ if (/\/(dashboard|overview|workspace|home)\b/.test(lower)) {
587
+ return ROUTE_DOMAINS.find((domain) => domain.sectionId === "observed-dashboard");
588
+ }
589
+ return ROUTE_DOMAINS.find((domain) => domain.sectionId === "observed-primary");
590
+ }
591
+ function platformForTarget(target, routeStrategy) {
592
+ const normalized = target.toLowerCase();
593
+ if (routeStrategy === "app-router" || routeStrategy === "pages-router" || routeStrategy === "mixed-next-router" || routeStrategy === "sveltekit-router" || routeStrategy === "nuxt-router") {
594
+ return { type: "ssr", routing: "pathname" };
595
+ }
596
+ if (normalized === "nextjs" || normalized === "nuxt" || normalized === "astro") {
597
+ return { type: "ssr", routing: "pathname" };
598
+ }
599
+ if (routeStrategy === "react-router") return { type: "spa", routing: "history" };
600
+ if (normalized === "html") return { type: "static", routing: "pathname" };
601
+ return { type: "spa", routing: "history" };
602
+ }
603
+ function featuresForSection(sectionId, features) {
604
+ const domain = ROUTE_DOMAINS.find((candidate) => candidate.sectionId === sectionId);
605
+ if (domain && domain.featureHints.length > 0) {
606
+ return features.filter((f) => domain.featureHints.includes(f));
607
+ }
608
+ return features;
609
+ }
610
+ function doctrineEffects(ambient) {
611
+ const effects = {};
612
+ if (ambient.summary["security-data"] > 0) {
613
+ effects["doctrine-security-data"] = "Preserve existing auth, middleware, schema, migration, RLS, and data-boundary rules unless the user explicitly approves a reviewed migration.";
614
+ }
615
+ if (ambient.summary["design-system"] > 0) {
616
+ effects["doctrine-design-system"] = "Preserve existing design-system tokens, component conventions, and styling framework unless the user explicitly opts into style migration.";
617
+ }
618
+ if (ambient.summary["workflow-ci"] > 0) {
619
+ effects["doctrine-workflow-ci"] = "Use existing package-manager, build, test, lint, deployment, and CI conventions as validation evidence.";
620
+ }
621
+ if (ambient.summary["feature-business"] > 0) {
622
+ effects["doctrine-feature-business"] = "Treat initiative, memory, and feature docs as business-domain evidence; verify stale risks before enforcing them.";
623
+ }
624
+ return Object.keys(effects).length > 0 ? effects : void 0;
625
+ }
626
+ function createBrownfieldProposal(input) {
627
+ const target = input.project.framework !== "unknown" ? input.project.framework : "generic-web";
628
+ const shell = input.layout.shellPattern || "observed-existing-shell";
629
+ const routeMap = {};
630
+ const sectionMap = /* @__PURE__ */ new Map();
631
+ const observedRoutes = input.routes.routes.length > 0 ? input.routes.routes : [{ path: "/", file: ".", hasLayout: false }];
632
+ for (const route of observedRoutes) {
633
+ const classified = routeDomain(route.path);
634
+ const section = sectionMap.get(classified.sectionId) ?? {
635
+ id: classified.sectionId,
636
+ role: classified.role,
637
+ shell,
638
+ features: featuresForSection(classified.sectionId, input.features.detected),
639
+ description: classified.description,
640
+ pages: [],
641
+ directives: [
642
+ `Semantic domain: ${classified.label}. Use this as an observed product-domain grouping, not a scaffold category.`,
643
+ "Preserve existing route files, layouts, data boundaries, and styling conventions unless the user explicitly approves a migration."
644
+ ]
645
+ };
646
+ const pageId = route.path === "/" ? "home" : slugify(route.path, "observed-page");
647
+ if (!section.pages.some((page) => page.id === pageId)) {
648
+ section.pages.push({
649
+ id: pageId,
650
+ route: route.path,
651
+ layout: ["existing-surface"],
652
+ directives: [
653
+ `Observed source: ${route.file}. Treat this route as existing product surface, not scaffold territory.`
654
+ ]
655
+ });
656
+ }
657
+ routeMap[route.path] = { section: section.id, page: pageId };
658
+ sectionMap.set(section.id, section);
659
+ }
660
+ const sections = [...sectionMap.values()].sort((a, b) => {
661
+ const aPriority = ROUTE_DOMAINS.find((domain) => domain.sectionId === a.id)?.priority ?? 99;
662
+ const bPriority = ROUTE_DOMAINS.find((domain) => domain.sectionId === b.id)?.priority ?? 99;
663
+ return aPriority - bPriority || a.id.localeCompare(b.id);
664
+ });
665
+ const essence = {
666
+ version: "3.1.0",
667
+ dna: {
668
+ theme: {
669
+ id: "existing",
670
+ mode: input.styling.darkMode ? "dark" : "auto",
671
+ shape: "rounded"
672
+ },
673
+ spacing: {
674
+ base_unit: 4,
675
+ scale: "observed",
676
+ density: "comfortable",
677
+ content_gap: "_gap4"
678
+ },
679
+ typography: {
680
+ scale: "observed",
681
+ heading_weight: 600,
682
+ body_weight: 400
683
+ },
684
+ color: {
685
+ palette: input.styling.approach === "unknown" ? "observed" : input.styling.approach,
686
+ accent_count: Math.max(1, Object.keys(input.styling.colors).length),
687
+ cvd_preference: "auto"
688
+ },
689
+ radius: {
690
+ philosophy: "observed",
691
+ base: 8
692
+ },
693
+ elevation: {
694
+ system: "observed",
695
+ max_levels: 3
696
+ },
697
+ motion: {
698
+ preference: input.dependencies.ui.includes("framer-motion") ? "expressive" : "subtle",
699
+ duration_scale: 1,
700
+ reduce_motion: true
701
+ },
702
+ accessibility: {
703
+ wcag_level: "AA",
704
+ focus_visible: true,
705
+ skip_nav: true
706
+ },
707
+ personality: ["observed brownfield product"],
708
+ constraints: {
709
+ mode: "existing project wins",
710
+ typography: "derive from existing docs, CSS variables, and component conventions",
711
+ borders: "derive from existing design system",
712
+ corners: "derive from existing design system",
713
+ shadows: "derive from existing design system",
714
+ effects: doctrineEffects(input.ambient)
715
+ }
716
+ },
717
+ blueprint: {
718
+ sections,
719
+ features: input.features.detected,
720
+ routes: routeMap
721
+ },
722
+ meta: {
723
+ archetype: "observed-brownfield",
724
+ target,
725
+ platform: platformForTarget(target, input.routes.strategy),
726
+ guard: {
727
+ mode: "guided",
728
+ dna_enforcement: "error",
729
+ blueprint_enforcement: "warn",
730
+ interactions_enforcement: "warn"
731
+ },
732
+ navigation: {
733
+ command_palette: false
734
+ }
735
+ }
736
+ };
737
+ return {
738
+ version: 1,
739
+ kind: "brownfield-observed-essence",
740
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
741
+ status: "proposed",
742
+ essence,
743
+ evidence: {
744
+ routeCount: input.routes.routes.length,
745
+ componentCount: input.components.componentCount,
746
+ featureCount: input.features.detected.length,
747
+ ambientContextCount: input.ambient.items.length,
748
+ stylingApproach: input.styling.approach,
749
+ shell,
750
+ semanticSectionCount: sections.length
751
+ },
752
+ conflicts: input.ambient.conflicts,
753
+ staleRisks: input.ambient.staleRisks,
754
+ acceptance: {
755
+ create: "decantr init --existing --accept-proposal",
756
+ merge: "decantr init --existing --merge-proposal",
757
+ replace: "decantr init --existing --replace-essence"
758
+ }
759
+ };
760
+ }
761
+ function proposalPath(projectRoot) {
762
+ return join4(projectRoot, ".decantr", "observed-essence.proposal.json");
763
+ }
764
+ function writeBrownfieldProposal(projectRoot, proposal) {
765
+ const decantrDir = join4(projectRoot, ".decantr");
766
+ mkdirSync3(decantrDir, { recursive: true });
767
+ writeFileSync4(proposalPath(projectRoot), JSON.stringify(proposal, null, 2) + "\n", "utf-8");
768
+ }
769
+ function readBrownfieldProposal(projectRoot) {
770
+ const path = proposalPath(projectRoot);
771
+ if (!existsSync4(path)) return null;
772
+ try {
773
+ const parsed = JSON.parse(readFileSync4(path, "utf-8"));
774
+ if (parsed.kind !== "brownfield-observed-essence" || !isV32(parsed.essence)) return null;
775
+ return parsed;
776
+ } catch {
777
+ return null;
778
+ }
779
+ }
780
+ function generateBrownfieldReport(proposal, ambient, doctrine) {
781
+ const lines = [];
782
+ lines.push("# Decantr Brownfield Report");
783
+ lines.push("");
784
+ lines.push("Decantr analyzed this app as an existing product. The proposal below is observed from the codebase and ambient project doctrine; it is not a Decantr scaffold.");
785
+ lines.push("");
786
+ lines.push("## Proposal");
787
+ lines.push("");
788
+ lines.push(`- Routes observed: ${proposal.evidence.routeCount}`);
789
+ lines.push(`- Semantic sections inferred: ${proposal.evidence.semanticSectionCount}`);
790
+ lines.push(`- Components observed: ${proposal.evidence.componentCount}`);
791
+ lines.push(`- Features observed: ${proposal.evidence.featureCount}`);
792
+ lines.push(`- Styling approach: ${proposal.evidence.stylingApproach}`);
793
+ lines.push(`- Shell signal: ${proposal.evidence.shell}`);
794
+ lines.push(`- Ambient context items: ${proposal.evidence.ambientContextCount}`);
795
+ lines.push(`- Theme posture: existing project wins`);
796
+ if (doctrine) {
797
+ lines.push(`- Doctrine sources ranked: ${doctrine.sources.length}`);
798
+ lines.push(`- Highest precedence source: ${doctrine.sources[0]?.path ?? "none"}`);
799
+ }
800
+ lines.push("");
801
+ lines.push("## Immediate Value");
802
+ lines.push("");
803
+ lines.push("- Converts scattered brownfield routes, styling signals, docs, rules, schemas, and CI evidence into one Decantr contract without scaffolding runtime code.");
804
+ lines.push("- Gives assistants a compiled contract layer while keeping original docs/rules available as cited evidence.");
805
+ lines.push("- Enables `decantr check --brownfield` to catch route drift, unsafe defaults, doctrine conflicts, and missing context.");
806
+ lines.push("");
807
+ lines.push("## Non-Goals By Default");
808
+ lines.push("");
809
+ lines.push("- Does not install Decantr CSS, switch themes, replace layouts, rewrite docs, mutate assistant rules, or import registry patterns unless explicitly requested.");
810
+ lines.push("- Does not treat stale migration or completion summaries as current doctrine without verification.");
811
+ lines.push("");
812
+ lines.push("## Accepted Evidence");
813
+ lines.push("");
814
+ lines.push("- Existing framework, routes, layout shell, feature names, styling signals, and ambient doctrine were used as evidence for the proposal.");
815
+ lines.push("- Decantr registry content, Decantr CSS, and default Decantr themes were not accepted as brownfield defaults.");
816
+ lines.push("");
817
+ lines.push("## Uncertain Evidence");
818
+ lines.push("");
819
+ const uncertain = [];
820
+ if (proposal.evidence.routeCount === 0) uncertain.push("No explicit route declarations were found; proposal uses `/` as a placeholder observation.");
821
+ if (proposal.evidence.stylingApproach === "unknown") uncertain.push("Styling approach was not confidently detected.");
822
+ if (proposal.evidence.ambientContextCount === 0) uncertain.push("No ambient docs, rules, CI, schema, or project-memory files were detected.");
823
+ if (uncertain.length === 0) {
824
+ lines.push("- No major uncertainty detected by the first-pass scanners.");
825
+ } else {
826
+ for (const item of uncertain) lines.push(`- ${item}`);
827
+ }
828
+ lines.push("");
829
+ lines.push("## Acceptance");
830
+ lines.push("");
831
+ lines.push(`- Create initial essence: \`${proposal.acceptance.create}\``);
832
+ lines.push(`- Merge into existing essence: \`${proposal.acceptance.merge}\``);
833
+ lines.push(`- Replace existing essence: \`${proposal.acceptance.replace}\``);
834
+ lines.push("");
835
+ lines.push("## Ambient Context Summary");
836
+ lines.push("");
837
+ for (const [role, count] of Object.entries(ambient.summary)) {
838
+ if (count > 0) lines.push(`- ${role}: ${count}`);
839
+ }
840
+ lines.push("");
841
+ if (doctrine) {
842
+ lines.push("## Doctrine Precedence");
843
+ lines.push("");
844
+ for (const source of doctrine.sources.slice(0, 12)) {
845
+ lines.push(
846
+ `- ${source.path}: ${source.area}, precedence ${source.precedence}, ${source.currency}`
847
+ );
848
+ }
849
+ if (doctrine.sources.length > 12) {
850
+ lines.push(`- +${doctrine.sources.length - 12} more source(s) in .decantr/doctrine-map.json`);
851
+ }
852
+ lines.push("");
853
+ lines.push("## Doctrine Resolution Suggestions");
854
+ lines.push("");
855
+ if (doctrine.resolutions.length === 0) {
856
+ lines.push("- No doctrine resolution suggestions were needed.");
857
+ } else {
858
+ for (const resolution of doctrine.resolutions.slice(0, 8)) {
859
+ lines.push(`- ${resolution.issue} ${resolution.recommendation}`);
860
+ if (resolution.preferredSources.length > 0) {
861
+ lines.push(` Preferred evidence: ${resolution.preferredSources.join(", ")}`);
862
+ }
863
+ }
864
+ if (doctrine.resolutions.length > 8) {
865
+ lines.push(`- +${doctrine.resolutions.length - 8} more resolution suggestion(s) in .decantr/doctrine-map.json`);
866
+ }
867
+ }
868
+ lines.push("");
869
+ }
870
+ lines.push("## Notable Context Evidence");
871
+ lines.push("");
872
+ const notable = ambient.items.slice(0, 30);
873
+ if (notable.length === 0) {
874
+ lines.push("- None detected.");
875
+ } else {
876
+ for (const item of notable) {
877
+ const cite = item.safeToCite ? "safe to cite" : "do not cite directly";
878
+ lines.push(`- ${item.path} (${item.role}, ${item.type}, ${cite}, confidence ${item.confidence.toFixed(2)})`);
879
+ }
880
+ if (ambient.items.length > notable.length) {
881
+ lines.push(`- +${ambient.items.length - notable.length} more item(s) in .decantr/ambient-context.json`);
882
+ }
883
+ }
884
+ lines.push("");
885
+ lines.push("## Conflicts And Stale Risks");
886
+ lines.push("");
887
+ if (proposal.conflicts.length === 0 && proposal.staleRisks.length === 0) {
888
+ lines.push("- No obvious doctrine conflicts or stale-document risks were detected.");
889
+ } else {
890
+ for (const conflict of proposal.conflicts) lines.push(`- Conflict: ${conflict}`);
891
+ for (const risk of proposal.staleRisks.slice(0, 8)) lines.push(`- Stale risk: ${risk}`);
892
+ }
893
+ lines.push("");
894
+ lines.push("## Assistant Posture");
895
+ lines.push("");
896
+ lines.push("LLMs should treat Decantr as the compiled contract layer and the original files as cited evidence. Do not migrate, rewrite, or delete existing docs/rules unless the user explicitly asks for doctrine migration.");
897
+ lines.push("");
898
+ return `${lines.join("\n")}
899
+ `;
900
+ }
901
+ function mergeEssenceWithProposal(existing, proposal) {
902
+ const next = JSON.parse(JSON.stringify(existing));
903
+ const proposed = proposal.essence;
904
+ next.version = "3.1.0";
905
+ next.blueprint.features = [
906
+ .../* @__PURE__ */ new Set([...next.blueprint.features ?? [], ...proposed.blueprint.features ?? []])
907
+ ];
908
+ next.blueprint.routes = {
909
+ ...proposed.blueprint.routes ?? {},
910
+ ...next.blueprint.routes ?? {}
911
+ };
912
+ const existingSections = new Map((next.blueprint.sections ?? []).map((section) => [section.id, section]));
913
+ for (const proposedSection of proposed.blueprint.sections ?? []) {
914
+ const current = existingSections.get(proposedSection.id);
915
+ if (!current) {
916
+ existingSections.set(proposedSection.id, proposedSection);
917
+ continue;
918
+ }
919
+ const pageIds = new Set(current.pages.map((page) => page.id));
920
+ current.pages.push(...proposedSection.pages.filter((page) => !pageIds.has(page.id)));
921
+ current.features = [.../* @__PURE__ */ new Set([...current.features ?? [], ...proposedSection.features ?? []])];
922
+ }
923
+ next.blueprint.sections = [...existingSections.values()];
924
+ delete next.blueprint.pages;
925
+ delete next.blueprint.shell;
926
+ return next;
927
+ }
928
+
374
929
  // src/commands/analyze.ts
375
- import { existsSync as existsSync12, mkdirSync as mkdirSync3, writeFileSync as writeFileSync4 } from "fs";
376
- import { join as join12 } from "path";
930
+ import { existsSync as existsSync11, mkdirSync as mkdirSync4, writeFileSync as writeFileSync5 } from "fs";
931
+ import { join as join11 } from "path";
377
932
 
378
933
  // src/analyzers/components.ts
379
- import { existsSync as existsSync4, readdirSync, statSync as statSync2 } from "fs";
380
- import { join as join4 } from "path";
934
+ import { existsSync as existsSync5, readdirSync, statSync as statSync2 } from "fs";
935
+ import { join as join5 } from "path";
381
936
  var PAGE_EXTENSIONS = /* @__PURE__ */ new Set([".tsx", ".ts", ".jsx", ".js"]);
382
937
  var ROOT_COMPONENT_CANDIDATES = [
383
938
  "src/App.tsx",
@@ -399,7 +954,7 @@ function countFilesRecursive(dir, extensions) {
399
954
  }
400
955
  for (const entry of entries) {
401
956
  if (entry.startsWith(".") || entry === "node_modules") continue;
402
- const fullPath = join4(dir, entry);
957
+ const fullPath = join5(dir, entry);
403
958
  try {
404
959
  const stat = statSync2(fullPath);
405
960
  if (stat.isDirectory()) {
@@ -425,7 +980,7 @@ function countPageFiles(dir) {
425
980
  }
426
981
  for (const entry of entries) {
427
982
  if (entry.startsWith(".") || entry === "node_modules") continue;
428
- const fullPath = join4(dir, entry);
983
+ const fullPath = join5(dir, entry);
429
984
  try {
430
985
  const stat = statSync2(fullPath);
431
986
  if (stat.isDirectory()) {
@@ -443,27 +998,27 @@ function countPageFiles(dir) {
443
998
  function scanComponents(projectRoot) {
444
999
  let pageCount = 0;
445
1000
  const componentDirs = [];
446
- const appDirs = [join4(projectRoot, "src", "app"), join4(projectRoot, "app")];
1001
+ const appDirs = [join5(projectRoot, "src", "app"), join5(projectRoot, "app")];
447
1002
  for (const dir of appDirs) {
448
- if (existsSync4(dir)) {
1003
+ if (existsSync5(dir)) {
449
1004
  pageCount += countPageFiles(dir);
450
1005
  }
451
1006
  }
452
- const pagesDirs = [join4(projectRoot, "src", "pages"), join4(projectRoot, "pages")];
1007
+ const pagesDirs = [join5(projectRoot, "src", "pages"), join5(projectRoot, "pages")];
453
1008
  for (const dir of pagesDirs) {
454
- if (existsSync4(dir)) {
1009
+ if (existsSync5(dir)) {
455
1010
  pageCount += countFilesRecursive(dir, PAGE_EXTENSIONS);
456
1011
  }
457
1012
  }
458
1013
  let componentCount = 0;
459
1014
  const componentPaths = [
460
- join4(projectRoot, "src", "components"),
461
- join4(projectRoot, "components"),
462
- join4(projectRoot, "src", "ui"),
463
- join4(projectRoot, "ui")
1015
+ join5(projectRoot, "src", "components"),
1016
+ join5(projectRoot, "components"),
1017
+ join5(projectRoot, "src", "ui"),
1018
+ join5(projectRoot, "ui")
464
1019
  ];
465
1020
  for (const dir of componentPaths) {
466
- if (existsSync4(dir)) {
1021
+ if (existsSync5(dir)) {
467
1022
  const count = countFilesRecursive(dir, PAGE_EXTENSIONS);
468
1023
  if (count > 0) {
469
1024
  componentCount += count;
@@ -473,7 +1028,7 @@ function scanComponents(projectRoot) {
473
1028
  }
474
1029
  }
475
1030
  const hasRootAppComponent = ROOT_COMPONENT_CANDIDATES.some(
476
- (relativePath) => existsSync4(join4(projectRoot, relativePath))
1031
+ (relativePath) => existsSync5(join5(projectRoot, relativePath))
477
1032
  );
478
1033
  if (pageCount === 0 && hasRootAppComponent) {
479
1034
  pageCount = 1;
@@ -492,8 +1047,8 @@ function scanComponents(projectRoot) {
492
1047
  }
493
1048
 
494
1049
  // src/analyzers/dependencies.ts
495
- import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
496
- import { join as join5 } from "path";
1050
+ import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
1051
+ import { join as join6 } from "path";
497
1052
  var CATEGORIES = {
498
1053
  ui: [
499
1054
  "react",
@@ -615,6 +1170,8 @@ var CATEGORIES = {
615
1170
  "@emotion/react",
616
1171
  "@emotion/styled",
617
1172
  "styled-jsx",
1173
+ "bootstrap",
1174
+ "react-bootstrap",
618
1175
  "linaria",
619
1176
  "vanilla-extract",
620
1177
  "clsx",
@@ -643,11 +1200,11 @@ function scanDependencies(projectRoot) {
643
1200
  styling: [],
644
1201
  other: []
645
1202
  };
646
- const pkgPath = join5(projectRoot, "package.json");
647
- if (!existsSync5(pkgPath)) return result;
1203
+ const pkgPath = join6(projectRoot, "package.json");
1204
+ if (!existsSync6(pkgPath)) return result;
648
1205
  let pkg;
649
1206
  try {
650
- pkg = JSON.parse(readFileSync4(pkgPath, "utf-8"));
1207
+ pkg = JSON.parse(readFileSync5(pkgPath, "utf-8"));
651
1208
  } catch {
652
1209
  return result;
653
1210
  }
@@ -667,8 +1224,8 @@ function scanDependencies(projectRoot) {
667
1224
  }
668
1225
 
669
1226
  // src/analyzers/features.ts
670
- import { existsSync as existsSync6, readdirSync as readdirSync2, statSync as statSync3 } from "fs";
671
- import { join as join6 } from "path";
1227
+ import { existsSync as existsSync7, readdirSync as readdirSync2, statSync as statSync3 } from "fs";
1228
+ import { join as join7 } from "path";
672
1229
  var FEATURE_PATTERNS = {
673
1230
  auth: [
674
1231
  "login",
@@ -721,7 +1278,7 @@ function collectPaths(dir, baseDir, depth = 0) {
721
1278
  for (const entry of entries) {
722
1279
  if (entry.startsWith(".") || entry.startsWith("_") || entry === "node_modules" || entry === "api")
723
1280
  continue;
724
- const fullPath = join6(dir, entry);
1281
+ const fullPath = join7(dir, entry);
725
1282
  const relPath = fullPath.slice(baseDir.length + 1);
726
1283
  paths.push(relPath);
727
1284
  try {
@@ -737,16 +1294,18 @@ function scanFeatures(projectRoot) {
737
1294
  const detected = [];
738
1295
  const evidence = {};
739
1296
  const scanDirs = [
740
- join6(projectRoot, "src", "app"),
741
- join6(projectRoot, "app"),
742
- join6(projectRoot, "src", "pages"),
743
- join6(projectRoot, "pages"),
744
- join6(projectRoot, "src", "components"),
745
- join6(projectRoot, "components")
1297
+ join7(projectRoot, "src", "app"),
1298
+ join7(projectRoot, "app"),
1299
+ join7(projectRoot, "src", "routes"),
1300
+ join7(projectRoot, "src", "pages"),
1301
+ join7(projectRoot, "pages"),
1302
+ join7(projectRoot, "src", "views"),
1303
+ join7(projectRoot, "src", "components"),
1304
+ join7(projectRoot, "components")
746
1305
  ];
747
1306
  const allPaths = [];
748
1307
  for (const dir of scanDirs) {
749
- if (existsSync6(dir)) {
1308
+ if (existsSync7(dir)) {
750
1309
  allPaths.push(...collectPaths(dir, projectRoot));
751
1310
  }
752
1311
  }
@@ -770,8 +1329,8 @@ function scanFeatures(projectRoot) {
770
1329
  }
771
1330
 
772
1331
  // src/analyzers/layout.ts
773
- import { existsSync as existsSync7, readdirSync as readdirSync3, readFileSync as readFileSync5 } from "fs";
774
- import { join as join7 } from "path";
1332
+ import { existsSync as existsSync8, readdirSync as readdirSync3, readFileSync as readFileSync6 } from "fs";
1333
+ import { join as join8 } from "path";
775
1334
  var SIDEBAR_PATTERNS = ["sidebar", "side-bar", "sidenav", "side-nav", "drawer", "aside"];
776
1335
  var NAV_PATTERNS = ["nav", "navbar", "header", "top-bar", "topbar", "app-bar", "appbar"];
777
1336
  var FOOTER_PATTERNS = ["footer", "bottom-bar", "bottombar"];
@@ -782,12 +1341,12 @@ function containsPattern(text, patterns) {
782
1341
  function checkComponentDirs(projectRoot) {
783
1342
  const result = { sidebar: false, nav: false, footer: false };
784
1343
  const componentDirs = [
785
- join7(projectRoot, "src", "components"),
786
- join7(projectRoot, "components"),
787
- join7(projectRoot, "src", "ui")
1344
+ join8(projectRoot, "src", "components"),
1345
+ join8(projectRoot, "components"),
1346
+ join8(projectRoot, "src", "ui")
788
1347
  ];
789
1348
  for (const dir of componentDirs) {
790
- if (!existsSync7(dir)) continue;
1349
+ if (!existsSync8(dir)) continue;
791
1350
  let entries;
792
1351
  try {
793
1352
  entries = readdirSync3(dir);
@@ -806,16 +1365,16 @@ function checkComponentDirs(projectRoot) {
806
1365
  function checkLayoutFiles(projectRoot) {
807
1366
  const result = { sidebar: false, nav: false, footer: false };
808
1367
  const layoutPaths = [
809
- join7(projectRoot, "src", "app", "layout.tsx"),
810
- join7(projectRoot, "src", "app", "layout.jsx"),
811
- join7(projectRoot, "app", "layout.tsx"),
812
- join7(projectRoot, "app", "layout.jsx")
1368
+ join8(projectRoot, "src", "app", "layout.tsx"),
1369
+ join8(projectRoot, "src", "app", "layout.jsx"),
1370
+ join8(projectRoot, "app", "layout.tsx"),
1371
+ join8(projectRoot, "app", "layout.jsx")
813
1372
  ];
814
1373
  for (const layoutPath of layoutPaths) {
815
- if (!existsSync7(layoutPath)) continue;
1374
+ if (!existsSync8(layoutPath)) continue;
816
1375
  let content;
817
1376
  try {
818
- content = readFileSync5(layoutPath, "utf-8");
1377
+ content = readFileSync6(layoutPath, "utf-8");
819
1378
  } catch {
820
1379
  continue;
821
1380
  }
@@ -826,16 +1385,16 @@ function checkLayoutFiles(projectRoot) {
826
1385
  const subLayoutDirs = ["dashboard", "admin", "app"];
827
1386
  for (const sub of subLayoutDirs) {
828
1387
  const paths = [
829
- join7(projectRoot, "src", "app", sub, "layout.tsx"),
830
- join7(projectRoot, "src", "app", sub, "layout.jsx"),
831
- join7(projectRoot, "app", sub, "layout.tsx"),
832
- join7(projectRoot, "app", sub, "layout.jsx")
1388
+ join8(projectRoot, "src", "app", sub, "layout.tsx"),
1389
+ join8(projectRoot, "src", "app", sub, "layout.jsx"),
1390
+ join8(projectRoot, "app", sub, "layout.tsx"),
1391
+ join8(projectRoot, "app", sub, "layout.jsx")
833
1392
  ];
834
1393
  for (const layoutPath of paths) {
835
- if (!existsSync7(layoutPath)) continue;
1394
+ if (!existsSync8(layoutPath)) continue;
836
1395
  let content;
837
1396
  try {
838
- content = readFileSync5(layoutPath, "utf-8");
1397
+ content = readFileSync6(layoutPath, "utf-8");
839
1398
  } catch {
840
1399
  continue;
841
1400
  }
@@ -857,10 +1416,10 @@ function inferShellPattern(hasSidebar, hasTopNav, hasFooter) {
857
1416
  return "main-only";
858
1417
  }
859
1418
  function inferShellPatternFromDecantrContract(projectRoot) {
860
- const essencePath = join7(projectRoot, "decantr.essence.json");
861
- if (!existsSync7(essencePath)) return null;
1419
+ const essencePath = join8(projectRoot, "decantr.essence.json");
1420
+ if (!existsSync8(essencePath)) return null;
862
1421
  try {
863
- const essence = JSON.parse(readFileSync5(essencePath, "utf-8"));
1422
+ const essence = JSON.parse(readFileSync6(essencePath, "utf-8"));
864
1423
  const sectionShells = essence.blueprint?.sections?.map((section) => section.shell).filter((shell) => typeof shell === "string" && shell.length > 0) ?? [];
865
1424
  if (sectionShells.length === 0 && essence.blueprint?.shell) {
866
1425
  return `${essence.blueprint.shell} (contract)`;
@@ -895,375 +1454,19 @@ function scanLayout(projectRoot) {
895
1454
  };
896
1455
  }
897
1456
 
898
- // src/analyzers/routes.ts
899
- import { existsSync as existsSync8, readdirSync as readdirSync4, readFileSync as readFileSync6, statSync as statSync4 } from "fs";
900
- import { join as join8, relative } from "path";
901
- var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".next", ".git", "api", "_app", "_document"]);
902
- function shouldSkipDir(name) {
903
- return name.startsWith("_") || name.startsWith(".") || SKIP_DIRS.has(name);
904
- }
905
- function segmentToRoute(segment) {
906
- if (segment.startsWith("(") && segment.endsWith(")")) {
907
- return null;
908
- }
909
- if (segment.startsWith("[") && segment.endsWith("]")) {
910
- const param = segment.slice(1, -1);
911
- if (param.startsWith("...")) {
912
- return `:${param.slice(3)}*`;
913
- }
914
- if (param.startsWith("[...") && param.endsWith("]")) {
915
- return `:${param.slice(4, -1)}*`;
916
- }
917
- return `:${param}`;
918
- }
919
- return segment;
920
- }
921
- function walkAppDir(dir, baseDir, segments) {
922
- const routes = [];
923
- let entries;
924
- try {
925
- entries = readdirSync4(dir);
926
- } catch {
927
- return routes;
928
- }
929
- const hasPage = entries.some(
930
- (e) => e === "page.tsx" || e === "page.ts" || e === "page.jsx" || e === "page.js"
931
- );
932
- const hasLayout = entries.some(
933
- (e) => e === "layout.tsx" || e === "layout.ts" || e === "layout.jsx" || e === "layout.js"
934
- );
935
- if (hasPage) {
936
- const routePath = "/" + segments.filter((s) => s !== "").join("/");
937
- const pageFile = entries.find((e) => e.startsWith("page."));
938
- routes.push({
939
- path: routePath || "/",
940
- file: relative(baseDir, join8(dir, pageFile)),
941
- hasLayout
942
- });
943
- }
944
- for (const entry of entries) {
945
- if (shouldSkipDir(entry)) continue;
946
- const fullPath = join8(dir, entry);
947
- try {
948
- if (!statSync4(fullPath).isDirectory()) continue;
949
- } catch {
950
- continue;
951
- }
952
- const routeSegment = segmentToRoute(entry);
953
- const nextSegments = routeSegment === null ? [...segments] : [...segments, routeSegment];
954
- routes.push(...walkAppDir(fullPath, baseDir, nextSegments));
955
- }
956
- return routes;
957
- }
958
- function walkPagesDir(dir, baseDir, segments) {
959
- const routes = [];
960
- let entries;
961
- try {
962
- entries = readdirSync4(dir);
963
- } catch {
964
- return routes;
965
- }
966
- for (const entry of entries) {
967
- if (shouldSkipDir(entry)) continue;
968
- const fullPath = join8(dir, entry);
969
- try {
970
- const stat = statSync4(fullPath);
971
- if (stat.isDirectory()) {
972
- const routeSegment = segmentToRoute(entry);
973
- const nextSegments = routeSegment === null ? [...segments] : [...segments, routeSegment];
974
- routes.push(...walkPagesDir(fullPath, baseDir, nextSegments));
975
- } else if (stat.isFile()) {
976
- const match = entry.match(/^(.+)\.(tsx?|jsx?|mdx?)$/);
977
- if (!match) continue;
978
- const name = match[1];
979
- if (name.startsWith("_")) continue;
980
- const routeSegment = name === "index" ? "" : segmentToRoute(name) ?? name;
981
- const routePath = "/" + [...segments, routeSegment].filter((s) => s !== "").join("/");
982
- routes.push({
983
- path: routePath || "/",
984
- file: relative(baseDir, fullPath),
985
- hasLayout: false
986
- });
987
- }
988
- } catch {
989
- }
990
- }
991
- return routes;
992
- }
993
- var ROUTER_FILE_EXTENSIONS = /* @__PURE__ */ new Set([".tsx", ".ts", ".jsx", ".js"]);
994
- function collectRouteCandidateFiles(dir, files, depth = 0) {
995
- if (depth > 5) return;
996
- let entries;
997
- try {
998
- entries = readdirSync4(dir);
999
- } catch {
1000
- return;
1001
- }
1002
- for (const entry of entries) {
1003
- if (entry.startsWith(".") || entry === "node_modules") continue;
1004
- const fullPath = join8(dir, entry);
1005
- try {
1006
- const stat = statSync4(fullPath);
1007
- if (stat.isDirectory()) {
1008
- collectRouteCandidateFiles(fullPath, files, depth + 1);
1009
- } else if (stat.isFile()) {
1010
- const ext = entry.slice(entry.lastIndexOf("."));
1011
- if (ROUTER_FILE_EXTENSIONS.has(ext)) {
1012
- files.push(fullPath);
1013
- }
1014
- }
1015
- } catch {
1016
- }
1017
- }
1018
- }
1019
- function scanReactRouter(projectRoot) {
1020
- const candidateDirs = [join8(projectRoot, "src"), projectRoot];
1021
- const candidateFiles = [];
1022
- for (const dir of candidateDirs) {
1023
- if (existsSync8(dir)) collectRouteCandidateFiles(dir, candidateFiles);
1024
- }
1025
- const routeMap = /* @__PURE__ */ new Map();
1026
- for (const absolutePath of candidateFiles) {
1027
- let content;
1028
- try {
1029
- content = readFileSync6(absolutePath, "utf-8");
1030
- } catch {
1031
- continue;
1032
- }
1033
- const isReactRouterFile = content.includes("react-router-dom") || content.includes("react-router") || content.includes("<Routes") || content.includes("createBrowserRouter") || content.includes("createHashRouter") || content.includes("RouterProvider") || content.includes("HashRouter") || content.includes("BrowserRouter");
1034
- if (!isReactRouterFile) continue;
1035
- const relativePath = relative(projectRoot, absolutePath);
1036
- const pathMatches = /* @__PURE__ */ new Set();
1037
- for (const match of content.matchAll(/<Route\b[^>]*\bpath=["'`]([^"'`]+)["'`]/g)) {
1038
- pathMatches.add(match[1]);
1039
- }
1040
- for (const match of content.matchAll(/\bpath\s*:\s*["'`]([^"'`]+)["'`]/g)) {
1041
- pathMatches.add(match[1]);
1042
- }
1043
- if (pathMatches.size === 0 && (content.includes("<Routes") || content.includes("RouterProvider"))) {
1044
- pathMatches.add("/");
1045
- }
1046
- for (const path of pathMatches) {
1047
- if (!routeMap.has(path)) {
1048
- routeMap.set(path, {
1049
- path,
1050
- file: relativePath,
1051
- hasLayout: false
1052
- });
1053
- }
1054
- }
1055
- }
1056
- return [...routeMap.values()];
1057
- }
1058
- function scanRoutes(projectRoot) {
1059
- const appDirs = [join8(projectRoot, "src", "app"), join8(projectRoot, "app")];
1060
- for (const appDir of appDirs) {
1061
- if (existsSync8(appDir)) {
1062
- const routes = walkAppDir(appDir, projectRoot, []);
1063
- if (routes.length > 0) {
1064
- return { strategy: "app-router", routes };
1065
- }
1066
- }
1067
- }
1068
- const pagesDirs = [join8(projectRoot, "src", "pages"), join8(projectRoot, "pages")];
1069
- for (const pagesDir of pagesDirs) {
1070
- if (existsSync8(pagesDir)) {
1071
- const routes = walkPagesDir(pagesDir, projectRoot, []);
1072
- if (routes.length > 0) {
1073
- return { strategy: "pages-router", routes };
1074
- }
1075
- }
1076
- }
1077
- const reactRouterRoutes = scanReactRouter(projectRoot);
1078
- if (reactRouterRoutes.length > 0) {
1079
- return { strategy: "react-router", routes: reactRouterRoutes };
1080
- }
1081
- return { strategy: "none", routes: [] };
1082
- }
1083
-
1084
- // src/analyzers/styling.ts
1457
+ // src/detect.ts
1085
1458
  import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
1086
1459
  import { join as join9 } from "path";
1087
- var TAILWIND_CONFIGS = [
1088
- "tailwind.config.js",
1089
- "tailwind.config.ts",
1090
- "tailwind.config.mjs",
1091
- "tailwind.config.cjs"
1092
- ];
1093
- var GLOBALS_CSS_PATHS = [
1094
- "src/app/globals.css",
1095
- "app/globals.css",
1096
- "src/styles/global.css",
1097
- "src/styles/globals.css",
1098
- "styles/globals.css",
1099
- "src/index.css",
1100
- "src/global.css"
1101
- ];
1102
- var DECANTR_STYLE_PATHS = [
1103
- "src/styles/tokens.css",
1104
- "src/styles/treatments.css",
1105
- "src/styles/global.css"
1106
- ];
1107
- function extractCSSVariables(content) {
1108
- const colors = {};
1109
- const variables = [];
1110
- const varRegex = /--([\w-]+)\s*:\s*([^;]+)/g;
1111
- let match;
1112
- while ((match = varRegex.exec(content)) !== null) {
1113
- const name = match[1];
1114
- const value = match[2].trim();
1115
- variables.push(`--${name}`);
1116
- const colorPatterns = [
1117
- "primary",
1118
- "secondary",
1119
- "accent",
1120
- "bg",
1121
- "fg",
1122
- "border",
1123
- "success",
1124
- "warning",
1125
- "error",
1126
- "surface",
1127
- "muted"
1128
- ];
1129
- if (value.startsWith("#") || value.startsWith("rgb") || value.startsWith("hsl") || colorPatterns.some((p) => name.includes(p))) {
1130
- colors[name] = value;
1131
- }
1132
- }
1133
- return { colors, variables };
1134
- }
1135
- function detectDarkMode(projectRoot, cssContents) {
1136
- for (const cssContent of cssContents) {
1137
- if (cssContent.includes(".dark") || cssContent.includes('[data-theme="dark"]') || cssContent.includes("prefers-color-scheme: dark") || cssContent.includes("color-scheme: dark")) {
1138
- return true;
1139
- }
1140
- }
1141
- const layoutPaths = [
1142
- "src/app/layout.tsx",
1143
- "app/layout.tsx",
1144
- "src/app/layout.jsx",
1145
- "app/layout.jsx"
1146
- ];
1147
- for (const rel of layoutPaths) {
1148
- const fullPath = join9(projectRoot, rel);
1149
- if (existsSync9(fullPath)) {
1150
- try {
1151
- const layoutContent = readFileSync7(fullPath, "utf-8");
1152
- if (layoutContent.includes('className="dark"') || layoutContent.includes("className='dark'") || layoutContent.includes('class="dark"')) {
1153
- return true;
1154
- }
1155
- } catch {
1156
- }
1157
- }
1158
- }
1159
- const pkgPath = join9(projectRoot, "package.json");
1160
- if (existsSync9(pkgPath)) {
1161
- try {
1162
- const pkg = JSON.parse(readFileSync7(pkgPath, "utf-8"));
1163
- const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
1164
- if (allDeps["next-themes"] || allDeps["theme-toggle"] || allDeps["use-dark-mode"]) {
1165
- return true;
1166
- }
1167
- } catch {
1168
- }
1169
- }
1170
- const essencePath = join9(projectRoot, "decantr.essence.json");
1171
- if (existsSync9(essencePath)) {
1172
- try {
1173
- const essence = JSON.parse(readFileSync7(essencePath, "utf-8"));
1174
- const mode = essence.dna?.theme?.mode;
1175
- if (mode === "dark" || mode === "auto") {
1176
- return true;
1177
- }
1178
- } catch {
1179
- }
1180
- }
1181
- return false;
1182
- }
1183
- function scanStyling(projectRoot) {
1184
- let approach = "unknown";
1185
- let configFile;
1186
- for (const cfg of TAILWIND_CONFIGS) {
1187
- if (existsSync9(join9(projectRoot, cfg))) {
1188
- approach = "tailwind";
1189
- configFile = cfg;
1190
- break;
1191
- }
1192
- }
1193
- if (approach === "unknown") {
1194
- const pkgPath = join9(projectRoot, "package.json");
1195
- if (existsSync9(pkgPath)) {
1196
- try {
1197
- const pkg = JSON.parse(readFileSync7(pkgPath, "utf-8"));
1198
- const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
1199
- if (allDeps["@decantr/css"]) {
1200
- approach = "decantr-css";
1201
- configFile = "src/styles/tokens.css";
1202
- }
1203
- if (allDeps.tailwindcss || allDeps["@tailwindcss/postcss"] || allDeps["@tailwindcss/vite"]) {
1204
- approach = "tailwind";
1205
- }
1206
- } catch {
1207
- }
1208
- }
1209
- }
1210
- const decantrStyleFiles = DECANTR_STYLE_PATHS.filter((rel) => existsSync9(join9(projectRoot, rel)));
1211
- if (decantrStyleFiles.length >= 2) {
1212
- approach = "decantr-css";
1213
- configFile = decantrStyleFiles.join(" + ");
1214
- }
1215
- const cssContents = [];
1216
- for (const rel of GLOBALS_CSS_PATHS) {
1217
- const fullPath = join9(projectRoot, rel);
1218
- if (existsSync9(fullPath)) {
1219
- try {
1220
- cssContents.push(readFileSync7(fullPath, "utf-8"));
1221
- } catch {
1222
- }
1223
- }
1224
- }
1225
- for (const rel of DECANTR_STYLE_PATHS) {
1226
- if (GLOBALS_CSS_PATHS.includes(rel)) continue;
1227
- const fullPath = join9(projectRoot, rel);
1228
- if (existsSync9(fullPath)) {
1229
- try {
1230
- cssContents.push(readFileSync7(fullPath, "utf-8"));
1231
- } catch {
1232
- }
1233
- }
1234
- }
1235
- let colors = {};
1236
- let cssVariables = [];
1237
- for (const cssContent of cssContents) {
1238
- const extracted = extractCSSVariables(cssContent);
1239
- colors = { ...colors, ...extracted.colors };
1240
- cssVariables.push(...extracted.variables);
1241
- }
1242
- cssVariables = [...new Set(cssVariables)];
1243
- const darkMode = detectDarkMode(projectRoot, cssContents);
1244
- if (approach === "unknown" && cssContents.length > 0) {
1245
- approach = "css";
1246
- configFile = GLOBALS_CSS_PATHS.find((rel) => existsSync9(join9(projectRoot, rel)));
1247
- }
1248
- return {
1249
- approach,
1250
- configFile,
1251
- colors,
1252
- darkMode,
1253
- cssVariables
1254
- };
1255
- }
1256
-
1257
- // src/detect.ts
1258
- import { existsSync as existsSync10, readFileSync as readFileSync8 } from "fs";
1259
- import { join as join10 } from "path";
1260
1460
  var RULE_FILES = [
1261
1461
  "CLAUDE.md",
1462
+ ".claude/rules",
1262
1463
  ".cursorrules",
1263
1464
  ".cursor/rules",
1264
1465
  "AGENTS.md",
1265
1466
  "GEMINI.md",
1266
- "copilot-instructions.md"
1467
+ "copilot-instructions.md",
1468
+ ".github/copilot-instructions.md",
1469
+ ".windsurfrules"
1267
1470
  ];
1268
1471
  function detectProject(projectRoot = process.cwd()) {
1269
1472
  const result = {
@@ -1275,52 +1478,52 @@ function detectProject(projectRoot = process.cwd()) {
1275
1478
  existingEssence: false,
1276
1479
  projectRoot
1277
1480
  };
1278
- result.existingEssence = existsSync10(join10(projectRoot, "decantr.essence.json"));
1481
+ result.existingEssence = existsSync9(join9(projectRoot, "decantr.essence.json"));
1279
1482
  for (const ruleFile of RULE_FILES) {
1280
- if (existsSync10(join10(projectRoot, ruleFile))) {
1483
+ if (existsSync9(join9(projectRoot, ruleFile))) {
1281
1484
  result.existingRuleFiles.push(ruleFile);
1282
1485
  }
1283
1486
  }
1284
- if (existsSync10(join10(projectRoot, "pnpm-lock.yaml"))) {
1487
+ if (existsSync9(join9(projectRoot, "pnpm-lock.yaml"))) {
1285
1488
  result.packageManager = "pnpm";
1286
- } else if (existsSync10(join10(projectRoot, "yarn.lock"))) {
1489
+ } else if (existsSync9(join9(projectRoot, "yarn.lock"))) {
1287
1490
  result.packageManager = "yarn";
1288
- } else if (existsSync10(join10(projectRoot, "bun.lockb"))) {
1491
+ } else if (existsSync9(join9(projectRoot, "bun.lockb"))) {
1289
1492
  result.packageManager = "bun";
1290
- } else if (existsSync10(join10(projectRoot, "package-lock.json"))) {
1493
+ } else if (existsSync9(join9(projectRoot, "package-lock.json"))) {
1291
1494
  result.packageManager = "npm";
1292
1495
  }
1293
- result.hasTypeScript = existsSync10(join10(projectRoot, "tsconfig.json"));
1294
- 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"));
1295
- if (existsSync10(join10(projectRoot, "next.config.js")) || existsSync10(join10(projectRoot, "next.config.ts")) || existsSync10(join10(projectRoot, "next.config.mjs"))) {
1496
+ result.hasTypeScript = existsSync9(join9(projectRoot, "tsconfig.json"));
1497
+ 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"));
1498
+ if (existsSync9(join9(projectRoot, "next.config.js")) || existsSync9(join9(projectRoot, "next.config.ts")) || existsSync9(join9(projectRoot, "next.config.mjs"))) {
1296
1499
  result.framework = "nextjs";
1297
1500
  result.version = getPackageVersion(projectRoot, "next");
1298
1501
  return result;
1299
1502
  }
1300
- if (existsSync10(join10(projectRoot, "nuxt.config.js")) || existsSync10(join10(projectRoot, "nuxt.config.ts"))) {
1503
+ if (existsSync9(join9(projectRoot, "nuxt.config.js")) || existsSync9(join9(projectRoot, "nuxt.config.ts"))) {
1301
1504
  result.framework = "nuxt";
1302
1505
  result.version = getPackageVersion(projectRoot, "nuxt");
1303
1506
  return result;
1304
1507
  }
1305
- if (existsSync10(join10(projectRoot, "astro.config.mjs")) || existsSync10(join10(projectRoot, "astro.config.ts"))) {
1508
+ if (existsSync9(join9(projectRoot, "astro.config.mjs")) || existsSync9(join9(projectRoot, "astro.config.ts"))) {
1306
1509
  result.framework = "astro";
1307
1510
  result.version = getPackageVersion(projectRoot, "astro");
1308
1511
  return result;
1309
1512
  }
1310
- if (existsSync10(join10(projectRoot, "svelte.config.js")) || existsSync10(join10(projectRoot, "svelte.config.ts"))) {
1513
+ if (existsSync9(join9(projectRoot, "svelte.config.js")) || existsSync9(join9(projectRoot, "svelte.config.ts"))) {
1311
1514
  result.framework = "svelte";
1312
1515
  result.version = getPackageVersion(projectRoot, "svelte");
1313
1516
  return result;
1314
1517
  }
1315
- if (existsSync10(join10(projectRoot, "angular.json"))) {
1518
+ if (existsSync9(join9(projectRoot, "angular.json"))) {
1316
1519
  result.framework = "angular";
1317
1520
  result.version = getPackageVersion(projectRoot, "@angular/core");
1318
1521
  return result;
1319
1522
  }
1320
- const packageJsonPath = join10(projectRoot, "package.json");
1321
- if (existsSync10(packageJsonPath)) {
1523
+ const packageJsonPath = join9(projectRoot, "package.json");
1524
+ if (existsSync9(packageJsonPath)) {
1322
1525
  try {
1323
- const packageJson = JSON.parse(readFileSync8(packageJsonPath, "utf-8"));
1526
+ const packageJson = JSON.parse(readFileSync7(packageJsonPath, "utf-8"));
1324
1527
  const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
1325
1528
  if (deps.next) {
1326
1529
  result.framework = "nextjs";
@@ -1347,18 +1550,18 @@ function detectProject(projectRoot = process.cwd()) {
1347
1550
  } catch {
1348
1551
  }
1349
1552
  }
1350
- if (result.framework === "unknown" && !existsSync10(packageJsonPath)) {
1351
- if (existsSync10(join10(projectRoot, "index.html"))) {
1553
+ if (result.framework === "unknown" && !existsSync9(packageJsonPath)) {
1554
+ if (existsSync9(join9(projectRoot, "index.html"))) {
1352
1555
  result.framework = "html";
1353
1556
  }
1354
1557
  }
1355
1558
  return result;
1356
1559
  }
1357
1560
  function getPackageVersion(projectRoot, packageName) {
1358
- const packageJsonPath = join10(projectRoot, "package.json");
1359
- if (!existsSync10(packageJsonPath)) return void 0;
1561
+ const packageJsonPath = join9(projectRoot, "package.json");
1562
+ if (!existsSync9(packageJsonPath)) return void 0;
1360
1563
  try {
1361
- const packageJson = JSON.parse(readFileSync8(packageJsonPath, "utf-8"));
1564
+ const packageJson = JSON.parse(readFileSync7(packageJsonPath, "utf-8"));
1362
1565
  const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
1363
1566
  const version = deps[packageName];
1364
1567
  return version?.replace(/^\^|~/, "");
@@ -1391,8 +1594,8 @@ function formatDetection(detected) {
1391
1594
  }
1392
1595
 
1393
1596
  // src/workflow-model.ts
1394
- import { existsSync as existsSync11, readFileSync as readFileSync9 } from "fs";
1395
- import { join as join11 } from "path";
1597
+ import { existsSync as existsSync10, readFileSync as readFileSync8 } from "fs";
1598
+ import { join as join10 } from "path";
1396
1599
  function inferSuggestedShell(layout) {
1397
1600
  if (layout.hasSidebar) return "sidebar-main";
1398
1601
  if (layout.hasTopNav) return "top-nav-main";
@@ -1468,7 +1671,7 @@ function createBrownfieldInitSeed(detected, layout, styling) {
1468
1671
  shell: inferSuggestedShell(layout),
1469
1672
  guard: "guided",
1470
1673
  density: "comfortable",
1471
- theme: "luminarum",
1674
+ theme: "existing",
1472
1675
  mode: styling.darkMode ? "dark" : "auto",
1473
1676
  existing: true,
1474
1677
  notes: [
@@ -1479,12 +1682,12 @@ function createBrownfieldInitSeed(detected, layout, styling) {
1479
1682
  };
1480
1683
  }
1481
1684
  function readBrownfieldInitSeed(projectRoot) {
1482
- const seedPath = join11(projectRoot, ".decantr", "init-seed.json");
1483
- if (!existsSync11(seedPath)) {
1685
+ const seedPath = join10(projectRoot, ".decantr", "init-seed.json");
1686
+ if (!existsSync10(seedPath)) {
1484
1687
  return null;
1485
1688
  }
1486
1689
  try {
1487
- const parsed = JSON.parse(readFileSync9(seedPath, "utf-8"));
1690
+ const parsed = JSON.parse(readFileSync8(seedPath, "utf-8"));
1488
1691
  if (parsed.workflow !== "brownfield-adoption") {
1489
1692
  return null;
1490
1693
  }
@@ -1519,8 +1722,21 @@ ${BOLD}Analyzing project...${RESET2}
1519
1722
  const features = scanFeatures(projectRoot);
1520
1723
  console.log(`${DIM2}Scanning dependencies...${RESET2}`);
1521
1724
  const dependencies = scanDependencies(projectRoot);
1725
+ console.log(`${DIM2}Scanning ambient project context...${RESET2}`);
1726
+ const ambient = scanAmbientContext(projectRoot);
1727
+ const doctrine = createDoctrineMap(ambient);
1522
1728
  const initSeed = createBrownfieldInitSeed(project, layout, styling);
1523
1729
  initSeed.projectScope = workspace?.projectScope ?? "single-app";
1730
+ const proposal = createBrownfieldProposal({
1731
+ project,
1732
+ routes,
1733
+ components,
1734
+ styling,
1735
+ layout,
1736
+ features,
1737
+ dependencies,
1738
+ ambient
1739
+ });
1524
1740
  const analysis = {
1525
1741
  version: 1,
1526
1742
  analyzedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -1549,7 +1765,8 @@ ${BOLD}Analyzing project...${RESET2}
1549
1765
  contractOnly: true,
1550
1766
  adoptionMode: "contract-only",
1551
1767
  initSeedPath: ".decantr/init-seed.json",
1552
- recommendedCommand: "decantr init --existing --yes --adoption=contract-only"
1768
+ proposalPath: ".decantr/observed-essence.proposal.json",
1769
+ recommendedCommand: "decantr init --existing --accept-proposal"
1553
1770
  },
1554
1771
  hybrid: {
1555
1772
  ownerCommands: [
@@ -1568,6 +1785,9 @@ ${BOLD}Analyzing project...${RESET2}
1568
1785
  routeAnchors: routes.routes.map((route) => route.path),
1569
1786
  stylingAnchors: [styling.configFile].filter(Boolean),
1570
1787
  ruleFiles: project.existingRuleFiles,
1788
+ ambientContextPath: ".decantr/ambient-context.json",
1789
+ doctrineMapPath: ".decantr/doctrine-map.json",
1790
+ proposalPath: ".decantr/observed-essence.proposal.json",
1571
1791
  preserve: [
1572
1792
  "framework",
1573
1793
  "package manager",
@@ -1577,14 +1797,21 @@ ${BOLD}Analyzing project...${RESET2}
1577
1797
  ]
1578
1798
  }
1579
1799
  };
1580
- const decantrDir = join12(projectRoot, ".decantr");
1581
- if (!existsSync12(decantrDir)) {
1582
- mkdirSync3(decantrDir, { recursive: true });
1583
- }
1584
- const outputPath = join12(decantrDir, "analysis.json");
1585
- const initSeedPath = join12(decantrDir, "init-seed.json");
1586
- writeFileSync4(outputPath, JSON.stringify(analysis, null, 2) + "\n", "utf-8");
1587
- writeFileSync4(initSeedPath, JSON.stringify(initSeed, null, 2) + "\n", "utf-8");
1800
+ const decantrDir = join11(projectRoot, ".decantr");
1801
+ if (!existsSync11(decantrDir)) {
1802
+ mkdirSync4(decantrDir, { recursive: true });
1803
+ }
1804
+ const outputPath = join11(decantrDir, "analysis.json");
1805
+ const initSeedPath = join11(decantrDir, "init-seed.json");
1806
+ const ambientPath = join11(decantrDir, "ambient-context.json");
1807
+ const doctrinePath = join11(decantrDir, "doctrine-map.json");
1808
+ const reportPath = join11(decantrDir, "brownfield-report.md");
1809
+ writeFileSync5(outputPath, JSON.stringify(analysis, null, 2) + "\n", "utf-8");
1810
+ writeFileSync5(initSeedPath, JSON.stringify(initSeed, null, 2) + "\n", "utf-8");
1811
+ writeFileSync5(ambientPath, JSON.stringify(ambient, null, 2) + "\n", "utf-8");
1812
+ writeDoctrineMap(projectRoot, doctrine);
1813
+ writeBrownfieldProposal(projectRoot, proposal);
1814
+ writeFileSync5(reportPath, generateBrownfieldReport(proposal, ambient, doctrine), "utf-8");
1588
1815
  console.log(`
1589
1816
  ${GREEN2}Analysis complete.${RESET2}
1590
1817
  `);
@@ -1605,6 +1832,8 @@ ${GREEN2}Analysis complete.${RESET2}
1605
1832
  console.log(
1606
1833
  ` Features: ${features.detected.length > 0 ? features.detected.join(", ") : "none detected"}`
1607
1834
  );
1835
+ console.log(` Context: ${ambient.items.length} ambient item(s)`);
1836
+ console.log(` Doctrine: ${doctrine.sources.length} ranked source(s)`);
1608
1837
  const depCounts = [
1609
1838
  dependencies.ui.length && `${dependencies.ui.length} ui`,
1610
1839
  dependencies.auth.length && `${dependencies.auth.length} auth`,
@@ -1616,16 +1845,20 @@ ${GREEN2}Analysis complete.${RESET2}
1616
1845
  console.log(`
1617
1846
  ${DIM2}Written to:${RESET2} ${outputPath}`);
1618
1847
  console.log(`${DIM2}Init seed:${RESET2} ${initSeedPath}`);
1848
+ console.log(`${DIM2}Ambient context:${RESET2} ${ambientPath}`);
1849
+ console.log(`${DIM2}Doctrine map:${RESET2} ${doctrinePath}`);
1850
+ console.log(`${DIM2}Observed proposal:${RESET2} ${join11(decantrDir, "observed-essence.proposal.json")}`);
1851
+ console.log(`${DIM2}Brownfield report:${RESET2} ${reportPath}`);
1619
1852
  console.log(
1620
1853
  `
1621
- ${YELLOW}Next step:${RESET2} Run ${BOLD}decantr init --existing --yes --adoption=contract-only${RESET2} to attach Decantr using the generated brownfield seed.
1854
+ ${YELLOW}Next step:${RESET2} Review ${BOLD}.decantr/brownfield-report.md${RESET2}, then run ${BOLD}decantr init --existing --accept-proposal${RESET2} to attach Decantr using the observed proposal.
1622
1855
  `
1623
1856
  );
1624
1857
  }
1625
1858
 
1626
1859
  // src/commands/create.ts
1627
- import { existsSync as existsSync13, mkdirSync as mkdirSync4, writeFileSync as writeFileSync5 } from "fs";
1628
- import { join as join13 } from "path";
1860
+ import { existsSync as existsSync12, mkdirSync as mkdirSync5, writeFileSync as writeFileSync6 } from "fs";
1861
+ import { join as join12 } from "path";
1629
1862
  import {
1630
1863
  CONTENT_TYPE_TO_API_CONTENT_TYPE,
1631
1864
  CONTENT_TYPES
@@ -1821,23 +2054,23 @@ function cmdCreate(type, name, projectRoot = process.cwd()) {
1821
2054
  }
1822
2055
  const contentType = type;
1823
2056
  const plural = PLURAL[contentType];
1824
- const customDir = join13(projectRoot, ".decantr", "custom", plural);
1825
- const filePath = join13(customDir, `${name}.json`);
1826
- if (existsSync13(filePath)) {
2057
+ const customDir = join12(projectRoot, ".decantr", "custom", plural);
2058
+ const filePath = join12(customDir, `${name}.json`);
2059
+ if (existsSync12(filePath)) {
1827
2060
  console.error(`${type} "${name}" already exists at ${filePath}`);
1828
2061
  process.exitCode = 1;
1829
2062
  return;
1830
2063
  }
1831
- mkdirSync4(customDir, { recursive: true });
2064
+ mkdirSync5(customDir, { recursive: true });
1832
2065
  const skeleton = getSkeleton(contentType, name, humanizeId(name));
1833
- writeFileSync5(filePath, JSON.stringify(skeleton, null, 2));
2066
+ writeFileSync6(filePath, JSON.stringify(skeleton, null, 2));
1834
2067
  console.log(`Created ${type} "${name}" at ${filePath}`);
1835
2068
  console.log(`Edit it, then publish with: decantr publish ${type} ${name}`);
1836
2069
  }
1837
2070
 
1838
2071
  // src/commands/export.ts
1839
- import { existsSync as existsSync14, mkdirSync as mkdirSync5, readFileSync as readFileSync10, writeFileSync as writeFileSync6 } from "fs";
1840
- import { dirname as dirname2, join as join14 } from "path";
2072
+ import { existsSync as existsSync13, mkdirSync as mkdirSync6, readFileSync as readFileSync9, writeFileSync as writeFileSync7 } from "fs";
2073
+ import { dirname as dirname2, join as join13 } from "path";
1841
2074
  var GREEN3 = "\x1B[32m";
1842
2075
  var RED2 = "\x1B[31m";
1843
2076
  var DIM3 = "\x1B[2m";
@@ -1987,21 +2220,21 @@ function generateCSSVars(tokens) {
1987
2220
  return lines.join("\n");
1988
2221
  }
1989
2222
  async function cmdExport(target, projectRoot, options = {}) {
1990
- const essencePath = join14(projectRoot, "decantr.essence.json");
1991
- const tokensPath = join14(projectRoot, "src", "styles", "tokens.css");
1992
- if (!existsSync14(essencePath)) {
2223
+ const essencePath = join13(projectRoot, "decantr.essence.json");
2224
+ const tokensPath = join13(projectRoot, "src", "styles", "tokens.css");
2225
+ if (!existsSync13(essencePath)) {
1993
2226
  console.error(`${RED2}No decantr.essence.json found. Run \`decantr init\` first.${RESET3}`);
1994
2227
  process.exitCode = 1;
1995
2228
  return;
1996
2229
  }
1997
- if (!existsSync14(tokensPath)) {
2230
+ if (!existsSync13(tokensPath)) {
1998
2231
  console.error(
1999
2232
  `${RED2}No src/styles/tokens.css found. Run \`decantr refresh\` to generate tokens.${RESET3}`
2000
2233
  );
2001
2234
  process.exitCode = 1;
2002
2235
  return;
2003
2236
  }
2004
- const tokensCSS = readFileSync10(tokensPath, "utf-8");
2237
+ const tokensCSS = readFileSync9(tokensPath, "utf-8");
2005
2238
  const tokens = parseTokensCSS(tokensCSS);
2006
2239
  if (tokens.size === 0) {
2007
2240
  console.error(`${RED2}No --d-* tokens found in tokens.css.${RESET3}`);
@@ -2010,28 +2243,28 @@ async function cmdExport(target, projectRoot, options = {}) {
2010
2243
  }
2011
2244
  switch (target) {
2012
2245
  case "shadcn": {
2013
- const cssOut = options.output ?? join14(projectRoot, "src", "styles", "shadcn-theme.css");
2014
- const jsonOut = join14(projectRoot, "components.json");
2246
+ const cssOut = options.output ?? join13(projectRoot, "src", "styles", "shadcn-theme.css");
2247
+ const jsonOut = join13(projectRoot, "components.json");
2015
2248
  ensureDir(cssOut);
2016
- writeFileSync6(cssOut, generateShadcnCSS(tokens), "utf-8");
2017
- writeFileSync6(jsonOut, generateShadcnComponentsJSON(), "utf-8");
2249
+ writeFileSync7(cssOut, generateShadcnCSS(tokens), "utf-8");
2250
+ writeFileSync7(jsonOut, generateShadcnComponentsJSON(), "utf-8");
2018
2251
  console.log(`${GREEN3}Exported shadcn theme:${RESET3}`);
2019
2252
  console.log(` ${DIM3}CSS:${RESET3} ${cssOut}`);
2020
2253
  console.log(` ${DIM3}JSON:${RESET3} ${jsonOut}`);
2021
2254
  break;
2022
2255
  }
2023
2256
  case "tailwind": {
2024
- const out = options.output ?? join14(projectRoot, "tailwind.decantr.config.ts");
2257
+ const out = options.output ?? join13(projectRoot, "tailwind.decantr.config.ts");
2025
2258
  ensureDir(out);
2026
- writeFileSync6(out, generateTailwindConfig(tokens), "utf-8");
2259
+ writeFileSync7(out, generateTailwindConfig(tokens), "utf-8");
2027
2260
  console.log(`${GREEN3}Exported Tailwind config:${RESET3}`);
2028
2261
  console.log(` ${DIM3}File:${RESET3} ${out}`);
2029
2262
  break;
2030
2263
  }
2031
2264
  case "css-vars": {
2032
- const out = options.output ?? join14(projectRoot, "decantr-tokens.css");
2265
+ const out = options.output ?? join13(projectRoot, "decantr-tokens.css");
2033
2266
  ensureDir(out);
2034
- writeFileSync6(out, generateCSSVars(tokens), "utf-8");
2267
+ writeFileSync7(out, generateCSSVars(tokens), "utf-8");
2035
2268
  console.log(`${GREEN3}Exported CSS variables:${RESET3}`);
2036
2269
  console.log(` ${DIM3}File:${RESET3} ${out}`);
2037
2270
  break;
@@ -2040,15 +2273,15 @@ async function cmdExport(target, projectRoot, options = {}) {
2040
2273
  }
2041
2274
  function ensureDir(filePath) {
2042
2275
  const dir = dirname2(filePath);
2043
- if (!existsSync14(dir)) {
2044
- mkdirSync5(dir, { recursive: true });
2276
+ if (!existsSync13(dir)) {
2277
+ mkdirSync6(dir, { recursive: true });
2045
2278
  }
2046
2279
  }
2047
2280
 
2048
2281
  // src/commands/magic.ts
2049
- import { existsSync as existsSync15 } from "fs";
2282
+ import { existsSync as existsSync14 } from "fs";
2050
2283
  import * as fs from "fs/promises";
2051
- import { join as join15 } from "path";
2284
+ import { join as join14 } from "path";
2052
2285
  var BOLD2 = "\x1B[1m";
2053
2286
  var DIM4 = "\x1B[2m";
2054
2287
  var RESET4 = "\x1B[0m";
@@ -2276,8 +2509,8 @@ async function cmdMagic(prompt, projectRoot, options) {
2276
2509
  console.log(` Archetype: ${intent.archetype}`);
2277
2510
  }
2278
2511
  console.log("");
2279
- const essencePath = join15(projectRoot, "decantr.essence.json");
2280
- if (existsSync15(essencePath)) {
2512
+ const essencePath = join14(projectRoot, "decantr.essence.json");
2513
+ if (existsSync14(essencePath)) {
2281
2514
  console.log(error(" decantr.essence.json already exists in this directory."));
2282
2515
  console.log(dim(" Remove it first or use a different directory."));
2283
2516
  process.exitCode = 1;
@@ -2295,12 +2528,14 @@ async function cmdMagic(prompt, projectRoot, options) {
2295
2528
  dim(" Running brownfield analysis instead so you can attach Decantr deliberately.\n")
2296
2529
  );
2297
2530
  cmdAnalyze(projectRoot);
2298
- console.log(`${BOLD2}Recommended next step:${RESET4} ${cyan("decantr init --existing --yes")}`);
2531
+ console.log(
2532
+ `${BOLD2}Recommended next step:${RESET4} ${cyan("decantr init --existing --accept-proposal")}`
2533
+ );
2299
2534
  console.log("");
2300
2535
  return;
2301
2536
  }
2302
2537
  const registryClient = new RegistryClient({
2303
- cacheDir: join15(projectRoot, ".decantr", "cache"),
2538
+ cacheDir: join14(projectRoot, ".decantr", "cache"),
2304
2539
  apiUrl: options.registry,
2305
2540
  offline: options.offline
2306
2541
  });
@@ -2571,14 +2806,14 @@ async function cmdMagic(prompt, projectRoot, options) {
2571
2806
  if (result.gitignoreUpdated) {
2572
2807
  console.log(` ${dim(".gitignore updated")}`);
2573
2808
  }
2574
- const contextDir = join15(projectRoot, ".decantr", "context");
2809
+ const contextDir = join14(projectRoot, ".decantr", "context");
2575
2810
  let sectionCount = 0;
2576
2811
  try {
2577
2812
  const files = await fs.readdir(contextDir);
2578
2813
  sectionCount = files.filter((f) => f.startsWith("section-")).length;
2579
2814
  } catch {
2580
2815
  }
2581
- const treatmentsPath = join15(projectRoot, "src", "styles", "treatments.css");
2816
+ const treatmentsPath = join14(projectRoot, "src", "styles", "treatments.css");
2582
2817
  let hasLayers = false;
2583
2818
  try {
2584
2819
  const css = await fs.readFile(treatmentsPath, "utf-8");
@@ -2611,21 +2846,21 @@ ${GREEN4}${BOLD2}Quality summary:${RESET4}`);
2611
2846
  }
2612
2847
 
2613
2848
  // src/commands/migrate.ts
2614
- import { copyFileSync, existsSync as existsSync16, readFileSync as readFileSync11, writeFileSync as writeFileSync7 } from "fs";
2615
- import { join as join16 } from "path";
2616
- import { isV3 as isV32, migrateV2ToV3, validateEssence } from "@decantr/essence-spec";
2849
+ import { copyFileSync, existsSync as existsSync15, readFileSync as readFileSync10, writeFileSync as writeFileSync8 } from "fs";
2850
+ import { join as join15 } from "path";
2851
+ import { isV3 as isV33, migrateV2ToV3, validateEssence } from "@decantr/essence-spec";
2617
2852
  var GREEN5 = "\x1B[32m";
2618
2853
  var RED4 = "\x1B[31m";
2619
2854
  var YELLOW3 = "\x1B[33m";
2620
2855
  var RESET5 = "\x1B[0m";
2621
2856
  var DIM5 = "\x1B[2m";
2622
2857
  function migrateEssenceFile(essencePath) {
2623
- if (!existsSync16(essencePath)) {
2858
+ if (!existsSync15(essencePath)) {
2624
2859
  return { success: false, error: `File not found: ${essencePath}` };
2625
2860
  }
2626
2861
  let raw;
2627
2862
  try {
2628
- raw = readFileSync11(essencePath, "utf-8");
2863
+ raw = readFileSync10(essencePath, "utf-8");
2629
2864
  } catch (e) {
2630
2865
  return { success: false, error: `Could not read ${essencePath}: ${e.message}` };
2631
2866
  }
@@ -2635,7 +2870,7 @@ function migrateEssenceFile(essencePath) {
2635
2870
  } catch (e) {
2636
2871
  return { success: false, error: `Invalid JSON: ${e.message}` };
2637
2872
  }
2638
- if (isV32(essence)) {
2873
+ if (isV33(essence)) {
2639
2874
  return { success: true, alreadyV3: true };
2640
2875
  }
2641
2876
  const preValidation = validateEssence(essence);
@@ -2666,7 +2901,7 @@ function migrateEssenceFile(essencePath) {
2666
2901
  };
2667
2902
  }
2668
2903
  try {
2669
- writeFileSync7(essencePath, JSON.stringify(v3, null, 2) + "\n");
2904
+ writeFileSync8(essencePath, JSON.stringify(v3, null, 2) + "\n");
2670
2905
  } catch (e) {
2671
2906
  return {
2672
2907
  success: false,
@@ -2677,8 +2912,8 @@ function migrateEssenceFile(essencePath) {
2677
2912
  return { success: true, backupPath };
2678
2913
  }
2679
2914
  async function cmdMigrate(projectRoot = process.cwd()) {
2680
- const essencePath = join16(projectRoot, "decantr.essence.json");
2681
- if (!existsSync16(essencePath)) {
2915
+ const essencePath = join15(projectRoot, "decantr.essence.json");
2916
+ if (!existsSync15(essencePath)) {
2682
2917
  console.error(`${RED4}No decantr.essence.json found. Run \`decantr init\` first.${RESET5}`);
2683
2918
  process.exitCode = 1;
2684
2919
  return;
@@ -2704,13 +2939,13 @@ async function cmdMigrate(projectRoot = process.cwd()) {
2704
2939
 
2705
2940
  // src/commands/new-project.ts
2706
2941
  import { spawnSync } from "child_process";
2707
- import { existsSync as existsSync18, mkdirSync as mkdirSync8 } from "fs";
2708
- import { join as join19, resolve as resolve2 } from "path";
2942
+ import { existsSync as existsSync17, mkdirSync as mkdirSync9 } from "fs";
2943
+ import { join as join18, resolve as resolve2 } from "path";
2709
2944
  import { fileURLToPath } from "url";
2710
2945
 
2711
2946
  // src/bootstrap.ts
2712
- import { mkdirSync as mkdirSync6, readFileSync as readFileSync12, writeFileSync as writeFileSync8 } from "fs";
2713
- import { basename, join as join17 } from "path";
2947
+ import { mkdirSync as mkdirSync7, readFileSync as readFileSync11, writeFileSync as writeFileSync9 } from "fs";
2948
+ import { basename, join as join16 } from "path";
2714
2949
  import { resolvePackAdapter } from "@decantr/core";
2715
2950
  var reactViteBootstrapAdapter = {
2716
2951
  id: "react-vite",
@@ -2734,7 +2969,7 @@ var reactViteBootstrapAdapter = {
2734
2969
  distDir: "dist"
2735
2970
  },
2736
2971
  writeProjectFiles(projectDir, title, routingMode) {
2737
- const srcDir = join17(projectDir, "src");
2972
+ const srcDir = join16(projectDir, "src");
2738
2973
  const routerImport = routingMode === "hash" ? "HashRouter" : "BrowserRouter";
2739
2974
  const packageJson = {
2740
2975
  name: basename(projectDir) || "decantr-app",
@@ -2766,7 +3001,7 @@ var reactViteBootstrapAdapter = {
2766
3001
  vite: "^6.0.0"
2767
3002
  }
2768
3003
  };
2769
- writeFileSync8(join17(projectDir, "package.json"), JSON.stringify(packageJson, null, 2) + "\n");
3004
+ writeFileSync9(join16(projectDir, "package.json"), JSON.stringify(packageJson, null, 2) + "\n");
2770
3005
  const viteConfig = `import { defineConfig } from 'vite';
2771
3006
  import react from '@vitejs/plugin-react';
2772
3007
 
@@ -2774,7 +3009,7 @@ export default defineConfig({
2774
3009
  plugins: [react()],
2775
3010
  });
2776
3011
  `;
2777
- writeFileSync8(join17(projectDir, "vite.config.ts"), viteConfig);
3012
+ writeFileSync9(join16(projectDir, "vite.config.ts"), viteConfig);
2778
3013
  const tsconfig = {
2779
3014
  compilerOptions: {
2780
3015
  target: "ES2020",
@@ -2796,7 +3031,7 @@ export default defineConfig({
2796
3031
  },
2797
3032
  include: ["src"]
2798
3033
  };
2799
- writeFileSync8(join17(projectDir, "tsconfig.json"), JSON.stringify(tsconfig, null, 2) + "\n");
3034
+ writeFileSync9(join16(projectDir, "tsconfig.json"), JSON.stringify(tsconfig, null, 2) + "\n");
2800
3035
  const tsconfigApp = {
2801
3036
  compilerOptions: {
2802
3037
  tsBuildInfoFile: "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
@@ -2819,8 +3054,8 @@ export default defineConfig({
2819
3054
  },
2820
3055
  include: ["src"]
2821
3056
  };
2822
- writeFileSync8(
2823
- join17(projectDir, "tsconfig.app.json"),
3057
+ writeFileSync9(
3058
+ join16(projectDir, "tsconfig.app.json"),
2824
3059
  JSON.stringify(tsconfigApp, null, 2) + "\n"
2825
3060
  );
2826
3061
  const indexHtml = `<!doctype html>
@@ -2836,8 +3071,8 @@ export default defineConfig({
2836
3071
  </body>
2837
3072
  </html>
2838
3073
  `;
2839
- writeFileSync8(join17(projectDir, "index.html"), indexHtml);
2840
- mkdirSync6(srcDir, { recursive: true });
3074
+ writeFileSync9(join16(projectDir, "index.html"), indexHtml);
3075
+ mkdirSync7(srcDir, { recursive: true });
2841
3076
  const mainTsx = `import { StrictMode } from 'react';
2842
3077
  import { createRoot } from 'react-dom/client';
2843
3078
  import { ${routerImport} } from 'react-router-dom';
@@ -2854,7 +3089,7 @@ createRoot(document.getElementById('root')!).render(
2854
3089
  </StrictMode>,
2855
3090
  );
2856
3091
  `;
2857
- writeFileSync8(join17(srcDir, "main.tsx"), mainTsx);
3092
+ writeFileSync9(join16(srcDir, "main.tsx"), mainTsx);
2858
3093
  const appTsx = `import { css } from '@decantr/css';
2859
3094
  import { Routes, Route } from 'react-router-dom';
2860
3095
 
@@ -2889,9 +3124,9 @@ export function App() {
2889
3124
  );
2890
3125
  }
2891
3126
  `;
2892
- writeFileSync8(join17(srcDir, "App.tsx"), appTsx);
2893
- writeFileSync8(join17(srcDir, "vite-env.d.ts"), '/// <reference types="vite/client" />\n');
2894
- mkdirSync6(join17(srcDir, "styles"), { recursive: true });
3127
+ writeFileSync9(join16(srcDir, "App.tsx"), appTsx);
3128
+ writeFileSync9(join16(srcDir, "vite-env.d.ts"), '/// <reference types="vite/client" />\n');
3129
+ mkdirSync7(join16(srcDir, "styles"), { recursive: true });
2895
3130
  }
2896
3131
  };
2897
3132
  var nextAppAdapter = {
@@ -2916,8 +3151,8 @@ var nextAppAdapter = {
2916
3151
  distDir: ".next"
2917
3152
  },
2918
3153
  writeProjectFiles(projectDir, title) {
2919
- const appDir = join17(projectDir, "app");
2920
- const stylesDir = join17(projectDir, "src", "styles");
3154
+ const appDir = join16(projectDir, "app");
3155
+ const stylesDir = join16(projectDir, "src", "styles");
2921
3156
  const packageJson = {
2922
3157
  name: basename(projectDir) || "decantr-next-app",
2923
3158
  private: true,
@@ -2942,11 +3177,11 @@ var nextAppAdapter = {
2942
3177
  typescript: "^5.7.0"
2943
3178
  }
2944
3179
  };
2945
- writeFileSync8(join17(projectDir, "package.json"), JSON.stringify(packageJson, null, 2) + "\n");
2946
- writeFileSync8(join17(projectDir, "next.config.ts"), 'import type { NextConfig } from "next";\n\nconst nextConfig: NextConfig = {};\n\nexport default nextConfig;\n');
2947
- 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');
2948
- writeFileSync8(
2949
- join17(projectDir, "tsconfig.json"),
3180
+ writeFileSync9(join16(projectDir, "package.json"), JSON.stringify(packageJson, null, 2) + "\n");
3181
+ writeFileSync9(join16(projectDir, "next.config.ts"), 'import type { NextConfig } from "next";\n\nconst nextConfig: NextConfig = {};\n\nexport default nextConfig;\n');
3182
+ writeFileSync9(join16(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');
3183
+ writeFileSync9(
3184
+ join16(projectDir, "tsconfig.json"),
2950
3185
  JSON.stringify(
2951
3186
  {
2952
3187
  compilerOptions: {
@@ -2972,10 +3207,10 @@ var nextAppAdapter = {
2972
3207
  2
2973
3208
  ) + "\n"
2974
3209
  );
2975
- mkdirSync6(appDir, { recursive: true });
2976
- mkdirSync6(stylesDir, { recursive: true });
2977
- writeFileSync8(
2978
- join17(appDir, "layout.tsx"),
3210
+ mkdirSync7(appDir, { recursive: true });
3211
+ mkdirSync7(stylesDir, { recursive: true });
3212
+ writeFileSync9(
3213
+ join16(appDir, "layout.tsx"),
2979
3214
  `import type { Metadata } from 'next';
2980
3215
  import '../src/styles/global.css';
2981
3216
  import '../src/styles/tokens.css';
@@ -2995,8 +3230,8 @@ export default function RootLayout({ children }: Readonly<{ children: React.Reac
2995
3230
  }
2996
3231
  `
2997
3232
  );
2998
- writeFileSync8(
2999
- join17(appDir, "page.tsx"),
3233
+ writeFileSync9(
3234
+ join16(appDir, "page.tsx"),
3000
3235
  `import { css } from '@decantr/css';
3001
3236
 
3002
3237
  export default function HomePage() {
@@ -3066,7 +3301,7 @@ function getBootstrapAdapter(resolution) {
3066
3301
  }
3067
3302
  function detectRoutingMode(projectDir) {
3068
3303
  try {
3069
- const essence = JSON.parse(readFileSync12(join17(projectDir, "decantr.essence.json"), "utf-8"));
3304
+ const essence = JSON.parse(readFileSync11(join16(projectDir, "decantr.essence.json"), "utf-8"));
3070
3305
  const routing = essence.meta?.platform?.routing;
3071
3306
  if (routing === "history" || routing === "pathname") {
3072
3307
  return routing;
@@ -3078,45 +3313,45 @@ function detectRoutingMode(projectDir) {
3078
3313
  }
3079
3314
 
3080
3315
  // src/offline-content.ts
3081
- import { cpSync, existsSync as existsSync17, mkdirSync as mkdirSync7 } from "fs";
3082
- import { join as join18, resolve } from "path";
3316
+ import { cpSync, existsSync as existsSync16, mkdirSync as mkdirSync8 } from "fs";
3317
+ import { join as join17, resolve } from "path";
3083
3318
  var CONTENT_TYPES2 = ["archetypes", "blueprints", "patterns", "themes", "shells"];
3084
3319
  function copyIfExists(source, target) {
3085
- if (!existsSync17(source)) return false;
3320
+ if (!existsSync16(source)) return false;
3086
3321
  if (resolve(source) === resolve(target)) return true;
3087
3322
  cpSync(source, target, { recursive: true });
3088
3323
  return true;
3089
3324
  }
3090
3325
  function hydrateContentRoot(projectDir, contentRoot) {
3091
- if (!existsSync17(contentRoot)) return false;
3092
- const customRoot = join18(projectDir, ".decantr", "custom");
3093
- const cacheRoot = join18(projectDir, ".decantr", "cache", "@official");
3094
- mkdirSync7(customRoot, { recursive: true });
3095
- mkdirSync7(cacheRoot, { recursive: true });
3326
+ if (!existsSync16(contentRoot)) return false;
3327
+ const customRoot = join17(projectDir, ".decantr", "custom");
3328
+ const cacheRoot = join17(projectDir, ".decantr", "cache", "@official");
3329
+ mkdirSync8(customRoot, { recursive: true });
3330
+ mkdirSync8(cacheRoot, { recursive: true });
3096
3331
  let copiedAny = false;
3097
3332
  for (const type of CONTENT_TYPES2) {
3098
- const sourceDir = join18(contentRoot, type);
3099
- if (!existsSync17(sourceDir)) continue;
3100
- cpSync(sourceDir, join18(customRoot, type), { recursive: true });
3101
- cpSync(sourceDir, join18(cacheRoot, type), { recursive: true });
3333
+ const sourceDir = join17(contentRoot, type);
3334
+ if (!existsSync16(sourceDir)) continue;
3335
+ cpSync(sourceDir, join17(customRoot, type), { recursive: true });
3336
+ cpSync(sourceDir, join17(cacheRoot, type), { recursive: true });
3102
3337
  copiedAny = true;
3103
3338
  }
3104
3339
  return copiedAny;
3105
3340
  }
3106
3341
  function seedOfflineRegistry(projectDir, workspaceRoot) {
3107
- const projectDecantrRoot = join18(projectDir, ".decantr");
3108
- mkdirSync7(projectDecantrRoot, { recursive: true });
3342
+ const projectDecantrRoot = join17(projectDir, ".decantr");
3343
+ mkdirSync8(projectDecantrRoot, { recursive: true });
3109
3344
  const configuredContentRoot = process.env.DECANTR_CONTENT_DIR ? resolve(process.env.DECANTR_CONTENT_DIR) : null;
3110
3345
  if (configuredContentRoot && hydrateContentRoot(projectDir, configuredContentRoot)) {
3111
3346
  return { seeded: true, strategy: "configured-content-root" };
3112
3347
  }
3113
3348
  const copiedCache = copyIfExists(
3114
- join18(workspaceRoot, ".decantr", "cache"),
3115
- join18(projectDecantrRoot, "cache")
3349
+ join17(workspaceRoot, ".decantr", "cache"),
3350
+ join17(projectDecantrRoot, "cache")
3116
3351
  );
3117
3352
  const copiedCustom = copyIfExists(
3118
- join18(workspaceRoot, ".decantr", "custom"),
3119
- join18(projectDecantrRoot, "custom")
3353
+ join17(workspaceRoot, ".decantr", "custom"),
3354
+ join17(projectDecantrRoot, "custom")
3120
3355
  );
3121
3356
  if (copiedCache || copiedCustom) {
3122
3357
  return { seeded: true, strategy: "workspace-cache" };
@@ -3207,7 +3442,7 @@ function runArgvCommand(command, args, cwd) {
3207
3442
  }
3208
3443
  function resolveInitCommand(initFlags) {
3209
3444
  const bundledCliEntrypoint = fileURLToPath(new URL("./bin.js", import.meta.url));
3210
- const cliEntrypoint = existsSync18(bundledCliEntrypoint) ? bundledCliEntrypoint : process.argv[1] && existsSync18(process.argv[1]) ? process.argv[1] : null;
3445
+ const cliEntrypoint = existsSync17(bundledCliEntrypoint) ? bundledCliEntrypoint : process.argv[1] && existsSync17(process.argv[1]) ? process.argv[1] : null;
3211
3446
  if (cliEntrypoint) {
3212
3447
  return {
3213
3448
  command: process.execPath,
@@ -3234,7 +3469,7 @@ async function cmdNewProject(projectName, options) {
3234
3469
  process.exitCode = 1;
3235
3470
  return;
3236
3471
  }
3237
- if (existsSync18(projectDir)) {
3472
+ if (existsSync17(projectDir)) {
3238
3473
  console.error(error2(`Directory "${projectName}" already exists.`));
3239
3474
  process.exitCode = 1;
3240
3475
  return;
@@ -3248,7 +3483,7 @@ async function cmdNewProject(projectName, options) {
3248
3483
  return;
3249
3484
  }
3250
3485
  console.log(heading(`Creating ${projectName}...`));
3251
- mkdirSync8(projectDir, { recursive: true });
3486
+ mkdirSync9(projectDir, { recursive: true });
3252
3487
  console.log(dim2(` Created ${projectName}/`));
3253
3488
  const title = projectName.replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
3254
3489
  if (shouldBootstrapRuntime && bootstrapAdapter) {
@@ -3339,21 +3574,21 @@ ${YELLOW4}Decantr init encountered issues. Run \`decantr init\` manually inside
3339
3574
  console.log("");
3340
3575
  }
3341
3576
  function detectPackageManager() {
3342
- if (existsSync18(join19(process.cwd(), "pnpm-lock.yaml")) || existsSync18(join19(process.cwd(), "pnpm-workspace.yaml"))) {
3577
+ if (existsSync17(join18(process.cwd(), "pnpm-lock.yaml")) || existsSync17(join18(process.cwd(), "pnpm-workspace.yaml"))) {
3343
3578
  return "pnpm";
3344
3579
  }
3345
- if (existsSync18(join19(process.cwd(), "yarn.lock"))) {
3580
+ if (existsSync17(join18(process.cwd(), "yarn.lock"))) {
3346
3581
  return "yarn";
3347
3582
  }
3348
- if (existsSync18(join19(process.cwd(), "bun.lockb")) || existsSync18(join19(process.cwd(), "bun.lock"))) {
3583
+ if (existsSync17(join18(process.cwd(), "bun.lockb")) || existsSync17(join18(process.cwd(), "bun.lock"))) {
3349
3584
  return "bun";
3350
3585
  }
3351
3586
  return "npm";
3352
3587
  }
3353
3588
 
3354
3589
  // src/commands/publish.ts
3355
- import { existsSync as existsSync19, readFileSync as readFileSync13 } from "fs";
3356
- import { join as join20 } from "path";
3590
+ import { existsSync as existsSync18, readFileSync as readFileSync12 } from "fs";
3591
+ import { join as join19 } from "path";
3357
3592
  import {
3358
3593
  API_CONTENT_TYPE_TO_CONTENT_TYPE,
3359
3594
  CONTENT_TYPE_TO_API_CONTENT_TYPE as CONTENT_TYPE_TO_API_CONTENT_TYPE2,
@@ -3369,8 +3604,8 @@ async function cmdPublish(type, name, projectRoot = process.cwd()) {
3369
3604
  }
3370
3605
  const singularType = API_CONTENT_TYPE_TO_CONTENT_TYPE[type] || type;
3371
3606
  const pluralType = CONTENT_TYPE_TO_API_CONTENT_TYPE2[type] || CONTENT_TYPE_TO_API_CONTENT_TYPE2[singularType] || `${type}s`;
3372
- const customPath = join20(projectRoot, ".decantr", "custom", pluralType, `${name}.json`);
3373
- if (!existsSync19(customPath)) {
3607
+ const customPath = join19(projectRoot, ".decantr", "custom", pluralType, `${name}.json`);
3608
+ if (!existsSync18(customPath)) {
3374
3609
  console.error(`Custom ${singularType} "${name}" not found at ${customPath}`);
3375
3610
  console.error(`Create one first: decantr create ${singularType} ${name}`);
3376
3611
  process.exitCode = 1;
@@ -3378,7 +3613,7 @@ async function cmdPublish(type, name, projectRoot = process.cwd()) {
3378
3613
  }
3379
3614
  let data;
3380
3615
  try {
3381
- data = JSON.parse(readFileSync13(customPath, "utf-8"));
3616
+ data = JSON.parse(readFileSync12(customPath, "utf-8"));
3382
3617
  } catch {
3383
3618
  console.error(`Failed to parse ${customPath}`);
3384
3619
  process.exitCode = 1;
@@ -3416,25 +3651,25 @@ async function cmdPublish(type, name, projectRoot = process.cwd()) {
3416
3651
  }
3417
3652
 
3418
3653
  // src/commands/refresh.ts
3419
- import { existsSync as existsSync20, readFileSync as readFileSync14 } from "fs";
3420
- import { join as join21 } from "path";
3421
- import { isV3 as isV33 } from "@decantr/essence-spec";
3654
+ import { existsSync as existsSync19, readFileSync as readFileSync13 } from "fs";
3655
+ import { join as join20 } from "path";
3656
+ import { isV3 as isV34 } from "@decantr/essence-spec";
3422
3657
  var GREEN7 = "\x1B[32m";
3423
3658
  var RED6 = "\x1B[31m";
3424
3659
  var DIM7 = "\x1B[2m";
3425
3660
  var RESET7 = "\x1B[0m";
3426
3661
  async function cmdRefresh(projectRoot = process.cwd(), options = {}) {
3427
- const essencePath = join21(projectRoot, "decantr.essence.json");
3428
- if (!existsSync20(essencePath)) {
3662
+ const essencePath = join20(projectRoot, "decantr.essence.json");
3663
+ if (!existsSync19(essencePath)) {
3429
3664
  console.error(`${RED6}No decantr.essence.json found. Run \`decantr init\` first.${RESET7}`);
3430
3665
  process.exitCode = 1;
3431
3666
  return;
3432
3667
  }
3433
3668
  let essence;
3434
3669
  try {
3435
- const raw = readFileSync14(essencePath, "utf-8");
3670
+ const raw = readFileSync13(essencePath, "utf-8");
3436
3671
  const parsed = JSON.parse(raw);
3437
- if (!isV33(parsed)) {
3672
+ if (!isV34(parsed)) {
3438
3673
  console.error(`${RED6}Essence is not v3. Run \`decantr migrate\` first.${RESET7}`);
3439
3674
  process.exitCode = 1;
3440
3675
  return;
@@ -3446,7 +3681,7 @@ async function cmdRefresh(projectRoot = process.cwd(), options = {}) {
3446
3681
  return;
3447
3682
  }
3448
3683
  const registryClient = new RegistryClient({
3449
- cacheDir: join21(projectRoot, ".decantr", "cache"),
3684
+ cacheDir: join20(projectRoot, ".decantr", "cache"),
3450
3685
  offline: options.offline
3451
3686
  });
3452
3687
  console.log("Regenerating derived files...\n");
@@ -3466,8 +3701,8 @@ async function cmdRefresh(projectRoot = process.cwd(), options = {}) {
3466
3701
  }
3467
3702
 
3468
3703
  // src/commands/registry-mirror.ts
3469
- import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync9 } from "fs";
3470
- import { join as join22 } from "path";
3704
+ import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync10 } from "fs";
3705
+ import { join as join21 } from "path";
3471
3706
  import { API_CONTENT_TYPES, RegistryAPIClient as RegistryAPIClient2 } from "@decantr/registry";
3472
3707
  var GREEN8 = "\x1B[32m";
3473
3708
  var RED7 = "\x1B[31m";
@@ -3495,7 +3730,7 @@ async function cmdRegistryMirror(projectRoot, options = {}) {
3495
3730
  process.exitCode = 1;
3496
3731
  return;
3497
3732
  }
3498
- const cacheDir = join22(projectRoot, ".decantr", "cache");
3733
+ const cacheDir = join21(projectRoot, ".decantr", "cache");
3499
3734
  const counts = {};
3500
3735
  const failed = [];
3501
3736
  console.log(`
@@ -3505,19 +3740,19 @@ Mirroring registry content to ${DIM8}.decantr/cache/${RESET8}
3505
3740
  try {
3506
3741
  const result = await apiClient.listContent(type, { namespace: "@official" });
3507
3742
  const items = result.items;
3508
- const typeDir = join22(cacheDir, "@official", type);
3509
- mkdirSync9(typeDir, { recursive: true });
3510
- writeFileSync9(join22(typeDir, "index.json"), JSON.stringify(result, null, 2));
3743
+ const typeDir = join21(cacheDir, "@official", type);
3744
+ mkdirSync10(typeDir, { recursive: true });
3745
+ writeFileSync10(join21(typeDir, "index.json"), JSON.stringify(result, null, 2));
3511
3746
  let itemCount = 0;
3512
3747
  for (const item of items) {
3513
3748
  const slug = item.slug || item.id;
3514
3749
  if (!slug) continue;
3515
3750
  try {
3516
3751
  const fullItem = await apiClient.getContent(type, "@official", slug);
3517
- writeFileSync9(join22(typeDir, `${slug}.json`), JSON.stringify(fullItem, null, 2));
3752
+ writeFileSync10(join21(typeDir, `${slug}.json`), JSON.stringify(fullItem, null, 2));
3518
3753
  itemCount++;
3519
3754
  } catch {
3520
- writeFileSync9(join22(typeDir, `${slug}.json`), JSON.stringify(item, null, 2));
3755
+ writeFileSync10(join21(typeDir, `${slug}.json`), JSON.stringify(item, null, 2));
3521
3756
  itemCount++;
3522
3757
  }
3523
3758
  }
@@ -3532,8 +3767,8 @@ Mirroring registry content to ${DIM8}.decantr/cache/${RESET8}
3532
3767
  mirrored_at: (/* @__PURE__ */ new Date()).toISOString(),
3533
3768
  counts
3534
3769
  };
3535
- mkdirSync9(join22(cacheDir), { recursive: true });
3536
- writeFileSync9(join22(cacheDir, "mirror-manifest.json"), JSON.stringify(manifest, null, 2));
3770
+ mkdirSync10(join21(cacheDir), { recursive: true });
3771
+ writeFileSync10(join21(cacheDir, "mirror-manifest.json"), JSON.stringify(manifest, null, 2));
3537
3772
  const totalItems = Object.values(counts).reduce((a, b) => a + b, 0);
3538
3773
  console.log("");
3539
3774
  if (failed.length > 0) {
@@ -3550,29 +3785,29 @@ Mirroring registry content to ${DIM8}.decantr/cache/${RESET8}
3550
3785
  }
3551
3786
 
3552
3787
  // src/commands/remove.ts
3553
- import { existsSync as existsSync22, readFileSync as readFileSync15, rmSync as rmSync2, writeFileSync as writeFileSync10 } from "fs";
3554
- import { join as join23 } from "path";
3555
- import { isV3 as isV34, migrateV30ToV31 as migrateV30ToV312 } from "@decantr/essence-spec";
3788
+ import { existsSync as existsSync21, readFileSync as readFileSync14, rmSync as rmSync2, writeFileSync as writeFileSync11 } from "fs";
3789
+ import { join as join22 } from "path";
3790
+ import { isV3 as isV35, migrateV30ToV31 as migrateV30ToV312 } from "@decantr/essence-spec";
3556
3791
  var GREEN9 = "\x1B[32m";
3557
3792
  var RED8 = "\x1B[31m";
3558
3793
  var DIM9 = "\x1B[2m";
3559
3794
  var RESET9 = "\x1B[0m";
3560
3795
  function readAndMigrate2(projectRoot) {
3561
- const essencePath = join23(projectRoot, "decantr.essence.json");
3562
- if (!existsSync22(essencePath)) {
3796
+ const essencePath = join22(projectRoot, "decantr.essence.json");
3797
+ if (!existsSync21(essencePath)) {
3563
3798
  console.error(`${RED8}No decantr.essence.json found. Run \`decantr init\` first.${RESET9}`);
3564
3799
  process.exitCode = 1;
3565
3800
  return null;
3566
3801
  }
3567
3802
  let parsed;
3568
3803
  try {
3569
- parsed = JSON.parse(readFileSync15(essencePath, "utf-8"));
3804
+ parsed = JSON.parse(readFileSync14(essencePath, "utf-8"));
3570
3805
  } catch (e) {
3571
3806
  console.error(`${RED8}Could not read essence: ${e.message}${RESET9}`);
3572
3807
  process.exitCode = 1;
3573
3808
  return null;
3574
3809
  }
3575
- if (!isV34(parsed)) {
3810
+ if (!isV35(parsed)) {
3576
3811
  console.error(`${RED8}Essence is not v3. Run \`decantr migrate\` first.${RESET9}`);
3577
3812
  process.exitCode = 1;
3578
3813
  return null;
@@ -3581,7 +3816,7 @@ function readAndMigrate2(projectRoot) {
3581
3816
  return { essence, essencePath };
3582
3817
  }
3583
3818
  function writeEssence2(essencePath, essence) {
3584
- writeFileSync10(essencePath, JSON.stringify(essence, null, 2) + "\n");
3819
+ writeFileSync11(essencePath, JSON.stringify(essence, null, 2) + "\n");
3585
3820
  }
3586
3821
  function recomputeGlobalFeatures(essence) {
3587
3822
  const all = /* @__PURE__ */ new Set();
@@ -3623,14 +3858,14 @@ async function cmdRemoveSection(sectionId, args, projectRoot = process.cwd()) {
3623
3858
  sections.splice(idx, 1);
3624
3859
  recomputeGlobalFeatures(essence);
3625
3860
  removeRoutes(essence, sectionId);
3626
- const contextFile = join23(projectRoot, ".decantr", "context", `${sectionId}.md`);
3627
- if (existsSync22(contextFile)) {
3861
+ const contextFile = join22(projectRoot, ".decantr", "context", `${sectionId}.md`);
3862
+ if (existsSync21(contextFile)) {
3628
3863
  rmSync2(contextFile);
3629
3864
  }
3630
3865
  writeEssence2(essencePath, essence);
3631
3866
  console.log(`${GREEN9}Removed section "${sectionId}".${RESET9}`);
3632
3867
  const registryClient = new RegistryClient({
3633
- cacheDir: join23(projectRoot, ".decantr", "cache")
3868
+ cacheDir: join22(projectRoot, ".decantr", "cache")
3634
3869
  });
3635
3870
  await refreshDerivedFiles(projectRoot, essence, registryClient);
3636
3871
  console.log(`${GREEN9}Derived files refreshed.${RESET9}`);
@@ -3666,7 +3901,7 @@ async function cmdRemovePage(path, args, projectRoot = process.cwd()) {
3666
3901
  writeEssence2(essencePath, essence);
3667
3902
  console.log(`${GREEN9}Removed page "${pageId}" from section "${sectionId}".${RESET9}`);
3668
3903
  const registryClient = new RegistryClient({
3669
- cacheDir: join23(projectRoot, ".decantr", "cache")
3904
+ cacheDir: join22(projectRoot, ".decantr", "cache")
3670
3905
  });
3671
3906
  await refreshDerivedFiles(projectRoot, essence, registryClient);
3672
3907
  console.log(`${GREEN9}Derived files refreshed.${RESET9}`);
@@ -3707,15 +3942,15 @@ async function cmdRemoveFeature(feature, args, projectRoot = process.cwd()) {
3707
3942
  const target = sectionId ? `section "${sectionId}" and global` : "global";
3708
3943
  console.log(`${GREEN9}Removed feature "${feature}" from ${target} features.${RESET9}`);
3709
3944
  const registryClient = new RegistryClient({
3710
- cacheDir: join23(projectRoot, ".decantr", "cache")
3945
+ cacheDir: join22(projectRoot, ".decantr", "cache")
3711
3946
  });
3712
3947
  await refreshDerivedFiles(projectRoot, essence, registryClient);
3713
3948
  console.log(`${GREEN9}Derived files refreshed.${RESET9}`);
3714
3949
  }
3715
3950
 
3716
3951
  // src/commands/sync-drift.ts
3717
- import { existsSync as existsSync23, readFileSync as readFileSync16, writeFileSync as writeFileSync11 } from "fs";
3718
- import { join as join24 } from "path";
3952
+ import { existsSync as existsSync22, readFileSync as readFileSync15, writeFileSync as writeFileSync12 } from "fs";
3953
+ import { join as join23 } from "path";
3719
3954
  var GREEN10 = "\x1B[32m";
3720
3955
  var RED9 = "\x1B[31m";
3721
3956
  var YELLOW6 = "\x1B[33m";
@@ -3724,8 +3959,8 @@ var DIM10 = "\x1B[2m";
3724
3959
  var BOLD4 = "\x1B[1m";
3725
3960
  var CYAN5 = "\x1B[36m";
3726
3961
  async function cmdSyncDrift(projectRoot = process.cwd()) {
3727
- const driftLogPath = join24(projectRoot, ".decantr", "drift-log.json");
3728
- if (!existsSync23(driftLogPath)) {
3962
+ const driftLogPath = join23(projectRoot, ".decantr", "drift-log.json");
3963
+ if (!existsSync22(driftLogPath)) {
3729
3964
  console.log(`${GREEN10}No drift log found \u2014 no drift recorded.${RESET10}`);
3730
3965
  console.log(
3731
3966
  `${DIM10}Drift is logged when guard violations are detected during development.${RESET10}`
@@ -3734,7 +3969,7 @@ async function cmdSyncDrift(projectRoot = process.cwd()) {
3734
3969
  }
3735
3970
  let entries;
3736
3971
  try {
3737
- entries = JSON.parse(readFileSync16(driftLogPath, "utf-8"));
3972
+ entries = JSON.parse(readFileSync15(driftLogPath, "utf-8"));
3738
3973
  } catch {
3739
3974
  console.error(`${RED9}Could not parse drift-log.json${RESET10}`);
3740
3975
  process.exitCode = 1;
@@ -3780,13 +4015,13 @@ ${BOLD4}Unresolved Drift Entries (${unresolved.length})${RESET10}
3780
4015
  console.log("");
3781
4016
  }
3782
4017
  function resolveDriftEntries(projectRoot, options) {
3783
- const driftLogPath = join24(projectRoot, ".decantr", "drift-log.json");
3784
- if (!existsSync23(driftLogPath)) {
4018
+ const driftLogPath = join23(projectRoot, ".decantr", "drift-log.json");
4019
+ if (!existsSync22(driftLogPath)) {
3785
4020
  return { success: true };
3786
4021
  }
3787
4022
  if (options.clear) {
3788
4023
  try {
3789
- writeFileSync11(driftLogPath, "[]");
4024
+ writeFileSync12(driftLogPath, "[]");
3790
4025
  return { success: true };
3791
4026
  } catch (e) {
3792
4027
  return { success: false, error: `Could not clear drift log: ${e.message}` };
@@ -3794,7 +4029,7 @@ function resolveDriftEntries(projectRoot, options) {
3794
4029
  }
3795
4030
  let entries;
3796
4031
  try {
3797
- entries = JSON.parse(readFileSync16(driftLogPath, "utf-8"));
4032
+ entries = JSON.parse(readFileSync15(driftLogPath, "utf-8"));
3798
4033
  } catch {
3799
4034
  return { success: false, error: "Could not parse drift-log.json" };
3800
4035
  }
@@ -3813,7 +4048,7 @@ function resolveDriftEntries(projectRoot, options) {
3813
4048
  }
3814
4049
  }
3815
4050
  try {
3816
- writeFileSync11(driftLogPath, JSON.stringify(entries, null, 2));
4051
+ writeFileSync12(driftLogPath, JSON.stringify(entries, null, 2));
3817
4052
  return { success: true };
3818
4053
  } catch (e) {
3819
4054
  return { success: false, error: `Could not write drift log: ${e.message}` };
@@ -3821,9 +4056,9 @@ function resolveDriftEntries(projectRoot, options) {
3821
4056
  }
3822
4057
 
3823
4058
  // src/commands/theme-switch.ts
3824
- import { existsSync as existsSync24, readFileSync as readFileSync17, writeFileSync as writeFileSync12 } from "fs";
3825
- import { join as join25 } from "path";
3826
- import { isV3 as isV35, migrateV30ToV31 as migrateV30ToV313 } from "@decantr/essence-spec";
4059
+ import { existsSync as existsSync23, readFileSync as readFileSync16, writeFileSync as writeFileSync13 } from "fs";
4060
+ import { join as join24 } from "path";
4061
+ import { isV3 as isV36, migrateV30ToV31 as migrateV30ToV313 } from "@decantr/essence-spec";
3827
4062
  var GREEN11 = "\x1B[32m";
3828
4063
  var RED10 = "\x1B[31m";
3829
4064
  var YELLOW7 = "\x1B[33m";
@@ -3839,21 +4074,21 @@ async function cmdThemeSwitch(themeName, args, projectRoot = process.cwd()) {
3839
4074
  process.exitCode = 1;
3840
4075
  return;
3841
4076
  }
3842
- const essencePath = join25(projectRoot, "decantr.essence.json");
3843
- if (!existsSync24(essencePath)) {
4077
+ const essencePath = join24(projectRoot, "decantr.essence.json");
4078
+ if (!existsSync23(essencePath)) {
3844
4079
  console.error(`${RED10}No decantr.essence.json found. Run \`decantr init\` first.${RESET11}`);
3845
4080
  process.exitCode = 1;
3846
4081
  return;
3847
4082
  }
3848
4083
  let parsed;
3849
4084
  try {
3850
- parsed = JSON.parse(readFileSync17(essencePath, "utf-8"));
4085
+ parsed = JSON.parse(readFileSync16(essencePath, "utf-8"));
3851
4086
  } catch (e) {
3852
4087
  console.error(`${RED10}Could not read essence: ${e.message}${RESET11}`);
3853
4088
  process.exitCode = 1;
3854
4089
  return;
3855
4090
  }
3856
- if (!isV35(parsed)) {
4091
+ if (!isV36(parsed)) {
3857
4092
  console.error(`${RED10}Essence is not v3. Run \`decantr migrate\` first.${RESET11}`);
3858
4093
  process.exitCode = 1;
3859
4094
  return;
@@ -3896,7 +4131,7 @@ async function cmdThemeSwitch(themeName, args, projectRoot = process.cwd()) {
3896
4131
  essence.dna.theme.mode = mode;
3897
4132
  }
3898
4133
  const registryClient = new RegistryClient({
3899
- cacheDir: join25(projectRoot, ".decantr", "cache")
4134
+ cacheDir: join24(projectRoot, ".decantr", "cache")
3900
4135
  });
3901
4136
  try {
3902
4137
  const themeResult = await registryClient.fetchTheme(themeName);
@@ -3911,7 +4146,7 @@ async function cmdThemeSwitch(themeName, args, projectRoot = process.cwd()) {
3911
4146
  }
3912
4147
  } catch {
3913
4148
  }
3914
- writeFileSync12(essencePath, JSON.stringify(essence, null, 2) + "\n");
4149
+ writeFileSync13(essencePath, JSON.stringify(essence, null, 2) + "\n");
3915
4150
  console.log(`${GREEN11}Switched theme: ${oldThemeId} \u2192 ${themeName}${RESET11}`);
3916
4151
  if (shape) console.log(` ${DIM11}Shape: ${shape}${RESET11}`);
3917
4152
  if (mode) console.log(` ${DIM11}Mode: ${mode}${RESET11}`);
@@ -3992,6 +4227,7 @@ ${CYAN6}Detected project configuration:${RESET12}`);
3992
4227
  }
3993
4228
  async function runInteractivePrompts(detected, archetypes, blueprints, themes, workflowSeed) {
3994
4229
  showDetection(detected);
4230
+ const existingProject = Boolean(workflowSeed?.existing) || detected.existingEssence || hasExistingProjectFootprint(detected);
3995
4231
  const blueprintOptions = [
3996
4232
  { value: "none", label: "none", description: "Start from scratch (blank canvas)" },
3997
4233
  ...blueprints.map((b) => ({
@@ -4002,12 +4238,21 @@ async function runInteractivePrompts(detected, archetypes, blueprints, themes, w
4002
4238
  ];
4003
4239
  const blueprint = await select("What are you building?", blueprintOptions, 0, true);
4004
4240
  const isBlank = blueprint === "none";
4005
- const themeOptions = themes.map((t) => ({
4006
- value: t.id,
4007
- label: t.id,
4008
- description: t.description
4009
- }));
4010
- const desiredTheme = workflowSeed?.theme || "luminarum";
4241
+ const themeOptions = [
4242
+ ...existingProject ? [
4243
+ {
4244
+ value: "existing",
4245
+ label: "existing",
4246
+ description: "Preserve the existing styling system"
4247
+ }
4248
+ ] : [],
4249
+ ...themes.map((t) => ({
4250
+ value: t.id,
4251
+ label: t.id,
4252
+ description: t.description
4253
+ }))
4254
+ ];
4255
+ const desiredTheme = workflowSeed?.theme || (existingProject ? "existing" : "luminarum");
4011
4256
  const defaultThemeIdx = Math.max(
4012
4257
  0,
4013
4258
  themeOptions.findIndex((t) => t.value === desiredTheme)
@@ -4020,7 +4265,7 @@ async function runInteractivePrompts(detected, archetypes, blueprints, themes, w
4020
4265
  { value: "light", label: "light", description: "Light background" },
4021
4266
  { value: "auto", label: "auto", description: "Follow system preference" }
4022
4267
  ],
4023
- workflowSeed?.mode === "light" ? 1 : workflowSeed?.mode === "auto" ? 2 : 0
4268
+ workflowSeed?.mode === "light" ? 1 : workflowSeed?.mode === "auto" || existingProject ? 2 : 0
4024
4269
  );
4025
4270
  const shape = await select(
4026
4271
  "Border shape",
@@ -4153,17 +4398,18 @@ function parseFlags(args, detected) {
4153
4398
  return options;
4154
4399
  }
4155
4400
  function mergeWithDefaults(flags, detected, workflowSeed) {
4401
+ const existingProject = flags.existing || workflowSeed?.existing || detected.existingEssence || hasExistingProjectFootprint(detected);
4156
4402
  return {
4157
4403
  blueprint: flags.blueprint,
4158
4404
  archetype: flags.archetype,
4159
- theme: flags.theme || workflowSeed?.theme || "luminarum",
4160
- mode: flags.mode || workflowSeed?.mode || "dark",
4405
+ theme: flags.theme || workflowSeed?.theme || (existingProject ? "existing" : "luminarum"),
4406
+ mode: flags.mode || workflowSeed?.mode || (existingProject ? "auto" : "dark"),
4161
4407
  shape: flags.shape || "rounded",
4162
4408
  target: flags.target || workflowSeed?.target || (detected.framework !== "unknown" ? detected.framework : "react"),
4163
4409
  guard: flags.guard || workflowSeed?.guard || (detected.existingEssence ? "guided" : "strict"),
4164
4410
  density: flags.density || workflowSeed?.density || "comfortable",
4165
- shell: flags.shell || workflowSeed?.shell || "sidebar-main",
4166
- personality: flags.personality || ["professional"],
4411
+ shell: flags.shell || workflowSeed?.shell || (existingProject ? "observed-existing-shell" : "sidebar-main"),
4412
+ personality: flags.personality || (existingProject ? ["observed brownfield product"] : ["professional"]),
4167
4413
  features: flags.features || [],
4168
4414
  existing: flags.existing || workflowSeed?.existing || detected.existingEssence,
4169
4415
  workflowMode: flags.workflowMode || workflowSeed?.workflowMode,
@@ -4207,8 +4453,8 @@ async function runSimplifiedInit(blueprints) {
4207
4453
  }
4208
4454
 
4209
4455
  // src/theme-commands.ts
4210
- import { existsSync as existsSync25, mkdirSync as mkdirSync10, readdirSync as readdirSync5, readFileSync as readFileSync18, rmSync as rmSync3, writeFileSync as writeFileSync13 } from "fs";
4211
- import { join as join26 } from "path";
4456
+ import { existsSync as existsSync24, mkdirSync as mkdirSync11, readdirSync as readdirSync4, readFileSync as readFileSync17, rmSync as rmSync3, writeFileSync as writeFileSync14 } from "fs";
4457
+ import { join as join25 } from "path";
4212
4458
  var REQUIRED_FIELDS = [
4213
4459
  "$schema",
4214
4460
  "id",
@@ -4268,20 +4514,20 @@ function validateCustomTheme(theme) {
4268
4514
  };
4269
4515
  }
4270
4516
  function createTheme(projectRoot, id, name) {
4271
- const customThemesDir = join26(projectRoot, ".decantr", "custom", "themes");
4272
- const themePath = join26(customThemesDir, `${id}.json`);
4273
- const howToPath = join26(customThemesDir, "how-to-theme.md");
4274
- mkdirSync10(customThemesDir, { recursive: true });
4275
- if (existsSync25(themePath)) {
4517
+ const customThemesDir = join25(projectRoot, ".decantr", "custom", "themes");
4518
+ const themePath = join25(customThemesDir, `${id}.json`);
4519
+ const howToPath = join25(customThemesDir, "how-to-theme.md");
4520
+ mkdirSync11(customThemesDir, { recursive: true });
4521
+ if (existsSync24(themePath)) {
4276
4522
  return {
4277
4523
  success: false,
4278
4524
  error: `Theme "${id}" already exists at ${themePath}`
4279
4525
  };
4280
4526
  }
4281
4527
  const skeleton = getThemeSkeleton(id, name);
4282
- writeFileSync13(themePath, JSON.stringify(skeleton, null, 2));
4283
- if (!existsSync25(howToPath)) {
4284
- writeFileSync13(howToPath, getHowToThemeDoc());
4528
+ writeFileSync14(themePath, JSON.stringify(skeleton, null, 2));
4529
+ if (!existsSync24(howToPath)) {
4530
+ writeFileSync14(howToPath, getHowToThemeDoc());
4285
4531
  }
4286
4532
  return {
4287
4533
  success: true,
@@ -4289,17 +4535,17 @@ function createTheme(projectRoot, id, name) {
4289
4535
  };
4290
4536
  }
4291
4537
  function listCustomThemes(projectRoot) {
4292
- const customThemesDir = join26(projectRoot, ".decantr", "custom", "themes");
4293
- if (!existsSync25(customThemesDir)) {
4538
+ const customThemesDir = join25(projectRoot, ".decantr", "custom", "themes");
4539
+ if (!existsSync24(customThemesDir)) {
4294
4540
  return [];
4295
4541
  }
4296
4542
  const themes = [];
4297
4543
  try {
4298
- const files = readdirSync5(customThemesDir).filter((f) => f.endsWith(".json"));
4544
+ const files = readdirSync4(customThemesDir).filter((f) => f.endsWith(".json"));
4299
4545
  for (const file of files) {
4300
- const filePath = join26(customThemesDir, file);
4546
+ const filePath = join25(customThemesDir, file);
4301
4547
  try {
4302
- const data = JSON.parse(readFileSync18(filePath, "utf-8"));
4548
+ const data = JSON.parse(readFileSync17(filePath, "utf-8"));
4303
4549
  themes.push({
4304
4550
  id: data.id || file.replace(".json", ""),
4305
4551
  name: data.name || data.id,
@@ -4314,8 +4560,8 @@ function listCustomThemes(projectRoot) {
4314
4560
  return themes;
4315
4561
  }
4316
4562
  function deleteTheme(projectRoot, id) {
4317
- const themePath = join26(projectRoot, ".decantr", "custom", "themes", `${id}.json`);
4318
- if (!existsSync25(themePath)) {
4563
+ const themePath = join25(projectRoot, ".decantr", "custom", "themes", `${id}.json`);
4564
+ if (!existsSync24(themePath)) {
4319
4565
  return {
4320
4566
  success: false,
4321
4567
  error: `Theme "${id}" not found at ${themePath}`
@@ -4332,7 +4578,7 @@ function deleteTheme(projectRoot, id) {
4332
4578
  }
4333
4579
  }
4334
4580
  function importTheme(projectRoot, sourcePath) {
4335
- if (!existsSync25(sourcePath)) {
4581
+ if (!existsSync24(sourcePath)) {
4336
4582
  return {
4337
4583
  success: false,
4338
4584
  errors: [`Source file not found: ${sourcePath}`]
@@ -4340,7 +4586,7 @@ function importTheme(projectRoot, sourcePath) {
4340
4586
  }
4341
4587
  let theme;
4342
4588
  try {
4343
- theme = JSON.parse(readFileSync18(sourcePath, "utf-8"));
4589
+ theme = JSON.parse(readFileSync17(sourcePath, "utf-8"));
4344
4590
  } catch (e) {
4345
4591
  return {
4346
4592
  success: false,
@@ -4356,14 +4602,14 @@ function importTheme(projectRoot, sourcePath) {
4356
4602
  }
4357
4603
  theme.source = "custom";
4358
4604
  const id = theme.id;
4359
- const customThemesDir = join26(projectRoot, ".decantr", "custom", "themes");
4360
- const destPath = join26(customThemesDir, `${id}.json`);
4361
- mkdirSync10(customThemesDir, { recursive: true });
4362
- const howToPath = join26(customThemesDir, "how-to-theme.md");
4363
- if (!existsSync25(howToPath)) {
4364
- writeFileSync13(howToPath, getHowToThemeDoc());
4365
- }
4366
- writeFileSync13(destPath, JSON.stringify(theme, null, 2));
4605
+ const customThemesDir = join25(projectRoot, ".decantr", "custom", "themes");
4606
+ const destPath = join25(customThemesDir, `${id}.json`);
4607
+ mkdirSync11(customThemesDir, { recursive: true });
4608
+ const howToPath = join25(customThemesDir, "how-to-theme.md");
4609
+ if (!existsSync24(howToPath)) {
4610
+ writeFileSync14(howToPath, getHowToThemeDoc());
4611
+ }
4612
+ writeFileSync14(destPath, JSON.stringify(theme, null, 2));
4367
4613
  return {
4368
4614
  success: true,
4369
4615
  path: destPath
@@ -4371,19 +4617,19 @@ function importTheme(projectRoot, sourcePath) {
4371
4617
  }
4372
4618
 
4373
4619
  // src/workspace.ts
4374
- import { existsSync as existsSync26, readdirSync as readdirSync6, readFileSync as readFileSync19 } from "fs";
4375
- import { dirname as dirname3, join as join27, resolve as resolve3 } from "path";
4620
+ import { existsSync as existsSync25, readdirSync as readdirSync5, readFileSync as readFileSync18 } from "fs";
4621
+ import { dirname as dirname3, join as join26, resolve as resolve3 } from "path";
4376
4622
  function readPackageJson(dir) {
4377
- const path = join27(dir, "package.json");
4378
- if (!existsSync26(path)) return null;
4623
+ const path = join26(dir, "package.json");
4624
+ if (!existsSync25(path)) return null;
4379
4625
  try {
4380
- return JSON.parse(readFileSync19(path, "utf-8"));
4626
+ return JSON.parse(readFileSync18(path, "utf-8"));
4381
4627
  } catch {
4382
4628
  return null;
4383
4629
  }
4384
4630
  }
4385
4631
  function hasWorkspaceMarker(dir) {
4386
- if (existsSync26(join27(dir, "pnpm-workspace.yaml")) || existsSync26(join27(dir, "turbo.json")) || existsSync26(join27(dir, "nx.json"))) {
4632
+ if (existsSync25(join26(dir, "pnpm-workspace.yaml")) || existsSync25(join26(dir, "turbo.json")) || existsSync25(join26(dir, "nx.json"))) {
4387
4633
  return true;
4388
4634
  }
4389
4635
  const pkg = readPackageJson(dir);
@@ -4399,7 +4645,7 @@ function findWorkspaceRoot(startDir) {
4399
4645
  }
4400
4646
  }
4401
4647
  function looksLikeApp(dir) {
4402
- 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"))) {
4648
+ if (existsSync25(join26(dir, "next.config.js")) || existsSync25(join26(dir, "next.config.ts")) || existsSync25(join26(dir, "next.config.mjs")) || existsSync25(join26(dir, "vite.config.ts")) || existsSync25(join26(dir, "vite.config.js")) || existsSync25(join26(dir, "angular.json")) || existsSync25(join26(dir, "svelte.config.js")) || existsSync25(join26(dir, "svelte.config.ts")) || existsSync25(join26(dir, "astro.config.mjs")) || existsSync25(join26(dir, "src")) || existsSync25(join26(dir, "app")) || existsSync25(join26(dir, "pages"))) {
4403
4649
  return true;
4404
4650
  }
4405
4651
  const pkg = readPackageJson(dir);
@@ -4411,11 +4657,11 @@ function looksLikeApp(dir) {
4411
4657
  function listWorkspaceApps(workspaceRoot) {
4412
4658
  const candidates = [];
4413
4659
  for (const base of ["apps", "packages"]) {
4414
- const baseDir = join27(workspaceRoot, base);
4415
- if (!existsSync26(baseDir)) continue;
4416
- for (const entry of readdirSync6(baseDir, { withFileTypes: true })) {
4660
+ const baseDir = join26(workspaceRoot, base);
4661
+ if (!existsSync25(baseDir)) continue;
4662
+ for (const entry of readdirSync5(baseDir, { withFileTypes: true })) {
4417
4663
  if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
4418
- const candidate = join27(baseDir, entry.name);
4664
+ const candidate = join26(baseDir, entry.name);
4419
4665
  if (looksLikeApp(candidate)) {
4420
4666
  candidates.push(`${base}/${entry.name}`);
4421
4667
  }
@@ -4797,7 +5043,8 @@ function generateBrownfieldPrompt(ctx) {
4797
5043
  lines.push("");
4798
5044
  if (ctx.analysisArtifacts) {
4799
5045
  lines.push("Treat .decantr/analysis.json as the factual inventory of the current app.");
4800
- lines.push("Treat .decantr/init-seed.json as the recommended Decantr attach defaults.");
5046
+ lines.push("Treat .decantr/doctrine-map.json, .decantr/ambient-context.json, and .decantr/brownfield-report.md as the ranked doctrine inventory and conflict report.");
5047
+ lines.push("Treat the accepted observed proposal as the source of Decantr route/section coverage.");
4801
5048
  } else {
4802
5049
  lines.push(
4803
5050
  "No Decantr analysis seed is present. Start by inventorying the app before changing runtime files."
@@ -4815,16 +5062,18 @@ function generateBrownfieldPrompt(ctx) {
4815
5062
  lines.push(
4816
5063
  "1. .decantr/analysis.json for the detected framework, routes, styling, layout, and dependencies."
4817
5064
  );
4818
- lines.push("2. .decantr/init-seed.json for the intended attach defaults and workflow lane.");
4819
- lines.push("3. DECANTR.md for guard rules, CSS expectations, and Decantr operating rules.");
5065
+ lines.push("2. .decantr/doctrine-map.json for ranked source precedence across security/data, architecture, design-system, workflow, feature, and assistant evidence.");
5066
+ lines.push("3. .decantr/ambient-context.json for assistant rules, docs, design-system, CI, schema, and workflow evidence.");
5067
+ lines.push("4. .decantr/brownfield-report.md for conflicts, stale risks, and acceptance context.");
5068
+ lines.push("5. DECANTR.md for guard rules, CSS expectations, and Decantr operating rules.");
4820
5069
  lines.push(
4821
- "4. .decantr/context/scaffold-pack.md for the compact compiled shell, theme, feature, and route contract."
5070
+ "6. .decantr/context/scaffold-pack.md for the compact compiled shell, theme, feature, and route contract."
4822
5071
  );
4823
5072
  lines.push(
4824
- "5. .decantr/context/scaffold.md for broader topology, route map, and voice guidance."
5073
+ "7. .decantr/context/scaffold.md for broader topology, route map, and voice guidance."
4825
5074
  );
4826
5075
  lines.push(
4827
- "6. The matching section and page pack files only when you are working on those specific surfaces."
5076
+ "8. The matching section and page pack files only when you are working on those specific surfaces."
4828
5077
  );
4829
5078
  } else {
4830
5079
  lines.push("1. Inventory existing framework, routes, styling, layout, rule files, and dependencies.");
@@ -4899,7 +5148,7 @@ function generateBrownfieldPrompt(ctx) {
4899
5148
  );
4900
5149
  lines.push("- Then attach or refine section pages using the matching section and page packs.");
4901
5150
  lines.push(
4902
- "- After implementation, run decantr check and decantr audit and fix contract or drift issues."
5151
+ "- After implementation, run decantr check --brownfield and decantr audit and fix contract or drift issues."
4903
5152
  );
4904
5153
  lines.push(
4905
5154
  "- If a required context file or runtime anchor is missing, stop and report exactly what is missing before continuing."
@@ -4934,18 +5183,18 @@ function extractHostedAssetPaths(indexHtml) {
4934
5183
  return [...assetPaths];
4935
5184
  }
4936
5185
  function readHostedDistSnapshot(distPath) {
4937
- const resolvedDistPath = distPath ? resolveUserPath(distPath) : join28(process.cwd(), "dist");
4938
- const indexPath = join28(resolvedDistPath, "index.html");
4939
- if (!existsSync27(indexPath)) {
5186
+ const resolvedDistPath = distPath ? resolveUserPath(distPath) : join27(process.cwd(), "dist");
5187
+ const indexPath = join27(resolvedDistPath, "index.html");
5188
+ if (!existsSync26(indexPath)) {
4940
5189
  return void 0;
4941
5190
  }
4942
- const indexHtml = readFileSync20(indexPath, "utf-8");
5191
+ const indexHtml = readFileSync19(indexPath, "utf-8");
4943
5192
  const assetPaths = extractHostedAssetPaths(indexHtml);
4944
5193
  const assets = {};
4945
5194
  for (const assetPath of assetPaths) {
4946
- const assetFilePath = join28(resolvedDistPath, assetPath.replace(/^[/\\]+/, ""));
4947
- if (existsSync27(assetFilePath)) {
4948
- assets[assetPath] = readFileSync20(assetFilePath, "utf-8");
5195
+ const assetFilePath = join27(resolvedDistPath, assetPath.replace(/^[/\\]+/, ""));
5196
+ if (existsSync26(assetFilePath)) {
5197
+ assets[assetPath] = readFileSync19(assetFilePath, "utf-8");
4949
5198
  }
4950
5199
  }
4951
5200
  return {
@@ -4960,7 +5209,7 @@ function isHostedSourceSnapshotFile(path) {
4960
5209
  function readHostedSourceSnapshot(sourcePath) {
4961
5210
  if (!sourcePath) return void 0;
4962
5211
  const resolvedSourcePath = resolveUserPath(sourcePath);
4963
- if (!existsSync27(resolvedSourcePath)) {
5212
+ if (!existsSync26(resolvedSourcePath)) {
4964
5213
  return void 0;
4965
5214
  }
4966
5215
  const files = {};
@@ -4974,17 +5223,17 @@ function readHostedSourceSnapshot(sourcePath) {
4974
5223
  ]);
4975
5224
  const rootPrefix = basename2(resolvedSourcePath);
4976
5225
  const walk = (absoluteDir, relativeDir) => {
4977
- for (const entry of readdirSync7(absoluteDir, { withFileTypes: true })) {
5226
+ for (const entry of readdirSync6(absoluteDir, { withFileTypes: true })) {
4978
5227
  if (ignoredDirNames.has(entry.name)) continue;
4979
- const absolutePath = join28(absoluteDir, entry.name);
4980
- const relativePath = join28(relativeDir, entry.name).replace(/\\/g, "/");
5228
+ const absolutePath = join27(absoluteDir, entry.name);
5229
+ const relativePath = join27(relativeDir, entry.name).replace(/\\/g, "/");
4981
5230
  if (entry.isDirectory()) {
4982
5231
  walk(absolutePath, relativePath);
4983
5232
  continue;
4984
5233
  }
4985
5234
  if (!entry.isFile()) continue;
4986
5235
  if (!isHostedSourceSnapshotFile(relativePath)) continue;
4987
- files[relativePath] = readFileSync20(absolutePath, "utf-8");
5236
+ files[relativePath] = readFileSync19(absolutePath, "utf-8");
4988
5237
  }
4989
5238
  };
4990
5239
  walk(resolvedSourcePath, rootPrefix);
@@ -5135,16 +5384,16 @@ async function printRegistryIntelligenceSummary(namespace, jsonOutput = false) {
5135
5384
  }
5136
5385
  async function printHostedExecutionPackBundle(essencePath, namespace, jsonOutput = false, writeContext = false) {
5137
5386
  const client = getPublicAPIClient();
5138
- const resolvedPath = essencePath ? resolveUserPath(essencePath) : join28(process.cwd(), "decantr.essence.json");
5139
- if (!existsSync27(resolvedPath)) {
5387
+ const resolvedPath = essencePath ? resolveUserPath(essencePath) : join27(process.cwd(), "decantr.essence.json");
5388
+ if (!existsSync26(resolvedPath)) {
5140
5389
  throw new Error(`Essence file not found at ${resolvedPath}`);
5141
5390
  }
5142
- const essence = JSON.parse(readFileSync20(resolvedPath, "utf-8"));
5391
+ const essence = JSON.parse(readFileSync19(resolvedPath, "utf-8"));
5143
5392
  const bundle = await client.compileExecutionPacks(essence, namespace ? { namespace } : void 0);
5144
5393
  let writtenContextPaths = [];
5145
5394
  if (writeContext) {
5146
- const contextDir = join28(process.cwd(), ".decantr", "context");
5147
- mkdirSync11(contextDir, { recursive: true });
5395
+ const contextDir = join27(process.cwd(), ".decantr", "context");
5396
+ mkdirSync12(contextDir, { recursive: true });
5148
5397
  const written = writeExecutionPackBundleArtifacts(
5149
5398
  contextDir,
5150
5399
  bundle
@@ -5169,7 +5418,7 @@ async function printHostedExecutionPackBundle(essencePath, namespace, jsonOutput
5169
5418
  console.log(` Sections: ${typedBundle.sections.length}`);
5170
5419
  console.log(` Mutations: ${typedBundle.mutations.length}`);
5171
5420
  if (writeContext) {
5172
- console.log(` Context bundle: ${join28(process.cwd(), ".decantr", "context")}`);
5421
+ console.log(` Context bundle: ${join27(process.cwd(), ".decantr", "context")}`);
5173
5422
  console.log(` Files written: ${writtenContextPaths.length}`);
5174
5423
  }
5175
5424
  console.log("");
@@ -5182,14 +5431,14 @@ async function printHostedExecutionPackBundle(essencePath, namespace, jsonOutput
5182
5431
  }
5183
5432
  async function printHostedSelectedExecutionPack(packType, id, essencePath, namespace, jsonOutput = false, writeContext = false) {
5184
5433
  const client = getPublicAPIClient();
5185
- const resolvedPath = essencePath ? resolveUserPath(essencePath) : join28(process.cwd(), "decantr.essence.json");
5186
- if (!existsSync27(resolvedPath)) {
5434
+ const resolvedPath = essencePath ? resolveUserPath(essencePath) : join27(process.cwd(), "decantr.essence.json");
5435
+ if (!existsSync26(resolvedPath)) {
5187
5436
  throw new Error(`Essence file not found at ${resolvedPath}`);
5188
5437
  }
5189
5438
  if ((packType === "section" || packType === "page" || packType === "mutation") && !id) {
5190
5439
  throw new Error(`Pack type "${packType}" requires an id.`);
5191
5440
  }
5192
- const essence = JSON.parse(readFileSync20(resolvedPath, "utf-8"));
5441
+ const essence = JSON.parse(readFileSync19(resolvedPath, "utf-8"));
5193
5442
  const selected = await client.selectExecutionPack(
5194
5443
  {
5195
5444
  essence,
@@ -5200,17 +5449,17 @@ async function printHostedSelectedExecutionPack(packType, id, essencePath, names
5200
5449
  );
5201
5450
  let writtenContextDir = null;
5202
5451
  if (writeContext) {
5203
- const contextDir = join28(process.cwd(), ".decantr", "context");
5204
- mkdirSync11(contextDir, { recursive: true });
5205
- writeFileSync14(
5206
- join28(contextDir, "pack-manifest.json"),
5452
+ const contextDir = join27(process.cwd(), ".decantr", "context");
5453
+ mkdirSync12(contextDir, { recursive: true });
5454
+ writeFileSync15(
5455
+ join27(contextDir, "pack-manifest.json"),
5207
5456
  JSON.stringify(selected.manifest, null, 2) + "\n"
5208
5457
  );
5209
5458
  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);
5210
5459
  const markdownFile = manifestEntry?.markdown ?? `${selected.selector.packType}${selected.selector.id ? `-${selected.selector.id}` : ""}-pack.md`;
5211
5460
  const jsonFile = manifestEntry?.json ?? `${selected.selector.packType}${selected.selector.id ? `-${selected.selector.id}` : ""}-pack.json`;
5212
- writeFileSync14(join28(contextDir, markdownFile), selected.pack.renderedMarkdown);
5213
- writeFileSync14(join28(contextDir, jsonFile), JSON.stringify(selected.pack, null, 2) + "\n");
5461
+ writeFileSync15(join27(contextDir, markdownFile), selected.pack.renderedMarkdown);
5462
+ writeFileSync15(join27(contextDir, jsonFile), JSON.stringify(selected.pack, null, 2) + "\n");
5214
5463
  writtenContextDir = contextDir;
5215
5464
  }
5216
5465
  if (jsonOutput) {
@@ -5235,20 +5484,20 @@ async function printHostedSelectedExecutionPack(packType, id, essencePath, names
5235
5484
  }
5236
5485
  async function printHostedExecutionPackManifest(essencePath, namespace, jsonOutput = false, writeContext = false) {
5237
5486
  const client = getPublicAPIClient();
5238
- const resolvedPath = essencePath ? resolveUserPath(essencePath) : join28(process.cwd(), "decantr.essence.json");
5239
- if (!existsSync27(resolvedPath)) {
5487
+ const resolvedPath = essencePath ? resolveUserPath(essencePath) : join27(process.cwd(), "decantr.essence.json");
5488
+ if (!existsSync26(resolvedPath)) {
5240
5489
  throw new Error(`Essence file not found at ${resolvedPath}`);
5241
5490
  }
5242
- const essence = JSON.parse(readFileSync20(resolvedPath, "utf-8"));
5491
+ const essence = JSON.parse(readFileSync19(resolvedPath, "utf-8"));
5243
5492
  const manifest = await client.getExecutionPackManifest(
5244
5493
  essence,
5245
5494
  namespace ? { namespace } : void 0
5246
5495
  );
5247
5496
  let writtenContextDir = null;
5248
5497
  if (writeContext) {
5249
- const contextDir = join28(process.cwd(), ".decantr", "context");
5250
- mkdirSync11(contextDir, { recursive: true });
5251
- writeFileSync14(join28(contextDir, "pack-manifest.json"), JSON.stringify(manifest, null, 2) + "\n");
5498
+ const contextDir = join27(process.cwd(), ".decantr", "context");
5499
+ mkdirSync12(contextDir, { recursive: true });
5500
+ writeFileSync15(join27(contextDir, "pack-manifest.json"), JSON.stringify(manifest, null, 2) + "\n");
5252
5501
  writtenContextDir = contextDir;
5253
5502
  }
5254
5503
  if (jsonOutput) {
@@ -5269,14 +5518,14 @@ async function printHostedExecutionPackManifest(essencePath, namespace, jsonOutp
5269
5518
  }
5270
5519
  }
5271
5520
  async function hydrateHostedExecutionPacksIfMissing(projectRoot, namespace = "@official") {
5272
- const contextDir = join28(projectRoot, ".decantr", "context");
5273
- const reviewPackPath = join28(contextDir, "review-pack.json");
5274
- const manifestPath = join28(contextDir, "pack-manifest.json");
5275
- if (existsSync27(reviewPackPath) && existsSync27(manifestPath)) {
5521
+ const contextDir = join27(projectRoot, ".decantr", "context");
5522
+ const reviewPackPath = join27(contextDir, "review-pack.json");
5523
+ const manifestPath = join27(contextDir, "pack-manifest.json");
5524
+ if (existsSync26(reviewPackPath) && existsSync26(manifestPath)) {
5276
5525
  return { attempted: false, hydrated: false };
5277
5526
  }
5278
- const essencePath = join28(projectRoot, "decantr.essence.json");
5279
- if (!existsSync27(essencePath)) {
5527
+ const essencePath = join27(projectRoot, "decantr.essence.json");
5528
+ if (!existsSync26(essencePath)) {
5280
5529
  return { attempted: false, hydrated: false };
5281
5530
  }
5282
5531
  const reviewHydration = await hydrateHostedReviewPackIfMissing(projectRoot, namespace);
@@ -5285,9 +5534,9 @@ async function hydrateHostedExecutionPacksIfMissing(projectRoot, namespace = "@o
5285
5534
  }
5286
5535
  try {
5287
5536
  const client = getPublicAPIClient();
5288
- const essence = JSON.parse(readFileSync20(essencePath, "utf-8"));
5537
+ const essence = JSON.parse(readFileSync19(essencePath, "utf-8"));
5289
5538
  const bundle = await client.compileExecutionPacks(essence, { namespace });
5290
- mkdirSync11(contextDir, { recursive: true });
5539
+ mkdirSync12(contextDir, { recursive: true });
5291
5540
  writeExecutionPackBundleArtifacts(contextDir, bundle);
5292
5541
  return { attempted: true, hydrated: true, scope: "bundle" };
5293
5542
  } catch {
@@ -5295,19 +5544,19 @@ async function hydrateHostedExecutionPacksIfMissing(projectRoot, namespace = "@o
5295
5544
  }
5296
5545
  }
5297
5546
  async function hydrateHostedReviewPackIfMissing(projectRoot, namespace = "@official") {
5298
- const contextDir = join28(projectRoot, ".decantr", "context");
5299
- const reviewPackPath = join28(contextDir, "review-pack.json");
5300
- const manifestPath = join28(contextDir, "pack-manifest.json");
5301
- if (existsSync27(reviewPackPath) && existsSync27(manifestPath)) {
5547
+ const contextDir = join27(projectRoot, ".decantr", "context");
5548
+ const reviewPackPath = join27(contextDir, "review-pack.json");
5549
+ const manifestPath = join27(contextDir, "pack-manifest.json");
5550
+ if (existsSync26(reviewPackPath) && existsSync26(manifestPath)) {
5302
5551
  return { attempted: false, hydrated: false };
5303
5552
  }
5304
- const essencePath = join28(projectRoot, "decantr.essence.json");
5305
- if (!existsSync27(essencePath)) {
5553
+ const essencePath = join27(projectRoot, "decantr.essence.json");
5554
+ if (!existsSync26(essencePath)) {
5306
5555
  return { attempted: false, hydrated: false };
5307
5556
  }
5308
5557
  try {
5309
5558
  const client = getPublicAPIClient();
5310
- const essence = JSON.parse(readFileSync20(essencePath, "utf-8"));
5559
+ const essence = JSON.parse(readFileSync19(essencePath, "utf-8"));
5311
5560
  const selected = await client.selectExecutionPack(
5312
5561
  {
5313
5562
  essence,
@@ -5315,14 +5564,14 @@ async function hydrateHostedReviewPackIfMissing(projectRoot, namespace = "@offic
5315
5564
  },
5316
5565
  { namespace }
5317
5566
  );
5318
- mkdirSync11(contextDir, { recursive: true });
5319
- writeFileSync14(join28(contextDir, "review-pack.md"), selected.pack.renderedMarkdown);
5320
- writeFileSync14(
5321
- join28(contextDir, "review-pack.json"),
5567
+ mkdirSync12(contextDir, { recursive: true });
5568
+ writeFileSync15(join27(contextDir, "review-pack.md"), selected.pack.renderedMarkdown);
5569
+ writeFileSync15(
5570
+ join27(contextDir, "review-pack.json"),
5322
5571
  JSON.stringify(selected.pack, null, 2) + "\n"
5323
5572
  );
5324
- if (!existsSync27(manifestPath)) {
5325
- writeFileSync14(manifestPath, JSON.stringify(selected.manifest, null, 2) + "\n");
5573
+ if (!existsSync26(manifestPath)) {
5574
+ writeFileSync15(manifestPath, JSON.stringify(selected.manifest, null, 2) + "\n");
5326
5575
  }
5327
5576
  return { attempted: true, hydrated: true, scope: "review" };
5328
5577
  } catch {
@@ -5332,17 +5581,17 @@ async function hydrateHostedReviewPackIfMissing(projectRoot, namespace = "@offic
5332
5581
  async function printHostedFileCritique(sourcePath, namespace, jsonOutput = false, essencePath, treatmentsPath) {
5333
5582
  const client = getPublicAPIClient();
5334
5583
  const resolvedSourcePath = resolveUserPath(sourcePath);
5335
- const resolvedEssencePath = essencePath ? resolveUserPath(essencePath) : join28(process.cwd(), "decantr.essence.json");
5336
- const resolvedTreatmentsPath = treatmentsPath ? resolveUserPath(treatmentsPath) : join28(process.cwd(), "src", "styles", "treatments.css");
5337
- if (!existsSync27(resolvedSourcePath)) {
5584
+ const resolvedEssencePath = essencePath ? resolveUserPath(essencePath) : join27(process.cwd(), "decantr.essence.json");
5585
+ const resolvedTreatmentsPath = treatmentsPath ? resolveUserPath(treatmentsPath) : join27(process.cwd(), "src", "styles", "treatments.css");
5586
+ if (!existsSync26(resolvedSourcePath)) {
5338
5587
  throw new Error(`Source file not found at ${resolvedSourcePath}`);
5339
5588
  }
5340
- if (!existsSync27(resolvedEssencePath)) {
5589
+ if (!existsSync26(resolvedEssencePath)) {
5341
5590
  throw new Error(`Essence file not found at ${resolvedEssencePath}`);
5342
5591
  }
5343
- const code = readFileSync20(resolvedSourcePath, "utf-8");
5344
- const essence = JSON.parse(readFileSync20(resolvedEssencePath, "utf-8"));
5345
- const treatmentsCss = existsSync27(resolvedTreatmentsPath) ? readFileSync20(resolvedTreatmentsPath, "utf-8") : void 0;
5592
+ const code = readFileSync19(resolvedSourcePath, "utf-8");
5593
+ const essence = JSON.parse(readFileSync19(resolvedEssencePath, "utf-8"));
5594
+ const treatmentsCss = existsSync26(resolvedTreatmentsPath) ? readFileSync19(resolvedTreatmentsPath, "utf-8") : void 0;
5346
5595
  const report = await client.critiqueFile(
5347
5596
  {
5348
5597
  essence,
@@ -5366,11 +5615,11 @@ async function printHostedFileCritique(sourcePath, namespace, jsonOutput = false
5366
5615
  }
5367
5616
  async function printHostedProjectAudit(namespace, jsonOutput = false, essencePath, distPath, sourcesPath) {
5368
5617
  const client = getPublicAPIClient();
5369
- const resolvedEssencePath = essencePath ? resolveUserPath(essencePath) : join28(process.cwd(), "decantr.essence.json");
5370
- if (!existsSync27(resolvedEssencePath)) {
5618
+ const resolvedEssencePath = essencePath ? resolveUserPath(essencePath) : join27(process.cwd(), "decantr.essence.json");
5619
+ if (!existsSync26(resolvedEssencePath)) {
5371
5620
  throw new Error(`Essence file not found at ${resolvedEssencePath}`);
5372
5621
  }
5373
- const essence = JSON.parse(readFileSync20(resolvedEssencePath, "utf-8"));
5622
+ const essence = JSON.parse(readFileSync19(resolvedEssencePath, "utf-8"));
5374
5623
  const dist = readHostedDistSnapshot(distPath);
5375
5624
  const sources = readHostedSourceSnapshot(sourcesPath);
5376
5625
  const report = await client.auditProject(
@@ -5388,7 +5637,7 @@ async function printHostedProjectAudit(namespace, jsonOutput = false, essencePat
5388
5637
  console.log(heading2("Hosted Project Audit"));
5389
5638
  console.log(` Essence: ${resolvedEssencePath}`);
5390
5639
  console.log(
5391
- ` Dist snapshot: ${dist ? distPath ? resolveUserPath(distPath) : join28(process.cwd(), "dist") : "none"}`
5640
+ ` Dist snapshot: ${dist ? distPath ? resolveUserPath(distPath) : join27(process.cwd(), "dist") : "none"}`
5392
5641
  );
5393
5642
  console.log(
5394
5643
  ` Source snapshot: ${sources && sourcesPath ? resolveUserPath(sourcesPath) : "none"}`
@@ -5469,7 +5718,7 @@ async function cmdGet(type, id) {
5469
5718
  }
5470
5719
  const apiType = CONTENT_TYPE_TO_API_CONTENT_TYPE3[type];
5471
5720
  const registryClient = new RegistryClient({
5472
- cacheDir: join28(process.cwd(), ".decantr", "cache")
5721
+ cacheDir: join27(process.cwd(), ".decantr", "cache")
5473
5722
  });
5474
5723
  const result = await registryClient.fetchContentItem(apiType, id);
5475
5724
  if (result) {
@@ -5478,16 +5727,16 @@ async function cmdGet(type, id) {
5478
5727
  }
5479
5728
  const currentDir = dirname4(fileURLToPath2(import.meta.url));
5480
5729
  const bundledCandidates = [
5481
- join28(currentDir, "bundled", apiType, `${id}.json`),
5730
+ join27(currentDir, "bundled", apiType, `${id}.json`),
5482
5731
  // Running from src/
5483
- join28(currentDir, "..", "src", "bundled", apiType, `${id}.json`),
5732
+ join27(currentDir, "..", "src", "bundled", apiType, `${id}.json`),
5484
5733
  // Running from dist/
5485
- join28(currentDir, "..", "bundled", apiType, `${id}.json`)
5734
+ join27(currentDir, "..", "bundled", apiType, `${id}.json`)
5486
5735
  // Alternative dist layout
5487
5736
  ];
5488
- const bundledPath = bundledCandidates.find((p) => existsSync27(p)) || null;
5737
+ const bundledPath = bundledCandidates.find((p) => existsSync26(p)) || null;
5489
5738
  if (bundledPath) {
5490
- const data = JSON.parse(readFileSync20(bundledPath, "utf-8"));
5739
+ const data = JSON.parse(readFileSync19(bundledPath, "utf-8"));
5491
5740
  console.log(JSON.stringify(data, null, 2));
5492
5741
  return;
5493
5742
  }
@@ -5496,10 +5745,10 @@ async function cmdGet(type, id) {
5496
5745
  return;
5497
5746
  }
5498
5747
  async function cmdValidate(path) {
5499
- const essencePath = path || join28(process.cwd(), "decantr.essence.json");
5748
+ const essencePath = path || join27(process.cwd(), "decantr.essence.json");
5500
5749
  let raw;
5501
5750
  try {
5502
- raw = readFileSync20(essencePath, "utf-8");
5751
+ raw = readFileSync19(essencePath, "utf-8");
5503
5752
  } catch {
5504
5753
  console.error(error3(`Could not read ${essencePath}`));
5505
5754
  process.exitCode = 1;
@@ -5513,7 +5762,7 @@ async function cmdValidate(path) {
5513
5762
  process.exitCode = 1;
5514
5763
  return;
5515
5764
  }
5516
- const detectedVersion = isV36(essence) ? "v3" : "v2";
5765
+ const detectedVersion = isV37(essence) ? "v3" : "v2";
5517
5766
  console.log(`${DIM13}Detected essence version: ${detectedVersion}${RESET13}`);
5518
5767
  const result = validateEssence2(essence);
5519
5768
  if (result.valid) {
@@ -5568,7 +5817,7 @@ async function cmdList(type, sort, recommended, intelligenceSource) {
5568
5817
  return;
5569
5818
  }
5570
5819
  const registryClient = new RegistryClient({
5571
- cacheDir: join28(process.cwd(), ".decantr", "cache")
5820
+ cacheDir: join27(process.cwd(), ".decantr", "cache")
5572
5821
  });
5573
5822
  const result = await registryClient.fetchContentList(
5574
5823
  type,
@@ -5614,6 +5863,176 @@ async function cmdList(type, sort, recommended, intelligenceSource) {
5614
5863
  }
5615
5864
  }
5616
5865
  }
5866
+ function readCliPackageVersion() {
5867
+ const here = dirname4(fileURLToPath2(import.meta.url));
5868
+ const candidates = [join27(here, "..", "package.json"), join27(here, "..", "..", "package.json")];
5869
+ for (const candidate of candidates) {
5870
+ try {
5871
+ const pkg = JSON.parse(readFileSync19(candidate, "utf-8"));
5872
+ if (pkg.version) return pkg.version;
5873
+ } catch {
5874
+ }
5875
+ }
5876
+ return "0.0.0";
5877
+ }
5878
+ function timestampForFile() {
5879
+ return (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
5880
+ }
5881
+ function backupExistingEssence(projectRoot, label) {
5882
+ const essencePath = join27(projectRoot, "decantr.essence.json");
5883
+ if (!existsSync26(essencePath)) return null;
5884
+ const backupPath = join27(projectRoot, `decantr.essence.${label}.${timestampForFile()}.backup.json`);
5885
+ writeFileSync15(backupPath, readFileSync19(essencePath, "utf-8"), "utf-8");
5886
+ return backupPath;
5887
+ }
5888
+ function writeBrownfieldProjectJson(input) {
5889
+ const decantrDir = join27(input.projectRoot, ".decantr");
5890
+ mkdirSync12(join27(decantrDir, "context"), { recursive: true });
5891
+ mkdirSync12(join27(decantrDir, "cache"), { recursive: true });
5892
+ const now = (/* @__PURE__ */ new Date()).toISOString();
5893
+ const projectJson = {
5894
+ detected: {
5895
+ framework: input.detected.framework,
5896
+ version: input.detected.version || null,
5897
+ packageManager: input.detected.packageManager,
5898
+ hasTypeScript: input.detected.hasTypeScript,
5899
+ hasTailwind: input.detected.hasTailwind,
5900
+ existingRuleFiles: input.detected.existingRuleFiles,
5901
+ workspaceRoot: input.workspaceInfo.workspaceRoot,
5902
+ appRoot: input.workspaceInfo.appRoot
5903
+ },
5904
+ sync: {
5905
+ status: "needs-sync",
5906
+ lastSync: now,
5907
+ registrySource: "cache",
5908
+ cachedContent: {
5909
+ archetypes: [],
5910
+ patterns: [],
5911
+ themes: []
5912
+ }
5913
+ },
5914
+ initialized: {
5915
+ at: now,
5916
+ via: "cli",
5917
+ version: readCliPackageVersion(),
5918
+ flags: `--existing --${input.mode === "replace" ? "replace-essence" : input.mode === "merge" ? "merge-proposal" : "accept-proposal"}`,
5919
+ workflowMode: "brownfield-attach",
5920
+ adoptionMode: "contract-only",
5921
+ contentSource: "none",
5922
+ assistantBridge: input.assistantBridge,
5923
+ projectScope: input.workspaceInfo.projectScope,
5924
+ adapterId: null,
5925
+ analysisArtifacts: true,
5926
+ acceptedProposal: {
5927
+ mode: input.mode,
5928
+ path: ".decantr/observed-essence.proposal.json"
5929
+ }
5930
+ }
5931
+ };
5932
+ writeFileSync15(join27(decantrDir, "project.json"), JSON.stringify(projectJson, null, 2) + "\n");
5933
+ }
5934
+ async function applyAcceptedBrownfieldProposal(input) {
5935
+ const proposal = readBrownfieldProposal(input.projectRoot);
5936
+ if (!proposal) {
5937
+ console.log(error3(`No observed brownfield proposal found at ${proposalPath(input.projectRoot)}.`));
5938
+ console.log(dim3("Run `decantr analyze` first, review `.decantr/brownfield-report.md`, then accept or merge the proposal."));
5939
+ process.exitCode = 1;
5940
+ return;
5941
+ }
5942
+ const essencePath = join27(input.projectRoot, "decantr.essence.json");
5943
+ const hasEssence = existsSync26(essencePath);
5944
+ let essence;
5945
+ let backupPath = null;
5946
+ if (input.mode === "accept" && hasEssence) {
5947
+ console.log(error3("Refusing to accept proposal over an existing decantr.essence.json."));
5948
+ console.log(dim3("Use `--merge-proposal` to preserve the existing contract or `--replace-essence` for an explicit destructive replacement."));
5949
+ process.exitCode = 1;
5950
+ return;
5951
+ }
5952
+ if (input.mode === "merge" && hasEssence) {
5953
+ const existing = JSON.parse(readFileSync19(essencePath, "utf-8"));
5954
+ if (!isV37(existing)) {
5955
+ console.log(error3("Existing essence is not v3. Run `decantr migrate` before merging a brownfield proposal."));
5956
+ process.exitCode = 1;
5957
+ return;
5958
+ }
5959
+ essence = mergeEssenceWithProposal(existing, proposal);
5960
+ } else {
5961
+ essence = proposal.essence;
5962
+ }
5963
+ const validation = validateEssence2(essence);
5964
+ if (!validation.valid) {
5965
+ console.log(error3("Brownfield proposal produced an invalid Decantr essence. No files were changed."));
5966
+ for (const validationError of validation.errors) {
5967
+ console.log(` ${RED11}${validationError}${RESET13}`);
5968
+ }
5969
+ process.exitCode = 1;
5970
+ return;
5971
+ }
5972
+ if (input.mode === "merge" && hasEssence) {
5973
+ backupPath = backupExistingEssence(input.projectRoot, "merge");
5974
+ } else if (input.mode === "replace" && hasEssence) {
5975
+ backupPath = backupExistingEssence(input.projectRoot, "replace");
5976
+ }
5977
+ writeBrownfieldProjectJson({
5978
+ projectRoot: input.projectRoot,
5979
+ detected: input.detected,
5980
+ workspaceInfo: input.workspaceInfo,
5981
+ assistantBridge: input.assistantBridge,
5982
+ mode: input.mode
5983
+ });
5984
+ writeFileSync15(essencePath, JSON.stringify(essence, null, 2) + "\n", "utf-8");
5985
+ const registryClient = new RegistryClient({
5986
+ cacheDir: join27(input.projectRoot, ".decantr", "cache"),
5987
+ offline: true,
5988
+ projectRoot: input.projectRoot
5989
+ });
5990
+ const refreshResult = await refreshDerivedFiles(input.projectRoot, essence, registryClient, void 0, {
5991
+ isInitialScaffold: true,
5992
+ workflowMode: "brownfield-attach",
5993
+ adoptionMode: "contract-only",
5994
+ analysisArtifacts: true
5995
+ });
5996
+ let assistantBridgePath = null;
5997
+ if (input.assistantBridge === "preview" || input.assistantBridge === "apply") {
5998
+ assistantBridgePath = writeAssistantBridgePreview({
5999
+ projectRoot: input.projectRoot,
6000
+ detected: input.detected,
6001
+ workflowMode: "brownfield-attach",
6002
+ assistantBridge: input.assistantBridge
6003
+ });
6004
+ }
6005
+ const appliedRuleFiles = input.assistantBridge === "apply" ? applyAssistantBridge(input.projectRoot, input.detected) : [];
6006
+ console.log(success3("\nBrownfield proposal accepted.\n"));
6007
+ console.log(" Files created/updated:");
6008
+ console.log(` ${cyan3("decantr.essence.json")} Observed brownfield contract`);
6009
+ console.log(` ${cyan3("DECANTR.md")} Reconciled assistant guidance`);
6010
+ console.log(` ${cyan3(".decantr/project.json")} Brownfield attach metadata`);
6011
+ console.log(` ${cyan3(".decantr/context/")} Generated contract context`);
6012
+ if (assistantBridgePath) {
6013
+ console.log(` ${cyan3(".decantr/context/assistant-bridge.md")} Assistant bridge preview`);
6014
+ }
6015
+ if (appliedRuleFiles.length > 0) {
6016
+ console.log(` ${dim3(`Rule bridge applied: ${appliedRuleFiles.join(", ")}`)}`);
6017
+ }
6018
+ if (backupPath) {
6019
+ console.log(` ${dim3(`Backup: ${backupPath}`)}`);
6020
+ }
6021
+ console.log("");
6022
+ console.log(" Generated context:");
6023
+ for (const contextFile of refreshResult.contextFiles.slice(0, 8)) {
6024
+ console.log(` ${dim3(contextFile.replace(`${input.projectRoot}/`, ""))}`);
6025
+ }
6026
+ if (refreshResult.contextFiles.length > 8) {
6027
+ console.log(` ${dim3(`(+${refreshResult.contextFiles.length - 8} more)`)}`);
6028
+ }
6029
+ console.log("");
6030
+ console.log(" Next steps:");
6031
+ console.log(` 1. Run ${cyan3("decantr check --brownfield")} to verify contract coverage`);
6032
+ console.log(` 2. Read ${cyan3(".decantr/brownfield-report.md")} for unresolved doctrine risks`);
6033
+ console.log(` 3. Use ${cyan3("decantr rules preview")} before mutating assistant rule files`);
6034
+ console.log("");
6035
+ }
5617
6036
  async function cmdInit(args) {
5618
6037
  const workspaceInfo = resolveWorkspaceInfo(process.cwd(), args.project);
5619
6038
  if (args.yes && workspaceInfo.requiresProjectSelection) {
@@ -5654,6 +6073,37 @@ async function cmdInit(args) {
5654
6073
  offline: args.offline,
5655
6074
  projectScope: workspaceInfo.projectScope
5656
6075
  });
6076
+ const proposalMode = args["replace-essence"] ? "replace" : args["merge-proposal"] ? "merge" : args["accept-proposal"] ? "accept" : null;
6077
+ if (proposalMode) {
6078
+ await applyAcceptedBrownfieldProposal({
6079
+ projectRoot,
6080
+ detected,
6081
+ workspaceInfo,
6082
+ mode: proposalMode,
6083
+ assistantBridge: policy.assistantBridge
6084
+ });
6085
+ return;
6086
+ }
6087
+ if (policy.workflowMode === "brownfield-attach" && detected.existingEssence) {
6088
+ console.log(error3("Refusing to overwrite existing decantr.essence.json in brownfield attach mode."));
6089
+ console.log(
6090
+ dim3(
6091
+ "Run `decantr analyze`, then use `decantr init --existing --merge-proposal` or the explicit destructive `--replace-essence`."
6092
+ )
6093
+ );
6094
+ process.exitCode = 1;
6095
+ return;
6096
+ }
6097
+ if (policy.workflowMode === "brownfield-attach" && readBrownfieldProposal(projectRoot)) {
6098
+ console.log(error3("Observed brownfield proposal found, but it was not accepted."));
6099
+ console.log(
6100
+ dim3(
6101
+ "Review `.decantr/brownfield-report.md`, then run `decantr init --existing --accept-proposal`."
6102
+ )
6103
+ );
6104
+ process.exitCode = 1;
6105
+ return;
6106
+ }
5657
6107
  const preferContractOnly = policy.contentSource === "none" && (policy.workflowMode === "brownfield-attach" || policy.workflowMode === "greenfield-contract-only");
5658
6108
  const shouldUseRegistry = !preferContractOnly || policy.registryRequired;
5659
6109
  let offlineSeed = {
@@ -5678,7 +6128,7 @@ async function cmdInit(args) {
5678
6128
  }
5679
6129
  }
5680
6130
  const registryClient = new RegistryClient({
5681
- cacheDir: join28(projectRoot, ".decantr", "cache"),
6131
+ cacheDir: join27(projectRoot, ".decantr", "cache"),
5682
6132
  apiUrl: args.registry,
5683
6133
  offline: args.offline,
5684
6134
  projectRoot
@@ -6005,7 +6455,7 @@ ${YELLOW9}You're offline. Scaffolding Decantr default.${RESET13}`);
6005
6455
  if (appliedRuleFiles.length > 0) {
6006
6456
  console.log(` ${dim3(`Rule bridge applied: ${appliedRuleFiles.join(", ")}`)}`);
6007
6457
  }
6008
- if (!existsSync27(join28(projectRoot, "package.json"))) {
6458
+ if (!existsSync26(join27(projectRoot, "package.json"))) {
6009
6459
  console.log("");
6010
6460
  console.log(
6011
6461
  dim3(` Note: ${cyan3("decantr init")} created Decantr contract/context files only.`)
@@ -6036,7 +6486,7 @@ ${YELLOW9}You're offline. Scaffolding Decantr default.${RESET13}`);
6036
6486
  console.log(` ${cyan3("decantr upgrade")} Update to latest patterns`);
6037
6487
  console.log(` ${cyan3("decantr check")} Detect drift issues`);
6038
6488
  console.log(` ${cyan3("decantr migrate")} Migrate v2 essence to v3`);
6039
- const essenceContent = readFileSync20(result.essencePath, "utf-8");
6489
+ const essenceContent = readFileSync19(result.essencePath, "utf-8");
6040
6490
  const essence = JSON.parse(essenceContent);
6041
6491
  if (essence.version !== "3.1.0") {
6042
6492
  const validation = validateEssence2(essence);
@@ -6047,7 +6497,7 @@ Validation warnings: ${validation.errors.join(", ")}`));
6047
6497
  }
6048
6498
  console.log("");
6049
6499
  let promptPages;
6050
- if (isV36(essence)) {
6500
+ if (isV37(essence)) {
6051
6501
  const allPages = essence.blueprint.sections ? essence.blueprint.sections.flatMap(
6052
6502
  (s) => s.pages.map((p) => ({ ...p, _sectionId: s.id, _shell: s.shell }))
6053
6503
  ) : essence.blueprint.pages || [];
@@ -6093,25 +6543,25 @@ Validation warnings: ${validation.errors.join(", ")}`));
6093
6543
  }
6094
6544
  async function cmdStatus() {
6095
6545
  const projectRoot = process.cwd();
6096
- const essencePath = join28(projectRoot, "decantr.essence.json");
6097
- const projectJsonPath = join28(projectRoot, ".decantr", "project.json");
6546
+ const essencePath = join27(projectRoot, "decantr.essence.json");
6547
+ const projectJsonPath = join27(projectRoot, ".decantr", "project.json");
6098
6548
  console.log(heading2("Decantr Project Status"));
6099
- if (!existsSync27(essencePath)) {
6549
+ if (!existsSync26(essencePath)) {
6100
6550
  console.log(`${RED11}No decantr.essence.json found.${RESET13}`);
6101
6551
  console.log(dim3('Run "decantr init" to create one.'));
6102
6552
  return;
6103
6553
  }
6104
6554
  try {
6105
- const essence = JSON.parse(readFileSync20(essencePath, "utf-8"));
6555
+ const essence = JSON.parse(readFileSync19(essencePath, "utf-8"));
6106
6556
  const validation = validateEssence2(essence);
6107
- const essenceVersion = isV36(essence) ? "v3" : "v2";
6557
+ const essenceVersion = isV37(essence) ? "v3" : "v2";
6108
6558
  console.log(`${BOLD6}Essence:${RESET13}`);
6109
6559
  if (validation.valid) {
6110
6560
  console.log(` ${GREEN13}Valid${RESET13} (${essenceVersion})`);
6111
6561
  } else {
6112
6562
  console.log(` ${RED11}Invalid: ${validation.errors.join(", ")}${RESET13}`);
6113
6563
  }
6114
- if (isV36(essence)) {
6564
+ if (isV37(essence)) {
6115
6565
  const v3 = essence;
6116
6566
  const sections = v3.blueprint.sections ?? [];
6117
6567
  const flatPages = sections.length > 0 ? sections.flatMap((section) => section.pages ?? []) : v3.blueprint.pages ?? [];
@@ -6159,9 +6609,9 @@ async function cmdStatus() {
6159
6609
  }
6160
6610
  console.log("");
6161
6611
  console.log(`${BOLD6}Sync Status:${RESET13}`);
6162
- if (existsSync27(projectJsonPath)) {
6612
+ if (existsSync26(projectJsonPath)) {
6163
6613
  try {
6164
- const projectJson = JSON.parse(readFileSync20(projectJsonPath, "utf-8"));
6614
+ const projectJson = JSON.parse(readFileSync19(projectJsonPath, "utf-8"));
6165
6615
  const syncStatus = projectJson.sync?.status || "unknown";
6166
6616
  const lastSync = projectJson.sync?.lastSync || "never";
6167
6617
  const source = projectJson.sync?.registrySource || "unknown";
@@ -6179,7 +6629,7 @@ async function cmdStatus() {
6179
6629
  }
6180
6630
  async function cmdSync() {
6181
6631
  const projectRoot = process.cwd();
6182
- const cacheDir = join28(projectRoot, ".decantr", "cache");
6632
+ const cacheDir = join27(projectRoot, ".decantr", "cache");
6183
6633
  console.log(heading2("Syncing registry content..."));
6184
6634
  const result = await syncRegistry(cacheDir);
6185
6635
  if (result.synced.length > 0) {
@@ -6374,14 +6824,14 @@ ${BOLD6}Examples:${RESET13}
6374
6824
  process.exitCode = 1;
6375
6825
  return;
6376
6826
  }
6377
- const themePath = join28(projectRoot, ".decantr", "custom", "themes", `${name}.json`);
6378
- if (!existsSync27(themePath)) {
6827
+ const themePath = join27(projectRoot, ".decantr", "custom", "themes", `${name}.json`);
6828
+ if (!existsSync26(themePath)) {
6379
6829
  console.error(error3(`Theme "${name}" not found at ${themePath}`));
6380
6830
  process.exitCode = 1;
6381
6831
  return;
6382
6832
  }
6383
6833
  try {
6384
- const theme = JSON.parse(readFileSync20(themePath, "utf-8"));
6834
+ const theme = JSON.parse(readFileSync19(themePath, "utf-8"));
6385
6835
  const result = validateCustomTheme(theme);
6386
6836
  if (result.valid) {
6387
6837
  console.log(success3(`Custom theme "${name}" is valid`));
@@ -6462,6 +6912,7 @@ ${BOLD6}Usage:${RESET13}
6462
6912
  decantr audit [file]
6463
6913
  decantr migrate
6464
6914
  decantr check
6915
+ decantr check --brownfield
6465
6916
  decantr sync-drift
6466
6917
  decantr search <query> [--type <type>] [--sort <recommended|recent|name>] [--recommended] [--source <authored|benchmark|hybrid>]
6467
6918
  decantr suggest <query> [--type <type>]
@@ -6473,6 +6924,7 @@ ${BOLD6}Usage:${RESET13}
6473
6924
  decantr registry get-pack <manifest|scaffold|review|section|page|mutation> [id] [--namespace <namespace>] [--json] [--essence <path>] [--write-context]
6474
6925
  decantr registry critique-file <file> [--namespace <namespace>] [--json] [--essence <path>] [--treatments <path>]
6475
6926
  decantr registry audit-project [--namespace <namespace>] [--json] [--essence <path>] [--dist <path>] [--sources <dir>]
6927
+ decantr rules preview [--project=<path>]
6476
6928
  decantr rules apply [--project=<path>]
6477
6929
  decantr validate [path]
6478
6930
  decantr theme <subcommand>
@@ -6488,13 +6940,16 @@ ${BOLD6}Init Options:${RESET13}
6488
6940
  --theme Theme ID
6489
6941
  --mode Color mode: dark | light | auto
6490
6942
  --shape Border shape: pill | rounded | sharp
6491
- --target Framework: react | vue | svelte | nextjs | html
6943
+ --target Framework: react | vue | svelte | angular | nextjs | nuxt | astro | html
6492
6944
  --guard Guard mode: creative | guided | strict
6493
6945
  --density Spacing: compact | comfortable | spacious
6494
6946
  --shell Default shell layout
6495
6947
  --workflow Workflow: greenfield | brownfield | hybrid
6496
6948
  --adoption Adoption: contract-only | style-bridge | decantr-css
6497
6949
  --assistant-bridge Assistant rules: none | preview | apply
6950
+ --accept-proposal Brownfield: accept observed proposal when no essence exists
6951
+ --merge-proposal Brownfield: merge observed proposal into an existing essence
6952
+ --replace-essence Brownfield: explicit destructive proposal replacement with backup
6498
6953
  --project App path inside a workspace/monorepo
6499
6954
  --existing Initialize in existing project
6500
6955
  --offline Force offline mode
@@ -6509,7 +6964,7 @@ ${BOLD6}Commands:${RESET13}
6509
6964
  ${cyan3("sync")} Sync registry content from API
6510
6965
  ${cyan3("audit")} Audit the project or critique a specific file against compiled packs
6511
6966
  ${cyan3("migrate")} Migrate v2 essence to v3 format (with .v2.backup.json backup)
6512
- ${cyan3("check")} Detect drift issues (validate + guard rules) [--telemetry]
6967
+ ${cyan3("check")} Detect drift issues (validate + guard rules) [--telemetry] [--brownfield]
6513
6968
  ${cyan3("sync-drift")} Review and resolve drift log entries
6514
6969
  ${cyan3("search")} Search the registry
6515
6970
  ${cyan3("suggest")} Suggest patterns or alternatives for a query
@@ -6533,16 +6988,19 @@ ${BOLD6}Examples:${RESET13}
6533
6988
  decantr new my-app --blueprint=carbon-ai-portal
6534
6989
  decantr magic "AI chatbot with dark cyber theme \u2014 bold and futuristic"
6535
6990
  decantr init
6536
- decantr init --existing --adoption=contract-only --yes
6991
+ decantr analyze
6992
+ decantr init --existing --accept-proposal
6993
+ decantr init --existing --merge-proposal
6537
6994
  decantr init --existing --adoption=style-bridge --assistant-bridge=preview
6538
6995
  decantr init --workflow=greenfield --adoption=contract-only
6539
6996
  decantr init --project=apps/web --yes
6997
+ decantr rules preview
6540
6998
  decantr rules apply
6541
6999
  decantr status
6542
7000
  decantr audit
6543
7001
  decantr audit src/pages/HomePage.tsx
6544
7002
  decantr migrate
6545
- decantr check
7003
+ decantr check --brownfield
6546
7004
  decantr sync-drift
6547
7005
  decantr search dashboard
6548
7006
  decantr suggest leaderboard
@@ -6562,7 +7020,7 @@ ${BOLD6}Examples:${RESET13}
6562
7020
  ${BOLD6}Workflow Model:${RESET13}
6563
7021
  ${cyan3("Greenfield blueprint")} decantr new my-app --blueprint=X --workflow=greenfield --adoption=decantr-css
6564
7022
  ${cyan3("Greenfield contract")} decantr init --workflow=greenfield --adoption=contract-only
6565
- ${cyan3("Brownfield adoption")} decantr analyze -> decantr init --existing --adoption=contract-only
7023
+ ${cyan3("Brownfield adoption")} decantr analyze -> decantr init --existing --accept-proposal -> decantr check --brownfield
6566
7024
  ${cyan3("Hybrid composition")} decantr add/remove, decantr theme switch, decantr registry, decantr upgrade
6567
7025
 
6568
7026
  ${BOLD6}Bootstrap adapters:${RESET13}
@@ -6570,6 +7028,24 @@ ${BOLD6}Bootstrap adapters:${RESET13}
6570
7028
  Unsupported targets resolve through ${cyan3("generic-web")} contract-only mode until their starter adapters land.
6571
7029
  `);
6572
7030
  }
7031
+ function cmdRulesHelp() {
7032
+ console.log(`
7033
+ ${BOLD6}decantr rules${RESET13} \u2014 Preview or apply assistant bridge snippets
7034
+
7035
+ ${BOLD6}Usage:${RESET13}
7036
+ decantr rules preview [--project=<path>]
7037
+ decantr rules apply [--project=<path>]
7038
+
7039
+ ${BOLD6}Subcommands:${RESET13}
7040
+ ${cyan3("preview")} Print target-specific Decantr bridge guidance without mutating rule files
7041
+ ${cyan3("apply")} Idempotently write Decantr bridge blocks to supported assistant rule files
7042
+
7043
+ ${BOLD6}Examples:${RESET13}
7044
+ decantr rules preview
7045
+ decantr rules preview --project=apps/web
7046
+ decantr rules apply --project=apps/web
7047
+ `);
7048
+ }
6573
7049
  async function main() {
6574
7050
  const args = process.argv.slice(2);
6575
7051
  const command = args[0];
@@ -6581,12 +7057,12 @@ async function main() {
6581
7057
  try {
6582
7058
  const here = dirname4(fileURLToPath2(import.meta.url));
6583
7059
  const candidates = [
6584
- join28(here, "..", "package.json"),
6585
- join28(here, "..", "..", "package.json")
7060
+ join27(here, "..", "package.json"),
7061
+ join27(here, "..", "..", "package.json")
6586
7062
  ];
6587
7063
  for (const candidate of candidates) {
6588
- if (existsSync27(candidate)) {
6589
- const pkg = JSON.parse(readFileSync20(candidate, "utf-8"));
7064
+ if (existsSync26(candidate)) {
7065
+ const pkg = JSON.parse(readFileSync19(candidate, "utf-8"));
6590
7066
  if (pkg.version) {
6591
7067
  console.log(pkg.version);
6592
7068
  return;
@@ -6650,6 +7126,12 @@ async function main() {
6650
7126
  initArgs.offline = true;
6651
7127
  } else if (arg === "--existing") {
6652
7128
  initArgs.existing = true;
7129
+ } else if (arg === "--accept-proposal") {
7130
+ initArgs["accept-proposal"] = true;
7131
+ } else if (arg === "--merge-proposal") {
7132
+ initArgs["merge-proposal"] = true;
7133
+ } else if (arg === "--replace-essence") {
7134
+ initArgs["replace-essence"] = true;
6653
7135
  } else if (arg.startsWith("--")) {
6654
7136
  const [key, value] = arg.slice(2).split("=");
6655
7137
  if (value) {
@@ -6675,7 +7157,7 @@ async function main() {
6675
7157
  break;
6676
7158
  }
6677
7159
  case "upgrade": {
6678
- const { cmdUpgrade } = await import("./upgrade-XMY6LIPS.js");
7160
+ const { cmdUpgrade } = await import("./upgrade-EV23CKA3.js");
6679
7161
  const applyFlag = args.includes("--apply");
6680
7162
  await cmdUpgrade(process.cwd(), { apply: applyFlag });
6681
7163
  break;
@@ -6687,9 +7169,10 @@ async function main() {
6687
7169
  `${YELLOW9}Note: \`decantr heal\` is deprecated. Use \`decantr check\` instead.${RESET13}`
6688
7170
  );
6689
7171
  }
6690
- const { cmdHeal } = await import("./heal-JPW3BXS3.js");
7172
+ const { cmdHeal } = await import("./heal-YHLXO5QL.js");
6691
7173
  const telemetryFlag = args.includes("--telemetry");
6692
- await cmdHeal(process.cwd(), { telemetry: telemetryFlag });
7174
+ const brownfieldFlag = args.includes("--brownfield");
7175
+ await cmdHeal(process.cwd(), { telemetry: telemetryFlag, brownfield: brownfieldFlag });
6693
7176
  break;
6694
7177
  }
6695
7178
  case "migrate": {
@@ -7087,8 +7570,12 @@ async function main() {
7087
7570
  }
7088
7571
  case "rules": {
7089
7572
  const subcommand = args[1];
7090
- if (subcommand !== "apply") {
7091
- console.error(error3("Usage: decantr rules apply [--project=<path>]"));
7573
+ if (!subcommand || subcommand === "--help" || subcommand === "-h" || subcommand === "help") {
7574
+ cmdRulesHelp();
7575
+ break;
7576
+ }
7577
+ if (subcommand !== "apply" && subcommand !== "preview") {
7578
+ console.error(error3("Usage: decantr rules <preview|apply> [--project=<path>]"));
7092
7579
  process.exitCode = 1;
7093
7580
  break;
7094
7581
  }
@@ -7102,6 +7589,16 @@ async function main() {
7102
7589
  }
7103
7590
  const workspaceInfo = resolveWorkspaceInfo(process.cwd(), projectArg);
7104
7591
  const detected = detectProject(workspaceInfo.appRoot);
7592
+ if (subcommand === "preview") {
7593
+ console.log(
7594
+ buildAssistantBridgeContent({
7595
+ detected,
7596
+ workflowMode: "brownfield-attach",
7597
+ assistantBridge: "preview"
7598
+ })
7599
+ );
7600
+ break;
7601
+ }
7105
7602
  const updated = applyAssistantBridge(workspaceInfo.appRoot, detected);
7106
7603
  if (updated.length === 0) {
7107
7604
  console.log(dim3("Assistant bridge rule files are already up to date."));