@decantr/cli 1.7.28 → 1.7.29
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 +15 -6
- package/dist/bin.js +3 -3
- package/dist/chunk-DI2PLOJ6.js +1418 -0
- package/dist/{chunk-GCDFX7UE.js → chunk-HULA6E2D.js} +5 -4
- package/dist/{chunk-56VBV4MT.js → chunk-US6RK5QT.js} +1271 -774
- package/dist/heal-YHLXO5QL.js +307 -0
- package/dist/index.js +3 -3
- package/dist/{upgrade-XMY6LIPS.js → upgrade-EV23CKA3.js} +1 -1
- package/package.json +4 -4
- package/dist/chunk-RRRHQ45P.js +0 -397
- package/dist/heal-SWUFIYU5.js +0 -99
|
@@ -14,18 +14,23 @@ import {
|
|
|
14
14
|
scaffoldProject,
|
|
15
15
|
syncRegistry,
|
|
16
16
|
writeExecutionPackBundleArtifacts
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-HULA6E2D.js";
|
|
18
18
|
import {
|
|
19
19
|
buildGuardRegistryContext,
|
|
20
|
+
createDoctrineMap,
|
|
21
|
+
scanAmbientContext,
|
|
20
22
|
scanProjectInteractions,
|
|
21
|
-
|
|
22
|
-
|
|
23
|
+
scanRoutes,
|
|
24
|
+
scanStyling,
|
|
25
|
+
sendCliCommandTelemetry,
|
|
26
|
+
writeDoctrineMap
|
|
27
|
+
} from "./chunk-DI2PLOJ6.js";
|
|
23
28
|
|
|
24
29
|
// src/index.ts
|
|
25
|
-
import { existsSync as
|
|
26
|
-
import { basename as basename2, dirname as dirname4, isAbsolute, join as
|
|
30
|
+
import { existsSync as existsSync26, mkdirSync as mkdirSync12, readdirSync as readdirSync6, readFileSync as readFileSync19, writeFileSync as writeFileSync15 } from "fs";
|
|
31
|
+
import { basename as basename2, dirname as dirname4, isAbsolute, join as join27, resolve as resolve4 } from "path";
|
|
27
32
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
28
|
-
import { evaluateGuard, isV3 as
|
|
33
|
+
import { evaluateGuard, isV3 as isV37, validateEssence as validateEssence2 } from "@decantr/essence-spec";
|
|
29
34
|
import {
|
|
30
35
|
CONTENT_TYPE_TO_API_CONTENT_TYPE as CONTENT_TYPE_TO_API_CONTENT_TYPE3,
|
|
31
36
|
CONTENT_TYPES as GET_CONTENT_TYPES,
|
|
@@ -274,7 +279,7 @@ function buildAssistantBridgeContent(input) {
|
|
|
274
279
|
lines.push("# Decantr Assistant Bridge");
|
|
275
280
|
lines.push("");
|
|
276
281
|
lines.push(
|
|
277
|
-
"Use this bridge when an AI assistant works in a repository that already has local rules."
|
|
282
|
+
"Use this bridge when an AI assistant works in a repository that already has local rules and Decantr has compiled them into a brownfield contract."
|
|
278
283
|
);
|
|
279
284
|
lines.push("");
|
|
280
285
|
lines.push(`- Workflow mode: ${input.workflowMode}`);
|
|
@@ -289,13 +294,13 @@ function buildAssistantBridgeContent(input) {
|
|
|
289
294
|
lines.push("");
|
|
290
295
|
lines.push(START);
|
|
291
296
|
lines.push(
|
|
292
|
-
"Before implementing Decantr-scoped work, read `
|
|
297
|
+
"Before implementing Decantr-scoped work, read `decantr.essence.json`, `.decantr/brownfield-report.md`, `.decantr/doctrine-map.json`, `.decantr/ambient-context.json`, and the compiled packs in `.decantr/context/`."
|
|
293
298
|
);
|
|
294
299
|
lines.push(
|
|
295
|
-
"
|
|
300
|
+
"Treat Decantr as the reconciled contract layer and the original project docs/rules as cited evidence; if they conflict, stop and report the conflict instead of guessing."
|
|
296
301
|
);
|
|
297
302
|
lines.push(
|
|
298
|
-
"
|
|
303
|
+
"For brownfield adoption, preserve existing framework, routing, styling, package manager, data boundaries, and build conventions unless the Decantr contract explicitly records a reviewed change."
|
|
299
304
|
);
|
|
300
305
|
lines.push(
|
|
301
306
|
"Do not install `@decantr/css` or rewrite styling unless the project adoption mode says `decantr-css` or the task explicitly asks for it."
|
|
@@ -315,8 +320,9 @@ function writeAssistantBridgePreview(input) {
|
|
|
315
320
|
function bridgeBlock() {
|
|
316
321
|
return [
|
|
317
322
|
START,
|
|
318
|
-
"Before implementing Decantr-scoped work, read `
|
|
319
|
-
"
|
|
323
|
+
"Before implementing Decantr-scoped work, read `decantr.essence.json`, `.decantr/brownfield-report.md`, `.decantr/doctrine-map.json`, `.decantr/ambient-context.json`, and `.decantr/context/scaffold-pack.md` first.",
|
|
324
|
+
"Treat Decantr as the reconciled contract layer and the original project docs/rules as cited evidence; if they conflict, stop and report the conflict instead of guessing.",
|
|
325
|
+
"For brownfield adoption, preserve existing framework, routing, styling, package manager, data boundaries, and build conventions unless the Decantr contract explicitly records a reviewed change.",
|
|
320
326
|
"Do not install `@decantr/css` or rewrite styling unless the project adoption mode says `decantr-css` or the task explicitly asks for it.",
|
|
321
327
|
END,
|
|
322
328
|
""
|
|
@@ -345,6 +351,16 @@ description: Decantr project contract and brownfield adoption bridge
|
|
|
345
351
|
alwaysApply: true
|
|
346
352
|
---
|
|
347
353
|
|
|
354
|
+
${bridgeBlock()}`;
|
|
355
|
+
if (existsSync3(path) && readFileSync3(path, "utf-8") === content) return false;
|
|
356
|
+
mkdirSync2(dirname(path), { recursive: true });
|
|
357
|
+
writeFileSync3(path, content);
|
|
358
|
+
return true;
|
|
359
|
+
}
|
|
360
|
+
function writeClaudeRule(projectRoot) {
|
|
361
|
+
const path = join3(projectRoot, ".claude", "rules", "decantr.md");
|
|
362
|
+
const content = `# Decantr Brownfield Bridge
|
|
363
|
+
|
|
348
364
|
${bridgeBlock()}`;
|
|
349
365
|
if (existsSync3(path) && readFileSync3(path, "utf-8") === content) return false;
|
|
350
366
|
mkdirSync2(dirname(path), { recursive: true });
|
|
@@ -353,7 +369,13 @@ ${bridgeBlock()}`;
|
|
|
353
369
|
}
|
|
354
370
|
function applyAssistantBridge(projectRoot, detected) {
|
|
355
371
|
const updated = [];
|
|
356
|
-
const markdownTargets = [
|
|
372
|
+
const markdownTargets = [
|
|
373
|
+
"CLAUDE.md",
|
|
374
|
+
"AGENTS.md",
|
|
375
|
+
"GEMINI.md",
|
|
376
|
+
"copilot-instructions.md",
|
|
377
|
+
".github/copilot-instructions.md"
|
|
378
|
+
];
|
|
357
379
|
for (const target of markdownTargets) {
|
|
358
380
|
if (detected.existingRuleFiles.includes(target) && upsertMarkdownBlock(join3(projectRoot, target))) {
|
|
359
381
|
updated.push(target);
|
|
@@ -362,6 +384,12 @@ function applyAssistantBridge(projectRoot, detected) {
|
|
|
362
384
|
if (detected.existingRuleFiles.includes(".cursorrules")) {
|
|
363
385
|
if (upsertMarkdownBlock(join3(projectRoot, ".cursorrules"))) updated.push(".cursorrules");
|
|
364
386
|
}
|
|
387
|
+
if (detected.existingRuleFiles.includes(".windsurfrules")) {
|
|
388
|
+
if (upsertMarkdownBlock(join3(projectRoot, ".windsurfrules"))) updated.push(".windsurfrules");
|
|
389
|
+
}
|
|
390
|
+
if (detected.existingRuleFiles.includes(".claude/rules")) {
|
|
391
|
+
if (writeClaudeRule(projectRoot)) updated.push(".claude/rules/decantr.md");
|
|
392
|
+
}
|
|
365
393
|
if (detected.existingRuleFiles.includes(".cursor/rules")) {
|
|
366
394
|
if (writeCursorRule(projectRoot)) updated.push(".cursor/rules/decantr.mdc");
|
|
367
395
|
}
|
|
@@ -371,13 +399,540 @@ function applyAssistantBridge(projectRoot, detected) {
|
|
|
371
399
|
return updated;
|
|
372
400
|
}
|
|
373
401
|
|
|
402
|
+
// src/brownfield-proposal.ts
|
|
403
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
404
|
+
import { join as join4 } from "path";
|
|
405
|
+
import { isV3 as isV32 } from "@decantr/essence-spec";
|
|
406
|
+
function slugify(value, fallback) {
|
|
407
|
+
const slug = value.replace(/^\//, "").replace(/:[^/]+/g, "param").replace(/\*/g, "all").replace(/[^a-zA-Z0-9]+/g, "-").replace(/^-+|-+$/g, "").toLowerCase();
|
|
408
|
+
return slug || fallback;
|
|
409
|
+
}
|
|
410
|
+
var ROUTE_DOMAINS = [
|
|
411
|
+
{
|
|
412
|
+
sectionId: "observed-auth",
|
|
413
|
+
role: "gateway",
|
|
414
|
+
label: "Authentication",
|
|
415
|
+
description: "Observed authentication, sign-in, sign-up, callback, and account-entry surfaces.",
|
|
416
|
+
featureHints: ["auth"],
|
|
417
|
+
priority: 10
|
|
418
|
+
},
|
|
419
|
+
{
|
|
420
|
+
sectionId: "observed-rbac",
|
|
421
|
+
role: "auxiliary",
|
|
422
|
+
label: "RBAC and User Administration",
|
|
423
|
+
description: "Observed role, permission, user-management, and access-control administration surfaces.",
|
|
424
|
+
featureHints: ["admin", "team", "settings", "auth"],
|
|
425
|
+
priority: 30
|
|
426
|
+
},
|
|
427
|
+
{
|
|
428
|
+
sectionId: "observed-billing",
|
|
429
|
+
role: "auxiliary",
|
|
430
|
+
label: "Billing",
|
|
431
|
+
description: "Observed billing, subscription, plan, payment, invoice, and checkout surfaces.",
|
|
432
|
+
featureHints: ["billing", "admin"],
|
|
433
|
+
priority: 34
|
|
434
|
+
},
|
|
435
|
+
{
|
|
436
|
+
sectionId: "observed-admin",
|
|
437
|
+
role: "auxiliary",
|
|
438
|
+
label: "Administration",
|
|
439
|
+
description: "Observed administrative and operational management surfaces.",
|
|
440
|
+
featureHints: ["admin", "team"],
|
|
441
|
+
priority: 36
|
|
442
|
+
},
|
|
443
|
+
{
|
|
444
|
+
sectionId: "observed-settings",
|
|
445
|
+
role: "auxiliary",
|
|
446
|
+
label: "Settings",
|
|
447
|
+
description: "Observed account, profile, client-settings, organization, and user-preference surfaces.",
|
|
448
|
+
featureHints: ["settings", "profile", "team"],
|
|
449
|
+
priority: 38
|
|
450
|
+
},
|
|
451
|
+
{
|
|
452
|
+
sectionId: "observed-api-access",
|
|
453
|
+
role: "auxiliary",
|
|
454
|
+
label: "API Access",
|
|
455
|
+
description: "Observed developer, API key, token, and credential-management surfaces.",
|
|
456
|
+
featureHints: ["api-keys", "admin"],
|
|
457
|
+
priority: 40
|
|
458
|
+
},
|
|
459
|
+
{
|
|
460
|
+
sectionId: "observed-reporting",
|
|
461
|
+
role: "primary",
|
|
462
|
+
label: "Reporting",
|
|
463
|
+
description: "Observed reporting, analytics, metrics, dashboard, and compliance surfaces.",
|
|
464
|
+
featureHints: ["dashboard", "admin"],
|
|
465
|
+
priority: 58
|
|
466
|
+
},
|
|
467
|
+
{
|
|
468
|
+
sectionId: "observed-facilities",
|
|
469
|
+
role: "primary",
|
|
470
|
+
label: "Facilities",
|
|
471
|
+
description: "Observed facility, location, bin, lifecycle, and operational surfaces.",
|
|
472
|
+
featureHints: ["file-upload", "dashboard"],
|
|
473
|
+
priority: 60
|
|
474
|
+
},
|
|
475
|
+
{
|
|
476
|
+
sectionId: "observed-lab-results",
|
|
477
|
+
role: "primary",
|
|
478
|
+
label: "Lab Results",
|
|
479
|
+
description: "Observed lab result, report detail, compliance, and evidence-review surfaces.",
|
|
480
|
+
featureHints: ["dashboard", "file-upload"],
|
|
481
|
+
priority: 62
|
|
482
|
+
},
|
|
483
|
+
{
|
|
484
|
+
sectionId: "observed-ai-insights",
|
|
485
|
+
role: "primary",
|
|
486
|
+
label: "AI Insights",
|
|
487
|
+
description: "Observed AI insight, assistant, intelligence, recommendation, and automation surfaces.",
|
|
488
|
+
featureHints: ["dashboard", "chat"],
|
|
489
|
+
priority: 64
|
|
490
|
+
},
|
|
491
|
+
{
|
|
492
|
+
sectionId: "observed-communications",
|
|
493
|
+
role: "primary",
|
|
494
|
+
label: "Communications",
|
|
495
|
+
description: "Observed chat, message, inbox, notification, and announcement surfaces.",
|
|
496
|
+
featureHints: ["chat", "notifications"],
|
|
497
|
+
priority: 66
|
|
498
|
+
},
|
|
499
|
+
{
|
|
500
|
+
sectionId: "observed-brand-portal",
|
|
501
|
+
role: "primary",
|
|
502
|
+
label: "Brand Portal",
|
|
503
|
+
description: "Observed brand, asset, media, gallery, and upload surfaces.",
|
|
504
|
+
featureHints: ["file-upload", "content"],
|
|
505
|
+
priority: 68
|
|
506
|
+
},
|
|
507
|
+
{
|
|
508
|
+
sectionId: "observed-content",
|
|
509
|
+
role: "primary",
|
|
510
|
+
label: "Content",
|
|
511
|
+
description: "Observed CMS, editor, article, post, catalog, and content-management surfaces.",
|
|
512
|
+
featureHints: ["content", "search"],
|
|
513
|
+
priority: 70
|
|
514
|
+
},
|
|
515
|
+
{
|
|
516
|
+
sectionId: "observed-dashboard",
|
|
517
|
+
role: "primary",
|
|
518
|
+
label: "Dashboard",
|
|
519
|
+
description: "Observed dashboard, overview, workspace, and primary application surfaces.",
|
|
520
|
+
featureHints: ["dashboard", "search"],
|
|
521
|
+
priority: 72
|
|
522
|
+
},
|
|
523
|
+
{
|
|
524
|
+
sectionId: "observed-public",
|
|
525
|
+
role: "public",
|
|
526
|
+
label: "Public",
|
|
527
|
+
description: "Observed public-facing, marketing, docs, blog, legal, contact, and home routes.",
|
|
528
|
+
featureHints: ["content", "search"],
|
|
529
|
+
priority: 0
|
|
530
|
+
},
|
|
531
|
+
{
|
|
532
|
+
sectionId: "observed-primary",
|
|
533
|
+
role: "primary",
|
|
534
|
+
label: "Primary Application",
|
|
535
|
+
description: "Observed primary application routes from the existing application.",
|
|
536
|
+
featureHints: [],
|
|
537
|
+
priority: 90
|
|
538
|
+
}
|
|
539
|
+
];
|
|
540
|
+
function routeDomain(path) {
|
|
541
|
+
const lower = path.toLowerCase();
|
|
542
|
+
if (/\/(login|signin|sign-in|signup|sign-up|register|auth|callback|reset-password|forgot-password)/.test(
|
|
543
|
+
lower
|
|
544
|
+
)) {
|
|
545
|
+
return ROUTE_DOMAINS[0];
|
|
546
|
+
}
|
|
547
|
+
if (/\/(rbac|roles?|permissions?|manage-rbac|manage-users|user-management|access-control)\b/.test(lower)) {
|
|
548
|
+
return ROUTE_DOMAINS.find((domain) => domain.sectionId === "observed-rbac");
|
|
549
|
+
}
|
|
550
|
+
if (/\/(billing|subscription|subscriptions|pricing|plans|checkout|payment|payments|stripe|invoice|invoices)\b/.test(lower)) {
|
|
551
|
+
return ROUTE_DOMAINS.find((domain) => domain.sectionId === "observed-billing");
|
|
552
|
+
}
|
|
553
|
+
if (/\/(admin|moderation|moderate|system-logs)\b/.test(lower)) {
|
|
554
|
+
return ROUTE_DOMAINS.find((domain) => domain.sectionId === "observed-admin");
|
|
555
|
+
}
|
|
556
|
+
if (/\/(settings|profile|account|client-settings|user-settings|preferences|organization|org|team|members)\b/.test(lower)) {
|
|
557
|
+
return ROUTE_DOMAINS.find((domain) => domain.sectionId === "observed-settings");
|
|
558
|
+
}
|
|
559
|
+
if (/\/(api-keys?|api-key-management|developer|developers|tokens?|credentials?)\b/.test(lower)) {
|
|
560
|
+
return ROUTE_DOMAINS.find((domain) => domain.sectionId === "observed-api-access");
|
|
561
|
+
}
|
|
562
|
+
if (/\/(reports?|analytics|metrics|stats|compliance|environmental|operations)\b/.test(lower)) {
|
|
563
|
+
return ROUTE_DOMAINS.find((domain) => domain.sectionId === "observed-reporting");
|
|
564
|
+
}
|
|
565
|
+
if (/\/(facilities|facility|locations?|bins?|lifecycle)\b/.test(lower)) {
|
|
566
|
+
return ROUTE_DOMAINS.find((domain) => domain.sectionId === "observed-facilities");
|
|
567
|
+
}
|
|
568
|
+
if (/\/(lab-results?|lab|results?)\b/.test(lower)) {
|
|
569
|
+
return ROUTE_DOMAINS.find((domain) => domain.sectionId === "observed-lab-results");
|
|
570
|
+
}
|
|
571
|
+
if (/\/(ai-insights?|insights?|ai|assistant|automation|recommendations?)\b/.test(lower)) {
|
|
572
|
+
return ROUTE_DOMAINS.find((domain) => domain.sectionId === "observed-ai-insights");
|
|
573
|
+
}
|
|
574
|
+
if (/\/(chat|messages?|messaging|conversations?|inbox|notifications?|alerts?)\b/.test(lower)) {
|
|
575
|
+
return ROUTE_DOMAINS.find((domain) => domain.sectionId === "observed-communications");
|
|
576
|
+
}
|
|
577
|
+
if (/\/(brand|brand-portal|assets?|media|gallery|uploads?)\b/.test(lower)) {
|
|
578
|
+
return ROUTE_DOMAINS.find((domain) => domain.sectionId === "observed-brand-portal");
|
|
579
|
+
}
|
|
580
|
+
if (/\/(content|cms|editor|posts?|articles?|catalog|library|registry|marketplace)\b/.test(lower)) {
|
|
581
|
+
return ROUTE_DOMAINS.find((domain) => domain.sectionId === "observed-content");
|
|
582
|
+
}
|
|
583
|
+
if (path === "/" || /\/(about|contact|pricing|blog|docs|legal|privacy|terms|marketing)\b/.test(lower)) {
|
|
584
|
+
return ROUTE_DOMAINS.find((domain) => domain.sectionId === "observed-public");
|
|
585
|
+
}
|
|
586
|
+
if (/\/(dashboard|overview|workspace|home)\b/.test(lower)) {
|
|
587
|
+
return ROUTE_DOMAINS.find((domain) => domain.sectionId === "observed-dashboard");
|
|
588
|
+
}
|
|
589
|
+
return ROUTE_DOMAINS.find((domain) => domain.sectionId === "observed-primary");
|
|
590
|
+
}
|
|
591
|
+
function platformForTarget(target, routeStrategy) {
|
|
592
|
+
const normalized = target.toLowerCase();
|
|
593
|
+
if (routeStrategy === "app-router" || routeStrategy === "pages-router" || routeStrategy === "mixed-next-router" || routeStrategy === "sveltekit-router" || routeStrategy === "nuxt-router") {
|
|
594
|
+
return { type: "ssr", routing: "pathname" };
|
|
595
|
+
}
|
|
596
|
+
if (normalized === "nextjs" || normalized === "nuxt" || normalized === "astro") {
|
|
597
|
+
return { type: "ssr", routing: "pathname" };
|
|
598
|
+
}
|
|
599
|
+
if (routeStrategy === "react-router") return { type: "spa", routing: "history" };
|
|
600
|
+
if (normalized === "html") return { type: "static", routing: "pathname" };
|
|
601
|
+
return { type: "spa", routing: "history" };
|
|
602
|
+
}
|
|
603
|
+
function featuresForSection(sectionId, features) {
|
|
604
|
+
const domain = ROUTE_DOMAINS.find((candidate) => candidate.sectionId === sectionId);
|
|
605
|
+
if (domain && domain.featureHints.length > 0) {
|
|
606
|
+
return features.filter((f) => domain.featureHints.includes(f));
|
|
607
|
+
}
|
|
608
|
+
return features;
|
|
609
|
+
}
|
|
610
|
+
function doctrineEffects(ambient) {
|
|
611
|
+
const effects = {};
|
|
612
|
+
if (ambient.summary["security-data"] > 0) {
|
|
613
|
+
effects["doctrine-security-data"] = "Preserve existing auth, middleware, schema, migration, RLS, and data-boundary rules unless the user explicitly approves a reviewed migration.";
|
|
614
|
+
}
|
|
615
|
+
if (ambient.summary["design-system"] > 0) {
|
|
616
|
+
effects["doctrine-design-system"] = "Preserve existing design-system tokens, component conventions, and styling framework unless the user explicitly opts into style migration.";
|
|
617
|
+
}
|
|
618
|
+
if (ambient.summary["workflow-ci"] > 0) {
|
|
619
|
+
effects["doctrine-workflow-ci"] = "Use existing package-manager, build, test, lint, deployment, and CI conventions as validation evidence.";
|
|
620
|
+
}
|
|
621
|
+
if (ambient.summary["feature-business"] > 0) {
|
|
622
|
+
effects["doctrine-feature-business"] = "Treat initiative, memory, and feature docs as business-domain evidence; verify stale risks before enforcing them.";
|
|
623
|
+
}
|
|
624
|
+
return Object.keys(effects).length > 0 ? effects : void 0;
|
|
625
|
+
}
|
|
626
|
+
function createBrownfieldProposal(input) {
|
|
627
|
+
const target = input.project.framework !== "unknown" ? input.project.framework : "generic-web";
|
|
628
|
+
const shell = input.layout.shellPattern || "observed-existing-shell";
|
|
629
|
+
const routeMap = {};
|
|
630
|
+
const sectionMap = /* @__PURE__ */ new Map();
|
|
631
|
+
const observedRoutes = input.routes.routes.length > 0 ? input.routes.routes : [{ path: "/", file: ".", hasLayout: false }];
|
|
632
|
+
for (const route of observedRoutes) {
|
|
633
|
+
const classified = routeDomain(route.path);
|
|
634
|
+
const section = sectionMap.get(classified.sectionId) ?? {
|
|
635
|
+
id: classified.sectionId,
|
|
636
|
+
role: classified.role,
|
|
637
|
+
shell,
|
|
638
|
+
features: featuresForSection(classified.sectionId, input.features.detected),
|
|
639
|
+
description: classified.description,
|
|
640
|
+
pages: [],
|
|
641
|
+
directives: [
|
|
642
|
+
`Semantic domain: ${classified.label}. Use this as an observed product-domain grouping, not a scaffold category.`,
|
|
643
|
+
"Preserve existing route files, layouts, data boundaries, and styling conventions unless the user explicitly approves a migration."
|
|
644
|
+
]
|
|
645
|
+
};
|
|
646
|
+
const pageId = route.path === "/" ? "home" : slugify(route.path, "observed-page");
|
|
647
|
+
if (!section.pages.some((page) => page.id === pageId)) {
|
|
648
|
+
section.pages.push({
|
|
649
|
+
id: pageId,
|
|
650
|
+
route: route.path,
|
|
651
|
+
layout: ["existing-surface"],
|
|
652
|
+
directives: [
|
|
653
|
+
`Observed source: ${route.file}. Treat this route as existing product surface, not scaffold territory.`
|
|
654
|
+
]
|
|
655
|
+
});
|
|
656
|
+
}
|
|
657
|
+
routeMap[route.path] = { section: section.id, page: pageId };
|
|
658
|
+
sectionMap.set(section.id, section);
|
|
659
|
+
}
|
|
660
|
+
const sections = [...sectionMap.values()].sort((a, b) => {
|
|
661
|
+
const aPriority = ROUTE_DOMAINS.find((domain) => domain.sectionId === a.id)?.priority ?? 99;
|
|
662
|
+
const bPriority = ROUTE_DOMAINS.find((domain) => domain.sectionId === b.id)?.priority ?? 99;
|
|
663
|
+
return aPriority - bPriority || a.id.localeCompare(b.id);
|
|
664
|
+
});
|
|
665
|
+
const essence = {
|
|
666
|
+
version: "3.1.0",
|
|
667
|
+
dna: {
|
|
668
|
+
theme: {
|
|
669
|
+
id: "existing",
|
|
670
|
+
mode: input.styling.darkMode ? "dark" : "auto",
|
|
671
|
+
shape: "rounded"
|
|
672
|
+
},
|
|
673
|
+
spacing: {
|
|
674
|
+
base_unit: 4,
|
|
675
|
+
scale: "observed",
|
|
676
|
+
density: "comfortable",
|
|
677
|
+
content_gap: "_gap4"
|
|
678
|
+
},
|
|
679
|
+
typography: {
|
|
680
|
+
scale: "observed",
|
|
681
|
+
heading_weight: 600,
|
|
682
|
+
body_weight: 400
|
|
683
|
+
},
|
|
684
|
+
color: {
|
|
685
|
+
palette: input.styling.approach === "unknown" ? "observed" : input.styling.approach,
|
|
686
|
+
accent_count: Math.max(1, Object.keys(input.styling.colors).length),
|
|
687
|
+
cvd_preference: "auto"
|
|
688
|
+
},
|
|
689
|
+
radius: {
|
|
690
|
+
philosophy: "observed",
|
|
691
|
+
base: 8
|
|
692
|
+
},
|
|
693
|
+
elevation: {
|
|
694
|
+
system: "observed",
|
|
695
|
+
max_levels: 3
|
|
696
|
+
},
|
|
697
|
+
motion: {
|
|
698
|
+
preference: input.dependencies.ui.includes("framer-motion") ? "expressive" : "subtle",
|
|
699
|
+
duration_scale: 1,
|
|
700
|
+
reduce_motion: true
|
|
701
|
+
},
|
|
702
|
+
accessibility: {
|
|
703
|
+
wcag_level: "AA",
|
|
704
|
+
focus_visible: true,
|
|
705
|
+
skip_nav: true
|
|
706
|
+
},
|
|
707
|
+
personality: ["observed brownfield product"],
|
|
708
|
+
constraints: {
|
|
709
|
+
mode: "existing project wins",
|
|
710
|
+
typography: "derive from existing docs, CSS variables, and component conventions",
|
|
711
|
+
borders: "derive from existing design system",
|
|
712
|
+
corners: "derive from existing design system",
|
|
713
|
+
shadows: "derive from existing design system",
|
|
714
|
+
effects: doctrineEffects(input.ambient)
|
|
715
|
+
}
|
|
716
|
+
},
|
|
717
|
+
blueprint: {
|
|
718
|
+
sections,
|
|
719
|
+
features: input.features.detected,
|
|
720
|
+
routes: routeMap
|
|
721
|
+
},
|
|
722
|
+
meta: {
|
|
723
|
+
archetype: "observed-brownfield",
|
|
724
|
+
target,
|
|
725
|
+
platform: platformForTarget(target, input.routes.strategy),
|
|
726
|
+
guard: {
|
|
727
|
+
mode: "guided",
|
|
728
|
+
dna_enforcement: "error",
|
|
729
|
+
blueprint_enforcement: "warn",
|
|
730
|
+
interactions_enforcement: "warn"
|
|
731
|
+
},
|
|
732
|
+
navigation: {
|
|
733
|
+
command_palette: false
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
};
|
|
737
|
+
return {
|
|
738
|
+
version: 1,
|
|
739
|
+
kind: "brownfield-observed-essence",
|
|
740
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
741
|
+
status: "proposed",
|
|
742
|
+
essence,
|
|
743
|
+
evidence: {
|
|
744
|
+
routeCount: input.routes.routes.length,
|
|
745
|
+
componentCount: input.components.componentCount,
|
|
746
|
+
featureCount: input.features.detected.length,
|
|
747
|
+
ambientContextCount: input.ambient.items.length,
|
|
748
|
+
stylingApproach: input.styling.approach,
|
|
749
|
+
shell,
|
|
750
|
+
semanticSectionCount: sections.length
|
|
751
|
+
},
|
|
752
|
+
conflicts: input.ambient.conflicts,
|
|
753
|
+
staleRisks: input.ambient.staleRisks,
|
|
754
|
+
acceptance: {
|
|
755
|
+
create: "decantr init --existing --accept-proposal",
|
|
756
|
+
merge: "decantr init --existing --merge-proposal",
|
|
757
|
+
replace: "decantr init --existing --replace-essence"
|
|
758
|
+
}
|
|
759
|
+
};
|
|
760
|
+
}
|
|
761
|
+
function proposalPath(projectRoot) {
|
|
762
|
+
return join4(projectRoot, ".decantr", "observed-essence.proposal.json");
|
|
763
|
+
}
|
|
764
|
+
function writeBrownfieldProposal(projectRoot, proposal) {
|
|
765
|
+
const decantrDir = join4(projectRoot, ".decantr");
|
|
766
|
+
mkdirSync3(decantrDir, { recursive: true });
|
|
767
|
+
writeFileSync4(proposalPath(projectRoot), JSON.stringify(proposal, null, 2) + "\n", "utf-8");
|
|
768
|
+
}
|
|
769
|
+
function readBrownfieldProposal(projectRoot) {
|
|
770
|
+
const path = proposalPath(projectRoot);
|
|
771
|
+
if (!existsSync4(path)) return null;
|
|
772
|
+
try {
|
|
773
|
+
const parsed = JSON.parse(readFileSync4(path, "utf-8"));
|
|
774
|
+
if (parsed.kind !== "brownfield-observed-essence" || !isV32(parsed.essence)) return null;
|
|
775
|
+
return parsed;
|
|
776
|
+
} catch {
|
|
777
|
+
return null;
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
function generateBrownfieldReport(proposal, ambient, doctrine) {
|
|
781
|
+
const lines = [];
|
|
782
|
+
lines.push("# Decantr Brownfield Report");
|
|
783
|
+
lines.push("");
|
|
784
|
+
lines.push("Decantr analyzed this app as an existing product. The proposal below is observed from the codebase and ambient project doctrine; it is not a Decantr scaffold.");
|
|
785
|
+
lines.push("");
|
|
786
|
+
lines.push("## Proposal");
|
|
787
|
+
lines.push("");
|
|
788
|
+
lines.push(`- Routes observed: ${proposal.evidence.routeCount}`);
|
|
789
|
+
lines.push(`- Semantic sections inferred: ${proposal.evidence.semanticSectionCount}`);
|
|
790
|
+
lines.push(`- Components observed: ${proposal.evidence.componentCount}`);
|
|
791
|
+
lines.push(`- Features observed: ${proposal.evidence.featureCount}`);
|
|
792
|
+
lines.push(`- Styling approach: ${proposal.evidence.stylingApproach}`);
|
|
793
|
+
lines.push(`- Shell signal: ${proposal.evidence.shell}`);
|
|
794
|
+
lines.push(`- Ambient context items: ${proposal.evidence.ambientContextCount}`);
|
|
795
|
+
lines.push(`- Theme posture: existing project wins`);
|
|
796
|
+
if (doctrine) {
|
|
797
|
+
lines.push(`- Doctrine sources ranked: ${doctrine.sources.length}`);
|
|
798
|
+
lines.push(`- Highest precedence source: ${doctrine.sources[0]?.path ?? "none"}`);
|
|
799
|
+
}
|
|
800
|
+
lines.push("");
|
|
801
|
+
lines.push("## Immediate Value");
|
|
802
|
+
lines.push("");
|
|
803
|
+
lines.push("- Converts scattered brownfield routes, styling signals, docs, rules, schemas, and CI evidence into one Decantr contract without scaffolding runtime code.");
|
|
804
|
+
lines.push("- Gives assistants a compiled contract layer while keeping original docs/rules available as cited evidence.");
|
|
805
|
+
lines.push("- Enables `decantr check --brownfield` to catch route drift, unsafe defaults, doctrine conflicts, and missing context.");
|
|
806
|
+
lines.push("");
|
|
807
|
+
lines.push("## Non-Goals By Default");
|
|
808
|
+
lines.push("");
|
|
809
|
+
lines.push("- Does not install Decantr CSS, switch themes, replace layouts, rewrite docs, mutate assistant rules, or import registry patterns unless explicitly requested.");
|
|
810
|
+
lines.push("- Does not treat stale migration or completion summaries as current doctrine without verification.");
|
|
811
|
+
lines.push("");
|
|
812
|
+
lines.push("## Accepted Evidence");
|
|
813
|
+
lines.push("");
|
|
814
|
+
lines.push("- Existing framework, routes, layout shell, feature names, styling signals, and ambient doctrine were used as evidence for the proposal.");
|
|
815
|
+
lines.push("- Decantr registry content, Decantr CSS, and default Decantr themes were not accepted as brownfield defaults.");
|
|
816
|
+
lines.push("");
|
|
817
|
+
lines.push("## Uncertain Evidence");
|
|
818
|
+
lines.push("");
|
|
819
|
+
const uncertain = [];
|
|
820
|
+
if (proposal.evidence.routeCount === 0) uncertain.push("No explicit route declarations were found; proposal uses `/` as a placeholder observation.");
|
|
821
|
+
if (proposal.evidence.stylingApproach === "unknown") uncertain.push("Styling approach was not confidently detected.");
|
|
822
|
+
if (proposal.evidence.ambientContextCount === 0) uncertain.push("No ambient docs, rules, CI, schema, or project-memory files were detected.");
|
|
823
|
+
if (uncertain.length === 0) {
|
|
824
|
+
lines.push("- No major uncertainty detected by the first-pass scanners.");
|
|
825
|
+
} else {
|
|
826
|
+
for (const item of uncertain) lines.push(`- ${item}`);
|
|
827
|
+
}
|
|
828
|
+
lines.push("");
|
|
829
|
+
lines.push("## Acceptance");
|
|
830
|
+
lines.push("");
|
|
831
|
+
lines.push(`- Create initial essence: \`${proposal.acceptance.create}\``);
|
|
832
|
+
lines.push(`- Merge into existing essence: \`${proposal.acceptance.merge}\``);
|
|
833
|
+
lines.push(`- Replace existing essence: \`${proposal.acceptance.replace}\``);
|
|
834
|
+
lines.push("");
|
|
835
|
+
lines.push("## Ambient Context Summary");
|
|
836
|
+
lines.push("");
|
|
837
|
+
for (const [role, count] of Object.entries(ambient.summary)) {
|
|
838
|
+
if (count > 0) lines.push(`- ${role}: ${count}`);
|
|
839
|
+
}
|
|
840
|
+
lines.push("");
|
|
841
|
+
if (doctrine) {
|
|
842
|
+
lines.push("## Doctrine Precedence");
|
|
843
|
+
lines.push("");
|
|
844
|
+
for (const source of doctrine.sources.slice(0, 12)) {
|
|
845
|
+
lines.push(
|
|
846
|
+
`- ${source.path}: ${source.area}, precedence ${source.precedence}, ${source.currency}`
|
|
847
|
+
);
|
|
848
|
+
}
|
|
849
|
+
if (doctrine.sources.length > 12) {
|
|
850
|
+
lines.push(`- +${doctrine.sources.length - 12} more source(s) in .decantr/doctrine-map.json`);
|
|
851
|
+
}
|
|
852
|
+
lines.push("");
|
|
853
|
+
lines.push("## Doctrine Resolution Suggestions");
|
|
854
|
+
lines.push("");
|
|
855
|
+
if (doctrine.resolutions.length === 0) {
|
|
856
|
+
lines.push("- No doctrine resolution suggestions were needed.");
|
|
857
|
+
} else {
|
|
858
|
+
for (const resolution of doctrine.resolutions.slice(0, 8)) {
|
|
859
|
+
lines.push(`- ${resolution.issue} ${resolution.recommendation}`);
|
|
860
|
+
if (resolution.preferredSources.length > 0) {
|
|
861
|
+
lines.push(` Preferred evidence: ${resolution.preferredSources.join(", ")}`);
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
if (doctrine.resolutions.length > 8) {
|
|
865
|
+
lines.push(`- +${doctrine.resolutions.length - 8} more resolution suggestion(s) in .decantr/doctrine-map.json`);
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
lines.push("");
|
|
869
|
+
}
|
|
870
|
+
lines.push("## Notable Context Evidence");
|
|
871
|
+
lines.push("");
|
|
872
|
+
const notable = ambient.items.slice(0, 30);
|
|
873
|
+
if (notable.length === 0) {
|
|
874
|
+
lines.push("- None detected.");
|
|
875
|
+
} else {
|
|
876
|
+
for (const item of notable) {
|
|
877
|
+
const cite = item.safeToCite ? "safe to cite" : "do not cite directly";
|
|
878
|
+
lines.push(`- ${item.path} (${item.role}, ${item.type}, ${cite}, confidence ${item.confidence.toFixed(2)})`);
|
|
879
|
+
}
|
|
880
|
+
if (ambient.items.length > notable.length) {
|
|
881
|
+
lines.push(`- +${ambient.items.length - notable.length} more item(s) in .decantr/ambient-context.json`);
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
lines.push("");
|
|
885
|
+
lines.push("## Conflicts And Stale Risks");
|
|
886
|
+
lines.push("");
|
|
887
|
+
if (proposal.conflicts.length === 0 && proposal.staleRisks.length === 0) {
|
|
888
|
+
lines.push("- No obvious doctrine conflicts or stale-document risks were detected.");
|
|
889
|
+
} else {
|
|
890
|
+
for (const conflict of proposal.conflicts) lines.push(`- Conflict: ${conflict}`);
|
|
891
|
+
for (const risk of proposal.staleRisks.slice(0, 8)) lines.push(`- Stale risk: ${risk}`);
|
|
892
|
+
}
|
|
893
|
+
lines.push("");
|
|
894
|
+
lines.push("## Assistant Posture");
|
|
895
|
+
lines.push("");
|
|
896
|
+
lines.push("LLMs should treat Decantr as the compiled contract layer and the original files as cited evidence. Do not migrate, rewrite, or delete existing docs/rules unless the user explicitly asks for doctrine migration.");
|
|
897
|
+
lines.push("");
|
|
898
|
+
return `${lines.join("\n")}
|
|
899
|
+
`;
|
|
900
|
+
}
|
|
901
|
+
function mergeEssenceWithProposal(existing, proposal) {
|
|
902
|
+
const next = JSON.parse(JSON.stringify(existing));
|
|
903
|
+
const proposed = proposal.essence;
|
|
904
|
+
next.version = "3.1.0";
|
|
905
|
+
next.blueprint.features = [
|
|
906
|
+
.../* @__PURE__ */ new Set([...next.blueprint.features ?? [], ...proposed.blueprint.features ?? []])
|
|
907
|
+
];
|
|
908
|
+
next.blueprint.routes = {
|
|
909
|
+
...proposed.blueprint.routes ?? {},
|
|
910
|
+
...next.blueprint.routes ?? {}
|
|
911
|
+
};
|
|
912
|
+
const existingSections = new Map((next.blueprint.sections ?? []).map((section) => [section.id, section]));
|
|
913
|
+
for (const proposedSection of proposed.blueprint.sections ?? []) {
|
|
914
|
+
const current = existingSections.get(proposedSection.id);
|
|
915
|
+
if (!current) {
|
|
916
|
+
existingSections.set(proposedSection.id, proposedSection);
|
|
917
|
+
continue;
|
|
918
|
+
}
|
|
919
|
+
const pageIds = new Set(current.pages.map((page) => page.id));
|
|
920
|
+
current.pages.push(...proposedSection.pages.filter((page) => !pageIds.has(page.id)));
|
|
921
|
+
current.features = [.../* @__PURE__ */ new Set([...current.features ?? [], ...proposedSection.features ?? []])];
|
|
922
|
+
}
|
|
923
|
+
next.blueprint.sections = [...existingSections.values()];
|
|
924
|
+
delete next.blueprint.pages;
|
|
925
|
+
delete next.blueprint.shell;
|
|
926
|
+
return next;
|
|
927
|
+
}
|
|
928
|
+
|
|
374
929
|
// src/commands/analyze.ts
|
|
375
|
-
import { existsSync as
|
|
376
|
-
import { join as
|
|
930
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync4, writeFileSync as writeFileSync5 } from "fs";
|
|
931
|
+
import { join as join11 } from "path";
|
|
377
932
|
|
|
378
933
|
// src/analyzers/components.ts
|
|
379
|
-
import { existsSync as
|
|
380
|
-
import { join as
|
|
934
|
+
import { existsSync as existsSync5, readdirSync, statSync as statSync2 } from "fs";
|
|
935
|
+
import { join as join5 } from "path";
|
|
381
936
|
var PAGE_EXTENSIONS = /* @__PURE__ */ new Set([".tsx", ".ts", ".jsx", ".js"]);
|
|
382
937
|
var ROOT_COMPONENT_CANDIDATES = [
|
|
383
938
|
"src/App.tsx",
|
|
@@ -399,7 +954,7 @@ function countFilesRecursive(dir, extensions) {
|
|
|
399
954
|
}
|
|
400
955
|
for (const entry of entries) {
|
|
401
956
|
if (entry.startsWith(".") || entry === "node_modules") continue;
|
|
402
|
-
const fullPath =
|
|
957
|
+
const fullPath = join5(dir, entry);
|
|
403
958
|
try {
|
|
404
959
|
const stat = statSync2(fullPath);
|
|
405
960
|
if (stat.isDirectory()) {
|
|
@@ -425,7 +980,7 @@ function countPageFiles(dir) {
|
|
|
425
980
|
}
|
|
426
981
|
for (const entry of entries) {
|
|
427
982
|
if (entry.startsWith(".") || entry === "node_modules") continue;
|
|
428
|
-
const fullPath =
|
|
983
|
+
const fullPath = join5(dir, entry);
|
|
429
984
|
try {
|
|
430
985
|
const stat = statSync2(fullPath);
|
|
431
986
|
if (stat.isDirectory()) {
|
|
@@ -443,27 +998,27 @@ function countPageFiles(dir) {
|
|
|
443
998
|
function scanComponents(projectRoot) {
|
|
444
999
|
let pageCount = 0;
|
|
445
1000
|
const componentDirs = [];
|
|
446
|
-
const appDirs = [
|
|
1001
|
+
const appDirs = [join5(projectRoot, "src", "app"), join5(projectRoot, "app")];
|
|
447
1002
|
for (const dir of appDirs) {
|
|
448
|
-
if (
|
|
1003
|
+
if (existsSync5(dir)) {
|
|
449
1004
|
pageCount += countPageFiles(dir);
|
|
450
1005
|
}
|
|
451
1006
|
}
|
|
452
|
-
const pagesDirs = [
|
|
1007
|
+
const pagesDirs = [join5(projectRoot, "src", "pages"), join5(projectRoot, "pages")];
|
|
453
1008
|
for (const dir of pagesDirs) {
|
|
454
|
-
if (
|
|
1009
|
+
if (existsSync5(dir)) {
|
|
455
1010
|
pageCount += countFilesRecursive(dir, PAGE_EXTENSIONS);
|
|
456
1011
|
}
|
|
457
1012
|
}
|
|
458
1013
|
let componentCount = 0;
|
|
459
1014
|
const componentPaths = [
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
1015
|
+
join5(projectRoot, "src", "components"),
|
|
1016
|
+
join5(projectRoot, "components"),
|
|
1017
|
+
join5(projectRoot, "src", "ui"),
|
|
1018
|
+
join5(projectRoot, "ui")
|
|
464
1019
|
];
|
|
465
1020
|
for (const dir of componentPaths) {
|
|
466
|
-
if (
|
|
1021
|
+
if (existsSync5(dir)) {
|
|
467
1022
|
const count = countFilesRecursive(dir, PAGE_EXTENSIONS);
|
|
468
1023
|
if (count > 0) {
|
|
469
1024
|
componentCount += count;
|
|
@@ -473,7 +1028,7 @@ function scanComponents(projectRoot) {
|
|
|
473
1028
|
}
|
|
474
1029
|
}
|
|
475
1030
|
const hasRootAppComponent = ROOT_COMPONENT_CANDIDATES.some(
|
|
476
|
-
(relativePath) =>
|
|
1031
|
+
(relativePath) => existsSync5(join5(projectRoot, relativePath))
|
|
477
1032
|
);
|
|
478
1033
|
if (pageCount === 0 && hasRootAppComponent) {
|
|
479
1034
|
pageCount = 1;
|
|
@@ -492,8 +1047,8 @@ function scanComponents(projectRoot) {
|
|
|
492
1047
|
}
|
|
493
1048
|
|
|
494
1049
|
// src/analyzers/dependencies.ts
|
|
495
|
-
import { existsSync as
|
|
496
|
-
import { join as
|
|
1050
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
|
|
1051
|
+
import { join as join6 } from "path";
|
|
497
1052
|
var CATEGORIES = {
|
|
498
1053
|
ui: [
|
|
499
1054
|
"react",
|
|
@@ -615,6 +1170,8 @@ var CATEGORIES = {
|
|
|
615
1170
|
"@emotion/react",
|
|
616
1171
|
"@emotion/styled",
|
|
617
1172
|
"styled-jsx",
|
|
1173
|
+
"bootstrap",
|
|
1174
|
+
"react-bootstrap",
|
|
618
1175
|
"linaria",
|
|
619
1176
|
"vanilla-extract",
|
|
620
1177
|
"clsx",
|
|
@@ -643,11 +1200,11 @@ function scanDependencies(projectRoot) {
|
|
|
643
1200
|
styling: [],
|
|
644
1201
|
other: []
|
|
645
1202
|
};
|
|
646
|
-
const pkgPath =
|
|
647
|
-
if (!
|
|
1203
|
+
const pkgPath = join6(projectRoot, "package.json");
|
|
1204
|
+
if (!existsSync6(pkgPath)) return result;
|
|
648
1205
|
let pkg;
|
|
649
1206
|
try {
|
|
650
|
-
pkg = JSON.parse(
|
|
1207
|
+
pkg = JSON.parse(readFileSync5(pkgPath, "utf-8"));
|
|
651
1208
|
} catch {
|
|
652
1209
|
return result;
|
|
653
1210
|
}
|
|
@@ -667,8 +1224,8 @@ function scanDependencies(projectRoot) {
|
|
|
667
1224
|
}
|
|
668
1225
|
|
|
669
1226
|
// src/analyzers/features.ts
|
|
670
|
-
import { existsSync as
|
|
671
|
-
import { join as
|
|
1227
|
+
import { existsSync as existsSync7, readdirSync as readdirSync2, statSync as statSync3 } from "fs";
|
|
1228
|
+
import { join as join7 } from "path";
|
|
672
1229
|
var FEATURE_PATTERNS = {
|
|
673
1230
|
auth: [
|
|
674
1231
|
"login",
|
|
@@ -721,7 +1278,7 @@ function collectPaths(dir, baseDir, depth = 0) {
|
|
|
721
1278
|
for (const entry of entries) {
|
|
722
1279
|
if (entry.startsWith(".") || entry.startsWith("_") || entry === "node_modules" || entry === "api")
|
|
723
1280
|
continue;
|
|
724
|
-
const fullPath =
|
|
1281
|
+
const fullPath = join7(dir, entry);
|
|
725
1282
|
const relPath = fullPath.slice(baseDir.length + 1);
|
|
726
1283
|
paths.push(relPath);
|
|
727
1284
|
try {
|
|
@@ -737,16 +1294,18 @@ function scanFeatures(projectRoot) {
|
|
|
737
1294
|
const detected = [];
|
|
738
1295
|
const evidence = {};
|
|
739
1296
|
const scanDirs = [
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
1297
|
+
join7(projectRoot, "src", "app"),
|
|
1298
|
+
join7(projectRoot, "app"),
|
|
1299
|
+
join7(projectRoot, "src", "routes"),
|
|
1300
|
+
join7(projectRoot, "src", "pages"),
|
|
1301
|
+
join7(projectRoot, "pages"),
|
|
1302
|
+
join7(projectRoot, "src", "views"),
|
|
1303
|
+
join7(projectRoot, "src", "components"),
|
|
1304
|
+
join7(projectRoot, "components")
|
|
746
1305
|
];
|
|
747
1306
|
const allPaths = [];
|
|
748
1307
|
for (const dir of scanDirs) {
|
|
749
|
-
if (
|
|
1308
|
+
if (existsSync7(dir)) {
|
|
750
1309
|
allPaths.push(...collectPaths(dir, projectRoot));
|
|
751
1310
|
}
|
|
752
1311
|
}
|
|
@@ -770,8 +1329,8 @@ function scanFeatures(projectRoot) {
|
|
|
770
1329
|
}
|
|
771
1330
|
|
|
772
1331
|
// src/analyzers/layout.ts
|
|
773
|
-
import { existsSync as
|
|
774
|
-
import { join as
|
|
1332
|
+
import { existsSync as existsSync8, readdirSync as readdirSync3, readFileSync as readFileSync6 } from "fs";
|
|
1333
|
+
import { join as join8 } from "path";
|
|
775
1334
|
var SIDEBAR_PATTERNS = ["sidebar", "side-bar", "sidenav", "side-nav", "drawer", "aside"];
|
|
776
1335
|
var NAV_PATTERNS = ["nav", "navbar", "header", "top-bar", "topbar", "app-bar", "appbar"];
|
|
777
1336
|
var FOOTER_PATTERNS = ["footer", "bottom-bar", "bottombar"];
|
|
@@ -782,12 +1341,12 @@ function containsPattern(text, patterns) {
|
|
|
782
1341
|
function checkComponentDirs(projectRoot) {
|
|
783
1342
|
const result = { sidebar: false, nav: false, footer: false };
|
|
784
1343
|
const componentDirs = [
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
1344
|
+
join8(projectRoot, "src", "components"),
|
|
1345
|
+
join8(projectRoot, "components"),
|
|
1346
|
+
join8(projectRoot, "src", "ui")
|
|
788
1347
|
];
|
|
789
1348
|
for (const dir of componentDirs) {
|
|
790
|
-
if (!
|
|
1349
|
+
if (!existsSync8(dir)) continue;
|
|
791
1350
|
let entries;
|
|
792
1351
|
try {
|
|
793
1352
|
entries = readdirSync3(dir);
|
|
@@ -806,16 +1365,16 @@ function checkComponentDirs(projectRoot) {
|
|
|
806
1365
|
function checkLayoutFiles(projectRoot) {
|
|
807
1366
|
const result = { sidebar: false, nav: false, footer: false };
|
|
808
1367
|
const layoutPaths = [
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
1368
|
+
join8(projectRoot, "src", "app", "layout.tsx"),
|
|
1369
|
+
join8(projectRoot, "src", "app", "layout.jsx"),
|
|
1370
|
+
join8(projectRoot, "app", "layout.tsx"),
|
|
1371
|
+
join8(projectRoot, "app", "layout.jsx")
|
|
813
1372
|
];
|
|
814
1373
|
for (const layoutPath of layoutPaths) {
|
|
815
|
-
if (!
|
|
1374
|
+
if (!existsSync8(layoutPath)) continue;
|
|
816
1375
|
let content;
|
|
817
1376
|
try {
|
|
818
|
-
content =
|
|
1377
|
+
content = readFileSync6(layoutPath, "utf-8");
|
|
819
1378
|
} catch {
|
|
820
1379
|
continue;
|
|
821
1380
|
}
|
|
@@ -826,16 +1385,16 @@ function checkLayoutFiles(projectRoot) {
|
|
|
826
1385
|
const subLayoutDirs = ["dashboard", "admin", "app"];
|
|
827
1386
|
for (const sub of subLayoutDirs) {
|
|
828
1387
|
const paths = [
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
1388
|
+
join8(projectRoot, "src", "app", sub, "layout.tsx"),
|
|
1389
|
+
join8(projectRoot, "src", "app", sub, "layout.jsx"),
|
|
1390
|
+
join8(projectRoot, "app", sub, "layout.tsx"),
|
|
1391
|
+
join8(projectRoot, "app", sub, "layout.jsx")
|
|
833
1392
|
];
|
|
834
1393
|
for (const layoutPath of paths) {
|
|
835
|
-
if (!
|
|
1394
|
+
if (!existsSync8(layoutPath)) continue;
|
|
836
1395
|
let content;
|
|
837
1396
|
try {
|
|
838
|
-
content =
|
|
1397
|
+
content = readFileSync6(layoutPath, "utf-8");
|
|
839
1398
|
} catch {
|
|
840
1399
|
continue;
|
|
841
1400
|
}
|
|
@@ -857,10 +1416,10 @@ function inferShellPattern(hasSidebar, hasTopNav, hasFooter) {
|
|
|
857
1416
|
return "main-only";
|
|
858
1417
|
}
|
|
859
1418
|
function inferShellPatternFromDecantrContract(projectRoot) {
|
|
860
|
-
const essencePath =
|
|
861
|
-
if (!
|
|
1419
|
+
const essencePath = join8(projectRoot, "decantr.essence.json");
|
|
1420
|
+
if (!existsSync8(essencePath)) return null;
|
|
862
1421
|
try {
|
|
863
|
-
const essence = JSON.parse(
|
|
1422
|
+
const essence = JSON.parse(readFileSync6(essencePath, "utf-8"));
|
|
864
1423
|
const sectionShells = essence.blueprint?.sections?.map((section) => section.shell).filter((shell) => typeof shell === "string" && shell.length > 0) ?? [];
|
|
865
1424
|
if (sectionShells.length === 0 && essence.blueprint?.shell) {
|
|
866
1425
|
return `${essence.blueprint.shell} (contract)`;
|
|
@@ -895,375 +1454,19 @@ function scanLayout(projectRoot) {
|
|
|
895
1454
|
};
|
|
896
1455
|
}
|
|
897
1456
|
|
|
898
|
-
// src/
|
|
899
|
-
import { existsSync as existsSync8, readdirSync as readdirSync4, readFileSync as readFileSync6, statSync as statSync4 } from "fs";
|
|
900
|
-
import { join as join8, relative } from "path";
|
|
901
|
-
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".next", ".git", "api", "_app", "_document"]);
|
|
902
|
-
function shouldSkipDir(name) {
|
|
903
|
-
return name.startsWith("_") || name.startsWith(".") || SKIP_DIRS.has(name);
|
|
904
|
-
}
|
|
905
|
-
function segmentToRoute(segment) {
|
|
906
|
-
if (segment.startsWith("(") && segment.endsWith(")")) {
|
|
907
|
-
return null;
|
|
908
|
-
}
|
|
909
|
-
if (segment.startsWith("[") && segment.endsWith("]")) {
|
|
910
|
-
const param = segment.slice(1, -1);
|
|
911
|
-
if (param.startsWith("...")) {
|
|
912
|
-
return `:${param.slice(3)}*`;
|
|
913
|
-
}
|
|
914
|
-
if (param.startsWith("[...") && param.endsWith("]")) {
|
|
915
|
-
return `:${param.slice(4, -1)}*`;
|
|
916
|
-
}
|
|
917
|
-
return `:${param}`;
|
|
918
|
-
}
|
|
919
|
-
return segment;
|
|
920
|
-
}
|
|
921
|
-
function walkAppDir(dir, baseDir, segments) {
|
|
922
|
-
const routes = [];
|
|
923
|
-
let entries;
|
|
924
|
-
try {
|
|
925
|
-
entries = readdirSync4(dir);
|
|
926
|
-
} catch {
|
|
927
|
-
return routes;
|
|
928
|
-
}
|
|
929
|
-
const hasPage = entries.some(
|
|
930
|
-
(e) => e === "page.tsx" || e === "page.ts" || e === "page.jsx" || e === "page.js"
|
|
931
|
-
);
|
|
932
|
-
const hasLayout = entries.some(
|
|
933
|
-
(e) => e === "layout.tsx" || e === "layout.ts" || e === "layout.jsx" || e === "layout.js"
|
|
934
|
-
);
|
|
935
|
-
if (hasPage) {
|
|
936
|
-
const routePath = "/" + segments.filter((s) => s !== "").join("/");
|
|
937
|
-
const pageFile = entries.find((e) => e.startsWith("page."));
|
|
938
|
-
routes.push({
|
|
939
|
-
path: routePath || "/",
|
|
940
|
-
file: relative(baseDir, join8(dir, pageFile)),
|
|
941
|
-
hasLayout
|
|
942
|
-
});
|
|
943
|
-
}
|
|
944
|
-
for (const entry of entries) {
|
|
945
|
-
if (shouldSkipDir(entry)) continue;
|
|
946
|
-
const fullPath = join8(dir, entry);
|
|
947
|
-
try {
|
|
948
|
-
if (!statSync4(fullPath).isDirectory()) continue;
|
|
949
|
-
} catch {
|
|
950
|
-
continue;
|
|
951
|
-
}
|
|
952
|
-
const routeSegment = segmentToRoute(entry);
|
|
953
|
-
const nextSegments = routeSegment === null ? [...segments] : [...segments, routeSegment];
|
|
954
|
-
routes.push(...walkAppDir(fullPath, baseDir, nextSegments));
|
|
955
|
-
}
|
|
956
|
-
return routes;
|
|
957
|
-
}
|
|
958
|
-
function walkPagesDir(dir, baseDir, segments) {
|
|
959
|
-
const routes = [];
|
|
960
|
-
let entries;
|
|
961
|
-
try {
|
|
962
|
-
entries = readdirSync4(dir);
|
|
963
|
-
} catch {
|
|
964
|
-
return routes;
|
|
965
|
-
}
|
|
966
|
-
for (const entry of entries) {
|
|
967
|
-
if (shouldSkipDir(entry)) continue;
|
|
968
|
-
const fullPath = join8(dir, entry);
|
|
969
|
-
try {
|
|
970
|
-
const stat = statSync4(fullPath);
|
|
971
|
-
if (stat.isDirectory()) {
|
|
972
|
-
const routeSegment = segmentToRoute(entry);
|
|
973
|
-
const nextSegments = routeSegment === null ? [...segments] : [...segments, routeSegment];
|
|
974
|
-
routes.push(...walkPagesDir(fullPath, baseDir, nextSegments));
|
|
975
|
-
} else if (stat.isFile()) {
|
|
976
|
-
const match = entry.match(/^(.+)\.(tsx?|jsx?|mdx?)$/);
|
|
977
|
-
if (!match) continue;
|
|
978
|
-
const name = match[1];
|
|
979
|
-
if (name.startsWith("_")) continue;
|
|
980
|
-
const routeSegment = name === "index" ? "" : segmentToRoute(name) ?? name;
|
|
981
|
-
const routePath = "/" + [...segments, routeSegment].filter((s) => s !== "").join("/");
|
|
982
|
-
routes.push({
|
|
983
|
-
path: routePath || "/",
|
|
984
|
-
file: relative(baseDir, fullPath),
|
|
985
|
-
hasLayout: false
|
|
986
|
-
});
|
|
987
|
-
}
|
|
988
|
-
} catch {
|
|
989
|
-
}
|
|
990
|
-
}
|
|
991
|
-
return routes;
|
|
992
|
-
}
|
|
993
|
-
var ROUTER_FILE_EXTENSIONS = /* @__PURE__ */ new Set([".tsx", ".ts", ".jsx", ".js"]);
|
|
994
|
-
function collectRouteCandidateFiles(dir, files, depth = 0) {
|
|
995
|
-
if (depth > 5) return;
|
|
996
|
-
let entries;
|
|
997
|
-
try {
|
|
998
|
-
entries = readdirSync4(dir);
|
|
999
|
-
} catch {
|
|
1000
|
-
return;
|
|
1001
|
-
}
|
|
1002
|
-
for (const entry of entries) {
|
|
1003
|
-
if (entry.startsWith(".") || entry === "node_modules") continue;
|
|
1004
|
-
const fullPath = join8(dir, entry);
|
|
1005
|
-
try {
|
|
1006
|
-
const stat = statSync4(fullPath);
|
|
1007
|
-
if (stat.isDirectory()) {
|
|
1008
|
-
collectRouteCandidateFiles(fullPath, files, depth + 1);
|
|
1009
|
-
} else if (stat.isFile()) {
|
|
1010
|
-
const ext = entry.slice(entry.lastIndexOf("."));
|
|
1011
|
-
if (ROUTER_FILE_EXTENSIONS.has(ext)) {
|
|
1012
|
-
files.push(fullPath);
|
|
1013
|
-
}
|
|
1014
|
-
}
|
|
1015
|
-
} catch {
|
|
1016
|
-
}
|
|
1017
|
-
}
|
|
1018
|
-
}
|
|
1019
|
-
function scanReactRouter(projectRoot) {
|
|
1020
|
-
const candidateDirs = [join8(projectRoot, "src"), projectRoot];
|
|
1021
|
-
const candidateFiles = [];
|
|
1022
|
-
for (const dir of candidateDirs) {
|
|
1023
|
-
if (existsSync8(dir)) collectRouteCandidateFiles(dir, candidateFiles);
|
|
1024
|
-
}
|
|
1025
|
-
const routeMap = /* @__PURE__ */ new Map();
|
|
1026
|
-
for (const absolutePath of candidateFiles) {
|
|
1027
|
-
let content;
|
|
1028
|
-
try {
|
|
1029
|
-
content = readFileSync6(absolutePath, "utf-8");
|
|
1030
|
-
} catch {
|
|
1031
|
-
continue;
|
|
1032
|
-
}
|
|
1033
|
-
const isReactRouterFile = content.includes("react-router-dom") || content.includes("react-router") || content.includes("<Routes") || content.includes("createBrowserRouter") || content.includes("createHashRouter") || content.includes("RouterProvider") || content.includes("HashRouter") || content.includes("BrowserRouter");
|
|
1034
|
-
if (!isReactRouterFile) continue;
|
|
1035
|
-
const relativePath = relative(projectRoot, absolutePath);
|
|
1036
|
-
const pathMatches = /* @__PURE__ */ new Set();
|
|
1037
|
-
for (const match of content.matchAll(/<Route\b[^>]*\bpath=["'`]([^"'`]+)["'`]/g)) {
|
|
1038
|
-
pathMatches.add(match[1]);
|
|
1039
|
-
}
|
|
1040
|
-
for (const match of content.matchAll(/\bpath\s*:\s*["'`]([^"'`]+)["'`]/g)) {
|
|
1041
|
-
pathMatches.add(match[1]);
|
|
1042
|
-
}
|
|
1043
|
-
if (pathMatches.size === 0 && (content.includes("<Routes") || content.includes("RouterProvider"))) {
|
|
1044
|
-
pathMatches.add("/");
|
|
1045
|
-
}
|
|
1046
|
-
for (const path of pathMatches) {
|
|
1047
|
-
if (!routeMap.has(path)) {
|
|
1048
|
-
routeMap.set(path, {
|
|
1049
|
-
path,
|
|
1050
|
-
file: relativePath,
|
|
1051
|
-
hasLayout: false
|
|
1052
|
-
});
|
|
1053
|
-
}
|
|
1054
|
-
}
|
|
1055
|
-
}
|
|
1056
|
-
return [...routeMap.values()];
|
|
1057
|
-
}
|
|
1058
|
-
function scanRoutes(projectRoot) {
|
|
1059
|
-
const appDirs = [join8(projectRoot, "src", "app"), join8(projectRoot, "app")];
|
|
1060
|
-
for (const appDir of appDirs) {
|
|
1061
|
-
if (existsSync8(appDir)) {
|
|
1062
|
-
const routes = walkAppDir(appDir, projectRoot, []);
|
|
1063
|
-
if (routes.length > 0) {
|
|
1064
|
-
return { strategy: "app-router", routes };
|
|
1065
|
-
}
|
|
1066
|
-
}
|
|
1067
|
-
}
|
|
1068
|
-
const pagesDirs = [join8(projectRoot, "src", "pages"), join8(projectRoot, "pages")];
|
|
1069
|
-
for (const pagesDir of pagesDirs) {
|
|
1070
|
-
if (existsSync8(pagesDir)) {
|
|
1071
|
-
const routes = walkPagesDir(pagesDir, projectRoot, []);
|
|
1072
|
-
if (routes.length > 0) {
|
|
1073
|
-
return { strategy: "pages-router", routes };
|
|
1074
|
-
}
|
|
1075
|
-
}
|
|
1076
|
-
}
|
|
1077
|
-
const reactRouterRoutes = scanReactRouter(projectRoot);
|
|
1078
|
-
if (reactRouterRoutes.length > 0) {
|
|
1079
|
-
return { strategy: "react-router", routes: reactRouterRoutes };
|
|
1080
|
-
}
|
|
1081
|
-
return { strategy: "none", routes: [] };
|
|
1082
|
-
}
|
|
1083
|
-
|
|
1084
|
-
// src/analyzers/styling.ts
|
|
1457
|
+
// src/detect.ts
|
|
1085
1458
|
import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
|
|
1086
1459
|
import { join as join9 } from "path";
|
|
1087
|
-
var TAILWIND_CONFIGS = [
|
|
1088
|
-
"tailwind.config.js",
|
|
1089
|
-
"tailwind.config.ts",
|
|
1090
|
-
"tailwind.config.mjs",
|
|
1091
|
-
"tailwind.config.cjs"
|
|
1092
|
-
];
|
|
1093
|
-
var GLOBALS_CSS_PATHS = [
|
|
1094
|
-
"src/app/globals.css",
|
|
1095
|
-
"app/globals.css",
|
|
1096
|
-
"src/styles/global.css",
|
|
1097
|
-
"src/styles/globals.css",
|
|
1098
|
-
"styles/globals.css",
|
|
1099
|
-
"src/index.css",
|
|
1100
|
-
"src/global.css"
|
|
1101
|
-
];
|
|
1102
|
-
var DECANTR_STYLE_PATHS = [
|
|
1103
|
-
"src/styles/tokens.css",
|
|
1104
|
-
"src/styles/treatments.css",
|
|
1105
|
-
"src/styles/global.css"
|
|
1106
|
-
];
|
|
1107
|
-
function extractCSSVariables(content) {
|
|
1108
|
-
const colors = {};
|
|
1109
|
-
const variables = [];
|
|
1110
|
-
const varRegex = /--([\w-]+)\s*:\s*([^;]+)/g;
|
|
1111
|
-
let match;
|
|
1112
|
-
while ((match = varRegex.exec(content)) !== null) {
|
|
1113
|
-
const name = match[1];
|
|
1114
|
-
const value = match[2].trim();
|
|
1115
|
-
variables.push(`--${name}`);
|
|
1116
|
-
const colorPatterns = [
|
|
1117
|
-
"primary",
|
|
1118
|
-
"secondary",
|
|
1119
|
-
"accent",
|
|
1120
|
-
"bg",
|
|
1121
|
-
"fg",
|
|
1122
|
-
"border",
|
|
1123
|
-
"success",
|
|
1124
|
-
"warning",
|
|
1125
|
-
"error",
|
|
1126
|
-
"surface",
|
|
1127
|
-
"muted"
|
|
1128
|
-
];
|
|
1129
|
-
if (value.startsWith("#") || value.startsWith("rgb") || value.startsWith("hsl") || colorPatterns.some((p) => name.includes(p))) {
|
|
1130
|
-
colors[name] = value;
|
|
1131
|
-
}
|
|
1132
|
-
}
|
|
1133
|
-
return { colors, variables };
|
|
1134
|
-
}
|
|
1135
|
-
function detectDarkMode(projectRoot, cssContents) {
|
|
1136
|
-
for (const cssContent of cssContents) {
|
|
1137
|
-
if (cssContent.includes(".dark") || cssContent.includes('[data-theme="dark"]') || cssContent.includes("prefers-color-scheme: dark") || cssContent.includes("color-scheme: dark")) {
|
|
1138
|
-
return true;
|
|
1139
|
-
}
|
|
1140
|
-
}
|
|
1141
|
-
const layoutPaths = [
|
|
1142
|
-
"src/app/layout.tsx",
|
|
1143
|
-
"app/layout.tsx",
|
|
1144
|
-
"src/app/layout.jsx",
|
|
1145
|
-
"app/layout.jsx"
|
|
1146
|
-
];
|
|
1147
|
-
for (const rel of layoutPaths) {
|
|
1148
|
-
const fullPath = join9(projectRoot, rel);
|
|
1149
|
-
if (existsSync9(fullPath)) {
|
|
1150
|
-
try {
|
|
1151
|
-
const layoutContent = readFileSync7(fullPath, "utf-8");
|
|
1152
|
-
if (layoutContent.includes('className="dark"') || layoutContent.includes("className='dark'") || layoutContent.includes('class="dark"')) {
|
|
1153
|
-
return true;
|
|
1154
|
-
}
|
|
1155
|
-
} catch {
|
|
1156
|
-
}
|
|
1157
|
-
}
|
|
1158
|
-
}
|
|
1159
|
-
const pkgPath = join9(projectRoot, "package.json");
|
|
1160
|
-
if (existsSync9(pkgPath)) {
|
|
1161
|
-
try {
|
|
1162
|
-
const pkg = JSON.parse(readFileSync7(pkgPath, "utf-8"));
|
|
1163
|
-
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
1164
|
-
if (allDeps["next-themes"] || allDeps["theme-toggle"] || allDeps["use-dark-mode"]) {
|
|
1165
|
-
return true;
|
|
1166
|
-
}
|
|
1167
|
-
} catch {
|
|
1168
|
-
}
|
|
1169
|
-
}
|
|
1170
|
-
const essencePath = join9(projectRoot, "decantr.essence.json");
|
|
1171
|
-
if (existsSync9(essencePath)) {
|
|
1172
|
-
try {
|
|
1173
|
-
const essence = JSON.parse(readFileSync7(essencePath, "utf-8"));
|
|
1174
|
-
const mode = essence.dna?.theme?.mode;
|
|
1175
|
-
if (mode === "dark" || mode === "auto") {
|
|
1176
|
-
return true;
|
|
1177
|
-
}
|
|
1178
|
-
} catch {
|
|
1179
|
-
}
|
|
1180
|
-
}
|
|
1181
|
-
return false;
|
|
1182
|
-
}
|
|
1183
|
-
function scanStyling(projectRoot) {
|
|
1184
|
-
let approach = "unknown";
|
|
1185
|
-
let configFile;
|
|
1186
|
-
for (const cfg of TAILWIND_CONFIGS) {
|
|
1187
|
-
if (existsSync9(join9(projectRoot, cfg))) {
|
|
1188
|
-
approach = "tailwind";
|
|
1189
|
-
configFile = cfg;
|
|
1190
|
-
break;
|
|
1191
|
-
}
|
|
1192
|
-
}
|
|
1193
|
-
if (approach === "unknown") {
|
|
1194
|
-
const pkgPath = join9(projectRoot, "package.json");
|
|
1195
|
-
if (existsSync9(pkgPath)) {
|
|
1196
|
-
try {
|
|
1197
|
-
const pkg = JSON.parse(readFileSync7(pkgPath, "utf-8"));
|
|
1198
|
-
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
1199
|
-
if (allDeps["@decantr/css"]) {
|
|
1200
|
-
approach = "decantr-css";
|
|
1201
|
-
configFile = "src/styles/tokens.css";
|
|
1202
|
-
}
|
|
1203
|
-
if (allDeps.tailwindcss || allDeps["@tailwindcss/postcss"] || allDeps["@tailwindcss/vite"]) {
|
|
1204
|
-
approach = "tailwind";
|
|
1205
|
-
}
|
|
1206
|
-
} catch {
|
|
1207
|
-
}
|
|
1208
|
-
}
|
|
1209
|
-
}
|
|
1210
|
-
const decantrStyleFiles = DECANTR_STYLE_PATHS.filter((rel) => existsSync9(join9(projectRoot, rel)));
|
|
1211
|
-
if (decantrStyleFiles.length >= 2) {
|
|
1212
|
-
approach = "decantr-css";
|
|
1213
|
-
configFile = decantrStyleFiles.join(" + ");
|
|
1214
|
-
}
|
|
1215
|
-
const cssContents = [];
|
|
1216
|
-
for (const rel of GLOBALS_CSS_PATHS) {
|
|
1217
|
-
const fullPath = join9(projectRoot, rel);
|
|
1218
|
-
if (existsSync9(fullPath)) {
|
|
1219
|
-
try {
|
|
1220
|
-
cssContents.push(readFileSync7(fullPath, "utf-8"));
|
|
1221
|
-
} catch {
|
|
1222
|
-
}
|
|
1223
|
-
}
|
|
1224
|
-
}
|
|
1225
|
-
for (const rel of DECANTR_STYLE_PATHS) {
|
|
1226
|
-
if (GLOBALS_CSS_PATHS.includes(rel)) continue;
|
|
1227
|
-
const fullPath = join9(projectRoot, rel);
|
|
1228
|
-
if (existsSync9(fullPath)) {
|
|
1229
|
-
try {
|
|
1230
|
-
cssContents.push(readFileSync7(fullPath, "utf-8"));
|
|
1231
|
-
} catch {
|
|
1232
|
-
}
|
|
1233
|
-
}
|
|
1234
|
-
}
|
|
1235
|
-
let colors = {};
|
|
1236
|
-
let cssVariables = [];
|
|
1237
|
-
for (const cssContent of cssContents) {
|
|
1238
|
-
const extracted = extractCSSVariables(cssContent);
|
|
1239
|
-
colors = { ...colors, ...extracted.colors };
|
|
1240
|
-
cssVariables.push(...extracted.variables);
|
|
1241
|
-
}
|
|
1242
|
-
cssVariables = [...new Set(cssVariables)];
|
|
1243
|
-
const darkMode = detectDarkMode(projectRoot, cssContents);
|
|
1244
|
-
if (approach === "unknown" && cssContents.length > 0) {
|
|
1245
|
-
approach = "css";
|
|
1246
|
-
configFile = GLOBALS_CSS_PATHS.find((rel) => existsSync9(join9(projectRoot, rel)));
|
|
1247
|
-
}
|
|
1248
|
-
return {
|
|
1249
|
-
approach,
|
|
1250
|
-
configFile,
|
|
1251
|
-
colors,
|
|
1252
|
-
darkMode,
|
|
1253
|
-
cssVariables
|
|
1254
|
-
};
|
|
1255
|
-
}
|
|
1256
|
-
|
|
1257
|
-
// src/detect.ts
|
|
1258
|
-
import { existsSync as existsSync10, readFileSync as readFileSync8 } from "fs";
|
|
1259
|
-
import { join as join10 } from "path";
|
|
1260
1460
|
var RULE_FILES = [
|
|
1261
1461
|
"CLAUDE.md",
|
|
1462
|
+
".claude/rules",
|
|
1262
1463
|
".cursorrules",
|
|
1263
1464
|
".cursor/rules",
|
|
1264
1465
|
"AGENTS.md",
|
|
1265
1466
|
"GEMINI.md",
|
|
1266
|
-
"copilot-instructions.md"
|
|
1467
|
+
"copilot-instructions.md",
|
|
1468
|
+
".github/copilot-instructions.md",
|
|
1469
|
+
".windsurfrules"
|
|
1267
1470
|
];
|
|
1268
1471
|
function detectProject(projectRoot = process.cwd()) {
|
|
1269
1472
|
const result = {
|
|
@@ -1275,52 +1478,52 @@ function detectProject(projectRoot = process.cwd()) {
|
|
|
1275
1478
|
existingEssence: false,
|
|
1276
1479
|
projectRoot
|
|
1277
1480
|
};
|
|
1278
|
-
result.existingEssence =
|
|
1481
|
+
result.existingEssence = existsSync9(join9(projectRoot, "decantr.essence.json"));
|
|
1279
1482
|
for (const ruleFile of RULE_FILES) {
|
|
1280
|
-
if (
|
|
1483
|
+
if (existsSync9(join9(projectRoot, ruleFile))) {
|
|
1281
1484
|
result.existingRuleFiles.push(ruleFile);
|
|
1282
1485
|
}
|
|
1283
1486
|
}
|
|
1284
|
-
if (
|
|
1487
|
+
if (existsSync9(join9(projectRoot, "pnpm-lock.yaml"))) {
|
|
1285
1488
|
result.packageManager = "pnpm";
|
|
1286
|
-
} else if (
|
|
1489
|
+
} else if (existsSync9(join9(projectRoot, "yarn.lock"))) {
|
|
1287
1490
|
result.packageManager = "yarn";
|
|
1288
|
-
} else if (
|
|
1491
|
+
} else if (existsSync9(join9(projectRoot, "bun.lockb"))) {
|
|
1289
1492
|
result.packageManager = "bun";
|
|
1290
|
-
} else if (
|
|
1493
|
+
} else if (existsSync9(join9(projectRoot, "package-lock.json"))) {
|
|
1291
1494
|
result.packageManager = "npm";
|
|
1292
1495
|
}
|
|
1293
|
-
result.hasTypeScript =
|
|
1294
|
-
result.hasTailwind =
|
|
1295
|
-
if (
|
|
1496
|
+
result.hasTypeScript = existsSync9(join9(projectRoot, "tsconfig.json"));
|
|
1497
|
+
result.hasTailwind = existsSync9(join9(projectRoot, "tailwind.config.js")) || existsSync9(join9(projectRoot, "tailwind.config.ts")) || existsSync9(join9(projectRoot, "tailwind.config.mjs")) || existsSync9(join9(projectRoot, "tailwind.config.cjs"));
|
|
1498
|
+
if (existsSync9(join9(projectRoot, "next.config.js")) || existsSync9(join9(projectRoot, "next.config.ts")) || existsSync9(join9(projectRoot, "next.config.mjs"))) {
|
|
1296
1499
|
result.framework = "nextjs";
|
|
1297
1500
|
result.version = getPackageVersion(projectRoot, "next");
|
|
1298
1501
|
return result;
|
|
1299
1502
|
}
|
|
1300
|
-
if (
|
|
1503
|
+
if (existsSync9(join9(projectRoot, "nuxt.config.js")) || existsSync9(join9(projectRoot, "nuxt.config.ts"))) {
|
|
1301
1504
|
result.framework = "nuxt";
|
|
1302
1505
|
result.version = getPackageVersion(projectRoot, "nuxt");
|
|
1303
1506
|
return result;
|
|
1304
1507
|
}
|
|
1305
|
-
if (
|
|
1508
|
+
if (existsSync9(join9(projectRoot, "astro.config.mjs")) || existsSync9(join9(projectRoot, "astro.config.ts"))) {
|
|
1306
1509
|
result.framework = "astro";
|
|
1307
1510
|
result.version = getPackageVersion(projectRoot, "astro");
|
|
1308
1511
|
return result;
|
|
1309
1512
|
}
|
|
1310
|
-
if (
|
|
1513
|
+
if (existsSync9(join9(projectRoot, "svelte.config.js")) || existsSync9(join9(projectRoot, "svelte.config.ts"))) {
|
|
1311
1514
|
result.framework = "svelte";
|
|
1312
1515
|
result.version = getPackageVersion(projectRoot, "svelte");
|
|
1313
1516
|
return result;
|
|
1314
1517
|
}
|
|
1315
|
-
if (
|
|
1518
|
+
if (existsSync9(join9(projectRoot, "angular.json"))) {
|
|
1316
1519
|
result.framework = "angular";
|
|
1317
1520
|
result.version = getPackageVersion(projectRoot, "@angular/core");
|
|
1318
1521
|
return result;
|
|
1319
1522
|
}
|
|
1320
|
-
const packageJsonPath =
|
|
1321
|
-
if (
|
|
1523
|
+
const packageJsonPath = join9(projectRoot, "package.json");
|
|
1524
|
+
if (existsSync9(packageJsonPath)) {
|
|
1322
1525
|
try {
|
|
1323
|
-
const packageJson = JSON.parse(
|
|
1526
|
+
const packageJson = JSON.parse(readFileSync7(packageJsonPath, "utf-8"));
|
|
1324
1527
|
const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
|
|
1325
1528
|
if (deps.next) {
|
|
1326
1529
|
result.framework = "nextjs";
|
|
@@ -1347,18 +1550,18 @@ function detectProject(projectRoot = process.cwd()) {
|
|
|
1347
1550
|
} catch {
|
|
1348
1551
|
}
|
|
1349
1552
|
}
|
|
1350
|
-
if (result.framework === "unknown" && !
|
|
1351
|
-
if (
|
|
1553
|
+
if (result.framework === "unknown" && !existsSync9(packageJsonPath)) {
|
|
1554
|
+
if (existsSync9(join9(projectRoot, "index.html"))) {
|
|
1352
1555
|
result.framework = "html";
|
|
1353
1556
|
}
|
|
1354
1557
|
}
|
|
1355
1558
|
return result;
|
|
1356
1559
|
}
|
|
1357
1560
|
function getPackageVersion(projectRoot, packageName) {
|
|
1358
|
-
const packageJsonPath =
|
|
1359
|
-
if (!
|
|
1561
|
+
const packageJsonPath = join9(projectRoot, "package.json");
|
|
1562
|
+
if (!existsSync9(packageJsonPath)) return void 0;
|
|
1360
1563
|
try {
|
|
1361
|
-
const packageJson = JSON.parse(
|
|
1564
|
+
const packageJson = JSON.parse(readFileSync7(packageJsonPath, "utf-8"));
|
|
1362
1565
|
const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
|
|
1363
1566
|
const version = deps[packageName];
|
|
1364
1567
|
return version?.replace(/^\^|~/, "");
|
|
@@ -1391,8 +1594,8 @@ function formatDetection(detected) {
|
|
|
1391
1594
|
}
|
|
1392
1595
|
|
|
1393
1596
|
// src/workflow-model.ts
|
|
1394
|
-
import { existsSync as
|
|
1395
|
-
import { join as
|
|
1597
|
+
import { existsSync as existsSync10, readFileSync as readFileSync8 } from "fs";
|
|
1598
|
+
import { join as join10 } from "path";
|
|
1396
1599
|
function inferSuggestedShell(layout) {
|
|
1397
1600
|
if (layout.hasSidebar) return "sidebar-main";
|
|
1398
1601
|
if (layout.hasTopNav) return "top-nav-main";
|
|
@@ -1468,7 +1671,7 @@ function createBrownfieldInitSeed(detected, layout, styling) {
|
|
|
1468
1671
|
shell: inferSuggestedShell(layout),
|
|
1469
1672
|
guard: "guided",
|
|
1470
1673
|
density: "comfortable",
|
|
1471
|
-
theme: "
|
|
1674
|
+
theme: "existing",
|
|
1472
1675
|
mode: styling.darkMode ? "dark" : "auto",
|
|
1473
1676
|
existing: true,
|
|
1474
1677
|
notes: [
|
|
@@ -1479,12 +1682,12 @@ function createBrownfieldInitSeed(detected, layout, styling) {
|
|
|
1479
1682
|
};
|
|
1480
1683
|
}
|
|
1481
1684
|
function readBrownfieldInitSeed(projectRoot) {
|
|
1482
|
-
const seedPath =
|
|
1483
|
-
if (!
|
|
1685
|
+
const seedPath = join10(projectRoot, ".decantr", "init-seed.json");
|
|
1686
|
+
if (!existsSync10(seedPath)) {
|
|
1484
1687
|
return null;
|
|
1485
1688
|
}
|
|
1486
1689
|
try {
|
|
1487
|
-
const parsed = JSON.parse(
|
|
1690
|
+
const parsed = JSON.parse(readFileSync8(seedPath, "utf-8"));
|
|
1488
1691
|
if (parsed.workflow !== "brownfield-adoption") {
|
|
1489
1692
|
return null;
|
|
1490
1693
|
}
|
|
@@ -1519,8 +1722,21 @@ ${BOLD}Analyzing project...${RESET2}
|
|
|
1519
1722
|
const features = scanFeatures(projectRoot);
|
|
1520
1723
|
console.log(`${DIM2}Scanning dependencies...${RESET2}`);
|
|
1521
1724
|
const dependencies = scanDependencies(projectRoot);
|
|
1725
|
+
console.log(`${DIM2}Scanning ambient project context...${RESET2}`);
|
|
1726
|
+
const ambient = scanAmbientContext(projectRoot);
|
|
1727
|
+
const doctrine = createDoctrineMap(ambient);
|
|
1522
1728
|
const initSeed = createBrownfieldInitSeed(project, layout, styling);
|
|
1523
1729
|
initSeed.projectScope = workspace?.projectScope ?? "single-app";
|
|
1730
|
+
const proposal = createBrownfieldProposal({
|
|
1731
|
+
project,
|
|
1732
|
+
routes,
|
|
1733
|
+
components,
|
|
1734
|
+
styling,
|
|
1735
|
+
layout,
|
|
1736
|
+
features,
|
|
1737
|
+
dependencies,
|
|
1738
|
+
ambient
|
|
1739
|
+
});
|
|
1524
1740
|
const analysis = {
|
|
1525
1741
|
version: 1,
|
|
1526
1742
|
analyzedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -1549,7 +1765,8 @@ ${BOLD}Analyzing project...${RESET2}
|
|
|
1549
1765
|
contractOnly: true,
|
|
1550
1766
|
adoptionMode: "contract-only",
|
|
1551
1767
|
initSeedPath: ".decantr/init-seed.json",
|
|
1552
|
-
|
|
1768
|
+
proposalPath: ".decantr/observed-essence.proposal.json",
|
|
1769
|
+
recommendedCommand: "decantr init --existing --accept-proposal"
|
|
1553
1770
|
},
|
|
1554
1771
|
hybrid: {
|
|
1555
1772
|
ownerCommands: [
|
|
@@ -1568,6 +1785,9 @@ ${BOLD}Analyzing project...${RESET2}
|
|
|
1568
1785
|
routeAnchors: routes.routes.map((route) => route.path),
|
|
1569
1786
|
stylingAnchors: [styling.configFile].filter(Boolean),
|
|
1570
1787
|
ruleFiles: project.existingRuleFiles,
|
|
1788
|
+
ambientContextPath: ".decantr/ambient-context.json",
|
|
1789
|
+
doctrineMapPath: ".decantr/doctrine-map.json",
|
|
1790
|
+
proposalPath: ".decantr/observed-essence.proposal.json",
|
|
1571
1791
|
preserve: [
|
|
1572
1792
|
"framework",
|
|
1573
1793
|
"package manager",
|
|
@@ -1577,14 +1797,21 @@ ${BOLD}Analyzing project...${RESET2}
|
|
|
1577
1797
|
]
|
|
1578
1798
|
}
|
|
1579
1799
|
};
|
|
1580
|
-
const decantrDir =
|
|
1581
|
-
if (!
|
|
1582
|
-
|
|
1583
|
-
}
|
|
1584
|
-
const outputPath =
|
|
1585
|
-
const initSeedPath =
|
|
1586
|
-
|
|
1587
|
-
|
|
1800
|
+
const decantrDir = join11(projectRoot, ".decantr");
|
|
1801
|
+
if (!existsSync11(decantrDir)) {
|
|
1802
|
+
mkdirSync4(decantrDir, { recursive: true });
|
|
1803
|
+
}
|
|
1804
|
+
const outputPath = join11(decantrDir, "analysis.json");
|
|
1805
|
+
const initSeedPath = join11(decantrDir, "init-seed.json");
|
|
1806
|
+
const ambientPath = join11(decantrDir, "ambient-context.json");
|
|
1807
|
+
const doctrinePath = join11(decantrDir, "doctrine-map.json");
|
|
1808
|
+
const reportPath = join11(decantrDir, "brownfield-report.md");
|
|
1809
|
+
writeFileSync5(outputPath, JSON.stringify(analysis, null, 2) + "\n", "utf-8");
|
|
1810
|
+
writeFileSync5(initSeedPath, JSON.stringify(initSeed, null, 2) + "\n", "utf-8");
|
|
1811
|
+
writeFileSync5(ambientPath, JSON.stringify(ambient, null, 2) + "\n", "utf-8");
|
|
1812
|
+
writeDoctrineMap(projectRoot, doctrine);
|
|
1813
|
+
writeBrownfieldProposal(projectRoot, proposal);
|
|
1814
|
+
writeFileSync5(reportPath, generateBrownfieldReport(proposal, ambient, doctrine), "utf-8");
|
|
1588
1815
|
console.log(`
|
|
1589
1816
|
${GREEN2}Analysis complete.${RESET2}
|
|
1590
1817
|
`);
|
|
@@ -1605,6 +1832,8 @@ ${GREEN2}Analysis complete.${RESET2}
|
|
|
1605
1832
|
console.log(
|
|
1606
1833
|
` Features: ${features.detected.length > 0 ? features.detected.join(", ") : "none detected"}`
|
|
1607
1834
|
);
|
|
1835
|
+
console.log(` Context: ${ambient.items.length} ambient item(s)`);
|
|
1836
|
+
console.log(` Doctrine: ${doctrine.sources.length} ranked source(s)`);
|
|
1608
1837
|
const depCounts = [
|
|
1609
1838
|
dependencies.ui.length && `${dependencies.ui.length} ui`,
|
|
1610
1839
|
dependencies.auth.length && `${dependencies.auth.length} auth`,
|
|
@@ -1616,16 +1845,20 @@ ${GREEN2}Analysis complete.${RESET2}
|
|
|
1616
1845
|
console.log(`
|
|
1617
1846
|
${DIM2}Written to:${RESET2} ${outputPath}`);
|
|
1618
1847
|
console.log(`${DIM2}Init seed:${RESET2} ${initSeedPath}`);
|
|
1848
|
+
console.log(`${DIM2}Ambient context:${RESET2} ${ambientPath}`);
|
|
1849
|
+
console.log(`${DIM2}Doctrine map:${RESET2} ${doctrinePath}`);
|
|
1850
|
+
console.log(`${DIM2}Observed proposal:${RESET2} ${join11(decantrDir, "observed-essence.proposal.json")}`);
|
|
1851
|
+
console.log(`${DIM2}Brownfield report:${RESET2} ${reportPath}`);
|
|
1619
1852
|
console.log(
|
|
1620
1853
|
`
|
|
1621
|
-
${YELLOW}Next step:${RESET2}
|
|
1854
|
+
${YELLOW}Next step:${RESET2} Review ${BOLD}.decantr/brownfield-report.md${RESET2}, then run ${BOLD}decantr init --existing --accept-proposal${RESET2} to attach Decantr using the observed proposal.
|
|
1622
1855
|
`
|
|
1623
1856
|
);
|
|
1624
1857
|
}
|
|
1625
1858
|
|
|
1626
1859
|
// src/commands/create.ts
|
|
1627
|
-
import { existsSync as
|
|
1628
|
-
import { join as
|
|
1860
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync5, writeFileSync as writeFileSync6 } from "fs";
|
|
1861
|
+
import { join as join12 } from "path";
|
|
1629
1862
|
import {
|
|
1630
1863
|
CONTENT_TYPE_TO_API_CONTENT_TYPE,
|
|
1631
1864
|
CONTENT_TYPES
|
|
@@ -1821,23 +2054,23 @@ function cmdCreate(type, name, projectRoot = process.cwd()) {
|
|
|
1821
2054
|
}
|
|
1822
2055
|
const contentType = type;
|
|
1823
2056
|
const plural = PLURAL[contentType];
|
|
1824
|
-
const customDir =
|
|
1825
|
-
const filePath =
|
|
1826
|
-
if (
|
|
2057
|
+
const customDir = join12(projectRoot, ".decantr", "custom", plural);
|
|
2058
|
+
const filePath = join12(customDir, `${name}.json`);
|
|
2059
|
+
if (existsSync12(filePath)) {
|
|
1827
2060
|
console.error(`${type} "${name}" already exists at ${filePath}`);
|
|
1828
2061
|
process.exitCode = 1;
|
|
1829
2062
|
return;
|
|
1830
2063
|
}
|
|
1831
|
-
|
|
2064
|
+
mkdirSync5(customDir, { recursive: true });
|
|
1832
2065
|
const skeleton = getSkeleton(contentType, name, humanizeId(name));
|
|
1833
|
-
|
|
2066
|
+
writeFileSync6(filePath, JSON.stringify(skeleton, null, 2));
|
|
1834
2067
|
console.log(`Created ${type} "${name}" at ${filePath}`);
|
|
1835
2068
|
console.log(`Edit it, then publish with: decantr publish ${type} ${name}`);
|
|
1836
2069
|
}
|
|
1837
2070
|
|
|
1838
2071
|
// src/commands/export.ts
|
|
1839
|
-
import { existsSync as
|
|
1840
|
-
import { dirname as dirname2, join as
|
|
2072
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync6, readFileSync as readFileSync9, writeFileSync as writeFileSync7 } from "fs";
|
|
2073
|
+
import { dirname as dirname2, join as join13 } from "path";
|
|
1841
2074
|
var GREEN3 = "\x1B[32m";
|
|
1842
2075
|
var RED2 = "\x1B[31m";
|
|
1843
2076
|
var DIM3 = "\x1B[2m";
|
|
@@ -1987,21 +2220,21 @@ function generateCSSVars(tokens) {
|
|
|
1987
2220
|
return lines.join("\n");
|
|
1988
2221
|
}
|
|
1989
2222
|
async function cmdExport(target, projectRoot, options = {}) {
|
|
1990
|
-
const essencePath =
|
|
1991
|
-
const tokensPath =
|
|
1992
|
-
if (!
|
|
2223
|
+
const essencePath = join13(projectRoot, "decantr.essence.json");
|
|
2224
|
+
const tokensPath = join13(projectRoot, "src", "styles", "tokens.css");
|
|
2225
|
+
if (!existsSync13(essencePath)) {
|
|
1993
2226
|
console.error(`${RED2}No decantr.essence.json found. Run \`decantr init\` first.${RESET3}`);
|
|
1994
2227
|
process.exitCode = 1;
|
|
1995
2228
|
return;
|
|
1996
2229
|
}
|
|
1997
|
-
if (!
|
|
2230
|
+
if (!existsSync13(tokensPath)) {
|
|
1998
2231
|
console.error(
|
|
1999
2232
|
`${RED2}No src/styles/tokens.css found. Run \`decantr refresh\` to generate tokens.${RESET3}`
|
|
2000
2233
|
);
|
|
2001
2234
|
process.exitCode = 1;
|
|
2002
2235
|
return;
|
|
2003
2236
|
}
|
|
2004
|
-
const tokensCSS =
|
|
2237
|
+
const tokensCSS = readFileSync9(tokensPath, "utf-8");
|
|
2005
2238
|
const tokens = parseTokensCSS(tokensCSS);
|
|
2006
2239
|
if (tokens.size === 0) {
|
|
2007
2240
|
console.error(`${RED2}No --d-* tokens found in tokens.css.${RESET3}`);
|
|
@@ -2010,28 +2243,28 @@ async function cmdExport(target, projectRoot, options = {}) {
|
|
|
2010
2243
|
}
|
|
2011
2244
|
switch (target) {
|
|
2012
2245
|
case "shadcn": {
|
|
2013
|
-
const cssOut = options.output ??
|
|
2014
|
-
const jsonOut =
|
|
2246
|
+
const cssOut = options.output ?? join13(projectRoot, "src", "styles", "shadcn-theme.css");
|
|
2247
|
+
const jsonOut = join13(projectRoot, "components.json");
|
|
2015
2248
|
ensureDir(cssOut);
|
|
2016
|
-
|
|
2017
|
-
|
|
2249
|
+
writeFileSync7(cssOut, generateShadcnCSS(tokens), "utf-8");
|
|
2250
|
+
writeFileSync7(jsonOut, generateShadcnComponentsJSON(), "utf-8");
|
|
2018
2251
|
console.log(`${GREEN3}Exported shadcn theme:${RESET3}`);
|
|
2019
2252
|
console.log(` ${DIM3}CSS:${RESET3} ${cssOut}`);
|
|
2020
2253
|
console.log(` ${DIM3}JSON:${RESET3} ${jsonOut}`);
|
|
2021
2254
|
break;
|
|
2022
2255
|
}
|
|
2023
2256
|
case "tailwind": {
|
|
2024
|
-
const out = options.output ??
|
|
2257
|
+
const out = options.output ?? join13(projectRoot, "tailwind.decantr.config.ts");
|
|
2025
2258
|
ensureDir(out);
|
|
2026
|
-
|
|
2259
|
+
writeFileSync7(out, generateTailwindConfig(tokens), "utf-8");
|
|
2027
2260
|
console.log(`${GREEN3}Exported Tailwind config:${RESET3}`);
|
|
2028
2261
|
console.log(` ${DIM3}File:${RESET3} ${out}`);
|
|
2029
2262
|
break;
|
|
2030
2263
|
}
|
|
2031
2264
|
case "css-vars": {
|
|
2032
|
-
const out = options.output ??
|
|
2265
|
+
const out = options.output ?? join13(projectRoot, "decantr-tokens.css");
|
|
2033
2266
|
ensureDir(out);
|
|
2034
|
-
|
|
2267
|
+
writeFileSync7(out, generateCSSVars(tokens), "utf-8");
|
|
2035
2268
|
console.log(`${GREEN3}Exported CSS variables:${RESET3}`);
|
|
2036
2269
|
console.log(` ${DIM3}File:${RESET3} ${out}`);
|
|
2037
2270
|
break;
|
|
@@ -2040,15 +2273,15 @@ async function cmdExport(target, projectRoot, options = {}) {
|
|
|
2040
2273
|
}
|
|
2041
2274
|
function ensureDir(filePath) {
|
|
2042
2275
|
const dir = dirname2(filePath);
|
|
2043
|
-
if (!
|
|
2044
|
-
|
|
2276
|
+
if (!existsSync13(dir)) {
|
|
2277
|
+
mkdirSync6(dir, { recursive: true });
|
|
2045
2278
|
}
|
|
2046
2279
|
}
|
|
2047
2280
|
|
|
2048
2281
|
// src/commands/magic.ts
|
|
2049
|
-
import { existsSync as
|
|
2282
|
+
import { existsSync as existsSync14 } from "fs";
|
|
2050
2283
|
import * as fs from "fs/promises";
|
|
2051
|
-
import { join as
|
|
2284
|
+
import { join as join14 } from "path";
|
|
2052
2285
|
var BOLD2 = "\x1B[1m";
|
|
2053
2286
|
var DIM4 = "\x1B[2m";
|
|
2054
2287
|
var RESET4 = "\x1B[0m";
|
|
@@ -2276,8 +2509,8 @@ async function cmdMagic(prompt, projectRoot, options) {
|
|
|
2276
2509
|
console.log(` Archetype: ${intent.archetype}`);
|
|
2277
2510
|
}
|
|
2278
2511
|
console.log("");
|
|
2279
|
-
const essencePath =
|
|
2280
|
-
if (
|
|
2512
|
+
const essencePath = join14(projectRoot, "decantr.essence.json");
|
|
2513
|
+
if (existsSync14(essencePath)) {
|
|
2281
2514
|
console.log(error(" decantr.essence.json already exists in this directory."));
|
|
2282
2515
|
console.log(dim(" Remove it first or use a different directory."));
|
|
2283
2516
|
process.exitCode = 1;
|
|
@@ -2295,12 +2528,14 @@ async function cmdMagic(prompt, projectRoot, options) {
|
|
|
2295
2528
|
dim(" Running brownfield analysis instead so you can attach Decantr deliberately.\n")
|
|
2296
2529
|
);
|
|
2297
2530
|
cmdAnalyze(projectRoot);
|
|
2298
|
-
console.log(
|
|
2531
|
+
console.log(
|
|
2532
|
+
`${BOLD2}Recommended next step:${RESET4} ${cyan("decantr init --existing --accept-proposal")}`
|
|
2533
|
+
);
|
|
2299
2534
|
console.log("");
|
|
2300
2535
|
return;
|
|
2301
2536
|
}
|
|
2302
2537
|
const registryClient = new RegistryClient({
|
|
2303
|
-
cacheDir:
|
|
2538
|
+
cacheDir: join14(projectRoot, ".decantr", "cache"),
|
|
2304
2539
|
apiUrl: options.registry,
|
|
2305
2540
|
offline: options.offline
|
|
2306
2541
|
});
|
|
@@ -2571,14 +2806,14 @@ async function cmdMagic(prompt, projectRoot, options) {
|
|
|
2571
2806
|
if (result.gitignoreUpdated) {
|
|
2572
2807
|
console.log(` ${dim(".gitignore updated")}`);
|
|
2573
2808
|
}
|
|
2574
|
-
const contextDir =
|
|
2809
|
+
const contextDir = join14(projectRoot, ".decantr", "context");
|
|
2575
2810
|
let sectionCount = 0;
|
|
2576
2811
|
try {
|
|
2577
2812
|
const files = await fs.readdir(contextDir);
|
|
2578
2813
|
sectionCount = files.filter((f) => f.startsWith("section-")).length;
|
|
2579
2814
|
} catch {
|
|
2580
2815
|
}
|
|
2581
|
-
const treatmentsPath =
|
|
2816
|
+
const treatmentsPath = join14(projectRoot, "src", "styles", "treatments.css");
|
|
2582
2817
|
let hasLayers = false;
|
|
2583
2818
|
try {
|
|
2584
2819
|
const css = await fs.readFile(treatmentsPath, "utf-8");
|
|
@@ -2611,21 +2846,21 @@ ${GREEN4}${BOLD2}Quality summary:${RESET4}`);
|
|
|
2611
2846
|
}
|
|
2612
2847
|
|
|
2613
2848
|
// src/commands/migrate.ts
|
|
2614
|
-
import { copyFileSync, existsSync as
|
|
2615
|
-
import { join as
|
|
2616
|
-
import { isV3 as
|
|
2849
|
+
import { copyFileSync, existsSync as existsSync15, readFileSync as readFileSync10, writeFileSync as writeFileSync8 } from "fs";
|
|
2850
|
+
import { join as join15 } from "path";
|
|
2851
|
+
import { isV3 as isV33, migrateV2ToV3, validateEssence } from "@decantr/essence-spec";
|
|
2617
2852
|
var GREEN5 = "\x1B[32m";
|
|
2618
2853
|
var RED4 = "\x1B[31m";
|
|
2619
2854
|
var YELLOW3 = "\x1B[33m";
|
|
2620
2855
|
var RESET5 = "\x1B[0m";
|
|
2621
2856
|
var DIM5 = "\x1B[2m";
|
|
2622
2857
|
function migrateEssenceFile(essencePath) {
|
|
2623
|
-
if (!
|
|
2858
|
+
if (!existsSync15(essencePath)) {
|
|
2624
2859
|
return { success: false, error: `File not found: ${essencePath}` };
|
|
2625
2860
|
}
|
|
2626
2861
|
let raw;
|
|
2627
2862
|
try {
|
|
2628
|
-
raw =
|
|
2863
|
+
raw = readFileSync10(essencePath, "utf-8");
|
|
2629
2864
|
} catch (e) {
|
|
2630
2865
|
return { success: false, error: `Could not read ${essencePath}: ${e.message}` };
|
|
2631
2866
|
}
|
|
@@ -2635,7 +2870,7 @@ function migrateEssenceFile(essencePath) {
|
|
|
2635
2870
|
} catch (e) {
|
|
2636
2871
|
return { success: false, error: `Invalid JSON: ${e.message}` };
|
|
2637
2872
|
}
|
|
2638
|
-
if (
|
|
2873
|
+
if (isV33(essence)) {
|
|
2639
2874
|
return { success: true, alreadyV3: true };
|
|
2640
2875
|
}
|
|
2641
2876
|
const preValidation = validateEssence(essence);
|
|
@@ -2666,7 +2901,7 @@ function migrateEssenceFile(essencePath) {
|
|
|
2666
2901
|
};
|
|
2667
2902
|
}
|
|
2668
2903
|
try {
|
|
2669
|
-
|
|
2904
|
+
writeFileSync8(essencePath, JSON.stringify(v3, null, 2) + "\n");
|
|
2670
2905
|
} catch (e) {
|
|
2671
2906
|
return {
|
|
2672
2907
|
success: false,
|
|
@@ -2677,8 +2912,8 @@ function migrateEssenceFile(essencePath) {
|
|
|
2677
2912
|
return { success: true, backupPath };
|
|
2678
2913
|
}
|
|
2679
2914
|
async function cmdMigrate(projectRoot = process.cwd()) {
|
|
2680
|
-
const essencePath =
|
|
2681
|
-
if (!
|
|
2915
|
+
const essencePath = join15(projectRoot, "decantr.essence.json");
|
|
2916
|
+
if (!existsSync15(essencePath)) {
|
|
2682
2917
|
console.error(`${RED4}No decantr.essence.json found. Run \`decantr init\` first.${RESET5}`);
|
|
2683
2918
|
process.exitCode = 1;
|
|
2684
2919
|
return;
|
|
@@ -2704,13 +2939,13 @@ async function cmdMigrate(projectRoot = process.cwd()) {
|
|
|
2704
2939
|
|
|
2705
2940
|
// src/commands/new-project.ts
|
|
2706
2941
|
import { spawnSync } from "child_process";
|
|
2707
|
-
import { existsSync as
|
|
2708
|
-
import { join as
|
|
2942
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync9 } from "fs";
|
|
2943
|
+
import { join as join18, resolve as resolve2 } from "path";
|
|
2709
2944
|
import { fileURLToPath } from "url";
|
|
2710
2945
|
|
|
2711
2946
|
// src/bootstrap.ts
|
|
2712
|
-
import { mkdirSync as
|
|
2713
|
-
import { basename, join as
|
|
2947
|
+
import { mkdirSync as mkdirSync7, readFileSync as readFileSync11, writeFileSync as writeFileSync9 } from "fs";
|
|
2948
|
+
import { basename, join as join16 } from "path";
|
|
2714
2949
|
import { resolvePackAdapter } from "@decantr/core";
|
|
2715
2950
|
var reactViteBootstrapAdapter = {
|
|
2716
2951
|
id: "react-vite",
|
|
@@ -2734,7 +2969,7 @@ var reactViteBootstrapAdapter = {
|
|
|
2734
2969
|
distDir: "dist"
|
|
2735
2970
|
},
|
|
2736
2971
|
writeProjectFiles(projectDir, title, routingMode) {
|
|
2737
|
-
const srcDir =
|
|
2972
|
+
const srcDir = join16(projectDir, "src");
|
|
2738
2973
|
const routerImport = routingMode === "hash" ? "HashRouter" : "BrowserRouter";
|
|
2739
2974
|
const packageJson = {
|
|
2740
2975
|
name: basename(projectDir) || "decantr-app",
|
|
@@ -2766,7 +3001,7 @@ var reactViteBootstrapAdapter = {
|
|
|
2766
3001
|
vite: "^6.0.0"
|
|
2767
3002
|
}
|
|
2768
3003
|
};
|
|
2769
|
-
|
|
3004
|
+
writeFileSync9(join16(projectDir, "package.json"), JSON.stringify(packageJson, null, 2) + "\n");
|
|
2770
3005
|
const viteConfig = `import { defineConfig } from 'vite';
|
|
2771
3006
|
import react from '@vitejs/plugin-react';
|
|
2772
3007
|
|
|
@@ -2774,7 +3009,7 @@ export default defineConfig({
|
|
|
2774
3009
|
plugins: [react()],
|
|
2775
3010
|
});
|
|
2776
3011
|
`;
|
|
2777
|
-
|
|
3012
|
+
writeFileSync9(join16(projectDir, "vite.config.ts"), viteConfig);
|
|
2778
3013
|
const tsconfig = {
|
|
2779
3014
|
compilerOptions: {
|
|
2780
3015
|
target: "ES2020",
|
|
@@ -2796,7 +3031,7 @@ export default defineConfig({
|
|
|
2796
3031
|
},
|
|
2797
3032
|
include: ["src"]
|
|
2798
3033
|
};
|
|
2799
|
-
|
|
3034
|
+
writeFileSync9(join16(projectDir, "tsconfig.json"), JSON.stringify(tsconfig, null, 2) + "\n");
|
|
2800
3035
|
const tsconfigApp = {
|
|
2801
3036
|
compilerOptions: {
|
|
2802
3037
|
tsBuildInfoFile: "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
|
@@ -2819,8 +3054,8 @@ export default defineConfig({
|
|
|
2819
3054
|
},
|
|
2820
3055
|
include: ["src"]
|
|
2821
3056
|
};
|
|
2822
|
-
|
|
2823
|
-
|
|
3057
|
+
writeFileSync9(
|
|
3058
|
+
join16(projectDir, "tsconfig.app.json"),
|
|
2824
3059
|
JSON.stringify(tsconfigApp, null, 2) + "\n"
|
|
2825
3060
|
);
|
|
2826
3061
|
const indexHtml = `<!doctype html>
|
|
@@ -2836,8 +3071,8 @@ export default defineConfig({
|
|
|
2836
3071
|
</body>
|
|
2837
3072
|
</html>
|
|
2838
3073
|
`;
|
|
2839
|
-
|
|
2840
|
-
|
|
3074
|
+
writeFileSync9(join16(projectDir, "index.html"), indexHtml);
|
|
3075
|
+
mkdirSync7(srcDir, { recursive: true });
|
|
2841
3076
|
const mainTsx = `import { StrictMode } from 'react';
|
|
2842
3077
|
import { createRoot } from 'react-dom/client';
|
|
2843
3078
|
import { ${routerImport} } from 'react-router-dom';
|
|
@@ -2854,7 +3089,7 @@ createRoot(document.getElementById('root')!).render(
|
|
|
2854
3089
|
</StrictMode>,
|
|
2855
3090
|
);
|
|
2856
3091
|
`;
|
|
2857
|
-
|
|
3092
|
+
writeFileSync9(join16(srcDir, "main.tsx"), mainTsx);
|
|
2858
3093
|
const appTsx = `import { css } from '@decantr/css';
|
|
2859
3094
|
import { Routes, Route } from 'react-router-dom';
|
|
2860
3095
|
|
|
@@ -2889,9 +3124,9 @@ export function App() {
|
|
|
2889
3124
|
);
|
|
2890
3125
|
}
|
|
2891
3126
|
`;
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
3127
|
+
writeFileSync9(join16(srcDir, "App.tsx"), appTsx);
|
|
3128
|
+
writeFileSync9(join16(srcDir, "vite-env.d.ts"), '/// <reference types="vite/client" />\n');
|
|
3129
|
+
mkdirSync7(join16(srcDir, "styles"), { recursive: true });
|
|
2895
3130
|
}
|
|
2896
3131
|
};
|
|
2897
3132
|
var nextAppAdapter = {
|
|
@@ -2916,8 +3151,8 @@ var nextAppAdapter = {
|
|
|
2916
3151
|
distDir: ".next"
|
|
2917
3152
|
},
|
|
2918
3153
|
writeProjectFiles(projectDir, title) {
|
|
2919
|
-
const appDir =
|
|
2920
|
-
const stylesDir =
|
|
3154
|
+
const appDir = join16(projectDir, "app");
|
|
3155
|
+
const stylesDir = join16(projectDir, "src", "styles");
|
|
2921
3156
|
const packageJson = {
|
|
2922
3157
|
name: basename(projectDir) || "decantr-next-app",
|
|
2923
3158
|
private: true,
|
|
@@ -2942,11 +3177,11 @@ var nextAppAdapter = {
|
|
|
2942
3177
|
typescript: "^5.7.0"
|
|
2943
3178
|
}
|
|
2944
3179
|
};
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
3180
|
+
writeFileSync9(join16(projectDir, "package.json"), JSON.stringify(packageJson, null, 2) + "\n");
|
|
3181
|
+
writeFileSync9(join16(projectDir, "next.config.ts"), 'import type { NextConfig } from "next";\n\nconst nextConfig: NextConfig = {};\n\nexport default nextConfig;\n');
|
|
3182
|
+
writeFileSync9(join16(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');
|
|
3183
|
+
writeFileSync9(
|
|
3184
|
+
join16(projectDir, "tsconfig.json"),
|
|
2950
3185
|
JSON.stringify(
|
|
2951
3186
|
{
|
|
2952
3187
|
compilerOptions: {
|
|
@@ -2972,10 +3207,10 @@ var nextAppAdapter = {
|
|
|
2972
3207
|
2
|
|
2973
3208
|
) + "\n"
|
|
2974
3209
|
);
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
3210
|
+
mkdirSync7(appDir, { recursive: true });
|
|
3211
|
+
mkdirSync7(stylesDir, { recursive: true });
|
|
3212
|
+
writeFileSync9(
|
|
3213
|
+
join16(appDir, "layout.tsx"),
|
|
2979
3214
|
`import type { Metadata } from 'next';
|
|
2980
3215
|
import '../src/styles/global.css';
|
|
2981
3216
|
import '../src/styles/tokens.css';
|
|
@@ -2995,8 +3230,8 @@ export default function RootLayout({ children }: Readonly<{ children: React.Reac
|
|
|
2995
3230
|
}
|
|
2996
3231
|
`
|
|
2997
3232
|
);
|
|
2998
|
-
|
|
2999
|
-
|
|
3233
|
+
writeFileSync9(
|
|
3234
|
+
join16(appDir, "page.tsx"),
|
|
3000
3235
|
`import { css } from '@decantr/css';
|
|
3001
3236
|
|
|
3002
3237
|
export default function HomePage() {
|
|
@@ -3066,7 +3301,7 @@ function getBootstrapAdapter(resolution) {
|
|
|
3066
3301
|
}
|
|
3067
3302
|
function detectRoutingMode(projectDir) {
|
|
3068
3303
|
try {
|
|
3069
|
-
const essence = JSON.parse(
|
|
3304
|
+
const essence = JSON.parse(readFileSync11(join16(projectDir, "decantr.essence.json"), "utf-8"));
|
|
3070
3305
|
const routing = essence.meta?.platform?.routing;
|
|
3071
3306
|
if (routing === "history" || routing === "pathname") {
|
|
3072
3307
|
return routing;
|
|
@@ -3078,45 +3313,45 @@ function detectRoutingMode(projectDir) {
|
|
|
3078
3313
|
}
|
|
3079
3314
|
|
|
3080
3315
|
// src/offline-content.ts
|
|
3081
|
-
import { cpSync, existsSync as
|
|
3082
|
-
import { join as
|
|
3316
|
+
import { cpSync, existsSync as existsSync16, mkdirSync as mkdirSync8 } from "fs";
|
|
3317
|
+
import { join as join17, resolve } from "path";
|
|
3083
3318
|
var CONTENT_TYPES2 = ["archetypes", "blueprints", "patterns", "themes", "shells"];
|
|
3084
3319
|
function copyIfExists(source, target) {
|
|
3085
|
-
if (!
|
|
3320
|
+
if (!existsSync16(source)) return false;
|
|
3086
3321
|
if (resolve(source) === resolve(target)) return true;
|
|
3087
3322
|
cpSync(source, target, { recursive: true });
|
|
3088
3323
|
return true;
|
|
3089
3324
|
}
|
|
3090
3325
|
function hydrateContentRoot(projectDir, contentRoot) {
|
|
3091
|
-
if (!
|
|
3092
|
-
const customRoot =
|
|
3093
|
-
const cacheRoot =
|
|
3094
|
-
|
|
3095
|
-
|
|
3326
|
+
if (!existsSync16(contentRoot)) return false;
|
|
3327
|
+
const customRoot = join17(projectDir, ".decantr", "custom");
|
|
3328
|
+
const cacheRoot = join17(projectDir, ".decantr", "cache", "@official");
|
|
3329
|
+
mkdirSync8(customRoot, { recursive: true });
|
|
3330
|
+
mkdirSync8(cacheRoot, { recursive: true });
|
|
3096
3331
|
let copiedAny = false;
|
|
3097
3332
|
for (const type of CONTENT_TYPES2) {
|
|
3098
|
-
const sourceDir =
|
|
3099
|
-
if (!
|
|
3100
|
-
cpSync(sourceDir,
|
|
3101
|
-
cpSync(sourceDir,
|
|
3333
|
+
const sourceDir = join17(contentRoot, type);
|
|
3334
|
+
if (!existsSync16(sourceDir)) continue;
|
|
3335
|
+
cpSync(sourceDir, join17(customRoot, type), { recursive: true });
|
|
3336
|
+
cpSync(sourceDir, join17(cacheRoot, type), { recursive: true });
|
|
3102
3337
|
copiedAny = true;
|
|
3103
3338
|
}
|
|
3104
3339
|
return copiedAny;
|
|
3105
3340
|
}
|
|
3106
3341
|
function seedOfflineRegistry(projectDir, workspaceRoot) {
|
|
3107
|
-
const projectDecantrRoot =
|
|
3108
|
-
|
|
3342
|
+
const projectDecantrRoot = join17(projectDir, ".decantr");
|
|
3343
|
+
mkdirSync8(projectDecantrRoot, { recursive: true });
|
|
3109
3344
|
const configuredContentRoot = process.env.DECANTR_CONTENT_DIR ? resolve(process.env.DECANTR_CONTENT_DIR) : null;
|
|
3110
3345
|
if (configuredContentRoot && hydrateContentRoot(projectDir, configuredContentRoot)) {
|
|
3111
3346
|
return { seeded: true, strategy: "configured-content-root" };
|
|
3112
3347
|
}
|
|
3113
3348
|
const copiedCache = copyIfExists(
|
|
3114
|
-
|
|
3115
|
-
|
|
3349
|
+
join17(workspaceRoot, ".decantr", "cache"),
|
|
3350
|
+
join17(projectDecantrRoot, "cache")
|
|
3116
3351
|
);
|
|
3117
3352
|
const copiedCustom = copyIfExists(
|
|
3118
|
-
|
|
3119
|
-
|
|
3353
|
+
join17(workspaceRoot, ".decantr", "custom"),
|
|
3354
|
+
join17(projectDecantrRoot, "custom")
|
|
3120
3355
|
);
|
|
3121
3356
|
if (copiedCache || copiedCustom) {
|
|
3122
3357
|
return { seeded: true, strategy: "workspace-cache" };
|
|
@@ -3207,7 +3442,7 @@ function runArgvCommand(command, args, cwd) {
|
|
|
3207
3442
|
}
|
|
3208
3443
|
function resolveInitCommand(initFlags) {
|
|
3209
3444
|
const bundledCliEntrypoint = fileURLToPath(new URL("./bin.js", import.meta.url));
|
|
3210
|
-
const cliEntrypoint =
|
|
3445
|
+
const cliEntrypoint = existsSync17(bundledCliEntrypoint) ? bundledCliEntrypoint : process.argv[1] && existsSync17(process.argv[1]) ? process.argv[1] : null;
|
|
3211
3446
|
if (cliEntrypoint) {
|
|
3212
3447
|
return {
|
|
3213
3448
|
command: process.execPath,
|
|
@@ -3234,7 +3469,7 @@ async function cmdNewProject(projectName, options) {
|
|
|
3234
3469
|
process.exitCode = 1;
|
|
3235
3470
|
return;
|
|
3236
3471
|
}
|
|
3237
|
-
if (
|
|
3472
|
+
if (existsSync17(projectDir)) {
|
|
3238
3473
|
console.error(error2(`Directory "${projectName}" already exists.`));
|
|
3239
3474
|
process.exitCode = 1;
|
|
3240
3475
|
return;
|
|
@@ -3248,7 +3483,7 @@ async function cmdNewProject(projectName, options) {
|
|
|
3248
3483
|
return;
|
|
3249
3484
|
}
|
|
3250
3485
|
console.log(heading(`Creating ${projectName}...`));
|
|
3251
|
-
|
|
3486
|
+
mkdirSync9(projectDir, { recursive: true });
|
|
3252
3487
|
console.log(dim2(` Created ${projectName}/`));
|
|
3253
3488
|
const title = projectName.replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
3254
3489
|
if (shouldBootstrapRuntime && bootstrapAdapter) {
|
|
@@ -3339,21 +3574,21 @@ ${YELLOW4}Decantr init encountered issues. Run \`decantr init\` manually inside
|
|
|
3339
3574
|
console.log("");
|
|
3340
3575
|
}
|
|
3341
3576
|
function detectPackageManager() {
|
|
3342
|
-
if (
|
|
3577
|
+
if (existsSync17(join18(process.cwd(), "pnpm-lock.yaml")) || existsSync17(join18(process.cwd(), "pnpm-workspace.yaml"))) {
|
|
3343
3578
|
return "pnpm";
|
|
3344
3579
|
}
|
|
3345
|
-
if (
|
|
3580
|
+
if (existsSync17(join18(process.cwd(), "yarn.lock"))) {
|
|
3346
3581
|
return "yarn";
|
|
3347
3582
|
}
|
|
3348
|
-
if (
|
|
3583
|
+
if (existsSync17(join18(process.cwd(), "bun.lockb")) || existsSync17(join18(process.cwd(), "bun.lock"))) {
|
|
3349
3584
|
return "bun";
|
|
3350
3585
|
}
|
|
3351
3586
|
return "npm";
|
|
3352
3587
|
}
|
|
3353
3588
|
|
|
3354
3589
|
// src/commands/publish.ts
|
|
3355
|
-
import { existsSync as
|
|
3356
|
-
import { join as
|
|
3590
|
+
import { existsSync as existsSync18, readFileSync as readFileSync12 } from "fs";
|
|
3591
|
+
import { join as join19 } from "path";
|
|
3357
3592
|
import {
|
|
3358
3593
|
API_CONTENT_TYPE_TO_CONTENT_TYPE,
|
|
3359
3594
|
CONTENT_TYPE_TO_API_CONTENT_TYPE as CONTENT_TYPE_TO_API_CONTENT_TYPE2,
|
|
@@ -3369,8 +3604,8 @@ async function cmdPublish(type, name, projectRoot = process.cwd()) {
|
|
|
3369
3604
|
}
|
|
3370
3605
|
const singularType = API_CONTENT_TYPE_TO_CONTENT_TYPE[type] || type;
|
|
3371
3606
|
const pluralType = CONTENT_TYPE_TO_API_CONTENT_TYPE2[type] || CONTENT_TYPE_TO_API_CONTENT_TYPE2[singularType] || `${type}s`;
|
|
3372
|
-
const customPath =
|
|
3373
|
-
if (!
|
|
3607
|
+
const customPath = join19(projectRoot, ".decantr", "custom", pluralType, `${name}.json`);
|
|
3608
|
+
if (!existsSync18(customPath)) {
|
|
3374
3609
|
console.error(`Custom ${singularType} "${name}" not found at ${customPath}`);
|
|
3375
3610
|
console.error(`Create one first: decantr create ${singularType} ${name}`);
|
|
3376
3611
|
process.exitCode = 1;
|
|
@@ -3378,7 +3613,7 @@ async function cmdPublish(type, name, projectRoot = process.cwd()) {
|
|
|
3378
3613
|
}
|
|
3379
3614
|
let data;
|
|
3380
3615
|
try {
|
|
3381
|
-
data = JSON.parse(
|
|
3616
|
+
data = JSON.parse(readFileSync12(customPath, "utf-8"));
|
|
3382
3617
|
} catch {
|
|
3383
3618
|
console.error(`Failed to parse ${customPath}`);
|
|
3384
3619
|
process.exitCode = 1;
|
|
@@ -3416,25 +3651,25 @@ async function cmdPublish(type, name, projectRoot = process.cwd()) {
|
|
|
3416
3651
|
}
|
|
3417
3652
|
|
|
3418
3653
|
// src/commands/refresh.ts
|
|
3419
|
-
import { existsSync as
|
|
3420
|
-
import { join as
|
|
3421
|
-
import { isV3 as
|
|
3654
|
+
import { existsSync as existsSync19, readFileSync as readFileSync13 } from "fs";
|
|
3655
|
+
import { join as join20 } from "path";
|
|
3656
|
+
import { isV3 as isV34 } from "@decantr/essence-spec";
|
|
3422
3657
|
var GREEN7 = "\x1B[32m";
|
|
3423
3658
|
var RED6 = "\x1B[31m";
|
|
3424
3659
|
var DIM7 = "\x1B[2m";
|
|
3425
3660
|
var RESET7 = "\x1B[0m";
|
|
3426
3661
|
async function cmdRefresh(projectRoot = process.cwd(), options = {}) {
|
|
3427
|
-
const essencePath =
|
|
3428
|
-
if (!
|
|
3662
|
+
const essencePath = join20(projectRoot, "decantr.essence.json");
|
|
3663
|
+
if (!existsSync19(essencePath)) {
|
|
3429
3664
|
console.error(`${RED6}No decantr.essence.json found. Run \`decantr init\` first.${RESET7}`);
|
|
3430
3665
|
process.exitCode = 1;
|
|
3431
3666
|
return;
|
|
3432
3667
|
}
|
|
3433
3668
|
let essence;
|
|
3434
3669
|
try {
|
|
3435
|
-
const raw =
|
|
3670
|
+
const raw = readFileSync13(essencePath, "utf-8");
|
|
3436
3671
|
const parsed = JSON.parse(raw);
|
|
3437
|
-
if (!
|
|
3672
|
+
if (!isV34(parsed)) {
|
|
3438
3673
|
console.error(`${RED6}Essence is not v3. Run \`decantr migrate\` first.${RESET7}`);
|
|
3439
3674
|
process.exitCode = 1;
|
|
3440
3675
|
return;
|
|
@@ -3446,7 +3681,7 @@ async function cmdRefresh(projectRoot = process.cwd(), options = {}) {
|
|
|
3446
3681
|
return;
|
|
3447
3682
|
}
|
|
3448
3683
|
const registryClient = new RegistryClient({
|
|
3449
|
-
cacheDir:
|
|
3684
|
+
cacheDir: join20(projectRoot, ".decantr", "cache"),
|
|
3450
3685
|
offline: options.offline
|
|
3451
3686
|
});
|
|
3452
3687
|
console.log("Regenerating derived files...\n");
|
|
@@ -3466,8 +3701,8 @@ async function cmdRefresh(projectRoot = process.cwd(), options = {}) {
|
|
|
3466
3701
|
}
|
|
3467
3702
|
|
|
3468
3703
|
// src/commands/registry-mirror.ts
|
|
3469
|
-
import { mkdirSync as
|
|
3470
|
-
import { join as
|
|
3704
|
+
import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync10 } from "fs";
|
|
3705
|
+
import { join as join21 } from "path";
|
|
3471
3706
|
import { API_CONTENT_TYPES, RegistryAPIClient as RegistryAPIClient2 } from "@decantr/registry";
|
|
3472
3707
|
var GREEN8 = "\x1B[32m";
|
|
3473
3708
|
var RED7 = "\x1B[31m";
|
|
@@ -3495,7 +3730,7 @@ async function cmdRegistryMirror(projectRoot, options = {}) {
|
|
|
3495
3730
|
process.exitCode = 1;
|
|
3496
3731
|
return;
|
|
3497
3732
|
}
|
|
3498
|
-
const cacheDir =
|
|
3733
|
+
const cacheDir = join21(projectRoot, ".decantr", "cache");
|
|
3499
3734
|
const counts = {};
|
|
3500
3735
|
const failed = [];
|
|
3501
3736
|
console.log(`
|
|
@@ -3505,19 +3740,19 @@ Mirroring registry content to ${DIM8}.decantr/cache/${RESET8}
|
|
|
3505
3740
|
try {
|
|
3506
3741
|
const result = await apiClient.listContent(type, { namespace: "@official" });
|
|
3507
3742
|
const items = result.items;
|
|
3508
|
-
const typeDir =
|
|
3509
|
-
|
|
3510
|
-
|
|
3743
|
+
const typeDir = join21(cacheDir, "@official", type);
|
|
3744
|
+
mkdirSync10(typeDir, { recursive: true });
|
|
3745
|
+
writeFileSync10(join21(typeDir, "index.json"), JSON.stringify(result, null, 2));
|
|
3511
3746
|
let itemCount = 0;
|
|
3512
3747
|
for (const item of items) {
|
|
3513
3748
|
const slug = item.slug || item.id;
|
|
3514
3749
|
if (!slug) continue;
|
|
3515
3750
|
try {
|
|
3516
3751
|
const fullItem = await apiClient.getContent(type, "@official", slug);
|
|
3517
|
-
|
|
3752
|
+
writeFileSync10(join21(typeDir, `${slug}.json`), JSON.stringify(fullItem, null, 2));
|
|
3518
3753
|
itemCount++;
|
|
3519
3754
|
} catch {
|
|
3520
|
-
|
|
3755
|
+
writeFileSync10(join21(typeDir, `${slug}.json`), JSON.stringify(item, null, 2));
|
|
3521
3756
|
itemCount++;
|
|
3522
3757
|
}
|
|
3523
3758
|
}
|
|
@@ -3532,8 +3767,8 @@ Mirroring registry content to ${DIM8}.decantr/cache/${RESET8}
|
|
|
3532
3767
|
mirrored_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3533
3768
|
counts
|
|
3534
3769
|
};
|
|
3535
|
-
|
|
3536
|
-
|
|
3770
|
+
mkdirSync10(join21(cacheDir), { recursive: true });
|
|
3771
|
+
writeFileSync10(join21(cacheDir, "mirror-manifest.json"), JSON.stringify(manifest, null, 2));
|
|
3537
3772
|
const totalItems = Object.values(counts).reduce((a, b) => a + b, 0);
|
|
3538
3773
|
console.log("");
|
|
3539
3774
|
if (failed.length > 0) {
|
|
@@ -3550,29 +3785,29 @@ Mirroring registry content to ${DIM8}.decantr/cache/${RESET8}
|
|
|
3550
3785
|
}
|
|
3551
3786
|
|
|
3552
3787
|
// src/commands/remove.ts
|
|
3553
|
-
import { existsSync as
|
|
3554
|
-
import { join as
|
|
3555
|
-
import { isV3 as
|
|
3788
|
+
import { existsSync as existsSync21, readFileSync as readFileSync14, rmSync as rmSync2, writeFileSync as writeFileSync11 } from "fs";
|
|
3789
|
+
import { join as join22 } from "path";
|
|
3790
|
+
import { isV3 as isV35, migrateV30ToV31 as migrateV30ToV312 } from "@decantr/essence-spec";
|
|
3556
3791
|
var GREEN9 = "\x1B[32m";
|
|
3557
3792
|
var RED8 = "\x1B[31m";
|
|
3558
3793
|
var DIM9 = "\x1B[2m";
|
|
3559
3794
|
var RESET9 = "\x1B[0m";
|
|
3560
3795
|
function readAndMigrate2(projectRoot) {
|
|
3561
|
-
const essencePath =
|
|
3562
|
-
if (!
|
|
3796
|
+
const essencePath = join22(projectRoot, "decantr.essence.json");
|
|
3797
|
+
if (!existsSync21(essencePath)) {
|
|
3563
3798
|
console.error(`${RED8}No decantr.essence.json found. Run \`decantr init\` first.${RESET9}`);
|
|
3564
3799
|
process.exitCode = 1;
|
|
3565
3800
|
return null;
|
|
3566
3801
|
}
|
|
3567
3802
|
let parsed;
|
|
3568
3803
|
try {
|
|
3569
|
-
parsed = JSON.parse(
|
|
3804
|
+
parsed = JSON.parse(readFileSync14(essencePath, "utf-8"));
|
|
3570
3805
|
} catch (e) {
|
|
3571
3806
|
console.error(`${RED8}Could not read essence: ${e.message}${RESET9}`);
|
|
3572
3807
|
process.exitCode = 1;
|
|
3573
3808
|
return null;
|
|
3574
3809
|
}
|
|
3575
|
-
if (!
|
|
3810
|
+
if (!isV35(parsed)) {
|
|
3576
3811
|
console.error(`${RED8}Essence is not v3. Run \`decantr migrate\` first.${RESET9}`);
|
|
3577
3812
|
process.exitCode = 1;
|
|
3578
3813
|
return null;
|
|
@@ -3581,7 +3816,7 @@ function readAndMigrate2(projectRoot) {
|
|
|
3581
3816
|
return { essence, essencePath };
|
|
3582
3817
|
}
|
|
3583
3818
|
function writeEssence2(essencePath, essence) {
|
|
3584
|
-
|
|
3819
|
+
writeFileSync11(essencePath, JSON.stringify(essence, null, 2) + "\n");
|
|
3585
3820
|
}
|
|
3586
3821
|
function recomputeGlobalFeatures(essence) {
|
|
3587
3822
|
const all = /* @__PURE__ */ new Set();
|
|
@@ -3623,14 +3858,14 @@ async function cmdRemoveSection(sectionId, args, projectRoot = process.cwd()) {
|
|
|
3623
3858
|
sections.splice(idx, 1);
|
|
3624
3859
|
recomputeGlobalFeatures(essence);
|
|
3625
3860
|
removeRoutes(essence, sectionId);
|
|
3626
|
-
const contextFile =
|
|
3627
|
-
if (
|
|
3861
|
+
const contextFile = join22(projectRoot, ".decantr", "context", `${sectionId}.md`);
|
|
3862
|
+
if (existsSync21(contextFile)) {
|
|
3628
3863
|
rmSync2(contextFile);
|
|
3629
3864
|
}
|
|
3630
3865
|
writeEssence2(essencePath, essence);
|
|
3631
3866
|
console.log(`${GREEN9}Removed section "${sectionId}".${RESET9}`);
|
|
3632
3867
|
const registryClient = new RegistryClient({
|
|
3633
|
-
cacheDir:
|
|
3868
|
+
cacheDir: join22(projectRoot, ".decantr", "cache")
|
|
3634
3869
|
});
|
|
3635
3870
|
await refreshDerivedFiles(projectRoot, essence, registryClient);
|
|
3636
3871
|
console.log(`${GREEN9}Derived files refreshed.${RESET9}`);
|
|
@@ -3666,7 +3901,7 @@ async function cmdRemovePage(path, args, projectRoot = process.cwd()) {
|
|
|
3666
3901
|
writeEssence2(essencePath, essence);
|
|
3667
3902
|
console.log(`${GREEN9}Removed page "${pageId}" from section "${sectionId}".${RESET9}`);
|
|
3668
3903
|
const registryClient = new RegistryClient({
|
|
3669
|
-
cacheDir:
|
|
3904
|
+
cacheDir: join22(projectRoot, ".decantr", "cache")
|
|
3670
3905
|
});
|
|
3671
3906
|
await refreshDerivedFiles(projectRoot, essence, registryClient);
|
|
3672
3907
|
console.log(`${GREEN9}Derived files refreshed.${RESET9}`);
|
|
@@ -3707,15 +3942,15 @@ async function cmdRemoveFeature(feature, args, projectRoot = process.cwd()) {
|
|
|
3707
3942
|
const target = sectionId ? `section "${sectionId}" and global` : "global";
|
|
3708
3943
|
console.log(`${GREEN9}Removed feature "${feature}" from ${target} features.${RESET9}`);
|
|
3709
3944
|
const registryClient = new RegistryClient({
|
|
3710
|
-
cacheDir:
|
|
3945
|
+
cacheDir: join22(projectRoot, ".decantr", "cache")
|
|
3711
3946
|
});
|
|
3712
3947
|
await refreshDerivedFiles(projectRoot, essence, registryClient);
|
|
3713
3948
|
console.log(`${GREEN9}Derived files refreshed.${RESET9}`);
|
|
3714
3949
|
}
|
|
3715
3950
|
|
|
3716
3951
|
// src/commands/sync-drift.ts
|
|
3717
|
-
import { existsSync as
|
|
3718
|
-
import { join as
|
|
3952
|
+
import { existsSync as existsSync22, readFileSync as readFileSync15, writeFileSync as writeFileSync12 } from "fs";
|
|
3953
|
+
import { join as join23 } from "path";
|
|
3719
3954
|
var GREEN10 = "\x1B[32m";
|
|
3720
3955
|
var RED9 = "\x1B[31m";
|
|
3721
3956
|
var YELLOW6 = "\x1B[33m";
|
|
@@ -3724,8 +3959,8 @@ var DIM10 = "\x1B[2m";
|
|
|
3724
3959
|
var BOLD4 = "\x1B[1m";
|
|
3725
3960
|
var CYAN5 = "\x1B[36m";
|
|
3726
3961
|
async function cmdSyncDrift(projectRoot = process.cwd()) {
|
|
3727
|
-
const driftLogPath =
|
|
3728
|
-
if (!
|
|
3962
|
+
const driftLogPath = join23(projectRoot, ".decantr", "drift-log.json");
|
|
3963
|
+
if (!existsSync22(driftLogPath)) {
|
|
3729
3964
|
console.log(`${GREEN10}No drift log found \u2014 no drift recorded.${RESET10}`);
|
|
3730
3965
|
console.log(
|
|
3731
3966
|
`${DIM10}Drift is logged when guard violations are detected during development.${RESET10}`
|
|
@@ -3734,7 +3969,7 @@ async function cmdSyncDrift(projectRoot = process.cwd()) {
|
|
|
3734
3969
|
}
|
|
3735
3970
|
let entries;
|
|
3736
3971
|
try {
|
|
3737
|
-
entries = JSON.parse(
|
|
3972
|
+
entries = JSON.parse(readFileSync15(driftLogPath, "utf-8"));
|
|
3738
3973
|
} catch {
|
|
3739
3974
|
console.error(`${RED9}Could not parse drift-log.json${RESET10}`);
|
|
3740
3975
|
process.exitCode = 1;
|
|
@@ -3780,13 +4015,13 @@ ${BOLD4}Unresolved Drift Entries (${unresolved.length})${RESET10}
|
|
|
3780
4015
|
console.log("");
|
|
3781
4016
|
}
|
|
3782
4017
|
function resolveDriftEntries(projectRoot, options) {
|
|
3783
|
-
const driftLogPath =
|
|
3784
|
-
if (!
|
|
4018
|
+
const driftLogPath = join23(projectRoot, ".decantr", "drift-log.json");
|
|
4019
|
+
if (!existsSync22(driftLogPath)) {
|
|
3785
4020
|
return { success: true };
|
|
3786
4021
|
}
|
|
3787
4022
|
if (options.clear) {
|
|
3788
4023
|
try {
|
|
3789
|
-
|
|
4024
|
+
writeFileSync12(driftLogPath, "[]");
|
|
3790
4025
|
return { success: true };
|
|
3791
4026
|
} catch (e) {
|
|
3792
4027
|
return { success: false, error: `Could not clear drift log: ${e.message}` };
|
|
@@ -3794,7 +4029,7 @@ function resolveDriftEntries(projectRoot, options) {
|
|
|
3794
4029
|
}
|
|
3795
4030
|
let entries;
|
|
3796
4031
|
try {
|
|
3797
|
-
entries = JSON.parse(
|
|
4032
|
+
entries = JSON.parse(readFileSync15(driftLogPath, "utf-8"));
|
|
3798
4033
|
} catch {
|
|
3799
4034
|
return { success: false, error: "Could not parse drift-log.json" };
|
|
3800
4035
|
}
|
|
@@ -3813,7 +4048,7 @@ function resolveDriftEntries(projectRoot, options) {
|
|
|
3813
4048
|
}
|
|
3814
4049
|
}
|
|
3815
4050
|
try {
|
|
3816
|
-
|
|
4051
|
+
writeFileSync12(driftLogPath, JSON.stringify(entries, null, 2));
|
|
3817
4052
|
return { success: true };
|
|
3818
4053
|
} catch (e) {
|
|
3819
4054
|
return { success: false, error: `Could not write drift log: ${e.message}` };
|
|
@@ -3821,9 +4056,9 @@ function resolveDriftEntries(projectRoot, options) {
|
|
|
3821
4056
|
}
|
|
3822
4057
|
|
|
3823
4058
|
// src/commands/theme-switch.ts
|
|
3824
|
-
import { existsSync as
|
|
3825
|
-
import { join as
|
|
3826
|
-
import { isV3 as
|
|
4059
|
+
import { existsSync as existsSync23, readFileSync as readFileSync16, writeFileSync as writeFileSync13 } from "fs";
|
|
4060
|
+
import { join as join24 } from "path";
|
|
4061
|
+
import { isV3 as isV36, migrateV30ToV31 as migrateV30ToV313 } from "@decantr/essence-spec";
|
|
3827
4062
|
var GREEN11 = "\x1B[32m";
|
|
3828
4063
|
var RED10 = "\x1B[31m";
|
|
3829
4064
|
var YELLOW7 = "\x1B[33m";
|
|
@@ -3839,21 +4074,21 @@ async function cmdThemeSwitch(themeName, args, projectRoot = process.cwd()) {
|
|
|
3839
4074
|
process.exitCode = 1;
|
|
3840
4075
|
return;
|
|
3841
4076
|
}
|
|
3842
|
-
const essencePath =
|
|
3843
|
-
if (!
|
|
4077
|
+
const essencePath = join24(projectRoot, "decantr.essence.json");
|
|
4078
|
+
if (!existsSync23(essencePath)) {
|
|
3844
4079
|
console.error(`${RED10}No decantr.essence.json found. Run \`decantr init\` first.${RESET11}`);
|
|
3845
4080
|
process.exitCode = 1;
|
|
3846
4081
|
return;
|
|
3847
4082
|
}
|
|
3848
4083
|
let parsed;
|
|
3849
4084
|
try {
|
|
3850
|
-
parsed = JSON.parse(
|
|
4085
|
+
parsed = JSON.parse(readFileSync16(essencePath, "utf-8"));
|
|
3851
4086
|
} catch (e) {
|
|
3852
4087
|
console.error(`${RED10}Could not read essence: ${e.message}${RESET11}`);
|
|
3853
4088
|
process.exitCode = 1;
|
|
3854
4089
|
return;
|
|
3855
4090
|
}
|
|
3856
|
-
if (!
|
|
4091
|
+
if (!isV36(parsed)) {
|
|
3857
4092
|
console.error(`${RED10}Essence is not v3. Run \`decantr migrate\` first.${RESET11}`);
|
|
3858
4093
|
process.exitCode = 1;
|
|
3859
4094
|
return;
|
|
@@ -3896,7 +4131,7 @@ async function cmdThemeSwitch(themeName, args, projectRoot = process.cwd()) {
|
|
|
3896
4131
|
essence.dna.theme.mode = mode;
|
|
3897
4132
|
}
|
|
3898
4133
|
const registryClient = new RegistryClient({
|
|
3899
|
-
cacheDir:
|
|
4134
|
+
cacheDir: join24(projectRoot, ".decantr", "cache")
|
|
3900
4135
|
});
|
|
3901
4136
|
try {
|
|
3902
4137
|
const themeResult = await registryClient.fetchTheme(themeName);
|
|
@@ -3911,7 +4146,7 @@ async function cmdThemeSwitch(themeName, args, projectRoot = process.cwd()) {
|
|
|
3911
4146
|
}
|
|
3912
4147
|
} catch {
|
|
3913
4148
|
}
|
|
3914
|
-
|
|
4149
|
+
writeFileSync13(essencePath, JSON.stringify(essence, null, 2) + "\n");
|
|
3915
4150
|
console.log(`${GREEN11}Switched theme: ${oldThemeId} \u2192 ${themeName}${RESET11}`);
|
|
3916
4151
|
if (shape) console.log(` ${DIM11}Shape: ${shape}${RESET11}`);
|
|
3917
4152
|
if (mode) console.log(` ${DIM11}Mode: ${mode}${RESET11}`);
|
|
@@ -3992,6 +4227,7 @@ ${CYAN6}Detected project configuration:${RESET12}`);
|
|
|
3992
4227
|
}
|
|
3993
4228
|
async function runInteractivePrompts(detected, archetypes, blueprints, themes, workflowSeed) {
|
|
3994
4229
|
showDetection(detected);
|
|
4230
|
+
const existingProject = Boolean(workflowSeed?.existing) || detected.existingEssence || hasExistingProjectFootprint(detected);
|
|
3995
4231
|
const blueprintOptions = [
|
|
3996
4232
|
{ value: "none", label: "none", description: "Start from scratch (blank canvas)" },
|
|
3997
4233
|
...blueprints.map((b) => ({
|
|
@@ -4002,12 +4238,21 @@ async function runInteractivePrompts(detected, archetypes, blueprints, themes, w
|
|
|
4002
4238
|
];
|
|
4003
4239
|
const blueprint = await select("What are you building?", blueprintOptions, 0, true);
|
|
4004
4240
|
const isBlank = blueprint === "none";
|
|
4005
|
-
const themeOptions =
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
|
|
4241
|
+
const themeOptions = [
|
|
4242
|
+
...existingProject ? [
|
|
4243
|
+
{
|
|
4244
|
+
value: "existing",
|
|
4245
|
+
label: "existing",
|
|
4246
|
+
description: "Preserve the existing styling system"
|
|
4247
|
+
}
|
|
4248
|
+
] : [],
|
|
4249
|
+
...themes.map((t) => ({
|
|
4250
|
+
value: t.id,
|
|
4251
|
+
label: t.id,
|
|
4252
|
+
description: t.description
|
|
4253
|
+
}))
|
|
4254
|
+
];
|
|
4255
|
+
const desiredTheme = workflowSeed?.theme || (existingProject ? "existing" : "luminarum");
|
|
4011
4256
|
const defaultThemeIdx = Math.max(
|
|
4012
4257
|
0,
|
|
4013
4258
|
themeOptions.findIndex((t) => t.value === desiredTheme)
|
|
@@ -4020,7 +4265,7 @@ async function runInteractivePrompts(detected, archetypes, blueprints, themes, w
|
|
|
4020
4265
|
{ value: "light", label: "light", description: "Light background" },
|
|
4021
4266
|
{ value: "auto", label: "auto", description: "Follow system preference" }
|
|
4022
4267
|
],
|
|
4023
|
-
workflowSeed?.mode === "light" ? 1 : workflowSeed?.mode === "auto" ? 2 : 0
|
|
4268
|
+
workflowSeed?.mode === "light" ? 1 : workflowSeed?.mode === "auto" || existingProject ? 2 : 0
|
|
4024
4269
|
);
|
|
4025
4270
|
const shape = await select(
|
|
4026
4271
|
"Border shape",
|
|
@@ -4153,17 +4398,18 @@ function parseFlags(args, detected) {
|
|
|
4153
4398
|
return options;
|
|
4154
4399
|
}
|
|
4155
4400
|
function mergeWithDefaults(flags, detected, workflowSeed) {
|
|
4401
|
+
const existingProject = flags.existing || workflowSeed?.existing || detected.existingEssence || hasExistingProjectFootprint(detected);
|
|
4156
4402
|
return {
|
|
4157
4403
|
blueprint: flags.blueprint,
|
|
4158
4404
|
archetype: flags.archetype,
|
|
4159
|
-
theme: flags.theme || workflowSeed?.theme || "luminarum",
|
|
4160
|
-
mode: flags.mode || workflowSeed?.mode || "dark",
|
|
4405
|
+
theme: flags.theme || workflowSeed?.theme || (existingProject ? "existing" : "luminarum"),
|
|
4406
|
+
mode: flags.mode || workflowSeed?.mode || (existingProject ? "auto" : "dark"),
|
|
4161
4407
|
shape: flags.shape || "rounded",
|
|
4162
4408
|
target: flags.target || workflowSeed?.target || (detected.framework !== "unknown" ? detected.framework : "react"),
|
|
4163
4409
|
guard: flags.guard || workflowSeed?.guard || (detected.existingEssence ? "guided" : "strict"),
|
|
4164
4410
|
density: flags.density || workflowSeed?.density || "comfortable",
|
|
4165
|
-
shell: flags.shell || workflowSeed?.shell || "sidebar-main",
|
|
4166
|
-
personality: flags.personality || ["professional"],
|
|
4411
|
+
shell: flags.shell || workflowSeed?.shell || (existingProject ? "observed-existing-shell" : "sidebar-main"),
|
|
4412
|
+
personality: flags.personality || (existingProject ? ["observed brownfield product"] : ["professional"]),
|
|
4167
4413
|
features: flags.features || [],
|
|
4168
4414
|
existing: flags.existing || workflowSeed?.existing || detected.existingEssence,
|
|
4169
4415
|
workflowMode: flags.workflowMode || workflowSeed?.workflowMode,
|
|
@@ -4207,8 +4453,8 @@ async function runSimplifiedInit(blueprints) {
|
|
|
4207
4453
|
}
|
|
4208
4454
|
|
|
4209
4455
|
// src/theme-commands.ts
|
|
4210
|
-
import { existsSync as
|
|
4211
|
-
import { join as
|
|
4456
|
+
import { existsSync as existsSync24, mkdirSync as mkdirSync11, readdirSync as readdirSync4, readFileSync as readFileSync17, rmSync as rmSync3, writeFileSync as writeFileSync14 } from "fs";
|
|
4457
|
+
import { join as join25 } from "path";
|
|
4212
4458
|
var REQUIRED_FIELDS = [
|
|
4213
4459
|
"$schema",
|
|
4214
4460
|
"id",
|
|
@@ -4268,20 +4514,20 @@ function validateCustomTheme(theme) {
|
|
|
4268
4514
|
};
|
|
4269
4515
|
}
|
|
4270
4516
|
function createTheme(projectRoot, id, name) {
|
|
4271
|
-
const customThemesDir =
|
|
4272
|
-
const themePath =
|
|
4273
|
-
const howToPath =
|
|
4274
|
-
|
|
4275
|
-
if (
|
|
4517
|
+
const customThemesDir = join25(projectRoot, ".decantr", "custom", "themes");
|
|
4518
|
+
const themePath = join25(customThemesDir, `${id}.json`);
|
|
4519
|
+
const howToPath = join25(customThemesDir, "how-to-theme.md");
|
|
4520
|
+
mkdirSync11(customThemesDir, { recursive: true });
|
|
4521
|
+
if (existsSync24(themePath)) {
|
|
4276
4522
|
return {
|
|
4277
4523
|
success: false,
|
|
4278
4524
|
error: `Theme "${id}" already exists at ${themePath}`
|
|
4279
4525
|
};
|
|
4280
4526
|
}
|
|
4281
4527
|
const skeleton = getThemeSkeleton(id, name);
|
|
4282
|
-
|
|
4283
|
-
if (!
|
|
4284
|
-
|
|
4528
|
+
writeFileSync14(themePath, JSON.stringify(skeleton, null, 2));
|
|
4529
|
+
if (!existsSync24(howToPath)) {
|
|
4530
|
+
writeFileSync14(howToPath, getHowToThemeDoc());
|
|
4285
4531
|
}
|
|
4286
4532
|
return {
|
|
4287
4533
|
success: true,
|
|
@@ -4289,17 +4535,17 @@ function createTheme(projectRoot, id, name) {
|
|
|
4289
4535
|
};
|
|
4290
4536
|
}
|
|
4291
4537
|
function listCustomThemes(projectRoot) {
|
|
4292
|
-
const customThemesDir =
|
|
4293
|
-
if (!
|
|
4538
|
+
const customThemesDir = join25(projectRoot, ".decantr", "custom", "themes");
|
|
4539
|
+
if (!existsSync24(customThemesDir)) {
|
|
4294
4540
|
return [];
|
|
4295
4541
|
}
|
|
4296
4542
|
const themes = [];
|
|
4297
4543
|
try {
|
|
4298
|
-
const files =
|
|
4544
|
+
const files = readdirSync4(customThemesDir).filter((f) => f.endsWith(".json"));
|
|
4299
4545
|
for (const file of files) {
|
|
4300
|
-
const filePath =
|
|
4546
|
+
const filePath = join25(customThemesDir, file);
|
|
4301
4547
|
try {
|
|
4302
|
-
const data = JSON.parse(
|
|
4548
|
+
const data = JSON.parse(readFileSync17(filePath, "utf-8"));
|
|
4303
4549
|
themes.push({
|
|
4304
4550
|
id: data.id || file.replace(".json", ""),
|
|
4305
4551
|
name: data.name || data.id,
|
|
@@ -4314,8 +4560,8 @@ function listCustomThemes(projectRoot) {
|
|
|
4314
4560
|
return themes;
|
|
4315
4561
|
}
|
|
4316
4562
|
function deleteTheme(projectRoot, id) {
|
|
4317
|
-
const themePath =
|
|
4318
|
-
if (!
|
|
4563
|
+
const themePath = join25(projectRoot, ".decantr", "custom", "themes", `${id}.json`);
|
|
4564
|
+
if (!existsSync24(themePath)) {
|
|
4319
4565
|
return {
|
|
4320
4566
|
success: false,
|
|
4321
4567
|
error: `Theme "${id}" not found at ${themePath}`
|
|
@@ -4332,7 +4578,7 @@ function deleteTheme(projectRoot, id) {
|
|
|
4332
4578
|
}
|
|
4333
4579
|
}
|
|
4334
4580
|
function importTheme(projectRoot, sourcePath) {
|
|
4335
|
-
if (!
|
|
4581
|
+
if (!existsSync24(sourcePath)) {
|
|
4336
4582
|
return {
|
|
4337
4583
|
success: false,
|
|
4338
4584
|
errors: [`Source file not found: ${sourcePath}`]
|
|
@@ -4340,7 +4586,7 @@ function importTheme(projectRoot, sourcePath) {
|
|
|
4340
4586
|
}
|
|
4341
4587
|
let theme;
|
|
4342
4588
|
try {
|
|
4343
|
-
theme = JSON.parse(
|
|
4589
|
+
theme = JSON.parse(readFileSync17(sourcePath, "utf-8"));
|
|
4344
4590
|
} catch (e) {
|
|
4345
4591
|
return {
|
|
4346
4592
|
success: false,
|
|
@@ -4356,14 +4602,14 @@ function importTheme(projectRoot, sourcePath) {
|
|
|
4356
4602
|
}
|
|
4357
4603
|
theme.source = "custom";
|
|
4358
4604
|
const id = theme.id;
|
|
4359
|
-
const customThemesDir =
|
|
4360
|
-
const destPath =
|
|
4361
|
-
|
|
4362
|
-
const howToPath =
|
|
4363
|
-
if (!
|
|
4364
|
-
|
|
4365
|
-
}
|
|
4366
|
-
|
|
4605
|
+
const customThemesDir = join25(projectRoot, ".decantr", "custom", "themes");
|
|
4606
|
+
const destPath = join25(customThemesDir, `${id}.json`);
|
|
4607
|
+
mkdirSync11(customThemesDir, { recursive: true });
|
|
4608
|
+
const howToPath = join25(customThemesDir, "how-to-theme.md");
|
|
4609
|
+
if (!existsSync24(howToPath)) {
|
|
4610
|
+
writeFileSync14(howToPath, getHowToThemeDoc());
|
|
4611
|
+
}
|
|
4612
|
+
writeFileSync14(destPath, JSON.stringify(theme, null, 2));
|
|
4367
4613
|
return {
|
|
4368
4614
|
success: true,
|
|
4369
4615
|
path: destPath
|
|
@@ -4371,19 +4617,19 @@ function importTheme(projectRoot, sourcePath) {
|
|
|
4371
4617
|
}
|
|
4372
4618
|
|
|
4373
4619
|
// src/workspace.ts
|
|
4374
|
-
import { existsSync as
|
|
4375
|
-
import { dirname as dirname3, join as
|
|
4620
|
+
import { existsSync as existsSync25, readdirSync as readdirSync5, readFileSync as readFileSync18 } from "fs";
|
|
4621
|
+
import { dirname as dirname3, join as join26, resolve as resolve3 } from "path";
|
|
4376
4622
|
function readPackageJson(dir) {
|
|
4377
|
-
const path =
|
|
4378
|
-
if (!
|
|
4623
|
+
const path = join26(dir, "package.json");
|
|
4624
|
+
if (!existsSync25(path)) return null;
|
|
4379
4625
|
try {
|
|
4380
|
-
return JSON.parse(
|
|
4626
|
+
return JSON.parse(readFileSync18(path, "utf-8"));
|
|
4381
4627
|
} catch {
|
|
4382
4628
|
return null;
|
|
4383
4629
|
}
|
|
4384
4630
|
}
|
|
4385
4631
|
function hasWorkspaceMarker(dir) {
|
|
4386
|
-
if (
|
|
4632
|
+
if (existsSync25(join26(dir, "pnpm-workspace.yaml")) || existsSync25(join26(dir, "turbo.json")) || existsSync25(join26(dir, "nx.json"))) {
|
|
4387
4633
|
return true;
|
|
4388
4634
|
}
|
|
4389
4635
|
const pkg = readPackageJson(dir);
|
|
@@ -4399,7 +4645,7 @@ function findWorkspaceRoot(startDir) {
|
|
|
4399
4645
|
}
|
|
4400
4646
|
}
|
|
4401
4647
|
function looksLikeApp(dir) {
|
|
4402
|
-
if (
|
|
4648
|
+
if (existsSync25(join26(dir, "next.config.js")) || existsSync25(join26(dir, "next.config.ts")) || existsSync25(join26(dir, "next.config.mjs")) || existsSync25(join26(dir, "vite.config.ts")) || existsSync25(join26(dir, "vite.config.js")) || existsSync25(join26(dir, "angular.json")) || existsSync25(join26(dir, "svelte.config.js")) || existsSync25(join26(dir, "svelte.config.ts")) || existsSync25(join26(dir, "astro.config.mjs")) || existsSync25(join26(dir, "src")) || existsSync25(join26(dir, "app")) || existsSync25(join26(dir, "pages"))) {
|
|
4403
4649
|
return true;
|
|
4404
4650
|
}
|
|
4405
4651
|
const pkg = readPackageJson(dir);
|
|
@@ -4411,11 +4657,11 @@ function looksLikeApp(dir) {
|
|
|
4411
4657
|
function listWorkspaceApps(workspaceRoot) {
|
|
4412
4658
|
const candidates = [];
|
|
4413
4659
|
for (const base of ["apps", "packages"]) {
|
|
4414
|
-
const baseDir =
|
|
4415
|
-
if (!
|
|
4416
|
-
for (const entry of
|
|
4660
|
+
const baseDir = join26(workspaceRoot, base);
|
|
4661
|
+
if (!existsSync25(baseDir)) continue;
|
|
4662
|
+
for (const entry of readdirSync5(baseDir, { withFileTypes: true })) {
|
|
4417
4663
|
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
|
|
4418
|
-
const candidate =
|
|
4664
|
+
const candidate = join26(baseDir, entry.name);
|
|
4419
4665
|
if (looksLikeApp(candidate)) {
|
|
4420
4666
|
candidates.push(`${base}/${entry.name}`);
|
|
4421
4667
|
}
|
|
@@ -4797,7 +5043,8 @@ function generateBrownfieldPrompt(ctx) {
|
|
|
4797
5043
|
lines.push("");
|
|
4798
5044
|
if (ctx.analysisArtifacts) {
|
|
4799
5045
|
lines.push("Treat .decantr/analysis.json as the factual inventory of the current app.");
|
|
4800
|
-
lines.push("Treat .decantr/
|
|
5046
|
+
lines.push("Treat .decantr/doctrine-map.json, .decantr/ambient-context.json, and .decantr/brownfield-report.md as the ranked doctrine inventory and conflict report.");
|
|
5047
|
+
lines.push("Treat the accepted observed proposal as the source of Decantr route/section coverage.");
|
|
4801
5048
|
} else {
|
|
4802
5049
|
lines.push(
|
|
4803
5050
|
"No Decantr analysis seed is present. Start by inventorying the app before changing runtime files."
|
|
@@ -4815,16 +5062,18 @@ function generateBrownfieldPrompt(ctx) {
|
|
|
4815
5062
|
lines.push(
|
|
4816
5063
|
"1. .decantr/analysis.json for the detected framework, routes, styling, layout, and dependencies."
|
|
4817
5064
|
);
|
|
4818
|
-
lines.push("2. .decantr/
|
|
4819
|
-
lines.push("3.
|
|
5065
|
+
lines.push("2. .decantr/doctrine-map.json for ranked source precedence across security/data, architecture, design-system, workflow, feature, and assistant evidence.");
|
|
5066
|
+
lines.push("3. .decantr/ambient-context.json for assistant rules, docs, design-system, CI, schema, and workflow evidence.");
|
|
5067
|
+
lines.push("4. .decantr/brownfield-report.md for conflicts, stale risks, and acceptance context.");
|
|
5068
|
+
lines.push("5. DECANTR.md for guard rules, CSS expectations, and Decantr operating rules.");
|
|
4820
5069
|
lines.push(
|
|
4821
|
-
"
|
|
5070
|
+
"6. .decantr/context/scaffold-pack.md for the compact compiled shell, theme, feature, and route contract."
|
|
4822
5071
|
);
|
|
4823
5072
|
lines.push(
|
|
4824
|
-
"
|
|
5073
|
+
"7. .decantr/context/scaffold.md for broader topology, route map, and voice guidance."
|
|
4825
5074
|
);
|
|
4826
5075
|
lines.push(
|
|
4827
|
-
"
|
|
5076
|
+
"8. The matching section and page pack files only when you are working on those specific surfaces."
|
|
4828
5077
|
);
|
|
4829
5078
|
} else {
|
|
4830
5079
|
lines.push("1. Inventory existing framework, routes, styling, layout, rule files, and dependencies.");
|
|
@@ -4899,7 +5148,7 @@ function generateBrownfieldPrompt(ctx) {
|
|
|
4899
5148
|
);
|
|
4900
5149
|
lines.push("- Then attach or refine section pages using the matching section and page packs.");
|
|
4901
5150
|
lines.push(
|
|
4902
|
-
"- After implementation, run decantr check and decantr audit and fix contract or drift issues."
|
|
5151
|
+
"- After implementation, run decantr check --brownfield and decantr audit and fix contract or drift issues."
|
|
4903
5152
|
);
|
|
4904
5153
|
lines.push(
|
|
4905
5154
|
"- If a required context file or runtime anchor is missing, stop and report exactly what is missing before continuing."
|
|
@@ -4934,18 +5183,18 @@ function extractHostedAssetPaths(indexHtml) {
|
|
|
4934
5183
|
return [...assetPaths];
|
|
4935
5184
|
}
|
|
4936
5185
|
function readHostedDistSnapshot(distPath) {
|
|
4937
|
-
const resolvedDistPath = distPath ? resolveUserPath(distPath) :
|
|
4938
|
-
const indexPath =
|
|
4939
|
-
if (!
|
|
5186
|
+
const resolvedDistPath = distPath ? resolveUserPath(distPath) : join27(process.cwd(), "dist");
|
|
5187
|
+
const indexPath = join27(resolvedDistPath, "index.html");
|
|
5188
|
+
if (!existsSync26(indexPath)) {
|
|
4940
5189
|
return void 0;
|
|
4941
5190
|
}
|
|
4942
|
-
const indexHtml =
|
|
5191
|
+
const indexHtml = readFileSync19(indexPath, "utf-8");
|
|
4943
5192
|
const assetPaths = extractHostedAssetPaths(indexHtml);
|
|
4944
5193
|
const assets = {};
|
|
4945
5194
|
for (const assetPath of assetPaths) {
|
|
4946
|
-
const assetFilePath =
|
|
4947
|
-
if (
|
|
4948
|
-
assets[assetPath] =
|
|
5195
|
+
const assetFilePath = join27(resolvedDistPath, assetPath.replace(/^[/\\]+/, ""));
|
|
5196
|
+
if (existsSync26(assetFilePath)) {
|
|
5197
|
+
assets[assetPath] = readFileSync19(assetFilePath, "utf-8");
|
|
4949
5198
|
}
|
|
4950
5199
|
}
|
|
4951
5200
|
return {
|
|
@@ -4960,7 +5209,7 @@ function isHostedSourceSnapshotFile(path) {
|
|
|
4960
5209
|
function readHostedSourceSnapshot(sourcePath) {
|
|
4961
5210
|
if (!sourcePath) return void 0;
|
|
4962
5211
|
const resolvedSourcePath = resolveUserPath(sourcePath);
|
|
4963
|
-
if (!
|
|
5212
|
+
if (!existsSync26(resolvedSourcePath)) {
|
|
4964
5213
|
return void 0;
|
|
4965
5214
|
}
|
|
4966
5215
|
const files = {};
|
|
@@ -4974,17 +5223,17 @@ function readHostedSourceSnapshot(sourcePath) {
|
|
|
4974
5223
|
]);
|
|
4975
5224
|
const rootPrefix = basename2(resolvedSourcePath);
|
|
4976
5225
|
const walk = (absoluteDir, relativeDir) => {
|
|
4977
|
-
for (const entry of
|
|
5226
|
+
for (const entry of readdirSync6(absoluteDir, { withFileTypes: true })) {
|
|
4978
5227
|
if (ignoredDirNames.has(entry.name)) continue;
|
|
4979
|
-
const absolutePath =
|
|
4980
|
-
const relativePath =
|
|
5228
|
+
const absolutePath = join27(absoluteDir, entry.name);
|
|
5229
|
+
const relativePath = join27(relativeDir, entry.name).replace(/\\/g, "/");
|
|
4981
5230
|
if (entry.isDirectory()) {
|
|
4982
5231
|
walk(absolutePath, relativePath);
|
|
4983
5232
|
continue;
|
|
4984
5233
|
}
|
|
4985
5234
|
if (!entry.isFile()) continue;
|
|
4986
5235
|
if (!isHostedSourceSnapshotFile(relativePath)) continue;
|
|
4987
|
-
files[relativePath] =
|
|
5236
|
+
files[relativePath] = readFileSync19(absolutePath, "utf-8");
|
|
4988
5237
|
}
|
|
4989
5238
|
};
|
|
4990
5239
|
walk(resolvedSourcePath, rootPrefix);
|
|
@@ -5135,16 +5384,16 @@ async function printRegistryIntelligenceSummary(namespace, jsonOutput = false) {
|
|
|
5135
5384
|
}
|
|
5136
5385
|
async function printHostedExecutionPackBundle(essencePath, namespace, jsonOutput = false, writeContext = false) {
|
|
5137
5386
|
const client = getPublicAPIClient();
|
|
5138
|
-
const resolvedPath = essencePath ? resolveUserPath(essencePath) :
|
|
5139
|
-
if (!
|
|
5387
|
+
const resolvedPath = essencePath ? resolveUserPath(essencePath) : join27(process.cwd(), "decantr.essence.json");
|
|
5388
|
+
if (!existsSync26(resolvedPath)) {
|
|
5140
5389
|
throw new Error(`Essence file not found at ${resolvedPath}`);
|
|
5141
5390
|
}
|
|
5142
|
-
const essence = JSON.parse(
|
|
5391
|
+
const essence = JSON.parse(readFileSync19(resolvedPath, "utf-8"));
|
|
5143
5392
|
const bundle = await client.compileExecutionPacks(essence, namespace ? { namespace } : void 0);
|
|
5144
5393
|
let writtenContextPaths = [];
|
|
5145
5394
|
if (writeContext) {
|
|
5146
|
-
const contextDir =
|
|
5147
|
-
|
|
5395
|
+
const contextDir = join27(process.cwd(), ".decantr", "context");
|
|
5396
|
+
mkdirSync12(contextDir, { recursive: true });
|
|
5148
5397
|
const written = writeExecutionPackBundleArtifacts(
|
|
5149
5398
|
contextDir,
|
|
5150
5399
|
bundle
|
|
@@ -5169,7 +5418,7 @@ async function printHostedExecutionPackBundle(essencePath, namespace, jsonOutput
|
|
|
5169
5418
|
console.log(` Sections: ${typedBundle.sections.length}`);
|
|
5170
5419
|
console.log(` Mutations: ${typedBundle.mutations.length}`);
|
|
5171
5420
|
if (writeContext) {
|
|
5172
|
-
console.log(` Context bundle: ${
|
|
5421
|
+
console.log(` Context bundle: ${join27(process.cwd(), ".decantr", "context")}`);
|
|
5173
5422
|
console.log(` Files written: ${writtenContextPaths.length}`);
|
|
5174
5423
|
}
|
|
5175
5424
|
console.log("");
|
|
@@ -5182,14 +5431,14 @@ async function printHostedExecutionPackBundle(essencePath, namespace, jsonOutput
|
|
|
5182
5431
|
}
|
|
5183
5432
|
async function printHostedSelectedExecutionPack(packType, id, essencePath, namespace, jsonOutput = false, writeContext = false) {
|
|
5184
5433
|
const client = getPublicAPIClient();
|
|
5185
|
-
const resolvedPath = essencePath ? resolveUserPath(essencePath) :
|
|
5186
|
-
if (!
|
|
5434
|
+
const resolvedPath = essencePath ? resolveUserPath(essencePath) : join27(process.cwd(), "decantr.essence.json");
|
|
5435
|
+
if (!existsSync26(resolvedPath)) {
|
|
5187
5436
|
throw new Error(`Essence file not found at ${resolvedPath}`);
|
|
5188
5437
|
}
|
|
5189
5438
|
if ((packType === "section" || packType === "page" || packType === "mutation") && !id) {
|
|
5190
5439
|
throw new Error(`Pack type "${packType}" requires an id.`);
|
|
5191
5440
|
}
|
|
5192
|
-
const essence = JSON.parse(
|
|
5441
|
+
const essence = JSON.parse(readFileSync19(resolvedPath, "utf-8"));
|
|
5193
5442
|
const selected = await client.selectExecutionPack(
|
|
5194
5443
|
{
|
|
5195
5444
|
essence,
|
|
@@ -5200,17 +5449,17 @@ async function printHostedSelectedExecutionPack(packType, id, essencePath, names
|
|
|
5200
5449
|
);
|
|
5201
5450
|
let writtenContextDir = null;
|
|
5202
5451
|
if (writeContext) {
|
|
5203
|
-
const contextDir =
|
|
5204
|
-
|
|
5205
|
-
|
|
5206
|
-
|
|
5452
|
+
const contextDir = join27(process.cwd(), ".decantr", "context");
|
|
5453
|
+
mkdirSync12(contextDir, { recursive: true });
|
|
5454
|
+
writeFileSync15(
|
|
5455
|
+
join27(contextDir, "pack-manifest.json"),
|
|
5207
5456
|
JSON.stringify(selected.manifest, null, 2) + "\n"
|
|
5208
5457
|
);
|
|
5209
5458
|
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);
|
|
5210
5459
|
const markdownFile = manifestEntry?.markdown ?? `${selected.selector.packType}${selected.selector.id ? `-${selected.selector.id}` : ""}-pack.md`;
|
|
5211
5460
|
const jsonFile = manifestEntry?.json ?? `${selected.selector.packType}${selected.selector.id ? `-${selected.selector.id}` : ""}-pack.json`;
|
|
5212
|
-
|
|
5213
|
-
|
|
5461
|
+
writeFileSync15(join27(contextDir, markdownFile), selected.pack.renderedMarkdown);
|
|
5462
|
+
writeFileSync15(join27(contextDir, jsonFile), JSON.stringify(selected.pack, null, 2) + "\n");
|
|
5214
5463
|
writtenContextDir = contextDir;
|
|
5215
5464
|
}
|
|
5216
5465
|
if (jsonOutput) {
|
|
@@ -5235,20 +5484,20 @@ async function printHostedSelectedExecutionPack(packType, id, essencePath, names
|
|
|
5235
5484
|
}
|
|
5236
5485
|
async function printHostedExecutionPackManifest(essencePath, namespace, jsonOutput = false, writeContext = false) {
|
|
5237
5486
|
const client = getPublicAPIClient();
|
|
5238
|
-
const resolvedPath = essencePath ? resolveUserPath(essencePath) :
|
|
5239
|
-
if (!
|
|
5487
|
+
const resolvedPath = essencePath ? resolveUserPath(essencePath) : join27(process.cwd(), "decantr.essence.json");
|
|
5488
|
+
if (!existsSync26(resolvedPath)) {
|
|
5240
5489
|
throw new Error(`Essence file not found at ${resolvedPath}`);
|
|
5241
5490
|
}
|
|
5242
|
-
const essence = JSON.parse(
|
|
5491
|
+
const essence = JSON.parse(readFileSync19(resolvedPath, "utf-8"));
|
|
5243
5492
|
const manifest = await client.getExecutionPackManifest(
|
|
5244
5493
|
essence,
|
|
5245
5494
|
namespace ? { namespace } : void 0
|
|
5246
5495
|
);
|
|
5247
5496
|
let writtenContextDir = null;
|
|
5248
5497
|
if (writeContext) {
|
|
5249
|
-
const contextDir =
|
|
5250
|
-
|
|
5251
|
-
|
|
5498
|
+
const contextDir = join27(process.cwd(), ".decantr", "context");
|
|
5499
|
+
mkdirSync12(contextDir, { recursive: true });
|
|
5500
|
+
writeFileSync15(join27(contextDir, "pack-manifest.json"), JSON.stringify(manifest, null, 2) + "\n");
|
|
5252
5501
|
writtenContextDir = contextDir;
|
|
5253
5502
|
}
|
|
5254
5503
|
if (jsonOutput) {
|
|
@@ -5269,14 +5518,14 @@ async function printHostedExecutionPackManifest(essencePath, namespace, jsonOutp
|
|
|
5269
5518
|
}
|
|
5270
5519
|
}
|
|
5271
5520
|
async function hydrateHostedExecutionPacksIfMissing(projectRoot, namespace = "@official") {
|
|
5272
|
-
const contextDir =
|
|
5273
|
-
const reviewPackPath =
|
|
5274
|
-
const manifestPath =
|
|
5275
|
-
if (
|
|
5521
|
+
const contextDir = join27(projectRoot, ".decantr", "context");
|
|
5522
|
+
const reviewPackPath = join27(contextDir, "review-pack.json");
|
|
5523
|
+
const manifestPath = join27(contextDir, "pack-manifest.json");
|
|
5524
|
+
if (existsSync26(reviewPackPath) && existsSync26(manifestPath)) {
|
|
5276
5525
|
return { attempted: false, hydrated: false };
|
|
5277
5526
|
}
|
|
5278
|
-
const essencePath =
|
|
5279
|
-
if (!
|
|
5527
|
+
const essencePath = join27(projectRoot, "decantr.essence.json");
|
|
5528
|
+
if (!existsSync26(essencePath)) {
|
|
5280
5529
|
return { attempted: false, hydrated: false };
|
|
5281
5530
|
}
|
|
5282
5531
|
const reviewHydration = await hydrateHostedReviewPackIfMissing(projectRoot, namespace);
|
|
@@ -5285,9 +5534,9 @@ async function hydrateHostedExecutionPacksIfMissing(projectRoot, namespace = "@o
|
|
|
5285
5534
|
}
|
|
5286
5535
|
try {
|
|
5287
5536
|
const client = getPublicAPIClient();
|
|
5288
|
-
const essence = JSON.parse(
|
|
5537
|
+
const essence = JSON.parse(readFileSync19(essencePath, "utf-8"));
|
|
5289
5538
|
const bundle = await client.compileExecutionPacks(essence, { namespace });
|
|
5290
|
-
|
|
5539
|
+
mkdirSync12(contextDir, { recursive: true });
|
|
5291
5540
|
writeExecutionPackBundleArtifacts(contextDir, bundle);
|
|
5292
5541
|
return { attempted: true, hydrated: true, scope: "bundle" };
|
|
5293
5542
|
} catch {
|
|
@@ -5295,19 +5544,19 @@ async function hydrateHostedExecutionPacksIfMissing(projectRoot, namespace = "@o
|
|
|
5295
5544
|
}
|
|
5296
5545
|
}
|
|
5297
5546
|
async function hydrateHostedReviewPackIfMissing(projectRoot, namespace = "@official") {
|
|
5298
|
-
const contextDir =
|
|
5299
|
-
const reviewPackPath =
|
|
5300
|
-
const manifestPath =
|
|
5301
|
-
if (
|
|
5547
|
+
const contextDir = join27(projectRoot, ".decantr", "context");
|
|
5548
|
+
const reviewPackPath = join27(contextDir, "review-pack.json");
|
|
5549
|
+
const manifestPath = join27(contextDir, "pack-manifest.json");
|
|
5550
|
+
if (existsSync26(reviewPackPath) && existsSync26(manifestPath)) {
|
|
5302
5551
|
return { attempted: false, hydrated: false };
|
|
5303
5552
|
}
|
|
5304
|
-
const essencePath =
|
|
5305
|
-
if (!
|
|
5553
|
+
const essencePath = join27(projectRoot, "decantr.essence.json");
|
|
5554
|
+
if (!existsSync26(essencePath)) {
|
|
5306
5555
|
return { attempted: false, hydrated: false };
|
|
5307
5556
|
}
|
|
5308
5557
|
try {
|
|
5309
5558
|
const client = getPublicAPIClient();
|
|
5310
|
-
const essence = JSON.parse(
|
|
5559
|
+
const essence = JSON.parse(readFileSync19(essencePath, "utf-8"));
|
|
5311
5560
|
const selected = await client.selectExecutionPack(
|
|
5312
5561
|
{
|
|
5313
5562
|
essence,
|
|
@@ -5315,14 +5564,14 @@ async function hydrateHostedReviewPackIfMissing(projectRoot, namespace = "@offic
|
|
|
5315
5564
|
},
|
|
5316
5565
|
{ namespace }
|
|
5317
5566
|
);
|
|
5318
|
-
|
|
5319
|
-
|
|
5320
|
-
|
|
5321
|
-
|
|
5567
|
+
mkdirSync12(contextDir, { recursive: true });
|
|
5568
|
+
writeFileSync15(join27(contextDir, "review-pack.md"), selected.pack.renderedMarkdown);
|
|
5569
|
+
writeFileSync15(
|
|
5570
|
+
join27(contextDir, "review-pack.json"),
|
|
5322
5571
|
JSON.stringify(selected.pack, null, 2) + "\n"
|
|
5323
5572
|
);
|
|
5324
|
-
if (!
|
|
5325
|
-
|
|
5573
|
+
if (!existsSync26(manifestPath)) {
|
|
5574
|
+
writeFileSync15(manifestPath, JSON.stringify(selected.manifest, null, 2) + "\n");
|
|
5326
5575
|
}
|
|
5327
5576
|
return { attempted: true, hydrated: true, scope: "review" };
|
|
5328
5577
|
} catch {
|
|
@@ -5332,17 +5581,17 @@ async function hydrateHostedReviewPackIfMissing(projectRoot, namespace = "@offic
|
|
|
5332
5581
|
async function printHostedFileCritique(sourcePath, namespace, jsonOutput = false, essencePath, treatmentsPath) {
|
|
5333
5582
|
const client = getPublicAPIClient();
|
|
5334
5583
|
const resolvedSourcePath = resolveUserPath(sourcePath);
|
|
5335
|
-
const resolvedEssencePath = essencePath ? resolveUserPath(essencePath) :
|
|
5336
|
-
const resolvedTreatmentsPath = treatmentsPath ? resolveUserPath(treatmentsPath) :
|
|
5337
|
-
if (!
|
|
5584
|
+
const resolvedEssencePath = essencePath ? resolveUserPath(essencePath) : join27(process.cwd(), "decantr.essence.json");
|
|
5585
|
+
const resolvedTreatmentsPath = treatmentsPath ? resolveUserPath(treatmentsPath) : join27(process.cwd(), "src", "styles", "treatments.css");
|
|
5586
|
+
if (!existsSync26(resolvedSourcePath)) {
|
|
5338
5587
|
throw new Error(`Source file not found at ${resolvedSourcePath}`);
|
|
5339
5588
|
}
|
|
5340
|
-
if (!
|
|
5589
|
+
if (!existsSync26(resolvedEssencePath)) {
|
|
5341
5590
|
throw new Error(`Essence file not found at ${resolvedEssencePath}`);
|
|
5342
5591
|
}
|
|
5343
|
-
const code =
|
|
5344
|
-
const essence = JSON.parse(
|
|
5345
|
-
const treatmentsCss =
|
|
5592
|
+
const code = readFileSync19(resolvedSourcePath, "utf-8");
|
|
5593
|
+
const essence = JSON.parse(readFileSync19(resolvedEssencePath, "utf-8"));
|
|
5594
|
+
const treatmentsCss = existsSync26(resolvedTreatmentsPath) ? readFileSync19(resolvedTreatmentsPath, "utf-8") : void 0;
|
|
5346
5595
|
const report = await client.critiqueFile(
|
|
5347
5596
|
{
|
|
5348
5597
|
essence,
|
|
@@ -5366,11 +5615,11 @@ async function printHostedFileCritique(sourcePath, namespace, jsonOutput = false
|
|
|
5366
5615
|
}
|
|
5367
5616
|
async function printHostedProjectAudit(namespace, jsonOutput = false, essencePath, distPath, sourcesPath) {
|
|
5368
5617
|
const client = getPublicAPIClient();
|
|
5369
|
-
const resolvedEssencePath = essencePath ? resolveUserPath(essencePath) :
|
|
5370
|
-
if (!
|
|
5618
|
+
const resolvedEssencePath = essencePath ? resolveUserPath(essencePath) : join27(process.cwd(), "decantr.essence.json");
|
|
5619
|
+
if (!existsSync26(resolvedEssencePath)) {
|
|
5371
5620
|
throw new Error(`Essence file not found at ${resolvedEssencePath}`);
|
|
5372
5621
|
}
|
|
5373
|
-
const essence = JSON.parse(
|
|
5622
|
+
const essence = JSON.parse(readFileSync19(resolvedEssencePath, "utf-8"));
|
|
5374
5623
|
const dist = readHostedDistSnapshot(distPath);
|
|
5375
5624
|
const sources = readHostedSourceSnapshot(sourcesPath);
|
|
5376
5625
|
const report = await client.auditProject(
|
|
@@ -5388,7 +5637,7 @@ async function printHostedProjectAudit(namespace, jsonOutput = false, essencePat
|
|
|
5388
5637
|
console.log(heading2("Hosted Project Audit"));
|
|
5389
5638
|
console.log(` Essence: ${resolvedEssencePath}`);
|
|
5390
5639
|
console.log(
|
|
5391
|
-
` Dist snapshot: ${dist ? distPath ? resolveUserPath(distPath) :
|
|
5640
|
+
` Dist snapshot: ${dist ? distPath ? resolveUserPath(distPath) : join27(process.cwd(), "dist") : "none"}`
|
|
5392
5641
|
);
|
|
5393
5642
|
console.log(
|
|
5394
5643
|
` Source snapshot: ${sources && sourcesPath ? resolveUserPath(sourcesPath) : "none"}`
|
|
@@ -5469,7 +5718,7 @@ async function cmdGet(type, id) {
|
|
|
5469
5718
|
}
|
|
5470
5719
|
const apiType = CONTENT_TYPE_TO_API_CONTENT_TYPE3[type];
|
|
5471
5720
|
const registryClient = new RegistryClient({
|
|
5472
|
-
cacheDir:
|
|
5721
|
+
cacheDir: join27(process.cwd(), ".decantr", "cache")
|
|
5473
5722
|
});
|
|
5474
5723
|
const result = await registryClient.fetchContentItem(apiType, id);
|
|
5475
5724
|
if (result) {
|
|
@@ -5478,16 +5727,16 @@ async function cmdGet(type, id) {
|
|
|
5478
5727
|
}
|
|
5479
5728
|
const currentDir = dirname4(fileURLToPath2(import.meta.url));
|
|
5480
5729
|
const bundledCandidates = [
|
|
5481
|
-
|
|
5730
|
+
join27(currentDir, "bundled", apiType, `${id}.json`),
|
|
5482
5731
|
// Running from src/
|
|
5483
|
-
|
|
5732
|
+
join27(currentDir, "..", "src", "bundled", apiType, `${id}.json`),
|
|
5484
5733
|
// Running from dist/
|
|
5485
|
-
|
|
5734
|
+
join27(currentDir, "..", "bundled", apiType, `${id}.json`)
|
|
5486
5735
|
// Alternative dist layout
|
|
5487
5736
|
];
|
|
5488
|
-
const bundledPath = bundledCandidates.find((p) =>
|
|
5737
|
+
const bundledPath = bundledCandidates.find((p) => existsSync26(p)) || null;
|
|
5489
5738
|
if (bundledPath) {
|
|
5490
|
-
const data = JSON.parse(
|
|
5739
|
+
const data = JSON.parse(readFileSync19(bundledPath, "utf-8"));
|
|
5491
5740
|
console.log(JSON.stringify(data, null, 2));
|
|
5492
5741
|
return;
|
|
5493
5742
|
}
|
|
@@ -5496,10 +5745,10 @@ async function cmdGet(type, id) {
|
|
|
5496
5745
|
return;
|
|
5497
5746
|
}
|
|
5498
5747
|
async function cmdValidate(path) {
|
|
5499
|
-
const essencePath = path ||
|
|
5748
|
+
const essencePath = path || join27(process.cwd(), "decantr.essence.json");
|
|
5500
5749
|
let raw;
|
|
5501
5750
|
try {
|
|
5502
|
-
raw =
|
|
5751
|
+
raw = readFileSync19(essencePath, "utf-8");
|
|
5503
5752
|
} catch {
|
|
5504
5753
|
console.error(error3(`Could not read ${essencePath}`));
|
|
5505
5754
|
process.exitCode = 1;
|
|
@@ -5513,7 +5762,7 @@ async function cmdValidate(path) {
|
|
|
5513
5762
|
process.exitCode = 1;
|
|
5514
5763
|
return;
|
|
5515
5764
|
}
|
|
5516
|
-
const detectedVersion =
|
|
5765
|
+
const detectedVersion = isV37(essence) ? "v3" : "v2";
|
|
5517
5766
|
console.log(`${DIM13}Detected essence version: ${detectedVersion}${RESET13}`);
|
|
5518
5767
|
const result = validateEssence2(essence);
|
|
5519
5768
|
if (result.valid) {
|
|
@@ -5568,7 +5817,7 @@ async function cmdList(type, sort, recommended, intelligenceSource) {
|
|
|
5568
5817
|
return;
|
|
5569
5818
|
}
|
|
5570
5819
|
const registryClient = new RegistryClient({
|
|
5571
|
-
cacheDir:
|
|
5820
|
+
cacheDir: join27(process.cwd(), ".decantr", "cache")
|
|
5572
5821
|
});
|
|
5573
5822
|
const result = await registryClient.fetchContentList(
|
|
5574
5823
|
type,
|
|
@@ -5614,6 +5863,176 @@ async function cmdList(type, sort, recommended, intelligenceSource) {
|
|
|
5614
5863
|
}
|
|
5615
5864
|
}
|
|
5616
5865
|
}
|
|
5866
|
+
function readCliPackageVersion() {
|
|
5867
|
+
const here = dirname4(fileURLToPath2(import.meta.url));
|
|
5868
|
+
const candidates = [join27(here, "..", "package.json"), join27(here, "..", "..", "package.json")];
|
|
5869
|
+
for (const candidate of candidates) {
|
|
5870
|
+
try {
|
|
5871
|
+
const pkg = JSON.parse(readFileSync19(candidate, "utf-8"));
|
|
5872
|
+
if (pkg.version) return pkg.version;
|
|
5873
|
+
} catch {
|
|
5874
|
+
}
|
|
5875
|
+
}
|
|
5876
|
+
return "0.0.0";
|
|
5877
|
+
}
|
|
5878
|
+
function timestampForFile() {
|
|
5879
|
+
return (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
5880
|
+
}
|
|
5881
|
+
function backupExistingEssence(projectRoot, label) {
|
|
5882
|
+
const essencePath = join27(projectRoot, "decantr.essence.json");
|
|
5883
|
+
if (!existsSync26(essencePath)) return null;
|
|
5884
|
+
const backupPath = join27(projectRoot, `decantr.essence.${label}.${timestampForFile()}.backup.json`);
|
|
5885
|
+
writeFileSync15(backupPath, readFileSync19(essencePath, "utf-8"), "utf-8");
|
|
5886
|
+
return backupPath;
|
|
5887
|
+
}
|
|
5888
|
+
function writeBrownfieldProjectJson(input) {
|
|
5889
|
+
const decantrDir = join27(input.projectRoot, ".decantr");
|
|
5890
|
+
mkdirSync12(join27(decantrDir, "context"), { recursive: true });
|
|
5891
|
+
mkdirSync12(join27(decantrDir, "cache"), { recursive: true });
|
|
5892
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5893
|
+
const projectJson = {
|
|
5894
|
+
detected: {
|
|
5895
|
+
framework: input.detected.framework,
|
|
5896
|
+
version: input.detected.version || null,
|
|
5897
|
+
packageManager: input.detected.packageManager,
|
|
5898
|
+
hasTypeScript: input.detected.hasTypeScript,
|
|
5899
|
+
hasTailwind: input.detected.hasTailwind,
|
|
5900
|
+
existingRuleFiles: input.detected.existingRuleFiles,
|
|
5901
|
+
workspaceRoot: input.workspaceInfo.workspaceRoot,
|
|
5902
|
+
appRoot: input.workspaceInfo.appRoot
|
|
5903
|
+
},
|
|
5904
|
+
sync: {
|
|
5905
|
+
status: "needs-sync",
|
|
5906
|
+
lastSync: now,
|
|
5907
|
+
registrySource: "cache",
|
|
5908
|
+
cachedContent: {
|
|
5909
|
+
archetypes: [],
|
|
5910
|
+
patterns: [],
|
|
5911
|
+
themes: []
|
|
5912
|
+
}
|
|
5913
|
+
},
|
|
5914
|
+
initialized: {
|
|
5915
|
+
at: now,
|
|
5916
|
+
via: "cli",
|
|
5917
|
+
version: readCliPackageVersion(),
|
|
5918
|
+
flags: `--existing --${input.mode === "replace" ? "replace-essence" : input.mode === "merge" ? "merge-proposal" : "accept-proposal"}`,
|
|
5919
|
+
workflowMode: "brownfield-attach",
|
|
5920
|
+
adoptionMode: "contract-only",
|
|
5921
|
+
contentSource: "none",
|
|
5922
|
+
assistantBridge: input.assistantBridge,
|
|
5923
|
+
projectScope: input.workspaceInfo.projectScope,
|
|
5924
|
+
adapterId: null,
|
|
5925
|
+
analysisArtifacts: true,
|
|
5926
|
+
acceptedProposal: {
|
|
5927
|
+
mode: input.mode,
|
|
5928
|
+
path: ".decantr/observed-essence.proposal.json"
|
|
5929
|
+
}
|
|
5930
|
+
}
|
|
5931
|
+
};
|
|
5932
|
+
writeFileSync15(join27(decantrDir, "project.json"), JSON.stringify(projectJson, null, 2) + "\n");
|
|
5933
|
+
}
|
|
5934
|
+
async function applyAcceptedBrownfieldProposal(input) {
|
|
5935
|
+
const proposal = readBrownfieldProposal(input.projectRoot);
|
|
5936
|
+
if (!proposal) {
|
|
5937
|
+
console.log(error3(`No observed brownfield proposal found at ${proposalPath(input.projectRoot)}.`));
|
|
5938
|
+
console.log(dim3("Run `decantr analyze` first, review `.decantr/brownfield-report.md`, then accept or merge the proposal."));
|
|
5939
|
+
process.exitCode = 1;
|
|
5940
|
+
return;
|
|
5941
|
+
}
|
|
5942
|
+
const essencePath = join27(input.projectRoot, "decantr.essence.json");
|
|
5943
|
+
const hasEssence = existsSync26(essencePath);
|
|
5944
|
+
let essence;
|
|
5945
|
+
let backupPath = null;
|
|
5946
|
+
if (input.mode === "accept" && hasEssence) {
|
|
5947
|
+
console.log(error3("Refusing to accept proposal over an existing decantr.essence.json."));
|
|
5948
|
+
console.log(dim3("Use `--merge-proposal` to preserve the existing contract or `--replace-essence` for an explicit destructive replacement."));
|
|
5949
|
+
process.exitCode = 1;
|
|
5950
|
+
return;
|
|
5951
|
+
}
|
|
5952
|
+
if (input.mode === "merge" && hasEssence) {
|
|
5953
|
+
const existing = JSON.parse(readFileSync19(essencePath, "utf-8"));
|
|
5954
|
+
if (!isV37(existing)) {
|
|
5955
|
+
console.log(error3("Existing essence is not v3. Run `decantr migrate` before merging a brownfield proposal."));
|
|
5956
|
+
process.exitCode = 1;
|
|
5957
|
+
return;
|
|
5958
|
+
}
|
|
5959
|
+
essence = mergeEssenceWithProposal(existing, proposal);
|
|
5960
|
+
} else {
|
|
5961
|
+
essence = proposal.essence;
|
|
5962
|
+
}
|
|
5963
|
+
const validation = validateEssence2(essence);
|
|
5964
|
+
if (!validation.valid) {
|
|
5965
|
+
console.log(error3("Brownfield proposal produced an invalid Decantr essence. No files were changed."));
|
|
5966
|
+
for (const validationError of validation.errors) {
|
|
5967
|
+
console.log(` ${RED11}${validationError}${RESET13}`);
|
|
5968
|
+
}
|
|
5969
|
+
process.exitCode = 1;
|
|
5970
|
+
return;
|
|
5971
|
+
}
|
|
5972
|
+
if (input.mode === "merge" && hasEssence) {
|
|
5973
|
+
backupPath = backupExistingEssence(input.projectRoot, "merge");
|
|
5974
|
+
} else if (input.mode === "replace" && hasEssence) {
|
|
5975
|
+
backupPath = backupExistingEssence(input.projectRoot, "replace");
|
|
5976
|
+
}
|
|
5977
|
+
writeBrownfieldProjectJson({
|
|
5978
|
+
projectRoot: input.projectRoot,
|
|
5979
|
+
detected: input.detected,
|
|
5980
|
+
workspaceInfo: input.workspaceInfo,
|
|
5981
|
+
assistantBridge: input.assistantBridge,
|
|
5982
|
+
mode: input.mode
|
|
5983
|
+
});
|
|
5984
|
+
writeFileSync15(essencePath, JSON.stringify(essence, null, 2) + "\n", "utf-8");
|
|
5985
|
+
const registryClient = new RegistryClient({
|
|
5986
|
+
cacheDir: join27(input.projectRoot, ".decantr", "cache"),
|
|
5987
|
+
offline: true,
|
|
5988
|
+
projectRoot: input.projectRoot
|
|
5989
|
+
});
|
|
5990
|
+
const refreshResult = await refreshDerivedFiles(input.projectRoot, essence, registryClient, void 0, {
|
|
5991
|
+
isInitialScaffold: true,
|
|
5992
|
+
workflowMode: "brownfield-attach",
|
|
5993
|
+
adoptionMode: "contract-only",
|
|
5994
|
+
analysisArtifacts: true
|
|
5995
|
+
});
|
|
5996
|
+
let assistantBridgePath = null;
|
|
5997
|
+
if (input.assistantBridge === "preview" || input.assistantBridge === "apply") {
|
|
5998
|
+
assistantBridgePath = writeAssistantBridgePreview({
|
|
5999
|
+
projectRoot: input.projectRoot,
|
|
6000
|
+
detected: input.detected,
|
|
6001
|
+
workflowMode: "brownfield-attach",
|
|
6002
|
+
assistantBridge: input.assistantBridge
|
|
6003
|
+
});
|
|
6004
|
+
}
|
|
6005
|
+
const appliedRuleFiles = input.assistantBridge === "apply" ? applyAssistantBridge(input.projectRoot, input.detected) : [];
|
|
6006
|
+
console.log(success3("\nBrownfield proposal accepted.\n"));
|
|
6007
|
+
console.log(" Files created/updated:");
|
|
6008
|
+
console.log(` ${cyan3("decantr.essence.json")} Observed brownfield contract`);
|
|
6009
|
+
console.log(` ${cyan3("DECANTR.md")} Reconciled assistant guidance`);
|
|
6010
|
+
console.log(` ${cyan3(".decantr/project.json")} Brownfield attach metadata`);
|
|
6011
|
+
console.log(` ${cyan3(".decantr/context/")} Generated contract context`);
|
|
6012
|
+
if (assistantBridgePath) {
|
|
6013
|
+
console.log(` ${cyan3(".decantr/context/assistant-bridge.md")} Assistant bridge preview`);
|
|
6014
|
+
}
|
|
6015
|
+
if (appliedRuleFiles.length > 0) {
|
|
6016
|
+
console.log(` ${dim3(`Rule bridge applied: ${appliedRuleFiles.join(", ")}`)}`);
|
|
6017
|
+
}
|
|
6018
|
+
if (backupPath) {
|
|
6019
|
+
console.log(` ${dim3(`Backup: ${backupPath}`)}`);
|
|
6020
|
+
}
|
|
6021
|
+
console.log("");
|
|
6022
|
+
console.log(" Generated context:");
|
|
6023
|
+
for (const contextFile of refreshResult.contextFiles.slice(0, 8)) {
|
|
6024
|
+
console.log(` ${dim3(contextFile.replace(`${input.projectRoot}/`, ""))}`);
|
|
6025
|
+
}
|
|
6026
|
+
if (refreshResult.contextFiles.length > 8) {
|
|
6027
|
+
console.log(` ${dim3(`(+${refreshResult.contextFiles.length - 8} more)`)}`);
|
|
6028
|
+
}
|
|
6029
|
+
console.log("");
|
|
6030
|
+
console.log(" Next steps:");
|
|
6031
|
+
console.log(` 1. Run ${cyan3("decantr check --brownfield")} to verify contract coverage`);
|
|
6032
|
+
console.log(` 2. Read ${cyan3(".decantr/brownfield-report.md")} for unresolved doctrine risks`);
|
|
6033
|
+
console.log(` 3. Use ${cyan3("decantr rules preview")} before mutating assistant rule files`);
|
|
6034
|
+
console.log("");
|
|
6035
|
+
}
|
|
5617
6036
|
async function cmdInit(args) {
|
|
5618
6037
|
const workspaceInfo = resolveWorkspaceInfo(process.cwd(), args.project);
|
|
5619
6038
|
if (args.yes && workspaceInfo.requiresProjectSelection) {
|
|
@@ -5654,6 +6073,37 @@ async function cmdInit(args) {
|
|
|
5654
6073
|
offline: args.offline,
|
|
5655
6074
|
projectScope: workspaceInfo.projectScope
|
|
5656
6075
|
});
|
|
6076
|
+
const proposalMode = args["replace-essence"] ? "replace" : args["merge-proposal"] ? "merge" : args["accept-proposal"] ? "accept" : null;
|
|
6077
|
+
if (proposalMode) {
|
|
6078
|
+
await applyAcceptedBrownfieldProposal({
|
|
6079
|
+
projectRoot,
|
|
6080
|
+
detected,
|
|
6081
|
+
workspaceInfo,
|
|
6082
|
+
mode: proposalMode,
|
|
6083
|
+
assistantBridge: policy.assistantBridge
|
|
6084
|
+
});
|
|
6085
|
+
return;
|
|
6086
|
+
}
|
|
6087
|
+
if (policy.workflowMode === "brownfield-attach" && detected.existingEssence) {
|
|
6088
|
+
console.log(error3("Refusing to overwrite existing decantr.essence.json in brownfield attach mode."));
|
|
6089
|
+
console.log(
|
|
6090
|
+
dim3(
|
|
6091
|
+
"Run `decantr analyze`, then use `decantr init --existing --merge-proposal` or the explicit destructive `--replace-essence`."
|
|
6092
|
+
)
|
|
6093
|
+
);
|
|
6094
|
+
process.exitCode = 1;
|
|
6095
|
+
return;
|
|
6096
|
+
}
|
|
6097
|
+
if (policy.workflowMode === "brownfield-attach" && readBrownfieldProposal(projectRoot)) {
|
|
6098
|
+
console.log(error3("Observed brownfield proposal found, but it was not accepted."));
|
|
6099
|
+
console.log(
|
|
6100
|
+
dim3(
|
|
6101
|
+
"Review `.decantr/brownfield-report.md`, then run `decantr init --existing --accept-proposal`."
|
|
6102
|
+
)
|
|
6103
|
+
);
|
|
6104
|
+
process.exitCode = 1;
|
|
6105
|
+
return;
|
|
6106
|
+
}
|
|
5657
6107
|
const preferContractOnly = policy.contentSource === "none" && (policy.workflowMode === "brownfield-attach" || policy.workflowMode === "greenfield-contract-only");
|
|
5658
6108
|
const shouldUseRegistry = !preferContractOnly || policy.registryRequired;
|
|
5659
6109
|
let offlineSeed = {
|
|
@@ -5678,7 +6128,7 @@ async function cmdInit(args) {
|
|
|
5678
6128
|
}
|
|
5679
6129
|
}
|
|
5680
6130
|
const registryClient = new RegistryClient({
|
|
5681
|
-
cacheDir:
|
|
6131
|
+
cacheDir: join27(projectRoot, ".decantr", "cache"),
|
|
5682
6132
|
apiUrl: args.registry,
|
|
5683
6133
|
offline: args.offline,
|
|
5684
6134
|
projectRoot
|
|
@@ -6005,7 +6455,7 @@ ${YELLOW9}You're offline. Scaffolding Decantr default.${RESET13}`);
|
|
|
6005
6455
|
if (appliedRuleFiles.length > 0) {
|
|
6006
6456
|
console.log(` ${dim3(`Rule bridge applied: ${appliedRuleFiles.join(", ")}`)}`);
|
|
6007
6457
|
}
|
|
6008
|
-
if (!
|
|
6458
|
+
if (!existsSync26(join27(projectRoot, "package.json"))) {
|
|
6009
6459
|
console.log("");
|
|
6010
6460
|
console.log(
|
|
6011
6461
|
dim3(` Note: ${cyan3("decantr init")} created Decantr contract/context files only.`)
|
|
@@ -6036,7 +6486,7 @@ ${YELLOW9}You're offline. Scaffolding Decantr default.${RESET13}`);
|
|
|
6036
6486
|
console.log(` ${cyan3("decantr upgrade")} Update to latest patterns`);
|
|
6037
6487
|
console.log(` ${cyan3("decantr check")} Detect drift issues`);
|
|
6038
6488
|
console.log(` ${cyan3("decantr migrate")} Migrate v2 essence to v3`);
|
|
6039
|
-
const essenceContent =
|
|
6489
|
+
const essenceContent = readFileSync19(result.essencePath, "utf-8");
|
|
6040
6490
|
const essence = JSON.parse(essenceContent);
|
|
6041
6491
|
if (essence.version !== "3.1.0") {
|
|
6042
6492
|
const validation = validateEssence2(essence);
|
|
@@ -6047,7 +6497,7 @@ Validation warnings: ${validation.errors.join(", ")}`));
|
|
|
6047
6497
|
}
|
|
6048
6498
|
console.log("");
|
|
6049
6499
|
let promptPages;
|
|
6050
|
-
if (
|
|
6500
|
+
if (isV37(essence)) {
|
|
6051
6501
|
const allPages = essence.blueprint.sections ? essence.blueprint.sections.flatMap(
|
|
6052
6502
|
(s) => s.pages.map((p) => ({ ...p, _sectionId: s.id, _shell: s.shell }))
|
|
6053
6503
|
) : essence.blueprint.pages || [];
|
|
@@ -6093,25 +6543,25 @@ Validation warnings: ${validation.errors.join(", ")}`));
|
|
|
6093
6543
|
}
|
|
6094
6544
|
async function cmdStatus() {
|
|
6095
6545
|
const projectRoot = process.cwd();
|
|
6096
|
-
const essencePath =
|
|
6097
|
-
const projectJsonPath =
|
|
6546
|
+
const essencePath = join27(projectRoot, "decantr.essence.json");
|
|
6547
|
+
const projectJsonPath = join27(projectRoot, ".decantr", "project.json");
|
|
6098
6548
|
console.log(heading2("Decantr Project Status"));
|
|
6099
|
-
if (!
|
|
6549
|
+
if (!existsSync26(essencePath)) {
|
|
6100
6550
|
console.log(`${RED11}No decantr.essence.json found.${RESET13}`);
|
|
6101
6551
|
console.log(dim3('Run "decantr init" to create one.'));
|
|
6102
6552
|
return;
|
|
6103
6553
|
}
|
|
6104
6554
|
try {
|
|
6105
|
-
const essence = JSON.parse(
|
|
6555
|
+
const essence = JSON.parse(readFileSync19(essencePath, "utf-8"));
|
|
6106
6556
|
const validation = validateEssence2(essence);
|
|
6107
|
-
const essenceVersion =
|
|
6557
|
+
const essenceVersion = isV37(essence) ? "v3" : "v2";
|
|
6108
6558
|
console.log(`${BOLD6}Essence:${RESET13}`);
|
|
6109
6559
|
if (validation.valid) {
|
|
6110
6560
|
console.log(` ${GREEN13}Valid${RESET13} (${essenceVersion})`);
|
|
6111
6561
|
} else {
|
|
6112
6562
|
console.log(` ${RED11}Invalid: ${validation.errors.join(", ")}${RESET13}`);
|
|
6113
6563
|
}
|
|
6114
|
-
if (
|
|
6564
|
+
if (isV37(essence)) {
|
|
6115
6565
|
const v3 = essence;
|
|
6116
6566
|
const sections = v3.blueprint.sections ?? [];
|
|
6117
6567
|
const flatPages = sections.length > 0 ? sections.flatMap((section) => section.pages ?? []) : v3.blueprint.pages ?? [];
|
|
@@ -6159,9 +6609,9 @@ async function cmdStatus() {
|
|
|
6159
6609
|
}
|
|
6160
6610
|
console.log("");
|
|
6161
6611
|
console.log(`${BOLD6}Sync Status:${RESET13}`);
|
|
6162
|
-
if (
|
|
6612
|
+
if (existsSync26(projectJsonPath)) {
|
|
6163
6613
|
try {
|
|
6164
|
-
const projectJson = JSON.parse(
|
|
6614
|
+
const projectJson = JSON.parse(readFileSync19(projectJsonPath, "utf-8"));
|
|
6165
6615
|
const syncStatus = projectJson.sync?.status || "unknown";
|
|
6166
6616
|
const lastSync = projectJson.sync?.lastSync || "never";
|
|
6167
6617
|
const source = projectJson.sync?.registrySource || "unknown";
|
|
@@ -6179,7 +6629,7 @@ async function cmdStatus() {
|
|
|
6179
6629
|
}
|
|
6180
6630
|
async function cmdSync() {
|
|
6181
6631
|
const projectRoot = process.cwd();
|
|
6182
|
-
const cacheDir =
|
|
6632
|
+
const cacheDir = join27(projectRoot, ".decantr", "cache");
|
|
6183
6633
|
console.log(heading2("Syncing registry content..."));
|
|
6184
6634
|
const result = await syncRegistry(cacheDir);
|
|
6185
6635
|
if (result.synced.length > 0) {
|
|
@@ -6374,14 +6824,14 @@ ${BOLD6}Examples:${RESET13}
|
|
|
6374
6824
|
process.exitCode = 1;
|
|
6375
6825
|
return;
|
|
6376
6826
|
}
|
|
6377
|
-
const themePath =
|
|
6378
|
-
if (!
|
|
6827
|
+
const themePath = join27(projectRoot, ".decantr", "custom", "themes", `${name}.json`);
|
|
6828
|
+
if (!existsSync26(themePath)) {
|
|
6379
6829
|
console.error(error3(`Theme "${name}" not found at ${themePath}`));
|
|
6380
6830
|
process.exitCode = 1;
|
|
6381
6831
|
return;
|
|
6382
6832
|
}
|
|
6383
6833
|
try {
|
|
6384
|
-
const theme = JSON.parse(
|
|
6834
|
+
const theme = JSON.parse(readFileSync19(themePath, "utf-8"));
|
|
6385
6835
|
const result = validateCustomTheme(theme);
|
|
6386
6836
|
if (result.valid) {
|
|
6387
6837
|
console.log(success3(`Custom theme "${name}" is valid`));
|
|
@@ -6462,6 +6912,7 @@ ${BOLD6}Usage:${RESET13}
|
|
|
6462
6912
|
decantr audit [file]
|
|
6463
6913
|
decantr migrate
|
|
6464
6914
|
decantr check
|
|
6915
|
+
decantr check --brownfield
|
|
6465
6916
|
decantr sync-drift
|
|
6466
6917
|
decantr search <query> [--type <type>] [--sort <recommended|recent|name>] [--recommended] [--source <authored|benchmark|hybrid>]
|
|
6467
6918
|
decantr suggest <query> [--type <type>]
|
|
@@ -6473,6 +6924,7 @@ ${BOLD6}Usage:${RESET13}
|
|
|
6473
6924
|
decantr registry get-pack <manifest|scaffold|review|section|page|mutation> [id] [--namespace <namespace>] [--json] [--essence <path>] [--write-context]
|
|
6474
6925
|
decantr registry critique-file <file> [--namespace <namespace>] [--json] [--essence <path>] [--treatments <path>]
|
|
6475
6926
|
decantr registry audit-project [--namespace <namespace>] [--json] [--essence <path>] [--dist <path>] [--sources <dir>]
|
|
6927
|
+
decantr rules preview [--project=<path>]
|
|
6476
6928
|
decantr rules apply [--project=<path>]
|
|
6477
6929
|
decantr validate [path]
|
|
6478
6930
|
decantr theme <subcommand>
|
|
@@ -6488,13 +6940,16 @@ ${BOLD6}Init Options:${RESET13}
|
|
|
6488
6940
|
--theme Theme ID
|
|
6489
6941
|
--mode Color mode: dark | light | auto
|
|
6490
6942
|
--shape Border shape: pill | rounded | sharp
|
|
6491
|
-
--target Framework: react | vue | svelte | nextjs | html
|
|
6943
|
+
--target Framework: react | vue | svelte | angular | nextjs | nuxt | astro | html
|
|
6492
6944
|
--guard Guard mode: creative | guided | strict
|
|
6493
6945
|
--density Spacing: compact | comfortable | spacious
|
|
6494
6946
|
--shell Default shell layout
|
|
6495
6947
|
--workflow Workflow: greenfield | brownfield | hybrid
|
|
6496
6948
|
--adoption Adoption: contract-only | style-bridge | decantr-css
|
|
6497
6949
|
--assistant-bridge Assistant rules: none | preview | apply
|
|
6950
|
+
--accept-proposal Brownfield: accept observed proposal when no essence exists
|
|
6951
|
+
--merge-proposal Brownfield: merge observed proposal into an existing essence
|
|
6952
|
+
--replace-essence Brownfield: explicit destructive proposal replacement with backup
|
|
6498
6953
|
--project App path inside a workspace/monorepo
|
|
6499
6954
|
--existing Initialize in existing project
|
|
6500
6955
|
--offline Force offline mode
|
|
@@ -6509,7 +6964,7 @@ ${BOLD6}Commands:${RESET13}
|
|
|
6509
6964
|
${cyan3("sync")} Sync registry content from API
|
|
6510
6965
|
${cyan3("audit")} Audit the project or critique a specific file against compiled packs
|
|
6511
6966
|
${cyan3("migrate")} Migrate v2 essence to v3 format (with .v2.backup.json backup)
|
|
6512
|
-
${cyan3("check")} Detect drift issues (validate + guard rules) [--telemetry]
|
|
6967
|
+
${cyan3("check")} Detect drift issues (validate + guard rules) [--telemetry] [--brownfield]
|
|
6513
6968
|
${cyan3("sync-drift")} Review and resolve drift log entries
|
|
6514
6969
|
${cyan3("search")} Search the registry
|
|
6515
6970
|
${cyan3("suggest")} Suggest patterns or alternatives for a query
|
|
@@ -6533,16 +6988,19 @@ ${BOLD6}Examples:${RESET13}
|
|
|
6533
6988
|
decantr new my-app --blueprint=carbon-ai-portal
|
|
6534
6989
|
decantr magic "AI chatbot with dark cyber theme \u2014 bold and futuristic"
|
|
6535
6990
|
decantr init
|
|
6536
|
-
decantr
|
|
6991
|
+
decantr analyze
|
|
6992
|
+
decantr init --existing --accept-proposal
|
|
6993
|
+
decantr init --existing --merge-proposal
|
|
6537
6994
|
decantr init --existing --adoption=style-bridge --assistant-bridge=preview
|
|
6538
6995
|
decantr init --workflow=greenfield --adoption=contract-only
|
|
6539
6996
|
decantr init --project=apps/web --yes
|
|
6997
|
+
decantr rules preview
|
|
6540
6998
|
decantr rules apply
|
|
6541
6999
|
decantr status
|
|
6542
7000
|
decantr audit
|
|
6543
7001
|
decantr audit src/pages/HomePage.tsx
|
|
6544
7002
|
decantr migrate
|
|
6545
|
-
decantr check
|
|
7003
|
+
decantr check --brownfield
|
|
6546
7004
|
decantr sync-drift
|
|
6547
7005
|
decantr search dashboard
|
|
6548
7006
|
decantr suggest leaderboard
|
|
@@ -6562,7 +7020,7 @@ ${BOLD6}Examples:${RESET13}
|
|
|
6562
7020
|
${BOLD6}Workflow Model:${RESET13}
|
|
6563
7021
|
${cyan3("Greenfield blueprint")} decantr new my-app --blueprint=X --workflow=greenfield --adoption=decantr-css
|
|
6564
7022
|
${cyan3("Greenfield contract")} decantr init --workflow=greenfield --adoption=contract-only
|
|
6565
|
-
${cyan3("Brownfield adoption")} decantr analyze -> decantr init --existing --
|
|
7023
|
+
${cyan3("Brownfield adoption")} decantr analyze -> decantr init --existing --accept-proposal -> decantr check --brownfield
|
|
6566
7024
|
${cyan3("Hybrid composition")} decantr add/remove, decantr theme switch, decantr registry, decantr upgrade
|
|
6567
7025
|
|
|
6568
7026
|
${BOLD6}Bootstrap adapters:${RESET13}
|
|
@@ -6570,6 +7028,24 @@ ${BOLD6}Bootstrap adapters:${RESET13}
|
|
|
6570
7028
|
Unsupported targets resolve through ${cyan3("generic-web")} contract-only mode until their starter adapters land.
|
|
6571
7029
|
`);
|
|
6572
7030
|
}
|
|
7031
|
+
function cmdRulesHelp() {
|
|
7032
|
+
console.log(`
|
|
7033
|
+
${BOLD6}decantr rules${RESET13} \u2014 Preview or apply assistant bridge snippets
|
|
7034
|
+
|
|
7035
|
+
${BOLD6}Usage:${RESET13}
|
|
7036
|
+
decantr rules preview [--project=<path>]
|
|
7037
|
+
decantr rules apply [--project=<path>]
|
|
7038
|
+
|
|
7039
|
+
${BOLD6}Subcommands:${RESET13}
|
|
7040
|
+
${cyan3("preview")} Print target-specific Decantr bridge guidance without mutating rule files
|
|
7041
|
+
${cyan3("apply")} Idempotently write Decantr bridge blocks to supported assistant rule files
|
|
7042
|
+
|
|
7043
|
+
${BOLD6}Examples:${RESET13}
|
|
7044
|
+
decantr rules preview
|
|
7045
|
+
decantr rules preview --project=apps/web
|
|
7046
|
+
decantr rules apply --project=apps/web
|
|
7047
|
+
`);
|
|
7048
|
+
}
|
|
6573
7049
|
async function main() {
|
|
6574
7050
|
const args = process.argv.slice(2);
|
|
6575
7051
|
const command = args[0];
|
|
@@ -6581,12 +7057,12 @@ async function main() {
|
|
|
6581
7057
|
try {
|
|
6582
7058
|
const here = dirname4(fileURLToPath2(import.meta.url));
|
|
6583
7059
|
const candidates = [
|
|
6584
|
-
|
|
6585
|
-
|
|
7060
|
+
join27(here, "..", "package.json"),
|
|
7061
|
+
join27(here, "..", "..", "package.json")
|
|
6586
7062
|
];
|
|
6587
7063
|
for (const candidate of candidates) {
|
|
6588
|
-
if (
|
|
6589
|
-
const pkg = JSON.parse(
|
|
7064
|
+
if (existsSync26(candidate)) {
|
|
7065
|
+
const pkg = JSON.parse(readFileSync19(candidate, "utf-8"));
|
|
6590
7066
|
if (pkg.version) {
|
|
6591
7067
|
console.log(pkg.version);
|
|
6592
7068
|
return;
|
|
@@ -6650,6 +7126,12 @@ async function main() {
|
|
|
6650
7126
|
initArgs.offline = true;
|
|
6651
7127
|
} else if (arg === "--existing") {
|
|
6652
7128
|
initArgs.existing = true;
|
|
7129
|
+
} else if (arg === "--accept-proposal") {
|
|
7130
|
+
initArgs["accept-proposal"] = true;
|
|
7131
|
+
} else if (arg === "--merge-proposal") {
|
|
7132
|
+
initArgs["merge-proposal"] = true;
|
|
7133
|
+
} else if (arg === "--replace-essence") {
|
|
7134
|
+
initArgs["replace-essence"] = true;
|
|
6653
7135
|
} else if (arg.startsWith("--")) {
|
|
6654
7136
|
const [key, value] = arg.slice(2).split("=");
|
|
6655
7137
|
if (value) {
|
|
@@ -6675,7 +7157,7 @@ async function main() {
|
|
|
6675
7157
|
break;
|
|
6676
7158
|
}
|
|
6677
7159
|
case "upgrade": {
|
|
6678
|
-
const { cmdUpgrade } = await import("./upgrade-
|
|
7160
|
+
const { cmdUpgrade } = await import("./upgrade-EV23CKA3.js");
|
|
6679
7161
|
const applyFlag = args.includes("--apply");
|
|
6680
7162
|
await cmdUpgrade(process.cwd(), { apply: applyFlag });
|
|
6681
7163
|
break;
|
|
@@ -6687,9 +7169,10 @@ async function main() {
|
|
|
6687
7169
|
`${YELLOW9}Note: \`decantr heal\` is deprecated. Use \`decantr check\` instead.${RESET13}`
|
|
6688
7170
|
);
|
|
6689
7171
|
}
|
|
6690
|
-
const { cmdHeal } = await import("./heal-
|
|
7172
|
+
const { cmdHeal } = await import("./heal-YHLXO5QL.js");
|
|
6691
7173
|
const telemetryFlag = args.includes("--telemetry");
|
|
6692
|
-
|
|
7174
|
+
const brownfieldFlag = args.includes("--brownfield");
|
|
7175
|
+
await cmdHeal(process.cwd(), { telemetry: telemetryFlag, brownfield: brownfieldFlag });
|
|
6693
7176
|
break;
|
|
6694
7177
|
}
|
|
6695
7178
|
case "migrate": {
|
|
@@ -7087,8 +7570,12 @@ async function main() {
|
|
|
7087
7570
|
}
|
|
7088
7571
|
case "rules": {
|
|
7089
7572
|
const subcommand = args[1];
|
|
7090
|
-
if (subcommand
|
|
7091
|
-
|
|
7573
|
+
if (!subcommand || subcommand === "--help" || subcommand === "-h" || subcommand === "help") {
|
|
7574
|
+
cmdRulesHelp();
|
|
7575
|
+
break;
|
|
7576
|
+
}
|
|
7577
|
+
if (subcommand !== "apply" && subcommand !== "preview") {
|
|
7578
|
+
console.error(error3("Usage: decantr rules <preview|apply> [--project=<path>]"));
|
|
7092
7579
|
process.exitCode = 1;
|
|
7093
7580
|
break;
|
|
7094
7581
|
}
|
|
@@ -7102,6 +7589,16 @@ async function main() {
|
|
|
7102
7589
|
}
|
|
7103
7590
|
const workspaceInfo = resolveWorkspaceInfo(process.cwd(), projectArg);
|
|
7104
7591
|
const detected = detectProject(workspaceInfo.appRoot);
|
|
7592
|
+
if (subcommand === "preview") {
|
|
7593
|
+
console.log(
|
|
7594
|
+
buildAssistantBridgeContent({
|
|
7595
|
+
detected,
|
|
7596
|
+
workflowMode: "brownfield-attach",
|
|
7597
|
+
assistantBridge: "preview"
|
|
7598
|
+
})
|
|
7599
|
+
);
|
|
7600
|
+
break;
|
|
7601
|
+
}
|
|
7105
7602
|
const updated = applyAssistantBridge(workspaceInfo.appRoot, detected);
|
|
7106
7603
|
if (updated.length === 0) {
|
|
7107
7604
|
console.log(dim3("Assistant bridge rule files are already up to date."));
|