@decantr/cli 2.7.0 → 2.8.1
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 +22 -11
- package/dist/bin.js +2 -1
- package/dist/{chunk-ICSLIYSX.js → chunk-FV6DGYD7.js} +49 -8
- package/dist/{chunk-ZYHR3BGT.js → chunk-RKZMHS2K.js} +836 -257
- package/dist/chunk-VE6N3XWG.js +78 -0
- package/dist/index.js +2 -1
- package/dist/{studio-MKLBUC3A.js → studio-G3YOU5YF.js} +2 -1
- package/dist/{workspace-KSFWRZEX.js → workspace-U7J3CJY3.js} +4 -1
- package/package.json +4 -4
|
@@ -15,6 +15,9 @@ import {
|
|
|
15
15
|
syncRegistry,
|
|
16
16
|
writeExecutionPackBundleArtifacts
|
|
17
17
|
} from "./chunk-V3XAQWKD.js";
|
|
18
|
+
import {
|
|
19
|
+
resolveWorkspaceInfo
|
|
20
|
+
} from "./chunk-VE6N3XWG.js";
|
|
18
21
|
import {
|
|
19
22
|
buildGuardRegistryContext,
|
|
20
23
|
createDoctrineMap,
|
|
@@ -34,10 +37,10 @@ import {
|
|
|
34
37
|
} from "./chunk-KT2ROK2D.js";
|
|
35
38
|
|
|
36
39
|
// src/index.ts
|
|
37
|
-
import { existsSync as existsSync27, mkdirSync as
|
|
38
|
-
import { basename as
|
|
40
|
+
import { existsSync as existsSync27, mkdirSync as mkdirSync14, readdirSync as readdirSync7, readFileSync as readFileSync20, writeFileSync as writeFileSync17 } from "fs";
|
|
41
|
+
import { basename as basename3, dirname as dirname3, isAbsolute, join as join28, resolve as resolve3 } from "path";
|
|
39
42
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
40
|
-
import { evaluateGuard, isV4 as
|
|
43
|
+
import { evaluateGuard, isV4 as isV48, validateEssence as validateEssence2 } from "@decantr/essence-spec";
|
|
41
44
|
import {
|
|
42
45
|
CONTENT_TYPE_TO_API_CONTENT_TYPE as CONTENT_TYPE_TO_API_CONTENT_TYPE3,
|
|
43
46
|
CONTENT_TYPES as GET_CONTENT_TYPES,
|
|
@@ -5271,6 +5274,488 @@ async function cmdThemeSwitch(themeName, args, projectRoot = process.cwd()) {
|
|
|
5271
5274
|
console.log(`${YELLOW7}Guard will flag code using old tokens. Run \`decantr check\`.${RESET12}`);
|
|
5272
5275
|
}
|
|
5273
5276
|
|
|
5277
|
+
// src/local-law.ts
|
|
5278
|
+
import { execFileSync } from "child_process";
|
|
5279
|
+
import { existsSync as existsSync25, mkdirSync as mkdirSync12, readdirSync as readdirSync5, readFileSync as readFileSync18, statSync as statSync5, writeFileSync as writeFileSync15 } from "fs";
|
|
5280
|
+
import { basename as basename2, extname, join as join26, relative as relative2, sep } from "path";
|
|
5281
|
+
import { isV4 as isV47 } from "@decantr/essence-spec";
|
|
5282
|
+
var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
5283
|
+
".astro",
|
|
5284
|
+
".html",
|
|
5285
|
+
".js",
|
|
5286
|
+
".jsx",
|
|
5287
|
+
".svelte",
|
|
5288
|
+
".ts",
|
|
5289
|
+
".tsx",
|
|
5290
|
+
".vue"
|
|
5291
|
+
]);
|
|
5292
|
+
var UI_TEMPLATE_EXTENSIONS = /* @__PURE__ */ new Set([".astro", ".html", ".jsx", ".svelte", ".tsx", ".vue"]);
|
|
5293
|
+
var IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
5294
|
+
".decantr",
|
|
5295
|
+
".git",
|
|
5296
|
+
".next",
|
|
5297
|
+
".nuxt",
|
|
5298
|
+
".svelte-kit",
|
|
5299
|
+
"build",
|
|
5300
|
+
"coverage",
|
|
5301
|
+
"dist",
|
|
5302
|
+
"node_modules",
|
|
5303
|
+
"out"
|
|
5304
|
+
]);
|
|
5305
|
+
var DEFAULT_RULE_EXTENSIONS = [".astro", ".html", ".jsx", ".svelte", ".tsx", ".vue"];
|
|
5306
|
+
function localPatternsProposalPath(projectRoot) {
|
|
5307
|
+
return join26(projectRoot, ".decantr", "local-patterns.proposal.json");
|
|
5308
|
+
}
|
|
5309
|
+
function localPatternsPath(projectRoot) {
|
|
5310
|
+
return join26(projectRoot, ".decantr", "local-patterns.json");
|
|
5311
|
+
}
|
|
5312
|
+
function localRulesProposalPath(projectRoot) {
|
|
5313
|
+
return join26(projectRoot, ".decantr", "rules.proposal.json");
|
|
5314
|
+
}
|
|
5315
|
+
function localRulesPath(projectRoot) {
|
|
5316
|
+
return join26(projectRoot, ".decantr", "rules.json");
|
|
5317
|
+
}
|
|
5318
|
+
function readLocalPatternPack(projectRoot) {
|
|
5319
|
+
return readJsonFile(localPatternsPath(projectRoot));
|
|
5320
|
+
}
|
|
5321
|
+
function readLocalRuleManifest(projectRoot) {
|
|
5322
|
+
return readJsonFile(localRulesPath(projectRoot));
|
|
5323
|
+
}
|
|
5324
|
+
function createBrownfieldCodifyProposal(input) {
|
|
5325
|
+
const sourceFiles = input.fromAudit ? listSourceFiles(input.projectRoot, 800) : [];
|
|
5326
|
+
const evidence = summarizeSourceEvidence(input.projectRoot, sourceFiles);
|
|
5327
|
+
const routes = input.essence && isV47(input.essence) ? Object.keys(input.essence.blueprint.routes ?? {}).sort() : [];
|
|
5328
|
+
const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
5329
|
+
const patternPack = {
|
|
5330
|
+
version: 2,
|
|
5331
|
+
generatedAt,
|
|
5332
|
+
status: "proposal",
|
|
5333
|
+
source: input.fromAudit ? "decantr codify --from-audit" : "decantr codify",
|
|
5334
|
+
project: {
|
|
5335
|
+
framework: input.detected.framework,
|
|
5336
|
+
packageManager: input.detected.packageManager,
|
|
5337
|
+
hasTailwind: input.detected.hasTailwind,
|
|
5338
|
+
ruleFiles: input.detected.existingRuleFiles,
|
|
5339
|
+
routeCount: routes.length
|
|
5340
|
+
},
|
|
5341
|
+
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.",
|
|
5342
|
+
patterns: [
|
|
5343
|
+
{
|
|
5344
|
+
id: "button",
|
|
5345
|
+
label: "Button primitives",
|
|
5346
|
+
role: "Actions and command triggers",
|
|
5347
|
+
appliesTo: [
|
|
5348
|
+
"primary action",
|
|
5349
|
+
"secondary action",
|
|
5350
|
+
"tertiary action",
|
|
5351
|
+
"destructive action",
|
|
5352
|
+
"icon action"
|
|
5353
|
+
],
|
|
5354
|
+
componentPaths: evidence.buttonComponents,
|
|
5355
|
+
decide: "Define primary, secondary, tertiary, destructive, icon-only, disabled, and loading button variants from this app.",
|
|
5356
|
+
evidence: evidence.buttonComponents.length ? evidence.buttonComponents : [
|
|
5357
|
+
"No obvious Button wrapper found yet. Add the project-owned wrapper path before strict enforcement."
|
|
5358
|
+
],
|
|
5359
|
+
forbiddenAlternatives: ["New one-off button variants without updating this manifest."]
|
|
5360
|
+
},
|
|
5361
|
+
{
|
|
5362
|
+
id: "surface-card",
|
|
5363
|
+
label: "Cards and surfaces",
|
|
5364
|
+
role: "Cards, panels, and reusable content surfaces",
|
|
5365
|
+
appliesTo: ["cards", "panels", "modals", "list items", "dashboard tiles"],
|
|
5366
|
+
componentPaths: evidence.cardComponents,
|
|
5367
|
+
decide: "Define the canonical card background, border, radius, shadow, padding, density, and hover treatment.",
|
|
5368
|
+
classHints: evidence.cardClassHints,
|
|
5369
|
+
evidence: evidence.cardComponents.length ? evidence.cardComponents : [
|
|
5370
|
+
"No obvious Card wrapper found yet. Add the project-owned wrapper path or class recipe."
|
|
5371
|
+
],
|
|
5372
|
+
forbiddenAlternatives: ["Flat ad hoc cards with unique color/radius/shadow recipes."]
|
|
5373
|
+
},
|
|
5374
|
+
{
|
|
5375
|
+
id: "page-shell",
|
|
5376
|
+
label: "Page shell and spacing",
|
|
5377
|
+
role: "Route shell, navigation, gutters, max-width, and scroll ownership",
|
|
5378
|
+
appliesTo: ["routes", "layouts", "navigation shells", "scroll containers"],
|
|
5379
|
+
componentPaths: evidence.shellComponents,
|
|
5380
|
+
decide: "Define which layout owns max width, gutters, sticky chrome, responsive breakpoints, and scroll containers.",
|
|
5381
|
+
evidence: evidence.shellComponents.length ? evidence.shellComponents : ["Add root layout, shell, or app frame files that establish route chrome and spacing."],
|
|
5382
|
+
forbiddenAlternatives: [
|
|
5383
|
+
"Each page inventing independent max-width, padding, or sticky nav rules."
|
|
5384
|
+
]
|
|
5385
|
+
},
|
|
5386
|
+
{
|
|
5387
|
+
id: "form-control",
|
|
5388
|
+
label: "Form controls",
|
|
5389
|
+
role: "Inputs, labels, validation, and form actions",
|
|
5390
|
+
appliesTo: ["inputs", "selects", "textareas", "validation messages", "form actions"],
|
|
5391
|
+
componentPaths: evidence.formComponents,
|
|
5392
|
+
decide: "Define input height, label placement, error copy, disabled state, required state, and focus treatment.",
|
|
5393
|
+
evidence: evidence.formComponents.length ? evidence.formComponents : ["Add form field wrapper paths and validation examples."],
|
|
5394
|
+
forbiddenAlternatives: [
|
|
5395
|
+
"Unlabeled one-off inputs or validation states that do not match the app standard."
|
|
5396
|
+
]
|
|
5397
|
+
},
|
|
5398
|
+
{
|
|
5399
|
+
id: "theme-variant",
|
|
5400
|
+
label: "Theme variants",
|
|
5401
|
+
role: "Light, dark, brand, density, and tenant/theme variants observed in the app",
|
|
5402
|
+
appliesTo: ["theme toggles", "mode-specific classes", "brand variants", "tenant variants"],
|
|
5403
|
+
componentPaths: evidence.themeComponents,
|
|
5404
|
+
decide: "Document which theme variants exist, where they are toggled, and which tokens/classes are legal per variant.",
|
|
5405
|
+
evidence: evidence.themeComponents.length ? evidence.themeComponents : ["If the app has dark/light or brand variants, add the toggles/providers here."],
|
|
5406
|
+
forbiddenAlternatives: [
|
|
5407
|
+
"Component-local theme forks that bypass shared theme providers or tokens."
|
|
5408
|
+
]
|
|
5409
|
+
}
|
|
5410
|
+
],
|
|
5411
|
+
starterRules: [
|
|
5412
|
+
"Prefer project-owned wrappers for repeated primitives once they exist.",
|
|
5413
|
+
"Avoid raw hex/rgb values in component templates unless documented as dynamic data.",
|
|
5414
|
+
"Avoid static inline styles for reusable visual treatment.",
|
|
5415
|
+
"When adding a new route, map it to an existing local pattern before inventing a new visual variant.",
|
|
5416
|
+
"When adding a theme variant, update .decantr/theme-inventory.json and this local pattern pack."
|
|
5417
|
+
],
|
|
5418
|
+
nextSteps: [
|
|
5419
|
+
"Edit this proposal with real component paths and token/class recipes.",
|
|
5420
|
+
"Run decantr codify --accept after review.",
|
|
5421
|
+
"Use decantr task <route> before LLM edits so local law appears in task context.",
|
|
5422
|
+
"Run decantr verify --brownfield --local-patterns after edits.",
|
|
5423
|
+
"Wire deterministic project rules into ESLint, Biome, Storybook, visual tests, or CI where Decantr should not guess."
|
|
5424
|
+
]
|
|
5425
|
+
};
|
|
5426
|
+
const ruleManifest = {
|
|
5427
|
+
version: 1,
|
|
5428
|
+
status: "proposal",
|
|
5429
|
+
generatedAt,
|
|
5430
|
+
source: input.fromAudit ? "decantr codify --from-audit" : "decantr codify",
|
|
5431
|
+
purpose: "Mechanical Brownfield checks owned by this project. These rules are intentionally local and stack-agnostic; edit before accepting.",
|
|
5432
|
+
enforcement: {
|
|
5433
|
+
defaultSeverity: "warn",
|
|
5434
|
+
mode: "warn",
|
|
5435
|
+
notes: [
|
|
5436
|
+
"Decantr local rules are a guardrail, not a replacement for ESLint, Biome, type checks, tests, or visual regression.",
|
|
5437
|
+
"Keep rules narrow enough that an LLM can fix findings without rewriting the app.",
|
|
5438
|
+
"Use error severity only after the team agrees the rule is stable."
|
|
5439
|
+
]
|
|
5440
|
+
},
|
|
5441
|
+
rules: [
|
|
5442
|
+
{
|
|
5443
|
+
id: "no-inline-style",
|
|
5444
|
+
type: "forbid-regex",
|
|
5445
|
+
enabled: true,
|
|
5446
|
+
severity: "warn",
|
|
5447
|
+
description: "Reusable UI should not add static inline style attributes.",
|
|
5448
|
+
includeExtensions: DEFAULT_RULE_EXTENSIONS,
|
|
5449
|
+
pattern: "\\bstyle\\s*=",
|
|
5450
|
+
message: "Inline style found in UI template.",
|
|
5451
|
+
suggestedFix: "Move reusable visual treatment into the project style system, component wrapper, token, or documented local pattern.",
|
|
5452
|
+
maxFindings: 25
|
|
5453
|
+
},
|
|
5454
|
+
{
|
|
5455
|
+
id: "no-raw-color-literals",
|
|
5456
|
+
type: "forbid-regex",
|
|
5457
|
+
enabled: true,
|
|
5458
|
+
severity: "warn",
|
|
5459
|
+
description: "Component templates should not introduce raw hex/rgb color literals.",
|
|
5460
|
+
includeExtensions: DEFAULT_RULE_EXTENSIONS,
|
|
5461
|
+
pattern: "#(?:[0-9a-fA-F]{3,8})\\b|rgba?\\s*\\(",
|
|
5462
|
+
message: "Raw color literal found in UI template.",
|
|
5463
|
+
suggestedFix: "Use an existing project token/class, or document the exception in .decantr/local-patterns.json if the value is data-driven.",
|
|
5464
|
+
maxFindings: 25
|
|
5465
|
+
},
|
|
5466
|
+
{
|
|
5467
|
+
id: "prefer-button-wrapper",
|
|
5468
|
+
type: "forbid-regex",
|
|
5469
|
+
enabled: evidence.buttonComponents.length > 0,
|
|
5470
|
+
severity: "info",
|
|
5471
|
+
description: "Prefer the project-owned button primitive instead of new raw button markup.",
|
|
5472
|
+
includeExtensions: DEFAULT_RULE_EXTENSIONS,
|
|
5473
|
+
pattern: "<button[\\s>]",
|
|
5474
|
+
message: "Raw <button> usage found outside the detected button wrapper.",
|
|
5475
|
+
suggestedFix: "Use the project-owned Button primitive, or add this file to allowedPaths if it is the primitive implementation.",
|
|
5476
|
+
allowedPaths: evidence.buttonComponents,
|
|
5477
|
+
maxFindings: 50
|
|
5478
|
+
}
|
|
5479
|
+
]
|
|
5480
|
+
};
|
|
5481
|
+
return { patternPack, ruleManifest };
|
|
5482
|
+
}
|
|
5483
|
+
function writeBrownfieldCodifyProposal(projectRoot, proposal) {
|
|
5484
|
+
const decantrDir = join26(projectRoot, ".decantr");
|
|
5485
|
+
mkdirSync12(decantrDir, { recursive: true });
|
|
5486
|
+
const patternPath = localPatternsProposalPath(projectRoot);
|
|
5487
|
+
const rulesPath = localRulesProposalPath(projectRoot);
|
|
5488
|
+
writeFileSync15(patternPath, `${JSON.stringify(proposal.patternPack, null, 2)}
|
|
5489
|
+
`, "utf-8");
|
|
5490
|
+
writeFileSync15(rulesPath, `${JSON.stringify(proposal.ruleManifest, null, 2)}
|
|
5491
|
+
`, "utf-8");
|
|
5492
|
+
return { patternPath, rulesPath };
|
|
5493
|
+
}
|
|
5494
|
+
function acceptBrownfieldLocalLaw(projectRoot) {
|
|
5495
|
+
const patternProposal = readJsonFile(localPatternsProposalPath(projectRoot));
|
|
5496
|
+
const ruleProposal = readJsonFile(localRulesProposalPath(projectRoot));
|
|
5497
|
+
const acceptedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
5498
|
+
let patternAcceptedPath = null;
|
|
5499
|
+
let rulesAcceptedPath = null;
|
|
5500
|
+
if (patternProposal) {
|
|
5501
|
+
patternProposal.status = "accepted";
|
|
5502
|
+
patternProposal.acceptedAt = acceptedAt;
|
|
5503
|
+
patternAcceptedPath = localPatternsPath(projectRoot);
|
|
5504
|
+
writeFileSync15(patternAcceptedPath, `${JSON.stringify(patternProposal, null, 2)}
|
|
5505
|
+
`, "utf-8");
|
|
5506
|
+
}
|
|
5507
|
+
if (ruleProposal) {
|
|
5508
|
+
ruleProposal.status = "accepted";
|
|
5509
|
+
ruleProposal.acceptedAt = acceptedAt;
|
|
5510
|
+
rulesAcceptedPath = localRulesPath(projectRoot);
|
|
5511
|
+
writeFileSync15(rulesAcceptedPath, `${JSON.stringify(ruleProposal, null, 2)}
|
|
5512
|
+
`, "utf-8");
|
|
5513
|
+
}
|
|
5514
|
+
return { patternAcceptedPath, rulesAcceptedPath };
|
|
5515
|
+
}
|
|
5516
|
+
function validateLocalLaw(projectRoot) {
|
|
5517
|
+
const patternsPath = localPatternsPath(projectRoot);
|
|
5518
|
+
const rulesPath = localRulesPath(projectRoot);
|
|
5519
|
+
const patternPack = readJsonFile(patternsPath);
|
|
5520
|
+
const ruleManifest = readJsonFile(rulesPath);
|
|
5521
|
+
const warnings = [];
|
|
5522
|
+
if (patternPack) {
|
|
5523
|
+
const patternIds = /* @__PURE__ */ new Set();
|
|
5524
|
+
const patterns = Array.isArray(patternPack.patterns) ? patternPack.patterns : [];
|
|
5525
|
+
if (patterns.length === 0) {
|
|
5526
|
+
warnings.push(".decantr/local-patterns.json has no patterns.");
|
|
5527
|
+
}
|
|
5528
|
+
for (const pattern of patterns) {
|
|
5529
|
+
const id = typeof pattern.id === "string" ? pattern.id.trim() : "";
|
|
5530
|
+
if (!id) warnings.push("A local pattern is missing an id.");
|
|
5531
|
+
if (id && patternIds.has(id)) warnings.push(`Duplicate local pattern id: ${id}`);
|
|
5532
|
+
if (id) patternIds.add(id);
|
|
5533
|
+
const paths = Array.isArray(pattern.componentPaths) ? pattern.componentPaths : [];
|
|
5534
|
+
const evidence = Array.isArray(pattern.evidence) ? pattern.evidence : [];
|
|
5535
|
+
const todoEvidence = Array.isArray(pattern.evidenceToCollect) ? pattern.evidenceToCollect : [];
|
|
5536
|
+
if (id && paths.length === 0 && evidence.length === 0 && todoEvidence.length > 0) {
|
|
5537
|
+
warnings.push(
|
|
5538
|
+
`Local pattern ${id} still reads like a TODO; add concrete component paths or evidence.`
|
|
5539
|
+
);
|
|
5540
|
+
}
|
|
5541
|
+
}
|
|
5542
|
+
}
|
|
5543
|
+
if (ruleManifest && !Array.isArray(ruleManifest.rules)) {
|
|
5544
|
+
warnings.push(".decantr/rules.json has no rules array.");
|
|
5545
|
+
}
|
|
5546
|
+
const findings = ruleManifest ? scanLocalRules(projectRoot, ruleManifest) : [];
|
|
5547
|
+
return {
|
|
5548
|
+
patternsPath,
|
|
5549
|
+
rulesPath,
|
|
5550
|
+
patternPackPresent: Boolean(patternPack),
|
|
5551
|
+
ruleManifestPresent: Boolean(ruleManifest),
|
|
5552
|
+
warnings,
|
|
5553
|
+
findings
|
|
5554
|
+
};
|
|
5555
|
+
}
|
|
5556
|
+
function createLocalLawTaskSummary(projectRoot) {
|
|
5557
|
+
const patternPack = readLocalPatternPack(projectRoot);
|
|
5558
|
+
const ruleManifest = readLocalRuleManifest(projectRoot);
|
|
5559
|
+
const patterns = (patternPack?.patterns ?? []).map((pattern) => ({
|
|
5560
|
+
id: typeof pattern.id === "string" ? pattern.id : "unknown",
|
|
5561
|
+
role: typeof pattern.role === "string" ? pattern.role : null,
|
|
5562
|
+
componentPaths: Array.isArray(pattern.componentPaths) ? pattern.componentPaths.filter((entry) => typeof entry === "string") : []
|
|
5563
|
+
}));
|
|
5564
|
+
const rules = (ruleManifest?.rules ?? []).map((rule) => ({
|
|
5565
|
+
id: rule.id,
|
|
5566
|
+
severity: rule.severity,
|
|
5567
|
+
enabled: rule.enabled,
|
|
5568
|
+
description: rule.description
|
|
5569
|
+
}));
|
|
5570
|
+
return {
|
|
5571
|
+
patternsPath: patternPack ? ".decantr/local-patterns.json" : null,
|
|
5572
|
+
rulesPath: ruleManifest ? ".decantr/rules.json" : null,
|
|
5573
|
+
patternCount: patterns.length,
|
|
5574
|
+
ruleCount: rules.length,
|
|
5575
|
+
patterns,
|
|
5576
|
+
rules
|
|
5577
|
+
};
|
|
5578
|
+
}
|
|
5579
|
+
function changedFiles(projectRoot, since) {
|
|
5580
|
+
const changed = /* @__PURE__ */ new Set();
|
|
5581
|
+
try {
|
|
5582
|
+
const commands = since ? [
|
|
5583
|
+
["diff", "--name-only", since, "--"],
|
|
5584
|
+
["diff", "--name-only", "--cached"]
|
|
5585
|
+
] : [
|
|
5586
|
+
["diff", "--name-only"],
|
|
5587
|
+
["diff", "--name-only", "--cached"]
|
|
5588
|
+
];
|
|
5589
|
+
for (const args of commands) {
|
|
5590
|
+
const output = execFileSync("git", args, {
|
|
5591
|
+
cwd: projectRoot,
|
|
5592
|
+
encoding: "utf-8",
|
|
5593
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
5594
|
+
});
|
|
5595
|
+
for (const line of output.split(/\r?\n/)) {
|
|
5596
|
+
const file = line.trim();
|
|
5597
|
+
if (file) changed.add(normalizePath(file));
|
|
5598
|
+
}
|
|
5599
|
+
}
|
|
5600
|
+
} catch {
|
|
5601
|
+
}
|
|
5602
|
+
return [...changed].sort();
|
|
5603
|
+
}
|
|
5604
|
+
function routeImpacts(projectRoot, files) {
|
|
5605
|
+
const analysis = readJsonFile(
|
|
5606
|
+
join26(projectRoot, ".decantr", "analysis.json")
|
|
5607
|
+
);
|
|
5608
|
+
const routeEntries = analysis?.routes?.routes ?? [];
|
|
5609
|
+
const impacted = /* @__PURE__ */ new Set();
|
|
5610
|
+
for (const file of files) {
|
|
5611
|
+
for (const route of routeEntries) {
|
|
5612
|
+
if (route.file && pathMatches(file, route.file)) {
|
|
5613
|
+
if (route.path) impacted.add(route.path);
|
|
5614
|
+
}
|
|
5615
|
+
}
|
|
5616
|
+
}
|
|
5617
|
+
return [...impacted].sort();
|
|
5618
|
+
}
|
|
5619
|
+
function scanLocalRules(projectRoot, manifest) {
|
|
5620
|
+
const findings = [];
|
|
5621
|
+
const files = listSourceFiles(projectRoot, 1200);
|
|
5622
|
+
for (const rule of manifest.rules ?? []) {
|
|
5623
|
+
if (!rule.enabled || rule.type !== "forbid-regex") continue;
|
|
5624
|
+
const extensions = new Set(
|
|
5625
|
+
rule.includeExtensions?.length ? rule.includeExtensions : DEFAULT_RULE_EXTENSIONS
|
|
5626
|
+
);
|
|
5627
|
+
let regex;
|
|
5628
|
+
try {
|
|
5629
|
+
regex = new RegExp(rule.pattern, "g");
|
|
5630
|
+
} catch {
|
|
5631
|
+
findings.push({
|
|
5632
|
+
ruleId: rule.id,
|
|
5633
|
+
severity: "error",
|
|
5634
|
+
file: ".decantr/rules.json",
|
|
5635
|
+
line: 1,
|
|
5636
|
+
column: 1,
|
|
5637
|
+
excerpt: rule.pattern,
|
|
5638
|
+
message: `Invalid regex for local rule ${rule.id}.`,
|
|
5639
|
+
suggestedFix: "Edit .decantr/rules.json so the pattern is a valid JavaScript regular expression."
|
|
5640
|
+
});
|
|
5641
|
+
continue;
|
|
5642
|
+
}
|
|
5643
|
+
let ruleFindingCount = 0;
|
|
5644
|
+
for (const file of files) {
|
|
5645
|
+
if (!extensions.has(extname(file.absolute))) continue;
|
|
5646
|
+
if (pathAllowed(file.relative, rule.allowedPaths ?? [])) continue;
|
|
5647
|
+
const contents = readFileSync18(file.absolute, "utf-8");
|
|
5648
|
+
for (const match of contents.matchAll(regex)) {
|
|
5649
|
+
const index = match.index ?? 0;
|
|
5650
|
+
const position = lineColumnAt(contents, index);
|
|
5651
|
+
findings.push({
|
|
5652
|
+
ruleId: rule.id,
|
|
5653
|
+
severity: rule.severity,
|
|
5654
|
+
file: file.relative,
|
|
5655
|
+
line: position.line,
|
|
5656
|
+
column: position.column,
|
|
5657
|
+
excerpt: lineAt(contents, position.line).trim().slice(0, 180),
|
|
5658
|
+
message: rule.message,
|
|
5659
|
+
suggestedFix: rule.suggestedFix
|
|
5660
|
+
});
|
|
5661
|
+
ruleFindingCount += 1;
|
|
5662
|
+
if (rule.maxFindings && ruleFindingCount >= rule.maxFindings) break;
|
|
5663
|
+
}
|
|
5664
|
+
if (rule.maxFindings && ruleFindingCount >= rule.maxFindings) break;
|
|
5665
|
+
}
|
|
5666
|
+
}
|
|
5667
|
+
return findings;
|
|
5668
|
+
}
|
|
5669
|
+
function summarizeSourceEvidence(projectRoot, files) {
|
|
5670
|
+
const componentPaths = files.filter((file) => /(^|[/\\])components?([/\\]|$)|(^|[/\\])ui([/\\]|$)/i.test(file.relative)).map((file) => file.relative);
|
|
5671
|
+
const byName = (terms) => componentPaths.filter((file) => terms.some((term) => basename2(file).toLowerCase().includes(term))).slice(0, 12);
|
|
5672
|
+
const themeComponents = componentPaths.filter((file) => /theme|provider|mode|appearance|tenant|brand/i.test(file)).slice(0, 12);
|
|
5673
|
+
const shellComponents = files.filter((file) => /layout|shell|frame|app|root|nav|sidebar/i.test(basename2(file.relative))).map((file) => file.relative).slice(0, 12);
|
|
5674
|
+
return {
|
|
5675
|
+
buttonComponents: byName(["button", "action"]),
|
|
5676
|
+
cardComponents: byName(["card", "panel", "surface", "tile"]),
|
|
5677
|
+
formComponents: byName(["input", "field", "form", "select", "textarea"]),
|
|
5678
|
+
shellComponents,
|
|
5679
|
+
themeComponents,
|
|
5680
|
+
cardClassHints: collectClassHints(projectRoot, files, ["card", "panel", "surface", "tile"])
|
|
5681
|
+
};
|
|
5682
|
+
}
|
|
5683
|
+
function collectClassHints(projectRoot, files, terms) {
|
|
5684
|
+
const hints = /* @__PURE__ */ new Map();
|
|
5685
|
+
for (const file of files) {
|
|
5686
|
+
if (!UI_TEMPLATE_EXTENSIONS.has(extname(file.absolute))) continue;
|
|
5687
|
+
const content = readFileSync18(join26(projectRoot, file.relative), "utf-8");
|
|
5688
|
+
if (!terms.some((term) => content.toLowerCase().includes(term))) continue;
|
|
5689
|
+
const matches = content.matchAll(/\bclass(?:Name)?\s*=\s*["'`]([^"'`]+)["'`]/g);
|
|
5690
|
+
for (const match of matches) {
|
|
5691
|
+
const value = match[1].trim();
|
|
5692
|
+
if (!/(card|panel|surface|rounded|shadow|border|bg-|p-\d|px-|py-)/i.test(value)) continue;
|
|
5693
|
+
hints.set(value, (hints.get(value) ?? 0) + 1);
|
|
5694
|
+
}
|
|
5695
|
+
}
|
|
5696
|
+
return [...hints.entries()].sort((a, b) => b[1] - a[1]).slice(0, 8).map(([hint]) => hint);
|
|
5697
|
+
}
|
|
5698
|
+
function listSourceFiles(projectRoot, maxFiles) {
|
|
5699
|
+
const files = [];
|
|
5700
|
+
const visit = (dir) => {
|
|
5701
|
+
if (files.length >= maxFiles) return;
|
|
5702
|
+
let entries;
|
|
5703
|
+
try {
|
|
5704
|
+
entries = readdirSync5(dir);
|
|
5705
|
+
} catch {
|
|
5706
|
+
return;
|
|
5707
|
+
}
|
|
5708
|
+
for (const entry of entries) {
|
|
5709
|
+
if (files.length >= maxFiles) return;
|
|
5710
|
+
if (IGNORED_DIRS.has(entry)) continue;
|
|
5711
|
+
const absolute = join26(dir, entry);
|
|
5712
|
+
let stat;
|
|
5713
|
+
try {
|
|
5714
|
+
stat = statSync5(absolute);
|
|
5715
|
+
} catch {
|
|
5716
|
+
continue;
|
|
5717
|
+
}
|
|
5718
|
+
if (stat.isDirectory()) {
|
|
5719
|
+
visit(absolute);
|
|
5720
|
+
} else if (stat.isFile() && SOURCE_EXTENSIONS.has(extname(entry))) {
|
|
5721
|
+
files.push({ absolute, relative: normalizePath(relative2(projectRoot, absolute)) });
|
|
5722
|
+
}
|
|
5723
|
+
}
|
|
5724
|
+
};
|
|
5725
|
+
visit(projectRoot);
|
|
5726
|
+
return files.sort((a, b) => a.relative.localeCompare(b.relative));
|
|
5727
|
+
}
|
|
5728
|
+
function readJsonFile(path) {
|
|
5729
|
+
if (!existsSync25(path)) return null;
|
|
5730
|
+
try {
|
|
5731
|
+
return JSON.parse(readFileSync18(path, "utf-8"));
|
|
5732
|
+
} catch {
|
|
5733
|
+
return null;
|
|
5734
|
+
}
|
|
5735
|
+
}
|
|
5736
|
+
function pathAllowed(file, allowedPaths) {
|
|
5737
|
+
return allowedPaths.some((allowedPath) => pathMatches(file, allowedPath));
|
|
5738
|
+
}
|
|
5739
|
+
function pathMatches(file, pattern) {
|
|
5740
|
+
const normalizedFile = normalizePath(file);
|
|
5741
|
+
const normalizedPattern = normalizePath(pattern);
|
|
5742
|
+
return normalizedFile === normalizedPattern || normalizedFile.endsWith(`/${normalizedPattern}`);
|
|
5743
|
+
}
|
|
5744
|
+
function normalizePath(path) {
|
|
5745
|
+
return path.split(sep).join("/").replace(/\\/g, "/");
|
|
5746
|
+
}
|
|
5747
|
+
function lineColumnAt(contents, index) {
|
|
5748
|
+
const before = contents.slice(0, index);
|
|
5749
|
+
const lines = before.split(/\r?\n/);
|
|
5750
|
+
return {
|
|
5751
|
+
line: lines.length,
|
|
5752
|
+
column: lines[lines.length - 1].length + 1
|
|
5753
|
+
};
|
|
5754
|
+
}
|
|
5755
|
+
function lineAt(contents, line) {
|
|
5756
|
+
return contents.split(/\r?\n/)[line - 1] ?? "";
|
|
5757
|
+
}
|
|
5758
|
+
|
|
5274
5759
|
// src/prompts.ts
|
|
5275
5760
|
import { createInterface } from "readline";
|
|
5276
5761
|
var BOLD6 = "\x1B[1m";
|
|
@@ -5282,10 +5767,10 @@ var CYAN7 = "\x1B[36m";
|
|
|
5282
5767
|
function ask(question, defaultValue) {
|
|
5283
5768
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
5284
5769
|
const prompt = defaultValue ? `${question} ${DIM13}(${defaultValue})${RESET13}: ` : `${question}: `;
|
|
5285
|
-
return new Promise((
|
|
5770
|
+
return new Promise((resolve4) => {
|
|
5286
5771
|
rl.question(prompt, (answer) => {
|
|
5287
5772
|
rl.close();
|
|
5288
|
-
|
|
5773
|
+
resolve4(answer.trim() || defaultValue || "");
|
|
5289
5774
|
});
|
|
5290
5775
|
});
|
|
5291
5776
|
}
|
|
@@ -5536,7 +6021,7 @@ function mergeWithDefaults(flags, detected, workflowSeed) {
|
|
|
5536
6021
|
}
|
|
5537
6022
|
async function runSimplifiedInit(blueprints) {
|
|
5538
6023
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
5539
|
-
const question = (q) => new Promise((
|
|
6024
|
+
const question = (q) => new Promise((resolve4) => rl.question(q, resolve4));
|
|
5540
6025
|
console.log("\n? What blueprint would you like to scaffold?\n");
|
|
5541
6026
|
console.log(" 1. Decantr default (recommended)");
|
|
5542
6027
|
console.log(" 2. Search registry...\n");
|
|
@@ -5568,8 +6053,8 @@ async function runSimplifiedInit(blueprints) {
|
|
|
5568
6053
|
}
|
|
5569
6054
|
|
|
5570
6055
|
// src/theme-commands.ts
|
|
5571
|
-
import { existsSync as
|
|
5572
|
-
import { join as
|
|
6056
|
+
import { existsSync as existsSync26, mkdirSync as mkdirSync13, readdirSync as readdirSync6, readFileSync as readFileSync19, rmSync as rmSync3, writeFileSync as writeFileSync16 } from "fs";
|
|
6057
|
+
import { join as join27 } from "path";
|
|
5573
6058
|
var REQUIRED_FIELDS = [
|
|
5574
6059
|
"$schema",
|
|
5575
6060
|
"id",
|
|
@@ -5629,20 +6114,20 @@ function validateCustomTheme(theme) {
|
|
|
5629
6114
|
};
|
|
5630
6115
|
}
|
|
5631
6116
|
function createTheme(projectRoot, id, name) {
|
|
5632
|
-
const customThemesDir =
|
|
5633
|
-
const themePath =
|
|
5634
|
-
const howToPath =
|
|
5635
|
-
|
|
5636
|
-
if (
|
|
6117
|
+
const customThemesDir = join27(projectRoot, ".decantr", "custom", "themes");
|
|
6118
|
+
const themePath = join27(customThemesDir, `${id}.json`);
|
|
6119
|
+
const howToPath = join27(customThemesDir, "how-to-theme.md");
|
|
6120
|
+
mkdirSync13(customThemesDir, { recursive: true });
|
|
6121
|
+
if (existsSync26(themePath)) {
|
|
5637
6122
|
return {
|
|
5638
6123
|
success: false,
|
|
5639
6124
|
error: `Theme "${id}" already exists at ${themePath}`
|
|
5640
6125
|
};
|
|
5641
6126
|
}
|
|
5642
6127
|
const skeleton = getThemeSkeleton(id, name);
|
|
5643
|
-
|
|
5644
|
-
if (!
|
|
5645
|
-
|
|
6128
|
+
writeFileSync16(themePath, JSON.stringify(skeleton, null, 2));
|
|
6129
|
+
if (!existsSync26(howToPath)) {
|
|
6130
|
+
writeFileSync16(howToPath, getHowToThemeDoc());
|
|
5646
6131
|
}
|
|
5647
6132
|
return {
|
|
5648
6133
|
success: true,
|
|
@@ -5650,17 +6135,17 @@ function createTheme(projectRoot, id, name) {
|
|
|
5650
6135
|
};
|
|
5651
6136
|
}
|
|
5652
6137
|
function listCustomThemes(projectRoot) {
|
|
5653
|
-
const customThemesDir =
|
|
5654
|
-
if (!
|
|
6138
|
+
const customThemesDir = join27(projectRoot, ".decantr", "custom", "themes");
|
|
6139
|
+
if (!existsSync26(customThemesDir)) {
|
|
5655
6140
|
return [];
|
|
5656
6141
|
}
|
|
5657
6142
|
const themes = [];
|
|
5658
6143
|
try {
|
|
5659
|
-
const files =
|
|
6144
|
+
const files = readdirSync6(customThemesDir).filter((f) => f.endsWith(".json"));
|
|
5660
6145
|
for (const file of files) {
|
|
5661
|
-
const filePath =
|
|
6146
|
+
const filePath = join27(customThemesDir, file);
|
|
5662
6147
|
try {
|
|
5663
|
-
const data = JSON.parse(
|
|
6148
|
+
const data = JSON.parse(readFileSync19(filePath, "utf-8"));
|
|
5664
6149
|
themes.push({
|
|
5665
6150
|
id: data.id || file.replace(".json", ""),
|
|
5666
6151
|
name: data.name || data.id,
|
|
@@ -5675,8 +6160,8 @@ function listCustomThemes(projectRoot) {
|
|
|
5675
6160
|
return themes;
|
|
5676
6161
|
}
|
|
5677
6162
|
function deleteTheme(projectRoot, id) {
|
|
5678
|
-
const themePath =
|
|
5679
|
-
if (!
|
|
6163
|
+
const themePath = join27(projectRoot, ".decantr", "custom", "themes", `${id}.json`);
|
|
6164
|
+
if (!existsSync26(themePath)) {
|
|
5680
6165
|
return {
|
|
5681
6166
|
success: false,
|
|
5682
6167
|
error: `Theme "${id}" not found at ${themePath}`
|
|
@@ -5693,7 +6178,7 @@ function deleteTheme(projectRoot, id) {
|
|
|
5693
6178
|
}
|
|
5694
6179
|
}
|
|
5695
6180
|
function importTheme(projectRoot, sourcePath) {
|
|
5696
|
-
if (!
|
|
6181
|
+
if (!existsSync26(sourcePath)) {
|
|
5697
6182
|
return {
|
|
5698
6183
|
success: false,
|
|
5699
6184
|
errors: [`Source file not found: ${sourcePath}`]
|
|
@@ -5701,7 +6186,7 @@ function importTheme(projectRoot, sourcePath) {
|
|
|
5701
6186
|
}
|
|
5702
6187
|
let theme;
|
|
5703
6188
|
try {
|
|
5704
|
-
theme = JSON.parse(
|
|
6189
|
+
theme = JSON.parse(readFileSync19(sourcePath, "utf-8"));
|
|
5705
6190
|
} catch (e) {
|
|
5706
6191
|
return {
|
|
5707
6192
|
success: false,
|
|
@@ -5717,90 +6202,20 @@ function importTheme(projectRoot, sourcePath) {
|
|
|
5717
6202
|
}
|
|
5718
6203
|
theme.source = "custom";
|
|
5719
6204
|
const id = theme.id;
|
|
5720
|
-
const customThemesDir =
|
|
5721
|
-
const destPath =
|
|
5722
|
-
|
|
5723
|
-
const howToPath =
|
|
5724
|
-
if (!
|
|
5725
|
-
|
|
5726
|
-
}
|
|
5727
|
-
|
|
6205
|
+
const customThemesDir = join27(projectRoot, ".decantr", "custom", "themes");
|
|
6206
|
+
const destPath = join27(customThemesDir, `${id}.json`);
|
|
6207
|
+
mkdirSync13(customThemesDir, { recursive: true });
|
|
6208
|
+
const howToPath = join27(customThemesDir, "how-to-theme.md");
|
|
6209
|
+
if (!existsSync26(howToPath)) {
|
|
6210
|
+
writeFileSync16(howToPath, getHowToThemeDoc());
|
|
6211
|
+
}
|
|
6212
|
+
writeFileSync16(destPath, JSON.stringify(theme, null, 2));
|
|
5728
6213
|
return {
|
|
5729
6214
|
success: true,
|
|
5730
6215
|
path: destPath
|
|
5731
6216
|
};
|
|
5732
6217
|
}
|
|
5733
6218
|
|
|
5734
|
-
// src/workspace.ts
|
|
5735
|
-
import { existsSync as existsSync26, readdirSync as readdirSync6, readFileSync as readFileSync19 } from "fs";
|
|
5736
|
-
import { dirname as dirname3, join as join27, resolve as resolve3 } from "path";
|
|
5737
|
-
function readPackageJson(dir) {
|
|
5738
|
-
const path = join27(dir, "package.json");
|
|
5739
|
-
if (!existsSync26(path)) return null;
|
|
5740
|
-
try {
|
|
5741
|
-
return JSON.parse(readFileSync19(path, "utf-8"));
|
|
5742
|
-
} catch {
|
|
5743
|
-
return null;
|
|
5744
|
-
}
|
|
5745
|
-
}
|
|
5746
|
-
function hasWorkspaceMarker(dir) {
|
|
5747
|
-
if (existsSync26(join27(dir, "pnpm-workspace.yaml")) || existsSync26(join27(dir, "turbo.json")) || existsSync26(join27(dir, "nx.json"))) {
|
|
5748
|
-
return true;
|
|
5749
|
-
}
|
|
5750
|
-
const pkg = readPackageJson(dir);
|
|
5751
|
-
return Boolean(pkg?.workspaces);
|
|
5752
|
-
}
|
|
5753
|
-
function findWorkspaceRoot(startDir) {
|
|
5754
|
-
let current = resolve3(startDir);
|
|
5755
|
-
while (true) {
|
|
5756
|
-
if (hasWorkspaceMarker(current)) return current;
|
|
5757
|
-
const parent = dirname3(current);
|
|
5758
|
-
if (parent === current) return null;
|
|
5759
|
-
current = parent;
|
|
5760
|
-
}
|
|
5761
|
-
}
|
|
5762
|
-
function looksLikeApp(dir) {
|
|
5763
|
-
if (existsSync26(join27(dir, "next.config.js")) || existsSync26(join27(dir, "next.config.ts")) || existsSync26(join27(dir, "next.config.mjs")) || existsSync26(join27(dir, "vite.config.ts")) || existsSync26(join27(dir, "vite.config.js")) || existsSync26(join27(dir, "angular.json")) || existsSync26(join27(dir, "svelte.config.js")) || existsSync26(join27(dir, "svelte.config.ts")) || existsSync26(join27(dir, "astro.config.mjs")) || existsSync26(join27(dir, "src")) || existsSync26(join27(dir, "app")) || existsSync26(join27(dir, "pages"))) {
|
|
5764
|
-
return true;
|
|
5765
|
-
}
|
|
5766
|
-
const pkg = readPackageJson(dir);
|
|
5767
|
-
const deps = { ...pkg?.dependencies, ...pkg?.devDependencies };
|
|
5768
|
-
return Boolean(
|
|
5769
|
-
deps.react || deps.next || deps.vue || deps.svelte || deps["@angular/core"] || deps.astro || deps.nuxt
|
|
5770
|
-
);
|
|
5771
|
-
}
|
|
5772
|
-
function listWorkspaceApps(workspaceRoot) {
|
|
5773
|
-
const candidates = [];
|
|
5774
|
-
for (const base of ["apps", "packages"]) {
|
|
5775
|
-
const baseDir = join27(workspaceRoot, base);
|
|
5776
|
-
if (!existsSync26(baseDir)) continue;
|
|
5777
|
-
for (const entry of readdirSync6(baseDir, { withFileTypes: true })) {
|
|
5778
|
-
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
|
|
5779
|
-
const candidate = join27(baseDir, entry.name);
|
|
5780
|
-
if (looksLikeApp(candidate)) {
|
|
5781
|
-
candidates.push(`${base}/${entry.name}`);
|
|
5782
|
-
}
|
|
5783
|
-
}
|
|
5784
|
-
}
|
|
5785
|
-
return candidates.sort();
|
|
5786
|
-
}
|
|
5787
|
-
function resolveWorkspaceInfo(cwd, projectArg) {
|
|
5788
|
-
const absoluteCwd = resolve3(cwd);
|
|
5789
|
-
const workspaceRoot = findWorkspaceRoot(absoluteCwd) ?? absoluteCwd;
|
|
5790
|
-
const appRoot = projectArg ? resolve3(absoluteCwd, projectArg) : absoluteCwd;
|
|
5791
|
-
const appCandidates = listWorkspaceApps(workspaceRoot);
|
|
5792
|
-
const projectScope = workspaceRoot !== appRoot || appCandidates.length > 0 ? "workspace-app" : "single-app";
|
|
5793
|
-
const requiresProjectSelection = !projectArg && workspaceRoot === absoluteCwd && appCandidates.length > 1;
|
|
5794
|
-
return {
|
|
5795
|
-
cwd: absoluteCwd,
|
|
5796
|
-
workspaceRoot,
|
|
5797
|
-
appRoot,
|
|
5798
|
-
projectScope,
|
|
5799
|
-
appCandidates,
|
|
5800
|
-
requiresProjectSelection
|
|
5801
|
-
};
|
|
5802
|
-
}
|
|
5803
|
-
|
|
5804
6219
|
// src/index.ts
|
|
5805
6220
|
var BOLD7 = "\x1B[1m";
|
|
5806
6221
|
var DIM14 = "\x1B[2m";
|
|
@@ -6328,7 +6743,7 @@ function getPublicAPIClient() {
|
|
|
6328
6743
|
});
|
|
6329
6744
|
}
|
|
6330
6745
|
function resolveUserPath(inputPath, cwd = process.cwd()) {
|
|
6331
|
-
return isAbsolute(inputPath) ? inputPath :
|
|
6746
|
+
return isAbsolute(inputPath) ? inputPath : resolve3(cwd, inputPath);
|
|
6332
6747
|
}
|
|
6333
6748
|
function extractHostedAssetPaths(indexHtml) {
|
|
6334
6749
|
const assetPaths = /* @__PURE__ */ new Set();
|
|
@@ -6379,7 +6794,7 @@ function readHostedSourceSnapshot(sourcePath) {
|
|
|
6379
6794
|
"build",
|
|
6380
6795
|
"coverage"
|
|
6381
6796
|
]);
|
|
6382
|
-
const rootPrefix =
|
|
6797
|
+
const rootPrefix = basename3(resolvedSourcePath);
|
|
6383
6798
|
const walk = (absoluteDir, relativeDir) => {
|
|
6384
6799
|
for (const entry of readdirSync7(absoluteDir, { withFileTypes: true })) {
|
|
6385
6800
|
if (ignoredDirNames.has(entry.name)) continue;
|
|
@@ -6551,7 +6966,7 @@ async function printHostedExecutionPackBundle(essencePath, namespace, jsonOutput
|
|
|
6551
6966
|
let writtenContextPaths = [];
|
|
6552
6967
|
if (writeContext) {
|
|
6553
6968
|
const contextDir = join28(process.cwd(), ".decantr", "context");
|
|
6554
|
-
|
|
6969
|
+
mkdirSync14(contextDir, { recursive: true });
|
|
6555
6970
|
const written = writeExecutionPackBundleArtifacts(
|
|
6556
6971
|
contextDir,
|
|
6557
6972
|
bundle
|
|
@@ -6592,7 +7007,7 @@ function resolvePagePackIdForRoute(essencePath, route) {
|
|
|
6592
7007
|
throw new Error(`Essence file not found at ${essencePath}`);
|
|
6593
7008
|
}
|
|
6594
7009
|
const essence = JSON.parse(readFileSync20(essencePath, "utf-8"));
|
|
6595
|
-
if (!
|
|
7010
|
+
if (!isV48(essence)) {
|
|
6596
7011
|
throw new Error("Route-based pack resolution requires Essence v4.0.0.");
|
|
6597
7012
|
}
|
|
6598
7013
|
const target = essence.blueprint.routes?.[route];
|
|
@@ -6625,16 +7040,16 @@ async function printHostedSelectedExecutionPack(packType, id, essencePath, names
|
|
|
6625
7040
|
let writtenContextDir = null;
|
|
6626
7041
|
if (writeContext) {
|
|
6627
7042
|
const contextDir = join28(process.cwd(), ".decantr", "context");
|
|
6628
|
-
|
|
6629
|
-
|
|
7043
|
+
mkdirSync14(contextDir, { recursive: true });
|
|
7044
|
+
writeFileSync17(
|
|
6630
7045
|
join28(contextDir, "pack-manifest.json"),
|
|
6631
7046
|
JSON.stringify(selected.manifest, null, 2) + "\n"
|
|
6632
7047
|
);
|
|
6633
7048
|
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
7049
|
const markdownFile = manifestEntry?.markdown ?? `${selected.selector.packType}${selected.selector.id ? `-${selected.selector.id}` : ""}-pack.md`;
|
|
6635
7050
|
const jsonFile = manifestEntry?.json ?? `${selected.selector.packType}${selected.selector.id ? `-${selected.selector.id}` : ""}-pack.json`;
|
|
6636
|
-
|
|
6637
|
-
|
|
7051
|
+
writeFileSync17(join28(contextDir, markdownFile), selected.pack.renderedMarkdown);
|
|
7052
|
+
writeFileSync17(join28(contextDir, jsonFile), JSON.stringify(selected.pack, null, 2) + "\n");
|
|
6638
7053
|
writtenContextDir = contextDir;
|
|
6639
7054
|
}
|
|
6640
7055
|
if (jsonOutput) {
|
|
@@ -6671,8 +7086,8 @@ async function printHostedExecutionPackManifest(essencePath, namespace, jsonOutp
|
|
|
6671
7086
|
let writtenContextDir = null;
|
|
6672
7087
|
if (writeContext) {
|
|
6673
7088
|
const contextDir = join28(process.cwd(), ".decantr", "context");
|
|
6674
|
-
|
|
6675
|
-
|
|
7089
|
+
mkdirSync14(contextDir, { recursive: true });
|
|
7090
|
+
writeFileSync17(join28(contextDir, "pack-manifest.json"), JSON.stringify(manifest, null, 2) + "\n");
|
|
6676
7091
|
writtenContextDir = contextDir;
|
|
6677
7092
|
}
|
|
6678
7093
|
if (jsonOutput) {
|
|
@@ -6711,7 +7126,7 @@ async function hydrateHostedExecutionPacksIfMissing(projectRoot, namespace = "@o
|
|
|
6711
7126
|
const client = getPublicAPIClient();
|
|
6712
7127
|
const essence = JSON.parse(readFileSync20(essencePath, "utf-8"));
|
|
6713
7128
|
const bundle = await client.compileExecutionPacks(essence, { namespace });
|
|
6714
|
-
|
|
7129
|
+
mkdirSync14(contextDir, { recursive: true });
|
|
6715
7130
|
writeExecutionPackBundleArtifacts(contextDir, bundle);
|
|
6716
7131
|
return { attempted: true, hydrated: true, scope: "bundle" };
|
|
6717
7132
|
} catch {
|
|
@@ -6739,14 +7154,14 @@ async function hydrateHostedReviewPackIfMissing(projectRoot, namespace = "@offic
|
|
|
6739
7154
|
},
|
|
6740
7155
|
{ namespace }
|
|
6741
7156
|
);
|
|
6742
|
-
|
|
6743
|
-
|
|
6744
|
-
|
|
7157
|
+
mkdirSync14(contextDir, { recursive: true });
|
|
7158
|
+
writeFileSync17(join28(contextDir, "review-pack.md"), selected.pack.renderedMarkdown);
|
|
7159
|
+
writeFileSync17(
|
|
6745
7160
|
join28(contextDir, "review-pack.json"),
|
|
6746
7161
|
JSON.stringify(selected.pack, null, 2) + "\n"
|
|
6747
7162
|
);
|
|
6748
7163
|
if (!existsSync27(manifestPath)) {
|
|
6749
|
-
|
|
7164
|
+
writeFileSync17(manifestPath, JSON.stringify(selected.manifest, null, 2) + "\n");
|
|
6750
7165
|
}
|
|
6751
7166
|
return { attempted: true, hydrated: true, scope: "review" };
|
|
6752
7167
|
} catch {
|
|
@@ -7082,7 +7497,7 @@ async function cmdValidate(path) {
|
|
|
7082
7497
|
process.exitCode = 1;
|
|
7083
7498
|
return;
|
|
7084
7499
|
}
|
|
7085
|
-
const detectedVersion =
|
|
7500
|
+
const detectedVersion = isV48(essence) ? "v4" : "legacy";
|
|
7086
7501
|
console.log(`${DIM14}Detected essence version: ${detectedVersion}${RESET14}`);
|
|
7087
7502
|
const result = validateEssence2(essence);
|
|
7088
7503
|
if (result.valid) {
|
|
@@ -7217,7 +7632,7 @@ ${CYAN8}Telemetry enabled.${RESET14} Decantr will send privacy-filtered CLI prod
|
|
|
7217
7632
|
console.log(`${DIM14}Set "telemetry": false in .decantr/project.json to opt out.${RESET14}`);
|
|
7218
7633
|
}
|
|
7219
7634
|
function readCliPackageVersion() {
|
|
7220
|
-
const here =
|
|
7635
|
+
const here = dirname3(fileURLToPath2(import.meta.url));
|
|
7221
7636
|
const candidates = [join28(here, "..", "package.json"), join28(here, "..", "..", "package.json")];
|
|
7222
7637
|
for (const candidate of candidates) {
|
|
7223
7638
|
try {
|
|
@@ -7238,13 +7653,13 @@ function backupExistingEssence(projectRoot, label) {
|
|
|
7238
7653
|
projectRoot,
|
|
7239
7654
|
`decantr.essence.${label}.${timestampForFile()}.backup.json`
|
|
7240
7655
|
);
|
|
7241
|
-
|
|
7656
|
+
writeFileSync17(backupPath, readFileSync20(essencePath, "utf-8"), "utf-8");
|
|
7242
7657
|
return backupPath;
|
|
7243
7658
|
}
|
|
7244
7659
|
function writeBrownfieldProjectJson(input) {
|
|
7245
7660
|
const decantrDir = join28(input.projectRoot, ".decantr");
|
|
7246
|
-
|
|
7247
|
-
|
|
7661
|
+
mkdirSync14(join28(decantrDir, "context"), { recursive: true });
|
|
7662
|
+
mkdirSync14(join28(decantrDir, "cache"), { recursive: true });
|
|
7248
7663
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
7249
7664
|
const projectJson = {
|
|
7250
7665
|
detected: {
|
|
@@ -7285,7 +7700,7 @@ function writeBrownfieldProjectJson(input) {
|
|
|
7285
7700
|
}
|
|
7286
7701
|
}
|
|
7287
7702
|
};
|
|
7288
|
-
|
|
7703
|
+
writeFileSync17(join28(decantrDir, "project.json"), JSON.stringify(projectJson, null, 2) + "\n");
|
|
7289
7704
|
}
|
|
7290
7705
|
async function applyAcceptedBrownfieldProposal(input) {
|
|
7291
7706
|
const proposal = readBrownfieldProposal(input.projectRoot);
|
|
@@ -7317,7 +7732,7 @@ async function applyAcceptedBrownfieldProposal(input) {
|
|
|
7317
7732
|
}
|
|
7318
7733
|
if (input.mode === "merge" && hasEssence) {
|
|
7319
7734
|
const existing = JSON.parse(readFileSync20(essencePath, "utf-8"));
|
|
7320
|
-
if (!
|
|
7735
|
+
if (!isV48(existing)) {
|
|
7321
7736
|
console.log(
|
|
7322
7737
|
error3(
|
|
7323
7738
|
"Existing essence is not v4. Run `decantr migrate --to v4` before merging a brownfield proposal."
|
|
@@ -7353,7 +7768,7 @@ async function applyAcceptedBrownfieldProposal(input) {
|
|
|
7353
7768
|
assistantBridge: input.assistantBridge,
|
|
7354
7769
|
mode: input.mode
|
|
7355
7770
|
});
|
|
7356
|
-
|
|
7771
|
+
writeFileSync17(essencePath, JSON.stringify(essence, null, 2) + "\n", "utf-8");
|
|
7357
7772
|
const registryClient = new RegistryClient({
|
|
7358
7773
|
cacheDir: join28(input.projectRoot, ".decantr", "cache"),
|
|
7359
7774
|
offline: true,
|
|
@@ -7414,8 +7829,7 @@ async function applyAcceptedBrownfieldProposal(input) {
|
|
|
7414
7829
|
async function cmdInit(args) {
|
|
7415
7830
|
const workspaceInfo = resolveWorkspaceInfo(process.cwd(), args.project);
|
|
7416
7831
|
if (args.yes && workspaceInfo.requiresProjectSelection) {
|
|
7417
|
-
|
|
7418
|
-
console.log(dim3(`Use --project=<path>. Candidates: ${workspaceInfo.appCandidates.join(", ")}`));
|
|
7832
|
+
printWorkspaceProjectSelection(workspaceInfo, "init");
|
|
7419
7833
|
process.exitCode = 1;
|
|
7420
7834
|
return;
|
|
7421
7835
|
}
|
|
@@ -7899,7 +8313,7 @@ Validation warnings: ${validation.errors.join(", ")}`));
|
|
|
7899
8313
|
}
|
|
7900
8314
|
console.log("");
|
|
7901
8315
|
let promptPages;
|
|
7902
|
-
if (
|
|
8316
|
+
if (isV48(essence)) {
|
|
7903
8317
|
const allPages = essence.blueprint.sections.flatMap(
|
|
7904
8318
|
(s) => s.pages.map((p) => ({ ...p, _sectionId: s.id, _shell: s.shell }))
|
|
7905
8319
|
);
|
|
@@ -7957,14 +8371,14 @@ async function cmdStatus() {
|
|
|
7957
8371
|
try {
|
|
7958
8372
|
const essence = JSON.parse(readFileSync20(essencePath, "utf-8"));
|
|
7959
8373
|
const validation = validateEssence2(essence);
|
|
7960
|
-
const essenceVersion =
|
|
8374
|
+
const essenceVersion = isV48(essence) ? "v4" : "legacy";
|
|
7961
8375
|
console.log(`${BOLD7}Essence:${RESET14}`);
|
|
7962
8376
|
if (validation.valid) {
|
|
7963
8377
|
console.log(` ${GREEN14}Valid${RESET14} (${essenceVersion})`);
|
|
7964
8378
|
} else {
|
|
7965
8379
|
console.log(` ${RED11}Invalid: ${validation.errors.join(", ")}${RESET14}`);
|
|
7966
8380
|
}
|
|
7967
|
-
if (
|
|
8381
|
+
if (isV48(essence)) {
|
|
7968
8382
|
const v4 = essence;
|
|
7969
8383
|
const sections = v4.blueprint.sections;
|
|
7970
8384
|
const flatPages = sections.flatMap((section) => section.pages ?? []);
|
|
@@ -8353,12 +8767,69 @@ function withoutWorkflowOnlyFlags(args) {
|
|
|
8353
8767
|
}
|
|
8354
8768
|
return stripped;
|
|
8355
8769
|
}
|
|
8356
|
-
function
|
|
8770
|
+
function withProject(command, projectArg) {
|
|
8771
|
+
return projectArg ? `${command} --project ${projectArg}` : command;
|
|
8772
|
+
}
|
|
8773
|
+
function firstWorkspaceCandidate(workspaceInfo) {
|
|
8774
|
+
return workspaceInfo.appCandidates[0] ?? "apps/web";
|
|
8775
|
+
}
|
|
8776
|
+
function printWorkspaceProjectSelection(workspaceInfo, commandName = "command") {
|
|
8777
|
+
const candidate = firstWorkspaceCandidate(workspaceInfo);
|
|
8778
|
+
const noun = commandName === "adopt" ? "Brownfield adoption" : `decantr ${commandName}`;
|
|
8779
|
+
console.log(error3(`${noun} needs an app path.`));
|
|
8780
|
+
console.log("");
|
|
8781
|
+
console.log(`${BOLD7}This looks like a monorepo.${RESET14}`);
|
|
8782
|
+
console.log("Install Decantr at the workspace root, then attach it to one app with --project.");
|
|
8783
|
+
console.log("");
|
|
8784
|
+
console.log("App candidates:");
|
|
8785
|
+
for (const appCandidate of workspaceInfo.appCandidates) {
|
|
8786
|
+
console.log(` ${appCandidate}`);
|
|
8787
|
+
}
|
|
8788
|
+
console.log("");
|
|
8789
|
+
console.log("Start by attaching one app:");
|
|
8790
|
+
console.log(` ${cyan3(`decantr adopt --project ${candidate} --yes`)}`);
|
|
8791
|
+
console.log("");
|
|
8792
|
+
console.log("Optional visual evidence after the app is running:");
|
|
8793
|
+
console.log(
|
|
8794
|
+
` ${cyan3(`decantr verify --project ${candidate} --base-url http://localhost:3000 --evidence`)}`
|
|
8795
|
+
);
|
|
8796
|
+
}
|
|
8797
|
+
function printMonorepoSetupGuidance(workspaceInfo) {
|
|
8798
|
+
const candidate = firstWorkspaceCandidate(workspaceInfo);
|
|
8799
|
+
console.log(heading2("Decantr Setup"));
|
|
8800
|
+
console.log(`${BOLD7}This looks like a monorepo.${RESET14}`);
|
|
8801
|
+
console.log(` Workspace root: ${workspaceInfo.workspaceRoot}`);
|
|
8802
|
+
console.log("");
|
|
8803
|
+
console.log(
|
|
8804
|
+
"Install Decantr at the workspace root, then attach it to the app you want Decantr to govern."
|
|
8805
|
+
);
|
|
8806
|
+
console.log("");
|
|
8807
|
+
console.log("App candidates:");
|
|
8808
|
+
for (const appCandidate of workspaceInfo.appCandidates) {
|
|
8809
|
+
console.log(` ${appCandidate}`);
|
|
8810
|
+
}
|
|
8811
|
+
console.log("");
|
|
8812
|
+
console.log(`${BOLD7}Start here:${RESET14}`);
|
|
8813
|
+
console.log(
|
|
8814
|
+
` ${cyan3("decantr workspace list")} Show attached projects and app candidates`
|
|
8815
|
+
);
|
|
8816
|
+
console.log(
|
|
8817
|
+
` ${cyan3(`decantr adopt --project ${candidate} --yes`)} Attach Decantr to one app`
|
|
8818
|
+
);
|
|
8819
|
+
console.log(
|
|
8820
|
+
` ${cyan3(`decantr codify --from-audit --project ${candidate}`)} Propose project-owned UI law`
|
|
8821
|
+
);
|
|
8822
|
+
console.log("");
|
|
8823
|
+
console.log(`${BOLD7}Optional visual evidence:${RESET14}`);
|
|
8824
|
+
console.log(
|
|
8825
|
+
` ${cyan3(`decantr verify --project ${candidate} --base-url http://localhost:3000 --evidence`)}`
|
|
8826
|
+
);
|
|
8827
|
+
}
|
|
8828
|
+
function resolveWorkflowProject(flags, commandName = "command") {
|
|
8357
8829
|
const projectArg = flagString(flags, "project");
|
|
8358
8830
|
const workspaceInfo = resolveWorkspaceInfo(process.cwd(), projectArg);
|
|
8359
8831
|
if (workspaceInfo.requiresProjectSelection) {
|
|
8360
|
-
|
|
8361
|
-
console.log(dim3(`Use --project=<path>. Candidates: ${workspaceInfo.appCandidates.join(", ")}`));
|
|
8832
|
+
printWorkspaceProjectSelection(workspaceInfo, commandName);
|
|
8362
8833
|
process.exitCode = 1;
|
|
8363
8834
|
return null;
|
|
8364
8835
|
}
|
|
@@ -8374,8 +8845,17 @@ function printWorkflowPlan(title, steps) {
|
|
|
8374
8845
|
}
|
|
8375
8846
|
async function cmdSetupWorkflow(args) {
|
|
8376
8847
|
const { flags } = parseLooseArgs(args);
|
|
8377
|
-
const
|
|
8378
|
-
|
|
8848
|
+
const projectArg = flagString(flags, "project");
|
|
8849
|
+
const workspaceInfo = resolveWorkspaceInfo(process.cwd(), projectArg);
|
|
8850
|
+
if (!projectArg && workspaceInfo.workspaceRoot === workspaceInfo.cwd && workspaceInfo.appCandidates.length > 0) {
|
|
8851
|
+
printMonorepoSetupGuidance(workspaceInfo);
|
|
8852
|
+
return;
|
|
8853
|
+
}
|
|
8854
|
+
if (workspaceInfo.requiresProjectSelection) {
|
|
8855
|
+
printWorkspaceProjectSelection(workspaceInfo, "setup");
|
|
8856
|
+
process.exitCode = 1;
|
|
8857
|
+
return;
|
|
8858
|
+
}
|
|
8379
8859
|
const detected = detectProject(workspaceInfo.appRoot);
|
|
8380
8860
|
const hasFootprint = detected.framework !== "unknown" || detected.packageManager !== "unknown" || detected.hasTypeScript || detected.hasTailwind || detected.existingRuleFiles.length > 0;
|
|
8381
8861
|
console.log(heading2("Decantr Setup"));
|
|
@@ -8384,18 +8864,30 @@ async function cmdSetupWorkflow(args) {
|
|
|
8384
8864
|
console.log("");
|
|
8385
8865
|
if (detected.existingEssence) {
|
|
8386
8866
|
console.log(`${BOLD7}Recommended path:${RESET14} maintain an attached Decantr project`);
|
|
8387
|
-
console.log(
|
|
8388
|
-
|
|
8389
|
-
|
|
8867
|
+
console.log(
|
|
8868
|
+
` ${cyan3(withProject('decantr task <route> "<change>"', projectArg))} Prepare LLM context before edits`
|
|
8869
|
+
);
|
|
8870
|
+
console.log(
|
|
8871
|
+
` ${cyan3(withProject("decantr verify --brownfield", projectArg))} Run local health and drift checks`
|
|
8872
|
+
);
|
|
8873
|
+
console.log(
|
|
8874
|
+
` ${cyan3(withProject("decantr codify --from-audit", projectArg))} Propose project-owned local law`
|
|
8875
|
+
);
|
|
8390
8876
|
return;
|
|
8391
8877
|
}
|
|
8392
8878
|
if (hasFootprint) {
|
|
8393
8879
|
console.log(`${BOLD7}Recommended path:${RESET14} brownfield adoption`);
|
|
8394
|
-
console.log(` ${cyan3("decantr adopt --yes")} Analyze, attach, and verify`);
|
|
8395
8880
|
console.log(
|
|
8396
|
-
` ${cyan3("decantr adopt --
|
|
8881
|
+
` ${cyan3(withProject("decantr adopt --yes", projectArg))} Analyze, attach, and verify`
|
|
8882
|
+
);
|
|
8883
|
+
console.log(
|
|
8884
|
+
` ${cyan3(withProject("decantr codify --from-audit", projectArg))} Propose local UI law`
|
|
8885
|
+
);
|
|
8886
|
+
console.log("");
|
|
8887
|
+
console.log(`${BOLD7}Optional visual evidence after the app is running:${RESET14}`);
|
|
8888
|
+
console.log(
|
|
8889
|
+
` ${cyan3(withProject("decantr verify --base-url http://localhost:3000 --evidence", projectArg))}`
|
|
8397
8890
|
);
|
|
8398
|
-
console.log(` ${cyan3("decantr codify")} Propose local UI law`);
|
|
8399
8891
|
return;
|
|
8400
8892
|
}
|
|
8401
8893
|
console.log(`${BOLD7}Recommended path:${RESET14} greenfield start`);
|
|
@@ -8404,9 +8896,10 @@ async function cmdSetupWorkflow(args) {
|
|
|
8404
8896
|
}
|
|
8405
8897
|
async function cmdAdoptWorkflow(args) {
|
|
8406
8898
|
const { flags } = parseLooseArgs(args);
|
|
8407
|
-
const workspaceInfo = resolveWorkflowProject(flags);
|
|
8899
|
+
const workspaceInfo = resolveWorkflowProject(flags, "adopt");
|
|
8408
8900
|
if (!workspaceInfo) return;
|
|
8409
8901
|
const projectRoot = workspaceInfo.appRoot;
|
|
8902
|
+
const projectArg = flagString(flags, "project");
|
|
8410
8903
|
const dryRun = flagBoolean(flags, "dry-run");
|
|
8411
8904
|
const yes = flagBoolean(flags, "yes") || flagBoolean(flags, "y");
|
|
8412
8905
|
const baseUrl = flagString(flags, "base-url");
|
|
@@ -8476,10 +8969,22 @@ async function cmdAdoptWorkflow(args) {
|
|
|
8476
8969
|
});
|
|
8477
8970
|
}
|
|
8478
8971
|
console.log("");
|
|
8479
|
-
console.log(`${BOLD7}
|
|
8480
|
-
console.log(
|
|
8481
|
-
|
|
8482
|
-
|
|
8972
|
+
console.log(`${BOLD7}Brownfield operating loop:${RESET14}`);
|
|
8973
|
+
console.log(
|
|
8974
|
+
` ${cyan3(withProject("decantr codify --from-audit", projectArg))} Discover and propose project-owned UI law`
|
|
8975
|
+
);
|
|
8976
|
+
console.log(
|
|
8977
|
+
` ${cyan3(withProject("decantr codify --accept", projectArg))} Accept reviewed local patterns and rules`
|
|
8978
|
+
);
|
|
8979
|
+
console.log(
|
|
8980
|
+
` ${cyan3(withProject('decantr task <route> "<change>"', projectArg))} Give your LLM route-specific context before edits`
|
|
8981
|
+
);
|
|
8982
|
+
console.log(
|
|
8983
|
+
` ${cyan3(withProject("decantr verify --brownfield --local-patterns", projectArg))} Check contract, health, and local law after edits`
|
|
8984
|
+
);
|
|
8985
|
+
console.log(
|
|
8986
|
+
` ${cyan3(withProject("decantr verify --since-baseline", projectArg))} Compare future work against this baseline`
|
|
8987
|
+
);
|
|
8483
8988
|
}
|
|
8484
8989
|
async function cmdVerifyWorkflow(args) {
|
|
8485
8990
|
const { flags } = parseLooseArgs(args);
|
|
@@ -8490,16 +8995,17 @@ async function cmdVerifyWorkflow(args) {
|
|
|
8490
8995
|
return;
|
|
8491
8996
|
}
|
|
8492
8997
|
if (workspaceMode) {
|
|
8493
|
-
const { cmdWorkspace } = await import("./workspace-
|
|
8998
|
+
const { cmdWorkspace } = await import("./workspace-U7J3CJY3.js");
|
|
8494
8999
|
await cmdWorkspace(process.cwd(), ["workspace", "health", ...withoutWorkflowOnlyFlags(args)]);
|
|
8495
9000
|
return;
|
|
8496
9001
|
}
|
|
8497
|
-
const workspaceInfo = resolveWorkflowProject(flags);
|
|
9002
|
+
const workspaceInfo = resolveWorkflowProject(flags, "verify");
|
|
8498
9003
|
if (!workspaceInfo) return;
|
|
8499
9004
|
const brownfield = flagBoolean(flags, "brownfield");
|
|
8500
9005
|
const localPatterns = flagBoolean(flags, "local-patterns");
|
|
8501
9006
|
const evidence = flagBoolean(flags, "evidence");
|
|
8502
9007
|
const baseUrl = flagString(flags, "base-url");
|
|
9008
|
+
const failOn = flagString(flags, "fail-on") ?? "error";
|
|
8503
9009
|
const healthArgs = ["health", ...withoutWorkflowOnlyFlags(args)];
|
|
8504
9010
|
if (flagBoolean(flags, "baseline") && !healthArgs.includes("--save-baseline")) {
|
|
8505
9011
|
healthArgs.push("--save-baseline");
|
|
@@ -8535,16 +9041,51 @@ async function cmdVerifyWorkflow(args) {
|
|
|
8535
9041
|
const { cmdHealth, parseHealthArgs } = await import("./health-ETZXWGTW.js");
|
|
8536
9042
|
await cmdHealth(workspaceInfo.appRoot, parseHealthArgs(healthArgs));
|
|
8537
9043
|
if (localPatterns) {
|
|
8538
|
-
const
|
|
8539
|
-
if (!
|
|
8540
|
-
|
|
8541
|
-
|
|
8542
|
-
|
|
8543
|
-
|
|
9044
|
+
const validation = validateLocalLaw(workspaceInfo.appRoot);
|
|
9045
|
+
if (!validation.patternPackPresent) {
|
|
9046
|
+
if (!quietOutput) {
|
|
9047
|
+
console.log("");
|
|
9048
|
+
console.log(
|
|
9049
|
+
`${YELLOW9}Local pattern pack missing.${RESET14} Run ${cyan3("decantr codify --from-audit")}, review the proposal, then run ${cyan3("decantr codify --accept")}.`
|
|
9050
|
+
);
|
|
9051
|
+
}
|
|
8544
9052
|
process.exitCode = process.exitCode || 1;
|
|
8545
9053
|
} else {
|
|
8546
|
-
|
|
8547
|
-
|
|
9054
|
+
const blockingFindings = failOn === "none" ? [] : validation.findings.filter(
|
|
9055
|
+
(finding) => failOn === "warn" ? finding.severity === "warn" || finding.severity === "error" : finding.severity === "error"
|
|
9056
|
+
);
|
|
9057
|
+
const blockingWarnings = failOn === "warn" ? validation.warnings : [];
|
|
9058
|
+
if (!quietOutput) {
|
|
9059
|
+
console.log("");
|
|
9060
|
+
console.log(`${GREEN14}Local pattern pack found:${RESET14} ${validation.patternsPath}`);
|
|
9061
|
+
if (validation.ruleManifestPresent) {
|
|
9062
|
+
console.log(`${GREEN14}Local rule manifest found:${RESET14} ${validation.rulesPath}`);
|
|
9063
|
+
} else {
|
|
9064
|
+
console.log(
|
|
9065
|
+
`${YELLOW9}Local rule manifest missing.${RESET14} Run ${cyan3("decantr codify --from-audit")} to propose .decantr/rules.json.`
|
|
9066
|
+
);
|
|
9067
|
+
}
|
|
9068
|
+
for (const warning of validation.warnings.slice(0, 8)) {
|
|
9069
|
+
console.log(`${YELLOW9}warn${RESET14} ${warning}`);
|
|
9070
|
+
}
|
|
9071
|
+
if (validation.findings.length > 0) {
|
|
9072
|
+
console.log("");
|
|
9073
|
+
console.log(`${BOLD7}Local law findings:${RESET14}`);
|
|
9074
|
+
for (const finding of validation.findings.slice(0, 20)) {
|
|
9075
|
+
console.log(
|
|
9076
|
+
` ${finding.severity.toUpperCase()} ${finding.ruleId} ${finding.file}:${finding.line}:${finding.column} ${finding.message}`
|
|
9077
|
+
);
|
|
9078
|
+
}
|
|
9079
|
+
if (validation.findings.length > 20) {
|
|
9080
|
+
console.log(dim3(` ...${validation.findings.length - 20} more finding(s)`));
|
|
9081
|
+
}
|
|
9082
|
+
} else if (validation.ruleManifestPresent) {
|
|
9083
|
+
console.log(`${GREEN14}Local rule checks passed.${RESET14}`);
|
|
9084
|
+
}
|
|
9085
|
+
}
|
|
9086
|
+
if (blockingFindings.length > 0 || blockingWarnings.length > 0) {
|
|
9087
|
+
process.exitCode = process.exitCode || 1;
|
|
9088
|
+
}
|
|
8548
9089
|
}
|
|
8549
9090
|
}
|
|
8550
9091
|
if (guardExitCode && guardExitCode !== 0 && (!process.exitCode || process.exitCode === 0)) {
|
|
@@ -8561,11 +9102,15 @@ function readJsonIfPresent(path) {
|
|
|
8561
9102
|
}
|
|
8562
9103
|
async function cmdTaskWorkflow(args) {
|
|
8563
9104
|
const { flags, positional } = parseLooseArgs(args);
|
|
8564
|
-
const workspaceInfo = resolveWorkflowProject(flags);
|
|
9105
|
+
const workspaceInfo = resolveWorkflowProject(flags, "task");
|
|
8565
9106
|
if (!workspaceInfo) return;
|
|
8566
9107
|
const routeInput = positional[0];
|
|
8567
9108
|
if (!routeInput) {
|
|
8568
|
-
console.error(
|
|
9109
|
+
console.error(
|
|
9110
|
+
error3(
|
|
9111
|
+
'Usage: decantr task <route> ["task summary"] [--project <path>] [--since origin/main] [--json]'
|
|
9112
|
+
)
|
|
9113
|
+
);
|
|
8569
9114
|
process.exitCode = 1;
|
|
8570
9115
|
return;
|
|
8571
9116
|
}
|
|
@@ -8574,11 +9119,13 @@ async function cmdTaskWorkflow(args) {
|
|
|
8574
9119
|
const essencePath = join28(workspaceInfo.appRoot, "decantr.essence.json");
|
|
8575
9120
|
const essence = readJsonIfPresent(essencePath);
|
|
8576
9121
|
if (!essence) {
|
|
8577
|
-
console.error(
|
|
9122
|
+
console.error(
|
|
9123
|
+
error3("No decantr.essence.json found. Run `decantr adopt` or `decantr init` first.")
|
|
9124
|
+
);
|
|
8578
9125
|
process.exitCode = 1;
|
|
8579
9126
|
return;
|
|
8580
9127
|
}
|
|
8581
|
-
if (!
|
|
9128
|
+
if (!isV48(essence)) {
|
|
8582
9129
|
console.error(error3("Task context requires Essence v4. Run `decantr migrate --to v4` first."));
|
|
8583
9130
|
process.exitCode = 1;
|
|
8584
9131
|
return;
|
|
@@ -8599,7 +9146,12 @@ async function cmdTaskWorkflow(args) {
|
|
|
8599
9146
|
const sectionPack = manifest?.sections?.find((entry) => entry.id === target.section);
|
|
8600
9147
|
const visualManifest = readJsonIfPresent(join28(workspaceInfo.appRoot, ".decantr", "evidence", "visual-manifest.json"));
|
|
8601
9148
|
const screenshot = visualManifest?.routes?.find((entry) => entry.route === route)?.screenshot;
|
|
8602
|
-
const
|
|
9149
|
+
const localPatternPackPath = localPatternsPath(workspaceInfo.appRoot);
|
|
9150
|
+
const localRuleManifestPath = localRulesPath(workspaceInfo.appRoot);
|
|
9151
|
+
const localLaw = createLocalLawTaskSummary(workspaceInfo.appRoot);
|
|
9152
|
+
const changedSince = flagString(flags, "since");
|
|
9153
|
+
const currentChangedFiles = changedFiles(workspaceInfo.appRoot, changedSince);
|
|
9154
|
+
const changedRoutes = routeImpacts(workspaceInfo.appRoot, currentChangedFiles);
|
|
8603
9155
|
const context = {
|
|
8604
9156
|
route,
|
|
8605
9157
|
task: taskSummary || null,
|
|
@@ -8613,9 +9165,14 @@ async function cmdTaskWorkflow(args) {
|
|
|
8613
9165
|
manifest?.scaffold?.markdown ? join28(".decantr/context", manifest.scaffold.markdown) : null,
|
|
8614
9166
|
".decantr/context/scaffold.md",
|
|
8615
9167
|
"DECANTR.md",
|
|
8616
|
-
existsSync27(
|
|
9168
|
+
existsSync27(localPatternPackPath) ? ".decantr/local-patterns.json" : null,
|
|
9169
|
+
existsSync27(localRuleManifestPath) ? ".decantr/rules.json" : null
|
|
8617
9170
|
].filter(Boolean),
|
|
8618
|
-
screenshot: screenshot ?? null
|
|
9171
|
+
screenshot: screenshot ?? null,
|
|
9172
|
+
localLaw,
|
|
9173
|
+
changedFiles: currentChangedFiles,
|
|
9174
|
+
changedRoutes,
|
|
9175
|
+
verifyCommand: "decantr verify --brownfield --local-patterns"
|
|
8619
9176
|
};
|
|
8620
9177
|
if (flagBoolean(flags, "json")) {
|
|
8621
9178
|
console.log(JSON.stringify(context, null, 2));
|
|
@@ -8637,95 +9194,105 @@ async function cmdTaskWorkflow(args) {
|
|
|
8637
9194
|
console.log(`${BOLD7}Visual evidence:${RESET14}`);
|
|
8638
9195
|
console.log(` ${cyan3(context.screenshot)}`);
|
|
8639
9196
|
}
|
|
9197
|
+
if (context.localLaw.patternCount > 0 || context.localLaw.ruleCount > 0) {
|
|
9198
|
+
console.log("");
|
|
9199
|
+
console.log(`${BOLD7}Project-owned local law:${RESET14}`);
|
|
9200
|
+
if (context.localLaw.patternsPath) {
|
|
9201
|
+
console.log(
|
|
9202
|
+
` Patterns: ${cyan3(context.localLaw.patternsPath)} (${context.localLaw.patternCount})`
|
|
9203
|
+
);
|
|
9204
|
+
}
|
|
9205
|
+
if (context.localLaw.rulesPath) {
|
|
9206
|
+
console.log(` Rules: ${cyan3(context.localLaw.rulesPath)} (${context.localLaw.ruleCount})`);
|
|
9207
|
+
}
|
|
9208
|
+
for (const pattern of context.localLaw.patterns.slice(0, 4)) {
|
|
9209
|
+
const pathHint = pattern.componentPaths.length > 0 ? ` \u2014 ${pattern.componentPaths.slice(0, 2).join(", ")}` : "";
|
|
9210
|
+
console.log(` ${pattern.id}: ${pattern.role ?? "local pattern"}${pathHint}`);
|
|
9211
|
+
}
|
|
9212
|
+
} else {
|
|
9213
|
+
console.log("");
|
|
9214
|
+
console.log(`${BOLD7}Project-owned local law:${RESET14}`);
|
|
9215
|
+
console.log(
|
|
9216
|
+
` ${YELLOW9}Not codified yet.${RESET14} Run ${cyan3("decantr codify --from-audit")} after adoption.`
|
|
9217
|
+
);
|
|
9218
|
+
}
|
|
9219
|
+
if (context.changedFiles.length > 0) {
|
|
9220
|
+
console.log("");
|
|
9221
|
+
console.log(`${BOLD7}Changed-file context:${RESET14}`);
|
|
9222
|
+
for (const file of context.changedFiles.slice(0, 8)) {
|
|
9223
|
+
console.log(` ${file}`);
|
|
9224
|
+
}
|
|
9225
|
+
if (context.changedFiles.length > 8) {
|
|
9226
|
+
console.log(dim3(` ...${context.changedFiles.length - 8} more changed file(s)`));
|
|
9227
|
+
}
|
|
9228
|
+
if (context.changedRoutes.length > 0) {
|
|
9229
|
+
console.log(` Impacted routes: ${context.changedRoutes.join(", ")}`);
|
|
9230
|
+
}
|
|
9231
|
+
}
|
|
8640
9232
|
console.log("");
|
|
8641
9233
|
console.log(`${BOLD7}LLM instruction:${RESET14}`);
|
|
8642
9234
|
console.log(
|
|
8643
|
-
" Preserve the existing runtime and styling system. Use the route pack, section context, local
|
|
9235
|
+
" Preserve the existing runtime and styling system. Use the route pack, section context, local laws, changed-file impact, and visual evidence above as the task contract before changing code."
|
|
8644
9236
|
);
|
|
9237
|
+
console.log(` After editing, run ${cyan3(context.verifyCommand)}.`);
|
|
8645
9238
|
}
|
|
8646
9239
|
async function cmdCodifyWorkflow(args) {
|
|
8647
9240
|
const { flags } = parseLooseArgs(args);
|
|
8648
|
-
const workspaceInfo = resolveWorkflowProject(flags);
|
|
9241
|
+
const workspaceInfo = resolveWorkflowProject(flags, "codify");
|
|
8649
9242
|
if (!workspaceInfo) return;
|
|
8650
|
-
const decantrDir = join28(workspaceInfo.appRoot, ".decantr");
|
|
8651
|
-
const proposalPathLocal = join28(decantrDir, "local-patterns.proposal.json");
|
|
8652
|
-
const acceptedPath = join28(decantrDir, "local-patterns.json");
|
|
8653
9243
|
if (flagBoolean(flags, "accept")) {
|
|
8654
|
-
if (!existsSync27(
|
|
8655
|
-
console.error(
|
|
9244
|
+
if (!existsSync27(localPatternsProposalPath(workspaceInfo.appRoot)) && !existsSync27(localRulesProposalPath(workspaceInfo.appRoot))) {
|
|
9245
|
+
console.error(
|
|
9246
|
+
error3(
|
|
9247
|
+
"No local law proposal found. Run `decantr codify --from-audit` or `decantr codify` first."
|
|
9248
|
+
)
|
|
9249
|
+
);
|
|
8656
9250
|
process.exitCode = 1;
|
|
8657
9251
|
return;
|
|
8658
9252
|
}
|
|
8659
|
-
|
|
8660
|
-
|
|
8661
|
-
|
|
9253
|
+
const result2 = acceptBrownfieldLocalLaw(workspaceInfo.appRoot);
|
|
9254
|
+
if (result2.patternAcceptedPath) {
|
|
9255
|
+
console.log(success3(`Accepted local pattern pack: ${result2.patternAcceptedPath}`));
|
|
9256
|
+
}
|
|
9257
|
+
if (result2.rulesAcceptedPath) {
|
|
9258
|
+
console.log(success3(`Accepted local rule manifest: ${result2.rulesAcceptedPath}`));
|
|
9259
|
+
}
|
|
9260
|
+
console.log(dim3("Run `decantr verify --brownfield --local-patterns` after project edits."));
|
|
8662
9261
|
return;
|
|
8663
9262
|
}
|
|
8664
|
-
mkdirSync13(decantrDir, { recursive: true });
|
|
8665
9263
|
const detected = detectProject(workspaceInfo.appRoot);
|
|
8666
|
-
const essence = readJsonIfPresent(
|
|
8667
|
-
|
|
8668
|
-
|
|
8669
|
-
|
|
8670
|
-
|
|
8671
|
-
|
|
8672
|
-
|
|
8673
|
-
|
|
8674
|
-
|
|
8675
|
-
|
|
8676
|
-
|
|
8677
|
-
|
|
8678
|
-
|
|
8679
|
-
|
|
8680
|
-
|
|
8681
|
-
|
|
8682
|
-
|
|
8683
|
-
|
|
8684
|
-
|
|
8685
|
-
|
|
8686
|
-
|
|
8687
|
-
|
|
8688
|
-
|
|
8689
|
-
id: "surface-card",
|
|
8690
|
-
role: "Cards, panels, and content surfaces",
|
|
8691
|
-
decide: "Define the canonical card background, border, radius, shadow, padding, and hover treatment.",
|
|
8692
|
-
evidenceToCollect: ["shared card component", "token/class recipe", "allowed density variants"]
|
|
8693
|
-
},
|
|
8694
|
-
{
|
|
8695
|
-
id: "page-shell",
|
|
8696
|
-
role: "Route shell, nav, spacing, and scroll ownership",
|
|
8697
|
-
decide: "Define which layout owns max width, gutters, sticky chrome, and scroll containers.",
|
|
8698
|
-
evidenceToCollect: ["root layout path", "page template path", "responsive breakpoints"]
|
|
8699
|
-
},
|
|
8700
|
-
{
|
|
8701
|
-
id: "form-control",
|
|
8702
|
-
role: "Inputs, labels, validation, and form actions",
|
|
8703
|
-
decide: "Define input height, label placement, error copy, disabled state, and focus treatment.",
|
|
8704
|
-
evidenceToCollect: ["form field wrapper", "validation pattern", "accessibility expectations"]
|
|
8705
|
-
}
|
|
8706
|
-
],
|
|
8707
|
-
starterRules: [
|
|
8708
|
-
"Prefer project-owned wrappers for repeated primitives once they exist.",
|
|
8709
|
-
"Avoid raw hex/rgb values in component templates unless explicitly documented as dynamic data.",
|
|
8710
|
-
"Avoid static inline styles for reusable visual treatment.",
|
|
8711
|
-
"When adding a new route, map it to an existing local pattern before inventing a new visual variant."
|
|
8712
|
-
],
|
|
8713
|
-
nextSteps: [
|
|
8714
|
-
"Edit this proposal with real component paths and token/class recipes.",
|
|
8715
|
-
"Run decantr codify --accept after review.",
|
|
8716
|
-
"Use decantr task <route> before LLM edits so local patterns appear in the task context.",
|
|
8717
|
-
"Wire mechanical enforcement through ESLint/Biome/project tests for rules Decantr cannot reliably infer."
|
|
8718
|
-
]
|
|
8719
|
-
};
|
|
8720
|
-
writeFileSync16(proposalPathLocal, JSON.stringify(proposal, null, 2) + "\n", "utf-8");
|
|
8721
|
-
console.log(success3(`Wrote local pattern proposal: ${proposalPathLocal}`));
|
|
8722
|
-
console.log(dim3("Review it, add real component paths/token recipes, then run `decantr codify --accept`."));
|
|
9264
|
+
const essence = readJsonIfPresent(
|
|
9265
|
+
join28(workspaceInfo.appRoot, "decantr.essence.json")
|
|
9266
|
+
);
|
|
9267
|
+
const fromAudit = flagBoolean(flags, "from-audit") || flagBoolean(flags, "discover-local-patterns") || flagBoolean(flags, "codify-local-patterns");
|
|
9268
|
+
const proposal = createBrownfieldCodifyProposal({
|
|
9269
|
+
projectRoot: workspaceInfo.appRoot,
|
|
9270
|
+
detected,
|
|
9271
|
+
essence,
|
|
9272
|
+
fromAudit
|
|
9273
|
+
});
|
|
9274
|
+
const result = writeBrownfieldCodifyProposal(workspaceInfo.appRoot, proposal);
|
|
9275
|
+
console.log(success3(`Wrote local pattern proposal: ${result.patternPath}`));
|
|
9276
|
+
console.log(success3(`Wrote local rule proposal: ${result.rulesPath}`));
|
|
9277
|
+
if (fromAudit) {
|
|
9278
|
+
console.log(
|
|
9279
|
+
dim3("Proposal includes source-derived component candidates and starter mechanical rules.")
|
|
9280
|
+
);
|
|
9281
|
+
}
|
|
9282
|
+
console.log(
|
|
9283
|
+
dim3(
|
|
9284
|
+
"Review both files, add real component paths/token recipes, then run `decantr codify --accept`."
|
|
9285
|
+
)
|
|
9286
|
+
);
|
|
8723
9287
|
}
|
|
8724
9288
|
async function cmdContentWorkflow(args) {
|
|
8725
9289
|
const subcommand = args[1] ?? "check";
|
|
8726
9290
|
if (subcommand === "check" || subcommand === "health") {
|
|
8727
9291
|
const { cmdContentHealth, parseContentHealthArgs } = await import("./content-health-QQHBR6XG.js");
|
|
8728
|
-
await cmdContentHealth(
|
|
9292
|
+
await cmdContentHealth(
|
|
9293
|
+
process.cwd(),
|
|
9294
|
+
parseContentHealthArgs(["content-health", ...args.slice(2)])
|
|
9295
|
+
);
|
|
8729
9296
|
return;
|
|
8730
9297
|
}
|
|
8731
9298
|
if (subcommand === "create") {
|
|
@@ -8761,9 +9328,9 @@ ${BOLD7}Usage:${RESET14}
|
|
|
8761
9328
|
decantr setup [--project <path>]
|
|
8762
9329
|
decantr new <name> [--blueprint=X] [--archetype=X] [--theme=X] [--workflow=greenfield] [--adoption=decantr-css] [--telemetry]
|
|
8763
9330
|
decantr adopt [--project <path>] [--base-url <url>] [--evidence] [--ci] [--yes]
|
|
8764
|
-
decantr task <route> ["task summary"] [--project <path>] [--json]
|
|
9331
|
+
decantr task <route> ["task summary"] [--project <path>] [--since origin/main] [--json]
|
|
8765
9332
|
decantr verify [--project <path>] [--brownfield] [--local-patterns] [health options]
|
|
8766
|
-
decantr codify [--accept] [--project <path>]
|
|
9333
|
+
decantr codify [--from-audit] [--accept] [--project <path>]
|
|
8767
9334
|
decantr studio [--port 4319] [--host 127.0.0.1] [--report decantr-health.json] [--workspace]
|
|
8768
9335
|
|
|
8769
9336
|
${BOLD7}Advanced primitives:${RESET14}
|
|
@@ -8834,9 +9401,9 @@ ${BOLD7}Commands:${RESET14}
|
|
|
8834
9401
|
${cyan3("setup")} Detect project state and recommend the right Decantr workflow
|
|
8835
9402
|
${cyan3("new")} Create a new greenfield workspace and bootstrap the available starter adapter
|
|
8836
9403
|
${cyan3("adopt")} Brownfield one-liner: analyze, attach, verify, and show next steps
|
|
8837
|
-
${cyan3("task")} Prepare route/task context for an AI coding assistant
|
|
9404
|
+
${cyan3("task")} Prepare route/task context, local law, evidence, and changed-file impact for an AI coding assistant
|
|
8838
9405
|
${cyan3("verify")} One reliability gate over Project Health, Brownfield checks, baselines, and evidence
|
|
8839
|
-
${cyan3("codify")} Propose or accept project-owned Brownfield UI patterns
|
|
9406
|
+
${cyan3("codify")} Propose or accept project-owned Brownfield UI patterns and rules
|
|
8840
9407
|
${cyan3("studio")} Open a local Project Health dashboard backed by the same report
|
|
8841
9408
|
${cyan3("content")} Content-author namespace: check, create, publish
|
|
8842
9409
|
|
|
@@ -8874,11 +9441,13 @@ ${BOLD7}Advanced commands:${RESET14}
|
|
|
8874
9441
|
${BOLD7}Examples:${RESET14}
|
|
8875
9442
|
decantr setup
|
|
8876
9443
|
decantr new my-app --blueprint=carbon-ai-portal
|
|
8877
|
-
decantr adopt --
|
|
9444
|
+
decantr adopt --yes
|
|
9445
|
+
decantr adopt --project apps/web --yes
|
|
8878
9446
|
decantr task /feed "add saved recipe actions"
|
|
8879
9447
|
decantr verify --brownfield --local-patterns
|
|
9448
|
+
decantr verify --base-url http://localhost:3000 --evidence
|
|
8880
9449
|
decantr verify --since-baseline
|
|
8881
|
-
decantr codify
|
|
9450
|
+
decantr codify --from-audit
|
|
8882
9451
|
decantr codify --accept
|
|
8883
9452
|
decantr content check --ci --fail-on error
|
|
8884
9453
|
decantr magic "AI chatbot with dark cyber theme \u2014 bold and futuristic"
|
|
@@ -8928,9 +9497,10 @@ ${BOLD7}Examples:${RESET14}
|
|
|
8928
9497
|
${BOLD7}Workflow Model:${RESET14}
|
|
8929
9498
|
${cyan3("Greenfield blueprint")} decantr new my-app --blueprint=X --workflow=greenfield --adoption=decantr-css
|
|
8930
9499
|
${cyan3("Greenfield contract")} decantr init --workflow=greenfield --adoption=contract-only
|
|
8931
|
-
${cyan3("Brownfield adoption")} decantr adopt --
|
|
8932
|
-
${cyan3("
|
|
8933
|
-
${cyan3("
|
|
9500
|
+
${cyan3("Brownfield adoption")} decantr adopt --yes
|
|
9501
|
+
${cyan3("Brownfield monorepo")} decantr adopt --project apps/web --yes
|
|
9502
|
+
${cyan3("Daily LLM work")} decantr task <route> "<change>" -> decantr verify --brownfield --local-patterns
|
|
9503
|
+
${cyan3("Project-owned law")} decantr codify --from-audit -> edit proposal -> decantr codify --accept
|
|
8934
9504
|
${cyan3("Hybrid composition")} decantr add/remove, decantr theme switch, decantr registry, decantr upgrade
|
|
8935
9505
|
|
|
8936
9506
|
${BOLD7}Bootstrap adapters:${RESET14}
|
|
@@ -9003,7 +9573,7 @@ ${BOLD7}Examples:${RESET14}
|
|
|
9003
9573
|
}
|
|
9004
9574
|
function cmdWorkspaceHelp() {
|
|
9005
9575
|
console.log(`
|
|
9006
|
-
${BOLD7}decantr workspace${RESET14} \u2014 Inspect Decantr projects across a monorepo
|
|
9576
|
+
${BOLD7}decantr workspace${RESET14} \u2014 Inspect Decantr projects and app candidates across a monorepo
|
|
9007
9577
|
|
|
9008
9578
|
${BOLD7}Usage:${RESET14}
|
|
9009
9579
|
decantr workspace list [--json]
|
|
@@ -9012,6 +9582,7 @@ ${BOLD7}Usage:${RESET14}
|
|
|
9012
9582
|
|
|
9013
9583
|
${BOLD7}Examples:${RESET14}
|
|
9014
9584
|
decantr workspace list
|
|
9585
|
+
decantr adopt --project apps/web --yes
|
|
9015
9586
|
decantr workspace health
|
|
9016
9587
|
decantr workspace health --json --output .decantr/workspace-health.json
|
|
9017
9588
|
decantr workspace health --changed --since origin/main
|
|
@@ -9111,11 +9682,11 @@ ${BOLD7}Examples:${RESET14}
|
|
|
9111
9682
|
}
|
|
9112
9683
|
function cmdAdoptHelp() {
|
|
9113
9684
|
console.log(`
|
|
9114
|
-
${BOLD7}decantr adopt${RESET14} \u2014 Brownfield one-liner: analyze, attach, verify, and show the
|
|
9685
|
+
${BOLD7}decantr adopt${RESET14} \u2014 Brownfield one-liner: analyze, attach, verify, and show the operating loop
|
|
9115
9686
|
|
|
9116
9687
|
${BOLD7}Usage:${RESET14}
|
|
9117
9688
|
decantr adopt [--project <path>] [--yes] [--dry-run]
|
|
9118
|
-
decantr adopt --base-url <url> [--evidence] [--ci] [--yes]
|
|
9689
|
+
decantr adopt [--project <path>] --base-url <url> [--evidence] [--ci] [--yes]
|
|
9119
9690
|
|
|
9120
9691
|
${BOLD7}Options:${RESET14}
|
|
9121
9692
|
--project App path inside a workspace/monorepo
|
|
@@ -9133,8 +9704,10 @@ ${BOLD7}Options:${RESET14}
|
|
|
9133
9704
|
|
|
9134
9705
|
${BOLD7}Examples:${RESET14}
|
|
9135
9706
|
decantr adopt --yes
|
|
9136
|
-
decantr adopt --
|
|
9707
|
+
decantr adopt --project apps/web --yes
|
|
9708
|
+
decantr adopt --project apps/web --base-url http://localhost:3000 --evidence --yes
|
|
9137
9709
|
decantr adopt --project apps/web --ci --yes
|
|
9710
|
+
decantr codify --from-audit --project apps/web
|
|
9138
9711
|
`);
|
|
9139
9712
|
}
|
|
9140
9713
|
function cmdVerifyHelp() {
|
|
@@ -9151,6 +9724,8 @@ ${BOLD7}Usage:${RESET14}
|
|
|
9151
9724
|
${BOLD7}Examples:${RESET14}
|
|
9152
9725
|
decantr verify
|
|
9153
9726
|
decantr verify --brownfield --local-patterns
|
|
9727
|
+
decantr verify --brownfield --local-patterns --project apps/web
|
|
9728
|
+
decantr verify --brownfield --local-patterns --fail-on warn
|
|
9154
9729
|
decantr verify --base-url http://localhost:3000 --evidence
|
|
9155
9730
|
decantr verify --workspace --changed --since origin/main
|
|
9156
9731
|
decantr verify init-ci --project apps/web
|
|
@@ -9161,25 +9736,27 @@ function cmdTaskHelp() {
|
|
|
9161
9736
|
${BOLD7}decantr task${RESET14} \u2014 Prepare compact route/task context for an AI coding assistant
|
|
9162
9737
|
|
|
9163
9738
|
${BOLD7}Usage:${RESET14}
|
|
9164
|
-
decantr task <route> ["task summary"] [--project <path>] [--json]
|
|
9739
|
+
decantr task <route> ["task summary"] [--project <path>] [--since origin/main] [--json]
|
|
9165
9740
|
|
|
9166
9741
|
${BOLD7}Examples:${RESET14}
|
|
9167
9742
|
decantr task /feed "add saved recipe actions"
|
|
9743
|
+
decantr task /feed "add saved recipe actions" --since origin/main
|
|
9168
9744
|
decantr task /profile --json
|
|
9169
9745
|
`);
|
|
9170
9746
|
}
|
|
9171
9747
|
function cmdCodifyHelp() {
|
|
9172
9748
|
console.log(`
|
|
9173
|
-
${BOLD7}decantr codify${RESET14} \u2014 Propose or accept project-owned Brownfield UI patterns
|
|
9749
|
+
${BOLD7}decantr codify${RESET14} \u2014 Propose or accept project-owned Brownfield UI patterns and rules
|
|
9174
9750
|
|
|
9175
9751
|
${BOLD7}Usage:${RESET14}
|
|
9176
|
-
decantr codify [--project <path>]
|
|
9752
|
+
decantr codify [--from-audit] [--project <path>]
|
|
9177
9753
|
decantr codify --accept [--project <path>]
|
|
9178
9754
|
|
|
9179
9755
|
${BOLD7}Examples:${RESET14}
|
|
9180
9756
|
decantr codify
|
|
9757
|
+
decantr codify --from-audit
|
|
9181
9758
|
decantr codify --accept
|
|
9182
|
-
decantr verify --local-patterns
|
|
9759
|
+
decantr verify --brownfield --local-patterns
|
|
9183
9760
|
`);
|
|
9184
9761
|
}
|
|
9185
9762
|
function cmdContentHelp() {
|
|
@@ -9253,7 +9830,7 @@ async function main() {
|
|
|
9253
9830
|
}
|
|
9254
9831
|
if (command === "--version" || command === "-v" || command === "version") {
|
|
9255
9832
|
try {
|
|
9256
|
-
const here =
|
|
9833
|
+
const here = dirname3(fileURLToPath2(import.meta.url));
|
|
9257
9834
|
const candidates = [join28(here, "..", "package.json"), join28(here, "..", "..", "package.json")];
|
|
9258
9835
|
for (const candidate of candidates) {
|
|
9259
9836
|
if (existsSync27(candidate)) {
|
|
@@ -9436,7 +10013,7 @@ async function main() {
|
|
|
9436
10013
|
cmdStudioHelp();
|
|
9437
10014
|
break;
|
|
9438
10015
|
}
|
|
9439
|
-
const { cmdStudio, parseStudioArgs } = await import("./studio-
|
|
10016
|
+
const { cmdStudio, parseStudioArgs } = await import("./studio-G3YOU5YF.js");
|
|
9440
10017
|
await cmdStudio(process.cwd(), parseStudioArgs(args));
|
|
9441
10018
|
} catch (e) {
|
|
9442
10019
|
console.error(error3(e.message));
|
|
@@ -9450,7 +10027,7 @@ async function main() {
|
|
|
9450
10027
|
cmdWorkspaceHelp();
|
|
9451
10028
|
break;
|
|
9452
10029
|
}
|
|
9453
|
-
const { cmdWorkspace } = await import("./workspace-
|
|
10030
|
+
const { cmdWorkspace } = await import("./workspace-U7J3CJY3.js");
|
|
9454
10031
|
await cmdWorkspace(process.cwd(), args);
|
|
9455
10032
|
} catch (e) {
|
|
9456
10033
|
console.error(error3(e.message));
|
|
@@ -9886,10 +10463,7 @@ async function main() {
|
|
|
9886
10463
|
}
|
|
9887
10464
|
const workspaceInfo = resolveWorkspaceInfo(process.cwd(), projectArg);
|
|
9888
10465
|
if (workspaceInfo.requiresProjectSelection) {
|
|
9889
|
-
|
|
9890
|
-
console.log(
|
|
9891
|
-
dim3(`Use --project=<path>. Candidates: ${workspaceInfo.appCandidates.join(", ")}`)
|
|
9892
|
-
);
|
|
10466
|
+
printWorkspaceProjectSelection(workspaceInfo, "analyze");
|
|
9893
10467
|
process.exitCode = 1;
|
|
9894
10468
|
break;
|
|
9895
10469
|
}
|
|
@@ -9916,6 +10490,11 @@ async function main() {
|
|
|
9916
10490
|
}
|
|
9917
10491
|
}
|
|
9918
10492
|
const workspaceInfo = resolveWorkspaceInfo(process.cwd(), projectArg);
|
|
10493
|
+
if (workspaceInfo.requiresProjectSelection) {
|
|
10494
|
+
printWorkspaceProjectSelection(workspaceInfo, "rules");
|
|
10495
|
+
process.exitCode = 1;
|
|
10496
|
+
break;
|
|
10497
|
+
}
|
|
9919
10498
|
const detected = detectProject(workspaceInfo.appRoot);
|
|
9920
10499
|
if (subcommand === "preview") {
|
|
9921
10500
|
console.log(
|