@decantr/cli 2.6.0 → 2.8.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.
@@ -34,10 +34,10 @@ import {
34
34
  } from "./chunk-KT2ROK2D.js";
35
35
 
36
36
  // src/index.ts
37
- import { existsSync as existsSync27, mkdirSync as mkdirSync13, readdirSync as readdirSync7, readFileSync as readFileSync20, writeFileSync as writeFileSync16 } from "fs";
38
- import { basename as basename2, dirname as dirname4, isAbsolute, join as join28, resolve as resolve4 } from "path";
37
+ import { existsSync as existsSync28, mkdirSync as mkdirSync14, readdirSync as readdirSync8, readFileSync as readFileSync21, writeFileSync as writeFileSync17 } from "fs";
38
+ import { basename as basename3, dirname as dirname4, isAbsolute, join as join29, resolve as resolve4 } from "path";
39
39
  import { fileURLToPath as fileURLToPath2 } from "url";
40
- import { evaluateGuard, isV4 as isV47, validateEssence as validateEssence2 } from "@decantr/essence-spec";
40
+ import { evaluateGuard, isV4 as isV48, validateEssence as validateEssence2 } from "@decantr/essence-spec";
41
41
  import {
42
42
  CONTENT_TYPE_TO_API_CONTENT_TYPE as CONTENT_TYPE_TO_API_CONTENT_TYPE3,
43
43
  CONTENT_TYPES as GET_CONTENT_TYPES,
@@ -5271,6 +5271,488 @@ async function cmdThemeSwitch(themeName, args, projectRoot = process.cwd()) {
5271
5271
  console.log(`${YELLOW7}Guard will flag code using old tokens. Run \`decantr check\`.${RESET12}`);
5272
5272
  }
5273
5273
 
5274
+ // src/local-law.ts
5275
+ import { execFileSync } from "child_process";
5276
+ import { existsSync as existsSync25, mkdirSync as mkdirSync12, readdirSync as readdirSync5, readFileSync as readFileSync18, statSync as statSync5, writeFileSync as writeFileSync15 } from "fs";
5277
+ import { basename as basename2, extname, join as join26, relative as relative2, sep } from "path";
5278
+ import { isV4 as isV47 } from "@decantr/essence-spec";
5279
+ var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
5280
+ ".astro",
5281
+ ".html",
5282
+ ".js",
5283
+ ".jsx",
5284
+ ".svelte",
5285
+ ".ts",
5286
+ ".tsx",
5287
+ ".vue"
5288
+ ]);
5289
+ var UI_TEMPLATE_EXTENSIONS = /* @__PURE__ */ new Set([".astro", ".html", ".jsx", ".svelte", ".tsx", ".vue"]);
5290
+ var IGNORED_DIRS = /* @__PURE__ */ new Set([
5291
+ ".decantr",
5292
+ ".git",
5293
+ ".next",
5294
+ ".nuxt",
5295
+ ".svelte-kit",
5296
+ "build",
5297
+ "coverage",
5298
+ "dist",
5299
+ "node_modules",
5300
+ "out"
5301
+ ]);
5302
+ var DEFAULT_RULE_EXTENSIONS = [".astro", ".html", ".jsx", ".svelte", ".tsx", ".vue"];
5303
+ function localPatternsProposalPath(projectRoot) {
5304
+ return join26(projectRoot, ".decantr", "local-patterns.proposal.json");
5305
+ }
5306
+ function localPatternsPath(projectRoot) {
5307
+ return join26(projectRoot, ".decantr", "local-patterns.json");
5308
+ }
5309
+ function localRulesProposalPath(projectRoot) {
5310
+ return join26(projectRoot, ".decantr", "rules.proposal.json");
5311
+ }
5312
+ function localRulesPath(projectRoot) {
5313
+ return join26(projectRoot, ".decantr", "rules.json");
5314
+ }
5315
+ function readLocalPatternPack(projectRoot) {
5316
+ return readJsonFile(localPatternsPath(projectRoot));
5317
+ }
5318
+ function readLocalRuleManifest(projectRoot) {
5319
+ return readJsonFile(localRulesPath(projectRoot));
5320
+ }
5321
+ function createBrownfieldCodifyProposal(input) {
5322
+ const sourceFiles = input.fromAudit ? listSourceFiles(input.projectRoot, 800) : [];
5323
+ const evidence = summarizeSourceEvidence(input.projectRoot, sourceFiles);
5324
+ const routes = input.essence && isV47(input.essence) ? Object.keys(input.essence.blueprint.routes ?? {}).sort() : [];
5325
+ const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
5326
+ const patternPack = {
5327
+ version: 2,
5328
+ generatedAt,
5329
+ status: "proposal",
5330
+ source: input.fromAudit ? "decantr codify --from-audit" : "decantr codify",
5331
+ project: {
5332
+ framework: input.detected.framework,
5333
+ packageManager: input.detected.packageManager,
5334
+ hasTailwind: input.detected.hasTailwind,
5335
+ ruleFiles: input.detected.existingRuleFiles,
5336
+ routeCount: routes.length
5337
+ },
5338
+ purpose: "Project-owned Brownfield UI law. Review and edit before accepting; Decantr treats this as authoritative only after it is copied to .decantr/local-patterns.json.",
5339
+ patterns: [
5340
+ {
5341
+ id: "button",
5342
+ label: "Button primitives",
5343
+ role: "Actions and command triggers",
5344
+ appliesTo: [
5345
+ "primary action",
5346
+ "secondary action",
5347
+ "tertiary action",
5348
+ "destructive action",
5349
+ "icon action"
5350
+ ],
5351
+ componentPaths: evidence.buttonComponents,
5352
+ decide: "Define primary, secondary, tertiary, destructive, icon-only, disabled, and loading button variants from this app.",
5353
+ evidence: evidence.buttonComponents.length ? evidence.buttonComponents : [
5354
+ "No obvious Button wrapper found yet. Add the project-owned wrapper path before strict enforcement."
5355
+ ],
5356
+ forbiddenAlternatives: ["New one-off button variants without updating this manifest."]
5357
+ },
5358
+ {
5359
+ id: "surface-card",
5360
+ label: "Cards and surfaces",
5361
+ role: "Cards, panels, and reusable content surfaces",
5362
+ appliesTo: ["cards", "panels", "modals", "list items", "dashboard tiles"],
5363
+ componentPaths: evidence.cardComponents,
5364
+ decide: "Define the canonical card background, border, radius, shadow, padding, density, and hover treatment.",
5365
+ classHints: evidence.cardClassHints,
5366
+ evidence: evidence.cardComponents.length ? evidence.cardComponents : [
5367
+ "No obvious Card wrapper found yet. Add the project-owned wrapper path or class recipe."
5368
+ ],
5369
+ forbiddenAlternatives: ["Flat ad hoc cards with unique color/radius/shadow recipes."]
5370
+ },
5371
+ {
5372
+ id: "page-shell",
5373
+ label: "Page shell and spacing",
5374
+ role: "Route shell, navigation, gutters, max-width, and scroll ownership",
5375
+ appliesTo: ["routes", "layouts", "navigation shells", "scroll containers"],
5376
+ componentPaths: evidence.shellComponents,
5377
+ decide: "Define which layout owns max width, gutters, sticky chrome, responsive breakpoints, and scroll containers.",
5378
+ evidence: evidence.shellComponents.length ? evidence.shellComponents : ["Add root layout, shell, or app frame files that establish route chrome and spacing."],
5379
+ forbiddenAlternatives: [
5380
+ "Each page inventing independent max-width, padding, or sticky nav rules."
5381
+ ]
5382
+ },
5383
+ {
5384
+ id: "form-control",
5385
+ label: "Form controls",
5386
+ role: "Inputs, labels, validation, and form actions",
5387
+ appliesTo: ["inputs", "selects", "textareas", "validation messages", "form actions"],
5388
+ componentPaths: evidence.formComponents,
5389
+ decide: "Define input height, label placement, error copy, disabled state, required state, and focus treatment.",
5390
+ evidence: evidence.formComponents.length ? evidence.formComponents : ["Add form field wrapper paths and validation examples."],
5391
+ forbiddenAlternatives: [
5392
+ "Unlabeled one-off inputs or validation states that do not match the app standard."
5393
+ ]
5394
+ },
5395
+ {
5396
+ id: "theme-variant",
5397
+ label: "Theme variants",
5398
+ role: "Light, dark, brand, density, and tenant/theme variants observed in the app",
5399
+ appliesTo: ["theme toggles", "mode-specific classes", "brand variants", "tenant variants"],
5400
+ componentPaths: evidence.themeComponents,
5401
+ decide: "Document which theme variants exist, where they are toggled, and which tokens/classes are legal per variant.",
5402
+ evidence: evidence.themeComponents.length ? evidence.themeComponents : ["If the app has dark/light or brand variants, add the toggles/providers here."],
5403
+ forbiddenAlternatives: [
5404
+ "Component-local theme forks that bypass shared theme providers or tokens."
5405
+ ]
5406
+ }
5407
+ ],
5408
+ starterRules: [
5409
+ "Prefer project-owned wrappers for repeated primitives once they exist.",
5410
+ "Avoid raw hex/rgb values in component templates unless documented as dynamic data.",
5411
+ "Avoid static inline styles for reusable visual treatment.",
5412
+ "When adding a new route, map it to an existing local pattern before inventing a new visual variant.",
5413
+ "When adding a theme variant, update .decantr/theme-inventory.json and this local pattern pack."
5414
+ ],
5415
+ nextSteps: [
5416
+ "Edit this proposal with real component paths and token/class recipes.",
5417
+ "Run decantr codify --accept after review.",
5418
+ "Use decantr task <route> before LLM edits so local law appears in task context.",
5419
+ "Run decantr verify --brownfield --local-patterns after edits.",
5420
+ "Wire deterministic project rules into ESLint, Biome, Storybook, visual tests, or CI where Decantr should not guess."
5421
+ ]
5422
+ };
5423
+ const ruleManifest = {
5424
+ version: 1,
5425
+ status: "proposal",
5426
+ generatedAt,
5427
+ source: input.fromAudit ? "decantr codify --from-audit" : "decantr codify",
5428
+ purpose: "Mechanical Brownfield checks owned by this project. These rules are intentionally local and stack-agnostic; edit before accepting.",
5429
+ enforcement: {
5430
+ defaultSeverity: "warn",
5431
+ mode: "warn",
5432
+ notes: [
5433
+ "Decantr local rules are a guardrail, not a replacement for ESLint, Biome, type checks, tests, or visual regression.",
5434
+ "Keep rules narrow enough that an LLM can fix findings without rewriting the app.",
5435
+ "Use error severity only after the team agrees the rule is stable."
5436
+ ]
5437
+ },
5438
+ rules: [
5439
+ {
5440
+ id: "no-inline-style",
5441
+ type: "forbid-regex",
5442
+ enabled: true,
5443
+ severity: "warn",
5444
+ description: "Reusable UI should not add static inline style attributes.",
5445
+ includeExtensions: DEFAULT_RULE_EXTENSIONS,
5446
+ pattern: "\\bstyle\\s*=",
5447
+ message: "Inline style found in UI template.",
5448
+ suggestedFix: "Move reusable visual treatment into the project style system, component wrapper, token, or documented local pattern.",
5449
+ maxFindings: 25
5450
+ },
5451
+ {
5452
+ id: "no-raw-color-literals",
5453
+ type: "forbid-regex",
5454
+ enabled: true,
5455
+ severity: "warn",
5456
+ description: "Component templates should not introduce raw hex/rgb color literals.",
5457
+ includeExtensions: DEFAULT_RULE_EXTENSIONS,
5458
+ pattern: "#(?:[0-9a-fA-F]{3,8})\\b|rgba?\\s*\\(",
5459
+ message: "Raw color literal found in UI template.",
5460
+ suggestedFix: "Use an existing project token/class, or document the exception in .decantr/local-patterns.json if the value is data-driven.",
5461
+ maxFindings: 25
5462
+ },
5463
+ {
5464
+ id: "prefer-button-wrapper",
5465
+ type: "forbid-regex",
5466
+ enabled: evidence.buttonComponents.length > 0,
5467
+ severity: "info",
5468
+ description: "Prefer the project-owned button primitive instead of new raw button markup.",
5469
+ includeExtensions: DEFAULT_RULE_EXTENSIONS,
5470
+ pattern: "<button[\\s>]",
5471
+ message: "Raw <button> usage found outside the detected button wrapper.",
5472
+ suggestedFix: "Use the project-owned Button primitive, or add this file to allowedPaths if it is the primitive implementation.",
5473
+ allowedPaths: evidence.buttonComponents,
5474
+ maxFindings: 50
5475
+ }
5476
+ ]
5477
+ };
5478
+ return { patternPack, ruleManifest };
5479
+ }
5480
+ function writeBrownfieldCodifyProposal(projectRoot, proposal) {
5481
+ const decantrDir = join26(projectRoot, ".decantr");
5482
+ mkdirSync12(decantrDir, { recursive: true });
5483
+ const patternPath = localPatternsProposalPath(projectRoot);
5484
+ const rulesPath = localRulesProposalPath(projectRoot);
5485
+ writeFileSync15(patternPath, `${JSON.stringify(proposal.patternPack, null, 2)}
5486
+ `, "utf-8");
5487
+ writeFileSync15(rulesPath, `${JSON.stringify(proposal.ruleManifest, null, 2)}
5488
+ `, "utf-8");
5489
+ return { patternPath, rulesPath };
5490
+ }
5491
+ function acceptBrownfieldLocalLaw(projectRoot) {
5492
+ const patternProposal = readJsonFile(localPatternsProposalPath(projectRoot));
5493
+ const ruleProposal = readJsonFile(localRulesProposalPath(projectRoot));
5494
+ const acceptedAt = (/* @__PURE__ */ new Date()).toISOString();
5495
+ let patternAcceptedPath = null;
5496
+ let rulesAcceptedPath = null;
5497
+ if (patternProposal) {
5498
+ patternProposal.status = "accepted";
5499
+ patternProposal.acceptedAt = acceptedAt;
5500
+ patternAcceptedPath = localPatternsPath(projectRoot);
5501
+ writeFileSync15(patternAcceptedPath, `${JSON.stringify(patternProposal, null, 2)}
5502
+ `, "utf-8");
5503
+ }
5504
+ if (ruleProposal) {
5505
+ ruleProposal.status = "accepted";
5506
+ ruleProposal.acceptedAt = acceptedAt;
5507
+ rulesAcceptedPath = localRulesPath(projectRoot);
5508
+ writeFileSync15(rulesAcceptedPath, `${JSON.stringify(ruleProposal, null, 2)}
5509
+ `, "utf-8");
5510
+ }
5511
+ return { patternAcceptedPath, rulesAcceptedPath };
5512
+ }
5513
+ function validateLocalLaw(projectRoot) {
5514
+ const patternsPath = localPatternsPath(projectRoot);
5515
+ const rulesPath = localRulesPath(projectRoot);
5516
+ const patternPack = readJsonFile(patternsPath);
5517
+ const ruleManifest = readJsonFile(rulesPath);
5518
+ const warnings = [];
5519
+ if (patternPack) {
5520
+ const patternIds = /* @__PURE__ */ new Set();
5521
+ const patterns = Array.isArray(patternPack.patterns) ? patternPack.patterns : [];
5522
+ if (patterns.length === 0) {
5523
+ warnings.push(".decantr/local-patterns.json has no patterns.");
5524
+ }
5525
+ for (const pattern of patterns) {
5526
+ const id = typeof pattern.id === "string" ? pattern.id.trim() : "";
5527
+ if (!id) warnings.push("A local pattern is missing an id.");
5528
+ if (id && patternIds.has(id)) warnings.push(`Duplicate local pattern id: ${id}`);
5529
+ if (id) patternIds.add(id);
5530
+ const paths = Array.isArray(pattern.componentPaths) ? pattern.componentPaths : [];
5531
+ const evidence = Array.isArray(pattern.evidence) ? pattern.evidence : [];
5532
+ const todoEvidence = Array.isArray(pattern.evidenceToCollect) ? pattern.evidenceToCollect : [];
5533
+ if (id && paths.length === 0 && evidence.length === 0 && todoEvidence.length > 0) {
5534
+ warnings.push(
5535
+ `Local pattern ${id} still reads like a TODO; add concrete component paths or evidence.`
5536
+ );
5537
+ }
5538
+ }
5539
+ }
5540
+ if (ruleManifest && !Array.isArray(ruleManifest.rules)) {
5541
+ warnings.push(".decantr/rules.json has no rules array.");
5542
+ }
5543
+ const findings = ruleManifest ? scanLocalRules(projectRoot, ruleManifest) : [];
5544
+ return {
5545
+ patternsPath,
5546
+ rulesPath,
5547
+ patternPackPresent: Boolean(patternPack),
5548
+ ruleManifestPresent: Boolean(ruleManifest),
5549
+ warnings,
5550
+ findings
5551
+ };
5552
+ }
5553
+ function createLocalLawTaskSummary(projectRoot) {
5554
+ const patternPack = readLocalPatternPack(projectRoot);
5555
+ const ruleManifest = readLocalRuleManifest(projectRoot);
5556
+ const patterns = (patternPack?.patterns ?? []).map((pattern) => ({
5557
+ id: typeof pattern.id === "string" ? pattern.id : "unknown",
5558
+ role: typeof pattern.role === "string" ? pattern.role : null,
5559
+ componentPaths: Array.isArray(pattern.componentPaths) ? pattern.componentPaths.filter((entry) => typeof entry === "string") : []
5560
+ }));
5561
+ const rules = (ruleManifest?.rules ?? []).map((rule) => ({
5562
+ id: rule.id,
5563
+ severity: rule.severity,
5564
+ enabled: rule.enabled,
5565
+ description: rule.description
5566
+ }));
5567
+ return {
5568
+ patternsPath: patternPack ? ".decantr/local-patterns.json" : null,
5569
+ rulesPath: ruleManifest ? ".decantr/rules.json" : null,
5570
+ patternCount: patterns.length,
5571
+ ruleCount: rules.length,
5572
+ patterns,
5573
+ rules
5574
+ };
5575
+ }
5576
+ function changedFiles(projectRoot, since) {
5577
+ const changed = /* @__PURE__ */ new Set();
5578
+ try {
5579
+ const commands = since ? [
5580
+ ["diff", "--name-only", since, "--"],
5581
+ ["diff", "--name-only", "--cached"]
5582
+ ] : [
5583
+ ["diff", "--name-only"],
5584
+ ["diff", "--name-only", "--cached"]
5585
+ ];
5586
+ for (const args of commands) {
5587
+ const output = execFileSync("git", args, {
5588
+ cwd: projectRoot,
5589
+ encoding: "utf-8",
5590
+ stdio: ["ignore", "pipe", "ignore"]
5591
+ });
5592
+ for (const line of output.split(/\r?\n/)) {
5593
+ const file = line.trim();
5594
+ if (file) changed.add(normalizePath(file));
5595
+ }
5596
+ }
5597
+ } catch {
5598
+ }
5599
+ return [...changed].sort();
5600
+ }
5601
+ function routeImpacts(projectRoot, files) {
5602
+ const analysis = readJsonFile(
5603
+ join26(projectRoot, ".decantr", "analysis.json")
5604
+ );
5605
+ const routeEntries = analysis?.routes?.routes ?? [];
5606
+ const impacted = /* @__PURE__ */ new Set();
5607
+ for (const file of files) {
5608
+ for (const route of routeEntries) {
5609
+ if (route.file && pathMatches(file, route.file)) {
5610
+ if (route.path) impacted.add(route.path);
5611
+ }
5612
+ }
5613
+ }
5614
+ return [...impacted].sort();
5615
+ }
5616
+ function scanLocalRules(projectRoot, manifest) {
5617
+ const findings = [];
5618
+ const files = listSourceFiles(projectRoot, 1200);
5619
+ for (const rule of manifest.rules ?? []) {
5620
+ if (!rule.enabled || rule.type !== "forbid-regex") continue;
5621
+ const extensions = new Set(
5622
+ rule.includeExtensions?.length ? rule.includeExtensions : DEFAULT_RULE_EXTENSIONS
5623
+ );
5624
+ let regex;
5625
+ try {
5626
+ regex = new RegExp(rule.pattern, "g");
5627
+ } catch {
5628
+ findings.push({
5629
+ ruleId: rule.id,
5630
+ severity: "error",
5631
+ file: ".decantr/rules.json",
5632
+ line: 1,
5633
+ column: 1,
5634
+ excerpt: rule.pattern,
5635
+ message: `Invalid regex for local rule ${rule.id}.`,
5636
+ suggestedFix: "Edit .decantr/rules.json so the pattern is a valid JavaScript regular expression."
5637
+ });
5638
+ continue;
5639
+ }
5640
+ let ruleFindingCount = 0;
5641
+ for (const file of files) {
5642
+ if (!extensions.has(extname(file.absolute))) continue;
5643
+ if (pathAllowed(file.relative, rule.allowedPaths ?? [])) continue;
5644
+ const contents = readFileSync18(file.absolute, "utf-8");
5645
+ for (const match of contents.matchAll(regex)) {
5646
+ const index = match.index ?? 0;
5647
+ const position = lineColumnAt(contents, index);
5648
+ findings.push({
5649
+ ruleId: rule.id,
5650
+ severity: rule.severity,
5651
+ file: file.relative,
5652
+ line: position.line,
5653
+ column: position.column,
5654
+ excerpt: lineAt(contents, position.line).trim().slice(0, 180),
5655
+ message: rule.message,
5656
+ suggestedFix: rule.suggestedFix
5657
+ });
5658
+ ruleFindingCount += 1;
5659
+ if (rule.maxFindings && ruleFindingCount >= rule.maxFindings) break;
5660
+ }
5661
+ if (rule.maxFindings && ruleFindingCount >= rule.maxFindings) break;
5662
+ }
5663
+ }
5664
+ return findings;
5665
+ }
5666
+ function summarizeSourceEvidence(projectRoot, files) {
5667
+ const componentPaths = files.filter((file) => /(^|[/\\])components?([/\\]|$)|(^|[/\\])ui([/\\]|$)/i.test(file.relative)).map((file) => file.relative);
5668
+ const byName = (terms) => componentPaths.filter((file) => terms.some((term) => basename2(file).toLowerCase().includes(term))).slice(0, 12);
5669
+ const themeComponents = componentPaths.filter((file) => /theme|provider|mode|appearance|tenant|brand/i.test(file)).slice(0, 12);
5670
+ const shellComponents = files.filter((file) => /layout|shell|frame|app|root|nav|sidebar/i.test(basename2(file.relative))).map((file) => file.relative).slice(0, 12);
5671
+ return {
5672
+ buttonComponents: byName(["button", "action"]),
5673
+ cardComponents: byName(["card", "panel", "surface", "tile"]),
5674
+ formComponents: byName(["input", "field", "form", "select", "textarea"]),
5675
+ shellComponents,
5676
+ themeComponents,
5677
+ cardClassHints: collectClassHints(projectRoot, files, ["card", "panel", "surface", "tile"])
5678
+ };
5679
+ }
5680
+ function collectClassHints(projectRoot, files, terms) {
5681
+ const hints = /* @__PURE__ */ new Map();
5682
+ for (const file of files) {
5683
+ if (!UI_TEMPLATE_EXTENSIONS.has(extname(file.absolute))) continue;
5684
+ const content = readFileSync18(join26(projectRoot, file.relative), "utf-8");
5685
+ if (!terms.some((term) => content.toLowerCase().includes(term))) continue;
5686
+ const matches = content.matchAll(/\bclass(?:Name)?\s*=\s*["'`]([^"'`]+)["'`]/g);
5687
+ for (const match of matches) {
5688
+ const value = match[1].trim();
5689
+ if (!/(card|panel|surface|rounded|shadow|border|bg-|p-\d|px-|py-)/i.test(value)) continue;
5690
+ hints.set(value, (hints.get(value) ?? 0) + 1);
5691
+ }
5692
+ }
5693
+ return [...hints.entries()].sort((a, b) => b[1] - a[1]).slice(0, 8).map(([hint]) => hint);
5694
+ }
5695
+ function listSourceFiles(projectRoot, maxFiles) {
5696
+ const files = [];
5697
+ const visit = (dir) => {
5698
+ if (files.length >= maxFiles) return;
5699
+ let entries;
5700
+ try {
5701
+ entries = readdirSync5(dir);
5702
+ } catch {
5703
+ return;
5704
+ }
5705
+ for (const entry of entries) {
5706
+ if (files.length >= maxFiles) return;
5707
+ if (IGNORED_DIRS.has(entry)) continue;
5708
+ const absolute = join26(dir, entry);
5709
+ let stat;
5710
+ try {
5711
+ stat = statSync5(absolute);
5712
+ } catch {
5713
+ continue;
5714
+ }
5715
+ if (stat.isDirectory()) {
5716
+ visit(absolute);
5717
+ } else if (stat.isFile() && SOURCE_EXTENSIONS.has(extname(entry))) {
5718
+ files.push({ absolute, relative: normalizePath(relative2(projectRoot, absolute)) });
5719
+ }
5720
+ }
5721
+ };
5722
+ visit(projectRoot);
5723
+ return files.sort((a, b) => a.relative.localeCompare(b.relative));
5724
+ }
5725
+ function readJsonFile(path) {
5726
+ if (!existsSync25(path)) return null;
5727
+ try {
5728
+ return JSON.parse(readFileSync18(path, "utf-8"));
5729
+ } catch {
5730
+ return null;
5731
+ }
5732
+ }
5733
+ function pathAllowed(file, allowedPaths) {
5734
+ return allowedPaths.some((allowedPath) => pathMatches(file, allowedPath));
5735
+ }
5736
+ function pathMatches(file, pattern) {
5737
+ const normalizedFile = normalizePath(file);
5738
+ const normalizedPattern = normalizePath(pattern);
5739
+ return normalizedFile === normalizedPattern || normalizedFile.endsWith(`/${normalizedPattern}`);
5740
+ }
5741
+ function normalizePath(path) {
5742
+ return path.split(sep).join("/").replace(/\\/g, "/");
5743
+ }
5744
+ function lineColumnAt(contents, index) {
5745
+ const before = contents.slice(0, index);
5746
+ const lines = before.split(/\r?\n/);
5747
+ return {
5748
+ line: lines.length,
5749
+ column: lines[lines.length - 1].length + 1
5750
+ };
5751
+ }
5752
+ function lineAt(contents, line) {
5753
+ return contents.split(/\r?\n/)[line - 1] ?? "";
5754
+ }
5755
+
5274
5756
  // src/prompts.ts
5275
5757
  import { createInterface } from "readline";
5276
5758
  var BOLD6 = "\x1B[1m";
@@ -5568,8 +6050,8 @@ async function runSimplifiedInit(blueprints) {
5568
6050
  }
5569
6051
 
5570
6052
  // src/theme-commands.ts
5571
- import { existsSync as existsSync25, mkdirSync as mkdirSync12, readdirSync as readdirSync5, readFileSync as readFileSync18, rmSync as rmSync3, writeFileSync as writeFileSync15 } from "fs";
5572
- import { join as join26 } from "path";
6053
+ import { existsSync as existsSync26, mkdirSync as mkdirSync13, readdirSync as readdirSync6, readFileSync as readFileSync19, rmSync as rmSync3, writeFileSync as writeFileSync16 } from "fs";
6054
+ import { join as join27 } from "path";
5573
6055
  var REQUIRED_FIELDS = [
5574
6056
  "$schema",
5575
6057
  "id",
@@ -5629,20 +6111,20 @@ function validateCustomTheme(theme) {
5629
6111
  };
5630
6112
  }
5631
6113
  function createTheme(projectRoot, id, name) {
5632
- const customThemesDir = join26(projectRoot, ".decantr", "custom", "themes");
5633
- const themePath = join26(customThemesDir, `${id}.json`);
5634
- const howToPath = join26(customThemesDir, "how-to-theme.md");
5635
- mkdirSync12(customThemesDir, { recursive: true });
5636
- if (existsSync25(themePath)) {
6114
+ const customThemesDir = join27(projectRoot, ".decantr", "custom", "themes");
6115
+ const themePath = join27(customThemesDir, `${id}.json`);
6116
+ const howToPath = join27(customThemesDir, "how-to-theme.md");
6117
+ mkdirSync13(customThemesDir, { recursive: true });
6118
+ if (existsSync26(themePath)) {
5637
6119
  return {
5638
6120
  success: false,
5639
6121
  error: `Theme "${id}" already exists at ${themePath}`
5640
6122
  };
5641
6123
  }
5642
6124
  const skeleton = getThemeSkeleton(id, name);
5643
- writeFileSync15(themePath, JSON.stringify(skeleton, null, 2));
5644
- if (!existsSync25(howToPath)) {
5645
- writeFileSync15(howToPath, getHowToThemeDoc());
6125
+ writeFileSync16(themePath, JSON.stringify(skeleton, null, 2));
6126
+ if (!existsSync26(howToPath)) {
6127
+ writeFileSync16(howToPath, getHowToThemeDoc());
5646
6128
  }
5647
6129
  return {
5648
6130
  success: true,
@@ -5650,17 +6132,17 @@ function createTheme(projectRoot, id, name) {
5650
6132
  };
5651
6133
  }
5652
6134
  function listCustomThemes(projectRoot) {
5653
- const customThemesDir = join26(projectRoot, ".decantr", "custom", "themes");
5654
- if (!existsSync25(customThemesDir)) {
6135
+ const customThemesDir = join27(projectRoot, ".decantr", "custom", "themes");
6136
+ if (!existsSync26(customThemesDir)) {
5655
6137
  return [];
5656
6138
  }
5657
6139
  const themes = [];
5658
6140
  try {
5659
- const files = readdirSync5(customThemesDir).filter((f) => f.endsWith(".json"));
6141
+ const files = readdirSync6(customThemesDir).filter((f) => f.endsWith(".json"));
5660
6142
  for (const file of files) {
5661
- const filePath = join26(customThemesDir, file);
6143
+ const filePath = join27(customThemesDir, file);
5662
6144
  try {
5663
- const data = JSON.parse(readFileSync18(filePath, "utf-8"));
6145
+ const data = JSON.parse(readFileSync19(filePath, "utf-8"));
5664
6146
  themes.push({
5665
6147
  id: data.id || file.replace(".json", ""),
5666
6148
  name: data.name || data.id,
@@ -5675,8 +6157,8 @@ function listCustomThemes(projectRoot) {
5675
6157
  return themes;
5676
6158
  }
5677
6159
  function deleteTheme(projectRoot, id) {
5678
- const themePath = join26(projectRoot, ".decantr", "custom", "themes", `${id}.json`);
5679
- if (!existsSync25(themePath)) {
6160
+ const themePath = join27(projectRoot, ".decantr", "custom", "themes", `${id}.json`);
6161
+ if (!existsSync26(themePath)) {
5680
6162
  return {
5681
6163
  success: false,
5682
6164
  error: `Theme "${id}" not found at ${themePath}`
@@ -5693,7 +6175,7 @@ function deleteTheme(projectRoot, id) {
5693
6175
  }
5694
6176
  }
5695
6177
  function importTheme(projectRoot, sourcePath) {
5696
- if (!existsSync25(sourcePath)) {
6178
+ if (!existsSync26(sourcePath)) {
5697
6179
  return {
5698
6180
  success: false,
5699
6181
  errors: [`Source file not found: ${sourcePath}`]
@@ -5701,7 +6183,7 @@ function importTheme(projectRoot, sourcePath) {
5701
6183
  }
5702
6184
  let theme;
5703
6185
  try {
5704
- theme = JSON.parse(readFileSync18(sourcePath, "utf-8"));
6186
+ theme = JSON.parse(readFileSync19(sourcePath, "utf-8"));
5705
6187
  } catch (e) {
5706
6188
  return {
5707
6189
  success: false,
@@ -5717,14 +6199,14 @@ function importTheme(projectRoot, sourcePath) {
5717
6199
  }
5718
6200
  theme.source = "custom";
5719
6201
  const id = theme.id;
5720
- const customThemesDir = join26(projectRoot, ".decantr", "custom", "themes");
5721
- const destPath = join26(customThemesDir, `${id}.json`);
5722
- mkdirSync12(customThemesDir, { recursive: true });
5723
- const howToPath = join26(customThemesDir, "how-to-theme.md");
5724
- if (!existsSync25(howToPath)) {
5725
- writeFileSync15(howToPath, getHowToThemeDoc());
5726
- }
5727
- writeFileSync15(destPath, JSON.stringify(theme, null, 2));
6202
+ const customThemesDir = join27(projectRoot, ".decantr", "custom", "themes");
6203
+ const destPath = join27(customThemesDir, `${id}.json`);
6204
+ mkdirSync13(customThemesDir, { recursive: true });
6205
+ const howToPath = join27(customThemesDir, "how-to-theme.md");
6206
+ if (!existsSync26(howToPath)) {
6207
+ writeFileSync16(howToPath, getHowToThemeDoc());
6208
+ }
6209
+ writeFileSync16(destPath, JSON.stringify(theme, null, 2));
5728
6210
  return {
5729
6211
  success: true,
5730
6212
  path: destPath
@@ -5732,19 +6214,19 @@ function importTheme(projectRoot, sourcePath) {
5732
6214
  }
5733
6215
 
5734
6216
  // src/workspace.ts
5735
- import { existsSync as existsSync26, readdirSync as readdirSync6, readFileSync as readFileSync19 } from "fs";
5736
- import { dirname as dirname3, join as join27, resolve as resolve3 } from "path";
6217
+ import { existsSync as existsSync27, readdirSync as readdirSync7, readFileSync as readFileSync20 } from "fs";
6218
+ import { dirname as dirname3, join as join28, resolve as resolve3 } from "path";
5737
6219
  function readPackageJson(dir) {
5738
- const path = join27(dir, "package.json");
5739
- if (!existsSync26(path)) return null;
6220
+ const path = join28(dir, "package.json");
6221
+ if (!existsSync27(path)) return null;
5740
6222
  try {
5741
- return JSON.parse(readFileSync19(path, "utf-8"));
6223
+ return JSON.parse(readFileSync20(path, "utf-8"));
5742
6224
  } catch {
5743
6225
  return null;
5744
6226
  }
5745
6227
  }
5746
6228
  function hasWorkspaceMarker(dir) {
5747
- if (existsSync26(join27(dir, "pnpm-workspace.yaml")) || existsSync26(join27(dir, "turbo.json")) || existsSync26(join27(dir, "nx.json"))) {
6229
+ if (existsSync27(join28(dir, "pnpm-workspace.yaml")) || existsSync27(join28(dir, "turbo.json")) || existsSync27(join28(dir, "nx.json"))) {
5748
6230
  return true;
5749
6231
  }
5750
6232
  const pkg = readPackageJson(dir);
@@ -5760,7 +6242,7 @@ function findWorkspaceRoot(startDir) {
5760
6242
  }
5761
6243
  }
5762
6244
  function looksLikeApp(dir) {
5763
- if (existsSync26(join27(dir, "next.config.js")) || existsSync26(join27(dir, "next.config.ts")) || existsSync26(join27(dir, "next.config.mjs")) || existsSync26(join27(dir, "vite.config.ts")) || existsSync26(join27(dir, "vite.config.js")) || existsSync26(join27(dir, "angular.json")) || existsSync26(join27(dir, "svelte.config.js")) || existsSync26(join27(dir, "svelte.config.ts")) || existsSync26(join27(dir, "astro.config.mjs")) || existsSync26(join27(dir, "src")) || existsSync26(join27(dir, "app")) || existsSync26(join27(dir, "pages"))) {
6245
+ if (existsSync27(join28(dir, "next.config.js")) || existsSync27(join28(dir, "next.config.ts")) || existsSync27(join28(dir, "next.config.mjs")) || existsSync27(join28(dir, "vite.config.ts")) || existsSync27(join28(dir, "vite.config.js")) || existsSync27(join28(dir, "angular.json")) || existsSync27(join28(dir, "svelte.config.js")) || existsSync27(join28(dir, "svelte.config.ts")) || existsSync27(join28(dir, "astro.config.mjs")) || existsSync27(join28(dir, "src")) || existsSync27(join28(dir, "app")) || existsSync27(join28(dir, "pages"))) {
5764
6246
  return true;
5765
6247
  }
5766
6248
  const pkg = readPackageJson(dir);
@@ -5772,11 +6254,11 @@ function looksLikeApp(dir) {
5772
6254
  function listWorkspaceApps(workspaceRoot) {
5773
6255
  const candidates = [];
5774
6256
  for (const base of ["apps", "packages"]) {
5775
- const baseDir = join27(workspaceRoot, base);
5776
- if (!existsSync26(baseDir)) continue;
5777
- for (const entry of readdirSync6(baseDir, { withFileTypes: true })) {
6257
+ const baseDir = join28(workspaceRoot, base);
6258
+ if (!existsSync27(baseDir)) continue;
6259
+ for (const entry of readdirSync7(baseDir, { withFileTypes: true })) {
5778
6260
  if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
5779
- const candidate = join27(baseDir, entry.name);
6261
+ const candidate = join28(baseDir, entry.name);
5780
6262
  if (looksLikeApp(candidate)) {
5781
6263
  candidates.push(`${base}/${entry.name}`);
5782
6264
  }
@@ -6341,18 +6823,18 @@ function extractHostedAssetPaths(indexHtml) {
6341
6823
  return [...assetPaths];
6342
6824
  }
6343
6825
  function readHostedDistSnapshot(distPath) {
6344
- const resolvedDistPath = distPath ? resolveUserPath(distPath) : join28(process.cwd(), "dist");
6345
- const indexPath = join28(resolvedDistPath, "index.html");
6346
- if (!existsSync27(indexPath)) {
6826
+ const resolvedDistPath = distPath ? resolveUserPath(distPath) : join29(process.cwd(), "dist");
6827
+ const indexPath = join29(resolvedDistPath, "index.html");
6828
+ if (!existsSync28(indexPath)) {
6347
6829
  return void 0;
6348
6830
  }
6349
- const indexHtml = readFileSync20(indexPath, "utf-8");
6831
+ const indexHtml = readFileSync21(indexPath, "utf-8");
6350
6832
  const assetPaths = extractHostedAssetPaths(indexHtml);
6351
6833
  const assets = {};
6352
6834
  for (const assetPath of assetPaths) {
6353
- const assetFilePath = join28(resolvedDistPath, assetPath.replace(/^[/\\]+/, ""));
6354
- if (existsSync27(assetFilePath)) {
6355
- assets[assetPath] = readFileSync20(assetFilePath, "utf-8");
6835
+ const assetFilePath = join29(resolvedDistPath, assetPath.replace(/^[/\\]+/, ""));
6836
+ if (existsSync28(assetFilePath)) {
6837
+ assets[assetPath] = readFileSync21(assetFilePath, "utf-8");
6356
6838
  }
6357
6839
  }
6358
6840
  return {
@@ -6367,7 +6849,7 @@ function isHostedSourceSnapshotFile(path) {
6367
6849
  function readHostedSourceSnapshot(sourcePath) {
6368
6850
  if (!sourcePath) return void 0;
6369
6851
  const resolvedSourcePath = resolveUserPath(sourcePath);
6370
- if (!existsSync27(resolvedSourcePath)) {
6852
+ if (!existsSync28(resolvedSourcePath)) {
6371
6853
  return void 0;
6372
6854
  }
6373
6855
  const files = {};
@@ -6379,19 +6861,19 @@ function readHostedSourceSnapshot(sourcePath) {
6379
6861
  "build",
6380
6862
  "coverage"
6381
6863
  ]);
6382
- const rootPrefix = basename2(resolvedSourcePath);
6864
+ const rootPrefix = basename3(resolvedSourcePath);
6383
6865
  const walk = (absoluteDir, relativeDir) => {
6384
- for (const entry of readdirSync7(absoluteDir, { withFileTypes: true })) {
6866
+ for (const entry of readdirSync8(absoluteDir, { withFileTypes: true })) {
6385
6867
  if (ignoredDirNames.has(entry.name)) continue;
6386
- const absolutePath = join28(absoluteDir, entry.name);
6387
- const relativePath = join28(relativeDir, entry.name).replace(/\\/g, "/");
6868
+ const absolutePath = join29(absoluteDir, entry.name);
6869
+ const relativePath = join29(relativeDir, entry.name).replace(/\\/g, "/");
6388
6870
  if (entry.isDirectory()) {
6389
6871
  walk(absolutePath, relativePath);
6390
6872
  continue;
6391
6873
  }
6392
6874
  if (!entry.isFile()) continue;
6393
6875
  if (!isHostedSourceSnapshotFile(relativePath)) continue;
6394
- files[relativePath] = readFileSync20(absolutePath, "utf-8");
6876
+ files[relativePath] = readFileSync21(absolutePath, "utf-8");
6395
6877
  }
6396
6878
  };
6397
6879
  walk(resolvedSourcePath, rootPrefix);
@@ -6542,16 +7024,16 @@ async function printRegistryIntelligenceSummary(namespace, jsonOutput = false) {
6542
7024
  }
6543
7025
  async function printHostedExecutionPackBundle(essencePath, namespace, jsonOutput = false, writeContext = false) {
6544
7026
  const client = getPublicAPIClient();
6545
- const resolvedPath = essencePath ? resolveUserPath(essencePath) : join28(process.cwd(), "decantr.essence.json");
6546
- if (!existsSync27(resolvedPath)) {
7027
+ const resolvedPath = essencePath ? resolveUserPath(essencePath) : join29(process.cwd(), "decantr.essence.json");
7028
+ if (!existsSync28(resolvedPath)) {
6547
7029
  throw new Error(`Essence file not found at ${resolvedPath}`);
6548
7030
  }
6549
- const essence = JSON.parse(readFileSync20(resolvedPath, "utf-8"));
7031
+ const essence = JSON.parse(readFileSync21(resolvedPath, "utf-8"));
6550
7032
  const bundle = await client.compileExecutionPacks(essence, namespace ? { namespace } : void 0);
6551
7033
  let writtenContextPaths = [];
6552
7034
  if (writeContext) {
6553
- const contextDir = join28(process.cwd(), ".decantr", "context");
6554
- mkdirSync13(contextDir, { recursive: true });
7035
+ const contextDir = join29(process.cwd(), ".decantr", "context");
7036
+ mkdirSync14(contextDir, { recursive: true });
6555
7037
  const written = writeExecutionPackBundleArtifacts(
6556
7038
  contextDir,
6557
7039
  bundle
@@ -6576,7 +7058,7 @@ async function printHostedExecutionPackBundle(essencePath, namespace, jsonOutput
6576
7058
  console.log(` Sections: ${typedBundle.sections.length}`);
6577
7059
  console.log(` Mutations: ${typedBundle.mutations.length}`);
6578
7060
  if (writeContext) {
6579
- console.log(` Context bundle: ${join28(process.cwd(), ".decantr", "context")}`);
7061
+ console.log(` Context bundle: ${join29(process.cwd(), ".decantr", "context")}`);
6580
7062
  console.log(` Files written: ${writtenContextPaths.length}`);
6581
7063
  }
6582
7064
  console.log("");
@@ -6588,11 +7070,11 @@ async function printHostedExecutionPackBundle(essencePath, namespace, jsonOutput
6588
7070
  }
6589
7071
  }
6590
7072
  function resolvePagePackIdForRoute(essencePath, route) {
6591
- if (!existsSync27(essencePath)) {
7073
+ if (!existsSync28(essencePath)) {
6592
7074
  throw new Error(`Essence file not found at ${essencePath}`);
6593
7075
  }
6594
- const essence = JSON.parse(readFileSync20(essencePath, "utf-8"));
6595
- if (!isV47(essence)) {
7076
+ const essence = JSON.parse(readFileSync21(essencePath, "utf-8"));
7077
+ if (!isV48(essence)) {
6596
7078
  throw new Error("Route-based pack resolution requires Essence v4.0.0.");
6597
7079
  }
6598
7080
  const target = essence.blueprint.routes?.[route];
@@ -6606,14 +7088,14 @@ function resolvePagePackIdForRoute(essencePath, route) {
6606
7088
  }
6607
7089
  async function printHostedSelectedExecutionPack(packType, id, essencePath, namespace, jsonOutput = false, writeContext = false) {
6608
7090
  const client = getPublicAPIClient();
6609
- const resolvedPath = essencePath ? resolveUserPath(essencePath) : join28(process.cwd(), "decantr.essence.json");
6610
- if (!existsSync27(resolvedPath)) {
7091
+ const resolvedPath = essencePath ? resolveUserPath(essencePath) : join29(process.cwd(), "decantr.essence.json");
7092
+ if (!existsSync28(resolvedPath)) {
6611
7093
  throw new Error(`Essence file not found at ${resolvedPath}`);
6612
7094
  }
6613
7095
  if ((packType === "section" || packType === "page" || packType === "mutation") && !id) {
6614
7096
  throw new Error(`Pack type "${packType}" requires an id.`);
6615
7097
  }
6616
- const essence = JSON.parse(readFileSync20(resolvedPath, "utf-8"));
7098
+ const essence = JSON.parse(readFileSync21(resolvedPath, "utf-8"));
6617
7099
  const selected = await client.selectExecutionPack(
6618
7100
  {
6619
7101
  essence,
@@ -6624,17 +7106,17 @@ async function printHostedSelectedExecutionPack(packType, id, essencePath, names
6624
7106
  );
6625
7107
  let writtenContextDir = null;
6626
7108
  if (writeContext) {
6627
- const contextDir = join28(process.cwd(), ".decantr", "context");
6628
- mkdirSync13(contextDir, { recursive: true });
6629
- writeFileSync16(
6630
- join28(contextDir, "pack-manifest.json"),
7109
+ const contextDir = join29(process.cwd(), ".decantr", "context");
7110
+ mkdirSync14(contextDir, { recursive: true });
7111
+ writeFileSync17(
7112
+ join29(contextDir, "pack-manifest.json"),
6631
7113
  JSON.stringify(selected.manifest, null, 2) + "\n"
6632
7114
  );
6633
7115
  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);
6634
7116
  const markdownFile = manifestEntry?.markdown ?? `${selected.selector.packType}${selected.selector.id ? `-${selected.selector.id}` : ""}-pack.md`;
6635
7117
  const jsonFile = manifestEntry?.json ?? `${selected.selector.packType}${selected.selector.id ? `-${selected.selector.id}` : ""}-pack.json`;
6636
- writeFileSync16(join28(contextDir, markdownFile), selected.pack.renderedMarkdown);
6637
- writeFileSync16(join28(contextDir, jsonFile), JSON.stringify(selected.pack, null, 2) + "\n");
7118
+ writeFileSync17(join29(contextDir, markdownFile), selected.pack.renderedMarkdown);
7119
+ writeFileSync17(join29(contextDir, jsonFile), JSON.stringify(selected.pack, null, 2) + "\n");
6638
7120
  writtenContextDir = contextDir;
6639
7121
  }
6640
7122
  if (jsonOutput) {
@@ -6659,20 +7141,20 @@ async function printHostedSelectedExecutionPack(packType, id, essencePath, names
6659
7141
  }
6660
7142
  async function printHostedExecutionPackManifest(essencePath, namespace, jsonOutput = false, writeContext = false) {
6661
7143
  const client = getPublicAPIClient();
6662
- const resolvedPath = essencePath ? resolveUserPath(essencePath) : join28(process.cwd(), "decantr.essence.json");
6663
- if (!existsSync27(resolvedPath)) {
7144
+ const resolvedPath = essencePath ? resolveUserPath(essencePath) : join29(process.cwd(), "decantr.essence.json");
7145
+ if (!existsSync28(resolvedPath)) {
6664
7146
  throw new Error(`Essence file not found at ${resolvedPath}`);
6665
7147
  }
6666
- const essence = JSON.parse(readFileSync20(resolvedPath, "utf-8"));
7148
+ const essence = JSON.parse(readFileSync21(resolvedPath, "utf-8"));
6667
7149
  const manifest = await client.getExecutionPackManifest(
6668
7150
  essence,
6669
7151
  namespace ? { namespace } : void 0
6670
7152
  );
6671
7153
  let writtenContextDir = null;
6672
7154
  if (writeContext) {
6673
- const contextDir = join28(process.cwd(), ".decantr", "context");
6674
- mkdirSync13(contextDir, { recursive: true });
6675
- writeFileSync16(join28(contextDir, "pack-manifest.json"), JSON.stringify(manifest, null, 2) + "\n");
7155
+ const contextDir = join29(process.cwd(), ".decantr", "context");
7156
+ mkdirSync14(contextDir, { recursive: true });
7157
+ writeFileSync17(join29(contextDir, "pack-manifest.json"), JSON.stringify(manifest, null, 2) + "\n");
6676
7158
  writtenContextDir = contextDir;
6677
7159
  }
6678
7160
  if (jsonOutput) {
@@ -6693,14 +7175,14 @@ async function printHostedExecutionPackManifest(essencePath, namespace, jsonOutp
6693
7175
  }
6694
7176
  }
6695
7177
  async function hydrateHostedExecutionPacksIfMissing(projectRoot, namespace = "@official") {
6696
- const contextDir = join28(projectRoot, ".decantr", "context");
6697
- const reviewPackPath = join28(contextDir, "review-pack.json");
6698
- const manifestPath = join28(contextDir, "pack-manifest.json");
6699
- if (existsSync27(reviewPackPath) && existsSync27(manifestPath)) {
7178
+ const contextDir = join29(projectRoot, ".decantr", "context");
7179
+ const reviewPackPath = join29(contextDir, "review-pack.json");
7180
+ const manifestPath = join29(contextDir, "pack-manifest.json");
7181
+ if (existsSync28(reviewPackPath) && existsSync28(manifestPath)) {
6700
7182
  return { attempted: false, hydrated: false };
6701
7183
  }
6702
- const essencePath = join28(projectRoot, "decantr.essence.json");
6703
- if (!existsSync27(essencePath)) {
7184
+ const essencePath = join29(projectRoot, "decantr.essence.json");
7185
+ if (!existsSync28(essencePath)) {
6704
7186
  return { attempted: false, hydrated: false };
6705
7187
  }
6706
7188
  const reviewHydration = await hydrateHostedReviewPackIfMissing(projectRoot, namespace);
@@ -6709,9 +7191,9 @@ async function hydrateHostedExecutionPacksIfMissing(projectRoot, namespace = "@o
6709
7191
  }
6710
7192
  try {
6711
7193
  const client = getPublicAPIClient();
6712
- const essence = JSON.parse(readFileSync20(essencePath, "utf-8"));
7194
+ const essence = JSON.parse(readFileSync21(essencePath, "utf-8"));
6713
7195
  const bundle = await client.compileExecutionPacks(essence, { namespace });
6714
- mkdirSync13(contextDir, { recursive: true });
7196
+ mkdirSync14(contextDir, { recursive: true });
6715
7197
  writeExecutionPackBundleArtifacts(contextDir, bundle);
6716
7198
  return { attempted: true, hydrated: true, scope: "bundle" };
6717
7199
  } catch {
@@ -6719,19 +7201,19 @@ async function hydrateHostedExecutionPacksIfMissing(projectRoot, namespace = "@o
6719
7201
  }
6720
7202
  }
6721
7203
  async function hydrateHostedReviewPackIfMissing(projectRoot, namespace = "@official") {
6722
- const contextDir = join28(projectRoot, ".decantr", "context");
6723
- const reviewPackPath = join28(contextDir, "review-pack.json");
6724
- const manifestPath = join28(contextDir, "pack-manifest.json");
6725
- if (existsSync27(reviewPackPath) && existsSync27(manifestPath)) {
7204
+ const contextDir = join29(projectRoot, ".decantr", "context");
7205
+ const reviewPackPath = join29(contextDir, "review-pack.json");
7206
+ const manifestPath = join29(contextDir, "pack-manifest.json");
7207
+ if (existsSync28(reviewPackPath) && existsSync28(manifestPath)) {
6726
7208
  return { attempted: false, hydrated: false };
6727
7209
  }
6728
- const essencePath = join28(projectRoot, "decantr.essence.json");
6729
- if (!existsSync27(essencePath)) {
7210
+ const essencePath = join29(projectRoot, "decantr.essence.json");
7211
+ if (!existsSync28(essencePath)) {
6730
7212
  return { attempted: false, hydrated: false };
6731
7213
  }
6732
7214
  try {
6733
7215
  const client = getPublicAPIClient();
6734
- const essence = JSON.parse(readFileSync20(essencePath, "utf-8"));
7216
+ const essence = JSON.parse(readFileSync21(essencePath, "utf-8"));
6735
7217
  const selected = await client.selectExecutionPack(
6736
7218
  {
6737
7219
  essence,
@@ -6739,14 +7221,14 @@ async function hydrateHostedReviewPackIfMissing(projectRoot, namespace = "@offic
6739
7221
  },
6740
7222
  { namespace }
6741
7223
  );
6742
- mkdirSync13(contextDir, { recursive: true });
6743
- writeFileSync16(join28(contextDir, "review-pack.md"), selected.pack.renderedMarkdown);
6744
- writeFileSync16(
6745
- join28(contextDir, "review-pack.json"),
7224
+ mkdirSync14(contextDir, { recursive: true });
7225
+ writeFileSync17(join29(contextDir, "review-pack.md"), selected.pack.renderedMarkdown);
7226
+ writeFileSync17(
7227
+ join29(contextDir, "review-pack.json"),
6746
7228
  JSON.stringify(selected.pack, null, 2) + "\n"
6747
7229
  );
6748
- if (!existsSync27(manifestPath)) {
6749
- writeFileSync16(manifestPath, JSON.stringify(selected.manifest, null, 2) + "\n");
7230
+ if (!existsSync28(manifestPath)) {
7231
+ writeFileSync17(manifestPath, JSON.stringify(selected.manifest, null, 2) + "\n");
6750
7232
  }
6751
7233
  return { attempted: true, hydrated: true, scope: "review" };
6752
7234
  } catch {
@@ -6756,17 +7238,17 @@ async function hydrateHostedReviewPackIfMissing(projectRoot, namespace = "@offic
6756
7238
  async function printHostedFileCritique(sourcePath, namespace, jsonOutput = false, essencePath, treatmentsPath) {
6757
7239
  const client = getPublicAPIClient();
6758
7240
  const resolvedSourcePath = resolveUserPath(sourcePath);
6759
- const resolvedEssencePath = essencePath ? resolveUserPath(essencePath) : join28(process.cwd(), "decantr.essence.json");
6760
- const resolvedTreatmentsPath = treatmentsPath ? resolveUserPath(treatmentsPath) : join28(process.cwd(), "src", "styles", "treatments.css");
6761
- if (!existsSync27(resolvedSourcePath)) {
7241
+ const resolvedEssencePath = essencePath ? resolveUserPath(essencePath) : join29(process.cwd(), "decantr.essence.json");
7242
+ const resolvedTreatmentsPath = treatmentsPath ? resolveUserPath(treatmentsPath) : join29(process.cwd(), "src", "styles", "treatments.css");
7243
+ if (!existsSync28(resolvedSourcePath)) {
6762
7244
  throw new Error(`Source file not found at ${resolvedSourcePath}`);
6763
7245
  }
6764
- if (!existsSync27(resolvedEssencePath)) {
7246
+ if (!existsSync28(resolvedEssencePath)) {
6765
7247
  throw new Error(`Essence file not found at ${resolvedEssencePath}`);
6766
7248
  }
6767
- const code = readFileSync20(resolvedSourcePath, "utf-8");
6768
- const essence = JSON.parse(readFileSync20(resolvedEssencePath, "utf-8"));
6769
- const treatmentsCss = existsSync27(resolvedTreatmentsPath) ? readFileSync20(resolvedTreatmentsPath, "utf-8") : void 0;
7249
+ const code = readFileSync21(resolvedSourcePath, "utf-8");
7250
+ const essence = JSON.parse(readFileSync21(resolvedEssencePath, "utf-8"));
7251
+ const treatmentsCss = existsSync28(resolvedTreatmentsPath) ? readFileSync21(resolvedTreatmentsPath, "utf-8") : void 0;
6770
7252
  const report = await client.critiqueFile(
6771
7253
  {
6772
7254
  essence,
@@ -6790,11 +7272,11 @@ async function printHostedFileCritique(sourcePath, namespace, jsonOutput = false
6790
7272
  }
6791
7273
  async function printHostedProjectAudit(namespace, jsonOutput = false, essencePath, distPath, sourcesPath) {
6792
7274
  const client = getPublicAPIClient();
6793
- const resolvedEssencePath = essencePath ? resolveUserPath(essencePath) : join28(process.cwd(), "decantr.essence.json");
6794
- if (!existsSync27(resolvedEssencePath)) {
7275
+ const resolvedEssencePath = essencePath ? resolveUserPath(essencePath) : join29(process.cwd(), "decantr.essence.json");
7276
+ if (!existsSync28(resolvedEssencePath)) {
6795
7277
  throw new Error(`Essence file not found at ${resolvedEssencePath}`);
6796
7278
  }
6797
- const essence = JSON.parse(readFileSync20(resolvedEssencePath, "utf-8"));
7279
+ const essence = JSON.parse(readFileSync21(resolvedEssencePath, "utf-8"));
6798
7280
  const dist = readHostedDistSnapshot(distPath);
6799
7281
  const sources = readHostedSourceSnapshot(sourcesPath);
6800
7282
  const report = await client.auditProject(
@@ -6812,7 +7294,7 @@ async function printHostedProjectAudit(namespace, jsonOutput = false, essencePat
6812
7294
  console.log(heading2("Hosted Project Audit"));
6813
7295
  console.log(` Essence: ${resolvedEssencePath}`);
6814
7296
  console.log(
6815
- ` Dist snapshot: ${dist ? distPath ? resolveUserPath(distPath) : join28(process.cwd(), "dist") : "none"}`
7297
+ ` Dist snapshot: ${dist ? distPath ? resolveUserPath(distPath) : join29(process.cwd(), "dist") : "none"}`
6816
7298
  );
6817
7299
  console.log(
6818
7300
  ` Source snapshot: ${sources && sourcesPath ? resolveUserPath(sourcesPath) : "none"}`
@@ -6915,21 +7397,21 @@ function patternCandidateFromRegistryItem(item, source) {
6915
7397
  function readSuggestCodeContext(route, file) {
6916
7398
  const pieces = [];
6917
7399
  if (file) {
6918
- const resolved = isAbsolute(file) ? file : join28(process.cwd(), file);
6919
- if (existsSync27(resolved)) {
6920
- pieces.push(readFileSync20(resolved, "utf-8"));
7400
+ const resolved = isAbsolute(file) ? file : join29(process.cwd(), file);
7401
+ if (existsSync28(resolved)) {
7402
+ pieces.push(readFileSync21(resolved, "utf-8"));
6921
7403
  }
6922
7404
  }
6923
7405
  if (route) {
6924
- const analysisPath = join28(process.cwd(), ".decantr", "analysis.json");
6925
- if (existsSync27(analysisPath)) {
7406
+ const analysisPath = join29(process.cwd(), ".decantr", "analysis.json");
7407
+ if (existsSync28(analysisPath)) {
6926
7408
  try {
6927
- const analysis = JSON.parse(readFileSync20(analysisPath, "utf-8"));
7409
+ const analysis = JSON.parse(readFileSync21(analysisPath, "utf-8"));
6928
7410
  const routeEntry = analysis.routes?.routes?.find((entry) => entry.path === route);
6929
7411
  if (routeEntry?.file) {
6930
- const resolved = join28(process.cwd(), routeEntry.file);
6931
- if (existsSync27(resolved)) {
6932
- pieces.push(readFileSync20(resolved, "utf-8"));
7412
+ const resolved = join29(process.cwd(), routeEntry.file);
7413
+ if (existsSync28(resolved)) {
7414
+ pieces.push(readFileSync21(resolved, "utf-8"));
6933
7415
  }
6934
7416
  }
6935
7417
  } catch {
@@ -6986,7 +7468,7 @@ async function cmdSuggest(query, options = {}) {
6986
7468
  }
6987
7469
  }
6988
7470
  const registryClient = new RegistryClient({
6989
- cacheDir: join28(process.cwd(), ".decantr", "cache")
7471
+ cacheDir: join29(process.cwd(), ".decantr", "cache")
6990
7472
  });
6991
7473
  const code = options.fromCode || options.file ? readSuggestCodeContext(options.route, options.file) : "";
6992
7474
  const candidates = await loadPatternDiscoveryCandidates(registryClient);
@@ -7048,7 +7530,7 @@ async function cmdGet(type, id) {
7048
7530
  }
7049
7531
  const apiType = CONTENT_TYPE_TO_API_CONTENT_TYPE3[type];
7050
7532
  const registryClient = new RegistryClient({
7051
- cacheDir: join28(process.cwd(), ".decantr", "cache")
7533
+ cacheDir: join29(process.cwd(), ".decantr", "cache")
7052
7534
  });
7053
7535
  const result = await registryClient.fetchContentItem(apiType, id);
7054
7536
  if (result) {
@@ -7065,10 +7547,10 @@ async function cmdGet(type, id) {
7065
7547
  return;
7066
7548
  }
7067
7549
  async function cmdValidate(path) {
7068
- const essencePath = path || join28(process.cwd(), "decantr.essence.json");
7550
+ const essencePath = path || join29(process.cwd(), "decantr.essence.json");
7069
7551
  let raw;
7070
7552
  try {
7071
- raw = readFileSync20(essencePath, "utf-8");
7553
+ raw = readFileSync21(essencePath, "utf-8");
7072
7554
  } catch {
7073
7555
  console.error(error3(`Could not read ${essencePath}`));
7074
7556
  process.exitCode = 1;
@@ -7082,7 +7564,7 @@ async function cmdValidate(path) {
7082
7564
  process.exitCode = 1;
7083
7565
  return;
7084
7566
  }
7085
- const detectedVersion = isV47(essence) ? "v4" : "legacy";
7567
+ const detectedVersion = isV48(essence) ? "v4" : "legacy";
7086
7568
  console.log(`${DIM14}Detected essence version: ${detectedVersion}${RESET14}`);
7087
7569
  const result = validateEssence2(essence);
7088
7570
  if (result.valid) {
@@ -7135,7 +7617,7 @@ async function cmdList(type, sort, recommended, intelligenceSource, blueprintSet
7135
7617
  return;
7136
7618
  }
7137
7619
  const registryClient = new RegistryClient({
7138
- cacheDir: join28(process.cwd(), ".decantr", "cache")
7620
+ cacheDir: join29(process.cwd(), ".decantr", "cache")
7139
7621
  });
7140
7622
  const result = await registryClient.fetchContentList(
7141
7623
  type,
@@ -7218,10 +7700,10 @@ ${CYAN8}Telemetry enabled.${RESET14} Decantr will send privacy-filtered CLI prod
7218
7700
  }
7219
7701
  function readCliPackageVersion() {
7220
7702
  const here = dirname4(fileURLToPath2(import.meta.url));
7221
- const candidates = [join28(here, "..", "package.json"), join28(here, "..", "..", "package.json")];
7703
+ const candidates = [join29(here, "..", "package.json"), join29(here, "..", "..", "package.json")];
7222
7704
  for (const candidate of candidates) {
7223
7705
  try {
7224
- const pkg = JSON.parse(readFileSync20(candidate, "utf-8"));
7706
+ const pkg = JSON.parse(readFileSync21(candidate, "utf-8"));
7225
7707
  if (pkg.version) return pkg.version;
7226
7708
  } catch {
7227
7709
  }
@@ -7232,19 +7714,19 @@ function timestampForFile() {
7232
7714
  return (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
7233
7715
  }
7234
7716
  function backupExistingEssence(projectRoot, label) {
7235
- const essencePath = join28(projectRoot, "decantr.essence.json");
7236
- if (!existsSync27(essencePath)) return null;
7237
- const backupPath = join28(
7717
+ const essencePath = join29(projectRoot, "decantr.essence.json");
7718
+ if (!existsSync28(essencePath)) return null;
7719
+ const backupPath = join29(
7238
7720
  projectRoot,
7239
7721
  `decantr.essence.${label}.${timestampForFile()}.backup.json`
7240
7722
  );
7241
- writeFileSync16(backupPath, readFileSync20(essencePath, "utf-8"), "utf-8");
7723
+ writeFileSync17(backupPath, readFileSync21(essencePath, "utf-8"), "utf-8");
7242
7724
  return backupPath;
7243
7725
  }
7244
7726
  function writeBrownfieldProjectJson(input) {
7245
- const decantrDir = join28(input.projectRoot, ".decantr");
7246
- mkdirSync13(join28(decantrDir, "context"), { recursive: true });
7247
- mkdirSync13(join28(decantrDir, "cache"), { recursive: true });
7727
+ const decantrDir = join29(input.projectRoot, ".decantr");
7728
+ mkdirSync14(join29(decantrDir, "context"), { recursive: true });
7729
+ mkdirSync14(join29(decantrDir, "cache"), { recursive: true });
7248
7730
  const now = (/* @__PURE__ */ new Date()).toISOString();
7249
7731
  const projectJson = {
7250
7732
  detected: {
@@ -7285,7 +7767,7 @@ function writeBrownfieldProjectJson(input) {
7285
7767
  }
7286
7768
  }
7287
7769
  };
7288
- writeFileSync16(join28(decantrDir, "project.json"), JSON.stringify(projectJson, null, 2) + "\n");
7770
+ writeFileSync17(join29(decantrDir, "project.json"), JSON.stringify(projectJson, null, 2) + "\n");
7289
7771
  }
7290
7772
  async function applyAcceptedBrownfieldProposal(input) {
7291
7773
  const proposal = readBrownfieldProposal(input.projectRoot);
@@ -7301,8 +7783,8 @@ async function applyAcceptedBrownfieldProposal(input) {
7301
7783
  process.exitCode = 1;
7302
7784
  return;
7303
7785
  }
7304
- const essencePath = join28(input.projectRoot, "decantr.essence.json");
7305
- const hasEssence = existsSync27(essencePath);
7786
+ const essencePath = join29(input.projectRoot, "decantr.essence.json");
7787
+ const hasEssence = existsSync28(essencePath);
7306
7788
  let essence;
7307
7789
  let backupPath = null;
7308
7790
  if (input.mode === "accept" && hasEssence) {
@@ -7316,8 +7798,8 @@ async function applyAcceptedBrownfieldProposal(input) {
7316
7798
  return;
7317
7799
  }
7318
7800
  if (input.mode === "merge" && hasEssence) {
7319
- const existing = JSON.parse(readFileSync20(essencePath, "utf-8"));
7320
- if (!isV47(existing)) {
7801
+ const existing = JSON.parse(readFileSync21(essencePath, "utf-8"));
7802
+ if (!isV48(existing)) {
7321
7803
  console.log(
7322
7804
  error3(
7323
7805
  "Existing essence is not v4. Run `decantr migrate --to v4` before merging a brownfield proposal."
@@ -7353,9 +7835,9 @@ async function applyAcceptedBrownfieldProposal(input) {
7353
7835
  assistantBridge: input.assistantBridge,
7354
7836
  mode: input.mode
7355
7837
  });
7356
- writeFileSync16(essencePath, JSON.stringify(essence, null, 2) + "\n", "utf-8");
7838
+ writeFileSync17(essencePath, JSON.stringify(essence, null, 2) + "\n", "utf-8");
7357
7839
  const registryClient = new RegistryClient({
7358
- cacheDir: join28(input.projectRoot, ".decantr", "cache"),
7840
+ cacheDir: join29(input.projectRoot, ".decantr", "cache"),
7359
7841
  offline: true,
7360
7842
  projectRoot: input.projectRoot
7361
7843
  });
@@ -7509,7 +7991,7 @@ async function cmdInit(args) {
7509
7991
  }
7510
7992
  }
7511
7993
  const registryClient = new RegistryClient({
7512
- cacheDir: join28(projectRoot, ".decantr", "cache"),
7994
+ cacheDir: join29(projectRoot, ".decantr", "cache"),
7513
7995
  apiUrl: args.registry,
7514
7996
  offline: args.offline,
7515
7997
  projectRoot
@@ -7839,7 +8321,7 @@ ${YELLOW9}You're offline. Scaffolding Decantr default.${RESET14}`);
7839
8321
  if (appliedRuleFiles.length > 0) {
7840
8322
  console.log(` ${dim3(`Rule bridge applied: ${appliedRuleFiles.join(", ")}`)}`);
7841
8323
  }
7842
- if (!existsSync27(join28(projectRoot, "package.json"))) {
8324
+ if (!existsSync28(join29(projectRoot, "package.json"))) {
7843
8325
  console.log("");
7844
8326
  console.log(
7845
8327
  dim3(` Note: ${cyan3("decantr init")} created Decantr contract/context files only.`)
@@ -7850,7 +8332,7 @@ ${YELLOW9}You're offline. Scaffolding Decantr default.${RESET14}`);
7850
8332
  )
7851
8333
  );
7852
8334
  }
7853
- const hasCompiledPacks = existsSync27(join28(projectRoot, ".decantr", "context", "scaffold-pack.md"));
8335
+ const hasCompiledPacks = existsSync28(join29(projectRoot, ".decantr", "context", "scaffold-pack.md"));
7854
8336
  console.log("");
7855
8337
  console.log(" Next steps:");
7856
8338
  if (hasCompiledPacks) {
@@ -7890,7 +8372,7 @@ ${YELLOW9}You're offline. Scaffolding Decantr default.${RESET14}`);
7890
8372
  console.log(` ${cyan3("decantr upgrade")} Update to latest patterns`);
7891
8373
  console.log(` ${cyan3("decantr check")} Detect drift issues`);
7892
8374
  console.log(` ${cyan3("decantr migrate --to v4")} Migrate older essence files to v4`);
7893
- const essenceContent = readFileSync20(result.essencePath, "utf-8");
8375
+ const essenceContent = readFileSync21(result.essencePath, "utf-8");
7894
8376
  const essence = JSON.parse(essenceContent);
7895
8377
  const validation = validateEssence2(essence);
7896
8378
  if (!validation.valid) {
@@ -7899,7 +8381,7 @@ Validation warnings: ${validation.errors.join(", ")}`));
7899
8381
  }
7900
8382
  console.log("");
7901
8383
  let promptPages;
7902
- if (isV47(essence)) {
8384
+ if (isV48(essence)) {
7903
8385
  const allPages = essence.blueprint.sections.flatMap(
7904
8386
  (s) => s.pages.map((p) => ({ ...p, _sectionId: s.id, _shell: s.shell }))
7905
8387
  );
@@ -7946,25 +8428,25 @@ Validation warnings: ${validation.errors.join(", ")}`));
7946
8428
  }
7947
8429
  async function cmdStatus() {
7948
8430
  const projectRoot = process.cwd();
7949
- const essencePath = join28(projectRoot, "decantr.essence.json");
7950
- const projectJsonPath = join28(projectRoot, ".decantr", "project.json");
8431
+ const essencePath = join29(projectRoot, "decantr.essence.json");
8432
+ const projectJsonPath = join29(projectRoot, ".decantr", "project.json");
7951
8433
  console.log(heading2("Decantr Project Status"));
7952
- if (!existsSync27(essencePath)) {
8434
+ if (!existsSync28(essencePath)) {
7953
8435
  console.log(`${RED11}No decantr.essence.json found.${RESET14}`);
7954
8436
  console.log(dim3('Run "decantr init" to create one.'));
7955
8437
  return;
7956
8438
  }
7957
8439
  try {
7958
- const essence = JSON.parse(readFileSync20(essencePath, "utf-8"));
8440
+ const essence = JSON.parse(readFileSync21(essencePath, "utf-8"));
7959
8441
  const validation = validateEssence2(essence);
7960
- const essenceVersion = isV47(essence) ? "v4" : "legacy";
8442
+ const essenceVersion = isV48(essence) ? "v4" : "legacy";
7961
8443
  console.log(`${BOLD7}Essence:${RESET14}`);
7962
8444
  if (validation.valid) {
7963
8445
  console.log(` ${GREEN14}Valid${RESET14} (${essenceVersion})`);
7964
8446
  } else {
7965
8447
  console.log(` ${RED11}Invalid: ${validation.errors.join(", ")}${RESET14}`);
7966
8448
  }
7967
- if (isV47(essence)) {
8449
+ if (isV48(essence)) {
7968
8450
  const v4 = essence;
7969
8451
  const sections = v4.blueprint.sections;
7970
8452
  const flatPages = sections.flatMap((section) => section.pages ?? []);
@@ -8003,9 +8485,9 @@ async function cmdStatus() {
8003
8485
  }
8004
8486
  console.log("");
8005
8487
  console.log(`${BOLD7}Sync Status:${RESET14}`);
8006
- if (existsSync27(projectJsonPath)) {
8488
+ if (existsSync28(projectJsonPath)) {
8007
8489
  try {
8008
- const projectJson = JSON.parse(readFileSync20(projectJsonPath, "utf-8"));
8490
+ const projectJson = JSON.parse(readFileSync21(projectJsonPath, "utf-8"));
8009
8491
  const syncStatus = projectJson.sync?.status || "unknown";
8010
8492
  const lastSync = projectJson.sync?.lastSync || "never";
8011
8493
  const source = projectJson.sync?.registrySource || "unknown";
@@ -8023,7 +8505,7 @@ async function cmdStatus() {
8023
8505
  }
8024
8506
  async function cmdSync() {
8025
8507
  const projectRoot = process.cwd();
8026
- const cacheDir = join28(projectRoot, ".decantr", "cache");
8508
+ const cacheDir = join29(projectRoot, ".decantr", "cache");
8027
8509
  console.log(heading2("Syncing registry content..."));
8028
8510
  const result = await syncRegistry(cacheDir);
8029
8511
  if (result.synced.length > 0) {
@@ -8218,14 +8700,14 @@ ${BOLD7}Examples:${RESET14}
8218
8700
  process.exitCode = 1;
8219
8701
  return;
8220
8702
  }
8221
- const themePath = join28(projectRoot, ".decantr", "custom", "themes", `${name}.json`);
8222
- if (!existsSync27(themePath)) {
8703
+ const themePath = join29(projectRoot, ".decantr", "custom", "themes", `${name}.json`);
8704
+ if (!existsSync28(themePath)) {
8223
8705
  console.error(error3(`Theme "${name}" not found at ${themePath}`));
8224
8706
  process.exitCode = 1;
8225
8707
  return;
8226
8708
  }
8227
8709
  try {
8228
- const theme = JSON.parse(readFileSync20(themePath, "utf-8"));
8710
+ const theme = JSON.parse(readFileSync21(themePath, "utf-8"));
8229
8711
  const result = validateCustomTheme(theme);
8230
8712
  if (result.valid) {
8231
8713
  console.log(success3(`Custom theme "${name}" is valid`));
@@ -8293,14 +8775,524 @@ ${BOLD7}Examples:${RESET14}
8293
8775
  process.exitCode = 1;
8294
8776
  }
8295
8777
  }
8778
+ function parseLooseArgs(args, startIndex = 1) {
8779
+ const flags = {};
8780
+ const positional = [];
8781
+ for (let index = startIndex; index < args.length; index += 1) {
8782
+ const arg = args[index];
8783
+ if (arg === "-y") {
8784
+ flags.yes = true;
8785
+ continue;
8786
+ }
8787
+ if (arg.startsWith("--no-")) {
8788
+ flags[arg.slice(5)] = false;
8789
+ continue;
8790
+ }
8791
+ if (arg.startsWith("--")) {
8792
+ const body = arg.slice(2);
8793
+ const equalsIndex = body.indexOf("=");
8794
+ if (equalsIndex !== -1) {
8795
+ flags[body.slice(0, equalsIndex)] = body.slice(equalsIndex + 1);
8796
+ continue;
8797
+ }
8798
+ if (args[index + 1] && !args[index + 1].startsWith("-")) {
8799
+ flags[body] = args[++index];
8800
+ } else {
8801
+ flags[body] = true;
8802
+ }
8803
+ continue;
8804
+ }
8805
+ positional.push(arg);
8806
+ }
8807
+ return { flags, positional };
8808
+ }
8809
+ function flagString(flags, key) {
8810
+ const value = flags[key];
8811
+ return typeof value === "string" ? value : void 0;
8812
+ }
8813
+ function flagBoolean(flags, key, defaultValue = false) {
8814
+ const value = flags[key];
8815
+ if (typeof value === "boolean") return value;
8816
+ if (typeof value === "string") return value !== "false";
8817
+ return defaultValue;
8818
+ }
8819
+ function withoutWorkflowOnlyFlags(args) {
8820
+ const stripped = [];
8821
+ const flagsWithValues = /* @__PURE__ */ new Set(["--project"]);
8822
+ for (let index = 1; index < args.length; index += 1) {
8823
+ const arg = args[index];
8824
+ if (arg === "--brownfield" || arg === "--local-patterns" || arg === "--workspace" || arg === "--baseline") {
8825
+ continue;
8826
+ }
8827
+ if (arg.startsWith("--project=")) {
8828
+ continue;
8829
+ }
8830
+ if (flagsWithValues.has(arg)) {
8831
+ index += 1;
8832
+ continue;
8833
+ }
8834
+ stripped.push(arg);
8835
+ }
8836
+ return stripped;
8837
+ }
8838
+ function resolveWorkflowProject(flags) {
8839
+ const projectArg = flagString(flags, "project");
8840
+ const workspaceInfo = resolveWorkspaceInfo(process.cwd(), projectArg);
8841
+ if (workspaceInfo.requiresProjectSelection) {
8842
+ console.log(error3("This looks like a workspace root with multiple app candidates."));
8843
+ console.log(dim3(`Use --project=<path>. Candidates: ${workspaceInfo.appCandidates.join(", ")}`));
8844
+ process.exitCode = 1;
8845
+ return null;
8846
+ }
8847
+ return workspaceInfo;
8848
+ }
8849
+ function printWorkflowPlan(title, steps) {
8850
+ console.log(heading2(title));
8851
+ console.log(" Decantr will run this workflow:");
8852
+ for (const step of steps) {
8853
+ console.log(` ${cyan3(step)}`);
8854
+ }
8855
+ console.log("");
8856
+ }
8857
+ async function cmdSetupWorkflow(args) {
8858
+ const { flags } = parseLooseArgs(args);
8859
+ const workspaceInfo = resolveWorkflowProject(flags);
8860
+ if (!workspaceInfo) return;
8861
+ const detected = detectProject(workspaceInfo.appRoot);
8862
+ const hasFootprint = detected.framework !== "unknown" || detected.packageManager !== "unknown" || detected.hasTypeScript || detected.hasTailwind || detected.existingRuleFiles.length > 0;
8863
+ console.log(heading2("Decantr Setup"));
8864
+ console.log(` Project: ${workspaceInfo.appRoot}`);
8865
+ console.log(` Detected: ${formatDetection(detected)}`);
8866
+ console.log("");
8867
+ if (detected.existingEssence) {
8868
+ console.log(`${BOLD7}Recommended path:${RESET14} maintain an attached Decantr project`);
8869
+ console.log(` ${cyan3('decantr task <route> "<change>"')} Prepare LLM context before edits`);
8870
+ console.log(` ${cyan3("decantr verify --brownfield")} Run local health and drift checks`);
8871
+ console.log(` ${cyan3("decantr codify --from-audit")} Propose project-owned local law`);
8872
+ return;
8873
+ }
8874
+ if (hasFootprint) {
8875
+ console.log(`${BOLD7}Recommended path:${RESET14} brownfield adoption`);
8876
+ console.log(` ${cyan3("decantr adopt --yes")} Analyze, attach, and verify`);
8877
+ console.log(
8878
+ ` ${cyan3("decantr adopt --base-url http://localhost:3000 --evidence --yes")} Include visual evidence`
8879
+ );
8880
+ console.log(` ${cyan3("decantr codify --from-audit")} Propose local UI law`);
8881
+ return;
8882
+ }
8883
+ console.log(`${BOLD7}Recommended path:${RESET14} greenfield start`);
8884
+ console.log(` ${cyan3("decantr new my-app --blueprint=<slug>")}`);
8885
+ console.log(` ${cyan3("decantr init --workflow=greenfield --adoption=contract-only")}`);
8886
+ }
8887
+ async function cmdAdoptWorkflow(args) {
8888
+ const { flags } = parseLooseArgs(args);
8889
+ const workspaceInfo = resolveWorkflowProject(flags);
8890
+ if (!workspaceInfo) return;
8891
+ const projectRoot = workspaceInfo.appRoot;
8892
+ const dryRun = flagBoolean(flags, "dry-run");
8893
+ const yes = flagBoolean(flags, "yes") || flagBoolean(flags, "y");
8894
+ const baseUrl = flagString(flags, "base-url");
8895
+ const runVerify = flagBoolean(flags, "verify", true);
8896
+ const runBrowser = flagBoolean(flags, "browser") || Boolean(baseUrl);
8897
+ const evidence = flagBoolean(flags, "evidence") || runBrowser;
8898
+ const saveBaseline = flagBoolean(flags, "baseline", true) || flagBoolean(flags, "save-baseline");
8899
+ const initCi = flagBoolean(flags, "ci") || flagBoolean(flags, "init-ci");
8900
+ const assistantBridge = flagString(flags, "assistant-bridge");
8901
+ const hasEssence = existsSync28(join29(projectRoot, "decantr.essence.json"));
8902
+ const proposalFlag = flagBoolean(flags, "replace-essence") ? "--replace-essence" : flagBoolean(flags, "merge-proposal") || hasEssence ? "--merge-proposal" : "--accept-proposal";
8903
+ const steps = [
8904
+ "analyze current app and write .decantr/brownfield intelligence",
8905
+ `init --existing ${proposalFlag} as contract-only Brownfield`
8906
+ ];
8907
+ if (runVerify) {
8908
+ steps.push(
8909
+ runBrowser ? "verify with Project Health, browser evidence, visual manifest, and baseline" : "verify with Project Health and baseline"
8910
+ );
8911
+ }
8912
+ if (initCi) {
8913
+ steps.push("install Project Health CI gate");
8914
+ }
8915
+ printWorkflowPlan("Decantr Adopt", steps);
8916
+ if (dryRun) {
8917
+ console.log(dim3("Dry run only. No files were written."));
8918
+ return;
8919
+ }
8920
+ if (!yes) {
8921
+ const ok = await confirm("Run this Brownfield adoption workflow?", false);
8922
+ if (!ok) {
8923
+ console.log(dim3("Cancelled."));
8924
+ return;
8925
+ }
8926
+ }
8927
+ await cmdAnalyze(projectRoot, workspaceInfo);
8928
+ if (process.exitCode && process.exitCode !== 0) return;
8929
+ await cmdInit({
8930
+ existing: true,
8931
+ yes: true,
8932
+ project: flagString(flags, "project"),
8933
+ "accept-proposal": proposalFlag === "--accept-proposal",
8934
+ "merge-proposal": proposalFlag === "--merge-proposal",
8935
+ "replace-essence": proposalFlag === "--replace-essence",
8936
+ "assistant-bridge": assistantBridge,
8937
+ telemetry: flagBoolean(flags, "telemetry")
8938
+ });
8939
+ if (process.exitCode && process.exitCode !== 0) return;
8940
+ if (runVerify) {
8941
+ const { cmdHealth } = await import("./health-ETZXWGTW.js");
8942
+ await cmdHealth(projectRoot, {
8943
+ browser: runBrowser,
8944
+ browserBaseUrl: baseUrl,
8945
+ evidence,
8946
+ output: evidence ? ".decantr/evidence/latest.json" : void 0,
8947
+ saveBaseline
8948
+ });
8949
+ }
8950
+ if (initCi) {
8951
+ const { cmdHealth } = await import("./health-ETZXWGTW.js");
8952
+ const ciRoot = flagString(flags, "project") ? process.cwd() : projectRoot;
8953
+ await cmdHealth(ciRoot, {
8954
+ initCi: {
8955
+ projectPath: flagString(flags, "project"),
8956
+ failOn: "error"
8957
+ }
8958
+ });
8959
+ }
8960
+ console.log("");
8961
+ console.log(`${BOLD7}Brownfield operating loop:${RESET14}`);
8962
+ console.log(` ${cyan3("decantr codify --from-audit")} Discover and propose project-owned UI law`);
8963
+ console.log(` ${cyan3("decantr codify --accept")} Accept reviewed local patterns and rules`);
8964
+ console.log(` ${cyan3('decantr task <route> "<change>"')} Give your LLM route-specific context before edits`);
8965
+ console.log(` ${cyan3("decantr verify --brownfield --local-patterns")} Check contract, health, and local law after edits`);
8966
+ console.log(` ${cyan3("decantr verify --since-baseline")} Compare future work against this baseline`);
8967
+ }
8968
+ async function cmdVerifyWorkflow(args) {
8969
+ const { flags } = parseLooseArgs(args);
8970
+ const workspaceMode = flagBoolean(flags, "workspace");
8971
+ if (args[1] === "init-ci") {
8972
+ const { cmdHealth: cmdHealth2, parseHealthArgs: parseHealthArgs2 } = await import("./health-ETZXWGTW.js");
8973
+ await cmdHealth2(process.cwd(), parseHealthArgs2(["health", ...args.slice(1)]));
8974
+ return;
8975
+ }
8976
+ if (workspaceMode) {
8977
+ const { cmdWorkspace } = await import("./workspace-KSFWRZEX.js");
8978
+ await cmdWorkspace(process.cwd(), ["workspace", "health", ...withoutWorkflowOnlyFlags(args)]);
8979
+ return;
8980
+ }
8981
+ const workspaceInfo = resolveWorkflowProject(flags);
8982
+ if (!workspaceInfo) return;
8983
+ const brownfield = flagBoolean(flags, "brownfield");
8984
+ const localPatterns = flagBoolean(flags, "local-patterns");
8985
+ const evidence = flagBoolean(flags, "evidence");
8986
+ const baseUrl = flagString(flags, "base-url");
8987
+ const failOn = flagString(flags, "fail-on") ?? "error";
8988
+ const healthArgs = ["health", ...withoutWorkflowOnlyFlags(args)];
8989
+ if (flagBoolean(flags, "baseline") && !healthArgs.includes("--save-baseline")) {
8990
+ healthArgs.push("--save-baseline");
8991
+ }
8992
+ if (evidence && !flagString(flags, "output")) {
8993
+ healthArgs.push("--output", ".decantr/evidence/latest.json");
8994
+ }
8995
+ if (baseUrl && !healthArgs.includes("--browser")) {
8996
+ healthArgs.push("--browser");
8997
+ }
8998
+ const quietOutput = flagBoolean(flags, "json") || flagBoolean(flags, "ci") || Boolean(flagString(flags, "output"));
8999
+ if (!quietOutput) {
9000
+ console.log(heading2("Decantr Verify"));
9001
+ console.log(
9002
+ dim3(
9003
+ brownfield ? "Running Brownfield guard validation before Project Health." : "Running Project Health as the canonical reliability gate."
9004
+ )
9005
+ );
9006
+ console.log("");
9007
+ }
9008
+ let guardExitCode;
9009
+ if (brownfield) {
9010
+ const { cmdHeal, collectCheckIssues } = await import("./heal-ZYD6NVGE.js");
9011
+ if (quietOutput) {
9012
+ const result = collectCheckIssues(workspaceInfo.appRoot, { brownfield: true });
9013
+ guardExitCode = result.issues.some((issue) => issue.type === "error") ? 1 : void 0;
9014
+ } else {
9015
+ await cmdHeal(workspaceInfo.appRoot, { brownfield: true });
9016
+ guardExitCode = process.exitCode;
9017
+ process.exitCode = void 0;
9018
+ }
9019
+ }
9020
+ const { cmdHealth, parseHealthArgs } = await import("./health-ETZXWGTW.js");
9021
+ await cmdHealth(workspaceInfo.appRoot, parseHealthArgs(healthArgs));
9022
+ if (localPatterns) {
9023
+ const validation = validateLocalLaw(workspaceInfo.appRoot);
9024
+ if (!validation.patternPackPresent) {
9025
+ if (!quietOutput) {
9026
+ console.log("");
9027
+ console.log(
9028
+ `${YELLOW9}Local pattern pack missing.${RESET14} Run ${cyan3("decantr codify --from-audit")}, review the proposal, then run ${cyan3("decantr codify --accept")}.`
9029
+ );
9030
+ }
9031
+ process.exitCode = process.exitCode || 1;
9032
+ } else {
9033
+ const blockingFindings = failOn === "none" ? [] : validation.findings.filter(
9034
+ (finding) => failOn === "warn" ? finding.severity === "warn" || finding.severity === "error" : finding.severity === "error"
9035
+ );
9036
+ const blockingWarnings = failOn === "warn" ? validation.warnings : [];
9037
+ if (!quietOutput) {
9038
+ console.log("");
9039
+ console.log(`${GREEN14}Local pattern pack found:${RESET14} ${validation.patternsPath}`);
9040
+ if (validation.ruleManifestPresent) {
9041
+ console.log(`${GREEN14}Local rule manifest found:${RESET14} ${validation.rulesPath}`);
9042
+ } else {
9043
+ console.log(
9044
+ `${YELLOW9}Local rule manifest missing.${RESET14} Run ${cyan3("decantr codify --from-audit")} to propose .decantr/rules.json.`
9045
+ );
9046
+ }
9047
+ for (const warning of validation.warnings.slice(0, 8)) {
9048
+ console.log(`${YELLOW9}warn${RESET14} ${warning}`);
9049
+ }
9050
+ if (validation.findings.length > 0) {
9051
+ console.log("");
9052
+ console.log(`${BOLD7}Local law findings:${RESET14}`);
9053
+ for (const finding of validation.findings.slice(0, 20)) {
9054
+ console.log(
9055
+ ` ${finding.severity.toUpperCase()} ${finding.ruleId} ${finding.file}:${finding.line}:${finding.column} ${finding.message}`
9056
+ );
9057
+ }
9058
+ if (validation.findings.length > 20) {
9059
+ console.log(dim3(` ...${validation.findings.length - 20} more finding(s)`));
9060
+ }
9061
+ } else if (validation.ruleManifestPresent) {
9062
+ console.log(`${GREEN14}Local rule checks passed.${RESET14}`);
9063
+ }
9064
+ }
9065
+ if (blockingFindings.length > 0 || blockingWarnings.length > 0) {
9066
+ process.exitCode = process.exitCode || 1;
9067
+ }
9068
+ }
9069
+ }
9070
+ if (guardExitCode && guardExitCode !== 0 && (!process.exitCode || process.exitCode === 0)) {
9071
+ process.exitCode = guardExitCode;
9072
+ }
9073
+ }
9074
+ function readJsonIfPresent(path) {
9075
+ if (!existsSync28(path)) return null;
9076
+ try {
9077
+ return JSON.parse(readFileSync21(path, "utf-8"));
9078
+ } catch {
9079
+ return null;
9080
+ }
9081
+ }
9082
+ async function cmdTaskWorkflow(args) {
9083
+ const { flags, positional } = parseLooseArgs(args);
9084
+ const workspaceInfo = resolveWorkflowProject(flags);
9085
+ if (!workspaceInfo) return;
9086
+ const routeInput = positional[0];
9087
+ if (!routeInput) {
9088
+ console.error(error3('Usage: decantr task <route> ["task summary"] [--project <path>] [--since origin/main] [--json]'));
9089
+ process.exitCode = 1;
9090
+ return;
9091
+ }
9092
+ const route = routeInput.startsWith("/") ? routeInput : `/${routeInput}`;
9093
+ const taskSummary = positional.slice(1).join(" ").trim();
9094
+ const essencePath = join29(workspaceInfo.appRoot, "decantr.essence.json");
9095
+ const essence = readJsonIfPresent(essencePath);
9096
+ if (!essence) {
9097
+ console.error(error3("No decantr.essence.json found. Run `decantr adopt` or `decantr init` first."));
9098
+ process.exitCode = 1;
9099
+ return;
9100
+ }
9101
+ if (!isV48(essence)) {
9102
+ console.error(error3("Task context requires Essence v4. Run `decantr migrate --to v4` first."));
9103
+ process.exitCode = 1;
9104
+ return;
9105
+ }
9106
+ const target = essence.blueprint.routes?.[route];
9107
+ if (!target) {
9108
+ const knownRoutes = Object.keys(essence.blueprint.routes ?? {}).sort();
9109
+ console.error(error3(`Route not found in Decantr contract: ${route}`));
9110
+ console.error(dim3(`Known routes: ${knownRoutes.join(", ") || "none"}`));
9111
+ process.exitCode = 1;
9112
+ return;
9113
+ }
9114
+ const section = essence.blueprint.sections.find((entry) => entry.id === target.section);
9115
+ const page = section?.pages.find((entry) => entry.id === target.page);
9116
+ const contextDir = join29(workspaceInfo.appRoot, ".decantr", "context");
9117
+ const manifest = readJsonIfPresent(join29(contextDir, "pack-manifest.json"));
9118
+ const pagePack = manifest?.pages?.find((entry) => entry.id === target.page);
9119
+ const sectionPack = manifest?.sections?.find((entry) => entry.id === target.section);
9120
+ const visualManifest = readJsonIfPresent(join29(workspaceInfo.appRoot, ".decantr", "evidence", "visual-manifest.json"));
9121
+ const screenshot = visualManifest?.routes?.find((entry) => entry.route === route)?.screenshot;
9122
+ const localPatternPackPath = localPatternsPath(workspaceInfo.appRoot);
9123
+ const localRuleManifestPath = localRulesPath(workspaceInfo.appRoot);
9124
+ const localLaw = createLocalLawTaskSummary(workspaceInfo.appRoot);
9125
+ const changedSince = flagString(flags, "since");
9126
+ const currentChangedFiles = changedFiles(workspaceInfo.appRoot, changedSince);
9127
+ const changedRoutes = routeImpacts(workspaceInfo.appRoot, currentChangedFiles);
9128
+ const context = {
9129
+ route,
9130
+ task: taskSummary || null,
9131
+ section: target.section,
9132
+ page: target.page,
9133
+ shell: page?.shell ?? section?.shell ?? null,
9134
+ patterns: page?.layout?.map(extractPatternName) ?? [],
9135
+ read: [
9136
+ pagePack ? join29(".decantr/context", pagePack.markdown) : null,
9137
+ sectionPack ? join29(".decantr/context", sectionPack.markdown) : null,
9138
+ manifest?.scaffold?.markdown ? join29(".decantr/context", manifest.scaffold.markdown) : null,
9139
+ ".decantr/context/scaffold.md",
9140
+ "DECANTR.md",
9141
+ existsSync28(localPatternPackPath) ? ".decantr/local-patterns.json" : null,
9142
+ existsSync28(localRuleManifestPath) ? ".decantr/rules.json" : null
9143
+ ].filter(Boolean),
9144
+ screenshot: screenshot ?? null,
9145
+ localLaw,
9146
+ changedFiles: currentChangedFiles,
9147
+ changedRoutes,
9148
+ verifyCommand: "decantr verify --brownfield --local-patterns"
9149
+ };
9150
+ if (flagBoolean(flags, "json")) {
9151
+ console.log(JSON.stringify(context, null, 2));
9152
+ return;
9153
+ }
9154
+ console.log(heading2("Decantr Task Context"));
9155
+ console.log(` Route: ${cyan3(context.route)}`);
9156
+ console.log(` Section/page: ${context.section}/${context.page}`);
9157
+ if (context.shell) console.log(` Shell: ${context.shell}`);
9158
+ if (context.patterns.length > 0) console.log(` Patterns: ${context.patterns.join(", ")}`);
9159
+ if (taskSummary) console.log(` Task: ${taskSummary}`);
9160
+ console.log("");
9161
+ console.log(`${BOLD7}Read before editing:${RESET14}`);
9162
+ for (const path of context.read) {
9163
+ console.log(` ${cyan3(path)}`);
9164
+ }
9165
+ if (context.screenshot) {
9166
+ console.log("");
9167
+ console.log(`${BOLD7}Visual evidence:${RESET14}`);
9168
+ console.log(` ${cyan3(context.screenshot)}`);
9169
+ }
9170
+ if (context.localLaw.patternCount > 0 || context.localLaw.ruleCount > 0) {
9171
+ console.log("");
9172
+ console.log(`${BOLD7}Project-owned local law:${RESET14}`);
9173
+ if (context.localLaw.patternsPath) {
9174
+ console.log(` Patterns: ${cyan3(context.localLaw.patternsPath)} (${context.localLaw.patternCount})`);
9175
+ }
9176
+ if (context.localLaw.rulesPath) {
9177
+ console.log(` Rules: ${cyan3(context.localLaw.rulesPath)} (${context.localLaw.ruleCount})`);
9178
+ }
9179
+ for (const pattern of context.localLaw.patterns.slice(0, 4)) {
9180
+ const pathHint = pattern.componentPaths.length > 0 ? ` \u2014 ${pattern.componentPaths.slice(0, 2).join(", ")}` : "";
9181
+ console.log(` ${pattern.id}: ${pattern.role ?? "local pattern"}${pathHint}`);
9182
+ }
9183
+ } else {
9184
+ console.log("");
9185
+ console.log(`${BOLD7}Project-owned local law:${RESET14}`);
9186
+ console.log(` ${YELLOW9}Not codified yet.${RESET14} Run ${cyan3("decantr codify --from-audit")} after adoption.`);
9187
+ }
9188
+ if (context.changedFiles.length > 0) {
9189
+ console.log("");
9190
+ console.log(`${BOLD7}Changed-file context:${RESET14}`);
9191
+ for (const file of context.changedFiles.slice(0, 8)) {
9192
+ console.log(` ${file}`);
9193
+ }
9194
+ if (context.changedFiles.length > 8) {
9195
+ console.log(dim3(` ...${context.changedFiles.length - 8} more changed file(s)`));
9196
+ }
9197
+ if (context.changedRoutes.length > 0) {
9198
+ console.log(` Impacted routes: ${context.changedRoutes.join(", ")}`);
9199
+ }
9200
+ }
9201
+ console.log("");
9202
+ console.log(`${BOLD7}LLM instruction:${RESET14}`);
9203
+ console.log(
9204
+ " Preserve the existing runtime and styling system. Use the route pack, section context, local laws, changed-file impact, and visual evidence above as the task contract before changing code."
9205
+ );
9206
+ console.log(` After editing, run ${cyan3(context.verifyCommand)}.`);
9207
+ }
9208
+ async function cmdCodifyWorkflow(args) {
9209
+ const { flags } = parseLooseArgs(args);
9210
+ const workspaceInfo = resolveWorkflowProject(flags);
9211
+ if (!workspaceInfo) return;
9212
+ if (flagBoolean(flags, "accept")) {
9213
+ if (!existsSync28(localPatternsProposalPath(workspaceInfo.appRoot)) && !existsSync28(localRulesProposalPath(workspaceInfo.appRoot))) {
9214
+ console.error(
9215
+ error3("No local law proposal found. Run `decantr codify --from-audit` or `decantr codify` first.")
9216
+ );
9217
+ process.exitCode = 1;
9218
+ return;
9219
+ }
9220
+ const result2 = acceptBrownfieldLocalLaw(workspaceInfo.appRoot);
9221
+ if (result2.patternAcceptedPath) {
9222
+ console.log(success3(`Accepted local pattern pack: ${result2.patternAcceptedPath}`));
9223
+ }
9224
+ if (result2.rulesAcceptedPath) {
9225
+ console.log(success3(`Accepted local rule manifest: ${result2.rulesAcceptedPath}`));
9226
+ }
9227
+ console.log(dim3("Run `decantr verify --brownfield --local-patterns` after project edits."));
9228
+ return;
9229
+ }
9230
+ const detected = detectProject(workspaceInfo.appRoot);
9231
+ const essence = readJsonIfPresent(join29(workspaceInfo.appRoot, "decantr.essence.json"));
9232
+ const fromAudit = flagBoolean(flags, "from-audit") || flagBoolean(flags, "discover-local-patterns") || flagBoolean(flags, "codify-local-patterns");
9233
+ const proposal = createBrownfieldCodifyProposal({
9234
+ projectRoot: workspaceInfo.appRoot,
9235
+ detected,
9236
+ essence,
9237
+ fromAudit
9238
+ });
9239
+ const result = writeBrownfieldCodifyProposal(workspaceInfo.appRoot, proposal);
9240
+ console.log(success3(`Wrote local pattern proposal: ${result.patternPath}`));
9241
+ console.log(success3(`Wrote local rule proposal: ${result.rulesPath}`));
9242
+ if (fromAudit) {
9243
+ console.log(dim3("Proposal includes source-derived component candidates and starter mechanical rules."));
9244
+ }
9245
+ console.log(dim3("Review both files, add real component paths/token recipes, then run `decantr codify --accept`."));
9246
+ }
9247
+ async function cmdContentWorkflow(args) {
9248
+ const subcommand = args[1] ?? "check";
9249
+ if (subcommand === "check" || subcommand === "health") {
9250
+ const { cmdContentHealth, parseContentHealthArgs } = await import("./content-health-QQHBR6XG.js");
9251
+ await cmdContentHealth(process.cwd(), parseContentHealthArgs(["content-health", ...args.slice(2)]));
9252
+ return;
9253
+ }
9254
+ if (subcommand === "create") {
9255
+ const type = args[2];
9256
+ const name = args[3];
9257
+ if (!type || !name) {
9258
+ console.error(error3("Usage: decantr content create <type> <name>"));
9259
+ process.exitCode = 1;
9260
+ return;
9261
+ }
9262
+ cmdCreate(type, name);
9263
+ return;
9264
+ }
9265
+ if (subcommand === "publish") {
9266
+ const type = args[2];
9267
+ const name = args[3];
9268
+ if (!type || !name) {
9269
+ console.error(error3("Usage: decantr content publish <type> <name>"));
9270
+ process.exitCode = 1;
9271
+ return;
9272
+ }
9273
+ await cmdPublish(type, name);
9274
+ return;
9275
+ }
9276
+ console.error(error3("Usage: decantr content <check|create|publish>"));
9277
+ process.exitCode = 1;
9278
+ }
8296
9279
  function cmdHelp() {
8297
9280
  console.log(`
8298
9281
  ${BOLD7}decantr${RESET14} \u2014 Design intelligence for AI-generated UI
8299
9282
 
8300
9283
  ${BOLD7}Usage:${RESET14}
9284
+ decantr setup [--project <path>]
8301
9285
  decantr new <name> [--blueprint=X] [--archetype=X] [--theme=X] [--workflow=greenfield] [--adoption=decantr-css] [--telemetry]
8302
- decantr magic <prompt> [--dry-run]
9286
+ decantr adopt [--project <path>] [--base-url <url>] [--evidence] [--ci] [--yes]
9287
+ decantr task <route> ["task summary"] [--project <path>] [--since origin/main] [--json]
9288
+ decantr verify [--project <path>] [--brownfield] [--local-patterns] [health options]
9289
+ decantr codify [--from-audit] [--accept] [--project <path>]
9290
+ decantr studio [--port 4319] [--host 127.0.0.1] [--report decantr-health.json] [--workspace]
9291
+
9292
+ ${BOLD7}Advanced primitives:${RESET14}
8303
9293
  decantr init [options]
9294
+ decantr analyze
9295
+ decantr magic <prompt> [--dry-run]
8304
9296
  decantr status
8305
9297
  decantr sync
8306
9298
  decantr audit [file]
@@ -8324,8 +9316,8 @@ ${BOLD7}Usage:${RESET14}
8324
9316
  decantr health init-ci [--force] [--project <path>] [--workspace] [--fail-on <error|warn|none>] [--cli-version <version|latest>]
8325
9317
  decantr workspace list [--json]
8326
9318
  decantr workspace health [--json] [--changed --since origin/main]
9319
+ decantr content check [--json] [--markdown] [--ci]
8327
9320
  decantr content-health [--json] [--markdown] [--ci]
8328
- decantr studio [--port 4319] [--host 127.0.0.1] [--report decantr-health.json] [--workspace]
8329
9321
  decantr telemetry status [--json]
8330
9322
  decantr telemetry explain [--json]
8331
9323
  decantr telemetry link [--enable] [--org <slug>]
@@ -8335,7 +9327,6 @@ ${BOLD7}Usage:${RESET14}
8335
9327
  decantr theme <subcommand>
8336
9328
  decantr create <type> <name>
8337
9329
  decantr publish <type> <name>
8338
- decantr analyze
8339
9330
  decantr login
8340
9331
  decantr logout
8341
9332
  decantr help
@@ -8363,14 +9354,22 @@ ${BOLD7}Init Options:${RESET14}
8363
9354
  --telemetry Opt this project into privacy-filtered CLI product telemetry
8364
9355
 
8365
9356
  ${BOLD7}Commands:${RESET14}
9357
+ ${cyan3("setup")} Detect project state and recommend the right Decantr workflow
8366
9358
  ${cyan3("new")} Create a new greenfield workspace and bootstrap the available starter adapter
9359
+ ${cyan3("adopt")} Brownfield one-liner: analyze, attach, verify, and show next steps
9360
+ ${cyan3("task")} Prepare route/task context, local law, evidence, and changed-file impact for an AI coding assistant
9361
+ ${cyan3("verify")} One reliability gate over Project Health, Brownfield checks, baselines, and evidence
9362
+ ${cyan3("codify")} Propose or accept project-owned Brownfield UI patterns and rules
9363
+ ${cyan3("studio")} Open a local Project Health dashboard backed by the same report
9364
+ ${cyan3("content")} Content-author namespace: check, create, publish
9365
+
9366
+ ${BOLD7}Advanced commands:${RESET14}
8367
9367
  ${cyan3("magic")} Greenfield-first intent flow; steers existing apps into analyze + init
8368
9368
  ${cyan3("init")} Attach Decantr contract/context files to an existing project or empty workspace
8369
9369
  ${cyan3("status")} Show project status, DNA axioms, and blueprint info
8370
9370
  ${cyan3("health")} Generate a local Project Health report [--json] [--markdown] [--ci]; use health init-ci to install a GitHub Actions gate
8371
9371
  ${cyan3("workspace")} Discover and aggregate health across Decantr projects in a monorepo
8372
9372
  ${cyan3("content-health")} Generate a local registry content health report [--json] [--markdown] [--ci]
8373
- ${cyan3("studio")} Open a local Project Health dashboard backed by the same report
8374
9373
  ${cyan3("sync")} Sync registry content from API
8375
9374
  ${cyan3("audit")} Audit the project or critique a specific file against compiled packs
8376
9375
  ${cyan3("migrate")} Migrate older essence files to v4 format (with .pre-v4.backup.json backup)
@@ -8396,7 +9395,15 @@ ${BOLD7}Commands:${RESET14}
8396
9395
  ${cyan3("help")} Show this help
8397
9396
 
8398
9397
  ${BOLD7}Examples:${RESET14}
9398
+ decantr setup
8399
9399
  decantr new my-app --blueprint=carbon-ai-portal
9400
+ decantr adopt --base-url http://localhost:3000 --evidence --yes
9401
+ decantr task /feed "add saved recipe actions"
9402
+ decantr verify --brownfield --local-patterns
9403
+ decantr verify --since-baseline
9404
+ decantr codify --from-audit
9405
+ decantr codify --accept
9406
+ decantr content check --ci --fail-on error
8400
9407
  decantr magic "AI chatbot with dark cyber theme \u2014 bold and futuristic"
8401
9408
  decantr init
8402
9409
  decantr analyze
@@ -8409,13 +9416,13 @@ ${BOLD7}Examples:${RESET14}
8409
9416
  decantr rules apply
8410
9417
  decantr status
8411
9418
  decantr health
8412
- decantr health init-ci
8413
- decantr health init-ci --project apps/web
8414
- decantr health --ci --fail-on error
9419
+ decantr verify init-ci
9420
+ decantr verify init-ci --project apps/web
9421
+ decantr verify --ci --fail-on error
8415
9422
  decantr health --evidence --output .decantr/evidence/latest.json
8416
9423
  decantr workspace list
8417
- decantr workspace health --changed --since origin/main
8418
- decantr content-health --ci --fail-on error
9424
+ decantr verify --workspace --changed --since origin/main
9425
+ decantr content check --ci --fail-on error
8419
9426
  decantr studio
8420
9427
  decantr studio --report decantr-health.json
8421
9428
  decantr telemetry status
@@ -8444,7 +9451,9 @@ ${BOLD7}Examples:${RESET14}
8444
9451
  ${BOLD7}Workflow Model:${RESET14}
8445
9452
  ${cyan3("Greenfield blueprint")} decantr new my-app --blueprint=X --workflow=greenfield --adoption=decantr-css
8446
9453
  ${cyan3("Greenfield contract")} decantr init --workflow=greenfield --adoption=contract-only
8447
- ${cyan3("Brownfield adoption")} decantr analyze -> decantr init --existing --accept-proposal -> decantr health --browser --evidence
9454
+ ${cyan3("Brownfield adoption")} decantr adopt --base-url <url> --evidence --yes
9455
+ ${cyan3("Daily LLM work")} decantr task <route> "<change>" -> decantr verify --brownfield --local-patterns
9456
+ ${cyan3("Project-owned law")} decantr codify --from-audit -> edit proposal -> decantr codify --accept
8448
9457
  ${cyan3("Hybrid composition")} decantr add/remove, decantr theme switch, decantr registry, decantr upgrade
8449
9458
 
8450
9459
  ${BOLD7}Bootstrap adapters:${RESET14}
@@ -8611,9 +9620,131 @@ ${BOLD7}Usage:${RESET14}
8611
9620
  decantr theme import <path>
8612
9621
  `);
8613
9622
  }
9623
+ function cmdSetupHelp() {
9624
+ console.log(`
9625
+ ${BOLD7}decantr setup${RESET14} \u2014 Detect the project state and recommend the right Decantr path
9626
+
9627
+ ${BOLD7}Usage:${RESET14}
9628
+ decantr setup [--project <path>]
9629
+
9630
+ ${BOLD7}Examples:${RESET14}
9631
+ decantr setup
9632
+ decantr setup --project apps/web
9633
+ `);
9634
+ }
9635
+ function cmdAdoptHelp() {
9636
+ console.log(`
9637
+ ${BOLD7}decantr adopt${RESET14} \u2014 Brownfield one-liner: analyze, attach, verify, and show the operating loop
9638
+
9639
+ ${BOLD7}Usage:${RESET14}
9640
+ decantr adopt [--project <path>] [--yes] [--dry-run]
9641
+ decantr adopt --base-url <url> [--evidence] [--ci] [--yes]
9642
+
9643
+ ${BOLD7}Options:${RESET14}
9644
+ --project App path inside a workspace/monorepo
9645
+ --yes, -y Run without confirmation
9646
+ --dry-run Show the workflow without writing files
9647
+ --base-url Include browser evidence against this dev server URL
9648
+ --evidence Write .decantr/evidence/latest.json
9649
+ --baseline Save a health baseline (default)
9650
+ --no-baseline Skip baseline save
9651
+ --no-verify Skip the verification step
9652
+ --ci, --init-ci Install the Project Health CI gate after adoption
9653
+ --telemetry Opt this project into privacy-filtered CLI product telemetry
9654
+ --merge-proposal Merge the observed proposal into an existing essence
9655
+ --replace-essence Replace an existing essence with backup
9656
+
9657
+ ${BOLD7}Examples:${RESET14}
9658
+ decantr adopt --yes
9659
+ decantr adopt --base-url http://localhost:3000 --evidence --yes
9660
+ decantr adopt --project apps/web --ci --yes
9661
+ decantr codify --from-audit
9662
+ `);
9663
+ }
9664
+ function cmdVerifyHelp() {
9665
+ console.log(`
9666
+ ${BOLD7}decantr verify${RESET14} \u2014 One reliability command for local work, CI, and LLM agent loops
9667
+
9668
+ ${BOLD7}Usage:${RESET14}
9669
+ decantr verify [--project <path>] [--brownfield] [--local-patterns]
9670
+ decantr verify --base-url <url> --evidence
9671
+ decantr verify --since-baseline
9672
+ decantr verify --workspace [--changed --since origin/main]
9673
+ decantr verify init-ci [health init-ci options]
9674
+
9675
+ ${BOLD7}Examples:${RESET14}
9676
+ decantr verify
9677
+ decantr verify --brownfield --local-patterns
9678
+ decantr verify --brownfield --local-patterns --fail-on warn
9679
+ decantr verify --base-url http://localhost:3000 --evidence
9680
+ decantr verify --workspace --changed --since origin/main
9681
+ decantr verify init-ci --project apps/web
9682
+ `);
9683
+ }
9684
+ function cmdTaskHelp() {
9685
+ console.log(`
9686
+ ${BOLD7}decantr task${RESET14} \u2014 Prepare compact route/task context for an AI coding assistant
9687
+
9688
+ ${BOLD7}Usage:${RESET14}
9689
+ decantr task <route> ["task summary"] [--project <path>] [--since origin/main] [--json]
9690
+
9691
+ ${BOLD7}Examples:${RESET14}
9692
+ decantr task /feed "add saved recipe actions"
9693
+ decantr task /feed "add saved recipe actions" --since origin/main
9694
+ decantr task /profile --json
9695
+ `);
9696
+ }
9697
+ function cmdCodifyHelp() {
9698
+ console.log(`
9699
+ ${BOLD7}decantr codify${RESET14} \u2014 Propose or accept project-owned Brownfield UI patterns and rules
9700
+
9701
+ ${BOLD7}Usage:${RESET14}
9702
+ decantr codify [--from-audit] [--project <path>]
9703
+ decantr codify --accept [--project <path>]
9704
+
9705
+ ${BOLD7}Examples:${RESET14}
9706
+ decantr codify
9707
+ decantr codify --from-audit
9708
+ decantr codify --accept
9709
+ decantr verify --brownfield --local-patterns
9710
+ `);
9711
+ }
9712
+ function cmdContentHelp() {
9713
+ console.log(`
9714
+ ${BOLD7}decantr content${RESET14} \u2014 Content-author namespace for registry content repositories
9715
+
9716
+ ${BOLD7}Usage:${RESET14}
9717
+ decantr content check [content-health options]
9718
+ decantr content create <type> <name>
9719
+ decantr content publish <type> <name>
9720
+
9721
+ ${BOLD7}Examples:${RESET14}
9722
+ decantr content check --ci --fail-on error
9723
+ decantr content create pattern my-card
9724
+ decantr content publish pattern my-card
9725
+ `);
9726
+ }
8614
9727
  function printCommandHelp(command, args) {
8615
9728
  if (!isCommandHelpRequest(args)) return false;
8616
9729
  switch (command) {
9730
+ case "setup":
9731
+ cmdSetupHelp();
9732
+ return true;
9733
+ case "adopt":
9734
+ cmdAdoptHelp();
9735
+ return true;
9736
+ case "verify":
9737
+ cmdVerifyHelp();
9738
+ return true;
9739
+ case "task":
9740
+ cmdTaskHelp();
9741
+ return true;
9742
+ case "codify":
9743
+ cmdCodifyHelp();
9744
+ return true;
9745
+ case "content":
9746
+ cmdContentHelp();
9747
+ return true;
8617
9748
  case "health":
8618
9749
  cmdHealthHelp();
8619
9750
  return true;
@@ -8650,10 +9781,10 @@ async function main() {
8650
9781
  if (command === "--version" || command === "-v" || command === "version") {
8651
9782
  try {
8652
9783
  const here = dirname4(fileURLToPath2(import.meta.url));
8653
- const candidates = [join28(here, "..", "package.json"), join28(here, "..", "..", "package.json")];
9784
+ const candidates = [join29(here, "..", "package.json"), join29(here, "..", "..", "package.json")];
8654
9785
  for (const candidate of candidates) {
8655
- if (existsSync27(candidate)) {
8656
- const pkg = JSON.parse(readFileSync20(candidate, "utf-8"));
9786
+ if (existsSync28(candidate)) {
9787
+ const pkg = JSON.parse(readFileSync21(candidate, "utf-8"));
8657
9788
  if (pkg.version) {
8658
9789
  console.log(pkg.version);
8659
9790
  return;
@@ -8672,6 +9803,30 @@ async function main() {
8672
9803
  return;
8673
9804
  }
8674
9805
  switch (command) {
9806
+ case "setup": {
9807
+ await cmdSetupWorkflow(args);
9808
+ break;
9809
+ }
9810
+ case "adopt": {
9811
+ await cmdAdoptWorkflow(args);
9812
+ break;
9813
+ }
9814
+ case "task": {
9815
+ await cmdTaskWorkflow(args);
9816
+ break;
9817
+ }
9818
+ case "verify": {
9819
+ await cmdVerifyWorkflow(args);
9820
+ break;
9821
+ }
9822
+ case "codify": {
9823
+ await cmdCodifyWorkflow(args);
9824
+ break;
9825
+ }
9826
+ case "content": {
9827
+ await cmdContentWorkflow(args);
9828
+ break;
9829
+ }
8675
9830
  case "new": {
8676
9831
  const newName = args[1];
8677
9832
  if (!newName) {
@@ -9102,7 +10257,7 @@ async function main() {
9102
10257
  break;
9103
10258
  }
9104
10259
  if (packType === "page" && route && !id) {
9105
- const resolvedPath = essencePath ? resolveUserPath(essencePath) : join28(process.cwd(), "decantr.essence.json");
10260
+ const resolvedPath = essencePath ? resolveUserPath(essencePath) : join29(process.cwd(), "decantr.essence.json");
9106
10261
  id = resolvePagePackIdForRoute(resolvedPath, route);
9107
10262
  }
9108
10263
  await printHostedSelectedExecutionPack(