@nolrm/contextkit 0.8.4 → 0.9.4

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/README.md CHANGED
@@ -111,13 +111,17 @@ Each platform generates bridge files that the AI tool auto-reads. If a bridge fi
111
111
 
112
112
  ## Use it in your tool
113
113
 
114
- **Cursor** — rules auto-load from `.cursor/rules/`
114
+ **Cursor** — rules auto-load from `.cursor/rules/`, slash commands in `.cursor/prompts/`
115
115
  ```
116
- @.contextkit/commands/analyze.md
116
+ /analyze # scan codebase and generate standards
117
+ /review # code review with checklist
118
+ /fix # diagnose and fix bugs
117
119
  ```
118
120
 
119
- **Claude Code** — reads `CLAUDE.md` + `.claude/rules/` automatically
121
+ **Claude Code** — reads `CLAUDE.md` + `.claude/rules/`, slash commands in `.claude/commands/`
120
122
  ```bash
123
+ /analyze # scan codebase and generate standards
124
+ /review # code review with checklist
121
125
  claude "create checkout flow for customer"
122
126
  ```
123
127
 
@@ -133,20 +137,56 @@ codex "create checkout flow for customer"
133
137
 
134
138
  **CLI** (Chat with AI)
135
139
  ```bash
136
- ck ai "create checkout flow for customer"
140
+ ck ai "create a button"
137
141
  ```
138
142
 
139
143
  ---
140
144
 
141
- ## Git Hooks
145
+ ## Slash Commands
142
146
 
