claude-toolkit 0.1.18 → 0.1.22
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/CHANGELOG.md +16 -0
- package/README.md +60 -26
- package/bin/cli.ts +57 -12
- package/docs/best-practices/testing/storybook-interaction.md +21 -11
- package/docs/stacks/storybook-patterns.md +22 -5
- package/package.json +1 -1
- package/src/detect.ts +140 -0
- package/src/index.ts +2 -0
- package/src/types.ts +4 -1
- package/stacks/storybook/skills/ct-storybook-patterns/SKILL.md +22 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.22 (2026-04-11)
|
|
4
|
+
|
|
5
|
+
- feat: add stack auto-detection with drift reporting
|
|
6
|
+
|
|
7
|
+
## 0.1.21 (2026-04-11)
|
|
8
|
+
|
|
9
|
+
- docs: improve README intro and standardize formatting
|
|
10
|
+
|
|
11
|
+
## 0.1.20 (2026-04-06)
|
|
12
|
+
|
|
13
|
+
- docs: update storybook docs with Vitest 4 addon-vitest config
|
|
14
|
+
|
|
15
|
+
## 0.1.19 (2026-04-06)
|
|
16
|
+
|
|
17
|
+
- fix: update storybook addon-vitest config for Vitest 4 compatibility
|
|
18
|
+
|
|
3
19
|
## 0.1.18 (2026-04-05)
|
|
4
20
|
|
|
5
21
|
- docs: update cross-references for vite, playwright, and storybook stacks
|
package/README.md
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
# claude-toolkit
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**claude-toolkit is a toolbox that Claude Code picks up and uses on its own — no prompting required, saving tokens and ensuring consistent behavior across your team.**
|
|
4
|
+
|
|
5
|
+
The toolkit auto-detects your tech stacks (SolidJS, Vite, Playwright, Cloudflare, etc.) and generates a `.claude/` directory that Claude Code automatically loads. Instead of every team member re-explaining project conventions, tooling, and patterns each session (burning tokens every time), Claude already knows — the knowledge is baked into the generated config. As your project evolves, the toolkit detects stack drift and suggests config updates to stay in sync.
|
|
6
|
+
|
|
7
|
+
This means any developer on your team gets the same Claude behavior for a given project, regardless of how they prompt. The toolkit is project-specific but team-consistent: Claude follows the same debugging methodology, the same testing patterns, and the same code quality standards for everyone.
|
|
8
|
+
|
|
9
|
+
Whether you're starting a new project from scratch or consolidating an existing one, the toolkit gives Claude immediate context about your stack and conventions — so it produces code that fits from day one, or aligns with what's already in place.
|
|
4
10
|
|
|
5
11
|
## Quick Start
|
|
6
12
|
|
|
@@ -19,45 +25,73 @@ bunx claude-toolkit init
|
|
|
19
25
|
3. Claude Code picks up the generated config automatically
|
|
20
26
|
|
|
21
27
|
```ts
|
|
22
|
-
import { defineConfig } from
|
|
28
|
+
import { defineConfig } from "claude-toolkit";
|
|
23
29
|
|
|
24
30
|
export default defineConfig({
|
|
25
|
-
stacks: [
|
|
26
|
-
packageManager:
|
|
31
|
+
stacks: ["solidjs", "rust-wasm", "cloudflare", "protobuf"],
|
|
32
|
+
packageManager: "bun",
|
|
27
33
|
hooks: {
|
|
28
|
-
formatter:
|
|
29
|
-
testRunner:
|
|
30
|
-
typeCheck:
|
|
31
|
-
extraChecks: [
|
|
34
|
+
formatter: "bun run prettier --write",
|
|
35
|
+
testRunner: "bun run vitest run",
|
|
36
|
+
typeCheck: "bun run tsc --noEmit",
|
|
37
|
+
extraChecks: ["cargo check --target wasm32-unknown-unknown"],
|
|
32
38
|
},
|
|
33
39
|
git: {
|
|
34
|
-
branchPrefix:
|
|
35
|
-
protectedBranches: [
|
|
40
|
+
branchPrefix: "feat",
|
|
41
|
+
protectedBranches: ["main"],
|
|
36
42
|
},
|
|
37
|
-
})
|
|
43
|
+
});
|
|
38
44
|
```
|
|
39
45
|
|
|
40
46
|
## CLI Commands
|
|
41
47
|
|
|
42
|
-
| Command
|
|
43
|
-
|
|
44
|
-
| `bunx claude-toolkit init` | Scaffold config file and generate `.claude/`
|
|
48
|
+
| Command | Description |
|
|
49
|
+
| -------------------------- | -------------------------------------------------------- |
|
|
50
|
+
| `bunx claude-toolkit init` | Scaffold config file and generate `.claude/` |
|
|
45
51
|
| `bunx claude-toolkit sync` | Regenerate `.claude/` from config (after toolkit update) |
|
|
46
|
-
| `bunx claude-toolkit help` | Show available commands
|
|
52
|
+
| `bunx claude-toolkit help` | Show available commands |
|
|
53
|
+
|
|
54
|
+
## Stack Auto-Detection
|
|
55
|
+
|
|
56
|
+
The toolkit automatically detects which stacks your project uses by scanning `package.json` dependencies, config files, and project structure.
|
|
57
|
+
|
|
58
|
+
**On `init`** (new project), detected stacks are pre-filled in the generated config:
|
|
59
|
+
|
|
60
|
+
```text
|
|
61
|
+
Detected stacks:
|
|
62
|
+
solidjs — found solid-js in dependencies
|
|
63
|
+
vite — found vite.config.ts
|
|
64
|
+
cloudflare — found wrangler.toml
|
|
65
|
+
|
|
66
|
+
Created claude-toolkit.config.ts
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
**On `sync`** (existing config), the toolkit compares your configured stacks against what it detects and reports any drift:
|
|
70
|
+
|
|
71
|
+
```text
|
|
72
|
+
Stack drift detected:
|
|
73
|
+
+ playwright — found @playwright/test in dependencies (not in config)
|
|
74
|
+
- rust-wasm — in config but not detected in project
|
|
75
|
+
|
|
76
|
+
Suggested update in claude-toolkit.config.ts:
|
|
77
|
+
stacks: ["solidjs", "vite", "cloudflare", "playwright"]
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
This keeps your config aligned as your project evolves — stacks you add or remove are surfaced automatically. The suggestion is informational; your config is not modified unless you update it yourself.
|
|
47
81
|
|
|
48
82
|
## Available Stacks
|
|
49
83
|
|
|
50
|
-
| Stack
|
|
51
|
-
|
|
52
|
-
| `solidjs`
|
|
53
|
-
| `vite`
|
|
54
|
-
| `vanilla-extract` | Type-safe CSS, sprinkles, recipes, themes
|
|
55
|
-
| `rust-wasm`
|
|
56
|
-
| `protobuf`
|
|
57
|
-
| `cloudflare`
|
|
58
|
-
| `i18n-typesafe`
|
|
59
|
-
| `playwright`
|
|
60
|
-
| `storybook`
|
|
84
|
+
| Stack | Skills Added |
|
|
85
|
+
| ----------------- | ------------------------------------------------------------------ |
|
|
86
|
+
| `solidjs` | SolidJS reactivity, signals, components |
|
|
87
|
+
| `vite` | Vite build config, plugins, Vitest testing, coverage, browser mode |
|
|
88
|
+
| `vanilla-extract` | Type-safe CSS, sprinkles, recipes, themes |
|
|
89
|
+
| `rust-wasm` | Rust WASM for Cloudflare Workers |
|
|
90
|
+
| `protobuf` | Protocol Buffers, code generation, contracts |
|
|
91
|
+
| `cloudflare` | D1 database, KV cache, Wrangler |
|
|
92
|
+
| `i18n-typesafe` | typesafe-i18n internationalization |
|
|
93
|
+
| `playwright` | Playwright E2E testing, Page Objects, fixtures, CI/CD |
|
|
94
|
+
| `storybook` | Storybook interaction testing, CSF 3, visual regression |
|
|
61
95
|
|
|
62
96
|
## Core Features (always included)
|
|
63
97
|
|
package/bin/cli.ts
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
import { existsSync } from "node:fs";
|
|
12
12
|
import { readFile, writeFile } from "node:fs/promises";
|
|
13
13
|
import { join, resolve } from "node:path";
|
|
14
|
+
import { detectStacks } from "../src/detect.js";
|
|
14
15
|
import { generate } from "../src/generator.js";
|
|
15
16
|
import type { ClaudeToolkitConfig } from "../src/types.js";
|
|
16
17
|
|
|
@@ -19,9 +20,7 @@ const CONFIG_FILENAME = "claude-toolkit.config.ts";
|
|
|
19
20
|
async function loadConfig(projectDir: string): Promise<ClaudeToolkitConfig> {
|
|
20
21
|
const configPath = join(projectDir, CONFIG_FILENAME);
|
|
21
22
|
if (!existsSync(configPath)) {
|
|
22
|
-
throw new Error(
|
|
23
|
-
`Config not found: ${configPath}\nRun "claude-toolkit init" first.`,
|
|
24
|
-
);
|
|
23
|
+
throw new Error(`Config not found: ${configPath}\nRun "claude-toolkit init" first.`);
|
|
25
24
|
}
|
|
26
25
|
const module = await import(configPath);
|
|
27
26
|
return module.default as ClaudeToolkitConfig;
|
|
@@ -36,23 +35,37 @@ async function init(projectDir: string): Promise<void> {
|
|
|
36
35
|
return sync(projectDir);
|
|
37
36
|
}
|
|
38
37
|
|
|
39
|
-
//
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
"
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
38
|
+
// Detect stacks
|
|
39
|
+
const detected = detectStacks(projectDir);
|
|
40
|
+
if (detected.length > 0) {
|
|
41
|
+
console.log("Detected stacks:");
|
|
42
|
+
const maxLen = Math.max(...detected.map((d) => d.name.length));
|
|
43
|
+
for (const d of detected) {
|
|
44
|
+
console.log(` ${d.name.padEnd(maxLen)} — ${d.reason}`);
|
|
45
|
+
}
|
|
46
|
+
} else {
|
|
47
|
+
console.log(`No stacks detected. You can add them manually in ${CONFIG_FILENAME}`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Build stacks literal for config injection
|
|
51
|
+
const stacksLiteral =
|
|
52
|
+
detected.length > 0
|
|
53
|
+
? `stacks: [${detected.map((d) => `"${d.name}"`).join(", ")}]`
|
|
54
|
+
: "stacks: []";
|
|
55
|
+
|
|
56
|
+
// Copy starter config with detected stacks injected
|
|
57
|
+
const templatePath = join(import.meta.dirname, "..", "templates", "claude-toolkit.config.ts");
|
|
46
58
|
if (existsSync(templatePath)) {
|
|
47
59
|
const template = await readFile(templatePath, "utf-8");
|
|
48
|
-
|
|
60
|
+
const configContent = template.replace("stacks: []", stacksLiteral);
|
|
61
|
+
await writeFile(configPath, configContent, "utf-8");
|
|
49
62
|
console.log(`Created ${CONFIG_FILENAME}`);
|
|
50
63
|
} else {
|
|
51
64
|
// Inline fallback
|
|
52
65
|
const defaultConfig = `import { defineConfig } from 'claude-toolkit'
|
|
53
66
|
|
|
54
67
|
export default defineConfig({
|
|
55
|
-
|
|
68
|
+
${stacksLiteral},
|
|
56
69
|
packageManager: 'bun',
|
|
57
70
|
hooks: {
|
|
58
71
|
formatter: 'bun run prettier --write',
|
|
@@ -75,6 +88,38 @@ export default defineConfig({
|
|
|
75
88
|
|
|
76
89
|
async function sync(projectDir: string): Promise<void> {
|
|
77
90
|
const config = await loadConfig(projectDir);
|
|
91
|
+
|
|
92
|
+
// Compare detected stacks against config
|
|
93
|
+
const detected = detectStacks(projectDir);
|
|
94
|
+
const configuredNames = new Set(config.stacks);
|
|
95
|
+
const detectedNames = new Set(detected.map((d) => d.name));
|
|
96
|
+
|
|
97
|
+
const missing = detected.filter((d) => !configuredNames.has(d.name));
|
|
98
|
+
const stale = config.stacks.filter((s) => !detectedNames.has(s));
|
|
99
|
+
|
|
100
|
+
if (missing.length > 0 || stale.length > 0) {
|
|
101
|
+
console.log("\nStack drift detected:");
|
|
102
|
+
if (missing.length > 0) {
|
|
103
|
+
const maxLen = Math.max(...missing.map((d) => d.name.length));
|
|
104
|
+
for (const d of missing) {
|
|
105
|
+
console.log(` + ${d.name.padEnd(maxLen)} — ${d.reason} (not in config)`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (stale.length > 0) {
|
|
109
|
+
for (const s of stale) {
|
|
110
|
+
console.log(` - ${s} — in config but not detected in project`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
const suggested = [
|
|
114
|
+
...new Set([
|
|
115
|
+
...config.stacks.filter((s) => !stale.includes(s)),
|
|
116
|
+
...missing.map((d) => d.name),
|
|
117
|
+
]),
|
|
118
|
+
];
|
|
119
|
+
console.log(`\nSuggested update in ${CONFIG_FILENAME}:`);
|
|
120
|
+
console.log(` stacks: [${suggested.map((s) => `"${s}"`).join(", ")}]\n`);
|
|
121
|
+
}
|
|
122
|
+
|
|
78
123
|
await generate(projectDir, config);
|
|
79
124
|
console.log("Sync complete.");
|
|
80
125
|
}
|
|
@@ -149,23 +149,33 @@ The **Vitest addon** (`@storybook/addon-vitest`) replaces the old `@storybook/te
|
|
|
149
149
|
|
|
150
150
|
### Configuration (Vitest 4.x)
|
|
151
151
|
|
|
152
|
-
|
|
152
|
+
Required dependencies: `@storybook/addon-vitest`, `@vitest/browser`, `@vitest/browser-playwright`, `@vitest/coverage-v8`.
|
|
153
|
+
|
|
154
|
+
Add as an inline project in your main Vite config using `test.projects`:
|
|
153
155
|
|
|
154
156
|
```typescript
|
|
155
|
-
//
|
|
156
|
-
import { defineConfig } from "vitest/config";
|
|
157
|
+
// vite.config.ts
|
|
157
158
|
import { storybookTest } from "@storybook/addon-vitest/vitest-plugin";
|
|
159
|
+
import { playwright } from "@vitest/browser-playwright";
|
|
158
160
|
|
|
159
161
|
export default defineConfig({
|
|
160
|
-
plugins: [
|
|
162
|
+
plugins: [/* ...app plugins */],
|
|
161
163
|
test: {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
164
|
+
projects: [
|
|
165
|
+
{
|
|
166
|
+
extends: true,
|
|
167
|
+
test: { name: "unit", environment: "jsdom", setupFiles: ["./tests/setup.ts"] },
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
extends: true,
|
|
171
|
+
plugins: [storybookTest({ configDir: ".storybook" })],
|
|
172
|
+
test: {
|
|
173
|
+
name: "storybook",
|
|
174
|
+
browser: { enabled: true, headless: true, provider: playwright(), instances: [{ browser: "chromium" }] },
|
|
175
|
+
setupFiles: [".storybook/vitest.setup.ts"],
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
],
|
|
169
179
|
},
|
|
170
180
|
});
|
|
171
181
|
```
|
|
@@ -81,16 +81,33 @@ export const SubmitForm: Story = {
|
|
|
81
81
|
|
|
82
82
|
Use `@storybook/addon-vitest` (replaces old test-runner). No running Storybook instance needed.
|
|
83
83
|
|
|
84
|
+
Required dependencies: `@storybook/addon-vitest`, `@vitest/browser`, `@vitest/browser-playwright`, `@vitest/coverage-v8`.
|
|
85
|
+
|
|
86
|
+
Add as an inline project in your main Vite config using `test.projects`:
|
|
87
|
+
|
|
84
88
|
```typescript
|
|
85
|
-
//
|
|
89
|
+
// vite.config.ts
|
|
86
90
|
import { storybookTest } from "@storybook/addon-vitest/vitest-plugin";
|
|
91
|
+
import { playwright } from "@vitest/browser-playwright";
|
|
87
92
|
|
|
88
93
|
export default defineConfig({
|
|
89
|
-
plugins: [
|
|
94
|
+
plugins: [/* ...app plugins */],
|
|
90
95
|
test: {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
96
|
+
projects: [
|
|
97
|
+
{
|
|
98
|
+
extends: true,
|
|
99
|
+
test: { name: "unit", environment: "jsdom", setupFiles: ["./tests/setup.ts"] },
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
extends: true,
|
|
103
|
+
plugins: [storybookTest({ configDir: ".storybook" })],
|
|
104
|
+
test: {
|
|
105
|
+
name: "storybook",
|
|
106
|
+
browser: { enabled: true, headless: true, provider: playwright(), instances: [{ browser: "chromium" }] },
|
|
107
|
+
setupFiles: [".storybook/vitest.setup.ts"],
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
],
|
|
94
111
|
},
|
|
95
112
|
});
|
|
96
113
|
```
|
package/package.json
CHANGED
package/src/detect.ts
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import type { StackName } from "./types.js";
|
|
4
|
+
|
|
5
|
+
/** Result of detecting a stack in a project */
|
|
6
|
+
export interface DetectedStack {
|
|
7
|
+
name: StackName;
|
|
8
|
+
reason: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface PackageJson {
|
|
12
|
+
dependencies?: Record<string, string>;
|
|
13
|
+
devDependencies?: Record<string, string>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface StackDetector {
|
|
17
|
+
name: StackName;
|
|
18
|
+
detect: (projectDir: string, pkg: PackageJson | null) => DetectedStack | null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function loadPackageJson(projectDir: string): PackageJson | null {
|
|
22
|
+
const pkgPath = join(projectDir, "package.json");
|
|
23
|
+
if (!existsSync(pkgPath)) return null;
|
|
24
|
+
try {
|
|
25
|
+
return JSON.parse(readFileSync(pkgPath, "utf-8")) as PackageJson;
|
|
26
|
+
} catch {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function hasDep(pkg: PackageJson | null, name: string): boolean {
|
|
32
|
+
if (!pkg) return false;
|
|
33
|
+
return name in (pkg.dependencies ?? {}) || name in (pkg.devDependencies ?? {});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function fileExists(projectDir: string, ...segments: string[]): boolean {
|
|
37
|
+
return existsSync(join(projectDir, ...segments));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function rootConfigExists(projectDir: string, prefix: string): string | null {
|
|
41
|
+
const glob = new Bun.Glob(`${prefix}.*`);
|
|
42
|
+
for (const match of glob.scanSync({ cwd: projectDir, onlyFiles: true })) {
|
|
43
|
+
return match;
|
|
44
|
+
}
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const DETECTORS: StackDetector[] = [
|
|
49
|
+
{
|
|
50
|
+
name: "solidjs",
|
|
51
|
+
detect: (_dir, pkg) =>
|
|
52
|
+
hasDep(pkg, "solid-js")
|
|
53
|
+
? { name: "solidjs", reason: "found solid-js in dependencies" }
|
|
54
|
+
: null,
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
name: "vite",
|
|
58
|
+
detect: (dir, pkg) => {
|
|
59
|
+
if (hasDep(pkg, "vite")) return { name: "vite", reason: "found vite in dependencies" };
|
|
60
|
+
const viteConfig = rootConfigExists(dir, "vite.config");
|
|
61
|
+
if (viteConfig) return { name: "vite", reason: `found ${viteConfig}` };
|
|
62
|
+
const vitestConfig = rootConfigExists(dir, "vitest.config");
|
|
63
|
+
if (vitestConfig) return { name: "vite", reason: `found ${vitestConfig}` };
|
|
64
|
+
return null;
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: "vanilla-extract",
|
|
69
|
+
detect: (_dir, pkg) =>
|
|
70
|
+
hasDep(pkg, "@vanilla-extract/css")
|
|
71
|
+
? { name: "vanilla-extract", reason: "found @vanilla-extract/css in dependencies" }
|
|
72
|
+
: null,
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
name: "rust-wasm",
|
|
76
|
+
detect: (dir) =>
|
|
77
|
+
fileExists(dir, "Cargo.toml") ? { name: "rust-wasm", reason: "found Cargo.toml" } : null,
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
name: "protobuf",
|
|
81
|
+
detect: (dir) => {
|
|
82
|
+
if (fileExists(dir, "buf.yaml")) return { name: "protobuf", reason: "found buf.yaml" };
|
|
83
|
+
if (fileExists(dir, "buf.gen.yaml"))
|
|
84
|
+
return { name: "protobuf", reason: "found buf.gen.yaml" };
|
|
85
|
+
const glob = new Bun.Glob("**/*.proto");
|
|
86
|
+
for (const _match of glob.scanSync({ cwd: dir, onlyFiles: true })) {
|
|
87
|
+
return { name: "protobuf", reason: "found .proto files" };
|
|
88
|
+
}
|
|
89
|
+
return null;
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
name: "cloudflare",
|
|
94
|
+
detect: (dir) => {
|
|
95
|
+
if (fileExists(dir, "wrangler.toml"))
|
|
96
|
+
return { name: "cloudflare", reason: "found wrangler.toml" };
|
|
97
|
+
if (fileExists(dir, "wrangler.jsonc"))
|
|
98
|
+
return { name: "cloudflare", reason: "found wrangler.jsonc" };
|
|
99
|
+
return null;
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
name: "i18n-typesafe",
|
|
104
|
+
detect: (_dir, pkg) =>
|
|
105
|
+
hasDep(pkg, "typesafe-i18n")
|
|
106
|
+
? { name: "i18n-typesafe", reason: "found typesafe-i18n in dependencies" }
|
|
107
|
+
: null,
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
name: "playwright",
|
|
111
|
+
detect: (dir, pkg) => {
|
|
112
|
+
if (hasDep(pkg, "@playwright/test"))
|
|
113
|
+
return { name: "playwright", reason: "found @playwright/test in dependencies" };
|
|
114
|
+
const config = rootConfigExists(dir, "playwright.config");
|
|
115
|
+
if (config) return { name: "playwright", reason: `found ${config}` };
|
|
116
|
+
return null;
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
name: "storybook",
|
|
121
|
+
detect: (dir, pkg) => {
|
|
122
|
+
if (hasDep(pkg, "storybook"))
|
|
123
|
+
return { name: "storybook", reason: "found storybook in dependencies" };
|
|
124
|
+
if (fileExists(dir, ".storybook"))
|
|
125
|
+
return { name: "storybook", reason: "found .storybook/ directory" };
|
|
126
|
+
return null;
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
];
|
|
130
|
+
|
|
131
|
+
/** Scan a project directory and detect which stacks are present */
|
|
132
|
+
export function detectStacks(projectDir: string): DetectedStack[] {
|
|
133
|
+
const pkg = loadPackageJson(projectDir);
|
|
134
|
+
const detected: DetectedStack[] = [];
|
|
135
|
+
for (const detector of DETECTORS) {
|
|
136
|
+
const result = detector.detect(projectDir, pkg);
|
|
137
|
+
if (result) detected.push(result);
|
|
138
|
+
}
|
|
139
|
+
return detected;
|
|
140
|
+
}
|
package/src/index.ts
CHANGED
package/src/types.ts
CHANGED
|
@@ -6,6 +6,9 @@ export type StackName =
|
|
|
6
6
|
| "protobuf"
|
|
7
7
|
| "cloudflare"
|
|
8
8
|
| "i18n-typesafe"
|
|
9
|
+
| "vite"
|
|
10
|
+
| "playwright"
|
|
11
|
+
| "storybook"
|
|
9
12
|
| (string & {});
|
|
10
13
|
|
|
11
14
|
/** Hook configuration for post-tool-use automation */
|
|
@@ -24,7 +27,7 @@ export interface HookConfig {
|
|
|
24
27
|
|
|
25
28
|
/** Git workflow configuration */
|
|
26
29
|
export interface GitConfig {
|
|
27
|
-
/** Branch name prefix (e.g., "
|
|
30
|
+
/** Branch name prefix (e.g., "feat" → "feat/feature-name") */
|
|
28
31
|
branchPrefix?: string;
|
|
29
32
|
/** Branches protected from direct edits */
|
|
30
33
|
protectedBranches?: string[];
|
|
@@ -77,16 +77,33 @@ export const SubmitForm: Story = {
|
|
|
77
77
|
|
|
78
78
|
Use `@storybook/addon-vitest` (replaces old test-runner). No running Storybook instance needed.
|
|
79
79
|
|
|
80
|
+
Required dependencies: `@storybook/addon-vitest`, `@vitest/browser`, `@vitest/browser-playwright`, `@vitest/coverage-v8`.
|
|
81
|
+
|
|
82
|
+
Add as an inline project in your main Vite config using `test.projects`:
|
|
83
|
+
|
|
80
84
|
```typescript
|
|
81
|
-
//
|
|
85
|
+
// vite.config.ts
|
|
82
86
|
import { storybookTest } from "@storybook/addon-vitest/vitest-plugin";
|
|
87
|
+
import { playwright } from "@vitest/browser-playwright";
|
|
83
88
|
|
|
84
89
|
export default defineConfig({
|
|
85
|
-
plugins: [
|
|
90
|
+
plugins: [/* ...app plugins */],
|
|
86
91
|
test: {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
92
|
+
projects: [
|
|
93
|
+
{
|
|
94
|
+
extends: true,
|
|
95
|
+
test: { name: "unit", environment: "jsdom", setupFiles: ["./tests/setup.ts"] },
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
extends: true,
|
|
99
|
+
plugins: [storybookTest({ configDir: ".storybook" })],
|
|
100
|
+
test: {
|
|
101
|
+
name: "storybook",
|
|
102
|
+
browser: { enabled: true, headless: true, provider: playwright(), instances: [{ browser: "chromium" }] },
|
|
103
|
+
setupFiles: [".storybook/vitest.setup.ts"],
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
],
|
|
90
107
|
},
|
|
91
108
|
});
|
|
92
109
|
```
|