@decantr/cli 2.8.0 → 2.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -38,7 +38,7 @@ Explicit workflow/adoption flags:
38
38
 
39
39
  ```bash
40
40
  decantr setup
41
- decantr adopt --base-url http://localhost:3000 --evidence --yes
41
+ decantr adopt --yes
42
42
  decantr codify --from-audit
43
43
  decantr codify --accept
44
44
  decantr task /feed "add saved recipe actions"
@@ -61,7 +61,14 @@ Adoption modes:
61
61
  - `style-bridge` writes bridge tokens/files that map Decantr intent onto an existing style system without requiring `@decantr/css`.
62
62
  - `decantr-css` writes the full Decantr CSS files and runtime guidance.
63
63
 
64
- Monorepos store both `workspaceRoot` and `appRoot`. In non-interactive workspace-root runs with multiple app candidates, pass `--project=<path>` so Decantr attaches to the intended app.
64
+ Monorepos store both `workspaceRoot` and `appRoot`. Install Decantr at the workspace root if that is where dependencies are managed, but attach Decantr to an app root with `--project=<path>`.
65
+
66
+ ```bash
67
+ pnpm add -D -w @decantr/cli
68
+ pnpm exec decantr setup
69
+ pnpm exec decantr workspace list
70
+ pnpm exec decantr adopt --project apps/web --yes
71
+ ```
65
72
 
66
73
  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.
67
74
 
@@ -86,11 +93,13 @@ Brownfield analysis also writes `.decantr/doctrine-map.json`, a ranked source-pr
86
93
  ```bash
87
94
  decantr setup
88
95
  decantr new my-app --blueprint=esports-hq
89
- decantr adopt --base-url http://localhost:3000 --evidence --yes
96
+ decantr adopt --yes
97
+ decantr adopt --project apps/web --yes
90
98
  decantr codify --from-audit
91
99
  decantr codify --accept
92
100
  decantr task /feed "add saved recipe actions"
93
101
  decantr verify --brownfield --local-patterns
102
+ decantr verify --base-url http://localhost:3000 --evidence
94
103
  decantr init --existing --blueprint=esports-hq
95
104
  decantr init --workflow=greenfield --adoption=contract-only
96
105
  decantr rules preview
@@ -151,7 +160,7 @@ Use `--json` for machines and schema validation, `--markdown` for CI summaries,
151
160
 
152
161
  `decantr verify init-ci` installs `.github/workflows/decantr-health.yml` for GitHub Actions. The generated workflow installs project dependencies, writes JSON/markdown health artifacts, gates with the Project Health CI command, appends the markdown report to the GitHub step summary, and uploads both files as artifacts. Use `--force` to replace an existing workflow, `--fail-on warn` for stricter repositories, or `--cli-version <version|latest>` to pin the package used by CI. In monorepos, add `--project <path>` from the repository root; dependency install stays at the root while health runs inside the app contract and uploads artifacts from that project path. Use `--workspace` to generate an aggregate gate that runs `decantr workspace health` from the repository root and uploads `.decantr/workspace-health.json` plus `.decantr/workspace-health.md`.
153
162
 
154
- `decantr workspace` is the monorepo reliability namespace. It discovers Decantr projects from `.decantr/workspace.json` or by finding `decantr.essence.json` files, runs projects with deterministic ordering, concurrency, per-project timeout, failure isolation, and aggregate JSON, and can limit a run to changed projects:
163
+ `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:
155
164
 
156
165
  ```bash
157
166
  decantr workspace list
package/dist/bin.js CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import "./chunk-QTPNV5WU.js";
2
+ import "./chunk-RKZMHS2K.js";
3
3
  import "./chunk-V3XAQWKD.js";
4
+ import "./chunk-VE6N3XWG.js";
4
5
  import "./chunk-KT2ROK2D.js";
@@ -1,3 +1,6 @@
1
+ import {
2
+ listWorkspaceAppCandidates
3
+ } from "./chunk-VE6N3XWG.js";
1
4
  import {
2
5
  createProjectHealthReport
3
6
  } from "./chunk-PAF4PBD3.js";
@@ -93,6 +96,14 @@ function listWorkspaceProjects(root = process.cwd()) {
93
96
  }
94
97
  return [...byPath.values()].sort((a, b) => a.path.localeCompare(b.path));
95
98
  }