143
- ContextKit can optionally install Git hooks during `ck install`:
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
+
165
+ ## Git Hooks & Quality Gates
166
+
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`.
144
170
 
145
171
  | Hook | What it does |
146
172
  |------|-------------|
147
- | **pre-push** | Runs tests, linting, and type checks before pushing |
173
+ | **pre-push** | **Quality Gates** auto-detects your project framework and runs the appropriate checks |
148
174
  | **commit-msg** | Enforces [Conventional Commits](https://www.conventionalcommits.org/) format |
149
175
 
176
+ ### Framework-Aware Quality Gates
177
+
178
+ The pre-push hook detects your project type and runs the right quality checks automatically. All gates skip gracefully when tools aren't installed.
179
+
180
+ | Framework | Checks |
181
+ |-----------|--------|
182
+ | **Node.js** | TypeScript, ESLint, Prettier, build, test (auto-detects npm/yarn/pnpm/bun) |
183
+ | **Python** | ruff/flake8, mypy, black/ruff format, pytest |
184
+ | **Rust** | cargo check, clippy, cargo test |
185
+ | **Go** | go vet, golangci-lint, go test |
186
+ | **PHP** | PHPStan, PHPUnit |
187
+ | **Ruby** | RuboCop, RSpec/rake test |
188
+ | **Java** | Maven verify / Gradle check |
189
+
150
190
  ### Commit Message Format
151
191
 
152
192
  When the `commit-msg` hook is enabled, all commits must follow this format:
@@ -178,20 +178,21 @@ class InstallCommand {
178
178
  return { prePush: false, commitMsg: false };
179
179
  }
180
180
 
181
- if (!await fs.pathExists('package.json')) {
182
- console.log(chalk.yellow('⚠️ Skipping Git hooks setup (no package.json found)'));
181
+ if (!await fs.pathExists('.git')) {
182
+ console.log(chalk.yellow('⚠️ Skipping Git hooks setup (not a git repository)'));
183
183
  return { prePush: false, commitMsg: false };
184
184
  }
185
185
 
186
186
  console.log('');
187
187
  console.log('──────────────────────────────────────────────');
188
- console.log(chalk.blue('⚙️ Git Hooks Setup'));
188
+ console.log(chalk.blue('⚙️ Quality Gates — Git Hooks'));
189
189
  console.log('──────────────────────────────────────────────');
190
190
  console.log('');
191
191
 
192
192
  // Ask about pre-push hook
193
- console.log(chalk.bold('Pre-push hook'));
194
- console.log(chalk.dim('Runs tests, linting, and type checks before pushing.'));
193
+ console.log(chalk.bold('Pre-push Quality Gates'));
194
+ console.log(chalk.dim('Auto-detects your framework (Node, Python, Rust, Go, etc.)'));
195
+ console.log(chalk.dim('and runs the right checks before pushing.'));
195
196
  console.log('');
196
197
  const { prePush } = await inquirer.prompt([
197
198
  {
@@ -514,12 +515,20 @@ Run \`ck analyze\` to generate this content, or manually add your API pattern be
514
515
 
515
516
  // Download commands
516
517
  await this.downloadManager.downloadFile(
517
- `${this.repoUrl}/commands/create-feature.md`,
518
- '.contextkit/commands/create-feature.md'
518
+ `${this.repoUrl}/commands/analyze.md`,
519
+ '.contextkit/commands/analyze.md'
519
520
  );
520
521
  await this.downloadManager.downloadFile(
521
- `${this.repoUrl}/commands/create-component.md`,
522
- '.contextkit/commands/create-component.md'
522
+ `${this.repoUrl}/commands/review.md`,
523
+ '.contextkit/commands/review.md'
524
+ );
525
+ await this.downloadManager.downloadFile(
526
+ `${this.repoUrl}/commands/fix.md`,
527
+ '.contextkit/commands/fix.md'
528
+ );
529
+ await this.downloadManager.downloadFile(
530
+ `${this.repoUrl}/commands/refactor.md`,
531
+ '.contextkit/commands/refactor.md'
523
532
  );
524
533
  await this.downloadManager.downloadFile(
525
534
  `${this.repoUrl}/commands/run-tests.md`,
@@ -534,18 +543,22 @@ Run \`ck analyze\` to generate this content, or manually add your API pattern be
534
543
  '.contextkit/commands/quality-check.md'
535
544
  );
536
545
  await this.downloadManager.downloadFile(
537
- `${this.repoUrl}/commands/analyze.md`,
538
- '.contextkit/commands/analyze.md'
546
+ `${this.repoUrl}/commands/create-feature.md`,
547
+ '.contextkit/commands/create-feature.md'
548
+ );
549
+ await this.downloadManager.downloadFile(
550
+ `${this.repoUrl}/commands/create-component.md`,
551
+ '.contextkit/commands/create-component.md'
539
552
  );
540
553
 
541
554
  // Download hooks (pre-push and commit-msg only, no pre-commit)
542
555
  await this.downloadManager.downloadFile(
543
- `${this.repoUrl}/hooks/pre-push.sh`,
544
- '.contextkit/hooks/pre-push.sh'
556
+ `${this.repoUrl}/hooks/pre-push`,
557
+ '.contextkit/hooks/pre-push'
545
558
  );
546
559
  await this.downloadManager.downloadFile(
547
- `${this.repoUrl}/hooks/commit-msg.sh`,
548
- '.contextkit/hooks/commit-msg.sh'
560
+ `${this.repoUrl}/hooks/commit-msg`,
561
+ '.contextkit/hooks/commit-msg'
549
562
  );
550
563
  await this.downloadManager.downloadFile(
551
564
  `${this.repoUrl}/hooks/setup-hooks.sh`,
@@ -580,8 +593,8 @@ Run \`ck analyze\` to generate this content, or manually add your API pattern be
580
593
  );
581
594
 
582
595
  // Make scripts executable
583
- await fs.chmod('.contextkit/hooks/pre-push.sh', '755');
584
- await fs.chmod('.contextkit/hooks/commit-msg.sh', '755');
596
+ await fs.chmod('.contextkit/hooks/pre-push', '755');
597
+ await fs.chmod('.contextkit/hooks/commit-msg', '755');
585
598
  await fs.chmod('.contextkit/hooks/setup-hooks.sh', '755');
586
599
  await fs.chmod('.contextkit/types/type-check.sh', '755');
587
600
  await fs.chmod('.contextkit/scripts/update.sh', '755');
@@ -682,11 +695,14 @@ claude "read .contextkit/context.md to see available standards, then create a bu
682
695
 
683
696
  ### Commands
684
697
  - \`.contextkit/commands/analyze.md\` - Analyze & customize standards
698
+ - \`.contextkit/commands/review.md\` - Code review
699
+ - \`.contextkit/commands/fix.md\` - Diagnose and fix bugs
700
+ - \`.contextkit/commands/refactor.md\` - Refactor code structure
701
+ - \`.contextkit/commands/run-tests.md\` - Generate or run tests
702
+ - \`.contextkit/commands/add-documentation.md\` - Add documentation
703
+ - \`.contextkit/commands/quality-check.md\` - Quality checks
685
704
  - \`.contextkit/commands/create-component.md\` - Create component
686
705
  - \`.contextkit/commands/create-feature.md\` - Create feature
687
- - \`.contextkit/commands/run-tests.md\` - Run tests
688
- - \`.contextkit/commands/quality-check.md\` - Quality checks
689
- - \`.contextkit/commands/add-documentation.md\` - Add documentation
690
706
 
691
707
  ### Instructions
692
708
  - \`.contextkit/instructions/meta/pre-flight.md\` - Pre-flight checks
@@ -805,12 +821,15 @@ esac
805
821
  docs: 'docs'
806
822
  },
807
823
  commands: {
808
- create_component: '@.contextkit/commands/create-component.md',
809
- create_feature: '@.contextkit/commands/create-feature.md',
824
+ analyze: '@.contextkit/commands/analyze.md',
825
+ review: '@.contextkit/commands/review.md',
826
+ fix: '@.contextkit/commands/fix.md',
827
+ refactor: '@.contextkit/commands/refactor.md',
810
828
  run_tests: '@.contextkit/commands/run-tests.md',
811
829
  add_docs: '@.contextkit/commands/add-documentation.md',
812
830
  quality_check: '@.contextkit/commands/quality-check.md',
813
- analyze: '@.contextkit/commands/analyze.md'
831
+ create_component: '@.contextkit/commands/create-component.md',
832
+ create_feature: '@.contextkit/commands/create-feature.md'
814
833
  }
815
834
  };
816
835
 
@@ -213,12 +213,20 @@ class UpdateCommand {
213
213
 
214
214
  // Download commands
215
215
  await this.downloadManager.downloadFile(
216
- `${this.repoUrl}/commands/create-feature.md`,
217
- '.contextkit/commands/create-feature.md'
216
+ `${this.repoUrl}/commands/analyze.md`,
217
+ '.contextkit/commands/analyze.md'
218
218
  );
219
219
  await this.downloadManager.downloadFile(
220
- `${this.repoUrl}/commands/create-component.md`,
221
- '.contextkit/commands/create-component.md'
220
+ `${this.repoUrl}/commands/review.md`,
221
+ '.contextkit/commands/review.md'
222
+ );
223
+ await this.downloadManager.downloadFile(
224
+ `${this.repoUrl}/commands/fix.md`,
225
+ '.contextkit/commands/fix.md'
226
+ );
227
+ await this.downloadManager.downloadFile(
228
+ `${this.repoUrl}/commands/refactor.md`,
229
+ '.contextkit/commands/refactor.md'
222
230
  );
223
231
  await this.downloadManager.downloadFile(
224
232
  `${this.repoUrl}/commands/run-tests.md`,
@@ -233,18 +241,22 @@ class UpdateCommand {
233
241
  '.contextkit/commands/quality-check.md'
234
242
  );
235
243
  await this.downloadManager.downloadFile(
236
- `${this.repoUrl}/commands/analyze.md`,
237
- '.contextkit/commands/analyze.md'
244
+ `${this.repoUrl}/commands/create-feature.md`,
245
+ '.contextkit/commands/create-feature.md'
246
+ );
247
+ await this.downloadManager.downloadFile(
248
+ `${this.repoUrl}/commands/create-component.md`,
249
+ '.contextkit/commands/create-component.md'
238
250
  );
239
251
 
240
252
  // Download hooks (pre-push and commit-msg only, no pre-commit)
241
253
  await this.downloadManager.downloadFile(
242
- `${this.repoUrl}/hooks/pre-push.sh`,
243
- '.contextkit/hooks/pre-push.sh'
254
+ `${this.repoUrl}/hooks/pre-push`,
255
+ '.contextkit/hooks/pre-push'
244
256
  );
245
257
  await this.downloadManager.downloadFile(
246
- `${this.repoUrl}/hooks/commit-msg.sh`,
247
- '.contextkit/hooks/commit-msg.sh'
258
+ `${this.repoUrl}/hooks/commit-msg`,
259
+ '.contextkit/hooks/commit-msg'
248
260
  );
249
261
  await this.downloadManager.downloadFile(
250
262
  `${this.repoUrl}/hooks/setup-hooks.sh`,
@@ -278,8 +290,8 @@ class UpdateCommand {
278
290
  );
279
291
 
280
292
  // Make scripts executable
281
- await fs.chmod('.contextkit/hooks/pre-push.sh', '755');
282
- await fs.chmod('.contextkit/hooks/commit-msg.sh', '755');
293
+ await fs.chmod('.contextkit/hooks/pre-push', '755');
294
+ await fs.chmod('.contextkit/hooks/commit-msg', '755');
283
295
  await fs.chmod('.contextkit/hooks/setup-hooks.sh', '755');
284
296
  await fs.chmod('.contextkit/types/type-check.sh', '755');
285
297
  await fs.chmod('.contextkit/scripts/update.sh', '755');
@@ -317,12 +329,15 @@ paths:
317
329
 
318
330
  # Commands
319
331
  commands:
320
- create_component: "${config.commands?.create_component || '@.contextkit/commands/create-component.md'}"
321
- create_feature: "${config.commands?.create_feature || '@.contextkit/commands/create-feature.md'}"
332
+ analyze: "${config.commands?.analyze || '@.contextkit/commands/analyze.md'}"
333
+ review: "${config.commands?.review || '@.contextkit/commands/review.md'}"
334
+ fix: "${config.commands?.fix || '@.contextkit/commands/fix.md'}"
335
+ refactor: "${config.commands?.refactor || '@.contextkit/commands/refactor.md'}"
322
336
  run_tests: "${config.commands?.run_tests || '@.contextkit/commands/run-tests.md'}"
323
337
  add_docs: "${config.commands?.add_docs || '@.contextkit/commands/add-documentation.md'}"
324
338
  quality_check: "${config.commands?.quality_check || '@.contextkit/commands/quality-check.md'}"
325
- analyze: "${config.commands?.analyze || '@.contextkit/commands/analyze.md'}"
339
+ create_component: "${config.commands?.create_component || '@.contextkit/commands/create-component.md'}"
340
+ create_feature: "${config.commands?.create_feature || '@.contextkit/commands/create-feature.md'}"
326
341
  `;
