claude-toolkit 0.1.20 → 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 CHANGED
@@ -1,5 +1,13 @@
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
+
3
11
  ## 0.1.20 (2026-04-06)
4
12
 
5
13
  - docs: update storybook docs with Vitest 4 addon-vitest config
package/README.md CHANGED
@@ -1,6 +1,12 @@
1
1
  # claude-toolkit
2
2
 
3
- Reusable Claude Code configuration toolkit with stack-specific connectors.
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 'claude-toolkit'
28
+ import { defineConfig } from "claude-toolkit";
23
29
 
24
30
  export default defineConfig({
25
- stacks: ['solidjs', 'rust-wasm', 'cloudflare', 'protobuf'],
26
- packageManager: 'bun',
31
+ stacks: ["solidjs", "rust-wasm", "cloudflare", "protobuf"],
32
+ packageManager: "bun",
27
33
  hooks: {
28
- formatter: 'bun run prettier --write',
29
- testRunner: 'bun run vitest run',
30
- typeCheck: 'bun run tsc --noEmit',
31
- extraChecks: ['cargo check --target wasm32-unknown-unknown'],
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: 'wq',
35
- protectedBranches: ['main'],
40
+ branchPrefix: "feat",
41
+ protectedBranches: ["main"],
36
42
  },
37
- })
43
+ });
38
44
  ```
39
45
 
40
46
  ## CLI Commands
41
47
 
42
- | Command | Description |
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 | Skills Added |
51
- |-------|-------------|
52
- | `solidjs` | SolidJS reactivity, signals, components |
53
- | `vite` | Vite build config, plugins, Vitest testing, coverage, browser mode |
54
- | `vanilla-extract` | Type-safe CSS, sprinkles, recipes, themes |
55
- | `rust-wasm` | Rust WASM for Cloudflare Workers |
56
- | `protobuf` | Protocol Buffers, code generation, contracts |
57
- | `cloudflare` | D1 database, KV cache, Wrangler |
58
- | `i18n-typesafe` | typesafe-i18n internationalization |
59
- | `playwright` | Playwright E2E testing, Page Objects, fixtures, CI/CD |
60
- | `storybook` | Storybook interaction testing, CSF 3, visual regression |
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
- // Copy starter config
40
- const templatePath = join(
41
- import.meta.dirname,
42
- "..",
43
- "templates",
44
- "claude-toolkit.config.ts",
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
- await writeFile(configPath, template, "utf-8");
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
- stacks: [],
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-toolkit",
3
- "version": "0.1.20",
3
+ "version": "0.1.22",
4
4
  "description": "Reusable Claude Code configuration toolkit with stack-specific connectors",
5
5
  "type": "module",
6
6
  "bin": {
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
@@ -1,3 +1,5 @@
1
+ export type { DetectedStack } from "./detect.js";
2
+ export { detectStacks } from "./detect.js";
1
3
  export type {
2
4
  ClaudeToolkitConfig,
3
5
  GitConfig,
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., "wq" → "wq/feature-name") */
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[];