start-vibing-stacks 2.24.0 → 2.25.2

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
@@ -6,7 +6,7 @@ Multi-stack AI workflow for **Claude Code** & **Cursor**. One command installs a
6
6
  npx start-vibing-stacks
7
7
  ```
8
8
 
9
- > Latest: **v2.24.0** — `migrate` is now smart and complete. Versioned hooks (`// @sv-version`), versioned commands, idempotent `settings.json` patcher, runtime dirs auto-creation, `.gitignore` update all reachable via `npx start-vibing-stacks migrate --apply`. New `--force-hooks` flag overwrites legacy hooks (no `@sv-version`) with timestamped `.bak` backup. v2.23.0 → multi-instance coordination layer.
9
+ > Latest: **v2.25.0** — `--force-hooks` (alias `--force-legacy`) now also covers legacy commands (`commands/*.md` without `version:` frontmatter), not just hooks. Single command rebuilds installs older than v2.23.0. v2.24.0 smart migrate (versioned hooks/commands, idempotent `settings.json`, runtime dirs, `.gitignore`). v2.23.0 → multi-instance coordination layer.
10
10
 
11
11
  ---
12
12
 
@@ -133,7 +133,8 @@ Single-host coordination only. It does **not** replace `git` for cross-machine c
133
133
  npx start-vibing-stacks # setup or resume current project
134
134
  npx start-vibing-stacks migrate # dry run: skills + agents + hooks + commands + settings.json + runtime dirs
135
135
  npx start-vibing-stacks migrate --apply # apply (idempotent; preserves customizations)
136
- npx start-vibing-stacks migrate --apply --force-hooks # also overwrite legacy hooks (no @sv-version) — creates .bak
136
+ npx start-vibing-stacks migrate --apply --force-legacy # also overwrite legacy hooks AND commands (no version) — creates .bak
137
+ npx start-vibing-stacks migrate --apply --force-hooks # alias for --force-legacy (back-compat)
137
138
 
138
139
  # flags: --force --no-claude --no-mcp --no-install --help --version
139
140
  ```
@@ -145,7 +146,7 @@ npx start-vibing-stacks migrate --apply --force-hooks # also overwrite legacy h
145
146
  - **Runtime artefacts** — `.claude/state/{sessions,inbox,file-touches}/_archive` auto-created; `_state.README.md` staged.
146
147
  - **`.gitignore`** — `.claude/state/` appended if not already covered.
147
148
 
148
- Legacy hooks without `@sv-version` (anything older than v2.24.0) are reported as `needs-update-legacy` and skipped by default. Pass `--force-hooks` to overwrite them — each gets a timestamped `.bak`.
149
+ Legacy files without versioning — hooks without `// @sv-version` AND commands without `version:` frontmatter (anything installed before the versioning landed) are reported as `needs-update-legacy` and skipped by default. Pass `--force-legacy` (or its alias `--force-hooks`) to overwrite them — each gets a timestamped `.bak`. Skills and agents are intentionally **not** covered because users customize them; they stay in `modified-no-version` (advisory only).
149
150
 
150
151
  Global install: `npm i -g start-vibing-stacks` → `svs` (alias).
151
152
 
package/dist/index.js CHANGED
@@ -33,7 +33,7 @@ const FLAGS = {
33
33
  help: args.includes('--help') || args.includes('-h'),
34
34
  version: args.includes('--version') || args.includes('-v'),
35
35
  apply: args.includes('--apply'),
36
- forceHooks: args.includes('--force-hooks'),
36
+ forceHooks: args.includes('--force-hooks') || args.includes('--force-legacy'),
37
37
  };