327
342
 
328
343
  await fs.writeFile('.contextkit/config.yml', configContent);
@@ -12,6 +12,11 @@ class ClaudeIntegration extends BaseIntegration {
12
12
  '.claude/rules/contextkit-testing.md',
13
13
  '.claude/rules/contextkit-code-style.md',
14
14
  '.claude/commands/analyze.md',
15
+ '.claude/commands/review.md',
16
+ '.claude/commands/fix.md',
17
+ '.claude/commands/refactor.md',
18
+ '.claude/commands/test.md',
19
+ '.claude/commands/doc.md',
15
20
  ];
16
21
  this.platformDir = '.claude/rules';
17
22
  }
@@ -112,14 +117,48 @@ When writing or modifying source code, follow:
112
117
  `;
113
118
  await this.writeGeneratedFile('.claude/rules/contextkit-code-style.md', codeStyleRule);
114
119
 
115
- // Command: analyze
116
- const analyzeCommand = `# Analyze Project
120
+ // Slash commands — thin wrappers that delegate to .contextkit/commands/
121
+ await this.writeGeneratedFile('.claude/commands/analyze.md', `# Analyze Project
117
122
 
118
123
  Read \`.contextkit/commands/analyze.md\` and execute the analysis workflow for this project.
119
124
 
120
125
  Scan the codebase structure, detect frameworks and patterns, then generate customized standards files in \`.contextkit/standards/\`.
121
- `;
122
- await this.writeGeneratedFile('.claude/commands/analyze.md', analyzeCommand);
126
+ `);
127
+
128
+ await this.writeGeneratedFile('.claude/commands/review.md', `# Code Review
129
+
130
+ Read \`.contextkit/commands/review.md\` and execute the review workflow.
131
+
132
+ Review current changes for correctness, standards compliance, and potential issues.
133
+ `);
134
+
135
+ await this.writeGeneratedFile('.claude/commands/fix.md', `# Fix Bug
136
+
137
+ Read \`.contextkit/commands/fix.md\` and execute the bug fix workflow.
138
+
139
+ Diagnose the root cause, implement the minimal fix, and add a regression test.
140
+ `);
141
+
142
+ await this.writeGeneratedFile('.claude/commands/refactor.md', `# Refactor
143
+
144
+ Read \`.contextkit/commands/refactor.md\` and execute the refactoring workflow.
145
+
146
+ Improve code structure without changing behavior, keeping tests green at every step.
147
+ `);
148
+
149
+ await this.writeGeneratedFile('.claude/commands/test.md', `# Run Tests
150
+
151
+ Read \`.contextkit/commands/run-tests.md\` and execute the testing workflow.
152
+
153
+ Generate or run tests for the specified code, covering happy paths, edge cases, and errors.
154
+ `);
155
+
156
+ await this.writeGeneratedFile('.claude/commands/doc.md', `# Add Documentation
157
+
158
+ Read \`.contextkit/commands/add-documentation.md\` and execute the documentation workflow.
159
+
160
+ Add inline docs, README sections, and usage examples for the specified code.
161
+ `);
123
162
  }
