claude-toolkit 0.1.9

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.
Files changed (85) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/LICENSE +21 -0
  3. package/README.md +126 -0
  4. package/bin/cli.ts +112 -0
  5. package/core/agents/ct-code-reviewer.md +123 -0
  6. package/core/agents/ct-github-workflow.md +137 -0
  7. package/core/commands/ct/code-quality.md +59 -0
  8. package/core/commands/ct/onboard.md +84 -0
  9. package/core/commands/ct/pr-review.md +104 -0
  10. package/core/commands/ct/pr-summary.md +59 -0
  11. package/core/commands/ct/proto-check.md +74 -0
  12. package/core/commands/ct/ticket.md +71 -0
  13. package/core/hooks/skill-eval.js +381 -0
  14. package/core/hooks/skill-eval.sh +35 -0
  15. package/core/hooks/skill-rules.schema.json +112 -0
  16. package/core/skills/systematic-debugging/SKILL.md +44 -0
  17. package/core/skills/testing-patterns/SKILL.md +52 -0
  18. package/core/skills/typescript-conventions/SKILL.md +57 -0
  19. package/core/skills/verification-before-completion/SKILL.md +42 -0
  20. package/docs/README.md +49 -0
  21. package/docs/agents/code-reviewer.md +76 -0
  22. package/docs/agents/github-workflow.md +98 -0
  23. package/docs/best-practices/solidjs/README.md +43 -0
  24. package/docs/best-practices/solidjs/anti-patterns.md +166 -0
  25. package/docs/best-practices/solidjs/component-patterns.md +131 -0
  26. package/docs/best-practices/solidjs/context-and-global-state.md +131 -0
  27. package/docs/best-practices/solidjs/control-flow.md +124 -0
  28. package/docs/best-practices/solidjs/data-fetching.md +205 -0
  29. package/docs/best-practices/solidjs/effects-and-lifecycle.md +113 -0
  30. package/docs/best-practices/solidjs/performance.md +100 -0
  31. package/docs/best-practices/solidjs/props-patterns.md +100 -0
  32. package/docs/best-practices/solidjs/reactivity-model.md +104 -0
  33. package/docs/best-practices/solidjs/signals-and-state.md +78 -0
  34. package/docs/best-practices/solidjs/stores-and-nested-state.md +111 -0
  35. package/docs/best-practices/solidjs/typescript-integration.md +186 -0
  36. package/docs/best-practices/typescript/README.md +45 -0
  37. package/docs/best-practices/typescript/any-and-unknown.md +73 -0
  38. package/docs/best-practices/typescript/deriving-vs-decoupling.md +83 -0
  39. package/docs/best-practices/typescript/discriminated-unions.md +75 -0
  40. package/docs/best-practices/typescript/enums-alternatives.md +72 -0
  41. package/docs/best-practices/typescript/essential-patterns.md +119 -0
  42. package/docs/best-practices/typescript/generics-patterns.md +105 -0
  43. package/docs/best-practices/typescript/micro-opinions.md +87 -0
  44. package/docs/best-practices/typescript/runtime-validation.md +62 -0
  45. package/docs/best-practices/typescript/satisfies-operator.md +100 -0
  46. package/docs/best-practices/typescript/tsconfig-cheat-sheet.md +129 -0
  47. package/docs/best-practices/typescript/type-organization.md +64 -0
  48. package/docs/best-practices/typescript/type-vs-interface.md +80 -0
  49. package/docs/commands/code-quality.md +42 -0
  50. package/docs/commands/onboard.md +72 -0
  51. package/docs/commands/pr-review.md +102 -0
  52. package/docs/commands/pr-summary.md +50 -0
  53. package/docs/commands/proto-check.md +59 -0
  54. package/docs/commands/ticket.md +56 -0
  55. package/docs/skills/systematic-debugging.md +70 -0
  56. package/docs/skills/testing-patterns.md +89 -0
  57. package/docs/skills/typescript-conventions.md +137 -0
  58. package/docs/skills/verification-before-completion.md +91 -0
  59. package/docs/stacks/cloudflare-d1-kv.md +110 -0
  60. package/docs/stacks/i18n-typesafe.md +141 -0
  61. package/docs/stacks/protobuf-contracts.md +85 -0
  62. package/docs/stacks/rust-wasm-patterns.md +106 -0
  63. package/docs/stacks/solidjs-patterns.md +110 -0
  64. package/docs/stacks/vanilla-extract-patterns.md +115 -0
  65. package/package.json +58 -0
  66. package/src/generator.ts +317 -0
  67. package/src/index.ts +30 -0
  68. package/src/types.ts +85 -0
  69. package/src/utils.ts +53 -0
  70. package/stacks/cloudflare/skills/cloudflare-d1-kv/SKILL.md +84 -0
  71. package/stacks/cloudflare/stack.json +26 -0
  72. package/stacks/i18n-typesafe/skills/i18n-typesafe/SKILL.md +64 -0
  73. package/stacks/i18n-typesafe/stack.json +25 -0
  74. package/stacks/protobuf/skills/protobuf-contracts/SKILL.md +78 -0
  75. package/stacks/protobuf/stack.json +25 -0
  76. package/stacks/rust-wasm/skills/rust-wasm-patterns/SKILL.md +76 -0
  77. package/stacks/rust-wasm/stack.json +26 -0
  78. package/stacks/solidjs/skills/solidjs-patterns/SKILL.md +66 -0
  79. package/stacks/solidjs/stack.json +52 -0
  80. package/stacks/vanilla-extract/skills/vanilla-extract-patterns/SKILL.md +76 -0
  81. package/stacks/vanilla-extract/stack.json +40 -0
  82. package/templates/claude-toolkit.config.ts +34 -0
  83. package/templates/configs/biome.base.json +35 -0
  84. package/templates/configs/tsconfig.base.json +16 -0
  85. package/templates/skill-rules.base.json +98 -0
