@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 +17 -3
- package/lib/commands/install.js +6 -5
- package/lib/utils/git-hooks.js +49 -148
- package/package.json +1 -1
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** |
|
|
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:
|
package/lib/commands/install.js
CHANGED
|
@@ -178,20 +178,21 @@ class InstallCommand {
|
|
|
178
178
|
return { prePush: false, commitMsg: false };
|
|
179
179
|
}
|
|
180
180
|
|
|
181
|
-
if (!await fs.pathExists('
|
|
182
|
-
console.log(chalk.yellow('⚠️ Skipping Git hooks setup (
|
|
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
|
|
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
|
|
194
|
-
console.log(chalk.dim('
|
|
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
|
{
|
package/lib/utils/git-hooks.js
CHANGED
|
@@ -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 = '.
|
|
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
|
|
14
|
-
if (!
|
|
15
|
-
console.log(chalk.yellow('⚠️
|
|
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
|
-
//
|
|
21
|
-
await this.
|
|
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(
|
|
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
|
|
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.
|
|
49
|
+
await this.addNativeHook('pre-push', '.contextkit/hooks/pre-push.sh');
|
|
168
50
|
}
|
|
169
51
|
if (hookChoices.commitMsg) {
|
|
170
|
-
await this.
|
|
52
|
+
await this.addNativeHook('commit-msg', '.contextkit/hooks/commit-msg.sh');
|
|
171
53
|
}
|
|
172
54
|
}
|
|
173
55
|
|
|
174
|
-
async
|
|
56
|
+
async addNativeHook(hookName, scriptPath) {
|
|
175
57
|
const hookPath = `${this.hooksDir}/${hookName}`;
|
|
176
|
-
|
|
177
|
-
// Ensure .
|
|
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.
|
|
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": {
|