124
163
 
125
164
  showUsage() {
@@ -127,14 +166,14 @@ Scan the codebase structure, detect frameworks and patterns, then generate custo
127
166
  console.log(chalk.bold(' Claude Code Usage:'));
128
167
  console.log(' CLAUDE.md is auto-loaded every session');
129
168
  console.log(' .claude/rules/ are loaded based on file context');
130
- console.log(' Use /analyze slash command for project analysis');
131
169
  console.log('');
132
- console.log(chalk.dim(' Files created:'));
133
- console.log(chalk.dim(' CLAUDE.md (bridge - auto-loaded)'));
134
- console.log(chalk.dim(' .claude/rules/contextkit-standards.md (always apply)'));
135
- console.log(chalk.dim(' .claude/rules/contextkit-testing.md (test files)'));
136
- console.log(chalk.dim(' .claude/rules/contextkit-code-style.md (source files)'));
137
- console.log(chalk.dim(' .claude/commands/analyze.md (slash command)'));
170
+ console.log(chalk.dim(' Slash commands:'));
171
+ console.log(chalk.dim(' /analyze — Analyze project and generate standards'));
172
+ console.log(chalk.dim(' /review — Review current changes'));
173
+ console.log(chalk.dim(' /fix — Diagnose and fix a bug'));
174
+ console.log(chalk.dim(' /refactor — Refactor code structure'));
175
+ console.log(chalk.dim(' /test — Generate or run tests'));
176
+ console.log(chalk.dim(' /doc — Add documentation'));
138
177
  }
139
178
  }
140
179
 
