@decantr/cli 2.11.0 → 2.13.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 +17 -5
- package/dist/bin.js +5 -5
- package/dist/{chunk-5WPRTVNA.js → chunk-3A2DLR47.js} +8 -5
- package/dist/{chunk-ET4BG746.js → chunk-5MZH6XXQ.js} +1424 -429
- package/dist/{chunk-4SZ4SEKT.js → chunk-MNZKOZW7.js} +78 -30
- package/dist/{chunk-IQZCIMQJ.js → chunk-SIDKK73N.js} +24 -2
- package/dist/{chunk-NUNUW5IS.js → chunk-YBSBAJ3E.js} +1 -1
- package/dist/{heal-2UCSPRTK.js → heal-IRIDB7IZ.js} +1 -1
- package/dist/{health-ADHNLMX3.js → health-B4W7UJBZ.js} +2 -2
- package/dist/index.js +5 -5
- package/dist/{studio-GZFCCVSO.js → studio-JOEECLI6.js} +3 -3
- package/dist/{upgrade-7ZYWR2GH.js → upgrade-WBFE6EN7.js} +1 -1
- package/dist/{workspace-YYCRXJNB.js → workspace-7RU77ZZW.js} +3 -3
- package/package.json +4 -4
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
scaffoldProject,
|
|
15
15
|
syncRegistry,
|
|
16
16
|
writeExecutionPackBundleArtifacts
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-SIDKK73N.js";
|
|
18
18
|
import {
|
|
19
19
|
createWorkspaceHealthReport,
|
|
20
20
|
formatWorkspaceHealthMarkdown,
|
|
@@ -22,14 +22,14 @@ import {
|
|
|
22
22
|
listWorkspaceCandidates,
|
|
23
23
|
listWorkspaceProjects,
|
|
24
24
|
shouldFailWorkspaceHealth
|
|
25
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-YBSBAJ3E.js";
|
|
26
26
|
import {
|
|
27
27
|
createProjectHealthReport,
|
|
28
28
|
formatProjectHealthMarkdown,
|
|
29
29
|
formatProjectHealthText,
|
|
30
30
|
resolveWorkspaceInfo,
|
|
31
31
|
shouldFailHealth
|
|
32
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-3A2DLR47.js";
|
|
33
33
|
import {
|
|
34
34
|
buildGuardRegistryContext,
|
|
35
35
|
createDoctrineMap,
|
|
@@ -46,11 +46,11 @@ import {
|
|
|
46
46
|
sendCliCommandTelemetry,
|
|
47
47
|
sendNewProjectCompletedTelemetry,
|
|
48
48
|
writeDoctrineMap
|
|
49
|
-
} from "./chunk-
|
|
49
|
+
} from "./chunk-MNZKOZW7.js";
|
|
50
50
|
|
|
51
51
|
// src/index.ts
|
|
52
|
-
import { existsSync as
|
|
53
|
-
import { basename as basename3, dirname as dirname6, isAbsolute as isAbsolute3, join as
|
|
52
|
+
import { existsSync as existsSync30, mkdirSync as mkdirSync17, readdirSync as readdirSync9, readFileSync as readFileSync23, writeFileSync as writeFileSync20 } from "fs";
|
|
53
|
+
import { basename as basename3, dirname as dirname6, isAbsolute as isAbsolute3, join as join32, relative as relative7, resolve as resolve4 } from "path";
|
|
54
54
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
55
55
|
import { evaluateGuard, isV4 as isV49, validateEssence as validateEssence2 } from "@decantr/essence-spec";
|
|
56
56
|
import {
|
|
@@ -68,7 +68,8 @@ import {
|
|
|
68
68
|
} from "@decantr/registry";
|
|
69
69
|
import {
|
|
70
70
|
auditProject,
|
|
71
|
-
critiqueFile as critiqueProjectFile
|
|
71
|
+
critiqueFile as critiqueProjectFile,
|
|
72
|
+
scanProject as scanProjectReadOnly
|
|
72
73
|
} from "@decantr/verifier";
|
|
73
74
|
|
|
74
75
|
// src/artifacts.ts
|
|
@@ -88,6 +89,7 @@ Commit these when they exist:
|
|
|
88
89
|
- \`.decantr/project.json\` - workflow, adoption mode, detection, and initialization metadata.
|
|
89
90
|
- \`.decantr/local-patterns.json\` - project-owned Brownfield UI patterns accepted by the team.
|
|
90
91
|
- \`.decantr/rules.json\` - project-owned mechanical rules accepted by the team.
|
|
92
|
+
- \`.decantr/style-bridge.json\` - accepted Hybrid style bridge from Decantr intent to project tokens/classes.
|
|
91
93
|
|
|
92
94
|
## Generated Context
|
|
93
95
|
|
|
@@ -3233,8 +3235,8 @@ ${YELLOW2}Next step:${RESET2} Review ${BOLD}${reportDisplayPath}${RESET2}, then
|
|
|
3233
3235
|
}
|
|
3234
3236
|
|
|
3235
3237
|
// src/commands/ci.ts
|
|
3236
|
-
import { existsSync as
|
|
3237
|
-
import { dirname as dirname3, join as
|
|
3238
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync10, readFileSync as readFileSync13, writeFileSync as writeFileSync11 } from "fs";
|
|
3239
|
+
import { dirname as dirname3, join as join17, relative as relative4, resolve } from "path";
|
|
3238
3240
|
|
|
3239
3241
|
// src/local-law.ts
|
|
3240
3242
|
import { execFileSync } from "child_process";
|
|
@@ -3326,6 +3328,22 @@ function createBrownfieldCodifyProposal(input) {
|
|
|
3326
3328
|
componentPaths: evidence.buttonComponents,
|
|
3327
3329
|
decide: "Define primary, secondary, tertiary, destructive, icon-only, disabled, and loading button variants from this app.",
|
|
3328
3330
|
classHints: evidence.buttonClassHints,
|
|
3331
|
+
variants: evidence.buttonVariants,
|
|
3332
|
+
sourceEvidence: evidence.buttonSourceEvidence,
|
|
3333
|
+
confidence: confidenceForEvidence({
|
|
3334
|
+
componentCount: evidence.buttonComponents.length,
|
|
3335
|
+
classHintCount: evidence.buttonClassHints.length,
|
|
3336
|
+
variantCount: evidence.buttonVariants.length,
|
|
3337
|
+
sourceEvidenceCount: evidence.buttonSourceEvidence.length
|
|
3338
|
+
}),
|
|
3339
|
+
enforcement: {
|
|
3340
|
+
level: "warn",
|
|
3341
|
+
status: "proposal",
|
|
3342
|
+
notes: [
|
|
3343
|
+
"Treat as advisory until the team confirms variant names and wrapper paths.",
|
|
3344
|
+
"Raise related rules to error only after raw button usage can be fixed without churn."
|
|
3345
|
+
]
|
|
3346
|
+
},
|
|
3329
3347
|
evidence: evidence.buttonComponents.length ? evidence.buttonComponents : evidence.buttonClassHints.length ? evidence.buttonClassHints : [
|
|
3330
3348
|
"No obvious Button wrapper found yet. Add the project-owned wrapper path before strict enforcement."
|
|
3331
3349
|
],
|
|
@@ -3339,6 +3357,22 @@ function createBrownfieldCodifyProposal(input) {
|
|
|
3339
3357
|
componentPaths: evidence.cardComponents,
|
|
3340
3358
|
decide: "Define the canonical card background, border, radius, shadow, padding, density, and hover treatment.",
|
|
3341
3359
|
classHints: evidence.cardClassHints,
|
|
3360
|
+
variants: evidence.cardVariants,
|
|
3361
|
+
sourceEvidence: evidence.cardSourceEvidence,
|
|
3362
|
+
confidence: confidenceForEvidence({
|
|
3363
|
+
componentCount: evidence.cardComponents.length,
|
|
3364
|
+
classHintCount: evidence.cardClassHints.length,
|
|
3365
|
+
variantCount: evidence.cardVariants.length,
|
|
3366
|
+
sourceEvidenceCount: evidence.cardSourceEvidence.length
|
|
3367
|
+
}),
|
|
3368
|
+
enforcement: {
|
|
3369
|
+
level: "warn",
|
|
3370
|
+
status: "proposal",
|
|
3371
|
+
notes: [
|
|
3372
|
+
"Use this family to converge repeated surface recipes before enforcing card rules.",
|
|
3373
|
+
"Document route-specific card exceptions before asking CI to block them."
|
|
3374
|
+
]
|
|
3375
|
+
},
|
|
3342
3376
|
evidence: evidence.cardComponents.length ? evidence.cardComponents : [
|
|
3343
3377
|
"No obvious Card wrapper found yet. Add the project-owned wrapper path or class recipe."
|
|
3344
3378
|
],
|
|
@@ -3351,6 +3385,24 @@ function createBrownfieldCodifyProposal(input) {
|
|
|
3351
3385
|
appliesTo: ["routes", "layouts", "navigation shells", "scroll containers"],
|
|
3352
3386
|
componentPaths: evidence.shellComponents,
|
|
3353
3387
|
decide: "Define which layout owns max width, gutters, sticky chrome, responsive breakpoints, and scroll containers.",
|
|
3388
|
+
sourceEvidence: collectSourceEvidence(input.projectRoot, sourceFiles, [
|
|
3389
|
+
"layout",
|
|
3390
|
+
"shell",
|
|
3391
|
+
"nav",
|
|
3392
|
+
"sidebar",
|
|
3393
|
+
"container"
|
|
3394
|
+
]),
|
|
3395
|
+
confidence: confidenceForEvidence({
|
|
3396
|
+
componentCount: evidence.shellComponents.length,
|
|
3397
|
+
sourceEvidenceCount: evidence.shellComponents.length
|
|
3398
|
+
}),
|
|
3399
|
+
enforcement: {
|
|
3400
|
+
level: "advisory",
|
|
3401
|
+
status: "proposal",
|
|
3402
|
+
notes: [
|
|
3403
|
+
"Layout ownership is usually reviewed by humans first because route shells can legitimately differ."
|
|
3404
|
+
]
|
|
3405
|
+
},
|
|
3354
3406
|
evidence: evidence.shellComponents.length ? evidence.shellComponents : ["Add root layout, shell, or app frame files that establish route chrome and spacing."],
|
|
3355
3407
|
forbiddenAlternatives: [
|
|
3356
3408
|
"Each page inventing independent max-width, padding, or sticky nav rules."
|
|
@@ -3363,6 +3415,20 @@ function createBrownfieldCodifyProposal(input) {
|
|
|
3363
3415
|
appliesTo: ["inputs", "selects", "textareas", "validation messages", "form actions"],
|
|
3364
3416
|
componentPaths: evidence.formComponents,
|
|
3365
3417
|
decide: "Define input height, label placement, error copy, disabled state, required state, and focus treatment.",
|
|
3418
|
+
variants: evidence.formVariants,
|
|
3419
|
+
sourceEvidence: evidence.formSourceEvidence,
|
|
3420
|
+
confidence: confidenceForEvidence({
|
|
3421
|
+
componentCount: evidence.formComponents.length,
|
|
3422
|
+
variantCount: evidence.formVariants.length,
|
|
3423
|
+
sourceEvidenceCount: evidence.formSourceEvidence.length
|
|
3424
|
+
}),
|
|
3425
|
+
enforcement: {
|
|
3426
|
+
level: "warn",
|
|
3427
|
+
status: "proposal",
|
|
3428
|
+
notes: [
|
|
3429
|
+
"Start by mapping field wrappers, labels, error states, and disabled states from source."
|
|
3430
|
+
]
|
|
3431
|
+
},
|
|
3366
3432
|
evidence: evidence.formComponents.length ? evidence.formComponents : ["Add form field wrapper paths and validation examples."],
|
|
3367
3433
|
forbiddenAlternatives: [
|
|
3368
3434
|
"Unlabeled one-off inputs or validation states that do not match the app standard."
|
|
@@ -3375,6 +3441,20 @@ function createBrownfieldCodifyProposal(input) {
|
|
|
3375
3441
|
appliesTo: ["theme toggles", "mode-specific classes", "brand variants", "tenant variants"],
|
|
3376
3442
|
componentPaths: evidence.themeComponents,
|
|
3377
3443
|
decide: "Document which theme variants exist, where they are toggled, and which tokens/classes are legal per variant.",
|
|
3444
|
+
variants: evidence.themeVariants,
|
|
3445
|
+
sourceEvidence: evidence.themeSourceEvidence,
|
|
3446
|
+
confidence: confidenceForEvidence({
|
|
3447
|
+
componentCount: evidence.themeComponents.length,
|
|
3448
|
+
variantCount: evidence.themeVariants.length,
|
|
3449
|
+
sourceEvidenceCount: evidence.themeSourceEvidence.length
|
|
3450
|
+
}),
|
|
3451
|
+
enforcement: {
|
|
3452
|
+
level: "advisory",
|
|
3453
|
+
status: "proposal",
|
|
3454
|
+
notes: [
|
|
3455
|
+
"Theme variant law should preserve the existing theme provider/tokens before adding any new mode."
|
|
3456
|
+
]
|
|
3457
|
+
},
|
|
3378
3458
|
evidence: evidence.themeComponents.length ? evidence.themeComponents : ["If the app has dark/light or brand variants, add the toggles/providers here."],
|
|
3379
3459
|
forbiddenAlternatives: [
|
|
3380
3460
|
"Component-local theme forks that bypass shared theme providers or tokens."
|
|
@@ -3466,6 +3546,107 @@ function writeBrownfieldCodifyProposal(projectRoot, proposal) {
|
|
|
3466
3546
|
`, "utf-8");
|
|
3467
3547
|
return { patternPath, rulesPath };
|
|
3468
3548
|
}
|
|
3549
|
+
function writeHostedPatternMappingProposal(input) {
|
|
3550
|
+
const decantrDir = join15(input.projectRoot, ".decantr");
|
|
3551
|
+
mkdirSync8(decantrDir, { recursive: true });
|
|
3552
|
+
const existing = readJsonFile(localPatternsProposalPath(input.projectRoot)) ?? readJsonFile(localPatternsPath(input.projectRoot));
|
|
3553
|
+
const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3554
|
+
const patternPack = {
|
|
3555
|
+
version: 2,
|
|
3556
|
+
generatedAt,
|
|
3557
|
+
status: "proposal",
|
|
3558
|
+
source: "decantr codify --map-pattern",
|
|
3559
|
+
purpose: existing?.purpose ?? "Project-owned Brownfield/Hybrid UI law. Review and edit before accepting; Decantr treats this as authoritative only after it is copied to .decantr/local-patterns.json.",
|
|
3560
|
+
hybrid: existing?.hybrid ?? {
|
|
3561
|
+
intent: "This local pattern pack maps Decantr concepts onto project-owned components, tokens, classes, and rules without replacing the app runtime.",
|
|
3562
|
+
authorityPrecedence: [
|
|
3563
|
+
"existing production source",
|
|
3564
|
+
"accepted local patterns and rules",
|
|
3565
|
+
"Decantr Essence V4 contract",
|
|
3566
|
+
"hosted registry patterns and execution packs as optional guidance"
|
|
3567
|
+
],
|
|
3568
|
+
hostedPatternMapping: "Hosted registry patterns are advisory until mapped to project-owned component paths, token recipes, class recipes, and explicit exceptions."
|
|
3569
|
+
},
|
|
3570
|
+
patterns: Array.isArray(existing?.patterns) ? [...existing.patterns] : [],
|
|
3571
|
+
starterRules: Array.isArray(existing?.starterRules) ? existing.starterRules : [],
|
|
3572
|
+
nextSteps: Array.isArray(existing?.nextSteps) ? existing.nextSteps : [
|
|
3573
|
+
"Review this proposal with the team.",
|
|
3574
|
+
"Add project-owned component paths, token hints, class recipes, and exceptions.",
|
|
3575
|
+
"Run decantr codify --accept after review.",
|
|
3576
|
+
"Use decantr task <route> before LLM edits so local law appears in task context."
|
|
3577
|
+
]
|
|
3578
|
+
};
|
|
3579
|
+
const slug = input.hostedPattern.slug.trim();
|
|
3580
|
+
const existingIds = new Set(
|
|
3581
|
+
(patternPack.patterns ?? []).map((pattern) => typeof pattern.id === "string" ? pattern.id : "").filter(Boolean)
|
|
3582
|
+
);
|
|
3583
|
+
const localPatternId = existingIds.has(slug) ? `${slug}-registry-map` : slug;
|
|
3584
|
+
const mappedPattern = {
|
|
3585
|
+
id: localPatternId,
|
|
3586
|
+
label: input.hostedPattern.name ? `${input.hostedPattern.name} registry mapping` : `${slug} registry mapping`,
|
|
3587
|
+
role: "Hosted registry guidance mapped into project-owned Hybrid law",
|
|
3588
|
+
appliesTo: [
|
|
3589
|
+
...input.hostedPattern.components ?? [],
|
|
3590
|
+
...input.hostedPattern.interactions ?? [],
|
|
3591
|
+
...input.hostedPattern.tags ?? []
|
|
3592
|
+
].slice(0, 24),
|
|
3593
|
+
componentPaths: [],
|
|
3594
|
+
tokenHints: [],
|
|
3595
|
+
classHints: [],
|
|
3596
|
+
decide: `Map hosted pattern "${slug}" into this app's existing components, tokens, classes, and exceptions before treating it as enforceable.`,
|
|
3597
|
+
hostedPatternRefs: [input.hostedPattern],
|
|
3598
|
+
confidence: {
|
|
3599
|
+
tier: "low",
|
|
3600
|
+
score: 0.25,
|
|
3601
|
+
rationale: [
|
|
3602
|
+
"A hosted registry pattern was selected intentionally.",
|
|
3603
|
+
"No project-owned component path or token recipe has been mapped yet."
|
|
3604
|
+
]
|
|
3605
|
+
},
|
|
3606
|
+
enforcement: {
|
|
3607
|
+
level: "advisory",
|
|
3608
|
+
status: "needs-mapping",
|
|
3609
|
+
notes: [
|
|
3610
|
+
"This proposal does not change source files.",
|
|
3611
|
+
"Do not enforce this hosted pattern until project-owned implementation details are filled in.",
|
|
3612
|
+
"Use accepted local rules, ESLint, Biome, Storybook, or visual regression for deterministic blocking checks."
|
|
3613
|
+
]
|
|
3614
|
+
},
|
|
3615
|
+
evidence: [
|
|
3616
|
+
`Hosted pattern ${input.hostedPattern.source}/${slug}${input.hostedPattern.description ? `: ${input.hostedPattern.description}` : ""}`,
|
|
3617
|
+
input.hostedPattern.visualBrief ? `Visual brief: ${input.hostedPattern.visualBrief}` : "Visual brief not provided by the hosted pattern."
|
|
3618
|
+
],
|
|
3619
|
+
evidenceToCollect: [
|
|
3620
|
+
"Project-owned component path(s) that implement this pattern.",
|
|
3621
|
+
"Allowed token/class recipe(s) for this app.",
|
|
3622
|
+
"Variant names and states that are legal in this project.",
|
|
3623
|
+
"Explicit exceptions where this hosted pattern should not apply."
|
|
3624
|
+
],
|
|
3625
|
+
forbiddenAlternatives: [
|
|
3626
|
+
"Replacing existing app components solely because a hosted pattern exists.",
|
|
3627
|
+
"Treating hosted registry guidance as enforceable without accepted local law."
|
|
3628
|
+
]
|
|
3629
|
+
};
|
|
3630
|
+
const index = (patternPack.patterns ?? []).findIndex((pattern) => pattern.id === localPatternId);
|
|
3631
|
+
const replacedExisting = index >= 0;
|
|
3632
|
+
if (replacedExisting) {
|
|
3633
|
+
patternPack.patterns[index] = mappedPattern;
|
|
3634
|
+
} else {
|
|
3635
|
+
patternPack.patterns = [...patternPack.patterns ?? [], mappedPattern];
|
|
3636
|
+
}
|
|
3637
|
+
patternPack.nextSteps = [
|
|
3638
|
+
.../* @__PURE__ */ new Set([
|
|
3639
|
+
...patternPack.nextSteps ?? [],
|
|
3640
|
+
"Fill the hosted mapping with project-owned component paths, token/class recipes, variants, and exceptions.",
|
|
3641
|
+
"Run decantr codify --accept only after the mapping reflects the existing app.",
|
|
3642
|
+
"Use decantr verify --brownfield --local-patterns after edits to keep mapped local law visible."
|
|
3643
|
+
])
|
|
3644
|
+
];
|
|
3645
|
+
const patternPath = localPatternsProposalPath(input.projectRoot);
|
|
3646
|
+
writeFileSync9(patternPath, `${JSON.stringify(patternPack, null, 2)}
|
|
3647
|
+
`, "utf-8");
|
|
3648
|
+
return { patternPath, localPatternId, replacedExisting };
|
|
3649
|
+
}
|
|
3469
3650
|
function acceptBrownfieldLocalLaw(projectRoot) {
|
|
3470
3651
|
const patternProposal = readJsonFile(localPatternsProposalPath(projectRoot));
|
|
3471
3652
|
const ruleProposal = readJsonFile(localRulesProposalPath(projectRoot));
|
|
@@ -3508,11 +3689,17 @@ function validateLocalLaw(projectRoot) {
|
|
|
3508
3689
|
const paths = Array.isArray(pattern.componentPaths) ? pattern.componentPaths : [];
|
|
3509
3690
|
const evidence = Array.isArray(pattern.evidence) ? pattern.evidence : [];
|
|
3510
3691
|
const todoEvidence = Array.isArray(pattern.evidenceToCollect) ? pattern.evidenceToCollect : [];
|
|
3692
|
+
const hostedRefs = Array.isArray(pattern.hostedPatternRefs) ? pattern.hostedPatternRefs : [];
|
|
3511
3693
|
if (id && paths.length === 0 && evidence.length === 0 && todoEvidence.length > 0) {
|
|
3512
3694
|
warnings.push(
|
|
3513
3695
|
`Local pattern ${id} still reads like a TODO; add concrete component paths or evidence.`
|
|
3514
3696
|
);
|
|
3515
3697
|
}
|
|
3698
|
+
if (id && hostedRefs.length > 0 && paths.length === 0) {
|
|
3699
|
+
warnings.push(
|
|
3700
|
+
`Local pattern ${id} maps hosted registry guidance but has no project-owned component path yet.`
|
|
3701
|
+
);
|
|
3702
|
+
}
|
|
3516
3703
|
}
|
|
3517
3704
|
}
|
|
3518
3705
|
if (ruleManifest && !Array.isArray(ruleManifest.rules)) {
|
|
@@ -3534,7 +3721,10 @@ function createLocalLawTaskSummary(projectRoot) {
|
|
|
3534
3721
|
const patterns = (patternPack?.patterns ?? []).map((pattern) => ({
|
|
3535
3722
|
id: typeof pattern.id === "string" ? pattern.id : "unknown",
|
|
3536
3723
|
role: typeof pattern.role === "string" ? pattern.role : null,
|
|
3537
|
-
componentPaths: Array.isArray(pattern.componentPaths) ? pattern.componentPaths.filter((entry) => typeof entry === "string") : []
|
|
3724
|
+
componentPaths: Array.isArray(pattern.componentPaths) ? pattern.componentPaths.filter((entry) => typeof entry === "string") : [],
|
|
3725
|
+
confidenceTier: typeof pattern.confidence === "object" && pattern.confidence !== null && typeof pattern.confidence.tier === "string" ? pattern.confidence.tier : null,
|
|
3726
|
+
enforcementLevel: typeof pattern.enforcement === "object" && pattern.enforcement !== null && typeof pattern.enforcement.level === "string" ? pattern.enforcement.level : null,
|
|
3727
|
+
hostedPatternRefs: Array.isArray(pattern.hostedPatternRefs) ? pattern.hostedPatternRefs.map((ref) => typeof ref?.slug === "string" ? ref.slug : null).filter((slug) => Boolean(slug)) : []
|
|
3538
3728
|
}));
|
|
3539
3729
|
const rules = (ruleManifest?.rules ?? []).map((rule) => ({
|
|
3540
3730
|
id: rule.id,
|
|
@@ -3652,6 +3842,50 @@ function summarizeSourceEvidence(projectRoot, files) {
|
|
|
3652
3842
|
formComponents: byName(["input", "field", "form", "select", "textarea"]),
|
|
3653
3843
|
shellComponents,
|
|
3654
3844
|
themeComponents,
|
|
3845
|
+
buttonVariants: detectVariants(projectRoot, files, {
|
|
3846
|
+
family: "button",
|
|
3847
|
+
terms: ["primary", "secondary", "tertiary", "ghost", "link", "destructive", "icon"]
|
|
3848
|
+
}),
|
|
3849
|
+
cardVariants: detectVariants(projectRoot, files, {
|
|
3850
|
+
family: "surface",
|
|
3851
|
+
terms: ["card", "panel", "surface", "tile", "elevated", "interactive", "glass"]
|
|
3852
|
+
}),
|
|
3853
|
+
formVariants: detectVariants(projectRoot, files, {
|
|
3854
|
+
family: "form",
|
|
3855
|
+
terms: ["error", "invalid", "disabled", "required", "success", "helper"]
|
|
3856
|
+
}),
|
|
3857
|
+
themeVariants: detectVariants(projectRoot, files, {
|
|
3858
|
+
family: "theme",
|
|
3859
|
+
terms: ["dark", "light", "brand", "tenant", "theme", "mode", "density"]
|
|
3860
|
+
}),
|
|
3861
|
+
buttonSourceEvidence: collectSourceEvidence(projectRoot, files, [
|
|
3862
|
+
"button",
|
|
3863
|
+
"primary",
|
|
3864
|
+
"secondary",
|
|
3865
|
+
"tertiary",
|
|
3866
|
+
"destructive"
|
|
3867
|
+
]),
|
|
3868
|
+
cardSourceEvidence: collectSourceEvidence(projectRoot, files, [
|
|
3869
|
+
"card",
|
|
3870
|
+
"panel",
|
|
3871
|
+
"surface",
|
|
3872
|
+
"rounded",
|
|
3873
|
+
"shadow"
|
|
3874
|
+
]),
|
|
3875
|
+
formSourceEvidence: collectSourceEvidence(projectRoot, files, [
|
|
3876
|
+
"input",
|
|
3877
|
+
"label",
|
|
3878
|
+
"error",
|
|
3879
|
+
"field",
|
|
3880
|
+
"select"
|
|
3881
|
+
]),
|
|
3882
|
+
themeSourceEvidence: collectSourceEvidence(projectRoot, files, [
|
|
3883
|
+
"theme",
|
|
3884
|
+
"dark",
|
|
3885
|
+
"light",
|
|
3886
|
+
"brand",
|
|
3887
|
+
"tenant"
|
|
3888
|
+
]),
|
|
3655
3889
|
buttonClassHints: collectClassHints(projectRoot, files, [
|
|
3656
3890
|
"button",
|
|
3657
3891
|
"btn",
|
|
@@ -3665,6 +3899,70 @@ function summarizeSourceEvidence(projectRoot, files) {
|
|
|
3665
3899
|
cardClassHints: collectClassHints(projectRoot, files, ["card", "panel", "surface", "tile"])
|
|
3666
3900
|
};
|
|
3667
3901
|
}
|
|
3902
|
+
function detectVariants(projectRoot, files, input) {
|
|
3903
|
+
const variants = /* @__PURE__ */ new Map();
|
|
3904
|
+
const termPattern = new RegExp(
|
|
3905
|
+
`\\b(${input.terms.map(escapeRegExp).join("|")})(?:[-_:][a-z0-9-]+)?\\b`,
|
|
3906
|
+
"gi"
|
|
3907
|
+
);
|
|
3908
|
+
for (const file of files) {
|
|
3909
|
+
if (!UI_TEMPLATE_EXTENSIONS.has(extname(file.absolute))) continue;
|
|
3910
|
+
const content = readFileSync11(join15(projectRoot, file.relative), "utf-8");
|
|
3911
|
+
for (const match of content.matchAll(termPattern)) {
|
|
3912
|
+
const raw = (match[0] ?? "").toLowerCase();
|
|
3913
|
+
const id = raw.replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
3914
|
+
if (!id || id.length < 3) continue;
|
|
3915
|
+
const position = lineColumnAt(content, match.index ?? 0);
|
|
3916
|
+
const line = lineAt(content, position.line).trim().slice(0, 160);
|
|
3917
|
+
const entry = variants.get(id) ?? { evidence: /* @__PURE__ */ new Set(), count: 0 };
|
|
3918
|
+
entry.count += 1;
|
|
3919
|
+
entry.evidence.add(`${file.relative}:${position.line} ${line}`);
|
|
3920
|
+
variants.set(id, entry);
|
|
3921
|
+
}
|
|
3922
|
+
}
|
|
3923
|
+
return [...variants.entries()].sort((a, b) => b[1].count - a[1].count || a[0].localeCompare(b[0])).slice(0, 8).map(([id, value]) => ({
|
|
3924
|
+
id,
|
|
3925
|
+
label: `${input.family} ${id}`,
|
|
3926
|
+
evidence: [...value.evidence].slice(0, 4),
|
|
3927
|
+
confidence: value.count >= 4 ? "high" : value.count >= 2 ? "medium" : "low"
|
|
3928
|
+
}));
|
|
3929
|
+
}
|
|
3930
|
+
function collectSourceEvidence(projectRoot, files, terms) {
|
|
3931
|
+
const results = [];
|
|
3932
|
+
const termPattern = new RegExp(terms.map(escapeRegExp).join("|"), "i");
|
|
3933
|
+
for (const file of files) {
|
|
3934
|
+
if (!UI_TEMPLATE_EXTENSIONS.has(extname(file.absolute))) continue;
|
|
3935
|
+
const content = readFileSync11(join15(projectRoot, file.relative), "utf-8");
|
|
3936
|
+
const lines = content.split(/\r?\n/);
|
|
3937
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
3938
|
+
const line = lines[index];
|
|
3939
|
+
if (!termPattern.test(line)) continue;
|
|
3940
|
+
const signals = terms.filter((term) => line.toLowerCase().includes(term.toLowerCase()));
|
|
3941
|
+
results.push({
|
|
3942
|
+
file: file.relative,
|
|
3943
|
+
line: index + 1,
|
|
3944
|
+
excerpt: line.trim().slice(0, 180),
|
|
3945
|
+
signals: signals.slice(0, 6)
|
|
3946
|
+
});
|
|
3947
|
+
if (results.length >= 16) return results;
|
|
3948
|
+
break;
|
|
3949
|
+
}
|
|
3950
|
+
}
|
|
3951
|
+
return results;
|
|
3952
|
+
}
|
|
3953
|
+
function confidenceForEvidence(input) {
|
|
3954
|
+
const score = Math.min(
|
|
3955
|
+
1,
|
|
3956
|
+
input.componentCount * 0.28 + (input.classHintCount ?? 0) * 0.08 + (input.variantCount ?? 0) * 0.08 + (input.sourceEvidenceCount ?? 0) * 0.05
|
|
3957
|
+
);
|
|
3958
|
+
const tier = score >= 0.7 ? "high" : score >= 0.4 ? "medium" : "low";
|
|
3959
|
+
const rationale = [
|
|
3960
|
+
input.componentCount > 0 ? `${input.componentCount} likely component path(s) detected` : "no likely component wrapper detected yet",
|
|
3961
|
+
(input.classHintCount ?? 0) > 0 ? `${input.classHintCount} class recipe hint(s) detected` : "no strong class recipe hints detected yet",
|
|
3962
|
+
(input.variantCount ?? 0) > 0 ? `${input.variantCount} possible variant signal(s) detected` : "variant names still need team confirmation"
|
|
3963
|
+
];
|
|
3964
|
+
return { tier, score: Number(score.toFixed(2)), rationale };
|
|
3965
|
+
}
|
|
3668
3966
|
function collectClassHints(projectRoot, files, terms) {
|
|
3669
3967
|
const hints = /* @__PURE__ */ new Map();
|
|
3670
3968
|
const wantsButton = terms.some(
|
|
@@ -3677,10 +3975,25 @@ function collectClassHints(projectRoot, files, terms) {
|
|
|
3677
3975
|
if (!UI_TEMPLATE_EXTENSIONS.has(extname(file.absolute))) continue;
|
|
3678
3976
|
const content = readFileSync11(join15(projectRoot, file.relative), "utf-8");
|
|
3679
3977
|
if (!terms.some((term) => content.toLowerCase().includes(term))) continue;
|
|
3978
|
+
if (wantsButton) {
|
|
3979
|
+
const openingTags = content.matchAll(
|
|
3980
|
+
/<([A-Za-z][\w.:/-]*)\b[^>]*\bclass(?:Name)?\s*=\s*["'`]([^"'`]+)["'`][^>]*>/g
|
|
3981
|
+
);
|
|
3982
|
+
for (const match of openingTags) {
|
|
3983
|
+
const tag = match[1];
|
|
3984
|
+
const value = match[2].trim();
|
|
3985
|
+
const tagLooksInteractive = /^(button|a|Link)$/i.test(tag) || /(^|\.)(Button|IconButton|LinkButton|Action)$/i.test(tag);
|
|
3986
|
+
const fileLooksInteractive = /button|action|link/i.test(basename2(file.relative));
|
|
3987
|
+
if (!tagLooksInteractive && !fileLooksInteractive) continue;
|
|
3988
|
+
if (!buttonSignal.test(value) && !fileLooksInteractive) continue;
|
|
3989
|
+
hints.set(value, (hints.get(value) ?? 0) + 1);
|
|
3990
|
+
}
|
|
3991
|
+
continue;
|
|
3992
|
+
}
|
|
3680
3993
|
const matches = content.matchAll(/\bclass(?:Name)?\s*=\s*["'`]([^"'`]+)["'`]/g);
|
|
3681
3994
|
for (const match of matches) {
|
|
3682
3995
|
const value = match[1].trim();
|
|
3683
|
-
const keep =
|
|
3996
|
+
const keep = wantsSurface && surfaceSignal.test(value) || !wantsButton && !wantsSurface && (buttonSignal.test(value) || surfaceSignal.test(value));
|
|
3684
3997
|
if (!keep) continue;
|
|
3685
3998
|
hints.set(value, (hints.get(value) ?? 0) + 1);
|
|
3686
3999
|
}
|
|
@@ -3744,10 +4057,284 @@ function lineColumnAt(contents, index) {
|
|
|
3744
4057
|
column: lines[lines.length - 1].length + 1
|
|
3745
4058
|
};
|
|
3746
4059
|
}
|
|
4060
|
+
function escapeRegExp(value) {
|
|
4061
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4062
|
+
}
|
|
3747
4063
|
function lineAt(contents, line) {
|
|
3748
4064
|
return contents.split(/\r?\n/)[line - 1] ?? "";
|
|
3749
4065
|
}
|
|
3750
4066
|
|
|
4067
|
+
// src/style-bridge.ts
|
|
4068
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync9, readFileSync as readFileSync12, writeFileSync as writeFileSync10 } from "fs";
|
|
4069
|
+
import { join as join16 } from "path";
|
|
4070
|
+
function styleBridgeProposalPath(projectRoot) {
|
|
4071
|
+
return join16(projectRoot, ".decantr", "style-bridge.proposal.json");
|
|
4072
|
+
}
|
|
4073
|
+
function styleBridgePath(projectRoot) {
|
|
4074
|
+
return join16(projectRoot, ".decantr", "style-bridge.json");
|
|
4075
|
+
}
|
|
4076
|
+
function readJsonFile2(path) {
|
|
4077
|
+
if (!existsSync14(path)) return null;
|
|
4078
|
+
try {
|
|
4079
|
+
return JSON.parse(readFileSync12(path, "utf-8"));
|
|
4080
|
+
} catch {
|
|
4081
|
+
return null;
|
|
4082
|
+
}
|
|
4083
|
+
}
|
|
4084
|
+
function writeJsonFile(path, value) {
|
|
4085
|
+
writeFileSync10(path, `${JSON.stringify(value, null, 2)}
|
|
4086
|
+
`, "utf-8");
|
|
4087
|
+
}
|
|
4088
|
+
function readStyleBridge(projectRoot) {
|
|
4089
|
+
return readJsonFile2(styleBridgePath(projectRoot));
|
|
4090
|
+
}
|
|
4091
|
+
function readStyleBridgeProposal(projectRoot) {
|
|
4092
|
+
return readJsonFile2(styleBridgeProposalPath(projectRoot));
|
|
4093
|
+
}
|
|
4094
|
+
function stringArray(value) {
|
|
4095
|
+
return Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
|
|
4096
|
+
}
|
|
4097
|
+
function readThemeInventory(projectRoot) {
|
|
4098
|
+
const inventory = readJsonFile2(join16(projectRoot, ".decantr", "theme-inventory.json"));
|
|
4099
|
+
return {
|
|
4100
|
+
modes: stringArray(inventory?.modes),
|
|
4101
|
+
variantIds: Array.isArray(inventory?.variants) ? inventory.variants.map((variant) => variant.id).filter((id) => typeof id === "string") : [],
|
|
4102
|
+
darkModeDetected: typeof inventory?.darkModeDetected === "boolean" ? inventory.darkModeDetected : null
|
|
4103
|
+
};
|
|
4104
|
+
}
|
|
4105
|
+
function tokenHints(styling, terms) {
|
|
4106
|
+
return styling.cssVariables.filter((name) => terms.test(name)).slice(0, 12);
|
|
4107
|
+
}
|
|
4108
|
+
function readProjectPatternPack(projectRoot) {
|
|
4109
|
+
return readLocalPatternPack(projectRoot) ?? readJsonFile2(
|
|
4110
|
+
join16(projectRoot, ".decantr", "local-patterns.proposal.json")
|
|
4111
|
+
);
|
|
4112
|
+
}
|
|
4113
|
+
function classHintsForPattern(projectRoot, ids) {
|
|
4114
|
+
const pack = readProjectPatternPack(projectRoot);
|
|
4115
|
+
const classes = /* @__PURE__ */ new Set();
|
|
4116
|
+
for (const pattern of pack?.patterns ?? []) {
|
|
4117
|
+
if (!ids.includes(String(pattern.id))) continue;
|
|
4118
|
+
for (const hint of pattern.classHints ?? []) classes.add(hint);
|
|
4119
|
+
}
|
|
4120
|
+
return [...classes].slice(0, 12);
|
|
4121
|
+
}
|
|
4122
|
+
function sourceEvidence(projectRoot, ids) {
|
|
4123
|
+
const pack = readProjectPatternPack(projectRoot);
|
|
4124
|
+
const evidence = /* @__PURE__ */ new Set();
|
|
4125
|
+
for (const pattern of pack?.patterns ?? []) {
|
|
4126
|
+
if (!ids.includes(String(pattern.id))) continue;
|
|
4127
|
+
for (const path of pattern.componentPaths ?? []) evidence.add(path);
|
|
4128
|
+
for (const item of pattern.evidence ?? []) evidence.add(item);
|
|
4129
|
+
}
|
|
4130
|
+
return [...evidence].slice(0, 12);
|
|
4131
|
+
}
|
|
4132
|
+
function colorTokenNames(styling) {
|
|
4133
|
+
const names = new Set(Object.keys(styling.colors ?? {}));
|
|
4134
|
+
for (const variable of styling.cssVariables) {
|
|
4135
|
+
if (/color|bg|background|surface|text|foreground|border|primary|secondary|accent|muted/i.test(variable)) {
|
|
4136
|
+
names.add(variable);
|
|
4137
|
+
}
|
|
4138
|
+
}
|
|
4139
|
+
return [...names].slice(0, 40);
|
|
4140
|
+
}
|
|
4141
|
+
function createStyleBridgeProposal(input) {
|
|
4142
|
+
const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4143
|
+
const theme = readThemeInventory(input.projectRoot);
|
|
4144
|
+
const routeCount = input.essence && typeof input.essence === "object" && "blueprint" in input.essence ? Object.keys(input.essence.blueprint?.routes ?? {}).length : 0;
|
|
4145
|
+
const target = input.essence && typeof input.essence === "object" && "meta" in input.essence ? String(input.essence.meta?.target ?? "") || null : null;
|
|
4146
|
+
const darkModeDetected = theme.darkModeDetected ?? input.styling.darkMode;
|
|
4147
|
+
const themeModes = theme.modes.length > 0 ? theme.modes : darkModeDetected ? ["base", "dark"] : ["base"];
|
|
4148
|
+
const themeVariantIds = theme.variantIds.length > 0 ? theme.variantIds : themeModes.filter((mode) => mode !== "base");
|
|
4149
|
+
return {
|
|
4150
|
+
version: 1,
|
|
4151
|
+
status: "proposal",
|
|
4152
|
+
generatedAt,
|
|
4153
|
+
source: "decantr codify --style-bridge",
|
|
4154
|
+
purpose: "Project-owned Hybrid style bridge. It maps Decantr design intent to the existing app styling system without installing Decantr CSS or taking over source.",
|
|
4155
|
+
adoption: {
|
|
4156
|
+
mode: "style-bridge",
|
|
4157
|
+
workflowMode: "brownfield-attach",
|
|
4158
|
+
sourceAuthority: "Existing production source stays authoritative.",
|
|
4159
|
+
styleAuthority: "Use these mappings to translate Decantr concepts into project-owned tokens, classes, and components.",
|
|
4160
|
+
notRuntimeTakeover: true,
|
|
4161
|
+
authorityPrecedence: [
|
|
4162
|
+
"existing production source",
|
|
4163
|
+
"accepted style bridge",
|
|
4164
|
+
"accepted local patterns and rules",
|
|
4165
|
+
"Essence V4 contract",
|
|
4166
|
+
"hosted registry patterns and execution packs as optional guidance"
|
|
4167
|
+
]
|
|
4168
|
+
},
|
|
4169
|
+
project: {
|
|
4170
|
+
framework: input.detected.framework,
|
|
4171
|
+
packageManager: input.detected.packageManager,
|
|
4172
|
+
target,
|
|
4173
|
+
routeCount
|
|
4174
|
+
},
|
|
4175
|
+
styling: {
|
|
4176
|
+
approach: input.styling.approach,
|
|
4177
|
+
configFile: input.styling.configFile ?? null,
|
|
4178
|
+
darkModeDetected,
|
|
4179
|
+
cssVariables: input.styling.cssVariables.slice(0, 80),
|
|
4180
|
+
colorTokenNames: colorTokenNames(input.styling),
|
|
4181
|
+
themeModes,
|
|
4182
|
+
themeVariantIds
|
|
4183
|
+
},
|
|
4184
|
+
mappings: [
|
|
4185
|
+
{
|
|
4186
|
+
id: "surface",
|
|
4187
|
+
label: "Surfaces and cards",
|
|
4188
|
+
decantrIntent: "surface background, card treatment, border, radius, depth, and hover state",
|
|
4189
|
+
projectAuthority: "Use the app card/surface primitives, tokens, and accepted local law.",
|
|
4190
|
+
tokenHints: tokenHints(input.styling, /surface|card|panel|bg|background|border|shadow|radius/i),
|
|
4191
|
+
classHints: classHintsForPattern(input.projectRoot, ["surface-card"]),
|
|
4192
|
+
sourceEvidence: sourceEvidence(input.projectRoot, ["surface-card"]),
|
|
4193
|
+
guardrails: [
|
|
4194
|
+
"Do not invent a new card color/radius/shadow recipe without updating local law.",
|
|
4195
|
+
"Do not add Decantr CSS d-* classes unless adoption mode changes to decantr-css."
|
|
4196
|
+
]
|
|
4197
|
+
},
|
|
4198
|
+
{
|
|
4199
|
+
id: "action",
|
|
4200
|
+
label: "Actions and buttons",
|
|
4201
|
+
decantrIntent: "primary, secondary, tertiary, destructive, icon-only, loading, and disabled action states",
|
|
4202
|
+
projectAuthority: "Use the app button/action primitives and local variant names.",
|
|
4203
|
+
tokenHints: tokenHints(input.styling, /primary|secondary|accent|danger|error|destructive|focus/i),
|
|
4204
|
+
classHints: classHintsForPattern(input.projectRoot, ["button"]),
|
|
4205
|
+
sourceEvidence: sourceEvidence(input.projectRoot, ["button"]),
|
|
4206
|
+
guardrails: [
|
|
4207
|
+
"Map any new action style to primary/secondary/tertiary/destructive/icon before coding.",
|
|
4208
|
+
"Avoid raw button markup when a project-owned primitive exists."
|
|
4209
|
+
]
|
|
4210
|
+
},
|
|
4211
|
+
{
|
|
4212
|
+
id: "focus-accessibility",
|
|
4213
|
+
label: "Focus and accessibility",
|
|
4214
|
+
decantrIntent: "visible focus, keyboard clarity, contrast, and reduced-motion safety",
|
|
4215
|
+
projectAuthority: "Use the app accessibility classes, focus tokens, and framework conventions.",
|
|
4216
|
+
tokenHints: tokenHints(input.styling, /focus|ring|outline|contrast|motion|duration/i),
|
|
4217
|
+
classHints: [],
|
|
4218
|
+
sourceEvidence: [],
|
|
4219
|
+
guardrails: [
|
|
4220
|
+
"Every new interactive control needs visible focus evidence.",
|
|
4221
|
+
"Motion should honor prefers-reduced-motion or an existing app motion helper."
|
|
4222
|
+
]
|
|
4223
|
+
},
|
|
4224
|
+
{
|
|
4225
|
+
id: "layout-density",
|
|
4226
|
+
label: "Layout density and spacing",
|
|
4227
|
+
decantrIntent: "route gutters, section gaps, component padding, density, and responsive rhythm",
|
|
4228
|
+
projectAuthority: "Use existing layout wrappers, shell primitives, and spacing tokens/classes.",
|
|
4229
|
+
tokenHints: tokenHints(input.styling, /space|spacing|gap|gutter|container|radius/i),
|
|
4230
|
+
classHints: classHintsForPattern(input.projectRoot, ["page-shell"]),
|
|
4231
|
+
sourceEvidence: sourceEvidence(input.projectRoot, ["page-shell"]),
|
|
4232
|
+
guardrails: [
|
|
4233
|
+
"Do not let each page invent independent max-width, gutters, or scroll ownership.",
|
|
4234
|
+
"Use the detected shell/layout authority before adding a new wrapper."
|
|
4235
|
+
]
|
|
4236
|
+
},
|
|
4237
|
+
{
|
|
4238
|
+
id: "theme-variant",
|
|
4239
|
+
label: "Theme variants",
|
|
4240
|
+
decantrIntent: "light, dark, brand, tenant, seasonal, and density variants",
|
|
4241
|
+
projectAuthority: "Use the app theme provider, data attributes, CSS variables, or Tailwind mode strategy.",
|
|
4242
|
+
tokenHints: tokenHints(input.styling, /theme|dark|light|brand|tenant|mode|color/i),
|
|
4243
|
+
classHints: classHintsForPattern(input.projectRoot, ["theme-variant"]),
|
|
4244
|
+
sourceEvidence: sourceEvidence(input.projectRoot, ["theme-variant"]),
|
|
4245
|
+
guardrails: [
|
|
4246
|
+
"Keep theme modes documented in .decantr/theme-inventory.json and this bridge.",
|
|
4247
|
+
"Do not treat theme switcher container classes as standalone theme variants."
|
|
4248
|
+
]
|
|
4249
|
+
}
|
|
4250
|
+
],
|
|
4251
|
+
rules: [
|
|
4252
|
+
"The bridge is advisory until accepted; after acceptance, task/doctor/CI should surface it as Hybrid authority.",
|
|
4253
|
+
"The bridge does not make hosted registry patterns enforceable. Map hosted concepts into local law first.",
|
|
4254
|
+
"Keep deterministic blocking checks in .decantr/rules.json, ESLint, Biome, tests, or visual regression."
|
|
4255
|
+
],
|
|
4256
|
+
nextSteps: [
|
|
4257
|
+
"Review token and class hints. Replace generic hints with the exact project tokens/classes the team owns.",
|
|
4258
|
+
"Run decantr codify --accept to promote the proposal to .decantr/style-bridge.json.",
|
|
4259
|
+
"Use decantr task <route> before LLM edits so the style bridge appears in task context.",
|
|
4260
|
+
"Run decantr ci or decantr verify after edits to keep local law and Project Health visible."
|
|
4261
|
+
]
|
|
4262
|
+
};
|
|
4263
|
+
}
|
|
4264
|
+
function writeStyleBridgeProposal(projectRoot, proposal) {
|
|
4265
|
+
const decantrDir = join16(projectRoot, ".decantr");
|
|
4266
|
+
mkdirSync9(decantrDir, { recursive: true });
|
|
4267
|
+
const path = styleBridgeProposalPath(projectRoot);
|
|
4268
|
+
writeJsonFile(path, proposal);
|
|
4269
|
+
return path;
|
|
4270
|
+
}
|
|
4271
|
+
function acceptStyleBridge(projectRoot) {
|
|
4272
|
+
const proposal = readStyleBridgeProposal(projectRoot);
|
|
4273
|
+
if (!proposal) return null;
|
|
4274
|
+
const accepted = {
|
|
4275
|
+
...proposal,
|
|
4276
|
+
status: "accepted",
|
|
4277
|
+
acceptedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4278
|
+
};
|
|
4279
|
+
const path = styleBridgePath(projectRoot);
|
|
4280
|
+
writeJsonFile(path, accepted);
|
|
4281
|
+
const projectJsonPath = join16(projectRoot, ".decantr", "project.json");
|
|
4282
|
+
const projectJson = readJsonFile2(projectJsonPath) ?? {};
|
|
4283
|
+
const initialized = typeof projectJson.initialized === "object" && projectJson.initialized !== null ? projectJson.initialized : {};
|
|
4284
|
+
writeJsonFile(projectJsonPath, {
|
|
4285
|
+
...projectJson,
|
|
4286
|
+
initialized: {
|
|
4287
|
+
...initialized,
|
|
4288
|
+
workflowMode: initialized.workflowMode ?? "brownfield-attach",
|
|
4289
|
+
adoptionMode: "style-bridge"
|
|
4290
|
+
}
|
|
4291
|
+
});
|
|
4292
|
+
return path;
|
|
4293
|
+
}
|
|
4294
|
+
function createStyleBridgeTaskSummary(projectRoot) {
|
|
4295
|
+
const bridge = readStyleBridge(projectRoot);
|
|
4296
|
+
return {
|
|
4297
|
+
path: bridge ? ".decantr/style-bridge.json" : null,
|
|
4298
|
+
status: bridge?.status ?? null,
|
|
4299
|
+
mappingCount: bridge?.mappings?.length ?? 0,
|
|
4300
|
+
stylingApproach: bridge?.styling?.approach ?? null,
|
|
4301
|
+
themeModes: bridge?.styling?.themeModes ?? [],
|
|
4302
|
+
mappings: bridge?.mappings?.map((mapping) => ({
|
|
4303
|
+
id: mapping.id,
|
|
4304
|
+
label: mapping.label,
|
|
4305
|
+
tokenHints: mapping.tokenHints.slice(0, 6),
|
|
4306
|
+
classHints: mapping.classHints.slice(0, 4),
|
|
4307
|
+
guardrails: mapping.guardrails.slice(0, 3)
|
|
4308
|
+
})) ?? []
|
|
4309
|
+
};
|
|
4310
|
+
}
|
|
4311
|
+
function styleBridgeMatches(projectRoot, query) {
|
|
4312
|
+
if (!projectRoot) return [];
|
|
4313
|
+
const bridge = readStyleBridge(projectRoot);
|
|
4314
|
+
if (!bridge) return [];
|
|
4315
|
+
const terms = query.toLowerCase().split(/[^a-z0-9]+/).filter((term) => term.length > 1).flatMap((term) => term.endsWith("s") && term.length > 3 ? [term, term.slice(0, -1)] : [term]);
|
|
4316
|
+
if (terms.length === 0) return [];
|
|
4317
|
+
return bridge.mappings.map((mapping) => {
|
|
4318
|
+
const haystack = [
|
|
4319
|
+
mapping.id,
|
|
4320
|
+
mapping.label,
|
|
4321
|
+
mapping.decantrIntent,
|
|
4322
|
+
mapping.projectAuthority,
|
|
4323
|
+
...mapping.tokenHints,
|
|
4324
|
+
...mapping.classHints,
|
|
4325
|
+
...mapping.guardrails
|
|
4326
|
+
].join(" ").toLowerCase();
|
|
4327
|
+
const score = terms.reduce((sum, term) => sum + (haystack.includes(term) ? 1 : 0), 0);
|
|
4328
|
+
return {
|
|
4329
|
+
id: mapping.id,
|
|
4330
|
+
label: mapping.label,
|
|
4331
|
+
score,
|
|
4332
|
+
tokenHints: mapping.tokenHints.slice(0, 4),
|
|
4333
|
+
classHints: mapping.classHints.slice(0, 3)
|
|
4334
|
+
};
|
|
4335
|
+
}).filter((match) => match.score > 0).sort((a, b) => b.score - a.score || a.id.localeCompare(b.id)).slice(0, 5);
|
|
4336
|
+
}
|
|
4337
|
+
|
|
3751
4338
|
// src/commands/ci.ts
|
|
3752
4339
|
var BOLD2 = "\x1B[1m";
|
|
3753
4340
|
var DIM3 = "\x1B[2m";
|
|
@@ -3756,9 +4343,9 @@ var RED2 = "\x1B[31m";
|
|
|
3756
4343
|
var RESET3 = "\x1B[0m";
|
|
3757
4344
|
var CI_SCHEMA = "https://decantr.ai/schemas/decantr-ci-report.v1.json";
|
|
3758
4345
|
function readJson(path) {
|
|
3759
|
-
if (!
|
|
4346
|
+
if (!existsSync15(path)) return null;
|
|
3760
4347
|
try {
|
|
3761
|
-
return JSON.parse(
|
|
4348
|
+
return JSON.parse(readFileSync13(path, "utf-8"));
|
|
3762
4349
|
} catch {
|
|
3763
4350
|
return null;
|
|
3764
4351
|
}
|
|
@@ -3802,21 +4389,21 @@ function parseProvider(value) {
|
|
|
3802
4389
|
throw new Error("Invalid --provider value. Use github or generic.");
|
|
3803
4390
|
}
|
|
3804
4391
|
function detectPackageManager2(root) {
|
|
3805
|
-
const pkg = readJson(
|
|
4392
|
+
const pkg = readJson(join17(root, "package.json"));
|
|
3806
4393
|
const declared = pkg?.packageManager?.split("@")[0];
|
|
3807
4394
|
if (declared === "pnpm" || declared === "npm" || declared === "yarn" || declared === "bun") {
|
|
3808
4395
|
return declared;
|
|
3809
4396
|
}
|
|
3810
|
-
if (
|
|
3811
|
-
if (
|
|
3812
|
-
if (
|
|
3813
|
-
if (
|
|
4397
|
+
if (existsSync15(join17(root, "pnpm-lock.yaml"))) return "pnpm";
|
|
4398
|
+
if (existsSync15(join17(root, "package-lock.json"))) return "npm";
|
|
4399
|
+
if (existsSync15(join17(root, "yarn.lock"))) return "yarn";
|
|
4400
|
+
if (existsSync15(join17(root, "bun.lock")) || existsSync15(join17(root, "bun.lockb"))) return "bun";
|
|
3814
4401
|
return "unknown";
|
|
3815
4402
|
}
|
|
3816
4403
|
function hasWorkspaceMarker(root) {
|
|
3817
|
-
const pkg = readJson(
|
|
4404
|
+
const pkg = readJson(join17(root, "package.json"));
|
|
3818
4405
|
return Boolean(
|
|
3819
|
-
|
|
4406
|
+
existsSync15(join17(root, "pnpm-workspace.yaml")) || existsSync15(join17(root, "turbo.json")) || existsSync15(join17(root, "nx.json")) || pkg?.workspaces
|
|
3820
4407
|
);
|
|
3821
4408
|
}
|
|
3822
4409
|
function installCommand(packageManager) {
|
|
@@ -3862,7 +4449,7 @@ function decantrCommand(packageManager) {
|
|
|
3862
4449
|
}
|
|
3863
4450
|
}
|
|
3864
4451
|
function localCliPinned(root) {
|
|
3865
|
-
const pkg = readJson(
|
|
4452
|
+
const pkg = readJson(join17(root, "package.json"));
|
|
3866
4453
|
return Boolean(pkg?.devDependencies?.["@decantr/cli"] || pkg?.dependencies?.["@decantr/cli"]);
|
|
3867
4454
|
}
|
|
3868
4455
|
function projectSlug(projectPath) {
|
|
@@ -3870,8 +4457,8 @@ function projectSlug(projectPath) {
|
|
|
3870
4457
|
}
|
|
3871
4458
|
function writeOutput(root, path, content) {
|
|
3872
4459
|
const absolute = resolve(root, path);
|
|
3873
|
-
|
|
3874
|
-
|
|
4460
|
+
mkdirSync10(dirname3(absolute), { recursive: true });
|
|
4461
|
+
writeFileSync11(absolute, content, "utf-8");
|
|
3875
4462
|
}
|
|
3876
4463
|
function summarizeLocalLaw(projectRoot) {
|
|
3877
4464
|
const validation = validateLocalLaw(projectRoot);
|
|
@@ -3893,14 +4480,36 @@ function summarizeLocalLaw(projectRoot) {
|
|
|
3893
4480
|
warnCount: validation.findings.filter((finding) => finding.severity === "warn").length + validation.warnings.length
|
|
3894
4481
|
};
|
|
3895
4482
|
}
|
|
4483
|
+
function summarizeStyleBridge(projectRoot) {
|
|
4484
|
+
const summary = createStyleBridgeTaskSummary(projectRoot);
|
|
4485
|
+
const warnings = [];
|
|
4486
|
+
if (summary.path && summary.mappingCount === 0) {
|
|
4487
|
+
warnings.push(".decantr/style-bridge.json has no mappings.");
|
|
4488
|
+
}
|
|
4489
|
+
return {
|
|
4490
|
+
checked: Boolean(summary.path),
|
|
4491
|
+
present: Boolean(summary.path),
|
|
4492
|
+
status: summary.status,
|
|
4493
|
+
mappingCount: summary.mappingCount,
|
|
4494
|
+
stylingApproach: summary.stylingApproach,
|
|
4495
|
+
themeModes: summary.themeModes,
|
|
4496
|
+
warnings
|
|
4497
|
+
};
|
|
4498
|
+
}
|
|
3896
4499
|
function localLawFails(summary, failOn) {
|
|
3897
4500
|
if (failOn === "none" || !summary.checked) return false;
|
|
3898
4501
|
if (summary.errorCount > 0) return true;
|
|
3899
4502
|
return failOn === "warn" && summary.warnCount > 0;
|
|
3900
4503
|
}
|
|
3901
|
-
function
|
|
4504
|
+
function styleBridgeFails(summary, failOn) {
|
|
4505
|
+
if (failOn === "none" || !summary.checked) return false;
|
|
4506
|
+
return failOn === "warn" && summary.warnings.length > 0;
|
|
4507
|
+
}
|
|
4508
|
+
function projectCiStatus(health, localLaw, styleBridge) {
|
|
3902
4509
|
if (health.status === "error" || localLaw.errorCount > 0) return "error";
|
|
3903
|
-
if (health.status === "warning" || localLaw.warnCount > 0)
|
|
4510
|
+
if (health.status === "warning" || localLaw.warnCount > 0 || styleBridge.warnings.length > 0) {
|
|
4511
|
+
return "warning";
|
|
4512
|
+
}
|
|
3904
4513
|
return health.status;
|
|
3905
4514
|
}
|
|
3906
4515
|
function formatLocalLawText(summary, health) {
|
|
@@ -3916,6 +4525,9 @@ function formatLocalLawText(summary, health) {
|
|
|
3916
4525
|
lines.push(
|
|
3917
4526
|
` Patterns: ${summary.patternsPresent ? "present" : "missing"} | Rules: ${summary.rulesPresent ? "present" : "missing"}`
|
|
3918
4527
|
);
|
|
4528
|
+
lines.push(
|
|
4529
|
+
summary.rulesPresent ? " Enforcement: Decantr scans accepted .decantr/rules.json; this command's --fail-on setting controls whether findings block." : " Enforcement: advisory only until .decantr/rules.json is accepted."
|
|
4530
|
+
);
|
|
3919
4531
|
lines.push(` Findings: ${summary.errorCount} error(s), ${summary.warnCount} warning(s)`);
|
|
3920
4532
|
for (const warning of summary.warnings.slice(0, 5)) {
|
|
3921
4533
|
lines.push(` ${DIM3}[WARN] ${warning}${RESET3}`);
|
|
@@ -3933,6 +4545,28 @@ function formatLocalLawText(summary, health) {
|
|
|
3933
4545
|
return `${lines.join("\n")}
|
|
3934
4546
|
`;
|
|
3935
4547
|
}
|
|
4548
|
+
function formatStyleBridgeText(summary) {
|
|
4549
|
+
const lines = ["", `${BOLD2}Project-owned style bridge:${RESET3}`];
|
|
4550
|
+
if (!summary.checked) {
|
|
4551
|
+
lines.push(" Not active for this project.");
|
|
4552
|
+
return `${lines.join("\n")}
|
|
4553
|
+
`;
|
|
4554
|
+
}
|
|
4555
|
+
lines.push(
|
|
4556
|
+
` Present: ${summary.present ? "yes" : "no"} | Mappings: ${summary.mappingCount} | Styling: ${summary.stylingApproach ?? "unknown"}`
|
|
4557
|
+
);
|
|
4558
|
+
lines.push(
|
|
4559
|
+
" Enforcement: advisory style-intent mapping; pair with accepted local rules, lint, tests, or visual regression when it should block."
|
|
4560
|
+
);
|
|
4561
|
+
if (summary.themeModes.length > 0) {
|
|
4562
|
+
lines.push(` Theme modes: ${summary.themeModes.join(", ")}`);
|
|
4563
|
+
}
|
|
4564
|
+
for (const warning of summary.warnings.slice(0, 5)) {
|
|
4565
|
+
lines.push(` ${DIM3}[WARN] ${warning}${RESET3}`);
|
|
4566
|
+
}
|
|
4567
|
+
return `${lines.join("\n")}
|
|
4568
|
+
`;
|
|
4569
|
+
}
|
|
3936
4570
|
function formatLocalLawMarkdown(summary, health) {
|
|
3937
4571
|
const lines = ["## Project-Owned Local Law", ""];
|
|
3938
4572
|
if (!summary.checked) {
|
|
@@ -3948,6 +4582,9 @@ function formatLocalLawMarkdown(summary, health) {
|
|
|
3948
4582
|
lines.push(
|
|
3949
4583
|
`Patterns: **${summary.patternsPresent ? "present" : "missing"}** \xB7 Rules: **${summary.rulesPresent ? "present" : "missing"}**`
|
|
3950
4584
|
);
|
|
4585
|
+
lines.push(
|
|
4586
|
+
summary.rulesPresent ? "Enforcement: Decantr scans accepted `.decantr/rules.json`; CI blocking depends on `--fail-on`." : "Enforcement: advisory only until `.decantr/rules.json` is accepted."
|
|
4587
|
+
);
|
|
3951
4588
|
lines.push("");
|
|
3952
4589
|
lines.push(`Findings: **${summary.errorCount} error(s), ${summary.warnCount} warning(s)**`);
|
|
3953
4590
|
if (summary.warnings.length > 0) {
|
|
@@ -3966,6 +4603,30 @@ function formatLocalLawMarkdown(summary, health) {
|
|
|
3966
4603
|
}
|
|
3967
4604
|
return lines.join("\n");
|
|
3968
4605
|
}
|
|
4606
|
+
function formatStyleBridgeMarkdown(summary) {
|
|
4607
|
+
const lines = ["## Project-Owned Style Bridge", ""];
|
|
4608
|
+
if (!summary.checked) {
|
|
4609
|
+
lines.push("Style bridge is not active for this project.");
|
|
4610
|
+
return lines.join("\n");
|
|
4611
|
+
}
|
|
4612
|
+
lines.push(
|
|
4613
|
+
`Present: **${summary.present ? "yes" : "no"}** \xB7 Mappings: **${summary.mappingCount}** \xB7 Styling: **${summary.stylingApproach ?? "unknown"}**`
|
|
4614
|
+
);
|
|
4615
|
+
lines.push("");
|
|
4616
|
+
lines.push(
|
|
4617
|
+
"Enforcement: advisory style-intent mapping; pair with accepted local rules, lint, tests, or visual regression when it should block."
|
|
4618
|
+
);
|
|
4619
|
+
if (summary.themeModes.length > 0) {
|
|
4620
|
+
lines.push("", `Theme modes: ${summary.themeModes.map((mode) => `\`${mode}\``).join(", ")}`);
|
|
4621
|
+
}
|
|
4622
|
+
if (summary.warnings.length > 0) {
|
|
4623
|
+
lines.push("");
|
|
4624
|
+
for (const warning of summary.warnings.slice(0, 5)) {
|
|
4625
|
+
lines.push(`- Warning: ${warning}`);
|
|
4626
|
+
}
|
|
4627
|
+
}
|
|
4628
|
+
return lines.join("\n");
|
|
4629
|
+
}
|
|
3969
4630
|
function formatProjectCiMarkdown(report) {
|
|
3970
4631
|
const lines = [
|
|
3971
4632
|
"# Decantr CI",
|
|
@@ -3975,10 +4636,13 @@ function formatProjectCiMarkdown(report) {
|
|
|
3975
4636
|
`- Status: **${report.status}**`,
|
|
3976
4637
|
`- Fail on: \`${report.failOn}\``,
|
|
3977
4638
|
`- Local law: ${report.localLaw.checked ? `${report.localLaw.errorCount} error(s), ${report.localLaw.warnCount} warning(s)` : "not accepted yet"}`,
|
|
4639
|
+
`- Style bridge: ${report.styleBridge.checked ? `${report.styleBridge.mappingCount} mapping(s)` : "not active"}`,
|
|
3978
4640
|
"",
|
|
3979
4641
|
formatProjectHealthMarkdown(report.health),
|
|
3980
4642
|
"",
|
|
3981
|
-
formatLocalLawMarkdown(report.localLaw, report.health)
|
|
4643
|
+
formatLocalLawMarkdown(report.localLaw, report.health),
|
|
4644
|
+
"",
|
|
4645
|
+
formatStyleBridgeMarkdown(report.styleBridge)
|
|
3982
4646
|
];
|
|
3983
4647
|
return `${lines.join("\n")}
|
|
3984
4648
|
`;
|
|
@@ -4069,7 +4733,7 @@ jobs:
|
|
|
4069
4733
|
}
|
|
4070
4734
|
function writeCiInit(root, options) {
|
|
4071
4735
|
const workspaceInfo = resolveWorkspaceInfo(root, options.project);
|
|
4072
|
-
if (options.project && !
|
|
4736
|
+
if (options.project && !existsSync15(workspaceInfo.appRoot)) {
|
|
4073
4737
|
throw new Error(`Project path does not exist: ${options.project}`);
|
|
4074
4738
|
}
|
|
4075
4739
|
if (workspaceInfo.requiresProjectSelection && !options.workspace) {
|
|
@@ -4092,7 +4756,7 @@ function writeCiInit(root, options) {
|
|
|
4092
4756
|
if (provider === "generic") {
|
|
4093
4757
|
const path2 = ".decantr/ci/decantr-ci.sh";
|
|
4094
4758
|
const absolute2 = resolve(outputRoot, path2);
|
|
4095
|
-
if (
|
|
4759
|
+
if (existsSync15(absolute2) && !options.force) {
|
|
4096
4760
|
throw new Error(`${path2} already exists. Re-run with --force to replace it.`);
|
|
4097
4761
|
}
|
|
4098
4762
|
writeOutput(
|
|
@@ -4113,7 +4777,7 @@ function writeCiInit(root, options) {
|
|
|
4113
4777
|
}
|
|
4114
4778
|
const path = ".github/workflows/decantr-ci.yml";
|
|
4115
4779
|
const absolute = resolve(outputRoot, path);
|
|
4116
|
-
if (
|
|
4780
|
+
if (existsSync15(absolute) && !options.force) {
|
|
4117
4781
|
throw new Error(`${path} already exists. Re-run with --force to replace it.`);
|
|
4118
4782
|
}
|
|
4119
4783
|
writeOutput(
|
|
@@ -4162,7 +4826,7 @@ async function runWorkspaceCi(root, options) {
|
|
|
4162
4826
|
}
|
|
4163
4827
|
async function runProjectCi(root, options) {
|
|
4164
4828
|
const workspaceInfo = resolveWorkspaceInfo(root, options.project);
|
|
4165
|
-
if (options.project && !
|
|
4829
|
+
if (options.project && !existsSync15(workspaceInfo.appRoot)) {
|
|
4166
4830
|
console.error(`${RED2}Project path does not exist: ${options.project}${RESET3}`);
|
|
4167
4831
|
return 1;
|
|
4168
4832
|
}
|
|
@@ -4175,6 +4839,7 @@ async function runProjectCi(root, options) {
|
|
|
4175
4839
|
const failOn = options.failOn ?? "error";
|
|
4176
4840
|
const health = await createProjectHealthReport(workspaceInfo.appRoot);
|
|
4177
4841
|
const localLaw = summarizeLocalLaw(workspaceInfo.appRoot);
|
|
4842
|
+
const styleBridge = summarizeStyleBridge(workspaceInfo.appRoot);
|
|
4178
4843
|
const projectPath = workspaceInfo.appRoot === workspaceInfo.workspaceRoot ? null : workspaceInfo.appRoot.replace(`${workspaceInfo.workspaceRoot}/`, "");
|
|
4179
4844
|
const report = {
|
|
4180
4845
|
$schema: CI_SCHEMA,
|
|
@@ -4182,9 +4847,10 @@ async function runProjectCi(root, options) {
|
|
|
4182
4847
|
mode: "project",
|
|
4183
4848
|
projectPath,
|
|
4184
4849
|
failOn,
|
|
4185
|
-
status: projectCiStatus(health, localLaw),
|
|
4850
|
+
status: projectCiStatus(health, localLaw, styleBridge),
|
|
4186
4851
|
health,
|
|
4187
|
-
localLaw
|
|
4852
|
+
localLaw,
|
|
4853
|
+
styleBridge
|
|
4188
4854
|
};
|
|
4189
4855
|
const json = `${JSON.stringify(report, null, 2)}
|
|
4190
4856
|
`;
|
|
@@ -4195,9 +4861,11 @@ async function runProjectCi(root, options) {
|
|
|
4195
4861
|
if (options.json) process.stdout.write(json);
|
|
4196
4862
|
else if (options.markdown) process.stdout.write(markdown);
|
|
4197
4863
|
else
|
|
4198
|
-
process.stdout.write(
|
|
4864
|
+
process.stdout.write(
|
|
4865
|
+
`${formatProjectHealthText(health)}${formatLocalLawText(localLaw, health)}${formatStyleBridgeText(styleBridge)}`
|
|
4866
|
+
);
|
|
4199
4867
|
}
|
|
4200
|
-
return shouldFailHealth(health, failOn) || localLawFails(localLaw, failOn) ? 1 : 0;
|
|
4868
|
+
return shouldFailHealth(health, failOn) || localLawFails(localLaw, failOn) || styleBridgeFails(styleBridge, failOn) ? 1 : 0;
|
|
4201
4869
|
}
|
|
4202
4870
|
function cmdCiHelp() {
|
|
4203
4871
|
console.log(`
|
|
@@ -4232,8 +4900,8 @@ async function cmdCi(args = ["ci"], root = process.cwd()) {
|
|
|
4232
4900
|
}
|
|
4233
4901
|
|
|
4234
4902
|
// src/commands/create.ts
|
|
4235
|
-
import { existsSync as
|
|
4236
|
-
import { join as
|
|
4903
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync11, writeFileSync as writeFileSync12 } from "fs";
|
|
4904
|
+
import { join as join18 } from "path";
|
|
4237
4905
|
import {
|
|
4238
4906
|
CONTENT_TYPE_TO_API_CONTENT_TYPE,
|
|
4239
4907
|
CONTENT_TYPES
|
|
@@ -4429,23 +5097,23 @@ function cmdCreate(type, name, projectRoot = process.cwd()) {
|
|
|
4429
5097
|
}
|
|
4430
5098
|
const contentType = type;
|
|
4431
5099
|
const plural = PLURAL[contentType];
|
|
4432
|
-
const customDir =
|
|
4433
|
-
const filePath =
|
|
4434
|
-
if (
|
|
5100
|
+
const customDir = join18(projectRoot, ".decantr", "custom", plural);
|
|
5101
|
+
const filePath = join18(customDir, `${name}.json`);
|
|
5102
|
+
if (existsSync16(filePath)) {
|
|
4435
5103
|
console.error(`${type} "${name}" already exists at ${filePath}`);
|
|
4436
5104
|
process.exitCode = 1;
|
|
4437
5105
|
return;
|
|
4438
5106
|
}
|
|
4439
|
-
|
|
5107
|
+
mkdirSync11(customDir, { recursive: true });
|
|
4440
5108
|
const skeleton = getSkeleton(contentType, name, humanizeId(name));
|
|
4441
|
-
|
|
5109
|
+
writeFileSync12(filePath, JSON.stringify(skeleton, null, 2));
|
|
4442
5110
|
console.log(`Created ${type} "${name}" at ${filePath}`);
|
|
4443
5111
|
console.log(`Edit it, then publish with: decantr publish ${type} ${name}`);
|
|
4444
5112
|
}
|
|
4445
5113
|
|
|
4446
5114
|
// src/commands/doctor.ts
|
|
4447
|
-
import { existsSync as
|
|
4448
|
-
import { dirname as dirname4, join as
|
|
5115
|
+
import { existsSync as existsSync17, readdirSync as readdirSync6, readFileSync as readFileSync14 } from "fs";
|
|
5116
|
+
import { dirname as dirname4, join as join19, relative as relative5 } from "path";
|
|
4449
5117
|
import { fileURLToPath } from "url";
|
|
4450
5118
|
import { isV4 as isV44 } from "@decantr/essence-spec";
|
|
4451
5119
|
import { collectMissingPackManifestFiles } from "@decantr/verifier";
|
|
@@ -4457,25 +5125,25 @@ var YELLOW3 = "\x1B[33m";
|
|
|
4457
5125
|
var CYAN2 = "\x1B[36m";
|
|
4458
5126
|
var RESET4 = "\x1B[0m";
|
|
4459
5127
|
function readJson2(path) {
|
|
4460
|
-
if (!
|
|
5128
|
+
if (!existsSync17(path)) return null;
|
|
4461
5129
|
try {
|
|
4462
|
-
return JSON.parse(
|
|
5130
|
+
return JSON.parse(readFileSync14(path, "utf-8"));
|
|
4463
5131
|
} catch {
|
|
4464
5132
|
return null;
|
|
4465
5133
|
}
|
|
4466
5134
|
}
|
|
4467
5135
|
function isContractOnlyProject(projectRoot) {
|
|
4468
|
-
const projectJson = readJson2(
|
|
4469
|
-
return projectJson?.initialized?.adoptionMode === "contract-only";
|
|
5136
|
+
const projectJson = readJson2(join19(projectRoot, ".decantr", "project.json"));
|
|
5137
|
+
return projectJson?.initialized?.adoptionMode === "contract-only" || projectJson?.initialized?.adoptionMode === "style-bridge";
|
|
4470
5138
|
}
|
|
4471
5139
|
function readCliPackageVersion() {
|
|
4472
|
-
const packagePath =
|
|
4473
|
-
const srcPackagePath =
|
|
5140
|
+
const packagePath = join19(dirname4(fileURLToPath(import.meta.url)), "..", "..", "package.json");
|
|
5141
|
+
const srcPackagePath = join19(dirname4(fileURLToPath(import.meta.url)), "..", "package.json");
|
|
4474
5142
|
const pkg = readJson2(packagePath) ?? readJson2(srcPackagePath);
|
|
4475
5143
|
return pkg?.version ?? "unknown";
|
|
4476
5144
|
}
|
|
4477
5145
|
function readPackageJson2(dir) {
|
|
4478
|
-
return readJson2(
|
|
5146
|
+
return readJson2(join19(dir, "package.json"));
|
|
4479
5147
|
}
|
|
4480
5148
|
function detectPackageManager3(root) {
|
|
4481
5149
|
const pkg = readPackageJson2(root);
|
|
@@ -4483,10 +5151,10 @@ function detectPackageManager3(root) {
|
|
|
4483
5151
|
if (declared === "pnpm" || declared === "npm" || declared === "yarn" || declared === "bun") {
|
|
4484
5152
|
return declared;
|
|
4485
5153
|
}
|
|
4486
|
-
if (
|
|
4487
|
-
if (
|
|
4488
|
-
if (
|
|
4489
|
-
if (
|
|
5154
|
+
if (existsSync17(join19(root, "pnpm-lock.yaml"))) return "pnpm";
|
|
5155
|
+
if (existsSync17(join19(root, "package-lock.json"))) return "npm";
|
|
5156
|
+
if (existsSync17(join19(root, "yarn.lock"))) return "yarn";
|
|
5157
|
+
if (existsSync17(join19(root, "bun.lock")) || existsSync17(join19(root, "bun.lockb"))) return "bun";
|
|
4490
5158
|
return "unknown";
|
|
4491
5159
|
}
|
|
4492
5160
|
function localCliDependency(root) {
|
|
@@ -4496,7 +5164,7 @@ function localCliDependency(root) {
|
|
|
4496
5164
|
function hasWorkspaceMarker2(root) {
|
|
4497
5165
|
const pkg = readPackageJson2(root);
|
|
4498
5166
|
return Boolean(
|
|
4499
|
-
|
|
5167
|
+
existsSync17(join19(root, "pnpm-workspace.yaml")) || existsSync17(join19(root, "turbo.json")) || existsSync17(join19(root, "nx.json")) || pkg?.workspaces
|
|
4500
5168
|
);
|
|
4501
5169
|
}
|
|
4502
5170
|
function pinCliCommand2(packageManager, root) {
|
|
@@ -4519,8 +5187,8 @@ function rel(root, path) {
|
|
|
4519
5187
|
}
|
|
4520
5188
|
function hasAnyFile(dir, names) {
|
|
4521
5189
|
for (const name of names) {
|
|
4522
|
-
const path =
|
|
4523
|
-
if (
|
|
5190
|
+
const path = join19(dir, name);
|
|
5191
|
+
if (existsSync17(path)) return name;
|
|
4524
5192
|
}
|
|
4525
5193
|
return null;
|
|
4526
5194
|
}
|
|
@@ -4542,7 +5210,7 @@ function detectDesignAuthority(workspaceRoot, appRoot) {
|
|
|
4542
5210
|
"libs/design-system"
|
|
4543
5211
|
];
|
|
4544
5212
|
for (const candidate of sharedCandidates) {
|
|
4545
|
-
if (
|
|
5213
|
+
if (existsSync17(join19(workspaceRoot, candidate))) found.add(candidate);
|
|
4546
5214
|
}
|
|
4547
5215
|
for (const root of [appRoot, workspaceRoot]) {
|
|
4548
5216
|
const label = root === appRoot ? "app" : "workspace";
|
|
@@ -4553,9 +5221,9 @@ function detectDesignAuthority(workspaceRoot, appRoot) {
|
|
|
4553
5221
|
"tailwind.config.cjs"
|
|
4554
5222
|
]);
|
|
4555
5223
|
if (tailwind) found.add(`${label}:${tailwind}`);
|
|
4556
|
-
if (
|
|
4557
|
-
if (
|
|
4558
|
-
if (
|
|
5224
|
+
if (existsSync17(join19(root, ".storybook"))) found.add(`${label}:.storybook`);
|
|
5225
|
+
if (existsSync17(join19(root, "components", "ui"))) found.add(`${label}:components/ui`);
|
|
5226
|
+
if (existsSync17(join19(root, "src", "components", "ui"))) found.add(`${label}:src/components/ui`);
|
|
4559
5227
|
if (packageHasDependency(root, ["@storybook/react", "storybook"]))
|
|
4560
5228
|
found.add(`${label}:storybook`);
|
|
4561
5229
|
if (packageHasDependency(root, ["tailwindcss"])) found.add(`${label}:tailwindcss`);
|
|
@@ -4567,18 +5235,18 @@ function detectDesignAuthority(workspaceRoot, appRoot) {
|
|
|
4567
5235
|
}
|
|
4568
5236
|
function findCiFiles(root) {
|
|
4569
5237
|
const files = [];
|
|
4570
|
-
const workflows =
|
|
4571
|
-
if (
|
|
5238
|
+
const workflows = join19(root, ".github", "workflows");
|
|
5239
|
+
if (existsSync17(workflows)) {
|
|
4572
5240
|
for (const entry of readdirSync6(workflows, { withFileTypes: true })) {
|
|
4573
5241
|
if (!entry.isFile()) continue;
|
|
4574
|
-
const path =
|
|
4575
|
-
const text =
|
|
5242
|
+
const path = join19(workflows, entry.name);
|
|
5243
|
+
const text = readFileSync14(path, "utf-8");
|
|
4576
5244
|
if (text.includes("decantr")) files.push(rel(root, path));
|
|
4577
5245
|
}
|
|
4578
5246
|
}
|
|
4579
5247
|
for (const candidate of ["Jenkinsfile", ".gitlab-ci.yml", "azure-pipelines.yml", "BUILD"]) {
|
|
4580
|
-
const path =
|
|
4581
|
-
if (
|
|
5248
|
+
const path = join19(root, candidate);
|
|
5249
|
+
if (existsSync17(path) && readFileSync14(path, "utf-8").includes("decantr")) {
|
|
4582
5250
|
files.push(candidate);
|
|
4583
5251
|
}
|
|
4584
5252
|
}
|
|
@@ -4618,15 +5286,17 @@ function deriveAdoptionLane(input) {
|
|
|
4618
5286
|
};
|
|
4619
5287
|
}
|
|
4620
5288
|
const hasLocalLaw = input.localPatternsPresent || input.localRulesPresent;
|
|
5289
|
+
const hasStyleBridge = input.styleBridgePresent || input.adoptionMode === "style-bridge";
|
|
4621
5290
|
if (input.workflowMode === "hybrid-compose") {
|
|
4622
5291
|
return {
|
|
4623
5292
|
id: "hybrid-compose",
|
|
4624
5293
|
label: "Hybrid composition",
|
|
4625
5294
|
sourceAuthority: "Existing app plus selected Decantr/local law",
|
|
4626
|
-
styleAuthority: input.adoptionMode === "decantr-css" ? "Decantr CSS runtime is active where adopted" :
|
|
5295
|
+
styleAuthority: input.adoptionMode === "decantr-css" ? "Decantr CSS runtime is active where adopted" : hasStyleBridge ? "Style bridge maps Decantr intent into the app styling system" : "Existing app styling system remains primary",
|
|
4627
5296
|
activeAuthorities: [
|
|
4628
5297
|
"existing source",
|
|
4629
5298
|
"Essence V4 contract",
|
|
5299
|
+
hasStyleBridge ? "accepted style bridge" : "no style bridge",
|
|
4630
5300
|
hasLocalLaw ? "accepted local law" : "reviewed Hybrid choices",
|
|
4631
5301
|
input.packManifestPresent ? "hosted execution packs" : "optional hosted packs"
|
|
4632
5302
|
],
|
|
@@ -4644,13 +5314,18 @@ function deriveAdoptionLane(input) {
|
|
|
4644
5314
|
nextChoice: "Keep Decantr CSS usage explicit and validate route changes with task and verify."
|
|
4645
5315
|
};
|
|
4646
5316
|
}
|
|
4647
|
-
if (
|
|
5317
|
+
if (hasStyleBridge) {
|
|
4648
5318
|
return {
|
|
4649
5319
|
id: "hybrid-style-bridge",
|
|
4650
5320
|
label: "Hybrid style bridge",
|
|
4651
5321
|
sourceAuthority: "Existing app plus Decantr intent mapped through a style bridge",
|
|
4652
5322
|
styleAuthority: "Style bridge over the existing app styling system",
|
|
4653
|
-
activeAuthorities: [
|
|
5323
|
+
activeAuthorities: [
|
|
5324
|
+
"existing source",
|
|
5325
|
+
"Essence V4 contract",
|
|
5326
|
+
"accepted style bridge",
|
|
5327
|
+
hasLocalLaw ? "accepted local patterns/rules" : "optional local law"
|
|
5328
|
+
],
|
|
4654
5329
|
nextChoice: "Use local law to decide which component families the bridge governs before making it strict."
|
|
4655
5330
|
};
|
|
4656
5331
|
}
|
|
@@ -4708,18 +5383,18 @@ function buildDoctorReport(root, args) {
|
|
|
4708
5383
|
const workspaceInfo = resolveWorkspaceInfo(root, projectArg);
|
|
4709
5384
|
const workspaceRoot = workspaceInfo.workspaceRoot;
|
|
4710
5385
|
const appRoot = workspaceMode ? workspaceRoot : workspaceInfo.appRoot;
|
|
4711
|
-
const projectMissing = Boolean(projectArg && !
|
|
5386
|
+
const projectMissing = Boolean(projectArg && !existsSync17(appRoot));
|
|
4712
5387
|
const projectPath = appRoot === workspaceRoot ? null : rel(workspaceRoot, appRoot);
|
|
4713
5388
|
const packageManager = detectPackageManager3(workspaceRoot);
|
|
4714
5389
|
const cliDependency = localCliDependency(workspaceRoot);
|
|
4715
5390
|
const detected = detectProject(appRoot);
|
|
4716
|
-
const projectJson = readJson2(
|
|
4717
|
-
const essence = readJson2(
|
|
5391
|
+
const projectJson = readJson2(join19(appRoot, ".decantr", "project.json"));
|
|
5392
|
+
const essence = readJson2(join19(appRoot, "decantr.essence.json"));
|
|
4718
5393
|
const essenceVersion = essence && typeof essence === "object" && "version" in essence ? String(essence.version) : null;
|
|
4719
|
-
const contextDir =
|
|
4720
|
-
const packManifestPresent =
|
|
4721
|
-
const reviewPackPresent =
|
|
4722
|
-
const artifactReadmePresent =
|
|
5394
|
+
const contextDir = join19(appRoot, ".decantr", "context");
|
|
5395
|
+
const packManifestPresent = existsSync17(join19(contextDir, "pack-manifest.json"));
|
|
5396
|
+
const reviewPackPresent = existsSync17(join19(contextDir, "review-pack.json"));
|
|
5397
|
+
const artifactReadmePresent = existsSync17(join19(appRoot, ARTIFACT_README_PATH));
|
|
4723
5398
|
const projects = listWorkspaceProjects(workspaceRoot);
|
|
4724
5399
|
const candidates = listWorkspaceCandidates(workspaceRoot, projects).map((candidate) => ({
|
|
4725
5400
|
path: candidate.path,
|
|
@@ -4729,21 +5404,23 @@ function buildDoctorReport(root, args) {
|
|
|
4729
5404
|
const ciFiles = findCiFiles(workspaceRoot);
|
|
4730
5405
|
const workflowMode = projectJson?.initialized?.workflowMode ?? null;
|
|
4731
5406
|
const adoptionMode = projectJson?.initialized?.adoptionMode ?? null;
|
|
4732
|
-
const packHydrationOptional = adoptionMode === "contract-only";
|
|
4733
|
-
const localPatternsPresent =
|
|
4734
|
-
const localRulesPresent =
|
|
5407
|
+
const packHydrationOptional = adoptionMode === "contract-only" || adoptionMode === "style-bridge";
|
|
5408
|
+
const localPatternsPresent = existsSync17(localPatternsPath(appRoot));
|
|
5409
|
+
const localRulesPresent = existsSync17(localRulesPath(appRoot));
|
|
5410
|
+
const styleBridgePresent = existsSync17(styleBridgePath(appRoot));
|
|
5411
|
+
const styleBridgeProposalPresent = existsSync17(styleBridgeProposalPath(appRoot));
|
|
4735
5412
|
const missingPackReferences = workspaceMode ? projects.flatMap(
|
|
4736
|
-
(project) => collectMissingPackManifestFiles(
|
|
5413
|
+
(project) => collectMissingPackManifestFiles(join19(workspaceRoot, project.path)).map(
|
|
4737
5414
|
(missing) => `${project.path}/${missing.relativePath}`
|
|
4738
5415
|
)
|
|
4739
5416
|
) : collectMissingPackManifestFiles(appRoot).map((missing) => missing.relativePath);
|
|
4740
5417
|
const workspaceProjectsMissingManifest = workspaceMode ? projects.map((project) => project.path).filter((projectPath2) => {
|
|
4741
|
-
const projectContextDir =
|
|
4742
|
-
return !isContractOnlyProject(
|
|
5418
|
+
const projectContextDir = join19(workspaceRoot, projectPath2, ".decantr", "context");
|
|
5419
|
+
return !isContractOnlyProject(join19(workspaceRoot, projectPath2)) && !existsSync17(join19(projectContextDir, "pack-manifest.json"));
|
|
4743
5420
|
}) : [];
|
|
4744
5421
|
const workspaceProjectsMissingReviewPack = workspaceMode ? projects.map((project) => project.path).filter((projectPath2) => {
|
|
4745
|
-
const projectContextDir =
|
|
4746
|
-
return !isContractOnlyProject(
|
|
5422
|
+
const projectContextDir = join19(workspaceRoot, projectPath2, ".decantr", "context");
|
|
5423
|
+
return !isContractOnlyProject(join19(workspaceRoot, projectPath2)) && !existsSync17(join19(projectContextDir, "review-pack.json"));
|
|
4747
5424
|
}) : [];
|
|
4748
5425
|
const issues = [];
|
|
4749
5426
|
if (projectMissing) {
|
|
@@ -4800,7 +5477,7 @@ function buildDoctorReport(root, args) {
|
|
|
4800
5477
|
nextCommand: "decantr refresh"
|
|
4801
5478
|
});
|
|
4802
5479
|
}
|
|
4803
|
-
if (essenceVersion === "4.0.0" && !packHydrationOptional && (!
|
|
5480
|
+
if (essenceVersion === "4.0.0" && !packHydrationOptional && (!existsSync17(contextDir) || !packManifestPresent)) {
|
|
4804
5481
|
issues.push({
|
|
4805
5482
|
category: "generated-artifact",
|
|
4806
5483
|
severity: "warn",
|
|
@@ -4832,6 +5509,14 @@ function buildDoctorReport(root, args) {
|
|
|
4832
5509
|
nextCommand: projectPath ? `decantr codify --from-audit --project ${projectPath}` : "decantr codify --from-audit"
|
|
4833
5510
|
});
|
|
4834
5511
|
}
|
|
5512
|
+
if (adoptionMode === "style-bridge" && !styleBridgePresent) {
|
|
5513
|
+
issues.push({
|
|
5514
|
+
category: "local-law",
|
|
5515
|
+
severity: "warn",
|
|
5516
|
+
message: "This app declares style-bridge adoption, but .decantr/style-bridge.json is missing.",
|
|
5517
|
+
nextCommand: styleBridgeProposalPresent ? projectPath ? `decantr codify --accept --project ${projectPath}` : "decantr codify --accept" : projectPath ? `decantr codify --style-bridge --project ${projectPath}` : "decantr codify --style-bridge"
|
|
5518
|
+
});
|
|
5519
|
+
}
|
|
4835
5520
|
if ((essenceVersion === "4.0.0" || workspaceMode && projects.length > 0) && ciFiles.length === 0) {
|
|
4836
5521
|
issues.push({
|
|
4837
5522
|
category: "ci",
|
|
@@ -4859,6 +5544,7 @@ function buildDoctorReport(root, args) {
|
|
|
4859
5544
|
adoptionMode,
|
|
4860
5545
|
localPatternsPresent,
|
|
4861
5546
|
localRulesPresent,
|
|
5547
|
+
styleBridgePresent,
|
|
4862
5548
|
designAuthority,
|
|
4863
5549
|
packManifestPresent
|
|
4864
5550
|
});
|
|
@@ -4880,6 +5566,12 @@ function buildDoctorReport(root, args) {
|
|
|
4880
5566
|
appendUnique(recommendedNextCommands, `decantr codify --from-audit${projectFlag}`);
|
|
4881
5567
|
appendUnique(recommendedNextCommands, `decantr codify --accept${projectFlag}`);
|
|
4882
5568
|
}
|
|
5569
|
+
if (adoptionMode === "style-bridge" && !styleBridgePresent) {
|
|
5570
|
+
appendUnique(
|
|
5571
|
+
recommendedNextCommands,
|
|
5572
|
+
styleBridgeProposalPresent ? `decantr codify --accept${projectFlag}` : `decantr codify --style-bridge${projectFlag}`
|
|
5573
|
+
);
|
|
5574
|
+
}
|
|
4883
5575
|
appendUnique(
|
|
4884
5576
|
recommendedNextCommands,
|
|
4885
5577
|
issues.find((issue) => issue.category === "generated-artifact")?.nextCommand
|
|
@@ -4919,18 +5611,20 @@ function buildDoctorReport(root, args) {
|
|
|
4919
5611
|
},
|
|
4920
5612
|
generatedArtifacts: {
|
|
4921
5613
|
contextDirPresent: workspaceMode ? projects.some(
|
|
4922
|
-
(project) =>
|
|
4923
|
-
) :
|
|
5614
|
+
(project) => existsSync17(join19(workspaceRoot, project.path, ".decantr", "context"))
|
|
5615
|
+
) : existsSync17(contextDir),
|
|
4924
5616
|
packManifestPresent: workspaceMode ? projects.length > 0 && workspaceProjectsMissingManifest.length === 0 : packManifestPresent,
|
|
4925
5617
|
reviewPackPresent: workspaceMode ? projects.length > 0 && workspaceProjectsMissingReviewPack.length === 0 : reviewPackPresent,
|
|
4926
5618
|
missingReferencedFiles: missingPackReferences.slice(0, 25)
|
|
4927
5619
|
},
|
|
4928
5620
|
localLaw: {
|
|
4929
5621
|
patternsPresent: localPatternsPresent,
|
|
4930
|
-
rulesPresent: localRulesPresent
|
|
5622
|
+
rulesPresent: localRulesPresent,
|
|
5623
|
+
styleBridgePresent,
|
|
5624
|
+
styleBridgeProposalPresent
|
|
4931
5625
|
},
|
|
4932
5626
|
visualEvidence: {
|
|
4933
|
-
manifestPresent:
|
|
5627
|
+
manifestPresent: existsSync17(join19(appRoot, ".decantr", "evidence", "visual-manifest.json"))
|
|
4934
5628
|
},
|
|
4935
5629
|
lane,
|
|
4936
5630
|
designAuthority,
|
|
@@ -4978,6 +5672,7 @@ function formatDoctorText(report) {
|
|
|
4978
5672
|
`${BOLD3}Local Law:${RESET4}`,
|
|
4979
5673
|
` Local patterns: ${report.localLaw.patternsPresent ? "present" : "missing"}`,
|
|
4980
5674
|
` Local rules: ${report.localLaw.rulesPresent ? "present" : "missing"}`,
|
|
5675
|
+
` Style bridge: ${report.localLaw.styleBridgePresent ? "present" : report.localLaw.styleBridgeProposalPresent ? "proposal pending" : "missing"}`,
|
|
4981
5676
|
"",
|
|
4982
5677
|
`${BOLD3}Workspace:${RESET4}`,
|
|
4983
5678
|
` Attached projects: ${report.workspace.attachedProjects.length}`,
|
|
@@ -5037,17 +5732,17 @@ async function cmdDoctor(args = ["doctor"], root = process.cwd()) {
|
|
|
5037
5732
|
}
|
|
5038
5733
|
|
|
5039
5734
|
// src/commands/export.ts
|
|
5040
|
-
import { existsSync as
|
|
5041
|
-
import { dirname as dirname5, isAbsolute, join as
|
|
5735
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync12, readFileSync as readFileSync15, writeFileSync as writeFileSync13 } from "fs";
|
|
5736
|
+
import { dirname as dirname5, isAbsolute, join as join20 } from "path";
|
|
5042
5737
|
var GREEN5 = "\x1B[32m";
|
|
5043
5738
|
var RED4 = "\x1B[31m";
|
|
5044
5739
|
var DIM5 = "\x1B[2m";
|
|
5045
5740
|
var RESET5 = "\x1B[0m";
|
|
5046
5741
|
function readAdoptionMode(projectRoot) {
|
|
5047
|
-
const projectJsonPath =
|
|
5048
|
-
if (!
|
|
5742
|
+
const projectJsonPath = join20(projectRoot, ".decantr", "project.json");
|
|
5743
|
+
if (!existsSync18(projectJsonPath)) return null;
|
|
5049
5744
|
try {
|
|
5050
|
-
const parsed = JSON.parse(
|
|
5745
|
+
const parsed = JSON.parse(readFileSync15(projectJsonPath, "utf-8"));
|
|
5051
5746
|
return typeof parsed.initialized?.adoptionMode === "string" ? parsed.initialized.adoptionMode : null;
|
|
5052
5747
|
} catch {
|
|
5053
5748
|
return null;
|
|
@@ -5055,7 +5750,7 @@ function readAdoptionMode(projectRoot) {
|
|
|
5055
5750
|
}
|
|
5056
5751
|
function resolveOutputPath(projectRoot, output, fallback) {
|
|
5057
5752
|
if (!output) return fallback;
|
|
5058
|
-
return isAbsolute(output) ? output :
|
|
5753
|
+
return isAbsolute(output) ? output : join20(projectRoot, output);
|
|
5059
5754
|
}
|
|
5060
5755
|
var SHADCN_MAP = {
|
|
5061
5756
|
"--d-bg": "--background",
|
|
@@ -5216,21 +5911,21 @@ function generateFigmaTokens(tokens) {
|
|
|
5216
5911
|
`;
|
|
5217
5912
|
}
|
|
5218
5913
|
async function cmdExport(target, projectRoot, options = {}) {
|
|
5219
|
-
const essencePath =
|
|
5220
|
-
const tokensPath =
|
|
5221
|
-
if (!
|
|
5914
|
+
const essencePath = join20(projectRoot, "decantr.essence.json");
|
|
5915
|
+
const tokensPath = join20(projectRoot, "src", "styles", "tokens.css");
|
|
5916
|
+
if (!existsSync18(essencePath)) {
|
|
5222
5917
|
console.error(`${RED4}No decantr.essence.json found. Run \`decantr init\` first.${RESET5}`);
|
|
5223
5918
|
process.exitCode = 1;
|
|
5224
5919
|
return;
|
|
5225
5920
|
}
|
|
5226
|
-
if (!
|
|
5921
|
+
if (!existsSync18(tokensPath)) {
|
|
5227
5922
|
const adoptionMode = readAdoptionMode(projectRoot);
|
|
5228
5923
|
const message = adoptionMode === "contract-only" ? `No src/styles/tokens.css found. This export reads Decantr CSS tokens, but this project is contract-only Brownfield and may intentionally use its own Tailwind/Sass token system. Export from the app's token source or adopt a style bridge before using \`decantr export --to ${target}\`.` : "No src/styles/tokens.css found. Run `decantr refresh` to generate tokens.";
|
|
5229
5924
|
console.error(`${RED4}${message}${RESET5}`);
|
|
5230
5925
|
process.exitCode = 1;
|
|
5231
5926
|
return;
|
|
5232
5927
|
}
|
|
5233
|
-
const tokensCSS =
|
|
5928
|
+
const tokensCSS = readFileSync15(tokensPath, "utf-8");
|
|
5234
5929
|
const tokens = parseTokensCSS(tokensCSS);
|
|
5235
5930
|
if (tokens.size === 0) {
|
|
5236
5931
|
console.error(`${RED4}No --d-* tokens found in tokens.css.${RESET5}`);
|
|
@@ -5242,12 +5937,12 @@ async function cmdExport(target, projectRoot, options = {}) {
|
|
|
5242
5937
|
const cssOut = resolveOutputPath(
|
|
5243
5938
|
projectRoot,
|
|
5244
5939
|
options.output,
|
|
5245
|
-
|
|
5940
|
+
join20(projectRoot, "src", "styles", "shadcn-theme.css")
|
|
5246
5941
|
);
|
|
5247
|
-
const jsonOut =
|
|
5942
|
+
const jsonOut = join20(projectRoot, "components.json");
|
|
5248
5943
|
ensureDir(cssOut);
|
|
5249
|
-
|
|
5250
|
-
|
|
5944
|
+
writeFileSync13(cssOut, generateShadcnCSS(tokens), "utf-8");
|
|
5945
|
+
writeFileSync13(jsonOut, generateShadcnComponentsJSON(), "utf-8");
|
|
5251
5946
|
console.log(`${GREEN5}Exported shadcn theme:${RESET5}`);
|
|
5252
5947
|
console.log(` ${DIM5}CSS:${RESET5} ${cssOut}`);
|
|
5253
5948
|
console.log(` ${DIM5}JSON:${RESET5} ${jsonOut}`);
|
|
@@ -5257,10 +5952,10 @@ async function cmdExport(target, projectRoot, options = {}) {
|
|
|
5257
5952
|
const out = resolveOutputPath(
|
|
5258
5953
|
projectRoot,
|
|
5259
5954
|
options.output,
|
|
5260
|
-
|
|
5955
|
+
join20(projectRoot, "tailwind.decantr.config.ts")
|
|
5261
5956
|
);
|
|
5262
5957
|
ensureDir(out);
|
|
5263
|
-
|
|
5958
|
+
writeFileSync13(out, generateTailwindConfig(tokens), "utf-8");
|
|
5264
5959
|
console.log(`${GREEN5}Exported Tailwind config:${RESET5}`);
|
|
5265
5960
|
console.log(` ${DIM5}File:${RESET5} ${out}`);
|
|
5266
5961
|
break;
|
|
@@ -5269,10 +5964,10 @@ async function cmdExport(target, projectRoot, options = {}) {
|
|
|
5269
5964
|
const out = resolveOutputPath(
|
|
5270
5965
|
projectRoot,
|
|
5271
5966
|
options.output,
|
|
5272
|
-
|
|
5967
|
+
join20(projectRoot, "decantr-tokens.css")
|
|
5273
5968
|
);
|
|
5274
5969
|
ensureDir(out);
|
|
5275
|
-
|
|
5970
|
+
writeFileSync13(out, generateCSSVars(tokens), "utf-8");
|
|
5276
5971
|
console.log(`${GREEN5}Exported CSS variables:${RESET5}`);
|
|
5277
5972
|
console.log(` ${DIM5}File:${RESET5} ${out}`);
|
|
5278
5973
|
break;
|
|
@@ -5281,10 +5976,10 @@ async function cmdExport(target, projectRoot, options = {}) {
|
|
|
5281
5976
|
const out = resolveOutputPath(
|
|
5282
5977
|
projectRoot,
|
|
5283
5978
|
options.output,
|
|
5284
|
-
|
|
5979
|
+
join20(projectRoot, ".decantr", "design", "figma-tokens.json")
|
|
5285
5980
|
);
|
|
5286
5981
|
ensureDir(out);
|
|
5287
|
-
|
|
5982
|
+
writeFileSync13(out, generateFigmaTokens(tokens), "utf-8");
|
|
5288
5983
|
console.log(`${GREEN5}Exported Figma/Tokens Studio tokens:${RESET5}`);
|
|
5289
5984
|
console.log(` ${DIM5}File:${RESET5} ${out}`);
|
|
5290
5985
|
break;
|
|
@@ -5293,15 +5988,15 @@ async function cmdExport(target, projectRoot, options = {}) {
|
|
|
5293
5988
|
}
|
|
5294
5989
|
function ensureDir(filePath) {
|
|
5295
5990
|
const dir = dirname5(filePath);
|
|
5296
|
-
if (!
|
|
5297
|
-
|
|
5991
|
+
if (!existsSync18(dir)) {
|
|
5992
|
+
mkdirSync12(dir, { recursive: true });
|
|
5298
5993
|
}
|
|
5299
5994
|
}
|
|
5300
5995
|
|
|
5301
5996
|
// src/commands/magic.ts
|
|
5302
|
-
import { existsSync as
|
|
5997
|
+
import { existsSync as existsSync19 } from "fs";
|
|
5303
5998
|
import * as fs from "fs/promises";
|
|
5304
|
-
import { join as
|
|
5999
|
+
import { join as join21 } from "path";
|
|
5305
6000
|
var BOLD4 = "\x1B[1m";
|
|
5306
6001
|
var DIM6 = "\x1B[2m";
|
|
5307
6002
|
var RESET6 = "\x1B[0m";
|
|
@@ -5525,8 +6220,8 @@ async function cmdMagic(prompt, projectRoot, options) {
|
|
|
5525
6220
|
console.log(` Archetype: ${intent.archetype}`);
|
|
5526
6221
|
}
|
|
5527
6222
|
console.log("");
|
|
5528
|
-
const essencePath =
|
|
5529
|
-
if (
|
|
6223
|
+
const essencePath = join21(projectRoot, "decantr.essence.json");
|
|
6224
|
+
if (existsSync19(essencePath)) {
|
|
5530
6225
|
const projectFlag = options.projectLabel ? ` --project ${options.projectLabel}` : "";
|
|
5531
6226
|
console.log(`${YELLOW4} Decantr is already attached to this project.${RESET6}`);
|
|
5532
6227
|
console.log(
|
|
@@ -5559,7 +6254,7 @@ async function cmdMagic(prompt, projectRoot, options) {
|
|
|
5559
6254
|
return;
|
|
5560
6255
|
}
|
|
5561
6256
|
const registryClient = new RegistryClient({
|
|
5562
|
-
cacheDir:
|
|
6257
|
+
cacheDir: join21(projectRoot, ".decantr", "cache"),
|
|
5563
6258
|
apiUrl: options.registry,
|
|
5564
6259
|
offline: options.offline
|
|
5565
6260
|
});
|
|
@@ -5830,14 +6525,14 @@ async function cmdMagic(prompt, projectRoot, options) {
|
|
|
5830
6525
|
if (result.gitignoreUpdated) {
|
|
5831
6526
|
console.log(` ${dim(".gitignore updated")}`);
|
|
5832
6527
|
}
|
|
5833
|
-
const contextDir =
|
|
6528
|
+
const contextDir = join21(projectRoot, ".decantr", "context");
|
|
5834
6529
|
let sectionCount = 0;
|
|
5835
6530
|
try {
|
|
5836
6531
|
const files = await fs.readdir(contextDir);
|
|
5837
6532
|
sectionCount = files.filter((f) => f.startsWith("section-")).length;
|
|
5838
6533
|
} catch {
|
|
5839
6534
|
}
|
|
5840
|
-
const treatmentsPath =
|
|
6535
|
+
const treatmentsPath = join21(projectRoot, "src", "styles", "treatments.css");
|
|
5841
6536
|
let hasLayers = false;
|
|
5842
6537
|
try {
|
|
5843
6538
|
const css = await fs.readFile(treatmentsPath, "utf-8");
|
|
@@ -5870,8 +6565,8 @@ ${GREEN6}${BOLD4}Quality summary:${RESET6}`);
|
|
|
5870
6565
|
}
|
|
5871
6566
|
|
|
5872
6567
|
// src/commands/migrate.ts
|
|
5873
|
-
import { copyFileSync, existsSync as
|
|
5874
|
-
import { join as
|
|
6568
|
+
import { copyFileSync, existsSync as existsSync20, readFileSync as readFileSync16, writeFileSync as writeFileSync14 } from "fs";
|
|
6569
|
+
import { join as join22 } from "path";
|
|
5875
6570
|
import {
|
|
5876
6571
|
isV4 as isV45,
|
|
5877
6572
|
migrateToV4,
|
|
@@ -5884,12 +6579,12 @@ var YELLOW5 = "\x1B[33m";
|
|
|
5884
6579
|
var RESET7 = "\x1B[0m";
|
|
5885
6580
|
var DIM7 = "\x1B[2m";
|
|
5886
6581
|
function migrateEssenceFile(essencePath) {
|
|
5887
|
-
if (!
|
|
6582
|
+
if (!existsSync20(essencePath)) {
|
|
5888
6583
|
return { success: false, error: `File not found: ${essencePath}` };
|
|
5889
6584
|
}
|
|
5890
6585
|
let raw;
|
|
5891
6586
|
try {
|
|
5892
|
-
raw =
|
|
6587
|
+
raw = readFileSync16(essencePath, "utf-8");
|
|
5893
6588
|
} catch (e) {
|
|
5894
6589
|
return { success: false, error: `Could not read ${essencePath}: ${e.message}` };
|
|
5895
6590
|
}
|
|
@@ -5930,7 +6625,7 @@ function migrateEssenceFile(essencePath) {
|
|
|
5930
6625
|
};
|
|
5931
6626
|
}
|
|
5932
6627
|
try {
|
|
5933
|
-
|
|
6628
|
+
writeFileSync14(essencePath, JSON.stringify(v4, null, 2) + "\n");
|
|
5934
6629
|
} catch (e) {
|
|
5935
6630
|
return {
|
|
5936
6631
|
success: false,
|
|
@@ -5951,8 +6646,8 @@ async function cmdMigrate(projectRoot = process.cwd(), args = []) {
|
|
|
5951
6646
|
process.exitCode = 1;
|
|
5952
6647
|
return;
|
|
5953
6648
|
}
|
|
5954
|
-
const essencePath =
|
|
5955
|
-
if (!
|
|
6649
|
+
const essencePath = join22(projectRoot, "decantr.essence.json");
|
|
6650
|
+
if (!existsSync20(essencePath)) {
|
|
5956
6651
|
console.error(`${RED5}No decantr.essence.json found. Run \`decantr init\` first.${RESET7}`);
|
|
5957
6652
|
process.exitCode = 1;
|
|
5958
6653
|
return;
|
|
@@ -5974,7 +6669,7 @@ async function cmdMigrate(projectRoot = process.cwd(), args = []) {
|
|
|
5974
6669
|
}
|
|
5975
6670
|
if (result.essence) {
|
|
5976
6671
|
const registryClient = new RegistryClient({
|
|
5977
|
-
cacheDir:
|
|
6672
|
+
cacheDir: join22(projectRoot, ".decantr", "cache")
|
|
5978
6673
|
});
|
|
5979
6674
|
await refreshDerivedFiles(projectRoot, result.essence, registryClient);
|
|
5980
6675
|
console.log(`${GREEN7}Derived context and execution packs refreshed.${RESET7}`);
|
|
@@ -5985,50 +6680,50 @@ async function cmdMigrate(projectRoot = process.cwd(), args = []) {
|
|
|
5985
6680
|
|
|
5986
6681
|
// src/commands/new-project.ts
|
|
5987
6682
|
import { spawnSync } from "child_process";
|
|
5988
|
-
import { existsSync as
|
|
5989
|
-
import { join as
|
|
6683
|
+
import { existsSync as existsSync22, mkdirSync as mkdirSync14 } from "fs";
|
|
6684
|
+
import { join as join24, resolve as resolve3 } from "path";
|
|
5990
6685
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
5991
6686
|
|
|
5992
6687
|
// src/offline-content.ts
|
|
5993
|
-
import { cpSync, existsSync as
|
|
5994
|
-
import { join as
|
|
6688
|
+
import { cpSync, existsSync as existsSync21, mkdirSync as mkdirSync13 } from "fs";
|
|
6689
|
+
import { join as join23, resolve as resolve2 } from "path";
|
|
5995
6690
|
var CONTENT_TYPES2 = ["archetypes", "blueprints", "patterns", "themes", "shells"];
|
|
5996
6691
|
function copyIfExists(source, target) {
|
|
5997
|
-
if (!
|
|
6692
|
+
if (!existsSync21(source)) return false;
|
|
5998
6693
|
if (resolve2(source) === resolve2(target)) return true;
|
|
5999
6694
|
cpSync(source, target, { recursive: true });
|
|
6000
6695
|
return true;
|
|
6001
6696
|
}
|
|
6002
6697
|
function hydrateContentRoot(projectDir, contentRoot) {
|
|
6003
|
-
if (!
|
|
6004
|
-
const customRoot =
|
|
6005
|
-
const cacheRoot =
|
|
6006
|
-
|
|
6007
|
-
|
|
6698
|
+
if (!existsSync21(contentRoot)) return false;
|
|
6699
|
+
const customRoot = join23(projectDir, ".decantr", "custom");
|
|
6700
|
+
const cacheRoot = join23(projectDir, ".decantr", "cache", "@official");
|
|
6701
|
+
mkdirSync13(customRoot, { recursive: true });
|
|
6702
|
+
mkdirSync13(cacheRoot, { recursive: true });
|
|
6008
6703
|
let copiedAny = false;
|
|
6009
6704
|
for (const type of CONTENT_TYPES2) {
|
|
6010
|
-
const sourceDir =
|
|
6011
|
-
if (!
|
|
6012
|
-
cpSync(sourceDir,
|
|
6013
|
-
cpSync(sourceDir,
|
|
6705
|
+
const sourceDir = join23(contentRoot, type);
|
|
6706
|
+
if (!existsSync21(sourceDir)) continue;
|
|
6707
|
+
cpSync(sourceDir, join23(customRoot, type), { recursive: true });
|
|
6708
|
+
cpSync(sourceDir, join23(cacheRoot, type), { recursive: true });
|
|
6014
6709
|
copiedAny = true;
|
|
6015
6710
|
}
|
|
6016
6711
|
return copiedAny;
|
|
6017
6712
|
}
|
|
6018
6713
|
function seedOfflineRegistry(projectDir, workspaceRoot) {
|
|
6019
|
-
const projectDecantrRoot =
|
|
6020
|
-
|
|
6714
|
+
const projectDecantrRoot = join23(projectDir, ".decantr");
|
|
6715
|
+
mkdirSync13(projectDecantrRoot, { recursive: true });
|
|
6021
6716
|
const configuredContentRoot = process.env.DECANTR_CONTENT_DIR ? resolve2(process.env.DECANTR_CONTENT_DIR) : null;
|
|
6022
6717
|
if (configuredContentRoot && hydrateContentRoot(projectDir, configuredContentRoot)) {
|
|
6023
6718
|
return { seeded: true, strategy: "configured-content-root" };
|
|
6024
6719
|
}
|
|
6025
6720
|
const copiedCache = copyIfExists(
|
|
6026
|
-
|
|
6027
|
-
|
|
6721
|
+
join23(workspaceRoot, ".decantr", "cache"),
|
|
6722
|
+
join23(projectDecantrRoot, "cache")
|
|
6028
6723
|
);
|
|
6029
6724
|
const copiedCustom = copyIfExists(
|
|
6030
|
-
|
|
6031
|
-
|
|
6725
|
+
join23(workspaceRoot, ".decantr", "custom"),
|
|
6726
|
+
join23(projectDecantrRoot, "custom")
|
|
6032
6727
|
);
|
|
6033
6728
|
if (copiedCache || copiedCustom) {
|
|
6034
6729
|
return { seeded: true, strategy: "workspace-cache" };
|
|
@@ -6141,7 +6836,7 @@ function runArgvCommand(command, args, cwd) {
|
|
|
6141
6836
|
}
|
|
6142
6837
|
function resolveInitCommand(initFlags) {
|
|
6143
6838
|
const bundledCliEntrypoint = fileURLToPath2(new URL("./bin.js", import.meta.url));
|
|
6144
|
-
const cliEntrypoint =
|
|
6839
|
+
const cliEntrypoint = existsSync22(bundledCliEntrypoint) ? bundledCliEntrypoint : process.argv[1] && existsSync22(process.argv[1]) ? process.argv[1] : null;
|
|
6145
6840
|
if (cliEntrypoint) {
|
|
6146
6841
|
return {
|
|
6147
6842
|
command: process.execPath,
|
|
@@ -6169,7 +6864,7 @@ async function cmdNewProject(projectName, options) {
|
|
|
6169
6864
|
process.exitCode = 1;
|
|
6170
6865
|
return;
|
|
6171
6866
|
}
|
|
6172
|
-
if (
|
|
6867
|
+
if (existsSync22(projectDir)) {
|
|
6173
6868
|
console.error(error(`Directory "${projectName}" already exists.`));
|
|
6174
6869
|
process.exitCode = 1;
|
|
6175
6870
|
return;
|
|
@@ -6183,7 +6878,7 @@ async function cmdNewProject(projectName, options) {
|
|
|
6183
6878
|
return;
|
|
6184
6879
|
}
|
|
6185
6880
|
console.log(heading(`Creating ${projectName}...`));
|
|
6186
|
-
|
|
6881
|
+
mkdirSync14(projectDir, { recursive: true });
|
|
6187
6882
|
console.log(dim2(` Created ${projectName}/`));
|
|
6188
6883
|
const title = projectName.replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
6189
6884
|
if (shouldBootstrapRuntime && bootstrapAdapter) {
|
|
@@ -6282,21 +6977,21 @@ ${YELLOW6}Decantr init encountered issues. Run \`decantr init\` manually inside
|
|
|
6282
6977
|
}
|
|
6283
6978
|
}
|
|
6284
6979
|
function detectPackageManager4() {
|
|
6285
|
-
if (
|
|
6980
|
+
if (existsSync22(join24(process.cwd(), "pnpm-lock.yaml")) || existsSync22(join24(process.cwd(), "pnpm-workspace.yaml"))) {
|
|
6286
6981
|
return "pnpm";
|
|
6287
6982
|
}
|
|
6288
|
-
if (
|
|
6983
|
+
if (existsSync22(join24(process.cwd(), "yarn.lock"))) {
|
|
6289
6984
|
return "yarn";
|
|
6290
6985
|
}
|
|
6291
|
-
if (
|
|
6986
|
+
if (existsSync22(join24(process.cwd(), "bun.lockb")) || existsSync22(join24(process.cwd(), "bun.lock"))) {
|
|
6292
6987
|
return "bun";
|
|
6293
6988
|
}
|
|
6294
6989
|
return "npm";
|
|
6295
6990
|
}
|
|
6296
6991
|
|
|
6297
6992
|
// src/commands/publish.ts
|
|
6298
|
-
import { existsSync as
|
|
6299
|
-
import { join as
|
|
6993
|
+
import { existsSync as existsSync23, readFileSync as readFileSync17 } from "fs";
|
|
6994
|
+
import { join as join25 } from "path";
|
|
6300
6995
|
import {
|
|
6301
6996
|
API_CONTENT_TYPE_TO_CONTENT_TYPE,
|
|
6302
6997
|
CONTENT_TYPE_TO_API_CONTENT_TYPE as CONTENT_TYPE_TO_API_CONTENT_TYPE2,
|
|
@@ -6312,8 +7007,8 @@ async function cmdPublish(type, name, projectRoot = process.cwd()) {
|
|
|
6312
7007
|
}
|
|
6313
7008
|
const singularType = API_CONTENT_TYPE_TO_CONTENT_TYPE[type] || type;
|
|
6314
7009
|
const pluralType = CONTENT_TYPE_TO_API_CONTENT_TYPE2[type] || CONTENT_TYPE_TO_API_CONTENT_TYPE2[singularType] || `${type}s`;
|
|
6315
|
-
const customPath =
|
|
6316
|
-
if (!
|
|
7010
|
+
const customPath = join25(projectRoot, ".decantr", "custom", pluralType, `${name}.json`);
|
|
7011
|
+
if (!existsSync23(customPath)) {
|
|
6317
7012
|
console.error(`Custom ${singularType} "${name}" not found at ${customPath}`);
|
|
6318
7013
|
console.error(`Create one first: decantr create ${singularType} ${name}`);
|
|
6319
7014
|
process.exitCode = 1;
|
|
@@ -6321,7 +7016,7 @@ async function cmdPublish(type, name, projectRoot = process.cwd()) {
|
|
|
6321
7016
|
}
|
|
6322
7017
|
let data;
|
|
6323
7018
|
try {
|
|
6324
|
-
data = JSON.parse(
|
|
7019
|
+
data = JSON.parse(readFileSync17(customPath, "utf-8"));
|
|
6325
7020
|
} catch {
|
|
6326
7021
|
console.error(`Failed to parse ${customPath}`);
|
|
6327
7022
|
process.exitCode = 1;
|
|
@@ -6360,8 +7055,8 @@ async function cmdPublish(type, name, projectRoot = process.cwd()) {
|
|
|
6360
7055
|
|
|
6361
7056
|
// src/commands/refresh.ts
|
|
6362
7057
|
import { createHash } from "crypto";
|
|
6363
|
-
import { existsSync as
|
|
6364
|
-
import { isAbsolute as isAbsolute2, join as
|
|
7058
|
+
import { existsSync as existsSync24, readdirSync as readdirSync7, readFileSync as readFileSync18, statSync as statSync6 } from "fs";
|
|
7059
|
+
import { isAbsolute as isAbsolute2, join as join26, relative as relative6 } from "path";
|
|
6365
7060
|
import { collectMissingPackManifestFiles as collectMissingPackManifestFiles2 } from "@decantr/verifier";
|
|
6366
7061
|
import { isV4 as isV46 } from "@decantr/essence-spec";
|
|
6367
7062
|
var GREEN9 = "\x1B[32m";
|
|
@@ -6369,13 +7064,13 @@ var RED7 = "\x1B[31m";
|
|
|
6369
7064
|
var DIM9 = "\x1B[2m";
|
|
6370
7065
|
var RESET9 = "\x1B[0m";
|
|
6371
7066
|
function hashFile(path) {
|
|
6372
|
-
return createHash("sha256").update(
|
|
7067
|
+
return createHash("sha256").update(readFileSync18(path)).digest("hex");
|
|
6373
7068
|
}
|
|
6374
7069
|
function walkFiles(dir) {
|
|
6375
|
-
if (!
|
|
7070
|
+
if (!existsSync24(dir)) return [];
|
|
6376
7071
|
const files = [];
|
|
6377
7072
|
for (const entry of readdirSync7(dir, { withFileTypes: true })) {
|
|
6378
|
-
const path =
|
|
7073
|
+
const path = join26(dir, entry.name);
|
|
6379
7074
|
if (entry.isDirectory()) {
|
|
6380
7075
|
files.push(...walkFiles(path));
|
|
6381
7076
|
} else if (entry.isFile()) {
|
|
@@ -6386,10 +7081,10 @@ function walkFiles(dir) {
|
|
|
6386
7081
|
}
|
|
6387
7082
|
function trackedGeneratedFiles(projectRoot) {
|
|
6388
7083
|
return [
|
|
6389
|
-
|
|
6390
|
-
...walkFiles(
|
|
6391
|
-
...["global.css", "tokens.css", "treatments.css", "decantr-bridge.css"].map((file) =>
|
|
6392
|
-
].filter((path) =>
|
|
7084
|
+
join26(projectRoot, "DECANTR.md"),
|
|
7085
|
+
...walkFiles(join26(projectRoot, ".decantr", "context")),
|
|
7086
|
+
...["global.css", "tokens.css", "treatments.css", "decantr-bridge.css"].map((file) => join26(projectRoot, "src", "styles", file)).filter((path) => existsSync24(path))
|
|
7087
|
+
].filter((path) => existsSync24(path));
|
|
6393
7088
|
}
|
|
6394
7089
|
function snapshotGeneratedFiles(projectRoot) {
|
|
6395
7090
|
const map = /* @__PURE__ */ new Map();
|
|
@@ -6412,33 +7107,33 @@ function fileMtimeMs(path) {
|
|
|
6412
7107
|
}
|
|
6413
7108
|
function newestInputMtime(projectRoot) {
|
|
6414
7109
|
return Math.max(
|
|
6415
|
-
fileMtimeMs(
|
|
6416
|
-
fileMtimeMs(
|
|
7110
|
+
fileMtimeMs(join26(projectRoot, "decantr.essence.json")),
|
|
7111
|
+
fileMtimeMs(join26(projectRoot, ".decantr", "project.json"))
|
|
6417
7112
|
);
|
|
6418
7113
|
}
|
|
6419
7114
|
function isContractOnlyProject2(projectRoot) {
|
|
6420
|
-
const projectJsonPath =
|
|
6421
|
-
if (!
|
|
7115
|
+
const projectJsonPath = join26(projectRoot, ".decantr", "project.json");
|
|
7116
|
+
if (!existsSync24(projectJsonPath)) return false;
|
|
6422
7117
|
try {
|
|
6423
|
-
const projectJson = JSON.parse(
|
|
7118
|
+
const projectJson = JSON.parse(readFileSync18(projectJsonPath, "utf-8"));
|
|
6424
7119
|
return projectJson.initialized?.adoptionMode === "contract-only";
|
|
6425
7120
|
} catch {
|
|
6426
7121
|
return false;
|
|
6427
7122
|
}
|
|
6428
7123
|
}
|
|
6429
7124
|
function checkRefreshFreshness(projectRoot) {
|
|
6430
|
-
const contextDir =
|
|
7125
|
+
const contextDir = join26(projectRoot, ".decantr", "context");
|
|
6431
7126
|
const generated = trackedGeneratedFiles(projectRoot);
|
|
6432
7127
|
const reasons = [];
|
|
6433
7128
|
const packHydrationOptional = isContractOnlyProject2(projectRoot);
|
|
6434
|
-
if (!
|
|
6435
|
-
if (!
|
|
6436
|
-
if (!
|
|
7129
|
+
if (!existsSync24(join26(projectRoot, "DECANTR.md"))) reasons.push("DECANTR.md is missing.");
|
|
7130
|
+
if (!existsSync24(contextDir)) reasons.push(".decantr/context is missing.");
|
|
7131
|
+
if (!existsSync24(join26(contextDir, "scaffold.md"))) {
|
|
6437
7132
|
reasons.push(".decantr/context/scaffold.md is missing.");
|
|
6438
7133
|
}
|
|
6439
|
-
if (!packHydrationOptional && !
|
|
7134
|
+
if (!packHydrationOptional && !existsSync24(join26(contextDir, "pack-manifest.json"))) {
|
|
6440
7135
|
reasons.push(".decantr/context/pack-manifest.json is missing.");
|
|
6441
|
-
} else if (
|
|
7136
|
+
} else if (existsSync24(join26(contextDir, "pack-manifest.json"))) {
|
|
6442
7137
|
const missingPackFiles = collectMissingPackManifestFiles2(projectRoot);
|
|
6443
7138
|
if (missingPackFiles.length > 0) {
|
|
6444
7139
|
reasons.push(
|
|
@@ -6491,7 +7186,7 @@ function summarizeChanges(projectRoot, before, after) {
|
|
|
6491
7186
|
}
|
|
6492
7187
|
function displayGeneratedPath(summary, file, displayRoot) {
|
|
6493
7188
|
if (!displayRoot) return file;
|
|
6494
|
-
const absolutePath =
|
|
7189
|
+
const absolutePath = join26(summary.projectRoot, file);
|
|
6495
7190
|
const relativePath = relative6(displayRoot, absolutePath).replace(/\\/g, "/");
|
|
6496
7191
|
if (relativePath && !relativePath.startsWith("..") && !isAbsolute2(relativePath)) {
|
|
6497
7192
|
return relativePath;
|
|
@@ -6526,15 +7221,15 @@ function printRefreshSummary(summary, displayRoot) {
|
|
|
6526
7221
|
}
|
|
6527
7222
|
}
|
|
6528
7223
|
async function cmdRefresh(projectRoot = process.cwd(), options = {}) {
|
|
6529
|
-
const essencePath =
|
|
6530
|
-
if (!
|
|
7224
|
+
const essencePath = join26(projectRoot, "decantr.essence.json");
|
|
7225
|
+
if (!existsSync24(essencePath)) {
|
|
6531
7226
|
console.error(`${RED7}No decantr.essence.json found. Run \`decantr init\` first.${RESET9}`);
|
|
6532
7227
|
process.exitCode = 1;
|
|
6533
7228
|
return;
|
|
6534
7229
|
}
|
|
6535
7230
|
let essence;
|
|
6536
7231
|
try {
|
|
6537
|
-
const raw =
|
|
7232
|
+
const raw = readFileSync18(essencePath, "utf-8");
|
|
6538
7233
|
const parsed = JSON.parse(raw);
|
|
6539
7234
|
if (!isV46(parsed)) {
|
|
6540
7235
|
console.error(
|
|
@@ -6560,7 +7255,7 @@ async function cmdRefresh(projectRoot = process.cwd(), options = {}) {
|
|
|
6560
7255
|
return;
|
|
6561
7256
|
}
|
|
6562
7257
|
const registryClient = new RegistryClient({
|
|
6563
|
-
cacheDir:
|
|
7258
|
+
cacheDir: join26(projectRoot, ".decantr", "cache"),
|
|
6564
7259
|
offline: options.offline
|
|
6565
7260
|
});
|
|
6566
7261
|
console.log("Regenerating derived files...\n");
|
|
@@ -6591,8 +7286,8 @@ async function cmdRefresh(projectRoot = process.cwd(), options = {}) {
|
|
|
6591
7286
|
}
|
|
6592
7287
|
|
|
6593
7288
|
// src/commands/registry-mirror.ts
|
|
6594
|
-
import { mkdirSync as
|
|
6595
|
-
import { join as
|
|
7289
|
+
import { mkdirSync as mkdirSync15, writeFileSync as writeFileSync15 } from "fs";
|
|
7290
|
+
import { join as join27 } from "path";
|
|
6596
7291
|
import { API_CONTENT_TYPES, RegistryAPIClient as RegistryAPIClient2 } from "@decantr/registry";
|
|
6597
7292
|
var GREEN10 = "\x1B[32m";
|
|
6598
7293
|
var RED8 = "\x1B[31m";
|
|
@@ -6620,7 +7315,7 @@ async function cmdRegistryMirror(projectRoot, options = {}) {
|
|
|
6620
7315
|
process.exitCode = 1;
|
|
6621
7316
|
return;
|
|
6622
7317
|
}
|
|
6623
|
-
const cacheDir =
|
|
7318
|
+
const cacheDir = join27(projectRoot, ".decantr", "cache");
|
|
6624
7319
|
const counts = {};
|
|
6625
7320
|
const failed = [];
|
|
6626
7321
|
console.log(`
|
|
@@ -6630,19 +7325,19 @@ Mirroring registry content to ${DIM10}.decantr/cache/${RESET10}
|
|
|
6630
7325
|
try {
|
|
6631
7326
|
const result = await apiClient.listContent(type, { namespace: "@official" });
|
|
6632
7327
|
const items = result.items;
|
|
6633
|
-
const typeDir =
|
|
6634
|
-
|
|
6635
|
-
|
|
7328
|
+
const typeDir = join27(cacheDir, "@official", type);
|
|
7329
|
+
mkdirSync15(typeDir, { recursive: true });
|
|
7330
|
+
writeFileSync15(join27(typeDir, "index.json"), JSON.stringify(result, null, 2));
|
|
6636
7331
|
let itemCount = 0;
|
|
6637
7332
|
for (const item of items) {
|
|
6638
7333
|
const slug = item.slug || item.id;
|
|
6639
7334
|
if (!slug) continue;
|
|
6640
7335
|
try {
|
|
6641
7336
|
const fullItem = await apiClient.getContent(type, "@official", slug);
|
|
6642
|
-
|
|
7337
|
+
writeFileSync15(join27(typeDir, `${slug}.json`), JSON.stringify(fullItem, null, 2));
|
|
6643
7338
|
itemCount++;
|
|
6644
7339
|
} catch {
|
|
6645
|
-
|
|
7340
|
+
writeFileSync15(join27(typeDir, `${slug}.json`), JSON.stringify(item, null, 2));
|
|
6646
7341
|
itemCount++;
|
|
6647
7342
|
}
|
|
6648
7343
|
}
|
|
@@ -6657,8 +7352,8 @@ Mirroring registry content to ${DIM10}.decantr/cache/${RESET10}
|
|
|
6657
7352
|
mirrored_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6658
7353
|
counts
|
|
6659
7354
|
};
|
|
6660
|
-
|
|
6661
|
-
|
|
7355
|
+
mkdirSync15(join27(cacheDir), { recursive: true });
|
|
7356
|
+
writeFileSync15(join27(cacheDir, "mirror-manifest.json"), JSON.stringify(manifest, null, 2));
|
|
6662
7357
|
const totalItems = Object.values(counts).reduce((a, b) => a + b, 0);
|
|
6663
7358
|
console.log("");
|
|
6664
7359
|
if (failed.length > 0) {
|
|
@@ -6675,8 +7370,8 @@ Mirroring registry content to ${DIM10}.decantr/cache/${RESET10}
|
|
|
6675
7370
|
}
|
|
6676
7371
|
|
|
6677
7372
|
// src/commands/remove.ts
|
|
6678
|
-
import { existsSync as
|
|
6679
|
-
import { join as
|
|
7373
|
+
import { existsSync as existsSync26, readFileSync as readFileSync19, rmSync as rmSync2, writeFileSync as writeFileSync16 } from "fs";
|
|
7374
|
+
import { join as join28 } from "path";
|
|
6680
7375
|
import { isV4 as isV47 } from "@decantr/essence-spec";
|
|
6681
7376
|
var GREEN11 = "\x1B[32m";
|
|
6682
7377
|
var RED9 = "\x1B[31m";
|
|
@@ -6684,15 +7379,15 @@ var YELLOW8 = "\x1B[33m";
|
|
|
6684
7379
|
var DIM11 = "\x1B[2m";
|
|
6685
7380
|
var RESET11 = "\x1B[0m";
|
|
6686
7381
|
function readV4Essence2(projectRoot) {
|
|
6687
|
-
const essencePath =
|
|
6688
|
-
if (!
|
|
7382
|
+
const essencePath = join28(projectRoot, "decantr.essence.json");
|
|
7383
|
+
if (!existsSync26(essencePath)) {
|
|
6689
7384
|
console.error(`${RED9}No decantr.essence.json found. Run \`decantr init\` first.${RESET11}`);
|
|
6690
7385
|
process.exitCode = 1;
|
|
6691
7386
|
return null;
|
|
6692
7387
|
}
|
|
6693
7388
|
let parsed;
|
|
6694
7389
|
try {
|
|
6695
|
-
parsed = JSON.parse(
|
|
7390
|
+
parsed = JSON.parse(readFileSync19(essencePath, "utf-8"));
|
|
6696
7391
|
} catch (e) {
|
|
6697
7392
|
console.error(`${RED9}Could not read essence: ${e.message}${RESET11}`);
|
|
6698
7393
|
process.exitCode = 1;
|
|
@@ -6708,7 +7403,7 @@ function readV4Essence2(projectRoot) {
|
|
|
6708
7403
|
return { essence: parsed, essencePath };
|
|
6709
7404
|
}
|
|
6710
7405
|
function writeEssence2(essencePath, essence) {
|
|
6711
|
-
|
|
7406
|
+
writeFileSync16(essencePath, JSON.stringify(essence, null, 2) + "\n");
|
|
6712
7407
|
}
|
|
6713
7408
|
function readFlagValue2(args, name) {
|
|
6714
7409
|
const prefix = `--${name}=`;
|
|
@@ -6780,14 +7475,14 @@ async function cmdRemoveSection(sectionId, args, projectRoot = process.cwd()) {
|
|
|
6780
7475
|
sections.splice(idx, 1);
|
|
6781
7476
|
recomputeGlobalFeatures(essence);
|
|
6782
7477
|
removeRoutes(essence, sectionId);
|
|
6783
|
-
const contextFile =
|
|
6784
|
-
if (
|
|
7478
|
+
const contextFile = join28(projectRoot, ".decantr", "context", `${sectionId}.md`);
|
|
7479
|
+
if (existsSync26(contextFile)) {
|
|
6785
7480
|
rmSync2(contextFile);
|
|
6786
7481
|
}
|
|
6787
7482
|
writeEssence2(essencePath, essence);
|
|
6788
7483
|
console.log(`${GREEN11}Removed section "${sectionId}".${RESET11}`);
|
|
6789
7484
|
const registryClient = new RegistryClient({
|
|
6790
|
-
cacheDir:
|
|
7485
|
+
cacheDir: join28(projectRoot, ".decantr", "cache")
|
|
6791
7486
|
});
|
|
6792
7487
|
await refreshDerivedFiles(projectRoot, essence, registryClient);
|
|
6793
7488
|
console.log(`${GREEN11}Derived files refreshed.${RESET11}`);
|
|
@@ -6830,7 +7525,7 @@ async function cmdRemovePage(path, args, projectRoot = process.cwd()) {
|
|
|
6830
7525
|
}
|
|
6831
7526
|
console.log(`${GREEN11}Removed page "${pageId}" from section "${resolvedSectionId}".${RESET11}`);
|
|
6832
7527
|
const registryClient = new RegistryClient({
|
|
6833
|
-
cacheDir:
|
|
7528
|
+
cacheDir: join28(projectRoot, ".decantr", "cache")
|
|
6834
7529
|
});
|
|
6835
7530
|
await refreshDerivedFiles(projectRoot, essence, registryClient);
|
|
6836
7531
|
console.log(`${GREEN11}Derived files refreshed.${RESET11}`);
|
|
@@ -6873,15 +7568,15 @@ async function cmdRemoveFeature(feature, args, projectRoot = process.cwd()) {
|
|
|
6873
7568
|
const target = sectionId ? `section "${sectionId}" and global` : "global";
|
|
6874
7569
|
console.log(`${GREEN11}Removed feature "${feature}" from ${target} features.${RESET11}`);
|
|
6875
7570
|
const registryClient = new RegistryClient({
|
|
6876
|
-
cacheDir:
|
|
7571
|
+
cacheDir: join28(projectRoot, ".decantr", "cache")
|
|
6877
7572
|
});
|
|
6878
7573
|
await refreshDerivedFiles(projectRoot, essence, registryClient);
|
|
6879
7574
|
console.log(`${GREEN11}Derived files refreshed.${RESET11}`);
|
|
6880
7575
|
}
|
|
6881
7576
|
|
|
6882
7577
|
// src/commands/sync-drift.ts
|
|
6883
|
-
import { existsSync as
|
|
6884
|
-
import { join as
|
|
7578
|
+
import { existsSync as existsSync27, readFileSync as readFileSync20, writeFileSync as writeFileSync17 } from "fs";
|
|
7579
|
+
import { join as join29 } from "path";
|
|
6885
7580
|
var GREEN12 = "\x1B[32m";
|
|
6886
7581
|
var RED10 = "\x1B[31m";
|
|
6887
7582
|
var YELLOW9 = "\x1B[33m";
|
|
@@ -6890,8 +7585,8 @@ var DIM12 = "\x1B[2m";
|
|
|
6890
7585
|
var BOLD6 = "\x1B[1m";
|
|
6891
7586
|
var CYAN6 = "\x1B[36m";
|
|
6892
7587
|
async function cmdSyncDrift(projectRoot = process.cwd()) {
|
|
6893
|
-
const driftLogPath =
|
|
6894
|
-
if (!
|
|
7588
|
+
const driftLogPath = join29(projectRoot, ".decantr", "drift-log.json");
|
|
7589
|
+
if (!existsSync27(driftLogPath)) {
|
|
6895
7590
|
console.log(`${GREEN12}No drift log found \u2014 no drift recorded.${RESET12}`);
|
|
6896
7591
|
console.log(
|
|
6897
7592
|
`${DIM12}Drift is logged when guard violations are detected during development.${RESET12}`
|
|
@@ -6900,7 +7595,7 @@ async function cmdSyncDrift(projectRoot = process.cwd()) {
|
|
|
6900
7595
|
}
|
|
6901
7596
|
let entries;
|
|
6902
7597
|
try {
|
|
6903
|
-
entries = JSON.parse(
|
|
7598
|
+
entries = JSON.parse(readFileSync20(driftLogPath, "utf-8"));
|
|
6904
7599
|
} catch {
|
|
6905
7600
|
console.error(`${RED10}Could not parse drift-log.json${RESET12}`);
|
|
6906
7601
|
process.exitCode = 1;
|
|
@@ -6946,13 +7641,13 @@ ${BOLD6}Unresolved Drift Entries (${unresolved.length})${RESET12}
|
|
|
6946
7641
|
console.log("");
|
|
6947
7642
|
}
|
|
6948
7643
|
function resolveDriftEntries(projectRoot, options) {
|
|
6949
|
-
const driftLogPath =
|
|
6950
|
-
if (!
|
|
7644
|
+
const driftLogPath = join29(projectRoot, ".decantr", "drift-log.json");
|
|
7645
|
+
if (!existsSync27(driftLogPath)) {
|
|
6951
7646
|
return { success: true };
|
|
6952
7647
|
}
|
|
6953
7648
|
if (options.clear) {
|
|
6954
7649
|
try {
|
|
6955
|
-
|
|
7650
|
+
writeFileSync17(driftLogPath, "[]");
|
|
6956
7651
|
return { success: true };
|
|
6957
7652
|
} catch (e) {
|
|
6958
7653
|
return { success: false, error: `Could not clear drift log: ${e.message}` };
|
|
@@ -6960,7 +7655,7 @@ function resolveDriftEntries(projectRoot, options) {
|
|
|
6960
7655
|
}
|
|
6961
7656
|
let entries;
|
|
6962
7657
|
try {
|
|
6963
|
-
entries = JSON.parse(
|
|
7658
|
+
entries = JSON.parse(readFileSync20(driftLogPath, "utf-8"));
|
|
6964
7659
|
} catch {
|
|
6965
7660
|
return { success: false, error: "Could not parse drift-log.json" };
|
|
6966
7661
|
}
|
|
@@ -6979,7 +7674,7 @@ function resolveDriftEntries(projectRoot, options) {
|
|
|
6979
7674
|
}
|
|
6980
7675
|
}
|
|
6981
7676
|
try {
|
|
6982
|
-
|
|
7677
|
+
writeFileSync17(driftLogPath, JSON.stringify(entries, null, 2));
|
|
6983
7678
|
return { success: true };
|
|
6984
7679
|
} catch (e) {
|
|
6985
7680
|
return { success: false, error: `Could not write drift log: ${e.message}` };
|
|
@@ -7204,8 +7899,8 @@ function trimTrailingSlashes(value) {
|
|
|
7204
7899
|
}
|
|
7205
7900
|
|
|
7206
7901
|
// src/commands/theme-switch.ts
|
|
7207
|
-
import { existsSync as
|
|
7208
|
-
import { join as
|
|
7902
|
+
import { existsSync as existsSync28, readFileSync as readFileSync21, writeFileSync as writeFileSync18 } from "fs";
|
|
7903
|
+
import { join as join30 } from "path";
|
|
7209
7904
|
import { isV4 as isV48 } from "@decantr/essence-spec";
|
|
7210
7905
|
var GREEN14 = "\x1B[32m";
|
|
7211
7906
|
var RED11 = "\x1B[31m";
|
|
@@ -7222,15 +7917,15 @@ async function cmdThemeSwitch(themeName, args, projectRoot = process.cwd()) {
|
|
|
7222
7917
|
process.exitCode = 1;
|
|
7223
7918
|
return;
|
|
7224
7919
|
}
|
|
7225
|
-
const essencePath =
|
|
7226
|
-
if (!
|
|
7920
|
+
const essencePath = join30(projectRoot, "decantr.essence.json");
|
|
7921
|
+
if (!existsSync28(essencePath)) {
|
|
7227
7922
|
console.error(`${RED11}No decantr.essence.json found. Run \`decantr init\` first.${RESET14}`);
|
|
7228
7923
|
process.exitCode = 1;
|
|
7229
7924
|
return;
|
|
7230
7925
|
}
|
|
7231
7926
|
let parsed;
|
|
7232
7927
|
try {
|
|
7233
|
-
parsed = JSON.parse(
|
|
7928
|
+
parsed = JSON.parse(readFileSync21(essencePath, "utf-8"));
|
|
7234
7929
|
} catch (e) {
|
|
7235
7930
|
console.error(`${RED11}Could not read essence: ${e.message}${RESET14}`);
|
|
7236
7931
|
process.exitCode = 1;
|
|
@@ -7281,7 +7976,7 @@ async function cmdThemeSwitch(themeName, args, projectRoot = process.cwd()) {
|
|
|
7281
7976
|
essence.dna.theme.mode = mode;
|
|
7282
7977
|
}
|
|
7283
7978
|
const registryClient = new RegistryClient({
|
|
7284
|
-
cacheDir:
|
|
7979
|
+
cacheDir: join30(projectRoot, ".decantr", "cache")
|
|
7285
7980
|
});
|
|
7286
7981
|
try {
|
|
7287
7982
|
const themeResult = await registryClient.fetchTheme(themeName);
|
|
@@ -7296,7 +7991,7 @@ async function cmdThemeSwitch(themeName, args, projectRoot = process.cwd()) {
|
|
|
7296
7991
|
}
|
|
7297
7992
|
} catch {
|
|
7298
7993
|
}
|
|
7299
|
-
|
|
7994
|
+
writeFileSync18(essencePath, JSON.stringify(essence, null, 2) + "\n");
|
|
7300
7995
|
console.log(`${GREEN14}Switched theme: ${oldThemeId} \u2192 ${themeName}${RESET14}`);
|
|
7301
7996
|
if (shape) console.log(` ${DIM14}Shape: ${shape}${RESET14}`);
|
|
7302
7997
|
if (mode) console.log(` ${DIM14}Mode: ${mode}${RESET14}`);
|
|
@@ -7604,8 +8299,8 @@ async function runSimplifiedInit(blueprints) {
|
|
|
7604
8299
|
}
|
|
7605
8300
|
|
|
7606
8301
|
// src/theme-commands.ts
|
|
7607
|
-
import { existsSync as
|
|
7608
|
-
import { join as
|
|
8302
|
+
import { existsSync as existsSync29, mkdirSync as mkdirSync16, readdirSync as readdirSync8, readFileSync as readFileSync22, rmSync as rmSync3, writeFileSync as writeFileSync19 } from "fs";
|
|
8303
|
+
import { join as join31 } from "path";
|
|
7609
8304
|
var REQUIRED_FIELDS = [
|
|
7610
8305
|
"$schema",
|
|
7611
8306
|
"id",
|
|
@@ -7665,20 +8360,20 @@ function validateCustomTheme(theme) {
|
|
|
7665
8360
|
};
|
|
7666
8361
|
}
|
|
7667
8362
|
function createTheme(projectRoot, id, name) {
|
|
7668
|
-
const customThemesDir =
|
|
7669
|
-
const themePath =
|
|
7670
|
-
const howToPath =
|
|
7671
|
-
|
|
7672
|
-
if (
|
|
8363
|
+
const customThemesDir = join31(projectRoot, ".decantr", "custom", "themes");
|
|
8364
|
+
const themePath = join31(customThemesDir, `${id}.json`);
|
|
8365
|
+
const howToPath = join31(customThemesDir, "how-to-theme.md");
|
|
8366
|
+
mkdirSync16(customThemesDir, { recursive: true });
|
|
8367
|
+
if (existsSync29(themePath)) {
|
|
7673
8368
|
return {
|
|
7674
8369
|
success: false,
|
|
7675
8370
|
error: `Theme "${id}" already exists at ${themePath}`
|
|
7676
8371
|
};
|
|
7677
8372
|
}
|
|
7678
8373
|
const skeleton = getThemeSkeleton(id, name);
|
|
7679
|
-
|
|
7680
|
-
if (!
|
|
7681
|
-
|
|
8374
|
+
writeFileSync19(themePath, JSON.stringify(skeleton, null, 2));
|
|
8375
|
+
if (!existsSync29(howToPath)) {
|
|
8376
|
+
writeFileSync19(howToPath, getHowToThemeDoc());
|
|
7682
8377
|
}
|
|
7683
8378
|
return {
|
|
7684
8379
|
success: true,
|
|
@@ -7686,17 +8381,17 @@ function createTheme(projectRoot, id, name) {
|
|
|
7686
8381
|
};
|
|
7687
8382
|
}
|
|
7688
8383
|
function listCustomThemes(projectRoot) {
|
|
7689
|
-
const customThemesDir =
|
|
7690
|
-
if (!
|
|
8384
|
+
const customThemesDir = join31(projectRoot, ".decantr", "custom", "themes");
|
|
8385
|
+
if (!existsSync29(customThemesDir)) {
|
|
7691
8386
|
return [];
|
|
7692
8387
|
}
|
|
7693
8388
|
const themes = [];
|
|
7694
8389
|
try {
|
|
7695
8390
|
const files = readdirSync8(customThemesDir).filter((f) => f.endsWith(".json"));
|
|
7696
8391
|
for (const file of files) {
|
|
7697
|
-
const filePath =
|
|
8392
|
+
const filePath = join31(customThemesDir, file);
|
|
7698
8393
|
try {
|
|
7699
|
-
const data = JSON.parse(
|
|
8394
|
+
const data = JSON.parse(readFileSync22(filePath, "utf-8"));
|
|
7700
8395
|
themes.push({
|
|
7701
8396
|
id: data.id || file.replace(".json", ""),
|
|
7702
8397
|
name: data.name || data.id,
|
|
@@ -7711,8 +8406,8 @@ function listCustomThemes(projectRoot) {
|
|
|
7711
8406
|
return themes;
|
|
7712
8407
|
}
|
|
7713
8408
|
function deleteTheme(projectRoot, id) {
|
|
7714
|
-
const themePath =
|
|
7715
|
-
if (!
|
|
8409
|
+
const themePath = join31(projectRoot, ".decantr", "custom", "themes", `${id}.json`);
|
|
8410
|
+
if (!existsSync29(themePath)) {
|
|
7716
8411
|
return {
|
|
7717
8412
|
success: false,
|
|
7718
8413
|
error: `Theme "${id}" not found at ${themePath}`
|
|
@@ -7729,7 +8424,7 @@ function deleteTheme(projectRoot, id) {
|
|
|
7729
8424
|
}
|
|
7730
8425
|
}
|
|
7731
8426
|
function importTheme(projectRoot, sourcePath) {
|
|
7732
|
-
if (!
|
|
8427
|
+
if (!existsSync29(sourcePath)) {
|
|
7733
8428
|
return {
|
|
7734
8429
|
success: false,
|
|
7735
8430
|
errors: [`Source file not found: ${sourcePath}`]
|
|
@@ -7737,7 +8432,7 @@ function importTheme(projectRoot, sourcePath) {
|
|
|
7737
8432
|
}
|
|
7738
8433
|
let theme;
|
|
7739
8434
|
try {
|
|
7740
|
-
theme = JSON.parse(
|
|
8435
|
+
theme = JSON.parse(readFileSync22(sourcePath, "utf-8"));
|
|
7741
8436
|
} catch (e) {
|
|
7742
8437
|
return {
|
|
7743
8438
|
success: false,
|
|
@@ -7753,14 +8448,14 @@ function importTheme(projectRoot, sourcePath) {
|
|
|
7753
8448
|
}
|
|
7754
8449
|
theme.source = "custom";
|
|
7755
8450
|
const id = theme.id;
|
|
7756
|
-
const customThemesDir =
|
|
7757
|
-
const destPath =
|
|
7758
|
-
|
|
7759
|
-
const howToPath =
|
|
7760
|
-
if (!
|
|
7761
|
-
|
|
7762
|
-
}
|
|
7763
|
-
|
|
8451
|
+
const customThemesDir = join31(projectRoot, ".decantr", "custom", "themes");
|
|
8452
|
+
const destPath = join31(customThemesDir, `${id}.json`);
|
|
8453
|
+
mkdirSync16(customThemesDir, { recursive: true });
|
|
8454
|
+
const howToPath = join31(customThemesDir, "how-to-theme.md");
|
|
8455
|
+
if (!existsSync29(howToPath)) {
|
|
8456
|
+
writeFileSync19(howToPath, getHowToThemeDoc());
|
|
8457
|
+
}
|
|
8458
|
+
writeFileSync19(destPath, JSON.stringify(theme, null, 2));
|
|
7764
8459
|
return {
|
|
7765
8460
|
success: true,
|
|
7766
8461
|
path: destPath
|
|
@@ -8307,18 +9002,18 @@ function extractHostedAssetPaths(indexHtml) {
|
|
|
8307
9002
|
return [...assetPaths];
|
|
8308
9003
|
}
|
|
8309
9004
|
function readHostedDistSnapshot(distPath) {
|
|
8310
|
-
const resolvedDistPath = distPath ? resolveUserPath(distPath) :
|
|
8311
|
-
const indexPath =
|
|
8312
|
-
if (!
|
|
9005
|
+
const resolvedDistPath = distPath ? resolveUserPath(distPath) : join32(process.cwd(), "dist");
|
|
9006
|
+
const indexPath = join32(resolvedDistPath, "index.html");
|
|
9007
|
+
if (!existsSync30(indexPath)) {
|
|
8313
9008
|
return void 0;
|
|
8314
9009
|
}
|
|
8315
|
-
const indexHtml =
|
|
9010
|
+
const indexHtml = readFileSync23(indexPath, "utf-8");
|
|
8316
9011
|
const assetPaths = extractHostedAssetPaths(indexHtml);
|
|
8317
9012
|
const assets = {};
|
|
8318
9013
|
for (const assetPath of assetPaths) {
|
|
8319
|
-
const assetFilePath =
|
|
8320
|
-
if (
|
|
8321
|
-
assets[assetPath] =
|
|
9014
|
+
const assetFilePath = join32(resolvedDistPath, assetPath.replace(/^[/\\]+/, ""));
|
|
9015
|
+
if (existsSync30(assetFilePath)) {
|
|
9016
|
+
assets[assetPath] = readFileSync23(assetFilePath, "utf-8");
|
|
8322
9017
|
}
|
|
8323
9018
|
}
|
|
8324
9019
|
return {
|
|
@@ -8333,7 +9028,7 @@ function isHostedSourceSnapshotFile(path) {
|
|
|
8333
9028
|
function readHostedSourceSnapshot(sourcePath) {
|
|
8334
9029
|
if (!sourcePath) return void 0;
|
|
8335
9030
|
const resolvedSourcePath = resolveUserPath(sourcePath);
|
|
8336
|
-
if (!
|
|
9031
|
+
if (!existsSync30(resolvedSourcePath)) {
|
|
8337
9032
|
return void 0;
|
|
8338
9033
|
}
|
|
8339
9034
|
const files = {};
|
|
@@ -8349,15 +9044,15 @@ function readHostedSourceSnapshot(sourcePath) {
|
|
|
8349
9044
|
const walk = (absoluteDir, relativeDir) => {
|
|
8350
9045
|
for (const entry of readdirSync9(absoluteDir, { withFileTypes: true })) {
|
|
8351
9046
|
if (ignoredDirNames.has(entry.name)) continue;
|
|
8352
|
-
const absolutePath =
|
|
8353
|
-
const relativePath =
|
|
9047
|
+
const absolutePath = join32(absoluteDir, entry.name);
|
|
9048
|
+
const relativePath = join32(relativeDir, entry.name).replace(/\\/g, "/");
|
|
8354
9049
|
if (entry.isDirectory()) {
|
|
8355
9050
|
walk(absolutePath, relativePath);
|
|
8356
9051
|
continue;
|
|
8357
9052
|
}
|
|
8358
9053
|
if (!entry.isFile()) continue;
|
|
8359
9054
|
if (!isHostedSourceSnapshotFile(relativePath)) continue;
|
|
8360
|
-
files[relativePath] =
|
|
9055
|
+
files[relativePath] = readFileSync23(absolutePath, "utf-8");
|
|
8361
9056
|
}
|
|
8362
9057
|
};
|
|
8363
9058
|
walk(resolvedSourcePath, rootPrefix);
|
|
@@ -8550,24 +9245,24 @@ async function printHostedExecutionPackBundle(essencePath, namespace, jsonOutput
|
|
|
8550
9245
|
}
|
|
8551
9246
|
async function compileHostedExecutionPackBundle(essencePath, namespace) {
|
|
8552
9247
|
const client = getPublicAPIClient();
|
|
8553
|
-
const resolvedPath = essencePath ? resolveUserPath(essencePath) :
|
|
8554
|
-
if (!
|
|
9248
|
+
const resolvedPath = essencePath ? resolveUserPath(essencePath) : join32(process.cwd(), "decantr.essence.json");
|
|
9249
|
+
if (!existsSync30(resolvedPath)) {
|
|
8555
9250
|
throw new Error(`Essence file not found at ${resolvedPath}`);
|
|
8556
9251
|
}
|
|
8557
|
-
const essence = JSON.parse(
|
|
9252
|
+
const essence = JSON.parse(readFileSync23(resolvedPath, "utf-8"));
|
|
8558
9253
|
const bundle = await client.compileExecutionPacks(essence, namespace ? { namespace } : void 0);
|
|
8559
|
-
const contextDir =
|
|
9254
|
+
const contextDir = join32(dirname6(resolvedPath), ".decantr", "context");
|
|
8560
9255
|
return { resolvedPath, bundle, contextDir };
|
|
8561
9256
|
}
|
|
8562
9257
|
function writeHostedExecutionPackContextArtifacts(contextDir, bundle) {
|
|
8563
|
-
|
|
9258
|
+
mkdirSync17(contextDir, { recursive: true });
|
|
8564
9259
|
return writeExecutionPackBundleArtifacts(contextDir, bundle);
|
|
8565
9260
|
}
|
|
8566
9261
|
function resolvePagePackIdForRoute(essencePath, route) {
|
|
8567
|
-
if (!
|
|
9262
|
+
if (!existsSync30(essencePath)) {
|
|
8568
9263
|
throw new Error(`Essence file not found at ${essencePath}`);
|
|
8569
9264
|
}
|
|
8570
|
-
const essence = JSON.parse(
|
|
9265
|
+
const essence = JSON.parse(readFileSync23(essencePath, "utf-8"));
|
|
8571
9266
|
if (!isV49(essence)) {
|
|
8572
9267
|
throw new Error("Route-based pack resolution requires Essence v4.0.0.");
|
|
8573
9268
|
}
|
|
@@ -8582,14 +9277,14 @@ function resolvePagePackIdForRoute(essencePath, route) {
|
|
|
8582
9277
|
}
|
|
8583
9278
|
async function printHostedSelectedExecutionPack(packType, id, essencePath, namespace, jsonOutput = false, writeContext = false) {
|
|
8584
9279
|
const client = getPublicAPIClient();
|
|
8585
|
-
const resolvedPath = essencePath ? resolveUserPath(essencePath) :
|
|
8586
|
-
if (!
|
|
9280
|
+
const resolvedPath = essencePath ? resolveUserPath(essencePath) : join32(process.cwd(), "decantr.essence.json");
|
|
9281
|
+
if (!existsSync30(resolvedPath)) {
|
|
8587
9282
|
throw new Error(`Essence file not found at ${resolvedPath}`);
|
|
8588
9283
|
}
|
|
8589
9284
|
if ((packType === "section" || packType === "page" || packType === "mutation") && !id) {
|
|
8590
9285
|
throw new Error(`Pack type "${packType}" requires an id.`);
|
|
8591
9286
|
}
|
|
8592
|
-
const essence = JSON.parse(
|
|
9287
|
+
const essence = JSON.parse(readFileSync23(resolvedPath, "utf-8"));
|
|
8593
9288
|
const selected = await client.selectExecutionPack(
|
|
8594
9289
|
{
|
|
8595
9290
|
essence,
|
|
@@ -8600,17 +9295,17 @@ async function printHostedSelectedExecutionPack(packType, id, essencePath, names
|
|
|
8600
9295
|
);
|
|
8601
9296
|
let writtenContextDir = null;
|
|
8602
9297
|
if (writeContext) {
|
|
8603
|
-
const contextDir =
|
|
8604
|
-
|
|
8605
|
-
|
|
8606
|
-
|
|
9298
|
+
const contextDir = join32(dirname6(resolvedPath), ".decantr", "context");
|
|
9299
|
+
mkdirSync17(contextDir, { recursive: true });
|
|
9300
|
+
writeFileSync20(
|
|
9301
|
+
join32(contextDir, "pack-manifest.json"),
|
|
8607
9302
|
JSON.stringify(selected.manifest, null, 2) + "\n"
|
|
8608
9303
|
);
|
|
8609
9304
|
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);
|
|
8610
9305
|
const markdownFile = manifestEntry?.markdown ?? `${selected.selector.packType}${selected.selector.id ? `-${selected.selector.id}` : ""}-pack.md`;
|
|
8611
9306
|
const jsonFile = manifestEntry?.json ?? `${selected.selector.packType}${selected.selector.id ? `-${selected.selector.id}` : ""}-pack.json`;
|
|
8612
|
-
|
|
8613
|
-
|
|
9307
|
+
writeFileSync20(join32(contextDir, markdownFile), selected.pack.renderedMarkdown);
|
|
9308
|
+
writeFileSync20(join32(contextDir, jsonFile), JSON.stringify(selected.pack, null, 2) + "\n");
|
|
8614
9309
|
writtenContextDir = contextDir;
|
|
8615
9310
|
}
|
|
8616
9311
|
if (jsonOutput) {
|
|
@@ -8635,20 +9330,20 @@ async function printHostedSelectedExecutionPack(packType, id, essencePath, names
|
|
|
8635
9330
|
}
|
|
8636
9331
|
async function printHostedExecutionPackManifest(essencePath, namespace, jsonOutput = false, writeContext = false) {
|
|
8637
9332
|
const client = getPublicAPIClient();
|
|
8638
|
-
const resolvedPath = essencePath ? resolveUserPath(essencePath) :
|
|
8639
|
-
if (!
|
|
9333
|
+
const resolvedPath = essencePath ? resolveUserPath(essencePath) : join32(process.cwd(), "decantr.essence.json");
|
|
9334
|
+
if (!existsSync30(resolvedPath)) {
|
|
8640
9335
|
throw new Error(`Essence file not found at ${resolvedPath}`);
|
|
8641
9336
|
}
|
|
8642
|
-
const essence = JSON.parse(
|
|
9337
|
+
const essence = JSON.parse(readFileSync23(resolvedPath, "utf-8"));
|
|
8643
9338
|
const manifest = await client.getExecutionPackManifest(
|
|
8644
9339
|
essence,
|
|
8645
9340
|
namespace ? { namespace } : void 0
|
|
8646
9341
|
);
|
|
8647
9342
|
let writtenContextDir = null;
|
|
8648
9343
|
if (writeContext) {
|
|
8649
|
-
const contextDir =
|
|
8650
|
-
|
|
8651
|
-
|
|
9344
|
+
const contextDir = join32(dirname6(resolvedPath), ".decantr", "context");
|
|
9345
|
+
mkdirSync17(contextDir, { recursive: true });
|
|
9346
|
+
writeFileSync20(join32(contextDir, "pack-manifest.json"), JSON.stringify(manifest, null, 2) + "\n");
|
|
8652
9347
|
writtenContextDir = contextDir;
|
|
8653
9348
|
}
|
|
8654
9349
|
if (jsonOutput) {
|
|
@@ -8669,14 +9364,14 @@ async function printHostedExecutionPackManifest(essencePath, namespace, jsonOutp
|
|
|
8669
9364
|
}
|
|
8670
9365
|
}
|
|
8671
9366
|
async function hydrateHostedExecutionPacksIfMissing(projectRoot, namespace = "@official") {
|
|
8672
|
-
const contextDir =
|
|
8673
|
-
const reviewPackPath =
|
|
8674
|
-
const manifestPath =
|
|
8675
|
-
if (
|
|
9367
|
+
const contextDir = join32(projectRoot, ".decantr", "context");
|
|
9368
|
+
const reviewPackPath = join32(contextDir, "review-pack.json");
|
|
9369
|
+
const manifestPath = join32(contextDir, "pack-manifest.json");
|
|
9370
|
+
if (existsSync30(reviewPackPath) && existsSync30(manifestPath)) {
|
|
8676
9371
|
return { attempted: false, hydrated: false };
|
|
8677
9372
|
}
|
|
8678
|
-
const essencePath =
|
|
8679
|
-
if (!
|
|
9373
|
+
const essencePath = join32(projectRoot, "decantr.essence.json");
|
|
9374
|
+
if (!existsSync30(essencePath)) {
|
|
8680
9375
|
return { attempted: false, hydrated: false };
|
|
8681
9376
|
}
|
|
8682
9377
|
const reviewHydration = await hydrateHostedReviewPackIfMissing(projectRoot, namespace);
|
|
@@ -8685,9 +9380,9 @@ async function hydrateHostedExecutionPacksIfMissing(projectRoot, namespace = "@o
|
|
|
8685
9380
|
}
|
|
8686
9381
|
try {
|
|
8687
9382
|
const client = getPublicAPIClient();
|
|
8688
|
-
const essence = JSON.parse(
|
|
9383
|
+
const essence = JSON.parse(readFileSync23(essencePath, "utf-8"));
|
|
8689
9384
|
const bundle = await client.compileExecutionPacks(essence, { namespace });
|
|
8690
|
-
|
|
9385
|
+
mkdirSync17(contextDir, { recursive: true });
|
|
8691
9386
|
writeExecutionPackBundleArtifacts(contextDir, bundle);
|
|
8692
9387
|
return { attempted: true, hydrated: true, scope: "bundle" };
|
|
8693
9388
|
} catch {
|
|
@@ -8695,19 +9390,19 @@ async function hydrateHostedExecutionPacksIfMissing(projectRoot, namespace = "@o
|
|
|
8695
9390
|
}
|
|
8696
9391
|
}
|
|
8697
9392
|
async function hydrateHostedReviewPackIfMissing(projectRoot, namespace = "@official") {
|
|
8698
|
-
const contextDir =
|
|
8699
|
-
const reviewPackPath =
|
|
8700
|
-
const manifestPath =
|
|
8701
|
-
if (
|
|
9393
|
+
const contextDir = join32(projectRoot, ".decantr", "context");
|
|
9394
|
+
const reviewPackPath = join32(contextDir, "review-pack.json");
|
|
9395
|
+
const manifestPath = join32(contextDir, "pack-manifest.json");
|
|
9396
|
+
if (existsSync30(reviewPackPath) && existsSync30(manifestPath)) {
|
|
8702
9397
|
return { attempted: false, hydrated: false };
|
|
8703
9398
|
}
|
|
8704
|
-
const essencePath =
|
|
8705
|
-
if (!
|
|
9399
|
+
const essencePath = join32(projectRoot, "decantr.essence.json");
|
|
9400
|
+
if (!existsSync30(essencePath)) {
|
|
8706
9401
|
return { attempted: false, hydrated: false };
|
|
8707
9402
|
}
|
|
8708
9403
|
try {
|
|
8709
9404
|
const client = getPublicAPIClient();
|
|
8710
|
-
const essence = JSON.parse(
|
|
9405
|
+
const essence = JSON.parse(readFileSync23(essencePath, "utf-8"));
|
|
8711
9406
|
const selected = await client.selectExecutionPack(
|
|
8712
9407
|
{
|
|
8713
9408
|
essence,
|
|
@@ -8715,14 +9410,14 @@ async function hydrateHostedReviewPackIfMissing(projectRoot, namespace = "@offic
|
|
|
8715
9410
|
},
|
|
8716
9411
|
{ namespace }
|
|
8717
9412
|
);
|
|
8718
|
-
|
|
8719
|
-
|
|
8720
|
-
|
|
8721
|
-
|
|
9413
|
+
mkdirSync17(contextDir, { recursive: true });
|
|
9414
|
+
writeFileSync20(join32(contextDir, "review-pack.md"), selected.pack.renderedMarkdown);
|
|
9415
|
+
writeFileSync20(
|
|
9416
|
+
join32(contextDir, "review-pack.json"),
|
|
8722
9417
|
JSON.stringify(selected.pack, null, 2) + "\n"
|
|
8723
9418
|
);
|
|
8724
|
-
if (!
|
|
8725
|
-
|
|
9419
|
+
if (!existsSync30(manifestPath)) {
|
|
9420
|
+
writeFileSync20(manifestPath, JSON.stringify(selected.manifest, null, 2) + "\n");
|
|
8726
9421
|
}
|
|
8727
9422
|
return { attempted: true, hydrated: true, scope: "review" };
|
|
8728
9423
|
} catch {
|
|
@@ -8732,17 +9427,17 @@ async function hydrateHostedReviewPackIfMissing(projectRoot, namespace = "@offic
|
|
|
8732
9427
|
async function printHostedFileCritique(sourcePath, namespace, jsonOutput = false, essencePath, treatmentsPath) {
|
|
8733
9428
|
const client = getPublicAPIClient();
|
|
8734
9429
|
const resolvedSourcePath = resolveUserPath(sourcePath);
|
|
8735
|
-
const resolvedEssencePath = essencePath ? resolveUserPath(essencePath) :
|
|
8736
|
-
const resolvedTreatmentsPath = treatmentsPath ? resolveUserPath(treatmentsPath) :
|
|
8737
|
-
if (!
|
|
9430
|
+
const resolvedEssencePath = essencePath ? resolveUserPath(essencePath) : join32(process.cwd(), "decantr.essence.json");
|
|
9431
|
+
const resolvedTreatmentsPath = treatmentsPath ? resolveUserPath(treatmentsPath) : join32(process.cwd(), "src", "styles", "treatments.css");
|
|
9432
|
+
if (!existsSync30(resolvedSourcePath)) {
|
|
8738
9433
|
throw new Error(`Source file not found at ${resolvedSourcePath}`);
|
|
8739
9434
|
}
|
|
8740
|
-
if (!
|
|
9435
|
+
if (!existsSync30(resolvedEssencePath)) {
|
|
8741
9436
|
throw new Error(`Essence file not found at ${resolvedEssencePath}`);
|
|
8742
9437
|
}
|
|
8743
|
-
const code =
|
|
8744
|
-
const essence = JSON.parse(
|
|
8745
|
-
const treatmentsCss =
|
|
9438
|
+
const code = readFileSync23(resolvedSourcePath, "utf-8");
|
|
9439
|
+
const essence = JSON.parse(readFileSync23(resolvedEssencePath, "utf-8"));
|
|
9440
|
+
const treatmentsCss = existsSync30(resolvedTreatmentsPath) ? readFileSync23(resolvedTreatmentsPath, "utf-8") : void 0;
|
|
8746
9441
|
const report = await client.critiqueFile(
|
|
8747
9442
|
{
|
|
8748
9443
|
essence,
|
|
@@ -8766,11 +9461,11 @@ async function printHostedFileCritique(sourcePath, namespace, jsonOutput = false
|
|
|
8766
9461
|
}
|
|
8767
9462
|
async function printHostedProjectAudit(namespace, jsonOutput = false, essencePath, distPath, sourcesPath) {
|
|
8768
9463
|
const client = getPublicAPIClient();
|
|
8769
|
-
const resolvedEssencePath = essencePath ? resolveUserPath(essencePath) :
|
|
8770
|
-
if (!
|
|
9464
|
+
const resolvedEssencePath = essencePath ? resolveUserPath(essencePath) : join32(process.cwd(), "decantr.essence.json");
|
|
9465
|
+
if (!existsSync30(resolvedEssencePath)) {
|
|
8771
9466
|
throw new Error(`Essence file not found at ${resolvedEssencePath}`);
|
|
8772
9467
|
}
|
|
8773
|
-
const essence = JSON.parse(
|
|
9468
|
+
const essence = JSON.parse(readFileSync23(resolvedEssencePath, "utf-8"));
|
|
8774
9469
|
const dist = readHostedDistSnapshot(distPath);
|
|
8775
9470
|
const sources = readHostedSourceSnapshot(sourcesPath);
|
|
8776
9471
|
const report = await client.auditProject(
|
|
@@ -8788,7 +9483,7 @@ async function printHostedProjectAudit(namespace, jsonOutput = false, essencePat
|
|
|
8788
9483
|
console.log(heading2("Hosted Project Audit"));
|
|
8789
9484
|
console.log(` Essence: ${resolvedEssencePath}`);
|
|
8790
9485
|
console.log(
|
|
8791
|
-
` Dist snapshot: ${dist ? distPath ? resolveUserPath(distPath) :
|
|
9486
|
+
` Dist snapshot: ${dist ? distPath ? resolveUserPath(distPath) : join32(process.cwd(), "dist") : "none"}`
|
|
8792
9487
|
);
|
|
8793
9488
|
console.log(
|
|
8794
9489
|
` Source snapshot: ${sources && sourcesPath ? resolveUserPath(sourcesPath) : "none"}`
|
|
@@ -8888,24 +9583,43 @@ function patternCandidateFromRegistryItem(item, source) {
|
|
|
8888
9583
|
{ source, slug }
|
|
8889
9584
|
);
|
|
8890
9585
|
}
|
|
9586
|
+
function hostedPatternRefFromCandidate(candidate) {
|
|
9587
|
+
const slug = candidate.slug || candidate.id;
|
|
9588
|
+
return {
|
|
9589
|
+
slug,
|
|
9590
|
+
source: candidate.source ?? "registry",
|
|
9591
|
+
name: candidate.name,
|
|
9592
|
+
description: candidate.description,
|
|
9593
|
+
tags: candidate.tags,
|
|
9594
|
+
components: candidate.components,
|
|
9595
|
+
interactions: candidate.interactions,
|
|
9596
|
+
visualBrief: candidate.visual_brief
|
|
9597
|
+
};
|
|
9598
|
+
}
|
|
9599
|
+
function findPatternCandidateBySlug(candidates, slug) {
|
|
9600
|
+
const normalized = slug.toLowerCase();
|
|
9601
|
+
return candidates.find(
|
|
9602
|
+
(candidate) => candidate.slug?.toLowerCase() === normalized || candidate.id.toLowerCase() === normalized || candidate.name?.toLowerCase() === normalized
|
|
9603
|
+
) ?? null;
|
|
9604
|
+
}
|
|
8891
9605
|
function readSuggestCodeContext(projectRoot, route, file) {
|
|
8892
9606
|
const pieces = [];
|
|
8893
9607
|
if (file) {
|
|
8894
|
-
const resolved = isAbsolute3(file) ? file :
|
|
8895
|
-
if (
|
|
8896
|
-
pieces.push(
|
|
9608
|
+
const resolved = isAbsolute3(file) ? file : join32(projectRoot, file);
|
|
9609
|
+
if (existsSync30(resolved)) {
|
|
9610
|
+
pieces.push(readFileSync23(resolved, "utf-8"));
|
|
8897
9611
|
}
|
|
8898
9612
|
}
|
|
8899
9613
|
if (route) {
|
|
8900
|
-
const analysisPath =
|
|
8901
|
-
if (
|
|
9614
|
+
const analysisPath = join32(projectRoot, ".decantr", "analysis.json");
|
|
9615
|
+
if (existsSync30(analysisPath)) {
|
|
8902
9616
|
try {
|
|
8903
|
-
const analysis = JSON.parse(
|
|
9617
|
+
const analysis = JSON.parse(readFileSync23(analysisPath, "utf-8"));
|
|
8904
9618
|
const routeEntry = analysis.routes?.routes?.find((entry) => entry.path === route);
|
|
8905
9619
|
if (routeEntry?.file) {
|
|
8906
|
-
const resolved =
|
|
8907
|
-
if (
|
|
8908
|
-
pieces.push(
|
|
9620
|
+
const resolved = join32(projectRoot, routeEntry.file);
|
|
9621
|
+
if (existsSync30(resolved)) {
|
|
9622
|
+
pieces.push(readFileSync23(resolved, "utf-8"));
|
|
8909
9623
|
}
|
|
8910
9624
|
}
|
|
8911
9625
|
} catch {
|
|
@@ -8989,10 +9703,11 @@ async function cmdSuggest(query, options = {}) {
|
|
|
8989
9703
|
}
|
|
8990
9704
|
}
|
|
8991
9705
|
const registryClient = new RegistryClient({
|
|
8992
|
-
cacheDir:
|
|
9706
|
+
cacheDir: join32(projectRoot, ".decantr", "cache")
|
|
8993
9707
|
});
|
|
8994
9708
|
const code = options.fromCode || options.file ? readSuggestCodeContext(projectRoot, options.route, options.file) : "";
|
|
8995
9709
|
const localMatches = localPatternMatches(projectRoot, [query, code].filter(Boolean).join("\n"));
|
|
9710
|
+
const bridgeMatches = styleBridgeMatches(projectRoot, [query, code].filter(Boolean).join("\n"));
|
|
8996
9711
|
const candidates = await loadPatternDiscoveryCandidates(registryClient);
|
|
8997
9712
|
const matches = rankPatternCandidates(
|
|
8998
9713
|
{
|
|
@@ -9003,7 +9718,7 @@ async function cmdSuggest(query, options = {}) {
|
|
|
9003
9718
|
},
|
|
9004
9719
|
candidates
|
|
9005
9720
|
);
|
|
9006
|
-
if (matches.length === 0 && localMatches.length === 0) {
|
|
9721
|
+
if (matches.length === 0 && localMatches.length === 0 && bridgeMatches.length === 0) {
|
|
9007
9722
|
console.log(dim3(`No pattern suggestions for "${query}"`));
|
|
9008
9723
|
console.log("");
|
|
9009
9724
|
console.log("Try:");
|
|
@@ -9023,6 +9738,14 @@ async function cmdSuggest(query, options = {}) {
|
|
|
9023
9738
|
`Pattern suggestions for "${query}"${contextBits.length > 0 ? ` (${contextBits.join(", ")})` : ""}`
|
|
9024
9739
|
)
|
|
9025
9740
|
);
|
|
9741
|
+
if (bridgeMatches.length > 0) {
|
|
9742
|
+
console.log(`${BOLD9}Project-owned style bridge:${RESET16}`);
|
|
9743
|
+
for (const match of bridgeMatches) {
|
|
9744
|
+
const hints = [...match.tokenHints, ...match.classHints].slice(0, 3).join(", ");
|
|
9745
|
+
console.log(` ${cyan3(match.id)} ${match.label}${hints ? ` ${dim3(hints)}` : ""}`);
|
|
9746
|
+
}
|
|
9747
|
+
console.log("");
|
|
9748
|
+
}
|
|
9026
9749
|
if (localMatches.length > 0) {
|
|
9027
9750
|
console.log(`${BOLD9}Project-owned local law:${RESET16}`);
|
|
9028
9751
|
for (const match of localMatches) {
|
|
@@ -9067,7 +9790,7 @@ async function cmdGet(type, id) {
|
|
|
9067
9790
|
}
|
|
9068
9791
|
const apiType = CONTENT_TYPE_TO_API_CONTENT_TYPE3[type];
|
|
9069
9792
|
const registryClient = new RegistryClient({
|
|
9070
|
-
cacheDir:
|
|
9793
|
+
cacheDir: join32(process.cwd(), ".decantr", "cache")
|
|
9071
9794
|
});
|
|
9072
9795
|
const result = await registryClient.fetchContentItem(apiType, id);
|
|
9073
9796
|
if (result) {
|
|
@@ -9084,10 +9807,10 @@ async function cmdGet(type, id) {
|
|
|
9084
9807
|
return;
|
|
9085
9808
|
}
|
|
9086
9809
|
async function cmdValidate(path) {
|
|
9087
|
-
const essencePath = path ||
|
|
9810
|
+
const essencePath = path || join32(process.cwd(), "decantr.essence.json");
|
|
9088
9811
|
let raw;
|
|
9089
9812
|
try {
|
|
9090
|
-
raw =
|
|
9813
|
+
raw = readFileSync23(essencePath, "utf-8");
|
|
9091
9814
|
} catch {
|
|
9092
9815
|
console.error(error2(`Could not read ${essencePath}`));
|
|
9093
9816
|
process.exitCode = 1;
|
|
@@ -9154,7 +9877,7 @@ async function cmdList(type, sort, recommended, intelligenceSource, blueprintSet
|
|
|
9154
9877
|
return;
|
|
9155
9878
|
}
|
|
9156
9879
|
const registryClient = new RegistryClient({
|
|
9157
|
-
cacheDir:
|
|
9880
|
+
cacheDir: join32(process.cwd(), ".decantr", "cache")
|
|
9158
9881
|
});
|
|
9159
9882
|
const result = await registryClient.fetchContentList(
|
|
9160
9883
|
type,
|
|
@@ -9237,10 +9960,10 @@ ${CYAN9}Telemetry enabled.${RESET16} Decantr will send privacy-filtered CLI prod
|
|
|
9237
9960
|
}
|
|
9238
9961
|
function readCliPackageVersion2() {
|
|
9239
9962
|
const here = dirname6(fileURLToPath3(import.meta.url));
|
|
9240
|
-
const candidates = [
|
|
9963
|
+
const candidates = [join32(here, "..", "package.json"), join32(here, "..", "..", "package.json")];
|
|
9241
9964
|
for (const candidate of candidates) {
|
|
9242
9965
|
try {
|
|
9243
|
-
const pkg = JSON.parse(
|
|
9966
|
+
const pkg = JSON.parse(readFileSync23(candidate, "utf-8"));
|
|
9244
9967
|
if (pkg.version) return pkg.version;
|
|
9245
9968
|
} catch {
|
|
9246
9969
|
}
|
|
@@ -9251,19 +9974,19 @@ function timestampForFile() {
|
|
|
9251
9974
|
return (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
9252
9975
|
}
|
|
9253
9976
|
function backupExistingEssence(projectRoot, label) {
|
|
9254
|
-
const essencePath =
|
|
9255
|
-
if (!
|
|
9256
|
-
const backupPath =
|
|
9977
|
+
const essencePath = join32(projectRoot, "decantr.essence.json");
|
|
9978
|
+
if (!existsSync30(essencePath)) return null;
|
|
9979
|
+
const backupPath = join32(
|
|
9257
9980
|
projectRoot,
|
|
9258
9981
|
`decantr.essence.${label}.${timestampForFile()}.backup.json`
|
|
9259
9982
|
);
|
|
9260
|
-
|
|
9983
|
+
writeFileSync20(backupPath, readFileSync23(essencePath, "utf-8"), "utf-8");
|
|
9261
9984
|
return backupPath;
|
|
9262
9985
|
}
|
|
9263
9986
|
function writeBrownfieldProjectJson(input) {
|
|
9264
|
-
const decantrDir =
|
|
9265
|
-
|
|
9266
|
-
|
|
9987
|
+
const decantrDir = join32(input.projectRoot, ".decantr");
|
|
9988
|
+
mkdirSync17(join32(decantrDir, "context"), { recursive: true });
|
|
9989
|
+
mkdirSync17(join32(decantrDir, "cache"), { recursive: true });
|
|
9267
9990
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
9268
9991
|
const projectJson = {
|
|
9269
9992
|
detected: {
|
|
@@ -9304,7 +10027,7 @@ function writeBrownfieldProjectJson(input) {
|
|
|
9304
10027
|
}
|
|
9305
10028
|
}
|
|
9306
10029
|
};
|
|
9307
|
-
|
|
10030
|
+
writeFileSync20(join32(decantrDir, "project.json"), JSON.stringify(projectJson, null, 2) + "\n");
|
|
9308
10031
|
}
|
|
9309
10032
|
async function applyAcceptedBrownfieldProposal(input) {
|
|
9310
10033
|
const proposal = readBrownfieldProposal(input.projectRoot);
|
|
@@ -9320,8 +10043,8 @@ async function applyAcceptedBrownfieldProposal(input) {
|
|
|
9320
10043
|
process.exitCode = 1;
|
|
9321
10044
|
return;
|
|
9322
10045
|
}
|
|
9323
|
-
const essencePath =
|
|
9324
|
-
const hasEssence =
|
|
10046
|
+
const essencePath = join32(input.projectRoot, "decantr.essence.json");
|
|
10047
|
+
const hasEssence = existsSync30(essencePath);
|
|
9325
10048
|
let essence;
|
|
9326
10049
|
let backupPath = null;
|
|
9327
10050
|
if (input.mode === "accept" && hasEssence) {
|
|
@@ -9335,7 +10058,7 @@ async function applyAcceptedBrownfieldProposal(input) {
|
|
|
9335
10058
|
return;
|
|
9336
10059
|
}
|
|
9337
10060
|
if (input.mode === "merge" && hasEssence) {
|
|
9338
|
-
const existing = JSON.parse(
|
|
10061
|
+
const existing = JSON.parse(readFileSync23(essencePath, "utf-8"));
|
|
9339
10062
|
if (!isV49(existing)) {
|
|
9340
10063
|
console.log(
|
|
9341
10064
|
error2(
|
|
@@ -9372,9 +10095,9 @@ async function applyAcceptedBrownfieldProposal(input) {
|
|
|
9372
10095
|
assistantBridge: input.assistantBridge,
|
|
9373
10096
|
mode: input.mode
|
|
9374
10097
|
});
|
|
9375
|
-
|
|
10098
|
+
writeFileSync20(essencePath, JSON.stringify(essence, null, 2) + "\n", "utf-8");
|
|
9376
10099
|
const registryClient = new RegistryClient({
|
|
9377
|
-
cacheDir:
|
|
10100
|
+
cacheDir: join32(input.projectRoot, ".decantr", "cache"),
|
|
9378
10101
|
offline: true,
|
|
9379
10102
|
projectRoot: input.projectRoot
|
|
9380
10103
|
});
|
|
@@ -9553,7 +10276,7 @@ async function cmdInit(args) {
|
|
|
9553
10276
|
}
|
|
9554
10277
|
}
|
|
9555
10278
|
const registryClient = new RegistryClient({
|
|
9556
|
-
cacheDir:
|
|
10279
|
+
cacheDir: join32(projectRoot, ".decantr", "cache"),
|
|
9557
10280
|
apiUrl: args.registry,
|
|
9558
10281
|
offline: args.offline,
|
|
9559
10282
|
projectRoot
|
|
@@ -9885,7 +10608,7 @@ ${YELLOW12}You're offline. Scaffolding Decantr default.${RESET16}`);
|
|
|
9885
10608
|
if (appliedRuleFiles.length > 0) {
|
|
9886
10609
|
console.log(` ${dim3(`Rule bridge applied: ${appliedRuleFiles.join(", ")}`)}`);
|
|
9887
10610
|
}
|
|
9888
|
-
if (!
|
|
10611
|
+
if (!existsSync30(join32(projectRoot, "package.json"))) {
|
|
9889
10612
|
console.log("");
|
|
9890
10613
|
console.log(
|
|
9891
10614
|
dim3(` Note: ${cyan3("decantr init")} created Decantr contract/context files only.`)
|
|
@@ -9896,7 +10619,7 @@ ${YELLOW12}You're offline. Scaffolding Decantr default.${RESET16}`);
|
|
|
9896
10619
|
)
|
|
9897
10620
|
);
|
|
9898
10621
|
}
|
|
9899
|
-
const hasCompiledPacks =
|
|
10622
|
+
const hasCompiledPacks = existsSync30(join32(projectRoot, ".decantr", "context", "scaffold-pack.md"));
|
|
9900
10623
|
console.log("");
|
|
9901
10624
|
console.log(" Next steps:");
|
|
9902
10625
|
if (hasCompiledPacks) {
|
|
@@ -9936,7 +10659,7 @@ ${YELLOW12}You're offline. Scaffolding Decantr default.${RESET16}`);
|
|
|
9936
10659
|
console.log(` ${cyan3("decantr upgrade")} Update to latest patterns`);
|
|
9937
10660
|
console.log(` ${cyan3("decantr check")} Detect drift issues`);
|
|
9938
10661
|
console.log(` ${cyan3("decantr migrate --to v4")} Migrate older essence files to v4`);
|
|
9939
|
-
const essenceContent =
|
|
10662
|
+
const essenceContent = readFileSync23(result.essencePath, "utf-8");
|
|
9940
10663
|
const essence = JSON.parse(essenceContent);
|
|
9941
10664
|
const validation = validateEssence2(essence);
|
|
9942
10665
|
if (!validation.valid) {
|
|
@@ -9991,16 +10714,16 @@ Validation warnings: ${validation.errors.join(", ")}`));
|
|
|
9991
10714
|
}
|
|
9992
10715
|
}
|
|
9993
10716
|
async function cmdStatus(projectRoot = process.cwd()) {
|
|
9994
|
-
const essencePath =
|
|
9995
|
-
const projectJsonPath =
|
|
10717
|
+
const essencePath = join32(projectRoot, "decantr.essence.json");
|
|
10718
|
+
const projectJsonPath = join32(projectRoot, ".decantr", "project.json");
|
|
9996
10719
|
console.log(heading2("Decantr Project Status"));
|
|
9997
|
-
if (!
|
|
10720
|
+
if (!existsSync30(essencePath)) {
|
|
9998
10721
|
console.log(`${RED12}No decantr.essence.json found.${RESET16}`);
|
|
9999
10722
|
console.log(dim3('Run "decantr init" to create one.'));
|
|
10000
10723
|
return;
|
|
10001
10724
|
}
|
|
10002
10725
|
try {
|
|
10003
|
-
const essence = JSON.parse(
|
|
10726
|
+
const essence = JSON.parse(readFileSync23(essencePath, "utf-8"));
|
|
10004
10727
|
const validation = validateEssence2(essence);
|
|
10005
10728
|
const essenceVersion = isV49(essence) ? "v4" : "legacy";
|
|
10006
10729
|
console.log(`${BOLD9}Essence:${RESET16}`);
|
|
@@ -10048,9 +10771,9 @@ async function cmdStatus(projectRoot = process.cwd()) {
|
|
|
10048
10771
|
}
|
|
10049
10772
|
console.log("");
|
|
10050
10773
|
console.log(`${BOLD9}Sync Status:${RESET16}`);
|
|
10051
|
-
if (
|
|
10774
|
+
if (existsSync30(projectJsonPath)) {
|
|
10052
10775
|
try {
|
|
10053
|
-
const projectJson = JSON.parse(
|
|
10776
|
+
const projectJson = JSON.parse(readFileSync23(projectJsonPath, "utf-8"));
|
|
10054
10777
|
const syncStatus = projectJson.sync?.status || "unknown";
|
|
10055
10778
|
const lastSync = projectJson.sync?.lastSync || "never";
|
|
10056
10779
|
const source = projectJson.sync?.registrySource || "unknown";
|
|
@@ -10068,7 +10791,7 @@ async function cmdStatus(projectRoot = process.cwd()) {
|
|
|
10068
10791
|
}
|
|
10069
10792
|
async function cmdSync() {
|
|
10070
10793
|
const projectRoot = process.cwd();
|
|
10071
|
-
const cacheDir =
|
|
10794
|
+
const cacheDir = join32(projectRoot, ".decantr", "cache");
|
|
10072
10795
|
console.log(heading2("Syncing registry content..."));
|
|
10073
10796
|
const result = await syncRegistry(cacheDir);
|
|
10074
10797
|
if (result.synced.length > 0) {
|
|
@@ -10262,14 +10985,14 @@ ${BOLD9}Examples:${RESET16}
|
|
|
10262
10985
|
process.exitCode = 1;
|
|
10263
10986
|
return;
|
|
10264
10987
|
}
|
|
10265
|
-
const themePath =
|
|
10266
|
-
if (!
|
|
10988
|
+
const themePath = join32(projectRoot, ".decantr", "custom", "themes", `${name}.json`);
|
|
10989
|
+
if (!existsSync30(themePath)) {
|
|
10267
10990
|
console.error(error2(`Theme "${name}" not found at ${themePath}`));
|
|
10268
10991
|
process.exitCode = 1;
|
|
10269
10992
|
return;
|
|
10270
10993
|
}
|
|
10271
10994
|
try {
|
|
10272
|
-
const theme = JSON.parse(
|
|
10995
|
+
const theme = JSON.parse(readFileSync23(themePath, "utf-8"));
|
|
10273
10996
|
const result = validateCustomTheme(theme);
|
|
10274
10997
|
if (result.valid) {
|
|
10275
10998
|
console.log(success3(`Custom theme "${name}" is valid`));
|
|
@@ -10401,7 +11124,7 @@ function withProject(command, projectArg) {
|
|
|
10401
11124
|
return projectArg ? `${command} --project ${projectArg}` : command;
|
|
10402
11125
|
}
|
|
10403
11126
|
function displayProjectPath(workspaceInfo, projectPath) {
|
|
10404
|
-
const absolutePath =
|
|
11127
|
+
const absolutePath = join32(workspaceInfo.appRoot, projectPath);
|
|
10405
11128
|
const relativePath = relative7(workspaceInfo.cwd, absolutePath).replace(/\\/g, "/");
|
|
10406
11129
|
if (relativePath && !relativePath.startsWith("..") && !isAbsolute3(relativePath)) {
|
|
10407
11130
|
return relativePath;
|
|
@@ -10460,6 +11183,7 @@ function printWorkspaceProjectSelection(workspaceInfo, commandName = "command")
|
|
|
10460
11183
|
console.log("");
|
|
10461
11184
|
console.log("Start by attaching one app:");
|
|
10462
11185
|
console.log(` ${cyan3(`decantr adopt --project ${candidate} --yes`)}`);
|
|
11186
|
+
console.log(` ${cyan3(`decantr codify --from-audit --style-bridge --project ${candidate}`)}`);
|
|
10463
11187
|
console.log("");
|
|
10464
11188
|
console.log("Optional visual evidence after the app is running:");
|
|
10465
11189
|
console.log(
|
|
@@ -10469,7 +11193,7 @@ function printWorkspaceProjectSelection(workspaceInfo, commandName = "command")
|
|
|
10469
11193
|
function printMonorepoSetupGuidance(workspaceInfo) {
|
|
10470
11194
|
const candidate = firstWorkspaceCandidate(workspaceInfo);
|
|
10471
11195
|
const attachedProjects = workspaceInfo.appCandidates.filter(
|
|
10472
|
-
(appCandidate) =>
|
|
11196
|
+
(appCandidate) => existsSync30(join32(workspaceInfo.workspaceRoot, appCandidate, "decantr.essence.json"))
|
|
10473
11197
|
);
|
|
10474
11198
|
const firstAttached = attachedProjects[0];
|
|
10475
11199
|
console.log(heading2("Decantr Setup"));
|
|
@@ -10526,7 +11250,7 @@ function printMonorepoSetupGuidance(workspaceInfo) {
|
|
|
10526
11250
|
` ${cyan3(`decantr adopt --project ${candidate} --yes`)} Attach Decantr to one app`
|
|
10527
11251
|
);
|
|
10528
11252
|
console.log(
|
|
10529
|
-
` ${cyan3(`decantr codify --from-audit --project ${candidate}`)} Propose project-owned UI law`
|
|
11253
|
+
` ${cyan3(`decantr codify --from-audit --style-bridge --project ${candidate}`)} Propose project-owned UI law and style bridge`
|
|
10530
11254
|
);
|
|
10531
11255
|
console.log("");
|
|
10532
11256
|
console.log(`${BOLD9}Optional visual evidence:${RESET16}`);
|
|
@@ -10537,14 +11261,19 @@ function printMonorepoSetupGuidance(workspaceInfo) {
|
|
|
10537
11261
|
function resolveWorkflowProject(flags, commandName = "command", options = {}) {
|
|
10538
11262
|
const projectArg = flagString(flags, "project");
|
|
10539
11263
|
const workspaceInfo = resolveWorkspaceInfo(process.cwd(), projectArg);
|
|
10540
|
-
if (projectArg && options.requireExisting !== false && !
|
|
11264
|
+
if (projectArg && options.requireExisting !== false && !existsSync30(workspaceInfo.appRoot)) {
|
|
10541
11265
|
printProjectNotFound(projectArg, commandName);
|
|
10542
11266
|
process.exitCode = 1;
|
|
10543
11267
|
return null;
|
|
10544
11268
|
}
|
|
10545
11269
|
if (projectArg && options.requireAppCandidate && workspaceInfo.appCandidates.length > 0) {
|
|
10546
11270
|
const normalizedProject = normalizedProjectPath(projectArg);
|
|
10547
|
-
const
|
|
11271
|
+
const normalizedWorkspaceProject = normalizedProjectPath(
|
|
11272
|
+
relative7(workspaceInfo.workspaceRoot, workspaceInfo.appRoot)
|
|
11273
|
+
);
|
|
11274
|
+
const knownCandidate = normalizedProject ? workspaceInfo.appCandidates.includes(normalizedProject) || Boolean(
|
|
11275
|
+
normalizedWorkspaceProject && workspaceInfo.appCandidates.includes(normalizedWorkspaceProject)
|
|
11276
|
+
) : false;
|
|
10548
11277
|
const forcePackage = flagBoolean(flags, "force-package") || flagBoolean(flags, "allow-package") || flagBoolean(flags, "force");
|
|
10549
11278
|
if (!knownCandidate && !forcePackage && !options.allowPackageProject) {
|
|
10550
11279
|
console.error(
|
|
@@ -10577,6 +11306,101 @@ function printWorkflowPlan(title, steps) {
|
|
|
10577
11306
|
}
|
|
10578
11307
|
console.log("");
|
|
10579
11308
|
}
|
|
11309
|
+
function scanSeverityColor(finding) {
|
|
11310
|
+
if (finding.severity === "success") return success3("ok");
|
|
11311
|
+
if (finding.severity === "error") return error2("error");
|
|
11312
|
+
if (finding.severity === "warn") return `${YELLOW12}warn${RESET16}`;
|
|
11313
|
+
return cyan3("info");
|
|
11314
|
+
}
|
|
11315
|
+
function formatScanApplicability(status) {
|
|
11316
|
+
if (status === "strong_fit") return success3("strong fit");
|
|
11317
|
+
if (status === "partial_fit") return `${YELLOW12}partial fit${RESET16}`;
|
|
11318
|
+
if (status === "not_applicable") return dim3("not applicable");
|
|
11319
|
+
return dim3("unknown");
|
|
11320
|
+
}
|
|
11321
|
+
function printScanReport(report) {
|
|
11322
|
+
console.log(heading2("Decantr Scan"));
|
|
11323
|
+
console.log(dim3("Read-only Brownfield reconnaissance. No files were written."));
|
|
11324
|
+
console.log("");
|
|
11325
|
+
console.log(`${BOLD9}Verdict${RESET16}`);
|
|
11326
|
+
console.log(` ${formatScanApplicability(report.applicability.status)} ${report.applicability.label}`);
|
|
11327
|
+
console.log(
|
|
11328
|
+
` Confidence: ${cyan3(`${report.confidence.score}/100`)} (${report.confidence.level})`
|
|
11329
|
+
);
|
|
11330
|
+
for (const reason of report.applicability.reasons.slice(0, 3)) {
|
|
11331
|
+
console.log(` ${dim3("-")} ${reason}`);
|
|
11332
|
+
}
|
|
11333
|
+
console.log("");
|
|
11334
|
+
console.log(`${BOLD9}Project${RESET16}`);
|
|
11335
|
+
console.log(` Framework: ${cyan3(report.project.framework)}${report.project.frameworkVersion ? ` ${report.project.frameworkVersion}` : ""}`);
|
|
11336
|
+
console.log(` Package manager:${" "} ${report.project.packageManager}`);
|
|
11337
|
+
console.log(` Language: ${report.project.primaryLanguage}`);
|
|
11338
|
+
console.log(` TypeScript: ${report.project.hasTypeScript ? "yes" : "no"}`);
|
|
11339
|
+
console.log(` Decantr: ${report.project.hasDecantr ? "present" : "not attached"}`);
|
|
11340
|
+
console.log("");
|
|
11341
|
+
console.log(`${BOLD9}Routes And Styling${RESET16}`);
|
|
11342
|
+
console.log(` Routes: ${report.routes.count} (${report.routes.strategy})`);
|
|
11343
|
+
for (const route of report.routes.items.slice(0, 8)) {
|
|
11344
|
+
console.log(` ${cyan3(route.path.padEnd(18))} ${dim3(route.file)}`);
|
|
11345
|
+
}
|
|
11346
|
+
if (report.routes.items.length > 8) {
|
|
11347
|
+
console.log(` ${dim3(`...${report.routes.items.length - 8} more route(s)`)}`);
|
|
11348
|
+
}
|
|
11349
|
+
console.log(` Components: ${report.components.componentCount}`);
|
|
11350
|
+
console.log(
|
|
11351
|
+
` Styling: ${report.styling.approach}${report.styling.configFile ? ` (${report.styling.configFile})` : ""}`
|
|
11352
|
+
);
|
|
11353
|
+
console.log(` CSS variables: ${report.styling.cssVariableCount}`);
|
|
11354
|
+
console.log(` Dark mode: ${report.styling.darkMode ? "yes" : "no"}`);
|
|
11355
|
+
console.log("");
|
|
11356
|
+
if (report.staticHosting.githubPagesLikely || report.pagesProbe) {
|
|
11357
|
+
console.log(`${BOLD9}Published Surface${RESET16}`);
|
|
11358
|
+
console.log(` GitHub Pages: ${report.staticHosting.githubPagesLikely ? "likely" : "not detected"}`);
|
|
11359
|
+
if (report.source.publishedSiteUrl) {
|
|
11360
|
+
console.log(` Site URL: ${report.source.publishedSiteUrl}`);
|
|
11361
|
+
}
|
|
11362
|
+
if (report.pagesProbe?.checked) {
|
|
11363
|
+
console.log(
|
|
11364
|
+
` HTTP probe: ${report.pagesProbe.reachable ? success3("reachable") : `${YELLOW12}unreachable${RESET16}`} ${report.pagesProbe.status ?? ""}`
|
|
11365
|
+
);
|
|
11366
|
+
if (report.pagesProbe.title) console.log(` Title: ${report.pagesProbe.title}`);
|
|
11367
|
+
}
|
|
11368
|
+
for (const item of report.staticHosting.evidence.slice(0, 4)) {
|
|
11369
|
+
console.log(` ${dim3("-")} ${item}`);
|
|
11370
|
+
}
|
|
11371
|
+
console.log("");
|
|
11372
|
+
}
|
|
11373
|
+
console.log(`${BOLD9}Findings${RESET16}`);
|
|
11374
|
+
for (const finding of report.findings.slice(0, 8)) {
|
|
11375
|
+
console.log(` [${scanSeverityColor(finding)}] ${finding.title}`);
|
|
11376
|
+
console.log(` ${finding.message}`);
|
|
11377
|
+
if (finding.recommendation) console.log(` ${dim3(finding.recommendation)}`);
|
|
11378
|
+
}
|
|
11379
|
+
console.log("");
|
|
11380
|
+
console.log(`${BOLD9}Next Commands${RESET16}`);
|
|
11381
|
+
for (const command of report.recommendedCommands) {
|
|
11382
|
+
console.log(` ${cyan3(command)}`);
|
|
11383
|
+
}
|
|
11384
|
+
console.log("");
|
|
11385
|
+
console.log(dim3(report.privacy.notes[0] ?? "Scan completed without writing files."));
|
|
11386
|
+
}
|
|
11387
|
+
async function cmdScanWorkflow(args) {
|
|
11388
|
+
const { flags } = parseLooseArgs(args);
|
|
11389
|
+
if (!ensureAllowedFlags(flags, ["project", "json"], "scan")) return;
|
|
11390
|
+
const workspaceInfo = resolveWorkflowProject(flags, "scan");
|
|
11391
|
+
if (!workspaceInfo) return;
|
|
11392
|
+
const jsonOutput = flagBoolean(flags, "json");
|
|
11393
|
+
const projectArg = flagString(flags, "project");
|
|
11394
|
+
const inputValue = projectArg ?? ".";
|
|
11395
|
+
const report = await scanProjectReadOnly(workspaceInfo.appRoot, {
|
|
11396
|
+
input: { kind: "local", value: inputValue }
|
|
11397
|
+
});
|
|
11398
|
+
if (jsonOutput) {
|
|
11399
|
+
console.log(JSON.stringify(report, null, 2));
|
|
11400
|
+
return;
|
|
11401
|
+
}
|
|
11402
|
+
printScanReport(report);
|
|
11403
|
+
}
|
|
10580
11404
|
async function cmdSetupWorkflow(args) {
|
|
10581
11405
|
const { flags } = parseLooseArgs(args);
|
|
10582
11406
|
const projectArg = flagString(flags, "project");
|
|
@@ -10597,21 +11421,27 @@ async function cmdSetupWorkflow(args) {
|
|
|
10597
11421
|
console.log(` Detected: ${formatDetection(detected)}`);
|
|
10598
11422
|
console.log("");
|
|
10599
11423
|
if (detected.existingEssence) {
|
|
10600
|
-
const hasLocalPatterns =
|
|
10601
|
-
const hasLocalRules =
|
|
11424
|
+
const hasLocalPatterns = existsSync30(localPatternsPath(workspaceInfo.appRoot));
|
|
11425
|
+
const hasLocalRules = existsSync30(localRulesPath(workspaceInfo.appRoot));
|
|
11426
|
+
const hasStyleBridge = existsSync30(styleBridgePath(workspaceInfo.appRoot));
|
|
10602
11427
|
const verifyCommand = hasLocalPatterns || hasLocalRules ? "decantr verify --brownfield --local-patterns" : "decantr verify --brownfield";
|
|
10603
11428
|
console.log(`${BOLD9}Recommended path:${RESET16} maintain an attached Decantr project`);
|
|
11429
|
+
console.log(
|
|
11430
|
+
` ${cyan3(withProject("decantr doctor", projectArg))} Explain current state and next command`
|
|
11431
|
+
);
|
|
11432
|
+
if (!hasLocalPatterns || !hasLocalRules || !hasStyleBridge) {
|
|
11433
|
+
const codifyCommand = hasLocalPatterns && hasLocalRules ? "decantr codify --style-bridge" : "decantr codify --from-audit --style-bridge";
|
|
11434
|
+
console.log(
|
|
11435
|
+
` ${cyan3(withProject(codifyCommand, projectArg))} Propose missing local law/style bridge`
|
|
11436
|
+
);
|
|
11437
|
+
}
|
|
10604
11438
|
console.log(
|
|
10605
11439
|
` ${cyan3(withProject('decantr task <route> "<change>"', projectArg))} Prepare LLM context before edits`
|
|
10606
11440
|
);
|
|
10607
11441
|
console.log(
|
|
10608
11442
|
` ${cyan3(withProject(verifyCommand, projectArg))} Run local health and drift checks`
|
|
10609
11443
|
);
|
|
10610
|
-
|
|
10611
|
-
console.log(
|
|
10612
|
-
` ${cyan3(withProject("decantr codify --from-audit", projectArg))} Propose project-owned local law`
|
|
10613
|
-
);
|
|
10614
|
-
}
|
|
11444
|
+
console.log(` ${cyan3(withProject("decantr ci init", projectArg))} Wire the app into CI`);
|
|
10615
11445
|
return;
|
|
10616
11446
|
}
|
|
10617
11447
|
if (hasFootprint) {
|
|
@@ -10620,7 +11450,7 @@ async function cmdSetupWorkflow(args) {
|
|
|
10620
11450
|
` ${cyan3(withProject("decantr adopt --yes", projectArg))} Analyze, attach, and verify`
|
|
10621
11451
|
);
|
|
10622
11452
|
console.log(
|
|
10623
|
-
` ${cyan3(withProject("decantr codify --from-audit", projectArg))} Propose local UI law`
|
|
11453
|
+
` ${cyan3(withProject("decantr codify --from-audit --style-bridge", projectArg))} Propose local UI law and style bridge`
|
|
10624
11454
|
);
|
|
10625
11455
|
console.log("");
|
|
10626
11456
|
console.log(`${BOLD9}Optional visual evidence after the app is running:${RESET16}`);
|
|
@@ -10679,7 +11509,7 @@ async function cmdAdoptWorkflow(args) {
|
|
|
10679
11509
|
const hydratePacks = flagBoolean(flags, "packs", true) && !flagBoolean(flags, "skip-packs") && !flagBoolean(flags, "offline") && process.env.DECANTR_OFFLINE !== "true";
|
|
10680
11510
|
const initCi = flagBoolean(flags, "ci") || flagBoolean(flags, "init-ci");
|
|
10681
11511
|
const assistantBridge = flagString(flags, "assistant-bridge");
|
|
10682
|
-
const hasEssence =
|
|
11512
|
+
const hasEssence = existsSync30(join32(projectRoot, "decantr.essence.json"));
|
|
10683
11513
|
const proposalFlag = flagBoolean(flags, "replace-essence") ? "--replace-essence" : flagBoolean(flags, "merge-proposal") || hasEssence ? "--merge-proposal" : "--accept-proposal";
|
|
10684
11514
|
const steps = [
|
|
10685
11515
|
"analyze current app and write .decantr/brownfield intelligence",
|
|
@@ -10727,7 +11557,7 @@ async function cmdAdoptWorkflow(args) {
|
|
|
10727
11557
|
if (hydratePacks) {
|
|
10728
11558
|
try {
|
|
10729
11559
|
const { bundle, contextDir } = await compileHostedExecutionPackBundle(
|
|
10730
|
-
|
|
11560
|
+
join32(projectRoot, "decantr.essence.json")
|
|
10731
11561
|
);
|
|
10732
11562
|
const written = writeHostedExecutionPackContextArtifacts(
|
|
10733
11563
|
contextDir,
|
|
@@ -10750,7 +11580,7 @@ async function cmdAdoptWorkflow(args) {
|
|
|
10750
11580
|
console.log(dim3("Skipping hosted pack hydration in offline mode."));
|
|
10751
11581
|
}
|
|
10752
11582
|
if (runVerify) {
|
|
10753
|
-
const { cmdHealth } = await import("./health-
|
|
11583
|
+
const { cmdHealth } = await import("./health-B4W7UJBZ.js");
|
|
10754
11584
|
await cmdHealth(projectRoot, {
|
|
10755
11585
|
browser: runBrowser,
|
|
10756
11586
|
browserBaseUrl: baseUrl,
|
|
@@ -10768,10 +11598,10 @@ async function cmdAdoptWorkflow(args) {
|
|
|
10768
11598
|
console.log("");
|
|
10769
11599
|
console.log(`${BOLD9}Brownfield operating loop:${RESET16}`);
|
|
10770
11600
|
console.log(
|
|
10771
|
-
` ${cyan3(withProject("decantr codify --from-audit", projectArg))}
|
|
11601
|
+
` ${cyan3(withProject("decantr codify --from-audit --style-bridge", projectArg))} Propose project-owned UI law and style bridge`
|
|
10772
11602
|
);
|
|
10773
11603
|
console.log(
|
|
10774
|
-
` ${cyan3(withProject("decantr codify --accept", projectArg))} Accept reviewed local patterns and
|
|
11604
|
+
` ${cyan3(withProject("decantr codify --accept", projectArg))} Accept reviewed local patterns, rules, and bridge`
|
|
10775
11605
|
);
|
|
10776
11606
|
console.log(
|
|
10777
11607
|
` ${cyan3(withProject('decantr task <route> "<change>"', projectArg))} Give your LLM route-specific context before edits`
|
|
@@ -10821,7 +11651,7 @@ async function cmdVerifyWorkflow(args) {
|
|
|
10821
11651
|
return;
|
|
10822
11652
|
}
|
|
10823
11653
|
if (workspaceMode) {
|
|
10824
|
-
const { cmdWorkspace } = await import("./workspace-
|
|
11654
|
+
const { cmdWorkspace } = await import("./workspace-7RU77ZZW.js");
|
|
10825
11655
|
await cmdWorkspace(process.cwd(), ["workspace", "health", ...withoutWorkflowOnlyFlags(args)]);
|
|
10826
11656
|
return;
|
|
10827
11657
|
}
|
|
@@ -10854,7 +11684,7 @@ async function cmdVerifyWorkflow(args) {
|
|
|
10854
11684
|
}
|
|
10855
11685
|
let guardExitCode;
|
|
10856
11686
|
if (brownfield) {
|
|
10857
|
-
const { cmdHeal, collectCheckIssues } = await import("./heal-
|
|
11687
|
+
const { cmdHeal, collectCheckIssues } = await import("./heal-IRIDB7IZ.js");
|
|
10858
11688
|
if (quietOutput) {
|
|
10859
11689
|
const result = collectCheckIssues(workspaceInfo.appRoot, { brownfield: true });
|
|
10860
11690
|
guardExitCode = result.issues.some((issue) => issue.type === "error") ? 1 : void 0;
|
|
@@ -10864,7 +11694,7 @@ async function cmdVerifyWorkflow(args) {
|
|
|
10864
11694
|
process.exitCode = void 0;
|
|
10865
11695
|
}
|
|
10866
11696
|
}
|
|
10867
|
-
const { cmdHealth, parseHealthArgs } = await import("./health-
|
|
11697
|
+
const { cmdHealth, parseHealthArgs } = await import("./health-B4W7UJBZ.js");
|
|
10868
11698
|
await cmdHealth(workspaceInfo.appRoot, parseHealthArgs(healthArgs));
|
|
10869
11699
|
if (localPatterns) {
|
|
10870
11700
|
const validation = validateLocalLaw(workspaceInfo.appRoot);
|
|
@@ -10886,6 +11716,9 @@ async function cmdVerifyWorkflow(args) {
|
|
|
10886
11716
|
console.log(`${GREEN16}Local pattern pack found:${RESET16} ${validation.patternsPath}`);
|
|
10887
11717
|
if (validation.ruleManifestPresent) {
|
|
10888
11718
|
console.log(`${GREEN16}Local rule manifest found:${RESET16} ${validation.rulesPath}`);
|
|
11719
|
+
console.log(
|
|
11720
|
+
`${DIM16}Enforcement: accepted .decantr/rules.json is the Decantr-scanned layer; --fail-on controls whether findings block.${RESET16}`
|
|
11721
|
+
);
|
|
10889
11722
|
} else {
|
|
10890
11723
|
console.log(
|
|
10891
11724
|
`${YELLOW12}Local rule manifest missing.${RESET16} Run ${cyan3("decantr codify --from-audit")} to propose .decantr/rules.json.`
|
|
@@ -10914,14 +11747,25 @@ async function cmdVerifyWorkflow(args) {
|
|
|
10914
11747
|
}
|
|
10915
11748
|
}
|
|
10916
11749
|
}
|
|
11750
|
+
const styleBridge = createStyleBridgeTaskSummary(workspaceInfo.appRoot);
|
|
11751
|
+
if (!quietOutput && styleBridge.path) {
|
|
11752
|
+
console.log("");
|
|
11753
|
+
console.log(`${GREEN16}Style bridge found:${RESET16} ${styleBridge.path}`);
|
|
11754
|
+
console.log(
|
|
11755
|
+
`${DIM16}${styleBridge.mappingCount} mapping(s), ${styleBridge.stylingApproach ?? "unknown"} styling${styleBridge.themeModes.length > 0 ? `, themes: ${styleBridge.themeModes.join(", ")}` : ""}${RESET16}`
|
|
11756
|
+
);
|
|
11757
|
+
console.log(
|
|
11758
|
+
`${DIM16}Enforcement: advisory style-intent mapping; pair with accepted local rules, lint, tests, or visual regression when it should block.${RESET16}`
|
|
11759
|
+
);
|
|
11760
|
+
}
|
|
10917
11761
|
if (guardExitCode && guardExitCode !== 0 && (!process.exitCode || process.exitCode === 0)) {
|
|
10918
11762
|
process.exitCode = guardExitCode;
|
|
10919
11763
|
}
|
|
10920
11764
|
}
|
|
10921
11765
|
function readJsonIfPresent(path) {
|
|
10922
|
-
if (!
|
|
11766
|
+
if (!existsSync30(path)) return null;
|
|
10923
11767
|
try {
|
|
10924
|
-
return JSON.parse(
|
|
11768
|
+
return JSON.parse(readFileSync23(path, "utf-8"));
|
|
10925
11769
|
} catch {
|
|
10926
11770
|
return null;
|
|
10927
11771
|
}
|
|
@@ -10933,6 +11777,7 @@ function mentionsWord(text, term) {
|
|
|
10933
11777
|
function createTaskAuthoritySummary(input) {
|
|
10934
11778
|
const detected = detectProject(input.projectRoot);
|
|
10935
11779
|
const hasLocalLaw = input.hasLocalPatterns || input.hasLocalRules;
|
|
11780
|
+
const hasStyleBridge = input.hasStyleBridge || input.adoptionMode === "style-bridge";
|
|
10936
11781
|
let lane = "Brownfield contract-only";
|
|
10937
11782
|
let sourceAuthority = "Existing app is authoritative; Decantr supplies contract context.";
|
|
10938
11783
|
let styleAuthority = "Use the existing styling system.";
|
|
@@ -10945,11 +11790,11 @@ function createTaskAuthoritySummary(input) {
|
|
|
10945
11790
|
sourceAuthority = "Existing app remains authoritative except where Decantr CSS is explicitly adopted.";
|
|
10946
11791
|
styleAuthority = "Decantr CSS runtime is active where adopted.";
|
|
10947
11792
|
activeAuthorities.push("Decantr CSS runtime");
|
|
10948
|
-
} else if (input.workflowMode === "brownfield-attach" &&
|
|
11793
|
+
} else if (input.workflowMode === "brownfield-attach" && hasStyleBridge) {
|
|
10949
11794
|
lane = "Hybrid style bridge";
|
|
10950
11795
|
sourceAuthority = "Existing app remains authoritative; Decantr intent maps through the style bridge.";
|
|
10951
11796
|
styleAuthority = "Use bridge tokens/classes as a mapping layer onto the app styling system.";
|
|
10952
|
-
activeAuthorities.push("style bridge");
|
|
11797
|
+
activeAuthorities.push("accepted style bridge");
|
|
10953
11798
|
} else if (input.workflowMode === "brownfield-attach" && hasLocalLaw) {
|
|
10954
11799
|
lane = "Hybrid local law";
|
|
10955
11800
|
sourceAuthority = "Existing app plus accepted project-owned UI law are authoritative.";
|
|
@@ -11018,7 +11863,7 @@ async function cmdTaskWorkflow(args) {
|
|
|
11018
11863
|
}
|
|
11019
11864
|
const route = routeInput.startsWith("/") ? routeInput : `/${routeInput}`;
|
|
11020
11865
|
const taskSummary = positional.slice(1).join(" ").trim();
|
|
11021
|
-
const essencePath =
|
|
11866
|
+
const essencePath = join32(workspaceInfo.appRoot, "decantr.essence.json");
|
|
11022
11867
|
const essence = readJsonIfPresent(essencePath);
|
|
11023
11868
|
if (!essence) {
|
|
11024
11869
|
console.error(
|
|
@@ -11042,16 +11887,22 @@ async function cmdTaskWorkflow(args) {
|
|
|
11042
11887
|
}
|
|
11043
11888
|
const section = essence.blueprint.sections.find((entry) => entry.id === target.section);
|
|
11044
11889
|
const page = section?.pages.find((entry) => entry.id === target.page);
|
|
11045
|
-
const contextDir =
|
|
11046
|
-
const manifest = readJsonIfPresent(
|
|
11047
|
-
const projectJson = readJsonIfPresent(
|
|
11890
|
+
const contextDir = join32(workspaceInfo.appRoot, ".decantr", "context");
|
|
11891
|
+
const manifest = readJsonIfPresent(join32(contextDir, "pack-manifest.json"));
|
|
11892
|
+
const projectJson = readJsonIfPresent(join32(workspaceInfo.appRoot, ".decantr", "project.json"));
|
|
11048
11893
|
const pagePack = manifest?.pages?.find((entry) => entry.id === target.page);
|
|
11049
11894
|
const sectionPack = manifest?.sections?.find((entry) => entry.id === target.section);
|
|
11050
|
-
const visualManifest = readJsonIfPresent(
|
|
11895
|
+
const visualManifest = readJsonIfPresent(join32(workspaceInfo.appRoot, ".decantr", "evidence", "visual-manifest.json"));
|
|
11051
11896
|
const screenshot = visualManifest?.routes?.find((entry) => entry.route === route)?.screenshot;
|
|
11052
11897
|
const localPatternPackPath = localPatternsPath(workspaceInfo.appRoot);
|
|
11053
11898
|
const localRuleManifestPath = localRulesPath(workspaceInfo.appRoot);
|
|
11899
|
+
const acceptedStyleBridgePath = styleBridgePath(workspaceInfo.appRoot);
|
|
11054
11900
|
const localLaw = createLocalLawTaskSummary(workspaceInfo.appRoot);
|
|
11901
|
+
const styleBridge = createStyleBridgeTaskSummary(workspaceInfo.appRoot);
|
|
11902
|
+
const displayedStyleBridge = {
|
|
11903
|
+
...styleBridge,
|
|
11904
|
+
path: styleBridge.path ? displayProjectPath(workspaceInfo, styleBridge.path) : null
|
|
11905
|
+
};
|
|
11055
11906
|
const displayedLocalLaw = {
|
|
11056
11907
|
...localLaw,
|
|
11057
11908
|
patternsPath: localLaw.patternsPath ? displayProjectPath(workspaceInfo, localLaw.patternsPath) : null,
|
|
@@ -11064,10 +11915,11 @@ async function cmdTaskWorkflow(args) {
|
|
|
11064
11915
|
projectRoot: workspaceInfo.appRoot,
|
|
11065
11916
|
workflowMode: projectJson?.initialized?.workflowMode ?? null,
|
|
11066
11917
|
adoptionMode: projectJson?.initialized?.adoptionMode ?? null,
|
|
11067
|
-
hasLocalPatterns:
|
|
11068
|
-
hasLocalRules:
|
|
11918
|
+
hasLocalPatterns: existsSync30(localPatternPackPath),
|
|
11919
|
+
hasLocalRules: existsSync30(localRuleManifestPath),
|
|
11069
11920
|
hasPackManifest: Boolean(manifest),
|
|
11070
|
-
taskSummary
|
|
11921
|
+
taskSummary,
|
|
11922
|
+
hasStyleBridge: existsSync30(acceptedStyleBridgePath)
|
|
11071
11923
|
});
|
|
11072
11924
|
const context = {
|
|
11073
11925
|
route,
|
|
@@ -11077,17 +11929,19 @@ async function cmdTaskWorkflow(args) {
|
|
|
11077
11929
|
shell: page?.shell ?? section?.shell ?? null,
|
|
11078
11930
|
patterns: page?.layout?.map(extractPatternName) ?? [],
|
|
11079
11931
|
read: [
|
|
11080
|
-
pagePack ? displayProjectPath(workspaceInfo,
|
|
11081
|
-
sectionPack ? displayProjectPath(workspaceInfo,
|
|
11082
|
-
manifest?.scaffold?.markdown ? displayProjectPath(workspaceInfo,
|
|
11932
|
+
pagePack ? displayProjectPath(workspaceInfo, join32(".decantr/context", pagePack.markdown)) : null,
|
|
11933
|
+
sectionPack ? displayProjectPath(workspaceInfo, join32(".decantr/context", sectionPack.markdown)) : null,
|
|
11934
|
+
manifest?.scaffold?.markdown ? displayProjectPath(workspaceInfo, join32(".decantr/context", manifest.scaffold.markdown)) : null,
|
|
11083
11935
|
displayProjectPath(workspaceInfo, ".decantr/context/scaffold.md"),
|
|
11084
11936
|
displayProjectPath(workspaceInfo, "DECANTR.md"),
|
|
11085
|
-
|
|
11086
|
-
|
|
11937
|
+
existsSync30(localPatternPackPath) ? displayProjectPath(workspaceInfo, ".decantr/local-patterns.json") : null,
|
|
11938
|
+
existsSync30(localRuleManifestPath) ? displayProjectPath(workspaceInfo, ".decantr/rules.json") : null,
|
|
11939
|
+
existsSync30(acceptedStyleBridgePath) ? displayProjectPath(workspaceInfo, ".decantr/style-bridge.json") : null
|
|
11087
11940
|
].filter(Boolean),
|
|
11088
11941
|
screenshot: screenshot?.startsWith(".decantr/") ? displayProjectPath(workspaceInfo, screenshot) : screenshot ?? null,
|
|
11089
11942
|
authority,
|
|
11090
11943
|
localLaw: displayedLocalLaw,
|
|
11944
|
+
styleBridge: displayedStyleBridge,
|
|
11091
11945
|
changedFiles: currentChangedFiles,
|
|
11092
11946
|
changedRoutes,
|
|
11093
11947
|
verifyCommand: withProject("decantr verify --brownfield --local-patterns", projectArg)
|
|
@@ -11132,10 +11986,17 @@ async function cmdTaskWorkflow(args) {
|
|
|
11132
11986
|
}
|
|
11133
11987
|
if (context.localLaw.rulesPath) {
|
|
11134
11988
|
console.log(` Rules: ${cyan3(context.localLaw.rulesPath)} (${context.localLaw.ruleCount})`);
|
|
11989
|
+
console.log(" Enforcement: accepted local rules are the Decantr-scanned layer.");
|
|
11135
11990
|
}
|
|
11136
11991
|
for (const pattern of context.localLaw.patterns.slice(0, 4)) {
|
|
11137
11992
|
const pathHint = pattern.componentPaths.length > 0 ? ` \u2014 ${pattern.componentPaths.slice(0, 2).join(", ")}` : "";
|
|
11138
|
-
|
|
11993
|
+
const authorityHint = [
|
|
11994
|
+
pattern.role ?? "local pattern",
|
|
11995
|
+
pattern.confidenceTier ? `confidence ${pattern.confidenceTier}` : null,
|
|
11996
|
+
pattern.enforcementLevel ? `${pattern.enforcementLevel}` : null,
|
|
11997
|
+
pattern.hostedPatternRefs.length > 0 ? `maps ${pattern.hostedPatternRefs.slice(0, 2).join(", ")}` : null
|
|
11998
|
+
].filter(Boolean).join(" | ");
|
|
11999
|
+
console.log(` ${pattern.id}: ${authorityHint}${pathHint}`);
|
|
11139
12000
|
}
|
|
11140
12001
|
} else {
|
|
11141
12002
|
console.log("");
|
|
@@ -11144,6 +12005,23 @@ async function cmdTaskWorkflow(args) {
|
|
|
11144
12005
|
` ${YELLOW12}Not codified yet.${RESET16} Run ${cyan3(withProject("decantr codify --from-audit", projectArg))} after adoption.`
|
|
11145
12006
|
);
|
|
11146
12007
|
}
|
|
12008
|
+
if (context.styleBridge.path) {
|
|
12009
|
+
console.log("");
|
|
12010
|
+
console.log(`${BOLD9}Project-owned style bridge:${RESET16}`);
|
|
12011
|
+
console.log(
|
|
12012
|
+
` Bridge: ${cyan3(context.styleBridge.path)} (${context.styleBridge.mappingCount} mappings, ${context.styleBridge.stylingApproach ?? "unknown"} styling)`
|
|
12013
|
+
);
|
|
12014
|
+
console.log(
|
|
12015
|
+
" Enforcement: advisory mapping layer; accepted local rules or project lint/tests do the blocking."
|
|
12016
|
+
);
|
|
12017
|
+
if (context.styleBridge.themeModes.length > 0) {
|
|
12018
|
+
console.log(` Theme modes: ${context.styleBridge.themeModes.join(", ")}`);
|
|
12019
|
+
}
|
|
12020
|
+
for (const mapping of context.styleBridge.mappings.slice(0, 4)) {
|
|
12021
|
+
const hints = [...mapping.tokenHints, ...mapping.classHints].slice(0, 4).join(", ");
|
|
12022
|
+
console.log(` ${mapping.id}: ${mapping.label}${hints ? ` \u2014 ${hints}` : ""}`);
|
|
12023
|
+
}
|
|
12024
|
+
}
|
|
11147
12025
|
if (context.changedFiles.length > 0) {
|
|
11148
12026
|
console.log("");
|
|
11149
12027
|
console.log(`${BOLD9}Changed-file context:${RESET16}`);
|
|
@@ -11169,7 +12047,16 @@ async function cmdCodifyWorkflow(args) {
|
|
|
11169
12047
|
const projectArg = flagString(flags, "project");
|
|
11170
12048
|
if (!ensureAllowedFlags(
|
|
11171
12049
|
flags,
|
|
11172
|
-
[
|
|
12050
|
+
[
|
|
12051
|
+
"project",
|
|
12052
|
+
"from-audit",
|
|
12053
|
+
"discover-local-patterns",
|
|
12054
|
+
"codify-local-patterns",
|
|
12055
|
+
"style-bridge",
|
|
12056
|
+
"map-pattern",
|
|
12057
|
+
"hosted-pattern",
|
|
12058
|
+
"accept"
|
|
12059
|
+
],
|
|
11173
12060
|
"codify"
|
|
11174
12061
|
)) {
|
|
11175
12062
|
return;
|
|
@@ -11177,25 +12064,29 @@ async function cmdCodifyWorkflow(args) {
|
|
|
11177
12064
|
const workspaceInfo = resolveWorkflowProject(flags, "codify");
|
|
11178
12065
|
if (!workspaceInfo) return;
|
|
11179
12066
|
if (flagBoolean(flags, "accept")) {
|
|
11180
|
-
if (!
|
|
12067
|
+
if (!existsSync30(localPatternsProposalPath(workspaceInfo.appRoot)) && !existsSync30(localRulesProposalPath(workspaceInfo.appRoot)) && !existsSync30(styleBridgeProposalPath(workspaceInfo.appRoot))) {
|
|
11181
12068
|
console.error(
|
|
11182
12069
|
error2(
|
|
11183
|
-
"No
|
|
12070
|
+
"No codify proposal found. Run `decantr codify --from-audit`, `decantr codify --style-bridge`, or `decantr codify` first."
|
|
11184
12071
|
)
|
|
11185
12072
|
);
|
|
11186
12073
|
process.exitCode = 1;
|
|
11187
12074
|
return;
|
|
11188
12075
|
}
|
|
11189
|
-
const
|
|
11190
|
-
|
|
11191
|
-
|
|
12076
|
+
const result = acceptBrownfieldLocalLaw(workspaceInfo.appRoot);
|
|
12077
|
+
const bridgeAcceptedPath = acceptStyleBridge(workspaceInfo.appRoot);
|
|
12078
|
+
if (result.patternAcceptedPath) {
|
|
12079
|
+
console.log(success3(`Accepted local pattern pack: ${result.patternAcceptedPath}`));
|
|
11192
12080
|
}
|
|
11193
|
-
if (
|
|
11194
|
-
console.log(success3(`Accepted local rule manifest: ${
|
|
12081
|
+
if (result.rulesAcceptedPath) {
|
|
12082
|
+
console.log(success3(`Accepted local rule manifest: ${result.rulesAcceptedPath}`));
|
|
12083
|
+
}
|
|
12084
|
+
if (bridgeAcceptedPath) {
|
|
12085
|
+
console.log(success3(`Accepted style bridge: ${bridgeAcceptedPath}`));
|
|
11195
12086
|
}
|
|
11196
12087
|
console.log(
|
|
11197
12088
|
dim3(
|
|
11198
|
-
"Hybrid local law is now active: Decantr will treat accepted local patterns and rules as project-owned authority in task and verify flows."
|
|
12089
|
+
bridgeAcceptedPath ? "Hybrid style bridge is now active: Decantr will map design intent through accepted project tokens/classes in task, doctor, suggest, and CI flows." : "Hybrid local law is now active: Decantr will treat accepted local patterns and rules as project-owned authority in task and verify flows."
|
|
11199
12090
|
)
|
|
11200
12091
|
);
|
|
11201
12092
|
console.log(
|
|
@@ -11207,19 +12098,84 @@ async function cmdCodifyWorkflow(args) {
|
|
|
11207
12098
|
}
|
|
11208
12099
|
const detected = detectProject(workspaceInfo.appRoot);
|
|
11209
12100
|
const essence = readJsonIfPresent(
|
|
11210
|
-
|
|
12101
|
+
join32(workspaceInfo.appRoot, "decantr.essence.json")
|
|
11211
12102
|
);
|
|
11212
12103
|
const fromAudit = flagBoolean(flags, "from-audit") || flagBoolean(flags, "discover-local-patterns") || flagBoolean(flags, "codify-local-patterns");
|
|
11213
|
-
const
|
|
11214
|
-
|
|
11215
|
-
|
|
11216
|
-
|
|
11217
|
-
|
|
11218
|
-
|
|
11219
|
-
|
|
11220
|
-
|
|
11221
|
-
|
|
11222
|
-
|
|
12104
|
+
const wantsStyleBridge = flagBoolean(flags, "style-bridge");
|
|
12105
|
+
const mapPatternSlug = flagString(flags, "map-pattern") ?? flagString(flags, "hosted-pattern");
|
|
12106
|
+
if (mapPatternSlug) {
|
|
12107
|
+
const registryClient = new RegistryClient({
|
|
12108
|
+
cacheDir: join32(workspaceInfo.appRoot, ".decantr", "cache")
|
|
12109
|
+
});
|
|
12110
|
+
const candidates = await loadPatternDiscoveryCandidates(registryClient);
|
|
12111
|
+
const candidate = findPatternCandidateBySlug(candidates, mapPatternSlug);
|
|
12112
|
+
if (!candidate) {
|
|
12113
|
+
const suggestions = rankPatternCandidates({ query: mapPatternSlug, limit: 5 }, candidates);
|
|
12114
|
+
console.error(error2(`Could not find pattern "${mapPatternSlug}" to map into local law.`));
|
|
12115
|
+
if (suggestions.length > 0) {
|
|
12116
|
+
console.error(dim3("Closest registry patterns:"));
|
|
12117
|
+
for (const suggestion of suggestions) {
|
|
12118
|
+
const slug2 = suggestion.candidate.slug || suggestion.candidate.id;
|
|
12119
|
+
console.error(dim3(` ${slug2} - ${suggestion.candidate.name ?? slug2}`));
|
|
12120
|
+
}
|
|
12121
|
+
}
|
|
12122
|
+
process.exitCode = 1;
|
|
12123
|
+
return;
|
|
12124
|
+
}
|
|
12125
|
+
const result = writeHostedPatternMappingProposal({
|
|
12126
|
+
projectRoot: workspaceInfo.appRoot,
|
|
12127
|
+
hostedPattern: hostedPatternRefFromCandidate(candidate)
|
|
12128
|
+
});
|
|
12129
|
+
const slug = candidate.slug || candidate.id;
|
|
12130
|
+
console.log(
|
|
12131
|
+
success3(
|
|
12132
|
+
`${result.replacedExisting ? "Updated" : "Wrote"} hosted pattern mapping proposal: ${result.patternPath}`
|
|
12133
|
+
)
|
|
12134
|
+
);
|
|
12135
|
+
console.log(
|
|
12136
|
+
dim3(
|
|
12137
|
+
`Mapped registry pattern "${slug}" into local pattern "${result.localPatternId}" as advisory Hybrid law. No source files were changed.`
|
|
12138
|
+
)
|
|
12139
|
+
);
|
|
12140
|
+
console.log(
|
|
12141
|
+
dim3(
|
|
12142
|
+
`Fill in project-owned component paths, token/class recipes, variants, and exceptions, then run \`${withProject("decantr codify --accept", projectArg)}\`.`
|
|
12143
|
+
)
|
|
12144
|
+
);
|
|
12145
|
+
return;
|
|
12146
|
+
}
|
|
12147
|
+
const wantsLocalLaw = !wantsStyleBridge || fromAudit;
|
|
12148
|
+
let localResult = null;
|
|
12149
|
+
if (wantsLocalLaw) {
|
|
12150
|
+
const proposal = createBrownfieldCodifyProposal({
|
|
12151
|
+
projectRoot: workspaceInfo.appRoot,
|
|
12152
|
+
detected,
|
|
12153
|
+
essence,
|
|
12154
|
+
fromAudit
|
|
12155
|
+
});
|
|
12156
|
+
localResult = writeBrownfieldCodifyProposal(workspaceInfo.appRoot, proposal);
|
|
12157
|
+
}
|
|
12158
|
+
let styleBridgeResult = null;
|
|
12159
|
+
if (wantsStyleBridge) {
|
|
12160
|
+
const styling = scanStyling(workspaceInfo.appRoot);
|
|
12161
|
+
styleBridgeResult = writeStyleBridgeProposal(
|
|
12162
|
+
workspaceInfo.appRoot,
|
|
12163
|
+
createStyleBridgeProposal({
|
|
12164
|
+
projectRoot: workspaceInfo.appRoot,
|
|
12165
|
+
detected,
|
|
12166
|
+
essence,
|
|
12167
|
+
styling
|
|
12168
|
+
})
|
|
12169
|
+
);
|
|
12170
|
+
}
|
|
12171
|
+
if (localResult) {
|
|
12172
|
+
console.log(success3(`Wrote local pattern proposal: ${localResult.patternPath}`));
|
|
12173
|
+
console.log(success3(`Wrote local rule proposal: ${localResult.rulesPath}`));
|
|
12174
|
+
}
|
|
12175
|
+
if (styleBridgeResult) {
|
|
12176
|
+
console.log(success3(`Wrote style bridge proposal: ${styleBridgeResult}`));
|
|
12177
|
+
}
|
|
12178
|
+
if (fromAudit && localResult) {
|
|
11223
12179
|
console.log(
|
|
11224
12180
|
dim3(
|
|
11225
12181
|
"Proposal includes source-derived component candidates, starter mechanical rules, and Hybrid authority guidance."
|
|
@@ -11228,7 +12184,7 @@ async function cmdCodifyWorkflow(args) {
|
|
|
11228
12184
|
}
|
|
11229
12185
|
console.log(
|
|
11230
12186
|
dim3(
|
|
11231
|
-
`Review
|
|
12187
|
+
`Review the proposal files, add real component paths/token/class recipes, map hosted pattern ideas into project-owned law, then run \`${withProject("decantr codify --accept", projectArg)}\`.`
|
|
11232
12188
|
)
|
|
11233
12189
|
);
|
|
11234
12190
|
}
|
|
@@ -11277,13 +12233,14 @@ ${BOLD9}decantr${RESET16} \u2014 Design intelligence for AI-generated UI
|
|
|
11277
12233
|
|
|
11278
12234
|
${BOLD9}Usage:${RESET16}
|
|
11279
12235
|
decantr setup [--project <path>]
|
|
12236
|
+
decantr scan [--project <path>] [--json]
|
|
11280
12237
|
decantr new <name> [--blueprint=X] [--archetype=X] [--theme=X] [--workflow=greenfield] [--adoption=decantr-css] [--telemetry]
|
|
11281
12238
|
decantr adopt [--project <path>] [--base-url <url>] [--evidence] [--ci] [--no-packs] [--yes]
|
|
11282
12239
|
decantr task <route> ["task summary"] [--project <path>] [--since origin/main] [--json]
|
|
11283
12240
|
decantr verify [--project <path>] [--brownfield] [--local-patterns] [health options]
|
|
11284
12241
|
decantr ci [--project <path>] [--workspace] [--fail-on error|warn|none]
|
|
11285
12242
|
decantr doctor [--project <path>] [--workspace] [--json]
|
|
11286
|
-
decantr codify [--from-audit] [--accept] [--project <path>]
|
|
12243
|
+
decantr codify [--from-audit] [--style-bridge] [--map-pattern <slug>] [--accept] [--project <path>]
|
|
11287
12244
|
decantr studio [--port 4319] [--host 127.0.0.1] [--report decantr-health.json] [--workspace]
|
|
11288
12245
|
|
|
11289
12246
|
${BOLD9}Advanced primitives:${RESET16}
|
|
@@ -11353,6 +12310,7 @@ ${BOLD9}Init Options:${RESET16}
|
|
|
11353
12310
|
|
|
11354
12311
|
${BOLD9}Commands:${RESET16}
|
|
11355
12312
|
${cyan3("setup")} Detect project state and recommend the right Decantr workflow
|
|
12313
|
+
${cyan3("scan")} Read-only Brownfield reconnaissance; no files written
|
|
11356
12314
|
${cyan3("new")} Create a new greenfield workspace and bootstrap the available starter adapter
|
|
11357
12315
|
${cyan3("adopt")} Brownfield one-liner: analyze, attach, verify, and show next steps
|
|
11358
12316
|
${cyan3("task")} Prepare route/task context, local law, evidence, and changed-file impact for an AI coding assistant
|
|
@@ -11396,6 +12354,8 @@ ${BOLD9}Advanced commands:${RESET16}
|
|
|
11396
12354
|
|
|
11397
12355
|
${BOLD9}Examples:${RESET16}
|
|
11398
12356
|
decantr setup
|
|
12357
|
+
decantr scan
|
|
12358
|
+
decantr scan --json
|
|
11399
12359
|
decantr new my-app --blueprint=carbon-ai-portal
|
|
11400
12360
|
decantr adopt --yes
|
|
11401
12361
|
decantr adopt --project apps/web --yes
|
|
@@ -11407,6 +12367,7 @@ ${BOLD9}Examples:${RESET16}
|
|
|
11407
12367
|
decantr ci --project apps/web
|
|
11408
12368
|
decantr ci init --project apps/web
|
|
11409
12369
|
decantr codify --from-audit
|
|
12370
|
+
decantr codify --map-pattern hero --project apps/web
|
|
11410
12371
|
decantr codify --accept
|
|
11411
12372
|
decantr content check --ci --fail-on error
|
|
11412
12373
|
decantr magic "AI chatbot with dark cyber theme \u2014 bold and futuristic"
|
|
@@ -11454,6 +12415,7 @@ ${BOLD9}Workflow Model:${RESET16}
|
|
|
11454
12415
|
${cyan3("Greenfield blueprint")} decantr new my-app --blueprint=X --workflow=greenfield --adoption=decantr-css
|
|
11455
12416
|
${cyan3("Greenfield contract")} decantr init --workflow=greenfield --adoption=contract-only
|
|
11456
12417
|
${cyan3("Brownfield adoption")} decantr adopt --yes
|
|
12418
|
+
${cyan3("Brownfield preview")} decantr scan -> decantr adopt --yes
|
|
11457
12419
|
${cyan3("Brownfield monorepo")} decantr adopt --project apps/web --yes
|
|
11458
12420
|
${cyan3("Daily LLM work")} decantr task <route> "<change>" -> decantr verify --brownfield --local-patterns
|
|
11459
12421
|
${cyan3("Project-owned law")} decantr codify --from-audit -> edit proposal -> decantr codify --accept
|
|
@@ -11482,6 +12444,28 @@ ${BOLD9}Examples:${RESET16}
|
|
|
11482
12444
|
decantr rules apply --project=apps/web
|
|
11483
12445
|
`);
|
|
11484
12446
|
}
|
|
12447
|
+
function cmdScanHelp() {
|
|
12448
|
+
console.log(`
|
|
12449
|
+
${BOLD9}decantr scan${RESET16} \u2014 Read-only Brownfield reconnaissance
|
|
12450
|
+
|
|
12451
|
+
${BOLD9}Usage:${RESET16}
|
|
12452
|
+
decantr scan [--project <path>] [--json]
|
|
12453
|
+
|
|
12454
|
+
${BOLD9}Options:${RESET16}
|
|
12455
|
+
--project App path inside a workspace/monorepo
|
|
12456
|
+
--json Emit the ScanReportV1 JSON to stdout
|
|
12457
|
+
|
|
12458
|
+
${BOLD9}Behavior:${RESET16}
|
|
12459
|
+
Reads local project files, detects frontend framework/routes/styling/static-hosting signals,
|
|
12460
|
+
and prints a terminal report. It does not write .decantr files, install dependencies,
|
|
12461
|
+
build the app, execute scripts, upload source, or open pull requests.
|
|
12462
|
+
|
|
12463
|
+
${BOLD9}Examples:${RESET16}
|
|
12464
|
+
decantr scan
|
|
12465
|
+
decantr scan --project apps/web
|
|
12466
|
+
decantr scan --json
|
|
12467
|
+
`);
|
|
12468
|
+
}
|
|
11485
12469
|
function isCommandHelpRequest(args) {
|
|
11486
12470
|
return args[1] === "help" || args.slice(1).some((arg) => arg === "--help" || arg === "-h");
|
|
11487
12471
|
}
|
|
@@ -11703,15 +12687,19 @@ ${BOLD9}Examples:${RESET16}
|
|
|
11703
12687
|
}
|
|
11704
12688
|
function cmdCodifyHelp() {
|
|
11705
12689
|
console.log(`
|
|
11706
|
-
${BOLD9}decantr codify${RESET16} \u2014 Propose or accept project-owned Brownfield UI
|
|
12690
|
+
${BOLD9}decantr codify${RESET16} \u2014 Propose or accept project-owned Brownfield UI law and style bridges
|
|
11707
12691
|
|
|
11708
12692
|
${BOLD9}Usage:${RESET16}
|
|
11709
|
-
decantr codify [--from-audit] [--project <path>]
|
|
12693
|
+
decantr codify [--from-audit] [--style-bridge] [--project <path>]
|
|
12694
|
+
decantr codify --map-pattern <registry-pattern-slug> [--project <path>]
|
|
11710
12695
|
decantr codify --accept [--project <path>]
|
|
11711
12696
|
|
|
11712
12697
|
${BOLD9}Examples:${RESET16}
|
|
11713
12698
|
decantr codify
|
|
11714
12699
|
decantr codify --from-audit
|
|
12700
|
+
decantr codify --style-bridge
|
|
12701
|
+
decantr codify --from-audit --style-bridge
|
|
12702
|
+
decantr codify --map-pattern hero --project apps/web
|
|
11715
12703
|
decantr codify --accept
|
|
11716
12704
|
decantr verify --brownfield --local-patterns
|
|
11717
12705
|
`);
|
|
@@ -11737,6 +12725,9 @@ function printCommandHelp(command, args) {
|
|
|
11737
12725
|
case "setup":
|
|
11738
12726
|
cmdSetupHelp();
|
|
11739
12727
|
return true;
|
|
12728
|
+
case "scan":
|
|
12729
|
+
cmdScanHelp();
|
|
12730
|
+
return true;
|
|
11740
12731
|
case "adopt":
|
|
11741
12732
|
cmdAdoptHelp();
|
|
11742
12733
|
return true;
|
|
@@ -11794,10 +12785,10 @@ async function main() {
|
|
|
11794
12785
|
if (command === "--version" || command === "-v" || command === "version") {
|
|
11795
12786
|
try {
|
|
11796
12787
|
const here = dirname6(fileURLToPath3(import.meta.url));
|
|
11797
|
-
const candidates = [
|
|
12788
|
+
const candidates = [join32(here, "..", "package.json"), join32(here, "..", "..", "package.json")];
|
|
11798
12789
|
for (const candidate of candidates) {
|
|
11799
|
-
if (
|
|
11800
|
-
const pkg = JSON.parse(
|
|
12790
|
+
if (existsSync30(candidate)) {
|
|
12791
|
+
const pkg = JSON.parse(readFileSync23(candidate, "utf-8"));
|
|
11801
12792
|
if (pkg.version) {
|
|
11802
12793
|
console.log(pkg.version);
|
|
11803
12794
|
return;
|
|
@@ -11820,6 +12811,10 @@ async function main() {
|
|
|
11820
12811
|
await cmdSetupWorkflow(args);
|
|
11821
12812
|
break;
|
|
11822
12813
|
}
|
|
12814
|
+
case "scan": {
|
|
12815
|
+
await cmdScanWorkflow(args);
|
|
12816
|
+
break;
|
|
12817
|
+
}
|
|
11823
12818
|
case "adopt": {
|
|
11824
12819
|
await cmdAdoptWorkflow(args);
|
|
11825
12820
|
break;
|
|
@@ -11935,7 +12930,7 @@ async function main() {
|
|
|
11935
12930
|
break;
|
|
11936
12931
|
}
|
|
11937
12932
|
case "upgrade": {
|
|
11938
|
-
const { cmdUpgrade } = await import("./upgrade-
|
|
12933
|
+
const { cmdUpgrade } = await import("./upgrade-WBFE6EN7.js");
|
|
11939
12934
|
const { flags } = parseLooseArgs(args);
|
|
11940
12935
|
const workspaceInfo = resolveWorkflowProject(flags, "upgrade");
|
|
11941
12936
|
if (!workspaceInfo) break;
|
|
@@ -11950,7 +12945,7 @@ async function main() {
|
|
|
11950
12945
|
`${YELLOW12}Note: \`decantr heal\` is deprecated. Use \`decantr check\` instead.${RESET16}`
|
|
11951
12946
|
);
|
|
11952
12947
|
}
|
|
11953
|
-
const { cmdHeal } = await import("./heal-
|
|
12948
|
+
const { cmdHeal } = await import("./heal-IRIDB7IZ.js");
|
|
11954
12949
|
const { flags } = parseLooseArgs(args);
|
|
11955
12950
|
const workspaceInfo = resolveWorkflowProject(flags, "check");
|
|
11956
12951
|
if (!workspaceInfo) break;
|
|
@@ -11975,7 +12970,7 @@ async function main() {
|
|
|
11975
12970
|
const { flags } = parseLooseArgs(args);
|
|
11976
12971
|
const workspaceInfo = resolveWorkflowProject(flags, "health");
|
|
11977
12972
|
if (!workspaceInfo) break;
|
|
11978
|
-
const { cmdHealth, parseHealthArgs } = await import("./health-
|
|
12973
|
+
const { cmdHealth, parseHealthArgs } = await import("./health-B4W7UJBZ.js");
|
|
11979
12974
|
await cmdHealth(workspaceInfo.appRoot, parseHealthArgs(stripProjectArgs(args)));
|
|
11980
12975
|
} catch (e) {
|
|
11981
12976
|
console.error(error2(e.message));
|
|
@@ -12003,7 +12998,7 @@ async function main() {
|
|
|
12003
12998
|
cmdStudioHelp();
|
|
12004
12999
|
break;
|
|
12005
13000
|
}
|
|
12006
|
-
const { cmdStudio, parseStudioArgs } = await import("./studio-
|
|
13001
|
+
const { cmdStudio, parseStudioArgs } = await import("./studio-JOEECLI6.js");
|
|
12007
13002
|
await cmdStudio(process.cwd(), parseStudioArgs(args));
|
|
12008
13003
|
} catch (e) {
|
|
12009
13004
|
console.error(error2(e.message));
|
|
@@ -12017,7 +13012,7 @@ async function main() {
|
|
|
12017
13012
|
cmdWorkspaceHelp();
|
|
12018
13013
|
break;
|
|
12019
13014
|
}
|
|
12020
|
-
const { cmdWorkspace } = await import("./workspace-
|
|
13015
|
+
const { cmdWorkspace } = await import("./workspace-7RU77ZZW.js");
|
|
12021
13016
|
await cmdWorkspace(process.cwd(), args);
|
|
12022
13017
|
} catch (e) {
|
|
12023
13018
|
console.error(error2(e.message));
|
|
@@ -12336,7 +13331,7 @@ async function main() {
|
|
|
12336
13331
|
break;
|
|
12337
13332
|
}
|
|
12338
13333
|
if (packType === "page" && route && !id) {
|
|
12339
|
-
const resolvedPath = essencePath ? resolveUserPath(essencePath) :
|
|
13334
|
+
const resolvedPath = essencePath ? resolveUserPath(essencePath) : join32(process.cwd(), "decantr.essence.json");
|
|
12340
13335
|
id = resolvePagePackIdForRoute(resolvedPath, route);
|
|
12341
13336
|
}
|
|
12342
13337
|
await printHostedSelectedExecutionPack(
|