@decantr/cli 1.7.26 → 1.7.28

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Decantr AI
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/dist/bin.js CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import "./chunk-RAAUNHXD.js";
3
- import "./chunk-3K6HWLD5.js";
4
- import "./chunk-QRQCPD3C.js";
2
+ import "./chunk-56VBV4MT.js";
3
+ import "./chunk-GCDFX7UE.js";
4
+ import "./chunk-RRRHQ45P.js";
@@ -14,11 +14,12 @@ import {
14
14
  scaffoldProject,
15
15
  syncRegistry,
16
16
  writeExecutionPackBundleArtifacts
17
- } from "./chunk-3K6HWLD5.js";
17
+ } from "./chunk-GCDFX7UE.js";
18
18
  import {
19
19
  buildGuardRegistryContext,
20
- scanProjectInteractions
21
- } from "./chunk-QRQCPD3C.js";
20
+ scanProjectInteractions,
21
+ sendCliCommandTelemetry
22
+ } from "./chunk-RRRHQ45P.js";
22
23
 
23
24
  // src/index.ts
24
25
  import { existsSync as existsSync27, mkdirSync as mkdirSync11, readdirSync as readdirSync7, readFileSync as readFileSync20, writeFileSync as writeFileSync14 } from "fs";
@@ -6674,7 +6675,7 @@ async function main() {
6674
6675
  break;
6675
6676
  }
6676
6677
  case "upgrade": {
6677
- const { cmdUpgrade } = await import("./upgrade-KG42WK5C.js");
6678
+ const { cmdUpgrade } = await import("./upgrade-XMY6LIPS.js");
6678
6679
  const applyFlag = args.includes("--apply");
6679
6680
  await cmdUpgrade(process.cwd(), { apply: applyFlag });
6680
6681
  break;
@@ -6686,7 +6687,7 @@ async function main() {
6686
6687
  `${YELLOW9}Note: \`decantr heal\` is deprecated. Use \`decantr check\` instead.${RESET13}`
6687
6688
  );
6688
6689
  }
6689
- const { cmdHeal } = await import("./heal-EMT5LYVZ.js");
6690
+ const { cmdHeal } = await import("./heal-SWUFIYU5.js");
6690
6691
  const telemetryFlag = args.includes("--telemetry");
6691
6692
  await cmdHeal(process.cwd(), { telemetry: telemetryFlag });
6692
6693
  break;
@@ -7172,8 +7173,23 @@ async function main() {
7172
7173
  process.exitCode = 1;
7173
7174
  }
7174
7175
  }
