@decantr/cli 2.7.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`));
@@ -8385,8 +8867,8 @@ async function cmdSetupWorkflow(args) {
8385
8867
  if (detected.existingEssence) {
8386
8868
  console.log(`${BOLD7}Recommended path:${RESET14} maintain an attached Decantr project`);
8387
8869
  console.log(` ${cyan3('decantr task <route> "<change>"')} Prepare LLM context before edits`);
8388
- console.log(` ${cyan3("decantr verify")} Run local health and drift checks`);
8389
- console.log(` ${cyan3("decantr codify")} Propose project-owned UI patterns`);
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`);
8390
8872
  return;
8391
8873
  }
8392
8874
  if (hasFootprint) {
@@ -8395,7 +8877,7 @@ async function cmdSetupWorkflow(args) {
8395
8877
  console.log(
8396
8878
  ` ${cyan3("decantr adopt --base-url http://localhost:3000 --evidence --yes")} Include visual evidence`
8397
8879
  );
8398
- console.log(` ${cyan3("decantr codify")} Propose local UI law`);
8880
+ console.log(` ${cyan3("decantr codify --from-audit")} Propose local UI law`);
8399
8881
  return;
8400
8882
  }
8401
8883
  console.log(`${BOLD7}Recommended path:${RESET14} greenfield start`);
@@ -8416,7 +8898,7 @@ async function cmdAdoptWorkflow(args) {
8416
8898
  const saveBaseline = flagBoolean(flags, "baseline", true) || flagBoolean(flags, "save-baseline");
8417
8899
  const initCi = flagBoolean(flags, "ci") || flagBoolean(flags, "init-ci");
8418
8900
  const assistantBridge = flagString(flags, "assistant-bridge");
8419
- const hasEssence = existsSync27(join28(projectRoot, "decantr.essence.json"));
8901
+ const hasEssence = existsSync28(join29(projectRoot, "decantr.essence.json"));
8420
8902
  const proposalFlag = flagBoolean(flags, "replace-essence") ? "--replace-essence" : flagBoolean(flags, "merge-proposal") || hasEssence ? "--merge-proposal" : "--accept-proposal";
8421
8903
  const steps = [
8422
8904
  "analyze current app and write .decantr/brownfield intelligence",
@@ -8476,10 +8958,12 @@ async function cmdAdoptWorkflow(args) {
8476
8958
  });
8477
8959
  }
8478
8960
  console.log("");
8479
- console.log(`${BOLD7}Next useful commands:${RESET14}`);
8480
- console.log(` ${cyan3('decantr task <route> "<change>"')} Give your LLM route-specific context`);
8481
- console.log(` ${cyan3("decantr codify")} Propose project-owned UI patterns`);
8482
- console.log(` ${cyan3("decantr verify --since-baseline")} Compare future work against this baseline`);
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`);
8483
8967
  }
8484
8968
  async function cmdVerifyWorkflow(args) {
8485
8969
  const { flags } = parseLooseArgs(args);
@@ -8500,6 +8984,7 @@ async function cmdVerifyWorkflow(args) {
8500
8984
  const localPatterns = flagBoolean(flags, "local-patterns");
8501
8985
  const evidence = flagBoolean(flags, "evidence");
8502
8986
  const baseUrl = flagString(flags, "base-url");
8987
+ const failOn = flagString(flags, "fail-on") ?? "error";
8503
8988
  const healthArgs = ["health", ...withoutWorkflowOnlyFlags(args)];
8504
8989
  if (flagBoolean(flags, "baseline") && !healthArgs.includes("--save-baseline")) {
8505
8990
  healthArgs.push("--save-baseline");
@@ -8535,16 +9020,51 @@ async function cmdVerifyWorkflow(args) {
8535
9020
  const { cmdHealth, parseHealthArgs } = await import("./health-ETZXWGTW.js");
8536
9021
  await cmdHealth(workspaceInfo.appRoot, parseHealthArgs(healthArgs));
8537
9022
  if (localPatterns) {
8538
- const localPatternsPath = join28(workspaceInfo.appRoot, ".decantr", "local-patterns.json");
8539
- if (!existsSync27(localPatternsPath)) {
8540
- console.log("");
8541
- console.log(
8542
- `${YELLOW9}Local pattern pack missing.${RESET14} Run ${cyan3("decantr codify --accept")} after reviewing the proposal.`
8543
- );
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
+ }
8544
9031
  process.exitCode = process.exitCode || 1;
8545
9032
  } else {
8546
- console.log("");
8547
- console.log(`${GREEN14}Local pattern pack found:${RESET14} ${localPatternsPath}`);
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
+ }
8548
9068
  }
8549
9069
  }
8550
9070
  if (guardExitCode && guardExitCode !== 0 && (!process.exitCode || process.exitCode === 0)) {
@@ -8552,9 +9072,9 @@ async function cmdVerifyWorkflow(args) {
8552
9072
  }
8553
9073
  }
8554
9074
  function readJsonIfPresent(path) {
8555
- if (!existsSync27(path)) return null;
9075
+ if (!existsSync28(path)) return null;
8556
9076
  try {
8557
- return JSON.parse(readFileSync20(path, "utf-8"));
9077
+ return JSON.parse(readFileSync21(path, "utf-8"));
8558
9078
  } catch {
8559
9079
  return null;
8560
9080
  }
@@ -8565,20 +9085,20 @@ async function cmdTaskWorkflow(args) {
8565
9085
  if (!workspaceInfo) return;
8566
9086
  const routeInput = positional[0];
8567
9087
  if (!routeInput) {
8568
- console.error(error3('Usage: decantr task <route> ["task summary"] [--project <path>] [--json]'));
9088
+ console.error(error3('Usage: decantr task <route> ["task summary"] [--project <path>] [--since origin/main] [--json]'));
8569
9089
  process.exitCode = 1;
8570
9090
  return;
8571
9091
  }
8572
9092
  const route = routeInput.startsWith("/") ? routeInput : `/${routeInput}`;
8573
9093
  const taskSummary = positional.slice(1).join(" ").trim();
8574
- const essencePath = join28(workspaceInfo.appRoot, "decantr.essence.json");
9094
+ const essencePath = join29(workspaceInfo.appRoot, "decantr.essence.json");
8575
9095
  const essence = readJsonIfPresent(essencePath);
8576
9096
  if (!essence) {
8577
9097
  console.error(error3("No decantr.essence.json found. Run `decantr adopt` or `decantr init` first."));
8578
9098
  process.exitCode = 1;
8579
9099
  return;
8580
9100
  }
8581
- if (!isV47(essence)) {
9101
+ if (!isV48(essence)) {
8582
9102
  console.error(error3("Task context requires Essence v4. Run `decantr migrate --to v4` first."));
8583
9103
  process.exitCode = 1;
8584
9104
  return;
@@ -8593,13 +9113,18 @@ async function cmdTaskWorkflow(args) {
8593
9113
  }
8594
9114
  const section = essence.blueprint.sections.find((entry) => entry.id === target.section);
8595
9115
  const page = section?.pages.find((entry) => entry.id === target.page);
8596
- const contextDir = join28(workspaceInfo.appRoot, ".decantr", "context");
8597
- const manifest = readJsonIfPresent(join28(contextDir, "pack-manifest.json"));
9116
+ const contextDir = join29(workspaceInfo.appRoot, ".decantr", "context");
9117
+ const manifest = readJsonIfPresent(join29(contextDir, "pack-manifest.json"));
8598
9118
  const pagePack = manifest?.pages?.find((entry) => entry.id === target.page);
8599
9119
  const sectionPack = manifest?.sections?.find((entry) => entry.id === target.section);
8600
- const visualManifest = readJsonIfPresent(join28(workspaceInfo.appRoot, ".decantr", "evidence", "visual-manifest.json"));
9120
+ const visualManifest = readJsonIfPresent(join29(workspaceInfo.appRoot, ".decantr", "evidence", "visual-manifest.json"));
8601
9121
  const screenshot = visualManifest?.routes?.find((entry) => entry.route === route)?.screenshot;
8602
- const localPatternsPath = join28(workspaceInfo.appRoot, ".decantr", "local-patterns.json");
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);
8603
9128
  const context = {
8604
9129
  route,
8605
9130
  task: taskSummary || null,
@@ -8608,14 +9133,19 @@ async function cmdTaskWorkflow(args) {
8608
9133
  shell: page?.shell ?? section?.shell ?? null,
8609
9134
  patterns: page?.layout?.map(extractPatternName) ?? [],
8610
9135
  read: [
8611
- pagePack ? join28(".decantr/context", pagePack.markdown) : null,
8612
- sectionPack ? join28(".decantr/context", sectionPack.markdown) : null,
8613
- manifest?.scaffold?.markdown ? join28(".decantr/context", manifest.scaffold.markdown) : null,
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,
8614
9139
  ".decantr/context/scaffold.md",
8615
9140
  "DECANTR.md",
8616
- existsSync27(localPatternsPath) ? ".decantr/local-patterns.json" : null
9141
+ existsSync28(localPatternPackPath) ? ".decantr/local-patterns.json" : null,
9142
+ existsSync28(localRuleManifestPath) ? ".decantr/rules.json" : null
8617
9143
  ].filter(Boolean),
8618
- screenshot: screenshot ?? null
9144
+ screenshot: screenshot ?? null,
9145
+ localLaw,
9146
+ changedFiles: currentChangedFiles,
9147
+ changedRoutes,
9148
+ verifyCommand: "decantr verify --brownfield --local-patterns"
8619
9149
  };
8620
9150
  if (flagBoolean(flags, "json")) {
8621
9151
  console.log(JSON.stringify(context, null, 2));
@@ -8637,89 +9167,82 @@ async function cmdTaskWorkflow(args) {
8637
9167
  console.log(`${BOLD7}Visual evidence:${RESET14}`);
8638
9168
  console.log(` ${cyan3(context.screenshot)}`);
8639
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
+ }
8640
9201
  console.log("");
8641
9202
  console.log(`${BOLD7}LLM instruction:${RESET14}`);
8642
9203
  console.log(
8643
- " Preserve the existing runtime and styling system. Use the route pack, section context, local patterns, and visual evidence above as the task contract before changing code."
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."
8644
9205
  );
9206
+ console.log(` After editing, run ${cyan3(context.verifyCommand)}.`);
8645
9207
  }
8646
9208
  async function cmdCodifyWorkflow(args) {
8647
9209
  const { flags } = parseLooseArgs(args);
8648
9210
  const workspaceInfo = resolveWorkflowProject(flags);
8649
9211
  if (!workspaceInfo) return;
8650
- const decantrDir = join28(workspaceInfo.appRoot, ".decantr");
8651
- const proposalPathLocal = join28(decantrDir, "local-patterns.proposal.json");
8652
- const acceptedPath = join28(decantrDir, "local-patterns.json");
8653
9212
  if (flagBoolean(flags, "accept")) {
8654
- if (!existsSync27(proposalPathLocal)) {
8655
- console.error(error3("No .decantr/local-patterns.proposal.json found. Run `decantr codify` first."));
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
+ );
8656
9217
  process.exitCode = 1;
8657
9218
  return;
8658
9219
  }
8659
- writeFileSync16(acceptedPath, readFileSync20(proposalPathLocal, "utf-8"), "utf-8");
8660
- console.log(success3(`Accepted local pattern pack: ${acceptedPath}`));
8661
- console.log(dim3("Run `decantr verify --local-patterns` to require the pack during verification."));
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."));
8662
9228
  return;
8663
9229
  }
8664
- mkdirSync13(decantrDir, { recursive: true });
8665
9230
  const detected = detectProject(workspaceInfo.appRoot);
8666
- const essence = readJsonIfPresent(join28(workspaceInfo.appRoot, "decantr.essence.json"));
8667
- const routes = essence && isV47(essence) ? Object.keys(essence.blueprint.routes ?? {}).sort() : [];
8668
- const proposal = {
8669
- version: 1,
8670
- generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
8671
- status: "proposal",
8672
- source: "decantr codify",
8673
- project: {
8674
- framework: detected.framework,
8675
- packageManager: detected.packageManager,
8676
- hasTailwind: detected.hasTailwind,
8677
- ruleFiles: detected.existingRuleFiles,
8678
- routeCount: routes.length
8679
- },
8680
- purpose: "Project-owned Brownfield UI law. Review and edit before accepting; Decantr does not treat this as authoritative until copied to .decantr/local-patterns.json.",
8681
- patterns: [
8682
- {
8683
- id: "button",
8684
- role: "Actions and command triggers",
8685
- decide: "Define primary, secondary, tertiary, destructive, icon-only, and loading button variants from this app.",
8686
- evidenceToCollect: ["component wrapper path", "allowed classes/tokens", "forbidden raw <button> usage"]
8687
- },
8688
- {
8689
- id: "surface-card",
8690
- role: "Cards, panels, and content surfaces",
8691
- decide: "Define the canonical card background, border, radius, shadow, padding, and hover treatment.",
8692
- evidenceToCollect: ["shared card component", "token/class recipe", "allowed density variants"]
8693
- },
8694
- {
8695
- id: "page-shell",
8696
- role: "Route shell, nav, spacing, and scroll ownership",
8697
- decide: "Define which layout owns max width, gutters, sticky chrome, and scroll containers.",
8698
- evidenceToCollect: ["root layout path", "page template path", "responsive breakpoints"]
8699
- },
8700
- {
8701
- id: "form-control",
8702
- role: "Inputs, labels, validation, and form actions",
8703
- decide: "Define input height, label placement, error copy, disabled state, and focus treatment.",
8704
- evidenceToCollect: ["form field wrapper", "validation pattern", "accessibility expectations"]
8705
- }
8706
- ],
8707
- starterRules: [
8708
- "Prefer project-owned wrappers for repeated primitives once they exist.",
8709
- "Avoid raw hex/rgb values in component templates unless explicitly documented as dynamic data.",
8710
- "Avoid static inline styles for reusable visual treatment.",
8711
- "When adding a new route, map it to an existing local pattern before inventing a new visual variant."
8712
- ],
8713
- nextSteps: [
8714
- "Edit this proposal with real component paths and token/class recipes.",
8715
- "Run decantr codify --accept after review.",
8716
- "Use decantr task <route> before LLM edits so local patterns appear in the task context.",
8717
- "Wire mechanical enforcement through ESLint/Biome/project tests for rules Decantr cannot reliably infer."
8718
- ]
8719
- };
8720
- writeFileSync16(proposalPathLocal, JSON.stringify(proposal, null, 2) + "\n", "utf-8");
8721
- console.log(success3(`Wrote local pattern proposal: ${proposalPathLocal}`));
8722
- console.log(dim3("Review it, add real component paths/token recipes, then run `decantr codify --accept`."));
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`."));
8723
9246
  }
