Skip to content

Commit 2ea7589

Browse files
committed
feat(profiles): ci + rust + settings.local.json guidance
Three additions you flagged that the v0.6.0 ship missed: - **ci** profile: CI-runner-tuned (permissive on toolchains, strict on anything that mutates remote state). Explicitly denies `gh release create`, `gh repo create`, every publish command, force-push variants. Use when Claude Code runs inside GitHub Actions / GitLab CI. - **rust** profile: cargo + rustfmt + clippy + cargo-audit + cargo-nextest. Denies `cargo publish` / `yank` / `login`. - **mixed** updated: includes Rust (cargo, rustc, rustfmt, rustup, clippy-driver) and Ruby (ruby, bundle, rake, rspec, rubocop) toolchains in allow; cargo publish + yank + gem push in deny. Plus the local-override convention that was missing from docs: - `init` now writes `settings.local.json` + `**/settings.local.json` to the content dir's .gitignore by default - docs/profiles.md gets a "settings.local.json — personal overrides" section explaining the merge semantics - docs/profiles.md gets a "Verifying" section pointing at `claude config list` 8 profiles in `profiles list` now (was 6). 294 tests still pass.
1 parent a68e64e commit 2ea7589

5 files changed

Lines changed: 320 additions & 2 deletions

File tree

docs/profiles.md

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,47 @@ de-duplicates while preserving order.
2020
| `laravel` | project | Composer + artisan + vendor/bin (Pest, PHPUnit, Pint, PHPStan, Rector), Sail. Denies `migrate:fresh` + `db:wipe`. |
2121
| `node` | project | npm/pnpm/yarn/bun, TS toolchain, framework CLIs (next, vite, astro), Vitest/Playwright. |
2222
| `go` | project | go toolchain, gofmt/goimports, golangci-lint, govulncheck. |
23-
| `mixed` | project | **Default.** Everything: Python + PHP + JS + Go + network diagnostics + Docker. |
23+
| `rust` | project | cargo + rustfmt + clippy + cargo-audit. Denies `cargo publish` + `cargo yank` + `cargo login`. |
24+
| `ci` | ci | For CI runners (GitHub Actions / GitLab CI). Permissive on toolchains; strict on anything that mutates remote state (denies `gh release create`, `gh repo create`, all publish commands, force-push). |
25+
| `mixed` | project | **Default.** Everything: Python + PHP + JS + Go + Rust + Ruby + network diagnostics + Docker. |
2426

2527
Every project profile extends `global`, so the baseline read/inspect
2628
permissions always apply.
2729

30+
## settings.local.json — personal overrides
31+
32+
Claude Code reads both `settings.json` (committed) and
33+
`settings.local.json` (gitignored) and merges per-key. The local
34+
file is for personal preferences that shouldn't be shared:
35+
36+
```bash
37+
# .gitignore (ai-config-kit init adds this for you)
38+
settings.local.json
39+
**/settings.local.json
40+
```
41+
42+
Use cases for local overrides:
43+
44+
- Allow specific commands you trust on YOUR machine but the team
45+
doesn't (e.g., `Bash(my-deploy-tool:*)`).
46+
- Bind a personal API token reference: `"env": {"MY_KEY": "..."}`.
47+
- Tighter denies than the team default.
48+
49+
Never put secrets in `settings.local.json` either — Claude Code
50+
treats it as configuration, not a secret store.
51+
52+
## Verifying
53+
54+
After `profiles apply --apply`, confirm the merged effective config:
55+
56+
```bash
57+
claude config list
58+
```
59+
60+
This prints the resolved JSON (your profile + any
61+
`settings.local.json` overrides on top). Look for your profile's
62+
allow patterns and the global denies you expected.
63+
2864
## Usage
2965

