@nolrm/contextkit 0.13.4 ā 0.13.7
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 +65 -46
- package/bin/contextkit.js +4 -2
- package/lib/commands/analyze.js +131 -91
- package/lib/commands/check.js +24 -18
- package/lib/commands/install.js +274 -71
- package/lib/commands/note.js +12 -20
- package/lib/commands/run.js +20 -22
- package/lib/commands/status.js +61 -32
- package/lib/commands/update.js +25 -26
- package/lib/index.js +1 -1
- package/lib/integrations/aider-integration.js +1 -1
- package/lib/integrations/base-integration.js +13 -6
- package/lib/integrations/claude-integration.js +103 -41
- package/lib/integrations/continue-integration.js +5 -5
- package/lib/integrations/copilot-integration.js +1 -1
- package/lib/integrations/cursor-integration.js +70 -28
- package/lib/integrations/gemini-integration.js +13 -11
- package/lib/integrations/index.js +11 -1
- package/lib/utils/banner.js +14 -11
- package/lib/utils/download.js +3 -4
- package/lib/utils/git-hooks.js +3 -4
- package/lib/utils/migrations.js +6 -4
- package/lib/utils/migrations.md +4 -3
- package/lib/utils/notifier.js +1 -1
- package/lib/utils/project-detector.js +11 -5
- package/lib/utils/status-manager.js +6 -7
- package/lib/utils/tool-detector.js +19 -21
- package/package.json +8 -2
package/lib/commands/analyze.js
CHANGED
|
@@ -13,7 +13,7 @@ class AnalyzeCommand {
|
|
|
13
13
|
console.log(chalk.magenta('š ContextKit Project Analysis\n'));
|
|
14
14
|
|
|
15
15
|
// Check if contextkit is installed
|
|
16
|
-
if (!await fs.pathExists('.contextkit/commands/analyze.md')) {
|
|
16
|
+
if (!(await fs.pathExists('.contextkit/commands/analyze.md'))) {
|
|
17
17
|
console.log(chalk.red('ā ContextKit not found.'));
|
|
18
18
|
console.log(chalk.yellow(' Run: contextkit install'));
|
|
19
19
|
return;
|
|
@@ -24,27 +24,27 @@ class AnalyzeCommand {
|
|
|
24
24
|
try {
|
|
25
25
|
// Detect monorepo structure
|
|
26
26
|
const monorepoStructure = await this.detectMonorepoStructure();
|
|
27
|
-
|
|
27
|
+
|
|
28
28
|
// Determine analysis scope
|
|
29
29
|
let analysisScope;
|
|
30
|
-
|
|
30
|
+
|
|
31
31
|
// Handle specific package option
|
|
32
32
|
if (options.package) {
|
|
33
33
|
const packagePath = path.resolve(options.package);
|
|
34
|
-
if (!await fs.pathExists(packagePath)) {
|
|
34
|
+
if (!(await fs.pathExists(packagePath))) {
|
|
35
35
|
spinner.fail(`Package path not found: ${options.package}`);
|
|
36
36
|
return;
|
|
37
37
|
}
|
|
38
|
-
|
|
38
|
+
|
|
39
39
|
spinner.succeed(`Analyzing specific package: ${options.package}\n`);
|
|
40
40
|
analysisScope = {
|
|
41
41
|
scope: 'package',
|
|
42
42
|
paths: [packagePath],
|
|
43
|
-
packages: [{ path: packagePath, name: path.basename(packagePath) }]
|
|
43
|
+
packages: [{ path: packagePath, name: path.basename(packagePath) }],
|
|
44
44
|
};
|
|
45
45
|
} else if (monorepoStructure.isMonorepo) {
|
|
46
46
|
spinner.succeed('Monorepo detected!\n');
|
|
47
|
-
|
|
47
|
+
|
|
48
48
|
if (options.scope) {
|
|
49
49
|
// Use provided scope
|
|
50
50
|
analysisScope = await this.getScopeFromOption(options.scope, monorepoStructure);
|
|
@@ -55,7 +55,7 @@ class AnalyzeCommand {
|
|
|
55
55
|
// Interactive: prompt user
|
|
56
56
|
analysisScope = await this.promptAnalysisScope(monorepoStructure);
|
|
57
57
|
}
|
|
58
|
-
|
|
58
|
+
|
|
59
59
|
this.displayScopeInfo(analysisScope, monorepoStructure);
|
|
60
60
|
} else {
|
|
61
61
|
spinner.succeed('Single package detected\n');
|
|
@@ -66,7 +66,7 @@ class AnalyzeCommand {
|
|
|
66
66
|
const contextSpinner = ora('Loading project context...').start();
|
|
67
67
|
const context = await this.loadProjectContext(analysisScope);
|
|
68
68
|
contextSpinner.succeed('Context loaded successfully\n');
|
|
69
|
-
|
|
69
|
+
|
|
70
70
|
// Load analyze.md instructions
|
|
71
71
|
const analyzeInstructions = await fs.readFile('.contextkit/commands/analyze.md', 'utf-8');
|
|
72
72
|
|
|
@@ -75,14 +75,20 @@ class AnalyzeCommand {
|
|
|
75
75
|
console.log('ā'.repeat(60));
|
|
76
76
|
console.log(analyzeInstructions);
|
|
77
77
|
console.log('ā'.repeat(60));
|
|
78
|
-
|
|
78
|
+
|
|
79
79
|
console.log(chalk.blue('\nš Project Context:\n'));
|
|
80
80
|
console.log('ā'.repeat(60));
|
|
81
|
-
console.log(
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
81
|
+
console.log(
|
|
82
|
+
JSON.stringify(
|
|
83
|
+
{
|
|
84
|
+
...context,
|
|
85
|
+
analysisScope: analysisScope.scope,
|
|
86
|
+
analyzedPaths: analysisScope.paths,
|
|
87
|
+
},
|
|
88
|
+
null,
|
|
89
|
+
2
|
|
90
|
+
)
|
|
91
|
+
);
|
|
86
92
|
console.log('ā'.repeat(60));
|
|
87
93
|
|
|
88
94
|
// Update config.yml with analysis scope
|
|
@@ -90,7 +96,6 @@ class AnalyzeCommand {
|
|
|
90
96
|
|
|
91
97
|
// Show usage instructions
|
|
92
98
|
this.showUsageInstructions(options, analysisScope);
|
|
93
|
-
|
|
94
99
|
} catch (error) {
|
|
95
100
|
spinner.fail('Failed to load context');
|
|
96
101
|
console.log(chalk.red(error.message));
|
|
@@ -101,18 +106,18 @@ class AnalyzeCommand {
|
|
|
101
106
|
try {
|
|
102
107
|
const yaml = require('js-yaml');
|
|
103
108
|
const configPath = '.contextkit/config.yml';
|
|
104
|
-
|
|
105
|
-
if (!await fs.pathExists(configPath)) {
|
|
109
|
+
|
|
110
|
+
if (!(await fs.pathExists(configPath))) {
|
|
106
111
|
return;
|
|
107
112
|
}
|
|
108
113
|
|
|
109
114
|
const configContent = await fs.readFile(configPath, 'utf-8');
|
|
110
115
|
const config = yaml.load(configContent);
|
|
111
|
-
|
|
116
|
+
|
|
112
117
|
// Update analysis scope info
|
|
113
118
|
config.analysis_scope = analysisScope.scope;
|
|
114
|
-
config.analyzed_packages = analysisScope.packages.map(p => p.relativePath || p.path);
|
|
115
|
-
|
|
119
|
+
config.analyzed_packages = analysisScope.packages.map((p) => p.relativePath || p.path);
|
|
120
|
+
|
|
116
121
|
// Write back
|
|
117
122
|
await fs.writeFile(configPath, yaml.dump(config, { lineWidth: 120 }));
|
|
118
123
|
} catch (error) {
|
|
@@ -124,7 +129,7 @@ class AnalyzeCommand {
|
|
|
124
129
|
async loadProjectContext(analysisScope = null) {
|
|
125
130
|
const context = {
|
|
126
131
|
paths: analysisScope?.paths || [process.cwd()],
|
|
127
|
-
scope: analysisScope?.scope || 'current'
|
|
132
|
+
scope: analysisScope?.scope || 'current',
|
|
128
133
|
};
|
|
129
134
|
|
|
130
135
|
// Load context for each path in scope
|
|
@@ -146,7 +151,7 @@ class AnalyzeCommand {
|
|
|
146
151
|
const originalCwd = process.cwd();
|
|
147
152
|
const pathContext = {
|
|
148
153
|
path: analysisPath,
|
|
149
|
-
relativePath: path.relative(originalCwd, analysisPath)
|
|
154
|
+
relativePath: path.relative(originalCwd, analysisPath),
|
|
150
155
|
};
|
|
151
156
|
|
|
152
157
|
try {
|
|
@@ -165,7 +170,7 @@ class AnalyzeCommand {
|
|
|
165
170
|
// Detect frameworks and build tools for this path
|
|
166
171
|
pathContext.frameworks = await this.detectFrameworks();
|
|
167
172
|
pathContext.buildTools = await this.detectBuildTools();
|
|
168
|
-
} catch
|
|
173
|
+
} catch {
|
|
169
174
|
// Continue even if one path fails
|
|
170
175
|
} finally {
|
|
171
176
|
process.chdir(originalCwd);
|
|
@@ -182,7 +187,7 @@ class AnalyzeCommand {
|
|
|
182
187
|
apps: [],
|
|
183
188
|
frontendPackages: [],
|
|
184
189
|
backendPackages: [],
|
|
185
|
-
fullstackPackages: []
|
|
190
|
+
fullstackPackages: [],
|
|
186
191
|
};
|
|
187
192
|
|
|
188
193
|
// Detect monorepo type
|
|
@@ -219,7 +224,7 @@ class AnalyzeCommand {
|
|
|
219
224
|
for (const pkg of allPackages) {
|
|
220
225
|
const pkgType = await this.classifyPackage(pkg.path);
|
|
221
226
|
pkg.type = pkgType;
|
|
222
|
-
|
|
227
|
+
|
|
223
228
|
if (pkgType === 'frontend') {
|
|
224
229
|
structure.frontendPackages.push(pkg);
|
|
225
230
|
} else if (pkgType === 'backend') {
|
|
@@ -239,24 +244,24 @@ class AnalyzeCommand {
|
|
|
239
244
|
|
|
240
245
|
async scanPackages(dir) {
|
|
241
246
|
const packages = [];
|
|
242
|
-
if (!await fs.pathExists(dir)) return packages;
|
|
247
|
+
if (!(await fs.pathExists(dir))) return packages;
|
|
243
248
|
|
|
244
249
|
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
245
|
-
|
|
250
|
+
|
|
246
251
|
for (const entry of entries) {
|
|
247
252
|
if (entry.isDirectory()) {
|
|
248
253
|
const packagePath = path.join(dir, entry.name);
|
|
249
254
|
const packageJsonPath = path.join(packagePath, 'package.json');
|
|
250
|
-
|
|
255
|
+
|
|
251
256
|
if (await fs.pathExists(packageJsonPath)) {
|
|
252
257
|
try {
|
|
253
258
|
const packageJson = await fs.readJson(packageJsonPath);
|
|
254
259
|
packages.push({
|
|
255
260
|
name: packageJson.name || entry.name,
|
|
256
261
|
path: packagePath,
|
|
257
|
-
relativePath: path.relative(process.cwd(), packagePath)
|
|
262
|
+
relativePath: path.relative(process.cwd(), packagePath),
|
|
258
263
|
});
|
|
259
|
-
} catch
|
|
264
|
+
} catch {
|
|
260
265
|
// Skip invalid package.json
|
|
261
266
|
}
|
|
262
267
|
}
|
|
@@ -268,86 +273,112 @@ class AnalyzeCommand {
|
|
|
268
273
|
|
|
269
274
|
async classifyPackage(packagePath) {
|
|
270
275
|
const packageJsonPath = path.join(packagePath, 'package.json');
|
|
271
|
-
if (!await fs.pathExists(packageJsonPath)) return 'unknown';
|
|
272
|
-
|
|
276
|
+
if (!(await fs.pathExists(packageJsonPath))) return 'unknown';
|
|
277
|
+
|
|
273
278
|
try {
|
|
274
279
|
const pkg = await fs.readJson(packageJsonPath);
|
|
275
280
|
const deps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
|
|
276
|
-
|
|
281
|
+
|
|
277
282
|
// Frontend indicators
|
|
278
|
-
const frontendDeps = [
|
|
279
|
-
|
|
280
|
-
|
|
283
|
+
const frontendDeps = [
|
|
284
|
+
'react',
|
|
285
|
+
'vue',
|
|
286
|
+
'@vue',
|
|
287
|
+
'angular',
|
|
288
|
+
'@angular/core',
|
|
289
|
+
'next',
|
|
290
|
+
'nuxt',
|
|
291
|
+
'svelte',
|
|
292
|
+
];
|
|
293
|
+
const hasFrontend = frontendDeps.some((dep) => deps[dep]);
|
|
294
|
+
|
|
281
295
|
// Backend indicators
|
|
282
|
-
const backendDeps = [
|
|
283
|
-
|
|
284
|
-
|
|
296
|
+
const backendDeps = [
|
|
297
|
+
'express',
|
|
298
|
+
'fastify',
|
|
299
|
+
'koa',
|
|
300
|
+
'@nestjs/core',
|
|
301
|
+
'django',
|
|
302
|
+
'flask',
|
|
303
|
+
'fastapi',
|
|
304
|
+
];
|
|
305
|
+
let hasBackend = backendDeps.some((dep) => deps[dep]);
|
|
306
|
+
|
|
285
307
|
// Check for backend file patterns
|
|
286
308
|
if (!hasBackend) {
|
|
287
309
|
const serverFiles = ['server.js', 'server.ts', 'index.js', 'index.ts', 'app.js', 'app.ts'];
|
|
288
310
|
for (const file of serverFiles) {
|
|
289
|
-
if (
|
|
290
|
-
|
|
311
|
+
if (
|
|
312
|
+
(await fs.pathExists(path.join(packagePath, file))) ||
|
|
313
|
+
(await fs.pathExists(path.join(packagePath, 'src', file)))
|
|
314
|
+
) {
|
|
291
315
|
hasBackend = true;
|
|
292
316
|
break;
|
|
293
317
|
}
|
|
294
318
|
}
|
|
295
319
|
}
|
|
296
|
-
|
|
320
|
+
|
|
297
321
|
if (hasFrontend && hasBackend) return 'fullstack';
|
|
298
322
|
if (hasFrontend) return 'frontend';
|
|
299
323
|
if (hasBackend) return 'backend';
|
|
300
|
-
|
|
324
|
+
|
|
301
325
|
// Check directory structure
|
|
302
|
-
if (
|
|
303
|
-
|
|
326
|
+
if (
|
|
327
|
+
(await fs.pathExists(path.join(packagePath, 'src', 'components'))) ||
|
|
328
|
+
(await fs.pathExists(path.join(packagePath, 'components')))
|
|
329
|
+
) {
|
|
304
330
|
return 'frontend';
|
|
305
331
|
}
|
|
306
|
-
|
|
307
|
-
if (
|
|
308
|
-
|
|
332
|
+
|
|
333
|
+
if (
|
|
334
|
+
(await fs.pathExists(path.join(packagePath, 'src', 'server'))) ||
|
|
335
|
+
(await fs.pathExists(path.join(packagePath, 'src', 'api')))
|
|
336
|
+
) {
|
|
309
337
|
return 'backend';
|
|
310
338
|
}
|
|
311
|
-
|
|
339
|
+
|
|
312
340
|
return 'unknown';
|
|
313
|
-
} catch
|
|
341
|
+
} catch {
|
|
314
342
|
return 'unknown';
|
|
315
343
|
}
|
|
316
344
|
}
|
|
317
345
|
|
|
318
346
|
async promptAnalysisScope(monorepoStructure) {
|
|
319
347
|
const choices = [];
|
|
320
|
-
|
|
348
|
+
|
|
321
349
|
if (monorepoStructure.frontendPackages.length > 0) {
|
|
322
|
-
const frontendNames = monorepoStructure.frontendPackages.map(p => p.name).join(', ');
|
|
350
|
+
const frontendNames = monorepoStructure.frontendPackages.map((p) => p.name).join(', ');
|
|
323
351
|
choices.push({
|
|
324
352
|
name: `Frontend (${monorepoStructure.frontendPackages.length} package(s): ${frontendNames})`,
|
|
325
353
|
value: 'frontend',
|
|
326
|
-
packages: monorepoStructure.frontendPackages
|
|
354
|
+
packages: monorepoStructure.frontendPackages,
|
|
327
355
|
});
|
|
328
356
|
}
|
|
329
|
-
|
|
357
|
+
|
|
330
358
|
if (monorepoStructure.backendPackages.length > 0) {
|
|
331
|
-
const backendNames = monorepoStructure.backendPackages.map(p => p.name).join(', ');
|
|
359
|
+
const backendNames = monorepoStructure.backendPackages.map((p) => p.name).join(', ');
|
|
332
360
|
choices.push({
|
|
333
361
|
name: `Backend (${monorepoStructure.backendPackages.length} package(s): ${backendNames})`,
|
|
334
362
|
value: 'backend',
|
|
335
|
-
packages: monorepoStructure.backendPackages
|
|
363
|
+
packages: monorepoStructure.backendPackages,
|
|
336
364
|
});
|
|
337
365
|
}
|
|
338
|
-
|
|
339
|
-
if (
|
|
366
|
+
|
|
367
|
+
if (
|
|
368
|
+
monorepoStructure.frontendPackages.length > 0 &&
|
|
369
|
+
monorepoStructure.backendPackages.length > 0
|
|
370
|
+
) {
|
|
340
371
|
choices.push({
|
|
341
372
|
name: 'Both (Frontend + Backend) - Separate standards',
|
|
342
373
|
value: 'both',
|
|
343
|
-
packages: [...monorepoStructure.frontendPackages, ...monorepoStructure.backendPackages]
|
|
374
|
+
packages: [...monorepoStructure.frontendPackages, ...monorepoStructure.backendPackages],
|
|
344
375
|
});
|
|
345
376
|
}
|
|
346
|
-
|
|
377
|
+
|
|
347
378
|
choices.push({
|
|
348
379
|
name: 'Current directory only',
|
|
349
380
|
value: 'current',
|
|
350
|
-
packages: []
|
|
381
|
+
packages: [],
|
|
351
382
|
});
|
|
352
383
|
|
|
353
384
|
if (choices.length === 1) {
|
|
@@ -360,17 +391,15 @@ class AnalyzeCommand {
|
|
|
360
391
|
type: 'list',
|
|
361
392
|
name: 'scope',
|
|
362
393
|
message: 'Which part of the monorepo should we analyze?',
|
|
363
|
-
choices: choices.map(c => c.name)
|
|
364
|
-
}
|
|
394
|
+
choices: choices.map((c) => c.name),
|
|
395
|
+
},
|
|
365
396
|
]);
|
|
366
397
|
|
|
367
|
-
const selected = choices.find(c => c.name === scope);
|
|
398
|
+
const selected = choices.find((c) => c.name === scope);
|
|
368
399
|
return {
|
|
369
400
|
scope: selected.value,
|
|
370
401
|
packages: selected.packages,
|
|
371
|
-
paths: selected.packages.length > 0
|
|
372
|
-
? selected.packages.map(p => p.path)
|
|
373
|
-
: [process.cwd()]
|
|
402
|
+
paths: selected.packages.length > 0 ? selected.packages.map((p) => p.path) : [process.cwd()],
|
|
374
403
|
};
|
|
375
404
|
}
|
|
376
405
|
|
|
@@ -380,19 +409,21 @@ class AnalyzeCommand {
|
|
|
380
409
|
return {
|
|
381
410
|
scope: 'frontend',
|
|
382
411
|
packages: monorepoStructure.frontendPackages,
|
|
383
|
-
paths: monorepoStructure.frontendPackages.map(p => p.path)
|
|
412
|
+
paths: monorepoStructure.frontendPackages.map((p) => p.path),
|
|
384
413
|
};
|
|
385
414
|
case 'backend':
|
|
386
415
|
return {
|
|
387
416
|
scope: 'backend',
|
|
388
417
|
packages: monorepoStructure.backendPackages,
|
|
389
|
-
paths: monorepoStructure.backendPackages.map(p => p.path)
|
|
418
|
+
paths: monorepoStructure.backendPackages.map((p) => p.path),
|
|
390
419
|
};
|
|
391
420
|
case 'both':
|
|
392
421
|
return {
|
|
393
422
|
scope: 'both',
|
|
394
423
|
packages: [...monorepoStructure.frontendPackages, ...monorepoStructure.backendPackages],
|
|
395
|
-
paths: [...monorepoStructure.frontendPackages, ...monorepoStructure.backendPackages].map(
|
|
424
|
+
paths: [...monorepoStructure.frontendPackages, ...monorepoStructure.backendPackages].map(
|
|
425
|
+
(p) => p.path
|
|
426
|
+
),
|
|
396
427
|
};
|
|
397
428
|
default:
|
|
398
429
|
return { scope: 'current', paths: [process.cwd()], packages: [] };
|
|
@@ -404,10 +435,10 @@ class AnalyzeCommand {
|
|
|
404
435
|
console.log(chalk.dim(` Type: ${monorepoStructure.type || 'workspace'}`));
|
|
405
436
|
console.log(chalk.dim(` Frontend packages: ${monorepoStructure.frontendPackages.length}`));
|
|
406
437
|
console.log(chalk.dim(` Backend packages: ${monorepoStructure.backendPackages.length}`));
|
|
407
|
-
|
|
438
|
+
|
|
408
439
|
console.log(chalk.blue(`\nšÆ Analysis Scope: ${analysisScope.scope}\n`));
|
|
409
440
|
if (analysisScope.packages.length > 0) {
|
|
410
|
-
analysisScope.packages.forEach(pkg => {
|
|
441
|
+
analysisScope.packages.forEach((pkg) => {
|
|
411
442
|
console.log(chalk.dim(` ⢠${pkg.name} (${pkg.type})`));
|
|
412
443
|
});
|
|
413
444
|
}
|
|
@@ -419,7 +450,7 @@ class AnalyzeCommand {
|
|
|
419
450
|
if (await fs.pathExists('package.json')) {
|
|
420
451
|
return await fs.readJson('package.json');
|
|
421
452
|
}
|
|
422
|
-
} catch
|
|
453
|
+
} catch {
|
|
423
454
|
// Ignore
|
|
424
455
|
}
|
|
425
456
|
return {};
|
|
@@ -430,7 +461,7 @@ class AnalyzeCommand {
|
|
|
430
461
|
hasSrc: await fs.pathExists('src'),
|
|
431
462
|
hasApp: await fs.pathExists('src/app'),
|
|
432
463
|
hasComponents: await fs.pathExists('src/components'),
|
|
433
|
-
isMonorepo: await fs.pathExists('packages') || await fs.pathExists('apps'),
|
|
464
|
+
isMonorepo: (await fs.pathExists('packages')) || (await fs.pathExists('apps')),
|
|
434
465
|
frameworks: await this.detectFrameworks(),
|
|
435
466
|
buildTools: await this.detectBuildTools(),
|
|
436
467
|
};
|
|
@@ -462,13 +493,13 @@ class AnalyzeCommand {
|
|
|
462
493
|
async detectBuildTools() {
|
|
463
494
|
const tools = [];
|
|
464
495
|
|
|
465
|
-
if (await fs.pathExists('vite.config.js') || await fs.pathExists('vite.config.ts')) {
|
|
496
|
+
if ((await fs.pathExists('vite.config.js')) || (await fs.pathExists('vite.config.ts'))) {
|
|
466
497
|
tools.push('Vite');
|
|
467
498
|
}
|
|
468
|
-
if (await fs.pathExists('webpack.config.js') || await fs.pathExists('webpack.config.ts')) {
|
|
499
|
+
if ((await fs.pathExists('webpack.config.js')) || (await fs.pathExists('webpack.config.ts'))) {
|
|
469
500
|
tools.push('Webpack');
|
|
470
501
|
}
|
|
471
|
-
if (await fs.pathExists('rollup.config.js') || await fs.pathExists('rollup.config.ts')) {
|
|
502
|
+
if ((await fs.pathExists('rollup.config.js')) || (await fs.pathExists('rollup.config.ts'))) {
|
|
472
503
|
tools.push('Rollup');
|
|
473
504
|
}
|
|
474
505
|
|
|
@@ -478,25 +509,26 @@ class AnalyzeCommand {
|
|
|
478
509
|
async detectAITools() {
|
|
479
510
|
const ToolDetector = require('../utils/tool-detector');
|
|
480
511
|
const detector = new ToolDetector();
|
|
481
|
-
const
|
|
512
|
+
const _tools = await detector.detectAll();
|
|
482
513
|
const summary = detector.getSummary();
|
|
483
514
|
|
|
484
515
|
return {
|
|
485
516
|
detected: summary.all,
|
|
486
517
|
editors: summary.editors,
|
|
487
518
|
cli: summary.cli,
|
|
488
|
-
count: summary.count
|
|
519
|
+
count: summary.count,
|
|
489
520
|
};
|
|
490
521
|
}
|
|
491
522
|
|
|
492
523
|
showUsageInstructions(options, analysisScope = null) {
|
|
493
524
|
console.log(chalk.blue('\nš” To execute analysis with AI:\n'));
|
|
494
|
-
|
|
525
|
+
|
|
495
526
|
console.log(chalk.yellow('1. Using contextkit AI chat:'));
|
|
496
|
-
const scopeHint =
|
|
497
|
-
? ' (will generate separate frontend/backend standards)'
|
|
498
|
-
|
|
499
|
-
|
|
527
|
+
const scopeHint =
|
|
528
|
+
analysisScope?.scope === 'both' ? ' (will generate separate frontend/backend standards)' : '';
|
|
529
|
+
console.log(
|
|
530
|
+
` contextkit ai "read .contextkit/commands/analyze.md and execute analysis${scopeHint}"\n`
|
|
531
|
+
);
|
|
500
532
|
|
|
501
533
|
console.log(chalk.yellow('2. Using Aider (if installed):'));
|
|
502
534
|
console.log(' aider');
|
|
@@ -506,14 +538,22 @@ class AnalyzeCommand {
|
|
|
506
538
|
console.log(' claude "read .contextkit/commands/analyze.md and execute analysis"\n');
|
|
507
539
|
|
|
508
540
|
console.log(chalk.blue('š Next: Use the AI tool of your choice to execute the analysis'));
|
|
509
|
-
console.log(
|
|
510
|
-
|
|
541
|
+
console.log(
|
|
542
|
+
chalk.blue(
|
|
543
|
+
'⨠The AI will generate content for your skeleton .contextkit/standards/*.md files'
|
|
544
|
+
)
|
|
545
|
+
);
|
|
546
|
+
|
|
511
547
|
if (analysisScope?.scope === 'both') {
|
|
512
|
-
console.log(
|
|
548
|
+
console.log(
|
|
549
|
+
chalk.blue(
|
|
550
|
+
'š¦ For monorepo with both frontend and backend, standards will be generated separately:'
|
|
551
|
+
)
|
|
552
|
+
);
|
|
513
553
|
console.log(chalk.dim(' ⢠.contextkit/standards/frontend/'));
|
|
514
554
|
console.log(chalk.dim(' ⢠.contextkit/standards/backend/'));
|
|
515
555
|
}
|
|
516
|
-
|
|
556
|
+
|
|
517
557
|
console.log('');
|
|
518
558
|
console.log(chalk.yellow('š What AI will generate:'));
|
|
519
559
|
console.log(' ⢠code-style.md - Extract formatting & conventions from your code');
|
|
@@ -526,8 +566,8 @@ class AnalyzeCommand {
|
|
|
526
566
|
console.log(chalk.yellow('ā ļø After generation completes:'));
|
|
527
567
|
console.log(chalk.yellow(' 1. Review generated files in .contextkit/standards/'));
|
|
528
568
|
console.log(chalk.yellow(' 2. Edit and refine to match YOUR exact needs'));
|
|
529
|
-
console.log(chalk.yellow(
|
|
530
|
-
|
|
569
|
+
console.log(chalk.yellow(" 3. Commit them to your repo (they're part of your project now)"));
|
|
570
|
+
|
|
531
571
|
if (analysisScope?.scope && analysisScope.scope !== 'current') {
|
|
532
572
|
console.log('');
|
|
533
573
|
console.log(chalk.blue('š” Tip: Run with --scope flag for non-interactive mode:'));
|
package/lib/commands/check.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
const chalk = require('chalk');
|
|
2
2
|
const fs = require('fs-extra');
|
|
3
|
-
const path = require('path');
|
|
4
3
|
const yaml = require('js-yaml');
|
|
5
4
|
|
|
6
5
|
class CheckCommand {
|
|
@@ -13,7 +12,7 @@ class CheckCommand {
|
|
|
13
12
|
async run(options = {}) {
|
|
14
13
|
console.log(chalk.magenta('š ContextKit Validation Check\n'));
|
|
15
14
|
|
|
16
|
-
if (!await fs.pathExists('.contextkit/config.yml')) {
|
|
15
|
+
if (!(await fs.pathExists('.contextkit/config.yml'))) {
|
|
17
16
|
console.log(chalk.red('ā ContextKit not installed'));
|
|
18
17
|
console.log(chalk.yellow(' Run: contextkit install'));
|
|
19
18
|
return;
|
|
@@ -88,7 +87,7 @@ class CheckCommand {
|
|
|
88
87
|
|
|
89
88
|
for (const file of config.required) {
|
|
90
89
|
const filePath = `.contextkit/${file}`;
|
|
91
|
-
if (!await fs.pathExists(filePath)) {
|
|
90
|
+
if (!(await fs.pathExists(filePath))) {
|
|
92
91
|
this.errors.push(`Required file missing: ${file}`);
|
|
93
92
|
} else {
|
|
94
93
|
this.info.push(`ā Required file exists: ${file}`);
|
|
@@ -103,13 +102,13 @@ class CheckCommand {
|
|
|
103
102
|
|
|
104
103
|
for (const file of config.optional) {
|
|
105
104
|
const filePath = `.contextkit/${file}`;
|
|
106
|
-
if (!await fs.pathExists(filePath)) {
|
|
105
|
+
if (!(await fs.pathExists(filePath))) {
|
|
107
106
|
this.warnings.push(`Optional file missing: ${file}`);
|
|
108
107
|
}
|
|
109
108
|
}
|
|
110
109
|
}
|
|
111
110
|
|
|
112
|
-
async checkStandardsFreshness(
|
|
111
|
+
async checkStandardsFreshness(_config) {
|
|
113
112
|
// Load policy to get freshness threshold
|
|
114
113
|
let freshnessDays = 90; // default
|
|
115
114
|
try {
|
|
@@ -120,7 +119,7 @@ class CheckCommand {
|
|
|
120
119
|
freshnessDays = policy.enforcement.standards.freshness_days;
|
|
121
120
|
}
|
|
122
121
|
}
|
|
123
|
-
} catch
|
|
122
|
+
} catch {
|
|
124
123
|
// Policy file might not exist or be invalid, use default
|
|
125
124
|
}
|
|
126
125
|
|
|
@@ -130,7 +129,7 @@ class CheckCommand {
|
|
|
130
129
|
'standards/testing.md',
|
|
131
130
|
'standards/architecture.md',
|
|
132
131
|
'standards/ai-guidelines.md',
|
|
133
|
-
'standards/workflows.md'
|
|
132
|
+
'standards/workflows.md',
|
|
134
133
|
];
|
|
135
134
|
|
|
136
135
|
for (const file of standardsFiles) {
|
|
@@ -138,7 +137,7 @@ class CheckCommand {
|
|
|
138
137
|
if (await fs.pathExists(filePath)) {
|
|
139
138
|
const stats = await fs.stat(filePath);
|
|
140
139
|
const daysSinceUpdate = (Date.now() - stats.mtime.getTime()) / (1000 * 60 * 60 * 24);
|
|
141
|
-
|
|
140
|
+
|
|
142
141
|
if (daysSinceUpdate > freshnessDays) {
|
|
143
142
|
this.warnings.push(
|
|
144
143
|
`Standards file outdated: ${file} (last updated ${Math.floor(daysSinceUpdate)} days ago, threshold: ${freshnessDays} days)`
|
|
@@ -148,9 +147,9 @@ class CheckCommand {
|
|
|
148
147
|
}
|
|
149
148
|
}
|
|
150
149
|
|
|
151
|
-
async checkPolicyCompliance(
|
|
150
|
+
async checkPolicyCompliance(_config) {
|
|
152
151
|
try {
|
|
153
|
-
if (!await fs.pathExists('.contextkit/policies/policy.yml')) {
|
|
152
|
+
if (!(await fs.pathExists('.contextkit/policies/policy.yml'))) {
|
|
154
153
|
this.warnings.push('Policy file missing: policies/policy.yml');
|
|
155
154
|
return;
|
|
156
155
|
}
|
|
@@ -171,7 +170,9 @@ class CheckCommand {
|
|
|
171
170
|
if (await fs.pathExists(testingPath)) {
|
|
172
171
|
const content = await fs.readFile(testingPath, 'utf-8');
|
|
173
172
|
if (!content.includes('numbered') && !content.includes('1., 2., 3.')) {
|
|
174
|
-
this.errors.push(
|
|
173
|
+
this.errors.push(
|
|
174
|
+
'Testing standards must require numbered test cases (policy: block)'
|
|
175
|
+
);
|
|
175
176
|
}
|
|
176
177
|
}
|
|
177
178
|
}
|
|
@@ -212,7 +213,7 @@ class CheckCommand {
|
|
|
212
213
|
for (const dep of deprecations) {
|
|
213
214
|
if (await fs.pathExists(dep.file)) {
|
|
214
215
|
if (dep.bridge) {
|
|
215
|
-
if (!await fs.pathExists(dep.bridge)) {
|
|
216
|
+
if (!(await fs.pathExists(dep.bridge))) {
|
|
216
217
|
this.warnings.push(dep.message);
|
|
217
218
|
}
|
|
218
219
|
} else {
|
|
@@ -240,7 +241,9 @@ class CheckCommand {
|
|
|
240
241
|
}
|
|
241
242
|
|
|
242
243
|
if (!anyBridge) {
|
|
243
|
-
this.warnings.push(
|
|
244
|
+
this.warnings.push(
|
|
245
|
+
'No platform bridge files found. Run: ck <platform> (e.g., ck claude, ck cursor)'
|
|
246
|
+
);
|
|
244
247
|
}
|
|
245
248
|
}
|
|
246
249
|
|
|
@@ -249,21 +252,21 @@ class CheckCommand {
|
|
|
249
252
|
|
|
250
253
|
if (this.errors.length > 0) {
|
|
251
254
|
console.log(chalk.red(`\nā Errors (${this.errors.length}):`));
|
|
252
|
-
this.errors.forEach(error => {
|
|
255
|
+
this.errors.forEach((error) => {
|
|
253
256
|
console.log(chalk.red(` ⢠${error}`));
|
|
254
257
|
});
|
|
255
258
|
}
|
|
256
259
|
|
|
257
260
|
if (this.warnings.length > 0) {
|
|
258
261
|
console.log(chalk.yellow(`\nā ļø Warnings (${this.warnings.length}):`));
|
|
259
|
-
this.warnings.forEach(warning => {
|
|
262
|
+
this.warnings.forEach((warning) => {
|
|
260
263
|
console.log(chalk.yellow(` ⢠${warning}`));
|
|
261
264
|
});
|
|
262
265
|
}
|
|
263
266
|
|
|
264
267
|
if (this.info.length > 0 && options.verbose) {
|
|
265
268
|
console.log(chalk.blue(`\nā¹ļø Info (${this.info.length}):`));
|
|
266
|
-
this.info.forEach(info => {
|
|
269
|
+
this.info.forEach((info) => {
|
|
267
270
|
console.log(chalk.blue(` ⢠${info}`));
|
|
268
271
|
});
|
|
269
272
|
}
|
|
@@ -274,7 +277,11 @@ class CheckCommand {
|
|
|
274
277
|
if (this.errors.length === 0 && this.warnings.length === 0) {
|
|
275
278
|
console.log(chalk.green('\nā
All checks passed!'));
|
|
276
279
|
} else if (this.errors.length === 0) {
|
|
277
|
-
console.log(
|
|
280
|
+
console.log(
|
|
281
|
+
chalk.yellow(
|
|
282
|
+
`\nā ļø ${this.warnings.length} warning(s) found (use --strict to fail on warnings)`
|
|
283
|
+
)
|
|
284
|
+
);
|
|
278
285
|
} else {
|
|
279
286
|
console.log(chalk.red(`\nā ${this.errors.length} error(s) found`));
|
|
280
287
|
}
|
|
@@ -287,4 +294,3 @@ async function check(options) {
|
|
|
287
294
|
}
|
|
288
295
|
|
|
289
296
|
module.exports = check;
|
|
290
|
-
|