@@ -12,6 +12,12 @@ class CursorIntegration extends BaseIntegration {
12
12
  '.cursor/rules/contextkit-testing.mdc',
13
13
  '.cursor/rules/contextkit-components.mdc',
14
14
  '.cursor/rules/contextkit-api.mdc',
15
+ '.cursor/prompts/analyze.md',
16
+ '.cursor/prompts/review.md',
17
+ '.cursor/prompts/fix.md',
18
+ '.cursor/prompts/refactor.md',
19
+ '.cursor/prompts/test.md',
20
+ '.cursor/prompts/doc.md',
15
21
  ];
16
22
  this.bridgeFiles = [];
17
23
  this.platformDir = '.cursor/rules';
@@ -19,6 +25,7 @@ class CursorIntegration extends BaseIntegration {
19
25
 
20
26
  async install() {
21
27
  await super.install();
28
+ await fs.ensureDir('.cursor/prompts');
22
29
  // Remove old monolithic rule file if present
23
30
  await this.removeLegacyFiles();
24
31
  }
@@ -142,20 +149,63 @@ Reference: @.contextkit/standards/architecture.md
142
149
  - @.contextkit/commands/create-feature.md — Create feature workflow
143
150
  `;
144
151
  await this.writeGeneratedFile('.cursor/rules/contextkit-api.mdc', apiRule);
152
+
153
+ // Cursor prompts — reusable slash commands in Cursor Chat
154
+ await this.writeGeneratedFile('.cursor/prompts/analyze.md', `# Analyze Project
155
+
156
+ Read \`.contextkit/commands/analyze.md\` and execute the analysis workflow for this project.
157
+
158
+ Scan the codebase structure, detect frameworks and patterns, then generate customized standards files in \`.contextkit/standards/\`.
159
+ `);
160
+
161
+ await this.writeGeneratedFile('.cursor/prompts/review.md', `# Code Review
162
+
163
+ Read \`.contextkit/commands/review.md\` and execute the review workflow.
164
+
165
+ Review current changes for correctness, standards compliance, and potential issues. Flag bugs, security concerns, and standards violations.
166
+ `);
167
+
168
+ await this.writeGeneratedFile('.cursor/prompts/fix.md', `# Fix Bug
169
+
170
+ Read \`.contextkit/commands/fix.md\` and execute the bug fix workflow.
171
+
172
+ Diagnose the root cause, implement the minimal fix, and add a regression test.
173
+ `);
174
+
175
+ await this.writeGeneratedFile('.cursor/prompts/refactor.md', `# Refactor
176
+
177
+ Read \`.contextkit/commands/refactor.md\` and execute the refactoring workflow.
178
+
179
+ Improve code structure without changing behavior, keeping tests green at every step.
180
+ `);
181
+
182
+ await this.writeGeneratedFile('.cursor/prompts/test.md', `# Run Tests
183
+
184
+ Read \`.contextkit/commands/run-tests.md\` and execute the testing workflow.
185
+
186
+ Generate or run tests for the specified code, covering happy paths, edge cases, and errors.
187
+ `);
188
+
189
+ await this.writeGeneratedFile('.cursor/prompts/doc.md', `# Add Documentation
190
+
191
+ Read \`.contextkit/commands/add-documentation.md\` and execute the documentation workflow.
192
+
193
+ Add inline docs, README sections, and usage examples for the specified code.
194
+ `);
145
195
  }
146
196
 
147
197
  showUsage() {
148
198
  console.log('');
149
199
  console.log(chalk.bold(' Cursor Usage:'));
150
200
  console.log(' Rules auto-load based on file context');
151
- console.log(' In Cursor Chat: @.contextkit/commands/analyze.md');
152
- console.log(' Or: @.contextkit Create a button component');
153
201
  console.log('');
154
- console.log(chalk.dim(' Files created:'));
155
- console.log(chalk.dim(' .cursor/rules/contextkit-standards.mdc (always apply)'));
156
- console.log(chalk.dim(' .cursor/rules/contextkit-testing.mdc (test files)'));
157
- console.log(chalk.dim(' .cursor/rules/contextkit-components.mdc (components)'));
158
- console.log(chalk.dim(' .cursor/rules/contextkit-api.mdc (API/services)'));
202
+ console.log(chalk.dim(' Slash commands (in Cursor Chat):'));
203
+ console.log(chalk.dim(' /analyze — Analyze project and generate standards'));
204
+ console.log(chalk.dim(' /review — Review current changes'));
205
+ console.log(chalk.dim(' /fix — Diagnose and fix a bug'));
206
+ console.log(chalk.dim(' /refactor — Refactor code structure'));
207
+ console.log(chalk.dim(' /test — Generate or run tests'));
208
+ console.log(chalk.dim(' /doc — Add documentation'));
159
209
  }
160
210
  }
161
211
 
@@ -4,27 +4,40 @@ const chalk = require('chalk');
4
4
 