3066
```bash

src/ai_config_kit/manager.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1517,7 +1517,10 @@ def init(
15171517
+ "\n".join(f"**/{p}" for p in self._secrets)
15181518
+ "\n\n# Local noise\n"
15191519
+ "\n".join(self._ignores)
1520-
+ "\n",
1520+
+ "\n\n# Personal overrides (Claude Code reads both files,\n"
1521+
+ "# settings.local.json wins per-key; never committed):\n"
1522+
+ "settings.local.json\n"
1523+
+ "**/settings.local.json\n",
15211524
encoding="utf-8",
15221525
)
15231526

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
{
2+
"_meta": {
3+
"name": "ci",
4+
"summary": "CI runner profile — for use inside GitHub Actions / GitLab CI. Permissive on language toolchains + git diagnostics; STRICTER deny than project profiles on anything that could mutate remote state outside the controlled CI flow.",
5+
"scope_hint": "ci",
6+
"extends": ["global"]
7+
},
8+
"permissions": {
9+
"allow": [
10+
"Edit",
11+
"Write",
12+
"MultiEdit",
13+
14+
"Bash(git add:*)",
15+
"Bash(git commit:*)",
16+
"Bash(git checkout:*)",
17+
"Bash(git switch:*)",
18+
"Bash(git restore:*)",
19+
"Bash(git pull:*)",
20+
"Bash(git merge:*)",
21+
"Bash(git rebase:*)",
22+
"Bash(git stash:*)",
23+
"Bash(git mv:*)",
24+
"Bash(git rm:*)",
25+
"Bash(git tag:*)",
26+
"Bash(git config user.*)",
27+
28+
"Bash(gh pr view:*)",
29+
"Bash(gh pr checks:*)",
30+
"Bash(gh run view:*)",
31+
"Bash(gh api:*)",
32+
"Bash(gh release upload:*)",
33+
"Bash(gh release view:*)",
34+
35+
"Bash(python:*)",
36+
"Bash(python3:*)",
37+
"Bash(pip:*)",
38+
"Bash(uv:*)",
39+
"Bash(pytest:*)",
40+
"Bash(coverage:*)",
41+
"Bash(ruff:*)",
42+
"Bash(mypy:*)",
43+
"Bash(pip-audit:*)",
44+
"Bash(python -m:*)",
45+
46+
"Bash(php:*)",
47+
"Bash(composer:*)",
48+
"Bash(./vendor/bin/*:*)",
49+
"Bash(vendor/bin/*:*)",
50+
51+
"Bash(npm:*)",
52+
"Bash(npm ci:*)",
53+
"Bash(npx:*)",
54+
"Bash(pnpm:*)",
55+
"Bash(yarn:*)",
56+
"Bash(node:*)",
57+
"Bash(tsc:*)",
58+
"Bash(eslint:*)",
59+
"Bash(prettier:*)",
60+
"Bash(vitest:*)",
61+
"Bash(jest:*)",
62+
63+
"Bash(go:*)",
64+
"Bash(gofmt:*)",
65+
"Bash(goimports:*)",
66+
"Bash(golangci-lint:*)",
67+
"Bash(govulncheck:*)",
68+
69+
"Bash(cargo:*)",
70+
"Bash(rustc:*)",
71+
"Bash(rustfmt:*)",
72+
"Bash(clippy-driver:*)",
73+
"Bash(cargo-audit:*)",
74+
75+
"Bash(make:*)",
76+
"Bash(./scripts/*:*)",
77+
"Bash(bash scripts/*:*)",
78+
"Bash(sh scripts/*:*)",
79+
80+
"Bash(pre-commit:*)",
81+
"Bash(shellcheck:*)",
82+
"Bash(shfmt:*)",
83+
"Bash(detect-secrets:*)",
84+
"Bash(gitleaks:*)",
85+
"Bash(actionlint:*)",
86+
"Bash(yamllint:*)",
87+
"Bash(hadolint:*)",
88+
"Bash(markdownlint:*)",
89+
"Bash(lychee:*)",
90+
91+
"Bash(docker build:*)",
92+
"Bash(docker pull:*)",
93+
"Bash(docker tag:*)",
94+
"Bash(docker compose config:*)",
95+
96+
"Bash(cp:*)",
97+
"Bash(mv:*)",
98+
"Bash(ln:*)",
99+
"Bash(chmod:*)",
100+
"Bash(tar:*)",
101+
"Bash(unzip:*)",
102+
"Bash(zip:*)",
103+
"Bash(gzip:*)"
104+
],
105+
"deny": [
106+
"Bash(rm -rf .git:*)",
107+
"Bash(rm -rf .git)",
108+
109+
"Bash(git push:* --force)",
110+
"Bash(git push:* -f)",
111+
"Bash(git push -f:*)",
112+
"Bash(git push --force:*)",
113+
"Bash(git push:* --force-with-lease)",
114+
"Bash(git reset --hard:*)",
115+
"Bash(git clean -fdx:*)",
116+
"Bash(git tag -d:*)",
117+
"Bash(git push:* --delete:*)",
118+
119+
"Bash(gh pr create:*)",
120+
"Bash(gh pr merge:*)",
121+
"Bash(gh release create:*)",
122+
"Bash(gh release delete:*)",
123+
"Bash(gh repo create:*)",
124+
"Bash(gh repo edit:*)",
125+
"Bash(gh repo delete:*)",
126+
"Bash(gh workflow run:*)",
127+
"Bash(gh secret:*)",
128+
"Bash(gh variable:*)",
129+
130+
"Bash(twine upload:*)",
131+
"Bash(python -m twine upload:*)",
132+
"Bash(uv publish:*)",
133+
"Bash(poetry publish:*)",
134+
"Bash(hatch publish:*)",
135+
"Bash(pdm publish:*)",
136+
"Bash(npm publish:*)",
137+
"Bash(pnpm publish:*)",
138+
"Bash(yarn publish:*)",
139+
"Bash(bun publish:*)",
140+
"Bash(cargo publish:*)",
141+
"Bash(gem push:*)",
142+
"Bash(composer publish:*)",
143+
"Bash(docker push:*)",
144+
"Bash(goreleaser publish:*)",
145+
"Bash(goreleaser release:*)",
146+
147+
"Bash(php artisan migrate:fresh:*)",
148+
"Bash(php artisan migrate:reset:*)",
149+
"Bash(php artisan db:wipe:*)"
150+
]
151+
}
152+
}

src/ai_config_kit/resources/profiles/mixed.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,19 @@
8080
"Bash(golangci-lint:*)",
8181
"Bash(govulncheck:*)",
8282

83+
"Bash(cargo:*)",
84+
"Bash(rustc:*)",
85+
"Bash(rustfmt:*)",
86+
"Bash(rustup:*)",
87+
"Bash(clippy-driver:*)",
88+
89+
"Bash(ruby:*)",
90+
"Bash(bundle:*)",
91+
"Bash(bundle exec:*)",
92+
"Bash(rake:*)",
93+
"Bash(rspec:*)",
94+
"Bash(rubocop:*)",
95+
8396
"Bash(make:*)",
8497
"Bash(./scripts/*:*)",
8598
"Bash(bash scripts/*:*)",
@@ -158,6 +171,9 @@
158171
"Bash(bun publish:*)",
159172
"Bash(goreleaser publish:*)",
160173
"Bash(goreleaser release:*)",
174+
"Bash(cargo publish:*)",
175+
"Bash(cargo yank:*)",
176+
"Bash(gem push:*)",
161177

162178
"Bash(gh pr merge --auto:*)",
163179

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
{
2+
"_meta": {
3+
"name": "rust",
4+
"summary": "Rust project profile: cargo, rustfmt, clippy, cargo-audit, common build/test/lint flow. Extends `global` baseline.",
5+
"scope_hint": "project",
6+
"extends": ["global"]
7+
},
8+
"permissions": {
9+
"allow": [
10+
"Edit",
11+
"Write",
12+
"MultiEdit",
13+
14+
"Bash(git add:*)",
15+
"Bash(git commit:*)",
16+
"Bash(git checkout:*)",
17+
"Bash(git switch:*)",
18+
"Bash(git restore:*)",
19+
"Bash(git pull:*)",
20+
"Bash(git push)",
21+
"Bash(git push origin:*)",
22+
"Bash(git push --set-upstream:*)",
23+
"Bash(git push -u:*)",
24+
"Bash(git merge:*)",
25+
"Bash(git rebase:*)",
26+
"Bash(git stash:*)",
27+
"Bash(git mv:*)",
28+
"Bash(git rm:*)",
29+
"Bash(git cherry-pick:*)",
30+
"Bash(git revert:*)",
31+
"Bash(git remote add:*)",
32+
"Bash(git remote set-url:*)",
33+
"Bash(git init:*)",
34+
"Bash(git tag:*)",
35+
36+
"Bash(gh pr create:*)",
37+
"Bash(gh pr edit:*)",
38+
"Bash(gh pr comment:*)",
39+
"Bash(gh pr review:*)",
40+
"Bash(gh pr merge:*)",
41+
"Bash(gh issue create:*)",
42+
"Bash(gh issue edit:*)",
43+
"Bash(gh repo edit:*)",
44+
"Bash(gh workflow run:*)",
45+
46+
"Bash(cargo:*)",
47+
"Bash(cargo build:*)",
48+
"Bash(cargo test:*)",
49+
"Bash(cargo run:*)",
50+
"Bash(cargo check:*)",
51+
"Bash(cargo doc:*)",
52+
"Bash(cargo bench:*)",
53+
"Bash(cargo update:*)",
54+
"Bash(cargo add:*)",
55+
"Bash(cargo remove:*)",
56+
"Bash(cargo tree:*)",
57+
"Bash(cargo metadata:*)",
58+
"Bash(cargo install:*)",
59+
"Bash(cargo fmt:*)",
60+
"Bash(cargo clippy:*)",
61+
"Bash(cargo audit:*)",
62+
"Bash(cargo deny:*)",
63+
"Bash(cargo nextest:*)",
64+
"Bash(cargo expand:*)",
65+
"Bash(cargo machete:*)",
66+
67+
"Bash(rustc:*)",
68+
"Bash(rustfmt:*)",
69+
"Bash(rustup:*)",
70+
"Bash(clippy-driver:*)",
71+
72+
"Bash(make:*)",
73+
"Bash(just:*)",
74+
"Bash(./scripts/*:*)",
75+
"Bash(bash scripts/*:*)",
76+
77+
"Bash(pre-commit:*)",
78+
"Bash(shellcheck:*)",
79+
"Bash(detect-secrets:*)",
80+
"Bash(gitleaks:*)",
81+
82+
"Bash(docker build:*)",
83+
"Bash(docker run:*)",
84+
"Bash(docker exec:*)",
85+
"Bash(docker compose up:*)",
86+
"Bash(docker compose down:*)",
87+
"Bash(docker compose build:*)",
88+
"Bash(docker compose exec:*)",
89+
90+
"Bash(rm:*)",
91+
"Bash(cp:*)",
92+
"Bash(mv:*)",
93+
"Bash(ln:*)",
94+
"Bash(chmod:*)",
95+
"Bash(tar:*)",
96+
"Bash(unzip:*)",
97+
"Bash(zip:*)"
98+
],
99+
"deny": [
100+
"Bash(rm -rf .git:*)",
101+
"Bash(rm -rf .git)",
102+
"Bash(rm -rf target:*)",
103+
104+
"Bash(cargo publish:*)",
105+
"Bash(cargo yank:*)",
106+
"Bash(cargo login:*)",
107+
108+
"Bash(gh pr merge --auto:*)"
109+
]
110+
}
111+
}

0 commit comments

Comments
 (0)