@decantr/cli 1.7.24 → 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
2564
|
console.log(
|
|
2381
|
-
`
|
|
2565
|
+
` 1. Read ${cyan(".decantr/context/scaffold-pack.md")} first as the primary compiled contract`
|
|
2382
2566
|
);
|
|
2383
2567
|
console.log(
|
|
2384
|
-
`
|
|
2568
|
+
` 2. Read the matching ${cyan(".decantr/context/section-*-pack.md")} and ${cyan(".decantr/context/page-*-pack.md")} files before section or route work`
|
|
2385
2569
|
);
|
|
2386
2570
|
console.log(
|
|
2387
|
-
`
|
|
2571
|
+
` 3. Use ${cyan("DECANTR.md")} as a lookup reference for guard rules, CSS atoms, treatments, decorators, and workflow`
|
|
2388
2572
|
);
|
|
2389
|
-
console.log(
|
|
2390
|
-
|
|
2391
|
-
);
|
|
2392
|
-
console.log(` 6. Build the shell and route structure first, then implement each page`);
|
|
2393
|
-
console.log(` 7. Run ${cyan("decantr check")} and ${cyan("decantr audit")} before you ship`);
|
|
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",
|
|
@@ -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,210 +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"
|
|
4042
|
-
);
|
|
4043
|
-
lines.push(
|
|
4044
|
-
'- stagger-children \u2192 d-stagger-children parent + style={{ "--d-stagger-index": i }} on each child'
|
|
4045
|
-
);
|
|
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."
|
|
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.'
|
|
4064
4488
|
);
|
|
4065
|
-
lines.push("");
|
|
4066
|
-
lines.push("\u2550\u2550\u2550 ATOMS-FIRST FOR LAYOUT \u2014 DO NOT INLINE-STYLE \u2550\u2550\u2550");
|
|
4067
|
-
lines.push("");
|
|
4068
4489
|
lines.push(
|
|
4069
|
-
|
|
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."
|
|
4070
4491
|
);
|
|
4071
4492
|
lines.push("");
|
|
4072
|
-
lines.push("Mandatory translation (NOT optional). USE THESE EXACT ATOM NAMES:");
|
|
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", alignItems: "center" }} \u2192 \u2705 className={css("_col _aic")}'
|
|
4078
|
-
);
|
|
4079
|
-
lines.push(
|
|
4080
|
-
' \u274C style={{ justifyContent: "space-between" }} \u2192 \u2705 className={css("_jcsb")}'
|
|
4081
|
-
);
|
|
4082
|
-
lines.push(
|
|
4083
|
-
' \u274C style={{ padding: "1rem 1.5rem" }} \u2192 \u2705 className={css("_py4 _px6")}'
|
|
4084
|
-
);
|
|
4085
|
-
lines.push(
|
|
4086
|
-
' \u274C style={{ gridTemplateColumns: "repeat(3, 1fr)" }} \u2192 \u2705 className={css("_grid _gc3")}'
|
|
4087
|
-
);
|
|
4088
|
-
lines.push(
|
|
4089
|
-
' \u274C style={{ position: "sticky", top: 0 }} \u2192 \u2705 className={css("_sticky _top0")}'
|
|
4090
|
-
);
|
|
4091
|
-
lines.push(
|
|
4092
|
-
' \u274C style={{ width: "100%", maxWidth: "40rem" }} \u2192 \u2705 className={css("_wfull _maxw[40rem]")}'
|
|
4093
|
-
);
|
|
4094
4493
|
lines.push(
|
|
4095
|
-
|
|
4096
|
-
);
|
|
4097
|
-
lines.push(
|
|
4098
|
-
' \u274C style={{ fontSize: "1.5rem" }} \u2192 \u2705 className={css("_text2xl")}'
|
|
4494
|
+
"`decantr check --strict` fails when a declared interaction has no matching implementation."
|
|
4099
4495
|
);
|
|
4100
4496
|
lines.push("");
|
|
4101
|
-
lines.push(
|
|
4102
|
-
'Atom naming convention: compact prefix-spelling \u2014 `_aic` (align-items:center) not `_items-center`, `_jcsb` (justify-content:space-between) not `_justify-between`, `_wfull` not `_w-full`, `_top0` not `_t0`. The runtime accepts hyphenated Tailwind-style aliases as fallback BUT compact prefix is canonical, shorter, and what every existing scaffold uses. Do not invent your own atom shapes \u2014 if you need an arbitrary value (e.g. `maxWidth: "72rem"`), use the brackets form `_maxw[72rem]`.'
|
|
4103
|
-
);
|
|
4497
|
+
lines.push("\u2550\u2550\u2550 STYLING ADOPTION \u2550\u2550\u2550");
|
|
4104
4498
|
lines.push("");
|
|
4105
|
-
|
|
4106
|
-
|
|
4107
|
-
|
|
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
|
+
}
|
|
4108
4542
|
lines.push("");
|
|
4109
4543
|
lines.push("Inline `style={{...}}` is ONLY acceptable for:");
|
|
4110
4544
|
lines.push(
|
|
4111
|
-
|
|
4545
|
+
" 1. CSS custom-property writes the contract requires (`--d-stagger-index`, theme color vars, etc.)"
|
|
4112
4546
|
);
|
|
4113
4547
|
lines.push(
|
|
4114
|
-
" 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)."
|
|
4115
4549
|
);
|
|
4116
4550
|
lines.push("");
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
|
|
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
|
+
}
|
|
4120
4560
|
lines.push("");
|
|
4121
4561
|
lines.push("\u2550\u2550\u2550 TREATMENT SURFACE \u2014 USE WHAT EXISTS \u2550\u2550\u2550");
|
|
4122
4562
|
lines.push("");
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
|
|
4136
|
-
|
|
4137
|
-
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
|
|
4144
|
-
lines.push("- Composite card: d-card, d-card-header, d-card-body, d-card-footer");
|
|
4145
|
-
lines.push(
|
|
4146
|
-
"- 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"
|
|
4147
|
-
);
|
|
4148
|
-
lines.push(
|
|
4149
|
-
"- Typography: d-display, d-headline, d-title, d-subtitle, d-prose, d-body, d-caption, d-eyebrow, d-numeric, d-mono-text"
|
|
4150
|
-
);
|
|
4151
|
-
lines.push('- Elevation: d-elevate[data-level="0..5"]');
|
|
4152
|
-
lines.push(
|
|
4153
|
-
"- 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"
|
|
4154
|
-
);
|
|
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
|
+
}
|
|
4155
4584
|
lines.push("");
|
|
4156
4585
|
lines.push(
|
|
4157
|
-
"
|
|
4586
|
+
"Consult DECANTR.md only when you need the full table or exact data-* attributes."
|
|
4158
4587
|
);
|
|
4159
4588
|
lines.push("");
|
|
4160
4589
|
lines.push("\u2550\u2550\u2550 THEME DECORATOR CONTRACT \u2014 APPLY OR THE THEME DOES NOT LAND \u2550\u2550\u2550");
|
|
4161
4590
|
lines.push("");
|
|
4162
4591
|
lines.push(
|
|
4163
|
-
'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.'
|
|
4164
4593
|
);
|
|
4165
4594
|
lines.push(
|
|
4166
|
-
|
|
4595
|
+
"Section packs may point back to the scaffold-pack table; scaffold-pack.md is authoritative."
|
|
4167
4596
|
);
|
|
4168
4597
|
lines.push("");
|
|
4169
4598
|
lines.push("\u2550\u2550\u2550 HARD RULES (NON-NEGOTIABLE) \u2550\u2550\u2550");
|
|
4170
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
|
+
}
|
|
4171
4613
|
lines.push(
|
|
4172
|
-
|
|
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."
|
|
4173
4615
|
);
|
|
4174
4616
|
lines.push(
|
|
4175
|
-
|
|
4617
|
+
"- Section Directives in section packs are execution rules for layout proportions, treatment stacks, copy conventions, and pattern fitness."
|
|
4176
4618
|
);
|
|
4177
4619
|
lines.push(
|
|
4178
|
-
"-
|
|
4620
|
+
"- Filter chip rows / tab strips use `d-step-chip[data-step-state]`, not bare `d-interactive` buttons."
|
|
4179
4621
|
);
|
|
4180
4622
|
lines.push(
|
|
4181
|
-
"-
|
|
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."
|
|
4182
4624
|
);
|
|
4183
4625
|
lines.push(
|
|
4184
|
-
"-
|
|
4626
|
+
"- Prevent layout collisions: hero content, CTA banners, cards, footers, and sticky chrome must not overlap or clip at desktop or mobile widths."
|
|
4185
4627
|
);
|
|
4186
4628
|
lines.push("");
|
|
4187
4629
|
lines.push("\u2550\u2550\u2550 IMPLEMENTATION RULES \u2550\u2550\u2550");
|
|
4188
4630
|
lines.push(
|
|
4189
|
-
"- Do not invent routes, sections, shells, themes, or features
|
|
4190
|
-
);
|
|
4191
|
-
lines.push(
|
|
4192
|
-
"- 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."
|
|
4193
4632
|
);
|
|
4633
|
+
lines.push("- Prefer scaffold-pack, section-pack, and page-pack guidance over narrative docs.");
|
|
4194
4634
|
lines.push(
|
|
4195
4635
|
"- Start with the shell layouts and route structure first, then build section pages route by route."
|
|
4196
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
|
+
}
|
|
4197
4644
|
lines.push(
|
|
4198
|
-
"-
|
|
4199
|
-
);
|
|
4200
|
-
lines.push(
|
|
4201
|
-
"- 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."
|
|
4202
4646
|
);
|
|
4203
4647
|
lines.push(
|
|
4204
|
-
"- 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."
|
|
4205
4649
|
);
|
|
4206
4650
|
lines.push(
|
|
4207
|
-
|
|
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."
|
|
4208
4652
|
);
|
|
4209
4653
|
lines.push(
|
|
4210
|
-
"- Let shells own spacing, centering, and scroll containers
|
|
4654
|
+
"- Let shells own spacing, centering, and scroll containers unless the route contract says otherwise."
|
|
4211
4655
|
);
|
|
4212
4656
|
lines.push(
|
|
4213
|
-
"- If command_palette or hotkeys are declared
|
|
4657
|
+
"- If command_palette or hotkeys are declared, implement them as real features rather than visible copy."
|
|
4214
4658
|
);
|
|
4215
4659
|
lines.push(
|
|
4216
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."
|
|
4217
4661
|
);
|
|
4218
4662
|
lines.push(
|
|
4219
|
-
"- 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."
|
|
4220
4664
|
);
|
|
4221
4665
|
lines.push(
|
|
4222
4666
|
"- Do not modify generated context files unless the task is explicitly to regenerate or refresh Decantr context."
|
|
@@ -4226,7 +4670,7 @@ function generateGreenfieldPrompt(ctx) {
|
|
|
4226
4670
|
lines.push("- Build the shell and shared layout first.");
|
|
4227
4671
|
lines.push("- Then implement each section's pages using the matching section and page packs.");
|
|
4228
4672
|
lines.push(
|
|
4229
|
-
"- After implementation, run `decantr check` (primary gate
|
|
4673
|
+
"- After implementation, run `decantr check` (primary gate) and `decantr audit` (supplementary diagnostics)."
|
|
4230
4674
|
);
|
|
4231
4675
|
lines.push("- Fix all violations until `decantr check` exits 0.");
|
|
4232
4676
|
lines.push(
|
|
@@ -4236,14 +4680,32 @@ function generateGreenfieldPrompt(ctx) {
|
|
|
4236
4680
|
}
|
|
4237
4681
|
function generateBrownfieldPrompt(ctx) {
|
|
4238
4682
|
const lines = [];
|
|
4239
|
-
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
|
+
}
|
|
4240
4696
|
lines.push("");
|
|
4241
4697
|
lines.push(
|
|
4242
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."
|
|
4243
4699
|
);
|
|
4244
4700
|
lines.push("");
|
|
4245
|
-
|
|
4246
|
-
|
|
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
|
+
}
|
|
4247
4709
|
lines.push(
|
|
4248
4710
|
"Treat the compiled execution-pack files as the Decantr contract you are layering onto the app."
|
|
4249
4711
|
);
|
|
@@ -4252,20 +4714,34 @@ function generateBrownfieldPrompt(ctx) {
|
|
|
4252
4714
|
);
|
|
4253
4715
|
lines.push("");
|
|
4254
4716
|
lines.push("Read in this order:");
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
|
|
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
|
+
}
|
|
4269
4745
|
lines.push("");
|
|
4270
4746
|
lines.push("Implementation rules:");
|
|
4271
4747
|
lines.push(
|
|
@@ -4277,21 +4753,33 @@ function generateBrownfieldPrompt(ctx) {
|
|
|
4277
4753
|
lines.push(
|
|
4278
4754
|
"- If package.json, router files, or style files already exist, extend them deliberately instead of replacing them with a different starter shape."
|
|
4279
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
|
+
}
|
|
4280
4772
|
lines.push(
|
|
4281
|
-
"-
|
|
4282
|
-
);
|
|
4283
|
-
lines.push(
|
|
4284
|
-
"- Use the existing Decantr tokens, treatments, and decorators instead of inventing a parallel visual system."
|
|
4285
|
-
);
|
|
4286
|
-
lines.push(
|
|
4287
|
-
"- 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."
|
|
4288
4774
|
);
|
|
4289
4775
|
lines.push(
|
|
4290
4776
|
"- Do not invent routes, sections, shells, themes, or features that are not present in the compiled packs."
|
|
4291
4777
|
);
|
|
4292
|
-
|
|
4293
|
-
|
|
4294
|
-
|
|
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
|
+
}
|
|
4295
4783
|
lines.push(
|
|
4296
4784
|
"- Let shells own spacing, centering, and scroll containers. Preserve app structure, but remove duplicated shell responsibilities when the contract makes them explicit."
|
|
4297
4785
|
);
|
|
@@ -4322,7 +4810,7 @@ function generateBrownfieldPrompt(ctx) {
|
|
|
4322
4810
|
return lines.join("\n");
|
|
4323
4811
|
}
|
|
4324
4812
|
function generateCuratedPrompt(ctx) {
|
|
4325
|
-
return ctx.workflow === "brownfield-attach" ? generateBrownfieldPrompt(ctx) : generateGreenfieldPrompt(ctx);
|
|
4813
|
+
return ctx.workflow === "brownfield-attach" || ctx.workflow === "hybrid-compose" ? generateBrownfieldPrompt(ctx) : generateGreenfieldPrompt(ctx);
|
|
4326
4814
|
}
|
|
4327
4815
|
function getAPIClient() {
|
|
4328
4816
|
return new RegistryAPIClient3({
|
|
@@ -4336,7 +4824,7 @@ function getPublicAPIClient() {
|
|
|
4336
4824
|
});
|
|
4337
4825
|
}
|
|
4338
4826
|
function resolveUserPath(inputPath, cwd = process.cwd()) {
|
|
4339
|
-
return isAbsolute(inputPath) ? inputPath :
|
|
4827
|
+
return isAbsolute(inputPath) ? inputPath : resolve4(cwd, inputPath);
|
|
4340
4828
|
}
|
|
4341
4829
|
function extractHostedAssetPaths(indexHtml) {
|
|
4342
4830
|
const assetPaths = /* @__PURE__ */ new Set();
|
|
@@ -4349,18 +4837,18 @@ function extractHostedAssetPaths(indexHtml) {
|
|
|
4349
4837
|
return [...assetPaths];
|
|
4350
4838
|
}
|
|
4351
4839
|
function readHostedDistSnapshot(distPath) {
|
|
4352
|
-
const resolvedDistPath = distPath ? resolveUserPath(distPath) :
|
|
4353
|
-
const indexPath =
|
|
4354
|
-
if (!
|
|
4840
|
+
const resolvedDistPath = distPath ? resolveUserPath(distPath) : join28(process.cwd(), "dist");
|
|
4841
|
+
const indexPath = join28(resolvedDistPath, "index.html");
|
|
4842
|
+
if (!existsSync27(indexPath)) {
|
|
4355
4843
|
return void 0;
|
|
4356
4844
|
}
|
|
4357
|
-
const indexHtml =
|
|
4845
|
+
const indexHtml = readFileSync20(indexPath, "utf-8");
|
|
4358
4846
|
const assetPaths = extractHostedAssetPaths(indexHtml);
|
|
4359
4847
|
const assets = {};
|
|
4360
4848
|
for (const assetPath of assetPaths) {
|
|
4361
|
-
const assetFilePath =
|
|
4362
|
-
if (
|
|
4363
|
-
assets[assetPath] =
|
|
4849
|
+
const assetFilePath = join28(resolvedDistPath, assetPath.replace(/^[/\\]+/, ""));
|
|
4850
|
+
if (existsSync27(assetFilePath)) {
|
|
4851
|
+
assets[assetPath] = readFileSync20(assetFilePath, "utf-8");
|
|
4364
4852
|
}
|
|
4365
4853
|
}
|
|
4366
4854
|
return {
|
|
@@ -4375,7 +4863,7 @@ function isHostedSourceSnapshotFile(path) {
|
|
|
4375
4863
|
function readHostedSourceSnapshot(sourcePath) {
|
|
4376
4864
|
if (!sourcePath) return void 0;
|
|
4377
4865
|
const resolvedSourcePath = resolveUserPath(sourcePath);
|
|
4378
|
-
if (!
|
|
4866
|
+
if (!existsSync27(resolvedSourcePath)) {
|
|
4379
4867
|
return void 0;
|
|
4380
4868
|
}
|
|
4381
4869
|
const files = {};
|
|
@@ -4389,17 +4877,17 @@ function readHostedSourceSnapshot(sourcePath) {
|
|
|
4389
4877
|
]);
|
|
4390
4878
|
const rootPrefix = basename2(resolvedSourcePath);
|
|
4391
4879
|
const walk = (absoluteDir, relativeDir) => {
|
|
4392
|
-
for (const entry of
|
|
4880
|
+
for (const entry of readdirSync7(absoluteDir, { withFileTypes: true })) {
|
|
4393
4881
|
if (ignoredDirNames.has(entry.name)) continue;
|
|
4394
|
-
const absolutePath =
|
|
4395
|
-
const relativePath =
|
|
4882
|
+
const absolutePath = join28(absoluteDir, entry.name);
|
|
4883
|
+
const relativePath = join28(relativeDir, entry.name).replace(/\\/g, "/");
|
|
4396
4884
|
if (entry.isDirectory()) {
|
|
4397
4885
|
walk(absolutePath, relativePath);
|
|
4398
4886
|
continue;
|
|
4399
4887
|
}
|
|
4400
4888
|
if (!entry.isFile()) continue;
|
|
4401
4889
|
if (!isHostedSourceSnapshotFile(relativePath)) continue;
|
|
4402
|
-
files[relativePath] =
|
|
4890
|
+
files[relativePath] = readFileSync20(absolutePath, "utf-8");
|
|
4403
4891
|
}
|
|
4404
4892
|
};
|
|
4405
4893
|
walk(resolvedSourcePath, rootPrefix);
|
|
@@ -4550,16 +5038,16 @@ async function printRegistryIntelligenceSummary(namespace, jsonOutput = false) {
|
|
|
4550
5038
|
}
|
|
4551
5039
|
async function printHostedExecutionPackBundle(essencePath, namespace, jsonOutput = false, writeContext = false) {
|
|
4552
5040
|
const client = getPublicAPIClient();
|
|
4553
|
-
const resolvedPath = essencePath ? resolveUserPath(essencePath) :
|
|
4554
|
-
if (!
|
|
5041
|
+
const resolvedPath = essencePath ? resolveUserPath(essencePath) : join28(process.cwd(), "decantr.essence.json");
|
|
5042
|
+
if (!existsSync27(resolvedPath)) {
|
|
4555
5043
|
throw new Error(`Essence file not found at ${resolvedPath}`);
|
|
4556
5044
|
}
|
|
4557
|
-
const essence = JSON.parse(
|
|
5045
|
+
const essence = JSON.parse(readFileSync20(resolvedPath, "utf-8"));
|
|
4558
5046
|
const bundle = await client.compileExecutionPacks(essence, namespace ? { namespace } : void 0);
|
|
4559
5047
|
let writtenContextPaths = [];
|
|
4560
5048
|
if (writeContext) {
|
|
4561
|
-
const contextDir =
|
|
4562
|
-
|
|
5049
|
+
const contextDir = join28(process.cwd(), ".decantr", "context");
|
|
5050
|
+
mkdirSync11(contextDir, { recursive: true });
|
|
4563
5051
|
const written = writeExecutionPackBundleArtifacts(
|
|
4564
5052
|
contextDir,
|
|
4565
5053
|
bundle
|
|
@@ -4584,26 +5072,27 @@ async function printHostedExecutionPackBundle(essencePath, namespace, jsonOutput
|
|
|
4584
5072
|
console.log(` Sections: ${typedBundle.sections.length}`);
|
|
4585
5073
|
console.log(` Mutations: ${typedBundle.mutations.length}`);
|
|
4586
5074
|
if (writeContext) {
|
|
4587
|
-
console.log(` Context bundle: ${
|
|
5075
|
+
console.log(` Context bundle: ${join28(process.cwd(), ".decantr", "context")}`);
|
|
4588
5076
|
console.log(` Files written: ${writtenContextPaths.length}`);
|
|
4589
5077
|
}
|
|
4590
5078
|
console.log("");
|
|
4591
5079
|
console.log(`${BOLD6}Route Plan:${RESET13}`);
|
|
4592
5080
|
for (const route of typedBundle.scaffold.data.routes) {
|
|
4593
5081
|
const patterns = route.patternIds.length > 0 ? route.patternIds.join(", ") : "none";
|
|
4594
|
-
|
|
5082
|
+
const pageLabel = route.sectionId ? `${route.sectionId}/${route.pageId}` : route.pageId;
|
|
5083
|
+
console.log(` ${cyan3(route.path)} -> ${pageLabel} [${patterns}]`);
|
|
4595
5084
|
}
|
|
4596
5085
|
}
|
|
4597
5086
|
async function printHostedSelectedExecutionPack(packType, id, essencePath, namespace, jsonOutput = false, writeContext = false) {
|
|
4598
5087
|
const client = getPublicAPIClient();
|
|
4599
|
-
const resolvedPath = essencePath ? resolveUserPath(essencePath) :
|
|
4600
|
-
if (!
|
|
5088
|
+
const resolvedPath = essencePath ? resolveUserPath(essencePath) : join28(process.cwd(), "decantr.essence.json");
|
|
5089
|
+
if (!existsSync27(resolvedPath)) {
|
|
4601
5090
|
throw new Error(`Essence file not found at ${resolvedPath}`);
|
|
4602
5091
|
}
|
|
4603
5092
|
if ((packType === "section" || packType === "page" || packType === "mutation") && !id) {
|
|
4604
5093
|
throw new Error(`Pack type "${packType}" requires an id.`);
|
|
4605
5094
|
}
|
|
4606
|
-
const essence = JSON.parse(
|
|
5095
|
+
const essence = JSON.parse(readFileSync20(resolvedPath, "utf-8"));
|
|
4607
5096
|
const selected = await client.selectExecutionPack(
|
|
4608
5097
|
{
|
|
4609
5098
|
essence,
|
|
@@ -4614,17 +5103,17 @@ async function printHostedSelectedExecutionPack(packType, id, essencePath, names
|
|
|
4614
5103
|
);
|
|
4615
5104
|
let writtenContextDir = null;
|
|
4616
5105
|
if (writeContext) {
|
|
4617
|
-
const contextDir =
|
|
4618
|
-
|
|
4619
|
-
|
|
4620
|
-
|
|
5106
|
+
const contextDir = join28(process.cwd(), ".decantr", "context");
|
|
5107
|
+
mkdirSync11(contextDir, { recursive: true });
|
|
5108
|
+
writeFileSync14(
|
|
5109
|
+
join28(contextDir, "pack-manifest.json"),
|
|
4621
5110
|
JSON.stringify(selected.manifest, null, 2) + "\n"
|
|
4622
5111
|
);
|
|
4623
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);
|
|
4624
5113
|
const markdownFile = manifestEntry?.markdown ?? `${selected.selector.packType}${selected.selector.id ? `-${selected.selector.id}` : ""}-pack.md`;
|
|
4625
5114
|
const jsonFile = manifestEntry?.json ?? `${selected.selector.packType}${selected.selector.id ? `-${selected.selector.id}` : ""}-pack.json`;
|
|
4626
|
-
|
|
4627
|
-
|
|
5115
|
+
writeFileSync14(join28(contextDir, markdownFile), selected.pack.renderedMarkdown);
|
|
5116
|
+
writeFileSync14(join28(contextDir, jsonFile), JSON.stringify(selected.pack, null, 2) + "\n");
|
|
4628
5117
|
writtenContextDir = contextDir;
|
|
4629
5118
|
}
|
|
4630
5119
|
if (jsonOutput) {
|
|
@@ -4649,20 +5138,20 @@ async function printHostedSelectedExecutionPack(packType, id, essencePath, names
|
|
|
4649
5138
|
}
|
|
4650
5139
|
async function printHostedExecutionPackManifest(essencePath, namespace, jsonOutput = false, writeContext = false) {
|
|
4651
5140
|
const client = getPublicAPIClient();
|
|
4652
|
-
const resolvedPath = essencePath ? resolveUserPath(essencePath) :
|
|
4653
|
-
if (!
|
|
5141
|
+
const resolvedPath = essencePath ? resolveUserPath(essencePath) : join28(process.cwd(), "decantr.essence.json");
|
|
5142
|
+
if (!existsSync27(resolvedPath)) {
|
|
4654
5143
|
throw new Error(`Essence file not found at ${resolvedPath}`);
|
|
4655
5144
|
}
|
|
4656
|
-
const essence = JSON.parse(
|
|
5145
|
+
const essence = JSON.parse(readFileSync20(resolvedPath, "utf-8"));
|
|
4657
5146
|
const manifest = await client.getExecutionPackManifest(
|
|
4658
5147
|
essence,
|
|
4659
5148
|
namespace ? { namespace } : void 0
|
|
4660
5149
|
);
|
|
4661
5150
|
let writtenContextDir = null;
|
|
4662
5151
|
if (writeContext) {
|
|
4663
|
-
const contextDir =
|
|
4664
|
-
|
|
4665
|
-
|
|
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");
|
|
4666
5155
|
writtenContextDir = contextDir;
|
|
4667
5156
|
}
|
|
4668
5157
|
if (jsonOutput) {
|
|
@@ -4683,14 +5172,14 @@ async function printHostedExecutionPackManifest(essencePath, namespace, jsonOutp
|
|
|
4683
5172
|
}
|
|
4684
5173
|
}
|
|
4685
5174
|
async function hydrateHostedExecutionPacksIfMissing(projectRoot, namespace = "@official") {
|
|
4686
|
-
const contextDir =
|
|
4687
|
-
const reviewPackPath =
|
|
4688
|
-
const manifestPath =
|
|
4689
|
-
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)) {
|
|
4690
5179
|
return { attempted: false, hydrated: false };
|
|
4691
5180
|
}
|
|
4692
|
-
const essencePath =
|
|
4693
|
-
if (!
|
|
5181
|
+
const essencePath = join28(projectRoot, "decantr.essence.json");
|
|
5182
|
+
if (!existsSync27(essencePath)) {
|
|
4694
5183
|
return { attempted: false, hydrated: false };
|
|
4695
5184
|
}
|
|
4696
5185
|
const reviewHydration = await hydrateHostedReviewPackIfMissing(projectRoot, namespace);
|
|
@@ -4699,9 +5188,9 @@ async function hydrateHostedExecutionPacksIfMissing(projectRoot, namespace = "@o
|
|
|
4699
5188
|
}
|
|
4700
5189
|
try {
|
|
4701
5190
|
const client = getPublicAPIClient();
|
|
4702
|
-
const essence = JSON.parse(
|
|
5191
|
+
const essence = JSON.parse(readFileSync20(essencePath, "utf-8"));
|
|
4703
5192
|
const bundle = await client.compileExecutionPacks(essence, { namespace });
|
|
4704
|
-
|
|
5193
|
+
mkdirSync11(contextDir, { recursive: true });
|
|
4705
5194
|
writeExecutionPackBundleArtifacts(contextDir, bundle);
|
|
4706
5195
|
return { attempted: true, hydrated: true, scope: "bundle" };
|
|
4707
5196
|
} catch {
|
|
@@ -4709,19 +5198,19 @@ async function hydrateHostedExecutionPacksIfMissing(projectRoot, namespace = "@o
|
|
|
4709
5198
|
}
|
|
4710
5199
|
}
|
|
4711
5200
|
async function hydrateHostedReviewPackIfMissing(projectRoot, namespace = "@official") {
|
|
4712
|
-
const contextDir =
|
|
4713
|
-
const reviewPackPath =
|
|
4714
|
-
const manifestPath =
|
|
4715
|
-
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)) {
|
|
4716
5205
|
return { attempted: false, hydrated: false };
|
|
4717
5206
|
}
|
|
4718
|
-
const essencePath =
|
|
4719
|
-
if (!
|
|
5207
|
+
const essencePath = join28(projectRoot, "decantr.essence.json");
|
|
5208
|
+
if (!existsSync27(essencePath)) {
|
|
4720
5209
|
return { attempted: false, hydrated: false };
|
|
4721
5210
|
}
|
|
4722
5211
|
try {
|
|
4723
5212
|
const client = getPublicAPIClient();
|
|
4724
|
-
const essence = JSON.parse(
|
|
5213
|
+
const essence = JSON.parse(readFileSync20(essencePath, "utf-8"));
|
|
4725
5214
|
const selected = await client.selectExecutionPack(
|
|
4726
5215
|
{
|
|
4727
5216
|
essence,
|
|
@@ -4729,14 +5218,14 @@ async function hydrateHostedReviewPackIfMissing(projectRoot, namespace = "@offic
|
|
|
4729
5218
|
},
|
|
4730
5219
|
{ namespace }
|
|
4731
5220
|
);
|
|
4732
|
-
|
|
4733
|
-
|
|
4734
|
-
|
|
4735
|
-
|
|
5221
|
+
mkdirSync11(contextDir, { recursive: true });
|
|
5222
|
+
writeFileSync14(join28(contextDir, "review-pack.md"), selected.pack.renderedMarkdown);
|
|
5223
|
+
writeFileSync14(
|
|
5224
|
+
join28(contextDir, "review-pack.json"),
|
|
4736
5225
|
JSON.stringify(selected.pack, null, 2) + "\n"
|
|
4737
5226
|
);
|
|
4738
|
-
if (!
|
|
4739
|
-
|
|
5227
|
+
if (!existsSync27(manifestPath)) {
|
|
5228
|
+
writeFileSync14(manifestPath, JSON.stringify(selected.manifest, null, 2) + "\n");
|
|
4740
5229
|
}
|
|
4741
5230
|
return { attempted: true, hydrated: true, scope: "review" };
|
|
4742
5231
|
} catch {
|
|
@@ -4746,17 +5235,17 @@ async function hydrateHostedReviewPackIfMissing(projectRoot, namespace = "@offic
|
|
|
4746
5235
|
async function printHostedFileCritique(sourcePath, namespace, jsonOutput = false, essencePath, treatmentsPath) {
|
|
4747
5236
|
const client = getPublicAPIClient();
|
|
4748
5237
|
const resolvedSourcePath = resolveUserPath(sourcePath);
|
|
4749
|
-
const resolvedEssencePath = essencePath ? resolveUserPath(essencePath) :
|
|
4750
|
-
const resolvedTreatmentsPath = treatmentsPath ? resolveUserPath(treatmentsPath) :
|
|
4751
|
-
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)) {
|
|
4752
5241
|
throw new Error(`Source file not found at ${resolvedSourcePath}`);
|
|
4753
5242
|
}
|
|
4754
|
-
if (!
|
|
5243
|
+
if (!existsSync27(resolvedEssencePath)) {
|
|
4755
5244
|
throw new Error(`Essence file not found at ${resolvedEssencePath}`);
|
|
4756
5245
|
}
|
|
4757
|
-
const code =
|
|
4758
|
-
const essence = JSON.parse(
|
|
4759
|
-
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;
|
|
4760
5249
|
const report = await client.critiqueFile(
|
|
4761
5250
|
{
|
|
4762
5251
|
essence,
|
|
@@ -4780,11 +5269,11 @@ async function printHostedFileCritique(sourcePath, namespace, jsonOutput = false
|
|
|
4780
5269
|
}
|
|
4781
5270
|
async function printHostedProjectAudit(namespace, jsonOutput = false, essencePath, distPath, sourcesPath) {
|
|
4782
5271
|
const client = getPublicAPIClient();
|
|
4783
|
-
const resolvedEssencePath = essencePath ? resolveUserPath(essencePath) :
|
|
4784
|
-
if (!
|
|
5272
|
+
const resolvedEssencePath = essencePath ? resolveUserPath(essencePath) : join28(process.cwd(), "decantr.essence.json");
|
|
5273
|
+
if (!existsSync27(resolvedEssencePath)) {
|
|
4785
5274
|
throw new Error(`Essence file not found at ${resolvedEssencePath}`);
|
|
4786
5275
|
}
|
|
4787
|
-
const essence = JSON.parse(
|
|
5276
|
+
const essence = JSON.parse(readFileSync20(resolvedEssencePath, "utf-8"));
|
|
4788
5277
|
const dist = readHostedDistSnapshot(distPath);
|
|
4789
5278
|
const sources = readHostedSourceSnapshot(sourcesPath);
|
|
4790
5279
|
const report = await client.auditProject(
|
|
@@ -4802,7 +5291,7 @@ async function printHostedProjectAudit(namespace, jsonOutput = false, essencePat
|
|
|
4802
5291
|
console.log(heading2("Hosted Project Audit"));
|
|
4803
5292
|
console.log(` Essence: ${resolvedEssencePath}`);
|
|
4804
5293
|
console.log(
|
|
4805
|
-
` Dist snapshot: ${dist ? distPath ? resolveUserPath(distPath) :
|
|
5294
|
+
` Dist snapshot: ${dist ? distPath ? resolveUserPath(distPath) : join28(process.cwd(), "dist") : "none"}`
|
|
4806
5295
|
);
|
|
4807
5296
|
console.log(
|
|
4808
5297
|
` Source snapshot: ${sources && sourcesPath ? resolveUserPath(sourcesPath) : "none"}`
|
|
@@ -4883,25 +5372,25 @@ async function cmdGet(type, id) {
|
|
|
4883
5372
|
}
|
|
4884
5373
|
const apiType = CONTENT_TYPE_TO_API_CONTENT_TYPE3[type];
|
|
4885
5374
|
const registryClient = new RegistryClient({
|
|
4886
|
-
cacheDir:
|
|
5375
|
+
cacheDir: join28(process.cwd(), ".decantr", "cache")
|
|
4887
5376
|
});
|
|
4888
5377
|
const result = await registryClient.fetchContentItem(apiType, id);
|
|
4889
5378
|
if (result) {
|
|
4890
5379
|
console.log(JSON.stringify(result.data, null, 2));
|
|
4891
5380
|
return;
|
|
4892
5381
|
}
|
|
4893
|
-
const currentDir =
|
|
5382
|
+
const currentDir = dirname4(fileURLToPath2(import.meta.url));
|
|
4894
5383
|
const bundledCandidates = [
|
|
4895
|
-
|
|
5384
|
+
join28(currentDir, "bundled", apiType, `${id}.json`),
|
|
4896
5385
|
// Running from src/
|
|
4897
|
-
|
|
5386
|
+
join28(currentDir, "..", "src", "bundled", apiType, `${id}.json`),
|
|
4898
5387
|
// Running from dist/
|
|
4899
|
-
|
|
5388
|
+
join28(currentDir, "..", "bundled", apiType, `${id}.json`)
|
|
4900
5389
|
// Alternative dist layout
|
|
4901
5390
|
];
|
|
4902
|
-
const bundledPath = bundledCandidates.find((p) =>
|
|
5391
|
+
const bundledPath = bundledCandidates.find((p) => existsSync27(p)) || null;
|
|
4903
5392
|
if (bundledPath) {
|
|
4904
|
-
const data = JSON.parse(
|
|
5393
|
+
const data = JSON.parse(readFileSync20(bundledPath, "utf-8"));
|
|
4905
5394
|
console.log(JSON.stringify(data, null, 2));
|
|
4906
5395
|
return;
|
|
4907
5396
|
}
|
|
@@ -4910,10 +5399,10 @@ async function cmdGet(type, id) {
|
|
|
4910
5399
|
return;
|
|
4911
5400
|
}
|
|
4912
5401
|
async function cmdValidate(path) {
|
|
4913
|
-
const essencePath = path ||
|
|
5402
|
+
const essencePath = path || join28(process.cwd(), "decantr.essence.json");
|
|
4914
5403
|
let raw;
|
|
4915
5404
|
try {
|
|
4916
|
-
raw =
|
|
5405
|
+
raw = readFileSync20(essencePath, "utf-8");
|
|
4917
5406
|
} catch {
|
|
4918
5407
|
console.error(error3(`Could not read ${essencePath}`));
|
|
4919
5408
|
process.exitCode = 1;
|
|
@@ -4982,7 +5471,7 @@ async function cmdList(type, sort, recommended, intelligenceSource) {
|
|
|
4982
5471
|
return;
|
|
4983
5472
|
}
|
|
4984
5473
|
const registryClient = new RegistryClient({
|
|
4985
|
-
cacheDir:
|
|
5474
|
+
cacheDir: join28(process.cwd(), ".decantr", "cache")
|
|
4986
5475
|
});
|
|
4987
5476
|
const result = await registryClient.fetchContentList(
|
|
4988
5477
|
type,
|
|
@@ -5029,11 +5518,17 @@ async function cmdList(type, sort, recommended, intelligenceSource) {
|
|
|
5029
5518
|
}
|
|
5030
5519
|
}
|
|
5031
5520
|
async function cmdInit(args) {
|
|
5032
|
-
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;
|
|
5033
5529
|
console.log(heading2("Decantr Project Setup"));
|
|
5034
5530
|
const detected = detectProject(projectRoot);
|
|
5035
5531
|
const workflowSeed = readBrownfieldInitSeed(projectRoot);
|
|
5036
|
-
const brownfieldAttach = Boolean(workflowSeed) || hasExistingProjectFootprint(detected);
|
|
5037
5532
|
if (workflowSeed) {
|
|
5038
5533
|
console.log(dim3(" Found .decantr/init-seed.json brownfield guidance."));
|
|
5039
5534
|
}
|
|
@@ -5048,11 +5543,27 @@ async function cmdInit(args) {
|
|
|
5048
5543
|
const requestedBlueprint = Boolean(args.blueprint);
|
|
5049
5544
|
const requestedArchetype = Boolean(args.archetype);
|
|
5050
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;
|
|
5051
5562
|
let offlineSeed = {
|
|
5052
5563
|
seeded: false,
|
|
5053
5564
|
strategy: null
|
|
5054
5565
|
};
|
|
5055
|
-
if (args.offline) {
|
|
5566
|
+
if (args.offline && shouldUseRegistry) {
|
|
5056
5567
|
offlineSeed = seedOfflineRegistry(projectRoot, projectRoot);
|
|
5057
5568
|
if (offlineSeed.seeded) {
|
|
5058
5569
|
console.log(dim3(` Seeded offline registry content from ${offlineSeed.strategy}.`));
|
|
@@ -5070,11 +5581,12 @@ async function cmdInit(args) {
|
|
|
5070
5581
|
}
|
|
5071
5582
|
}
|
|
5072
5583
|
const registryClient = new RegistryClient({
|
|
5073
|
-
cacheDir:
|
|
5584
|
+
cacheDir: join28(projectRoot, ".decantr", "cache"),
|
|
5074
5585
|
apiUrl: args.registry,
|
|
5075
|
-
offline: args.offline
|
|
5586
|
+
offline: args.offline,
|
|
5587
|
+
projectRoot
|
|
5076
5588
|
});
|
|
5077
|
-
const apiAvailable = await registryClient.checkApiAvailability();
|
|
5589
|
+
const apiAvailable = shouldUseRegistry ? await registryClient.checkApiAvailability() : false;
|
|
5078
5590
|
if (!apiAvailable && !args.offline && (requestedBlueprint || requestedArchetype)) {
|
|
5079
5591
|
const fallbackSeed = seedOfflineRegistry(projectRoot, projectRoot);
|
|
5080
5592
|
if (fallbackSeed.seeded) {
|
|
@@ -5084,18 +5596,21 @@ async function cmdInit(args) {
|
|
|
5084
5596
|
}
|
|
5085
5597
|
let selectedBlueprint = "default";
|
|
5086
5598
|
let registrySource = "cache";
|
|
5087
|
-
const preferContractOnly = brownfieldAttach && !requestedBlueprint && !requestedArchetype;
|
|
5088
|
-
const workflowMode = workflowSeed || brownfieldAttach && !requestedBlueprint && !requestedArchetype ? "brownfield-attach" : "greenfield-scaffold";
|
|
5089
5599
|
if (args.yes) {
|
|
5090
5600
|
selectedBlueprint = args.blueprint || "default";
|
|
5091
|
-
} else if (!apiAvailable) {
|
|
5601
|
+
} else if (shouldUseRegistry && !apiAvailable) {
|
|
5092
5602
|
if (!args.blueprint) {
|
|
5093
5603
|
console.log(`
|
|
5094
5604
|
${YELLOW9}You're offline. Scaffolding minimal Decantr project.${RESET13}`);
|
|
5095
5605
|
console.log(
|
|
5096
5606
|
dim3("Run `decantr sync` or `decantr upgrade` when online to pull full registry content.\n")
|
|
5097
5607
|
);
|
|
5098
|
-
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
|
+
});
|
|
5099
5614
|
console.log(success3("\nProject scaffolded (minimal/offline)!\n"));
|
|
5100
5615
|
console.log(" Files created:");
|
|
5101
5616
|
console.log(` ${cyan3("decantr.essence.json")} Design specification`);
|
|
@@ -5111,7 +5626,7 @@ ${YELLOW9}You're offline. Scaffolding minimal Decantr project.${RESET13}`);
|
|
|
5111
5626
|
` 2. Run ${cyan3("decantr refresh")} after syncing to generate scaffold, section, and page packs`
|
|
5112
5627
|
);
|
|
5113
5628
|
console.log(
|
|
5114
|
-
` 3. Read ${cyan3("
|
|
5629
|
+
` 3. Read ${cyan3(".decantr/context/scaffold-pack.md")} first, then use ${cyan3("DECANTR.md")} as a lookup reference`
|
|
5115
5630
|
);
|
|
5116
5631
|
console.log(
|
|
5117
5632
|
` 4. Use ${cyan3("decantr create <type> <name>")} to create custom content if needed`
|
|
@@ -5136,22 +5651,22 @@ ${YELLOW9}You're offline. Scaffolding minimal Decantr project.${RESET13}`);
|
|
|
5136
5651
|
${YELLOW9}You're offline. Scaffolding Decantr default.${RESET13}`);
|
|
5137
5652
|
console.log(dim3("Run `decantr upgrade` when online, or visit decantr.ai/registry\n"));
|
|
5138
5653
|
selectedBlueprint = "default";
|
|
5139
|
-
} else if (
|
|
5654
|
+
} else if (shouldUseRegistry) {
|
|
5140
5655
|
console.log(dim3("Fetching registry content..."));
|
|
5141
5656
|
const blueprintsResult2 = await registryClient.fetchBlueprints();
|
|
5142
5657
|
registrySource = blueprintsResult2.source.type === "api" ? "api" : "cache";
|
|
5143
5658
|
const { selectedBlueprint: selected } = await runSimplifiedInit(blueprintsResult2.data.items);
|
|
5144
5659
|
selectedBlueprint = selected || "default";
|
|
5145
5660
|
}
|
|
5146
|
-
const archetypesResult = await registryClient.fetchArchetypes();
|
|
5147
|
-
const blueprintsResult = await registryClient.fetchBlueprints();
|
|
5148
|
-
const themesResult = await registryClient.fetchThemes();
|
|
5149
|
-
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") {
|
|
5150
5665
|
registrySource = "api";
|
|
5151
5666
|
}
|
|
5152
|
-
const archetypes = archetypesResult
|
|
5153
|
-
const blueprints = blueprintsResult
|
|
5154
|
-
const themes = themesResult
|
|
5667
|
+
const archetypes = archetypesResult?.data.items ?? [];
|
|
5668
|
+
const blueprints = blueprintsResult?.data.items ?? [];
|
|
5669
|
+
const themes = themesResult?.data.items ?? [];
|
|
5155
5670
|
let options;
|
|
5156
5671
|
const userExplicit = {
|
|
5157
5672
|
theme: Boolean(args.theme),
|
|
@@ -5159,7 +5674,10 @@ ${YELLOW9}You're offline. Scaffolding Decantr default.${RESET13}`);
|
|
|
5159
5674
|
shape: Boolean(args.shape),
|
|
5160
5675
|
personality: Boolean(args.personality)
|
|
5161
5676
|
};
|
|
5162
|
-
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") {
|
|
5163
5681
|
const flags = parseFlags(args, detected);
|
|
5164
5682
|
flags.blueprint = selectedBlueprint !== "default" ? selectedBlueprint : flags.blueprint;
|
|
5165
5683
|
options = mergeWithDefaults(flags, detected, workflowSeed ?? void 0);
|
|
@@ -5176,14 +5694,22 @@ ${YELLOW9}You're offline. Scaffolding Decantr default.${RESET13}`);
|
|
|
5176
5694
|
userExplicit.shape = true;
|
|
5177
5695
|
userExplicit.personality = true;
|
|
5178
5696
|
}
|
|
5179
|
-
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;
|
|
5180
5706
|
let topologyMarkdown = "";
|
|
5181
5707
|
let archetypeData;
|
|
5182
5708
|
let composedSections;
|
|
5183
5709
|
let routeMap;
|
|
5184
5710
|
let patternSpecs;
|
|
5185
5711
|
let blueprintData;
|
|
5186
|
-
if (options.blueprint) {
|
|
5712
|
+
if (shouldUseRegistry && options.blueprint) {
|
|
5187
5713
|
const blueprintResult = await registryClient.fetchBlueprint(options.blueprint);
|
|
5188
5714
|
if (blueprintResult) {
|
|
5189
5715
|
const blueprint = blueprintResult.data;
|
|
@@ -5306,7 +5832,7 @@ ${YELLOW9}You're offline. Scaffolding Decantr default.${RESET13}`);
|
|
|
5306
5832
|
`${YELLOW9} Warning: Could not fetch blueprint "${options.blueprint}". Using defaults.${RESET13}`
|
|
5307
5833
|
);
|
|
5308
5834
|
}
|
|
5309
|
-
} else if (options.archetype) {
|
|
5835
|
+
} else if (shouldUseRegistry && options.archetype) {
|
|
5310
5836
|
const archetypeResult = await registryClient.fetchArchetype(options.archetype);
|
|
5311
5837
|
if (archetypeResult) {
|
|
5312
5838
|
archetypeData = mapRegistryArchetypeToArchetypeData(archetypeResult.data);
|
|
@@ -5323,7 +5849,7 @@ ${YELLOW9}You're offline. Scaffolding Decantr default.${RESET13}`);
|
|
|
5323
5849
|
}
|
|
5324
5850
|
}
|
|
5325
5851
|
let themeData;
|
|
5326
|
-
if (options.theme) {
|
|
5852
|
+
if (shouldUseRegistry && options.theme) {
|
|
5327
5853
|
const themeResult = await registryClient.fetchTheme(options.theme);
|
|
5328
5854
|
if (themeResult) {
|
|
5329
5855
|
themeData = mapRegistryThemeToThemeData(themeResult.data);
|
|
@@ -5355,6 +5881,19 @@ ${YELLOW9}You're offline. Scaffolding Decantr default.${RESET13}`);
|
|
|
5355
5881
|
patternSpecs,
|
|
5356
5882
|
blueprintData
|
|
5357
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
|
+
}
|
|
5358
5897
|
console.log(success3("\nProject scaffolded!\n"));
|
|
5359
5898
|
console.log(" Files created:");
|
|
5360
5899
|
console.log(` ${cyan3("decantr.essence.json")} Design specification`);
|
|
@@ -5363,7 +5902,13 @@ ${YELLOW9}You're offline. Scaffolding Decantr default.${RESET13}`);
|
|
|
5363
5902
|
if (result.gitignoreUpdated) {
|
|
5364
5903
|
console.log(` ${dim3(".gitignore updated")}`);
|
|
5365
5904
|
}
|
|
5366
|
-
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"))) {
|
|
5367
5912
|
console.log("");
|
|
5368
5913
|
console.log(
|
|
5369
5914
|
dim3(` Note: ${cyan3("decantr init")} created Decantr contract/context files only.`)
|
|
@@ -5376,14 +5921,15 @@ ${YELLOW9}You're offline. Scaffolding Decantr default.${RESET13}`);
|
|
|
5376
5921
|
}
|
|
5377
5922
|
console.log("");
|
|
5378
5923
|
console.log(" Next steps:");
|
|
5379
|
-
console.log(" 1. Read
|
|
5924
|
+
console.log(" 1. Read .decantr/context/scaffold-pack.md first as the primary compiled contract");
|
|
5380
5925
|
console.log(
|
|
5381
|
-
" 2. Read .decantr/context/scaffold
|
|
5926
|
+
" 2. Read .decantr/context/scaffold.md for broader topology, route map, and voice guidance"
|
|
5382
5927
|
);
|
|
5383
5928
|
console.log(" 3. Read the matching section and page packs before implementing each route");
|
|
5384
|
-
console.log(" 4.
|
|
5385
|
-
console.log(" 5.
|
|
5386
|
-
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");
|
|
5387
5933
|
console.log("");
|
|
5388
5934
|
console.log(" Commands:");
|
|
5389
5935
|
console.log(` ${cyan3("decantr status")} Project health`);
|
|
@@ -5393,7 +5939,7 @@ ${YELLOW9}You're offline. Scaffolding Decantr default.${RESET13}`);
|
|
|
5393
5939
|
console.log(` ${cyan3("decantr upgrade")} Update to latest patterns`);
|
|
5394
5940
|
console.log(` ${cyan3("decantr check")} Detect drift issues`);
|
|
5395
5941
|
console.log(` ${cyan3("decantr migrate")} Migrate v2 essence to v3`);
|
|
5396
|
-
const essenceContent =
|
|
5942
|
+
const essenceContent = readFileSync20(result.essencePath, "utf-8");
|
|
5397
5943
|
const essence = JSON.parse(essenceContent);
|
|
5398
5944
|
if (essence.version !== "3.1.0") {
|
|
5399
5945
|
const validation = validateEssence2(essence);
|
|
@@ -5406,11 +5952,12 @@ Validation warnings: ${validation.errors.join(", ")}`));
|
|
|
5406
5952
|
let promptPages;
|
|
5407
5953
|
if (isV36(essence)) {
|
|
5408
5954
|
const allPages = essence.blueprint.sections ? essence.blueprint.sections.flatMap(
|
|
5409
|
-
(s) => s.pages.map((p) => ({ ...p, _shell: s.shell }))
|
|
5955
|
+
(s) => s.pages.map((p) => ({ ...p, _sectionId: s.id, _shell: s.shell }))
|
|
5410
5956
|
) : essence.blueprint.pages || [];
|
|
5411
5957
|
promptPages = allPages.map(
|
|
5412
5958
|
(p) => ({
|
|
5413
5959
|
id: p.id,
|
|
5960
|
+
sectionId: p._sectionId,
|
|
5414
5961
|
shell: p.shell_override ?? p._shell ?? essence.blueprint.shell,
|
|
5415
5962
|
layout: (p.layout || []).map(
|
|
5416
5963
|
(item) => typeof item === "string" ? item : extractPatternName(item)
|
|
@@ -5421,7 +5968,9 @@ Validation warnings: ${validation.errors.join(", ")}`));
|
|
|
5421
5968
|
promptPages = essence.structure || [{ id: "home", shell: options.shell, layout: ["hero"] }];
|
|
5422
5969
|
}
|
|
5423
5970
|
const promptCtx = {
|
|
5424
|
-
workflow: options.workflowMode
|
|
5971
|
+
workflow: options.workflowMode || "greenfield-scaffold",
|
|
5972
|
+
adoptionMode: options.adoptionMode,
|
|
5973
|
+
analysisArtifacts: options.analysisArtifacts,
|
|
5425
5974
|
archetype: options.archetype || "custom",
|
|
5426
5975
|
blueprint: options.blueprint,
|
|
5427
5976
|
theme: options.theme,
|
|
@@ -5447,16 +5996,16 @@ Validation warnings: ${validation.errors.join(", ")}`));
|
|
|
5447
5996
|
}
|
|
5448
5997
|
async function cmdStatus() {
|
|
5449
5998
|
const projectRoot = process.cwd();
|
|
5450
|
-
const essencePath =
|
|
5451
|
-
const projectJsonPath =
|
|
5999
|
+
const essencePath = join28(projectRoot, "decantr.essence.json");
|
|
6000
|
+
const projectJsonPath = join28(projectRoot, ".decantr", "project.json");
|
|
5452
6001
|
console.log(heading2("Decantr Project Status"));
|
|
5453
|
-
if (!
|
|
6002
|
+
if (!existsSync27(essencePath)) {
|
|
5454
6003
|
console.log(`${RED11}No decantr.essence.json found.${RESET13}`);
|
|
5455
6004
|
console.log(dim3('Run "decantr init" to create one.'));
|
|
5456
6005
|
return;
|
|
5457
6006
|
}
|
|
5458
6007
|
try {
|
|
5459
|
-
const essence = JSON.parse(
|
|
6008
|
+
const essence = JSON.parse(readFileSync20(essencePath, "utf-8"));
|
|
5460
6009
|
const validation = validateEssence2(essence);
|
|
5461
6010
|
const essenceVersion = isV36(essence) ? "v3" : "v2";
|
|
5462
6011
|
console.log(`${BOLD6}Essence:${RESET13}`);
|
|
@@ -5513,9 +6062,9 @@ async function cmdStatus() {
|
|
|
5513
6062
|
}
|
|
5514
6063
|
console.log("");
|
|
5515
6064
|
console.log(`${BOLD6}Sync Status:${RESET13}`);
|
|
5516
|
-
if (
|
|
6065
|
+
if (existsSync27(projectJsonPath)) {
|
|
5517
6066
|
try {
|
|
5518
|
-
const projectJson = JSON.parse(
|
|
6067
|
+
const projectJson = JSON.parse(readFileSync20(projectJsonPath, "utf-8"));
|
|
5519
6068
|
const syncStatus = projectJson.sync?.status || "unknown";
|
|
5520
6069
|
const lastSync = projectJson.sync?.lastSync || "never";
|
|
5521
6070
|
const source = projectJson.sync?.registrySource || "unknown";
|
|
@@ -5533,7 +6082,7 @@ async function cmdStatus() {
|
|
|
5533
6082
|
}
|
|
5534
6083
|
async function cmdSync() {
|
|
5535
6084
|
const projectRoot = process.cwd();
|
|
5536
|
-
const cacheDir =
|
|
6085
|
+
const cacheDir = join28(projectRoot, ".decantr", "cache");
|
|
5537
6086
|
console.log(heading2("Syncing registry content..."));
|
|
5538
6087
|
const result = await syncRegistry(cacheDir);
|
|
5539
6088
|
if (result.synced.length > 0) {
|
|
@@ -5728,14 +6277,14 @@ ${BOLD6}Examples:${RESET13}
|
|
|
5728
6277
|
process.exitCode = 1;
|
|
5729
6278
|
return;
|
|
5730
6279
|
}
|
|
5731
|
-
const themePath =
|
|
5732
|
-
if (!
|
|
6280
|
+
const themePath = join28(projectRoot, ".decantr", "custom", "themes", `${name}.json`);
|
|
6281
|
+
if (!existsSync27(themePath)) {
|
|
5733
6282
|
console.error(error3(`Theme "${name}" not found at ${themePath}`));
|
|
5734
6283
|
process.exitCode = 1;
|
|
5735
6284
|
return;
|
|
5736
6285
|
}
|
|
5737
6286
|
try {
|
|
5738
|
-
const theme = JSON.parse(
|
|
6287
|
+
const theme = JSON.parse(readFileSync20(themePath, "utf-8"));
|
|
5739
6288
|
const result = validateCustomTheme(theme);
|
|
5740
6289
|
if (result.valid) {
|
|
5741
6290
|
console.log(success3(`Custom theme "${name}" is valid`));
|
|
@@ -5808,7 +6357,7 @@ function cmdHelp() {
|
|
|
5808
6357
|
${BOLD6}decantr${RESET13} \u2014 Design intelligence for AI-generated UI
|
|
5809
6358
|
|
|
5810
6359
|
${BOLD6}Usage:${RESET13}
|
|
5811
|
-
decantr new <name> [--blueprint=X] [--archetype=X] [--theme=X]
|
|
6360
|
+
decantr new <name> [--blueprint=X] [--archetype=X] [--theme=X] [--workflow=greenfield] [--adoption=decantr-css]
|
|
5812
6361
|
decantr magic <prompt> [--dry-run]
|
|
5813
6362
|
decantr init [options]
|
|
5814
6363
|
decantr status
|
|
@@ -5827,6 +6376,7 @@ ${BOLD6}Usage:${RESET13}
|
|
|
5827
6376
|
decantr registry get-pack <manifest|scaffold|review|section|page|mutation> [id] [--namespace <namespace>] [--json] [--essence <path>] [--write-context]
|
|
5828
6377
|
decantr registry critique-file <file> [--namespace <namespace>] [--json] [--essence <path>] [--treatments <path>]
|
|
5829
6378
|
decantr registry audit-project [--namespace <namespace>] [--json] [--essence <path>] [--dist <path>] [--sources <dir>]
|
|
6379
|
+
decantr rules apply [--project=<path>]
|
|
5830
6380
|
decantr validate [path]
|
|
5831
6381
|
decantr theme <subcommand>
|
|
5832
6382
|
decantr create <type> <name>
|
|
@@ -5845,6 +6395,10 @@ ${BOLD6}Init Options:${RESET13}
|
|
|
5845
6395
|
--guard Guard mode: creative | guided | strict
|
|
5846
6396
|
--density Spacing: compact | comfortable | spacious
|
|
5847
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
|
|
5848
6402
|
--existing Initialize in existing project
|
|
5849
6403
|
--offline Force offline mode
|
|
5850
6404
|
--yes, -y Accept defaults, skip confirmations
|
|
@@ -5874,6 +6428,7 @@ ${BOLD6}Commands:${RESET13}
|
|
|
5874
6428
|
${cyan3("analyze")} Brownfield entrypoint: scan an existing project and emit attach guidance
|
|
5875
6429
|
${cyan3("export")} Export design tokens to framework format (shadcn, tailwind, css-vars)
|
|
5876
6430
|
${cyan3("registry")} Registry management and intelligence summary
|
|
6431
|
+
${cyan3("rules")} Preview/apply Decantr assistant bridge blocks to repo rule files
|
|
5877
6432
|
${cyan3("upgrade")} Check for content updates from registry
|
|
5878
6433
|
${cyan3("help")} Show this help
|
|
5879
6434
|
|
|
@@ -5881,7 +6436,11 @@ ${BOLD6}Examples:${RESET13}
|
|
|
5881
6436
|
decantr new my-app --blueprint=carbon-ai-portal
|
|
5882
6437
|
decantr magic "AI chatbot with dark cyber theme \u2014 bold and futuristic"
|
|
5883
6438
|
decantr init
|
|
5884
|
-
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
|
|
5885
6444
|
decantr status
|
|
5886
6445
|
decantr audit
|
|
5887
6446
|
decantr audit src/pages/HomePage.tsx
|
|
@@ -5904,13 +6463,14 @@ ${BOLD6}Examples:${RESET13}
|
|
|
5904
6463
|
decantr create pattern my-card
|
|
5905
6464
|
|
|
5906
6465
|
${BOLD6}Workflow Model:${RESET13}
|
|
5907
|
-
${cyan3("Greenfield blueprint")} decantr new
|
|
5908
|
-
${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
|
|
5909
6469
|
${cyan3("Hybrid composition")} decantr add/remove, decantr theme switch, decantr registry, decantr upgrade
|
|
5910
6470
|
|
|
5911
6471
|
${BOLD6}Bootstrap adapters:${RESET13}
|
|
5912
|
-
|
|
5913
|
-
|
|
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.
|
|
5914
6474
|
`);
|
|
5915
6475
|
}
|
|
5916
6476
|
async function main() {
|
|
@@ -5922,14 +6482,14 @@ async function main() {
|
|
|
5922
6482
|
}
|
|
5923
6483
|
if (command === "--version" || command === "-v" || command === "version") {
|
|
5924
6484
|
try {
|
|
5925
|
-
const here =
|
|
6485
|
+
const here = dirname4(fileURLToPath2(import.meta.url));
|
|
5926
6486
|
const candidates = [
|
|
5927
|
-
|
|
5928
|
-
|
|
6487
|
+
join28(here, "..", "package.json"),
|
|
6488
|
+
join28(here, "..", "..", "package.json")
|
|
5929
6489
|
];
|
|
5930
6490
|
for (const candidate of candidates) {
|
|
5931
|
-
if (
|
|
5932
|
-
const pkg = JSON.parse(
|
|
6491
|
+
if (existsSync27(candidate)) {
|
|
6492
|
+
const pkg = JSON.parse(readFileSync20(candidate, "utf-8"));
|
|
5933
6493
|
if (pkg.version) {
|
|
5934
6494
|
console.log(pkg.version);
|
|
5935
6495
|
return;
|
|
@@ -5976,7 +6536,10 @@ async function main() {
|
|
|
5976
6536
|
shape: newOpts.shape,
|
|
5977
6537
|
target: newOpts.target,
|
|
5978
6538
|
offline: newOpts.offline === true,
|
|
5979
|
-
registry: newOpts.registry
|
|
6539
|
+
registry: newOpts.registry,
|
|
6540
|
+
workflow: newOpts.workflow,
|
|
6541
|
+
adoption: newOpts.adoption,
|
|
6542
|
+
assistantBridge: newOpts["assistant-bridge"]
|
|
5980
6543
|
});
|
|
5981
6544
|
break;
|
|
5982
6545
|
}
|
|
@@ -6015,7 +6578,7 @@ async function main() {
|
|
|
6015
6578
|
break;
|
|
6016
6579
|
}
|
|
6017
6580
|
case "upgrade": {
|
|
6018
|
-
const { cmdUpgrade } = await import("./upgrade-
|
|
6581
|
+
const { cmdUpgrade } = await import("./upgrade-KG42WK5C.js");
|
|
6019
6582
|
const applyFlag = args.includes("--apply");
|
|
6020
6583
|
await cmdUpgrade(process.cwd(), { apply: applyFlag });
|
|
6021
6584
|
break;
|
|
@@ -6407,7 +6970,47 @@ async function main() {
|
|
|
6407
6970
|
break;
|
|
6408
6971
|
}
|
|
6409
6972
|
case "analyze": {
|
|
6410
|
-
|
|
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
|
+
}
|
|
6411
7014
|
break;
|
|
6412
7015
|
}
|
|
6413
7016
|
case "magic": {
|