@decantr/cli 2.13.0 → 3.0.0-next.0

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.
@@ -22,14 +22,37 @@ import {
22
22
  listWorkspaceCandidates,
23
23
  listWorkspaceProjects,
24
24
  shouldFailWorkspaceHealth
25
- } from "./chunk-YBSBAJ3E.js";
25
+ } from "./chunk-GXC5IJQY.js";
26
26
  import {
27
+ acceptBrownfieldLocalLaw,
28
+ acceptStyleBridge,
29
+ buildGraphArtifacts,
30
+ changedFiles,
31
+ cmdGraph,
32
+ cmdGraphHelp,
33
+ createBrownfieldCodifyProposal,
34
+ createLocalLawTaskSummary,
27
35
  createProjectHealthReport,
36
+ createStyleBridgeProposal,
37
+ createStyleBridgeTaskSummary,
28
38
  formatProjectHealthMarkdown,
29
39
  formatProjectHealthText,
40
+ localPatternsPath,
41
+ localPatternsProposalPath,
42
+ localRulesPath,
43
+ localRulesProposalPath,
44
+ readLocalPatternPack,
30
45
  resolveWorkspaceInfo,
31
- shouldFailHealth
32
- } from "./chunk-3A2DLR47.js";
46
+ routeImpacts,
47
+ shouldFailHealth,
48
+ styleBridgeMatches,
49
+ styleBridgePath,
50
+ styleBridgeProposalPath,
51
+ validateLocalLaw,
52
+ writeBrownfieldCodifyProposal,
53
+ writeHostedPatternMappingProposal,
54
+ writeStyleBridgeProposal
55
+ } from "./chunk-WUIFEHWM.js";
33
56
  import {
34
57
  buildGuardRegistryContext,
35
58
  createDoctrineMap,
@@ -49,10 +72,16 @@ import {
49
72
  } from "./chunk-MNZKOZW7.js";
50
73
 
51
74
  // src/index.ts
52
- import { existsSync as existsSync30, mkdirSync as mkdirSync17, readdirSync as readdirSync9, readFileSync as readFileSync23, writeFileSync as writeFileSync20 } from "fs";
53
- import { basename as basename3, dirname as dirname6, isAbsolute as isAbsolute3, join as join32, relative as relative7, resolve as resolve4 } from "path";
75
+ import { existsSync as existsSync28, mkdirSync as mkdirSync15, readdirSync as readdirSync8, readFileSync as readFileSync21, writeFileSync as writeFileSync18 } from "fs";
76
+ import { basename as basename2, dirname as dirname6, isAbsolute as isAbsolute3, join as join30, relative as relative6, resolve as resolve4 } from "path";
54
77
  import { fileURLToPath as fileURLToPath3 } from "url";
55
- import { evaluateGuard, isV4 as isV49, validateEssence as validateEssence2 } from "@decantr/essence-spec";
78
+ import {
79
+ buildGraphImpactContext,
80
+ buildGraphRouteContext,
81
+ graphPayloadString,
82
+ summarizeGraphDiff
83
+ } from "@decantr/core";
84
+ import { evaluateGuard, isV4 as isV48, validateEssence as validateEssence2 } from "@decantr/essence-spec";
56
85
  import {
57
86
  CONTENT_TYPE_TO_API_CONTENT_TYPE as CONTENT_TYPE_TO_API_CONTENT_TYPE3,
58
87
  CONTENT_TYPES as GET_CONTENT_TYPES,
@@ -3235,1107 +3264,8 @@ ${YELLOW2}Next step:${RESET2} Review ${BOLD}${reportDisplayPath}${RESET2}, then
3235
3264
  }
3236
3265
 
3237
3266
  // src/commands/ci.ts
3238
- import { existsSync as existsSync15, mkdirSync as mkdirSync10, readFileSync as readFileSync13, writeFileSync as writeFileSync11 } from "fs";
3239
- import { dirname as dirname3, join as join17, relative as relative4, resolve } from "path";
3240
-
3241
- // src/local-law.ts
3242
- import { execFileSync } from "child_process";
3243
- import { existsSync as existsSync13, mkdirSync as mkdirSync8, readdirSync as readdirSync5, readFileSync as readFileSync11, statSync as statSync5, writeFileSync as writeFileSync9 } from "fs";
3244
- import { basename as basename2, extname, join as join15, relative as relative3, sep } from "path";
3245
- import { isV4 as isV43 } from "@decantr/essence-spec";
3246
- var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
3247
- ".astro",
3248
- ".html",
3249
- ".js",
3250
- ".jsx",
3251
- ".svelte",
3252
- ".ts",
3253
- ".tsx",
3254
- ".vue"
3255
- ]);
3256
- var UI_TEMPLATE_EXTENSIONS = /* @__PURE__ */ new Set([".astro", ".html", ".jsx", ".svelte", ".tsx", ".vue"]);
3257
- var IGNORED_DIRS = /* @__PURE__ */ new Set([
3258
- ".decantr",
3259
- ".git",
3260
- ".next",
3261
- ".nuxt",
3262
- ".svelte-kit",
3263
- "build",
3264
- "coverage",
3265
- "dist",
3266
- "node_modules",
3267
- "out"
3268
- ]);
3269
- var DEFAULT_RULE_EXTENSIONS = [".astro", ".html", ".jsx", ".svelte", ".tsx", ".vue"];
3270
- function localPatternsProposalPath(projectRoot) {
3271
- return join15(projectRoot, ".decantr", "local-patterns.proposal.json");
3272
- }
3273
- function localPatternsPath(projectRoot) {
3274
- return join15(projectRoot, ".decantr", "local-patterns.json");
3275
- }
3276
- function localRulesProposalPath(projectRoot) {
3277
- return join15(projectRoot, ".decantr", "rules.proposal.json");
3278
- }
3279
- function localRulesPath(projectRoot) {
3280
- return join15(projectRoot, ".decantr", "rules.json");
3281
- }
3282
- function readLocalPatternPack(projectRoot) {
3283
- return readJsonFile(localPatternsPath(projectRoot));
3284
- }
3285
- function readLocalRuleManifest(projectRoot) {
3286
- return readJsonFile(localRulesPath(projectRoot));
3287
- }
3288
- function createBrownfieldCodifyProposal(input) {
3289
- const sourceFiles = input.fromAudit ? listSourceFiles(input.projectRoot, 800) : [];
3290
- const evidence = summarizeSourceEvidence(input.projectRoot, sourceFiles);
3291
- const routes = input.essence && isV43(input.essence) ? Object.keys(input.essence.blueprint.routes ?? {}).sort() : [];
3292
- const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
3293
- const patternPack = {
3294
- version: 2,
3295
- generatedAt,
3296
- status: "proposal",
3297
- source: input.fromAudit ? "decantr codify --from-audit" : "decantr codify",
3298
- project: {
3299
- framework: input.detected.framework,
3300
- packageManager: input.detected.packageManager,
3301
- hasTailwind: input.detected.hasTailwind,
3302
- ruleFiles: input.detected.existingRuleFiles,
3303
- routeCount: routes.length
3304
- },
3305
- purpose: "Project-owned Brownfield/Hybrid UI law. Review and edit before accepting; Decantr treats this as authoritative only after it is copied to .decantr/local-patterns.json.",
3306
- hybrid: {
3307
- intent: "This local pattern pack is the first Hybrid authority layer: it maps Decantr concepts onto project-owned components, tokens, classes, and rules without replacing the app runtime.",
3308
- authorityPrecedence: [
3309
- "existing production source",
3310
- "accepted local patterns and rules",
3311
- "Decantr Essence V4 contract",
3312
- "hosted registry patterns and execution packs as optional guidance"
3313
- ],
3314
- hostedPatternMapping: "Use hosted patterns as vocabulary and review guidance. Before enforcing one, map it to a project-owned component path, class recipe, token recipe, or explicit exception here."
3315
- },
3316
- patterns: [
3317
- {
3318
- id: "button",
3319
- label: "Button primitives",
3320
- role: "Actions and command triggers",
3321
- appliesTo: [
3322
- "primary action",
3323
- "secondary action",
3324
- "tertiary action",
3325
- "destructive action",
3326
- "icon action"
3327
- ],
3328
- componentPaths: evidence.buttonComponents,
3329
- decide: "Define primary, secondary, tertiary, destructive, icon-only, disabled, and loading button variants from this app.",
3330
- classHints: evidence.buttonClassHints,
3331
- variants: evidence.buttonVariants,
3332
- sourceEvidence: evidence.buttonSourceEvidence,
3333
- confidence: confidenceForEvidence({
3334
- componentCount: evidence.buttonComponents.length,
3335
- classHintCount: evidence.buttonClassHints.length,
3336
- variantCount: evidence.buttonVariants.length,
3337
- sourceEvidenceCount: evidence.buttonSourceEvidence.length
3338
- }),
3339
- enforcement: {
3340
- level: "warn",
3341
- status: "proposal",
3342
- notes: [
3343
- "Treat as advisory until the team confirms variant names and wrapper paths.",
3344
- "Raise related rules to error only after raw button usage can be fixed without churn."
3345
- ]
3346
- },
3347
- evidence: evidence.buttonComponents.length ? evidence.buttonComponents : evidence.buttonClassHints.length ? evidence.buttonClassHints : [
3348
- "No obvious Button wrapper found yet. Add the project-owned wrapper path before strict enforcement."
3349
- ],
3350
- forbiddenAlternatives: ["New one-off button variants without updating this manifest."]
3351
- },
3352
- {
3353
- id: "surface-card",
3354
- label: "Cards and surfaces",
3355
- role: "Cards, panels, and reusable content surfaces",
3356
- appliesTo: ["cards", "panels", "modals", "list items", "dashboard tiles"],
3357
- componentPaths: evidence.cardComponents,
3358
- decide: "Define the canonical card background, border, radius, shadow, padding, density, and hover treatment.",
3359
- classHints: evidence.cardClassHints,
3360
- variants: evidence.cardVariants,
3361
- sourceEvidence: evidence.cardSourceEvidence,
3362
- confidence: confidenceForEvidence({
3363
- componentCount: evidence.cardComponents.length,
3364
- classHintCount: evidence.cardClassHints.length,
3365
- variantCount: evidence.cardVariants.length,
3366
- sourceEvidenceCount: evidence.cardSourceEvidence.length
3367
- }),
3368
- enforcement: {
3369
- level: "warn",
3370
- status: "proposal",
3371
- notes: [
3372
- "Use this family to converge repeated surface recipes before enforcing card rules.",
3373
- "Document route-specific card exceptions before asking CI to block them."
3374
- ]
3375
- },
3376
- evidence: evidence.cardComponents.length ? evidence.cardComponents : [
3377
- "No obvious Card wrapper found yet. Add the project-owned wrapper path or class recipe."
3378
- ],
3379
- forbiddenAlternatives: ["Flat ad hoc cards with unique color/radius/shadow recipes."]
3380
- },
3381
- {
3382
- id: "page-shell",
3383
- label: "Page shell and spacing",
3384
- role: "Route shell, navigation, gutters, max-width, and scroll ownership",
3385
- appliesTo: ["routes", "layouts", "navigation shells", "scroll containers"],
3386
- componentPaths: evidence.shellComponents,
3387
- decide: "Define which layout owns max width, gutters, sticky chrome, responsive breakpoints, and scroll containers.",
3388
- sourceEvidence: collectSourceEvidence(input.projectRoot, sourceFiles, [
3389
- "layout",
3390
- "shell",
3391
- "nav",
3392
- "sidebar",
3393
- "container"
3394
- ]),
3395
- confidence: confidenceForEvidence({
3396
- componentCount: evidence.shellComponents.length,
3397
- sourceEvidenceCount: evidence.shellComponents.length
3398
- }),
3399
- enforcement: {
3400
- level: "advisory",
3401
- status: "proposal",
3402
- notes: [
3403
- "Layout ownership is usually reviewed by humans first because route shells can legitimately differ."
3404
- ]
3405
- },
3406
- evidence: evidence.shellComponents.length ? evidence.shellComponents : ["Add root layout, shell, or app frame files that establish route chrome and spacing."],
3407
- forbiddenAlternatives: [
3408
- "Each page inventing independent max-width, padding, or sticky nav rules."
3409
- ]
3410
- },
3411
- {
3412
- id: "form-control",
3413
- label: "Form controls",
3414
- role: "Inputs, labels, validation, and form actions",
3415
- appliesTo: ["inputs", "selects", "textareas", "validation messages", "form actions"],
3416
- componentPaths: evidence.formComponents,
3417
- decide: "Define input height, label placement, error copy, disabled state, required state, and focus treatment.",
3418
- variants: evidence.formVariants,
3419
- sourceEvidence: evidence.formSourceEvidence,
3420
- confidence: confidenceForEvidence({
3421
- componentCount: evidence.formComponents.length,
3422
- variantCount: evidence.formVariants.length,
3423
- sourceEvidenceCount: evidence.formSourceEvidence.length
3424
- }),
3425
- enforcement: {
3426
- level: "warn",
3427
- status: "proposal",
3428
- notes: [
3429
- "Start by mapping field wrappers, labels, error states, and disabled states from source."
3430
- ]
3431
- },
3432
- evidence: evidence.formComponents.length ? evidence.formComponents : ["Add form field wrapper paths and validation examples."],
3433
- forbiddenAlternatives: [
3434
- "Unlabeled one-off inputs or validation states that do not match the app standard."
3435
- ]
3436
- },
3437
- {
3438
- id: "theme-variant",
3439
- label: "Theme variants",
3440
- role: "Light, dark, brand, density, and tenant/theme variants observed in the app",
3441
- appliesTo: ["theme toggles", "mode-specific classes", "brand variants", "tenant variants"],
3442
- componentPaths: evidence.themeComponents,
3443
- decide: "Document which theme variants exist, where they are toggled, and which tokens/classes are legal per variant.",
3444
- variants: evidence.themeVariants,
3445
- sourceEvidence: evidence.themeSourceEvidence,
3446
- confidence: confidenceForEvidence({
3447
- componentCount: evidence.themeComponents.length,
3448
- variantCount: evidence.themeVariants.length,
3449
- sourceEvidenceCount: evidence.themeSourceEvidence.length
3450
- }),
3451
- enforcement: {
3452
- level: "advisory",
3453
- status: "proposal",
3454
- notes: [
3455
- "Theme variant law should preserve the existing theme provider/tokens before adding any new mode."
3456
- ]
3457
- },
3458
- evidence: evidence.themeComponents.length ? evidence.themeComponents : ["If the app has dark/light or brand variants, add the toggles/providers here."],
3459
- forbiddenAlternatives: [
3460
- "Component-local theme forks that bypass shared theme providers or tokens."
3461
- ]
3462
- }
3463
- ],
3464
- starterRules: [
3465
- "Prefer project-owned wrappers for repeated primitives once they exist.",
3466
- "Avoid raw hex/rgb values in component templates unless documented as dynamic data.",
3467
- "Avoid static inline styles for reusable visual treatment.",
3468
- "When adding a new route, map it to an existing local pattern before inventing a new visual variant.",
3469
- "When adding a theme variant, update .decantr/theme-inventory.json and this local pattern pack.",
3470
- "Map hosted Decantr patterns into project-owned local law before making them enforceable."
3471
- ],
3472
- nextSteps: [
3473
- "Edit this proposal with real component paths and token/class recipes.",
3474
- "Run decantr codify --accept after review.",
3475
- "Use decantr task <route> before LLM edits so local law appears in task context.",
3476
- "Run decantr verify --brownfield --local-patterns after edits.",
3477
- "For Hybrid adoption, start with warn-level local rules and raise severities only after the team agrees the law is stable.",
3478
- "Wire deterministic project rules into ESLint, Biome, Storybook, visual tests, or CI where Decantr should not guess."
3479
- ]
3480
- };
3481
- const ruleManifest = {
3482
- version: 1,
3483
- status: "proposal",
3484
- generatedAt,
3485
- source: input.fromAudit ? "decantr codify --from-audit" : "decantr codify",
3486
- purpose: "Mechanical Brownfield/Hybrid checks owned by this project. These rules are intentionally local and stack-agnostic; edit before accepting.",
3487
- enforcement: {
3488
- defaultSeverity: "warn",
3489
- mode: "warn",
3490
- notes: [
3491
- "Decantr local rules are a guardrail, not a replacement for ESLint, Biome, type checks, tests, or visual regression.",
3492
- "Keep rules narrow enough that an LLM can fix findings without rewriting the app.",
3493
- "Use error severity only after the team agrees the rule is stable."
3494
- ]
3495
- },
3496
- rules: [
3497
- {
3498
- id: "no-inline-style",
3499
- type: "forbid-regex",
3500
- enabled: true,
3501
- severity: "warn",
3502
- description: "Reusable UI should not add static inline style attributes.",
3503
- includeExtensions: DEFAULT_RULE_EXTENSIONS,
3504
- pattern: "\\bstyle\\s*=",
3505
- message: "Inline style found in UI template.",
3506
- suggestedFix: "Move reusable visual treatment into the project style system, component wrapper, token, or documented local pattern.",
3507
- maxFindings: 25
3508
- },
3509
- {
3510
- id: "no-raw-color-literals",
3511
- type: "forbid-regex",
3512
- enabled: true,
3513
- severity: "warn",
3514
- description: "Component templates should not introduce raw hex/rgb color literals.",
3515
- includeExtensions: DEFAULT_RULE_EXTENSIONS,
3516
- pattern: "#(?:[0-9a-fA-F]{3,8})\\b|rgba?\\s*\\(",
3517
- message: "Raw color literal found in UI template.",
3518
- suggestedFix: "Use an existing project token/class, or document the exception in .decantr/local-patterns.json if the value is data-driven.",
3519
- maxFindings: 25
3520
- },
3521
- {
3522
- id: "prefer-button-wrapper",
3523
- type: "forbid-regex",
3524
- enabled: evidence.buttonComponents.length > 0,
3525
- severity: "info",
3526
- description: "Prefer the project-owned button primitive instead of new raw button markup.",
3527
- includeExtensions: DEFAULT_RULE_EXTENSIONS,
3528
- pattern: "<button[\\s>]",
3529
- message: "Raw <button> usage found outside the detected button wrapper.",
3530
- suggestedFix: "Use the project-owned Button primitive, or add this file to allowedPaths if it is the primitive implementation.",
3531
- allowedPaths: evidence.buttonComponents,
3532
- maxFindings: 50
3533
- }
3534
- ]
3535
- };
3536
- return { patternPack, ruleManifest };
3537
- }
3538
- function writeBrownfieldCodifyProposal(projectRoot, proposal) {
3539
- const decantrDir = join15(projectRoot, ".decantr");
3540
- mkdirSync8(decantrDir, { recursive: true });
3541
- const patternPath = localPatternsProposalPath(projectRoot);
3542
- const rulesPath = localRulesProposalPath(projectRoot);
3543
- writeFileSync9(patternPath, `${JSON.stringify(proposal.patternPack, null, 2)}
3544
- `, "utf-8");
3545
- writeFileSync9(rulesPath, `${JSON.stringify(proposal.ruleManifest, null, 2)}
3546
- `, "utf-8");
3547
- return { patternPath, rulesPath };
3548
- }
3549
- function writeHostedPatternMappingProposal(input) {
3550
- const decantrDir = join15(input.projectRoot, ".decantr");
3551
- mkdirSync8(decantrDir, { recursive: true });
3552
- const existing = readJsonFile(localPatternsProposalPath(input.projectRoot)) ?? readJsonFile(localPatternsPath(input.projectRoot));
3553
- const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
3554
- const patternPack = {
3555
- version: 2,
3556
- generatedAt,
3557
- status: "proposal",
3558
- source: "decantr codify --map-pattern",
3559
- purpose: existing?.purpose ?? "Project-owned Brownfield/Hybrid UI law. Review and edit before accepting; Decantr treats this as authoritative only after it is copied to .decantr/local-patterns.json.",
3560
- hybrid: existing?.hybrid ?? {
3561
- intent: "This local pattern pack maps Decantr concepts onto project-owned components, tokens, classes, and rules without replacing the app runtime.",
3562
- authorityPrecedence: [
3563
- "existing production source",
3564
- "accepted local patterns and rules",
3565
- "Decantr Essence V4 contract",
3566
- "hosted registry patterns and execution packs as optional guidance"
3567
- ],
3568
- hostedPatternMapping: "Hosted registry patterns are advisory until mapped to project-owned component paths, token recipes, class recipes, and explicit exceptions."
3569
- },
3570
- patterns: Array.isArray(existing?.patterns) ? [...existing.patterns] : [],
3571
- starterRules: Array.isArray(existing?.starterRules) ? existing.starterRules : [],
3572
- nextSteps: Array.isArray(existing?.nextSteps) ? existing.nextSteps : [
3573
- "Review this proposal with the team.",
3574
- "Add project-owned component paths, token hints, class recipes, and exceptions.",
3575
- "Run decantr codify --accept after review.",
3576
- "Use decantr task <route> before LLM edits so local law appears in task context."
3577
- ]
3578
- };
3579
- const slug = input.hostedPattern.slug.trim();
3580
- const existingIds = new Set(
3581
- (patternPack.patterns ?? []).map((pattern) => typeof pattern.id === "string" ? pattern.id : "").filter(Boolean)
3582
- );
3583
- const localPatternId = existingIds.has(slug) ? `${slug}-registry-map` : slug;
3584
- const mappedPattern = {
3585
- id: localPatternId,
3586
- label: input.hostedPattern.name ? `${input.hostedPattern.name} registry mapping` : `${slug} registry mapping`,
3587
- role: "Hosted registry guidance mapped into project-owned Hybrid law",
3588
- appliesTo: [
3589
- ...input.hostedPattern.components ?? [],
3590
- ...input.hostedPattern.interactions ?? [],
3591
- ...input.hostedPattern.tags ?? []
3592
- ].slice(0, 24),
3593
- componentPaths: [],
3594
- tokenHints: [],
3595
- classHints: [],
3596
- decide: `Map hosted pattern "${slug}" into this app's existing components, tokens, classes, and exceptions before treating it as enforceable.`,
3597
- hostedPatternRefs: [input.hostedPattern],
3598
- confidence: {
3599
- tier: "low",
3600
- score: 0.25,
3601
- rationale: [
3602
- "A hosted registry pattern was selected intentionally.",
3603
- "No project-owned component path or token recipe has been mapped yet."
3604
- ]
3605
- },
3606
- enforcement: {
3607
- level: "advisory",
3608
- status: "needs-mapping",
3609
- notes: [
3610
- "This proposal does not change source files.",
3611
- "Do not enforce this hosted pattern until project-owned implementation details are filled in.",
3612
- "Use accepted local rules, ESLint, Biome, Storybook, or visual regression for deterministic blocking checks."
3613
- ]
3614
- },
3615
- evidence: [
3616
- `Hosted pattern ${input.hostedPattern.source}/${slug}${input.hostedPattern.description ? `: ${input.hostedPattern.description}` : ""}`,
3617
- input.hostedPattern.visualBrief ? `Visual brief: ${input.hostedPattern.visualBrief}` : "Visual brief not provided by the hosted pattern."
3618
- ],
3619
- evidenceToCollect: [
3620
- "Project-owned component path(s) that implement this pattern.",
3621
- "Allowed token/class recipe(s) for this app.",
3622
- "Variant names and states that are legal in this project.",
3623
- "Explicit exceptions where this hosted pattern should not apply."
3624
- ],
3625
- forbiddenAlternatives: [
3626
- "Replacing existing app components solely because a hosted pattern exists.",
3627
- "Treating hosted registry guidance as enforceable without accepted local law."
3628
- ]
3629
- };
3630
- const index = (patternPack.patterns ?? []).findIndex((pattern) => pattern.id === localPatternId);
3631
- const replacedExisting = index >= 0;
3632
- if (replacedExisting) {
3633
- patternPack.patterns[index] = mappedPattern;
3634
- } else {
3635
- patternPack.patterns = [...patternPack.patterns ?? [], mappedPattern];
3636
- }
3637
- patternPack.nextSteps = [
3638
- .../* @__PURE__ */ new Set([
3639
- ...patternPack.nextSteps ?? [],
3640
- "Fill the hosted mapping with project-owned component paths, token/class recipes, variants, and exceptions.",
3641
- "Run decantr codify --accept only after the mapping reflects the existing app.",
3642
- "Use decantr verify --brownfield --local-patterns after edits to keep mapped local law visible."
3643
- ])
3644
- ];
3645
- const patternPath = localPatternsProposalPath(input.projectRoot);
3646
- writeFileSync9(patternPath, `${JSON.stringify(patternPack, null, 2)}
3647
- `, "utf-8");
3648
- return { patternPath, localPatternId, replacedExisting };
3649
- }
3650
- function acceptBrownfieldLocalLaw(projectRoot) {
3651
- const patternProposal = readJsonFile(localPatternsProposalPath(projectRoot));
3652
- const ruleProposal = readJsonFile(localRulesProposalPath(projectRoot));
3653
- const acceptedAt = (/* @__PURE__ */ new Date()).toISOString();
3654
- let patternAcceptedPath = null;
3655
- let rulesAcceptedPath = null;
3656
- if (patternProposal) {
3657
- patternProposal.status = "accepted";
3658
- patternProposal.acceptedAt = acceptedAt;
3659
- patternAcceptedPath = localPatternsPath(projectRoot);
3660
- writeFileSync9(patternAcceptedPath, `${JSON.stringify(patternProposal, null, 2)}
3661
- `, "utf-8");
3662
- }
3663
- if (ruleProposal) {
3664
- ruleProposal.status = "accepted";
3665
- ruleProposal.acceptedAt = acceptedAt;
3666
- rulesAcceptedPath = localRulesPath(projectRoot);
3667
- writeFileSync9(rulesAcceptedPath, `${JSON.stringify(ruleProposal, null, 2)}
3668
- `, "utf-8");
3669
- }
3670
- return { patternAcceptedPath, rulesAcceptedPath };
3671
- }
3672
- function validateLocalLaw(projectRoot) {
3673
- const patternsPath = localPatternsPath(projectRoot);
3674
- const rulesPath = localRulesPath(projectRoot);
3675
- const patternPack = readJsonFile(patternsPath);
3676
- const ruleManifest = readJsonFile(rulesPath);
3677
- const warnings = [];
3678
- if (patternPack) {
3679
- const patternIds = /* @__PURE__ */ new Set();
3680
- const patterns = Array.isArray(patternPack.patterns) ? patternPack.patterns : [];
3681
- if (patterns.length === 0) {
3682
- warnings.push(".decantr/local-patterns.json has no patterns.");
3683
- }
3684
- for (const pattern of patterns) {
3685
- const id = typeof pattern.id === "string" ? pattern.id.trim() : "";
3686
- if (!id) warnings.push("A local pattern is missing an id.");
3687
- if (id && patternIds.has(id)) warnings.push(`Duplicate local pattern id: ${id}`);
3688
- if (id) patternIds.add(id);
3689
- const paths = Array.isArray(pattern.componentPaths) ? pattern.componentPaths : [];
3690
- const evidence = Array.isArray(pattern.evidence) ? pattern.evidence : [];
3691
- const todoEvidence = Array.isArray(pattern.evidenceToCollect) ? pattern.evidenceToCollect : [];
3692
- const hostedRefs = Array.isArray(pattern.hostedPatternRefs) ? pattern.hostedPatternRefs : [];
3693
- if (id && paths.length === 0 && evidence.length === 0 && todoEvidence.length > 0) {
3694
- warnings.push(
3695
- `Local pattern ${id} still reads like a TODO; add concrete component paths or evidence.`
3696
- );
3697
- }
3698
- if (id && hostedRefs.length > 0 && paths.length === 0) {
3699
- warnings.push(
3700
- `Local pattern ${id} maps hosted registry guidance but has no project-owned component path yet.`
3701
- );
3702
- }
3703
- }
3704
- }
3705
- if (ruleManifest && !Array.isArray(ruleManifest.rules)) {
3706
- warnings.push(".decantr/rules.json has no rules array.");
3707
- }
3708
- const findings = ruleManifest ? scanLocalRules(projectRoot, ruleManifest) : [];
3709
- return {
3710
- patternsPath,
3711
- rulesPath,
3712
- patternPackPresent: Boolean(patternPack),
3713
- ruleManifestPresent: Boolean(ruleManifest),
3714
- warnings,
3715
- findings
3716
- };
3717
- }
3718
- function createLocalLawTaskSummary(projectRoot) {
3719
- const patternPack = readLocalPatternPack(projectRoot);
3720
- const ruleManifest = readLocalRuleManifest(projectRoot);
3721
- const patterns = (patternPack?.patterns ?? []).map((pattern) => ({
3722
- id: typeof pattern.id === "string" ? pattern.id : "unknown",
3723
- role: typeof pattern.role === "string" ? pattern.role : null,
3724
- componentPaths: Array.isArray(pattern.componentPaths) ? pattern.componentPaths.filter((entry) => typeof entry === "string") : [],
3725
- confidenceTier: typeof pattern.confidence === "object" && pattern.confidence !== null && typeof pattern.confidence.tier === "string" ? pattern.confidence.tier : null,
3726
- enforcementLevel: typeof pattern.enforcement === "object" && pattern.enforcement !== null && typeof pattern.enforcement.level === "string" ? pattern.enforcement.level : null,
3727
- hostedPatternRefs: Array.isArray(pattern.hostedPatternRefs) ? pattern.hostedPatternRefs.map((ref) => typeof ref?.slug === "string" ? ref.slug : null).filter((slug) => Boolean(slug)) : []
3728
- }));
3729
- const rules = (ruleManifest?.rules ?? []).map((rule) => ({
3730
- id: rule.id,
3731
- severity: rule.severity,
3732
- enabled: rule.enabled,
3733
- description: rule.description
3734
- }));
3735
- return {
3736
- patternsPath: patternPack ? ".decantr/local-patterns.json" : null,
3737
- rulesPath: ruleManifest ? ".decantr/rules.json" : null,
3738
- patternCount: patterns.length,
3739
- ruleCount: rules.length,
3740
- patterns,
3741
- rules
3742
- };
3743
- }
3744
- function changedFiles(projectRoot, since) {
3745
- const changed = /* @__PURE__ */ new Set();
3746
- try {
3747
- const commands = since ? [
3748
- ["diff", "--name-only", since, "--"],
3749
- ["diff", "--name-only", "--cached"]
3750
- ] : [
3751
- ["diff", "--name-only"],
3752
- ["diff", "--name-only", "--cached"]
3753
- ];
3754
- for (const args of commands) {
3755
- const output = execFileSync("git", args, {
3756
- cwd: projectRoot,
3757
- encoding: "utf-8",
3758
- stdio: ["ignore", "pipe", "ignore"]
3759
- });
3760
- for (const line of output.split(/\r?\n/)) {
3761
- const file = line.trim();
3762
- if (file) changed.add(normalizePath(file));
3763
- }
3764
- }
3765
- } catch {
3766
- }
3767
- return [...changed].sort();
3768
- }
3769
- function routeImpacts(projectRoot, files) {
3770
- const analysis = readJsonFile(
3771
- join15(projectRoot, ".decantr", "analysis.json")
3772
- );
3773
- const routeEntries = analysis?.routes?.routes ?? [];
3774
- const impacted = /* @__PURE__ */ new Set();
3775
- for (const file of files) {
3776
- for (const route of routeEntries) {
3777
- if (route.file && pathMatches(file, route.file)) {
3778
- if (route.path) impacted.add(route.path);
3779
- }
3780
- }
3781
- }
3782
- return [...impacted].sort();
3783
- }
3784
- function scanLocalRules(projectRoot, manifest) {
3785
- const findings = [];
3786
- const files = listSourceFiles(projectRoot, 1200);
3787
- for (const rule of manifest.rules ?? []) {
3788
- if (!rule.enabled || rule.type !== "forbid-regex") continue;
3789
- const extensions = new Set(
3790
- rule.includeExtensions?.length ? rule.includeExtensions : DEFAULT_RULE_EXTENSIONS
3791
- );
3792
- let regex;
3793
- try {
3794
- regex = new RegExp(rule.pattern, "g");
3795
- } catch {
3796
- findings.push({
3797
- ruleId: rule.id,
3798
- severity: "error",
3799
- file: ".decantr/rules.json",
3800
- line: 1,
3801
- column: 1,
3802
- excerpt: rule.pattern,
3803
- message: `Invalid regex for local rule ${rule.id}.`,
3804
- suggestedFix: "Edit .decantr/rules.json so the pattern is a valid JavaScript regular expression."
3805
- });
3806
- continue;
3807
- }
3808
- let ruleFindingCount = 0;
3809
- for (const file of files) {
3810
- if (!extensions.has(extname(file.absolute))) continue;
3811
- if (pathAllowed(file.relative, rule.allowedPaths ?? [])) continue;
3812
- const contents = readFileSync11(file.absolute, "utf-8");
3813
- for (const match of contents.matchAll(regex)) {
3814
- const index = match.index ?? 0;
3815
- const position = lineColumnAt(contents, index);
3816
- findings.push({
3817
- ruleId: rule.id,
3818
- severity: rule.severity,
3819
- file: file.relative,
3820
- line: position.line,
3821
- column: position.column,
3822
- excerpt: lineAt(contents, position.line).trim().slice(0, 180),
3823
- message: rule.message,
3824
- suggestedFix: rule.suggestedFix
3825
- });
3826
- ruleFindingCount += 1;
3827
- if (rule.maxFindings && ruleFindingCount >= rule.maxFindings) break;
3828
- }
3829
- if (rule.maxFindings && ruleFindingCount >= rule.maxFindings) break;
3830
- }
3831
- }
3832
- return findings;
3833
- }
3834
- function summarizeSourceEvidence(projectRoot, files) {
3835
- const componentPaths = files.filter((file) => /(^|[/\\])components?([/\\]|$)|(^|[/\\])ui([/\\]|$)/i.test(file.relative)).map((file) => file.relative);
3836
- const byName = (terms) => componentPaths.filter((file) => terms.some((term) => basename2(file).toLowerCase().includes(term))).slice(0, 12);
3837
- const themeComponents = componentPaths.filter((file) => /theme|provider|mode|appearance|tenant|brand/i.test(file)).slice(0, 12);
3838
- const shellComponents = files.filter((file) => /layout|shell|frame|app|root|nav|sidebar/i.test(basename2(file.relative))).map((file) => file.relative).slice(0, 12);
3839
- return {
3840
- buttonComponents: byName(["button", "action"]),
3841
- cardComponents: byName(["card", "panel", "surface", "tile"]),
3842
- formComponents: byName(["input", "field", "form", "select", "textarea"]),
3843
- shellComponents,
3844
- themeComponents,
3845
- buttonVariants: detectVariants(projectRoot, files, {
3846
- family: "button",
3847
- terms: ["primary", "secondary", "tertiary", "ghost", "link", "destructive", "icon"]
3848
- }),
3849
- cardVariants: detectVariants(projectRoot, files, {
3850
- family: "surface",
3851
- terms: ["card", "panel", "surface", "tile", "elevated", "interactive", "glass"]
3852
- }),
3853
- formVariants: detectVariants(projectRoot, files, {
3854
- family: "form",
3855
- terms: ["error", "invalid", "disabled", "required", "success", "helper"]
3856
- }),
3857
- themeVariants: detectVariants(projectRoot, files, {
3858
- family: "theme",
3859
- terms: ["dark", "light", "brand", "tenant", "theme", "mode", "density"]
3860
- }),
3861
- buttonSourceEvidence: collectSourceEvidence(projectRoot, files, [
3862
- "button",
3863
- "primary",
3864
- "secondary",
3865
- "tertiary",
3866
- "destructive"
3867
- ]),
3868
- cardSourceEvidence: collectSourceEvidence(projectRoot, files, [
3869
- "card",
3870
- "panel",
3871
- "surface",
3872
- "rounded",
3873
- "shadow"
3874
- ]),
3875
- formSourceEvidence: collectSourceEvidence(projectRoot, files, [
3876
- "input",
3877
- "label",
3878
- "error",
3879
- "field",
3880
- "select"
3881
- ]),
3882
- themeSourceEvidence: collectSourceEvidence(projectRoot, files, [
3883
- "theme",
3884
- "dark",
3885
- "light",
3886
- "brand",
3887
- "tenant"
3888
- ]),
3889
- buttonClassHints: collectClassHints(projectRoot, files, [
3890
- "button",
3891
- "btn",
3892
- "action",
3893
- "primary",
3894
- "secondary",
3895
- "tertiary",
3896
- "ghost",
3897
- "link"
3898
- ]),
3899
- cardClassHints: collectClassHints(projectRoot, files, ["card", "panel", "surface", "tile"])
3900
- };
3901
- }
3902
- function detectVariants(projectRoot, files, input) {
3903
- const variants = /* @__PURE__ */ new Map();
3904
- const termPattern = new RegExp(
3905
- `\\b(${input.terms.map(escapeRegExp).join("|")})(?:[-_:][a-z0-9-]+)?\\b`,
3906
- "gi"
3907
- );
3908
- for (const file of files) {
3909
- if (!UI_TEMPLATE_EXTENSIONS.has(extname(file.absolute))) continue;
3910
- const content = readFileSync11(join15(projectRoot, file.relative), "utf-8");
3911
- for (const match of content.matchAll(termPattern)) {
3912
- const raw = (match[0] ?? "").toLowerCase();
3913
- const id = raw.replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "");
3914
- if (!id || id.length < 3) continue;
3915
- const position = lineColumnAt(content, match.index ?? 0);
3916
- const line = lineAt(content, position.line).trim().slice(0, 160);
3917
- const entry = variants.get(id) ?? { evidence: /* @__PURE__ */ new Set(), count: 0 };
3918
- entry.count += 1;
3919
- entry.evidence.add(`${file.relative}:${position.line} ${line}`);
3920
- variants.set(id, entry);
3921
- }
3922
- }
3923
- return [...variants.entries()].sort((a, b) => b[1].count - a[1].count || a[0].localeCompare(b[0])).slice(0, 8).map(([id, value]) => ({
3924
- id,
3925
- label: `${input.family} ${id}`,
3926
- evidence: [...value.evidence].slice(0, 4),
3927
- confidence: value.count >= 4 ? "high" : value.count >= 2 ? "medium" : "low"
3928
- }));
3929
- }
3930
- function collectSourceEvidence(projectRoot, files, terms) {
3931
- const results = [];
3932
- const termPattern = new RegExp(terms.map(escapeRegExp).join("|"), "i");
3933
- for (const file of files) {
3934
- if (!UI_TEMPLATE_EXTENSIONS.has(extname(file.absolute))) continue;
3935
- const content = readFileSync11(join15(projectRoot, file.relative), "utf-8");
3936
- const lines = content.split(/\r?\n/);
3937
- for (let index = 0; index < lines.length; index += 1) {
3938
- const line = lines[index];
3939
- if (!termPattern.test(line)) continue;
3940
- const signals = terms.filter((term) => line.toLowerCase().includes(term.toLowerCase()));
3941
- results.push({
3942
- file: file.relative,
3943
- line: index + 1,
3944
- excerpt: line.trim().slice(0, 180),
3945
- signals: signals.slice(0, 6)
3946
- });
3947
- if (results.length >= 16) return results;
3948
- break;
3949
- }
3950
- }
3951
- return results;
3952
- }
3953
- function confidenceForEvidence(input) {
3954
- const score = Math.min(
3955
- 1,
3956
- input.componentCount * 0.28 + (input.classHintCount ?? 0) * 0.08 + (input.variantCount ?? 0) * 0.08 + (input.sourceEvidenceCount ?? 0) * 0.05
3957
- );
3958
- const tier = score >= 0.7 ? "high" : score >= 0.4 ? "medium" : "low";
3959
- const rationale = [
3960
- input.componentCount > 0 ? `${input.componentCount} likely component path(s) detected` : "no likely component wrapper detected yet",
3961
- (input.classHintCount ?? 0) > 0 ? `${input.classHintCount} class recipe hint(s) detected` : "no strong class recipe hints detected yet",
3962
- (input.variantCount ?? 0) > 0 ? `${input.variantCount} possible variant signal(s) detected` : "variant names still need team confirmation"
3963
- ];
3964
- return { tier, score: Number(score.toFixed(2)), rationale };
3965
- }
3966
- function collectClassHints(projectRoot, files, terms) {
3967
- const hints = /* @__PURE__ */ new Map();
3968
- const wantsButton = terms.some(
3969
- (term) => /^(button|btn|action|primary|secondary|tertiary|ghost|link)$/i.test(term)
3970
- );
3971
- const wantsSurface = terms.some((term) => /^(card|panel|surface|tile)$/i.test(term));
3972
- const buttonSignal = /button|btn|action|primary|secondary|tertiary|ghost|link|destructive|icon/i;
3973
- const surfaceSignal = /card|panel|surface|tile|rounded|shadow|border|bg-|p-\d|px-|py-/i;
3974
- for (const file of files) {
3975
- if (!UI_TEMPLATE_EXTENSIONS.has(extname(file.absolute))) continue;
3976
- const content = readFileSync11(join15(projectRoot, file.relative), "utf-8");
3977
- if (!terms.some((term) => content.toLowerCase().includes(term))) continue;
3978
- if (wantsButton) {
3979
- const openingTags = content.matchAll(
3980
- /<([A-Za-z][\w.:/-]*)\b[^>]*\bclass(?:Name)?\s*=\s*["'`]([^"'`]+)["'`][^>]*>/g
3981
- );
3982
- for (const match of openingTags) {
3983
- const tag = match[1];
3984
- const value = match[2].trim();
3985
- const tagLooksInteractive = /^(button|a|Link)$/i.test(tag) || /(^|\.)(Button|IconButton|LinkButton|Action)$/i.test(tag);
3986
- const fileLooksInteractive = /button|action|link/i.test(basename2(file.relative));
3987
- if (!tagLooksInteractive && !fileLooksInteractive) continue;
3988
- if (!buttonSignal.test(value) && !fileLooksInteractive) continue;
3989
- hints.set(value, (hints.get(value) ?? 0) + 1);
3990
- }
3991
- continue;
3992
- }
3993
- const matches = content.matchAll(/\bclass(?:Name)?\s*=\s*["'`]([^"'`]+)["'`]/g);
3994
- for (const match of matches) {
3995
- const value = match[1].trim();
3996
- const keep = wantsSurface && surfaceSignal.test(value) || !wantsButton && !wantsSurface && (buttonSignal.test(value) || surfaceSignal.test(value));
3997
- if (!keep) continue;
3998
- hints.set(value, (hints.get(value) ?? 0) + 1);
3999
- }
4000
- }
4001
- return [...hints.entries()].sort((a, b) => b[1] - a[1]).slice(0, 8).map(([hint]) => hint);
4002
- }
4003
- function listSourceFiles(projectRoot, maxFiles) {
4004
- const files = [];
4005
- const visit = (dir) => {
4006
- if (files.length >= maxFiles) return;
4007
- let entries;
4008
- try {
4009
- entries = readdirSync5(dir);
4010
- } catch {
4011
- return;
4012
- }
4013
- for (const entry of entries) {
4014
- if (files.length >= maxFiles) return;
4015
- if (IGNORED_DIRS.has(entry)) continue;
4016
- const absolute = join15(dir, entry);
4017
- let stat;
4018
- try {
4019
- stat = statSync5(absolute);
4020
- } catch {
4021
- continue;
4022
- }
4023
- if (stat.isDirectory()) {
4024
- visit(absolute);
4025
- } else if (stat.isFile() && SOURCE_EXTENSIONS.has(extname(entry))) {
4026
- files.push({ absolute, relative: normalizePath(relative3(projectRoot, absolute)) });
4027
- }
4028
- }
4029
- };
4030
- visit(projectRoot);
4031
- return files.sort((a, b) => a.relative.localeCompare(b.relative));
4032
- }
4033
- function readJsonFile(path) {
4034
- if (!existsSync13(path)) return null;
4035
- try {
4036
- return JSON.parse(readFileSync11(path, "utf-8"));
4037
- } catch {
4038
- return null;
4039
- }
4040
- }
4041
- function pathAllowed(file, allowedPaths) {
4042
- return allowedPaths.some((allowedPath) => pathMatches(file, allowedPath));
4043
- }
4044
- function pathMatches(file, pattern) {
4045
- const normalizedFile = normalizePath(file);
4046
- const normalizedPattern = normalizePath(pattern);
4047
- return normalizedFile === normalizedPattern || normalizedFile.endsWith(`/${normalizedPattern}`);
4048
- }
4049
- function normalizePath(path) {
4050
- return path.split(sep).join("/").replace(/\\/g, "/");
4051
- }
4052
- function lineColumnAt(contents, index) {
4053
- const before = contents.slice(0, index);
4054
- const lines = before.split(/\r?\n/);
4055
- return {
4056
- line: lines.length,
4057
- column: lines[lines.length - 1].length + 1
4058
- };
4059
- }
4060
- function escapeRegExp(value) {
4061
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4062
- }
4063
- function lineAt(contents, line) {
4064
- return contents.split(/\r?\n/)[line - 1] ?? "";
4065
- }
4066
-
4067
- // src/style-bridge.ts
4068
- import { existsSync as existsSync14, mkdirSync as mkdirSync9, readFileSync as readFileSync12, writeFileSync as writeFileSync10 } from "fs";
4069
- import { join as join16 } from "path";
4070
- function styleBridgeProposalPath(projectRoot) {
4071
- return join16(projectRoot, ".decantr", "style-bridge.proposal.json");
4072
- }
4073
- function styleBridgePath(projectRoot) {
4074
- return join16(projectRoot, ".decantr", "style-bridge.json");
4075
- }
4076
- function readJsonFile2(path) {
4077
- if (!existsSync14(path)) return null;
4078
- try {
4079
- return JSON.parse(readFileSync12(path, "utf-8"));
4080
- } catch {
4081
- return null;
4082
- }
4083
- }
4084
- function writeJsonFile(path, value) {
4085
- writeFileSync10(path, `${JSON.stringify(value, null, 2)}
4086
- `, "utf-8");
4087
- }
4088
- function readStyleBridge(projectRoot) {
4089
- return readJsonFile2(styleBridgePath(projectRoot));
4090
- }
4091
- function readStyleBridgeProposal(projectRoot) {
4092
- return readJsonFile2(styleBridgeProposalPath(projectRoot));
4093
- }
4094
- function stringArray(value) {
4095
- return Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
4096
- }
4097
- function readThemeInventory(projectRoot) {
4098
- const inventory = readJsonFile2(join16(projectRoot, ".decantr", "theme-inventory.json"));
4099
- return {
4100
- modes: stringArray(inventory?.modes),
4101
- variantIds: Array.isArray(inventory?.variants) ? inventory.variants.map((variant) => variant.id).filter((id) => typeof id === "string") : [],
4102
- darkModeDetected: typeof inventory?.darkModeDetected === "boolean" ? inventory.darkModeDetected : null
4103
- };
4104
- }
4105
- function tokenHints(styling, terms) {
4106
- return styling.cssVariables.filter((name) => terms.test(name)).slice(0, 12);
4107
- }
4108
- function readProjectPatternPack(projectRoot) {
4109
- return readLocalPatternPack(projectRoot) ?? readJsonFile2(
4110
- join16(projectRoot, ".decantr", "local-patterns.proposal.json")
4111
- );
4112
- }
4113
- function classHintsForPattern(projectRoot, ids) {
4114
- const pack = readProjectPatternPack(projectRoot);
4115
- const classes = /* @__PURE__ */ new Set();
4116
- for (const pattern of pack?.patterns ?? []) {
4117
- if (!ids.includes(String(pattern.id))) continue;
4118
- for (const hint of pattern.classHints ?? []) classes.add(hint);
4119
- }
4120
- return [...classes].slice(0, 12);
4121
- }
4122
- function sourceEvidence(projectRoot, ids) {
4123
- const pack = readProjectPatternPack(projectRoot);
4124
- const evidence = /* @__PURE__ */ new Set();
4125
- for (const pattern of pack?.patterns ?? []) {
4126
- if (!ids.includes(String(pattern.id))) continue;
4127
- for (const path of pattern.componentPaths ?? []) evidence.add(path);
4128
- for (const item of pattern.evidence ?? []) evidence.add(item);
4129
- }
4130
- return [...evidence].slice(0, 12);
4131
- }
4132
- function colorTokenNames(styling) {
4133
- const names = new Set(Object.keys(styling.colors ?? {}));
4134
- for (const variable of styling.cssVariables) {
4135
- if (/color|bg|background|surface|text|foreground|border|primary|secondary|accent|muted/i.test(variable)) {
4136
- names.add(variable);
4137
- }
4138
- }
4139
- return [...names].slice(0, 40);
4140
- }
4141
- function createStyleBridgeProposal(input) {
4142
- const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
4143
- const theme = readThemeInventory(input.projectRoot);
4144
- const routeCount = input.essence && typeof input.essence === "object" && "blueprint" in input.essence ? Object.keys(input.essence.blueprint?.routes ?? {}).length : 0;
4145
- const target = input.essence && typeof input.essence === "object" && "meta" in input.essence ? String(input.essence.meta?.target ?? "") || null : null;
4146
- const darkModeDetected = theme.darkModeDetected ?? input.styling.darkMode;
4147
- const themeModes = theme.modes.length > 0 ? theme.modes : darkModeDetected ? ["base", "dark"] : ["base"];
4148
- const themeVariantIds = theme.variantIds.length > 0 ? theme.variantIds : themeModes.filter((mode) => mode !== "base");
4149
- return {
4150
- version: 1,
4151
- status: "proposal",
4152
- generatedAt,
4153
- source: "decantr codify --style-bridge",
4154
- purpose: "Project-owned Hybrid style bridge. It maps Decantr design intent to the existing app styling system without installing Decantr CSS or taking over source.",
4155
- adoption: {
4156
- mode: "style-bridge",
4157
- workflowMode: "brownfield-attach",
4158
- sourceAuthority: "Existing production source stays authoritative.",
4159
- styleAuthority: "Use these mappings to translate Decantr concepts into project-owned tokens, classes, and components.",
4160
- notRuntimeTakeover: true,
4161
- authorityPrecedence: [
4162
- "existing production source",
4163
- "accepted style bridge",
4164
- "accepted local patterns and rules",
4165
- "Essence V4 contract",
4166
- "hosted registry patterns and execution packs as optional guidance"
4167
- ]
4168
- },
4169
- project: {
4170
- framework: input.detected.framework,
4171
- packageManager: input.detected.packageManager,
4172
- target,
4173
- routeCount
4174
- },
4175
- styling: {
4176
- approach: input.styling.approach,
4177
- configFile: input.styling.configFile ?? null,
4178
- darkModeDetected,
4179
- cssVariables: input.styling.cssVariables.slice(0, 80),
4180
- colorTokenNames: colorTokenNames(input.styling),
4181
- themeModes,
4182
- themeVariantIds
4183
- },
4184
- mappings: [
4185
- {
4186
- id: "surface",
4187
- label: "Surfaces and cards",
4188
- decantrIntent: "surface background, card treatment, border, radius, depth, and hover state",
4189
- projectAuthority: "Use the app card/surface primitives, tokens, and accepted local law.",
4190
- tokenHints: tokenHints(input.styling, /surface|card|panel|bg|background|border|shadow|radius/i),
4191
- classHints: classHintsForPattern(input.projectRoot, ["surface-card"]),
4192
- sourceEvidence: sourceEvidence(input.projectRoot, ["surface-card"]),
4193
- guardrails: [
4194
- "Do not invent a new card color/radius/shadow recipe without updating local law.",
4195
- "Do not add Decantr CSS d-* classes unless adoption mode changes to decantr-css."
4196
- ]
4197
- },
4198
- {
4199
- id: "action",
4200
- label: "Actions and buttons",
4201
- decantrIntent: "primary, secondary, tertiary, destructive, icon-only, loading, and disabled action states",
4202
- projectAuthority: "Use the app button/action primitives and local variant names.",
4203
- tokenHints: tokenHints(input.styling, /primary|secondary|accent|danger|error|destructive|focus/i),
4204
- classHints: classHintsForPattern(input.projectRoot, ["button"]),
4205
- sourceEvidence: sourceEvidence(input.projectRoot, ["button"]),
4206
- guardrails: [
4207
- "Map any new action style to primary/secondary/tertiary/destructive/icon before coding.",
4208
- "Avoid raw button markup when a project-owned primitive exists."
4209
- ]
4210
- },
4211
- {
4212
- id: "focus-accessibility",
4213
- label: "Focus and accessibility",
4214
- decantrIntent: "visible focus, keyboard clarity, contrast, and reduced-motion safety",
4215
- projectAuthority: "Use the app accessibility classes, focus tokens, and framework conventions.",
4216
- tokenHints: tokenHints(input.styling, /focus|ring|outline|contrast|motion|duration/i),
4217
- classHints: [],
4218
- sourceEvidence: [],
4219
- guardrails: [
4220
- "Every new interactive control needs visible focus evidence.",
4221
- "Motion should honor prefers-reduced-motion or an existing app motion helper."
4222
- ]
4223
- },
4224
- {
4225
- id: "layout-density",
4226
- label: "Layout density and spacing",
4227
- decantrIntent: "route gutters, section gaps, component padding, density, and responsive rhythm",
4228
- projectAuthority: "Use existing layout wrappers, shell primitives, and spacing tokens/classes.",
4229
- tokenHints: tokenHints(input.styling, /space|spacing|gap|gutter|container|radius/i),
4230
- classHints: classHintsForPattern(input.projectRoot, ["page-shell"]),
4231
- sourceEvidence: sourceEvidence(input.projectRoot, ["page-shell"]),
4232
- guardrails: [
4233
- "Do not let each page invent independent max-width, gutters, or scroll ownership.",
4234
- "Use the detected shell/layout authority before adding a new wrapper."
4235
- ]
4236
- },
4237
- {
4238
- id: "theme-variant",
4239
- label: "Theme variants",
4240
- decantrIntent: "light, dark, brand, tenant, seasonal, and density variants",
4241
- projectAuthority: "Use the app theme provider, data attributes, CSS variables, or Tailwind mode strategy.",
4242
- tokenHints: tokenHints(input.styling, /theme|dark|light|brand|tenant|mode|color/i),
4243
- classHints: classHintsForPattern(input.projectRoot, ["theme-variant"]),
4244
- sourceEvidence: sourceEvidence(input.projectRoot, ["theme-variant"]),
4245
- guardrails: [
4246
- "Keep theme modes documented in .decantr/theme-inventory.json and this bridge.",
4247
- "Do not treat theme switcher container classes as standalone theme variants."
4248
- ]
4249
- }
4250
- ],
4251
- rules: [
4252
- "The bridge is advisory until accepted; after acceptance, task/doctor/CI should surface it as Hybrid authority.",
4253
- "The bridge does not make hosted registry patterns enforceable. Map hosted concepts into local law first.",
4254
- "Keep deterministic blocking checks in .decantr/rules.json, ESLint, Biome, tests, or visual regression."
4255
- ],
4256
- nextSteps: [
4257
- "Review token and class hints. Replace generic hints with the exact project tokens/classes the team owns.",
4258
- "Run decantr codify --accept to promote the proposal to .decantr/style-bridge.json.",
4259
- "Use decantr task <route> before LLM edits so the style bridge appears in task context.",
4260
- "Run decantr ci or decantr verify after edits to keep local law and Project Health visible."
4261
- ]
4262
- };
4263
- }
4264
- function writeStyleBridgeProposal(projectRoot, proposal) {
4265
- const decantrDir = join16(projectRoot, ".decantr");
4266
- mkdirSync9(decantrDir, { recursive: true });
4267
- const path = styleBridgeProposalPath(projectRoot);
4268
- writeJsonFile(path, proposal);
4269
- return path;
4270
- }
4271
- function acceptStyleBridge(projectRoot) {
4272
- const proposal = readStyleBridgeProposal(projectRoot);
4273
- if (!proposal) return null;
4274
- const accepted = {
4275
- ...proposal,
4276
- status: "accepted",
4277
- acceptedAt: (/* @__PURE__ */ new Date()).toISOString()
4278
- };
4279
- const path = styleBridgePath(projectRoot);
4280
- writeJsonFile(path, accepted);
4281
- const projectJsonPath = join16(projectRoot, ".decantr", "project.json");
4282
- const projectJson = readJsonFile2(projectJsonPath) ?? {};
4283
- const initialized = typeof projectJson.initialized === "object" && projectJson.initialized !== null ? projectJson.initialized : {};
4284
- writeJsonFile(projectJsonPath, {
4285
- ...projectJson,
4286
- initialized: {
4287
- ...initialized,
4288
- workflowMode: initialized.workflowMode ?? "brownfield-attach",
4289
- adoptionMode: "style-bridge"
4290
- }
4291
- });
4292
- return path;
4293
- }
4294
- function createStyleBridgeTaskSummary(projectRoot) {
4295
- const bridge = readStyleBridge(projectRoot);
4296
- return {
4297
- path: bridge ? ".decantr/style-bridge.json" : null,
4298
- status: bridge?.status ?? null,
4299
- mappingCount: bridge?.mappings?.length ?? 0,
4300
- stylingApproach: bridge?.styling?.approach ?? null,
4301
- themeModes: bridge?.styling?.themeModes ?? [],
4302
- mappings: bridge?.mappings?.map((mapping) => ({
4303
- id: mapping.id,
4304
- label: mapping.label,
4305
- tokenHints: mapping.tokenHints.slice(0, 6),
4306
- classHints: mapping.classHints.slice(0, 4),
4307
- guardrails: mapping.guardrails.slice(0, 3)
4308
- })) ?? []
4309
- };
4310
- }
4311
- function styleBridgeMatches(projectRoot, query) {
4312
- if (!projectRoot) return [];
4313
- const bridge = readStyleBridge(projectRoot);
4314
- if (!bridge) return [];
4315
- const terms = query.toLowerCase().split(/[^a-z0-9]+/).filter((term) => term.length > 1).flatMap((term) => term.endsWith("s") && term.length > 3 ? [term, term.slice(0, -1)] : [term]);
4316
- if (terms.length === 0) return [];
4317
- return bridge.mappings.map((mapping) => {
4318
- const haystack = [
4319
- mapping.id,
4320
- mapping.label,
4321
- mapping.decantrIntent,
4322
- mapping.projectAuthority,
4323
- ...mapping.tokenHints,
4324
- ...mapping.classHints,
4325
- ...mapping.guardrails
4326
- ].join(" ").toLowerCase();
4327
- const score = terms.reduce((sum, term) => sum + (haystack.includes(term) ? 1 : 0), 0);
4328
- return {
4329
- id: mapping.id,
4330
- label: mapping.label,
4331
- score,
4332
- tokenHints: mapping.tokenHints.slice(0, 4),
4333
- classHints: mapping.classHints.slice(0, 3)
4334
- };
4335
- }).filter((match) => match.score > 0).sort((a, b) => b.score - a.score || a.id.localeCompare(b.id)).slice(0, 5);
4336
- }
4337
-
4338
- // src/commands/ci.ts
3267
+ import { existsSync as existsSync13, mkdirSync as mkdirSync8, readFileSync as readFileSync11, writeFileSync as writeFileSync9 } from "fs";
3268
+ import { dirname as dirname3, join as join15, relative as relative3, resolve } from "path";
4339
3269
  var BOLD2 = "\x1B[1m";
4340
3270
  var DIM3 = "\x1B[2m";
4341
3271
  var GREEN3 = "\x1B[32m";
@@ -4343,9 +3273,9 @@ var RED2 = "\x1B[31m";
4343
3273
  var RESET3 = "\x1B[0m";
4344
3274
  var CI_SCHEMA = "https://decantr.ai/schemas/decantr-ci-report.v1.json";
4345
3275
  function readJson(path) {
4346
- if (!existsSync15(path)) return null;
3276
+ if (!existsSync13(path)) return null;
4347
3277
  try {
4348
- return JSON.parse(readFileSync13(path, "utf-8"));
3278
+ return JSON.parse(readFileSync11(path, "utf-8"));
4349
3279
  } catch {
4350
3280
  return null;
4351
3281
  }
@@ -4389,21 +3319,21 @@ function parseProvider(value) {
4389
3319
  throw new Error("Invalid --provider value. Use github or generic.");
4390
3320
  }
4391
3321
  function detectPackageManager2(root) {
4392
- const pkg = readJson(join17(root, "package.json"));
3322
+ const pkg = readJson(join15(root, "package.json"));
4393
3323
  const declared = pkg?.packageManager?.split("@")[0];
4394
3324
  if (declared === "pnpm" || declared === "npm" || declared === "yarn" || declared === "bun") {
4395
3325
  return declared;
4396
3326
  }
4397
- if (existsSync15(join17(root, "pnpm-lock.yaml"))) return "pnpm";
4398
- if (existsSync15(join17(root, "package-lock.json"))) return "npm";
4399
- if (existsSync15(join17(root, "yarn.lock"))) return "yarn";
4400
- if (existsSync15(join17(root, "bun.lock")) || existsSync15(join17(root, "bun.lockb"))) return "bun";
3327
+ if (existsSync13(join15(root, "pnpm-lock.yaml"))) return "pnpm";
3328
+ if (existsSync13(join15(root, "package-lock.json"))) return "npm";
3329
+ if (existsSync13(join15(root, "yarn.lock"))) return "yarn";
3330
+ if (existsSync13(join15(root, "bun.lock")) || existsSync13(join15(root, "bun.lockb"))) return "bun";
4401
3331
  return "unknown";
4402
3332
  }
4403
3333
  function hasWorkspaceMarker(root) {
4404
- const pkg = readJson(join17(root, "package.json"));
3334
+ const pkg = readJson(join15(root, "package.json"));
4405
3335
  return Boolean(
4406
- existsSync15(join17(root, "pnpm-workspace.yaml")) || existsSync15(join17(root, "turbo.json")) || existsSync15(join17(root, "nx.json")) || pkg?.workspaces
3336
+ existsSync13(join15(root, "pnpm-workspace.yaml")) || existsSync13(join15(root, "turbo.json")) || existsSync13(join15(root, "nx.json")) || pkg?.workspaces
4407
3337
  );
4408
3338
  }
4409
3339
  function installCommand(packageManager) {
@@ -4449,7 +3379,7 @@ function decantrCommand(packageManager) {
4449
3379
  }
4450
3380
  }
4451
3381
  function localCliPinned(root) {
4452
- const pkg = readJson(join17(root, "package.json"));
3382
+ const pkg = readJson(join15(root, "package.json"));
4453
3383
  return Boolean(pkg?.devDependencies?.["@decantr/cli"] || pkg?.dependencies?.["@decantr/cli"]);
4454
3384
  }
4455
3385
  function projectSlug(projectPath) {
@@ -4457,8 +3387,8 @@ function projectSlug(projectPath) {
4457
3387
  }
4458
3388
  function writeOutput(root, path, content) {
4459
3389
  const absolute = resolve(root, path);
4460
- mkdirSync10(dirname3(absolute), { recursive: true });
4461
- writeFileSync11(absolute, content, "utf-8");
3390
+ mkdirSync8(dirname3(absolute), { recursive: true });
3391
+ writeFileSync9(absolute, content, "utf-8");
4462
3392
  }
4463
3393
  function summarizeLocalLaw(projectRoot) {
4464
3394
  const validation = validateLocalLaw(projectRoot);
@@ -4733,7 +3663,7 @@ jobs:
4733
3663
  }
4734
3664
  function writeCiInit(root, options) {
4735
3665
  const workspaceInfo = resolveWorkspaceInfo(root, options.project);
4736
- if (options.project && !existsSync15(workspaceInfo.appRoot)) {
3666
+ if (options.project && !existsSync13(workspaceInfo.appRoot)) {
4737
3667
  throw new Error(`Project path does not exist: ${options.project}`);
4738
3668
  }
4739
3669
  if (workspaceInfo.requiresProjectSelection && !options.workspace) {
@@ -4743,7 +3673,7 @@ function writeCiInit(root, options) {
4743
3673
  );
4744
3674
  }
4745
3675
  const outputRoot = workspaceInfo.workspaceRoot;
4746
- const projectPath = options.project ?? (options.workspace || workspaceInfo.appRoot === workspaceInfo.workspaceRoot ? void 0 : relative4(workspaceInfo.workspaceRoot, workspaceInfo.appRoot).replace(/\\/g, "/"));
3676
+ const projectPath = options.project ?? (options.workspace || workspaceInfo.appRoot === workspaceInfo.workspaceRoot ? void 0 : relative3(workspaceInfo.workspaceRoot, workspaceInfo.appRoot).replace(/\\/g, "/"));
4747
3677
  const packageManager = detectPackageManager2(outputRoot);
4748
3678
  const command = decantrCommand(packageManager);
4749
3679
  const failOn = options.failOn ?? "error";
@@ -4756,7 +3686,7 @@ function writeCiInit(root, options) {
4756
3686
  if (provider === "generic") {
4757
3687
  const path2 = ".decantr/ci/decantr-ci.sh";
4758
3688
  const absolute2 = resolve(outputRoot, path2);
4759
- if (existsSync15(absolute2) && !options.force) {
3689
+ if (existsSync13(absolute2) && !options.force) {
4760
3690
  throw new Error(`${path2} already exists. Re-run with --force to replace it.`);
4761
3691
  }
4762
3692
  writeOutput(
@@ -4777,7 +3707,7 @@ function writeCiInit(root, options) {
4777
3707
  }
4778
3708
  const path = ".github/workflows/decantr-ci.yml";
4779
3709
  const absolute = resolve(outputRoot, path);
4780
- if (existsSync15(absolute) && !options.force) {
3710
+ if (existsSync13(absolute) && !options.force) {
4781
3711
  throw new Error(`${path} already exists. Re-run with --force to replace it.`);
4782
3712
  }
4783
3713
  writeOutput(
@@ -4826,7 +3756,7 @@ async function runWorkspaceCi(root, options) {
4826
3756
  }
4827
3757
  async function runProjectCi(root, options) {
4828
3758
  const workspaceInfo = resolveWorkspaceInfo(root, options.project);
4829
- if (options.project && !existsSync15(workspaceInfo.appRoot)) {
3759
+ if (options.project && !existsSync13(workspaceInfo.appRoot)) {
4830
3760
  console.error(`${RED2}Project path does not exist: ${options.project}${RESET3}`);
4831
3761
  return 1;
4832
3762
  }
@@ -4900,8 +3830,8 @@ async function cmdCi(args = ["ci"], root = process.cwd()) {
4900
3830
  }
4901
3831
 
4902
3832
  // src/commands/create.ts
4903
- import { existsSync as existsSync16, mkdirSync as mkdirSync11, writeFileSync as writeFileSync12 } from "fs";
4904
- import { join as join18 } from "path";
3833
+ import { existsSync as existsSync14, mkdirSync as mkdirSync9, writeFileSync as writeFileSync10 } from "fs";
3834
+ import { join as join16 } from "path";
4905
3835
  import {
4906
3836
  CONTENT_TYPE_TO_API_CONTENT_TYPE,
4907
3837
  CONTENT_TYPES
@@ -5097,25 +4027,25 @@ function cmdCreate(type, name, projectRoot = process.cwd()) {
5097
4027
  }
5098
4028
  const contentType = type;
5099
4029
  const plural = PLURAL[contentType];
5100
- const customDir = join18(projectRoot, ".decantr", "custom", plural);
5101
- const filePath = join18(customDir, `${name}.json`);
5102
- if (existsSync16(filePath)) {
4030
+ const customDir = join16(projectRoot, ".decantr", "custom", plural);
4031
+ const filePath = join16(customDir, `${name}.json`);
4032
+ if (existsSync14(filePath)) {
5103
4033
  console.error(`${type} "${name}" already exists at ${filePath}`);
5104
4034
  process.exitCode = 1;
5105
4035
  return;
5106
4036
  }
5107
- mkdirSync11(customDir, { recursive: true });
4037
+ mkdirSync9(customDir, { recursive: true });
5108
4038
  const skeleton = getSkeleton(contentType, name, humanizeId(name));
5109
- writeFileSync12(filePath, JSON.stringify(skeleton, null, 2));
4039
+ writeFileSync10(filePath, JSON.stringify(skeleton, null, 2));
5110
4040
  console.log(`Created ${type} "${name}" at ${filePath}`);
5111
4041
  console.log(`Edit it, then publish with: decantr publish ${type} ${name}`);
5112
4042
  }
5113
4043
 
5114
4044
  // src/commands/doctor.ts
5115
- import { existsSync as existsSync17, readdirSync as readdirSync6, readFileSync as readFileSync14 } from "fs";
5116
- import { dirname as dirname4, join as join19, relative as relative5 } from "path";
4045
+ import { existsSync as existsSync15, readdirSync as readdirSync5, readFileSync as readFileSync12 } from "fs";
4046
+ import { dirname as dirname4, join as join17, relative as relative4 } from "path";
5117
4047
  import { fileURLToPath } from "url";
5118
- import { isV4 as isV44 } from "@decantr/essence-spec";
4048
+ import { isV4 as isV43 } from "@decantr/essence-spec";
5119
4049
  import { collectMissingPackManifestFiles } from "@decantr/verifier";
5120
4050
  var BOLD3 = "\x1B[1m";
5121
4051
  var DIM4 = "\x1B[2m";
@@ -5125,25 +4055,25 @@ var YELLOW3 = "\x1B[33m";
5125
4055
  var CYAN2 = "\x1B[36m";
5126
4056
  var RESET4 = "\x1B[0m";
5127
4057
  function readJson2(path) {
5128
- if (!existsSync17(path)) return null;
4058
+ if (!existsSync15(path)) return null;
5129
4059
  try {
5130
- return JSON.parse(readFileSync14(path, "utf-8"));
4060
+ return JSON.parse(readFileSync12(path, "utf-8"));
5131
4061
  } catch {
5132
4062
  return null;
5133
4063
  }
5134
4064
  }
5135
4065
  function isContractOnlyProject(projectRoot) {
5136
- const projectJson = readJson2(join19(projectRoot, ".decantr", "project.json"));
4066
+ const projectJson = readJson2(join17(projectRoot, ".decantr", "project.json"));
5137
4067
  return projectJson?.initialized?.adoptionMode === "contract-only" || projectJson?.initialized?.adoptionMode === "style-bridge";
5138
4068
  }
5139
4069
  function readCliPackageVersion() {
5140
- const packagePath = join19(dirname4(fileURLToPath(import.meta.url)), "..", "..", "package.json");
5141
- const srcPackagePath = join19(dirname4(fileURLToPath(import.meta.url)), "..", "package.json");
4070
+ const packagePath = join17(dirname4(fileURLToPath(import.meta.url)), "..", "..", "package.json");
4071
+ const srcPackagePath = join17(dirname4(fileURLToPath(import.meta.url)), "..", "package.json");
5142
4072
  const pkg = readJson2(packagePath) ?? readJson2(srcPackagePath);
5143
4073
  return pkg?.version ?? "unknown";
5144
4074
  }
5145
4075
  function readPackageJson2(dir) {
5146
- return readJson2(join19(dir, "package.json"));
4076
+ return readJson2(join17(dir, "package.json"));
5147
4077
  }
5148
4078
  function detectPackageManager3(root) {
5149
4079
  const pkg = readPackageJson2(root);
@@ -5151,10 +4081,10 @@ function detectPackageManager3(root) {
5151
4081
  if (declared === "pnpm" || declared === "npm" || declared === "yarn" || declared === "bun") {
5152
4082
  return declared;
5153
4083
  }
5154
- if (existsSync17(join19(root, "pnpm-lock.yaml"))) return "pnpm";
5155
- if (existsSync17(join19(root, "package-lock.json"))) return "npm";
5156
- if (existsSync17(join19(root, "yarn.lock"))) return "yarn";
5157
- if (existsSync17(join19(root, "bun.lock")) || existsSync17(join19(root, "bun.lockb"))) return "bun";
4084
+ if (existsSync15(join17(root, "pnpm-lock.yaml"))) return "pnpm";
4085
+ if (existsSync15(join17(root, "package-lock.json"))) return "npm";
4086
+ if (existsSync15(join17(root, "yarn.lock"))) return "yarn";
4087
+ if (existsSync15(join17(root, "bun.lock")) || existsSync15(join17(root, "bun.lockb"))) return "bun";
5158
4088
  return "unknown";
5159
4089
  }
5160
4090
  function localCliDependency(root) {
@@ -5164,7 +4094,7 @@ function localCliDependency(root) {
5164
4094
  function hasWorkspaceMarker2(root) {
5165
4095
  const pkg = readPackageJson2(root);
5166
4096
  return Boolean(
5167
- existsSync17(join19(root, "pnpm-workspace.yaml")) || existsSync17(join19(root, "turbo.json")) || existsSync17(join19(root, "nx.json")) || pkg?.workspaces
4097
+ existsSync15(join17(root, "pnpm-workspace.yaml")) || existsSync15(join17(root, "turbo.json")) || existsSync15(join17(root, "nx.json")) || pkg?.workspaces
5168
4098
  );
5169
4099
  }
5170
4100
  function pinCliCommand2(packageManager, root) {
@@ -5182,13 +4112,13 @@ function pinCliCommand2(packageManager, root) {
5182
4112
  }
5183
4113
  }
5184
4114
  function rel(root, path) {
5185
- const value = relative5(root, path).replace(/\\/g, "/");
4115
+ const value = relative4(root, path).replace(/\\/g, "/");
5186
4116
  return value || ".";
5187
4117
  }
5188
4118
  function hasAnyFile(dir, names) {
5189
4119
  for (const name of names) {
5190
- const path = join19(dir, name);
5191
- if (existsSync17(path)) return name;
4120
+ const path = join17(dir, name);
4121
+ if (existsSync15(path)) return name;
5192
4122
  }
5193
4123
  return null;
5194
4124
  }
@@ -5210,7 +4140,7 @@ function detectDesignAuthority(workspaceRoot, appRoot) {
5210
4140
  "libs/design-system"
5211
4141
  ];
5212
4142
  for (const candidate of sharedCandidates) {
5213
- if (existsSync17(join19(workspaceRoot, candidate))) found.add(candidate);
4143
+ if (existsSync15(join17(workspaceRoot, candidate))) found.add(candidate);
5214
4144
  }
5215
4145
  for (const root of [appRoot, workspaceRoot]) {
5216
4146
  const label = root === appRoot ? "app" : "workspace";
@@ -5221,9 +4151,9 @@ function detectDesignAuthority(workspaceRoot, appRoot) {
5221
4151
  "tailwind.config.cjs"
5222
4152
  ]);
5223
4153
  if (tailwind) found.add(`${label}:${tailwind}`);
5224
- if (existsSync17(join19(root, ".storybook"))) found.add(`${label}:.storybook`);
5225
- if (existsSync17(join19(root, "components", "ui"))) found.add(`${label}:components/ui`);
5226
- if (existsSync17(join19(root, "src", "components", "ui"))) found.add(`${label}:src/components/ui`);
4154
+ if (existsSync15(join17(root, ".storybook"))) found.add(`${label}:.storybook`);
4155
+ if (existsSync15(join17(root, "components", "ui"))) found.add(`${label}:components/ui`);
4156
+ if (existsSync15(join17(root, "src", "components", "ui"))) found.add(`${label}:src/components/ui`);
5227
4157
  if (packageHasDependency(root, ["@storybook/react", "storybook"]))
5228
4158
  found.add(`${label}:storybook`);
5229
4159
  if (packageHasDependency(root, ["tailwindcss"])) found.add(`${label}:tailwindcss`);
@@ -5235,18 +4165,18 @@ function detectDesignAuthority(workspaceRoot, appRoot) {
5235
4165
  }
5236
4166
  function findCiFiles(root) {
5237
4167
  const files = [];
5238
- const workflows = join19(root, ".github", "workflows");
5239
- if (existsSync17(workflows)) {
5240
- for (const entry of readdirSync6(workflows, { withFileTypes: true })) {
4168
+ const workflows = join17(root, ".github", "workflows");
4169
+ if (existsSync15(workflows)) {
4170
+ for (const entry of readdirSync5(workflows, { withFileTypes: true })) {
5241
4171
  if (!entry.isFile()) continue;
5242
- const path = join19(workflows, entry.name);
5243
- const text = readFileSync14(path, "utf-8");
4172
+ const path = join17(workflows, entry.name);
4173
+ const text = readFileSync12(path, "utf-8");
5244
4174
  if (text.includes("decantr")) files.push(rel(root, path));
5245
4175
  }
5246
4176
  }
5247
4177
  for (const candidate of ["Jenkinsfile", ".gitlab-ci.yml", "azure-pipelines.yml", "BUILD"]) {
5248
- const path = join19(root, candidate);
5249
- if (existsSync17(path) && readFileSync14(path, "utf-8").includes("decantr")) {
4178
+ const path = join17(root, candidate);
4179
+ if (existsSync15(path) && readFileSync12(path, "utf-8").includes("decantr")) {
5250
4180
  files.push(candidate);
5251
4181
  }
5252
4182
  }
@@ -5372,6 +4302,28 @@ function deriveAdoptionLane(input) {
5372
4302
  nextChoice: "Use task and verify to keep generated routes aligned with the compiled contract."
5373
4303
  };
5374
4304
  }
4305
+ function inspectGraphArtifacts(appRoot, workspaceRoot) {
4306
+ const snapshotPath = join17(appRoot, ".decantr", "graph", "graph.snapshot.json");
4307
+ const capsulePath = join17(appRoot, ".decantr", "graph", "contract-capsule.json");
4308
+ try {
4309
+ const artifacts = buildGraphArtifacts(appRoot);
4310
+ return {
4311
+ snapshotPresent: existsSync15(snapshotPath),
4312
+ capsulePresent: existsSync15(capsulePath),
4313
+ artifactsCurrent: artifacts ? artifacts.staleArtifacts.length === 0 : null,
4314
+ staleArtifacts: artifacts ? artifacts.staleArtifacts.map((path) => rel(workspaceRoot, path)) : [],
4315
+ error: null
4316
+ };
4317
+ } catch (error3) {
4318
+ return {
4319
+ snapshotPresent: existsSync15(snapshotPath),
4320
+ capsulePresent: existsSync15(capsulePath),
4321
+ artifactsCurrent: false,
4322
+ staleArtifacts: [],
4323
+ error: error3.message
4324
+ };
4325
+ }
4326
+ }
5375
4327
  function buildDoctorReport(root, args) {
5376
4328
  let projectArg;
5377
4329
  for (let index = 1; index < args.length; index += 1) {
@@ -5383,18 +4335,18 @@ function buildDoctorReport(root, args) {
5383
4335
  const workspaceInfo = resolveWorkspaceInfo(root, projectArg);
5384
4336
  const workspaceRoot = workspaceInfo.workspaceRoot;
5385
4337
  const appRoot = workspaceMode ? workspaceRoot : workspaceInfo.appRoot;
5386
- const projectMissing = Boolean(projectArg && !existsSync17(appRoot));
4338
+ const projectMissing = Boolean(projectArg && !existsSync15(appRoot));
5387
4339
  const projectPath = appRoot === workspaceRoot ? null : rel(workspaceRoot, appRoot);
5388
4340
  const packageManager = detectPackageManager3(workspaceRoot);
5389
4341
  const cliDependency = localCliDependency(workspaceRoot);
5390
4342
  const detected = detectProject(appRoot);
5391
- const projectJson = readJson2(join19(appRoot, ".decantr", "project.json"));
5392
- const essence = readJson2(join19(appRoot, "decantr.essence.json"));
4343
+ const projectJson = readJson2(join17(appRoot, ".decantr", "project.json"));
4344
+ const essence = readJson2(join17(appRoot, "decantr.essence.json"));
5393
4345
  const essenceVersion = essence && typeof essence === "object" && "version" in essence ? String(essence.version) : null;
5394
- const contextDir = join19(appRoot, ".decantr", "context");
5395
- const packManifestPresent = existsSync17(join19(contextDir, "pack-manifest.json"));
5396
- const reviewPackPresent = existsSync17(join19(contextDir, "review-pack.json"));
5397
- const artifactReadmePresent = existsSync17(join19(appRoot, ARTIFACT_README_PATH));
4346
+ const contextDir = join17(appRoot, ".decantr", "context");
4347
+ const packManifestPresent = existsSync15(join17(contextDir, "pack-manifest.json"));
4348
+ const reviewPackPresent = existsSync15(join17(contextDir, "review-pack.json"));
4349
+ const artifactReadmePresent = existsSync15(join17(appRoot, ARTIFACT_README_PATH));
5398
4350
  const projects = listWorkspaceProjects(workspaceRoot);
5399
4351
  const candidates = listWorkspaceCandidates(workspaceRoot, projects).map((candidate) => ({
5400
4352
  path: candidate.path,
@@ -5405,22 +4357,23 @@ function buildDoctorReport(root, args) {
5405
4357
  const workflowMode = projectJson?.initialized?.workflowMode ?? null;
5406
4358
  const adoptionMode = projectJson?.initialized?.adoptionMode ?? null;
5407
4359
  const packHydrationOptional = adoptionMode === "contract-only" || adoptionMode === "style-bridge";
5408
- const localPatternsPresent = existsSync17(localPatternsPath(appRoot));
5409
- const localRulesPresent = existsSync17(localRulesPath(appRoot));
5410
- const styleBridgePresent = existsSync17(styleBridgePath(appRoot));
5411
- const styleBridgeProposalPresent = existsSync17(styleBridgeProposalPath(appRoot));
4360
+ const localPatternsPresent = existsSync15(localPatternsPath(appRoot));
4361
+ const localRulesPresent = existsSync15(localRulesPath(appRoot));
4362
+ const styleBridgePresent = existsSync15(styleBridgePath(appRoot));
4363
+ const styleBridgeProposalPresent = existsSync15(styleBridgeProposalPath(appRoot));
4364
+ const graphArtifacts = inspectGraphArtifacts(appRoot, workspaceRoot);
5412
4365
  const missingPackReferences = workspaceMode ? projects.flatMap(
5413
- (project) => collectMissingPackManifestFiles(join19(workspaceRoot, project.path)).map(
4366
+ (project) => collectMissingPackManifestFiles(join17(workspaceRoot, project.path)).map(
5414
4367
  (missing) => `${project.path}/${missing.relativePath}`
5415
4368
  )
5416
4369
  ) : collectMissingPackManifestFiles(appRoot).map((missing) => missing.relativePath);
5417
4370
  const workspaceProjectsMissingManifest = workspaceMode ? projects.map((project) => project.path).filter((projectPath2) => {
5418
- const projectContextDir = join19(workspaceRoot, projectPath2, ".decantr", "context");
5419
- return !isContractOnlyProject(join19(workspaceRoot, projectPath2)) && !existsSync17(join19(projectContextDir, "pack-manifest.json"));
4371
+ const projectContextDir = join17(workspaceRoot, projectPath2, ".decantr", "context");
4372
+ return !isContractOnlyProject(join17(workspaceRoot, projectPath2)) && !existsSync15(join17(projectContextDir, "pack-manifest.json"));
5420
4373
  }) : [];
5421
4374
  const workspaceProjectsMissingReviewPack = workspaceMode ? projects.map((project) => project.path).filter((projectPath2) => {
5422
- const projectContextDir = join19(workspaceRoot, projectPath2, ".decantr", "context");
5423
- return !isContractOnlyProject(join19(workspaceRoot, projectPath2)) && !existsSync17(join19(projectContextDir, "review-pack.json"));
4375
+ const projectContextDir = join17(workspaceRoot, projectPath2, ".decantr", "context");
4376
+ return !isContractOnlyProject(join17(workspaceRoot, projectPath2)) && !existsSync15(join17(projectContextDir, "review-pack.json"));
5424
4377
  }) : [];
5425
4378
  const issues = [];
5426
4379
  if (projectMissing) {
@@ -5437,7 +4390,7 @@ function buildDoctorReport(root, args) {
5437
4390
  message: "No decantr.essence.json found for this app.",
5438
4391
  nextCommand: projectPath ? `decantr adopt --project ${projectPath} --yes` : "decantr adopt --yes"
5439
4392
  });
5440
- } else if (essenceVersion && !isV44(essence)) {
4393
+ } else if (essenceVersion && !isV43(essence)) {
5441
4394
  issues.push({
5442
4395
  category: "migration",
5443
4396
  severity: "error",
@@ -5477,7 +4430,7 @@ function buildDoctorReport(root, args) {
5477
4430
  nextCommand: "decantr refresh"
5478
4431
  });
5479
4432
  }
5480
- if (essenceVersion === "4.0.0" && !packHydrationOptional && (!existsSync17(contextDir) || !packManifestPresent)) {
4433
+ if (essenceVersion === "4.0.0" && !packHydrationOptional && (!existsSync15(contextDir) || !packManifestPresent)) {
5481
4434
  issues.push({
5482
4435
  category: "generated-artifact",
5483
4436
  severity: "warn",
@@ -5493,6 +4446,14 @@ function buildDoctorReport(root, args) {
5493
4446
  nextCommand: projectPath ? `decantr registry compile-packs ${projectPath}/decantr.essence.json --write-context` : "decantr registry compile-packs decantr.essence.json --write-context"
5494
4447
  });
5495
4448
  }
4449
+ if (essenceVersion === "4.0.0" && (Boolean(projectJson) || graphArtifacts.snapshotPresent || graphArtifacts.capsulePresent) && (graphArtifacts.error || graphArtifacts.artifactsCurrent === false)) {
4450
+ issues.push({
4451
+ category: "graph",
4452
+ severity: "warn",
4453
+ message: graphArtifacts.error ? `Typed Contract graph could not be derived: ${graphArtifacts.error}` : "Typed Contract graph artifacts are missing or stale.",
4454
+ nextCommand: projectPath ? `decantr graph --project ${projectPath}` : "decantr graph"
4455
+ });
4456
+ }
5496
4457
  if (workspaceMode && (workspaceProjectsMissingManifest.length > 0 || workspaceProjectsMissingReviewPack.length > 0 || missingPackReferences.length > 0)) {
5497
4458
  issues.push({
5498
4459
  category: "generated-artifact",
@@ -5562,6 +4523,10 @@ function buildDoctorReport(root, args) {
5562
4523
  recommendedNextCommands,
5563
4524
  issues.find((issue) => issue.category === "ci" && issue.message.includes("not pinned"))?.nextCommand
5564
4525
  );
4526
+ appendUnique(
4527
+ recommendedNextCommands,
4528
+ issues.find((issue) => issue.category === "graph")?.nextCommand
4529
+ );
5565
4530
  if (workflowMode === "brownfield-attach" && !localPatternsPresent) {
5566
4531
  appendUnique(recommendedNextCommands, `decantr codify --from-audit${projectFlag}`);
5567
4532
  appendUnique(recommendedNextCommands, `decantr codify --accept${projectFlag}`);
@@ -5611,11 +4576,16 @@ function buildDoctorReport(root, args) {
5611
4576
  },
5612
4577
  generatedArtifacts: {
5613
4578
  contextDirPresent: workspaceMode ? projects.some(
5614
- (project) => existsSync17(join19(workspaceRoot, project.path, ".decantr", "context"))
5615
- ) : existsSync17(contextDir),
4579
+ (project) => existsSync15(join17(workspaceRoot, project.path, ".decantr", "context"))
4580
+ ) : existsSync15(contextDir),
5616
4581
  packManifestPresent: workspaceMode ? projects.length > 0 && workspaceProjectsMissingManifest.length === 0 : packManifestPresent,
5617
4582
  reviewPackPresent: workspaceMode ? projects.length > 0 && workspaceProjectsMissingReviewPack.length === 0 : reviewPackPresent,
5618
- missingReferencedFiles: missingPackReferences.slice(0, 25)
4583
+ missingReferencedFiles: missingPackReferences.slice(0, 25),
4584
+ graphSnapshotPresent: graphArtifacts.snapshotPresent,
4585
+ graphCapsulePresent: graphArtifacts.capsulePresent,
4586
+ graphArtifactsCurrent: graphArtifacts.artifactsCurrent,
4587
+ graphStaleArtifacts: graphArtifacts.staleArtifacts.slice(0, 25),
4588
+ graphError: graphArtifacts.error
5619
4589
  },
5620
4590
  localLaw: {
5621
4591
  patternsPresent: localPatternsPresent,
@@ -5624,7 +4594,7 @@ function buildDoctorReport(root, args) {
5624
4594
  styleBridgeProposalPresent
5625
4595
  },
5626
4596
  visualEvidence: {
5627
- manifestPresent: existsSync17(join19(appRoot, ".decantr", "evidence", "visual-manifest.json"))
4597
+ manifestPresent: existsSync15(join17(appRoot, ".decantr", "evidence", "visual-manifest.json"))
5628
4598
  },
5629
4599
  lane,
5630
4600
  designAuthority,
@@ -5668,6 +4638,8 @@ function formatDoctorText(report) {
5668
4638
  ` Review pack: ${report.generatedArtifacts.reviewPackPresent ? "present" : "missing"}`,
5669
4639
  ` Manifest references: ${report.generatedArtifacts.missingReferencedFiles.length === 0 ? "complete" : `${report.generatedArtifacts.missingReferencedFiles.length} missing`}`,
5670
4640
  ` Artifact guide: ${report.project.artifactReadmePresent ? "present" : "missing"}`,
4641
+ ` Typed graph: ${report.generatedArtifacts.graphError ? "error" : report.generatedArtifacts.graphArtifactsCurrent === true ? "current" : report.generatedArtifacts.graphArtifactsCurrent === false ? "stale or missing" : "not generated"}`,
4642
+ ` Graph capsule: ${report.generatedArtifacts.graphCapsulePresent ? "present" : "missing"}`,
5671
4643
  "",
5672
4644
  `${BOLD3}Local Law:${RESET4}`,
5673
4645
  ` Local patterns: ${report.localLaw.patternsPresent ? "present" : "missing"}`,
@@ -5732,17 +4704,17 @@ async function cmdDoctor(args = ["doctor"], root = process.cwd()) {
5732
4704
  }
5733
4705
 
5734
4706
  // src/commands/export.ts
5735
- import { existsSync as existsSync18, mkdirSync as mkdirSync12, readFileSync as readFileSync15, writeFileSync as writeFileSync13 } from "fs";
5736
- import { dirname as dirname5, isAbsolute, join as join20 } from "path";
4707
+ import { existsSync as existsSync16, mkdirSync as mkdirSync10, readFileSync as readFileSync13, writeFileSync as writeFileSync11 } from "fs";
4708
+ import { dirname as dirname5, isAbsolute, join as join18 } from "path";
5737
4709
  var GREEN5 = "\x1B[32m";
5738
4710
  var RED4 = "\x1B[31m";
5739
4711
  var DIM5 = "\x1B[2m";
5740
4712
  var RESET5 = "\x1B[0m";
5741
4713
  function readAdoptionMode(projectRoot) {
5742
- const projectJsonPath = join20(projectRoot, ".decantr", "project.json");
5743
- if (!existsSync18(projectJsonPath)) return null;
4714
+ const projectJsonPath = join18(projectRoot, ".decantr", "project.json");
4715
+ if (!existsSync16(projectJsonPath)) return null;
5744
4716
  try {
5745
- const parsed = JSON.parse(readFileSync15(projectJsonPath, "utf-8"));
4717
+ const parsed = JSON.parse(readFileSync13(projectJsonPath, "utf-8"));
5746
4718
  return typeof parsed.initialized?.adoptionMode === "string" ? parsed.initialized.adoptionMode : null;
5747
4719
  } catch {
5748
4720
  return null;
@@ -5750,7 +4722,7 @@ function readAdoptionMode(projectRoot) {
5750
4722
  }
5751
4723
  function resolveOutputPath(projectRoot, output, fallback) {
5752
4724
  if (!output) return fallback;
5753
- return isAbsolute(output) ? output : join20(projectRoot, output);
4725
+ return isAbsolute(output) ? output : join18(projectRoot, output);
5754
4726
  }
5755
4727
  var SHADCN_MAP = {
5756
4728
  "--d-bg": "--background",
@@ -5911,21 +4883,21 @@ function generateFigmaTokens(tokens) {
5911
4883
  `;
5912
4884
  }
5913
4885
  async function cmdExport(target, projectRoot, options = {}) {
5914
- const essencePath = join20(projectRoot, "decantr.essence.json");
5915
- const tokensPath = join20(projectRoot, "src", "styles", "tokens.css");
5916
- if (!existsSync18(essencePath)) {
4886
+ const essencePath = join18(projectRoot, "decantr.essence.json");
4887
+ const tokensPath = join18(projectRoot, "src", "styles", "tokens.css");
4888
+ if (!existsSync16(essencePath)) {
5917
4889
  console.error(`${RED4}No decantr.essence.json found. Run \`decantr init\` first.${RESET5}`);
5918
4890
  process.exitCode = 1;
5919
4891
  return;
5920
4892
  }
5921
- if (!existsSync18(tokensPath)) {
4893
+ if (!existsSync16(tokensPath)) {
5922
4894
  const adoptionMode = readAdoptionMode(projectRoot);
5923
4895
  const message = adoptionMode === "contract-only" ? `No src/styles/tokens.css found. This export reads Decantr CSS tokens, but this project is contract-only Brownfield and may intentionally use its own Tailwind/Sass token system. Export from the app's token source or adopt a style bridge before using \`decantr export --to ${target}\`.` : "No src/styles/tokens.css found. Run `decantr refresh` to generate tokens.";
5924
4896
  console.error(`${RED4}${message}${RESET5}`);
5925
4897
  process.exitCode = 1;
5926
4898
  return;
5927
4899
  }
5928
- const tokensCSS = readFileSync15(tokensPath, "utf-8");
4900
+ const tokensCSS = readFileSync13(tokensPath, "utf-8");
5929
4901
  const tokens = parseTokensCSS(tokensCSS);
5930
4902
  if (tokens.size === 0) {
5931
4903
  console.error(`${RED4}No --d-* tokens found in tokens.css.${RESET5}`);
@@ -5937,12 +4909,12 @@ async function cmdExport(target, projectRoot, options = {}) {
5937
4909
  const cssOut = resolveOutputPath(
5938
4910
  projectRoot,
5939
4911
  options.output,
5940
- join20(projectRoot, "src", "styles", "shadcn-theme.css")
4912
+ join18(projectRoot, "src", "styles", "shadcn-theme.css")
5941
4913
  );
5942
- const jsonOut = join20(projectRoot, "components.json");
4914
+ const jsonOut = join18(projectRoot, "components.json");
5943
4915
  ensureDir(cssOut);
5944
- writeFileSync13(cssOut, generateShadcnCSS(tokens), "utf-8");
5945
- writeFileSync13(jsonOut, generateShadcnComponentsJSON(), "utf-8");
4916
+ writeFileSync11(cssOut, generateShadcnCSS(tokens), "utf-8");
4917
+ writeFileSync11(jsonOut, generateShadcnComponentsJSON(), "utf-8");
5946
4918
  console.log(`${GREEN5}Exported shadcn theme:${RESET5}`);
5947
4919
  console.log(` ${DIM5}CSS:${RESET5} ${cssOut}`);
5948
4920
  console.log(` ${DIM5}JSON:${RESET5} ${jsonOut}`);
@@ -5952,10 +4924,10 @@ async function cmdExport(target, projectRoot, options = {}) {
5952
4924
  const out = resolveOutputPath(
5953
4925
  projectRoot,
5954
4926
  options.output,
5955
- join20(projectRoot, "tailwind.decantr.config.ts")
4927
+ join18(projectRoot, "tailwind.decantr.config.ts")
5956
4928
  );
5957
4929
  ensureDir(out);
5958
- writeFileSync13(out, generateTailwindConfig(tokens), "utf-8");
4930
+ writeFileSync11(out, generateTailwindConfig(tokens), "utf-8");
5959
4931
  console.log(`${GREEN5}Exported Tailwind config:${RESET5}`);
5960
4932
  console.log(` ${DIM5}File:${RESET5} ${out}`);
5961
4933
  break;
@@ -5964,10 +4936,10 @@ async function cmdExport(target, projectRoot, options = {}) {
5964
4936
  const out = resolveOutputPath(
5965
4937
  projectRoot,
5966
4938
  options.output,
5967
- join20(projectRoot, "decantr-tokens.css")
4939
+ join18(projectRoot, "decantr-tokens.css")
5968
4940
  );
5969
4941
  ensureDir(out);
5970
- writeFileSync13(out, generateCSSVars(tokens), "utf-8");
4942
+ writeFileSync11(out, generateCSSVars(tokens), "utf-8");
5971
4943
  console.log(`${GREEN5}Exported CSS variables:${RESET5}`);
5972
4944
  console.log(` ${DIM5}File:${RESET5} ${out}`);
5973
4945
  break;
@@ -5976,10 +4948,10 @@ async function cmdExport(target, projectRoot, options = {}) {
5976
4948
  const out = resolveOutputPath(
5977
4949
  projectRoot,
5978
4950
  options.output,
5979
- join20(projectRoot, ".decantr", "design", "figma-tokens.json")
4951
+ join18(projectRoot, ".decantr", "design", "figma-tokens.json")
5980
4952
  );
5981
4953
  ensureDir(out);
5982
- writeFileSync13(out, generateFigmaTokens(tokens), "utf-8");
4954
+ writeFileSync11(out, generateFigmaTokens(tokens), "utf-8");
5983
4955
  console.log(`${GREEN5}Exported Figma/Tokens Studio tokens:${RESET5}`);
5984
4956
  console.log(` ${DIM5}File:${RESET5} ${out}`);
5985
4957
  break;
@@ -5988,15 +4960,15 @@ async function cmdExport(target, projectRoot, options = {}) {
5988
4960
  }
5989
4961
  function ensureDir(filePath) {
5990
4962
  const dir = dirname5(filePath);
5991
- if (!existsSync18(dir)) {
5992
- mkdirSync12(dir, { recursive: true });
4963
+ if (!existsSync16(dir)) {
4964
+ mkdirSync10(dir, { recursive: true });
5993
4965
  }
5994
4966
  }
5995
4967
 
5996
4968
  // src/commands/magic.ts
5997
- import { existsSync as existsSync19 } from "fs";
4969
+ import { existsSync as existsSync17 } from "fs";
5998
4970
  import * as fs from "fs/promises";
5999
- import { join as join21 } from "path";
4971
+ import { join as join19 } from "path";
6000
4972
  var BOLD4 = "\x1B[1m";
6001
4973
  var DIM6 = "\x1B[2m";
6002
4974
  var RESET6 = "\x1B[0m";
@@ -6220,8 +5192,8 @@ async function cmdMagic(prompt, projectRoot, options) {
6220
5192
  console.log(` Archetype: ${intent.archetype}`);
6221
5193
  }
6222
5194
  console.log("");
6223
- const essencePath = join21(projectRoot, "decantr.essence.json");
6224
- if (existsSync19(essencePath)) {
5195
+ const essencePath = join19(projectRoot, "decantr.essence.json");
5196
+ if (existsSync17(essencePath)) {
6225
5197
  const projectFlag = options.projectLabel ? ` --project ${options.projectLabel}` : "";
6226
5198
  console.log(`${YELLOW4} Decantr is already attached to this project.${RESET6}`);
6227
5199
  console.log(
@@ -6254,7 +5226,7 @@ async function cmdMagic(prompt, projectRoot, options) {
6254
5226
  return;
6255
5227
  }
6256
5228
  const registryClient = new RegistryClient({
6257
- cacheDir: join21(projectRoot, ".decantr", "cache"),
5229
+ cacheDir: join19(projectRoot, ".decantr", "cache"),
6258
5230
  apiUrl: options.registry,
6259
5231
  offline: options.offline
6260
5232
  });
@@ -6525,14 +5497,14 @@ async function cmdMagic(prompt, projectRoot, options) {
6525
5497
  if (result.gitignoreUpdated) {
6526
5498
  console.log(` ${dim(".gitignore updated")}`);
6527
5499
  }
6528
- const contextDir = join21(projectRoot, ".decantr", "context");
5500
+ const contextDir = join19(projectRoot, ".decantr", "context");
6529
5501
  let sectionCount = 0;
6530
5502
  try {
6531
5503
  const files = await fs.readdir(contextDir);
6532
5504
  sectionCount = files.filter((f) => f.startsWith("section-")).length;
6533
5505
  } catch {
6534
5506
  }
6535
- const treatmentsPath = join21(projectRoot, "src", "styles", "treatments.css");
5507
+ const treatmentsPath = join19(projectRoot, "src", "styles", "treatments.css");
6536
5508
  let hasLayers = false;
6537
5509
  try {
6538
5510
  const css = await fs.readFile(treatmentsPath, "utf-8");
@@ -6565,10 +5537,10 @@ ${GREEN6}${BOLD4}Quality summary:${RESET6}`);
6565
5537
  }
6566
5538
 
6567
5539
  // src/commands/migrate.ts
6568
- import { copyFileSync, existsSync as existsSync20, readFileSync as readFileSync16, writeFileSync as writeFileSync14 } from "fs";
6569
- import { join as join22 } from "path";
5540
+ import { copyFileSync, existsSync as existsSync18, readFileSync as readFileSync14, writeFileSync as writeFileSync12 } from "fs";
5541
+ import { join as join20 } from "path";
6570
5542
  import {
6571
- isV4 as isV45,
5543
+ isV4 as isV44,
6572
5544
  migrateToV4,
6573
5545
  validateEssence,
6574
5546
  validateLegacyEssenceForMigration
@@ -6579,12 +5551,12 @@ var YELLOW5 = "\x1B[33m";
6579
5551
  var RESET7 = "\x1B[0m";
6580
5552
  var DIM7 = "\x1B[2m";
6581
5553
  function migrateEssenceFile(essencePath) {
6582
- if (!existsSync20(essencePath)) {
5554
+ if (!existsSync18(essencePath)) {
6583
5555
  return { success: false, error: `File not found: ${essencePath}` };
6584
5556
  }
6585
5557
  let raw;
6586
5558
  try {
6587
- raw = readFileSync16(essencePath, "utf-8");
5559
+ raw = readFileSync14(essencePath, "utf-8");
6588
5560
  } catch (e) {
6589
5561
  return { success: false, error: `Could not read ${essencePath}: ${e.message}` };
6590
5562
  }
@@ -6594,7 +5566,7 @@ function migrateEssenceFile(essencePath) {
6594
5566
  } catch (e) {
6595
5567
  return { success: false, error: `Invalid JSON: ${e.message}` };
6596
5568
  }
6597
- if (isV45(essence)) {
5569
+ if (isV44(essence)) {
6598
5570
  return { success: true, alreadyV4: true, essence };
6599
5571
  }
6600
5572
  const preValidation = validateLegacyEssenceForMigration(essence);
@@ -6625,7 +5597,7 @@ function migrateEssenceFile(essencePath) {
6625
5597
  };
6626
5598
  }
6627
5599
  try {
6628
- writeFileSync14(essencePath, JSON.stringify(v4, null, 2) + "\n");
5600
+ writeFileSync12(essencePath, JSON.stringify(v4, null, 2) + "\n");
6629
5601
  } catch (e) {
6630
5602
  return {
6631
5603
  success: false,
@@ -6646,8 +5618,8 @@ async function cmdMigrate(projectRoot = process.cwd(), args = []) {
6646
5618
  process.exitCode = 1;
6647
5619
  return;
6648
5620
  }
6649
- const essencePath = join22(projectRoot, "decantr.essence.json");
6650
- if (!existsSync20(essencePath)) {
5621
+ const essencePath = join20(projectRoot, "decantr.essence.json");
5622
+ if (!existsSync18(essencePath)) {
6651
5623
  console.error(`${RED5}No decantr.essence.json found. Run \`decantr init\` first.${RESET7}`);
6652
5624
  process.exitCode = 1;
6653
5625
  return;
@@ -6669,7 +5641,7 @@ async function cmdMigrate(projectRoot = process.cwd(), args = []) {
6669
5641
  }
6670
5642
  if (result.essence) {
6671
5643
  const registryClient = new RegistryClient({
6672
- cacheDir: join22(projectRoot, ".decantr", "cache")
5644
+ cacheDir: join20(projectRoot, ".decantr", "cache")
6673
5645
  });
6674
5646
  await refreshDerivedFiles(projectRoot, result.essence, registryClient);
6675
5647
  console.log(`${GREEN7}Derived context and execution packs refreshed.${RESET7}`);
@@ -6680,50 +5652,50 @@ async function cmdMigrate(projectRoot = process.cwd(), args = []) {
6680
5652
 
6681
5653
  // src/commands/new-project.ts
6682
5654
  import { spawnSync } from "child_process";
6683
- import { existsSync as existsSync22, mkdirSync as mkdirSync14 } from "fs";
6684
- import { join as join24, resolve as resolve3 } from "path";
5655
+ import { existsSync as existsSync20, mkdirSync as mkdirSync12 } from "fs";
5656
+ import { join as join22, resolve as resolve3 } from "path";
6685
5657
  import { fileURLToPath as fileURLToPath2 } from "url";
6686
5658
 
6687
5659
  // src/offline-content.ts
6688
- import { cpSync, existsSync as existsSync21, mkdirSync as mkdirSync13 } from "fs";
6689
- import { join as join23, resolve as resolve2 } from "path";
5660
+ import { cpSync, existsSync as existsSync19, mkdirSync as mkdirSync11 } from "fs";
5661
+ import { join as join21, resolve as resolve2 } from "path";
6690
5662
  var CONTENT_TYPES2 = ["archetypes", "blueprints", "patterns", "themes", "shells"];
6691
5663
  function copyIfExists(source, target) {
6692
- if (!existsSync21(source)) return false;
5664
+ if (!existsSync19(source)) return false;
6693
5665
  if (resolve2(source) === resolve2(target)) return true;
6694
5666
  cpSync(source, target, { recursive: true });
6695
5667
  return true;
6696
5668
  }
6697
5669
  function hydrateContentRoot(projectDir, contentRoot) {
6698
- if (!existsSync21(contentRoot)) return false;
6699
- const customRoot = join23(projectDir, ".decantr", "custom");
6700
- const cacheRoot = join23(projectDir, ".decantr", "cache", "@official");
6701
- mkdirSync13(customRoot, { recursive: true });
6702
- mkdirSync13(cacheRoot, { recursive: true });
5670
+ if (!existsSync19(contentRoot)) return false;
5671
+ const customRoot = join21(projectDir, ".decantr", "custom");
5672
+ const cacheRoot = join21(projectDir, ".decantr", "cache", "@official");
5673
+ mkdirSync11(customRoot, { recursive: true });
5674
+ mkdirSync11(cacheRoot, { recursive: true });
6703
5675
  let copiedAny = false;
6704
5676
  for (const type of CONTENT_TYPES2) {
6705
- const sourceDir = join23(contentRoot, type);
6706
- if (!existsSync21(sourceDir)) continue;
6707
- cpSync(sourceDir, join23(customRoot, type), { recursive: true });
6708
- cpSync(sourceDir, join23(cacheRoot, type), { recursive: true });
5677
+ const sourceDir = join21(contentRoot, type);
5678
+ if (!existsSync19(sourceDir)) continue;
5679
+ cpSync(sourceDir, join21(customRoot, type), { recursive: true });
5680
+ cpSync(sourceDir, join21(cacheRoot, type), { recursive: true });
6709
5681
  copiedAny = true;
6710
5682
  }
6711
5683
  return copiedAny;
6712
5684
  }
6713
5685
  function seedOfflineRegistry(projectDir, workspaceRoot) {
6714
- const projectDecantrRoot = join23(projectDir, ".decantr");
6715
- mkdirSync13(projectDecantrRoot, { recursive: true });
5686
+ const projectDecantrRoot = join21(projectDir, ".decantr");
5687
+ mkdirSync11(projectDecantrRoot, { recursive: true });
6716
5688
  const configuredContentRoot = process.env.DECANTR_CONTENT_DIR ? resolve2(process.env.DECANTR_CONTENT_DIR) : null;
6717
5689
  if (configuredContentRoot && hydrateContentRoot(projectDir, configuredContentRoot)) {
6718
5690
  return { seeded: true, strategy: "configured-content-root" };
6719
5691
  }
6720
5692
  const copiedCache = copyIfExists(
6721
- join23(workspaceRoot, ".decantr", "cache"),
6722
- join23(projectDecantrRoot, "cache")
5693
+ join21(workspaceRoot, ".decantr", "cache"),
5694
+ join21(projectDecantrRoot, "cache")
6723
5695
  );
6724
5696
  const copiedCustom = copyIfExists(
6725
- join23(workspaceRoot, ".decantr", "custom"),
6726
- join23(projectDecantrRoot, "custom")
5697
+ join21(workspaceRoot, ".decantr", "custom"),
5698
+ join21(projectDecantrRoot, "custom")
6727
5699
  );
6728
5700
  if (copiedCache || copiedCustom) {
6729
5701
  return { seeded: true, strategy: "workspace-cache" };
@@ -6836,7 +5808,7 @@ function runArgvCommand(command, args, cwd) {
6836
5808
  }
6837
5809
  function resolveInitCommand(initFlags) {
6838
5810
  const bundledCliEntrypoint = fileURLToPath2(new URL("./bin.js", import.meta.url));
6839
- const cliEntrypoint = existsSync22(bundledCliEntrypoint) ? bundledCliEntrypoint : process.argv[1] && existsSync22(process.argv[1]) ? process.argv[1] : null;
5811
+ const cliEntrypoint = existsSync20(bundledCliEntrypoint) ? bundledCliEntrypoint : process.argv[1] && existsSync20(process.argv[1]) ? process.argv[1] : null;
6840
5812
  if (cliEntrypoint) {
6841
5813
  return {
6842
5814
  command: process.execPath,
@@ -6864,7 +5836,7 @@ async function cmdNewProject(projectName, options) {
6864
5836
  process.exitCode = 1;
6865
5837
  return;
6866
5838
  }
6867
- if (existsSync22(projectDir)) {
5839
+ if (existsSync20(projectDir)) {
6868
5840
  console.error(error(`Directory "${projectName}" already exists.`));
6869
5841
  process.exitCode = 1;
6870
5842
  return;
@@ -6878,7 +5850,7 @@ async function cmdNewProject(projectName, options) {
6878
5850
  return;
6879
5851
  }
6880
5852
  console.log(heading(`Creating ${projectName}...`));
6881
- mkdirSync14(projectDir, { recursive: true });
5853
+ mkdirSync12(projectDir, { recursive: true });
6882
5854
  console.log(dim2(` Created ${projectName}/`));
6883
5855
  const title = projectName.replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
6884
5856
  if (shouldBootstrapRuntime && bootstrapAdapter) {
@@ -6977,21 +5949,21 @@ ${YELLOW6}Decantr init encountered issues. Run \`decantr init\` manually inside
6977
5949
  }
6978
5950
  }
6979
5951
  function detectPackageManager4() {
6980
- if (existsSync22(join24(process.cwd(), "pnpm-lock.yaml")) || existsSync22(join24(process.cwd(), "pnpm-workspace.yaml"))) {
5952
+ if (existsSync20(join22(process.cwd(), "pnpm-lock.yaml")) || existsSync20(join22(process.cwd(), "pnpm-workspace.yaml"))) {
6981
5953
  return "pnpm";
6982
5954
  }
6983
- if (existsSync22(join24(process.cwd(), "yarn.lock"))) {
5955
+ if (existsSync20(join22(process.cwd(), "yarn.lock"))) {
6984
5956
  return "yarn";
6985
5957
  }
6986
- if (existsSync22(join24(process.cwd(), "bun.lockb")) || existsSync22(join24(process.cwd(), "bun.lock"))) {
5958
+ if (existsSync20(join22(process.cwd(), "bun.lockb")) || existsSync20(join22(process.cwd(), "bun.lock"))) {
6987
5959
  return "bun";
6988
5960
  }
6989
5961
  return "npm";
6990
5962
  }
6991
5963
 
6992
5964
  // src/commands/publish.ts
6993
- import { existsSync as existsSync23, readFileSync as readFileSync17 } from "fs";
6994
- import { join as join25 } from "path";
5965
+ import { existsSync as existsSync21, readFileSync as readFileSync15 } from "fs";
5966
+ import { join as join23 } from "path";
6995
5967
  import {
6996
5968
  API_CONTENT_TYPE_TO_CONTENT_TYPE,
6997
5969
  CONTENT_TYPE_TO_API_CONTENT_TYPE as CONTENT_TYPE_TO_API_CONTENT_TYPE2,
@@ -7007,8 +5979,8 @@ async function cmdPublish(type, name, projectRoot = process.cwd()) {
7007
5979
  }
7008
5980
  const singularType = API_CONTENT_TYPE_TO_CONTENT_TYPE[type] || type;
7009
5981
  const pluralType = CONTENT_TYPE_TO_API_CONTENT_TYPE2[type] || CONTENT_TYPE_TO_API_CONTENT_TYPE2[singularType] || `${type}s`;
7010
- const customPath = join25(projectRoot, ".decantr", "custom", pluralType, `${name}.json`);
7011
- if (!existsSync23(customPath)) {
5982
+ const customPath = join23(projectRoot, ".decantr", "custom", pluralType, `${name}.json`);
5983
+ if (!existsSync21(customPath)) {
7012
5984
  console.error(`Custom ${singularType} "${name}" not found at ${customPath}`);
7013
5985
  console.error(`Create one first: decantr create ${singularType} ${name}`);
7014
5986
  process.exitCode = 1;
@@ -7016,7 +5988,7 @@ async function cmdPublish(type, name, projectRoot = process.cwd()) {
7016
5988
  }
7017
5989
  let data;
7018
5990
  try {
7019
- data = JSON.parse(readFileSync17(customPath, "utf-8"));
5991
+ data = JSON.parse(readFileSync15(customPath, "utf-8"));
7020
5992
  } catch {
7021
5993
  console.error(`Failed to parse ${customPath}`);
7022
5994
  process.exitCode = 1;
@@ -7055,22 +6027,22 @@ async function cmdPublish(type, name, projectRoot = process.cwd()) {
7055
6027
 
7056
6028
  // src/commands/refresh.ts
7057
6029
  import { createHash } from "crypto";
7058
- import { existsSync as existsSync24, readdirSync as readdirSync7, readFileSync as readFileSync18, statSync as statSync6 } from "fs";
7059
- import { isAbsolute as isAbsolute2, join as join26, relative as relative6 } from "path";
6030
+ import { existsSync as existsSync22, readdirSync as readdirSync6, readFileSync as readFileSync16, statSync as statSync5 } from "fs";
6031
+ import { isAbsolute as isAbsolute2, join as join24, relative as relative5 } from "path";
7060
6032
  import { collectMissingPackManifestFiles as collectMissingPackManifestFiles2 } from "@decantr/verifier";
7061
- import { isV4 as isV46 } from "@decantr/essence-spec";
6033
+ import { isV4 as isV45 } from "@decantr/essence-spec";
7062
6034
  var GREEN9 = "\x1B[32m";
7063
6035
  var RED7 = "\x1B[31m";
7064
6036
  var DIM9 = "\x1B[2m";
7065
6037
  var RESET9 = "\x1B[0m";
7066
6038
  function hashFile(path) {
7067
- return createHash("sha256").update(readFileSync18(path)).digest("hex");
6039
+ return createHash("sha256").update(readFileSync16(path)).digest("hex");
7068
6040
  }
7069
6041
  function walkFiles(dir) {
7070
- if (!existsSync24(dir)) return [];
6042
+ if (!existsSync22(dir)) return [];
7071
6043
  const files = [];
7072
- for (const entry of readdirSync7(dir, { withFileTypes: true })) {
7073
- const path = join26(dir, entry.name);
6044
+ for (const entry of readdirSync6(dir, { withFileTypes: true })) {
6045
+ const path = join24(dir, entry.name);
7074
6046
  if (entry.isDirectory()) {
7075
6047
  files.push(...walkFiles(path));
7076
6048
  } else if (entry.isFile()) {
@@ -7081,10 +6053,10 @@ function walkFiles(dir) {
7081
6053
  }
7082
6054
  function trackedGeneratedFiles(projectRoot) {
7083
6055
  return [
7084
- join26(projectRoot, "DECANTR.md"),
7085
- ...walkFiles(join26(projectRoot, ".decantr", "context")),
7086
- ...["global.css", "tokens.css", "treatments.css", "decantr-bridge.css"].map((file) => join26(projectRoot, "src", "styles", file)).filter((path) => existsSync24(path))
7087
- ].filter((path) => existsSync24(path));
6056
+ join24(projectRoot, "DECANTR.md"),
6057
+ ...walkFiles(join24(projectRoot, ".decantr", "context")),
6058
+ ...["global.css", "tokens.css", "treatments.css", "decantr-bridge.css"].map((file) => join24(projectRoot, "src", "styles", file)).filter((path) => existsSync22(path))
6059
+ ].filter((path) => existsSync22(path));
7088
6060
  }
7089
6061
  function snapshotGeneratedFiles(projectRoot) {
7090
6062
  const map = /* @__PURE__ */ new Map();
@@ -7100,40 +6072,40 @@ function snapshotGeneratedFiles(projectRoot) {
7100
6072
  }
7101
6073
  function fileMtimeMs(path) {
7102
6074
  try {
7103
- return statSync6(path).mtimeMs;
6075
+ return statSync5(path).mtimeMs;
7104
6076
  } catch {
7105
6077
  return 0;
7106
6078
  }
7107
6079
  }
7108
6080
  function newestInputMtime(projectRoot) {
7109
6081
  return Math.max(
7110
- fileMtimeMs(join26(projectRoot, "decantr.essence.json")),
7111
- fileMtimeMs(join26(projectRoot, ".decantr", "project.json"))
6082
+ fileMtimeMs(join24(projectRoot, "decantr.essence.json")),
6083
+ fileMtimeMs(join24(projectRoot, ".decantr", "project.json"))
7112
6084
  );
7113
6085
  }
7114
6086
  function isContractOnlyProject2(projectRoot) {
7115
- const projectJsonPath = join26(projectRoot, ".decantr", "project.json");
7116
- if (!existsSync24(projectJsonPath)) return false;
6087
+ const projectJsonPath = join24(projectRoot, ".decantr", "project.json");
6088
+ if (!existsSync22(projectJsonPath)) return false;
7117
6089
  try {
7118
- const projectJson = JSON.parse(readFileSync18(projectJsonPath, "utf-8"));
6090
+ const projectJson = JSON.parse(readFileSync16(projectJsonPath, "utf-8"));
7119
6091
  return projectJson.initialized?.adoptionMode === "contract-only";
7120
6092
  } catch {
7121
6093
  return false;
7122
6094
  }
7123
6095
  }
7124
6096
  function checkRefreshFreshness(projectRoot) {
7125
- const contextDir = join26(projectRoot, ".decantr", "context");
6097
+ const contextDir = join24(projectRoot, ".decantr", "context");
7126
6098
  const generated = trackedGeneratedFiles(projectRoot);
7127
6099
  const reasons = [];
7128
6100
  const packHydrationOptional = isContractOnlyProject2(projectRoot);
7129
- if (!existsSync24(join26(projectRoot, "DECANTR.md"))) reasons.push("DECANTR.md is missing.");
7130
- if (!existsSync24(contextDir)) reasons.push(".decantr/context is missing.");
7131
- if (!existsSync24(join26(contextDir, "scaffold.md"))) {
6101
+ if (!existsSync22(join24(projectRoot, "DECANTR.md"))) reasons.push("DECANTR.md is missing.");
6102
+ if (!existsSync22(contextDir)) reasons.push(".decantr/context is missing.");
6103
+ if (!existsSync22(join24(contextDir, "scaffold.md"))) {
7132
6104
  reasons.push(".decantr/context/scaffold.md is missing.");
7133
6105
  }
7134
- if (!packHydrationOptional && !existsSync24(join26(contextDir, "pack-manifest.json"))) {
6106
+ if (!packHydrationOptional && !existsSync22(join24(contextDir, "pack-manifest.json"))) {
7135
6107
  reasons.push(".decantr/context/pack-manifest.json is missing.");
7136
- } else if (existsSync24(join26(contextDir, "pack-manifest.json"))) {
6108
+ } else if (existsSync22(join24(contextDir, "pack-manifest.json"))) {
7137
6109
  const missingPackFiles = collectMissingPackManifestFiles2(projectRoot);
7138
6110
  if (missingPackFiles.length > 0) {
7139
6111
  reasons.push(
@@ -7186,8 +6158,8 @@ function summarizeChanges(projectRoot, before, after) {
7186
6158
  }
7187
6159
  function displayGeneratedPath(summary, file, displayRoot) {
7188
6160
  if (!displayRoot) return file;
7189
- const absolutePath = join26(summary.projectRoot, file);
7190
- const relativePath = relative6(displayRoot, absolutePath).replace(/\\/g, "/");
6161
+ const absolutePath = join24(summary.projectRoot, file);
6162
+ const relativePath = relative5(displayRoot, absolutePath).replace(/\\/g, "/");
7191
6163
  if (relativePath && !relativePath.startsWith("..") && !isAbsolute2(relativePath)) {
7192
6164
  return relativePath;
7193
6165
  }
@@ -7221,17 +6193,17 @@ function printRefreshSummary(summary, displayRoot) {
7221
6193
  }
7222
6194
  }
7223
6195
  async function cmdRefresh(projectRoot = process.cwd(), options = {}) {
7224
- const essencePath = join26(projectRoot, "decantr.essence.json");
7225
- if (!existsSync24(essencePath)) {
6196
+ const essencePath = join24(projectRoot, "decantr.essence.json");
6197
+ if (!existsSync22(essencePath)) {
7226
6198
  console.error(`${RED7}No decantr.essence.json found. Run \`decantr init\` first.${RESET9}`);
7227
6199
  process.exitCode = 1;
7228
6200
  return;
7229
6201
  }
7230
6202
  let essence;
7231
6203
  try {
7232
- const raw = readFileSync18(essencePath, "utf-8");
6204
+ const raw = readFileSync16(essencePath, "utf-8");
7233
6205
  const parsed = JSON.parse(raw);
7234
- if (!isV46(parsed)) {
6206
+ if (!isV45(parsed)) {
7235
6207
  console.error(
7236
6208
  `${RED7}Active workflows require Essence v4.0.0. Run \`decantr migrate --to v4\` first.${RESET9}`
7237
6209
  );
@@ -7255,7 +6227,7 @@ async function cmdRefresh(projectRoot = process.cwd(), options = {}) {
7255
6227
  return;
7256
6228
  }
7257
6229
  const registryClient = new RegistryClient({
7258
- cacheDir: join26(projectRoot, ".decantr", "cache"),
6230
+ cacheDir: join24(projectRoot, ".decantr", "cache"),
7259
6231
  offline: options.offline
7260
6232
  });
7261
6233
  console.log("Regenerating derived files...\n");
@@ -7286,8 +6258,8 @@ async function cmdRefresh(projectRoot = process.cwd(), options = {}) {
7286
6258
  }
7287
6259
 
7288
6260
  // src/commands/registry-mirror.ts
7289
- import { mkdirSync as mkdirSync15, writeFileSync as writeFileSync15 } from "fs";
7290
- import { join as join27 } from "path";
6261
+ import { mkdirSync as mkdirSync13, writeFileSync as writeFileSync13 } from "fs";
6262
+ import { join as join25 } from "path";
7291
6263
  import { API_CONTENT_TYPES, RegistryAPIClient as RegistryAPIClient2 } from "@decantr/registry";
7292
6264
  var GREEN10 = "\x1B[32m";
7293
6265
  var RED8 = "\x1B[31m";
@@ -7315,7 +6287,7 @@ async function cmdRegistryMirror(projectRoot, options = {}) {
7315
6287
  process.exitCode = 1;
7316
6288
  return;
7317
6289
  }
7318
- const cacheDir = join27(projectRoot, ".decantr", "cache");
6290
+ const cacheDir = join25(projectRoot, ".decantr", "cache");
7319
6291
  const counts = {};
7320
6292
  const failed = [];
7321
6293
  console.log(`
@@ -7325,19 +6297,19 @@ Mirroring registry content to ${DIM10}.decantr/cache/${RESET10}
7325
6297
  try {
7326
6298
  const result = await apiClient.listContent(type, { namespace: "@official" });
7327
6299
  const items = result.items;
7328
- const typeDir = join27(cacheDir, "@official", type);
7329
- mkdirSync15(typeDir, { recursive: true });
7330
- writeFileSync15(join27(typeDir, "index.json"), JSON.stringify(result, null, 2));
6300
+ const typeDir = join25(cacheDir, "@official", type);
6301
+ mkdirSync13(typeDir, { recursive: true });
6302
+ writeFileSync13(join25(typeDir, "index.json"), JSON.stringify(result, null, 2));
7331
6303
  let itemCount = 0;
7332
6304
  for (const item of items) {
7333
6305
  const slug = item.slug || item.id;
7334
6306
  if (!slug) continue;
7335
6307
  try {
7336
6308
  const fullItem = await apiClient.getContent(type, "@official", slug);
7337
- writeFileSync15(join27(typeDir, `${slug}.json`), JSON.stringify(fullItem, null, 2));
6309
+ writeFileSync13(join25(typeDir, `${slug}.json`), JSON.stringify(fullItem, null, 2));
7338
6310
  itemCount++;
7339
6311
  } catch {
7340
- writeFileSync15(join27(typeDir, `${slug}.json`), JSON.stringify(item, null, 2));
6312
+ writeFileSync13(join25(typeDir, `${slug}.json`), JSON.stringify(item, null, 2));
7341
6313
  itemCount++;
7342
6314
  }
7343
6315
  }
@@ -7352,8 +6324,8 @@ Mirroring registry content to ${DIM10}.decantr/cache/${RESET10}
7352
6324
  mirrored_at: (/* @__PURE__ */ new Date()).toISOString(),
7353
6325
  counts
7354
6326
  };
7355
- mkdirSync15(join27(cacheDir), { recursive: true });
7356
- writeFileSync15(join27(cacheDir, "mirror-manifest.json"), JSON.stringify(manifest, null, 2));
6327
+ mkdirSync13(join25(cacheDir), { recursive: true });
6328
+ writeFileSync13(join25(cacheDir, "mirror-manifest.json"), JSON.stringify(manifest, null, 2));
7357
6329
  const totalItems = Object.values(counts).reduce((a, b) => a + b, 0);
7358
6330
  console.log("");
7359
6331
  if (failed.length > 0) {
@@ -7370,30 +6342,30 @@ Mirroring registry content to ${DIM10}.decantr/cache/${RESET10}
7370
6342
  }
7371
6343
 
7372
6344
  // src/commands/remove.ts
7373
- import { existsSync as existsSync26, readFileSync as readFileSync19, rmSync as rmSync2, writeFileSync as writeFileSync16 } from "fs";
7374
- import { join as join28 } from "path";
7375
- import { isV4 as isV47 } from "@decantr/essence-spec";
6345
+ import { existsSync as existsSync24, readFileSync as readFileSync17, rmSync as rmSync2, writeFileSync as writeFileSync14 } from "fs";
6346
+ import { join as join26 } from "path";
6347
+ import { isV4 as isV46 } from "@decantr/essence-spec";
7376
6348
  var GREEN11 = "\x1B[32m";
7377
6349
  var RED9 = "\x1B[31m";
7378
6350
  var YELLOW8 = "\x1B[33m";
7379
6351
  var DIM11 = "\x1B[2m";
7380
6352
  var RESET11 = "\x1B[0m";
7381
6353
  function readV4Essence2(projectRoot) {
7382
- const essencePath = join28(projectRoot, "decantr.essence.json");
7383
- if (!existsSync26(essencePath)) {
6354
+ const essencePath = join26(projectRoot, "decantr.essence.json");
6355
+ if (!existsSync24(essencePath)) {
7384
6356
  console.error(`${RED9}No decantr.essence.json found. Run \`decantr init\` first.${RESET11}`);
7385
6357
  process.exitCode = 1;
7386
6358
  return null;
7387
6359
  }
7388
6360
  let parsed;
7389
6361
  try {
7390
- parsed = JSON.parse(readFileSync19(essencePath, "utf-8"));
6362
+ parsed = JSON.parse(readFileSync17(essencePath, "utf-8"));
7391
6363
  } catch (e) {
7392
6364
  console.error(`${RED9}Could not read essence: ${e.message}${RESET11}`);
7393
6365
  process.exitCode = 1;
7394
6366
  return null;
7395
6367
  }
7396
- if (!isV47(parsed)) {
6368
+ if (!isV46(parsed)) {
7397
6369
  console.error(
7398
6370
  `${RED9}Active workflows require Essence v4.0.0. Run \`decantr migrate --to v4\` first.${RESET11}`
7399
6371
  );
@@ -7403,7 +6375,7 @@ function readV4Essence2(projectRoot) {
7403
6375
  return { essence: parsed, essencePath };
7404
6376
  }
7405
6377
  function writeEssence2(essencePath, essence) {
7406
- writeFileSync16(essencePath, JSON.stringify(essence, null, 2) + "\n");
6378
+ writeFileSync14(essencePath, JSON.stringify(essence, null, 2) + "\n");
7407
6379
  }
7408
6380
  function readFlagValue2(args, name) {
7409
6381
  const prefix = `--${name}=`;
@@ -7475,14 +6447,14 @@ async function cmdRemoveSection(sectionId, args, projectRoot = process.cwd()) {
7475
6447
  sections.splice(idx, 1);
7476
6448
  recomputeGlobalFeatures(essence);
7477
6449
  removeRoutes(essence, sectionId);
7478
- const contextFile = join28(projectRoot, ".decantr", "context", `${sectionId}.md`);
7479
- if (existsSync26(contextFile)) {
6450
+ const contextFile = join26(projectRoot, ".decantr", "context", `${sectionId}.md`);
6451
+ if (existsSync24(contextFile)) {
7480
6452
  rmSync2(contextFile);
7481
6453
  }
7482
6454
  writeEssence2(essencePath, essence);
7483
6455
  console.log(`${GREEN11}Removed section "${sectionId}".${RESET11}`);
7484
6456
  const registryClient = new RegistryClient({
7485
- cacheDir: join28(projectRoot, ".decantr", "cache")
6457
+ cacheDir: join26(projectRoot, ".decantr", "cache")
7486
6458
  });
7487
6459
  await refreshDerivedFiles(projectRoot, essence, registryClient);
7488
6460
  console.log(`${GREEN11}Derived files refreshed.${RESET11}`);
@@ -7525,7 +6497,7 @@ async function cmdRemovePage(path, args, projectRoot = process.cwd()) {
7525
6497
  }
7526
6498
  console.log(`${GREEN11}Removed page "${pageId}" from section "${resolvedSectionId}".${RESET11}`);
7527
6499
  const registryClient = new RegistryClient({
7528
- cacheDir: join28(projectRoot, ".decantr", "cache")
6500
+ cacheDir: join26(projectRoot, ".decantr", "cache")
7529
6501
  });
7530
6502
  await refreshDerivedFiles(projectRoot, essence, registryClient);
7531
6503
  console.log(`${GREEN11}Derived files refreshed.${RESET11}`);
@@ -7568,15 +6540,15 @@ async function cmdRemoveFeature(feature, args, projectRoot = process.cwd()) {
7568
6540
  const target = sectionId ? `section "${sectionId}" and global` : "global";
7569
6541
  console.log(`${GREEN11}Removed feature "${feature}" from ${target} features.${RESET11}`);
7570
6542
  const registryClient = new RegistryClient({
7571
- cacheDir: join28(projectRoot, ".decantr", "cache")
6543
+ cacheDir: join26(projectRoot, ".decantr", "cache")
7572
6544
  });
7573
6545
  await refreshDerivedFiles(projectRoot, essence, registryClient);
7574
6546
  console.log(`${GREEN11}Derived files refreshed.${RESET11}`);
7575
6547
  }
7576
6548
 
7577
6549
  // src/commands/sync-drift.ts
7578
- import { existsSync as existsSync27, readFileSync as readFileSync20, writeFileSync as writeFileSync17 } from "fs";
7579
- import { join as join29 } from "path";
6550
+ import { existsSync as existsSync25, readFileSync as readFileSync18, writeFileSync as writeFileSync15 } from "fs";
6551
+ import { join as join27 } from "path";
7580
6552
  var GREEN12 = "\x1B[32m";
7581
6553
  var RED10 = "\x1B[31m";
7582
6554
  var YELLOW9 = "\x1B[33m";
@@ -7585,8 +6557,8 @@ var DIM12 = "\x1B[2m";
7585
6557
  var BOLD6 = "\x1B[1m";
7586
6558
  var CYAN6 = "\x1B[36m";
7587
6559
  async function cmdSyncDrift(projectRoot = process.cwd()) {
7588
- const driftLogPath = join29(projectRoot, ".decantr", "drift-log.json");
7589
- if (!existsSync27(driftLogPath)) {
6560
+ const driftLogPath = join27(projectRoot, ".decantr", "drift-log.json");
6561
+ if (!existsSync25(driftLogPath)) {
7590
6562
  console.log(`${GREEN12}No drift log found \u2014 no drift recorded.${RESET12}`);
7591
6563
  console.log(
7592
6564
  `${DIM12}Drift is logged when guard violations are detected during development.${RESET12}`
@@ -7595,7 +6567,7 @@ async function cmdSyncDrift(projectRoot = process.cwd()) {
7595
6567
  }
7596
6568
  let entries;
7597
6569
  try {
7598
- entries = JSON.parse(readFileSync20(driftLogPath, "utf-8"));
6570
+ entries = JSON.parse(readFileSync18(driftLogPath, "utf-8"));
7599
6571
  } catch {
7600
6572
  console.error(`${RED10}Could not parse drift-log.json${RESET12}`);
7601
6573
  process.exitCode = 1;
@@ -7641,13 +6613,13 @@ ${BOLD6}Unresolved Drift Entries (${unresolved.length})${RESET12}
7641
6613
  console.log("");
7642
6614
  }
7643
6615
  function resolveDriftEntries(projectRoot, options) {
7644
- const driftLogPath = join29(projectRoot, ".decantr", "drift-log.json");
7645
- if (!existsSync27(driftLogPath)) {
6616
+ const driftLogPath = join27(projectRoot, ".decantr", "drift-log.json");
6617
+ if (!existsSync25(driftLogPath)) {
7646
6618
  return { success: true };
7647
6619
  }
7648
6620
  if (options.clear) {
7649
6621
  try {
7650
- writeFileSync17(driftLogPath, "[]");
6622
+ writeFileSync15(driftLogPath, "[]");
7651
6623
  return { success: true };
7652
6624
  } catch (e) {
7653
6625
  return { success: false, error: `Could not clear drift log: ${e.message}` };
@@ -7655,7 +6627,7 @@ function resolveDriftEntries(projectRoot, options) {
7655
6627
  }
7656
6628
  let entries;
7657
6629
  try {
7658
- entries = JSON.parse(readFileSync20(driftLogPath, "utf-8"));
6630
+ entries = JSON.parse(readFileSync18(driftLogPath, "utf-8"));
7659
6631
  } catch {
7660
6632
  return { success: false, error: "Could not parse drift-log.json" };
7661
6633
  }
@@ -7674,7 +6646,7 @@ function resolveDriftEntries(projectRoot, options) {
7674
6646
  }
7675
6647
  }
7676
6648
  try {
7677
- writeFileSync17(driftLogPath, JSON.stringify(entries, null, 2));
6649
+ writeFileSync15(driftLogPath, JSON.stringify(entries, null, 2));
7678
6650
  return { success: true };
7679
6651
  } catch (e) {
7680
6652
  return { success: false, error: `Could not write drift log: ${e.message}` };
@@ -7899,9 +6871,9 @@ function trimTrailingSlashes(value) {
7899
6871
  }
7900
6872
 
7901
6873
  // src/commands/theme-switch.ts
7902
- import { existsSync as existsSync28, readFileSync as readFileSync21, writeFileSync as writeFileSync18 } from "fs";
7903
- import { join as join30 } from "path";
7904
- import { isV4 as isV48 } from "@decantr/essence-spec";
6874
+ import { existsSync as existsSync26, readFileSync as readFileSync19, writeFileSync as writeFileSync16 } from "fs";
6875
+ import { join as join28 } from "path";
6876
+ import { isV4 as isV47 } from "@decantr/essence-spec";
7905
6877
  var GREEN14 = "\x1B[32m";
7906
6878
  var RED11 = "\x1B[31m";
7907
6879
  var YELLOW10 = "\x1B[33m";
@@ -7917,21 +6889,21 @@ async function cmdThemeSwitch(themeName, args, projectRoot = process.cwd()) {
7917
6889
  process.exitCode = 1;
7918
6890
  return;
7919
6891
  }
7920
- const essencePath = join30(projectRoot, "decantr.essence.json");
7921
- if (!existsSync28(essencePath)) {
6892
+ const essencePath = join28(projectRoot, "decantr.essence.json");
6893
+ if (!existsSync26(essencePath)) {
7922
6894
  console.error(`${RED11}No decantr.essence.json found. Run \`decantr init\` first.${RESET14}`);
7923
6895
  process.exitCode = 1;
7924
6896
  return;
7925
6897
  }
7926
6898
  let parsed;
7927
6899
  try {
7928
- parsed = JSON.parse(readFileSync21(essencePath, "utf-8"));
6900
+ parsed = JSON.parse(readFileSync19(essencePath, "utf-8"));
7929
6901
  } catch (e) {
7930
6902
  console.error(`${RED11}Could not read essence: ${e.message}${RESET14}`);
7931
6903
  process.exitCode = 1;
7932
6904
  return;
7933
6905
  }
7934
- if (!isV48(parsed)) {
6906
+ if (!isV47(parsed)) {
7935
6907
  console.error(
7936
6908
  `${RED11}Active workflows require Essence v4.0.0. Run \`decantr migrate --to v4\` first.${RESET14}`
7937
6909
  );
@@ -7976,7 +6948,7 @@ async function cmdThemeSwitch(themeName, args, projectRoot = process.cwd()) {
7976
6948
  essence.dna.theme.mode = mode;
7977
6949
  }
7978
6950
  const registryClient = new RegistryClient({
7979
- cacheDir: join30(projectRoot, ".decantr", "cache")
6951
+ cacheDir: join28(projectRoot, ".decantr", "cache")
7980
6952
  });
7981
6953
  try {
7982
6954
  const themeResult = await registryClient.fetchTheme(themeName);
@@ -7991,7 +6963,7 @@ async function cmdThemeSwitch(themeName, args, projectRoot = process.cwd()) {
7991
6963
  }
7992
6964
  } catch {
7993
6965
  }
7994
- writeFileSync18(essencePath, JSON.stringify(essence, null, 2) + "\n");
6966
+ writeFileSync16(essencePath, JSON.stringify(essence, null, 2) + "\n");
7995
6967
  console.log(`${GREEN14}Switched theme: ${oldThemeId} \u2192 ${themeName}${RESET14}`);
7996
6968
  if (shape) console.log(` ${DIM14}Shape: ${shape}${RESET14}`);
7997
6969
  if (mode) console.log(` ${DIM14}Mode: ${mode}${RESET14}`);
@@ -8299,8 +7271,8 @@ async function runSimplifiedInit(blueprints) {
8299
7271
  }
8300
7272
 
8301
7273
  // src/theme-commands.ts
8302
- import { existsSync as existsSync29, mkdirSync as mkdirSync16, readdirSync as readdirSync8, readFileSync as readFileSync22, rmSync as rmSync3, writeFileSync as writeFileSync19 } from "fs";
8303
- import { join as join31 } from "path";
7274
+ import { existsSync as existsSync27, mkdirSync as mkdirSync14, readdirSync as readdirSync7, readFileSync as readFileSync20, rmSync as rmSync3, writeFileSync as writeFileSync17 } from "fs";
7275
+ import { join as join29 } from "path";
8304
7276
  var REQUIRED_FIELDS = [
8305
7277
  "$schema",
8306
7278
  "id",
@@ -8360,20 +7332,20 @@ function validateCustomTheme(theme) {
8360
7332
  };
8361
7333
  }
8362
7334
  function createTheme(projectRoot, id, name) {
8363
- const customThemesDir = join31(projectRoot, ".decantr", "custom", "themes");
8364
- const themePath = join31(customThemesDir, `${id}.json`);
8365
- const howToPath = join31(customThemesDir, "how-to-theme.md");
8366
- mkdirSync16(customThemesDir, { recursive: true });
8367
- if (existsSync29(themePath)) {
7335
+ const customThemesDir = join29(projectRoot, ".decantr", "custom", "themes");
7336
+ const themePath = join29(customThemesDir, `${id}.json`);
7337
+ const howToPath = join29(customThemesDir, "how-to-theme.md");
7338
+ mkdirSync14(customThemesDir, { recursive: true });
7339
+ if (existsSync27(themePath)) {
8368
7340
  return {
8369
7341
  success: false,
8370
7342
  error: `Theme "${id}" already exists at ${themePath}`
8371
7343
  };
8372
7344
  }
8373
7345
  const skeleton = getThemeSkeleton(id, name);
8374
- writeFileSync19(themePath, JSON.stringify(skeleton, null, 2));
8375
- if (!existsSync29(howToPath)) {
8376
- writeFileSync19(howToPath, getHowToThemeDoc());
7346
+ writeFileSync17(themePath, JSON.stringify(skeleton, null, 2));
7347
+ if (!existsSync27(howToPath)) {
7348
+ writeFileSync17(howToPath, getHowToThemeDoc());
8377
7349
  }
8378
7350
  return {
8379
7351
  success: true,
@@ -8381,17 +7353,17 @@ function createTheme(projectRoot, id, name) {
8381
7353
  };
8382
7354
  }
8383
7355
  function listCustomThemes(projectRoot) {
8384
- const customThemesDir = join31(projectRoot, ".decantr", "custom", "themes");
8385
- if (!existsSync29(customThemesDir)) {
7356
+ const customThemesDir = join29(projectRoot, ".decantr", "custom", "themes");
7357
+ if (!existsSync27(customThemesDir)) {
8386
7358
  return [];
8387
7359
  }
8388
7360
  const themes = [];
8389
7361
  try {
8390
- const files = readdirSync8(customThemesDir).filter((f) => f.endsWith(".json"));
7362
+ const files = readdirSync7(customThemesDir).filter((f) => f.endsWith(".json"));
8391
7363
  for (const file of files) {
8392
- const filePath = join31(customThemesDir, file);
7364
+ const filePath = join29(customThemesDir, file);
8393
7365
  try {
8394
- const data = JSON.parse(readFileSync22(filePath, "utf-8"));
7366
+ const data = JSON.parse(readFileSync20(filePath, "utf-8"));
8395
7367
  themes.push({
8396
7368
  id: data.id || file.replace(".json", ""),
8397
7369
  name: data.name || data.id,
@@ -8406,8 +7378,8 @@ function listCustomThemes(projectRoot) {
8406
7378
  return themes;
8407
7379
  }
8408
7380
  function deleteTheme(projectRoot, id) {
8409
- const themePath = join31(projectRoot, ".decantr", "custom", "themes", `${id}.json`);
8410
- if (!existsSync29(themePath)) {
7381
+ const themePath = join29(projectRoot, ".decantr", "custom", "themes", `${id}.json`);
7382
+ if (!existsSync27(themePath)) {
8411
7383
  return {
8412
7384
  success: false,
8413
7385
  error: `Theme "${id}" not found at ${themePath}`
@@ -8424,7 +7396,7 @@ function deleteTheme(projectRoot, id) {
8424
7396
  }
8425
7397
  }
8426
7398
  function importTheme(projectRoot, sourcePath) {
8427
- if (!existsSync29(sourcePath)) {
7399
+ if (!existsSync27(sourcePath)) {
8428
7400
  return {
8429
7401
  success: false,
8430
7402
  errors: [`Source file not found: ${sourcePath}`]
@@ -8432,7 +7404,7 @@ function importTheme(projectRoot, sourcePath) {
8432
7404
  }
8433
7405
  let theme;
8434
7406
  try {
8435
- theme = JSON.parse(readFileSync22(sourcePath, "utf-8"));
7407
+ theme = JSON.parse(readFileSync20(sourcePath, "utf-8"));
8436
7408
  } catch (e) {
8437
7409
  return {
8438
7410
  success: false,
@@ -8448,14 +7420,14 @@ function importTheme(projectRoot, sourcePath) {
8448
7420
  }
8449
7421
  theme.source = "custom";
8450
7422
  const id = theme.id;
8451
- const customThemesDir = join31(projectRoot, ".decantr", "custom", "themes");
8452
- const destPath = join31(customThemesDir, `${id}.json`);
8453
- mkdirSync16(customThemesDir, { recursive: true });
8454
- const howToPath = join31(customThemesDir, "how-to-theme.md");
8455
- if (!existsSync29(howToPath)) {
8456
- writeFileSync19(howToPath, getHowToThemeDoc());
8457
- }
8458
- writeFileSync19(destPath, JSON.stringify(theme, null, 2));
7423
+ const customThemesDir = join29(projectRoot, ".decantr", "custom", "themes");
7424
+ const destPath = join29(customThemesDir, `${id}.json`);
7425
+ mkdirSync14(customThemesDir, { recursive: true });
7426
+ const howToPath = join29(customThemesDir, "how-to-theme.md");
7427
+ if (!existsSync27(howToPath)) {
7428
+ writeFileSync17(howToPath, getHowToThemeDoc());
7429
+ }
7430
+ writeFileSync17(destPath, JSON.stringify(theme, null, 2));
8459
7431
  return {
8460
7432
  success: true,
8461
7433
  path: destPath
@@ -9002,18 +7974,18 @@ function extractHostedAssetPaths(indexHtml) {
9002
7974
  return [...assetPaths];
9003
7975
  }
9004
7976
  function readHostedDistSnapshot(distPath) {
9005
- const resolvedDistPath = distPath ? resolveUserPath(distPath) : join32(process.cwd(), "dist");
9006
- const indexPath = join32(resolvedDistPath, "index.html");
9007
- if (!existsSync30(indexPath)) {
7977
+ const resolvedDistPath = distPath ? resolveUserPath(distPath) : join30(process.cwd(), "dist");
7978
+ const indexPath = join30(resolvedDistPath, "index.html");
7979
+ if (!existsSync28(indexPath)) {
9008
7980
  return void 0;
9009
7981
  }
9010
- const indexHtml = readFileSync23(indexPath, "utf-8");
7982
+ const indexHtml = readFileSync21(indexPath, "utf-8");
9011
7983
  const assetPaths = extractHostedAssetPaths(indexHtml);
9012
7984
  const assets = {};
9013
7985
  for (const assetPath of assetPaths) {
9014
- const assetFilePath = join32(resolvedDistPath, assetPath.replace(/^[/\\]+/, ""));
9015
- if (existsSync30(assetFilePath)) {
9016
- assets[assetPath] = readFileSync23(assetFilePath, "utf-8");
7986
+ const assetFilePath = join30(resolvedDistPath, assetPath.replace(/^[/\\]+/, ""));
7987
+ if (existsSync28(assetFilePath)) {
7988
+ assets[assetPath] = readFileSync21(assetFilePath, "utf-8");
9017
7989
  }
9018
7990
  }
9019
7991
  return {
@@ -9028,7 +8000,7 @@ function isHostedSourceSnapshotFile(path) {
9028
8000
  function readHostedSourceSnapshot(sourcePath) {
9029
8001
  if (!sourcePath) return void 0;
9030
8002
  const resolvedSourcePath = resolveUserPath(sourcePath);
9031
- if (!existsSync30(resolvedSourcePath)) {
8003
+ if (!existsSync28(resolvedSourcePath)) {
9032
8004
  return void 0;
9033
8005
  }
9034
8006
  const files = {};
@@ -9040,19 +8012,19 @@ function readHostedSourceSnapshot(sourcePath) {
9040
8012
  "build",
9041
8013
  "coverage"
9042
8014
  ]);
9043
- const rootPrefix = basename3(resolvedSourcePath);
8015
+ const rootPrefix = basename2(resolvedSourcePath);
9044
8016
  const walk = (absoluteDir, relativeDir) => {
9045
- for (const entry of readdirSync9(absoluteDir, { withFileTypes: true })) {
8017
+ for (const entry of readdirSync8(absoluteDir, { withFileTypes: true })) {
9046
8018
  if (ignoredDirNames.has(entry.name)) continue;
9047
- const absolutePath = join32(absoluteDir, entry.name);
9048
- const relativePath = join32(relativeDir, entry.name).replace(/\\/g, "/");
8019
+ const absolutePath = join30(absoluteDir, entry.name);
8020
+ const relativePath = join30(relativeDir, entry.name).replace(/\\/g, "/");
9049
8021
  if (entry.isDirectory()) {
9050
8022
  walk(absolutePath, relativePath);
9051
8023
  continue;
9052
8024
  }
9053
8025
  if (!entry.isFile()) continue;
9054
8026
  if (!isHostedSourceSnapshotFile(relativePath)) continue;
9055
- files[relativePath] = readFileSync23(absolutePath, "utf-8");
8027
+ files[relativePath] = readFileSync21(absolutePath, "utf-8");
9056
8028
  }
9057
8029
  };
9058
8030
  walk(resolvedSourcePath, rootPrefix);
@@ -9245,25 +8217,25 @@ async function printHostedExecutionPackBundle(essencePath, namespace, jsonOutput
9245
8217
  }
9246
8218
  async function compileHostedExecutionPackBundle(essencePath, namespace) {
9247
8219
  const client = getPublicAPIClient();
9248
- const resolvedPath = essencePath ? resolveUserPath(essencePath) : join32(process.cwd(), "decantr.essence.json");
9249
- if (!existsSync30(resolvedPath)) {
8220
+ const resolvedPath = essencePath ? resolveUserPath(essencePath) : join30(process.cwd(), "decantr.essence.json");
8221
+ if (!existsSync28(resolvedPath)) {
9250
8222
  throw new Error(`Essence file not found at ${resolvedPath}`);
9251
8223
  }
9252
- const essence = JSON.parse(readFileSync23(resolvedPath, "utf-8"));
8224
+ const essence = JSON.parse(readFileSync21(resolvedPath, "utf-8"));
9253
8225
  const bundle = await client.compileExecutionPacks(essence, namespace ? { namespace } : void 0);
9254
- const contextDir = join32(dirname6(resolvedPath), ".decantr", "context");
8226
+ const contextDir = join30(dirname6(resolvedPath), ".decantr", "context");
9255
8227
  return { resolvedPath, bundle, contextDir };
9256
8228
  }
9257
8229
  function writeHostedExecutionPackContextArtifacts(contextDir, bundle) {
9258
- mkdirSync17(contextDir, { recursive: true });
8230
+ mkdirSync15(contextDir, { recursive: true });
9259
8231
  return writeExecutionPackBundleArtifacts(contextDir, bundle);
9260
8232
  }
9261
8233
  function resolvePagePackIdForRoute(essencePath, route) {
9262
- if (!existsSync30(essencePath)) {
8234
+ if (!existsSync28(essencePath)) {
9263
8235
  throw new Error(`Essence file not found at ${essencePath}`);
9264
8236
  }
9265
- const essence = JSON.parse(readFileSync23(essencePath, "utf-8"));
9266
- if (!isV49(essence)) {
8237
+ const essence = JSON.parse(readFileSync21(essencePath, "utf-8"));
8238
+ if (!isV48(essence)) {
9267
8239
  throw new Error("Route-based pack resolution requires Essence v4.0.0.");
9268
8240
  }
9269
8241
  const target = essence.blueprint.routes?.[route];
@@ -9277,14 +8249,14 @@ function resolvePagePackIdForRoute(essencePath, route) {
9277
8249
  }
9278
8250
  async function printHostedSelectedExecutionPack(packType, id, essencePath, namespace, jsonOutput = false, writeContext = false) {
9279
8251
  const client = getPublicAPIClient();
9280
- const resolvedPath = essencePath ? resolveUserPath(essencePath) : join32(process.cwd(), "decantr.essence.json");
9281
- if (!existsSync30(resolvedPath)) {
8252
+ const resolvedPath = essencePath ? resolveUserPath(essencePath) : join30(process.cwd(), "decantr.essence.json");
8253
+ if (!existsSync28(resolvedPath)) {
9282
8254
  throw new Error(`Essence file not found at ${resolvedPath}`);
9283
8255
  }
9284
8256
  if ((packType === "section" || packType === "page" || packType === "mutation") && !id) {
9285
8257
  throw new Error(`Pack type "${packType}" requires an id.`);
9286
8258
  }
9287
- const essence = JSON.parse(readFileSync23(resolvedPath, "utf-8"));
8259
+ const essence = JSON.parse(readFileSync21(resolvedPath, "utf-8"));
9288
8260
  const selected = await client.selectExecutionPack(
9289
8261
  {
9290
8262
  essence,
@@ -9295,17 +8267,17 @@ async function printHostedSelectedExecutionPack(packType, id, essencePath, names
9295
8267
  );
9296
8268
  let writtenContextDir = null;
9297
8269
  if (writeContext) {
9298
- const contextDir = join32(dirname6(resolvedPath), ".decantr", "context");
9299
- mkdirSync17(contextDir, { recursive: true });
9300
- writeFileSync20(
9301
- join32(contextDir, "pack-manifest.json"),
8270
+ const contextDir = join30(dirname6(resolvedPath), ".decantr", "context");
8271
+ mkdirSync15(contextDir, { recursive: true });
8272
+ writeFileSync18(
8273
+ join30(contextDir, "pack-manifest.json"),
9302
8274
  JSON.stringify(selected.manifest, null, 2) + "\n"
9303
8275
  );
9304
8276
  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);
9305
8277
  const markdownFile = manifestEntry?.markdown ?? `${selected.selector.packType}${selected.selector.id ? `-${selected.selector.id}` : ""}-pack.md`;
9306
8278
  const jsonFile = manifestEntry?.json ?? `${selected.selector.packType}${selected.selector.id ? `-${selected.selector.id}` : ""}-pack.json`;
9307
- writeFileSync20(join32(contextDir, markdownFile), selected.pack.renderedMarkdown);
9308
- writeFileSync20(join32(contextDir, jsonFile), JSON.stringify(selected.pack, null, 2) + "\n");
8279
+ writeFileSync18(join30(contextDir, markdownFile), selected.pack.renderedMarkdown);
8280
+ writeFileSync18(join30(contextDir, jsonFile), JSON.stringify(selected.pack, null, 2) + "\n");
9309
8281
  writtenContextDir = contextDir;
9310
8282
  }
9311
8283
  if (jsonOutput) {
@@ -9330,20 +8302,20 @@ async function printHostedSelectedExecutionPack(packType, id, essencePath, names
9330
8302
  }
9331
8303
  async function printHostedExecutionPackManifest(essencePath, namespace, jsonOutput = false, writeContext = false) {
9332
8304
  const client = getPublicAPIClient();
9333
- const resolvedPath = essencePath ? resolveUserPath(essencePath) : join32(process.cwd(), "decantr.essence.json");
9334
- if (!existsSync30(resolvedPath)) {
8305
+ const resolvedPath = essencePath ? resolveUserPath(essencePath) : join30(process.cwd(), "decantr.essence.json");
8306
+ if (!existsSync28(resolvedPath)) {
9335
8307
  throw new Error(`Essence file not found at ${resolvedPath}`);
9336
8308
  }
9337
- const essence = JSON.parse(readFileSync23(resolvedPath, "utf-8"));
8309
+ const essence = JSON.parse(readFileSync21(resolvedPath, "utf-8"));
9338
8310
  const manifest = await client.getExecutionPackManifest(
9339
8311
  essence,
9340
8312
  namespace ? { namespace } : void 0
9341
8313
  );
9342
8314
  let writtenContextDir = null;
9343
8315
  if (writeContext) {
9344
- const contextDir = join32(dirname6(resolvedPath), ".decantr", "context");
9345
- mkdirSync17(contextDir, { recursive: true });
9346
- writeFileSync20(join32(contextDir, "pack-manifest.json"), JSON.stringify(manifest, null, 2) + "\n");
8316
+ const contextDir = join30(dirname6(resolvedPath), ".decantr", "context");
8317
+ mkdirSync15(contextDir, { recursive: true });
8318
+ writeFileSync18(join30(contextDir, "pack-manifest.json"), JSON.stringify(manifest, null, 2) + "\n");
9347
8319
  writtenContextDir = contextDir;
9348
8320
  }
9349
8321
  if (jsonOutput) {
@@ -9364,14 +8336,14 @@ async function printHostedExecutionPackManifest(essencePath, namespace, jsonOutp
9364
8336
  }
9365
8337
  }
9366
8338
  async function hydrateHostedExecutionPacksIfMissing(projectRoot, namespace = "@official") {
9367
- const contextDir = join32(projectRoot, ".decantr", "context");
9368
- const reviewPackPath = join32(contextDir, "review-pack.json");
9369
- const manifestPath = join32(contextDir, "pack-manifest.json");
9370
- if (existsSync30(reviewPackPath) && existsSync30(manifestPath)) {
8339
+ const contextDir = join30(projectRoot, ".decantr", "context");
8340
+ const reviewPackPath = join30(contextDir, "review-pack.json");
8341
+ const manifestPath = join30(contextDir, "pack-manifest.json");
8342
+ if (existsSync28(reviewPackPath) && existsSync28(manifestPath)) {
9371
8343
  return { attempted: false, hydrated: false };
9372
8344
  }
9373
- const essencePath = join32(projectRoot, "decantr.essence.json");
9374
- if (!existsSync30(essencePath)) {
8345
+ const essencePath = join30(projectRoot, "decantr.essence.json");
8346
+ if (!existsSync28(essencePath)) {
9375
8347
  return { attempted: false, hydrated: false };
9376
8348
  }
9377
8349
  const reviewHydration = await hydrateHostedReviewPackIfMissing(projectRoot, namespace);
@@ -9380,9 +8352,9 @@ async function hydrateHostedExecutionPacksIfMissing(projectRoot, namespace = "@o
9380
8352
  }
9381
8353
  try {
9382
8354
  const client = getPublicAPIClient();
9383
- const essence = JSON.parse(readFileSync23(essencePath, "utf-8"));
8355
+ const essence = JSON.parse(readFileSync21(essencePath, "utf-8"));
9384
8356
  const bundle = await client.compileExecutionPacks(essence, { namespace });
9385
- mkdirSync17(contextDir, { recursive: true });
8357
+ mkdirSync15(contextDir, { recursive: true });
9386
8358
  writeExecutionPackBundleArtifacts(contextDir, bundle);
9387
8359
  return { attempted: true, hydrated: true, scope: "bundle" };
9388
8360
  } catch {
@@ -9390,19 +8362,19 @@ async function hydrateHostedExecutionPacksIfMissing(projectRoot, namespace = "@o
9390
8362
  }
9391
8363
  }
9392
8364
  async function hydrateHostedReviewPackIfMissing(projectRoot, namespace = "@official") {
9393
- const contextDir = join32(projectRoot, ".decantr", "context");
9394
- const reviewPackPath = join32(contextDir, "review-pack.json");
9395
- const manifestPath = join32(contextDir, "pack-manifest.json");
9396
- if (existsSync30(reviewPackPath) && existsSync30(manifestPath)) {
8365
+ const contextDir = join30(projectRoot, ".decantr", "context");
8366
+ const reviewPackPath = join30(contextDir, "review-pack.json");
8367
+ const manifestPath = join30(contextDir, "pack-manifest.json");
8368
+ if (existsSync28(reviewPackPath) && existsSync28(manifestPath)) {
9397
8369
  return { attempted: false, hydrated: false };
9398
8370
  }
9399
- const essencePath = join32(projectRoot, "decantr.essence.json");
9400
- if (!existsSync30(essencePath)) {
8371
+ const essencePath = join30(projectRoot, "decantr.essence.json");
8372
+ if (!existsSync28(essencePath)) {
9401
8373
  return { attempted: false, hydrated: false };
9402
8374
  }
9403
8375
  try {
9404
8376
  const client = getPublicAPIClient();
9405
- const essence = JSON.parse(readFileSync23(essencePath, "utf-8"));
8377
+ const essence = JSON.parse(readFileSync21(essencePath, "utf-8"));
9406
8378
  const selected = await client.selectExecutionPack(
9407
8379
  {
9408
8380
  essence,
@@ -9410,14 +8382,14 @@ async function hydrateHostedReviewPackIfMissing(projectRoot, namespace = "@offic
9410
8382
  },
9411
8383
  { namespace }
9412
8384
  );
9413
- mkdirSync17(contextDir, { recursive: true });
9414
- writeFileSync20(join32(contextDir, "review-pack.md"), selected.pack.renderedMarkdown);
9415
- writeFileSync20(
9416
- join32(contextDir, "review-pack.json"),
8385
+ mkdirSync15(contextDir, { recursive: true });
8386
+ writeFileSync18(join30(contextDir, "review-pack.md"), selected.pack.renderedMarkdown);
8387
+ writeFileSync18(
8388
+ join30(contextDir, "review-pack.json"),
9417
8389
  JSON.stringify(selected.pack, null, 2) + "\n"
9418
8390
  );
9419
- if (!existsSync30(manifestPath)) {
9420
- writeFileSync20(manifestPath, JSON.stringify(selected.manifest, null, 2) + "\n");
8391
+ if (!existsSync28(manifestPath)) {
8392
+ writeFileSync18(manifestPath, JSON.stringify(selected.manifest, null, 2) + "\n");
9421
8393
  }
9422
8394
  return { attempted: true, hydrated: true, scope: "review" };
9423
8395
  } catch {
@@ -9427,17 +8399,17 @@ async function hydrateHostedReviewPackIfMissing(projectRoot, namespace = "@offic
9427
8399
  async function printHostedFileCritique(sourcePath, namespace, jsonOutput = false, essencePath, treatmentsPath) {
9428
8400
  const client = getPublicAPIClient();
9429
8401
  const resolvedSourcePath = resolveUserPath(sourcePath);
9430
- const resolvedEssencePath = essencePath ? resolveUserPath(essencePath) : join32(process.cwd(), "decantr.essence.json");
9431
- const resolvedTreatmentsPath = treatmentsPath ? resolveUserPath(treatmentsPath) : join32(process.cwd(), "src", "styles", "treatments.css");
9432
- if (!existsSync30(resolvedSourcePath)) {
8402
+ const resolvedEssencePath = essencePath ? resolveUserPath(essencePath) : join30(process.cwd(), "decantr.essence.json");
8403
+ const resolvedTreatmentsPath = treatmentsPath ? resolveUserPath(treatmentsPath) : join30(process.cwd(), "src", "styles", "treatments.css");
8404
+ if (!existsSync28(resolvedSourcePath)) {
9433
8405
  throw new Error(`Source file not found at ${resolvedSourcePath}`);
9434
8406
  }
9435
- if (!existsSync30(resolvedEssencePath)) {
8407
+ if (!existsSync28(resolvedEssencePath)) {
9436
8408
  throw new Error(`Essence file not found at ${resolvedEssencePath}`);
9437
8409
  }
9438
- const code = readFileSync23(resolvedSourcePath, "utf-8");
9439
- const essence = JSON.parse(readFileSync23(resolvedEssencePath, "utf-8"));
9440
- const treatmentsCss = existsSync30(resolvedTreatmentsPath) ? readFileSync23(resolvedTreatmentsPath, "utf-8") : void 0;
8410
+ const code = readFileSync21(resolvedSourcePath, "utf-8");
8411
+ const essence = JSON.parse(readFileSync21(resolvedEssencePath, "utf-8"));
8412
+ const treatmentsCss = existsSync28(resolvedTreatmentsPath) ? readFileSync21(resolvedTreatmentsPath, "utf-8") : void 0;
9441
8413
  const report = await client.critiqueFile(
9442
8414
  {
9443
8415
  essence,
@@ -9461,11 +8433,11 @@ async function printHostedFileCritique(sourcePath, namespace, jsonOutput = false
9461
8433
  }
9462
8434
  async function printHostedProjectAudit(namespace, jsonOutput = false, essencePath, distPath, sourcesPath) {
9463
8435
  const client = getPublicAPIClient();
9464
- const resolvedEssencePath = essencePath ? resolveUserPath(essencePath) : join32(process.cwd(), "decantr.essence.json");
9465
- if (!existsSync30(resolvedEssencePath)) {
8436
+ const resolvedEssencePath = essencePath ? resolveUserPath(essencePath) : join30(process.cwd(), "decantr.essence.json");
8437
+ if (!existsSync28(resolvedEssencePath)) {
9466
8438
  throw new Error(`Essence file not found at ${resolvedEssencePath}`);
9467
8439
  }
9468
- const essence = JSON.parse(readFileSync23(resolvedEssencePath, "utf-8"));
8440
+ const essence = JSON.parse(readFileSync21(resolvedEssencePath, "utf-8"));
9469
8441
  const dist = readHostedDistSnapshot(distPath);
9470
8442
  const sources = readHostedSourceSnapshot(sourcesPath);
9471
8443
  const report = await client.auditProject(
@@ -9483,7 +8455,7 @@ async function printHostedProjectAudit(namespace, jsonOutput = false, essencePat
9483
8455
  console.log(heading2("Hosted Project Audit"));
9484
8456
  console.log(` Essence: ${resolvedEssencePath}`);
9485
8457
  console.log(
9486
- ` Dist snapshot: ${dist ? distPath ? resolveUserPath(distPath) : join32(process.cwd(), "dist") : "none"}`
8458
+ ` Dist snapshot: ${dist ? distPath ? resolveUserPath(distPath) : join30(process.cwd(), "dist") : "none"}`
9487
8459
  );
9488
8460
  console.log(
9489
8461
  ` Source snapshot: ${sources && sourcesPath ? resolveUserPath(sourcesPath) : "none"}`
@@ -9605,21 +8577,21 @@ function findPatternCandidateBySlug(candidates, slug) {
9605
8577
  function readSuggestCodeContext(projectRoot, route, file) {
9606
8578
  const pieces = [];
9607
8579
  if (file) {
9608
- const resolved = isAbsolute3(file) ? file : join32(projectRoot, file);
9609
- if (existsSync30(resolved)) {
9610
- pieces.push(readFileSync23(resolved, "utf-8"));
8580
+ const resolved = isAbsolute3(file) ? file : join30(projectRoot, file);
8581
+ if (existsSync28(resolved)) {
8582
+ pieces.push(readFileSync21(resolved, "utf-8"));
9611
8583
  }
9612
8584
  }
9613
8585
  if (route) {
9614
- const analysisPath = join32(projectRoot, ".decantr", "analysis.json");
9615
- if (existsSync30(analysisPath)) {
8586
+ const analysisPath = join30(projectRoot, ".decantr", "analysis.json");
8587
+ if (existsSync28(analysisPath)) {
9616
8588
  try {
9617
- const analysis = JSON.parse(readFileSync23(analysisPath, "utf-8"));
8589
+ const analysis = JSON.parse(readFileSync21(analysisPath, "utf-8"));
9618
8590
  const routeEntry = analysis.routes?.routes?.find((entry) => entry.path === route);
9619
8591
  if (routeEntry?.file) {
9620
- const resolved = join32(projectRoot, routeEntry.file);
9621
- if (existsSync30(resolved)) {
9622
- pieces.push(readFileSync23(resolved, "utf-8"));
8592
+ const resolved = join30(projectRoot, routeEntry.file);
8593
+ if (existsSync28(resolved)) {
8594
+ pieces.push(readFileSync21(resolved, "utf-8"));
9623
8595
  }
9624
8596
  }
9625
8597
  } catch {
@@ -9703,7 +8675,7 @@ async function cmdSuggest(query, options = {}) {
9703
8675
  }
9704
8676
  }
9705
8677
  const registryClient = new RegistryClient({
9706
- cacheDir: join32(projectRoot, ".decantr", "cache")
8678
+ cacheDir: join30(projectRoot, ".decantr", "cache")
9707
8679
  });
9708
8680
  const code = options.fromCode || options.file ? readSuggestCodeContext(projectRoot, options.route, options.file) : "";
9709
8681
  const localMatches = localPatternMatches(projectRoot, [query, code].filter(Boolean).join("\n"));
@@ -9790,7 +8762,7 @@ async function cmdGet(type, id) {
9790
8762
  }
9791
8763
  const apiType = CONTENT_TYPE_TO_API_CONTENT_TYPE3[type];
9792
8764
  const registryClient = new RegistryClient({
9793
- cacheDir: join32(process.cwd(), ".decantr", "cache")
8765
+ cacheDir: join30(process.cwd(), ".decantr", "cache")
9794
8766
  });
9795
8767
  const result = await registryClient.fetchContentItem(apiType, id);
9796
8768
  if (result) {
@@ -9807,10 +8779,10 @@ async function cmdGet(type, id) {
9807
8779
  return;
9808
8780
  }
9809
8781
  async function cmdValidate(path) {
9810
- const essencePath = path || join32(process.cwd(), "decantr.essence.json");
8782
+ const essencePath = path || join30(process.cwd(), "decantr.essence.json");
9811
8783
  let raw;
9812
8784
  try {
9813
- raw = readFileSync23(essencePath, "utf-8");
8785
+ raw = readFileSync21(essencePath, "utf-8");
9814
8786
  } catch {
9815
8787
  console.error(error2(`Could not read ${essencePath}`));
9816
8788
  process.exitCode = 1;
@@ -9824,7 +8796,7 @@ async function cmdValidate(path) {
9824
8796
  process.exitCode = 1;
9825
8797
  return;
9826
8798
  }
9827
- const detectedVersion = isV49(essence) ? "v4" : "legacy";
8799
+ const detectedVersion = isV48(essence) ? "v4" : "legacy";
9828
8800
  console.log(`${DIM16}Detected essence version: ${detectedVersion}${RESET16}`);
9829
8801
  const result = validateEssence2(essence);
9830
8802
  if (result.valid) {
@@ -9877,7 +8849,7 @@ async function cmdList(type, sort, recommended, intelligenceSource, blueprintSet
9877
8849
  return;
9878
8850
  }
9879
8851
  const registryClient = new RegistryClient({
9880
- cacheDir: join32(process.cwd(), ".decantr", "cache")
8852
+ cacheDir: join30(process.cwd(), ".decantr", "cache")
9881
8853
  });
9882
8854
  const result = await registryClient.fetchContentList(
9883
8855
  type,
@@ -9960,10 +8932,10 @@ ${CYAN9}Telemetry enabled.${RESET16} Decantr will send privacy-filtered CLI prod
9960
8932
  }
9961
8933
  function readCliPackageVersion2() {
9962
8934
  const here = dirname6(fileURLToPath3(import.meta.url));
9963
- const candidates = [join32(here, "..", "package.json"), join32(here, "..", "..", "package.json")];
8935
+ const candidates = [join30(here, "..", "package.json"), join30(here, "..", "..", "package.json")];
9964
8936
  for (const candidate of candidates) {
9965
8937
  try {
9966
- const pkg = JSON.parse(readFileSync23(candidate, "utf-8"));
8938
+ const pkg = JSON.parse(readFileSync21(candidate, "utf-8"));
9967
8939
  if (pkg.version) return pkg.version;
9968
8940
  } catch {
9969
8941
  }
@@ -9974,19 +8946,19 @@ function timestampForFile() {
9974
8946
  return (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
9975
8947
  }
9976
8948
  function backupExistingEssence(projectRoot, label) {
9977
- const essencePath = join32(projectRoot, "decantr.essence.json");
9978
- if (!existsSync30(essencePath)) return null;
9979
- const backupPath = join32(
8949
+ const essencePath = join30(projectRoot, "decantr.essence.json");
8950
+ if (!existsSync28(essencePath)) return null;
8951
+ const backupPath = join30(
9980
8952
  projectRoot,
9981
8953
  `decantr.essence.${label}.${timestampForFile()}.backup.json`
9982
8954
  );
9983
- writeFileSync20(backupPath, readFileSync23(essencePath, "utf-8"), "utf-8");
8955
+ writeFileSync18(backupPath, readFileSync21(essencePath, "utf-8"), "utf-8");
9984
8956
  return backupPath;
9985
8957
  }
9986
8958
  function writeBrownfieldProjectJson(input) {
9987
- const decantrDir = join32(input.projectRoot, ".decantr");
9988
- mkdirSync17(join32(decantrDir, "context"), { recursive: true });
9989
- mkdirSync17(join32(decantrDir, "cache"), { recursive: true });
8959
+ const decantrDir = join30(input.projectRoot, ".decantr");
8960
+ mkdirSync15(join30(decantrDir, "context"), { recursive: true });
8961
+ mkdirSync15(join30(decantrDir, "cache"), { recursive: true });
9990
8962
  const now = (/* @__PURE__ */ new Date()).toISOString();
9991
8963
  const projectJson = {
9992
8964
  detected: {
@@ -10027,7 +8999,7 @@ function writeBrownfieldProjectJson(input) {
10027
8999
  }
10028
9000
  }
10029
9001
  };
10030
- writeFileSync20(join32(decantrDir, "project.json"), JSON.stringify(projectJson, null, 2) + "\n");
9002
+ writeFileSync18(join30(decantrDir, "project.json"), JSON.stringify(projectJson, null, 2) + "\n");
10031
9003
  }
10032
9004
  async function applyAcceptedBrownfieldProposal(input) {
10033
9005
  const proposal = readBrownfieldProposal(input.projectRoot);
@@ -10043,8 +9015,8 @@ async function applyAcceptedBrownfieldProposal(input) {
10043
9015
  process.exitCode = 1;
10044
9016
  return;
10045
9017
  }
10046
- const essencePath = join32(input.projectRoot, "decantr.essence.json");
10047
- const hasEssence = existsSync30(essencePath);
9018
+ const essencePath = join30(input.projectRoot, "decantr.essence.json");
9019
+ const hasEssence = existsSync28(essencePath);
10048
9020
  let essence;
10049
9021
  let backupPath = null;
10050
9022
  if (input.mode === "accept" && hasEssence) {
@@ -10058,8 +9030,8 @@ async function applyAcceptedBrownfieldProposal(input) {
10058
9030
  return;
10059
9031
  }
10060
9032
  if (input.mode === "merge" && hasEssence) {
10061
- const existing = JSON.parse(readFileSync23(essencePath, "utf-8"));
10062
- if (!isV49(existing)) {
9033
+ const existing = JSON.parse(readFileSync21(essencePath, "utf-8"));
9034
+ if (!isV48(existing)) {
10063
9035
  console.log(
10064
9036
  error2(
10065
9037
  "Existing essence is not v4. Run `decantr migrate --to v4` before merging a brownfield proposal."
@@ -10095,9 +9067,9 @@ async function applyAcceptedBrownfieldProposal(input) {
10095
9067
  assistantBridge: input.assistantBridge,
10096
9068
  mode: input.mode
10097
9069
  });
10098
- writeFileSync20(essencePath, JSON.stringify(essence, null, 2) + "\n", "utf-8");
9070
+ writeFileSync18(essencePath, JSON.stringify(essence, null, 2) + "\n", "utf-8");
10099
9071
  const registryClient = new RegistryClient({
10100
- cacheDir: join32(input.projectRoot, ".decantr", "cache"),
9072
+ cacheDir: join30(input.projectRoot, ".decantr", "cache"),
10101
9073
  offline: true,
10102
9074
  projectRoot: input.projectRoot
10103
9075
  });
@@ -10124,7 +9096,7 @@ async function applyAcceptedBrownfieldProposal(input) {
10124
9096
  }
10125
9097
  const appliedRuleFiles = input.assistantBridge === "apply" ? applyAssistantBridge(input.projectRoot, input.detected) : [];
10126
9098
  console.log(success3("\nBrownfield proposal accepted.\n"));
10127
- const projectLabel = input.workspaceInfo.appRoot !== input.workspaceInfo.workspaceRoot ? relative7(input.workspaceInfo.workspaceRoot, input.workspaceInfo.appRoot).replace(/\\/g, "/") : void 0;
9099
+ const projectLabel = input.workspaceInfo.appRoot !== input.workspaceInfo.workspaceRoot ? relative6(input.workspaceInfo.workspaceRoot, input.workspaceInfo.appRoot).replace(/\\/g, "/") : void 0;
10128
9100
  console.log(" Files created/updated:");
10129
9101
  console.log(
10130
9102
  ` ${cyan3(displayProjectPath(input.workspaceInfo, "decantr.essence.json"))} Observed brownfield contract`
@@ -10276,7 +9248,7 @@ async function cmdInit(args) {
10276
9248
  }
10277
9249
  }
10278
9250
  const registryClient = new RegistryClient({
10279
- cacheDir: join32(projectRoot, ".decantr", "cache"),
9251
+ cacheDir: join30(projectRoot, ".decantr", "cache"),
10280
9252
  apiUrl: args.registry,
10281
9253
  offline: args.offline,
10282
9254
  projectRoot
@@ -10608,7 +9580,7 @@ ${YELLOW12}You're offline. Scaffolding Decantr default.${RESET16}`);
10608
9580
  if (appliedRuleFiles.length > 0) {
10609
9581
  console.log(` ${dim3(`Rule bridge applied: ${appliedRuleFiles.join(", ")}`)}`);
10610
9582
  }
10611
- if (!existsSync30(join32(projectRoot, "package.json"))) {
9583
+ if (!existsSync28(join30(projectRoot, "package.json"))) {
10612
9584
  console.log("");
10613
9585
  console.log(
10614
9586
  dim3(` Note: ${cyan3("decantr init")} created Decantr contract/context files only.`)
@@ -10619,7 +9591,7 @@ ${YELLOW12}You're offline. Scaffolding Decantr default.${RESET16}`);
10619
9591
  )
10620
9592
  );
10621
9593
  }
10622
- const hasCompiledPacks = existsSync30(join32(projectRoot, ".decantr", "context", "scaffold-pack.md"));
9594
+ const hasCompiledPacks = existsSync28(join30(projectRoot, ".decantr", "context", "scaffold-pack.md"));
10623
9595
  console.log("");
10624
9596
  console.log(" Next steps:");
10625
9597
  if (hasCompiledPacks) {
@@ -10659,7 +9631,7 @@ ${YELLOW12}You're offline. Scaffolding Decantr default.${RESET16}`);
10659
9631
  console.log(` ${cyan3("decantr upgrade")} Update to latest patterns`);
10660
9632
  console.log(` ${cyan3("decantr check")} Detect drift issues`);
10661
9633
  console.log(` ${cyan3("decantr migrate --to v4")} Migrate older essence files to v4`);
10662
- const essenceContent = readFileSync23(result.essencePath, "utf-8");
9634
+ const essenceContent = readFileSync21(result.essencePath, "utf-8");
10663
9635
  const essence = JSON.parse(essenceContent);
10664
9636
  const validation = validateEssence2(essence);
10665
9637
  if (!validation.valid) {
@@ -10668,7 +9640,7 @@ Validation warnings: ${validation.errors.join(", ")}`));
10668
9640
  }
10669
9641
  console.log("");
10670
9642
  let promptPages;
10671
- if (isV49(essence)) {
9643
+ if (isV48(essence)) {
10672
9644
  const allPages = essence.blueprint.sections.flatMap(
10673
9645
  (s) => s.pages.map((p) => ({ ...p, _sectionId: s.id, _shell: s.shell }))
10674
9646
  );
@@ -10714,25 +9686,25 @@ Validation warnings: ${validation.errors.join(", ")}`));
10714
9686
  }
10715
9687
  }
10716
9688
  async function cmdStatus(projectRoot = process.cwd()) {
10717
- const essencePath = join32(projectRoot, "decantr.essence.json");
10718
- const projectJsonPath = join32(projectRoot, ".decantr", "project.json");
9689
+ const essencePath = join30(projectRoot, "decantr.essence.json");
9690
+ const projectJsonPath = join30(projectRoot, ".decantr", "project.json");
10719
9691
  console.log(heading2("Decantr Project Status"));
10720
- if (!existsSync30(essencePath)) {
9692
+ if (!existsSync28(essencePath)) {
10721
9693
  console.log(`${RED12}No decantr.essence.json found.${RESET16}`);
10722
9694
  console.log(dim3('Run "decantr init" to create one.'));
10723
9695
  return;
10724
9696
  }
10725
9697
  try {
10726
- const essence = JSON.parse(readFileSync23(essencePath, "utf-8"));
9698
+ const essence = JSON.parse(readFileSync21(essencePath, "utf-8"));
10727
9699
  const validation = validateEssence2(essence);
10728
- const essenceVersion = isV49(essence) ? "v4" : "legacy";
9700
+ const essenceVersion = isV48(essence) ? "v4" : "legacy";
10729
9701
  console.log(`${BOLD9}Essence:${RESET16}`);
10730
9702
  if (validation.valid) {
10731
9703
  console.log(` ${GREEN16}Valid${RESET16} (${essenceVersion})`);
10732
9704
  } else {
10733
9705
  console.log(` ${RED12}Invalid: ${validation.errors.join(", ")}${RESET16}`);
10734
9706
  }
10735
- if (isV49(essence)) {
9707
+ if (isV48(essence)) {
10736
9708
  const v4 = essence;
10737
9709
  const sections = v4.blueprint.sections;
10738
9710
  const flatPages = sections.flatMap((section) => section.pages ?? []);
@@ -10771,9 +9743,9 @@ async function cmdStatus(projectRoot = process.cwd()) {
10771
9743
  }
10772
9744
  console.log("");
10773
9745
  console.log(`${BOLD9}Sync Status:${RESET16}`);
10774
- if (existsSync30(projectJsonPath)) {
9746
+ if (existsSync28(projectJsonPath)) {
10775
9747
  try {
10776
- const projectJson = JSON.parse(readFileSync23(projectJsonPath, "utf-8"));
9748
+ const projectJson = JSON.parse(readFileSync21(projectJsonPath, "utf-8"));
10777
9749
  const syncStatus = projectJson.sync?.status || "unknown";
10778
9750
  const lastSync = projectJson.sync?.lastSync || "never";
10779
9751
  const source = projectJson.sync?.registrySource || "unknown";
@@ -10791,7 +9763,7 @@ async function cmdStatus(projectRoot = process.cwd()) {
10791
9763
  }
10792
9764
  async function cmdSync() {
10793
9765
  const projectRoot = process.cwd();
10794
- const cacheDir = join32(projectRoot, ".decantr", "cache");
9766
+ const cacheDir = join30(projectRoot, ".decantr", "cache");
10795
9767
  console.log(heading2("Syncing registry content..."));
10796
9768
  const result = await syncRegistry(cacheDir);
10797
9769
  if (result.synced.length > 0) {
@@ -10985,14 +9957,14 @@ ${BOLD9}Examples:${RESET16}
10985
9957
  process.exitCode = 1;
10986
9958
  return;
10987
9959
  }
10988
- const themePath = join32(projectRoot, ".decantr", "custom", "themes", `${name}.json`);
10989
- if (!existsSync30(themePath)) {
9960
+ const themePath = join30(projectRoot, ".decantr", "custom", "themes", `${name}.json`);
9961
+ if (!existsSync28(themePath)) {
10990
9962
  console.error(error2(`Theme "${name}" not found at ${themePath}`));
10991
9963
  process.exitCode = 1;
10992
9964
  return;
10993
9965
  }
10994
9966
  try {
10995
- const theme = JSON.parse(readFileSync23(themePath, "utf-8"));
9967
+ const theme = JSON.parse(readFileSync21(themePath, "utf-8"));
10996
9968
  const result = validateCustomTheme(theme);
10997
9969
  if (result.valid) {
10998
9970
  console.log(success3(`Custom theme "${name}" is valid`));
@@ -11124,13 +10096,42 @@ function withProject(command, projectArg) {
11124
10096
  return projectArg ? `${command} --project ${projectArg}` : command;
11125
10097
  }
11126
10098
  function displayProjectPath(workspaceInfo, projectPath) {
11127
- const absolutePath = join32(workspaceInfo.appRoot, projectPath);
11128
- const relativePath = relative7(workspaceInfo.cwd, absolutePath).replace(/\\/g, "/");
10099
+ const absolutePath = join30(workspaceInfo.appRoot, projectPath);
10100
+ const relativePath = relative6(workspaceInfo.cwd, absolutePath).replace(/\\/g, "/");
11129
10101
  if (relativePath && !relativePath.startsWith("..") && !isAbsolute3(relativePath)) {
11130
10102
  return relativePath;
11131
10103
  }
11132
10104
  return absolutePath;
11133
10105
  }
10106
+ function projectRelativeGraphPath(projectRoot, filePath) {
10107
+ const relativePath = relative6(projectRoot, isAbsolute3(filePath) ? filePath : resolve4(filePath));
10108
+ if (relativePath && !relativePath.startsWith("..") && !isAbsolute3(relativePath)) {
10109
+ return relativePath.replace(/\\/g, "/");
10110
+ }
10111
+ return null;
10112
+ }
10113
+ function graphSourceNodeIdForTaskFile(projectRoot, snapshot, filePath) {
10114
+ const trimmed = filePath.trim();
10115
+ if (!trimmed) return null;
10116
+ if (trimmed.startsWith("src:") && snapshot.nodes.some((node) => node.id === trimmed)) {
10117
+ return trimmed;
10118
+ }
10119
+ const candidates = /* @__PURE__ */ new Set();
10120
+ candidates.add(trimmed.replace(/\\/g, "/").replace(/^\.\//, ""));
10121
+ const cwdRelative = projectRelativeGraphPath(projectRoot, trimmed);
10122
+ if (cwdRelative) candidates.add(cwdRelative);
10123
+ const projectRelative = projectRelativeGraphPath(projectRoot, join30(projectRoot, trimmed));
10124
+ if (projectRelative) candidates.add(projectRelative);
10125
+ for (const candidate of candidates) {
10126
+ const nodeId = `src:${candidate}`;
10127
+ if (snapshot.nodes.some((node) => node.id === nodeId)) return nodeId;
10128
+ }
10129
+ return snapshot.nodes.find((node) => {
10130
+ if (node.type !== "SourceArtifact") return false;
10131
+ const path = graphPayloadString(node.payload, "path");
10132
+ return Boolean(path && (path === trimmed || candidates.has(path)));
10133
+ })?.id ?? null;
10134
+ }
11134
10135
  function stripProjectArgs(args, startIndex = 1) {
11135
10136
  const stripped = [args[0]];
11136
10137
  for (let index = startIndex; index < args.length; index += 1) {
@@ -11193,7 +10194,7 @@ function printWorkspaceProjectSelection(workspaceInfo, commandName = "command")
11193
10194
  function printMonorepoSetupGuidance(workspaceInfo) {
11194
10195
  const candidate = firstWorkspaceCandidate(workspaceInfo);
11195
10196
  const attachedProjects = workspaceInfo.appCandidates.filter(
11196
- (appCandidate) => existsSync30(join32(workspaceInfo.workspaceRoot, appCandidate, "decantr.essence.json"))
10197
+ (appCandidate) => existsSync28(join30(workspaceInfo.workspaceRoot, appCandidate, "decantr.essence.json"))
11197
10198
  );
11198
10199
  const firstAttached = attachedProjects[0];
11199
10200
  console.log(heading2("Decantr Setup"));
@@ -11261,7 +10262,7 @@ function printMonorepoSetupGuidance(workspaceInfo) {
11261
10262
  function resolveWorkflowProject(flags, commandName = "command", options = {}) {
11262
10263
  const projectArg = flagString(flags, "project");
11263
10264
  const workspaceInfo = resolveWorkspaceInfo(process.cwd(), projectArg);
11264
- if (projectArg && options.requireExisting !== false && !existsSync30(workspaceInfo.appRoot)) {
10265
+ if (projectArg && options.requireExisting !== false && !existsSync28(workspaceInfo.appRoot)) {
11265
10266
  printProjectNotFound(projectArg, commandName);
11266
10267
  process.exitCode = 1;
11267
10268
  return null;
@@ -11269,7 +10270,7 @@ function resolveWorkflowProject(flags, commandName = "command", options = {}) {
11269
10270
  if (projectArg && options.requireAppCandidate && workspaceInfo.appCandidates.length > 0) {
11270
10271
  const normalizedProject = normalizedProjectPath(projectArg);
11271
10272
  const normalizedWorkspaceProject = normalizedProjectPath(
11272
- relative7(workspaceInfo.workspaceRoot, workspaceInfo.appRoot)
10273
+ relative6(workspaceInfo.workspaceRoot, workspaceInfo.appRoot)
11273
10274
  );
11274
10275
  const knownCandidate = normalizedProject ? workspaceInfo.appCandidates.includes(normalizedProject) || Boolean(
11275
10276
  normalizedWorkspaceProject && workspaceInfo.appCandidates.includes(normalizedWorkspaceProject)
@@ -11318,6 +10319,129 @@ function formatScanApplicability(status) {
11318
10319
  if (status === "not_applicable") return dim3("not applicable");
11319
10320
  return dim3("unknown");
11320
10321
  }
10322
+ function formatScanGraphPreviewStatus(status) {
10323
+ if (status === "current") return success3("current");
10324
+ if (status === "stale") return `${YELLOW12}stale or missing${RESET16}`;
10325
+ if (status === "needs_migration") return `${YELLOW12}needs migration${RESET16}`;
10326
+ if (status === "not_attached") return dim3("not attached");
10327
+ return dim3("unavailable");
10328
+ }
10329
+ function relativeGraphArtifactPath(projectRoot, artifactPath) {
10330
+ return relative6(projectRoot, artifactPath).replace(/\\/g, "/");
10331
+ }
10332
+ function buildScanGraphPreview(workspaceInfo, projectArg) {
10333
+ let artifacts;
10334
+ try {
10335
+ artifacts = buildGraphArtifacts(workspaceInfo.appRoot);
10336
+ } catch (error3) {
10337
+ const message = error3.message;
10338
+ const needsMigration = message.includes("Essence v4");
10339
+ return {
10340
+ status: needsMigration ? "needs_migration" : "unavailable",
10341
+ canPreview: false,
10342
+ readOnly: true,
10343
+ message: needsMigration ? "Existing Decantr contract needs Essence v4 before a typed Contract graph can be previewed." : message,
10344
+ nextCommand: needsMigration ? withProject("decantr migrate --to v4", projectArg) : null,
10345
+ staleArtifacts: [],
10346
+ snapshot: null,
10347
+ capsule: null,
10348
+ diff: null
10349
+ };
10350
+ }
10351
+ if (!artifacts) {
10352
+ return {
10353
+ status: "not_attached",
10354
+ canPreview: false,
10355
+ readOnly: true,
10356
+ message: "No decantr.essence.json found. Adopt the project to create the first typed Contract graph baseline.",
10357
+ nextCommand: withProject("decantr adopt --yes", projectArg),
10358
+ staleArtifacts: [],
10359
+ snapshot: null,
10360
+ capsule: null,
10361
+ diff: null
10362
+ };
10363
+ }
10364
+ const stale = artifacts.staleArtifacts.length > 0;
10365
+ const diffSummary = summarizeGraphDiff(artifacts.diff);
10366
+ return {
10367
+ status: stale ? "stale" : "current",
10368
+ canPreview: true,
10369
+ readOnly: true,
10370
+ message: stale ? "A typed Contract graph can be derived now, but saved graph artifacts are missing or stale." : "Typed Contract graph artifacts are current with the project-owned contract sources.",
10371
+ nextCommand: stale ? withProject("decantr graph", projectArg) : null,
10372
+ staleArtifacts: artifacts.staleArtifacts.map(
10373
+ (path) => displayProjectPath(workspaceInfo, relativeGraphArtifactPath(artifacts.projectRoot, path))
10374
+ ),
10375
+ snapshot: {
10376
+ id: artifacts.snapshot.id,
10377
+ schemaVersion: artifacts.snapshot.schema_version,
10378
+ sourceHash: artifacts.snapshot.source_hash,
10379
+ nodes: artifacts.snapshot.summary.nodes,
10380
+ edges: artifacts.snapshot.summary.edges,
10381
+ findings: artifacts.snapshot.summary.findings,
10382
+ evidence: artifacts.snapshot.summary.evidence,
10383
+ sourceArtifacts: artifacts.snapshot.nodes.filter((node) => node.type === "SourceArtifact").length
10384
+ },
10385
+ capsule: {
10386
+ cacheKey: artifacts.capsule.cache_key,
10387
+ routes: artifacts.capsule.summary.routes,
10388
+ components: artifacts.capsule.summary.components,
10389
+ tokens: artifacts.capsule.summary.tokens,
10390
+ localRules: artifacts.capsule.summary.local_rules,
10391
+ styleBridge: artifacts.capsule.summary.style_bridge,
10392
+ sourceArtifacts: artifacts.capsule.summary.source_artifacts,
10393
+ sourceArtifactLimit: artifacts.capsule.source_artifact_limit,
10394
+ sourceArtifactsTruncated: artifacts.capsule.source_artifacts_truncated,
10395
+ openFindings: artifacts.capsule.summary.open_findings
10396
+ },
10397
+ diff: {
10398
+ ops: diffSummary.total,
10399
+ findingsAdded: diffSummary.findings.added,
10400
+ findingsResolved: diffSummary.findings.resolved,
10401
+ evidenceAdded: diffSummary.evidence.added
10402
+ }
10403
+ };
10404
+ }
10405
+ function printScanGraphPreview(preview) {
10406
+ if (!preview) return;
10407
+ console.log(`${BOLD9}Typed Contract Graph${RESET16}`);
10408
+ console.log(` Status: ${formatScanGraphPreviewStatus(preview.status)}`);
10409
+ console.log(` Read-only: ${preview.readOnly ? "yes" : "no"}`);
10410
+ if (preview.snapshot && preview.capsule) {
10411
+ console.log(
10412
+ ` Snapshot: ${preview.snapshot.nodes} nodes, ${preview.snapshot.edges} edges`
10413
+ );
10414
+ console.log(
10415
+ ` Evidence: ${preview.snapshot.findings} finding nodes, ${preview.snapshot.evidence} evidence nodes, ${preview.snapshot.sourceArtifacts} sources`
10416
+ );
10417
+ console.log(
10418
+ ` Capsule: ${preview.capsule.routes} routes, ${preview.capsule.localRules} local rules, ${preview.capsule.styleBridge} style bridge mappings`
10419
+ );
10420
+ const sourceArtifactIndex = preview.capsule.sourceArtifactsTruncated ? `${preview.capsule.sourceArtifactLimit}/${preview.capsule.sourceArtifacts} source handles` : `${preview.capsule.sourceArtifacts} source handles`;
10421
+ console.log(` Source handles: ${sourceArtifactIndex}`);
10422
+ if (preview.diff) {
10423
+ const diffHints = [
10424
+ preview.diff.findingsAdded > 0 ? `${preview.diff.findingsAdded} finding added` : null,
10425
+ preview.diff.findingsResolved > 0 ? `${preview.diff.findingsResolved} finding resolved` : null,
10426
+ preview.diff.evidenceAdded > 0 ? `${preview.diff.evidenceAdded} evidence added` : null
10427
+ ].filter(Boolean);
10428
+ console.log(
10429
+ ` Diff: ${preview.diff.ops} ops${diffHints.length > 0 ? ` (${diffHints.join(", ")})` : ""}`
10430
+ );
10431
+ }
10432
+ }
10433
+ console.log(` ${dim3(preview.message)}`);
10434
+ if (preview.staleArtifacts.length > 0) {
10435
+ console.log(` Stale artifacts:${" "} ${preview.staleArtifacts.slice(0, 3).join(", ")}`);
10436
+ if (preview.staleArtifacts.length > 3) {
10437
+ console.log(` ${dim3(`...${preview.staleArtifacts.length - 3} more`)}`);
10438
+ }
10439
+ }
10440
+ if (preview.nextCommand) {
10441
+ console.log(` Next: ${cyan3(preview.nextCommand)}`);
10442
+ }
10443
+ console.log("");
10444
+ }
11321
10445
  function printScanReport(report) {
11322
10446
  console.log(heading2("Decantr Scan"));
11323
10447
  console.log(dim3("Read-only Brownfield reconnaissance. No files were written."));
@@ -11338,6 +10462,7 @@ function printScanReport(report) {
11338
10462
  console.log(` TypeScript: ${report.project.hasTypeScript ? "yes" : "no"}`);
11339
10463
  console.log(` Decantr: ${report.project.hasDecantr ? "present" : "not attached"}`);
11340
10464
  console.log("");
10465
+ printScanGraphPreview(report.graphPreview);
11341
10466
  console.log(`${BOLD9}Routes And Styling${RESET16}`);
11342
10467
  console.log(` Routes: ${report.routes.count} (${report.routes.strategy})`);
11343
10468
  for (const route of report.routes.items.slice(0, 8)) {
@@ -11395,11 +10520,13 @@ async function cmdScanWorkflow(args) {
11395
10520
  const report = await scanProjectReadOnly(workspaceInfo.appRoot, {
11396
10521
  input: { kind: "local", value: inputValue }
11397
10522
  });
10523
+ const graphPreview = buildScanGraphPreview(workspaceInfo, projectArg);
10524
+ const reportWithGraph = { ...report, graphPreview };
11398
10525
  if (jsonOutput) {
11399
- console.log(JSON.stringify(report, null, 2));
10526
+ console.log(JSON.stringify(reportWithGraph, null, 2));
11400
10527
  return;
11401
10528
  }
11402
- printScanReport(report);
10529
+ printScanReport(reportWithGraph);
11403
10530
  }
11404
10531
  async function cmdSetupWorkflow(args) {
11405
10532
  const { flags } = parseLooseArgs(args);
@@ -11421,9 +10548,9 @@ async function cmdSetupWorkflow(args) {
11421
10548
  console.log(` Detected: ${formatDetection(detected)}`);
11422
10549
  console.log("");
11423
10550
  if (detected.existingEssence) {
11424
- const hasLocalPatterns = existsSync30(localPatternsPath(workspaceInfo.appRoot));
11425
- const hasLocalRules = existsSync30(localRulesPath(workspaceInfo.appRoot));
11426
- const hasStyleBridge = existsSync30(styleBridgePath(workspaceInfo.appRoot));
10551
+ const hasLocalPatterns = existsSync28(localPatternsPath(workspaceInfo.appRoot));
10552
+ const hasLocalRules = existsSync28(localRulesPath(workspaceInfo.appRoot));
10553
+ const hasStyleBridge = existsSync28(styleBridgePath(workspaceInfo.appRoot));
11427
10554
  const verifyCommand = hasLocalPatterns || hasLocalRules ? "decantr verify --brownfield --local-patterns" : "decantr verify --brownfield";
11428
10555
  console.log(`${BOLD9}Recommended path:${RESET16} maintain an attached Decantr project`);
11429
10556
  console.log(
@@ -11509,7 +10636,7 @@ async function cmdAdoptWorkflow(args) {
11509
10636
  const hydratePacks = flagBoolean(flags, "packs", true) && !flagBoolean(flags, "skip-packs") && !flagBoolean(flags, "offline") && process.env.DECANTR_OFFLINE !== "true";
11510
10637
  const initCi = flagBoolean(flags, "ci") || flagBoolean(flags, "init-ci");
11511
10638
  const assistantBridge = flagString(flags, "assistant-bridge");
11512
- const hasEssence = existsSync30(join32(projectRoot, "decantr.essence.json"));
10639
+ const hasEssence = existsSync28(join30(projectRoot, "decantr.essence.json"));
11513
10640
  const proposalFlag = flagBoolean(flags, "replace-essence") ? "--replace-essence" : flagBoolean(flags, "merge-proposal") || hasEssence ? "--merge-proposal" : "--accept-proposal";
11514
10641
  const steps = [
11515
10642
  "analyze current app and write .decantr/brownfield intelligence",
@@ -11518,6 +10645,7 @@ async function cmdAdoptWorkflow(args) {
11518
10645
  if (hydratePacks) {
11519
10646
  steps.push("hydrate hosted execution packs into the app context");
11520
10647
  }
10648
+ steps.push("write typed Contract graph baseline");
11521
10649
  if (runVerify) {
11522
10650
  steps.push(
11523
10651
  runBrowser ? "verify with Project Health, browser evidence, visual manifest, and baseline" : "verify with Project Health and baseline"
@@ -11557,7 +10685,7 @@ async function cmdAdoptWorkflow(args) {
11557
10685
  if (hydratePacks) {
11558
10686
  try {
11559
10687
  const { bundle, contextDir } = await compileHostedExecutionPackBundle(
11560
- join32(projectRoot, "decantr.essence.json")
10688
+ join30(projectRoot, "decantr.essence.json")
11561
10689
  );
11562
10690
  const written = writeHostedExecutionPackContextArtifacts(
11563
10691
  contextDir,
@@ -11579,8 +10707,10 @@ async function cmdAdoptWorkflow(args) {
11579
10707
  } else if (flagBoolean(flags, "offline") || process.env.DECANTR_OFFLINE === "true") {
11580
10708
  console.log(dim3("Skipping hosted pack hydration in offline mode."));
11581
10709
  }
10710
+ await cmdGraph(projectRoot, { displayRoot: process.cwd() });
10711
+ if (process.exitCode && process.exitCode !== 0) return;
11582
10712
  if (runVerify) {
11583
- const { cmdHealth } = await import("./health-B4W7UJBZ.js");
10713
+ const { cmdHealth } = await import("./health-ASR7RWZJ.js");
11584
10714
  await cmdHealth(projectRoot, {
11585
10715
  browser: runBrowser,
11586
10716
  browserBaseUrl: baseUrl,
@@ -11651,7 +10781,7 @@ async function cmdVerifyWorkflow(args) {
11651
10781
  return;
11652
10782
  }
11653
10783
  if (workspaceMode) {
11654
- const { cmdWorkspace } = await import("./workspace-7RU77ZZW.js");
10784
+ const { cmdWorkspace } = await import("./workspace-2FFQEEQQ.js");
11655
10785
  await cmdWorkspace(process.cwd(), ["workspace", "health", ...withoutWorkflowOnlyFlags(args)]);
11656
10786
  return;
11657
10787
  }
@@ -11694,7 +10824,7 @@ async function cmdVerifyWorkflow(args) {
11694
10824
  process.exitCode = void 0;
11695
10825
  }
11696
10826
  }
11697
- const { cmdHealth, parseHealthArgs } = await import("./health-B4W7UJBZ.js");
10827
+ const { cmdHealth, parseHealthArgs } = await import("./health-ASR7RWZJ.js");
11698
10828
  await cmdHealth(workspaceInfo.appRoot, parseHealthArgs(healthArgs));
11699
10829
  if (localPatterns) {
11700
10830
  const validation = validateLocalLaw(workspaceInfo.appRoot);
@@ -11763,9 +10893,9 @@ async function cmdVerifyWorkflow(args) {
11763
10893
  }
11764
10894
  }
11765
10895
  function readJsonIfPresent(path) {
11766
- if (!existsSync30(path)) return null;
10896
+ if (!existsSync28(path)) return null;
11767
10897
  try {
11768
- return JSON.parse(readFileSync23(path, "utf-8"));
10898
+ return JSON.parse(readFileSync21(path, "utf-8"));
11769
10899
  } catch {
11770
10900
  return null;
11771
10901
  }
@@ -11863,7 +10993,7 @@ async function cmdTaskWorkflow(args) {
11863
10993
  }
11864
10994
  const route = routeInput.startsWith("/") ? routeInput : `/${routeInput}`;
11865
10995
  const taskSummary = positional.slice(1).join(" ").trim();
11866
- const essencePath = join32(workspaceInfo.appRoot, "decantr.essence.json");
10996
+ const essencePath = join30(workspaceInfo.appRoot, "decantr.essence.json");
11867
10997
  const essence = readJsonIfPresent(essencePath);
11868
10998
  if (!essence) {
11869
10999
  console.error(
@@ -11872,7 +11002,7 @@ async function cmdTaskWorkflow(args) {
11872
11002
  process.exitCode = 1;
11873
11003
  return;
11874
11004
  }
11875
- if (!isV49(essence)) {
11005
+ if (!isV48(essence)) {
11876
11006
  console.error(error2("Task context requires Essence v4. Run `decantr migrate --to v4` first."));
11877
11007
  process.exitCode = 1;
11878
11008
  return;
@@ -11887,16 +11017,22 @@ async function cmdTaskWorkflow(args) {
11887
11017
  }
11888
11018
  const section = essence.blueprint.sections.find((entry) => entry.id === target.section);
11889
11019
  const page = section?.pages.find((entry) => entry.id === target.page);
11890
- const contextDir = join32(workspaceInfo.appRoot, ".decantr", "context");
11891
- const manifest = readJsonIfPresent(join32(contextDir, "pack-manifest.json"));
11892
- const projectJson = readJsonIfPresent(join32(workspaceInfo.appRoot, ".decantr", "project.json"));
11020
+ const contextDir = join30(workspaceInfo.appRoot, ".decantr", "context");
11021
+ const manifest = readJsonIfPresent(join30(contextDir, "pack-manifest.json"));
11022
+ const projectJson = readJsonIfPresent(join30(workspaceInfo.appRoot, ".decantr", "project.json"));
11893
11023
  const pagePack = manifest?.pages?.find((entry) => entry.id === target.page);
11894
11024
  const sectionPack = manifest?.sections?.find((entry) => entry.id === target.section);
11895
- const visualManifest = readJsonIfPresent(join32(workspaceInfo.appRoot, ".decantr", "evidence", "visual-manifest.json"));
11025
+ const visualManifest = readJsonIfPresent(join30(workspaceInfo.appRoot, ".decantr", "evidence", "visual-manifest.json"));
11896
11026
  const screenshot = visualManifest?.routes?.find((entry) => entry.route === route)?.screenshot;
11897
11027
  const localPatternPackPath = localPatternsPath(workspaceInfo.appRoot);
11898
11028
  const localRuleManifestPath = localRulesPath(workspaceInfo.appRoot);
11899
11029
  const acceptedStyleBridgePath = styleBridgePath(workspaceInfo.appRoot);
11030
+ const graphDir = join30(workspaceInfo.appRoot, ".decantr", "graph");
11031
+ const contractCapsulePath = join30(graphDir, "contract-capsule.json");
11032
+ const graphSnapshotPath = join30(graphDir, "graph.snapshot.json");
11033
+ const contractCapsule = readJsonIfPresent(contractCapsulePath);
11034
+ const graphSnapshot = readJsonIfPresent(graphSnapshotPath);
11035
+ const routeGraphContext = buildGraphRouteContext(graphSnapshot, route, { task: taskSummary });
11900
11036
  const localLaw = createLocalLawTaskSummary(workspaceInfo.appRoot);
11901
11037
  const styleBridge = createStyleBridgeTaskSummary(workspaceInfo.appRoot);
11902
11038
  const displayedStyleBridge = {
@@ -11911,15 +11047,29 @@ async function cmdTaskWorkflow(args) {
11911
11047
  const changedSince = flagString(flags, "since");
11912
11048
  const currentChangedFiles = changedFiles(workspaceInfo.appRoot, changedSince);
11913
11049
  const changedRoutes = routeImpacts(workspaceInfo.appRoot, currentChangedFiles);
11050
+ const changedFileSourceNodes = graphSnapshot ? currentChangedFiles.map((file) => ({
11051
+ file,
11052
+ nodeId: graphSourceNodeIdForTaskFile(workspaceInfo.appRoot, graphSnapshot, file)
11053
+ })) : [];
11054
+ const changedFileSourceNodeIds = [
11055
+ ...new Set(
11056
+ changedFileSourceNodes.map((entry) => entry.nodeId).filter((nodeId) => Boolean(nodeId))
11057
+ )
11058
+ ];
11059
+ const changedFileMissingFiles = graphSnapshot ? changedFileSourceNodes.filter((entry) => !entry.nodeId).map((entry) => entry.file) : currentChangedFiles;
11060
+ const changedFileGraphContext = graphSnapshot && changedFileSourceNodeIds.length > 0 ? buildGraphImpactContext(graphSnapshot, changedFileSourceNodeIds, {
11061
+ task: taskSummary,
11062
+ limit: 120
11063
+ }) : null;
11914
11064
  const authority = createTaskAuthoritySummary({
11915
11065
  projectRoot: workspaceInfo.appRoot,
11916
11066
  workflowMode: projectJson?.initialized?.workflowMode ?? null,
11917
11067
  adoptionMode: projectJson?.initialized?.adoptionMode ?? null,
11918
- hasLocalPatterns: existsSync30(localPatternPackPath),
11919
- hasLocalRules: existsSync30(localRuleManifestPath),
11068
+ hasLocalPatterns: existsSync28(localPatternPackPath),
11069
+ hasLocalRules: existsSync28(localRuleManifestPath),
11920
11070
  hasPackManifest: Boolean(manifest),
11921
11071
  taskSummary,
11922
- hasStyleBridge: existsSync30(acceptedStyleBridgePath)
11072
+ hasStyleBridge: existsSync28(acceptedStyleBridgePath)
11923
11073
  });
11924
11074
  const context = {
11925
11075
  route,
@@ -11929,15 +11079,56 @@ async function cmdTaskWorkflow(args) {
11929
11079
  shell: page?.shell ?? section?.shell ?? null,
11930
11080
  patterns: page?.layout?.map(extractPatternName) ?? [],
11931
11081
  read: [
11932
- pagePack ? displayProjectPath(workspaceInfo, join32(".decantr/context", pagePack.markdown)) : null,
11933
- sectionPack ? displayProjectPath(workspaceInfo, join32(".decantr/context", sectionPack.markdown)) : null,
11934
- manifest?.scaffold?.markdown ? displayProjectPath(workspaceInfo, join32(".decantr/context", manifest.scaffold.markdown)) : null,
11082
+ pagePack ? displayProjectPath(workspaceInfo, join30(".decantr/context", pagePack.markdown)) : null,
11083
+ sectionPack ? displayProjectPath(workspaceInfo, join30(".decantr/context", sectionPack.markdown)) : null,
11084
+ manifest?.scaffold?.markdown ? displayProjectPath(workspaceInfo, join30(".decantr/context", manifest.scaffold.markdown)) : null,
11935
11085
  displayProjectPath(workspaceInfo, ".decantr/context/scaffold.md"),
11936
11086
  displayProjectPath(workspaceInfo, "DECANTR.md"),
11937
- existsSync30(localPatternPackPath) ? displayProjectPath(workspaceInfo, ".decantr/local-patterns.json") : null,
11938
- existsSync30(localRuleManifestPath) ? displayProjectPath(workspaceInfo, ".decantr/rules.json") : null,
11939
- existsSync30(acceptedStyleBridgePath) ? displayProjectPath(workspaceInfo, ".decantr/style-bridge.json") : null
11087
+ existsSync28(localPatternPackPath) ? displayProjectPath(workspaceInfo, ".decantr/local-patterns.json") : null,
11088
+ existsSync28(localRuleManifestPath) ? displayProjectPath(workspaceInfo, ".decantr/rules.json") : null,
11089
+ existsSync28(acceptedStyleBridgePath) ? displayProjectPath(workspaceInfo, ".decantr/style-bridge.json") : null,
11090
+ contractCapsule ? displayProjectPath(workspaceInfo, ".decantr/graph/contract-capsule.json") : null,
11091
+ routeGraphContext && !contractCapsule ? displayProjectPath(workspaceInfo, ".decantr/graph/graph.snapshot.json") : null
11940
11092
  ].filter(Boolean),
11093
+ graph: contractCapsule || graphSnapshot ? {
11094
+ capsule: contractCapsule ? {
11095
+ path: displayProjectPath(workspaceInfo, ".decantr/graph/contract-capsule.json"),
11096
+ snapshotId: contractCapsule.snapshot_id ?? null,
11097
+ cacheKey: contractCapsule.cache_key ?? null,
11098
+ contractHash: contractCapsule.contract_hash ?? null,
11099
+ contractCacheKey: contractCapsule.contract_cache_key ?? null,
11100
+ summary: contractCapsule.summary ?? null
11101
+ } : null,
11102
+ snapshot: graphSnapshot ? {
11103
+ path: displayProjectPath(workspaceInfo, ".decantr/graph/graph.snapshot.json"),
11104
+ id: graphSnapshot.id ?? null,
11105
+ sourceHash: graphSnapshot.source_hash ?? null,
11106
+ summary: graphSnapshot.summary ?? null
11107
+ } : null,
11108
+ routeContext: routeGraphContext ? {
11109
+ path: displayProjectPath(workspaceInfo, ".decantr/graph/graph.snapshot.json"),
11110
+ ...routeGraphContext
11111
+ } : null,
11112
+ changedFileContext: currentChangedFiles.length > 0 ? {
11113
+ path: displayProjectPath(
11114
+ workspaceInfo,
11115
+ ".decantr/graph/graph.snapshot.json"
11116
+ ),
11117
+ changedFiles: currentChangedFiles.slice(0, 40),
11118
+ resolvedNodeIds: changedFileSourceNodeIds,
11119
+ missingFiles: changedFileMissingFiles.slice(0, 40),
11120
+ impact: changedFileGraphContext ? {
11121
+ snapshotId: changedFileGraphContext.snapshotId,
11122
+ sourceHash: changedFileGraphContext.sourceHash,
11123
+ ranking: changedFileGraphContext.ranking,
11124
+ summary: changedFileGraphContext.summary,
11125
+ ids: changedFileGraphContext.ids,
11126
+ ranked: changedFileGraphContext.ranked.slice(0, 24),
11127
+ nodes: changedFileGraphContext.nodes,
11128
+ edges: changedFileGraphContext.edges
11129
+ } : null
11130
+ } : null
11131
+ } : null,
11941
11132
  screenshot: screenshot?.startsWith(".decantr/") ? displayProjectPath(workspaceInfo, screenshot) : screenshot ?? null,
11942
11133
  authority,
11943
11134
  localLaw: displayedLocalLaw,
@@ -11966,6 +11157,55 @@ async function cmdTaskWorkflow(args) {
11966
11157
  console.log(`${BOLD9}Visual evidence:${RESET16}`);
11967
11158
  console.log(` ${cyan3(context.screenshot)}`);
11968
11159
  }
11160
+ if (context.graph?.capsule || context.graph?.snapshot) {
11161
+ console.log("");
11162
+ console.log(`${BOLD9}Typed contract graph:${RESET16}`);
11163
+ if (context.graph.capsule) {
11164
+ console.log(` Capsule: ${cyan3(context.graph.capsule.path)}`);
11165
+ if (context.graph.capsule.cacheKey) {
11166
+ console.log(` Cache key: ${context.graph.capsule.cacheKey}`);
11167
+ }
11168
+ if (context.graph.capsule.contractCacheKey && context.graph.capsule.contractCacheKey !== context.graph.capsule.cacheKey) {
11169
+ console.log(` Contract cache key: ${context.graph.capsule.contractCacheKey}`);
11170
+ }
11171
+ }
11172
+ if (context.graph.snapshot) {
11173
+ const summary = context.graph.snapshot.summary;
11174
+ const counts = summary && typeof summary.nodes === "number" && typeof summary.edges === "number" ? ` (${summary.nodes} nodes, ${summary.edges} edges)` : "";
11175
+ console.log(` Snapshot: ${cyan3(context.graph.snapshot.path)}${counts}`);
11176
+ }
11177
+ if (context.graph.routeContext) {
11178
+ const routeSummary = context.graph.routeContext.summary;
11179
+ const routeFindings = context.graph.routeContext.nodes.filter((node) => node.type === "Finding").map((node) => graphPayloadString(node.payload, "code") ?? node.id).slice(0, 4);
11180
+ const routeSources = context.graph.routeContext.nodes.filter((node) => node.type === "SourceArtifact").map((node) => graphPayloadString(node.payload, "path") ?? node.id.replace(/^src:/, "")).slice(0, 4);
11181
+ const routeHints = [
11182
+ context.graph.routeContext.ids.patterns.length > 0 ? `patterns ${context.graph.routeContext.ids.patterns.join(", ")}` : null,
11183
+ routeSummary.openFindings > 0 ? `${routeSummary.openFindings} finding(s)` : null,
11184
+ routeSummary.evidence > 0 ? `${routeSummary.evidence} evidence node(s)` : null,
11185
+ routeSummary.sourceArtifacts > 0 ? `${routeSummary.sourceArtifacts} source artifact(s)` : null
11186
+ ].filter(Boolean);
11187
+ console.log(
11188
+ ` Route subgraph: ${routeSummary.nodes} nodes, ${routeSummary.edges} edges${routeHints.length > 0 ? `; ${routeHints.join("; ")}` : ""}`
11189
+ );
11190
+ if (routeFindings.length > 0) {
11191
+ console.log(` Route findings: ${routeFindings.join(", ")}`);
11192
+ }
11193
+ if (routeSources.length > 0) {
11194
+ console.log(` Route sources: ${routeSources.join(", ")}`);
11195
+ }
11196
+ }
11197
+ if (context.graph.changedFileContext?.impact) {
11198
+ const impact = context.graph.changedFileContext.impact;
11199
+ const impactHints = [
11200
+ impact.ids.routes.length > 0 ? `routes ${impact.ids.routes.join(", ")}` : null,
11201
+ impact.summary.pages > 0 ? `${impact.summary.pages} page(s)` : null,
11202
+ impact.summary.openFindings > 0 ? `${impact.summary.openFindings} finding(s)` : null
11203
+ ].filter(Boolean);
11204
+ console.log(
11205
+ ` Changed-file impact: ${impact.summary.nodes} nodes, ${impact.summary.edges} edges${impactHints.length > 0 ? `; ${impactHints.join("; ")}` : ""}`
11206
+ );
11207
+ }
11208
+ }
11969
11209
  console.log("");
11970
11210
  console.log(`${BOLD9}Authority for this task:${RESET16}`);
11971
11211
  console.log(` Lane: ${context.authority.lane}`);
@@ -12038,7 +11278,7 @@ async function cmdTaskWorkflow(args) {
12038
11278
  console.log("");
12039
11279
  console.log(`${BOLD9}LLM instruction:${RESET16}`);
12040
11280
  console.log(
12041
- " Preserve the active authority above. Use the route pack, section context, local laws, changed-file impact, and visual evidence as the task contract before changing code."
11281
+ " Preserve the active authority above. Use the route pack, section context, typed route graph, local laws, changed-file impact, and visual evidence as the task contract before changing code."
12042
11282
  );
12043
11283
  console.log(` After editing, run ${cyan3(context.verifyCommand)}.`);
12044
11284
  }
@@ -12064,7 +11304,7 @@ async function cmdCodifyWorkflow(args) {
12064
11304
  const workspaceInfo = resolveWorkflowProject(flags, "codify");
12065
11305
  if (!workspaceInfo) return;
12066
11306
  if (flagBoolean(flags, "accept")) {
12067
- if (!existsSync30(localPatternsProposalPath(workspaceInfo.appRoot)) && !existsSync30(localRulesProposalPath(workspaceInfo.appRoot)) && !existsSync30(styleBridgeProposalPath(workspaceInfo.appRoot))) {
11307
+ if (!existsSync28(localPatternsProposalPath(workspaceInfo.appRoot)) && !existsSync28(localRulesProposalPath(workspaceInfo.appRoot)) && !existsSync28(styleBridgeProposalPath(workspaceInfo.appRoot))) {
12068
11308
  console.error(
12069
11309
  error2(
12070
11310
  "No codify proposal found. Run `decantr codify --from-audit`, `decantr codify --style-bridge`, or `decantr codify` first."
@@ -12098,14 +11338,14 @@ async function cmdCodifyWorkflow(args) {
12098
11338
  }
12099
11339
  const detected = detectProject(workspaceInfo.appRoot);
12100
11340
  const essence = readJsonIfPresent(
12101
- join32(workspaceInfo.appRoot, "decantr.essence.json")
11341
+ join30(workspaceInfo.appRoot, "decantr.essence.json")
12102
11342
  );
12103
11343
  const fromAudit = flagBoolean(flags, "from-audit") || flagBoolean(flags, "discover-local-patterns") || flagBoolean(flags, "codify-local-patterns");
12104
11344
  const wantsStyleBridge = flagBoolean(flags, "style-bridge");
12105
11345
  const mapPatternSlug = flagString(flags, "map-pattern") ?? flagString(flags, "hosted-pattern");
12106
11346
  if (mapPatternSlug) {
12107
11347
  const registryClient = new RegistryClient({
12108
- cacheDir: join32(workspaceInfo.appRoot, ".decantr", "cache")
11348
+ cacheDir: join30(workspaceInfo.appRoot, ".decantr", "cache")
12109
11349
  });
12110
11350
  const candidates = await loadPatternDiscoveryCandidates(registryClient);
12111
11351
  const candidate = findPatternCandidateBySlug(candidates, mapPatternSlug);
@@ -12229,7 +11469,7 @@ async function cmdContentWorkflow(args) {
12229
11469
  }
12230
11470
  function cmdHelp() {
12231
11471
  console.log(`
12232
- ${BOLD9}decantr${RESET16} \u2014 Design intelligence for AI-generated UI
11472
+ ${BOLD9}decantr${RESET16} \u2014 AI Frontend Governance for codebases touched by AI agents
12233
11473
 
12234
11474
  ${BOLD9}Usage:${RESET16}
12235
11475
  decantr setup [--project <path>]
@@ -12238,6 +11478,7 @@ ${BOLD9}Usage:${RESET16}
12238
11478
  decantr adopt [--project <path>] [--base-url <url>] [--evidence] [--ci] [--no-packs] [--yes]
12239
11479
  decantr task <route> ["task summary"] [--project <path>] [--since origin/main] [--json]
12240
11480
  decantr verify [--project <path>] [--brownfield] [--local-patterns] [health options]
11481
+ decantr graph [--project <path>] [--route <route>] [--node <id>] [--file <path>] [--task <text>] [--snapshot-id <id>] [--compare-to <id>] [--capsule-source-limit <count>] [--check] [--json]
12241
11482
  decantr ci [--project <path>] [--workspace] [--fail-on error|warn|none]
12242
11483
  decantr doctor [--project <path>] [--workspace] [--json]
12243
11484
  decantr codify [--from-audit] [--style-bridge] [--map-pattern <slug>] [--accept] [--project <path>]
@@ -12254,6 +11495,7 @@ ${BOLD9}Advanced primitives:${RESET16}
12254
11495
  decantr check
12255
11496
  decantr check --brownfield
12256
11497
  decantr sync-drift
11498
+ decantr graph [--project <path>] [--route <route>] [--node <id>] [--file <path>] [--task <text>] [--snapshot-id <id>] [--compare-to <id>] [--capsule-source-limit <count>] [--check] [--json]
12257
11499
  decantr search <query> [--type <type>] [--sort <recommended|recent|name>] [--recommended] [--source <authored|benchmark|hybrid>]
12258
11500
  decantr suggest <query> [--type <type>] [--route <route>] [--file <path>] [--from-code]
12259
11501
  decantr get <type> <id>
@@ -12266,6 +11508,7 @@ ${BOLD9}Advanced primitives:${RESET16}
12266
11508
  decantr registry audit-project [--namespace <namespace>] [--json] [--essence <path>] [--dist <path>] [--sources <dir>]
12267
11509
  decantr health [--format text|json|markdown] [--ci] [--fail-on error|warn|none]
12268
11510
  decantr health --evidence [--browser] [--base-url <url>] [--design-tokens <path>]
11511
+ decantr health --diagnostics [--json|--markdown]
12269
11512
  decantr health --save-baseline | --since-baseline
12270
11513
  decantr health init-ci [legacy alias for decantr ci init]
12271
11514
  decantr ci init [--project <path>] [--workspace] [--provider github|generic] [--force]
@@ -12315,6 +11558,7 @@ ${BOLD9}Commands:${RESET16}
12315
11558
  ${cyan3("adopt")} Brownfield one-liner: analyze, attach, verify, and show next steps
12316
11559
  ${cyan3("task")} Prepare route/task context, local law, evidence, and changed-file impact for an AI coding assistant
12317
11560
  ${cyan3("verify")} One reliability gate over Project Health, Brownfield checks, baselines, and evidence
11561
+ ${cyan3("graph")} Build typed Contract graph artifacts and the agent cache capsule
12318
11562
  ${cyan3("ci")} Non-mutating CI gate and CI integration generator
12319
11563
  ${cyan3("doctor")} Explain Decantr state, artifact ownership, and the next command
12320
11564
  ${cyan3("codify")} Propose or accept project-owned Brownfield UI patterns and rules
@@ -12327,21 +11571,22 @@ ${BOLD9}Advanced commands:${RESET16}
12327
11571
  ${cyan3("status")} Show project status, DNA axioms, and blueprint info
12328
11572
  ${cyan3("health")} Advanced Project Health primitive [--json] [--markdown] [--ci]; use decantr ci for automation
12329
11573
  ${cyan3("workspace")} Discover and aggregate health across Decantr projects in a monorepo
12330
- ${cyan3("content-health")} Generate a local registry content health report [--json] [--markdown] [--ci]
12331
- ${cyan3("sync")} Sync registry content from API
11574
+ ${cyan3("content-health")} Generate a local official-vocabulary health report [--json] [--markdown] [--ci]
11575
+ ${cyan3("sync")} Sync official vocabulary from API
12332
11576
  ${cyan3("audit")} Audit the project or critique a specific file against compiled packs
12333
11577
  ${cyan3("migrate")} Migrate older essence files to v4 format (with .pre-v4.backup.json backup)
12334
11578
  ${cyan3("check")} Detect drift issues (validate + guard rules) [--telemetry] [--brownfield]
12335
11579
  ${cyan3("sync-drift")} Review and resolve drift log entries
12336
- ${cyan3("search")} Search the registry
11580
+ ${cyan3("graph")} Generate .decantr/graph snapshot, history, manifest, diff, and contract capsule
11581
+ ${cyan3("search")} Search official/community vocabulary
12337
11582
  ${cyan3("suggest")} Suggest patterns or alternatives for a query
12338
- ${cyan3("get")} Get full details of a registry item
11583
+ ${cyan3("get")} Get full details of a vocabulary item
12339
11584
  ${cyan3("list")} List items by type
12340
11585
  ${cyan3("showcase")} Inspect audited showcase benchmark metadata
12341
11586
  ${cyan3("validate")} Validate an Essence v4 file
12342
11587
  ${cyan3("theme")} Manage custom themes (create, list, validate, delete, import)
12343
11588
  ${cyan3("create")} Create a custom content item (pattern, theme, blueprint, etc.)
12344
- ${cyan3("publish")} Publish a custom content item to the community registry
11589
+ ${cyan3("publish")} Publish a custom vocabulary item to the community content service
12345
11590
  ${cyan3("login")} Authenticate with the Decantr registry
12346
11591
  ${cyan3("logout")} Remove stored credentials
12347
11592
  ${cyan3("analyze")} Brownfield entrypoint: scan an existing project and emit attach guidance
@@ -12361,6 +11606,11 @@ ${BOLD9}Examples:${RESET16}
12361
11606
  decantr adopt --project apps/web --yes
12362
11607
  decantr task /feed "add saved recipe actions"
12363
11608
  decantr verify --brownfield --local-patterns
11609
+ decantr graph --project apps/web
11610
+ decantr graph --project apps/web --route /feed --task "improve loading" --json
11611
+ decantr graph --project apps/web --file src/app/page.tsx --impact --json
11612
+ decantr graph --project apps/web --compare-to graph:previous --include-diff-ops --json
11613
+ decantr graph --check --json
12364
11614
  decantr verify --base-url http://localhost:3000 --evidence
12365
11615
  decantr verify --since-baseline
12366
11616
  decantr doctor --project apps/web
@@ -12418,6 +11668,7 @@ ${BOLD9}Workflow Model:${RESET16}
12418
11668
  ${cyan3("Brownfield preview")} decantr scan -> decantr adopt --yes
12419
11669
  ${cyan3("Brownfield monorepo")} decantr adopt --project apps/web --yes
12420
11670
  ${cyan3("Daily LLM work")} decantr task <route> "<change>" -> decantr verify --brownfield --local-patterns
11671
+ ${cyan3("Typed contract graph")} decantr graph -> agent session loads .decantr/graph/contract-capsule.json
12421
11672
  ${cyan3("Project-owned law")} decantr codify --from-audit -> edit proposal -> decantr codify --accept
12422
11673
  ${cyan3("Hybrid composition")} decantr add/remove, decantr theme switch, decantr registry, decantr upgrade
12423
11674
 
@@ -12480,6 +11731,7 @@ ${BOLD9}Usage:${RESET16}
12480
11731
  decantr health --ci [--fail-on error|warn|none]
12481
11732
  decantr health --prompt <finding-id>
12482
11733
  decantr health --evidence [--browser] [--design-tokens <path>]
11734
+ decantr health --diagnostics [--json|--markdown]
12483
11735
  decantr health --browser --base-url <url> --evidence
12484
11736
  decantr health --save-baseline
12485
11737
  decantr health --since-baseline
@@ -12494,6 +11746,7 @@ ${BOLD9}Options:${RESET16}
12494
11746
  --fail-on CI threshold: error, warn, or none
12495
11747
  --prompt Print an AI-ready remediation prompt for a finding
12496
11748
  --evidence Emit a local Evidence Bundle JSON artifact
11749
+ --diagnostics Print the stable diagnostic code and repair ID catalog
12497
11750
  --browser Include optional rendered-browser setup/evidence checks
12498
11751
  --base-url Base URL for rendered route checks when --browser is enabled
12499
11752
  --save-baseline Save the current health state for later comparison
@@ -12506,6 +11759,7 @@ ${BOLD9}Examples:${RESET16}
12506
11759
  decantr health --markdown --output decantr-health.md
12507
11760
  decantr ci --project apps/web
12508
11761
  decantr health --prompt audit-essence-missing
11762
+ decantr health --diagnostics --markdown
12509
11763
  decantr health --evidence --output .decantr/evidence/latest.json
12510
11764
  decantr ci init --project apps/web
12511
11765
  decantr ci init --workspace
@@ -12530,7 +11784,7 @@ ${BOLD9}Examples:${RESET16}
12530
11784
  }
12531
11785
  function cmdContentHealthHelp() {
12532
11786
  console.log(`
12533
- ${BOLD9}decantr content-health${RESET16} \u2014 Generate a local registry content health report
11787
+ ${BOLD9}decantr content-health${RESET16} \u2014 Generate a local official-vocabulary health report
12534
11788
 
12535
11789
  ${BOLD9}Usage:${RESET16}
12536
11790
  decantr content-health [--format text|json|markdown] [--output <file>]
@@ -12679,6 +11933,10 @@ ${BOLD9}decantr task${RESET16} \u2014 Prepare compact route/task context for an
12679
11933
  ${BOLD9}Usage:${RESET16}
12680
11934
  decantr task <route> ["task summary"] [--project <path>] [--since origin/main] [--json]
12681
11935
 
11936
+ ${BOLD9}Behavior:${RESET16}
11937
+ Includes the typed contract capsule path when .decantr/graph exists. Run decantr graph first
11938
+ when you want graph-backed agent context in CLI-only workflows.
11939
+
12682
11940
  ${BOLD9}Examples:${RESET16}
12683
11941
  decantr task /feed "add saved recipe actions"
12684
11942
  decantr task /feed "add saved recipe actions" --since origin/main
@@ -12706,7 +11964,7 @@ ${BOLD9}Examples:${RESET16}
12706
11964
  }
12707
11965
  function cmdContentHelp() {
12708
11966
  console.log(`
12709
- ${BOLD9}decantr content${RESET16} \u2014 Content-author namespace for registry content repositories
11967
+ ${BOLD9}decantr content${RESET16} \u2014 Content-author namespace for official-vocabulary repositories
12710
11968
 
12711
11969
  ${BOLD9}Usage:${RESET16}
12712
11970
  decantr content check [content-health options]
@@ -12734,6 +11992,9 @@ function printCommandHelp(command, args) {
12734
11992
  case "verify":
12735
11993
  cmdVerifyHelp();
12736
11994
  return true;
11995
+ case "graph":
11996
+ cmdGraphHelp();
11997
+ return true;
12737
11998
  case "ci":
12738
11999
  cmdCiHelp();
12739
12000
  return true;
@@ -12785,10 +12046,10 @@ async function main() {
12785
12046
  if (command === "--version" || command === "-v" || command === "version") {
12786
12047
  try {
12787
12048
  const here = dirname6(fileURLToPath3(import.meta.url));
12788
- const candidates = [join32(here, "..", "package.json"), join32(here, "..", "..", "package.json")];
12049
+ const candidates = [join30(here, "..", "package.json"), join30(here, "..", "..", "package.json")];
12789
12050
  for (const candidate of candidates) {
12790
- if (existsSync30(candidate)) {
12791
- const pkg = JSON.parse(readFileSync23(candidate, "utf-8"));
12051
+ if (existsSync28(candidate)) {
12052
+ const pkg = JSON.parse(readFileSync21(candidate, "utf-8"));
12792
12053
  if (pkg.version) {
12793
12054
  console.log(pkg.version);
12794
12055
  return;
@@ -12827,6 +12088,51 @@ async function main() {
12827
12088
  await cmdVerifyWorkflow(args);
12828
12089
  break;
12829
12090
  }
12091
+ case "graph": {
12092
+ const { flags } = parseLooseArgs(args);
12093
+ if (!ensureAllowedFlags(
12094
+ flags,
12095
+ [
12096
+ "project",
12097
+ "route",
12098
+ "node",
12099
+ "file",
12100
+ "task",
12101
+ "snapshot-id",
12102
+ "compare-to",
12103
+ "include-diff-ops",
12104
+ "impact",
12105
+ "limit",
12106
+ "capsule-source-limit",
12107
+ "check",
12108
+ "json"
12109
+ ],
12110
+ "graph"
12111
+ ))
12112
+ break;
12113
+ const workspaceInfo = resolveWorkflowProject(flags, "graph");
12114
+ if (!workspaceInfo) break;
12115
+ const limitArg = flagString(flags, "limit");
12116
+ const limit = limitArg ? Number(limitArg) : void 0;
12117
+ const capsuleSourceLimitArg = flagString(flags, "capsule-source-limit");
12118
+ const capsuleSourceLimit = capsuleSourceLimitArg ? Number(capsuleSourceLimitArg) : void 0;
12119
+ await cmdGraph(workspaceInfo.appRoot, {
12120
+ check: flagBoolean(flags, "check"),
12121
+ json: flagBoolean(flags, "json"),
12122
+ route: flagString(flags, "route"),
12123
+ node: flagString(flags, "node"),
12124
+ file: flagString(flags, "file"),
12125
+ impact: flagBoolean(flags, "impact"),
12126
+ task: flagString(flags, "task"),
12127
+ snapshotId: flagString(flags, "snapshot-id"),
12128
+ compareTo: flagString(flags, "compare-to"),
12129
+ includeDiffOps: flagBoolean(flags, "include-diff-ops"),
12130
+ limit,
12131
+ capsuleSourceLimit,
12132
+ displayRoot: workspaceInfo.cwd
12133
+ });
12134
+ break;
12135
+ }
12830
12136
  case "ci": {
12831
12137
  await cmdCi(args, process.cwd());
12832
12138
  break;
@@ -12970,7 +12276,7 @@ async function main() {
12970
12276
  const { flags } = parseLooseArgs(args);
12971
12277
  const workspaceInfo = resolveWorkflowProject(flags, "health");
12972
12278
  if (!workspaceInfo) break;
12973
- const { cmdHealth, parseHealthArgs } = await import("./health-B4W7UJBZ.js");
12279
+ const { cmdHealth, parseHealthArgs } = await import("./health-ASR7RWZJ.js");
12974
12280
  await cmdHealth(workspaceInfo.appRoot, parseHealthArgs(stripProjectArgs(args)));
12975
12281
  } catch (e) {
12976
12282
  console.error(error2(e.message));
@@ -12998,7 +12304,7 @@ async function main() {
12998
12304
  cmdStudioHelp();
12999
12305
  break;
13000
12306
  }
13001
- const { cmdStudio, parseStudioArgs } = await import("./studio-JOEECLI6.js");
12307
+ const { cmdStudio, parseStudioArgs } = await import("./studio-R65NXPJW.js");
13002
12308
  await cmdStudio(process.cwd(), parseStudioArgs(args));
13003
12309
  } catch (e) {
13004
12310
  console.error(error2(e.message));
@@ -13012,7 +12318,7 @@ async function main() {
13012
12318
  cmdWorkspaceHelp();
13013
12319
  break;
13014
12320
  }
13015
- const { cmdWorkspace } = await import("./workspace-7RU77ZZW.js");
12321
+ const { cmdWorkspace } = await import("./workspace-2FFQEEQQ.js");
13016
12322
  await cmdWorkspace(process.cwd(), args);
13017
12323
  } catch (e) {
13018
12324
  console.error(error2(e.message));
@@ -13109,7 +12415,7 @@ async function main() {
13109
12415
  if (!query && (route || file || fromCode)) {
13110
12416
  query = [
13111
12417
  route ? `route ${route}` : null,
13112
- file ? `file ${basename3(file)}` : null,
12418
+ file ? `file ${basename2(file)}` : null,
13113
12419
  fromCode ? "source code patterns" : null
13114
12420
  ].filter((entry) => Boolean(entry)).join(" ");
13115
12421
  }
@@ -13331,7 +12637,7 @@ async function main() {
13331
12637
  break;
13332
12638
  }
13333
12639
  if (packType === "page" && route && !id) {
13334
- const resolvedPath = essencePath ? resolveUserPath(essencePath) : join32(process.cwd(), "decantr.essence.json");
12640
+ const resolvedPath = essencePath ? resolveUserPath(essencePath) : join30(process.cwd(), "decantr.essence.json");
13335
12641
  id = resolvePagePackIdForRoute(resolvedPath, route);
13336
12642
  }
13337
12643
  await printHostedSelectedExecutionPack(