Skip to content

Commit 75465e7

Browse files
committed
feat: add slash commands for Claude/Cursor and switch to core.hooksPath
- Add 6 reusable slash commands (analyze, review, fix, refactor, test, doc) for Claude (.claude/commands/) and Cursor (.cursor/prompts/) - Rewrite existing commands to be framework-agnostic (no React assumptions) - Switch git hooks from .git/hooks/ wrappers to git core.hooksPath - Auto-inject prepare script into package.json so hooks work for all devs - Add legacy cleanup for .git/hooks/ ContextKit wrappers - Rename hook files from .sh extension to match git conventions
1 parent 79717e5 commit 75465e7

24 files changed

Lines changed: 668 additions & 346 deletions

‎CHANGELOG.md‎

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,29 @@
11
# Changelog
22

3+
## [0.9.4] - 2026-02-16
4+
5+
### Added
6+
- **Slash Commands** for Claude Code (`.claude/commands/`) and Cursor (`.cursor/prompts/`)
7+
- `/analyze` — scan codebase and generate standards content
8+
- `/review` — code review with checklist
9+
- `/fix` — diagnose and fix bugs
10+
- `/refactor` — refactor code with safety checks
11+
- `/test` — generate comprehensive tests
12+
- `/doc` — add documentation
13+
- Both platforms delegate to universal `.contextkit/commands/` files
14+
- New command files: `review.md`, `fix.md`, `refactor.md`
15+
16+
### Changed
17+
- Git hooks now use `git config core.hooksPath .contextkit/hooks` instead of writing to `.git/hooks/`
18+
- Auto-injects `prepare` script into `package.json` so hooks work for all devs after `npm install`
19+
- Hook files renamed from `.sh` extension to match git conventions (`pre-push`, `commit-msg`)
20+
- All existing commands rewritten to be framework-agnostic (no React assumptions)
21+
- Legacy `.git/hooks/` ContextKit wrappers automatically cleaned up
22+
23+
### Fixed
24+
- Gradle quality gate now checks for `gradlew` and verifies `check` task exists
25+
- Go and Maven gates skip when no source files exist
26+
327
## [0.9.0] - 2026-02-16
428

529
### Added
@@ -16,7 +40,7 @@
1640
- Integration test suite for framework detection and hook installation
1741

1842
### Changed
19-
- Git hooks now install to `.git/hooks/` (native) instead of `.husky/`
43+
- Git hooks now use native approach instead of Husky
2044
- Hooks work in any git repo — no longer requires package.json or Node.js
2145
- `ck install` hooks prompt now checks for `.git/` instead of `package.json`
2246
- Install prompt updated with "Quality Gates" branding

‎README.md‎

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -111,13 +111,17 @@ Each platform generates bridge files that the AI tool auto-reads. If a bridge fi
111111

112112
## Use it in your tool
113113

114-
**Cursor** — rules auto-load from `.cursor/rules/`
114+
**Cursor** — rules auto-load from `.cursor/rules/`, slash commands in `.cursor/prompts/`
115115
```
116-
@.contextkit/commands/analyze.md
116+
/analyze # scan codebase and generate standards
117+
/review # code review with checklist
118+
/fix # diagnose and fix bugs
117119
```
118120

119-
**Claude Code** — reads `CLAUDE.md` + `.claude/rules/` automatically
121+
**Claude Code** — reads `CLAUDE.md` + `.claude/rules/`, slash commands in `.claude/commands/`
120122
```bash
123+
/analyze # scan codebase and generate standards
124+
/review # code review with checklist
121125
claude "create checkout flow for customer"
122126
```
123127

@@ -133,14 +137,36 @@ codex "create checkout flow for customer"
133137

134138
**CLI** (Chat with AI)
135139
```bash
136-
ck ai "create checkout flow for customer"
140+
ck ai "create a button"
137141
```
138142

139143
---
140144

145+
## Slash Commands
146+
147+
ContextKit installs reusable slash commands for supported platforms:
148+
149+
| Command | What it does |
150+
|---------|-------------|
151+
| `/analyze` | Scan codebase and generate standards content |
152+
| `/review` | Code review with checklist |
153+
| `/fix` | Diagnose and fix bugs |
154+
| `/refactor` | Refactor code with safety checks |
155+
| `/test` | Generate comprehensive tests |
156+
| `/doc` | Add documentation |
157+
158+
**Claude Code** — available as `/analyze`, `/review`, etc. in `.claude/commands/`
159+
**Cursor** — available as slash commands in Chat via `.cursor/prompts/`
160+
161+
Both platforms delegate to the universal command files in `.contextkit/commands/`, so you maintain one set of workflows.
162+
163+
---
164+
141165
## Git Hooks & Quality Gates
142166

