@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.
- package/LICENSE +21 -0
- package/README.md +216 -0
- package/bin/contextkit.js +324 -0
- package/bin/vibe-kit.js +3 -0
- package/install-fallback.sh +59 -0
- package/lib/commands/ai.js +147 -0
- package/lib/commands/analyze.js +544 -0
- package/lib/commands/check.js +290 -0
- package/lib/commands/dashboard.js +383 -0
- package/lib/commands/install.js +1454 -0
- package/lib/commands/note.js +120 -0
- package/lib/commands/publish.js +184 -0
- package/lib/commands/pull.js +191 -0
- package/lib/commands/run.js +232 -0
- package/lib/commands/status.js +253 -0
- package/lib/commands/update.js +376 -0
- package/lib/index.js +9 -0
- package/lib/integrations/aider-integration.js +93 -0
- package/lib/integrations/base-integration.js +123 -0
- package/lib/integrations/claude-integration.js +141 -0
- package/lib/integrations/codex-integration.js +45 -0
- package/lib/integrations/continue-integration.js +99 -0
- package/lib/integrations/copilot-integration.js +73 -0
- package/lib/integrations/cursor-integration.js +162 -0
- package/lib/integrations/gemini-integration.js +62 -0
- package/lib/integrations/index.js +33 -0
- package/lib/integrations/windsurf-integration.js +88 -0
- package/lib/utils/download.js +50 -0
- package/lib/utils/git-hooks.js +228 -0
- package/lib/utils/project-detector.js +110 -0
- package/lib/utils/status-manager.js +107 -0
- package/lib/utils/tool-detector.js +137 -0
- package/package.json +85 -0
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
const ora = require('ora');
|
|
3
|
+
const fs = require('fs-extra');
|
|
4
|
+
const { execSync } = require('child_process');
|
|
5
|
+
|
|
6
|
+
class AICommand {
|
|
7
|
+
async run(prompt, options = {}) {
|
|
8
|
+
if (!prompt) {
|
|
9
|
+
console.log(chalk.red('ā Please provide a prompt'));
|
|
10
|
+
console.log(chalk.blue(' Usage: contextkit ai "your prompt here"'));
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Check if contextkit is installed
|
|
15
|
+
if (!await fs.pathExists('.contextkit/context.md')) {
|
|
16
|
+
console.log(chalk.red('ā ContextKit not initialized.'));
|
|
17
|
+
console.log(chalk.yellow(' Run: contextkit install'));
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Get AI tool preference
|
|
22
|
+
const aiTool = options.ai || process.env.AI_TOOL || 'display';
|
|
23
|
+
|
|
24
|
+
const spinner = ora('Loading ContextKit context...').start();
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
// Load context
|
|
28
|
+
const context = await fs.readFile('.contextkit/context.md', 'utf-8');
|
|
29
|
+
const standards = await this.loadStandards();
|
|
30
|
+
|
|
31
|
+
spinner.succeed('Context loaded\n');
|
|
32
|
+
|
|
33
|
+
// Prepare full prompt
|
|
34
|
+
const fullPrompt = `${context}\n\n## Project Standards\n\n${standards}\n\n## User Request\n\n${prompt}`;
|
|
35
|
+
|
|
36
|
+
// Execute with AI tool
|
|
37
|
+
await this.executeWithAI(fullPrompt, aiTool, prompt);
|
|
38
|
+
|
|
39
|
+
} catch (error) {
|
|
40
|
+
spinner.fail('Failed to load context');
|
|
41
|
+
console.log(chalk.red(error.message));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async loadStandards() {
|
|
46
|
+
const standards = {
|
|
47
|
+
codeStyle: '',
|
|
48
|
+
testing: '',
|
|
49
|
+
architecture: '',
|
|
50
|
+
guidelines: ''
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
if (await fs.pathExists('.contextkit/standards/code-style.md')) {
|
|
55
|
+
standards.codeStyle = await fs.readFile('.contextkit/standards/code-style.md', 'utf-8');
|
|
56
|
+
}
|
|
57
|
+
if (await fs.pathExists('.contextkit/standards/testing.md')) {
|
|
58
|
+
standards.testing = await fs.readFile('.contextkit/standards/testing.md', 'utf-8');
|
|
59
|
+
}
|
|
60
|
+
if (await fs.pathExists('.contextkit/standards/architecture.md')) {
|
|
61
|
+
standards.architecture = await fs.readFile('.contextkit/standards/architecture.md', 'utf-8');
|
|
62
|
+
}
|
|
63
|
+
if (await fs.pathExists('.contextkit/standards/ai-guidelines.md')) {
|
|
64
|
+
standards.guidelines = await fs.readFile('.contextkit/standards/ai-guidelines.md', 'utf-8');
|
|
65
|
+
}
|
|
66
|
+
} catch (error) {
|
|
67
|
+
console.log(chalk.yellow('ā ļø Some standards files not found'));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return `Code Style: ${standards.codeStyle.substring(0, 500)}...\n\nTesting: ${standards.testing.substring(0, 500)}...\n\nArchitecture: ${standards.architecture.substring(0, 500)}...\n\nGuidelines: ${standards.guidelines.substring(0, 500)}...`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async executeWithAI(fullPrompt, aiTool, userPrompt) {
|
|
74
|
+
console.log(chalk.blue(`š¤ Using AI tool: ${aiTool}\n`));
|
|
75
|
+
|
|
76
|
+
switch(aiTool) {
|
|
77
|
+
case 'aider':
|
|
78
|
+
await this.executeWithAider(userPrompt);
|
|
79
|
+
break;
|
|
80
|
+
case 'claude':
|
|
81
|
+
await this.executeWithClaude(userPrompt, fullPrompt);
|
|
82
|
+
break;
|
|
83
|
+
case 'gemini':
|
|
84
|
+
await this.executeWithGemini(userPrompt, fullPrompt);
|
|
85
|
+
break;
|
|
86
|
+
case 'display':
|
|
87
|
+
default:
|
|
88
|
+
this.displayPrompt(fullPrompt, aiTool);
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async executeWithAider(prompt) {
|
|
94
|
+
console.log(chalk.green('š Starting Aider...\n'));
|
|
95
|
+
console.log(chalk.blue('š” Aider will use .aider/rules.md for context\n'));
|
|
96
|
+
console.log(chalk.yellow(`Prompt: ${prompt}\n`));
|
|
97
|
+
console.log('ā'.repeat(60));
|
|
98
|
+
try {
|
|
99
|
+
execSync(`echo "${prompt}" | aider`, { stdio: 'inherit' });
|
|
100
|
+
} catch (error) {
|
|
101
|
+
console.log(chalk.red(`Aider error: ${error.message}`));
|
|
102
|
+
console.log(chalk.yellow('š” Make sure Aider is installed and in your PATH'));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async executeWithClaude(prompt, context) {
|
|
107
|
+
console.log(chalk.green('š Starting Claude CLI...\n'));
|
|
108
|
+
console.log('ā'.repeat(60));
|
|
109
|
+
try {
|
|
110
|
+
execSync(`echo "${context}\n\nUser: ${prompt}" | claude`, { stdio: 'inherit' });
|
|
111
|
+
} catch (error) {
|
|
112
|
+
console.log(chalk.red(`Claude error: ${error.message}`));
|
|
113
|
+
console.log(chalk.yellow('š” Make sure Claude CLI is installed'));
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async executeWithGemini(prompt, context) {
|
|
118
|
+
console.log(chalk.green('š Starting Gemini CLI...\n'));
|
|
119
|
+
console.log('ā'.repeat(60));
|
|
120
|
+
try {
|
|
121
|
+
execSync(`echo "${context}\n\nUser: ${prompt}" | gemini`, { stdio: 'inherit' });
|
|
122
|
+
} catch (error) {
|
|
123
|
+
console.log(chalk.red(`Gemini error: ${error.message}`));
|
|
124
|
+
console.log(chalk.yellow('š” Make sure Gemini CLI is installed'));
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
displayPrompt(fullPrompt, aiTool) {
|
|
129
|
+
console.log(chalk.yellow('\nš Full Prompt (display mode):\n'));
|
|
130
|
+
console.log('ā'.repeat(60));
|
|
131
|
+
console.log(fullPrompt);
|
|
132
|
+
console.log('ā'.repeat(60));
|
|
133
|
+
|
|
134
|
+
console.log(chalk.blue('\nš” To execute with AI:'));
|
|
135
|
+
console.log(' Set AI_TOOL environment variable:');
|
|
136
|
+
console.log(' export AI_TOOL=aider && contextkit ai "your prompt"');
|
|
137
|
+
console.log(' export AI_TOOL=claude && contextkit ai "your prompt"');
|
|
138
|
+
console.log(' export AI_TOOL=gemini && contextkit ai "your prompt"\n');
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async function ai(prompt, options) {
|
|
143
|
+
const cmd = new AICommand();
|
|
144
|
+
await cmd.run(prompt, options);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
module.exports = ai;
|
|
@@ -0,0 +1,544 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
const ora = require('ora');
|
|
3
|
+
const fs = require('fs-extra');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const inquirer = require('inquirer');
|
|
6
|
+
|
|
7
|
+
class AnalyzeCommand {
|
|
8
|
+
constructor() {
|
|
9
|
+
this.repoUrl = 'https://raw.githubusercontent.com/nolrm/contextkit/main';
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async run(options = {}) {
|
|
13
|
+
console.log(chalk.magenta('š ContextKit Project Analysis\n'));
|
|
14
|
+
|
|
15
|
+
// Check if contextkit is installed
|
|
16
|
+
if (!await fs.pathExists('.contextkit/commands/analyze.md')) {
|
|
17
|
+
console.log(chalk.red('ā ContextKit not found.'));
|
|
18
|
+
console.log(chalk.yellow(' Run: contextkit install'));
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const spinner = ora('Detecting project structure...').start();
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
// Detect monorepo structure
|
|
26
|
+
const monorepoStructure = await this.detectMonorepoStructure();
|
|
27
|
+
|
|
28
|
+
// Determine analysis scope
|
|
29
|
+
let analysisScope;
|
|
30
|
+
|
|
31
|
+
// Handle specific package option
|
|
32
|
+
if (options.package) {
|
|
33
|
+
const packagePath = path.resolve(options.package);
|
|
34
|
+
if (!await fs.pathExists(packagePath)) {
|
|
35
|
+
spinner.fail(`Package path not found: ${options.package}`);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
spinner.succeed(`Analyzing specific package: ${options.package}\n`);
|
|
40
|
+
analysisScope = {
|
|
41
|
+
scope: 'package',
|
|
42
|
+
paths: [packagePath],
|
|
43
|
+
packages: [{ path: packagePath, name: path.basename(packagePath) }]
|
|
44
|
+
};
|
|
45
|
+
} else if (monorepoStructure.isMonorepo) {
|
|
46
|
+
spinner.succeed('Monorepo detected!\n');
|
|
47
|
+
|
|
48
|
+
if (options.scope) {
|
|
49
|
+
// Use provided scope
|
|
50
|
+
analysisScope = await this.getScopeFromOption(options.scope, monorepoStructure);
|
|
51
|
+
} else if (options.nonInteractive) {
|
|
52
|
+
// Non-interactive: analyze current directory
|
|
53
|
+
analysisScope = { scope: 'current', paths: [process.cwd()], packages: [] };
|
|
54
|
+
} else {
|
|
55
|
+
// Interactive: prompt user
|
|
56
|
+
analysisScope = await this.promptAnalysisScope(monorepoStructure);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
this.displayScopeInfo(analysisScope, monorepoStructure);
|
|
60
|
+
} else {
|
|
61
|
+
spinner.succeed('Single package detected\n');
|
|
62
|
+
analysisScope = { scope: 'current', paths: [process.cwd()], packages: [] };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Load project context for selected scope
|
|
66
|
+
const contextSpinner = ora('Loading project context...').start();
|
|
67
|
+
const context = await this.loadProjectContext(analysisScope);
|
|
68
|
+
contextSpinner.succeed('Context loaded successfully\n');
|
|
69
|
+
|
|
70
|
+
// Load analyze.md instructions
|
|
71
|
+
const analyzeInstructions = await fs.readFile('.contextkit/commands/analyze.md', 'utf-8');
|
|
72
|
+
|
|
73
|
+
// Display instructions
|
|
74
|
+
console.log(chalk.yellow('š Analysis Instructions:\n'));
|
|
75
|
+
console.log('ā'.repeat(60));
|
|
76
|
+
console.log(analyzeInstructions);
|
|
77
|
+
console.log('ā'.repeat(60));
|
|
78
|
+
|
|
79
|
+
console.log(chalk.blue('\nš Project Context:\n'));
|
|
80
|
+
console.log('ā'.repeat(60));
|
|
81
|
+
console.log(JSON.stringify({
|
|
82
|
+
...context,
|
|
83
|
+
analysisScope: analysisScope.scope,
|
|
84
|
+
analyzedPaths: analysisScope.paths
|
|
85
|
+
}, null, 2));
|
|
86
|
+
console.log('ā'.repeat(60));
|
|
87
|
+
|
|
88
|
+
// Update config.yml with analysis scope
|
|
89
|
+
await this.updateConfigWithScope(analysisScope);
|
|
90
|
+
|
|
91
|
+
// Show usage instructions
|
|
92
|
+
this.showUsageInstructions(options, analysisScope);
|
|
93
|
+
|
|
94
|
+
} catch (error) {
|
|
95
|
+
spinner.fail('Failed to load context');
|
|
96
|
+
console.log(chalk.red(error.message));
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async updateConfigWithScope(analysisScope) {
|
|
101
|
+
try {
|
|
102
|
+
const yaml = require('js-yaml');
|
|
103
|
+
const configPath = '.contextkit/config.yml';
|
|
104
|
+
|
|
105
|
+
if (!await fs.pathExists(configPath)) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const configContent = await fs.readFile(configPath, 'utf-8');
|
|
110
|
+
const config = yaml.load(configContent);
|
|
111
|
+
|
|
112
|
+
// Update analysis scope info
|
|
113
|
+
config.analysis_scope = analysisScope.scope;
|
|
114
|
+
config.analyzed_packages = analysisScope.packages.map(p => p.relativePath || p.path);
|
|
115
|
+
|
|
116
|
+
// Write back
|
|
117
|
+
await fs.writeFile(configPath, yaml.dump(config, { lineWidth: 120 }));
|
|
118
|
+
} catch (error) {
|
|
119
|
+
// Non-critical, continue even if config update fails
|
|
120
|
+
console.log(chalk.yellow(`ā ļø Could not update config.yml: ${error.message}`));
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async loadProjectContext(analysisScope = null) {
|
|
125
|
+
const context = {
|
|
126
|
+
paths: analysisScope?.paths || [process.cwd()],
|
|
127
|
+
scope: analysisScope?.scope || 'current'
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
// Load context for each path in scope
|
|
131
|
+
const contexts = [];
|
|
132
|
+
for (const analysisPath of context.paths) {
|
|
133
|
+
const pathContext = await this.loadPathContext(analysisPath);
|
|
134
|
+
contexts.push(pathContext);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Merge contexts
|
|
138
|
+
context.packages = contexts;
|
|
139
|
+
context.structure = await this.detectProjectStructure();
|
|
140
|
+
context.detectedTools = await this.detectAITools();
|
|
141
|
+
|
|
142
|
+
return context;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async loadPathContext(analysisPath) {
|
|
146
|
+
const originalCwd = process.cwd();
|
|
147
|
+
const pathContext = {
|
|
148
|
+
path: analysisPath,
|
|
149
|
+
relativePath: path.relative(originalCwd, analysisPath)
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
try {
|
|
153
|
+
process.chdir(analysisPath);
|
|
154
|
+
|
|
155
|
+
// Load package.json
|
|
156
|
+
if (await fs.pathExists('package.json')) {
|
|
157
|
+
pathContext.package = await fs.readJson('package.json');
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Load tsconfig.json
|
|
161
|
+
if (await fs.pathExists('tsconfig.json')) {
|
|
162
|
+
pathContext.tsconfig = await fs.readJson('tsconfig.json');
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Detect frameworks and build tools for this path
|
|
166
|
+
pathContext.frameworks = await this.detectFrameworks();
|
|
167
|
+
pathContext.buildTools = await this.detectBuildTools();
|
|
168
|
+
} catch (error) {
|
|
169
|
+
// Continue even if one path fails
|
|
170
|
+
} finally {
|
|
171
|
+
process.chdir(originalCwd);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return pathContext;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async detectMonorepoStructure() {
|
|
178
|
+
const structure = {
|
|
179
|
+
isMonorepo: false,
|
|
180
|
+
type: null, // 'turborepo', 'nx', 'lerna', 'pnpm-workspace', 'yarn-workspace'
|
|
181
|
+
packages: [],
|
|
182
|
+
apps: [],
|
|
183
|
+
frontendPackages: [],
|
|
184
|
+
backendPackages: [],
|
|
185
|
+
fullstackPackages: []
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
// Detect monorepo type
|
|
189
|
+
if (await fs.pathExists('turbo.json')) {
|
|
190
|
+
structure.type = 'turborepo';
|
|
191
|
+
structure.isMonorepo = true;
|
|
192
|
+
} else if (await fs.pathExists('nx.json')) {
|
|
193
|
+
structure.type = 'nx';
|
|
194
|
+
structure.isMonorepo = true;
|
|
195
|
+
} else if (await fs.pathExists('lerna.json')) {
|
|
196
|
+
structure.type = 'lerna';
|
|
197
|
+
structure.isMonorepo = true;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Check for workspace indicators
|
|
201
|
+
const packageJson = await this.readPackageJson();
|
|
202
|
+
if (packageJson.workspaces || packageJson.workspace) {
|
|
203
|
+
structure.isMonorepo = true;
|
|
204
|
+
if (!structure.type) {
|
|
205
|
+
structure.type = packageJson.workspaces ? 'npm-workspace' : 'yarn-workspace';
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Scan packages and apps directories
|
|
210
|
+
if (await fs.pathExists('packages')) {
|
|
211
|
+
structure.packages = await this.scanPackages('packages');
|
|
212
|
+
}
|
|
213
|
+
if (await fs.pathExists('apps')) {
|
|
214
|
+
structure.apps = await this.scanPackages('apps');
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Classify packages
|
|
218
|
+
const allPackages = [...structure.packages, ...structure.apps];
|
|
219
|
+
for (const pkg of allPackages) {
|
|
220
|
+
const pkgType = await this.classifyPackage(pkg.path);
|
|
221
|
+
pkg.type = pkgType;
|
|
222
|
+
|
|
223
|
+
if (pkgType === 'frontend') {
|
|
224
|
+
structure.frontendPackages.push(pkg);
|
|
225
|
+
} else if (pkgType === 'backend') {
|
|
226
|
+
structure.backendPackages.push(pkg);
|
|
227
|
+
} else if (pkgType === 'fullstack') {
|
|
228
|
+
structure.fullstackPackages.push(pkg);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// If no packages found but has workspace structure, still consider it a monorepo
|
|
233
|
+
if (structure.isMonorepo && allPackages.length === 0) {
|
|
234
|
+
structure.isMonorepo = false; // Might be workspace but not monorepo yet
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return structure;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
async scanPackages(dir) {
|
|
241
|
+
const packages = [];
|
|
242
|
+
if (!await fs.pathExists(dir)) return packages;
|
|
243
|
+
|
|
244
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
245
|
+
|
|
246
|
+
for (const entry of entries) {
|
|
247
|
+
if (entry.isDirectory()) {
|
|
248
|
+
const packagePath = path.join(dir, entry.name);
|
|
249
|
+
const packageJsonPath = path.join(packagePath, 'package.json');
|
|
250
|
+
|
|
251
|
+
if (await fs.pathExists(packageJsonPath)) {
|
|
252
|
+
try {
|
|
253
|
+
const packageJson = await fs.readJson(packageJsonPath);
|
|
254
|
+
packages.push({
|
|
255
|
+
name: packageJson.name || entry.name,
|
|
256
|
+
path: packagePath,
|
|
257
|
+
relativePath: path.relative(process.cwd(), packagePath)
|
|
258
|
+
});
|
|
259
|
+
} catch (error) {
|
|
260
|
+
// Skip invalid package.json
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return packages;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
async classifyPackage(packagePath) {
|
|
270
|
+
const packageJsonPath = path.join(packagePath, 'package.json');
|
|
271
|
+
if (!await fs.pathExists(packageJsonPath)) return 'unknown';
|
|
272
|
+
|
|
273
|
+
try {
|
|
274
|
+
const pkg = await fs.readJson(packageJsonPath);
|
|
275
|
+
const deps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
|
|
276
|
+
|
|
277
|
+
// Frontend indicators
|
|
278
|
+
const frontendDeps = ['react', 'vue', '@vue', 'angular', '@angular/core', 'next', 'nuxt', 'svelte'];
|
|
279
|
+
const hasFrontend = frontendDeps.some(dep => deps[dep]);
|
|
280
|
+
|
|
281
|
+
// Backend indicators
|
|
282
|
+
const backendDeps = ['express', 'fastify', 'koa', '@nestjs/core', 'django', 'flask', 'fastapi'];
|
|
283
|
+
const hasBackend = backendDeps.some(dep => deps[dep]);
|
|
284
|
+
|
|
285
|
+
// Check for backend file patterns
|
|
286
|
+
if (!hasBackend) {
|
|
287
|
+
const serverFiles = ['server.js', 'server.ts', 'index.js', 'index.ts', 'app.js', 'app.ts'];
|
|
288
|
+
for (const file of serverFiles) {
|
|
289
|
+
if (await fs.pathExists(path.join(packagePath, file)) ||
|
|
290
|
+
await fs.pathExists(path.join(packagePath, 'src', file))) {
|
|
291
|
+
hasBackend = true;
|
|
292
|
+
break;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (hasFrontend && hasBackend) return 'fullstack';
|
|
298
|
+
if (hasFrontend) return 'frontend';
|
|
299
|
+
if (hasBackend) return 'backend';
|
|
300
|
+
|
|
301
|
+
// Check directory structure
|
|
302
|
+
if (await fs.pathExists(path.join(packagePath, 'src', 'components')) ||
|
|
303
|
+
await fs.pathExists(path.join(packagePath, 'components'))) {
|
|
304
|
+
return 'frontend';
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (await fs.pathExists(path.join(packagePath, 'src', 'server')) ||
|
|
308
|
+
await fs.pathExists(path.join(packagePath, 'src', 'api'))) {
|
|
309
|
+
return 'backend';
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return 'unknown';
|
|
313
|
+
} catch (error) {
|
|
314
|
+
return 'unknown';
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
async promptAnalysisScope(monorepoStructure) {
|
|
319
|
+
const choices = [];
|
|
320
|
+
|
|
321
|
+
if (monorepoStructure.frontendPackages.length > 0) {
|
|
322
|
+
const frontendNames = monorepoStructure.frontendPackages.map(p => p.name).join(', ');
|
|
323
|
+
choices.push({
|
|
324
|
+
name: `Frontend (${monorepoStructure.frontendPackages.length} package(s): ${frontendNames})`,
|
|
325
|
+
value: 'frontend',
|
|
326
|
+
packages: monorepoStructure.frontendPackages
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if (monorepoStructure.backendPackages.length > 0) {
|
|
331
|
+
const backendNames = monorepoStructure.backendPackages.map(p => p.name).join(', ');
|
|
332
|
+
choices.push({
|
|
333
|
+
name: `Backend (${monorepoStructure.backendPackages.length} package(s): ${backendNames})`,
|
|
334
|
+
value: 'backend',
|
|
335
|
+
packages: monorepoStructure.backendPackages
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if (monorepoStructure.frontendPackages.length > 0 && monorepoStructure.backendPackages.length > 0) {
|
|
340
|
+
choices.push({
|
|
341
|
+
name: 'Both (Frontend + Backend) - Separate standards',
|
|
342
|
+
value: 'both',
|
|
343
|
+
packages: [...monorepoStructure.frontendPackages, ...monorepoStructure.backendPackages]
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
choices.push({
|
|
348
|
+
name: 'Current directory only',
|
|
349
|
+
value: 'current',
|
|
350
|
+
packages: []
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
if (choices.length === 1) {
|
|
354
|
+
// Only "current directory" option
|
|
355
|
+
return { scope: 'current', paths: [process.cwd()], packages: [] };
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const { scope } = await inquirer.prompt([
|
|
359
|
+
{
|
|
360
|
+
type: 'list',
|
|
361
|
+
name: 'scope',
|
|
362
|
+
message: 'Which part of the monorepo should we analyze?',
|
|
363
|
+
choices: choices.map(c => c.name)
|
|
364
|
+
}
|
|
365
|
+
]);
|
|
366
|
+
|
|
367
|
+
const selected = choices.find(c => c.name === scope);
|
|
368
|
+
return {
|
|
369
|
+
scope: selected.value,
|
|
370
|
+
packages: selected.packages,
|
|
371
|
+
paths: selected.packages.length > 0
|
|
372
|
+
? selected.packages.map(p => p.path)
|
|
373
|
+
: [process.cwd()]
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
async getScopeFromOption(scopeOption, monorepoStructure) {
|
|
378
|
+
switch (scopeOption.toLowerCase()) {
|
|
379
|
+
case 'frontend':
|
|
380
|
+
return {
|
|
381
|
+
scope: 'frontend',
|
|
382
|
+
packages: monorepoStructure.frontendPackages,
|
|
383
|
+
paths: monorepoStructure.frontendPackages.map(p => p.path)
|
|
384
|
+
};
|
|
385
|
+
case 'backend':
|
|
386
|
+
return {
|
|
387
|
+
scope: 'backend',
|
|
388
|
+
packages: monorepoStructure.backendPackages,
|
|
389
|
+
paths: monorepoStructure.backendPackages.map(p => p.path)
|
|
390
|
+
};
|
|
391
|
+
case 'both':
|
|
392
|
+
return {
|
|
393
|
+
scope: 'both',
|
|
394
|
+
packages: [...monorepoStructure.frontendPackages, ...monorepoStructure.backendPackages],
|
|
395
|
+
paths: [...monorepoStructure.frontendPackages, ...monorepoStructure.backendPackages].map(p => p.path)
|
|
396
|
+
};
|
|
397
|
+
default:
|
|
398
|
+
return { scope: 'current', paths: [process.cwd()], packages: [] };
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
displayScopeInfo(analysisScope, monorepoStructure) {
|
|
403
|
+
console.log(chalk.blue('\nš¦ Monorepo Structure:\n'));
|
|
404
|
+
console.log(chalk.dim(` Type: ${monorepoStructure.type || 'workspace'}`));
|
|
405
|
+
console.log(chalk.dim(` Frontend packages: ${monorepoStructure.frontendPackages.length}`));
|
|
406
|
+
console.log(chalk.dim(` Backend packages: ${monorepoStructure.backendPackages.length}`));
|
|
407
|
+
|
|
408
|
+
console.log(chalk.blue(`\nšÆ Analysis Scope: ${analysisScope.scope}\n`));
|
|
409
|
+
if (analysisScope.packages.length > 0) {
|
|
410
|
+
analysisScope.packages.forEach(pkg => {
|
|
411
|
+
console.log(chalk.dim(` ⢠${pkg.name} (${pkg.type})`));
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
console.log('');
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
async readPackageJson() {
|
|
418
|
+
try {
|
|
419
|
+
if (await fs.pathExists('package.json')) {
|
|
420
|
+
return await fs.readJson('package.json');
|
|
421
|
+
}
|
|
422
|
+
} catch (error) {
|
|
423
|
+
// Ignore
|
|
424
|
+
}
|
|
425
|
+
return {};
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
async detectProjectStructure() {
|
|
429
|
+
const structure = {
|
|
430
|
+
hasSrc: await fs.pathExists('src'),
|
|
431
|
+
hasApp: await fs.pathExists('src/app'),
|
|
432
|
+
hasComponents: await fs.pathExists('src/components'),
|
|
433
|
+
isMonorepo: await fs.pathExists('packages') || await fs.pathExists('apps'),
|
|
434
|
+
frameworks: await this.detectFrameworks(),
|
|
435
|
+
buildTools: await this.detectBuildTools(),
|
|
436
|
+
};
|
|
437
|
+
|
|
438
|
+
structure.type = structure.isMonorepo ? 'monorepo' : 'single-package';
|
|
439
|
+
|
|
440
|
+
return structure;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
async detectFrameworks() {
|
|
444
|
+
const frameworks = [];
|
|
445
|
+
|
|
446
|
+
if (await fs.pathExists('src/components')) {
|
|
447
|
+
frameworks.push('React');
|
|
448
|
+
}
|
|
449
|
+
if (await fs.pathExists('src/pages')) {
|
|
450
|
+
frameworks.push('Next.js');
|
|
451
|
+
}
|
|
452
|
+
if (await fs.pathExists('src/views')) {
|
|
453
|
+
frameworks.push('Vue');
|
|
454
|
+
}
|
|
455
|
+
if (await fs.pathExists('angular.json')) {
|
|
456
|
+
frameworks.push('Angular');
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
return frameworks;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
async detectBuildTools() {
|
|
463
|
+
const tools = [];
|
|
464
|
+
|
|
465
|
+
if (await fs.pathExists('vite.config.js') || await fs.pathExists('vite.config.ts')) {
|
|
466
|
+
tools.push('Vite');
|
|
467
|
+
}
|
|
468
|
+
if (await fs.pathExists('webpack.config.js') || await fs.pathExists('webpack.config.ts')) {
|
|
469
|
+
tools.push('Webpack');
|
|
470
|
+
}
|
|
471
|
+
if (await fs.pathExists('rollup.config.js') || await fs.pathExists('rollup.config.ts')) {
|
|
472
|
+
tools.push('Rollup');
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
return tools;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
async detectAITools() {
|
|
479
|
+
const ToolDetector = require('../utils/tool-detector');
|
|
480
|
+
const detector = new ToolDetector();
|
|
481
|
+
const tools = await detector.detectAll();
|
|
482
|
+
const summary = detector.getSummary();
|
|
483
|
+
|
|
484
|
+
return {
|
|
485
|
+
detected: summary.all,
|
|
486
|
+
editors: summary.editors,
|
|
487
|
+
cli: summary.cli,
|
|
488
|
+
count: summary.count
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
showUsageInstructions(options, analysisScope = null) {
|
|
493
|
+
console.log(chalk.blue('\nš” To execute analysis with AI:\n'));
|
|
494
|
+
|
|
495
|
+
console.log(chalk.yellow('1. Using contextkit AI chat:'));
|
|
496
|
+
const scopeHint = analysisScope?.scope === 'both'
|
|
497
|
+
? ' (will generate separate frontend/backend standards)'
|
|
498
|
+
: '';
|
|
499
|
+
console.log(` contextkit ai "read .contextkit/commands/analyze.md and execute analysis${scopeHint}"\n`);
|
|
500
|
+
|
|
501
|
+
console.log(chalk.yellow('2. Using Aider (if installed):'));
|
|
502
|
+
console.log(' aider');
|
|
503
|
+
console.log(' Then paste the analysis instructions above\n');
|
|
504
|
+
|
|
505
|
+
console.log(chalk.yellow('3. Using Claude CLI (if installed):'));
|
|
506
|
+
console.log(' claude "read .contextkit/commands/analyze.md and execute analysis"\n');
|
|
507
|
+
|
|
508
|
+
console.log(chalk.blue('š Next: Use the AI tool of your choice to execute the analysis'));
|
|
509
|
+
console.log(chalk.blue('⨠The AI will generate content for your skeleton .contextkit/standards/*.md files'));
|
|
510
|
+
|
|
511
|
+
if (analysisScope?.scope === 'both') {
|
|
512
|
+
console.log(chalk.blue('š¦ For monorepo with both frontend and backend, standards will be generated separately:'));
|
|
513
|
+
console.log(chalk.dim(' ⢠.contextkit/standards/frontend/'));
|
|
514
|
+
console.log(chalk.dim(' ⢠.contextkit/standards/backend/'));
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
console.log('');
|
|
518
|
+
console.log(chalk.yellow('š What AI will generate:'));
|
|
519
|
+
console.log(' ⢠code-style.md - Extract formatting & conventions from your code');
|
|
520
|
+
console.log(' ⢠testing.md - Document your test patterns and framework');
|
|
521
|
+
console.log(' ⢠architecture.md - Map your folder structure and patterns');
|
|
522
|
+
console.log(' ⢠ai-guidelines.md - Create AI usage guidelines for your project');
|
|
523
|
+
console.log(' ⢠glossary.md - Extract domain terms from your codebase');
|
|
524
|
+
console.log(' ⢠workflows.md - Document your development processes');
|
|
525
|
+
console.log('');
|
|
526
|
+
console.log(chalk.yellow('ā ļø After generation completes:'));
|
|
527
|
+
console.log(chalk.yellow(' 1. Review generated files in .contextkit/standards/'));
|
|
528
|
+
console.log(chalk.yellow(' 2. Edit and refine to match YOUR exact needs'));
|
|
529
|
+
console.log(chalk.yellow(' 3. Commit them to your repo (they\'re part of your project now)'));
|
|
530
|
+
|
|
531
|
+
if (analysisScope?.scope && analysisScope.scope !== 'current') {
|
|
532
|
+
console.log('');
|
|
533
|
+
console.log(chalk.blue('š” Tip: Run with --scope flag for non-interactive mode:'));
|
|
534
|
+
console.log(chalk.dim(` contextkit analyze --scope ${analysisScope.scope}`));
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
async function analyze(options) {
|
|
540
|
+
const cmd = new AnalyzeCommand();
|
|
541
|
+
await cmd.run(options);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
module.exports = analyze;
|