8724
9247
  async function cmdContentWorkflow(args) {
8725
9248
  const subcommand = args[1] ?? "check";
@@ -8761,9 +9284,9 @@ ${BOLD7}Usage:${RESET14}
8761
9284
  decantr setup [--project <path>]
8762
9285
  decantr new <name> [--blueprint=X] [--archetype=X] [--theme=X] [--workflow=greenfield] [--adoption=decantr-css] [--telemetry]
8763
9286
  decantr adopt [--project <path>] [--base-url <url>] [--evidence] [--ci] [--yes]
8764
- decantr task <route> ["task summary"] [--project <path>] [--json]
9287
+ decantr task <route> ["task summary"] [--project <path>] [--since origin/main] [--json]
8765
9288
  decantr verify [--project <path>] [--brownfield] [--local-patterns] [health options]
8766
- decantr codify [--accept] [--project <path>]
9289
+ decantr codify [--from-audit] [--accept] [--project <path>]
8767
9290
  decantr studio [--port 4319] [--host 127.0.0.1] [--report decantr-health.json] [--workspace]
8768
9291
 
8769
9292
  ${BOLD7}Advanced primitives:${RESET14}
@@ -8834,9 +9357,9 @@ ${BOLD7}Commands:${RESET14}
8834
9357
  ${cyan3("setup")} Detect project state and recommend the right Decantr workflow
8835
9358
  ${cyan3("new")} Create a new greenfield workspace and bootstrap the available starter adapter
8836
9359
  ${cyan3("adopt")} Brownfield one-liner: analyze, attach, verify, and show next steps
8837
- ${cyan3("task")} Prepare route/task context for an AI coding assistant
9360
+ ${cyan3("task")} Prepare route/task context, local law, evidence, and changed-file impact for an AI coding assistant
8838
9361
  ${cyan3("verify")} One reliability gate over Project Health, Brownfield checks, baselines, and evidence
8839
- ${cyan3("codify")} Propose or accept project-owned Brownfield UI patterns
9362
+ ${cyan3("codify")} Propose or accept project-owned Brownfield UI patterns and rules
8840
9363
  ${cyan3("studio")} Open a local Project Health dashboard backed by the same report
8841
9364
  ${cyan3("content")} Content-author namespace: check, create, publish
8842
9365
 
@@ -8878,7 +9401,7 @@ ${BOLD7}Examples:${RESET14}
8878
9401
  decantr task /feed "add saved recipe actions"
8879
9402
  decantr verify --brownfield --local-patterns
8880
9403
  decantr verify --since-baseline
8881
- decantr codify
9404
+ decantr codify --from-audit
8882
9405
  decantr codify --accept
8883
9406
  decantr content check --ci --fail-on error
8884
9407
  decantr magic "AI chatbot with dark cyber theme \u2014 bold and futuristic"
@@ -8929,8 +9452,8 @@ ${BOLD7}Workflow Model:${RESET14}
8929
9452
  ${cyan3("Greenfield blueprint")} decantr new my-app --blueprint=X --workflow=greenfield --adoption=decantr-css
8930
9453
  ${cyan3("Greenfield contract")} decantr init --workflow=greenfield --adoption=contract-only
8931
9454
  ${cyan3("Brownfield adoption")} decantr adopt --base-url <url> --evidence --yes
8932
- ${cyan3("Daily LLM work")} decantr task <route> "<change>" -> decantr verify
8933
- ${cyan3("Project-owned law")} decantr codify -> edit proposal -> decantr codify --accept
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
8934
9457
  ${cyan3("Hybrid composition")} decantr add/remove, decantr theme switch, decantr registry, decantr upgrade
8935
9458
 
8936
9459
  ${BOLD7}Bootstrap adapters:${RESET14}
@@ -9111,7 +9634,7 @@ ${BOLD7}Examples:${RESET14}
9111
9634
  }
