@nolrm/contextkit 0.8.2 → 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 +45 -0
- package/lib/commands/install.js +64 -29
- package/lib/commands/status.js +9 -1
- package/lib/commands/update.js +15 -3
- package/lib/utils/git-hooks.js +54 -155
- package/lib/utils/status-manager.js +2 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -138,6 +138,51 @@ ck ai "create checkout flow for customer"
|
|
|
138
138
|
|
|
139
139
|
---
|
|
140
140
|
|
|
141
|
+
## Git Hooks & Quality Gates
|
|
142
|
+
|
|
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
|
+
|
|
145
|
+
| Hook | What it does |
|
|
146
|
+
|------|-------------|
|
|
147
|
+
| **pre-push** | **Quality Gates** — auto-detects your project framework and runs the appropriate checks |
|
|
148
|
+
| **commit-msg** | Enforces [Conventional Commits](https://www.conventionalcommits.org/) format |
|
|
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
|
+
|
|
164
|
+
### Commit Message Format
|
|
165
|
+
|
|
166
|
+
When the `commit-msg` hook is enabled, all commits must follow this format:
|
|
167
|
+
|
|
168
|
+
```
|
|
169
|
+
<type>(<scope>): <description>
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
**Types:** `feat`, `fix`, `docs`, `style`, `refactor`, `test`, `chore`
|
|
173
|
+
|
|
174
|
+
**Examples:**
|
|
175
|
+
```bash
|
|
176
|
+
git commit -m "feat(auth): add login page"
|
|
177
|
+
git commit -m "fix: resolve null pointer in checkout"
|
|
178
|
+
git commit -m "docs: update API reference"
|
|
179
|
+
git commit -m "test(cart): add edge case coverage"
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Hooks are optional and can be skipped with `ck install --no-hooks`.
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
141
186
|
## Key Features
|
|
142
187
|
|
|
143
188
|
- 🧠 **Context Engineering** - Structured MD files your AI reads automatically
|
package/lib/commands/install.js
CHANGED
|
@@ -101,24 +101,26 @@ class InstallCommand {
|
|
|
101
101
|
await this.downloadFiles(projectType, options, detectedTools);
|
|
102
102
|
|
|
103
103
|
// Ask about Git hooks
|
|
104
|
-
let
|
|
104
|
+
let hookChoices = { prePush: true, commitMsg: true };
|
|
105
105
|
if (!options.nonInteractive && !options.noHooks) {
|
|
106
|
-
|
|
106
|
+
hookChoices = await this.promptGitHooks();
|
|
107
|
+
} else if (options.noHooks) {
|
|
108
|
+
hookChoices = { prePush: false, commitMsg: false };
|
|
107
109
|
}
|
|
108
110
|
|
|
109
111
|
// Install Git hooks if requested
|
|
110
|
-
if (
|
|
111
|
-
await this.gitHooksManager.installHooks(packageManager);
|
|
112
|
+
if (hookChoices.prePush || hookChoices.commitMsg) {
|
|
113
|
+
await this.gitHooksManager.installHooks(packageManager, hookChoices);
|
|
112
114
|
}
|
|
113
115
|
|
|
114
116
|
// Create configuration
|
|
115
|
-
await this.createConfiguration(projectType,
|
|
117
|
+
await this.createConfiguration(projectType, hookChoices);
|
|
116
118
|
|
|
117
119
|
// Create status tracking
|
|
118
|
-
await this.createStatusFile(projectType, packageManager,
|
|
120
|
+
await this.createStatusFile(projectType, packageManager, hookChoices);
|
|
119
121
|
|
|
120
122
|
// Success message
|
|
121
|
-
this.showSuccessMessage(
|
|
123
|
+
this.showSuccessMessage(hookChoices, detectedTools, projectType, packageManager);
|
|
122
124
|
}
|
|
123
125
|
|
|
124
126
|
async installPlatformIntegration(platform) {
|
|
@@ -173,41 +175,71 @@ class InstallCommand {
|
|
|
173
175
|
// Check for non-interactive mode
|
|
174
176
|
if (process.env.CI === 'true' || process.env.NON_INTERACTIVE === 'true') {
|
|
175
177
|
console.log(chalk.yellow('🤖 Non-interactive mode detected, skipping Git hooks'));
|
|
176
|
-
return false;
|
|
178
|
+
return { prePush: false, commitMsg: false };
|
|
177
179
|
}
|
|
178
180
|
|
|
179
|
-
if (!await fs.pathExists('
|
|
180
|
-
console.log(chalk.yellow('⚠️ Skipping Git hooks setup (
|
|
181
|
-
return false;
|
|
181
|
+
if (!await fs.pathExists('.git')) {
|
|
182
|
+
console.log(chalk.yellow('⚠️ Skipping Git hooks setup (not a git repository)'));
|
|
183
|
+
return { prePush: false, commitMsg: false };
|
|
182
184
|
}
|
|
183
185
|
|
|
184
186
|
console.log('');
|
|
185
187
|
console.log('──────────────────────────────────────────────');
|
|
186
|
-
console.log(chalk.blue('⚙️ Git Hooks
|
|
188
|
+
console.log(chalk.blue('⚙️ Quality Gates — Git Hooks'));
|
|
187
189
|
console.log('──────────────────────────────────────────────');
|
|
188
|
-
console.log('ContextKit can install **pre-push** and **commit-msg** hooks');
|
|
189
|
-
console.log('to automatically run tests, linting, and type checks before pushing.');
|
|
190
190
|
console.log('');
|
|
191
|
+
|
|
192
|
+
// Ask about pre-push hook
|
|
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.'));
|
|
196
|
+
console.log('');
|
|
197
|
+
const { prePush } = await inquirer.prompt([
|
|
198
|
+
{
|
|
199
|
+
type: 'confirm',
|
|
200
|
+
name: 'prePush',
|
|
201
|
+
message: 'Enable pre-push hook? (runs quality checks before push)',
|
|
202
|
+
default: false
|
|
203
|
+
}
|
|
204
|
+
]);
|
|
205
|
+
|
|
206
|
+
if (prePush) {
|
|
207
|
+
console.log(chalk.green(' ✅ Pre-push hook enabled'));
|
|
208
|
+
} else {
|
|
209
|
+
console.log(chalk.yellow(' ⏭️ Skipping pre-push hook'));
|
|
210
|
+
}
|
|
191
211
|
console.log('');
|
|
192
212
|
|
|
193
|
-
|
|
213
|
+
// Ask about commit-msg hook
|
|
214
|
+
console.log(chalk.bold('Commit message hook'));
|
|
215
|
+
console.log(chalk.dim('Enforces conventional commit format:'));
|
|
216
|
+
console.log(chalk.dim(' <type>(<scope>): <description>'));
|
|
217
|
+
console.log(chalk.dim(' types: feat, fix, docs, style, refactor, test, chore'));
|
|
218
|
+
console.log(chalk.dim(' example: feat(auth): add login page'));
|
|
219
|
+
console.log('');
|
|
220
|
+
const { commitMsg } = await inquirer.prompt([
|
|
194
221
|
{
|
|
195
222
|
type: 'confirm',
|
|
196
|
-
name: '
|
|
197
|
-
message: '
|
|
223
|
+
name: 'commitMsg',
|
|
224
|
+
message: 'Enable commit-msg hook? (enforces conventional commit format)',
|
|
198
225
|
default: false
|
|
199
226
|
}
|
|
200
227
|
]);
|
|
201
228
|
|
|
202
|
-
if (
|
|
203
|
-
console.log(chalk.green('✅
|
|
229
|
+
if (commitMsg) {
|
|
230
|
+
console.log(chalk.green(' ✅ Commit message hook enabled'));
|
|
231
|
+
console.log(chalk.blue(' 💡 Commits must use: <type>(scope): description'));
|
|
204
232
|
} else {
|
|
205
|
-
console.log(chalk.yellow('⏭️ Skipping
|
|
206
|
-
console.log(chalk.blue('💡 You can enable them anytime with: `ck update --hooks`'));
|
|
233
|
+
console.log(chalk.yellow(' ⏭️ Skipping commit message hook'));
|
|
207
234
|
}
|
|
208
235
|
console.log('');
|
|
209
236
|
|
|
210
|
-
|
|
237
|
+
if (!prePush && !commitMsg) {
|
|
238
|
+
console.log(chalk.blue('💡 You can enable hooks anytime with: `ck update --hooks`'));
|
|
239
|
+
console.log('');
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return { prePush, commitMsg };
|
|
211
243
|
}
|
|
212
244
|
|
|
213
245
|
async createDirectoryStructure() {
|
|
@@ -751,9 +783,9 @@ esac
|
|
|
751
783
|
console.log(chalk.green('✅ CLI helpers installed'));
|
|
752
784
|
}
|
|
753
785
|
|
|
754
|
-
async createConfiguration(projectType,
|
|
786
|
+
async createConfiguration(projectType, hookChoices) {
|
|
755
787
|
const projectName = path.basename(process.cwd());
|
|
756
|
-
|
|
788
|
+
|
|
757
789
|
const config = {
|
|
758
790
|
version: '1.0.0',
|
|
759
791
|
project_name: projectName,
|
|
@@ -764,7 +796,8 @@ esac
|
|
|
764
796
|
code_review: true,
|
|
765
797
|
linting: true,
|
|
766
798
|
type_safety: true,
|
|
767
|
-
|
|
799
|
+
pre_push_hook: hookChoices.prePush,
|
|
800
|
+
commit_msg_hook: hookChoices.commitMsg
|
|
768
801
|
},
|
|
769
802
|
paths: {
|
|
770
803
|
components: 'src/components',
|
|
@@ -830,7 +863,8 @@ features:
|
|
|
830
863
|
code_review: ${config.features.code_review}
|
|
831
864
|
linting: ${config.features.linting}
|
|
832
865
|
type_safety: ${config.features.type_safety}
|
|
833
|
-
|
|
866
|
+
pre_push_hook: ${config.features.pre_push_hook}
|
|
867
|
+
commit_msg_hook: ${config.features.commit_msg_hook}
|
|
834
868
|
|
|
835
869
|
# Paths (customize for your project)
|
|
836
870
|
paths:
|
|
@@ -857,12 +891,13 @@ metadata:
|
|
|
857
891
|
);
|
|
858
892
|
}
|
|
859
893
|
|
|
860
|
-
async createStatusFile(projectType, packageManager,
|
|
894
|
+
async createStatusFile(projectType, packageManager, hookChoices) {
|
|
861
895
|
try {
|
|
862
896
|
const status = this.statusManager.getDefaultStatus();
|
|
863
897
|
status.project_type = projectType;
|
|
864
898
|
status.package_manager = packageManager;
|
|
865
|
-
status.features.
|
|
899
|
+
status.features.pre_push_hook = hookChoices.prePush;
|
|
900
|
+
status.features.commit_msg_hook = hookChoices.commitMsg;
|
|
866
901
|
|
|
867
902
|
await this.statusManager.saveStatus(status);
|
|
868
903
|
console.log(chalk.green('✅ Status tracking initialized'));
|
|
@@ -1378,7 +1413,7 @@ enforcement:
|
|
|
1378
1413
|
console.log(chalk.green('✅ Policy file created'));
|
|
1379
1414
|
}
|
|
1380
1415
|
|
|
1381
|
-
showSuccessMessage(
|
|
1416
|
+
showSuccessMessage(hookChoices, detectedTools = {}, projectType = '', packageManager = '') {
|
|
1382
1417
|
console.log('');
|
|
1383
1418
|
console.log(chalk.green('🎉 ContextKit v1.0.0 successfully installed!'));
|
|
1384
1419
|
console.log('');
|
package/lib/commands/status.js
CHANGED
|
@@ -48,7 +48,8 @@ class StatusCommand {
|
|
|
48
48
|
console.log('');
|
|
49
49
|
|
|
50
50
|
console.log(chalk.blue('✅ Features:'));
|
|
51
|
-
console.log(`
|
|
51
|
+
console.log(` Pre-push hook: ${status.features.pre_push_hook ? '✅' : '❌'}`);
|
|
52
|
+
console.log(` Commit-msg hook: ${status.features.commit_msg_hook ? '✅' : '❌'}`);
|
|
52
53
|
console.log(` Standards: ${status.features.standards ? '✅' : '❌'}`);
|
|
53
54
|
console.log(` Templates: ${status.features.templates ? '✅' : '❌'}`);
|
|
54
55
|
console.log('');
|
|
@@ -121,8 +122,15 @@ class StatusCommand {
|
|
|
121
122
|
config.features = config.features || {};
|
|
122
123
|
config.features.type_safety = trimmed.split('type_safety:')[1].trim() === 'true';
|
|
123
124
|
} else if (trimmed.startsWith('git_hooks:')) {
|
|
125
|
+
// Legacy single-flag compat
|
|
124
126
|
config.features = config.features || {};
|
|
125
127
|
config.features.git_hooks = trimmed.split('git_hooks:')[1].trim() === 'true';
|
|
128
|
+
} else if (trimmed.startsWith('pre_push_hook:')) {
|
|
129
|
+
config.features = config.features || {};
|
|
130
|
+
config.features.pre_push_hook = trimmed.split('pre_push_hook:')[1].trim() === 'true';
|
|
131
|
+
} else if (trimmed.startsWith('commit_msg_hook:')) {
|
|
132
|
+
config.features = config.features || {};
|
|
133
|
+
config.features.commit_msg_hook = trimmed.split('commit_msg_hook:')[1].trim() === 'true';
|
|
126
134
|
}
|
|
127
135
|
}
|
|
128
136
|
|
package/lib/commands/update.js
CHANGED
|
@@ -59,8 +59,12 @@ class UpdateCommand {
|
|
|
59
59
|
await this.restoreUserConfig(config);
|
|
60
60
|
|
|
61
61
|
// Update Git hooks if they were enabled
|
|
62
|
-
|
|
63
|
-
|
|
62
|
+
const hookChoices = {
|
|
63
|
+
prePush: config.features?.pre_push_hook || config.features?.git_hooks || false,
|
|
64
|
+
commitMsg: config.features?.commit_msg_hook || config.features?.git_hooks || false
|
|
65
|
+
};
|
|
66
|
+
if (hookChoices.prePush || hookChoices.commitMsg) {
|
|
67
|
+
await this.gitHooksManager.installHooks(packageManager, hookChoices);
|
|
64
68
|
}
|
|
65
69
|
|
|
66
70
|
// Refresh installed platform integrations
|
|
@@ -176,8 +180,15 @@ class UpdateCommand {
|
|
|
176
180
|
config.features = config.features || {};
|
|
177
181
|
config.features.type_safety = trimmed.split('type_safety:')[1].trim() === 'true';
|
|
178
182
|
} else if (trimmed.startsWith('git_hooks:')) {
|
|
183
|
+
// Legacy single-flag compat
|
|
179
184
|
config.features = config.features || {};
|
|
180
185
|
config.features.git_hooks = trimmed.split('git_hooks:')[1].trim() === 'true';
|
|
186
|
+
} else if (trimmed.startsWith('pre_push_hook:')) {
|
|
187
|
+
config.features = config.features || {};
|
|
188
|
+
config.features.pre_push_hook = trimmed.split('pre_push_hook:')[1].trim() === 'true';
|
|
189
|
+
} else if (trimmed.startsWith('commit_msg_hook:')) {
|
|
190
|
+
config.features = config.features || {};
|
|
191
|
+
config.features.commit_msg_hook = trimmed.split('commit_msg_hook:')[1].trim() === 'true';
|
|
181
192
|
}
|
|
182
193
|
}
|
|
183
194
|
|
|
@@ -294,7 +305,8 @@ features:
|
|
|
294
305
|
code_review: ${config.features?.code_review || true}
|
|
295
306
|
linting: ${config.features?.linting || true}
|
|
296
307
|
type_safety: ${config.features?.type_safety || true}
|
|
297
|
-
|
|
308
|
+
pre_push_hook: ${config.features?.pre_push_hook || config.features?.git_hooks || false}
|
|
309
|
+
commit_msg_hook: ${config.features?.commit_msg_hook || config.features?.git_hooks || false}
|
|
298
310
|
|
|
299
311
|
# Paths (customize for your project)
|
|
300
312
|
paths:
|
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
|
-
async installHooks(packageManager) {
|
|
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) {
|
|
33
|
+
async addHooks(hookChoices = { prePush: true, commitMsg: true }) {
|
|
152
34
|
// Backup existing hooks
|
|
153
35
|
await this.backupExistingHooks();
|
|
154
36
|
|
|
@@ -162,42 +44,68 @@ fi
|
|
|
162
44
|
}
|
|
163
45
|
}
|
|
164
46
|
|
|
165
|
-
// Add
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
for (const hook of hooks) {
|
|
172
|
-
await this.addHook(packageManager, hook.name, hook.script);
|
|
47
|
+
// Add selected hooks
|
|
48
|
+
if (hookChoices.prePush) {
|
|
49
|
+
await this.addNativeHook('pre-push', '.contextkit/hooks/pre-push.sh');
|
|
50
|
+
}
|
|
51
|
+
if (hookChoices.commitMsg) {
|
|
52
|
+
await this.addNativeHook('commit-msg', '.contextkit/hooks/commit-msg.sh');
|
|
173
53
|
}
|
|
174
54
|
}
|
|
175
55
|
|
|
176
|
-
async
|
|
56
|
+
async addNativeHook(hookName, scriptPath) {
|
|
177
57
|
const hookPath = `${this.hooksDir}/${hookName}`;
|
|
178
|
-
|
|
179
|
-
// Ensure .
|
|
58
|
+
|
|
59
|
+
// Ensure .git/hooks directory exists
|
|
180
60
|
await fs.ensureDir(this.hooksDir);
|
|
181
|
-
|
|
182
|
-
// Create hook file directly (modern Husky approach)
|
|
183
|
-
const hookContent = `#!/usr/bin/env sh
|
|
184
|
-
. "$(dirname -- "$0")/_/husky.sh"
|
|
185
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
|
|
186
65
|
${scriptPath} "$@"
|
|
187
66
|
`;
|
|
188
|
-
|
|
67
|
+
|
|
189
68
|
await fs.writeFile(hookPath, hookContent);
|
|
190
69
|
await fs.chmod(hookPath, '755');
|
|
191
|
-
|
|
70
|
+
|
|
192
71
|
console.log(chalk.green(`✅ Created hook: ${hookName}`));
|
|
193
72
|
}
|
|
194
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
|
+
|
|
195
99
|
async backupExistingHooks() {
|
|
196
100
|
const hooks = ['pre-push', 'commit-msg'];
|
|
197
101
|
|
|
198
102
|
for (const hook of hooks) {
|
|
199
103
|
const hookPath = `${this.hooksDir}/${hook}`;
|
|
200
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
|
+
|
|
201
109
|
const backupPath = `${hookPath}.backup`;
|
|
202
110
|
await fs.copy(hookPath, backupPath);
|
|
203
111
|
console.log(chalk.yellow(`📦 Backed up existing hook: ${hook}`));
|
|
@@ -205,24 +113,15 @@ ${scriptPath} "$@"
|
|
|
205
113
|
}
|
|
206
114
|
}
|
|
207
115
|
|
|
208
|
-
checkCommandExists(command) {
|
|
209
|
-
try {
|
|
210
|
-
execSync(`which ${command}`, { stdio: 'pipe' });
|
|
211
|
-
return true;
|
|
212
|
-
} catch (error) {
|
|
213
|
-
return false;
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
116
|
async uninstallHooks() {
|
|
218
117
|
console.log(chalk.yellow('🪝 Removing Git hooks...'));
|
|
219
|
-
|
|
118
|
+
|
|
220
119
|
const hooks = ['pre-push', 'commit-msg'];
|
|
221
120
|
|
|
222
121
|
for (const hook of hooks) {
|
|
223
122
|
const hookPath = `${this.hooksDir}/${hook}`;
|
|
224
123
|
const backupPath = `${hookPath}.backup`;
|
|
225
|
-
|
|
124
|
+
|
|
226
125
|
if (fs.existsSync(backupPath)) {
|
|
227
126
|
await fs.move(backupPath, hookPath);
|
|
228
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": {
|