7175
- main().catch((e) => {
7176
+ var cliStartedAt = Date.now();
7177
+ var cliArgs = process.argv.slice(2);
7178
+ main().then(async () => {
7179
+ await sendCliCommandTelemetry({
7180
+ args: cliArgs,
7181
+ durationMs: Date.now() - cliStartedAt,
7182
+ projectRoot: process.cwd(),
7183
+ success: !process.exitCode || process.exitCode === 0
7184
+ });
7185
+ }).catch(async (e) => {
7176
7186
  console.error(error3(e.message));
7177
7187
  if (e.stack) console.error(e.stack);
7178
7188
  process.exitCode = 1;
7189
+ await sendCliCommandTelemetry({
7190
+ args: cliArgs,
7191
+ durationMs: Date.now() - cliStartedAt,
7192
+ projectRoot: process.cwd(),
7193
+ success: false
7194
+ });
7179
7195
  });
@@ -2422,6 +2422,18 @@ function generateTokensCSS(themeData, mode, spatialTokens, options) {
2422
2422
  }
2423
2423
  const seed = themeData.seed || {};
2424
2424
  const palette = themeData.palette || {};
2425
+ const CORE_PALETTE_KEYS = /* @__PURE__ */ new Set([
2426
+ "background",
2427
+ "surface",
2428
+ "surface-raised",
2429
+ "border",
2430
+ "text",
2431
+ "text-muted",
2432
+ "primary",
2433
+ "primary-hover",
2434
+ "secondary",
2435
+ "accent"
2436
+ ]);
2425
2437
  const resolvedMode = mode === "auto" ? "dark" : mode;
2426
2438
  const FALLBACKS = {
2427
2439
  bg: { light: "#ffffff", dark: "#18181b" },
@@ -2439,11 +2451,19 @@ function generateTokensCSS(themeData, mode, spatialTokens, options) {
2439
2451
  if (!fallbacks) return tokenModeKey === "light" ? "#ffffff" : "#18181b";
2440
2452
  return fallbacks[tokenModeKey];
2441
2453
  };
2454
+ const pickPalette = (key) => {
2455
+ const entry = palette[key];
2456
+ return entry?.[tokenMode] || entry?.[tokenModeKey] || entry?.dark || entry?.light;
2457
+ };
2458
+ const extendedPaletteTokens = Object.fromEntries(
2459
+ Object.keys(palette).filter((key) => !CORE_PALETTE_KEYS.has(key)).map((key) => [`--d-${key.replace(/[^a-zA-Z0-9-]/g, "-")}`, pickPalette(key)]).filter((entry) => typeof entry[1] === "string" && entry[1].length > 0)
2460
+ );
2442
2461
  return {
2443
2462
  // Seed colors
2444
- "--d-primary": seed.primary || "#6366f1",
2445
- "--d-secondary": palette.secondary?.[tokenMode] || pickFb("secondary"),
2446
- "--d-accent": seed.accent || "#f59e0b",
2463
+ "--d-primary": pickPalette("primary") || seed.primary || "#6366f1",
2464
+ "--d-secondary": pickPalette("secondary") || seed.secondary || pickFb("secondary"),
2465
+ "--d-accent": pickPalette("accent") || seed.accent || "#f59e0b",
2466
+ ...extendedPaletteTokens,
2447
2467
  // Palette colors (mode-aware with mode-aware fallbacks)
2448
2468
  "--d-bg": palette.background?.[tokenMode] || pickFb("bg"),
2449
2469
  "--d-surface": palette.surface?.[tokenMode] || pickFb("surface"),
@@ -2513,12 +2533,17 @@ function generateTokensCSS(themeData, mode, spatialTokens, options) {
2513
2533
  "--d-motion-ease-out": "cubic-bezier(0, 0, 0.2, 1)",
2514
2534
  "--d-motion-ease-in": "cubic-bezier(0.4, 0, 1, 1)",
2515
2535
  "--d-motion-ease-spring": "cubic-bezier(0.34, 1.56, 0.64, 1)",
2536
+ "--d-duration-hover": "var(--d-motion-fast)",
2537
+ "--d-duration-entrance": "var(--d-motion-base)",
2538
+ "--d-easing": "var(--d-motion-ease-out)",
2539
+ "--d-accent-glow": "color-mix(in srgb, var(--d-accent) 24%, transparent)",
2516
2540
  // Typography scale (v2.1 Tier B2). Canonical sizes + weights +
2517
2541
  // tracking + leading. d-display, d-headline, d-title, d-prose,
2518
2542
  // d-caption, d-eyebrow read these. Themes override via
2519
2543
  // theme.typography.* below.
2520
2544
  "--d-font-body": "ui-sans-serif, system-ui, -apple-system, sans-serif",
2521
2545
  "--d-font-display": "ui-sans-serif, system-ui, -apple-system, sans-serif",
2546
+ "--d-font-mono": "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace",
2522
2547
  "--d-weight-regular": "400",
2523
2548
  "--d-weight-medium": "500",
2524
2549
  "--d-weight-semibold": "600",
@@ -2631,7 +2656,8 @@ ${lines}${spatialLines}
2631
2656
  "--d-elevation-2",
2632
2657
  "--d-elevation-3",
2633
2658
  "--d-elevation-4",
2634
- "--d-elevation-5"
2659
+ "--d-elevation-5",
2660
+ ...Object.keys(palette).filter((key) => !CORE_PALETTE_KEYS.has(key)).map((key) => `--d-${key.replace(/[^a-zA-Z0-9-]/g, "-")}`)
2635
2661
  ];
2636
2662
  if (mode === "auto") {
2637
2663
  const lightTokens = buildTokens("light");
@@ -0,0 +1,397 @@
1
+ // src/lib/scan-interactions.ts
2
+ import { existsSync, readdirSync, readFileSync, statSync } from "fs";
3
+ import { extname, join } from "path";
4
+ import { verifyInteractionsInSource } from "@decantr/verifier";
5
+ var SCAN_EXTENSIONS = /* @__PURE__ */ new Set([".tsx", ".jsx", ".ts", ".js", ".html", ".mdx"]);
6
+ var SKIP_DIRECTORIES = /* @__PURE__ */ new Set([
7
+ "node_modules",
8
+ ".decantr",
9
+ ".git",
10
+ "dist",
11
+ "build",
12
+ ".next",
13
+ ".turbo",
14
+ "coverage",
15
+ ".cache"
16
+ ]);
17
+ var MAX_FILE_SIZE = 1024 * 1024;
18
+ function walkSourceTree(rootDir) {
19
+ const sources = /* @__PURE__ */ new Map();
20
+ function walk(dir) {
21
+ let entries;
22
+ try {
23
+ entries = readdirSync(dir);
24
+ } catch {
25
+ return;
26
+ }
27
+ for (const entry of entries) {
28
+ if (SKIP_DIRECTORIES.has(entry)) continue;
29
+ const fullPath = join(dir, entry);
30
+ let s;
31
+ try {
32
+ s = statSync(fullPath);
33
+ } catch {
34
+ continue;
35
+ }
36
+ if (s.isDirectory()) {
37
+ walk(fullPath);
38
+ } else if (s.isFile() && SCAN_EXTENSIONS.has(extname(entry))) {
39
+ if (s.size > MAX_FILE_SIZE) continue;
40
+ try {
41
+ sources.set(fullPath, readFileSync(fullPath, "utf8"));
42
+ } catch {
43
+ }
44
+ }
45
+ }
46
+ }
47
+ walk(rootDir);
48
+ return sources;
49
+ }
50
+ function collectDeclaredInteractions(projectRoot) {
51
+ const manifestPath = join(projectRoot, ".decantr", "context", "pack-manifest.json");
52
+ if (!existsSync(manifestPath)) return [];
53
+ let manifest;
54
+ try {
55
+ manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
56
+ } catch {
57
+ return [];
58
+ }
59
+ const all = [];
60
+ const pages = manifest.pages ?? [];
61
+ const contextDir = join(projectRoot, ".decantr", "context");
62
+ for (const page of pages) {
63
+ const packPath = join(contextDir, page.json);
64
+ if (!existsSync(packPath)) continue;
65
+ let pack;
66
+ try {
67
+ pack = JSON.parse(readFileSync(packPath, "utf8"));
68
+ } catch {
69
+ continue;
70
+ }
71
+ const patterns = pack.data?.patterns ?? [];
72
+ for (const pat of patterns) {
73
+ if (Array.isArray(pat.interactions)) {
74
+ all.push(...pat.interactions);
75
+ }
76
+ }
77
+ }
78
+ return all;
79
+ }
80
+ function scanProjectInteractions(projectRoot) {
81
+ const declared = collectDeclaredInteractions(projectRoot);
82
+ if (declared.length === 0) return [];
83
+ const sources = walkSourceTree(projectRoot);
84
+ if (sources.size === 0) return [];
85
+ const missing = verifyInteractionsInSource(declared, sources);
86
+ return missing.map(({ interaction, suggestion }) => `${interaction} \u2192 ${suggestion}`);
87
+ }
88
+
89
+ // src/guard-context.ts
90
+ import { existsSync as existsSync2, readdirSync as readdirSync2, readFileSync as readFileSync2 } from "fs";
91
+ import { join as join2 } from "path";
92
+ function loadJsonEntries(dir) {
93
+ if (!existsSync2(dir)) return [];
94
+ try {
95
+ return readdirSync2(dir).filter((file) => file.endsWith(".json") && file !== "index.json").map((file) => JSON.parse(readFileSync2(join2(dir, file), "utf-8")));
96
+ } catch {
97
+ return [];
98
+ }
99
+ }
100
+ function buildGuardRegistryContext(projectRoot = process.cwd()) {
101
+ const themeRegistry = /* @__PURE__ */ new Map();
102
+ const patternRegistry = /* @__PURE__ */ new Map();
103
+ const cacheDir = join2(projectRoot, ".decantr", "cache");
104
+ const customDir = join2(projectRoot, ".decantr", "custom");
105
+ for (const data of loadJsonEntries(join2(cacheDir, "@official", "themes"))) {
106
+ if (typeof data.id === "string" && !themeRegistry.has(data.id)) {
107
+ themeRegistry.set(data.id, {
108
+ modes: Array.isArray(data.modes) ? data.modes.filter((mode) => typeof mode === "string") : ["light", "dark"]
109
+ });
110
+ }
111
+ }
112
+ for (const data of loadJsonEntries(join2(customDir, "themes"))) {
113
+ if (typeof data.id === "string") {
114
+ themeRegistry.set(`custom:${data.id}`, {
115
+ modes: Array.isArray(data.modes) ? data.modes.filter((mode) => typeof mode === "string") : ["light", "dark"]
116
+ });
117
+ }
118
+ }
119
+ for (const data of loadJsonEntries(join2(cacheDir, "@official", "patterns"))) {
120
+ if (typeof data.id === "string" && !patternRegistry.has(data.id)) {
121
+ patternRegistry.set(data.id, data);
122
+ }
123
+ }
124
+ for (const data of loadJsonEntries(join2(customDir, "patterns"))) {
125
+ if (typeof data.id === "string") {
126
+ patternRegistry.set(data.id, data);
127
+ }
128
+ }
129
+ return { themeRegistry, patternRegistry };
130
+ }
131
+
132
+ // src/telemetry.ts
133
+ import { randomUUID } from "crypto";
134
+ import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync3, writeFileSync } from "fs";
135
+ import { homedir } from "os";
136
+ import { dirname, join as join3 } from "path";
137
+ import { fileURLToPath } from "url";
138
+ import {
139
+ createFetchTelemetrySink,
140
+ createTelemetryClient,
141
+ isTelemetryActorType
142
+ } from "@decantr/telemetry";
143
+ var TELEMETRY_ENDPOINT = "https://api.decantr.ai/v1/telemetry/guard";
144
+ var DEFAULT_TELEMETRY_EVENTS_ENDPOINT = "https://api.decantr.ai/v1/telemetry/events";
145
+ var TELEMETRY_TIMEOUT_MS = 3e3;
146
+ var DNA_RULES = /* @__PURE__ */ new Set(["theme", "style", "density", "accessibility", "theme-mode"]);
147
+ async function sendGuardMetrics(metrics) {
148
+ try {
149
+ const controller = new AbortController();
150
+ const timer = setTimeout(() => controller.abort(), TELEMETRY_TIMEOUT_MS);
151
+ await fetch(TELEMETRY_ENDPOINT, {
152
+ method: "POST",
153
+ headers: { "Content-Type": "application/json" },
154
+ body: JSON.stringify(metrics),
155
+ signal: controller.signal
156
+ });
157
+ clearTimeout(timer);
158
+ } catch {
159
+ }
160
+ }
161
+ function isOptedIn(projectRoot) {
162
+ const projectJsonPath = join3(projectRoot, ".decantr", "project.json");
163
+ if (!existsSync3(projectJsonPath)) return false;
164
+ try {
165
+ const data = JSON.parse(readFileSync3(projectJsonPath, "utf-8"));
166
+ return data.telemetry === true;
167
+ } catch {
168
+ return false;
169
+ }
170
+ }
171
+ function optIn(projectRoot) {
172
+ const projectJsonPath = join3(projectRoot, ".decantr", "project.json");
173
+ let data = {};
174
+ if (existsSync3(projectJsonPath)) {
175
+ try {
176
+ data = JSON.parse(readFileSync3(projectJsonPath, "utf-8"));
177
+ } catch {
178
+ }
179
+ }
180
+ data.telemetry = true;
181
+ mkdirSync(dirname(projectJsonPath), { recursive: true });
182
+ writeFileSync(projectJsonPath, JSON.stringify(data, null, 2) + "\n", "utf-8");
183
+ }
184
+ async function sendCliCommandTelemetry(input) {
185
+ const projectRoot = input.projectRoot ?? process.cwd();
186
+ const command = normalizeCommand(input.args[0]);
187
+ if (!isOptedIn(projectRoot) || !command || command === "help" || command === "version") {
188
+ return;
189
+ }
190
+ const identities = ensureTelemetryIdentities(projectRoot);
191
+ if (!identities) {
192
+ return;
193
+ }
194
+ const client = createTelemetryClient({
195
+ sink: createFetchTelemetrySink({
196
+ endpoint: getTelemetryEventsEndpoint(),
197
+ timeoutMs: TELEMETRY_TIMEOUT_MS
198
+ })
199
+ });
200
+ const event = {
201
+ name: "cli.command.completed",
202
+ context: {
203
+ source: "cli",
204
+ actorType: getTelemetryActorType(),
205
+ environment: "production",
206
+ decantrVersion: getCliVersion(),
207
+ installId: identities.installId,
208
+ projectId: identities.projectId,
209
+ registrySource: inferRegistrySource(input.args)
210
+ },
211
+ properties: {
212
+ command,
213
+ success: input.success,
214
+ durationMs: input.durationMs,
215
+ adoptionMode: inferAdoptionMode(input.args),
216
+ errorCode: input.success ? void 0 : "cli_command_failed",
217
+ offline: input.args.includes("--offline"),
218
+ projectScope: inferProjectScope(projectRoot),
219
+ registrySource: inferRegistrySource(input.args),
220
+ targetFramework: inferFlagValue(input.args, "--target"),
221
+ workflowMode: inferWorkflowMode(input.args)
222
+ }
223
+ };
224
+ try {
225
+ await client.capture(event);
226
+ } catch {
227
+ }
228
+ }
229
+ function collectMetrics(essence, issues) {
230
+ const dna = essence.dna ?? {};
231
+ const blueprint = essence.blueprint ?? {};
232
+ const meta = essence.meta ?? {};
233
+ const guard = meta.guard ?? {};
234
+ const theme = dna.theme ?? {};
235
+ const sections = blueprint.sections ?? [];
236
+ const routes = blueprint.routes ?? {};
237
+ const byRule = {};
238
+ let dnaCount = 0;
239
+ let blueprintCount = 0;
240
+ for (const issue of issues) {
241
+ byRule[issue.rule] = (byRule[issue.rule] ?? 0) + 1;
242
+ if (DNA_RULES.has(issue.rule)) {
243
+ dnaCount++;
244
+ } else {
245
+ blueprintCount++;
246
+ }
247
+ }
248
+ return {
249
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
250
+ cli_version: getCliVersion(),
251
+ essence_version: essence.version ?? "unknown",
252
+ guard_mode: guard.mode ?? "unknown",
253
+ violations: {
254
+ dna: dnaCount,
255
+ blueprint: blueprintCount,
256
+ by_rule: byRule
257
+ },
258
+ resolution_rate: 0,
259
+ sections_count: sections.length,
260
+ routes_count: Object.keys(routes).length,
261
+ theme: theme.id ?? "unknown"
262
+ };
263
+ }
264
+ function ensureTelemetryIdentities(projectRoot) {
265
+ const installId = getOrCreateInstallId();
266
+ const projectJsonPath = join3(projectRoot, ".decantr", "project.json");
267
+ if (!existsSync3(projectJsonPath)) {
268
+ return null;
269
+ }
270
+ try {
271
+ const data = JSON.parse(readFileSync3(projectJsonPath, "utf-8"));
272
+ let projectId = typeof data.telemetryProjectId === "string" ? data.telemetryProjectId : void 0;
273
+ if (!projectId) {
274
+ projectId = `project_${randomUUID()}`;
275
+ data.telemetryProjectId = projectId;
276
+ writeFileSync(projectJsonPath, JSON.stringify(data, null, 2) + "\n", "utf-8");
277
+ }
278
+ return { installId, projectId };
279
+ } catch {
280
+ return null;
281
+ }
282
+ }
283
+ function getOrCreateInstallId() {
284
+ const configDir = getConfigDir();
285
+ const configPath = join3(configDir, "config.json");
286
+ try {
287
+ if (existsSync3(configPath)) {
288
+ const data = JSON.parse(readFileSync3(configPath, "utf-8"));
289
+ if (typeof data.telemetryInstallId === "string") {
290
+ return data.telemetryInstallId;
291
+ }
292
+ const installId2 = `install_${randomUUID()}`;
293
+ data.telemetryInstallId = installId2;
294
+ writeFileSync(configPath, JSON.stringify(data, null, 2) + "\n", "utf-8");
295
+ return installId2;
296
+ }
297
+ mkdirSync(configDir, { recursive: true });
298
+ const installId = `install_${randomUUID()}`;
299
+ writeFileSync(
300
+ configPath,
301
+ JSON.stringify({ telemetryInstallId: installId }, null, 2) + "\n",
302
+ "utf-8"
303
+ );
304
+ return installId;
305
+ } catch {
306
+ return `install_${randomUUID()}`;
307
+ }
308
+ }
309
+ function getConfigDir() {
310
+ return process.env.DECANTR_CONFIG_DIR || join3(homedir(), ".config", "decantr");
311
+ }
312
+ function getTelemetryEventsEndpoint() {
313
+ return process.env.DECANTR_TELEMETRY_ENDPOINT || DEFAULT_TELEMETRY_EVENTS_ENDPOINT;
314
+ }
315
+ function getTelemetryActorType() {
316
+ const configured = process.env.DECANTR_TELEMETRY_ACTOR_TYPE;
317
+ return isTelemetryActorType(configured) ? configured : "customer";
318
+ }
319
+ function normalizeCommand(command) {
320
+ if (!command) return null;
321
+ if (command === "--help" || command === "-h") return "help";
322
+ if (command === "--version" || command === "-v") return "version";
323
+ return command;
324
+ }
325
+ function inferFlagValue(args, flag) {
326
+ const equalsPrefix = `${flag}=`;
327
+ const inline = args.find((arg) => arg.startsWith(equalsPrefix));
328
+ if (inline) {
329
+ return inline.slice(equalsPrefix.length) || void 0;
330
+ }
331
+ const index = args.indexOf(flag);
332
+ if (index !== -1 && args[index + 1] && !args[index + 1].startsWith("-")) {
333
+ return args[index + 1];
334
+ }
335
+ return void 0;
336
+ }
337
+ function inferAdoptionMode(args) {
338
+ const value = inferFlagValue(args, "--adoption");
339
+ if (value === "contract-only" || value === "decantr-css" || value === "style-bridge") {
340
+ return value;
341
+ }
342
+ return void 0;
343
+ }
344
+ function inferWorkflowMode(args) {
345
+ const value = inferFlagValue(args, "--workflow");
346
+ if (value === "greenfield" || value === "greenfield-scaffold") {
347
+ return "greenfield-scaffold";
348
+ }
349
+ if (value === "contract" || value === "greenfield-contract-only") {
350
+ return "greenfield-contract-only";
351
+ }
352
+ if (value === "brownfield" || value === "brownfield-attach") {
353
+ return "brownfield-attach";
354
+ }
355
+ if (value === "hybrid" || value === "hybrid-compose") {
356
+ return "hybrid-compose";
357
+ }
358
+ return void 0;
359
+ }
360
+ function inferRegistrySource(args) {
361
+ if (args.includes("--offline")) {
362
+ return "cache";
363
+ }
364
+ if (args.some((arg) => arg === "--registry" || arg.startsWith("--registry="))) {
365
+ return "custom";
366
+ }
367
+ return "official";
368
+ }
369
+ function inferProjectScope(projectRoot) {
370
+ return existsSync3(join3(projectRoot, "pnpm-workspace.yaml")) || existsSync3(join3(projectRoot, "turbo.json")) || existsSync3(join3(projectRoot, "lerna.json")) ? "workspace-app" : "single-app";
371
+ }
372
+ function getCliVersion() {
373
+ try {
374
+ const here = dirname(fileURLToPath(import.meta.url));
375
+ const candidates = [join3(here, "..", "package.json"), join3(here, "..", "..", "package.json")];
376
+ for (const candidate of candidates) {
377
+ if (existsSync3(candidate)) {
378
+ const pkg = JSON.parse(readFileSync3(candidate, "utf-8"));
379
+ if (pkg.version) {
380
+ return pkg.version;
381
+ }
382
+ }
383
+ }
384
+ } catch {
385
+ }
386
+ return "unknown";
387
+ }
388
+
389
+ export {
390
+ scanProjectInteractions,
391
+ buildGuardRegistryContext,
392
+ sendGuardMetrics,
393
+ isOptedIn,
394
+ optIn,
395
+ sendCliCommandTelemetry,
396
+ collectMetrics
397
+ };
@@ -0,0 +1,99 @@
1
+ import {
2
+ buildGuardRegistryContext,
3
+ collectMetrics,
4
+ isOptedIn,
5
+ optIn,
6
+ scanProjectInteractions,
7
+ sendGuardMetrics
8
+ } from "./chunk-RRRHQ45P.js";
9
+
10
+ // src/commands/heal.ts
11
+ import { existsSync, readFileSync } from "fs";
12
+ import { join } from "path";
13
+ import { evaluateGuard, validateEssence } from "@decantr/essence-spec";
14
+ var GREEN = "\x1B[32m";
15
+ var RED = "\x1B[31m";
16
+ var YELLOW = "\x1B[33m";
17
+ var CYAN = "\x1B[36m";
18
+ var RESET = "\x1B[0m";
19
+ var DIM = "\x1B[2m";
20
+ async function cmdHeal(projectRoot = process.cwd(), options = {}) {
21
+ const essencePath = join(projectRoot, "decantr.essence.json");
22
+ if (!existsSync(essencePath)) {
23
+ console.error("No decantr.essence.json found. Run `decantr init` first.");
24
+ process.exitCode = 1;
25
+ return;
26
+ }
27
+ const essence = JSON.parse(readFileSync(essencePath, "utf-8"));
28
+ console.log("Scanning for issues...\n");
29
+ const issues = [];
30
+ const validation = validateEssence(essence);
31
+ if (!validation.valid) {
32
+ for (const err of validation.errors) {
33
+ issues.push({
34
+ type: "error",
35
+ rule: "schema",
36
+ message: err
37
+ });
38
+ }
39
+ }
40
+ let interactionIssues = [];
41
+ try {
42
+ interactionIssues = scanProjectInteractions(projectRoot);
43
+ } catch {
44
+ }
45
+ try {
46
+ const guardContext = buildGuardRegistryContext(projectRoot);
47
+ const violations = evaluateGuard(essence, {
48
+ ...guardContext,
49
+ interaction_issues: interactionIssues
50
+ });
51
+ for (const v of violations) {
52
+ issues.push({
53
+ type: v.severity === "error" ? "error" : "warning",
54
+ rule: v.rule,
55
+ message: v.message,
56
+ suggestion: v.suggestion
57
+ });
58
+ }
59
+ } catch {
60
+ }
61
+ if (issues.length === 0) {
62
+ console.log(`${GREEN}No issues found. Project is healthy.${RESET}`);
63
+ await maybeSendTelemetry(projectRoot, essence, issues, options);
64
+ return;
65
+ }
66
+ console.log(`Found ${issues.length} issue(s):
67
+ `);
68
+ for (const issue of issues) {
69
+ const icon = issue.type === "error" ? `${RED}x${RESET}` : `${YELLOW}!${RESET}`;
70
+ console.log(`${icon} [${issue.rule}] ${issue.message}`);
71
+ if (issue.suggestion) {
72
+ console.log(` ${DIM}Suggestion: ${issue.suggestion}${RESET}`);
73
+ }
74
+ }
75
+ console.log(`
76
+ ${YELLOW}Manual fixes required. Review the issues above.${RESET}`);
77
+ const hasError = issues.some((i) => i.type === "error");
78
+ if (hasError) {
79
+ process.exitCode = 1;
80
+ }
81
+ await maybeSendTelemetry(projectRoot, essence, issues, options);
82
+ }
83
+ async function maybeSendTelemetry(projectRoot, essence, issues, options) {
84
+ if (options.telemetry && !isOptedIn(projectRoot)) {
85
+ optIn(projectRoot);
86
+ console.log(
87
+ `
88
+ ${CYAN}Telemetry enabled.${RESET} Anonymous guard metrics will be sent on future checks.`
89
+ );
90
+ console.log(`${DIM}Set "telemetry": false in .decantr/project.json to opt out.${RESET}`);
91
+ }
92
+ if (isOptedIn(projectRoot)) {
93
+ const metrics = collectMetrics(essence, issues);
94
+ sendGuardMetrics(metrics);
95
+ }
96
+ }
97
+ export {
98
+ cmdHeal
99
+ };
package/dist/index.js CHANGED
@@ -1,3 +1,3 @@
1
- import "./chunk-RAAUNHXD.js";
2
- import "./chunk-3K6HWLD5.js";
3
- import "./chunk-QRQCPD3C.js";
1
+ import "./chunk-56VBV4MT.js";
2
+ import "./chunk-GCDFX7UE.js";
3
+ import "./chunk-RRRHQ45P.js";
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  RegistryClient,
3
3
  refreshDerivedFiles
4
- } from "./chunk-3K6HWLD5.js";
4
+ } from "./chunk-GCDFX7UE.js";
5
5
 
6
6
  // src/commands/upgrade.ts
7
7
  import { existsSync, readFileSync, writeFileSync } from "fs";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decantr/cli",
3
- "version": "1.7.26",
3
+ "version": "1.7.28",
4
4
  "description": "Decantr CLI — scaffold, audit, and maintain Decantr projects from the terminal",
5
5
  "author": "Decantr AI",
6
6
  "license": "MIT",
@@ -29,19 +29,19 @@
29
29
  "publishConfig": {
30
30
  "access": "public"
31
31
  },
32
+ "dependencies": {
33
+ "@decantr/core": "1.0.6",
34
+ "@decantr/registry": "1.0.4",
35
+ "@decantr/verifier": "1.0.6",
36
+ "@decantr/telemetry": "0.1.2",
37
+ "@decantr/essence-spec": "1.0.6"
38
+ },
32
39
  "scripts": {
33
40
  "build": "tsup",
34
41
  "certify:blueprints": "pnpm build && node scripts/certify-blueprints.mjs",
35
42
  "certify:workflows": "pnpm build && node scripts/certify-workflows.mjs",
36
43
  "test": "vitest run",
37
44
  "test:watch": "vitest",
38
- "prepublishOnly": "pnpm build",
39
45
  "preversion": "pnpm build && pnpm test"
40
- },
41
- "dependencies": {
42
- "@decantr/core": "workspace:*",
43
- "@decantr/essence-spec": "workspace:*",
44
- "@decantr/registry": "workspace:*",
45
- "@decantr/verifier": "workspace:*"
46
46
  }
47
- }
47
+ }
@@ -1,135 +0,0 @@
1
- // src/lib/scan-interactions.ts
2
- import { existsSync, readdirSync, readFileSync, statSync } from "fs";
3
- import { extname, join } from "path";
4
- import { verifyInteractionsInSource } from "@decantr/verifier";
5
- var SCAN_EXTENSIONS = /* @__PURE__ */ new Set([".tsx", ".jsx", ".ts", ".js", ".html", ".mdx"]);
6
- var SKIP_DIRECTORIES = /* @__PURE__ */ new Set([
7
- "node_modules",
8
- ".decantr",
9
- ".git",
10
- "dist",
11
- "build",
12
- ".next",
13
- ".turbo",
14
- "coverage",
15
- ".cache"
16
- ]);
17
- var MAX_FILE_SIZE = 1024 * 1024;
18
- function walkSourceTree(rootDir) {
19
- const sources = /* @__PURE__ */ new Map();
20
- function walk(dir) {
21
- let entries;
22
- try {
23
- entries = readdirSync(dir);
24
- } catch {
25
- return;
26
- }
27
- for (const entry of entries) {
28
- if (SKIP_DIRECTORIES.has(entry)) continue;
29
- const fullPath = join(dir, entry);
30
- let s;
31
- try {
32
- s = statSync(fullPath);
33
- } catch {
34
- continue;
35
- }
36
- if (s.isDirectory()) {
37
- walk(fullPath);
38
- } else if (s.isFile() && SCAN_EXTENSIONS.has(extname(entry))) {
39
- if (s.size > MAX_FILE_SIZE) continue;
40
- try {
41
- sources.set(fullPath, readFileSync(fullPath, "utf8"));
42
- } catch {
43
- }
44
- }
45
- }
46
- }
47
- walk(rootDir);
48
- return sources;
49
- }
50
- function collectDeclaredInteractions(projectRoot) {
51
- const manifestPath = join(projectRoot, ".decantr", "context", "pack-manifest.json");
52
- if (!existsSync(manifestPath)) return [];
53
- let manifest;
54
- try {
55
- manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
56
- } catch {
57
- return [];
58
- }
59
- const all = [];
60
- const pages = manifest.pages ?? [];
61
- const contextDir = join(projectRoot, ".decantr", "context");
62
- for (const page of pages) {
63
- const packPath = join(contextDir, page.json);
64
- if (!existsSync(packPath)) continue;
65
- let pack;
66
- try {
67
- pack = JSON.parse(readFileSync(packPath, "utf8"));
68
- } catch {
69
- continue;
70
- }
71
- const patterns = pack.data?.patterns ?? [];
72
- for (const pat of patterns) {
73
- if (Array.isArray(pat.interactions)) {
74
- all.push(...pat.interactions);
75
- }
76
- }
77
- }
78
- return all;
79
- }
80
- function scanProjectInteractions(projectRoot) {
81
- const declared = collectDeclaredInteractions(projectRoot);
82
- if (declared.length === 0) return [];
83
- const sources = walkSourceTree(projectRoot);
84
- if (sources.size === 0) return [];
85
- const missing = verifyInteractionsInSource(declared, sources);
86
- return missing.map(({ interaction, suggestion }) => `${interaction} \u2192 ${suggestion}`);
87
- }
88
-
89
- // src/guard-context.ts
90
- import { existsSync as existsSync2, readdirSync as readdirSync2, readFileSync as readFileSync2 } from "fs";
91
- import { join as join2 } from "path";
92
- function loadJsonEntries(dir) {
93
- if (!existsSync2(dir)) return [];
94
- try {
95
- return readdirSync2(dir).filter((file) => file.endsWith(".json") && file !== "index.json").map((file) => JSON.parse(readFileSync2(join2(dir, file), "utf-8")));
96
- } catch {
97
- return [];
98
- }
99
- }
100
- function buildGuardRegistryContext(projectRoot = process.cwd()) {
101
- const themeRegistry = /* @__PURE__ */ new Map();
102
- const patternRegistry = /* @__PURE__ */ new Map();
103
- const cacheDir = join2(projectRoot, ".decantr", "cache");
104
- const customDir = join2(projectRoot, ".decantr", "custom");
105
- for (const data of loadJsonEntries(join2(cacheDir, "@official", "themes"))) {
106
- if (typeof data.id === "string" && !themeRegistry.has(data.id)) {
107
- themeRegistry.set(data.id, {
108
- modes: Array.isArray(data.modes) ? data.modes.filter((mode) => typeof mode === "string") : ["light", "dark"]
109
- });
110
- }
111
- }
112
- for (const data of loadJsonEntries(join2(customDir, "themes"))) {
113
- if (typeof data.id === "string") {
114
- themeRegistry.set(`custom:${data.id}`, {
115
- modes: Array.isArray(data.modes) ? data.modes.filter((mode) => typeof mode === "string") : ["light", "dark"]
116
- });
117
- }
118
- }
119
- for (const data of loadJsonEntries(join2(cacheDir, "@official", "patterns"))) {
120
- if (typeof data.id === "string" && !patternRegistry.has(data.id)) {
121
- patternRegistry.set(data.id, data);
122
- }
123
- }
124
- for (const data of loadJsonEntries(join2(customDir, "patterns"))) {
125
- if (typeof data.id === "string") {
126
- patternRegistry.set(data.id, data);
127
- }
128
- }
129
- return { themeRegistry, patternRegistry };
130
- }
131
-
132
- export {
133
- scanProjectInteractions,
134
- buildGuardRegistryContext
135
- };
@@ -1,175 +0,0 @@
1
- import {
2
- buildGuardRegistryContext,
3
- scanProjectInteractions
4
- } from "./chunk-QRQCPD3C.js";
5
-
6
- // src/commands/heal.ts
7
- import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
8
- import { join as join2 } from "path";
9
- import { evaluateGuard, validateEssence } from "@decantr/essence-spec";
10
-
11
- // src/telemetry.ts
12
- import { existsSync, readFileSync, writeFileSync } from "fs";
13
- import { join } from "path";
14
- var TELEMETRY_ENDPOINT = "https://api.decantr.ai/v1/telemetry/guard";
15
- var TELEMETRY_TIMEOUT_MS = 3e3;
16
- var DNA_RULES = /* @__PURE__ */ new Set(["theme", "style", "density", "accessibility", "theme-mode"]);
17
- async function sendGuardMetrics(metrics) {
18
- try {
19
- const controller = new AbortController();
20
- const timer = setTimeout(() => controller.abort(), TELEMETRY_TIMEOUT_MS);
21
- await fetch(TELEMETRY_ENDPOINT, {
22
- method: "POST",
23
- headers: { "Content-Type": "application/json" },
24
- body: JSON.stringify(metrics),
25
- signal: controller.signal
26
- });
27
- clearTimeout(timer);
28
- } catch {
29
- }
30
- }
31
- function isOptedIn(projectRoot) {
32
- const projectJsonPath = join(projectRoot, ".decantr", "project.json");
33
- if (!existsSync(projectJsonPath)) return false;
34
- try {
35
- const data = JSON.parse(readFileSync(projectJsonPath, "utf-8"));
36
- return data.telemetry === true;
37
- } catch {
38
- return false;
39
- }
40
- }
41
- function optIn(projectRoot) {
42
- const projectJsonPath = join(projectRoot, ".decantr", "project.json");
43
- let data = {};
44
- if (existsSync(projectJsonPath)) {
45
- try {
46
- data = JSON.parse(readFileSync(projectJsonPath, "utf-8"));
47
- } catch {
48
- }
49
- }
50
- data.telemetry = true;
51
- writeFileSync(projectJsonPath, JSON.stringify(data, null, 2) + "\n", "utf-8");
52
- }
53
- function collectMetrics(essence, issues) {
54
- const dna = essence.dna ?? {};
55
- const blueprint = essence.blueprint ?? {};
56
- const meta = essence.meta ?? {};
57
- const guard = meta.guard ?? {};
58
- const theme = dna.theme ?? {};
59
- const sections = blueprint.sections ?? [];
60
- const routes = blueprint.routes ?? {};
61
- const byRule = {};
62
- let dnaCount = 0;
63
- let blueprintCount = 0;
64
- for (const issue of issues) {
65
- byRule[issue.rule] = (byRule[issue.rule] ?? 0) + 1;
66
- if (DNA_RULES.has(issue.rule)) {
67
- dnaCount++;
68
- } else {
69
- blueprintCount++;
70
- }
71
- }
72
- return {
73
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
74
- cli_version: "1.5.1",
75
- essence_version: essence.version ?? "unknown",
76
- guard_mode: guard.mode ?? "unknown",
77
- violations: {
78
- dna: dnaCount,
79
- blueprint: blueprintCount,
80
- by_rule: byRule
81
- },
82
- resolution_rate: 0,
83
- sections_count: sections.length,
84
- routes_count: Object.keys(routes).length,
85
- theme: theme.id ?? "unknown"
86
- };
87
- }
88
-
89
- // src/commands/heal.ts
90
- var GREEN = "\x1B[32m";
91
- var RED = "\x1B[31m";
92
- var YELLOW = "\x1B[33m";
93
- var CYAN = "\x1B[36m";
94
- var RESET = "\x1B[0m";
95
- var DIM = "\x1B[2m";
96
- async function cmdHeal(projectRoot = process.cwd(), options = {}) {
97
- const essencePath = join2(projectRoot, "decantr.essence.json");
98
- if (!existsSync2(essencePath)) {
99
- console.error("No decantr.essence.json found. Run `decantr init` first.");
100
- process.exitCode = 1;
101
- return;
102
- }
103
- const essence = JSON.parse(readFileSync2(essencePath, "utf-8"));
104
- console.log("Scanning for issues...\n");
105
- const issues = [];
106
- const validation = validateEssence(essence);
107
- if (!validation.valid) {
108
- for (const err of validation.errors) {
109
- issues.push({
110
- type: "error",
111
- rule: "schema",
112
- message: err
113
- });
114
- }
115
- }
116
- let interactionIssues = [];
117
- try {
118
- interactionIssues = scanProjectInteractions(projectRoot);
119
- } catch {
120
- }
121
- try {
122
- const guardContext = buildGuardRegistryContext(projectRoot);
123
- const violations = evaluateGuard(essence, {
124
- ...guardContext,
125
- interaction_issues: interactionIssues
126
- });
127
- for (const v of violations) {
128
- issues.push({
129
- type: v.severity === "error" ? "error" : "warning",
130
- rule: v.rule,
131
- message: v.message,
132
- suggestion: v.suggestion
133
- });
134
- }
135
- } catch {
136
- }
137
- if (issues.length === 0) {
138
- console.log(`${GREEN}No issues found. Project is healthy.${RESET}`);
139
- await maybeSendTelemetry(projectRoot, essence, issues, options);
140
- return;
141
- }
142
- console.log(`Found ${issues.length} issue(s):
143
- `);
144
- for (const issue of issues) {
145
- const icon = issue.type === "error" ? `${RED}x${RESET}` : `${YELLOW}!${RESET}`;
146
- console.log(`${icon} [${issue.rule}] ${issue.message}`);
147
- if (issue.suggestion) {
148
- console.log(` ${DIM}Suggestion: ${issue.suggestion}${RESET}`);
149
- }
150
- }
151
- console.log(`
152
- ${YELLOW}Manual fixes required. Review the issues above.${RESET}`);
153
- const hasError = issues.some((i) => i.type === "error");
154
- if (hasError) {
155
- process.exitCode = 1;
156
- }
157
- await maybeSendTelemetry(projectRoot, essence, issues, options);
158
- }
159
- async function maybeSendTelemetry(projectRoot, essence, issues, options) {
160
- if (options.telemetry && !isOptedIn(projectRoot)) {
161
- optIn(projectRoot);
162
- console.log(
163
- `
164
- ${CYAN}Telemetry enabled.${RESET} Anonymous guard metrics will be sent on future checks.`
165
- );
166
- console.log(`${DIM}Set "telemetry": false in .decantr/project.json to opt out.${RESET}`);
167
- }
168
- if (isOptedIn(projectRoot)) {
169
- const metrics = collectMetrics(essence, issues);
170
- sendGuardMetrics(metrics);
171
- }
172
- }
173
- export {
174
- cmdHeal
175
- };