@decantr/cli 2.13.1 → 3.0.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 +28 -13
- package/dist/bin.js +4 -4
- package/dist/chunk-4NDOHCYY.js +4861 -0
- package/dist/{chunk-MNZKOZW7.js → chunk-7Z74ETDR.js} +5 -2
- package/dist/{chunk-YBSBAJ3E.js → chunk-B2PJDAMS.js} +1 -1
- package/dist/{chunk-5MZH6XXQ.js → chunk-GN63PIKK.js} +1079 -1579
- package/dist/{heal-IRIDB7IZ.js → heal-HHVVTVIH.js} +1 -1
- package/dist/{health-B4W7UJBZ.js → health-DDT4RABT.js} +8 -2
- package/dist/index.js +4 -4
- package/dist/{studio-JOEECLI6.js → studio-WDJ3BOCU.js} +3 -3
- package/dist/{workspace-7RU77ZZW.js → workspace-3XNCMZ5L.js} +3 -3
- package/package.json +11 -10
- package/src/templates/DECANTR.md.template +3 -3
- package/dist/chunk-3A2DLR47.js +0 -1565
|
@@ -22,14 +22,37 @@ import {
|
|
|
22
22
|
listWorkspaceCandidates,
|
|
23
23
|
listWorkspaceProjects,
|
|
24
24
|
shouldFailWorkspaceHealth
|
|
25
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-B2PJDAMS.js";
|
|
26
26
|
import {
|
|
27
|
+
acceptBrownfieldLocalLaw,
|
|
28
|
+
acceptStyleBridge,
|
|
29
|
+
buildGraphArtifacts,
|
|
30
|
+
changedFiles,
|
|
31
|
+
cmdGraph,
|
|
32
|
+
cmdGraphHelp,
|
|
33
|
+
createBrownfieldCodifyProposal,
|
|
34
|
+
createLocalLawTaskSummary,
|
|
27
35
|
createProjectHealthReport,
|
|
36
|
+
createStyleBridgeProposal,
|
|
37
|
+
createStyleBridgeTaskSummary,
|
|
28
38
|
formatProjectHealthMarkdown,
|
|
29
39
|
formatProjectHealthText,
|
|
40
|
+
localPatternsPath,
|
|
41
|
+
localPatternsProposalPath,
|
|
42
|
+
localRulesPath,
|
|
43
|
+
localRulesProposalPath,
|
|
44
|
+
readLocalPatternPack,
|
|
30
45
|
resolveWorkspaceInfo,
|
|
31
|
-
|
|
32
|
-
|
|
46
|
+
routeImpacts,
|
|
47
|
+
shouldFailHealth,
|
|
48
|
+
styleBridgeMatches,
|
|
49
|
+
styleBridgePath,
|
|
50
|
+
styleBridgeProposalPath,
|
|
51
|
+
validateLocalLaw,
|
|
52
|
+
writeBrownfieldCodifyProposal,
|
|
53
|
+
writeHostedPatternMappingProposal,
|
|
54
|
+
writeStyleBridgeProposal
|
|
55
|
+
} from "./chunk-4NDOHCYY.js";
|
|
33
56
|
import {
|
|
34
57
|
buildGuardRegistryContext,
|
|
35
58
|
createDoctrineMap,
|
|
@@ -46,13 +69,19 @@ import {
|
|
|
46
69
|
sendCliCommandTelemetry,
|
|
47
70
|
sendNewProjectCompletedTelemetry,
|
|
48
71
|
writeDoctrineMap
|
|
49
|
-
} from "./chunk-
|
|
72
|
+
} from "./chunk-7Z74ETDR.js";
|
|
50
73
|
|
|
51
74
|
// src/index.ts
|
|
52
|
-
import { existsSync as
|
|
53
|
-
import { basename as
|
|
75
|
+
import { existsSync as existsSync28, mkdirSync as mkdirSync15, readdirSync as readdirSync8, readFileSync as readFileSync21, writeFileSync as writeFileSync18 } from "fs";
|
|
76
|
+
import { basename as basename2, dirname as dirname6, isAbsolute as isAbsolute3, join as join30, relative as relative6, resolve as resolve4 } from "path";
|
|
54
77
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
55
|
-
import {
|
|
78
|
+
import {
|
|
79
|
+
buildGraphImpactContext,
|
|
80
|
+
buildGraphRouteContext,
|
|
81
|
+
graphPayloadString,
|
|
82
|
+
summarizeGraphDiff
|
|
83
|
+
} from "@decantr/core";
|
|
84
|
+
import { evaluateGuard, isV4 as isV48, validateEssence as validateEssence2 } from "@decantr/essence-spec";
|
|
56
85
|
import {
|
|
57
86
|
CONTENT_TYPE_TO_API_CONTENT_TYPE as CONTENT_TYPE_TO_API_CONTENT_TYPE3,
|
|
58
87
|
CONTENT_TYPES as GET_CONTENT_TYPES,
|
|
@@ -538,7 +567,10 @@ var solidPackagePlan = {
|
|
|
538
567
|
function writeReactViteProject(projectDir, title, routingMode) {
|
|
539
568
|
const srcDir = join4(projectDir, "src");
|
|
540
569
|
const routerImport = routingMode === "hash" ? "HashRouter" : "BrowserRouter";
|
|
541
|
-
writeJson(
|
|
570
|
+
writeJson(
|
|
571
|
+
join4(projectDir, "package.json"),
|
|
572
|
+
packagePlanPackageJson(projectDir, "decantr-app", reactVitePackagePlan)
|
|
573
|
+
);
|
|
542
574
|
writeFileSync4(
|
|
543
575
|
join4(projectDir, "vite.config.ts"),
|
|
544
576
|
`import { defineConfig } from 'vite';
|
|
@@ -550,7 +582,10 @@ export default defineConfig({
|
|
|
550
582
|
`
|
|
551
583
|
);
|
|
552
584
|
writeViteTsConfig(projectDir, { jsx: "react-jsx" });
|
|
553
|
-
writeJson(
|
|
585
|
+
writeJson(
|
|
586
|
+
join4(projectDir, "tsconfig.app.json"),
|
|
587
|
+
JSON.parse(readFileSync3(join4(projectDir, "tsconfig.json"), "utf-8"))
|
|
588
|
+
);
|
|
554
589
|
writeIndexHtml(projectDir, title, "/src/main.tsx");
|
|
555
590
|
mkdirp(srcDir);
|
|
556
591
|
writeFileSync4(
|
|
@@ -613,9 +648,18 @@ export function App() {
|
|
|
613
648
|
function writeNextProject(projectDir, title) {
|
|
614
649
|
const appDir = join4(projectDir, "app");
|
|
615
650
|
const stylesDir = join4(projectDir, "src", "styles");
|
|
616
|
-
writeJson(
|
|
617
|
-
|
|
618
|
-
|
|
651
|
+
writeJson(
|
|
652
|
+
join4(projectDir, "package.json"),
|
|
653
|
+
packagePlanPackageJson(projectDir, "decantr-next-app", nextPackagePlan)
|
|
654
|
+
);
|
|
655
|
+
writeFileSync4(
|
|
656
|
+
join4(projectDir, "next.config.ts"),
|
|
657
|
+
'import type { NextConfig } from "next";\n\nconst nextConfig: NextConfig = {};\n\nexport default nextConfig;\n'
|
|
658
|
+
);
|
|
659
|
+
writeFileSync4(
|
|
660
|
+
join4(projectDir, "next-env.d.ts"),
|
|
661
|
+
'/// <reference types="next" />\n/// <reference types="next/image-types/global" />\n\n// This file is generated by Next.js.\n'
|
|
662
|
+
);
|
|
619
663
|
writeJson(join4(projectDir, "tsconfig.json"), {
|
|
620
664
|
compilerOptions: {
|
|
621
665
|
target: "ES2017",
|
|
@@ -682,7 +726,10 @@ export default function HomePage() {
|
|
|
682
726
|
}
|
|
683
727
|
function writeVanillaProject(projectDir, title, routingMode) {
|
|
684
728
|
const srcDir = join4(projectDir, "src");
|
|
685
|
-
writeJson(
|
|
729
|
+
writeJson(
|
|
730
|
+
join4(projectDir, "package.json"),
|
|
731
|
+
packagePlanPackageJson(projectDir, "decantr-vanilla-app", vanillaPackagePlan)
|
|
732
|
+
);
|
|
686
733
|
writeIndexHtml(projectDir, title, "/src/main.js");
|
|
687
734
|
mkdirp(srcDir);
|
|
688
735
|
writeFileSync4(
|
|
@@ -718,14 +765,23 @@ root.innerHTML = \`
|
|
|
718
765
|
function writeVueProject(projectDir, title, routingMode) {
|
|
719
766
|
const srcDir = join4(projectDir, "src");
|
|
720
767
|
const historyFactory = routingMode === "hash" ? "createWebHashHistory" : "createWebHistory";
|
|
721
|
-
writeJson(
|
|
722
|
-
|
|
768
|
+
writeJson(
|
|
769
|
+
join4(projectDir, "package.json"),
|
|
770
|
+
packagePlanPackageJson(projectDir, "decantr-vue-app", vuePackagePlan)
|
|
771
|
+
);
|
|
772
|
+
writeFileSync4(
|
|
773
|
+
join4(projectDir, "vite.config.ts"),
|
|
774
|
+
`import { defineConfig } from 'vite';
|
|
723
775
|
import vue from '@vitejs/plugin-vue';
|
|
724
776
|
|
|
725
777
|
export default defineConfig({ plugins: [vue()] });
|
|
726
|
-
`
|
|
778
|
+
`
|
|
779
|
+
);
|
|
727
780
|
writeViteTsConfig(projectDir);
|
|
728
|
-
writeFileSync4(
|
|
781
|
+
writeFileSync4(
|
|
782
|
+
join4(projectDir, "env.d.ts"),
|
|
783
|
+
'/// <reference types="vite/client" />\n\ndeclare module "*.vue";\n'
|
|
784
|
+
);
|
|
729
785
|
writeIndexHtml(projectDir, title, "/src/main.ts");
|
|
730
786
|
mkdirp(srcDir);
|
|
731
787
|
writeFileSync4(
|
|
@@ -773,21 +829,35 @@ import { css } from '@decantr/css';
|
|
|
773
829
|
}
|
|
774
830
|
function writeSvelteProject(projectDir, title) {
|
|
775
831
|
const routesDir = join4(projectDir, "src", "routes");
|
|
776
|
-
writeJson(
|
|
777
|
-
|
|
832
|
+
writeJson(
|
|
833
|
+
join4(projectDir, "package.json"),
|
|
834
|
+
packagePlanPackageJson(projectDir, "decantr-svelte-app", sveltePackagePlan)
|
|
835
|
+
);
|
|
836
|
+
writeFileSync4(
|
|
837
|
+
join4(projectDir, "svelte.config.js"),
|
|
838
|
+
`import adapter from '@sveltejs/adapter-auto';
|
|
778
839
|
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
|
|
779
840
|
|
|
780
841
|
export default { preprocess: vitePreprocess(), kit: { adapter: adapter() } };
|
|
781
|
-
`
|
|
782
|
-
|
|
842
|
+
`
|
|
843
|
+
);
|
|
844
|
+
writeFileSync4(
|
|
845
|
+
join4(projectDir, "vite.config.ts"),
|
|
846
|
+
`import { sveltekit } from '@sveltejs/kit/vite';
|
|
783
847
|
import { defineConfig } from 'vite';
|
|
784
848
|
|
|
785
849
|
export default defineConfig({ plugins: [sveltekit()] });
|
|
786
|
-
`
|
|
850
|
+
`
|
|
851
|
+
);
|
|
787
852
|
writeViteTsConfig(projectDir);
|
|
788
853
|
mkdirp(routesDir);
|
|
789
|
-
writeFileSync4(
|
|
790
|
-
|
|
854
|
+
writeFileSync4(
|
|
855
|
+
join4(projectDir, "src", "app.html"),
|
|
856
|
+
'<!doctype html>\n<html lang="en">\n <head>\n <meta charset="utf-8" />\n <meta name="viewport" content="width=device-width, initial-scale=1" />\n %sveltekit.head%\n </head>\n <body data-sveltekit-preload-data="hover">\n <div style="display: contents">%sveltekit.body%</div>\n </body>\n</html>\n'
|
|
857
|
+
);
|
|
858
|
+
writeFileSync4(
|
|
859
|
+
join4(routesDir, "+layout.svelte"),
|
|
860
|
+
`<script lang="ts">
|
|
791
861
|
import '../styles/global.css';
|
|
792
862
|
import '../styles/tokens.css';
|
|
793
863
|
import '../styles/treatments.css';
|
|
@@ -795,7 +865,8 @@ let { children } = $props();
|
|
|
795
865
|
</script>
|
|
796
866
|
|
|
797
867
|
{@render children()}
|
|
798
|
-
`
|
|
868
|
+
`
|
|
869
|
+
);
|
|
799
870
|
writeFileSync4(
|
|
800
871
|
join4(routesDir, "+page.svelte"),
|
|
801
872
|
`<script lang="ts">
|
|
@@ -823,7 +894,10 @@ import { css } from '@decantr/css';
|
|
|
823
894
|
}
|
|
824
895
|
function writeAngularProject(projectDir, title) {
|
|
825
896
|
const appDir = join4(projectDir, "src", "app");
|
|
826
|
-
writeJson(
|
|
897
|
+
writeJson(
|
|
898
|
+
join4(projectDir, "package.json"),
|
|
899
|
+
packagePlanPackageJson(projectDir, "decantr-angular-app", angularPackagePlan)
|
|
900
|
+
);
|
|
827
901
|
writeJson(join4(projectDir, "angular.json"), {
|
|
828
902
|
version: 1,
|
|
829
903
|
projects: {
|
|
@@ -862,27 +936,40 @@ function writeAngularProject(projectDir, title) {
|
|
|
862
936
|
lib: ["ES2022", "DOM"]
|
|
863
937
|
}
|
|
864
938
|
});
|
|
865
|
-
writeJson(join4(projectDir, "tsconfig.app.json"), {
|
|
939
|
+
writeJson(join4(projectDir, "tsconfig.app.json"), {
|
|
940
|
+
extends: "./tsconfig.json",
|
|
941
|
+
files: ["src/main.ts"],
|
|
942
|
+
include: ["src/**/*.d.ts"]
|
|
943
|
+
});
|
|
866
944
|
mkdirp(appDir);
|
|
867
|
-
writeFileSync4(
|
|
945
|
+
writeFileSync4(
|
|
946
|
+
join4(projectDir, "src", "index.html"),
|
|
947
|
+
`<!doctype html>
|
|
868
948
|
<html lang="en">
|
|
869
949
|
<head><meta charset="utf-8"><title>${title}</title><meta name="viewport" content="width=device-width, initial-scale=1"></head>
|
|
870
950
|
<body><app-root></app-root></body>
|
|
871
951
|
</html>
|
|
872
|
-
`
|
|
873
|
-
|
|
952
|
+
`
|
|
953
|
+
);
|
|
954
|
+
writeFileSync4(
|
|
955
|
+
join4(projectDir, "src", "main.ts"),
|
|
956
|
+
`import { bootstrapApplication } from '@angular/platform-browser';
|
|
874
957
|
import { provideRouter } from '@angular/router';
|
|
875
958
|
import { AppComponent } from './app/app.component';
|
|
876
959
|
import { routes } from './app/app.routes';
|
|
877
960
|
|
|
878
961
|
bootstrapApplication(AppComponent, { providers: [provideRouter(routes)] }).catch((err) => console.error(err));
|
|
879
|
-
`
|
|
962
|
+
`
|
|
963
|
+
);
|
|
880
964
|
writeFileSync4(join4(projectDir, "src", "styles.css"), '@import "./styles/global.css";\n');
|
|
881
|
-
writeFileSync4(
|
|
965
|
+
writeFileSync4(
|
|
966
|
+
join4(appDir, "app.routes.ts"),
|
|
967
|
+
`import type { Routes } from '@angular/router';
|
|
882
968
|
import { AppComponent } from './app.component';
|
|
883
969
|
|
|
884
970
|
export const routes: Routes = [{ path: '', component: AppComponent }];
|
|
885
|
-
`
|
|
971
|
+
`
|
|
972
|
+
);
|
|
886
973
|
writeFileSync4(
|
|
887
974
|
join4(appDir, "app.component.ts"),
|
|
888
975
|
`import { Component } from '@angular/core';
|
|
@@ -912,14 +999,23 @@ export class AppComponent {}
|
|
|
912
999
|
function writeSolidProject(projectDir, title, routingMode) {
|
|
913
1000
|
const srcDir = join4(projectDir, "src");
|
|
914
1001
|
const routerImport = routingMode === "hash" ? "HashRouter" : "Router";
|
|
915
|
-
writeJson(
|
|
916
|
-
|
|
1002
|
+
writeJson(
|
|
1003
|
+
join4(projectDir, "package.json"),
|
|
1004
|
+
packagePlanPackageJson(projectDir, "decantr-solid-app", solidPackagePlan)
|
|
1005
|
+
);
|
|
1006
|
+
writeFileSync4(
|
|
1007
|
+
join4(projectDir, "vite.config.ts"),
|
|
1008
|
+
`import { defineConfig } from 'vite';
|
|
917
1009
|
import solid from 'vite-plugin-solid';
|
|
918
1010
|
|
|
919
1011
|
export default defineConfig({ plugins: [solid()] });
|
|
920
|
-
`
|
|
1012
|
+
`
|
|
1013
|
+
);
|
|
921
1014
|
writeViteTsConfig(projectDir, { jsx: "preserve" });
|
|
922
|
-
writeJson(
|
|
1015
|
+
writeJson(
|
|
1016
|
+
join4(projectDir, "tsconfig.app.json"),
|
|
1017
|
+
JSON.parse(readFileSync3(join4(projectDir, "tsconfig.json"), "utf-8"))
|
|
1018
|
+
);
|
|
923
1019
|
writeIndexHtml(projectDir, title, "/src/main.tsx");
|
|
924
1020
|
mkdirp(srcDir);
|
|
925
1021
|
writeFileSync4(
|
|
@@ -990,7 +1086,10 @@ var DECANTR_ADAPTERS = {
|
|
|
990
1086
|
},
|
|
991
1087
|
verify: { devCommand: "npm run dev", buildCommand: "npm run build", distDir: "dist" },
|
|
992
1088
|
packagePlan: reactVitePackagePlan,
|
|
993
|
-
docs: {
|
|
1089
|
+
docs: {
|
|
1090
|
+
summary: "React + Vite runnable starter.",
|
|
1091
|
+
notes: ["Uses React Router and @decantr/css."]
|
|
1092
|
+
},
|
|
994
1093
|
writeProjectFiles: writeReactViteProject
|
|
995
1094
|
},
|
|
996
1095
|
"next-app": {
|
|
@@ -1009,7 +1108,10 @@ var DECANTR_ADAPTERS = {
|
|
|
1009
1108
|
},
|
|
1010
1109
|
verify: { devCommand: "npm run dev", buildCommand: "npm run build", distDir: ".next" },
|
|
1011
1110
|
packagePlan: nextPackagePlan,
|
|
1012
|
-
docs: {
|
|
1111
|
+
docs: {
|
|
1112
|
+
summary: "Next.js App Router runnable starter.",
|
|
1113
|
+
notes: ["Uses App Router for greenfield bootstraps."]
|
|
1114
|
+
},
|
|
1013
1115
|
writeProjectFiles: writeNextProject
|
|
1014
1116
|
},
|
|
1015
1117
|
"vanilla-vite": {
|
|
@@ -1028,7 +1130,10 @@ var DECANTR_ADAPTERS = {
|
|
|
1028
1130
|
},
|
|
1029
1131
|
verify: { devCommand: "npm run dev", buildCommand: "npm run build", distDir: "dist" },
|
|
1030
1132
|
packagePlan: vanillaPackagePlan,
|
|
1031
|
-
docs: {
|
|
1133
|
+
docs: {
|
|
1134
|
+
summary: "Plain web runnable starter.",
|
|
1135
|
+
notes: ["Uses DOM APIs and @decantr/css without a UI framework."]
|
|
1136
|
+
},
|
|
1032
1137
|
writeProjectFiles: writeVanillaProject
|
|
1033
1138
|
},
|
|
1034
1139
|
"vue-vite": {
|
|
@@ -1047,7 +1152,10 @@ var DECANTR_ADAPTERS = {
|
|
|
1047
1152
|
},
|
|
1048
1153
|
verify: { devCommand: "npm run dev", buildCommand: "npm run build", distDir: "dist" },
|
|
1049
1154
|
packagePlan: vuePackagePlan,
|
|
1050
|
-
docs: {
|
|
1155
|
+
docs: {
|
|
1156
|
+
summary: "Vue 3 + Vite runnable starter.",
|
|
1157
|
+
notes: ["Uses Vue Router and @decantr/css."]
|
|
1158
|
+
},
|
|
1051
1159
|
writeProjectFiles: writeVueProject
|
|
1052
1160
|
},
|
|
1053
1161
|
sveltekit: {
|
|
@@ -1066,7 +1174,10 @@ var DECANTR_ADAPTERS = {
|
|
|
1066
1174
|
},
|
|
1067
1175
|
verify: { devCommand: "npm run dev", buildCommand: "npm run build", distDir: ".svelte-kit" },
|
|
1068
1176
|
packagePlan: sveltePackagePlan,
|
|
1069
|
-
docs: {
|
|
1177
|
+
docs: {
|
|
1178
|
+
summary: "SvelteKit runnable starter.",
|
|
1179
|
+
notes: ["Uses SvelteKit file routing and @decantr/css."]
|
|
1180
|
+
},
|
|
1070
1181
|
writeProjectFiles: writeSvelteProject
|
|
1071
1182
|
},
|
|
1072
1183
|
angular: {
|
|
@@ -1085,7 +1196,10 @@ var DECANTR_ADAPTERS = {
|
|
|
1085
1196
|
},
|
|
1086
1197
|
verify: { devCommand: "npm run dev", buildCommand: "npm run build", distDir: "dist" },
|
|
1087
1198
|
packagePlan: angularPackagePlan,
|
|
1088
|
-
docs: {
|
|
1199
|
+
docs: {
|
|
1200
|
+
summary: "Angular standalone runnable starter.",
|
|
1201
|
+
notes: ["Uses Angular Router and global Decantr CSS files."]
|
|
1202
|
+
},
|
|
1089
1203
|
writeProjectFiles: writeAngularProject
|
|
1090
1204
|
},
|
|
1091
1205
|
"solid-vite": {
|
|
@@ -1104,7 +1218,10 @@ var DECANTR_ADAPTERS = {
|
|
|
1104
1218
|
},
|
|
1105
1219
|
verify: { devCommand: "npm run dev", buildCommand: "npm run build", distDir: "dist" },
|
|
1106
1220
|
packagePlan: solidPackagePlan,
|
|
1107
|
-
docs: {
|
|
1221
|
+
docs: {
|
|
1222
|
+
summary: "Solid + Vite runnable starter.",
|
|
1223
|
+
notes: ["Uses @solidjs/router and @decantr/css."]
|
|
1224
|
+
},
|
|
1108
1225
|
writeProjectFiles: writeSolidProject
|
|
1109
1226
|
},
|
|
1110
1227
|
"generic-web": {
|
|
@@ -1122,7 +1239,10 @@ var DECANTR_ADAPTERS = {
|
|
|
1122
1239
|
componentRoots: ["src/components", "components"]
|
|
1123
1240
|
},
|
|
1124
1241
|
verify: { devCommand: "npm run dev", buildCommand: "npm run build", distDir: "dist" },
|
|
1125
|
-
docs: {
|
|
1242
|
+
docs: {
|
|
1243
|
+
summary: "Contract-only fallback for unsupported web targets.",
|
|
1244
|
+
notes: ["Does not write framework runtime files."]
|
|
1245
|
+
}
|
|
1126
1246
|
}
|
|
1127
1247
|
};
|
|
1128
1248
|
var TARGET_ALIASES = /* @__PURE__ */ new Map();
|
|
@@ -3235,1107 +3355,8 @@ ${YELLOW2}Next step:${RESET2} Review ${BOLD}${reportDisplayPath}${RESET2}, then
|
|
|
3235
3355
|
}
|
|
3236
3356
|
|
|
3237
3357
|
// src/commands/ci.ts
|
|
3238
|
-
import { existsSync as
|
|
3239
|
-
import { dirname as dirname3, join as
|
|
3240
|
-
|
|
3241
|
-
// src/local-law.ts
|
|
3242
|
-
import { execFileSync } from "child_process";
|
|
3243
|
-
import { existsSync as existsSync13, mkdirSync as mkdirSync8, readdirSync as readdirSync5, readFileSync as readFileSync11, statSync as statSync5, writeFileSync as writeFileSync9 } from "fs";
|
|
3244
|
-
import { basename as basename2, extname, join as join15, relative as relative3, sep } from "path";
|
|
3245
|
-
import { isV4 as isV43 } from "@decantr/essence-spec";
|
|
3246
|
-
var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
3247
|
-
".astro",
|
|
3248
|
-
".html",
|
|
3249
|
-
".js",
|
|
3250
|
-
".jsx",
|
|
3251
|
-
".svelte",
|
|
3252
|
-
".ts",
|
|
3253
|
-
".tsx",
|
|
3254
|
-
".vue"
|
|
3255
|
-
]);
|
|
3256
|
-
var UI_TEMPLATE_EXTENSIONS = /* @__PURE__ */ new Set([".astro", ".html", ".jsx", ".svelte", ".tsx", ".vue"]);
|
|
3257
|
-
var IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
3258
|
-
".decantr",
|
|
3259
|
-
".git",
|
|
3260
|
-
".next",
|
|
3261
|
-
".nuxt",
|
|
3262
|
-
".svelte-kit",
|
|
3263
|
-
"build",
|
|
3264
|
-
"coverage",
|
|
3265
|
-
"dist",
|
|
3266
|
-
"node_modules",
|
|
3267
|
-
"out"
|
|
3268
|
-
]);
|
|
3269
|
-
var DEFAULT_RULE_EXTENSIONS = [".astro", ".html", ".jsx", ".svelte", ".tsx", ".vue"];
|
|
3270
|
-
function localPatternsProposalPath(projectRoot) {
|
|
3271
|
-
return join15(projectRoot, ".decantr", "local-patterns.proposal.json");
|
|
3272
|
-
}
|
|
3273
|
-
function localPatternsPath(projectRoot) {
|
|
3274
|
-
return join15(projectRoot, ".decantr", "local-patterns.json");
|
|
3275
|
-
}
|
|
3276
|
-
function localRulesProposalPath(projectRoot) {
|
|
3277
|
-
return join15(projectRoot, ".decantr", "rules.proposal.json");
|
|
3278
|
-
}
|
|
3279
|
-
function localRulesPath(projectRoot) {
|
|
3280
|
-
return join15(projectRoot, ".decantr", "rules.json");
|
|
3281
|
-
}
|
|
3282
|
-
function readLocalPatternPack(projectRoot) {
|
|
3283
|
-
return readJsonFile(localPatternsPath(projectRoot));
|
|
3284
|
-
}
|
|
3285
|
-
function readLocalRuleManifest(projectRoot) {
|
|
3286
|
-
return readJsonFile(localRulesPath(projectRoot));
|
|
3287
|
-
}
|
|
3288
|
-
function createBrownfieldCodifyProposal(input) {
|
|
3289
|
-
const sourceFiles = input.fromAudit ? listSourceFiles(input.projectRoot, 800) : [];
|
|
3290
|
-
const evidence = summarizeSourceEvidence(input.projectRoot, sourceFiles);
|
|
3291
|
-
const routes = input.essence && isV43(input.essence) ? Object.keys(input.essence.blueprint.routes ?? {}).sort() : [];
|
|
3292
|
-
const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3293
|
-
const patternPack = {
|
|
3294
|
-
version: 2,
|
|
3295
|
-
generatedAt,
|
|
3296
|
-
status: "proposal",
|
|
3297
|
-
source: input.fromAudit ? "decantr codify --from-audit" : "decantr codify",
|
|
3298
|
-
project: {
|
|
3299
|
-
framework: input.detected.framework,
|
|
3300
|
-
packageManager: input.detected.packageManager,
|
|
3301
|
-
hasTailwind: input.detected.hasTailwind,
|
|
3302
|
-
ruleFiles: input.detected.existingRuleFiles,
|
|
3303
|
-
routeCount: routes.length
|
|
3304
|
-
},
|
|
3305
|
-
purpose: "Project-owned Brownfield/Hybrid UI law. Review and edit before accepting; Decantr treats this as authoritative only after it is copied to .decantr/local-patterns.json.",
|
|
3306
|
-
hybrid: {
|
|
3307
|
-
intent: "This local pattern pack is the first Hybrid authority layer: it maps Decantr concepts onto project-owned components, tokens, classes, and rules without replacing the app runtime.",
|
|
3308
|
-
authorityPrecedence: [
|
|
3309
|
-
"existing production source",
|
|
3310
|
-
"accepted local patterns and rules",
|
|
3311
|
-
"Decantr Essence V4 contract",
|
|
3312
|
-
"hosted registry patterns and execution packs as optional guidance"
|
|
3313
|
-
],
|
|
3314
|
-
hostedPatternMapping: "Use hosted patterns as vocabulary and review guidance. Before enforcing one, map it to a project-owned component path, class recipe, token recipe, or explicit exception here."
|
|
3315
|
-
},
|
|
3316
|
-
patterns: [
|
|
3317
|
-
{
|
|
3318
|
-
id: "button",
|
|
3319
|
-
label: "Button primitives",
|
|
3320
|
-
role: "Actions and command triggers",
|
|
3321
|
-
appliesTo: [
|
|
3322
|
-
"primary action",
|
|
3323
|
-
"secondary action",
|
|
3324
|
-
"tertiary action",
|
|
3325
|
-
"destructive action",
|
|
3326
|
-
"icon action"
|
|
3327
|
-
],
|
|
3328
|
-
componentPaths: evidence.buttonComponents,
|
|
3329
|
-
decide: "Define primary, secondary, tertiary, destructive, icon-only, disabled, and loading button variants from this app.",
|
|
3330
|
-
classHints: evidence.buttonClassHints,
|
|
3331
|
-
variants: evidence.buttonVariants,
|
|
3332
|
-
sourceEvidence: evidence.buttonSourceEvidence,
|
|
3333
|
-
confidence: confidenceForEvidence({
|
|
3334
|
-
componentCount: evidence.buttonComponents.length,
|
|
3335
|
-
classHintCount: evidence.buttonClassHints.length,
|
|
3336
|
-
variantCount: evidence.buttonVariants.length,
|
|
3337
|
-
sourceEvidenceCount: evidence.buttonSourceEvidence.length
|
|
3338
|
-
}),
|
|
3339
|
-
enforcement: {
|
|
3340
|
-
level: "warn",
|
|
3341
|
-
status: "proposal",
|
|
3342
|
-
notes: [
|
|
3343
|
-
"Treat as advisory until the team confirms variant names and wrapper paths.",
|
|
3344
|
-
"Raise related rules to error only after raw button usage can be fixed without churn."
|
|
3345
|
-
]
|
|
3346
|
-
},
|
|
3347
|
-
evidence: evidence.buttonComponents.length ? evidence.buttonComponents : evidence.buttonClassHints.length ? evidence.buttonClassHints : [
|
|
3348
|
-
"No obvious Button wrapper found yet. Add the project-owned wrapper path before strict enforcement."
|
|
3349
|
-
],
|
|
3350
|
-
forbiddenAlternatives: ["New one-off button variants without updating this manifest."]
|
|
3351
|
-
},
|
|
3352
|
-
{
|
|
3353
|
-
id: "surface-card",
|
|
3354
|
-
label: "Cards and surfaces",
|
|
3355
|
-
role: "Cards, panels, and reusable content surfaces",
|
|
3356
|
-
appliesTo: ["cards", "panels", "modals", "list items", "dashboard tiles"],
|
|
3357
|
-
componentPaths: evidence.cardComponents,
|
|
3358
|
-
decide: "Define the canonical card background, border, radius, shadow, padding, density, and hover treatment.",
|
|
3359
|
-
classHints: evidence.cardClassHints,
|
|
3360
|
-
variants: evidence.cardVariants,
|
|
3361
|
-
sourceEvidence: evidence.cardSourceEvidence,
|
|
3362
|
-
confidence: confidenceForEvidence({
|
|
3363
|
-
componentCount: evidence.cardComponents.length,
|
|
3364
|
-
classHintCount: evidence.cardClassHints.length,
|
|
3365
|
-
variantCount: evidence.cardVariants.length,
|
|
3366
|
-
sourceEvidenceCount: evidence.cardSourceEvidence.length
|
|
3367
|
-
}),
|
|
3368
|
-
enforcement: {
|
|
3369
|
-
level: "warn",
|
|
3370
|
-
status: "proposal",
|
|
3371
|
-
notes: [
|
|
3372
|
-
"Use this family to converge repeated surface recipes before enforcing card rules.",
|
|
3373
|
-
"Document route-specific card exceptions before asking CI to block them."
|
|
3374
|
-
]
|
|
3375
|
-
},
|
|
3376
|
-
evidence: evidence.cardComponents.length ? evidence.cardComponents : [
|
|
3377
|
-
"No obvious Card wrapper found yet. Add the project-owned wrapper path or class recipe."
|
|
3378
|
-
],
|
|
3379
|
-
forbiddenAlternatives: ["Flat ad hoc cards with unique color/radius/shadow recipes."]
|
|
3380
|
-
},
|
|
3381
|
-
{
|
|
3382
|
-
id: "page-shell",
|
|
3383
|
-
label: "Page shell and spacing",
|
|
3384
|
-
role: "Route shell, navigation, gutters, max-width, and scroll ownership",
|
|
3385
|
-
appliesTo: ["routes", "layouts", "navigation shells", "scroll containers"],
|
|
3386
|
-
componentPaths: evidence.shellComponents,
|
|
3387
|
-
decide: "Define which layout owns max width, gutters, sticky chrome, responsive breakpoints, and scroll containers.",
|
|
3388
|
-
sourceEvidence: collectSourceEvidence(input.projectRoot, sourceFiles, [
|
|
3389
|
-
"layout",
|
|
3390
|
-
"shell",
|
|
3391
|
-
"nav",
|
|
3392
|
-
"sidebar",
|
|
3393
|
-
"container"
|
|
3394
|
-
]),
|
|
3395
|
-
confidence: confidenceForEvidence({
|
|
3396
|
-
componentCount: evidence.shellComponents.length,
|
|
3397
|
-
sourceEvidenceCount: evidence.shellComponents.length
|
|
3398
|
-
}),
|
|
3399
|
-
enforcement: {
|
|
3400
|
-
level: "advisory",
|
|
3401
|
-
status: "proposal",
|
|
3402
|
-
notes: [
|
|
3403
|
-
"Layout ownership is usually reviewed by humans first because route shells can legitimately differ."
|
|
3404
|
-
]
|
|
3405
|
-
},
|
|
3406
|
-
evidence: evidence.shellComponents.length ? evidence.shellComponents : ["Add root layout, shell, or app frame files that establish route chrome and spacing."],
|
|
3407
|
-
forbiddenAlternatives: [
|
|
3408
|
-
"Each page inventing independent max-width, padding, or sticky nav rules."
|
|
3409
|
-
]
|
|
3410
|
-
},
|
|
3411
|
-
{
|
|
3412
|
-
id: "form-control",
|
|
3413
|
-
label: "Form controls",
|
|
3414
|
-
role: "Inputs, labels, validation, and form actions",
|
|
3415
|
-
appliesTo: ["inputs", "selects", "textareas", "validation messages", "form actions"],
|
|
3416
|
-
componentPaths: evidence.formComponents,
|
|
3417
|
-
decide: "Define input height, label placement, error copy, disabled state, required state, and focus treatment.",
|
|
3418
|
-
variants: evidence.formVariants,
|
|
3419
|
-
sourceEvidence: evidence.formSourceEvidence,
|
|
3420
|
-
confidence: confidenceForEvidence({
|
|
3421
|
-
componentCount: evidence.formComponents.length,
|
|
3422
|
-
variantCount: evidence.formVariants.length,
|
|
3423
|
-
sourceEvidenceCount: evidence.formSourceEvidence.length
|
|
3424
|
-
}),
|
|
3425
|
-
enforcement: {
|
|
3426
|
-
level: "warn",
|
|
3427
|
-
status: "proposal",
|
|
3428
|
-
notes: [
|
|
3429
|
-
"Start by mapping field wrappers, labels, error states, and disabled states from source."
|
|
3430
|
-
]
|
|
3431
|
-
},
|
|
3432
|
-
evidence: evidence.formComponents.length ? evidence.formComponents : ["Add form field wrapper paths and validation examples."],
|
|
3433
|
-
forbiddenAlternatives: [
|
|
3434
|
-
"Unlabeled one-off inputs or validation states that do not match the app standard."
|
|
3435
|
-
]
|
|
3436
|
-
},
|
|
3437
|
-
{
|
|
3438
|
-
id: "theme-variant",
|
|
3439
|
-
label: "Theme variants",
|
|
3440
|
-
role: "Light, dark, brand, density, and tenant/theme variants observed in the app",
|
|
3441
|
-
appliesTo: ["theme toggles", "mode-specific classes", "brand variants", "tenant variants"],
|
|
3442
|
-
componentPaths: evidence.themeComponents,
|
|
3443
|
-
decide: "Document which theme variants exist, where they are toggled, and which tokens/classes are legal per variant.",
|
|
3444
|
-
variants: evidence.themeVariants,
|
|
3445
|
-
sourceEvidence: evidence.themeSourceEvidence,
|
|
3446
|
-
confidence: confidenceForEvidence({
|
|
3447
|
-
componentCount: evidence.themeComponents.length,
|
|
3448
|
-
variantCount: evidence.themeVariants.length,
|
|
3449
|
-
sourceEvidenceCount: evidence.themeSourceEvidence.length
|
|
3450
|
-
}),
|
|
3451
|
-
enforcement: {
|
|
3452
|
-
level: "advisory",
|
|
3453
|
-
status: "proposal",
|
|
3454
|
-
notes: [
|
|
3455
|
-
"Theme variant law should preserve the existing theme provider/tokens before adding any new mode."
|
|
3456
|
-
]
|
|
3457
|
-
},
|
|
3458
|
-
evidence: evidence.themeComponents.length ? evidence.themeComponents : ["If the app has dark/light or brand variants, add the toggles/providers here."],
|
|
3459
|
-
forbiddenAlternatives: [
|
|
3460
|
-
"Component-local theme forks that bypass shared theme providers or tokens."
|
|
3461
|
-
]
|
|
3462
|
-
}
|
|
3463
|
-
],
|
|
3464
|
-
starterRules: [
|
|
3465
|
-
"Prefer project-owned wrappers for repeated primitives once they exist.",
|
|
3466
|
-
"Avoid raw hex/rgb values in component templates unless documented as dynamic data.",
|
|
3467
|
-
"Avoid static inline styles for reusable visual treatment.",
|
|
3468
|
-
"When adding a new route, map it to an existing local pattern before inventing a new visual variant.",
|
|
3469
|
-
"When adding a theme variant, update .decantr/theme-inventory.json and this local pattern pack.",
|
|
3470
|
-
"Map hosted Decantr patterns into project-owned local law before making them enforceable."
|
|
3471
|
-
],
|
|
3472
|
-
nextSteps: [
|
|
3473
|
-
"Edit this proposal with real component paths and token/class recipes.",
|
|
3474
|
-
"Run decantr codify --accept after review.",
|
|
3475
|
-
"Use decantr task <route> before LLM edits so local law appears in task context.",
|
|
3476
|
-
"Run decantr verify --brownfield --local-patterns after edits.",
|
|
3477
|
-
"For Hybrid adoption, start with warn-level local rules and raise severities only after the team agrees the law is stable.",
|
|
3478
|
-
"Wire deterministic project rules into ESLint, Biome, Storybook, visual tests, or CI where Decantr should not guess."
|
|
3479
|
-
]
|
|
3480
|
-
};
|
|
3481
|
-
const ruleManifest = {
|
|
3482
|
-
version: 1,
|
|
3483
|
-
status: "proposal",
|
|
3484
|
-
generatedAt,
|
|
3485
|
-
source: input.fromAudit ? "decantr codify --from-audit" : "decantr codify",
|
|
3486
|
-
purpose: "Mechanical Brownfield/Hybrid checks owned by this project. These rules are intentionally local and stack-agnostic; edit before accepting.",
|
|
3487
|
-
enforcement: {
|
|
3488
|
-
defaultSeverity: "warn",
|
|
3489
|
-
mode: "warn",
|
|
3490
|
-
notes: [
|
|
3491
|
-
"Decantr local rules are a guardrail, not a replacement for ESLint, Biome, type checks, tests, or visual regression.",
|
|
3492
|
-
"Keep rules narrow enough that an LLM can fix findings without rewriting the app.",
|
|
3493
|
-
"Use error severity only after the team agrees the rule is stable."
|
|
3494
|
-
]
|
|
3495
|
-
},
|
|
3496
|
-
rules: [
|
|
3497
|
-
{
|
|
3498
|
-
id: "no-inline-style",
|
|
3499
|
-
type: "forbid-regex",
|
|
3500
|
-
enabled: true,
|
|
3501
|
-
severity: "warn",
|
|
3502
|
-
description: "Reusable UI should not add static inline style attributes.",
|
|
3503
|
-
includeExtensions: DEFAULT_RULE_EXTENSIONS,
|
|
3504
|
-
pattern: "\\bstyle\\s*=",
|
|
3505
|
-
message: "Inline style found in UI template.",
|
|
3506
|
-
suggestedFix: "Move reusable visual treatment into the project style system, component wrapper, token, or documented local pattern.",
|
|
3507
|
-
maxFindings: 25
|
|
3508
|
-
},
|
|
3509
|
-
{
|
|
3510
|
-
id: "no-raw-color-literals",
|
|
3511
|
-
type: "forbid-regex",
|
|
3512
|
-
enabled: true,
|
|
3513
|
-
severity: "warn",
|
|
3514
|
-
description: "Component templates should not introduce raw hex/rgb color literals.",
|
|
3515
|
-
includeExtensions: DEFAULT_RULE_EXTENSIONS,
|
|
3516
|
-
pattern: "#(?:[0-9a-fA-F]{3,8})\\b|rgba?\\s*\\(",
|
|
3517
|
-
message: "Raw color literal found in UI template.",
|
|
3518
|
-
suggestedFix: "Use an existing project token/class, or document the exception in .decantr/local-patterns.json if the value is data-driven.",
|
|
3519
|
-
maxFindings: 25
|
|
3520
|
-
},
|
|
3521
|
-
{
|
|
3522
|
-
id: "prefer-button-wrapper",
|
|
3523
|
-
type: "forbid-regex",
|
|
3524
|
-
enabled: evidence.buttonComponents.length > 0,
|
|
3525
|
-
severity: "info",
|
|
3526
|
-
description: "Prefer the project-owned button primitive instead of new raw button markup.",
|
|
3527
|
-
includeExtensions: DEFAULT_RULE_EXTENSIONS,
|
|
3528
|
-
pattern: "<button[\\s>]",
|
|
3529
|
-
message: "Raw <button> usage found outside the detected button wrapper.",
|
|
3530
|
-
suggestedFix: "Use the project-owned Button primitive, or add this file to allowedPaths if it is the primitive implementation.",
|
|
3531
|
-
allowedPaths: evidence.buttonComponents,
|
|
3532
|
-
maxFindings: 50
|
|
3533
|
-
}
|
|
3534
|
-
]
|
|
3535
|
-
};
|
|
3536
|
-
return { patternPack, ruleManifest };
|
|
3537
|
-
}
|
|
3538
|
-
function writeBrownfieldCodifyProposal(projectRoot, proposal) {
|
|
3539
|
-
const decantrDir = join15(projectRoot, ".decantr");
|
|
3540
|
-
mkdirSync8(decantrDir, { recursive: true });
|
|
3541
|
-
const patternPath = localPatternsProposalPath(projectRoot);
|
|
3542
|
-
const rulesPath = localRulesProposalPath(projectRoot);
|
|
3543
|
-
writeFileSync9(patternPath, `${JSON.stringify(proposal.patternPack, null, 2)}
|
|
3544
|
-
`, "utf-8");
|
|
3545
|
-
writeFileSync9(rulesPath, `${JSON.stringify(proposal.ruleManifest, null, 2)}
|
|
3546
|
-
`, "utf-8");
|
|
3547
|
-
return { patternPath, rulesPath };
|
|
3548
|
-
}
|
|
3549
|
-
function writeHostedPatternMappingProposal(input) {
|
|
3550
|
-
const decantrDir = join15(input.projectRoot, ".decantr");
|
|
3551
|
-
mkdirSync8(decantrDir, { recursive: true });
|
|
3552
|
-
const existing = readJsonFile(localPatternsProposalPath(input.projectRoot)) ?? readJsonFile(localPatternsPath(input.projectRoot));
|
|
3553
|
-
const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3554
|
-
const patternPack = {
|
|
3555
|
-
version: 2,
|
|
3556
|
-
generatedAt,
|
|
3557
|
-
status: "proposal",
|
|
3558
|
-
source: "decantr codify --map-pattern",
|
|
3559
|
-
purpose: existing?.purpose ?? "Project-owned Brownfield/Hybrid UI law. Review and edit before accepting; Decantr treats this as authoritative only after it is copied to .decantr/local-patterns.json.",
|
|
3560
|
-
hybrid: existing?.hybrid ?? {
|
|
3561
|
-
intent: "This local pattern pack maps Decantr concepts onto project-owned components, tokens, classes, and rules without replacing the app runtime.",
|
|
3562
|
-
authorityPrecedence: [
|
|
3563
|
-
"existing production source",
|
|
3564
|
-
"accepted local patterns and rules",
|
|
3565
|
-
"Decantr Essence V4 contract",
|
|
3566
|
-
"hosted registry patterns and execution packs as optional guidance"
|
|
3567
|
-
],
|
|
3568
|
-
hostedPatternMapping: "Hosted registry patterns are advisory until mapped to project-owned component paths, token recipes, class recipes, and explicit exceptions."
|
|
3569
|
-
},
|
|
3570
|
-
patterns: Array.isArray(existing?.patterns) ? [...existing.patterns] : [],
|
|
3571
|
-
starterRules: Array.isArray(existing?.starterRules) ? existing.starterRules : [],
|
|
3572
|
-
nextSteps: Array.isArray(existing?.nextSteps) ? existing.nextSteps : [
|
|
3573
|
-
"Review this proposal with the team.",
|
|
3574
|
-
"Add project-owned component paths, token hints, class recipes, and exceptions.",
|
|
3575
|
-
"Run decantr codify --accept after review.",
|
|
3576
|
-
"Use decantr task <route> before LLM edits so local law appears in task context."
|
|
3577
|
-
]
|
|
3578
|
-
};
|
|
3579
|
-
const slug = input.hostedPattern.slug.trim();
|
|
3580
|
-
const existingIds = new Set(
|
|
3581
|
-
(patternPack.patterns ?? []).map((pattern) => typeof pattern.id === "string" ? pattern.id : "").filter(Boolean)
|
|
3582
|
-
);
|
|
3583
|
-
const localPatternId = existingIds.has(slug) ? `${slug}-registry-map` : slug;
|
|
3584
|
-
const mappedPattern = {
|
|
3585
|
-
id: localPatternId,
|
|
3586
|
-
label: input.hostedPattern.name ? `${input.hostedPattern.name} registry mapping` : `${slug} registry mapping`,
|
|
3587
|
-
role: "Hosted registry guidance mapped into project-owned Hybrid law",
|
|
3588
|
-
appliesTo: [
|
|
3589
|
-
...input.hostedPattern.components ?? [],
|
|
3590
|
-
...input.hostedPattern.interactions ?? [],
|
|
3591
|
-
...input.hostedPattern.tags ?? []
|
|
3592
|
-
].slice(0, 24),
|
|
3593
|
-
componentPaths: [],
|
|
3594
|
-
tokenHints: [],
|
|
3595
|
-
classHints: [],
|
|
3596
|
-
decide: `Map hosted pattern "${slug}" into this app's existing components, tokens, classes, and exceptions before treating it as enforceable.`,
|
|
3597
|
-
hostedPatternRefs: [input.hostedPattern],
|
|
3598
|
-
confidence: {
|
|
3599
|
-
tier: "low",
|
|
3600
|
-
score: 0.25,
|
|
3601
|
-
rationale: [
|
|
3602
|
-
"A hosted registry pattern was selected intentionally.",
|
|
3603
|
-
"No project-owned component path or token recipe has been mapped yet."
|
|
3604
|
-
]
|
|
3605
|
-
},
|
|
3606
|
-
enforcement: {
|
|
3607
|
-
level: "advisory",
|
|
3608
|
-
status: "needs-mapping",
|
|
3609
|
-
notes: [
|
|
3610
|
-
"This proposal does not change source files.",
|
|
3611
|
-
"Do not enforce this hosted pattern until project-owned implementation details are filled in.",
|
|
3612
|
-
"Use accepted local rules, ESLint, Biome, Storybook, or visual regression for deterministic blocking checks."
|
|
3613
|
-
]
|
|
3614
|
-
},
|
|
3615
|
-
evidence: [
|
|
3616
|
-
`Hosted pattern ${input.hostedPattern.source}/${slug}${input.hostedPattern.description ? `: ${input.hostedPattern.description}` : ""}`,
|
|
3617
|
-
input.hostedPattern.visualBrief ? `Visual brief: ${input.hostedPattern.visualBrief}` : "Visual brief not provided by the hosted pattern."
|
|
3618
|
-
],
|
|
3619
|
-
evidenceToCollect: [
|
|
3620
|
-
"Project-owned component path(s) that implement this pattern.",
|
|
3621
|
-
"Allowed token/class recipe(s) for this app.",
|
|
3622
|
-
"Variant names and states that are legal in this project.",
|
|
3623
|
-
"Explicit exceptions where this hosted pattern should not apply."
|
|
3624
|
-
],
|
|
3625
|
-
forbiddenAlternatives: [
|
|
3626
|
-
"Replacing existing app components solely because a hosted pattern exists.",
|
|
3627
|
-
"Treating hosted registry guidance as enforceable without accepted local law."
|
|
3628
|
-
]
|
|
3629
|
-
};
|
|
3630
|
-
const index = (patternPack.patterns ?? []).findIndex((pattern) => pattern.id === localPatternId);
|
|
3631
|
-
const replacedExisting = index >= 0;
|
|
3632
|
-
if (replacedExisting) {
|
|
3633
|
-
patternPack.patterns[index] = mappedPattern;
|
|
3634
|
-
} else {
|
|
3635
|
-
patternPack.patterns = [...patternPack.patterns ?? [], mappedPattern];
|
|
3636
|
-
}
|
|
3637
|
-
patternPack.nextSteps = [
|
|
3638
|
-
.../* @__PURE__ */ new Set([
|
|
3639
|
-
...patternPack.nextSteps ?? [],
|
|
3640
|
-
"Fill the hosted mapping with project-owned component paths, token/class recipes, variants, and exceptions.",
|
|
3641
|
-
"Run decantr codify --accept only after the mapping reflects the existing app.",
|
|
3642
|
-
"Use decantr verify --brownfield --local-patterns after edits to keep mapped local law visible."
|
|
3643
|
-
])
|
|
3644
|
-
];
|
|
3645
|
-
const patternPath = localPatternsProposalPath(input.projectRoot);
|
|
3646
|
-
writeFileSync9(patternPath, `${JSON.stringify(patternPack, null, 2)}
|
|
3647
|
-
`, "utf-8");
|
|
3648
|
-
return { patternPath, localPatternId, replacedExisting };
|
|
3649
|
-
}
|
|
3650
|
-
function acceptBrownfieldLocalLaw(projectRoot) {
|
|
3651
|
-
const patternProposal = readJsonFile(localPatternsProposalPath(projectRoot));
|
|
3652
|
-
const ruleProposal = readJsonFile(localRulesProposalPath(projectRoot));
|
|
3653
|
-
const acceptedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3654
|
-
let patternAcceptedPath = null;
|
|
3655
|
-
let rulesAcceptedPath = null;
|
|
3656
|
-
if (patternProposal) {
|
|
3657
|
-
patternProposal.status = "accepted";
|
|
3658
|
-
patternProposal.acceptedAt = acceptedAt;
|
|
3659
|
-
patternAcceptedPath = localPatternsPath(projectRoot);
|
|
3660
|
-
writeFileSync9(patternAcceptedPath, `${JSON.stringify(patternProposal, null, 2)}
|
|
3661
|
-
`, "utf-8");
|
|
3662
|
-
}
|
|
3663
|
-
if (ruleProposal) {
|
|
3664
|
-
ruleProposal.status = "accepted";
|
|
3665
|
-
ruleProposal.acceptedAt = acceptedAt;
|
|
3666
|
-
rulesAcceptedPath = localRulesPath(projectRoot);
|
|
3667
|
-
writeFileSync9(rulesAcceptedPath, `${JSON.stringify(ruleProposal, null, 2)}
|
|
3668
|
-
`, "utf-8");
|
|
3669
|
-
}
|
|
3670
|
-
return { patternAcceptedPath, rulesAcceptedPath };
|
|
3671
|
-
}
|
|
3672
|
-
function validateLocalLaw(projectRoot) {
|
|
3673
|
-
const patternsPath = localPatternsPath(projectRoot);
|
|
3674
|
-
const rulesPath = localRulesPath(projectRoot);
|
|
3675
|
-
const patternPack = readJsonFile(patternsPath);
|
|
3676
|
-
const ruleManifest = readJsonFile(rulesPath);
|
|
3677
|
-
const warnings = [];
|
|
3678
|
-
if (patternPack) {
|
|
3679
|
-
const patternIds = /* @__PURE__ */ new Set();
|
|
3680
|
-
const patterns = Array.isArray(patternPack.patterns) ? patternPack.patterns : [];
|
|
3681
|
-
if (patterns.length === 0) {
|
|
3682
|
-
warnings.push(".decantr/local-patterns.json has no patterns.");
|
|
3683
|
-
}
|
|
3684
|
-
for (const pattern of patterns) {
|
|
3685
|
-
const id = typeof pattern.id === "string" ? pattern.id.trim() : "";
|
|
3686
|
-
if (!id) warnings.push("A local pattern is missing an id.");
|
|
3687
|
-
if (id && patternIds.has(id)) warnings.push(`Duplicate local pattern id: ${id}`);
|
|
3688
|
-
if (id) patternIds.add(id);
|
|
3689
|
-
const paths = Array.isArray(pattern.componentPaths) ? pattern.componentPaths : [];
|
|
3690
|
-
const evidence = Array.isArray(pattern.evidence) ? pattern.evidence : [];
|
|
3691
|
-
const todoEvidence = Array.isArray(pattern.evidenceToCollect) ? pattern.evidenceToCollect : [];
|
|
3692
|
-
const hostedRefs = Array.isArray(pattern.hostedPatternRefs) ? pattern.hostedPatternRefs : [];
|
|
3693
|
-
if (id && paths.length === 0 && evidence.length === 0 && todoEvidence.length > 0) {
|
|
3694
|
-
warnings.push(
|
|
3695
|
-
`Local pattern ${id} still reads like a TODO; add concrete component paths or evidence.`
|
|
3696
|
-
);
|
|
3697
|
-
}
|
|
3698
|
-
if (id && hostedRefs.length > 0 && paths.length === 0) {
|
|
3699
|
-
warnings.push(
|
|
3700
|
-
`Local pattern ${id} maps hosted registry guidance but has no project-owned component path yet.`
|
|
3701
|
-
);
|
|
3702
|
-
}
|
|
3703
|
-
}
|
|
3704
|
-
}
|
|
3705
|
-
if (ruleManifest && !Array.isArray(ruleManifest.rules)) {
|
|
3706
|
-
warnings.push(".decantr/rules.json has no rules array.");
|
|
3707
|
-
}
|
|
3708
|
-
const findings = ruleManifest ? scanLocalRules(projectRoot, ruleManifest) : [];
|
|
3709
|
-
return {
|
|
3710
|
-
patternsPath,
|
|
3711
|
-
rulesPath,
|
|
3712
|
-
patternPackPresent: Boolean(patternPack),
|
|
3713
|
-
ruleManifestPresent: Boolean(ruleManifest),
|
|
3714
|
-
warnings,
|
|
3715
|
-
findings
|
|
3716
|
-
};
|
|
3717
|
-
}
|
|
3718
|
-
function createLocalLawTaskSummary(projectRoot) {
|
|
3719
|
-
const patternPack = readLocalPatternPack(projectRoot);
|
|
3720
|
-
const ruleManifest = readLocalRuleManifest(projectRoot);
|
|
3721
|
-
const patterns = (patternPack?.patterns ?? []).map((pattern) => ({
|
|
3722
|
-
id: typeof pattern.id === "string" ? pattern.id : "unknown",
|
|
3723
|
-
role: typeof pattern.role === "string" ? pattern.role : null,
|
|
3724
|
-
componentPaths: Array.isArray(pattern.componentPaths) ? pattern.componentPaths.filter((entry) => typeof entry === "string") : [],
|
|
3725
|
-
confidenceTier: typeof pattern.confidence === "object" && pattern.confidence !== null && typeof pattern.confidence.tier === "string" ? pattern.confidence.tier : null,
|
|
3726
|
-
enforcementLevel: typeof pattern.enforcement === "object" && pattern.enforcement !== null && typeof pattern.enforcement.level === "string" ? pattern.enforcement.level : null,
|
|
3727
|
-
hostedPatternRefs: Array.isArray(pattern.hostedPatternRefs) ? pattern.hostedPatternRefs.map((ref) => typeof ref?.slug === "string" ? ref.slug : null).filter((slug) => Boolean(slug)) : []
|
|
3728
|
-
}));
|
|
3729
|
-
const rules = (ruleManifest?.rules ?? []).map((rule) => ({
|
|
3730
|
-
id: rule.id,
|
|
3731
|
-
severity: rule.severity,
|
|
3732
|
-
enabled: rule.enabled,
|
|
3733
|
-
description: rule.description
|
|
3734
|
-
}));
|
|
3735
|
-
return {
|
|
3736
|
-
patternsPath: patternPack ? ".decantr/local-patterns.json" : null,
|
|
3737
|
-
rulesPath: ruleManifest ? ".decantr/rules.json" : null,
|
|
3738
|
-
patternCount: patterns.length,
|
|
3739
|
-
ruleCount: rules.length,
|
|
3740
|
-
patterns,
|
|
3741
|
-
rules
|
|
3742
|
-
};
|
|
3743
|
-
}
|
|
3744
|
-
function changedFiles(projectRoot, since) {
|
|
3745
|
-
const changed = /* @__PURE__ */ new Set();
|
|
3746
|
-
try {
|
|
3747
|
-
const commands = since ? [
|
|
3748
|
-
["diff", "--name-only", since, "--"],
|
|
3749
|
-
["diff", "--name-only", "--cached"]
|
|
3750
|
-
] : [
|
|
3751
|
-
["diff", "--name-only"],
|
|
3752
|
-
["diff", "--name-only", "--cached"]
|
|
3753
|
-
];
|
|
3754
|
-
for (const args of commands) {
|
|
3755
|
-
const output = execFileSync("git", args, {
|
|
3756
|
-
cwd: projectRoot,
|
|
3757
|
-
encoding: "utf-8",
|
|
3758
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
3759
|
-
});
|
|
3760
|
-
for (const line of output.split(/\r?\n/)) {
|
|
3761
|
-
const file = line.trim();
|
|
3762
|
-
if (file) changed.add(normalizePath(file));
|
|
3763
|
-
}
|
|
3764
|
-
}
|
|
3765
|
-
} catch {
|
|
3766
|
-
}
|
|
3767
|
-
return [...changed].sort();
|
|
3768
|
-
}
|
|
3769
|
-
function routeImpacts(projectRoot, files) {
|
|
3770
|
-
const analysis = readJsonFile(
|
|
3771
|
-
join15(projectRoot, ".decantr", "analysis.json")
|
|
3772
|
-
);
|
|
3773
|
-
const routeEntries = analysis?.routes?.routes ?? [];
|
|
3774
|
-
const impacted = /* @__PURE__ */ new Set();
|
|
3775
|
-
for (const file of files) {
|
|
3776
|
-
for (const route of routeEntries) {
|
|
3777
|
-
if (route.file && pathMatches(file, route.file)) {
|
|
3778
|
-
if (route.path) impacted.add(route.path);
|
|
3779
|
-
}
|
|
3780
|
-
}
|
|
3781
|
-
}
|
|
3782
|
-
return [...impacted].sort();
|
|
3783
|
-
}
|
|
3784
|
-
function scanLocalRules(projectRoot, manifest) {
|
|
3785
|
-
const findings = [];
|
|
3786
|
-
const files = listSourceFiles(projectRoot, 1200);
|
|
3787
|
-
for (const rule of manifest.rules ?? []) {
|
|
3788
|
-
if (!rule.enabled || rule.type !== "forbid-regex") continue;
|
|
3789
|
-
const extensions = new Set(
|
|
3790
|
-
rule.includeExtensions?.length ? rule.includeExtensions : DEFAULT_RULE_EXTENSIONS
|
|
3791
|
-
);
|
|
3792
|
-
let regex;
|
|
3793
|
-
try {
|
|
3794
|
-
regex = new RegExp(rule.pattern, "g");
|
|
3795
|
-
} catch {
|
|
3796
|
-
findings.push({
|
|
3797
|
-
ruleId: rule.id,
|
|
3798
|
-
severity: "error",
|
|
3799
|
-
file: ".decantr/rules.json",
|
|
3800
|
-
line: 1,
|
|
3801
|
-
column: 1,
|
|
3802
|
-
excerpt: rule.pattern,
|
|
3803
|
-
message: `Invalid regex for local rule ${rule.id}.`,
|
|
3804
|
-
suggestedFix: "Edit .decantr/rules.json so the pattern is a valid JavaScript regular expression."
|
|
3805
|
-
});
|
|
3806
|
-
continue;
|
|
3807
|
-
}
|
|
3808
|
-
let ruleFindingCount = 0;
|
|
3809
|
-
for (const file of files) {
|
|
3810
|
-
if (!extensions.has(extname(file.absolute))) continue;
|
|
3811
|
-
if (pathAllowed(file.relative, rule.allowedPaths ?? [])) continue;
|
|
3812
|
-
const contents = readFileSync11(file.absolute, "utf-8");
|
|
3813
|
-
for (const match of contents.matchAll(regex)) {
|
|
3814
|
-
const index = match.index ?? 0;
|
|
3815
|
-
const position = lineColumnAt(contents, index);
|
|
3816
|
-
findings.push({
|
|
3817
|
-
ruleId: rule.id,
|
|
3818
|
-
severity: rule.severity,
|
|
3819
|
-
file: file.relative,
|
|
3820
|
-
line: position.line,
|
|
3821
|
-
column: position.column,
|
|
3822
|
-
excerpt: lineAt(contents, position.line).trim().slice(0, 180),
|
|
3823
|
-
message: rule.message,
|
|
3824
|
-
suggestedFix: rule.suggestedFix
|
|
3825
|
-
});
|
|
3826
|
-
ruleFindingCount += 1;
|
|
3827
|
-
if (rule.maxFindings && ruleFindingCount >= rule.maxFindings) break;
|
|
3828
|
-
}
|
|
3829
|
-
if (rule.maxFindings && ruleFindingCount >= rule.maxFindings) break;
|
|
3830
|
-
}
|
|
3831
|
-
}
|
|
3832
|
-
return findings;
|
|
3833
|
-
}
|
|
3834
|
-
function summarizeSourceEvidence(projectRoot, files) {
|
|
3835
|
-
const componentPaths = files.filter((file) => /(^|[/\\])components?([/\\]|$)|(^|[/\\])ui([/\\]|$)/i.test(file.relative)).map((file) => file.relative);
|
|
3836
|
-
const byName = (terms) => componentPaths.filter((file) => terms.some((term) => basename2(file).toLowerCase().includes(term))).slice(0, 12);
|
|
3837
|
-
const themeComponents = componentPaths.filter((file) => /theme|provider|mode|appearance|tenant|brand/i.test(file)).slice(0, 12);
|
|
3838
|
-
const shellComponents = files.filter((file) => /layout|shell|frame|app|root|nav|sidebar/i.test(basename2(file.relative))).map((file) => file.relative).slice(0, 12);
|
|
3839
|
-
return {
|
|
3840
|
-
buttonComponents: byName(["button", "action"]),
|
|
3841
|
-
cardComponents: byName(["card", "panel", "surface", "tile"]),
|
|
3842
|
-
formComponents: byName(["input", "field", "form", "select", "textarea"]),
|
|
3843
|
-
shellComponents,
|
|
3844
|
-
themeComponents,
|
|
3845
|
-
buttonVariants: detectVariants(projectRoot, files, {
|
|
3846
|
-
family: "button",
|
|
3847
|
-
terms: ["primary", "secondary", "tertiary", "ghost", "link", "destructive", "icon"]
|
|
3848
|
-
}),
|
|
3849
|
-
cardVariants: detectVariants(projectRoot, files, {
|
|
3850
|
-
family: "surface",
|
|
3851
|
-
terms: ["card", "panel", "surface", "tile", "elevated", "interactive", "glass"]
|
|
3852
|
-
}),
|
|
3853
|
-
formVariants: detectVariants(projectRoot, files, {
|
|
3854
|
-
family: "form",
|
|
3855
|
-
terms: ["error", "invalid", "disabled", "required", "success", "helper"]
|
|
3856
|
-
}),
|
|
3857
|
-
themeVariants: detectVariants(projectRoot, files, {
|
|
3858
|
-
family: "theme",
|
|
3859
|
-
terms: ["dark", "light", "brand", "tenant", "theme", "mode", "density"]
|
|
3860
|
-
}),
|
|
3861
|
-
buttonSourceEvidence: collectSourceEvidence(projectRoot, files, [
|
|
3862
|
-
"button",
|
|
3863
|
-
"primary",
|
|
3864
|
-
"secondary",
|
|
3865
|
-
"tertiary",
|
|
3866
|
-
"destructive"
|
|
3867
|
-
]),
|
|
3868
|
-
cardSourceEvidence: collectSourceEvidence(projectRoot, files, [
|
|
3869
|
-
"card",
|
|
3870
|
-
"panel",
|
|
3871
|
-
"surface",
|
|
3872
|
-
"rounded",
|
|
3873
|
-
"shadow"
|
|
3874
|
-
]),
|
|
3875
|
-
formSourceEvidence: collectSourceEvidence(projectRoot, files, [
|
|
3876
|
-
"input",
|
|
3877
|
-
"label",
|
|
3878
|
-
"error",
|
|
3879
|
-
"field",
|
|
3880
|
-
"select"
|
|
3881
|
-
]),
|
|
3882
|
-
themeSourceEvidence: collectSourceEvidence(projectRoot, files, [
|
|
3883
|
-
"theme",
|
|
3884
|
-
"dark",
|
|
3885
|
-
"light",
|
|
3886
|
-
"brand",
|
|
3887
|
-
"tenant"
|
|
3888
|
-
]),
|
|
3889
|
-
buttonClassHints: collectClassHints(projectRoot, files, [
|
|
3890
|
-
"button",
|
|
3891
|
-
"btn",
|
|
3892
|
-
"action",
|
|
3893
|
-
"primary",
|
|
3894
|
-
"secondary",
|
|
3895
|
-
"tertiary",
|
|
3896
|
-
"ghost",
|
|
3897
|
-
"link"
|
|
3898
|
-
]),
|
|
3899
|
-
cardClassHints: collectClassHints(projectRoot, files, ["card", "panel", "surface", "tile"])
|
|
3900
|
-
};
|
|
3901
|
-
}
|
|
3902
|
-
function detectVariants(projectRoot, files, input) {
|
|
3903
|
-
const variants = /* @__PURE__ */ new Map();
|
|
3904
|
-
const termPattern = new RegExp(
|
|
3905
|
-
`\\b(${input.terms.map(escapeRegExp).join("|")})(?:[-_:][a-z0-9-]+)?\\b`,
|
|
3906
|
-
"gi"
|
|
3907
|
-
);
|
|
3908
|
-
for (const file of files) {
|
|
3909
|
-
if (!UI_TEMPLATE_EXTENSIONS.has(extname(file.absolute))) continue;
|
|
3910
|
-
const content = readFileSync11(join15(projectRoot, file.relative), "utf-8");
|
|
3911
|
-
for (const match of content.matchAll(termPattern)) {
|
|
3912
|
-
const raw = (match[0] ?? "").toLowerCase();
|
|
3913
|
-
const id = raw.replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
3914
|
-
if (!id || id.length < 3) continue;
|
|
3915
|
-
const position = lineColumnAt(content, match.index ?? 0);
|
|
3916
|
-
const line = lineAt(content, position.line).trim().slice(0, 160);
|
|
3917
|
-
const entry = variants.get(id) ?? { evidence: /* @__PURE__ */ new Set(), count: 0 };
|
|
3918
|
-
entry.count += 1;
|
|
3919
|
-
entry.evidence.add(`${file.relative}:${position.line} ${line}`);
|
|
3920
|
-
variants.set(id, entry);
|
|
3921
|
-
}
|
|
3922
|
-
}
|
|
3923
|
-
return [...variants.entries()].sort((a, b) => b[1].count - a[1].count || a[0].localeCompare(b[0])).slice(0, 8).map(([id, value]) => ({
|
|
3924
|
-
id,
|
|
3925
|
-
label: `${input.family} ${id}`,
|
|
3926
|
-
evidence: [...value.evidence].slice(0, 4),
|
|
3927
|
-
confidence: value.count >= 4 ? "high" : value.count >= 2 ? "medium" : "low"
|
|
3928
|
-
}));
|
|
3929
|
-
}
|
|
3930
|
-
function collectSourceEvidence(projectRoot, files, terms) {
|
|
3931
|
-
const results = [];
|
|
3932
|
-
const termPattern = new RegExp(terms.map(escapeRegExp).join("|"), "i");
|
|
3933
|
-
for (const file of files) {
|
|
3934
|
-
if (!UI_TEMPLATE_EXTENSIONS.has(extname(file.absolute))) continue;
|
|
3935
|
-
const content = readFileSync11(join15(projectRoot, file.relative), "utf-8");
|
|
3936
|
-
const lines = content.split(/\r?\n/);
|
|
3937
|
-
for (let index = 0; index < lines.length; index += 1) {
|
|
3938
|
-
const line = lines[index];
|
|
3939
|
-
if (!termPattern.test(line)) continue;
|
|
3940
|
-
const signals = terms.filter((term) => line.toLowerCase().includes(term.toLowerCase()));
|
|
3941
|
-
results.push({
|
|
3942
|
-
file: file.relative,
|
|
3943
|
-
line: index + 1,
|
|
3944
|
-
excerpt: line.trim().slice(0, 180),
|
|
3945
|
-
signals: signals.slice(0, 6)
|
|
3946
|
-
});
|
|
3947
|
-
if (results.length >= 16) return results;
|
|
3948
|
-
break;
|
|
3949
|
-
}
|
|
3950
|
-
}
|
|
3951
|
-
return results;
|
|
3952
|
-
}
|
|
3953
|
-
function confidenceForEvidence(input) {
|
|
3954
|
-
const score = Math.min(
|
|
3955
|
-
1,
|
|
3956
|
-
input.componentCount * 0.28 + (input.classHintCount ?? 0) * 0.08 + (input.variantCount ?? 0) * 0.08 + (input.sourceEvidenceCount ?? 0) * 0.05
|
|
3957
|
-
);
|
|
3958
|
-
const tier = score >= 0.7 ? "high" : score >= 0.4 ? "medium" : "low";
|
|
3959
|
-
const rationale = [
|
|
3960
|
-
input.componentCount > 0 ? `${input.componentCount} likely component path(s) detected` : "no likely component wrapper detected yet",
|
|
3961
|
-
(input.classHintCount ?? 0) > 0 ? `${input.classHintCount} class recipe hint(s) detected` : "no strong class recipe hints detected yet",
|
|
3962
|
-
(input.variantCount ?? 0) > 0 ? `${input.variantCount} possible variant signal(s) detected` : "variant names still need team confirmation"
|
|
3963
|
-
];
|
|
3964
|
-
return { tier, score: Number(score.toFixed(2)), rationale };
|
|
3965
|
-
}
|
|
3966
|
-
function collectClassHints(projectRoot, files, terms) {
|
|
3967
|
-
const hints = /* @__PURE__ */ new Map();
|
|
3968
|
-
const wantsButton = terms.some(
|
|
3969
|
-
(term) => /^(button|btn|action|primary|secondary|tertiary|ghost|link)$/i.test(term)
|
|
3970
|
-
);
|
|
3971
|
-
const wantsSurface = terms.some((term) => /^(card|panel|surface|tile)$/i.test(term));
|
|
3972
|
-
const buttonSignal = /button|btn|action|primary|secondary|tertiary|ghost|link|destructive|icon/i;
|
|
3973
|
-
const surfaceSignal = /card|panel|surface|tile|rounded|shadow|border|bg-|p-\d|px-|py-/i;
|
|
3974
|
-
for (const file of files) {
|
|
3975
|
-
if (!UI_TEMPLATE_EXTENSIONS.has(extname(file.absolute))) continue;
|
|
3976
|
-
const content = readFileSync11(join15(projectRoot, file.relative), "utf-8");
|
|
3977
|
-
if (!terms.some((term) => content.toLowerCase().includes(term))) continue;
|
|
3978
|
-
if (wantsButton) {
|
|
3979
|
-
const openingTags = content.matchAll(
|
|
3980
|
-
/<([A-Za-z][\w.:/-]*)\b[^>]*\bclass(?:Name)?\s*=\s*["'`]([^"'`]+)["'`][^>]*>/g
|
|
3981
|
-
);
|
|
3982
|
-
for (const match of openingTags) {
|
|
3983
|
-
const tag = match[1];
|
|
3984
|
-
const value = match[2].trim();
|
|
3985
|
-
const tagLooksInteractive = /^(button|a|Link)$/i.test(tag) || /(^|\.)(Button|IconButton|LinkButton|Action)$/i.test(tag);
|
|
3986
|
-
const fileLooksInteractive = /button|action|link/i.test(basename2(file.relative));
|
|
3987
|
-
if (!tagLooksInteractive && !fileLooksInteractive) continue;
|
|
3988
|
-
if (!buttonSignal.test(value) && !fileLooksInteractive) continue;
|
|
3989
|
-
hints.set(value, (hints.get(value) ?? 0) + 1);
|
|
3990
|
-
}
|
|
3991
|
-
continue;
|
|
3992
|
-
}
|
|
3993
|
-
const matches = content.matchAll(/\bclass(?:Name)?\s*=\s*["'`]([^"'`]+)["'`]/g);
|
|
3994
|
-
for (const match of matches) {
|
|
3995
|
-
const value = match[1].trim();
|
|
3996
|
-
const keep = wantsSurface && surfaceSignal.test(value) || !wantsButton && !wantsSurface && (buttonSignal.test(value) || surfaceSignal.test(value));
|
|
3997
|
-
if (!keep) continue;
|
|
3998
|
-
hints.set(value, (hints.get(value) ?? 0) + 1);
|
|
3999
|
-
}
|
|
4000
|
-
}
|
|
4001
|
-
return [...hints.entries()].sort((a, b) => b[1] - a[1]).slice(0, 8).map(([hint]) => hint);
|
|
4002
|
-
}
|
|
4003
|
-
function listSourceFiles(projectRoot, maxFiles) {
|
|
4004
|
-
const files = [];
|
|
4005
|
-
const visit = (dir) => {
|
|
4006
|
-
if (files.length >= maxFiles) return;
|
|
4007
|
-
let entries;
|
|
4008
|
-
try {
|
|
4009
|
-
entries = readdirSync5(dir);
|
|
4010
|
-
} catch {
|
|
4011
|
-
return;
|
|
4012
|
-
}
|
|
4013
|
-
for (const entry of entries) {
|
|
4014
|
-
if (files.length >= maxFiles) return;
|
|
4015
|
-
if (IGNORED_DIRS.has(entry)) continue;
|
|
4016
|
-
const absolute = join15(dir, entry);
|
|
4017
|
-
let stat;
|
|
4018
|
-
try {
|
|
4019
|
-
stat = statSync5(absolute);
|
|
4020
|
-
} catch {
|
|
4021
|
-
continue;
|
|
4022
|
-
}
|
|
4023
|
-
if (stat.isDirectory()) {
|
|
4024
|
-
visit(absolute);
|
|
4025
|
-
} else if (stat.isFile() && SOURCE_EXTENSIONS.has(extname(entry))) {
|
|
4026
|
-
files.push({ absolute, relative: normalizePath(relative3(projectRoot, absolute)) });
|
|
4027
|
-
}
|
|
4028
|
-
}
|
|
4029
|
-
};
|
|
4030
|
-
visit(projectRoot);
|
|
4031
|
-
return files.sort((a, b) => a.relative.localeCompare(b.relative));
|
|
4032
|
-
}
|
|
4033
|
-
function readJsonFile(path) {
|
|
4034
|
-
if (!existsSync13(path)) return null;
|
|
4035
|
-
try {
|
|
4036
|
-
return JSON.parse(readFileSync11(path, "utf-8"));
|
|
4037
|
-
} catch {
|
|
4038
|
-
return null;
|
|
4039
|
-
}
|
|
4040
|
-
}
|
|
4041
|
-
function pathAllowed(file, allowedPaths) {
|
|
4042
|
-
return allowedPaths.some((allowedPath) => pathMatches(file, allowedPath));
|
|
4043
|
-
}
|
|
4044
|
-
function pathMatches(file, pattern) {
|
|
4045
|
-
const normalizedFile = normalizePath(file);
|
|
4046
|
-
const normalizedPattern = normalizePath(pattern);
|
|
4047
|
-
return normalizedFile === normalizedPattern || normalizedFile.endsWith(`/${normalizedPattern}`);
|
|
4048
|
-
}
|
|
4049
|
-
function normalizePath(path) {
|
|
4050
|
-
return path.split(sep).join("/").replace(/\\/g, "/");
|
|
4051
|
-
}
|
|
4052
|
-
function lineColumnAt(contents, index) {
|
|
4053
|
-
const before = contents.slice(0, index);
|
|
4054
|
-
const lines = before.split(/\r?\n/);
|
|
4055
|
-
return {
|
|
4056
|
-
line: lines.length,
|
|
4057
|
-
column: lines[lines.length - 1].length + 1
|
|
4058
|
-
};
|
|
4059
|
-
}
|
|
4060
|
-
function escapeRegExp(value) {
|
|
4061
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4062
|
-
}
|
|
4063
|
-
function lineAt(contents, line) {
|
|
4064
|
-
return contents.split(/\r?\n/)[line - 1] ?? "";
|
|
4065
|
-
}
|
|
4066
|
-
|
|
4067
|
-
// src/style-bridge.ts
|
|
4068
|
-
import { existsSync as existsSync14, mkdirSync as mkdirSync9, readFileSync as readFileSync12, writeFileSync as writeFileSync10 } from "fs";
|
|
4069
|
-
import { join as join16 } from "path";
|
|
4070
|
-
function styleBridgeProposalPath(projectRoot) {
|
|
4071
|
-
return join16(projectRoot, ".decantr", "style-bridge.proposal.json");
|
|
4072
|
-
}
|
|
4073
|
-
function styleBridgePath(projectRoot) {
|
|
4074
|
-
return join16(projectRoot, ".decantr", "style-bridge.json");
|
|
4075
|
-
}
|
|
4076
|
-
function readJsonFile2(path) {
|
|
4077
|
-
if (!existsSync14(path)) return null;
|
|
4078
|
-
try {
|
|
4079
|
-
return JSON.parse(readFileSync12(path, "utf-8"));
|
|
4080
|
-
} catch {
|
|
4081
|
-
return null;
|
|
4082
|
-
}
|
|
4083
|
-
}
|
|
4084
|
-
function writeJsonFile(path, value) {
|
|
4085
|
-
writeFileSync10(path, `${JSON.stringify(value, null, 2)}
|
|
4086
|
-
`, "utf-8");
|
|
4087
|
-
}
|
|
4088
|
-
function readStyleBridge(projectRoot) {
|
|
4089
|
-
return readJsonFile2(styleBridgePath(projectRoot));
|
|
4090
|
-
}
|
|
4091
|
-
function readStyleBridgeProposal(projectRoot) {
|
|
4092
|
-
return readJsonFile2(styleBridgeProposalPath(projectRoot));
|
|
4093
|
-
}
|
|
4094
|
-
function stringArray(value) {
|
|
4095
|
-
return Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
|
|
4096
|
-
}
|
|
4097
|
-
function readThemeInventory(projectRoot) {
|
|
4098
|
-
const inventory = readJsonFile2(join16(projectRoot, ".decantr", "theme-inventory.json"));
|
|
4099
|
-
return {
|
|
4100
|
-
modes: stringArray(inventory?.modes),
|
|
4101
|
-
variantIds: Array.isArray(inventory?.variants) ? inventory.variants.map((variant) => variant.id).filter((id) => typeof id === "string") : [],
|
|
4102
|
-
darkModeDetected: typeof inventory?.darkModeDetected === "boolean" ? inventory.darkModeDetected : null
|
|
4103
|
-
};
|
|
4104
|
-
}
|
|
4105
|
-
function tokenHints(styling, terms) {
|
|
4106
|
-
return styling.cssVariables.filter((name) => terms.test(name)).slice(0, 12);
|
|
4107
|
-
}
|
|
4108
|
-
function readProjectPatternPack(projectRoot) {
|
|
4109
|
-
return readLocalPatternPack(projectRoot) ?? readJsonFile2(
|
|
4110
|
-
join16(projectRoot, ".decantr", "local-patterns.proposal.json")
|
|
4111
|
-
);
|
|
4112
|
-
}
|
|
4113
|
-
function classHintsForPattern(projectRoot, ids) {
|
|
4114
|
-
const pack = readProjectPatternPack(projectRoot);
|
|
4115
|
-
const classes = /* @__PURE__ */ new Set();
|
|
4116
|
-
for (const pattern of pack?.patterns ?? []) {
|
|
4117
|
-
if (!ids.includes(String(pattern.id))) continue;
|
|
4118
|
-
for (const hint of pattern.classHints ?? []) classes.add(hint);
|
|
4119
|
-
}
|
|
4120
|
-
return [...classes].slice(0, 12);
|
|
4121
|
-
}
|
|
4122
|
-
function sourceEvidence(projectRoot, ids) {
|
|
4123
|
-
const pack = readProjectPatternPack(projectRoot);
|
|
4124
|
-
const evidence = /* @__PURE__ */ new Set();
|
|
4125
|
-
for (const pattern of pack?.patterns ?? []) {
|
|
4126
|
-
if (!ids.includes(String(pattern.id))) continue;
|
|
4127
|
-
for (const path of pattern.componentPaths ?? []) evidence.add(path);
|
|
4128
|
-
for (const item of pattern.evidence ?? []) evidence.add(item);
|
|
4129
|
-
}
|
|
4130
|
-
return [...evidence].slice(0, 12);
|
|
4131
|
-
}
|
|
4132
|
-
function colorTokenNames(styling) {
|
|
4133
|
-
const names = new Set(Object.keys(styling.colors ?? {}));
|
|
4134
|
-
for (const variable of styling.cssVariables) {
|
|
4135
|
-
if (/color|bg|background|surface|text|foreground|border|primary|secondary|accent|muted/i.test(variable)) {
|
|
4136
|
-
names.add(variable);
|
|
4137
|
-
}
|
|
4138
|
-
}
|
|
4139
|
-
return [...names].slice(0, 40);
|
|
4140
|
-
}
|
|
4141
|
-
function createStyleBridgeProposal(input) {
|
|
4142
|
-
const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4143
|
-
const theme = readThemeInventory(input.projectRoot);
|
|
4144
|
-
const routeCount = input.essence && typeof input.essence === "object" && "blueprint" in input.essence ? Object.keys(input.essence.blueprint?.routes ?? {}).length : 0;
|
|
4145
|
-
const target = input.essence && typeof input.essence === "object" && "meta" in input.essence ? String(input.essence.meta?.target ?? "") || null : null;
|
|
4146
|
-
const darkModeDetected = theme.darkModeDetected ?? input.styling.darkMode;
|
|
4147
|
-
const themeModes = theme.modes.length > 0 ? theme.modes : darkModeDetected ? ["base", "dark"] : ["base"];
|
|
4148
|
-
const themeVariantIds = theme.variantIds.length > 0 ? theme.variantIds : themeModes.filter((mode) => mode !== "base");
|
|
4149
|
-
return {
|
|
4150
|
-
version: 1,
|
|
4151
|
-
status: "proposal",
|
|
4152
|
-
generatedAt,
|
|
4153
|
-
source: "decantr codify --style-bridge",
|
|
4154
|
-
purpose: "Project-owned Hybrid style bridge. It maps Decantr design intent to the existing app styling system without installing Decantr CSS or taking over source.",
|
|
4155
|
-
adoption: {
|
|
4156
|
-
mode: "style-bridge",
|
|
4157
|
-
workflowMode: "brownfield-attach",
|
|
4158
|
-
sourceAuthority: "Existing production source stays authoritative.",
|
|
4159
|
-
styleAuthority: "Use these mappings to translate Decantr concepts into project-owned tokens, classes, and components.",
|
|
4160
|
-
notRuntimeTakeover: true,
|
|
4161
|
-
authorityPrecedence: [
|
|
4162
|
-
"existing production source",
|
|
4163
|
-
"accepted style bridge",
|
|
4164
|
-
"accepted local patterns and rules",
|
|
4165
|
-
"Essence V4 contract",
|
|
4166
|
-
"hosted registry patterns and execution packs as optional guidance"
|
|
4167
|
-
]
|
|
4168
|
-
},
|
|
4169
|
-
project: {
|
|
4170
|
-
framework: input.detected.framework,
|
|
4171
|
-
packageManager: input.detected.packageManager,
|
|
4172
|
-
target,
|
|
4173
|
-
routeCount
|
|
4174
|
-
},
|
|
4175
|
-
styling: {
|
|
4176
|
-
approach: input.styling.approach,
|
|
4177
|
-
configFile: input.styling.configFile ?? null,
|
|
4178
|
-
darkModeDetected,
|
|
4179
|
-
cssVariables: input.styling.cssVariables.slice(0, 80),
|
|
4180
|
-
colorTokenNames: colorTokenNames(input.styling),
|
|
4181
|
-
themeModes,
|
|
4182
|
-
themeVariantIds
|
|
4183
|
-
},
|
|
4184
|
-
mappings: [
|
|
4185
|
-
{
|
|
4186
|
-
id: "surface",
|
|
4187
|
-
label: "Surfaces and cards",
|
|
4188
|
-
decantrIntent: "surface background, card treatment, border, radius, depth, and hover state",
|
|
4189
|
-
projectAuthority: "Use the app card/surface primitives, tokens, and accepted local law.",
|
|
4190
|
-
tokenHints: tokenHints(input.styling, /surface|card|panel|bg|background|border|shadow|radius/i),
|
|
4191
|
-
classHints: classHintsForPattern(input.projectRoot, ["surface-card"]),
|
|
4192
|
-
sourceEvidence: sourceEvidence(input.projectRoot, ["surface-card"]),
|
|
4193
|
-
guardrails: [
|
|
4194
|
-
"Do not invent a new card color/radius/shadow recipe without updating local law.",
|
|
4195
|
-
"Do not add Decantr CSS d-* classes unless adoption mode changes to decantr-css."
|
|
4196
|
-
]
|
|
4197
|
-
},
|
|
4198
|
-
{
|
|
4199
|
-
id: "action",
|
|
4200
|
-
label: "Actions and buttons",
|
|
4201
|
-
decantrIntent: "primary, secondary, tertiary, destructive, icon-only, loading, and disabled action states",
|
|
4202
|
-
projectAuthority: "Use the app button/action primitives and local variant names.",
|
|
4203
|
-
tokenHints: tokenHints(input.styling, /primary|secondary|accent|danger|error|destructive|focus/i),
|
|
4204
|
-
classHints: classHintsForPattern(input.projectRoot, ["button"]),
|
|
4205
|
-
sourceEvidence: sourceEvidence(input.projectRoot, ["button"]),
|
|
4206
|
-
guardrails: [
|
|
4207
|
-
"Map any new action style to primary/secondary/tertiary/destructive/icon before coding.",
|
|
4208
|
-
"Avoid raw button markup when a project-owned primitive exists."
|
|
4209
|
-
]
|
|
4210
|
-
},
|
|
4211
|
-
{
|
|
4212
|
-
id: "focus-accessibility",
|
|
4213
|
-
label: "Focus and accessibility",
|
|
4214
|
-
decantrIntent: "visible focus, keyboard clarity, contrast, and reduced-motion safety",
|
|
4215
|
-
projectAuthority: "Use the app accessibility classes, focus tokens, and framework conventions.",
|
|
4216
|
-
tokenHints: tokenHints(input.styling, /focus|ring|outline|contrast|motion|duration/i),
|
|
4217
|
-
classHints: [],
|
|
4218
|
-
sourceEvidence: [],
|
|
4219
|
-
guardrails: [
|
|
4220
|
-
"Every new interactive control needs visible focus evidence.",
|
|
4221
|
-
"Motion should honor prefers-reduced-motion or an existing app motion helper."
|
|
4222
|
-
]
|
|
4223
|
-
},
|
|
4224
|
-
{
|
|
4225
|
-
id: "layout-density",
|
|
4226
|
-
label: "Layout density and spacing",
|
|
4227
|
-
decantrIntent: "route gutters, section gaps, component padding, density, and responsive rhythm",
|
|
4228
|
-
projectAuthority: "Use existing layout wrappers, shell primitives, and spacing tokens/classes.",
|
|
4229
|
-
tokenHints: tokenHints(input.styling, /space|spacing|gap|gutter|container|radius/i),
|
|
4230
|
-
classHints: classHintsForPattern(input.projectRoot, ["page-shell"]),
|
|
4231
|
-
sourceEvidence: sourceEvidence(input.projectRoot, ["page-shell"]),
|
|
4232
|
-
guardrails: [
|
|
4233
|
-
"Do not let each page invent independent max-width, gutters, or scroll ownership.",
|
|
4234
|
-
"Use the detected shell/layout authority before adding a new wrapper."
|
|
4235
|
-
]
|
|
4236
|
-
},
|
|
4237
|
-
{
|
|
4238
|
-
id: "theme-variant",
|
|
4239
|
-
label: "Theme variants",
|
|
4240
|
-
decantrIntent: "light, dark, brand, tenant, seasonal, and density variants",
|
|
4241
|
-
projectAuthority: "Use the app theme provider, data attributes, CSS variables, or Tailwind mode strategy.",
|
|
4242
|
-
tokenHints: tokenHints(input.styling, /theme|dark|light|brand|tenant|mode|color/i),
|
|
4243
|
-
classHints: classHintsForPattern(input.projectRoot, ["theme-variant"]),
|
|
4244
|
-
sourceEvidence: sourceEvidence(input.projectRoot, ["theme-variant"]),
|
|
4245
|
-
guardrails: [
|
|
4246
|
-
"Keep theme modes documented in .decantr/theme-inventory.json and this bridge.",
|
|
4247
|
-
"Do not treat theme switcher container classes as standalone theme variants."
|
|
4248
|
-
]
|
|
4249
|
-
}
|
|
4250
|
-
],
|
|
4251
|
-
rules: [
|
|
4252
|
-
"The bridge is advisory until accepted; after acceptance, task/doctor/CI should surface it as Hybrid authority.",
|
|
4253
|
-
"The bridge does not make hosted registry patterns enforceable. Map hosted concepts into local law first.",
|
|
4254
|
-
"Keep deterministic blocking checks in .decantr/rules.json, ESLint, Biome, tests, or visual regression."
|
|
4255
|
-
],
|
|
4256
|
-
nextSteps: [
|
|
4257
|
-
"Review token and class hints. Replace generic hints with the exact project tokens/classes the team owns.",
|
|
4258
|
-
"Run decantr codify --accept to promote the proposal to .decantr/style-bridge.json.",
|
|
4259
|
-
"Use decantr task <route> before LLM edits so the style bridge appears in task context.",
|
|
4260
|
-
"Run decantr ci or decantr verify after edits to keep local law and Project Health visible."
|
|
4261
|
-
]
|
|
4262
|
-
};
|
|
4263
|
-
}
|
|
4264
|
-
function writeStyleBridgeProposal(projectRoot, proposal) {
|
|
4265
|
-
const decantrDir = join16(projectRoot, ".decantr");
|
|
4266
|
-
mkdirSync9(decantrDir, { recursive: true });
|
|
4267
|
-
const path = styleBridgeProposalPath(projectRoot);
|
|
4268
|
-
writeJsonFile(path, proposal);
|
|
4269
|
-
return path;
|
|
4270
|
-
}
|
|
4271
|
-
function acceptStyleBridge(projectRoot) {
|
|
4272
|
-
const proposal = readStyleBridgeProposal(projectRoot);
|
|
4273
|
-
if (!proposal) return null;
|
|
4274
|
-
const accepted = {
|
|
4275
|
-
...proposal,
|
|
4276
|
-
status: "accepted",
|
|
4277
|
-
acceptedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4278
|
-
};
|
|
4279
|
-
const path = styleBridgePath(projectRoot);
|
|
4280
|
-
writeJsonFile(path, accepted);
|
|
4281
|
-
const projectJsonPath = join16(projectRoot, ".decantr", "project.json");
|
|
4282
|
-
const projectJson = readJsonFile2(projectJsonPath) ?? {};
|
|
4283
|
-
const initialized = typeof projectJson.initialized === "object" && projectJson.initialized !== null ? projectJson.initialized : {};
|
|
4284
|
-
writeJsonFile(projectJsonPath, {
|
|
4285
|
-
...projectJson,
|
|
4286
|
-
initialized: {
|
|
4287
|
-
...initialized,
|
|
4288
|
-
workflowMode: initialized.workflowMode ?? "brownfield-attach",
|
|
4289
|
-
adoptionMode: "style-bridge"
|
|
4290
|
-
}
|
|
4291
|
-
});
|
|
4292
|
-
return path;
|
|
4293
|
-
}
|
|
4294
|
-
function createStyleBridgeTaskSummary(projectRoot) {
|
|
4295
|
-
const bridge = readStyleBridge(projectRoot);
|
|
4296
|
-
return {
|
|
4297
|
-
path: bridge ? ".decantr/style-bridge.json" : null,
|
|
4298
|
-
status: bridge?.status ?? null,
|
|
4299
|
-
mappingCount: bridge?.mappings?.length ?? 0,
|
|
4300
|
-
stylingApproach: bridge?.styling?.approach ?? null,
|
|
4301
|
-
themeModes: bridge?.styling?.themeModes ?? [],
|
|
4302
|
-
mappings: bridge?.mappings?.map((mapping) => ({
|
|
4303
|
-
id: mapping.id,
|
|
4304
|
-
label: mapping.label,
|
|
4305
|
-
tokenHints: mapping.tokenHints.slice(0, 6),
|
|
4306
|
-
classHints: mapping.classHints.slice(0, 4),
|
|
4307
|
-
guardrails: mapping.guardrails.slice(0, 3)
|
|
4308
|
-
})) ?? []
|
|
4309
|
-
};
|
|
4310
|
-
}
|
|
4311
|
-
function styleBridgeMatches(projectRoot, query) {
|
|
4312
|
-
if (!projectRoot) return [];
|
|
4313
|
-
const bridge = readStyleBridge(projectRoot);
|
|
4314
|
-
if (!bridge) return [];
|
|
4315
|
-
const terms = query.toLowerCase().split(/[^a-z0-9]+/).filter((term) => term.length > 1).flatMap((term) => term.endsWith("s") && term.length > 3 ? [term, term.slice(0, -1)] : [term]);
|
|
4316
|
-
if (terms.length === 0) return [];
|
|
4317
|
-
return bridge.mappings.map((mapping) => {
|
|
4318
|
-
const haystack = [
|
|
4319
|
-
mapping.id,
|
|
4320
|
-
mapping.label,
|
|
4321
|
-
mapping.decantrIntent,
|
|
4322
|
-
mapping.projectAuthority,
|
|
4323
|
-
...mapping.tokenHints,
|
|
4324
|
-
...mapping.classHints,
|
|
4325
|
-
...mapping.guardrails
|
|
4326
|
-
].join(" ").toLowerCase();
|
|
4327
|
-
const score = terms.reduce((sum, term) => sum + (haystack.includes(term) ? 1 : 0), 0);
|
|
4328
|
-
return {
|
|
4329
|
-
id: mapping.id,
|
|
4330
|
-
label: mapping.label,
|
|
4331
|
-
score,
|
|
4332
|
-
tokenHints: mapping.tokenHints.slice(0, 4),
|
|
4333
|
-
classHints: mapping.classHints.slice(0, 3)
|
|
4334
|
-
};
|
|
4335
|
-
}).filter((match) => match.score > 0).sort((a, b) => b.score - a.score || a.id.localeCompare(b.id)).slice(0, 5);
|
|
4336
|
-
}
|
|
4337
|
-
|
|
4338
|
-
// src/commands/ci.ts
|
|
3358
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync8, readFileSync as readFileSync11, writeFileSync as writeFileSync9 } from "fs";
|
|
3359
|
+
import { dirname as dirname3, join as join15, relative as relative3, resolve } from "path";
|
|
4339
3360
|
var BOLD2 = "\x1B[1m";
|
|
4340
3361
|
var DIM3 = "\x1B[2m";
|
|
4341
3362
|
var GREEN3 = "\x1B[32m";
|
|
@@ -4343,9 +3364,9 @@ var RED2 = "\x1B[31m";
|
|
|
4343
3364
|
var RESET3 = "\x1B[0m";
|
|
4344
3365
|
var CI_SCHEMA = "https://decantr.ai/schemas/decantr-ci-report.v1.json";
|
|
4345
3366
|
function readJson(path) {
|
|
4346
|
-
if (!
|
|
3367
|
+
if (!existsSync13(path)) return null;
|
|
4347
3368
|
try {
|
|
4348
|
-
return JSON.parse(
|
|
3369
|
+
return JSON.parse(readFileSync11(path, "utf-8"));
|
|
4349
3370
|
} catch {
|
|
4350
3371
|
return null;
|
|
4351
3372
|
}
|
|
@@ -4389,21 +3410,21 @@ function parseProvider(value) {
|
|
|
4389
3410
|
throw new Error("Invalid --provider value. Use github or generic.");
|
|
4390
3411
|
}
|
|
4391
3412
|
function detectPackageManager2(root) {
|
|
4392
|
-
const pkg = readJson(
|
|
3413
|
+
const pkg = readJson(join15(root, "package.json"));
|
|
4393
3414
|
const declared = pkg?.packageManager?.split("@")[0];
|
|
4394
3415
|
if (declared === "pnpm" || declared === "npm" || declared === "yarn" || declared === "bun") {
|
|
4395
3416
|
return declared;
|
|
4396
3417
|
}
|
|
4397
|
-
if (
|
|
4398
|
-
if (
|
|
4399
|
-
if (
|
|
4400
|
-
if (
|
|
3418
|
+
if (existsSync13(join15(root, "pnpm-lock.yaml"))) return "pnpm";
|
|
3419
|
+
if (existsSync13(join15(root, "package-lock.json"))) return "npm";
|
|
3420
|
+
if (existsSync13(join15(root, "yarn.lock"))) return "yarn";
|
|
3421
|
+
if (existsSync13(join15(root, "bun.lock")) || existsSync13(join15(root, "bun.lockb"))) return "bun";
|
|
4401
3422
|
return "unknown";
|
|
4402
3423
|
}
|
|
4403
3424
|
function hasWorkspaceMarker(root) {
|
|
4404
|
-
const pkg = readJson(
|
|
3425
|
+
const pkg = readJson(join15(root, "package.json"));
|
|
4405
3426
|
return Boolean(
|
|
4406
|
-
|
|
3427
|
+
existsSync13(join15(root, "pnpm-workspace.yaml")) || existsSync13(join15(root, "turbo.json")) || existsSync13(join15(root, "nx.json")) || pkg?.workspaces
|
|
4407
3428
|
);
|
|
4408
3429
|
}
|
|
4409
3430
|
function installCommand(packageManager) {
|
|
@@ -4449,7 +3470,7 @@ function decantrCommand(packageManager) {
|
|
|
4449
3470
|
}
|
|
4450
3471
|
}
|
|
4451
3472
|
function localCliPinned(root) {
|
|
4452
|
-
const pkg = readJson(
|
|
3473
|
+
const pkg = readJson(join15(root, "package.json"));
|
|
4453
3474
|
return Boolean(pkg?.devDependencies?.["@decantr/cli"] || pkg?.dependencies?.["@decantr/cli"]);
|
|
4454
3475
|
}
|
|
4455
3476
|
function projectSlug(projectPath) {
|
|
@@ -4457,8 +3478,8 @@ function projectSlug(projectPath) {
|
|
|
4457
3478
|
}
|
|
4458
3479
|
function writeOutput(root, path, content) {
|
|
4459
3480
|
const absolute = resolve(root, path);
|
|
4460
|
-
|
|
4461
|
-
|
|
3481
|
+
mkdirSync8(dirname3(absolute), { recursive: true });
|
|
3482
|
+
writeFileSync9(absolute, content, "utf-8");
|
|
4462
3483
|
}
|
|
4463
3484
|
function summarizeLocalLaw(projectRoot) {
|
|
4464
3485
|
const validation = validateLocalLaw(projectRoot);
|
|
@@ -4733,7 +3754,7 @@ jobs:
|
|
|
4733
3754
|
}
|
|
4734
3755
|
function writeCiInit(root, options) {
|
|
4735
3756
|
const workspaceInfo = resolveWorkspaceInfo(root, options.project);
|
|
4736
|
-
if (options.project && !
|
|
3757
|
+
if (options.project && !existsSync13(workspaceInfo.appRoot)) {
|
|
4737
3758
|
throw new Error(`Project path does not exist: ${options.project}`);
|
|
4738
3759
|
}
|
|
4739
3760
|
if (workspaceInfo.requiresProjectSelection && !options.workspace) {
|
|
@@ -4743,7 +3764,7 @@ function writeCiInit(root, options) {
|
|
|
4743
3764
|
);
|
|
4744
3765
|
}
|
|
4745
3766
|
const outputRoot = workspaceInfo.workspaceRoot;
|
|
4746
|
-
const projectPath = options.project ?? (options.workspace || workspaceInfo.appRoot === workspaceInfo.workspaceRoot ? void 0 :
|
|
3767
|
+
const projectPath = options.project ?? (options.workspace || workspaceInfo.appRoot === workspaceInfo.workspaceRoot ? void 0 : relative3(workspaceInfo.workspaceRoot, workspaceInfo.appRoot).replace(/\\/g, "/"));
|
|
4747
3768
|
const packageManager = detectPackageManager2(outputRoot);
|
|
4748
3769
|
const command = decantrCommand(packageManager);
|
|
4749
3770
|
const failOn = options.failOn ?? "error";
|
|
@@ -4756,7 +3777,7 @@ function writeCiInit(root, options) {
|
|
|
4756
3777
|
if (provider === "generic") {
|
|
4757
3778
|
const path2 = ".decantr/ci/decantr-ci.sh";
|
|
4758
3779
|
const absolute2 = resolve(outputRoot, path2);
|
|
4759
|
-
if (
|
|
3780
|
+
if (existsSync13(absolute2) && !options.force) {
|
|
4760
3781
|
throw new Error(`${path2} already exists. Re-run with --force to replace it.`);
|
|
4761
3782
|
}
|
|
4762
3783
|
writeOutput(
|
|
@@ -4777,7 +3798,7 @@ function writeCiInit(root, options) {
|
|
|
4777
3798
|
}
|
|
4778
3799
|
const path = ".github/workflows/decantr-ci.yml";
|
|
4779
3800
|
const absolute = resolve(outputRoot, path);
|
|
4780
|
-
if (
|
|
3801
|
+
if (existsSync13(absolute) && !options.force) {
|
|
4781
3802
|
throw new Error(`${path} already exists. Re-run with --force to replace it.`);
|
|
4782
3803
|
}
|
|
4783
3804
|
writeOutput(
|
|
@@ -4826,7 +3847,7 @@ async function runWorkspaceCi(root, options) {
|
|
|
4826
3847
|
}
|
|
4827
3848
|
async function runProjectCi(root, options) {
|
|
4828
3849
|
const workspaceInfo = resolveWorkspaceInfo(root, options.project);
|
|
4829
|
-
if (options.project && !
|
|
3850
|
+
if (options.project && !existsSync13(workspaceInfo.appRoot)) {
|
|
4830
3851
|
console.error(`${RED2}Project path does not exist: ${options.project}${RESET3}`);
|
|
4831
3852
|
return 1;
|
|
4832
3853
|
}
|
|
@@ -4900,8 +3921,8 @@ async function cmdCi(args = ["ci"], root = process.cwd()) {
|
|
|
4900
3921
|
}
|
|
4901
3922
|
|
|
4902
3923
|
// src/commands/create.ts
|
|
4903
|
-
import { existsSync as
|
|
4904
|
-
import { join as
|
|
3924
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync9, writeFileSync as writeFileSync10 } from "fs";
|
|
3925
|
+
import { join as join16 } from "path";
|
|
4905
3926
|
import {
|
|
4906
3927
|
CONTENT_TYPE_TO_API_CONTENT_TYPE,
|
|
4907
3928
|
CONTENT_TYPES
|
|
@@ -5097,25 +4118,25 @@ function cmdCreate(type, name, projectRoot = process.cwd()) {
|
|
|
5097
4118
|
}
|
|
5098
4119
|
const contentType = type;
|
|
5099
4120
|
const plural = PLURAL[contentType];
|
|
5100
|
-
const customDir =
|
|
5101
|
-
const filePath =
|
|
5102
|
-
if (
|
|
4121
|
+
const customDir = join16(projectRoot, ".decantr", "custom", plural);
|
|
4122
|
+
const filePath = join16(customDir, `${name}.json`);
|
|
4123
|
+
if (existsSync14(filePath)) {
|
|
5103
4124
|
console.error(`${type} "${name}" already exists at ${filePath}`);
|
|
5104
4125
|
process.exitCode = 1;
|
|
5105
4126
|
return;
|
|
5106
4127
|
}
|
|
5107
|
-
|
|
4128
|
+
mkdirSync9(customDir, { recursive: true });
|
|
5108
4129
|
const skeleton = getSkeleton(contentType, name, humanizeId(name));
|
|
5109
|
-
|
|
4130
|
+
writeFileSync10(filePath, JSON.stringify(skeleton, null, 2));
|
|
5110
4131
|
console.log(`Created ${type} "${name}" at ${filePath}`);
|
|
5111
4132
|
console.log(`Edit it, then publish with: decantr publish ${type} ${name}`);
|
|
5112
4133
|
}
|
|
5113
4134
|
|
|
5114
4135
|
// src/commands/doctor.ts
|
|
5115
|
-
import { existsSync as
|
|
5116
|
-
import { dirname as dirname4, join as
|
|
4136
|
+
import { existsSync as existsSync15, readdirSync as readdirSync5, readFileSync as readFileSync12 } from "fs";
|
|
4137
|
+
import { dirname as dirname4, join as join17, relative as relative4 } from "path";
|
|
5117
4138
|
import { fileURLToPath } from "url";
|
|
5118
|
-
import { isV4 as
|
|
4139
|
+
import { isV4 as isV43 } from "@decantr/essence-spec";
|
|
5119
4140
|
import { collectMissingPackManifestFiles } from "@decantr/verifier";
|
|
5120
4141
|
var BOLD3 = "\x1B[1m";
|
|
5121
4142
|
var DIM4 = "\x1B[2m";
|
|
@@ -5125,25 +4146,25 @@ var YELLOW3 = "\x1B[33m";
|
|
|
5125
4146
|
var CYAN2 = "\x1B[36m";
|
|
5126
4147
|
var RESET4 = "\x1B[0m";
|
|
5127
4148
|
function readJson2(path) {
|
|
5128
|
-
if (!
|
|
4149
|
+
if (!existsSync15(path)) return null;
|
|
5129
4150
|
try {
|
|
5130
|
-
return JSON.parse(
|
|
4151
|
+
return JSON.parse(readFileSync12(path, "utf-8"));
|
|
5131
4152
|
} catch {
|
|
5132
4153
|
return null;
|
|
5133
4154
|
}
|
|
5134
4155
|
}
|
|
5135
4156
|
function isContractOnlyProject(projectRoot) {
|
|
5136
|
-
const projectJson = readJson2(
|
|
4157
|
+
const projectJson = readJson2(join17(projectRoot, ".decantr", "project.json"));
|
|
5137
4158
|
return projectJson?.initialized?.adoptionMode === "contract-only" || projectJson?.initialized?.adoptionMode === "style-bridge";
|
|
5138
4159
|
}
|
|
5139
4160
|
function readCliPackageVersion() {
|
|
5140
|
-
const packagePath =
|
|
5141
|
-
const srcPackagePath =
|
|
4161
|
+
const packagePath = join17(dirname4(fileURLToPath(import.meta.url)), "..", "..", "package.json");
|
|
4162
|
+
const srcPackagePath = join17(dirname4(fileURLToPath(import.meta.url)), "..", "package.json");
|
|
5142
4163
|
const pkg = readJson2(packagePath) ?? readJson2(srcPackagePath);
|
|
5143
4164
|
return pkg?.version ?? "unknown";
|
|
5144
4165
|
}
|
|
5145
4166
|
function readPackageJson2(dir) {
|
|
5146
|
-
return readJson2(
|
|
4167
|
+
return readJson2(join17(dir, "package.json"));
|
|
5147
4168
|
}
|
|
5148
4169
|
function detectPackageManager3(root) {
|
|
5149
4170
|
const pkg = readPackageJson2(root);
|
|
@@ -5151,10 +4172,10 @@ function detectPackageManager3(root) {
|
|
|
5151
4172
|
if (declared === "pnpm" || declared === "npm" || declared === "yarn" || declared === "bun") {
|
|
5152
4173
|
return declared;
|
|
5153
4174
|
}
|
|
5154
|
-
if (
|
|
5155
|
-
if (
|
|
5156
|
-
if (
|
|
5157
|
-
if (
|
|
4175
|
+
if (existsSync15(join17(root, "pnpm-lock.yaml"))) return "pnpm";
|
|
4176
|
+
if (existsSync15(join17(root, "package-lock.json"))) return "npm";
|
|
4177
|
+
if (existsSync15(join17(root, "yarn.lock"))) return "yarn";
|
|
4178
|
+
if (existsSync15(join17(root, "bun.lock")) || existsSync15(join17(root, "bun.lockb"))) return "bun";
|
|
5158
4179
|
return "unknown";
|
|
5159
4180
|
}
|
|
5160
4181
|
function localCliDependency(root) {
|
|
@@ -5164,7 +4185,7 @@ function localCliDependency(root) {
|
|
|
5164
4185
|
function hasWorkspaceMarker2(root) {
|
|
5165
4186
|
const pkg = readPackageJson2(root);
|
|
5166
4187
|
return Boolean(
|
|
5167
|
-
|
|
4188
|
+
existsSync15(join17(root, "pnpm-workspace.yaml")) || existsSync15(join17(root, "turbo.json")) || existsSync15(join17(root, "nx.json")) || pkg?.workspaces
|
|
5168
4189
|
);
|
|
5169
4190
|
}
|
|
5170
4191
|
function pinCliCommand2(packageManager, root) {
|
|
@@ -5182,13 +4203,13 @@ function pinCliCommand2(packageManager, root) {
|
|
|
5182
4203
|
}
|
|
5183
4204
|
}
|
|
5184
4205
|
function rel(root, path) {
|
|
5185
|
-
const value =
|
|
4206
|
+
const value = relative4(root, path).replace(/\\/g, "/");
|
|
5186
4207
|
return value || ".";
|
|
5187
4208
|
}
|
|
5188
4209
|
function hasAnyFile(dir, names) {
|
|
5189
4210
|
for (const name of names) {
|
|
5190
|
-
const path =
|
|
5191
|
-
if (
|
|
4211
|
+
const path = join17(dir, name);
|
|
4212
|
+
if (existsSync15(path)) return name;
|
|
5192
4213
|
}
|
|
5193
4214
|
return null;
|
|
5194
4215
|
}
|
|
@@ -5210,7 +4231,7 @@ function detectDesignAuthority(workspaceRoot, appRoot) {
|
|
|
5210
4231
|
"libs/design-system"
|
|
5211
4232
|
];
|
|
5212
4233
|
for (const candidate of sharedCandidates) {
|
|
5213
|
-
if (
|
|
4234
|
+
if (existsSync15(join17(workspaceRoot, candidate))) found.add(candidate);
|
|
5214
4235
|
}
|
|
5215
4236
|
for (const root of [appRoot, workspaceRoot]) {
|
|
5216
4237
|
const label = root === appRoot ? "app" : "workspace";
|
|
@@ -5221,9 +4242,9 @@ function detectDesignAuthority(workspaceRoot, appRoot) {
|
|
|
5221
4242
|
"tailwind.config.cjs"
|
|
5222
4243
|
]);
|
|
5223
4244
|
if (tailwind) found.add(`${label}:${tailwind}`);
|
|
5224
|
-
if (
|
|
5225
|
-
if (
|
|
5226
|
-
if (
|
|
4245
|
+
if (existsSync15(join17(root, ".storybook"))) found.add(`${label}:.storybook`);
|
|
4246
|
+
if (existsSync15(join17(root, "components", "ui"))) found.add(`${label}:components/ui`);
|
|
4247
|
+
if (existsSync15(join17(root, "src", "components", "ui"))) found.add(`${label}:src/components/ui`);
|
|
5227
4248
|
if (packageHasDependency(root, ["@storybook/react", "storybook"]))
|
|
5228
4249
|
found.add(`${label}:storybook`);
|
|
5229
4250
|
if (packageHasDependency(root, ["tailwindcss"])) found.add(`${label}:tailwindcss`);
|
|
@@ -5235,18 +4256,18 @@ function detectDesignAuthority(workspaceRoot, appRoot) {
|
|
|
5235
4256
|
}
|
|
5236
4257
|
function findCiFiles(root) {
|
|
5237
4258
|
const files = [];
|
|
5238
|
-
const workflows =
|
|
5239
|
-
if (
|
|
5240
|
-
for (const entry of
|
|
4259
|
+
const workflows = join17(root, ".github", "workflows");
|
|
4260
|
+
if (existsSync15(workflows)) {
|
|
4261
|
+
for (const entry of readdirSync5(workflows, { withFileTypes: true })) {
|
|
5241
4262
|
if (!entry.isFile()) continue;
|
|
5242
|
-
const path =
|
|
5243
|
-
const text =
|
|
4263
|
+
const path = join17(workflows, entry.name);
|
|
4264
|
+
const text = readFileSync12(path, "utf-8");
|
|
5244
4265
|
if (text.includes("decantr")) files.push(rel(root, path));
|
|
5245
4266
|
}
|
|
5246
4267
|
}
|
|
5247
4268
|
for (const candidate of ["Jenkinsfile", ".gitlab-ci.yml", "azure-pipelines.yml", "BUILD"]) {
|
|
5248
|
-
const path =
|
|
5249
|
-
if (
|
|
4269
|
+
const path = join17(root, candidate);
|
|
4270
|
+
if (existsSync15(path) && readFileSync12(path, "utf-8").includes("decantr")) {
|
|
5250
4271
|
files.push(candidate);
|
|
5251
4272
|
}
|
|
5252
4273
|
}
|
|
@@ -5372,6 +4393,28 @@ function deriveAdoptionLane(input) {
|
|
|
5372
4393
|
nextChoice: "Use task and verify to keep generated routes aligned with the compiled contract."
|
|
5373
4394
|
};
|
|
5374
4395
|
}
|
|
4396
|
+
function inspectGraphArtifacts(appRoot, workspaceRoot) {
|
|
4397
|
+
const snapshotPath = join17(appRoot, ".decantr", "graph", "graph.snapshot.json");
|
|
4398
|
+
const capsulePath = join17(appRoot, ".decantr", "graph", "contract-capsule.json");
|
|
4399
|
+
try {
|
|
4400
|
+
const artifacts = buildGraphArtifacts(appRoot);
|
|
4401
|
+
return {
|
|
4402
|
+
snapshotPresent: existsSync15(snapshotPath),
|
|
4403
|
+
capsulePresent: existsSync15(capsulePath),
|
|
4404
|
+
artifactsCurrent: artifacts ? artifacts.staleArtifacts.length === 0 : null,
|
|
4405
|
+
staleArtifacts: artifacts ? artifacts.staleArtifacts.map((path) => rel(workspaceRoot, path)) : [],
|
|
4406
|
+
error: null
|
|
4407
|
+
};
|
|
4408
|
+
} catch (error3) {
|
|
4409
|
+
return {
|
|
4410
|
+
snapshotPresent: existsSync15(snapshotPath),
|
|
4411
|
+
capsulePresent: existsSync15(capsulePath),
|
|
4412
|
+
artifactsCurrent: false,
|
|
4413
|
+
staleArtifacts: [],
|
|
4414
|
+
error: error3.message
|
|
4415
|
+
};
|
|
4416
|
+
}
|
|
4417
|
+
}
|
|
5375
4418
|
function buildDoctorReport(root, args) {
|
|
5376
4419
|
let projectArg;
|
|
5377
4420
|
for (let index = 1; index < args.length; index += 1) {
|
|
@@ -5383,18 +4426,18 @@ function buildDoctorReport(root, args) {
|
|
|
5383
4426
|
const workspaceInfo = resolveWorkspaceInfo(root, projectArg);
|
|
5384
4427
|
const workspaceRoot = workspaceInfo.workspaceRoot;
|
|
5385
4428
|
const appRoot = workspaceMode ? workspaceRoot : workspaceInfo.appRoot;
|
|
5386
|
-
const projectMissing = Boolean(projectArg && !
|
|
4429
|
+
const projectMissing = Boolean(projectArg && !existsSync15(appRoot));
|
|
5387
4430
|
const projectPath = appRoot === workspaceRoot ? null : rel(workspaceRoot, appRoot);
|
|
5388
4431
|
const packageManager = detectPackageManager3(workspaceRoot);
|
|
5389
4432
|
const cliDependency = localCliDependency(workspaceRoot);
|
|
5390
4433
|
const detected = detectProject(appRoot);
|
|
5391
|
-
const projectJson = readJson2(
|
|
5392
|
-
const essence = readJson2(
|
|
4434
|
+
const projectJson = readJson2(join17(appRoot, ".decantr", "project.json"));
|
|
4435
|
+
const essence = readJson2(join17(appRoot, "decantr.essence.json"));
|
|
5393
4436
|
const essenceVersion = essence && typeof essence === "object" && "version" in essence ? String(essence.version) : null;
|
|
5394
|
-
const contextDir =
|
|
5395
|
-
const packManifestPresent =
|
|
5396
|
-
const reviewPackPresent =
|
|
5397
|
-
const artifactReadmePresent =
|
|
4437
|
+
const contextDir = join17(appRoot, ".decantr", "context");
|
|
4438
|
+
const packManifestPresent = existsSync15(join17(contextDir, "pack-manifest.json"));
|
|
4439
|
+
const reviewPackPresent = existsSync15(join17(contextDir, "review-pack.json"));
|
|
4440
|
+
const artifactReadmePresent = existsSync15(join17(appRoot, ARTIFACT_README_PATH));
|
|
5398
4441
|
const projects = listWorkspaceProjects(workspaceRoot);
|
|
5399
4442
|
const candidates = listWorkspaceCandidates(workspaceRoot, projects).map((candidate) => ({
|
|
5400
4443
|
path: candidate.path,
|
|
@@ -5405,22 +4448,23 @@ function buildDoctorReport(root, args) {
|
|
|
5405
4448
|
const workflowMode = projectJson?.initialized?.workflowMode ?? null;
|
|
5406
4449
|
const adoptionMode = projectJson?.initialized?.adoptionMode ?? null;
|
|
5407
4450
|
const packHydrationOptional = adoptionMode === "contract-only" || adoptionMode === "style-bridge";
|
|
5408
|
-
const localPatternsPresent =
|
|
5409
|
-
const localRulesPresent =
|
|
5410
|
-
const styleBridgePresent =
|
|
5411
|
-
const styleBridgeProposalPresent =
|
|
4451
|
+
const localPatternsPresent = existsSync15(localPatternsPath(appRoot));
|
|
4452
|
+
const localRulesPresent = existsSync15(localRulesPath(appRoot));
|
|
4453
|
+
const styleBridgePresent = existsSync15(styleBridgePath(appRoot));
|
|
4454
|
+
const styleBridgeProposalPresent = existsSync15(styleBridgeProposalPath(appRoot));
|
|
4455
|
+
const graphArtifacts = inspectGraphArtifacts(appRoot, workspaceRoot);
|
|
5412
4456
|
const missingPackReferences = workspaceMode ? projects.flatMap(
|
|
5413
|
-
(project) => collectMissingPackManifestFiles(
|
|
4457
|
+
(project) => collectMissingPackManifestFiles(join17(workspaceRoot, project.path)).map(
|
|
5414
4458
|
(missing) => `${project.path}/${missing.relativePath}`
|
|
5415
4459
|
)
|
|
5416
4460
|
) : collectMissingPackManifestFiles(appRoot).map((missing) => missing.relativePath);
|
|
5417
4461
|
const workspaceProjectsMissingManifest = workspaceMode ? projects.map((project) => project.path).filter((projectPath2) => {
|
|
5418
|
-
const projectContextDir =
|
|
5419
|
-
return !isContractOnlyProject(
|
|
4462
|
+
const projectContextDir = join17(workspaceRoot, projectPath2, ".decantr", "context");
|
|
4463
|
+
return !isContractOnlyProject(join17(workspaceRoot, projectPath2)) && !existsSync15(join17(projectContextDir, "pack-manifest.json"));
|
|
5420
4464
|
}) : [];
|
|
5421
4465
|
const workspaceProjectsMissingReviewPack = workspaceMode ? projects.map((project) => project.path).filter((projectPath2) => {
|
|
5422
|
-
const projectContextDir =
|
|
5423
|
-
return !isContractOnlyProject(
|
|
4466
|
+
const projectContextDir = join17(workspaceRoot, projectPath2, ".decantr", "context");
|
|
4467
|
+
return !isContractOnlyProject(join17(workspaceRoot, projectPath2)) && !existsSync15(join17(projectContextDir, "review-pack.json"));
|
|
5424
4468
|
}) : [];
|
|
5425
4469
|
const issues = [];
|
|
5426
4470
|
if (projectMissing) {
|
|
@@ -5437,7 +4481,7 @@ function buildDoctorReport(root, args) {
|
|
|
5437
4481
|
message: "No decantr.essence.json found for this app.",
|
|
5438
4482
|
nextCommand: projectPath ? `decantr adopt --project ${projectPath} --yes` : "decantr adopt --yes"
|
|
5439
4483
|
});
|
|
5440
|
-
} else if (essenceVersion && !
|
|
4484
|
+
} else if (essenceVersion && !isV43(essence)) {
|
|
5441
4485
|
issues.push({
|
|
5442
4486
|
category: "migration",
|
|
5443
4487
|
severity: "error",
|
|
@@ -5477,7 +4521,7 @@ function buildDoctorReport(root, args) {
|
|
|
5477
4521
|
nextCommand: "decantr refresh"
|
|
5478
4522
|
});
|
|
5479
4523
|
}
|
|
5480
|
-
if (essenceVersion === "4.0.0" && !packHydrationOptional && (!
|
|
4524
|
+
if (essenceVersion === "4.0.0" && !packHydrationOptional && (!existsSync15(contextDir) || !packManifestPresent)) {
|
|
5481
4525
|
issues.push({
|
|
5482
4526
|
category: "generated-artifact",
|
|
5483
4527
|
severity: "warn",
|
|
@@ -5493,6 +4537,14 @@ function buildDoctorReport(root, args) {
|
|
|
5493
4537
|
nextCommand: projectPath ? `decantr registry compile-packs ${projectPath}/decantr.essence.json --write-context` : "decantr registry compile-packs decantr.essence.json --write-context"
|
|
5494
4538
|
});
|
|
5495
4539
|
}
|
|
4540
|
+
if (essenceVersion === "4.0.0" && (Boolean(projectJson) || graphArtifacts.snapshotPresent || graphArtifacts.capsulePresent) && (graphArtifacts.error || graphArtifacts.artifactsCurrent === false)) {
|
|
4541
|
+
issues.push({
|
|
4542
|
+
category: "graph",
|
|
4543
|
+
severity: "warn",
|
|
4544
|
+
message: graphArtifacts.error ? `Typed Contract graph could not be derived: ${graphArtifacts.error}` : "Typed Contract graph artifacts are missing or stale.",
|
|
4545
|
+
nextCommand: projectPath ? `decantr graph --project ${projectPath}` : "decantr graph"
|
|
4546
|
+
});
|
|
4547
|
+
}
|
|
5496
4548
|
if (workspaceMode && (workspaceProjectsMissingManifest.length > 0 || workspaceProjectsMissingReviewPack.length > 0 || missingPackReferences.length > 0)) {
|
|
5497
4549
|
issues.push({
|
|
5498
4550
|
category: "generated-artifact",
|
|
@@ -5562,6 +4614,10 @@ function buildDoctorReport(root, args) {
|
|
|
5562
4614
|
recommendedNextCommands,
|
|
5563
4615
|
issues.find((issue) => issue.category === "ci" && issue.message.includes("not pinned"))?.nextCommand
|
|
5564
4616
|
);
|
|
4617
|
+
appendUnique(
|
|
4618
|
+
recommendedNextCommands,
|
|
4619
|
+
issues.find((issue) => issue.category === "graph")?.nextCommand
|
|
4620
|
+
);
|
|
5565
4621
|
if (workflowMode === "brownfield-attach" && !localPatternsPresent) {
|
|
5566
4622
|
appendUnique(recommendedNextCommands, `decantr codify --from-audit${projectFlag}`);
|
|
5567
4623
|
appendUnique(recommendedNextCommands, `decantr codify --accept${projectFlag}`);
|
|
@@ -5611,11 +4667,16 @@ function buildDoctorReport(root, args) {
|
|
|
5611
4667
|
},
|
|
5612
4668
|
generatedArtifacts: {
|
|
5613
4669
|
contextDirPresent: workspaceMode ? projects.some(
|
|
5614
|
-
(project) =>
|
|
5615
|
-
) :
|
|
4670
|
+
(project) => existsSync15(join17(workspaceRoot, project.path, ".decantr", "context"))
|
|
4671
|
+
) : existsSync15(contextDir),
|
|
5616
4672
|
packManifestPresent: workspaceMode ? projects.length > 0 && workspaceProjectsMissingManifest.length === 0 : packManifestPresent,
|
|
5617
4673
|
reviewPackPresent: workspaceMode ? projects.length > 0 && workspaceProjectsMissingReviewPack.length === 0 : reviewPackPresent,
|
|
5618
|
-
missingReferencedFiles: missingPackReferences.slice(0, 25)
|
|
4674
|
+
missingReferencedFiles: missingPackReferences.slice(0, 25),
|
|
4675
|
+
graphSnapshotPresent: graphArtifacts.snapshotPresent,
|
|
4676
|
+
graphCapsulePresent: graphArtifacts.capsulePresent,
|
|
4677
|
+
graphArtifactsCurrent: graphArtifacts.artifactsCurrent,
|
|
4678
|
+
graphStaleArtifacts: graphArtifacts.staleArtifacts.slice(0, 25),
|
|
4679
|
+
graphError: graphArtifacts.error
|
|
5619
4680
|
},
|
|
5620
4681
|
localLaw: {
|
|
5621
4682
|
patternsPresent: localPatternsPresent,
|
|
@@ -5624,7 +4685,7 @@ function buildDoctorReport(root, args) {
|
|
|
5624
4685
|
styleBridgeProposalPresent
|
|
5625
4686
|
},
|
|
5626
4687
|
visualEvidence: {
|
|
5627
|
-
manifestPresent:
|
|
4688
|
+
manifestPresent: existsSync15(join17(appRoot, ".decantr", "evidence", "visual-manifest.json"))
|
|
5628
4689
|
},
|
|
5629
4690
|
lane,
|
|
5630
4691
|
designAuthority,
|
|
@@ -5668,6 +4729,8 @@ function formatDoctorText(report) {
|
|
|
5668
4729
|
` Review pack: ${report.generatedArtifacts.reviewPackPresent ? "present" : "missing"}`,
|
|
5669
4730
|
` Manifest references: ${report.generatedArtifacts.missingReferencedFiles.length === 0 ? "complete" : `${report.generatedArtifacts.missingReferencedFiles.length} missing`}`,
|
|
5670
4731
|
` Artifact guide: ${report.project.artifactReadmePresent ? "present" : "missing"}`,
|
|
4732
|
+
` Typed graph: ${report.generatedArtifacts.graphError ? "error" : report.generatedArtifacts.graphArtifactsCurrent === true ? "current" : report.generatedArtifacts.graphArtifactsCurrent === false ? "stale or missing" : "not generated"}`,
|
|
4733
|
+
` Graph capsule: ${report.generatedArtifacts.graphCapsulePresent ? "present" : "missing"}`,
|
|
5671
4734
|
"",
|
|
5672
4735
|
`${BOLD3}Local Law:${RESET4}`,
|
|
5673
4736
|
` Local patterns: ${report.localLaw.patternsPresent ? "present" : "missing"}`,
|
|
@@ -5732,17 +4795,17 @@ async function cmdDoctor(args = ["doctor"], root = process.cwd()) {
|
|
|
5732
4795
|
}
|
|
5733
4796
|
|
|
5734
4797
|
// src/commands/export.ts
|
|
5735
|
-
import { existsSync as
|
|
5736
|
-
import { dirname as dirname5, isAbsolute, join as
|
|
4798
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync10, readFileSync as readFileSync13, writeFileSync as writeFileSync11 } from "fs";
|
|
4799
|
+
import { dirname as dirname5, isAbsolute, join as join18 } from "path";
|
|
5737
4800
|
var GREEN5 = "\x1B[32m";
|
|
5738
4801
|
var RED4 = "\x1B[31m";
|
|
5739
4802
|
var DIM5 = "\x1B[2m";
|
|
5740
4803
|
var RESET5 = "\x1B[0m";
|
|
5741
4804
|
function readAdoptionMode(projectRoot) {
|
|
5742
|
-
const projectJsonPath =
|
|
5743
|
-
if (!
|
|
4805
|
+
const projectJsonPath = join18(projectRoot, ".decantr", "project.json");
|
|
4806
|
+
if (!existsSync16(projectJsonPath)) return null;
|
|
5744
4807
|
try {
|
|
5745
|
-
const parsed = JSON.parse(
|
|
4808
|
+
const parsed = JSON.parse(readFileSync13(projectJsonPath, "utf-8"));
|
|
5746
4809
|
return typeof parsed.initialized?.adoptionMode === "string" ? parsed.initialized.adoptionMode : null;
|
|
5747
4810
|
} catch {
|
|
5748
4811
|
return null;
|
|
@@ -5750,7 +4813,7 @@ function readAdoptionMode(projectRoot) {
|
|
|
5750
4813
|
}
|
|
5751
4814
|
function resolveOutputPath(projectRoot, output, fallback) {
|
|
5752
4815
|
if (!output) return fallback;
|
|
5753
|
-
return isAbsolute(output) ? output :
|
|
4816
|
+
return isAbsolute(output) ? output : join18(projectRoot, output);
|
|
5754
4817
|
}
|
|
5755
4818
|
var SHADCN_MAP = {
|
|
5756
4819
|
"--d-bg": "--background",
|
|
@@ -5911,21 +4974,21 @@ function generateFigmaTokens(tokens) {
|
|
|
5911
4974
|
`;
|
|
5912
4975
|
}
|
|
5913
4976
|
async function cmdExport(target, projectRoot, options = {}) {
|
|
5914
|
-
const essencePath =
|
|
5915
|
-
const tokensPath =
|
|
5916
|
-
if (!
|
|
4977
|
+
const essencePath = join18(projectRoot, "decantr.essence.json");
|
|
4978
|
+
const tokensPath = join18(projectRoot, "src", "styles", "tokens.css");
|
|
4979
|
+
if (!existsSync16(essencePath)) {
|
|
5917
4980
|
console.error(`${RED4}No decantr.essence.json found. Run \`decantr init\` first.${RESET5}`);
|
|
5918
4981
|
process.exitCode = 1;
|
|
5919
4982
|
return;
|
|
5920
4983
|
}
|
|
5921
|
-
if (!
|
|
4984
|
+
if (!existsSync16(tokensPath)) {
|
|
5922
4985
|
const adoptionMode = readAdoptionMode(projectRoot);
|
|
5923
4986
|
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.";
|
|
5924
4987
|
console.error(`${RED4}${message}${RESET5}`);
|
|
5925
4988
|
process.exitCode = 1;
|
|
5926
4989
|
return;
|
|
5927
4990
|
}
|
|
5928
|
-
const tokensCSS =
|
|
4991
|
+
const tokensCSS = readFileSync13(tokensPath, "utf-8");
|
|
5929
4992
|
const tokens = parseTokensCSS(tokensCSS);
|
|
5930
4993
|
if (tokens.size === 0) {
|
|
5931
4994
|
console.error(`${RED4}No --d-* tokens found in tokens.css.${RESET5}`);
|
|
@@ -5937,12 +5000,12 @@ async function cmdExport(target, projectRoot, options = {}) {
|
|
|
5937
5000
|
const cssOut = resolveOutputPath(
|
|
5938
5001
|
projectRoot,
|
|
5939
5002
|
options.output,
|
|
5940
|
-
|
|
5003
|
+
join18(projectRoot, "src", "styles", "shadcn-theme.css")
|
|
5941
5004
|
);
|
|
5942
|
-
const jsonOut =
|
|
5005
|
+
const jsonOut = join18(projectRoot, "components.json");
|
|
5943
5006
|
ensureDir(cssOut);
|
|
5944
|
-
|
|
5945
|
-
|
|
5007
|
+
writeFileSync11(cssOut, generateShadcnCSS(tokens), "utf-8");
|
|
5008
|
+
writeFileSync11(jsonOut, generateShadcnComponentsJSON(), "utf-8");
|
|
5946
5009
|
console.log(`${GREEN5}Exported shadcn theme:${RESET5}`);
|
|
5947
5010
|
console.log(` ${DIM5}CSS:${RESET5} ${cssOut}`);
|
|
5948
5011
|
console.log(` ${DIM5}JSON:${RESET5} ${jsonOut}`);
|
|
@@ -5952,10 +5015,10 @@ async function cmdExport(target, projectRoot, options = {}) {
|
|
|
5952
5015
|
const out = resolveOutputPath(
|
|
5953
5016
|
projectRoot,
|
|
5954
5017
|
options.output,
|
|
5955
|
-
|
|
5018
|
+
join18(projectRoot, "tailwind.decantr.config.ts")
|
|
5956
5019
|
);
|
|
5957
5020
|
ensureDir(out);
|
|
5958
|
-
|
|
5021
|
+
writeFileSync11(out, generateTailwindConfig(tokens), "utf-8");
|
|
5959
5022
|
console.log(`${GREEN5}Exported Tailwind config:${RESET5}`);
|
|
5960
5023
|
console.log(` ${DIM5}File:${RESET5} ${out}`);
|
|
5961
5024
|
break;
|
|
@@ -5964,10 +5027,10 @@ async function cmdExport(target, projectRoot, options = {}) {
|
|
|
5964
5027
|
const out = resolveOutputPath(
|
|
5965
5028
|
projectRoot,
|
|
5966
5029
|
options.output,
|
|
5967
|
-
|
|
5030
|
+
join18(projectRoot, "decantr-tokens.css")
|
|
5968
5031
|
);
|
|
5969
5032
|
ensureDir(out);
|
|
5970
|
-
|
|
5033
|
+
writeFileSync11(out, generateCSSVars(tokens), "utf-8");
|
|
5971
5034
|
console.log(`${GREEN5}Exported CSS variables:${RESET5}`);
|
|
5972
5035
|
console.log(` ${DIM5}File:${RESET5} ${out}`);
|
|
5973
5036
|
break;
|
|
@@ -5976,10 +5039,10 @@ async function cmdExport(target, projectRoot, options = {}) {
|
|
|
5976
5039
|
const out = resolveOutputPath(
|
|
5977
5040
|
projectRoot,
|
|
5978
5041
|
options.output,
|
|
5979
|
-
|
|
5042
|
+
join18(projectRoot, ".decantr", "design", "figma-tokens.json")
|
|
5980
5043
|
);
|
|
5981
5044
|
ensureDir(out);
|
|
5982
|
-
|
|
5045
|
+
writeFileSync11(out, generateFigmaTokens(tokens), "utf-8");
|
|
5983
5046
|
console.log(`${GREEN5}Exported Figma/Tokens Studio tokens:${RESET5}`);
|
|
5984
5047
|
console.log(` ${DIM5}File:${RESET5} ${out}`);
|
|
5985
5048
|
break;
|
|
@@ -5988,15 +5051,15 @@ async function cmdExport(target, projectRoot, options = {}) {
|
|
|
5988
5051
|
}
|
|
5989
5052
|
function ensureDir(filePath) {
|
|
5990
5053
|
const dir = dirname5(filePath);
|
|
5991
|
-
if (!
|
|
5992
|
-
|
|
5054
|
+
if (!existsSync16(dir)) {
|
|
5055
|
+
mkdirSync10(dir, { recursive: true });
|
|
5993
5056
|
}
|
|
5994
5057
|
}
|
|
5995
5058
|
|
|
5996
5059
|
// src/commands/magic.ts
|
|
5997
|
-
import { existsSync as
|
|
5060
|
+
import { existsSync as existsSync17 } from "fs";
|
|
5998
5061
|
import * as fs from "fs/promises";
|
|
5999
|
-
import { join as
|
|
5062
|
+
import { join as join19 } from "path";
|
|
6000
5063
|
var BOLD4 = "\x1B[1m";
|
|
6001
5064
|
var DIM6 = "\x1B[2m";
|
|
6002
5065
|
var RESET6 = "\x1B[0m";
|
|
@@ -6220,8 +5283,8 @@ async function cmdMagic(prompt, projectRoot, options) {
|
|
|
6220
5283
|
console.log(` Archetype: ${intent.archetype}`);
|
|
6221
5284
|
}
|
|
6222
5285
|
console.log("");
|
|
6223
|
-
const essencePath =
|
|
6224
|
-
if (
|
|
5286
|
+
const essencePath = join19(projectRoot, "decantr.essence.json");
|
|
5287
|
+
if (existsSync17(essencePath)) {
|
|
6225
5288
|
const projectFlag = options.projectLabel ? ` --project ${options.projectLabel}` : "";
|
|
6226
5289
|
console.log(`${YELLOW4} Decantr is already attached to this project.${RESET6}`);
|
|
6227
5290
|
console.log(
|
|
@@ -6254,7 +5317,7 @@ async function cmdMagic(prompt, projectRoot, options) {
|
|
|
6254
5317
|
return;
|
|
6255
5318
|
}
|
|
6256
5319
|
const registryClient = new RegistryClient({
|
|
6257
|
-
cacheDir:
|
|
5320
|
+
cacheDir: join19(projectRoot, ".decantr", "cache"),
|
|
6258
5321
|
apiUrl: options.registry,
|
|
6259
5322
|
offline: options.offline
|
|
6260
5323
|
});
|
|
@@ -6525,14 +5588,14 @@ async function cmdMagic(prompt, projectRoot, options) {
|
|
|
6525
5588
|
if (result.gitignoreUpdated) {
|
|
6526
5589
|
console.log(` ${dim(".gitignore updated")}`);
|
|
6527
5590
|
}
|
|
6528
|
-
const contextDir =
|
|
5591
|
+
const contextDir = join19(projectRoot, ".decantr", "context");
|
|
6529
5592
|
let sectionCount = 0;
|
|
6530
5593
|
try {
|
|
6531
5594
|
const files = await fs.readdir(contextDir);
|
|
6532
5595
|
sectionCount = files.filter((f) => f.startsWith("section-")).length;
|
|
6533
5596
|
} catch {
|
|
6534
5597
|
}
|
|
6535
|
-
const treatmentsPath =
|
|
5598
|
+
const treatmentsPath = join19(projectRoot, "src", "styles", "treatments.css");
|
|
6536
5599
|
let hasLayers = false;
|
|
6537
5600
|
try {
|
|
6538
5601
|
const css = await fs.readFile(treatmentsPath, "utf-8");
|
|
@@ -6565,10 +5628,10 @@ ${GREEN6}${BOLD4}Quality summary:${RESET6}`);
|
|
|
6565
5628
|
}
|
|
6566
5629
|
|
|
6567
5630
|
// src/commands/migrate.ts
|
|
6568
|
-
import { copyFileSync, existsSync as
|
|
6569
|
-
import { join as
|
|
5631
|
+
import { copyFileSync, existsSync as existsSync18, readFileSync as readFileSync14, writeFileSync as writeFileSync12 } from "fs";
|
|
5632
|
+
import { join as join20 } from "path";
|
|
6570
5633
|
import {
|
|
6571
|
-
isV4 as
|
|
5634
|
+
isV4 as isV44,
|
|
6572
5635
|
migrateToV4,
|
|
6573
5636
|
validateEssence,
|
|
6574
5637
|
validateLegacyEssenceForMigration
|
|
@@ -6579,12 +5642,12 @@ var YELLOW5 = "\x1B[33m";
|
|
|
6579
5642
|
var RESET7 = "\x1B[0m";
|
|
6580
5643
|
var DIM7 = "\x1B[2m";
|
|
6581
5644
|
function migrateEssenceFile(essencePath) {
|
|
6582
|
-
if (!
|
|
5645
|
+
if (!existsSync18(essencePath)) {
|
|
6583
5646
|
return { success: false, error: `File not found: ${essencePath}` };
|
|
6584
5647
|
}
|
|
6585
5648
|
let raw;
|
|
6586
5649
|
try {
|
|
6587
|
-
raw =
|
|
5650
|
+
raw = readFileSync14(essencePath, "utf-8");
|
|
6588
5651
|
} catch (e) {
|
|
6589
5652
|
return { success: false, error: `Could not read ${essencePath}: ${e.message}` };
|
|
6590
5653
|
}
|
|
@@ -6594,7 +5657,7 @@ function migrateEssenceFile(essencePath) {
|
|
|
6594
5657
|
} catch (e) {
|
|
6595
5658
|
return { success: false, error: `Invalid JSON: ${e.message}` };
|
|
6596
5659
|
}
|
|
6597
|
-
if (
|
|
5660
|
+
if (isV44(essence)) {
|
|
6598
5661
|
return { success: true, alreadyV4: true, essence };
|
|
6599
5662
|
}
|
|
6600
5663
|
const preValidation = validateLegacyEssenceForMigration(essence);
|
|
@@ -6625,7 +5688,7 @@ function migrateEssenceFile(essencePath) {
|
|
|
6625
5688
|
};
|
|
6626
5689
|
}
|
|
6627
5690
|
try {
|
|
6628
|
-
|
|
5691
|
+
writeFileSync12(essencePath, JSON.stringify(v4, null, 2) + "\n");
|
|
6629
5692
|
} catch (e) {
|
|
6630
5693
|
return {
|
|
6631
5694
|
success: false,
|
|
@@ -6646,8 +5709,8 @@ async function cmdMigrate(projectRoot = process.cwd(), args = []) {
|
|
|
6646
5709
|
process.exitCode = 1;
|
|
6647
5710
|
return;
|
|
6648
5711
|
}
|
|
6649
|
-
const essencePath =
|
|
6650
|
-
if (!
|
|
5712
|
+
const essencePath = join20(projectRoot, "decantr.essence.json");
|
|
5713
|
+
if (!existsSync18(essencePath)) {
|
|
6651
5714
|
console.error(`${RED5}No decantr.essence.json found. Run \`decantr init\` first.${RESET7}`);
|
|
6652
5715
|
process.exitCode = 1;
|
|
6653
5716
|
return;
|
|
@@ -6669,7 +5732,7 @@ async function cmdMigrate(projectRoot = process.cwd(), args = []) {
|
|
|
6669
5732
|
}
|
|
6670
5733
|
if (result.essence) {
|
|
6671
5734
|
const registryClient = new RegistryClient({
|
|
6672
|
-
cacheDir:
|
|
5735
|
+
cacheDir: join20(projectRoot, ".decantr", "cache")
|
|
6673
5736
|
});
|
|
6674
5737
|
await refreshDerivedFiles(projectRoot, result.essence, registryClient);
|
|
6675
5738
|
console.log(`${GREEN7}Derived context and execution packs refreshed.${RESET7}`);
|
|
@@ -6680,50 +5743,50 @@ async function cmdMigrate(projectRoot = process.cwd(), args = []) {
|
|
|
6680
5743
|
|
|
6681
5744
|
// src/commands/new-project.ts
|
|
6682
5745
|
import { spawnSync } from "child_process";
|
|
6683
|
-
import { existsSync as
|
|
6684
|
-
import { join as
|
|
5746
|
+
import { existsSync as existsSync20, mkdirSync as mkdirSync12 } from "fs";
|
|
5747
|
+
import { join as join22, resolve as resolve3 } from "path";
|
|
6685
5748
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
6686
5749
|
|
|
6687
5750
|
// src/offline-content.ts
|
|
6688
|
-
import { cpSync, existsSync as
|
|
6689
|
-
import { join as
|
|
5751
|
+
import { cpSync, existsSync as existsSync19, mkdirSync as mkdirSync11 } from "fs";
|
|
5752
|
+
import { join as join21, resolve as resolve2 } from "path";
|
|
6690
5753
|
var CONTENT_TYPES2 = ["archetypes", "blueprints", "patterns", "themes", "shells"];
|
|
6691
5754
|
function copyIfExists(source, target) {
|
|
6692
|
-
if (!
|
|
5755
|
+
if (!existsSync19(source)) return false;
|
|
6693
5756
|
if (resolve2(source) === resolve2(target)) return true;
|
|
6694
5757
|
cpSync(source, target, { recursive: true });
|
|
6695
5758
|
return true;
|
|
6696
5759
|
}
|
|
6697
5760
|
function hydrateContentRoot(projectDir, contentRoot) {
|
|
6698
|
-
if (!
|
|
6699
|
-
const customRoot =
|
|
6700
|
-
const cacheRoot =
|
|
6701
|
-
|
|
6702
|
-
|
|
5761
|
+
if (!existsSync19(contentRoot)) return false;
|
|
5762
|
+
const customRoot = join21(projectDir, ".decantr", "custom");
|
|
5763
|
+
const cacheRoot = join21(projectDir, ".decantr", "cache", "@official");
|
|
5764
|
+
mkdirSync11(customRoot, { recursive: true });
|
|
5765
|
+
mkdirSync11(cacheRoot, { recursive: true });
|
|
6703
5766
|
let copiedAny = false;
|
|
6704
5767
|
for (const type of CONTENT_TYPES2) {
|
|
6705
|
-
const sourceDir =
|
|
6706
|
-
if (!
|
|
6707
|
-
cpSync(sourceDir,
|
|
6708
|
-
cpSync(sourceDir,
|
|
5768
|
+
const sourceDir = join21(contentRoot, type);
|
|
5769
|
+
if (!existsSync19(sourceDir)) continue;
|
|
5770
|
+
cpSync(sourceDir, join21(customRoot, type), { recursive: true });
|
|
5771
|
+
cpSync(sourceDir, join21(cacheRoot, type), { recursive: true });
|
|
6709
5772
|
copiedAny = true;
|
|
6710
5773
|
}
|
|
6711
5774
|
return copiedAny;
|
|
6712
5775
|
}
|
|
6713
5776
|
function seedOfflineRegistry(projectDir, workspaceRoot) {
|
|
6714
|
-
const projectDecantrRoot =
|
|
6715
|
-
|
|
5777
|
+
const projectDecantrRoot = join21(projectDir, ".decantr");
|
|
5778
|
+
mkdirSync11(projectDecantrRoot, { recursive: true });
|
|
6716
5779
|
const configuredContentRoot = process.env.DECANTR_CONTENT_DIR ? resolve2(process.env.DECANTR_CONTENT_DIR) : null;
|
|
6717
5780
|
if (configuredContentRoot && hydrateContentRoot(projectDir, configuredContentRoot)) {
|
|
6718
5781
|
return { seeded: true, strategy: "configured-content-root" };
|
|
6719
5782
|
}
|
|
6720
5783
|
const copiedCache = copyIfExists(
|
|
6721
|
-
|
|
6722
|
-
|
|
5784
|
+
join21(workspaceRoot, ".decantr", "cache"),
|
|
5785
|
+
join21(projectDecantrRoot, "cache")
|
|
6723
5786
|
);
|
|
6724
5787
|
const copiedCustom = copyIfExists(
|
|
6725
|
-
|
|
6726
|
-
|
|
5788
|
+
join21(workspaceRoot, ".decantr", "custom"),
|
|
5789
|
+
join21(projectDecantrRoot, "custom")
|
|
6727
5790
|
);
|
|
6728
5791
|
if (copiedCache || copiedCustom) {
|
|
6729
5792
|
return { seeded: true, strategy: "workspace-cache" };
|
|
@@ -6767,7 +5830,10 @@ function validatePassThroughFlagValue(flag, value) {
|
|
|
6767
5830
|
if (value.length > 512) {
|
|
6768
5831
|
throw new Error(`--${flag} is too long.`);
|
|
6769
5832
|
}
|
|
6770
|
-
if (
|
|
5833
|
+
if (Array.from(value).some((character) => {
|
|
5834
|
+
const code = character.charCodeAt(0);
|
|
5835
|
+
return code <= 31 || code === 127;
|
|
5836
|
+
})) {
|
|
6771
5837
|
throw new Error(`--${flag} contains unsupported control characters.`);
|
|
6772
5838
|
}
|
|
6773
5839
|
return value;
|
|
@@ -6836,7 +5902,7 @@ function runArgvCommand(command, args, cwd) {
|
|
|
6836
5902
|
}
|
|
6837
5903
|
function resolveInitCommand(initFlags) {
|
|
6838
5904
|
const bundledCliEntrypoint = fileURLToPath2(new URL("./bin.js", import.meta.url));
|
|
6839
|
-
const cliEntrypoint =
|
|
5905
|
+
const cliEntrypoint = existsSync20(bundledCliEntrypoint) ? bundledCliEntrypoint : process.argv[1] && existsSync20(process.argv[1]) ? process.argv[1] : null;
|
|
6840
5906
|
if (cliEntrypoint) {
|
|
6841
5907
|
return {
|
|
6842
5908
|
command: process.execPath,
|
|
@@ -6864,7 +5930,7 @@ async function cmdNewProject(projectName, options) {
|
|
|
6864
5930
|
process.exitCode = 1;
|
|
6865
5931
|
return;
|
|
6866
5932
|
}
|
|
6867
|
-
if (
|
|
5933
|
+
if (existsSync20(projectDir)) {
|
|
6868
5934
|
console.error(error(`Directory "${projectName}" already exists.`));
|
|
6869
5935
|
process.exitCode = 1;
|
|
6870
5936
|
return;
|
|
@@ -6878,7 +5944,7 @@ async function cmdNewProject(projectName, options) {
|
|
|
6878
5944
|
return;
|
|
6879
5945
|
}
|
|
6880
5946
|
console.log(heading(`Creating ${projectName}...`));
|
|
6881
|
-
|
|
5947
|
+
mkdirSync12(projectDir, { recursive: true });
|
|
6882
5948
|
console.log(dim2(` Created ${projectName}/`));
|
|
6883
5949
|
const title = projectName.replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
6884
5950
|
if (shouldBootstrapRuntime && bootstrapAdapter) {
|
|
@@ -6977,21 +6043,21 @@ ${YELLOW6}Decantr init encountered issues. Run \`decantr init\` manually inside
|
|
|
6977
6043
|
}
|
|
6978
6044
|
}
|
|
6979
6045
|
function detectPackageManager4() {
|
|
6980
|
-
if (
|
|
6046
|
+
if (existsSync20(join22(process.cwd(), "pnpm-lock.yaml")) || existsSync20(join22(process.cwd(), "pnpm-workspace.yaml"))) {
|
|
6981
6047
|
return "pnpm";
|
|
6982
6048
|
}
|
|
6983
|
-
if (
|
|
6049
|
+
if (existsSync20(join22(process.cwd(), "yarn.lock"))) {
|
|
6984
6050
|
return "yarn";
|
|
6985
6051
|
}
|
|
6986
|
-
if (
|
|
6052
|
+
if (existsSync20(join22(process.cwd(), "bun.lockb")) || existsSync20(join22(process.cwd(), "bun.lock"))) {
|
|
6987
6053
|
return "bun";
|
|
6988
6054
|
}
|
|
6989
6055
|
return "npm";
|
|
6990
6056
|
}
|
|
6991
6057
|
|
|
6992
6058
|
// src/commands/publish.ts
|
|
6993
|
-
import { existsSync as
|
|
6994
|
-
import { join as
|
|
6059
|
+
import { existsSync as existsSync21, readFileSync as readFileSync15 } from "fs";
|
|
6060
|
+
import { join as join23 } from "path";
|
|
6995
6061
|
import {
|
|
6996
6062
|
API_CONTENT_TYPE_TO_CONTENT_TYPE,
|
|
6997
6063
|
CONTENT_TYPE_TO_API_CONTENT_TYPE as CONTENT_TYPE_TO_API_CONTENT_TYPE2,
|
|
@@ -7007,8 +6073,8 @@ async function cmdPublish(type, name, projectRoot = process.cwd()) {
|
|
|
7007
6073
|
}
|
|
7008
6074
|
const singularType = API_CONTENT_TYPE_TO_CONTENT_TYPE[type] || type;
|
|
7009
6075
|
const pluralType = CONTENT_TYPE_TO_API_CONTENT_TYPE2[type] || CONTENT_TYPE_TO_API_CONTENT_TYPE2[singularType] || `${type}s`;
|
|
7010
|
-
const customPath =
|
|
7011
|
-
if (!
|
|
6076
|
+
const customPath = join23(projectRoot, ".decantr", "custom", pluralType, `${name}.json`);
|
|
6077
|
+
if (!existsSync21(customPath)) {
|
|
7012
6078
|
console.error(`Custom ${singularType} "${name}" not found at ${customPath}`);
|
|
7013
6079
|
console.error(`Create one first: decantr create ${singularType} ${name}`);
|
|
7014
6080
|
process.exitCode = 1;
|
|
@@ -7016,7 +6082,7 @@ async function cmdPublish(type, name, projectRoot = process.cwd()) {
|
|
|
7016
6082
|
}
|
|
7017
6083
|
let data;
|
|
7018
6084
|
try {
|
|
7019
|
-
data = JSON.parse(
|
|
6085
|
+
data = JSON.parse(readFileSync15(customPath, "utf-8"));
|
|
7020
6086
|
} catch {
|
|
7021
6087
|
console.error(`Failed to parse ${customPath}`);
|
|
7022
6088
|
process.exitCode = 1;
|
|
@@ -7055,22 +6121,22 @@ async function cmdPublish(type, name, projectRoot = process.cwd()) {
|
|
|
7055
6121
|
|
|
7056
6122
|
// src/commands/refresh.ts
|
|
7057
6123
|
import { createHash } from "crypto";
|
|
7058
|
-
import { existsSync as
|
|
7059
|
-
import { isAbsolute as isAbsolute2, join as
|
|
6124
|
+
import { existsSync as existsSync22, readdirSync as readdirSync6, readFileSync as readFileSync16, statSync as statSync5 } from "fs";
|
|
6125
|
+
import { isAbsolute as isAbsolute2, join as join24, relative as relative5 } from "path";
|
|
6126
|
+
import { isV4 as isV45 } from "@decantr/essence-spec";
|
|
7060
6127
|
import { collectMissingPackManifestFiles as collectMissingPackManifestFiles2 } from "@decantr/verifier";
|
|
7061
|
-
import { isV4 as isV46 } from "@decantr/essence-spec";
|
|
7062
6128
|
var GREEN9 = "\x1B[32m";
|
|
7063
6129
|
var RED7 = "\x1B[31m";
|
|
7064
6130
|
var DIM9 = "\x1B[2m";
|
|
7065
6131
|
var RESET9 = "\x1B[0m";
|
|
7066
6132
|
function hashFile(path) {
|
|
7067
|
-
return createHash("sha256").update(
|
|
6133
|
+
return createHash("sha256").update(readFileSync16(path)).digest("hex");
|
|
7068
6134
|
}
|
|
7069
6135
|
function walkFiles(dir) {
|
|
7070
|
-
if (!
|
|
6136
|
+
if (!existsSync22(dir)) return [];
|
|
7071
6137
|
const files = [];
|
|
7072
|
-
for (const entry of
|
|
7073
|
-
const path =
|
|
6138
|
+
for (const entry of readdirSync6(dir, { withFileTypes: true })) {
|
|
6139
|
+
const path = join24(dir, entry.name);
|
|
7074
6140
|
if (entry.isDirectory()) {
|
|
7075
6141
|
files.push(...walkFiles(path));
|
|
7076
6142
|
} else if (entry.isFile()) {
|
|
@@ -7081,10 +6147,10 @@ function walkFiles(dir) {
|
|
|
7081
6147
|
}
|
|
7082
6148
|
function trackedGeneratedFiles(projectRoot) {
|
|
7083
6149
|
return [
|
|
7084
|
-
|
|
7085
|
-
...walkFiles(
|
|
7086
|
-
...["global.css", "tokens.css", "treatments.css", "decantr-bridge.css"].map((file) =>
|
|
7087
|
-
].filter((path) =>
|
|
6150
|
+
join24(projectRoot, "DECANTR.md"),
|
|
6151
|
+
...walkFiles(join24(projectRoot, ".decantr", "context")),
|
|
6152
|
+
...["global.css", "tokens.css", "treatments.css", "decantr-bridge.css"].map((file) => join24(projectRoot, "src", "styles", file)).filter((path) => existsSync22(path))
|
|
6153
|
+
].filter((path) => existsSync22(path));
|
|
7088
6154
|
}
|
|
7089
6155
|
function snapshotGeneratedFiles(projectRoot) {
|
|
7090
6156
|
const map = /* @__PURE__ */ new Map();
|
|
@@ -7100,40 +6166,40 @@ function snapshotGeneratedFiles(projectRoot) {
|
|
|
7100
6166
|
}
|
|
7101
6167
|
function fileMtimeMs(path) {
|
|
7102
6168
|
try {
|
|
7103
|
-
return
|
|
6169
|
+
return statSync5(path).mtimeMs;
|
|
7104
6170
|
} catch {
|
|
7105
6171
|
return 0;
|
|
7106
6172
|
}
|
|
7107
6173
|
}
|
|
7108
6174
|
function newestInputMtime(projectRoot) {
|
|
7109
6175
|
return Math.max(
|
|
7110
|
-
fileMtimeMs(
|
|
7111
|
-
fileMtimeMs(
|
|
6176
|
+
fileMtimeMs(join24(projectRoot, "decantr.essence.json")),
|
|
6177
|
+
fileMtimeMs(join24(projectRoot, ".decantr", "project.json"))
|
|
7112
6178
|
);
|
|
7113
6179
|
}
|
|
7114
6180
|
function isContractOnlyProject2(projectRoot) {
|
|
7115
|
-
const projectJsonPath =
|
|
7116
|
-
if (!
|
|
6181
|
+
const projectJsonPath = join24(projectRoot, ".decantr", "project.json");
|
|
6182
|
+
if (!existsSync22(projectJsonPath)) return false;
|
|
7117
6183
|
try {
|
|
7118
|
-
const projectJson = JSON.parse(
|
|
6184
|
+
const projectJson = JSON.parse(readFileSync16(projectJsonPath, "utf-8"));
|
|
7119
6185
|
return projectJson.initialized?.adoptionMode === "contract-only";
|
|
7120
6186
|
} catch {
|
|
7121
6187
|
return false;
|
|
7122
6188
|
}
|
|
7123
6189
|
}
|
|
7124
6190
|
function checkRefreshFreshness(projectRoot) {
|
|
7125
|
-
const contextDir =
|
|
6191
|
+
const contextDir = join24(projectRoot, ".decantr", "context");
|
|
7126
6192
|
const generated = trackedGeneratedFiles(projectRoot);
|
|
7127
6193
|
const reasons = [];
|
|
7128
6194
|
const packHydrationOptional = isContractOnlyProject2(projectRoot);
|
|
7129
|
-
if (!
|
|
7130
|
-
if (!
|
|
7131
|
-
if (!
|
|
6195
|
+
if (!existsSync22(join24(projectRoot, "DECANTR.md"))) reasons.push("DECANTR.md is missing.");
|
|
6196
|
+
if (!existsSync22(contextDir)) reasons.push(".decantr/context is missing.");
|
|
6197
|
+
if (!existsSync22(join24(contextDir, "scaffold.md"))) {
|
|
7132
6198
|
reasons.push(".decantr/context/scaffold.md is missing.");
|
|
7133
6199
|
}
|
|
7134
|
-
if (!packHydrationOptional && !
|
|
6200
|
+
if (!packHydrationOptional && !existsSync22(join24(contextDir, "pack-manifest.json"))) {
|
|
7135
6201
|
reasons.push(".decantr/context/pack-manifest.json is missing.");
|
|
7136
|
-
} else if (
|
|
6202
|
+
} else if (existsSync22(join24(contextDir, "pack-manifest.json"))) {
|
|
7137
6203
|
const missingPackFiles = collectMissingPackManifestFiles2(projectRoot);
|
|
7138
6204
|
if (missingPackFiles.length > 0) {
|
|
7139
6205
|
reasons.push(
|
|
@@ -7186,8 +6252,8 @@ function summarizeChanges(projectRoot, before, after) {
|
|
|
7186
6252
|
}
|
|
7187
6253
|
function displayGeneratedPath(summary, file, displayRoot) {
|
|
7188
6254
|
if (!displayRoot) return file;
|
|
7189
|
-
const absolutePath =
|
|
7190
|
-
const relativePath =
|
|
6255
|
+
const absolutePath = join24(summary.projectRoot, file);
|
|
6256
|
+
const relativePath = relative5(displayRoot, absolutePath).replace(/\\/g, "/");
|
|
7191
6257
|
if (relativePath && !relativePath.startsWith("..") && !isAbsolute2(relativePath)) {
|
|
7192
6258
|
return relativePath;
|
|
7193
6259
|
}
|
|
@@ -7221,17 +6287,17 @@ function printRefreshSummary(summary, displayRoot) {
|
|
|
7221
6287
|
}
|
|
7222
6288
|
}
|
|
7223
6289
|
async function cmdRefresh(projectRoot = process.cwd(), options = {}) {
|
|
7224
|
-
const essencePath =
|
|
7225
|
-
if (!
|
|
6290
|
+
const essencePath = join24(projectRoot, "decantr.essence.json");
|
|
6291
|
+
if (!existsSync22(essencePath)) {
|
|
7226
6292
|
console.error(`${RED7}No decantr.essence.json found. Run \`decantr init\` first.${RESET9}`);
|
|
7227
6293
|
process.exitCode = 1;
|
|
7228
6294
|
return;
|
|
7229
6295
|
}
|
|
7230
6296
|
let essence;
|
|
7231
6297
|
try {
|
|
7232
|
-
const raw =
|
|
6298
|
+
const raw = readFileSync16(essencePath, "utf-8");
|
|
7233
6299
|
const parsed = JSON.parse(raw);
|
|
7234
|
-
if (!
|
|
6300
|
+
if (!isV45(parsed)) {
|
|
7235
6301
|
console.error(
|
|
7236
6302
|
`${RED7}Active workflows require Essence v4.0.0. Run \`decantr migrate --to v4\` first.${RESET9}`
|
|
7237
6303
|
);
|
|
@@ -7255,7 +6321,7 @@ async function cmdRefresh(projectRoot = process.cwd(), options = {}) {
|
|
|
7255
6321
|
return;
|
|
7256
6322
|
}
|
|
7257
6323
|
const registryClient = new RegistryClient({
|
|
7258
|
-
cacheDir:
|
|
6324
|
+
cacheDir: join24(projectRoot, ".decantr", "cache"),
|
|
7259
6325
|
offline: options.offline
|
|
7260
6326
|
});
|
|
7261
6327
|
console.log("Regenerating derived files...\n");
|
|
@@ -7286,8 +6352,8 @@ async function cmdRefresh(projectRoot = process.cwd(), options = {}) {
|
|
|
7286
6352
|
}
|
|
7287
6353
|
|
|
7288
6354
|
// src/commands/registry-mirror.ts
|
|
7289
|
-
import { mkdirSync as
|
|
7290
|
-
import { join as
|
|
6355
|
+
import { mkdirSync as mkdirSync13, writeFileSync as writeFileSync13 } from "fs";
|
|
6356
|
+
import { join as join25 } from "path";
|
|
7291
6357
|
import { API_CONTENT_TYPES, RegistryAPIClient as RegistryAPIClient2 } from "@decantr/registry";
|
|
7292
6358
|
var GREEN10 = "\x1B[32m";
|
|
7293
6359
|
var RED8 = "\x1B[31m";
|
|
@@ -7315,7 +6381,7 @@ async function cmdRegistryMirror(projectRoot, options = {}) {
|
|
|
7315
6381
|
process.exitCode = 1;
|
|
7316
6382
|
return;
|
|
7317
6383
|
}
|
|
7318
|
-
const cacheDir =
|
|
6384
|
+
const cacheDir = join25(projectRoot, ".decantr", "cache");
|
|
7319
6385
|
const counts = {};
|
|
7320
6386
|
const failed = [];
|
|
7321
6387
|
console.log(`
|
|
@@ -7325,19 +6391,19 @@ Mirroring registry content to ${DIM10}.decantr/cache/${RESET10}
|
|
|
7325
6391
|
try {
|
|
7326
6392
|
const result = await apiClient.listContent(type, { namespace: "@official" });
|
|
7327
6393
|
const items = result.items;
|
|
7328
|
-
const typeDir =
|
|
7329
|
-
|
|
7330
|
-
|
|
6394
|
+
const typeDir = join25(cacheDir, "@official", type);
|
|
6395
|
+
mkdirSync13(typeDir, { recursive: true });
|
|
6396
|
+
writeFileSync13(join25(typeDir, "index.json"), JSON.stringify(result, null, 2));
|
|
7331
6397
|
let itemCount = 0;
|
|
7332
6398
|
for (const item of items) {
|
|
7333
6399
|
const slug = item.slug || item.id;
|
|
7334
6400
|
if (!slug) continue;
|
|
7335
6401
|
try {
|
|
7336
6402
|
const fullItem = await apiClient.getContent(type, "@official", slug);
|
|
7337
|
-
|
|
6403
|
+
writeFileSync13(join25(typeDir, `${slug}.json`), JSON.stringify(fullItem, null, 2));
|
|
7338
6404
|
itemCount++;
|
|
7339
6405
|
} catch {
|
|
7340
|
-
|
|
6406
|
+
writeFileSync13(join25(typeDir, `${slug}.json`), JSON.stringify(item, null, 2));
|
|
7341
6407
|
itemCount++;
|
|
7342
6408
|
}
|
|
7343
6409
|
}
|
|
@@ -7352,8 +6418,8 @@ Mirroring registry content to ${DIM10}.decantr/cache/${RESET10}
|
|
|
7352
6418
|
mirrored_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7353
6419
|
counts
|
|
7354
6420
|
};
|
|
7355
|
-
|
|
7356
|
-
|
|
6421
|
+
mkdirSync13(join25(cacheDir), { recursive: true });
|
|
6422
|
+
writeFileSync13(join25(cacheDir, "mirror-manifest.json"), JSON.stringify(manifest, null, 2));
|
|
7357
6423
|
const totalItems = Object.values(counts).reduce((a, b) => a + b, 0);
|
|
7358
6424
|
console.log("");
|
|
7359
6425
|
if (failed.length > 0) {
|
|
@@ -7370,30 +6436,30 @@ Mirroring registry content to ${DIM10}.decantr/cache/${RESET10}
|
|
|
7370
6436
|
}
|
|
7371
6437
|
|
|
7372
6438
|
// src/commands/remove.ts
|
|
7373
|
-
import { existsSync as
|
|
7374
|
-
import { join as
|
|
7375
|
-
import { isV4 as
|
|
6439
|
+
import { existsSync as existsSync24, readFileSync as readFileSync17, rmSync as rmSync2, writeFileSync as writeFileSync14 } from "fs";
|
|
6440
|
+
import { join as join26 } from "path";
|
|
6441
|
+
import { isV4 as isV46 } from "@decantr/essence-spec";
|
|
7376
6442
|
var GREEN11 = "\x1B[32m";
|
|
7377
6443
|
var RED9 = "\x1B[31m";
|
|
7378
6444
|
var YELLOW8 = "\x1B[33m";
|
|
7379
6445
|
var DIM11 = "\x1B[2m";
|
|
7380
6446
|
var RESET11 = "\x1B[0m";
|
|
7381
6447
|
function readV4Essence2(projectRoot) {
|
|
7382
|
-
const essencePath =
|
|
7383
|
-
if (!
|
|
6448
|
+
const essencePath = join26(projectRoot, "decantr.essence.json");
|
|
6449
|
+
if (!existsSync24(essencePath)) {
|
|
7384
6450
|
console.error(`${RED9}No decantr.essence.json found. Run \`decantr init\` first.${RESET11}`);
|
|
7385
6451
|
process.exitCode = 1;
|
|
7386
6452
|
return null;
|
|
7387
6453
|
}
|
|
7388
6454
|
let parsed;
|
|
7389
6455
|
try {
|
|
7390
|
-
parsed = JSON.parse(
|
|
6456
|
+
parsed = JSON.parse(readFileSync17(essencePath, "utf-8"));
|
|
7391
6457
|
} catch (e) {
|
|
7392
6458
|
console.error(`${RED9}Could not read essence: ${e.message}${RESET11}`);
|
|
7393
6459
|
process.exitCode = 1;
|
|
7394
6460
|
return null;
|
|
7395
6461
|
}
|
|
7396
|
-
if (!
|
|
6462
|
+
if (!isV46(parsed)) {
|
|
7397
6463
|
console.error(
|
|
7398
6464
|
`${RED9}Active workflows require Essence v4.0.0. Run \`decantr migrate --to v4\` first.${RESET11}`
|
|
7399
6465
|
);
|
|
@@ -7403,7 +6469,7 @@ function readV4Essence2(projectRoot) {
|
|
|
7403
6469
|
return { essence: parsed, essencePath };
|
|
7404
6470
|
}
|
|
7405
6471
|
function writeEssence2(essencePath, essence) {
|
|
7406
|
-
|
|
6472
|
+
writeFileSync14(essencePath, JSON.stringify(essence, null, 2) + "\n");
|
|
7407
6473
|
}
|
|
7408
6474
|
function readFlagValue2(args, name) {
|
|
7409
6475
|
const prefix = `--${name}=`;
|
|
@@ -7475,14 +6541,14 @@ async function cmdRemoveSection(sectionId, args, projectRoot = process.cwd()) {
|
|
|
7475
6541
|
sections.splice(idx, 1);
|
|
7476
6542
|
recomputeGlobalFeatures(essence);
|
|
7477
6543
|
removeRoutes(essence, sectionId);
|
|
7478
|
-
const contextFile =
|
|
7479
|
-
if (
|
|
6544
|
+
const contextFile = join26(projectRoot, ".decantr", "context", `${sectionId}.md`);
|
|
6545
|
+
if (existsSync24(contextFile)) {
|
|
7480
6546
|
rmSync2(contextFile);
|
|
7481
6547
|
}
|
|
7482
6548
|
writeEssence2(essencePath, essence);
|
|
7483
6549
|
console.log(`${GREEN11}Removed section "${sectionId}".${RESET11}`);
|
|
7484
6550
|
const registryClient = new RegistryClient({
|
|
7485
|
-
cacheDir:
|
|
6551
|
+
cacheDir: join26(projectRoot, ".decantr", "cache")
|
|
7486
6552
|
});
|
|
7487
6553
|
await refreshDerivedFiles(projectRoot, essence, registryClient);
|
|
7488
6554
|
console.log(`${GREEN11}Derived files refreshed.${RESET11}`);
|
|
@@ -7525,7 +6591,7 @@ async function cmdRemovePage(path, args, projectRoot = process.cwd()) {
|
|
|
7525
6591
|
}
|
|
7526
6592
|
console.log(`${GREEN11}Removed page "${pageId}" from section "${resolvedSectionId}".${RESET11}`);
|
|
7527
6593
|
const registryClient = new RegistryClient({
|
|
7528
|
-
cacheDir:
|
|
6594
|
+
cacheDir: join26(projectRoot, ".decantr", "cache")
|
|
7529
6595
|
});
|
|
7530
6596
|
await refreshDerivedFiles(projectRoot, essence, registryClient);
|
|
7531
6597
|
console.log(`${GREEN11}Derived files refreshed.${RESET11}`);
|
|
@@ -7568,15 +6634,15 @@ async function cmdRemoveFeature(feature, args, projectRoot = process.cwd()) {
|
|
|
7568
6634
|
const target = sectionId ? `section "${sectionId}" and global` : "global";
|
|
7569
6635
|
console.log(`${GREEN11}Removed feature "${feature}" from ${target} features.${RESET11}`);
|
|
7570
6636
|
const registryClient = new RegistryClient({
|
|
7571
|
-
cacheDir:
|
|
6637
|
+
cacheDir: join26(projectRoot, ".decantr", "cache")
|
|
7572
6638
|
});
|
|
7573
6639
|
await refreshDerivedFiles(projectRoot, essence, registryClient);
|
|
7574
6640
|
console.log(`${GREEN11}Derived files refreshed.${RESET11}`);
|
|
7575
6641
|
}
|
|
7576
6642
|
|
|
7577
6643
|
// src/commands/sync-drift.ts
|
|
7578
|
-
import { existsSync as
|
|
7579
|
-
import { join as
|
|
6644
|
+
import { existsSync as existsSync25, readFileSync as readFileSync18, writeFileSync as writeFileSync15 } from "fs";
|
|
6645
|
+
import { join as join27 } from "path";
|
|
7580
6646
|
var GREEN12 = "\x1B[32m";
|
|
7581
6647
|
var RED10 = "\x1B[31m";
|
|
7582
6648
|
var YELLOW9 = "\x1B[33m";
|
|
@@ -7585,8 +6651,8 @@ var DIM12 = "\x1B[2m";
|
|
|
7585
6651
|
var BOLD6 = "\x1B[1m";
|
|
7586
6652
|
var CYAN6 = "\x1B[36m";
|
|
7587
6653
|
async function cmdSyncDrift(projectRoot = process.cwd()) {
|
|
7588
|
-
const driftLogPath =
|
|
7589
|
-
if (!
|
|
6654
|
+
const driftLogPath = join27(projectRoot, ".decantr", "drift-log.json");
|
|
6655
|
+
if (!existsSync25(driftLogPath)) {
|
|
7590
6656
|
console.log(`${GREEN12}No drift log found \u2014 no drift recorded.${RESET12}`);
|
|
7591
6657
|
console.log(
|
|
7592
6658
|
`${DIM12}Drift is logged when guard violations are detected during development.${RESET12}`
|
|
@@ -7595,7 +6661,7 @@ async function cmdSyncDrift(projectRoot = process.cwd()) {
|
|
|
7595
6661
|
}
|
|
7596
6662
|
let entries;
|
|
7597
6663
|
try {
|
|
7598
|
-
entries = JSON.parse(
|
|
6664
|
+
entries = JSON.parse(readFileSync18(driftLogPath, "utf-8"));
|
|
7599
6665
|
} catch {
|
|
7600
6666
|
console.error(`${RED10}Could not parse drift-log.json${RESET12}`);
|
|
7601
6667
|
process.exitCode = 1;
|
|
@@ -7641,13 +6707,13 @@ ${BOLD6}Unresolved Drift Entries (${unresolved.length})${RESET12}
|
|
|
7641
6707
|
console.log("");
|
|
7642
6708
|
}
|
|
7643
6709
|
function resolveDriftEntries(projectRoot, options) {
|
|
7644
|
-
const driftLogPath =
|
|
7645
|
-
if (!
|
|
6710
|
+
const driftLogPath = join27(projectRoot, ".decantr", "drift-log.json");
|
|
6711
|
+
if (!existsSync25(driftLogPath)) {
|
|
7646
6712
|
return { success: true };
|
|
7647
6713
|
}
|
|
7648
6714
|
if (options.clear) {
|
|
7649
6715
|
try {
|
|
7650
|
-
|
|
6716
|
+
writeFileSync15(driftLogPath, "[]");
|
|
7651
6717
|
return { success: true };
|
|
7652
6718
|
} catch (e) {
|
|
7653
6719
|
return { success: false, error: `Could not clear drift log: ${e.message}` };
|
|
@@ -7655,7 +6721,7 @@ function resolveDriftEntries(projectRoot, options) {
|
|
|
7655
6721
|
}
|
|
7656
6722
|
let entries;
|
|
7657
6723
|
try {
|
|
7658
|
-
entries = JSON.parse(
|
|
6724
|
+
entries = JSON.parse(readFileSync18(driftLogPath, "utf-8"));
|
|
7659
6725
|
} catch {
|
|
7660
6726
|
return { success: false, error: "Could not parse drift-log.json" };
|
|
7661
6727
|
}
|
|
@@ -7674,7 +6740,7 @@ function resolveDriftEntries(projectRoot, options) {
|
|
|
7674
6740
|
}
|
|
7675
6741
|
}
|
|
7676
6742
|
try {
|
|
7677
|
-
|
|
6743
|
+
writeFileSync15(driftLogPath, JSON.stringify(entries, null, 2));
|
|
7678
6744
|
return { success: true };
|
|
7679
6745
|
} catch (e) {
|
|
7680
6746
|
return { success: false, error: `Could not write drift log: ${e.message}` };
|
|
@@ -7744,12 +6810,16 @@ ${BOLD7}Decantr telemetry${RESET13}`);
|
|
|
7744
6810
|
DIM13 + "Run `decantr telemetry link` after login to attach these opaque IDs to your Decantr account/org." + RESET13
|
|
7745
6811
|
);
|
|
7746
6812
|
} else {
|
|
7747
|
-
console.log(
|
|
6813
|
+
console.log(
|
|
6814
|
+
DIM13 + "Run `decantr init --telemetry` or `decantr telemetry link --enable` to opt in." + RESET13
|
|
6815
|
+
);
|
|
7748
6816
|
}
|
|
7749
6817
|
}
|
|
7750
6818
|
function printTelemetryExplain(projectRoot, options) {
|
|
7751
6819
|
const status = getCliTelemetryIdentityStatus(projectRoot, { create: false });
|
|
7752
|
-
const cliEvents = DECANTR_TELEMETRY_EVENT_CATALOG.filter(
|
|
6820
|
+
const cliEvents = DECANTR_TELEMETRY_EVENT_CATALOG.filter(
|
|
6821
|
+
(entry) => entry.allowedSources.includes("cli")
|
|
6822
|
+
).map((entry) => ({
|
|
7753
6823
|
name: entry.name,
|
|
7754
6824
|
bucket: entry.bucket,
|
|
7755
6825
|
privacy: entry.privacy,
|
|
@@ -7828,7 +6898,9 @@ async function linkTelemetryIdentity(projectRoot, options) {
|
|
|
7828
6898
|
optIn(projectRoot);
|
|
7829
6899
|
}
|
|
7830
6900
|
if (!isOptedIn(projectRoot)) {
|
|
7831
|
-
throw new Error(
|
|
6901
|
+
throw new Error(
|
|
6902
|
+
"This project has not opted into telemetry. Re-run with --enable or use `decantr init --telemetry`."
|
|
6903
|
+
);
|
|
7832
6904
|
}
|
|
7833
6905
|
const identity = getCliTelemetryIdentityStatus(projectRoot, { create: true });
|
|
7834
6906
|
if (!identity.installId && !identity.projectId) {
|
|
@@ -7836,9 +6908,13 @@ async function linkTelemetryIdentity(projectRoot, options) {
|
|
|
7836
6908
|
}
|
|
7837
6909
|
const apiKey = options.apiKey ?? getApiKeyOrToken();
|
|
7838
6910
|
if (!apiKey) {
|
|
7839
|
-
throw new Error(
|
|
6911
|
+
throw new Error(
|
|
6912
|
+
"Run `decantr login --api-key=<key>` or pass `--api-key <key>` before linking telemetry."
|
|
6913
|
+
);
|
|
7840
6914
|
}
|
|
7841
|
-
const apiUrl = trimTrailingSlashes(
|
|
6915
|
+
const apiUrl = trimTrailingSlashes(
|
|
6916
|
+
options.apiUrl ?? process.env.DECANTR_API_URL ?? DEFAULT_API_URL
|
|
6917
|
+
);
|
|
7842
6918
|
const response = await fetch(`${apiUrl}/me/telemetry-link`, {
|
|
7843
6919
|
method: "POST",
|
|
7844
6920
|
headers: {
|
|
@@ -7862,7 +6938,9 @@ async function linkTelemetryIdentity(projectRoot, options) {
|
|
|
7862
6938
|
console.log(` Install ID: ${identity.installId ?? `${DIM13}none${RESET13}`}`);
|
|
7863
6939
|
console.log(` Project ID: ${identity.projectId ?? `${DIM13}none${RESET13}`}`);
|
|
7864
6940
|
if (options.org) console.log(` Org: ${CYAN7}${options.org}${RESET13}`);
|
|
7865
|
-
console.log(
|
|
6941
|
+
console.log(
|
|
6942
|
+
DIM13 + "These opaque IDs now attribute opted-in CLI usage to your Decantr account/org." + RESET13
|
|
6943
|
+
);
|
|
7866
6944
|
}
|
|
7867
6945
|
function parseTelemetryOptions(args) {
|
|
7868
6946
|
const options = {};
|
|
@@ -7899,9 +6977,9 @@ function trimTrailingSlashes(value) {
|
|
|
7899
6977
|
}
|
|
7900
6978
|
|
|
7901
6979
|
// src/commands/theme-switch.ts
|
|
7902
|
-
import { existsSync as
|
|
7903
|
-
import { join as
|
|
7904
|
-
import { isV4 as
|
|
6980
|
+
import { existsSync as existsSync26, readFileSync as readFileSync19, writeFileSync as writeFileSync16 } from "fs";
|
|
6981
|
+
import { join as join28 } from "path";
|
|
6982
|
+
import { isV4 as isV47 } from "@decantr/essence-spec";
|
|
7905
6983
|
var GREEN14 = "\x1B[32m";
|
|
7906
6984
|
var RED11 = "\x1B[31m";
|
|
7907
6985
|
var YELLOW10 = "\x1B[33m";
|
|
@@ -7917,21 +6995,21 @@ async function cmdThemeSwitch(themeName, args, projectRoot = process.cwd()) {
|
|
|
7917
6995
|
process.exitCode = 1;
|
|
7918
6996
|
return;
|
|
7919
6997
|
}
|
|
7920
|
-
const essencePath =
|
|
7921
|
-
if (!
|
|
6998
|
+
const essencePath = join28(projectRoot, "decantr.essence.json");
|
|
6999
|
+
if (!existsSync26(essencePath)) {
|
|
7922
7000
|
console.error(`${RED11}No decantr.essence.json found. Run \`decantr init\` first.${RESET14}`);
|
|
7923
7001
|
process.exitCode = 1;
|
|
7924
7002
|
return;
|
|
7925
7003
|
}
|
|
7926
7004
|
let parsed;
|
|
7927
7005
|
try {
|
|
7928
|
-
parsed = JSON.parse(
|
|
7006
|
+
parsed = JSON.parse(readFileSync19(essencePath, "utf-8"));
|
|
7929
7007
|
} catch (e) {
|
|
7930
7008
|
console.error(`${RED11}Could not read essence: ${e.message}${RESET14}`);
|
|
7931
7009
|
process.exitCode = 1;
|
|
7932
7010
|
return;
|
|
7933
7011
|
}
|
|
7934
|
-
if (!
|
|
7012
|
+
if (!isV47(parsed)) {
|
|
7935
7013
|
console.error(
|
|
7936
7014
|
`${RED11}Active workflows require Essence v4.0.0. Run \`decantr migrate --to v4\` first.${RESET14}`
|
|
7937
7015
|
);
|
|
@@ -7976,7 +7054,7 @@ async function cmdThemeSwitch(themeName, args, projectRoot = process.cwd()) {
|
|
|
7976
7054
|
essence.dna.theme.mode = mode;
|
|
7977
7055
|
}
|
|
7978
7056
|
const registryClient = new RegistryClient({
|
|
7979
|
-
cacheDir:
|
|
7057
|
+
cacheDir: join28(projectRoot, ".decantr", "cache")
|
|
7980
7058
|
});
|
|
7981
7059
|
try {
|
|
7982
7060
|
const themeResult = await registryClient.fetchTheme(themeName);
|
|
@@ -7991,7 +7069,7 @@ async function cmdThemeSwitch(themeName, args, projectRoot = process.cwd()) {
|
|
|
7991
7069
|
}
|
|
7992
7070
|
} catch {
|
|
7993
7071
|
}
|
|
7994
|
-
|
|
7072
|
+
writeFileSync16(essencePath, JSON.stringify(essence, null, 2) + "\n");
|
|
7995
7073
|
console.log(`${GREEN14}Switched theme: ${oldThemeId} \u2192 ${themeName}${RESET14}`);
|
|
7996
7074
|
if (shape) console.log(` ${DIM14}Shape: ${shape}${RESET14}`);
|
|
7997
7075
|
if (mode) console.log(` ${DIM14}Mode: ${mode}${RESET14}`);
|
|
@@ -8299,8 +7377,8 @@ async function runSimplifiedInit(blueprints) {
|
|
|
8299
7377
|
}
|
|
8300
7378
|
|
|
8301
7379
|
// src/theme-commands.ts
|
|
8302
|
-
import { existsSync as
|
|
8303
|
-
import { join as
|
|
7380
|
+
import { existsSync as existsSync27, mkdirSync as mkdirSync14, readdirSync as readdirSync7, readFileSync as readFileSync20, rmSync as rmSync3, writeFileSync as writeFileSync17 } from "fs";
|
|
7381
|
+
import { join as join29 } from "path";
|
|
8304
7382
|
var REQUIRED_FIELDS = [
|
|
8305
7383
|
"$schema",
|
|
8306
7384
|
"id",
|
|
@@ -8360,20 +7438,20 @@ function validateCustomTheme(theme) {
|
|
|
8360
7438
|
};
|
|
8361
7439
|
}
|
|
8362
7440
|
function createTheme(projectRoot, id, name) {
|
|
8363
|
-
const customThemesDir =
|
|
8364
|
-
const themePath =
|
|
8365
|
-
const howToPath =
|
|
8366
|
-
|
|
8367
|
-
if (
|
|
7441
|
+
const customThemesDir = join29(projectRoot, ".decantr", "custom", "themes");
|
|
7442
|
+
const themePath = join29(customThemesDir, `${id}.json`);
|
|
7443
|
+
const howToPath = join29(customThemesDir, "how-to-theme.md");
|
|
7444
|
+
mkdirSync14(customThemesDir, { recursive: true });
|
|
7445
|
+
if (existsSync27(themePath)) {
|
|
8368
7446
|
return {
|
|
8369
7447
|
success: false,
|
|
8370
7448
|
error: `Theme "${id}" already exists at ${themePath}`
|
|
8371
7449
|
};
|
|
8372
7450
|
}
|
|
8373
7451
|
const skeleton = getThemeSkeleton(id, name);
|
|
8374
|
-
|
|
8375
|
-
if (!
|
|
8376
|
-
|
|
7452
|
+
writeFileSync17(themePath, JSON.stringify(skeleton, null, 2));
|
|
7453
|
+
if (!existsSync27(howToPath)) {
|
|
7454
|
+
writeFileSync17(howToPath, getHowToThemeDoc());
|
|
8377
7455
|
}
|
|
8378
7456
|
return {
|
|
8379
7457
|
success: true,
|
|
@@ -8381,17 +7459,17 @@ function createTheme(projectRoot, id, name) {
|
|
|
8381
7459
|
};
|
|
8382
7460
|
}
|
|
8383
7461
|
function listCustomThemes(projectRoot) {
|
|
8384
|
-
const customThemesDir =
|
|
8385
|
-
if (!
|
|
7462
|
+
const customThemesDir = join29(projectRoot, ".decantr", "custom", "themes");
|
|
7463
|
+
if (!existsSync27(customThemesDir)) {
|
|
8386
7464
|
return [];
|
|
8387
7465
|
}
|
|
8388
7466
|
const themes = [];
|
|
8389
7467
|
try {
|
|
8390
|
-
const files =
|
|
7468
|
+
const files = readdirSync7(customThemesDir).filter((f) => f.endsWith(".json"));
|
|
8391
7469
|
for (const file of files) {
|
|
8392
|
-
const filePath =
|
|
7470
|
+
const filePath = join29(customThemesDir, file);
|
|
8393
7471
|
try {
|
|
8394
|
-
const data = JSON.parse(
|
|
7472
|
+
const data = JSON.parse(readFileSync20(filePath, "utf-8"));
|
|
8395
7473
|
themes.push({
|
|
8396
7474
|
id: data.id || file.replace(".json", ""),
|
|
8397
7475
|
name: data.name || data.id,
|
|
@@ -8406,8 +7484,8 @@ function listCustomThemes(projectRoot) {
|
|
|
8406
7484
|
return themes;
|
|
8407
7485
|
}
|
|
8408
7486
|
function deleteTheme(projectRoot, id) {
|
|
8409
|
-
const themePath =
|
|
8410
|
-
if (!
|
|
7487
|
+
const themePath = join29(projectRoot, ".decantr", "custom", "themes", `${id}.json`);
|
|
7488
|
+
if (!existsSync27(themePath)) {
|
|
8411
7489
|
return {
|
|
8412
7490
|
success: false,
|
|
8413
7491
|
error: `Theme "${id}" not found at ${themePath}`
|
|
@@ -8424,7 +7502,7 @@ function deleteTheme(projectRoot, id) {
|
|
|
8424
7502
|
}
|
|
8425
7503
|
}
|
|
8426
7504
|
function importTheme(projectRoot, sourcePath) {
|
|
8427
|
-
if (!
|
|
7505
|
+
if (!existsSync27(sourcePath)) {
|
|
8428
7506
|
return {
|
|
8429
7507
|
success: false,
|
|
8430
7508
|
errors: [`Source file not found: ${sourcePath}`]
|
|
@@ -8432,7 +7510,7 @@ function importTheme(projectRoot, sourcePath) {
|
|
|
8432
7510
|
}
|
|
8433
7511
|
let theme;
|
|
8434
7512
|
try {
|
|
8435
|
-
theme = JSON.parse(
|
|
7513
|
+
theme = JSON.parse(readFileSync20(sourcePath, "utf-8"));
|
|
8436
7514
|
} catch (e) {
|
|
8437
7515
|
return {
|
|
8438
7516
|
success: false,
|
|
@@ -8448,14 +7526,14 @@ function importTheme(projectRoot, sourcePath) {
|
|
|
8448
7526
|
}
|
|
8449
7527
|
theme.source = "custom";
|
|
8450
7528
|
const id = theme.id;
|
|
8451
|
-
const customThemesDir =
|
|
8452
|
-
const destPath =
|
|
8453
|
-
|
|
8454
|
-
const howToPath =
|
|
8455
|
-
if (!
|
|
8456
|
-
|
|
8457
|
-
}
|
|
8458
|
-
|
|
7529
|
+
const customThemesDir = join29(projectRoot, ".decantr", "custom", "themes");
|
|
7530
|
+
const destPath = join29(customThemesDir, `${id}.json`);
|
|
7531
|
+
mkdirSync14(customThemesDir, { recursive: true });
|
|
7532
|
+
const howToPath = join29(customThemesDir, "how-to-theme.md");
|
|
7533
|
+
if (!existsSync27(howToPath)) {
|
|
7534
|
+
writeFileSync17(howToPath, getHowToThemeDoc());
|
|
7535
|
+
}
|
|
7536
|
+
writeFileSync17(destPath, JSON.stringify(theme, null, 2));
|
|
8459
7537
|
return {
|
|
8460
7538
|
success: true,
|
|
8461
7539
|
path: destPath
|
|
@@ -9002,18 +8080,18 @@ function extractHostedAssetPaths(indexHtml) {
|
|
|
9002
8080
|
return [...assetPaths];
|
|
9003
8081
|
}
|
|
9004
8082
|
function readHostedDistSnapshot(distPath) {
|
|
9005
|
-
const resolvedDistPath = distPath ? resolveUserPath(distPath) :
|
|
9006
|
-
const indexPath =
|
|
9007
|
-
if (!
|
|
8083
|
+
const resolvedDistPath = distPath ? resolveUserPath(distPath) : join30(process.cwd(), "dist");
|
|
8084
|
+
const indexPath = join30(resolvedDistPath, "index.html");
|
|
8085
|
+
if (!existsSync28(indexPath)) {
|
|
9008
8086
|
return void 0;
|
|
9009
8087
|
}
|
|
9010
|
-
const indexHtml =
|
|
8088
|
+
const indexHtml = readFileSync21(indexPath, "utf-8");
|
|
9011
8089
|
const assetPaths = extractHostedAssetPaths(indexHtml);
|
|
9012
8090
|
const assets = {};
|
|
9013
8091
|
for (const assetPath of assetPaths) {
|
|
9014
|
-
const assetFilePath =
|
|
9015
|
-
if (
|
|
9016
|
-
assets[assetPath] =
|
|
8092
|
+
const assetFilePath = join30(resolvedDistPath, assetPath.replace(/^[/\\]+/, ""));
|
|
8093
|
+
if (existsSync28(assetFilePath)) {
|
|
8094
|
+
assets[assetPath] = readFileSync21(assetFilePath, "utf-8");
|
|
9017
8095
|
}
|
|
9018
8096
|
}
|
|
9019
8097
|
return {
|
|
@@ -9028,7 +8106,7 @@ function isHostedSourceSnapshotFile(path) {
|
|
|
9028
8106
|
function readHostedSourceSnapshot(sourcePath) {
|
|
9029
8107
|
if (!sourcePath) return void 0;
|
|
9030
8108
|
const resolvedSourcePath = resolveUserPath(sourcePath);
|
|
9031
|
-
if (!
|
|
8109
|
+
if (!existsSync28(resolvedSourcePath)) {
|
|
9032
8110
|
return void 0;
|
|
9033
8111
|
}
|
|
9034
8112
|
const files = {};
|
|
@@ -9040,19 +8118,19 @@ function readHostedSourceSnapshot(sourcePath) {
|
|
|
9040
8118
|
"build",
|
|
9041
8119
|
"coverage"
|
|
9042
8120
|
]);
|
|
9043
|
-
const rootPrefix =
|
|
8121
|
+
const rootPrefix = basename2(resolvedSourcePath);
|
|
9044
8122
|
const walk = (absoluteDir, relativeDir) => {
|
|
9045
|
-
for (const entry of
|
|
8123
|
+
for (const entry of readdirSync8(absoluteDir, { withFileTypes: true })) {
|
|
9046
8124
|
if (ignoredDirNames.has(entry.name)) continue;
|
|
9047
|
-
const absolutePath =
|
|
9048
|
-
const relativePath =
|
|
8125
|
+
const absolutePath = join30(absoluteDir, entry.name);
|
|
8126
|
+
const relativePath = join30(relativeDir, entry.name).replace(/\\/g, "/");
|
|
9049
8127
|
if (entry.isDirectory()) {
|
|
9050
8128
|
walk(absolutePath, relativePath);
|
|
9051
8129
|
continue;
|
|
9052
8130
|
}
|
|
9053
8131
|
if (!entry.isFile()) continue;
|
|
9054
8132
|
if (!isHostedSourceSnapshotFile(relativePath)) continue;
|
|
9055
|
-
files[relativePath] =
|
|
8133
|
+
files[relativePath] = readFileSync21(absolutePath, "utf-8");
|
|
9056
8134
|
}
|
|
9057
8135
|
};
|
|
9058
8136
|
walk(resolvedSourcePath, rootPrefix);
|
|
@@ -9245,25 +8323,25 @@ async function printHostedExecutionPackBundle(essencePath, namespace, jsonOutput
|
|
|
9245
8323
|
}
|
|
9246
8324
|
async function compileHostedExecutionPackBundle(essencePath, namespace) {
|
|
9247
8325
|
const client = getPublicAPIClient();
|
|
9248
|
-
const resolvedPath = essencePath ? resolveUserPath(essencePath) :
|
|
9249
|
-
if (!
|
|
8326
|
+
const resolvedPath = essencePath ? resolveUserPath(essencePath) : join30(process.cwd(), "decantr.essence.json");
|
|
8327
|
+
if (!existsSync28(resolvedPath)) {
|
|
9250
8328
|
throw new Error(`Essence file not found at ${resolvedPath}`);
|
|
9251
8329
|
}
|
|
9252
|
-
const essence = JSON.parse(
|
|
8330
|
+
const essence = JSON.parse(readFileSync21(resolvedPath, "utf-8"));
|
|
9253
8331
|
const bundle = await client.compileExecutionPacks(essence, namespace ? { namespace } : void 0);
|
|
9254
|
-
const contextDir =
|
|
8332
|
+
const contextDir = join30(dirname6(resolvedPath), ".decantr", "context");
|
|
9255
8333
|
return { resolvedPath, bundle, contextDir };
|
|
9256
8334
|
}
|
|
9257
8335
|
function writeHostedExecutionPackContextArtifacts(contextDir, bundle) {
|
|
9258
|
-
|
|
8336
|
+
mkdirSync15(contextDir, { recursive: true });
|
|
9259
8337
|
return writeExecutionPackBundleArtifacts(contextDir, bundle);
|
|
9260
8338
|
}
|
|
9261
8339
|
function resolvePagePackIdForRoute(essencePath, route) {
|
|
9262
|
-
if (!
|
|
8340
|
+
if (!existsSync28(essencePath)) {
|
|
9263
8341
|
throw new Error(`Essence file not found at ${essencePath}`);
|
|
9264
8342
|
}
|
|
9265
|
-
const essence = JSON.parse(
|
|
9266
|
-
if (!
|
|
8343
|
+
const essence = JSON.parse(readFileSync21(essencePath, "utf-8"));
|
|
8344
|
+
if (!isV48(essence)) {
|
|
9267
8345
|
throw new Error("Route-based pack resolution requires Essence v4.0.0.");
|
|
9268
8346
|
}
|
|
9269
8347
|
const target = essence.blueprint.routes?.[route];
|
|
@@ -9277,14 +8355,14 @@ function resolvePagePackIdForRoute(essencePath, route) {
|
|
|
9277
8355
|
}
|
|
9278
8356
|
async function printHostedSelectedExecutionPack(packType, id, essencePath, namespace, jsonOutput = false, writeContext = false) {
|
|
9279
8357
|
const client = getPublicAPIClient();
|
|
9280
|
-
const resolvedPath = essencePath ? resolveUserPath(essencePath) :
|
|
9281
|
-
if (!
|
|
8358
|
+
const resolvedPath = essencePath ? resolveUserPath(essencePath) : join30(process.cwd(), "decantr.essence.json");
|
|
8359
|
+
if (!existsSync28(resolvedPath)) {
|
|
9282
8360
|
throw new Error(`Essence file not found at ${resolvedPath}`);
|
|
9283
8361
|
}
|
|
9284
8362
|
if ((packType === "section" || packType === "page" || packType === "mutation") && !id) {
|
|
9285
8363
|
throw new Error(`Pack type "${packType}" requires an id.`);
|
|
9286
8364
|
}
|
|
9287
|
-
const essence = JSON.parse(
|
|
8365
|
+
const essence = JSON.parse(readFileSync21(resolvedPath, "utf-8"));
|
|
9288
8366
|
const selected = await client.selectExecutionPack(
|
|
9289
8367
|
{
|
|
9290
8368
|
essence,
|
|
@@ -9295,17 +8373,17 @@ async function printHostedSelectedExecutionPack(packType, id, essencePath, names
|
|
|
9295
8373
|
);
|
|
9296
8374
|
let writtenContextDir = null;
|
|
9297
8375
|
if (writeContext) {
|
|
9298
|
-
const contextDir =
|
|
9299
|
-
|
|
9300
|
-
|
|
9301
|
-
|
|
8376
|
+
const contextDir = join30(dirname6(resolvedPath), ".decantr", "context");
|
|
8377
|
+
mkdirSync15(contextDir, { recursive: true });
|
|
8378
|
+
writeFileSync18(
|
|
8379
|
+
join30(contextDir, "pack-manifest.json"),
|
|
9302
8380
|
JSON.stringify(selected.manifest, null, 2) + "\n"
|
|
9303
8381
|
);
|
|
9304
8382
|
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);
|
|
9305
8383
|
const markdownFile = manifestEntry?.markdown ?? `${selected.selector.packType}${selected.selector.id ? `-${selected.selector.id}` : ""}-pack.md`;
|
|
9306
8384
|
const jsonFile = manifestEntry?.json ?? `${selected.selector.packType}${selected.selector.id ? `-${selected.selector.id}` : ""}-pack.json`;
|
|
9307
|
-
|
|
9308
|
-
|
|
8385
|
+
writeFileSync18(join30(contextDir, markdownFile), selected.pack.renderedMarkdown);
|
|
8386
|
+
writeFileSync18(join30(contextDir, jsonFile), JSON.stringify(selected.pack, null, 2) + "\n");
|
|
9309
8387
|
writtenContextDir = contextDir;
|
|
9310
8388
|
}
|
|
9311
8389
|
if (jsonOutput) {
|
|
@@ -9330,20 +8408,20 @@ async function printHostedSelectedExecutionPack(packType, id, essencePath, names
|
|
|
9330
8408
|
}
|
|
9331
8409
|
async function printHostedExecutionPackManifest(essencePath, namespace, jsonOutput = false, writeContext = false) {
|
|
9332
8410
|
const client = getPublicAPIClient();
|
|
9333
|
-
const resolvedPath = essencePath ? resolveUserPath(essencePath) :
|
|
9334
|
-
if (!
|
|
8411
|
+
const resolvedPath = essencePath ? resolveUserPath(essencePath) : join30(process.cwd(), "decantr.essence.json");
|
|
8412
|
+
if (!existsSync28(resolvedPath)) {
|
|
9335
8413
|
throw new Error(`Essence file not found at ${resolvedPath}`);
|
|
9336
8414
|
}
|
|
9337
|
-
const essence = JSON.parse(
|
|
8415
|
+
const essence = JSON.parse(readFileSync21(resolvedPath, "utf-8"));
|
|
9338
8416
|
const manifest = await client.getExecutionPackManifest(
|
|
9339
8417
|
essence,
|
|
9340
8418
|
namespace ? { namespace } : void 0
|
|
9341
8419
|
);
|
|
9342
8420
|
let writtenContextDir = null;
|
|
9343
8421
|
if (writeContext) {
|
|
9344
|
-
const contextDir =
|
|
9345
|
-
|
|
9346
|
-
|
|
8422
|
+
const contextDir = join30(dirname6(resolvedPath), ".decantr", "context");
|
|
8423
|
+
mkdirSync15(contextDir, { recursive: true });
|
|
8424
|
+
writeFileSync18(join30(contextDir, "pack-manifest.json"), JSON.stringify(manifest, null, 2) + "\n");
|
|
9347
8425
|
writtenContextDir = contextDir;
|
|
9348
8426
|
}
|
|
9349
8427
|
if (jsonOutput) {
|
|
@@ -9364,14 +8442,14 @@ async function printHostedExecutionPackManifest(essencePath, namespace, jsonOutp
|
|
|
9364
8442
|
}
|
|
9365
8443
|
}
|
|
9366
8444
|
async function hydrateHostedExecutionPacksIfMissing(projectRoot, namespace = "@official") {
|
|
9367
|
-
const contextDir =
|
|
9368
|
-
const reviewPackPath =
|
|
9369
|
-
const manifestPath =
|
|
9370
|
-
if (
|
|
8445
|
+
const contextDir = join30(projectRoot, ".decantr", "context");
|
|
8446
|
+
const reviewPackPath = join30(contextDir, "review-pack.json");
|
|
8447
|
+
const manifestPath = join30(contextDir, "pack-manifest.json");
|
|
8448
|
+
if (existsSync28(reviewPackPath) && existsSync28(manifestPath)) {
|
|
9371
8449
|
return { attempted: false, hydrated: false };
|
|
9372
8450
|
}
|
|
9373
|
-
const essencePath =
|
|
9374
|
-
if (!
|
|
8451
|
+
const essencePath = join30(projectRoot, "decantr.essence.json");
|
|
8452
|
+
if (!existsSync28(essencePath)) {
|
|
9375
8453
|
return { attempted: false, hydrated: false };
|
|
9376
8454
|
}
|
|
9377
8455
|
const reviewHydration = await hydrateHostedReviewPackIfMissing(projectRoot, namespace);
|
|
@@ -9380,9 +8458,9 @@ async function hydrateHostedExecutionPacksIfMissing(projectRoot, namespace = "@o
|
|
|
9380
8458
|
}
|
|
9381
8459
|
try {
|
|
9382
8460
|
const client = getPublicAPIClient();
|
|
9383
|
-
const essence = JSON.parse(
|
|
8461
|
+
const essence = JSON.parse(readFileSync21(essencePath, "utf-8"));
|
|
9384
8462
|
const bundle = await client.compileExecutionPacks(essence, { namespace });
|
|
9385
|
-
|
|
8463
|
+
mkdirSync15(contextDir, { recursive: true });
|
|
9386
8464
|
writeExecutionPackBundleArtifacts(contextDir, bundle);
|
|
9387
8465
|
return { attempted: true, hydrated: true, scope: "bundle" };
|
|
9388
8466
|
} catch {
|
|
@@ -9390,19 +8468,19 @@ async function hydrateHostedExecutionPacksIfMissing(projectRoot, namespace = "@o
|
|
|
9390
8468
|
}
|
|
9391
8469
|
}
|
|
9392
8470
|
async function hydrateHostedReviewPackIfMissing(projectRoot, namespace = "@official") {
|
|
9393
|
-
const contextDir =
|
|
9394
|
-
const reviewPackPath =
|
|
9395
|
-
const manifestPath =
|
|
9396
|
-
if (
|
|
8471
|
+
const contextDir = join30(projectRoot, ".decantr", "context");
|
|
8472
|
+
const reviewPackPath = join30(contextDir, "review-pack.json");
|
|
8473
|
+
const manifestPath = join30(contextDir, "pack-manifest.json");
|
|
8474
|
+
if (existsSync28(reviewPackPath) && existsSync28(manifestPath)) {
|
|
9397
8475
|
return { attempted: false, hydrated: false };
|
|
9398
8476
|
}
|
|
9399
|
-
const essencePath =
|
|
9400
|
-
if (!
|
|
8477
|
+
const essencePath = join30(projectRoot, "decantr.essence.json");
|
|
8478
|
+
if (!existsSync28(essencePath)) {
|
|
9401
8479
|
return { attempted: false, hydrated: false };
|
|
9402
8480
|
}
|
|
9403
8481
|
try {
|
|
9404
8482
|
const client = getPublicAPIClient();
|
|
9405
|
-
const essence = JSON.parse(
|
|
8483
|
+
const essence = JSON.parse(readFileSync21(essencePath, "utf-8"));
|
|
9406
8484
|
const selected = await client.selectExecutionPack(
|
|
9407
8485
|
{
|
|
9408
8486
|
essence,
|
|
@@ -9410,14 +8488,14 @@ async function hydrateHostedReviewPackIfMissing(projectRoot, namespace = "@offic
|
|
|
9410
8488
|
},
|
|
9411
8489
|
{ namespace }
|
|
9412
8490
|
);
|
|
9413
|
-
|
|
9414
|
-
|
|
9415
|
-
|
|
9416
|
-
|
|
8491
|
+
mkdirSync15(contextDir, { recursive: true });
|
|
8492
|
+
writeFileSync18(join30(contextDir, "review-pack.md"), selected.pack.renderedMarkdown);
|
|
8493
|
+
writeFileSync18(
|
|
8494
|
+
join30(contextDir, "review-pack.json"),
|
|
9417
8495
|
JSON.stringify(selected.pack, null, 2) + "\n"
|
|
9418
8496
|
);
|
|
9419
|
-
if (!
|
|
9420
|
-
|
|
8497
|
+
if (!existsSync28(manifestPath)) {
|
|
8498
|
+
writeFileSync18(manifestPath, JSON.stringify(selected.manifest, null, 2) + "\n");
|
|
9421
8499
|
}
|
|
9422
8500
|
return { attempted: true, hydrated: true, scope: "review" };
|
|
9423
8501
|
} catch {
|
|
@@ -9427,17 +8505,17 @@ async function hydrateHostedReviewPackIfMissing(projectRoot, namespace = "@offic
|
|
|
9427
8505
|
async function printHostedFileCritique(sourcePath, namespace, jsonOutput = false, essencePath, treatmentsPath) {
|
|
9428
8506
|
const client = getPublicAPIClient();
|
|
9429
8507
|
const resolvedSourcePath = resolveUserPath(sourcePath);
|
|
9430
|
-
const resolvedEssencePath = essencePath ? resolveUserPath(essencePath) :
|
|
9431
|
-
const resolvedTreatmentsPath = treatmentsPath ? resolveUserPath(treatmentsPath) :
|
|
9432
|
-
if (!
|
|
8508
|
+
const resolvedEssencePath = essencePath ? resolveUserPath(essencePath) : join30(process.cwd(), "decantr.essence.json");
|
|
8509
|
+
const resolvedTreatmentsPath = treatmentsPath ? resolveUserPath(treatmentsPath) : join30(process.cwd(), "src", "styles", "treatments.css");
|
|
8510
|
+
if (!existsSync28(resolvedSourcePath)) {
|
|
9433
8511
|
throw new Error(`Source file not found at ${resolvedSourcePath}`);
|
|
9434
8512
|
}
|
|
9435
|
-
if (!
|
|
8513
|
+
if (!existsSync28(resolvedEssencePath)) {
|
|
9436
8514
|
throw new Error(`Essence file not found at ${resolvedEssencePath}`);
|
|
9437
8515
|
}
|
|
9438
|
-
const code =
|
|
9439
|
-
const essence = JSON.parse(
|
|
9440
|
-
const treatmentsCss =
|
|
8516
|
+
const code = readFileSync21(resolvedSourcePath, "utf-8");
|
|
8517
|
+
const essence = JSON.parse(readFileSync21(resolvedEssencePath, "utf-8"));
|
|
8518
|
+
const treatmentsCss = existsSync28(resolvedTreatmentsPath) ? readFileSync21(resolvedTreatmentsPath, "utf-8") : void 0;
|
|
9441
8519
|
const report = await client.critiqueFile(
|
|
9442
8520
|
{
|
|
9443
8521
|
essence,
|
|
@@ -9461,11 +8539,11 @@ async function printHostedFileCritique(sourcePath, namespace, jsonOutput = false
|
|
|
9461
8539
|
}
|
|
9462
8540
|
async function printHostedProjectAudit(namespace, jsonOutput = false, essencePath, distPath, sourcesPath) {
|
|
9463
8541
|
const client = getPublicAPIClient();
|
|
9464
|
-
const resolvedEssencePath = essencePath ? resolveUserPath(essencePath) :
|
|
9465
|
-
if (!
|
|
8542
|
+
const resolvedEssencePath = essencePath ? resolveUserPath(essencePath) : join30(process.cwd(), "decantr.essence.json");
|
|
8543
|
+
if (!existsSync28(resolvedEssencePath)) {
|
|
9466
8544
|
throw new Error(`Essence file not found at ${resolvedEssencePath}`);
|
|
9467
8545
|
}
|
|
9468
|
-
const essence = JSON.parse(
|
|
8546
|
+
const essence = JSON.parse(readFileSync21(resolvedEssencePath, "utf-8"));
|
|
9469
8547
|
const dist = readHostedDistSnapshot(distPath);
|
|
9470
8548
|
const sources = readHostedSourceSnapshot(sourcesPath);
|
|
9471
8549
|
const report = await client.auditProject(
|
|
@@ -9483,7 +8561,7 @@ async function printHostedProjectAudit(namespace, jsonOutput = false, essencePat
|
|
|
9483
8561
|
console.log(heading2("Hosted Project Audit"));
|
|
9484
8562
|
console.log(` Essence: ${resolvedEssencePath}`);
|
|
9485
8563
|
console.log(
|
|
9486
|
-
` Dist snapshot: ${dist ? distPath ? resolveUserPath(distPath) :
|
|
8564
|
+
` Dist snapshot: ${dist ? distPath ? resolveUserPath(distPath) : join30(process.cwd(), "dist") : "none"}`
|
|
9487
8565
|
);
|
|
9488
8566
|
console.log(
|
|
9489
8567
|
` Source snapshot: ${sources && sourcesPath ? resolveUserPath(sourcesPath) : "none"}`
|
|
@@ -9605,21 +8683,21 @@ function findPatternCandidateBySlug(candidates, slug) {
|
|
|
9605
8683
|
function readSuggestCodeContext(projectRoot, route, file) {
|
|
9606
8684
|
const pieces = [];
|
|
9607
8685
|
if (file) {
|
|
9608
|
-
const resolved = isAbsolute3(file) ? file :
|
|
9609
|
-
if (
|
|
9610
|
-
pieces.push(
|
|
8686
|
+
const resolved = isAbsolute3(file) ? file : join30(projectRoot, file);
|
|
8687
|
+
if (existsSync28(resolved)) {
|
|
8688
|
+
pieces.push(readFileSync21(resolved, "utf-8"));
|
|
9611
8689
|
}
|
|
9612
8690
|
}
|
|
9613
8691
|
if (route) {
|
|
9614
|
-
const analysisPath =
|
|
9615
|
-
if (
|
|
8692
|
+
const analysisPath = join30(projectRoot, ".decantr", "analysis.json");
|
|
8693
|
+
if (existsSync28(analysisPath)) {
|
|
9616
8694
|
try {
|
|
9617
|
-
const analysis = JSON.parse(
|
|
8695
|
+
const analysis = JSON.parse(readFileSync21(analysisPath, "utf-8"));
|
|
9618
8696
|
const routeEntry = analysis.routes?.routes?.find((entry) => entry.path === route);
|
|
9619
8697
|
if (routeEntry?.file) {
|
|
9620
|
-
const resolved =
|
|
9621
|
-
if (
|
|
9622
|
-
pieces.push(
|
|
8698
|
+
const resolved = join30(projectRoot, routeEntry.file);
|
|
8699
|
+
if (existsSync28(resolved)) {
|
|
8700
|
+
pieces.push(readFileSync21(resolved, "utf-8"));
|
|
9623
8701
|
}
|
|
9624
8702
|
}
|
|
9625
8703
|
} catch {
|
|
@@ -9632,7 +8710,9 @@ function localPatternMatches(projectRoot, query) {
|
|
|
9632
8710
|
if (!projectRoot) return [];
|
|
9633
8711
|
const pack = readLocalPatternPack(projectRoot);
|
|
9634
8712
|
const patterns = Array.isArray(pack?.patterns) ? pack.patterns : [];
|
|
9635
|
-
const queryTerms = query.toLowerCase().split(/[^a-z0-9]+/).filter((term) => term.length > 1).flatMap(
|
|
8713
|
+
const queryTerms = query.toLowerCase().split(/[^a-z0-9]+/).filter((term) => term.length > 1).flatMap(
|
|
8714
|
+
(term) => term.endsWith("s") && term.length > 3 ? [term, term.slice(0, -1)] : [term]
|
|
8715
|
+
);
|
|
9636
8716
|
if (queryTerms.length === 0) return [];
|
|
9637
8717
|
return patterns.map((pattern) => {
|
|
9638
8718
|
const id = typeof pattern.id === "string" ? pattern.id : "local-pattern";
|
|
@@ -9703,7 +8783,7 @@ async function cmdSuggest(query, options = {}) {
|
|
|
9703
8783
|
}
|
|
9704
8784
|
}
|
|
9705
8785
|
const registryClient = new RegistryClient({
|
|
9706
|
-
cacheDir:
|
|
8786
|
+
cacheDir: join30(projectRoot, ".decantr", "cache")
|
|
9707
8787
|
});
|
|
9708
8788
|
const code = options.fromCode || options.file ? readSuggestCodeContext(projectRoot, options.route, options.file) : "";
|
|
9709
8789
|
const localMatches = localPatternMatches(projectRoot, [query, code].filter(Boolean).join("\n"));
|
|
@@ -9758,7 +8838,9 @@ async function cmdSuggest(query, options = {}) {
|
|
|
9758
8838
|
if (matches.length === 0) {
|
|
9759
8839
|
console.log(dim3("No hosted/bundled registry patterns matched this query."));
|
|
9760
8840
|
console.log("");
|
|
9761
|
-
console.log(
|
|
8841
|
+
console.log(
|
|
8842
|
+
dim3('Use local law first, or run "decantr list patterns" to browse registry options.')
|
|
8843
|
+
);
|
|
9762
8844
|
return;
|
|
9763
8845
|
}
|
|
9764
8846
|
for (const match of matches.slice(0, 8)) {
|
|
@@ -9790,7 +8872,7 @@ async function cmdGet(type, id) {
|
|
|
9790
8872
|
}
|
|
9791
8873
|
const apiType = CONTENT_TYPE_TO_API_CONTENT_TYPE3[type];
|
|
9792
8874
|
const registryClient = new RegistryClient({
|
|
9793
|
-
cacheDir:
|
|
8875
|
+
cacheDir: join30(process.cwd(), ".decantr", "cache")
|
|
9794
8876
|
});
|
|
9795
8877
|
const result = await registryClient.fetchContentItem(apiType, id);
|
|
9796
8878
|
if (result) {
|
|
@@ -9807,10 +8889,10 @@ async function cmdGet(type, id) {
|
|
|
9807
8889
|
return;
|
|
9808
8890
|
}
|
|
9809
8891
|
async function cmdValidate(path) {
|
|
9810
|
-
const essencePath = path ||
|
|
8892
|
+
const essencePath = path || join30(process.cwd(), "decantr.essence.json");
|
|
9811
8893
|
let raw;
|
|
9812
8894
|
try {
|
|
9813
|
-
raw =
|
|
8895
|
+
raw = readFileSync21(essencePath, "utf-8");
|
|
9814
8896
|
} catch {
|
|
9815
8897
|
console.error(error2(`Could not read ${essencePath}`));
|
|
9816
8898
|
process.exitCode = 1;
|
|
@@ -9824,7 +8906,7 @@ async function cmdValidate(path) {
|
|
|
9824
8906
|
process.exitCode = 1;
|
|
9825
8907
|
return;
|
|
9826
8908
|
}
|
|
9827
|
-
const detectedVersion =
|
|
8909
|
+
const detectedVersion = isV48(essence) ? "v4" : "legacy";
|
|
9828
8910
|
console.log(`${DIM16}Detected essence version: ${detectedVersion}${RESET16}`);
|
|
9829
8911
|
const result = validateEssence2(essence);
|
|
9830
8912
|
if (result.valid) {
|
|
@@ -9877,7 +8959,7 @@ async function cmdList(type, sort, recommended, intelligenceSource, blueprintSet
|
|
|
9877
8959
|
return;
|
|
9878
8960
|
}
|
|
9879
8961
|
const registryClient = new RegistryClient({
|
|
9880
|
-
cacheDir:
|
|
8962
|
+
cacheDir: join30(process.cwd(), ".decantr", "cache")
|
|
9881
8963
|
});
|
|
9882
8964
|
const result = await registryClient.fetchContentList(
|
|
9883
8965
|
type,
|
|
@@ -9960,10 +9042,10 @@ ${CYAN9}Telemetry enabled.${RESET16} Decantr will send privacy-filtered CLI prod
|
|
|
9960
9042
|
}
|
|
9961
9043
|
function readCliPackageVersion2() {
|
|
9962
9044
|
const here = dirname6(fileURLToPath3(import.meta.url));
|
|
9963
|
-
const candidates = [
|
|
9045
|
+
const candidates = [join30(here, "..", "package.json"), join30(here, "..", "..", "package.json")];
|
|
9964
9046
|
for (const candidate of candidates) {
|
|
9965
9047
|
try {
|
|
9966
|
-
const pkg = JSON.parse(
|
|
9048
|
+
const pkg = JSON.parse(readFileSync21(candidate, "utf-8"));
|
|
9967
9049
|
if (pkg.version) return pkg.version;
|
|
9968
9050
|
} catch {
|
|
9969
9051
|
}
|
|
@@ -9974,19 +9056,19 @@ function timestampForFile() {
|
|
|
9974
9056
|
return (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
9975
9057
|
}
|
|
9976
9058
|
function backupExistingEssence(projectRoot, label) {
|
|
9977
|
-
const essencePath =
|
|
9978
|
-
if (!
|
|
9979
|
-
const backupPath =
|
|
9059
|
+
const essencePath = join30(projectRoot, "decantr.essence.json");
|
|
9060
|
+
if (!existsSync28(essencePath)) return null;
|
|
9061
|
+
const backupPath = join30(
|
|
9980
9062
|
projectRoot,
|
|
9981
9063
|
`decantr.essence.${label}.${timestampForFile()}.backup.json`
|
|
9982
9064
|
);
|
|
9983
|
-
|
|
9065
|
+
writeFileSync18(backupPath, readFileSync21(essencePath, "utf-8"), "utf-8");
|
|
9984
9066
|
return backupPath;
|
|
9985
9067
|
}
|
|
9986
9068
|
function writeBrownfieldProjectJson(input) {
|
|
9987
|
-
const decantrDir =
|
|
9988
|
-
|
|
9989
|
-
|
|
9069
|
+
const decantrDir = join30(input.projectRoot, ".decantr");
|
|
9070
|
+
mkdirSync15(join30(decantrDir, "context"), { recursive: true });
|
|
9071
|
+
mkdirSync15(join30(decantrDir, "cache"), { recursive: true });
|
|
9990
9072
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
9991
9073
|
const projectJson = {
|
|
9992
9074
|
detected: {
|
|
@@ -10027,7 +9109,7 @@ function writeBrownfieldProjectJson(input) {
|
|
|
10027
9109
|
}
|
|
10028
9110
|
}
|
|
10029
9111
|
};
|
|
10030
|
-
|
|
9112
|
+
writeFileSync18(join30(decantrDir, "project.json"), JSON.stringify(projectJson, null, 2) + "\n");
|
|
10031
9113
|
}
|
|
10032
9114
|
async function applyAcceptedBrownfieldProposal(input) {
|
|
10033
9115
|
const proposal = readBrownfieldProposal(input.projectRoot);
|
|
@@ -10043,8 +9125,8 @@ async function applyAcceptedBrownfieldProposal(input) {
|
|
|
10043
9125
|
process.exitCode = 1;
|
|
10044
9126
|
return;
|
|
10045
9127
|
}
|
|
10046
|
-
const essencePath =
|
|
10047
|
-
const hasEssence =
|
|
9128
|
+
const essencePath = join30(input.projectRoot, "decantr.essence.json");
|
|
9129
|
+
const hasEssence = existsSync28(essencePath);
|
|
10048
9130
|
let essence;
|
|
10049
9131
|
let backupPath = null;
|
|
10050
9132
|
if (input.mode === "accept" && hasEssence) {
|
|
@@ -10058,8 +9140,8 @@ async function applyAcceptedBrownfieldProposal(input) {
|
|
|
10058
9140
|
return;
|
|
10059
9141
|
}
|
|
10060
9142
|
if (input.mode === "merge" && hasEssence) {
|
|
10061
|
-
const existing = JSON.parse(
|
|
10062
|
-
if (!
|
|
9143
|
+
const existing = JSON.parse(readFileSync21(essencePath, "utf-8"));
|
|
9144
|
+
if (!isV48(existing)) {
|
|
10063
9145
|
console.log(
|
|
10064
9146
|
error2(
|
|
10065
9147
|
"Existing essence is not v4. Run `decantr migrate --to v4` before merging a brownfield proposal."
|
|
@@ -10095,9 +9177,9 @@ async function applyAcceptedBrownfieldProposal(input) {
|
|
|
10095
9177
|
assistantBridge: input.assistantBridge,
|
|
10096
9178
|
mode: input.mode
|
|
10097
9179
|
});
|
|
10098
|
-
|
|
9180
|
+
writeFileSync18(essencePath, JSON.stringify(essence, null, 2) + "\n", "utf-8");
|
|
10099
9181
|
const registryClient = new RegistryClient({
|
|
10100
|
-
cacheDir:
|
|
9182
|
+
cacheDir: join30(input.projectRoot, ".decantr", "cache"),
|
|
10101
9183
|
offline: true,
|
|
10102
9184
|
projectRoot: input.projectRoot
|
|
10103
9185
|
});
|
|
@@ -10124,7 +9206,7 @@ async function applyAcceptedBrownfieldProposal(input) {
|
|
|
10124
9206
|
}
|
|
10125
9207
|
const appliedRuleFiles = input.assistantBridge === "apply" ? applyAssistantBridge(input.projectRoot, input.detected) : [];
|
|
10126
9208
|
console.log(success3("\nBrownfield proposal accepted.\n"));
|
|
10127
|
-
const projectLabel = input.workspaceInfo.appRoot !== input.workspaceInfo.workspaceRoot ?
|
|
9209
|
+
const projectLabel = input.workspaceInfo.appRoot !== input.workspaceInfo.workspaceRoot ? relative6(input.workspaceInfo.workspaceRoot, input.workspaceInfo.appRoot).replace(/\\/g, "/") : void 0;
|
|
10128
9210
|
console.log(" Files created/updated:");
|
|
10129
9211
|
console.log(
|
|
10130
9212
|
` ${cyan3(displayProjectPath(input.workspaceInfo, "decantr.essence.json"))} Observed brownfield contract`
|
|
@@ -10276,7 +9358,7 @@ async function cmdInit(args) {
|
|
|
10276
9358
|
}
|
|
10277
9359
|
}
|
|
10278
9360
|
const registryClient = new RegistryClient({
|
|
10279
|
-
cacheDir:
|
|
9361
|
+
cacheDir: join30(projectRoot, ".decantr", "cache"),
|
|
10280
9362
|
apiUrl: args.registry,
|
|
10281
9363
|
offline: args.offline,
|
|
10282
9364
|
projectRoot
|
|
@@ -10608,7 +9690,7 @@ ${YELLOW12}You're offline. Scaffolding Decantr default.${RESET16}`);
|
|
|
10608
9690
|
if (appliedRuleFiles.length > 0) {
|
|
10609
9691
|
console.log(` ${dim3(`Rule bridge applied: ${appliedRuleFiles.join(", ")}`)}`);
|
|
10610
9692
|
}
|
|
10611
|
-
if (!
|
|
9693
|
+
if (!existsSync28(join30(projectRoot, "package.json"))) {
|
|
10612
9694
|
console.log("");
|
|
10613
9695
|
console.log(
|
|
10614
9696
|
dim3(` Note: ${cyan3("decantr init")} created Decantr contract/context files only.`)
|
|
@@ -10619,7 +9701,7 @@ ${YELLOW12}You're offline. Scaffolding Decantr default.${RESET16}`);
|
|
|
10619
9701
|
)
|
|
10620
9702
|
);
|
|
10621
9703
|
}
|
|
10622
|
-
const hasCompiledPacks =
|
|
9704
|
+
const hasCompiledPacks = existsSync28(join30(projectRoot, ".decantr", "context", "scaffold-pack.md"));
|
|
10623
9705
|
console.log("");
|
|
10624
9706
|
console.log(" Next steps:");
|
|
10625
9707
|
if (hasCompiledPacks) {
|
|
@@ -10659,7 +9741,7 @@ ${YELLOW12}You're offline. Scaffolding Decantr default.${RESET16}`);
|
|
|
10659
9741
|
console.log(` ${cyan3("decantr upgrade")} Update to latest patterns`);
|
|
10660
9742
|
console.log(` ${cyan3("decantr check")} Detect drift issues`);
|
|
10661
9743
|
console.log(` ${cyan3("decantr migrate --to v4")} Migrate older essence files to v4`);
|
|
10662
|
-
const essenceContent =
|
|
9744
|
+
const essenceContent = readFileSync21(result.essencePath, "utf-8");
|
|
10663
9745
|
const essence = JSON.parse(essenceContent);
|
|
10664
9746
|
const validation = validateEssence2(essence);
|
|
10665
9747
|
if (!validation.valid) {
|
|
@@ -10668,7 +9750,7 @@ Validation warnings: ${validation.errors.join(", ")}`));
|
|
|
10668
9750
|
}
|
|
10669
9751
|
console.log("");
|
|
10670
9752
|
let promptPages;
|
|
10671
|
-
if (
|
|
9753
|
+
if (isV48(essence)) {
|
|
10672
9754
|
const allPages = essence.blueprint.sections.flatMap(
|
|
10673
9755
|
(s) => s.pages.map((p) => ({ ...p, _sectionId: s.id, _shell: s.shell }))
|
|
10674
9756
|
);
|
|
@@ -10714,25 +9796,25 @@ Validation warnings: ${validation.errors.join(", ")}`));
|
|
|
10714
9796
|
}
|
|
10715
9797
|
}
|
|
10716
9798
|
async function cmdStatus(projectRoot = process.cwd()) {
|
|
10717
|
-
const essencePath =
|
|
10718
|
-
const projectJsonPath =
|
|
9799
|
+
const essencePath = join30(projectRoot, "decantr.essence.json");
|
|
9800
|
+
const projectJsonPath = join30(projectRoot, ".decantr", "project.json");
|
|
10719
9801
|
console.log(heading2("Decantr Project Status"));
|
|
10720
|
-
if (!
|
|
9802
|
+
if (!existsSync28(essencePath)) {
|
|
10721
9803
|
console.log(`${RED12}No decantr.essence.json found.${RESET16}`);
|
|
10722
9804
|
console.log(dim3('Run "decantr init" to create one.'));
|
|
10723
9805
|
return;
|
|
10724
9806
|
}
|
|
10725
9807
|
try {
|
|
10726
|
-
const essence = JSON.parse(
|
|
9808
|
+
const essence = JSON.parse(readFileSync21(essencePath, "utf-8"));
|
|
10727
9809
|
const validation = validateEssence2(essence);
|
|
10728
|
-
const essenceVersion =
|
|
9810
|
+
const essenceVersion = isV48(essence) ? "v4" : "legacy";
|
|
10729
9811
|
console.log(`${BOLD9}Essence:${RESET16}`);
|
|
10730
9812
|
if (validation.valid) {
|
|
10731
9813
|
console.log(` ${GREEN16}Valid${RESET16} (${essenceVersion})`);
|
|
10732
9814
|
} else {
|
|
10733
9815
|
console.log(` ${RED12}Invalid: ${validation.errors.join(", ")}${RESET16}`);
|
|
10734
9816
|
}
|
|
10735
|
-
if (
|
|
9817
|
+
if (isV48(essence)) {
|
|
10736
9818
|
const v4 = essence;
|
|
10737
9819
|
const sections = v4.blueprint.sections;
|
|
10738
9820
|
const flatPages = sections.flatMap((section) => section.pages ?? []);
|
|
@@ -10771,9 +9853,9 @@ async function cmdStatus(projectRoot = process.cwd()) {
|
|
|
10771
9853
|
}
|
|
10772
9854
|
console.log("");
|
|
10773
9855
|
console.log(`${BOLD9}Sync Status:${RESET16}`);
|
|
10774
|
-
if (
|
|
9856
|
+
if (existsSync28(projectJsonPath)) {
|
|
10775
9857
|
try {
|
|
10776
|
-
const projectJson = JSON.parse(
|
|
9858
|
+
const projectJson = JSON.parse(readFileSync21(projectJsonPath, "utf-8"));
|
|
10777
9859
|
const syncStatus = projectJson.sync?.status || "unknown";
|
|
10778
9860
|
const lastSync = projectJson.sync?.lastSync || "never";
|
|
10779
9861
|
const source = projectJson.sync?.registrySource || "unknown";
|
|
@@ -10791,7 +9873,7 @@ async function cmdStatus(projectRoot = process.cwd()) {
|
|
|
10791
9873
|
}
|
|
10792
9874
|
async function cmdSync() {
|
|
10793
9875
|
const projectRoot = process.cwd();
|
|
10794
|
-
const cacheDir =
|
|
9876
|
+
const cacheDir = join30(projectRoot, ".decantr", "cache");
|
|
10795
9877
|
console.log(heading2("Syncing registry content..."));
|
|
10796
9878
|
const result = await syncRegistry(cacheDir);
|
|
10797
9879
|
if (result.synced.length > 0) {
|
|
@@ -10985,14 +10067,14 @@ ${BOLD9}Examples:${RESET16}
|
|
|
10985
10067
|
process.exitCode = 1;
|
|
10986
10068
|
return;
|
|
10987
10069
|
}
|
|
10988
|
-
const themePath =
|
|
10989
|
-
if (!
|
|
10070
|
+
const themePath = join30(projectRoot, ".decantr", "custom", "themes", `${name}.json`);
|
|
10071
|
+
if (!existsSync28(themePath)) {
|
|
10990
10072
|
console.error(error2(`Theme "${name}" not found at ${themePath}`));
|
|
10991
10073
|
process.exitCode = 1;
|
|
10992
10074
|
return;
|
|
10993
10075
|
}
|
|
10994
10076
|
try {
|
|
10995
|
-
const theme = JSON.parse(
|
|
10077
|
+
const theme = JSON.parse(readFileSync21(themePath, "utf-8"));
|
|
10996
10078
|
const result = validateCustomTheme(theme);
|
|
10997
10079
|
if (result.valid) {
|
|
10998
10080
|
console.log(success3(`Custom theme "${name}" is valid`));
|
|
@@ -11124,13 +10206,42 @@ function withProject(command, projectArg) {
|
|
|
11124
10206
|
return projectArg ? `${command} --project ${projectArg}` : command;
|
|
11125
10207
|
}
|
|
11126
10208
|
function displayProjectPath(workspaceInfo, projectPath) {
|
|
11127
|
-
const absolutePath =
|
|
11128
|
-
const relativePath =
|
|
10209
|
+
const absolutePath = join30(workspaceInfo.appRoot, projectPath);
|
|
10210
|
+
const relativePath = relative6(workspaceInfo.cwd, absolutePath).replace(/\\/g, "/");
|
|
11129
10211
|
if (relativePath && !relativePath.startsWith("..") && !isAbsolute3(relativePath)) {
|
|
11130
10212
|
return relativePath;
|
|
11131
10213
|
}
|
|
11132
10214
|
return absolutePath;
|
|
11133
10215
|
}
|
|
10216
|
+
function projectRelativeGraphPath(projectRoot, filePath) {
|
|
10217
|
+
const relativePath = relative6(projectRoot, isAbsolute3(filePath) ? filePath : resolve4(filePath));
|
|
10218
|
+
if (relativePath && !relativePath.startsWith("..") && !isAbsolute3(relativePath)) {
|
|
10219
|
+
return relativePath.replace(/\\/g, "/");
|
|
10220
|
+
}
|
|
10221
|
+
return null;
|
|
10222
|
+
}
|
|
10223
|
+
function graphSourceNodeIdForTaskFile(projectRoot, snapshot, filePath) {
|
|
10224
|
+
const trimmed = filePath.trim();
|
|
10225
|
+
if (!trimmed) return null;
|
|
10226
|
+
if (trimmed.startsWith("src:") && snapshot.nodes.some((node) => node.id === trimmed)) {
|
|
10227
|
+
return trimmed;
|
|
10228
|
+
}
|
|
10229
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
10230
|
+
candidates.add(trimmed.replace(/\\/g, "/").replace(/^\.\//, ""));
|
|
10231
|
+
const cwdRelative = projectRelativeGraphPath(projectRoot, trimmed);
|
|
10232
|
+
if (cwdRelative) candidates.add(cwdRelative);
|
|
10233
|
+
const projectRelative = projectRelativeGraphPath(projectRoot, join30(projectRoot, trimmed));
|
|
10234
|
+
if (projectRelative) candidates.add(projectRelative);
|
|
10235
|
+
for (const candidate of candidates) {
|
|
10236
|
+
const nodeId = `src:${candidate}`;
|
|
10237
|
+
if (snapshot.nodes.some((node) => node.id === nodeId)) return nodeId;
|
|
10238
|
+
}
|
|
10239
|
+
return snapshot.nodes.find((node) => {
|
|
10240
|
+
if (node.type !== "SourceArtifact") return false;
|
|
10241
|
+
const path = graphPayloadString(node.payload, "path");
|
|
10242
|
+
return Boolean(path && (path === trimmed || candidates.has(path)));
|
|
10243
|
+
})?.id ?? null;
|
|
10244
|
+
}
|
|
11134
10245
|
function stripProjectArgs(args, startIndex = 1) {
|
|
11135
10246
|
const stripped = [args[0]];
|
|
11136
10247
|
for (let index = startIndex; index < args.length; index += 1) {
|
|
@@ -11193,7 +10304,7 @@ function printWorkspaceProjectSelection(workspaceInfo, commandName = "command")
|
|
|
11193
10304
|
function printMonorepoSetupGuidance(workspaceInfo) {
|
|
11194
10305
|
const candidate = firstWorkspaceCandidate(workspaceInfo);
|
|
11195
10306
|
const attachedProjects = workspaceInfo.appCandidates.filter(
|
|
11196
|
-
(appCandidate) =>
|
|
10307
|
+
(appCandidate) => existsSync28(join30(workspaceInfo.workspaceRoot, appCandidate, "decantr.essence.json"))
|
|
11197
10308
|
);
|
|
11198
10309
|
const firstAttached = attachedProjects[0];
|
|
11199
10310
|
console.log(heading2("Decantr Setup"));
|
|
@@ -11261,7 +10372,7 @@ function printMonorepoSetupGuidance(workspaceInfo) {
|
|
|
11261
10372
|
function resolveWorkflowProject(flags, commandName = "command", options = {}) {
|
|
11262
10373
|
const projectArg = flagString(flags, "project");
|
|
11263
10374
|
const workspaceInfo = resolveWorkspaceInfo(process.cwd(), projectArg);
|
|
11264
|
-
if (projectArg && options.requireExisting !== false && !
|
|
10375
|
+
if (projectArg && options.requireExisting !== false && !existsSync28(workspaceInfo.appRoot)) {
|
|
11265
10376
|
printProjectNotFound(projectArg, commandName);
|
|
11266
10377
|
process.exitCode = 1;
|
|
11267
10378
|
return null;
|
|
@@ -11269,7 +10380,7 @@ function resolveWorkflowProject(flags, commandName = "command", options = {}) {
|
|
|
11269
10380
|
if (projectArg && options.requireAppCandidate && workspaceInfo.appCandidates.length > 0) {
|
|
11270
10381
|
const normalizedProject = normalizedProjectPath(projectArg);
|
|
11271
10382
|
const normalizedWorkspaceProject = normalizedProjectPath(
|
|
11272
|
-
|
|
10383
|
+
relative6(workspaceInfo.workspaceRoot, workspaceInfo.appRoot)
|
|
11273
10384
|
);
|
|
11274
10385
|
const knownCandidate = normalizedProject ? workspaceInfo.appCandidates.includes(normalizedProject) || Boolean(
|
|
11275
10386
|
normalizedWorkspaceProject && workspaceInfo.appCandidates.includes(normalizedWorkspaceProject)
|
|
@@ -11318,12 +10429,137 @@ function formatScanApplicability(status) {
|
|
|
11318
10429
|
if (status === "not_applicable") return dim3("not applicable");
|
|
11319
10430
|
return dim3("unknown");
|
|
11320
10431
|
}
|
|
10432
|
+
function formatScanGraphPreviewStatus(status) {
|
|
10433
|
+
if (status === "current") return success3("current");
|
|
10434
|
+
if (status === "stale") return `${YELLOW12}stale or missing${RESET16}`;
|
|
10435
|
+
if (status === "needs_migration") return `${YELLOW12}needs migration${RESET16}`;
|
|
10436
|
+
if (status === "not_attached") return dim3("not attached");
|
|
10437
|
+
return dim3("unavailable");
|
|
10438
|
+
}
|
|
10439
|
+
function relativeGraphArtifactPath(projectRoot, artifactPath) {
|
|
10440
|
+
return relative6(projectRoot, artifactPath).replace(/\\/g, "/");
|
|
10441
|
+
}
|
|
10442
|
+
function buildScanGraphPreview(workspaceInfo, projectArg) {
|
|
10443
|
+
let artifacts;
|
|
10444
|
+
try {
|
|
10445
|
+
artifacts = buildGraphArtifacts(workspaceInfo.appRoot);
|
|
10446
|
+
} catch (error3) {
|
|
10447
|
+
const message = error3.message;
|
|
10448
|
+
const needsMigration = message.includes("Essence v4");
|
|
10449
|
+
return {
|
|
10450
|
+
status: needsMigration ? "needs_migration" : "unavailable",
|
|
10451
|
+
canPreview: false,
|
|
10452
|
+
readOnly: true,
|
|
10453
|
+
message: needsMigration ? "Existing Decantr contract needs Essence v4 before a typed Contract graph can be previewed." : message,
|
|
10454
|
+
nextCommand: needsMigration ? withProject("decantr migrate --to v4", projectArg) : null,
|
|
10455
|
+
staleArtifacts: [],
|
|
10456
|
+
snapshot: null,
|
|
10457
|
+
capsule: null,
|
|
10458
|
+
diff: null
|
|
10459
|
+
};
|
|
10460
|
+
}
|
|
10461
|
+
if (!artifacts) {
|
|
10462
|
+
return {
|
|
10463
|
+
status: "not_attached",
|
|
10464
|
+
canPreview: false,
|
|
10465
|
+
readOnly: true,
|
|
10466
|
+
message: "No decantr.essence.json found. Adopt the project to create the first typed Contract graph baseline.",
|
|
10467
|
+
nextCommand: withProject("decantr adopt --yes", projectArg),
|
|
10468
|
+
staleArtifacts: [],
|
|
10469
|
+
snapshot: null,
|
|
10470
|
+
capsule: null,
|
|
10471
|
+
diff: null
|
|
10472
|
+
};
|
|
10473
|
+
}
|
|
10474
|
+
const stale = artifacts.staleArtifacts.length > 0;
|
|
10475
|
+
const diffSummary = summarizeGraphDiff(artifacts.diff);
|
|
10476
|
+
return {
|
|
10477
|
+
status: stale ? "stale" : "current",
|
|
10478
|
+
canPreview: true,
|
|
10479
|
+
readOnly: true,
|
|
10480
|
+
message: stale ? "A typed Contract graph can be derived now, but saved graph artifacts are missing or stale." : "Typed Contract graph artifacts are current with the project-owned contract sources.",
|
|
10481
|
+
nextCommand: stale ? withProject("decantr graph", projectArg) : null,
|
|
10482
|
+
staleArtifacts: artifacts.staleArtifacts.map(
|
|
10483
|
+
(path) => displayProjectPath(workspaceInfo, relativeGraphArtifactPath(artifacts.projectRoot, path))
|
|
10484
|
+
),
|
|
10485
|
+
snapshot: {
|
|
10486
|
+
id: artifacts.snapshot.id,
|
|
10487
|
+
schemaVersion: artifacts.snapshot.schema_version,
|
|
10488
|
+
sourceHash: artifacts.snapshot.source_hash,
|
|
10489
|
+
nodes: artifacts.snapshot.summary.nodes,
|
|
10490
|
+
edges: artifacts.snapshot.summary.edges,
|
|
10491
|
+
findings: artifacts.snapshot.summary.findings,
|
|
10492
|
+
evidence: artifacts.snapshot.summary.evidence,
|
|
10493
|
+
sourceArtifacts: artifacts.snapshot.nodes.filter((node) => node.type === "SourceArtifact").length
|
|
10494
|
+
},
|
|
10495
|
+
capsule: {
|
|
10496
|
+
cacheKey: artifacts.capsule.cache_key,
|
|
10497
|
+
routes: artifacts.capsule.summary.routes,
|
|
10498
|
+
components: artifacts.capsule.summary.components,
|
|
10499
|
+
tokens: artifacts.capsule.summary.tokens,
|
|
10500
|
+
localRules: artifacts.capsule.summary.local_rules,
|
|
10501
|
+
styleBridge: artifacts.capsule.summary.style_bridge,
|
|
10502
|
+
sourceArtifacts: artifacts.capsule.summary.source_artifacts,
|
|
10503
|
+
sourceArtifactLimit: artifacts.capsule.source_artifact_limit,
|
|
10504
|
+
sourceArtifactsTruncated: artifacts.capsule.source_artifacts_truncated,
|
|
10505
|
+
openFindings: artifacts.capsule.summary.open_findings
|
|
10506
|
+
},
|
|
10507
|
+
diff: {
|
|
10508
|
+
ops: diffSummary.total,
|
|
10509
|
+
findingsAdded: diffSummary.findings.added,
|
|
10510
|
+
findingsResolved: diffSummary.findings.resolved,
|
|
10511
|
+
evidenceAdded: diffSummary.evidence.added
|
|
10512
|
+
}
|
|
10513
|
+
};
|
|
10514
|
+
}
|
|
10515
|
+
function printScanGraphPreview(preview) {
|
|
10516
|
+
if (!preview) return;
|
|
10517
|
+
console.log(`${BOLD9}Typed Contract Graph${RESET16}`);
|
|
10518
|
+
console.log(` Status: ${formatScanGraphPreviewStatus(preview.status)}`);
|
|
10519
|
+
console.log(` Read-only: ${preview.readOnly ? "yes" : "no"}`);
|
|
10520
|
+
if (preview.snapshot && preview.capsule) {
|
|
10521
|
+
console.log(
|
|
10522
|
+
` Snapshot: ${preview.snapshot.nodes} nodes, ${preview.snapshot.edges} edges`
|
|
10523
|
+
);
|
|
10524
|
+
console.log(
|
|
10525
|
+
` Evidence: ${preview.snapshot.findings} finding nodes, ${preview.snapshot.evidence} evidence nodes, ${preview.snapshot.sourceArtifacts} sources`
|
|
10526
|
+
);
|
|
10527
|
+
console.log(
|
|
10528
|
+
` Capsule: ${preview.capsule.routes} routes, ${preview.capsule.localRules} local rules, ${preview.capsule.styleBridge} style bridge mappings`
|
|
10529
|
+
);
|
|
10530
|
+
const sourceArtifactIndex = preview.capsule.sourceArtifactsTruncated ? `${preview.capsule.sourceArtifactLimit}/${preview.capsule.sourceArtifacts} source handles` : `${preview.capsule.sourceArtifacts} source handles`;
|
|
10531
|
+
console.log(` Source handles: ${sourceArtifactIndex}`);
|
|
10532
|
+
if (preview.diff) {
|
|
10533
|
+
const diffHints = [
|
|
10534
|
+
preview.diff.findingsAdded > 0 ? `${preview.diff.findingsAdded} finding added` : null,
|
|
10535
|
+
preview.diff.findingsResolved > 0 ? `${preview.diff.findingsResolved} finding resolved` : null,
|
|
10536
|
+
preview.diff.evidenceAdded > 0 ? `${preview.diff.evidenceAdded} evidence added` : null
|
|
10537
|
+
].filter(Boolean);
|
|
10538
|
+
console.log(
|
|
10539
|
+
` Diff: ${preview.diff.ops} ops${diffHints.length > 0 ? ` (${diffHints.join(", ")})` : ""}`
|
|
10540
|
+
);
|
|
10541
|
+
}
|
|
10542
|
+
}
|
|
10543
|
+
console.log(` ${dim3(preview.message)}`);
|
|
10544
|
+
if (preview.staleArtifacts.length > 0) {
|
|
10545
|
+
console.log(` Stale artifacts:${" "} ${preview.staleArtifacts.slice(0, 3).join(", ")}`);
|
|
10546
|
+
if (preview.staleArtifacts.length > 3) {
|
|
10547
|
+
console.log(` ${dim3(`...${preview.staleArtifacts.length - 3} more`)}`);
|
|
10548
|
+
}
|
|
10549
|
+
}
|
|
10550
|
+
if (preview.nextCommand) {
|
|
10551
|
+
console.log(` Next: ${cyan3(preview.nextCommand)}`);
|
|
10552
|
+
}
|
|
10553
|
+
console.log("");
|
|
10554
|
+
}
|
|
11321
10555
|
function printScanReport(report) {
|
|
11322
10556
|
console.log(heading2("Decantr Scan"));
|
|
11323
10557
|
console.log(dim3("Read-only Brownfield reconnaissance. No files were written."));
|
|
11324
10558
|
console.log("");
|
|
11325
10559
|
console.log(`${BOLD9}Verdict${RESET16}`);
|
|
11326
|
-
console.log(
|
|
10560
|
+
console.log(
|
|
10561
|
+
` ${formatScanApplicability(report.applicability.status)} ${report.applicability.label}`
|
|
10562
|
+
);
|
|
11327
10563
|
console.log(
|
|
11328
10564
|
` Confidence: ${cyan3(`${report.confidence.score}/100`)} (${report.confidence.level})`
|
|
11329
10565
|
);
|
|
@@ -11332,12 +10568,15 @@ function printScanReport(report) {
|
|
|
11332
10568
|
}
|
|
11333
10569
|
console.log("");
|
|
11334
10570
|
console.log(`${BOLD9}Project${RESET16}`);
|
|
11335
|
-
console.log(
|
|
10571
|
+
console.log(
|
|
10572
|
+
` Framework: ${cyan3(report.project.framework)}${report.project.frameworkVersion ? ` ${report.project.frameworkVersion}` : ""}`
|
|
10573
|
+
);
|
|
11336
10574
|
console.log(` Package manager:${" "} ${report.project.packageManager}`);
|
|
11337
10575
|
console.log(` Language: ${report.project.primaryLanguage}`);
|
|
11338
10576
|
console.log(` TypeScript: ${report.project.hasTypeScript ? "yes" : "no"}`);
|
|
11339
10577
|
console.log(` Decantr: ${report.project.hasDecantr ? "present" : "not attached"}`);
|
|
11340
10578
|
console.log("");
|
|
10579
|
+
printScanGraphPreview(report.graphPreview);
|
|
11341
10580
|
console.log(`${BOLD9}Routes And Styling${RESET16}`);
|
|
11342
10581
|
console.log(` Routes: ${report.routes.count} (${report.routes.strategy})`);
|
|
11343
10582
|
for (const route of report.routes.items.slice(0, 8)) {
|
|
@@ -11355,7 +10594,9 @@ function printScanReport(report) {
|
|
|
11355
10594
|
console.log("");
|
|
11356
10595
|
if (report.staticHosting.githubPagesLikely || report.pagesProbe) {
|
|
11357
10596
|
console.log(`${BOLD9}Published Surface${RESET16}`);
|
|
11358
|
-
console.log(
|
|
10597
|
+
console.log(
|
|
10598
|
+
` GitHub Pages: ${report.staticHosting.githubPagesLikely ? "likely" : "not detected"}`
|
|
10599
|
+
);
|
|
11359
10600
|
if (report.source.publishedSiteUrl) {
|
|
11360
10601
|
console.log(` Site URL: ${report.source.publishedSiteUrl}`);
|
|
11361
10602
|
}
|
|
@@ -11395,11 +10636,13 @@ async function cmdScanWorkflow(args) {
|
|
|
11395
10636
|
const report = await scanProjectReadOnly(workspaceInfo.appRoot, {
|
|
11396
10637
|
input: { kind: "local", value: inputValue }
|
|
11397
10638
|
});
|
|
10639
|
+
const graphPreview = buildScanGraphPreview(workspaceInfo, projectArg);
|
|
10640
|
+
const reportWithGraph = { ...report, graphPreview };
|
|
11398
10641
|
if (jsonOutput) {
|
|
11399
|
-
console.log(JSON.stringify(
|
|
10642
|
+
console.log(JSON.stringify(reportWithGraph, null, 2));
|
|
11400
10643
|
return;
|
|
11401
10644
|
}
|
|
11402
|
-
printScanReport(
|
|
10645
|
+
printScanReport(reportWithGraph);
|
|
11403
10646
|
}
|
|
11404
10647
|
async function cmdSetupWorkflow(args) {
|
|
11405
10648
|
const { flags } = parseLooseArgs(args);
|
|
@@ -11421,9 +10664,9 @@ async function cmdSetupWorkflow(args) {
|
|
|
11421
10664
|
console.log(` Detected: ${formatDetection(detected)}`);
|
|
11422
10665
|
console.log("");
|
|
11423
10666
|
if (detected.existingEssence) {
|
|
11424
|
-
const hasLocalPatterns =
|
|
11425
|
-
const hasLocalRules =
|
|
11426
|
-
const hasStyleBridge =
|
|
10667
|
+
const hasLocalPatterns = existsSync28(localPatternsPath(workspaceInfo.appRoot));
|
|
10668
|
+
const hasLocalRules = existsSync28(localRulesPath(workspaceInfo.appRoot));
|
|
10669
|
+
const hasStyleBridge = existsSync28(styleBridgePath(workspaceInfo.appRoot));
|
|
11427
10670
|
const verifyCommand = hasLocalPatterns || hasLocalRules ? "decantr verify --brownfield --local-patterns" : "decantr verify --brownfield";
|
|
11428
10671
|
console.log(`${BOLD9}Recommended path:${RESET16} maintain an attached Decantr project`);
|
|
11429
10672
|
console.log(
|
|
@@ -11441,7 +10684,9 @@ async function cmdSetupWorkflow(args) {
|
|
|
11441
10684
|
console.log(
|
|
11442
10685
|
` ${cyan3(withProject(verifyCommand, projectArg))} Run local health and drift checks`
|
|
11443
10686
|
);
|
|
11444
|
-
console.log(
|
|
10687
|
+
console.log(
|
|
10688
|
+
` ${cyan3(withProject("decantr ci init", projectArg))} Wire the app into CI`
|
|
10689
|
+
);
|
|
11445
10690
|
return;
|
|
11446
10691
|
}
|
|
11447
10692
|
if (hasFootprint) {
|
|
@@ -11509,7 +10754,7 @@ async function cmdAdoptWorkflow(args) {
|
|
|
11509
10754
|
const hydratePacks = flagBoolean(flags, "packs", true) && !flagBoolean(flags, "skip-packs") && !flagBoolean(flags, "offline") && process.env.DECANTR_OFFLINE !== "true";
|
|
11510
10755
|
const initCi = flagBoolean(flags, "ci") || flagBoolean(flags, "init-ci");
|
|
11511
10756
|
const assistantBridge = flagString(flags, "assistant-bridge");
|
|
11512
|
-
const hasEssence =
|
|
10757
|
+
const hasEssence = existsSync28(join30(projectRoot, "decantr.essence.json"));
|
|
11513
10758
|
const proposalFlag = flagBoolean(flags, "replace-essence") ? "--replace-essence" : flagBoolean(flags, "merge-proposal") || hasEssence ? "--merge-proposal" : "--accept-proposal";
|
|
11514
10759
|
const steps = [
|
|
11515
10760
|
"analyze current app and write .decantr/brownfield intelligence",
|
|
@@ -11518,6 +10763,7 @@ async function cmdAdoptWorkflow(args) {
|
|
|
11518
10763
|
if (hydratePacks) {
|
|
11519
10764
|
steps.push("hydrate hosted execution packs into the app context");
|
|
11520
10765
|
}
|
|
10766
|
+
steps.push("write typed Contract graph baseline");
|
|
11521
10767
|
if (runVerify) {
|
|
11522
10768
|
steps.push(
|
|
11523
10769
|
runBrowser ? "verify with Project Health, browser evidence, visual manifest, and baseline" : "verify with Project Health and baseline"
|
|
@@ -11557,7 +10803,7 @@ async function cmdAdoptWorkflow(args) {
|
|
|
11557
10803
|
if (hydratePacks) {
|
|
11558
10804
|
try {
|
|
11559
10805
|
const { bundle, contextDir } = await compileHostedExecutionPackBundle(
|
|
11560
|
-
|
|
10806
|
+
join30(projectRoot, "decantr.essence.json")
|
|
11561
10807
|
);
|
|
11562
10808
|
const written = writeHostedExecutionPackContextArtifacts(
|
|
11563
10809
|
contextDir,
|
|
@@ -11579,8 +10825,10 @@ async function cmdAdoptWorkflow(args) {
|
|
|
11579
10825
|
} else if (flagBoolean(flags, "offline") || process.env.DECANTR_OFFLINE === "true") {
|
|
11580
10826
|
console.log(dim3("Skipping hosted pack hydration in offline mode."));
|
|
11581
10827
|
}
|
|
10828
|
+
await cmdGraph(projectRoot, { displayRoot: process.cwd() });
|
|
10829
|
+
if (process.exitCode && process.exitCode !== 0) return;
|
|
11582
10830
|
if (runVerify) {
|
|
11583
|
-
const { cmdHealth } = await import("./health-
|
|
10831
|
+
const { cmdHealth } = await import("./health-DDT4RABT.js");
|
|
11584
10832
|
await cmdHealth(projectRoot, {
|
|
11585
10833
|
browser: runBrowser,
|
|
11586
10834
|
browserBaseUrl: baseUrl,
|
|
@@ -11651,7 +10899,7 @@ async function cmdVerifyWorkflow(args) {
|
|
|
11651
10899
|
return;
|
|
11652
10900
|
}
|
|
11653
10901
|
if (workspaceMode) {
|
|
11654
|
-
const { cmdWorkspace } = await import("./workspace-
|
|
10902
|
+
const { cmdWorkspace } = await import("./workspace-3XNCMZ5L.js");
|
|
11655
10903
|
await cmdWorkspace(process.cwd(), ["workspace", "health", ...withoutWorkflowOnlyFlags(args)]);
|
|
11656
10904
|
return;
|
|
11657
10905
|
}
|
|
@@ -11684,7 +10932,7 @@ async function cmdVerifyWorkflow(args) {
|
|
|
11684
10932
|
}
|
|
11685
10933
|
let guardExitCode;
|
|
11686
10934
|
if (brownfield) {
|
|
11687
|
-
const { cmdHeal, collectCheckIssues } = await import("./heal-
|
|
10935
|
+
const { cmdHeal, collectCheckIssues } = await import("./heal-HHVVTVIH.js");
|
|
11688
10936
|
if (quietOutput) {
|
|
11689
10937
|
const result = collectCheckIssues(workspaceInfo.appRoot, { brownfield: true });
|
|
11690
10938
|
guardExitCode = result.issues.some((issue) => issue.type === "error") ? 1 : void 0;
|
|
@@ -11694,7 +10942,7 @@ async function cmdVerifyWorkflow(args) {
|
|
|
11694
10942
|
process.exitCode = void 0;
|
|
11695
10943
|
}
|
|
11696
10944
|
}
|
|
11697
|
-
const { cmdHealth, parseHealthArgs } = await import("./health-
|
|
10945
|
+
const { cmdHealth, parseHealthArgs } = await import("./health-DDT4RABT.js");
|
|
11698
10946
|
await cmdHealth(workspaceInfo.appRoot, parseHealthArgs(healthArgs));
|
|
11699
10947
|
if (localPatterns) {
|
|
11700
10948
|
const validation = validateLocalLaw(workspaceInfo.appRoot);
|
|
@@ -11763,9 +11011,9 @@ async function cmdVerifyWorkflow(args) {
|
|
|
11763
11011
|
}
|
|
11764
11012
|
}
|
|
11765
11013
|
function readJsonIfPresent(path) {
|
|
11766
|
-
if (!
|
|
11014
|
+
if (!existsSync28(path)) return null;
|
|
11767
11015
|
try {
|
|
11768
|
-
return JSON.parse(
|
|
11016
|
+
return JSON.parse(readFileSync21(path, "utf-8"));
|
|
11769
11017
|
} catch {
|
|
11770
11018
|
return null;
|
|
11771
11019
|
}
|
|
@@ -11846,6 +11094,60 @@ function createTaskAuthoritySummary(input) {
|
|
|
11846
11094
|
}
|
|
11847
11095
|
return { lane, sourceAuthority, styleAuthority, activeAuthorities, runtimeBoundary, warnings };
|
|
11848
11096
|
}
|
|
11097
|
+
function behaviorTaskKeywords(task) {
|
|
11098
|
+
return [
|
|
11099
|
+
...new Set(
|
|
11100
|
+
task.toLowerCase().split(/[^a-z0-9_-]+/).map((term) => term.trim()).filter((term) => term.length >= 3)
|
|
11101
|
+
)
|
|
11102
|
+
];
|
|
11103
|
+
}
|
|
11104
|
+
function rankBehaviorObligationsForTask(obligations, routePatterns, taskSummary) {
|
|
11105
|
+
const routePatternSet = new Set(routePatterns.map((pattern) => pattern.toLowerCase()));
|
|
11106
|
+
const keywords = behaviorTaskKeywords(taskSummary);
|
|
11107
|
+
const ranked = obligations.map((entry) => {
|
|
11108
|
+
const haystack = [
|
|
11109
|
+
entry.patternId,
|
|
11110
|
+
entry.patternRole,
|
|
11111
|
+
entry.intent,
|
|
11112
|
+
...entry.riskProfile,
|
|
11113
|
+
...entry.componentPaths,
|
|
11114
|
+
...entry.obligations.flatMap((obligation) => [obligation.id, obligation.label])
|
|
11115
|
+
].filter((value) => typeof value === "string").join(" ").toLowerCase();
|
|
11116
|
+
const reasons = [];
|
|
11117
|
+
let score = 0;
|
|
11118
|
+
if (routePatternSet.has(entry.patternId.toLowerCase())) {
|
|
11119
|
+
score += 5;
|
|
11120
|
+
reasons.push("route_pattern");
|
|
11121
|
+
}
|
|
11122
|
+
for (const keyword of keywords) {
|
|
11123
|
+
if (!haystack.includes(keyword)) continue;
|
|
11124
|
+
score += 2;
|
|
11125
|
+
reasons.push(`task:${keyword}`);
|
|
11126
|
+
}
|
|
11127
|
+
if (/dialog|modal|confirm|delete|destructive|remove|account/.test(taskSummary)) {
|
|
11128
|
+
if (/dialog|modal|confirm|destructive/.test(haystack)) {
|
|
11129
|
+
score += 3;
|
|
11130
|
+
reasons.push("interaction_intent");
|
|
11131
|
+
}
|
|
11132
|
+
}
|
|
11133
|
+
if (/form|input|field|label|submit|validation/.test(taskSummary)) {
|
|
11134
|
+
if (/form|input|label|submit|validation/.test(haystack)) {
|
|
11135
|
+
score += 3;
|
|
11136
|
+
reasons.push("form_intent");
|
|
11137
|
+
}
|
|
11138
|
+
}
|
|
11139
|
+
return {
|
|
11140
|
+
...entry,
|
|
11141
|
+
relevance: {
|
|
11142
|
+
score,
|
|
11143
|
+
reasons: reasons.length > 0 ? [...new Set(reasons)] : ["accepted_local_law"]
|
|
11144
|
+
}
|
|
11145
|
+
};
|
|
11146
|
+
});
|
|
11147
|
+
return ranked.sort(
|
|
11148
|
+
(a, b) => b.relevance.score - a.relevance.score || a.patternId.localeCompare(b.patternId)
|
|
11149
|
+
);
|
|
11150
|
+
}
|
|
11849
11151
|
async function cmdTaskWorkflow(args) {
|
|
11850
11152
|
const { flags, positional } = parseLooseArgs(args);
|
|
11851
11153
|
const workspaceInfo = resolveWorkflowProject(flags, "task");
|
|
@@ -11863,7 +11165,7 @@ async function cmdTaskWorkflow(args) {
|
|
|
11863
11165
|
}
|
|
11864
11166
|
const route = routeInput.startsWith("/") ? routeInput : `/${routeInput}`;
|
|
11865
11167
|
const taskSummary = positional.slice(1).join(" ").trim();
|
|
11866
|
-
const essencePath =
|
|
11168
|
+
const essencePath = join30(workspaceInfo.appRoot, "decantr.essence.json");
|
|
11867
11169
|
const essence = readJsonIfPresent(essencePath);
|
|
11868
11170
|
if (!essence) {
|
|
11869
11171
|
console.error(
|
|
@@ -11872,7 +11174,7 @@ async function cmdTaskWorkflow(args) {
|
|
|
11872
11174
|
process.exitCode = 1;
|
|
11873
11175
|
return;
|
|
11874
11176
|
}
|
|
11875
|
-
if (!
|
|
11177
|
+
if (!isV48(essence)) {
|
|
11876
11178
|
console.error(error2("Task context requires Essence v4. Run `decantr migrate --to v4` first."));
|
|
11877
11179
|
process.exitCode = 1;
|
|
11878
11180
|
return;
|
|
@@ -11887,17 +11189,29 @@ async function cmdTaskWorkflow(args) {
|
|
|
11887
11189
|
}
|
|
11888
11190
|
const section = essence.blueprint.sections.find((entry) => entry.id === target.section);
|
|
11889
11191
|
const page = section?.pages.find((entry) => entry.id === target.page);
|
|
11890
|
-
const contextDir =
|
|
11891
|
-
const manifest = readJsonIfPresent(
|
|
11892
|
-
const projectJson = readJsonIfPresent(
|
|
11192
|
+
const contextDir = join30(workspaceInfo.appRoot, ".decantr", "context");
|
|
11193
|
+
const manifest = readJsonIfPresent(join30(contextDir, "pack-manifest.json"));
|
|
11194
|
+
const projectJson = readJsonIfPresent(join30(workspaceInfo.appRoot, ".decantr", "project.json"));
|
|
11893
11195
|
const pagePack = manifest?.pages?.find((entry) => entry.id === target.page);
|
|
11894
11196
|
const sectionPack = manifest?.sections?.find((entry) => entry.id === target.section);
|
|
11895
|
-
const visualManifest = readJsonIfPresent(
|
|
11197
|
+
const visualManifest = readJsonIfPresent(join30(workspaceInfo.appRoot, ".decantr", "evidence", "visual-manifest.json"));
|
|
11896
11198
|
const screenshot = visualManifest?.routes?.find((entry) => entry.route === route)?.screenshot;
|
|
11897
11199
|
const localPatternPackPath = localPatternsPath(workspaceInfo.appRoot);
|
|
11898
11200
|
const localRuleManifestPath = localRulesPath(workspaceInfo.appRoot);
|
|
11899
11201
|
const acceptedStyleBridgePath = styleBridgePath(workspaceInfo.appRoot);
|
|
11202
|
+
const graphDir = join30(workspaceInfo.appRoot, ".decantr", "graph");
|
|
11203
|
+
const contractCapsulePath = join30(graphDir, "contract-capsule.json");
|
|
11204
|
+
const graphSnapshotPath = join30(graphDir, "graph.snapshot.json");
|
|
11205
|
+
const contractCapsule = readJsonIfPresent(contractCapsulePath);
|
|
11206
|
+
const graphSnapshot = readJsonIfPresent(graphSnapshotPath);
|
|
11207
|
+
const routeGraphContext = buildGraphRouteContext(graphSnapshot, route, { task: taskSummary });
|
|
11208
|
+
const routePatterns = page?.layout?.map(extractPatternName) ?? [];
|
|
11900
11209
|
const localLaw = createLocalLawTaskSummary(workspaceInfo.appRoot);
|
|
11210
|
+
const rankedBehaviorObligations = rankBehaviorObligationsForTask(
|
|
11211
|
+
localLaw.behaviorObligations,
|
|
11212
|
+
routePatterns,
|
|
11213
|
+
taskSummary
|
|
11214
|
+
);
|
|
11901
11215
|
const styleBridge = createStyleBridgeTaskSummary(workspaceInfo.appRoot);
|
|
11902
11216
|
const displayedStyleBridge = {
|
|
11903
11217
|
...styleBridge,
|
|
@@ -11906,20 +11220,35 @@ async function cmdTaskWorkflow(args) {
|
|
|
11906
11220
|
const displayedLocalLaw = {
|
|
11907
11221
|
...localLaw,
|
|
11908
11222
|
patternsPath: localLaw.patternsPath ? displayProjectPath(workspaceInfo, localLaw.patternsPath) : null,
|
|
11909
|
-
rulesPath: localLaw.rulesPath ? displayProjectPath(workspaceInfo, localLaw.rulesPath) : null
|
|
11223
|
+
rulesPath: localLaw.rulesPath ? displayProjectPath(workspaceInfo, localLaw.rulesPath) : null,
|
|
11224
|
+
behaviorObligations: rankedBehaviorObligations
|
|
11910
11225
|
};
|
|
11911
11226
|
const changedSince = flagString(flags, "since");
|
|
11912
11227
|
const currentChangedFiles = changedFiles(workspaceInfo.appRoot, changedSince);
|
|
11913
11228
|
const changedRoutes = routeImpacts(workspaceInfo.appRoot, currentChangedFiles);
|
|
11229
|
+
const changedFileSourceNodes = graphSnapshot ? currentChangedFiles.map((file) => ({
|
|
11230
|
+
file,
|
|
11231
|
+
nodeId: graphSourceNodeIdForTaskFile(workspaceInfo.appRoot, graphSnapshot, file)
|
|
11232
|
+
})) : [];
|
|
11233
|
+
const changedFileSourceNodeIds = [
|
|
11234
|
+
...new Set(
|
|
11235
|
+
changedFileSourceNodes.map((entry) => entry.nodeId).filter((nodeId) => Boolean(nodeId))
|
|
11236
|
+
)
|
|
11237
|
+
];
|
|
11238
|
+
const changedFileMissingFiles = graphSnapshot ? changedFileSourceNodes.filter((entry) => !entry.nodeId).map((entry) => entry.file) : currentChangedFiles;
|
|
11239
|
+
const changedFileGraphContext = graphSnapshot && changedFileSourceNodeIds.length > 0 ? buildGraphImpactContext(graphSnapshot, changedFileSourceNodeIds, {
|
|
11240
|
+
task: taskSummary,
|
|
11241
|
+
limit: 120
|
|
11242
|
+
}) : null;
|
|
11914
11243
|
const authority = createTaskAuthoritySummary({
|
|
11915
11244
|
projectRoot: workspaceInfo.appRoot,
|
|
11916
11245
|
workflowMode: projectJson?.initialized?.workflowMode ?? null,
|
|
11917
11246
|
adoptionMode: projectJson?.initialized?.adoptionMode ?? null,
|
|
11918
|
-
hasLocalPatterns:
|
|
11919
|
-
hasLocalRules:
|
|
11247
|
+
hasLocalPatterns: existsSync28(localPatternPackPath),
|
|
11248
|
+
hasLocalRules: existsSync28(localRuleManifestPath),
|
|
11920
11249
|
hasPackManifest: Boolean(manifest),
|
|
11921
11250
|
taskSummary,
|
|
11922
|
-
hasStyleBridge:
|
|
11251
|
+
hasStyleBridge: existsSync28(acceptedStyleBridgePath)
|
|
11923
11252
|
});
|
|
11924
11253
|
const context = {
|
|
11925
11254
|
route,
|
|
@@ -11927,17 +11256,55 @@ async function cmdTaskWorkflow(args) {
|
|
|
11927
11256
|
section: target.section,
|
|
11928
11257
|
page: target.page,
|
|
11929
11258
|
shell: page?.shell ?? section?.shell ?? null,
|
|
11930
|
-
patterns:
|
|
11259
|
+
patterns: routePatterns,
|
|
11931
11260
|
read: [
|
|
11932
|
-
pagePack ? displayProjectPath(workspaceInfo,
|
|
11933
|
-
sectionPack ? displayProjectPath(workspaceInfo,
|
|
11934
|
-
manifest?.scaffold?.markdown ? displayProjectPath(workspaceInfo,
|
|
11261
|
+
pagePack ? displayProjectPath(workspaceInfo, join30(".decantr/context", pagePack.markdown)) : null,
|
|
11262
|
+
sectionPack ? displayProjectPath(workspaceInfo, join30(".decantr/context", sectionPack.markdown)) : null,
|
|
11263
|
+
manifest?.scaffold?.markdown ? displayProjectPath(workspaceInfo, join30(".decantr/context", manifest.scaffold.markdown)) : null,
|
|
11935
11264
|
displayProjectPath(workspaceInfo, ".decantr/context/scaffold.md"),
|
|
11936
11265
|
displayProjectPath(workspaceInfo, "DECANTR.md"),
|
|
11937
|
-
|
|
11938
|
-
|
|
11939
|
-
|
|
11266
|
+
existsSync28(localPatternPackPath) ? displayProjectPath(workspaceInfo, ".decantr/local-patterns.json") : null,
|
|
11267
|
+
existsSync28(localRuleManifestPath) ? displayProjectPath(workspaceInfo, ".decantr/rules.json") : null,
|
|
11268
|
+
existsSync28(acceptedStyleBridgePath) ? displayProjectPath(workspaceInfo, ".decantr/style-bridge.json") : null,
|
|
11269
|
+
contractCapsule ? displayProjectPath(workspaceInfo, ".decantr/graph/contract-capsule.json") : null,
|
|
11270
|
+
routeGraphContext && !contractCapsule ? displayProjectPath(workspaceInfo, ".decantr/graph/graph.snapshot.json") : null
|
|
11940
11271
|
].filter(Boolean),
|
|
11272
|
+
graph: contractCapsule || graphSnapshot ? {
|
|
11273
|
+
capsule: contractCapsule ? {
|
|
11274
|
+
path: displayProjectPath(workspaceInfo, ".decantr/graph/contract-capsule.json"),
|
|
11275
|
+
snapshotId: contractCapsule.snapshot_id ?? null,
|
|
11276
|
+
cacheKey: contractCapsule.cache_key ?? null,
|
|
11277
|
+
contractHash: contractCapsule.contract_hash ?? null,
|
|
11278
|
+
contractCacheKey: contractCapsule.contract_cache_key ?? null,
|
|
11279
|
+
summary: contractCapsule.summary ?? null
|
|
11280
|
+
} : null,
|
|
11281
|
+
snapshot: graphSnapshot ? {
|
|
11282
|
+
path: displayProjectPath(workspaceInfo, ".decantr/graph/graph.snapshot.json"),
|
|
11283
|
+
id: graphSnapshot.id ?? null,
|
|
11284
|
+
sourceHash: graphSnapshot.source_hash ?? null,
|
|
11285
|
+
summary: graphSnapshot.summary ?? null
|
|
11286
|
+
} : null,
|
|
11287
|
+
routeContext: routeGraphContext ? {
|
|
11288
|
+
path: displayProjectPath(workspaceInfo, ".decantr/graph/graph.snapshot.json"),
|
|
11289
|
+
...routeGraphContext
|
|
11290
|
+
} : null,
|
|
11291
|
+
changedFileContext: currentChangedFiles.length > 0 ? {
|
|
11292
|
+
path: displayProjectPath(workspaceInfo, ".decantr/graph/graph.snapshot.json"),
|
|
11293
|
+
changedFiles: currentChangedFiles.slice(0, 40),
|
|
11294
|
+
resolvedNodeIds: changedFileSourceNodeIds,
|
|
11295
|
+
missingFiles: changedFileMissingFiles.slice(0, 40),
|
|
11296
|
+
impact: changedFileGraphContext ? {
|
|
11297
|
+
snapshotId: changedFileGraphContext.snapshotId,
|
|
11298
|
+
sourceHash: changedFileGraphContext.sourceHash,
|
|
11299
|
+
ranking: changedFileGraphContext.ranking,
|
|
11300
|
+
summary: changedFileGraphContext.summary,
|
|
11301
|
+
ids: changedFileGraphContext.ids,
|
|
11302
|
+
ranked: changedFileGraphContext.ranked.slice(0, 24),
|
|
11303
|
+
nodes: changedFileGraphContext.nodes,
|
|
11304
|
+
edges: changedFileGraphContext.edges
|
|
11305
|
+
} : null
|
|
11306
|
+
} : null
|
|
11307
|
+
} : null,
|
|
11941
11308
|
screenshot: screenshot?.startsWith(".decantr/") ? displayProjectPath(workspaceInfo, screenshot) : screenshot ?? null,
|
|
11942
11309
|
authority,
|
|
11943
11310
|
localLaw: displayedLocalLaw,
|
|
@@ -11966,6 +11333,55 @@ async function cmdTaskWorkflow(args) {
|
|
|
11966
11333
|
console.log(`${BOLD9}Visual evidence:${RESET16}`);
|
|
11967
11334
|
console.log(` ${cyan3(context.screenshot)}`);
|
|
11968
11335
|
}
|
|
11336
|
+
if (context.graph?.capsule || context.graph?.snapshot) {
|
|
11337
|
+
console.log("");
|
|
11338
|
+
console.log(`${BOLD9}Typed contract graph:${RESET16}`);
|
|
11339
|
+
if (context.graph.capsule) {
|
|
11340
|
+
console.log(` Capsule: ${cyan3(context.graph.capsule.path)}`);
|
|
11341
|
+
if (context.graph.capsule.cacheKey) {
|
|
11342
|
+
console.log(` Cache key: ${context.graph.capsule.cacheKey}`);
|
|
11343
|
+
}
|
|
11344
|
+
if (context.graph.capsule.contractCacheKey && context.graph.capsule.contractCacheKey !== context.graph.capsule.cacheKey) {
|
|
11345
|
+
console.log(` Contract cache key: ${context.graph.capsule.contractCacheKey}`);
|
|
11346
|
+
}
|
|
11347
|
+
}
|
|
11348
|
+
if (context.graph.snapshot) {
|
|
11349
|
+
const summary = context.graph.snapshot.summary;
|
|
11350
|
+
const counts = summary && typeof summary.nodes === "number" && typeof summary.edges === "number" ? ` (${summary.nodes} nodes, ${summary.edges} edges)` : "";
|
|
11351
|
+
console.log(` Snapshot: ${cyan3(context.graph.snapshot.path)}${counts}`);
|
|
11352
|
+
}
|
|
11353
|
+
if (context.graph.routeContext) {
|
|
11354
|
+
const routeSummary = context.graph.routeContext.summary;
|
|
11355
|
+
const routeFindings = context.graph.routeContext.nodes.filter((node) => node.type === "Finding").map((node) => graphPayloadString(node.payload, "code") ?? node.id).slice(0, 4);
|
|
11356
|
+
const routeSources = context.graph.routeContext.nodes.filter((node) => node.type === "SourceArtifact").map((node) => graphPayloadString(node.payload, "path") ?? node.id.replace(/^src:/, "")).slice(0, 4);
|
|
11357
|
+
const routeHints = [
|
|
11358
|
+
context.graph.routeContext.ids.patterns.length > 0 ? `patterns ${context.graph.routeContext.ids.patterns.join(", ")}` : null,
|
|
11359
|
+
routeSummary.openFindings > 0 ? `${routeSummary.openFindings} finding(s)` : null,
|
|
11360
|
+
routeSummary.evidence > 0 ? `${routeSummary.evidence} evidence node(s)` : null,
|
|
11361
|
+
routeSummary.sourceArtifacts > 0 ? `${routeSummary.sourceArtifacts} source artifact(s)` : null
|
|
11362
|
+
].filter(Boolean);
|
|
11363
|
+
console.log(
|
|
11364
|
+
` Route subgraph: ${routeSummary.nodes} nodes, ${routeSummary.edges} edges${routeHints.length > 0 ? `; ${routeHints.join("; ")}` : ""}`
|
|
11365
|
+
);
|
|
11366
|
+
if (routeFindings.length > 0) {
|
|
11367
|
+
console.log(` Route findings: ${routeFindings.join(", ")}`);
|
|
11368
|
+
}
|
|
11369
|
+
if (routeSources.length > 0) {
|
|
11370
|
+
console.log(` Route sources: ${routeSources.join(", ")}`);
|
|
11371
|
+
}
|
|
11372
|
+
}
|
|
11373
|
+
if (context.graph.changedFileContext?.impact) {
|
|
11374
|
+
const impact = context.graph.changedFileContext.impact;
|
|
11375
|
+
const impactHints = [
|
|
11376
|
+
impact.ids.routes.length > 0 ? `routes ${impact.ids.routes.join(", ")}` : null,
|
|
11377
|
+
impact.summary.pages > 0 ? `${impact.summary.pages} page(s)` : null,
|
|
11378
|
+
impact.summary.openFindings > 0 ? `${impact.summary.openFindings} finding(s)` : null
|
|
11379
|
+
].filter(Boolean);
|
|
11380
|
+
console.log(
|
|
11381
|
+
` Changed-file impact: ${impact.summary.nodes} nodes, ${impact.summary.edges} edges${impactHints.length > 0 ? `; ${impactHints.join("; ")}` : ""}`
|
|
11382
|
+
);
|
|
11383
|
+
}
|
|
11384
|
+
}
|
|
11969
11385
|
console.log("");
|
|
11970
11386
|
console.log(`${BOLD9}Authority for this task:${RESET16}`);
|
|
11971
11387
|
console.log(` Lane: ${context.authority.lane}`);
|
|
@@ -11998,6 +11414,23 @@ async function cmdTaskWorkflow(args) {
|
|
|
11998
11414
|
].filter(Boolean).join(" | ");
|
|
11999
11415
|
console.log(` ${pattern.id}: ${authorityHint}${pathHint}`);
|
|
12000
11416
|
}
|
|
11417
|
+
if (context.localLaw.behaviorObligations.length > 0) {
|
|
11418
|
+
console.log("");
|
|
11419
|
+
console.log(`${BOLD9}Behavior obligations:${RESET16}`);
|
|
11420
|
+
for (const behavior of context.localLaw.behaviorObligations.slice(0, 3)) {
|
|
11421
|
+
const obligations = behavior.obligations.slice(0, 3).map((obligation) => obligation.label).join("; ");
|
|
11422
|
+
const reasons = behavior.relevance.reasons.slice(0, 3).join(", ");
|
|
11423
|
+
console.log(
|
|
11424
|
+
` ${behavior.patternId}: ${behavior.intent ?? behavior.patternRole ?? "interaction law"} (${reasons})`
|
|
11425
|
+
);
|
|
11426
|
+
console.log(` ${obligations}`);
|
|
11427
|
+
}
|
|
11428
|
+
if (context.localLaw.behaviorObligations.length > 3) {
|
|
11429
|
+
console.log(
|
|
11430
|
+
dim3(` ...${context.localLaw.behaviorObligations.length - 3} more behavior pattern(s)`)
|
|
11431
|
+
);
|
|
11432
|
+
}
|
|
11433
|
+
}
|
|
12001
11434
|
} else {
|
|
12002
11435
|
console.log("");
|
|
12003
11436
|
console.log(`${BOLD9}Project-owned local law:${RESET16}`);
|
|
@@ -12038,7 +11471,7 @@ async function cmdTaskWorkflow(args) {
|
|
|
12038
11471
|
console.log("");
|
|
12039
11472
|
console.log(`${BOLD9}LLM instruction:${RESET16}`);
|
|
12040
11473
|
console.log(
|
|
12041
|
-
" Preserve the active authority above. Use the route pack, section context, local laws, changed-file impact, and visual evidence as the task contract before changing code."
|
|
11474
|
+
" Preserve the active authority above. Use the route pack, section context, typed route graph, local laws, changed-file impact, and visual evidence as the task contract before changing code."
|
|
12042
11475
|
);
|
|
12043
11476
|
console.log(` After editing, run ${cyan3(context.verifyCommand)}.`);
|
|
12044
11477
|
}
|
|
@@ -12064,7 +11497,7 @@ async function cmdCodifyWorkflow(args) {
|
|
|
12064
11497
|
const workspaceInfo = resolveWorkflowProject(flags, "codify");
|
|
12065
11498
|
if (!workspaceInfo) return;
|
|
12066
11499
|
if (flagBoolean(flags, "accept")) {
|
|
12067
|
-
if (!
|
|
11500
|
+
if (!existsSync28(localPatternsProposalPath(workspaceInfo.appRoot)) && !existsSync28(localRulesProposalPath(workspaceInfo.appRoot)) && !existsSync28(styleBridgeProposalPath(workspaceInfo.appRoot))) {
|
|
12068
11501
|
console.error(
|
|
12069
11502
|
error2(
|
|
12070
11503
|
"No codify proposal found. Run `decantr codify --from-audit`, `decantr codify --style-bridge`, or `decantr codify` first."
|
|
@@ -12098,14 +11531,14 @@ async function cmdCodifyWorkflow(args) {
|
|
|
12098
11531
|
}
|
|
12099
11532
|
const detected = detectProject(workspaceInfo.appRoot);
|
|
12100
11533
|
const essence = readJsonIfPresent(
|
|
12101
|
-
|
|
11534
|
+
join30(workspaceInfo.appRoot, "decantr.essence.json")
|
|
12102
11535
|
);
|
|
12103
11536
|
const fromAudit = flagBoolean(flags, "from-audit") || flagBoolean(flags, "discover-local-patterns") || flagBoolean(flags, "codify-local-patterns");
|
|
12104
11537
|
const wantsStyleBridge = flagBoolean(flags, "style-bridge");
|
|
12105
11538
|
const mapPatternSlug = flagString(flags, "map-pattern") ?? flagString(flags, "hosted-pattern");
|
|
12106
11539
|
if (mapPatternSlug) {
|
|
12107
11540
|
const registryClient = new RegistryClient({
|
|
12108
|
-
cacheDir:
|
|
11541
|
+
cacheDir: join30(workspaceInfo.appRoot, ".decantr", "cache")
|
|
12109
11542
|
});
|
|
12110
11543
|
const candidates = await loadPatternDiscoveryCandidates(registryClient);
|
|
12111
11544
|
const candidate = findPatternCandidateBySlug(candidates, mapPatternSlug);
|
|
@@ -12229,7 +11662,7 @@ async function cmdContentWorkflow(args) {
|
|
|
12229
11662
|
}
|
|
12230
11663
|
function cmdHelp() {
|
|
12231
11664
|
console.log(`
|
|
12232
|
-
${BOLD9}decantr${RESET16} \u2014
|
|
11665
|
+
${BOLD9}decantr${RESET16} \u2014 AI Frontend Governance for codebases touched by AI agents
|
|
12233
11666
|
|
|
12234
11667
|
${BOLD9}Usage:${RESET16}
|
|
12235
11668
|
decantr setup [--project <path>]
|
|
@@ -12238,6 +11671,7 @@ ${BOLD9}Usage:${RESET16}
|
|
|
12238
11671
|
decantr adopt [--project <path>] [--base-url <url>] [--evidence] [--ci] [--no-packs] [--yes]
|
|
12239
11672
|
decantr task <route> ["task summary"] [--project <path>] [--since origin/main] [--json]
|
|
12240
11673
|
decantr verify [--project <path>] [--brownfield] [--local-patterns] [health options]
|
|
11674
|
+
decantr graph [--project <path>] [--route <route>] [--node <id>] [--file <path>] [--task <text>] [--snapshot-id <id>] [--compare-to <id>] [--capsule-source-limit <count>] [--check] [--json]
|
|
12241
11675
|
decantr ci [--project <path>] [--workspace] [--fail-on error|warn|none]
|
|
12242
11676
|
decantr doctor [--project <path>] [--workspace] [--json]
|
|
12243
11677
|
decantr codify [--from-audit] [--style-bridge] [--map-pattern <slug>] [--accept] [--project <path>]
|
|
@@ -12254,6 +11688,7 @@ ${BOLD9}Advanced primitives:${RESET16}
|
|
|
12254
11688
|
decantr check
|
|
12255
11689
|
decantr check --brownfield
|
|
12256
11690
|
decantr sync-drift
|
|
11691
|
+
decantr graph [--project <path>] [--route <route>] [--node <id>] [--file <path>] [--task <text>] [--snapshot-id <id>] [--compare-to <id>] [--capsule-source-limit <count>] [--check] [--json]
|
|
12257
11692
|
decantr search <query> [--type <type>] [--sort <recommended|recent|name>] [--recommended] [--source <authored|benchmark|hybrid>]
|
|
12258
11693
|
decantr suggest <query> [--type <type>] [--route <route>] [--file <path>] [--from-code]
|
|
12259
11694
|
decantr get <type> <id>
|
|
@@ -12266,6 +11701,7 @@ ${BOLD9}Advanced primitives:${RESET16}
|
|
|
12266
11701
|
decantr registry audit-project [--namespace <namespace>] [--json] [--essence <path>] [--dist <path>] [--sources <dir>]
|
|
12267
11702
|
decantr health [--format text|json|markdown] [--ci] [--fail-on error|warn|none]
|
|
12268
11703
|
decantr health --evidence [--browser] [--base-url <url>] [--design-tokens <path>]
|
|
11704
|
+
decantr health --diagnostics [--json|--markdown]
|
|
12269
11705
|
decantr health --save-baseline | --since-baseline
|
|
12270
11706
|
decantr health init-ci [legacy alias for decantr ci init]
|
|
12271
11707
|
decantr ci init [--project <path>] [--workspace] [--provider github|generic] [--force]
|
|
@@ -12313,11 +11749,12 @@ ${BOLD9}Commands:${RESET16}
|
|
|
12313
11749
|
${cyan3("scan")} Read-only Brownfield reconnaissance; no files written
|
|
12314
11750
|
${cyan3("new")} Create a new greenfield workspace and bootstrap the available starter adapter
|
|
12315
11751
|
${cyan3("adopt")} Brownfield one-liner: analyze, attach, verify, and show next steps
|
|
12316
|
-
${cyan3("task")} Prepare route/task context, local law, evidence, and changed-file impact for an AI coding assistant
|
|
11752
|
+
${cyan3("task")} Prepare route/task context, local law, behavior obligations, evidence, and changed-file impact for an AI coding assistant
|
|
12317
11753
|
${cyan3("verify")} One reliability gate over Project Health, Brownfield checks, baselines, and evidence
|
|
11754
|
+
${cyan3("graph")} Build typed Contract graph artifacts and the agent cache capsule
|
|
12318
11755
|
${cyan3("ci")} Non-mutating CI gate and CI integration generator
|
|
12319
11756
|
${cyan3("doctor")} Explain Decantr state, artifact ownership, and the next command
|
|
12320
|
-
${cyan3("codify")} Propose or accept project-owned Brownfield UI patterns and rules
|
|
11757
|
+
${cyan3("codify")} Propose or accept project-owned Brownfield UI patterns, behavior obligations, and rules
|
|
12321
11758
|
${cyan3("studio")} Open a local Project Health dashboard backed by the same report
|
|
12322
11759
|
${cyan3("content")} Content-author namespace: check, create, publish
|
|
12323
11760
|
|
|
@@ -12327,21 +11764,22 @@ ${BOLD9}Advanced commands:${RESET16}
|
|
|
12327
11764
|
${cyan3("status")} Show project status, DNA axioms, and blueprint info
|
|
12328
11765
|
${cyan3("health")} Advanced Project Health primitive [--json] [--markdown] [--ci]; use decantr ci for automation
|
|
12329
11766
|
${cyan3("workspace")} Discover and aggregate health across Decantr projects in a monorepo
|
|
12330
|
-
${cyan3("content-health")} Generate a local
|
|
12331
|
-
${cyan3("sync")} Sync
|
|
11767
|
+
${cyan3("content-health")} Generate a local official-vocabulary health report [--json] [--markdown] [--ci]
|
|
11768
|
+
${cyan3("sync")} Sync official vocabulary from API
|
|
12332
11769
|
${cyan3("audit")} Audit the project or critique a specific file against compiled packs
|
|
12333
11770
|
${cyan3("migrate")} Migrate older essence files to v4 format (with .pre-v4.backup.json backup)
|
|
12334
11771
|
${cyan3("check")} Detect drift issues (validate + guard rules) [--telemetry] [--brownfield]
|
|
12335
11772
|
${cyan3("sync-drift")} Review and resolve drift log entries
|
|
12336
|
-
${cyan3("
|
|
11773
|
+
${cyan3("graph")} Generate .decantr/graph snapshot, history, manifest, diff, and contract capsule
|
|
11774
|
+
${cyan3("search")} Search official/community vocabulary
|
|
12337
11775
|
${cyan3("suggest")} Suggest patterns or alternatives for a query
|
|
12338
|
-
${cyan3("get")} Get full details of a
|
|
11776
|
+
${cyan3("get")} Get full details of a vocabulary item
|
|
12339
11777
|
${cyan3("list")} List items by type
|
|
12340
11778
|
${cyan3("showcase")} Inspect audited showcase benchmark metadata
|
|
12341
11779
|
${cyan3("validate")} Validate an Essence v4 file
|
|
12342
11780
|
${cyan3("theme")} Manage custom themes (create, list, validate, delete, import)
|
|
12343
11781
|
${cyan3("create")} Create a custom content item (pattern, theme, blueprint, etc.)
|
|
12344
|
-
${cyan3("publish")} Publish a custom
|
|
11782
|
+
${cyan3("publish")} Publish a custom vocabulary item to the community content service
|
|
12345
11783
|
${cyan3("login")} Authenticate with the Decantr registry
|
|
12346
11784
|
${cyan3("logout")} Remove stored credentials
|
|
12347
11785
|
${cyan3("analyze")} Brownfield entrypoint: scan an existing project and emit attach guidance
|
|
@@ -12361,6 +11799,11 @@ ${BOLD9}Examples:${RESET16}
|
|
|
12361
11799
|
decantr adopt --project apps/web --yes
|
|
12362
11800
|
decantr task /feed "add saved recipe actions"
|
|
12363
11801
|
decantr verify --brownfield --local-patterns
|
|
11802
|
+
decantr graph --project apps/web
|
|
11803
|
+
decantr graph --project apps/web --route /feed --task "improve loading" --json
|
|
11804
|
+
decantr graph --project apps/web --file src/app/page.tsx --impact --json
|
|
11805
|
+
decantr graph --project apps/web --compare-to graph:previous --include-diff-ops --json
|
|
11806
|
+
decantr graph --check --json
|
|
12364
11807
|
decantr verify --base-url http://localhost:3000 --evidence
|
|
12365
11808
|
decantr verify --since-baseline
|
|
12366
11809
|
decantr doctor --project apps/web
|
|
@@ -12418,6 +11861,7 @@ ${BOLD9}Workflow Model:${RESET16}
|
|
|
12418
11861
|
${cyan3("Brownfield preview")} decantr scan -> decantr adopt --yes
|
|
12419
11862
|
${cyan3("Brownfield monorepo")} decantr adopt --project apps/web --yes
|
|
12420
11863
|
${cyan3("Daily LLM work")} decantr task <route> "<change>" -> decantr verify --brownfield --local-patterns
|
|
11864
|
+
${cyan3("Typed contract graph")} decantr graph -> agent session loads .decantr/graph/contract-capsule.json
|
|
12421
11865
|
${cyan3("Project-owned law")} decantr codify --from-audit -> edit proposal -> decantr codify --accept
|
|
12422
11866
|
${cyan3("Hybrid composition")} decantr add/remove, decantr theme switch, decantr registry, decantr upgrade
|
|
12423
11867
|
|
|
@@ -12480,6 +11924,7 @@ ${BOLD9}Usage:${RESET16}
|
|
|
12480
11924
|
decantr health --ci [--fail-on error|warn|none]
|
|
12481
11925
|
decantr health --prompt <finding-id>
|
|
12482
11926
|
decantr health --evidence [--browser] [--design-tokens <path>]
|
|
11927
|
+
decantr health --diagnostics [--json|--markdown]
|
|
12483
11928
|
decantr health --browser --base-url <url> --evidence
|
|
12484
11929
|
decantr health --save-baseline
|
|
12485
11930
|
decantr health --since-baseline
|
|
@@ -12494,6 +11939,7 @@ ${BOLD9}Options:${RESET16}
|
|
|
12494
11939
|
--fail-on CI threshold: error, warn, or none
|
|
12495
11940
|
--prompt Print an AI-ready remediation prompt for a finding
|
|
12496
11941
|
--evidence Emit a local Evidence Bundle JSON artifact
|
|
11942
|
+
--diagnostics Print the stable diagnostic code and repair ID catalog
|
|
12497
11943
|
--browser Include optional rendered-browser setup/evidence checks
|
|
12498
11944
|
--base-url Base URL for rendered route checks when --browser is enabled
|
|
12499
11945
|
--save-baseline Save the current health state for later comparison
|
|
@@ -12506,6 +11952,7 @@ ${BOLD9}Examples:${RESET16}
|
|
|
12506
11952
|
decantr health --markdown --output decantr-health.md
|
|
12507
11953
|
decantr ci --project apps/web
|
|
12508
11954
|
decantr health --prompt audit-essence-missing
|
|
11955
|
+
decantr health --diagnostics --markdown
|
|
12509
11956
|
decantr health --evidence --output .decantr/evidence/latest.json
|
|
12510
11957
|
decantr ci init --project apps/web
|
|
12511
11958
|
decantr ci init --workspace
|
|
@@ -12530,7 +11977,7 @@ ${BOLD9}Examples:${RESET16}
|
|
|
12530
11977
|
}
|
|
12531
11978
|
function cmdContentHealthHelp() {
|
|
12532
11979
|
console.log(`
|
|
12533
|
-
${BOLD9}decantr content-health${RESET16} \u2014 Generate a local
|
|
11980
|
+
${BOLD9}decantr content-health${RESET16} \u2014 Generate a local official-vocabulary health report
|
|
12534
11981
|
|
|
12535
11982
|
${BOLD9}Usage:${RESET16}
|
|
12536
11983
|
decantr content-health [--format text|json|markdown] [--output <file>]
|
|
@@ -12679,6 +12126,11 @@ ${BOLD9}decantr task${RESET16} \u2014 Prepare compact route/task context for an
|
|
|
12679
12126
|
${BOLD9}Usage:${RESET16}
|
|
12680
12127
|
decantr task <route> ["task summary"] [--project <path>] [--since origin/main] [--json]
|
|
12681
12128
|
|
|
12129
|
+
${BOLD9}Behavior:${RESET16}
|
|
12130
|
+
Includes accepted local law and behavior obligations, plus the typed contract capsule path when
|
|
12131
|
+
.decantr/graph exists. Run decantr graph first when you want graph-backed agent context in
|
|
12132
|
+
CLI-only workflows.
|
|
12133
|
+
|
|
12682
12134
|
${BOLD9}Examples:${RESET16}
|
|
12683
12135
|
decantr task /feed "add saved recipe actions"
|
|
12684
12136
|
decantr task /feed "add saved recipe actions" --since origin/main
|
|
@@ -12687,7 +12139,7 @@ ${BOLD9}Examples:${RESET16}
|
|
|
12687
12139
|
}
|
|
12688
12140
|
function cmdCodifyHelp() {
|
|
12689
12141
|
console.log(`
|
|
12690
|
-
${BOLD9}decantr codify${RESET16} \u2014 Propose or accept project-owned Brownfield UI law and style bridges
|
|
12142
|
+
${BOLD9}decantr codify${RESET16} \u2014 Propose or accept project-owned Brownfield UI law, behavior obligations, and style bridges
|
|
12691
12143
|
|
|
12692
12144
|
${BOLD9}Usage:${RESET16}
|
|
12693
12145
|
decantr codify [--from-audit] [--style-bridge] [--project <path>]
|
|
@@ -12706,7 +12158,7 @@ ${BOLD9}Examples:${RESET16}
|
|
|
12706
12158
|
}
|
|
12707
12159
|
function cmdContentHelp() {
|
|
12708
12160
|
console.log(`
|
|
12709
|
-
${BOLD9}decantr content${RESET16} \u2014 Content-author namespace for
|
|
12161
|
+
${BOLD9}decantr content${RESET16} \u2014 Content-author namespace for official-vocabulary repositories
|
|
12710
12162
|
|
|
12711
12163
|
${BOLD9}Usage:${RESET16}
|
|
12712
12164
|
decantr content check [content-health options]
|
|
@@ -12734,6 +12186,9 @@ function printCommandHelp(command, args) {
|
|
|
12734
12186
|
case "verify":
|
|
12735
12187
|
cmdVerifyHelp();
|
|
12736
12188
|
return true;
|
|
12189
|
+
case "graph":
|
|
12190
|
+
cmdGraphHelp();
|
|
12191
|
+
return true;
|
|
12737
12192
|
case "ci":
|
|
12738
12193
|
cmdCiHelp();
|
|
12739
12194
|
return true;
|
|
@@ -12785,10 +12240,10 @@ async function main() {
|
|
|
12785
12240
|
if (command === "--version" || command === "-v" || command === "version") {
|
|
12786
12241
|
try {
|
|
12787
12242
|
const here = dirname6(fileURLToPath3(import.meta.url));
|
|
12788
|
-
const candidates = [
|
|
12243
|
+
const candidates = [join30(here, "..", "package.json"), join30(here, "..", "..", "package.json")];
|
|
12789
12244
|
for (const candidate of candidates) {
|
|
12790
|
-
if (
|
|
12791
|
-
const pkg = JSON.parse(
|
|
12245
|
+
if (existsSync28(candidate)) {
|
|
12246
|
+
const pkg = JSON.parse(readFileSync21(candidate, "utf-8"));
|
|
12792
12247
|
if (pkg.version) {
|
|
12793
12248
|
console.log(pkg.version);
|
|
12794
12249
|
return;
|
|
@@ -12827,6 +12282,51 @@ async function main() {
|
|
|
12827
12282
|
await cmdVerifyWorkflow(args);
|
|
12828
12283
|
break;
|
|
12829
12284
|
}
|
|
12285
|
+
case "graph": {
|
|
12286
|
+
const { flags } = parseLooseArgs(args);
|
|
12287
|
+
if (!ensureAllowedFlags(
|
|
12288
|
+
flags,
|
|
12289
|
+
[
|
|
12290
|
+
"project",
|
|
12291
|
+
"route",
|
|
12292
|
+
"node",
|
|
12293
|
+
"file",
|
|
12294
|
+
"task",
|
|
12295
|
+
"snapshot-id",
|
|
12296
|
+
"compare-to",
|
|
12297
|
+
"include-diff-ops",
|
|
12298
|
+
"impact",
|
|
12299
|
+
"limit",
|
|
12300
|
+
"capsule-source-limit",
|
|
12301
|
+
"check",
|
|
12302
|
+
"json"
|
|
12303
|
+
],
|
|
12304
|
+
"graph"
|
|
12305
|
+
))
|
|
12306
|
+
break;
|
|
12307
|
+
const workspaceInfo = resolveWorkflowProject(flags, "graph");
|
|
12308
|
+
if (!workspaceInfo) break;
|
|
12309
|
+
const limitArg = flagString(flags, "limit");
|
|
12310
|
+
const limit = limitArg ? Number(limitArg) : void 0;
|
|
12311
|
+
const capsuleSourceLimitArg = flagString(flags, "capsule-source-limit");
|
|
12312
|
+
const capsuleSourceLimit = capsuleSourceLimitArg ? Number(capsuleSourceLimitArg) : void 0;
|
|
12313
|
+
await cmdGraph(workspaceInfo.appRoot, {
|
|
12314
|
+
check: flagBoolean(flags, "check"),
|
|
12315
|
+
json: flagBoolean(flags, "json"),
|
|
12316
|
+
route: flagString(flags, "route"),
|
|
12317
|
+
node: flagString(flags, "node"),
|
|
12318
|
+
file: flagString(flags, "file"),
|
|
12319
|
+
impact: flagBoolean(flags, "impact"),
|
|
12320
|
+
task: flagString(flags, "task"),
|
|
12321
|
+
snapshotId: flagString(flags, "snapshot-id"),
|
|
12322
|
+
compareTo: flagString(flags, "compare-to"),
|
|
12323
|
+
includeDiffOps: flagBoolean(flags, "include-diff-ops"),
|
|
12324
|
+
limit,
|
|
12325
|
+
capsuleSourceLimit,
|
|
12326
|
+
displayRoot: workspaceInfo.cwd
|
|
12327
|
+
});
|
|
12328
|
+
break;
|
|
12329
|
+
}
|
|
12830
12330
|
case "ci": {
|
|
12831
12331
|
await cmdCi(args, process.cwd());
|
|
12832
12332
|
break;
|
|
@@ -12945,7 +12445,7 @@ async function main() {
|
|
|
12945
12445
|
`${YELLOW12}Note: \`decantr heal\` is deprecated. Use \`decantr check\` instead.${RESET16}`
|
|
12946
12446
|
);
|
|
12947
12447
|
}
|
|
12948
|
-
const { cmdHeal } = await import("./heal-
|
|
12448
|
+
const { cmdHeal } = await import("./heal-HHVVTVIH.js");
|
|
12949
12449
|
const { flags } = parseLooseArgs(args);
|
|
12950
12450
|
const workspaceInfo = resolveWorkflowProject(flags, "check");
|
|
12951
12451
|
if (!workspaceInfo) break;
|
|
@@ -12970,7 +12470,7 @@ async function main() {
|
|
|
12970
12470
|
const { flags } = parseLooseArgs(args);
|
|
12971
12471
|
const workspaceInfo = resolveWorkflowProject(flags, "health");
|
|
12972
12472
|
if (!workspaceInfo) break;
|
|
12973
|
-
const { cmdHealth, parseHealthArgs } = await import("./health-
|
|
12473
|
+
const { cmdHealth, parseHealthArgs } = await import("./health-DDT4RABT.js");
|
|
12974
12474
|
await cmdHealth(workspaceInfo.appRoot, parseHealthArgs(stripProjectArgs(args)));
|
|
12975
12475
|
} catch (e) {
|
|
12976
12476
|
console.error(error2(e.message));
|
|
@@ -12998,7 +12498,7 @@ async function main() {
|
|
|
12998
12498
|
cmdStudioHelp();
|
|
12999
12499
|
break;
|
|
13000
12500
|
}
|
|
13001
|
-
const { cmdStudio, parseStudioArgs } = await import("./studio-
|
|
12501
|
+
const { cmdStudio, parseStudioArgs } = await import("./studio-WDJ3BOCU.js");
|
|
13002
12502
|
await cmdStudio(process.cwd(), parseStudioArgs(args));
|
|
13003
12503
|
} catch (e) {
|
|
13004
12504
|
console.error(error2(e.message));
|
|
@@ -13012,7 +12512,7 @@ async function main() {
|
|
|
13012
12512
|
cmdWorkspaceHelp();
|
|
13013
12513
|
break;
|
|
13014
12514
|
}
|
|
13015
|
-
const { cmdWorkspace } = await import("./workspace-
|
|
12515
|
+
const { cmdWorkspace } = await import("./workspace-3XNCMZ5L.js");
|
|
13016
12516
|
await cmdWorkspace(process.cwd(), args);
|
|
13017
12517
|
} catch (e) {
|
|
13018
12518
|
console.error(error2(e.message));
|
|
@@ -13109,7 +12609,7 @@ async function main() {
|
|
|
13109
12609
|
if (!query && (route || file || fromCode)) {
|
|
13110
12610
|
query = [
|
|
13111
12611
|
route ? `route ${route}` : null,
|
|
13112
|
-
file ? `file ${
|
|
12612
|
+
file ? `file ${basename2(file)}` : null,
|
|
13113
12613
|
fromCode ? "source code patterns" : null
|
|
13114
12614
|
].filter((entry) => Boolean(entry)).join(" ");
|
|
13115
12615
|
}
|
|
@@ -13331,7 +12831,7 @@ async function main() {
|
|
|
13331
12831
|
break;
|
|
13332
12832
|
}
|
|
13333
12833
|
if (packType === "page" && route && !id) {
|
|
13334
|
-
const resolvedPath = essencePath ? resolveUserPath(essencePath) :
|
|
12834
|
+
const resolvedPath = essencePath ? resolveUserPath(essencePath) : join30(process.cwd(), "decantr.essence.json");
|
|
13335
12835
|
id = resolvePagePackIdForRoute(resolvedPath, route);
|
|
13336
12836
|
}
|
|
13337
12837
|
await printHostedSelectedExecutionPack(
|