143-
ContextKit can optionally install native Git hooks (`.git/hooks/`) during `ck install`. No external dependencies like Husky required — works in any git repo, not just Node.js projects.
167+
ContextKit can optionally install Git hooks during `ck install`. Uses `git config core.hooksPath` to point Git at `.contextkit/hooks/` — no external dependencies like Husky required. Works in any git repo, not just Node.js projects.
168+
169+
For **Node.js projects**, a `prepare` script is automatically added to `package.json` so hooks activate for all developers after `npm install` — no need for everyone to run `ck install`.
144170

145171
| Hook | What it does |
146172
|------|-------------|

‎__tests__/integration/quality-gates.test.js‎

Lines changed: 17 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ const path = require('path');
33
const os = require('os');
44
const { execSync } = require('child_process');
55

6-
const prePushScript = path.join(__dirname, '../../hooks/pre-push.sh');
6+
const prePushScript = path.join(__dirname, '../../hooks/pre-push');
77
const setupScript = path.join(__dirname, '../../hooks/setup-hooks.sh');
88

99
let tmpDir;
@@ -28,17 +28,15 @@ function runPrePush() {
2828
}
2929

3030
// Run with minimal PATH so framework tools (cargo, go, mvn, etc.) are not found.
31-
// This prevents failures from CI-installed tools running against minimal project files.
3231
function runPrePushMinimalPath() {
3332
return execSync(`bash "${prePushScript}"`, {
3433
encoding: 'utf8',
3534
env: { ...process.env, PATH: '/usr/bin:/bin' }
3635
});
3736
}
3837

