@vodmal/vdx-cli 0.4.0 → 0.5.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 CHANGED
@@ -19,9 +19,34 @@ npx -y -p @vodmal/vdx-cli vdx audit /path/to/project
19
19
  ## Use
20
20
 
21
21
  ```bash
22
+ # Lifecycle verbs (pass-through to `mise run <verb>`; requires mise + mise.toml)
23
+ vdx up | down | build | test | check | fix
24
+
25
+ # Maturity audit against the owner baseline rubric
22
26
  vdx audit <project-path> [--json] [--rubric <path>] [--stack <id>]
23
- vdx init <project-path> [--baseline github.com/org/repo@vX.Y] [--dry-run] [--force]
24
- vdx-mcp --project <path> # MCP stdio server (used by the plugin)
27
+
28
+ # Generate mise.toml + AGENTS.md for a project
29
+ vdx init <project-path> [--baseline github.com/org/repo@vX.Y] [--stack <id>] [--dry-run] [--force]
30
+
31
+ # Publish a library (Node MVP; PHP/Python coming in Y.3)
32
+ vdx publish <patch|minor|major> [--dry-run] [--force]
33
+
34
+ # MCP stdio server consumed by the Claude Code plugin
35
+ vdx-mcp --project <path>
36
+ ```
37
+
38
+ **Three quick examples**
39
+
40
+ ```bash
41
+ # 1. Score the project I'm standing in
42
+ vdx audit .
43
+
44
+ # 2. Wire native scripts into the 6 lifecycle verbs, then run one
45
+ vdx init . --stack node
46
+ vdx test # → mise run test (which calls `vitest run` or whatever was detected)
47
+
48
+ # 3. Ship a new minor release of a Node lib
49
+ vdx publish minor # bump → npm publish (OTP prompt) → git commit + tag (no push)
25
50
  ```
26
51
 
27
52
  By default the bundled `rubric/vdx-rubric.yaml` is used (a mirror of canonical
@@ -48,6 +73,9 @@ npm run typecheck
48
73
  - `src/evaluator.ts` — recursive evaluator + sugar notation
49
74
  - `src/scoring.ts` — delta-style levels, flags for orthogonal axes
50
75
  - `src/audit.ts` — orchestrator: overrides + applies_to filter + subpackage-ctx
76
+ - `src/init.ts` — `vdx init` planner (`selectVerbTask` + mise.toml/AGENTS.md renderers)
77
+ - `src/run.ts` — `resolveLifecycleVerb` + error renderer (pure logic for `vdx <verb>`)
78
+ - `src/publish.ts` — `planPublish` (pre-flight) + `executePublish` (bump → npm → git)
51
79
  - `src/report.ts` — markdown / JSON output
52
80
  - `src/index.ts` — CLI entry
53
81
  - `src/mcp-server.ts` — MCP stdio server (9 tools)
@@ -60,4 +88,8 @@ npm run typecheck
60
88
  - Baseline loading from a git ref is documented but evaluator still reads file
61
89
  paths only — `baseline:` in `mise.toml` is recorded but does not auto-fetch.
62
90
  - Watermark drift (phase 2 of `drift-algorithm.md`).
63
- - O25/O26/O27/O33/O34 — see [docs/decisions.md](../docs/decisions.md).
91
+ - `vdx publish` only ships for Node (MVP). PHP/Python in Y.3; Cargo/Ruby/Go/Java
92
+ in Y.4.
93
+ - Open items: O25 (mock-infra delta for Node), O26 (TOML round-trip), O27 (real
94
+ shared-infra precheck), O32 (multi-subpackage monorepo). See
95
+ [docs/decisions.md](../docs/decisions.md).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vodmal/vdx-cli",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "vdx — unified lifecycle interface (up/down/build/test/check/fix) + versioned maturity audit with drift detection. Pairs with the vdx Claude Code plugin.",
5
5
  "keywords": [
6
6
  "vdx",
package/src/index.ts CHANGED
@@ -1,8 +1,15 @@
1
1
  #!/usr/bin/env node
2
2
  import * as path from 'node:path';
3
+ import { execFileSync } from 'node:child_process';
3
4
  import { loadRubric } from './rubric.ts';
4
5
  import { loadManifest, loadOverrides } from './manifest.ts';
5
6
  import { autoDetectStack, type Ctx } from './facts.ts';
7
+ import {
8
+ LIFECYCLE_VERBS,
9
+ type LifecycleVerb,
10
+ resolveLifecycleVerb,
11
+ renderResolveError,
12
+ } from './run.ts';
6
13
  import { audit } from './audit.ts';
7
14
  import { reportMarkdown, reportJson } from './report.ts';
8
15
  import { planInit, writeInit, renderPlanSummary } from './init.ts';
@@ -20,6 +27,7 @@ const DEFAULT_RUBRIC = resolveDefaultRubric();
20
27
  function usage(): never {
21
28
  process.stderr.write(
22
29
  `Usage:
30
+ vdx <up|down|build|test|check|fix> run lifecycle verb (via mise run <verb>)
23
31
  vdx audit <project_path> [--rubric <path>] [--stack <stack>] [--json]
24
32
  vdx init <project_path> [--stack <id>] [--baseline <ref>] [--dry-run] [--force]
25
33
  vdx publish <patch|minor|major> [--dry-run] [--force]
@@ -176,8 +184,32 @@ function cmdPublish(opts: ParsedArgs): void {
176
184
  }
177
185
  }
178
186
 
187
+ function cmdRun(verb: LifecycleVerb): void {
188
+ const projectRoot = process.cwd();
189
+ const res = resolveLifecycleVerb(projectRoot, verb);
190
+ if (!res.ok) {
191
+ process.stderr.write(renderResolveError(res, projectRoot, verb));
192
+ process.exit(2);
193
+ }
194
+
195
+ try {
196
+ execFileSync('mise', ['run', verb], { cwd: projectRoot, stdio: 'inherit' });
197
+ } catch (e: any) {
198
+ if (e?.code === 'ENOENT') {
199
+ process.stderr.write(
200
+ `vdx: \`mise\` binary not found on PATH\n` +
201
+ `hint: install mise — https://mise.jdx.dev/getting-started.html\n`,
202
+ );
203
+ process.exit(127);
204
+ }
205
+ process.exit(typeof e?.status === 'number' ? e.status : 1);
206
+ }
207
+ }
208
+
179
209
  const parsed = parseArgs(process.argv);
