@decantr/cli 1.7.23 → 1.7.25
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.
|
@@ -14,15 +14,15 @@ import {
|
|
|
14
14
|
scaffoldProject,
|
|
15
15
|
syncRegistry,
|
|
16
16
|
writeExecutionPackBundleArtifacts
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-3K6HWLD5.js";
|
|
18
18
|
import {
|
|
19
19
|
buildGuardRegistryContext,
|
|
20
20
|
scanProjectInteractions
|
|
21
21
|
} from "./chunk-QRQCPD3C.js";
|
|
22
22
|
|
|
23
23
|
// src/index.ts
|
|
24
|
-
import { existsSync as
|
|
25
|
-
import { basename as basename2, dirname as
|
|
24
|
+
import { existsSync as existsSync27, mkdirSync as mkdirSync11, readdirSync as readdirSync7, readFileSync as readFileSync20, writeFileSync as writeFileSync14 } from "fs";
|
|
25
|
+
import { basename as basename2, dirname as dirname4, isAbsolute, join as join28, resolve as resolve4 } from "path";
|
|
26
26
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
27
27
|
import { evaluateGuard, isV3 as isV36, validateEssence as validateEssence2 } from "@decantr/essence-spec";
|
|
28
28
|
import {
|
|
@@ -229,13 +229,120 @@ async function cmdAddFeature(feature, args, projectRoot = process.cwd()) {
|
|
|
229
229
|
console.log(`${GREEN}Derived files refreshed.${RESET}`);
|
|
230
230
|
}
|
|
231
231
|
|
|
232
|
+
// src/assistant-bridge.ts
|
|
233
|
+
import { appendFileSync, existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
234
|
+
import { dirname, join as join3 } from "path";
|
|
235
|
+
var START = "<!-- decantr:assistant-bridge:start -->";
|
|
236
|
+
var END = "<!-- decantr:assistant-bridge:end -->";
|
|
237
|
+
function buildAssistantBridgeContent(input) {
|
|
238
|
+
const lines = [];
|
|
239
|
+
lines.push("# Decantr Assistant Bridge");
|
|
240
|
+
lines.push("");
|
|
241
|
+
lines.push(
|
|
242
|
+
"Use this bridge when an AI assistant works in a repository that already has local rules."
|
|
243
|
+
);
|
|
244
|
+
lines.push("");
|
|
245
|
+
lines.push(`- Workflow mode: ${input.workflowMode}`);
|
|
246
|
+
lines.push(`- Detected framework: ${input.detected.framework}`);
|
|
247
|
+
lines.push(`- Package manager: ${input.detected.packageManager}`);
|
|
248
|
+
lines.push(
|
|
249
|
+
`- Existing rule files: ${input.detected.existingRuleFiles.length > 0 ? input.detected.existingRuleFiles.join(", ") : "none detected"}`
|
|
250
|
+
);
|
|
251
|
+
lines.push(`- Bridge mode: ${input.assistantBridge}`);
|
|
252
|
+
lines.push("");
|
|
253
|
+
lines.push("## Suggested Rule Block");
|
|
254
|
+
lines.push("");
|
|
255
|
+
lines.push(START);
|
|
256
|
+
lines.push(
|
|
257
|
+
"Before implementing Decantr-scoped work, read `DECANTR.md`, `decantr.essence.json`, and the compiled packs in `.decantr/context/`."
|
|
258
|
+
);
|
|
259
|
+
lines.push(
|
|
260
|
+
"For brownfield adoption, preserve existing framework, routing, styling, package manager, and build conventions unless the Decantr contract explicitly requires a reviewed change."
|
|
261
|
+
);
|
|
262
|
+
lines.push(
|
|
263
|
+
"Use `.decantr/context/scaffold-pack.md` as the primary compact contract, then read matching section/page packs for the surface being changed."
|
|
264
|
+
);
|
|
265
|
+
lines.push(
|
|
266
|
+
"Do not install `@decantr/css` or rewrite styling unless the project adoption mode says `decantr-css` or the task explicitly asks for it."
|
|
267
|
+
);
|
|
268
|
+
lines.push(END);
|
|
269
|
+
lines.push("");
|
|
270
|
+
return `${lines.join("\n")}
|
|
271
|
+
`;
|
|
272
|
+
}
|
|
273
|
+
function writeAssistantBridgePreview(input) {
|
|
274
|
+
const contextDir = join3(input.projectRoot, ".decantr", "context");
|
|
275
|
+
mkdirSync2(contextDir, { recursive: true });
|
|
276
|
+
const bridgePath = join3(contextDir, "assistant-bridge.md");
|
|
277
|
+
writeFileSync3(bridgePath, buildAssistantBridgeContent(input));
|
|
278
|
+
return bridgePath;
|
|
279
|
+
}
|
|
280
|
+
function bridgeBlock() {
|
|
281
|
+
return [
|
|
282
|
+
START,
|
|
283
|
+
"Before implementing Decantr-scoped work, read `DECANTR.md`, `decantr.essence.json`, and `.decantr/context/scaffold-pack.md` first.",
|
|
284
|
+
"For brownfield adoption, preserve existing framework, routing, styling, package manager, and build conventions unless the Decantr contract explicitly requires a reviewed change.",
|
|
285
|
+
"Do not install `@decantr/css` or rewrite styling unless the project adoption mode says `decantr-css` or the task explicitly asks for it.",
|
|
286
|
+
END,
|
|
287
|
+
""
|
|
288
|
+
].join("\n");
|
|
289
|
+
}
|
|
290
|
+
function upsertMarkdownBlock(path) {
|
|
291
|
+
const block = bridgeBlock();
|
|
292
|
+
if (!existsSync3(path)) {
|
|
293
|
+
mkdirSync2(dirname(path), { recursive: true });
|
|
294
|
+
writeFileSync3(path, `# Project Rules
|
|
295
|
+
|
|
296
|
+
${block}`);
|
|
297
|
+
return true;
|
|
298
|
+
}
|
|
299
|
+
const content = readFileSync3(path, "utf-8");
|
|
300
|
+
if (content.includes(START) && content.includes(END)) return false;
|
|
301
|
+
appendFileSync(path, `
|
|
302
|
+
|
|
303
|
+
${block}`);
|
|
304
|
+
return true;
|
|
305
|
+
}
|
|
306
|
+
function writeCursorRule(projectRoot) {
|
|
307
|
+
const path = join3(projectRoot, ".cursor", "rules", "decantr.mdc");
|
|
308
|
+
const content = `---
|
|
309
|
+
description: Decantr project contract and brownfield adoption bridge
|
|
310
|
+
alwaysApply: true
|
|
311
|
+
---
|
|
312
|
+
|
|
313
|
+
${bridgeBlock()}`;
|
|
314
|
+
if (existsSync3(path) && readFileSync3(path, "utf-8") === content) return false;
|
|
315
|
+
mkdirSync2(dirname(path), { recursive: true });
|
|
316
|
+
writeFileSync3(path, content);
|
|
317
|
+
return true;
|
|
318
|
+
}
|
|
319
|
+
function applyAssistantBridge(projectRoot, detected) {
|
|
320
|
+
const updated = [];
|
|
321
|
+
const markdownTargets = ["CLAUDE.md", "AGENTS.md", "GEMINI.md", "copilot-instructions.md"];
|
|
322
|
+
for (const target of markdownTargets) {
|
|
323
|
+
if (detected.existingRuleFiles.includes(target) && upsertMarkdownBlock(join3(projectRoot, target))) {
|
|
324
|
+
updated.push(target);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
if (detected.existingRuleFiles.includes(".cursorrules")) {
|
|
328
|
+
if (upsertMarkdownBlock(join3(projectRoot, ".cursorrules"))) updated.push(".cursorrules");
|
|
329
|
+
}
|
|
330
|
+
if (detected.existingRuleFiles.includes(".cursor/rules")) {
|
|
331
|
+
if (writeCursorRule(projectRoot)) updated.push(".cursor/rules/decantr.mdc");
|
|
332
|
+
}
|
|
333
|
+
if (updated.length === 0 && detected.existingRuleFiles.length === 0) {
|
|
334
|
+
if (writeCursorRule(projectRoot)) updated.push(".cursor/rules/decantr.mdc");
|
|
335
|
+
}
|
|
336
|
+
return updated;
|
|
337
|
+
}
|
|
338
|
+
|
|
232
339
|
// src/commands/analyze.ts
|
|
233
|
-
import { existsSync as
|
|
234
|
-
import { join as
|
|
340
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync3, writeFileSync as writeFileSync4 } from "fs";
|
|
341
|
+
import { join as join12 } from "path";
|
|
235
342
|
|
|
236
343
|
// src/analyzers/components.ts
|
|
237
|
-
import { existsSync as
|
|
238
|
-
import { join as
|
|
344
|
+
import { existsSync as existsSync4, readdirSync, statSync } from "fs";
|
|
345
|
+
import { join as join4 } from "path";
|
|
239
346
|
var PAGE_EXTENSIONS = /* @__PURE__ */ new Set([".tsx", ".ts", ".jsx", ".js"]);
|
|
240
347
|
var ROOT_COMPONENT_CANDIDATES = [
|
|
241
348
|
"src/App.tsx",
|
|
@@ -257,7 +364,7 @@ function countFilesRecursive(dir, extensions) {
|
|
|
257
364
|
}
|
|
258
365
|
for (const entry of entries) {
|
|
259
366
|
if (entry.startsWith(".") || entry === "node_modules") continue;
|
|
260
|
-
const fullPath =
|
|
367
|
+
const fullPath = join4(dir, entry);
|
|
261
368
|
try {
|
|
262
369
|
const stat = statSync(fullPath);
|
|
263
370
|
if (stat.isDirectory()) {
|
|
@@ -283,7 +390,7 @@ function countPageFiles(dir) {
|
|
|
283
390
|
}
|
|
284
391
|
for (const entry of entries) {
|
|
285
392
|
if (entry.startsWith(".") || entry === "node_modules") continue;
|
|
286
|
-
const fullPath =
|
|
393
|
+
const fullPath = join4(dir, entry);
|
|
287
394
|
try {
|
|
288
395
|
const stat = statSync(fullPath);
|
|
289
396
|
if (stat.isDirectory()) {
|
|
@@ -301,27 +408,27 @@ function countPageFiles(dir) {
|
|
|
301
408
|
function scanComponents(projectRoot) {
|
|
302
409
|
let pageCount = 0;
|
|
303
410
|
const componentDirs = [];
|
|
304
|
-
const appDirs = [
|
|
411
|
+
const appDirs = [join4(projectRoot, "src", "app"), join4(projectRoot, "app")];
|
|
305
412
|
for (const dir of appDirs) {
|
|
306
|
-
if (
|
|
413
|
+
if (existsSync4(dir)) {
|
|
307
414
|
pageCount += countPageFiles(dir);
|
|
308
415
|
}
|
|
309
416
|
}
|
|
310
|
-
const pagesDirs = [
|
|
417
|
+
const pagesDirs = [join4(projectRoot, "src", "pages"), join4(projectRoot, "pages")];
|
|
311
418
|
for (const dir of pagesDirs) {
|
|
312
|
-
if (
|
|
419
|
+
if (existsSync4(dir)) {
|
|
313
420
|
pageCount += countFilesRecursive(dir, PAGE_EXTENSIONS);
|
|
314
421
|
}
|
|
315
422
|
}
|
|
316
423
|
let componentCount = 0;
|
|
317
424
|
const componentPaths = [
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
425
|
+
join4(projectRoot, "src", "components"),
|
|
426
|
+
join4(projectRoot, "components"),
|
|
427
|
+
join4(projectRoot, "src", "ui"),
|
|
428
|
+
join4(projectRoot, "ui")
|
|
322
429
|
];
|
|
323
430
|
for (const dir of componentPaths) {
|
|
324
|
-
if (
|
|
431
|
+
if (existsSync4(dir)) {
|
|
325
432
|
const count = countFilesRecursive(dir, PAGE_EXTENSIONS);
|
|
326
433
|
if (count > 0) {
|
|
327
434
|
componentCount += count;
|
|
@@ -331,7 +438,7 @@ function scanComponents(projectRoot) {
|
|
|
331
438
|
}
|
|
332
439
|
}
|
|
333
440
|
const hasRootAppComponent = ROOT_COMPONENT_CANDIDATES.some(
|
|
334
|
-
(relativePath) =>
|
|
441
|
+
(relativePath) => existsSync4(join4(projectRoot, relativePath))
|
|
335
442
|
);
|
|
336
443
|
if (pageCount === 0 && hasRootAppComponent) {
|
|
337
444
|
pageCount = 1;
|
|
@@ -350,8 +457,8 @@ function scanComponents(projectRoot) {
|
|
|
350
457
|
}
|
|
351
458
|
|
|
352
459
|
// src/analyzers/dependencies.ts
|
|
353
|
-
import { existsSync as
|
|
354
|
-
import { join as
|
|
460
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
|
|
461
|
+
import { join as join5 } from "path";
|
|
355
462
|
var CATEGORIES = {
|
|
356
463
|
ui: [
|
|
357
464
|
"react",
|
|
@@ -501,11 +608,11 @@ function scanDependencies(projectRoot) {
|
|
|
501
608
|
styling: [],
|
|
502
609
|
other: []
|
|
503
610
|
};
|
|
504
|
-
const pkgPath =
|
|
505
|
-
if (!
|
|
611
|
+
const pkgPath = join5(projectRoot, "package.json");
|
|
612
|
+
if (!existsSync5(pkgPath)) return result;
|
|
506
613
|
let pkg;
|
|
507
614
|
try {
|
|
508
|
-
pkg = JSON.parse(
|
|
615
|
+
pkg = JSON.parse(readFileSync4(pkgPath, "utf-8"));
|
|
509
616
|
} catch {
|
|
510
617
|
return result;
|
|
511
618
|
}
|
|
@@ -525,8 +632,8 @@ function scanDependencies(projectRoot) {
|
|
|
525
632
|
}
|
|
526
633
|
|
|
527
634
|
// src/analyzers/features.ts
|
|
528
|
-
import { existsSync as
|
|
529
|
-
import { join as
|
|
635
|
+
import { existsSync as existsSync6, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
|
|
636
|
+
import { join as join6 } from "path";
|
|
530
637
|
var FEATURE_PATTERNS = {
|
|
531
638
|
auth: [
|
|
532
639
|
"login",
|
|
@@ -579,7 +686,7 @@ function collectPaths(dir, baseDir, depth = 0) {
|
|
|
579
686
|
for (const entry of entries) {
|
|
580
687
|
if (entry.startsWith(".") || entry.startsWith("_") || entry === "node_modules" || entry === "api")
|
|
581
688
|
continue;
|
|
582
|
-
const fullPath =
|
|
689
|
+
const fullPath = join6(dir, entry);
|
|
583
690
|
const relPath = fullPath.slice(baseDir.length + 1);
|
|
584
691
|
paths.push(relPath);
|
|
585
692
|
try {
|
|
@@ -595,16 +702,16 @@ function scanFeatures(projectRoot) {
|
|
|
595
702
|
const detected = [];
|
|
596
703
|
const evidence = {};
|
|
597
704
|
const scanDirs = [
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
705
|
+
join6(projectRoot, "src", "app"),
|
|
706
|
+
join6(projectRoot, "app"),
|
|
707
|
+
join6(projectRoot, "src", "pages"),
|
|
708
|
+
join6(projectRoot, "pages"),
|
|
709
|
+
join6(projectRoot, "src", "components"),
|
|
710
|
+
join6(projectRoot, "components")
|
|
604
711
|
];
|
|
605
712
|
const allPaths = [];
|
|
606
713
|
for (const dir of scanDirs) {
|
|
607
|
-
if (
|
|
714
|
+
if (existsSync6(dir)) {
|
|
608
715
|
allPaths.push(...collectPaths(dir, projectRoot));
|
|
609
716
|
}
|
|
610
717
|
}
|
|
@@ -628,8 +735,8 @@ function scanFeatures(projectRoot) {
|
|
|
628
735
|
}
|
|
629
736
|
|
|
630
737
|
// src/analyzers/layout.ts
|
|
631
|
-
import { existsSync as
|
|
632
|
-
import { join as
|
|
738
|
+
import { existsSync as existsSync7, readdirSync as readdirSync3, readFileSync as readFileSync5 } from "fs";
|
|
739
|
+
import { join as join7 } from "path";
|
|
633
740
|
var SIDEBAR_PATTERNS = ["sidebar", "side-bar", "sidenav", "side-nav", "drawer", "aside"];
|
|
634
741
|
var NAV_PATTERNS = ["nav", "navbar", "header", "top-bar", "topbar", "app-bar", "appbar"];
|
|
635
742
|
var FOOTER_PATTERNS = ["footer", "bottom-bar", "bottombar"];
|
|
@@ -640,12 +747,12 @@ function containsPattern(text, patterns) {
|
|
|
640
747
|
function checkComponentDirs(projectRoot) {
|
|
641
748
|
const result = { sidebar: false, nav: false, footer: false };
|
|
642
749
|
const componentDirs = [
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
750
|
+
join7(projectRoot, "src", "components"),
|
|
751
|
+
join7(projectRoot, "components"),
|
|
752
|
+
join7(projectRoot, "src", "ui")
|
|
646
753
|
];
|
|
647
754
|
for (const dir of componentDirs) {
|
|
648
|
-
if (!
|
|
755
|
+
if (!existsSync7(dir)) continue;
|
|
649
756
|
let entries;
|
|
650
757
|
try {
|
|
651
758
|
entries = readdirSync3(dir);
|
|
@@ -664,16 +771,16 @@ function checkComponentDirs(projectRoot) {
|
|
|
664
771
|
function checkLayoutFiles(projectRoot) {
|
|
665
772
|
const result = { sidebar: false, nav: false, footer: false };
|
|
666
773
|
const layoutPaths = [
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
774
|
+
join7(projectRoot, "src", "app", "layout.tsx"),
|
|
775
|
+
join7(projectRoot, "src", "app", "layout.jsx"),
|
|
776
|
+
join7(projectRoot, "app", "layout.tsx"),
|
|
777
|
+
join7(projectRoot, "app", "layout.jsx")
|
|
671
778
|
];
|
|
672
779
|
for (const layoutPath of layoutPaths) {
|
|
673
|
-
if (!
|
|
780
|
+
if (!existsSync7(layoutPath)) continue;
|
|
674
781
|
let content;
|
|
675
782
|
try {
|
|
676
|
-
content =
|
|
783
|
+
content = readFileSync5(layoutPath, "utf-8");
|
|
677
784
|
} catch {
|
|
678
785
|
continue;
|
|
679
786
|
}
|
|
@@ -684,16 +791,16 @@ function checkLayoutFiles(projectRoot) {
|
|
|
684
791
|
const subLayoutDirs = ["dashboard", "admin", "app"];
|
|
685
792
|
for (const sub of subLayoutDirs) {
|
|
686
793
|
const paths = [
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
794
|
+
join7(projectRoot, "src", "app", sub, "layout.tsx"),
|
|
795
|
+
join7(projectRoot, "src", "app", sub, "layout.jsx"),
|
|
796
|
+
join7(projectRoot, "app", sub, "layout.tsx"),
|
|
797
|
+
join7(projectRoot, "app", sub, "layout.jsx")
|
|
691
798
|
];
|
|
692
799
|
for (const layoutPath of paths) {
|
|
693
|
-
if (!
|
|
800
|
+
if (!existsSync7(layoutPath)) continue;
|
|
694
801
|
let content;
|
|
695
802
|
try {
|
|
696
|
-
content =
|
|
803
|
+
content = readFileSync5(layoutPath, "utf-8");
|
|
697
804
|
} catch {
|
|
698
805
|
continue;
|
|
699
806
|
}
|
|
@@ -715,10 +822,10 @@ function inferShellPattern(hasSidebar, hasTopNav, hasFooter) {
|
|
|
715
822
|
return "main-only";
|
|
716
823
|
}
|
|
717
824
|
function inferShellPatternFromDecantrContract(projectRoot) {
|
|
718
|
-
const essencePath =
|
|
719
|
-
if (!
|
|
825
|
+
const essencePath = join7(projectRoot, "decantr.essence.json");
|
|
826
|
+
if (!existsSync7(essencePath)) return null;
|
|
720
827
|
try {
|
|
721
|
-
const essence = JSON.parse(
|
|
828
|
+
const essence = JSON.parse(readFileSync5(essencePath, "utf-8"));
|
|
722
829
|
const sectionShells = essence.blueprint?.sections?.map((section) => section.shell).filter((shell) => typeof shell === "string" && shell.length > 0) ?? [];
|
|
723
830
|
if (sectionShells.length === 0 && essence.blueprint?.shell) {
|
|
724
831
|
return `${essence.blueprint.shell} (contract)`;
|
|
@@ -754,8 +861,8 @@ function scanLayout(projectRoot) {
|
|
|
754
861
|
}
|
|
755
862
|
|
|
756
863
|
// src/analyzers/routes.ts
|
|
757
|
-
import { existsSync as
|
|
758
|
-
import { join as
|
|
864
|
+
import { existsSync as existsSync8, readdirSync as readdirSync4, readFileSync as readFileSync6, statSync as statSync3 } from "fs";
|
|
865
|
+
import { join as join8, relative } from "path";
|
|
759
866
|
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".next", ".git", "api", "_app", "_document"]);
|
|
760
867
|
function shouldSkipDir(name) {
|
|
761
868
|
return name.startsWith("_") || name.startsWith(".") || SKIP_DIRS.has(name);
|
|
@@ -795,13 +902,13 @@ function walkAppDir(dir, baseDir, segments) {
|
|
|
795
902
|
const pageFile = entries.find((e) => e.startsWith("page."));
|
|
796
903
|
routes.push({
|
|
797
904
|
path: routePath || "/",
|
|
798
|
-
file: relative(baseDir,
|
|
905
|
+
file: relative(baseDir, join8(dir, pageFile)),
|
|
799
906
|
hasLayout
|
|
800
907
|
});
|
|
801
908
|
}
|
|
802
909
|
for (const entry of entries) {
|
|
803
910
|
if (shouldSkipDir(entry)) continue;
|
|
804
|
-
const fullPath =
|
|
911
|
+
const fullPath = join8(dir, entry);
|
|
805
912
|
try {
|
|
806
913
|
if (!statSync3(fullPath).isDirectory()) continue;
|
|
807
914
|
} catch {
|
|
@@ -823,7 +930,7 @@ function walkPagesDir(dir, baseDir, segments) {
|
|
|
823
930
|
}
|
|
824
931
|
for (const entry of entries) {
|
|
825
932
|
if (shouldSkipDir(entry)) continue;
|
|
826
|
-
const fullPath =
|
|
933
|
+
const fullPath = join8(dir, entry);
|
|
827
934
|
try {
|
|
828
935
|
const stat = statSync3(fullPath);
|
|
829
936
|
if (stat.isDirectory()) {
|
|
@@ -859,7 +966,7 @@ function collectRouteCandidateFiles(dir, files, depth = 0) {
|
|
|
859
966
|
}
|
|
860
967
|
for (const entry of entries) {
|
|
861
968
|
if (entry.startsWith(".") || entry === "node_modules") continue;
|
|
862
|
-
const fullPath =
|
|
969
|
+
const fullPath = join8(dir, entry);
|
|
863
970
|
try {
|
|
864
971
|
const stat = statSync3(fullPath);
|
|
865
972
|
if (stat.isDirectory()) {
|
|
@@ -875,16 +982,16 @@ function collectRouteCandidateFiles(dir, files, depth = 0) {
|
|
|
875
982
|
}
|
|
876
983
|
}
|
|
877
984
|
function scanReactRouter(projectRoot) {
|
|
878
|
-
const candidateDirs = [
|
|
985
|
+
const candidateDirs = [join8(projectRoot, "src"), projectRoot];
|
|
879
986
|
const candidateFiles = [];
|
|
880
987
|
for (const dir of candidateDirs) {
|
|
881
|
-
if (
|
|
988
|
+
if (existsSync8(dir)) collectRouteCandidateFiles(dir, candidateFiles);
|
|
882
989
|
}
|
|
883
990
|
const routeMap = /* @__PURE__ */ new Map();
|
|
884
991
|
for (const absolutePath of candidateFiles) {
|
|
885
992
|
let content;
|
|
886
993
|
try {
|
|
887
|
-
content =
|
|
994
|
+
content = readFileSync6(absolutePath, "utf-8");
|
|
888
995
|
} catch {
|
|
889
996
|
continue;
|
|
890
997
|
}
|
|
@@ -914,18 +1021,18 @@ function scanReactRouter(projectRoot) {
|
|
|
914
1021
|
return [...routeMap.values()];
|
|
915
1022
|
}
|
|
916
1023
|
function scanRoutes(projectRoot) {
|
|
917
|
-
const appDirs = [
|
|
1024
|
+
const appDirs = [join8(projectRoot, "src", "app"), join8(projectRoot, "app")];
|
|
918
1025
|
for (const appDir of appDirs) {
|
|
919
|
-
if (
|
|
1026
|
+
if (existsSync8(appDir)) {
|
|
920
1027
|
const routes = walkAppDir(appDir, projectRoot, []);
|
|
921
1028
|
if (routes.length > 0) {
|
|
922
1029
|
return { strategy: "app-router", routes };
|
|
923
1030
|
}
|
|
924
1031
|
}
|
|
925
1032
|
}
|
|
926
|
-
const pagesDirs = [
|
|
1033
|
+
const pagesDirs = [join8(projectRoot, "src", "pages"), join8(projectRoot, "pages")];
|
|
927
1034
|
for (const pagesDir of pagesDirs) {
|
|
928
|
-
if (
|
|
1035
|
+
if (existsSync8(pagesDir)) {
|
|
929
1036
|
const routes = walkPagesDir(pagesDir, projectRoot, []);
|
|
930
1037
|
if (routes.length > 0) {
|
|
931
1038
|
return { strategy: "pages-router", routes };
|
|
@@ -940,8 +1047,8 @@ function scanRoutes(projectRoot) {
|
|
|
940
1047
|
}
|
|
941
1048
|
|
|
942
1049
|
// src/analyzers/styling.ts
|
|
943
|
-
import { existsSync as
|
|
944
|
-
import { join as
|
|
1050
|
+
import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
|
|
1051
|
+
import { join as join9 } from "path";
|
|
945
1052
|
var TAILWIND_CONFIGS = [
|
|
946
1053
|
"tailwind.config.js",
|
|
947
1054
|
"tailwind.config.ts",
|
|
@@ -1003,10 +1110,10 @@ function detectDarkMode(projectRoot, cssContents) {
|
|
|
1003
1110
|
"app/layout.jsx"
|
|
1004
1111
|
];
|
|
1005
1112
|
for (const rel of layoutPaths) {
|
|
1006
|
-
const fullPath =
|
|
1007
|
-
if (
|
|
1113
|
+
const fullPath = join9(projectRoot, rel);
|
|
1114
|
+
if (existsSync9(fullPath)) {
|
|
1008
1115
|
try {
|
|
1009
|
-
const layoutContent =
|
|
1116
|
+
const layoutContent = readFileSync7(fullPath, "utf-8");
|
|
1010
1117
|
if (layoutContent.includes('className="dark"') || layoutContent.includes("className='dark'") || layoutContent.includes('class="dark"')) {
|
|
1011
1118
|
return true;
|
|
1012
1119
|
}
|
|
@@ -1014,10 +1121,10 @@ function detectDarkMode(projectRoot, cssContents) {
|
|
|
1014
1121
|
}
|
|
1015
1122
|
}
|
|
1016
1123
|
}
|
|
1017
|
-
const pkgPath =
|
|
1018
|
-
if (
|
|
1124
|
+
const pkgPath = join9(projectRoot, "package.json");
|
|
1125
|
+
if (existsSync9(pkgPath)) {
|
|
1019
1126
|
try {
|
|
1020
|
-
const pkg = JSON.parse(
|
|
1127
|
+
const pkg = JSON.parse(readFileSync7(pkgPath, "utf-8"));
|
|
1021
1128
|
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
1022
1129
|
if (allDeps["next-themes"] || allDeps["theme-toggle"] || allDeps["use-dark-mode"]) {
|
|
1023
1130
|
return true;
|
|
@@ -1025,10 +1132,10 @@ function detectDarkMode(projectRoot, cssContents) {
|
|
|
1025
1132
|
} catch {
|
|
1026
1133
|
}
|
|
1027
1134
|
}
|
|
1028
|
-
const essencePath =
|
|
1029
|
-
if (
|
|
1135
|
+
const essencePath = join9(projectRoot, "decantr.essence.json");
|
|
1136
|
+
if (existsSync9(essencePath)) {
|
|
1030
1137
|
try {
|
|
1031
|
-
const essence = JSON.parse(
|
|
1138
|
+
const essence = JSON.parse(readFileSync7(essencePath, "utf-8"));
|
|
1032
1139
|
const mode = essence.dna?.theme?.mode;
|
|
1033
1140
|
if (mode === "dark" || mode === "auto") {
|
|
1034
1141
|
return true;
|
|
@@ -1042,17 +1149,17 @@ function scanStyling(projectRoot) {
|
|
|
1042
1149
|
let approach = "unknown";
|
|
1043
1150
|
let configFile;
|
|
1044
1151
|
for (const cfg of TAILWIND_CONFIGS) {
|
|
1045
|
-
if (
|
|
1152
|
+
if (existsSync9(join9(projectRoot, cfg))) {
|
|
1046
1153
|
approach = "tailwind";
|
|
1047
1154
|
configFile = cfg;
|
|
1048
1155
|
break;
|
|
1049
1156
|
}
|
|
1050
1157
|
}
|
|
1051
1158
|
if (approach === "unknown") {
|
|
1052
|
-
const pkgPath =
|
|
1053
|
-
if (
|
|
1159
|
+
const pkgPath = join9(projectRoot, "package.json");
|
|
1160
|
+
if (existsSync9(pkgPath)) {
|
|
1054
1161
|
try {
|
|
1055
|
-
const pkg = JSON.parse(
|
|
1162
|
+
const pkg = JSON.parse(readFileSync7(pkgPath, "utf-8"));
|
|
1056
1163
|
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
1057
1164
|
if (allDeps["@decantr/css"]) {
|
|
1058
1165
|
approach = "decantr-css";
|
|
@@ -1065,27 +1172,27 @@ function scanStyling(projectRoot) {
|
|
|
1065
1172
|
}
|
|
1066
1173
|
}
|
|
1067
1174
|
}
|
|
1068
|
-
const decantrStyleFiles = DECANTR_STYLE_PATHS.filter((rel) =>
|
|
1175
|
+
const decantrStyleFiles = DECANTR_STYLE_PATHS.filter((rel) => existsSync9(join9(projectRoot, rel)));
|
|
1069
1176
|
if (decantrStyleFiles.length >= 2) {
|
|
1070
1177
|
approach = "decantr-css";
|
|
1071
1178
|
configFile = decantrStyleFiles.join(" + ");
|
|
1072
1179
|
}
|
|
1073
1180
|
const cssContents = [];
|
|
1074
1181
|
for (const rel of GLOBALS_CSS_PATHS) {
|
|
1075
|
-
const fullPath =
|
|
1076
|
-
if (
|
|
1182
|
+
const fullPath = join9(projectRoot, rel);
|
|
1183
|
+
if (existsSync9(fullPath)) {
|
|
1077
1184
|
try {
|
|
1078
|
-
cssContents.push(
|
|
1185
|
+
cssContents.push(readFileSync7(fullPath, "utf-8"));
|
|
1079
1186
|
} catch {
|
|
1080
1187
|
}
|
|
1081
1188
|
}
|
|
1082
1189
|
}
|
|
1083
1190
|
for (const rel of DECANTR_STYLE_PATHS) {
|
|
1084
1191
|
if (GLOBALS_CSS_PATHS.includes(rel)) continue;
|
|
1085
|
-
const fullPath =
|
|
1086
|
-
if (
|
|
1192
|
+
const fullPath = join9(projectRoot, rel);
|
|
1193
|
+
if (existsSync9(fullPath)) {
|
|
1087
1194
|
try {
|
|
1088
|
-
cssContents.push(
|
|
1195
|
+
cssContents.push(readFileSync7(fullPath, "utf-8"));
|
|
1089
1196
|
} catch {
|
|
1090
1197
|
}
|
|
1091
1198
|
}
|
|
@@ -1101,7 +1208,7 @@ function scanStyling(projectRoot) {
|
|
|
1101
1208
|
const darkMode = detectDarkMode(projectRoot, cssContents);
|
|
1102
1209
|
if (approach === "unknown" && cssContents.length > 0) {
|
|
1103
1210
|
approach = "css";
|
|
1104
|
-
configFile = GLOBALS_CSS_PATHS.find((rel) =>
|
|
1211
|
+
configFile = GLOBALS_CSS_PATHS.find((rel) => existsSync9(join9(projectRoot, rel)));
|
|
1105
1212
|
}
|
|
1106
1213
|
return {
|
|
1107
1214
|
approach,
|
|
@@ -1113,8 +1220,8 @@ function scanStyling(projectRoot) {
|
|
|
1113
1220
|
}
|
|
1114
1221
|
|
|
1115
1222
|
// src/detect.ts
|
|
1116
|
-
import { existsSync as
|
|
1117
|
-
import { join as
|
|
1223
|
+
import { existsSync as existsSync10, readFileSync as readFileSync8 } from "fs";
|
|
1224
|
+
import { join as join10 } from "path";
|
|
1118
1225
|
var RULE_FILES = [
|
|
1119
1226
|
"CLAUDE.md",
|
|
1120
1227
|
".cursorrules",
|
|
@@ -1133,52 +1240,52 @@ function detectProject(projectRoot = process.cwd()) {
|
|
|
1133
1240
|
existingEssence: false,
|
|
1134
1241
|
projectRoot
|
|
1135
1242
|
};
|
|
1136
|
-
result.existingEssence =
|
|
1243
|
+
result.existingEssence = existsSync10(join10(projectRoot, "decantr.essence.json"));
|
|
1137
1244
|
for (const ruleFile of RULE_FILES) {
|
|
1138
|
-
if (
|
|
1245
|
+
if (existsSync10(join10(projectRoot, ruleFile))) {
|
|
1139
1246
|
result.existingRuleFiles.push(ruleFile);
|
|
1140
1247
|
}
|
|
1141
1248
|
}
|
|
1142
|
-
if (
|
|
1249
|
+
if (existsSync10(join10(projectRoot, "pnpm-lock.yaml"))) {
|
|
1143
1250
|
result.packageManager = "pnpm";
|
|
1144
|
-
} else if (
|
|
1251
|
+
} else if (existsSync10(join10(projectRoot, "yarn.lock"))) {
|
|
1145
1252
|
result.packageManager = "yarn";
|
|
1146
|
-
} else if (
|
|
1253
|
+
} else if (existsSync10(join10(projectRoot, "bun.lockb"))) {
|
|
1147
1254
|
result.packageManager = "bun";
|
|
1148
|
-
} else if (
|
|
1255
|
+
} else if (existsSync10(join10(projectRoot, "package-lock.json"))) {
|
|
1149
1256
|
result.packageManager = "npm";
|
|
1150
1257
|
}
|
|
1151
|
-
result.hasTypeScript =
|
|
1152
|
-
result.hasTailwind =
|
|
1153
|
-
if (
|
|
1258
|
+
result.hasTypeScript = existsSync10(join10(projectRoot, "tsconfig.json"));
|
|
1259
|
+
result.hasTailwind = existsSync10(join10(projectRoot, "tailwind.config.js")) || existsSync10(join10(projectRoot, "tailwind.config.ts")) || existsSync10(join10(projectRoot, "tailwind.config.mjs")) || existsSync10(join10(projectRoot, "tailwind.config.cjs"));
|
|
1260
|
+
if (existsSync10(join10(projectRoot, "next.config.js")) || existsSync10(join10(projectRoot, "next.config.ts")) || existsSync10(join10(projectRoot, "next.config.mjs"))) {
|
|
1154
1261
|
result.framework = "nextjs";
|
|
1155
1262
|
result.version = getPackageVersion(projectRoot, "next");
|
|
1156
1263
|
return result;
|
|
1157
1264
|
}
|
|
1158
|
-
if (
|
|
1265
|
+
if (existsSync10(join10(projectRoot, "nuxt.config.js")) || existsSync10(join10(projectRoot, "nuxt.config.ts"))) {
|
|
1159
1266
|
result.framework = "nuxt";
|
|
1160
1267
|
result.version = getPackageVersion(projectRoot, "nuxt");
|
|
1161
1268
|
return result;
|
|
1162
1269
|
}
|
|
1163
|
-
if (
|
|
1270
|
+
if (existsSync10(join10(projectRoot, "astro.config.mjs")) || existsSync10(join10(projectRoot, "astro.config.ts"))) {
|
|
1164
1271
|
result.framework = "astro";
|
|
1165
1272
|
result.version = getPackageVersion(projectRoot, "astro");
|
|
1166
1273
|
return result;
|
|
1167
1274
|
}
|
|
1168
|
-
if (
|
|
1275
|
+
if (existsSync10(join10(projectRoot, "svelte.config.js")) || existsSync10(join10(projectRoot, "svelte.config.ts"))) {
|
|
1169
1276
|
result.framework = "svelte";
|
|
1170
1277
|
result.version = getPackageVersion(projectRoot, "svelte");
|
|
1171
1278
|
return result;
|
|
1172
1279
|
}
|
|
1173
|
-
if (
|
|
1280
|
+
if (existsSync10(join10(projectRoot, "angular.json"))) {
|
|
1174
1281
|
result.framework = "angular";
|
|
1175
1282
|
result.version = getPackageVersion(projectRoot, "@angular/core");
|
|
1176
1283
|
return result;
|
|
1177
1284
|
}
|
|
1178
|
-
const packageJsonPath =
|
|
1179
|
-
if (
|
|
1285
|
+
const packageJsonPath = join10(projectRoot, "package.json");
|
|
1286
|
+
if (existsSync10(packageJsonPath)) {
|
|
1180
1287
|
try {
|
|
1181
|
-
const packageJson = JSON.parse(
|
|
1288
|
+
const packageJson = JSON.parse(readFileSync8(packageJsonPath, "utf-8"));
|
|
1182
1289
|
const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
|
|
1183
1290
|
if (deps.next) {
|
|
1184
1291
|
result.framework = "nextjs";
|
|
@@ -1205,18 +1312,18 @@ function detectProject(projectRoot = process.cwd()) {
|
|
|
1205
1312
|
} catch {
|
|
1206
1313
|
}
|
|
1207
1314
|
}
|
|
1208
|
-
if (result.framework === "unknown" && !
|
|
1209
|
-
if (
|
|
1315
|
+
if (result.framework === "unknown" && !existsSync10(packageJsonPath)) {
|
|
1316
|
+
if (existsSync10(join10(projectRoot, "index.html"))) {
|
|
1210
1317
|
result.framework = "html";
|
|
1211
1318
|
}
|
|
1212
1319
|
}
|
|
1213
1320
|
return result;
|
|
1214
1321
|
}
|
|
1215
1322
|
function getPackageVersion(projectRoot, packageName) {
|
|
1216
|
-
const packageJsonPath =
|
|
1217
|
-
if (!
|
|
1323
|
+
const packageJsonPath = join10(projectRoot, "package.json");
|
|
1324
|
+
if (!existsSync10(packageJsonPath)) return void 0;
|
|
1218
1325
|
try {
|
|
1219
|
-
const packageJson = JSON.parse(
|
|
1326
|
+
const packageJson = JSON.parse(readFileSync8(packageJsonPath, "utf-8"));
|
|
1220
1327
|
const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
|
|
1221
1328
|
const version = deps[packageName];
|
|
1222
1329
|
return version?.replace(/^\^|~/, "");
|
|
@@ -1249,8 +1356,8 @@ function formatDetection(detected) {
|
|
|
1249
1356
|
}
|
|
1250
1357
|
|
|
1251
1358
|
// src/workflow-model.ts
|
|
1252
|
-
import { existsSync as
|
|
1253
|
-
import { join as
|
|
1359
|
+
import { existsSync as existsSync11, readFileSync as readFileSync9 } from "fs";
|
|
1360
|
+
import { join as join11 } from "path";
|
|
1254
1361
|
function inferSuggestedShell(layout) {
|
|
1255
1362
|
if (layout.hasSidebar) return "sidebar-main";
|
|
1256
1363
|
if (layout.hasTopNav) return "top-nav-main";
|
|
@@ -1259,6 +1366,58 @@ function inferSuggestedShell(layout) {
|
|
|
1259
1366
|
function hasExistingProjectFootprint(detected) {
|
|
1260
1367
|
return detected.framework !== "unknown" || detected.packageManager !== "unknown" || detected.hasTypeScript || detected.hasTailwind || detected.existingRuleFiles.length > 0;
|
|
1261
1368
|
}
|
|
1369
|
+
function normalizeWorkflowFlag(value) {
|
|
1370
|
+
if (value === "greenfield" || value === "brownfield" || value === "hybrid") return value;
|
|
1371
|
+
return void 0;
|
|
1372
|
+
}
|
|
1373
|
+
function normalizeAdoptionMode(value) {
|
|
1374
|
+
if (value === "contract-only" || value === "style-bridge" || value === "decantr-css") {
|
|
1375
|
+
return value;
|
|
1376
|
+
}
|
|
1377
|
+
return void 0;
|
|
1378
|
+
}
|
|
1379
|
+
function normalizeAssistantBridge(value) {
|
|
1380
|
+
if (value === "none" || value === "preview" || value === "apply") return value;
|
|
1381
|
+
return void 0;
|
|
1382
|
+
}
|
|
1383
|
+
function resolveWorkflowPolicy(input) {
|
|
1384
|
+
const requestedWorkflow = normalizeWorkflowFlag(input.requestedWorkflow);
|
|
1385
|
+
const requestedAdoption = normalizeAdoptionMode(input.requestedAdoption);
|
|
1386
|
+
const requestedAssistantBridge = normalizeAssistantBridge(input.requestedAssistantBridge);
|
|
1387
|
+
const hasRegistryContent = Boolean(
|
|
1388
|
+
input.requestedBlueprint || input.requestedArchetype || input.requestedTheme
|
|
1389
|
+
);
|
|
1390
|
+
const hasAnalysisArtifacts = Boolean(input.workflowSeed);
|
|
1391
|
+
const existingFootprint = hasExistingProjectFootprint(input.detected);
|
|
1392
|
+
let workflowMode;
|
|
1393
|
+
if (requestedWorkflow === "hybrid") {
|
|
1394
|
+
workflowMode = "hybrid-compose";
|
|
1395
|
+
} else if (requestedWorkflow === "brownfield" || input.explicitExisting || input.workflowSeed) {
|
|
1396
|
+
workflowMode = "brownfield-attach";
|
|
1397
|
+
} else if (requestedWorkflow === "greenfield") {
|
|
1398
|
+
workflowMode = hasRegistryContent ? "greenfield-scaffold" : "greenfield-contract-only";
|
|
1399
|
+
} else if (input.command === "new") {
|
|
1400
|
+
workflowMode = hasRegistryContent ? "greenfield-scaffold" : "greenfield-contract-only";
|
|
1401
|
+
} else if (existingFootprint && hasRegistryContent) {
|
|
1402
|
+
workflowMode = "hybrid-compose";
|
|
1403
|
+
} else if (existingFootprint && !hasRegistryContent) {
|
|
1404
|
+
workflowMode = "brownfield-attach";
|
|
1405
|
+
} else {
|
|
1406
|
+
workflowMode = hasRegistryContent ? "greenfield-scaffold" : "greenfield-contract-only";
|
|
1407
|
+
}
|
|
1408
|
+
const adoptionMode = requestedAdoption ?? input.workflowSeed?.adoptionMode ?? (workflowMode === "brownfield-attach" ? "contract-only" : workflowMode === "hybrid-compose" ? "contract-only" : workflowMode === "greenfield-contract-only" ? "contract-only" : "decantr-css");
|
|
1409
|
+
const contentSource = hasRegistryContent ? input.offline ? "cache" : "official" : "none";
|
|
1410
|
+
const assistantBridge = requestedAssistantBridge ?? input.workflowSeed?.assistantBridge ?? (workflowMode === "brownfield-attach" && input.detected.existingRuleFiles.length > 0 ? "preview" : "none");
|
|
1411
|
+
return {
|
|
1412
|
+
workflowMode,
|
|
1413
|
+
adoptionMode,
|
|
1414
|
+
contentSource,
|
|
1415
|
+
assistantBridge,
|
|
1416
|
+
projectScope: input.projectScope ?? "single-app",
|
|
1417
|
+
hasAnalysisArtifacts,
|
|
1418
|
+
registryRequired: hasRegistryContent
|
|
1419
|
+
};
|
|
1420
|
+
}
|
|
1262
1421
|
function createBrownfieldInitSeed(detected, layout, styling) {
|
|
1263
1422
|
return {
|
|
1264
1423
|
version: 1,
|
|
@@ -1266,6 +1425,10 @@ function createBrownfieldInitSeed(detected, layout, styling) {
|
|
|
1266
1425
|
contractOnly: true,
|
|
1267
1426
|
registryOptional: true,
|
|
1268
1427
|
workflowMode: "brownfield-attach",
|
|
1428
|
+
adoptionMode: "contract-only",
|
|
1429
|
+
contentSource: "none",
|
|
1430
|
+
assistantBridge: detected.existingRuleFiles.length > 0 ? "preview" : "none",
|
|
1431
|
+
projectScope: "single-app",
|
|
1269
1432
|
target: detected.framework !== "unknown" ? detected.framework : "react",
|
|
1270
1433
|
shell: inferSuggestedShell(layout),
|
|
1271
1434
|
guard: "guided",
|
|
@@ -1281,12 +1444,12 @@ function createBrownfieldInitSeed(detected, layout, styling) {
|
|
|
1281
1444
|
};
|
|
1282
1445
|
}
|
|
1283
1446
|
function readBrownfieldInitSeed(projectRoot) {
|
|
1284
|
-
const seedPath =
|
|
1285
|
-
if (!
|
|
1447
|
+
const seedPath = join11(projectRoot, ".decantr", "init-seed.json");
|
|
1448
|
+
if (!existsSync11(seedPath)) {
|
|
1286
1449
|
return null;
|
|
1287
1450
|
}
|
|
1288
1451
|
try {
|
|
1289
|
-
const parsed = JSON.parse(
|
|
1452
|
+
const parsed = JSON.parse(readFileSync9(seedPath, "utf-8"));
|
|
1290
1453
|
if (parsed.workflow !== "brownfield-adoption") {
|
|
1291
1454
|
return null;
|
|
1292
1455
|
}
|
|
@@ -1303,7 +1466,7 @@ var RESET2 = "\x1B[0m";
|
|
|
1303
1466
|
var GREEN2 = "\x1B[32m";
|
|
1304
1467
|
var CYAN = "\x1B[36m";
|
|
1305
1468
|
var YELLOW = "\x1B[33m";
|
|
1306
|
-
function cmdAnalyze(projectRoot = process.cwd()) {
|
|
1469
|
+
function cmdAnalyze(projectRoot = process.cwd(), workspace) {
|
|
1307
1470
|
console.log(`
|
|
1308
1471
|
${BOLD}Analyzing project...${RESET2}
|
|
1309
1472
|
`);
|
|
@@ -1322,6 +1485,7 @@ ${BOLD}Analyzing project...${RESET2}
|
|
|
1322
1485
|
console.log(`${DIM2}Scanning dependencies...${RESET2}`);
|
|
1323
1486
|
const dependencies = scanDependencies(projectRoot);
|
|
1324
1487
|
const initSeed = createBrownfieldInitSeed(project, layout, styling);
|
|
1488
|
+
initSeed.projectScope = workspace?.projectScope ?? "single-app";
|
|
1325
1489
|
const analysis = {
|
|
1326
1490
|
version: 1,
|
|
1327
1491
|
analyzedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -1330,7 +1494,11 @@ ${BOLD}Analyzing project...${RESET2}
|
|
|
1330
1494
|
frameworkVersion: project.version ?? null,
|
|
1331
1495
|
packageManager: project.packageManager,
|
|
1332
1496
|
hasTypeScript: project.hasTypeScript,
|
|
1333
|
-
hasTailwind: project.hasTailwind
|
|
1497
|
+
hasTailwind: project.hasTailwind,
|
|
1498
|
+
existingRuleFiles: project.existingRuleFiles,
|
|
1499
|
+
workspaceRoot: workspace?.workspaceRoot ?? projectRoot,
|
|
1500
|
+
appRoot: workspace?.appRoot ?? projectRoot,
|
|
1501
|
+
projectScope: workspace?.projectScope ?? "single-app"
|
|
1334
1502
|
},
|
|
1335
1503
|
routes,
|
|
1336
1504
|
components,
|
|
@@ -1344,8 +1512,9 @@ ${BOLD}Analyzing project...${RESET2}
|
|
|
1344
1512
|
attach: {
|
|
1345
1513
|
entrypoint: "decantr analyze",
|
|
1346
1514
|
contractOnly: true,
|
|
1515
|
+
adoptionMode: "contract-only",
|
|
1347
1516
|
initSeedPath: ".decantr/init-seed.json",
|
|
1348
|
-
recommendedCommand: "decantr init --existing --yes"
|
|
1517
|
+
recommendedCommand: "decantr init --existing --yes --adoption=contract-only"
|
|
1349
1518
|
},
|
|
1350
1519
|
hybrid: {
|
|
1351
1520
|
ownerCommands: [
|
|
@@ -1356,16 +1525,31 @@ ${BOLD}Analyzing project...${RESET2}
|
|
|
1356
1525
|
"decantr upgrade"
|
|
1357
1526
|
]
|
|
1358
1527
|
}
|
|
1528
|
+
},
|
|
1529
|
+
retrofitPlan: {
|
|
1530
|
+
recommendedWorkflowMode: "brownfield-attach",
|
|
1531
|
+
recommendedAdoptionMode: "contract-only",
|
|
1532
|
+
assistantBridge: project.existingRuleFiles.length > 0 ? "preview existing rule files before applying" : "none detected",
|
|
1533
|
+
routeAnchors: routes.routes.map((route) => route.path),
|
|
1534
|
+
stylingAnchors: [styling.configFile].filter(Boolean),
|
|
1535
|
+
ruleFiles: project.existingRuleFiles,
|
|
1536
|
+
preserve: [
|
|
1537
|
+
"framework",
|
|
1538
|
+
"package manager",
|
|
1539
|
+
"router",
|
|
1540
|
+
"build tooling",
|
|
1541
|
+
"existing styling system"
|
|
1542
|
+
]
|
|
1359
1543
|
}
|
|
1360
1544
|
};
|
|
1361
|
-
const decantrDir =
|
|
1362
|
-
if (!
|
|
1363
|
-
|
|
1364
|
-
}
|
|
1365
|
-
const outputPath =
|
|
1366
|
-
const initSeedPath =
|
|
1367
|
-
|
|
1368
|
-
|
|
1545
|
+
const decantrDir = join12(projectRoot, ".decantr");
|
|
1546
|
+
if (!existsSync12(decantrDir)) {
|
|
1547
|
+
mkdirSync3(decantrDir, { recursive: true });
|
|
1548
|
+
}
|
|
1549
|
+
const outputPath = join12(decantrDir, "analysis.json");
|
|
1550
|
+
const initSeedPath = join12(decantrDir, "init-seed.json");
|
|
1551
|
+
writeFileSync4(outputPath, JSON.stringify(analysis, null, 2) + "\n", "utf-8");
|
|
1552
|
+
writeFileSync4(initSeedPath, JSON.stringify(initSeed, null, 2) + "\n", "utf-8");
|
|
1369
1553
|
console.log(`
|
|
1370
1554
|
${GREEN2}Analysis complete.${RESET2}
|
|
1371
1555
|
`);
|
|
@@ -1399,14 +1583,14 @@ ${DIM2}Written to:${RESET2} ${outputPath}`);
|
|
|
1399
1583
|
console.log(`${DIM2}Init seed:${RESET2} ${initSeedPath}`);
|
|
1400
1584
|
console.log(
|
|
1401
1585
|
`
|
|
1402
|
-
${YELLOW}Next step:${RESET2} Run ${BOLD}decantr init --existing --yes${RESET2} to attach Decantr using the generated brownfield seed.
|
|
1586
|
+
${YELLOW}Next step:${RESET2} Run ${BOLD}decantr init --existing --yes --adoption=contract-only${RESET2} to attach Decantr using the generated brownfield seed.
|
|
1403
1587
|
`
|
|
1404
1588
|
);
|
|
1405
1589
|
}
|
|
1406
1590
|
|
|
1407
1591
|
// src/commands/create.ts
|
|
1408
|
-
import { existsSync as
|
|
1409
|
-
import { join as
|
|
1592
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync4, writeFileSync as writeFileSync5 } from "fs";
|
|
1593
|
+
import { join as join13 } from "path";
|
|
1410
1594
|
import {
|
|
1411
1595
|
CONTENT_TYPE_TO_API_CONTENT_TYPE,
|
|
1412
1596
|
CONTENT_TYPES
|
|
@@ -1602,23 +1786,23 @@ function cmdCreate(type, name, projectRoot = process.cwd()) {
|
|
|
1602
1786
|
}
|
|
1603
1787
|
const contentType = type;
|
|
1604
1788
|
const plural = PLURAL[contentType];
|
|
1605
|
-
const customDir =
|
|
1606
|
-
const filePath =
|
|
1607
|
-
if (
|
|
1789
|
+
const customDir = join13(projectRoot, ".decantr", "custom", plural);
|
|
1790
|
+
const filePath = join13(customDir, `${name}.json`);
|
|
1791
|
+
if (existsSync13(filePath)) {
|
|
1608
1792
|
console.error(`${type} "${name}" already exists at ${filePath}`);
|
|
1609
1793
|
process.exitCode = 1;
|
|
1610
1794
|
return;
|
|
1611
1795
|
}
|
|
1612
|
-
|
|
1796
|
+
mkdirSync4(customDir, { recursive: true });
|
|
1613
1797
|
const skeleton = getSkeleton(contentType, name, humanizeId(name));
|
|
1614
|
-
|
|
1798
|
+
writeFileSync5(filePath, JSON.stringify(skeleton, null, 2));
|
|
1615
1799
|
console.log(`Created ${type} "${name}" at ${filePath}`);
|
|
1616
1800
|
console.log(`Edit it, then publish with: decantr publish ${type} ${name}`);
|
|
1617
1801
|
}
|
|
1618
1802
|
|
|
1619
1803
|
// src/commands/export.ts
|
|
1620
|
-
import { existsSync as
|
|
1621
|
-
import { dirname, join as
|
|
1804
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync5, readFileSync as readFileSync10, writeFileSync as writeFileSync6 } from "fs";
|
|
1805
|
+
import { dirname as dirname2, join as join14 } from "path";
|
|
1622
1806
|
var GREEN3 = "\x1B[32m";
|
|
1623
1807
|
var RED2 = "\x1B[31m";
|
|
1624
1808
|
var DIM3 = "\x1B[2m";
|
|
@@ -1768,21 +1952,21 @@ function generateCSSVars(tokens) {
|
|
|
1768
1952
|
return lines.join("\n");
|
|
1769
1953
|
}
|
|
1770
1954
|
async function cmdExport(target, projectRoot, options = {}) {
|
|
1771
|
-
const essencePath =
|
|
1772
|
-
const tokensPath =
|
|
1773
|
-
if (!
|
|
1955
|
+
const essencePath = join14(projectRoot, "decantr.essence.json");
|
|
1956
|
+
const tokensPath = join14(projectRoot, "src", "styles", "tokens.css");
|
|
1957
|
+
if (!existsSync14(essencePath)) {
|
|
1774
1958
|
console.error(`${RED2}No decantr.essence.json found. Run \`decantr init\` first.${RESET3}`);
|
|
1775
1959
|
process.exitCode = 1;
|
|
1776
1960
|
return;
|
|
1777
1961
|
}
|
|
1778
|
-
if (!
|
|
1962
|
+
if (!existsSync14(tokensPath)) {
|
|
1779
1963
|
console.error(
|
|
1780
1964
|
`${RED2}No src/styles/tokens.css found. Run \`decantr refresh\` to generate tokens.${RESET3}`
|
|
1781
1965
|
);
|
|
1782
1966
|
process.exitCode = 1;
|
|
1783
1967
|
return;
|
|
1784
1968
|
}
|
|
1785
|
-
const tokensCSS =
|
|
1969
|
+
const tokensCSS = readFileSync10(tokensPath, "utf-8");
|
|
1786
1970
|
const tokens = parseTokensCSS(tokensCSS);
|
|
1787
1971
|
if (tokens.size === 0) {
|
|
1788
1972
|
console.error(`${RED2}No --d-* tokens found in tokens.css.${RESET3}`);
|
|
@@ -1791,28 +1975,28 @@ async function cmdExport(target, projectRoot, options = {}) {
|
|
|
1791
1975
|
}
|
|
1792
1976
|
switch (target) {
|
|
1793
1977
|
case "shadcn": {
|
|
1794
|
-
const cssOut = options.output ??
|
|
1795
|
-
const jsonOut =
|
|
1978
|
+
const cssOut = options.output ?? join14(projectRoot, "src", "styles", "shadcn-theme.css");
|
|
1979
|
+
const jsonOut = join14(projectRoot, "components.json");
|
|
1796
1980
|
ensureDir(cssOut);
|
|
1797
|
-
|
|
1798
|
-
|
|
1981
|
+
writeFileSync6(cssOut, generateShadcnCSS(tokens), "utf-8");
|
|
1982
|
+
writeFileSync6(jsonOut, generateShadcnComponentsJSON(), "utf-8");
|
|
1799
1983
|
console.log(`${GREEN3}Exported shadcn theme:${RESET3}`);
|
|
1800
1984
|
console.log(` ${DIM3}CSS:${RESET3} ${cssOut}`);
|
|
1801
1985
|
console.log(` ${DIM3}JSON:${RESET3} ${jsonOut}`);
|
|
1802
1986
|
break;
|
|
1803
1987
|
}
|
|
1804
1988
|
case "tailwind": {
|
|
1805
|
-
const out = options.output ??
|
|
1989
|
+
const out = options.output ?? join14(projectRoot, "tailwind.decantr.config.ts");
|
|
1806
1990
|
ensureDir(out);
|
|
1807
|
-
|
|
1991
|
+
writeFileSync6(out, generateTailwindConfig(tokens), "utf-8");
|
|
1808
1992
|
console.log(`${GREEN3}Exported Tailwind config:${RESET3}`);
|
|
1809
1993
|
console.log(` ${DIM3}File:${RESET3} ${out}`);
|
|
1810
1994
|
break;
|
|
1811
1995
|
}
|
|
1812
1996
|
case "css-vars": {
|
|
1813
|
-
const out = options.output ??
|
|
1997
|
+
const out = options.output ?? join14(projectRoot, "decantr-tokens.css");
|
|
1814
1998
|
ensureDir(out);
|
|
1815
|
-
|
|
1999
|
+
writeFileSync6(out, generateCSSVars(tokens), "utf-8");
|
|
1816
2000
|
console.log(`${GREEN3}Exported CSS variables:${RESET3}`);
|
|
1817
2001
|
console.log(` ${DIM3}File:${RESET3} ${out}`);
|
|
1818
2002
|
break;
|
|
@@ -1820,16 +2004,16 @@ async function cmdExport(target, projectRoot, options = {}) {
|
|
|
1820
2004
|
}
|
|
1821
2005
|
}
|
|
1822
2006
|
function ensureDir(filePath) {
|
|
1823
|
-
const dir =
|
|
1824
|
-
if (!
|
|
1825
|
-
|
|
2007
|
+
const dir = dirname2(filePath);
|
|
2008
|
+
if (!existsSync14(dir)) {
|
|
2009
|
+
mkdirSync5(dir, { recursive: true });
|
|
1826
2010
|
}
|
|
1827
2011
|
}
|
|
1828
2012
|
|
|
1829
2013
|
// src/commands/magic.ts
|
|
1830
|
-
import { existsSync as
|
|
2014
|
+
import { existsSync as existsSync15 } from "fs";
|
|
1831
2015
|
import * as fs from "fs/promises";
|
|
1832
|
-
import { join as
|
|
2016
|
+
import { join as join15 } from "path";
|
|
1833
2017
|
var BOLD2 = "\x1B[1m";
|
|
1834
2018
|
var DIM4 = "\x1B[2m";
|
|
1835
2019
|
var RESET4 = "\x1B[0m";
|
|
@@ -1979,7 +2163,8 @@ async function resolveTheme(intent, registryClient) {
|
|
|
1979
2163
|
const hints = intent.themeHints;
|
|
1980
2164
|
if (registryClient) {
|
|
1981
2165
|
try {
|
|
1982
|
-
const
|
|
2166
|
+
const themesResult = await registryClient.fetchThemes();
|
|
2167
|
+
const themes = Array.isArray(themesResult) ? themesResult : Array.isArray(themesResult?.data?.items) ? themesResult.data.items : [];
|
|
1983
2168
|
if (themes && themes.length > 0) {
|
|
1984
2169
|
const scored = themes.map((t) => {
|
|
1985
2170
|
let score = 0;
|
|
@@ -1994,7 +2179,7 @@ async function resolveTheme(intent, registryClient) {
|
|
|
1994
2179
|
return { id: t.id || t.slug, score, modes: t.modes };
|
|
1995
2180
|
}).sort((a, b) => b.score - a.score);
|
|
1996
2181
|
if (scored[0] && scored[0].score > 0) {
|
|
1997
|
-
const mode2 = intent.themeHints.includes("light") ? "light" : intent.themeHints.includes("dark") ? "dark" : scored[0].modes?.includes("dark") ? "dark" : "light";
|
|
2182
|
+
const mode2 = intent.themeHints.includes("light") ? "light" : intent.themeHints.includes("dark") ? "dark" : scored[0].modes?.includes("light") ? "light" : scored[0].modes?.includes("dark") ? "dark" : "light";
|
|
1998
2183
|
return { id: scored[0].id, mode: mode2 };
|
|
1999
2184
|
}
|
|
2000
2185
|
}
|
|
@@ -2056,8 +2241,8 @@ async function cmdMagic(prompt, projectRoot, options) {
|
|
|
2056
2241
|
console.log(` Archetype: ${intent.archetype}`);
|
|
2057
2242
|
}
|
|
2058
2243
|
console.log("");
|
|
2059
|
-
const essencePath =
|
|
2060
|
-
if (
|
|
2244
|
+
const essencePath = join15(projectRoot, "decantr.essence.json");
|
|
2245
|
+
if (existsSync15(essencePath)) {
|
|
2061
2246
|
console.log(error(" decantr.essence.json already exists in this directory."));
|
|
2062
2247
|
console.log(dim(" Remove it first or use a different directory."));
|
|
2063
2248
|
process.exitCode = 1;
|
|
@@ -2080,7 +2265,7 @@ async function cmdMagic(prompt, projectRoot, options) {
|
|
|
2080
2265
|
return;
|
|
2081
2266
|
}
|
|
2082
2267
|
const registryClient = new RegistryClient({
|
|
2083
|
-
cacheDir:
|
|
2268
|
+
cacheDir: join15(projectRoot, ".decantr", "cache"),
|
|
2084
2269
|
apiUrl: options.registry,
|
|
2085
2270
|
offline: options.offline
|
|
2086
2271
|
});
|
|
@@ -2351,14 +2536,14 @@ async function cmdMagic(prompt, projectRoot, options) {
|
|
|
2351
2536
|
if (result.gitignoreUpdated) {
|
|
2352
2537
|
console.log(` ${dim(".gitignore updated")}`);
|
|
2353
2538
|
}
|
|
2354
|
-
const contextDir =
|
|
2539
|
+
const contextDir = join15(projectRoot, ".decantr", "context");
|
|
2355
2540
|
let sectionCount = 0;
|
|
2356
2541
|
try {
|
|
2357
2542
|
const files = await fs.readdir(contextDir);
|
|
2358
2543
|
sectionCount = files.filter((f) => f.startsWith("section-")).length;
|
|
2359
2544
|
} catch {
|
|
2360
2545
|
}
|
|
2361
|
-
const treatmentsPath =
|
|
2546
|
+
const treatmentsPath = join15(projectRoot, "src", "styles", "treatments.css");
|
|
2362
2547
|
let hasLayers = false;
|
|
2363
2548
|
try {
|
|
2364
2549
|
const css = await fs.readFile(treatmentsPath, "utf-8");
|
|
@@ -2376,27 +2561,23 @@ ${GREEN4}${BOLD2}Quality summary:${RESET4}`);
|
|
|
2376
2561
|
);
|
|
2377
2562
|
console.log("");
|
|
2378
2563
|
console.log(`${BOLD2} Ready!${RESET4} Next steps:`);
|
|
2379
|
-
console.log(` 1. Read ${cyan("DECANTR.md")} for guard rules, CSS approach, and workflow`);
|
|
2380
|
-
console.log(
|
|
2381
|
-
` 2. Read ${cyan(".decantr/context/scaffold-pack.md")} first as the primary compiled contract`
|
|
2382
|
-
);
|
|
2383
2564
|
console.log(
|
|
2384
|
-
`
|
|
2565
|
+
` 1. Read ${cyan(".decantr/context/scaffold-pack.md")} first as the primary compiled contract`
|
|
2385
2566
|
);
|
|
2386
2567
|
console.log(
|
|
2387
|
-
`
|
|
2568
|
+
` 2. Read the matching ${cyan(".decantr/context/section-*-pack.md")} and ${cyan(".decantr/context/page-*-pack.md")} files before section or route work`
|
|
2388
2569
|
);
|
|
2389
2570
|
console.log(
|
|
2390
|
-
`
|
|
2571
|
+
` 3. Use ${cyan("DECANTR.md")} as a lookup reference for guard rules, CSS atoms, treatments, decorators, and workflow`
|
|
2391
2572
|
);
|
|
2392
|
-
console.log(`
|
|
2393
|
-
console.log(`
|
|
2573
|
+
console.log(` 4. Build the shell and route structure first, then implement each page`);
|
|
2574
|
+
console.log(` 5. Run ${cyan("decantr check")} and ${cyan("decantr audit")} before you ship`);
|
|
2394
2575
|
console.log("");
|
|
2395
2576
|
}
|
|
2396
2577
|
|
|
2397
2578
|
// src/commands/migrate.ts
|
|
2398
|
-
import { copyFileSync, existsSync as
|
|
2399
|
-
import { join as
|
|
2579
|
+
import { copyFileSync, existsSync as existsSync16, readFileSync as readFileSync11, writeFileSync as writeFileSync7 } from "fs";
|
|
2580
|
+
import { join as join16 } from "path";
|
|
2400
2581
|
import { isV3 as isV32, migrateV2ToV3, validateEssence } from "@decantr/essence-spec";
|
|
2401
2582
|
var GREEN5 = "\x1B[32m";
|
|
2402
2583
|
var RED4 = "\x1B[31m";
|
|
@@ -2404,12 +2585,12 @@ var YELLOW3 = "\x1B[33m";
|
|
|
2404
2585
|
var RESET5 = "\x1B[0m";
|
|
2405
2586
|
var DIM5 = "\x1B[2m";
|
|
2406
2587
|
function migrateEssenceFile(essencePath) {
|
|
2407
|
-
if (!
|
|
2588
|
+
if (!existsSync16(essencePath)) {
|
|
2408
2589
|
return { success: false, error: `File not found: ${essencePath}` };
|
|
2409
2590
|
}
|
|
2410
2591
|
let raw;
|
|
2411
2592
|
try {
|
|
2412
|
-
raw =
|
|
2593
|
+
raw = readFileSync11(essencePath, "utf-8");
|
|
2413
2594
|
} catch (e) {
|
|
2414
2595
|
return { success: false, error: `Could not read ${essencePath}: ${e.message}` };
|
|
2415
2596
|
}
|
|
@@ -2450,7 +2631,7 @@ function migrateEssenceFile(essencePath) {
|
|
|
2450
2631
|
};
|
|
2451
2632
|
}
|
|
2452
2633
|
try {
|
|
2453
|
-
|
|
2634
|
+
writeFileSync7(essencePath, JSON.stringify(v3, null, 2) + "\n");
|
|
2454
2635
|
} catch (e) {
|
|
2455
2636
|
return {
|
|
2456
2637
|
success: false,
|
|
@@ -2461,8 +2642,8 @@ function migrateEssenceFile(essencePath) {
|
|
|
2461
2642
|
return { success: true, backupPath };
|
|
2462
2643
|
}
|
|
2463
2644
|
async function cmdMigrate(projectRoot = process.cwd()) {
|
|
2464
|
-
const essencePath =
|
|
2465
|
-
if (!
|
|
2645
|
+
const essencePath = join16(projectRoot, "decantr.essence.json");
|
|
2646
|
+
if (!existsSync16(essencePath)) {
|
|
2466
2647
|
console.error(`${RED4}No decantr.essence.json found. Run \`decantr init\` first.${RESET5}`);
|
|
2467
2648
|
process.exitCode = 1;
|
|
2468
2649
|
return;
|
|
@@ -2488,19 +2669,37 @@ async function cmdMigrate(projectRoot = process.cwd()) {
|
|
|
2488
2669
|
|
|
2489
2670
|
// src/commands/new-project.ts
|
|
2490
2671
|
import { execSync } from "child_process";
|
|
2491
|
-
import { existsSync as
|
|
2492
|
-
import { join as
|
|
2672
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync8 } from "fs";
|
|
2673
|
+
import { join as join19, resolve as resolve2 } from "path";
|
|
2493
2674
|
import { fileURLToPath } from "url";
|
|
2494
2675
|
|
|
2495
2676
|
// src/bootstrap.ts
|
|
2496
|
-
import { mkdirSync as
|
|
2497
|
-
import { basename, join as
|
|
2677
|
+
import { mkdirSync as mkdirSync6, readFileSync as readFileSync12, writeFileSync as writeFileSync8 } from "fs";
|
|
2678
|
+
import { basename, join as join17 } from "path";
|
|
2498
2679
|
import { resolvePackAdapter } from "@decantr/core";
|
|
2499
2680
|
var reactViteBootstrapAdapter = {
|
|
2500
2681
|
id: "react-vite",
|
|
2501
2682
|
label: "React + Vite starter",
|
|
2683
|
+
target: "react",
|
|
2684
|
+
packAdapter: "react-vite",
|
|
2685
|
+
capabilities: {
|
|
2686
|
+
bootstrap: true,
|
|
2687
|
+
attach: true,
|
|
2688
|
+
styling: true,
|
|
2689
|
+
verify: true
|
|
2690
|
+
},
|
|
2691
|
+
attach: {
|
|
2692
|
+
routeRoots: ["src/App.tsx", "src/routes", "src/pages"],
|
|
2693
|
+
layoutFiles: ["src/App.tsx", "src/main.tsx"],
|
|
2694
|
+
componentRoots: ["src/components", "src/pages", "src/routes"]
|
|
2695
|
+
},
|
|
2696
|
+
verify: {
|
|
2697
|
+
devCommand: "npm run dev",
|
|
2698
|
+
buildCommand: "npm run build",
|
|
2699
|
+
distDir: "dist"
|
|
2700
|
+
},
|
|
2502
2701
|
writeProjectFiles(projectDir, title, routingMode) {
|
|
2503
|
-
const srcDir =
|
|
2702
|
+
const srcDir = join17(projectDir, "src");
|
|
2504
2703
|
const routerImport = routingMode === "hash" ? "HashRouter" : "BrowserRouter";
|
|
2505
2704
|
const packageJson = {
|
|
2506
2705
|
name: basename(projectDir) || "decantr-app",
|
|
@@ -2516,7 +2715,7 @@ var reactViteBootstrapAdapter = {
|
|
|
2516
2715
|
react: "^19.0.0",
|
|
2517
2716
|
"react-dom": "^19.0.0",
|
|
2518
2717
|
"react-router-dom": "^7.0.0",
|
|
2519
|
-
"@decantr/css": "^1.0.
|
|
2718
|
+
"@decantr/css": "^1.0.4",
|
|
2520
2719
|
// P0-4: Lucide is the canonical icon library Decantr blueprints
|
|
2521
2720
|
// reference in personality prose ("Lucide icons"). Including it by
|
|
2522
2721
|
// default means cold scaffolds don't have to hand-roll inline SVGs.
|
|
@@ -2532,7 +2731,7 @@ var reactViteBootstrapAdapter = {
|
|
|
2532
2731
|
vite: "^6.0.0"
|
|
2533
2732
|
}
|
|
2534
2733
|
};
|
|
2535
|
-
|
|
2734
|
+
writeFileSync8(join17(projectDir, "package.json"), JSON.stringify(packageJson, null, 2) + "\n");
|
|
2536
2735
|
const viteConfig = `import { defineConfig } from 'vite';
|
|
2537
2736
|
import react from '@vitejs/plugin-react';
|
|
2538
2737
|
|
|
@@ -2540,7 +2739,7 @@ export default defineConfig({
|
|
|
2540
2739
|
plugins: [react()],
|
|
2541
2740
|
});
|
|
2542
2741
|
`;
|
|
2543
|
-
|
|
2742
|
+
writeFileSync8(join17(projectDir, "vite.config.ts"), viteConfig);
|
|
2544
2743
|
const tsconfig = {
|
|
2545
2744
|
compilerOptions: {
|
|
2546
2745
|
target: "ES2020",
|
|
@@ -2562,7 +2761,7 @@ export default defineConfig({
|
|
|
2562
2761
|
},
|
|
2563
2762
|
include: ["src"]
|
|
2564
2763
|
};
|
|
2565
|
-
|
|
2764
|
+
writeFileSync8(join17(projectDir, "tsconfig.json"), JSON.stringify(tsconfig, null, 2) + "\n");
|
|
2566
2765
|
const tsconfigApp = {
|
|
2567
2766
|
compilerOptions: {
|
|
2568
2767
|
tsBuildInfoFile: "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
|
@@ -2585,8 +2784,8 @@ export default defineConfig({
|
|
|
2585
2784
|
},
|
|
2586
2785
|
include: ["src"]
|
|
2587
2786
|
};
|
|
2588
|
-
|
|
2589
|
-
|
|
2787
|
+
writeFileSync8(
|
|
2788
|
+
join17(projectDir, "tsconfig.app.json"),
|
|
2590
2789
|
JSON.stringify(tsconfigApp, null, 2) + "\n"
|
|
2591
2790
|
);
|
|
2592
2791
|
const indexHtml = `<!doctype html>
|
|
@@ -2602,8 +2801,8 @@ export default defineConfig({
|
|
|
2602
2801
|
</body>
|
|
2603
2802
|
</html>
|
|
2604
2803
|
`;
|
|
2605
|
-
|
|
2606
|
-
|
|
2804
|
+
writeFileSync8(join17(projectDir, "index.html"), indexHtml);
|
|
2805
|
+
mkdirSync6(srcDir, { recursive: true });
|
|
2607
2806
|
const mainTsx = `import { StrictMode } from 'react';
|
|
2608
2807
|
import { createRoot } from 'react-dom/client';
|
|
2609
2808
|
import { ${routerImport} } from 'react-router-dom';
|
|
@@ -2620,7 +2819,7 @@ createRoot(document.getElementById('root')!).render(
|
|
|
2620
2819
|
</StrictMode>,
|
|
2621
2820
|
);
|
|
2622
2821
|
`;
|
|
2623
|
-
|
|
2822
|
+
writeFileSync8(join17(srcDir, "main.tsx"), mainTsx);
|
|
2624
2823
|
const appTsx = `import { css } from '@decantr/css';
|
|
2625
2824
|
import { Routes, Route } from 'react-router-dom';
|
|
2626
2825
|
|
|
@@ -2634,7 +2833,7 @@ function WelcomePage() {
|
|
|
2634
2833
|
<p className="d-label" data-anchor>Decantr starter</p>
|
|
2635
2834
|
<h1 className={css('_heading2')}>${title}</h1>
|
|
2636
2835
|
<p className={css('_textsm _fgmuted _mw[32rem]')}>
|
|
2637
|
-
Scaffolded with Decantr. Read
|
|
2836
|
+
Scaffolded with Decantr. Read .decantr/context/scaffold-pack.md first, then use DECANTR.md as a lookup reference.
|
|
2638
2837
|
</p>
|
|
2639
2838
|
<div className={css('_flex _gap3 _wrap _jcc')}>
|
|
2640
2839
|
<span className="d-annotation" data-status="info">Runtime: @decantr/css</span>
|
|
@@ -2655,34 +2854,184 @@ export function App() {
|
|
|
2655
2854
|
);
|
|
2656
2855
|
}
|
|
2657
2856
|
`;
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2857
|
+
writeFileSync8(join17(srcDir, "App.tsx"), appTsx);
|
|
2858
|
+
writeFileSync8(join17(srcDir, "vite-env.d.ts"), '/// <reference types="vite/client" />\n');
|
|
2859
|
+
mkdirSync6(join17(srcDir, "styles"), { recursive: true });
|
|
2661
2860
|
}
|
|
2662
2861
|
};
|
|
2663
|
-
var
|
|
2664
|
-
"
|
|
2862
|
+
var nextAppAdapter = {
|
|
2863
|
+
id: "next-app",
|
|
2864
|
+
label: "Next.js App Router starter",
|
|
2865
|
+
target: "nextjs",
|
|
2866
|
+
packAdapter: "nextjs",
|
|
2867
|
+
capabilities: {
|
|
2868
|
+
bootstrap: true,
|
|
2869
|
+
attach: true,
|
|
2870
|
+
styling: true,
|
|
2871
|
+
verify: true
|
|
2872
|
+
},
|
|
2873
|
+
attach: {
|
|
2874
|
+
routeRoots: ["app", "pages"],
|
|
2875
|
+
layoutFiles: ["app/layout.tsx", "pages/_app.tsx"],
|
|
2876
|
+
componentRoots: ["components", "src/components", "app"]
|
|
2877
|
+
},
|
|
2878
|
+
verify: {
|
|
2879
|
+
devCommand: "npm run dev",
|
|
2880
|
+
buildCommand: "npm run build",
|
|
2881
|
+
distDir: ".next"
|
|
2882
|
+
},
|
|
2883
|
+
writeProjectFiles(projectDir, title) {
|
|
2884
|
+
const appDir = join17(projectDir, "app");
|
|
2885
|
+
const stylesDir = join17(projectDir, "src", "styles");
|
|
2886
|
+
const packageJson = {
|
|
2887
|
+
name: basename(projectDir) || "decantr-next-app",
|
|
2888
|
+
private: true,
|
|
2889
|
+
version: "0.0.0",
|
|
2890
|
+
type: "module",
|
|
2891
|
+
scripts: {
|
|
2892
|
+
dev: "next dev",
|
|
2893
|
+
build: "next build",
|
|
2894
|
+
start: "next start"
|
|
2895
|
+
},
|
|
2896
|
+
dependencies: {
|
|
2897
|
+
"@decantr/css": "^1.0.4",
|
|
2898
|
+
"lucide-react": "^0.468.0",
|
|
2899
|
+
next: "^16.0.0",
|
|
2900
|
+
react: "^19.0.0",
|
|
2901
|
+
"react-dom": "^19.0.0"
|
|
2902
|
+
},
|
|
2903
|
+
devDependencies: {
|
|
2904
|
+
"@types/node": "^20.0.0",
|
|
2905
|
+
"@types/react": "^19.0.0",
|
|
2906
|
+
"@types/react-dom": "^19.0.0",
|
|
2907
|
+
typescript: "^5.7.0"
|
|
2908
|
+
}
|
|
2909
|
+
};
|
|
2910
|
+
writeFileSync8(join17(projectDir, "package.json"), JSON.stringify(packageJson, null, 2) + "\n");
|
|
2911
|
+
writeFileSync8(join17(projectDir, "next.config.ts"), 'import type { NextConfig } from "next";\n\nconst nextConfig: NextConfig = {};\n\nexport default nextConfig;\n');
|
|
2912
|
+
writeFileSync8(join17(projectDir, "next-env.d.ts"), '/// <reference types="next" />\n/// <reference types="next/image-types/global" />\n\n// This file is generated by Next.js.\n');
|
|
2913
|
+
writeFileSync8(
|
|
2914
|
+
join17(projectDir, "tsconfig.json"),
|
|
2915
|
+
JSON.stringify(
|
|
2916
|
+
{
|
|
2917
|
+
compilerOptions: {
|
|
2918
|
+
target: "ES2017",
|
|
2919
|
+
lib: ["dom", "dom.iterable", "esnext"],
|
|
2920
|
+
allowJs: true,
|
|
2921
|
+
skipLibCheck: true,
|
|
2922
|
+
strict: true,
|
|
2923
|
+
noEmit: true,
|
|
2924
|
+
esModuleInterop: true,
|
|
2925
|
+
module: "esnext",
|
|
2926
|
+
moduleResolution: "bundler",
|
|
2927
|
+
resolveJsonModule: true,
|
|
2928
|
+
isolatedModules: true,
|
|
2929
|
+
jsx: "preserve",
|
|
2930
|
+
incremental: true,
|
|
2931
|
+
plugins: [{ name: "next" }]
|
|
2932
|
+
},
|
|
2933
|
+
include: ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
|
2934
|
+
exclude: ["node_modules"]
|
|
2935
|
+
},
|
|
2936
|
+
null,
|
|
2937
|
+
2
|
|
2938
|
+
) + "\n"
|
|
2939
|
+
);
|
|
2940
|
+
mkdirSync6(appDir, { recursive: true });
|
|
2941
|
+
mkdirSync6(stylesDir, { recursive: true });
|
|
2942
|
+
writeFileSync8(
|
|
2943
|
+
join17(appDir, "layout.tsx"),
|
|
2944
|
+
`import type { Metadata } from 'next';
|
|
2945
|
+
import '../src/styles/global.css';
|
|
2946
|
+
import '../src/styles/tokens.css';
|
|
2947
|
+
import '../src/styles/treatments.css';
|
|
2948
|
+
|
|
2949
|
+
export const metadata: Metadata = {
|
|
2950
|
+
title: '${title}',
|
|
2951
|
+
description: 'Scaffolded with Decantr',
|
|
2952
|
+
};
|
|
2953
|
+
|
|
2954
|
+
export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) {
|
|
2955
|
+
return (
|
|
2956
|
+
<html lang="en">
|
|
2957
|
+
<body>{children}</body>
|
|
2958
|
+
</html>
|
|
2959
|
+
);
|
|
2960
|
+
}
|
|
2961
|
+
`
|
|
2962
|
+
);
|
|
2963
|
+
writeFileSync8(
|
|
2964
|
+
join17(appDir, "page.tsx"),
|
|
2965
|
+
`import { css } from '@decantr/css';
|
|
2966
|
+
|
|
2967
|
+
export default function HomePage() {
|
|
2968
|
+
return (
|
|
2969
|
+
<main id="main-content" className={css('_minh[100vh] _flex _col _aic _jcc _p6 _gap4')}>
|
|
2970
|
+
<section className={css('_wfull _mw[42rem]') + ' d-section'} data-density="comfortable">
|
|
2971
|
+
<div className={css('_flex _col _aic _gap4 _textc') + ' d-surface'} data-elevation="raised">
|
|
2972
|
+
<p className="d-label" data-anchor>Decantr starter</p>
|
|
2973
|
+
<h1 className={css('_heading2')}>${title}</h1>
|
|
2974
|
+
<p className={css('_textsm _fgmuted _mw[32rem]')}>Scaffolded with Decantr. Read .decantr/context/scaffold-pack.md first, then use DECANTR.md as a lookup reference.</p>
|
|
2975
|
+
<span className="d-annotation" data-status="info">Runtime: Next.js App Router</span>
|
|
2976
|
+
</div>
|
|
2977
|
+
</section>
|
|
2978
|
+
</main>
|
|
2979
|
+
);
|
|
2980
|
+
}
|
|
2981
|
+
`
|
|
2982
|
+
);
|
|
2983
|
+
}
|
|
2984
|
+
};
|
|
2985
|
+
var genericWebAdapter = {
|
|
2986
|
+
id: "generic-web",
|
|
2987
|
+
label: "Generic web contract adapter",
|
|
2988
|
+
target: "generic",
|
|
2989
|
+
packAdapter: "generic-web",
|
|
2990
|
+
capabilities: {
|
|
2991
|
+
bootstrap: false,
|
|
2992
|
+
attach: true,
|
|
2993
|
+
styling: true,
|
|
2994
|
+
verify: true
|
|
2995
|
+
},
|
|
2996
|
+
attach: {
|
|
2997
|
+
routeRoots: ["src", "app", "pages"],
|
|
2998
|
+
layoutFiles: ["index.html", "src/main.ts", "src/main.tsx"],
|
|
2999
|
+
componentRoots: ["src/components", "components"]
|
|
3000
|
+
},
|
|
3001
|
+
verify: {
|
|
3002
|
+
devCommand: "npm run dev",
|
|
3003
|
+
buildCommand: "npm run build",
|
|
3004
|
+
distDir: "dist"
|
|
3005
|
+
}
|
|
3006
|
+
};
|
|
3007
|
+
var DECANTR_ADAPTERS = {
|
|
3008
|
+
"react-vite": reactViteBootstrapAdapter,
|
|
3009
|
+
"next-app": nextAppAdapter,
|
|
3010
|
+
"generic-web": genericWebAdapter
|
|
2665
3011
|
};
|
|
2666
3012
|
function resolveBootstrapTarget(target) {
|
|
2667
3013
|
const normalizedTarget = (target || "react").toLowerCase();
|
|
2668
|
-
const platformType = "spa";
|
|
3014
|
+
const platformType = normalizedTarget === "nextjs" ? "ssr" : "spa";
|
|
2669
3015
|
const packAdapter = resolvePackAdapter(normalizedTarget, platformType);
|
|
3016
|
+
const adapterId = normalizedTarget === "nextjs" ? "next-app" : DECANTR_ADAPTERS[packAdapter] ? packAdapter : "generic-web";
|
|
2670
3017
|
return {
|
|
2671
3018
|
target: normalizedTarget,
|
|
2672
3019
|
platformType,
|
|
2673
3020
|
packAdapter,
|
|
2674
|
-
|
|
3021
|
+
adapterId,
|
|
3022
|
+
bootstrapAdapterId: DECANTR_ADAPTERS[adapterId]?.capabilities.bootstrap && DECANTR_ADAPTERS[adapterId]?.writeProjectFiles ? adapterId : null
|
|
2675
3023
|
};
|
|
2676
3024
|
}
|
|
2677
3025
|
function getBootstrapAdapter(resolution) {
|
|
2678
3026
|
if (!resolution.bootstrapAdapterId) {
|
|
2679
3027
|
return null;
|
|
2680
3028
|
}
|
|
2681
|
-
|
|
3029
|
+
const adapter = DECANTR_ADAPTERS[resolution.bootstrapAdapterId];
|
|
3030
|
+
return adapter?.writeProjectFiles ? adapter : null;
|
|
2682
3031
|
}
|
|
2683
3032
|
function detectRoutingMode(projectDir) {
|
|
2684
3033
|
try {
|
|
2685
|
-
const essence = JSON.parse(
|
|
3034
|
+
const essence = JSON.parse(readFileSync12(join17(projectDir, "decantr.essence.json"), "utf-8"));
|
|
2686
3035
|
const routing = essence.meta?.platform?.routing;
|
|
2687
3036
|
if (routing === "history" || routing === "pathname") {
|
|
2688
3037
|
return routing;
|
|
@@ -2694,45 +3043,45 @@ function detectRoutingMode(projectDir) {
|
|
|
2694
3043
|
}
|
|
2695
3044
|
|
|
2696
3045
|
// src/offline-content.ts
|
|
2697
|
-
import { cpSync, existsSync as
|
|
2698
|
-
import { join as
|
|
3046
|
+
import { cpSync, existsSync as existsSync17, mkdirSync as mkdirSync7 } from "fs";
|
|
3047
|
+
import { join as join18, resolve } from "path";
|
|
2699
3048
|
var CONTENT_TYPES2 = ["archetypes", "blueprints", "patterns", "themes", "shells"];
|
|
2700
3049
|
function copyIfExists(source, target) {
|
|
2701
|
-
if (!
|
|
3050
|
+
if (!existsSync17(source)) return false;
|
|
2702
3051
|
if (resolve(source) === resolve(target)) return true;
|
|
2703
3052
|
cpSync(source, target, { recursive: true });
|
|
2704
3053
|
return true;
|
|
2705
3054
|
}
|
|
2706
3055
|
function hydrateContentRoot(projectDir, contentRoot) {
|
|
2707
|
-
if (!
|
|
2708
|
-
const customRoot =
|
|
2709
|
-
const cacheRoot =
|
|
2710
|
-
|
|
2711
|
-
|
|
3056
|
+
if (!existsSync17(contentRoot)) return false;
|
|
3057
|
+
const customRoot = join18(projectDir, ".decantr", "custom");
|
|
3058
|
+
const cacheRoot = join18(projectDir, ".decantr", "cache", "@official");
|
|
3059
|
+
mkdirSync7(customRoot, { recursive: true });
|
|
3060
|
+
mkdirSync7(cacheRoot, { recursive: true });
|
|
2712
3061
|
let copiedAny = false;
|
|
2713
3062
|
for (const type of CONTENT_TYPES2) {
|
|
2714
|
-
const sourceDir =
|
|
2715
|
-
if (!
|
|
2716
|
-
cpSync(sourceDir,
|
|
2717
|
-
cpSync(sourceDir,
|
|
3063
|
+
const sourceDir = join18(contentRoot, type);
|
|
3064
|
+
if (!existsSync17(sourceDir)) continue;
|
|
3065
|
+
cpSync(sourceDir, join18(customRoot, type), { recursive: true });
|
|
3066
|
+
cpSync(sourceDir, join18(cacheRoot, type), { recursive: true });
|
|
2718
3067
|
copiedAny = true;
|
|
2719
3068
|
}
|
|
2720
3069
|
return copiedAny;
|
|
2721
3070
|
}
|
|
2722
3071
|
function seedOfflineRegistry(projectDir, workspaceRoot) {
|
|
2723
|
-
const projectDecantrRoot =
|
|
2724
|
-
|
|
3072
|
+
const projectDecantrRoot = join18(projectDir, ".decantr");
|
|
3073
|
+
mkdirSync7(projectDecantrRoot, { recursive: true });
|
|
2725
3074
|
const configuredContentRoot = process.env.DECANTR_CONTENT_DIR ? resolve(process.env.DECANTR_CONTENT_DIR) : null;
|
|
2726
3075
|
if (configuredContentRoot && hydrateContentRoot(projectDir, configuredContentRoot)) {
|
|
2727
3076
|
return { seeded: true, strategy: "configured-content-root" };
|
|
2728
3077
|
}
|
|
2729
3078
|
const copiedCache = copyIfExists(
|
|
2730
|
-
|
|
2731
|
-
|
|
3079
|
+
join18(workspaceRoot, ".decantr", "cache"),
|
|
3080
|
+
join18(projectDecantrRoot, "cache")
|
|
2732
3081
|
);
|
|
2733
3082
|
const copiedCustom = copyIfExists(
|
|
2734
|
-
|
|
2735
|
-
|
|
3083
|
+
join18(workspaceRoot, ".decantr", "custom"),
|
|
3084
|
+
join18(projectDecantrRoot, "custom")
|
|
2736
3085
|
);
|
|
2737
3086
|
if (copiedCache || copiedCustom) {
|
|
2738
3087
|
return { seeded: true, strategy: "workspace-cache" };
|
|
@@ -2774,7 +3123,9 @@ async function cmdNewProject(projectName, options) {
|
|
|
2774
3123
|
const projectDir = resolve2(workspaceRoot, projectName);
|
|
2775
3124
|
const bootstrapTarget = resolveBootstrapTarget(options.target);
|
|
2776
3125
|
const bootstrapAdapter = getBootstrapAdapter(bootstrapTarget);
|
|
2777
|
-
const
|
|
3126
|
+
const registryBackedScaffold = Boolean(options.blueprint || options.archetype);
|
|
3127
|
+
const inferredAdoption = options.adoption || (registryBackedScaffold ? "decantr-css" : "contract-only");
|
|
3128
|
+
const shouldBootstrapRuntime = Boolean(bootstrapAdapter && inferredAdoption === "decantr-css");
|
|
2778
3129
|
if (!/^[a-z0-9][a-z0-9._-]*$/i.test(projectName)) {
|
|
2779
3130
|
console.error(
|
|
2780
3131
|
error2("Invalid project name. Use alphanumeric characters, hyphens, dots, or underscores.")
|
|
@@ -2782,18 +3133,24 @@ async function cmdNewProject(projectName, options) {
|
|
|
2782
3133
|
process.exitCode = 1;
|
|
2783
3134
|
return;
|
|
2784
3135
|
}
|
|
2785
|
-
if (
|
|
3136
|
+
if (existsSync18(projectDir)) {
|
|
2786
3137
|
console.error(error2(`Directory "${projectName}" already exists.`));
|
|
2787
3138
|
process.exitCode = 1;
|
|
2788
3139
|
return;
|
|
2789
3140
|
}
|
|
2790
3141
|
console.log(heading(`Creating ${projectName}...`));
|
|
2791
|
-
|
|
3142
|
+
mkdirSync8(projectDir, { recursive: true });
|
|
2792
3143
|
console.log(dim2(` Created ${projectName}/`));
|
|
2793
3144
|
const title = projectName.replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
2794
|
-
if (bootstrapAdapter) {
|
|
3145
|
+
if (shouldBootstrapRuntime && bootstrapAdapter) {
|
|
2795
3146
|
bootstrapAdapter.writeProjectFiles(projectDir, title, "hash");
|
|
2796
3147
|
console.log(dim2(` Bootstrapped ${bootstrapAdapter.label}`));
|
|
3148
|
+
} else if (bootstrapAdapter) {
|
|
3149
|
+
console.log(
|
|
3150
|
+
dim2(
|
|
3151
|
+
` Skipping runtime bootstrap for adoption=${inferredAdoption}; creating a Decantr contract-only workspace.`
|
|
3152
|
+
)
|
|
3153
|
+
);
|
|
2797
3154
|
} else {
|
|
2798
3155
|
console.log(
|
|
2799
3156
|
`${YELLOW4} No greenfield bootstrap adapter is available yet for target "${bootstrapTarget.target}" (${bootstrapTarget.packAdapter}).${RESET6}`
|
|
@@ -2805,7 +3162,7 @@ async function cmdNewProject(projectName, options) {
|
|
|
2805
3162
|
);
|
|
2806
3163
|
}
|
|
2807
3164
|
const packageManager = detectPackageManager();
|
|
2808
|
-
if (
|
|
3165
|
+
if (shouldBootstrapRuntime) {
|
|
2809
3166
|
console.log(heading("Installing dependencies..."));
|
|
2810
3167
|
try {
|
|
2811
3168
|
execSync(`${packageManager} install`, { cwd: projectDir, stdio: "inherit" });
|
|
@@ -2845,7 +3202,7 @@ ${YELLOW4}Dependency install failed. Run \`${packageManager} install\` manually.
|
|
|
2845
3202
|
return;
|
|
2846
3203
|
}
|
|
2847
3204
|
console.log(heading("Initializing Decantr..."));
|
|
2848
|
-
const initFlags = ["--yes", "--
|
|
3205
|
+
const initFlags = ["--yes", "--workflow=greenfield", `--adoption=${inferredAdoption}`];
|
|
2849
3206
|
if (options.blueprint) initFlags.push(`--blueprint=${options.blueprint}`);
|
|
2850
3207
|
if (options.archetype) initFlags.push(`--archetype=${options.archetype}`);
|
|
2851
3208
|
if (options.theme) initFlags.push(`--theme=${options.theme}`);
|
|
@@ -2854,12 +3211,13 @@ ${YELLOW4}Dependency install failed. Run \`${packageManager} install\` manually.
|
|
|
2854
3211
|
if (options.target) initFlags.push(`--target=${options.target}`);
|
|
2855
3212
|
if (options.offline) initFlags.push("--offline");
|
|
2856
3213
|
if (options.registry) initFlags.push(`--registry=${options.registry}`);
|
|
3214
|
+
if (options.assistantBridge) initFlags.push(`--assistant-bridge=${options.assistantBridge}`);
|
|
2857
3215
|
try {
|
|
2858
3216
|
const bundledCliEntrypoint = fileURLToPath(new URL("./bin.js", import.meta.url));
|
|
2859
|
-
const cliEntrypoint =
|
|
3217
|
+
const cliEntrypoint = existsSync18(bundledCliEntrypoint) ? bundledCliEntrypoint : process.argv[1] && existsSync18(process.argv[1]) ? process.argv[1] : null;
|
|
2860
3218
|
const cliPath = cliEntrypoint ? `"${process.execPath}" "${cliEntrypoint}"` : "npx decantr";
|
|
2861
3219
|
execSync(`${cliPath} init ${initFlags.join(" ")}`, { cwd: projectDir, stdio: "inherit" });
|
|
2862
|
-
if (bootstrapAdapter) {
|
|
3220
|
+
if (shouldBootstrapRuntime && bootstrapAdapter) {
|
|
2863
3221
|
bootstrapAdapter.writeProjectFiles(projectDir, title, detectRoutingMode(projectDir));
|
|
2864
3222
|
}
|
|
2865
3223
|
} catch {
|
|
@@ -2872,33 +3230,33 @@ ${YELLOW4}Decantr init encountered issues. Run \`decantr init\` manually inside
|
|
|
2872
3230
|
\u2713 Project "${projectName}" created!
|
|
2873
3231
|
`));
|
|
2874
3232
|
console.log(` ${cyan2("cd " + projectName)}`);
|
|
2875
|
-
if (
|
|
3233
|
+
if (shouldBootstrapRuntime) {
|
|
2876
3234
|
console.log(` ${cyan2(packageManager + " run dev")}`);
|
|
2877
3235
|
} else {
|
|
2878
3236
|
console.log(
|
|
2879
3237
|
dim2(
|
|
2880
|
-
` Contract-only mode for target ${bootstrapTarget.target}. Bring your own runtime, or rerun ${cyan2(
|
|
3238
|
+
` Contract-only mode for target ${bootstrapTarget.target}. Bring your own runtime, or rerun with ${cyan2("--adoption=decantr-css")} for a runnable Decantr CSS starter adapter.`
|
|
2881
3239
|
)
|
|
2882
3240
|
);
|
|
2883
3241
|
}
|
|
2884
3242
|
console.log("");
|
|
2885
3243
|
}
|
|
2886
3244
|
function detectPackageManager() {
|
|
2887
|
-
if (
|
|
3245
|
+
if (existsSync18(join19(process.cwd(), "pnpm-lock.yaml")) || existsSync18(join19(process.cwd(), "pnpm-workspace.yaml"))) {
|
|
2888
3246
|
return "pnpm";
|
|
2889
3247
|
}
|
|
2890
|
-
if (
|
|
3248
|
+
if (existsSync18(join19(process.cwd(), "yarn.lock"))) {
|
|
2891
3249
|
return "yarn";
|
|
2892
3250
|
}
|
|
2893
|
-
if (
|
|
3251
|
+
if (existsSync18(join19(process.cwd(), "bun.lockb")) || existsSync18(join19(process.cwd(), "bun.lock"))) {
|
|
2894
3252
|
return "bun";
|
|
2895
3253
|
}
|
|
2896
3254
|
return "npm";
|
|
2897
3255
|
}
|
|
2898
3256
|
|
|
2899
3257
|
// src/commands/publish.ts
|
|
2900
|
-
import { existsSync as
|
|
2901
|
-
import { join as
|
|
3258
|
+
import { existsSync as existsSync19, readFileSync as readFileSync13 } from "fs";
|
|
3259
|
+
import { join as join20 } from "path";
|
|
2902
3260
|
import {
|
|
2903
3261
|
API_CONTENT_TYPE_TO_CONTENT_TYPE,
|
|
2904
3262
|
CONTENT_TYPE_TO_API_CONTENT_TYPE as CONTENT_TYPE_TO_API_CONTENT_TYPE2,
|
|
@@ -2914,8 +3272,8 @@ async function cmdPublish(type, name, projectRoot = process.cwd()) {
|
|
|
2914
3272
|
}
|
|
2915
3273
|
const singularType = API_CONTENT_TYPE_TO_CONTENT_TYPE[type] || type;
|
|
2916
3274
|
const pluralType = CONTENT_TYPE_TO_API_CONTENT_TYPE2[type] || CONTENT_TYPE_TO_API_CONTENT_TYPE2[singularType] || `${type}s`;
|
|
2917
|
-
const customPath =
|
|
2918
|
-
if (!
|
|
3275
|
+
const customPath = join20(projectRoot, ".decantr", "custom", pluralType, `${name}.json`);
|
|
3276
|
+
if (!existsSync19(customPath)) {
|
|
2919
3277
|
console.error(`Custom ${singularType} "${name}" not found at ${customPath}`);
|
|
2920
3278
|
console.error(`Create one first: decantr create ${singularType} ${name}`);
|
|
2921
3279
|
process.exitCode = 1;
|
|
@@ -2923,7 +3281,7 @@ async function cmdPublish(type, name, projectRoot = process.cwd()) {
|
|
|
2923
3281
|
}
|
|
2924
3282
|
let data;
|
|
2925
3283
|
try {
|
|
2926
|
-
data = JSON.parse(
|
|
3284
|
+
data = JSON.parse(readFileSync13(customPath, "utf-8"));
|
|
2927
3285
|
} catch {
|
|
2928
3286
|
console.error(`Failed to parse ${customPath}`);
|
|
2929
3287
|
process.exitCode = 1;
|
|
@@ -2961,23 +3319,23 @@ async function cmdPublish(type, name, projectRoot = process.cwd()) {
|
|
|
2961
3319
|
}
|
|
2962
3320
|
|
|
2963
3321
|
// src/commands/refresh.ts
|
|
2964
|
-
import { existsSync as
|
|
2965
|
-
import { join as
|
|
3322
|
+
import { existsSync as existsSync20, readFileSync as readFileSync14 } from "fs";
|
|
3323
|
+
import { join as join21 } from "path";
|
|
2966
3324
|
import { isV3 as isV33 } from "@decantr/essence-spec";
|
|
2967
3325
|
var GREEN7 = "\x1B[32m";
|
|
2968
3326
|
var RED6 = "\x1B[31m";
|
|
2969
3327
|
var DIM7 = "\x1B[2m";
|
|
2970
3328
|
var RESET7 = "\x1B[0m";
|
|
2971
3329
|
async function cmdRefresh(projectRoot = process.cwd(), options = {}) {
|
|
2972
|
-
const essencePath =
|
|
2973
|
-
if (!
|
|
3330
|
+
const essencePath = join21(projectRoot, "decantr.essence.json");
|
|
3331
|
+
if (!existsSync20(essencePath)) {
|
|
2974
3332
|
console.error(`${RED6}No decantr.essence.json found. Run \`decantr init\` first.${RESET7}`);
|
|
2975
3333
|
process.exitCode = 1;
|
|
2976
3334
|
return;
|
|
2977
3335
|
}
|
|
2978
3336
|
let essence;
|
|
2979
3337
|
try {
|
|
2980
|
-
const raw =
|
|
3338
|
+
const raw = readFileSync14(essencePath, "utf-8");
|
|
2981
3339
|
const parsed = JSON.parse(raw);
|
|
2982
3340
|
if (!isV33(parsed)) {
|
|
2983
3341
|
console.error(`${RED6}Essence is not v3. Run \`decantr migrate\` first.${RESET7}`);
|
|
@@ -2991,7 +3349,7 @@ async function cmdRefresh(projectRoot = process.cwd(), options = {}) {
|
|
|
2991
3349
|
return;
|
|
2992
3350
|
}
|
|
2993
3351
|
const registryClient = new RegistryClient({
|
|
2994
|
-
cacheDir:
|
|
3352
|
+
cacheDir: join21(projectRoot, ".decantr", "cache"),
|
|
2995
3353
|
offline: options.offline
|
|
2996
3354
|
});
|
|
2997
3355
|
console.log("Regenerating derived files...\n");
|
|
@@ -3011,8 +3369,8 @@ async function cmdRefresh(projectRoot = process.cwd(), options = {}) {
|
|
|
3011
3369
|
}
|
|
3012
3370
|
|
|
3013
3371
|
// src/commands/registry-mirror.ts
|
|
3014
|
-
import { mkdirSync as
|
|
3015
|
-
import { join as
|
|
3372
|
+
import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync9 } from "fs";
|
|
3373
|
+
import { join as join22 } from "path";
|
|
3016
3374
|
import { API_CONTENT_TYPES, RegistryAPIClient as RegistryAPIClient2 } from "@decantr/registry";
|
|
3017
3375
|
var GREEN8 = "\x1B[32m";
|
|
3018
3376
|
var RED7 = "\x1B[31m";
|
|
@@ -3040,7 +3398,7 @@ async function cmdRegistryMirror(projectRoot, options = {}) {
|
|
|
3040
3398
|
process.exitCode = 1;
|
|
3041
3399
|
return;
|
|
3042
3400
|
}
|
|
3043
|
-
const cacheDir =
|
|
3401
|
+
const cacheDir = join22(projectRoot, ".decantr", "cache");
|
|
3044
3402
|
const counts = {};
|
|
3045
3403
|
const failed = [];
|
|
3046
3404
|
console.log(`
|
|
@@ -3050,19 +3408,19 @@ Mirroring registry content to ${DIM8}.decantr/cache/${RESET8}
|
|
|
3050
3408
|
try {
|
|
3051
3409
|
const result = await apiClient.listContent(type, { namespace: "@official" });
|
|
3052
3410
|
const items = result.items;
|
|
3053
|
-
const typeDir =
|
|
3054
|
-
|
|
3055
|
-
|
|
3411
|
+
const typeDir = join22(cacheDir, "@official", type);
|
|
3412
|
+
mkdirSync9(typeDir, { recursive: true });
|
|
3413
|
+
writeFileSync9(join22(typeDir, "index.json"), JSON.stringify(result, null, 2));
|
|
3056
3414
|
let itemCount = 0;
|
|
3057
3415
|
for (const item of items) {
|
|
3058
3416
|
const slug = item.slug || item.id;
|
|
3059
3417
|
if (!slug) continue;
|
|
3060
3418
|
try {
|
|
3061
3419
|
const fullItem = await apiClient.getContent(type, "@official", slug);
|
|
3062
|
-
|
|
3420
|
+
writeFileSync9(join22(typeDir, `${slug}.json`), JSON.stringify(fullItem, null, 2));
|
|
3063
3421
|
itemCount++;
|
|
3064
3422
|
} catch {
|
|
3065
|
-
|
|
3423
|
+
writeFileSync9(join22(typeDir, `${slug}.json`), JSON.stringify(item, null, 2));
|
|
3066
3424
|
itemCount++;
|
|
3067
3425
|
}
|
|
3068
3426
|
}
|
|
@@ -3077,8 +3435,8 @@ Mirroring registry content to ${DIM8}.decantr/cache/${RESET8}
|
|
|
3077
3435
|
mirrored_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3078
3436
|
counts
|
|
3079
3437
|
};
|
|
3080
|
-
|
|
3081
|
-
|
|
3438
|
+
mkdirSync9(join22(cacheDir), { recursive: true });
|
|
3439
|
+
writeFileSync9(join22(cacheDir, "mirror-manifest.json"), JSON.stringify(manifest, null, 2));
|
|
3082
3440
|
const totalItems = Object.values(counts).reduce((a, b) => a + b, 0);
|
|
3083
3441
|
console.log("");
|
|
3084
3442
|
if (failed.length > 0) {
|
|
@@ -3095,23 +3453,23 @@ Mirroring registry content to ${DIM8}.decantr/cache/${RESET8}
|
|
|
3095
3453
|
}
|
|
3096
3454
|
|
|
3097
3455
|
// src/commands/remove.ts
|
|
3098
|
-
import { existsSync as
|
|
3099
|
-
import { join as
|
|
3456
|
+
import { existsSync as existsSync22, readFileSync as readFileSync15, rmSync as rmSync2, writeFileSync as writeFileSync10 } from "fs";
|
|
3457
|
+
import { join as join23 } from "path";
|
|
3100
3458
|
import { isV3 as isV34, migrateV30ToV31 as migrateV30ToV312 } from "@decantr/essence-spec";
|
|
3101
3459
|
var GREEN9 = "\x1B[32m";
|
|
3102
3460
|
var RED8 = "\x1B[31m";
|
|
3103
3461
|
var DIM9 = "\x1B[2m";
|
|
3104
3462
|
var RESET9 = "\x1B[0m";
|
|
3105
3463
|
function readAndMigrate2(projectRoot) {
|
|
3106
|
-
const essencePath =
|
|
3107
|
-
if (!
|
|
3464
|
+
const essencePath = join23(projectRoot, "decantr.essence.json");
|
|
3465
|
+
if (!existsSync22(essencePath)) {
|
|
3108
3466
|
console.error(`${RED8}No decantr.essence.json found. Run \`decantr init\` first.${RESET9}`);
|
|
3109
3467
|
process.exitCode = 1;
|
|
3110
3468
|
return null;
|
|
3111
3469
|
}
|
|
3112
3470
|
let parsed;
|
|
3113
3471
|
try {
|
|
3114
|
-
parsed = JSON.parse(
|
|
3472
|
+
parsed = JSON.parse(readFileSync15(essencePath, "utf-8"));
|
|
3115
3473
|
} catch (e) {
|
|
3116
3474
|
console.error(`${RED8}Could not read essence: ${e.message}${RESET9}`);
|
|
3117
3475
|
process.exitCode = 1;
|
|
@@ -3126,7 +3484,7 @@ function readAndMigrate2(projectRoot) {
|
|
|
3126
3484
|
return { essence, essencePath };
|
|
3127
3485
|
}
|
|
3128
3486
|
function writeEssence2(essencePath, essence) {
|
|
3129
|
-
|
|
3487
|
+
writeFileSync10(essencePath, JSON.stringify(essence, null, 2) + "\n");
|
|
3130
3488
|
}
|
|
3131
3489
|
function recomputeGlobalFeatures(essence) {
|
|
3132
3490
|
const all = /* @__PURE__ */ new Set();
|
|
@@ -3168,14 +3526,14 @@ async function cmdRemoveSection(sectionId, args, projectRoot = process.cwd()) {
|
|
|
3168
3526
|
sections.splice(idx, 1);
|
|
3169
3527
|
recomputeGlobalFeatures(essence);
|
|
3170
3528
|
removeRoutes(essence, sectionId);
|
|
3171
|
-
const contextFile =
|
|
3172
|
-
if (
|
|
3529
|
+
const contextFile = join23(projectRoot, ".decantr", "context", `${sectionId}.md`);
|
|
3530
|
+
if (existsSync22(contextFile)) {
|
|
3173
3531
|
rmSync2(contextFile);
|
|
3174
3532
|
}
|
|
3175
3533
|
writeEssence2(essencePath, essence);
|
|
3176
3534
|
console.log(`${GREEN9}Removed section "${sectionId}".${RESET9}`);
|
|
3177
3535
|
const registryClient = new RegistryClient({
|
|
3178
|
-
cacheDir:
|
|
3536
|
+
cacheDir: join23(projectRoot, ".decantr", "cache")
|
|
3179
3537
|
});
|
|
3180
3538
|
await refreshDerivedFiles(projectRoot, essence, registryClient);
|
|
3181
3539
|
console.log(`${GREEN9}Derived files refreshed.${RESET9}`);
|
|
@@ -3211,7 +3569,7 @@ async function cmdRemovePage(path, args, projectRoot = process.cwd()) {
|
|
|
3211
3569
|
writeEssence2(essencePath, essence);
|
|
3212
3570
|
console.log(`${GREEN9}Removed page "${pageId}" from section "${sectionId}".${RESET9}`);
|
|
3213
3571
|
const registryClient = new RegistryClient({
|
|
3214
|
-
cacheDir:
|
|
3572
|
+
cacheDir: join23(projectRoot, ".decantr", "cache")
|
|
3215
3573
|
});
|
|
3216
3574
|
await refreshDerivedFiles(projectRoot, essence, registryClient);
|
|
3217
3575
|
console.log(`${GREEN9}Derived files refreshed.${RESET9}`);
|
|
@@ -3252,15 +3610,15 @@ async function cmdRemoveFeature(feature, args, projectRoot = process.cwd()) {
|
|
|
3252
3610
|
const target = sectionId ? `section "${sectionId}" and global` : "global";
|
|
3253
3611
|
console.log(`${GREEN9}Removed feature "${feature}" from ${target} features.${RESET9}`);
|
|
3254
3612
|
const registryClient = new RegistryClient({
|
|
3255
|
-
cacheDir:
|
|
3613
|
+
cacheDir: join23(projectRoot, ".decantr", "cache")
|
|
3256
3614
|
});
|
|
3257
3615
|
await refreshDerivedFiles(projectRoot, essence, registryClient);
|
|
3258
3616
|
console.log(`${GREEN9}Derived files refreshed.${RESET9}`);
|
|
3259
3617
|
}
|
|
3260
3618
|
|
|
3261
3619
|
// src/commands/sync-drift.ts
|
|
3262
|
-
import { existsSync as
|
|
3263
|
-
import { join as
|
|
3620
|
+
import { existsSync as existsSync23, readFileSync as readFileSync16, writeFileSync as writeFileSync11 } from "fs";
|
|
3621
|
+
import { join as join24 } from "path";
|
|
3264
3622
|
var GREEN10 = "\x1B[32m";
|
|
3265
3623
|
var RED9 = "\x1B[31m";
|
|
3266
3624
|
var YELLOW6 = "\x1B[33m";
|
|
@@ -3269,8 +3627,8 @@ var DIM10 = "\x1B[2m";
|
|
|
3269
3627
|
var BOLD4 = "\x1B[1m";
|
|
3270
3628
|
var CYAN5 = "\x1B[36m";
|
|
3271
3629
|
async function cmdSyncDrift(projectRoot = process.cwd()) {
|
|
3272
|
-
const driftLogPath =
|
|
3273
|
-
if (!
|
|
3630
|
+
const driftLogPath = join24(projectRoot, ".decantr", "drift-log.json");
|
|
3631
|
+
if (!existsSync23(driftLogPath)) {
|
|
3274
3632
|
console.log(`${GREEN10}No drift log found \u2014 no drift recorded.${RESET10}`);
|
|
3275
3633
|
console.log(
|
|
3276
3634
|
`${DIM10}Drift is logged when guard violations are detected during development.${RESET10}`
|
|
@@ -3279,7 +3637,7 @@ async function cmdSyncDrift(projectRoot = process.cwd()) {
|
|
|
3279
3637
|
}
|
|
3280
3638
|
let entries;
|
|
3281
3639
|
try {
|
|
3282
|
-
entries = JSON.parse(
|
|
3640
|
+
entries = JSON.parse(readFileSync16(driftLogPath, "utf-8"));
|
|
3283
3641
|
} catch {
|
|
3284
3642
|
console.error(`${RED9}Could not parse drift-log.json${RESET10}`);
|
|
3285
3643
|
process.exitCode = 1;
|
|
@@ -3325,13 +3683,13 @@ ${BOLD4}Unresolved Drift Entries (${unresolved.length})${RESET10}
|
|
|
3325
3683
|
console.log("");
|
|
3326
3684
|
}
|
|
3327
3685
|
function resolveDriftEntries(projectRoot, options) {
|
|
3328
|
-
const driftLogPath =
|
|
3329
|
-
if (!
|
|
3686
|
+
const driftLogPath = join24(projectRoot, ".decantr", "drift-log.json");
|
|
3687
|
+
if (!existsSync23(driftLogPath)) {
|
|
3330
3688
|
return { success: true };
|
|
3331
3689
|
}
|
|
3332
3690
|
if (options.clear) {
|
|
3333
3691
|
try {
|
|
3334
|
-
|
|
3692
|
+
writeFileSync11(driftLogPath, "[]");
|
|
3335
3693
|
return { success: true };
|
|
3336
3694
|
} catch (e) {
|
|
3337
3695
|
return { success: false, error: `Could not clear drift log: ${e.message}` };
|
|
@@ -3339,7 +3697,7 @@ function resolveDriftEntries(projectRoot, options) {
|
|
|
3339
3697
|
}
|
|
3340
3698
|
let entries;
|
|
3341
3699
|
try {
|
|
3342
|
-
entries = JSON.parse(
|
|
3700
|
+
entries = JSON.parse(readFileSync16(driftLogPath, "utf-8"));
|
|
3343
3701
|
} catch {
|
|
3344
3702
|
return { success: false, error: "Could not parse drift-log.json" };
|
|
3345
3703
|
}
|
|
@@ -3358,7 +3716,7 @@ function resolveDriftEntries(projectRoot, options) {
|
|
|
3358
3716
|
}
|
|
3359
3717
|
}
|
|
3360
3718
|
try {
|
|
3361
|
-
|
|
3719
|
+
writeFileSync11(driftLogPath, JSON.stringify(entries, null, 2));
|
|
3362
3720
|
return { success: true };
|
|
3363
3721
|
} catch (e) {
|
|
3364
3722
|
return { success: false, error: `Could not write drift log: ${e.message}` };
|
|
@@ -3366,8 +3724,8 @@ function resolveDriftEntries(projectRoot, options) {
|
|
|
3366
3724
|
}
|
|
3367
3725
|
|
|
3368
3726
|
// src/commands/theme-switch.ts
|
|
3369
|
-
import { existsSync as
|
|
3370
|
-
import { join as
|
|
3727
|
+
import { existsSync as existsSync24, readFileSync as readFileSync17, writeFileSync as writeFileSync12 } from "fs";
|
|
3728
|
+
import { join as join25 } from "path";
|
|
3371
3729
|
import { isV3 as isV35, migrateV30ToV31 as migrateV30ToV313 } from "@decantr/essence-spec";
|
|
3372
3730
|
var GREEN11 = "\x1B[32m";
|
|
3373
3731
|
var RED10 = "\x1B[31m";
|
|
@@ -3384,15 +3742,15 @@ async function cmdThemeSwitch(themeName, args, projectRoot = process.cwd()) {
|
|
|
3384
3742
|
process.exitCode = 1;
|
|
3385
3743
|
return;
|
|
3386
3744
|
}
|
|
3387
|
-
const essencePath =
|
|
3388
|
-
if (!
|
|
3745
|
+
const essencePath = join25(projectRoot, "decantr.essence.json");
|
|
3746
|
+
if (!existsSync24(essencePath)) {
|
|
3389
3747
|
console.error(`${RED10}No decantr.essence.json found. Run \`decantr init\` first.${RESET11}`);
|
|
3390
3748
|
process.exitCode = 1;
|
|
3391
3749
|
return;
|
|
3392
3750
|
}
|
|
3393
3751
|
let parsed;
|
|
3394
3752
|
try {
|
|
3395
|
-
parsed = JSON.parse(
|
|
3753
|
+
parsed = JSON.parse(readFileSync17(essencePath, "utf-8"));
|
|
3396
3754
|
} catch (e) {
|
|
3397
3755
|
console.error(`${RED10}Could not read essence: ${e.message}${RESET11}`);
|
|
3398
3756
|
process.exitCode = 1;
|
|
@@ -3441,7 +3799,7 @@ async function cmdThemeSwitch(themeName, args, projectRoot = process.cwd()) {
|
|
|
3441
3799
|
essence.dna.theme.mode = mode;
|
|
3442
3800
|
}
|
|
3443
3801
|
const registryClient = new RegistryClient({
|
|
3444
|
-
cacheDir:
|
|
3802
|
+
cacheDir: join25(projectRoot, ".decantr", "cache")
|
|
3445
3803
|
});
|
|
3446
3804
|
try {
|
|
3447
3805
|
const themeResult = await registryClient.fetchTheme(themeName);
|
|
@@ -3456,7 +3814,7 @@ async function cmdThemeSwitch(themeName, args, projectRoot = process.cwd()) {
|
|
|
3456
3814
|
}
|
|
3457
3815
|
} catch {
|
|
3458
3816
|
}
|
|
3459
|
-
|
|
3817
|
+
writeFileSync12(essencePath, JSON.stringify(essence, null, 2) + "\n");
|
|
3460
3818
|
console.log(`${GREEN11}Switched theme: ${oldThemeId} \u2192 ${themeName}${RESET11}`);
|
|
3461
3819
|
if (shape) console.log(` ${DIM11}Shape: ${shape}${RESET11}`);
|
|
3462
3820
|
if (mode) console.log(` ${DIM11}Mode: ${mode}${RESET11}`);
|
|
@@ -3478,10 +3836,10 @@ var CYAN6 = "\x1B[36m";
|
|
|
3478
3836
|
function ask(question, defaultValue) {
|
|
3479
3837
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
3480
3838
|
const prompt = defaultValue ? `${question} ${DIM12}(${defaultValue})${RESET12}: ` : `${question}: `;
|
|
3481
|
-
return new Promise((
|
|
3839
|
+
return new Promise((resolve5) => {
|
|
3482
3840
|
rl.question(prompt, (answer) => {
|
|
3483
3841
|
rl.close();
|
|
3484
|
-
|
|
3842
|
+
resolve5(answer.trim() || defaultValue || "");
|
|
3485
3843
|
});
|
|
3486
3844
|
});
|
|
3487
3845
|
}
|
|
@@ -3663,7 +4021,11 @@ async function runInteractivePrompts(detected, archetypes, blueprints, themes, w
|
|
|
3663
4021
|
personality: ["professional"],
|
|
3664
4022
|
features: [],
|
|
3665
4023
|
existing: workflowSeed?.existing || detected.existingEssence,
|
|
3666
|
-
workflowMode: workflowSeed?.workflowMode
|
|
4024
|
+
workflowMode: workflowSeed?.workflowMode,
|
|
4025
|
+
adoptionMode: workflowSeed?.adoptionMode,
|
|
4026
|
+
contentSource: workflowSeed?.contentSource,
|
|
4027
|
+
assistantBridge: workflowSeed?.assistantBridge,
|
|
4028
|
+
projectScope: workflowSeed?.projectScope
|
|
3667
4029
|
};
|
|
3668
4030
|
}
|
|
3669
4031
|
function parseFlags(args, detected) {
|
|
@@ -3685,6 +4047,12 @@ function parseFlags(args, detected) {
|
|
|
3685
4047
|
if (typeof args.features === "string")
|
|
3686
4048
|
options.features = args.features.split(",").map((s) => s.trim());
|
|
3687
4049
|
if (args.existing === true) options.existing = true;
|
|
4050
|
+
if (args.adoption === "contract-only" || args.adoption === "style-bridge" || args.adoption === "decantr-css") {
|
|
4051
|
+
options.adoptionMode = args.adoption;
|
|
4052
|
+
}
|
|
4053
|
+
if (args["assistant-bridge"] === "none" || args["assistant-bridge"] === "preview" || args["assistant-bridge"] === "apply") {
|
|
4054
|
+
options.assistantBridge = args["assistant-bridge"];
|
|
4055
|
+
}
|
|
3688
4056
|
return options;
|
|
3689
4057
|
}
|
|
3690
4058
|
function mergeWithDefaults(flags, detected, workflowSeed) {
|
|
@@ -3701,12 +4069,16 @@ function mergeWithDefaults(flags, detected, workflowSeed) {
|
|
|
3701
4069
|
personality: flags.personality || ["professional"],
|
|
3702
4070
|
features: flags.features || [],
|
|
3703
4071
|
existing: flags.existing || workflowSeed?.existing || detected.existingEssence,
|
|
3704
|
-
workflowMode: flags.workflowMode || workflowSeed?.workflowMode
|
|
4072
|
+
workflowMode: flags.workflowMode || workflowSeed?.workflowMode,
|
|
4073
|
+
adoptionMode: flags.adoptionMode || workflowSeed?.adoptionMode,
|
|
4074
|
+
contentSource: flags.contentSource || workflowSeed?.contentSource,
|
|
4075
|
+
assistantBridge: flags.assistantBridge || workflowSeed?.assistantBridge,
|
|
4076
|
+
projectScope: flags.projectScope || workflowSeed?.projectScope
|
|
3705
4077
|
};
|
|
3706
4078
|
}
|
|
3707
4079
|
async function runSimplifiedInit(blueprints) {
|
|
3708
4080
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
3709
|
-
const question = (q) => new Promise((
|
|
4081
|
+
const question = (q) => new Promise((resolve5) => rl.question(q, resolve5));
|
|
3710
4082
|
console.log("\n? What blueprint would you like to scaffold?\n");
|
|
3711
4083
|
console.log(" 1. Decantr default (recommended)");
|
|
3712
4084
|
console.log(" 2. Search registry...\n");
|
|
@@ -3738,8 +4110,8 @@ async function runSimplifiedInit(blueprints) {
|
|
|
3738
4110
|
}
|
|
3739
4111
|
|
|
3740
4112
|
// src/theme-commands.ts
|
|
3741
|
-
import { existsSync as
|
|
3742
|
-
import { join as
|
|
4113
|
+
import { existsSync as existsSync25, mkdirSync as mkdirSync10, readdirSync as readdirSync5, readFileSync as readFileSync18, rmSync as rmSync3, writeFileSync as writeFileSync13 } from "fs";
|
|
4114
|
+
import { join as join26 } from "path";
|
|
3743
4115
|
var REQUIRED_FIELDS = [
|
|
3744
4116
|
"$schema",
|
|
3745
4117
|
"id",
|
|
@@ -3799,20 +4171,20 @@ function validateCustomTheme(theme) {
|
|
|
3799
4171
|
};
|
|
3800
4172
|
}
|
|
3801
4173
|
function createTheme(projectRoot, id, name) {
|
|
3802
|
-
const customThemesDir =
|
|
3803
|
-
const themePath =
|
|
3804
|
-
const howToPath =
|
|
3805
|
-
|
|
3806
|
-
if (
|
|
4174
|
+
const customThemesDir = join26(projectRoot, ".decantr", "custom", "themes");
|
|
4175
|
+
const themePath = join26(customThemesDir, `${id}.json`);
|
|
4176
|
+
const howToPath = join26(customThemesDir, "how-to-theme.md");
|
|
4177
|
+
mkdirSync10(customThemesDir, { recursive: true });
|
|
4178
|
+
if (existsSync25(themePath)) {
|
|
3807
4179
|
return {
|
|
3808
4180
|
success: false,
|
|
3809
4181
|
error: `Theme "${id}" already exists at ${themePath}`
|
|
3810
4182
|
};
|
|
3811
4183
|
}
|
|
3812
4184
|
const skeleton = getThemeSkeleton(id, name);
|
|
3813
|
-
|
|
3814
|
-
if (!
|
|
3815
|
-
|
|
4185
|
+
writeFileSync13(themePath, JSON.stringify(skeleton, null, 2));
|
|
4186
|
+
if (!existsSync25(howToPath)) {
|
|
4187
|
+
writeFileSync13(howToPath, getHowToThemeDoc());
|
|
3816
4188
|
}
|
|
3817
4189
|
return {
|
|
3818
4190
|
success: true,
|
|
@@ -3820,17 +4192,17 @@ function createTheme(projectRoot, id, name) {
|
|
|
3820
4192
|
};
|
|
3821
4193
|
}
|
|
3822
4194
|
function listCustomThemes(projectRoot) {
|
|
3823
|
-
const customThemesDir =
|
|
3824
|
-
if (!
|
|
4195
|
+
const customThemesDir = join26(projectRoot, ".decantr", "custom", "themes");
|
|
4196
|
+
if (!existsSync25(customThemesDir)) {
|
|
3825
4197
|
return [];
|
|
3826
4198
|
}
|
|
3827
4199
|
const themes = [];
|
|
3828
4200
|
try {
|
|
3829
4201
|
const files = readdirSync5(customThemesDir).filter((f) => f.endsWith(".json"));
|
|
3830
4202
|
for (const file of files) {
|
|
3831
|
-
const filePath =
|
|
4203
|
+
const filePath = join26(customThemesDir, file);
|
|
3832
4204
|
try {
|
|
3833
|
-
const data = JSON.parse(
|
|
4205
|
+
const data = JSON.parse(readFileSync18(filePath, "utf-8"));
|
|
3834
4206
|
themes.push({
|
|
3835
4207
|
id: data.id || file.replace(".json", ""),
|
|
3836
4208
|
name: data.name || data.id,
|
|
@@ -3845,8 +4217,8 @@ function listCustomThemes(projectRoot) {
|
|
|
3845
4217
|
return themes;
|
|
3846
4218
|
}
|
|
3847
4219
|
function deleteTheme(projectRoot, id) {
|
|
3848
|
-
const themePath =
|
|
3849
|
-
if (!
|
|
4220
|
+
const themePath = join26(projectRoot, ".decantr", "custom", "themes", `${id}.json`);
|
|
4221
|
+
if (!existsSync25(themePath)) {
|
|
3850
4222
|
return {
|
|
3851
4223
|
success: false,
|
|
3852
4224
|
error: `Theme "${id}" not found at ${themePath}`
|
|
@@ -3863,7 +4235,7 @@ function deleteTheme(projectRoot, id) {
|
|
|
3863
4235
|
}
|
|
3864
4236
|
}
|
|
3865
4237
|
function importTheme(projectRoot, sourcePath) {
|
|
3866
|
-
if (!
|
|
4238
|
+
if (!existsSync25(sourcePath)) {
|
|
3867
4239
|
return {
|
|
3868
4240
|
success: false,
|
|
3869
4241
|
errors: [`Source file not found: ${sourcePath}`]
|
|
@@ -3871,7 +4243,7 @@ function importTheme(projectRoot, sourcePath) {
|
|
|
3871
4243
|
}
|
|
3872
4244
|
let theme;
|
|
3873
4245
|
try {
|
|
3874
|
-
theme = JSON.parse(
|
|
4246
|
+
theme = JSON.parse(readFileSync18(sourcePath, "utf-8"));
|
|
3875
4247
|
} catch (e) {
|
|
3876
4248
|
return {
|
|
3877
4249
|
success: false,
|
|
@@ -3887,20 +4259,90 @@ function importTheme(projectRoot, sourcePath) {
|
|
|
3887
4259
|
}
|
|
3888
4260
|
theme.source = "custom";
|
|
3889
4261
|
const id = theme.id;
|
|
3890
|
-
const customThemesDir =
|
|
3891
|
-
const destPath =
|
|
3892
|
-
|
|
3893
|
-
const howToPath =
|
|
3894
|
-
if (!
|
|
3895
|
-
|
|
3896
|
-
}
|
|
3897
|
-
|
|
4262
|
+
const customThemesDir = join26(projectRoot, ".decantr", "custom", "themes");
|
|
4263
|
+
const destPath = join26(customThemesDir, `${id}.json`);
|
|
4264
|
+
mkdirSync10(customThemesDir, { recursive: true });
|
|
4265
|
+
const howToPath = join26(customThemesDir, "how-to-theme.md");
|
|
4266
|
+
if (!existsSync25(howToPath)) {
|
|
4267
|
+
writeFileSync13(howToPath, getHowToThemeDoc());
|
|
4268
|
+
}
|
|
4269
|
+
writeFileSync13(destPath, JSON.stringify(theme, null, 2));
|
|
3898
4270
|
return {
|
|
3899
4271
|
success: true,
|
|
3900
4272
|
path: destPath
|
|
3901
4273
|
};
|
|
3902
4274
|
}
|
|
3903
4275
|
|
|
4276
|
+
// src/workspace.ts
|
|
4277
|
+
import { existsSync as existsSync26, readdirSync as readdirSync6, readFileSync as readFileSync19 } from "fs";
|
|
4278
|
+
import { dirname as dirname3, join as join27, resolve as resolve3 } from "path";
|
|
4279
|
+
function readPackageJson(dir) {
|
|
4280
|
+
const path = join27(dir, "package.json");
|
|
4281
|
+
if (!existsSync26(path)) return null;
|
|
4282
|
+
try {
|
|
4283
|
+
return JSON.parse(readFileSync19(path, "utf-8"));
|
|
4284
|
+
} catch {
|
|
4285
|
+
return null;
|
|
4286
|
+
}
|
|
4287
|
+
}
|
|
4288
|
+
function hasWorkspaceMarker(dir) {
|
|
4289
|
+
if (existsSync26(join27(dir, "pnpm-workspace.yaml")) || existsSync26(join27(dir, "turbo.json")) || existsSync26(join27(dir, "nx.json"))) {
|
|
4290
|
+
return true;
|
|
4291
|
+
}
|
|
4292
|
+
const pkg = readPackageJson(dir);
|
|
4293
|
+
return Boolean(pkg?.workspaces);
|
|
4294
|
+
}
|
|
4295
|
+
function findWorkspaceRoot(startDir) {
|
|
4296
|
+
let current = resolve3(startDir);
|
|
4297
|
+
while (true) {
|
|
4298
|
+
if (hasWorkspaceMarker(current)) return current;
|
|
4299
|
+
const parent = dirname3(current);
|
|
4300
|
+
if (parent === current) return null;
|
|
4301
|
+
current = parent;
|
|
4302
|
+
}
|
|
4303
|
+
}
|
|
4304
|
+
function looksLikeApp(dir) {
|
|
4305
|
+
if (existsSync26(join27(dir, "next.config.js")) || existsSync26(join27(dir, "next.config.ts")) || existsSync26(join27(dir, "next.config.mjs")) || existsSync26(join27(dir, "vite.config.ts")) || existsSync26(join27(dir, "vite.config.js")) || existsSync26(join27(dir, "angular.json")) || existsSync26(join27(dir, "svelte.config.js")) || existsSync26(join27(dir, "svelte.config.ts")) || existsSync26(join27(dir, "astro.config.mjs")) || existsSync26(join27(dir, "src")) || existsSync26(join27(dir, "app")) || existsSync26(join27(dir, "pages"))) {
|
|
4306
|
+
return true;
|
|
4307
|
+
}
|
|
4308
|
+
const pkg = readPackageJson(dir);
|
|
4309
|
+
const deps = { ...pkg?.dependencies, ...pkg?.devDependencies };
|
|
4310
|
+
return Boolean(
|
|
4311
|
+
deps.react || deps.next || deps.vue || deps.svelte || deps["@angular/core"] || deps.astro || deps.nuxt
|
|
4312
|
+
);
|
|
4313
|
+
}
|
|
4314
|
+
function listWorkspaceApps(workspaceRoot) {
|
|
4315
|
+
const candidates = [];
|
|
4316
|
+
for (const base of ["apps", "packages"]) {
|
|
4317
|
+
const baseDir = join27(workspaceRoot, base);
|
|
4318
|
+
if (!existsSync26(baseDir)) continue;
|
|
4319
|
+
for (const entry of readdirSync6(baseDir, { withFileTypes: true })) {
|
|
4320
|
+
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
|
|
4321
|
+
const candidate = join27(baseDir, entry.name);
|
|
4322
|
+
if (looksLikeApp(candidate)) {
|
|
4323
|
+
candidates.push(`${base}/${entry.name}`);
|
|
4324
|
+
}
|
|
4325
|
+
}
|
|
4326
|
+
}
|
|
4327
|
+
return candidates.sort();
|
|
4328
|
+
}
|
|
4329
|
+
function resolveWorkspaceInfo(cwd, projectArg) {
|
|
4330
|
+
const absoluteCwd = resolve3(cwd);
|
|
4331
|
+
const workspaceRoot = findWorkspaceRoot(absoluteCwd) ?? absoluteCwd;
|
|
4332
|
+
const appRoot = projectArg ? resolve3(absoluteCwd, projectArg) : absoluteCwd;
|
|
4333
|
+
const appCandidates = listWorkspaceApps(workspaceRoot);
|
|
4334
|
+
const projectScope = workspaceRoot !== appRoot || appCandidates.length > 0 ? "workspace-app" : "single-app";
|
|
4335
|
+
const requiresProjectSelection = !projectArg && workspaceRoot === absoluteCwd && appCandidates.length > 1;
|
|
4336
|
+
return {
|
|
4337
|
+
cwd: absoluteCwd,
|
|
4338
|
+
workspaceRoot,
|
|
4339
|
+
appRoot,
|
|
4340
|
+
projectScope,
|
|
4341
|
+
appCandidates,
|
|
4342
|
+
requiresProjectSelection
|
|
4343
|
+
};
|
|
4344
|
+
}
|
|
4345
|
+
|
|
3904
4346
|
// src/index.ts
|
|
3905
4347
|
var BOLD6 = "\x1B[1m";
|
|
3906
4348
|
var DIM13 = "\x1B[2m";
|
|
@@ -3997,8 +4439,19 @@ function extractPatternName(item) {
|
|
|
3997
4439
|
}
|
|
3998
4440
|
function generateGreenfieldPrompt(ctx) {
|
|
3999
4441
|
const lines = [];
|
|
4442
|
+
const usesDecantrCss = ctx.adoptionMode === "decantr-css" || !ctx.adoptionMode;
|
|
4000
4443
|
lines.push("Build this greenfield application using the Decantr design system.");
|
|
4001
4444
|
lines.push("");
|
|
4445
|
+
if (ctx.blueprint) lines.push(`Blueprint: ${ctx.blueprint}`);
|
|
4446
|
+
if (ctx.archetype) lines.push(`Primary archetype: ${ctx.archetype}`);
|
|
4447
|
+
if (ctx.theme) lines.push(`Theme: ${ctx.theme}${ctx.mode ? ` (${ctx.mode})` : ""}`);
|
|
4448
|
+
if (ctx.target) lines.push(`Target: ${ctx.target}`);
|
|
4449
|
+
if (ctx.pages.length > 0) {
|
|
4450
|
+
lines.push(
|
|
4451
|
+
`Routes/pages: ${ctx.pages.map((page) => `${page.sectionId ? `${page.sectionId}/` : ""}${page.id}:${page.shell}`).join(", ")}`
|
|
4452
|
+
);
|
|
4453
|
+
}
|
|
4454
|
+
lines.push("");
|
|
4002
4455
|
lines.push(
|
|
4003
4456
|
"This workspace is a new Decantr scaffold. Use the contract to create or extend the runtime deliberately, not to reverse-engineer a hidden starter."
|
|
4004
4457
|
);
|
|
@@ -4013,197 +4466,201 @@ function generateGreenfieldPrompt(ctx) {
|
|
|
4013
4466
|
lines.push("");
|
|
4014
4467
|
lines.push("Read in this order:");
|
|
4015
4468
|
lines.push(
|
|
4016
|
-
|
|
4469
|
+
"1. .decantr/context/scaffold-pack.md \u2014 the canonical compiled contract. Contains route plan, shell layouts, navigation, Required Theme Decorators, and project-wide execution rules."
|
|
4017
4470
|
);
|
|
4018
4471
|
lines.push(
|
|
4019
|
-
|
|
4472
|
+
"2. Before section work, read the matching .decantr/context/section-*-pack.md first, then .decantr/context/section-*.md only for extra slot/layout detail."
|
|
4020
4473
|
);
|
|
4021
4474
|
lines.push(
|
|
4022
|
-
"3. .decantr/context/
|
|
4475
|
+
"3. Before route work, read the matching .decantr/context/page-*-pack.md file. Its pattern layout and interaction checklists are contract."
|
|
4023
4476
|
);
|
|
4024
4477
|
lines.push(
|
|
4025
|
-
|
|
4478
|
+
"4. .decantr/context/scaffold.md for broader topology, route map, and voice guidance after the compact packs are understood."
|
|
4026
4479
|
);
|
|
4027
4480
|
lines.push(
|
|
4028
|
-
|
|
4481
|
+
"5. DECANTR.md as a lookup reference for atoms, treatments, decorators, interaction implementations, and guard rules. Do not let narrative docs override compiled packs."
|
|
4029
4482
|
);
|
|
4030
4483
|
lines.push("");
|
|
4031
4484
|
lines.push("\u2550\u2550\u2550 INTERACTIONS ARE CONTRACT, NOT GUIDANCE \u2550\u2550\u2550");
|
|
4032
4485
|
lines.push("");
|
|
4033
4486
|
lines.push(
|
|
4034
|
-
'Each page pack lists
|
|
4035
|
-
);
|
|
4036
|
-
lines.push(
|
|
4037
|
-
"- drag-nodes \u2192 onPointerDown/Move handlers with 4px threshold + cursor: grab/grabbing"
|
|
4038
|
-
);
|
|
4039
|
-
lines.push("- status-pulse \u2192 d-pulse class on the indicator element");
|
|
4040
|
-
lines.push(
|
|
4041
|
-
"- animate-on-mount \u2192 d-enter-fade / d-enter-slide-up / d-enter-scale on the pattern root"
|
|
4487
|
+
'Each page pack lists "Interactions (MUST implement each)" per pattern. Implement the actual behavior, not visible text saying it exists. Use DECANTR.md only to look up the canonical implementation shape when needed.'
|
|
4042
4488
|
);
|
|
4043
4489
|
lines.push(
|
|
4044
|
-
|
|
4490
|
+
"Examples: pointer handlers for dragging/panning, onWheel for zoom, onKeyDown + tabIndex for keyboard navigation, IntersectionObserver for scroll reveal, state updates for real-time indicators, and d-* motion classes where the contract calls for animation."
|
|
4045
4491
|
);
|
|
4046
|
-
lines.push(
|
|
4047
|
-
"- pan-background \u2192 pointer handlers on canvas BACKGROUND only (not nodes); translate viewport transform"
|
|
4048
|
-
);
|
|
4049
|
-
lines.push("- zoom-scroll \u2192 onWheel adjusting a scale transform, clamped [0.25, 4]");
|
|
4050
|
-
lines.push("- keyboard-navigation \u2192 onKeyDown arrow handlers + tabIndex={0}");
|
|
4051
|
-
lines.push("- focus-trap \u2192 Tab cycle inside dialog; restore focus on close");
|
|
4052
|
-
lines.push("- hover-tooltip \u2192 data-tooltip + onMouseEnter handler showing popover");
|
|
4053
|
-
lines.push(
|
|
4054
|
-
"- real-time-updates \u2192 setInterval (2-8s) updating mock state with d-pulse on changed elements"
|
|
4055
|
-
);
|
|
4056
|
-
lines.push("- scroll-reveal \u2192 IntersectionObserver (once: true) triggering d-enter-fade");
|
|
4057
|
-
lines.push("- inline-edit \u2192 controlled <input> on click; commit on blur or Enter");
|
|
4058
|
-
lines.push(
|
|
4059
|
-
"- lift-hover / scale-hover / glow-hover \u2192 matching d-*-hover treatment classes"
|
|
4060
|
-
);
|
|
4061
|
-
lines.push("");
|
|
4062
|
-
lines.push(
|
|
4063
|
-
"`decantr check --strict` FAILS the build when a declared interaction has no matching implementation. The full canonical-implementation table is in DECANTR.md."
|
|
4064
|
-
);
|
|
4065
|
-
lines.push("");
|
|
4066
|
-
lines.push("\u2550\u2550\u2550 ATOMS-FIRST FOR LAYOUT \u2014 DO NOT INLINE-STYLE \u2550\u2550\u2550");
|
|
4067
4492
|
lines.push("");
|
|
4068
4493
|
lines.push(
|
|
4069
|
-
|
|
4494
|
+
"`decantr check --strict` fails when a declared interaction has no matching implementation."
|
|
4070
4495
|
);
|
|
4071
4496
|
lines.push("");
|
|
4072
|
-
lines.push("
|
|
4073
|
-
lines.push(
|
|
4074
|
-
' \u274C style={{ display: "flex", gap: "1rem" }} \u2192 \u2705 className={css("_flex _gap4")}'
|
|
4075
|
-
);
|
|
4076
|
-
lines.push(
|
|
4077
|
-
' \u274C style={{ flexDirection: "column" }} \u2192 \u2705 className={css("_col")}'
|
|
4078
|
-
);
|
|
4079
|
-
lines.push(
|
|
4080
|
-
' \u274C style={{ padding: "1rem 1.5rem" }} \u2192 \u2705 className={css("_py4 _px6")}'
|
|
4081
|
-
);
|
|
4082
|
-
lines.push(
|
|
4083
|
-
' \u274C style={{ gridTemplateColumns: "repeat(3, 1fr)" }} \u2192 \u2705 className={css("_grid _gc3")}'
|
|
4084
|
-
);
|
|
4085
|
-
lines.push(
|
|
4086
|
-
' \u274C style={{ position: "sticky", top: 0 }} \u2192 \u2705 className={css("_sticky _t0")}'
|
|
4087
|
-
);
|
|
4088
|
-
lines.push(
|
|
4089
|
-
' \u274C style={{ width: "100%", maxWidth: "40rem" }} \u2192 \u2705 className={css("_w-full _maxw[40rem]")}'
|
|
4090
|
-
);
|
|
4497
|
+
lines.push("\u2550\u2550\u2550 STYLING ADOPTION \u2550\u2550\u2550");
|
|
4091
4498
|
lines.push("");
|
|
4092
|
-
|
|
4093
|
-
|
|
4094
|
-
|
|
4499
|
+
if (ctx.adoptionMode === "contract-only") {
|
|
4500
|
+
lines.push(
|
|
4501
|
+
"This project is contract-only. Use Decantr packs for design intent and governance, but implement through the app runtime and styling system already present or explicitly chosen for this project."
|
|
4502
|
+
);
|
|
4503
|
+
lines.push(
|
|
4504
|
+
"Do not install @decantr/css or add Decantr style files unless the adoption mode changes."
|
|
4505
|
+
);
|
|
4506
|
+
} else if (ctx.adoptionMode === "style-bridge") {
|
|
4507
|
+
lines.push(
|
|
4508
|
+
"This project uses Decantr style-bridge mode. Use generated bridge tokens as a mapping layer onto the selected styling system; @decantr/css is not required."
|
|
4509
|
+
);
|
|
4510
|
+
} else {
|
|
4511
|
+
lines.push(
|
|
4512
|
+
"Use @decantr/css atoms via `css(...)` for layout, spacing, sizing, flex/grid, position, and typography sizing. Static visual values should not live in inline style props."
|
|
4513
|
+
);
|
|
4514
|
+
}
|
|
4515
|
+
if (usesDecantrCss) {
|
|
4516
|
+
lines.push("");
|
|
4517
|
+
lines.push("Use these canonical compact atom shapes:");
|
|
4518
|
+
lines.push(
|
|
4519
|
+
"- Layout: _flex, _col, _aic, _jcc, _jcsb, _grid, _gc3, _gc[2fr_1fr], _gap4, _wrap"
|
|
4520
|
+
);
|
|
4521
|
+
lines.push(
|
|
4522
|
+
"- Spacing/sizing: _p4, _py4, _px6, _wfull, _maxw[40rem], _mxauto, _h[20rem]"
|
|
4523
|
+
);
|
|
4524
|
+
lines.push(
|
|
4525
|
+
"- Position/type: _rel, _abs, _sticky, _top0, _text2xl, _textlg, _fgmuted"
|
|
4526
|
+
);
|
|
4527
|
+
lines.push("- Responsive: _sm:gc2, _lg:gc3, _mdmax:p4, _lg:gc[1.05fr_1fr]");
|
|
4528
|
+
lines.push("");
|
|
4529
|
+
lines.push(
|
|
4530
|
+
"Use compact atom names: `_aic` not `_items-center`, `_jcsb` not `_justify-between`, `_wfull` not `_w-full`, `_top0` not `_t0`. For arbitrary values, use brackets such as `_maxw[72rem]`."
|
|
4531
|
+
);
|
|
4532
|
+
lines.push("");
|
|
4533
|
+
lines.push(
|
|
4534
|
+
'Combine atoms with treatment / decorator strings: `className={css("_flex _col _gap4") + " d-card clean-card"}`.'
|
|
4535
|
+
);
|
|
4536
|
+
} else {
|
|
4537
|
+
lines.push("");
|
|
4538
|
+
lines.push(
|
|
4539
|
+
"When packs mention atoms, treatments, decorators, or shell class names, treat them as Decantr vocabulary and map the intent into the selected runtime. Keep the literal class names only if this project has a compatible implementation."
|
|
4540
|
+
);
|
|
4541
|
+
}
|
|
4095
4542
|
lines.push("");
|
|
4096
4543
|
lines.push("Inline `style={{...}}` is ONLY acceptable for:");
|
|
4097
4544
|
lines.push(
|
|
4098
|
-
|
|
4545
|
+
" 1. CSS custom-property writes the contract requires (`--d-stagger-index`, theme color vars, etc.)"
|
|
4099
4546
|
);
|
|
4100
4547
|
lines.push(
|
|
4101
|
-
" 2. Truly dynamic geometry no atom can express (computed
|
|
4548
|
+
" 2. Truly dynamic geometry no atom can express (computed transforms, drag positions, live chart geometry)."
|
|
4102
4549
|
);
|
|
4103
4550
|
lines.push("");
|
|
4104
|
-
|
|
4105
|
-
|
|
4106
|
-
|
|
4551
|
+
if (usesDecantrCss) {
|
|
4552
|
+
lines.push(
|
|
4553
|
+
"If a component accumulates static inline visual styles, migrate them to atoms, treatments, decorators, or CSS vars. `decantr check` flags inline-style drift."
|
|
4554
|
+
);
|
|
4555
|
+
} else {
|
|
4556
|
+
lines.push(
|
|
4557
|
+
"If a component accumulates static inline visual styles, migrate them into the project styling system or mapped Decantr bridge variables. `decantr check` flags inline-style drift."
|
|
4558
|
+
);
|
|
4559
|
+
}
|
|
4107
4560
|
lines.push("");
|
|
4108
4561
|
lines.push("\u2550\u2550\u2550 TREATMENT SURFACE \u2014 USE WHAT EXISTS \u2550\u2550\u2550");
|
|
4109
4562
|
lines.push("");
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
lines.push("- Composite card: d-card, d-card-header, d-card-body, d-card-footer");
|
|
4132
|
-
lines.push(
|
|
4133
|
-
"- Motion: d-enter-fade, d-enter-slide-up, d-enter-scale, d-stagger-children, d-pulse, d-pulse-ring, d-shimmer, d-float, d-glow-hover, d-scale-hover, d-lift-hover, d-ripple"
|
|
4134
|
-
);
|
|
4135
|
-
lines.push(
|
|
4136
|
-
"- Typography: d-display, d-headline, d-title, d-subtitle, d-prose, d-body, d-caption, d-eyebrow, d-numeric, d-mono-text"
|
|
4137
|
-
);
|
|
4138
|
-
lines.push('- Elevation: d-elevate[data-level="0..5"]');
|
|
4139
|
-
lines.push(
|
|
4140
|
-
"- Data-viz: d-timeline-rail + d-timeline-dot[data-state], d-sparkline + d-sparkline-path/area, d-intent-radar + d-intent-radar-ring/axis, d-waveform, d-qr-placeholder, d-conic-ring (--d-conic-value 0..1), d-heatmap-cell"
|
|
4141
|
-
);
|
|
4563
|
+
if (usesDecantrCss) {
|
|
4564
|
+
lines.push(
|
|
4565
|
+
"60+ treatment classes ship in src/styles/treatments.css. Reach for these before inventing CSS:"
|
|
4566
|
+
);
|
|
4567
|
+
lines.push(
|
|
4568
|
+
"- Shells: d-shell + data-layout, d-shell-sidebar/main/aside/header/body/footer, d-shell-mobile-trigger/backdrop"
|
|
4569
|
+
);
|
|
4570
|
+
lines.push(
|
|
4571
|
+
"- Core UI: d-interactive, d-icon-btn, d-nav-link, d-step-chip, d-control, d-card, d-data, d-label, d-annotation"
|
|
4572
|
+
);
|
|
4573
|
+
lines.push(
|
|
4574
|
+
"- Overlays: d-modal, d-modal-backdrop, d-modal-panel, d-palette, d-tooltip, d-popover"
|
|
4575
|
+
);
|
|
4576
|
+
lines.push(
|
|
4577
|
+
"- Motion/type/data-viz: d-enter-*, d-stagger-children, d-pulse, d-lift-hover, d-display/headline/title, d-sparkline, d-conic-ring, d-heatmap-cell"
|
|
4578
|
+
);
|
|
4579
|
+
} else {
|
|
4580
|
+
lines.push(
|
|
4581
|
+
"The treatment names in the packs describe reusable UI roles. Map shells, cards, controls, overlays, motion, typography, and data-viz roles into the project styling system instead of inventing unrelated component language."
|
|
4582
|
+
);
|
|
4583
|
+
}
|
|
4142
4584
|
lines.push("");
|
|
4143
4585
|
lines.push(
|
|
4144
|
-
"
|
|
4586
|
+
"Consult DECANTR.md only when you need the full table or exact data-* attributes."
|
|
4145
4587
|
);
|
|
4146
4588
|
lines.push("");
|
|
4147
4589
|
lines.push("\u2550\u2550\u2550 THEME DECORATOR CONTRACT \u2014 APPLY OR THE THEME DOES NOT LAND \u2550\u2550\u2550");
|
|
4148
4590
|
lines.push("");
|
|
4149
4591
|
lines.push(
|
|
4150
|
-
'Each theme ships
|
|
4592
|
+
'Each theme ships namespaced decorator classes (`clean-card`, `lum-glass`, `carbon-canvas`, `paper-card`, etc.). Apply the scaffold-pack.md "Required Theme Decorators" as additive classes alongside d-* treatments so the theme lands as more than token colors.'
|
|
4151
4593
|
);
|
|
4152
4594
|
lines.push(
|
|
4153
|
-
|
|
4595
|
+
"Section packs may point back to the scaffold-pack table; scaffold-pack.md is authoritative."
|
|
4154
4596
|
);
|
|
4155
4597
|
lines.push("");
|
|
4156
4598
|
lines.push("\u2550\u2550\u2550 HARD RULES (NON-NEGOTIABLE) \u2550\u2550\u2550");
|
|
4157
4599
|
lines.push("");
|
|
4600
|
+
if (usesDecantrCss) {
|
|
4601
|
+
lines.push(
|
|
4602
|
+
'- Auth pages use `d-shell[data-layout="centered"]` with `d-shell-centered-card` around the form.'
|
|
4603
|
+
);
|
|
4604
|
+
lines.push(
|
|
4605
|
+
'- Command palette uses `d-modal[data-align="top"]` + `d-modal-backdrop` + `d-palette`; rows include Lucide icon, label, and d-kbd hotkey.'
|
|
4606
|
+
);
|
|
4607
|
+
} else {
|
|
4608
|
+
lines.push("- Auth pages use a centered shell with a focused centered-card form surface.");
|
|
4609
|
+
lines.push(
|
|
4610
|
+
"- Command palette uses an accessible modal/palette structure; rows include Lucide icon, label, and keyboard hint where the product contract calls for it."
|
|
4611
|
+
);
|
|
4612
|
+
}
|
|
4158
4613
|
lines.push(
|
|
4159
|
-
|
|
4614
|
+
"- Use lucide-react for ALL iconography (already in package.json). Pick semantic icons (Bot, Activity, Database, Search) over generic ones. Do NOT inline SVGs for icons that have Lucide equivalents."
|
|
4160
4615
|
);
|
|
4161
4616
|
lines.push(
|
|
4162
|
-
|
|
4617
|
+
"- Section Directives in section packs are execution rules for layout proportions, treatment stacks, copy conventions, and pattern fitness."
|
|
4163
4618
|
);
|
|
4164
4619
|
lines.push(
|
|
4165
|
-
"-
|
|
4620
|
+
"- Filter chip rows / tab strips use `d-step-chip[data-step-state]`, not bare `d-interactive` buttons."
|
|
4166
4621
|
);
|
|
4167
4622
|
lines.push(
|
|
4168
|
-
"-
|
|
4623
|
+
"- Do not render Decantr guard prose, implementation notes, keyboard shortcut hints, or treatment/debug labels as product UI unless a route/shell contract explicitly declares that text as user-facing."
|
|
4169
4624
|
);
|
|
4170
4625
|
lines.push(
|
|
4171
|
-
"-
|
|
4626
|
+
"- Prevent layout collisions: hero content, CTA banners, cards, footers, and sticky chrome must not overlap or clip at desktop or mobile widths."
|
|
4172
4627
|
);
|
|
4173
4628
|
lines.push("");
|
|
4174
4629
|
lines.push("\u2550\u2550\u2550 IMPLEMENTATION RULES \u2550\u2550\u2550");
|
|
4175
4630
|
lines.push(
|
|
4176
|
-
"- Do not invent routes, sections, shells, themes, or features
|
|
4177
|
-
);
|
|
4178
|
-
lines.push(
|
|
4179
|
-
"- Prefer scaffold-pack, section-pack, and page-pack guidance over broader narrative docs when they differ."
|
|
4631
|
+
"- Do not invent routes, sections, shells, themes, or features beyond the compiled packs."
|
|
4180
4632
|
);
|
|
4633
|
+
lines.push("- Prefer scaffold-pack, section-pack, and page-pack guidance over narrative docs.");
|
|
4181
4634
|
lines.push(
|
|
4182
4635
|
"- Start with the shell layouts and route structure first, then build section pages route by route."
|
|
4183
4636
|
);
|
|
4637
|
+
if (ctx.adoptionMode === "decantr-css") {
|
|
4638
|
+
lines.push("- Import src/styles/global.css, src/styles/tokens.css, and src/styles/treatments.css.");
|
|
4639
|
+
} else if (ctx.adoptionMode === "style-bridge") {
|
|
4640
|
+
lines.push("- Import src/styles/tokens.css and src/styles/decantr-bridge.css where appropriate.");
|
|
4641
|
+
} else {
|
|
4642
|
+
lines.push("- Keep styling imports aligned with the selected runtime; Decantr does not own CSS here.");
|
|
4643
|
+
}
|
|
4184
4644
|
lines.push(
|
|
4185
|
-
"-
|
|
4186
|
-
);
|
|
4187
|
-
lines.push(
|
|
4188
|
-
"- Use the existing Decantr tokens, treatments, and decorators instead of inventing a new visual system."
|
|
4645
|
+
usesDecantrCss ? "- Use the existing Decantr tokens, treatments, and decorators instead of inventing a new visual system." : "- Map Decantr tokens, treatments, and decorators into the selected runtime instead of inventing an unrelated visual system."
|
|
4189
4646
|
);
|
|
4190
4647
|
lines.push(
|
|
4191
|
-
"- If package.json, app entry files, or router/runtime files are absent, create them
|
|
4648
|
+
"- If package.json, app entry files, or router/runtime files are absent, create them for the declared target."
|
|
4192
4649
|
);
|
|
4193
4650
|
lines.push(
|
|
4194
|
-
|
|
4651
|
+
usesDecantrCss ? "- Colors, spacing, borders, shadows, gradients, and transitions should come from atoms, treatments, decorators, or CSS variables." : "- Colors, spacing, borders, shadows, gradients, and transitions should come from the project styling system or mapped Decantr variables."
|
|
4195
4652
|
);
|
|
4196
4653
|
lines.push(
|
|
4197
|
-
"- Let shells own spacing, centering, and scroll containers
|
|
4654
|
+
"- Let shells own spacing, centering, and scroll containers unless the route contract says otherwise."
|
|
4198
4655
|
);
|
|
4199
4656
|
lines.push(
|
|
4200
|
-
"- If command_palette or hotkeys are declared
|
|
4657
|
+
"- If command_palette or hotkeys are declared, implement them as real features rather than visible copy."
|
|
4201
4658
|
);
|
|
4202
4659
|
lines.push(
|
|
4203
4660
|
"- Treat declared hotkeys as interaction bindings by default, not visible navigation label text, unless the shell or route contract explicitly calls for shown shortcut hints."
|
|
4204
4661
|
);
|
|
4205
4662
|
lines.push(
|
|
4206
|
-
"- If a required decorator class is
|
|
4663
|
+
"- If a required decorator class is missing from generated CSS, report the contract gap instead of inventing a parallel system."
|
|
4207
4664
|
);
|
|
4208
4665
|
lines.push(
|
|
4209
4666
|
"- Do not modify generated context files unless the task is explicitly to regenerate or refresh Decantr context."
|
|
@@ -4213,7 +4670,7 @@ function generateGreenfieldPrompt(ctx) {
|
|
|
4213
4670
|
lines.push("- Build the shell and shared layout first.");
|
|
4214
4671
|
lines.push("- Then implement each section's pages using the matching section and page packs.");
|
|
4215
4672
|
lines.push(
|
|
4216
|
-
"- After implementation, run `decantr check` (primary gate
|
|
4673
|
+
"- After implementation, run `decantr check` (primary gate) and `decantr audit` (supplementary diagnostics)."
|
|
4217
4674
|
);
|
|
4218
4675
|
lines.push("- Fix all violations until `decantr check` exits 0.");
|
|
4219
4676
|
lines.push(
|
|
@@ -4223,14 +4680,32 @@ function generateGreenfieldPrompt(ctx) {
|
|
|
4223
4680
|
}
|
|
4224
4681
|
function generateBrownfieldPrompt(ctx) {
|
|
4225
4682
|
const lines = [];
|
|
4226
|
-
lines.push(
|
|
4683
|
+
lines.push(
|
|
4684
|
+
ctx.workflow === "hybrid-compose" ? "Compose the requested Decantr registry contract into this existing application without rebuilding it from scratch." : "Attach Decantr to this existing application without rebuilding it from scratch."
|
|
4685
|
+
);
|
|
4686
|
+
lines.push("");
|
|
4687
|
+
if (ctx.blueprint) lines.push(`Blueprint: ${ctx.blueprint}`);
|
|
4688
|
+
if (ctx.archetype) lines.push(`Primary archetype: ${ctx.archetype}`);
|
|
4689
|
+
if (ctx.theme) lines.push(`Theme: ${ctx.theme}${ctx.mode ? ` (${ctx.mode})` : ""}`);
|
|
4690
|
+
if (ctx.target) lines.push(`Target: ${ctx.target}`);
|
|
4691
|
+
if (ctx.pages.length > 0) {
|
|
4692
|
+
lines.push(
|
|
4693
|
+
`Routes/pages: ${ctx.pages.map((page) => `${page.sectionId ? `${page.sectionId}/` : ""}${page.id}:${page.shell}`).join(", ")}`
|
|
4694
|
+
);
|
|
4695
|
+
}
|
|
4227
4696
|
lines.push("");
|
|
4228
4697
|
lines.push(
|
|
4229
4698
|
"Preserve the current framework, package manager, router, build tooling, and working runtime structure unless the generated Decantr contract gives you a reviewed reason to change them."
|
|
4230
4699
|
);
|
|
4231
4700
|
lines.push("");
|
|
4232
|
-
|
|
4233
|
-
|
|
4701
|
+
if (ctx.analysisArtifacts) {
|
|
4702
|
+
lines.push("Treat .decantr/analysis.json as the factual inventory of the current app.");
|
|
4703
|
+
lines.push("Treat .decantr/init-seed.json as the recommended Decantr attach defaults.");
|
|
4704
|
+
} else {
|
|
4705
|
+
lines.push(
|
|
4706
|
+
"No Decantr analysis seed is present. Start by inventorying the app before changing runtime files."
|
|
4707
|
+
);
|
|
4708
|
+
}
|
|
4234
4709
|
lines.push(
|
|
4235
4710
|
"Treat the compiled execution-pack files as the Decantr contract you are layering onto the app."
|
|
4236
4711
|
);
|
|
@@ -4239,20 +4714,34 @@ function generateBrownfieldPrompt(ctx) {
|
|
|
4239
4714
|
);
|
|
4240
4715
|
lines.push("");
|
|
4241
4716
|
lines.push("Read in this order:");
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
|
|
4717
|
+
if (ctx.analysisArtifacts) {
|
|
4718
|
+
lines.push(
|
|
4719
|
+
"1. .decantr/analysis.json for the detected framework, routes, styling, layout, and dependencies."
|
|
4720
|
+
);
|
|
4721
|
+
lines.push("2. .decantr/init-seed.json for the intended attach defaults and workflow lane.");
|
|
4722
|
+
lines.push("3. DECANTR.md for guard rules, CSS expectations, and Decantr operating rules.");
|
|
4723
|
+
lines.push(
|
|
4724
|
+
"4. .decantr/context/scaffold-pack.md for the compact compiled shell, theme, feature, and route contract."
|
|
4725
|
+
);
|
|
4726
|
+
lines.push(
|
|
4727
|
+
"5. .decantr/context/scaffold.md for broader topology, route map, and voice guidance."
|
|
4728
|
+
);
|
|
4729
|
+
lines.push(
|
|
4730
|
+
"6. The matching section and page pack files only when you are working on those specific surfaces."
|
|
4731
|
+
);
|
|
4732
|
+
} else {
|
|
4733
|
+
lines.push("1. Inventory existing framework, routes, styling, layout, rule files, and dependencies.");
|
|
4734
|
+
lines.push("2. DECANTR.md for guard rules, adoption mode, and Decantr operating rules.");
|
|
4735
|
+
lines.push(
|
|
4736
|
+
"3. .decantr/context/scaffold-pack.md for the compact compiled shell, theme, feature, and route contract."
|
|
4737
|
+
);
|
|
4738
|
+
lines.push(
|
|
4739
|
+
"4. .decantr/context/scaffold.md for broader topology, route map, and voice guidance."
|
|
4740
|
+
);
|
|
4741
|
+
lines.push(
|
|
4742
|
+
"5. The matching section and page pack files only when you are working on those specific surfaces."
|
|
4743
|
+
);
|
|
4744
|
+
}
|
|
4256
4745
|
lines.push("");
|
|
4257
4746
|
lines.push("Implementation rules:");
|
|
4258
4747
|
lines.push(
|
|
@@ -4264,21 +4753,33 @@ function generateBrownfieldPrompt(ctx) {
|
|
|
4264
4753
|
lines.push(
|
|
4265
4754
|
"- If package.json, router files, or style files already exist, extend them deliberately instead of replacing them with a different starter shape."
|
|
4266
4755
|
);
|
|
4756
|
+
if (ctx.adoptionMode === "decantr-css") {
|
|
4757
|
+
lines.push(
|
|
4758
|
+
"- If Decantr style files are absent, add src/styles/global.css, src/styles/tokens.css, and src/styles/treatments.css in a way that fits the current app structure."
|
|
4759
|
+
);
|
|
4760
|
+
lines.push(
|
|
4761
|
+
"- Use the existing Decantr tokens, treatments, and decorators instead of inventing a parallel visual system."
|
|
4762
|
+
);
|
|
4763
|
+
} else if (ctx.adoptionMode === "style-bridge") {
|
|
4764
|
+
lines.push(
|
|
4765
|
+
"- Use Decantr bridge files as a mapping layer onto the existing styling system; do not install @decantr/css unless explicitly requested."
|
|
4766
|
+
);
|
|
4767
|
+
} else {
|
|
4768
|
+
lines.push(
|
|
4769
|
+
"- Keep the existing styling system. Do not add Decantr CSS files or @decantr/css unless the adoption mode changes."
|
|
4770
|
+
);
|
|
4771
|
+
}
|
|
4267
4772
|
lines.push(
|
|
4268
|
-
"-
|
|
4269
|
-
);
|
|
4270
|
-
lines.push(
|
|
4271
|
-
"- Use the existing Decantr tokens, treatments, and decorators instead of inventing a parallel visual system."
|
|
4272
|
-
);
|
|
4273
|
-
lines.push(
|
|
4274
|
-
"- Registry content is optional in this workflow unless the task explicitly asks for blueprint/theme/pattern enrichment."
|
|
4773
|
+
ctx.workflow === "hybrid-compose" ? "- Registry content is part of this task. Layer it onto the current app through existing route/component anchors before creating new runtime structure." : "- Registry content is optional in this workflow unless the task explicitly asks for blueprint/theme/pattern enrichment."
|
|
4275
4774
|
);
|
|
4276
4775
|
lines.push(
|
|
4277
4776
|
"- Do not invent routes, sections, shells, themes, or features that are not present in the compiled packs."
|
|
4278
4777
|
);
|
|
4279
|
-
|
|
4280
|
-
|
|
4281
|
-
|
|
4778
|
+
if (ctx.adoptionMode === "decantr-css") {
|
|
4779
|
+
lines.push(
|
|
4780
|
+
"- Do not use inline visual style values or component-scoped <style> tags as the primary styling path. Colors, spacing, borders, shadows, gradients, and transitions should come from atoms, treatments, decorators, or CSS variables."
|
|
4781
|
+
);
|
|
4782
|
+
}
|
|
4282
4783
|
lines.push(
|
|
4283
4784
|
"- Let shells own spacing, centering, and scroll containers. Preserve app structure, but remove duplicated shell responsibilities when the contract makes them explicit."
|
|
4284
4785
|
);
|
|
@@ -4309,7 +4810,7 @@ function generateBrownfieldPrompt(ctx) {
|
|
|
4309
4810
|
return lines.join("\n");
|
|
4310
4811
|
}
|
|
4311
4812
|
function generateCuratedPrompt(ctx) {
|
|
4312
|
-
return ctx.workflow === "brownfield-attach" ? generateBrownfieldPrompt(ctx) : generateGreenfieldPrompt(ctx);
|
|
4813
|
+
return ctx.workflow === "brownfield-attach" || ctx.workflow === "hybrid-compose" ? generateBrownfieldPrompt(ctx) : generateGreenfieldPrompt(ctx);
|
|
4313
4814
|
}
|
|
4314
4815
|
function getAPIClient() {
|
|
4315
4816
|
return new RegistryAPIClient3({
|
|
@@ -4323,7 +4824,7 @@ function getPublicAPIClient() {
|
|
|
4323
4824
|
});
|
|
4324
4825
|
}
|
|
4325
4826
|
function resolveUserPath(inputPath, cwd = process.cwd()) {
|
|
4326
|
-
return isAbsolute(inputPath) ? inputPath :
|
|
4827
|
+
return isAbsolute(inputPath) ? inputPath : resolve4(cwd, inputPath);
|
|
4327
4828
|
}
|
|
4328
4829
|
function extractHostedAssetPaths(indexHtml) {
|
|
4329
4830
|
const assetPaths = /* @__PURE__ */ new Set();
|
|
@@ -4336,18 +4837,18 @@ function extractHostedAssetPaths(indexHtml) {
|
|
|
4336
4837
|
return [...assetPaths];
|
|
4337
4838
|
}
|
|
4338
4839
|
function readHostedDistSnapshot(distPath) {
|
|
4339
|
-
const resolvedDistPath = distPath ? resolveUserPath(distPath) :
|
|
4340
|
-
const indexPath =
|
|
4341
|
-
if (!
|
|
4840
|
+
const resolvedDistPath = distPath ? resolveUserPath(distPath) : join28(process.cwd(), "dist");
|
|
4841
|
+
const indexPath = join28(resolvedDistPath, "index.html");
|
|
4842
|
+
if (!existsSync27(indexPath)) {
|
|
4342
4843
|
return void 0;
|
|
4343
4844
|
}
|
|
4344
|
-
const indexHtml =
|
|
4845
|
+
const indexHtml = readFileSync20(indexPath, "utf-8");
|
|
4345
4846
|
const assetPaths = extractHostedAssetPaths(indexHtml);
|
|
4346
4847
|
const assets = {};
|
|
4347
4848
|
for (const assetPath of assetPaths) {
|
|
4348
|
-
const assetFilePath =
|
|
4349
|
-
if (
|
|
4350
|
-
assets[assetPath] =
|
|
4849
|
+
const assetFilePath = join28(resolvedDistPath, assetPath.replace(/^[/\\]+/, ""));
|
|
4850
|
+
if (existsSync27(assetFilePath)) {
|
|
4851
|
+
assets[assetPath] = readFileSync20(assetFilePath, "utf-8");
|
|
4351
4852
|
}
|
|
4352
4853
|
}
|
|
4353
4854
|
return {
|
|
@@ -4362,7 +4863,7 @@ function isHostedSourceSnapshotFile(path) {
|
|
|
4362
4863
|
function readHostedSourceSnapshot(sourcePath) {
|
|
4363
4864
|
if (!sourcePath) return void 0;
|
|
4364
4865
|
const resolvedSourcePath = resolveUserPath(sourcePath);
|
|
4365
|
-
if (!
|
|
4866
|
+
if (!existsSync27(resolvedSourcePath)) {
|
|
4366
4867
|
return void 0;
|
|
4367
4868
|
}
|
|
4368
4869
|
const files = {};
|
|
@@ -4376,17 +4877,17 @@ function readHostedSourceSnapshot(sourcePath) {
|
|
|
4376
4877
|
]);
|
|
4377
4878
|
const rootPrefix = basename2(resolvedSourcePath);
|
|
4378
4879
|
const walk = (absoluteDir, relativeDir) => {
|
|
4379
|
-
for (const entry of
|
|
4880
|
+
for (const entry of readdirSync7(absoluteDir, { withFileTypes: true })) {
|
|
4380
4881
|
if (ignoredDirNames.has(entry.name)) continue;
|
|
4381
|
-
const absolutePath =
|
|
4382
|
-
const relativePath =
|
|
4882
|
+
const absolutePath = join28(absoluteDir, entry.name);
|
|
4883
|
+
const relativePath = join28(relativeDir, entry.name).replace(/\\/g, "/");
|
|
4383
4884
|
if (entry.isDirectory()) {
|
|
4384
4885
|
walk(absolutePath, relativePath);
|
|
4385
4886
|
continue;
|
|
4386
4887
|
}
|
|
4387
4888
|
if (!entry.isFile()) continue;
|
|
4388
4889
|
if (!isHostedSourceSnapshotFile(relativePath)) continue;
|
|
4389
|
-
files[relativePath] =
|
|
4890
|
+
files[relativePath] = readFileSync20(absolutePath, "utf-8");
|
|
4390
4891
|
}
|
|
4391
4892
|
};
|
|
4392
4893
|
walk(resolvedSourcePath, rootPrefix);
|
|
@@ -4537,16 +5038,16 @@ async function printRegistryIntelligenceSummary(namespace, jsonOutput = false) {
|
|
|
4537
5038
|
}
|
|
4538
5039
|
async function printHostedExecutionPackBundle(essencePath, namespace, jsonOutput = false, writeContext = false) {
|
|
4539
5040
|
const client = getPublicAPIClient();
|
|
4540
|
-
const resolvedPath = essencePath ? resolveUserPath(essencePath) :
|
|
4541
|
-
if (!
|
|
5041
|
+
const resolvedPath = essencePath ? resolveUserPath(essencePath) : join28(process.cwd(), "decantr.essence.json");
|
|
5042
|
+
if (!existsSync27(resolvedPath)) {
|
|
4542
5043
|
throw new Error(`Essence file not found at ${resolvedPath}`);
|
|
4543
5044
|
}
|
|
4544
|
-
const essence = JSON.parse(
|
|
5045
|
+
const essence = JSON.parse(readFileSync20(resolvedPath, "utf-8"));
|
|
4545
5046
|
const bundle = await client.compileExecutionPacks(essence, namespace ? { namespace } : void 0);
|
|
4546
5047
|
let writtenContextPaths = [];
|
|
4547
5048
|
if (writeContext) {
|
|
4548
|
-
const contextDir =
|
|
4549
|
-
|
|
5049
|
+
const contextDir = join28(process.cwd(), ".decantr", "context");
|
|
5050
|
+
mkdirSync11(contextDir, { recursive: true });
|
|
4550
5051
|
const written = writeExecutionPackBundleArtifacts(
|
|
4551
5052
|
contextDir,
|
|
4552
5053
|
bundle
|
|
@@ -4571,26 +5072,27 @@ async function printHostedExecutionPackBundle(essencePath, namespace, jsonOutput
|
|
|
4571
5072
|
console.log(` Sections: ${typedBundle.sections.length}`);
|
|
4572
5073
|
console.log(` Mutations: ${typedBundle.mutations.length}`);
|
|
4573
5074
|
if (writeContext) {
|
|
4574
|
-
console.log(` Context bundle: ${
|
|
5075
|
+
console.log(` Context bundle: ${join28(process.cwd(), ".decantr", "context")}`);
|
|
4575
5076
|
console.log(` Files written: ${writtenContextPaths.length}`);
|
|
4576
5077
|
}
|
|
4577
5078
|
console.log("");
|
|
4578
5079
|
console.log(`${BOLD6}Route Plan:${RESET13}`);
|
|
4579
5080
|
for (const route of typedBundle.scaffold.data.routes) {
|
|
4580
5081
|
const patterns = route.patternIds.length > 0 ? route.patternIds.join(", ") : "none";
|
|
4581
|
-
|
|
5082
|
+
const pageLabel = route.sectionId ? `${route.sectionId}/${route.pageId}` : route.pageId;
|
|
5083
|
+
console.log(` ${cyan3(route.path)} -> ${pageLabel} [${patterns}]`);
|
|
4582
5084
|
}
|
|
4583
5085
|
}
|
|
4584
5086
|
async function printHostedSelectedExecutionPack(packType, id, essencePath, namespace, jsonOutput = false, writeContext = false) {
|
|
4585
5087
|
const client = getPublicAPIClient();
|
|
4586
|
-
const resolvedPath = essencePath ? resolveUserPath(essencePath) :
|
|
4587
|
-
if (!
|
|
5088
|
+
const resolvedPath = essencePath ? resolveUserPath(essencePath) : join28(process.cwd(), "decantr.essence.json");
|
|
5089
|
+
if (!existsSync27(resolvedPath)) {
|
|
4588
5090
|
throw new Error(`Essence file not found at ${resolvedPath}`);
|
|
4589
5091
|
}
|
|
4590
5092
|
if ((packType === "section" || packType === "page" || packType === "mutation") && !id) {
|
|
4591
5093
|
throw new Error(`Pack type "${packType}" requires an id.`);
|
|
4592
5094
|
}
|
|
4593
|
-
const essence = JSON.parse(
|
|
5095
|
+
const essence = JSON.parse(readFileSync20(resolvedPath, "utf-8"));
|
|
4594
5096
|
const selected = await client.selectExecutionPack(
|
|
4595
5097
|
{
|
|
4596
5098
|
essence,
|
|
@@ -4601,17 +5103,17 @@ async function printHostedSelectedExecutionPack(packType, id, essencePath, names
|
|
|
4601
5103
|
);
|
|
4602
5104
|
let writtenContextDir = null;
|
|
4603
5105
|
if (writeContext) {
|
|
4604
|
-
const contextDir =
|
|
4605
|
-
|
|
4606
|
-
|
|
4607
|
-
|
|
5106
|
+
const contextDir = join28(process.cwd(), ".decantr", "context");
|
|
5107
|
+
mkdirSync11(contextDir, { recursive: true });
|
|
5108
|
+
writeFileSync14(
|
|
5109
|
+
join28(contextDir, "pack-manifest.json"),
|
|
4608
5110
|
JSON.stringify(selected.manifest, null, 2) + "\n"
|
|
4609
5111
|
);
|
|
4610
5112
|
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);
|
|
4611
5113
|
const markdownFile = manifestEntry?.markdown ?? `${selected.selector.packType}${selected.selector.id ? `-${selected.selector.id}` : ""}-pack.md`;
|
|
4612
5114
|
const jsonFile = manifestEntry?.json ?? `${selected.selector.packType}${selected.selector.id ? `-${selected.selector.id}` : ""}-pack.json`;
|
|
4613
|
-
|
|
4614
|
-
|
|
5115
|
+
writeFileSync14(join28(contextDir, markdownFile), selected.pack.renderedMarkdown);
|
|
5116
|
+
writeFileSync14(join28(contextDir, jsonFile), JSON.stringify(selected.pack, null, 2) + "\n");
|
|
4615
5117
|
writtenContextDir = contextDir;
|
|
4616
5118
|
}
|
|
4617
5119
|
if (jsonOutput) {
|
|
@@ -4636,20 +5138,20 @@ async function printHostedSelectedExecutionPack(packType, id, essencePath, names
|
|
|
4636
5138
|
}
|
|
4637
5139
|
async function printHostedExecutionPackManifest(essencePath, namespace, jsonOutput = false, writeContext = false) {
|
|
4638
5140
|
const client = getPublicAPIClient();
|
|
4639
|
-
const resolvedPath = essencePath ? resolveUserPath(essencePath) :
|
|
4640
|
-
if (!
|
|
5141
|
+
const resolvedPath = essencePath ? resolveUserPath(essencePath) : join28(process.cwd(), "decantr.essence.json");
|
|
5142
|
+
if (!existsSync27(resolvedPath)) {
|
|
4641
5143
|
throw new Error(`Essence file not found at ${resolvedPath}`);
|
|
4642
5144
|
}
|
|
4643
|
-
const essence = JSON.parse(
|
|
5145
|
+
const essence = JSON.parse(readFileSync20(resolvedPath, "utf-8"));
|
|
4644
5146
|
const manifest = await client.getExecutionPackManifest(
|
|
4645
5147
|
essence,
|
|
4646
5148
|
namespace ? { namespace } : void 0
|
|
4647
5149
|
);
|
|
4648
5150
|
let writtenContextDir = null;
|
|
4649
5151
|
if (writeContext) {
|
|
4650
|
-
const contextDir =
|
|
4651
|
-
|
|
4652
|
-
|
|
5152
|
+
const contextDir = join28(process.cwd(), ".decantr", "context");
|
|
5153
|
+
mkdirSync11(contextDir, { recursive: true });
|
|
5154
|
+
writeFileSync14(join28(contextDir, "pack-manifest.json"), JSON.stringify(manifest, null, 2) + "\n");
|
|
4653
5155
|
writtenContextDir = contextDir;
|
|
4654
5156
|
}
|
|
4655
5157
|
if (jsonOutput) {
|
|
@@ -4670,14 +5172,14 @@ async function printHostedExecutionPackManifest(essencePath, namespace, jsonOutp
|
|
|
4670
5172
|
}
|
|
4671
5173
|
}
|
|
4672
5174
|
async function hydrateHostedExecutionPacksIfMissing(projectRoot, namespace = "@official") {
|
|
4673
|
-
const contextDir =
|
|
4674
|
-
const reviewPackPath =
|
|
4675
|
-
const manifestPath =
|
|
4676
|
-
if (
|
|
5175
|
+
const contextDir = join28(projectRoot, ".decantr", "context");
|
|
5176
|
+
const reviewPackPath = join28(contextDir, "review-pack.json");
|
|
5177
|
+
const manifestPath = join28(contextDir, "pack-manifest.json");
|
|
5178
|
+
if (existsSync27(reviewPackPath) && existsSync27(manifestPath)) {
|
|
4677
5179
|
return { attempted: false, hydrated: false };
|
|
4678
5180
|
}
|
|
4679
|
-
const essencePath =
|
|
4680
|
-
if (!
|
|
5181
|
+
const essencePath = join28(projectRoot, "decantr.essence.json");
|
|
5182
|
+
if (!existsSync27(essencePath)) {
|
|
4681
5183
|
return { attempted: false, hydrated: false };
|
|
4682
5184
|
}
|
|
4683
5185
|
const reviewHydration = await hydrateHostedReviewPackIfMissing(projectRoot, namespace);
|
|
@@ -4686,9 +5188,9 @@ async function hydrateHostedExecutionPacksIfMissing(projectRoot, namespace = "@o
|
|
|
4686
5188
|
}
|
|
4687
5189
|
try {
|
|
4688
5190
|
const client = getPublicAPIClient();
|
|
4689
|
-
const essence = JSON.parse(
|
|
5191
|
+
const essence = JSON.parse(readFileSync20(essencePath, "utf-8"));
|
|
4690
5192
|
const bundle = await client.compileExecutionPacks(essence, { namespace });
|
|
4691
|
-
|
|
5193
|
+
mkdirSync11(contextDir, { recursive: true });
|
|
4692
5194
|
writeExecutionPackBundleArtifacts(contextDir, bundle);
|
|
4693
5195
|
return { attempted: true, hydrated: true, scope: "bundle" };
|
|
4694
5196
|
} catch {
|
|
@@ -4696,19 +5198,19 @@ async function hydrateHostedExecutionPacksIfMissing(projectRoot, namespace = "@o
|
|
|
4696
5198
|
}
|
|
4697
5199
|
}
|
|
4698
5200
|
async function hydrateHostedReviewPackIfMissing(projectRoot, namespace = "@official") {
|
|
4699
|
-
const contextDir =
|
|
4700
|
-
const reviewPackPath =
|
|
4701
|
-
const manifestPath =
|
|
4702
|
-
if (
|
|
5201
|
+
const contextDir = join28(projectRoot, ".decantr", "context");
|
|
5202
|
+
const reviewPackPath = join28(contextDir, "review-pack.json");
|
|
5203
|
+
const manifestPath = join28(contextDir, "pack-manifest.json");
|
|
5204
|
+
if (existsSync27(reviewPackPath) && existsSync27(manifestPath)) {
|
|
4703
5205
|
return { attempted: false, hydrated: false };
|
|
4704
5206
|
}
|
|
4705
|
-
const essencePath =
|
|
4706
|
-
if (!
|
|
5207
|
+
const essencePath = join28(projectRoot, "decantr.essence.json");
|
|
5208
|
+
if (!existsSync27(essencePath)) {
|
|
4707
5209
|
return { attempted: false, hydrated: false };
|
|
4708
5210
|
}
|
|
4709
5211
|
try {
|
|
4710
5212
|
const client = getPublicAPIClient();
|
|
4711
|
-
const essence = JSON.parse(
|
|
5213
|
+
const essence = JSON.parse(readFileSync20(essencePath, "utf-8"));
|
|
4712
5214
|
const selected = await client.selectExecutionPack(
|
|
4713
5215
|
{
|
|
4714
5216
|
essence,
|
|
@@ -4716,14 +5218,14 @@ async function hydrateHostedReviewPackIfMissing(projectRoot, namespace = "@offic
|
|
|
4716
5218
|
},
|
|
4717
5219
|
{ namespace }
|
|
4718
5220
|
);
|
|
4719
|
-
|
|
4720
|
-
|
|
4721
|
-
|
|
4722
|
-
|
|
5221
|
+
mkdirSync11(contextDir, { recursive: true });
|
|
5222
|
+
writeFileSync14(join28(contextDir, "review-pack.md"), selected.pack.renderedMarkdown);
|
|
5223
|
+
writeFileSync14(
|
|
5224
|
+
join28(contextDir, "review-pack.json"),
|
|
4723
5225
|
JSON.stringify(selected.pack, null, 2) + "\n"
|
|
4724
5226
|
);
|
|
4725
|
-
if (!
|
|
4726
|
-
|
|
5227
|
+
if (!existsSync27(manifestPath)) {
|
|
5228
|
+
writeFileSync14(manifestPath, JSON.stringify(selected.manifest, null, 2) + "\n");
|
|
4727
5229
|
}
|
|
4728
5230
|
return { attempted: true, hydrated: true, scope: "review" };
|
|
4729
5231
|
} catch {
|
|
@@ -4733,17 +5235,17 @@ async function hydrateHostedReviewPackIfMissing(projectRoot, namespace = "@offic
|
|
|
4733
5235
|
async function printHostedFileCritique(sourcePath, namespace, jsonOutput = false, essencePath, treatmentsPath) {
|
|
4734
5236
|
const client = getPublicAPIClient();
|
|
4735
5237
|
const resolvedSourcePath = resolveUserPath(sourcePath);
|
|
4736
|
-
const resolvedEssencePath = essencePath ? resolveUserPath(essencePath) :
|
|
4737
|
-
const resolvedTreatmentsPath = treatmentsPath ? resolveUserPath(treatmentsPath) :
|
|
4738
|
-
if (!
|
|
5238
|
+
const resolvedEssencePath = essencePath ? resolveUserPath(essencePath) : join28(process.cwd(), "decantr.essence.json");
|
|
5239
|
+
const resolvedTreatmentsPath = treatmentsPath ? resolveUserPath(treatmentsPath) : join28(process.cwd(), "src", "styles", "treatments.css");
|
|
5240
|
+
if (!existsSync27(resolvedSourcePath)) {
|
|
4739
5241
|
throw new Error(`Source file not found at ${resolvedSourcePath}`);
|
|
4740
5242
|
}
|
|
4741
|
-
if (!
|
|
5243
|
+
if (!existsSync27(resolvedEssencePath)) {
|
|
4742
5244
|
throw new Error(`Essence file not found at ${resolvedEssencePath}`);
|
|
4743
5245
|
}
|
|
4744
|
-
const code =
|
|
4745
|
-
const essence = JSON.parse(
|
|
4746
|
-
const treatmentsCss =
|
|
5246
|
+
const code = readFileSync20(resolvedSourcePath, "utf-8");
|
|
5247
|
+
const essence = JSON.parse(readFileSync20(resolvedEssencePath, "utf-8"));
|
|
5248
|
+
const treatmentsCss = existsSync27(resolvedTreatmentsPath) ? readFileSync20(resolvedTreatmentsPath, "utf-8") : void 0;
|
|
4747
5249
|
const report = await client.critiqueFile(
|
|
4748
5250
|
{
|
|
4749
5251
|
essence,
|
|
@@ -4767,11 +5269,11 @@ async function printHostedFileCritique(sourcePath, namespace, jsonOutput = false
|
|
|
4767
5269
|
}
|
|
4768
5270
|
async function printHostedProjectAudit(namespace, jsonOutput = false, essencePath, distPath, sourcesPath) {
|
|
4769
5271
|
const client = getPublicAPIClient();
|
|
4770
|
-
const resolvedEssencePath = essencePath ? resolveUserPath(essencePath) :
|
|
4771
|
-
if (!
|
|
5272
|
+
const resolvedEssencePath = essencePath ? resolveUserPath(essencePath) : join28(process.cwd(), "decantr.essence.json");
|
|
5273
|
+
if (!existsSync27(resolvedEssencePath)) {
|
|
4772
5274
|
throw new Error(`Essence file not found at ${resolvedEssencePath}`);
|
|
4773
5275
|
}
|
|
4774
|
-
const essence = JSON.parse(
|
|
5276
|
+
const essence = JSON.parse(readFileSync20(resolvedEssencePath, "utf-8"));
|
|
4775
5277
|
const dist = readHostedDistSnapshot(distPath);
|
|
4776
5278
|
const sources = readHostedSourceSnapshot(sourcesPath);
|
|
4777
5279
|
const report = await client.auditProject(
|
|
@@ -4789,7 +5291,7 @@ async function printHostedProjectAudit(namespace, jsonOutput = false, essencePat
|
|
|
4789
5291
|
console.log(heading2("Hosted Project Audit"));
|
|
4790
5292
|
console.log(` Essence: ${resolvedEssencePath}`);
|
|
4791
5293
|
console.log(
|
|
4792
|
-
` Dist snapshot: ${dist ? distPath ? resolveUserPath(distPath) :
|
|
5294
|
+
` Dist snapshot: ${dist ? distPath ? resolveUserPath(distPath) : join28(process.cwd(), "dist") : "none"}`
|
|
4793
5295
|
);
|
|
4794
5296
|
console.log(
|
|
4795
5297
|
` Source snapshot: ${sources && sourcesPath ? resolveUserPath(sourcesPath) : "none"}`
|
|
@@ -4870,25 +5372,25 @@ async function cmdGet(type, id) {
|
|
|
4870
5372
|
}
|
|
4871
5373
|
const apiType = CONTENT_TYPE_TO_API_CONTENT_TYPE3[type];
|
|
4872
5374
|
const registryClient = new RegistryClient({
|
|
4873
|
-
cacheDir:
|
|
5375
|
+
cacheDir: join28(process.cwd(), ".decantr", "cache")
|
|
4874
5376
|
});
|
|
4875
5377
|
const result = await registryClient.fetchContentItem(apiType, id);
|
|
4876
5378
|
if (result) {
|
|
4877
5379
|
console.log(JSON.stringify(result.data, null, 2));
|
|
4878
5380
|
return;
|
|
4879
5381
|
}
|
|
4880
|
-
const currentDir =
|
|
5382
|
+
const currentDir = dirname4(fileURLToPath2(import.meta.url));
|
|
4881
5383
|
const bundledCandidates = [
|
|
4882
|
-
|
|
5384
|
+
join28(currentDir, "bundled", apiType, `${id}.json`),
|
|
4883
5385
|
// Running from src/
|
|
4884
|
-
|
|
5386
|
+
join28(currentDir, "..", "src", "bundled", apiType, `${id}.json`),
|
|
4885
5387
|
// Running from dist/
|
|
4886
|
-
|
|
5388
|
+
join28(currentDir, "..", "bundled", apiType, `${id}.json`)
|
|
4887
5389
|
// Alternative dist layout
|
|
4888
5390
|
];
|
|
4889
|
-
const bundledPath = bundledCandidates.find((p) =>
|
|
5391
|
+
const bundledPath = bundledCandidates.find((p) => existsSync27(p)) || null;
|
|
4890
5392
|
if (bundledPath) {
|
|
4891
|
-
const data = JSON.parse(
|
|
5393
|
+
const data = JSON.parse(readFileSync20(bundledPath, "utf-8"));
|
|
4892
5394
|
console.log(JSON.stringify(data, null, 2));
|
|
4893
5395
|
return;
|
|
4894
5396
|
}
|
|
@@ -4897,10 +5399,10 @@ async function cmdGet(type, id) {
|
|
|
4897
5399
|
return;
|
|
4898
5400
|
}
|
|
4899
5401
|
async function cmdValidate(path) {
|
|
4900
|
-
const essencePath = path ||
|
|
5402
|
+
const essencePath = path || join28(process.cwd(), "decantr.essence.json");
|
|
4901
5403
|
let raw;
|
|
4902
5404
|
try {
|
|
4903
|
-
raw =
|
|
5405
|
+
raw = readFileSync20(essencePath, "utf-8");
|
|
4904
5406
|
} catch {
|
|
4905
5407
|
console.error(error3(`Could not read ${essencePath}`));
|
|
4906
5408
|
process.exitCode = 1;
|
|
@@ -4969,7 +5471,7 @@ async function cmdList(type, sort, recommended, intelligenceSource) {
|
|
|
4969
5471
|
return;
|
|
4970
5472
|
}
|
|
4971
5473
|
const registryClient = new RegistryClient({
|
|
4972
|
-
cacheDir:
|
|
5474
|
+
cacheDir: join28(process.cwd(), ".decantr", "cache")
|
|
4973
5475
|
});
|
|
4974
5476
|
const result = await registryClient.fetchContentList(
|
|
4975
5477
|
type,
|
|
@@ -5016,11 +5518,17 @@ async function cmdList(type, sort, recommended, intelligenceSource) {
|
|
|
5016
5518
|
}
|
|
5017
5519
|
}
|
|
5018
5520
|
async function cmdInit(args) {
|
|
5019
|
-
const
|
|
5521
|
+
const workspaceInfo = resolveWorkspaceInfo(process.cwd(), args.project);
|
|
5522
|
+
if (args.yes && workspaceInfo.requiresProjectSelection) {
|
|
5523
|
+
console.log(error3("This looks like a workspace root with multiple app candidates."));
|
|
5524
|
+
console.log(dim3(`Use --project=<path>. Candidates: ${workspaceInfo.appCandidates.join(", ")}`));
|
|
5525
|
+
process.exitCode = 1;
|
|
5526
|
+
return;
|
|
5527
|
+
}
|
|
5528
|
+
const projectRoot = workspaceInfo.appRoot;
|
|
5020
5529
|
console.log(heading2("Decantr Project Setup"));
|
|
5021
5530
|
const detected = detectProject(projectRoot);
|
|
5022
5531
|
const workflowSeed = readBrownfieldInitSeed(projectRoot);
|
|
5023
|
-
const brownfieldAttach = Boolean(workflowSeed) || hasExistingProjectFootprint(detected);
|
|
5024
5532
|
if (workflowSeed) {
|
|
5025
5533
|
console.log(dim3(" Found .decantr/init-seed.json brownfield guidance."));
|
|
5026
5534
|
}
|
|
@@ -5035,11 +5543,27 @@ async function cmdInit(args) {
|
|
|
5035
5543
|
const requestedBlueprint = Boolean(args.blueprint);
|
|
5036
5544
|
const requestedArchetype = Boolean(args.archetype);
|
|
5037
5545
|
const requestedTheme = Boolean(args.theme);
|
|
5546
|
+
const policy = resolveWorkflowPolicy({
|
|
5547
|
+
command: "init",
|
|
5548
|
+
detected,
|
|
5549
|
+
workflowSeed,
|
|
5550
|
+
requestedWorkflow: args.workflow,
|
|
5551
|
+
requestedAdoption: args.adoption,
|
|
5552
|
+
requestedAssistantBridge: args["assistant-bridge"],
|
|
5553
|
+
requestedBlueprint,
|
|
5554
|
+
requestedArchetype,
|
|
5555
|
+
requestedTheme,
|
|
5556
|
+
explicitExisting: args.existing,
|
|
5557
|
+
offline: args.offline,
|
|
5558
|
+
projectScope: workspaceInfo.projectScope
|
|
5559
|
+
});
|
|
5560
|
+
const preferContractOnly = policy.contentSource === "none" && (policy.workflowMode === "brownfield-attach" || policy.workflowMode === "greenfield-contract-only");
|
|
5561
|
+
const shouldUseRegistry = !preferContractOnly || policy.registryRequired;
|
|
5038
5562
|
let offlineSeed = {
|
|
5039
5563
|
seeded: false,
|
|
5040
5564
|
strategy: null
|
|
5041
5565
|
};
|
|
5042
|
-
if (args.offline) {
|
|
5566
|
+
if (args.offline && shouldUseRegistry) {
|
|
5043
5567
|
offlineSeed = seedOfflineRegistry(projectRoot, projectRoot);
|
|
5044
5568
|
if (offlineSeed.seeded) {
|
|
5045
5569
|
console.log(dim3(` Seeded offline registry content from ${offlineSeed.strategy}.`));
|
|
@@ -5057,11 +5581,12 @@ async function cmdInit(args) {
|
|
|
5057
5581
|
}
|
|
5058
5582
|
}
|
|
5059
5583
|
const registryClient = new RegistryClient({
|
|
5060
|
-
cacheDir:
|
|
5584
|
+
cacheDir: join28(projectRoot, ".decantr", "cache"),
|
|
5061
5585
|
apiUrl: args.registry,
|
|
5062
|
-
offline: args.offline
|
|
5586
|
+
offline: args.offline,
|
|
5587
|
+
projectRoot
|
|
5063
5588
|
});
|
|
5064
|
-
const apiAvailable = await registryClient.checkApiAvailability();
|
|
5589
|
+
const apiAvailable = shouldUseRegistry ? await registryClient.checkApiAvailability() : false;
|
|
5065
5590
|
if (!apiAvailable && !args.offline && (requestedBlueprint || requestedArchetype)) {
|
|
5066
5591
|
const fallbackSeed = seedOfflineRegistry(projectRoot, projectRoot);
|
|
5067
5592
|
if (fallbackSeed.seeded) {
|
|
@@ -5071,18 +5596,21 @@ async function cmdInit(args) {
|
|
|
5071
5596
|
}
|
|
5072
5597
|
let selectedBlueprint = "default";
|
|
5073
5598
|
let registrySource = "cache";
|
|
5074
|
-
const preferContractOnly = brownfieldAttach && !requestedBlueprint && !requestedArchetype;
|
|
5075
|
-
const workflowMode = workflowSeed || brownfieldAttach && !requestedBlueprint && !requestedArchetype ? "brownfield-attach" : "greenfield-scaffold";
|
|
5076
5599
|
if (args.yes) {
|
|
5077
5600
|
selectedBlueprint = args.blueprint || "default";
|
|
5078
|
-
} else if (!apiAvailable) {
|
|
5601
|
+
} else if (shouldUseRegistry && !apiAvailable) {
|
|
5079
5602
|
if (!args.blueprint) {
|
|
5080
5603
|
console.log(`
|
|
5081
5604
|
${YELLOW9}You're offline. Scaffolding minimal Decantr project.${RESET13}`);
|
|
5082
5605
|
console.log(
|
|
5083
5606
|
dim3("Run `decantr sync` or `decantr upgrade` when online to pull full registry content.\n")
|
|
5084
5607
|
);
|
|
5085
|
-
const result2 = scaffoldMinimal(projectRoot
|
|
5608
|
+
const result2 = scaffoldMinimal(projectRoot, {
|
|
5609
|
+
workflowMode: policy.workflowMode,
|
|
5610
|
+
adoptionMode: policy.adoptionMode,
|
|
5611
|
+
contentSource: policy.contentSource,
|
|
5612
|
+
assistantBridge: policy.assistantBridge
|
|
5613
|
+
});
|
|
5086
5614
|
console.log(success3("\nProject scaffolded (minimal/offline)!\n"));
|
|
5087
5615
|
console.log(" Files created:");
|
|
5088
5616
|
console.log(` ${cyan3("decantr.essence.json")} Design specification`);
|
|
@@ -5098,7 +5626,7 @@ ${YELLOW9}You're offline. Scaffolding minimal Decantr project.${RESET13}`);
|
|
|
5098
5626
|
` 2. Run ${cyan3("decantr refresh")} after syncing to generate scaffold, section, and page packs`
|
|
5099
5627
|
);
|
|
5100
5628
|
console.log(
|
|
5101
|
-
` 3. Read ${cyan3("
|
|
5629
|
+
` 3. Read ${cyan3(".decantr/context/scaffold-pack.md")} first, then use ${cyan3("DECANTR.md")} as a lookup reference`
|
|
5102
5630
|
);
|
|
5103
5631
|
console.log(
|
|
5104
5632
|
` 4. Use ${cyan3("decantr create <type> <name>")} to create custom content if needed`
|
|
@@ -5123,22 +5651,22 @@ ${YELLOW9}You're offline. Scaffolding minimal Decantr project.${RESET13}`);
|
|
|
5123
5651
|
${YELLOW9}You're offline. Scaffolding Decantr default.${RESET13}`);
|
|
5124
5652
|
console.log(dim3("Run `decantr upgrade` when online, or visit decantr.ai/registry\n"));
|
|
5125
5653
|
selectedBlueprint = "default";
|
|
5126
|
-
} else if (
|
|
5654
|
+
} else if (shouldUseRegistry) {
|
|
5127
5655
|
console.log(dim3("Fetching registry content..."));
|
|
5128
5656
|
const blueprintsResult2 = await registryClient.fetchBlueprints();
|
|
5129
5657
|
registrySource = blueprintsResult2.source.type === "api" ? "api" : "cache";
|
|
5130
5658
|
const { selectedBlueprint: selected } = await runSimplifiedInit(blueprintsResult2.data.items);
|
|
5131
5659
|
selectedBlueprint = selected || "default";
|
|
5132
5660
|
}
|
|
5133
|
-
const archetypesResult = await registryClient.fetchArchetypes();
|
|
5134
|
-
const blueprintsResult = await registryClient.fetchBlueprints();
|
|
5135
|
-
const themesResult = await registryClient.fetchThemes();
|
|
5136
|
-
if (archetypesResult
|
|
5661
|
+
const archetypesResult = shouldUseRegistry ? await registryClient.fetchArchetypes() : null;
|
|
5662
|
+
const blueprintsResult = shouldUseRegistry ? await registryClient.fetchBlueprints() : null;
|
|
5663
|
+
const themesResult = shouldUseRegistry ? await registryClient.fetchThemes() : null;
|
|
5664
|
+
if (archetypesResult?.source.type === "api") {
|
|
5137
5665
|
registrySource = "api";
|
|
5138
5666
|
}
|
|
5139
|
-
const archetypes = archetypesResult
|
|
5140
|
-
const blueprints = blueprintsResult
|
|
5141
|
-
const themes = themesResult
|
|
5667
|
+
const archetypes = archetypesResult?.data.items ?? [];
|
|
5668
|
+
const blueprints = blueprintsResult?.data.items ?? [];
|
|
5669
|
+
const themes = themesResult?.data.items ?? [];
|
|
5142
5670
|
let options;
|
|
5143
5671
|
const userExplicit = {
|
|
5144
5672
|
theme: Boolean(args.theme),
|
|
@@ -5146,7 +5674,10 @@ ${YELLOW9}You're offline. Scaffolding Decantr default.${RESET13}`);
|
|
|
5146
5674
|
shape: Boolean(args.shape),
|
|
5147
5675
|
personality: Boolean(args.personality)
|
|
5148
5676
|
};
|
|
5149
|
-
if (
|
|
5677
|
+
if (preferContractOnly) {
|
|
5678
|
+
const flags = parseFlags(args, detected);
|
|
5679
|
+
options = mergeWithDefaults(flags, detected, workflowSeed ?? void 0);
|
|
5680
|
+
} else if (args.yes || selectedBlueprint !== "default") {
|
|
5150
5681
|
const flags = parseFlags(args, detected);
|
|
5151
5682
|
flags.blueprint = selectedBlueprint !== "default" ? selectedBlueprint : flags.blueprint;
|
|
5152
5683
|
options = mergeWithDefaults(flags, detected, workflowSeed ?? void 0);
|
|
@@ -5163,14 +5694,22 @@ ${YELLOW9}You're offline. Scaffolding Decantr default.${RESET13}`);
|
|
|
5163
5694
|
userExplicit.shape = true;
|
|
5164
5695
|
userExplicit.personality = true;
|
|
5165
5696
|
}
|
|
5166
|
-
options.workflowMode = workflowMode;
|
|
5697
|
+
options.workflowMode = policy.workflowMode;
|
|
5698
|
+
options.adoptionMode = policy.adoptionMode;
|
|
5699
|
+
options.contentSource = policy.contentSource;
|
|
5700
|
+
options.assistantBridge = policy.assistantBridge;
|
|
5701
|
+
options.projectScope = policy.projectScope;
|
|
5702
|
+
options.workspaceRoot = workspaceInfo.workspaceRoot;
|
|
5703
|
+
options.appRoot = workspaceInfo.appRoot;
|
|
5704
|
+
options.analysisArtifacts = policy.hasAnalysisArtifacts;
|
|
5705
|
+
options.adapterId = resolveBootstrapTarget(options.target).adapterId;
|
|
5167
5706
|
let topologyMarkdown = "";
|
|
5168
5707
|
let archetypeData;
|
|
5169
5708
|
let composedSections;
|
|
5170
5709
|
let routeMap;
|
|
5171
5710
|
let patternSpecs;
|
|
5172
5711
|
let blueprintData;
|
|
5173
|
-
if (options.blueprint) {
|
|
5712
|
+
if (shouldUseRegistry && options.blueprint) {
|
|
5174
5713
|
const blueprintResult = await registryClient.fetchBlueprint(options.blueprint);
|
|
5175
5714
|
if (blueprintResult) {
|
|
5176
5715
|
const blueprint = blueprintResult.data;
|
|
@@ -5293,7 +5832,7 @@ ${YELLOW9}You're offline. Scaffolding Decantr default.${RESET13}`);
|
|
|
5293
5832
|
`${YELLOW9} Warning: Could not fetch blueprint "${options.blueprint}". Using defaults.${RESET13}`
|
|
5294
5833
|
);
|
|
5295
5834
|
}
|
|
5296
|
-
} else if (options.archetype) {
|
|
5835
|
+
} else if (shouldUseRegistry && options.archetype) {
|
|
5297
5836
|
const archetypeResult = await registryClient.fetchArchetype(options.archetype);
|
|
5298
5837
|
if (archetypeResult) {
|
|
5299
5838
|
archetypeData = mapRegistryArchetypeToArchetypeData(archetypeResult.data);
|
|
@@ -5310,7 +5849,7 @@ ${YELLOW9}You're offline. Scaffolding Decantr default.${RESET13}`);
|
|
|
5310
5849
|
}
|
|
5311
5850
|
}
|
|
5312
5851
|
let themeData;
|
|
5313
|
-
if (options.theme) {
|
|
5852
|
+
if (shouldUseRegistry && options.theme) {
|
|
5314
5853
|
const themeResult = await registryClient.fetchTheme(options.theme);
|
|
5315
5854
|
if (themeResult) {
|
|
5316
5855
|
themeData = mapRegistryThemeToThemeData(themeResult.data);
|
|
@@ -5342,6 +5881,19 @@ ${YELLOW9}You're offline. Scaffolding Decantr default.${RESET13}`);
|
|
|
5342
5881
|
patternSpecs,
|
|
5343
5882
|
blueprintData
|
|
5344
5883
|
);
|
|
5884
|
+
let assistantBridgePath = null;
|
|
5885
|
+
let appliedRuleFiles = [];
|
|
5886
|
+
if (policy.assistantBridge === "preview" || policy.assistantBridge === "apply") {
|
|
5887
|
+
assistantBridgePath = writeAssistantBridgePreview({
|
|
5888
|
+
projectRoot,
|
|
5889
|
+
detected,
|
|
5890
|
+
workflowMode: policy.workflowMode,
|
|
5891
|
+
assistantBridge: policy.assistantBridge
|
|
5892
|
+
});
|
|
5893
|
+
}
|
|
5894
|
+
if (policy.assistantBridge === "apply") {
|
|
5895
|
+
appliedRuleFiles = applyAssistantBridge(projectRoot, detected);
|
|
5896
|
+
}
|
|
5345
5897
|
console.log(success3("\nProject scaffolded!\n"));
|
|
5346
5898
|
console.log(" Files created:");
|
|
5347
5899
|
console.log(` ${cyan3("decantr.essence.json")} Design specification`);
|
|
@@ -5350,7 +5902,13 @@ ${YELLOW9}You're offline. Scaffolding Decantr default.${RESET13}`);
|
|
|
5350
5902
|
if (result.gitignoreUpdated) {
|
|
5351
5903
|
console.log(` ${dim3(".gitignore updated")}`);
|
|
5352
5904
|
}
|
|
5353
|
-
if (
|
|
5905
|
+
if (assistantBridgePath) {
|
|
5906
|
+
console.log(` ${cyan3(".decantr/context/assistant-bridge.md")} Assistant bridge preview`);
|
|
5907
|
+
}
|
|
5908
|
+
if (appliedRuleFiles.length > 0) {
|
|
5909
|
+
console.log(` ${dim3(`Rule bridge applied: ${appliedRuleFiles.join(", ")}`)}`);
|
|
5910
|
+
}
|
|
5911
|
+
if (!existsSync27(join28(projectRoot, "package.json"))) {
|
|
5354
5912
|
console.log("");
|
|
5355
5913
|
console.log(
|
|
5356
5914
|
dim3(` Note: ${cyan3("decantr init")} created Decantr contract/context files only.`)
|
|
@@ -5363,14 +5921,15 @@ ${YELLOW9}You're offline. Scaffolding Decantr default.${RESET13}`);
|
|
|
5363
5921
|
}
|
|
5364
5922
|
console.log("");
|
|
5365
5923
|
console.log(" Next steps:");
|
|
5366
|
-
console.log(" 1. Read
|
|
5924
|
+
console.log(" 1. Read .decantr/context/scaffold-pack.md first as the primary compiled contract");
|
|
5367
5925
|
console.log(
|
|
5368
|
-
" 2. Read .decantr/context/scaffold
|
|
5926
|
+
" 2. Read .decantr/context/scaffold.md for broader topology, route map, and voice guidance"
|
|
5369
5927
|
);
|
|
5370
5928
|
console.log(" 3. Read the matching section and page packs before implementing each route");
|
|
5371
|
-
console.log(" 4.
|
|
5372
|
-
console.log(" 5.
|
|
5373
|
-
console.log(" 6.
|
|
5929
|
+
console.log(" 4. Use DECANTR.md as a lookup reference for atoms, treatments, and guard rules");
|
|
5930
|
+
console.log(" 5. Build the shell and route structure first, then implement the pages");
|
|
5931
|
+
console.log(" 6. Run decantr check and decantr audit after implementation");
|
|
5932
|
+
console.log(" 7. Explore more at decantr.ai/registry");
|
|
5374
5933
|
console.log("");
|
|
5375
5934
|
console.log(" Commands:");
|
|
5376
5935
|
console.log(` ${cyan3("decantr status")} Project health`);
|
|
@@ -5380,7 +5939,7 @@ ${YELLOW9}You're offline. Scaffolding Decantr default.${RESET13}`);
|
|
|
5380
5939
|
console.log(` ${cyan3("decantr upgrade")} Update to latest patterns`);
|
|
5381
5940
|
console.log(` ${cyan3("decantr check")} Detect drift issues`);
|
|
5382
5941
|
console.log(` ${cyan3("decantr migrate")} Migrate v2 essence to v3`);
|
|
5383
|
-
const essenceContent =
|
|
5942
|
+
const essenceContent = readFileSync20(result.essencePath, "utf-8");
|
|
5384
5943
|
const essence = JSON.parse(essenceContent);
|
|
5385
5944
|
if (essence.version !== "3.1.0") {
|
|
5386
5945
|
const validation = validateEssence2(essence);
|
|
@@ -5393,11 +5952,12 @@ Validation warnings: ${validation.errors.join(", ")}`));
|
|
|
5393
5952
|
let promptPages;
|
|
5394
5953
|
if (isV36(essence)) {
|
|
5395
5954
|
const allPages = essence.blueprint.sections ? essence.blueprint.sections.flatMap(
|
|
5396
|
-
(s) => s.pages.map((p) => ({ ...p, _shell: s.shell }))
|
|
5955
|
+
(s) => s.pages.map((p) => ({ ...p, _sectionId: s.id, _shell: s.shell }))
|
|
5397
5956
|
) : essence.blueprint.pages || [];
|
|
5398
5957
|
promptPages = allPages.map(
|
|
5399
5958
|
(p) => ({
|
|
5400
5959
|
id: p.id,
|
|
5960
|
+
sectionId: p._sectionId,
|
|
5401
5961
|
shell: p.shell_override ?? p._shell ?? essence.blueprint.shell,
|
|
5402
5962
|
layout: (p.layout || []).map(
|
|
5403
5963
|
(item) => typeof item === "string" ? item : extractPatternName(item)
|
|
@@ -5408,7 +5968,9 @@ Validation warnings: ${validation.errors.join(", ")}`));
|
|
|
5408
5968
|
promptPages = essence.structure || [{ id: "home", shell: options.shell, layout: ["hero"] }];
|
|
5409
5969
|
}
|
|
5410
5970
|
const promptCtx = {
|
|
5411
|
-
workflow: options.workflowMode
|
|
5971
|
+
workflow: options.workflowMode || "greenfield-scaffold",
|
|
5972
|
+
adoptionMode: options.adoptionMode,
|
|
5973
|
+
analysisArtifacts: options.analysisArtifacts,
|
|
5412
5974
|
archetype: options.archetype || "custom",
|
|
5413
5975
|
blueprint: options.blueprint,
|
|
5414
5976
|
theme: options.theme,
|
|
@@ -5434,16 +5996,16 @@ Validation warnings: ${validation.errors.join(", ")}`));
|
|
|
5434
5996
|
}
|
|
5435
5997
|
async function cmdStatus() {
|
|
5436
5998
|
const projectRoot = process.cwd();
|
|
5437
|
-
const essencePath =
|
|
5438
|
-
const projectJsonPath =
|
|
5999
|
+
const essencePath = join28(projectRoot, "decantr.essence.json");
|
|
6000
|
+
const projectJsonPath = join28(projectRoot, ".decantr", "project.json");
|
|
5439
6001
|
console.log(heading2("Decantr Project Status"));
|
|
5440
|
-
if (!
|
|
6002
|
+
if (!existsSync27(essencePath)) {
|
|
5441
6003
|
console.log(`${RED11}No decantr.essence.json found.${RESET13}`);
|
|
5442
6004
|
console.log(dim3('Run "decantr init" to create one.'));
|
|
5443
6005
|
return;
|
|
5444
6006
|
}
|
|
5445
6007
|
try {
|
|
5446
|
-
const essence = JSON.parse(
|
|
6008
|
+
const essence = JSON.parse(readFileSync20(essencePath, "utf-8"));
|
|
5447
6009
|
const validation = validateEssence2(essence);
|
|
5448
6010
|
const essenceVersion = isV36(essence) ? "v3" : "v2";
|
|
5449
6011
|
console.log(`${BOLD6}Essence:${RESET13}`);
|
|
@@ -5500,9 +6062,9 @@ async function cmdStatus() {
|
|
|
5500
6062
|
}
|
|
5501
6063
|
console.log("");
|
|
5502
6064
|
console.log(`${BOLD6}Sync Status:${RESET13}`);
|
|
5503
|
-
if (
|
|
6065
|
+
if (existsSync27(projectJsonPath)) {
|
|
5504
6066
|
try {
|
|
5505
|
-
const projectJson = JSON.parse(
|
|
6067
|
+
const projectJson = JSON.parse(readFileSync20(projectJsonPath, "utf-8"));
|
|
5506
6068
|
const syncStatus = projectJson.sync?.status || "unknown";
|
|
5507
6069
|
const lastSync = projectJson.sync?.lastSync || "never";
|
|
5508
6070
|
const source = projectJson.sync?.registrySource || "unknown";
|
|
@@ -5520,7 +6082,7 @@ async function cmdStatus() {
|
|
|
5520
6082
|
}
|
|
5521
6083
|
async function cmdSync() {
|
|
5522
6084
|
const projectRoot = process.cwd();
|
|
5523
|
-
const cacheDir =
|
|
6085
|
+
const cacheDir = join28(projectRoot, ".decantr", "cache");
|
|
5524
6086
|
console.log(heading2("Syncing registry content..."));
|
|
5525
6087
|
const result = await syncRegistry(cacheDir);
|
|
5526
6088
|
if (result.synced.length > 0) {
|
|
@@ -5715,14 +6277,14 @@ ${BOLD6}Examples:${RESET13}
|
|
|
5715
6277
|
process.exitCode = 1;
|
|
5716
6278
|
return;
|
|
5717
6279
|
}
|
|
5718
|
-
const themePath =
|
|
5719
|
-
if (!
|
|
6280
|
+
const themePath = join28(projectRoot, ".decantr", "custom", "themes", `${name}.json`);
|
|
6281
|
+
if (!existsSync27(themePath)) {
|
|
5720
6282
|
console.error(error3(`Theme "${name}" not found at ${themePath}`));
|
|
5721
6283
|
process.exitCode = 1;
|
|
5722
6284
|
return;
|
|
5723
6285
|
}
|
|
5724
6286
|
try {
|
|
5725
|
-
const theme = JSON.parse(
|
|
6287
|
+
const theme = JSON.parse(readFileSync20(themePath, "utf-8"));
|
|
5726
6288
|
const result = validateCustomTheme(theme);
|
|
5727
6289
|
if (result.valid) {
|
|
5728
6290
|
console.log(success3(`Custom theme "${name}" is valid`));
|
|
@@ -5795,7 +6357,7 @@ function cmdHelp() {
|
|
|
5795
6357
|
${BOLD6}decantr${RESET13} \u2014 Design intelligence for AI-generated UI
|
|
5796
6358
|
|
|
5797
6359
|
${BOLD6}Usage:${RESET13}
|
|
5798
|
-
decantr new <name> [--blueprint=X] [--archetype=X] [--theme=X]
|
|
6360
|
+
decantr new <name> [--blueprint=X] [--archetype=X] [--theme=X] [--workflow=greenfield] [--adoption=decantr-css]
|
|
5799
6361
|
decantr magic <prompt> [--dry-run]
|
|
5800
6362
|
decantr init [options]
|
|
5801
6363
|
decantr status
|
|
@@ -5814,6 +6376,7 @@ ${BOLD6}Usage:${RESET13}
|
|
|
5814
6376
|
decantr registry get-pack <manifest|scaffold|review|section|page|mutation> [id] [--namespace <namespace>] [--json] [--essence <path>] [--write-context]
|
|
5815
6377
|
decantr registry critique-file <file> [--namespace <namespace>] [--json] [--essence <path>] [--treatments <path>]
|
|
5816
6378
|
decantr registry audit-project [--namespace <namespace>] [--json] [--essence <path>] [--dist <path>] [--sources <dir>]
|
|
6379
|
+
decantr rules apply [--project=<path>]
|
|
5817
6380
|
decantr validate [path]
|
|
5818
6381
|
decantr theme <subcommand>
|
|
5819
6382
|
decantr create <type> <name>
|
|
@@ -5832,6 +6395,10 @@ ${BOLD6}Init Options:${RESET13}
|
|
|
5832
6395
|
--guard Guard mode: creative | guided | strict
|
|
5833
6396
|
--density Spacing: compact | comfortable | spacious
|
|
5834
6397
|
--shell Default shell layout
|
|
6398
|
+
--workflow Workflow: greenfield | brownfield | hybrid
|
|
6399
|
+
--adoption Adoption: contract-only | style-bridge | decantr-css
|
|
6400
|
+
--assistant-bridge Assistant rules: none | preview | apply
|
|
6401
|
+
--project App path inside a workspace/monorepo
|
|
5835
6402
|
--existing Initialize in existing project
|
|
5836
6403
|
--offline Force offline mode
|
|
5837
6404
|
--yes, -y Accept defaults, skip confirmations
|
|
@@ -5861,6 +6428,7 @@ ${BOLD6}Commands:${RESET13}
|
|
|
5861
6428
|
${cyan3("analyze")} Brownfield entrypoint: scan an existing project and emit attach guidance
|
|
5862
6429
|
${cyan3("export")} Export design tokens to framework format (shadcn, tailwind, css-vars)
|
|
5863
6430
|
${cyan3("registry")} Registry management and intelligence summary
|
|
6431
|
+
${cyan3("rules")} Preview/apply Decantr assistant bridge blocks to repo rule files
|
|
5864
6432
|
${cyan3("upgrade")} Check for content updates from registry
|
|
5865
6433
|
${cyan3("help")} Show this help
|
|
5866
6434
|
|
|
@@ -5868,7 +6436,11 @@ ${BOLD6}Examples:${RESET13}
|
|
|
5868
6436
|
decantr new my-app --blueprint=carbon-ai-portal
|
|
5869
6437
|
decantr magic "AI chatbot with dark cyber theme \u2014 bold and futuristic"
|
|
5870
6438
|
decantr init
|
|
5871
|
-
decantr init --existing --
|
|
6439
|
+
decantr init --existing --adoption=contract-only --yes
|
|
6440
|
+
decantr init --existing --adoption=style-bridge --assistant-bridge=preview
|
|
6441
|
+
decantr init --workflow=greenfield --adoption=contract-only
|
|
6442
|
+
decantr init --project=apps/web --yes
|
|
6443
|
+
decantr rules apply
|
|
5872
6444
|
decantr status
|
|
5873
6445
|
decantr audit
|
|
5874
6446
|
decantr audit src/pages/HomePage.tsx
|
|
@@ -5891,13 +6463,14 @@ ${BOLD6}Examples:${RESET13}
|
|
|
5891
6463
|
decantr create pattern my-card
|
|
5892
6464
|
|
|
5893
6465
|
${BOLD6}Workflow Model:${RESET13}
|
|
5894
|
-
${cyan3("Greenfield blueprint")} decantr new
|
|
5895
|
-
${cyan3("
|
|
6466
|
+
${cyan3("Greenfield blueprint")} decantr new my-app --blueprint=X --workflow=greenfield --adoption=decantr-css
|
|
6467
|
+
${cyan3("Greenfield contract")} decantr init --workflow=greenfield --adoption=contract-only
|
|
6468
|
+
${cyan3("Brownfield adoption")} decantr analyze -> decantr init --existing --adoption=contract-only
|
|
5896
6469
|
${cyan3("Hybrid composition")} decantr add/remove, decantr theme switch, decantr registry, decantr upgrade
|
|
5897
6470
|
|
|
5898
6471
|
${BOLD6}Bootstrap adapters:${RESET13}
|
|
5899
|
-
|
|
5900
|
-
|
|
6472
|
+
Runnable starter adapters: ${cyan3("react-vite")}, ${cyan3("next-app")}
|
|
6473
|
+
Unsupported targets resolve through ${cyan3("generic-web")} contract-only mode until their starter adapters land.
|
|
5901
6474
|
`);
|
|
5902
6475
|
}
|
|
5903
6476
|
async function main() {
|
|
@@ -5909,14 +6482,14 @@ async function main() {
|
|
|
5909
6482
|
}
|
|
5910
6483
|
if (command === "--version" || command === "-v" || command === "version") {
|
|
5911
6484
|
try {
|
|
5912
|
-
const here =
|
|
6485
|
+
const here = dirname4(fileURLToPath2(import.meta.url));
|
|
5913
6486
|
const candidates = [
|
|
5914
|
-
|
|
5915
|
-
|
|
6487
|
+
join28(here, "..", "package.json"),
|
|
6488
|
+
join28(here, "..", "..", "package.json")
|
|
5916
6489
|
];
|
|
5917
6490
|
for (const candidate of candidates) {
|
|
5918
|
-
if (
|
|
5919
|
-
const pkg = JSON.parse(
|
|
6491
|
+
if (existsSync27(candidate)) {
|
|
6492
|
+
const pkg = JSON.parse(readFileSync20(candidate, "utf-8"));
|
|
5920
6493
|
if (pkg.version) {
|
|
5921
6494
|
console.log(pkg.version);
|
|
5922
6495
|
return;
|
|
@@ -5963,7 +6536,10 @@ async function main() {
|
|
|
5963
6536
|
shape: newOpts.shape,
|
|
5964
6537
|
target: newOpts.target,
|
|
5965
6538
|
offline: newOpts.offline === true,
|
|
5966
|
-
registry: newOpts.registry
|
|
6539
|
+
registry: newOpts.registry,
|
|
6540
|
+
workflow: newOpts.workflow,
|
|
6541
|
+
adoption: newOpts.adoption,
|
|
6542
|
+
assistantBridge: newOpts["assistant-bridge"]
|
|
5967
6543
|
});
|
|
5968
6544
|
break;
|
|
5969
6545
|
}
|
|
@@ -6002,7 +6578,7 @@ async function main() {
|
|
|
6002
6578
|
break;
|
|
6003
6579
|
}
|
|
6004
6580
|
case "upgrade": {
|
|
6005
|
-
const { cmdUpgrade } = await import("./upgrade-
|
|
6581
|
+
const { cmdUpgrade } = await import("./upgrade-KG42WK5C.js");
|
|
6006
6582
|
const applyFlag = args.includes("--apply");
|
|
6007
6583
|
await cmdUpgrade(process.cwd(), { apply: applyFlag });
|
|
6008
6584
|
break;
|
|
@@ -6394,7 +6970,47 @@ async function main() {
|
|
|
6394
6970
|
break;
|
|
6395
6971
|
}
|
|
6396
6972
|
case "analyze": {
|
|
6397
|
-
|
|
6973
|
+
let projectArg;
|
|
6974
|
+
for (let i = 1; i < args.length; i++) {
|
|
6975
|
+
if (args[i].startsWith("--project=")) {
|
|
6976
|
+
projectArg = args[i].split("=")[1];
|
|
6977
|
+
} else if (args[i] === "--project" && args[i + 1]) {
|
|
6978
|
+
projectArg = args[++i];
|
|
6979
|
+
}
|
|
6980
|
+
}
|
|
6981
|
+
const workspaceInfo = resolveWorkspaceInfo(process.cwd(), projectArg);
|
|
6982
|
+
if (workspaceInfo.requiresProjectSelection) {
|
|
6983
|
+
console.log(error3("This looks like a workspace root with multiple app candidates."));
|
|
6984
|
+
console.log(dim3(`Use --project=<path>. Candidates: ${workspaceInfo.appCandidates.join(", ")}`));
|
|
6985
|
+
process.exitCode = 1;
|
|
6986
|
+
break;
|
|
6987
|
+
}
|
|
6988
|
+
cmdAnalyze(workspaceInfo.appRoot, workspaceInfo);
|
|
6989
|
+
break;
|
|
6990
|
+
}
|
|
6991
|
+
case "rules": {
|
|
6992
|
+
const subcommand = args[1];
|
|
6993
|
+
if (subcommand !== "apply") {
|
|
6994
|
+
console.error(error3("Usage: decantr rules apply [--project=<path>]"));
|
|
6995
|
+
process.exitCode = 1;
|
|
6996
|
+
break;
|
|
6997
|
+
}
|
|
6998
|
+
let projectArg;
|
|
6999
|
+
for (let i = 2; i < args.length; i++) {
|
|
7000
|
+
if (args[i].startsWith("--project=")) {
|
|
7001
|
+
projectArg = args[i].split("=")[1];
|
|
7002
|
+
} else if (args[i] === "--project" && args[i + 1]) {
|
|
7003
|
+
projectArg = args[++i];
|
|
7004
|
+
}
|
|
7005
|
+
}
|
|
7006
|
+
const workspaceInfo = resolveWorkspaceInfo(process.cwd(), projectArg);
|
|
7007
|
+
const detected = detectProject(workspaceInfo.appRoot);
|
|
7008
|
+
const updated = applyAssistantBridge(workspaceInfo.appRoot, detected);
|
|
7009
|
+
if (updated.length === 0) {
|
|
7010
|
+
console.log(dim3("Assistant bridge rule files are already up to date."));
|
|
7011
|
+
} else {
|
|
7012
|
+
console.log(success3(`Applied Decantr assistant bridge to ${updated.join(", ")}.`));
|
|
7013
|
+
}
|
|
6398
7014
|
break;
|
|
6399
7015
|
}
|
|
6400
7016
|
case "magic": {
|