claude-toolkit 0.15.0 → 0.17.0-rc.1
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 +30 -0
- package/README.md +55 -14
- package/bin/cli.ts +75 -19
- package/core/skills/ct-typescript-conventions/SKILL.md +59 -1
- package/package.json +6 -7
- package/src/generator.ts +66 -27
- package/src/refresh-hint.ts +95 -0
- package/templates/skill-rules.base.json +29 -6
- package/bin/postinstall.mjs +0 -69
- package/docs/README.md +0 -59
- package/docs/agents/code-reviewer.md +0 -76
- package/docs/agents/github-workflow.md +0 -98
- package/docs/best-practices/capacitor/README.md +0 -64
- package/docs/best-practices/capacitor/capacitor-8.md +0 -60
- package/docs/best-practices/capacitor/capgo-setup.md +0 -151
- package/docs/best-practices/capacitor/channels-and-rollouts.md +0 -101
- package/docs/best-practices/capacitor/live-updates-ota.md +0 -75
- package/docs/best-practices/capacitor/security-encryption.md +0 -68
- package/docs/best-practices/solidjs/README.md +0 -43
- package/docs/best-practices/solidjs/anti-patterns.md +0 -166
- package/docs/best-practices/solidjs/component-patterns.md +0 -131
- package/docs/best-practices/solidjs/context-and-global-state.md +0 -131
- package/docs/best-practices/solidjs/control-flow.md +0 -124
- package/docs/best-practices/solidjs/data-fetching.md +0 -205
- package/docs/best-practices/solidjs/effects-and-lifecycle.md +0 -113
- package/docs/best-practices/solidjs/performance.md +0 -100
- package/docs/best-practices/solidjs/props-patterns.md +0 -100
- package/docs/best-practices/solidjs/reactivity-model.md +0 -104
- package/docs/best-practices/solidjs/signals-and-state.md +0 -78
- package/docs/best-practices/solidjs/stores-and-nested-state.md +0 -111
- package/docs/best-practices/solidjs/typescript-integration.md +0 -186
- package/docs/best-practices/testing/README.md +0 -84
- package/docs/best-practices/testing/playwright-e2e.md +0 -649
- package/docs/best-practices/testing/storybook-interaction.md +0 -455
- package/docs/best-practices/testing/vitest-unit.md +0 -451
- package/docs/best-practices/typescript/README.md +0 -45
- package/docs/best-practices/typescript/any-and-unknown.md +0 -73
- package/docs/best-practices/typescript/deriving-vs-decoupling.md +0 -83
- package/docs/best-practices/typescript/discriminated-unions.md +0 -75
- package/docs/best-practices/typescript/enums-alternatives.md +0 -72
- package/docs/best-practices/typescript/essential-patterns.md +0 -119
- package/docs/best-practices/typescript/generics-patterns.md +0 -105
- package/docs/best-practices/typescript/micro-opinions.md +0 -87
- package/docs/best-practices/typescript/runtime-validation.md +0 -62
- package/docs/best-practices/typescript/satisfies-operator.md +0 -100
- package/docs/best-practices/typescript/tsconfig-cheat-sheet.md +0 -129
- package/docs/best-practices/typescript/type-organization.md +0 -64
- package/docs/best-practices/typescript/type-vs-interface.md +0 -80
- package/docs/commands/code-quality.md +0 -36
- package/docs/commands/onboard.md +0 -63
- package/docs/commands/pr-review.md +0 -91
- package/docs/commands/pr-summary.md +0 -50
- package/docs/commands/proto-check.md +0 -59
- package/docs/commands/ticket.md +0 -56
- package/docs/skills/code-style.md +0 -26
- package/docs/skills/systematic-debugging.md +0 -70
- package/docs/skills/testing-patterns.md +0 -140
- package/docs/skills/typescript-conventions.md +0 -191
- package/docs/skills/verification-before-completion.md +0 -103
- package/docs/stacks/capacitor-ota.md +0 -145
- package/docs/stacks/cloudflare-d1-kv.md +0 -144
- package/docs/stacks/esnext-idioms.md +0 -25
- package/docs/stacks/i18n-typesafe.md +0 -141
- package/docs/stacks/playwright-patterns.md +0 -179
- package/docs/stacks/protobuf-contracts.md +0 -85
- package/docs/stacks/rust-wasm-patterns.md +0 -106
- package/docs/stacks/solidjs-patterns.md +0 -110
- package/docs/stacks/storybook-patterns.md +0 -177
- package/docs/stacks/vanilla-extract-patterns.md +0 -115
- package/docs/stacks/vite-vitest-patterns.md +0 -485
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.17.0 (unreleased)
|
|
4
|
+
|
|
5
|
+
Nothing runs at install time any more. The `postinstall` script that regenerated `.claude/` is gone; projects opt in with one line in their own `package.json`, and the new `claude-toolkit refresh` does the work. The package also stops shipping `docs/`, and releases move to npm trusted publishing behind a tarball guard.
|
|
6
|
+
|
|
7
|
+
**BREAKING:** installing or upgrading claude-toolkit no longer regenerates `.claude/`. After upgrading, run `bunx claude-toolkit` once, then add to your `package.json` scripts:
|
|
8
|
+
|
|
9
|
+
"prepare": "claude-toolkit refresh || exit 0"
|
|
10
|
+
|
|
11
|
+
This line is **required** when `.claude/` is gitignored (the README's recommended setup): without it a fresh clone has no `.claude/` until someone runs `bunx claude-toolkit`. Yarn 2+ never runs `prepare` for your own project — use `"postinstall"` there. npm runs `prepare` only on a plain `npm install` and pnpm skips it on `pnpm add`; after those, run `bunx claude-toolkit refresh`. If you added `claude-toolkit` to Bun's `trustedDependencies` or pnpm's `onlyBuiltDependencies`, remove it.
|
|
12
|
+
|
|
13
|
+
- feat!: remove the install-time `postinstall` script (`bin/postinstall.mjs`) and the internal `postinstall` command. The package now ships no install scripts and no `child_process` or `process.env` use.
|
|
14
|
+
- feat: `claude-toolkit refresh [dir]` regenerates `.claude/` only when the installed toolkit version differs from `.claude/.toolkit-version`; the up-to-date path never imports the config. It never creates the config and never scaffolds `biome.json`/`tsconfig.json`; a config that fails to load exits 1. Accepts `claude-toolkit.config.ts` or `claude-toolkit.config.js`.
|
|
15
|
+
- feat: every non-quiet `bunx claude-toolkit` run ends with the `prepare` line until `package.json` has a script running `claude-toolkit refresh` — with a named-script variant when `prepare` is already taken, `postinstall` for Yarn 2+ (`.yarnrc.yml`), the npm and pnpm cases where `prepare` does not run, and a leading line when upgrading from 0.16 or earlier. The CLI never edits `package.json`.
|
|
16
|
+
- fix(settings): the protected-branch guard blocked every edit on every branch when two or more `git.protectedBranches` were configured (`[` does not accept `&&`). It is now a `case` statement; branch names are validated against `^[A-Za-z0-9._/][A-Za-z0-9._/-]*$`, and an invalid name fails generation with an error naming it. An empty list now generates no guard instead of blocking every edit.
|
|
17
|
+
- build: stop shipping `docs/` (about 270 KB); the README links the docs on GitHub. `prepare` runs the husky 9 binary instead of `npx husky`. Dropped the dead pre-0.11 `skill-eval.sh` cleanup.
|
|
18
|
+
- ci: publish with npm trusted publishing (OIDC, automatic provenance, no token secret); prerelease versions go to the `next` dist-tag, and the workflow can be re-run by hand on a tag. Before publishing it runs typecheck, lint and tests, strips `prepare`, and runs `check:pack --publish` (no install scripts, no shell or environment access in shipped code, no `docs/`) and `check:smoke` (installs the packed tarball and runs `refresh`). A new CI workflow runs the checks on every push and pull request.
|
|
19
|
+
- build: the post-commit version hook no longer bumps prerelease versions (it turned `0.17.0-rc.0` into `0.17.NaN`); `@types/bun` is pinned to `^1.3.11`.
|
|
20
|
+
- docs: README documents `refresh`, the `prepare` opt-in per package manager, upgrading from 0.16, and the release-candidate flow; the auto-format, auto-test and type-check hooks are marked not yet functional (fixed in 0.18.0). CLAUDE.md drops the postinstall description and corrects the post-commit and `core/hooks` notes.
|
|
21
|
+
- fix(package): `bin` is now `bin/cli.ts` without a leading `./`. npm 11.10 and later silently drop a `./`-prefixed `bin` entry at publish time (`npm pack` keeps it), which would have shipped 0.17.0 without its CLI; `check:pack` now rejects such paths. `repository.url` uses the `git+https` form npm normalizes to.
|
|
22
|
+
|
|
23
|
+
## 0.16.0 (2026-08-08)
|
|
24
|
+
|
|
25
|
+
`ct-typescript-conventions` now selects a compiler as well as types: prefer the TypeScript 7 native compiler where a project already ships it, fall back cleanly where it does not, and never upgrade a project unprompted. Availability-aware in the same spirit as `ct-esnext-idioms`.
|
|
26
|
+
|
|
27
|
+
- feat: prioritise the TypeScript 7 native compiler in `ct-typescript-conventions` — TS7 reached GA on 2026-07-08 as `typescript@7.0.2`, with Microsoft reporting 8x–12x faster full builds. The skill detects the *installed* version (`require('typescript/package.json').version`) rather than the declared semver range, which `overrides`/`resolutions`/catalogs routinely defeat, and names the three detection traps: the binary is `tsc` not `tsgo` (that was the pre-GA `@typescript/native-preview` name), range-sniffing is unreliable, and an API probe gives a false negative because TS7 exposes only `version`/`versionMajorMinor`.
|
|
28
|
+
- feat: document when *not* to switch — TS7.0 ships no stable programmatic API, so `typescript-eslint`, `ts-jest`, `ts-node`, `vue-tsc`, `svelte-check`, `@astrojs/check` and `@angular/compiler-cli` break at install or lint time rather than at `tsc --noEmit`, which means a green type-check proves nothing. The skill prescribes the documented side-by-side alias install instead of a downgrade.
|
|
29
|
+
- feat: add the TS7 `tsconfig.json` deltas — the options TS7 rejects outright (`ignoreDeprecations` no longer silences them), plus the two changed defaults behind most real breakage. Verified empirically against 5.9.3 / 6.0.3 / 7.0.2: both defaults land in 6.0 and carry forward, `types` fails `TS2591` even with `@types/node` installed, `rootDir` only bites when an `outDir` is set, and `types: ["*"]` is `TS2688` on 5.x — so no literal value is safe across majors.
|
|
30
|
+
- feat: skill triggers now fire on typecheck, tsconfig, and compiler work — added the `tsc`, `tsgo`, `tsconfig`, `typecheck` and `compiler` keywords, the `**/tsconfig*.json` path pattern, and intents for running a typecheck or upgrading TypeScript.
|
|
31
|
+
- docs: TSConfig cheat sheet gains a TypeScript 7 delta section; the skill's new remit is reflected in the generated skills index, README, and docs index.
|
|
32
|
+
|
|
3
33
|
## 0.15.0 (2026-07-04)
|
|
4
34
|
|
|
5
35
|
Two new authoring-convention assets — an opt-in `esnext` stack and an always-on `ct-code-style` core skill — plus Biome enforcement of the mechanically-checkable style rules.
|
package/README.md
CHANGED
|
@@ -18,12 +18,31 @@ bun add -d claude-toolkit
|
|
|
18
18
|
bunx claude-toolkit
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
+
Then add one line to your `package.json` so `.claude/` is rebuilt after toolkit upgrades and on fresh clones. It is **required** when `.claude/` is gitignored (the setup recommended below):
|
|
22
|
+
|
|
23
|
+
```json
|
|
24
|
+
{
|
|
25
|
+
"scripts": {
|
|
26
|
+
"prepare": "claude-toolkit refresh || exit 0"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
claude-toolkit itself runs nothing at install time: this is your project's own script, and `|| exit 0` keeps installs working where bun is missing. If `prepare` is already taken, add `"claude-toolkit:refresh": "claude-toolkit refresh || exit 0"` and append `&& bun run claude-toolkit:refresh` (or `npm run`/`pnpm run`/`yarn run`) to your `prepare`. Until one of these is in place, `bunx claude-toolkit` prints the exact line for your project.
|
|
32
|
+
|
|
33
|
+
When your package manager runs it:
|
|
34
|
+
|
|
35
|
+
- **bun** runs `prepare` on `bun install`, `bun add` and `bun update`.
|
|
36
|
+
- **npm** runs it only on a plain `npm install`. After `npm install <pkg>` or `npm update`, run `bunx claude-toolkit refresh`.
|
|
37
|
+
- **pnpm** runs it on `pnpm install` and `pnpm update`, not on `pnpm add`. After `pnpm add`, run `bunx claude-toolkit refresh`.
|
|
38
|
+
- **Yarn 2+** never runs `prepare` for your own project. Use `"postinstall": "claude-toolkit refresh || exit 0"` instead; it runs whenever your dependencies change. If you publish the package, don't ship that `postinstall` — run `bunx claude-toolkit refresh` by hand instead.
|
|
39
|
+
|
|
21
40
|
## How It Works
|
|
22
41
|
|
|
23
42
|
1. Run `bunx claude-toolkit` — on the first run it creates a `claude-toolkit.config.ts` at your project root, pre-filled with the stacks it detects
|
|
24
43
|
2. It generates a `.claude/` directory with skills, hooks, commands, and agents — regenerated cleanly each run, so stacks you remove leave no stale skills behind
|
|
25
44
|
3. Claude Code picks up the generated config automatically
|
|
26
|
-
4. `.claude/`
|
|
45
|
+
4. After a toolkit upgrade, the `prepare` line from Quick Start runs `claude-toolkit refresh`, which rebuilds `.claude/` only when the installed version differs from the one that generated it. Nothing else runs at install time
|
|
27
46
|
|
|
28
47
|
```ts
|
|
29
48
|
import { defineConfig } from "claude-toolkit";
|
|
@@ -46,13 +65,14 @@ export default defineConfig({
|
|
|
46
65
|
|
|
47
66
|
## CLI Commands
|
|
48
67
|
|
|
49
|
-
| Command
|
|
50
|
-
|
|
|
51
|
-
| `bunx claude-toolkit`
|
|
52
|
-
| `bunx claude-toolkit --update`
|
|
53
|
-
| `bunx claude-toolkit
|
|
68
|
+
| Command | Description |
|
|
69
|
+
| ----------------------------------- | ---------------------------------------------------------------------------------------------------- |
|
|
70
|
+
| `bunx claude-toolkit` | Create the config if missing, then (re)generate `.claude/`. Idempotent. |
|
|
71
|
+
| `bunx claude-toolkit --update` | Same, and also add newly-detected stacks to your config |
|
|
72
|
+
| `bunx claude-toolkit refresh [dir]` | Regenerate `.claude/` only if the toolkit version changed. Never creates the config or other files. |
|
|
73
|
+
| `bunx claude-toolkit help` | Show available commands |
|
|
54
74
|
|
|
55
|
-
Aliases (back-compat): `init` is a friendly name for the bare command, `update` equals `--update`, and `sync` is deprecated — use the bare command.
|
|
75
|
+
Aliases (back-compat): `init` is a friendly name for the bare command, `update` equals `--update`, and `sync` is deprecated — use the bare command. `refresh` is what your `prepare` script runs: with no config it prints one setup line and exits 0, a config that fails to load exits 1, and `--quiet` silences it.
|
|
56
76
|
|
|
57
77
|
## Stack Auto-Detection
|
|
58
78
|
|
|
@@ -91,7 +111,7 @@ Generated .claude/ with 4 stack(s) and 4 core skills
|
|
|
91
111
|
|
|
92
112
|
Stacks in your config that are no longer detected are reported but left unchanged (remove them manually if intended).
|
|
93
113
|
|
|
94
|
-
**
|
|
114
|
+
**Keeping `.claude/` in sync** — `claude-toolkit refresh` compares the installed toolkit version with `.claude/.toolkit-version` and regenerates only when they differ; when they match it returns without loading your config. It never creates the config and never scaffolds `biome.json`/`tsconfig.json`. Run it from your `prepare` script (see Quick Start).
|
|
95
115
|
|
|
96
116
|
## Available Stacks
|
|
97
117
|
|
|
@@ -113,15 +133,15 @@ Stacks in your config that are no longer detected are reported but left unchange
|
|
|
113
133
|
|
|
114
134
|
- **Skill Evaluation Engine** — Analyzes prompts and suggests relevant skills
|
|
115
135
|
- **Main Branch Protection** — Prevents accidental edits on protected branches
|
|
116
|
-
- **Auto-formatting** — Formats files after edits
|
|
117
|
-
- **Auto-testing** — Runs related tests when test files change
|
|
118
|
-
- **Type checking** — Checks TypeScript types on save
|
|
136
|
+
- **Auto-formatting** — Formats files after edits _(not yet functional — fixed in 0.18.0)_
|
|
137
|
+
- **Auto-testing** — Runs related tests when test files change _(not yet functional — fixed in 0.18.0)_
|
|
138
|
+
- **Type checking** — Checks TypeScript types on save _(not yet functional — fixed in 0.18.0)_
|
|
119
139
|
|
|
120
140
|
### Core Skills
|
|
121
141
|
|
|
122
142
|
- `ct-systematic-debugging` — Four-phase debugging methodology
|
|
123
143
|
- `ct-testing-patterns` — TDD workflow and patterns
|
|
124
|
-
- `ct-typescript-conventions` — TypeScript strict mode best practices
|
|
144
|
+
- `ct-typescript-conventions` — TypeScript strict mode best practices, and preferring the TS7 native compiler where available
|
|
125
145
|
- `ct-verification-before-completion` — Evidence-based completion claims
|
|
126
146
|
- `ct-code-style` — Code structure & style: guard clauses, lookup tables, no magic values
|
|
127
147
|
|
|
@@ -149,6 +169,8 @@ Add `.claude/` to your `.gitignore` (it's generated, not tracked):
|
|
|
149
169
|
CLAUDE.local.md
|
|
150
170
|
```
|
|
151
171
|
|
|
172
|
+
With `.claude/` gitignored, a fresh clone has no `.claude/` until something generates it, so the `prepare` line from Quick Start is **required** in this setup.
|
|
173
|
+
|
|
152
174
|
Track the config file and CLAUDE.md:
|
|
153
175
|
|
|
154
176
|
```
|
|
@@ -156,9 +178,17 @@ claude-toolkit.config.ts # tracked — your project's Claude config
|
|
|
156
178
|
CLAUDE.md # tracked — project-specific documentation
|
|
157
179
|
```
|
|
158
180
|
|
|
181
|
+
## Upgrading from 0.16 or earlier
|
|
182
|
+
|
|
183
|
+
0.17.0 removed the install-time `postinstall` script: installing or upgrading claude-toolkit no longer runs anything. After upgrading:
|
|
184
|
+
|
|
185
|
+
1. Run `bunx claude-toolkit` once to regenerate `.claude/`.
|
|
186
|
+
2. Add `"prepare": "claude-toolkit refresh || exit 0"` to your `package.json` scripts (the CLI prints the exact line for your project, including the Yarn 2+ and existing-`prepare` variants).
|
|
187
|
+
3. If you added `claude-toolkit` to Bun's `trustedDependencies` or pnpm's `onlyBuiltDependencies` so its install script could run, remove it — there is no install script any more.
|
|
188
|
+
|
|
159
189
|
## Documentation
|
|
160
190
|
|
|
161
|
-
Full reference documentation for all skills, commands, and agents
|
|
191
|
+
Full reference documentation for all skills, commands, and agents lives in the [`docs/` directory on GitHub](https://github.com/william-queant/claude-toolkit/blob/main/docs/README.md). It is not included in the npm package.
|
|
162
192
|
|
|
163
193
|
## Versioning
|
|
164
194
|
|
|
@@ -167,7 +197,15 @@ Version bumps are derived from your commit messages (Conventional Commits) by a
|
|
|
167
197
|
- `feat:` → minor · `fix:` / `perf:` → patch · `feat!:` / `BREAKING CHANGE` → major (capped to minor while pre-1.0)
|
|
168
198
|
- `docs:` / `chore:` / `refactor:` / `style:` / `test:` / `ci:` / `build:` → no version change
|
|
169
199
|
|
|
170
|
-
To set an exact version deliberately (a re-baseline, or `1.0.0`), edit `package.json` and
|
|
200
|
+
To set an exact version deliberately (a re-baseline, a release candidate, or `1.0.0`), edit `package.json` and the `CHANGELOG.md` entry, then commit with `SKIP_POST_COMMIT=1` so the hook doesn't re-bump. While `package.json` holds a prerelease version (for example `0.17.0-rc.0`), the hook never bumps.
|
|
201
|
+
|
|
202
|
+
Releases go through a release candidate:
|
|
203
|
+
|
|
204
|
+
1. Set `X.Y.0-rc.N`, commit with `SKIP_POST_COMMIT=1`, and publish a GitHub **pre-release** for tag `vX.Y.0-rc.N`. `.github/workflows/publish.yml` publishes it to the `next` dist-tag.
|
|
205
|
+
2. Verify the published candidate; fix and cut `rc.N+1` if needed.
|
|
206
|
+
3. Set `X.Y.0`, commit with `SKIP_POST_COMMIT=1`, and publish a normal GitHub Release for `vX.Y.0`. It goes to `latest`.
|
|
207
|
+
|
|
208
|
+
Publishing uses npm trusted publishing (OIDC): there is no npm token secret, and provenance is automatic. Before publishing, the workflow runs typecheck, lint and tests, strips the dev-only `prepare` script, then runs `bun run check:pack --publish` (no install scripts, no shell or environment access in shipped code, no `docs/`) and `bun run check:smoke` (installs the packed tarball and runs `claude-toolkit refresh`). `.github/workflows/ci.yml` runs typecheck, lint, tests, `check:pack` and `check:smoke` on every push and pull request.
|
|
171
209
|
|
|
172
210
|
## Development
|
|
173
211
|
|
|
@@ -175,4 +213,7 @@ To set an exact version deliberately (a re-baseline, or `1.0.0`), edit `package.
|
|
|
175
213
|
git clone https://github.com/william-queant/claude-toolkit.git
|
|
176
214
|
cd claude-toolkit
|
|
177
215
|
bun install
|
|
216
|
+
bun test # test suite
|
|
217
|
+
bun run check:pack # tarball guard
|
|
218
|
+
bun run check:smoke # pack, install into a temp project, run refresh
|
|
178
219
|
```
|
package/bin/cli.ts
CHANGED
|
@@ -6,24 +6,34 @@
|
|
|
6
6
|
* One idempotent command does it all:
|
|
7
7
|
* bunx claude-toolkit Create the config if missing, then (re)generate .claude/
|
|
8
8
|
* bunx claude-toolkit --update Also pull newly-detected stacks into the config
|
|
9
|
+
* bunx claude-toolkit refresh Regenerate .claude/ only if the toolkit version changed
|
|
9
10
|
*
|
|
10
11
|
* Aliases / back-compat:
|
|
11
12
|
* init Friendly name for the first run (same as the bare command)
|
|
12
13
|
* update Same as `--update`
|
|
13
14
|
* sync Deprecated alias of the bare command
|
|
14
15
|
*
|
|
15
|
-
*
|
|
16
|
-
* toolkit
|
|
16
|
+
* Nothing runs at install time. Consumers keep .claude/ in sync by adding
|
|
17
|
+
* "prepare": "claude-toolkit refresh || exit 0" to their own package.json scripts;
|
|
18
|
+
* the bare command prints that hint until they do.
|
|
17
19
|
*/
|
|
18
20
|
|
|
19
21
|
import { existsSync } from "node:fs";
|
|
20
22
|
import { readFile, writeFile } from "node:fs/promises";
|
|
21
|
-
import { join, resolve } from "node:path";
|
|
23
|
+
import { basename, join, resolve } from "node:path";
|
|
22
24
|
import { detectStacks } from "../src/detect.js";
|
|
23
|
-
import { generate } from "../src/generator.js";
|
|
25
|
+
import { generate, readMarker, readToolkitVersion } from "../src/generator.js";
|
|
26
|
+
import { buildRefreshHint } from "../src/refresh-hint.js";
|
|
24
27
|
import type { ClaudeToolkitConfig } from "../src/types.js";
|
|
25
28
|
|
|
26
29
|
const CONFIG_FILENAME = "claude-toolkit.config.ts";
|
|
30
|
+
/** Config files `loadConfig` and `refresh` accept, in lookup order. */
|
|
31
|
+
const CONFIG_FILENAMES = [CONFIG_FILENAME, "claude-toolkit.config.js"];
|
|
32
|
+
|
|
33
|
+
/** Path of the project's config file, or null when there is none. */
|
|
34
|
+
function findConfigPath(projectDir: string): string | null {
|
|
35
|
+
return CONFIG_FILENAMES.map((name) => join(projectDir, name)).find((p) => existsSync(p)) ?? null;
|
|
36
|
+
}
|
|
27
37
|
|
|
28
38
|
/** Build a `stacks: [...]` literal for injection into the config file. */
|
|
29
39
|
function buildStacksLiteral(stacks: string[]): string {
|
|
@@ -44,16 +54,16 @@ async function updateConfigStacks(configPath: string, stacks: string[]): Promise
|
|
|
44
54
|
}
|
|
45
55
|
|
|
46
56
|
async function loadConfig(projectDir: string): Promise<ClaudeToolkitConfig> {
|
|
47
|
-
const configPath =
|
|
48
|
-
if (
|
|
49
|
-
throw new Error(`Config not found
|
|
57
|
+
const configPath = findConfigPath(projectDir);
|
|
58
|
+
if (configPath === null) {
|
|
59
|
+
throw new Error(`Config not found in ${projectDir}\nRun "bunx claude-toolkit" first.`);
|
|
50
60
|
}
|
|
51
61
|
let mod: { default?: ClaudeToolkitConfig };
|
|
52
62
|
try {
|
|
53
63
|
mod = await import(configPath);
|
|
54
64
|
} catch (err) {
|
|
55
65
|
throw new Error(
|
|
56
|
-
`Failed to load ${
|
|
66
|
+
`Failed to load ${basename(configPath)}. Make sure claude-toolkit is installed in this project ` +
|
|
57
67
|
`(e.g. "bun add -d claude-toolkit"), then run "bunx claude-toolkit" again.\n ${(err as Error).message}`,
|
|
58
68
|
);
|
|
59
69
|
}
|
|
@@ -104,11 +114,14 @@ interface RunOptions {
|
|
|
104
114
|
async function run(projectDir: string, options: RunOptions = {}): Promise<void> {
|
|
105
115
|
const { update = false, quiet = false } = options;
|
|
106
116
|
const log = quiet ? (_msg = "") => {} : (msg = "") => console.log(msg);
|
|
107
|
-
const
|
|
117
|
+
const existingConfig = findConfigPath(projectDir);
|
|
118
|
+
const configPath = existingConfig ?? join(projectDir, CONFIG_FILENAME);
|
|
119
|
+
// Read before generate() rewrites it: a pre-0.17 marker means this run is an upgrade.
|
|
120
|
+
const previousVersion = await readMarker(projectDir);
|
|
108
121
|
|
|
109
122
|
let config: ClaudeToolkitConfig;
|
|
110
123
|
|
|
111
|
-
if (
|
|
124
|
+
if (existingConfig === null) {
|
|
112
125
|
// First run: create the config from what we detect.
|
|
113
126
|
const detected = detectStacks(projectDir);
|
|
114
127
|
if (detected.length > 0) {
|
|
@@ -175,15 +188,52 @@ async function run(projectDir: string, options: RunOptions = {}): Promise<void>
|
|
|
175
188
|
|
|
176
189
|
await generate(projectDir, config, { quiet });
|
|
177
190
|
log("Done.");
|
|
191
|
+
if (!quiet) await printRefreshHint(projectDir, config.packageManager, previousVersion);
|
|
178
192
|
}
|
|
179
193
|
|
|
180
|
-
/**
|
|
181
|
-
|
|
182
|
-
|
|
194
|
+
/**
|
|
195
|
+
* Print the "keep .claude/ in sync" hint unless package.json already runs
|
|
196
|
+
* `claude-toolkit refresh`. Only prints: package.json is a committed file.
|
|
197
|
+
*/
|
|
198
|
+
async function printRefreshHint(
|
|
199
|
+
projectDir: string,
|
|
200
|
+
packageManager: ClaudeToolkitConfig["packageManager"],
|
|
201
|
+
previousVersion: string | null,
|
|
202
|
+
): Promise<void> {
|
|
203
|
+
const pkgPath = join(projectDir, "package.json");
|
|
204
|
+
if (!existsSync(pkgPath)) return;
|
|
205
|
+
let scripts: Record<string, unknown>;
|
|
206
|
+
try {
|
|
207
|
+
scripts = JSON.parse(await readFile(pkgPath, "utf-8")).scripts ?? {};
|
|
208
|
+
} catch {
|
|
209
|
+
return; // an unparsable package.json is not worth failing the run over
|
|
210
|
+
}
|
|
211
|
+
const lines = buildRefreshHint({
|
|
212
|
+
scripts,
|
|
213
|
+
packageManager,
|
|
214
|
+
yarnBerry: existsSync(join(projectDir, ".yarnrc.yml")),
|
|
215
|
+
previousVersion,
|
|
216
|
+
});
|
|
217
|
+
if (lines.length > 0) console.log(`\n${lines.join("\n")}`);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* refresh: regenerate .claude/ when the installed toolkit version differs from the
|
|
222
|
+
* one recorded in .claude/.toolkit-version. Built for the consumer's own "prepare"
|
|
223
|
+
* script: it never creates the config, never scaffolds committed files, and the
|
|
224
|
+
* up-to-date path returns before the config is imported.
|
|
225
|
+
*/
|
|
226
|
+
async function refresh(projectDir: string, quiet: boolean): Promise<void> {
|
|
227
|
+
const log = quiet ? (_msg = "") => {} : (msg = "") => console.log(msg);
|
|
228
|
+
if (findConfigPath(projectDir) === null) {
|
|
229
|
+
log('[claude-toolkit] No config found — run "bunx claude-toolkit" to set up .claude/.');
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
const version = await readToolkitVersion();
|
|
233
|
+
if ((await readMarker(projectDir)) === version) return;
|
|
183
234
|
const config = await loadConfig(projectDir);
|
|
184
|
-
// scaffold:false — never write committed project files (biome.json/tsconfig.json) on install.
|
|
185
235
|
await generate(projectDir, config, { quiet: true, scaffold: false });
|
|
186
|
-
|
|
236
|
+
log(`[claude-toolkit] Regenerated .claude/ for toolkit ${version}.`);
|
|
187
237
|
}
|
|
188
238
|
|
|
189
239
|
const HELP = `
|
|
@@ -193,6 +243,10 @@ Usage:
|
|
|
193
243
|
bunx claude-toolkit [project-dir] Create config if missing, then regenerate .claude/
|
|
194
244
|
bunx claude-toolkit --update [project-dir] Also add newly-detected stacks to the config
|
|
195
245
|
|
|
246
|
+
Commands:
|
|
247
|
+
refresh [project-dir] Regenerate .claude/ only if the toolkit version changed.
|
|
248
|
+
Never creates the config; meant for a "prepare" script.
|
|
249
|
+
|
|
196
250
|
Commands (aliases):
|
|
197
251
|
init First-run friendly name (same as the bare command)
|
|
198
252
|
update Same as --update
|
|
@@ -203,14 +257,16 @@ Flags:
|
|
|
203
257
|
--update, -u Add newly-detected stacks to the config before regenerating
|
|
204
258
|
--quiet, -q Suppress informational output
|
|
205
259
|
|
|
206
|
-
.
|
|
260
|
+
Nothing runs at install time. To keep .claude/ in sync after upgrades, add to
|
|
261
|
+
your package.json "scripts":
|
|
262
|
+
"prepare": "claude-toolkit refresh || exit 0"
|
|
207
263
|
`;
|
|
208
264
|
|
|
209
265
|
// ---- entry ----
|
|
210
266
|
const argv = process.argv.slice(2);
|
|
211
267
|
const flags = new Set(argv.filter((a) => a.startsWith("-")));
|
|
212
268
|
const positional = argv.filter((a) => !a.startsWith("-"));
|
|
213
|
-
const COMMANDS = new Set(["init", "update", "sync", "help", "
|
|
269
|
+
const COMMANDS = new Set(["init", "update", "sync", "help", "refresh"]);
|
|
214
270
|
const KNOWN_FLAGS = new Set(["--update", "-u", "--quiet", "-q"]);
|
|
215
271
|
|
|
216
272
|
// Reject unknown flags so a typo'd flag (e.g. --updat) isn't silently ignored.
|
|
@@ -248,8 +304,8 @@ try {
|
|
|
248
304
|
case "help":
|
|
249
305
|
console.log(HELP);
|
|
250
306
|
break;
|
|
251
|
-
case "
|
|
252
|
-
await
|
|
307
|
+
case "refresh":
|
|
308
|
+
await refresh(projectDir, quiet);
|
|
253
309
|
break;
|
|
254
310
|
case "sync":
|
|
255
311
|
if (!quiet) {
|
|
@@ -1,12 +1,70 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: ct-typescript-conventions
|
|
3
|
-
description: Strict TypeScript patterns
|
|
3
|
+
description: Strict TypeScript patterns and compiler selection -- prefer the TypeScript 7 native compiler when a project ships it. Use when writing or reviewing TypeScript, running a typecheck, adding or tightening types, or deciding between type and interface
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# TypeScript Conventions
|
|
7
7
|
|
|
8
8
|
Strict mode enabled. Catch errors at compile time, not runtime.
|
|
9
9
|
|
|
10
|
+
## Compiler -- prefer TypeScript 7 when the project has it
|
|
11
|
+
|
|
12
|
+
TypeScript 7 (GA 2026-07-08, `typescript@7.0.2`) is the Go-native compiler; Microsoft reports **8x--12x faster full builds**. In a project that already ships it, use it for every type-check and build. Never install or bump it unprompted.
|
|
13
|
+
|
|
14
|
+
**Detect the installed version, not the declared range:**
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
node -p "require('typescript/package.json').version" # major >= 7 -> TS7
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Resolve from the directory you are editing -- monorepo workspaces can pin different majors. Three traps:
|
|
21
|
+
|
|
22
|
+
- The TS7 binary is **`tsc`**. `tsgo` was the pre-GA `@typescript/native-preview` name -- never put it in a script.
|
|
23
|
+
- `package.json` ranges lie: `overrides`, `resolutions`, catalogs, and stale lockfiles all win, and `*` / `latest` / `>=5` resolve to 7.x today.
|
|
24
|
+
- Don't probe the API -- on TS7 `require("typescript")` exposes only `version` and `versionMajorMinor`, so a `ts.createProgram` check gives a false negative.
|
|
25
|
+
|
|
26
|
+
**Using it** -- same flags as 6.x:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
tsc --noEmit # type-check
|
|
30
|
+
tsc -b # build mode / project references
|
|
31
|
+
tsc --checkers 1 --noEmit # CI: pin the worker count
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
`--checkers` defaults to 4. Pin it in CI -- Microsoft warns that varying it can surface order-dependent results.
|
|
35
|
+
|
|
36
|
+
**Stay on 6.x when a tool embeds the compiler.** TS7.0 ships no stable programmatic API (7.1 targets a new one), so `typescript-eslint`, `ts-jest`, `ts-node`, `vue-tsc`, `svelte-check`, `@astrojs/check`, and `@angular/compiler-cli` all break -- at install or lint time, *not* at `tsc --noEmit`. A green type-check proves nothing here. Vite, Bun, `tsx`, Vitest, and Biome are unaffected. Run both rather than downgrading:
|
|
37
|
+
|
|
38
|
+
```json
|
|
39
|
+
{
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@typescript/native": "npm:typescript@^7.0.2",
|
|
42
|
+
"typescript": "npm:@typescript/typescript6@^6.0.2"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Keep `typescript` pointed at the 6.0 package -- blocked tools import it by peer dependency. Where TS7 is absent, the floor is `typescript@6.0.3`, the bridge release; its readiness gate is a clean build with `"stableTypeOrdering": true` and no `ignoreDeprecations`.
|
|
48
|
+
|
|
49
|
+
**`tsconfig.json` rules TS7 enforces** -- `ignoreDeprecations` no longer silences any of them:
|
|
50
|
+
|
|
51
|
+
| Now rejected | Fix |
|
|
52
|
+
|---|---|
|
|
53
|
+
| `baseUrl` | project-root-relative `paths` |
|
|
54
|
+
| `outFile`, `downlevelIteration` | remove |
|
|
55
|
+
| `target: "es5"` | `"es2015"` or later |
|
|
56
|
+
| `module: "amd" \| "umd" \| "system" \| "none" \| "systemjs"` | `"esnext"`, `"preserve"`, or `"nodenext"` |
|
|
57
|
+
| `moduleResolution: "node" \| "node10" \| "classic"` | `"bundler"`, `"node16"`, or `"nodenext"` |
|
|
58
|
+
| `esModuleInterop` / `allowSyntheticDefaultImports` / `alwaysStrict` set to `false` | drop the override |
|
|
59
|
+
| `out`, `charset`, `keyofStringsOnly`, `importsNotUsedAsValues`, `preserveValueImports`, `suppressImplicitAnyIndexErrors`, `suppressExcessPropertyErrors`, `noStrictGenericChecks`, `noImplicitUseStrict` | gone -- `TS5023: Unknown compiler option` |
|
|
60
|
+
|
|
61
|
+
Two changed defaults cause most real-world breakage. Both arrived in 6.0 and carry forward, so they bite on either major -- verified identical on 6.0.3 and 7.0.2:
|
|
62
|
+
|
|
63
|
+
- **`types` defaults to `[]`** -- `process`, `Bun` and friends disappear (`TS2591`) *even with `@types/node` installed*. Set `"types": ["node"]` / `["bun"]` explicitly, naming only packages that are actually installed (a missing one is `TS2688`). `"types": ["*"]` restores the old catch-all on 6.x/7.x but is itself `TS2688` on 5.x.
|
|
64
|
+
- **`rootDir` defaults to `./`** -- with sources in `src/` and an `outDir`, emit fails `TS5011`. Set `"rootDir": "./src"`. `--noEmit` does *not* surface this, so type-check CI stays green while the build breaks.
|
|
65
|
+
|
|
66
|
+
`strict` and `stableTypeOrdering` are on by default; `stableTypeOrdering` cannot be turned off.
|
|
67
|
+
|
|
10
68
|
## Rules
|
|
11
69
|
|
|
12
70
|
- **No `any`** -- Use `unknown` + narrowing, generics, or specific types.
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-toolkit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0-rc.1",
|
|
4
4
|
"description": "Reusable Claude Code configuration toolkit with stack-specific connectors",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"claude-toolkit": "
|
|
7
|
+
"claude-toolkit": "bin/cli.ts"
|
|
8
8
|
},
|
|
9
9
|
"exports": {
|
|
10
10
|
".": {
|
|
@@ -18,7 +18,6 @@
|
|
|
18
18
|
"core/",
|
|
19
19
|
"stacks/",
|
|
20
20
|
"templates/",
|
|
21
|
-
"docs/",
|
|
22
21
|
"CHANGELOG.md"
|
|
23
22
|
],
|
|
24
23
|
"scripts": {
|
|
@@ -26,8 +25,8 @@
|
|
|
26
25
|
"lint": "biome check --write",
|
|
27
26
|
"lint:check": "biome check",
|
|
28
27
|
"test": "bun test",
|
|
29
|
-
"
|
|
30
|
-
"
|
|
28
|
+
"check:pack": "bun scripts/check-pack.ts",
|
|
29
|
+
"check:smoke": "bun scripts/check-smoke.ts"
|
|
31
30
|
},
|
|
32
31
|
"lint-staged": {
|
|
33
32
|
"*.{ts,tsx,js,jsx,json,css}": "biome check --write --no-errors-on-unmatched"
|
|
@@ -44,11 +43,11 @@
|
|
|
44
43
|
"license": "MIT",
|
|
45
44
|
"repository": {
|
|
46
45
|
"type": "git",
|
|
47
|
-
"url": "https://github.com/william-queant/claude-toolkit.git"
|
|
46
|
+
"url": "git+https://github.com/william-queant/claude-toolkit.git"
|
|
48
47
|
},
|
|
49
48
|
"devDependencies": {
|
|
50
49
|
"@biomejs/biome": "^2.4.10",
|
|
51
|
-
"@types/bun": "
|
|
50
|
+
"@types/bun": "^1.3.11",
|
|
52
51
|
"husky": "^9.1.7",
|
|
53
52
|
"lint-staged": "^16.4.0",
|
|
54
53
|
"typescript": "^6.0.2"
|
package/src/generator.ts
CHANGED
|
@@ -1,12 +1,58 @@
|
|
|
1
|
-
import { copyFile as fsCopyFile, readdir } from "node:fs/promises";
|
|
1
|
+
import { copyFile as fsCopyFile, readdir, readFile } from "node:fs/promises";
|
|
2
2
|
import { join, resolve } from "node:path";
|
|
3
3
|
import type { ClaudeToolkitConfig, ResolvedConfig, StackPack } from "./types.js";
|
|
4
4
|
import { copyDir, exists, readJson, removePath, writeFileEnsureDir } from "./utils.js";
|
|
5
5
|
|
|
6
6
|
const TOOLKIT_ROOT = resolve(import.meta.dirname, "..");
|
|
7
7
|
|
|
8
|
+
/** Branches protected when the config sets no `git.protectedBranches`. */
|
|
9
|
+
const DEFAULT_PROTECTED_BRANCHES = ["main"];
|
|
10
|
+
/** A protected branch name must match this before it is quoted into the guard's `case` pattern. */
|
|
11
|
+
const BRANCH_NAME_PATTERN = /^[A-Za-z0-9._/][A-Za-z0-9._/-]*$/;
|
|
12
|
+
|
|
13
|
+
/** Throw a clear error naming the first protected branch that fails BRANCH_NAME_PATTERN. */
|
|
14
|
+
function assertValidBranchNames(branches: string[]): void {
|
|
15
|
+
const invalid = branches.find((b) => !BRANCH_NAME_PATTERN.test(b));
|
|
16
|
+
if (invalid === undefined) return;
|
|
17
|
+
throw new Error(
|
|
18
|
+
`Invalid branch name in git.protectedBranches: ${JSON.stringify(invalid)}. ` +
|
|
19
|
+
'Use only letters, digits, ".", "_", "/" and "-", and do not start with "-".',
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The PreToolUse command that blocks edits on a protected branch, or null when no
|
|
25
|
+
* branch is protected. Names are validated first, then single-quoted into a `case`
|
|
26
|
+
* pattern, so each name is matched literally and never parsed by the shell.
|
|
27
|
+
*/
|
|
28
|
+
function buildBranchGuard(branches: string[]): string | null {
|
|
29
|
+
assertValidBranchNames(branches);
|
|
30
|
+
if (branches.length === 0) return null;
|
|
31
|
+
const patterns = branches.map((b) => `'${b}'`).join("|");
|
|
32
|
+
return `# Prevent editing on protected branches\ncase "$(git branch --show-current)" in ${patterns}) echo '{"block": true, "message": "Cannot edit files on a protected branch. Create a feature branch first."}' >&2; exit 2;; esac`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** File in .claude/ recording the toolkit version that last generated it. */
|
|
36
|
+
const MARKER_FILE = ".toolkit-version";
|
|
37
|
+
|
|
38
|
+
/** The installed toolkit version, read from the toolkit's own package.json. */
|
|
39
|
+
export async function readToolkitVersion(): Promise<string> {
|
|
40
|
+
const { version } = await readJson<{ version: string }>(join(TOOLKIT_ROOT, "package.json"));
|
|
41
|
+
return version;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** The version recorded in `<projectDir>/.claude/.toolkit-version`, or null when absent. */
|
|
45
|
+
export async function readMarker(projectDir: string): Promise<string | null> {
|
|
46
|
+
const markerPath = join(projectDir, ".claude", MARKER_FILE);
|
|
47
|
+
if (!exists(markerPath)) return null;
|
|
48
|
+
return (await readFile(markerPath, "utf-8")).trim();
|
|
49
|
+
}
|
|
50
|
+
|
|
8
51
|
/** Resolve the full configuration by merging core + stacks + project config */
|
|
9
52
|
async function resolveConfig(config: ClaudeToolkitConfig): Promise<ResolvedConfig> {
|
|
53
|
+
// Fail before anything is written when a branch name is unsafe to quote into a hook.
|
|
54
|
+
assertValidBranchNames(config.git?.protectedBranches ?? DEFAULT_PROTECTED_BRANCHES);
|
|
55
|
+
|
|
10
56
|
const stacks: StackPack[] = [];
|
|
11
57
|
const allMappings: Record<string, string> = {};
|
|
12
58
|
|
|
@@ -79,10 +125,8 @@ async function removeGenerated(claudeDir: string): Promise<void> {
|
|
|
79
125
|
// Toolkit hook files (copied from core/hooks) + the generated skill-rules.json.
|
|
80
126
|
...hookFiles.map((f) => removePath(join(claudeDir, "hooks", f))),
|
|
81
127
|
removePath(join(claudeDir, "hooks", "skill-rules.json")),
|
|
82
|
-
// Stale
|
|
83
|
-
// skill-eval.sh wrapper was dropped in favour of exec-form node registration.
|
|
128
|
+
// Stale file from prior installs: skill-eval.js was renamed to .cjs.
|
|
84
129
|
removePath(join(claudeDir, "hooks", "skill-eval.js")),
|
|
85
|
-
removePath(join(claudeDir, "hooks", "skill-eval.sh")),
|
|
86
130
|
]);
|
|
87
131
|
}
|
|
88
132
|
|
|
@@ -153,8 +197,8 @@ export async function generate(
|
|
|
153
197
|
].join("\n"),
|
|
154
198
|
);
|
|
155
199
|
|
|
156
|
-
// 9. Scaffold base configs into the project root (committed files). Skipped
|
|
157
|
-
//
|
|
200
|
+
// 9. Scaffold base configs into the project root (committed files). Skipped by
|
|
201
|
+
// `refresh` (scaffold: false), which must never write committed files.
|
|
158
202
|
if (options.scaffold !== false) {
|
|
159
203
|
await scaffoldConfigs(projectDir, resolved, options.quiet);
|
|
160
204
|
}
|
|
@@ -162,9 +206,9 @@ export async function generate(
|
|
|
162
206
|
// 10. Generate skills README
|
|
163
207
|
await generateSkillsReadme(claudeDir, resolved);
|
|
164
208
|
|
|
165
|
-
// Record the toolkit version that produced this output —
|
|
166
|
-
const
|
|
167
|
-
await writeFileEnsureDir(join(claudeDir,
|
|
209
|
+
// Record the toolkit version that produced this output — `refresh` compares against it.
|
|
210
|
+
const version = await readToolkitVersion();
|
|
211
|
+
await writeFileEnsureDir(join(claudeDir, MARKER_FILE), `${version}\n`);
|
|
168
212
|
|
|
169
213
|
if (!options.quiet) {
|
|
170
214
|
console.log(
|
|
@@ -230,11 +274,7 @@ async function generateSkillRules(claudeDir: string, resolved: ResolvedConfig):
|
|
|
230
274
|
/** Generate settings.json with hooks */
|
|
231
275
|
async function generateSettings(claudeDir: string, resolved: ResolvedConfig): Promise<void> {
|
|
232
276
|
const { hooks, config } = resolved;
|
|
233
|
-
const
|
|
234
|
-
|
|
235
|
-
const branchCheck = protectedBranches
|
|
236
|
-
.map((b) => `"$(git branch --show-current)" != "${b}"`)
|
|
237
|
-
.join(" && ");
|
|
277
|
+
const branchGuard = buildBranchGuard(config.git?.protectedBranches ?? DEFAULT_PROTECTED_BRANCHES);
|
|
238
278
|
|
|
239
279
|
const postToolUseHooks: unknown[] = [];
|
|
240
280
|
|
|
@@ -313,18 +353,16 @@ async function generateSettings(claudeDir: string, resolved: ResolvedConfig): Pr
|
|
|
313
353
|
],
|
|
314
354
|
},
|
|
315
355
|
],
|
|
316
|
-
|
|
317
|
-
{
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
},
|
|
327
|
-
],
|
|
356
|
+
...(branchGuard === null
|
|
357
|
+
? {}
|
|
358
|
+
: {
|
|
359
|
+
PreToolUse: [
|
|
360
|
+
{
|
|
361
|
+
matcher: "Edit|MultiEdit|Write",
|
|
362
|
+
hooks: [{ type: "command", command: branchGuard, timeout: 5 }],
|
|
363
|
+
},
|
|
364
|
+
],
|
|
365
|
+
}),
|
|
328
366
|
PostToolUse: postToolUseHooks.map((hook) => ({
|
|
329
367
|
matcher: "Edit|MultiEdit|Write",
|
|
330
368
|
hooks: [hook],
|
|
@@ -373,7 +411,8 @@ async function generateSkillsReadme(claudeDir: string, resolved: ResolvedConfig)
|
|
|
373
411
|
content +=
|
|
374
412
|
"| ct-systematic-debugging | Four-phase debugging methodology, root cause analysis |\n";
|
|
375
413
|
content += "| ct-testing-patterns | Test-driven development workflow and patterns |\n";
|
|
376
|
-
content +=
|
|
414
|
+
content +=
|
|
415
|
+
"| ct-typescript-conventions | TypeScript strict mode, conventions, and TS7 compiler selection |\n";
|
|
377
416
|
content += "| ct-verification-before-completion | Evidence-based completion claims protocol |\n";
|
|
378
417
|
content +=
|
|
379
418
|
"| ct-code-style | Code structure & style: guard clauses, lookup tables, no magic values |\n";
|