5
5
  class GitHooksManager {
6
6
  constructor() {
7
- this.hooksDir = '.husky';
7
+ this.hooksPath = '.contextkit/hooks';
8
8
  }
9
9
 
10
10
  async installHooks(packageManager, hookChoices = { prePush: true, commitMsg: true }) {
11
11
  console.log(chalk.yellow('🪝 Setting up Git hooks...'));
12
12
 
13
- // Check if Node.js is available
14
- if (!this.checkCommandExists('node')) {
15
- console.log(chalk.yellow('⚠️ Node.js not found, skipping Git hooks setup'));
13
+ // Check if this is a git repo
14
+ if (!fs.existsSync('.git')) {
15
+ console.log(chalk.yellow('⚠️ Not a git repository, skipping Git hooks setup'));
16
16
  return;
17
17
  }
18
18
 
19
19
  try {
20
- // Install Husky based on package manager
21
- await this.installHusky(packageManager);
20
+ // Clean up legacy Husky directory if present
21
+ await this.cleanupLegacyHusky();
22
22
 
23
- // Initialize Husky
24
- await this.initializeHusky(packageManager);
23
+ // Clean up legacy .git/hooks/ files from previous ContextKit versions
24
+ await this.cleanupLegacyGitHooks();
25
25
 
26
- // Add hooks
27
- await this.addHooks(packageManager, hookChoices);
26
+ // Remove hooks the user didn't select
27
+ await this.removeUnselectedHooks(hookChoices);
28
+
29
+ // Set core.hooksPath so git uses .contextkit/hooks/ directly
30
+ execSync(`git config core.hooksPath ${this.hooksPath}`, { stdio: 'pipe' });
31
+ console.log(chalk.green(`✅ Git hooks path set to ${this.hooksPath}`));
32
+
33
+ // Add prepare script to package.json for automatic setup on npm install
34
+ await this.addPrepareScript();
35
+
36
+ // Show non-Node setup hint if no package.json
37
+ if (!fs.existsSync('package.json')) {
38
+ console.log(chalk.dim(' 💡 For other developers, add to your setup:'));
39
+ console.log(chalk.dim(` git config core.hooksPath ${this.hooksPath}`));
40
+ }
28
41
 
29
42
  console.log(chalk.green('✅ Git hooks setup complete'));
30
43
 
@@ -34,200 +47,128 @@ class GitHooksManager {
34
47
  }
35
48
  }
36
49
 
37
- async installHusky(packageManager) {
38
- const huskyInstalled = await this.checkHuskyInstalled(packageManager);
39
-
40
- if (!huskyInstalled) {
41
- console.log(chalk.blue(`📦 Installing Husky with ${packageManager}...`));
42
-
43
- try {
44
- switch (packageManager) {
45
- case 'yarn':
46
- execSync('yarn add --dev husky', { stdio: 'inherit' });
47
- break;
48
- case 'pnpm':
49
- execSync('pnpm add --save-dev husky', { stdio: 'inherit' });
50
- break;
51
- case 'npm':
52
- default:
53
- execSync('npm install --save-dev husky', { stdio: 'inherit' });
54
- break;
55
- }
56
- console.log(chalk.green(`✅ Husky installed with ${packageManager}`));
57
- } catch (error) {
58
- console.log(chalk.yellow(`⚠️ Failed to install Husky with ${packageManager}, trying fallback...`));
59
- // Fallback to npm if the detected package manager fails
60
- if (packageManager !== 'npm') {
61
- try {
62
- execSync('npm install --save-dev husky', { stdio: 'inherit' });
63
- console.log(chalk.green('✅ Husky installed with npm (fallback)'));
64
- } catch (fallbackError) {
65
- console.log(chalk.red('❌ Failed to install Husky with any package manager'));
66
- throw new Error(`Failed to install Husky: ${fallbackError.message}`);
67
- }
68
- } else {
69
- throw error;
70
- }
50
+ async removeUnselectedHooks(hookChoices) {
51
+ // If user didn't select a hook, remove it from .contextkit/hooks/
52
+ // so core.hooksPath doesn't run hooks they didn't want
53
+ if (!hookChoices.prePush) {
54
+ const hookPath = `${this.hooksPath}/pre-push`;
55
+ if (fs.existsSync(hookPath)) {
56
+ await fs.remove(hookPath);
57
+ }
58
+ }
59
+ if (!hookChoices.commitMsg) {
60
+ const hookPath = `${this.hooksPath}/commit-msg`;
61
+ if (fs.existsSync(hookPath)) {
62
+ await fs.remove(hookPath);
71
63
  }
72
- } else {
73
- console.log(chalk.green('✅ Husky already installed'));
74
64
  }
75
65
  }
76
66
 
77
- async checkHuskyInstalled(packageManager) {
67
+ async addPrepareScript() {
68
+ if (!fs.existsSync('package.json')) return;
69
+
78
70
  try {
79
- // Check if husky is in package.json dependencies
80
- const packageJson = await fs.readJson('package.json').catch(() => ({}));
81
- const allDeps = {
82
- ...packageJson.dependencies,
83
- ...packageJson.devDependencies,
84
- ...packageJson.peerDependencies
85
- };
86
-
87
- if (allDeps.husky) {
88
- return true;
71
+ const pkg = await fs.readJson('package.json');
72
+ const prepareCmd = `git config core.hooksPath ${this.hooksPath}`;
73
+
74
+ if (!pkg.scripts) pkg.scripts = {};
75
+
76
+ // Don't overwrite if prepare already has our command
77
+ if (pkg.scripts.prepare && pkg.scripts.prepare.includes('core.hooksPath')) {
78
+ return;
89
79
  }
90
-
91
- // Fallback: try package manager specific commands
92
- switch (packageManager) {
93
- case 'yarn':
94
- execSync('yarn list --pattern husky', { stdio: 'pipe' });
95
- return true;
96
- case 'pnpm':
97
- execSync('pnpm list husky', { stdio: 'pipe' });
98
- return true;
99
- case 'npm':
100
- default:
101
- execSync('npm list husky', { stdio: 'pipe' });
102
- return true;
80
+
81
+ // Append to existing prepare script or create new one
82
+ if (pkg.scripts.prepare) {
83
+ pkg.scripts.prepare = `${pkg.scripts.prepare} && ${prepareCmd}`;
84
+ } else {
85
+ pkg.scripts.prepare = prepareCmd;
103
86
  }
87
+
88
+ await fs.writeJson('package.json', pkg, { spaces: 2 });
89
+ console.log(chalk.green('✅ Added prepare script to package.json'));
104
90
  } catch (error) {
105
- return false;
91
+ console.log(chalk.yellow('⚠️ Could not update package.json prepare script'));
106
92
  }
107
93
  }
108
94
 
109
- async initializeHusky(packageManager) {
110
- // Modern Husky doesn't need explicit initialization
111
- // Just ensure .husky directory exists
112
- await fs.ensureDir(this.hooksDir);
113
-
114
- // Create husky.sh if it doesn't exist (for compatibility)
115
- const huskyShPath = `${this.hooksDir}/_/husky.sh`;
116
- if (!await fs.pathExists(huskyShPath)) {
117
- await fs.ensureDir(`${this.hooksDir}/_`);
118
- const huskyShContent = `#!/usr/bin/env sh
119
- if [ -z "$husky_skip_init" ]; then
120
- debug () {
121
- if [ "$HUSKY_DEBUG" = "1" ]; then
122
- echo "husky (debug) - $1"
123
- fi
124
- }
95
+ async cleanupLegacyHusky() {
96
+ if (!fs.existsSync('.husky')) return;
125
97
 
126
- readonly hook_name="$(basename -- "$0")"
127
- debug "starting $hook_name..."
128
-
129
- if [ "$HUSKY" = "0" ]; then
130
- debug "HUSKY env variable is set to 0, skipping hook"
131
- exit 0
132
- fi
133
-
134
- if [ -f ~/.huskyrc ]; then
135
- debug "sourcing ~/.huskyrc"
136
- . ~/.huskyrc
137
- fi
138
-
139
- readonly husky_skip_init=1
140
- export husky_skip_init
141
- sh -e "$0" "$@"
142
- fi
143
- `;
144
- await fs.writeFile(huskyShPath, huskyShContent);
145
- await fs.chmod(huskyShPath, '755');
146
- }
147
-
148
- console.log(chalk.green('✅ Husky initialized'));
149
- }
98
+ // Check if the .husky dir contains ContextKit/Vibe Kit markers
99
+ const huskyHooks = ['pre-push', 'commit-msg', 'pre-commit'];
100
+ let hasContextKitHooks = false;
150
101
 
151
- async addHooks(packageManager, hookChoices = { prePush: true, commitMsg: true }) {
152
- // Backup existing hooks
153
- await this.backupExistingHooks();
154
-
155
- // Remove legacy pre-commit hook if it exists (replaced by pre-push)
156
- const legacyPreCommitHook = `${this.hooksDir}/pre-commit`;
157
- if (fs.existsSync(legacyPreCommitHook)) {
158
- const content = await fs.readFile(legacyPreCommitHook, 'utf8');
159
- if (content.includes('.contextkit/') || content.includes('.vibe-kit/')) {
160
- await fs.remove(legacyPreCommitHook);
161
- console.log(chalk.yellow('🧹 Removed legacy pre-commit hook (replaced by pre-push)'));
102
+ for (const hook of huskyHooks) {
103
+ const hookPath = `.husky/${hook}`;
104
+ if (fs.existsSync(hookPath)) {
105
+ const content = await fs.readFile(hookPath, 'utf8');
106
+ if (content.includes('.contextkit/') || content.includes('.vibe-kit/')) {
107
+ hasContextKitHooks = true;
108
+ break;
109
+ }
162
110
  }
163
111
  }
164
112
 
165
- // Add selected hooks
166
- if (hookChoices.prePush) {
167
- await this.addHook(packageManager, 'pre-push', '.contextkit/hooks/pre-push.sh');
168
- }
169
- if (hookChoices.commitMsg) {
170
- await this.addHook(packageManager, 'commit-msg', '.contextkit/hooks/commit-msg.sh');
113
+ if (hasContextKitHooks) {
114
+ await fs.remove('.husky');
115
+ console.log(chalk.yellow('🧹 Removed legacy .husky/ directory'));
116
+ console.log(chalk.dim(' 💡 You can also run: npm uninstall husky'));
171
117
  }
172
118
  }
173
119
 
174
- async addHook(packageManager, hookName, scriptPath) {
175
- const hookPath = `${this.hooksDir}/${hookName}`;
176
-
177
- // Ensure .husky directory exists
178
- await fs.ensureDir(this.hooksDir);
179
-
180
- // Create hook file directly (modern Husky approach)
181
- const hookContent = `#!/usr/bin/env sh
182
- . "$(dirname -- "$0")/_/husky.sh"
183
-
184
- ${scriptPath} "$@"
185
- `;
186
-
187
- await fs.writeFile(hookPath, hookContent);
188
- await fs.chmod(hookPath, '755');
189
-
190
- console.log(chalk.green(`✅ Created hook: ${hookName}`));
191
- }
192
-
193
- async backupExistingHooks() {
120
+ async cleanupLegacyGitHooks() {
121
+ // Remove wrapper hooks from .git/hooks/ left by previous ContextKit versions
194
122
  const hooks = ['pre-push', 'commit-msg'];
195
-
196
123
  for (const hook of hooks) {
197
- const hookPath = `${this.hooksDir}/${hook}`;
124
+ const hookPath = `.git/hooks/${hook}`;
198
125
  if (fs.existsSync(hookPath)) {
199
- const backupPath = `${hookPath}.backup`;
200
- await fs.copy(hookPath, backupPath);
201
- console.log(chalk.yellow(`📦 Backed up existing hook: ${hook}`));
126
+ const content = await fs.readFile(hookPath, 'utf8');
127
+ if (content.includes('ContextKit managed hook')) {
128
+ await fs.remove(hookPath);
129
+ }
202
130
  }
203
131
  }
204
132
  }
205
133
 
206
- checkCommandExists(command) {
134
+ async uninstallHooks() {
135
+ console.log(chalk.yellow('🪝 Removing Git hooks...'));
136
+
137
+ // Unset core.hooksPath
207
138
  try {
208
- execSync(`which ${command}`, { stdio: 'pipe' });
209
- return true;
139
+ execSync('git config --unset core.hooksPath', { stdio: 'pipe' });
140
+ console.log(chalk.green('✅ Removed core.hooksPath config'));
210
141
  } catch (error) {
211
- return false;
142
+ // Already unset, that's fine
212
143
  }
144
+
145
+ // Remove prepare script from package.json
146
+ await this.removePrepareScript();
147
+
148
+ console.log(chalk.green('✅ Git hooks removed'));
213
149
  }
214
150
 
215
- async uninstallHooks() {
216
- console.log(chalk.yellow('🪝 Removing Git hooks...'));
217
-
218
- const hooks = ['pre-push', 'commit-msg'];
151
+ async removePrepareScript() {
152
+ if (!fs.existsSync('package.json')) return;
219
153
 
220
- for (const hook of hooks) {
221
- const hookPath = `${this.hooksDir}/${hook}`;
222
- const backupPath = `${hookPath}.backup`;
223
-
224
- if (fs.existsSync(backupPath)) {
225
- await fs.move(backupPath, hookPath);
226
- console.log(chalk.green(`✅ Restored original hook: ${hook}`));
227
- } else if (fs.existsSync(hookPath)) {
228
- await fs.remove(hookPath);
229
- console.log(chalk.green(`✅ Removed hook: ${hook}`));
154
+ try {
155
+ const pkg = await fs.readJson('package.json');
156
+ if (!pkg.scripts?.prepare) return;
157
+
158
+ const prepareCmd = `git config core.hooksPath ${this.hooksPath}`;
159
+
160
+ if (pkg.scripts.prepare === prepareCmd) {
161
+ delete pkg.scripts.prepare;
162
+ } else if (pkg.scripts.prepare.includes(prepareCmd)) {
163
+ // Remove our command from a chained prepare script
164
+ pkg.scripts.prepare = pkg.scripts.prepare
165
+ .replace(` && ${prepareCmd}`, '')
166
+ .replace(`${prepareCmd} && `, '');
230
167
  }
168
+
169
+ await fs.writeJson('package.json', pkg, { spaces: 2 });
170
+ } catch (error) {
171
+ // Best effort
231
172
  }
232
173
  }
233
174
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nolrm/contextkit",
3
- "version": "0.8.4",
3
+ "version": "0.9.4",
4
4
  "description": "ContextKit - Context Engineering for AI Development. Provide rich context to AI through structured MD files with standards, code guides, and documentation. Works with Cursor, Claude, Aider, VS Code Copilot, and more.",
5
5
  "main": "lib/index.js",
6
6
  "bin": {