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.
- package/CHANGELOG.md +38 -0
- package/LICENSE +21 -0
- package/README.md +126 -0
- package/bin/cli.ts +112 -0
- package/core/agents/ct-code-reviewer.md +123 -0
- package/core/agents/ct-github-workflow.md +137 -0
- package/core/commands/ct/code-quality.md +59 -0
- package/core/commands/ct/onboard.md +84 -0
- package/core/commands/ct/pr-review.md +104 -0
- package/core/commands/ct/pr-summary.md +59 -0
- package/core/commands/ct/proto-check.md +74 -0
- package/core/commands/ct/ticket.md +71 -0
- package/core/hooks/skill-eval.js +381 -0
- package/core/hooks/skill-eval.sh +35 -0
- package/core/hooks/skill-rules.schema.json +112 -0
- package/core/skills/systematic-debugging/SKILL.md +44 -0
- package/core/skills/testing-patterns/SKILL.md +52 -0
- package/core/skills/typescript-conventions/SKILL.md +57 -0
- package/core/skills/verification-before-completion/SKILL.md +42 -0
- package/docs/README.md +49 -0
- package/docs/agents/code-reviewer.md +76 -0
- package/docs/agents/github-workflow.md +98 -0
- package/docs/best-practices/solidjs/README.md +43 -0
- package/docs/best-practices/solidjs/anti-patterns.md +166 -0
- package/docs/best-practices/solidjs/component-patterns.md +131 -0
- package/docs/best-practices/solidjs/context-and-global-state.md +131 -0
- package/docs/best-practices/solidjs/control-flow.md +124 -0
- package/docs/best-practices/solidjs/data-fetching.md +205 -0
- package/docs/best-practices/solidjs/effects-and-lifecycle.md +113 -0
- package/docs/best-practices/solidjs/performance.md +100 -0
- package/docs/best-practices/solidjs/props-patterns.md +100 -0
- package/docs/best-practices/solidjs/reactivity-model.md +104 -0
- package/docs/best-practices/solidjs/signals-and-state.md +78 -0
- package/docs/best-practices/solidjs/stores-and-nested-state.md +111 -0
- package/docs/best-practices/solidjs/typescript-integration.md +186 -0
- package/docs/best-practices/typescript/README.md +45 -0
- package/docs/best-practices/typescript/any-and-unknown.md +73 -0
- package/docs/best-practices/typescript/deriving-vs-decoupling.md +83 -0
- package/docs/best-practices/typescript/discriminated-unions.md +75 -0
- package/docs/best-practices/typescript/enums-alternatives.md +72 -0
- package/docs/best-practices/typescript/essential-patterns.md +119 -0
- package/docs/best-practices/typescript/generics-patterns.md +105 -0
- package/docs/best-practices/typescript/micro-opinions.md +87 -0
- package/docs/best-practices/typescript/runtime-validation.md +62 -0
- package/docs/best-practices/typescript/satisfies-operator.md +100 -0
- package/docs/best-practices/typescript/tsconfig-cheat-sheet.md +129 -0
- package/docs/best-practices/typescript/type-organization.md +64 -0
- package/docs/best-practices/typescript/type-vs-interface.md +80 -0
- package/docs/commands/code-quality.md +42 -0
- package/docs/commands/onboard.md +72 -0
- package/docs/commands/pr-review.md +102 -0
- package/docs/commands/pr-summary.md +50 -0
- package/docs/commands/proto-check.md +59 -0
- package/docs/commands/ticket.md +56 -0
- package/docs/skills/systematic-debugging.md +70 -0
- package/docs/skills/testing-patterns.md +89 -0
- package/docs/skills/typescript-conventions.md +137 -0
- package/docs/skills/verification-before-completion.md +91 -0
- package/docs/stacks/cloudflare-d1-kv.md +110 -0
- package/docs/stacks/i18n-typesafe.md +141 -0
- package/docs/stacks/protobuf-contracts.md +85 -0
- package/docs/stacks/rust-wasm-patterns.md +106 -0
- package/docs/stacks/solidjs-patterns.md +110 -0
- package/docs/stacks/vanilla-extract-patterns.md +115 -0
- package/package.json +58 -0
- package/src/generator.ts +317 -0
- package/src/index.ts +30 -0
- package/src/types.ts +85 -0
- package/src/utils.ts +53 -0
- package/stacks/cloudflare/skills/cloudflare-d1-kv/SKILL.md +84 -0
- package/stacks/cloudflare/stack.json +26 -0
- package/stacks/i18n-typesafe/skills/i18n-typesafe/SKILL.md +64 -0
- package/stacks/i18n-typesafe/stack.json +25 -0
- package/stacks/protobuf/skills/protobuf-contracts/SKILL.md +78 -0
- package/stacks/protobuf/stack.json +25 -0
- package/stacks/rust-wasm/skills/rust-wasm-patterns/SKILL.md +76 -0
- package/stacks/rust-wasm/stack.json +26 -0
- package/stacks/solidjs/skills/solidjs-patterns/SKILL.md +66 -0
- package/stacks/solidjs/stack.json +52 -0
- package/stacks/vanilla-extract/skills/vanilla-extract-patterns/SKILL.md +76 -0
- package/stacks/vanilla-extract/stack.json +40 -0
- package/templates/claude-toolkit.config.ts +34 -0
- package/templates/configs/biome.base.json +35 -0
- package/templates/configs/tsconfig.base.json +16 -0
- 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
|
+
}
|
package/src/generator.ts
ADDED
|
@@ -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
|
+
}
|