@decantr/cli 2.9.8 → 2.11.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 +5 -4
- package/dist/bin.js +3 -3
- package/dist/{chunk-WPP3JEMC.js → chunk-5WPRTVNA.js} +2 -2
- package/dist/{chunk-JSILTE5V.js → chunk-ET4BG746.js} +410 -46
- package/dist/{chunk-QDMXZSVK.js → chunk-NUNUW5IS.js} +1 -1
- package/dist/{health-VAYRQVTF.js → health-ADHNLMX3.js} +1 -1
- package/dist/index.js +3 -3
- package/dist/{studio-WPETOZRW.js → studio-GZFCCVSO.js} +2 -2
- package/dist/{workspace-PBO3BJRL.js → workspace-YYCRXJNB.js} +2 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ npx @decantr/cli new my-app --blueprint=esports-hq
|
|
|
20
20
|
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.
|
|
21
21
|
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
22
|
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
|
-
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` when you want project-owned UI patterns and local rules such as button/card/shell/theme standards to appear in future task context and verification.
|
|
23
|
+
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` when you want project-owned UI patterns and local rules 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 and rules as project authority.
|
|
24
24
|
In monorepos, app-scoped commands accept `--project <app-path>`. `setup` shows attach guidance before adoption and the day-two loop after adoption. Hosted pack hydration also follows the essence path: `decantr registry compile-packs apps/web/decantr.essence.json --write-context` writes into `apps/web/.decantr/context`. In contract-only/offline Brownfield, deferred hosted packs are optional context unless a present manifest references missing files.
|
|
25
25
|
Use `decantr init`, `decantr analyze`, `decantr check`, and `decantr health` as advanced primitives when you need direct control over one step.
|
|
26
26
|
|
|
@@ -80,13 +80,13 @@ pnpm exec decantr ci init --project apps/web
|
|
|
80
80
|
|
|
81
81
|
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.
|
|
82
82
|
|
|
83
|
-
Brownfield analysis also 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
|
|
83
|
+
Brownfield analysis also 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 and source-derived button/card class hints; after review, `decantr codify --accept` promotes `.decantr/local-patterns.json` and `.decantr/rules.json`. `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 from the app root or selected `--project`, and `decantr ci` prints accepted local-rule findings with file/line evidence. `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.
|
|
84
84
|
|
|
85
85
|
## What It Does
|
|
86
86
|
|
|
87
87
|
- scaffolds Decantr projects from blueprints, archetypes, or prompts
|
|
88
88
|
- guides users through human workflow commands: setup, adopt, doctor, task, verify, ci, and codify
|
|
89
|
-
- supports explicit workflow lanes: greenfield blueprint, greenfield contract-only, brownfield adoption, and hybrid composition
|
|
89
|
+
- supports explicit workflow lanes: greenfield blueprint, greenfield contract-only, brownfield adoption, Hybrid local law, Hybrid style bridge, Hybrid Decantr CSS, and hybrid composition
|
|
90
90
|
- generates execution-pack context files for AI coding assistants
|
|
91
91
|
- audits projects against Decantr contracts
|
|
92
92
|
- produces local Project Health reports, Evidence Bundles, workspace health, and a localhost Studio dashboard for end-user drift triage
|
|
@@ -117,6 +117,7 @@ decantr task /feed "add saved recipe actions"
|
|
|
117
117
|
decantr verify --brownfield --local-patterns
|
|
118
118
|
decantr verify --base-url http://localhost:3000 --evidence
|
|
119
119
|
decantr ci --project apps/web
|
|
120
|
+
decantr ci --project apps/web --fail-on warn
|
|
120
121
|
decantr ci --workspace --changed --since origin/main
|
|
121
122
|
decantr ci init --project apps/web
|
|
122
123
|
decantr init --existing --blueprint=esports-hq
|
|
@@ -148,7 +149,7 @@ decantr showcase verification --json
|
|
|
148
149
|
|
|
149
150
|
`decantr verify` is the workflow command most users should run locally after edits. It delegates to Project Health, can add Brownfield guard validation with `--brownfield`, requires an accepted local pattern pack with `--local-patterns`, scans `.decantr/rules.json` when present, supports workspace mode, and writes evidence to `.decantr/evidence/latest.json` by default when `--evidence` is used.
|
|
150
151
|
|
|
151
|
-
`decantr doctor` explains project/workspace state, adoption mode, generated artifacts, local law, visual evidence, design authority signals, CI wiring, and an ordered next-step queue. It is the command to reach for when an app is in a monorepo, has stale Decantr files, or someone is not sure what Decantr expects next.
|
|
152
|
+
`decantr doctor` explains project/workspace state, adoption mode, adoption lane, generated artifacts, local law, visual evidence, design authority signals, CI wiring, and an ordered next-step queue. It is the command to reach for when an app is in a monorepo, has stale Decantr files, or someone is not sure what Decantr expects next.
|
|
152
153
|
|
|
153
154
|
`decantr setup` is non-mutating orientation. In an attached Brownfield app it reflects whether local law is already accepted, so the recommended verify command includes `--local-patterns` only when the project has that layer.
|
|
154
155
|
|
package/dist/bin.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import "./chunk-
|
|
2
|
+
import "./chunk-ET4BG746.js";
|
|
3
3
|
import "./chunk-IQZCIMQJ.js";
|
|
4
|
-
import "./chunk-
|
|
5
|
-
import "./chunk-
|
|
4
|
+
import "./chunk-NUNUW5IS.js";
|
|
5
|
+
import "./chunk-5WPRTVNA.js";
|
|
6
6
|
import "./chunk-4SZ4SEKT.js";
|
|
@@ -713,7 +713,7 @@ async function collectBrowserVerification(projectRoot, options, declaredRoutes)
|
|
|
713
713
|
message: "Browser verification was requested, but Playwright is not installed in this project.",
|
|
714
714
|
evidence: ["Expected dependency: playwright or @playwright/test"],
|
|
715
715
|
rule: "browser-playwright-missing",
|
|
716
|
-
suggestedFix: "Install Playwright in the project or
|
|
716
|
+
suggestedFix: "Install Playwright in the project or omit browser/base-url evidence for static-only health checks.",
|
|
717
717
|
baseId: "playwright-missing"
|
|
718
718
|
});
|
|
719
719
|
return {
|
|
@@ -1404,7 +1404,7 @@ async function cmdHealth(projectRoot = process.cwd(), options = {}) {
|
|
|
1404
1404
|
const reason = evidenceBundle.browser.findings[0] ?? "Playwright is not available to Decantr in this project.";
|
|
1405
1405
|
console.log(`${YELLOW}Browser evidence unavailable:${RESET} ${reason}`);
|
|
1406
1406
|
console.log(
|
|
1407
|
-
`${DIM}Static evidence was still written. Install Playwright or
|
|
1407
|
+
`${DIM}Static evidence was still written. Install Playwright or omit --browser/--base-url evidence if screenshots are not needed.${RESET}`
|
|
1408
1408
|
);
|
|
1409
1409
|
}
|
|
1410
1410
|
}
|
|
@@ -22,14 +22,14 @@ import {
|
|
|
22
22
|
listWorkspaceCandidates,
|
|
23
23
|
listWorkspaceProjects,
|
|
24
24
|
shouldFailWorkspaceHealth
|
|
25
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-NUNUW5IS.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-5WPRTVNA.js";
|
|
33
33
|
import {
|
|
34
34
|
buildGuardRegistryContext,
|
|
35
35
|
createDoctrineMap,
|
|
@@ -2549,6 +2549,17 @@ function collectCssFiles(projectRoot) {
|
|
|
2549
2549
|
function selectorFromMatch(raw) {
|
|
2550
2550
|
return raw.replace(/\s+/g, " ").trim().slice(0, 120);
|
|
2551
2551
|
}
|
|
2552
|
+
function isThemeVariantClass(id, selector) {
|
|
2553
|
+
if (/^(switcher|toggle|selector|picker|menu|button|btn|control|provider|container|panel|card|icon|label|group|actions?)$/i.test(
|
|
2554
|
+
id
|
|
2555
|
+
)) {
|
|
2556
|
+
return false;
|
|
2557
|
+
}
|
|
2558
|
+
const selectors = selector.split(",").map((entry) => entry.trim());
|
|
2559
|
+
return selectors.some(
|
|
2560
|
+
(entry) => /^(?::root|html|body)?(?:\.[\w-]+)*\.theme-[a-z0-9-]+(?:\s|$|:|\[|\.)/i.test(entry)
|
|
2561
|
+
);
|
|
2562
|
+
}
|
|
2552
2563
|
function createThemeInventory(projectRoot, styling) {
|
|
2553
2564
|
const files = collectCssFiles(projectRoot);
|
|
2554
2565
|
const variantMap = /* @__PURE__ */ new Map();
|
|
@@ -2582,8 +2593,12 @@ function createThemeInventory(projectRoot, styling) {
|
|
|
2582
2593
|
const themeClass = selector.match(/\.theme-([a-z0-9-]+)/i);
|
|
2583
2594
|
if (/dark/i.test(selector)) id = "dark";
|
|
2584
2595
|
if (dataTheme?.[1]) id = dataTheme[1].toLowerCase();
|
|
2585
|
-
if (themeClass?.[1])
|
|
2586
|
-
|
|
2596
|
+
if (themeClass?.[1]) {
|
|
2597
|
+
const classId = themeClass[1].toLowerCase();
|
|
2598
|
+
if (!isThemeVariantClass(classId, selector)) continue;
|
|
2599
|
+
id = classId;
|
|
2600
|
+
}
|
|
2601
|
+
if (/^:root(?:\s|$|,)/.test(selector)) id = "base";
|
|
2587
2602
|
ensureVariant(id, selector, rel2);
|
|
2588
2603
|
}
|
|
2589
2604
|
for (const [id, entry] of variantMap) {
|
|
@@ -2617,6 +2632,7 @@ function createThemeInventory(projectRoot, styling) {
|
|
|
2617
2632
|
}
|
|
2618
2633
|
const variants = [...variantMap.values()].sort((a, b) => a.id.localeCompare(b.id));
|
|
2619
2634
|
const modes = variants.map((variant) => variant.id);
|
|
2635
|
+
const darkModeDetected = styling.darkMode || modes.includes("dark") || variants.some((variant) => /dark/i.test(variant.id));
|
|
2620
2636
|
if (variants.length > 2) {
|
|
2621
2637
|
notes.push(
|
|
2622
2638
|
"Multiple theme variants were observed. Essence V4 remains unchanged; variants are reported here for task-time context."
|
|
@@ -2627,7 +2643,7 @@ function createThemeInventory(projectRoot, styling) {
|
|
|
2627
2643
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2628
2644
|
localOnly: true,
|
|
2629
2645
|
stylingApproach: styling.approach,
|
|
2630
|
-
darkModeDetected
|
|
2646
|
+
darkModeDetected,
|
|
2631
2647
|
modes,
|
|
2632
2648
|
variants,
|
|
2633
2649
|
tokens: {
|
|
@@ -3284,7 +3300,17 @@ function createBrownfieldCodifyProposal(input) {
|
|
|
3284
3300
|
ruleFiles: input.detected.existingRuleFiles,
|
|
3285
3301
|
routeCount: routes.length
|
|
3286
3302
|
},
|
|
3287
|
-
purpose: "Project-owned Brownfield UI law. Review and edit before accepting; Decantr treats this as authoritative only after it is copied to .decantr/local-patterns.json.",
|
|
3303
|
+
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.",
|
|
3304
|
+
hybrid: {
|
|
3305
|
+
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.",
|
|
3306
|
+
authorityPrecedence: [
|
|
3307
|
+
"existing production source",
|
|
3308
|
+
"accepted local patterns and rules",
|
|
3309
|
+
"Decantr Essence V4 contract",
|
|
3310
|
+
"hosted registry patterns and execution packs as optional guidance"
|
|
3311
|
+
],
|
|
3312
|
+
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."
|
|
3313
|
+
},
|
|
3288
3314
|
patterns: [
|
|
3289
3315
|
{
|
|
3290
3316
|
id: "button",
|
|
@@ -3299,7 +3325,8 @@ function createBrownfieldCodifyProposal(input) {
|
|
|
3299
3325
|
],
|
|
3300
3326
|
componentPaths: evidence.buttonComponents,
|
|
3301
3327
|
decide: "Define primary, secondary, tertiary, destructive, icon-only, disabled, and loading button variants from this app.",
|
|
3302
|
-
|
|
3328
|
+
classHints: evidence.buttonClassHints,
|
|
3329
|
+
evidence: evidence.buttonComponents.length ? evidence.buttonComponents : evidence.buttonClassHints.length ? evidence.buttonClassHints : [
|
|
3303
3330
|
"No obvious Button wrapper found yet. Add the project-owned wrapper path before strict enforcement."
|
|
3304
3331
|
],
|
|
3305
3332
|
forbiddenAlternatives: ["New one-off button variants without updating this manifest."]
|
|
@@ -3359,13 +3386,15 @@ function createBrownfieldCodifyProposal(input) {
|
|
|
3359
3386
|
"Avoid raw hex/rgb values in component templates unless documented as dynamic data.",
|
|
3360
3387
|
"Avoid static inline styles for reusable visual treatment.",
|
|
3361
3388
|
"When adding a new route, map it to an existing local pattern before inventing a new visual variant.",
|
|
3362
|
-
"When adding a theme variant, update .decantr/theme-inventory.json and this local pattern pack."
|
|
3389
|
+
"When adding a theme variant, update .decantr/theme-inventory.json and this local pattern pack.",
|
|
3390
|
+
"Map hosted Decantr patterns into project-owned local law before making them enforceable."
|
|
3363
3391
|
],
|
|
3364
3392
|
nextSteps: [
|
|
3365
3393
|
"Edit this proposal with real component paths and token/class recipes.",
|
|
3366
3394
|
"Run decantr codify --accept after review.",
|
|
3367
3395
|
"Use decantr task <route> before LLM edits so local law appears in task context.",
|
|
3368
3396
|
"Run decantr verify --brownfield --local-patterns after edits.",
|
|
3397
|
+
"For Hybrid adoption, start with warn-level local rules and raise severities only after the team agrees the law is stable.",
|
|
3369
3398
|
"Wire deterministic project rules into ESLint, Biome, Storybook, visual tests, or CI where Decantr should not guess."
|
|
3370
3399
|
]
|
|
3371
3400
|
};
|
|
@@ -3374,7 +3403,7 @@ function createBrownfieldCodifyProposal(input) {
|
|
|
3374
3403
|
status: "proposal",
|
|
3375
3404
|
generatedAt,
|
|
3376
3405
|
source: input.fromAudit ? "decantr codify --from-audit" : "decantr codify",
|
|
3377
|
-
purpose: "Mechanical Brownfield checks owned by this project. These rules are intentionally local and stack-agnostic; edit before accepting.",
|
|
3406
|
+
purpose: "Mechanical Brownfield/Hybrid checks owned by this project. These rules are intentionally local and stack-agnostic; edit before accepting.",
|
|
3378
3407
|
enforcement: {
|
|
3379
3408
|
defaultSeverity: "warn",
|
|
3380
3409
|
mode: "warn",
|
|
@@ -3623,11 +3652,27 @@ function summarizeSourceEvidence(projectRoot, files) {
|
|
|
3623
3652
|
formComponents: byName(["input", "field", "form", "select", "textarea"]),
|
|
3624
3653
|
shellComponents,
|
|
3625
3654
|
themeComponents,
|
|
3655
|
+
buttonClassHints: collectClassHints(projectRoot, files, [
|
|
3656
|
+
"button",
|
|
3657
|
+
"btn",
|
|
3658
|
+
"action",
|
|
3659
|
+
"primary",
|
|
3660
|
+
"secondary",
|
|
3661
|
+
"tertiary",
|
|
3662
|
+
"ghost",
|
|
3663
|
+
"link"
|
|
3664
|
+
]),
|
|
3626
3665
|
cardClassHints: collectClassHints(projectRoot, files, ["card", "panel", "surface", "tile"])
|
|
3627
3666
|
};
|
|
3628
3667
|
}
|
|
3629
3668
|
function collectClassHints(projectRoot, files, terms) {
|
|
3630
3669
|
const hints = /* @__PURE__ */ new Map();
|
|
3670
|
+
const wantsButton = terms.some(
|
|
3671
|
+
(term) => /^(button|btn|action|primary|secondary|tertiary|ghost|link)$/i.test(term)
|
|
3672
|
+
);
|
|
3673
|
+
const wantsSurface = terms.some((term) => /^(card|panel|surface|tile)$/i.test(term));
|
|
3674
|
+
const buttonSignal = /button|btn|action|primary|secondary|tertiary|ghost|link|destructive|icon/i;
|
|
3675
|
+
const surfaceSignal = /card|panel|surface|tile|rounded|shadow|border|bg-|p-\d|px-|py-/i;
|
|
3631
3676
|
for (const file of files) {
|
|
3632
3677
|
if (!UI_TEMPLATE_EXTENSIONS.has(extname(file.absolute))) continue;
|
|
3633
3678
|
const content = readFileSync11(join15(projectRoot, file.relative), "utf-8");
|
|
@@ -3635,7 +3680,8 @@ function collectClassHints(projectRoot, files, terms) {
|
|
|
3635
3680
|
const matches = content.matchAll(/\bclass(?:Name)?\s*=\s*["'`]([^"'`]+)["'`]/g);
|
|
3636
3681
|
for (const match of matches) {
|
|
3637
3682
|
const value = match[1].trim();
|
|
3638
|
-
|
|
3683
|
+
const keep = wantsButton && buttonSignal.test(value) || wantsSurface && surfaceSignal.test(value) || !wantsButton && !wantsSurface && (buttonSignal.test(value) || surfaceSignal.test(value));
|
|
3684
|
+
if (!keep) continue;
|
|
3639
3685
|
hints.set(value, (hints.get(value) ?? 0) + 1);
|
|
3640
3686
|
}
|
|
3641
3687
|
}
|
|
@@ -3833,6 +3879,16 @@ function summarizeLocalLaw(projectRoot) {
|
|
|
3833
3879
|
checked: validation.patternPackPresent || validation.ruleManifestPresent,
|
|
3834
3880
|
patternsPresent: validation.patternPackPresent,
|
|
3835
3881
|
rulesPresent: validation.ruleManifestPresent,
|
|
3882
|
+
warnings: validation.warnings,
|
|
3883
|
+
findings: validation.findings.map((finding) => ({
|
|
3884
|
+
ruleId: finding.ruleId,
|
|
3885
|
+
severity: finding.severity,
|
|
3886
|
+
file: finding.file,
|
|
3887
|
+
line: finding.line,
|
|
3888
|
+
column: finding.column,
|
|
3889
|
+
message: finding.message,
|
|
3890
|
+
suggestedFix: finding.suggestedFix
|
|
3891
|
+
})),
|
|
3836
3892
|
errorCount: validation.findings.filter((finding) => finding.severity === "error").length,
|
|
3837
3893
|
warnCount: validation.findings.filter((finding) => finding.severity === "warn").length + validation.warnings.length
|
|
3838
3894
|
};
|
|
@@ -3842,6 +3898,74 @@ function localLawFails(summary, failOn) {
|
|
|
3842
3898
|
if (summary.errorCount > 0) return true;
|
|
3843
3899
|
return failOn === "warn" && summary.warnCount > 0;
|
|
3844
3900
|
}
|
|
3901
|
+
function projectCiStatus(health, localLaw) {
|
|
3902
|
+
if (health.status === "error" || localLaw.errorCount > 0) return "error";
|
|
3903
|
+
if (health.status === "warning" || localLaw.warnCount > 0) return "warning";
|
|
3904
|
+
return health.status;
|
|
3905
|
+
}
|
|
3906
|
+
function formatLocalLawText(summary, health) {
|
|
3907
|
+
const lines = ["", `${BOLD2}Project-owned local law:${RESET3}`];
|
|
3908
|
+
if (!summary.checked) {
|
|
3909
|
+
const isBrownfield = health.summary.workflowMode === "brownfield-attach";
|
|
3910
|
+
lines.push(
|
|
3911
|
+
isBrownfield ? " Not accepted yet. Run `decantr codify --from-audit`, review, then `decantr codify --accept`." : " Not active for this project."
|
|
3912
|
+
);
|
|
3913
|
+
return `${lines.join("\n")}
|
|
3914
|
+
`;
|
|
3915
|
+
}
|
|
3916
|
+
lines.push(
|
|
3917
|
+
` Patterns: ${summary.patternsPresent ? "present" : "missing"} | Rules: ${summary.rulesPresent ? "present" : "missing"}`
|
|
3918
|
+
);
|
|
3919
|
+
lines.push(` Findings: ${summary.errorCount} error(s), ${summary.warnCount} warning(s)`);
|
|
3920
|
+
for (const warning of summary.warnings.slice(0, 5)) {
|
|
3921
|
+
lines.push(` ${DIM3}[WARN] ${warning}${RESET3}`);
|
|
3922
|
+
}
|
|
3923
|
+
for (const finding of summary.findings.slice(0, 8)) {
|
|
3924
|
+
const label = finding.severity.toUpperCase();
|
|
3925
|
+
lines.push(
|
|
3926
|
+
` [${label}] ${finding.ruleId} ${finding.file}:${finding.line}:${finding.column} ${finding.message}`
|
|
3927
|
+
);
|
|
3928
|
+
lines.push(` ${DIM3}${finding.suggestedFix}${RESET3}`);
|
|
3929
|
+
}
|
|
3930
|
+
if (summary.findings.length > 8) {
|
|
3931
|
+
lines.push(` ${DIM3}...${summary.findings.length - 8} more local-law finding(s)${RESET3}`);
|
|
3932
|
+
}
|
|
3933
|
+
return `${lines.join("\n")}
|
|
3934
|
+
`;
|
|
3935
|
+
}
|
|
3936
|
+
function formatLocalLawMarkdown(summary, health) {
|
|
3937
|
+
const lines = ["## Project-Owned Local Law", ""];
|
|
3938
|
+
if (!summary.checked) {
|
|
3939
|
+
if (health.summary.workflowMode !== "brownfield-attach") {
|
|
3940
|
+
lines.push("Local law is not active for this project.");
|
|
3941
|
+
return lines.join("\n");
|
|
3942
|
+
}
|
|
3943
|
+
lines.push(
|
|
3944
|
+
"Local law has not been accepted yet. Run `decantr codify --from-audit`, review the proposal, then run `decantr codify --accept`."
|
|
3945
|
+
);
|
|
3946
|
+
return lines.join("\n");
|
|
3947
|
+
}
|
|
3948
|
+
lines.push(
|
|
3949
|
+
`Patterns: **${summary.patternsPresent ? "present" : "missing"}** \xB7 Rules: **${summary.rulesPresent ? "present" : "missing"}**`
|
|
3950
|
+
);
|
|
3951
|
+
lines.push("");
|
|
3952
|
+
lines.push(`Findings: **${summary.errorCount} error(s), ${summary.warnCount} warning(s)**`);
|
|
3953
|
+
if (summary.warnings.length > 0) {
|
|
3954
|
+
lines.push("");
|
|
3955
|
+
for (const warning of summary.warnings.slice(0, 5)) {
|
|
3956
|
+
lines.push(`- Warning: ${warning}`);
|
|
3957
|
+
}
|
|
3958
|
+
}
|
|
3959
|
+
if (summary.findings.length > 0) {
|
|
3960
|
+
lines.push("");
|
|
3961
|
+
for (const finding of summary.findings.slice(0, 12)) {
|
|
3962
|
+
lines.push(
|
|
3963
|
+
`- \`${finding.severity}\` \`${finding.ruleId}\` at \`${finding.file}:${finding.line}:${finding.column}\`: ${finding.message}`
|
|
3964
|
+
);
|
|
3965
|
+
}
|
|
3966
|
+
}
|
|
3967
|
+
return lines.join("\n");
|
|
3968
|
+
}
|
|
3845
3969
|
function formatProjectCiMarkdown(report) {
|
|
3846
3970
|
const lines = [
|
|
3847
3971
|
"# Decantr CI",
|
|
@@ -3852,7 +3976,9 @@ function formatProjectCiMarkdown(report) {
|
|
|
3852
3976
|
`- Fail on: \`${report.failOn}\``,
|
|
3853
3977
|
`- Local law: ${report.localLaw.checked ? `${report.localLaw.errorCount} error(s), ${report.localLaw.warnCount} warning(s)` : "not accepted yet"}`,
|
|
3854
3978
|
"",
|
|
3855
|
-
formatProjectHealthMarkdown(report.health)
|
|
3979
|
+
formatProjectHealthMarkdown(report.health),
|
|
3980
|
+
"",
|
|
3981
|
+
formatLocalLawMarkdown(report.localLaw, report.health)
|
|
3856
3982
|
];
|
|
3857
3983
|
return `${lines.join("\n")}
|
|
3858
3984
|
`;
|
|
@@ -4056,7 +4182,7 @@ async function runProjectCi(root, options) {
|
|
|
4056
4182
|
mode: "project",
|
|
4057
4183
|
projectPath,
|
|
4058
4184
|
failOn,
|
|
4059
|
-
status: health
|
|
4185
|
+
status: projectCiStatus(health, localLaw),
|
|
4060
4186
|
health,
|
|
4061
4187
|
localLaw
|
|
4062
4188
|
};
|
|
@@ -4068,7 +4194,8 @@ async function runProjectCi(root, options) {
|
|
|
4068
4194
|
if (!options.output && !options.markdownOutput) {
|
|
4069
4195
|
if (options.json) process.stdout.write(json);
|
|
4070
4196
|
else if (options.markdown) process.stdout.write(markdown);
|
|
4071
|
-
else
|
|
4197
|
+
else
|
|
4198
|
+
process.stdout.write(`${formatProjectHealthText(health)}${formatLocalLawText(localLaw, health)}`);
|
|
4072
4199
|
}
|
|
4073
4200
|
return shouldFailHealth(health, failOn) || localLawFails(localLaw, failOn) ? 1 : 0;
|
|
4074
4201
|
}
|
|
@@ -4469,6 +4596,107 @@ function appendUnique(commands, command) {
|
|
|
4469
4596
|
if (!command) return;
|
|
4470
4597
|
if (!commands.includes(command)) commands.push(command);
|
|
4471
4598
|
}
|
|
4599
|
+
function deriveAdoptionLane(input) {
|
|
4600
|
+
if (input.workspaceMode) {
|
|
4601
|
+
return {
|
|
4602
|
+
id: "workspace",
|
|
4603
|
+
label: "Workspace overview",
|
|
4604
|
+
sourceAuthority: "Each attached app keeps its own Decantr contract and source authority.",
|
|
4605
|
+
styleAuthority: "Per-app adoption mode",
|
|
4606
|
+
activeAuthorities: ["workspace project list", "attached app contracts"],
|
|
4607
|
+
nextChoice: "Pick an app with --project when you want route-level or source-level guidance."
|
|
4608
|
+
};
|
|
4609
|
+
}
|
|
4610
|
+
if (!input.essenceVersion) {
|
|
4611
|
+
return {
|
|
4612
|
+
id: "unattached",
|
|
4613
|
+
label: "Unattached app",
|
|
4614
|
+
sourceAuthority: "Existing app only",
|
|
4615
|
+
styleAuthority: "Existing app styling system",
|
|
4616
|
+
activeAuthorities: ["source tree"],
|
|
4617
|
+
nextChoice: "Run decantr adopt to attach an observed contract before choosing Brownfield or Hybrid law."
|
|
4618
|
+
};
|
|
4619
|
+
}
|
|
4620
|
+
const hasLocalLaw = input.localPatternsPresent || input.localRulesPresent;
|
|
4621
|
+
if (input.workflowMode === "hybrid-compose") {
|
|
4622
|
+
return {
|
|
4623
|
+
id: "hybrid-compose",
|
|
4624
|
+
label: "Hybrid composition",
|
|
4625
|
+
sourceAuthority: "Existing app plus selected Decantr/local law",
|
|
4626
|
+
styleAuthority: input.adoptionMode === "decantr-css" ? "Decantr CSS runtime is active where adopted" : input.adoptionMode === "style-bridge" ? "Style bridge maps Decantr intent into the app styling system" : "Existing app styling system remains primary",
|
|
4627
|
+
activeAuthorities: [
|
|
4628
|
+
"existing source",
|
|
4629
|
+
"Essence V4 contract",
|
|
4630
|
+
hasLocalLaw ? "accepted local law" : "reviewed Hybrid choices",
|
|
4631
|
+
input.packManifestPresent ? "hosted execution packs" : "optional hosted packs"
|
|
4632
|
+
],
|
|
4633
|
+
nextChoice: "Use task/verify for daily work and keep any hosted pattern adoption mapped into project-owned law."
|
|
4634
|
+
};
|
|
4635
|
+
}
|
|
4636
|
+
if (input.workflowMode === "brownfield-attach") {
|
|
4637
|
+
if (input.adoptionMode === "decantr-css") {
|
|
4638
|
+
return {
|
|
4639
|
+
id: "hybrid-decantr-css",
|
|
4640
|
+
label: "Hybrid with Decantr CSS",
|
|
4641
|
+
sourceAuthority: "Existing app plus explicitly adopted Decantr CSS runtime",
|
|
4642
|
+
styleAuthority: "Decantr CSS runtime is active where adopted",
|
|
4643
|
+
activeAuthorities: ["existing source", "Essence V4 contract", "Decantr CSS runtime"],
|
|
4644
|
+
nextChoice: "Keep Decantr CSS usage explicit and validate route changes with task and verify."
|
|
4645
|
+
};
|
|
4646
|
+
}
|
|
4647
|
+
if (input.adoptionMode === "style-bridge") {
|
|
4648
|
+
return {
|
|
4649
|
+
id: "hybrid-style-bridge",
|
|
4650
|
+
label: "Hybrid style bridge",
|
|
4651
|
+
sourceAuthority: "Existing app plus Decantr intent mapped through a style bridge",
|
|
4652
|
+
styleAuthority: "Style bridge over the existing app styling system",
|
|
4653
|
+
activeAuthorities: ["existing source", "Essence V4 contract", "style bridge"],
|
|
4654
|
+
nextChoice: "Use local law to decide which component families the bridge governs before making it strict."
|
|
4655
|
+
};
|
|
4656
|
+
}
|
|
4657
|
+
if (hasLocalLaw) {
|
|
4658
|
+
return {
|
|
4659
|
+
id: "hybrid-local-law",
|
|
4660
|
+
label: "Hybrid local law",
|
|
4661
|
+
sourceAuthority: "Existing app plus accepted project-owned UI law",
|
|
4662
|
+
styleAuthority: input.designAuthority.length > 0 ? "Existing design authority plus accepted local rules" : "Accepted local rules over the current app styling system",
|
|
4663
|
+
activeAuthorities: [
|
|
4664
|
+
"existing source",
|
|
4665
|
+
"Essence V4 contract",
|
|
4666
|
+
"accepted local patterns/rules",
|
|
4667
|
+
input.packManifestPresent ? "hosted execution packs as guidance" : "optional hosted packs"
|
|
4668
|
+
],
|
|
4669
|
+
nextChoice: "Use task before edits and verify --local-patterns after edits; map hosted patterns into local law before enforcing them."
|
|
4670
|
+
};
|
|
4671
|
+
}
|
|
4672
|
+
return {
|
|
4673
|
+
id: "brownfield-contract-only",
|
|
4674
|
+
label: "Brownfield contract-only",
|
|
4675
|
+
sourceAuthority: "Existing app is authoritative",
|
|
4676
|
+
styleAuthority: "Existing app styling system",
|
|
4677
|
+
activeAuthorities: ["existing source", "Essence V4 contract"],
|
|
4678
|
+
nextChoice: "Stay contract-only for context, or codify local patterns/rules when you want Hybrid drift control."
|
|
4679
|
+
};
|
|
4680
|
+
}
|
|
4681
|
+
if (input.workflowMode === "greenfield-contract-only") {
|
|
4682
|
+
return {
|
|
4683
|
+
id: "greenfield-contract-only",
|
|
4684
|
+
label: "Greenfield contract-only",
|
|
4685
|
+
sourceAuthority: "Essence V4 contract",
|
|
4686
|
+
styleAuthority: "Project-chosen styling system",
|
|
4687
|
+
activeAuthorities: ["Essence V4 contract", "generated context"],
|
|
4688
|
+
nextChoice: "Add local rules or a style bridge only after the runtime conventions are clear."
|
|
4689
|
+
};
|
|
4690
|
+
}
|
|
4691
|
+
return {
|
|
4692
|
+
id: "greenfield-scaffold",
|
|
4693
|
+
label: "Greenfield scaffold",
|
|
4694
|
+
sourceAuthority: "Decantr contract and certified adapter output",
|
|
4695
|
+
styleAuthority: input.adoptionMode === "contract-only" ? "Project-chosen styling system" : "Decantr CSS",
|
|
4696
|
+
activeAuthorities: ["Essence V4 contract", "adapter output", "execution packs"],
|
|
4697
|
+
nextChoice: "Use task and verify to keep generated routes aligned with the compiled contract."
|
|
4698
|
+
};
|
|
4699
|
+
}
|
|
4472
4700
|
function buildDoctorReport(root, args) {
|
|
4473
4701
|
let projectArg;
|
|
4474
4702
|
for (let index = 1; index < args.length; index += 1) {
|
|
@@ -4624,6 +4852,16 @@ function buildDoctorReport(root, args) {
|
|
|
4624
4852
|
issues,
|
|
4625
4853
|
workspaceMode || workspaceInfo.requiresProjectSelection ? "4.0.0" : essenceVersion
|
|
4626
4854
|
);
|
|
4855
|
+
const lane = deriveAdoptionLane({
|
|
4856
|
+
workspaceMode: workspaceMode || workspaceInfo.requiresProjectSelection,
|
|
4857
|
+
essenceVersion,
|
|
4858
|
+
workflowMode,
|
|
4859
|
+
adoptionMode,
|
|
4860
|
+
localPatternsPresent,
|
|
4861
|
+
localRulesPresent,
|
|
4862
|
+
designAuthority,
|
|
4863
|
+
packManifestPresent
|
|
4864
|
+
});
|
|
4627
4865
|
const projectFlag = projectPath ? ` --project ${projectPath}` : "";
|
|
4628
4866
|
const verifyCommand = workflowMode === "brownfield-attach" ? `decantr verify --brownfield --local-patterns${projectFlag}` : workspaceMode ? "decantr ci --workspace" : `decantr verify${projectFlag}`;
|
|
4629
4867
|
const ciCommand = workspaceMode ? "decantr ci --workspace --fail-on error" : `decantr ci${projectFlag} --fail-on error`;
|
|
@@ -4694,6 +4932,7 @@ function buildDoctorReport(root, args) {
|
|
|
4694
4932
|
visualEvidence: {
|
|
4695
4933
|
manifestPresent: existsSync16(join18(appRoot, ".decantr", "evidence", "visual-manifest.json"))
|
|
4696
4934
|
},
|
|
4935
|
+
lane,
|
|
4697
4936
|
designAuthority,
|
|
4698
4937
|
status,
|
|
4699
4938
|
issues,
|
|
@@ -4722,6 +4961,13 @@ function formatDoctorText(report) {
|
|
|
4722
4961
|
` Workflow: ${report.project.workflowMode ?? "unknown"} | adoption ${report.project.adoptionMode ?? "unknown"}`,
|
|
4723
4962
|
` Sync state: ${report.project.syncStatus ?? "unknown"} ${DIM4}(registry/cache state, not generated context freshness)${RESET4}`,
|
|
4724
4963
|
"",
|
|
4964
|
+
`${BOLD3}Adoption Lane:${RESET4}`,
|
|
4965
|
+
` ${report.lane.label}`,
|
|
4966
|
+
` Source authority: ${report.lane.sourceAuthority}`,
|
|
4967
|
+
` Style authority: ${report.lane.styleAuthority}`,
|
|
4968
|
+
` Active: ${report.lane.activeAuthorities.join(", ")}`,
|
|
4969
|
+
` Choice: ${report.lane.nextChoice}`,
|
|
4970
|
+
"",
|
|
4725
4971
|
`${BOLD3}Generated Artifacts:${RESET4}`,
|
|
4726
4972
|
` Context directory: ${report.generatedArtifacts.contextDirPresent ? "present" : "missing"}`,
|
|
4727
4973
|
` Pack manifest: ${report.generatedArtifacts.packManifestPresent ? "present" : "missing"}`,
|
|
@@ -8672,7 +8918,7 @@ function localPatternMatches(projectRoot, query) {
|
|
|
8672
8918
|
if (!projectRoot) return [];
|
|
8673
8919
|
const pack = readLocalPatternPack(projectRoot);
|
|
8674
8920
|
const patterns = Array.isArray(pack?.patterns) ? pack.patterns : [];
|
|
8675
|
-
const queryTerms = query.toLowerCase().split(/[^a-z0-9]+/).filter((term) => term.length > 1);
|
|
8921
|
+
const queryTerms = 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]);
|
|
8676
8922
|
if (queryTerms.length === 0) return [];
|
|
8677
8923
|
return patterns.map((pattern) => {
|
|
8678
8924
|
const id = typeof pattern.id === "string" ? pattern.id : "local-pattern";
|
|
@@ -8684,7 +8930,11 @@ function localPatternMatches(projectRoot, query) {
|
|
|
8684
8930
|
role,
|
|
8685
8931
|
typeof pattern.decide === "string" ? pattern.decide : null,
|
|
8686
8932
|
...Array.isArray(pattern.appliesTo) ? pattern.appliesTo : [],
|
|
8687
|
-
...Array.isArray(pattern.componentPaths) ? pattern.componentPaths : []
|
|
8933
|
+
...Array.isArray(pattern.componentPaths) ? pattern.componentPaths : [],
|
|
8934
|
+
...Array.isArray(pattern.tokenHints) ? pattern.tokenHints : [],
|
|
8935
|
+
...Array.isArray(pattern.classHints) ? pattern.classHints : [],
|
|
8936
|
+
...Array.isArray(pattern.evidence) ? pattern.evidence : [],
|
|
8937
|
+
...Array.isArray(pattern.forbiddenAlternatives) ? pattern.forbiddenAlternatives : []
|
|
8688
8938
|
].filter((entry) => typeof entry === "string").join(" ").toLowerCase();
|
|
8689
8939
|
const score = queryTerms.reduce((sum, term) => sum + (haystack.includes(term) ? 1 : 0), 0);
|
|
8690
8940
|
return { id, label, role, score };
|
|
@@ -8718,6 +8968,7 @@ async function loadPatternDiscoveryCandidates(registryClient) {
|
|
|
8718
8968
|
}
|
|
8719
8969
|
async function cmdSuggest(query, options = {}) {
|
|
8720
8970
|
const searchType = options.type || "pattern";
|
|
8971
|
+
const projectRoot = options.projectRoot ?? process.cwd();
|
|
8721
8972
|
if (searchType !== "pattern" && searchType !== "patterns") {
|
|
8722
8973
|
const apiClient = getAPIClient();
|
|
8723
8974
|
try {
|
|
@@ -8738,9 +8989,10 @@ async function cmdSuggest(query, options = {}) {
|
|
|
8738
8989
|
}
|
|
8739
8990
|
}
|
|
8740
8991
|
const registryClient = new RegistryClient({
|
|
8741
|
-
cacheDir: join31(
|
|
8992
|
+
cacheDir: join31(projectRoot, ".decantr", "cache")
|
|
8742
8993
|
});
|
|
8743
|
-
const code = options.fromCode || options.file ? readSuggestCodeContext(
|
|
8994
|
+
const code = options.fromCode || options.file ? readSuggestCodeContext(projectRoot, options.route, options.file) : "";
|
|
8995
|
+
const localMatches = localPatternMatches(projectRoot, [query, code].filter(Boolean).join("\n"));
|
|
8744
8996
|
const candidates = await loadPatternDiscoveryCandidates(registryClient);
|
|
8745
8997
|
const matches = rankPatternCandidates(
|
|
8746
8998
|
{
|
|
@@ -8751,7 +9003,7 @@ async function cmdSuggest(query, options = {}) {
|
|
|
8751
9003
|
},
|
|
8752
9004
|
candidates
|
|
8753
9005
|
);
|
|
8754
|
-
if (matches.length === 0) {
|
|
9006
|
+
if (matches.length === 0 && localMatches.length === 0) {
|
|
8755
9007
|
console.log(dim3(`No pattern suggestions for "${query}"`));
|
|
8756
9008
|
console.log("");
|
|
8757
9009
|
console.log("Try:");
|
|
@@ -8771,10 +9023,6 @@ async function cmdSuggest(query, options = {}) {
|
|
|
8771
9023
|
`Pattern suggestions for "${query}"${contextBits.length > 0 ? ` (${contextBits.join(", ")})` : ""}`
|
|
8772
9024
|
)
|
|
8773
9025
|
);
|
|
8774
|
-
const localMatches = localPatternMatches(
|
|
8775
|
-
options.projectRoot,
|
|
8776
|
-
[query, code].filter(Boolean).join("\n")
|
|
8777
|
-
);
|
|
8778
9026
|
if (localMatches.length > 0) {
|
|
8779
9027
|
console.log(`${BOLD9}Project-owned local law:${RESET16}`);
|
|
8780
9028
|
for (const match of localMatches) {
|
|
@@ -8784,6 +9032,12 @@ async function cmdSuggest(query, options = {}) {
|
|
|
8784
9032
|
console.log("");
|
|
8785
9033
|
console.log(`${BOLD9}Registry patterns:${RESET16}`);
|
|
8786
9034
|
}
|
|
9035
|
+
if (matches.length === 0) {
|
|
9036
|
+
console.log(dim3("No hosted/bundled registry patterns matched this query."));
|
|
9037
|
+
console.log("");
|
|
9038
|
+
console.log(dim3('Use local law first, or run "decantr list patterns" to browse registry options.'));
|
|
9039
|
+
return;
|
|
9040
|
+
}
|
|
8787
9041
|
for (const match of matches.slice(0, 8)) {
|
|
8788
9042
|
const candidate = match.candidate;
|
|
8789
9043
|
const slug = candidate.slug || candidate.id;
|
|
@@ -9182,18 +9436,23 @@ async function applyAcceptedBrownfieldProposal(input) {
|
|
|
9182
9436
|
if (refreshResult.contextFiles.length > 8) {
|
|
9183
9437
|
console.log(` ${dim3(`(+${refreshResult.contextFiles.length - 8} more)`)}`);
|
|
9184
9438
|
}
|
|
9185
|
-
|
|
9186
|
-
|
|
9187
|
-
|
|
9188
|
-
|
|
9189
|
-
|
|
9190
|
-
|
|
9191
|
-
|
|
9192
|
-
|
|
9193
|
-
|
|
9194
|
-
|
|
9195
|
-
|
|
9196
|
-
|
|
9439
|
+
if (!input.suppressNextSteps) {
|
|
9440
|
+
console.log("");
|
|
9441
|
+
console.log(" Next steps:");
|
|
9442
|
+
console.log(
|
|
9443
|
+
` 1. Run ${cyan3(withProject("decantr doctor", projectLabel))} to explain adoption state and the next command`
|
|
9444
|
+
);
|
|
9445
|
+
console.log(
|
|
9446
|
+
` 2. Run ${cyan3(withProject("decantr codify --from-audit", projectLabel))} when you are ready to propose project-owned UI law`
|
|
9447
|
+
);
|
|
9448
|
+
console.log(
|
|
9449
|
+
` 3. Use ${cyan3(withProject('decantr task / "change summary"', projectLabel))} before LLM edits`
|
|
9450
|
+
);
|
|
9451
|
+
console.log(
|
|
9452
|
+
` 4. Run ${cyan3(withProject("decantr verify --brownfield", projectLabel))} after edits`
|
|
9453
|
+
);
|
|
9454
|
+
console.log("");
|
|
9455
|
+
}
|
|
9197
9456
|
}
|
|
9198
9457
|
async function cmdInit(args) {
|
|
9199
9458
|
const workspaceInfo = resolveWorkspaceInfo(process.cwd(), args.project);
|
|
@@ -9241,7 +9500,8 @@ async function cmdInit(args) {
|
|
|
9241
9500
|
detected,
|
|
9242
9501
|
workspaceInfo,
|
|
9243
9502
|
mode: proposalMode,
|
|
9244
|
-
assistantBridge: policy.assistantBridge
|
|
9503
|
+
assistantBridge: policy.assistantBridge,
|
|
9504
|
+
suppressNextSteps: args.internalSuppressNextSteps
|
|
9245
9505
|
});
|
|
9246
9506
|
if (args.telemetry) enableCliTelemetry(projectRoot);
|
|
9247
9507
|
writeArtifactReadme(projectRoot);
|
|
@@ -10460,7 +10720,8 @@ async function cmdAdoptWorkflow(args) {
|
|
|
10460
10720
|
"merge-proposal": proposalFlag === "--merge-proposal",
|
|
10461
10721
|
"replace-essence": proposalFlag === "--replace-essence",
|
|
10462
10722
|
"assistant-bridge": assistantBridge,
|
|
10463
|
-
telemetry: flagBoolean(flags, "telemetry")
|
|
10723
|
+
telemetry: flagBoolean(flags, "telemetry"),
|
|
10724
|
+
internalSuppressNextSteps: true
|
|
10464
10725
|
});
|
|
10465
10726
|
if (process.exitCode && process.exitCode !== 0) return;
|
|
10466
10727
|
if (hydratePacks) {
|
|
@@ -10489,7 +10750,7 @@ async function cmdAdoptWorkflow(args) {
|
|
|
10489
10750
|
console.log(dim3("Skipping hosted pack hydration in offline mode."));
|
|
10490
10751
|
}
|
|
10491
10752
|
if (runVerify) {
|
|
10492
|
-
const { cmdHealth } = await import("./health-
|
|
10753
|
+
const { cmdHealth } = await import("./health-ADHNLMX3.js");
|
|
10493
10754
|
await cmdHealth(projectRoot, {
|
|
10494
10755
|
browser: runBrowser,
|
|
10495
10756
|
browserBaseUrl: baseUrl,
|
|
@@ -10560,7 +10821,7 @@ async function cmdVerifyWorkflow(args) {
|
|
|
10560
10821
|
return;
|
|
10561
10822
|
}
|
|
10562
10823
|
if (workspaceMode) {
|
|
10563
|
-
const { cmdWorkspace } = await import("./workspace-
|
|
10824
|
+
const { cmdWorkspace } = await import("./workspace-YYCRXJNB.js");
|
|
10564
10825
|
await cmdWorkspace(process.cwd(), ["workspace", "health", ...withoutWorkflowOnlyFlags(args)]);
|
|
10565
10826
|
return;
|
|
10566
10827
|
}
|
|
@@ -10603,7 +10864,7 @@ async function cmdVerifyWorkflow(args) {
|
|
|
10603
10864
|
process.exitCode = void 0;
|
|
10604
10865
|
}
|
|
10605
10866
|
}
|
|
10606
|
-
const { cmdHealth, parseHealthArgs } = await import("./health-
|
|
10867
|
+
const { cmdHealth, parseHealthArgs } = await import("./health-ADHNLMX3.js");
|
|
10607
10868
|
await cmdHealth(workspaceInfo.appRoot, parseHealthArgs(healthArgs));
|
|
10608
10869
|
if (localPatterns) {
|
|
10609
10870
|
const validation = validateLocalLaw(workspaceInfo.appRoot);
|
|
@@ -10665,6 +10926,81 @@ function readJsonIfPresent(path) {
|
|
|
10665
10926
|
return null;
|
|
10666
10927
|
}
|
|
10667
10928
|
}
|
|
10929
|
+
function mentionsWord(text, term) {
|
|
10930
|
+
const escaped = term.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
10931
|
+
return new RegExp(`\\b${escaped}\\b`, "i").test(text);
|
|
10932
|
+
}
|
|
10933
|
+
function createTaskAuthoritySummary(input) {
|
|
10934
|
+
const detected = detectProject(input.projectRoot);
|
|
10935
|
+
const hasLocalLaw = input.hasLocalPatterns || input.hasLocalRules;
|
|
10936
|
+
let lane = "Brownfield contract-only";
|
|
10937
|
+
let sourceAuthority = "Existing app is authoritative; Decantr supplies contract context.";
|
|
10938
|
+
let styleAuthority = "Use the existing styling system.";
|
|
10939
|
+
const activeAuthorities = ["existing source", "Essence V4 contract"];
|
|
10940
|
+
if (input.workflowMode === "hybrid-compose") {
|
|
10941
|
+
lane = "Hybrid composition";
|
|
10942
|
+
sourceAuthority = "Existing app plus selected Decantr/local law are authoritative.";
|
|
10943
|
+
} else if (input.workflowMode === "brownfield-attach" && input.adoptionMode === "decantr-css") {
|
|
10944
|
+
lane = "Hybrid with Decantr CSS";
|
|
10945
|
+
sourceAuthority = "Existing app remains authoritative except where Decantr CSS is explicitly adopted.";
|
|
10946
|
+
styleAuthority = "Decantr CSS runtime is active where adopted.";
|
|
10947
|
+
activeAuthorities.push("Decantr CSS runtime");
|
|
10948
|
+
} else if (input.workflowMode === "brownfield-attach" && input.adoptionMode === "style-bridge") {
|
|
10949
|
+
lane = "Hybrid style bridge";
|
|
10950
|
+
sourceAuthority = "Existing app remains authoritative; Decantr intent maps through the style bridge.";
|
|
10951
|
+
styleAuthority = "Use bridge tokens/classes as a mapping layer onto the app styling system.";
|
|
10952
|
+
activeAuthorities.push("style bridge");
|
|
10953
|
+
} else if (input.workflowMode === "brownfield-attach" && hasLocalLaw) {
|
|
10954
|
+
lane = "Hybrid local law";
|
|
10955
|
+
sourceAuthority = "Existing app plus accepted project-owned UI law are authoritative.";
|
|
10956
|
+
styleAuthority = "Use project-owned components, tokens, classes, and accepted local rules.";
|
|
10957
|
+
} else if (input.workflowMode?.startsWith("greenfield")) {
|
|
10958
|
+
lane = input.workflowMode === "greenfield-contract-only" ? "Greenfield contract-only" : "Greenfield scaffold";
|
|
10959
|
+
sourceAuthority = "Essence V4 and generated context are authoritative.";
|
|
10960
|
+
styleAuthority = input.adoptionMode === "contract-only" ? "Use the project-chosen styling system." : "Use Decantr CSS where generated by the adapter.";
|
|
10961
|
+
}
|
|
10962
|
+
if (hasLocalLaw) activeAuthorities.push("accepted local patterns/rules");
|
|
10963
|
+
if (input.hasPackManifest) activeAuthorities.push("hosted execution packs as guidance");
|
|
10964
|
+
const framework = detected.framework ?? "unknown";
|
|
10965
|
+
const runtimeBoundary = `Detected ${framework}; do not introduce another frontend runtime inside this route unless the task is explicitly a reviewed migration or isolated integration plan.`;
|
|
10966
|
+
const warnings = [];
|
|
10967
|
+
const task = input.taskSummary;
|
|
10968
|
+
const runtimeTerms = [
|
|
10969
|
+
"angular",
|
|
10970
|
+
"vue",
|
|
10971
|
+
"svelte",
|
|
10972
|
+
"solid",
|
|
10973
|
+
"nextjs",
|
|
10974
|
+
"next.js",
|
|
10975
|
+
"react",
|
|
10976
|
+
"bootstrap",
|
|
10977
|
+
"shadcn"
|
|
10978
|
+
];
|
|
10979
|
+
const compatibleMentions = /* @__PURE__ */ new Set();
|
|
10980
|
+
if (framework === "nextjs") compatibleMentions.add("react");
|
|
10981
|
+
if (framework === "react") compatibleMentions.add("react");
|
|
10982
|
+
if (framework === "vue" || framework === "nuxt") compatibleMentions.add("vue");
|
|
10983
|
+
for (const term of runtimeTerms) {
|
|
10984
|
+
if (!mentionsWord(task, term)) continue;
|
|
10985
|
+
if (term === framework || compatibleMentions.has(term)) continue;
|
|
10986
|
+
if ((term === "nextjs" || term === "next.js") && framework === "nextjs") continue;
|
|
10987
|
+
if (term === "shadcn" || term === "bootstrap") {
|
|
10988
|
+
warnings.push(
|
|
10989
|
+
`Requested ${term} should be treated as optional Hybrid guidance unless this project already owns that library or the task explicitly adopts it.`
|
|
10990
|
+
);
|
|
10991
|
+
continue;
|
|
10992
|
+
}
|
|
10993
|
+
warnings.push(
|
|
10994
|
+
`Task mentions ${term}, but the detected runtime is ${framework}. Prefer a dry-run plan or local pattern mapping before adding cross-runtime code.`
|
|
10995
|
+
);
|
|
10996
|
+
}
|
|
10997
|
+
if (input.adoptionMode !== "decantr-css" && (/@decantr\/css/i.test(task) || /\bdecantr css\b/i.test(task) || /\bd-[a-z0-9-]+/i.test(task))) {
|
|
10998
|
+
warnings.push(
|
|
10999
|
+
"This project is not in decantr-css adoption mode. Do not add @decantr/css or d-* classes unless the user explicitly changes adoption mode."
|
|
11000
|
+
);
|
|
11001
|
+
}
|
|
11002
|
+
return { lane, sourceAuthority, styleAuthority, activeAuthorities, runtimeBoundary, warnings };
|
|
11003
|
+
}
|
|
10668
11004
|
async function cmdTaskWorkflow(args) {
|
|
10669
11005
|
const { flags, positional } = parseLooseArgs(args);
|
|
10670
11006
|
const workspaceInfo = resolveWorkflowProject(flags, "task");
|
|
@@ -10708,6 +11044,7 @@ async function cmdTaskWorkflow(args) {
|
|
|
10708
11044
|
const page = section?.pages.find((entry) => entry.id === target.page);
|
|
10709
11045
|
const contextDir = join31(workspaceInfo.appRoot, ".decantr", "context");
|
|
10710
11046
|
const manifest = readJsonIfPresent(join31(contextDir, "pack-manifest.json"));
|
|
11047
|
+
const projectJson = readJsonIfPresent(join31(workspaceInfo.appRoot, ".decantr", "project.json"));
|
|
10711
11048
|
const pagePack = manifest?.pages?.find((entry) => entry.id === target.page);
|
|
10712
11049
|
const sectionPack = manifest?.sections?.find((entry) => entry.id === target.section);
|
|
10713
11050
|
const visualManifest = readJsonIfPresent(join31(workspaceInfo.appRoot, ".decantr", "evidence", "visual-manifest.json"));
|
|
@@ -10723,6 +11060,15 @@ async function cmdTaskWorkflow(args) {
|
|
|
10723
11060
|
const changedSince = flagString(flags, "since");
|
|
10724
11061
|
const currentChangedFiles = changedFiles(workspaceInfo.appRoot, changedSince);
|
|
10725
11062
|
const changedRoutes = routeImpacts(workspaceInfo.appRoot, currentChangedFiles);
|
|
11063
|
+
const authority = createTaskAuthoritySummary({
|
|
11064
|
+
projectRoot: workspaceInfo.appRoot,
|
|
11065
|
+
workflowMode: projectJson?.initialized?.workflowMode ?? null,
|
|
11066
|
+
adoptionMode: projectJson?.initialized?.adoptionMode ?? null,
|
|
11067
|
+
hasLocalPatterns: existsSync29(localPatternPackPath),
|
|
11068
|
+
hasLocalRules: existsSync29(localRuleManifestPath),
|
|
11069
|
+
hasPackManifest: Boolean(manifest),
|
|
11070
|
+
taskSummary
|
|
11071
|
+
});
|
|
10726
11072
|
const context = {
|
|
10727
11073
|
route,
|
|
10728
11074
|
task: taskSummary || null,
|
|
@@ -10740,6 +11086,7 @@ async function cmdTaskWorkflow(args) {
|
|
|
10740
11086
|
existsSync29(localRuleManifestPath) ? displayProjectPath(workspaceInfo, ".decantr/rules.json") : null
|
|
10741
11087
|
].filter(Boolean),
|
|
10742
11088
|
screenshot: screenshot?.startsWith(".decantr/") ? displayProjectPath(workspaceInfo, screenshot) : screenshot ?? null,
|
|
11089
|
+
authority,
|
|
10743
11090
|
localLaw: displayedLocalLaw,
|
|
10744
11091
|
changedFiles: currentChangedFiles,
|
|
10745
11092
|
changedRoutes,
|
|
@@ -10765,6 +11112,16 @@ async function cmdTaskWorkflow(args) {
|
|
|
10765
11112
|
console.log(`${BOLD9}Visual evidence:${RESET16}`);
|
|
10766
11113
|
console.log(` ${cyan3(context.screenshot)}`);
|
|
10767
11114
|
}
|
|
11115
|
+
console.log("");
|
|
11116
|
+
console.log(`${BOLD9}Authority for this task:${RESET16}`);
|
|
11117
|
+
console.log(` Lane: ${context.authority.lane}`);
|
|
11118
|
+
console.log(` Source: ${context.authority.sourceAuthority}`);
|
|
11119
|
+
console.log(` Style: ${context.authority.styleAuthority}`);
|
|
11120
|
+
console.log(` Active: ${context.authority.activeAuthorities.join(", ")}`);
|
|
11121
|
+
console.log(` Runtime: ${context.authority.runtimeBoundary}`);
|
|
11122
|
+
for (const warning of context.authority.warnings) {
|
|
11123
|
+
console.log(` ${YELLOW12}Boundary:${RESET16} ${warning}`);
|
|
11124
|
+
}
|
|
10768
11125
|
if (context.localLaw.patternCount > 0 || context.localLaw.ruleCount > 0) {
|
|
10769
11126
|
console.log("");
|
|
10770
11127
|
console.log(`${BOLD9}Project-owned local law:${RESET16}`);
|
|
@@ -10803,7 +11160,7 @@ async function cmdTaskWorkflow(args) {
|
|
|
10803
11160
|
console.log("");
|
|
10804
11161
|
console.log(`${BOLD9}LLM instruction:${RESET16}`);
|
|
10805
11162
|
console.log(
|
|
10806
|
-
" Preserve the
|
|
11163
|
+
" 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."
|
|
10807
11164
|
);
|
|
10808
11165
|
console.log(` After editing, run ${cyan3(context.verifyCommand)}.`);
|
|
10809
11166
|
}
|
|
@@ -10836,6 +11193,11 @@ async function cmdCodifyWorkflow(args) {
|
|
|
10836
11193
|
if (result2.rulesAcceptedPath) {
|
|
10837
11194
|
console.log(success3(`Accepted local rule manifest: ${result2.rulesAcceptedPath}`));
|
|
10838
11195
|
}
|
|
11196
|
+
console.log(
|
|
11197
|
+
dim3(
|
|
11198
|
+
"Hybrid local law is now active: Decantr will treat accepted local patterns and rules as project-owned authority in task and verify flows."
|
|
11199
|
+
)
|
|
11200
|
+
);
|
|
10839
11201
|
console.log(
|
|
10840
11202
|
dim3(
|
|
10841
11203
|
`Run \`${withProject("decantr verify --brownfield --local-patterns", projectArg)}\` after project edits.`
|
|
@@ -10859,12 +11221,14 @@ async function cmdCodifyWorkflow(args) {
|
|
|
10859
11221
|
console.log(success3(`Wrote local rule proposal: ${result.rulesPath}`));
|
|
10860
11222
|
if (fromAudit) {
|
|
10861
11223
|
console.log(
|
|
10862
|
-
dim3(
|
|
11224
|
+
dim3(
|
|
11225
|
+
"Proposal includes source-derived component candidates, starter mechanical rules, and Hybrid authority guidance."
|
|
11226
|
+
)
|
|
10863
11227
|
);
|
|
10864
11228
|
}
|
|
10865
11229
|
console.log(
|
|
10866
11230
|
dim3(
|
|
10867
|
-
`Review both files, add real component paths/token recipes, then run \`${withProject("decantr codify --accept", projectArg)}\`.`
|
|
11231
|
+
`Review both files, add real component paths/token recipes, map hosted pattern ideas into project-owned law, then run \`${withProject("decantr codify --accept", projectArg)}\`.`
|
|
10868
11232
|
)
|
|
10869
11233
|
);
|
|
10870
11234
|
}
|
|
@@ -11611,7 +11975,7 @@ async function main() {
|
|
|
11611
11975
|
const { flags } = parseLooseArgs(args);
|
|
11612
11976
|
const workspaceInfo = resolveWorkflowProject(flags, "health");
|
|
11613
11977
|
if (!workspaceInfo) break;
|
|
11614
|
-
const { cmdHealth, parseHealthArgs } = await import("./health-
|
|
11978
|
+
const { cmdHealth, parseHealthArgs } = await import("./health-ADHNLMX3.js");
|
|
11615
11979
|
await cmdHealth(workspaceInfo.appRoot, parseHealthArgs(stripProjectArgs(args)));
|
|
11616
11980
|
} catch (e) {
|
|
11617
11981
|
console.error(error2(e.message));
|
|
@@ -11639,7 +12003,7 @@ async function main() {
|
|
|
11639
12003
|
cmdStudioHelp();
|
|
11640
12004
|
break;
|
|
11641
12005
|
}
|
|
11642
|
-
const { cmdStudio, parseStudioArgs } = await import("./studio-
|
|
12006
|
+
const { cmdStudio, parseStudioArgs } = await import("./studio-GZFCCVSO.js");
|
|
11643
12007
|
await cmdStudio(process.cwd(), parseStudioArgs(args));
|
|
11644
12008
|
} catch (e) {
|
|
11645
12009
|
console.error(error2(e.message));
|
|
@@ -11653,7 +12017,7 @@ async function main() {
|
|
|
11653
12017
|
cmdWorkspaceHelp();
|
|
11654
12018
|
break;
|
|
11655
12019
|
}
|
|
11656
|
-
const { cmdWorkspace } = await import("./workspace-
|
|
12020
|
+
const { cmdWorkspace } = await import("./workspace-YYCRXJNB.js");
|
|
11657
12021
|
await cmdWorkspace(process.cwd(), args);
|
|
11658
12022
|
} catch (e) {
|
|
11659
12023
|
console.error(error2(e.message));
|
|
@@ -11770,7 +12134,7 @@ async function main() {
|
|
|
11770
12134
|
route,
|
|
11771
12135
|
file,
|
|
11772
12136
|
fromCode,
|
|
11773
|
-
projectRoot: workspaceInfo?.appRoot
|
|
12137
|
+
projectRoot: workspaceInfo?.appRoot ?? process.cwd()
|
|
11774
12138
|
});
|
|
11775
12139
|
break;
|
|
11776
12140
|
}
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createWorkspaceHealthReport
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-NUNUW5IS.js";
|
|
4
4
|
import {
|
|
5
5
|
createProjectHealthReport
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-5WPRTVNA.js";
|
|
7
7
|
import {
|
|
8
8
|
sendStudioHealthRefreshedTelemetry,
|
|
9
9
|
sendStudioStartedTelemetry
|
|
@@ -7,8 +7,8 @@ import {
|
|
|
7
7
|
listWorkspaceProjects,
|
|
8
8
|
parseWorkspaceArgs,
|
|
9
9
|
shouldFailWorkspaceHealth
|
|
10
|
-
} from "./chunk-
|
|
11
|
-
import "./chunk-
|
|
10
|
+
} from "./chunk-NUNUW5IS.js";
|
|
11
|
+
import "./chunk-5WPRTVNA.js";
|
|
12
12
|
import "./chunk-4SZ4SEKT.js";
|
|
13
13
|
export {
|
|
14
14
|
cmdWorkspace,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decantr/cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.11.0",
|
|
4
4
|
"description": "Decantr CLI - scaffold, audit, inspect Project Health, and maintain Decantr projects from the terminal",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"decantr",
|
|
@@ -49,9 +49,9 @@
|
|
|
49
49
|
"dependencies": {
|
|
50
50
|
"ajv": "^8.20.0",
|
|
51
51
|
"@decantr/core": "2.1.0",
|
|
52
|
-
"@decantr/verifier": "2.3.3",
|
|
53
52
|
"@decantr/telemetry": "2.2.1",
|
|
54
53
|
"@decantr/essence-spec": "2.0.1",
|
|
54
|
+
"@decantr/verifier": "2.4.0",
|
|
55
55
|
"@decantr/registry": "2.2.0"
|
|
56
56
|
},
|
|
57
57
|
"scripts": {
|