@@ -0,0 +1,110 @@
1
+ # SolidJS Patterns
2
+
3
+ > SolidJS reactivity, signals, effects, and component patterns.
4
+
5
+ **Type:** Stack Skill (requires `solidjs` stack)
6
+ **Source:** [`stacks/solidjs/skills/solidjs-patterns/SKILL.md`](../stacks/solidjs/skills/solidjs-patterns/SKILL.md)
7
+ **Directory Mappings:** `src/components/`, `src/hooks/`, `src/pages/`
8
+ **File Extensions:** `.tsx`, `.jsx`
9
+
10
+ ## Overview
11
+
12
+ SolidJS uses **fine-grained reactivity** with no virtual DOM. Components run once; only the reactive expressions inside them re-execute when dependencies change. This is fundamentally different from React.
13
+
14
+ ## Core Primitives
15
+
16
+ ### createSignal
17
+ ```tsx
18
+ const [count, setCount] = createSignal(0);
19
+ // Read: count() -- always call as function
20
+ // Write: setCount(1) or setCount(prev => prev + 1)
21
+ ```
22
+
23
+ ### createEffect
24
+ Runs side effects when tracked dependencies change. Automatically tracks any signal read inside it.
25
+ ```tsx
26
+ createEffect(() => {
27
+ console.log("Count changed:", count());
28
+ });
29
+ ```
30
+
31
+ ### createMemo
32
+ Derived computation that caches its result. Only recalculates when dependencies change.
33
+ ```tsx
34
+ const doubled = createMemo(() => count() * 2);
35
+ ```
36
+
37
+ ### createResource
38
+ Async data fetching tied to a signal source. Returns a resource with loading/error states.
39
+ ```tsx
40
+ const [data] = createResource(userId, fetchUser);
41
+ // data(), data.loading, data.error
42
+ ```
43
+
44
+ ## Component Patterns
45
+
46
+ | Component | Purpose | Example |
47
+ |---|---|---|
48
+ | `<Show>` | Conditional rendering | `<Show when={isLoggedIn()} fallback={<Login />}>` |
49
+ | `<For>` | List rendering (keyed by reference) | `<For each={items()}>{(item) => ...}</For>` |
50
+ | `<Switch>/<Match>` | Multi-branch conditional | `<Match when={state() === "loading"}>` |
51
+ | `<ErrorBoundary>` | Error catching | `<ErrorBoundary fallback={(err) => ...}>` |
52
+ | `<Suspense>` | Async loading states | `<Suspense fallback={<Loading />}>` |
53
+
54
+ ## Props Handling
55
+
56
+ **CRITICAL: Never destructure props.** Destructuring breaks reactivity.
57
+
58
+ ```tsx
59
+ // WRONG -- kills reactivity
60
+ const MyComponent = ({ name, count }) => { ... };
61
+
62
+ // CORRECT -- preserves reactivity
63
+ const MyComponent = (props) => {
64
+ return <div>{props.name}: {props.count}</div>;
65
+ };
66
+ ```
67
+
68
+ - Use `mergeProps` for defaults: `const merged = mergeProps({ color: "blue" }, props)`
69
+ - Use `splitProps` to separate prop groups: `const [local, others] = splitProps(props, ["class", "style"])`
70
+
71
+ ## Store Patterns
72
+
73
+ `createStore` provides deep reactivity for complex nested state with path-based updates:
74
+
75
+ ```tsx
76
+ const [state, setState] = createStore({
77
+ user: { name: "Alice", settings: { theme: "dark" } }
78
+ });
79
+
80
+ // Granular updates
81
+ setState("user", "settings", "theme", "light");
82
+ ```
83
+
84
+ ## Anti-patterns
85
+
86
+ | Anti-pattern | Why it's wrong |
87
+ |---|---|
88
+ | **Destructuring props** | Breaks reactivity. Always use `props.x`. |
89
+ | **Unnecessary createEffect** | If you just need a derived value, use `createMemo`. |
90
+ | **Treating it like React** | Components do NOT re-run. Only reactive expressions update. |
91
+ | **Array index as key in For** | `<For>` is keyed by reference automatically. |
92
+ | **Reading signals outside reactive context** | Captures the value once at component top level. |
93
+ | **Forgetting to call signals** | `count` is a getter function, `count()` is the value. |
94
+
95
+ ## Best Practices Reference
96
+
97
+ For deeper guidance on SolidJS patterns (sourced from official docs and Ryan Carniato):
98
+
99
+ | Topic | Guide |
100
+ |---|---|
101
+ | How fine-grained reactivity works internally | [Reactivity Model](../best-practices/solidjs/reactivity-model.md) |
102
+ | Signal vs memo, derived values, when to use what | [Signals & State](../best-practices/solidjs/signals-and-state.md) |
103
+ | `produce`, `reconcile`, path syntax for nested state | [Stores & Nested State](../best-practices/solidjs/stores-and-nested-state.md) |
104
+ | `mergeProps`, `splitProps`, `children` helper | [Props Patterns](../best-practices/solidjs/props-patterns.md) |
105
+ | When NOT to use effects, `onMount`, `onCleanup` | [Effects & Lifecycle](../best-practices/solidjs/effects-and-lifecycle.md) |
106
+ | `Show`, `For`, `Switch`/`Match`, `Suspense` details | [Control Flow](../best-practices/solidjs/control-flow.md) |
107
+ | `createResource`, `createAsync`, optimistic updates | [Data Fetching](../best-practices/solidjs/data-fetching.md) |
108
+ | Full catalog of common SolidJS mistakes | [Anti-Patterns](../best-practices/solidjs/anti-patterns.md) |
109
+
110
+ See the full collection: [SolidJS Best Practices](../best-practices/solidjs/README.md)
@@ -0,0 +1,115 @@
1
+ # Vanilla Extract Patterns
2
+
3
+ > Type-safe CSS with vanilla-extract, sprinkles, and recipes.
4
+
5
+ **Type:** Stack Skill (requires `vanilla-extract` stack)
6
+ **Source:** [`stacks/vanilla-extract/skills/vanilla-extract-patterns/SKILL.md`](../stacks/vanilla-extract/skills/vanilla-extract-patterns/SKILL.md)
7
+ **Directory Mappings:** `src/styles/`, `src/theme/`
8
+ **File Extensions:** `.css.ts`
9
+
10
+ ## Overview
11
+
12
+ vanilla-extract provides zero-runtime, type-safe CSS written in TypeScript. All styles are authored in `*.css.ts` files and extracted to static CSS at build time.
13
+
14
+ **Important:** All vanilla-extract files MUST use the `*.css.ts` extension. The build plugin only processes these files.
15
+
16
+ ## Core API
17
+
18
+ ### style() -- Single Class Styles
19
+
20
+ Creates a single scoped class name with type-checked CSS properties.
21
+
22
+ ```ts
23
+ import { style } from '@vanilla-extract/css';
24
+
25
+ export const button = style({
26
+ padding: '8px 16px',
27
+ borderRadius: '4px',
28
+ ':hover': { opacity: 0.9 },
29
+ });
30
+ ```
31
+
32
+ Compose styles by passing an array:
33
+
34
+ ```ts
35
+ export const primaryButton = style([button, { background: 'blue', color: 'white' }]);
36
+ ```
37
+
38
+ ### styleVariants() -- Variant Maps
39
+
40
+ Creates a map of related style variants from a single definition.
41
+
42
+ ```ts
43
+ export const colorVariant = styleVariants({
44
+ primary: { background: 'blue', color: 'white' },
45
+ secondary: { background: 'gray', color: 'black' },
46
+ danger: { background: 'red', color: 'white' },
47
+ });
48
+ ```
49
+
50
+ ### recipe() -- Component Variants
51
+
52
+ Creates multi-variant styles with `defaultVariants` and compound variants. Requires `@vanilla-extract/recipes`.
53
+
54
+ ```ts
55
+ export const buttonRecipe = recipe({
56
+ base: { padding: '8px 16px', borderRadius: '4px' },
57
+ variants: {
58
+ color: {
59
+ primary: { background: 'blue', color: 'white' },
60
+ secondary: { background: 'gray', color: 'black' },
61
+ },
62
+ size: {
63
+ small: { fontSize: '12px' },
64
+ medium: { fontSize: '14px' },
65
+ large: { fontSize: '16px' },
66
+ },
67
+ },
68
+ defaultVariants: { color: 'primary', size: 'medium' },
69
+ });
70
+ ```
71
+
72
+ ### globalStyle() -- Use Sparingly
73
+
74
+ For CSS resets, body defaults, or third-party element overrides only.
75
+
76
+ ## Theming
77
+
78
+ ### Contract-first theming (recommended)
79
+
80
+ ```ts
81
+ import { createThemeContract, createTheme } from '@vanilla-extract/css';
82
+
83
+ export const vars = createThemeContract({
84
+ color: { brand: null, text: null, background: null },
85
+ space: { small: null, medium: null, large: null },
86
+ });
87
+
88
+ export const lightTheme = createTheme(vars, {
89
+ color: { brand: '#0066ff', text: '#1a1a1a', background: '#ffffff' },
90
+ space: { small: '4px', medium: '8px', large: '16px' },
91
+ });
92
+ ```
93
+
94
+ Use `vars` for type-safe token references: `style({ color: vars.color.brand })`
95
+
96
+ ### Sprinkles -- Atomic Utility Classes
97
+
98
+ Requires `@vanilla-extract/sprinkles`. Creates constrained atomic CSS classes with responsive conditions.
99
+
100
+ ## Anti-patterns
101
+
102
+ | Anti-pattern | Why it's wrong |
103
+ | ----------------------------- | ---------------------------------------------------------------- |
104
+ | **Inline styles** | Defeats the purpose. Use `style()` or `sprinkles()`. |
105
+ | **Runtime CSS** | vanilla-extract is build-time only. |
106
+ | **Overusing globalStyle** | Prefer scoped `style()`. Global styles create implicit coupling. |
107
+ | **Non-`.css.ts` files** | Style definitions in `.ts` files will not be processed. |
108
+ | **String-based theme values** | Use the `vars` object for type-safe references. |
109
+
110
+ ## Best Practices Reference
111
+
112
+ | Topic | Guide |
113
+ | ----------------------------------------- | ---------------------------------------------------------------------------- |
114
+ | `type` vs `interface` for theme contracts | [Type vs Interface](../best-practices/typescript/type-vs-interface.md) |
115
+ | `satisfies` for config validation | [The satisfies Operator](../best-practices/typescript/satisfies-operator.md) |
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "claude-toolkit",
3
+ "version": "0.1.9",
4
+ "description": "Reusable Claude Code configuration toolkit with stack-specific connectors",
5
+ "type": "module",
6
+ "bin": {
7
+ "claude-toolkit": "./bin/cli.ts"
8
+ },
9
+ "exports": {
10
+ ".": {
11
+ "import": "./src/index.ts",
12
+ "types": "./src/index.ts"
13
+ }
14
+ },
15
+ "files": [
16
+ "bin/",
17
+ "src/",
18
+ "core/",
19
+ "stacks/",
20
+ "templates/",
21
+ "docs/",
22
+ "CHANGELOG.md"
23
+ ],
24
+ "scripts": {
25
+ "typecheck": "tsc --noEmit",
26
+ "lint": "biome check --write",
27
+ "lint:check": "biome check",
28
+ "prepare": "npx husky"
29
+ },
30
+ "lint-staged": {
31
+ "*.{ts,tsx,js,jsx,json,css}": "biome check --write --no-errors-on-unmatched"
32
+ },
33
+ "keywords": [
34
+ "claude",
35
+ "claude-code",
36
+ "ai",
37
+ "developer-tools",
38
+ "configuration",
39
+ "toolkit"
40
+ ],
41
+ "author": "william-queant",
42
+ "license": "MIT",
43
+ "repository": {
44
+ "type": "git",
45
+ "url": "https://github.com/william-queant/claude-toolkit.git"
46
+ },
47
+ "devDependencies": {
48
+ "@biomejs/biome": "^2.4.10",
49
+ "@types/bun": "latest",
50
+ "husky": "^9.1.7",
51
+ "lint-staged": "^16.4.0",
52
+ "typescript": "^5.8.0"
53
+ },
54
+ "engines": {
55
+ "bun": ">=1.0.0"
56
+ },
57
+ "module": "src/index.ts"
58
+ }
@@ -0,0 +1,317 @@
1
+ import { copyFile as fsCopyFile } from "node:fs/promises";
2
+ import { join, resolve } from "node:path";
3
+ import type { ClaudeToolkitConfig, ResolvedConfig, StackPack } from "./types.js";
4
+ import { copyDir, exists, readJson, writeFileEnsureDir } from "./utils.js";
5
+
6
+ const TOOLKIT_ROOT = resolve(import.meta.dirname, "..");
7
+
8
+ /** Resolve the full configuration by merging core + stacks + project config */
9
+ async function resolveConfig(config: ClaudeToolkitConfig): Promise<ResolvedConfig> {
10
+ const stacks: StackPack[] = [];
11
+ const allMappings: Record<string, string> = {};
12
+
13
+ // Load stack packs
14
+ for (const stackName of config.stacks) {
15
+ const stackDir = join(TOOLKIT_ROOT, "stacks", stackName);
16
+ const stackJsonPath = join(stackDir, "stack.json");
17
+ if (exists(stackJsonPath)) {
18
+ const pack = await readJson<StackPack>(stackJsonPath);
19
+ stacks.push(pack);
20
+ Object.assign(allMappings, pack.defaultMappings);
21
+ }
22
+ }
23
+
24
+ // Project mappings override stack defaults
25
+ if (config.directoryMappings) {
26
+ Object.assign(allMappings, config.directoryMappings);
27
+ }
28
+
29
+ // Resolve hooks with package manager defaults
30
+ const installCmd =
31
+ config.hooks?.installCommand ??
32
+ (config.packageManager === "bun" ? "bun install" : `${config.packageManager} install`);
33
+
34
+ const hooks = {
35
+ ...config.hooks,
36
+ installCommand: installCmd,
37
+ };
38
+
39
+ // Collect skill names
40
+ const skills = [
41
+ "ct-systematic-debugging",
42
+ "ct-testing-patterns",
43
+ "ct-typescript-conventions",
44
+ "ct-verification-before-completion",
45
+ ...(config.excludeSkills ? [] : []),
46
+ ].filter((s) => !config.excludeSkills?.includes(s));
47
+
48
+ return { config, skills, directoryMappings: allMappings, hooks, stacks };
49
+ }
50
+
51
+ /** Generate the .claude/ directory from resolved config */
52
+ export async function generate(projectDir: string, config: ClaudeToolkitConfig): Promise<void> {
53
+ const resolved = await resolveConfig(config);
54
+ const claudeDir = join(projectDir, ".claude");
55
+
56
+ // 1. Copy core hooks
57
+ await copyDir(join(TOOLKIT_ROOT, "core", "hooks"), join(claudeDir, "hooks"));
58
+
59
+ // 2. Copy core skills
60
+ await copyDir(join(TOOLKIT_ROOT, "core", "skills"), join(claudeDir, "skills"));
61
+
62
+ // 3. Copy core commands (namespaced under ct/)
63
+ await copyDir(join(TOOLKIT_ROOT, "core", "commands", "ct"), join(claudeDir, "commands", "ct"));
64
+
65
+ // 4. Copy core agents
66
+ await copyDir(join(TOOLKIT_ROOT, "core", "agents"), join(claudeDir, "agents"));
67
+
68
+ // 5. Copy stack-specific skills
69
+ for (const stackName of config.stacks) {
70
+ const stackSkillsDir = join(TOOLKIT_ROOT, "stacks", stackName, "skills");
71
+ if (exists(stackSkillsDir)) {
72
+ await copyDir(stackSkillsDir, join(claudeDir, "skills"));
73
+ }
74
+ }
75
+
76
+ // 6. Generate skill-rules.json
77
+ await generateSkillRules(claudeDir, resolved);
78
+
79
+ // 7. Generate settings.json
80
+ await generateSettings(claudeDir, resolved);
81
+
82
+ // 8. Generate .claude/.gitignore
83
+ await writeFileEnsureDir(
84
+ join(claudeDir, ".gitignore"),
85
+ [
86
+ "# User-specific configuration",
87
+ "user-team-info.json",
88
+ "",
89
+ "# Local settings (personal overrides)",
90
+ "settings.local.json",
91
+ "",
92
+ "# Task-specific context",
93
+ "tasks/",
94
+ "",
95
+ ].join("\n"),
96
+ );
97
+
98
+ // 9. Scaffold base configs
99
+ await scaffoldConfigs(projectDir, resolved);
100
+
101
+ // 10. Generate skills README
102
+ await generateSkillsReadme(claudeDir, resolved);
103
+
104
+ console.log(
105
+ `Generated .claude/ with ${resolved.stacks.length} stack(s) and ${resolved.skills.length} core skills`,
106
+ );
107
+ }
108
+
109
+ /** Generate skill-rules.json from resolved config */
110
+ async function generateSkillRules(claudeDir: string, resolved: ResolvedConfig): Promise<void> {
111
+ // Load all SKILL.md files to build the rules
112
+ const rules: Record<string, unknown> = {};
113
+
114
+ // Load skill rules from each stack's stack.json
115
+ for (const stackName of resolved.config.stacks) {
116
+ const stackJsonPath = join(TOOLKIT_ROOT, "stacks", stackName, "stack.json");
117
+ if (exists(stackJsonPath)) {
118
+ const pack = await readJson<StackPack & { skillRules?: Record<string, unknown> }>(
119
+ stackJsonPath,
120
+ );
121
+ if (pack.skillRules) {
122
+ Object.assign(rules, pack.skillRules);
123
+ }
124
+ }
125
+ }
126
+
127
+ // Load core skill rules template
128
+ const coreRulesPath = join(TOOLKIT_ROOT, "templates", "skill-rules.base.json");
129
+ const baseRules = exists(coreRulesPath)
130
+ ? await readJson<Record<string, unknown>>(coreRulesPath)
131
+ : {};
132
+
133
+ const skillRules = {
134
+ $schema: "./skill-rules.schema.json",
135
+ version: "2.0",
136
+ config: {
137
+ minConfidenceScore: 3,
138
+ showMatchReasons: true,
139
+ maxSkillsToShow: 5,
140
+ },
141
+ scoring: {
142
+ keyword: 2,
143
+ keywordPattern: 3,
144
+ pathPattern: 4,
145
+ directoryMatch: 5,
146
+ intentPattern: 4,
147
+ contentPattern: 3,
148
+ contextPattern: 2,
149
+ },
150
+ directoryMappings: resolved.directoryMappings,
151
+ skills: {
152
+ ...(((baseRules as Record<string, unknown>).skills as Record<string, unknown>) ?? {}),
153
+ ...rules,
154
+ },
155
+ };
156
+
157
+ await writeFileEnsureDir(
158
+ join(claudeDir, "hooks", "skill-rules.json"),
159
+ `${JSON.stringify(skillRules, null, 2)}\n`,
160
+ );
161
+ }
162
+
163
+ /** Generate settings.json with hooks */
164
+ async function generateSettings(claudeDir: string, resolved: ResolvedConfig): Promise<void> {
165
+ const { hooks, config } = resolved;
166
+ const protectedBranches = config.git?.protectedBranches ?? ["main"];
167
+
168
+ const branchCheck = protectedBranches
169
+ .map((b) => `"$(git branch --show-current)" != "${b}"`)
170
+ .join(" && ");
171
+
172
+ const postToolUseHooks: unknown[] = [];
173
+
174
+ // Auto-format
175
+ if (hooks.formatter) {
176
+ const extensions = ["js", "jsx", "ts", "tsx"];
177
+ // Add stack-specific extensions
178
+ for (const stack of resolved.stacks) {
179
+ for (const ext of stack.fileExtensions) {
180
+ if (!extensions.includes(ext)) extensions.push(ext);
181
+ }
182
+ }
183
+ const extPattern = extensions.join("|");
184
+
185
+ postToolUseHooks.push({
186
+ type: "command",
187
+ command: `# Auto-format files\nif [[ "$CLAUDE_TOOL_INPUT_FILE_PATH" =~ \\.(${extPattern})$ ]]; then\n file_path="$CLAUDE_TOOL_INPUT_FILE_PATH"\n ${hooks.formatter} "$file_path" 2>&1\n exit_code=$?\n if [ $exit_code -ne 0 ]; then\n echo '{"feedback": "Formatting failed. Check file for syntax errors."}' >&2\n exit 1\n else\n echo '{"feedback": "Formatting applied.", "suppressOutput": true}'\n fi\nfi`,
188
+ timeout: 30,
189
+ });
190
+ }
191
+
192
+ // Auto-install
193
+ const installCmd = hooks.installCommand ?? `${config.packageManager} install`;
194
+ postToolUseHooks.push({
195
+ type: "command",
196
+ command: `# Auto-install dependencies when package.json changes\nif [[ "$CLAUDE_TOOL_INPUT_FILE_PATH" =~ package\\.json$ ]]; then\n echo '{"feedback": "Installing dependencies..."}' >&2\n ${installCmd} >/dev/null 2>&1 && echo '{"feedback": "Dependencies installed.", "suppressOutput": true}' || {\n echo '{"feedback": "Failed to install dependencies."}' >&2\n exit 1\n }\nfi`,
197
+ timeout: 60,
198
+ });
199
+
200
+ // Auto-run tests
201
+ if (hooks.testRunner) {
202
+ postToolUseHooks.push({
203
+ type: "command",
204
+ command: `# Auto-run tests when test files change\nif [[ "$CLAUDE_TOOL_INPUT_FILE_PATH" =~ \\.test\\.(js|jsx|ts|tsx)$ ]] || [[ "$CLAUDE_TOOL_INPUT_FILE_PATH" =~ \\.spec\\.(js|jsx|ts|tsx)$ ]]; then\n echo '{"feedback": "Running tests..."}' >&2\n ${hooks.testRunner} "$CLAUDE_TOOL_INPUT_FILE_PATH" 2>&1 | tail -30\n exit_code=\${PIPESTATUS[0]}\n if [ $exit_code -eq 0 ]; then\n echo '{"feedback": "Tests passed."}'\n else\n echo '{"feedback": "Tests failed. See output above."}' >&2\n fi\nfi`,
205
+ timeout: 90,
206
+ });
207
+ }
208
+
209
+ // Type-check
210
+ if (hooks.typeCheck) {
211
+ postToolUseHooks.push({
212
+ type: "command",
213
+ command: `# Type-check TypeScript files\nif [[ "$CLAUDE_TOOL_INPUT_FILE_PATH" =~ \\.(ts|tsx)$ ]]; then\n echo '{"feedback": "Checking TypeScript types..."}' >&2\n output=$(${hooks.typeCheck} 2>&1)\n exit_code=$?\n if [ $exit_code -eq 0 ]; then\n echo '{"feedback": "No TypeScript errors.", "suppressOutput": true}'\n else\n errors=$(echo "$output" | grep -A 2 "error TS" | head -30)\n if [ -n "$errors" ]; then\n echo '{"feedback": "TypeScript found type errors:"}' >&2\n echo "$errors" >&2\n fi\n fi\n exit 0\nfi`,
214
+ timeout: 30,
215
+ });
216
+ }
217
+
218
+ // Extra checks (e.g., cargo check for Rust)
219
+ if (hooks.extraChecks) {
220
+ for (const check of hooks.extraChecks) {
221
+ postToolUseHooks.push({
222
+ type: "command",
223
+ command: `# Extra check: ${check}\nif [[ "$CLAUDE_TOOL_INPUT_FILE_PATH" =~ \\.rs$ ]]; then\n echo '{"feedback": "Running extra check..."}' >&2\n output=$(${check} 2>&1)\n exit_code=$?\n if [ $exit_code -eq 0 ]; then\n echo '{"feedback": "Check passed.", "suppressOutput": true}'\n else\n echo '{"feedback": "Check failed:"}' >&2\n echo "$output" | tail -20 >&2\n fi\n exit 0\nfi`,
224
+ timeout: 60,
225
+ });
226
+ }
227
+ }
228
+
229
+ const settings = {
230
+ includeCoAuthoredBy: true,
231
+ env: {
232
+ INSIDE_CLAUDE_CODE: "1",
233
+ BASH_DEFAULT_TIMEOUT_MS: "420000",
234
+ BASH_MAX_TIMEOUT_MS: "420000",
235
+ },
236
+ hooks: {
237
+ UserPromptSubmit: [
238
+ {
239
+ hooks: [
240
+ {
241
+ type: "command",
242
+ command: '"$CLAUDE_PROJECT_DIR"/.claude/hooks/skill-eval.sh',
243
+ timeout: 5,
244
+ },
245
+ ],
246
+ },
247
+ ],
248
+ PreToolUse: [
249
+ {
250
+ matcher: "Edit|MultiEdit|Write",
251
+ hooks: [
252
+ {
253
+ type: "command",
254
+ command: `# Prevent editing on protected branches\n[ ${branchCheck} ] || { echo '{"block": true, "message": "Cannot edit files on a protected branch. Create a feature branch first."}' >&2; exit 2; }`,
255
+ timeout: 5,
256
+ },
257
+ ],
258
+ },
259
+ ],
260
+ PostToolUse: postToolUseHooks.map((hook) => ({
261
+ matcher: "Edit|MultiEdit|Write",
262
+ hooks: [hook],
263
+ })),
264
+ },
265
+ };
266
+
267
+ await writeFileEnsureDir(
268
+ join(claudeDir, "settings.json"),
269
+ `${JSON.stringify(settings, null, 2)}\n`,
270
+ );
271
+ }
272
+
273
+ /** Scaffold base config files (biome.json, tsconfig.json) into the project */
274
+ async function scaffoldConfigs(projectDir: string, resolved: ResolvedConfig): Promise<void> {
275
+ if (resolved.config.scaffoldConfigs === false) return;
276
+
277
+ const configsDir = join(TOOLKIT_ROOT, "templates", "configs");
278
+ const configs = [
279
+ { src: "biome.base.json", dest: "biome.json" },
280
+ { src: "tsconfig.base.json", dest: "tsconfig.json" },
281
+ ];
282
+
283
+ for (const { src, dest } of configs) {
284
+ const destPath = join(projectDir, dest);
285
+ if (exists(destPath)) {
286
+ console.log(` Skipped ${dest} (already exists)`);
287
+ } else {
288
+ await fsCopyFile(join(configsDir, src), destPath);
289
+ console.log(` Scaffolded ${dest}`);
290
+ }
291
+ }
292
+ }
293
+
294
+ /** Generate skills/README.md */
295
+ async function generateSkillsReadme(claudeDir: string, resolved: ResolvedConfig): Promise<void> {
296
+ let content = "# Claude Code Skills\n\n";
297
+ content += "Auto-generated by claude-toolkit. Do not edit manually.\n\n";
298
+ content += "## Core Skills\n\n";
299
+ content += "| Skill | Description |\n";
300
+ content += "|-------|-------------|\n";
301
+ content +=
302
+ "| ct-systematic-debugging | Four-phase debugging methodology, root cause analysis |\n";
303
+ content += "| ct-testing-patterns | Test-driven development workflow and patterns |\n";
304
+ content += "| ct-typescript-conventions | TypeScript strict mode and conventions |\n";
305
+ content += "| ct-verification-before-completion | Evidence-based completion claims protocol |\n";
306
+ content += "\n## Stack Skills\n\n";
307
+ content += "| Skill | Stack | Description |\n";
308
+ content += "|-------|-------|-------------|\n";
309
+
310
+ for (const stack of resolved.stacks) {
311
+ content += `| ${stack.name} | ${stack.name} | ${stack.description} |\n`;
312
+ }
313
+
314
+ content += "\n---\n\n*Generated from claude-toolkit.config.ts*\n";
315
+
316
+ await writeFileEnsureDir(join(claudeDir, "skills", "README.md"), content);
317
+ }
package/src/index.ts ADDED
@@ -0,0 +1,30 @@
1
+ export type {
2
+ ClaudeToolkitConfig,
3
+ GitConfig,
4
+ HookConfig,
5
+ ProjectInfo,
6
+ ResolvedConfig,
7
+ StackName,
8
+ StackPack,
9
+ } from "./types.js";
10
+
11
+ /**
12
+ * Define a claude-toolkit configuration with full type safety.
13
+ *
14
+ * @example
15
+ * ```ts
16
+ * import { defineConfig } from 'claude-toolkit'
17
+ *
18
+ * export default defineConfig({
19
+ * stacks: ['solidjs', 'rust-wasm', 'cloudflare'],
20
+ * packageManager: 'bun',
21
+ * hooks: {
22
+ * formatter: 'bun run prettier --write',
23
+ * testRunner: 'bun run vitest run',
24
+ * },
25
+ * })
26
+ * ```
27
+ */
28
+ export function defineConfig(config: import("./types.js").ClaudeToolkitConfig) {
29
+ return config;
30
+ }