@decantr/cli 2.11.0 → 2.13.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.
@@ -660,8 +660,8 @@ function scanRoutes(projectRoot) {
660
660
  }
661
661
 
662
662
  // src/analyzers/styling.ts
663
- import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
664
- import { join as join3 } from "path";
663
+ import { existsSync as existsSync3, readdirSync as readdirSync3, readFileSync as readFileSync3, statSync as statSync3 } from "fs";
664
+ import { join as join3, relative as relative3 } from "path";
665
665
  var TAILWIND_CONFIGS = [
666
666
  "tailwind.config.js",
667
667
  "tailwind.config.ts",
@@ -674,8 +674,10 @@ var GLOBALS_CSS_PATHS = [
674
674
  "src/styles/global.css",
675
675
  "src/styles/globals.css",
676
676
  "src/styles/main.css",
677
+ "src/styles/themes.css",
677
678
  "src/styles.css",
678
679
  "styles/globals.css",
680
+ "styles/themes.css",
679
681
  "styles.css",
680
682
  "assets/css/main.css",
681
683
  "src/index.css",
@@ -716,8 +718,15 @@ function extractCSSVariables(content) {
716
718
  return { colors, variables };
717
719
  }
718
720
  function detectDarkMode(projectRoot, cssContents) {
721
+ const darkSelectors = [
722
+ /\.dark\b/i,
723
+ /html\.dark\b/i,
724
+ /\[data-theme\s*=\s*["']dark["']\]/i,
725
+ /prefers-color-scheme\s*:\s*dark/i,
726
+ /color-scheme\s*:\s*dark/i
727
+ ];
719
728
  for (const cssContent of cssContents) {
720
- if (cssContent.includes(".dark") || cssContent.includes('[data-theme="dark"]') || cssContent.includes("prefers-color-scheme: dark") || cssContent.includes("color-scheme: dark")) {
729
+ if (darkSelectors.some((selector) => selector.test(cssContent))) {
721
730
  return true;
722
731
  }
723
732
  }
@@ -763,6 +772,46 @@ function detectDarkMode(projectRoot, cssContents) {
763
772
  }
764
773
  return false;
765
774
  }
775
+ function collectCssFiles(projectRoot) {
776
+ const seen = /* @__PURE__ */ new Set();
777
+ const ordered = [];
778
+ const add = (path) => {
779
+ if (seen.has(path)) return;
780
+ if (!existsSync3(join3(projectRoot, path))) return;
781
+ seen.add(path);
782
+ ordered.push(path);
783
+ };
784
+ for (const rel of GLOBALS_CSS_PATHS) add(rel);
785
+ for (const rel of DECANTR_STYLE_PATHS) add(rel);
786
+ const roots = ["src/styles", "styles", "assets/css", "src/app", "app"];
787
+ const visit = (dir) => {
788
+ const absolute = join3(projectRoot, dir);
789
+ if (!existsSync3(absolute)) return;
790
+ let entries;
791
+ try {
792
+ entries = readdirSync3(absolute);
793
+ } catch {
794
+ return;
795
+ }
796
+ for (const entry of entries) {
797
+ const child = join3(absolute, entry);
798
+ let stat;
799
+ try {
800
+ stat = statSync3(child);
801
+ } catch {
802
+ continue;
803
+ }
804
+ if (stat.isDirectory()) {
805
+ if (entry === "node_modules" || entry === ".next" || entry === "dist") continue;
806
+ visit(relative3(projectRoot, child).replace(/\\/g, "/"));
807
+ } else if (stat.isFile() && /\.css$/i.test(entry)) {
808
+ add(relative3(projectRoot, child).replace(/\\/g, "/"));
809
+ }
810
+ }
811
+ };
812
+ for (const root of roots) visit(root);
813
+ return ordered.slice(0, 80);
814
+ }
766
815
  function scanStyling(projectRoot) {
767
816
  let approach = "unknown";
768
817
  let configFile;
@@ -811,23 +860,12 @@ function scanStyling(projectRoot) {
811
860
  configFile = decantrStyleFiles.join(" + ");
812
861
  }
813
862
  const cssContents = [];
814
- for (const rel of GLOBALS_CSS_PATHS) {
863
+ const cssFiles = collectCssFiles(projectRoot);
864
+ for (const rel of cssFiles) {
815
865
  const fullPath = join3(projectRoot, rel);
816
- if (existsSync3(fullPath)) {
817
- try {
818
- cssContents.push(readFileSync3(fullPath, "utf-8"));
819
- } catch {
820
- }
821
- }
822
- }
823
- for (const rel of DECANTR_STYLE_PATHS) {
824
- if (GLOBALS_CSS_PATHS.includes(rel)) continue;
825
- const fullPath = join3(projectRoot, rel);
826
- if (existsSync3(fullPath)) {
827
- try {
828
- cssContents.push(readFileSync3(fullPath, "utf-8"));
829
- } catch {
830
- }
866
+ try {
867
+ cssContents.push(readFileSync3(fullPath, "utf-8"));
868
+ } catch {
831
869
  }
832
870
  }
833
871
  let colors = {};
@@ -841,7 +879,7 @@ function scanStyling(projectRoot) {
841
879
  const darkMode = detectDarkMode(projectRoot, cssContents);
842
880
  if (approach === "unknown" && cssContents.length > 0) {
843
881
  approach = "css";
844
- configFile = GLOBALS_CSS_PATHS.find((rel) => existsSync3(join3(projectRoot, rel)));
882
+ configFile = cssFiles[0];
845
883
  }
846
884
  return {
847
885
  approach,
@@ -1045,6 +1083,14 @@ function routeLabel(routes) {
1045
1083
  if (routes.length <= 6) return routes.join(", ");
1046
1084
  return `${routes.slice(0, 6).join(", ")} (+${routes.length - 6} more)`;
1047
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
+ }
1048
1094
  function hasDoctrineEffect(essence, key) {
1049
1095
  const effects = essence.dna.constraints?.effects;
1050
1096
  return Boolean(effects && effects[key]);
@@ -1088,7 +1134,9 @@ function scanBrownfieldIssues(projectRoot, essence) {
1088
1134
  const declaredRoutes = essenceRoutes(essence);
1089
1135
  const observedRoutes = new Set(routes.routes.map((route) => route.path));
1090
1136
  const missingFromEssence = [...observedRoutes].filter((route) => !declaredRoutes.has(route));
1091
- const missingFromSource = [...declaredRoutes].filter((route) => !observedRoutes.has(route));
1137
+ const missingFromSource = [...declaredRoutes].filter(
1138
+ (route) => !declaredRouteObserved(route, observedRoutes)
1139
+ );
1092
1140
  if (routes.routes.length > 0 && declaredRoutes.size === 0) {
1093
1141
  issues.push({
1094
1142
  type: "error",
@@ -1206,11 +1254,11 @@ function scanBrownfieldIssues(projectRoot, essence) {
1206
1254
  }
1207
1255
 
1208
1256
  // src/guard-context.ts
1209
- import { existsSync as existsSync7, readdirSync as readdirSync4, readFileSync as readFileSync7 } from "fs";
1257
+ import { existsSync as existsSync7, readdirSync as readdirSync5, readFileSync as readFileSync7 } from "fs";
1210
1258
  import { join as join7 } from "path";
1211
1259
 
1212
1260
  // src/bundled-content.ts
1213
- import { existsSync as existsSync6, readdirSync as readdirSync3, readFileSync as readFileSync6 } from "fs";
1261
+ import { existsSync as existsSync6, readdirSync as readdirSync4, readFileSync as readFileSync6 } from "fs";
1214
1262
  import { dirname, join as join6 } from "path";
1215
1263
  import { fileURLToPath } from "url";
1216
1264
  function bundledDirCandidates(contentType) {
@@ -1244,7 +1292,7 @@ function loadBundledContentList(contentType) {
1244
1292
  for (const dir of bundledDirCandidates(contentType)) {
1245
1293
  if (!existsSync6(dir)) continue;
1246
1294
  try {
1247
- for (const file of readdirSync3(dir).filter((name) => name.endsWith(".json"))) {
1295
+ for (const file of readdirSync4(dir).filter((name) => name.endsWith(".json"))) {
1248
1296
  const id = file.replace(/\.json$/, "");
1249
1297
  if (seen.has(id)) continue;
1250
1298
  const path = join6(dir, file);
@@ -1262,7 +1310,7 @@ function loadBundledContentList(contentType) {
1262
1310
  function loadJsonEntries(dir) {
1263
1311
  if (!existsSync7(dir)) return [];
1264
1312
  try {
1265
- return readdirSync4(dir).filter((file) => file.endsWith(".json") && file !== "index.json").map((file) => JSON.parse(readFileSync7(join7(dir, file), "utf-8")));
1313
+ return readdirSync5(dir).filter((file) => file.endsWith(".json") && file !== "index.json").map((file) => JSON.parse(readFileSync7(join7(dir, file), "utf-8")));
1266
1314
  } catch {
1267
1315
  return [];
1268
1316
  }
@@ -1307,8 +1355,8 @@ function buildGuardRegistryContext(projectRoot = process.cwd()) {
1307
1355
  }
1308
1356
 
1309
1357
  // src/lib/scan-interactions.ts
1310
- import { existsSync as existsSync8, readdirSync as readdirSync5, readFileSync as readFileSync8, statSync as statSync3 } from "fs";
1311
- import { extname as extname2, join as join8, relative as relative3 } from "path";
1358
+ import { existsSync as existsSync8, readdirSync as readdirSync6, readFileSync as readFileSync8, statSync as statSync4 } from "fs";
1359
+ import { extname as extname2, join as join8, relative as relative4 } from "path";
1312
1360
  import { verifyInteractionsInSource } from "@decantr/verifier";
1313
1361
  var SCAN_EXTENSIONS = /* @__PURE__ */ new Set([".tsx", ".jsx", ".ts", ".js", ".html", ".mdx"]);
1314
1362
  var SKIP_DIRECTORIES = /* @__PURE__ */ new Set([
@@ -1344,7 +1392,7 @@ function walkSourceTree(rootDir) {
1344
1392
  function walk2(dir) {
1345
1393
  let entries;
1346
1394
  try {
1347
- entries = readdirSync5(dir);
1395
+ entries = readdirSync6(dir);
1348
1396
  } catch {
1349
1397
  return;
1350
1398
  }
@@ -1353,11 +1401,11 @@ function walkSourceTree(rootDir) {
1353
1401
  const fullPath = join8(dir, entry);
1354
1402
  let s;
1355
1403
  try {
1356
- s = statSync3(fullPath);
1404
+ s = statSync4(fullPath);
1357
1405
  } catch {
1358
1406
  continue;
1359
1407
  }
1360
- const relativePath = relative3(rootDir, fullPath) || entry;
1408
+ const relativePath = relative4(rootDir, fullPath) || entry;
1361
1409
  if (s.isDirectory()) {
1362
1410
  walk2(fullPath);
1363
1411
  } else if (s.isFile() && SCAN_EXTENSIONS.has(extname2(entry))) {
@@ -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;
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  createProjectHealthReport,
3
3
  listWorkspaceAppCandidates
4
- } from "./chunk-5WPRTVNA.js";
4
+ } from "./chunk-3A2DLR47.js";
5
5
 
6
6
  // src/commands/workspace.ts
7
7
  import { execFileSync } from "child_process";
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  cmdHeal,
3
3
  collectCheckIssues
4
- } from "./chunk-4SZ4SEKT.js";
4
+ } from "./chunk-MNZKOZW7.js";
5
5
  export {
6
6
  cmdHeal,
7
7
  collectCheckIssues
@@ -10,8 +10,8 @@ import {
10
10
  renderProjectHealthCiWorkflow,
11
11
  shouldFailHealth,
12
12
  writeProjectHealthCiWorkflow
13
- } from "./chunk-5WPRTVNA.js";
14
- import "./chunk-4SZ4SEKT.js";
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-ET4BG746.js";
2
- import "./chunk-IQZCIMQJ.js";
3
- import "./chunk-NUNUW5IS.js";
4
- import "./chunk-5WPRTVNA.js";
5
- import "./chunk-4SZ4SEKT.js";
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-NUNUW5IS.js";
3
+ } from "./chunk-YBSBAJ3E.js";
4
4
  import {
5
5
  createProjectHealthReport
6
- } from "./chunk-5WPRTVNA.js";
6
+ } from "./chunk-3A2DLR47.js";
7
7
  import {
8
8
  sendStudioHealthRefreshedTelemetry,
9
9
  sendStudioStartedTelemetry
10
- } from "./chunk-4SZ4SEKT.js";
10
+ } from "./chunk-MNZKOZW7.js";
11
11
 
12
12
  // src/commands/studio.ts
13
13
  import { readFileSync } from "fs";
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  RegistryClient,
3
3
  refreshDerivedFiles
4
- } from "./chunk-IQZCIMQJ.js";
4
+ } from "./chunk-SIDKK73N.js";
5
5
 
6
6
  // src/commands/upgrade.ts
7
7
  import { existsSync, readFileSync, writeFileSync } from "fs";
@@ -7,9 +7,9 @@ import {
7
7
  listWorkspaceProjects,
8
8
  parseWorkspaceArgs,
9
9
  shouldFailWorkspaceHealth
10
- } from "./chunk-NUNUW5IS.js";
11
- import "./chunk-5WPRTVNA.js";
12
- import "./chunk-4SZ4SEKT.js";
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.11.0",
3
+ "version": "2.13.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,10 +49,10 @@
49
49
  "dependencies": {
50
50
  "ajv": "^8.20.0",
51
51
  "@decantr/core": "2.1.0",
52
- "@decantr/telemetry": "2.2.1",
53
52
  "@decantr/essence-spec": "2.0.1",
54
- "@decantr/verifier": "2.4.0",
55
- "@decantr/registry": "2.2.0"
53
+ "@decantr/telemetry": "2.2.1",
54
+ "@decantr/registry": "2.2.0",
55
+ "@decantr/verifier": "2.5.0"
56
56
  },
57
57
  "scripts": {
58
58
  "build": "tsup",