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 +4 -3
- package/dist/index.js +4 -2
- package/dist/migrate.js +22 -7
- package/package.json +1 -1
- package/stacks/_shared/commands/validate.md +99 -14
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.
|
|
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-
|
|
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
|
|
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-
|
|
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
|
-
|
|
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
|
|
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
|
|
419
|
-
`Use --force-hooks to
|
|
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
|
|
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
|
|
467
|
-
`Re-run with --force-
|
|
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,28 +1,113 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: validate
|
|
3
|
-
description: Run the full quality gate (typecheck → lint → test → build) using
|
|
4
|
-
version: 1.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
|
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
|
|
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.
|