9112
9635
  function cmdAdoptHelp() {
9113
9636
  console.log(`
9114
- ${BOLD7}decantr adopt${RESET14} \u2014 Brownfield one-liner: analyze, attach, verify, and show the next step
9637
+ ${BOLD7}decantr adopt${RESET14} \u2014 Brownfield one-liner: analyze, attach, verify, and show the operating loop
9115
9638
 
9116
9639
  ${BOLD7}Usage:${RESET14}
9117
9640
  decantr adopt [--project <path>] [--yes] [--dry-run]
@@ -9135,6 +9658,7 @@ ${BOLD7}Examples:${RESET14}
9135
9658
  decantr adopt --yes
9136
9659
  decantr adopt --base-url http://localhost:3000 --evidence --yes
9137
9660
  decantr adopt --project apps/web --ci --yes
9661
+ decantr codify --from-audit
9138
9662
  `);
9139
9663
  }
9140
9664
  function cmdVerifyHelp() {
@@ -9151,6 +9675,7 @@ ${BOLD7}Usage:${RESET14}
9151
9675
  ${BOLD7}Examples:${RESET14}
9152
9676
  decantr verify
9153
9677
  decantr verify --brownfield --local-patterns
9678
+ decantr verify --brownfield --local-patterns --fail-on warn
9154
9679
  decantr verify --base-url http://localhost:3000 --evidence
9155
9680
  decantr verify --workspace --changed --since origin/main
9156
9681
  decantr verify init-ci --project apps/web
@@ -9161,25 +9686,27 @@ function cmdTaskHelp() {
9161
9686
  ${BOLD7}decantr task${RESET14} \u2014 Prepare compact route/task context for an AI coding assistant
9162
9687
 
9163
9688
  ${BOLD7}Usage:${RESET14}
9164
- decantr task <route> ["task summary"] [--project <path>] [--json]
9689
+ decantr task <route> ["task summary"] [--project <path>] [--since origin/main] [--json]
9165
9690
 
9166
9691
  ${BOLD7}Examples:${RESET14}
9167
9692
  decantr task /feed "add saved recipe actions"
9693
+ decantr task /feed "add saved recipe actions" --since origin/main
9168
9694
  decantr task /profile --json
9169
9695
  `);
9170
9696
  }
9171
9697
  function cmdCodifyHelp() {
9172
9698
  console.log(`
9173
- ${BOLD7}decantr codify${RESET14} \u2014 Propose or accept project-owned Brownfield UI patterns
9699
+ ${BOLD7}decantr codify${RESET14} \u2014 Propose or accept project-owned Brownfield UI patterns and rules
9174
9700
 
9175
9701
  ${BOLD7}Usage:${RESET14}
9176
- decantr codify [--project <path>]
9702
+ decantr codify [--from-audit] [--project <path>]
9177
9703
  decantr codify --accept [--project <path>]
9178
9704
 
9179
9705
  ${BOLD7}Examples:${RESET14}
9180
9706
  decantr codify
9707
+ decantr codify --from-audit
9181
9708
  decantr codify --accept
9182
- decantr verify --local-patterns
9709
+ decantr verify --brownfield --local-patterns
9183
9710
  `);
9184
9711
  }
9185
9712
  function cmdContentHelp() {
@@ -9254,10 +9781,10 @@ async function main() {
9254
9781
  if (command === "--version" || command === "-v" || command === "version") {
9255
9782
  try {
9256
9783
  const here = dirname4(fileURLToPath2(import.meta.url));
9257
- const candidates = [join28(here, "..", "package.json"), join28(here, "..", "..", "package.json")];
9784
+ const candidates = [join29(here, "..", "package.json"), join29(here, "..", "..", "package.json")];
9258
9785
  for (const candidate of candidates) {
9259
- if (existsSync27(candidate)) {
9260
- const pkg = JSON.parse(readFileSync20(candidate, "utf-8"));
9786
+ if (existsSync28(candidate)) {
9787
+ const pkg = JSON.parse(readFileSync21(candidate, "utf-8"));
9261
9788
  if (pkg.version) {
9262
9789
  console.log(pkg.version);
9263
9790
  return;
@@ -9730,7 +10257,7 @@ async function main() {
9730
10257
  break;
9731
10258
  }
9732
10259
  if (packType === "page" && route && !id) {
9733
- const resolvedPath = essencePath ? resolveUserPath(essencePath) : join28(process.cwd(), "decantr.essence.json");
10260
+ const resolvedPath = essencePath ? resolveUserPath(essencePath) : join29(process.cwd(), "decantr.essence.json");
9734
10261
  id = resolvePagePackIdForRoute(resolvedPath, route);
9735
10262
  }
9736
10263
  await printHostedSelectedExecutionPack(