@nolrm/contextkit 0.7.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.
@@ -0,0 +1,88 @@
1
+ const chalk = require('chalk');
2
+ const BaseIntegration = require('./base-integration');
3
+
4
+ class WindsurfIntegration extends BaseIntegration {
5
+ constructor() {
6
+ super();
7
+ this.name = 'windsurf';
8
+ this.displayName = 'Windsurf';
9
+ this.bridgeFiles = ['.windsurfrules'];
10
+ this.generatedFiles = ['.windsurf/rules/contextkit-standards.md'];
11
+ this.platformDir = '.windsurf/rules';
12
+ }
13
+
14
+ async install() {
15
+ await super.install();
16
+ // Remove old vibe-kit-named files
17
+ const fs = require('fs-extra');
18
+ const legacyFile = '.windsurf/rules/vibe-kit-standards.md';
19
+ if (await fs.pathExists(legacyFile)) {
20
+ await fs.remove(legacyFile);
21
+ }
22
+ }
23
+
24
+ async generateFiles() {
25
+ // Bridge file: .windsurfrules (auto-loaded by Windsurf)
26
+ const bridgeContent = `# Project Standards (ContextKit)
27
+
28
+ This project uses [ContextKit](https://github.com/nolrm/contextkit) for AI development standards.
29
+
30
+ ${this.getStandardsBlock()}
31
+
32
+ ## Key Rules
33
+
34
+ - Follow coding conventions in \`.contextkit/standards/code-style.md\`
35
+ - Use numbered test cases as defined in \`.contextkit/standards/testing.md\`
36
+ - Check \`.contextkit/standards/glossary.md\` for project-specific terminology
37
+ - Reference \`.contextkit/templates/\` for code generation patterns`;
38
+
39
+ await this.writeBridgeFile('.windsurfrules', bridgeContent);
40
+
41
+ // Modular rules file
42
+ const standardsRule = `---
43
+ description: ContextKit project standards
44
+ alwaysApply: true
45
+ ---
46
+
47
+ # ContextKit Standards
48
+
49
+ This project uses ContextKit for structured development standards.
50
+
51
+ ## References
52
+
53
+ - \`.contextkit/standards/code-style.md\` — Coding conventions
54
+ - \`.contextkit/standards/testing.md\` — Testing patterns (numbered test cases required)
55
+ - \`.contextkit/standards/architecture.md\` — Architecture patterns
56
+ - \`.contextkit/standards/ai-guidelines.md\` — AI behavior rules
57
+ - \`.contextkit/standards/glossary.md\` — Project terminology
58
+ - \`.contextkit/product/mission-lite.md\` — Product context
59
+
60
+ ## Templates
61
+
62
+ - \`.contextkit/templates/component.tsx\` — Component template
63
+ - \`.contextkit/templates/test.tsx\` — Test template
64
+ - \`.contextkit/templates/hook.ts\` — Custom hook template
65
+ - \`.contextkit/templates/api.ts\` — API service template
66
+
67
+ ## Commands
68
+
69
+ - \`.contextkit/commands/analyze.md\` — Analyze and customize standards
70
+ - \`.contextkit/commands/create-component.md\` — Create components
71
+ - \`.contextkit/commands/create-feature.md\` — Create features
72
+ `;
73
+ await this.writeGeneratedFile('.windsurf/rules/contextkit-standards.md', standardsRule);
74
+ }
75
+
76
+ showUsage() {
77
+ console.log('');
78
+ console.log(chalk.bold(' Windsurf Usage:'));
79
+ console.log(' .windsurfrules is auto-loaded by Windsurf');
80
+ console.log(' .windsurf/rules/ provides additional context');
81
+ console.log('');
82
+ console.log(chalk.dim(' Files created:'));
83
+ console.log(chalk.dim(' .windsurfrules (bridge - auto-loaded)'));
84
+ console.log(chalk.dim(' .windsurf/rules/contextkit-standards.md (rules)'));
85
+ }
86
+ }
87
+
88
+ module.exports = WindsurfIntegration;
@@ -0,0 +1,50 @@
1
+ const axios = require('axios');
2
+ const fs = require('fs-extra');
3
+ const path = require('path');
4
+
5
+ class DownloadManager {
6
+ constructor() {
7
+ this.timeout = 30000; // 30 seconds
8
+ this.retries = 3;
9
+ }
10
+
11
+ async downloadFile(url, outputPath, retryCount = 0) {
12
+ try {
13
+ // Ensure directory exists
14
+ await fs.ensureDir(path.dirname(outputPath));
15
+
16
+ const response = await axios({
17
+ method: 'GET',
18
+ url: url,
19
+ responseType: 'stream',
20
+ timeout: this.timeout,
21
+ headers: {
22
+ 'User-Agent': 'contextkit-cli/1.0.0'
23
+ }
24
+ });
25
+
26
+ const writer = fs.createWriteStream(outputPath);
27
+ response.data.pipe(writer);
28
+
29
+ return new Promise((resolve, reject) => {
30
+ writer.on('finish', resolve);
31
+ writer.on('error', reject);
32
+ });
33
+
34
+ } catch (error) {
35
+ if (retryCount < this.retries) {
36
+ console.log(`Retrying download (${retryCount + 1}/${this.retries})...`);
37
+ await new Promise(resolve => setTimeout(resolve, 1000 * (retryCount + 1)));
38
+ return this.downloadFile(url, outputPath, retryCount + 1);
39
+ }
40
+ throw new Error(`Failed to download ${url}: ${error.message}`);
41
+ }
42
+ }
43
+
44
+ async downloadMultiple(files) {
45
+ const promises = files.map(({ url, path }) => this.downloadFile(url, path));
46
+ return Promise.all(promises);
47
+ }
48
+ }
49
+
50
+ module.exports = DownloadManager;
@@ -0,0 +1,228 @@
1
+ const { execSync } = require('child_process');
2
+ const fs = require('fs-extra');
3
+ const chalk = require('chalk');
4
+
5
+ class GitHooksManager {
6
+ constructor() {
7
+ this.hooksDir = '.husky';
8
+ }
9
+
10
+ async installHooks(packageManager) {
11
+ console.log(chalk.yellow('🪝 Setting up Git hooks...'));
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'));
16
+ return;
17
+ }
18
+
19
+ try {
20
+ // Install Husky based on package manager
21
+ await this.installHusky(packageManager);
22
+
23
+ // Initialize Husky
24
+ await this.initializeHusky(packageManager);
25
+
26
+ // Add hooks
27
+ await this.addHooks(packageManager);
28
+
29
+ console.log(chalk.green('✅ Git hooks setup complete'));
30
+
31
+ } catch (error) {
32
+ console.log(chalk.red('❌ Git hooks setup failed:'), error.message);
33
+ throw error;
34
+ }
35
+ }
36
+
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) {
152
+ // Backup existing hooks
153
+ await this.backupExistingHooks();
154
+
155
+ // Add new hooks
156
+ const hooks = [
157
+ { name: 'pre-commit', script: '.contextkit/hooks/pre-commit.sh' },
158
+ { name: 'pre-push', script: '.contextkit/hooks/pre-push.sh' },
159
+ { name: 'commit-msg', script: '.contextkit/hooks/commit-msg.sh' }
160
+ ];
161
+
162
+ for (const hook of hooks) {
163
+ await this.addHook(packageManager, hook.name, hook.script);
164
+ }
165
+ }
166
+
167
+ async addHook(packageManager, hookName, scriptPath) {
168
+ const hookPath = `${this.hooksDir}/${hookName}`;
169
+
170
+ // Ensure .husky directory exists
171
+ await fs.ensureDir(this.hooksDir);
172
+
173
+ // Create hook file directly (modern Husky approach)
174
+ const hookContent = `#!/usr/bin/env sh
175
+ . "$(dirname -- "$0")/_/husky.sh"
176
+
177
+ ${scriptPath}
178
+ `;
179
+
180
+ await fs.writeFile(hookPath, hookContent);
181
+ await fs.chmod(hookPath, '755');
182
+
183
+ console.log(chalk.green(`✅ Created hook: ${hookName}`));
184
+ }
185
+
186
+ async backupExistingHooks() {
187
+ const hooks = ['pre-commit', 'pre-push', 'commit-msg'];
188
+
189
+ for (const hook of hooks) {
190
+ const hookPath = `${this.hooksDir}/${hook}`;
191
+ if (fs.existsSync(hookPath)) {
192
+ const backupPath = `${hookPath}.backup`;
193
+ await fs.copy(hookPath, backupPath);
194
+ console.log(chalk.yellow(`📦 Backed up existing hook: ${hook}`));
195
+ }
196
+ }
197
+ }
198
+
199
+ checkCommandExists(command) {
200
+ try {
201
+ execSync(`which ${command}`, { stdio: 'pipe' });
202
+ return true;
203
+ } catch (error) {
204
+ return false;
205
+ }
206
+ }
207
+
208
+ async uninstallHooks() {
209
+ console.log(chalk.yellow('🪝 Removing Git hooks...'));
210
+
211
+ const hooks = ['pre-commit', 'pre-push', 'commit-msg'];
212
+
213
+ for (const hook of hooks) {
214
+ const hookPath = `${this.hooksDir}/${hook}`;
215
+ const backupPath = `${hookPath}.backup`;
216
+
217
+ if (fs.existsSync(backupPath)) {
218
+ await fs.move(backupPath, hookPath);
219
+ console.log(chalk.green(`✅ Restored original hook: ${hook}`));
220
+ } else if (fs.existsSync(hookPath)) {
221
+ await fs.remove(hookPath);
222
+ console.log(chalk.green(`✅ Removed hook: ${hook}`));
223
+ }
224
+ }
225
+ }
226
+ }
227
+
228
+ module.exports = GitHooksManager;
@@ -0,0 +1,110 @@
1
+ const fs = require('fs-extra');
2
+ const path = require('path');
3
+
4
+ class ProjectDetector {
5
+ constructor() {
6
+ this.projectRoot = process.cwd();
7
+ }
8
+
9
+ detectProjectType() {
10
+ if (fs.existsSync('package.json')) {
11
+ const packageJson = this.readPackageJson();
12
+
13
+ // Detect framework
14
+ if (this.hasDependency(packageJson, 'react') || this.hasDependency(packageJson, '@types/react')) {
15
+ return this.detectReactVariant(packageJson);
16
+ } else if (this.hasDependency(packageJson, 'vue') || this.hasDependency(packageJson, '@vue')) {
17
+ return this.detectVueVariant(packageJson);
18
+ } else if (this.hasDependency(packageJson, '@angular/core')) {
19
+ return 'angular';
20
+ } else if (this.hasDependency(packageJson, 'next')) {
21
+ return 'nextjs';
22
+ } else if (this.hasDependency(packageJson, 'nuxt')) {
23
+ return 'nuxt';
24
+ } else if (this.hasDependency(packageJson, 'svelte')) {
25
+ return 'svelte';
26
+ } else {
27
+ return 'node';
28
+ }
29
+ } else if (fs.existsSync('requirements.txt') || fs.existsSync('pyproject.toml')) {
30
+ return 'python';
31
+ } else if (fs.existsSync('Cargo.toml')) {
32
+ return 'rust';
33
+ } else if (fs.existsSync('go.mod')) {
34
+ return 'go';
35
+ } else if (fs.existsSync('composer.json')) {
36
+ return 'php';
37
+ } else if (fs.existsSync('Gemfile')) {
38
+ return 'ruby';
39
+ } else if (fs.existsSync('pom.xml') || fs.existsSync('build.gradle')) {
40
+ return 'java';
41
+ }
42
+
43
+ return 'generic';
44
+ }
45
+
46
+ detectReactVariant(packageJson) {
47
+ if (this.hasDependency(packageJson, 'vite')) {
48
+ return 'react-vite';
49
+ } else if (this.hasDependency(packageJson, 'webpack')) {
50
+ return 'react-webpack';
51
+ } else if (this.hasDependency(packageJson, 'rollup')) {
52
+ return 'react-rollup';
53
+ } else if (this.hasDependency(packageJson, 'next')) {
54
+ return 'nextjs';
55
+ }
56
+ return 'react';
57
+ }
58
+
59
+ detectVueVariant(packageJson) {
60
+ if (this.hasDependency(packageJson, 'vite')) {
61
+ return 'vue-vite';
62
+ } else if (this.hasDependency(packageJson, 'webpack')) {
63
+ return 'vue-webpack';
64
+ } else if (this.hasDependency(packageJson, 'nuxt')) {
65
+ return 'nuxt';
66
+ }
67
+ return 'vue';
68
+ }
69
+
70
+ detectPackageManager() {
71
+ if (fs.existsSync('yarn.lock')) {
72
+ return 'yarn';
73
+ } else if (fs.existsSync('pnpm-lock.yaml')) {
74
+ return 'pnpm';
75
+ } else if (fs.existsSync('package-lock.json')) {
76
+ return 'npm';
77
+ } else if (fs.existsSync('package.json')) {
78
+ return 'npm'; // Default to npm
79
+ }
80
+ return 'none';
81
+ }
82
+
83
+ hasDependency(packageJson, dependency) {
84
+ const deps = packageJson.dependencies || {};
85
+ const devDeps = packageJson.devDependencies || {};
86
+ return deps[dependency] || devDeps[dependency];
87
+ }
88
+
89
+ readPackageJson() {
90
+ try {
91
+ return JSON.parse(fs.readFileSync('package.json', 'utf8'));
92
+ } catch (error) {
93
+ return {};
94
+ }
95
+ }
96
+
97
+ isGitRepository() {
98
+ return fs.existsSync('.git');
99
+ }
100
+
101
+ getProjectName() {
102
+ if (fs.existsSync('package.json')) {
103
+ const packageJson = this.readPackageJson();
104
+ return packageJson.name || path.basename(this.projectRoot);
105
+ }
106
+ return path.basename(this.projectRoot);
107
+ }
108
+ }
109
+
110
+ module.exports = ProjectDetector;
@@ -0,0 +1,107 @@
1
+ const fs = require('fs-extra');
2
+ const path = require('path');
3
+
4
+ class StatusManager {
5
+ constructor() {
6
+ this.statusPath = '.contextkit/status.json';
7
+ }
8
+
9
+ async getStatus() {
10
+ try {
11
+ if (await fs.pathExists(this.statusPath)) {
12
+ return await fs.readJson(this.statusPath);
13
+ }
14
+ } catch (error) {
15
+ console.warn('Warning: Could not read status file:', error.message);
16
+ }
17
+ return this.getDefaultStatus();
18
+ }
19
+
20
+ getDefaultStatus() {
21
+ const packageJson = require('../../package.json');
22
+ return {
23
+ version: packageJson.version,
24
+ installed_at: new Date().toISOString(),
25
+ last_updated: new Date().toISOString(),
26
+ analyze: {
27
+ run: false,
28
+ last_run: null,
29
+ customizations: [],
30
+ project_type: null,
31
+ package_manager: null
32
+ },
33
+ features: {
34
+ git_hooks: false,
35
+ standards: true,
36
+ templates: true
37
+ }
38
+ };
39
+ }
40
+
41
+ async saveStatus(status) {
42
+ try {
43
+ await fs.ensureDir('.contextkit');
44
+ await fs.writeJson(this.statusPath, status, { spaces: 2 });
45
+ } catch (error) {
46
+ throw new Error(`Failed to save status: ${error.message}`);
47
+ }
48
+ }
49
+
50
+ async updateAnalyzeStatus(analyzeData) {
51
+ const status = await this.getStatus();
52
+ status.analyze = {
53
+ run: true,
54
+ last_run: new Date().toISOString(),
55
+ customizations: analyzeData.customizations || [],
56
+ project_type: analyzeData.project_type,
57
+ package_manager: analyzeData.package_manager
58
+ };
59
+ status.last_updated = new Date().toISOString();
60
+ await this.saveStatus(status);
61
+ }
62
+
63
+ async updateFeatureStatus(feature, enabled) {
64
+ const status = await this.getStatus();
65
+ status.features[feature] = enabled;
66
+ status.last_updated = new Date().toISOString();
67
+ await this.saveStatus(status);
68
+ }
69
+
70
+ async isFirstTimeAnalyze() {
71
+ const status = await this.getStatus();
72
+ return !status.analyze.run;
73
+ }
74
+
75
+ async getAnalyzeInfo() {
76
+ const status = await this.getStatus();
77
+ return {
78
+ isFirstTime: !status.analyze.run,
79
+ lastRun: status.analyze.last_run,
80
+ projectType: status.analyze.project_type,
81
+ packageManager: status.analyze.package_manager,
82
+ customizations: status.analyze.customizations
83
+ };
84
+ }
85
+
86
+ async updateVersion(version) {
87
+ const status = await this.getStatus();
88
+ status.version = version;
89
+ status.last_updated = new Date().toISOString();
90
+ await this.saveStatus(status);
91
+ }
92
+
93
+ async resetAnalyzeStatus() {
94
+ const status = await this.getStatus();
95
+ status.analyze = {
96
+ run: false,
97
+ last_run: null,
98
+ customizations: [],
99
+ project_type: null,
100
+ package_manager: null
101
+ };
102
+ status.last_updated = new Date().toISOString();
103
+ await this.saveStatus(status);
104
+ }
105
+ }
106
+
107
+ module.exports = StatusManager;