180
210
  if (parsed.cmd === 'audit') cmdAudit(parsed);
181
211
  else if (parsed.cmd === 'init') cmdInit(parsed);
182
212
  else if (parsed.cmd === 'publish') cmdPublish(parsed);
213
+ else if ((LIFECYCLE_VERBS as readonly string[]).includes(parsed.cmd))
214
+ cmdRun(parsed.cmd as LifecycleVerb);
183
215
  else usage();
package/src/run.ts ADDED
@@ -0,0 +1,56 @@
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+ import * as TOML from 'smol-toml';
4
+
5
+ export const LIFECYCLE_VERBS = ['up', 'down', 'build', 'test', 'check', 'fix'] as const;
6
+ export type LifecycleVerb = (typeof LIFECYCLE_VERBS)[number];
7
+
8
+ export type VerbResolveResult =
9
+ | { ok: true; miseTomlPath: string }
10
+ | {
11
+ ok: false;
12
+ reason: 'no-mise-toml' | 'parse-error' | 'no-task';
13
+ detail?: string;
14
+ };
15
+
16
+ export function resolveLifecycleVerb(
17
+ projectRoot: string,
18
+ verb: string,
19
+ ): VerbResolveResult {
20
+ const miseTomlPath = path.join(projectRoot, 'mise.toml');
21
+ if (!fs.existsSync(miseTomlPath)) {
22
+ return { ok: false, reason: 'no-mise-toml' };
23
+ }
24
+ let parsed: unknown;
25
+ try {
26
+ parsed = TOML.parse(fs.readFileSync(miseTomlPath, 'utf8'));
27
+ } catch (e: any) {
28
+ return { ok: false, reason: 'parse-error', detail: e?.message ?? String(e) };
29
+ }
30
+ const tasks = (parsed as { tasks?: Record<string, unknown> })?.tasks;
31
+ if (!tasks || typeof tasks !== 'object' || !(verb in tasks)) {
32
+ return { ok: false, reason: 'no-task' };
33
+ }
34
+ return { ok: true, miseTomlPath };
35
+ }
36
+
37
+ export function renderResolveError(
38
+ res: Exclude<VerbResolveResult, { ok: true }>,
39
+ projectRoot: string,
40
+ verb: string,
41
+ ): string {
42
+ switch (res.reason) {
43
+ case 'no-mise-toml':
44
+ return (
45
+ `vdx: no mise.toml in ${projectRoot}\n` +
46
+ `hint: run \`vdx init .\` to generate one, or define it manually.\n`
47
+ );
48
+ case 'parse-error':
49
+ return `vdx: failed to parse mise.toml: ${res.detail ?? 'unknown error'}\n`;
50
+ case 'no-task':
51
+ return (
52
+ `vdx: no \`[tasks.${verb}]\` in mise.toml\n` +
53
+ `hint: run \`vdx init . --force\` to re-detect, or add the task manually.\n`
54
+ );
55
+ }
56
+ }