@decantr/cli 2.9.1 → 2.9.3
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 +10 -5
- package/dist/bin.js +3 -3
- package/dist/{chunk-FKM4OQDF.js → chunk-ARR3EPS2.js} +17 -107
- package/dist/{chunk-6UDJDQPT.js → chunk-VMNUJOEH.js} +794 -328
- package/dist/{chunk-TMOCTDYY.js → chunk-XZFKK6V7.js} +311 -50
- package/dist/{health-Q7XF3I5Z.js → health-MB63O56B.js} +1 -1
- package/dist/index.js +3 -3
- package/dist/{studio-EDQMI6JE.js → studio-6QGXJBVH.js} +2 -2
- package/dist/{workspace-JA2RZI6V.js → workspace-OGFYJA4N.js} +2 -2
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -19,11 +19,13 @@ npx @decantr/cli new my-app --blueprint=esports-hq
|
|
|
19
19
|
|
|
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 next command.
|
|
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
|
-
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, and only applies
|
|
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
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.
|
|
24
|
-
In monorepos, app-scoped commands accept `--project <app-path>`. 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`.
|
|
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
|
|
|
27
|
+
App-scoped primitives now share the same `--project` posture as the primary workflow commands. From a workspace root, `health`, `status`, `upgrade`, `add`, `remove`, `theme`, `export`, `suggest`, `magic`, `rules`, and `telemetry` target the selected app instead of the root. Task/read paths, local-law summaries, and refresh change summaries are printed as openable workspace paths. Nonexistent project paths fail immediately, and Brownfield adoption refuses component packages unless you intentionally pass `--force-package`.
|
|
28
|
+
|
|
27
29
|
Current starter adapter availability:
|
|
28
30
|
|
|
29
31
|
- `react-vite` is the React + Vite runnable bootstrap adapter
|
|
@@ -128,6 +130,8 @@ decantr list blueprints --blueprint-set featured
|
|
|
128
130
|
decantr list blueprints --blueprint-set certified
|
|
129
131
|
decantr search dashboard --type blueprint --blueprint-set labs
|
|
130
132
|
decantr suggest "recipe feed with infinite scroll" --route /feed --from-code
|
|
133
|
+
decantr suggest --from-code --file app/page.tsx --project apps/web
|
|
134
|
+
decantr suggest "standardize buttons" --project apps/web
|
|
131
135
|
decantr list patterns
|
|
132
136
|
decantr showcase verification --json
|
|
133
137
|
```
|
|
@@ -138,7 +142,7 @@ decantr showcase verification --json
|
|
|
138
142
|
|
|
139
143
|
`decantr doctor` explains project/workspace state, adoption mode, generated artifacts, local law, visual evidence, design authority signals, CI wiring, and the next command to run. 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.
|
|
140
144
|
|
|
141
|
-
`decantr ci` is the blessed non-mutating automation gate. It runs the Project Health surface with adoption-mode-aware local law checks and emits a schema-backed CI report. `decantr ci init` writes root GitHub workflows or portable generic snippets using the detected package manager and pinned local CLI command instead of `@latest
|
|
145
|
+
`decantr ci` is the blessed non-mutating automation gate. It runs the Project Health surface with adoption-mode-aware local law checks and emits a schema-backed CI report. `decantr ci init` writes root GitHub workflows or portable generic snippets using the detected package manager and pinned local CLI command instead of `@latest`; if the root manifest has not pinned Decantr yet, it prints the exact install command first.
|
|
142
146
|
|
|
143
147
|
`decantr health` remains the advanced project observability primitive. It composes the existing verifier audit, guard checks, brownfield route drift checks, runtime evidence, and execution-pack files into a `ProjectHealthReport` with a status, score, route summary, pack summary, findings, and AI-ready remediation prompts.
|
|
144
148
|
|
|
@@ -156,6 +160,7 @@ decantr health
|
|
|
156
160
|
decantr health --format json
|
|
157
161
|
decantr health --markdown --output health.md
|
|
158
162
|
decantr health --prompt <finding-id>
|
|
163
|
+
decantr health --project apps/registry --prompt <finding-id>
|
|
159
164
|
decantr health --evidence --output .decantr/evidence/latest.json
|
|
160
165
|
decantr health --browser --base-url http://localhost:3000 --evidence
|
|
161
166
|
decantr health --save-baseline
|
|
@@ -171,9 +176,9 @@ decantr verify --workspace --changed --since origin/main
|
|
|
171
176
|
decantr export --to figma-tokens
|
|
172
177
|
```
|
|
173
178
|
|
|
174
|
-
Use `--json` for machines and schema validation, `--markdown` for summaries, `--evidence` for the privacy-redacted Evidence Bundle, and `--prompt <finding-id>` when you want a scoped remediation prompt for an AI assistant. The prompt command prints instructions only; it does not modify source files. `--browser` uses a project-local Playwright install and a supplied base URL to capture local route screenshots under `.decantr/evidence/screenshots/` and write `.decantr/evidence/visual-manifest.json`; missing Playwright becomes a setup finding, not a crash. `--save-baseline` writes `.decantr/health-baseline.json`; `--since-baseline` writes `.decantr/health-baseline-diff.json` with changed files, route impact, finding deltas, screenshot hash drift, and contract drift. `--design-tokens <path>` compares a Tokens Studio/Figma token JSON export against Decantr CSS token names. `decantr ci --fail-on error` fails only when blocking errors exist; `decantr ci --fail-on warn` also fails on warnings.
|
|
179
|
+
Use `--json` for machines and schema validation, `--markdown` for summaries, `--evidence` for the privacy-redacted Evidence Bundle, and `--prompt <finding-id>` when you want a scoped remediation prompt for an AI assistant. The prompt command prints instructions only; it does not modify source files. In monorepos, prompt commands preserve `--project <path>` so the finding resolves from the same app that produced it. `--browser` uses a project-local Playwright install and a supplied base URL to capture local route screenshots under `.decantr/evidence/screenshots/` and write `.decantr/evidence/visual-manifest.json`; missing Playwright becomes a visible setup finding/message, not a crash or silent skip. `--save-baseline` writes `.decantr/health-baseline.json`; `--since-baseline` writes `.decantr/health-baseline-diff.json` with changed files, route impact, finding deltas, screenshot hash drift, and contract drift. `--design-tokens <path>` compares a Tokens Studio/Figma token JSON export against Decantr CSS token names. `decantr ci --fail-on error` fails only when blocking errors exist; `decantr ci --fail-on warn` also fails on warnings.
|
|
175
180
|
|
|
176
|
-
`decantr ci init` installs `.github/workflows/decantr-ci.yml` for GitHub Actions. The generated workflow installs dependencies at the workspace root, writes JSON/markdown CI artifacts, gates with `decantr ci`, appends the markdown report to the GitHub step summary, and uploads both files as artifacts. Use `--force` to replace an existing workflow or `--fail-on warn` for stricter repositories. In monorepos, add `--project <path>` from the repository root; dependency install stays at the root while CI evaluates the app contract and uploads app-scoped artifacts. Use `--workspace` to generate an aggregate gate. Use `--provider generic` for Jenkins, Please, Buildkite, GitLab, Azure DevOps, or internal deployment tools. Generated CI uses the pinned local package-manager command and does not depend on `@latest`.
|
|
181
|
+
`decantr ci init` installs `.github/workflows/decantr-ci.yml` for GitHub Actions. The generated workflow installs dependencies at the workspace root, writes JSON/markdown CI artifacts, gates with `decantr ci`, appends the markdown report to the GitHub step summary, and uploads both files as artifacts. Use `--force` to replace an existing workflow or `--fail-on warn` for stricter repositories. In monorepos, add `--project <path>` from the repository root; dependency install stays at the root while CI evaluates the app contract and uploads app-scoped artifacts. Use `--workspace` to generate an aggregate gate. Use `--provider generic` for Jenkins, Please, Buildkite, GitLab, Azure DevOps, or internal deployment tools. Generated CI uses the pinned local package-manager command and does not depend on `@latest`. Project Health remediation prompts are also monorepo-aware, so missing-pack fixes use `apps/web/decantr.essence.json` and CI recommendations include `--project apps/web`.
|
|
177
182
|
|
|
178
183
|
`decantr workspace` is the monorepo reliability namespace. Before attach, `workspace list` shows app candidates. After attach, it also discovers Decantr projects from `.decantr/workspace.json` or by finding `decantr.essence.json` files. Workspace health runs projects with deterministic ordering, concurrency, per-project timeout, failure isolation, and aggregate JSON, and can limit a run to changed projects:
|
|
179
184
|
|
package/dist/bin.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import "./chunk-
|
|
2
|
+
import "./chunk-VMNUJOEH.js";
|
|
3
3
|
import "./chunk-RXF7ZYGK.js";
|
|
4
|
-
import "./chunk-
|
|
5
|
-
import "./chunk-
|
|
4
|
+
import "./chunk-ARR3EPS2.js";
|
|
5
|
+
import "./chunk-XZFKK6V7.js";
|
|
6
6
|
import "./chunk-34TZXWIF.js";
|
|
@@ -1,101 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
|
-
createProjectHealthReport
|
|
3
|
-
|
|
2
|
+
createProjectHealthReport,
|
|
3
|
+
listWorkspaceAppCandidates
|
|
4
|
+
} from "./chunk-XZFKK6V7.js";
|
|
4
5
|
|
|
5
6
|
// src/commands/workspace.ts
|
|
6
7
|
import { execFileSync } from "child_process";
|
|
7
|
-
import { existsSync
|
|
8
|
-
import { dirname
|
|
9
|
-
|
|
10
|
-
// src/workspace.ts
|
|
11
|
-
import { existsSync, readdirSync, readFileSync } from "fs";
|
|
12
|
-
import { dirname, join, resolve } from "path";
|
|
13
|
-
function readPackageJson(dir) {
|
|
14
|
-
const path = join(dir, "package.json");
|
|
15
|
-
if (!existsSync(path)) return null;
|
|
16
|
-
try {
|
|
17
|
-
return JSON.parse(readFileSync(path, "utf-8"));
|
|
18
|
-
} catch {
|
|
19
|
-
return null;
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
function hasWorkspaceMarker(dir) {
|
|
23
|
-
if (existsSync(join(dir, "pnpm-workspace.yaml")) || existsSync(join(dir, "turbo.json")) || existsSync(join(dir, "nx.json"))) {
|
|
24
|
-
return true;
|
|
25
|
-
}
|
|
26
|
-
const pkg = readPackageJson(dir);
|
|
27
|
-
return Boolean(pkg?.workspaces);
|
|
28
|
-
}
|
|
29
|
-
function findWorkspaceRoot(startDir) {
|
|
30
|
-
let current = resolve(startDir);
|
|
31
|
-
while (true) {
|
|
32
|
-
if (hasWorkspaceMarker(current)) return current;
|
|
33
|
-
const parent = dirname(current);
|
|
34
|
-
if (parent === current) return null;
|
|
35
|
-
current = parent;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
function looksLikeApp(dir, options = {}) {
|
|
39
|
-
const allowSourceDirs = options.allowSourceDirs ?? true;
|
|
40
|
-
const allowPackageDeps = options.allowPackageDeps ?? true;
|
|
41
|
-
const pkg = readPackageJson(dir);
|
|
42
|
-
const deps = { ...pkg?.dependencies, ...pkg?.devDependencies };
|
|
43
|
-
const hasFrontendDependency = Boolean(
|
|
44
|
-
deps.react || deps["react-dom"] || deps.next || deps.vue || deps.svelte || deps["@angular/core"] || deps.astro || deps.nuxt
|
|
45
|
-
);
|
|
46
|
-
const hasServerOnlyDependency = Boolean(
|
|
47
|
-
deps.hono || deps.express || deps.fastify || deps.koa || deps["@hapi/hapi"]
|
|
48
|
-
);
|
|
49
|
-
if (existsSync(join(dir, "next.config.js")) || existsSync(join(dir, "next.config.ts")) || existsSync(join(dir, "next.config.mjs")) || existsSync(join(dir, "vite.config.ts")) || existsSync(join(dir, "vite.config.js")) || existsSync(join(dir, "angular.json")) || existsSync(join(dir, "svelte.config.js")) || existsSync(join(dir, "svelte.config.ts")) || existsSync(join(dir, "astro.config.mjs"))) {
|
|
50
|
-
return true;
|
|
51
|
-
}
|
|
52
|
-
if (allowSourceDirs && (existsSync(join(dir, "src")) || existsSync(join(dir, "app")) || existsSync(join(dir, "pages")))) {
|
|
53
|
-
if (hasFrontendDependency) return true;
|
|
54
|
-
if (hasServerOnlyDependency) return false;
|
|
55
|
-
return true;
|
|
56
|
-
}
|
|
57
|
-
if (!allowPackageDeps) return false;
|
|
58
|
-
return hasFrontendDependency;
|
|
59
|
-
}
|
|
60
|
-
function listWorkspaceApps(workspaceRoot) {
|
|
61
|
-
const candidates = [];
|
|
62
|
-
for (const base of ["apps", "packages"]) {
|
|
63
|
-
const baseDir = join(workspaceRoot, base);
|
|
64
|
-
if (!existsSync(baseDir)) continue;
|
|
65
|
-
for (const entry of readdirSync(baseDir, { withFileTypes: true })) {
|
|
66
|
-
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
|
|
67
|
-
const candidate = join(baseDir, entry.name);
|
|
68
|
-
if (looksLikeApp(candidate, {
|
|
69
|
-
allowSourceDirs: base === "apps",
|
|
70
|
-
allowPackageDeps: base === "apps"
|
|
71
|
-
})) {
|
|
72
|
-
candidates.push(`${base}/${entry.name}`);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
return candidates.sort();
|
|
77
|
-
}
|
|
78
|
-
function listWorkspaceAppCandidates(workspaceRoot) {
|
|
79
|
-
return listWorkspaceApps(resolve(workspaceRoot));
|
|
80
|
-
}
|
|
81
|
-
function resolveWorkspaceInfo(cwd, projectArg) {
|
|
82
|
-
const absoluteCwd = resolve(cwd);
|
|
83
|
-
const workspaceRoot = findWorkspaceRoot(absoluteCwd) ?? absoluteCwd;
|
|
84
|
-
const appRoot = projectArg ? resolve(absoluteCwd, projectArg) : absoluteCwd;
|
|
85
|
-
const appCandidates = listWorkspaceApps(workspaceRoot);
|
|
86
|
-
const projectScope = workspaceRoot !== appRoot || appCandidates.length > 0 ? "workspace-app" : "single-app";
|
|
87
|
-
const requiresProjectSelection = !projectArg && workspaceRoot === absoluteCwd && appCandidates.length > 0;
|
|
88
|
-
return {
|
|
89
|
-
cwd: absoluteCwd,
|
|
90
|
-
workspaceRoot,
|
|
91
|
-
appRoot,
|
|
92
|
-
projectScope,
|
|
93
|
-
appCandidates,
|
|
94
|
-
requiresProjectSelection
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// src/commands/workspace.ts
|
|
8
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "fs";
|
|
9
|
+
import { dirname, join, relative, resolve } from "path";
|
|
99
10
|
var BOLD = "\x1B[1m";
|
|
100
11
|
var DIM = "\x1B[2m";
|
|
101
12
|
var GREEN = "\x1B[32m";
|
|
@@ -114,12 +25,12 @@ var DEFAULT_IGNORES = /* @__PURE__ */ new Set([
|
|
|
114
25
|
"playwright-report"
|
|
115
26
|
]);
|
|
116
27
|
function workspaceConfigPath(root) {
|
|
117
|
-
return
|
|
28
|
+
return join(root, ".decantr", "workspace.json");
|
|
118
29
|
}
|
|
119
30
|
function readWorkspaceConfig(root) {
|
|
120
31
|
const path = workspaceConfigPath(root);
|
|
121
|
-
if (!
|
|
122
|
-
return JSON.parse(
|
|
32
|
+
if (!existsSync(path)) return null;
|
|
33
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
123
34
|
}
|
|
124
35
|
function normalizeProjectPath(raw) {
|
|
125
36
|
const normalized = raw.replace(/^\.\/+/, "").replace(/\/+$/, "");
|
|
@@ -138,21 +49,21 @@ function discoverProjectPaths(root, config) {
|
|
|
138
49
|
if (depth > 6) return;
|
|
139
50
|
const rel = relative(root, dir).replace(/\\/g, "/");
|
|
140
51
|
if (rel && [...ignored].some((entry) => rel === entry || rel.startsWith(`${entry}/`))) return;
|
|
141
|
-
if (
|
|
52
|
+
if (existsSync(join(dir, "decantr.essence.json"))) {
|
|
142
53
|
results.add(rel || ".");
|
|
143
54
|
return;
|
|
144
55
|
}
|
|
145
|
-
for (const entry of
|
|
56
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
146
57
|
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
|
|
147
58
|
if (ignored.has(entry.name)) continue;
|
|
148
|
-
walk(
|
|
59
|
+
walk(join(dir, entry.name), depth + 1);
|
|
149
60
|
}
|
|
150
61
|
}
|
|
151
62
|
walk(root, 0);
|
|
152
63
|
return [...results].sort();
|
|
153
64
|
}
|
|
154
65
|
function listWorkspaceProjects(root = process.cwd()) {
|
|
155
|
-
const workspaceRoot =
|
|
66
|
+
const workspaceRoot = resolve(root);
|
|
156
67
|
const config = readWorkspaceConfig(workspaceRoot);
|
|
157
68
|
const byPath = /* @__PURE__ */ new Map();
|
|
158
69
|
for (const project of config?.projects ?? []) {
|
|
@@ -160,7 +71,7 @@ function listWorkspaceProjects(root = process.cwd()) {
|
|
|
160
71
|
byPath.set(path, {
|
|
161
72
|
id: project.id ?? projectIdFromPath(path),
|
|
162
73
|
path,
|
|
163
|
-
absolutePath:
|
|
74
|
+
absolutePath: resolve(workspaceRoot, path),
|
|
164
75
|
owner: project.owner ?? null,
|
|
165
76
|
tags: project.tags ?? [],
|
|
166
77
|
criticality: project.criticality ?? "normal",
|
|
@@ -173,7 +84,7 @@ function listWorkspaceProjects(root = process.cwd()) {
|
|
|
173
84
|
byPath.set(path, {
|
|
174
85
|
id: projectIdFromPath(path),
|
|
175
86
|
path,
|
|
176
|
-
absolutePath:
|
|
87
|
+
absolutePath: resolve(workspaceRoot, path),
|
|
177
88
|
owner: null,
|
|
178
89
|
tags: [],
|
|
179
90
|
criticality: "normal",
|
|
@@ -240,7 +151,7 @@ async function mapLimited(items, concurrency, fn) {
|
|
|
240
151
|
return results;
|
|
241
152
|
}
|
|
242
153
|
async function createWorkspaceHealthReport(root = process.cwd(), options = {}) {
|
|
243
|
-
const workspaceRoot =
|
|
154
|
+
const workspaceRoot = resolve(root);
|
|
244
155
|
const config = readWorkspaceConfig(workspaceRoot);
|
|
245
156
|
const since = options.since ?? "origin/main";
|
|
246
157
|
const changed = options.changedOnly ? changedPaths(workspaceRoot, since) : /* @__PURE__ */ new Set();
|
|
@@ -420,8 +331,8 @@ async function cmdWorkspace(workspaceRoot = process.cwd(), args = ["workspace"])
|
|
|
420
331
|
const payload = options.json ? `${JSON.stringify(report, null, 2)}
|
|
421
332
|
` : options.markdown ? formatWorkspaceHealthMarkdown(report) : formatWorkspaceHealthText(report);
|
|
422
333
|
if (options.output) {
|
|
423
|
-
mkdirSync(
|
|
424
|
-
writeFileSync(
|
|
334
|
+
mkdirSync(dirname(resolve(workspaceRoot, options.output)), { recursive: true });
|
|
335
|
+
writeFileSync(resolve(workspaceRoot, options.output), payload, "utf-8");
|
|
425
336
|
if (!options.ci)
|
|
426
337
|
console.log(`${GREEN}Wrote Decantr workspace health:${RESET} ${options.output}`);
|
|
427
338
|
} else {
|
|
@@ -433,7 +344,6 @@ async function cmdWorkspace(workspaceRoot = process.cwd(), args = ["workspace"])
|
|
|
433
344
|
}
|
|
434
345
|
|
|
435
346
|
export {
|
|
436
|
-
resolveWorkspaceInfo,
|
|
437
347
|
listWorkspaceProjects,
|
|
438
348
|
listWorkspaceCandidates,
|
|
439
349
|
createWorkspaceHealthReport,
|