@decantr/cli 1.7.24 → 1.7.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -4
- package/dist/bin.js +2 -2
- package/dist/{chunk-RH6IQ7YX.js → chunk-3K6HWLD5.js} +221 -47
- package/dist/{chunk-7H3PAC5Y.js → chunk-RAAUNHXD.js} +1334 -635
- package/dist/index.js +2 -2
- package/dist/{upgrade-7LHT3S7E.js → upgrade-KG42WK5C.js} +1 -1
- package/package.json +12 -8
- package/LICENSE +0 -21
|
@@ -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 {
|
|
@@ -40,29 +40,63 @@ import {
|
|
|
40
40
|
} from "@decantr/verifier";
|
|
41
41
|
|
|
42
42
|
// src/auth.ts
|
|
43
|
-
import {
|
|
43
|
+
import {
|
|
44
|
+
chmodSync,
|
|
45
|
+
existsSync,
|
|
46
|
+
mkdirSync,
|
|
47
|
+
readFileSync,
|
|
48
|
+
rmSync,
|
|
49
|
+
statSync,
|
|
50
|
+
writeFileSync
|
|
51
|
+
} from "fs";
|
|
44
52
|
import { homedir } from "os";
|
|
45
53
|
import { join } from "path";
|
|
46
|
-
var
|
|
47
|
-
var
|
|
54
|
+
var CONFIG_DIR_MODE = 448;
|
|
55
|
+
var AUTH_FILE_MODE = 384;
|
|
56
|
+
function getConfigDir() {
|
|
57
|
+
return process.env.DECANTR_CONFIG_DIR || join(homedir(), ".config", "decantr");
|
|
58
|
+
}
|
|
59
|
+
function getAuthFile() {
|
|
60
|
+
return join(getConfigDir(), "auth.json");
|
|
61
|
+
}
|
|
62
|
+
function chmodIfNeeded(path, mode) {
|
|
63
|
+
try {
|
|
64
|
+
if ((statSync(path).mode & 511) !== mode) {
|
|
65
|
+
chmodSync(path, mode);
|
|
66
|
+
}
|
|
67
|
+
} catch {
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function ensureConfigDir() {
|
|
71
|
+
const configDir = getConfigDir();
|
|
72
|
+
mkdirSync(configDir, { recursive: true, mode: CONFIG_DIR_MODE });
|
|
73
|
+
chmodIfNeeded(configDir, CONFIG_DIR_MODE);
|
|
74
|
+
}
|
|
48
75
|
function getCredentials() {
|
|
49
|
-
|
|
76
|
+
const authFile = getAuthFile();
|
|
77
|
+
if (!existsSync(authFile)) return null;
|
|
50
78
|
try {
|
|
51
|
-
|
|
79
|
+
chmodIfNeeded(authFile, AUTH_FILE_MODE);
|
|
80
|
+
return JSON.parse(readFileSync(authFile, "utf-8"));
|
|
52
81
|
} catch {
|
|
53
82
|
return null;
|
|
54
83
|
}
|
|
55
84
|
}
|
|
56
85
|
function saveCredentials(creds) {
|
|
57
|
-
|
|
58
|
-
|
|
86
|
+
ensureConfigDir();
|
|
87
|
+
const authFile = getAuthFile();
|
|
88
|
+
writeFileSync(authFile, JSON.stringify(creds, null, 2), { mode: AUTH_FILE_MODE });
|
|
89
|
+
chmodIfNeeded(authFile, AUTH_FILE_MODE);
|
|
59
90
|
}
|
|
60
91
|
function clearCredentials() {
|
|
61
|
-
|
|
62
|
-
|
|
92
|
+
const authFile = getAuthFile();
|
|
93
|
+
if (existsSync(authFile)) {
|
|
94
|
+
rmSync(authFile);
|
|
63
95
|
}
|
|
64
96
|
}
|
|
65
97
|
function getApiKeyOrToken() {
|
|
98
|
+
const envKey = process.env.DECANTR_API_KEY?.trim();
|
|
99
|
+
if (envKey) return envKey;
|
|
66
100
|
const creds = getCredentials();
|
|
67
101
|
if (!creds) return null;
|
|
68
102
|
return creds.api_key || creds.access_token || null;
|
|
@@ -229,13 +263,120 @@ async function cmdAddFeature(feature, args, projectRoot = process.cwd()) {
|
|
|
229
263
|
console.log(`${GREEN}Derived files refreshed.${RESET}`);
|
|
230
264
|
}
|
|
231
265
|
|
|
266
|
+
// src/assistant-bridge.ts
|
|
267
|
+
import { appendFileSync, existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
268
|
+
import { dirname, join as join3 } from "path";
|
|
269
|
+
var START = "<!-- decantr:assistant-bridge:start -->";
|
|
270
|
+
var END = "<!-- decantr:assistant-bridge:end -->";
|
|
271
|
+
function buildAssistantBridgeContent(input) {
|
|
272
|
+
const lines = [];
|
|
273
|
+
lines.push("# Decantr Assistant Bridge");
|
|
274
|
+
lines.push("");
|
|
275
|
+
lines.push(
|
|
276
|
+
"Use this bridge when an AI assistant works in a repository that already has local rules."
|
|
277
|
+
);
|
|
278
|
+
lines.push("");
|
|
279
|
+
lines.push(`- Workflow mode: ${input.workflowMode}`);
|
|
280
|
+
lines.push(`- Detected framework: ${input.detected.framework}`);
|
|
281
|
+
lines.push(`- Package manager: ${input.detected.packageManager}`);
|
|
282
|
+
lines.push(
|
|
283
|
+
`- Existing rule files: ${input.detected.existingRuleFiles.length > 0 ? input.detected.existingRuleFiles.join(", ") : "none detected"}`
|
|
284
|
+
);
|
|
285
|
+
lines.push(`- Bridge mode: ${input.assistantBridge}`);
|
|
286
|
+
lines.push("");
|
|
287
|
+
lines.push("## Suggested Rule Block");
|
|
288
|
+
lines.push("");
|
|
289
|
+
lines.push(START);
|
|
290
|
+
lines.push(
|
|
291
|
+
"Before implementing Decantr-scoped work, read `DECANTR.md`, `decantr.essence.json`, and the compiled packs in `.decantr/context/`."
|
|
292
|
+
);
|
|
293
|
+
lines.push(
|
|
294
|
+
"For brownfield adoption, preserve existing framework, routing, styling, package manager, and build conventions unless the Decantr contract explicitly requires a reviewed change."
|
|
295
|
+
);
|
|
296
|
+
lines.push(
|
|
297
|
+
"Use `.decantr/context/scaffold-pack.md` as the primary compact contract, then read matching section/page packs for the surface being changed."
|
|
298
|
+
);
|
|
299
|
+
lines.push(
|
|
300
|
+
"Do not install `@decantr/css` or rewrite styling unless the project adoption mode says `decantr-css` or the task explicitly asks for it."
|
|
301
|
+
);
|
|
302
|
+
lines.push(END);
|
|
303
|
+
lines.push("");
|
|
304
|
+
return `${lines.join("\n")}
|
|
305
|
+
`;
|
|
306
|
+
}
|
|
307
|
+
function writeAssistantBridgePreview(input) {
|
|
308
|
+
const contextDir = join3(input.projectRoot, ".decantr", "context");
|
|
309
|
+
mkdirSync2(contextDir, { recursive: true });
|
|
310
|
+
const bridgePath = join3(contextDir, "assistant-bridge.md");
|
|
311
|
+
writeFileSync3(bridgePath, buildAssistantBridgeContent(input));
|
|
312
|
+
return bridgePath;
|
|
313
|
+
}
|
|
314
|
+
function bridgeBlock() {
|
|
315
|
+
return [
|
|
316
|
+
START,
|
|
317
|
+
"Before implementing Decantr-scoped work, read `DECANTR.md`, `decantr.essence.json`, and `.decantr/context/scaffold-pack.md` first.",
|
|
318
|
+
"For brownfield adoption, preserve existing framework, routing, styling, package manager, and build conventions unless the Decantr contract explicitly requires a reviewed change.",
|
|
319
|
+
"Do not install `@decantr/css` or rewrite styling unless the project adoption mode says `decantr-css` or the task explicitly asks for it.",
|
|
320
|
+
END,
|
|
321
|
+
""
|
|
322
|
+
].join("\n");
|
|
323
|
+
}
|
|
324
|
+
function upsertMarkdownBlock(path) {
|
|
325
|
+
const block = bridgeBlock();
|
|
326
|
+
if (!existsSync3(path)) {
|
|
327
|
+
mkdirSync2(dirname(path), { recursive: true });
|
|
328
|
+
writeFileSync3(path, `# Project Rules
|
|
329
|
+
|
|
330
|
+
${block}`);
|
|
331
|
+
return true;
|
|
332
|
+
}
|
|
333
|
+
const content = readFileSync3(path, "utf-8");
|
|
334
|
+
if (content.includes(START) && content.includes(END)) return false;
|
|
335
|
+
appendFileSync(path, `
|
|
336
|
+
|
|
337
|
+
${block}`);
|
|
338
|
+
return true;
|
|
339
|
+
}
|
|
340
|
+
function writeCursorRule(projectRoot) {
|
|
341
|
+
const path = join3(projectRoot, ".cursor", "rules", "decantr.mdc");
|
|
342
|
+
const content = `---
|
|
343
|
+
description: Decantr project contract and brownfield adoption bridge
|
|
344
|
+
alwaysApply: true
|
|
345
|
+
---
|
|
346
|
+
|
|
347
|
+
${bridgeBlock()}`;
|
|
348
|
+
if (existsSync3(path) && readFileSync3(path, "utf-8") === content) return false;
|
|
349
|
+
mkdirSync2(dirname(path), { recursive: true });
|
|
350
|
+
writeFileSync3(path, content);
|
|
351
|
+
return true;
|
|
352
|
+
}
|
|
353
|
+
function applyAssistantBridge(projectRoot, detected) {
|
|
354
|
+
const updated = [];
|
|
355
|
+
const markdownTargets = ["CLAUDE.md", "AGENTS.md", "GEMINI.md", "copilot-instructions.md"];
|
|
356
|
+
for (const target of markdownTargets) {
|
|
357
|
+
if (detected.existingRuleFiles.includes(target) && upsertMarkdownBlock(join3(projectRoot, target))) {
|
|
358
|
+
updated.push(target);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
if (detected.existingRuleFiles.includes(".cursorrules")) {
|
|
362
|
+
if (upsertMarkdownBlock(join3(projectRoot, ".cursorrules"))) updated.push(".cursorrules");
|
|
363
|
+
}
|
|
364
|
+
if (detected.existingRuleFiles.includes(".cursor/rules")) {
|
|
365
|
+
if (writeCursorRule(projectRoot)) updated.push(".cursor/rules/decantr.mdc");
|
|
366
|
+
}
|
|
367
|
+
if (updated.length === 0 && detected.existingRuleFiles.length === 0) {
|
|
368
|
+
if (writeCursorRule(projectRoot)) updated.push(".cursor/rules/decantr.mdc");
|
|
369
|
+
}
|
|
370
|
+
return updated;
|
|
371
|
+
}
|
|
372
|
+
|
|
232
373
|
// src/commands/analyze.ts
|
|
233
|
-
import { existsSync as
|
|
234
|
-
import { join as
|
|
374
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync3, writeFileSync as writeFileSync4 } from "fs";
|
|
375
|
+
import { join as join12 } from "path";
|
|
235
376
|
|
|
236
377
|
// src/analyzers/components.ts
|
|
237
|
-
import { existsSync as
|
|
238
|
-
import { join as
|
|
378
|
+
import { existsSync as existsSync4, readdirSync, statSync as statSync2 } from "fs";
|
|
379
|
+
import { join as join4 } from "path";
|
|
239
380
|
var PAGE_EXTENSIONS = /* @__PURE__ */ new Set([".tsx", ".ts", ".jsx", ".js"]);
|
|
240
381
|
var ROOT_COMPONENT_CANDIDATES = [
|
|
241
382
|
"src/App.tsx",
|
|
@@ -257,9 +398,9 @@ function countFilesRecursive(dir, extensions) {
|
|
|
257
398
|
}
|
|
258
399
|
for (const entry of entries) {
|
|
259
400
|
if (entry.startsWith(".") || entry === "node_modules") continue;
|
|
260
|
-
const fullPath =
|
|
401
|
+
const fullPath = join4(dir, entry);
|
|
261
402
|
try {
|
|
262
|
-
const stat =
|
|
403
|
+
const stat = statSync2(fullPath);
|
|
263
404
|
if (stat.isDirectory()) {
|
|
264
405
|
count += countFilesRecursive(fullPath, extensions);
|
|
265
406
|
} else if (stat.isFile()) {
|
|
@@ -283,9 +424,9 @@ function countPageFiles(dir) {
|
|
|
283
424
|
}
|
|
284
425
|
for (const entry of entries) {
|
|
285
426
|
if (entry.startsWith(".") || entry === "node_modules") continue;
|
|
286
|
-
const fullPath =
|
|
427
|
+
const fullPath = join4(dir, entry);
|
|
287
428
|
try {
|
|
288
|
-
const stat =
|
|
429
|
+
const stat = statSync2(fullPath);
|
|
289
430
|
if (stat.isDirectory()) {
|
|
290
431
|
count += countPageFiles(fullPath);
|
|
291
432
|
} else if (stat.isFile()) {
|
|
@@ -301,27 +442,27 @@ function countPageFiles(dir) {
|
|
|
301
442
|
function scanComponents(projectRoot) {
|
|
302
443
|
let pageCount = 0;
|
|
303
444
|
const componentDirs = [];
|
|
304
|
-
const appDirs = [
|
|
445
|
+
const appDirs = [join4(projectRoot, "src", "app"), join4(projectRoot, "app")];
|
|
305
446
|
for (const dir of appDirs) {
|
|
306
|
-
if (
|
|
447
|
+
if (existsSync4(dir)) {
|
|
307
448
|
pageCount += countPageFiles(dir);
|
|
308
449
|
}
|
|
309
450
|
}
|
|
310
|
-
const pagesDirs = [
|
|
451
|
+
const pagesDirs = [join4(projectRoot, "src", "pages"), join4(projectRoot, "pages")];
|
|
311
452
|
for (const dir of pagesDirs) {
|
|
312
|
-
if (
|
|
453
|
+
if (existsSync4(dir)) {
|
|
313
454
|
pageCount += countFilesRecursive(dir, PAGE_EXTENSIONS);
|
|
314
455
|
}
|
|
315
456
|
}
|
|
316
457
|
let componentCount = 0;
|
|
317
458
|
const componentPaths = [
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
459
|
+
join4(projectRoot, "src", "components"),
|
|
460
|
+
join4(projectRoot, "components"),
|
|
461
|
+
join4(projectRoot, "src", "ui"),
|
|
462
|
+
join4(projectRoot, "ui")
|
|
322
463
|
];
|
|
323
464
|
for (const dir of componentPaths) {
|
|
324
|
-
if (
|
|
465
|
+
if (existsSync4(dir)) {
|
|
325
466
|
const count = countFilesRecursive(dir, PAGE_EXTENSIONS);
|
|
326
467
|
if (count > 0) {
|
|
327
468
|
componentCount += count;
|
|
@@ -331,7 +472,7 @@ function scanComponents(projectRoot) {
|
|
|
331
472
|
}
|
|
332
473
|
}
|
|
333
474
|
const hasRootAppComponent = ROOT_COMPONENT_CANDIDATES.some(
|
|
334
|
-
(relativePath) =>
|
|
475
|
+
(relativePath) => existsSync4(join4(projectRoot, relativePath))
|
|
335
476
|
);
|
|
336
477
|
if (pageCount === 0 && hasRootAppComponent) {
|
|
337
478
|
pageCount = 1;
|
|
@@ -350,8 +491,8 @@ function scanComponents(projectRoot) {
|
|
|
350
491
|
}
|
|
351
492
|
|
|
352
493
|
// src/analyzers/dependencies.ts
|
|
353
|
-
import { existsSync as
|
|
354
|
-
import { join as
|
|
494
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
|
|
495
|
+
import { join as join5 } from "path";
|
|
355
496
|
var CATEGORIES = {
|
|
356
497
|
ui: [
|
|
357
498
|
"react",
|
|
@@ -501,11 +642,11 @@ function scanDependencies(projectRoot) {
|
|
|
501
642
|
styling: [],
|
|
502
643
|
other: []
|
|
503
644
|
};
|
|
504
|
-
const pkgPath =
|
|
505
|
-
if (!
|
|
645
|
+
const pkgPath = join5(projectRoot, "package.json");
|
|
646
|
+
if (!existsSync5(pkgPath)) return result;
|
|
506
647
|
let pkg;
|
|
507
648
|
try {
|
|
508
|
-
pkg = JSON.parse(
|
|
649
|
+
pkg = JSON.parse(readFileSync4(pkgPath, "utf-8"));
|
|
509
650
|
} catch {
|
|
510
651
|
return result;
|
|
511
652
|
}
|
|
@@ -525,8 +666,8 @@ function scanDependencies(projectRoot) {
|
|
|
525
666
|
}
|
|
526
667
|
|
|
527
668
|
// src/analyzers/features.ts
|
|
528
|
-
import { existsSync as
|
|
529
|
-
import { join as
|
|
669
|
+
import { existsSync as existsSync6, readdirSync as readdirSync2, statSync as statSync3 } from "fs";
|
|
670
|
+
import { join as join6 } from "path";
|
|
530
671
|
var FEATURE_PATTERNS = {
|
|
531
672
|
auth: [
|
|
532
673
|
"login",
|
|
@@ -579,11 +720,11 @@ function collectPaths(dir, baseDir, depth = 0) {
|
|
|
579
720
|
for (const entry of entries) {
|
|
580
721
|
if (entry.startsWith(".") || entry.startsWith("_") || entry === "node_modules" || entry === "api")
|
|
581
722
|
continue;
|
|
582
|
-
const fullPath =
|
|
723
|
+
const fullPath = join6(dir, entry);
|
|
583
724
|
const relPath = fullPath.slice(baseDir.length + 1);
|
|
584
725
|
paths.push(relPath);
|
|
585
726
|
try {
|
|
586
|
-
if (
|
|
727
|
+
if (statSync3(fullPath).isDirectory()) {
|
|
587
728
|
paths.push(...collectPaths(fullPath, baseDir, depth + 1));
|
|
588
729
|
}
|
|
589
730
|
} catch {
|
|
@@ -595,16 +736,16 @@ function scanFeatures(projectRoot) {
|
|
|
595
736
|
const detected = [];
|
|
596
737
|
const evidence = {};
|
|
597
738
|
const scanDirs = [
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
739
|
+
join6(projectRoot, "src", "app"),
|
|
740
|
+
join6(projectRoot, "app"),
|
|
741
|
+
join6(projectRoot, "src", "pages"),
|
|
742
|
+
join6(projectRoot, "pages"),
|
|
743
|
+
join6(projectRoot, "src", "components"),
|
|
744
|
+
join6(projectRoot, "components")
|
|
604
745
|
];
|
|
605
746
|
const allPaths = [];
|
|
606
747
|
for (const dir of scanDirs) {
|
|
607
|
-
if (
|
|
748
|
+
if (existsSync6(dir)) {
|
|
608
749
|
allPaths.push(...collectPaths(dir, projectRoot));
|
|
609
750
|
}
|
|
610
751
|
}
|
|
@@ -628,8 +769,8 @@ function scanFeatures(projectRoot) {
|
|
|
628
769
|
}
|
|
629
770
|
|
|
630
771
|
// src/analyzers/layout.ts
|
|
631
|
-
import { existsSync as
|
|
632
|
-
import { join as
|
|
772
|
+
import { existsSync as existsSync7, readdirSync as readdirSync3, readFileSync as readFileSync5 } from "fs";
|
|
773
|
+
import { join as join7 } from "path";
|
|
633
774
|
var SIDEBAR_PATTERNS = ["sidebar", "side-bar", "sidenav", "side-nav", "drawer", "aside"];
|
|
634
775
|
var NAV_PATTERNS = ["nav", "navbar", "header", "top-bar", "topbar", "app-bar", "appbar"];
|
|
635
776
|
var FOOTER_PATTERNS = ["footer", "bottom-bar", "bottombar"];
|
|
@@ -640,12 +781,12 @@ function containsPattern(text, patterns) {
|
|
|
640
781
|
function checkComponentDirs(projectRoot) {
|
|
641
782
|
const result = { sidebar: false, nav: false, footer: false };
|
|
642
783
|
const componentDirs = [
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
784
|
+
join7(projectRoot, "src", "components"),
|
|
785
|
+
join7(projectRoot, "components"),
|
|
786
|
+
join7(projectRoot, "src", "ui")
|
|
646
787
|
];
|
|
647
788
|
for (const dir of componentDirs) {
|
|
648
|
-
if (!
|
|
789
|
+
if (!existsSync7(dir)) continue;
|
|
649
790
|
let entries;
|
|
650
791
|
try {
|
|
651
792
|
entries = readdirSync3(dir);
|
|
@@ -664,16 +805,16 @@ function checkComponentDirs(projectRoot) {
|
|
|
664
805
|
function checkLayoutFiles(projectRoot) {
|
|
665
806
|
const result = { sidebar: false, nav: false, footer: false };
|
|
666
807
|
const layoutPaths = [
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
808
|
+
join7(projectRoot, "src", "app", "layout.tsx"),
|
|
809
|
+
join7(projectRoot, "src", "app", "layout.jsx"),
|
|
810
|
+
join7(projectRoot, "app", "layout.tsx"),
|
|
811
|
+
join7(projectRoot, "app", "layout.jsx")
|
|
671
812
|
];
|
|
672
813
|
for (const layoutPath of layoutPaths) {
|
|
673
|
-
if (!
|
|
814
|
+
if (!existsSync7(layoutPath)) continue;
|
|
674
815
|
let content;
|
|
675
816
|
try {
|
|
676
|
-
content =
|
|
817
|
+
content = readFileSync5(layoutPath, "utf-8");
|
|
677
818
|
} catch {
|
|
678
819
|
continue;
|
|
679
820
|
}
|
|
@@ -684,16 +825,16 @@ function checkLayoutFiles(projectRoot) {
|
|
|
684
825
|
const subLayoutDirs = ["dashboard", "admin", "app"];
|
|
685
826
|
for (const sub of subLayoutDirs) {
|
|
686
827
|
const paths = [
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
828
|
+
join7(projectRoot, "src", "app", sub, "layout.tsx"),
|
|
829
|
+
join7(projectRoot, "src", "app", sub, "layout.jsx"),
|
|
830
|
+
join7(projectRoot, "app", sub, "layout.tsx"),
|
|
831
|
+
join7(projectRoot, "app", sub, "layout.jsx")
|
|
691
832
|
];
|
|
692
833
|
for (const layoutPath of paths) {
|
|
693
|
-
if (!
|
|
834
|
+
if (!existsSync7(layoutPath)) continue;
|
|
694
835
|
let content;
|
|
695
836
|
try {
|
|
696
|
-
content =
|
|
837
|
+
content = readFileSync5(layoutPath, "utf-8");
|
|
697
838
|
} catch {
|
|
698
839
|
continue;
|
|
699
840
|
}
|
|
@@ -715,10 +856,10 @@ function inferShellPattern(hasSidebar, hasTopNav, hasFooter) {
|
|
|
715
856
|
return "main-only";
|
|
716
857
|
}
|
|
717
858
|
function inferShellPatternFromDecantrContract(projectRoot) {
|
|
718
|
-
const essencePath =
|
|
719
|
-
if (!
|
|
859
|
+
const essencePath = join7(projectRoot, "decantr.essence.json");
|
|
860
|
+
if (!existsSync7(essencePath)) return null;
|
|
720
861
|
try {
|
|
721
|
-
const essence = JSON.parse(
|
|
862
|
+
const essence = JSON.parse(readFileSync5(essencePath, "utf-8"));
|
|
722
863
|
const sectionShells = essence.blueprint?.sections?.map((section) => section.shell).filter((shell) => typeof shell === "string" && shell.length > 0) ?? [];
|
|
723
864
|
if (sectionShells.length === 0 && essence.blueprint?.shell) {
|
|
724
865
|
return `${essence.blueprint.shell} (contract)`;
|
|
@@ -754,8 +895,8 @@ function scanLayout(projectRoot) {
|
|
|
754
895
|
}
|
|
755
896
|
|
|
756
897
|
// src/analyzers/routes.ts
|
|
757
|
-
import { existsSync as
|
|
758
|
-
import { join as
|
|
898
|
+
import { existsSync as existsSync8, readdirSync as readdirSync4, readFileSync as readFileSync6, statSync as statSync4 } from "fs";
|
|
899
|
+
import { join as join8, relative } from "path";
|
|
759
900
|
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".next", ".git", "api", "_app", "_document"]);
|
|
760
901
|
function shouldSkipDir(name) {
|
|
761
902
|
return name.startsWith("_") || name.startsWith(".") || SKIP_DIRS.has(name);
|
|
@@ -795,15 +936,15 @@ function walkAppDir(dir, baseDir, segments) {
|
|
|
795
936
|
const pageFile = entries.find((e) => e.startsWith("page."));
|
|
796
937
|
routes.push({
|
|
797
938
|
path: routePath || "/",
|
|
798
|
-
file: relative(baseDir,
|
|
939
|
+
file: relative(baseDir, join8(dir, pageFile)),
|
|
799
940
|
hasLayout
|
|
800
941
|
});
|
|
801
942
|
}
|
|
802
943
|
for (const entry of entries) {
|
|
803
944
|
if (shouldSkipDir(entry)) continue;
|
|
804
|
-
const fullPath =
|
|
945
|
+
const fullPath = join8(dir, entry);
|
|
805
946
|
try {
|
|
806
|
-
if (!
|
|
947
|
+
if (!statSync4(fullPath).isDirectory()) continue;
|
|
807
948
|
} catch {
|
|
808
949
|
continue;
|
|
809
950
|
}
|
|
@@ -823,9 +964,9 @@ function walkPagesDir(dir, baseDir, segments) {
|
|
|
823
964
|
}
|
|
824
965
|
for (const entry of entries) {
|
|
825
966
|
if (shouldSkipDir(entry)) continue;
|
|
826
|
-
const fullPath =
|
|
967
|
+
const fullPath = join8(dir, entry);
|
|
827
968
|
try {
|
|
828
|
-
const stat =
|
|
969
|
+
const stat = statSync4(fullPath);
|
|
829
970
|
if (stat.isDirectory()) {
|
|
830
971
|
const routeSegment = segmentToRoute(entry);
|
|
831
972
|
const nextSegments = routeSegment === null ? [...segments] : [...segments, routeSegment];
|
|
@@ -859,9 +1000,9 @@ function collectRouteCandidateFiles(dir, files, depth = 0) {
|
|
|
859
1000
|
}
|
|
860
1001
|
for (const entry of entries) {
|
|
861
1002
|
if (entry.startsWith(".") || entry === "node_modules") continue;
|
|
862
|
-
const fullPath =
|
|
1003
|
+
const fullPath = join8(dir, entry);
|
|
863
1004
|
try {
|
|
864
|
-
const stat =
|
|
1005
|
+
const stat = statSync4(fullPath);
|
|
865
1006
|
if (stat.isDirectory()) {
|
|
866
1007
|
collectRouteCandidateFiles(fullPath, files, depth + 1);
|
|
867
1008
|
} else if (stat.isFile()) {
|
|
@@ -875,16 +1016,16 @@ function collectRouteCandidateFiles(dir, files, depth = 0) {
|
|
|
875
1016
|
}
|
|
876
1017
|
}
|
|
877
1018
|
function scanReactRouter(projectRoot) {
|
|
878
|
-
const candidateDirs = [
|
|
1019
|
+
const candidateDirs = [join8(projectRoot, "src"), projectRoot];
|
|
879
1020
|
const candidateFiles = [];
|
|
880
1021
|
for (const dir of candidateDirs) {
|
|
881
|
-
if (
|
|
1022
|
+
if (existsSync8(dir)) collectRouteCandidateFiles(dir, candidateFiles);
|
|
882
1023
|
}
|
|
883
1024
|
const routeMap = /* @__PURE__ */ new Map();
|
|
884
1025
|
for (const absolutePath of candidateFiles) {
|
|
885
1026
|
let content;
|
|
886
1027
|
try {
|
|
887
|
-
content =
|
|
1028
|
+
content = readFileSync6(absolutePath, "utf-8");
|
|
888
1029
|
} catch {
|
|
889
1030
|
continue;
|
|
890
1031
|
}
|
|
@@ -914,18 +1055,18 @@ function scanReactRouter(projectRoot) {
|
|
|
914
1055
|
return [...routeMap.values()];
|
|
915
1056
|
}
|
|
916
1057
|
function scanRoutes(projectRoot) {
|
|
917
|
-
const appDirs = [
|
|
1058
|
+
const appDirs = [join8(projectRoot, "src", "app"), join8(projectRoot, "app")];
|
|
918
1059
|
for (const appDir of appDirs) {
|
|
919
|
-
if (
|
|
1060
|
+
if (existsSync8(appDir)) {
|
|
920
1061
|
const routes = walkAppDir(appDir, projectRoot, []);
|
|
921
1062
|
if (routes.length > 0) {
|
|
922
1063
|
return { strategy: "app-router", routes };
|
|
923
1064
|
}
|
|
924
1065
|
}
|
|
925
1066
|
}
|
|
926
|
-
const pagesDirs = [
|
|
1067
|
+
const pagesDirs = [join8(projectRoot, "src", "pages"), join8(projectRoot, "pages")];
|
|
927
1068
|
for (const pagesDir of pagesDirs) {
|
|
928
|
-
if (
|
|
1069
|
+
if (existsSync8(pagesDir)) {
|
|
929
1070
|
const routes = walkPagesDir(pagesDir, projectRoot, []);
|
|
930
1071
|
if (routes.length > 0) {
|
|
931
1072
|
return { strategy: "pages-router", routes };
|
|
@@ -940,8 +1081,8 @@ function scanRoutes(projectRoot) {
|
|
|
940
1081
|
}
|
|
941
1082
|
|
|
942
1083
|
// src/analyzers/styling.ts
|
|
943
|
-
import { existsSync as
|
|
944
|
-
import { join as
|
|
1084
|
+
import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
|
|
1085
|
+
import { join as join9 } from "path";
|
|
945
1086
|
var TAILWIND_CONFIGS = [
|
|
946
1087
|
"tailwind.config.js",
|
|
947
1088
|
"tailwind.config.ts",
|
|
@@ -1003,10 +1144,10 @@ function detectDarkMode(projectRoot, cssContents) {
|
|
|
1003
1144
|
"app/layout.jsx"
|
|
1004
1145
|
];
|
|
1005
1146
|
for (const rel of layoutPaths) {
|
|
1006
|
-
const fullPath =
|
|
1007
|
-
if (
|
|
1147
|
+
const fullPath = join9(projectRoot, rel);
|
|
1148
|
+
if (existsSync9(fullPath)) {
|
|
1008
1149
|
try {
|
|
1009
|
-
const layoutContent =
|
|
1150
|
+
const layoutContent = readFileSync7(fullPath, "utf-8");
|
|
1010
1151
|
if (layoutContent.includes('className="dark"') || layoutContent.includes("className='dark'") || layoutContent.includes('class="dark"')) {
|
|
1011
1152
|
return true;
|
|
1012
1153
|
}
|
|
@@ -1014,10 +1155,10 @@ function detectDarkMode(projectRoot, cssContents) {
|
|
|
1014
1155
|
}
|
|
1015
1156
|
}
|
|
1016
1157
|
}
|
|
1017
|
-
const pkgPath =
|
|
1018
|
-
if (
|
|
1158
|
+
const pkgPath = join9(projectRoot, "package.json");
|
|
1159
|
+
if (existsSync9(pkgPath)) {
|
|
1019
1160
|
try {
|
|
1020
|
-
const pkg = JSON.parse(
|
|
1161
|
+
const pkg = JSON.parse(readFileSync7(pkgPath, "utf-8"));
|
|
1021
1162
|
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
1022
1163
|
if (allDeps["next-themes"] || allDeps["theme-toggle"] || allDeps["use-dark-mode"]) {
|
|
1023
1164
|
return true;
|
|
@@ -1025,10 +1166,10 @@ function detectDarkMode(projectRoot, cssContents) {
|
|
|
1025
1166
|
} catch {
|
|
1026
1167
|
}
|
|
1027
1168
|
}
|
|
1028
|
-
const essencePath =
|
|
1029
|
-
if (
|
|
1169
|
+
const essencePath = join9(projectRoot, "decantr.essence.json");
|
|
1170
|
+
if (existsSync9(essencePath)) {
|
|
1030
1171
|
try {
|
|
1031
|
-
const essence = JSON.parse(
|
|
1172
|
+
const essence = JSON.parse(readFileSync7(essencePath, "utf-8"));
|
|
1032
1173
|
const mode = essence.dna?.theme?.mode;
|
|
1033
1174
|
if (mode === "dark" || mode === "auto") {
|
|
1034
1175
|
return true;
|
|
@@ -1042,17 +1183,17 @@ function scanStyling(projectRoot) {
|
|
|
1042
1183
|
let approach = "unknown";
|
|
1043
1184
|
let configFile;
|
|
1044
1185
|
for (const cfg of TAILWIND_CONFIGS) {
|
|
1045
|
-
if (
|
|
1186
|
+
if (existsSync9(join9(projectRoot, cfg))) {
|
|
1046
1187
|
approach = "tailwind";
|
|
1047
1188
|
configFile = cfg;
|
|
1048
1189
|
break;
|
|
1049
1190
|
}
|
|
1050
1191
|
}
|
|
1051
1192
|
if (approach === "unknown") {
|
|
1052
|
-
const pkgPath =
|
|
1053
|
-
if (
|
|
1193
|
+
const pkgPath = join9(projectRoot, "package.json");
|
|
1194
|
+
if (existsSync9(pkgPath)) {
|
|
1054
1195
|
try {
|
|
1055
|
-
const pkg = JSON.parse(
|
|
1196
|
+
const pkg = JSON.parse(readFileSync7(pkgPath, "utf-8"));
|
|
1056
1197
|
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
1057
1198
|
if (allDeps["@decantr/css"]) {
|
|
1058
1199
|
approach = "decantr-css";
|
|
@@ -1065,27 +1206,27 @@ function scanStyling(projectRoot) {
|
|
|
1065
1206
|
}
|
|
1066
1207
|
}
|
|
1067
1208
|
}
|
|
1068
|
-
const decantrStyleFiles = DECANTR_STYLE_PATHS.filter((rel) =>
|
|
1209
|
+
const decantrStyleFiles = DECANTR_STYLE_PATHS.filter((rel) => existsSync9(join9(projectRoot, rel)));
|
|
1069
1210
|
if (decantrStyleFiles.length >= 2) {
|
|
1070
1211
|
approach = "decantr-css";
|
|
1071
1212
|
configFile = decantrStyleFiles.join(" + ");
|
|
1072
1213
|
}
|
|
1073
1214
|
const cssContents = [];
|
|
1074
1215
|
for (const rel of GLOBALS_CSS_PATHS) {
|
|
1075
|
-
const fullPath =
|
|
1076
|
-
if (
|
|
1216
|
+
const fullPath = join9(projectRoot, rel);
|
|
1217
|
+
if (existsSync9(fullPath)) {
|
|
1077
1218
|
try {
|
|
1078
|
-
cssContents.push(
|
|
1219
|
+
cssContents.push(readFileSync7(fullPath, "utf-8"));
|
|
1079
1220
|
} catch {
|
|
1080
1221
|
}
|
|
1081
1222
|
}
|
|
1082
1223
|
}
|
|
1083
1224
|
for (const rel of DECANTR_STYLE_PATHS) {
|
|
1084
1225
|
if (GLOBALS_CSS_PATHS.includes(rel)) continue;
|
|
1085
|
-
const fullPath =
|
|
1086
|
-
if (
|
|
1226
|
+
const fullPath = join9(projectRoot, rel);
|
|
1227
|
+
if (existsSync9(fullPath)) {
|
|
1087
1228
|
try {
|
|
1088
|
-
cssContents.push(
|
|
1229
|
+
cssContents.push(readFileSync7(fullPath, "utf-8"));
|
|
1089
1230
|
} catch {
|
|
1090
1231
|
}
|
|
1091
1232
|
}
|
|
@@ -1101,7 +1242,7 @@ function scanStyling(projectRoot) {
|
|
|
1101
1242
|
const darkMode = detectDarkMode(projectRoot, cssContents);
|
|
1102
1243
|
if (approach === "unknown" && cssContents.length > 0) {
|
|
1103
1244
|
approach = "css";
|
|
1104
|
-
configFile = GLOBALS_CSS_PATHS.find((rel) =>
|
|
1245
|
+
configFile = GLOBALS_CSS_PATHS.find((rel) => existsSync9(join9(projectRoot, rel)));
|
|
1105
1246
|
}
|
|
1106
1247
|
return {
|
|
1107
1248
|
approach,
|
|
@@ -1113,8 +1254,8 @@ function scanStyling(projectRoot) {
|
|
|
1113
1254
|
}
|
|
1114
1255
|
|
|
1115
1256
|
// src/detect.ts
|
|
1116
|
-
import { existsSync as
|
|
1117
|
-
import { join as
|
|
1257
|
+
import { existsSync as existsSync10, readFileSync as readFileSync8 } from "fs";
|
|
1258
|
+
import { join as join10 } from "path";
|
|
1118
1259
|
var RULE_FILES = [
|
|
1119
1260
|
"CLAUDE.md",
|
|
1120
1261
|
".cursorrules",
|
|
@@ -1133,52 +1274,52 @@ function detectProject(projectRoot = process.cwd()) {
|
|
|
1133
1274
|
existingEssence: false,
|
|
1134
1275
|
projectRoot
|
|
1135
1276
|
};
|
|
1136
|
-
result.existingEssence =
|
|
1277
|
+
result.existingEssence = existsSync10(join10(projectRoot, "decantr.essence.json"));
|
|
1137
1278
|
for (const ruleFile of RULE_FILES) {
|
|
1138
|
-
if (
|
|
1279
|
+
if (existsSync10(join10(projectRoot, ruleFile))) {
|
|
1139
1280
|
result.existingRuleFiles.push(ruleFile);
|
|
1140
1281
|
}
|
|
1141
1282
|
}
|
|
1142
|
-
if (
|
|
1283
|
+
if (existsSync10(join10(projectRoot, "pnpm-lock.yaml"))) {
|
|
1143
1284
|
result.packageManager = "pnpm";
|
|
1144
|
-
} else if (
|
|
1285
|
+
} else if (existsSync10(join10(projectRoot, "yarn.lock"))) {
|
|
1145
1286
|
result.packageManager = "yarn";
|
|
1146
|
-
} else if (
|
|
1287
|
+
} else if (existsSync10(join10(projectRoot, "bun.lockb"))) {
|
|
1147
1288
|
result.packageManager = "bun";
|
|
1148
|
-
} else if (
|
|
1289
|
+
} else if (existsSync10(join10(projectRoot, "package-lock.json"))) {
|
|
1149
1290
|
result.packageManager = "npm";
|
|
1150
1291
|
}
|
|
1151
|
-
result.hasTypeScript =
|
|
1152
|
-
result.hasTailwind =
|
|
1153
|
-
if (
|
|
1292
|
+
result.hasTypeScript = existsSync10(join10(projectRoot, "tsconfig.json"));
|
|
1293
|
+
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"));
|
|
1294
|
+
if (existsSync10(join10(projectRoot, "next.config.js")) || existsSync10(join10(projectRoot, "next.config.ts")) || existsSync10(join10(projectRoot, "next.config.mjs"))) {
|
|
1154
1295
|
result.framework = "nextjs";
|
|
1155
1296
|
result.version = getPackageVersion(projectRoot, "next");
|
|
1156
1297
|
return result;
|
|
1157
1298
|
}
|
|
1158
|
-
if (
|
|
1299
|
+
if (existsSync10(join10(projectRoot, "nuxt.config.js")) || existsSync10(join10(projectRoot, "nuxt.config.ts"))) {
|
|
1159
1300
|
result.framework = "nuxt";
|
|
1160
1301
|
result.version = getPackageVersion(projectRoot, "nuxt");
|
|
1161
1302
|
return result;
|
|
1162
1303
|
}
|
|
1163
|
-
if (
|
|
1304
|
+
if (existsSync10(join10(projectRoot, "astro.config.mjs")) || existsSync10(join10(projectRoot, "astro.config.ts"))) {
|
|
1164
1305
|
result.framework = "astro";
|
|
1165
1306
|
result.version = getPackageVersion(projectRoot, "astro");
|
|
1166
1307
|
return result;
|
|
1167
1308
|
}
|
|
1168
|
-
if (
|
|
1309
|
+
if (existsSync10(join10(projectRoot, "svelte.config.js")) || existsSync10(join10(projectRoot, "svelte.config.ts"))) {
|
|
1169
1310
|
result.framework = "svelte";
|
|
1170
1311
|
result.version = getPackageVersion(projectRoot, "svelte");
|
|
1171
1312
|
return result;
|
|
1172
1313
|
}
|
|
1173
|
-
if (
|
|
1314
|
+
if (existsSync10(join10(projectRoot, "angular.json"))) {
|
|
1174
1315
|
result.framework = "angular";
|
|
1175
1316
|
result.version = getPackageVersion(projectRoot, "@angular/core");
|
|
1176
1317
|
return result;
|
|
1177
1318
|
}
|
|
1178
|
-
const packageJsonPath =
|
|
1179
|
-
if (
|
|
1319
|
+
const packageJsonPath = join10(projectRoot, "package.json");
|
|
1320
|
+
if (existsSync10(packageJsonPath)) {
|
|
1180
1321
|
try {
|
|
1181
|
-
const packageJson = JSON.parse(
|
|
1322
|
+
const packageJson = JSON.parse(readFileSync8(packageJsonPath, "utf-8"));
|
|
1182
1323
|
const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
|
|
1183
1324
|
if (deps.next) {
|
|
1184
1325
|
result.framework = "nextjs";
|
|
@@ -1205,18 +1346,18 @@ function detectProject(projectRoot = process.cwd()) {
|
|
|
1205
1346
|
} catch {
|
|
1206
1347
|
}
|
|
1207
1348
|
}
|
|
1208
|
-
if (result.framework === "unknown" && !
|
|
1209
|
-
if (
|
|
1349
|
+
if (result.framework === "unknown" && !existsSync10(packageJsonPath)) {
|
|
1350
|
+
if (existsSync10(join10(projectRoot, "index.html"))) {
|
|
1210
1351
|
result.framework = "html";
|
|
1211
1352
|
}
|
|
1212
1353
|
}
|
|
1213
1354
|
return result;
|
|
1214
1355
|
}
|
|
1215
1356
|
function getPackageVersion(projectRoot, packageName) {
|
|
1216
|
-
const packageJsonPath =
|
|
1217
|
-
if (!
|
|
1357
|
+
const packageJsonPath = join10(projectRoot, "package.json");
|
|
1358
|
+
if (!existsSync10(packageJsonPath)) return void 0;
|
|
1218
1359
|
try {
|
|
1219
|
-
const packageJson = JSON.parse(
|
|
1360
|
+
const packageJson = JSON.parse(readFileSync8(packageJsonPath, "utf-8"));
|
|
1220
1361
|
const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
|
|
1221
1362
|
const version = deps[packageName];
|
|
1222
1363
|
return version?.replace(/^\^|~/, "");
|
|
@@ -1249,8 +1390,8 @@ function formatDetection(detected) {
|
|
|
1249
1390
|
}
|
|
1250
1391
|
|
|
1251
1392
|
// src/workflow-model.ts
|
|
1252
|
-
import { existsSync as
|
|
1253
|
-
import { join as
|
|
1393
|
+
import { existsSync as existsSync11, readFileSync as readFileSync9 } from "fs";
|
|
1394
|
+
import { join as join11 } from "path";
|
|
1254
1395
|
function inferSuggestedShell(layout) {
|
|
1255
1396
|
if (layout.hasSidebar) return "sidebar-main";
|
|
1256
1397
|
if (layout.hasTopNav) return "top-nav-main";
|
|
@@ -1259,6 +1400,58 @@ function inferSuggestedShell(layout) {
|
|
|
1259
1400
|
function hasExistingProjectFootprint(detected) {
|
|
1260
1401
|
return detected.framework !== "unknown" || detected.packageManager !== "unknown" || detected.hasTypeScript || detected.hasTailwind || detected.existingRuleFiles.length > 0;
|
|
1261
1402
|
}
|
|
1403
|
+
function normalizeWorkflowFlag(value) {
|
|
1404
|
+
if (value === "greenfield" || value === "brownfield" || value === "hybrid") return value;
|
|
1405
|
+
return void 0;
|
|
1406
|
+
}
|
|
1407
|
+
function normalizeAdoptionMode(value) {
|
|
1408
|
+
if (value === "contract-only" || value === "style-bridge" || value === "decantr-css") {
|
|
1409
|
+
return value;
|
|
1410
|
+
}
|
|
1411
|
+
return void 0;
|
|
1412
|
+
}
|
|
1413
|
+
function normalizeAssistantBridge(value) {
|
|
1414
|
+
if (value === "none" || value === "preview" || value === "apply") return value;
|
|
1415
|
+
return void 0;
|
|
1416
|
+
}
|
|
1417
|
+
function resolveWorkflowPolicy(input) {
|
|
1418
|
+
const requestedWorkflow = normalizeWorkflowFlag(input.requestedWorkflow);
|
|
1419
|
+
const requestedAdoption = normalizeAdoptionMode(input.requestedAdoption);
|
|
1420
|
+
const requestedAssistantBridge = normalizeAssistantBridge(input.requestedAssistantBridge);
|
|
1421
|
+
const hasRegistryContent = Boolean(
|
|
1422
|
+
input.requestedBlueprint || input.requestedArchetype || input.requestedTheme
|
|
1423
|
+
);
|
|
1424
|
+
const hasAnalysisArtifacts = Boolean(input.workflowSeed);
|
|
1425
|
+
const existingFootprint = hasExistingProjectFootprint(input.detected);
|
|
1426
|
+
let workflowMode;
|
|
1427
|
+
if (requestedWorkflow === "hybrid") {
|
|
1428
|
+
workflowMode = "hybrid-compose";
|
|
1429
|
+
} else if (requestedWorkflow === "brownfield" || input.explicitExisting || input.workflowSeed) {
|
|
1430
|
+
workflowMode = "brownfield-attach";
|
|
1431
|
+
} else if (requestedWorkflow === "greenfield") {
|
|
1432
|
+
workflowMode = hasRegistryContent ? "greenfield-scaffold" : "greenfield-contract-only";
|
|
1433
|
+
} else if (input.command === "new") {
|
|
1434
|
+
workflowMode = hasRegistryContent ? "greenfield-scaffold" : "greenfield-contract-only";
|
|
1435
|
+
} else if (existingFootprint && hasRegistryContent) {
|
|
1436
|
+
workflowMode = "hybrid-compose";
|
|
1437
|
+
} else if (existingFootprint && !hasRegistryContent) {
|
|
1438
|
+
workflowMode = "brownfield-attach";
|
|
1439
|
+
} else {
|
|
1440
|
+
workflowMode = hasRegistryContent ? "greenfield-scaffold" : "greenfield-contract-only";
|
|
1441
|
+
}
|
|
1442
|
+
const adoptionMode = requestedAdoption ?? input.workflowSeed?.adoptionMode ?? (workflowMode === "brownfield-attach" ? "contract-only" : workflowMode === "hybrid-compose" ? "contract-only" : workflowMode === "greenfield-contract-only" ? "contract-only" : "decantr-css");
|
|
1443
|
+
const contentSource = hasRegistryContent ? input.offline ? "cache" : "official" : "none";
|
|
1444
|
+
const assistantBridge = requestedAssistantBridge ?? input.workflowSeed?.assistantBridge ?? (workflowMode === "brownfield-attach" && input.detected.existingRuleFiles.length > 0 ? "preview" : "none");
|
|
1445
|
+
return {
|
|
1446
|
+
workflowMode,
|
|
1447
|
+
adoptionMode,
|
|
1448
|
+
contentSource,
|
|
1449
|
+
assistantBridge,
|
|
1450
|
+
projectScope: input.projectScope ?? "single-app",
|
|
1451
|
+
hasAnalysisArtifacts,
|
|
1452
|
+
registryRequired: hasRegistryContent
|
|
1453
|
+
};
|
|
1454
|
+
}
|
|
1262
1455
|
function createBrownfieldInitSeed(detected, layout, styling) {
|
|
1263
1456
|
return {
|
|
1264
1457
|
version: 1,
|
|
@@ -1266,6 +1459,10 @@ function createBrownfieldInitSeed(detected, layout, styling) {
|
|
|
1266
1459
|
contractOnly: true,
|
|
1267
1460
|
registryOptional: true,
|
|
1268
1461
|
workflowMode: "brownfield-attach",
|
|
1462
|
+
adoptionMode: "contract-only",
|
|
1463
|
+
contentSource: "none",
|
|
1464
|
+
assistantBridge: detected.existingRuleFiles.length > 0 ? "preview" : "none",
|
|
1465
|
+
projectScope: "single-app",
|
|
1269
1466
|
target: detected.framework !== "unknown" ? detected.framework : "react",
|
|
1270
1467
|
shell: inferSuggestedShell(layout),
|
|
1271
1468
|
guard: "guided",
|
|
@@ -1281,12 +1478,12 @@ function createBrownfieldInitSeed(detected, layout, styling) {
|
|
|
1281
1478
|
};
|
|
1282
1479
|
}
|
|
1283
1480
|
function readBrownfieldInitSeed(projectRoot) {
|
|
1284
|
-
const seedPath =
|
|
1285
|
-
if (!
|
|
1481
|
+
const seedPath = join11(projectRoot, ".decantr", "init-seed.json");
|
|
1482
|
+
if (!existsSync11(seedPath)) {
|
|
1286
1483
|
return null;
|
|
1287
1484
|
}
|
|
1288
1485
|
try {
|
|
1289
|
-
const parsed = JSON.parse(
|
|
1486
|
+
const parsed = JSON.parse(readFileSync9(seedPath, "utf-8"));
|
|
1290
1487
|
if (parsed.workflow !== "brownfield-adoption") {
|
|
1291
1488
|
return null;
|
|
1292
1489
|
}
|
|
@@ -1303,7 +1500,7 @@ var RESET2 = "\x1B[0m";
|
|
|
1303
1500
|
var GREEN2 = "\x1B[32m";
|
|
1304
1501
|
var CYAN = "\x1B[36m";
|
|
1305
1502
|
var YELLOW = "\x1B[33m";
|
|
1306
|
-
function cmdAnalyze(projectRoot = process.cwd()) {
|
|
1503
|
+
function cmdAnalyze(projectRoot = process.cwd(), workspace) {
|
|
1307
1504
|
console.log(`
|
|
1308
1505
|
${BOLD}Analyzing project...${RESET2}
|
|
1309
1506
|
`);
|
|
@@ -1322,6 +1519,7 @@ ${BOLD}Analyzing project...${RESET2}
|
|
|
1322
1519
|
console.log(`${DIM2}Scanning dependencies...${RESET2}`);
|
|
1323
1520
|
const dependencies = scanDependencies(projectRoot);
|
|
1324
1521
|
const initSeed = createBrownfieldInitSeed(project, layout, styling);
|
|
1522
|
+
initSeed.projectScope = workspace?.projectScope ?? "single-app";
|
|
1325
1523
|
const analysis = {
|
|
1326
1524
|
version: 1,
|
|
1327
1525
|
analyzedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -1330,7 +1528,11 @@ ${BOLD}Analyzing project...${RESET2}
|
|
|
1330
1528
|
frameworkVersion: project.version ?? null,
|
|
1331
1529
|
packageManager: project.packageManager,
|
|
1332
1530
|
hasTypeScript: project.hasTypeScript,
|
|
1333
|
-
hasTailwind: project.hasTailwind
|
|
1531
|
+
hasTailwind: project.hasTailwind,
|
|
1532
|
+
existingRuleFiles: project.existingRuleFiles,
|
|
1533
|
+
workspaceRoot: workspace?.workspaceRoot ?? projectRoot,
|
|
1534
|
+
appRoot: workspace?.appRoot ?? projectRoot,
|
|
1535
|
+
projectScope: workspace?.projectScope ?? "single-app"
|
|
1334
1536
|
},
|
|
1335
1537
|
routes,
|
|
1336
1538
|
components,
|
|
@@ -1344,8 +1546,9 @@ ${BOLD}Analyzing project...${RESET2}
|
|
|
1344
1546
|
attach: {
|
|
1345
1547
|
entrypoint: "decantr analyze",
|
|
1346
1548
|
contractOnly: true,
|
|
1549
|
+
adoptionMode: "contract-only",
|
|
1347
1550
|
initSeedPath: ".decantr/init-seed.json",
|
|
1348
|
-
recommendedCommand: "decantr init --existing --yes"
|
|
1551
|
+
recommendedCommand: "decantr init --existing --yes --adoption=contract-only"
|
|
1349
1552
|
},
|
|
1350
1553
|
hybrid: {
|
|
1351
1554
|
ownerCommands: [
|
|
@@ -1356,16 +1559,31 @@ ${BOLD}Analyzing project...${RESET2}
|
|
|
1356
1559
|
"decantr upgrade"
|
|
1357
1560
|
]
|
|
1358
1561
|
}
|
|
1562
|
+
},
|
|
1563
|
+
retrofitPlan: {
|
|
1564
|
+
recommendedWorkflowMode: "brownfield-attach",
|
|
1565
|
+
recommendedAdoptionMode: "contract-only",
|
|
1566
|
+
assistantBridge: project.existingRuleFiles.length > 0 ? "preview existing rule files before applying" : "none detected",
|
|
1567
|
+
routeAnchors: routes.routes.map((route) => route.path),
|
|
1568
|
+
stylingAnchors: [styling.configFile].filter(Boolean),
|
|
1569
|
+
ruleFiles: project.existingRuleFiles,
|
|
1570
|
+
preserve: [
|
|
1571
|
+
"framework",
|
|
1572
|
+
"package manager",
|
|
1573
|
+
"router",
|
|
1574
|
+
"build tooling",
|
|
1575
|
+
"existing styling system"
|
|
1576
|
+
]
|
|
1359
1577
|
}
|
|
1360
1578
|
};
|
|
1361
|
-
const decantrDir =
|
|
1362
|
-
if (!
|
|
1363
|
-
|
|
1364
|
-
}
|
|
1365
|
-
const outputPath =
|
|
1366
|
-
const initSeedPath =
|
|
1367
|
-
|
|
1368
|
-
|
|
1579
|
+
const decantrDir = join12(projectRoot, ".decantr");
|
|
1580
|
+
if (!existsSync12(decantrDir)) {
|
|
1581
|
+
mkdirSync3(decantrDir, { recursive: true });
|
|
1582
|
+
}
|
|
1583
|
+
const outputPath = join12(decantrDir, "analysis.json");
|
|
1584
|
+
const initSeedPath = join12(decantrDir, "init-seed.json");
|
|
1585
|
+
writeFileSync4(outputPath, JSON.stringify(analysis, null, 2) + "\n", "utf-8");
|
|
1586
|
+
writeFileSync4(initSeedPath, JSON.stringify(initSeed, null, 2) + "\n", "utf-8");
|
|
1369
1587
|
console.log(`
|
|
1370
1588
|
${GREEN2}Analysis complete.${RESET2}
|
|
1371
1589
|
`);
|
|
@@ -1399,14 +1617,14 @@ ${DIM2}Written to:${RESET2} ${outputPath}`);
|
|
|
1399
1617
|
console.log(`${DIM2}Init seed:${RESET2} ${initSeedPath}`);
|
|
1400
1618
|
console.log(
|
|
1401
1619
|
`
|
|
1402
|
-
${YELLOW}Next step:${RESET2} Run ${BOLD}decantr init --existing --yes${RESET2} to attach Decantr using the generated brownfield seed.
|
|
1620
|
+
${YELLOW}Next step:${RESET2} Run ${BOLD}decantr init --existing --yes --adoption=contract-only${RESET2} to attach Decantr using the generated brownfield seed.
|
|
1403
1621
|
`
|
|
1404
1622
|
);
|
|
1405
1623
|
}
|
|
1406
1624
|
|
|
1407
1625
|
// src/commands/create.ts
|
|
1408
|
-
import { existsSync as
|
|
1409
|
-
import { join as
|
|
1626
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync4, writeFileSync as writeFileSync5 } from "fs";
|
|
1627
|
+
import { join as join13 } from "path";
|
|
1410
1628
|
import {
|
|
1411
1629
|
CONTENT_TYPE_TO_API_CONTENT_TYPE,
|
|
1412
1630
|
CONTENT_TYPES
|
|
@@ -1602,23 +1820,23 @@ function cmdCreate(type, name, projectRoot = process.cwd()) {
|
|
|
1602
1820
|
}
|
|
1603
1821
|
const contentType = type;
|
|
1604
1822
|
const plural = PLURAL[contentType];
|
|
1605
|
-
const customDir =
|
|
1606
|
-
const filePath =
|
|
1607
|
-
if (
|
|
1823
|
+
const customDir = join13(projectRoot, ".decantr", "custom", plural);
|
|
1824
|
+
const filePath = join13(customDir, `${name}.json`);
|
|
1825
|
+
if (existsSync13(filePath)) {
|
|
1608
1826
|
console.error(`${type} "${name}" already exists at ${filePath}`);
|
|
1609
1827
|
process.exitCode = 1;
|
|
1610
1828
|
return;
|
|
1611
1829
|
}
|
|
1612
|
-
|
|
1830
|
+
mkdirSync4(customDir, { recursive: true });
|
|
1613
1831
|
const skeleton = getSkeleton(contentType, name, humanizeId(name));
|
|
1614
|
-
|
|
1832
|
+
writeFileSync5(filePath, JSON.stringify(skeleton, null, 2));
|
|
1615
1833
|
console.log(`Created ${type} "${name}" at ${filePath}`);
|
|
1616
1834
|
console.log(`Edit it, then publish with: decantr publish ${type} ${name}`);
|
|
1617
1835
|
}
|
|
1618
1836
|
|
|
1619
1837
|
// src/commands/export.ts
|
|
1620
|
-
import { existsSync as
|
|
1621
|
-
import { dirname, join as
|
|
1838
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync5, readFileSync as readFileSync10, writeFileSync as writeFileSync6 } from "fs";
|
|
1839
|
+
import { dirname as dirname2, join as join14 } from "path";
|
|
1622
1840
|
var GREEN3 = "\x1B[32m";
|
|
1623
1841
|
var RED2 = "\x1B[31m";
|
|
1624
1842
|
var DIM3 = "\x1B[2m";
|
|
@@ -1768,21 +1986,21 @@ function generateCSSVars(tokens) {
|
|
|
1768
1986
|
return lines.join("\n");
|
|
1769
1987
|
}
|
|
1770
1988
|
async function cmdExport(target, projectRoot, options = {}) {
|
|
1771
|
-
const essencePath =
|
|
1772
|
-
const tokensPath =
|
|
1773
|
-
if (!
|
|
1989
|
+
const essencePath = join14(projectRoot, "decantr.essence.json");
|
|
1990
|
+
const tokensPath = join14(projectRoot, "src", "styles", "tokens.css");
|
|
1991
|
+
if (!existsSync14(essencePath)) {
|
|
1774
1992
|
console.error(`${RED2}No decantr.essence.json found. Run \`decantr init\` first.${RESET3}`);
|
|
1775
1993
|
process.exitCode = 1;
|
|
1776
1994
|
return;
|
|
1777
1995
|
}
|
|
1778
|
-
if (!
|
|
1996
|
+
if (!existsSync14(tokensPath)) {
|
|
1779
1997
|
console.error(
|
|
1780
1998
|
`${RED2}No src/styles/tokens.css found. Run \`decantr refresh\` to generate tokens.${RESET3}`
|
|
1781
1999
|
);
|
|
1782
2000
|
process.exitCode = 1;
|
|
1783
2001
|
return;
|
|
1784
2002
|
}
|
|
1785
|
-
const tokensCSS =
|
|
2003
|
+
const tokensCSS = readFileSync10(tokensPath, "utf-8");
|
|
1786
2004
|
const tokens = parseTokensCSS(tokensCSS);
|
|
1787
2005
|
if (tokens.size === 0) {
|
|
1788
2006
|
console.error(`${RED2}No --d-* tokens found in tokens.css.${RESET3}`);
|
|
@@ -1791,28 +2009,28 @@ async function cmdExport(target, projectRoot, options = {}) {
|
|
|
1791
2009
|
}
|
|
1792
2010
|
switch (target) {
|
|
1793
2011
|
case "shadcn": {
|
|
1794
|
-
const cssOut = options.output ??
|
|
1795
|
-
const jsonOut =
|
|
2012
|
+
const cssOut = options.output ?? join14(projectRoot, "src", "styles", "shadcn-theme.css");
|
|
2013
|
+
const jsonOut = join14(projectRoot, "components.json");
|
|
1796
2014
|
ensureDir(cssOut);
|
|
1797
|
-
|
|
1798
|
-
|
|
2015
|
+
writeFileSync6(cssOut, generateShadcnCSS(tokens), "utf-8");
|
|
2016
|
+
writeFileSync6(jsonOut, generateShadcnComponentsJSON(), "utf-8");
|
|
1799
2017
|
console.log(`${GREEN3}Exported shadcn theme:${RESET3}`);
|
|
1800
2018
|
console.log(` ${DIM3}CSS:${RESET3} ${cssOut}`);
|
|
1801
2019
|
console.log(` ${DIM3}JSON:${RESET3} ${jsonOut}`);
|
|
1802
2020
|
break;
|
|
1803
2021
|
}
|
|
1804
2022
|
case "tailwind": {
|
|
1805
|
-
const out = options.output ??
|
|
2023
|
+
const out = options.output ?? join14(projectRoot, "tailwind.decantr.config.ts");
|
|
1806
2024
|
ensureDir(out);
|
|
1807
|
-
|
|
2025
|
+
writeFileSync6(out, generateTailwindConfig(tokens), "utf-8");
|
|
1808
2026
|
console.log(`${GREEN3}Exported Tailwind config:${RESET3}`);
|
|
1809
2027
|
console.log(` ${DIM3}File:${RESET3} ${out}`);
|
|
1810
2028
|
break;
|
|
1811
2029
|
}
|
|
1812
2030
|
case "css-vars": {
|
|
1813
|
-
const out = options.output ??
|
|
2031
|
+
const out = options.output ?? join14(projectRoot, "decantr-tokens.css");
|
|
1814
2032
|
ensureDir(out);
|
|
1815
|
-
|
|
2033
|
+
writeFileSync6(out, generateCSSVars(tokens), "utf-8");
|
|
1816
2034
|
console.log(`${GREEN3}Exported CSS variables:${RESET3}`);
|
|
1817
2035
|
console.log(` ${DIM3}File:${RESET3} ${out}`);
|
|
1818
2036
|
break;
|
|
@@ -1820,16 +2038,16 @@ async function cmdExport(target, projectRoot, options = {}) {
|
|
|
1820
2038
|
}
|
|
1821
2039
|
}
|
|
1822
2040
|
function ensureDir(filePath) {
|
|
1823
|
-
const dir =
|
|
1824
|
-
if (!
|
|
1825
|
-
|
|
2041
|
+
const dir = dirname2(filePath);
|
|
2042
|
+
if (!existsSync14(dir)) {
|
|
2043
|
+
mkdirSync5(dir, { recursive: true });
|
|
1826
2044
|
}
|
|
1827
2045
|
}
|
|
1828
2046
|
|
|
1829
2047
|
// src/commands/magic.ts
|
|
1830
|
-
import { existsSync as
|
|
2048
|
+
import { existsSync as existsSync15 } from "fs";
|
|
1831
2049
|
import * as fs from "fs/promises";
|
|
1832
|
-
import { join as
|
|
2050
|
+
import { join as join15 } from "path";
|
|
1833
2051
|
var BOLD2 = "\x1B[1m";
|
|
1834
2052
|
var DIM4 = "\x1B[2m";
|
|
1835
2053
|
var RESET4 = "\x1B[0m";
|
|
@@ -1979,7 +2197,8 @@ async function resolveTheme(intent, registryClient) {
|
|
|
1979
2197
|
const hints = intent.themeHints;
|
|
1980
2198
|
if (registryClient) {
|
|
1981
2199
|
try {
|
|
1982
|
-
const
|
|
2200
|
+
const themesResult = await registryClient.fetchThemes();
|
|
2201
|
+
const themes = Array.isArray(themesResult) ? themesResult : Array.isArray(themesResult?.data?.items) ? themesResult.data.items : [];
|
|
1983
2202
|
if (themes && themes.length > 0) {
|
|
1984
2203
|
const scored = themes.map((t) => {
|
|
1985
2204
|
let score = 0;
|
|
@@ -1994,7 +2213,7 @@ async function resolveTheme(intent, registryClient) {
|
|
|
1994
2213
|
return { id: t.id || t.slug, score, modes: t.modes };
|
|
1995
2214
|
}).sort((a, b) => b.score - a.score);
|
|
1996
2215
|
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";
|
|
2216
|
+
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
2217
|
return { id: scored[0].id, mode: mode2 };
|
|
1999
2218
|
}
|
|
2000
2219
|
}
|
|
@@ -2056,8 +2275,8 @@ async function cmdMagic(prompt, projectRoot, options) {
|
|
|
2056
2275
|
console.log(` Archetype: ${intent.archetype}`);
|
|
2057
2276
|
}
|
|
2058
2277
|
console.log("");
|
|
2059
|
-
const essencePath =
|
|
2060
|
-
if (
|
|
2278
|
+
const essencePath = join15(projectRoot, "decantr.essence.json");
|
|
2279
|
+
if (existsSync15(essencePath)) {
|
|
2061
2280
|
console.log(error(" decantr.essence.json already exists in this directory."));
|
|
2062
2281
|
console.log(dim(" Remove it first or use a different directory."));
|
|
2063
2282
|
process.exitCode = 1;
|
|
@@ -2080,7 +2299,7 @@ async function cmdMagic(prompt, projectRoot, options) {
|
|
|
2080
2299
|
return;
|
|
2081
2300
|
}
|
|
2082
2301
|
const registryClient = new RegistryClient({
|
|
2083
|
-
cacheDir:
|
|
2302
|
+
cacheDir: join15(projectRoot, ".decantr", "cache"),
|
|
2084
2303
|
apiUrl: options.registry,
|
|
2085
2304
|
offline: options.offline
|
|
2086
2305
|
});
|
|
@@ -2351,14 +2570,14 @@ async function cmdMagic(prompt, projectRoot, options) {
|
|
|
2351
2570
|
if (result.gitignoreUpdated) {
|
|
2352
2571
|
console.log(` ${dim(".gitignore updated")}`);
|
|
2353
2572
|
}
|
|
2354
|
-
const contextDir =
|
|
2573
|
+
const contextDir = join15(projectRoot, ".decantr", "context");
|
|
2355
2574
|
let sectionCount = 0;
|
|
2356
2575
|
try {
|
|
2357
2576
|
const files = await fs.readdir(contextDir);
|
|
2358
2577
|
sectionCount = files.filter((f) => f.startsWith("section-")).length;
|
|
2359
2578
|
} catch {
|
|
2360
2579
|
}
|
|
2361
|
-
const treatmentsPath =
|
|
2580
|
+
const treatmentsPath = join15(projectRoot, "src", "styles", "treatments.css");
|
|
2362
2581
|
let hasLayers = false;
|
|
2363
2582
|
try {
|
|
2364
2583
|
const css = await fs.readFile(treatmentsPath, "utf-8");
|
|
@@ -2376,27 +2595,23 @@ ${GREEN4}${BOLD2}Quality summary:${RESET4}`);
|
|
|
2376
2595
|
);
|
|
2377
2596
|
console.log("");
|
|
2378
2597
|
console.log(`${BOLD2} Ready!${RESET4} Next steps:`);
|
|
2379
|
-
console.log(` 1. Read ${cyan("DECANTR.md")} for guard rules, CSS approach, and workflow`);
|
|
2380
|
-
console.log(
|
|
2381
|
-
` 2. Read ${cyan(".decantr/context/scaffold-pack.md")} first as the primary compiled contract`
|
|
2382
|
-
);
|
|
2383
2598
|
console.log(
|
|
2384
|
-
`
|
|
2599
|
+
` 1. Read ${cyan(".decantr/context/scaffold-pack.md")} first as the primary compiled contract`
|
|
2385
2600
|
);
|
|
2386
2601
|
console.log(
|
|
2387
|
-
`
|
|
2602
|
+
` 2. Read the matching ${cyan(".decantr/context/section-*-pack.md")} and ${cyan(".decantr/context/page-*-pack.md")} files before section or route work`
|
|
2388
2603
|
);
|
|
2389
2604
|
console.log(
|
|
2390
|
-
`
|
|
2605
|
+
` 3. Use ${cyan("DECANTR.md")} as a lookup reference for guard rules, CSS atoms, treatments, decorators, and workflow`
|
|
2391
2606
|
);
|
|
2392
|
-
console.log(`
|
|
2393
|
-
console.log(`
|
|
2607
|
+
console.log(` 4. Build the shell and route structure first, then implement each page`);
|
|
2608
|
+
console.log(` 5. Run ${cyan("decantr check")} and ${cyan("decantr audit")} before you ship`);
|
|
2394
2609
|
console.log("");
|
|
2395
2610
|
}
|
|
2396
2611
|
|
|
2397
2612
|
// src/commands/migrate.ts
|
|
2398
|
-
import { copyFileSync, existsSync as
|
|
2399
|
-
import { join as
|
|
2613
|
+
import { copyFileSync, existsSync as existsSync16, readFileSync as readFileSync11, writeFileSync as writeFileSync7 } from "fs";
|
|
2614
|
+
import { join as join16 } from "path";
|
|
2400
2615
|
import { isV3 as isV32, migrateV2ToV3, validateEssence } from "@decantr/essence-spec";
|
|
2401
2616
|
var GREEN5 = "\x1B[32m";
|
|
2402
2617
|
var RED4 = "\x1B[31m";
|
|
@@ -2404,12 +2619,12 @@ var YELLOW3 = "\x1B[33m";
|
|
|
2404
2619
|
var RESET5 = "\x1B[0m";
|
|
2405
2620
|
var DIM5 = "\x1B[2m";
|
|
2406
2621
|
function migrateEssenceFile(essencePath) {
|
|
2407
|
-
if (!
|
|
2622
|
+
if (!existsSync16(essencePath)) {
|
|
2408
2623
|
return { success: false, error: `File not found: ${essencePath}` };
|
|
2409
2624
|
}
|
|
2410
2625
|
let raw;
|
|
2411
2626
|
try {
|
|
2412
|
-
raw =
|
|
2627
|
+
raw = readFileSync11(essencePath, "utf-8");
|
|
2413
2628
|
} catch (e) {
|
|
2414
2629
|
return { success: false, error: `Could not read ${essencePath}: ${e.message}` };
|
|
2415
2630
|
}
|
|
@@ -2450,7 +2665,7 @@ function migrateEssenceFile(essencePath) {
|
|
|
2450
2665
|
};
|
|
2451
2666
|
}
|
|
2452
2667
|
try {
|
|
2453
|
-
|
|
2668
|
+
writeFileSync7(essencePath, JSON.stringify(v3, null, 2) + "\n");
|
|
2454
2669
|
} catch (e) {
|
|
2455
2670
|
return {
|
|
2456
2671
|
success: false,
|
|
@@ -2461,8 +2676,8 @@ function migrateEssenceFile(essencePath) {
|
|
|
2461
2676
|
return { success: true, backupPath };
|
|
2462
2677
|
}
|
|
2463
2678
|
async function cmdMigrate(projectRoot = process.cwd()) {
|
|
2464
|
-
const essencePath =
|
|
2465
|
-
if (!
|
|
2679
|
+
const essencePath = join16(projectRoot, "decantr.essence.json");
|
|
2680
|
+
if (!existsSync16(essencePath)) {
|
|
2466
2681
|
console.error(`${RED4}No decantr.essence.json found. Run \`decantr init\` first.${RESET5}`);
|
|
2467
2682
|
process.exitCode = 1;
|
|
2468
2683
|
return;
|
|
@@ -2487,20 +2702,38 @@ async function cmdMigrate(projectRoot = process.cwd()) {
|
|
|
2487
2702
|
}
|
|
2488
2703
|
|
|
2489
2704
|
// src/commands/new-project.ts
|
|
2490
|
-
import {
|
|
2491
|
-
import { existsSync as
|
|
2492
|
-
import { join as
|
|
2705
|
+
import { spawnSync } from "child_process";
|
|
2706
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync8 } from "fs";
|
|
2707
|
+
import { join as join19, resolve as resolve2 } from "path";
|
|
2493
2708
|
import { fileURLToPath } from "url";
|
|
2494
2709
|
|
|
2495
2710
|
// src/bootstrap.ts
|
|
2496
|
-
import { mkdirSync as
|
|
2497
|
-
import { basename, join as
|
|
2711
|
+
import { mkdirSync as mkdirSync6, readFileSync as readFileSync12, writeFileSync as writeFileSync8 } from "fs";
|
|
2712
|
+
import { basename, join as join17 } from "path";
|
|
2498
2713
|
import { resolvePackAdapter } from "@decantr/core";
|
|
2499
2714
|
var reactViteBootstrapAdapter = {
|
|
2500
2715
|
id: "react-vite",
|
|
2501
2716
|
label: "React + Vite starter",
|
|
2717
|
+
target: "react",
|
|
2718
|
+
packAdapter: "react-vite",
|
|
2719
|
+
capabilities: {
|
|
2720
|
+
bootstrap: true,
|
|
2721
|
+
attach: true,
|
|
2722
|
+
styling: true,
|
|
2723
|
+
verify: true
|
|
2724
|
+
},
|
|
2725
|
+
attach: {
|
|
2726
|
+
routeRoots: ["src/App.tsx", "src/routes", "src/pages"],
|
|
2727
|
+
layoutFiles: ["src/App.tsx", "src/main.tsx"],
|
|
2728
|
+
componentRoots: ["src/components", "src/pages", "src/routes"]
|
|
2729
|
+
},
|
|
2730
|
+
verify: {
|
|
2731
|
+
devCommand: "npm run dev",
|
|
2732
|
+
buildCommand: "npm run build",
|
|
2733
|
+
distDir: "dist"
|
|
2734
|
+
},
|
|
2502
2735
|
writeProjectFiles(projectDir, title, routingMode) {
|
|
2503
|
-
const srcDir =
|
|
2736
|
+
const srcDir = join17(projectDir, "src");
|
|
2504
2737
|
const routerImport = routingMode === "hash" ? "HashRouter" : "BrowserRouter";
|
|
2505
2738
|
const packageJson = {
|
|
2506
2739
|
name: basename(projectDir) || "decantr-app",
|
|
@@ -2532,7 +2765,7 @@ var reactViteBootstrapAdapter = {
|
|
|
2532
2765
|
vite: "^6.0.0"
|
|
2533
2766
|
}
|
|
2534
2767
|
};
|
|
2535
|
-
|
|
2768
|
+
writeFileSync8(join17(projectDir, "package.json"), JSON.stringify(packageJson, null, 2) + "\n");
|
|
2536
2769
|
const viteConfig = `import { defineConfig } from 'vite';
|
|
2537
2770
|
import react from '@vitejs/plugin-react';
|
|
2538
2771
|
|
|
@@ -2540,7 +2773,7 @@ export default defineConfig({
|
|
|
2540
2773
|
plugins: [react()],
|
|
2541
2774
|
});
|
|
2542
2775
|
`;
|
|
2543
|
-
|
|
2776
|
+
writeFileSync8(join17(projectDir, "vite.config.ts"), viteConfig);
|
|
2544
2777
|
const tsconfig = {
|
|
2545
2778
|
compilerOptions: {
|
|
2546
2779
|
target: "ES2020",
|
|
@@ -2562,7 +2795,7 @@ export default defineConfig({
|
|
|
2562
2795
|
},
|
|
2563
2796
|
include: ["src"]
|
|
2564
2797
|
};
|
|
2565
|
-
|
|
2798
|
+
writeFileSync8(join17(projectDir, "tsconfig.json"), JSON.stringify(tsconfig, null, 2) + "\n");
|
|
2566
2799
|
const tsconfigApp = {
|
|
2567
2800
|
compilerOptions: {
|
|
2568
2801
|
tsBuildInfoFile: "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
|
@@ -2585,8 +2818,8 @@ export default defineConfig({
|
|
|
2585
2818
|
},
|
|
2586
2819
|
include: ["src"]
|
|
2587
2820
|
};
|
|
2588
|
-
|
|
2589
|
-
|
|
2821
|
+
writeFileSync8(
|
|
2822
|
+
join17(projectDir, "tsconfig.app.json"),
|
|
2590
2823
|
JSON.stringify(tsconfigApp, null, 2) + "\n"
|
|
2591
2824
|
);
|
|
2592
2825
|
const indexHtml = `<!doctype html>
|
|
@@ -2602,8 +2835,8 @@ export default defineConfig({
|
|
|
2602
2835
|
</body>
|
|
2603
2836
|
</html>
|
|
2604
2837
|
`;
|
|
2605
|
-
|
|
2606
|
-
|
|
2838
|
+
writeFileSync8(join17(projectDir, "index.html"), indexHtml);
|
|
2839
|
+
mkdirSync6(srcDir, { recursive: true });
|
|
2607
2840
|
const mainTsx = `import { StrictMode } from 'react';
|
|
2608
2841
|
import { createRoot } from 'react-dom/client';
|
|
2609
2842
|
import { ${routerImport} } from 'react-router-dom';
|
|
@@ -2620,7 +2853,7 @@ createRoot(document.getElementById('root')!).render(
|
|
|
2620
2853
|
</StrictMode>,
|
|
2621
2854
|
);
|
|
2622
2855
|
`;
|
|
2623
|
-
|
|
2856
|
+
writeFileSync8(join17(srcDir, "main.tsx"), mainTsx);
|
|
2624
2857
|
const appTsx = `import { css } from '@decantr/css';
|
|
2625
2858
|
import { Routes, Route } from 'react-router-dom';
|
|
2626
2859
|
|
|
@@ -2634,7 +2867,7 @@ function WelcomePage() {
|
|
|
2634
2867
|
<p className="d-label" data-anchor>Decantr starter</p>
|
|
2635
2868
|
<h1 className={css('_heading2')}>${title}</h1>
|
|
2636
2869
|
<p className={css('_textsm _fgmuted _mw[32rem]')}>
|
|
2637
|
-
Scaffolded with Decantr. Read
|
|
2870
|
+
Scaffolded with Decantr. Read .decantr/context/scaffold-pack.md first, then use DECANTR.md as a lookup reference.
|
|
2638
2871
|
</p>
|
|
2639
2872
|
<div className={css('_flex _gap3 _wrap _jcc')}>
|
|
2640
2873
|
<span className="d-annotation" data-status="info">Runtime: @decantr/css</span>
|
|
@@ -2655,34 +2888,184 @@ export function App() {
|
|
|
2655
2888
|
);
|
|
2656
2889
|
}
|
|
2657
2890
|
`;
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2891
|
+
writeFileSync8(join17(srcDir, "App.tsx"), appTsx);
|
|
2892
|
+
writeFileSync8(join17(srcDir, "vite-env.d.ts"), '/// <reference types="vite/client" />\n');
|
|
2893
|
+
mkdirSync6(join17(srcDir, "styles"), { recursive: true });
|
|
2661
2894
|
}
|
|
2662
2895
|
};
|
|
2663
|
-
var
|
|
2664
|
-
"
|
|
2896
|
+
var nextAppAdapter = {
|
|
2897
|
+
id: "next-app",
|
|
2898
|
+
label: "Next.js App Router starter",
|
|
2899
|
+
target: "nextjs",
|
|
2900
|
+
packAdapter: "nextjs",
|
|
2901
|
+
capabilities: {
|
|
2902
|
+
bootstrap: true,
|
|
2903
|
+
attach: true,
|
|
2904
|
+
styling: true,
|
|
2905
|
+
verify: true
|
|
2906
|
+
},
|
|
2907
|
+
attach: {
|
|
2908
|
+
routeRoots: ["app", "pages"],
|
|
2909
|
+
layoutFiles: ["app/layout.tsx", "pages/_app.tsx"],
|
|
2910
|
+
componentRoots: ["components", "src/components", "app"]
|
|
2911
|
+
},
|
|
2912
|
+
verify: {
|
|
2913
|
+
devCommand: "npm run dev",
|
|
2914
|
+
buildCommand: "npm run build",
|
|
2915
|
+
distDir: ".next"
|
|
2916
|
+
},
|
|
2917
|
+
writeProjectFiles(projectDir, title) {
|
|
2918
|
+
const appDir = join17(projectDir, "app");
|
|
2919
|
+
const stylesDir = join17(projectDir, "src", "styles");
|
|
2920
|
+
const packageJson = {
|
|
2921
|
+
name: basename(projectDir) || "decantr-next-app",
|
|
2922
|
+
private: true,
|
|
2923
|
+
version: "0.0.0",
|
|
2924
|
+
type: "module",
|
|
2925
|
+
scripts: {
|
|
2926
|
+
dev: "next dev",
|
|
2927
|
+
build: "next build",
|
|
2928
|
+
start: "next start"
|
|
2929
|
+
},
|
|
2930
|
+
dependencies: {
|
|
2931
|
+
"@decantr/css": "^1.0.4",
|
|
2932
|
+
"lucide-react": "^0.468.0",
|
|
2933
|
+
next: "^16.0.0",
|
|
2934
|
+
react: "^19.0.0",
|
|
2935
|
+
"react-dom": "^19.0.0"
|
|
2936
|
+
},
|
|
2937
|
+
devDependencies: {
|
|
2938
|
+
"@types/node": "^20.0.0",
|
|
2939
|
+
"@types/react": "^19.0.0",
|
|
2940
|
+
"@types/react-dom": "^19.0.0",
|
|
2941
|
+
typescript: "^5.7.0"
|
|
2942
|
+
}
|
|
2943
|
+
};
|
|
2944
|
+
writeFileSync8(join17(projectDir, "package.json"), JSON.stringify(packageJson, null, 2) + "\n");
|
|
2945
|
+
writeFileSync8(join17(projectDir, "next.config.ts"), 'import type { NextConfig } from "next";\n\nconst nextConfig: NextConfig = {};\n\nexport default nextConfig;\n');
|
|
2946
|
+
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');
|
|
2947
|
+
writeFileSync8(
|
|
2948
|
+
join17(projectDir, "tsconfig.json"),
|
|
2949
|
+
JSON.stringify(
|
|
2950
|
+
{
|
|
2951
|
+
compilerOptions: {
|
|
2952
|
+
target: "ES2017",
|
|
2953
|
+
lib: ["dom", "dom.iterable", "esnext"],
|
|
2954
|
+
allowJs: true,
|
|
2955
|
+
skipLibCheck: true,
|
|
2956
|
+
strict: true,
|
|
2957
|
+
noEmit: true,
|
|
2958
|
+
esModuleInterop: true,
|
|
2959
|
+
module: "esnext",
|
|
2960
|
+
moduleResolution: "bundler",
|
|
2961
|
+
resolveJsonModule: true,
|
|
2962
|
+
isolatedModules: true,
|
|
2963
|
+
jsx: "preserve",
|
|
2964
|
+
incremental: true,
|
|
2965
|
+
plugins: [{ name: "next" }]
|
|
2966
|
+
},
|
|
2967
|
+
include: ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
|
2968
|
+
exclude: ["node_modules"]
|
|
2969
|
+
},
|
|
2970
|
+
null,
|
|
2971
|
+
2
|
|
2972
|
+
) + "\n"
|
|
2973
|
+
);
|
|
2974
|
+
mkdirSync6(appDir, { recursive: true });
|
|
2975
|
+
mkdirSync6(stylesDir, { recursive: true });
|
|
2976
|
+
writeFileSync8(
|
|
2977
|
+
join17(appDir, "layout.tsx"),
|
|
2978
|
+
`import type { Metadata } from 'next';
|
|
2979
|
+
import '../src/styles/global.css';
|
|
2980
|
+
import '../src/styles/tokens.css';
|
|
2981
|
+
import '../src/styles/treatments.css';
|
|
2982
|
+
|
|
2983
|
+
export const metadata: Metadata = {
|
|
2984
|
+
title: '${title}',
|
|
2985
|
+
description: 'Scaffolded with Decantr',
|
|
2986
|
+
};
|
|
2987
|
+
|
|
2988
|
+
export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) {
|
|
2989
|
+
return (
|
|
2990
|
+
<html lang="en">
|
|
2991
|
+
<body>{children}</body>
|
|
2992
|
+
</html>
|
|
2993
|
+
);
|
|
2994
|
+
}
|
|
2995
|
+
`
|
|
2996
|
+
);
|
|
2997
|
+
writeFileSync8(
|
|
2998
|
+
join17(appDir, "page.tsx"),
|
|
2999
|
+
`import { css } from '@decantr/css';
|
|
3000
|
+
|
|
3001
|
+
export default function HomePage() {
|
|
3002
|
+
return (
|
|
3003
|
+
<main id="main-content" className={css('_minh[100vh] _flex _col _aic _jcc _p6 _gap4')}>
|
|
3004
|
+
<section className={css('_wfull _mw[42rem]') + ' d-section'} data-density="comfortable">
|
|
3005
|
+
<div className={css('_flex _col _aic _gap4 _textc') + ' d-surface'} data-elevation="raised">
|
|
3006
|
+
<p className="d-label" data-anchor>Decantr starter</p>
|
|
3007
|
+
<h1 className={css('_heading2')}>${title}</h1>
|
|
3008
|
+
<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>
|
|
3009
|
+
<span className="d-annotation" data-status="info">Runtime: Next.js App Router</span>
|
|
3010
|
+
</div>
|
|
3011
|
+
</section>
|
|
3012
|
+
</main>
|
|
3013
|
+
);
|
|
3014
|
+
}
|
|
3015
|
+
`
|
|
3016
|
+
);
|
|
3017
|
+
}
|
|
3018
|
+
};
|
|
3019
|
+
var genericWebAdapter = {
|
|
3020
|
+
id: "generic-web",
|
|
3021
|
+
label: "Generic web contract adapter",
|
|
3022
|
+
target: "generic",
|
|
3023
|
+
packAdapter: "generic-web",
|
|
3024
|
+
capabilities: {
|
|
3025
|
+
bootstrap: false,
|
|
3026
|
+
attach: true,
|
|
3027
|
+
styling: true,
|
|
3028
|
+
verify: true
|
|
3029
|
+
},
|
|
3030
|
+
attach: {
|
|
3031
|
+
routeRoots: ["src", "app", "pages"],
|
|
3032
|
+
layoutFiles: ["index.html", "src/main.ts", "src/main.tsx"],
|
|
3033
|
+
componentRoots: ["src/components", "components"]
|
|
3034
|
+
},
|
|
3035
|
+
verify: {
|
|
3036
|
+
devCommand: "npm run dev",
|
|
3037
|
+
buildCommand: "npm run build",
|
|
3038
|
+
distDir: "dist"
|
|
3039
|
+
}
|
|
3040
|
+
};
|
|
3041
|
+
var DECANTR_ADAPTERS = {
|
|
3042
|
+
"react-vite": reactViteBootstrapAdapter,
|
|
3043
|
+
"next-app": nextAppAdapter,
|
|
3044
|
+
"generic-web": genericWebAdapter
|
|
2665
3045
|
};
|
|
2666
3046
|
function resolveBootstrapTarget(target) {
|
|
2667
3047
|
const normalizedTarget = (target || "react").toLowerCase();
|
|
2668
|
-
const platformType = "spa";
|
|
3048
|
+
const platformType = normalizedTarget === "nextjs" ? "ssr" : "spa";
|
|
2669
3049
|
const packAdapter = resolvePackAdapter(normalizedTarget, platformType);
|
|
3050
|
+
const adapterId = normalizedTarget === "nextjs" ? "next-app" : DECANTR_ADAPTERS[packAdapter] ? packAdapter : "generic-web";
|
|
2670
3051
|
return {
|
|
2671
3052
|
target: normalizedTarget,
|
|
2672
3053
|
platformType,
|
|
2673
3054
|
packAdapter,
|
|
2674
|
-
|
|
3055
|
+
adapterId,
|
|
3056
|
+
bootstrapAdapterId: DECANTR_ADAPTERS[adapterId]?.capabilities.bootstrap && DECANTR_ADAPTERS[adapterId]?.writeProjectFiles ? adapterId : null
|
|
2675
3057
|
};
|
|
2676
3058
|
}
|
|
2677
3059
|
function getBootstrapAdapter(resolution) {
|
|
2678
3060
|
if (!resolution.bootstrapAdapterId) {
|
|
2679
3061
|
return null;
|
|
2680
3062
|
}
|
|
2681
|
-
|
|
3063
|
+
const adapter = DECANTR_ADAPTERS[resolution.bootstrapAdapterId];
|
|
3064
|
+
return adapter?.writeProjectFiles ? adapter : null;
|
|
2682
3065
|
}
|
|
2683
3066
|
function detectRoutingMode(projectDir) {
|
|
2684
3067
|
try {
|
|
2685
|
-
const essence = JSON.parse(
|
|
3068
|
+
const essence = JSON.parse(readFileSync12(join17(projectDir, "decantr.essence.json"), "utf-8"));
|
|
2686
3069
|
const routing = essence.meta?.platform?.routing;
|
|
2687
3070
|
if (routing === "history" || routing === "pathname") {
|
|
2688
3071
|
return routing;
|
|
@@ -2694,45 +3077,45 @@ function detectRoutingMode(projectDir) {
|
|
|
2694
3077
|
}
|
|
2695
3078
|
|
|
2696
3079
|
// src/offline-content.ts
|
|
2697
|
-
import { cpSync, existsSync as
|
|
2698
|
-
import { join as
|
|
3080
|
+
import { cpSync, existsSync as existsSync17, mkdirSync as mkdirSync7 } from "fs";
|
|
3081
|
+
import { join as join18, resolve } from "path";
|
|
2699
3082
|
var CONTENT_TYPES2 = ["archetypes", "blueprints", "patterns", "themes", "shells"];
|
|
2700
3083
|
function copyIfExists(source, target) {
|
|
2701
|
-
if (!
|
|
3084
|
+
if (!existsSync17(source)) return false;
|
|
2702
3085
|
if (resolve(source) === resolve(target)) return true;
|
|
2703
3086
|
cpSync(source, target, { recursive: true });
|
|
2704
3087
|
return true;
|
|
2705
3088
|
}
|
|
2706
3089
|
function hydrateContentRoot(projectDir, contentRoot) {
|
|
2707
|
-
if (!
|
|
2708
|
-
const customRoot =
|
|
2709
|
-
const cacheRoot =
|
|
2710
|
-
|
|
2711
|
-
|
|
3090
|
+
if (!existsSync17(contentRoot)) return false;
|
|
3091
|
+
const customRoot = join18(projectDir, ".decantr", "custom");
|
|
3092
|
+
const cacheRoot = join18(projectDir, ".decantr", "cache", "@official");
|
|
3093
|
+
mkdirSync7(customRoot, { recursive: true });
|
|
3094
|
+
mkdirSync7(cacheRoot, { recursive: true });
|
|
2712
3095
|
let copiedAny = false;
|
|
2713
3096
|
for (const type of CONTENT_TYPES2) {
|
|
2714
|
-
const sourceDir =
|
|
2715
|
-
if (!
|
|
2716
|
-
cpSync(sourceDir,
|
|
2717
|
-
cpSync(sourceDir,
|
|
3097
|
+
const sourceDir = join18(contentRoot, type);
|
|
3098
|
+
if (!existsSync17(sourceDir)) continue;
|
|
3099
|
+
cpSync(sourceDir, join18(customRoot, type), { recursive: true });
|
|
3100
|
+
cpSync(sourceDir, join18(cacheRoot, type), { recursive: true });
|
|
2718
3101
|
copiedAny = true;
|
|
2719
3102
|
}
|
|
2720
3103
|
return copiedAny;
|
|
2721
3104
|
}
|
|
2722
3105
|
function seedOfflineRegistry(projectDir, workspaceRoot) {
|
|
2723
|
-
const projectDecantrRoot =
|
|
2724
|
-
|
|
3106
|
+
const projectDecantrRoot = join18(projectDir, ".decantr");
|
|
3107
|
+
mkdirSync7(projectDecantrRoot, { recursive: true });
|
|
2725
3108
|
const configuredContentRoot = process.env.DECANTR_CONTENT_DIR ? resolve(process.env.DECANTR_CONTENT_DIR) : null;
|
|
2726
3109
|
if (configuredContentRoot && hydrateContentRoot(projectDir, configuredContentRoot)) {
|
|
2727
3110
|
return { seeded: true, strategy: "configured-content-root" };
|
|
2728
3111
|
}
|
|
2729
3112
|
const copiedCache = copyIfExists(
|
|
2730
|
-
|
|
2731
|
-
|
|
3113
|
+
join18(workspaceRoot, ".decantr", "cache"),
|
|
3114
|
+
join18(projectDecantrRoot, "cache")
|
|
2732
3115
|
);
|
|
2733
3116
|
const copiedCustom = copyIfExists(
|
|
2734
|
-
|
|
2735
|
-
|
|
3117
|
+
join18(workspaceRoot, ".decantr", "custom"),
|
|
3118
|
+
join18(projectDecantrRoot, "custom")
|
|
2736
3119
|
);
|
|
2737
3120
|
if (copiedCache || copiedCustom) {
|
|
2738
3121
|
return { seeded: true, strategy: "workspace-cache" };
|
|
@@ -2769,12 +3152,80 @@ function dim2(text) {
|
|
|
2769
3152
|
function cyan2(text) {
|
|
2770
3153
|
return `${CYAN3}${text}${RESET6}`;
|
|
2771
3154
|
}
|
|
3155
|
+
function validatePassThroughFlagValue(flag, value) {
|
|
3156
|
+
if (value.length === 0) {
|
|
3157
|
+
throw new Error(`--${flag} cannot be empty.`);
|
|
3158
|
+
}
|
|
3159
|
+
if (value.length > 512) {
|
|
3160
|
+
throw new Error(`--${flag} is too long.`);
|
|
3161
|
+
}
|
|
3162
|
+
if (/[\u0000-\u001f\u007f]/.test(value)) {
|
|
3163
|
+
throw new Error(`--${flag} contains unsupported control characters.`);
|
|
3164
|
+
}
|
|
3165
|
+
return value;
|
|
3166
|
+
}
|
|
3167
|
+
function pushPassThroughFlag(flags, flag, value) {
|
|
3168
|
+
if (value == null) return;
|
|
3169
|
+
flags.push(`--${flag}=${validatePassThroughFlagValue(flag, value)}`);
|
|
3170
|
+
}
|
|
3171
|
+
function buildNewProjectInitArgs(options, inferredAdoption) {
|
|
3172
|
+
const initFlags = [
|
|
3173
|
+
"--yes",
|
|
3174
|
+
"--workflow=greenfield",
|
|
3175
|
+
`--adoption=${validatePassThroughFlagValue("mode", inferredAdoption)}`
|
|
3176
|
+
];
|
|
3177
|
+
pushPassThroughFlag(initFlags, "blueprint", options.blueprint);
|
|
3178
|
+
pushPassThroughFlag(initFlags, "archetype", options.archetype);
|
|
3179
|
+
pushPassThroughFlag(initFlags, "theme", options.theme);
|
|
3180
|
+
pushPassThroughFlag(initFlags, "mode", options.mode);
|
|
3181
|
+
pushPassThroughFlag(initFlags, "shape", options.shape);
|
|
3182
|
+
pushPassThroughFlag(initFlags, "target", options.target);
|
|
3183
|
+
if (options.offline) initFlags.push("--offline");
|
|
3184
|
+
pushPassThroughFlag(initFlags, "registry", options.registry);
|
|
3185
|
+
pushPassThroughFlag(initFlags, "assistant-bridge", options.assistantBridge);
|
|
3186
|
+
return initFlags;
|
|
3187
|
+
}
|
|
3188
|
+
function commandForPlatform(command) {
|
|
3189
|
+
if (process.platform !== "win32") {
|
|
3190
|
+
return command;
|
|
3191
|
+
}
|
|
3192
|
+
return /^(?:npm|pnpm|yarn|bun|npx)$/.test(command) ? `${command}.cmd` : command;
|
|
3193
|
+
}
|
|
3194
|
+
function runArgvCommand(command, args, cwd) {
|
|
3195
|
+
const result = spawnSync(commandForPlatform(command), args, {
|
|
3196
|
+
cwd,
|
|
3197
|
+
stdio: "inherit",
|
|
3198
|
+
shell: false
|
|
3199
|
+
});
|
|
3200
|
+
if (result.error) {
|
|
3201
|
+
throw result.error;
|
|
3202
|
+
}
|
|
3203
|
+
if (result.status !== 0) {
|
|
3204
|
+
throw new Error(`Command failed: ${command} ${args.join(" ")}`);
|
|
3205
|
+
}
|
|
3206
|
+
}
|
|
3207
|
+
function resolveInitCommand(initFlags) {
|
|
3208
|
+
const bundledCliEntrypoint = fileURLToPath(new URL("./bin.js", import.meta.url));
|
|
3209
|
+
const cliEntrypoint = existsSync18(bundledCliEntrypoint) ? bundledCliEntrypoint : process.argv[1] && existsSync18(process.argv[1]) ? process.argv[1] : null;
|
|
3210
|
+
if (cliEntrypoint) {
|
|
3211
|
+
return {
|
|
3212
|
+
command: process.execPath,
|
|
3213
|
+
args: [cliEntrypoint, "init", ...initFlags]
|
|
3214
|
+
};
|
|
3215
|
+
}
|
|
3216
|
+
return {
|
|
3217
|
+
command: "npx",
|
|
3218
|
+
args: ["decantr", "init", ...initFlags]
|
|
3219
|
+
};
|
|
3220
|
+
}
|
|
2772
3221
|
async function cmdNewProject(projectName, options) {
|
|
2773
3222
|
const workspaceRoot = process.cwd();
|
|
2774
3223
|
const projectDir = resolve2(workspaceRoot, projectName);
|
|
2775
3224
|
const bootstrapTarget = resolveBootstrapTarget(options.target);
|
|
2776
3225
|
const bootstrapAdapter = getBootstrapAdapter(bootstrapTarget);
|
|
2777
|
-
const
|
|
3226
|
+
const registryBackedScaffold = Boolean(options.blueprint || options.archetype);
|
|
3227
|
+
const inferredAdoption = options.adoption || (registryBackedScaffold ? "decantr-css" : "contract-only");
|
|
3228
|
+
const shouldBootstrapRuntime = Boolean(bootstrapAdapter && inferredAdoption === "decantr-css");
|
|
2778
3229
|
if (!/^[a-z0-9][a-z0-9._-]*$/i.test(projectName)) {
|
|
2779
3230
|
console.error(
|
|
2780
3231
|
error2("Invalid project name. Use alphanumeric characters, hyphens, dots, or underscores.")
|
|
@@ -2782,18 +3233,32 @@ async function cmdNewProject(projectName, options) {
|
|
|
2782
3233
|
process.exitCode = 1;
|
|
2783
3234
|
return;
|
|
2784
3235
|
}
|
|
2785
|
-
if (
|
|
3236
|
+
if (existsSync18(projectDir)) {
|
|
2786
3237
|
console.error(error2(`Directory "${projectName}" already exists.`));
|
|
2787
3238
|
process.exitCode = 1;
|
|
2788
3239
|
return;
|
|
2789
3240
|
}
|
|
3241
|
+
let initFlags;
|
|
3242
|
+
try {
|
|
3243
|
+
initFlags = buildNewProjectInitArgs(options, inferredAdoption);
|
|
3244
|
+
} catch (err) {
|
|
3245
|
+
console.error(error2(err.message));
|
|
3246
|
+
process.exitCode = 1;
|
|
3247
|
+
return;
|
|
3248
|
+
}
|
|
2790
3249
|
console.log(heading(`Creating ${projectName}...`));
|
|
2791
|
-
|
|
3250
|
+
mkdirSync8(projectDir, { recursive: true });
|
|
2792
3251
|
console.log(dim2(` Created ${projectName}/`));
|
|
2793
3252
|
const title = projectName.replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
2794
|
-
if (bootstrapAdapter) {
|
|
3253
|
+
if (shouldBootstrapRuntime && bootstrapAdapter) {
|
|
2795
3254
|
bootstrapAdapter.writeProjectFiles(projectDir, title, "hash");
|
|
2796
3255
|
console.log(dim2(` Bootstrapped ${bootstrapAdapter.label}`));
|
|
3256
|
+
} else if (bootstrapAdapter) {
|
|
3257
|
+
console.log(
|
|
3258
|
+
dim2(
|
|
3259
|
+
` Skipping runtime bootstrap for adoption=${inferredAdoption}; creating a Decantr contract-only workspace.`
|
|
3260
|
+
)
|
|
3261
|
+
);
|
|
2797
3262
|
} else {
|
|
2798
3263
|
console.log(
|
|
2799
3264
|
`${YELLOW4} No greenfield bootstrap adapter is available yet for target "${bootstrapTarget.target}" (${bootstrapTarget.packAdapter}).${RESET6}`
|
|
@@ -2805,10 +3270,10 @@ async function cmdNewProject(projectName, options) {
|
|
|
2805
3270
|
);
|
|
2806
3271
|
}
|
|
2807
3272
|
const packageManager = detectPackageManager();
|
|
2808
|
-
if (
|
|
3273
|
+
if (shouldBootstrapRuntime) {
|
|
2809
3274
|
console.log(heading("Installing dependencies..."));
|
|
2810
3275
|
try {
|
|
2811
|
-
|
|
3276
|
+
runArgvCommand(packageManager, ["install"], projectDir);
|
|
2812
3277
|
} catch {
|
|
2813
3278
|
console.log(
|
|
2814
3279
|
`
|
|
@@ -2845,21 +3310,10 @@ ${YELLOW4}Dependency install failed. Run \`${packageManager} install\` manually.
|
|
|
2845
3310
|
return;
|
|
2846
3311
|
}
|
|
2847
3312
|
console.log(heading("Initializing Decantr..."));
|
|
2848
|
-
const initFlags = ["--yes", "--existing"];
|
|
2849
|
-
if (options.blueprint) initFlags.push(`--blueprint=${options.blueprint}`);
|
|
2850
|
-
if (options.archetype) initFlags.push(`--archetype=${options.archetype}`);
|
|
2851
|
-
if (options.theme) initFlags.push(`--theme=${options.theme}`);
|
|
2852
|
-
if (options.mode) initFlags.push(`--mode=${options.mode}`);
|
|
2853
|
-
if (options.shape) initFlags.push(`--shape=${options.shape}`);
|
|
2854
|
-
if (options.target) initFlags.push(`--target=${options.target}`);
|
|
2855
|
-
if (options.offline) initFlags.push("--offline");
|
|
2856
|
-
if (options.registry) initFlags.push(`--registry=${options.registry}`);
|
|
2857
3313
|
try {
|
|
2858
|
-
const
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
execSync(`${cliPath} init ${initFlags.join(" ")}`, { cwd: projectDir, stdio: "inherit" });
|
|
2862
|
-
if (bootstrapAdapter) {
|
|
3314
|
+
const initCommand = resolveInitCommand(initFlags);
|
|
3315
|
+
runArgvCommand(initCommand.command, initCommand.args, projectDir);
|
|
3316
|
+
if (shouldBootstrapRuntime && bootstrapAdapter) {
|
|
2863
3317
|
bootstrapAdapter.writeProjectFiles(projectDir, title, detectRoutingMode(projectDir));
|
|
2864
3318
|
}
|
|
2865
3319
|
} catch {
|
|
@@ -2872,33 +3326,33 @@ ${YELLOW4}Decantr init encountered issues. Run \`decantr init\` manually inside
|
|
|
2872
3326
|
\u2713 Project "${projectName}" created!
|
|
2873
3327
|
`));
|
|
2874
3328
|
console.log(` ${cyan2("cd " + projectName)}`);
|
|
2875
|
-
if (
|
|
3329
|
+
if (shouldBootstrapRuntime) {
|
|
2876
3330
|
console.log(` ${cyan2(packageManager + " run dev")}`);
|
|
2877
3331
|
} else {
|
|
2878
3332
|
console.log(
|
|
2879
3333
|
dim2(
|
|
2880
|
-
` Contract-only mode for target ${bootstrapTarget.target}. Bring your own runtime, or rerun ${cyan2(
|
|
3334
|
+
` 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
3335
|
)
|
|
2882
3336
|
);
|
|
2883
3337
|
}
|
|
2884
3338
|
console.log("");
|
|
2885
3339
|
}
|
|
2886
3340
|
function detectPackageManager() {
|
|
2887
|
-
if (
|
|
3341
|
+
if (existsSync18(join19(process.cwd(), "pnpm-lock.yaml")) || existsSync18(join19(process.cwd(), "pnpm-workspace.yaml"))) {
|
|
2888
3342
|
return "pnpm";
|
|
2889
3343
|
}
|
|
2890
|
-
if (
|
|
3344
|
+
if (existsSync18(join19(process.cwd(), "yarn.lock"))) {
|
|
2891
3345
|
return "yarn";
|
|
2892
3346
|
}
|
|
2893
|
-
if (
|
|
3347
|
+
if (existsSync18(join19(process.cwd(), "bun.lockb")) || existsSync18(join19(process.cwd(), "bun.lock"))) {
|
|
2894
3348
|
return "bun";
|
|
2895
3349
|
}
|
|
2896
3350
|
return "npm";
|
|
2897
3351
|
}
|
|
2898
3352
|
|
|
2899
3353
|
// src/commands/publish.ts
|
|
2900
|
-
import { existsSync as
|
|
2901
|
-
import { join as
|
|
3354
|
+
import { existsSync as existsSync19, readFileSync as readFileSync13 } from "fs";
|
|
3355
|
+
import { join as join20 } from "path";
|
|
2902
3356
|
import {
|
|
2903
3357
|
API_CONTENT_TYPE_TO_CONTENT_TYPE,
|
|
2904
3358
|
CONTENT_TYPE_TO_API_CONTENT_TYPE as CONTENT_TYPE_TO_API_CONTENT_TYPE2,
|
|
@@ -2914,8 +3368,8 @@ async function cmdPublish(type, name, projectRoot = process.cwd()) {
|
|
|
2914
3368
|
}
|
|
2915
3369
|
const singularType = API_CONTENT_TYPE_TO_CONTENT_TYPE[type] || type;
|
|
2916
3370
|
const pluralType = CONTENT_TYPE_TO_API_CONTENT_TYPE2[type] || CONTENT_TYPE_TO_API_CONTENT_TYPE2[singularType] || `${type}s`;
|
|
2917
|
-
const customPath =
|
|
2918
|
-
if (!
|
|
3371
|
+
const customPath = join20(projectRoot, ".decantr", "custom", pluralType, `${name}.json`);
|
|
3372
|
+
if (!existsSync19(customPath)) {
|
|
2919
3373
|
console.error(`Custom ${singularType} "${name}" not found at ${customPath}`);
|
|
2920
3374
|
console.error(`Create one first: decantr create ${singularType} ${name}`);
|
|
2921
3375
|
process.exitCode = 1;
|
|
@@ -2923,7 +3377,7 @@ async function cmdPublish(type, name, projectRoot = process.cwd()) {
|
|
|
2923
3377
|
}
|
|
2924
3378
|
let data;
|
|
2925
3379
|
try {
|
|
2926
|
-
data = JSON.parse(
|
|
3380
|
+
data = JSON.parse(readFileSync13(customPath, "utf-8"));
|
|
2927
3381
|
} catch {
|
|
2928
3382
|
console.error(`Failed to parse ${customPath}`);
|
|
2929
3383
|
process.exitCode = 1;
|
|
@@ -2961,23 +3415,23 @@ async function cmdPublish(type, name, projectRoot = process.cwd()) {
|
|
|
2961
3415
|
}
|
|
2962
3416
|
|
|
2963
3417
|
// src/commands/refresh.ts
|
|
2964
|
-
import { existsSync as
|
|
2965
|
-
import { join as
|
|
3418
|
+
import { existsSync as existsSync20, readFileSync as readFileSync14 } from "fs";
|
|
3419
|
+
import { join as join21 } from "path";
|
|
2966
3420
|
import { isV3 as isV33 } from "@decantr/essence-spec";
|
|
2967
3421
|
var GREEN7 = "\x1B[32m";
|
|
2968
3422
|
var RED6 = "\x1B[31m";
|
|
2969
3423
|
var DIM7 = "\x1B[2m";
|
|
2970
3424
|
var RESET7 = "\x1B[0m";
|
|
2971
3425
|
async function cmdRefresh(projectRoot = process.cwd(), options = {}) {
|
|
2972
|
-
const essencePath =
|
|
2973
|
-
if (!
|
|
3426
|
+
const essencePath = join21(projectRoot, "decantr.essence.json");
|
|
3427
|
+
if (!existsSync20(essencePath)) {
|
|
2974
3428
|
console.error(`${RED6}No decantr.essence.json found. Run \`decantr init\` first.${RESET7}`);
|
|
2975
3429
|
process.exitCode = 1;
|
|
2976
3430
|
return;
|
|
2977
3431
|
}
|
|
2978
3432
|
let essence;
|
|
2979
3433
|
try {
|
|
2980
|
-
const raw =
|
|
3434
|
+
const raw = readFileSync14(essencePath, "utf-8");
|
|
2981
3435
|
const parsed = JSON.parse(raw);
|
|
2982
3436
|
if (!isV33(parsed)) {
|
|
2983
3437
|
console.error(`${RED6}Essence is not v3. Run \`decantr migrate\` first.${RESET7}`);
|
|
@@ -2991,7 +3445,7 @@ async function cmdRefresh(projectRoot = process.cwd(), options = {}) {
|
|
|
2991
3445
|
return;
|
|
2992
3446
|
}
|
|
2993
3447
|
const registryClient = new RegistryClient({
|
|
2994
|
-
cacheDir:
|
|
3448
|
+
cacheDir: join21(projectRoot, ".decantr", "cache"),
|
|
2995
3449
|
offline: options.offline
|
|
2996
3450
|
});
|
|
2997
3451
|
console.log("Regenerating derived files...\n");
|
|
@@ -3011,8 +3465,8 @@ async function cmdRefresh(projectRoot = process.cwd(), options = {}) {
|
|
|
3011
3465
|
}
|
|
3012
3466
|
|
|
3013
3467
|
// src/commands/registry-mirror.ts
|
|
3014
|
-
import { mkdirSync as
|
|
3015
|
-
import { join as
|
|
3468
|
+
import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync9 } from "fs";
|
|
3469
|
+
import { join as join22 } from "path";
|
|
3016
3470
|
import { API_CONTENT_TYPES, RegistryAPIClient as RegistryAPIClient2 } from "@decantr/registry";
|
|
3017
3471
|
var GREEN8 = "\x1B[32m";
|
|
3018
3472
|
var RED7 = "\x1B[31m";
|
|
@@ -3040,7 +3494,7 @@ async function cmdRegistryMirror(projectRoot, options = {}) {
|
|
|
3040
3494
|
process.exitCode = 1;
|
|
3041
3495
|
return;
|
|
3042
3496
|
}
|
|
3043
|
-
const cacheDir =
|
|
3497
|
+
const cacheDir = join22(projectRoot, ".decantr", "cache");
|
|
3044
3498
|
const counts = {};
|
|
3045
3499
|
const failed = [];
|
|
3046
3500
|
console.log(`
|
|
@@ -3050,19 +3504,19 @@ Mirroring registry content to ${DIM8}.decantr/cache/${RESET8}
|
|
|
3050
3504
|
try {
|
|
3051
3505
|
const result = await apiClient.listContent(type, { namespace: "@official" });
|
|
3052
3506
|
const items = result.items;
|
|
3053
|
-
const typeDir =
|
|
3054
|
-
|
|
3055
|
-
|
|
3507
|
+
const typeDir = join22(cacheDir, "@official", type);
|
|
3508
|
+
mkdirSync9(typeDir, { recursive: true });
|
|
3509
|
+
writeFileSync9(join22(typeDir, "index.json"), JSON.stringify(result, null, 2));
|
|
3056
3510
|
let itemCount = 0;
|
|
3057
3511
|
for (const item of items) {
|
|
3058
3512
|
const slug = item.slug || item.id;
|
|
3059
3513
|
if (!slug) continue;
|
|
3060
3514
|
try {
|
|
3061
3515
|
const fullItem = await apiClient.getContent(type, "@official", slug);
|
|
3062
|
-
|
|
3516
|
+
writeFileSync9(join22(typeDir, `${slug}.json`), JSON.stringify(fullItem, null, 2));
|
|
3063
3517
|
itemCount++;
|
|
3064
3518
|
} catch {
|
|
3065
|
-
|
|
3519
|
+
writeFileSync9(join22(typeDir, `${slug}.json`), JSON.stringify(item, null, 2));
|
|
3066
3520
|
itemCount++;
|
|
3067
3521
|
}
|
|
3068
3522
|
}
|
|
@@ -3077,8 +3531,8 @@ Mirroring registry content to ${DIM8}.decantr/cache/${RESET8}
|
|
|
3077
3531
|
mirrored_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3078
3532
|
counts
|
|
3079
3533
|
};
|
|
3080
|
-
|
|
3081
|
-
|
|
3534
|
+
mkdirSync9(join22(cacheDir), { recursive: true });
|
|
3535
|
+
writeFileSync9(join22(cacheDir, "mirror-manifest.json"), JSON.stringify(manifest, null, 2));
|
|
3082
3536
|
const totalItems = Object.values(counts).reduce((a, b) => a + b, 0);
|
|
3083
3537
|
console.log("");
|
|
3084
3538
|
if (failed.length > 0) {
|
|
@@ -3095,23 +3549,23 @@ Mirroring registry content to ${DIM8}.decantr/cache/${RESET8}
|
|
|
3095
3549
|
}
|
|
3096
3550
|
|
|
3097
3551
|
// src/commands/remove.ts
|
|
3098
|
-
import { existsSync as
|
|
3099
|
-
import { join as
|
|
3552
|
+
import { existsSync as existsSync22, readFileSync as readFileSync15, rmSync as rmSync2, writeFileSync as writeFileSync10 } from "fs";
|
|
3553
|
+
import { join as join23 } from "path";
|
|
3100
3554
|
import { isV3 as isV34, migrateV30ToV31 as migrateV30ToV312 } from "@decantr/essence-spec";
|
|
3101
3555
|
var GREEN9 = "\x1B[32m";
|
|
3102
3556
|
var RED8 = "\x1B[31m";
|
|
3103
3557
|
var DIM9 = "\x1B[2m";
|
|
3104
3558
|
var RESET9 = "\x1B[0m";
|
|
3105
3559
|
function readAndMigrate2(projectRoot) {
|
|
3106
|
-
const essencePath =
|
|
3107
|
-
if (!
|
|
3560
|
+
const essencePath = join23(projectRoot, "decantr.essence.json");
|
|
3561
|
+
if (!existsSync22(essencePath)) {
|
|
3108
3562
|
console.error(`${RED8}No decantr.essence.json found. Run \`decantr init\` first.${RESET9}`);
|
|
3109
3563
|
process.exitCode = 1;
|
|
3110
3564
|
return null;
|
|
3111
3565
|
}
|
|
3112
3566
|
let parsed;
|
|
3113
3567
|
try {
|
|
3114
|
-
parsed = JSON.parse(
|
|
3568
|
+
parsed = JSON.parse(readFileSync15(essencePath, "utf-8"));
|
|
3115
3569
|
} catch (e) {
|
|
3116
3570
|
console.error(`${RED8}Could not read essence: ${e.message}${RESET9}`);
|
|
3117
3571
|
process.exitCode = 1;
|
|
@@ -3126,7 +3580,7 @@ function readAndMigrate2(projectRoot) {
|
|
|
3126
3580
|
return { essence, essencePath };
|
|
3127
3581
|
}
|
|
3128
3582
|
function writeEssence2(essencePath, essence) {
|
|
3129
|
-
|
|
3583
|
+
writeFileSync10(essencePath, JSON.stringify(essence, null, 2) + "\n");
|
|
3130
3584
|
}
|
|
3131
3585
|
function recomputeGlobalFeatures(essence) {
|
|
3132
3586
|
const all = /* @__PURE__ */ new Set();
|
|
@@ -3168,14 +3622,14 @@ async function cmdRemoveSection(sectionId, args, projectRoot = process.cwd()) {
|
|
|
3168
3622
|
sections.splice(idx, 1);
|
|
3169
3623
|
recomputeGlobalFeatures(essence);
|
|
3170
3624
|
removeRoutes(essence, sectionId);
|
|
3171
|
-
const contextFile =
|
|
3172
|
-
if (
|
|
3625
|
+
const contextFile = join23(projectRoot, ".decantr", "context", `${sectionId}.md`);
|
|
3626
|
+
if (existsSync22(contextFile)) {
|
|
3173
3627
|
rmSync2(contextFile);
|
|
3174
3628
|
}
|
|
3175
3629
|
writeEssence2(essencePath, essence);
|
|
3176
3630
|
console.log(`${GREEN9}Removed section "${sectionId}".${RESET9}`);
|
|
3177
3631
|
const registryClient = new RegistryClient({
|
|
3178
|
-
cacheDir:
|
|
3632
|
+
cacheDir: join23(projectRoot, ".decantr", "cache")
|
|
3179
3633
|
});
|
|
3180
3634
|
await refreshDerivedFiles(projectRoot, essence, registryClient);
|
|
3181
3635
|
console.log(`${GREEN9}Derived files refreshed.${RESET9}`);
|
|
@@ -3211,7 +3665,7 @@ async function cmdRemovePage(path, args, projectRoot = process.cwd()) {
|
|
|
3211
3665
|
writeEssence2(essencePath, essence);
|
|
3212
3666
|
console.log(`${GREEN9}Removed page "${pageId}" from section "${sectionId}".${RESET9}`);
|
|
3213
3667
|
const registryClient = new RegistryClient({
|
|
3214
|
-
cacheDir:
|
|
3668
|
+
cacheDir: join23(projectRoot, ".decantr", "cache")
|
|
3215
3669
|
});
|
|
3216
3670
|
await refreshDerivedFiles(projectRoot, essence, registryClient);
|
|
3217
3671
|
console.log(`${GREEN9}Derived files refreshed.${RESET9}`);
|
|
@@ -3252,15 +3706,15 @@ async function cmdRemoveFeature(feature, args, projectRoot = process.cwd()) {
|
|
|
3252
3706
|
const target = sectionId ? `section "${sectionId}" and global` : "global";
|
|
3253
3707
|
console.log(`${GREEN9}Removed feature "${feature}" from ${target} features.${RESET9}`);
|
|
3254
3708
|
const registryClient = new RegistryClient({
|
|
3255
|
-
cacheDir:
|
|
3709
|
+
cacheDir: join23(projectRoot, ".decantr", "cache")
|
|
3256
3710
|
});
|
|
3257
3711
|
await refreshDerivedFiles(projectRoot, essence, registryClient);
|
|
3258
3712
|
console.log(`${GREEN9}Derived files refreshed.${RESET9}`);
|
|
3259
3713
|
}
|
|
3260
3714
|
|
|
3261
3715
|
// src/commands/sync-drift.ts
|
|
3262
|
-
import { existsSync as
|
|
3263
|
-
import { join as
|
|
3716
|
+
import { existsSync as existsSync23, readFileSync as readFileSync16, writeFileSync as writeFileSync11 } from "fs";
|
|
3717
|
+
import { join as join24 } from "path";
|
|
3264
3718
|
var GREEN10 = "\x1B[32m";
|
|
3265
3719
|
var RED9 = "\x1B[31m";
|
|
3266
3720
|
var YELLOW6 = "\x1B[33m";
|
|
@@ -3269,8 +3723,8 @@ var DIM10 = "\x1B[2m";
|
|
|
3269
3723
|
var BOLD4 = "\x1B[1m";
|
|
3270
3724
|
var CYAN5 = "\x1B[36m";
|
|
3271
3725
|
async function cmdSyncDrift(projectRoot = process.cwd()) {
|
|
3272
|
-
const driftLogPath =
|
|
3273
|
-
if (!
|
|
3726
|
+
const driftLogPath = join24(projectRoot, ".decantr", "drift-log.json");
|
|
3727
|
+
if (!existsSync23(driftLogPath)) {
|
|
3274
3728
|
console.log(`${GREEN10}No drift log found \u2014 no drift recorded.${RESET10}`);
|
|
3275
3729
|
console.log(
|
|
3276
3730
|
`${DIM10}Drift is logged when guard violations are detected during development.${RESET10}`
|
|
@@ -3279,7 +3733,7 @@ async function cmdSyncDrift(projectRoot = process.cwd()) {
|
|
|
3279
3733
|
}
|
|
3280
3734
|
let entries;
|
|
3281
3735
|
try {
|
|
3282
|
-
entries = JSON.parse(
|
|
3736
|
+
entries = JSON.parse(readFileSync16(driftLogPath, "utf-8"));
|
|
3283
3737
|
} catch {
|
|
3284
3738
|
console.error(`${RED9}Could not parse drift-log.json${RESET10}`);
|
|
3285
3739
|
process.exitCode = 1;
|
|
@@ -3325,13 +3779,13 @@ ${BOLD4}Unresolved Drift Entries (${unresolved.length})${RESET10}
|
|
|
3325
3779
|
console.log("");
|
|
3326
3780
|
}
|
|
3327
3781
|
function resolveDriftEntries(projectRoot, options) {
|
|
3328
|
-
const driftLogPath =
|
|
3329
|
-
if (!
|
|
3782
|
+
const driftLogPath = join24(projectRoot, ".decantr", "drift-log.json");
|
|
3783
|
+
if (!existsSync23(driftLogPath)) {
|
|
3330
3784
|
return { success: true };
|
|
3331
3785
|
}
|
|
3332
3786
|
if (options.clear) {
|
|
3333
3787
|
try {
|
|
3334
|
-
|
|
3788
|
+
writeFileSync11(driftLogPath, "[]");
|
|
3335
3789
|
return { success: true };
|
|
3336
3790
|
} catch (e) {
|
|
3337
3791
|
return { success: false, error: `Could not clear drift log: ${e.message}` };
|
|
@@ -3339,7 +3793,7 @@ function resolveDriftEntries(projectRoot, options) {
|
|
|
3339
3793
|
}
|
|
3340
3794
|
let entries;
|
|
3341
3795
|
try {
|
|
3342
|
-
entries = JSON.parse(
|
|
3796
|
+
entries = JSON.parse(readFileSync16(driftLogPath, "utf-8"));
|
|
3343
3797
|
} catch {
|
|
3344
3798
|
return { success: false, error: "Could not parse drift-log.json" };
|
|
3345
3799
|
}
|
|
@@ -3358,7 +3812,7 @@ function resolveDriftEntries(projectRoot, options) {
|
|
|
3358
3812
|
}
|
|
3359
3813
|
}
|
|
3360
3814
|
try {
|
|
3361
|
-
|
|
3815
|
+
writeFileSync11(driftLogPath, JSON.stringify(entries, null, 2));
|
|
3362
3816
|
return { success: true };
|
|
3363
3817
|
} catch (e) {
|
|
3364
3818
|
return { success: false, error: `Could not write drift log: ${e.message}` };
|
|
@@ -3366,8 +3820,8 @@ function resolveDriftEntries(projectRoot, options) {
|
|
|
3366
3820
|
}
|
|
3367
3821
|
|
|
3368
3822
|
// src/commands/theme-switch.ts
|
|
3369
|
-
import { existsSync as
|
|
3370
|
-
import { join as
|
|
3823
|
+
import { existsSync as existsSync24, readFileSync as readFileSync17, writeFileSync as writeFileSync12 } from "fs";
|
|
3824
|
+
import { join as join25 } from "path";
|
|
3371
3825
|
import { isV3 as isV35, migrateV30ToV31 as migrateV30ToV313 } from "@decantr/essence-spec";
|
|
3372
3826
|
var GREEN11 = "\x1B[32m";
|
|
3373
3827
|
var RED10 = "\x1B[31m";
|
|
@@ -3384,15 +3838,15 @@ async function cmdThemeSwitch(themeName, args, projectRoot = process.cwd()) {
|
|
|
3384
3838
|
process.exitCode = 1;
|
|
3385
3839
|
return;
|
|
3386
3840
|
}
|
|
3387
|
-
const essencePath =
|
|
3388
|
-
if (!
|
|
3841
|
+
const essencePath = join25(projectRoot, "decantr.essence.json");
|
|
3842
|
+
if (!existsSync24(essencePath)) {
|
|
3389
3843
|
console.error(`${RED10}No decantr.essence.json found. Run \`decantr init\` first.${RESET11}`);
|
|
3390
3844
|
process.exitCode = 1;
|
|
3391
3845
|
return;
|
|
3392
3846
|
}
|
|
3393
3847
|
let parsed;
|
|
3394
3848
|
try {
|
|
3395
|
-
parsed = JSON.parse(
|
|
3849
|
+
parsed = JSON.parse(readFileSync17(essencePath, "utf-8"));
|
|
3396
3850
|
} catch (e) {
|
|
3397
3851
|
console.error(`${RED10}Could not read essence: ${e.message}${RESET11}`);
|
|
3398
3852
|
process.exitCode = 1;
|
|
@@ -3441,7 +3895,7 @@ async function cmdThemeSwitch(themeName, args, projectRoot = process.cwd()) {
|
|
|
3441
3895
|
essence.dna.theme.mode = mode;
|
|
3442
3896
|
}
|
|
3443
3897
|
const registryClient = new RegistryClient({
|
|
3444
|
-
cacheDir:
|
|
3898
|
+
cacheDir: join25(projectRoot, ".decantr", "cache")
|
|
3445
3899
|
});
|
|
3446
3900
|
try {
|
|
3447
3901
|
const themeResult = await registryClient.fetchTheme(themeName);
|
|
@@ -3456,7 +3910,7 @@ async function cmdThemeSwitch(themeName, args, projectRoot = process.cwd()) {
|
|
|
3456
3910
|
}
|
|
3457
3911
|
} catch {
|
|
3458
3912
|
}
|
|
3459
|
-
|
|
3913
|
+
writeFileSync12(essencePath, JSON.stringify(essence, null, 2) + "\n");
|
|
3460
3914
|
console.log(`${GREEN11}Switched theme: ${oldThemeId} \u2192 ${themeName}${RESET11}`);
|
|
3461
3915
|
if (shape) console.log(` ${DIM11}Shape: ${shape}${RESET11}`);
|
|
3462
3916
|
if (mode) console.log(` ${DIM11}Mode: ${mode}${RESET11}`);
|
|
@@ -3478,10 +3932,10 @@ var CYAN6 = "\x1B[36m";
|
|
|
3478
3932
|
function ask(question, defaultValue) {
|
|
3479
3933
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
3480
3934
|
const prompt = defaultValue ? `${question} ${DIM12}(${defaultValue})${RESET12}: ` : `${question}: `;
|
|
3481
|
-
return new Promise((
|
|
3935
|
+
return new Promise((resolve5) => {
|
|
3482
3936
|
rl.question(prompt, (answer) => {
|
|
3483
3937
|
rl.close();
|
|
3484
|
-
|
|
3938
|
+
resolve5(answer.trim() || defaultValue || "");
|
|
3485
3939
|
});
|
|
3486
3940
|
});
|
|
3487
3941
|
}
|
|
@@ -3663,7 +4117,11 @@ async function runInteractivePrompts(detected, archetypes, blueprints, themes, w
|
|
|
3663
4117
|
personality: ["professional"],
|
|
3664
4118
|
features: [],
|
|
3665
4119
|
existing: workflowSeed?.existing || detected.existingEssence,
|
|
3666
|
-
workflowMode: workflowSeed?.workflowMode
|
|
4120
|
+
workflowMode: workflowSeed?.workflowMode,
|
|
4121
|
+
adoptionMode: workflowSeed?.adoptionMode,
|
|
4122
|
+
contentSource: workflowSeed?.contentSource,
|
|
4123
|
+
assistantBridge: workflowSeed?.assistantBridge,
|
|
4124
|
+
projectScope: workflowSeed?.projectScope
|
|
3667
4125
|
};
|
|
3668
4126
|
}
|
|
3669
4127
|
function parseFlags(args, detected) {
|
|
@@ -3685,6 +4143,12 @@ function parseFlags(args, detected) {
|
|
|
3685
4143
|
if (typeof args.features === "string")
|
|
3686
4144
|
options.features = args.features.split(",").map((s) => s.trim());
|
|
3687
4145
|
if (args.existing === true) options.existing = true;
|
|
4146
|
+
if (args.adoption === "contract-only" || args.adoption === "style-bridge" || args.adoption === "decantr-css") {
|
|
4147
|
+
options.adoptionMode = args.adoption;
|
|
4148
|
+
}
|
|
4149
|
+
if (args["assistant-bridge"] === "none" || args["assistant-bridge"] === "preview" || args["assistant-bridge"] === "apply") {
|
|
4150
|
+
options.assistantBridge = args["assistant-bridge"];
|
|
4151
|
+
}
|
|
3688
4152
|
return options;
|
|
3689
4153
|
}
|
|
3690
4154
|
function mergeWithDefaults(flags, detected, workflowSeed) {
|
|
@@ -3701,12 +4165,16 @@ function mergeWithDefaults(flags, detected, workflowSeed) {
|
|
|
3701
4165
|
personality: flags.personality || ["professional"],
|
|
3702
4166
|
features: flags.features || [],
|
|
3703
4167
|
existing: flags.existing || workflowSeed?.existing || detected.existingEssence,
|
|
3704
|
-
workflowMode: flags.workflowMode || workflowSeed?.workflowMode
|
|
4168
|
+
workflowMode: flags.workflowMode || workflowSeed?.workflowMode,
|
|
4169
|
+
adoptionMode: flags.adoptionMode || workflowSeed?.adoptionMode,
|
|
4170
|
+
contentSource: flags.contentSource || workflowSeed?.contentSource,
|
|
4171
|
+
assistantBridge: flags.assistantBridge || workflowSeed?.assistantBridge,
|
|
4172
|
+
projectScope: flags.projectScope || workflowSeed?.projectScope
|
|
3705
4173
|
};
|
|
3706
4174
|
}
|
|
3707
4175
|
async function runSimplifiedInit(blueprints) {
|
|
3708
4176
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
3709
|
-
const question = (q) => new Promise((
|
|
4177
|
+
const question = (q) => new Promise((resolve5) => rl.question(q, resolve5));
|
|
3710
4178
|
console.log("\n? What blueprint would you like to scaffold?\n");
|
|
3711
4179
|
console.log(" 1. Decantr default (recommended)");
|
|
3712
4180
|
console.log(" 2. Search registry...\n");
|
|
@@ -3738,8 +4206,8 @@ async function runSimplifiedInit(blueprints) {
|
|
|
3738
4206
|
}
|
|
3739
4207
|
|
|
3740
4208
|
// src/theme-commands.ts
|
|
3741
|
-
import { existsSync as
|
|
3742
|
-
import { join as
|
|
4209
|
+
import { existsSync as existsSync25, mkdirSync as mkdirSync10, readdirSync as readdirSync5, readFileSync as readFileSync18, rmSync as rmSync3, writeFileSync as writeFileSync13 } from "fs";
|
|
4210
|
+
import { join as join26 } from "path";
|
|
3743
4211
|
var REQUIRED_FIELDS = [
|
|
3744
4212
|
"$schema",
|
|
3745
4213
|
"id",
|
|
@@ -3799,20 +4267,20 @@ function validateCustomTheme(theme) {
|
|
|
3799
4267
|
};
|
|
3800
4268
|
}
|
|
3801
4269
|
function createTheme(projectRoot, id, name) {
|
|
3802
|
-
const customThemesDir =
|
|
3803
|
-
const themePath =
|
|
3804
|
-
const howToPath =
|
|
3805
|
-
|
|
3806
|
-
if (
|
|
4270
|
+
const customThemesDir = join26(projectRoot, ".decantr", "custom", "themes");
|
|
4271
|
+
const themePath = join26(customThemesDir, `${id}.json`);
|
|
4272
|
+
const howToPath = join26(customThemesDir, "how-to-theme.md");
|
|
4273
|
+
mkdirSync10(customThemesDir, { recursive: true });
|
|
4274
|
+
if (existsSync25(themePath)) {
|
|
3807
4275
|
return {
|
|
3808
4276
|
success: false,
|
|
3809
4277
|
error: `Theme "${id}" already exists at ${themePath}`
|
|
3810
4278
|
};
|
|
3811
4279
|
}
|
|
3812
4280
|
const skeleton = getThemeSkeleton(id, name);
|
|
3813
|
-
|
|
3814
|
-
if (!
|
|
3815
|
-
|
|
4281
|
+
writeFileSync13(themePath, JSON.stringify(skeleton, null, 2));
|
|
4282
|
+
if (!existsSync25(howToPath)) {
|
|
4283
|
+
writeFileSync13(howToPath, getHowToThemeDoc());
|
|
3816
4284
|
}
|
|
3817
4285
|
return {
|
|
3818
4286
|
success: true,
|
|
@@ -3820,17 +4288,17 @@ function createTheme(projectRoot, id, name) {
|
|
|
3820
4288
|
};
|
|
3821
4289
|
}
|
|
3822
4290
|
function listCustomThemes(projectRoot) {
|
|
3823
|
-
const customThemesDir =
|
|
3824
|
-
if (!
|
|
4291
|
+
const customThemesDir = join26(projectRoot, ".decantr", "custom", "themes");
|
|
4292
|
+
if (!existsSync25(customThemesDir)) {
|
|
3825
4293
|
return [];
|
|
3826
4294
|
}
|
|
3827
4295
|
const themes = [];
|
|
3828
4296
|
try {
|
|
3829
4297
|
const files = readdirSync5(customThemesDir).filter((f) => f.endsWith(".json"));
|
|
3830
4298
|
for (const file of files) {
|
|
3831
|
-
const filePath =
|
|
4299
|
+
const filePath = join26(customThemesDir, file);
|
|
3832
4300
|
try {
|
|
3833
|
-
const data = JSON.parse(
|
|
4301
|
+
const data = JSON.parse(readFileSync18(filePath, "utf-8"));
|
|
3834
4302
|
themes.push({
|
|
3835
4303
|
id: data.id || file.replace(".json", ""),
|
|
3836
4304
|
name: data.name || data.id,
|
|
@@ -3845,8 +4313,8 @@ function listCustomThemes(projectRoot) {
|
|
|
3845
4313
|
return themes;
|
|
3846
4314
|
}
|
|
3847
4315
|
function deleteTheme(projectRoot, id) {
|
|
3848
|
-
const themePath =
|
|
3849
|
-
if (!
|
|
4316
|
+
const themePath = join26(projectRoot, ".decantr", "custom", "themes", `${id}.json`);
|
|
4317
|
+
if (!existsSync25(themePath)) {
|
|
3850
4318
|
return {
|
|
3851
4319
|
success: false,
|
|
3852
4320
|
error: `Theme "${id}" not found at ${themePath}`
|
|
@@ -3863,7 +4331,7 @@ function deleteTheme(projectRoot, id) {
|
|
|
3863
4331
|
}
|
|
3864
4332
|
}
|
|
3865
4333
|
function importTheme(projectRoot, sourcePath) {
|
|
3866
|
-
if (!
|
|
4334
|
+
if (!existsSync25(sourcePath)) {
|
|
3867
4335
|
return {
|
|
3868
4336
|
success: false,
|
|
3869
4337
|
errors: [`Source file not found: ${sourcePath}`]
|
|
@@ -3871,7 +4339,7 @@ function importTheme(projectRoot, sourcePath) {
|
|
|
3871
4339
|
}
|
|
3872
4340
|
let theme;
|
|
3873
4341
|
try {
|
|
3874
|
-
theme = JSON.parse(
|
|
4342
|
+
theme = JSON.parse(readFileSync18(sourcePath, "utf-8"));
|
|
3875
4343
|
} catch (e) {
|
|
3876
4344
|
return {
|
|
3877
4345
|
success: false,
|
|
@@ -3887,20 +4355,90 @@ function importTheme(projectRoot, sourcePath) {
|
|
|
3887
4355
|
}
|
|
3888
4356
|
theme.source = "custom";
|
|
3889
4357
|
const id = theme.id;
|
|
3890
|
-
const customThemesDir =
|
|
3891
|
-
const destPath =
|
|
3892
|
-
|
|
3893
|
-
const howToPath =
|
|
3894
|
-
if (!
|
|
3895
|
-
|
|
3896
|
-
}
|
|
3897
|
-
|
|
4358
|
+
const customThemesDir = join26(projectRoot, ".decantr", "custom", "themes");
|
|
4359
|
+
const destPath = join26(customThemesDir, `${id}.json`);
|
|
4360
|
+
mkdirSync10(customThemesDir, { recursive: true });
|
|
4361
|
+
const howToPath = join26(customThemesDir, "how-to-theme.md");
|
|
4362
|
+
if (!existsSync25(howToPath)) {
|
|
4363
|
+
writeFileSync13(howToPath, getHowToThemeDoc());
|
|
4364
|
+
}
|
|
4365
|
+
writeFileSync13(destPath, JSON.stringify(theme, null, 2));
|
|
3898
4366
|
return {
|
|
3899
4367
|
success: true,
|
|
3900
4368
|
path: destPath
|
|
3901
4369
|
};
|
|
3902
4370
|
}
|
|
3903
4371
|
|
|
4372
|
+
// src/workspace.ts
|
|
4373
|
+
import { existsSync as existsSync26, readdirSync as readdirSync6, readFileSync as readFileSync19 } from "fs";
|
|
4374
|
+
import { dirname as dirname3, join as join27, resolve as resolve3 } from "path";
|
|
4375
|
+
function readPackageJson(dir) {
|
|
4376
|
+
const path = join27(dir, "package.json");
|
|
4377
|
+
if (!existsSync26(path)) return null;
|
|
4378
|
+
try {
|
|
4379
|
+
return JSON.parse(readFileSync19(path, "utf-8"));
|
|
4380
|
+
} catch {
|
|
4381
|
+
return null;
|
|
4382
|
+
}
|
|
4383
|
+
}
|
|
4384
|
+
function hasWorkspaceMarker(dir) {
|
|
4385
|
+
if (existsSync26(join27(dir, "pnpm-workspace.yaml")) || existsSync26(join27(dir, "turbo.json")) || existsSync26(join27(dir, "nx.json"))) {
|
|
4386
|
+
return true;
|
|
4387
|
+
}
|
|
4388
|
+
const pkg = readPackageJson(dir);
|
|
4389
|
+
return Boolean(pkg?.workspaces);
|
|
4390
|
+
}
|
|
4391
|
+
function findWorkspaceRoot(startDir) {
|
|
4392
|
+
let current = resolve3(startDir);
|
|
4393
|
+
while (true) {
|
|
4394
|
+
if (hasWorkspaceMarker(current)) return current;
|
|
4395
|
+
const parent = dirname3(current);
|
|
4396
|
+
if (parent === current) return null;
|
|
4397
|
+
current = parent;
|
|
4398
|
+
}
|
|
4399
|
+
}
|
|
4400
|
+
function looksLikeApp(dir) {
|
|
4401
|
+
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"))) {
|
|
4402
|
+
return true;
|
|
4403
|
+
}
|
|
4404
|
+
const pkg = readPackageJson(dir);
|
|
4405
|
+
const deps = { ...pkg?.dependencies, ...pkg?.devDependencies };
|
|
4406
|
+
return Boolean(
|
|
4407
|
+
deps.react || deps.next || deps.vue || deps.svelte || deps["@angular/core"] || deps.astro || deps.nuxt
|
|
4408
|
+
);
|
|
4409
|
+
}
|
|
4410
|
+
function listWorkspaceApps(workspaceRoot) {
|
|
4411
|
+
const candidates = [];
|
|
4412
|
+
for (const base of ["apps", "packages"]) {
|
|
4413
|
+
const baseDir = join27(workspaceRoot, base);
|
|
4414
|
+
if (!existsSync26(baseDir)) continue;
|
|
4415
|
+
for (const entry of readdirSync6(baseDir, { withFileTypes: true })) {
|
|
4416
|
+
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
|
|
4417
|
+
const candidate = join27(baseDir, entry.name);
|
|
4418
|
+
if (looksLikeApp(candidate)) {
|
|
4419
|
+
candidates.push(`${base}/${entry.name}`);
|
|
4420
|
+
}
|
|
4421
|
+
}
|
|
4422
|
+
}
|
|
4423
|
+
return candidates.sort();
|
|
4424
|
+
}
|
|
4425
|
+
function resolveWorkspaceInfo(cwd, projectArg) {
|
|
4426
|
+
const absoluteCwd = resolve3(cwd);
|
|
4427
|
+
const workspaceRoot = findWorkspaceRoot(absoluteCwd) ?? absoluteCwd;
|
|
4428
|
+
const appRoot = projectArg ? resolve3(absoluteCwd, projectArg) : absoluteCwd;
|
|
4429
|
+
const appCandidates = listWorkspaceApps(workspaceRoot);
|
|
4430
|
+
const projectScope = workspaceRoot !== appRoot || appCandidates.length > 0 ? "workspace-app" : "single-app";
|
|
4431
|
+
const requiresProjectSelection = !projectArg && workspaceRoot === absoluteCwd && appCandidates.length > 1;
|
|
4432
|
+
return {
|
|
4433
|
+
cwd: absoluteCwd,
|
|
4434
|
+
workspaceRoot,
|
|
4435
|
+
appRoot,
|
|
4436
|
+
projectScope,
|
|
4437
|
+
appCandidates,
|
|
4438
|
+
requiresProjectSelection
|
|
4439
|
+
};
|
|
4440
|
+
}
|
|
4441
|
+
|
|
3904
4442
|
// src/index.ts
|
|
3905
4443
|
var BOLD6 = "\x1B[1m";
|
|
3906
4444
|
var DIM13 = "\x1B[2m";
|
|
@@ -3997,8 +4535,19 @@ function extractPatternName(item) {
|
|
|
3997
4535
|
}
|
|
3998
4536
|
function generateGreenfieldPrompt(ctx) {
|
|
3999
4537
|
const lines = [];
|
|
4538
|
+
const usesDecantrCss = ctx.adoptionMode === "decantr-css" || !ctx.adoptionMode;
|
|
4000
4539
|
lines.push("Build this greenfield application using the Decantr design system.");
|
|
4001
4540
|
lines.push("");
|
|
4541
|
+
if (ctx.blueprint) lines.push(`Blueprint: ${ctx.blueprint}`);
|
|
4542
|
+
if (ctx.archetype) lines.push(`Primary archetype: ${ctx.archetype}`);
|
|
4543
|
+
if (ctx.theme) lines.push(`Theme: ${ctx.theme}${ctx.mode ? ` (${ctx.mode})` : ""}`);
|
|
4544
|
+
if (ctx.target) lines.push(`Target: ${ctx.target}`);
|
|
4545
|
+
if (ctx.pages.length > 0) {
|
|
4546
|
+
lines.push(
|
|
4547
|
+
`Routes/pages: ${ctx.pages.map((page) => `${page.sectionId ? `${page.sectionId}/` : ""}${page.id}:${page.shell}`).join(", ")}`
|
|
4548
|
+
);
|
|
4549
|
+
}
|
|
4550
|
+
lines.push("");
|
|
4002
4551
|
lines.push(
|
|
4003
4552
|
"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
4553
|
);
|
|
@@ -4013,210 +4562,201 @@ function generateGreenfieldPrompt(ctx) {
|
|
|
4013
4562
|
lines.push("");
|
|
4014
4563
|
lines.push("Read in this order:");
|
|
4015
4564
|
lines.push(
|
|
4016
|
-
|
|
4565
|
+
"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
4566
|
);
|
|
4018
4567
|
lines.push(
|
|
4019
|
-
|
|
4568
|
+
"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
4569
|
);
|
|
4021
4570
|
lines.push(
|
|
4022
|
-
"3. .decantr/context/
|
|
4571
|
+
"3. Before route work, read the matching .decantr/context/page-*-pack.md file. Its pattern layout and interaction checklists are contract."
|
|
4023
4572
|
);
|
|
4024
4573
|
lines.push(
|
|
4025
|
-
|
|
4574
|
+
"4. .decantr/context/scaffold.md for broader topology, route map, and voice guidance after the compact packs are understood."
|
|
4026
4575
|
);
|
|
4027
4576
|
lines.push(
|
|
4028
|
-
|
|
4577
|
+
"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
4578
|
);
|
|
4030
4579
|
lines.push("");
|
|
4031
4580
|
lines.push("\u2550\u2550\u2550 INTERACTIONS ARE CONTRACT, NOT GUIDANCE \u2550\u2550\u2550");
|
|
4032
4581
|
lines.push("");
|
|
4033
4582
|
lines.push(
|
|
4034
|
-
'Each page pack lists
|
|
4035
|
-
);
|
|
4036
|
-
lines.push(
|
|
4037
|
-
"- drag-nodes \u2192 onPointerDown/Move handlers with 4px threshold + cursor: grab/grabbing"
|
|
4583
|
+
'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.'
|
|
4038
4584
|
);
|
|
4039
|
-
lines.push("- status-pulse \u2192 d-pulse class on the indicator element");
|
|
4040
4585
|
lines.push(
|
|
4041
|
-
"
|
|
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"
|
|
4586
|
+
"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."
|
|
4060
4587
|
);
|
|
4061
4588
|
lines.push("");
|
|
4062
4589
|
lines.push(
|
|
4063
|
-
"`decantr check --strict`
|
|
4590
|
+
"`decantr check --strict` fails when a declared interaction has no matching implementation."
|
|
4064
4591
|
);
|
|
4065
4592
|
lines.push("");
|
|
4066
|
-
lines.push("\u2550\u2550\u2550
|
|
4593
|
+
lines.push("\u2550\u2550\u2550 STYLING ADOPTION \u2550\u2550\u2550");
|
|
4067
4594
|
lines.push("");
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
)
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
|
|
4080
|
-
|
|
4081
|
-
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
)
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
|
|
4094
|
-
|
|
4095
|
-
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
|
|
4099
|
-
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
|
|
4105
|
-
|
|
4106
|
-
|
|
4107
|
-
|
|
4595
|
+
if (ctx.adoptionMode === "contract-only") {
|
|
4596
|
+
lines.push(
|
|
4597
|
+
"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."
|
|
4598
|
+
);
|
|
4599
|
+
lines.push(
|
|
4600
|
+
"Do not install @decantr/css or add Decantr style files unless the adoption mode changes."
|
|
4601
|
+
);
|
|
4602
|
+
} else if (ctx.adoptionMode === "style-bridge") {
|
|
4603
|
+
lines.push(
|
|
4604
|
+
"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."
|
|
4605
|
+
);
|
|
4606
|
+
} else {
|
|
4607
|
+
lines.push(
|
|
4608
|
+
"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."
|
|
4609
|
+
);
|
|
4610
|
+
}
|
|
4611
|
+
if (usesDecantrCss) {
|
|
4612
|
+
lines.push("");
|
|
4613
|
+
lines.push("Use these canonical compact atom shapes:");
|
|
4614
|
+
lines.push(
|
|
4615
|
+
"- Layout: _flex, _col, _aic, _jcc, _jcsb, _grid, _gc3, _gc[2fr_1fr], _gap4, _wrap"
|
|
4616
|
+
);
|
|
4617
|
+
lines.push(
|
|
4618
|
+
"- Spacing/sizing: _p4, _py4, _px6, _wfull, _maxw[40rem], _mxauto, _h[20rem]"
|
|
4619
|
+
);
|
|
4620
|
+
lines.push(
|
|
4621
|
+
"- Position/type: _rel, _abs, _sticky, _top0, _text2xl, _textlg, _fgmuted"
|
|
4622
|
+
);
|
|
4623
|
+
lines.push("- Responsive: _sm:gc2, _lg:gc3, _mdmax:p4, _lg:gc[1.05fr_1fr]");
|
|
4624
|
+
lines.push("");
|
|
4625
|
+
lines.push(
|
|
4626
|
+
"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]`."
|
|
4627
|
+
);
|
|
4628
|
+
lines.push("");
|
|
4629
|
+
lines.push(
|
|
4630
|
+
'Combine atoms with treatment / decorator strings: `className={css("_flex _col _gap4") + " d-card clean-card"}`.'
|
|
4631
|
+
);
|
|
4632
|
+
} else {
|
|
4633
|
+
lines.push("");
|
|
4634
|
+
lines.push(
|
|
4635
|
+
"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."
|
|
4636
|
+
);
|
|
4637
|
+
}
|
|
4108
4638
|
lines.push("");
|
|
4109
4639
|
lines.push("Inline `style={{...}}` is ONLY acceptable for:");
|
|
4110
4640
|
lines.push(
|
|
4111
|
-
|
|
4641
|
+
" 1. CSS custom-property writes the contract requires (`--d-stagger-index`, theme color vars, etc.)"
|
|
4112
4642
|
);
|
|
4113
4643
|
lines.push(
|
|
4114
|
-
" 2. Truly dynamic geometry no atom can express (computed
|
|
4644
|
+
" 2. Truly dynamic geometry no atom can express (computed transforms, drag positions, live chart geometry)."
|
|
4115
4645
|
);
|
|
4116
4646
|
lines.push("");
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
|
|
4647
|
+
if (usesDecantrCss) {
|
|
4648
|
+
lines.push(
|
|
4649
|
+
"If a component accumulates static inline visual styles, migrate them to atoms, treatments, decorators, or CSS vars. `decantr check` flags inline-style drift."
|
|
4650
|
+
);
|
|
4651
|
+
} else {
|
|
4652
|
+
lines.push(
|
|
4653
|
+
"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."
|
|
4654
|
+
);
|
|
4655
|
+
}
|
|
4120
4656
|
lines.push("");
|
|
4121
4657
|
lines.push("\u2550\u2550\u2550 TREATMENT SURFACE \u2014 USE WHAT EXISTS \u2550\u2550\u2550");
|
|
4122
4658
|
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
|
-
);
|
|
4659
|
+
if (usesDecantrCss) {
|
|
4660
|
+
lines.push(
|
|
4661
|
+
"60+ treatment classes ship in src/styles/treatments.css. Reach for these before inventing CSS:"
|
|
4662
|
+
);
|
|
4663
|
+
lines.push(
|
|
4664
|
+
"- Shells: d-shell + data-layout, d-shell-sidebar/main/aside/header/body/footer, d-shell-mobile-trigger/backdrop"
|
|
4665
|
+
);
|
|
4666
|
+
lines.push(
|
|
4667
|
+
"- Core UI: d-interactive, d-icon-btn, d-nav-link, d-step-chip, d-control, d-card, d-data, d-label, d-annotation"
|
|
4668
|
+
);
|
|
4669
|
+
lines.push(
|
|
4670
|
+
"- Overlays: d-modal, d-modal-backdrop, d-modal-panel, d-palette, d-tooltip, d-popover"
|
|
4671
|
+
);
|
|
4672
|
+
lines.push(
|
|
4673
|
+
"- 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"
|
|
4674
|
+
);
|
|
4675
|
+
} else {
|
|
4676
|
+
lines.push(
|
|
4677
|
+
"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."
|
|
4678
|
+
);
|
|
4679
|
+
}
|
|
4155
4680
|
lines.push("");
|
|
4156
4681
|
lines.push(
|
|
4157
|
-
"
|
|
4682
|
+
"Consult DECANTR.md only when you need the full table or exact data-* attributes."
|
|
4158
4683
|
);
|
|
4159
4684
|
lines.push("");
|
|
4160
4685
|
lines.push("\u2550\u2550\u2550 THEME DECORATOR CONTRACT \u2014 APPLY OR THE THEME DOES NOT LAND \u2550\u2550\u2550");
|
|
4161
4686
|
lines.push("");
|
|
4162
4687
|
lines.push(
|
|
4163
|
-
'Each theme ships
|
|
4688
|
+
'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
4689
|
);
|
|
4165
4690
|
lines.push(
|
|
4166
|
-
|
|
4691
|
+
"Section packs may point back to the scaffold-pack table; scaffold-pack.md is authoritative."
|
|
4167
4692
|
);
|
|
4168
4693
|
lines.push("");
|
|
4169
4694
|
lines.push("\u2550\u2550\u2550 HARD RULES (NON-NEGOTIABLE) \u2550\u2550\u2550");
|
|
4170
4695
|
lines.push("");
|
|
4696
|
+
if (usesDecantrCss) {
|
|
4697
|
+
lines.push(
|
|
4698
|
+
'- Auth pages use `d-shell[data-layout="centered"]` with `d-shell-centered-card` around the form.'
|
|
4699
|
+
);
|
|
4700
|
+
lines.push(
|
|
4701
|
+
'- Command palette uses `d-modal[data-align="top"]` + `d-modal-backdrop` + `d-palette`; rows include Lucide icon, label, and d-kbd hotkey.'
|
|
4702
|
+
);
|
|
4703
|
+
} else {
|
|
4704
|
+
lines.push("- Auth pages use a centered shell with a focused centered-card form surface.");
|
|
4705
|
+
lines.push(
|
|
4706
|
+
"- Command palette uses an accessible modal/palette structure; rows include Lucide icon, label, and keyboard hint where the product contract calls for it."
|
|
4707
|
+
);
|
|
4708
|
+
}
|
|
4171
4709
|
lines.push(
|
|
4172
|
-
|
|
4710
|
+
"- 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
4711
|
);
|
|
4174
4712
|
lines.push(
|
|
4175
|
-
|
|
4713
|
+
"- Section Directives in section packs are execution rules for layout proportions, treatment stacks, copy conventions, and pattern fitness."
|
|
4176
4714
|
);
|
|
4177
4715
|
lines.push(
|
|
4178
|
-
"-
|
|
4716
|
+
"- Filter chip rows / tab strips use `d-step-chip[data-step-state]`, not bare `d-interactive` buttons."
|
|
4179
4717
|
);
|
|
4180
4718
|
lines.push(
|
|
4181
|
-
"-
|
|
4719
|
+
"- 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
4720
|
);
|
|
4183
4721
|
lines.push(
|
|
4184
|
-
"-
|
|
4722
|
+
"- Prevent layout collisions: hero content, CTA banners, cards, footers, and sticky chrome must not overlap or clip at desktop or mobile widths."
|
|
4185
4723
|
);
|
|
4186
4724
|
lines.push("");
|
|
4187
4725
|
lines.push("\u2550\u2550\u2550 IMPLEMENTATION RULES \u2550\u2550\u2550");
|
|
4188
4726
|
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."
|
|
4727
|
+
"- Do not invent routes, sections, shells, themes, or features beyond the compiled packs."
|
|
4193
4728
|
);
|
|
4729
|
+
lines.push("- Prefer scaffold-pack, section-pack, and page-pack guidance over narrative docs.");
|
|
4194
4730
|
lines.push(
|
|
4195
4731
|
"- Start with the shell layouts and route structure first, then build section pages route by route."
|
|
4196
4732
|
);
|
|
4733
|
+
if (ctx.adoptionMode === "decantr-css") {
|
|
4734
|
+
lines.push("- Import src/styles/global.css, src/styles/tokens.css, and src/styles/treatments.css.");
|
|
4735
|
+
} else if (ctx.adoptionMode === "style-bridge") {
|
|
4736
|
+
lines.push("- Import src/styles/tokens.css and src/styles/decantr-bridge.css where appropriate.");
|
|
4737
|
+
} else {
|
|
4738
|
+
lines.push("- Keep styling imports aligned with the selected runtime; Decantr does not own CSS here.");
|
|
4739
|
+
}
|
|
4197
4740
|
lines.push(
|
|
4198
|
-
"-
|
|
4199
|
-
);
|
|
4200
|
-
lines.push(
|
|
4201
|
-
"- Use the existing Decantr tokens, treatments, and decorators instead of inventing a new visual system."
|
|
4741
|
+
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
4742
|
);
|
|
4203
4743
|
lines.push(
|
|
4204
|
-
"- If package.json, app entry files, or router/runtime files are absent, create them
|
|
4744
|
+
"- If package.json, app entry files, or router/runtime files are absent, create them for the declared target."
|
|
4205
4745
|
);
|
|
4206
4746
|
lines.push(
|
|
4207
|
-
|
|
4747
|
+
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
4748
|
);
|
|
4209
4749
|
lines.push(
|
|
4210
|
-
"- Let shells own spacing, centering, and scroll containers
|
|
4750
|
+
"- Let shells own spacing, centering, and scroll containers unless the route contract says otherwise."
|
|
4211
4751
|
);
|
|
4212
4752
|
lines.push(
|
|
4213
|
-
"- If command_palette or hotkeys are declared
|
|
4753
|
+
"- If command_palette or hotkeys are declared, implement them as real features rather than visible copy."
|
|
4214
4754
|
);
|
|
4215
4755
|
lines.push(
|
|
4216
4756
|
"- 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
4757
|
);
|
|
4218
4758
|
lines.push(
|
|
4219
|
-
"- If a required decorator class is
|
|
4759
|
+
"- If a required decorator class is missing from generated CSS, report the contract gap instead of inventing a parallel system."
|
|
4220
4760
|
);
|
|
4221
4761
|
lines.push(
|
|
4222
4762
|
"- Do not modify generated context files unless the task is explicitly to regenerate or refresh Decantr context."
|
|
@@ -4226,7 +4766,7 @@ function generateGreenfieldPrompt(ctx) {
|
|
|
4226
4766
|
lines.push("- Build the shell and shared layout first.");
|
|
4227
4767
|
lines.push("- Then implement each section's pages using the matching section and page packs.");
|
|
4228
4768
|
lines.push(
|
|
4229
|
-
"- After implementation, run `decantr check` (primary gate
|
|
4769
|
+
"- After implementation, run `decantr check` (primary gate) and `decantr audit` (supplementary diagnostics)."
|
|
4230
4770
|
);
|
|
4231
4771
|
lines.push("- Fix all violations until `decantr check` exits 0.");
|
|
4232
4772
|
lines.push(
|
|
@@ -4236,14 +4776,32 @@ function generateGreenfieldPrompt(ctx) {
|
|
|
4236
4776
|
}
|
|
4237
4777
|
function generateBrownfieldPrompt(ctx) {
|
|
4238
4778
|
const lines = [];
|
|
4239
|
-
lines.push(
|
|
4779
|
+
lines.push(
|
|
4780
|
+
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."
|
|
4781
|
+
);
|
|
4782
|
+
lines.push("");
|
|
4783
|
+
if (ctx.blueprint) lines.push(`Blueprint: ${ctx.blueprint}`);
|
|
4784
|
+
if (ctx.archetype) lines.push(`Primary archetype: ${ctx.archetype}`);
|
|
4785
|
+
if (ctx.theme) lines.push(`Theme: ${ctx.theme}${ctx.mode ? ` (${ctx.mode})` : ""}`);
|
|
4786
|
+
if (ctx.target) lines.push(`Target: ${ctx.target}`);
|
|
4787
|
+
if (ctx.pages.length > 0) {
|
|
4788
|
+
lines.push(
|
|
4789
|
+
`Routes/pages: ${ctx.pages.map((page) => `${page.sectionId ? `${page.sectionId}/` : ""}${page.id}:${page.shell}`).join(", ")}`
|
|
4790
|
+
);
|
|
4791
|
+
}
|
|
4240
4792
|
lines.push("");
|
|
4241
4793
|
lines.push(
|
|
4242
4794
|
"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
4795
|
);
|
|
4244
4796
|
lines.push("");
|
|
4245
|
-
|
|
4246
|
-
|
|
4797
|
+
if (ctx.analysisArtifacts) {
|
|
4798
|
+
lines.push("Treat .decantr/analysis.json as the factual inventory of the current app.");
|
|
4799
|
+
lines.push("Treat .decantr/init-seed.json as the recommended Decantr attach defaults.");
|
|
4800
|
+
} else {
|
|
4801
|
+
lines.push(
|
|
4802
|
+
"No Decantr analysis seed is present. Start by inventorying the app before changing runtime files."
|
|
4803
|
+
);
|
|
4804
|
+
}
|
|
4247
4805
|
lines.push(
|
|
4248
4806
|
"Treat the compiled execution-pack files as the Decantr contract you are layering onto the app."
|
|
4249
4807
|
);
|
|
@@ -4252,20 +4810,34 @@ function generateBrownfieldPrompt(ctx) {
|
|
|
4252
4810
|
);
|
|
4253
4811
|
lines.push("");
|
|
4254
4812
|
lines.push("Read in this order:");
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
|
|
4813
|
+
if (ctx.analysisArtifacts) {
|
|
4814
|
+
lines.push(
|
|
4815
|
+
"1. .decantr/analysis.json for the detected framework, routes, styling, layout, and dependencies."
|
|
4816
|
+
);
|
|
4817
|
+
lines.push("2. .decantr/init-seed.json for the intended attach defaults and workflow lane.");
|
|
4818
|
+
lines.push("3. DECANTR.md for guard rules, CSS expectations, and Decantr operating rules.");
|
|
4819
|
+
lines.push(
|
|
4820
|
+
"4. .decantr/context/scaffold-pack.md for the compact compiled shell, theme, feature, and route contract."
|
|
4821
|
+
);
|
|
4822
|
+
lines.push(
|
|
4823
|
+
"5. .decantr/context/scaffold.md for broader topology, route map, and voice guidance."
|
|
4824
|
+
);
|
|
4825
|
+
lines.push(
|
|
4826
|
+
"6. The matching section and page pack files only when you are working on those specific surfaces."
|
|
4827
|
+
);
|
|
4828
|
+
} else {
|
|
4829
|
+
lines.push("1. Inventory existing framework, routes, styling, layout, rule files, and dependencies.");
|
|
4830
|
+
lines.push("2. DECANTR.md for guard rules, adoption mode, and Decantr operating rules.");
|
|
4831
|
+
lines.push(
|
|
4832
|
+
"3. .decantr/context/scaffold-pack.md for the compact compiled shell, theme, feature, and route contract."
|
|
4833
|
+
);
|
|
4834
|
+
lines.push(
|
|
4835
|
+
"4. .decantr/context/scaffold.md for broader topology, route map, and voice guidance."
|
|
4836
|
+
);
|
|
4837
|
+
lines.push(
|
|
4838
|
+
"5. The matching section and page pack files only when you are working on those specific surfaces."
|
|
4839
|
+
);
|
|
4840
|
+
}
|
|
4269
4841
|
lines.push("");
|
|
4270
4842
|
lines.push("Implementation rules:");
|
|
4271
4843
|
lines.push(
|
|
@@ -4277,21 +4849,33 @@ function generateBrownfieldPrompt(ctx) {
|
|
|
4277
4849
|
lines.push(
|
|
4278
4850
|
"- If package.json, router files, or style files already exist, extend them deliberately instead of replacing them with a different starter shape."
|
|
4279
4851
|
);
|
|
4852
|
+
if (ctx.adoptionMode === "decantr-css") {
|
|
4853
|
+
lines.push(
|
|
4854
|
+
"- 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."
|
|
4855
|
+
);
|
|
4856
|
+
lines.push(
|
|
4857
|
+
"- Use the existing Decantr tokens, treatments, and decorators instead of inventing a parallel visual system."
|
|
4858
|
+
);
|
|
4859
|
+
} else if (ctx.adoptionMode === "style-bridge") {
|
|
4860
|
+
lines.push(
|
|
4861
|
+
"- Use Decantr bridge files as a mapping layer onto the existing styling system; do not install @decantr/css unless explicitly requested."
|
|
4862
|
+
);
|
|
4863
|
+
} else {
|
|
4864
|
+
lines.push(
|
|
4865
|
+
"- Keep the existing styling system. Do not add Decantr CSS files or @decantr/css unless the adoption mode changes."
|
|
4866
|
+
);
|
|
4867
|
+
}
|
|
4280
4868
|
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."
|
|
4869
|
+
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
4870
|
);
|
|
4289
4871
|
lines.push(
|
|
4290
4872
|
"- Do not invent routes, sections, shells, themes, or features that are not present in the compiled packs."
|
|
4291
4873
|
);
|
|
4292
|
-
|
|
4293
|
-
|
|
4294
|
-
|
|
4874
|
+
if (ctx.adoptionMode === "decantr-css") {
|
|
4875
|
+
lines.push(
|
|
4876
|
+
"- 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."
|
|
4877
|
+
);
|
|
4878
|
+
}
|
|
4295
4879
|
lines.push(
|
|
4296
4880
|
"- Let shells own spacing, centering, and scroll containers. Preserve app structure, but remove duplicated shell responsibilities when the contract makes them explicit."
|
|
4297
4881
|
);
|
|
@@ -4322,7 +4906,7 @@ function generateBrownfieldPrompt(ctx) {
|
|
|
4322
4906
|
return lines.join("\n");
|
|
4323
4907
|
}
|
|
4324
4908
|
function generateCuratedPrompt(ctx) {
|
|
4325
|
-
return ctx.workflow === "brownfield-attach" ? generateBrownfieldPrompt(ctx) : generateGreenfieldPrompt(ctx);
|
|
4909
|
+
return ctx.workflow === "brownfield-attach" || ctx.workflow === "hybrid-compose" ? generateBrownfieldPrompt(ctx) : generateGreenfieldPrompt(ctx);
|
|
4326
4910
|
}
|
|
4327
4911
|
function getAPIClient() {
|
|
4328
4912
|
return new RegistryAPIClient3({
|
|
@@ -4336,7 +4920,7 @@ function getPublicAPIClient() {
|
|
|
4336
4920
|
});
|
|
4337
4921
|
}
|
|
4338
4922
|
function resolveUserPath(inputPath, cwd = process.cwd()) {
|
|
4339
|
-
return isAbsolute(inputPath) ? inputPath :
|
|
4923
|
+
return isAbsolute(inputPath) ? inputPath : resolve4(cwd, inputPath);
|
|
4340
4924
|
}
|
|
4341
4925
|
function extractHostedAssetPaths(indexHtml) {
|
|
4342
4926
|
const assetPaths = /* @__PURE__ */ new Set();
|
|
@@ -4349,18 +4933,18 @@ function extractHostedAssetPaths(indexHtml) {
|
|
|
4349
4933
|
return [...assetPaths];
|
|
4350
4934
|
}
|
|
4351
4935
|
function readHostedDistSnapshot(distPath) {
|
|
4352
|
-
const resolvedDistPath = distPath ? resolveUserPath(distPath) :
|
|
4353
|
-
const indexPath =
|
|
4354
|
-
if (!
|
|
4936
|
+
const resolvedDistPath = distPath ? resolveUserPath(distPath) : join28(process.cwd(), "dist");
|
|
4937
|
+
const indexPath = join28(resolvedDistPath, "index.html");
|
|
4938
|
+
if (!existsSync27(indexPath)) {
|
|
4355
4939
|
return void 0;
|
|
4356
4940
|
}
|
|
4357
|
-
const indexHtml =
|
|
4941
|
+
const indexHtml = readFileSync20(indexPath, "utf-8");
|
|
4358
4942
|
const assetPaths = extractHostedAssetPaths(indexHtml);
|
|
4359
4943
|
const assets = {};
|
|
4360
4944
|
for (const assetPath of assetPaths) {
|
|
4361
|
-
const assetFilePath =
|
|
4362
|
-
if (
|
|
4363
|
-
assets[assetPath] =
|
|
4945
|
+
const assetFilePath = join28(resolvedDistPath, assetPath.replace(/^[/\\]+/, ""));
|
|
4946
|
+
if (existsSync27(assetFilePath)) {
|
|
4947
|
+
assets[assetPath] = readFileSync20(assetFilePath, "utf-8");
|
|
4364
4948
|
}
|
|
4365
4949
|
}
|
|
4366
4950
|
return {
|
|
@@ -4375,7 +4959,7 @@ function isHostedSourceSnapshotFile(path) {
|
|
|
4375
4959
|
function readHostedSourceSnapshot(sourcePath) {
|
|
4376
4960
|
if (!sourcePath) return void 0;
|
|
4377
4961
|
const resolvedSourcePath = resolveUserPath(sourcePath);
|
|
4378
|
-
if (!
|
|
4962
|
+
if (!existsSync27(resolvedSourcePath)) {
|
|
4379
4963
|
return void 0;
|
|
4380
4964
|
}
|
|
4381
4965
|
const files = {};
|
|
@@ -4389,17 +4973,17 @@ function readHostedSourceSnapshot(sourcePath) {
|
|
|
4389
4973
|
]);
|
|
4390
4974
|
const rootPrefix = basename2(resolvedSourcePath);
|
|
4391
4975
|
const walk = (absoluteDir, relativeDir) => {
|
|
4392
|
-
for (const entry of
|
|
4976
|
+
for (const entry of readdirSync7(absoluteDir, { withFileTypes: true })) {
|
|
4393
4977
|
if (ignoredDirNames.has(entry.name)) continue;
|
|
4394
|
-
const absolutePath =
|
|
4395
|
-
const relativePath =
|
|
4978
|
+
const absolutePath = join28(absoluteDir, entry.name);
|
|
4979
|
+
const relativePath = join28(relativeDir, entry.name).replace(/\\/g, "/");
|
|
4396
4980
|
if (entry.isDirectory()) {
|
|
4397
4981
|
walk(absolutePath, relativePath);
|
|
4398
4982
|
continue;
|
|
4399
4983
|
}
|
|
4400
4984
|
if (!entry.isFile()) continue;
|
|
4401
4985
|
if (!isHostedSourceSnapshotFile(relativePath)) continue;
|
|
4402
|
-
files[relativePath] =
|
|
4986
|
+
files[relativePath] = readFileSync20(absolutePath, "utf-8");
|
|
4403
4987
|
}
|
|
4404
4988
|
};
|
|
4405
4989
|
walk(resolvedSourcePath, rootPrefix);
|
|
@@ -4550,16 +5134,16 @@ async function printRegistryIntelligenceSummary(namespace, jsonOutput = false) {
|
|
|
4550
5134
|
}
|
|
4551
5135
|
async function printHostedExecutionPackBundle(essencePath, namespace, jsonOutput = false, writeContext = false) {
|
|
4552
5136
|
const client = getPublicAPIClient();
|
|
4553
|
-
const resolvedPath = essencePath ? resolveUserPath(essencePath) :
|
|
4554
|
-
if (!
|
|
5137
|
+
const resolvedPath = essencePath ? resolveUserPath(essencePath) : join28(process.cwd(), "decantr.essence.json");
|
|
5138
|
+
if (!existsSync27(resolvedPath)) {
|
|
4555
5139
|
throw new Error(`Essence file not found at ${resolvedPath}`);
|
|
4556
5140
|
}
|
|
4557
|
-
const essence = JSON.parse(
|
|
5141
|
+
const essence = JSON.parse(readFileSync20(resolvedPath, "utf-8"));
|
|
4558
5142
|
const bundle = await client.compileExecutionPacks(essence, namespace ? { namespace } : void 0);
|
|
4559
5143
|
let writtenContextPaths = [];
|
|
4560
5144
|
if (writeContext) {
|
|
4561
|
-
const contextDir =
|
|
4562
|
-
|
|
5145
|
+
const contextDir = join28(process.cwd(), ".decantr", "context");
|
|
5146
|
+
mkdirSync11(contextDir, { recursive: true });
|
|
4563
5147
|
const written = writeExecutionPackBundleArtifacts(
|
|
4564
5148
|
contextDir,
|
|
4565
5149
|
bundle
|
|
@@ -4584,26 +5168,27 @@ async function printHostedExecutionPackBundle(essencePath, namespace, jsonOutput
|
|
|
4584
5168
|
console.log(` Sections: ${typedBundle.sections.length}`);
|
|
4585
5169
|
console.log(` Mutations: ${typedBundle.mutations.length}`);
|
|
4586
5170
|
if (writeContext) {
|
|
4587
|
-
console.log(` Context bundle: ${
|
|
5171
|
+
console.log(` Context bundle: ${join28(process.cwd(), ".decantr", "context")}`);
|
|
4588
5172
|
console.log(` Files written: ${writtenContextPaths.length}`);
|
|
4589
5173
|
}
|
|
4590
5174
|
console.log("");
|
|
4591
5175
|
console.log(`${BOLD6}Route Plan:${RESET13}`);
|
|
4592
5176
|
for (const route of typedBundle.scaffold.data.routes) {
|
|
4593
5177
|
const patterns = route.patternIds.length > 0 ? route.patternIds.join(", ") : "none";
|
|
4594
|
-
|
|
5178
|
+
const pageLabel = route.sectionId ? `${route.sectionId}/${route.pageId}` : route.pageId;
|
|
5179
|
+
console.log(` ${cyan3(route.path)} -> ${pageLabel} [${patterns}]`);
|
|
4595
5180
|
}
|
|
4596
5181
|
}
|
|
4597
5182
|
async function printHostedSelectedExecutionPack(packType, id, essencePath, namespace, jsonOutput = false, writeContext = false) {
|
|
4598
5183
|
const client = getPublicAPIClient();
|
|
4599
|
-
const resolvedPath = essencePath ? resolveUserPath(essencePath) :
|
|
4600
|
-
if (!
|
|
5184
|
+
const resolvedPath = essencePath ? resolveUserPath(essencePath) : join28(process.cwd(), "decantr.essence.json");
|
|
5185
|
+
if (!existsSync27(resolvedPath)) {
|
|
4601
5186
|
throw new Error(`Essence file not found at ${resolvedPath}`);
|
|
4602
5187
|
}
|
|
4603
5188
|
if ((packType === "section" || packType === "page" || packType === "mutation") && !id) {
|
|
4604
5189
|
throw new Error(`Pack type "${packType}" requires an id.`);
|
|
4605
5190
|
}
|
|
4606
|
-
const essence = JSON.parse(
|
|
5191
|
+
const essence = JSON.parse(readFileSync20(resolvedPath, "utf-8"));
|
|
4607
5192
|
const selected = await client.selectExecutionPack(
|
|
4608
5193
|
{
|
|
4609
5194
|
essence,
|
|
@@ -4614,17 +5199,17 @@ async function printHostedSelectedExecutionPack(packType, id, essencePath, names
|
|
|
4614
5199
|
);
|
|
4615
5200
|
let writtenContextDir = null;
|
|
4616
5201
|
if (writeContext) {
|
|
4617
|
-
const contextDir =
|
|
4618
|
-
|
|
4619
|
-
|
|
4620
|
-
|
|
5202
|
+
const contextDir = join28(process.cwd(), ".decantr", "context");
|
|
5203
|
+
mkdirSync11(contextDir, { recursive: true });
|
|
5204
|
+
writeFileSync14(
|
|
5205
|
+
join28(contextDir, "pack-manifest.json"),
|
|
4621
5206
|
JSON.stringify(selected.manifest, null, 2) + "\n"
|
|
4622
5207
|
);
|
|
4623
5208
|
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
5209
|
const markdownFile = manifestEntry?.markdown ?? `${selected.selector.packType}${selected.selector.id ? `-${selected.selector.id}` : ""}-pack.md`;
|
|
4625
5210
|
const jsonFile = manifestEntry?.json ?? `${selected.selector.packType}${selected.selector.id ? `-${selected.selector.id}` : ""}-pack.json`;
|
|
4626
|
-
|
|
4627
|
-
|
|
5211
|
+
writeFileSync14(join28(contextDir, markdownFile), selected.pack.renderedMarkdown);
|
|
5212
|
+
writeFileSync14(join28(contextDir, jsonFile), JSON.stringify(selected.pack, null, 2) + "\n");
|
|
4628
5213
|
writtenContextDir = contextDir;
|
|
4629
5214
|
}
|
|
4630
5215
|
if (jsonOutput) {
|
|
@@ -4649,20 +5234,20 @@ async function printHostedSelectedExecutionPack(packType, id, essencePath, names
|
|
|
4649
5234
|
}
|
|
4650
5235
|
async function printHostedExecutionPackManifest(essencePath, namespace, jsonOutput = false, writeContext = false) {
|
|
4651
5236
|
const client = getPublicAPIClient();
|
|
4652
|
-
const resolvedPath = essencePath ? resolveUserPath(essencePath) :
|
|
4653
|
-
if (!
|
|
5237
|
+
const resolvedPath = essencePath ? resolveUserPath(essencePath) : join28(process.cwd(), "decantr.essence.json");
|
|
5238
|
+
if (!existsSync27(resolvedPath)) {
|
|
4654
5239
|
throw new Error(`Essence file not found at ${resolvedPath}`);
|
|
4655
5240
|
}
|
|
4656
|
-
const essence = JSON.parse(
|
|
5241
|
+
const essence = JSON.parse(readFileSync20(resolvedPath, "utf-8"));
|
|
4657
5242
|
const manifest = await client.getExecutionPackManifest(
|
|
4658
5243
|
essence,
|
|
4659
5244
|
namespace ? { namespace } : void 0
|
|
4660
5245
|
);
|
|
4661
5246
|
let writtenContextDir = null;
|
|
4662
5247
|
if (writeContext) {
|
|
4663
|
-
const contextDir =
|
|
4664
|
-
|
|
4665
|
-
|
|
5248
|
+
const contextDir = join28(process.cwd(), ".decantr", "context");
|
|
5249
|
+
mkdirSync11(contextDir, { recursive: true });
|
|
5250
|
+
writeFileSync14(join28(contextDir, "pack-manifest.json"), JSON.stringify(manifest, null, 2) + "\n");
|
|
4666
5251
|
writtenContextDir = contextDir;
|
|
4667
5252
|
}
|
|
4668
5253
|
if (jsonOutput) {
|
|
@@ -4683,14 +5268,14 @@ async function printHostedExecutionPackManifest(essencePath, namespace, jsonOutp
|
|
|
4683
5268
|
}
|
|
4684
5269
|
}
|
|
4685
5270
|
async function hydrateHostedExecutionPacksIfMissing(projectRoot, namespace = "@official") {
|
|
4686
|
-
const contextDir =
|
|
4687
|
-
const reviewPackPath =
|
|
4688
|
-
const manifestPath =
|
|
4689
|
-
if (
|
|
5271
|
+
const contextDir = join28(projectRoot, ".decantr", "context");
|
|
5272
|
+
const reviewPackPath = join28(contextDir, "review-pack.json");
|
|
5273
|
+
const manifestPath = join28(contextDir, "pack-manifest.json");
|
|
5274
|
+
if (existsSync27(reviewPackPath) && existsSync27(manifestPath)) {
|
|
4690
5275
|
return { attempted: false, hydrated: false };
|
|
4691
5276
|
}
|
|
4692
|
-
const essencePath =
|
|
4693
|
-
if (!
|
|
5277
|
+
const essencePath = join28(projectRoot, "decantr.essence.json");
|
|
5278
|
+
if (!existsSync27(essencePath)) {
|
|
4694
5279
|
return { attempted: false, hydrated: false };
|
|
4695
5280
|
}
|
|
4696
5281
|
const reviewHydration = await hydrateHostedReviewPackIfMissing(projectRoot, namespace);
|
|
@@ -4699,9 +5284,9 @@ async function hydrateHostedExecutionPacksIfMissing(projectRoot, namespace = "@o
|
|
|
4699
5284
|
}
|
|
4700
5285
|
try {
|
|
4701
5286
|
const client = getPublicAPIClient();
|
|
4702
|
-
const essence = JSON.parse(
|
|
5287
|
+
const essence = JSON.parse(readFileSync20(essencePath, "utf-8"));
|
|
4703
5288
|
const bundle = await client.compileExecutionPacks(essence, { namespace });
|
|
4704
|
-
|
|
5289
|
+
mkdirSync11(contextDir, { recursive: true });
|
|
4705
5290
|
writeExecutionPackBundleArtifacts(contextDir, bundle);
|
|
4706
5291
|
return { attempted: true, hydrated: true, scope: "bundle" };
|
|
4707
5292
|
} catch {
|
|
@@ -4709,19 +5294,19 @@ async function hydrateHostedExecutionPacksIfMissing(projectRoot, namespace = "@o
|
|
|
4709
5294
|
}
|
|
4710
5295
|
}
|
|
4711
5296
|
async function hydrateHostedReviewPackIfMissing(projectRoot, namespace = "@official") {
|
|
4712
|
-
const contextDir =
|
|
4713
|
-
const reviewPackPath =
|
|
4714
|
-
const manifestPath =
|
|
4715
|
-
if (
|
|
5297
|
+
const contextDir = join28(projectRoot, ".decantr", "context");
|
|
5298
|
+
const reviewPackPath = join28(contextDir, "review-pack.json");
|
|
5299
|
+
const manifestPath = join28(contextDir, "pack-manifest.json");
|
|
5300
|
+
if (existsSync27(reviewPackPath) && existsSync27(manifestPath)) {
|
|
4716
5301
|
return { attempted: false, hydrated: false };
|
|
4717
5302
|
}
|
|
4718
|
-
const essencePath =
|
|
4719
|
-
if (!
|
|
5303
|
+
const essencePath = join28(projectRoot, "decantr.essence.json");
|
|
5304
|
+
if (!existsSync27(essencePath)) {
|
|
4720
5305
|
return { attempted: false, hydrated: false };
|
|
4721
5306
|
}
|
|
4722
5307
|
try {
|
|
4723
5308
|
const client = getPublicAPIClient();
|
|
4724
|
-
const essence = JSON.parse(
|
|
5309
|
+
const essence = JSON.parse(readFileSync20(essencePath, "utf-8"));
|
|
4725
5310
|
const selected = await client.selectExecutionPack(
|
|
4726
5311
|
{
|
|
4727
5312
|
essence,
|
|
@@ -4729,14 +5314,14 @@ async function hydrateHostedReviewPackIfMissing(projectRoot, namespace = "@offic
|
|
|
4729
5314
|
},
|
|
4730
5315
|
{ namespace }
|
|
4731
5316
|
);
|
|
4732
|
-
|
|
4733
|
-
|
|
4734
|
-
|
|
4735
|
-
|
|
5317
|
+
mkdirSync11(contextDir, { recursive: true });
|
|
5318
|
+
writeFileSync14(join28(contextDir, "review-pack.md"), selected.pack.renderedMarkdown);
|
|
5319
|
+
writeFileSync14(
|
|
5320
|
+
join28(contextDir, "review-pack.json"),
|
|
4736
5321
|
JSON.stringify(selected.pack, null, 2) + "\n"
|
|
4737
5322
|
);
|
|
4738
|
-
if (!
|
|
4739
|
-
|
|
5323
|
+
if (!existsSync27(manifestPath)) {
|
|
5324
|
+
writeFileSync14(manifestPath, JSON.stringify(selected.manifest, null, 2) + "\n");
|
|
4740
5325
|
}
|
|
4741
5326
|
return { attempted: true, hydrated: true, scope: "review" };
|
|
4742
5327
|
} catch {
|
|
@@ -4746,17 +5331,17 @@ async function hydrateHostedReviewPackIfMissing(projectRoot, namespace = "@offic
|
|
|
4746
5331
|
async function printHostedFileCritique(sourcePath, namespace, jsonOutput = false, essencePath, treatmentsPath) {
|
|
4747
5332
|
const client = getPublicAPIClient();
|
|
4748
5333
|
const resolvedSourcePath = resolveUserPath(sourcePath);
|
|
4749
|
-
const resolvedEssencePath = essencePath ? resolveUserPath(essencePath) :
|
|
4750
|
-
const resolvedTreatmentsPath = treatmentsPath ? resolveUserPath(treatmentsPath) :
|
|
4751
|
-
if (!
|
|
5334
|
+
const resolvedEssencePath = essencePath ? resolveUserPath(essencePath) : join28(process.cwd(), "decantr.essence.json");
|
|
5335
|
+
const resolvedTreatmentsPath = treatmentsPath ? resolveUserPath(treatmentsPath) : join28(process.cwd(), "src", "styles", "treatments.css");
|
|
5336
|
+
if (!existsSync27(resolvedSourcePath)) {
|
|
4752
5337
|
throw new Error(`Source file not found at ${resolvedSourcePath}`);
|
|
4753
5338
|
}
|
|
4754
|
-
if (!
|
|
5339
|
+
if (!existsSync27(resolvedEssencePath)) {
|
|
4755
5340
|
throw new Error(`Essence file not found at ${resolvedEssencePath}`);
|
|
4756
5341
|
}
|
|
4757
|
-
const code =
|
|
4758
|
-
const essence = JSON.parse(
|
|
4759
|
-
const treatmentsCss =
|
|
5342
|
+
const code = readFileSync20(resolvedSourcePath, "utf-8");
|
|
5343
|
+
const essence = JSON.parse(readFileSync20(resolvedEssencePath, "utf-8"));
|
|
5344
|
+
const treatmentsCss = existsSync27(resolvedTreatmentsPath) ? readFileSync20(resolvedTreatmentsPath, "utf-8") : void 0;
|
|
4760
5345
|
const report = await client.critiqueFile(
|
|
4761
5346
|
{
|
|
4762
5347
|
essence,
|
|
@@ -4780,11 +5365,11 @@ async function printHostedFileCritique(sourcePath, namespace, jsonOutput = false
|
|
|
4780
5365
|
}
|
|
4781
5366
|
async function printHostedProjectAudit(namespace, jsonOutput = false, essencePath, distPath, sourcesPath) {
|
|
4782
5367
|
const client = getPublicAPIClient();
|
|
4783
|
-
const resolvedEssencePath = essencePath ? resolveUserPath(essencePath) :
|
|
4784
|
-
if (!
|
|
5368
|
+
const resolvedEssencePath = essencePath ? resolveUserPath(essencePath) : join28(process.cwd(), "decantr.essence.json");
|
|
5369
|
+
if (!existsSync27(resolvedEssencePath)) {
|
|
4785
5370
|
throw new Error(`Essence file not found at ${resolvedEssencePath}`);
|
|
4786
5371
|
}
|
|
4787
|
-
const essence = JSON.parse(
|
|
5372
|
+
const essence = JSON.parse(readFileSync20(resolvedEssencePath, "utf-8"));
|
|
4788
5373
|
const dist = readHostedDistSnapshot(distPath);
|
|
4789
5374
|
const sources = readHostedSourceSnapshot(sourcesPath);
|
|
4790
5375
|
const report = await client.auditProject(
|
|
@@ -4802,7 +5387,7 @@ async function printHostedProjectAudit(namespace, jsonOutput = false, essencePat
|
|
|
4802
5387
|
console.log(heading2("Hosted Project Audit"));
|
|
4803
5388
|
console.log(` Essence: ${resolvedEssencePath}`);
|
|
4804
5389
|
console.log(
|
|
4805
|
-
` Dist snapshot: ${dist ? distPath ? resolveUserPath(distPath) :
|
|
5390
|
+
` Dist snapshot: ${dist ? distPath ? resolveUserPath(distPath) : join28(process.cwd(), "dist") : "none"}`
|
|
4806
5391
|
);
|
|
4807
5392
|
console.log(
|
|
4808
5393
|
` Source snapshot: ${sources && sourcesPath ? resolveUserPath(sourcesPath) : "none"}`
|
|
@@ -4883,25 +5468,25 @@ async function cmdGet(type, id) {
|
|
|
4883
5468
|
}
|
|
4884
5469
|
const apiType = CONTENT_TYPE_TO_API_CONTENT_TYPE3[type];
|
|
4885
5470
|
const registryClient = new RegistryClient({
|
|
4886
|
-
cacheDir:
|
|
5471
|
+
cacheDir: join28(process.cwd(), ".decantr", "cache")
|
|
4887
5472
|
});
|
|
4888
5473
|
const result = await registryClient.fetchContentItem(apiType, id);
|
|
4889
5474
|
if (result) {
|
|
4890
5475
|
console.log(JSON.stringify(result.data, null, 2));
|
|
4891
5476
|
return;
|
|
4892
5477
|
}
|
|
4893
|
-
const currentDir =
|
|
5478
|
+
const currentDir = dirname4(fileURLToPath2(import.meta.url));
|
|
4894
5479
|
const bundledCandidates = [
|
|
4895
|
-
|
|
5480
|
+
join28(currentDir, "bundled", apiType, `${id}.json`),
|
|
4896
5481
|
// Running from src/
|
|
4897
|
-
|
|
5482
|
+
join28(currentDir, "..", "src", "bundled", apiType, `${id}.json`),
|
|
4898
5483
|
// Running from dist/
|
|
4899
|
-
|
|
5484
|
+
join28(currentDir, "..", "bundled", apiType, `${id}.json`)
|
|
4900
5485
|
// Alternative dist layout
|
|
4901
5486
|
];
|
|
4902
|
-
const bundledPath = bundledCandidates.find((p) =>
|
|
5487
|
+
const bundledPath = bundledCandidates.find((p) => existsSync27(p)) || null;
|
|
4903
5488
|
if (bundledPath) {
|
|
4904
|
-
const data = JSON.parse(
|
|
5489
|
+
const data = JSON.parse(readFileSync20(bundledPath, "utf-8"));
|
|
4905
5490
|
console.log(JSON.stringify(data, null, 2));
|
|
4906
5491
|
return;
|
|
4907
5492
|
}
|
|
@@ -4910,10 +5495,10 @@ async function cmdGet(type, id) {
|
|
|
4910
5495
|
return;
|
|
4911
5496
|
}
|
|
4912
5497
|
async function cmdValidate(path) {
|
|
4913
|
-
const essencePath = path ||
|
|
5498
|
+
const essencePath = path || join28(process.cwd(), "decantr.essence.json");
|
|
4914
5499
|
let raw;
|
|
4915
5500
|
try {
|
|
4916
|
-
raw =
|
|
5501
|
+
raw = readFileSync20(essencePath, "utf-8");
|
|
4917
5502
|
} catch {
|
|
4918
5503
|
console.error(error3(`Could not read ${essencePath}`));
|
|
4919
5504
|
process.exitCode = 1;
|
|
@@ -4982,7 +5567,7 @@ async function cmdList(type, sort, recommended, intelligenceSource) {
|
|
|
4982
5567
|
return;
|
|
4983
5568
|
}
|
|
4984
5569
|
const registryClient = new RegistryClient({
|
|
4985
|
-
cacheDir:
|
|
5570
|
+
cacheDir: join28(process.cwd(), ".decantr", "cache")
|
|
4986
5571
|
});
|
|
4987
5572
|
const result = await registryClient.fetchContentList(
|
|
4988
5573
|
type,
|
|
@@ -5029,11 +5614,17 @@ async function cmdList(type, sort, recommended, intelligenceSource) {
|
|
|
5029
5614
|
}
|
|
5030
5615
|
}
|
|
5031
5616
|
async function cmdInit(args) {
|
|
5032
|
-
const
|
|
5617
|
+
const workspaceInfo = resolveWorkspaceInfo(process.cwd(), args.project);
|
|
5618
|
+
if (args.yes && workspaceInfo.requiresProjectSelection) {
|
|
5619
|
+
console.log(error3("This looks like a workspace root with multiple app candidates."));
|
|
5620
|
+
console.log(dim3(`Use --project=<path>. Candidates: ${workspaceInfo.appCandidates.join(", ")}`));
|
|
5621
|
+
process.exitCode = 1;
|
|
5622
|
+
return;
|
|
5623
|
+
}
|
|
5624
|
+
const projectRoot = workspaceInfo.appRoot;
|
|
5033
5625
|
console.log(heading2("Decantr Project Setup"));
|
|
5034
5626
|
const detected = detectProject(projectRoot);
|
|
5035
5627
|
const workflowSeed = readBrownfieldInitSeed(projectRoot);
|
|
5036
|
-
const brownfieldAttach = Boolean(workflowSeed) || hasExistingProjectFootprint(detected);
|
|
5037
5628
|
if (workflowSeed) {
|
|
5038
5629
|
console.log(dim3(" Found .decantr/init-seed.json brownfield guidance."));
|
|
5039
5630
|
}
|
|
@@ -5048,11 +5639,27 @@ async function cmdInit(args) {
|
|
|
5048
5639
|
const requestedBlueprint = Boolean(args.blueprint);
|
|
5049
5640
|
const requestedArchetype = Boolean(args.archetype);
|
|
5050
5641
|
const requestedTheme = Boolean(args.theme);
|
|
5642
|
+
const policy = resolveWorkflowPolicy({
|
|
5643
|
+
command: "init",
|
|
5644
|
+
detected,
|
|
5645
|
+
workflowSeed,
|
|
5646
|
+
requestedWorkflow: args.workflow,
|
|
5647
|
+
requestedAdoption: args.adoption,
|
|
5648
|
+
requestedAssistantBridge: args["assistant-bridge"],
|
|
5649
|
+
requestedBlueprint,
|
|
5650
|
+
requestedArchetype,
|
|
5651
|
+
requestedTheme,
|
|
5652
|
+
explicitExisting: args.existing,
|
|
5653
|
+
offline: args.offline,
|
|
5654
|
+
projectScope: workspaceInfo.projectScope
|
|
5655
|
+
});
|
|
5656
|
+
const preferContractOnly = policy.contentSource === "none" && (policy.workflowMode === "brownfield-attach" || policy.workflowMode === "greenfield-contract-only");
|
|
5657
|
+
const shouldUseRegistry = !preferContractOnly || policy.registryRequired;
|
|
5051
5658
|
let offlineSeed = {
|
|
5052
5659
|
seeded: false,
|
|
5053
5660
|
strategy: null
|
|
5054
5661
|
};
|
|
5055
|
-
if (args.offline) {
|
|
5662
|
+
if (args.offline && shouldUseRegistry) {
|
|
5056
5663
|
offlineSeed = seedOfflineRegistry(projectRoot, projectRoot);
|
|
5057
5664
|
if (offlineSeed.seeded) {
|
|
5058
5665
|
console.log(dim3(` Seeded offline registry content from ${offlineSeed.strategy}.`));
|
|
@@ -5070,11 +5677,12 @@ async function cmdInit(args) {
|
|
|
5070
5677
|
}
|
|
5071
5678
|
}
|
|
5072
5679
|
const registryClient = new RegistryClient({
|
|
5073
|
-
cacheDir:
|
|
5680
|
+
cacheDir: join28(projectRoot, ".decantr", "cache"),
|
|
5074
5681
|
apiUrl: args.registry,
|
|
5075
|
-
offline: args.offline
|
|
5682
|
+
offline: args.offline,
|
|
5683
|
+
projectRoot
|
|
5076
5684
|
});
|
|
5077
|
-
const apiAvailable = await registryClient.checkApiAvailability();
|
|
5685
|
+
const apiAvailable = shouldUseRegistry ? await registryClient.checkApiAvailability() : false;
|
|
5078
5686
|
if (!apiAvailable && !args.offline && (requestedBlueprint || requestedArchetype)) {
|
|
5079
5687
|
const fallbackSeed = seedOfflineRegistry(projectRoot, projectRoot);
|
|
5080
5688
|
if (fallbackSeed.seeded) {
|
|
@@ -5084,18 +5692,21 @@ async function cmdInit(args) {
|
|
|
5084
5692
|
}
|
|
5085
5693
|
let selectedBlueprint = "default";
|
|
5086
5694
|
let registrySource = "cache";
|
|
5087
|
-
const preferContractOnly = brownfieldAttach && !requestedBlueprint && !requestedArchetype;
|
|
5088
|
-
const workflowMode = workflowSeed || brownfieldAttach && !requestedBlueprint && !requestedArchetype ? "brownfield-attach" : "greenfield-scaffold";
|
|
5089
5695
|
if (args.yes) {
|
|
5090
5696
|
selectedBlueprint = args.blueprint || "default";
|
|
5091
|
-
} else if (!apiAvailable) {
|
|
5697
|
+
} else if (shouldUseRegistry && !apiAvailable) {
|
|
5092
5698
|
if (!args.blueprint) {
|
|
5093
5699
|
console.log(`
|
|
5094
5700
|
${YELLOW9}You're offline. Scaffolding minimal Decantr project.${RESET13}`);
|
|
5095
5701
|
console.log(
|
|
5096
5702
|
dim3("Run `decantr sync` or `decantr upgrade` when online to pull full registry content.\n")
|
|
5097
5703
|
);
|
|
5098
|
-
const result2 = scaffoldMinimal(projectRoot
|
|
5704
|
+
const result2 = scaffoldMinimal(projectRoot, {
|
|
5705
|
+
workflowMode: policy.workflowMode,
|
|
5706
|
+
adoptionMode: policy.adoptionMode,
|
|
5707
|
+
contentSource: policy.contentSource,
|
|
5708
|
+
assistantBridge: policy.assistantBridge
|
|
5709
|
+
});
|
|
5099
5710
|
console.log(success3("\nProject scaffolded (minimal/offline)!\n"));
|
|
5100
5711
|
console.log(" Files created:");
|
|
5101
5712
|
console.log(` ${cyan3("decantr.essence.json")} Design specification`);
|
|
@@ -5111,7 +5722,7 @@ ${YELLOW9}You're offline. Scaffolding minimal Decantr project.${RESET13}`);
|
|
|
5111
5722
|
` 2. Run ${cyan3("decantr refresh")} after syncing to generate scaffold, section, and page packs`
|
|
5112
5723
|
);
|
|
5113
5724
|
console.log(
|
|
5114
|
-
` 3. Read ${cyan3("
|
|
5725
|
+
` 3. Read ${cyan3(".decantr/context/scaffold-pack.md")} first, then use ${cyan3("DECANTR.md")} as a lookup reference`
|
|
5115
5726
|
);
|
|
5116
5727
|
console.log(
|
|
5117
5728
|
` 4. Use ${cyan3("decantr create <type> <name>")} to create custom content if needed`
|
|
@@ -5136,22 +5747,22 @@ ${YELLOW9}You're offline. Scaffolding minimal Decantr project.${RESET13}`);
|
|
|
5136
5747
|
${YELLOW9}You're offline. Scaffolding Decantr default.${RESET13}`);
|
|
5137
5748
|
console.log(dim3("Run `decantr upgrade` when online, or visit decantr.ai/registry\n"));
|
|
5138
5749
|
selectedBlueprint = "default";
|
|
5139
|
-
} else if (
|
|
5750
|
+
} else if (shouldUseRegistry) {
|
|
5140
5751
|
console.log(dim3("Fetching registry content..."));
|
|
5141
5752
|
const blueprintsResult2 = await registryClient.fetchBlueprints();
|
|
5142
5753
|
registrySource = blueprintsResult2.source.type === "api" ? "api" : "cache";
|
|
5143
5754
|
const { selectedBlueprint: selected } = await runSimplifiedInit(blueprintsResult2.data.items);
|
|
5144
5755
|
selectedBlueprint = selected || "default";
|
|
5145
5756
|
}
|
|
5146
|
-
const archetypesResult = await registryClient.fetchArchetypes();
|
|
5147
|
-
const blueprintsResult = await registryClient.fetchBlueprints();
|
|
5148
|
-
const themesResult = await registryClient.fetchThemes();
|
|
5149
|
-
if (archetypesResult
|
|
5757
|
+
const archetypesResult = shouldUseRegistry ? await registryClient.fetchArchetypes() : null;
|
|
5758
|
+
const blueprintsResult = shouldUseRegistry ? await registryClient.fetchBlueprints() : null;
|
|
5759
|
+
const themesResult = shouldUseRegistry ? await registryClient.fetchThemes() : null;
|
|
5760
|
+
if (archetypesResult?.source.type === "api") {
|
|
5150
5761
|
registrySource = "api";
|
|
5151
5762
|
}
|
|
5152
|
-
const archetypes = archetypesResult
|
|
5153
|
-
const blueprints = blueprintsResult
|
|
5154
|
-
const themes = themesResult
|
|
5763
|
+
const archetypes = archetypesResult?.data.items ?? [];
|
|
5764
|
+
const blueprints = blueprintsResult?.data.items ?? [];
|
|
5765
|
+
const themes = themesResult?.data.items ?? [];
|
|
5155
5766
|
let options;
|
|
5156
5767
|
const userExplicit = {
|
|
5157
5768
|
theme: Boolean(args.theme),
|
|
@@ -5159,7 +5770,10 @@ ${YELLOW9}You're offline. Scaffolding Decantr default.${RESET13}`);
|
|
|
5159
5770
|
shape: Boolean(args.shape),
|
|
5160
5771
|
personality: Boolean(args.personality)
|
|
5161
5772
|
};
|
|
5162
|
-
if (
|
|
5773
|
+
if (preferContractOnly) {
|
|
5774
|
+
const flags = parseFlags(args, detected);
|
|
5775
|
+
options = mergeWithDefaults(flags, detected, workflowSeed ?? void 0);
|
|
5776
|
+
} else if (args.yes || selectedBlueprint !== "default") {
|
|
5163
5777
|
const flags = parseFlags(args, detected);
|
|
5164
5778
|
flags.blueprint = selectedBlueprint !== "default" ? selectedBlueprint : flags.blueprint;
|
|
5165
5779
|
options = mergeWithDefaults(flags, detected, workflowSeed ?? void 0);
|
|
@@ -5176,14 +5790,22 @@ ${YELLOW9}You're offline. Scaffolding Decantr default.${RESET13}`);
|
|
|
5176
5790
|
userExplicit.shape = true;
|
|
5177
5791
|
userExplicit.personality = true;
|
|
5178
5792
|
}
|
|
5179
|
-
options.workflowMode = workflowMode;
|
|
5793
|
+
options.workflowMode = policy.workflowMode;
|
|
5794
|
+
options.adoptionMode = policy.adoptionMode;
|
|
5795
|
+
options.contentSource = policy.contentSource;
|
|
5796
|
+
options.assistantBridge = policy.assistantBridge;
|
|
5797
|
+
options.projectScope = policy.projectScope;
|
|
5798
|
+
options.workspaceRoot = workspaceInfo.workspaceRoot;
|
|
5799
|
+
options.appRoot = workspaceInfo.appRoot;
|
|
5800
|
+
options.analysisArtifacts = policy.hasAnalysisArtifacts;
|
|
5801
|
+
options.adapterId = resolveBootstrapTarget(options.target).adapterId;
|
|
5180
5802
|
let topologyMarkdown = "";
|
|
5181
5803
|
let archetypeData;
|
|
5182
5804
|
let composedSections;
|
|
5183
5805
|
let routeMap;
|
|
5184
5806
|
let patternSpecs;
|
|
5185
5807
|
let blueprintData;
|
|
5186
|
-
if (options.blueprint) {
|
|
5808
|
+
if (shouldUseRegistry && options.blueprint) {
|
|
5187
5809
|
const blueprintResult = await registryClient.fetchBlueprint(options.blueprint);
|
|
5188
5810
|
if (blueprintResult) {
|
|
5189
5811
|
const blueprint = blueprintResult.data;
|
|
@@ -5306,7 +5928,7 @@ ${YELLOW9}You're offline. Scaffolding Decantr default.${RESET13}`);
|
|
|
5306
5928
|
`${YELLOW9} Warning: Could not fetch blueprint "${options.blueprint}". Using defaults.${RESET13}`
|
|
5307
5929
|
);
|
|
5308
5930
|
}
|
|
5309
|
-
} else if (options.archetype) {
|
|
5931
|
+
} else if (shouldUseRegistry && options.archetype) {
|
|
5310
5932
|
const archetypeResult = await registryClient.fetchArchetype(options.archetype);
|
|
5311
5933
|
if (archetypeResult) {
|
|
5312
5934
|
archetypeData = mapRegistryArchetypeToArchetypeData(archetypeResult.data);
|
|
@@ -5323,7 +5945,7 @@ ${YELLOW9}You're offline. Scaffolding Decantr default.${RESET13}`);
|
|
|
5323
5945
|
}
|
|
5324
5946
|
}
|
|
5325
5947
|
let themeData;
|
|
5326
|
-
if (options.theme) {
|
|
5948
|
+
if (shouldUseRegistry && options.theme) {
|
|
5327
5949
|
const themeResult = await registryClient.fetchTheme(options.theme);
|
|
5328
5950
|
if (themeResult) {
|
|
5329
5951
|
themeData = mapRegistryThemeToThemeData(themeResult.data);
|
|
@@ -5355,6 +5977,19 @@ ${YELLOW9}You're offline. Scaffolding Decantr default.${RESET13}`);
|
|
|
5355
5977
|
patternSpecs,
|
|
5356
5978
|
blueprintData
|
|
5357
5979
|
);
|
|
5980
|
+
let assistantBridgePath = null;
|
|
5981
|
+
let appliedRuleFiles = [];
|
|
5982
|
+
if (policy.assistantBridge === "preview" || policy.assistantBridge === "apply") {
|
|
5983
|
+
assistantBridgePath = writeAssistantBridgePreview({
|
|
5984
|
+
projectRoot,
|
|
5985
|
+
detected,
|
|
5986
|
+
workflowMode: policy.workflowMode,
|
|
5987
|
+
assistantBridge: policy.assistantBridge
|
|
5988
|
+
});
|
|
5989
|
+
}
|
|
5990
|
+
if (policy.assistantBridge === "apply") {
|
|
5991
|
+
appliedRuleFiles = applyAssistantBridge(projectRoot, detected);
|
|
5992
|
+
}
|
|
5358
5993
|
console.log(success3("\nProject scaffolded!\n"));
|
|
5359
5994
|
console.log(" Files created:");
|
|
5360
5995
|
console.log(` ${cyan3("decantr.essence.json")} Design specification`);
|
|
@@ -5363,7 +5998,13 @@ ${YELLOW9}You're offline. Scaffolding Decantr default.${RESET13}`);
|
|
|
5363
5998
|
if (result.gitignoreUpdated) {
|
|
5364
5999
|
console.log(` ${dim3(".gitignore updated")}`);
|
|
5365
6000
|
}
|
|
5366
|
-
if (
|
|
6001
|
+
if (assistantBridgePath) {
|
|
6002
|
+
console.log(` ${cyan3(".decantr/context/assistant-bridge.md")} Assistant bridge preview`);
|
|
6003
|
+
}
|
|
6004
|
+
if (appliedRuleFiles.length > 0) {
|
|
6005
|
+
console.log(` ${dim3(`Rule bridge applied: ${appliedRuleFiles.join(", ")}`)}`);
|
|
6006
|
+
}
|
|
6007
|
+
if (!existsSync27(join28(projectRoot, "package.json"))) {
|
|
5367
6008
|
console.log("");
|
|
5368
6009
|
console.log(
|
|
5369
6010
|
dim3(` Note: ${cyan3("decantr init")} created Decantr contract/context files only.`)
|
|
@@ -5376,14 +6017,15 @@ ${YELLOW9}You're offline. Scaffolding Decantr default.${RESET13}`);
|
|
|
5376
6017
|
}
|
|
5377
6018
|
console.log("");
|
|
5378
6019
|
console.log(" Next steps:");
|
|
5379
|
-
console.log(" 1. Read
|
|
6020
|
+
console.log(" 1. Read .decantr/context/scaffold-pack.md first as the primary compiled contract");
|
|
5380
6021
|
console.log(
|
|
5381
|
-
" 2. Read .decantr/context/scaffold
|
|
6022
|
+
" 2. Read .decantr/context/scaffold.md for broader topology, route map, and voice guidance"
|
|
5382
6023
|
);
|
|
5383
6024
|
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.
|
|
6025
|
+
console.log(" 4. Use DECANTR.md as a lookup reference for atoms, treatments, and guard rules");
|
|
6026
|
+
console.log(" 5. Build the shell and route structure first, then implement the pages");
|
|
6027
|
+
console.log(" 6. Run decantr check and decantr audit after implementation");
|
|
6028
|
+
console.log(" 7. Explore more at decantr.ai/registry");
|
|
5387
6029
|
console.log("");
|
|
5388
6030
|
console.log(" Commands:");
|
|
5389
6031
|
console.log(` ${cyan3("decantr status")} Project health`);
|
|
@@ -5393,7 +6035,7 @@ ${YELLOW9}You're offline. Scaffolding Decantr default.${RESET13}`);
|
|
|
5393
6035
|
console.log(` ${cyan3("decantr upgrade")} Update to latest patterns`);
|
|
5394
6036
|
console.log(` ${cyan3("decantr check")} Detect drift issues`);
|
|
5395
6037
|
console.log(` ${cyan3("decantr migrate")} Migrate v2 essence to v3`);
|
|
5396
|
-
const essenceContent =
|
|
6038
|
+
const essenceContent = readFileSync20(result.essencePath, "utf-8");
|
|
5397
6039
|
const essence = JSON.parse(essenceContent);
|
|
5398
6040
|
if (essence.version !== "3.1.0") {
|
|
5399
6041
|
const validation = validateEssence2(essence);
|
|
@@ -5406,11 +6048,12 @@ Validation warnings: ${validation.errors.join(", ")}`));
|
|
|
5406
6048
|
let promptPages;
|
|
5407
6049
|
if (isV36(essence)) {
|
|
5408
6050
|
const allPages = essence.blueprint.sections ? essence.blueprint.sections.flatMap(
|
|
5409
|
-
(s) => s.pages.map((p) => ({ ...p, _shell: s.shell }))
|
|
6051
|
+
(s) => s.pages.map((p) => ({ ...p, _sectionId: s.id, _shell: s.shell }))
|
|
5410
6052
|
) : essence.blueprint.pages || [];
|
|
5411
6053
|
promptPages = allPages.map(
|
|
5412
6054
|
(p) => ({
|
|
5413
6055
|
id: p.id,
|
|
6056
|
+
sectionId: p._sectionId,
|
|
5414
6057
|
shell: p.shell_override ?? p._shell ?? essence.blueprint.shell,
|
|
5415
6058
|
layout: (p.layout || []).map(
|
|
5416
6059
|
(item) => typeof item === "string" ? item : extractPatternName(item)
|
|
@@ -5421,7 +6064,9 @@ Validation warnings: ${validation.errors.join(", ")}`));
|
|
|
5421
6064
|
promptPages = essence.structure || [{ id: "home", shell: options.shell, layout: ["hero"] }];
|
|
5422
6065
|
}
|
|
5423
6066
|
const promptCtx = {
|
|
5424
|
-
workflow: options.workflowMode
|
|
6067
|
+
workflow: options.workflowMode || "greenfield-scaffold",
|
|
6068
|
+
adoptionMode: options.adoptionMode,
|
|
6069
|
+
analysisArtifacts: options.analysisArtifacts,
|
|
5425
6070
|
archetype: options.archetype || "custom",
|
|
5426
6071
|
blueprint: options.blueprint,
|
|
5427
6072
|
theme: options.theme,
|
|
@@ -5447,16 +6092,16 @@ Validation warnings: ${validation.errors.join(", ")}`));
|
|
|
5447
6092
|
}
|
|
5448
6093
|
async function cmdStatus() {
|
|
5449
6094
|
const projectRoot = process.cwd();
|
|
5450
|
-
const essencePath =
|
|
5451
|
-
const projectJsonPath =
|
|
6095
|
+
const essencePath = join28(projectRoot, "decantr.essence.json");
|
|
6096
|
+
const projectJsonPath = join28(projectRoot, ".decantr", "project.json");
|
|
5452
6097
|
console.log(heading2("Decantr Project Status"));
|
|
5453
|
-
if (!
|
|
6098
|
+
if (!existsSync27(essencePath)) {
|
|
5454
6099
|
console.log(`${RED11}No decantr.essence.json found.${RESET13}`);
|
|
5455
6100
|
console.log(dim3('Run "decantr init" to create one.'));
|
|
5456
6101
|
return;
|
|
5457
6102
|
}
|
|
5458
6103
|
try {
|
|
5459
|
-
const essence = JSON.parse(
|
|
6104
|
+
const essence = JSON.parse(readFileSync20(essencePath, "utf-8"));
|
|
5460
6105
|
const validation = validateEssence2(essence);
|
|
5461
6106
|
const essenceVersion = isV36(essence) ? "v3" : "v2";
|
|
5462
6107
|
console.log(`${BOLD6}Essence:${RESET13}`);
|
|
@@ -5513,9 +6158,9 @@ async function cmdStatus() {
|
|
|
5513
6158
|
}
|
|
5514
6159
|
console.log("");
|
|
5515
6160
|
console.log(`${BOLD6}Sync Status:${RESET13}`);
|
|
5516
|
-
if (
|
|
6161
|
+
if (existsSync27(projectJsonPath)) {
|
|
5517
6162
|
try {
|
|
5518
|
-
const projectJson = JSON.parse(
|
|
6163
|
+
const projectJson = JSON.parse(readFileSync20(projectJsonPath, "utf-8"));
|
|
5519
6164
|
const syncStatus = projectJson.sync?.status || "unknown";
|
|
5520
6165
|
const lastSync = projectJson.sync?.lastSync || "never";
|
|
5521
6166
|
const source = projectJson.sync?.registrySource || "unknown";
|
|
@@ -5533,7 +6178,7 @@ async function cmdStatus() {
|
|
|
5533
6178
|
}
|
|
5534
6179
|
async function cmdSync() {
|
|
5535
6180
|
const projectRoot = process.cwd();
|
|
5536
|
-
const cacheDir =
|
|
6181
|
+
const cacheDir = join28(projectRoot, ".decantr", "cache");
|
|
5537
6182
|
console.log(heading2("Syncing registry content..."));
|
|
5538
6183
|
const result = await syncRegistry(cacheDir);
|
|
5539
6184
|
if (result.synced.length > 0) {
|
|
@@ -5728,14 +6373,14 @@ ${BOLD6}Examples:${RESET13}
|
|
|
5728
6373
|
process.exitCode = 1;
|
|
5729
6374
|
return;
|
|
5730
6375
|
}
|
|
5731
|
-
const themePath =
|
|
5732
|
-
if (!
|
|
6376
|
+
const themePath = join28(projectRoot, ".decantr", "custom", "themes", `${name}.json`);
|
|
6377
|
+
if (!existsSync27(themePath)) {
|
|
5733
6378
|
console.error(error3(`Theme "${name}" not found at ${themePath}`));
|
|
5734
6379
|
process.exitCode = 1;
|
|
5735
6380
|
return;
|
|
5736
6381
|
}
|
|
5737
6382
|
try {
|
|
5738
|
-
const theme = JSON.parse(
|
|
6383
|
+
const theme = JSON.parse(readFileSync20(themePath, "utf-8"));
|
|
5739
6384
|
const result = validateCustomTheme(theme);
|
|
5740
6385
|
if (result.valid) {
|
|
5741
6386
|
console.log(success3(`Custom theme "${name}" is valid`));
|
|
@@ -5808,7 +6453,7 @@ function cmdHelp() {
|
|
|
5808
6453
|
${BOLD6}decantr${RESET13} \u2014 Design intelligence for AI-generated UI
|
|
5809
6454
|
|
|
5810
6455
|
${BOLD6}Usage:${RESET13}
|
|
5811
|
-
decantr new <name> [--blueprint=X] [--archetype=X] [--theme=X]
|
|
6456
|
+
decantr new <name> [--blueprint=X] [--archetype=X] [--theme=X] [--workflow=greenfield] [--adoption=decantr-css]
|
|
5812
6457
|
decantr magic <prompt> [--dry-run]
|
|
5813
6458
|
decantr init [options]
|
|
5814
6459
|
decantr status
|
|
@@ -5827,6 +6472,7 @@ ${BOLD6}Usage:${RESET13}
|
|
|
5827
6472
|
decantr registry get-pack <manifest|scaffold|review|section|page|mutation> [id] [--namespace <namespace>] [--json] [--essence <path>] [--write-context]
|
|
5828
6473
|
decantr registry critique-file <file> [--namespace <namespace>] [--json] [--essence <path>] [--treatments <path>]
|
|
5829
6474
|
decantr registry audit-project [--namespace <namespace>] [--json] [--essence <path>] [--dist <path>] [--sources <dir>]
|
|
6475
|
+
decantr rules apply [--project=<path>]
|
|
5830
6476
|
decantr validate [path]
|
|
5831
6477
|
decantr theme <subcommand>
|
|
5832
6478
|
decantr create <type> <name>
|
|
@@ -5845,6 +6491,10 @@ ${BOLD6}Init Options:${RESET13}
|
|
|
5845
6491
|
--guard Guard mode: creative | guided | strict
|
|
5846
6492
|
--density Spacing: compact | comfortable | spacious
|
|
5847
6493
|
--shell Default shell layout
|
|
6494
|
+
--workflow Workflow: greenfield | brownfield | hybrid
|
|
6495
|
+
--adoption Adoption: contract-only | style-bridge | decantr-css
|
|
6496
|
+
--assistant-bridge Assistant rules: none | preview | apply
|
|
6497
|
+
--project App path inside a workspace/monorepo
|
|
5848
6498
|
--existing Initialize in existing project
|
|
5849
6499
|
--offline Force offline mode
|
|
5850
6500
|
--yes, -y Accept defaults, skip confirmations
|
|
@@ -5874,6 +6524,7 @@ ${BOLD6}Commands:${RESET13}
|
|
|
5874
6524
|
${cyan3("analyze")} Brownfield entrypoint: scan an existing project and emit attach guidance
|
|
5875
6525
|
${cyan3("export")} Export design tokens to framework format (shadcn, tailwind, css-vars)
|
|
5876
6526
|
${cyan3("registry")} Registry management and intelligence summary
|
|
6527
|
+
${cyan3("rules")} Preview/apply Decantr assistant bridge blocks to repo rule files
|
|
5877
6528
|
${cyan3("upgrade")} Check for content updates from registry
|
|
5878
6529
|
${cyan3("help")} Show this help
|
|
5879
6530
|
|
|
@@ -5881,7 +6532,11 @@ ${BOLD6}Examples:${RESET13}
|
|
|
5881
6532
|
decantr new my-app --blueprint=carbon-ai-portal
|
|
5882
6533
|
decantr magic "AI chatbot with dark cyber theme \u2014 bold and futuristic"
|
|
5883
6534
|
decantr init
|
|
5884
|
-
decantr init --existing --
|
|
6535
|
+
decantr init --existing --adoption=contract-only --yes
|
|
6536
|
+
decantr init --existing --adoption=style-bridge --assistant-bridge=preview
|
|
6537
|
+
decantr init --workflow=greenfield --adoption=contract-only
|
|
6538
|
+
decantr init --project=apps/web --yes
|
|
6539
|
+
decantr rules apply
|
|
5885
6540
|
decantr status
|
|
5886
6541
|
decantr audit
|
|
5887
6542
|
decantr audit src/pages/HomePage.tsx
|
|
@@ -5904,13 +6559,14 @@ ${BOLD6}Examples:${RESET13}
|
|
|
5904
6559
|
decantr create pattern my-card
|
|
5905
6560
|
|
|
5906
6561
|
${BOLD6}Workflow Model:${RESET13}
|
|
5907
|
-
${cyan3("Greenfield blueprint")} decantr new
|
|
5908
|
-
${cyan3("
|
|
6562
|
+
${cyan3("Greenfield blueprint")} decantr new my-app --blueprint=X --workflow=greenfield --adoption=decantr-css
|
|
6563
|
+
${cyan3("Greenfield contract")} decantr init --workflow=greenfield --adoption=contract-only
|
|
6564
|
+
${cyan3("Brownfield adoption")} decantr analyze -> decantr init --existing --adoption=contract-only
|
|
5909
6565
|
${cyan3("Hybrid composition")} decantr add/remove, decantr theme switch, decantr registry, decantr upgrade
|
|
5910
6566
|
|
|
5911
6567
|
${BOLD6}Bootstrap adapters:${RESET13}
|
|
5912
|
-
|
|
5913
|
-
|
|
6568
|
+
Runnable starter adapters: ${cyan3("react-vite")}, ${cyan3("next-app")}
|
|
6569
|
+
Unsupported targets resolve through ${cyan3("generic-web")} contract-only mode until their starter adapters land.
|
|
5914
6570
|
`);
|
|
5915
6571
|
}
|
|
5916
6572
|
async function main() {
|
|
@@ -5922,14 +6578,14 @@ async function main() {
|
|
|
5922
6578
|
}
|
|
5923
6579
|
if (command === "--version" || command === "-v" || command === "version") {
|
|
5924
6580
|
try {
|
|
5925
|
-
const here =
|
|
6581
|
+
const here = dirname4(fileURLToPath2(import.meta.url));
|
|
5926
6582
|
const candidates = [
|
|
5927
|
-
|
|
5928
|
-
|
|
6583
|
+
join28(here, "..", "package.json"),
|
|
6584
|
+
join28(here, "..", "..", "package.json")
|
|
5929
6585
|
];
|
|
5930
6586
|
for (const candidate of candidates) {
|
|
5931
|
-
if (
|
|
5932
|
-
const pkg = JSON.parse(
|
|
6587
|
+
if (existsSync27(candidate)) {
|
|
6588
|
+
const pkg = JSON.parse(readFileSync20(candidate, "utf-8"));
|
|
5933
6589
|
if (pkg.version) {
|
|
5934
6590
|
console.log(pkg.version);
|
|
5935
6591
|
return;
|
|
@@ -5976,7 +6632,10 @@ async function main() {
|
|
|
5976
6632
|
shape: newOpts.shape,
|
|
5977
6633
|
target: newOpts.target,
|
|
5978
6634
|
offline: newOpts.offline === true,
|
|
5979
|
-
registry: newOpts.registry
|
|
6635
|
+
registry: newOpts.registry,
|
|
6636
|
+
workflow: newOpts.workflow,
|
|
6637
|
+
adoption: newOpts.adoption,
|
|
6638
|
+
assistantBridge: newOpts["assistant-bridge"]
|
|
5980
6639
|
});
|
|
5981
6640
|
break;
|
|
5982
6641
|
}
|
|
@@ -6015,7 +6674,7 @@ async function main() {
|
|
|
6015
6674
|
break;
|
|
6016
6675
|
}
|
|
6017
6676
|
case "upgrade": {
|
|
6018
|
-
const { cmdUpgrade } = await import("./upgrade-
|
|
6677
|
+
const { cmdUpgrade } = await import("./upgrade-KG42WK5C.js");
|
|
6019
6678
|
const applyFlag = args.includes("--apply");
|
|
6020
6679
|
await cmdUpgrade(process.cwd(), { apply: applyFlag });
|
|
6021
6680
|
break;
|
|
@@ -6407,7 +7066,47 @@ async function main() {
|
|
|
6407
7066
|
break;
|
|
6408
7067
|
}
|
|
6409
7068
|
case "analyze": {
|
|
6410
|
-
|
|
7069
|
+
let projectArg;
|
|
7070
|
+
for (let i = 1; i < args.length; i++) {
|
|
7071
|
+
if (args[i].startsWith("--project=")) {
|
|
7072
|
+
projectArg = args[i].split("=")[1];
|
|
7073
|
+
} else if (args[i] === "--project" && args[i + 1]) {
|
|
7074
|
+
projectArg = args[++i];
|
|
7075
|
+
}
|
|
7076
|
+
}
|
|
7077
|
+
const workspaceInfo = resolveWorkspaceInfo(process.cwd(), projectArg);
|
|
7078
|
+
if (workspaceInfo.requiresProjectSelection) {
|
|
7079
|
+
console.log(error3("This looks like a workspace root with multiple app candidates."));
|
|
7080
|
+
console.log(dim3(`Use --project=<path>. Candidates: ${workspaceInfo.appCandidates.join(", ")}`));
|
|
7081
|
+
process.exitCode = 1;
|
|
7082
|
+
break;
|
|
7083
|
+
}
|
|
7084
|
+
cmdAnalyze(workspaceInfo.appRoot, workspaceInfo);
|
|
7085
|
+
break;
|
|
7086
|
+
}
|
|
7087
|
+
case "rules": {
|
|
7088
|
+
const subcommand = args[1];
|
|
7089
|
+
if (subcommand !== "apply") {
|
|
7090
|
+
console.error(error3("Usage: decantr rules apply [--project=<path>]"));
|
|
7091
|
+
process.exitCode = 1;
|
|
7092
|
+
break;
|
|
7093
|
+
}
|
|
7094
|
+
let projectArg;
|
|
7095
|
+
for (let i = 2; i < args.length; i++) {
|
|
7096
|
+
if (args[i].startsWith("--project=")) {
|
|
7097
|
+
projectArg = args[i].split("=")[1];
|
|
7098
|
+
} else if (args[i] === "--project" && args[i + 1]) {
|
|
7099
|
+
projectArg = args[++i];
|
|
7100
|
+
}
|
|
7101
|
+
}
|
|
7102
|
+
const workspaceInfo = resolveWorkspaceInfo(process.cwd(), projectArg);
|
|
7103
|
+
const detected = detectProject(workspaceInfo.appRoot);
|
|
7104
|
+
const updated = applyAssistantBridge(workspaceInfo.appRoot, detected);
|
|
7105
|
+
if (updated.length === 0) {
|
|
7106
|
+
console.log(dim3("Assistant bridge rule files are already up to date."));
|
|
7107
|
+
} else {
|
|
7108
|
+
console.log(success3(`Applied Decantr assistant bridge to ${updated.join(", ")}.`));
|
|
7109
|
+
}
|
|
6411
7110
|
break;
|
|
6412
7111
|
}
|
|
6413
7112
|
case "magic": {
|