@decantr/cli 2.12.0 → 2.13.1
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 +13 -2
- package/dist/bin.js +5 -5
- package/dist/{chunk-PHRVC575.js → chunk-3A2DLR47.js} +5 -3
- package/dist/{chunk-SWPAAAO4.js → chunk-5MZH6XXQ.js} +547 -18
- package/dist/{chunk-CAKQWTZS.js → chunk-MNZKOZW7.js} +11 -1
- package/dist/{chunk-IQZCIMQJ.js → chunk-SIDKK73N.js} +24 -2
- package/dist/{chunk-F6SWDGKO.js → chunk-YBSBAJ3E.js} +1 -1
- package/dist/{heal-WMSAX4LU.js → heal-IRIDB7IZ.js} +1 -1
- package/dist/{health-DBCUR6VQ.js → health-B4W7UJBZ.js} +2 -2
- package/dist/index.js +5 -5
- package/dist/{studio-L6JJQYCS.js → studio-JOEECLI6.js} +3 -3
- package/dist/{upgrade-7ZYWR2GH.js → upgrade-WBFE6EN7.js} +1 -1
- package/dist/{workspace-BCA5RCHN.js → workspace-7RU77ZZW.js} +3 -3
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -14,10 +14,12 @@ npm install -D @decantr/cli
|
|
|
14
14
|
Or run it without installing:
|
|
15
15
|
|
|
16
16
|
```bash
|
|
17
|
+
npx @decantr/cli scan
|
|
17
18
|
npx @decantr/cli new my-app --blueprint=esports-hq
|
|
18
19
|
```
|
|
19
20
|
|
|
20
21
|
Use `decantr setup` when you are unsure which path applies. It detects whether the repo is empty, already attached, or a Brownfield app and recommends the right entry path.
|
|
22
|
+
Use `decantr scan` when you want a zero-commit Brownfield preview. It reads local files, detects framework/routes/styling/static-hosting/assistant-rule signals, prints a terminal report, and writes no `.decantr` files or report artifacts. Add `--json` when automation needs the `ScanReportV1` payload.
|
|
21
23
|
Use `decantr new` for a greenfield workspace in a fresh directory. With a blueprint/archetype it uses the runnable adapter and Decantr CSS; without registry content it creates a contract-only workspace unless you explicitly pass `--adoption=decantr-css`.
|
|
22
24
|
Use `decantr adopt` when you already have an app and want Decantr governance without adopting a blueprint. Brownfield attach is proposal-driven: Decantr inventories the app, writes an observed essence proposal, hydrates hosted execution packs when online, and only applies the contract when you explicitly accept or merge it.
|
|
23
25
|
Use `decantr doctor` when the next step is unclear, `decantr task` before asking an LLM to modify a route, `decantr verify` after the edit, and `decantr ci` in required automation. Use `decantr codify --from-audit --style-bridge` when you want project-owned UI patterns, local rules, and token/class bridge mappings such as button/card/shell/theme standards to appear in future task context and verification. Once accepted, that local law is the first Hybrid lane: the app still owns source and styling, but Decantr treats accepted local patterns, rules, and style bridge mappings as project authority.
|
|
@@ -41,11 +43,14 @@ Explicit workflow/adoption flags:
|
|
|
41
43
|
|
|
42
44
|
```bash
|
|
43
45
|
decantr setup
|
|
46
|
+
decantr scan
|
|
47
|
+
decantr scan --project apps/web --json
|
|
44
48
|
decantr adopt --yes
|
|
45
49
|
decantr doctor
|
|
46
50
|
decantr ci --fail-on error
|
|
47
51
|
decantr ci init
|
|
48
52
|
decantr codify --from-audit --style-bridge
|
|
53
|
+
decantr codify --map-pattern hero
|
|
49
54
|
decantr codify --accept
|
|
50
55
|
decantr task /feed "add saved recipe actions"
|
|
51
56
|
decantr verify --brownfield --local-patterns
|
|
@@ -76,16 +81,18 @@ pnpm exec decantr workspace list
|
|
|
76
81
|
pnpm exec decantr adopt --project apps/web --yes
|
|
77
82
|
pnpm exec decantr doctor --project apps/web
|
|
78
83
|
pnpm exec decantr codify --from-audit --style-bridge --project apps/web
|
|
84
|
+
pnpm exec decantr codify --map-pattern hero --project apps/web
|
|
79
85
|
pnpm exec decantr ci init --project apps/web
|
|
80
86
|
```
|
|
81
87
|
|
|
82
88
|
Assistant rule integration is preview-first: `--assistant-bridge=preview` writes `.decantr/context/assistant-bridge.md`, `decantr rules preview` prints the bridge, and `--assistant-bridge=apply` or `decantr rules apply` mutates supported rule files with idempotent marked blocks.
|
|
83
89
|
|
|
84
|
-
Brownfield
|
|
90
|
+
`decantr scan` is different from the mutating Brownfield primitives: it is look-don't-touch reconnaissance for fit, route/style evidence, GitHub Pages hints, and next-command guidance. `decantr analyze` writes `.decantr/doctrine-map.json`, a ranked source-precedence map across security/data, architecture, design-system, workflow/CI, feature/business, assistant-specific, stale, and unsafe-to-cite evidence. It also writes `.decantr/brownfield-intelligence.json`, `.decantr/theme-inventory.json`, and `.decantr/enrichment-backlog.md`. The proposal groups routes into observed semantic domains such as auth, RBAC, billing, reporting, facilities, settings, and public surfaces across Next App/Pages Router, React Router, Angular Router, SvelteKit, Vue Router, and Nuxt file routes. Existing styling systems such as Tailwind, Bootstrap, MUI, Chakra, plain CSS, and Decantr CSS are observed as evidence instead of replaced. Theme variants are observed in the theme inventory without changing Essence V4. `decantr codify --from-audit` proposes `.decantr/local-patterns.proposal.json` and `.decantr/rules.proposal.json` with Hybrid authority guidance, source-derived button/card/form/theme evidence, variant hints, and confidence tiers; `decantr codify --style-bridge` proposes `.decantr/style-bridge.proposal.json`, mapping Decantr intent to project-owned tokens/classes without requiring `@decantr/css`; `decantr codify --map-pattern <slug>` maps a hosted or bundled registry pattern into an advisory local-law proposal without changing source. After review, `decantr codify --accept` promotes whichever proposals exist. `decantr doctor` reports whether the app is contract-only, Hybrid local law, style bridge, Decantr CSS, or Hybrid composition. `decantr task` prints that authority block and warns before mixing runtimes or adding Decantr CSS to a non-Decantr-CSS app. `decantr suggest --from-code` surfaces accepted local patterns and style bridge mappings from the app root or selected `--project`, and `decantr ci` prints accepted local-rule findings plus style bridge status in text, markdown, and JSON reports. `decantr verify --brownfield --local-patterns` uses the Brownfield guard layer plus accepted local law to flag actionable missing doctrine coverage, unsafe context, missing assistant bridges, style drift, raw local-rule violations, and unsafe defaults without treating current database migrations as stale docs.
|
|
85
91
|
|
|
86
92
|
## What It Does
|
|
87
93
|
|
|
88
94
|
- scaffolds Decantr projects from blueprints, archetypes, or prompts
|
|
95
|
+
- previews existing apps with read-only Brownfield scan reports
|
|
89
96
|
- guides users through human workflow commands: setup, adopt, doctor, task, verify, ci, and codify
|
|
90
97
|
- supports explicit workflow lanes: greenfield blueprint, greenfield contract-only, brownfield adoption, Hybrid local law, Hybrid style bridge, Hybrid Decantr CSS, and hybrid composition
|
|
91
98
|
- generates execution-pack context files for AI coding assistants
|
|
@@ -99,7 +106,7 @@ Brownfield analysis also writes `.decantr/doctrine-map.json`, a ranked source-pr
|
|
|
99
106
|
|
|
100
107
|
## Security And Permissions
|
|
101
108
|
|
|
102
|
-
The CLI is intentionally a local project inspector and artifact writer. It reads selected project/workspace files, package manifests, routing/style/config files, `.decantr` artifacts, and Decantr cache/config files. It writes `decantr.essence.json`, `DECANTR.md`, `.decantr/*`, generated context packs, optional CI workflows/snippets, optional style/export files, and auth/telemetry config only when explicitly requested.
|
|
109
|
+
The CLI is intentionally a local project inspector and artifact writer. It reads selected project/workspace files, package manifests, routing/style/config files, `.decantr` artifacts, and Decantr cache/config files. It writes `decantr.essence.json`, `DECANTR.md`, `.decantr/*`, generated context packs, optional CI workflows/snippets, optional style/export files, and auth/telemetry config only when explicitly requested. `decantr scan` is the exception by design: it reads and prints only, and does not create `.decantr`, save reports, upload source, run package scripts, or install dependencies.
|
|
103
110
|
|
|
104
111
|
Telemetry is disabled by default. Hosted registry, hosted pack hydration, hosted critique/audit, and browser evidence are explicit command paths; screenshots and Evidence Bundles stay local unless a hosted workflow is invoked. Release audits prove the installed package with `npm pack --dry-run --json`. See [security permissions](https://decantr.ai/reference/security-permissions.md).
|
|
105
112
|
|
|
@@ -107,12 +114,16 @@ Telemetry is disabled by default. Hosted registry, hosted pack hydration, hosted
|
|
|
107
114
|
|
|
108
115
|
```bash
|
|
109
116
|
decantr setup
|
|
117
|
+
decantr scan
|
|
118
|
+
decantr scan --project apps/web
|
|
119
|
+
decantr scan --json
|
|
110
120
|
decantr new my-app --blueprint=esports-hq
|
|
111
121
|
decantr adopt --yes
|
|
112
122
|
decantr adopt --project apps/web --yes
|
|
113
123
|
decantr doctor
|
|
114
124
|
decantr doctor --project apps/web
|
|
115
125
|
decantr codify --from-audit --style-bridge
|
|
126
|
+
decantr codify --map-pattern hero --project apps/web
|
|
116
127
|
decantr codify --accept
|
|
117
128
|
decantr task /feed "add saved recipe actions"
|
|
118
129
|
decantr verify --brownfield --local-patterns
|
package/dist/bin.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import "./chunk-
|
|
3
|
-
import "./chunk-
|
|
4
|
-
import "./chunk-
|
|
5
|
-
import "./chunk-
|
|
6
|
-
import "./chunk-
|
|
2
|
+
import "./chunk-5MZH6XXQ.js";
|
|
3
|
+
import "./chunk-SIDKK73N.js";
|
|
4
|
+
import "./chunk-YBSBAJ3E.js";
|
|
5
|
+
import "./chunk-3A2DLR47.js";
|
|
6
|
+
import "./chunk-MNZKOZW7.js";
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
sendProjectHealthCiFailedTelemetry,
|
|
4
4
|
sendProjectHealthPromptTelemetry,
|
|
5
5
|
sendProjectHealthReportTelemetry
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-MNZKOZW7.js";
|
|
7
7
|
|
|
8
8
|
// src/commands/health.ts
|
|
9
9
|
import { execFileSync } from "child_process";
|
|
@@ -91,8 +91,10 @@ function listWorkspaceAppCandidates(workspaceRoot) {
|
|
|
91
91
|
}
|
|
92
92
|
function resolveWorkspaceInfo(cwd, projectArg) {
|
|
93
93
|
const absoluteCwd = resolve(cwd);
|
|
94
|
-
const
|
|
95
|
-
|
|
94
|
+
const appRoot = projectArg ? resolve(
|
|
95
|
+
isAbsolute(projectArg) ? projectArg : join(findWorkspaceRoot(absoluteCwd) ?? absoluteCwd, projectArg)
|
|
96
|
+
) : absoluteCwd;
|
|
97
|
+
const workspaceRoot = projectArg && isAbsolute(projectArg) ? findWorkspaceRoot(appRoot) ?? appRoot : findWorkspaceRoot(absoluteCwd) ?? absoluteCwd;
|
|
96
98
|
const appCandidates = listWorkspaceApps(workspaceRoot);
|
|
97
99
|
const projectScope = workspaceRoot !== appRoot || appCandidates.length > 0 ? "workspace-app" : "single-app";
|
|
98
100
|
const requiresProjectSelection = !projectArg && workspaceRoot === absoluteCwd && appCandidates.length > 0;
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
scaffoldProject,
|
|
15
15
|
syncRegistry,
|
|
16
16
|
writeExecutionPackBundleArtifacts
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-SIDKK73N.js";
|
|
18
18
|
import {
|
|
19
19
|
createWorkspaceHealthReport,
|
|
20
20
|
formatWorkspaceHealthMarkdown,
|
|
@@ -22,14 +22,14 @@ import {
|
|
|
22
22
|
listWorkspaceCandidates,
|
|
23
23
|
listWorkspaceProjects,
|
|
24
24
|
shouldFailWorkspaceHealth
|
|
25
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-YBSBAJ3E.js";
|
|
26
26
|
import {
|
|
27
27
|
createProjectHealthReport,
|
|
28
28
|
formatProjectHealthMarkdown,
|
|
29
29
|
formatProjectHealthText,
|
|
30
30
|
resolveWorkspaceInfo,
|
|
31
31
|
shouldFailHealth
|
|
32
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-3A2DLR47.js";
|
|
33
33
|
import {
|
|
34
34
|
buildGuardRegistryContext,
|
|
35
35
|
createDoctrineMap,
|
|
@@ -46,7 +46,7 @@ import {
|
|
|
46
46
|
sendCliCommandTelemetry,
|
|
47
47
|
sendNewProjectCompletedTelemetry,
|
|
48
48
|
writeDoctrineMap
|
|
49
|
-
} from "./chunk-
|
|
49
|
+
} from "./chunk-MNZKOZW7.js";
|
|
50
50
|
|
|
51
51
|
// src/index.ts
|
|
52
52
|
import { existsSync as existsSync30, mkdirSync as mkdirSync17, readdirSync as readdirSync9, readFileSync as readFileSync23, writeFileSync as writeFileSync20 } from "fs";
|
|
@@ -68,7 +68,8 @@ import {
|
|
|
68
68
|
} from "@decantr/registry";
|
|
69
69
|
import {
|
|
70
70
|
auditProject,
|
|
71
|
-
critiqueFile as critiqueProjectFile
|
|
71
|
+
critiqueFile as critiqueProjectFile,
|
|
72
|
+
scanProject as scanProjectReadOnly
|
|
72
73
|
} from "@decantr/verifier";
|
|
73
74
|
|
|
74
75
|
// src/artifacts.ts
|
|
@@ -3327,6 +3328,22 @@ function createBrownfieldCodifyProposal(input) {
|
|
|
3327
3328
|
componentPaths: evidence.buttonComponents,
|
|
3328
3329
|
decide: "Define primary, secondary, tertiary, destructive, icon-only, disabled, and loading button variants from this app.",
|
|
3329
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
|
+
},
|
|
3330
3347
|
evidence: evidence.buttonComponents.length ? evidence.buttonComponents : evidence.buttonClassHints.length ? evidence.buttonClassHints : [
|
|
3331
3348
|
"No obvious Button wrapper found yet. Add the project-owned wrapper path before strict enforcement."
|
|
3332
3349
|
],
|
|
@@ -3340,6 +3357,22 @@ function createBrownfieldCodifyProposal(input) {
|
|
|
3340
3357
|
componentPaths: evidence.cardComponents,
|
|
3341
3358
|
decide: "Define the canonical card background, border, radius, shadow, padding, density, and hover treatment.",
|
|
3342
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
|
+
},
|
|
3343
3376
|
evidence: evidence.cardComponents.length ? evidence.cardComponents : [
|
|
3344
3377
|
"No obvious Card wrapper found yet. Add the project-owned wrapper path or class recipe."
|
|
3345
3378
|
],
|
|
@@ -3352,6 +3385,24 @@ function createBrownfieldCodifyProposal(input) {
|
|
|
3352
3385
|
appliesTo: ["routes", "layouts", "navigation shells", "scroll containers"],
|
|
3353
3386
|
componentPaths: evidence.shellComponents,
|
|
3354
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
|
+
},
|
|
3355
3406
|
evidence: evidence.shellComponents.length ? evidence.shellComponents : ["Add root layout, shell, or app frame files that establish route chrome and spacing."],
|
|
3356
3407
|
forbiddenAlternatives: [
|
|
3357
3408
|
"Each page inventing independent max-width, padding, or sticky nav rules."
|
|
@@ -3364,6 +3415,20 @@ function createBrownfieldCodifyProposal(input) {
|
|
|
3364
3415
|
appliesTo: ["inputs", "selects", "textareas", "validation messages", "form actions"],
|
|
3365
3416
|
componentPaths: evidence.formComponents,
|
|
3366
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
|
+
},
|
|
3367
3432
|
evidence: evidence.formComponents.length ? evidence.formComponents : ["Add form field wrapper paths and validation examples."],
|
|
3368
3433
|
forbiddenAlternatives: [
|
|
3369
3434
|
"Unlabeled one-off inputs or validation states that do not match the app standard."
|
|
@@ -3376,6 +3441,20 @@ function createBrownfieldCodifyProposal(input) {
|
|
|
3376
3441
|
appliesTo: ["theme toggles", "mode-specific classes", "brand variants", "tenant variants"],
|
|
3377
3442
|
componentPaths: evidence.themeComponents,
|
|
3378
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
|
+
},
|
|
3379
3458
|
evidence: evidence.themeComponents.length ? evidence.themeComponents : ["If the app has dark/light or brand variants, add the toggles/providers here."],
|
|
3380
3459
|
forbiddenAlternatives: [
|
|
3381
3460
|
"Component-local theme forks that bypass shared theme providers or tokens."
|
|
@@ -3467,6 +3546,107 @@ function writeBrownfieldCodifyProposal(projectRoot, proposal) {
|
|
|
3467
3546
|
`, "utf-8");
|
|
3468
3547
|
return { patternPath, rulesPath };
|
|
3469
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
|
+
}
|
|
3470
3650
|
function acceptBrownfieldLocalLaw(projectRoot) {
|
|
3471
3651
|
const patternProposal = readJsonFile(localPatternsProposalPath(projectRoot));
|
|
3472
3652
|
const ruleProposal = readJsonFile(localRulesProposalPath(projectRoot));
|
|
@@ -3509,11 +3689,17 @@ function validateLocalLaw(projectRoot) {
|
|
|
3509
3689
|
const paths = Array.isArray(pattern.componentPaths) ? pattern.componentPaths : [];
|
|
3510
3690
|
const evidence = Array.isArray(pattern.evidence) ? pattern.evidence : [];
|
|
3511
3691
|
const todoEvidence = Array.isArray(pattern.evidenceToCollect) ? pattern.evidenceToCollect : [];
|
|
3692
|
+
const hostedRefs = Array.isArray(pattern.hostedPatternRefs) ? pattern.hostedPatternRefs : [];
|
|
3512
3693
|
if (id && paths.length === 0 && evidence.length === 0 && todoEvidence.length > 0) {
|
|
3513
3694
|
warnings.push(
|
|
3514
3695
|
`Local pattern ${id} still reads like a TODO; add concrete component paths or evidence.`
|
|
3515
3696
|
);
|
|
3516
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
|
+
}
|
|
3517
3703
|
}
|
|
3518
3704
|
}
|
|
3519
3705
|
if (ruleManifest && !Array.isArray(ruleManifest.rules)) {
|
|
@@ -3535,7 +3721,10 @@ function createLocalLawTaskSummary(projectRoot) {
|
|
|
3535
3721
|
const patterns = (patternPack?.patterns ?? []).map((pattern) => ({
|
|
3536
3722
|
id: typeof pattern.id === "string" ? pattern.id : "unknown",
|
|
3537
3723
|
role: typeof pattern.role === "string" ? pattern.role : null,
|
|
3538
|
-
componentPaths: Array.isArray(pattern.componentPaths) ? pattern.componentPaths.filter((entry) => typeof entry === "string") : []
|
|
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)) : []
|
|
3539
3728
|
}));
|
|
3540
3729
|
const rules = (ruleManifest?.rules ?? []).map((rule) => ({
|
|
3541
3730
|
id: rule.id,
|
|
@@ -3653,6 +3842,50 @@ function summarizeSourceEvidence(projectRoot, files) {
|
|
|
3653
3842
|
formComponents: byName(["input", "field", "form", "select", "textarea"]),
|
|
3654
3843
|
shellComponents,
|
|
3655
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
|
+
]),
|
|
3656
3889
|
buttonClassHints: collectClassHints(projectRoot, files, [
|
|
3657
3890
|
"button",
|
|
3658
3891
|
"btn",
|
|
@@ -3666,6 +3899,70 @@ function summarizeSourceEvidence(projectRoot, files) {
|
|
|
3666
3899
|
cardClassHints: collectClassHints(projectRoot, files, ["card", "panel", "surface", "tile"])
|
|
3667
3900
|
};
|
|
3668
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
|
+
}
|
|
3669
3966
|
function collectClassHints(projectRoot, files, terms) {
|
|
3670
3967
|
const hints = /* @__PURE__ */ new Map();
|
|
3671
3968
|
const wantsButton = terms.some(
|
|
@@ -3760,6 +4057,9 @@ function lineColumnAt(contents, index) {
|
|
|
3760
4057
|
column: lines[lines.length - 1].length + 1
|
|
3761
4058
|
};
|
|
3762
4059
|
}
|
|
4060
|
+
function escapeRegExp(value) {
|
|
4061
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4062
|
+
}
|
|
3763
4063
|
function lineAt(contents, line) {
|
|
3764
4064
|
return contents.split(/\r?\n/)[line - 1] ?? "";
|
|
3765
4065
|
}
|
|
@@ -4225,6 +4525,9 @@ function formatLocalLawText(summary, health) {
|
|
|
4225
4525
|
lines.push(
|
|
4226
4526
|
` Patterns: ${summary.patternsPresent ? "present" : "missing"} | Rules: ${summary.rulesPresent ? "present" : "missing"}`
|
|
4227
4527
|
);
|
|
4528
|
+
lines.push(
|
|
4529
|
+
summary.rulesPresent ? " Enforcement: Decantr scans accepted .decantr/rules.json; this command's --fail-on setting controls whether findings block." : " Enforcement: advisory only until .decantr/rules.json is accepted."
|
|
4530
|
+
);
|
|
4228
4531
|
lines.push(` Findings: ${summary.errorCount} error(s), ${summary.warnCount} warning(s)`);
|
|
4229
4532
|
for (const warning of summary.warnings.slice(0, 5)) {
|
|
4230
4533
|
lines.push(` ${DIM3}[WARN] ${warning}${RESET3}`);
|
|
@@ -4252,6 +4555,9 @@ function formatStyleBridgeText(summary) {
|
|
|
4252
4555
|
lines.push(
|
|
4253
4556
|
` Present: ${summary.present ? "yes" : "no"} | Mappings: ${summary.mappingCount} | Styling: ${summary.stylingApproach ?? "unknown"}`
|
|
4254
4557
|
);
|
|
4558
|
+
lines.push(
|
|
4559
|
+
" Enforcement: advisory style-intent mapping; pair with accepted local rules, lint, tests, or visual regression when it should block."
|
|
4560
|
+
);
|
|
4255
4561
|
if (summary.themeModes.length > 0) {
|
|
4256
4562
|
lines.push(` Theme modes: ${summary.themeModes.join(", ")}`);
|
|
4257
4563
|
}
|
|
@@ -4276,6 +4582,9 @@ function formatLocalLawMarkdown(summary, health) {
|
|
|
4276
4582
|
lines.push(
|
|
4277
4583
|
`Patterns: **${summary.patternsPresent ? "present" : "missing"}** \xB7 Rules: **${summary.rulesPresent ? "present" : "missing"}**`
|
|
4278
4584
|
);
|
|
4585
|
+
lines.push(
|
|
4586
|
+
summary.rulesPresent ? "Enforcement: Decantr scans accepted `.decantr/rules.json`; CI blocking depends on `--fail-on`." : "Enforcement: advisory only until `.decantr/rules.json` is accepted."
|
|
4587
|
+
);
|
|
4279
4588
|
lines.push("");
|
|
4280
4589
|
lines.push(`Findings: **${summary.errorCount} error(s), ${summary.warnCount} warning(s)**`);
|
|
4281
4590
|
if (summary.warnings.length > 0) {
|
|
@@ -4303,6 +4612,10 @@ function formatStyleBridgeMarkdown(summary) {
|
|
|
4303
4612
|
lines.push(
|
|
4304
4613
|
`Present: **${summary.present ? "yes" : "no"}** \xB7 Mappings: **${summary.mappingCount}** \xB7 Styling: **${summary.stylingApproach ?? "unknown"}**`
|
|
4305
4614
|
);
|
|
4615
|
+
lines.push("");
|
|
4616
|
+
lines.push(
|
|
4617
|
+
"Enforcement: advisory style-intent mapping; pair with accepted local rules, lint, tests, or visual regression when it should block."
|
|
4618
|
+
);
|
|
4306
4619
|
if (summary.themeModes.length > 0) {
|
|
4307
4620
|
lines.push("", `Theme modes: ${summary.themeModes.map((mode) => `\`${mode}\``).join(", ")}`);
|
|
4308
4621
|
}
|
|
@@ -9270,6 +9583,25 @@ function patternCandidateFromRegistryItem(item, source) {
|
|
|
9270
9583
|
{ source, slug }
|
|
9271
9584
|
);
|
|
9272
9585
|
}
|
|
9586
|
+
function hostedPatternRefFromCandidate(candidate) {
|
|
9587
|
+
const slug = candidate.slug || candidate.id;
|
|
9588
|
+
return {
|
|
9589
|
+
slug,
|
|
9590
|
+
source: candidate.source ?? "registry",
|
|
9591
|
+
name: candidate.name,
|
|
9592
|
+
description: candidate.description,
|
|
9593
|
+
tags: candidate.tags,
|
|
9594
|
+
components: candidate.components,
|
|
9595
|
+
interactions: candidate.interactions,
|
|
9596
|
+
visualBrief: candidate.visual_brief
|
|
9597
|
+
};
|
|
9598
|
+
}
|
|
9599
|
+
function findPatternCandidateBySlug(candidates, slug) {
|
|
9600
|
+
const normalized = slug.toLowerCase();
|
|
9601
|
+
return candidates.find(
|
|
9602
|
+
(candidate) => candidate.slug?.toLowerCase() === normalized || candidate.id.toLowerCase() === normalized || candidate.name?.toLowerCase() === normalized
|
|
9603
|
+
) ?? null;
|
|
9604
|
+
}
|
|
9273
9605
|
function readSuggestCodeContext(projectRoot, route, file) {
|
|
9274
9606
|
const pieces = [];
|
|
9275
9607
|
if (file) {
|
|
@@ -10936,7 +11268,12 @@ function resolveWorkflowProject(flags, commandName = "command", options = {}) {
|
|
|
10936
11268
|
}
|
|
10937
11269
|
if (projectArg && options.requireAppCandidate && workspaceInfo.appCandidates.length > 0) {
|
|
10938
11270
|
const normalizedProject = normalizedProjectPath(projectArg);
|
|
10939
|
-
const
|
|
11271
|
+
const normalizedWorkspaceProject = normalizedProjectPath(
|
|
11272
|
+
relative7(workspaceInfo.workspaceRoot, workspaceInfo.appRoot)
|
|
11273
|
+
);
|
|
11274
|
+
const knownCandidate = normalizedProject ? workspaceInfo.appCandidates.includes(normalizedProject) || Boolean(
|
|
11275
|
+
normalizedWorkspaceProject && workspaceInfo.appCandidates.includes(normalizedWorkspaceProject)
|
|
11276
|
+
) : false;
|
|
10940
11277
|
const forcePackage = flagBoolean(flags, "force-package") || flagBoolean(flags, "allow-package") || flagBoolean(flags, "force");
|
|
10941
11278
|
if (!knownCandidate && !forcePackage && !options.allowPackageProject) {
|
|
10942
11279
|
console.error(
|
|
@@ -10969,6 +11306,101 @@ function printWorkflowPlan(title, steps) {
|
|
|
10969
11306
|
}
|
|
10970
11307
|
console.log("");
|
|
10971
11308
|
}
|
|
11309
|
+
function scanSeverityColor(finding) {
|
|
11310
|
+
if (finding.severity === "success") return success3("ok");
|
|
11311
|
+
if (finding.severity === "error") return error2("error");
|
|
11312
|
+
if (finding.severity === "warn") return `${YELLOW12}warn${RESET16}`;
|
|
11313
|
+
return cyan3("info");
|
|
11314
|
+
}
|
|
11315
|
+
function formatScanApplicability(status) {
|
|
11316
|
+
if (status === "strong_fit") return success3("strong fit");
|
|
11317
|
+
if (status === "partial_fit") return `${YELLOW12}partial fit${RESET16}`;
|
|
11318
|
+
if (status === "not_applicable") return dim3("not applicable");
|
|
11319
|
+
return dim3("unknown");
|
|
11320
|
+
}
|
|
11321
|
+
function printScanReport(report) {
|
|
11322
|
+
console.log(heading2("Decantr Scan"));
|
|
11323
|
+
console.log(dim3("Read-only Brownfield reconnaissance. No files were written."));
|
|
11324
|
+
console.log("");
|
|
11325
|
+
console.log(`${BOLD9}Verdict${RESET16}`);
|
|
11326
|
+
console.log(` ${formatScanApplicability(report.applicability.status)} ${report.applicability.label}`);
|
|
11327
|
+
console.log(
|
|
11328
|
+
` Confidence: ${cyan3(`${report.confidence.score}/100`)} (${report.confidence.level})`
|
|
11329
|
+
);
|
|
11330
|
+
for (const reason of report.applicability.reasons.slice(0, 3)) {
|
|
11331
|
+
console.log(` ${dim3("-")} ${reason}`);
|
|
11332
|
+
}
|
|
11333
|
+
console.log("");
|
|
11334
|
+
console.log(`${BOLD9}Project${RESET16}`);
|
|
11335
|
+
console.log(` Framework: ${cyan3(report.project.framework)}${report.project.frameworkVersion ? ` ${report.project.frameworkVersion}` : ""}`);
|
|
11336
|
+
console.log(` Package manager:${" "} ${report.project.packageManager}`);
|
|
11337
|
+
console.log(` Language: ${report.project.primaryLanguage}`);
|
|
11338
|
+
console.log(` TypeScript: ${report.project.hasTypeScript ? "yes" : "no"}`);
|
|
11339
|
+
console.log(` Decantr: ${report.project.hasDecantr ? "present" : "not attached"}`);
|
|
11340
|
+
console.log("");
|
|
11341
|
+
console.log(`${BOLD9}Routes And Styling${RESET16}`);
|
|
11342
|
+
console.log(` Routes: ${report.routes.count} (${report.routes.strategy})`);
|
|
11343
|
+
for (const route of report.routes.items.slice(0, 8)) {
|
|
11344
|
+
console.log(` ${cyan3(route.path.padEnd(18))} ${dim3(route.file)}`);
|
|
11345
|
+
}
|
|
11346
|
+
if (report.routes.items.length > 8) {
|
|
11347
|
+
console.log(` ${dim3(`...${report.routes.items.length - 8} more route(s)`)}`);
|
|
11348
|
+
}
|
|
11349
|
+
console.log(` Components: ${report.components.componentCount}`);
|
|
11350
|
+
console.log(
|
|
11351
|
+
` Styling: ${report.styling.approach}${report.styling.configFile ? ` (${report.styling.configFile})` : ""}`
|
|
11352
|
+
);
|
|
11353
|
+
console.log(` CSS variables: ${report.styling.cssVariableCount}`);
|
|
11354
|
+
console.log(` Dark mode: ${report.styling.darkMode ? "yes" : "no"}`);
|
|
11355
|
+
console.log("");
|
|
11356
|
+
if (report.staticHosting.githubPagesLikely || report.pagesProbe) {
|
|
11357
|
+
console.log(`${BOLD9}Published Surface${RESET16}`);
|
|
11358
|
+
console.log(` GitHub Pages: ${report.staticHosting.githubPagesLikely ? "likely" : "not detected"}`);
|
|
11359
|
+
if (report.source.publishedSiteUrl) {
|
|
11360
|
+
console.log(` Site URL: ${report.source.publishedSiteUrl}`);
|
|
11361
|
+
}
|
|
11362
|
+
if (report.pagesProbe?.checked) {
|
|
11363
|
+
console.log(
|
|
11364
|
+
` HTTP probe: ${report.pagesProbe.reachable ? success3("reachable") : `${YELLOW12}unreachable${RESET16}`} ${report.pagesProbe.status ?? ""}`
|
|
11365
|
+
);
|
|
11366
|
+
if (report.pagesProbe.title) console.log(` Title: ${report.pagesProbe.title}`);
|
|
11367
|
+
}
|
|
11368
|
+
for (const item of report.staticHosting.evidence.slice(0, 4)) {
|
|
11369
|
+
console.log(` ${dim3("-")} ${item}`);
|
|
11370
|
+
}
|
|
11371
|
+
console.log("");
|
|
11372
|
+
}
|
|
11373
|
+
console.log(`${BOLD9}Findings${RESET16}`);
|
|
11374
|
+
for (const finding of report.findings.slice(0, 8)) {
|
|
11375
|
+
console.log(` [${scanSeverityColor(finding)}] ${finding.title}`);
|
|
11376
|
+
console.log(` ${finding.message}`);
|
|
11377
|
+
if (finding.recommendation) console.log(` ${dim3(finding.recommendation)}`);
|
|
11378
|
+
}
|
|
11379
|
+
console.log("");
|
|
11380
|
+
console.log(`${BOLD9}Next Commands${RESET16}`);
|
|
11381
|
+
for (const command of report.recommendedCommands) {
|
|
11382
|
+
console.log(` ${cyan3(command)}`);
|
|
11383
|
+
}
|
|
11384
|
+
console.log("");
|
|
11385
|
+
console.log(dim3(report.privacy.notes[0] ?? "Scan completed without writing files."));
|
|
11386
|
+
}
|
|
11387
|
+
async function cmdScanWorkflow(args) {
|
|
11388
|
+
const { flags } = parseLooseArgs(args);
|
|
11389
|
+
if (!ensureAllowedFlags(flags, ["project", "json"], "scan")) return;
|
|
11390
|
+
const workspaceInfo = resolveWorkflowProject(flags, "scan");
|
|
11391
|
+
if (!workspaceInfo) return;
|
|
11392
|
+
const jsonOutput = flagBoolean(flags, "json");
|
|
11393
|
+
const projectArg = flagString(flags, "project");
|
|
11394
|
+
const inputValue = projectArg ?? ".";
|
|
11395
|
+
const report = await scanProjectReadOnly(workspaceInfo.appRoot, {
|
|
11396
|
+
input: { kind: "local", value: inputValue }
|
|
11397
|
+
});
|
|
11398
|
+
if (jsonOutput) {
|
|
11399
|
+
console.log(JSON.stringify(report, null, 2));
|
|
11400
|
+
return;
|
|
11401
|
+
}
|
|
11402
|
+
printScanReport(report);
|
|
11403
|
+
}
|
|
10972
11404
|
async function cmdSetupWorkflow(args) {
|
|
10973
11405
|
const { flags } = parseLooseArgs(args);
|
|
10974
11406
|
const projectArg = flagString(flags, "project");
|
|
@@ -11148,7 +11580,7 @@ async function cmdAdoptWorkflow(args) {
|
|
|
11148
11580
|
console.log(dim3("Skipping hosted pack hydration in offline mode."));
|
|
11149
11581
|
}
|
|
11150
11582
|
if (runVerify) {
|
|
11151
|
-
const { cmdHealth } = await import("./health-
|
|
11583
|
+
const { cmdHealth } = await import("./health-B4W7UJBZ.js");
|
|
11152
11584
|
await cmdHealth(projectRoot, {
|
|
11153
11585
|
browser: runBrowser,
|
|
11154
11586
|
browserBaseUrl: baseUrl,
|
|
@@ -11219,7 +11651,7 @@ async function cmdVerifyWorkflow(args) {
|
|
|
11219
11651
|
return;
|
|
11220
11652
|
}
|
|
11221
11653
|
if (workspaceMode) {
|
|
11222
|
-
const { cmdWorkspace } = await import("./workspace-
|
|
11654
|
+
const { cmdWorkspace } = await import("./workspace-7RU77ZZW.js");
|
|
11223
11655
|
await cmdWorkspace(process.cwd(), ["workspace", "health", ...withoutWorkflowOnlyFlags(args)]);
|
|
11224
11656
|
return;
|
|
11225
11657
|
}
|
|
@@ -11252,7 +11684,7 @@ async function cmdVerifyWorkflow(args) {
|
|
|
11252
11684
|
}
|
|
11253
11685
|
let guardExitCode;
|
|
11254
11686
|
if (brownfield) {
|
|
11255
|
-
const { cmdHeal, collectCheckIssues } = await import("./heal-
|
|
11687
|
+
const { cmdHeal, collectCheckIssues } = await import("./heal-IRIDB7IZ.js");
|
|
11256
11688
|
if (quietOutput) {
|
|
11257
11689
|
const result = collectCheckIssues(workspaceInfo.appRoot, { brownfield: true });
|
|
11258
11690
|
guardExitCode = result.issues.some((issue) => issue.type === "error") ? 1 : void 0;
|
|
@@ -11262,7 +11694,7 @@ async function cmdVerifyWorkflow(args) {
|
|
|
11262
11694
|
process.exitCode = void 0;
|
|
11263
11695
|
}
|
|
11264
11696
|
}
|
|
11265
|
-
const { cmdHealth, parseHealthArgs } = await import("./health-
|
|
11697
|
+
const { cmdHealth, parseHealthArgs } = await import("./health-B4W7UJBZ.js");
|
|
11266
11698
|
await cmdHealth(workspaceInfo.appRoot, parseHealthArgs(healthArgs));
|
|
11267
11699
|
if (localPatterns) {
|
|
11268
11700
|
const validation = validateLocalLaw(workspaceInfo.appRoot);
|
|
@@ -11284,6 +11716,9 @@ async function cmdVerifyWorkflow(args) {
|
|
|
11284
11716
|
console.log(`${GREEN16}Local pattern pack found:${RESET16} ${validation.patternsPath}`);
|
|
11285
11717
|
if (validation.ruleManifestPresent) {
|
|
11286
11718
|
console.log(`${GREEN16}Local rule manifest found:${RESET16} ${validation.rulesPath}`);
|
|
11719
|
+
console.log(
|
|
11720
|
+
`${DIM16}Enforcement: accepted .decantr/rules.json is the Decantr-scanned layer; --fail-on controls whether findings block.${RESET16}`
|
|
11721
|
+
);
|
|
11287
11722
|
} else {
|
|
11288
11723
|
console.log(
|
|
11289
11724
|
`${YELLOW12}Local rule manifest missing.${RESET16} Run ${cyan3("decantr codify --from-audit")} to propose .decantr/rules.json.`
|
|
@@ -11319,6 +11754,9 @@ async function cmdVerifyWorkflow(args) {
|
|
|
11319
11754
|
console.log(
|
|
11320
11755
|
`${DIM16}${styleBridge.mappingCount} mapping(s), ${styleBridge.stylingApproach ?? "unknown"} styling${styleBridge.themeModes.length > 0 ? `, themes: ${styleBridge.themeModes.join(", ")}` : ""}${RESET16}`
|
|
11321
11756
|
);
|
|
11757
|
+
console.log(
|
|
11758
|
+
`${DIM16}Enforcement: advisory style-intent mapping; pair with accepted local rules, lint, tests, or visual regression when it should block.${RESET16}`
|
|
11759
|
+
);
|
|
11322
11760
|
}
|
|
11323
11761
|
if (guardExitCode && guardExitCode !== 0 && (!process.exitCode || process.exitCode === 0)) {
|
|
11324
11762
|
process.exitCode = guardExitCode;
|
|
@@ -11548,10 +11986,17 @@ async function cmdTaskWorkflow(args) {
|
|
|
11548
11986
|
}
|
|
11549
11987
|
if (context.localLaw.rulesPath) {
|
|
11550
11988
|
console.log(` Rules: ${cyan3(context.localLaw.rulesPath)} (${context.localLaw.ruleCount})`);
|
|
11989
|
+
console.log(" Enforcement: accepted local rules are the Decantr-scanned layer.");
|
|
11551
11990
|
}
|
|
11552
11991
|
for (const pattern of context.localLaw.patterns.slice(0, 4)) {
|
|
11553
11992
|
const pathHint = pattern.componentPaths.length > 0 ? ` \u2014 ${pattern.componentPaths.slice(0, 2).join(", ")}` : "";
|
|
11554
|
-
|
|
11993
|
+
const authorityHint = [
|
|
11994
|
+
pattern.role ?? "local pattern",
|
|
11995
|
+
pattern.confidenceTier ? `confidence ${pattern.confidenceTier}` : null,
|
|
11996
|
+
pattern.enforcementLevel ? `${pattern.enforcementLevel}` : null,
|
|
11997
|
+
pattern.hostedPatternRefs.length > 0 ? `maps ${pattern.hostedPatternRefs.slice(0, 2).join(", ")}` : null
|
|
11998
|
+
].filter(Boolean).join(" | ");
|
|
11999
|
+
console.log(` ${pattern.id}: ${authorityHint}${pathHint}`);
|
|
11555
12000
|
}
|
|
11556
12001
|
} else {
|
|
11557
12002
|
console.log("");
|
|
@@ -11566,6 +12011,9 @@ async function cmdTaskWorkflow(args) {
|
|
|
11566
12011
|
console.log(
|
|
11567
12012
|
` Bridge: ${cyan3(context.styleBridge.path)} (${context.styleBridge.mappingCount} mappings, ${context.styleBridge.stylingApproach ?? "unknown"} styling)`
|
|
11568
12013
|
);
|
|
12014
|
+
console.log(
|
|
12015
|
+
" Enforcement: advisory mapping layer; accepted local rules or project lint/tests do the blocking."
|
|
12016
|
+
);
|
|
11569
12017
|
if (context.styleBridge.themeModes.length > 0) {
|
|
11570
12018
|
console.log(` Theme modes: ${context.styleBridge.themeModes.join(", ")}`);
|
|
11571
12019
|
}
|
|
@@ -11605,6 +12053,8 @@ async function cmdCodifyWorkflow(args) {
|
|
|
11605
12053
|
"discover-local-patterns",
|
|
11606
12054
|
"codify-local-patterns",
|
|
11607
12055
|
"style-bridge",
|
|
12056
|
+
"map-pattern",
|
|
12057
|
+
"hosted-pattern",
|
|
11608
12058
|
"accept"
|
|
11609
12059
|
],
|
|
11610
12060
|
"codify"
|
|
@@ -11652,6 +12102,48 @@ async function cmdCodifyWorkflow(args) {
|
|
|
11652
12102
|
);
|
|
11653
12103
|
const fromAudit = flagBoolean(flags, "from-audit") || flagBoolean(flags, "discover-local-patterns") || flagBoolean(flags, "codify-local-patterns");
|
|
11654
12104
|
const wantsStyleBridge = flagBoolean(flags, "style-bridge");
|
|
12105
|
+
const mapPatternSlug = flagString(flags, "map-pattern") ?? flagString(flags, "hosted-pattern");
|
|
12106
|
+
if (mapPatternSlug) {
|
|
12107
|
+
const registryClient = new RegistryClient({
|
|
12108
|
+
cacheDir: join32(workspaceInfo.appRoot, ".decantr", "cache")
|
|
12109
|
+
});
|
|
12110
|
+
const candidates = await loadPatternDiscoveryCandidates(registryClient);
|
|
12111
|
+
const candidate = findPatternCandidateBySlug(candidates, mapPatternSlug);
|
|
12112
|
+
if (!candidate) {
|
|
12113
|
+
const suggestions = rankPatternCandidates({ query: mapPatternSlug, limit: 5 }, candidates);
|
|
12114
|
+
console.error(error2(`Could not find pattern "${mapPatternSlug}" to map into local law.`));
|
|
12115
|
+
if (suggestions.length > 0) {
|
|
12116
|
+
console.error(dim3("Closest registry patterns:"));
|
|
12117
|
+
for (const suggestion of suggestions) {
|
|
12118
|
+
const slug2 = suggestion.candidate.slug || suggestion.candidate.id;
|
|
12119
|
+
console.error(dim3(` ${slug2} - ${suggestion.candidate.name ?? slug2}`));
|
|
12120
|
+
}
|
|
12121
|
+
}
|
|
12122
|
+
process.exitCode = 1;
|
|
12123
|
+
return;
|
|
12124
|
+
}
|
|
12125
|
+
const result = writeHostedPatternMappingProposal({
|
|
12126
|
+
projectRoot: workspaceInfo.appRoot,
|
|
12127
|
+
hostedPattern: hostedPatternRefFromCandidate(candidate)
|
|
12128
|
+
});
|
|
12129
|
+
const slug = candidate.slug || candidate.id;
|
|
12130
|
+
console.log(
|
|
12131
|
+
success3(
|
|
12132
|
+
`${result.replacedExisting ? "Updated" : "Wrote"} hosted pattern mapping proposal: ${result.patternPath}`
|
|
12133
|
+
)
|
|
12134
|
+
);
|
|
12135
|
+
console.log(
|
|
12136
|
+
dim3(
|
|
12137
|
+
`Mapped registry pattern "${slug}" into local pattern "${result.localPatternId}" as advisory Hybrid law. No source files were changed.`
|
|
12138
|
+
)
|
|
12139
|
+
);
|
|
12140
|
+
console.log(
|
|
12141
|
+
dim3(
|
|
12142
|
+
`Fill in project-owned component paths, token/class recipes, variants, and exceptions, then run \`${withProject("decantr codify --accept", projectArg)}\`.`
|
|
12143
|
+
)
|
|
12144
|
+
);
|
|
12145
|
+
return;
|
|
12146
|
+
}
|
|
11655
12147
|
const wantsLocalLaw = !wantsStyleBridge || fromAudit;
|
|
11656
12148
|
let localResult = null;
|
|
11657
12149
|
if (wantsLocalLaw) {
|
|
@@ -11741,13 +12233,14 @@ ${BOLD9}decantr${RESET16} \u2014 Design intelligence for AI-generated UI
|
|
|
11741
12233
|
|
|
11742
12234
|
${BOLD9}Usage:${RESET16}
|
|
11743
12235
|
decantr setup [--project <path>]
|
|
12236
|
+
decantr scan [--project <path>] [--json]
|
|
11744
12237
|
decantr new <name> [--blueprint=X] [--archetype=X] [--theme=X] [--workflow=greenfield] [--adoption=decantr-css] [--telemetry]
|
|
11745
12238
|
decantr adopt [--project <path>] [--base-url <url>] [--evidence] [--ci] [--no-packs] [--yes]
|
|
11746
12239
|
decantr task <route> ["task summary"] [--project <path>] [--since origin/main] [--json]
|
|
11747
12240
|
decantr verify [--project <path>] [--brownfield] [--local-patterns] [health options]
|
|
11748
12241
|
decantr ci [--project <path>] [--workspace] [--fail-on error|warn|none]
|
|
11749
12242
|
decantr doctor [--project <path>] [--workspace] [--json]
|
|
11750
|
-
decantr codify [--from-audit] [--style-bridge] [--accept] [--project <path>]
|
|
12243
|
+
decantr codify [--from-audit] [--style-bridge] [--map-pattern <slug>] [--accept] [--project <path>]
|
|
11751
12244
|
decantr studio [--port 4319] [--host 127.0.0.1] [--report decantr-health.json] [--workspace]
|
|
11752
12245
|
|
|
11753
12246
|
${BOLD9}Advanced primitives:${RESET16}
|
|
@@ -11817,6 +12310,7 @@ ${BOLD9}Init Options:${RESET16}
|
|
|
11817
12310
|
|
|
11818
12311
|
${BOLD9}Commands:${RESET16}
|
|
11819
12312
|
${cyan3("setup")} Detect project state and recommend the right Decantr workflow
|
|
12313
|
+
${cyan3("scan")} Read-only Brownfield reconnaissance; no files written
|
|
11820
12314
|
${cyan3("new")} Create a new greenfield workspace and bootstrap the available starter adapter
|
|
11821
12315
|
${cyan3("adopt")} Brownfield one-liner: analyze, attach, verify, and show next steps
|
|
11822
12316
|
${cyan3("task")} Prepare route/task context, local law, evidence, and changed-file impact for an AI coding assistant
|
|
@@ -11860,6 +12354,8 @@ ${BOLD9}Advanced commands:${RESET16}
|
|
|
11860
12354
|
|
|
11861
12355
|
${BOLD9}Examples:${RESET16}
|
|
11862
12356
|
decantr setup
|
|
12357
|
+
decantr scan
|
|
12358
|
+
decantr scan --json
|
|
11863
12359
|
decantr new my-app --blueprint=carbon-ai-portal
|
|
11864
12360
|
decantr adopt --yes
|
|
11865
12361
|
decantr adopt --project apps/web --yes
|
|
@@ -11871,6 +12367,7 @@ ${BOLD9}Examples:${RESET16}
|
|
|
11871
12367
|
decantr ci --project apps/web
|
|
11872
12368
|
decantr ci init --project apps/web
|
|
11873
12369
|
decantr codify --from-audit
|
|
12370
|
+
decantr codify --map-pattern hero --project apps/web
|
|
11874
12371
|
decantr codify --accept
|
|
11875
12372
|
decantr content check --ci --fail-on error
|
|
11876
12373
|
decantr magic "AI chatbot with dark cyber theme \u2014 bold and futuristic"
|
|
@@ -11918,6 +12415,7 @@ ${BOLD9}Workflow Model:${RESET16}
|
|
|
11918
12415
|
${cyan3("Greenfield blueprint")} decantr new my-app --blueprint=X --workflow=greenfield --adoption=decantr-css
|
|
11919
12416
|
${cyan3("Greenfield contract")} decantr init --workflow=greenfield --adoption=contract-only
|
|
11920
12417
|
${cyan3("Brownfield adoption")} decantr adopt --yes
|
|
12418
|
+
${cyan3("Brownfield preview")} decantr scan -> decantr adopt --yes
|
|
11921
12419
|
${cyan3("Brownfield monorepo")} decantr adopt --project apps/web --yes
|
|
11922
12420
|
${cyan3("Daily LLM work")} decantr task <route> "<change>" -> decantr verify --brownfield --local-patterns
|
|
11923
12421
|
${cyan3("Project-owned law")} decantr codify --from-audit -> edit proposal -> decantr codify --accept
|
|
@@ -11946,6 +12444,28 @@ ${BOLD9}Examples:${RESET16}
|
|
|
11946
12444
|
decantr rules apply --project=apps/web
|
|
11947
12445
|
`);
|
|
11948
12446
|
}
|
|
12447
|
+
function cmdScanHelp() {
|
|
12448
|
+
console.log(`
|
|
12449
|
+
${BOLD9}decantr scan${RESET16} \u2014 Read-only Brownfield reconnaissance
|
|
12450
|
+
|
|
12451
|
+
${BOLD9}Usage:${RESET16}
|
|
12452
|
+
decantr scan [--project <path>] [--json]
|
|
12453
|
+
|
|
12454
|
+
${BOLD9}Options:${RESET16}
|
|
12455
|
+
--project App path inside a workspace/monorepo
|
|
12456
|
+
--json Emit the ScanReportV1 JSON to stdout
|
|
12457
|
+
|
|
12458
|
+
${BOLD9}Behavior:${RESET16}
|
|
12459
|
+
Reads local project files, detects frontend framework/routes/styling/static-hosting signals,
|
|
12460
|
+
and prints a terminal report. It does not write .decantr files, install dependencies,
|
|
12461
|
+
build the app, execute scripts, upload source, or open pull requests.
|
|
12462
|
+
|
|
12463
|
+
${BOLD9}Examples:${RESET16}
|
|
12464
|
+
decantr scan
|
|
12465
|
+
decantr scan --project apps/web
|
|
12466
|
+
decantr scan --json
|
|
12467
|
+
`);
|
|
12468
|
+
}
|
|
11949
12469
|
function isCommandHelpRequest(args) {
|
|
11950
12470
|
return args[1] === "help" || args.slice(1).some((arg) => arg === "--help" || arg === "-h");
|
|
11951
12471
|
}
|
|
@@ -12171,6 +12691,7 @@ ${BOLD9}decantr codify${RESET16} \u2014 Propose or accept project-owned Brownfie
|
|
|
12171
12691
|
|
|
12172
12692
|
${BOLD9}Usage:${RESET16}
|
|
12173
12693
|
decantr codify [--from-audit] [--style-bridge] [--project <path>]
|
|
12694
|
+
decantr codify --map-pattern <registry-pattern-slug> [--project <path>]
|
|
12174
12695
|
decantr codify --accept [--project <path>]
|
|
12175
12696
|
|
|
12176
12697
|
${BOLD9}Examples:${RESET16}
|
|
@@ -12178,6 +12699,7 @@ ${BOLD9}Examples:${RESET16}
|
|
|
12178
12699
|
decantr codify --from-audit
|
|
12179
12700
|
decantr codify --style-bridge
|
|
12180
12701
|
decantr codify --from-audit --style-bridge
|
|
12702
|
+
decantr codify --map-pattern hero --project apps/web
|
|
12181
12703
|
decantr codify --accept
|
|
12182
12704
|
decantr verify --brownfield --local-patterns
|
|
12183
12705
|
`);
|
|
@@ -12203,6 +12725,9 @@ function printCommandHelp(command, args) {
|
|
|
12203
12725
|
case "setup":
|
|
12204
12726
|
cmdSetupHelp();
|
|
12205
12727
|
return true;
|
|
12728
|
+
case "scan":
|
|
12729
|
+
cmdScanHelp();
|
|
12730
|
+
return true;
|
|
12206
12731
|
case "adopt":
|
|
12207
12732
|
cmdAdoptHelp();
|
|
12208
12733
|
return true;
|
|
@@ -12286,6 +12811,10 @@ async function main() {
|
|
|
12286
12811
|
await cmdSetupWorkflow(args);
|
|
12287
12812
|
break;
|
|
12288
12813
|
}
|
|
12814
|
+
case "scan": {
|
|
12815
|
+
await cmdScanWorkflow(args);
|
|
12816
|
+
break;
|
|
12817
|
+
}
|
|
12289
12818
|
case "adopt": {
|
|
12290
12819
|
await cmdAdoptWorkflow(args);
|
|
12291
12820
|
break;
|
|
@@ -12401,7 +12930,7 @@ async function main() {
|
|
|
12401
12930
|
break;
|
|
12402
12931
|
}
|
|
12403
12932
|
case "upgrade": {
|
|
12404
|
-
const { cmdUpgrade } = await import("./upgrade-
|
|
12933
|
+
const { cmdUpgrade } = await import("./upgrade-WBFE6EN7.js");
|
|
12405
12934
|
const { flags } = parseLooseArgs(args);
|
|
12406
12935
|
const workspaceInfo = resolveWorkflowProject(flags, "upgrade");
|
|
12407
12936
|
if (!workspaceInfo) break;
|
|
@@ -12416,7 +12945,7 @@ async function main() {
|
|
|
12416
12945
|
`${YELLOW12}Note: \`decantr heal\` is deprecated. Use \`decantr check\` instead.${RESET16}`
|
|
12417
12946
|
);
|
|
12418
12947
|
}
|
|
12419
|
-
const { cmdHeal } = await import("./heal-
|
|
12948
|
+
const { cmdHeal } = await import("./heal-IRIDB7IZ.js");
|
|
12420
12949
|
const { flags } = parseLooseArgs(args);
|
|
12421
12950
|
const workspaceInfo = resolveWorkflowProject(flags, "check");
|
|
12422
12951
|
if (!workspaceInfo) break;
|
|
@@ -12441,7 +12970,7 @@ async function main() {
|
|
|
12441
12970
|
const { flags } = parseLooseArgs(args);
|
|
12442
12971
|
const workspaceInfo = resolveWorkflowProject(flags, "health");
|
|
12443
12972
|
if (!workspaceInfo) break;
|
|
12444
|
-
const { cmdHealth, parseHealthArgs } = await import("./health-
|
|
12973
|
+
const { cmdHealth, parseHealthArgs } = await import("./health-B4W7UJBZ.js");
|
|
12445
12974
|
await cmdHealth(workspaceInfo.appRoot, parseHealthArgs(stripProjectArgs(args)));
|
|
12446
12975
|
} catch (e) {
|
|
12447
12976
|
console.error(error2(e.message));
|
|
@@ -12469,7 +12998,7 @@ async function main() {
|
|
|
12469
12998
|
cmdStudioHelp();
|
|
12470
12999
|
break;
|
|
12471
13000
|
}
|
|
12472
|
-
const { cmdStudio, parseStudioArgs } = await import("./studio-
|
|
13001
|
+
const { cmdStudio, parseStudioArgs } = await import("./studio-JOEECLI6.js");
|
|
12473
13002
|
await cmdStudio(process.cwd(), parseStudioArgs(args));
|
|
12474
13003
|
} catch (e) {
|
|
12475
13004
|
console.error(error2(e.message));
|
|
@@ -12483,7 +13012,7 @@ async function main() {
|
|
|
12483
13012
|
cmdWorkspaceHelp();
|
|
12484
13013
|
break;
|
|
12485
13014
|
}
|
|
12486
|
-
const { cmdWorkspace } = await import("./workspace-
|
|
13015
|
+
const { cmdWorkspace } = await import("./workspace-7RU77ZZW.js");
|
|
12487
13016
|
await cmdWorkspace(process.cwd(), args);
|
|
12488
13017
|
} catch (e) {
|
|
12489
13018
|
console.error(error2(e.message));
|
|
@@ -1083,6 +1083,14 @@ function routeLabel(routes) {
|
|
|
1083
1083
|
if (routes.length <= 6) return routes.join(", ");
|
|
1084
1084
|
return `${routes.slice(0, 6).join(", ")} (+${routes.length - 6} more)`;
|
|
1085
1085
|
}
|
|
1086
|
+
function routePathname(route) {
|
|
1087
|
+
const queryIndex = route.indexOf("?");
|
|
1088
|
+
if (queryIndex === -1) return route;
|
|
1089
|
+
return route.slice(0, queryIndex) || "/";
|
|
1090
|
+
}
|
|
1091
|
+
function declaredRouteObserved(route, observedRoutes) {
|
|
1092
|
+
return observedRoutes.has(route) || route.includes("?") && observedRoutes.has(routePathname(route));
|
|
1093
|
+
}
|
|
1086
1094
|
function hasDoctrineEffect(essence, key) {
|
|
1087
1095
|
const effects = essence.dna.constraints?.effects;
|
|
1088
1096
|
return Boolean(effects && effects[key]);
|
|
@@ -1126,7 +1134,9 @@ function scanBrownfieldIssues(projectRoot, essence) {
|
|
|
1126
1134
|
const declaredRoutes = essenceRoutes(essence);
|
|
1127
1135
|
const observedRoutes = new Set(routes.routes.map((route) => route.path));
|
|
1128
1136
|
const missingFromEssence = [...observedRoutes].filter((route) => !declaredRoutes.has(route));
|
|
1129
|
-
const missingFromSource = [...declaredRoutes].filter(
|
|
1137
|
+
const missingFromSource = [...declaredRoutes].filter(
|
|
1138
|
+
(route) => !declaredRouteObserved(route, observedRoutes)
|
|
1139
|
+
);
|
|
1130
1140
|
if (routes.routes.length > 0 && declaredRoutes.size === 0) {
|
|
1131
1141
|
issues.push({
|
|
1132
1142
|
type: "error",
|
|
@@ -3780,14 +3780,36 @@ This project uses Decantr as a **contract and governance layer only**.
|
|
|
3780
3780
|
|
|
3781
3781
|
Do not install \`@decantr/css\`, rewrite the styling system, or add generated Decantr CSS files unless the task explicitly changes the adoption mode. Preserve the existing framework styling conventions and map them into the Decantr context before changing implementation files.
|
|
3782
3782
|
|
|
3783
|
-
Use \`.decantr/context/scaffold-pack.md\` and the matching section/page packs to understand visual intent, shell structure, and route contracts. Implement those contracts through the project's current CSS, component library, tokens, or design-system primitives
|
|
3783
|
+
Use \`.decantr/context/scaffold-pack.md\` and the matching section/page packs to understand visual intent, shell structure, and route contracts. Implement those contracts through the project's current CSS, component library, tokens, or design-system primitives.
|
|
3784
|
+
|
|
3785
|
+
### Interaction Requirements
|
|
3786
|
+
|
|
3787
|
+
Every pattern declares its required interactions in its page-pack \`Interactions\` checklist. A declared interaction must be implemented in source through the project's existing component library, CSS, or event-handler patterns.
|
|
3788
|
+
|
|
3789
|
+
| Declared interaction | Canonical implementation shape |
|
|
3790
|
+
|----------------------|--------------------------------|
|
|
3791
|
+
| \`animate-on-mount\` | Entrance animation class or component transition on the pattern root |
|
|
3792
|
+
| \`stagger-children\` | Parent stagger class or animation delay driven by child index |
|
|
3793
|
+
| \`keyboard-navigation\` | Arrow-key/Enter/Space handlers with visible focus state |
|
|
3794
|
+
| \`ripple-click\` | \`d-ripple\` or an equivalent click feedback class on the interactive surface |`;
|
|
3784
3795
|
var STYLE_BRIDGE_CSS_APPROACH = `## Styling Adoption
|
|
3785
3796
|
|
|
3786
3797
|
This project uses Decantr in **style-bridge** mode.
|
|
3787
3798
|
|
|
3788
3799
|
Decantr may generate lightweight bridge files such as \`src/styles/tokens.css\` and \`src/styles/decantr-bridge.css\`, but \`@decantr/css\` is not required. Treat these files as a mapping layer between Decantr context and the app's existing styling system.
|
|
3789
3800
|
|
|
3790
|
-
Preserve the current CSS framework/component library. Use Decantr tokens and bridge classes only where they clarify design intent without replacing the app's established styling conventions
|
|
3801
|
+
Preserve the current CSS framework/component library. Use Decantr tokens and bridge classes only where they clarify design intent without replacing the app's established styling conventions.
|
|
3802
|
+
|
|
3803
|
+
### Interaction Requirements
|
|
3804
|
+
|
|
3805
|
+
Every pattern declares its required interactions in its page-pack \`Interactions\` checklist. A declared interaction must be implemented in source through the project's existing component library, CSS, or event-handler patterns.
|
|
3806
|
+
|
|
3807
|
+
| Declared interaction | Canonical implementation shape |
|
|
3808
|
+
|----------------------|--------------------------------|
|
|
3809
|
+
| \`animate-on-mount\` | Entrance animation class or component transition on the pattern root |
|
|
3810
|
+
| \`stagger-children\` | Parent stagger class or animation delay driven by child index |
|
|
3811
|
+
| \`keyboard-navigation\` | Arrow-key/Enter/Space handlers with visible focus state |
|
|
3812
|
+
| \`ripple-click\` | \`d-ripple\` or an equivalent click feedback class on the interactive surface |`;
|
|
3791
3813
|
function getCssApproachContent(adoptionMode) {
|
|
3792
3814
|
if (adoptionMode === "contract-only") return CONTRACT_ONLY_CSS_APPROACH;
|
|
3793
3815
|
if (adoptionMode === "style-bridge") return STYLE_BRIDGE_CSS_APPROACH;
|
|
@@ -10,8 +10,8 @@ import {
|
|
|
10
10
|
renderProjectHealthCiWorkflow,
|
|
11
11
|
shouldFailHealth,
|
|
12
12
|
writeProjectHealthCiWorkflow
|
|
13
|
-
} from "./chunk-
|
|
14
|
-
import "./chunk-
|
|
13
|
+
} from "./chunk-3A2DLR47.js";
|
|
14
|
+
import "./chunk-MNZKOZW7.js";
|
|
15
15
|
export {
|
|
16
16
|
cmdHealth,
|
|
17
17
|
collectDesignTokenEvidence,
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import "./chunk-
|
|
2
|
-
import "./chunk-
|
|
3
|
-
import "./chunk-
|
|
4
|
-
import "./chunk-
|
|
5
|
-
import "./chunk-
|
|
1
|
+
import "./chunk-5MZH6XXQ.js";
|
|
2
|
+
import "./chunk-SIDKK73N.js";
|
|
3
|
+
import "./chunk-YBSBAJ3E.js";
|
|
4
|
+
import "./chunk-3A2DLR47.js";
|
|
5
|
+
import "./chunk-MNZKOZW7.js";
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createWorkspaceHealthReport
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-YBSBAJ3E.js";
|
|
4
4
|
import {
|
|
5
5
|
createProjectHealthReport
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-3A2DLR47.js";
|
|
7
7
|
import {
|
|
8
8
|
sendStudioHealthRefreshedTelemetry,
|
|
9
9
|
sendStudioStartedTelemetry
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-MNZKOZW7.js";
|
|
11
11
|
|
|
12
12
|
// src/commands/studio.ts
|
|
13
13
|
import { readFileSync } from "fs";
|
|
@@ -7,9 +7,9 @@ import {
|
|
|
7
7
|
listWorkspaceProjects,
|
|
8
8
|
parseWorkspaceArgs,
|
|
9
9
|
shouldFailWorkspaceHealth
|
|
10
|
-
} from "./chunk-
|
|
11
|
-
import "./chunk-
|
|
12
|
-
import "./chunk-
|
|
10
|
+
} from "./chunk-YBSBAJ3E.js";
|
|
11
|
+
import "./chunk-3A2DLR47.js";
|
|
12
|
+
import "./chunk-MNZKOZW7.js";
|
|
13
13
|
export {
|
|
14
14
|
cmdWorkspace,
|
|
15
15
|
createWorkspaceHealthReport,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decantr/cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.13.1",
|
|
4
4
|
"description": "Decantr CLI - scaffold, audit, inspect Project Health, and maintain Decantr projects from the terminal",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"decantr",
|
|
@@ -48,10 +48,10 @@
|
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
50
|
"ajv": "^8.20.0",
|
|
51
|
-
"@decantr/telemetry": "2.2.1",
|
|
52
|
-
"@decantr/registry": "2.2.0",
|
|
53
|
-
"@decantr/verifier": "2.5.0",
|
|
54
51
|
"@decantr/core": "2.1.0",
|
|
52
|
+
"@decantr/registry": "2.2.0",
|
|
53
|
+
"@decantr/telemetry": "2.2.1",
|
|
54
|
+
"@decantr/verifier": "2.5.1",
|
|
55
55
|
"@decantr/essence-spec": "2.0.1"
|
|
56
56
|
},
|
|
57
57
|
"scripts": {
|