39-
describe('Quality Gates — pre-push.sh framework detection', () => {
38+
describe('Quality Gates — pre-push framework detection', () => {
4039
it('1. detects Node.js project (package.json)', async () => {
41-
// Use a name that won't trigger gate grep patterns (no "test", "build", etc.)
4240
await fs.writeJson('package.json', { name: 'my-app', version: '1.0.0' });
4341
const output = runPrePush();
4442
expect(output).toContain('Quality Gates');
@@ -100,41 +98,31 @@ describe('Quality Gates — pre-push.sh framework detection', () => {
10098
});
10199

102100
it('11. Node.js gates skip gracefully when tools are not in package.json', async () => {
103-
// Minimal package.json with no tools or scripts referenced
104101
await fs.writeJson('package.json', { name: 'bare-project', version: '1.0.0' });
105102
const output = runPrePush();
106103
expect(output).toContain('Project: node');
107104
expect(output).toContain('passed');
108-
// Should not fail — no gates triggered
109105
});
110106

111107
it('12. Python gates skip gracefully when tools are not installed', async () => {
112108
await fs.writeFile('requirements.txt', 'flask\n');
113-
// Create a restricted PATH so tools aren't found
114-
const output = execSync(`bash "${prePushScript}"`, {
115-
encoding: 'utf8',
116-
env: { ...process.env, PATH: '/usr/bin:/bin' }
117-
});
109+
const output = runPrePushMinimalPath();
118110
expect(output).toContain('Project: python');
119111
expect(output).toContain('skipped');
120112
});
121113
});
122114

123115
describe('Quality Gates — setup-hooks.sh', () => {
124-
it('13. installs hooks to .git/hooks/ in a git repo', async () => {
116+
it('13. sets core.hooksPath via setup script', async () => {
125117
execSync('git init', { stdio: 'pipe' });
126118
await fs.ensureDir('.contextkit/hooks');
127-
await fs.writeFile('.contextkit/hooks/pre-push.sh', '#!/bin/sh\nexit 0');
128-
await fs.writeFile('.contextkit/hooks/commit-msg.sh', '#!/bin/sh\nexit 0');
119+
await fs.writeFile('.contextkit/hooks/pre-push', '#!/bin/sh\nexit 0');
120+
await fs.writeFile('.contextkit/hooks/commit-msg', '#!/bin/sh\nexit 0');
129121

130122
execSync(`bash "${setupScript}"`, { encoding: 'utf8' });
131123

132-
expect(fs.existsSync('.git/hooks/pre-push')).toBe(true);
133-
expect(fs.existsSync('.git/hooks/commit-msg')).toBe(true);
134-
135-
const prePush = await fs.readFile('.git/hooks/pre-push', 'utf8');
136-
expect(prePush).toContain('ContextKit managed hook');
137-
expect(prePush).toContain('.contextkit/hooks/pre-push.sh "$@"');
124+
const hooksPath = execSync('git config core.hooksPath', { encoding: 'utf8' }).trim();
125+
expect(hooksPath).toBe('.contextkit/hooks');
138126
});
139127

140128
it('14. fails if not a git repo', () => {
@@ -145,24 +133,23 @@ describe('Quality Gates — setup-hooks.sh', () => {
145133
});
146134

147135
describe('Quality Gates — GitHooksManager end-to-end', () => {
148-
it('15. full install flow: hooks land in .git/hooks/ and delegate correctly', async () => {
136+
it('15. full flow: core.hooksPath set, hook executes correctly', async () => {
149137
const GitHooksManager = require('../../lib/utils/git-hooks');
150138

151139
execSync('git init', { stdio: 'pipe' });
152140
await fs.ensureDir('.contextkit/hooks');
153-
await fs.writeFile('.contextkit/hooks/pre-push.sh', '#!/bin/sh\nexit 0');
154-
await fs.chmod('.contextkit/hooks/pre-push.sh', '755');
141+
await fs.writeFile('.contextkit/hooks/pre-push', '#!/bin/sh\necho "hook ran"\nexit 0');
142+
await fs.chmod('.contextkit/hooks/pre-push', '755');
155143

156144
const manager = new GitHooksManager();
157145
await manager.installHooks('npm', { prePush: true, commitMsg: false });
158146

159-
// Verify the hook exists and is executable
160-
const hookPath = '.git/hooks/pre-push';
161-
expect(fs.existsSync(hookPath)).toBe(true);
162-
const stat = await fs.stat(hookPath);
163-
expect(stat.mode & 0o111).toBeTruthy();
147+
// Verify core.hooksPath is set
148+
const hooksPath = execSync('git config core.hooksPath', { encoding: 'utf8' }).trim();
149+
expect(hooksPath).toBe('.contextkit/hooks');
164150

165-
// Actually run the hook — it should delegate to our dummy script and exit 0
166-
execSync(`./${hookPath}`, { encoding: 'utf8' });
151+
// Actually run the hook directly
152+
const output = execSync('.contextkit/hooks/pre-push', { encoding: 'utf8' });
153+
expect(output).toContain('hook ran');
167154
});
168155
});

‎__tests__/integrations/claude-integration.test.js‎

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,14 +64,17 @@ describe('ClaudeIntegration', () => {
6464
expect(content).toContain('lib/**');
6565
});
6666

67-
test('5. creates .claude/commands/analyze.md', async () => {
67+
test('5. creates all slash command files', async () => {
6868
const integration = new ClaudeIntegration();
6969
await integration.install();
7070

71-
const filePath = '.claude/commands/analyze.md';
72-
expect(await fs.pathExists(filePath)).toBe(true);
73-
const content = await fs.readFile(filePath, 'utf-8');
74-
expect(content).toContain('analyze');
71+
const commands = ['analyze', 'review', 'fix', 'refactor', 'test', 'doc'];
72+
for (const cmd of commands) {
73+
const filePath = `.claude/commands/${cmd}.md`;
74+
expect(await fs.pathExists(filePath)).toBe(true);
75+
const content = await fs.readFile(filePath, 'utf-8');
76+
expect(content).toContain('.contextkit/commands/');
77+
}
7578
});
7679

7780
test('6. appends to existing CLAUDE.md without overwriting', async () => {

‎__tests__/integrations/cursor-integration.test.js‎

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,20 @@ describe('CursorIntegration', () => {
6868
expect(await fs.pathExists('.cursor/rules/contextkit-standards.mdc')).toBe(true);
6969
});
7070

71-
test('6. validate returns valid after install', async () => {
71+
test('6. creates all prompt files for slash commands', async () => {
72+
const integration = new CursorIntegration();
73+
await integration.install();
74+
75+
const prompts = ['analyze', 'review', 'fix', 'refactor', 'test', 'doc'];
76+
for (const prompt of prompts) {
77+
const filePath = `.cursor/prompts/${prompt}.md`;
78+
expect(await fs.pathExists(filePath)).toBe(true);
79+
const content = await fs.readFile(filePath, 'utf-8');
80+
expect(content).toContain('.contextkit/commands/');
81+
}
82+
});
83+
84+
test('7. validate returns valid after install', async () => {
7285
const integration = new CursorIntegration();
7386
await integration.install();
7487

0 commit comments

Comments
 (0)