38
38
  if (FLAGS.version) {
39
39
  console.log(PKG_VERSION);
@@ -53,7 +53,9 @@ if (FLAGS.help) {
53
53
  ${chalk.bold('Options:')}
54
54
  --force Overwrite existing configuration (default command)
55
55
  --apply Apply updates (migrate command)
56
- --force-hooks Overwrite legacy hooks without @sv-version (migrate; creates .bak)
56
+ --force-legacy Overwrite legacy hooks (no @sv-version) AND legacy commands
57
+ (no version: frontmatter) — each gets a timestamped .bak
58
+ --force-hooks Alias for --force-legacy (kept for back-compat)
57
59
  --no-claude Skip Claude Code installation
58
60
  --no-mcp Skip MCP server selection
59
61
  --no-install Skip dependency installation
package/dist/migrate.js CHANGED
@@ -187,7 +187,20 @@ export function planMigration(projectDir, opts) {
187
187
  if (!bundledVersion)
188
188
  continue;
189
189
  const installedVersion = parseFrontmatterVersion(target);
190
- const status = !existsSync(target) ? 'missing' : statusFromSemver(bundledVersion, installedVersion);
190
+ let status;
191
+ if (!existsSync(target)) {
192
+ status = 'missing';
193
+ }
194
+ else if (installedVersion === null) {
195
+ // Commands are canonical infra files (slash-command routes). When the
196
+ // installed copy lacks `version:` frontmatter it predates versioned
197
+ // commands — treat as legacy so `--force-hooks` can heal it (same
198
+ // .bak machinery as legacy hooks). Skills/agents stay manual-review.
199
+ status = 'needs-update-legacy';
200
+ }
201
+ else {
202
+ status = statusFromSemver(bundledVersion, installedVersion);
203
+ }
191
204
  items.push({ kind: 'command', name, source, target, bundledVersion, installedVersion, status });
192
205
  }
193
206
  }
@@ -363,7 +376,7 @@ export async function runMigrate(projectDir, opts) {
363
376
  };
364
377
  summary('MISSING', grouped.missing);
365
378
  summary('OUTDATED', grouped.outdated);
366
- summary('NEEDS UPDATE — legacy hooks (no @sv-version tag)', grouped['needs-update-legacy']);
379
+ summary('NEEDS UPDATE — legacy (hooks without @sv-version, commands without version: frontmatter)', grouped['needs-update-legacy']);
367
380
  summary('AHEAD (installed newer than bundled — kept)', grouped.ahead);
368
381
  summary('UNVERSIONED (manual review)', grouped['modified-no-version']);
369
382
  summary('CURRENT', grouped.current);
@@ -415,8 +428,9 @@ export async function runMigrate(projectDir, opts) {
415
428
  todo.push('runtime artefacts');
416
429
  ui.info(`Run with --apply to update: ${todo.join(' + ')}.`);
417
430
  if (grouped['needs-update-legacy'].length > 0 && !opts.forceHooks) {
418
- ui.info(`${grouped['needs-update-legacy'].length} legacy hook(s) without @sv-version detected. ` +
419
- `Use --force-hooks to update them with a .bak backup.`);
431
+ ui.info(`${grouped['needs-update-legacy'].length} legacy file(s) detected (hooks without @sv-version, ` +
432
+ `commands without \`version:\` frontmatter). Use --force-legacy (alias --force-hooks) to ` +
433
+ `update them with a .bak backup.`);
420
434
  }
421
435
  return;
422
436
  }
@@ -453,7 +467,7 @@ export async function runMigrate(projectDir, opts) {
453
467
  ui.success('gitignore: appended .claude/state/');
454
468
  console.log('');
455
469
  ui.success(`Migration complete: ${updated} file(s), ${settingsApply.added.length} settings entry(ies)` +
456
- (legacyUpdated > 0 ? `, ${legacyUpdated} legacy hook(s) overwritten with backup` : ''));
470
+ (legacyUpdated > 0 ? `, ${legacyUpdated} legacy file(s) overwritten with backup` : ''));
457
471
  if (grouped['modified-no-version'].length > 0) {
458
472
  console.log('');
459
473
  ui.warn(`${grouped['modified-no-version'].length} unversioned local skill/agent/command(s) skipped — review manually.`);
@@ -463,8 +477,9 @@ export async function runMigrate(projectDir, opts) {
463
477
  }
464
478
  if (grouped['needs-update-legacy'].length > 0 && !opts.forceHooks) {
465
479
  console.log('');
466
- ui.warn(`${grouped['needs-update-legacy'].length} legacy hook(s) without @sv-version were skipped. ` +
467
- `Re-run with --force-hooks to update them (each gets a .bak backup).`);
480
+ ui.warn(`${grouped['needs-update-legacy'].length} legacy file(s) skipped (hooks without @sv-version, ` +
481
+ `commands without \`version:\` frontmatter). Re-run with --force-legacy ` +
482
+ `(alias --force-hooks) to update them (each gets a .bak backup).`);
468
483
  for (const it of grouped['needs-update-legacy'].slice(0, 10)) {
469
484
  console.log(` ${relative(projectDir, it.target)}`);
470
485
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "start-vibing-stacks",
3
- "version": "2.24.0",
3
+ "version": "2.25.2",
4
4
  "description": "AI-powered multi-stack dev workflow for Claude Code. Supports PHP, Node.js, Python and more.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,28 +1,113 @@
1
1
  ---
2
2
  name: validate
3
- description: Run the full quality gate (typecheck → lint → test → build) using the stack's commands from active-project.json.
4
- version: 1.0.0
3
+ description: Run the full quality gate (typecheck → lint → test → build) using THIS project's `active-project.json#qualityGates`. Per-stack reference is below.
4
+ version: 1.1.0
5
5
  ---
6
6
 
7
7
  # /validate — Run Full Validation
8
8
 
9
- Read the quality gates from `.claude/config/active-project.json#qualityGates` and run them in order. Stop at the first failure.
9
+ ## How it works (canonical always use this)
10
+
11
+ 1. **Read the gates from `.claude/config/active-project.json#qualityGates`.** This is the **only** source of truth — it was populated at install time from the active stack and reflects this project's actual tooling.
12
+ 2. Run each gate in `order` ascending; stop at the first failure that has `required: true`.
13
+ 3. If a gate has `appliesTo: ["nextjs", ...]`, only run it when `framework` in `active-project.json` matches.
10
14
 
11
15
  ```bash
12
- jq -r '.qualityGates' .claude/config/active-project.json
16
+ jq -r '.qualityGates | sort_by(.order) | .[] | "\(.order). \(.name) (required=\(.required)): \(.command)"' .claude/config/active-project.json
17
+ ```
18
+
19
+ `commit-manager` refuses to commit while `/validate` is failing.
20
+
21
+ ---
22
+
23
+ ## Per-stack reference (suggested — only used if `active-project.json` is missing or corrupted)
24
+
25
+ This block exists so the model has a stack-aware fallback. **Do not pick from here when `active-project.json#qualityGates` is present and valid** — that JSON always wins.
26
+
27
+ ```json
28
+ {
29
+ "python": {
30
+ "_default": {
31
+ "typecheck": "mypy .",
32
+ "lint": "ruff check .",
33
+ "format": "ruff format .",
34
+ "test": "pytest --tb=short",
35
+ "serve": "uvicorn app.main:app --reload"
36
+ },
37
+ "frameworks": {
38
+ "fastapi": { "test": "pytest --tb=short" },
39
+ "django": { "test": "python manage.py test", "migrate": "python manage.py migrate" },
40
+ "flask": { "test": "pytest --tb=short" },
41
+ "scripts": { "test": "pytest --tb=short" }
42
+ },
43
+ "_runOrder": ["typecheck", "lint", "test"]
44
+ },
45
+
46
+ "nodejs": {
47
+ "_default": {
48
+ "typecheck": "bun run typecheck",
49
+ "lint": "bun run lint",
50
+ "format": "bun run format",
51
+ "test": "bun run test",
52
+ "build": "bun run build",
53
+ "serve": "bun run dev"
54
+ },
55
+ "frameworks": {
56
+ "nextjs": {
57
+ "_extra": [
58
+ { "name": "RouteSlugs", "command": "node scripts/check-route-slugs.mjs", "order": 4, "required": true, "why": "next build does not catch dynamic-route slug mismatch" },
59
+ { "name": "BuildScripts", "command": "node scripts/check-build-scripts.mjs", "order": 5, "required": true, "why": "Vercel/Docker strip devDeps; scripts.build calling tsx/ts-node/vitest crash exit 127" }
60
+ ]
61
+ },
62
+ "nuxt": { "build": "bun run build" },
63
+ "astro": { "build": "bun run build" },
64
+ "express": { "test": "bun run test" },
65
+ "fastify": { "test": "bun run test" },
66
+ "vanilla": { "test": "bun run test" }
67
+ },
68
+ "_runOrder": ["typecheck", "lint", "test", "_extra", "build"]
69
+ },
70
+
71
+ "php": {
72
+ "_default": {
73
+ "static": "vendor/bin/phpstan analyse --level=6",
74
+ "test": "vendor/bin/phpunit",
75
+ "format": "vendor/bin/php-cs-fixer fix",
76
+ "serve": "php artisan serve"
77
+ },
78
+ "frameworks": {
79
+ "laravel-octane": {
80
+ "serve": "php artisan octane:start --watch",
81
+ "_extraIfFrontend": ["typecheck", "lint", "viteManifest"]
82
+ },
83
+ "laravel": { "_extraIfFrontend": ["typecheck", "lint", "viteManifest"] }
84
+ },
85
+ "_frontendChecks": {
86
+ "typecheck": "npx tsc --noEmit",
87
+ "lint": "npx eslint resources/js/",
88
+ "viteManifest": "node scripts/check-vite-manifest.mjs"
89
+ },
90
+ "_runOrder": ["static", "test", "typecheck", "lint", "viteManifest"]
91
+ }
92
+ }
13
93
  ```
14
94
 
15
- Execution order (each step blocks the next):
95
+ ### How to read this fallback
96
+
97
+ - `_default` — commands that always apply within the stack.
98
+ - `frameworks.<framework>` — overrides or extras gated by `framework` in `active-project.json`.
99
+ - `_extra` — additional gates with explicit `order` (PHP/Next.js use this for stack-specific static checks like vite-manifest, route-slugs, build-scripts).
100
+ - `_runOrder` — order to chain when no `qualityGates` is available.
16
101
 
17
- | # | Gate | Typical commands |
18
- |---|---|---|
19
- | 1 | **Typecheck** | `npx tsc --noEmit` · `mypy .` · `vendor/bin/phpstan analyse` |
20
- | 2 | **Lint** | `npx eslint .` · `ruff check .` · `vendor/bin/pint --test` |
21
- | 3 | **Unit tests** | `npx vitest run` · `pytest` · `vendor/bin/phpunit` |
22
- | 4 | **E2E** (only if configured) | `npx playwright test` |
23
- | 5 | **Build** | `npm run build` · `composer dump-autoload --optimize` |
102
+ If `.claude/config/active-project.json#qualityGates` is missing, **do not silently fall back** — instead:
103
+
104
+ 1. Print a warning: `WARN: active-project.json#qualityGates missing using fallback for stack=<X>, framework=<Y>`.
105
+ 2. Run the fallback in `_runOrder`.
106
+ 3. Recommend the user re-run `npx start-vibing-stacks` (or `migrate --apply`) to repair the config.
107
+
108
+ ---
24
109
 
25
- Output format (one line per gate):
110
+ ## Output format
26
111
 
27
112
  ```
28
113
  typecheck: PASS (1.4s)
@@ -34,4 +119,4 @@ build: PASS (3.1s)
34
119
  ✅ All gates passed — safe to invoke commit-manager
35
120
  ```
36
121
 
37
- If any gate fails, print the failure output and exit non-zero. `commit-manager` will refuse to commit while validation is failing.
122
+ If any required gate fails, print its full output and exit non-zero. Optional gates (`required: false`) print a warning but do not block.