@decantr/cli 2.6.0 → 2.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +41 -19
- package/dist/bin.js +1 -1
- package/dist/{chunk-Q5OFRQY6.js → chunk-QTPNV5WU.js} +1327 -172
- package/dist/index.js +1 -1
- package/package.json +3 -3
|
@@ -34,10 +34,10 @@ import {
|
|
|
34
34
|
} from "./chunk-KT2ROK2D.js";
|
|
35
35
|
|
|
36
36
|
// src/index.ts
|
|
37
|
-
import { existsSync as
|
|
38
|
-
import { basename as
|
|
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
|
|
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
|
|
5572
|
-
import { join as
|
|
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 =
|
|
5633
|
-
const themePath =
|
|
5634
|
-
const howToPath =
|
|
5635
|
-
|
|
5636
|
-
if (
|
|
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
|
-
|
|
5644
|
-
if (!
|
|
5645
|
-
|
|
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 =
|
|
5654
|
-
if (!
|
|
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 =
|
|
6141
|
+
const files = readdirSync6(customThemesDir).filter((f) => f.endsWith(".json"));
|
|
5660
6142
|
for (const file of files) {
|
|
5661
|
-
const filePath =
|
|
6143
|
+
const filePath = join27(customThemesDir, file);
|
|
5662
6144
|
try {
|
|
5663
|
-
const data = JSON.parse(
|
|
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 =
|
|
5679
|
-
if (!
|
|
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 (!
|
|
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(
|
|
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 =
|
|
5721
|
-
const destPath =
|
|
5722
|
-
|
|
5723
|
-
const howToPath =
|
|
5724
|
-
if (!
|
|
5725
|
-
|
|
5726
|
-
}
|
|
5727
|
-
|
|
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
|
|
5736
|
-
import { dirname as dirname3, join as
|
|
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 =
|
|
5739
|
-
if (!
|
|
6220
|
+
const path = join28(dir, "package.json");
|
|
6221
|
+
if (!existsSync27(path)) return null;
|
|
5740
6222
|
try {
|
|
5741
|
-
return JSON.parse(
|
|
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 (
|
|
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 (
|
|
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 =
|
|
5776
|
-
if (!
|
|
5777
|
-
for (const entry of
|
|
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 =
|
|
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) :
|
|
6345
|
-
const indexPath =
|
|
6346
|
-
if (!
|
|
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 =
|
|
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 =
|
|
6354
|
-
if (
|
|
6355
|
-
assets[assetPath] =
|
|
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 (!
|
|
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 =
|
|
6864
|
+
const rootPrefix = basename3(resolvedSourcePath);
|
|
6383
6865
|
const walk = (absoluteDir, relativeDir) => {
|
|
6384
|
-
for (const entry of
|
|
6866
|
+
for (const entry of readdirSync8(absoluteDir, { withFileTypes: true })) {
|
|
6385
6867
|
if (ignoredDirNames.has(entry.name)) continue;
|
|
6386
|
-
const absolutePath =
|
|
6387
|
-
const relativePath =
|
|
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] =
|
|
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) :
|
|
6546
|
-
if (!
|
|
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(
|
|
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 =
|
|
6554
|
-
|
|
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: ${
|
|
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 (!
|
|
7073
|
+
if (!existsSync28(essencePath)) {
|
|
6592
7074
|
throw new Error(`Essence file not found at ${essencePath}`);
|
|
6593
7075
|
}
|
|
6594
|
-
const essence = JSON.parse(
|
|
6595
|
-
if (!
|
|
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) :
|
|
6610
|
-
if (!
|
|
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(
|
|
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 =
|
|
6628
|
-
|
|
6629
|
-
|
|
6630
|
-
|
|
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
|
-
|
|
6637
|
-
|
|
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) :
|
|
6663
|
-
if (!
|
|
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(
|
|
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 =
|
|
6674
|
-
|
|
6675
|
-
|
|
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 =
|
|
6697
|
-
const reviewPackPath =
|
|
6698
|
-
const manifestPath =
|
|
6699
|
-
if (
|
|
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 =
|
|
6703
|
-
if (!
|
|
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(
|
|
7194
|
+
const essence = JSON.parse(readFileSync21(essencePath, "utf-8"));
|
|
6713
7195
|
const bundle = await client.compileExecutionPacks(essence, { namespace });
|
|
6714
|
-
|
|
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 =
|
|
6723
|
-
const reviewPackPath =
|
|
6724
|
-
const manifestPath =
|
|
6725
|
-
if (
|
|
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 =
|
|
6729
|
-
if (!
|
|
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(
|
|
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
|
-
|
|
6743
|
-
|
|
6744
|
-
|
|
6745
|
-
|
|
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 (!
|
|
6749
|
-
|
|
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) :
|
|
6760
|
-
const resolvedTreatmentsPath = treatmentsPath ? resolveUserPath(treatmentsPath) :
|
|
6761
|
-
if (!
|
|
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 (!
|
|
7246
|
+
if (!existsSync28(resolvedEssencePath)) {
|
|
6765
7247
|
throw new Error(`Essence file not found at ${resolvedEssencePath}`);
|
|
6766
7248
|
}
|
|
6767
|
-
const code =
|
|
6768
|
-
const essence = JSON.parse(
|
|
6769
|
-
const treatmentsCss =
|
|
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) :
|
|
6794
|
-
if (!
|
|
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(
|
|
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) :
|
|
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 :
|
|
6919
|
-
if (
|
|
6920
|
-
pieces.push(
|
|
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 =
|
|
6925
|
-
if (
|
|
7406
|
+
const analysisPath = join29(process.cwd(), ".decantr", "analysis.json");
|
|
7407
|
+
if (existsSync28(analysisPath)) {
|
|
6926
7408
|
try {
|
|
6927
|
-
const analysis = JSON.parse(
|
|
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 =
|
|
6931
|
-
if (
|
|
6932
|
-
pieces.push(
|
|
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:
|
|
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:
|
|
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 ||
|
|
7550
|
+
const essencePath = path || join29(process.cwd(), "decantr.essence.json");
|
|
7069
7551
|
let raw;
|
|
7070
7552
|
try {
|
|
7071
|
-
raw =
|
|
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 =
|
|
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:
|
|
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 = [
|
|
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(
|
|
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 =
|
|
7236
|
-
if (!
|
|
7237
|
-
const backupPath =
|
|
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
|
-
|
|
7723
|
+
writeFileSync17(backupPath, readFileSync21(essencePath, "utf-8"), "utf-8");
|
|
7242
7724
|
return backupPath;
|
|
7243
7725
|
}
|
|
7244
7726
|
function writeBrownfieldProjectJson(input) {
|
|
7245
|
-
const decantrDir =
|
|
7246
|
-
|
|
7247
|
-
|
|
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
|
-
|
|
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 =
|
|
7305
|
-
const hasEssence =
|
|
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(
|
|
7320
|
-
if (!
|
|
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
|
-
|
|
7838
|
+
writeFileSync17(essencePath, JSON.stringify(essence, null, 2) + "\n", "utf-8");
|
|
7357
7839
|
const registryClient = new RegistryClient({
|
|
7358
|
-
cacheDir:
|
|
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:
|
|
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 (!
|
|
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 =
|
|
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 =
|
|
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 (
|
|
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 =
|
|
7950
|
-
const projectJsonPath =
|
|
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 (!
|
|
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(
|
|
8440
|
+
const essence = JSON.parse(readFileSync21(essencePath, "utf-8"));
|
|
7959
8441
|
const validation = validateEssence2(essence);
|
|
7960
|
-
const essenceVersion =
|
|
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 (
|
|
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 (
|
|
8488
|
+
if (existsSync28(projectJsonPath)) {
|
|
8007
8489
|
try {
|
|
8008
|
-
const projectJson = JSON.parse(
|
|
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 =
|
|
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 =
|
|
8222
|
-
if (!
|
|
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(
|
|
8710
|
+
const theme = JSON.parse(readFileSync21(themePath, "utf-8"));
|
|
8229
8711
|
const result = validateCustomTheme(theme);
|
|
8230
8712
|
if (result.valid) {
|
|
8231
8713
|
console.log(success3(`Custom theme "${name}" is valid`));
|
|
@@ -8293,14 +8775,524 @@ ${BOLD7}Examples:${RESET14}
|
|
|
8293
8775
|
process.exitCode = 1;
|
|
8294
8776
|
}
|
|
8295
8777
|
}
|
|
8778
|
+
function parseLooseArgs(args, startIndex = 1) {
|
|
8779
|
+
const flags = {};
|
|
8780
|
+
const positional = [];
|
|
8781
|
+
for (let index = startIndex; index < args.length; index += 1) {
|
|
8782
|
+
const arg = args[index];
|
|
8783
|
+
if (arg === "-y") {
|
|
8784
|
+
flags.yes = true;
|
|
8785
|
+
continue;
|
|
8786
|
+
}
|
|
8787
|
+
if (arg.startsWith("--no-")) {
|
|
8788
|
+
flags[arg.slice(5)] = false;
|
|
8789
|
+
continue;
|
|
8790
|
+
}
|
|
8791
|
+
if (arg.startsWith("--")) {
|
|
8792
|
+
const body = arg.slice(2);
|
|
8793
|
+
const equalsIndex = body.indexOf("=");
|
|
8794
|
+
if (equalsIndex !== -1) {
|
|
8795
|
+
flags[body.slice(0, equalsIndex)] = body.slice(equalsIndex + 1);
|
|
8796
|
+
continue;
|
|
8797
|
+
}
|
|
8798
|
+
if (args[index + 1] && !args[index + 1].startsWith("-")) {
|
|
8799
|
+
flags[body] = args[++index];
|
|
8800
|
+
} else {
|
|
8801
|
+
flags[body] = true;
|
|
8802
|
+
}
|
|
8803
|
+
continue;
|
|
8804
|
+
}
|
|
8805
|
+
positional.push(arg);
|
|
8806
|
+
}
|
|
8807
|
+
return { flags, positional };
|
|
8808
|
+
}
|
|
8809
|
+
function flagString(flags, key) {
|
|
8810
|
+
const value = flags[key];
|
|
8811
|
+
return typeof value === "string" ? value : void 0;
|
|
8812
|
+
}
|
|
8813
|
+
function flagBoolean(flags, key, defaultValue = false) {
|
|
8814
|
+
const value = flags[key];
|
|
8815
|
+
if (typeof value === "boolean") return value;
|
|
8816
|
+
if (typeof value === "string") return value !== "false";
|
|
8817
|
+
return defaultValue;
|
|
8818
|
+
}
|
|
8819
|
+
function withoutWorkflowOnlyFlags(args) {
|
|
8820
|
+
const stripped = [];
|
|
8821
|
+
const flagsWithValues = /* @__PURE__ */ new Set(["--project"]);
|
|
8822
|
+
for (let index = 1; index < args.length; index += 1) {
|
|
8823
|
+
const arg = args[index];
|
|
8824
|
+
if (arg === "--brownfield" || arg === "--local-patterns" || arg === "--workspace" || arg === "--baseline") {
|
|
8825
|
+
continue;
|
|
8826
|
+
}
|
|
8827
|
+
if (arg.startsWith("--project=")) {
|
|
8828
|
+
continue;
|
|
8829
|
+
}
|
|
8830
|
+
if (flagsWithValues.has(arg)) {
|
|
8831
|
+
index += 1;
|
|
8832
|
+
continue;
|
|
8833
|
+
}
|
|
8834
|
+
stripped.push(arg);
|
|
8835
|
+
}
|
|
8836
|
+
return stripped;
|
|
8837
|
+
}
|
|
8838
|
+
function resolveWorkflowProject(flags) {
|
|
8839
|
+
const projectArg = flagString(flags, "project");
|
|
8840
|
+
const workspaceInfo = resolveWorkspaceInfo(process.cwd(), projectArg);
|
|
8841
|
+
if (workspaceInfo.requiresProjectSelection) {
|
|
8842
|
+
console.log(error3("This looks like a workspace root with multiple app candidates."));
|
|
8843
|
+
console.log(dim3(`Use --project=<path>. Candidates: ${workspaceInfo.appCandidates.join(", ")}`));
|
|
8844
|
+
process.exitCode = 1;
|
|
8845
|
+
return null;
|
|
8846
|
+
}
|
|
8847
|
+
return workspaceInfo;
|
|
8848
|
+
}
|
|
8849
|
+
function printWorkflowPlan(title, steps) {
|
|
8850
|
+
console.log(heading2(title));
|
|
8851
|
+
console.log(" Decantr will run this workflow:");
|
|
8852
|
+
for (const step of steps) {
|
|
8853
|
+
console.log(` ${cyan3(step)}`);
|
|
8854
|
+
}
|
|
8855
|
+
console.log("");
|
|
8856
|
+
}
|
|
8857
|
+
async function cmdSetupWorkflow(args) {
|
|
8858
|
+
const { flags } = parseLooseArgs(args);
|
|
8859
|
+
const workspaceInfo = resolveWorkflowProject(flags);
|
|
8860
|
+
if (!workspaceInfo) return;
|
|
8861
|
+
const detected = detectProject(workspaceInfo.appRoot);
|
|
8862
|
+
const hasFootprint = detected.framework !== "unknown" || detected.packageManager !== "unknown" || detected.hasTypeScript || detected.hasTailwind || detected.existingRuleFiles.length > 0;
|
|
8863
|
+
console.log(heading2("Decantr Setup"));
|
|
8864
|
+
console.log(` Project: ${workspaceInfo.appRoot}`);
|
|
8865
|
+
console.log(` Detected: ${formatDetection(detected)}`);
|
|
8866
|
+
console.log("");
|
|
8867
|
+
if (detected.existingEssence) {
|
|
8868
|
+
console.log(`${BOLD7}Recommended path:${RESET14} maintain an attached Decantr project`);
|
|
8869
|
+
console.log(` ${cyan3('decantr task <route> "<change>"')} Prepare LLM context before edits`);
|
|
8870
|
+
console.log(` ${cyan3("decantr verify --brownfield")} Run local health and drift checks`);
|
|
8871
|
+
console.log(` ${cyan3("decantr codify --from-audit")} Propose project-owned local law`);
|
|
8872
|
+
return;
|
|
8873
|
+
}
|
|
8874
|
+
if (hasFootprint) {
|
|
8875
|
+
console.log(`${BOLD7}Recommended path:${RESET14} brownfield adoption`);
|
|
8876
|
+
console.log(` ${cyan3("decantr adopt --yes")} Analyze, attach, and verify`);
|
|
8877
|
+
console.log(
|
|
8878
|
+
` ${cyan3("decantr adopt --base-url http://localhost:3000 --evidence --yes")} Include visual evidence`
|
|
8879
|
+
);
|
|
8880
|
+
console.log(` ${cyan3("decantr codify --from-audit")} Propose local UI law`);
|
|
8881
|
+
return;
|
|
8882
|
+
}
|
|
8883
|
+
console.log(`${BOLD7}Recommended path:${RESET14} greenfield start`);
|
|
8884
|
+
console.log(` ${cyan3("decantr new my-app --blueprint=<slug>")}`);
|
|
8885
|
+
console.log(` ${cyan3("decantr init --workflow=greenfield --adoption=contract-only")}`);
|
|
8886
|
+
}
|
|
8887
|
+
async function cmdAdoptWorkflow(args) {
|
|
8888
|
+
const { flags } = parseLooseArgs(args);
|
|
8889
|
+
const workspaceInfo = resolveWorkflowProject(flags);
|
|
8890
|
+
if (!workspaceInfo) return;
|
|
8891
|
+
const projectRoot = workspaceInfo.appRoot;
|
|
8892
|
+
const dryRun = flagBoolean(flags, "dry-run");
|
|
8893
|
+
const yes = flagBoolean(flags, "yes") || flagBoolean(flags, "y");
|
|
8894
|
+
const baseUrl = flagString(flags, "base-url");
|
|
8895
|
+
const runVerify = flagBoolean(flags, "verify", true);
|
|
8896
|
+
const runBrowser = flagBoolean(flags, "browser") || Boolean(baseUrl);
|
|
8897
|
+
const evidence = flagBoolean(flags, "evidence") || runBrowser;
|
|
8898
|
+
const saveBaseline = flagBoolean(flags, "baseline", true) || flagBoolean(flags, "save-baseline");
|
|
8899
|
+
const initCi = flagBoolean(flags, "ci") || flagBoolean(flags, "init-ci");
|
|
8900
|
+
const assistantBridge = flagString(flags, "assistant-bridge");
|
|
8901
|
+
const hasEssence = existsSync28(join29(projectRoot, "decantr.essence.json"));
|
|
8902
|
+
const proposalFlag = flagBoolean(flags, "replace-essence") ? "--replace-essence" : flagBoolean(flags, "merge-proposal") || hasEssence ? "--merge-proposal" : "--accept-proposal";
|
|
8903
|
+
const steps = [
|
|
8904
|
+
"analyze current app and write .decantr/brownfield intelligence",
|
|
8905
|
+
`init --existing ${proposalFlag} as contract-only Brownfield`
|
|
8906
|
+
];
|
|
8907
|
+
if (runVerify) {
|
|
8908
|
+
steps.push(
|
|
8909
|
+
runBrowser ? "verify with Project Health, browser evidence, visual manifest, and baseline" : "verify with Project Health and baseline"
|
|
8910
|
+
);
|
|
8911
|
+
}
|
|
8912
|
+
if (initCi) {
|
|
8913
|
+
steps.push("install Project Health CI gate");
|
|
8914
|
+
}
|
|
8915
|
+
printWorkflowPlan("Decantr Adopt", steps);
|
|
8916
|
+
if (dryRun) {
|
|
8917
|
+
console.log(dim3("Dry run only. No files were written."));
|
|
8918
|
+
return;
|
|
8919
|
+
}
|
|
8920
|
+
if (!yes) {
|
|
8921
|
+
const ok = await confirm("Run this Brownfield adoption workflow?", false);
|
|
8922
|
+
if (!ok) {
|
|
8923
|
+
console.log(dim3("Cancelled."));
|
|
8924
|
+
return;
|
|
8925
|
+
}
|
|
8926
|
+
}
|
|
8927
|
+
await cmdAnalyze(projectRoot, workspaceInfo);
|
|
8928
|
+
if (process.exitCode && process.exitCode !== 0) return;
|
|
8929
|
+
await cmdInit({
|
|
8930
|
+
existing: true,
|
|
8931
|
+
yes: true,
|
|
8932
|
+
project: flagString(flags, "project"),
|
|
8933
|
+
"accept-proposal": proposalFlag === "--accept-proposal",
|
|
8934
|
+
"merge-proposal": proposalFlag === "--merge-proposal",
|
|
8935
|
+
"replace-essence": proposalFlag === "--replace-essence",
|
|
8936
|
+
"assistant-bridge": assistantBridge,
|
|
8937
|
+
telemetry: flagBoolean(flags, "telemetry")
|
|
8938
|
+
});
|
|
8939
|
+
if (process.exitCode && process.exitCode !== 0) return;
|
|
8940
|
+
if (runVerify) {
|
|
8941
|
+
const { cmdHealth } = await import("./health-ETZXWGTW.js");
|
|
8942
|
+
await cmdHealth(projectRoot, {
|
|
8943
|
+
browser: runBrowser,
|
|
8944
|
+
browserBaseUrl: baseUrl,
|
|
8945
|
+
evidence,
|
|
8946
|
+
output: evidence ? ".decantr/evidence/latest.json" : void 0,
|
|
8947
|
+
saveBaseline
|
|
8948
|
+
});
|
|
8949
|
+
}
|
|
8950
|
+
if (initCi) {
|
|
8951
|
+
const { cmdHealth } = await import("./health-ETZXWGTW.js");
|
|
8952
|
+
const ciRoot = flagString(flags, "project") ? process.cwd() : projectRoot;
|
|
8953
|
+
await cmdHealth(ciRoot, {
|
|
8954
|
+
initCi: {
|
|
8955
|
+
projectPath: flagString(flags, "project"),
|
|
8956
|
+
failOn: "error"
|
|
8957
|
+
}
|
|
8958
|
+
});
|
|
8959
|
+
}
|
|
8960
|
+
console.log("");
|
|
8961
|
+
console.log(`${BOLD7}Brownfield operating loop:${RESET14}`);
|
|
8962
|
+
console.log(` ${cyan3("decantr codify --from-audit")} Discover and propose project-owned UI law`);
|
|
8963
|
+
console.log(` ${cyan3("decantr codify --accept")} Accept reviewed local patterns and rules`);
|
|
8964
|
+
console.log(` ${cyan3('decantr task <route> "<change>"')} Give your LLM route-specific context before edits`);
|
|
8965
|
+
console.log(` ${cyan3("decantr verify --brownfield --local-patterns")} Check contract, health, and local law after edits`);
|
|
8966
|
+
console.log(` ${cyan3("decantr verify --since-baseline")} Compare future work against this baseline`);
|
|
8967
|
+
}
|
|
8968
|
+
async function cmdVerifyWorkflow(args) {
|
|
8969
|
+
const { flags } = parseLooseArgs(args);
|
|
8970
|
+
const workspaceMode = flagBoolean(flags, "workspace");
|
|
8971
|
+
if (args[1] === "init-ci") {
|
|
8972
|
+
const { cmdHealth: cmdHealth2, parseHealthArgs: parseHealthArgs2 } = await import("./health-ETZXWGTW.js");
|
|
8973
|
+
await cmdHealth2(process.cwd(), parseHealthArgs2(["health", ...args.slice(1)]));
|
|
8974
|
+
return;
|
|
8975
|
+
}
|
|
8976
|
+
if (workspaceMode) {
|
|
8977
|
+
const { cmdWorkspace } = await import("./workspace-KSFWRZEX.js");
|
|
8978
|
+
await cmdWorkspace(process.cwd(), ["workspace", "health", ...withoutWorkflowOnlyFlags(args)]);
|
|
8979
|
+
return;
|
|
8980
|
+
}
|
|
8981
|
+
const workspaceInfo = resolveWorkflowProject(flags);
|
|
8982
|
+
if (!workspaceInfo) return;
|
|
8983
|
+
const brownfield = flagBoolean(flags, "brownfield");
|
|
8984
|
+
const localPatterns = flagBoolean(flags, "local-patterns");
|
|
8985
|
+
const evidence = flagBoolean(flags, "evidence");
|
|
8986
|
+
const baseUrl = flagString(flags, "base-url");
|
|
8987
|
+
const failOn = flagString(flags, "fail-on") ?? "error";
|
|
8988
|
+
const healthArgs = ["health", ...withoutWorkflowOnlyFlags(args)];
|
|
8989
|
+
if (flagBoolean(flags, "baseline") && !healthArgs.includes("--save-baseline")) {
|
|
8990
|
+
healthArgs.push("--save-baseline");
|
|
8991
|
+
}
|
|
8992
|
+
if (evidence && !flagString(flags, "output")) {
|
|
8993
|
+
healthArgs.push("--output", ".decantr/evidence/latest.json");
|
|
8994
|
+
}
|
|
8995
|
+
if (baseUrl && !healthArgs.includes("--browser")) {
|
|
8996
|
+
healthArgs.push("--browser");
|
|
8997
|
+
}
|
|
8998
|
+
const quietOutput = flagBoolean(flags, "json") || flagBoolean(flags, "ci") || Boolean(flagString(flags, "output"));
|
|
8999
|
+
if (!quietOutput) {
|
|
9000
|
+
console.log(heading2("Decantr Verify"));
|
|
9001
|
+
console.log(
|
|
9002
|
+
dim3(
|
|
9003
|
+
brownfield ? "Running Brownfield guard validation before Project Health." : "Running Project Health as the canonical reliability gate."
|
|
9004
|
+
)
|
|
9005
|
+
);
|
|
9006
|
+
console.log("");
|
|
9007
|
+
}
|
|
9008
|
+
let guardExitCode;
|
|
9009
|
+
if (brownfield) {
|
|
9010
|
+
const { cmdHeal, collectCheckIssues } = await import("./heal-ZYD6NVGE.js");
|
|
9011
|
+
if (quietOutput) {
|
|
9012
|
+
const result = collectCheckIssues(workspaceInfo.appRoot, { brownfield: true });
|
|
9013
|
+
guardExitCode = result.issues.some((issue) => issue.type === "error") ? 1 : void 0;
|
|
9014
|
+
} else {
|
|
9015
|
+
await cmdHeal(workspaceInfo.appRoot, { brownfield: true });
|
|
9016
|
+
guardExitCode = process.exitCode;
|
|
9017
|
+
process.exitCode = void 0;
|
|
9018
|
+
}
|
|
9019
|
+
}
|
|
9020
|
+
const { cmdHealth, parseHealthArgs } = await import("./health-ETZXWGTW.js");
|
|
9021
|
+
await cmdHealth(workspaceInfo.appRoot, parseHealthArgs(healthArgs));
|
|
9022
|
+
if (localPatterns) {
|
|
9023
|
+
const validation = validateLocalLaw(workspaceInfo.appRoot);
|
|
9024
|
+
if (!validation.patternPackPresent) {
|
|
9025
|
+
if (!quietOutput) {
|
|
9026
|
+
console.log("");
|
|
9027
|
+
console.log(
|
|
9028
|
+
`${YELLOW9}Local pattern pack missing.${RESET14} Run ${cyan3("decantr codify --from-audit")}, review the proposal, then run ${cyan3("decantr codify --accept")}.`
|
|
9029
|
+
);
|
|
9030
|
+
}
|
|
9031
|
+
process.exitCode = process.exitCode || 1;
|
|
9032
|
+
} else {
|
|
9033
|
+
const blockingFindings = failOn === "none" ? [] : validation.findings.filter(
|
|
9034
|
+
(finding) => failOn === "warn" ? finding.severity === "warn" || finding.severity === "error" : finding.severity === "error"
|
|
9035
|
+
);
|
|
9036
|
+
const blockingWarnings = failOn === "warn" ? validation.warnings : [];
|
|
9037
|
+
if (!quietOutput) {
|
|
9038
|
+
console.log("");
|
|
9039
|
+
console.log(`${GREEN14}Local pattern pack found:${RESET14} ${validation.patternsPath}`);
|
|
9040
|
+
if (validation.ruleManifestPresent) {
|
|
9041
|
+
console.log(`${GREEN14}Local rule manifest found:${RESET14} ${validation.rulesPath}`);
|
|
9042
|
+
} else {
|
|
9043
|
+
console.log(
|
|
9044
|
+
`${YELLOW9}Local rule manifest missing.${RESET14} Run ${cyan3("decantr codify --from-audit")} to propose .decantr/rules.json.`
|
|
9045
|
+
);
|
|
9046
|
+
}
|
|
9047
|
+
for (const warning of validation.warnings.slice(0, 8)) {
|
|
9048
|
+
console.log(`${YELLOW9}warn${RESET14} ${warning}`);
|
|
9049
|
+
}
|
|
9050
|
+
if (validation.findings.length > 0) {
|
|
9051
|
+
console.log("");
|
|
9052
|
+
console.log(`${BOLD7}Local law findings:${RESET14}`);
|
|
9053
|
+
for (const finding of validation.findings.slice(0, 20)) {
|
|
9054
|
+
console.log(
|
|
9055
|
+
` ${finding.severity.toUpperCase()} ${finding.ruleId} ${finding.file}:${finding.line}:${finding.column} ${finding.message}`
|
|
9056
|
+
);
|
|
9057
|
+
}
|
|
9058
|
+
if (validation.findings.length > 20) {
|
|
9059
|
+
console.log(dim3(` ...${validation.findings.length - 20} more finding(s)`));
|
|
9060
|
+
}
|
|
9061
|
+
} else if (validation.ruleManifestPresent) {
|
|
9062
|
+
console.log(`${GREEN14}Local rule checks passed.${RESET14}`);
|
|
9063
|
+
}
|
|
9064
|
+
}
|
|
9065
|
+
if (blockingFindings.length > 0 || blockingWarnings.length > 0) {
|
|
9066
|
+
process.exitCode = process.exitCode || 1;
|
|
9067
|
+
}
|
|
9068
|
+
}
|
|
9069
|
+
}
|
|
9070
|
+
if (guardExitCode && guardExitCode !== 0 && (!process.exitCode || process.exitCode === 0)) {
|
|
9071
|
+
process.exitCode = guardExitCode;
|
|
9072
|
+
}
|
|
9073
|
+
}
|
|
9074
|
+
function readJsonIfPresent(path) {
|
|
9075
|
+
if (!existsSync28(path)) return null;
|
|
9076
|
+
try {
|
|
9077
|
+
return JSON.parse(readFileSync21(path, "utf-8"));
|
|
9078
|
+
} catch {
|
|
9079
|
+
return null;
|
|
9080
|
+
}
|
|
9081
|
+
}
|
|
9082
|
+
async function cmdTaskWorkflow(args) {
|
|
9083
|
+
const { flags, positional } = parseLooseArgs(args);
|
|
9084
|
+
const workspaceInfo = resolveWorkflowProject(flags);
|
|
9085
|
+
if (!workspaceInfo) return;
|
|
9086
|
+
const routeInput = positional[0];
|
|
9087
|
+
if (!routeInput) {
|
|
9088
|
+
console.error(error3('Usage: decantr task <route> ["task summary"] [--project <path>] [--since origin/main] [--json]'));
|
|
9089
|
+
process.exitCode = 1;
|
|
9090
|
+
return;
|
|
9091
|
+
}
|
|
9092
|
+
const route = routeInput.startsWith("/") ? routeInput : `/${routeInput}`;
|
|
9093
|
+
const taskSummary = positional.slice(1).join(" ").trim();
|
|
9094
|
+
const essencePath = join29(workspaceInfo.appRoot, "decantr.essence.json");
|
|
9095
|
+
const essence = readJsonIfPresent(essencePath);
|
|
9096
|
+
if (!essence) {
|
|
9097
|
+
console.error(error3("No decantr.essence.json found. Run `decantr adopt` or `decantr init` first."));
|
|
9098
|
+
process.exitCode = 1;
|
|
9099
|
+
return;
|
|
9100
|
+
}
|
|
9101
|
+
if (!isV48(essence)) {
|
|
9102
|
+
console.error(error3("Task context requires Essence v4. Run `decantr migrate --to v4` first."));
|
|
9103
|
+
process.exitCode = 1;
|
|
9104
|
+
return;
|
|
9105
|
+
}
|
|
9106
|
+
const target = essence.blueprint.routes?.[route];
|
|
9107
|
+
if (!target) {
|
|
9108
|
+
const knownRoutes = Object.keys(essence.blueprint.routes ?? {}).sort();
|
|
9109
|
+
console.error(error3(`Route not found in Decantr contract: ${route}`));
|
|
9110
|
+
console.error(dim3(`Known routes: ${knownRoutes.join(", ") || "none"}`));
|
|
9111
|
+
process.exitCode = 1;
|
|
9112
|
+
return;
|
|
9113
|
+
}
|
|
9114
|
+
const section = essence.blueprint.sections.find((entry) => entry.id === target.section);
|
|
9115
|
+
const page = section?.pages.find((entry) => entry.id === target.page);
|
|
9116
|
+
const contextDir = join29(workspaceInfo.appRoot, ".decantr", "context");
|
|
9117
|
+
const manifest = readJsonIfPresent(join29(contextDir, "pack-manifest.json"));
|
|
9118
|
+
const pagePack = manifest?.pages?.find((entry) => entry.id === target.page);
|
|
9119
|
+
const sectionPack = manifest?.sections?.find((entry) => entry.id === target.section);
|
|
9120
|
+
const visualManifest = readJsonIfPresent(join29(workspaceInfo.appRoot, ".decantr", "evidence", "visual-manifest.json"));
|
|
9121
|
+
const screenshot = visualManifest?.routes?.find((entry) => entry.route === route)?.screenshot;
|
|
9122
|
+
const localPatternPackPath = localPatternsPath(workspaceInfo.appRoot);
|
|
9123
|
+
const localRuleManifestPath = localRulesPath(workspaceInfo.appRoot);
|
|
9124
|
+
const localLaw = createLocalLawTaskSummary(workspaceInfo.appRoot);
|
|
9125
|
+
const changedSince = flagString(flags, "since");
|
|
9126
|
+
const currentChangedFiles = changedFiles(workspaceInfo.appRoot, changedSince);
|
|
9127
|
+
const changedRoutes = routeImpacts(workspaceInfo.appRoot, currentChangedFiles);
|
|
9128
|
+
const context = {
|
|
9129
|
+
route,
|
|
9130
|
+
task: taskSummary || null,
|
|
9131
|
+
section: target.section,
|
|
9132
|
+
page: target.page,
|
|
9133
|
+
shell: page?.shell ?? section?.shell ?? null,
|
|
9134
|
+
patterns: page?.layout?.map(extractPatternName) ?? [],
|
|
9135
|
+
read: [
|
|
9136
|
+
pagePack ? join29(".decantr/context", pagePack.markdown) : null,
|
|
9137
|
+
sectionPack ? join29(".decantr/context", sectionPack.markdown) : null,
|
|
9138
|
+
manifest?.scaffold?.markdown ? join29(".decantr/context", manifest.scaffold.markdown) : null,
|
|
9139
|
+
".decantr/context/scaffold.md",
|
|
9140
|
+
"DECANTR.md",
|
|
9141
|
+
existsSync28(localPatternPackPath) ? ".decantr/local-patterns.json" : null,
|
|
9142
|
+
existsSync28(localRuleManifestPath) ? ".decantr/rules.json" : null
|
|
9143
|
+
].filter(Boolean),
|
|
9144
|
+
screenshot: screenshot ?? null,
|
|
9145
|
+
localLaw,
|
|
9146
|
+
changedFiles: currentChangedFiles,
|
|
9147
|
+
changedRoutes,
|
|
9148
|
+
verifyCommand: "decantr verify --brownfield --local-patterns"
|
|
9149
|
+
};
|
|
9150
|
+
if (flagBoolean(flags, "json")) {
|
|
9151
|
+
console.log(JSON.stringify(context, null, 2));
|
|
9152
|
+
return;
|
|
9153
|
+
}
|
|
9154
|
+
console.log(heading2("Decantr Task Context"));
|
|
9155
|
+
console.log(` Route: ${cyan3(context.route)}`);
|
|
9156
|
+
console.log(` Section/page: ${context.section}/${context.page}`);
|
|
9157
|
+
if (context.shell) console.log(` Shell: ${context.shell}`);
|
|
9158
|
+
if (context.patterns.length > 0) console.log(` Patterns: ${context.patterns.join(", ")}`);
|
|
9159
|
+
if (taskSummary) console.log(` Task: ${taskSummary}`);
|
|
9160
|
+
console.log("");
|
|
9161
|
+
console.log(`${BOLD7}Read before editing:${RESET14}`);
|
|
9162
|
+
for (const path of context.read) {
|
|
9163
|
+
console.log(` ${cyan3(path)}`);
|
|
9164
|
+
}
|
|
9165
|
+
if (context.screenshot) {
|
|
9166
|
+
console.log("");
|
|
9167
|
+
console.log(`${BOLD7}Visual evidence:${RESET14}`);
|
|
9168
|
+
console.log(` ${cyan3(context.screenshot)}`);
|
|
9169
|
+
}
|
|
9170
|
+
if (context.localLaw.patternCount > 0 || context.localLaw.ruleCount > 0) {
|
|
9171
|
+
console.log("");
|
|
9172
|
+
console.log(`${BOLD7}Project-owned local law:${RESET14}`);
|
|
9173
|
+
if (context.localLaw.patternsPath) {
|
|
9174
|
+
console.log(` Patterns: ${cyan3(context.localLaw.patternsPath)} (${context.localLaw.patternCount})`);
|
|
9175
|
+
}
|
|
9176
|
+
if (context.localLaw.rulesPath) {
|
|
9177
|
+
console.log(` Rules: ${cyan3(context.localLaw.rulesPath)} (${context.localLaw.ruleCount})`);
|
|
9178
|
+
}
|
|
9179
|
+
for (const pattern of context.localLaw.patterns.slice(0, 4)) {
|
|
9180
|
+
const pathHint = pattern.componentPaths.length > 0 ? ` \u2014 ${pattern.componentPaths.slice(0, 2).join(", ")}` : "";
|
|
9181
|
+
console.log(` ${pattern.id}: ${pattern.role ?? "local pattern"}${pathHint}`);
|
|
9182
|
+
}
|
|
9183
|
+
} else {
|
|
9184
|
+
console.log("");
|
|
9185
|
+
console.log(`${BOLD7}Project-owned local law:${RESET14}`);
|
|
9186
|
+
console.log(` ${YELLOW9}Not codified yet.${RESET14} Run ${cyan3("decantr codify --from-audit")} after adoption.`);
|
|
9187
|
+
}
|
|
9188
|
+
if (context.changedFiles.length > 0) {
|
|
9189
|
+
console.log("");
|
|
9190
|
+
console.log(`${BOLD7}Changed-file context:${RESET14}`);
|
|
9191
|
+
for (const file of context.changedFiles.slice(0, 8)) {
|
|
9192
|
+
console.log(` ${file}`);
|
|
9193
|
+
}
|
|
9194
|
+
if (context.changedFiles.length > 8) {
|
|
9195
|
+
console.log(dim3(` ...${context.changedFiles.length - 8} more changed file(s)`));
|
|
9196
|
+
}
|
|
9197
|
+
if (context.changedRoutes.length > 0) {
|
|
9198
|
+
console.log(` Impacted routes: ${context.changedRoutes.join(", ")}`);
|
|
9199
|
+
}
|
|
9200
|
+
}
|
|
9201
|
+
console.log("");
|
|
9202
|
+
console.log(`${BOLD7}LLM instruction:${RESET14}`);
|
|
9203
|
+
console.log(
|
|
9204
|
+
" Preserve the existing runtime and styling system. Use the route pack, section context, local laws, changed-file impact, and visual evidence above as the task contract before changing code."
|
|
9205
|
+
);
|
|
9206
|
+
console.log(` After editing, run ${cyan3(context.verifyCommand)}.`);
|
|
9207
|
+
}
|
|
9208
|
+
async function cmdCodifyWorkflow(args) {
|
|
9209
|
+
const { flags } = parseLooseArgs(args);
|
|
9210
|
+
const workspaceInfo = resolveWorkflowProject(flags);
|
|
9211
|
+
if (!workspaceInfo) return;
|
|
9212
|
+
if (flagBoolean(flags, "accept")) {
|
|
9213
|
+
if (!existsSync28(localPatternsProposalPath(workspaceInfo.appRoot)) && !existsSync28(localRulesProposalPath(workspaceInfo.appRoot))) {
|
|
9214
|
+
console.error(
|
|
9215
|
+
error3("No local law proposal found. Run `decantr codify --from-audit` or `decantr codify` first.")
|
|
9216
|
+
);
|
|
9217
|
+
process.exitCode = 1;
|
|
9218
|
+
return;
|
|
9219
|
+
}
|
|
9220
|
+
const result2 = acceptBrownfieldLocalLaw(workspaceInfo.appRoot);
|
|
9221
|
+
if (result2.patternAcceptedPath) {
|
|
9222
|
+
console.log(success3(`Accepted local pattern pack: ${result2.patternAcceptedPath}`));
|
|
9223
|
+
}
|
|
9224
|
+
if (result2.rulesAcceptedPath) {
|
|
9225
|
+
console.log(success3(`Accepted local rule manifest: ${result2.rulesAcceptedPath}`));
|
|
9226
|
+
}
|
|
9227
|
+
console.log(dim3("Run `decantr verify --brownfield --local-patterns` after project edits."));
|
|
9228
|
+
return;
|
|
9229
|
+
}
|
|
9230
|
+
const detected = detectProject(workspaceInfo.appRoot);
|
|
9231
|
+
const essence = readJsonIfPresent(join29(workspaceInfo.appRoot, "decantr.essence.json"));
|
|
9232
|
+
const fromAudit = flagBoolean(flags, "from-audit") || flagBoolean(flags, "discover-local-patterns") || flagBoolean(flags, "codify-local-patterns");
|
|
9233
|
+
const proposal = createBrownfieldCodifyProposal({
|
|
9234
|
+
projectRoot: workspaceInfo.appRoot,
|
|
9235
|
+
detected,
|
|
9236
|
+
essence,
|
|
9237
|
+
fromAudit
|
|
9238
|
+
});
|
|
9239
|
+
const result = writeBrownfieldCodifyProposal(workspaceInfo.appRoot, proposal);
|
|
9240
|
+
console.log(success3(`Wrote local pattern proposal: ${result.patternPath}`));
|
|
9241
|
+
console.log(success3(`Wrote local rule proposal: ${result.rulesPath}`));
|
|
9242
|
+
if (fromAudit) {
|
|
9243
|
+
console.log(dim3("Proposal includes source-derived component candidates and starter mechanical rules."));
|
|
9244
|
+
}
|
|
9245
|
+
console.log(dim3("Review both files, add real component paths/token recipes, then run `decantr codify --accept`."));
|
|
9246
|
+
}
|
|
9247
|
+
async function cmdContentWorkflow(args) {
|
|
9248
|
+
const subcommand = args[1] ?? "check";
|
|
9249
|
+
if (subcommand === "check" || subcommand === "health") {
|
|
9250
|
+
const { cmdContentHealth, parseContentHealthArgs } = await import("./content-health-QQHBR6XG.js");
|
|
9251
|
+
await cmdContentHealth(process.cwd(), parseContentHealthArgs(["content-health", ...args.slice(2)]));
|
|
9252
|
+
return;
|
|
9253
|
+
}
|
|
9254
|
+
if (subcommand === "create") {
|
|
9255
|
+
const type = args[2];
|
|
9256
|
+
const name = args[3];
|
|
9257
|
+
if (!type || !name) {
|
|
9258
|
+
console.error(error3("Usage: decantr content create <type> <name>"));
|
|
9259
|
+
process.exitCode = 1;
|
|
9260
|
+
return;
|
|
9261
|
+
}
|
|
9262
|
+
cmdCreate(type, name);
|
|
9263
|
+
return;
|
|
9264
|
+
}
|
|
9265
|
+
if (subcommand === "publish") {
|
|
9266
|
+
const type = args[2];
|
|
9267
|
+
const name = args[3];
|
|
9268
|
+
if (!type || !name) {
|
|
9269
|
+
console.error(error3("Usage: decantr content publish <type> <name>"));
|
|
9270
|
+
process.exitCode = 1;
|
|
9271
|
+
return;
|
|
9272
|
+
}
|
|
9273
|
+
await cmdPublish(type, name);
|
|
9274
|
+
return;
|
|
9275
|
+
}
|
|
9276
|
+
console.error(error3("Usage: decantr content <check|create|publish>"));
|
|
9277
|
+
process.exitCode = 1;
|
|
9278
|
+
}
|
|
8296
9279
|
function cmdHelp() {
|
|
8297
9280
|
console.log(`
|
|
8298
9281
|
${BOLD7}decantr${RESET14} \u2014 Design intelligence for AI-generated UI
|
|
8299
9282
|
|
|
8300
9283
|
${BOLD7}Usage:${RESET14}
|
|
9284
|
+
decantr setup [--project <path>]
|
|
8301
9285
|
decantr new <name> [--blueprint=X] [--archetype=X] [--theme=X] [--workflow=greenfield] [--adoption=decantr-css] [--telemetry]
|
|
8302
|
-
decantr
|
|
9286
|
+
decantr adopt [--project <path>] [--base-url <url>] [--evidence] [--ci] [--yes]
|
|
9287
|
+
decantr task <route> ["task summary"] [--project <path>] [--since origin/main] [--json]
|
|
9288
|
+
decantr verify [--project <path>] [--brownfield] [--local-patterns] [health options]
|
|
9289
|
+
decantr codify [--from-audit] [--accept] [--project <path>]
|
|
9290
|
+
decantr studio [--port 4319] [--host 127.0.0.1] [--report decantr-health.json] [--workspace]
|
|
9291
|
+
|
|
9292
|
+
${BOLD7}Advanced primitives:${RESET14}
|
|
8303
9293
|
decantr init [options]
|
|
9294
|
+
decantr analyze
|
|
9295
|
+
decantr magic <prompt> [--dry-run]
|
|
8304
9296
|
decantr status
|
|
8305
9297
|
decantr sync
|
|
8306
9298
|
decantr audit [file]
|
|
@@ -8324,8 +9316,8 @@ ${BOLD7}Usage:${RESET14}
|
|
|
8324
9316
|
decantr health init-ci [--force] [--project <path>] [--workspace] [--fail-on <error|warn|none>] [--cli-version <version|latest>]
|
|
8325
9317
|
decantr workspace list [--json]
|
|
8326
9318
|
decantr workspace health [--json] [--changed --since origin/main]
|
|
9319
|
+
decantr content check [--json] [--markdown] [--ci]
|
|
8327
9320
|
decantr content-health [--json] [--markdown] [--ci]
|
|
8328
|
-
decantr studio [--port 4319] [--host 127.0.0.1] [--report decantr-health.json] [--workspace]
|
|
8329
9321
|
decantr telemetry status [--json]
|
|
8330
9322
|
decantr telemetry explain [--json]
|
|
8331
9323
|
decantr telemetry link [--enable] [--org <slug>]
|
|
@@ -8335,7 +9327,6 @@ ${BOLD7}Usage:${RESET14}
|
|
|
8335
9327
|
decantr theme <subcommand>
|
|
8336
9328
|
decantr create <type> <name>
|
|
8337
9329
|
decantr publish <type> <name>
|
|
8338
|
-
decantr analyze
|
|
8339
9330
|
decantr login
|
|
8340
9331
|
decantr logout
|
|
8341
9332
|
decantr help
|
|
@@ -8363,14 +9354,22 @@ ${BOLD7}Init Options:${RESET14}
|
|
|
8363
9354
|
--telemetry Opt this project into privacy-filtered CLI product telemetry
|
|
8364
9355
|
|
|
8365
9356
|
${BOLD7}Commands:${RESET14}
|
|
9357
|
+
${cyan3("setup")} Detect project state and recommend the right Decantr workflow
|
|
8366
9358
|
${cyan3("new")} Create a new greenfield workspace and bootstrap the available starter adapter
|
|
9359
|
+
${cyan3("adopt")} Brownfield one-liner: analyze, attach, verify, and show next steps
|
|
9360
|
+
${cyan3("task")} Prepare route/task context, local law, evidence, and changed-file impact for an AI coding assistant
|
|
9361
|
+
${cyan3("verify")} One reliability gate over Project Health, Brownfield checks, baselines, and evidence
|
|
9362
|
+
${cyan3("codify")} Propose or accept project-owned Brownfield UI patterns and rules
|
|
9363
|
+
${cyan3("studio")} Open a local Project Health dashboard backed by the same report
|
|
9364
|
+
${cyan3("content")} Content-author namespace: check, create, publish
|
|
9365
|
+
|
|
9366
|
+
${BOLD7}Advanced commands:${RESET14}
|
|
8367
9367
|
${cyan3("magic")} Greenfield-first intent flow; steers existing apps into analyze + init
|
|
8368
9368
|
${cyan3("init")} Attach Decantr contract/context files to an existing project or empty workspace
|
|
8369
9369
|
${cyan3("status")} Show project status, DNA axioms, and blueprint info
|
|
8370
9370
|
${cyan3("health")} Generate a local Project Health report [--json] [--markdown] [--ci]; use health init-ci to install a GitHub Actions gate
|
|
8371
9371
|
${cyan3("workspace")} Discover and aggregate health across Decantr projects in a monorepo
|
|
8372
9372
|
${cyan3("content-health")} Generate a local registry content health report [--json] [--markdown] [--ci]
|
|
8373
|
-
${cyan3("studio")} Open a local Project Health dashboard backed by the same report
|
|
8374
9373
|
${cyan3("sync")} Sync registry content from API
|
|
8375
9374
|
${cyan3("audit")} Audit the project or critique a specific file against compiled packs
|
|
8376
9375
|
${cyan3("migrate")} Migrate older essence files to v4 format (with .pre-v4.backup.json backup)
|
|
@@ -8396,7 +9395,15 @@ ${BOLD7}Commands:${RESET14}
|
|
|
8396
9395
|
${cyan3("help")} Show this help
|
|
8397
9396
|
|
|
8398
9397
|
${BOLD7}Examples:${RESET14}
|
|
9398
|
+
decantr setup
|
|
8399
9399
|
decantr new my-app --blueprint=carbon-ai-portal
|
|
9400
|
+
decantr adopt --base-url http://localhost:3000 --evidence --yes
|
|
9401
|
+
decantr task /feed "add saved recipe actions"
|
|
9402
|
+
decantr verify --brownfield --local-patterns
|
|
9403
|
+
decantr verify --since-baseline
|
|
9404
|
+
decantr codify --from-audit
|
|
9405
|
+
decantr codify --accept
|
|
9406
|
+
decantr content check --ci --fail-on error
|
|
8400
9407
|
decantr magic "AI chatbot with dark cyber theme \u2014 bold and futuristic"
|
|
8401
9408
|
decantr init
|
|
8402
9409
|
decantr analyze
|
|
@@ -8409,13 +9416,13 @@ ${BOLD7}Examples:${RESET14}
|
|
|
8409
9416
|
decantr rules apply
|
|
8410
9417
|
decantr status
|
|
8411
9418
|
decantr health
|
|
8412
|
-
decantr
|
|
8413
|
-
decantr
|
|
8414
|
-
decantr
|
|
9419
|
+
decantr verify init-ci
|
|
9420
|
+
decantr verify init-ci --project apps/web
|
|
9421
|
+
decantr verify --ci --fail-on error
|
|
8415
9422
|
decantr health --evidence --output .decantr/evidence/latest.json
|
|
8416
9423
|
decantr workspace list
|
|
8417
|
-
decantr workspace
|
|
8418
|
-
decantr content
|
|
9424
|
+
decantr verify --workspace --changed --since origin/main
|
|
9425
|
+
decantr content check --ci --fail-on error
|
|
8419
9426
|
decantr studio
|
|
8420
9427
|
decantr studio --report decantr-health.json
|
|
8421
9428
|
decantr telemetry status
|
|
@@ -8444,7 +9451,9 @@ ${BOLD7}Examples:${RESET14}
|
|
|
8444
9451
|
${BOLD7}Workflow Model:${RESET14}
|
|
8445
9452
|
${cyan3("Greenfield blueprint")} decantr new my-app --blueprint=X --workflow=greenfield --adoption=decantr-css
|
|
8446
9453
|
${cyan3("Greenfield contract")} decantr init --workflow=greenfield --adoption=contract-only
|
|
8447
|
-
${cyan3("Brownfield adoption")} decantr
|
|
9454
|
+
${cyan3("Brownfield adoption")} decantr adopt --base-url <url> --evidence --yes
|
|
9455
|
+
${cyan3("Daily LLM work")} decantr task <route> "<change>" -> decantr verify --brownfield --local-patterns
|
|
9456
|
+
${cyan3("Project-owned law")} decantr codify --from-audit -> edit proposal -> decantr codify --accept
|
|
8448
9457
|
${cyan3("Hybrid composition")} decantr add/remove, decantr theme switch, decantr registry, decantr upgrade
|
|
8449
9458
|
|
|
8450
9459
|
${BOLD7}Bootstrap adapters:${RESET14}
|
|
@@ -8611,9 +9620,131 @@ ${BOLD7}Usage:${RESET14}
|
|
|
8611
9620
|
decantr theme import <path>
|
|
8612
9621
|
`);
|
|
8613
9622
|
}
|
|
9623
|
+
function cmdSetupHelp() {
|
|
9624
|
+
console.log(`
|
|
9625
|
+
${BOLD7}decantr setup${RESET14} \u2014 Detect the project state and recommend the right Decantr path
|
|
9626
|
+
|
|
9627
|
+
${BOLD7}Usage:${RESET14}
|
|
9628
|
+
decantr setup [--project <path>]
|
|
9629
|
+
|
|
9630
|
+
${BOLD7}Examples:${RESET14}
|
|
9631
|
+
decantr setup
|
|
9632
|
+
decantr setup --project apps/web
|
|
9633
|
+
`);
|
|
9634
|
+
}
|
|
9635
|
+
function cmdAdoptHelp() {
|
|
9636
|
+
console.log(`
|
|
9637
|
+
${BOLD7}decantr adopt${RESET14} \u2014 Brownfield one-liner: analyze, attach, verify, and show the operating loop
|
|
9638
|
+
|
|
9639
|
+
${BOLD7}Usage:${RESET14}
|
|
9640
|
+
decantr adopt [--project <path>] [--yes] [--dry-run]
|
|
9641
|
+
decantr adopt --base-url <url> [--evidence] [--ci] [--yes]
|
|
9642
|
+
|
|
9643
|
+
${BOLD7}Options:${RESET14}
|
|
9644
|
+
--project App path inside a workspace/monorepo
|
|
9645
|
+
--yes, -y Run without confirmation
|
|
9646
|
+
--dry-run Show the workflow without writing files
|
|
9647
|
+
--base-url Include browser evidence against this dev server URL
|
|
9648
|
+
--evidence Write .decantr/evidence/latest.json
|
|
9649
|
+
--baseline Save a health baseline (default)
|
|
9650
|
+
--no-baseline Skip baseline save
|
|
9651
|
+
--no-verify Skip the verification step
|
|
9652
|
+
--ci, --init-ci Install the Project Health CI gate after adoption
|
|
9653
|
+
--telemetry Opt this project into privacy-filtered CLI product telemetry
|
|
9654
|
+
--merge-proposal Merge the observed proposal into an existing essence
|
|
9655
|
+
--replace-essence Replace an existing essence with backup
|
|
9656
|
+
|
|
9657
|
+
${BOLD7}Examples:${RESET14}
|
|
9658
|
+
decantr adopt --yes
|
|
9659
|
+
decantr adopt --base-url http://localhost:3000 --evidence --yes
|
|
9660
|
+
decantr adopt --project apps/web --ci --yes
|
|
9661
|
+
decantr codify --from-audit
|
|
9662
|
+
`);
|
|
9663
|
+
}
|
|
9664
|
+
function cmdVerifyHelp() {
|
|
9665
|
+
console.log(`
|
|
9666
|
+
${BOLD7}decantr verify${RESET14} \u2014 One reliability command for local work, CI, and LLM agent loops
|
|
9667
|
+
|
|
9668
|
+
${BOLD7}Usage:${RESET14}
|
|
9669
|
+
decantr verify [--project <path>] [--brownfield] [--local-patterns]
|
|
9670
|
+
decantr verify --base-url <url> --evidence
|
|
9671
|
+
decantr verify --since-baseline
|
|
9672
|
+
decantr verify --workspace [--changed --since origin/main]
|
|
9673
|
+
decantr verify init-ci [health init-ci options]
|
|
9674
|
+
|
|
9675
|
+
${BOLD7}Examples:${RESET14}
|
|
9676
|
+
decantr verify
|
|
9677
|
+
decantr verify --brownfield --local-patterns
|
|
9678
|
+
decantr verify --brownfield --local-patterns --fail-on warn
|
|
9679
|
+
decantr verify --base-url http://localhost:3000 --evidence
|
|
9680
|
+
decantr verify --workspace --changed --since origin/main
|
|
9681
|
+
decantr verify init-ci --project apps/web
|
|
9682
|
+
`);
|
|
9683
|
+
}
|
|
9684
|
+
function cmdTaskHelp() {
|
|
9685
|
+
console.log(`
|
|
9686
|
+
${BOLD7}decantr task${RESET14} \u2014 Prepare compact route/task context for an AI coding assistant
|
|
9687
|
+
|
|
9688
|
+
${BOLD7}Usage:${RESET14}
|
|
9689
|
+
decantr task <route> ["task summary"] [--project <path>] [--since origin/main] [--json]
|
|
9690
|
+
|
|
9691
|
+
${BOLD7}Examples:${RESET14}
|
|
9692
|
+
decantr task /feed "add saved recipe actions"
|
|
9693
|
+
decantr task /feed "add saved recipe actions" --since origin/main
|
|
9694
|
+
decantr task /profile --json
|
|
9695
|
+
`);
|
|
9696
|
+
}
|
|
9697
|
+
function cmdCodifyHelp() {
|
|
9698
|
+
console.log(`
|
|
9699
|
+
${BOLD7}decantr codify${RESET14} \u2014 Propose or accept project-owned Brownfield UI patterns and rules
|
|
9700
|
+
|
|
9701
|
+
${BOLD7}Usage:${RESET14}
|
|
9702
|
+
decantr codify [--from-audit] [--project <path>]
|
|
9703
|
+
decantr codify --accept [--project <path>]
|
|
9704
|
+
|
|
9705
|
+
${BOLD7}Examples:${RESET14}
|
|
9706
|
+
decantr codify
|
|
9707
|
+
decantr codify --from-audit
|
|
9708
|
+
decantr codify --accept
|
|
9709
|
+
decantr verify --brownfield --local-patterns
|
|
9710
|
+
`);
|
|
9711
|
+
}
|
|
9712
|
+
function cmdContentHelp() {
|
|
9713
|
+
console.log(`
|
|
9714
|
+
${BOLD7}decantr content${RESET14} \u2014 Content-author namespace for registry content repositories
|
|
9715
|
+
|
|
9716
|
+
${BOLD7}Usage:${RESET14}
|
|
9717
|
+
decantr content check [content-health options]
|
|
9718
|
+
decantr content create <type> <name>
|
|
9719
|
+
decantr content publish <type> <name>
|
|
9720
|
+
|
|
9721
|
+
${BOLD7}Examples:${RESET14}
|
|
9722
|
+
decantr content check --ci --fail-on error
|
|
9723
|
+
decantr content create pattern my-card
|
|
9724
|
+
decantr content publish pattern my-card
|
|
9725
|
+
`);
|
|
9726
|
+
}
|
|
8614
9727
|
function printCommandHelp(command, args) {
|
|
8615
9728
|
if (!isCommandHelpRequest(args)) return false;
|
|
8616
9729
|
switch (command) {
|
|
9730
|
+
case "setup":
|
|
9731
|
+
cmdSetupHelp();
|
|
9732
|
+
return true;
|
|
9733
|
+
case "adopt":
|
|
9734
|
+
cmdAdoptHelp();
|
|
9735
|
+
return true;
|
|
9736
|
+
case "verify":
|
|
9737
|
+
cmdVerifyHelp();
|
|
9738
|
+
return true;
|
|
9739
|
+
case "task":
|
|
9740
|
+
cmdTaskHelp();
|
|
9741
|
+
return true;
|
|
9742
|
+
case "codify":
|
|
9743
|
+
cmdCodifyHelp();
|
|
9744
|
+
return true;
|
|
9745
|
+
case "content":
|
|
9746
|
+
cmdContentHelp();
|
|
9747
|
+
return true;
|
|
8617
9748
|
case "health":
|
|
8618
9749
|
cmdHealthHelp();
|
|
8619
9750
|
return true;
|
|
@@ -8650,10 +9781,10 @@ async function main() {
|
|
|
8650
9781
|
if (command === "--version" || command === "-v" || command === "version") {
|
|
8651
9782
|
try {
|
|
8652
9783
|
const here = dirname4(fileURLToPath2(import.meta.url));
|
|
8653
|
-
const candidates = [
|
|
9784
|
+
const candidates = [join29(here, "..", "package.json"), join29(here, "..", "..", "package.json")];
|
|
8654
9785
|
for (const candidate of candidates) {
|
|
8655
|
-
if (
|
|
8656
|
-
const pkg = JSON.parse(
|
|
9786
|
+
if (existsSync28(candidate)) {
|
|
9787
|
+
const pkg = JSON.parse(readFileSync21(candidate, "utf-8"));
|
|
8657
9788
|
if (pkg.version) {
|
|
8658
9789
|
console.log(pkg.version);
|
|
8659
9790
|
return;
|
|
@@ -8672,6 +9803,30 @@ async function main() {
|
|
|
8672
9803
|
return;
|
|
8673
9804
|
}
|
|
8674
9805
|
switch (command) {
|
|
9806
|
+
case "setup": {
|
|
9807
|
+
await cmdSetupWorkflow(args);
|
|
9808
|
+
break;
|
|
9809
|
+
}
|
|
9810
|
+
case "adopt": {
|
|
9811
|
+
await cmdAdoptWorkflow(args);
|
|
9812
|
+
break;
|
|
9813
|
+
}
|
|
9814
|
+
case "task": {
|
|
9815
|
+
await cmdTaskWorkflow(args);
|
|
9816
|
+
break;
|
|
9817
|
+
}
|
|
9818
|
+
case "verify": {
|
|
9819
|
+
await cmdVerifyWorkflow(args);
|
|
9820
|
+
break;
|
|
9821
|
+
}
|
|
9822
|
+
case "codify": {
|
|
9823
|
+
await cmdCodifyWorkflow(args);
|
|
9824
|
+
break;
|
|
9825
|
+
}
|
|
9826
|
+
case "content": {
|
|
9827
|
+
await cmdContentWorkflow(args);
|
|
9828
|
+
break;
|
|
9829
|
+
}
|
|
8675
9830
|
case "new": {
|
|
8676
9831
|
const newName = args[1];
|
|
8677
9832
|
if (!newName) {
|
|
@@ -9102,7 +10257,7 @@ async function main() {
|
|
|
9102
10257
|
break;
|
|
9103
10258
|
}
|
|
9104
10259
|
if (packType === "page" && route && !id) {
|
|
9105
|
-
const resolvedPath = essencePath ? resolveUserPath(essencePath) :
|
|
10260
|
+
const resolvedPath = essencePath ? resolveUserPath(essencePath) : join29(process.cwd(), "decantr.essence.json");
|
|
9106
10261
|
id = resolvePagePackIdForRoute(resolvedPath, route);
|
|
9107
10262
|
}
|
|
9108
10263
|
await printHostedSelectedExecutionPack(
|