@decantr/cli 2.10.0 → 2.12.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 +6 -4
- package/dist/bin.js +4 -4
- package/dist/{chunk-4SZ4SEKT.js → chunk-CAKQWTZS.js} +67 -29
- package/dist/{chunk-QDMXZSVK.js → chunk-F6SWDGKO.js} +1 -1
- package/dist/{chunk-WPP3JEMC.js → chunk-PHRVC575.js} +6 -5
- package/dist/{chunk-HQUTNZKF.js → chunk-SWPAAAO4.js} +1043 -447
- package/dist/{heal-2UCSPRTK.js → heal-WMSAX4LU.js} +1 -1
- package/dist/{health-VAYRQVTF.js → health-DBCUR6VQ.js} +2 -2
- package/dist/index.js +4 -4
- package/dist/{studio-WPETOZRW.js → studio-L6JJQYCS.js} +3 -3
- package/dist/{workspace-PBO3BJRL.js → workspace-BCA5RCHN.js} +3 -3
- package/package.json +5 -5
|
@@ -22,14 +22,14 @@ import {
|
|
|
22
22
|
listWorkspaceCandidates,
|
|
23
23
|
listWorkspaceProjects,
|
|
24
24
|
shouldFailWorkspaceHealth
|
|
25
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-F6SWDGKO.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-PHRVC575.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-CAKQWTZS.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 {
|
|
@@ -88,6 +88,7 @@ Commit these when they exist:
|
|
|
88
88
|
- \`.decantr/project.json\` - workflow, adoption mode, detection, and initialization metadata.
|
|
89
89
|
- \`.decantr/local-patterns.json\` - project-owned Brownfield UI patterns accepted by the team.
|
|
90
90
|
- \`.decantr/rules.json\` - project-owned mechanical rules accepted by the team.
|
|
91
|
+
- \`.decantr/style-bridge.json\` - accepted Hybrid style bridge from Decantr intent to project tokens/classes.
|
|
91
92
|
|
|
92
93
|
## Generated Context
|
|
93
94
|
|
|
@@ -2549,6 +2550,17 @@ function collectCssFiles(projectRoot) {
|
|
|
2549
2550
|
function selectorFromMatch(raw) {
|
|
2550
2551
|
return raw.replace(/\s+/g, " ").trim().slice(0, 120);
|
|
2551
2552
|
}
|
|
2553
|
+
function isThemeVariantClass(id, selector) {
|
|
2554
|
+
if (/^(switcher|toggle|selector|picker|menu|button|btn|control|provider|container|panel|card|icon|label|group|actions?)$/i.test(
|
|
2555
|
+
id
|
|
2556
|
+
)) {
|
|
2557
|
+
return false;
|
|
2558
|
+
}
|
|
2559
|
+
const selectors = selector.split(",").map((entry) => entry.trim());
|
|
2560
|
+
return selectors.some(
|
|
2561
|
+
(entry) => /^(?::root|html|body)?(?:\.[\w-]+)*\.theme-[a-z0-9-]+(?:\s|$|:|\[|\.)/i.test(entry)
|
|
2562
|
+
);
|
|
2563
|
+
}
|
|
2552
2564
|
function createThemeInventory(projectRoot, styling) {
|
|
2553
2565
|
const files = collectCssFiles(projectRoot);
|
|
2554
2566
|
const variantMap = /* @__PURE__ */ new Map();
|
|
@@ -2582,8 +2594,12 @@ function createThemeInventory(projectRoot, styling) {
|
|
|
2582
2594
|
const themeClass = selector.match(/\.theme-([a-z0-9-]+)/i);
|
|
2583
2595
|
if (/dark/i.test(selector)) id = "dark";
|
|
2584
2596
|
if (dataTheme?.[1]) id = dataTheme[1].toLowerCase();
|
|
2585
|
-
if (themeClass?.[1])
|
|
2586
|
-
|
|
2597
|
+
if (themeClass?.[1]) {
|
|
2598
|
+
const classId = themeClass[1].toLowerCase();
|
|
2599
|
+
if (!isThemeVariantClass(classId, selector)) continue;
|
|
2600
|
+
id = classId;
|
|
2601
|
+
}
|
|
2602
|
+
if (/^:root(?:\s|$|,)/.test(selector)) id = "base";
|
|
2587
2603
|
ensureVariant(id, selector, rel2);
|
|
2588
2604
|
}
|
|
2589
2605
|
for (const [id, entry] of variantMap) {
|
|
@@ -2617,6 +2633,7 @@ function createThemeInventory(projectRoot, styling) {
|
|
|
2617
2633
|
}
|
|
2618
2634
|
const variants = [...variantMap.values()].sort((a, b) => a.id.localeCompare(b.id));
|
|
2619
2635
|
const modes = variants.map((variant) => variant.id);
|
|
2636
|
+
const darkModeDetected = styling.darkMode || modes.includes("dark") || variants.some((variant) => /dark/i.test(variant.id));
|
|
2620
2637
|
if (variants.length > 2) {
|
|
2621
2638
|
notes.push(
|
|
2622
2639
|
"Multiple theme variants were observed. Essence V4 remains unchanged; variants are reported here for task-time context."
|
|
@@ -2627,7 +2644,7 @@ function createThemeInventory(projectRoot, styling) {
|
|
|
2627
2644
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2628
2645
|
localOnly: true,
|
|
2629
2646
|
stylingApproach: styling.approach,
|
|
2630
|
-
darkModeDetected
|
|
2647
|
+
darkModeDetected,
|
|
2631
2648
|
modes,
|
|
2632
2649
|
variants,
|
|
2633
2650
|
tokens: {
|
|
@@ -3217,8 +3234,8 @@ ${YELLOW2}Next step:${RESET2} Review ${BOLD}${reportDisplayPath}${RESET2}, then
|
|
|
3217
3234
|
}
|
|
3218
3235
|
|
|
3219
3236
|
// src/commands/ci.ts
|
|
3220
|
-
import { existsSync as
|
|
3221
|
-
import { dirname as dirname3, join as
|
|
3237
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync10, readFileSync as readFileSync13, writeFileSync as writeFileSync11 } from "fs";
|
|
3238
|
+
import { dirname as dirname3, join as join17, relative as relative4, resolve } from "path";
|
|
3222
3239
|
|
|
3223
3240
|
// src/local-law.ts
|
|
3224
3241
|
import { execFileSync } from "child_process";
|
|
@@ -3309,7 +3326,8 @@ function createBrownfieldCodifyProposal(input) {
|
|
|
3309
3326
|
],
|
|
3310
3327
|
componentPaths: evidence.buttonComponents,
|
|
3311
3328
|
decide: "Define primary, secondary, tertiary, destructive, icon-only, disabled, and loading button variants from this app.",
|
|
3312
|
-
|
|
3329
|
+
classHints: evidence.buttonClassHints,
|
|
3330
|
+
evidence: evidence.buttonComponents.length ? evidence.buttonComponents : evidence.buttonClassHints.length ? evidence.buttonClassHints : [
|
|
3313
3331
|
"No obvious Button wrapper found yet. Add the project-owned wrapper path before strict enforcement."
|
|
3314
3332
|
],
|
|
3315
3333
|
forbiddenAlternatives: ["New one-off button variants without updating this manifest."]
|
|
@@ -3635,19 +3653,51 @@ function summarizeSourceEvidence(projectRoot, files) {
|
|
|
3635
3653
|
formComponents: byName(["input", "field", "form", "select", "textarea"]),
|
|
3636
3654
|
shellComponents,
|
|
3637
3655
|
themeComponents,
|
|
3656
|
+
buttonClassHints: collectClassHints(projectRoot, files, [
|
|
3657
|
+
"button",
|
|
3658
|
+
"btn",
|
|
3659
|
+
"action",
|
|
3660
|
+
"primary",
|
|
3661
|
+
"secondary",
|
|
3662
|
+
"tertiary",
|
|
3663
|
+
"ghost",
|
|
3664
|
+
"link"
|
|
3665
|
+
]),
|
|
3638
3666
|
cardClassHints: collectClassHints(projectRoot, files, ["card", "panel", "surface", "tile"])
|
|
3639
3667
|
};
|
|
3640
3668
|
}
|
|
3641
3669
|
function collectClassHints(projectRoot, files, terms) {
|
|
3642
3670
|
const hints = /* @__PURE__ */ new Map();
|
|
3671
|
+
const wantsButton = terms.some(
|
|
3672
|
+
(term) => /^(button|btn|action|primary|secondary|tertiary|ghost|link)$/i.test(term)
|
|
3673
|
+
);
|
|
3674
|
+
const wantsSurface = terms.some((term) => /^(card|panel|surface|tile)$/i.test(term));
|
|
3675
|
+
const buttonSignal = /button|btn|action|primary|secondary|tertiary|ghost|link|destructive|icon/i;
|
|
3676
|
+
const surfaceSignal = /card|panel|surface|tile|rounded|shadow|border|bg-|p-\d|px-|py-/i;
|
|
3643
3677
|
for (const file of files) {
|
|
3644
3678
|
if (!UI_TEMPLATE_EXTENSIONS.has(extname(file.absolute))) continue;
|
|
3645
3679
|
const content = readFileSync11(join15(projectRoot, file.relative), "utf-8");
|
|
3646
3680
|
if (!terms.some((term) => content.toLowerCase().includes(term))) continue;
|
|
3681
|
+
if (wantsButton) {
|
|
3682
|
+
const openingTags = content.matchAll(
|
|
3683
|
+
/<([A-Za-z][\w.:/-]*)\b[^>]*\bclass(?:Name)?\s*=\s*["'`]([^"'`]+)["'`][^>]*>/g
|
|
3684
|
+
);
|
|
3685
|
+
for (const match of openingTags) {
|
|
3686
|
+
const tag = match[1];
|
|
3687
|
+
const value = match[2].trim();
|
|
3688
|
+
const tagLooksInteractive = /^(button|a|Link)$/i.test(tag) || /(^|\.)(Button|IconButton|LinkButton|Action)$/i.test(tag);
|
|
3689
|
+
const fileLooksInteractive = /button|action|link/i.test(basename2(file.relative));
|
|
3690
|
+
if (!tagLooksInteractive && !fileLooksInteractive) continue;
|
|
3691
|
+
if (!buttonSignal.test(value) && !fileLooksInteractive) continue;
|
|
3692
|
+
hints.set(value, (hints.get(value) ?? 0) + 1);
|
|
3693
|
+
}
|
|
3694
|
+
continue;
|
|
3695
|
+
}
|
|
3647
3696
|
const matches = content.matchAll(/\bclass(?:Name)?\s*=\s*["'`]([^"'`]+)["'`]/g);
|
|
3648
3697
|
for (const match of matches) {
|
|
3649
3698
|
const value = match[1].trim();
|
|
3650
|
-
|
|
3699
|
+
const keep = wantsSurface && surfaceSignal.test(value) || !wantsButton && !wantsSurface && (buttonSignal.test(value) || surfaceSignal.test(value));
|
|
3700
|
+
if (!keep) continue;
|
|
3651
3701
|
hints.set(value, (hints.get(value) ?? 0) + 1);
|
|
3652
3702
|
}
|
|
3653
3703
|
}
|
|
@@ -3714,6 +3764,277 @@ function lineAt(contents, line) {
|
|
|
3714
3764
|
return contents.split(/\r?\n/)[line - 1] ?? "";
|
|
3715
3765
|
}
|
|
3716
3766
|
|
|
3767
|
+
// src/style-bridge.ts
|
|
3768
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync9, readFileSync as readFileSync12, writeFileSync as writeFileSync10 } from "fs";
|
|
3769
|
+
import { join as join16 } from "path";
|
|
3770
|
+
function styleBridgeProposalPath(projectRoot) {
|
|
3771
|
+
return join16(projectRoot, ".decantr", "style-bridge.proposal.json");
|
|
3772
|
+
}
|
|
3773
|
+
function styleBridgePath(projectRoot) {
|
|
3774
|
+
return join16(projectRoot, ".decantr", "style-bridge.json");
|
|
3775
|
+
}
|
|
3776
|
+
function readJsonFile2(path) {
|
|
3777
|
+
if (!existsSync14(path)) return null;
|
|
3778
|
+
try {
|
|
3779
|
+
return JSON.parse(readFileSync12(path, "utf-8"));
|
|
3780
|
+
} catch {
|
|
3781
|
+
return null;
|
|
3782
|
+
}
|
|
3783
|
+
}
|
|
3784
|
+
function writeJsonFile(path, value) {
|
|
3785
|
+
writeFileSync10(path, `${JSON.stringify(value, null, 2)}
|
|
3786
|
+
`, "utf-8");
|
|
3787
|
+
}
|
|
3788
|
+
function readStyleBridge(projectRoot) {
|
|
3789
|
+
return readJsonFile2(styleBridgePath(projectRoot));
|
|
3790
|
+
}
|
|
3791
|
+
function readStyleBridgeProposal(projectRoot) {
|
|
3792
|
+
return readJsonFile2(styleBridgeProposalPath(projectRoot));
|
|
3793
|
+
}
|
|
3794
|
+
function stringArray(value) {
|
|
3795
|
+
return Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
|
|
3796
|
+
}
|
|
3797
|
+
function readThemeInventory(projectRoot) {
|
|
3798
|
+
const inventory = readJsonFile2(join16(projectRoot, ".decantr", "theme-inventory.json"));
|
|
3799
|
+
return {
|
|
3800
|
+
modes: stringArray(inventory?.modes),
|
|
3801
|
+
variantIds: Array.isArray(inventory?.variants) ? inventory.variants.map((variant) => variant.id).filter((id) => typeof id === "string") : [],
|
|
3802
|
+
darkModeDetected: typeof inventory?.darkModeDetected === "boolean" ? inventory.darkModeDetected : null
|
|
3803
|
+
};
|
|
3804
|
+
}
|
|
3805
|
+
function tokenHints(styling, terms) {
|
|
3806
|
+
return styling.cssVariables.filter((name) => terms.test(name)).slice(0, 12);
|
|
3807
|
+
}
|
|
3808
|
+
function readProjectPatternPack(projectRoot) {
|
|
3809
|
+
return readLocalPatternPack(projectRoot) ?? readJsonFile2(
|
|
3810
|
+
join16(projectRoot, ".decantr", "local-patterns.proposal.json")
|
|
3811
|
+
);
|
|
3812
|
+
}
|
|
3813
|
+
function classHintsForPattern(projectRoot, ids) {
|
|
3814
|
+
const pack = readProjectPatternPack(projectRoot);
|
|
3815
|
+
const classes = /* @__PURE__ */ new Set();
|
|
3816
|
+
for (const pattern of pack?.patterns ?? []) {
|
|
3817
|
+
if (!ids.includes(String(pattern.id))) continue;
|
|
3818
|
+
for (const hint of pattern.classHints ?? []) classes.add(hint);
|
|
3819
|
+
}
|
|
3820
|
+
return [...classes].slice(0, 12);
|
|
3821
|
+
}
|
|
3822
|
+
function sourceEvidence(projectRoot, ids) {
|
|
3823
|
+
const pack = readProjectPatternPack(projectRoot);
|
|
3824
|
+
const evidence = /* @__PURE__ */ new Set();
|
|
3825
|
+
for (const pattern of pack?.patterns ?? []) {
|
|
3826
|
+
if (!ids.includes(String(pattern.id))) continue;
|
|
3827
|
+
for (const path of pattern.componentPaths ?? []) evidence.add(path);
|
|
3828
|
+
for (const item of pattern.evidence ?? []) evidence.add(item);
|
|
3829
|
+
}
|
|
3830
|
+
return [...evidence].slice(0, 12);
|
|
3831
|
+
}
|
|
3832
|
+
function colorTokenNames(styling) {
|
|
3833
|
+
const names = new Set(Object.keys(styling.colors ?? {}));
|
|
3834
|
+
for (const variable of styling.cssVariables) {
|
|
3835
|
+
if (/color|bg|background|surface|text|foreground|border|primary|secondary|accent|muted/i.test(variable)) {
|
|
3836
|
+
names.add(variable);
|
|
3837
|
+
}
|
|
3838
|
+
}
|
|
3839
|
+
return [...names].slice(0, 40);
|
|
3840
|
+
}
|
|
3841
|
+
function createStyleBridgeProposal(input) {
|
|
3842
|
+
const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3843
|
+
const theme = readThemeInventory(input.projectRoot);
|
|
3844
|
+
const routeCount = input.essence && typeof input.essence === "object" && "blueprint" in input.essence ? Object.keys(input.essence.blueprint?.routes ?? {}).length : 0;
|
|
3845
|
+
const target = input.essence && typeof input.essence === "object" && "meta" in input.essence ? String(input.essence.meta?.target ?? "") || null : null;
|
|
3846
|
+
const darkModeDetected = theme.darkModeDetected ?? input.styling.darkMode;
|
|
3847
|
+
const themeModes = theme.modes.length > 0 ? theme.modes : darkModeDetected ? ["base", "dark"] : ["base"];
|
|
3848
|
+
const themeVariantIds = theme.variantIds.length > 0 ? theme.variantIds : themeModes.filter((mode) => mode !== "base");
|
|
3849
|
+
return {
|
|
3850
|
+
version: 1,
|
|
3851
|
+
status: "proposal",
|
|
3852
|
+
generatedAt,
|
|
3853
|
+
source: "decantr codify --style-bridge",
|
|
3854
|
+
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.",
|
|
3855
|
+
adoption: {
|
|
3856
|
+
mode: "style-bridge",
|
|
3857
|
+
workflowMode: "brownfield-attach",
|
|
3858
|
+
sourceAuthority: "Existing production source stays authoritative.",
|
|
3859
|
+
styleAuthority: "Use these mappings to translate Decantr concepts into project-owned tokens, classes, and components.",
|
|
3860
|
+
notRuntimeTakeover: true,
|
|
3861
|
+
authorityPrecedence: [
|
|
3862
|
+
"existing production source",
|
|
3863
|
+
"accepted style bridge",
|
|
3864
|
+
"accepted local patterns and rules",
|
|
3865
|
+
"Essence V4 contract",
|
|
3866
|
+
"hosted registry patterns and execution packs as optional guidance"
|
|
3867
|
+
]
|
|
3868
|
+
},
|
|
3869
|
+
project: {
|
|
3870
|
+
framework: input.detected.framework,
|
|
3871
|
+
packageManager: input.detected.packageManager,
|
|
3872
|
+
target,
|
|
3873
|
+
routeCount
|
|
3874
|
+
},
|
|
3875
|
+
styling: {
|
|
3876
|
+
approach: input.styling.approach,
|
|
3877
|
+
configFile: input.styling.configFile ?? null,
|
|
3878
|
+
darkModeDetected,
|
|
3879
|
+
cssVariables: input.styling.cssVariables.slice(0, 80),
|
|
3880
|
+
colorTokenNames: colorTokenNames(input.styling),
|
|
3881
|
+
themeModes,
|
|
3882
|
+
themeVariantIds
|
|
3883
|
+
},
|
|
3884
|
+
mappings: [
|
|
3885
|
+
{
|
|
3886
|
+
id: "surface",
|
|
3887
|
+
label: "Surfaces and cards",
|
|
3888
|
+
decantrIntent: "surface background, card treatment, border, radius, depth, and hover state",
|
|
3889
|
+
projectAuthority: "Use the app card/surface primitives, tokens, and accepted local law.",
|
|
3890
|
+
tokenHints: tokenHints(input.styling, /surface|card|panel|bg|background|border|shadow|radius/i),
|
|
3891
|
+
classHints: classHintsForPattern(input.projectRoot, ["surface-card"]),
|
|
3892
|
+
sourceEvidence: sourceEvidence(input.projectRoot, ["surface-card"]),
|
|
3893
|
+
guardrails: [
|
|
3894
|
+
"Do not invent a new card color/radius/shadow recipe without updating local law.",
|
|
3895
|
+
"Do not add Decantr CSS d-* classes unless adoption mode changes to decantr-css."
|
|
3896
|
+
]
|
|
3897
|
+
},
|
|
3898
|
+
{
|
|
3899
|
+
id: "action",
|
|
3900
|
+
label: "Actions and buttons",
|
|
3901
|
+
decantrIntent: "primary, secondary, tertiary, destructive, icon-only, loading, and disabled action states",
|
|
3902
|
+
projectAuthority: "Use the app button/action primitives and local variant names.",
|
|
3903
|
+
tokenHints: tokenHints(input.styling, /primary|secondary|accent|danger|error|destructive|focus/i),
|
|
3904
|
+
classHints: classHintsForPattern(input.projectRoot, ["button"]),
|
|
3905
|
+
sourceEvidence: sourceEvidence(input.projectRoot, ["button"]),
|
|
3906
|
+
guardrails: [
|
|
3907
|
+
"Map any new action style to primary/secondary/tertiary/destructive/icon before coding.",
|
|
3908
|
+
"Avoid raw button markup when a project-owned primitive exists."
|
|
3909
|
+
]
|
|
3910
|
+
},
|
|
3911
|
+
{
|
|
3912
|
+
id: "focus-accessibility",
|
|
3913
|
+
label: "Focus and accessibility",
|
|
3914
|
+
decantrIntent: "visible focus, keyboard clarity, contrast, and reduced-motion safety",
|
|
3915
|
+
projectAuthority: "Use the app accessibility classes, focus tokens, and framework conventions.",
|
|
3916
|
+
tokenHints: tokenHints(input.styling, /focus|ring|outline|contrast|motion|duration/i),
|
|
3917
|
+
classHints: [],
|
|
3918
|
+
sourceEvidence: [],
|
|
3919
|
+
guardrails: [
|
|
3920
|
+
"Every new interactive control needs visible focus evidence.",
|
|
3921
|
+
"Motion should honor prefers-reduced-motion or an existing app motion helper."
|
|
3922
|
+
]
|
|
3923
|
+
},
|
|
3924
|
+
{
|
|
3925
|
+
id: "layout-density",
|
|
3926
|
+
label: "Layout density and spacing",
|
|
3927
|
+
decantrIntent: "route gutters, section gaps, component padding, density, and responsive rhythm",
|
|
3928
|
+
projectAuthority: "Use existing layout wrappers, shell primitives, and spacing tokens/classes.",
|
|
3929
|
+
tokenHints: tokenHints(input.styling, /space|spacing|gap|gutter|container|radius/i),
|
|
3930
|
+
classHints: classHintsForPattern(input.projectRoot, ["page-shell"]),
|
|
3931
|
+
sourceEvidence: sourceEvidence(input.projectRoot, ["page-shell"]),
|
|
3932
|
+
guardrails: [
|
|
3933
|
+
"Do not let each page invent independent max-width, gutters, or scroll ownership.",
|
|
3934
|
+
"Use the detected shell/layout authority before adding a new wrapper."
|
|
3935
|
+
]
|
|
3936
|
+
},
|
|
3937
|
+
{
|
|
3938
|
+
id: "theme-variant",
|
|
3939
|
+
label: "Theme variants",
|
|
3940
|
+
decantrIntent: "light, dark, brand, tenant, seasonal, and density variants",
|
|
3941
|
+
projectAuthority: "Use the app theme provider, data attributes, CSS variables, or Tailwind mode strategy.",
|
|
3942
|
+
tokenHints: tokenHints(input.styling, /theme|dark|light|brand|tenant|mode|color/i),
|
|
3943
|
+
classHints: classHintsForPattern(input.projectRoot, ["theme-variant"]),
|
|
3944
|
+
sourceEvidence: sourceEvidence(input.projectRoot, ["theme-variant"]),
|
|
3945
|
+
guardrails: [
|
|
3946
|
+
"Keep theme modes documented in .decantr/theme-inventory.json and this bridge.",
|
|
3947
|
+
"Do not treat theme switcher container classes as standalone theme variants."
|
|
3948
|
+
]
|
|
3949
|
+
}
|
|
3950
|
+
],
|
|
3951
|
+
rules: [
|
|
3952
|
+
"The bridge is advisory until accepted; after acceptance, task/doctor/CI should surface it as Hybrid authority.",
|
|
3953
|
+
"The bridge does not make hosted registry patterns enforceable. Map hosted concepts into local law first.",
|
|
3954
|
+
"Keep deterministic blocking checks in .decantr/rules.json, ESLint, Biome, tests, or visual regression."
|
|
3955
|
+
],
|
|
3956
|
+
nextSteps: [
|
|
3957
|
+
"Review token and class hints. Replace generic hints with the exact project tokens/classes the team owns.",
|
|
3958
|
+
"Run decantr codify --accept to promote the proposal to .decantr/style-bridge.json.",
|
|
3959
|
+
"Use decantr task <route> before LLM edits so the style bridge appears in task context.",
|
|
3960
|
+
"Run decantr ci or decantr verify after edits to keep local law and Project Health visible."
|
|
3961
|
+
]
|
|
3962
|
+
};
|
|
3963
|
+
}
|
|
3964
|
+
function writeStyleBridgeProposal(projectRoot, proposal) {
|
|
3965
|
+
const decantrDir = join16(projectRoot, ".decantr");
|
|
3966
|
+
mkdirSync9(decantrDir, { recursive: true });
|
|
3967
|
+
const path = styleBridgeProposalPath(projectRoot);
|
|
3968
|
+
writeJsonFile(path, proposal);
|
|
3969
|
+
return path;
|
|
3970
|
+
}
|
|
3971
|
+
function acceptStyleBridge(projectRoot) {
|
|
3972
|
+
const proposal = readStyleBridgeProposal(projectRoot);
|
|
3973
|
+
if (!proposal) return null;
|
|
3974
|
+
const accepted = {
|
|
3975
|
+
...proposal,
|
|
3976
|
+
status: "accepted",
|
|
3977
|
+
acceptedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3978
|
+
};
|
|
3979
|
+
const path = styleBridgePath(projectRoot);
|
|
3980
|
+
writeJsonFile(path, accepted);
|
|
3981
|
+
const projectJsonPath = join16(projectRoot, ".decantr", "project.json");
|
|
3982
|
+
const projectJson = readJsonFile2(projectJsonPath) ?? {};
|
|
3983
|
+
const initialized = typeof projectJson.initialized === "object" && projectJson.initialized !== null ? projectJson.initialized : {};
|
|
3984
|
+
writeJsonFile(projectJsonPath, {
|
|
3985
|
+
...projectJson,
|
|
3986
|
+
initialized: {
|
|
3987
|
+
...initialized,
|
|
3988
|
+
workflowMode: initialized.workflowMode ?? "brownfield-attach",
|
|
3989
|
+
adoptionMode: "style-bridge"
|
|
3990
|
+
}
|
|
3991
|
+
});
|
|
3992
|
+
return path;
|
|
3993
|
+
}
|
|
3994
|
+
function createStyleBridgeTaskSummary(projectRoot) {
|
|
3995
|
+
const bridge = readStyleBridge(projectRoot);
|
|
3996
|
+
return {
|
|
3997
|
+
path: bridge ? ".decantr/style-bridge.json" : null,
|
|
3998
|
+
status: bridge?.status ?? null,
|
|
3999
|
+
mappingCount: bridge?.mappings?.length ?? 0,
|
|
4000
|
+
stylingApproach: bridge?.styling?.approach ?? null,
|
|
4001
|
+
themeModes: bridge?.styling?.themeModes ?? [],
|
|
4002
|
+
mappings: bridge?.mappings?.map((mapping) => ({
|
|
4003
|
+
id: mapping.id,
|
|
4004
|
+
label: mapping.label,
|
|
4005
|
+
tokenHints: mapping.tokenHints.slice(0, 6),
|
|
4006
|
+
classHints: mapping.classHints.slice(0, 4),
|
|
4007
|
+
guardrails: mapping.guardrails.slice(0, 3)
|
|
4008
|
+
})) ?? []
|
|
4009
|
+
};
|
|
4010
|
+
}
|
|
4011
|
+
function styleBridgeMatches(projectRoot, query) {
|
|
4012
|
+
if (!projectRoot) return [];
|
|
4013
|
+
const bridge = readStyleBridge(projectRoot);
|
|
4014
|
+
if (!bridge) return [];
|
|
4015
|
+
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]);
|
|
4016
|
+
if (terms.length === 0) return [];
|
|
4017
|
+
return bridge.mappings.map((mapping) => {
|
|
4018
|
+
const haystack = [
|
|
4019
|
+
mapping.id,
|
|
4020
|
+
mapping.label,
|
|
4021
|
+
mapping.decantrIntent,
|
|
4022
|
+
mapping.projectAuthority,
|
|
4023
|
+
...mapping.tokenHints,
|
|
4024
|
+
...mapping.classHints,
|
|
4025
|
+
...mapping.guardrails
|
|
4026
|
+
].join(" ").toLowerCase();
|
|
4027
|
+
const score = terms.reduce((sum, term) => sum + (haystack.includes(term) ? 1 : 0), 0);
|
|
4028
|
+
return {
|
|
4029
|
+
id: mapping.id,
|
|
4030
|
+
label: mapping.label,
|
|
4031
|
+
score,
|
|
4032
|
+
tokenHints: mapping.tokenHints.slice(0, 4),
|
|
4033
|
+
classHints: mapping.classHints.slice(0, 3)
|
|
4034
|
+
};
|
|
4035
|
+
}).filter((match) => match.score > 0).sort((a, b) => b.score - a.score || a.id.localeCompare(b.id)).slice(0, 5);
|
|
4036
|
+
}
|
|
4037
|
+
|
|
3717
4038
|
// src/commands/ci.ts
|
|
3718
4039
|
var BOLD2 = "\x1B[1m";
|
|
3719
4040
|
var DIM3 = "\x1B[2m";
|
|
@@ -3722,9 +4043,9 @@ var RED2 = "\x1B[31m";
|
|
|
3722
4043
|
var RESET3 = "\x1B[0m";
|
|
3723
4044
|
var CI_SCHEMA = "https://decantr.ai/schemas/decantr-ci-report.v1.json";
|
|
3724
4045
|
function readJson(path) {
|
|
3725
|
-
if (!
|
|
4046
|
+
if (!existsSync15(path)) return null;
|
|
3726
4047
|
try {
|
|
3727
|
-
return JSON.parse(
|
|
4048
|
+
return JSON.parse(readFileSync13(path, "utf-8"));
|
|
3728
4049
|
} catch {
|
|
3729
4050
|
return null;
|
|
3730
4051
|
}
|
|
@@ -3768,21 +4089,21 @@ function parseProvider(value) {
|
|
|
3768
4089
|
throw new Error("Invalid --provider value. Use github or generic.");
|
|
3769
4090
|
}
|
|
3770
4091
|
function detectPackageManager2(root) {
|
|
3771
|
-
const pkg = readJson(
|
|
4092
|
+
const pkg = readJson(join17(root, "package.json"));
|
|
3772
4093
|
const declared = pkg?.packageManager?.split("@")[0];
|
|
3773
4094
|
if (declared === "pnpm" || declared === "npm" || declared === "yarn" || declared === "bun") {
|
|
3774
4095
|
return declared;
|
|
3775
4096
|
}
|
|
3776
|
-
if (
|
|
3777
|
-
if (
|
|
3778
|
-
if (
|
|
3779
|
-
if (
|
|
4097
|
+
if (existsSync15(join17(root, "pnpm-lock.yaml"))) return "pnpm";
|
|
4098
|
+
if (existsSync15(join17(root, "package-lock.json"))) return "npm";
|
|
4099
|
+
if (existsSync15(join17(root, "yarn.lock"))) return "yarn";
|
|
4100
|
+
if (existsSync15(join17(root, "bun.lock")) || existsSync15(join17(root, "bun.lockb"))) return "bun";
|
|
3780
4101
|
return "unknown";
|
|
3781
4102
|
}
|
|
3782
4103
|
function hasWorkspaceMarker(root) {
|
|
3783
|
-
const pkg = readJson(
|
|
4104
|
+
const pkg = readJson(join17(root, "package.json"));
|
|
3784
4105
|
return Boolean(
|
|
3785
|
-
|
|
4106
|
+
existsSync15(join17(root, "pnpm-workspace.yaml")) || existsSync15(join17(root, "turbo.json")) || existsSync15(join17(root, "nx.json")) || pkg?.workspaces
|
|
3786
4107
|
);
|
|
3787
4108
|
}
|
|
3788
4109
|
function installCommand(packageManager) {
|
|
@@ -3828,7 +4149,7 @@ function decantrCommand(packageManager) {
|
|
|
3828
4149
|
}
|
|
3829
4150
|
}
|
|
3830
4151
|
function localCliPinned(root) {
|
|
3831
|
-
const pkg = readJson(
|
|
4152
|
+
const pkg = readJson(join17(root, "package.json"));
|
|
3832
4153
|
return Boolean(pkg?.devDependencies?.["@decantr/cli"] || pkg?.dependencies?.["@decantr/cli"]);
|
|
3833
4154
|
}
|
|
3834
4155
|
function projectSlug(projectPath) {
|
|
@@ -3836,8 +4157,8 @@ function projectSlug(projectPath) {
|
|
|
3836
4157
|
}
|
|
3837
4158
|
function writeOutput(root, path, content) {
|
|
3838
4159
|
const absolute = resolve(root, path);
|
|
3839
|
-
|
|
3840
|
-
|
|
4160
|
+
mkdirSync10(dirname3(absolute), { recursive: true });
|
|
4161
|
+
writeFileSync11(absolute, content, "utf-8");
|
|
3841
4162
|
}
|
|
3842
4163
|
function summarizeLocalLaw(projectRoot) {
|
|
3843
4164
|
const validation = validateLocalLaw(projectRoot);
|
|
@@ -3845,15 +4166,154 @@ function summarizeLocalLaw(projectRoot) {
|
|
|
3845
4166
|
checked: validation.patternPackPresent || validation.ruleManifestPresent,
|
|
3846
4167
|
patternsPresent: validation.patternPackPresent,
|
|
3847
4168
|
rulesPresent: validation.ruleManifestPresent,
|
|
4169
|
+
warnings: validation.warnings,
|
|
4170
|
+
findings: validation.findings.map((finding) => ({
|
|
4171
|
+
ruleId: finding.ruleId,
|
|
4172
|
+
severity: finding.severity,
|
|
4173
|
+
file: finding.file,
|
|
4174
|
+
line: finding.line,
|
|
4175
|
+
column: finding.column,
|
|
4176
|
+
message: finding.message,
|
|
4177
|
+
suggestedFix: finding.suggestedFix
|
|
4178
|
+
})),
|
|
3848
4179
|
errorCount: validation.findings.filter((finding) => finding.severity === "error").length,
|
|
3849
4180
|
warnCount: validation.findings.filter((finding) => finding.severity === "warn").length + validation.warnings.length
|
|
3850
4181
|
};
|
|
3851
4182
|
}
|
|
4183
|
+
function summarizeStyleBridge(projectRoot) {
|
|
4184
|
+
const summary = createStyleBridgeTaskSummary(projectRoot);
|
|
4185
|
+
const warnings = [];
|
|
4186
|
+
if (summary.path && summary.mappingCount === 0) {
|
|
4187
|
+
warnings.push(".decantr/style-bridge.json has no mappings.");
|
|
4188
|
+
}
|
|
4189
|
+
return {
|
|
4190
|
+
checked: Boolean(summary.path),
|
|
4191
|
+
present: Boolean(summary.path),
|
|
4192
|
+
status: summary.status,
|
|
4193
|
+
mappingCount: summary.mappingCount,
|
|
4194
|
+
stylingApproach: summary.stylingApproach,
|
|
4195
|
+
themeModes: summary.themeModes,
|
|
4196
|
+
warnings
|
|
4197
|
+
};
|
|
4198
|
+
}
|
|
3852
4199
|
function localLawFails(summary, failOn) {
|
|
3853
4200
|
if (failOn === "none" || !summary.checked) return false;
|
|
3854
4201
|
if (summary.errorCount > 0) return true;
|
|
3855
4202
|
return failOn === "warn" && summary.warnCount > 0;
|
|
3856
4203
|
}
|
|
4204
|
+
function styleBridgeFails(summary, failOn) {
|
|
4205
|
+
if (failOn === "none" || !summary.checked) return false;
|
|
4206
|
+
return failOn === "warn" && summary.warnings.length > 0;
|
|
4207
|
+
}
|
|
4208
|
+
function projectCiStatus(health, localLaw, styleBridge) {
|
|
4209
|
+
if (health.status === "error" || localLaw.errorCount > 0) return "error";
|
|
4210
|
+
if (health.status === "warning" || localLaw.warnCount > 0 || styleBridge.warnings.length > 0) {
|
|
4211
|
+
return "warning";
|
|
4212
|
+
}
|
|
4213
|
+
return health.status;
|
|
4214
|
+
}
|
|
4215
|
+
function formatLocalLawText(summary, health) {
|
|
4216
|
+
const lines = ["", `${BOLD2}Project-owned local law:${RESET3}`];
|
|
4217
|
+
if (!summary.checked) {
|
|
4218
|
+
const isBrownfield = health.summary.workflowMode === "brownfield-attach";
|
|
4219
|
+
lines.push(
|
|
4220
|
+
isBrownfield ? " Not accepted yet. Run `decantr codify --from-audit`, review, then `decantr codify --accept`." : " Not active for this project."
|
|
4221
|
+
);
|
|
4222
|
+
return `${lines.join("\n")}
|
|
4223
|
+
`;
|
|
4224
|
+
}
|
|
4225
|
+
lines.push(
|
|
4226
|
+
` Patterns: ${summary.patternsPresent ? "present" : "missing"} | Rules: ${summary.rulesPresent ? "present" : "missing"}`
|
|
4227
|
+
);
|
|
4228
|
+
lines.push(` Findings: ${summary.errorCount} error(s), ${summary.warnCount} warning(s)`);
|
|
4229
|
+
for (const warning of summary.warnings.slice(0, 5)) {
|
|
4230
|
+
lines.push(` ${DIM3}[WARN] ${warning}${RESET3}`);
|
|
4231
|
+
}
|
|
4232
|
+
for (const finding of summary.findings.slice(0, 8)) {
|
|
4233
|
+
const label = finding.severity.toUpperCase();
|
|
4234
|
+
lines.push(
|
|
4235
|
+
` [${label}] ${finding.ruleId} ${finding.file}:${finding.line}:${finding.column} ${finding.message}`
|
|
4236
|
+
);
|
|
4237
|
+
lines.push(` ${DIM3}${finding.suggestedFix}${RESET3}`);
|
|
4238
|
+
}
|
|
4239
|
+
if (summary.findings.length > 8) {
|
|
4240
|
+
lines.push(` ${DIM3}...${summary.findings.length - 8} more local-law finding(s)${RESET3}`);
|
|
4241
|
+
}
|
|
4242
|
+
return `${lines.join("\n")}
|
|
4243
|
+
`;
|
|
4244
|
+
}
|
|
4245
|
+
function formatStyleBridgeText(summary) {
|
|
4246
|
+
const lines = ["", `${BOLD2}Project-owned style bridge:${RESET3}`];
|
|
4247
|
+
if (!summary.checked) {
|
|
4248
|
+
lines.push(" Not active for this project.");
|
|
4249
|
+
return `${lines.join("\n")}
|
|
4250
|
+
`;
|
|
4251
|
+
}
|
|
4252
|
+
lines.push(
|
|
4253
|
+
` Present: ${summary.present ? "yes" : "no"} | Mappings: ${summary.mappingCount} | Styling: ${summary.stylingApproach ?? "unknown"}`
|
|
4254
|
+
);
|
|
4255
|
+
if (summary.themeModes.length > 0) {
|
|
4256
|
+
lines.push(` Theme modes: ${summary.themeModes.join(", ")}`);
|
|
4257
|
+
}
|
|
4258
|
+
for (const warning of summary.warnings.slice(0, 5)) {
|
|
4259
|
+
lines.push(` ${DIM3}[WARN] ${warning}${RESET3}`);
|
|
4260
|
+
}
|
|
4261
|
+
return `${lines.join("\n")}
|
|
4262
|
+
`;
|
|
4263
|
+
}
|
|
4264
|
+
function formatLocalLawMarkdown(summary, health) {
|
|
4265
|
+
const lines = ["## Project-Owned Local Law", ""];
|
|
4266
|
+
if (!summary.checked) {
|
|
4267
|
+
if (health.summary.workflowMode !== "brownfield-attach") {
|
|
4268
|
+
lines.push("Local law is not active for this project.");
|
|
4269
|
+
return lines.join("\n");
|
|
4270
|
+
}
|
|
4271
|
+
lines.push(
|
|
4272
|
+
"Local law has not been accepted yet. Run `decantr codify --from-audit`, review the proposal, then run `decantr codify --accept`."
|
|
4273
|
+
);
|
|
4274
|
+
return lines.join("\n");
|
|
4275
|
+
}
|
|
4276
|
+
lines.push(
|
|
4277
|
+
`Patterns: **${summary.patternsPresent ? "present" : "missing"}** \xB7 Rules: **${summary.rulesPresent ? "present" : "missing"}**`
|
|
4278
|
+
);
|
|
4279
|
+
lines.push("");
|
|
4280
|
+
lines.push(`Findings: **${summary.errorCount} error(s), ${summary.warnCount} warning(s)**`);
|
|
4281
|
+
if (summary.warnings.length > 0) {
|
|
4282
|
+
lines.push("");
|
|
4283
|
+
for (const warning of summary.warnings.slice(0, 5)) {
|
|
4284
|
+
lines.push(`- Warning: ${warning}`);
|
|
4285
|
+
}
|
|
4286
|
+
}
|
|
4287
|
+
if (summary.findings.length > 0) {
|
|
4288
|
+
lines.push("");
|
|
4289
|
+
for (const finding of summary.findings.slice(0, 12)) {
|
|
4290
|
+
lines.push(
|
|
4291
|
+
`- \`${finding.severity}\` \`${finding.ruleId}\` at \`${finding.file}:${finding.line}:${finding.column}\`: ${finding.message}`
|
|
4292
|
+
);
|
|
4293
|
+
}
|
|
4294
|
+
}
|
|
4295
|
+
return lines.join("\n");
|
|
4296
|
+
}
|
|
4297
|
+
function formatStyleBridgeMarkdown(summary) {
|
|
4298
|
+
const lines = ["## Project-Owned Style Bridge", ""];
|
|
4299
|
+
if (!summary.checked) {
|
|
4300
|
+
lines.push("Style bridge is not active for this project.");
|
|
4301
|
+
return lines.join("\n");
|
|
4302
|
+
}
|
|
4303
|
+
lines.push(
|
|
4304
|
+
`Present: **${summary.present ? "yes" : "no"}** \xB7 Mappings: **${summary.mappingCount}** \xB7 Styling: **${summary.stylingApproach ?? "unknown"}**`
|
|
4305
|
+
);
|
|
4306
|
+
if (summary.themeModes.length > 0) {
|
|
4307
|
+
lines.push("", `Theme modes: ${summary.themeModes.map((mode) => `\`${mode}\``).join(", ")}`);
|
|
4308
|
+
}
|
|
4309
|
+
if (summary.warnings.length > 0) {
|
|
4310
|
+
lines.push("");
|
|
4311
|
+
for (const warning of summary.warnings.slice(0, 5)) {
|
|
4312
|
+
lines.push(`- Warning: ${warning}`);
|
|
4313
|
+
}
|
|
4314
|
+
}
|
|
4315
|
+
return lines.join("\n");
|
|
4316
|
+
}
|
|
3857
4317
|
function formatProjectCiMarkdown(report) {
|
|
3858
4318
|
const lines = [
|
|
3859
4319
|
"# Decantr CI",
|
|
@@ -3863,8 +4323,13 @@ function formatProjectCiMarkdown(report) {
|
|
|
3863
4323
|
`- Status: **${report.status}**`,
|
|
3864
4324
|
`- Fail on: \`${report.failOn}\``,
|
|
3865
4325
|
`- Local law: ${report.localLaw.checked ? `${report.localLaw.errorCount} error(s), ${report.localLaw.warnCount} warning(s)` : "not accepted yet"}`,
|
|
4326
|
+
`- Style bridge: ${report.styleBridge.checked ? `${report.styleBridge.mappingCount} mapping(s)` : "not active"}`,
|
|
4327
|
+
"",
|
|
4328
|
+
formatProjectHealthMarkdown(report.health),
|
|
4329
|
+
"",
|
|
4330
|
+
formatLocalLawMarkdown(report.localLaw, report.health),
|
|
3866
4331
|
"",
|
|
3867
|
-
|
|
4332
|
+
formatStyleBridgeMarkdown(report.styleBridge)
|
|
3868
4333
|
];
|
|
3869
4334
|
return `${lines.join("\n")}
|
|
3870
4335
|
`;
|
|
@@ -3955,7 +4420,7 @@ jobs:
|
|
|
3955
4420
|
}
|
|
3956
4421
|
function writeCiInit(root, options) {
|
|
3957
4422
|
const workspaceInfo = resolveWorkspaceInfo(root, options.project);
|
|
3958
|
-
if (options.project && !
|
|
4423
|
+
if (options.project && !existsSync15(workspaceInfo.appRoot)) {
|
|
3959
4424
|
throw new Error(`Project path does not exist: ${options.project}`);
|
|
3960
4425
|
}
|
|
3961
4426
|
if (workspaceInfo.requiresProjectSelection && !options.workspace) {
|
|
@@ -3978,7 +4443,7 @@ function writeCiInit(root, options) {
|
|
|
3978
4443
|
if (provider === "generic") {
|
|
3979
4444
|
const path2 = ".decantr/ci/decantr-ci.sh";
|
|
3980
4445
|
const absolute2 = resolve(outputRoot, path2);
|
|
3981
|
-
if (
|
|
4446
|
+
if (existsSync15(absolute2) && !options.force) {
|
|
3982
4447
|
throw new Error(`${path2} already exists. Re-run with --force to replace it.`);
|
|
3983
4448
|
}
|
|
3984
4449
|
writeOutput(
|
|
@@ -3999,7 +4464,7 @@ function writeCiInit(root, options) {
|
|
|
3999
4464
|
}
|
|
4000
4465
|
const path = ".github/workflows/decantr-ci.yml";
|
|
4001
4466
|
const absolute = resolve(outputRoot, path);
|
|
4002
|
-
if (
|
|
4467
|
+
if (existsSync15(absolute) && !options.force) {
|
|
4003
4468
|
throw new Error(`${path} already exists. Re-run with --force to replace it.`);
|
|
4004
4469
|
}
|
|
4005
4470
|
writeOutput(
|
|
@@ -4048,7 +4513,7 @@ async function runWorkspaceCi(root, options) {
|
|
|
4048
4513
|
}
|
|
4049
4514
|
async function runProjectCi(root, options) {
|
|
4050
4515
|
const workspaceInfo = resolveWorkspaceInfo(root, options.project);
|
|
4051
|
-
if (options.project && !
|
|
4516
|
+
if (options.project && !existsSync15(workspaceInfo.appRoot)) {
|
|
4052
4517
|
console.error(`${RED2}Project path does not exist: ${options.project}${RESET3}`);
|
|
4053
4518
|
return 1;
|
|
4054
4519
|
}
|
|
@@ -4061,6 +4526,7 @@ async function runProjectCi(root, options) {
|
|
|
4061
4526
|
const failOn = options.failOn ?? "error";
|
|
4062
4527
|
const health = await createProjectHealthReport(workspaceInfo.appRoot);
|
|
4063
4528
|
const localLaw = summarizeLocalLaw(workspaceInfo.appRoot);
|
|
4529
|
+
const styleBridge = summarizeStyleBridge(workspaceInfo.appRoot);
|
|
4064
4530
|
const projectPath = workspaceInfo.appRoot === workspaceInfo.workspaceRoot ? null : workspaceInfo.appRoot.replace(`${workspaceInfo.workspaceRoot}/`, "");
|
|
4065
4531
|
const report = {
|
|
4066
4532
|
$schema: CI_SCHEMA,
|
|
@@ -4068,9 +4534,10 @@ async function runProjectCi(root, options) {
|
|
|
4068
4534
|
mode: "project",
|
|
4069
4535
|
projectPath,
|
|
4070
4536
|
failOn,
|
|
4071
|
-
status: health
|
|
4537
|
+
status: projectCiStatus(health, localLaw, styleBridge),
|
|
4072
4538
|
health,
|
|
4073
|
-
localLaw
|
|
4539
|
+
localLaw,
|
|
4540
|
+
styleBridge
|
|
4074
4541
|
};
|
|
4075
4542
|
const json = `${JSON.stringify(report, null, 2)}
|
|
4076
4543
|
`;
|
|
@@ -4080,9 +4547,12 @@ async function runProjectCi(root, options) {
|
|
|
4080
4547
|
if (!options.output && !options.markdownOutput) {
|
|
4081
4548
|
if (options.json) process.stdout.write(json);
|
|
4082
4549
|
else if (options.markdown) process.stdout.write(markdown);
|
|
4083
|
-
else
|
|
4550
|
+
else
|
|
4551
|
+
process.stdout.write(
|
|
4552
|
+
`${formatProjectHealthText(health)}${formatLocalLawText(localLaw, health)}${formatStyleBridgeText(styleBridge)}`
|
|
4553
|
+
);
|
|
4084
4554
|
}
|
|
4085
|
-
return shouldFailHealth(health, failOn) || localLawFails(localLaw, failOn) ? 1 : 0;
|
|
4555
|
+
return shouldFailHealth(health, failOn) || localLawFails(localLaw, failOn) || styleBridgeFails(styleBridge, failOn) ? 1 : 0;
|
|
4086
4556
|
}
|
|
4087
4557
|
function cmdCiHelp() {
|
|
4088
4558
|
console.log(`
|
|
@@ -4117,8 +4587,8 @@ async function cmdCi(args = ["ci"], root = process.cwd()) {
|
|
|
4117
4587
|
}
|
|
4118
4588
|
|
|
4119
4589
|
// src/commands/create.ts
|
|
4120
|
-
import { existsSync as
|
|
4121
|
-
import { join as
|
|
4590
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync11, writeFileSync as writeFileSync12 } from "fs";
|
|
4591
|
+
import { join as join18 } from "path";
|
|
4122
4592
|
import {
|
|
4123
4593
|
CONTENT_TYPE_TO_API_CONTENT_TYPE,
|
|
4124
4594
|
CONTENT_TYPES
|
|
@@ -4314,23 +4784,23 @@ function cmdCreate(type, name, projectRoot = process.cwd()) {
|
|
|
4314
4784
|
}
|
|
4315
4785
|
const contentType = type;
|
|
4316
4786
|
const plural = PLURAL[contentType];
|
|
4317
|
-
const customDir =
|
|
4318
|
-
const filePath =
|
|
4319
|
-
if (
|
|
4787
|
+
const customDir = join18(projectRoot, ".decantr", "custom", plural);
|
|
4788
|
+
const filePath = join18(customDir, `${name}.json`);
|
|
4789
|
+
if (existsSync16(filePath)) {
|
|
4320
4790
|
console.error(`${type} "${name}" already exists at ${filePath}`);
|
|
4321
4791
|
process.exitCode = 1;
|
|
4322
4792
|
return;
|
|
4323
4793
|
}
|
|
4324
|
-
|
|
4794
|
+
mkdirSync11(customDir, { recursive: true });
|
|
4325
4795
|
const skeleton = getSkeleton(contentType, name, humanizeId(name));
|
|
4326
|
-
|
|
4796
|
+
writeFileSync12(filePath, JSON.stringify(skeleton, null, 2));
|
|
4327
4797
|
console.log(`Created ${type} "${name}" at ${filePath}`);
|
|
4328
4798
|
console.log(`Edit it, then publish with: decantr publish ${type} ${name}`);
|
|
4329
4799
|
}
|
|
4330
4800
|
|
|
4331
4801
|
// src/commands/doctor.ts
|
|
4332
|
-
import { existsSync as
|
|
4333
|
-
import { dirname as dirname4, join as
|
|
4802
|
+
import { existsSync as existsSync17, readdirSync as readdirSync6, readFileSync as readFileSync14 } from "fs";
|
|
4803
|
+
import { dirname as dirname4, join as join19, relative as relative5 } from "path";
|
|
4334
4804
|
import { fileURLToPath } from "url";
|
|
4335
4805
|
import { isV4 as isV44 } from "@decantr/essence-spec";
|
|
4336
4806
|
import { collectMissingPackManifestFiles } from "@decantr/verifier";
|
|
@@ -4342,25 +4812,25 @@ var YELLOW3 = "\x1B[33m";
|
|
|
4342
4812
|
var CYAN2 = "\x1B[36m";
|
|
4343
4813
|
var RESET4 = "\x1B[0m";
|
|
4344
4814
|
function readJson2(path) {
|
|
4345
|
-
if (!
|
|
4815
|
+
if (!existsSync17(path)) return null;
|
|
4346
4816
|
try {
|
|
4347
|
-
return JSON.parse(
|
|
4817
|
+
return JSON.parse(readFileSync14(path, "utf-8"));
|
|
4348
4818
|
} catch {
|
|
4349
4819
|
return null;
|
|
4350
4820
|
}
|
|
4351
4821
|
}
|
|
4352
4822
|
function isContractOnlyProject(projectRoot) {
|
|
4353
|
-
const projectJson = readJson2(
|
|
4354
|
-
return projectJson?.initialized?.adoptionMode === "contract-only";
|
|
4823
|
+
const projectJson = readJson2(join19(projectRoot, ".decantr", "project.json"));
|
|
4824
|
+
return projectJson?.initialized?.adoptionMode === "contract-only" || projectJson?.initialized?.adoptionMode === "style-bridge";
|
|
4355
4825
|
}
|
|
4356
4826
|
function readCliPackageVersion() {
|
|
4357
|
-
const packagePath =
|
|
4358
|
-
const srcPackagePath =
|
|
4827
|
+
const packagePath = join19(dirname4(fileURLToPath(import.meta.url)), "..", "..", "package.json");
|
|
4828
|
+
const srcPackagePath = join19(dirname4(fileURLToPath(import.meta.url)), "..", "package.json");
|
|
4359
4829
|
const pkg = readJson2(packagePath) ?? readJson2(srcPackagePath);
|
|
4360
4830
|
return pkg?.version ?? "unknown";
|
|
4361
4831
|
}
|
|
4362
4832
|
function readPackageJson2(dir) {
|
|
4363
|
-
return readJson2(
|
|
4833
|
+
return readJson2(join19(dir, "package.json"));
|
|
4364
4834
|
}
|
|
4365
4835
|
function detectPackageManager3(root) {
|
|
4366
4836
|
const pkg = readPackageJson2(root);
|
|
@@ -4368,10 +4838,10 @@ function detectPackageManager3(root) {
|
|
|
4368
4838
|
if (declared === "pnpm" || declared === "npm" || declared === "yarn" || declared === "bun") {
|
|
4369
4839
|
return declared;
|
|
4370
4840
|
}
|
|
4371
|
-
if (
|
|
4372
|
-
if (
|
|
4373
|
-
if (
|
|
4374
|
-
if (
|
|
4841
|
+
if (existsSync17(join19(root, "pnpm-lock.yaml"))) return "pnpm";
|
|
4842
|
+
if (existsSync17(join19(root, "package-lock.json"))) return "npm";
|
|
4843
|
+
if (existsSync17(join19(root, "yarn.lock"))) return "yarn";
|
|
4844
|
+
if (existsSync17(join19(root, "bun.lock")) || existsSync17(join19(root, "bun.lockb"))) return "bun";
|
|
4375
4845
|
return "unknown";
|
|
4376
4846
|
}
|
|
4377
4847
|
function localCliDependency(root) {
|
|
@@ -4381,7 +4851,7 @@ function localCliDependency(root) {
|
|
|
4381
4851
|
function hasWorkspaceMarker2(root) {
|
|
4382
4852
|
const pkg = readPackageJson2(root);
|
|
4383
4853
|
return Boolean(
|
|
4384
|
-
|
|
4854
|
+
existsSync17(join19(root, "pnpm-workspace.yaml")) || existsSync17(join19(root, "turbo.json")) || existsSync17(join19(root, "nx.json")) || pkg?.workspaces
|
|
4385
4855
|
);
|
|
4386
4856
|
}
|
|
4387
4857
|
function pinCliCommand2(packageManager, root) {
|
|
@@ -4404,8 +4874,8 @@ function rel(root, path) {
|
|
|
4404
4874
|
}
|
|
4405
4875
|
function hasAnyFile(dir, names) {
|
|
4406
4876
|
for (const name of names) {
|
|
4407
|
-
const path =
|
|
4408
|
-
if (
|
|
4877
|
+
const path = join19(dir, name);
|
|
4878
|
+
if (existsSync17(path)) return name;
|
|
4409
4879
|
}
|
|
4410
4880
|
return null;
|
|
4411
4881
|
}
|
|
@@ -4427,7 +4897,7 @@ function detectDesignAuthority(workspaceRoot, appRoot) {
|
|
|
4427
4897
|
"libs/design-system"
|
|
4428
4898
|
];
|
|
4429
4899
|
for (const candidate of sharedCandidates) {
|
|
4430
|
-
if (
|
|
4900
|
+
if (existsSync17(join19(workspaceRoot, candidate))) found.add(candidate);
|
|
4431
4901
|
}
|
|
4432
4902
|
for (const root of [appRoot, workspaceRoot]) {
|
|
4433
4903
|
const label = root === appRoot ? "app" : "workspace";
|
|
@@ -4438,9 +4908,9 @@ function detectDesignAuthority(workspaceRoot, appRoot) {
|
|
|
4438
4908
|
"tailwind.config.cjs"
|
|
4439
4909
|
]);
|
|
4440
4910
|
if (tailwind) found.add(`${label}:${tailwind}`);
|
|
4441
|
-
if (
|
|
4442
|
-
if (
|
|
4443
|
-
if (
|
|
4911
|
+
if (existsSync17(join19(root, ".storybook"))) found.add(`${label}:.storybook`);
|
|
4912
|
+
if (existsSync17(join19(root, "components", "ui"))) found.add(`${label}:components/ui`);
|
|
4913
|
+
if (existsSync17(join19(root, "src", "components", "ui"))) found.add(`${label}:src/components/ui`);
|
|
4444
4914
|
if (packageHasDependency(root, ["@storybook/react", "storybook"]))
|
|
4445
4915
|
found.add(`${label}:storybook`);
|
|
4446
4916
|
if (packageHasDependency(root, ["tailwindcss"])) found.add(`${label}:tailwindcss`);
|
|
@@ -4452,18 +4922,18 @@ function detectDesignAuthority(workspaceRoot, appRoot) {
|
|
|
4452
4922
|
}
|
|
4453
4923
|
function findCiFiles(root) {
|
|
4454
4924
|
const files = [];
|
|
4455
|
-
const workflows =
|
|
4456
|
-
if (
|
|
4925
|
+
const workflows = join19(root, ".github", "workflows");
|
|
4926
|
+
if (existsSync17(workflows)) {
|
|
4457
4927
|
for (const entry of readdirSync6(workflows, { withFileTypes: true })) {
|
|
4458
4928
|
if (!entry.isFile()) continue;
|
|
4459
|
-
const path =
|
|
4460
|
-
const text =
|
|
4929
|
+
const path = join19(workflows, entry.name);
|
|
4930
|
+
const text = readFileSync14(path, "utf-8");
|
|
4461
4931
|
if (text.includes("decantr")) files.push(rel(root, path));
|
|
4462
4932
|
}
|
|
4463
4933
|
}
|
|
4464
4934
|
for (const candidate of ["Jenkinsfile", ".gitlab-ci.yml", "azure-pipelines.yml", "BUILD"]) {
|
|
4465
|
-
const path =
|
|
4466
|
-
if (
|
|
4935
|
+
const path = join19(root, candidate);
|
|
4936
|
+
if (existsSync17(path) && readFileSync14(path, "utf-8").includes("decantr")) {
|
|
4467
4937
|
files.push(candidate);
|
|
4468
4938
|
}
|
|
4469
4939
|
}
|
|
@@ -4503,15 +4973,17 @@ function deriveAdoptionLane(input) {
|
|
|
4503
4973
|
};
|
|
4504
4974
|
}
|
|
4505
4975
|
const hasLocalLaw = input.localPatternsPresent || input.localRulesPresent;
|
|
4976
|
+
const hasStyleBridge = input.styleBridgePresent || input.adoptionMode === "style-bridge";
|
|
4506
4977
|
if (input.workflowMode === "hybrid-compose") {
|
|
4507
4978
|
return {
|
|
4508
4979
|
id: "hybrid-compose",
|
|
4509
4980
|
label: "Hybrid composition",
|
|
4510
4981
|
sourceAuthority: "Existing app plus selected Decantr/local law",
|
|
4511
|
-
styleAuthority: input.adoptionMode === "decantr-css" ? "Decantr CSS runtime is active where adopted" :
|
|
4982
|
+
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",
|
|
4512
4983
|
activeAuthorities: [
|
|
4513
4984
|
"existing source",
|
|
4514
4985
|
"Essence V4 contract",
|
|
4986
|
+
hasStyleBridge ? "accepted style bridge" : "no style bridge",
|
|
4515
4987
|
hasLocalLaw ? "accepted local law" : "reviewed Hybrid choices",
|
|
4516
4988
|
input.packManifestPresent ? "hosted execution packs" : "optional hosted packs"
|
|
4517
4989
|
],
|
|
@@ -4529,13 +5001,18 @@ function deriveAdoptionLane(input) {
|
|
|
4529
5001
|
nextChoice: "Keep Decantr CSS usage explicit and validate route changes with task and verify."
|
|
4530
5002
|
};
|
|
4531
5003
|
}
|
|
4532
|
-
if (
|
|
5004
|
+
if (hasStyleBridge) {
|
|
4533
5005
|
return {
|
|
4534
5006
|
id: "hybrid-style-bridge",
|
|
4535
5007
|
label: "Hybrid style bridge",
|
|
4536
5008
|
sourceAuthority: "Existing app plus Decantr intent mapped through a style bridge",
|
|
4537
5009
|
styleAuthority: "Style bridge over the existing app styling system",
|
|
4538
|
-
activeAuthorities: [
|
|
5010
|
+
activeAuthorities: [
|
|
5011
|
+
"existing source",
|
|
5012
|
+
"Essence V4 contract",
|
|
5013
|
+
"accepted style bridge",
|
|
5014
|
+
hasLocalLaw ? "accepted local patterns/rules" : "optional local law"
|
|
5015
|
+
],
|
|
4539
5016
|
nextChoice: "Use local law to decide which component families the bridge governs before making it strict."
|
|
4540
5017
|
};
|
|
4541
5018
|
}
|
|
@@ -4593,18 +5070,18 @@ function buildDoctorReport(root, args) {
|
|
|
4593
5070
|
const workspaceInfo = resolveWorkspaceInfo(root, projectArg);
|
|
4594
5071
|
const workspaceRoot = workspaceInfo.workspaceRoot;
|
|
4595
5072
|
const appRoot = workspaceMode ? workspaceRoot : workspaceInfo.appRoot;
|
|
4596
|
-
const projectMissing = Boolean(projectArg && !
|
|
5073
|
+
const projectMissing = Boolean(projectArg && !existsSync17(appRoot));
|
|
4597
5074
|
const projectPath = appRoot === workspaceRoot ? null : rel(workspaceRoot, appRoot);
|
|
4598
5075
|
const packageManager = detectPackageManager3(workspaceRoot);
|
|
4599
5076
|
const cliDependency = localCliDependency(workspaceRoot);
|
|
4600
5077
|
const detected = detectProject(appRoot);
|
|
4601
|
-
const projectJson = readJson2(
|
|
4602
|
-
const essence = readJson2(
|
|
5078
|
+
const projectJson = readJson2(join19(appRoot, ".decantr", "project.json"));
|
|
5079
|
+
const essence = readJson2(join19(appRoot, "decantr.essence.json"));
|
|
4603
5080
|
const essenceVersion = essence && typeof essence === "object" && "version" in essence ? String(essence.version) : null;
|
|
4604
|
-
const contextDir =
|
|
4605
|
-
const packManifestPresent =
|
|
4606
|
-
const reviewPackPresent =
|
|
4607
|
-
const artifactReadmePresent =
|
|
5081
|
+
const contextDir = join19(appRoot, ".decantr", "context");
|
|
5082
|
+
const packManifestPresent = existsSync17(join19(contextDir, "pack-manifest.json"));
|
|
5083
|
+
const reviewPackPresent = existsSync17(join19(contextDir, "review-pack.json"));
|
|
5084
|
+
const artifactReadmePresent = existsSync17(join19(appRoot, ARTIFACT_README_PATH));
|
|
4608
5085
|
const projects = listWorkspaceProjects(workspaceRoot);
|
|
4609
5086
|
const candidates = listWorkspaceCandidates(workspaceRoot, projects).map((candidate) => ({
|
|
4610
5087
|
path: candidate.path,
|
|
@@ -4614,21 +5091,23 @@ function buildDoctorReport(root, args) {
|
|
|
4614
5091
|
const ciFiles = findCiFiles(workspaceRoot);
|
|
4615
5092
|
const workflowMode = projectJson?.initialized?.workflowMode ?? null;
|
|
4616
5093
|
const adoptionMode = projectJson?.initialized?.adoptionMode ?? null;
|
|
4617
|
-
const packHydrationOptional = adoptionMode === "contract-only";
|
|
4618
|
-
const localPatternsPresent =
|
|
4619
|
-
const localRulesPresent =
|
|
5094
|
+
const packHydrationOptional = adoptionMode === "contract-only" || adoptionMode === "style-bridge";
|
|
5095
|
+
const localPatternsPresent = existsSync17(localPatternsPath(appRoot));
|
|
5096
|
+
const localRulesPresent = existsSync17(localRulesPath(appRoot));
|
|
5097
|
+
const styleBridgePresent = existsSync17(styleBridgePath(appRoot));
|
|
5098
|
+
const styleBridgeProposalPresent = existsSync17(styleBridgeProposalPath(appRoot));
|
|
4620
5099
|
const missingPackReferences = workspaceMode ? projects.flatMap(
|
|
4621
|
-
(project) => collectMissingPackManifestFiles(
|
|
5100
|
+
(project) => collectMissingPackManifestFiles(join19(workspaceRoot, project.path)).map(
|
|
4622
5101
|
(missing) => `${project.path}/${missing.relativePath}`
|
|
4623
5102
|
)
|
|
4624
5103
|
) : collectMissingPackManifestFiles(appRoot).map((missing) => missing.relativePath);
|
|
4625
5104
|
const workspaceProjectsMissingManifest = workspaceMode ? projects.map((project) => project.path).filter((projectPath2) => {
|
|
4626
|
-
const projectContextDir =
|
|
4627
|
-
return !isContractOnlyProject(
|
|
5105
|
+
const projectContextDir = join19(workspaceRoot, projectPath2, ".decantr", "context");
|
|
5106
|
+
return !isContractOnlyProject(join19(workspaceRoot, projectPath2)) && !existsSync17(join19(projectContextDir, "pack-manifest.json"));
|
|
4628
5107
|
}) : [];
|
|
4629
5108
|
const workspaceProjectsMissingReviewPack = workspaceMode ? projects.map((project) => project.path).filter((projectPath2) => {
|
|
4630
|
-
const projectContextDir =
|
|
4631
|
-
return !isContractOnlyProject(
|
|
5109
|
+
const projectContextDir = join19(workspaceRoot, projectPath2, ".decantr", "context");
|
|
5110
|
+
return !isContractOnlyProject(join19(workspaceRoot, projectPath2)) && !existsSync17(join19(projectContextDir, "review-pack.json"));
|
|
4632
5111
|
}) : [];
|
|
4633
5112
|
const issues = [];
|
|
4634
5113
|
if (projectMissing) {
|
|
@@ -4685,7 +5164,7 @@ function buildDoctorReport(root, args) {
|
|
|
4685
5164
|
nextCommand: "decantr refresh"
|
|
4686
5165
|
});
|
|
4687
5166
|
}
|
|
4688
|
-
if (essenceVersion === "4.0.0" && !packHydrationOptional && (!
|
|
5167
|
+
if (essenceVersion === "4.0.0" && !packHydrationOptional && (!existsSync17(contextDir) || !packManifestPresent)) {
|
|
4689
5168
|
issues.push({
|
|
4690
5169
|
category: "generated-artifact",
|
|
4691
5170
|
severity: "warn",
|
|
@@ -4717,6 +5196,14 @@ function buildDoctorReport(root, args) {
|
|
|
4717
5196
|
nextCommand: projectPath ? `decantr codify --from-audit --project ${projectPath}` : "decantr codify --from-audit"
|
|
4718
5197
|
});
|
|
4719
5198
|
}
|
|
5199
|
+
if (adoptionMode === "style-bridge" && !styleBridgePresent) {
|
|
5200
|
+
issues.push({
|
|
5201
|
+
category: "local-law",
|
|
5202
|
+
severity: "warn",
|
|
5203
|
+
message: "This app declares style-bridge adoption, but .decantr/style-bridge.json is missing.",
|
|
5204
|
+
nextCommand: styleBridgeProposalPresent ? projectPath ? `decantr codify --accept --project ${projectPath}` : "decantr codify --accept" : projectPath ? `decantr codify --style-bridge --project ${projectPath}` : "decantr codify --style-bridge"
|
|
5205
|
+
});
|
|
5206
|
+
}
|
|
4720
5207
|
if ((essenceVersion === "4.0.0" || workspaceMode && projects.length > 0) && ciFiles.length === 0) {
|
|
4721
5208
|
issues.push({
|
|
4722
5209
|
category: "ci",
|
|
@@ -4744,6 +5231,7 @@ function buildDoctorReport(root, args) {
|
|
|
4744
5231
|
adoptionMode,
|
|
4745
5232
|
localPatternsPresent,
|
|
4746
5233
|
localRulesPresent,
|
|
5234
|
+
styleBridgePresent,
|
|
4747
5235
|
designAuthority,
|
|
4748
5236
|
packManifestPresent
|
|
4749
5237
|
});
|
|
@@ -4765,6 +5253,12 @@ function buildDoctorReport(root, args) {
|
|
|
4765
5253
|
appendUnique(recommendedNextCommands, `decantr codify --from-audit${projectFlag}`);
|
|
4766
5254
|
appendUnique(recommendedNextCommands, `decantr codify --accept${projectFlag}`);
|
|
4767
5255
|
}
|
|
5256
|
+
if (adoptionMode === "style-bridge" && !styleBridgePresent) {
|
|
5257
|
+
appendUnique(
|
|
5258
|
+
recommendedNextCommands,
|
|
5259
|
+
styleBridgeProposalPresent ? `decantr codify --accept${projectFlag}` : `decantr codify --style-bridge${projectFlag}`
|
|
5260
|
+
);
|
|
5261
|
+
}
|
|
4768
5262
|
appendUnique(
|
|
4769
5263
|
recommendedNextCommands,
|
|
4770
5264
|
issues.find((issue) => issue.category === "generated-artifact")?.nextCommand
|
|
@@ -4804,18 +5298,20 @@ function buildDoctorReport(root, args) {
|
|
|
4804
5298
|
},
|
|
4805
5299
|
generatedArtifacts: {
|
|
4806
5300
|
contextDirPresent: workspaceMode ? projects.some(
|
|
4807
|
-
(project) =>
|
|
4808
|
-
) :
|
|
5301
|
+
(project) => existsSync17(join19(workspaceRoot, project.path, ".decantr", "context"))
|
|
5302
|
+
) : existsSync17(contextDir),
|
|
4809
5303
|
packManifestPresent: workspaceMode ? projects.length > 0 && workspaceProjectsMissingManifest.length === 0 : packManifestPresent,
|
|
4810
5304
|
reviewPackPresent: workspaceMode ? projects.length > 0 && workspaceProjectsMissingReviewPack.length === 0 : reviewPackPresent,
|
|
4811
5305
|
missingReferencedFiles: missingPackReferences.slice(0, 25)
|
|
4812
5306
|
},
|
|
4813
5307
|
localLaw: {
|
|
4814
5308
|
patternsPresent: localPatternsPresent,
|
|
4815
|
-
rulesPresent: localRulesPresent
|
|
5309
|
+
rulesPresent: localRulesPresent,
|
|
5310
|
+
styleBridgePresent,
|
|
5311
|
+
styleBridgeProposalPresent
|
|
4816
5312
|
},
|
|
4817
5313
|
visualEvidence: {
|
|
4818
|
-
manifestPresent:
|
|
5314
|
+
manifestPresent: existsSync17(join19(appRoot, ".decantr", "evidence", "visual-manifest.json"))
|
|
4819
5315
|
},
|
|
4820
5316
|
lane,
|
|
4821
5317
|
designAuthority,
|
|
@@ -4863,6 +5359,7 @@ function formatDoctorText(report) {
|
|
|
4863
5359
|
`${BOLD3}Local Law:${RESET4}`,
|
|
4864
5360
|
` Local patterns: ${report.localLaw.patternsPresent ? "present" : "missing"}`,
|
|
4865
5361
|
` Local rules: ${report.localLaw.rulesPresent ? "present" : "missing"}`,
|
|
5362
|
+
` Style bridge: ${report.localLaw.styleBridgePresent ? "present" : report.localLaw.styleBridgeProposalPresent ? "proposal pending" : "missing"}`,
|
|
4866
5363
|
"",
|
|
4867
5364
|
`${BOLD3}Workspace:${RESET4}`,
|
|
4868
5365
|
` Attached projects: ${report.workspace.attachedProjects.length}`,
|
|
@@ -4922,17 +5419,17 @@ async function cmdDoctor(args = ["doctor"], root = process.cwd()) {
|
|
|
4922
5419
|
}
|
|
4923
5420
|
|
|
4924
5421
|
// src/commands/export.ts
|
|
4925
|
-
import { existsSync as
|
|
4926
|
-
import { dirname as dirname5, isAbsolute, join as
|
|
5422
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync12, readFileSync as readFileSync15, writeFileSync as writeFileSync13 } from "fs";
|
|
5423
|
+
import { dirname as dirname5, isAbsolute, join as join20 } from "path";
|
|
4927
5424
|
var GREEN5 = "\x1B[32m";
|
|
4928
5425
|
var RED4 = "\x1B[31m";
|
|
4929
5426
|
var DIM5 = "\x1B[2m";
|
|
4930
5427
|
var RESET5 = "\x1B[0m";
|
|
4931
5428
|
function readAdoptionMode(projectRoot) {
|
|
4932
|
-
const projectJsonPath =
|
|
4933
|
-
if (!
|
|
5429
|
+
const projectJsonPath = join20(projectRoot, ".decantr", "project.json");
|
|
5430
|
+
if (!existsSync18(projectJsonPath)) return null;
|
|
4934
5431
|
try {
|
|
4935
|
-
const parsed = JSON.parse(
|
|
5432
|
+
const parsed = JSON.parse(readFileSync15(projectJsonPath, "utf-8"));
|
|
4936
5433
|
return typeof parsed.initialized?.adoptionMode === "string" ? parsed.initialized.adoptionMode : null;
|
|
4937
5434
|
} catch {
|
|
4938
5435
|
return null;
|
|
@@ -4940,7 +5437,7 @@ function readAdoptionMode(projectRoot) {
|
|
|
4940
5437
|
}
|
|
4941
5438
|
function resolveOutputPath(projectRoot, output, fallback) {
|
|
4942
5439
|
if (!output) return fallback;
|
|
4943
|
-
return isAbsolute(output) ? output :
|
|
5440
|
+
return isAbsolute(output) ? output : join20(projectRoot, output);
|
|
4944
5441
|
}
|
|
4945
5442
|
var SHADCN_MAP = {
|
|
4946
5443
|
"--d-bg": "--background",
|
|
@@ -5101,21 +5598,21 @@ function generateFigmaTokens(tokens) {
|
|
|
5101
5598
|
`;
|
|
5102
5599
|
}
|
|
5103
5600
|
async function cmdExport(target, projectRoot, options = {}) {
|
|
5104
|
-
const essencePath =
|
|
5105
|
-
const tokensPath =
|
|
5106
|
-
if (!
|
|
5601
|
+
const essencePath = join20(projectRoot, "decantr.essence.json");
|
|
5602
|
+
const tokensPath = join20(projectRoot, "src", "styles", "tokens.css");
|
|
5603
|
+
if (!existsSync18(essencePath)) {
|
|
5107
5604
|
console.error(`${RED4}No decantr.essence.json found. Run \`decantr init\` first.${RESET5}`);
|
|
5108
5605
|
process.exitCode = 1;
|
|
5109
5606
|
return;
|
|
5110
5607
|
}
|
|
5111
|
-
if (!
|
|
5608
|
+
if (!existsSync18(tokensPath)) {
|
|
5112
5609
|
const adoptionMode = readAdoptionMode(projectRoot);
|
|
5113
5610
|
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.";
|
|
5114
5611
|
console.error(`${RED4}${message}${RESET5}`);
|
|
5115
5612
|
process.exitCode = 1;
|
|
5116
5613
|
return;
|
|
5117
5614
|
}
|
|
5118
|
-
const tokensCSS =
|
|
5615
|
+
const tokensCSS = readFileSync15(tokensPath, "utf-8");
|
|
5119
5616
|
const tokens = parseTokensCSS(tokensCSS);
|
|
5120
5617
|
if (tokens.size === 0) {
|
|
5121
5618
|
console.error(`${RED4}No --d-* tokens found in tokens.css.${RESET5}`);
|
|
@@ -5127,12 +5624,12 @@ async function cmdExport(target, projectRoot, options = {}) {
|
|
|
5127
5624
|
const cssOut = resolveOutputPath(
|
|
5128
5625
|
projectRoot,
|
|
5129
5626
|
options.output,
|
|
5130
|
-
|
|
5627
|
+
join20(projectRoot, "src", "styles", "shadcn-theme.css")
|
|
5131
5628
|
);
|
|
5132
|
-
const jsonOut =
|
|
5629
|
+
const jsonOut = join20(projectRoot, "components.json");
|
|
5133
5630
|
ensureDir(cssOut);
|
|
5134
|
-
|
|
5135
|
-
|
|
5631
|
+
writeFileSync13(cssOut, generateShadcnCSS(tokens), "utf-8");
|
|
5632
|
+
writeFileSync13(jsonOut, generateShadcnComponentsJSON(), "utf-8");
|
|
5136
5633
|
console.log(`${GREEN5}Exported shadcn theme:${RESET5}`);
|
|
5137
5634
|
console.log(` ${DIM5}CSS:${RESET5} ${cssOut}`);
|
|
5138
5635
|
console.log(` ${DIM5}JSON:${RESET5} ${jsonOut}`);
|
|
@@ -5142,10 +5639,10 @@ async function cmdExport(target, projectRoot, options = {}) {
|
|
|
5142
5639
|
const out = resolveOutputPath(
|
|
5143
5640
|
projectRoot,
|
|
5144
5641
|
options.output,
|
|
5145
|
-
|
|
5642
|
+
join20(projectRoot, "tailwind.decantr.config.ts")
|
|
5146
5643
|
);
|
|
5147
5644
|
ensureDir(out);
|
|
5148
|
-
|
|
5645
|
+
writeFileSync13(out, generateTailwindConfig(tokens), "utf-8");
|
|
5149
5646
|
console.log(`${GREEN5}Exported Tailwind config:${RESET5}`);
|
|
5150
5647
|
console.log(` ${DIM5}File:${RESET5} ${out}`);
|
|
5151
5648
|
break;
|
|
@@ -5154,10 +5651,10 @@ async function cmdExport(target, projectRoot, options = {}) {
|
|
|
5154
5651
|
const out = resolveOutputPath(
|
|
5155
5652
|
projectRoot,
|
|
5156
5653
|
options.output,
|
|
5157
|
-
|
|
5654
|
+
join20(projectRoot, "decantr-tokens.css")
|
|
5158
5655
|
);
|
|
5159
5656
|
ensureDir(out);
|
|
5160
|
-
|
|
5657
|
+
writeFileSync13(out, generateCSSVars(tokens), "utf-8");
|
|
5161
5658
|
console.log(`${GREEN5}Exported CSS variables:${RESET5}`);
|
|
5162
5659
|
console.log(` ${DIM5}File:${RESET5} ${out}`);
|
|
5163
5660
|
break;
|
|
@@ -5166,10 +5663,10 @@ async function cmdExport(target, projectRoot, options = {}) {
|
|
|
5166
5663
|
const out = resolveOutputPath(
|
|
5167
5664
|
projectRoot,
|
|
5168
5665
|
options.output,
|
|
5169
|
-
|
|
5666
|
+
join20(projectRoot, ".decantr", "design", "figma-tokens.json")
|
|
5170
5667
|
);
|
|
5171
5668
|
ensureDir(out);
|
|
5172
|
-
|
|
5669
|
+
writeFileSync13(out, generateFigmaTokens(tokens), "utf-8");
|
|
5173
5670
|
console.log(`${GREEN5}Exported Figma/Tokens Studio tokens:${RESET5}`);
|
|
5174
5671
|
console.log(` ${DIM5}File:${RESET5} ${out}`);
|
|
5175
5672
|
break;
|
|
@@ -5178,15 +5675,15 @@ async function cmdExport(target, projectRoot, options = {}) {
|
|
|
5178
5675
|
}
|
|
5179
5676
|
function ensureDir(filePath) {
|
|
5180
5677
|
const dir = dirname5(filePath);
|
|
5181
|
-
if (!
|
|
5182
|
-
|
|
5678
|
+
if (!existsSync18(dir)) {
|
|
5679
|
+
mkdirSync12(dir, { recursive: true });
|
|
5183
5680
|
}
|
|
5184
5681
|
}
|
|
5185
5682
|
|
|
5186
5683
|
// src/commands/magic.ts
|
|
5187
|
-
import { existsSync as
|
|
5684
|
+
import { existsSync as existsSync19 } from "fs";
|
|
5188
5685
|
import * as fs from "fs/promises";
|
|
5189
|
-
import { join as
|
|
5686
|
+
import { join as join21 } from "path";
|
|
5190
5687
|
var BOLD4 = "\x1B[1m";
|
|
5191
5688
|
var DIM6 = "\x1B[2m";
|
|
5192
5689
|
var RESET6 = "\x1B[0m";
|
|
@@ -5410,8 +5907,8 @@ async function cmdMagic(prompt, projectRoot, options) {
|
|
|
5410
5907
|
console.log(` Archetype: ${intent.archetype}`);
|
|
5411
5908
|
}
|
|
5412
5909
|
console.log("");
|
|
5413
|
-
const essencePath =
|
|
5414
|
-
if (
|
|
5910
|
+
const essencePath = join21(projectRoot, "decantr.essence.json");
|
|
5911
|
+
if (existsSync19(essencePath)) {
|
|
5415
5912
|
const projectFlag = options.projectLabel ? ` --project ${options.projectLabel}` : "";
|
|
5416
5913
|
console.log(`${YELLOW4} Decantr is already attached to this project.${RESET6}`);
|
|
5417
5914
|
console.log(
|
|
@@ -5444,7 +5941,7 @@ async function cmdMagic(prompt, projectRoot, options) {
|
|
|
5444
5941
|
return;
|
|
5445
5942
|
}
|
|
5446
5943
|
const registryClient = new RegistryClient({
|
|
5447
|
-
cacheDir:
|
|
5944
|
+
cacheDir: join21(projectRoot, ".decantr", "cache"),
|
|
5448
5945
|
apiUrl: options.registry,
|
|
5449
5946
|
offline: options.offline
|
|
5450
5947
|
});
|
|
@@ -5715,14 +6212,14 @@ async function cmdMagic(prompt, projectRoot, options) {
|
|
|
5715
6212
|
if (result.gitignoreUpdated) {
|
|
5716
6213
|
console.log(` ${dim(".gitignore updated")}`);
|
|
5717
6214
|
}
|
|
5718
|
-
const contextDir =
|
|
6215
|
+
const contextDir = join21(projectRoot, ".decantr", "context");
|
|
5719
6216
|
let sectionCount = 0;
|
|
5720
6217
|
try {
|
|
5721
6218
|
const files = await fs.readdir(contextDir);
|
|
5722
6219
|
sectionCount = files.filter((f) => f.startsWith("section-")).length;
|
|
5723
6220
|
} catch {
|
|
5724
6221
|
}
|
|
5725
|
-
const treatmentsPath =
|
|
6222
|
+
const treatmentsPath = join21(projectRoot, "src", "styles", "treatments.css");
|
|
5726
6223
|
let hasLayers = false;
|
|
5727
6224
|
try {
|
|
5728
6225
|
const css = await fs.readFile(treatmentsPath, "utf-8");
|
|
@@ -5755,8 +6252,8 @@ ${GREEN6}${BOLD4}Quality summary:${RESET6}`);
|
|
|
5755
6252
|
}
|
|
5756
6253
|
|
|
5757
6254
|
// src/commands/migrate.ts
|
|
5758
|
-
import { copyFileSync, existsSync as
|
|
5759
|
-
import { join as
|
|
6255
|
+
import { copyFileSync, existsSync as existsSync20, readFileSync as readFileSync16, writeFileSync as writeFileSync14 } from "fs";
|
|
6256
|
+
import { join as join22 } from "path";
|
|
5760
6257
|
import {
|
|
5761
6258
|
isV4 as isV45,
|
|
5762
6259
|
migrateToV4,
|
|
@@ -5769,12 +6266,12 @@ var YELLOW5 = "\x1B[33m";
|
|
|
5769
6266
|
var RESET7 = "\x1B[0m";
|
|
5770
6267
|
var DIM7 = "\x1B[2m";
|
|
5771
6268
|
function migrateEssenceFile(essencePath) {
|
|
5772
|
-
if (!
|
|
6269
|
+
if (!existsSync20(essencePath)) {
|
|
5773
6270
|
return { success: false, error: `File not found: ${essencePath}` };
|
|
5774
6271
|
}
|
|
5775
6272
|
let raw;
|
|
5776
6273
|
try {
|
|
5777
|
-
raw =
|
|
6274
|
+
raw = readFileSync16(essencePath, "utf-8");
|
|
5778
6275
|
} catch (e) {
|
|
5779
6276
|
return { success: false, error: `Could not read ${essencePath}: ${e.message}` };
|
|
5780
6277
|
}
|
|
@@ -5815,7 +6312,7 @@ function migrateEssenceFile(essencePath) {
|
|
|
5815
6312
|
};
|
|
5816
6313
|
}
|
|
5817
6314
|
try {
|
|
5818
|
-
|
|
6315
|
+
writeFileSync14(essencePath, JSON.stringify(v4, null, 2) + "\n");
|
|
5819
6316
|
} catch (e) {
|
|
5820
6317
|
return {
|
|
5821
6318
|
success: false,
|
|
@@ -5836,8 +6333,8 @@ async function cmdMigrate(projectRoot = process.cwd(), args = []) {
|
|
|
5836
6333
|
process.exitCode = 1;
|
|
5837
6334
|
return;
|
|
5838
6335
|
}
|
|
5839
|
-
const essencePath =
|
|
5840
|
-
if (!
|
|
6336
|
+
const essencePath = join22(projectRoot, "decantr.essence.json");
|
|
6337
|
+
if (!existsSync20(essencePath)) {
|
|
5841
6338
|
console.error(`${RED5}No decantr.essence.json found. Run \`decantr init\` first.${RESET7}`);
|
|
5842
6339
|
process.exitCode = 1;
|
|
5843
6340
|
return;
|
|
@@ -5859,7 +6356,7 @@ async function cmdMigrate(projectRoot = process.cwd(), args = []) {
|
|
|
5859
6356
|
}
|
|
5860
6357
|
if (result.essence) {
|
|
5861
6358
|
const registryClient = new RegistryClient({
|
|
5862
|
-
cacheDir:
|
|
6359
|
+
cacheDir: join22(projectRoot, ".decantr", "cache")
|
|
5863
6360
|
});
|
|
5864
6361
|
await refreshDerivedFiles(projectRoot, result.essence, registryClient);
|
|
5865
6362
|
console.log(`${GREEN7}Derived context and execution packs refreshed.${RESET7}`);
|
|
@@ -5870,50 +6367,50 @@ async function cmdMigrate(projectRoot = process.cwd(), args = []) {
|
|
|
5870
6367
|
|
|
5871
6368
|
// src/commands/new-project.ts
|
|
5872
6369
|
import { spawnSync } from "child_process";
|
|
5873
|
-
import { existsSync as
|
|
5874
|
-
import { join as
|
|
6370
|
+
import { existsSync as existsSync22, mkdirSync as mkdirSync14 } from "fs";
|
|
6371
|
+
import { join as join24, resolve as resolve3 } from "path";
|
|
5875
6372
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
5876
6373
|
|
|
5877
6374
|
// src/offline-content.ts
|
|
5878
|
-
import { cpSync, existsSync as
|
|
5879
|
-
import { join as
|
|
6375
|
+
import { cpSync, existsSync as existsSync21, mkdirSync as mkdirSync13 } from "fs";
|
|
6376
|
+
import { join as join23, resolve as resolve2 } from "path";
|
|
5880
6377
|
var CONTENT_TYPES2 = ["archetypes", "blueprints", "patterns", "themes", "shells"];
|
|
5881
6378
|
function copyIfExists(source, target) {
|
|
5882
|
-
if (!
|
|
6379
|
+
if (!existsSync21(source)) return false;
|
|
5883
6380
|
if (resolve2(source) === resolve2(target)) return true;
|
|
5884
6381
|
cpSync(source, target, { recursive: true });
|
|
5885
6382
|
return true;
|
|
5886
6383
|
}
|
|
5887
6384
|
function hydrateContentRoot(projectDir, contentRoot) {
|
|
5888
|
-
if (!
|
|
5889
|
-
const customRoot =
|
|
5890
|
-
const cacheRoot =
|
|
5891
|
-
|
|
5892
|
-
|
|
6385
|
+
if (!existsSync21(contentRoot)) return false;
|
|
6386
|
+
const customRoot = join23(projectDir, ".decantr", "custom");
|
|
6387
|
+
const cacheRoot = join23(projectDir, ".decantr", "cache", "@official");
|
|
6388
|
+
mkdirSync13(customRoot, { recursive: true });
|
|
6389
|
+
mkdirSync13(cacheRoot, { recursive: true });
|
|
5893
6390
|
let copiedAny = false;
|
|
5894
6391
|
for (const type of CONTENT_TYPES2) {
|
|
5895
|
-
const sourceDir =
|
|
5896
|
-
if (!
|
|
5897
|
-
cpSync(sourceDir,
|
|
5898
|
-
cpSync(sourceDir,
|
|
6392
|
+
const sourceDir = join23(contentRoot, type);
|
|
6393
|
+
if (!existsSync21(sourceDir)) continue;
|
|
6394
|
+
cpSync(sourceDir, join23(customRoot, type), { recursive: true });
|
|
6395
|
+
cpSync(sourceDir, join23(cacheRoot, type), { recursive: true });
|
|
5899
6396
|
copiedAny = true;
|
|
5900
6397
|
}
|
|
5901
6398
|
return copiedAny;
|
|
5902
6399
|
}
|
|
5903
6400
|
function seedOfflineRegistry(projectDir, workspaceRoot) {
|
|
5904
|
-
const projectDecantrRoot =
|
|
5905
|
-
|
|
6401
|
+
const projectDecantrRoot = join23(projectDir, ".decantr");
|
|
6402
|
+
mkdirSync13(projectDecantrRoot, { recursive: true });
|
|
5906
6403
|
const configuredContentRoot = process.env.DECANTR_CONTENT_DIR ? resolve2(process.env.DECANTR_CONTENT_DIR) : null;
|
|
5907
6404
|
if (configuredContentRoot && hydrateContentRoot(projectDir, configuredContentRoot)) {
|
|
5908
6405
|
return { seeded: true, strategy: "configured-content-root" };
|
|
5909
6406
|
}
|
|
5910
6407
|
const copiedCache = copyIfExists(
|
|
5911
|
-
|
|
5912
|
-
|
|
6408
|
+
join23(workspaceRoot, ".decantr", "cache"),
|
|
6409
|
+
join23(projectDecantrRoot, "cache")
|
|
5913
6410
|
);
|
|
5914
6411
|
const copiedCustom = copyIfExists(
|
|
5915
|
-
|
|
5916
|
-
|
|
6412
|
+
join23(workspaceRoot, ".decantr", "custom"),
|
|
6413
|
+
join23(projectDecantrRoot, "custom")
|
|
5917
6414
|
);
|
|
5918
6415
|
if (copiedCache || copiedCustom) {
|
|
5919
6416
|
return { seeded: true, strategy: "workspace-cache" };
|
|
@@ -6026,7 +6523,7 @@ function runArgvCommand(command, args, cwd) {
|
|
|
6026
6523
|
}
|
|
6027
6524
|
function resolveInitCommand(initFlags) {
|
|
6028
6525
|
const bundledCliEntrypoint = fileURLToPath2(new URL("./bin.js", import.meta.url));
|
|
6029
|
-
const cliEntrypoint =
|
|
6526
|
+
const cliEntrypoint = existsSync22(bundledCliEntrypoint) ? bundledCliEntrypoint : process.argv[1] && existsSync22(process.argv[1]) ? process.argv[1] : null;
|
|
6030
6527
|
if (cliEntrypoint) {
|
|
6031
6528
|
return {
|
|
6032
6529
|
command: process.execPath,
|
|
@@ -6054,7 +6551,7 @@ async function cmdNewProject(projectName, options) {
|
|
|
6054
6551
|
process.exitCode = 1;
|
|
6055
6552
|
return;
|
|
6056
6553
|
}
|
|
6057
|
-
if (
|
|
6554
|
+
if (existsSync22(projectDir)) {
|
|
6058
6555
|
console.error(error(`Directory "${projectName}" already exists.`));
|
|
6059
6556
|
process.exitCode = 1;
|
|
6060
6557
|
return;
|
|
@@ -6068,7 +6565,7 @@ async function cmdNewProject(projectName, options) {
|
|
|
6068
6565
|
return;
|
|
6069
6566
|
}
|
|
6070
6567
|
console.log(heading(`Creating ${projectName}...`));
|
|
6071
|
-
|
|
6568
|
+
mkdirSync14(projectDir, { recursive: true });
|
|
6072
6569
|
console.log(dim2(` Created ${projectName}/`));
|
|
6073
6570
|
const title = projectName.replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
6074
6571
|
if (shouldBootstrapRuntime && bootstrapAdapter) {
|
|
@@ -6167,21 +6664,21 @@ ${YELLOW6}Decantr init encountered issues. Run \`decantr init\` manually inside
|
|
|
6167
6664
|
}
|
|
6168
6665
|
}
|
|
6169
6666
|
function detectPackageManager4() {
|
|
6170
|
-
if (
|
|
6667
|
+
if (existsSync22(join24(process.cwd(), "pnpm-lock.yaml")) || existsSync22(join24(process.cwd(), "pnpm-workspace.yaml"))) {
|
|
6171
6668
|
return "pnpm";
|
|
6172
6669
|
}
|
|
6173
|
-
if (
|
|
6670
|
+
if (existsSync22(join24(process.cwd(), "yarn.lock"))) {
|
|
6174
6671
|
return "yarn";
|
|
6175
6672
|
}
|
|
6176
|
-
if (
|
|
6673
|
+
if (existsSync22(join24(process.cwd(), "bun.lockb")) || existsSync22(join24(process.cwd(), "bun.lock"))) {
|
|
6177
6674
|
return "bun";
|
|
6178
6675
|
}
|
|
6179
6676
|
return "npm";
|
|
6180
6677
|
}
|
|
6181
6678
|
|
|
6182
6679
|
// src/commands/publish.ts
|
|
6183
|
-
import { existsSync as
|
|
6184
|
-
import { join as
|
|
6680
|
+
import { existsSync as existsSync23, readFileSync as readFileSync17 } from "fs";
|
|
6681
|
+
import { join as join25 } from "path";
|
|
6185
6682
|
import {
|
|
6186
6683
|
API_CONTENT_TYPE_TO_CONTENT_TYPE,
|
|
6187
6684
|
CONTENT_TYPE_TO_API_CONTENT_TYPE as CONTENT_TYPE_TO_API_CONTENT_TYPE2,
|
|
@@ -6197,8 +6694,8 @@ async function cmdPublish(type, name, projectRoot = process.cwd()) {
|
|
|
6197
6694
|
}
|
|
6198
6695
|
const singularType = API_CONTENT_TYPE_TO_CONTENT_TYPE[type] || type;
|
|
6199
6696
|
const pluralType = CONTENT_TYPE_TO_API_CONTENT_TYPE2[type] || CONTENT_TYPE_TO_API_CONTENT_TYPE2[singularType] || `${type}s`;
|
|
6200
|
-
const customPath =
|
|
6201
|
-
if (!
|
|
6697
|
+
const customPath = join25(projectRoot, ".decantr", "custom", pluralType, `${name}.json`);
|
|
6698
|
+
if (!existsSync23(customPath)) {
|
|
6202
6699
|
console.error(`Custom ${singularType} "${name}" not found at ${customPath}`);
|
|
6203
6700
|
console.error(`Create one first: decantr create ${singularType} ${name}`);
|
|
6204
6701
|
process.exitCode = 1;
|
|
@@ -6206,7 +6703,7 @@ async function cmdPublish(type, name, projectRoot = process.cwd()) {
|
|
|
6206
6703
|
}
|
|
6207
6704
|
let data;
|
|
6208
6705
|
try {
|
|
6209
|
-
data = JSON.parse(
|
|
6706
|
+
data = JSON.parse(readFileSync17(customPath, "utf-8"));
|
|
6210
6707
|
} catch {
|
|
6211
6708
|
console.error(`Failed to parse ${customPath}`);
|
|
6212
6709
|
process.exitCode = 1;
|
|
@@ -6245,8 +6742,8 @@ async function cmdPublish(type, name, projectRoot = process.cwd()) {
|
|
|
6245
6742
|
|
|
6246
6743
|
// src/commands/refresh.ts
|
|
6247
6744
|
import { createHash } from "crypto";
|
|
6248
|
-
import { existsSync as
|
|
6249
|
-
import { isAbsolute as isAbsolute2, join as
|
|
6745
|
+
import { existsSync as existsSync24, readdirSync as readdirSync7, readFileSync as readFileSync18, statSync as statSync6 } from "fs";
|
|
6746
|
+
import { isAbsolute as isAbsolute2, join as join26, relative as relative6 } from "path";
|
|
6250
6747
|
import { collectMissingPackManifestFiles as collectMissingPackManifestFiles2 } from "@decantr/verifier";
|
|
6251
6748
|
import { isV4 as isV46 } from "@decantr/essence-spec";
|
|
6252
6749
|
var GREEN9 = "\x1B[32m";
|
|
@@ -6254,13 +6751,13 @@ var RED7 = "\x1B[31m";
|
|
|
6254
6751
|
var DIM9 = "\x1B[2m";
|
|
6255
6752
|
var RESET9 = "\x1B[0m";
|
|
6256
6753
|
function hashFile(path) {
|
|
6257
|
-
return createHash("sha256").update(
|
|
6754
|
+
return createHash("sha256").update(readFileSync18(path)).digest("hex");
|
|
6258
6755
|
}
|
|
6259
6756
|
function walkFiles(dir) {
|
|
6260
|
-
if (!
|
|
6757
|
+
if (!existsSync24(dir)) return [];
|
|
6261
6758
|
const files = [];
|
|
6262
6759
|
for (const entry of readdirSync7(dir, { withFileTypes: true })) {
|
|
6263
|
-
const path =
|
|
6760
|
+
const path = join26(dir, entry.name);
|
|
6264
6761
|
if (entry.isDirectory()) {
|
|
6265
6762
|
files.push(...walkFiles(path));
|
|
6266
6763
|
} else if (entry.isFile()) {
|
|
@@ -6271,10 +6768,10 @@ function walkFiles(dir) {
|
|
|
6271
6768
|
}
|
|
6272
6769
|
function trackedGeneratedFiles(projectRoot) {
|
|
6273
6770
|
return [
|
|
6274
|
-
|
|
6275
|
-
...walkFiles(
|
|
6276
|
-
...["global.css", "tokens.css", "treatments.css", "decantr-bridge.css"].map((file) =>
|
|
6277
|
-
].filter((path) =>
|
|
6771
|
+
join26(projectRoot, "DECANTR.md"),
|
|
6772
|
+
...walkFiles(join26(projectRoot, ".decantr", "context")),
|
|
6773
|
+
...["global.css", "tokens.css", "treatments.css", "decantr-bridge.css"].map((file) => join26(projectRoot, "src", "styles", file)).filter((path) => existsSync24(path))
|
|
6774
|
+
].filter((path) => existsSync24(path));
|
|
6278
6775
|
}
|
|
6279
6776
|
function snapshotGeneratedFiles(projectRoot) {
|
|
6280
6777
|
const map = /* @__PURE__ */ new Map();
|
|
@@ -6297,33 +6794,33 @@ function fileMtimeMs(path) {
|
|
|
6297
6794
|
}
|
|
6298
6795
|
function newestInputMtime(projectRoot) {
|
|
6299
6796
|
return Math.max(
|
|
6300
|
-
fileMtimeMs(
|
|
6301
|
-
fileMtimeMs(
|
|
6797
|
+
fileMtimeMs(join26(projectRoot, "decantr.essence.json")),
|
|
6798
|
+
fileMtimeMs(join26(projectRoot, ".decantr", "project.json"))
|
|
6302
6799
|
);
|
|
6303
6800
|
}
|
|
6304
6801
|
function isContractOnlyProject2(projectRoot) {
|
|
6305
|
-
const projectJsonPath =
|
|
6306
|
-
if (!
|
|
6802
|
+
const projectJsonPath = join26(projectRoot, ".decantr", "project.json");
|
|
6803
|
+
if (!existsSync24(projectJsonPath)) return false;
|
|
6307
6804
|
try {
|
|
6308
|
-
const projectJson = JSON.parse(
|
|
6805
|
+
const projectJson = JSON.parse(readFileSync18(projectJsonPath, "utf-8"));
|
|
6309
6806
|
return projectJson.initialized?.adoptionMode === "contract-only";
|
|
6310
6807
|
} catch {
|
|
6311
6808
|
return false;
|
|
6312
6809
|
}
|
|
6313
6810
|
}
|
|
6314
6811
|
function checkRefreshFreshness(projectRoot) {
|
|
6315
|
-
const contextDir =
|
|
6812
|
+
const contextDir = join26(projectRoot, ".decantr", "context");
|
|
6316
6813
|
const generated = trackedGeneratedFiles(projectRoot);
|
|
6317
6814
|
const reasons = [];
|
|
6318
6815
|
const packHydrationOptional = isContractOnlyProject2(projectRoot);
|
|
6319
|
-
if (!
|
|
6320
|
-
if (!
|
|
6321
|
-
if (!
|
|
6816
|
+
if (!existsSync24(join26(projectRoot, "DECANTR.md"))) reasons.push("DECANTR.md is missing.");
|
|
6817
|
+
if (!existsSync24(contextDir)) reasons.push(".decantr/context is missing.");
|
|
6818
|
+
if (!existsSync24(join26(contextDir, "scaffold.md"))) {
|
|
6322
6819
|
reasons.push(".decantr/context/scaffold.md is missing.");
|
|
6323
6820
|
}
|
|
6324
|
-
if (!packHydrationOptional && !
|
|
6821
|
+
if (!packHydrationOptional && !existsSync24(join26(contextDir, "pack-manifest.json"))) {
|
|
6325
6822
|
reasons.push(".decantr/context/pack-manifest.json is missing.");
|
|
6326
|
-
} else if (
|
|
6823
|
+
} else if (existsSync24(join26(contextDir, "pack-manifest.json"))) {
|
|
6327
6824
|
const missingPackFiles = collectMissingPackManifestFiles2(projectRoot);
|
|
6328
6825
|
if (missingPackFiles.length > 0) {
|
|
6329
6826
|
reasons.push(
|
|
@@ -6376,7 +6873,7 @@ function summarizeChanges(projectRoot, before, after) {
|
|
|
6376
6873
|
}
|
|
6377
6874
|
function displayGeneratedPath(summary, file, displayRoot) {
|
|
6378
6875
|
if (!displayRoot) return file;
|
|
6379
|
-
const absolutePath =
|
|
6876
|
+
const absolutePath = join26(summary.projectRoot, file);
|
|
6380
6877
|
const relativePath = relative6(displayRoot, absolutePath).replace(/\\/g, "/");
|
|
6381
6878
|
if (relativePath && !relativePath.startsWith("..") && !isAbsolute2(relativePath)) {
|
|
6382
6879
|
return relativePath;
|
|
@@ -6411,15 +6908,15 @@ function printRefreshSummary(summary, displayRoot) {
|
|
|
6411
6908
|
}
|
|
6412
6909
|
}
|
|
6413
6910
|
async function cmdRefresh(projectRoot = process.cwd(), options = {}) {
|
|
6414
|
-
const essencePath =
|
|
6415
|
-
if (!
|
|
6911
|
+
const essencePath = join26(projectRoot, "decantr.essence.json");
|
|
6912
|
+
if (!existsSync24(essencePath)) {
|
|
6416
6913
|
console.error(`${RED7}No decantr.essence.json found. Run \`decantr init\` first.${RESET9}`);
|
|
6417
6914
|
process.exitCode = 1;
|
|
6418
6915
|
return;
|
|
6419
6916
|
}
|
|
6420
6917
|
let essence;
|
|
6421
6918
|
try {
|
|
6422
|
-
const raw =
|
|
6919
|
+
const raw = readFileSync18(essencePath, "utf-8");
|
|
6423
6920
|
const parsed = JSON.parse(raw);
|
|
6424
6921
|
if (!isV46(parsed)) {
|
|
6425
6922
|
console.error(
|
|
@@ -6445,7 +6942,7 @@ async function cmdRefresh(projectRoot = process.cwd(), options = {}) {
|
|
|
6445
6942
|
return;
|
|
6446
6943
|
}
|
|
6447
6944
|
const registryClient = new RegistryClient({
|
|
6448
|
-
cacheDir:
|
|
6945
|
+
cacheDir: join26(projectRoot, ".decantr", "cache"),
|
|
6449
6946
|
offline: options.offline
|
|
6450
6947
|
});
|
|
6451
6948
|
console.log("Regenerating derived files...\n");
|
|
@@ -6476,8 +6973,8 @@ async function cmdRefresh(projectRoot = process.cwd(), options = {}) {
|
|
|
6476
6973
|
}
|
|
6477
6974
|
|
|
6478
6975
|
// src/commands/registry-mirror.ts
|
|
6479
|
-
import { mkdirSync as
|
|
6480
|
-
import { join as
|
|
6976
|
+
import { mkdirSync as mkdirSync15, writeFileSync as writeFileSync15 } from "fs";
|
|
6977
|
+
import { join as join27 } from "path";
|
|
6481
6978
|
import { API_CONTENT_TYPES, RegistryAPIClient as RegistryAPIClient2 } from "@decantr/registry";
|
|
6482
6979
|
var GREEN10 = "\x1B[32m";
|
|
6483
6980
|
var RED8 = "\x1B[31m";
|
|
@@ -6505,7 +7002,7 @@ async function cmdRegistryMirror(projectRoot, options = {}) {
|
|
|
6505
7002
|
process.exitCode = 1;
|
|
6506
7003
|
return;
|
|
6507
7004
|
}
|
|
6508
|
-
const cacheDir =
|
|
7005
|
+
const cacheDir = join27(projectRoot, ".decantr", "cache");
|
|
6509
7006
|
const counts = {};
|
|
6510
7007
|
const failed = [];
|
|
6511
7008
|
console.log(`
|
|
@@ -6515,19 +7012,19 @@ Mirroring registry content to ${DIM10}.decantr/cache/${RESET10}
|
|
|
6515
7012
|
try {
|
|
6516
7013
|
const result = await apiClient.listContent(type, { namespace: "@official" });
|
|
6517
7014
|
const items = result.items;
|
|
6518
|
-
const typeDir =
|
|
6519
|
-
|
|
6520
|
-
|
|
7015
|
+
const typeDir = join27(cacheDir, "@official", type);
|
|
7016
|
+
mkdirSync15(typeDir, { recursive: true });
|
|
7017
|
+
writeFileSync15(join27(typeDir, "index.json"), JSON.stringify(result, null, 2));
|
|
6521
7018
|
let itemCount = 0;
|
|
6522
7019
|
for (const item of items) {
|
|
6523
7020
|
const slug = item.slug || item.id;
|
|
6524
7021
|
if (!slug) continue;
|
|
6525
7022
|
try {
|
|
6526
7023
|
const fullItem = await apiClient.getContent(type, "@official", slug);
|
|
6527
|
-
|
|
7024
|
+
writeFileSync15(join27(typeDir, `${slug}.json`), JSON.stringify(fullItem, null, 2));
|
|
6528
7025
|
itemCount++;
|
|
6529
7026
|
} catch {
|
|
6530
|
-
|
|
7027
|
+
writeFileSync15(join27(typeDir, `${slug}.json`), JSON.stringify(item, null, 2));
|
|
6531
7028
|
itemCount++;
|
|
6532
7029
|
}
|
|
6533
7030
|
}
|
|
@@ -6542,8 +7039,8 @@ Mirroring registry content to ${DIM10}.decantr/cache/${RESET10}
|
|
|
6542
7039
|
mirrored_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6543
7040
|
counts
|
|
6544
7041
|
};
|
|
6545
|
-
|
|
6546
|
-
|
|
7042
|
+
mkdirSync15(join27(cacheDir), { recursive: true });
|
|
7043
|
+
writeFileSync15(join27(cacheDir, "mirror-manifest.json"), JSON.stringify(manifest, null, 2));
|
|
6547
7044
|
const totalItems = Object.values(counts).reduce((a, b) => a + b, 0);
|
|
6548
7045
|
console.log("");
|
|
6549
7046
|
if (failed.length > 0) {
|
|
@@ -6560,8 +7057,8 @@ Mirroring registry content to ${DIM10}.decantr/cache/${RESET10}
|
|
|
6560
7057
|
}
|
|
6561
7058
|
|
|
6562
7059
|
// src/commands/remove.ts
|
|
6563
|
-
import { existsSync as
|
|
6564
|
-
import { join as
|
|
7060
|
+
import { existsSync as existsSync26, readFileSync as readFileSync19, rmSync as rmSync2, writeFileSync as writeFileSync16 } from "fs";
|
|
7061
|
+
import { join as join28 } from "path";
|
|
6565
7062
|
import { isV4 as isV47 } from "@decantr/essence-spec";
|
|
6566
7063
|
var GREEN11 = "\x1B[32m";
|
|
6567
7064
|
var RED9 = "\x1B[31m";
|
|
@@ -6569,15 +7066,15 @@ var YELLOW8 = "\x1B[33m";
|
|
|
6569
7066
|
var DIM11 = "\x1B[2m";
|
|
6570
7067
|
var RESET11 = "\x1B[0m";
|
|
6571
7068
|
function readV4Essence2(projectRoot) {
|
|
6572
|
-
const essencePath =
|
|
6573
|
-
if (!
|
|
7069
|
+
const essencePath = join28(projectRoot, "decantr.essence.json");
|
|
7070
|
+
if (!existsSync26(essencePath)) {
|
|
6574
7071
|
console.error(`${RED9}No decantr.essence.json found. Run \`decantr init\` first.${RESET11}`);
|
|
6575
7072
|
process.exitCode = 1;
|
|
6576
7073
|
return null;
|
|
6577
7074
|
}
|
|
6578
7075
|
let parsed;
|
|
6579
7076
|
try {
|
|
6580
|
-
parsed = JSON.parse(
|
|
7077
|
+
parsed = JSON.parse(readFileSync19(essencePath, "utf-8"));
|
|
6581
7078
|
} catch (e) {
|
|
6582
7079
|
console.error(`${RED9}Could not read essence: ${e.message}${RESET11}`);
|
|
6583
7080
|
process.exitCode = 1;
|
|
@@ -6593,7 +7090,7 @@ function readV4Essence2(projectRoot) {
|
|
|
6593
7090
|
return { essence: parsed, essencePath };
|
|
6594
7091
|
}
|
|
6595
7092
|
function writeEssence2(essencePath, essence) {
|
|
6596
|
-
|
|
7093
|
+
writeFileSync16(essencePath, JSON.stringify(essence, null, 2) + "\n");
|
|
6597
7094
|
}
|
|
6598
7095
|
function readFlagValue2(args, name) {
|
|
6599
7096
|
const prefix = `--${name}=`;
|
|
@@ -6665,14 +7162,14 @@ async function cmdRemoveSection(sectionId, args, projectRoot = process.cwd()) {
|
|
|
6665
7162
|
sections.splice(idx, 1);
|
|
6666
7163
|
recomputeGlobalFeatures(essence);
|
|
6667
7164
|
removeRoutes(essence, sectionId);
|
|
6668
|
-
const contextFile =
|
|
6669
|
-
if (
|
|
7165
|
+
const contextFile = join28(projectRoot, ".decantr", "context", `${sectionId}.md`);
|
|
7166
|
+
if (existsSync26(contextFile)) {
|
|
6670
7167
|
rmSync2(contextFile);
|
|
6671
7168
|
}
|
|
6672
7169
|
writeEssence2(essencePath, essence);
|
|
6673
7170
|
console.log(`${GREEN11}Removed section "${sectionId}".${RESET11}`);
|
|
6674
7171
|
const registryClient = new RegistryClient({
|
|
6675
|
-
cacheDir:
|
|
7172
|
+
cacheDir: join28(projectRoot, ".decantr", "cache")
|
|
6676
7173
|
});
|
|
6677
7174
|
await refreshDerivedFiles(projectRoot, essence, registryClient);
|
|
6678
7175
|
console.log(`${GREEN11}Derived files refreshed.${RESET11}`);
|
|
@@ -6715,7 +7212,7 @@ async function cmdRemovePage(path, args, projectRoot = process.cwd()) {
|
|
|
6715
7212
|
}
|
|
6716
7213
|
console.log(`${GREEN11}Removed page "${pageId}" from section "${resolvedSectionId}".${RESET11}`);
|
|
6717
7214
|
const registryClient = new RegistryClient({
|
|
6718
|
-
cacheDir:
|
|
7215
|
+
cacheDir: join28(projectRoot, ".decantr", "cache")
|
|
6719
7216
|
});
|
|
6720
7217
|
await refreshDerivedFiles(projectRoot, essence, registryClient);
|
|
6721
7218
|
console.log(`${GREEN11}Derived files refreshed.${RESET11}`);
|
|
@@ -6758,15 +7255,15 @@ async function cmdRemoveFeature(feature, args, projectRoot = process.cwd()) {
|
|
|
6758
7255
|
const target = sectionId ? `section "${sectionId}" and global` : "global";
|
|
6759
7256
|
console.log(`${GREEN11}Removed feature "${feature}" from ${target} features.${RESET11}`);
|
|
6760
7257
|
const registryClient = new RegistryClient({
|
|
6761
|
-
cacheDir:
|
|
7258
|
+
cacheDir: join28(projectRoot, ".decantr", "cache")
|
|
6762
7259
|
});
|
|
6763
7260
|
await refreshDerivedFiles(projectRoot, essence, registryClient);
|
|
6764
7261
|
console.log(`${GREEN11}Derived files refreshed.${RESET11}`);
|
|
6765
7262
|
}
|
|
6766
7263
|
|
|
6767
7264
|
// src/commands/sync-drift.ts
|
|
6768
|
-
import { existsSync as
|
|
6769
|
-
import { join as
|
|
7265
|
+
import { existsSync as existsSync27, readFileSync as readFileSync20, writeFileSync as writeFileSync17 } from "fs";
|
|
7266
|
+
import { join as join29 } from "path";
|
|
6770
7267
|
var GREEN12 = "\x1B[32m";
|
|
6771
7268
|
var RED10 = "\x1B[31m";
|
|
6772
7269
|
var YELLOW9 = "\x1B[33m";
|
|
@@ -6775,8 +7272,8 @@ var DIM12 = "\x1B[2m";
|
|
|
6775
7272
|
var BOLD6 = "\x1B[1m";
|
|
6776
7273
|
var CYAN6 = "\x1B[36m";
|
|
6777
7274
|
async function cmdSyncDrift(projectRoot = process.cwd()) {
|
|
6778
|
-
const driftLogPath =
|
|
6779
|
-
if (!
|
|
7275
|
+
const driftLogPath = join29(projectRoot, ".decantr", "drift-log.json");
|
|
7276
|
+
if (!existsSync27(driftLogPath)) {
|
|
6780
7277
|
console.log(`${GREEN12}No drift log found \u2014 no drift recorded.${RESET12}`);
|
|
6781
7278
|
console.log(
|
|
6782
7279
|
`${DIM12}Drift is logged when guard violations are detected during development.${RESET12}`
|
|
@@ -6785,7 +7282,7 @@ async function cmdSyncDrift(projectRoot = process.cwd()) {
|
|
|
6785
7282
|
}
|
|
6786
7283
|
let entries;
|
|
6787
7284
|
try {
|
|
6788
|
-
entries = JSON.parse(
|
|
7285
|
+
entries = JSON.parse(readFileSync20(driftLogPath, "utf-8"));
|
|
6789
7286
|
} catch {
|
|
6790
7287
|
console.error(`${RED10}Could not parse drift-log.json${RESET12}`);
|
|
6791
7288
|
process.exitCode = 1;
|
|
@@ -6831,13 +7328,13 @@ ${BOLD6}Unresolved Drift Entries (${unresolved.length})${RESET12}
|
|
|
6831
7328
|
console.log("");
|
|
6832
7329
|
}
|
|
6833
7330
|
function resolveDriftEntries(projectRoot, options) {
|
|
6834
|
-
const driftLogPath =
|
|
6835
|
-
if (!
|
|
7331
|
+
const driftLogPath = join29(projectRoot, ".decantr", "drift-log.json");
|
|
7332
|
+
if (!existsSync27(driftLogPath)) {
|
|
6836
7333
|
return { success: true };
|
|
6837
7334
|
}
|
|
6838
7335
|
if (options.clear) {
|
|
6839
7336
|
try {
|
|
6840
|
-
|
|
7337
|
+
writeFileSync17(driftLogPath, "[]");
|
|
6841
7338
|
return { success: true };
|
|
6842
7339
|
} catch (e) {
|
|
6843
7340
|
return { success: false, error: `Could not clear drift log: ${e.message}` };
|
|
@@ -6845,7 +7342,7 @@ function resolveDriftEntries(projectRoot, options) {
|
|
|
6845
7342
|
}
|
|
6846
7343
|
let entries;
|
|
6847
7344
|
try {
|
|
6848
|
-
entries = JSON.parse(
|
|
7345
|
+
entries = JSON.parse(readFileSync20(driftLogPath, "utf-8"));
|
|
6849
7346
|
} catch {
|
|
6850
7347
|
return { success: false, error: "Could not parse drift-log.json" };
|
|
6851
7348
|
}
|
|
@@ -6864,7 +7361,7 @@ function resolveDriftEntries(projectRoot, options) {
|
|
|
6864
7361
|
}
|
|
6865
7362
|
}
|
|
6866
7363
|
try {
|
|
6867
|
-
|
|
7364
|
+
writeFileSync17(driftLogPath, JSON.stringify(entries, null, 2));
|
|
6868
7365
|
return { success: true };
|
|
6869
7366
|
} catch (e) {
|
|
6870
7367
|
return { success: false, error: `Could not write drift log: ${e.message}` };
|
|
@@ -7089,8 +7586,8 @@ function trimTrailingSlashes(value) {
|
|
|
7089
7586
|
}
|
|
7090
7587
|
|
|
7091
7588
|
// src/commands/theme-switch.ts
|
|
7092
|
-
import { existsSync as
|
|
7093
|
-
import { join as
|
|
7589
|
+
import { existsSync as existsSync28, readFileSync as readFileSync21, writeFileSync as writeFileSync18 } from "fs";
|
|
7590
|
+
import { join as join30 } from "path";
|
|
7094
7591
|
import { isV4 as isV48 } from "@decantr/essence-spec";
|
|
7095
7592
|
var GREEN14 = "\x1B[32m";
|
|
7096
7593
|
var RED11 = "\x1B[31m";
|
|
@@ -7107,15 +7604,15 @@ async function cmdThemeSwitch(themeName, args, projectRoot = process.cwd()) {
|
|
|
7107
7604
|
process.exitCode = 1;
|
|
7108
7605
|
return;
|
|
7109
7606
|
}
|
|
7110
|
-
const essencePath =
|
|
7111
|
-
if (!
|
|
7607
|
+
const essencePath = join30(projectRoot, "decantr.essence.json");
|
|
7608
|
+
if (!existsSync28(essencePath)) {
|
|
7112
7609
|
console.error(`${RED11}No decantr.essence.json found. Run \`decantr init\` first.${RESET14}`);
|
|
7113
7610
|
process.exitCode = 1;
|
|
7114
7611
|
return;
|
|
7115
7612
|
}
|
|
7116
7613
|
let parsed;
|
|
7117
7614
|
try {
|
|
7118
|
-
parsed = JSON.parse(
|
|
7615
|
+
parsed = JSON.parse(readFileSync21(essencePath, "utf-8"));
|
|
7119
7616
|
} catch (e) {
|
|
7120
7617
|
console.error(`${RED11}Could not read essence: ${e.message}${RESET14}`);
|
|
7121
7618
|
process.exitCode = 1;
|
|
@@ -7166,7 +7663,7 @@ async function cmdThemeSwitch(themeName, args, projectRoot = process.cwd()) {
|
|
|
7166
7663
|
essence.dna.theme.mode = mode;
|
|
7167
7664
|
}
|
|
7168
7665
|
const registryClient = new RegistryClient({
|
|
7169
|
-
cacheDir:
|
|
7666
|
+
cacheDir: join30(projectRoot, ".decantr", "cache")
|
|
7170
7667
|
});
|
|
7171
7668
|
try {
|
|
7172
7669
|
const themeResult = await registryClient.fetchTheme(themeName);
|
|
@@ -7181,7 +7678,7 @@ async function cmdThemeSwitch(themeName, args, projectRoot = process.cwd()) {
|
|
|
7181
7678
|
}
|
|
7182
7679
|
} catch {
|
|
7183
7680
|
}
|
|
7184
|
-
|
|
7681
|
+
writeFileSync18(essencePath, JSON.stringify(essence, null, 2) + "\n");
|
|
7185
7682
|
console.log(`${GREEN14}Switched theme: ${oldThemeId} \u2192 ${themeName}${RESET14}`);
|
|
7186
7683
|
if (shape) console.log(` ${DIM14}Shape: ${shape}${RESET14}`);
|
|
7187
7684
|
if (mode) console.log(` ${DIM14}Mode: ${mode}${RESET14}`);
|
|
@@ -7489,8 +7986,8 @@ async function runSimplifiedInit(blueprints) {
|
|
|
7489
7986
|
}
|
|
7490
7987
|
|
|
7491
7988
|
// src/theme-commands.ts
|
|
7492
|
-
import { existsSync as
|
|
7493
|
-
import { join as
|
|
7989
|
+
import { existsSync as existsSync29, mkdirSync as mkdirSync16, readdirSync as readdirSync8, readFileSync as readFileSync22, rmSync as rmSync3, writeFileSync as writeFileSync19 } from "fs";
|
|
7990
|
+
import { join as join31 } from "path";
|
|
7494
7991
|
var REQUIRED_FIELDS = [
|
|
7495
7992
|
"$schema",
|
|
7496
7993
|
"id",
|
|
@@ -7550,20 +8047,20 @@ function validateCustomTheme(theme) {
|
|
|
7550
8047
|
};
|
|
7551
8048
|
}
|
|
7552
8049
|
function createTheme(projectRoot, id, name) {
|
|
7553
|
-
const customThemesDir =
|
|
7554
|
-
const themePath =
|
|
7555
|
-
const howToPath =
|
|
7556
|
-
|
|
7557
|
-
if (
|
|
8050
|
+
const customThemesDir = join31(projectRoot, ".decantr", "custom", "themes");
|
|
8051
|
+
const themePath = join31(customThemesDir, `${id}.json`);
|
|
8052
|
+
const howToPath = join31(customThemesDir, "how-to-theme.md");
|
|
8053
|
+
mkdirSync16(customThemesDir, { recursive: true });
|
|
8054
|
+
if (existsSync29(themePath)) {
|
|
7558
8055
|
return {
|
|
7559
8056
|
success: false,
|
|
7560
8057
|
error: `Theme "${id}" already exists at ${themePath}`
|
|
7561
8058
|
};
|
|
7562
8059
|
}
|
|
7563
8060
|
const skeleton = getThemeSkeleton(id, name);
|
|
7564
|
-
|
|
7565
|
-
if (!
|
|
7566
|
-
|
|
8061
|
+
writeFileSync19(themePath, JSON.stringify(skeleton, null, 2));
|
|
8062
|
+
if (!existsSync29(howToPath)) {
|
|
8063
|
+
writeFileSync19(howToPath, getHowToThemeDoc());
|
|
7567
8064
|
}
|
|
7568
8065
|
return {
|
|
7569
8066
|
success: true,
|
|
@@ -7571,17 +8068,17 @@ function createTheme(projectRoot, id, name) {
|
|
|
7571
8068
|
};
|
|
7572
8069
|
}
|
|
7573
8070
|
function listCustomThemes(projectRoot) {
|
|
7574
|
-
const customThemesDir =
|
|
7575
|
-
if (!
|
|
8071
|
+
const customThemesDir = join31(projectRoot, ".decantr", "custom", "themes");
|
|
8072
|
+
if (!existsSync29(customThemesDir)) {
|
|
7576
8073
|
return [];
|
|
7577
8074
|
}
|
|
7578
8075
|
const themes = [];
|
|
7579
8076
|
try {
|
|
7580
8077
|
const files = readdirSync8(customThemesDir).filter((f) => f.endsWith(".json"));
|
|
7581
8078
|
for (const file of files) {
|
|
7582
|
-
const filePath =
|
|
8079
|
+
const filePath = join31(customThemesDir, file);
|
|
7583
8080
|
try {
|
|
7584
|
-
const data = JSON.parse(
|
|
8081
|
+
const data = JSON.parse(readFileSync22(filePath, "utf-8"));
|
|
7585
8082
|
themes.push({
|
|
7586
8083
|
id: data.id || file.replace(".json", ""),
|
|
7587
8084
|
name: data.name || data.id,
|
|
@@ -7596,8 +8093,8 @@ function listCustomThemes(projectRoot) {
|
|
|
7596
8093
|
return themes;
|
|
7597
8094
|
}
|
|
7598
8095
|
function deleteTheme(projectRoot, id) {
|
|
7599
|
-
const themePath =
|
|
7600
|
-
if (!
|
|
8096
|
+
const themePath = join31(projectRoot, ".decantr", "custom", "themes", `${id}.json`);
|
|
8097
|
+
if (!existsSync29(themePath)) {
|
|
7601
8098
|
return {
|
|
7602
8099
|
success: false,
|
|
7603
8100
|
error: `Theme "${id}" not found at ${themePath}`
|
|
@@ -7614,7 +8111,7 @@ function deleteTheme(projectRoot, id) {
|
|
|
7614
8111
|
}
|
|
7615
8112
|
}
|
|
7616
8113
|
function importTheme(projectRoot, sourcePath) {
|
|
7617
|
-
if (!
|
|
8114
|
+
if (!existsSync29(sourcePath)) {
|
|
7618
8115
|
return {
|
|
7619
8116
|
success: false,
|
|
7620
8117
|
errors: [`Source file not found: ${sourcePath}`]
|
|
@@ -7622,7 +8119,7 @@ function importTheme(projectRoot, sourcePath) {
|
|
|
7622
8119
|
}
|
|
7623
8120
|
let theme;
|
|
7624
8121
|
try {
|
|
7625
|
-
theme = JSON.parse(
|
|
8122
|
+
theme = JSON.parse(readFileSync22(sourcePath, "utf-8"));
|
|
7626
8123
|
} catch (e) {
|
|
7627
8124
|
return {
|
|
7628
8125
|
success: false,
|
|
@@ -7638,14 +8135,14 @@ function importTheme(projectRoot, sourcePath) {
|
|
|
7638
8135
|
}
|
|
7639
8136
|
theme.source = "custom";
|
|
7640
8137
|
const id = theme.id;
|
|
7641
|
-
const customThemesDir =
|
|
7642
|
-
const destPath =
|
|
7643
|
-
|
|
7644
|
-
const howToPath =
|
|
7645
|
-
if (!
|
|
7646
|
-
|
|
7647
|
-
}
|
|
7648
|
-
|
|
8138
|
+
const customThemesDir = join31(projectRoot, ".decantr", "custom", "themes");
|
|
8139
|
+
const destPath = join31(customThemesDir, `${id}.json`);
|
|
8140
|
+
mkdirSync16(customThemesDir, { recursive: true });
|
|
8141
|
+
const howToPath = join31(customThemesDir, "how-to-theme.md");
|
|
8142
|
+
if (!existsSync29(howToPath)) {
|
|
8143
|
+
writeFileSync19(howToPath, getHowToThemeDoc());
|
|
8144
|
+
}
|
|
8145
|
+
writeFileSync19(destPath, JSON.stringify(theme, null, 2));
|
|
7649
8146
|
return {
|
|
7650
8147
|
success: true,
|
|
7651
8148
|
path: destPath
|
|
@@ -8192,18 +8689,18 @@ function extractHostedAssetPaths(indexHtml) {
|
|
|
8192
8689
|
return [...assetPaths];
|
|
8193
8690
|
}
|
|
8194
8691
|
function readHostedDistSnapshot(distPath) {
|
|
8195
|
-
const resolvedDistPath = distPath ? resolveUserPath(distPath) :
|
|
8196
|
-
const indexPath =
|
|
8197
|
-
if (!
|
|
8692
|
+
const resolvedDistPath = distPath ? resolveUserPath(distPath) : join32(process.cwd(), "dist");
|
|
8693
|
+
const indexPath = join32(resolvedDistPath, "index.html");
|
|
8694
|
+
if (!existsSync30(indexPath)) {
|
|
8198
8695
|
return void 0;
|
|
8199
8696
|
}
|
|
8200
|
-
const indexHtml =
|
|
8697
|
+
const indexHtml = readFileSync23(indexPath, "utf-8");
|
|
8201
8698
|
const assetPaths = extractHostedAssetPaths(indexHtml);
|
|
8202
8699
|
const assets = {};
|
|
8203
8700
|
for (const assetPath of assetPaths) {
|
|
8204
|
-
const assetFilePath =
|
|
8205
|
-
if (
|
|
8206
|
-
assets[assetPath] =
|
|
8701
|
+
const assetFilePath = join32(resolvedDistPath, assetPath.replace(/^[/\\]+/, ""));
|
|
8702
|
+
if (existsSync30(assetFilePath)) {
|
|
8703
|
+
assets[assetPath] = readFileSync23(assetFilePath, "utf-8");
|
|
8207
8704
|
}
|
|
8208
8705
|
}
|
|
8209
8706
|
return {
|
|
@@ -8218,7 +8715,7 @@ function isHostedSourceSnapshotFile(path) {
|
|
|
8218
8715
|
function readHostedSourceSnapshot(sourcePath) {
|
|
8219
8716
|
if (!sourcePath) return void 0;
|
|
8220
8717
|
const resolvedSourcePath = resolveUserPath(sourcePath);
|
|
8221
|
-
if (!
|
|
8718
|
+
if (!existsSync30(resolvedSourcePath)) {
|
|
8222
8719
|
return void 0;
|
|
8223
8720
|
}
|
|
8224
8721
|
const files = {};
|
|
@@ -8234,15 +8731,15 @@ function readHostedSourceSnapshot(sourcePath) {
|
|
|
8234
8731
|
const walk = (absoluteDir, relativeDir) => {
|
|
8235
8732
|
for (const entry of readdirSync9(absoluteDir, { withFileTypes: true })) {
|
|
8236
8733
|
if (ignoredDirNames.has(entry.name)) continue;
|
|
8237
|
-
const absolutePath =
|
|
8238
|
-
const relativePath =
|
|
8734
|
+
const absolutePath = join32(absoluteDir, entry.name);
|
|
8735
|
+
const relativePath = join32(relativeDir, entry.name).replace(/\\/g, "/");
|
|
8239
8736
|
if (entry.isDirectory()) {
|
|
8240
8737
|
walk(absolutePath, relativePath);
|
|
8241
8738
|
continue;
|
|
8242
8739
|
}
|
|
8243
8740
|
if (!entry.isFile()) continue;
|
|
8244
8741
|
if (!isHostedSourceSnapshotFile(relativePath)) continue;
|
|
8245
|
-
files[relativePath] =
|
|
8742
|
+
files[relativePath] = readFileSync23(absolutePath, "utf-8");
|
|
8246
8743
|
}
|
|
8247
8744
|
};
|
|
8248
8745
|
walk(resolvedSourcePath, rootPrefix);
|
|
@@ -8435,24 +8932,24 @@ async function printHostedExecutionPackBundle(essencePath, namespace, jsonOutput
|
|
|
8435
8932
|
}
|
|
8436
8933
|
async function compileHostedExecutionPackBundle(essencePath, namespace) {
|
|
8437
8934
|
const client = getPublicAPIClient();
|
|
8438
|
-
const resolvedPath = essencePath ? resolveUserPath(essencePath) :
|
|
8439
|
-
if (!
|
|
8935
|
+
const resolvedPath = essencePath ? resolveUserPath(essencePath) : join32(process.cwd(), "decantr.essence.json");
|
|
8936
|
+
if (!existsSync30(resolvedPath)) {
|
|
8440
8937
|
throw new Error(`Essence file not found at ${resolvedPath}`);
|
|
8441
8938
|
}
|
|
8442
|
-
const essence = JSON.parse(
|
|
8939
|
+
const essence = JSON.parse(readFileSync23(resolvedPath, "utf-8"));
|
|
8443
8940
|
const bundle = await client.compileExecutionPacks(essence, namespace ? { namespace } : void 0);
|
|
8444
|
-
const contextDir =
|
|
8941
|
+
const contextDir = join32(dirname6(resolvedPath), ".decantr", "context");
|
|
8445
8942
|
return { resolvedPath, bundle, contextDir };
|
|
8446
8943
|
}
|
|
8447
8944
|
function writeHostedExecutionPackContextArtifacts(contextDir, bundle) {
|
|
8448
|
-
|
|
8945
|
+
mkdirSync17(contextDir, { recursive: true });
|
|
8449
8946
|
return writeExecutionPackBundleArtifacts(contextDir, bundle);
|
|
8450
8947
|
}
|
|
8451
8948
|
function resolvePagePackIdForRoute(essencePath, route) {
|
|
8452
|
-
if (!
|
|
8949
|
+
if (!existsSync30(essencePath)) {
|
|
8453
8950
|
throw new Error(`Essence file not found at ${essencePath}`);
|
|
8454
8951
|
}
|
|
8455
|
-
const essence = JSON.parse(
|
|
8952
|
+
const essence = JSON.parse(readFileSync23(essencePath, "utf-8"));
|
|
8456
8953
|
if (!isV49(essence)) {
|
|
8457
8954
|
throw new Error("Route-based pack resolution requires Essence v4.0.0.");
|
|
8458
8955
|
}
|
|
@@ -8467,14 +8964,14 @@ function resolvePagePackIdForRoute(essencePath, route) {
|
|
|
8467
8964
|
}
|
|
8468
8965
|
async function printHostedSelectedExecutionPack(packType, id, essencePath, namespace, jsonOutput = false, writeContext = false) {
|
|
8469
8966
|
const client = getPublicAPIClient();
|
|
8470
|
-
const resolvedPath = essencePath ? resolveUserPath(essencePath) :
|
|
8471
|
-
if (!
|
|
8967
|
+
const resolvedPath = essencePath ? resolveUserPath(essencePath) : join32(process.cwd(), "decantr.essence.json");
|
|
8968
|
+
if (!existsSync30(resolvedPath)) {
|
|
8472
8969
|
throw new Error(`Essence file not found at ${resolvedPath}`);
|
|
8473
8970
|
}
|
|
8474
8971
|
if ((packType === "section" || packType === "page" || packType === "mutation") && !id) {
|
|
8475
8972
|
throw new Error(`Pack type "${packType}" requires an id.`);
|
|
8476
8973
|
}
|
|
8477
|
-
const essence = JSON.parse(
|
|
8974
|
+
const essence = JSON.parse(readFileSync23(resolvedPath, "utf-8"));
|
|
8478
8975
|
const selected = await client.selectExecutionPack(
|
|
8479
8976
|
{
|
|
8480
8977
|
essence,
|
|
@@ -8485,17 +8982,17 @@ async function printHostedSelectedExecutionPack(packType, id, essencePath, names
|
|
|
8485
8982
|
);
|
|
8486
8983
|
let writtenContextDir = null;
|
|
8487
8984
|
if (writeContext) {
|
|
8488
|
-
const contextDir =
|
|
8489
|
-
|
|
8490
|
-
|
|
8491
|
-
|
|
8985
|
+
const contextDir = join32(dirname6(resolvedPath), ".decantr", "context");
|
|
8986
|
+
mkdirSync17(contextDir, { recursive: true });
|
|
8987
|
+
writeFileSync20(
|
|
8988
|
+
join32(contextDir, "pack-manifest.json"),
|
|
8492
8989
|
JSON.stringify(selected.manifest, null, 2) + "\n"
|
|
8493
8990
|
);
|
|
8494
8991
|
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);
|
|
8495
8992
|
const markdownFile = manifestEntry?.markdown ?? `${selected.selector.packType}${selected.selector.id ? `-${selected.selector.id}` : ""}-pack.md`;
|
|
8496
8993
|
const jsonFile = manifestEntry?.json ?? `${selected.selector.packType}${selected.selector.id ? `-${selected.selector.id}` : ""}-pack.json`;
|
|
8497
|
-
|
|
8498
|
-
|
|
8994
|
+
writeFileSync20(join32(contextDir, markdownFile), selected.pack.renderedMarkdown);
|
|
8995
|
+
writeFileSync20(join32(contextDir, jsonFile), JSON.stringify(selected.pack, null, 2) + "\n");
|
|
8499
8996
|
writtenContextDir = contextDir;
|
|
8500
8997
|
}
|
|
8501
8998
|
if (jsonOutput) {
|
|
@@ -8520,20 +9017,20 @@ async function printHostedSelectedExecutionPack(packType, id, essencePath, names
|
|
|
8520
9017
|
}
|
|
8521
9018
|
async function printHostedExecutionPackManifest(essencePath, namespace, jsonOutput = false, writeContext = false) {
|
|
8522
9019
|
const client = getPublicAPIClient();
|
|
8523
|
-
const resolvedPath = essencePath ? resolveUserPath(essencePath) :
|
|
8524
|
-
if (!
|
|
9020
|
+
const resolvedPath = essencePath ? resolveUserPath(essencePath) : join32(process.cwd(), "decantr.essence.json");
|
|
9021
|
+
if (!existsSync30(resolvedPath)) {
|
|
8525
9022
|
throw new Error(`Essence file not found at ${resolvedPath}`);
|
|
8526
9023
|
}
|
|
8527
|
-
const essence = JSON.parse(
|
|
9024
|
+
const essence = JSON.parse(readFileSync23(resolvedPath, "utf-8"));
|
|
8528
9025
|
const manifest = await client.getExecutionPackManifest(
|
|
8529
9026
|
essence,
|
|
8530
9027
|
namespace ? { namespace } : void 0
|
|
8531
9028
|
);
|
|
8532
9029
|
let writtenContextDir = null;
|
|
8533
9030
|
if (writeContext) {
|
|
8534
|
-
const contextDir =
|
|
8535
|
-
|
|
8536
|
-
|
|
9031
|
+
const contextDir = join32(dirname6(resolvedPath), ".decantr", "context");
|
|
9032
|
+
mkdirSync17(contextDir, { recursive: true });
|
|
9033
|
+
writeFileSync20(join32(contextDir, "pack-manifest.json"), JSON.stringify(manifest, null, 2) + "\n");
|
|
8537
9034
|
writtenContextDir = contextDir;
|
|
8538
9035
|
}
|
|
8539
9036
|
if (jsonOutput) {
|
|
@@ -8554,14 +9051,14 @@ async function printHostedExecutionPackManifest(essencePath, namespace, jsonOutp
|
|
|
8554
9051
|
}
|
|
8555
9052
|
}
|
|
8556
9053
|
async function hydrateHostedExecutionPacksIfMissing(projectRoot, namespace = "@official") {
|
|
8557
|
-
const contextDir =
|
|
8558
|
-
const reviewPackPath =
|
|
8559
|
-
const manifestPath =
|
|
8560
|
-
if (
|
|
9054
|
+
const contextDir = join32(projectRoot, ".decantr", "context");
|
|
9055
|
+
const reviewPackPath = join32(contextDir, "review-pack.json");
|
|
9056
|
+
const manifestPath = join32(contextDir, "pack-manifest.json");
|
|
9057
|
+
if (existsSync30(reviewPackPath) && existsSync30(manifestPath)) {
|
|
8561
9058
|
return { attempted: false, hydrated: false };
|
|
8562
9059
|
}
|
|
8563
|
-
const essencePath =
|
|
8564
|
-
if (!
|
|
9060
|
+
const essencePath = join32(projectRoot, "decantr.essence.json");
|
|
9061
|
+
if (!existsSync30(essencePath)) {
|
|
8565
9062
|
return { attempted: false, hydrated: false };
|
|
8566
9063
|
}
|
|
8567
9064
|
const reviewHydration = await hydrateHostedReviewPackIfMissing(projectRoot, namespace);
|
|
@@ -8570,9 +9067,9 @@ async function hydrateHostedExecutionPacksIfMissing(projectRoot, namespace = "@o
|
|
|
8570
9067
|
}
|
|
8571
9068
|
try {
|
|
8572
9069
|
const client = getPublicAPIClient();
|
|
8573
|
-
const essence = JSON.parse(
|
|
9070
|
+
const essence = JSON.parse(readFileSync23(essencePath, "utf-8"));
|
|
8574
9071
|
const bundle = await client.compileExecutionPacks(essence, { namespace });
|
|
8575
|
-
|
|
9072
|
+
mkdirSync17(contextDir, { recursive: true });
|
|
8576
9073
|
writeExecutionPackBundleArtifacts(contextDir, bundle);
|
|
8577
9074
|
return { attempted: true, hydrated: true, scope: "bundle" };
|
|
8578
9075
|
} catch {
|
|
@@ -8580,19 +9077,19 @@ async function hydrateHostedExecutionPacksIfMissing(projectRoot, namespace = "@o
|
|
|
8580
9077
|
}
|
|
8581
9078
|
}
|
|
8582
9079
|
async function hydrateHostedReviewPackIfMissing(projectRoot, namespace = "@official") {
|
|
8583
|
-
const contextDir =
|
|
8584
|
-
const reviewPackPath =
|
|
8585
|
-
const manifestPath =
|
|
8586
|
-
if (
|
|
9080
|
+
const contextDir = join32(projectRoot, ".decantr", "context");
|
|
9081
|
+
const reviewPackPath = join32(contextDir, "review-pack.json");
|
|
9082
|
+
const manifestPath = join32(contextDir, "pack-manifest.json");
|
|
9083
|
+
if (existsSync30(reviewPackPath) && existsSync30(manifestPath)) {
|
|
8587
9084
|
return { attempted: false, hydrated: false };
|
|
8588
9085
|
}
|
|
8589
|
-
const essencePath =
|
|
8590
|
-
if (!
|
|
9086
|
+
const essencePath = join32(projectRoot, "decantr.essence.json");
|
|
9087
|
+
if (!existsSync30(essencePath)) {
|
|
8591
9088
|
return { attempted: false, hydrated: false };
|
|
8592
9089
|
}
|
|
8593
9090
|
try {
|
|
8594
9091
|
const client = getPublicAPIClient();
|
|
8595
|
-
const essence = JSON.parse(
|
|
9092
|
+
const essence = JSON.parse(readFileSync23(essencePath, "utf-8"));
|
|
8596
9093
|
const selected = await client.selectExecutionPack(
|
|
8597
9094
|
{
|
|
8598
9095
|
essence,
|
|
@@ -8600,14 +9097,14 @@ async function hydrateHostedReviewPackIfMissing(projectRoot, namespace = "@offic
|
|
|
8600
9097
|
},
|
|
8601
9098
|
{ namespace }
|
|
8602
9099
|
);
|
|
8603
|
-
|
|
8604
|
-
|
|
8605
|
-
|
|
8606
|
-
|
|
9100
|
+
mkdirSync17(contextDir, { recursive: true });
|
|
9101
|
+
writeFileSync20(join32(contextDir, "review-pack.md"), selected.pack.renderedMarkdown);
|
|
9102
|
+
writeFileSync20(
|
|
9103
|
+
join32(contextDir, "review-pack.json"),
|
|
8607
9104
|
JSON.stringify(selected.pack, null, 2) + "\n"
|
|
8608
9105
|
);
|
|
8609
|
-
if (!
|
|
8610
|
-
|
|
9106
|
+
if (!existsSync30(manifestPath)) {
|
|
9107
|
+
writeFileSync20(manifestPath, JSON.stringify(selected.manifest, null, 2) + "\n");
|
|
8611
9108
|
}
|
|
8612
9109
|
return { attempted: true, hydrated: true, scope: "review" };
|
|
8613
9110
|
} catch {
|
|
@@ -8617,17 +9114,17 @@ async function hydrateHostedReviewPackIfMissing(projectRoot, namespace = "@offic
|
|
|
8617
9114
|
async function printHostedFileCritique(sourcePath, namespace, jsonOutput = false, essencePath, treatmentsPath) {
|
|
8618
9115
|
const client = getPublicAPIClient();
|
|
8619
9116
|
const resolvedSourcePath = resolveUserPath(sourcePath);
|
|
8620
|
-
const resolvedEssencePath = essencePath ? resolveUserPath(essencePath) :
|
|
8621
|
-
const resolvedTreatmentsPath = treatmentsPath ? resolveUserPath(treatmentsPath) :
|
|
8622
|
-
if (!
|
|
9117
|
+
const resolvedEssencePath = essencePath ? resolveUserPath(essencePath) : join32(process.cwd(), "decantr.essence.json");
|
|
9118
|
+
const resolvedTreatmentsPath = treatmentsPath ? resolveUserPath(treatmentsPath) : join32(process.cwd(), "src", "styles", "treatments.css");
|
|
9119
|
+
if (!existsSync30(resolvedSourcePath)) {
|
|
8623
9120
|
throw new Error(`Source file not found at ${resolvedSourcePath}`);
|
|
8624
9121
|
}
|
|
8625
|
-
if (!
|
|
9122
|
+
if (!existsSync30(resolvedEssencePath)) {
|
|
8626
9123
|
throw new Error(`Essence file not found at ${resolvedEssencePath}`);
|
|
8627
9124
|
}
|
|
8628
|
-
const code =
|
|
8629
|
-
const essence = JSON.parse(
|
|
8630
|
-
const treatmentsCss =
|
|
9125
|
+
const code = readFileSync23(resolvedSourcePath, "utf-8");
|
|
9126
|
+
const essence = JSON.parse(readFileSync23(resolvedEssencePath, "utf-8"));
|
|
9127
|
+
const treatmentsCss = existsSync30(resolvedTreatmentsPath) ? readFileSync23(resolvedTreatmentsPath, "utf-8") : void 0;
|
|
8631
9128
|
const report = await client.critiqueFile(
|
|
8632
9129
|
{
|
|
8633
9130
|
essence,
|
|
@@ -8651,11 +9148,11 @@ async function printHostedFileCritique(sourcePath, namespace, jsonOutput = false
|
|
|
8651
9148
|
}
|
|
8652
9149
|
async function printHostedProjectAudit(namespace, jsonOutput = false, essencePath, distPath, sourcesPath) {
|
|
8653
9150
|
const client = getPublicAPIClient();
|
|
8654
|
-
const resolvedEssencePath = essencePath ? resolveUserPath(essencePath) :
|
|
8655
|
-
if (!
|
|
9151
|
+
const resolvedEssencePath = essencePath ? resolveUserPath(essencePath) : join32(process.cwd(), "decantr.essence.json");
|
|
9152
|
+
if (!existsSync30(resolvedEssencePath)) {
|
|
8656
9153
|
throw new Error(`Essence file not found at ${resolvedEssencePath}`);
|
|
8657
9154
|
}
|
|
8658
|
-
const essence = JSON.parse(
|
|
9155
|
+
const essence = JSON.parse(readFileSync23(resolvedEssencePath, "utf-8"));
|
|
8659
9156
|
const dist = readHostedDistSnapshot(distPath);
|
|
8660
9157
|
const sources = readHostedSourceSnapshot(sourcesPath);
|
|
8661
9158
|
const report = await client.auditProject(
|
|
@@ -8673,7 +9170,7 @@ async function printHostedProjectAudit(namespace, jsonOutput = false, essencePat
|
|
|
8673
9170
|
console.log(heading2("Hosted Project Audit"));
|
|
8674
9171
|
console.log(` Essence: ${resolvedEssencePath}`);
|
|
8675
9172
|
console.log(
|
|
8676
|
-
` Dist snapshot: ${dist ? distPath ? resolveUserPath(distPath) :
|
|
9173
|
+
` Dist snapshot: ${dist ? distPath ? resolveUserPath(distPath) : join32(process.cwd(), "dist") : "none"}`
|
|
8677
9174
|
);
|
|
8678
9175
|
console.log(
|
|
8679
9176
|
` Source snapshot: ${sources && sourcesPath ? resolveUserPath(sourcesPath) : "none"}`
|
|
@@ -8776,21 +9273,21 @@ function patternCandidateFromRegistryItem(item, source) {
|
|
|
8776
9273
|
function readSuggestCodeContext(projectRoot, route, file) {
|
|
8777
9274
|
const pieces = [];
|
|
8778
9275
|
if (file) {
|
|
8779
|
-
const resolved = isAbsolute3(file) ? file :
|
|
8780
|
-
if (
|
|
8781
|
-
pieces.push(
|
|
9276
|
+
const resolved = isAbsolute3(file) ? file : join32(projectRoot, file);
|
|
9277
|
+
if (existsSync30(resolved)) {
|
|
9278
|
+
pieces.push(readFileSync23(resolved, "utf-8"));
|
|
8782
9279
|
}
|
|
8783
9280
|
}
|
|
8784
9281
|
if (route) {
|
|
8785
|
-
const analysisPath =
|
|
8786
|
-
if (
|
|
9282
|
+
const analysisPath = join32(projectRoot, ".decantr", "analysis.json");
|
|
9283
|
+
if (existsSync30(analysisPath)) {
|
|
8787
9284
|
try {
|
|
8788
|
-
const analysis = JSON.parse(
|
|
9285
|
+
const analysis = JSON.parse(readFileSync23(analysisPath, "utf-8"));
|
|
8789
9286
|
const routeEntry = analysis.routes?.routes?.find((entry) => entry.path === route);
|
|
8790
9287
|
if (routeEntry?.file) {
|
|
8791
|
-
const resolved =
|
|
8792
|
-
if (
|
|
8793
|
-
pieces.push(
|
|
9288
|
+
const resolved = join32(projectRoot, routeEntry.file);
|
|
9289
|
+
if (existsSync30(resolved)) {
|
|
9290
|
+
pieces.push(readFileSync23(resolved, "utf-8"));
|
|
8794
9291
|
}
|
|
8795
9292
|
}
|
|
8796
9293
|
} catch {
|
|
@@ -8803,7 +9300,7 @@ function localPatternMatches(projectRoot, query) {
|
|
|
8803
9300
|
if (!projectRoot) return [];
|
|
8804
9301
|
const pack = readLocalPatternPack(projectRoot);
|
|
8805
9302
|
const patterns = Array.isArray(pack?.patterns) ? pack.patterns : [];
|
|
8806
|
-
const queryTerms = query.toLowerCase().split(/[^a-z0-9]+/).filter((term) => term.length > 1);
|
|
9303
|
+
const queryTerms = 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]);
|
|
8807
9304
|
if (queryTerms.length === 0) return [];
|
|
8808
9305
|
return patterns.map((pattern) => {
|
|
8809
9306
|
const id = typeof pattern.id === "string" ? pattern.id : "local-pattern";
|
|
@@ -8815,7 +9312,11 @@ function localPatternMatches(projectRoot, query) {
|
|
|
8815
9312
|
role,
|
|
8816
9313
|
typeof pattern.decide === "string" ? pattern.decide : null,
|
|
8817
9314
|
...Array.isArray(pattern.appliesTo) ? pattern.appliesTo : [],
|
|
8818
|
-
...Array.isArray(pattern.componentPaths) ? pattern.componentPaths : []
|
|
9315
|
+
...Array.isArray(pattern.componentPaths) ? pattern.componentPaths : [],
|
|
9316
|
+
...Array.isArray(pattern.tokenHints) ? pattern.tokenHints : [],
|
|
9317
|
+
...Array.isArray(pattern.classHints) ? pattern.classHints : [],
|
|
9318
|
+
...Array.isArray(pattern.evidence) ? pattern.evidence : [],
|
|
9319
|
+
...Array.isArray(pattern.forbiddenAlternatives) ? pattern.forbiddenAlternatives : []
|
|
8819
9320
|
].filter((entry) => typeof entry === "string").join(" ").toLowerCase();
|
|
8820
9321
|
const score = queryTerms.reduce((sum, term) => sum + (haystack.includes(term) ? 1 : 0), 0);
|
|
8821
9322
|
return { id, label, role, score };
|
|
@@ -8849,6 +9350,7 @@ async function loadPatternDiscoveryCandidates(registryClient) {
|
|
|
8849
9350
|
}
|
|
8850
9351
|
async function cmdSuggest(query, options = {}) {
|
|
8851
9352
|
const searchType = options.type || "pattern";
|
|
9353
|
+
const projectRoot = options.projectRoot ?? process.cwd();
|
|
8852
9354
|
if (searchType !== "pattern" && searchType !== "patterns") {
|
|
8853
9355
|
const apiClient = getAPIClient();
|
|
8854
9356
|
try {
|
|
@@ -8869,9 +9371,11 @@ async function cmdSuggest(query, options = {}) {
|
|
|
8869
9371
|
}
|
|
8870
9372
|
}
|
|
8871
9373
|
const registryClient = new RegistryClient({
|
|
8872
|
-
cacheDir:
|
|
9374
|
+
cacheDir: join32(projectRoot, ".decantr", "cache")
|
|
8873
9375
|
});
|
|
8874
|
-
const code = options.fromCode || options.file ? readSuggestCodeContext(
|
|
9376
|
+
const code = options.fromCode || options.file ? readSuggestCodeContext(projectRoot, options.route, options.file) : "";
|
|
9377
|
+
const localMatches = localPatternMatches(projectRoot, [query, code].filter(Boolean).join("\n"));
|
|
9378
|
+
const bridgeMatches = styleBridgeMatches(projectRoot, [query, code].filter(Boolean).join("\n"));
|
|
8875
9379
|
const candidates = await loadPatternDiscoveryCandidates(registryClient);
|
|
8876
9380
|
const matches = rankPatternCandidates(
|
|
8877
9381
|
{
|
|
@@ -8882,7 +9386,7 @@ async function cmdSuggest(query, options = {}) {
|
|
|
8882
9386
|
},
|
|
8883
9387
|
candidates
|
|
8884
9388
|
);
|
|
8885
|
-
if (matches.length === 0) {
|
|
9389
|
+
if (matches.length === 0 && localMatches.length === 0 && bridgeMatches.length === 0) {
|
|
8886
9390
|
console.log(dim3(`No pattern suggestions for "${query}"`));
|
|
8887
9391
|
console.log("");
|
|
8888
9392
|
console.log("Try:");
|
|
@@ -8902,10 +9406,14 @@ async function cmdSuggest(query, options = {}) {
|
|
|
8902
9406
|
`Pattern suggestions for "${query}"${contextBits.length > 0 ? ` (${contextBits.join(", ")})` : ""}`
|
|
8903
9407
|
)
|
|
8904
9408
|
);
|
|
8905
|
-
|
|
8906
|
-
|
|
8907
|
-
|
|
8908
|
-
|
|
9409
|
+
if (bridgeMatches.length > 0) {
|
|
9410
|
+
console.log(`${BOLD9}Project-owned style bridge:${RESET16}`);
|
|
9411
|
+
for (const match of bridgeMatches) {
|
|
9412
|
+
const hints = [...match.tokenHints, ...match.classHints].slice(0, 3).join(", ");
|
|
9413
|
+
console.log(` ${cyan3(match.id)} ${match.label}${hints ? ` ${dim3(hints)}` : ""}`);
|
|
9414
|
+
}
|
|
9415
|
+
console.log("");
|
|
9416
|
+
}
|
|
8909
9417
|
if (localMatches.length > 0) {
|
|
8910
9418
|
console.log(`${BOLD9}Project-owned local law:${RESET16}`);
|
|
8911
9419
|
for (const match of localMatches) {
|
|
@@ -8915,6 +9423,12 @@ async function cmdSuggest(query, options = {}) {
|
|
|
8915
9423
|
console.log("");
|
|
8916
9424
|
console.log(`${BOLD9}Registry patterns:${RESET16}`);
|
|
8917
9425
|
}
|
|
9426
|
+
if (matches.length === 0) {
|
|
9427
|
+
console.log(dim3("No hosted/bundled registry patterns matched this query."));
|
|
9428
|
+
console.log("");
|
|
9429
|
+
console.log(dim3('Use local law first, or run "decantr list patterns" to browse registry options.'));
|
|
9430
|
+
return;
|
|
9431
|
+
}
|
|
8918
9432
|
for (const match of matches.slice(0, 8)) {
|
|
8919
9433
|
const candidate = match.candidate;
|
|
8920
9434
|
const slug = candidate.slug || candidate.id;
|
|
@@ -8944,7 +9458,7 @@ async function cmdGet(type, id) {
|
|
|
8944
9458
|
}
|
|
8945
9459
|
const apiType = CONTENT_TYPE_TO_API_CONTENT_TYPE3[type];
|
|
8946
9460
|
const registryClient = new RegistryClient({
|
|
8947
|
-
cacheDir:
|
|
9461
|
+
cacheDir: join32(process.cwd(), ".decantr", "cache")
|
|
8948
9462
|
});
|
|
8949
9463
|
const result = await registryClient.fetchContentItem(apiType, id);
|
|
8950
9464
|
if (result) {
|
|
@@ -8961,10 +9475,10 @@ async function cmdGet(type, id) {
|
|
|
8961
9475
|
return;
|
|
8962
9476
|
}
|
|
8963
9477
|
async function cmdValidate(path) {
|
|
8964
|
-
const essencePath = path ||
|
|
9478
|
+
const essencePath = path || join32(process.cwd(), "decantr.essence.json");
|
|
8965
9479
|
let raw;
|
|
8966
9480
|
try {
|
|
8967
|
-
raw =
|
|
9481
|
+
raw = readFileSync23(essencePath, "utf-8");
|
|
8968
9482
|
} catch {
|
|
8969
9483
|
console.error(error2(`Could not read ${essencePath}`));
|
|
8970
9484
|
process.exitCode = 1;
|
|
@@ -9031,7 +9545,7 @@ async function cmdList(type, sort, recommended, intelligenceSource, blueprintSet
|
|
|
9031
9545
|
return;
|
|
9032
9546
|
}
|
|
9033
9547
|
const registryClient = new RegistryClient({
|
|
9034
|
-
cacheDir:
|
|
9548
|
+
cacheDir: join32(process.cwd(), ".decantr", "cache")
|
|
9035
9549
|
});
|
|
9036
9550
|
const result = await registryClient.fetchContentList(
|
|
9037
9551
|
type,
|
|
@@ -9114,10 +9628,10 @@ ${CYAN9}Telemetry enabled.${RESET16} Decantr will send privacy-filtered CLI prod
|
|
|
9114
9628
|
}
|
|
9115
9629
|
function readCliPackageVersion2() {
|
|
9116
9630
|
const here = dirname6(fileURLToPath3(import.meta.url));
|
|
9117
|
-
const candidates = [
|
|
9631
|
+
const candidates = [join32(here, "..", "package.json"), join32(here, "..", "..", "package.json")];
|
|
9118
9632
|
for (const candidate of candidates) {
|
|
9119
9633
|
try {
|
|
9120
|
-
const pkg = JSON.parse(
|
|
9634
|
+
const pkg = JSON.parse(readFileSync23(candidate, "utf-8"));
|
|
9121
9635
|
if (pkg.version) return pkg.version;
|
|
9122
9636
|
} catch {
|
|
9123
9637
|
}
|
|
@@ -9128,19 +9642,19 @@ function timestampForFile() {
|
|
|
9128
9642
|
return (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
9129
9643
|
}
|
|
9130
9644
|
function backupExistingEssence(projectRoot, label) {
|
|
9131
|
-
const essencePath =
|
|
9132
|
-
if (!
|
|
9133
|
-
const backupPath =
|
|
9645
|
+
const essencePath = join32(projectRoot, "decantr.essence.json");
|
|
9646
|
+
if (!existsSync30(essencePath)) return null;
|
|
9647
|
+
const backupPath = join32(
|
|
9134
9648
|
projectRoot,
|
|
9135
9649
|
`decantr.essence.${label}.${timestampForFile()}.backup.json`
|
|
9136
9650
|
);
|
|
9137
|
-
|
|
9651
|
+
writeFileSync20(backupPath, readFileSync23(essencePath, "utf-8"), "utf-8");
|
|
9138
9652
|
return backupPath;
|
|
9139
9653
|
}
|
|
9140
9654
|
function writeBrownfieldProjectJson(input) {
|
|
9141
|
-
const decantrDir =
|
|
9142
|
-
|
|
9143
|
-
|
|
9655
|
+
const decantrDir = join32(input.projectRoot, ".decantr");
|
|
9656
|
+
mkdirSync17(join32(decantrDir, "context"), { recursive: true });
|
|
9657
|
+
mkdirSync17(join32(decantrDir, "cache"), { recursive: true });
|
|
9144
9658
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
9145
9659
|
const projectJson = {
|
|
9146
9660
|
detected: {
|
|
@@ -9181,7 +9695,7 @@ function writeBrownfieldProjectJson(input) {
|
|
|
9181
9695
|
}
|
|
9182
9696
|
}
|
|
9183
9697
|
};
|
|
9184
|
-
|
|
9698
|
+
writeFileSync20(join32(decantrDir, "project.json"), JSON.stringify(projectJson, null, 2) + "\n");
|
|
9185
9699
|
}
|
|
9186
9700
|
async function applyAcceptedBrownfieldProposal(input) {
|
|
9187
9701
|
const proposal = readBrownfieldProposal(input.projectRoot);
|
|
@@ -9197,8 +9711,8 @@ async function applyAcceptedBrownfieldProposal(input) {
|
|
|
9197
9711
|
process.exitCode = 1;
|
|
9198
9712
|
return;
|
|
9199
9713
|
}
|
|
9200
|
-
const essencePath =
|
|
9201
|
-
const hasEssence =
|
|
9714
|
+
const essencePath = join32(input.projectRoot, "decantr.essence.json");
|
|
9715
|
+
const hasEssence = existsSync30(essencePath);
|
|
9202
9716
|
let essence;
|
|
9203
9717
|
let backupPath = null;
|
|
9204
9718
|
if (input.mode === "accept" && hasEssence) {
|
|
@@ -9212,7 +9726,7 @@ async function applyAcceptedBrownfieldProposal(input) {
|
|
|
9212
9726
|
return;
|
|
9213
9727
|
}
|
|
9214
9728
|
if (input.mode === "merge" && hasEssence) {
|
|
9215
|
-
const existing = JSON.parse(
|
|
9729
|
+
const existing = JSON.parse(readFileSync23(essencePath, "utf-8"));
|
|
9216
9730
|
if (!isV49(existing)) {
|
|
9217
9731
|
console.log(
|
|
9218
9732
|
error2(
|
|
@@ -9249,9 +9763,9 @@ async function applyAcceptedBrownfieldProposal(input) {
|
|
|
9249
9763
|
assistantBridge: input.assistantBridge,
|
|
9250
9764
|
mode: input.mode
|
|
9251
9765
|
});
|
|
9252
|
-
|
|
9766
|
+
writeFileSync20(essencePath, JSON.stringify(essence, null, 2) + "\n", "utf-8");
|
|
9253
9767
|
const registryClient = new RegistryClient({
|
|
9254
|
-
cacheDir:
|
|
9768
|
+
cacheDir: join32(input.projectRoot, ".decantr", "cache"),
|
|
9255
9769
|
offline: true,
|
|
9256
9770
|
projectRoot: input.projectRoot
|
|
9257
9771
|
});
|
|
@@ -9313,18 +9827,23 @@ async function applyAcceptedBrownfieldProposal(input) {
|
|
|
9313
9827
|
if (refreshResult.contextFiles.length > 8) {
|
|
9314
9828
|
console.log(` ${dim3(`(+${refreshResult.contextFiles.length - 8} more)`)}`);
|
|
9315
9829
|
}
|
|
9316
|
-
|
|
9317
|
-
|
|
9318
|
-
|
|
9319
|
-
|
|
9320
|
-
|
|
9321
|
-
|
|
9322
|
-
|
|
9323
|
-
|
|
9324
|
-
|
|
9325
|
-
|
|
9326
|
-
|
|
9327
|
-
|
|
9830
|
+
if (!input.suppressNextSteps) {
|
|
9831
|
+
console.log("");
|
|
9832
|
+
console.log(" Next steps:");
|
|
9833
|
+
console.log(
|
|
9834
|
+
` 1. Run ${cyan3(withProject("decantr doctor", projectLabel))} to explain adoption state and the next command`
|
|
9835
|
+
);
|
|
9836
|
+
console.log(
|
|
9837
|
+
` 2. Run ${cyan3(withProject("decantr codify --from-audit", projectLabel))} when you are ready to propose project-owned UI law`
|
|
9838
|
+
);
|
|
9839
|
+
console.log(
|
|
9840
|
+
` 3. Use ${cyan3(withProject('decantr task / "change summary"', projectLabel))} before LLM edits`
|
|
9841
|
+
);
|
|
9842
|
+
console.log(
|
|
9843
|
+
` 4. Run ${cyan3(withProject("decantr verify --brownfield", projectLabel))} after edits`
|
|
9844
|
+
);
|
|
9845
|
+
console.log("");
|
|
9846
|
+
}
|
|
9328
9847
|
}
|
|
9329
9848
|
async function cmdInit(args) {
|
|
9330
9849
|
const workspaceInfo = resolveWorkspaceInfo(process.cwd(), args.project);
|
|
@@ -9372,7 +9891,8 @@ async function cmdInit(args) {
|
|
|
9372
9891
|
detected,
|
|
9373
9892
|
workspaceInfo,
|
|
9374
9893
|
mode: proposalMode,
|
|
9375
|
-
assistantBridge: policy.assistantBridge
|
|
9894
|
+
assistantBridge: policy.assistantBridge,
|
|
9895
|
+
suppressNextSteps: args.internalSuppressNextSteps
|
|
9376
9896
|
});
|
|
9377
9897
|
if (args.telemetry) enableCliTelemetry(projectRoot);
|
|
9378
9898
|
writeArtifactReadme(projectRoot);
|
|
@@ -9424,7 +9944,7 @@ async function cmdInit(args) {
|
|
|
9424
9944
|
}
|
|
9425
9945
|
}
|
|
9426
9946
|
const registryClient = new RegistryClient({
|
|
9427
|
-
cacheDir:
|
|
9947
|
+
cacheDir: join32(projectRoot, ".decantr", "cache"),
|
|
9428
9948
|
apiUrl: args.registry,
|
|
9429
9949
|
offline: args.offline,
|
|
9430
9950
|
projectRoot
|
|
@@ -9756,7 +10276,7 @@ ${YELLOW12}You're offline. Scaffolding Decantr default.${RESET16}`);
|
|
|
9756
10276
|
if (appliedRuleFiles.length > 0) {
|
|
9757
10277
|
console.log(` ${dim3(`Rule bridge applied: ${appliedRuleFiles.join(", ")}`)}`);
|
|
9758
10278
|
}
|
|
9759
|
-
if (!
|
|
10279
|
+
if (!existsSync30(join32(projectRoot, "package.json"))) {
|
|
9760
10280
|
console.log("");
|
|
9761
10281
|
console.log(
|
|
9762
10282
|
dim3(` Note: ${cyan3("decantr init")} created Decantr contract/context files only.`)
|
|
@@ -9767,7 +10287,7 @@ ${YELLOW12}You're offline. Scaffolding Decantr default.${RESET16}`);
|
|
|
9767
10287
|
)
|
|
9768
10288
|
);
|
|
9769
10289
|
}
|
|
9770
|
-
const hasCompiledPacks =
|
|
10290
|
+
const hasCompiledPacks = existsSync30(join32(projectRoot, ".decantr", "context", "scaffold-pack.md"));
|
|
9771
10291
|
console.log("");
|
|
9772
10292
|
console.log(" Next steps:");
|
|
9773
10293
|
if (hasCompiledPacks) {
|
|
@@ -9807,7 +10327,7 @@ ${YELLOW12}You're offline. Scaffolding Decantr default.${RESET16}`);
|
|
|
9807
10327
|
console.log(` ${cyan3("decantr upgrade")} Update to latest patterns`);
|
|
9808
10328
|
console.log(` ${cyan3("decantr check")} Detect drift issues`);
|
|
9809
10329
|
console.log(` ${cyan3("decantr migrate --to v4")} Migrate older essence files to v4`);
|
|
9810
|
-
const essenceContent =
|
|
10330
|
+
const essenceContent = readFileSync23(result.essencePath, "utf-8");
|
|
9811
10331
|
const essence = JSON.parse(essenceContent);
|
|
9812
10332
|
const validation = validateEssence2(essence);
|
|
9813
10333
|
if (!validation.valid) {
|
|
@@ -9862,16 +10382,16 @@ Validation warnings: ${validation.errors.join(", ")}`));
|
|
|
9862
10382
|
}
|
|
9863
10383
|
}
|
|
9864
10384
|
async function cmdStatus(projectRoot = process.cwd()) {
|
|
9865
|
-
const essencePath =
|
|
9866
|
-
const projectJsonPath =
|
|
10385
|
+
const essencePath = join32(projectRoot, "decantr.essence.json");
|
|
10386
|
+
const projectJsonPath = join32(projectRoot, ".decantr", "project.json");
|
|
9867
10387
|
console.log(heading2("Decantr Project Status"));
|
|
9868
|
-
if (!
|
|
10388
|
+
if (!existsSync30(essencePath)) {
|
|
9869
10389
|
console.log(`${RED12}No decantr.essence.json found.${RESET16}`);
|
|
9870
10390
|
console.log(dim3('Run "decantr init" to create one.'));
|
|
9871
10391
|
return;
|
|
9872
10392
|
}
|
|
9873
10393
|
try {
|
|
9874
|
-
const essence = JSON.parse(
|
|
10394
|
+
const essence = JSON.parse(readFileSync23(essencePath, "utf-8"));
|
|
9875
10395
|
const validation = validateEssence2(essence);
|
|
9876
10396
|
const essenceVersion = isV49(essence) ? "v4" : "legacy";
|
|
9877
10397
|
console.log(`${BOLD9}Essence:${RESET16}`);
|
|
@@ -9919,9 +10439,9 @@ async function cmdStatus(projectRoot = process.cwd()) {
|
|
|
9919
10439
|
}
|
|
9920
10440
|
console.log("");
|
|
9921
10441
|
console.log(`${BOLD9}Sync Status:${RESET16}`);
|
|
9922
|
-
if (
|
|
10442
|
+
if (existsSync30(projectJsonPath)) {
|
|
9923
10443
|
try {
|
|
9924
|
-
const projectJson = JSON.parse(
|
|
10444
|
+
const projectJson = JSON.parse(readFileSync23(projectJsonPath, "utf-8"));
|
|
9925
10445
|
const syncStatus = projectJson.sync?.status || "unknown";
|
|
9926
10446
|
const lastSync = projectJson.sync?.lastSync || "never";
|
|
9927
10447
|
const source = projectJson.sync?.registrySource || "unknown";
|
|
@@ -9939,7 +10459,7 @@ async function cmdStatus(projectRoot = process.cwd()) {
|
|
|
9939
10459
|
}
|
|
9940
10460
|
async function cmdSync() {
|
|
9941
10461
|
const projectRoot = process.cwd();
|
|
9942
|
-
const cacheDir =
|
|
10462
|
+
const cacheDir = join32(projectRoot, ".decantr", "cache");
|
|
9943
10463
|
console.log(heading2("Syncing registry content..."));
|
|
9944
10464
|
const result = await syncRegistry(cacheDir);
|
|
9945
10465
|
if (result.synced.length > 0) {
|
|
@@ -10133,14 +10653,14 @@ ${BOLD9}Examples:${RESET16}
|
|
|
10133
10653
|
process.exitCode = 1;
|
|
10134
10654
|
return;
|
|
10135
10655
|
}
|
|
10136
|
-
const themePath =
|
|
10137
|
-
if (!
|
|
10656
|
+
const themePath = join32(projectRoot, ".decantr", "custom", "themes", `${name}.json`);
|
|
10657
|
+
if (!existsSync30(themePath)) {
|
|
10138
10658
|
console.error(error2(`Theme "${name}" not found at ${themePath}`));
|
|
10139
10659
|
process.exitCode = 1;
|
|
10140
10660
|
return;
|
|
10141
10661
|
}
|
|
10142
10662
|
try {
|
|
10143
|
-
const theme = JSON.parse(
|
|
10663
|
+
const theme = JSON.parse(readFileSync23(themePath, "utf-8"));
|
|
10144
10664
|
const result = validateCustomTheme(theme);
|
|
10145
10665
|
if (result.valid) {
|
|
10146
10666
|
console.log(success3(`Custom theme "${name}" is valid`));
|
|
@@ -10272,7 +10792,7 @@ function withProject(command, projectArg) {
|
|
|
10272
10792
|
return projectArg ? `${command} --project ${projectArg}` : command;
|
|
10273
10793
|
}
|
|
10274
10794
|
function displayProjectPath(workspaceInfo, projectPath) {
|
|
10275
|
-
const absolutePath =
|
|
10795
|
+
const absolutePath = join32(workspaceInfo.appRoot, projectPath);
|
|
10276
10796
|
const relativePath = relative7(workspaceInfo.cwd, absolutePath).replace(/\\/g, "/");
|
|
10277
10797
|
if (relativePath && !relativePath.startsWith("..") && !isAbsolute3(relativePath)) {
|
|
10278
10798
|
return relativePath;
|
|
@@ -10331,6 +10851,7 @@ function printWorkspaceProjectSelection(workspaceInfo, commandName = "command")
|
|
|
10331
10851
|
console.log("");
|
|
10332
10852
|
console.log("Start by attaching one app:");
|
|
10333
10853
|
console.log(` ${cyan3(`decantr adopt --project ${candidate} --yes`)}`);
|
|
10854
|
+
console.log(` ${cyan3(`decantr codify --from-audit --style-bridge --project ${candidate}`)}`);
|
|
10334
10855
|
console.log("");
|
|
10335
10856
|
console.log("Optional visual evidence after the app is running:");
|
|
10336
10857
|
console.log(
|
|
@@ -10340,7 +10861,7 @@ function printWorkspaceProjectSelection(workspaceInfo, commandName = "command")
|
|
|
10340
10861
|
function printMonorepoSetupGuidance(workspaceInfo) {
|
|
10341
10862
|
const candidate = firstWorkspaceCandidate(workspaceInfo);
|
|
10342
10863
|
const attachedProjects = workspaceInfo.appCandidates.filter(
|
|
10343
|
-
(appCandidate) =>
|
|
10864
|
+
(appCandidate) => existsSync30(join32(workspaceInfo.workspaceRoot, appCandidate, "decantr.essence.json"))
|
|
10344
10865
|
);
|
|
10345
10866
|
const firstAttached = attachedProjects[0];
|
|
10346
10867
|
console.log(heading2("Decantr Setup"));
|
|
@@ -10397,7 +10918,7 @@ function printMonorepoSetupGuidance(workspaceInfo) {
|
|
|
10397
10918
|
` ${cyan3(`decantr adopt --project ${candidate} --yes`)} Attach Decantr to one app`
|
|
10398
10919
|
);
|
|
10399
10920
|
console.log(
|
|
10400
|
-
` ${cyan3(`decantr codify --from-audit --project ${candidate}`)} Propose project-owned UI law`
|
|
10921
|
+
` ${cyan3(`decantr codify --from-audit --style-bridge --project ${candidate}`)} Propose project-owned UI law and style bridge`
|
|
10401
10922
|
);
|
|
10402
10923
|
console.log("");
|
|
10403
10924
|
console.log(`${BOLD9}Optional visual evidence:${RESET16}`);
|
|
@@ -10408,7 +10929,7 @@ function printMonorepoSetupGuidance(workspaceInfo) {
|
|
|
10408
10929
|
function resolveWorkflowProject(flags, commandName = "command", options = {}) {
|
|
10409
10930
|
const projectArg = flagString(flags, "project");
|
|
10410
10931
|
const workspaceInfo = resolveWorkspaceInfo(process.cwd(), projectArg);
|
|
10411
|
-
if (projectArg && options.requireExisting !== false && !
|
|
10932
|
+
if (projectArg && options.requireExisting !== false && !existsSync30(workspaceInfo.appRoot)) {
|
|
10412
10933
|
printProjectNotFound(projectArg, commandName);
|
|
10413
10934
|
process.exitCode = 1;
|
|
10414
10935
|
return null;
|
|
@@ -10468,21 +10989,27 @@ async function cmdSetupWorkflow(args) {
|
|
|
10468
10989
|
console.log(` Detected: ${formatDetection(detected)}`);
|
|
10469
10990
|
console.log("");
|
|
10470
10991
|
if (detected.existingEssence) {
|
|
10471
|
-
const hasLocalPatterns =
|
|
10472
|
-
const hasLocalRules =
|
|
10992
|
+
const hasLocalPatterns = existsSync30(localPatternsPath(workspaceInfo.appRoot));
|
|
10993
|
+
const hasLocalRules = existsSync30(localRulesPath(workspaceInfo.appRoot));
|
|
10994
|
+
const hasStyleBridge = existsSync30(styleBridgePath(workspaceInfo.appRoot));
|
|
10473
10995
|
const verifyCommand = hasLocalPatterns || hasLocalRules ? "decantr verify --brownfield --local-patterns" : "decantr verify --brownfield";
|
|
10474
10996
|
console.log(`${BOLD9}Recommended path:${RESET16} maintain an attached Decantr project`);
|
|
10997
|
+
console.log(
|
|
10998
|
+
` ${cyan3(withProject("decantr doctor", projectArg))} Explain current state and next command`
|
|
10999
|
+
);
|
|
11000
|
+
if (!hasLocalPatterns || !hasLocalRules || !hasStyleBridge) {
|
|
11001
|
+
const codifyCommand = hasLocalPatterns && hasLocalRules ? "decantr codify --style-bridge" : "decantr codify --from-audit --style-bridge";
|
|
11002
|
+
console.log(
|
|
11003
|
+
` ${cyan3(withProject(codifyCommand, projectArg))} Propose missing local law/style bridge`
|
|
11004
|
+
);
|
|
11005
|
+
}
|
|
10475
11006
|
console.log(
|
|
10476
11007
|
` ${cyan3(withProject('decantr task <route> "<change>"', projectArg))} Prepare LLM context before edits`
|
|
10477
11008
|
);
|
|
10478
11009
|
console.log(
|
|
10479
11010
|
` ${cyan3(withProject(verifyCommand, projectArg))} Run local health and drift checks`
|
|
10480
11011
|
);
|
|
10481
|
-
|
|
10482
|
-
console.log(
|
|
10483
|
-
` ${cyan3(withProject("decantr codify --from-audit", projectArg))} Propose project-owned local law`
|
|
10484
|
-
);
|
|
10485
|
-
}
|
|
11012
|
+
console.log(` ${cyan3(withProject("decantr ci init", projectArg))} Wire the app into CI`);
|
|
10486
11013
|
return;
|
|
10487
11014
|
}
|
|
10488
11015
|
if (hasFootprint) {
|
|
@@ -10491,7 +11018,7 @@ async function cmdSetupWorkflow(args) {
|
|
|
10491
11018
|
` ${cyan3(withProject("decantr adopt --yes", projectArg))} Analyze, attach, and verify`
|
|
10492
11019
|
);
|
|
10493
11020
|
console.log(
|
|
10494
|
-
` ${cyan3(withProject("decantr codify --from-audit", projectArg))} Propose local UI law`
|
|
11021
|
+
` ${cyan3(withProject("decantr codify --from-audit --style-bridge", projectArg))} Propose local UI law and style bridge`
|
|
10495
11022
|
);
|
|
10496
11023
|
console.log("");
|
|
10497
11024
|
console.log(`${BOLD9}Optional visual evidence after the app is running:${RESET16}`);
|
|
@@ -10550,7 +11077,7 @@ async function cmdAdoptWorkflow(args) {
|
|
|
10550
11077
|
const hydratePacks = flagBoolean(flags, "packs", true) && !flagBoolean(flags, "skip-packs") && !flagBoolean(flags, "offline") && process.env.DECANTR_OFFLINE !== "true";
|
|
10551
11078
|
const initCi = flagBoolean(flags, "ci") || flagBoolean(flags, "init-ci");
|
|
10552
11079
|
const assistantBridge = flagString(flags, "assistant-bridge");
|
|
10553
|
-
const hasEssence =
|
|
11080
|
+
const hasEssence = existsSync30(join32(projectRoot, "decantr.essence.json"));
|
|
10554
11081
|
const proposalFlag = flagBoolean(flags, "replace-essence") ? "--replace-essence" : flagBoolean(flags, "merge-proposal") || hasEssence ? "--merge-proposal" : "--accept-proposal";
|
|
10555
11082
|
const steps = [
|
|
10556
11083
|
"analyze current app and write .decantr/brownfield intelligence",
|
|
@@ -10591,13 +11118,14 @@ async function cmdAdoptWorkflow(args) {
|
|
|
10591
11118
|
"merge-proposal": proposalFlag === "--merge-proposal",
|
|
10592
11119
|
"replace-essence": proposalFlag === "--replace-essence",
|
|
10593
11120
|
"assistant-bridge": assistantBridge,
|
|
10594
|
-
telemetry: flagBoolean(flags, "telemetry")
|
|
11121
|
+
telemetry: flagBoolean(flags, "telemetry"),
|
|
11122
|
+
internalSuppressNextSteps: true
|
|
10595
11123
|
});
|
|
10596
11124
|
if (process.exitCode && process.exitCode !== 0) return;
|
|
10597
11125
|
if (hydratePacks) {
|
|
10598
11126
|
try {
|
|
10599
11127
|
const { bundle, contextDir } = await compileHostedExecutionPackBundle(
|
|
10600
|
-
|
|
11128
|
+
join32(projectRoot, "decantr.essence.json")
|
|
10601
11129
|
);
|
|
10602
11130
|
const written = writeHostedExecutionPackContextArtifacts(
|
|
10603
11131
|
contextDir,
|
|
@@ -10620,7 +11148,7 @@ async function cmdAdoptWorkflow(args) {
|
|
|
10620
11148
|
console.log(dim3("Skipping hosted pack hydration in offline mode."));
|
|
10621
11149
|
}
|
|
10622
11150
|
if (runVerify) {
|
|
10623
|
-
const { cmdHealth } = await import("./health-
|
|
11151
|
+
const { cmdHealth } = await import("./health-DBCUR6VQ.js");
|
|
10624
11152
|
await cmdHealth(projectRoot, {
|
|
10625
11153
|
browser: runBrowser,
|
|
10626
11154
|
browserBaseUrl: baseUrl,
|
|
@@ -10638,10 +11166,10 @@ async function cmdAdoptWorkflow(args) {
|
|
|
10638
11166
|
console.log("");
|
|
10639
11167
|
console.log(`${BOLD9}Brownfield operating loop:${RESET16}`);
|
|
10640
11168
|
console.log(
|
|
10641
|
-
` ${cyan3(withProject("decantr codify --from-audit", projectArg))}
|
|
11169
|
+
` ${cyan3(withProject("decantr codify --from-audit --style-bridge", projectArg))} Propose project-owned UI law and style bridge`
|
|
10642
11170
|
);
|
|
10643
11171
|
console.log(
|
|
10644
|
-
` ${cyan3(withProject("decantr codify --accept", projectArg))} Accept reviewed local patterns and
|
|
11172
|
+
` ${cyan3(withProject("decantr codify --accept", projectArg))} Accept reviewed local patterns, rules, and bridge`
|
|
10645
11173
|
);
|
|
10646
11174
|
console.log(
|
|
10647
11175
|
` ${cyan3(withProject('decantr task <route> "<change>"', projectArg))} Give your LLM route-specific context before edits`
|
|
@@ -10691,7 +11219,7 @@ async function cmdVerifyWorkflow(args) {
|
|
|
10691
11219
|
return;
|
|
10692
11220
|
}
|
|
10693
11221
|
if (workspaceMode) {
|
|
10694
|
-
const { cmdWorkspace } = await import("./workspace-
|
|
11222
|
+
const { cmdWorkspace } = await import("./workspace-BCA5RCHN.js");
|
|
10695
11223
|
await cmdWorkspace(process.cwd(), ["workspace", "health", ...withoutWorkflowOnlyFlags(args)]);
|
|
10696
11224
|
return;
|
|
10697
11225
|
}
|
|
@@ -10724,7 +11252,7 @@ async function cmdVerifyWorkflow(args) {
|
|
|
10724
11252
|
}
|
|
10725
11253
|
let guardExitCode;
|
|
10726
11254
|
if (brownfield) {
|
|
10727
|
-
const { cmdHeal, collectCheckIssues } = await import("./heal-
|
|
11255
|
+
const { cmdHeal, collectCheckIssues } = await import("./heal-WMSAX4LU.js");
|
|
10728
11256
|
if (quietOutput) {
|
|
10729
11257
|
const result = collectCheckIssues(workspaceInfo.appRoot, { brownfield: true });
|
|
10730
11258
|
guardExitCode = result.issues.some((issue) => issue.type === "error") ? 1 : void 0;
|
|
@@ -10734,7 +11262,7 @@ async function cmdVerifyWorkflow(args) {
|
|
|
10734
11262
|
process.exitCode = void 0;
|
|
10735
11263
|
}
|
|
10736
11264
|
}
|
|
10737
|
-
const { cmdHealth, parseHealthArgs } = await import("./health-
|
|
11265
|
+
const { cmdHealth, parseHealthArgs } = await import("./health-DBCUR6VQ.js");
|
|
10738
11266
|
await cmdHealth(workspaceInfo.appRoot, parseHealthArgs(healthArgs));
|
|
10739
11267
|
if (localPatterns) {
|
|
10740
11268
|
const validation = validateLocalLaw(workspaceInfo.appRoot);
|
|
@@ -10784,14 +11312,22 @@ async function cmdVerifyWorkflow(args) {
|
|
|
10784
11312
|
}
|
|
10785
11313
|
}
|
|
10786
11314
|
}
|
|
11315
|
+
const styleBridge = createStyleBridgeTaskSummary(workspaceInfo.appRoot);
|
|
11316
|
+
if (!quietOutput && styleBridge.path) {
|
|
11317
|
+
console.log("");
|
|
11318
|
+
console.log(`${GREEN16}Style bridge found:${RESET16} ${styleBridge.path}`);
|
|
11319
|
+
console.log(
|
|
11320
|
+
`${DIM16}${styleBridge.mappingCount} mapping(s), ${styleBridge.stylingApproach ?? "unknown"} styling${styleBridge.themeModes.length > 0 ? `, themes: ${styleBridge.themeModes.join(", ")}` : ""}${RESET16}`
|
|
11321
|
+
);
|
|
11322
|
+
}
|
|
10787
11323
|
if (guardExitCode && guardExitCode !== 0 && (!process.exitCode || process.exitCode === 0)) {
|
|
10788
11324
|
process.exitCode = guardExitCode;
|
|
10789
11325
|
}
|
|
10790
11326
|
}
|
|
10791
11327
|
function readJsonIfPresent(path) {
|
|
10792
|
-
if (!
|
|
11328
|
+
if (!existsSync30(path)) return null;
|
|
10793
11329
|
try {
|
|
10794
|
-
return JSON.parse(
|
|
11330
|
+
return JSON.parse(readFileSync23(path, "utf-8"));
|
|
10795
11331
|
} catch {
|
|
10796
11332
|
return null;
|
|
10797
11333
|
}
|
|
@@ -10803,6 +11339,7 @@ function mentionsWord(text, term) {
|
|
|
10803
11339
|
function createTaskAuthoritySummary(input) {
|
|
10804
11340
|
const detected = detectProject(input.projectRoot);
|
|
10805
11341
|
const hasLocalLaw = input.hasLocalPatterns || input.hasLocalRules;
|
|
11342
|
+
const hasStyleBridge = input.hasStyleBridge || input.adoptionMode === "style-bridge";
|
|
10806
11343
|
let lane = "Brownfield contract-only";
|
|
10807
11344
|
let sourceAuthority = "Existing app is authoritative; Decantr supplies contract context.";
|
|
10808
11345
|
let styleAuthority = "Use the existing styling system.";
|
|
@@ -10815,11 +11352,11 @@ function createTaskAuthoritySummary(input) {
|
|
|
10815
11352
|
sourceAuthority = "Existing app remains authoritative except where Decantr CSS is explicitly adopted.";
|
|
10816
11353
|
styleAuthority = "Decantr CSS runtime is active where adopted.";
|
|
10817
11354
|
activeAuthorities.push("Decantr CSS runtime");
|
|
10818
|
-
} else if (input.workflowMode === "brownfield-attach" &&
|
|
11355
|
+
} else if (input.workflowMode === "brownfield-attach" && hasStyleBridge) {
|
|
10819
11356
|
lane = "Hybrid style bridge";
|
|
10820
11357
|
sourceAuthority = "Existing app remains authoritative; Decantr intent maps through the style bridge.";
|
|
10821
11358
|
styleAuthority = "Use bridge tokens/classes as a mapping layer onto the app styling system.";
|
|
10822
|
-
activeAuthorities.push("style bridge");
|
|
11359
|
+
activeAuthorities.push("accepted style bridge");
|
|
10823
11360
|
} else if (input.workflowMode === "brownfield-attach" && hasLocalLaw) {
|
|
10824
11361
|
lane = "Hybrid local law";
|
|
10825
11362
|
sourceAuthority = "Existing app plus accepted project-owned UI law are authoritative.";
|
|
@@ -10888,7 +11425,7 @@ async function cmdTaskWorkflow(args) {
|
|
|
10888
11425
|
}
|
|
10889
11426
|
const route = routeInput.startsWith("/") ? routeInput : `/${routeInput}`;
|
|
10890
11427
|
const taskSummary = positional.slice(1).join(" ").trim();
|
|
10891
|
-
const essencePath =
|
|
11428
|
+
const essencePath = join32(workspaceInfo.appRoot, "decantr.essence.json");
|
|
10892
11429
|
const essence = readJsonIfPresent(essencePath);
|
|
10893
11430
|
if (!essence) {
|
|
10894
11431
|
console.error(
|
|
@@ -10912,16 +11449,22 @@ async function cmdTaskWorkflow(args) {
|
|
|
10912
11449
|
}
|
|
10913
11450
|
const section = essence.blueprint.sections.find((entry) => entry.id === target.section);
|
|
10914
11451
|
const page = section?.pages.find((entry) => entry.id === target.page);
|
|
10915
|
-
const contextDir =
|
|
10916
|
-
const manifest = readJsonIfPresent(
|
|
10917
|
-
const projectJson = readJsonIfPresent(
|
|
11452
|
+
const contextDir = join32(workspaceInfo.appRoot, ".decantr", "context");
|
|
11453
|
+
const manifest = readJsonIfPresent(join32(contextDir, "pack-manifest.json"));
|
|
11454
|
+
const projectJson = readJsonIfPresent(join32(workspaceInfo.appRoot, ".decantr", "project.json"));
|
|
10918
11455
|
const pagePack = manifest?.pages?.find((entry) => entry.id === target.page);
|
|
10919
11456
|
const sectionPack = manifest?.sections?.find((entry) => entry.id === target.section);
|
|
10920
|
-
const visualManifest = readJsonIfPresent(
|
|
11457
|
+
const visualManifest = readJsonIfPresent(join32(workspaceInfo.appRoot, ".decantr", "evidence", "visual-manifest.json"));
|
|
10921
11458
|
const screenshot = visualManifest?.routes?.find((entry) => entry.route === route)?.screenshot;
|
|
10922
11459
|
const localPatternPackPath = localPatternsPath(workspaceInfo.appRoot);
|
|
10923
11460
|
const localRuleManifestPath = localRulesPath(workspaceInfo.appRoot);
|
|
11461
|
+
const acceptedStyleBridgePath = styleBridgePath(workspaceInfo.appRoot);
|
|
10924
11462
|
const localLaw = createLocalLawTaskSummary(workspaceInfo.appRoot);
|
|
11463
|
+
const styleBridge = createStyleBridgeTaskSummary(workspaceInfo.appRoot);
|
|
11464
|
+
const displayedStyleBridge = {
|
|
11465
|
+
...styleBridge,
|
|
11466
|
+
path: styleBridge.path ? displayProjectPath(workspaceInfo, styleBridge.path) : null
|
|
11467
|
+
};
|
|
10925
11468
|
const displayedLocalLaw = {
|
|
10926
11469
|
...localLaw,
|
|
10927
11470
|
patternsPath: localLaw.patternsPath ? displayProjectPath(workspaceInfo, localLaw.patternsPath) : null,
|
|
@@ -10934,10 +11477,11 @@ async function cmdTaskWorkflow(args) {
|
|
|
10934
11477
|
projectRoot: workspaceInfo.appRoot,
|
|
10935
11478
|
workflowMode: projectJson?.initialized?.workflowMode ?? null,
|
|
10936
11479
|
adoptionMode: projectJson?.initialized?.adoptionMode ?? null,
|
|
10937
|
-
hasLocalPatterns:
|
|
10938
|
-
hasLocalRules:
|
|
11480
|
+
hasLocalPatterns: existsSync30(localPatternPackPath),
|
|
11481
|
+
hasLocalRules: existsSync30(localRuleManifestPath),
|
|
10939
11482
|
hasPackManifest: Boolean(manifest),
|
|
10940
|
-
taskSummary
|
|
11483
|
+
taskSummary,
|
|
11484
|
+
hasStyleBridge: existsSync30(acceptedStyleBridgePath)
|
|
10941
11485
|
});
|
|
10942
11486
|
const context = {
|
|
10943
11487
|
route,
|
|
@@ -10947,17 +11491,19 @@ async function cmdTaskWorkflow(args) {
|
|
|
10947
11491
|
shell: page?.shell ?? section?.shell ?? null,
|
|
10948
11492
|
patterns: page?.layout?.map(extractPatternName) ?? [],
|
|
10949
11493
|
read: [
|
|
10950
|
-
pagePack ? displayProjectPath(workspaceInfo,
|
|
10951
|
-
sectionPack ? displayProjectPath(workspaceInfo,
|
|
10952
|
-
manifest?.scaffold?.markdown ? displayProjectPath(workspaceInfo,
|
|
11494
|
+
pagePack ? displayProjectPath(workspaceInfo, join32(".decantr/context", pagePack.markdown)) : null,
|
|
11495
|
+
sectionPack ? displayProjectPath(workspaceInfo, join32(".decantr/context", sectionPack.markdown)) : null,
|
|
11496
|
+
manifest?.scaffold?.markdown ? displayProjectPath(workspaceInfo, join32(".decantr/context", manifest.scaffold.markdown)) : null,
|
|
10953
11497
|
displayProjectPath(workspaceInfo, ".decantr/context/scaffold.md"),
|
|
10954
11498
|
displayProjectPath(workspaceInfo, "DECANTR.md"),
|
|
10955
|
-
|
|
10956
|
-
|
|
11499
|
+
existsSync30(localPatternPackPath) ? displayProjectPath(workspaceInfo, ".decantr/local-patterns.json") : null,
|
|
11500
|
+
existsSync30(localRuleManifestPath) ? displayProjectPath(workspaceInfo, ".decantr/rules.json") : null,
|
|
11501
|
+
existsSync30(acceptedStyleBridgePath) ? displayProjectPath(workspaceInfo, ".decantr/style-bridge.json") : null
|
|
10957
11502
|
].filter(Boolean),
|
|
10958
11503
|
screenshot: screenshot?.startsWith(".decantr/") ? displayProjectPath(workspaceInfo, screenshot) : screenshot ?? null,
|
|
10959
11504
|
authority,
|
|
10960
11505
|
localLaw: displayedLocalLaw,
|
|
11506
|
+
styleBridge: displayedStyleBridge,
|
|
10961
11507
|
changedFiles: currentChangedFiles,
|
|
10962
11508
|
changedRoutes,
|
|
10963
11509
|
verifyCommand: withProject("decantr verify --brownfield --local-patterns", projectArg)
|
|
@@ -11014,6 +11560,20 @@ async function cmdTaskWorkflow(args) {
|
|
|
11014
11560
|
` ${YELLOW12}Not codified yet.${RESET16} Run ${cyan3(withProject("decantr codify --from-audit", projectArg))} after adoption.`
|
|
11015
11561
|
);
|
|
11016
11562
|
}
|
|
11563
|
+
if (context.styleBridge.path) {
|
|
11564
|
+
console.log("");
|
|
11565
|
+
console.log(`${BOLD9}Project-owned style bridge:${RESET16}`);
|
|
11566
|
+
console.log(
|
|
11567
|
+
` Bridge: ${cyan3(context.styleBridge.path)} (${context.styleBridge.mappingCount} mappings, ${context.styleBridge.stylingApproach ?? "unknown"} styling)`
|
|
11568
|
+
);
|
|
11569
|
+
if (context.styleBridge.themeModes.length > 0) {
|
|
11570
|
+
console.log(` Theme modes: ${context.styleBridge.themeModes.join(", ")}`);
|
|
11571
|
+
}
|
|
11572
|
+
for (const mapping of context.styleBridge.mappings.slice(0, 4)) {
|
|
11573
|
+
const hints = [...mapping.tokenHints, ...mapping.classHints].slice(0, 4).join(", ");
|
|
11574
|
+
console.log(` ${mapping.id}: ${mapping.label}${hints ? ` \u2014 ${hints}` : ""}`);
|
|
11575
|
+
}
|
|
11576
|
+
}
|
|
11017
11577
|
if (context.changedFiles.length > 0) {
|
|
11018
11578
|
console.log("");
|
|
11019
11579
|
console.log(`${BOLD9}Changed-file context:${RESET16}`);
|
|
@@ -11039,7 +11599,14 @@ async function cmdCodifyWorkflow(args) {
|
|
|
11039
11599
|
const projectArg = flagString(flags, "project");
|
|
11040
11600
|
if (!ensureAllowedFlags(
|
|
11041
11601
|
flags,
|
|
11042
|
-
[
|
|
11602
|
+
[
|
|
11603
|
+
"project",
|
|
11604
|
+
"from-audit",
|
|
11605
|
+
"discover-local-patterns",
|
|
11606
|
+
"codify-local-patterns",
|
|
11607
|
+
"style-bridge",
|
|
11608
|
+
"accept"
|
|
11609
|
+
],
|
|
11043
11610
|
"codify"
|
|
11044
11611
|
)) {
|
|
11045
11612
|
return;
|
|
@@ -11047,25 +11614,29 @@ async function cmdCodifyWorkflow(args) {
|
|
|
11047
11614
|
const workspaceInfo = resolveWorkflowProject(flags, "codify");
|
|
11048
11615
|
if (!workspaceInfo) return;
|
|
11049
11616
|
if (flagBoolean(flags, "accept")) {
|
|
11050
|
-
if (!
|
|
11617
|
+
if (!existsSync30(localPatternsProposalPath(workspaceInfo.appRoot)) && !existsSync30(localRulesProposalPath(workspaceInfo.appRoot)) && !existsSync30(styleBridgeProposalPath(workspaceInfo.appRoot))) {
|
|
11051
11618
|
console.error(
|
|
11052
11619
|
error2(
|
|
11053
|
-
"No
|
|
11620
|
+
"No codify proposal found. Run `decantr codify --from-audit`, `decantr codify --style-bridge`, or `decantr codify` first."
|
|
11054
11621
|
)
|
|
11055
11622
|
);
|
|
11056
11623
|
process.exitCode = 1;
|
|
11057
11624
|
return;
|
|
11058
11625
|
}
|
|
11059
|
-
const
|
|
11060
|
-
|
|
11061
|
-
|
|
11626
|
+
const result = acceptBrownfieldLocalLaw(workspaceInfo.appRoot);
|
|
11627
|
+
const bridgeAcceptedPath = acceptStyleBridge(workspaceInfo.appRoot);
|
|
11628
|
+
if (result.patternAcceptedPath) {
|
|
11629
|
+
console.log(success3(`Accepted local pattern pack: ${result.patternAcceptedPath}`));
|
|
11062
11630
|
}
|
|
11063
|
-
if (
|
|
11064
|
-
console.log(success3(`Accepted local rule manifest: ${
|
|
11631
|
+
if (result.rulesAcceptedPath) {
|
|
11632
|
+
console.log(success3(`Accepted local rule manifest: ${result.rulesAcceptedPath}`));
|
|
11633
|
+
}
|
|
11634
|
+
if (bridgeAcceptedPath) {
|
|
11635
|
+
console.log(success3(`Accepted style bridge: ${bridgeAcceptedPath}`));
|
|
11065
11636
|
}
|
|
11066
11637
|
console.log(
|
|
11067
11638
|
dim3(
|
|
11068
|
-
"Hybrid local law is now active: Decantr will treat accepted local patterns and rules as project-owned authority in task and verify flows."
|
|
11639
|
+
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."
|
|
11069
11640
|
)
|
|
11070
11641
|
);
|
|
11071
11642
|
console.log(
|
|
@@ -11077,19 +11648,42 @@ async function cmdCodifyWorkflow(args) {
|
|
|
11077
11648
|
}
|
|
11078
11649
|
const detected = detectProject(workspaceInfo.appRoot);
|
|
11079
11650
|
const essence = readJsonIfPresent(
|
|
11080
|
-
|
|
11651
|
+
join32(workspaceInfo.appRoot, "decantr.essence.json")
|
|
11081
11652
|
);
|
|
11082
11653
|
const fromAudit = flagBoolean(flags, "from-audit") || flagBoolean(flags, "discover-local-patterns") || flagBoolean(flags, "codify-local-patterns");
|
|
11083
|
-
const
|
|
11084
|
-
|
|
11085
|
-
|
|
11086
|
-
|
|
11087
|
-
|
|
11088
|
-
|
|
11089
|
-
|
|
11090
|
-
|
|
11091
|
-
|
|
11092
|
-
|
|
11654
|
+
const wantsStyleBridge = flagBoolean(flags, "style-bridge");
|
|
11655
|
+
const wantsLocalLaw = !wantsStyleBridge || fromAudit;
|
|
11656
|
+
let localResult = null;
|
|
11657
|
+
if (wantsLocalLaw) {
|
|
11658
|
+
const proposal = createBrownfieldCodifyProposal({
|
|
11659
|
+
projectRoot: workspaceInfo.appRoot,
|
|
11660
|
+
detected,
|
|
11661
|
+
essence,
|
|
11662
|
+
fromAudit
|
|
11663
|
+
});
|
|
11664
|
+
localResult = writeBrownfieldCodifyProposal(workspaceInfo.appRoot, proposal);
|
|
11665
|
+
}
|
|
11666
|
+
let styleBridgeResult = null;
|
|
11667
|
+
if (wantsStyleBridge) {
|
|
11668
|
+
const styling = scanStyling(workspaceInfo.appRoot);
|
|
11669
|
+
styleBridgeResult = writeStyleBridgeProposal(
|
|
11670
|
+
workspaceInfo.appRoot,
|
|
11671
|
+
createStyleBridgeProposal({
|
|
11672
|
+
projectRoot: workspaceInfo.appRoot,
|
|
11673
|
+
detected,
|
|
11674
|
+
essence,
|
|
11675
|
+
styling
|
|
11676
|
+
})
|
|
11677
|
+
);
|
|
11678
|
+
}
|
|
11679
|
+
if (localResult) {
|
|
11680
|
+
console.log(success3(`Wrote local pattern proposal: ${localResult.patternPath}`));
|
|
11681
|
+
console.log(success3(`Wrote local rule proposal: ${localResult.rulesPath}`));
|
|
11682
|
+
}
|
|
11683
|
+
if (styleBridgeResult) {
|
|
11684
|
+
console.log(success3(`Wrote style bridge proposal: ${styleBridgeResult}`));
|
|
11685
|
+
}
|
|
11686
|
+
if (fromAudit && localResult) {
|
|
11093
11687
|
console.log(
|
|
11094
11688
|
dim3(
|
|
11095
11689
|
"Proposal includes source-derived component candidates, starter mechanical rules, and Hybrid authority guidance."
|
|
@@ -11098,7 +11692,7 @@ async function cmdCodifyWorkflow(args) {
|
|
|
11098
11692
|
}
|
|
11099
11693
|
console.log(
|
|
11100
11694
|
dim3(
|
|
11101
|
-
`Review
|
|
11695
|
+
`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)}\`.`
|
|
11102
11696
|
)
|
|
11103
11697
|
);
|
|
11104
11698
|
}
|
|
@@ -11153,7 +11747,7 @@ ${BOLD9}Usage:${RESET16}
|
|
|
11153
11747
|
decantr verify [--project <path>] [--brownfield] [--local-patterns] [health options]
|
|
11154
11748
|
decantr ci [--project <path>] [--workspace] [--fail-on error|warn|none]
|
|
11155
11749
|
decantr doctor [--project <path>] [--workspace] [--json]
|
|
11156
|
-
decantr codify [--from-audit] [--accept] [--project <path>]
|
|
11750
|
+
decantr codify [--from-audit] [--style-bridge] [--accept] [--project <path>]
|
|
11157
11751
|
decantr studio [--port 4319] [--host 127.0.0.1] [--report decantr-health.json] [--workspace]
|
|
11158
11752
|
|
|
11159
11753
|
${BOLD9}Advanced primitives:${RESET16}
|
|
@@ -11573,15 +12167,17 @@ ${BOLD9}Examples:${RESET16}
|
|
|
11573
12167
|
}
|
|
11574
12168
|
function cmdCodifyHelp() {
|
|
11575
12169
|
console.log(`
|
|
11576
|
-
${BOLD9}decantr codify${RESET16} \u2014 Propose or accept project-owned Brownfield UI
|
|
12170
|
+
${BOLD9}decantr codify${RESET16} \u2014 Propose or accept project-owned Brownfield UI law and style bridges
|
|
11577
12171
|
|
|
11578
12172
|
${BOLD9}Usage:${RESET16}
|
|
11579
|
-
decantr codify [--from-audit] [--project <path>]
|
|
12173
|
+
decantr codify [--from-audit] [--style-bridge] [--project <path>]
|
|
11580
12174
|
decantr codify --accept [--project <path>]
|
|
11581
12175
|
|
|
11582
12176
|
${BOLD9}Examples:${RESET16}
|
|
11583
12177
|
decantr codify
|
|
11584
12178
|
decantr codify --from-audit
|
|
12179
|
+
decantr codify --style-bridge
|
|
12180
|
+
decantr codify --from-audit --style-bridge
|
|
11585
12181
|
decantr codify --accept
|
|
11586
12182
|
decantr verify --brownfield --local-patterns
|
|
11587
12183
|
`);
|
|
@@ -11664,10 +12260,10 @@ async function main() {
|
|
|
11664
12260
|
if (command === "--version" || command === "-v" || command === "version") {
|
|
11665
12261
|
try {
|
|
11666
12262
|
const here = dirname6(fileURLToPath3(import.meta.url));
|
|
11667
|
-
const candidates = [
|
|
12263
|
+
const candidates = [join32(here, "..", "package.json"), join32(here, "..", "..", "package.json")];
|
|
11668
12264
|
for (const candidate of candidates) {
|
|
11669
|
-
if (
|
|
11670
|
-
const pkg = JSON.parse(
|
|
12265
|
+
if (existsSync30(candidate)) {
|
|
12266
|
+
const pkg = JSON.parse(readFileSync23(candidate, "utf-8"));
|
|
11671
12267
|
if (pkg.version) {
|
|
11672
12268
|
console.log(pkg.version);
|
|
11673
12269
|
return;
|
|
@@ -11820,7 +12416,7 @@ async function main() {
|
|
|
11820
12416
|
`${YELLOW12}Note: \`decantr heal\` is deprecated. Use \`decantr check\` instead.${RESET16}`
|
|
11821
12417
|
);
|
|
11822
12418
|
}
|
|
11823
|
-
const { cmdHeal } = await import("./heal-
|
|
12419
|
+
const { cmdHeal } = await import("./heal-WMSAX4LU.js");
|
|
11824
12420
|
const { flags } = parseLooseArgs(args);
|
|
11825
12421
|
const workspaceInfo = resolveWorkflowProject(flags, "check");
|
|
11826
12422
|
if (!workspaceInfo) break;
|
|
@@ -11845,7 +12441,7 @@ async function main() {
|
|
|
11845
12441
|
const { flags } = parseLooseArgs(args);
|
|
11846
12442
|
const workspaceInfo = resolveWorkflowProject(flags, "health");
|
|
11847
12443
|
if (!workspaceInfo) break;
|
|
11848
|
-
const { cmdHealth, parseHealthArgs } = await import("./health-
|
|
12444
|
+
const { cmdHealth, parseHealthArgs } = await import("./health-DBCUR6VQ.js");
|
|
11849
12445
|
await cmdHealth(workspaceInfo.appRoot, parseHealthArgs(stripProjectArgs(args)));
|
|
11850
12446
|
} catch (e) {
|
|
11851
12447
|
console.error(error2(e.message));
|
|
@@ -11873,7 +12469,7 @@ async function main() {
|
|
|
11873
12469
|
cmdStudioHelp();
|
|
11874
12470
|
break;
|
|
11875
12471
|
}
|
|
11876
|
-
const { cmdStudio, parseStudioArgs } = await import("./studio-
|
|
12472
|
+
const { cmdStudio, parseStudioArgs } = await import("./studio-L6JJQYCS.js");
|
|
11877
12473
|
await cmdStudio(process.cwd(), parseStudioArgs(args));
|
|
11878
12474
|
} catch (e) {
|
|
11879
12475
|
console.error(error2(e.message));
|
|
@@ -11887,7 +12483,7 @@ async function main() {
|
|
|
11887
12483
|
cmdWorkspaceHelp();
|
|
11888
12484
|
break;
|
|
11889
12485
|
}
|
|
11890
|
-
const { cmdWorkspace } = await import("./workspace-
|
|
12486
|
+
const { cmdWorkspace } = await import("./workspace-BCA5RCHN.js");
|
|
11891
12487
|
await cmdWorkspace(process.cwd(), args);
|
|
11892
12488
|
} catch (e) {
|
|
11893
12489
|
console.error(error2(e.message));
|
|
@@ -12004,7 +12600,7 @@ async function main() {
|
|
|
12004
12600
|
route,
|
|
12005
12601
|
file,
|
|
12006
12602
|
fromCode,
|
|
12007
|
-
projectRoot: workspaceInfo?.appRoot
|
|
12603
|
+
projectRoot: workspaceInfo?.appRoot ?? process.cwd()
|
|
12008
12604
|
});
|
|
12009
12605
|
break;
|
|
12010
12606
|
}
|
|
@@ -12206,7 +12802,7 @@ async function main() {
|
|
|
12206
12802
|
break;
|
|
12207
12803
|
}
|
|
12208
12804
|
if (packType === "page" && route && !id) {
|
|
12209
|
-
const resolvedPath = essencePath ? resolveUserPath(essencePath) :
|
|
12805
|
+
const resolvedPath = essencePath ? resolveUserPath(essencePath) : join32(process.cwd(), "decantr.essence.json");
|
|
12210
12806
|
id = resolvePagePackIdForRoute(resolvedPath, route);
|
|
12211
12807
|
}
|
|
12212
12808
|
await printHostedSelectedExecutionPack(
|