@nolrm/contextkit 0.8.4 → 0.9.3

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
@@ -138,15 +138,29 @@ ck ai "create checkout flow for customer"
138
138
 
139
139
  ---
140
140
 
141
- ## Git Hooks
141
+ ## Git Hooks & Quality Gates
142
142
 
143
- ContextKit can optionally install Git hooks during `ck install`:
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.
144
144
 
145
145
  | Hook | What it does |
146
146
  |------|-------------|
147
- | **pre-push** | Runs tests, linting, and type checks before pushing |
147
+ | **pre-push** | **Quality Gates** auto-detects your project framework and runs the appropriate checks |
148
148
  | **commit-msg** | Enforces [Conventional Commits](https://www.conventionalcommits.org/) format |
149
149
 
150
+ ### Framework-Aware Quality Gates
151
+
152
+ The pre-push hook detects your project type and runs the right quality checks automatically. All gates skip gracefully when tools aren't installed.
153
+
154
+ | Framework | Checks |
155
+ |-----------|--------|
156
+ | **Node.js** | TypeScript, ESLint, Prettier, build, test (auto-detects npm/yarn/pnpm/bun) |
157
+ | **Python** | ruff/flake8, mypy, black/ruff format, pytest |
158
+ | **Rust** | cargo check, clippy, cargo test |
159
+ | **Go** | go vet, golangci-lint, go test |
160
+ | **PHP** | PHPStan, PHPUnit |
161
+ | **Ruby** | RuboCop, RSpec/rake test |
162
+ | **Java** | Maven verify / Gradle check |
163
+
150
164
  ### Commit Message Format
151
165
 
152
166
  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
  {
@@ -1,30 +1,26 @@
1
- const { execSync } = require('child_process');
2
1
  const fs = require('fs-extra');
3
2
  const chalk = require('chalk');
4
3
 
5
4
  class GitHooksManager {
6
5
  constructor() {
7
- this.hooksDir = '.husky';
6
+ this.hooksDir = '.git/hooks';
8
7
  }
9
8
 
10
9
  async installHooks(packageManager, hookChoices = { prePush: true, commitMsg: true }) {
11
10
  console.log(chalk.yellow('🪝 Setting up Git hooks...'));
12
11
 
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'));
12
+ // Check if this is a git repo
13
+ if (!fs.existsSync('.git')) {
14
+ console.log(chalk.yellow('⚠️ Not a git repository, skipping Git hooks setup'));
16
15
  return;
17
16
  }
18
17
 
19
18
  try {
20
- // Install Husky based on package manager
21
- await this.installHusky(packageManager);
22
-
23
- // Initialize Husky
24
- await this.initializeHusky(packageManager);
19
+ // Clean up legacy Husky directory if present
20
+ await this.cleanupLegacyHusky();
25
21
 
26
22
  // Add hooks
27
- await this.addHooks(packageManager, hookChoices);
23
+ await this.addHooks(hookChoices);
28
24
 
29
25
  console.log(chalk.green('✅ Git hooks setup complete'));
30
26
 
@@ -34,121 +30,7 @@ class GitHooksManager {
34
30
  }
35
31
  }
36
32
 
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
- }
71
- }
72
- } else {
73
- console.log(chalk.green('✅ Husky already installed'));
74
- }
75
- }
76
-
77
- async checkHuskyInstalled(packageManager) {
78
- 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;
89
- }
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;
103
- }
104
- } catch (error) {
105
- return false;
106
- }
107
- }
108
-
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
- }
125
-
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
- }
150
-
151
- async addHooks(packageManager, hookChoices = { prePush: true, commitMsg: true }) {
33
+ async addHooks(hookChoices = { prePush: true, commitMsg: true }) {
152
34
  // Backup existing hooks
153
35
  await this.backupExistingHooks();
154
36
 
@@ -164,38 +46,66 @@ fi
164
46
 
165
47
  // Add selected hooks
166
48
  if (hookChoices.prePush) {
167
- await this.addHook(packageManager, 'pre-push', '.contextkit/hooks/pre-push.sh');
49
+ await this.addNativeHook('pre-push', '.contextkit/hooks/pre-push.sh');
168
50
  }
169
51
  if (hookChoices.commitMsg) {
170
- await this.addHook(packageManager, 'commit-msg', '.contextkit/hooks/commit-msg.sh');
52
+ await this.addNativeHook('commit-msg', '.contextkit/hooks/commit-msg.sh');
171
53
  }
172
54
  }
173
55
 
174
- async addHook(packageManager, hookName, scriptPath) {
56
+ async addNativeHook(hookName, scriptPath) {
175
57
  const hookPath = `${this.hooksDir}/${hookName}`;
176
-
177
- // Ensure .husky directory exists
58
+
59
+ // Ensure .git/hooks directory exists
178
60
  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
61
 
62
+ // Write hook directly — no Husky, just a shebang + delegation
63
+ const hookContent = `#!/usr/bin/env sh
64
+ # ContextKit managed hook — do not edit
184
65
  ${scriptPath} "$@"
185
66
  `;
186
-
67
+
187
68
  await fs.writeFile(hookPath, hookContent);
188
69
  await fs.chmod(hookPath, '755');
189
-
70
+
190
71
  console.log(chalk.green(`✅ Created hook: ${hookName}`));
191
72
  }
192
73
 
74
+ async cleanupLegacyHusky() {
75
+ if (!fs.existsSync('.husky')) return;
76
+
77
+ // Check if the .husky dir contains ContextKit/Vibe Kit markers
78
+ const huskyHooks = ['pre-push', 'commit-msg', 'pre-commit'];
79
+ let hasContextKitHooks = false;
80
+
81
+ for (const hook of huskyHooks) {
82
+ const hookPath = `.husky/${hook}`;
83
+ if (fs.existsSync(hookPath)) {
84
+ const content = await fs.readFile(hookPath, 'utf8');
85
+ if (content.includes('.contextkit/') || content.includes('.vibe-kit/')) {
86
+ hasContextKitHooks = true;
87
+ break;
88
+ }
89
+ }
90
+ }
91
+
92
+ if (hasContextKitHooks) {
93
+ await fs.remove('.husky');
94
+ console.log(chalk.yellow('🧹 Removed legacy .husky/ directory (now using native .git/hooks/)'));
95
+ console.log(chalk.dim(' 💡 You can also run: npm uninstall husky'));
96
+ }
97
+ }
98
+
193
99
  async backupExistingHooks() {
194
100
  const hooks = ['pre-push', 'commit-msg'];
195
101
 
196
102
  for (const hook of hooks) {
197
103
  const hookPath = `${this.hooksDir}/${hook}`;
198
104
  if (fs.existsSync(hookPath)) {
105
+ // Don't back up our own hooks
106
+ const content = await fs.readFile(hookPath, 'utf8');
107
+ if (content.includes('ContextKit managed hook')) continue;
108
+
199
109
  const backupPath = `${hookPath}.backup`;
200
110
  await fs.copy(hookPath, backupPath);
201
111
  console.log(chalk.yellow(`📦 Backed up existing hook: ${hook}`));
@@ -203,24 +113,15 @@ ${scriptPath} "$@"
203
113
  }
204
114
  }
205
115
 
206
- checkCommandExists(command) {
207
- try {
208
- execSync(`which ${command}`, { stdio: 'pipe' });
209
- return true;
210
- } catch (error) {
211
- return false;
212
- }
213
- }
214
-
215
116
  async uninstallHooks() {
216
117
  console.log(chalk.yellow('🪝 Removing Git hooks...'));
217
-
118
+
218
119
  const hooks = ['pre-push', 'commit-msg'];
219
120
 
220
121
  for (const hook of hooks) {
221
122
  const hookPath = `${this.hooksDir}/${hook}`;
222
123
  const backupPath = `${hookPath}.backup`;
223
-
124
+
224
125
  if (fs.existsSync(backupPath)) {
225
126
  await fs.move(backupPath, hookPath);
226
127
  console.log(chalk.green(`✅ Restored original hook: ${hook}`));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nolrm/contextkit",
3
- "version": "0.8.4",
3
+ "version": "0.9.3",
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": {