@decantr/cli 2.13.1 → 3.0.0-next.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -13
- package/dist/bin.js +3 -3
- package/dist/{chunk-5MZH6XXQ.js → chunk-BDY4JCFH.js} +826 -1520
- package/dist/{chunk-YBSBAJ3E.js → chunk-GXC5IJQY.js} +1 -1
- package/dist/chunk-WUIFEHWM.js +4573 -0
- package/dist/{health-B4W7UJBZ.js → health-ASR7RWZJ.js} +7 -1
- package/dist/index.js +3 -3
- package/dist/{studio-JOEECLI6.js → studio-R65NXPJW.js} +2 -2
- package/dist/{workspace-7RU77ZZW.js → workspace-2FFQEEQQ.js} +2 -2
- package/package.json +10 -9
- package/src/templates/DECANTR.md.template +3 -3
- package/dist/chunk-3A2DLR47.js +0 -1565
|
@@ -22,14 +22,37 @@ import {
|
|
|
22
22
|
listWorkspaceCandidates,
|
|
23
23
|
listWorkspaceProjects,
|
|
24
24
|
shouldFailWorkspaceHealth
|
|
25
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-GXC5IJQY.js";
|
|
26
26
|
import {
|
|
27
|
+
acceptBrownfieldLocalLaw,
|
|
28
|
+
acceptStyleBridge,
|
|
29
|
+
buildGraphArtifacts,
|
|
30
|
+
changedFiles,
|
|
31
|
+
cmdGraph,
|
|
32
|
+
cmdGraphHelp,
|
|
33
|
+
createBrownfieldCodifyProposal,
|
|
34
|
+
createLocalLawTaskSummary,
|
|
27
35
|
createProjectHealthReport,
|
|
36
|
+
createStyleBridgeProposal,
|
|
37
|
+
createStyleBridgeTaskSummary,
|
|
28
38
|
formatProjectHealthMarkdown,
|
|
29
39
|
formatProjectHealthText,
|
|
40
|
+
localPatternsPath,
|
|
41
|
+
localPatternsProposalPath,
|
|
42
|
+
localRulesPath,
|
|
43
|
+
localRulesProposalPath,
|
|
44
|
+
readLocalPatternPack,
|
|
30
45
|
resolveWorkspaceInfo,
|
|
31
|
-
|
|
32
|
-
|
|
46
|
+
routeImpacts,
|
|
47
|
+
shouldFailHealth,
|
|
48
|
+
styleBridgeMatches,
|
|
49
|
+
styleBridgePath,
|
|
50
|
+
styleBridgeProposalPath,
|
|
51
|
+
validateLocalLaw,
|
|
52
|
+
writeBrownfieldCodifyProposal,
|
|
53
|
+
writeHostedPatternMappingProposal,
|
|
54
|
+
writeStyleBridgeProposal
|
|
55
|
+
} from "./chunk-WUIFEHWM.js";
|
|
33
56
|
import {
|
|
34
57
|
buildGuardRegistryContext,
|
|
35
58
|
createDoctrineMap,
|
|
@@ -49,10 +72,16 @@ import {
|
|
|
49
72
|
} from "./chunk-MNZKOZW7.js";
|
|
50
73
|
|
|
51
74
|
// src/index.ts
|
|
52
|
-
import { existsSync as
|
|
53
|
-
import { basename as
|
|
75
|
+
import { existsSync as existsSync28, mkdirSync as mkdirSync15, readdirSync as readdirSync8, readFileSync as readFileSync21, writeFileSync as writeFileSync18 } from "fs";
|
|
76
|
+
import { basename as basename2, dirname as dirname6, isAbsolute as isAbsolute3, join as join30, relative as relative6, resolve as resolve4 } from "path";
|
|
54
77
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
55
|
-
import {
|
|
78
|
+
import {
|
|
79
|
+
buildGraphImpactContext,
|
|
80
|
+
buildGraphRouteContext,
|
|
81
|
+
graphPayloadString,
|
|
82
|
+
summarizeGraphDiff
|
|
83
|
+
} from "@decantr/core";
|
|
84
|
+
import { evaluateGuard, isV4 as isV48, validateEssence as validateEssence2 } from "@decantr/essence-spec";
|
|
56
85
|
import {
|
|
57
86
|
CONTENT_TYPE_TO_API_CONTENT_TYPE as CONTENT_TYPE_TO_API_CONTENT_TYPE3,
|
|
58
87
|
CONTENT_TYPES as GET_CONTENT_TYPES,
|
|
@@ -3235,1107 +3264,8 @@ ${YELLOW2}Next step:${RESET2} Review ${BOLD}${reportDisplayPath}${RESET2}, then
|
|
|
3235
3264
|
}
|
|
3236
3265
|
|
|
3237
3266
|
// src/commands/ci.ts
|
|
3238
|
-
import { existsSync as
|
|
3239
|
-
import { dirname as dirname3, join as
|
|
3240
|
-
|
|
3241
|
-
// src/local-law.ts
|
|
3242
|
-
import { execFileSync } from "child_process";
|
|
3243
|
-
import { existsSync as existsSync13, mkdirSync as mkdirSync8, readdirSync as readdirSync5, readFileSync as readFileSync11, statSync as statSync5, writeFileSync as writeFileSync9 } from "fs";
|
|
3244
|
-
import { basename as basename2, extname, join as join15, relative as relative3, sep } from "path";
|
|
3245
|
-
import { isV4 as isV43 } from "@decantr/essence-spec";
|
|
3246
|
-
var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
3247
|
-
".astro",
|
|
3248
|
-
".html",
|
|
3249
|
-
".js",
|
|
3250
|
-
".jsx",
|
|
3251
|
-
".svelte",
|
|
3252
|
-
".ts",
|
|
3253
|
-
".tsx",
|
|
3254
|
-
".vue"
|
|
3255
|
-
]);
|
|
3256
|
-
var UI_TEMPLATE_EXTENSIONS = /* @__PURE__ */ new Set([".astro", ".html", ".jsx", ".svelte", ".tsx", ".vue"]);
|
|
3257
|
-
var IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
3258
|
-
".decantr",
|
|
3259
|
-
".git",
|
|
3260
|
-
".next",
|
|
3261
|
-
".nuxt",
|
|
3262
|
-
".svelte-kit",
|
|
3263
|
-
"build",
|
|
3264
|
-
"coverage",
|
|
3265
|
-
"dist",
|
|
3266
|
-
"node_modules",
|
|
3267
|
-
"out"
|
|
3268
|
-
]);
|
|
3269
|
-
var DEFAULT_RULE_EXTENSIONS = [".astro", ".html", ".jsx", ".svelte", ".tsx", ".vue"];
|
|
3270
|
-
function localPatternsProposalPath(projectRoot) {
|
|
3271
|
-
return join15(projectRoot, ".decantr", "local-patterns.proposal.json");
|
|
3272
|
-
}
|
|
3273
|
-
function localPatternsPath(projectRoot) {
|
|
3274
|
-
return join15(projectRoot, ".decantr", "local-patterns.json");
|
|
3275
|
-
}
|
|
3276
|
-
function localRulesProposalPath(projectRoot) {
|
|
3277
|
-
return join15(projectRoot, ".decantr", "rules.proposal.json");
|
|
3278
|
-
}
|
|
3279
|
-
function localRulesPath(projectRoot) {
|
|
3280
|
-
return join15(projectRoot, ".decantr", "rules.json");
|
|
3281
|
-
}
|
|
3282
|
-
function readLocalPatternPack(projectRoot) {
|
|
3283
|
-
return readJsonFile(localPatternsPath(projectRoot));
|
|
3284
|
-
}
|
|
3285
|
-
function readLocalRuleManifest(projectRoot) {
|
|
3286
|
-
return readJsonFile(localRulesPath(projectRoot));
|
|
3287
|
-
}
|
|
3288
|
-
function createBrownfieldCodifyProposal(input) {
|
|
3289
|
-
const sourceFiles = input.fromAudit ? listSourceFiles(input.projectRoot, 800) : [];
|
|
3290
|
-
const evidence = summarizeSourceEvidence(input.projectRoot, sourceFiles);
|
|
3291
|
-
const routes = input.essence && isV43(input.essence) ? Object.keys(input.essence.blueprint.routes ?? {}).sort() : [];
|
|
3292
|
-
const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3293
|
-
const patternPack = {
|
|
3294
|
-
version: 2,
|
|
3295
|
-
generatedAt,
|
|
3296
|
-
status: "proposal",
|
|
3297
|
-
source: input.fromAudit ? "decantr codify --from-audit" : "decantr codify",
|
|
3298
|
-
project: {
|
|
3299
|
-
framework: input.detected.framework,
|
|
3300
|
-
packageManager: input.detected.packageManager,
|
|
3301
|
-
hasTailwind: input.detected.hasTailwind,
|
|
3302
|
-
ruleFiles: input.detected.existingRuleFiles,
|
|
3303
|
-
routeCount: routes.length
|
|
3304
|
-
},
|
|
3305
|
-
purpose: "Project-owned Brownfield/Hybrid UI law. Review and edit before accepting; Decantr treats this as authoritative only after it is copied to .decantr/local-patterns.json.",
|
|
3306
|
-
hybrid: {
|
|
3307
|
-
intent: "This local pattern pack is the first Hybrid authority layer: it maps Decantr concepts onto project-owned components, tokens, classes, and rules without replacing the app runtime.",
|
|
3308
|
-
authorityPrecedence: [
|
|
3309
|
-
"existing production source",
|
|
3310
|
-
"accepted local patterns and rules",
|
|
3311
|
-
"Decantr Essence V4 contract",
|
|
3312
|
-
"hosted registry patterns and execution packs as optional guidance"
|
|
3313
|
-
],
|
|
3314
|
-
hostedPatternMapping: "Use hosted patterns as vocabulary and review guidance. Before enforcing one, map it to a project-owned component path, class recipe, token recipe, or explicit exception here."
|
|
3315
|
-
},
|
|
3316
|
-
patterns: [
|
|
3317
|
-
{
|
|
3318
|
-
id: "button",
|
|
3319
|
-
label: "Button primitives",
|
|
3320
|
-
role: "Actions and command triggers",
|
|
3321
|
-
appliesTo: [
|
|
3322
|
-
"primary action",
|
|
3323
|
-
"secondary action",
|
|
3324
|
-
"tertiary action",
|
|
3325
|
-
"destructive action",
|
|
3326
|
-
"icon action"
|
|
3327
|
-
],
|
|
3328
|
-
componentPaths: evidence.buttonComponents,
|
|
3329
|
-
decide: "Define primary, secondary, tertiary, destructive, icon-only, disabled, and loading button variants from this app.",
|
|
3330
|
-
classHints: evidence.buttonClassHints,
|
|
3331
|
-
variants: evidence.buttonVariants,
|
|
3332
|
-
sourceEvidence: evidence.buttonSourceEvidence,
|
|
3333
|
-
confidence: confidenceForEvidence({
|
|
3334
|
-
componentCount: evidence.buttonComponents.length,
|
|
3335
|
-
classHintCount: evidence.buttonClassHints.length,
|
|
3336
|
-
variantCount: evidence.buttonVariants.length,
|
|
3337
|
-
sourceEvidenceCount: evidence.buttonSourceEvidence.length
|
|
3338
|
-
}),
|
|
3339
|
-
enforcement: {
|
|
3340
|
-
level: "warn",
|
|
3341
|
-
status: "proposal",
|
|
3342
|
-
notes: [
|
|
3343
|
-
"Treat as advisory until the team confirms variant names and wrapper paths.",
|
|
3344
|
-
"Raise related rules to error only after raw button usage can be fixed without churn."
|
|
3345
|
-
]
|
|
3346
|
-
},
|
|
3347
|
-
evidence: evidence.buttonComponents.length ? evidence.buttonComponents : evidence.buttonClassHints.length ? evidence.buttonClassHints : [
|
|
3348
|
-
"No obvious Button wrapper found yet. Add the project-owned wrapper path before strict enforcement."
|
|
3349
|
-
],
|
|
3350
|
-
forbiddenAlternatives: ["New one-off button variants without updating this manifest."]
|
|
3351
|
-
},
|
|
3352
|
-
{
|
|
3353
|
-
id: "surface-card",
|
|
3354
|
-
label: "Cards and surfaces",
|
|
3355
|
-
role: "Cards, panels, and reusable content surfaces",
|
|
3356
|
-
appliesTo: ["cards", "panels", "modals", "list items", "dashboard tiles"],
|
|
3357
|
-
componentPaths: evidence.cardComponents,
|
|
3358
|
-
decide: "Define the canonical card background, border, radius, shadow, padding, density, and hover treatment.",
|
|
3359
|
-
classHints: evidence.cardClassHints,
|
|
3360
|
-
variants: evidence.cardVariants,
|
|
3361
|
-
sourceEvidence: evidence.cardSourceEvidence,
|
|
3362
|
-
confidence: confidenceForEvidence({
|
|
3363
|
-
componentCount: evidence.cardComponents.length,
|
|
3364
|
-
classHintCount: evidence.cardClassHints.length,
|
|
3365
|
-
variantCount: evidence.cardVariants.length,
|
|
3366
|
-
sourceEvidenceCount: evidence.cardSourceEvidence.length
|
|
3367
|
-
}),
|
|
3368
|
-
enforcement: {
|
|
3369
|
-
level: "warn",
|
|
3370
|
-
status: "proposal",
|
|
3371
|
-
notes: [
|
|
3372
|
-
"Use this family to converge repeated surface recipes before enforcing card rules.",
|
|
3373
|
-
"Document route-specific card exceptions before asking CI to block them."
|
|
3374
|
-
]
|
|
3375
|
-
},
|
|
3376
|
-
evidence: evidence.cardComponents.length ? evidence.cardComponents : [
|
|
3377
|
-
"No obvious Card wrapper found yet. Add the project-owned wrapper path or class recipe."
|
|
3378
|
-
],
|
|
3379
|
-
forbiddenAlternatives: ["Flat ad hoc cards with unique color/radius/shadow recipes."]
|
|
3380
|
-
},
|
|
3381
|
-
{
|
|
3382
|
-
id: "page-shell",
|
|
3383
|
-
label: "Page shell and spacing",
|
|
3384
|
-
role: "Route shell, navigation, gutters, max-width, and scroll ownership",
|
|
3385
|
-
appliesTo: ["routes", "layouts", "navigation shells", "scroll containers"],
|
|
3386
|
-
componentPaths: evidence.shellComponents,
|
|
3387
|
-
decide: "Define which layout owns max width, gutters, sticky chrome, responsive breakpoints, and scroll containers.",
|
|
3388
|
-
sourceEvidence: collectSourceEvidence(input.projectRoot, sourceFiles, [
|
|
3389
|
-
"layout",
|
|
3390
|
-
"shell",
|
|
3391
|
-
"nav",
|
|
3392
|
-
"sidebar",
|
|
3393
|
-
"container"
|
|
3394
|
-
]),
|
|
3395
|
-
confidence: confidenceForEvidence({
|
|
3396
|
-
componentCount: evidence.shellComponents.length,
|
|
3397
|
-
sourceEvidenceCount: evidence.shellComponents.length
|
|
3398
|
-
}),
|
|
3399
|
-
enforcement: {
|
|
3400
|
-
level: "advisory",
|
|
3401
|
-
status: "proposal",
|
|
3402
|
-
notes: [
|
|
3403
|
-
"Layout ownership is usually reviewed by humans first because route shells can legitimately differ."
|
|
3404
|
-
]
|
|
3405
|
-
},
|
|
3406
|
-
evidence: evidence.shellComponents.length ? evidence.shellComponents : ["Add root layout, shell, or app frame files that establish route chrome and spacing."],
|
|
3407
|
-
forbiddenAlternatives: [
|
|
3408
|
-
"Each page inventing independent max-width, padding, or sticky nav rules."
|
|
3409
|
-
]
|
|
3410
|
-
},
|
|
3411
|
-
{
|
|
3412
|
-
id: "form-control",
|
|
3413
|
-
label: "Form controls",
|
|
3414
|
-
role: "Inputs, labels, validation, and form actions",
|
|
3415
|
-
appliesTo: ["inputs", "selects", "textareas", "validation messages", "form actions"],
|
|
3416
|
-
componentPaths: evidence.formComponents,
|
|
3417
|
-
decide: "Define input height, label placement, error copy, disabled state, required state, and focus treatment.",
|
|
3418
|
-
variants: evidence.formVariants,
|
|
3419
|
-
sourceEvidence: evidence.formSourceEvidence,
|
|
3420
|
-
confidence: confidenceForEvidence({
|
|
3421
|
-
componentCount: evidence.formComponents.length,
|
|
3422
|
-
variantCount: evidence.formVariants.length,
|
|
3423
|
-
sourceEvidenceCount: evidence.formSourceEvidence.length
|
|
3424
|
-
}),
|
|
3425
|
-
enforcement: {
|
|
3426
|
-
level: "warn",
|
|
3427
|
-
status: "proposal",
|
|
3428
|
-
notes: [
|
|
3429
|
-
"Start by mapping field wrappers, labels, error states, and disabled states from source."
|
|
3430
|
-
]
|
|
3431
|
-
},
|
|
3432
|
-
evidence: evidence.formComponents.length ? evidence.formComponents : ["Add form field wrapper paths and validation examples."],
|
|
3433
|
-
forbiddenAlternatives: [
|
|
3434
|
-
"Unlabeled one-off inputs or validation states that do not match the app standard."
|
|
3435
|
-
]
|
|
3436
|
-
},
|
|
3437
|
-
{
|
|
3438
|
-
id: "theme-variant",
|
|
3439
|
-
label: "Theme variants",
|
|
3440
|
-
role: "Light, dark, brand, density, and tenant/theme variants observed in the app",
|
|
3441
|
-
appliesTo: ["theme toggles", "mode-specific classes", "brand variants", "tenant variants"],
|
|
3442
|
-
componentPaths: evidence.themeComponents,
|
|
3443
|
-
decide: "Document which theme variants exist, where they are toggled, and which tokens/classes are legal per variant.",
|
|
3444
|
-
variants: evidence.themeVariants,
|
|
3445
|
-
sourceEvidence: evidence.themeSourceEvidence,
|
|
3446
|
-
confidence: confidenceForEvidence({
|
|
3447
|
-
componentCount: evidence.themeComponents.length,
|
|
3448
|
-
variantCount: evidence.themeVariants.length,
|
|
3449
|
-
sourceEvidenceCount: evidence.themeSourceEvidence.length
|
|
3450
|
-
}),
|
|
3451
|
-
enforcement: {
|
|
3452
|
-
level: "advisory",
|
|
3453
|
-
status: "proposal",
|
|
3454
|
-
notes: [
|
|
3455
|
-
"Theme variant law should preserve the existing theme provider/tokens before adding any new mode."
|
|
3456
|
-
]
|
|
3457
|
-
},
|
|
3458
|
-
evidence: evidence.themeComponents.length ? evidence.themeComponents : ["If the app has dark/light or brand variants, add the toggles/providers here."],
|
|
3459
|
-
forbiddenAlternatives: [
|
|
3460
|
-
"Component-local theme forks that bypass shared theme providers or tokens."
|
|
3461
|
-
]
|
|
3462
|
-
}
|
|
3463
|
-
],
|
|
3464
|
-
starterRules: [
|
|
3465
|
-
"Prefer project-owned wrappers for repeated primitives once they exist.",
|
|
3466
|
-
"Avoid raw hex/rgb values in component templates unless documented as dynamic data.",
|
|
3467
|
-
"Avoid static inline styles for reusable visual treatment.",
|
|
3468
|
-
"When adding a new route, map it to an existing local pattern before inventing a new visual variant.",
|
|
3469
|
-
"When adding a theme variant, update .decantr/theme-inventory.json and this local pattern pack.",
|
|
3470
|
-
"Map hosted Decantr patterns into project-owned local law before making them enforceable."
|
|
3471
|
-
],
|
|
3472
|
-
nextSteps: [
|
|
3473
|
-
"Edit this proposal with real component paths and token/class recipes.",
|
|
3474
|
-
"Run decantr codify --accept after review.",
|
|
3475
|
-
"Use decantr task <route> before LLM edits so local law appears in task context.",
|
|
3476
|
-
"Run decantr verify --brownfield --local-patterns after edits.",
|
|
3477
|
-
"For Hybrid adoption, start with warn-level local rules and raise severities only after the team agrees the law is stable.",
|
|
3478
|
-
"Wire deterministic project rules into ESLint, Biome, Storybook, visual tests, or CI where Decantr should not guess."
|
|
3479
|
-
]
|
|
3480
|
-
};
|
|
3481
|
-
const ruleManifest = {
|
|
3482
|
-
version: 1,
|
|
3483
|
-
status: "proposal",
|
|
3484
|
-
generatedAt,
|
|
3485
|
-
source: input.fromAudit ? "decantr codify --from-audit" : "decantr codify",
|
|
3486
|
-
purpose: "Mechanical Brownfield/Hybrid checks owned by this project. These rules are intentionally local and stack-agnostic; edit before accepting.",
|
|
3487
|
-
enforcement: {
|
|
3488
|
-
defaultSeverity: "warn",
|
|
3489
|
-
mode: "warn",
|
|
3490
|
-
notes: [
|
|
3491
|
-
"Decantr local rules are a guardrail, not a replacement for ESLint, Biome, type checks, tests, or visual regression.",
|
|
3492
|
-
"Keep rules narrow enough that an LLM can fix findings without rewriting the app.",
|
|
3493
|
-
"Use error severity only after the team agrees the rule is stable."
|
|
3494
|
-
]
|
|
3495
|
-
},
|
|
3496
|
-
rules: [
|
|
3497
|
-
{
|
|
3498
|
-
id: "no-inline-style",
|
|
3499
|
-
type: "forbid-regex",
|
|
3500
|
-
enabled: true,
|
|
3501
|
-
severity: "warn",
|
|
3502
|
-
description: "Reusable UI should not add static inline style attributes.",
|
|
3503
|
-
includeExtensions: DEFAULT_RULE_EXTENSIONS,
|
|
3504
|
-
pattern: "\\bstyle\\s*=",
|
|
3505
|
-
message: "Inline style found in UI template.",
|
|
3506
|
-
suggestedFix: "Move reusable visual treatment into the project style system, component wrapper, token, or documented local pattern.",
|
|
3507
|
-
maxFindings: 25
|
|
3508
|
-
},
|
|
3509
|
-
{
|
|
3510
|
-
id: "no-raw-color-literals",
|
|
3511
|
-
type: "forbid-regex",
|
|
3512
|
-
enabled: true,
|
|
3513
|
-
severity: "warn",
|
|
3514
|
-
description: "Component templates should not introduce raw hex/rgb color literals.",
|
|
3515
|
-
includeExtensions: DEFAULT_RULE_EXTENSIONS,
|
|
3516
|
-
pattern: "#(?:[0-9a-fA-F]{3,8})\\b|rgba?\\s*\\(",
|
|
3517
|
-
message: "Raw color literal found in UI template.",
|
|
3518
|
-
suggestedFix: "Use an existing project token/class, or document the exception in .decantr/local-patterns.json if the value is data-driven.",
|
|
3519
|
-
maxFindings: 25
|
|
3520
|
-
},
|
|
3521
|
-
{
|
|
3522
|
-
id: "prefer-button-wrapper",
|
|
3523
|
-
type: "forbid-regex",
|
|
3524
|
-
enabled: evidence.buttonComponents.length > 0,
|
|
3525
|
-
severity: "info",
|
|
3526
|
-
description: "Prefer the project-owned button primitive instead of new raw button markup.",
|
|
3527
|
-
includeExtensions: DEFAULT_RULE_EXTENSIONS,
|
|
3528
|
-
pattern: "<button[\\s>]",
|
|
3529
|
-
message: "Raw <button> usage found outside the detected button wrapper.",
|
|
3530
|
-
suggestedFix: "Use the project-owned Button primitive, or add this file to allowedPaths if it is the primitive implementation.",
|
|
3531
|
-
allowedPaths: evidence.buttonComponents,
|
|
3532
|
-
maxFindings: 50
|
|
3533
|
-
}
|
|
3534
|
-
]
|
|
3535
|
-
};
|
|
3536
|
-
return { patternPack, ruleManifest };
|
|
3537
|
-
}
|
|
3538
|
-
function writeBrownfieldCodifyProposal(projectRoot, proposal) {
|
|
3539
|
-
const decantrDir = join15(projectRoot, ".decantr");
|
|
3540
|
-
mkdirSync8(decantrDir, { recursive: true });
|
|
3541
|
-
const patternPath = localPatternsProposalPath(projectRoot);
|
|
3542
|
-
const rulesPath = localRulesProposalPath(projectRoot);
|
|
3543
|
-
writeFileSync9(patternPath, `${JSON.stringify(proposal.patternPack, null, 2)}
|
|
3544
|
-
`, "utf-8");
|
|
3545
|
-
writeFileSync9(rulesPath, `${JSON.stringify(proposal.ruleManifest, null, 2)}
|
|
3546
|
-
`, "utf-8");
|
|
3547
|
-
return { patternPath, rulesPath };
|
|
3548
|
-
}
|
|
3549
|
-
function writeHostedPatternMappingProposal(input) {
|
|
3550
|
-
const decantrDir = join15(input.projectRoot, ".decantr");
|
|
3551
|
-
mkdirSync8(decantrDir, { recursive: true });
|
|
3552
|
-
const existing = readJsonFile(localPatternsProposalPath(input.projectRoot)) ?? readJsonFile(localPatternsPath(input.projectRoot));
|
|
3553
|
-
const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3554
|
-
const patternPack = {
|
|
3555
|
-
version: 2,
|
|
3556
|
-
generatedAt,
|
|
3557
|
-
status: "proposal",
|
|
3558
|
-
source: "decantr codify --map-pattern",
|
|
3559
|
-
purpose: existing?.purpose ?? "Project-owned Brownfield/Hybrid UI law. Review and edit before accepting; Decantr treats this as authoritative only after it is copied to .decantr/local-patterns.json.",
|
|
3560
|
-
hybrid: existing?.hybrid ?? {
|
|
3561
|
-
intent: "This local pattern pack maps Decantr concepts onto project-owned components, tokens, classes, and rules without replacing the app runtime.",
|
|
3562
|
-
authorityPrecedence: [
|
|
3563
|
-
"existing production source",
|
|
3564
|
-
"accepted local patterns and rules",
|
|
3565
|
-
"Decantr Essence V4 contract",
|
|
3566
|
-
"hosted registry patterns and execution packs as optional guidance"
|
|
3567
|
-
],
|
|
3568
|
-
hostedPatternMapping: "Hosted registry patterns are advisory until mapped to project-owned component paths, token recipes, class recipes, and explicit exceptions."
|
|
3569
|
-
},
|
|
3570
|
-
patterns: Array.isArray(existing?.patterns) ? [...existing.patterns] : [],
|
|
3571
|
-
starterRules: Array.isArray(existing?.starterRules) ? existing.starterRules : [],
|
|
3572
|
-
nextSteps: Array.isArray(existing?.nextSteps) ? existing.nextSteps : [
|
|
3573
|
-
"Review this proposal with the team.",
|
|
3574
|
-
"Add project-owned component paths, token hints, class recipes, and exceptions.",
|
|
3575
|
-
"Run decantr codify --accept after review.",
|
|
3576
|
-
"Use decantr task <route> before LLM edits so local law appears in task context."
|
|
3577
|
-
]
|
|
3578
|
-
};
|
|
3579
|
-
const slug = input.hostedPattern.slug.trim();
|
|
3580
|
-
const existingIds = new Set(
|
|
3581
|
-
(patternPack.patterns ?? []).map((pattern) => typeof pattern.id === "string" ? pattern.id : "").filter(Boolean)
|
|
3582
|
-
);
|
|
3583
|
-
const localPatternId = existingIds.has(slug) ? `${slug}-registry-map` : slug;
|
|
3584
|
-
const mappedPattern = {
|
|
3585
|
-
id: localPatternId,
|
|
3586
|
-
label: input.hostedPattern.name ? `${input.hostedPattern.name} registry mapping` : `${slug} registry mapping`,
|
|
3587
|
-
role: "Hosted registry guidance mapped into project-owned Hybrid law",
|
|
3588
|
-
appliesTo: [
|
|
3589
|
-
...input.hostedPattern.components ?? [],
|
|
3590
|
-
...input.hostedPattern.interactions ?? [],
|
|
3591
|
-
...input.hostedPattern.tags ?? []
|
|
3592
|
-
].slice(0, 24),
|
|
3593
|
-
componentPaths: [],
|
|
3594
|
-
tokenHints: [],
|
|
3595
|
-
classHints: [],
|
|
3596
|
-
decide: `Map hosted pattern "${slug}" into this app's existing components, tokens, classes, and exceptions before treating it as enforceable.`,
|
|
3597
|
-
hostedPatternRefs: [input.hostedPattern],
|
|
3598
|
-
confidence: {
|
|
3599
|
-
tier: "low",
|
|
3600
|
-
score: 0.25,
|
|
3601
|
-
rationale: [
|
|
3602
|
-
"A hosted registry pattern was selected intentionally.",
|
|
3603
|
-
"No project-owned component path or token recipe has been mapped yet."
|
|
3604
|
-
]
|
|
3605
|
-
},
|
|
3606
|
-
enforcement: {
|
|
3607
|
-
level: "advisory",
|
|
3608
|
-
status: "needs-mapping",
|
|
3609
|
-
notes: [
|
|
3610
|
-
"This proposal does not change source files.",
|
|
3611
|
-
"Do not enforce this hosted pattern until project-owned implementation details are filled in.",
|
|
3612
|
-
"Use accepted local rules, ESLint, Biome, Storybook, or visual regression for deterministic blocking checks."
|
|
3613
|
-
]
|
|
3614
|
-
},
|
|
3615
|
-
evidence: [
|
|
3616
|
-
`Hosted pattern ${input.hostedPattern.source}/${slug}${input.hostedPattern.description ? `: ${input.hostedPattern.description}` : ""}`,
|
|
3617
|
-
input.hostedPattern.visualBrief ? `Visual brief: ${input.hostedPattern.visualBrief}` : "Visual brief not provided by the hosted pattern."
|
|
3618
|
-
],
|
|
3619
|
-
evidenceToCollect: [
|
|
3620
|
-
"Project-owned component path(s) that implement this pattern.",
|
|
3621
|
-
"Allowed token/class recipe(s) for this app.",
|
|
3622
|
-
"Variant names and states that are legal in this project.",
|
|
3623
|
-
"Explicit exceptions where this hosted pattern should not apply."
|
|
3624
|
-
],
|
|
3625
|
-
forbiddenAlternatives: [
|
|
3626
|
-
"Replacing existing app components solely because a hosted pattern exists.",
|
|
3627
|
-
"Treating hosted registry guidance as enforceable without accepted local law."
|
|
3628
|
-
]
|
|
3629
|
-
};
|
|
3630
|
-
const index = (patternPack.patterns ?? []).findIndex((pattern) => pattern.id === localPatternId);
|
|
3631
|
-
const replacedExisting = index >= 0;
|
|
3632
|
-
if (replacedExisting) {
|
|
3633
|
-
patternPack.patterns[index] = mappedPattern;
|
|
3634
|
-
} else {
|
|
3635
|
-
patternPack.patterns = [...patternPack.patterns ?? [], mappedPattern];
|
|
3636
|
-
}
|
|
3637
|
-
patternPack.nextSteps = [
|
|
3638
|
-
.../* @__PURE__ */ new Set([
|
|
3639
|
-
...patternPack.nextSteps ?? [],
|
|
3640
|
-
"Fill the hosted mapping with project-owned component paths, token/class recipes, variants, and exceptions.",
|
|
3641
|
-
"Run decantr codify --accept only after the mapping reflects the existing app.",
|
|
3642
|
-
"Use decantr verify --brownfield --local-patterns after edits to keep mapped local law visible."
|
|
3643
|
-
])
|
|
3644
|
-
];
|
|
3645
|
-
const patternPath = localPatternsProposalPath(input.projectRoot);
|
|
3646
|
-
writeFileSync9(patternPath, `${JSON.stringify(patternPack, null, 2)}
|
|
3647
|
-
`, "utf-8");
|
|
3648
|
-
return { patternPath, localPatternId, replacedExisting };
|
|
3649
|
-
}
|
|
3650
|
-
function acceptBrownfieldLocalLaw(projectRoot) {
|
|
3651
|
-
const patternProposal = readJsonFile(localPatternsProposalPath(projectRoot));
|
|
3652
|
-
const ruleProposal = readJsonFile(localRulesProposalPath(projectRoot));
|
|
3653
|
-
const acceptedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3654
|
-
let patternAcceptedPath = null;
|
|
3655
|
-
let rulesAcceptedPath = null;
|
|
3656
|
-
if (patternProposal) {
|
|
3657
|
-
patternProposal.status = "accepted";
|
|
3658
|
-
patternProposal.acceptedAt = acceptedAt;
|
|
3659
|
-
patternAcceptedPath = localPatternsPath(projectRoot);
|
|
3660
|
-
writeFileSync9(patternAcceptedPath, `${JSON.stringify(patternProposal, null, 2)}
|
|
3661
|
-
`, "utf-8");
|
|
3662
|
-
}
|
|
3663
|
-
if (ruleProposal) {
|
|
3664
|
-
ruleProposal.status = "accepted";
|
|
3665
|
-
ruleProposal.acceptedAt = acceptedAt;
|
|
3666
|
-
rulesAcceptedPath = localRulesPath(projectRoot);
|
|
3667
|
-
writeFileSync9(rulesAcceptedPath, `${JSON.stringify(ruleProposal, null, 2)}
|
|
3668
|
-
`, "utf-8");
|
|
3669
|
-
}
|
|
3670
|
-
return { patternAcceptedPath, rulesAcceptedPath };
|
|
3671
|
-
}
|
|
3672
|
-
function validateLocalLaw(projectRoot) {
|
|
3673
|
-
const patternsPath = localPatternsPath(projectRoot);
|
|
3674
|
-
const rulesPath = localRulesPath(projectRoot);
|
|
3675
|
-
const patternPack = readJsonFile(patternsPath);
|
|
3676
|
-
const ruleManifest = readJsonFile(rulesPath);
|
|
3677
|
-
const warnings = [];
|
|
3678
|
-
if (patternPack) {
|
|
3679
|
-
const patternIds = /* @__PURE__ */ new Set();
|
|
3680
|
-
const patterns = Array.isArray(patternPack.patterns) ? patternPack.patterns : [];
|
|
3681
|
-
if (patterns.length === 0) {
|
|
3682
|
-
warnings.push(".decantr/local-patterns.json has no patterns.");
|
|
3683
|
-
}
|
|
3684
|
-
for (const pattern of patterns) {
|
|
3685
|
-
const id = typeof pattern.id === "string" ? pattern.id.trim() : "";
|
|
3686
|
-
if (!id) warnings.push("A local pattern is missing an id.");
|
|
3687
|
-
if (id && patternIds.has(id)) warnings.push(`Duplicate local pattern id: ${id}`);
|
|
3688
|
-
if (id) patternIds.add(id);
|
|
3689
|
-
const paths = Array.isArray(pattern.componentPaths) ? pattern.componentPaths : [];
|
|
3690
|
-
const evidence = Array.isArray(pattern.evidence) ? pattern.evidence : [];
|
|
3691
|
-
const todoEvidence = Array.isArray(pattern.evidenceToCollect) ? pattern.evidenceToCollect : [];
|
|
3692
|
-
const hostedRefs = Array.isArray(pattern.hostedPatternRefs) ? pattern.hostedPatternRefs : [];
|
|
3693
|
-
if (id && paths.length === 0 && evidence.length === 0 && todoEvidence.length > 0) {
|
|
3694
|
-
warnings.push(
|
|
3695
|
-
`Local pattern ${id} still reads like a TODO; add concrete component paths or evidence.`
|
|
3696
|
-
);
|
|
3697
|
-
}
|
|
3698
|
-
if (id && hostedRefs.length > 0 && paths.length === 0) {
|
|
3699
|
-
warnings.push(
|
|
3700
|
-
`Local pattern ${id} maps hosted registry guidance but has no project-owned component path yet.`
|
|
3701
|
-
);
|
|
3702
|
-
}
|
|
3703
|
-
}
|
|
3704
|
-
}
|
|
3705
|
-
if (ruleManifest && !Array.isArray(ruleManifest.rules)) {
|
|
3706
|
-
warnings.push(".decantr/rules.json has no rules array.");
|
|
3707
|
-
}
|
|
3708
|
-
const findings = ruleManifest ? scanLocalRules(projectRoot, ruleManifest) : [];
|
|
3709
|
-
return {
|
|
3710
|
-
patternsPath,
|
|
3711
|
-
rulesPath,
|
|
3712
|
-
patternPackPresent: Boolean(patternPack),
|
|
3713
|
-
ruleManifestPresent: Boolean(ruleManifest),
|
|
3714
|
-
warnings,
|
|
3715
|
-
findings
|
|
3716
|
-
};
|
|
3717
|
-
}
|
|
3718
|
-
function createLocalLawTaskSummary(projectRoot) {
|
|
3719
|
-
const patternPack = readLocalPatternPack(projectRoot);
|
|
3720
|
-
const ruleManifest = readLocalRuleManifest(projectRoot);
|
|
3721
|
-
const patterns = (patternPack?.patterns ?? []).map((pattern) => ({
|
|
3722
|
-
id: typeof pattern.id === "string" ? pattern.id : "unknown",
|
|
3723
|
-
role: typeof pattern.role === "string" ? pattern.role : null,
|
|
3724
|
-
componentPaths: Array.isArray(pattern.componentPaths) ? pattern.componentPaths.filter((entry) => typeof entry === "string") : [],
|
|
3725
|
-
confidenceTier: typeof pattern.confidence === "object" && pattern.confidence !== null && typeof pattern.confidence.tier === "string" ? pattern.confidence.tier : null,
|
|
3726
|
-
enforcementLevel: typeof pattern.enforcement === "object" && pattern.enforcement !== null && typeof pattern.enforcement.level === "string" ? pattern.enforcement.level : null,
|
|
3727
|
-
hostedPatternRefs: Array.isArray(pattern.hostedPatternRefs) ? pattern.hostedPatternRefs.map((ref) => typeof ref?.slug === "string" ? ref.slug : null).filter((slug) => Boolean(slug)) : []
|
|
3728
|
-
}));
|
|
3729
|
-
const rules = (ruleManifest?.rules ?? []).map((rule) => ({
|
|
3730
|
-
id: rule.id,
|
|
3731
|
-
severity: rule.severity,
|
|
3732
|
-
enabled: rule.enabled,
|
|
3733
|
-
description: rule.description
|
|
3734
|
-
}));
|
|
3735
|
-
return {
|
|
3736
|
-
patternsPath: patternPack ? ".decantr/local-patterns.json" : null,
|
|
3737
|
-
rulesPath: ruleManifest ? ".decantr/rules.json" : null,
|
|
3738
|
-
patternCount: patterns.length,
|
|
3739
|
-
ruleCount: rules.length,
|
|
3740
|
-
patterns,
|
|
3741
|
-
rules
|
|
3742
|
-
};
|
|
3743
|
-
}
|
|
3744
|
-
function changedFiles(projectRoot, since) {
|
|
3745
|
-
const changed = /* @__PURE__ */ new Set();
|
|
3746
|
-
try {
|
|
3747
|
-
const commands = since ? [
|
|
3748
|
-
["diff", "--name-only", since, "--"],
|
|
3749
|
-
["diff", "--name-only", "--cached"]
|
|
3750
|
-
] : [
|
|
3751
|
-
["diff", "--name-only"],
|
|
3752
|
-
["diff", "--name-only", "--cached"]
|
|
3753
|
-
];
|
|
3754
|
-
for (const args of commands) {
|
|
3755
|
-
const output = execFileSync("git", args, {
|
|
3756
|
-
cwd: projectRoot,
|
|
3757
|
-
encoding: "utf-8",
|
|
3758
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
3759
|
-
});
|
|
3760
|
-
for (const line of output.split(/\r?\n/)) {
|
|
3761
|
-
const file = line.trim();
|
|
3762
|
-
if (file) changed.add(normalizePath(file));
|
|
3763
|
-
}
|
|
3764
|
-
}
|
|
3765
|
-
} catch {
|
|
3766
|
-
}
|
|
3767
|
-
return [...changed].sort();
|
|
3768
|
-
}
|
|
3769
|
-
function routeImpacts(projectRoot, files) {
|
|
3770
|
-
const analysis = readJsonFile(
|
|
3771
|
-
join15(projectRoot, ".decantr", "analysis.json")
|
|
3772
|
-
);
|
|
3773
|
-
const routeEntries = analysis?.routes?.routes ?? [];
|
|
3774
|
-
const impacted = /* @__PURE__ */ new Set();
|
|
3775
|
-
for (const file of files) {
|
|
3776
|
-
for (const route of routeEntries) {
|
|
3777
|
-
if (route.file && pathMatches(file, route.file)) {
|
|
3778
|
-
if (route.path) impacted.add(route.path);
|
|
3779
|
-
}
|
|
3780
|
-
}
|
|
3781
|
-
}
|
|
3782
|
-
return [...impacted].sort();
|
|
3783
|
-
}
|
|
3784
|
-
function scanLocalRules(projectRoot, manifest) {
|
|
3785
|
-
const findings = [];
|
|
3786
|
-
const files = listSourceFiles(projectRoot, 1200);
|
|
3787
|
-
for (const rule of manifest.rules ?? []) {
|
|
3788
|
-
if (!rule.enabled || rule.type !== "forbid-regex") continue;
|
|
3789
|
-
const extensions = new Set(
|
|
3790
|
-
rule.includeExtensions?.length ? rule.includeExtensions : DEFAULT_RULE_EXTENSIONS
|
|
3791
|
-
);
|
|
3792
|
-
let regex;
|
|
3793
|
-
try {
|
|
3794
|
-
regex = new RegExp(rule.pattern, "g");
|
|
3795
|
-
} catch {
|
|
3796
|
-
findings.push({
|
|
3797
|
-
ruleId: rule.id,
|
|
3798
|
-
severity: "error",
|
|
3799
|
-
file: ".decantr/rules.json",
|
|
3800
|
-
line: 1,
|
|
3801
|
-
column: 1,
|
|
3802
|
-
excerpt: rule.pattern,
|
|
3803
|
-
message: `Invalid regex for local rule ${rule.id}.`,
|
|
3804
|
-
suggestedFix: "Edit .decantr/rules.json so the pattern is a valid JavaScript regular expression."
|
|
3805
|
-
});
|
|
3806
|
-
continue;
|
|
3807
|
-
}
|
|
3808
|
-
let ruleFindingCount = 0;
|
|
3809
|
-
for (const file of files) {
|
|
3810
|
-
if (!extensions.has(extname(file.absolute))) continue;
|
|
3811
|
-
if (pathAllowed(file.relative, rule.allowedPaths ?? [])) continue;
|
|
3812
|
-
const contents = readFileSync11(file.absolute, "utf-8");
|
|
3813
|
-
for (const match of contents.matchAll(regex)) {
|
|
3814
|
-
const index = match.index ?? 0;
|
|
3815
|
-
const position = lineColumnAt(contents, index);
|
|
3816
|
-
findings.push({
|
|
3817
|
-
ruleId: rule.id,
|
|
3818
|
-
severity: rule.severity,
|
|
3819
|
-
file: file.relative,
|
|
3820
|
-
line: position.line,
|
|
3821
|
-
column: position.column,
|
|
3822
|
-
excerpt: lineAt(contents, position.line).trim().slice(0, 180),
|
|
3823
|
-
message: rule.message,
|
|
3824
|
-
suggestedFix: rule.suggestedFix
|
|
3825
|
-
});
|
|
3826
|
-
ruleFindingCount += 1;
|
|
3827
|
-
if (rule.maxFindings && ruleFindingCount >= rule.maxFindings) break;
|
|
3828
|
-
}
|
|
3829
|
-
if (rule.maxFindings && ruleFindingCount >= rule.maxFindings) break;
|
|
3830
|
-
}
|
|
3831
|
-
}
|
|
3832
|
-
return findings;
|
|
3833
|
-
}
|
|
3834
|
-
function summarizeSourceEvidence(projectRoot, files) {
|
|
3835
|
-
const componentPaths = files.filter((file) => /(^|[/\\])components?([/\\]|$)|(^|[/\\])ui([/\\]|$)/i.test(file.relative)).map((file) => file.relative);
|
|
3836
|
-
const byName = (terms) => componentPaths.filter((file) => terms.some((term) => basename2(file).toLowerCase().includes(term))).slice(0, 12);
|
|
3837
|
-
const themeComponents = componentPaths.filter((file) => /theme|provider|mode|appearance|tenant|brand/i.test(file)).slice(0, 12);
|
|
3838
|
-
const shellComponents = files.filter((file) => /layout|shell|frame|app|root|nav|sidebar/i.test(basename2(file.relative))).map((file) => file.relative).slice(0, 12);
|
|
3839
|
-
return {
|
|
3840
|
-
buttonComponents: byName(["button", "action"]),
|
|
3841
|
-
cardComponents: byName(["card", "panel", "surface", "tile"]),
|
|
3842
|
-
formComponents: byName(["input", "field", "form", "select", "textarea"]),
|
|
3843
|
-
shellComponents,
|
|
3844
|
-
themeComponents,
|
|
3845
|
-
buttonVariants: detectVariants(projectRoot, files, {
|
|
3846
|
-
family: "button",
|
|
3847
|
-
terms: ["primary", "secondary", "tertiary", "ghost", "link", "destructive", "icon"]
|
|
3848
|
-
}),
|
|
3849
|
-
cardVariants: detectVariants(projectRoot, files, {
|
|
3850
|
-
family: "surface",
|
|
3851
|
-
terms: ["card", "panel", "surface", "tile", "elevated", "interactive", "glass"]
|
|
3852
|
-
}),
|
|
3853
|
-
formVariants: detectVariants(projectRoot, files, {
|
|
3854
|
-
family: "form",
|
|
3855
|
-
terms: ["error", "invalid", "disabled", "required", "success", "helper"]
|
|
3856
|
-
}),
|
|
3857
|
-
themeVariants: detectVariants(projectRoot, files, {
|
|
3858
|
-
family: "theme",
|
|
3859
|
-
terms: ["dark", "light", "brand", "tenant", "theme", "mode", "density"]
|
|
3860
|
-
}),
|
|
3861
|
-
buttonSourceEvidence: collectSourceEvidence(projectRoot, files, [
|
|
3862
|
-
"button",
|
|
3863
|
-
"primary",
|
|
3864
|
-
"secondary",
|
|
3865
|
-
"tertiary",
|
|
3866
|
-
"destructive"
|
|
3867
|
-
]),
|
|
3868
|
-
cardSourceEvidence: collectSourceEvidence(projectRoot, files, [
|
|
3869
|
-
"card",
|
|
3870
|
-
"panel",
|
|
3871
|
-
"surface",
|
|
3872
|
-
"rounded",
|
|
3873
|
-
"shadow"
|
|
3874
|
-
]),
|
|
3875
|
-
formSourceEvidence: collectSourceEvidence(projectRoot, files, [
|
|
3876
|
-
"input",
|
|
3877
|
-
"label",
|
|
3878
|
-
"error",
|
|
3879
|
-
"field",
|
|
3880
|
-
"select"
|
|
3881
|
-
]),
|
|
3882
|
-
themeSourceEvidence: collectSourceEvidence(projectRoot, files, [
|
|
3883
|
-
"theme",
|
|
3884
|
-
"dark",
|
|
3885
|
-
"light",
|
|
3886
|
-
"brand",
|
|
3887
|
-
"tenant"
|
|
3888
|
-
]),
|
|
3889
|
-
buttonClassHints: collectClassHints(projectRoot, files, [
|
|
3890
|
-
"button",
|
|
3891
|
-
"btn",
|
|
3892
|
-
"action",
|
|
3893
|
-
"primary",
|
|
3894
|
-
"secondary",
|
|
3895
|
-
"tertiary",
|
|
3896
|
-
"ghost",
|
|
3897
|
-
"link"
|
|
3898
|
-
]),
|
|
3899
|
-
cardClassHints: collectClassHints(projectRoot, files, ["card", "panel", "surface", "tile"])
|
|
3900
|
-
};
|
|
3901
|
-
}
|
|
3902
|
-
function detectVariants(projectRoot, files, input) {
|
|
3903
|
-
const variants = /* @__PURE__ */ new Map();
|
|
3904
|
-
const termPattern = new RegExp(
|
|
3905
|
-
`\\b(${input.terms.map(escapeRegExp).join("|")})(?:[-_:][a-z0-9-]+)?\\b`,
|
|
3906
|
-
"gi"
|
|
3907
|
-
);
|
|
3908
|
-
for (const file of files) {
|
|
3909
|
-
if (!UI_TEMPLATE_EXTENSIONS.has(extname(file.absolute))) continue;
|
|
3910
|
-
const content = readFileSync11(join15(projectRoot, file.relative), "utf-8");
|
|
3911
|
-
for (const match of content.matchAll(termPattern)) {
|
|
3912
|
-
const raw = (match[0] ?? "").toLowerCase();
|
|
3913
|
-
const id = raw.replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
3914
|
-
if (!id || id.length < 3) continue;
|
|
3915
|
-
const position = lineColumnAt(content, match.index ?? 0);
|
|
3916
|
-
const line = lineAt(content, position.line).trim().slice(0, 160);
|
|
3917
|
-
const entry = variants.get(id) ?? { evidence: /* @__PURE__ */ new Set(), count: 0 };
|
|
3918
|
-
entry.count += 1;
|
|
3919
|
-
entry.evidence.add(`${file.relative}:${position.line} ${line}`);
|
|
3920
|
-
variants.set(id, entry);
|
|
3921
|
-
}
|
|
3922
|
-
}
|
|
3923
|
-
return [...variants.entries()].sort((a, b) => b[1].count - a[1].count || a[0].localeCompare(b[0])).slice(0, 8).map(([id, value]) => ({
|
|
3924
|
-
id,
|
|
3925
|
-
label: `${input.family} ${id}`,
|
|
3926
|
-
evidence: [...value.evidence].slice(0, 4),
|
|
3927
|
-
confidence: value.count >= 4 ? "high" : value.count >= 2 ? "medium" : "low"
|
|
3928
|
-
}));
|
|
3929
|
-
}
|
|
3930
|
-
function collectSourceEvidence(projectRoot, files, terms) {
|
|
3931
|
-
const results = [];
|
|
3932
|
-
const termPattern = new RegExp(terms.map(escapeRegExp).join("|"), "i");
|
|
3933
|
-
for (const file of files) {
|
|
3934
|
-
if (!UI_TEMPLATE_EXTENSIONS.has(extname(file.absolute))) continue;
|
|
3935
|
-
const content = readFileSync11(join15(projectRoot, file.relative), "utf-8");
|
|
3936
|
-
const lines = content.split(/\r?\n/);
|
|
3937
|
-
for (let index = 0; index < lines.length; index += 1) {
|
|
3938
|
-
const line = lines[index];
|
|
3939
|
-
if (!termPattern.test(line)) continue;
|
|
3940
|
-
const signals = terms.filter((term) => line.toLowerCase().includes(term.toLowerCase()));
|
|
3941
|
-
results.push({
|
|
3942
|
-
file: file.relative,
|
|
3943
|
-
line: index + 1,
|
|
3944
|
-
excerpt: line.trim().slice(0, 180),
|
|
3945
|
-
signals: signals.slice(0, 6)
|
|
3946
|
-
});
|
|
3947
|
-
if (results.length >= 16) return results;
|
|
3948
|
-
break;
|
|
3949
|
-
}
|
|
3950
|
-
}
|
|
3951
|
-
return results;
|
|
3952
|
-
}
|
|
3953
|
-
function confidenceForEvidence(input) {
|
|
3954
|
-
const score = Math.min(
|
|
3955
|
-
1,
|
|
3956
|
-
input.componentCount * 0.28 + (input.classHintCount ?? 0) * 0.08 + (input.variantCount ?? 0) * 0.08 + (input.sourceEvidenceCount ?? 0) * 0.05
|
|
3957
|
-
);
|
|
3958
|
-
const tier = score >= 0.7 ? "high" : score >= 0.4 ? "medium" : "low";
|
|
3959
|
-
const rationale = [
|
|
3960
|
-
input.componentCount > 0 ? `${input.componentCount} likely component path(s) detected` : "no likely component wrapper detected yet",
|
|
3961
|
-
(input.classHintCount ?? 0) > 0 ? `${input.classHintCount} class recipe hint(s) detected` : "no strong class recipe hints detected yet",
|
|
3962
|
-
(input.variantCount ?? 0) > 0 ? `${input.variantCount} possible variant signal(s) detected` : "variant names still need team confirmation"
|
|
3963
|
-
];
|
|
3964
|
-
return { tier, score: Number(score.toFixed(2)), rationale };
|
|
3965
|
-
}
|
|
3966
|
-
function collectClassHints(projectRoot, files, terms) {
|
|
3967
|
-
const hints = /* @__PURE__ */ new Map();
|
|
3968
|
-
const wantsButton = terms.some(
|
|
3969
|
-
(term) => /^(button|btn|action|primary|secondary|tertiary|ghost|link)$/i.test(term)
|
|
3970
|
-
);
|
|
3971
|
-
const wantsSurface = terms.some((term) => /^(card|panel|surface|tile)$/i.test(term));
|
|
3972
|
-
const buttonSignal = /button|btn|action|primary|secondary|tertiary|ghost|link|destructive|icon/i;
|
|
3973
|
-
const surfaceSignal = /card|panel|surface|tile|rounded|shadow|border|bg-|p-\d|px-|py-/i;
|
|
3974
|
-
for (const file of files) {
|
|
3975
|
-
if (!UI_TEMPLATE_EXTENSIONS.has(extname(file.absolute))) continue;
|
|
3976
|
-
const content = readFileSync11(join15(projectRoot, file.relative), "utf-8");
|
|
3977
|
-
if (!terms.some((term) => content.toLowerCase().includes(term))) continue;
|
|
3978
|
-
if (wantsButton) {
|
|
3979
|
-
const openingTags = content.matchAll(
|
|
3980
|
-
/<([A-Za-z][\w.:/-]*)\b[^>]*\bclass(?:Name)?\s*=\s*["'`]([^"'`]+)["'`][^>]*>/g
|
|
3981
|
-
);
|
|
3982
|
-
for (const match of openingTags) {
|
|
3983
|
-
const tag = match[1];
|
|
3984
|
-
const value = match[2].trim();
|
|
3985
|
-
const tagLooksInteractive = /^(button|a|Link)$/i.test(tag) || /(^|\.)(Button|IconButton|LinkButton|Action)$/i.test(tag);
|
|
3986
|
-
const fileLooksInteractive = /button|action|link/i.test(basename2(file.relative));
|
|
3987
|
-
if (!tagLooksInteractive && !fileLooksInteractive) continue;
|
|
3988
|
-
if (!buttonSignal.test(value) && !fileLooksInteractive) continue;
|
|
3989
|
-
hints.set(value, (hints.get(value) ?? 0) + 1);
|
|
3990
|
-
}
|
|
3991
|
-
continue;
|
|
3992
|
-
}
|
|
3993
|
-
const matches = content.matchAll(/\bclass(?:Name)?\s*=\s*["'`]([^"'`]+)["'`]/g);
|
|
3994
|
-
for (const match of matches) {
|
|
3995
|
-
const value = match[1].trim();
|
|
3996
|
-
const keep = wantsSurface && surfaceSignal.test(value) || !wantsButton && !wantsSurface && (buttonSignal.test(value) || surfaceSignal.test(value));
|
|
3997
|
-
if (!keep) continue;
|
|
3998
|
-
hints.set(value, (hints.get(value) ?? 0) + 1);
|
|
3999
|
-
}
|
|
4000
|
-
}
|
|
4001
|
-
return [...hints.entries()].sort((a, b) => b[1] - a[1]).slice(0, 8).map(([hint]) => hint);
|
|
4002
|
-
}
|
|
4003
|
-
function listSourceFiles(projectRoot, maxFiles) {
|
|
4004
|
-
const files = [];
|
|
4005
|
-
const visit = (dir) => {
|
|
4006
|
-
if (files.length >= maxFiles) return;
|
|
4007
|
-
let entries;
|
|
4008
|
-
try {
|
|
4009
|
-
entries = readdirSync5(dir);
|
|
4010
|
-
} catch {
|
|
4011
|
-
return;
|
|
4012
|
-
}
|
|
4013
|
-
for (const entry of entries) {
|
|
4014
|
-
if (files.length >= maxFiles) return;
|
|
4015
|
-
if (IGNORED_DIRS.has(entry)) continue;
|
|
4016
|
-
const absolute = join15(dir, entry);
|
|
4017
|
-
let stat;
|
|
4018
|
-
try {
|
|
4019
|
-
stat = statSync5(absolute);
|
|
4020
|
-
} catch {
|
|
4021
|
-
continue;
|
|
4022
|
-
}
|
|
4023
|
-
if (stat.isDirectory()) {
|
|
4024
|
-
visit(absolute);
|
|
4025
|
-
} else if (stat.isFile() && SOURCE_EXTENSIONS.has(extname(entry))) {
|
|
4026
|
-
files.push({ absolute, relative: normalizePath(relative3(projectRoot, absolute)) });
|
|
4027
|
-
}
|
|
4028
|
-
}
|
|
4029
|
-
};
|
|
4030
|
-
visit(projectRoot);
|
|
4031
|
-
return files.sort((a, b) => a.relative.localeCompare(b.relative));
|
|
4032
|
-
}
|
|
4033
|
-
function readJsonFile(path) {
|
|
4034
|
-
if (!existsSync13(path)) return null;
|
|
4035
|
-
try {
|
|
4036
|
-
return JSON.parse(readFileSync11(path, "utf-8"));
|
|
4037
|
-
} catch {
|
|
4038
|
-
return null;
|
|
4039
|
-
}
|
|
4040
|
-
}
|
|
4041
|
-
function pathAllowed(file, allowedPaths) {
|
|
4042
|
-
return allowedPaths.some((allowedPath) => pathMatches(file, allowedPath));
|
|
4043
|
-
}
|
|
4044
|
-
function pathMatches(file, pattern) {
|
|
4045
|
-
const normalizedFile = normalizePath(file);
|
|
4046
|
-
const normalizedPattern = normalizePath(pattern);
|
|
4047
|
-
return normalizedFile === normalizedPattern || normalizedFile.endsWith(`/${normalizedPattern}`);
|
|
4048
|
-
}
|
|
4049
|
-
function normalizePath(path) {
|
|
4050
|
-
return path.split(sep).join("/").replace(/\\/g, "/");
|
|
4051
|
-
}
|
|
4052
|
-
function lineColumnAt(contents, index) {
|
|
4053
|
-
const before = contents.slice(0, index);
|
|
4054
|
-
const lines = before.split(/\r?\n/);
|
|
4055
|
-
return {
|
|
4056
|
-
line: lines.length,
|
|
4057
|
-
column: lines[lines.length - 1].length + 1
|
|
4058
|
-
};
|
|
4059
|
-
}
|
|
4060
|
-
function escapeRegExp(value) {
|
|
4061
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4062
|
-
}
|
|
4063
|
-
function lineAt(contents, line) {
|
|
4064
|
-
return contents.split(/\r?\n/)[line - 1] ?? "";
|
|
4065
|
-
}
|
|
4066
|
-
|
|
4067
|
-
// src/style-bridge.ts
|
|
4068
|
-
import { existsSync as existsSync14, mkdirSync as mkdirSync9, readFileSync as readFileSync12, writeFileSync as writeFileSync10 } from "fs";
|
|
4069
|
-
import { join as join16 } from "path";
|
|
4070
|
-
function styleBridgeProposalPath(projectRoot) {
|
|
4071
|
-
return join16(projectRoot, ".decantr", "style-bridge.proposal.json");
|
|
4072
|
-
}
|
|
4073
|
-
function styleBridgePath(projectRoot) {
|
|
4074
|
-
return join16(projectRoot, ".decantr", "style-bridge.json");
|
|
4075
|
-
}
|
|
4076
|
-
function readJsonFile2(path) {
|
|
4077
|
-
if (!existsSync14(path)) return null;
|
|
4078
|
-
try {
|
|
4079
|
-
return JSON.parse(readFileSync12(path, "utf-8"));
|
|
4080
|
-
} catch {
|
|
4081
|
-
return null;
|
|
4082
|
-
}
|
|
4083
|
-
}
|
|
4084
|
-
function writeJsonFile(path, value) {
|
|
4085
|
-
writeFileSync10(path, `${JSON.stringify(value, null, 2)}
|
|
4086
|
-
`, "utf-8");
|
|
4087
|
-
}
|
|
4088
|
-
function readStyleBridge(projectRoot) {
|
|
4089
|
-
return readJsonFile2(styleBridgePath(projectRoot));
|
|
4090
|
-
}
|
|
4091
|
-
function readStyleBridgeProposal(projectRoot) {
|
|
4092
|
-
return readJsonFile2(styleBridgeProposalPath(projectRoot));
|
|
4093
|
-
}
|
|
4094
|
-
function stringArray(value) {
|
|
4095
|
-
return Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
|
|
4096
|
-
}
|
|
4097
|
-
function readThemeInventory(projectRoot) {
|
|
4098
|
-
const inventory = readJsonFile2(join16(projectRoot, ".decantr", "theme-inventory.json"));
|
|
4099
|
-
return {
|
|
4100
|
-
modes: stringArray(inventory?.modes),
|
|
4101
|
-
variantIds: Array.isArray(inventory?.variants) ? inventory.variants.map((variant) => variant.id).filter((id) => typeof id === "string") : [],
|
|
4102
|
-
darkModeDetected: typeof inventory?.darkModeDetected === "boolean" ? inventory.darkModeDetected : null
|
|
4103
|
-
};
|
|
4104
|
-
}
|
|
4105
|
-
function tokenHints(styling, terms) {
|
|
4106
|
-
return styling.cssVariables.filter((name) => terms.test(name)).slice(0, 12);
|
|
4107
|
-
}
|
|
4108
|
-
function readProjectPatternPack(projectRoot) {
|
|
4109
|
-
return readLocalPatternPack(projectRoot) ?? readJsonFile2(
|
|
4110
|
-
join16(projectRoot, ".decantr", "local-patterns.proposal.json")
|
|
4111
|
-
);
|
|
4112
|
-
}
|
|
4113
|
-
function classHintsForPattern(projectRoot, ids) {
|
|
4114
|
-
const pack = readProjectPatternPack(projectRoot);
|
|
4115
|
-
const classes = /* @__PURE__ */ new Set();
|
|
4116
|
-
for (const pattern of pack?.patterns ?? []) {
|
|
4117
|
-
if (!ids.includes(String(pattern.id))) continue;
|
|
4118
|
-
for (const hint of pattern.classHints ?? []) classes.add(hint);
|
|
4119
|
-
}
|
|
4120
|
-
return [...classes].slice(0, 12);
|
|
4121
|
-
}
|
|
4122
|
-
function sourceEvidence(projectRoot, ids) {
|
|
4123
|
-
const pack = readProjectPatternPack(projectRoot);
|
|
4124
|
-
const evidence = /* @__PURE__ */ new Set();
|
|
4125
|
-
for (const pattern of pack?.patterns ?? []) {
|
|
4126
|
-
if (!ids.includes(String(pattern.id))) continue;
|
|
4127
|
-
for (const path of pattern.componentPaths ?? []) evidence.add(path);
|
|
4128
|
-
for (const item of pattern.evidence ?? []) evidence.add(item);
|
|
4129
|
-
}
|
|
4130
|
-
return [...evidence].slice(0, 12);
|
|
4131
|
-
}
|
|
4132
|
-
function colorTokenNames(styling) {
|
|
4133
|
-
const names = new Set(Object.keys(styling.colors ?? {}));
|
|
4134
|
-
for (const variable of styling.cssVariables) {
|
|
4135
|
-
if (/color|bg|background|surface|text|foreground|border|primary|secondary|accent|muted/i.test(variable)) {
|
|
4136
|
-
names.add(variable);
|
|
4137
|
-
}
|
|
4138
|
-
}
|
|
4139
|
-
return [...names].slice(0, 40);
|
|
4140
|
-
}
|
|
4141
|
-
function createStyleBridgeProposal(input) {
|
|
4142
|
-
const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4143
|
-
const theme = readThemeInventory(input.projectRoot);
|
|
4144
|
-
const routeCount = input.essence && typeof input.essence === "object" && "blueprint" in input.essence ? Object.keys(input.essence.blueprint?.routes ?? {}).length : 0;
|
|
4145
|
-
const target = input.essence && typeof input.essence === "object" && "meta" in input.essence ? String(input.essence.meta?.target ?? "") || null : null;
|
|
4146
|
-
const darkModeDetected = theme.darkModeDetected ?? input.styling.darkMode;
|
|
4147
|
-
const themeModes = theme.modes.length > 0 ? theme.modes : darkModeDetected ? ["base", "dark"] : ["base"];
|
|
4148
|
-
const themeVariantIds = theme.variantIds.length > 0 ? theme.variantIds : themeModes.filter((mode) => mode !== "base");
|
|
4149
|
-
return {
|
|
4150
|
-
version: 1,
|
|
4151
|
-
status: "proposal",
|
|
4152
|
-
generatedAt,
|
|
4153
|
-
source: "decantr codify --style-bridge",
|
|
4154
|
-
purpose: "Project-owned Hybrid style bridge. It maps Decantr design intent to the existing app styling system without installing Decantr CSS or taking over source.",
|
|
4155
|
-
adoption: {
|
|
4156
|
-
mode: "style-bridge",
|
|
4157
|
-
workflowMode: "brownfield-attach",
|
|
4158
|
-
sourceAuthority: "Existing production source stays authoritative.",
|
|
4159
|
-
styleAuthority: "Use these mappings to translate Decantr concepts into project-owned tokens, classes, and components.",
|
|
4160
|
-
notRuntimeTakeover: true,
|
|
4161
|
-
authorityPrecedence: [
|
|
4162
|
-
"existing production source",
|
|
4163
|
-
"accepted style bridge",
|
|
4164
|
-
"accepted local patterns and rules",
|
|
4165
|
-
"Essence V4 contract",
|
|
4166
|
-
"hosted registry patterns and execution packs as optional guidance"
|
|
4167
|
-
]
|
|
4168
|
-
},
|
|
4169
|
-
project: {
|
|
4170
|
-
framework: input.detected.framework,
|
|
4171
|
-
packageManager: input.detected.packageManager,
|
|
4172
|
-
target,
|
|
4173
|
-
routeCount
|
|
4174
|
-
},
|
|
4175
|
-
styling: {
|
|
4176
|
-
approach: input.styling.approach,
|
|
4177
|
-
configFile: input.styling.configFile ?? null,
|
|
4178
|
-
darkModeDetected,
|
|
4179
|
-
cssVariables: input.styling.cssVariables.slice(0, 80),
|
|
4180
|
-
colorTokenNames: colorTokenNames(input.styling),
|
|
4181
|
-
themeModes,
|
|
4182
|
-
themeVariantIds
|
|
4183
|
-
},
|
|
4184
|
-
mappings: [
|
|
4185
|
-
{
|
|
4186
|
-
id: "surface",
|
|
4187
|
-
label: "Surfaces and cards",
|
|
4188
|
-
decantrIntent: "surface background, card treatment, border, radius, depth, and hover state",
|
|
4189
|
-
projectAuthority: "Use the app card/surface primitives, tokens, and accepted local law.",
|
|
4190
|
-
tokenHints: tokenHints(input.styling, /surface|card|panel|bg|background|border|shadow|radius/i),
|
|
4191
|
-
classHints: classHintsForPattern(input.projectRoot, ["surface-card"]),
|
|
4192
|
-
sourceEvidence: sourceEvidence(input.projectRoot, ["surface-card"]),
|
|
4193
|
-
guardrails: [
|
|
4194
|
-
"Do not invent a new card color/radius/shadow recipe without updating local law.",
|
|
4195
|
-
"Do not add Decantr CSS d-* classes unless adoption mode changes to decantr-css."
|
|
4196
|
-
]
|
|
4197
|
-
},
|
|
4198
|
-
{
|
|
4199
|
-
id: "action",
|
|
4200
|
-
label: "Actions and buttons",
|
|
4201
|
-
decantrIntent: "primary, secondary, tertiary, destructive, icon-only, loading, and disabled action states",
|
|
4202
|
-
projectAuthority: "Use the app button/action primitives and local variant names.",
|
|
4203
|
-
tokenHints: tokenHints(input.styling, /primary|secondary|accent|danger|error|destructive|focus/i),
|
|
4204
|
-
classHints: classHintsForPattern(input.projectRoot, ["button"]),
|
|
4205
|
-
sourceEvidence: sourceEvidence(input.projectRoot, ["button"]),
|
|
4206
|
-
guardrails: [
|
|
4207
|
-
"Map any new action style to primary/secondary/tertiary/destructive/icon before coding.",
|
|
4208
|
-
"Avoid raw button markup when a project-owned primitive exists."
|
|
4209
|
-
]
|
|
4210
|
-
},
|
|
4211
|
-
{
|
|
4212
|
-
id: "focus-accessibility",
|
|
4213
|
-
label: "Focus and accessibility",
|
|
4214
|
-
decantrIntent: "visible focus, keyboard clarity, contrast, and reduced-motion safety",
|
|
4215
|
-
projectAuthority: "Use the app accessibility classes, focus tokens, and framework conventions.",
|
|
4216
|
-
tokenHints: tokenHints(input.styling, /focus|ring|outline|contrast|motion|duration/i),
|
|
4217
|
-
classHints: [],
|
|
4218
|
-
sourceEvidence: [],
|
|
4219
|
-
guardrails: [
|
|
4220
|
-
"Every new interactive control needs visible focus evidence.",
|
|
4221
|
-
"Motion should honor prefers-reduced-motion or an existing app motion helper."
|
|
4222
|
-
]
|
|
4223
|
-
},
|
|
4224
|
-
{
|
|
4225
|
-
id: "layout-density",
|
|
4226
|
-
label: "Layout density and spacing",
|
|
4227
|
-
decantrIntent: "route gutters, section gaps, component padding, density, and responsive rhythm",
|
|
4228
|
-
projectAuthority: "Use existing layout wrappers, shell primitives, and spacing tokens/classes.",
|
|
4229
|
-
tokenHints: tokenHints(input.styling, /space|spacing|gap|gutter|container|radius/i),
|
|
4230
|
-
classHints: classHintsForPattern(input.projectRoot, ["page-shell"]),
|
|
4231
|
-
sourceEvidence: sourceEvidence(input.projectRoot, ["page-shell"]),
|
|
4232
|
-
guardrails: [
|
|
4233
|
-
"Do not let each page invent independent max-width, gutters, or scroll ownership.",
|
|
4234
|
-
"Use the detected shell/layout authority before adding a new wrapper."
|
|
4235
|
-
]
|
|
4236
|
-
},
|
|
4237
|
-
{
|
|
4238
|
-
id: "theme-variant",
|
|
4239
|
-
label: "Theme variants",
|
|
4240
|
-
decantrIntent: "light, dark, brand, tenant, seasonal, and density variants",
|
|
4241
|
-
projectAuthority: "Use the app theme provider, data attributes, CSS variables, or Tailwind mode strategy.",
|
|
4242
|
-
tokenHints: tokenHints(input.styling, /theme|dark|light|brand|tenant|mode|color/i),
|
|
4243
|
-
classHints: classHintsForPattern(input.projectRoot, ["theme-variant"]),
|
|
4244
|
-
sourceEvidence: sourceEvidence(input.projectRoot, ["theme-variant"]),
|
|
4245
|
-
guardrails: [
|
|
4246
|
-
"Keep theme modes documented in .decantr/theme-inventory.json and this bridge.",
|
|
4247
|
-
"Do not treat theme switcher container classes as standalone theme variants."
|
|
4248
|
-
]
|
|
4249
|
-
}
|
|
4250
|
-
],
|
|
4251
|
-
rules: [
|
|
4252
|
-
"The bridge is advisory until accepted; after acceptance, task/doctor/CI should surface it as Hybrid authority.",
|
|
4253
|
-
"The bridge does not make hosted registry patterns enforceable. Map hosted concepts into local law first.",
|
|
4254
|
-
"Keep deterministic blocking checks in .decantr/rules.json, ESLint, Biome, tests, or visual regression."
|
|
4255
|
-
],
|
|
4256
|
-
nextSteps: [
|
|
4257
|
-
"Review token and class hints. Replace generic hints with the exact project tokens/classes the team owns.",
|
|
4258
|
-
"Run decantr codify --accept to promote the proposal to .decantr/style-bridge.json.",
|
|
4259
|
-
"Use decantr task <route> before LLM edits so the style bridge appears in task context.",
|
|
4260
|
-
"Run decantr ci or decantr verify after edits to keep local law and Project Health visible."
|
|
4261
|
-
]
|
|
4262
|
-
};
|
|
4263
|
-
}
|
|
4264
|
-
function writeStyleBridgeProposal(projectRoot, proposal) {
|
|
4265
|
-
const decantrDir = join16(projectRoot, ".decantr");
|
|
4266
|
-
mkdirSync9(decantrDir, { recursive: true });
|
|
4267
|
-
const path = styleBridgeProposalPath(projectRoot);
|
|
4268
|
-
writeJsonFile(path, proposal);
|
|
4269
|
-
return path;
|
|
4270
|
-
}
|
|
4271
|
-
function acceptStyleBridge(projectRoot) {
|
|
4272
|
-
const proposal = readStyleBridgeProposal(projectRoot);
|
|
4273
|
-
if (!proposal) return null;
|
|
4274
|
-
const accepted = {
|
|
4275
|
-
...proposal,
|
|
4276
|
-
status: "accepted",
|
|
4277
|
-
acceptedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4278
|
-
};
|
|
4279
|
-
const path = styleBridgePath(projectRoot);
|
|
4280
|
-
writeJsonFile(path, accepted);
|
|
4281
|
-
const projectJsonPath = join16(projectRoot, ".decantr", "project.json");
|
|
4282
|
-
const projectJson = readJsonFile2(projectJsonPath) ?? {};
|
|
4283
|
-
const initialized = typeof projectJson.initialized === "object" && projectJson.initialized !== null ? projectJson.initialized : {};
|
|
4284
|
-
writeJsonFile(projectJsonPath, {
|
|
4285
|
-
...projectJson,
|
|
4286
|
-
initialized: {
|
|
4287
|
-
...initialized,
|
|
4288
|
-
workflowMode: initialized.workflowMode ?? "brownfield-attach",
|
|
4289
|
-
adoptionMode: "style-bridge"
|
|
4290
|
-
}
|
|
4291
|
-
});
|
|
4292
|
-
return path;
|
|
4293
|
-
}
|
|
4294
|
-
function createStyleBridgeTaskSummary(projectRoot) {
|
|
4295
|
-
const bridge = readStyleBridge(projectRoot);
|
|
4296
|
-
return {
|
|
4297
|
-
path: bridge ? ".decantr/style-bridge.json" : null,
|
|
4298
|
-
status: bridge?.status ?? null,
|
|
4299
|
-
mappingCount: bridge?.mappings?.length ?? 0,
|
|
4300
|
-
stylingApproach: bridge?.styling?.approach ?? null,
|
|
4301
|
-
themeModes: bridge?.styling?.themeModes ?? [],
|
|
4302
|
-
mappings: bridge?.mappings?.map((mapping) => ({
|
|
4303
|
-
id: mapping.id,
|
|
4304
|
-
label: mapping.label,
|
|
4305
|
-
tokenHints: mapping.tokenHints.slice(0, 6),
|
|
4306
|
-
classHints: mapping.classHints.slice(0, 4),
|
|
4307
|
-
guardrails: mapping.guardrails.slice(0, 3)
|
|
4308
|
-
})) ?? []
|
|
4309
|
-
};
|
|
4310
|
-
}
|
|
4311
|
-
function styleBridgeMatches(projectRoot, query) {
|
|
4312
|
-
if (!projectRoot) return [];
|
|
4313
|
-
const bridge = readStyleBridge(projectRoot);
|
|
4314
|
-
if (!bridge) return [];
|
|
4315
|
-
const terms = query.toLowerCase().split(/[^a-z0-9]+/).filter((term) => term.length > 1).flatMap((term) => term.endsWith("s") && term.length > 3 ? [term, term.slice(0, -1)] : [term]);
|
|
4316
|
-
if (terms.length === 0) return [];
|
|
4317
|
-
return bridge.mappings.map((mapping) => {
|
|
4318
|
-
const haystack = [
|
|
4319
|
-
mapping.id,
|
|
4320
|
-
mapping.label,
|
|
4321
|
-
mapping.decantrIntent,
|
|
4322
|
-
mapping.projectAuthority,
|
|
4323
|
-
...mapping.tokenHints,
|
|
4324
|
-
...mapping.classHints,
|
|
4325
|
-
...mapping.guardrails
|
|
4326
|
-
].join(" ").toLowerCase();
|
|
4327
|
-
const score = terms.reduce((sum, term) => sum + (haystack.includes(term) ? 1 : 0), 0);
|
|
4328
|
-
return {
|
|
4329
|
-
id: mapping.id,
|
|
4330
|
-
label: mapping.label,
|
|
4331
|
-
score,
|
|
4332
|
-
tokenHints: mapping.tokenHints.slice(0, 4),
|
|
4333
|
-
classHints: mapping.classHints.slice(0, 3)
|
|
4334
|
-
};
|
|
4335
|
-
}).filter((match) => match.score > 0).sort((a, b) => b.score - a.score || a.id.localeCompare(b.id)).slice(0, 5);
|
|
4336
|
-
}
|
|
4337
|
-
|
|
4338
|
-
// src/commands/ci.ts
|
|
3267
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync8, readFileSync as readFileSync11, writeFileSync as writeFileSync9 } from "fs";
|
|
3268
|
+
import { dirname as dirname3, join as join15, relative as relative3, resolve } from "path";
|
|
4339
3269
|
var BOLD2 = "\x1B[1m";
|
|
4340
3270
|
var DIM3 = "\x1B[2m";
|
|
4341
3271
|
var GREEN3 = "\x1B[32m";
|
|
@@ -4343,9 +3273,9 @@ var RED2 = "\x1B[31m";
|
|
|
4343
3273
|
var RESET3 = "\x1B[0m";
|
|
4344
3274
|
var CI_SCHEMA = "https://decantr.ai/schemas/decantr-ci-report.v1.json";
|
|
4345
3275
|
function readJson(path) {
|
|
4346
|
-
if (!
|
|
3276
|
+
if (!existsSync13(path)) return null;
|
|
4347
3277
|
try {
|
|
4348
|
-
return JSON.parse(
|
|
3278
|
+
return JSON.parse(readFileSync11(path, "utf-8"));
|
|
4349
3279
|
} catch {
|
|
4350
3280
|
return null;
|
|
4351
3281
|
}
|
|
@@ -4389,21 +3319,21 @@ function parseProvider(value) {
|
|
|
4389
3319
|
throw new Error("Invalid --provider value. Use github or generic.");
|
|
4390
3320
|
}
|
|
4391
3321
|
function detectPackageManager2(root) {
|
|
4392
|
-
const pkg = readJson(
|
|
3322
|
+
const pkg = readJson(join15(root, "package.json"));
|
|
4393
3323
|
const declared = pkg?.packageManager?.split("@")[0];
|
|
4394
3324
|
if (declared === "pnpm" || declared === "npm" || declared === "yarn" || declared === "bun") {
|
|
4395
3325
|
return declared;
|
|
4396
3326
|
}
|
|
4397
|
-
if (
|
|
4398
|
-
if (
|
|
4399
|
-
if (
|
|
4400
|
-
if (
|
|
3327
|
+
if (existsSync13(join15(root, "pnpm-lock.yaml"))) return "pnpm";
|
|
3328
|
+
if (existsSync13(join15(root, "package-lock.json"))) return "npm";
|
|
3329
|
+
if (existsSync13(join15(root, "yarn.lock"))) return "yarn";
|
|
3330
|
+
if (existsSync13(join15(root, "bun.lock")) || existsSync13(join15(root, "bun.lockb"))) return "bun";
|
|
4401
3331
|
return "unknown";
|
|
4402
3332
|
}
|
|
4403
3333
|
function hasWorkspaceMarker(root) {
|
|
4404
|
-
const pkg = readJson(
|
|
3334
|
+
const pkg = readJson(join15(root, "package.json"));
|
|
4405
3335
|
return Boolean(
|
|
4406
|
-
|
|
3336
|
+
existsSync13(join15(root, "pnpm-workspace.yaml")) || existsSync13(join15(root, "turbo.json")) || existsSync13(join15(root, "nx.json")) || pkg?.workspaces
|
|
4407
3337
|
);
|
|
4408
3338
|
}
|
|
4409
3339
|
function installCommand(packageManager) {
|
|
@@ -4449,7 +3379,7 @@ function decantrCommand(packageManager) {
|
|
|
4449
3379
|
}
|
|
4450
3380
|
}
|
|
4451
3381
|
function localCliPinned(root) {
|
|
4452
|
-
const pkg = readJson(
|
|
3382
|
+
const pkg = readJson(join15(root, "package.json"));
|
|
4453
3383
|
return Boolean(pkg?.devDependencies?.["@decantr/cli"] || pkg?.dependencies?.["@decantr/cli"]);
|
|
4454
3384
|
}
|
|
4455
3385
|
function projectSlug(projectPath) {
|
|
@@ -4457,8 +3387,8 @@ function projectSlug(projectPath) {
|
|
|
4457
3387
|
}
|
|
4458
3388
|
function writeOutput(root, path, content) {
|
|
4459
3389
|
const absolute = resolve(root, path);
|
|
4460
|
-
|
|
4461
|
-
|
|
3390
|
+
mkdirSync8(dirname3(absolute), { recursive: true });
|
|
3391
|
+
writeFileSync9(absolute, content, "utf-8");
|
|
4462
3392
|
}
|
|
4463
3393
|
function summarizeLocalLaw(projectRoot) {
|
|
4464
3394
|
const validation = validateLocalLaw(projectRoot);
|
|
@@ -4733,7 +3663,7 @@ jobs:
|
|
|
4733
3663
|
}
|
|
4734
3664
|
function writeCiInit(root, options) {
|
|
4735
3665
|
const workspaceInfo = resolveWorkspaceInfo(root, options.project);
|
|
4736
|
-
if (options.project && !
|
|
3666
|
+
if (options.project && !existsSync13(workspaceInfo.appRoot)) {
|
|
4737
3667
|
throw new Error(`Project path does not exist: ${options.project}`);
|
|
4738
3668
|
}
|
|
4739
3669
|
if (workspaceInfo.requiresProjectSelection && !options.workspace) {
|
|
@@ -4743,7 +3673,7 @@ function writeCiInit(root, options) {
|
|
|
4743
3673
|
);
|
|
4744
3674
|
}
|
|
4745
3675
|
const outputRoot = workspaceInfo.workspaceRoot;
|
|
4746
|
-
const projectPath = options.project ?? (options.workspace || workspaceInfo.appRoot === workspaceInfo.workspaceRoot ? void 0 :
|
|
3676
|
+
const projectPath = options.project ?? (options.workspace || workspaceInfo.appRoot === workspaceInfo.workspaceRoot ? void 0 : relative3(workspaceInfo.workspaceRoot, workspaceInfo.appRoot).replace(/\\/g, "/"));
|
|
4747
3677
|
const packageManager = detectPackageManager2(outputRoot);
|
|
4748
3678
|
const command = decantrCommand(packageManager);
|
|
4749
3679
|
const failOn = options.failOn ?? "error";
|
|
@@ -4756,7 +3686,7 @@ function writeCiInit(root, options) {
|
|
|
4756
3686
|
if (provider === "generic") {
|
|
4757
3687
|
const path2 = ".decantr/ci/decantr-ci.sh";
|
|
4758
3688
|
const absolute2 = resolve(outputRoot, path2);
|
|
4759
|
-
if (
|
|
3689
|
+
if (existsSync13(absolute2) && !options.force) {
|
|
4760
3690
|
throw new Error(`${path2} already exists. Re-run with --force to replace it.`);
|
|
4761
3691
|
}
|
|
4762
3692
|
writeOutput(
|
|
@@ -4777,7 +3707,7 @@ function writeCiInit(root, options) {
|
|
|
4777
3707
|
}
|
|
4778
3708
|
const path = ".github/workflows/decantr-ci.yml";
|
|
4779
3709
|
const absolute = resolve(outputRoot, path);
|
|
4780
|
-
if (
|
|
3710
|
+
if (existsSync13(absolute) && !options.force) {
|
|
4781
3711
|
throw new Error(`${path} already exists. Re-run with --force to replace it.`);
|
|
4782
3712
|
}
|
|
4783
3713
|
writeOutput(
|
|
@@ -4826,7 +3756,7 @@ async function runWorkspaceCi(root, options) {
|
|
|
4826
3756
|
}
|
|
4827
3757
|
async function runProjectCi(root, options) {
|
|
4828
3758
|
const workspaceInfo = resolveWorkspaceInfo(root, options.project);
|
|
4829
|
-
if (options.project && !
|
|
3759
|
+
if (options.project && !existsSync13(workspaceInfo.appRoot)) {
|
|
4830
3760
|
console.error(`${RED2}Project path does not exist: ${options.project}${RESET3}`);
|
|
4831
3761
|
return 1;
|
|
4832
3762
|
}
|
|
@@ -4900,8 +3830,8 @@ async function cmdCi(args = ["ci"], root = process.cwd()) {
|
|
|
4900
3830
|
}
|
|
4901
3831
|
|
|
4902
3832
|
// src/commands/create.ts
|
|
4903
|
-
import { existsSync as
|
|
4904
|
-
import { join as
|
|
3833
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync9, writeFileSync as writeFileSync10 } from "fs";
|
|
3834
|
+
import { join as join16 } from "path";
|
|
4905
3835
|
import {
|
|
4906
3836
|
CONTENT_TYPE_TO_API_CONTENT_TYPE,
|
|
4907
3837
|
CONTENT_TYPES
|
|
@@ -5097,25 +4027,25 @@ function cmdCreate(type, name, projectRoot = process.cwd()) {
|
|
|
5097
4027
|
}
|
|
5098
4028
|
const contentType = type;
|
|
5099
4029
|
const plural = PLURAL[contentType];
|
|
5100
|
-
const customDir =
|
|
5101
|
-
const filePath =
|
|
5102
|
-
if (
|
|
4030
|
+
const customDir = join16(projectRoot, ".decantr", "custom", plural);
|
|
4031
|
+
const filePath = join16(customDir, `${name}.json`);
|
|
4032
|
+
if (existsSync14(filePath)) {
|
|
5103
4033
|
console.error(`${type} "${name}" already exists at ${filePath}`);
|
|
5104
4034
|
process.exitCode = 1;
|
|
5105
4035
|
return;
|
|
5106
4036
|
}
|
|
5107
|
-
|
|
4037
|
+
mkdirSync9(customDir, { recursive: true });
|
|
5108
4038
|
const skeleton = getSkeleton(contentType, name, humanizeId(name));
|
|
5109
|
-
|
|
4039
|
+
writeFileSync10(filePath, JSON.stringify(skeleton, null, 2));
|
|
5110
4040
|
console.log(`Created ${type} "${name}" at ${filePath}`);
|
|
5111
4041
|
console.log(`Edit it, then publish with: decantr publish ${type} ${name}`);
|
|
5112
4042
|
}
|
|
5113
4043
|
|
|
5114
4044
|
// src/commands/doctor.ts
|
|
5115
|
-
import { existsSync as
|
|
5116
|
-
import { dirname as dirname4, join as
|
|
4045
|
+
import { existsSync as existsSync15, readdirSync as readdirSync5, readFileSync as readFileSync12 } from "fs";
|
|
4046
|
+
import { dirname as dirname4, join as join17, relative as relative4 } from "path";
|
|
5117
4047
|
import { fileURLToPath } from "url";
|
|
5118
|
-
import { isV4 as
|
|
4048
|
+
import { isV4 as isV43 } from "@decantr/essence-spec";
|
|
5119
4049
|
import { collectMissingPackManifestFiles } from "@decantr/verifier";
|
|
5120
4050
|
var BOLD3 = "\x1B[1m";
|
|
5121
4051
|
var DIM4 = "\x1B[2m";
|
|
@@ -5125,25 +4055,25 @@ var YELLOW3 = "\x1B[33m";
|
|
|
5125
4055
|
var CYAN2 = "\x1B[36m";
|
|
5126
4056
|
var RESET4 = "\x1B[0m";
|
|
5127
4057
|
function readJson2(path) {
|
|
5128
|
-
if (!
|
|
4058
|
+
if (!existsSync15(path)) return null;
|
|
5129
4059
|
try {
|
|
5130
|
-
return JSON.parse(
|
|
4060
|
+
return JSON.parse(readFileSync12(path, "utf-8"));
|
|
5131
4061
|
} catch {
|
|
5132
4062
|
return null;
|
|
5133
4063
|
}
|
|
5134
4064
|
}
|
|
5135
4065
|
function isContractOnlyProject(projectRoot) {
|
|
5136
|
-
const projectJson = readJson2(
|
|
4066
|
+
const projectJson = readJson2(join17(projectRoot, ".decantr", "project.json"));
|
|
5137
4067
|
return projectJson?.initialized?.adoptionMode === "contract-only" || projectJson?.initialized?.adoptionMode === "style-bridge";
|
|
5138
4068
|
}
|
|
5139
4069
|
function readCliPackageVersion() {
|
|
5140
|
-
const packagePath =
|
|
5141
|
-
const srcPackagePath =
|
|
4070
|
+
const packagePath = join17(dirname4(fileURLToPath(import.meta.url)), "..", "..", "package.json");
|
|
4071
|
+
const srcPackagePath = join17(dirname4(fileURLToPath(import.meta.url)), "..", "package.json");
|
|
5142
4072
|
const pkg = readJson2(packagePath) ?? readJson2(srcPackagePath);
|
|
5143
4073
|
return pkg?.version ?? "unknown";
|
|
5144
4074
|
}
|
|
5145
4075
|
function readPackageJson2(dir) {
|
|
5146
|
-
return readJson2(
|
|
4076
|
+
return readJson2(join17(dir, "package.json"));
|
|
5147
4077
|
}
|
|
5148
4078
|
function detectPackageManager3(root) {
|
|
5149
4079
|
const pkg = readPackageJson2(root);
|
|
@@ -5151,10 +4081,10 @@ function detectPackageManager3(root) {
|
|
|
5151
4081
|
if (declared === "pnpm" || declared === "npm" || declared === "yarn" || declared === "bun") {
|
|
5152
4082
|
return declared;
|
|
5153
4083
|
}
|
|
5154
|
-
if (
|
|
5155
|
-
if (
|
|
5156
|
-
if (
|
|
5157
|
-
if (
|
|
4084
|
+
if (existsSync15(join17(root, "pnpm-lock.yaml"))) return "pnpm";
|
|
4085
|
+
if (existsSync15(join17(root, "package-lock.json"))) return "npm";
|
|
4086
|
+
if (existsSync15(join17(root, "yarn.lock"))) return "yarn";
|
|
4087
|
+
if (existsSync15(join17(root, "bun.lock")) || existsSync15(join17(root, "bun.lockb"))) return "bun";
|
|
5158
4088
|
return "unknown";
|
|
5159
4089
|
}
|
|
5160
4090
|
function localCliDependency(root) {
|
|
@@ -5164,7 +4094,7 @@ function localCliDependency(root) {
|
|
|
5164
4094
|
function hasWorkspaceMarker2(root) {
|
|
5165
4095
|
const pkg = readPackageJson2(root);
|
|
5166
4096
|
return Boolean(
|
|
5167
|
-
|
|
4097
|
+
existsSync15(join17(root, "pnpm-workspace.yaml")) || existsSync15(join17(root, "turbo.json")) || existsSync15(join17(root, "nx.json")) || pkg?.workspaces
|
|
5168
4098
|
);
|
|
5169
4099
|
}
|
|
5170
4100
|
function pinCliCommand2(packageManager, root) {
|
|
@@ -5182,13 +4112,13 @@ function pinCliCommand2(packageManager, root) {
|
|
|
5182
4112
|
}
|
|
5183
4113
|
}
|
|
5184
4114
|
function rel(root, path) {
|
|
5185
|
-
const value =
|
|
4115
|
+
const value = relative4(root, path).replace(/\\/g, "/");
|
|
5186
4116
|
return value || ".";
|
|
5187
4117
|
}
|
|
5188
4118
|
function hasAnyFile(dir, names) {
|
|
5189
4119
|
for (const name of names) {
|
|
5190
|
-
const path =
|
|
5191
|
-
if (
|
|
4120
|
+
const path = join17(dir, name);
|
|
4121
|
+
if (existsSync15(path)) return name;
|
|
5192
4122
|
}
|
|
5193
4123
|
return null;
|
|
5194
4124
|
}
|
|
@@ -5210,7 +4140,7 @@ function detectDesignAuthority(workspaceRoot, appRoot) {
|
|
|
5210
4140
|
"libs/design-system"
|
|
5211
4141
|
];
|
|
5212
4142
|
for (const candidate of sharedCandidates) {
|
|
5213
|
-
if (
|
|
4143
|
+
if (existsSync15(join17(workspaceRoot, candidate))) found.add(candidate);
|
|
5214
4144
|
}
|
|
5215
4145
|
for (const root of [appRoot, workspaceRoot]) {
|
|
5216
4146
|
const label = root === appRoot ? "app" : "workspace";
|
|
@@ -5221,9 +4151,9 @@ function detectDesignAuthority(workspaceRoot, appRoot) {
|
|
|
5221
4151
|
"tailwind.config.cjs"
|
|
5222
4152
|
]);
|
|
5223
4153
|
if (tailwind) found.add(`${label}:${tailwind}`);
|
|
5224
|
-
if (
|
|
5225
|
-
if (
|
|
5226
|
-
if (
|
|
4154
|
+
if (existsSync15(join17(root, ".storybook"))) found.add(`${label}:.storybook`);
|
|
4155
|
+
if (existsSync15(join17(root, "components", "ui"))) found.add(`${label}:components/ui`);
|
|
4156
|
+
if (existsSync15(join17(root, "src", "components", "ui"))) found.add(`${label}:src/components/ui`);
|
|
5227
4157
|
if (packageHasDependency(root, ["@storybook/react", "storybook"]))
|
|
5228
4158
|
found.add(`${label}:storybook`);
|
|
5229
4159
|
if (packageHasDependency(root, ["tailwindcss"])) found.add(`${label}:tailwindcss`);
|
|
@@ -5235,18 +4165,18 @@ function detectDesignAuthority(workspaceRoot, appRoot) {
|
|
|
5235
4165
|
}
|
|
5236
4166
|
function findCiFiles(root) {
|
|
5237
4167
|
const files = [];
|
|
5238
|
-
const workflows =
|
|
5239
|
-
if (
|
|
5240
|
-
for (const entry of
|
|
4168
|
+
const workflows = join17(root, ".github", "workflows");
|
|
4169
|
+
if (existsSync15(workflows)) {
|
|
4170
|
+
for (const entry of readdirSync5(workflows, { withFileTypes: true })) {
|
|
5241
4171
|
if (!entry.isFile()) continue;
|
|
5242
|
-
const path =
|
|
5243
|
-
const text =
|
|
4172
|
+
const path = join17(workflows, entry.name);
|
|
4173
|
+
const text = readFileSync12(path, "utf-8");
|
|
5244
4174
|
if (text.includes("decantr")) files.push(rel(root, path));
|
|
5245
4175
|
}
|
|
5246
4176
|
}
|
|
5247
4177
|
for (const candidate of ["Jenkinsfile", ".gitlab-ci.yml", "azure-pipelines.yml", "BUILD"]) {
|
|
5248
|
-
const path =
|
|
5249
|
-
if (
|
|
4178
|
+
const path = join17(root, candidate);
|
|
4179
|
+
if (existsSync15(path) && readFileSync12(path, "utf-8").includes("decantr")) {
|
|
5250
4180
|
files.push(candidate);
|
|
5251
4181
|
}
|
|
5252
4182
|
}
|
|
@@ -5372,6 +4302,28 @@ function deriveAdoptionLane(input) {
|
|
|
5372
4302
|
nextChoice: "Use task and verify to keep generated routes aligned with the compiled contract."
|
|
5373
4303
|
};
|
|
5374
4304
|
}
|
|
4305
|
+
function inspectGraphArtifacts(appRoot, workspaceRoot) {
|
|
4306
|
+
const snapshotPath = join17(appRoot, ".decantr", "graph", "graph.snapshot.json");
|
|
4307
|
+
const capsulePath = join17(appRoot, ".decantr", "graph", "contract-capsule.json");
|
|
4308
|
+
try {
|
|
4309
|
+
const artifacts = buildGraphArtifacts(appRoot);
|
|
4310
|
+
return {
|
|
4311
|
+
snapshotPresent: existsSync15(snapshotPath),
|
|
4312
|
+
capsulePresent: existsSync15(capsulePath),
|
|
4313
|
+
artifactsCurrent: artifacts ? artifacts.staleArtifacts.length === 0 : null,
|
|
4314
|
+
staleArtifacts: artifacts ? artifacts.staleArtifacts.map((path) => rel(workspaceRoot, path)) : [],
|
|
4315
|
+
error: null
|
|
4316
|
+
};
|
|
4317
|
+
} catch (error3) {
|
|
4318
|
+
return {
|
|
4319
|
+
snapshotPresent: existsSync15(snapshotPath),
|
|
4320
|
+
capsulePresent: existsSync15(capsulePath),
|
|
4321
|
+
artifactsCurrent: false,
|
|
4322
|
+
staleArtifacts: [],
|
|
4323
|
+
error: error3.message
|
|
4324
|
+
};
|
|
4325
|
+
}
|
|
4326
|
+
}
|
|
5375
4327
|
function buildDoctorReport(root, args) {
|
|
5376
4328
|
let projectArg;
|
|
5377
4329
|
for (let index = 1; index < args.length; index += 1) {
|
|
@@ -5383,18 +4335,18 @@ function buildDoctorReport(root, args) {
|
|
|
5383
4335
|
const workspaceInfo = resolveWorkspaceInfo(root, projectArg);
|
|
5384
4336
|
const workspaceRoot = workspaceInfo.workspaceRoot;
|
|
5385
4337
|
const appRoot = workspaceMode ? workspaceRoot : workspaceInfo.appRoot;
|
|
5386
|
-
const projectMissing = Boolean(projectArg && !
|
|
4338
|
+
const projectMissing = Boolean(projectArg && !existsSync15(appRoot));
|
|
5387
4339
|
const projectPath = appRoot === workspaceRoot ? null : rel(workspaceRoot, appRoot);
|
|
5388
4340
|
const packageManager = detectPackageManager3(workspaceRoot);
|
|
5389
4341
|
const cliDependency = localCliDependency(workspaceRoot);
|
|
5390
4342
|
const detected = detectProject(appRoot);
|
|
5391
|
-
const projectJson = readJson2(
|
|
5392
|
-
const essence = readJson2(
|
|
4343
|
+
const projectJson = readJson2(join17(appRoot, ".decantr", "project.json"));
|
|
4344
|
+
const essence = readJson2(join17(appRoot, "decantr.essence.json"));
|
|
5393
4345
|
const essenceVersion = essence && typeof essence === "object" && "version" in essence ? String(essence.version) : null;
|
|
5394
|
-
const contextDir =
|
|
5395
|
-
const packManifestPresent =
|
|
5396
|
-
const reviewPackPresent =
|
|
5397
|
-
const artifactReadmePresent =
|
|
4346
|
+
const contextDir = join17(appRoot, ".decantr", "context");
|
|
4347
|
+
const packManifestPresent = existsSync15(join17(contextDir, "pack-manifest.json"));
|
|
4348
|
+
const reviewPackPresent = existsSync15(join17(contextDir, "review-pack.json"));
|
|
4349
|
+
const artifactReadmePresent = existsSync15(join17(appRoot, ARTIFACT_README_PATH));
|
|
5398
4350
|
const projects = listWorkspaceProjects(workspaceRoot);
|
|
5399
4351
|
const candidates = listWorkspaceCandidates(workspaceRoot, projects).map((candidate) => ({
|
|
5400
4352
|
path: candidate.path,
|
|
@@ -5405,22 +4357,23 @@ function buildDoctorReport(root, args) {
|
|
|
5405
4357
|
const workflowMode = projectJson?.initialized?.workflowMode ?? null;
|
|
5406
4358
|
const adoptionMode = projectJson?.initialized?.adoptionMode ?? null;
|
|
5407
4359
|
const packHydrationOptional = adoptionMode === "contract-only" || adoptionMode === "style-bridge";
|
|
5408
|
-
const localPatternsPresent =
|
|
5409
|
-
const localRulesPresent =
|
|
5410
|
-
const styleBridgePresent =
|
|
5411
|
-
const styleBridgeProposalPresent =
|
|
4360
|
+
const localPatternsPresent = existsSync15(localPatternsPath(appRoot));
|
|
4361
|
+
const localRulesPresent = existsSync15(localRulesPath(appRoot));
|
|
4362
|
+
const styleBridgePresent = existsSync15(styleBridgePath(appRoot));
|
|
4363
|
+
const styleBridgeProposalPresent = existsSync15(styleBridgeProposalPath(appRoot));
|
|
4364
|
+
const graphArtifacts = inspectGraphArtifacts(appRoot, workspaceRoot);
|
|
5412
4365
|
const missingPackReferences = workspaceMode ? projects.flatMap(
|
|
5413
|
-
(project) => collectMissingPackManifestFiles(
|
|
4366
|
+
(project) => collectMissingPackManifestFiles(join17(workspaceRoot, project.path)).map(
|
|
5414
4367
|
(missing) => `${project.path}/${missing.relativePath}`
|
|
5415
4368
|
)
|
|
5416
4369
|
) : collectMissingPackManifestFiles(appRoot).map((missing) => missing.relativePath);
|
|
5417
4370
|
const workspaceProjectsMissingManifest = workspaceMode ? projects.map((project) => project.path).filter((projectPath2) => {
|
|
5418
|
-
const projectContextDir =
|
|
5419
|
-
return !isContractOnlyProject(
|
|
4371
|
+
const projectContextDir = join17(workspaceRoot, projectPath2, ".decantr", "context");
|
|
4372
|
+
return !isContractOnlyProject(join17(workspaceRoot, projectPath2)) && !existsSync15(join17(projectContextDir, "pack-manifest.json"));
|
|
5420
4373
|
}) : [];
|
|
5421
4374
|
const workspaceProjectsMissingReviewPack = workspaceMode ? projects.map((project) => project.path).filter((projectPath2) => {
|
|
5422
|
-
const projectContextDir =
|
|
5423
|
-
return !isContractOnlyProject(
|
|
4375
|
+
const projectContextDir = join17(workspaceRoot, projectPath2, ".decantr", "context");
|
|
4376
|
+
return !isContractOnlyProject(join17(workspaceRoot, projectPath2)) && !existsSync15(join17(projectContextDir, "review-pack.json"));
|
|
5424
4377
|
}) : [];
|
|
5425
4378
|
const issues = [];
|
|
5426
4379
|
if (projectMissing) {
|
|
@@ -5437,7 +4390,7 @@ function buildDoctorReport(root, args) {
|
|
|
5437
4390
|
message: "No decantr.essence.json found for this app.",
|
|
5438
4391
|
nextCommand: projectPath ? `decantr adopt --project ${projectPath} --yes` : "decantr adopt --yes"
|
|
5439
4392
|
});
|
|
5440
|
-
} else if (essenceVersion && !
|
|
4393
|
+
} else if (essenceVersion && !isV43(essence)) {
|
|
5441
4394
|
issues.push({
|
|
5442
4395
|
category: "migration",
|
|
5443
4396
|
severity: "error",
|
|
@@ -5477,7 +4430,7 @@ function buildDoctorReport(root, args) {
|
|
|
5477
4430
|
nextCommand: "decantr refresh"
|
|
5478
4431
|
});
|
|
5479
4432
|
}
|
|
5480
|
-
if (essenceVersion === "4.0.0" && !packHydrationOptional && (!
|
|
4433
|
+
if (essenceVersion === "4.0.0" && !packHydrationOptional && (!existsSync15(contextDir) || !packManifestPresent)) {
|
|
5481
4434
|
issues.push({
|
|
5482
4435
|
category: "generated-artifact",
|
|
5483
4436
|
severity: "warn",
|
|
@@ -5493,6 +4446,14 @@ function buildDoctorReport(root, args) {
|
|
|
5493
4446
|
nextCommand: projectPath ? `decantr registry compile-packs ${projectPath}/decantr.essence.json --write-context` : "decantr registry compile-packs decantr.essence.json --write-context"
|
|
5494
4447
|
});
|
|
5495
4448
|
}
|
|
4449
|
+
if (essenceVersion === "4.0.0" && (Boolean(projectJson) || graphArtifacts.snapshotPresent || graphArtifacts.capsulePresent) && (graphArtifacts.error || graphArtifacts.artifactsCurrent === false)) {
|
|
4450
|
+
issues.push({
|
|
4451
|
+
category: "graph",
|
|
4452
|
+
severity: "warn",
|
|
4453
|
+
message: graphArtifacts.error ? `Typed Contract graph could not be derived: ${graphArtifacts.error}` : "Typed Contract graph artifacts are missing or stale.",
|
|
4454
|
+
nextCommand: projectPath ? `decantr graph --project ${projectPath}` : "decantr graph"
|
|
4455
|
+
});
|
|
4456
|
+
}
|
|
5496
4457
|
if (workspaceMode && (workspaceProjectsMissingManifest.length > 0 || workspaceProjectsMissingReviewPack.length > 0 || missingPackReferences.length > 0)) {
|
|
5497
4458
|
issues.push({
|
|
5498
4459
|
category: "generated-artifact",
|
|
@@ -5562,6 +4523,10 @@ function buildDoctorReport(root, args) {
|
|
|
5562
4523
|
recommendedNextCommands,
|
|
5563
4524
|
issues.find((issue) => issue.category === "ci" && issue.message.includes("not pinned"))?.nextCommand
|
|
5564
4525
|
);
|
|
4526
|
+
appendUnique(
|
|
4527
|
+
recommendedNextCommands,
|
|
4528
|
+
issues.find((issue) => issue.category === "graph")?.nextCommand
|
|
4529
|
+
);
|
|
5565
4530
|
if (workflowMode === "brownfield-attach" && !localPatternsPresent) {
|
|
5566
4531
|
appendUnique(recommendedNextCommands, `decantr codify --from-audit${projectFlag}`);
|
|
5567
4532
|
appendUnique(recommendedNextCommands, `decantr codify --accept${projectFlag}`);
|
|
@@ -5611,11 +4576,16 @@ function buildDoctorReport(root, args) {
|
|
|
5611
4576
|
},
|
|
5612
4577
|
generatedArtifacts: {
|
|
5613
4578
|
contextDirPresent: workspaceMode ? projects.some(
|
|
5614
|
-
(project) =>
|
|
5615
|
-
) :
|
|
4579
|
+
(project) => existsSync15(join17(workspaceRoot, project.path, ".decantr", "context"))
|
|
4580
|
+
) : existsSync15(contextDir),
|
|
5616
4581
|
packManifestPresent: workspaceMode ? projects.length > 0 && workspaceProjectsMissingManifest.length === 0 : packManifestPresent,
|
|
5617
4582
|
reviewPackPresent: workspaceMode ? projects.length > 0 && workspaceProjectsMissingReviewPack.length === 0 : reviewPackPresent,
|
|
5618
|
-
missingReferencedFiles: missingPackReferences.slice(0, 25)
|
|
4583
|
+
missingReferencedFiles: missingPackReferences.slice(0, 25),
|
|
4584
|
+
graphSnapshotPresent: graphArtifacts.snapshotPresent,
|
|
4585
|
+
graphCapsulePresent: graphArtifacts.capsulePresent,
|
|
4586
|
+
graphArtifactsCurrent: graphArtifacts.artifactsCurrent,
|
|
4587
|
+
graphStaleArtifacts: graphArtifacts.staleArtifacts.slice(0, 25),
|
|
4588
|
+
graphError: graphArtifacts.error
|
|
5619
4589
|
},
|
|
5620
4590
|
localLaw: {
|
|
5621
4591
|
patternsPresent: localPatternsPresent,
|
|
@@ -5624,7 +4594,7 @@ function buildDoctorReport(root, args) {
|
|
|
5624
4594
|
styleBridgeProposalPresent
|
|
5625
4595
|
},
|
|
5626
4596
|
visualEvidence: {
|
|
5627
|
-
manifestPresent:
|
|
4597
|
+
manifestPresent: existsSync15(join17(appRoot, ".decantr", "evidence", "visual-manifest.json"))
|
|
5628
4598
|
},
|
|
5629
4599
|
lane,
|
|
5630
4600
|
designAuthority,
|
|
@@ -5668,6 +4638,8 @@ function formatDoctorText(report) {
|
|
|
5668
4638
|
` Review pack: ${report.generatedArtifacts.reviewPackPresent ? "present" : "missing"}`,
|
|
5669
4639
|
` Manifest references: ${report.generatedArtifacts.missingReferencedFiles.length === 0 ? "complete" : `${report.generatedArtifacts.missingReferencedFiles.length} missing`}`,
|
|
5670
4640
|
` Artifact guide: ${report.project.artifactReadmePresent ? "present" : "missing"}`,
|
|
4641
|
+
` Typed graph: ${report.generatedArtifacts.graphError ? "error" : report.generatedArtifacts.graphArtifactsCurrent === true ? "current" : report.generatedArtifacts.graphArtifactsCurrent === false ? "stale or missing" : "not generated"}`,
|
|
4642
|
+
` Graph capsule: ${report.generatedArtifacts.graphCapsulePresent ? "present" : "missing"}`,
|
|
5671
4643
|
"",
|
|
5672
4644
|
`${BOLD3}Local Law:${RESET4}`,
|
|
5673
4645
|
` Local patterns: ${report.localLaw.patternsPresent ? "present" : "missing"}`,
|
|
@@ -5732,17 +4704,17 @@ async function cmdDoctor(args = ["doctor"], root = process.cwd()) {
|
|
|
5732
4704
|
}
|
|
5733
4705
|
|
|
5734
4706
|
// src/commands/export.ts
|
|
5735
|
-
import { existsSync as
|
|
5736
|
-
import { dirname as dirname5, isAbsolute, join as
|
|
4707
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync10, readFileSync as readFileSync13, writeFileSync as writeFileSync11 } from "fs";
|
|
4708
|
+
import { dirname as dirname5, isAbsolute, join as join18 } from "path";
|
|
5737
4709
|
var GREEN5 = "\x1B[32m";
|
|
5738
4710
|
var RED4 = "\x1B[31m";
|
|
5739
4711
|
var DIM5 = "\x1B[2m";
|
|
5740
4712
|
var RESET5 = "\x1B[0m";
|
|
5741
4713
|
function readAdoptionMode(projectRoot) {
|
|
5742
|
-
const projectJsonPath =
|
|
5743
|
-
if (!
|
|
4714
|
+
const projectJsonPath = join18(projectRoot, ".decantr", "project.json");
|
|
4715
|
+
if (!existsSync16(projectJsonPath)) return null;
|
|
5744
4716
|
try {
|
|
5745
|
-
const parsed = JSON.parse(
|
|
4717
|
+
const parsed = JSON.parse(readFileSync13(projectJsonPath, "utf-8"));
|
|
5746
4718
|
return typeof parsed.initialized?.adoptionMode === "string" ? parsed.initialized.adoptionMode : null;
|
|
5747
4719
|
} catch {
|
|
5748
4720
|
return null;
|
|
@@ -5750,7 +4722,7 @@ function readAdoptionMode(projectRoot) {
|
|
|
5750
4722
|
}
|
|
5751
4723
|
function resolveOutputPath(projectRoot, output, fallback) {
|
|
5752
4724
|
if (!output) return fallback;
|
|
5753
|
-
return isAbsolute(output) ? output :
|
|
4725
|
+
return isAbsolute(output) ? output : join18(projectRoot, output);
|
|
5754
4726
|
}
|
|
5755
4727
|
var SHADCN_MAP = {
|
|
5756
4728
|
"--d-bg": "--background",
|
|
@@ -5911,21 +4883,21 @@ function generateFigmaTokens(tokens) {
|
|
|
5911
4883
|
`;
|
|
5912
4884
|
}
|
|
5913
4885
|
async function cmdExport(target, projectRoot, options = {}) {
|
|
5914
|
-
const essencePath =
|
|
5915
|
-
const tokensPath =
|
|
5916
|
-
if (!
|
|
4886
|
+
const essencePath = join18(projectRoot, "decantr.essence.json");
|
|
4887
|
+
const tokensPath = join18(projectRoot, "src", "styles", "tokens.css");
|
|
4888
|
+
if (!existsSync16(essencePath)) {
|
|
5917
4889
|
console.error(`${RED4}No decantr.essence.json found. Run \`decantr init\` first.${RESET5}`);
|
|
5918
4890
|
process.exitCode = 1;
|
|
5919
4891
|
return;
|
|
5920
4892
|
}
|
|
5921
|
-
if (!
|
|
4893
|
+
if (!existsSync16(tokensPath)) {
|
|
5922
4894
|
const adoptionMode = readAdoptionMode(projectRoot);
|
|
5923
4895
|
const message = adoptionMode === "contract-only" ? `No src/styles/tokens.css found. This export reads Decantr CSS tokens, but this project is contract-only Brownfield and may intentionally use its own Tailwind/Sass token system. Export from the app's token source or adopt a style bridge before using \`decantr export --to ${target}\`.` : "No src/styles/tokens.css found. Run `decantr refresh` to generate tokens.";
|
|
5924
4896
|
console.error(`${RED4}${message}${RESET5}`);
|
|
5925
4897
|
process.exitCode = 1;
|
|
5926
4898
|
return;
|
|
5927
4899
|
}
|
|
5928
|
-
const tokensCSS =
|
|
4900
|
+
const tokensCSS = readFileSync13(tokensPath, "utf-8");
|
|
5929
4901
|
const tokens = parseTokensCSS(tokensCSS);
|
|
5930
4902
|
if (tokens.size === 0) {
|
|
5931
4903
|
console.error(`${RED4}No --d-* tokens found in tokens.css.${RESET5}`);
|
|
@@ -5937,12 +4909,12 @@ async function cmdExport(target, projectRoot, options = {}) {
|
|
|
5937
4909
|
const cssOut = resolveOutputPath(
|
|
5938
4910
|
projectRoot,
|
|
5939
4911
|
options.output,
|
|
5940
|
-
|
|
4912
|
+
join18(projectRoot, "src", "styles", "shadcn-theme.css")
|
|
5941
4913
|
);
|
|
5942
|
-
const jsonOut =
|
|
4914
|
+
const jsonOut = join18(projectRoot, "components.json");
|
|
5943
4915
|
ensureDir(cssOut);
|
|
5944
|
-
|
|
5945
|
-
|
|
4916
|
+
writeFileSync11(cssOut, generateShadcnCSS(tokens), "utf-8");
|
|
4917
|
+
writeFileSync11(jsonOut, generateShadcnComponentsJSON(), "utf-8");
|
|
5946
4918
|
console.log(`${GREEN5}Exported shadcn theme:${RESET5}`);
|
|
5947
4919
|
console.log(` ${DIM5}CSS:${RESET5} ${cssOut}`);
|
|
5948
4920
|
console.log(` ${DIM5}JSON:${RESET5} ${jsonOut}`);
|
|
@@ -5952,10 +4924,10 @@ async function cmdExport(target, projectRoot, options = {}) {
|
|
|
5952
4924
|
const out = resolveOutputPath(
|
|
5953
4925
|
projectRoot,
|
|
5954
4926
|
options.output,
|
|
5955
|
-
|
|
4927
|
+
join18(projectRoot, "tailwind.decantr.config.ts")
|
|
5956
4928
|
);
|
|
5957
4929
|
ensureDir(out);
|
|
5958
|
-
|
|
4930
|
+
writeFileSync11(out, generateTailwindConfig(tokens), "utf-8");
|
|
5959
4931
|
console.log(`${GREEN5}Exported Tailwind config:${RESET5}`);
|
|
5960
4932
|
console.log(` ${DIM5}File:${RESET5} ${out}`);
|
|
5961
4933
|
break;
|
|
@@ -5964,10 +4936,10 @@ async function cmdExport(target, projectRoot, options = {}) {
|
|
|
5964
4936
|
const out = resolveOutputPath(
|
|
5965
4937
|
projectRoot,
|
|
5966
4938
|
options.output,
|
|
5967
|
-
|
|
4939
|
+
join18(projectRoot, "decantr-tokens.css")
|
|
5968
4940
|
);
|
|
5969
4941
|
ensureDir(out);
|
|
5970
|
-
|
|
4942
|
+
writeFileSync11(out, generateCSSVars(tokens), "utf-8");
|
|
5971
4943
|
console.log(`${GREEN5}Exported CSS variables:${RESET5}`);
|
|
5972
4944
|
console.log(` ${DIM5}File:${RESET5} ${out}`);
|
|
5973
4945
|
break;
|
|
@@ -5976,10 +4948,10 @@ async function cmdExport(target, projectRoot, options = {}) {
|
|
|
5976
4948
|
const out = resolveOutputPath(
|
|
5977
4949
|
projectRoot,
|
|
5978
4950
|
options.output,
|
|
5979
|
-
|
|
4951
|
+
join18(projectRoot, ".decantr", "design", "figma-tokens.json")
|
|
5980
4952
|
);
|
|
5981
4953
|
ensureDir(out);
|
|
5982
|
-
|
|
4954
|
+
writeFileSync11(out, generateFigmaTokens(tokens), "utf-8");
|
|
5983
4955
|
console.log(`${GREEN5}Exported Figma/Tokens Studio tokens:${RESET5}`);
|
|
5984
4956
|
console.log(` ${DIM5}File:${RESET5} ${out}`);
|
|
5985
4957
|
break;
|
|
@@ -5988,15 +4960,15 @@ async function cmdExport(target, projectRoot, options = {}) {
|
|
|
5988
4960
|
}
|
|
5989
4961
|
function ensureDir(filePath) {
|
|
5990
4962
|
const dir = dirname5(filePath);
|
|
5991
|
-
if (!
|
|
5992
|
-
|
|
4963
|
+
if (!existsSync16(dir)) {
|
|
4964
|
+
mkdirSync10(dir, { recursive: true });
|
|
5993
4965
|
}
|
|
5994
4966
|
}
|
|
5995
4967
|
|
|
5996
4968
|
// src/commands/magic.ts
|
|
5997
|
-
import { existsSync as
|
|
4969
|
+
import { existsSync as existsSync17 } from "fs";
|
|
5998
4970
|
import * as fs from "fs/promises";
|
|
5999
|
-
import { join as
|
|
4971
|
+
import { join as join19 } from "path";
|
|
6000
4972
|
var BOLD4 = "\x1B[1m";
|
|
6001
4973
|
var DIM6 = "\x1B[2m";
|
|
6002
4974
|
var RESET6 = "\x1B[0m";
|
|
@@ -6220,8 +5192,8 @@ async function cmdMagic(prompt, projectRoot, options) {
|
|
|
6220
5192
|
console.log(` Archetype: ${intent.archetype}`);
|
|
6221
5193
|
}
|
|
6222
5194
|
console.log("");
|
|
6223
|
-
const essencePath =
|
|
6224
|
-
if (
|
|
5195
|
+
const essencePath = join19(projectRoot, "decantr.essence.json");
|
|
5196
|
+
if (existsSync17(essencePath)) {
|
|
6225
5197
|
const projectFlag = options.projectLabel ? ` --project ${options.projectLabel}` : "";
|
|
6226
5198
|
console.log(`${YELLOW4} Decantr is already attached to this project.${RESET6}`);
|
|
6227
5199
|
console.log(
|
|
@@ -6254,7 +5226,7 @@ async function cmdMagic(prompt, projectRoot, options) {
|
|
|
6254
5226
|
return;
|
|
6255
5227
|
}
|
|
6256
5228
|
const registryClient = new RegistryClient({
|
|
6257
|
-
cacheDir:
|
|
5229
|
+
cacheDir: join19(projectRoot, ".decantr", "cache"),
|
|
6258
5230
|
apiUrl: options.registry,
|
|
6259
5231
|
offline: options.offline
|
|
6260
5232
|
});
|
|
@@ -6525,14 +5497,14 @@ async function cmdMagic(prompt, projectRoot, options) {
|
|
|
6525
5497
|
if (result.gitignoreUpdated) {
|
|
6526
5498
|
console.log(` ${dim(".gitignore updated")}`);
|
|
6527
5499
|
}
|
|
6528
|
-
const contextDir =
|
|
5500
|
+
const contextDir = join19(projectRoot, ".decantr", "context");
|
|
6529
5501
|
let sectionCount = 0;
|
|
6530
5502
|
try {
|
|
6531
5503
|
const files = await fs.readdir(contextDir);
|
|
6532
5504
|
sectionCount = files.filter((f) => f.startsWith("section-")).length;
|
|
6533
5505
|
} catch {
|
|
6534
5506
|
}
|
|
6535
|
-
const treatmentsPath =
|
|
5507
|
+
const treatmentsPath = join19(projectRoot, "src", "styles", "treatments.css");
|
|
6536
5508
|
let hasLayers = false;
|
|
6537
5509
|
try {
|
|
6538
5510
|
const css = await fs.readFile(treatmentsPath, "utf-8");
|
|
@@ -6565,10 +5537,10 @@ ${GREEN6}${BOLD4}Quality summary:${RESET6}`);
|
|
|
6565
5537
|
}
|
|
6566
5538
|
|
|
6567
5539
|
// src/commands/migrate.ts
|
|
6568
|
-
import { copyFileSync, existsSync as
|
|
6569
|
-
import { join as
|
|
5540
|
+
import { copyFileSync, existsSync as existsSync18, readFileSync as readFileSync14, writeFileSync as writeFileSync12 } from "fs";
|
|
5541
|
+
import { join as join20 } from "path";
|
|
6570
5542
|
import {
|
|
6571
|
-
isV4 as
|
|
5543
|
+
isV4 as isV44,
|
|
6572
5544
|
migrateToV4,
|
|
6573
5545
|
validateEssence,
|
|
6574
5546
|
validateLegacyEssenceForMigration
|
|
@@ -6579,12 +5551,12 @@ var YELLOW5 = "\x1B[33m";
|
|
|
6579
5551
|
var RESET7 = "\x1B[0m";
|
|
6580
5552
|
var DIM7 = "\x1B[2m";
|
|
6581
5553
|
function migrateEssenceFile(essencePath) {
|
|
6582
|
-
if (!
|
|
5554
|
+
if (!existsSync18(essencePath)) {
|
|
6583
5555
|
return { success: false, error: `File not found: ${essencePath}` };
|
|
6584
5556
|
}
|
|
6585
5557
|
let raw;
|
|
6586
5558
|
try {
|
|
6587
|
-
raw =
|
|
5559
|
+
raw = readFileSync14(essencePath, "utf-8");
|
|
6588
5560
|
} catch (e) {
|
|
6589
5561
|
return { success: false, error: `Could not read ${essencePath}: ${e.message}` };
|
|
6590
5562
|
}
|
|
@@ -6594,7 +5566,7 @@ function migrateEssenceFile(essencePath) {
|
|
|
6594
5566
|
} catch (e) {
|
|
6595
5567
|
return { success: false, error: `Invalid JSON: ${e.message}` };
|
|
6596
5568
|
}
|
|
6597
|
-
if (
|
|
5569
|
+
if (isV44(essence)) {
|
|
6598
5570
|
return { success: true, alreadyV4: true, essence };
|
|
6599
5571
|
}
|
|
6600
5572
|
const preValidation = validateLegacyEssenceForMigration(essence);
|
|
@@ -6625,7 +5597,7 @@ function migrateEssenceFile(essencePath) {
|
|
|
6625
5597
|
};
|
|
6626
5598
|
}
|
|
6627
5599
|
try {
|
|
6628
|
-
|
|
5600
|
+
writeFileSync12(essencePath, JSON.stringify(v4, null, 2) + "\n");
|
|
6629
5601
|
} catch (e) {
|
|
6630
5602
|
return {
|
|
6631
5603
|
success: false,
|
|
@@ -6646,8 +5618,8 @@ async function cmdMigrate(projectRoot = process.cwd(), args = []) {
|
|
|
6646
5618
|
process.exitCode = 1;
|
|
6647
5619
|
return;
|
|
6648
5620
|
}
|
|
6649
|
-
const essencePath =
|
|
6650
|
-
if (!
|
|
5621
|
+
const essencePath = join20(projectRoot, "decantr.essence.json");
|
|
5622
|
+
if (!existsSync18(essencePath)) {
|
|
6651
5623
|
console.error(`${RED5}No decantr.essence.json found. Run \`decantr init\` first.${RESET7}`);
|
|
6652
5624
|
process.exitCode = 1;
|
|
6653
5625
|
return;
|
|
@@ -6669,7 +5641,7 @@ async function cmdMigrate(projectRoot = process.cwd(), args = []) {
|
|
|
6669
5641
|
}
|
|
6670
5642
|
if (result.essence) {
|
|
6671
5643
|
const registryClient = new RegistryClient({
|
|
6672
|
-
cacheDir:
|
|
5644
|
+
cacheDir: join20(projectRoot, ".decantr", "cache")
|
|
6673
5645
|
});
|
|
6674
5646
|
await refreshDerivedFiles(projectRoot, result.essence, registryClient);
|
|
6675
5647
|
console.log(`${GREEN7}Derived context and execution packs refreshed.${RESET7}`);
|
|
@@ -6680,50 +5652,50 @@ async function cmdMigrate(projectRoot = process.cwd(), args = []) {
|
|
|
6680
5652
|
|
|
6681
5653
|
// src/commands/new-project.ts
|
|
6682
5654
|
import { spawnSync } from "child_process";
|
|
6683
|
-
import { existsSync as
|
|
6684
|
-
import { join as
|
|
5655
|
+
import { existsSync as existsSync20, mkdirSync as mkdirSync12 } from "fs";
|
|
5656
|
+
import { join as join22, resolve as resolve3 } from "path";
|
|
6685
5657
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
6686
5658
|
|
|
6687
5659
|
// src/offline-content.ts
|
|
6688
|
-
import { cpSync, existsSync as
|
|
6689
|
-
import { join as
|
|
5660
|
+
import { cpSync, existsSync as existsSync19, mkdirSync as mkdirSync11 } from "fs";
|
|
5661
|
+
import { join as join21, resolve as resolve2 } from "path";
|
|
6690
5662
|
var CONTENT_TYPES2 = ["archetypes", "blueprints", "patterns", "themes", "shells"];
|
|
6691
5663
|
function copyIfExists(source, target) {
|
|
6692
|
-
if (!
|
|
5664
|
+
if (!existsSync19(source)) return false;
|
|
6693
5665
|
if (resolve2(source) === resolve2(target)) return true;
|
|
6694
5666
|
cpSync(source, target, { recursive: true });
|
|
6695
5667
|
return true;
|
|
6696
5668
|
}
|
|
6697
5669
|
function hydrateContentRoot(projectDir, contentRoot) {
|
|
6698
|
-
if (!
|
|
6699
|
-
const customRoot =
|
|
6700
|
-
const cacheRoot =
|
|
6701
|
-
|
|
6702
|
-
|
|
5670
|
+
if (!existsSync19(contentRoot)) return false;
|
|
5671
|
+
const customRoot = join21(projectDir, ".decantr", "custom");
|
|
5672
|
+
const cacheRoot = join21(projectDir, ".decantr", "cache", "@official");
|
|
5673
|
+
mkdirSync11(customRoot, { recursive: true });
|
|
5674
|
+
mkdirSync11(cacheRoot, { recursive: true });
|
|
6703
5675
|
let copiedAny = false;
|
|
6704
5676
|
for (const type of CONTENT_TYPES2) {
|
|
6705
|
-
const sourceDir =
|
|
6706
|
-
if (!
|
|
6707
|
-
cpSync(sourceDir,
|
|
6708
|
-
cpSync(sourceDir,
|
|
5677
|
+
const sourceDir = join21(contentRoot, type);
|
|
5678
|
+
if (!existsSync19(sourceDir)) continue;
|
|
5679
|
+
cpSync(sourceDir, join21(customRoot, type), { recursive: true });
|
|
5680
|
+
cpSync(sourceDir, join21(cacheRoot, type), { recursive: true });
|
|
6709
5681
|
copiedAny = true;
|
|
6710
5682
|
}
|
|
6711
5683
|
return copiedAny;
|
|
6712
5684
|
}
|
|
6713
5685
|
function seedOfflineRegistry(projectDir, workspaceRoot) {
|
|
6714
|
-
const projectDecantrRoot =
|
|
6715
|
-
|
|
5686
|
+
const projectDecantrRoot = join21(projectDir, ".decantr");
|
|
5687
|
+
mkdirSync11(projectDecantrRoot, { recursive: true });
|
|
6716
5688
|
const configuredContentRoot = process.env.DECANTR_CONTENT_DIR ? resolve2(process.env.DECANTR_CONTENT_DIR) : null;
|
|
6717
5689
|
if (configuredContentRoot && hydrateContentRoot(projectDir, configuredContentRoot)) {
|
|
6718
5690
|
return { seeded: true, strategy: "configured-content-root" };
|
|
6719
5691
|
}
|
|
6720
5692
|
const copiedCache = copyIfExists(
|
|
6721
|
-
|
|
6722
|
-
|
|
5693
|
+
join21(workspaceRoot, ".decantr", "cache"),
|
|
5694
|
+
join21(projectDecantrRoot, "cache")
|
|
6723
5695
|
);
|
|
6724
5696
|
const copiedCustom = copyIfExists(
|
|
6725
|
-
|
|
6726
|
-
|
|
5697
|
+
join21(workspaceRoot, ".decantr", "custom"),
|
|
5698
|
+
join21(projectDecantrRoot, "custom")
|
|
6727
5699
|
);
|
|
6728
5700
|
if (copiedCache || copiedCustom) {
|
|
6729
5701
|
return { seeded: true, strategy: "workspace-cache" };
|
|
@@ -6836,7 +5808,7 @@ function runArgvCommand(command, args, cwd) {
|
|
|
6836
5808
|
}
|
|
6837
5809
|
function resolveInitCommand(initFlags) {
|
|
6838
5810
|
const bundledCliEntrypoint = fileURLToPath2(new URL("./bin.js", import.meta.url));
|
|
6839
|
-
const cliEntrypoint =
|
|
5811
|
+
const cliEntrypoint = existsSync20(bundledCliEntrypoint) ? bundledCliEntrypoint : process.argv[1] && existsSync20(process.argv[1]) ? process.argv[1] : null;
|
|
6840
5812
|
if (cliEntrypoint) {
|
|
6841
5813
|
return {
|
|
6842
5814
|
command: process.execPath,
|
|
@@ -6864,7 +5836,7 @@ async function cmdNewProject(projectName, options) {
|
|
|
6864
5836
|
process.exitCode = 1;
|
|
6865
5837
|
return;
|
|
6866
5838
|
}
|
|
6867
|
-
if (
|
|
5839
|
+
if (existsSync20(projectDir)) {
|
|
6868
5840
|
console.error(error(`Directory "${projectName}" already exists.`));
|
|
6869
5841
|
process.exitCode = 1;
|
|
6870
5842
|
return;
|
|
@@ -6878,7 +5850,7 @@ async function cmdNewProject(projectName, options) {
|
|
|
6878
5850
|
return;
|
|
6879
5851
|
}
|
|
6880
5852
|
console.log(heading(`Creating ${projectName}...`));
|
|
6881
|
-
|
|
5853
|
+
mkdirSync12(projectDir, { recursive: true });
|
|
6882
5854
|
console.log(dim2(` Created ${projectName}/`));
|
|
6883
5855
|
const title = projectName.replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
6884
5856
|
if (shouldBootstrapRuntime && bootstrapAdapter) {
|
|
@@ -6977,21 +5949,21 @@ ${YELLOW6}Decantr init encountered issues. Run \`decantr init\` manually inside
|
|
|
6977
5949
|
}
|
|
6978
5950
|
}
|
|
6979
5951
|
function detectPackageManager4() {
|
|
6980
|
-
if (
|
|
5952
|
+
if (existsSync20(join22(process.cwd(), "pnpm-lock.yaml")) || existsSync20(join22(process.cwd(), "pnpm-workspace.yaml"))) {
|
|
6981
5953
|
return "pnpm";
|
|
6982
5954
|
}
|
|
6983
|
-
if (
|
|
5955
|
+
if (existsSync20(join22(process.cwd(), "yarn.lock"))) {
|
|
6984
5956
|
return "yarn";
|
|
6985
5957
|
}
|
|
6986
|
-
if (
|
|
5958
|
+
if (existsSync20(join22(process.cwd(), "bun.lockb")) || existsSync20(join22(process.cwd(), "bun.lock"))) {
|
|
6987
5959
|
return "bun";
|
|
6988
5960
|
}
|
|
6989
5961
|
return "npm";
|
|
6990
5962
|
}
|
|
6991
5963
|
|
|
6992
5964
|
// src/commands/publish.ts
|
|
6993
|
-
import { existsSync as
|
|
6994
|
-
import { join as
|
|
5965
|
+
import { existsSync as existsSync21, readFileSync as readFileSync15 } from "fs";
|
|
5966
|
+
import { join as join23 } from "path";
|
|
6995
5967
|
import {
|
|
6996
5968
|
API_CONTENT_TYPE_TO_CONTENT_TYPE,
|
|
6997
5969
|
CONTENT_TYPE_TO_API_CONTENT_TYPE as CONTENT_TYPE_TO_API_CONTENT_TYPE2,
|
|
@@ -7007,8 +5979,8 @@ async function cmdPublish(type, name, projectRoot = process.cwd()) {
|
|
|
7007
5979
|
}
|
|
7008
5980
|
const singularType = API_CONTENT_TYPE_TO_CONTENT_TYPE[type] || type;
|
|
7009
5981
|
const pluralType = CONTENT_TYPE_TO_API_CONTENT_TYPE2[type] || CONTENT_TYPE_TO_API_CONTENT_TYPE2[singularType] || `${type}s`;
|
|
7010
|
-
const customPath =
|
|
7011
|
-
if (!
|
|
5982
|
+
const customPath = join23(projectRoot, ".decantr", "custom", pluralType, `${name}.json`);
|
|
5983
|
+
if (!existsSync21(customPath)) {
|
|
7012
5984
|
console.error(`Custom ${singularType} "${name}" not found at ${customPath}`);
|
|
7013
5985
|
console.error(`Create one first: decantr create ${singularType} ${name}`);
|
|
7014
5986
|
process.exitCode = 1;
|
|
@@ -7016,7 +5988,7 @@ async function cmdPublish(type, name, projectRoot = process.cwd()) {
|
|
|
7016
5988
|
}
|
|
7017
5989
|
let data;
|
|
7018
5990
|
try {
|
|
7019
|
-
data = JSON.parse(
|
|
5991
|
+
data = JSON.parse(readFileSync15(customPath, "utf-8"));
|
|
7020
5992
|
} catch {
|
|
7021
5993
|
console.error(`Failed to parse ${customPath}`);
|
|
7022
5994
|
process.exitCode = 1;
|
|
@@ -7055,22 +6027,22 @@ async function cmdPublish(type, name, projectRoot = process.cwd()) {
|
|
|
7055
6027
|
|
|
7056
6028
|
// src/commands/refresh.ts
|
|
7057
6029
|
import { createHash } from "crypto";
|
|
7058
|
-
import { existsSync as
|
|
7059
|
-
import { isAbsolute as isAbsolute2, join as
|
|
6030
|
+
import { existsSync as existsSync22, readdirSync as readdirSync6, readFileSync as readFileSync16, statSync as statSync5 } from "fs";
|
|
6031
|
+
import { isAbsolute as isAbsolute2, join as join24, relative as relative5 } from "path";
|
|
7060
6032
|
import { collectMissingPackManifestFiles as collectMissingPackManifestFiles2 } from "@decantr/verifier";
|
|
7061
|
-
import { isV4 as
|
|
6033
|
+
import { isV4 as isV45 } from "@decantr/essence-spec";
|
|
7062
6034
|
var GREEN9 = "\x1B[32m";
|
|
7063
6035
|
var RED7 = "\x1B[31m";
|
|
7064
6036
|
var DIM9 = "\x1B[2m";
|
|
7065
6037
|
var RESET9 = "\x1B[0m";
|
|
7066
6038
|
function hashFile(path) {
|
|
7067
|
-
return createHash("sha256").update(
|
|
6039
|
+
return createHash("sha256").update(readFileSync16(path)).digest("hex");
|
|
7068
6040
|
}
|
|
7069
6041
|
function walkFiles(dir) {
|
|
7070
|
-
if (!
|
|
6042
|
+
if (!existsSync22(dir)) return [];
|
|
7071
6043
|
const files = [];
|
|
7072
|
-
for (const entry of
|
|
7073
|
-
const path =
|
|
6044
|
+
for (const entry of readdirSync6(dir, { withFileTypes: true })) {
|
|
6045
|
+
const path = join24(dir, entry.name);
|
|
7074
6046
|
if (entry.isDirectory()) {
|
|
7075
6047
|
files.push(...walkFiles(path));
|
|
7076
6048
|
} else if (entry.isFile()) {
|
|
@@ -7081,10 +6053,10 @@ function walkFiles(dir) {
|
|
|
7081
6053
|
}
|
|
7082
6054
|
function trackedGeneratedFiles(projectRoot) {
|
|
7083
6055
|
return [
|
|
7084
|
-
|
|
7085
|
-
...walkFiles(
|
|
7086
|
-
...["global.css", "tokens.css", "treatments.css", "decantr-bridge.css"].map((file) =>
|
|
7087
|
-
].filter((path) =>
|
|
6056
|
+
join24(projectRoot, "DECANTR.md"),
|
|
6057
|
+
...walkFiles(join24(projectRoot, ".decantr", "context")),
|
|
6058
|
+
...["global.css", "tokens.css", "treatments.css", "decantr-bridge.css"].map((file) => join24(projectRoot, "src", "styles", file)).filter((path) => existsSync22(path))
|
|
6059
|
+
].filter((path) => existsSync22(path));
|
|
7088
6060
|
}
|
|
7089
6061
|
function snapshotGeneratedFiles(projectRoot) {
|
|
7090
6062
|
const map = /* @__PURE__ */ new Map();
|
|
@@ -7100,40 +6072,40 @@ function snapshotGeneratedFiles(projectRoot) {
|
|
|
7100
6072
|
}
|
|
7101
6073
|
function fileMtimeMs(path) {
|
|
7102
6074
|
try {
|
|
7103
|
-
return
|
|
6075
|
+
return statSync5(path).mtimeMs;
|
|
7104
6076
|
} catch {
|
|
7105
6077
|
return 0;
|
|
7106
6078
|
}
|
|
7107
6079
|
}
|
|
7108
6080
|
function newestInputMtime(projectRoot) {
|
|
7109
6081
|
return Math.max(
|
|
7110
|
-
fileMtimeMs(
|
|
7111
|
-
fileMtimeMs(
|
|
6082
|
+
fileMtimeMs(join24(projectRoot, "decantr.essence.json")),
|
|
6083
|
+
fileMtimeMs(join24(projectRoot, ".decantr", "project.json"))
|
|
7112
6084
|
);
|
|
7113
6085
|
}
|
|
7114
6086
|
function isContractOnlyProject2(projectRoot) {
|
|
7115
|
-
const projectJsonPath =
|
|
7116
|
-
if (!
|
|
6087
|
+
const projectJsonPath = join24(projectRoot, ".decantr", "project.json");
|
|
6088
|
+
if (!existsSync22(projectJsonPath)) return false;
|
|
7117
6089
|
try {
|
|
7118
|
-
const projectJson = JSON.parse(
|
|
6090
|
+
const projectJson = JSON.parse(readFileSync16(projectJsonPath, "utf-8"));
|
|
7119
6091
|
return projectJson.initialized?.adoptionMode === "contract-only";
|
|
7120
6092
|
} catch {
|
|
7121
6093
|
return false;
|
|
7122
6094
|
}
|
|
7123
6095
|
}
|
|
7124
6096
|
function checkRefreshFreshness(projectRoot) {
|
|
7125
|
-
const contextDir =
|
|
6097
|
+
const contextDir = join24(projectRoot, ".decantr", "context");
|
|
7126
6098
|
const generated = trackedGeneratedFiles(projectRoot);
|
|
7127
6099
|
const reasons = [];
|
|
7128
6100
|
const packHydrationOptional = isContractOnlyProject2(projectRoot);
|
|
7129
|
-
if (!
|
|
7130
|
-
if (!
|
|
7131
|
-
if (!
|
|
6101
|
+
if (!existsSync22(join24(projectRoot, "DECANTR.md"))) reasons.push("DECANTR.md is missing.");
|
|
6102
|
+
if (!existsSync22(contextDir)) reasons.push(".decantr/context is missing.");
|
|
6103
|
+
if (!existsSync22(join24(contextDir, "scaffold.md"))) {
|
|
7132
6104
|
reasons.push(".decantr/context/scaffold.md is missing.");
|
|
7133
6105
|
}
|
|
7134
|
-
if (!packHydrationOptional && !
|
|
6106
|
+
if (!packHydrationOptional && !existsSync22(join24(contextDir, "pack-manifest.json"))) {
|
|
7135
6107
|
reasons.push(".decantr/context/pack-manifest.json is missing.");
|
|
7136
|
-
} else if (
|
|
6108
|
+
} else if (existsSync22(join24(contextDir, "pack-manifest.json"))) {
|
|
7137
6109
|
const missingPackFiles = collectMissingPackManifestFiles2(projectRoot);
|
|
7138
6110
|
if (missingPackFiles.length > 0) {
|
|
7139
6111
|
reasons.push(
|
|
@@ -7186,8 +6158,8 @@ function summarizeChanges(projectRoot, before, after) {
|
|
|
7186
6158
|
}
|
|
7187
6159
|
function displayGeneratedPath(summary, file, displayRoot) {
|
|
7188
6160
|
if (!displayRoot) return file;
|
|
7189
|
-
const absolutePath =
|
|
7190
|
-
const relativePath =
|
|
6161
|
+
const absolutePath = join24(summary.projectRoot, file);
|
|
6162
|
+
const relativePath = relative5(displayRoot, absolutePath).replace(/\\/g, "/");
|
|
7191
6163
|
if (relativePath && !relativePath.startsWith("..") && !isAbsolute2(relativePath)) {
|
|
7192
6164
|
return relativePath;
|
|
7193
6165
|
}
|
|
@@ -7221,17 +6193,17 @@ function printRefreshSummary(summary, displayRoot) {
|
|
|
7221
6193
|
}
|
|
7222
6194
|
}
|
|
7223
6195
|
async function cmdRefresh(projectRoot = process.cwd(), options = {}) {
|
|
7224
|
-
const essencePath =
|
|
7225
|
-
if (!
|
|
6196
|
+
const essencePath = join24(projectRoot, "decantr.essence.json");
|
|
6197
|
+
if (!existsSync22(essencePath)) {
|
|
7226
6198
|
console.error(`${RED7}No decantr.essence.json found. Run \`decantr init\` first.${RESET9}`);
|
|
7227
6199
|
process.exitCode = 1;
|
|
7228
6200
|
return;
|
|
7229
6201
|
}
|
|
7230
6202
|
let essence;
|
|
7231
6203
|
try {
|
|
7232
|
-
const raw =
|
|
6204
|
+
const raw = readFileSync16(essencePath, "utf-8");
|
|
7233
6205
|
const parsed = JSON.parse(raw);
|
|
7234
|
-
if (!
|
|
6206
|
+
if (!isV45(parsed)) {
|
|
7235
6207
|
console.error(
|
|
7236
6208
|
`${RED7}Active workflows require Essence v4.0.0. Run \`decantr migrate --to v4\` first.${RESET9}`
|
|
7237
6209
|
);
|
|
@@ -7255,7 +6227,7 @@ async function cmdRefresh(projectRoot = process.cwd(), options = {}) {
|
|
|
7255
6227
|
return;
|
|
7256
6228
|
}
|
|
7257
6229
|
const registryClient = new RegistryClient({
|
|
7258
|
-
cacheDir:
|
|
6230
|
+
cacheDir: join24(projectRoot, ".decantr", "cache"),
|
|
7259
6231
|
offline: options.offline
|
|
7260
6232
|
});
|
|
7261
6233
|
console.log("Regenerating derived files...\n");
|
|
@@ -7286,8 +6258,8 @@ async function cmdRefresh(projectRoot = process.cwd(), options = {}) {
|
|
|
7286
6258
|
}
|
|
7287
6259
|
|
|
7288
6260
|
// src/commands/registry-mirror.ts
|
|
7289
|
-
import { mkdirSync as
|
|
7290
|
-
import { join as
|
|
6261
|
+
import { mkdirSync as mkdirSync13, writeFileSync as writeFileSync13 } from "fs";
|
|
6262
|
+
import { join as join25 } from "path";
|
|
7291
6263
|
import { API_CONTENT_TYPES, RegistryAPIClient as RegistryAPIClient2 } from "@decantr/registry";
|
|
7292
6264
|
var GREEN10 = "\x1B[32m";
|
|
7293
6265
|
var RED8 = "\x1B[31m";
|
|
@@ -7315,7 +6287,7 @@ async function cmdRegistryMirror(projectRoot, options = {}) {
|
|
|
7315
6287
|
process.exitCode = 1;
|
|
7316
6288
|
return;
|
|
7317
6289
|
}
|
|
7318
|
-
const cacheDir =
|
|
6290
|
+
const cacheDir = join25(projectRoot, ".decantr", "cache");
|
|
7319
6291
|
const counts = {};
|
|
7320
6292
|
const failed = [];
|
|
7321
6293
|
console.log(`
|
|
@@ -7325,19 +6297,19 @@ Mirroring registry content to ${DIM10}.decantr/cache/${RESET10}
|
|
|
7325
6297
|
try {
|
|
7326
6298
|
const result = await apiClient.listContent(type, { namespace: "@official" });
|
|
7327
6299
|
const items = result.items;
|
|
7328
|
-
const typeDir =
|
|
7329
|
-
|
|
7330
|
-
|
|
6300
|
+
const typeDir = join25(cacheDir, "@official", type);
|
|
6301
|
+
mkdirSync13(typeDir, { recursive: true });
|
|
6302
|
+
writeFileSync13(join25(typeDir, "index.json"), JSON.stringify(result, null, 2));
|
|
7331
6303
|
let itemCount = 0;
|
|
7332
6304
|
for (const item of items) {
|
|
7333
6305
|
const slug = item.slug || item.id;
|
|
7334
6306
|
if (!slug) continue;
|
|
7335
6307
|
try {
|
|
7336
6308
|
const fullItem = await apiClient.getContent(type, "@official", slug);
|
|
7337
|
-
|
|
6309
|
+
writeFileSync13(join25(typeDir, `${slug}.json`), JSON.stringify(fullItem, null, 2));
|
|
7338
6310
|
itemCount++;
|
|
7339
6311
|
} catch {
|
|
7340
|
-
|
|
6312
|
+
writeFileSync13(join25(typeDir, `${slug}.json`), JSON.stringify(item, null, 2));
|
|
7341
6313
|
itemCount++;
|
|
7342
6314
|
}
|
|
7343
6315
|
}
|
|
@@ -7352,8 +6324,8 @@ Mirroring registry content to ${DIM10}.decantr/cache/${RESET10}
|
|
|
7352
6324
|
mirrored_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7353
6325
|
counts
|
|
7354
6326
|
};
|
|
7355
|
-
|
|
7356
|
-
|
|
6327
|
+
mkdirSync13(join25(cacheDir), { recursive: true });
|
|
6328
|
+
writeFileSync13(join25(cacheDir, "mirror-manifest.json"), JSON.stringify(manifest, null, 2));
|
|
7357
6329
|
const totalItems = Object.values(counts).reduce((a, b) => a + b, 0);
|
|
7358
6330
|
console.log("");
|
|
7359
6331
|
if (failed.length > 0) {
|
|
@@ -7370,30 +6342,30 @@ Mirroring registry content to ${DIM10}.decantr/cache/${RESET10}
|
|
|
7370
6342
|
}
|
|
7371
6343
|
|
|
7372
6344
|
// src/commands/remove.ts
|
|
7373
|
-
import { existsSync as
|
|
7374
|
-
import { join as
|
|
7375
|
-
import { isV4 as
|
|
6345
|
+
import { existsSync as existsSync24, readFileSync as readFileSync17, rmSync as rmSync2, writeFileSync as writeFileSync14 } from "fs";
|
|
6346
|
+
import { join as join26 } from "path";
|
|
6347
|
+
import { isV4 as isV46 } from "@decantr/essence-spec";
|
|
7376
6348
|
var GREEN11 = "\x1B[32m";
|
|
7377
6349
|
var RED9 = "\x1B[31m";
|
|
7378
6350
|
var YELLOW8 = "\x1B[33m";
|
|
7379
6351
|
var DIM11 = "\x1B[2m";
|
|
7380
6352
|
var RESET11 = "\x1B[0m";
|
|
7381
6353
|
function readV4Essence2(projectRoot) {
|
|
7382
|
-
const essencePath =
|
|
7383
|
-
if (!
|
|
6354
|
+
const essencePath = join26(projectRoot, "decantr.essence.json");
|
|
6355
|
+
if (!existsSync24(essencePath)) {
|
|
7384
6356
|
console.error(`${RED9}No decantr.essence.json found. Run \`decantr init\` first.${RESET11}`);
|
|
7385
6357
|
process.exitCode = 1;
|
|
7386
6358
|
return null;
|
|
7387
6359
|
}
|
|
7388
6360
|
let parsed;
|
|
7389
6361
|
try {
|
|
7390
|
-
parsed = JSON.parse(
|
|
6362
|
+
parsed = JSON.parse(readFileSync17(essencePath, "utf-8"));
|
|
7391
6363
|
} catch (e) {
|
|
7392
6364
|
console.error(`${RED9}Could not read essence: ${e.message}${RESET11}`);
|
|
7393
6365
|
process.exitCode = 1;
|
|
7394
6366
|
return null;
|
|
7395
6367
|
}
|
|
7396
|
-
if (!
|
|
6368
|
+
if (!isV46(parsed)) {
|
|
7397
6369
|
console.error(
|
|
7398
6370
|
`${RED9}Active workflows require Essence v4.0.0. Run \`decantr migrate --to v4\` first.${RESET11}`
|
|
7399
6371
|
);
|
|
@@ -7403,7 +6375,7 @@ function readV4Essence2(projectRoot) {
|
|
|
7403
6375
|
return { essence: parsed, essencePath };
|
|
7404
6376
|
}
|
|
7405
6377
|
function writeEssence2(essencePath, essence) {
|
|
7406
|
-
|
|
6378
|
+
writeFileSync14(essencePath, JSON.stringify(essence, null, 2) + "\n");
|
|
7407
6379
|
}
|
|
7408
6380
|
function readFlagValue2(args, name) {
|
|
7409
6381
|
const prefix = `--${name}=`;
|
|
@@ -7475,14 +6447,14 @@ async function cmdRemoveSection(sectionId, args, projectRoot = process.cwd()) {
|
|
|
7475
6447
|
sections.splice(idx, 1);
|
|
7476
6448
|
recomputeGlobalFeatures(essence);
|
|
7477
6449
|
removeRoutes(essence, sectionId);
|
|
7478
|
-
const contextFile =
|
|
7479
|
-
if (
|
|
6450
|
+
const contextFile = join26(projectRoot, ".decantr", "context", `${sectionId}.md`);
|
|
6451
|
+
if (existsSync24(contextFile)) {
|
|
7480
6452
|
rmSync2(contextFile);
|
|
7481
6453
|
}
|
|
7482
6454
|
writeEssence2(essencePath, essence);
|
|
7483
6455
|
console.log(`${GREEN11}Removed section "${sectionId}".${RESET11}`);
|
|
7484
6456
|
const registryClient = new RegistryClient({
|
|
7485
|
-
cacheDir:
|
|
6457
|
+
cacheDir: join26(projectRoot, ".decantr", "cache")
|
|
7486
6458
|
});
|
|
7487
6459
|
await refreshDerivedFiles(projectRoot, essence, registryClient);
|
|
7488
6460
|
console.log(`${GREEN11}Derived files refreshed.${RESET11}`);
|
|
@@ -7525,7 +6497,7 @@ async function cmdRemovePage(path, args, projectRoot = process.cwd()) {
|
|
|
7525
6497
|
}
|
|
7526
6498
|
console.log(`${GREEN11}Removed page "${pageId}" from section "${resolvedSectionId}".${RESET11}`);
|
|
7527
6499
|
const registryClient = new RegistryClient({
|
|
7528
|
-
cacheDir:
|
|
6500
|
+
cacheDir: join26(projectRoot, ".decantr", "cache")
|
|
7529
6501
|
});
|
|
7530
6502
|
await refreshDerivedFiles(projectRoot, essence, registryClient);
|
|
7531
6503
|
console.log(`${GREEN11}Derived files refreshed.${RESET11}`);
|
|
@@ -7568,15 +6540,15 @@ async function cmdRemoveFeature(feature, args, projectRoot = process.cwd()) {
|
|
|
7568
6540
|
const target = sectionId ? `section "${sectionId}" and global` : "global";
|
|
7569
6541
|
console.log(`${GREEN11}Removed feature "${feature}" from ${target} features.${RESET11}`);
|
|
7570
6542
|
const registryClient = new RegistryClient({
|
|
7571
|
-
cacheDir:
|
|
6543
|
+
cacheDir: join26(projectRoot, ".decantr", "cache")
|
|
7572
6544
|
});
|
|
7573
6545
|
await refreshDerivedFiles(projectRoot, essence, registryClient);
|
|
7574
6546
|
console.log(`${GREEN11}Derived files refreshed.${RESET11}`);
|
|
7575
6547
|
}
|
|
7576
6548
|
|
|
7577
6549
|
// src/commands/sync-drift.ts
|
|
7578
|
-
import { existsSync as
|
|
7579
|
-
import { join as
|
|
6550
|
+
import { existsSync as existsSync25, readFileSync as readFileSync18, writeFileSync as writeFileSync15 } from "fs";
|
|
6551
|
+
import { join as join27 } from "path";
|
|
7580
6552
|
var GREEN12 = "\x1B[32m";
|
|
7581
6553
|
var RED10 = "\x1B[31m";
|
|
7582
6554
|
var YELLOW9 = "\x1B[33m";
|
|
@@ -7585,8 +6557,8 @@ var DIM12 = "\x1B[2m";
|
|
|
7585
6557
|
var BOLD6 = "\x1B[1m";
|
|
7586
6558
|
var CYAN6 = "\x1B[36m";
|
|
7587
6559
|
async function cmdSyncDrift(projectRoot = process.cwd()) {
|
|
7588
|
-
const driftLogPath =
|
|
7589
|
-
if (!
|
|
6560
|
+
const driftLogPath = join27(projectRoot, ".decantr", "drift-log.json");
|
|
6561
|
+
if (!existsSync25(driftLogPath)) {
|
|
7590
6562
|
console.log(`${GREEN12}No drift log found \u2014 no drift recorded.${RESET12}`);
|
|
7591
6563
|
console.log(
|
|
7592
6564
|
`${DIM12}Drift is logged when guard violations are detected during development.${RESET12}`
|
|
@@ -7595,7 +6567,7 @@ async function cmdSyncDrift(projectRoot = process.cwd()) {
|
|
|
7595
6567
|
}
|
|
7596
6568
|
let entries;
|
|
7597
6569
|
try {
|
|
7598
|
-
entries = JSON.parse(
|
|
6570
|
+
entries = JSON.parse(readFileSync18(driftLogPath, "utf-8"));
|
|
7599
6571
|
} catch {
|
|
7600
6572
|
console.error(`${RED10}Could not parse drift-log.json${RESET12}`);
|
|
7601
6573
|
process.exitCode = 1;
|
|
@@ -7641,13 +6613,13 @@ ${BOLD6}Unresolved Drift Entries (${unresolved.length})${RESET12}
|
|
|
7641
6613
|
console.log("");
|
|
7642
6614
|
}
|
|
7643
6615
|
function resolveDriftEntries(projectRoot, options) {
|
|
7644
|
-
const driftLogPath =
|
|
7645
|
-
if (!
|
|
6616
|
+
const driftLogPath = join27(projectRoot, ".decantr", "drift-log.json");
|
|
6617
|
+
if (!existsSync25(driftLogPath)) {
|
|
7646
6618
|
return { success: true };
|
|
7647
6619
|
}
|
|
7648
6620
|
if (options.clear) {
|
|
7649
6621
|
try {
|
|
7650
|
-
|
|
6622
|
+
writeFileSync15(driftLogPath, "[]");
|
|
7651
6623
|
return { success: true };
|
|
7652
6624
|
} catch (e) {
|
|
7653
6625
|
return { success: false, error: `Could not clear drift log: ${e.message}` };
|
|
@@ -7655,7 +6627,7 @@ function resolveDriftEntries(projectRoot, options) {
|
|
|
7655
6627
|
}
|
|
7656
6628
|
let entries;
|
|
7657
6629
|
try {
|
|
7658
|
-
entries = JSON.parse(
|
|
6630
|
+
entries = JSON.parse(readFileSync18(driftLogPath, "utf-8"));
|
|
7659
6631
|
} catch {
|
|
7660
6632
|
return { success: false, error: "Could not parse drift-log.json" };
|
|
7661
6633
|
}
|
|
@@ -7674,7 +6646,7 @@ function resolveDriftEntries(projectRoot, options) {
|
|
|
7674
6646
|
}
|
|
7675
6647
|
}
|
|
7676
6648
|
try {
|
|
7677
|
-
|
|
6649
|
+
writeFileSync15(driftLogPath, JSON.stringify(entries, null, 2));
|
|
7678
6650
|
return { success: true };
|
|
7679
6651
|
} catch (e) {
|
|
7680
6652
|
return { success: false, error: `Could not write drift log: ${e.message}` };
|
|
@@ -7899,9 +6871,9 @@ function trimTrailingSlashes(value) {
|
|
|
7899
6871
|
}
|
|
7900
6872
|
|
|
7901
6873
|
// src/commands/theme-switch.ts
|
|
7902
|
-
import { existsSync as
|
|
7903
|
-
import { join as
|
|
7904
|
-
import { isV4 as
|
|
6874
|
+
import { existsSync as existsSync26, readFileSync as readFileSync19, writeFileSync as writeFileSync16 } from "fs";
|
|
6875
|
+
import { join as join28 } from "path";
|
|
6876
|
+
import { isV4 as isV47 } from "@decantr/essence-spec";
|
|
7905
6877
|
var GREEN14 = "\x1B[32m";
|
|
7906
6878
|
var RED11 = "\x1B[31m";
|
|
7907
6879
|
var YELLOW10 = "\x1B[33m";
|
|
@@ -7917,21 +6889,21 @@ async function cmdThemeSwitch(themeName, args, projectRoot = process.cwd()) {
|
|
|
7917
6889
|
process.exitCode = 1;
|
|
7918
6890
|
return;
|
|
7919
6891
|
}
|
|
7920
|
-
const essencePath =
|
|
7921
|
-
if (!
|
|
6892
|
+
const essencePath = join28(projectRoot, "decantr.essence.json");
|
|
6893
|
+
if (!existsSync26(essencePath)) {
|
|
7922
6894
|
console.error(`${RED11}No decantr.essence.json found. Run \`decantr init\` first.${RESET14}`);
|
|
7923
6895
|
process.exitCode = 1;
|
|
7924
6896
|
return;
|
|
7925
6897
|
}
|
|
7926
6898
|
let parsed;
|
|
7927
6899
|
try {
|
|
7928
|
-
parsed = JSON.parse(
|
|
6900
|
+
parsed = JSON.parse(readFileSync19(essencePath, "utf-8"));
|
|
7929
6901
|
} catch (e) {
|
|
7930
6902
|
console.error(`${RED11}Could not read essence: ${e.message}${RESET14}`);
|
|
7931
6903
|
process.exitCode = 1;
|
|
7932
6904
|
return;
|
|
7933
6905
|
}
|
|
7934
|
-
if (!
|
|
6906
|
+
if (!isV47(parsed)) {
|
|
7935
6907
|
console.error(
|
|
7936
6908
|
`${RED11}Active workflows require Essence v4.0.0. Run \`decantr migrate --to v4\` first.${RESET14}`
|
|
7937
6909
|
);
|
|
@@ -7976,7 +6948,7 @@ async function cmdThemeSwitch(themeName, args, projectRoot = process.cwd()) {
|
|
|
7976
6948
|
essence.dna.theme.mode = mode;
|
|
7977
6949
|
}
|
|
7978
6950
|
const registryClient = new RegistryClient({
|
|
7979
|
-
cacheDir:
|
|
6951
|
+
cacheDir: join28(projectRoot, ".decantr", "cache")
|
|
7980
6952
|
});
|
|
7981
6953
|
try {
|
|
7982
6954
|
const themeResult = await registryClient.fetchTheme(themeName);
|
|
@@ -7991,7 +6963,7 @@ async function cmdThemeSwitch(themeName, args, projectRoot = process.cwd()) {
|
|
|
7991
6963
|
}
|
|
7992
6964
|
} catch {
|
|
7993
6965
|
}
|
|
7994
|
-
|
|
6966
|
+
writeFileSync16(essencePath, JSON.stringify(essence, null, 2) + "\n");
|
|
7995
6967
|
console.log(`${GREEN14}Switched theme: ${oldThemeId} \u2192 ${themeName}${RESET14}`);
|
|
7996
6968
|
if (shape) console.log(` ${DIM14}Shape: ${shape}${RESET14}`);
|
|
7997
6969
|
if (mode) console.log(` ${DIM14}Mode: ${mode}${RESET14}`);
|
|
@@ -8299,8 +7271,8 @@ async function runSimplifiedInit(blueprints) {
|
|
|
8299
7271
|
}
|
|
8300
7272
|
|
|
8301
7273
|
// src/theme-commands.ts
|
|
8302
|
-
import { existsSync as
|
|
8303
|
-
import { join as
|
|
7274
|
+
import { existsSync as existsSync27, mkdirSync as mkdirSync14, readdirSync as readdirSync7, readFileSync as readFileSync20, rmSync as rmSync3, writeFileSync as writeFileSync17 } from "fs";
|
|
7275
|
+
import { join as join29 } from "path";
|
|
8304
7276
|
var REQUIRED_FIELDS = [
|
|
8305
7277
|
"$schema",
|
|
8306
7278
|
"id",
|
|
@@ -8360,20 +7332,20 @@ function validateCustomTheme(theme) {
|
|
|
8360
7332
|
};
|
|
8361
7333
|
}
|
|
8362
7334
|
function createTheme(projectRoot, id, name) {
|
|
8363
|
-
const customThemesDir =
|
|
8364
|
-
const themePath =
|
|
8365
|
-
const howToPath =
|
|
8366
|
-
|
|
8367
|
-
if (
|
|
7335
|
+
const customThemesDir = join29(projectRoot, ".decantr", "custom", "themes");
|
|
7336
|
+
const themePath = join29(customThemesDir, `${id}.json`);
|
|
7337
|
+
const howToPath = join29(customThemesDir, "how-to-theme.md");
|
|
7338
|
+
mkdirSync14(customThemesDir, { recursive: true });
|
|
7339
|
+
if (existsSync27(themePath)) {
|
|
8368
7340
|
return {
|
|
8369
7341
|
success: false,
|
|
8370
7342
|
error: `Theme "${id}" already exists at ${themePath}`
|
|
8371
7343
|
};
|
|
8372
7344
|
}
|
|
8373
7345
|
const skeleton = getThemeSkeleton(id, name);
|
|
8374
|
-
|
|
8375
|
-
if (!
|
|
8376
|
-
|
|
7346
|
+
writeFileSync17(themePath, JSON.stringify(skeleton, null, 2));
|
|
7347
|
+
if (!existsSync27(howToPath)) {
|
|
7348
|
+
writeFileSync17(howToPath, getHowToThemeDoc());
|
|
8377
7349
|
}
|
|
8378
7350
|
return {
|
|
8379
7351
|
success: true,
|
|
@@ -8381,17 +7353,17 @@ function createTheme(projectRoot, id, name) {
|
|
|
8381
7353
|
};
|
|
8382
7354
|
}
|
|
8383
7355
|
function listCustomThemes(projectRoot) {
|
|
8384
|
-
const customThemesDir =
|
|
8385
|
-
if (!
|
|
7356
|
+
const customThemesDir = join29(projectRoot, ".decantr", "custom", "themes");
|
|
7357
|
+
if (!existsSync27(customThemesDir)) {
|
|
8386
7358
|
return [];
|
|
8387
7359
|
}
|
|
8388
7360
|
const themes = [];
|
|
8389
7361
|
try {
|
|
8390
|
-
const files =
|
|
7362
|
+
const files = readdirSync7(customThemesDir).filter((f) => f.endsWith(".json"));
|
|
8391
7363
|
for (const file of files) {
|
|
8392
|
-
const filePath =
|
|
7364
|
+
const filePath = join29(customThemesDir, file);
|
|
8393
7365
|
try {
|
|
8394
|
-
const data = JSON.parse(
|
|
7366
|
+
const data = JSON.parse(readFileSync20(filePath, "utf-8"));
|
|
8395
7367
|
themes.push({
|
|
8396
7368
|
id: data.id || file.replace(".json", ""),
|
|
8397
7369
|
name: data.name || data.id,
|
|
@@ -8406,8 +7378,8 @@ function listCustomThemes(projectRoot) {
|
|
|
8406
7378
|
return themes;
|
|
8407
7379
|
}
|
|
8408
7380
|
function deleteTheme(projectRoot, id) {
|
|
8409
|
-
const themePath =
|
|
8410
|
-
if (!
|
|
7381
|
+
const themePath = join29(projectRoot, ".decantr", "custom", "themes", `${id}.json`);
|
|
7382
|
+
if (!existsSync27(themePath)) {
|
|
8411
7383
|
return {
|
|
8412
7384
|
success: false,
|
|
8413
7385
|
error: `Theme "${id}" not found at ${themePath}`
|
|
@@ -8424,7 +7396,7 @@ function deleteTheme(projectRoot, id) {
|
|
|
8424
7396
|
}
|
|
8425
7397
|
}
|
|
8426
7398
|
function importTheme(projectRoot, sourcePath) {
|
|
8427
|
-
if (!
|
|
7399
|
+
if (!existsSync27(sourcePath)) {
|
|
8428
7400
|
return {
|
|
8429
7401
|
success: false,
|
|
8430
7402
|
errors: [`Source file not found: ${sourcePath}`]
|
|
@@ -8432,7 +7404,7 @@ function importTheme(projectRoot, sourcePath) {
|
|
|
8432
7404
|
}
|
|
8433
7405
|
let theme;
|
|
8434
7406
|
try {
|
|
8435
|
-
theme = JSON.parse(
|
|
7407
|
+
theme = JSON.parse(readFileSync20(sourcePath, "utf-8"));
|
|
8436
7408
|
} catch (e) {
|
|
8437
7409
|
return {
|
|
8438
7410
|
success: false,
|
|
@@ -8448,14 +7420,14 @@ function importTheme(projectRoot, sourcePath) {
|
|
|
8448
7420
|
}
|
|
8449
7421
|
theme.source = "custom";
|
|
8450
7422
|
const id = theme.id;
|
|
8451
|
-
const customThemesDir =
|
|
8452
|
-
const destPath =
|
|
8453
|
-
|
|
8454
|
-
const howToPath =
|
|
8455
|
-
if (!
|
|
8456
|
-
|
|
8457
|
-
}
|
|
8458
|
-
|
|
7423
|
+
const customThemesDir = join29(projectRoot, ".decantr", "custom", "themes");
|
|
7424
|
+
const destPath = join29(customThemesDir, `${id}.json`);
|
|
7425
|
+
mkdirSync14(customThemesDir, { recursive: true });
|
|
7426
|
+
const howToPath = join29(customThemesDir, "how-to-theme.md");
|
|
7427
|
+
if (!existsSync27(howToPath)) {
|
|
7428
|
+
writeFileSync17(howToPath, getHowToThemeDoc());
|
|
7429
|
+
}
|
|
7430
|
+
writeFileSync17(destPath, JSON.stringify(theme, null, 2));
|
|
8459
7431
|
return {
|
|
8460
7432
|
success: true,
|
|
8461
7433
|
path: destPath
|
|
@@ -9002,18 +7974,18 @@ function extractHostedAssetPaths(indexHtml) {
|
|
|
9002
7974
|
return [...assetPaths];
|
|
9003
7975
|
}
|
|
9004
7976
|
function readHostedDistSnapshot(distPath) {
|
|
9005
|
-
const resolvedDistPath = distPath ? resolveUserPath(distPath) :
|
|
9006
|
-
const indexPath =
|
|
9007
|
-
if (!
|
|
7977
|
+
const resolvedDistPath = distPath ? resolveUserPath(distPath) : join30(process.cwd(), "dist");
|
|
7978
|
+
const indexPath = join30(resolvedDistPath, "index.html");
|
|
7979
|
+
if (!existsSync28(indexPath)) {
|
|
9008
7980
|
return void 0;
|
|
9009
7981
|
}
|
|
9010
|
-
const indexHtml =
|
|
7982
|
+
const indexHtml = readFileSync21(indexPath, "utf-8");
|
|
9011
7983
|
const assetPaths = extractHostedAssetPaths(indexHtml);
|
|
9012
7984
|
const assets = {};
|
|
9013
7985
|
for (const assetPath of assetPaths) {
|
|
9014
|
-
const assetFilePath =
|
|
9015
|
-
if (
|
|
9016
|
-
assets[assetPath] =
|
|
7986
|
+
const assetFilePath = join30(resolvedDistPath, assetPath.replace(/^[/\\]+/, ""));
|
|
7987
|
+
if (existsSync28(assetFilePath)) {
|
|
7988
|
+
assets[assetPath] = readFileSync21(assetFilePath, "utf-8");
|
|
9017
7989
|
}
|
|
9018
7990
|
}
|
|
9019
7991
|
return {
|
|
@@ -9028,7 +8000,7 @@ function isHostedSourceSnapshotFile(path) {
|
|
|
9028
8000
|
function readHostedSourceSnapshot(sourcePath) {
|
|
9029
8001
|
if (!sourcePath) return void 0;
|
|
9030
8002
|
const resolvedSourcePath = resolveUserPath(sourcePath);
|
|
9031
|
-
if (!
|
|
8003
|
+
if (!existsSync28(resolvedSourcePath)) {
|
|
9032
8004
|
return void 0;
|
|
9033
8005
|
}
|
|
9034
8006
|
const files = {};
|
|
@@ -9040,19 +8012,19 @@ function readHostedSourceSnapshot(sourcePath) {
|
|
|
9040
8012
|
"build",
|
|
9041
8013
|
"coverage"
|
|
9042
8014
|
]);
|
|
9043
|
-
const rootPrefix =
|
|
8015
|
+
const rootPrefix = basename2(resolvedSourcePath);
|
|
9044
8016
|
const walk = (absoluteDir, relativeDir) => {
|
|
9045
|
-
for (const entry of
|
|
8017
|
+
for (const entry of readdirSync8(absoluteDir, { withFileTypes: true })) {
|
|
9046
8018
|
if (ignoredDirNames.has(entry.name)) continue;
|
|
9047
|
-
const absolutePath =
|
|
9048
|
-
const relativePath =
|
|
8019
|
+
const absolutePath = join30(absoluteDir, entry.name);
|
|
8020
|
+
const relativePath = join30(relativeDir, entry.name).replace(/\\/g, "/");
|
|
9049
8021
|
if (entry.isDirectory()) {
|
|
9050
8022
|
walk(absolutePath, relativePath);
|
|
9051
8023
|
continue;
|
|
9052
8024
|
}
|
|
9053
8025
|
if (!entry.isFile()) continue;
|
|
9054
8026
|
if (!isHostedSourceSnapshotFile(relativePath)) continue;
|
|
9055
|
-
files[relativePath] =
|
|
8027
|
+
files[relativePath] = readFileSync21(absolutePath, "utf-8");
|
|
9056
8028
|
}
|
|
9057
8029
|
};
|
|
9058
8030
|
walk(resolvedSourcePath, rootPrefix);
|
|
@@ -9245,25 +8217,25 @@ async function printHostedExecutionPackBundle(essencePath, namespace, jsonOutput
|
|
|
9245
8217
|
}
|
|
9246
8218
|
async function compileHostedExecutionPackBundle(essencePath, namespace) {
|
|
9247
8219
|
const client = getPublicAPIClient();
|
|
9248
|
-
const resolvedPath = essencePath ? resolveUserPath(essencePath) :
|
|
9249
|
-
if (!
|
|
8220
|
+
const resolvedPath = essencePath ? resolveUserPath(essencePath) : join30(process.cwd(), "decantr.essence.json");
|
|
8221
|
+
if (!existsSync28(resolvedPath)) {
|
|
9250
8222
|
throw new Error(`Essence file not found at ${resolvedPath}`);
|
|
9251
8223
|
}
|
|
9252
|
-
const essence = JSON.parse(
|
|
8224
|
+
const essence = JSON.parse(readFileSync21(resolvedPath, "utf-8"));
|
|
9253
8225
|
const bundle = await client.compileExecutionPacks(essence, namespace ? { namespace } : void 0);
|
|
9254
|
-
const contextDir =
|
|
8226
|
+
const contextDir = join30(dirname6(resolvedPath), ".decantr", "context");
|
|
9255
8227
|
return { resolvedPath, bundle, contextDir };
|
|
9256
8228
|
}
|
|
9257
8229
|
function writeHostedExecutionPackContextArtifacts(contextDir, bundle) {
|
|
9258
|
-
|
|
8230
|
+
mkdirSync15(contextDir, { recursive: true });
|
|
9259
8231
|
return writeExecutionPackBundleArtifacts(contextDir, bundle);
|
|
9260
8232
|
}
|
|
9261
8233
|
function resolvePagePackIdForRoute(essencePath, route) {
|
|
9262
|
-
if (!
|
|
8234
|
+
if (!existsSync28(essencePath)) {
|
|
9263
8235
|
throw new Error(`Essence file not found at ${essencePath}`);
|
|
9264
8236
|
}
|
|
9265
|
-
const essence = JSON.parse(
|
|
9266
|
-
if (!
|
|
8237
|
+
const essence = JSON.parse(readFileSync21(essencePath, "utf-8"));
|
|
8238
|
+
if (!isV48(essence)) {
|
|
9267
8239
|
throw new Error("Route-based pack resolution requires Essence v4.0.0.");
|
|
9268
8240
|
}
|
|
9269
8241
|
const target = essence.blueprint.routes?.[route];
|
|
@@ -9277,14 +8249,14 @@ function resolvePagePackIdForRoute(essencePath, route) {
|
|
|
9277
8249
|
}
|
|
9278
8250
|
async function printHostedSelectedExecutionPack(packType, id, essencePath, namespace, jsonOutput = false, writeContext = false) {
|
|
9279
8251
|
const client = getPublicAPIClient();
|
|
9280
|
-
const resolvedPath = essencePath ? resolveUserPath(essencePath) :
|
|
9281
|
-
if (!
|
|
8252
|
+
const resolvedPath = essencePath ? resolveUserPath(essencePath) : join30(process.cwd(), "decantr.essence.json");
|
|
8253
|
+
if (!existsSync28(resolvedPath)) {
|
|
9282
8254
|
throw new Error(`Essence file not found at ${resolvedPath}`);
|
|
9283
8255
|
}
|
|
9284
8256
|
if ((packType === "section" || packType === "page" || packType === "mutation") && !id) {
|
|
9285
8257
|
throw new Error(`Pack type "${packType}" requires an id.`);
|
|
9286
8258
|
}
|
|
9287
|
-
const essence = JSON.parse(
|
|
8259
|
+
const essence = JSON.parse(readFileSync21(resolvedPath, "utf-8"));
|
|
9288
8260
|
const selected = await client.selectExecutionPack(
|
|
9289
8261
|
{
|
|
9290
8262
|
essence,
|
|
@@ -9295,17 +8267,17 @@ async function printHostedSelectedExecutionPack(packType, id, essencePath, names
|
|
|
9295
8267
|
);
|
|
9296
8268
|
let writtenContextDir = null;
|
|
9297
8269
|
if (writeContext) {
|
|
9298
|
-
const contextDir =
|
|
9299
|
-
|
|
9300
|
-
|
|
9301
|
-
|
|
8270
|
+
const contextDir = join30(dirname6(resolvedPath), ".decantr", "context");
|
|
8271
|
+
mkdirSync15(contextDir, { recursive: true });
|
|
8272
|
+
writeFileSync18(
|
|
8273
|
+
join30(contextDir, "pack-manifest.json"),
|
|
9302
8274
|
JSON.stringify(selected.manifest, null, 2) + "\n"
|
|
9303
8275
|
);
|
|
9304
8276
|
const manifestEntry = selected.selector.packType === "scaffold" ? selected.manifest.scaffold : selected.selector.packType === "review" ? selected.manifest.review : selected.selector.packType === "section" ? selected.manifest.sections.find((entry) => entry.id === selected.selector.id) : selected.selector.packType === "page" ? selected.manifest.pages.find((entry) => entry.id === selected.selector.id) : selected.manifest.mutations.find((entry) => entry.id === selected.selector.id);
|
|
9305
8277
|
const markdownFile = manifestEntry?.markdown ?? `${selected.selector.packType}${selected.selector.id ? `-${selected.selector.id}` : ""}-pack.md`;
|
|
9306
8278
|
const jsonFile = manifestEntry?.json ?? `${selected.selector.packType}${selected.selector.id ? `-${selected.selector.id}` : ""}-pack.json`;
|
|
9307
|
-
|
|
9308
|
-
|
|
8279
|
+
writeFileSync18(join30(contextDir, markdownFile), selected.pack.renderedMarkdown);
|
|
8280
|
+
writeFileSync18(join30(contextDir, jsonFile), JSON.stringify(selected.pack, null, 2) + "\n");
|
|
9309
8281
|
writtenContextDir = contextDir;
|
|
9310
8282
|
}
|
|
9311
8283
|
if (jsonOutput) {
|
|
@@ -9330,20 +8302,20 @@ async function printHostedSelectedExecutionPack(packType, id, essencePath, names
|
|
|
9330
8302
|
}
|
|
9331
8303
|
async function printHostedExecutionPackManifest(essencePath, namespace, jsonOutput = false, writeContext = false) {
|
|
9332
8304
|
const client = getPublicAPIClient();
|
|
9333
|
-
const resolvedPath = essencePath ? resolveUserPath(essencePath) :
|
|
9334
|
-
if (!
|
|
8305
|
+
const resolvedPath = essencePath ? resolveUserPath(essencePath) : join30(process.cwd(), "decantr.essence.json");
|
|
8306
|
+
if (!existsSync28(resolvedPath)) {
|
|
9335
8307
|
throw new Error(`Essence file not found at ${resolvedPath}`);
|
|
9336
8308
|
}
|
|
9337
|
-
const essence = JSON.parse(
|
|
8309
|
+
const essence = JSON.parse(readFileSync21(resolvedPath, "utf-8"));
|
|
9338
8310
|
const manifest = await client.getExecutionPackManifest(
|
|
9339
8311
|
essence,
|
|
9340
8312
|
namespace ? { namespace } : void 0
|
|
9341
8313
|
);
|
|
9342
8314
|
let writtenContextDir = null;
|
|
9343
8315
|
if (writeContext) {
|
|
9344
|
-
const contextDir =
|
|
9345
|
-
|
|
9346
|
-
|
|
8316
|
+
const contextDir = join30(dirname6(resolvedPath), ".decantr", "context");
|
|
8317
|
+
mkdirSync15(contextDir, { recursive: true });
|
|
8318
|
+
writeFileSync18(join30(contextDir, "pack-manifest.json"), JSON.stringify(manifest, null, 2) + "\n");
|
|
9347
8319
|
writtenContextDir = contextDir;
|
|
9348
8320
|
}
|
|
9349
8321
|
if (jsonOutput) {
|
|
@@ -9364,14 +8336,14 @@ async function printHostedExecutionPackManifest(essencePath, namespace, jsonOutp
|
|
|
9364
8336
|
}
|
|
9365
8337
|
}
|
|
9366
8338
|
async function hydrateHostedExecutionPacksIfMissing(projectRoot, namespace = "@official") {
|
|
9367
|
-
const contextDir =
|
|
9368
|
-
const reviewPackPath =
|
|
9369
|
-
const manifestPath =
|
|
9370
|
-
if (
|
|
8339
|
+
const contextDir = join30(projectRoot, ".decantr", "context");
|
|
8340
|
+
const reviewPackPath = join30(contextDir, "review-pack.json");
|
|
8341
|
+
const manifestPath = join30(contextDir, "pack-manifest.json");
|
|
8342
|
+
if (existsSync28(reviewPackPath) && existsSync28(manifestPath)) {
|
|
9371
8343
|
return { attempted: false, hydrated: false };
|
|
9372
8344
|
}
|
|
9373
|
-
const essencePath =
|
|
9374
|
-
if (!
|
|
8345
|
+
const essencePath = join30(projectRoot, "decantr.essence.json");
|
|
8346
|
+
if (!existsSync28(essencePath)) {
|
|
9375
8347
|
return { attempted: false, hydrated: false };
|
|
9376
8348
|
}
|
|
9377
8349
|
const reviewHydration = await hydrateHostedReviewPackIfMissing(projectRoot, namespace);
|
|
@@ -9380,9 +8352,9 @@ async function hydrateHostedExecutionPacksIfMissing(projectRoot, namespace = "@o
|
|
|
9380
8352
|
}
|
|
9381
8353
|
try {
|
|
9382
8354
|
const client = getPublicAPIClient();
|
|
9383
|
-
const essence = JSON.parse(
|
|
8355
|
+
const essence = JSON.parse(readFileSync21(essencePath, "utf-8"));
|
|
9384
8356
|
const bundle = await client.compileExecutionPacks(essence, { namespace });
|
|
9385
|
-
|
|
8357
|
+
mkdirSync15(contextDir, { recursive: true });
|
|
9386
8358
|
writeExecutionPackBundleArtifacts(contextDir, bundle);
|
|
9387
8359
|
return { attempted: true, hydrated: true, scope: "bundle" };
|
|
9388
8360
|
} catch {
|
|
@@ -9390,19 +8362,19 @@ async function hydrateHostedExecutionPacksIfMissing(projectRoot, namespace = "@o
|
|
|
9390
8362
|
}
|
|
9391
8363
|
}
|
|
9392
8364
|
async function hydrateHostedReviewPackIfMissing(projectRoot, namespace = "@official") {
|
|
9393
|
-
const contextDir =
|
|
9394
|
-
const reviewPackPath =
|
|
9395
|
-
const manifestPath =
|
|
9396
|
-
if (
|
|
8365
|
+
const contextDir = join30(projectRoot, ".decantr", "context");
|
|
8366
|
+
const reviewPackPath = join30(contextDir, "review-pack.json");
|
|
8367
|
+
const manifestPath = join30(contextDir, "pack-manifest.json");
|
|
8368
|
+
if (existsSync28(reviewPackPath) && existsSync28(manifestPath)) {
|
|
9397
8369
|
return { attempted: false, hydrated: false };
|
|
9398
8370
|
}
|
|
9399
|
-
const essencePath =
|
|
9400
|
-
if (!
|
|
8371
|
+
const essencePath = join30(projectRoot, "decantr.essence.json");
|
|
8372
|
+
if (!existsSync28(essencePath)) {
|
|
9401
8373
|
return { attempted: false, hydrated: false };
|
|
9402
8374
|
}
|
|
9403
8375
|
try {
|
|
9404
8376
|
const client = getPublicAPIClient();
|
|
9405
|
-
const essence = JSON.parse(
|
|
8377
|
+
const essence = JSON.parse(readFileSync21(essencePath, "utf-8"));
|
|
9406
8378
|
const selected = await client.selectExecutionPack(
|
|
9407
8379
|
{
|
|
9408
8380
|
essence,
|
|
@@ -9410,14 +8382,14 @@ async function hydrateHostedReviewPackIfMissing(projectRoot, namespace = "@offic
|
|
|
9410
8382
|
},
|
|
9411
8383
|
{ namespace }
|
|
9412
8384
|
);
|
|
9413
|
-
|
|
9414
|
-
|
|
9415
|
-
|
|
9416
|
-
|
|
8385
|
+
mkdirSync15(contextDir, { recursive: true });
|
|
8386
|
+
writeFileSync18(join30(contextDir, "review-pack.md"), selected.pack.renderedMarkdown);
|
|
8387
|
+
writeFileSync18(
|
|
8388
|
+
join30(contextDir, "review-pack.json"),
|
|
9417
8389
|
JSON.stringify(selected.pack, null, 2) + "\n"
|
|
9418
8390
|
);
|
|
9419
|
-
if (!
|
|
9420
|
-
|
|
8391
|
+
if (!existsSync28(manifestPath)) {
|
|
8392
|
+
writeFileSync18(manifestPath, JSON.stringify(selected.manifest, null, 2) + "\n");
|
|
9421
8393
|
}
|
|
9422
8394
|
return { attempted: true, hydrated: true, scope: "review" };
|
|
9423
8395
|
} catch {
|
|
@@ -9427,17 +8399,17 @@ async function hydrateHostedReviewPackIfMissing(projectRoot, namespace = "@offic
|
|
|
9427
8399
|
async function printHostedFileCritique(sourcePath, namespace, jsonOutput = false, essencePath, treatmentsPath) {
|
|
9428
8400
|
const client = getPublicAPIClient();
|
|
9429
8401
|
const resolvedSourcePath = resolveUserPath(sourcePath);
|
|
9430
|
-
const resolvedEssencePath = essencePath ? resolveUserPath(essencePath) :
|
|
9431
|
-
const resolvedTreatmentsPath = treatmentsPath ? resolveUserPath(treatmentsPath) :
|
|
9432
|
-
if (!
|
|
8402
|
+
const resolvedEssencePath = essencePath ? resolveUserPath(essencePath) : join30(process.cwd(), "decantr.essence.json");
|
|
8403
|
+
const resolvedTreatmentsPath = treatmentsPath ? resolveUserPath(treatmentsPath) : join30(process.cwd(), "src", "styles", "treatments.css");
|
|
8404
|
+
if (!existsSync28(resolvedSourcePath)) {
|
|
9433
8405
|
throw new Error(`Source file not found at ${resolvedSourcePath}`);
|
|
9434
8406
|
}
|
|
9435
|
-
if (!
|
|
8407
|
+
if (!existsSync28(resolvedEssencePath)) {
|
|
9436
8408
|
throw new Error(`Essence file not found at ${resolvedEssencePath}`);
|
|
9437
8409
|
}
|
|
9438
|
-
const code =
|
|
9439
|
-
const essence = JSON.parse(
|
|
9440
|
-
const treatmentsCss =
|
|
8410
|
+
const code = readFileSync21(resolvedSourcePath, "utf-8");
|
|
8411
|
+
const essence = JSON.parse(readFileSync21(resolvedEssencePath, "utf-8"));
|
|
8412
|
+
const treatmentsCss = existsSync28(resolvedTreatmentsPath) ? readFileSync21(resolvedTreatmentsPath, "utf-8") : void 0;
|
|
9441
8413
|
const report = await client.critiqueFile(
|
|
9442
8414
|
{
|
|
9443
8415
|
essence,
|
|
@@ -9461,11 +8433,11 @@ async function printHostedFileCritique(sourcePath, namespace, jsonOutput = false
|
|
|
9461
8433
|
}
|
|
9462
8434
|
async function printHostedProjectAudit(namespace, jsonOutput = false, essencePath, distPath, sourcesPath) {
|
|
9463
8435
|
const client = getPublicAPIClient();
|
|
9464
|
-
const resolvedEssencePath = essencePath ? resolveUserPath(essencePath) :
|
|
9465
|
-
if (!
|
|
8436
|
+
const resolvedEssencePath = essencePath ? resolveUserPath(essencePath) : join30(process.cwd(), "decantr.essence.json");
|
|
8437
|
+
if (!existsSync28(resolvedEssencePath)) {
|
|
9466
8438
|
throw new Error(`Essence file not found at ${resolvedEssencePath}`);
|
|
9467
8439
|
}
|
|
9468
|
-
const essence = JSON.parse(
|
|
8440
|
+
const essence = JSON.parse(readFileSync21(resolvedEssencePath, "utf-8"));
|
|
9469
8441
|
const dist = readHostedDistSnapshot(distPath);
|
|
9470
8442
|
const sources = readHostedSourceSnapshot(sourcesPath);
|
|
9471
8443
|
const report = await client.auditProject(
|
|
@@ -9483,7 +8455,7 @@ async function printHostedProjectAudit(namespace, jsonOutput = false, essencePat
|
|
|
9483
8455
|
console.log(heading2("Hosted Project Audit"));
|
|
9484
8456
|
console.log(` Essence: ${resolvedEssencePath}`);
|
|
9485
8457
|
console.log(
|
|
9486
|
-
` Dist snapshot: ${dist ? distPath ? resolveUserPath(distPath) :
|
|
8458
|
+
` Dist snapshot: ${dist ? distPath ? resolveUserPath(distPath) : join30(process.cwd(), "dist") : "none"}`
|
|
9487
8459
|
);
|
|
9488
8460
|
console.log(
|
|
9489
8461
|
` Source snapshot: ${sources && sourcesPath ? resolveUserPath(sourcesPath) : "none"}`
|
|
@@ -9605,21 +8577,21 @@ function findPatternCandidateBySlug(candidates, slug) {
|
|
|
9605
8577
|
function readSuggestCodeContext(projectRoot, route, file) {
|
|
9606
8578
|
const pieces = [];
|
|
9607
8579
|
if (file) {
|
|
9608
|
-
const resolved = isAbsolute3(file) ? file :
|
|
9609
|
-
if (
|
|
9610
|
-
pieces.push(
|
|
8580
|
+
const resolved = isAbsolute3(file) ? file : join30(projectRoot, file);
|
|
8581
|
+
if (existsSync28(resolved)) {
|
|
8582
|
+
pieces.push(readFileSync21(resolved, "utf-8"));
|
|
9611
8583
|
}
|
|
9612
8584
|
}
|
|
9613
8585
|
if (route) {
|
|
9614
|
-
const analysisPath =
|
|
9615
|
-
if (
|
|
8586
|
+
const analysisPath = join30(projectRoot, ".decantr", "analysis.json");
|
|
8587
|
+
if (existsSync28(analysisPath)) {
|
|
9616
8588
|
try {
|
|
9617
|
-
const analysis = JSON.parse(
|
|
8589
|
+
const analysis = JSON.parse(readFileSync21(analysisPath, "utf-8"));
|
|
9618
8590
|
const routeEntry = analysis.routes?.routes?.find((entry) => entry.path === route);
|
|
9619
8591
|
if (routeEntry?.file) {
|
|
9620
|
-
const resolved =
|
|
9621
|
-
if (
|
|
9622
|
-
pieces.push(
|
|
8592
|
+
const resolved = join30(projectRoot, routeEntry.file);
|
|
8593
|
+
if (existsSync28(resolved)) {
|
|
8594
|
+
pieces.push(readFileSync21(resolved, "utf-8"));
|
|
9623
8595
|
}
|
|
9624
8596
|
}
|
|
9625
8597
|
} catch {
|
|
@@ -9703,7 +8675,7 @@ async function cmdSuggest(query, options = {}) {
|
|
|
9703
8675
|
}
|
|
9704
8676
|
}
|
|
9705
8677
|
const registryClient = new RegistryClient({
|
|
9706
|
-
cacheDir:
|
|
8678
|
+
cacheDir: join30(projectRoot, ".decantr", "cache")
|
|
9707
8679
|
});
|
|
9708
8680
|
const code = options.fromCode || options.file ? readSuggestCodeContext(projectRoot, options.route, options.file) : "";
|
|
9709
8681
|
const localMatches = localPatternMatches(projectRoot, [query, code].filter(Boolean).join("\n"));
|
|
@@ -9790,7 +8762,7 @@ async function cmdGet(type, id) {
|
|
|
9790
8762
|
}
|
|
9791
8763
|
const apiType = CONTENT_TYPE_TO_API_CONTENT_TYPE3[type];
|
|
9792
8764
|
const registryClient = new RegistryClient({
|
|
9793
|
-
cacheDir:
|
|
8765
|
+
cacheDir: join30(process.cwd(), ".decantr", "cache")
|
|
9794
8766
|
});
|
|
9795
8767
|
const result = await registryClient.fetchContentItem(apiType, id);
|
|
9796
8768
|
if (result) {
|
|
@@ -9807,10 +8779,10 @@ async function cmdGet(type, id) {
|
|
|
9807
8779
|
return;
|
|
9808
8780
|
}
|
|
9809
8781
|
async function cmdValidate(path) {
|
|
9810
|
-
const essencePath = path ||
|
|
8782
|
+
const essencePath = path || join30(process.cwd(), "decantr.essence.json");
|
|
9811
8783
|
let raw;
|
|
9812
8784
|
try {
|
|
9813
|
-
raw =
|
|
8785
|
+
raw = readFileSync21(essencePath, "utf-8");
|
|
9814
8786
|
} catch {
|
|
9815
8787
|
console.error(error2(`Could not read ${essencePath}`));
|
|
9816
8788
|
process.exitCode = 1;
|
|
@@ -9824,7 +8796,7 @@ async function cmdValidate(path) {
|
|
|
9824
8796
|
process.exitCode = 1;
|
|
9825
8797
|
return;
|
|
9826
8798
|
}
|
|
9827
|
-
const detectedVersion =
|
|
8799
|
+
const detectedVersion = isV48(essence) ? "v4" : "legacy";
|
|
9828
8800
|
console.log(`${DIM16}Detected essence version: ${detectedVersion}${RESET16}`);
|
|
9829
8801
|
const result = validateEssence2(essence);
|
|
9830
8802
|
if (result.valid) {
|
|
@@ -9877,7 +8849,7 @@ async function cmdList(type, sort, recommended, intelligenceSource, blueprintSet
|
|
|
9877
8849
|
return;
|
|
9878
8850
|
}
|
|
9879
8851
|
const registryClient = new RegistryClient({
|
|
9880
|
-
cacheDir:
|
|
8852
|
+
cacheDir: join30(process.cwd(), ".decantr", "cache")
|
|
9881
8853
|
});
|
|
9882
8854
|
const result = await registryClient.fetchContentList(
|
|
9883
8855
|
type,
|
|
@@ -9960,10 +8932,10 @@ ${CYAN9}Telemetry enabled.${RESET16} Decantr will send privacy-filtered CLI prod
|
|
|
9960
8932
|
}
|
|
9961
8933
|
function readCliPackageVersion2() {
|
|
9962
8934
|
const here = dirname6(fileURLToPath3(import.meta.url));
|
|
9963
|
-
const candidates = [
|
|
8935
|
+
const candidates = [join30(here, "..", "package.json"), join30(here, "..", "..", "package.json")];
|
|
9964
8936
|
for (const candidate of candidates) {
|
|
9965
8937
|
try {
|
|
9966
|
-
const pkg = JSON.parse(
|
|
8938
|
+
const pkg = JSON.parse(readFileSync21(candidate, "utf-8"));
|
|
9967
8939
|
if (pkg.version) return pkg.version;
|
|
9968
8940
|
} catch {
|
|
9969
8941
|
}
|
|
@@ -9974,19 +8946,19 @@ function timestampForFile() {
|
|
|
9974
8946
|
return (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
9975
8947
|
}
|
|
9976
8948
|
function backupExistingEssence(projectRoot, label) {
|
|
9977
|
-
const essencePath =
|
|
9978
|
-
if (!
|
|
9979
|
-
const backupPath =
|
|
8949
|
+
const essencePath = join30(projectRoot, "decantr.essence.json");
|
|
8950
|
+
if (!existsSync28(essencePath)) return null;
|
|
8951
|
+
const backupPath = join30(
|
|
9980
8952
|
projectRoot,
|
|
9981
8953
|
`decantr.essence.${label}.${timestampForFile()}.backup.json`
|
|
9982
8954
|
);
|
|
9983
|
-
|
|
8955
|
+
writeFileSync18(backupPath, readFileSync21(essencePath, "utf-8"), "utf-8");
|
|
9984
8956
|
return backupPath;
|
|
9985
8957
|
}
|
|
9986
8958
|
function writeBrownfieldProjectJson(input) {
|
|
9987
|
-
const decantrDir =
|
|
9988
|
-
|
|
9989
|
-
|
|
8959
|
+
const decantrDir = join30(input.projectRoot, ".decantr");
|
|
8960
|
+
mkdirSync15(join30(decantrDir, "context"), { recursive: true });
|
|
8961
|
+
mkdirSync15(join30(decantrDir, "cache"), { recursive: true });
|
|
9990
8962
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
9991
8963
|
const projectJson = {
|
|
9992
8964
|
detected: {
|
|
@@ -10027,7 +8999,7 @@ function writeBrownfieldProjectJson(input) {
|
|
|
10027
8999
|
}
|
|
10028
9000
|
}
|
|
10029
9001
|
};
|
|
10030
|
-
|
|
9002
|
+
writeFileSync18(join30(decantrDir, "project.json"), JSON.stringify(projectJson, null, 2) + "\n");
|
|
10031
9003
|
}
|
|
10032
9004
|
async function applyAcceptedBrownfieldProposal(input) {
|
|
10033
9005
|
const proposal = readBrownfieldProposal(input.projectRoot);
|
|
@@ -10043,8 +9015,8 @@ async function applyAcceptedBrownfieldProposal(input) {
|
|
|
10043
9015
|
process.exitCode = 1;
|
|
10044
9016
|
return;
|
|
10045
9017
|
}
|
|
10046
|
-
const essencePath =
|
|
10047
|
-
const hasEssence =
|
|
9018
|
+
const essencePath = join30(input.projectRoot, "decantr.essence.json");
|
|
9019
|
+
const hasEssence = existsSync28(essencePath);
|
|
10048
9020
|
let essence;
|
|
10049
9021
|
let backupPath = null;
|
|
10050
9022
|
if (input.mode === "accept" && hasEssence) {
|
|
@@ -10058,8 +9030,8 @@ async function applyAcceptedBrownfieldProposal(input) {
|
|
|
10058
9030
|
return;
|
|
10059
9031
|
}
|
|
10060
9032
|
if (input.mode === "merge" && hasEssence) {
|
|
10061
|
-
const existing = JSON.parse(
|
|
10062
|
-
if (!
|
|
9033
|
+
const existing = JSON.parse(readFileSync21(essencePath, "utf-8"));
|
|
9034
|
+
if (!isV48(existing)) {
|
|
10063
9035
|
console.log(
|
|
10064
9036
|
error2(
|
|
10065
9037
|
"Existing essence is not v4. Run `decantr migrate --to v4` before merging a brownfield proposal."
|
|
@@ -10095,9 +9067,9 @@ async function applyAcceptedBrownfieldProposal(input) {
|
|
|
10095
9067
|
assistantBridge: input.assistantBridge,
|
|
10096
9068
|
mode: input.mode
|
|
10097
9069
|
});
|
|
10098
|
-
|
|
9070
|
+
writeFileSync18(essencePath, JSON.stringify(essence, null, 2) + "\n", "utf-8");
|
|
10099
9071
|
const registryClient = new RegistryClient({
|
|
10100
|
-
cacheDir:
|
|
9072
|
+
cacheDir: join30(input.projectRoot, ".decantr", "cache"),
|
|
10101
9073
|
offline: true,
|
|
10102
9074
|
projectRoot: input.projectRoot
|
|
10103
9075
|
});
|
|
@@ -10124,7 +9096,7 @@ async function applyAcceptedBrownfieldProposal(input) {
|
|
|
10124
9096
|
}
|
|
10125
9097
|
const appliedRuleFiles = input.assistantBridge === "apply" ? applyAssistantBridge(input.projectRoot, input.detected) : [];
|
|
10126
9098
|
console.log(success3("\nBrownfield proposal accepted.\n"));
|
|
10127
|
-
const projectLabel = input.workspaceInfo.appRoot !== input.workspaceInfo.workspaceRoot ?
|
|
9099
|
+
const projectLabel = input.workspaceInfo.appRoot !== input.workspaceInfo.workspaceRoot ? relative6(input.workspaceInfo.workspaceRoot, input.workspaceInfo.appRoot).replace(/\\/g, "/") : void 0;
|
|
10128
9100
|
console.log(" Files created/updated:");
|
|
10129
9101
|
console.log(
|
|
10130
9102
|
` ${cyan3(displayProjectPath(input.workspaceInfo, "decantr.essence.json"))} Observed brownfield contract`
|
|
@@ -10276,7 +9248,7 @@ async function cmdInit(args) {
|
|
|
10276
9248
|
}
|
|
10277
9249
|
}
|
|
10278
9250
|
const registryClient = new RegistryClient({
|
|
10279
|
-
cacheDir:
|
|
9251
|
+
cacheDir: join30(projectRoot, ".decantr", "cache"),
|
|
10280
9252
|
apiUrl: args.registry,
|
|
10281
9253
|
offline: args.offline,
|
|
10282
9254
|
projectRoot
|
|
@@ -10608,7 +9580,7 @@ ${YELLOW12}You're offline. Scaffolding Decantr default.${RESET16}`);
|
|
|
10608
9580
|
if (appliedRuleFiles.length > 0) {
|
|
10609
9581
|
console.log(` ${dim3(`Rule bridge applied: ${appliedRuleFiles.join(", ")}`)}`);
|
|
10610
9582
|
}
|
|
10611
|
-
if (!
|
|
9583
|
+
if (!existsSync28(join30(projectRoot, "package.json"))) {
|
|
10612
9584
|
console.log("");
|
|
10613
9585
|
console.log(
|
|
10614
9586
|
dim3(` Note: ${cyan3("decantr init")} created Decantr contract/context files only.`)
|
|
@@ -10619,7 +9591,7 @@ ${YELLOW12}You're offline. Scaffolding Decantr default.${RESET16}`);
|
|
|
10619
9591
|
)
|
|
10620
9592
|
);
|
|
10621
9593
|
}
|
|
10622
|
-
const hasCompiledPacks =
|
|
9594
|
+
const hasCompiledPacks = existsSync28(join30(projectRoot, ".decantr", "context", "scaffold-pack.md"));
|
|
10623
9595
|
console.log("");
|
|
10624
9596
|
console.log(" Next steps:");
|
|
10625
9597
|
if (hasCompiledPacks) {
|
|
@@ -10659,7 +9631,7 @@ ${YELLOW12}You're offline. Scaffolding Decantr default.${RESET16}`);
|
|
|
10659
9631
|
console.log(` ${cyan3("decantr upgrade")} Update to latest patterns`);
|
|
10660
9632
|
console.log(` ${cyan3("decantr check")} Detect drift issues`);
|
|
10661
9633
|
console.log(` ${cyan3("decantr migrate --to v4")} Migrate older essence files to v4`);
|
|
10662
|
-
const essenceContent =
|
|
9634
|
+
const essenceContent = readFileSync21(result.essencePath, "utf-8");
|
|
10663
9635
|
const essence = JSON.parse(essenceContent);
|
|
10664
9636
|
const validation = validateEssence2(essence);
|
|
10665
9637
|
if (!validation.valid) {
|
|
@@ -10668,7 +9640,7 @@ Validation warnings: ${validation.errors.join(", ")}`));
|
|
|
10668
9640
|
}
|
|
10669
9641
|
console.log("");
|
|
10670
9642
|
let promptPages;
|
|
10671
|
-
if (
|
|
9643
|
+
if (isV48(essence)) {
|
|
10672
9644
|
const allPages = essence.blueprint.sections.flatMap(
|
|
10673
9645
|
(s) => s.pages.map((p) => ({ ...p, _sectionId: s.id, _shell: s.shell }))
|
|
10674
9646
|
);
|
|
@@ -10714,25 +9686,25 @@ Validation warnings: ${validation.errors.join(", ")}`));
|
|
|
10714
9686
|
}
|
|
10715
9687
|
}
|
|
10716
9688
|
async function cmdStatus(projectRoot = process.cwd()) {
|
|
10717
|
-
const essencePath =
|
|
10718
|
-
const projectJsonPath =
|
|
9689
|
+
const essencePath = join30(projectRoot, "decantr.essence.json");
|
|
9690
|
+
const projectJsonPath = join30(projectRoot, ".decantr", "project.json");
|
|
10719
9691
|
console.log(heading2("Decantr Project Status"));
|
|
10720
|
-
if (!
|
|
9692
|
+
if (!existsSync28(essencePath)) {
|
|
10721
9693
|
console.log(`${RED12}No decantr.essence.json found.${RESET16}`);
|
|
10722
9694
|
console.log(dim3('Run "decantr init" to create one.'));
|
|
10723
9695
|
return;
|
|
10724
9696
|
}
|
|
10725
9697
|
try {
|
|
10726
|
-
const essence = JSON.parse(
|
|
9698
|
+
const essence = JSON.parse(readFileSync21(essencePath, "utf-8"));
|
|
10727
9699
|
const validation = validateEssence2(essence);
|
|
10728
|
-
const essenceVersion =
|
|
9700
|
+
const essenceVersion = isV48(essence) ? "v4" : "legacy";
|
|
10729
9701
|
console.log(`${BOLD9}Essence:${RESET16}`);
|
|
10730
9702
|
if (validation.valid) {
|
|
10731
9703
|
console.log(` ${GREEN16}Valid${RESET16} (${essenceVersion})`);
|
|
10732
9704
|
} else {
|
|
10733
9705
|
console.log(` ${RED12}Invalid: ${validation.errors.join(", ")}${RESET16}`);
|
|
10734
9706
|
}
|
|
10735
|
-
if (
|
|
9707
|
+
if (isV48(essence)) {
|
|
10736
9708
|
const v4 = essence;
|
|
10737
9709
|
const sections = v4.blueprint.sections;
|
|
10738
9710
|
const flatPages = sections.flatMap((section) => section.pages ?? []);
|
|
@@ -10771,9 +9743,9 @@ async function cmdStatus(projectRoot = process.cwd()) {
|
|
|
10771
9743
|
}
|
|
10772
9744
|
console.log("");
|
|
10773
9745
|
console.log(`${BOLD9}Sync Status:${RESET16}`);
|
|
10774
|
-
if (
|
|
9746
|
+
if (existsSync28(projectJsonPath)) {
|
|
10775
9747
|
try {
|
|
10776
|
-
const projectJson = JSON.parse(
|
|
9748
|
+
const projectJson = JSON.parse(readFileSync21(projectJsonPath, "utf-8"));
|
|
10777
9749
|
const syncStatus = projectJson.sync?.status || "unknown";
|
|
10778
9750
|
const lastSync = projectJson.sync?.lastSync || "never";
|
|
10779
9751
|
const source = projectJson.sync?.registrySource || "unknown";
|
|
@@ -10791,7 +9763,7 @@ async function cmdStatus(projectRoot = process.cwd()) {
|
|
|
10791
9763
|
}
|
|
10792
9764
|
async function cmdSync() {
|
|
10793
9765
|
const projectRoot = process.cwd();
|
|
10794
|
-
const cacheDir =
|
|
9766
|
+
const cacheDir = join30(projectRoot, ".decantr", "cache");
|
|
10795
9767
|
console.log(heading2("Syncing registry content..."));
|
|
10796
9768
|
const result = await syncRegistry(cacheDir);
|
|
10797
9769
|
if (result.synced.length > 0) {
|
|
@@ -10985,14 +9957,14 @@ ${BOLD9}Examples:${RESET16}
|
|
|
10985
9957
|
process.exitCode = 1;
|
|
10986
9958
|
return;
|
|
10987
9959
|
}
|
|
10988
|
-
const themePath =
|
|
10989
|
-
if (!
|
|
9960
|
+
const themePath = join30(projectRoot, ".decantr", "custom", "themes", `${name}.json`);
|
|
9961
|
+
if (!existsSync28(themePath)) {
|
|
10990
9962
|
console.error(error2(`Theme "${name}" not found at ${themePath}`));
|
|
10991
9963
|
process.exitCode = 1;
|
|
10992
9964
|
return;
|
|
10993
9965
|
}
|
|
10994
9966
|
try {
|
|
10995
|
-
const theme = JSON.parse(
|
|
9967
|
+
const theme = JSON.parse(readFileSync21(themePath, "utf-8"));
|
|
10996
9968
|
const result = validateCustomTheme(theme);
|
|
10997
9969
|
if (result.valid) {
|
|
10998
9970
|
console.log(success3(`Custom theme "${name}" is valid`));
|
|
@@ -11124,13 +10096,42 @@ function withProject(command, projectArg) {
|
|
|
11124
10096
|
return projectArg ? `${command} --project ${projectArg}` : command;
|
|
11125
10097
|
}
|
|
11126
10098
|
function displayProjectPath(workspaceInfo, projectPath) {
|
|
11127
|
-
const absolutePath =
|
|
11128
|
-
const relativePath =
|
|
10099
|
+
const absolutePath = join30(workspaceInfo.appRoot, projectPath);
|
|
10100
|
+
const relativePath = relative6(workspaceInfo.cwd, absolutePath).replace(/\\/g, "/");
|
|
11129
10101
|
if (relativePath && !relativePath.startsWith("..") && !isAbsolute3(relativePath)) {
|
|
11130
10102
|
return relativePath;
|
|
11131
10103
|
}
|
|
11132
10104
|
return absolutePath;
|
|
11133
10105
|
}
|
|
10106
|
+
function projectRelativeGraphPath(projectRoot, filePath) {
|
|
10107
|
+
const relativePath = relative6(projectRoot, isAbsolute3(filePath) ? filePath : resolve4(filePath));
|
|
10108
|
+
if (relativePath && !relativePath.startsWith("..") && !isAbsolute3(relativePath)) {
|
|
10109
|
+
return relativePath.replace(/\\/g, "/");
|
|
10110
|
+
}
|
|
10111
|
+
return null;
|
|
10112
|
+
}
|
|
10113
|
+
function graphSourceNodeIdForTaskFile(projectRoot, snapshot, filePath) {
|
|
10114
|
+
const trimmed = filePath.trim();
|
|
10115
|
+
if (!trimmed) return null;
|
|
10116
|
+
if (trimmed.startsWith("src:") && snapshot.nodes.some((node) => node.id === trimmed)) {
|
|
10117
|
+
return trimmed;
|
|
10118
|
+
}
|
|
10119
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
10120
|
+
candidates.add(trimmed.replace(/\\/g, "/").replace(/^\.\//, ""));
|
|
10121
|
+
const cwdRelative = projectRelativeGraphPath(projectRoot, trimmed);
|
|
10122
|
+
if (cwdRelative) candidates.add(cwdRelative);
|
|
10123
|
+
const projectRelative = projectRelativeGraphPath(projectRoot, join30(projectRoot, trimmed));
|
|
10124
|
+
if (projectRelative) candidates.add(projectRelative);
|
|
10125
|
+
for (const candidate of candidates) {
|
|
10126
|
+
const nodeId = `src:${candidate}`;
|
|
10127
|
+
if (snapshot.nodes.some((node) => node.id === nodeId)) return nodeId;
|
|
10128
|
+
}
|
|
10129
|
+
return snapshot.nodes.find((node) => {
|
|
10130
|
+
if (node.type !== "SourceArtifact") return false;
|
|
10131
|
+
const path = graphPayloadString(node.payload, "path");
|
|
10132
|
+
return Boolean(path && (path === trimmed || candidates.has(path)));
|
|
10133
|
+
})?.id ?? null;
|
|
10134
|
+
}
|
|
11134
10135
|
function stripProjectArgs(args, startIndex = 1) {
|
|
11135
10136
|
const stripped = [args[0]];
|
|
11136
10137
|
for (let index = startIndex; index < args.length; index += 1) {
|
|
@@ -11193,7 +10194,7 @@ function printWorkspaceProjectSelection(workspaceInfo, commandName = "command")
|
|
|
11193
10194
|
function printMonorepoSetupGuidance(workspaceInfo) {
|
|
11194
10195
|
const candidate = firstWorkspaceCandidate(workspaceInfo);
|
|
11195
10196
|
const attachedProjects = workspaceInfo.appCandidates.filter(
|
|
11196
|
-
(appCandidate) =>
|
|
10197
|
+
(appCandidate) => existsSync28(join30(workspaceInfo.workspaceRoot, appCandidate, "decantr.essence.json"))
|
|
11197
10198
|
);
|
|
11198
10199
|
const firstAttached = attachedProjects[0];
|
|
11199
10200
|
console.log(heading2("Decantr Setup"));
|
|
@@ -11261,7 +10262,7 @@ function printMonorepoSetupGuidance(workspaceInfo) {
|
|
|
11261
10262
|
function resolveWorkflowProject(flags, commandName = "command", options = {}) {
|
|
11262
10263
|
const projectArg = flagString(flags, "project");
|
|
11263
10264
|
const workspaceInfo = resolveWorkspaceInfo(process.cwd(), projectArg);
|
|
11264
|
-
if (projectArg && options.requireExisting !== false && !
|
|
10265
|
+
if (projectArg && options.requireExisting !== false && !existsSync28(workspaceInfo.appRoot)) {
|
|
11265
10266
|
printProjectNotFound(projectArg, commandName);
|
|
11266
10267
|
process.exitCode = 1;
|
|
11267
10268
|
return null;
|
|
@@ -11269,7 +10270,7 @@ function resolveWorkflowProject(flags, commandName = "command", options = {}) {
|
|
|
11269
10270
|
if (projectArg && options.requireAppCandidate && workspaceInfo.appCandidates.length > 0) {
|
|
11270
10271
|
const normalizedProject = normalizedProjectPath(projectArg);
|
|
11271
10272
|
const normalizedWorkspaceProject = normalizedProjectPath(
|
|
11272
|
-
|
|
10273
|
+
relative6(workspaceInfo.workspaceRoot, workspaceInfo.appRoot)
|
|
11273
10274
|
);
|
|
11274
10275
|
const knownCandidate = normalizedProject ? workspaceInfo.appCandidates.includes(normalizedProject) || Boolean(
|
|
11275
10276
|
normalizedWorkspaceProject && workspaceInfo.appCandidates.includes(normalizedWorkspaceProject)
|
|
@@ -11318,6 +10319,129 @@ function formatScanApplicability(status) {
|
|
|
11318
10319
|
if (status === "not_applicable") return dim3("not applicable");
|
|
11319
10320
|
return dim3("unknown");
|
|
11320
10321
|
}
|
|
10322
|
+
function formatScanGraphPreviewStatus(status) {
|
|
10323
|
+
if (status === "current") return success3("current");
|
|
10324
|
+
if (status === "stale") return `${YELLOW12}stale or missing${RESET16}`;
|
|
10325
|
+
if (status === "needs_migration") return `${YELLOW12}needs migration${RESET16}`;
|
|
10326
|
+
if (status === "not_attached") return dim3("not attached");
|
|
10327
|
+
return dim3("unavailable");
|
|
10328
|
+
}
|
|
10329
|
+
function relativeGraphArtifactPath(projectRoot, artifactPath) {
|
|
10330
|
+
return relative6(projectRoot, artifactPath).replace(/\\/g, "/");
|
|
10331
|
+
}
|
|
10332
|
+
function buildScanGraphPreview(workspaceInfo, projectArg) {
|
|
10333
|
+
let artifacts;
|
|
10334
|
+
try {
|
|
10335
|
+
artifacts = buildGraphArtifacts(workspaceInfo.appRoot);
|
|
10336
|
+
} catch (error3) {
|
|
10337
|
+
const message = error3.message;
|
|
10338
|
+
const needsMigration = message.includes("Essence v4");
|
|
10339
|
+
return {
|
|
10340
|
+
status: needsMigration ? "needs_migration" : "unavailable",
|
|
10341
|
+
canPreview: false,
|
|
10342
|
+
readOnly: true,
|
|
10343
|
+
message: needsMigration ? "Existing Decantr contract needs Essence v4 before a typed Contract graph can be previewed." : message,
|
|
10344
|
+
nextCommand: needsMigration ? withProject("decantr migrate --to v4", projectArg) : null,
|
|
10345
|
+
staleArtifacts: [],
|
|
10346
|
+
snapshot: null,
|
|
10347
|
+
capsule: null,
|
|
10348
|
+
diff: null
|
|
10349
|
+
};
|
|
10350
|
+
}
|
|
10351
|
+
if (!artifacts) {
|
|
10352
|
+
return {
|
|
10353
|
+
status: "not_attached",
|
|
10354
|
+
canPreview: false,
|
|
10355
|
+
readOnly: true,
|
|
10356
|
+
message: "No decantr.essence.json found. Adopt the project to create the first typed Contract graph baseline.",
|
|
10357
|
+
nextCommand: withProject("decantr adopt --yes", projectArg),
|
|
10358
|
+
staleArtifacts: [],
|
|
10359
|
+
snapshot: null,
|
|
10360
|
+
capsule: null,
|
|
10361
|
+
diff: null
|
|
10362
|
+
};
|
|
10363
|
+
}
|
|
10364
|
+
const stale = artifacts.staleArtifacts.length > 0;
|
|
10365
|
+
const diffSummary = summarizeGraphDiff(artifacts.diff);
|
|
10366
|
+
return {
|
|
10367
|
+
status: stale ? "stale" : "current",
|
|
10368
|
+
canPreview: true,
|
|
10369
|
+
readOnly: true,
|
|
10370
|
+
message: stale ? "A typed Contract graph can be derived now, but saved graph artifacts are missing or stale." : "Typed Contract graph artifacts are current with the project-owned contract sources.",
|
|
10371
|
+
nextCommand: stale ? withProject("decantr graph", projectArg) : null,
|
|
10372
|
+
staleArtifacts: artifacts.staleArtifacts.map(
|
|
10373
|
+
(path) => displayProjectPath(workspaceInfo, relativeGraphArtifactPath(artifacts.projectRoot, path))
|
|
10374
|
+
),
|
|
10375
|
+
snapshot: {
|
|
10376
|
+
id: artifacts.snapshot.id,
|
|
10377
|
+
schemaVersion: artifacts.snapshot.schema_version,
|
|
10378
|
+
sourceHash: artifacts.snapshot.source_hash,
|
|
10379
|
+
nodes: artifacts.snapshot.summary.nodes,
|
|
10380
|
+
edges: artifacts.snapshot.summary.edges,
|
|
10381
|
+
findings: artifacts.snapshot.summary.findings,
|
|
10382
|
+
evidence: artifacts.snapshot.summary.evidence,
|
|
10383
|
+
sourceArtifacts: artifacts.snapshot.nodes.filter((node) => node.type === "SourceArtifact").length
|
|
10384
|
+
},
|
|
10385
|
+
capsule: {
|
|
10386
|
+
cacheKey: artifacts.capsule.cache_key,
|
|
10387
|
+
routes: artifacts.capsule.summary.routes,
|
|
10388
|
+
components: artifacts.capsule.summary.components,
|
|
10389
|
+
tokens: artifacts.capsule.summary.tokens,
|
|
10390
|
+
localRules: artifacts.capsule.summary.local_rules,
|
|
10391
|
+
styleBridge: artifacts.capsule.summary.style_bridge,
|
|
10392
|
+
sourceArtifacts: artifacts.capsule.summary.source_artifacts,
|
|
10393
|
+
sourceArtifactLimit: artifacts.capsule.source_artifact_limit,
|
|
10394
|
+
sourceArtifactsTruncated: artifacts.capsule.source_artifacts_truncated,
|
|
10395
|
+
openFindings: artifacts.capsule.summary.open_findings
|
|
10396
|
+
},
|
|
10397
|
+
diff: {
|
|
10398
|
+
ops: diffSummary.total,
|
|
10399
|
+
findingsAdded: diffSummary.findings.added,
|
|
10400
|
+
findingsResolved: diffSummary.findings.resolved,
|
|
10401
|
+
evidenceAdded: diffSummary.evidence.added
|
|
10402
|
+
}
|
|
10403
|
+
};
|
|
10404
|
+
}
|
|
10405
|
+
function printScanGraphPreview(preview) {
|
|
10406
|
+
if (!preview) return;
|
|
10407
|
+
console.log(`${BOLD9}Typed Contract Graph${RESET16}`);
|
|
10408
|
+
console.log(` Status: ${formatScanGraphPreviewStatus(preview.status)}`);
|
|
10409
|
+
console.log(` Read-only: ${preview.readOnly ? "yes" : "no"}`);
|
|
10410
|
+
if (preview.snapshot && preview.capsule) {
|
|
10411
|
+
console.log(
|
|
10412
|
+
` Snapshot: ${preview.snapshot.nodes} nodes, ${preview.snapshot.edges} edges`
|
|
10413
|
+
);
|
|
10414
|
+
console.log(
|
|
10415
|
+
` Evidence: ${preview.snapshot.findings} finding nodes, ${preview.snapshot.evidence} evidence nodes, ${preview.snapshot.sourceArtifacts} sources`
|
|
10416
|
+
);
|
|
10417
|
+
console.log(
|
|
10418
|
+
` Capsule: ${preview.capsule.routes} routes, ${preview.capsule.localRules} local rules, ${preview.capsule.styleBridge} style bridge mappings`
|
|
10419
|
+
);
|
|
10420
|
+
const sourceArtifactIndex = preview.capsule.sourceArtifactsTruncated ? `${preview.capsule.sourceArtifactLimit}/${preview.capsule.sourceArtifacts} source handles` : `${preview.capsule.sourceArtifacts} source handles`;
|
|
10421
|
+
console.log(` Source handles: ${sourceArtifactIndex}`);
|
|
10422
|
+
if (preview.diff) {
|
|
10423
|
+
const diffHints = [
|
|
10424
|
+
preview.diff.findingsAdded > 0 ? `${preview.diff.findingsAdded} finding added` : null,
|
|
10425
|
+
preview.diff.findingsResolved > 0 ? `${preview.diff.findingsResolved} finding resolved` : null,
|
|
10426
|
+
preview.diff.evidenceAdded > 0 ? `${preview.diff.evidenceAdded} evidence added` : null
|
|
10427
|
+
].filter(Boolean);
|
|
10428
|
+
console.log(
|
|
10429
|
+
` Diff: ${preview.diff.ops} ops${diffHints.length > 0 ? ` (${diffHints.join(", ")})` : ""}`
|
|
10430
|
+
);
|
|
10431
|
+
}
|
|
10432
|
+
}
|
|
10433
|
+
console.log(` ${dim3(preview.message)}`);
|
|
10434
|
+
if (preview.staleArtifacts.length > 0) {
|
|
10435
|
+
console.log(` Stale artifacts:${" "} ${preview.staleArtifacts.slice(0, 3).join(", ")}`);
|
|
10436
|
+
if (preview.staleArtifacts.length > 3) {
|
|
10437
|
+
console.log(` ${dim3(`...${preview.staleArtifacts.length - 3} more`)}`);
|
|
10438
|
+
}
|
|
10439
|
+
}
|
|
10440
|
+
if (preview.nextCommand) {
|
|
10441
|
+
console.log(` Next: ${cyan3(preview.nextCommand)}`);
|
|
10442
|
+
}
|
|
10443
|
+
console.log("");
|
|
10444
|
+
}
|
|
11321
10445
|
function printScanReport(report) {
|
|
11322
10446
|
console.log(heading2("Decantr Scan"));
|
|
11323
10447
|
console.log(dim3("Read-only Brownfield reconnaissance. No files were written."));
|
|
@@ -11338,6 +10462,7 @@ function printScanReport(report) {
|
|
|
11338
10462
|
console.log(` TypeScript: ${report.project.hasTypeScript ? "yes" : "no"}`);
|
|
11339
10463
|
console.log(` Decantr: ${report.project.hasDecantr ? "present" : "not attached"}`);
|
|
11340
10464
|
console.log("");
|
|
10465
|
+
printScanGraphPreview(report.graphPreview);
|
|
11341
10466
|
console.log(`${BOLD9}Routes And Styling${RESET16}`);
|
|
11342
10467
|
console.log(` Routes: ${report.routes.count} (${report.routes.strategy})`);
|
|
11343
10468
|
for (const route of report.routes.items.slice(0, 8)) {
|
|
@@ -11395,11 +10520,13 @@ async function cmdScanWorkflow(args) {
|
|
|
11395
10520
|
const report = await scanProjectReadOnly(workspaceInfo.appRoot, {
|
|
11396
10521
|
input: { kind: "local", value: inputValue }
|
|
11397
10522
|
});
|
|
10523
|
+
const graphPreview = buildScanGraphPreview(workspaceInfo, projectArg);
|
|
10524
|
+
const reportWithGraph = { ...report, graphPreview };
|
|
11398
10525
|
if (jsonOutput) {
|
|
11399
|
-
console.log(JSON.stringify(
|
|
10526
|
+
console.log(JSON.stringify(reportWithGraph, null, 2));
|
|
11400
10527
|
return;
|
|
11401
10528
|
}
|
|
11402
|
-
printScanReport(
|
|
10529
|
+
printScanReport(reportWithGraph);
|
|
11403
10530
|
}
|
|
11404
10531
|
async function cmdSetupWorkflow(args) {
|
|
11405
10532
|
const { flags } = parseLooseArgs(args);
|
|
@@ -11421,9 +10548,9 @@ async function cmdSetupWorkflow(args) {
|
|
|
11421
10548
|
console.log(` Detected: ${formatDetection(detected)}`);
|
|
11422
10549
|
console.log("");
|
|
11423
10550
|
if (detected.existingEssence) {
|
|
11424
|
-
const hasLocalPatterns =
|
|
11425
|
-
const hasLocalRules =
|
|
11426
|
-
const hasStyleBridge =
|
|
10551
|
+
const hasLocalPatterns = existsSync28(localPatternsPath(workspaceInfo.appRoot));
|
|
10552
|
+
const hasLocalRules = existsSync28(localRulesPath(workspaceInfo.appRoot));
|
|
10553
|
+
const hasStyleBridge = existsSync28(styleBridgePath(workspaceInfo.appRoot));
|
|
11427
10554
|
const verifyCommand = hasLocalPatterns || hasLocalRules ? "decantr verify --brownfield --local-patterns" : "decantr verify --brownfield";
|
|
11428
10555
|
console.log(`${BOLD9}Recommended path:${RESET16} maintain an attached Decantr project`);
|
|
11429
10556
|
console.log(
|
|
@@ -11509,7 +10636,7 @@ async function cmdAdoptWorkflow(args) {
|
|
|
11509
10636
|
const hydratePacks = flagBoolean(flags, "packs", true) && !flagBoolean(flags, "skip-packs") && !flagBoolean(flags, "offline") && process.env.DECANTR_OFFLINE !== "true";
|
|
11510
10637
|
const initCi = flagBoolean(flags, "ci") || flagBoolean(flags, "init-ci");
|
|
11511
10638
|
const assistantBridge = flagString(flags, "assistant-bridge");
|
|
11512
|
-
const hasEssence =
|
|
10639
|
+
const hasEssence = existsSync28(join30(projectRoot, "decantr.essence.json"));
|
|
11513
10640
|
const proposalFlag = flagBoolean(flags, "replace-essence") ? "--replace-essence" : flagBoolean(flags, "merge-proposal") || hasEssence ? "--merge-proposal" : "--accept-proposal";
|
|
11514
10641
|
const steps = [
|
|
11515
10642
|
"analyze current app and write .decantr/brownfield intelligence",
|
|
@@ -11518,6 +10645,7 @@ async function cmdAdoptWorkflow(args) {
|
|
|
11518
10645
|
if (hydratePacks) {
|
|
11519
10646
|
steps.push("hydrate hosted execution packs into the app context");
|
|
11520
10647
|
}
|
|
10648
|
+
steps.push("write typed Contract graph baseline");
|
|
11521
10649
|
if (runVerify) {
|
|
11522
10650
|
steps.push(
|
|
11523
10651
|
runBrowser ? "verify with Project Health, browser evidence, visual manifest, and baseline" : "verify with Project Health and baseline"
|
|
@@ -11557,7 +10685,7 @@ async function cmdAdoptWorkflow(args) {
|
|
|
11557
10685
|
if (hydratePacks) {
|
|
11558
10686
|
try {
|
|
11559
10687
|
const { bundle, contextDir } = await compileHostedExecutionPackBundle(
|
|
11560
|
-
|
|
10688
|
+
join30(projectRoot, "decantr.essence.json")
|
|
11561
10689
|
);
|
|
11562
10690
|
const written = writeHostedExecutionPackContextArtifacts(
|
|
11563
10691
|
contextDir,
|
|
@@ -11579,8 +10707,10 @@ async function cmdAdoptWorkflow(args) {
|
|
|
11579
10707
|
} else if (flagBoolean(flags, "offline") || process.env.DECANTR_OFFLINE === "true") {
|
|
11580
10708
|
console.log(dim3("Skipping hosted pack hydration in offline mode."));
|
|
11581
10709
|
}
|
|
10710
|
+
await cmdGraph(projectRoot, { displayRoot: process.cwd() });
|
|
10711
|
+
if (process.exitCode && process.exitCode !== 0) return;
|
|
11582
10712
|
if (runVerify) {
|
|
11583
|
-
const { cmdHealth } = await import("./health-
|
|
10713
|
+
const { cmdHealth } = await import("./health-ASR7RWZJ.js");
|
|
11584
10714
|
await cmdHealth(projectRoot, {
|
|
11585
10715
|
browser: runBrowser,
|
|
11586
10716
|
browserBaseUrl: baseUrl,
|
|
@@ -11651,7 +10781,7 @@ async function cmdVerifyWorkflow(args) {
|
|
|
11651
10781
|
return;
|
|
11652
10782
|
}
|
|
11653
10783
|
if (workspaceMode) {
|
|
11654
|
-
const { cmdWorkspace } = await import("./workspace-
|
|
10784
|
+
const { cmdWorkspace } = await import("./workspace-2FFQEEQQ.js");
|
|
11655
10785
|
await cmdWorkspace(process.cwd(), ["workspace", "health", ...withoutWorkflowOnlyFlags(args)]);
|
|
11656
10786
|
return;
|
|
11657
10787
|
}
|
|
@@ -11694,7 +10824,7 @@ async function cmdVerifyWorkflow(args) {
|
|
|
11694
10824
|
process.exitCode = void 0;
|
|
11695
10825
|
}
|
|
11696
10826
|
}
|
|
11697
|
-
const { cmdHealth, parseHealthArgs } = await import("./health-
|
|
10827
|
+
const { cmdHealth, parseHealthArgs } = await import("./health-ASR7RWZJ.js");
|
|
11698
10828
|
await cmdHealth(workspaceInfo.appRoot, parseHealthArgs(healthArgs));
|
|
11699
10829
|
if (localPatterns) {
|
|
11700
10830
|
const validation = validateLocalLaw(workspaceInfo.appRoot);
|
|
@@ -11763,9 +10893,9 @@ async function cmdVerifyWorkflow(args) {
|
|
|
11763
10893
|
}
|
|
11764
10894
|
}
|
|
11765
10895
|
function readJsonIfPresent(path) {
|
|
11766
|
-
if (!
|
|
10896
|
+
if (!existsSync28(path)) return null;
|
|
11767
10897
|
try {
|
|
11768
|
-
return JSON.parse(
|
|
10898
|
+
return JSON.parse(readFileSync21(path, "utf-8"));
|
|
11769
10899
|
} catch {
|
|
11770
10900
|
return null;
|
|
11771
10901
|
}
|
|
@@ -11863,7 +10993,7 @@ async function cmdTaskWorkflow(args) {
|
|
|
11863
10993
|
}
|
|
11864
10994
|
const route = routeInput.startsWith("/") ? routeInput : `/${routeInput}`;
|
|
11865
10995
|
const taskSummary = positional.slice(1).join(" ").trim();
|
|
11866
|
-
const essencePath =
|
|
10996
|
+
const essencePath = join30(workspaceInfo.appRoot, "decantr.essence.json");
|
|
11867
10997
|
const essence = readJsonIfPresent(essencePath);
|
|
11868
10998
|
if (!essence) {
|
|
11869
10999
|
console.error(
|
|
@@ -11872,7 +11002,7 @@ async function cmdTaskWorkflow(args) {
|
|
|
11872
11002
|
process.exitCode = 1;
|
|
11873
11003
|
return;
|
|
11874
11004
|
}
|
|
11875
|
-
if (!
|
|
11005
|
+
if (!isV48(essence)) {
|
|
11876
11006
|
console.error(error2("Task context requires Essence v4. Run `decantr migrate --to v4` first."));
|
|
11877
11007
|
process.exitCode = 1;
|
|
11878
11008
|
return;
|
|
@@ -11887,16 +11017,22 @@ async function cmdTaskWorkflow(args) {
|
|
|
11887
11017
|
}
|
|
11888
11018
|
const section = essence.blueprint.sections.find((entry) => entry.id === target.section);
|
|
11889
11019
|
const page = section?.pages.find((entry) => entry.id === target.page);
|
|
11890
|
-
const contextDir =
|
|
11891
|
-
const manifest = readJsonIfPresent(
|
|
11892
|
-
const projectJson = readJsonIfPresent(
|
|
11020
|
+
const contextDir = join30(workspaceInfo.appRoot, ".decantr", "context");
|
|
11021
|
+
const manifest = readJsonIfPresent(join30(contextDir, "pack-manifest.json"));
|
|
11022
|
+
const projectJson = readJsonIfPresent(join30(workspaceInfo.appRoot, ".decantr", "project.json"));
|
|
11893
11023
|
const pagePack = manifest?.pages?.find((entry) => entry.id === target.page);
|
|
11894
11024
|
const sectionPack = manifest?.sections?.find((entry) => entry.id === target.section);
|
|
11895
|
-
const visualManifest = readJsonIfPresent(
|
|
11025
|
+
const visualManifest = readJsonIfPresent(join30(workspaceInfo.appRoot, ".decantr", "evidence", "visual-manifest.json"));
|
|
11896
11026
|
const screenshot = visualManifest?.routes?.find((entry) => entry.route === route)?.screenshot;
|
|
11897
11027
|
const localPatternPackPath = localPatternsPath(workspaceInfo.appRoot);
|
|
11898
11028
|
const localRuleManifestPath = localRulesPath(workspaceInfo.appRoot);
|
|
11899
11029
|
const acceptedStyleBridgePath = styleBridgePath(workspaceInfo.appRoot);
|
|
11030
|
+
const graphDir = join30(workspaceInfo.appRoot, ".decantr", "graph");
|
|
11031
|
+
const contractCapsulePath = join30(graphDir, "contract-capsule.json");
|
|
11032
|
+
const graphSnapshotPath = join30(graphDir, "graph.snapshot.json");
|
|
11033
|
+
const contractCapsule = readJsonIfPresent(contractCapsulePath);
|
|
11034
|
+
const graphSnapshot = readJsonIfPresent(graphSnapshotPath);
|
|
11035
|
+
const routeGraphContext = buildGraphRouteContext(graphSnapshot, route, { task: taskSummary });
|
|
11900
11036
|
const localLaw = createLocalLawTaskSummary(workspaceInfo.appRoot);
|
|
11901
11037
|
const styleBridge = createStyleBridgeTaskSummary(workspaceInfo.appRoot);
|
|
11902
11038
|
const displayedStyleBridge = {
|
|
@@ -11911,15 +11047,29 @@ async function cmdTaskWorkflow(args) {
|
|
|
11911
11047
|
const changedSince = flagString(flags, "since");
|
|
11912
11048
|
const currentChangedFiles = changedFiles(workspaceInfo.appRoot, changedSince);
|
|
11913
11049
|
const changedRoutes = routeImpacts(workspaceInfo.appRoot, currentChangedFiles);
|
|
11050
|
+
const changedFileSourceNodes = graphSnapshot ? currentChangedFiles.map((file) => ({
|
|
11051
|
+
file,
|
|
11052
|
+
nodeId: graphSourceNodeIdForTaskFile(workspaceInfo.appRoot, graphSnapshot, file)
|
|
11053
|
+
})) : [];
|
|
11054
|
+
const changedFileSourceNodeIds = [
|
|
11055
|
+
...new Set(
|
|
11056
|
+
changedFileSourceNodes.map((entry) => entry.nodeId).filter((nodeId) => Boolean(nodeId))
|
|
11057
|
+
)
|
|
11058
|
+
];
|
|
11059
|
+
const changedFileMissingFiles = graphSnapshot ? changedFileSourceNodes.filter((entry) => !entry.nodeId).map((entry) => entry.file) : currentChangedFiles;
|
|
11060
|
+
const changedFileGraphContext = graphSnapshot && changedFileSourceNodeIds.length > 0 ? buildGraphImpactContext(graphSnapshot, changedFileSourceNodeIds, {
|
|
11061
|
+
task: taskSummary,
|
|
11062
|
+
limit: 120
|
|
11063
|
+
}) : null;
|
|
11914
11064
|
const authority = createTaskAuthoritySummary({
|
|
11915
11065
|
projectRoot: workspaceInfo.appRoot,
|
|
11916
11066
|
workflowMode: projectJson?.initialized?.workflowMode ?? null,
|
|
11917
11067
|
adoptionMode: projectJson?.initialized?.adoptionMode ?? null,
|
|
11918
|
-
hasLocalPatterns:
|
|
11919
|
-
hasLocalRules:
|
|
11068
|
+
hasLocalPatterns: existsSync28(localPatternPackPath),
|
|
11069
|
+
hasLocalRules: existsSync28(localRuleManifestPath),
|
|
11920
11070
|
hasPackManifest: Boolean(manifest),
|
|
11921
11071
|
taskSummary,
|
|
11922
|
-
hasStyleBridge:
|
|
11072
|
+
hasStyleBridge: existsSync28(acceptedStyleBridgePath)
|
|
11923
11073
|
});
|
|
11924
11074
|
const context = {
|
|
11925
11075
|
route,
|
|
@@ -11929,15 +11079,56 @@ async function cmdTaskWorkflow(args) {
|
|
|
11929
11079
|
shell: page?.shell ?? section?.shell ?? null,
|
|
11930
11080
|
patterns: page?.layout?.map(extractPatternName) ?? [],
|
|
11931
11081
|
read: [
|
|
11932
|
-
pagePack ? displayProjectPath(workspaceInfo,
|
|
11933
|
-
sectionPack ? displayProjectPath(workspaceInfo,
|
|
11934
|
-
manifest?.scaffold?.markdown ? displayProjectPath(workspaceInfo,
|
|
11082
|
+
pagePack ? displayProjectPath(workspaceInfo, join30(".decantr/context", pagePack.markdown)) : null,
|
|
11083
|
+
sectionPack ? displayProjectPath(workspaceInfo, join30(".decantr/context", sectionPack.markdown)) : null,
|
|
11084
|
+
manifest?.scaffold?.markdown ? displayProjectPath(workspaceInfo, join30(".decantr/context", manifest.scaffold.markdown)) : null,
|
|
11935
11085
|
displayProjectPath(workspaceInfo, ".decantr/context/scaffold.md"),
|
|
11936
11086
|
displayProjectPath(workspaceInfo, "DECANTR.md"),
|
|
11937
|
-
|
|
11938
|
-
|
|
11939
|
-
|
|
11087
|
+
existsSync28(localPatternPackPath) ? displayProjectPath(workspaceInfo, ".decantr/local-patterns.json") : null,
|
|
11088
|
+
existsSync28(localRuleManifestPath) ? displayProjectPath(workspaceInfo, ".decantr/rules.json") : null,
|
|
11089
|
+
existsSync28(acceptedStyleBridgePath) ? displayProjectPath(workspaceInfo, ".decantr/style-bridge.json") : null,
|
|
11090
|
+
contractCapsule ? displayProjectPath(workspaceInfo, ".decantr/graph/contract-capsule.json") : null,
|
|
11091
|
+
routeGraphContext && !contractCapsule ? displayProjectPath(workspaceInfo, ".decantr/graph/graph.snapshot.json") : null
|
|
11940
11092
|
].filter(Boolean),
|
|
11093
|
+
graph: contractCapsule || graphSnapshot ? {
|
|
11094
|
+
capsule: contractCapsule ? {
|
|
11095
|
+
path: displayProjectPath(workspaceInfo, ".decantr/graph/contract-capsule.json"),
|
|
11096
|
+
snapshotId: contractCapsule.snapshot_id ?? null,
|
|
11097
|
+
cacheKey: contractCapsule.cache_key ?? null,
|
|
11098
|
+
contractHash: contractCapsule.contract_hash ?? null,
|
|
11099
|
+
contractCacheKey: contractCapsule.contract_cache_key ?? null,
|
|
11100
|
+
summary: contractCapsule.summary ?? null
|
|
11101
|
+
} : null,
|
|
11102
|
+
snapshot: graphSnapshot ? {
|
|
11103
|
+
path: displayProjectPath(workspaceInfo, ".decantr/graph/graph.snapshot.json"),
|
|
11104
|
+
id: graphSnapshot.id ?? null,
|
|
11105
|
+
sourceHash: graphSnapshot.source_hash ?? null,
|
|
11106
|
+
summary: graphSnapshot.summary ?? null
|
|
11107
|
+
} : null,
|
|
11108
|
+
routeContext: routeGraphContext ? {
|
|
11109
|
+
path: displayProjectPath(workspaceInfo, ".decantr/graph/graph.snapshot.json"),
|
|
11110
|
+
...routeGraphContext
|
|
11111
|
+
} : null,
|
|
11112
|
+
changedFileContext: currentChangedFiles.length > 0 ? {
|
|
11113
|
+
path: displayProjectPath(
|
|
11114
|
+
workspaceInfo,
|
|
11115
|
+
".decantr/graph/graph.snapshot.json"
|
|
11116
|
+
),
|
|
11117
|
+
changedFiles: currentChangedFiles.slice(0, 40),
|
|
11118
|
+
resolvedNodeIds: changedFileSourceNodeIds,
|
|
11119
|
+
missingFiles: changedFileMissingFiles.slice(0, 40),
|
|
11120
|
+
impact: changedFileGraphContext ? {
|
|
11121
|
+
snapshotId: changedFileGraphContext.snapshotId,
|
|
11122
|
+
sourceHash: changedFileGraphContext.sourceHash,
|
|
11123
|
+
ranking: changedFileGraphContext.ranking,
|
|
11124
|
+
summary: changedFileGraphContext.summary,
|
|
11125
|
+
ids: changedFileGraphContext.ids,
|
|
11126
|
+
ranked: changedFileGraphContext.ranked.slice(0, 24),
|
|
11127
|
+
nodes: changedFileGraphContext.nodes,
|
|
11128
|
+
edges: changedFileGraphContext.edges
|
|
11129
|
+
} : null
|
|
11130
|
+
} : null
|
|
11131
|
+
} : null,
|
|
11941
11132
|
screenshot: screenshot?.startsWith(".decantr/") ? displayProjectPath(workspaceInfo, screenshot) : screenshot ?? null,
|
|
11942
11133
|
authority,
|
|
11943
11134
|
localLaw: displayedLocalLaw,
|
|
@@ -11966,6 +11157,55 @@ async function cmdTaskWorkflow(args) {
|
|
|
11966
11157
|
console.log(`${BOLD9}Visual evidence:${RESET16}`);
|
|
11967
11158
|
console.log(` ${cyan3(context.screenshot)}`);
|
|
11968
11159
|
}
|
|
11160
|
+
if (context.graph?.capsule || context.graph?.snapshot) {
|
|
11161
|
+
console.log("");
|
|
11162
|
+
console.log(`${BOLD9}Typed contract graph:${RESET16}`);
|
|
11163
|
+
if (context.graph.capsule) {
|
|
11164
|
+
console.log(` Capsule: ${cyan3(context.graph.capsule.path)}`);
|
|
11165
|
+
if (context.graph.capsule.cacheKey) {
|
|
11166
|
+
console.log(` Cache key: ${context.graph.capsule.cacheKey}`);
|
|
11167
|
+
}
|
|
11168
|
+
if (context.graph.capsule.contractCacheKey && context.graph.capsule.contractCacheKey !== context.graph.capsule.cacheKey) {
|
|
11169
|
+
console.log(` Contract cache key: ${context.graph.capsule.contractCacheKey}`);
|
|
11170
|
+
}
|
|
11171
|
+
}
|
|
11172
|
+
if (context.graph.snapshot) {
|
|
11173
|
+
const summary = context.graph.snapshot.summary;
|
|
11174
|
+
const counts = summary && typeof summary.nodes === "number" && typeof summary.edges === "number" ? ` (${summary.nodes} nodes, ${summary.edges} edges)` : "";
|
|
11175
|
+
console.log(` Snapshot: ${cyan3(context.graph.snapshot.path)}${counts}`);
|
|
11176
|
+
}
|
|
11177
|
+
if (context.graph.routeContext) {
|
|
11178
|
+
const routeSummary = context.graph.routeContext.summary;
|
|
11179
|
+
const routeFindings = context.graph.routeContext.nodes.filter((node) => node.type === "Finding").map((node) => graphPayloadString(node.payload, "code") ?? node.id).slice(0, 4);
|
|
11180
|
+
const routeSources = context.graph.routeContext.nodes.filter((node) => node.type === "SourceArtifact").map((node) => graphPayloadString(node.payload, "path") ?? node.id.replace(/^src:/, "")).slice(0, 4);
|
|
11181
|
+
const routeHints = [
|
|
11182
|
+
context.graph.routeContext.ids.patterns.length > 0 ? `patterns ${context.graph.routeContext.ids.patterns.join(", ")}` : null,
|
|
11183
|
+
routeSummary.openFindings > 0 ? `${routeSummary.openFindings} finding(s)` : null,
|
|
11184
|
+
routeSummary.evidence > 0 ? `${routeSummary.evidence} evidence node(s)` : null,
|
|
11185
|
+
routeSummary.sourceArtifacts > 0 ? `${routeSummary.sourceArtifacts} source artifact(s)` : null
|
|
11186
|
+
].filter(Boolean);
|
|
11187
|
+
console.log(
|
|
11188
|
+
` Route subgraph: ${routeSummary.nodes} nodes, ${routeSummary.edges} edges${routeHints.length > 0 ? `; ${routeHints.join("; ")}` : ""}`
|
|
11189
|
+
);
|
|
11190
|
+
if (routeFindings.length > 0) {
|
|
11191
|
+
console.log(` Route findings: ${routeFindings.join(", ")}`);
|
|
11192
|
+
}
|
|
11193
|
+
if (routeSources.length > 0) {
|
|
11194
|
+
console.log(` Route sources: ${routeSources.join(", ")}`);
|
|
11195
|
+
}
|
|
11196
|
+
}
|
|
11197
|
+
if (context.graph.changedFileContext?.impact) {
|
|
11198
|
+
const impact = context.graph.changedFileContext.impact;
|
|
11199
|
+
const impactHints = [
|
|
11200
|
+
impact.ids.routes.length > 0 ? `routes ${impact.ids.routes.join(", ")}` : null,
|
|
11201
|
+
impact.summary.pages > 0 ? `${impact.summary.pages} page(s)` : null,
|
|
11202
|
+
impact.summary.openFindings > 0 ? `${impact.summary.openFindings} finding(s)` : null
|
|
11203
|
+
].filter(Boolean);
|
|
11204
|
+
console.log(
|
|
11205
|
+
` Changed-file impact: ${impact.summary.nodes} nodes, ${impact.summary.edges} edges${impactHints.length > 0 ? `; ${impactHints.join("; ")}` : ""}`
|
|
11206
|
+
);
|
|
11207
|
+
}
|
|
11208
|
+
}
|
|
11969
11209
|
console.log("");
|
|
11970
11210
|
console.log(`${BOLD9}Authority for this task:${RESET16}`);
|
|
11971
11211
|
console.log(` Lane: ${context.authority.lane}`);
|
|
@@ -12038,7 +11278,7 @@ async function cmdTaskWorkflow(args) {
|
|
|
12038
11278
|
console.log("");
|
|
12039
11279
|
console.log(`${BOLD9}LLM instruction:${RESET16}`);
|
|
12040
11280
|
console.log(
|
|
12041
|
-
" Preserve the active authority above. Use the route pack, section context, local laws, changed-file impact, and visual evidence as the task contract before changing code."
|
|
11281
|
+
" Preserve the active authority above. Use the route pack, section context, typed route graph, local laws, changed-file impact, and visual evidence as the task contract before changing code."
|
|
12042
11282
|
);
|
|
12043
11283
|
console.log(` After editing, run ${cyan3(context.verifyCommand)}.`);
|
|
12044
11284
|
}
|
|
@@ -12064,7 +11304,7 @@ async function cmdCodifyWorkflow(args) {
|
|
|
12064
11304
|
const workspaceInfo = resolveWorkflowProject(flags, "codify");
|
|
12065
11305
|
if (!workspaceInfo) return;
|
|
12066
11306
|
if (flagBoolean(flags, "accept")) {
|
|
12067
|
-
if (!
|
|
11307
|
+
if (!existsSync28(localPatternsProposalPath(workspaceInfo.appRoot)) && !existsSync28(localRulesProposalPath(workspaceInfo.appRoot)) && !existsSync28(styleBridgeProposalPath(workspaceInfo.appRoot))) {
|
|
12068
11308
|
console.error(
|
|
12069
11309
|
error2(
|
|
12070
11310
|
"No codify proposal found. Run `decantr codify --from-audit`, `decantr codify --style-bridge`, or `decantr codify` first."
|
|
@@ -12098,14 +11338,14 @@ async function cmdCodifyWorkflow(args) {
|
|
|
12098
11338
|
}
|
|
12099
11339
|
const detected = detectProject(workspaceInfo.appRoot);
|
|
12100
11340
|
const essence = readJsonIfPresent(
|
|
12101
|
-
|
|
11341
|
+
join30(workspaceInfo.appRoot, "decantr.essence.json")
|
|
12102
11342
|
);
|
|
12103
11343
|
const fromAudit = flagBoolean(flags, "from-audit") || flagBoolean(flags, "discover-local-patterns") || flagBoolean(flags, "codify-local-patterns");
|
|
12104
11344
|
const wantsStyleBridge = flagBoolean(flags, "style-bridge");
|
|
12105
11345
|
const mapPatternSlug = flagString(flags, "map-pattern") ?? flagString(flags, "hosted-pattern");
|
|
12106
11346
|
if (mapPatternSlug) {
|
|
12107
11347
|
const registryClient = new RegistryClient({
|
|
12108
|
-
cacheDir:
|
|
11348
|
+
cacheDir: join30(workspaceInfo.appRoot, ".decantr", "cache")
|
|
12109
11349
|
});
|
|
12110
11350
|
const candidates = await loadPatternDiscoveryCandidates(registryClient);
|
|
12111
11351
|
const candidate = findPatternCandidateBySlug(candidates, mapPatternSlug);
|
|
@@ -12229,7 +11469,7 @@ async function cmdContentWorkflow(args) {
|
|
|
12229
11469
|
}
|
|
12230
11470
|
function cmdHelp() {
|
|
12231
11471
|
console.log(`
|
|
12232
|
-
${BOLD9}decantr${RESET16} \u2014
|
|
11472
|
+
${BOLD9}decantr${RESET16} \u2014 AI Frontend Governance for codebases touched by AI agents
|
|
12233
11473
|
|
|
12234
11474
|
${BOLD9}Usage:${RESET16}
|
|
12235
11475
|
decantr setup [--project <path>]
|
|
@@ -12238,6 +11478,7 @@ ${BOLD9}Usage:${RESET16}
|
|
|
12238
11478
|
decantr adopt [--project <path>] [--base-url <url>] [--evidence] [--ci] [--no-packs] [--yes]
|
|
12239
11479
|
decantr task <route> ["task summary"] [--project <path>] [--since origin/main] [--json]
|
|
12240
11480
|
decantr verify [--project <path>] [--brownfield] [--local-patterns] [health options]
|
|
11481
|
+
decantr graph [--project <path>] [--route <route>] [--node <id>] [--file <path>] [--task <text>] [--snapshot-id <id>] [--compare-to <id>] [--capsule-source-limit <count>] [--check] [--json]
|
|
12241
11482
|
decantr ci [--project <path>] [--workspace] [--fail-on error|warn|none]
|
|
12242
11483
|
decantr doctor [--project <path>] [--workspace] [--json]
|
|
12243
11484
|
decantr codify [--from-audit] [--style-bridge] [--map-pattern <slug>] [--accept] [--project <path>]
|
|
@@ -12254,6 +11495,7 @@ ${BOLD9}Advanced primitives:${RESET16}
|
|
|
12254
11495
|
decantr check
|
|
12255
11496
|
decantr check --brownfield
|
|
12256
11497
|
decantr sync-drift
|
|
11498
|
+
decantr graph [--project <path>] [--route <route>] [--node <id>] [--file <path>] [--task <text>] [--snapshot-id <id>] [--compare-to <id>] [--capsule-source-limit <count>] [--check] [--json]
|
|
12257
11499
|
decantr search <query> [--type <type>] [--sort <recommended|recent|name>] [--recommended] [--source <authored|benchmark|hybrid>]
|
|
12258
11500
|
decantr suggest <query> [--type <type>] [--route <route>] [--file <path>] [--from-code]
|
|
12259
11501
|
decantr get <type> <id>
|
|
@@ -12266,6 +11508,7 @@ ${BOLD9}Advanced primitives:${RESET16}
|
|
|
12266
11508
|
decantr registry audit-project [--namespace <namespace>] [--json] [--essence <path>] [--dist <path>] [--sources <dir>]
|
|
12267
11509
|
decantr health [--format text|json|markdown] [--ci] [--fail-on error|warn|none]
|
|
12268
11510
|
decantr health --evidence [--browser] [--base-url <url>] [--design-tokens <path>]
|
|
11511
|
+
decantr health --diagnostics [--json|--markdown]
|
|
12269
11512
|
decantr health --save-baseline | --since-baseline
|
|
12270
11513
|
decantr health init-ci [legacy alias for decantr ci init]
|
|
12271
11514
|
decantr ci init [--project <path>] [--workspace] [--provider github|generic] [--force]
|
|
@@ -12315,6 +11558,7 @@ ${BOLD9}Commands:${RESET16}
|
|
|
12315
11558
|
${cyan3("adopt")} Brownfield one-liner: analyze, attach, verify, and show next steps
|
|
12316
11559
|
${cyan3("task")} Prepare route/task context, local law, evidence, and changed-file impact for an AI coding assistant
|
|
12317
11560
|
${cyan3("verify")} One reliability gate over Project Health, Brownfield checks, baselines, and evidence
|
|
11561
|
+
${cyan3("graph")} Build typed Contract graph artifacts and the agent cache capsule
|
|
12318
11562
|
${cyan3("ci")} Non-mutating CI gate and CI integration generator
|
|
12319
11563
|
${cyan3("doctor")} Explain Decantr state, artifact ownership, and the next command
|
|
12320
11564
|
${cyan3("codify")} Propose or accept project-owned Brownfield UI patterns and rules
|
|
@@ -12327,21 +11571,22 @@ ${BOLD9}Advanced commands:${RESET16}
|
|
|
12327
11571
|
${cyan3("status")} Show project status, DNA axioms, and blueprint info
|
|
12328
11572
|
${cyan3("health")} Advanced Project Health primitive [--json] [--markdown] [--ci]; use decantr ci for automation
|
|
12329
11573
|
${cyan3("workspace")} Discover and aggregate health across Decantr projects in a monorepo
|
|
12330
|
-
${cyan3("content-health")} Generate a local
|
|
12331
|
-
${cyan3("sync")} Sync
|
|
11574
|
+
${cyan3("content-health")} Generate a local official-vocabulary health report [--json] [--markdown] [--ci]
|
|
11575
|
+
${cyan3("sync")} Sync official vocabulary from API
|
|
12332
11576
|
${cyan3("audit")} Audit the project or critique a specific file against compiled packs
|
|
12333
11577
|
${cyan3("migrate")} Migrate older essence files to v4 format (with .pre-v4.backup.json backup)
|
|
12334
11578
|
${cyan3("check")} Detect drift issues (validate + guard rules) [--telemetry] [--brownfield]
|
|
12335
11579
|
${cyan3("sync-drift")} Review and resolve drift log entries
|
|
12336
|
-
${cyan3("
|
|
11580
|
+
${cyan3("graph")} Generate .decantr/graph snapshot, history, manifest, diff, and contract capsule
|
|
11581
|
+
${cyan3("search")} Search official/community vocabulary
|
|
12337
11582
|
${cyan3("suggest")} Suggest patterns or alternatives for a query
|
|
12338
|
-
${cyan3("get")} Get full details of a
|
|
11583
|
+
${cyan3("get")} Get full details of a vocabulary item
|
|
12339
11584
|
${cyan3("list")} List items by type
|
|
12340
11585
|
${cyan3("showcase")} Inspect audited showcase benchmark metadata
|
|
12341
11586
|
${cyan3("validate")} Validate an Essence v4 file
|
|
12342
11587
|
${cyan3("theme")} Manage custom themes (create, list, validate, delete, import)
|
|
12343
11588
|
${cyan3("create")} Create a custom content item (pattern, theme, blueprint, etc.)
|
|
12344
|
-
${cyan3("publish")} Publish a custom
|
|
11589
|
+
${cyan3("publish")} Publish a custom vocabulary item to the community content service
|
|
12345
11590
|
${cyan3("login")} Authenticate with the Decantr registry
|
|
12346
11591
|
${cyan3("logout")} Remove stored credentials
|
|
12347
11592
|
${cyan3("analyze")} Brownfield entrypoint: scan an existing project and emit attach guidance
|
|
@@ -12361,6 +11606,11 @@ ${BOLD9}Examples:${RESET16}
|
|
|
12361
11606
|
decantr adopt --project apps/web --yes
|
|
12362
11607
|
decantr task /feed "add saved recipe actions"
|
|
12363
11608
|
decantr verify --brownfield --local-patterns
|
|
11609
|
+
decantr graph --project apps/web
|
|
11610
|
+
decantr graph --project apps/web --route /feed --task "improve loading" --json
|
|
11611
|
+
decantr graph --project apps/web --file src/app/page.tsx --impact --json
|
|
11612
|
+
decantr graph --project apps/web --compare-to graph:previous --include-diff-ops --json
|
|
11613
|
+
decantr graph --check --json
|
|
12364
11614
|
decantr verify --base-url http://localhost:3000 --evidence
|
|
12365
11615
|
decantr verify --since-baseline
|
|
12366
11616
|
decantr doctor --project apps/web
|
|
@@ -12418,6 +11668,7 @@ ${BOLD9}Workflow Model:${RESET16}
|
|
|
12418
11668
|
${cyan3("Brownfield preview")} decantr scan -> decantr adopt --yes
|
|
12419
11669
|
${cyan3("Brownfield monorepo")} decantr adopt --project apps/web --yes
|
|
12420
11670
|
${cyan3("Daily LLM work")} decantr task <route> "<change>" -> decantr verify --brownfield --local-patterns
|
|
11671
|
+
${cyan3("Typed contract graph")} decantr graph -> agent session loads .decantr/graph/contract-capsule.json
|
|
12421
11672
|
${cyan3("Project-owned law")} decantr codify --from-audit -> edit proposal -> decantr codify --accept
|
|
12422
11673
|
${cyan3("Hybrid composition")} decantr add/remove, decantr theme switch, decantr registry, decantr upgrade
|
|
12423
11674
|
|
|
@@ -12480,6 +11731,7 @@ ${BOLD9}Usage:${RESET16}
|
|
|
12480
11731
|
decantr health --ci [--fail-on error|warn|none]
|
|
12481
11732
|
decantr health --prompt <finding-id>
|
|
12482
11733
|
decantr health --evidence [--browser] [--design-tokens <path>]
|
|
11734
|
+
decantr health --diagnostics [--json|--markdown]
|
|
12483
11735
|
decantr health --browser --base-url <url> --evidence
|
|
12484
11736
|
decantr health --save-baseline
|
|
12485
11737
|
decantr health --since-baseline
|
|
@@ -12494,6 +11746,7 @@ ${BOLD9}Options:${RESET16}
|
|
|
12494
11746
|
--fail-on CI threshold: error, warn, or none
|
|
12495
11747
|
--prompt Print an AI-ready remediation prompt for a finding
|
|
12496
11748
|
--evidence Emit a local Evidence Bundle JSON artifact
|
|
11749
|
+
--diagnostics Print the stable diagnostic code and repair ID catalog
|
|
12497
11750
|
--browser Include optional rendered-browser setup/evidence checks
|
|
12498
11751
|
--base-url Base URL for rendered route checks when --browser is enabled
|
|
12499
11752
|
--save-baseline Save the current health state for later comparison
|
|
@@ -12506,6 +11759,7 @@ ${BOLD9}Examples:${RESET16}
|
|
|
12506
11759
|
decantr health --markdown --output decantr-health.md
|
|
12507
11760
|
decantr ci --project apps/web
|
|
12508
11761
|
decantr health --prompt audit-essence-missing
|
|
11762
|
+
decantr health --diagnostics --markdown
|
|
12509
11763
|
decantr health --evidence --output .decantr/evidence/latest.json
|
|
12510
11764
|
decantr ci init --project apps/web
|
|
12511
11765
|
decantr ci init --workspace
|
|
@@ -12530,7 +11784,7 @@ ${BOLD9}Examples:${RESET16}
|
|
|
12530
11784
|
}
|
|
12531
11785
|
function cmdContentHealthHelp() {
|
|
12532
11786
|
console.log(`
|
|
12533
|
-
${BOLD9}decantr content-health${RESET16} \u2014 Generate a local
|
|
11787
|
+
${BOLD9}decantr content-health${RESET16} \u2014 Generate a local official-vocabulary health report
|
|
12534
11788
|
|
|
12535
11789
|
${BOLD9}Usage:${RESET16}
|
|
12536
11790
|
decantr content-health [--format text|json|markdown] [--output <file>]
|
|
@@ -12679,6 +11933,10 @@ ${BOLD9}decantr task${RESET16} \u2014 Prepare compact route/task context for an
|
|
|
12679
11933
|
${BOLD9}Usage:${RESET16}
|
|
12680
11934
|
decantr task <route> ["task summary"] [--project <path>] [--since origin/main] [--json]
|
|
12681
11935
|
|
|
11936
|
+
${BOLD9}Behavior:${RESET16}
|
|
11937
|
+
Includes the typed contract capsule path when .decantr/graph exists. Run decantr graph first
|
|
11938
|
+
when you want graph-backed agent context in CLI-only workflows.
|
|
11939
|
+
|
|
12682
11940
|
${BOLD9}Examples:${RESET16}
|
|
12683
11941
|
decantr task /feed "add saved recipe actions"
|
|
12684
11942
|
decantr task /feed "add saved recipe actions" --since origin/main
|
|
@@ -12706,7 +11964,7 @@ ${BOLD9}Examples:${RESET16}
|
|
|
12706
11964
|
}
|
|
12707
11965
|
function cmdContentHelp() {
|
|
12708
11966
|
console.log(`
|
|
12709
|
-
${BOLD9}decantr content${RESET16} \u2014 Content-author namespace for
|
|
11967
|
+
${BOLD9}decantr content${RESET16} \u2014 Content-author namespace for official-vocabulary repositories
|
|
12710
11968
|
|
|
12711
11969
|
${BOLD9}Usage:${RESET16}
|
|
12712
11970
|
decantr content check [content-health options]
|
|
@@ -12734,6 +11992,9 @@ function printCommandHelp(command, args) {
|
|
|
12734
11992
|
case "verify":
|
|
12735
11993
|
cmdVerifyHelp();
|
|
12736
11994
|
return true;
|
|
11995
|
+
case "graph":
|
|
11996
|
+
cmdGraphHelp();
|
|
11997
|
+
return true;
|
|
12737
11998
|
case "ci":
|
|
12738
11999
|
cmdCiHelp();
|
|
12739
12000
|
return true;
|
|
@@ -12785,10 +12046,10 @@ async function main() {
|
|
|
12785
12046
|
if (command === "--version" || command === "-v" || command === "version") {
|
|
12786
12047
|
try {
|
|
12787
12048
|
const here = dirname6(fileURLToPath3(import.meta.url));
|
|
12788
|
-
const candidates = [
|
|
12049
|
+
const candidates = [join30(here, "..", "package.json"), join30(here, "..", "..", "package.json")];
|
|
12789
12050
|
for (const candidate of candidates) {
|
|
12790
|
-
if (
|
|
12791
|
-
const pkg = JSON.parse(
|
|
12051
|
+
if (existsSync28(candidate)) {
|
|
12052
|
+
const pkg = JSON.parse(readFileSync21(candidate, "utf-8"));
|
|
12792
12053
|
if (pkg.version) {
|
|
12793
12054
|
console.log(pkg.version);
|
|
12794
12055
|
return;
|
|
@@ -12827,6 +12088,51 @@ async function main() {
|
|
|
12827
12088
|
await cmdVerifyWorkflow(args);
|
|
12828
12089
|
break;
|
|
12829
12090
|
}
|
|
12091
|
+
case "graph": {
|
|
12092
|
+
const { flags } = parseLooseArgs(args);
|
|
12093
|
+
if (!ensureAllowedFlags(
|
|
12094
|
+
flags,
|
|
12095
|
+
[
|
|
12096
|
+
"project",
|
|
12097
|
+
"route",
|
|
12098
|
+
"node",
|
|
12099
|
+
"file",
|
|
12100
|
+
"task",
|
|
12101
|
+
"snapshot-id",
|
|
12102
|
+
"compare-to",
|
|
12103
|
+
"include-diff-ops",
|
|
12104
|
+
"impact",
|
|
12105
|
+
"limit",
|
|
12106
|
+
"capsule-source-limit",
|
|
12107
|
+
"check",
|
|
12108
|
+
"json"
|
|
12109
|
+
],
|
|
12110
|
+
"graph"
|
|
12111
|
+
))
|
|
12112
|
+
break;
|
|
12113
|
+
const workspaceInfo = resolveWorkflowProject(flags, "graph");
|
|
12114
|
+
if (!workspaceInfo) break;
|
|
12115
|
+
const limitArg = flagString(flags, "limit");
|
|
12116
|
+
const limit = limitArg ? Number(limitArg) : void 0;
|
|
12117
|
+
const capsuleSourceLimitArg = flagString(flags, "capsule-source-limit");
|
|
12118
|
+
const capsuleSourceLimit = capsuleSourceLimitArg ? Number(capsuleSourceLimitArg) : void 0;
|
|
12119
|
+
await cmdGraph(workspaceInfo.appRoot, {
|
|
12120
|
+
check: flagBoolean(flags, "check"),
|
|
12121
|
+
json: flagBoolean(flags, "json"),
|
|
12122
|
+
route: flagString(flags, "route"),
|
|
12123
|
+
node: flagString(flags, "node"),
|
|
12124
|
+
file: flagString(flags, "file"),
|
|
12125
|
+
impact: flagBoolean(flags, "impact"),
|
|
12126
|
+
task: flagString(flags, "task"),
|
|
12127
|
+
snapshotId: flagString(flags, "snapshot-id"),
|
|
12128
|
+
compareTo: flagString(flags, "compare-to"),
|
|
12129
|
+
includeDiffOps: flagBoolean(flags, "include-diff-ops"),
|
|
12130
|
+
limit,
|
|
12131
|
+
capsuleSourceLimit,
|
|
12132
|
+
displayRoot: workspaceInfo.cwd
|
|
12133
|
+
});
|
|
12134
|
+
break;
|
|
12135
|
+
}
|
|
12830
12136
|
case "ci": {
|
|
12831
12137
|
await cmdCi(args, process.cwd());
|
|
12832
12138
|
break;
|
|
@@ -12970,7 +12276,7 @@ async function main() {
|
|
|
12970
12276
|
const { flags } = parseLooseArgs(args);
|
|
12971
12277
|
const workspaceInfo = resolveWorkflowProject(flags, "health");
|
|
12972
12278
|
if (!workspaceInfo) break;
|
|
12973
|
-
const { cmdHealth, parseHealthArgs } = await import("./health-
|
|
12279
|
+
const { cmdHealth, parseHealthArgs } = await import("./health-ASR7RWZJ.js");
|
|
12974
12280
|
await cmdHealth(workspaceInfo.appRoot, parseHealthArgs(stripProjectArgs(args)));
|
|
12975
12281
|
} catch (e) {
|
|
12976
12282
|
console.error(error2(e.message));
|
|
@@ -12998,7 +12304,7 @@ async function main() {
|
|
|
12998
12304
|
cmdStudioHelp();
|
|
12999
12305
|
break;
|
|
13000
12306
|
}
|
|
13001
|
-
const { cmdStudio, parseStudioArgs } = await import("./studio-
|
|
12307
|
+
const { cmdStudio, parseStudioArgs } = await import("./studio-R65NXPJW.js");
|
|
13002
12308
|
await cmdStudio(process.cwd(), parseStudioArgs(args));
|
|
13003
12309
|
} catch (e) {
|
|
13004
12310
|
console.error(error2(e.message));
|
|
@@ -13012,7 +12318,7 @@ async function main() {
|
|
|
13012
12318
|
cmdWorkspaceHelp();
|
|
13013
12319
|
break;
|
|
13014
12320
|
}
|
|
13015
|
-
const { cmdWorkspace } = await import("./workspace-
|
|
12321
|
+
const { cmdWorkspace } = await import("./workspace-2FFQEEQQ.js");
|
|
13016
12322
|
await cmdWorkspace(process.cwd(), args);
|
|
13017
12323
|
} catch (e) {
|
|
13018
12324
|
console.error(error2(e.message));
|
|
@@ -13109,7 +12415,7 @@ async function main() {
|
|
|
13109
12415
|
if (!query && (route || file || fromCode)) {
|
|
13110
12416
|
query = [
|
|
13111
12417
|
route ? `route ${route}` : null,
|
|
13112
|
-
file ? `file ${
|
|
12418
|
+
file ? `file ${basename2(file)}` : null,
|
|
13113
12419
|
fromCode ? "source code patterns" : null
|
|
13114
12420
|
].filter((entry) => Boolean(entry)).join(" ");
|
|
13115
12421
|
}
|
|
@@ -13331,7 +12637,7 @@ async function main() {
|
|
|
13331
12637
|
break;
|
|
13332
12638
|
}
|
|
13333
12639
|
if (packType === "page" && route && !id) {
|
|
13334
|
-
const resolvedPath = essencePath ? resolveUserPath(essencePath) :
|
|
12640
|
+
const resolvedPath = essencePath ? resolveUserPath(essencePath) : join30(process.cwd(), "decantr.essence.json");
|
|
13335
12641
|
id = resolvePagePackIdForRoute(resolvedPath, route);
|
|
13336
12642
|
}
|
|
13337
12643
|
await printHostedSelectedExecutionPack(
|