99
+ function listWorkspaceCandidates(root = process.cwd(), projects = listWorkspaceProjects(root)) {
100
+ const attached = new Set(projects.map((project) => project.path));
101
+ return listWorkspaceAppCandidates(root).map((path) => ({
102
+ path,
103
+ attached: attached.has(path),
104
+ suggestedAdoptCommand: `decantr adopt --project ${path} --yes`
105
+ }));
106
+ }
96
107
  function changedPaths(root, since) {
97
108
  try {
98
109
  const output = execFileSync("git", ["diff", "--name-only", since, "--"], {
@@ -100,7 +111,9 @@ function changedPaths(root, since) {
100
111
  encoding: "utf-8",
101
112
  stdio: ["ignore", "pipe", "ignore"]
102
113
  });
103
- return new Set(output.split("\n").map((line) => line.trim()).filter(Boolean));
114
+ return new Set(
115
+ output.split("\n").map((line) => line.trim()).filter(Boolean)
116
+ );
104
117
  } catch {
105
118
  return /* @__PURE__ */ new Set();
106
119
  }
@@ -116,7 +129,10 @@ function projectChanged(project, changed) {
116
129
  async function withTimeout(promise, timeoutMs, label) {
117
130
  let timeout;
118
131
  const timer = new Promise((_, reject) => {
119
- timeout = setTimeout(() => reject(new Error(`${label} timed out after ${timeoutMs}ms`)), timeoutMs);
132
+ timeout = setTimeout(
133
+ () => reject(new Error(`${label} timed out after ${timeoutMs}ms`)),
134
+ timeoutMs
135
+ );
120
136
  });
121
137
  try {
122
138
  return await Promise.race([promise, timer]);
@@ -265,9 +281,11 @@ function parseWorkspaceArgs(args) {
265
281
  else if (arg.startsWith("--since=")) options.since = arg.split("=")[1];
266
282
  else if (arg === "--output" && args[index + 1]) options.output = args[++index];
267
283
  else if (arg.startsWith("--output=")) options.output = arg.split("=")[1];
268
- else if (arg === "--fail-on" && args[index + 1]) options.failOn = parseHealthFailOn(args[++index]);
284
+ else if (arg === "--fail-on" && args[index + 1])
285
+ options.failOn = parseHealthFailOn(args[++index]);
269
286
  else if (arg.startsWith("--fail-on=")) options.failOn = parseHealthFailOn(arg.split("=")[1]);
270
- else if (arg === "--concurrency" && args[index + 1]) options.concurrency = Number(args[++index]);
287
+ else if (arg === "--concurrency" && args[index + 1])
288
+ options.concurrency = Number(args[++index]);
271
289
  else if (arg.startsWith("--concurrency=")) options.concurrency = Number(arg.split("=")[1]);
272
290
  else if (arg === "--timeout-ms" && args[index + 1]) options.timeoutMs = Number(args[++index]);
273
291
  else if (arg.startsWith("--timeout-ms=")) options.timeoutMs = Number(arg.split("=")[1]);
@@ -278,15 +296,36 @@ async function cmdWorkspace(workspaceRoot = process.cwd(), args = ["workspace"])
278
296
  const options = parseWorkspaceArgs(args);
279
297
  if (options.subcommand === "list") {
280
298
  const projects = listWorkspaceProjects(workspaceRoot);
281
- const payload2 = `${JSON.stringify({ projects }, null, 2)}
299
+ const candidates = listWorkspaceCandidates(workspaceRoot, projects);
300
+ const unattachedCandidates = candidates.filter((candidate) => !candidate.attached);
301
+ const payload2 = `${JSON.stringify({ projects, candidates }, null, 2)}
282
302
  `;
283
303
  if (options.json) {
284
304
  process.stdout.write(payload2);
285
305
  return;
286
306
  }
287
307
  console.log(`${BOLD}Decantr workspace projects${RESET}`);
288
- for (const project of projects) {
289
- console.log(`${project.path} ${DIM}${project.source}${RESET}`);
308
+ console.log("");
309
+ console.log("Attached Decantr projects:");
310
+ if (projects.length === 0) {
311
+ console.log(` ${DIM}(none yet)${RESET}`);
312
+ } else {
313
+ for (const project of projects) {
314
+ console.log(` ${project.path} ${DIM}${project.source}${RESET}`);
315
+ }
316
+ }
317
+ if (candidates.length > 0) {
318
+ console.log("");
319
+ console.log("App candidates:");
320
+ for (const candidate of candidates) {
321
+ const status = candidate.attached ? `${GREEN}attached${RESET}` : `${YELLOW}unattached${RESET}`;
322
+ console.log(` ${candidate.path} ${DIM}${status}${RESET}`);
323
+ }
324
+ }
325
+ if (unattachedCandidates.length > 0) {
326
+ console.log("");
327
+ console.log("Start by attaching one app:");
328
+ console.log(` ${unattachedCandidates[0].suggestedAdoptCommand}`);
290
329
  }
291
330
  return;
292
331
  }
@@ -296,7 +335,8 @@ async function cmdWorkspace(workspaceRoot = process.cwd(), args = ["workspace"])
296
335
  if (options.output) {
297
336
  mkdirSync(dirname(resolve(workspaceRoot, options.output)), { recursive: true });
298
337
  writeFileSync(resolve(workspaceRoot, options.output), payload, "utf-8");
299
- if (!options.ci) console.log(`${GREEN}Wrote Decantr workspace health:${RESET} ${options.output}`);
338
+ if (!options.ci)
339
+ console.log(`${GREEN}Wrote Decantr workspace health:${RESET} ${options.output}`);
300
340
  } else {
301
341
  process.stdout.write(payload);
302
342
  }
@@ -307,6 +347,7 @@ async function cmdWorkspace(workspaceRoot = process.cwd(), args = ["workspace"])
307
347
 
308
348
  export {
309
349
  listWorkspaceProjects,
350
+ listWorkspaceCandidates,
310
351
  createWorkspaceHealthReport,
311
352
  formatWorkspaceHealthText,
312
353
  formatWorkspaceHealthMarkdown,