@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,290 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
const fs = require('fs-extra');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const yaml = require('js-yaml');
|
|
5
|
+
|
|
6
|
+
class CheckCommand {
|
|
7
|
+
constructor() {
|
|
8
|
+
this.errors = [];
|
|
9
|
+
this.warnings = [];
|
|
10
|
+
this.info = [];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async run(options = {}) {
|
|
14
|
+
console.log(chalk.magenta('🔍 ContextKit Validation Check\n'));
|
|
15
|
+
|
|
16
|
+
if (!await fs.pathExists('.contextkit/config.yml')) {
|
|
17
|
+
console.log(chalk.red('❌ ContextKit not installed'));
|
|
18
|
+
console.log(chalk.yellow(' Run: contextkit install'));
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Load config
|
|
23
|
+
const config = await this.loadConfig();
|
|
24
|
+
if (!config) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Run checks
|
|
29
|
+
await this.checkManifest(config);
|
|
30
|
+
await this.checkRequiredFiles(config);
|
|
31
|
+
await this.checkOptionalFiles(config);
|
|
32
|
+
await this.checkStandardsFreshness(config);
|
|
33
|
+
await this.checkPolicyCompliance(config);
|
|
34
|
+
await this.checkPlatformIntegrations();
|
|
35
|
+
|
|
36
|
+
// Display results
|
|
37
|
+
this.displayResults(options);
|
|
38
|
+
|
|
39
|
+
// Exit with appropriate code
|
|
40
|
+
if (this.errors.length > 0) {
|
|
41
|
+
process.exit(1);
|
|
42
|
+
} else if (this.warnings.length > 0 && options.strict) {
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async loadConfig() {
|
|
48
|
+
try {
|
|
49
|
+
const configContent = await fs.readFile('.contextkit/config.yml', 'utf-8');
|
|
50
|
+
return yaml.load(configContent);
|
|
51
|
+
} catch (error) {
|
|
52
|
+
this.errors.push('Failed to load config.yml: ' + error.message);
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async checkManifest(config) {
|
|
58
|
+
// Check manifest schema
|
|
59
|
+
if (!config.vk) {
|
|
60
|
+
this.warnings.push('Manifest missing "vk" field (should be 1)');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (!config.version) {
|
|
64
|
+
this.warnings.push('Manifest missing "version" field');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (!config.updated) {
|
|
68
|
+
this.warnings.push('Manifest missing "updated" field');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (!config.profile) {
|
|
72
|
+
this.warnings.push('Manifest missing "profile" field');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (config.metadata) {
|
|
76
|
+
this.info.push(`Generated by: ${config.metadata.generated_by || 'unknown'}`);
|
|
77
|
+
if (config.metadata.generated_at) {
|
|
78
|
+
this.info.push(`Generated at: ${config.metadata.generated_at}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async checkRequiredFiles(config) {
|
|
84
|
+
if (!config.required || !Array.isArray(config.required)) {
|
|
85
|
+
this.warnings.push('No required files specified in config');
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
for (const file of config.required) {
|
|
90
|
+
const filePath = `.contextkit/${file}`;
|
|
91
|
+
if (!await fs.pathExists(filePath)) {
|
|
92
|
+
this.errors.push(`Required file missing: ${file}`);
|
|
93
|
+
} else {
|
|
94
|
+
this.info.push(`✓ Required file exists: ${file}`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async checkOptionalFiles(config) {
|
|
100
|
+
if (!config.optional || !Array.isArray(config.optional)) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
for (const file of config.optional) {
|
|
105
|
+
const filePath = `.contextkit/${file}`;
|
|
106
|
+
if (!await fs.pathExists(filePath)) {
|
|
107
|
+
this.warnings.push(`Optional file missing: ${file}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async checkStandardsFreshness(config) {
|
|
113
|
+
// Load policy to get freshness threshold
|
|
114
|
+
let freshnessDays = 90; // default
|
|
115
|
+
try {
|
|
116
|
+
if (await fs.pathExists('.contextkit/policies/policy.yml')) {
|
|
117
|
+
const policyContent = await fs.readFile('.contextkit/policies/policy.yml', 'utf-8');
|
|
118
|
+
const policy = yaml.load(policyContent);
|
|
119
|
+
if (policy?.enforcement?.standards?.freshness_days) {
|
|
120
|
+
freshnessDays = policy.enforcement.standards.freshness_days;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
} catch (error) {
|
|
124
|
+
// Policy file might not exist or be invalid, use default
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Check standards files
|
|
128
|
+
const standardsFiles = [
|
|
129
|
+
'standards/code-style.md',
|
|
130
|
+
'standards/testing.md',
|
|
131
|
+
'standards/architecture.md',
|
|
132
|
+
'standards/ai-guidelines.md',
|
|
133
|
+
'standards/workflows.md'
|
|
134
|
+
];
|
|
135
|
+
|
|
136
|
+
for (const file of standardsFiles) {
|
|
137
|
+
const filePath = `.contextkit/${file}`;
|
|
138
|
+
if (await fs.pathExists(filePath)) {
|
|
139
|
+
const stats = await fs.stat(filePath);
|
|
140
|
+
const daysSinceUpdate = (Date.now() - stats.mtime.getTime()) / (1000 * 60 * 60 * 24);
|
|
141
|
+
|
|
142
|
+
if (daysSinceUpdate > freshnessDays) {
|
|
143
|
+
this.warnings.push(
|
|
144
|
+
`Standards file outdated: ${file} (last updated ${Math.floor(daysSinceUpdate)} days ago, threshold: ${freshnessDays} days)`
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async checkPolicyCompliance(config) {
|
|
152
|
+
try {
|
|
153
|
+
if (!await fs.pathExists('.contextkit/policies/policy.yml')) {
|
|
154
|
+
this.warnings.push('Policy file missing: policies/policy.yml');
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const policyContent = await fs.readFile('.contextkit/policies/policy.yml', 'utf-8');
|
|
159
|
+
const policy = yaml.load(policyContent);
|
|
160
|
+
|
|
161
|
+
if (!policy.enforcement) {
|
|
162
|
+
this.warnings.push('Policy file missing enforcement rules');
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Check testing policy
|
|
167
|
+
if (policy.enforcement.testing) {
|
|
168
|
+
if (policy.enforcement.testing.numbered_cases === 'block') {
|
|
169
|
+
// Check if testing.md mentions numbered test cases
|
|
170
|
+
const testingPath = '.contextkit/standards/testing.md';
|
|
171
|
+
if (await fs.pathExists(testingPath)) {
|
|
172
|
+
const content = await fs.readFile(testingPath, 'utf-8');
|
|
173
|
+
if (!content.includes('numbered') && !content.includes('1., 2., 3.')) {
|
|
174
|
+
this.errors.push('Testing standards must require numbered test cases (policy: block)');
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
this.info.push('✓ Policy file exists and is valid');
|
|
181
|
+
} catch (error) {
|
|
182
|
+
this.warnings.push(`Policy check failed: ${error.message}`);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async checkPlatformIntegrations() {
|
|
187
|
+
const fs = require('fs-extra');
|
|
188
|
+
|
|
189
|
+
// Check for deprecated files and suggest modern alternatives
|
|
190
|
+
const deprecations = [
|
|
191
|
+
{
|
|
192
|
+
file: '.cursor/rules/contextkit.mdc',
|
|
193
|
+
message: 'Deprecated monolithic Cursor rule found. Run: ck cursor (creates scoped rules)',
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
file: '.codex/README.md',
|
|
197
|
+
bridge: 'AGENTS.md',
|
|
198
|
+
message: 'Legacy .codex/README.md found without AGENTS.md. Run: ck codex',
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
file: '.gemini/README.md',
|
|
202
|
+
bridge: 'GEMINI.md',
|
|
203
|
+
message: 'Legacy .gemini/README.md found without GEMINI.md. Run: ck gemini',
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
file: '.claude/README.md',
|
|
207
|
+
bridge: 'CLAUDE.md',
|
|
208
|
+
message: 'Legacy .claude/README.md found without CLAUDE.md. Run: ck claude',
|
|
209
|
+
},
|
|
210
|
+
];
|
|
211
|
+
|
|
212
|
+
for (const dep of deprecations) {
|
|
213
|
+
if (await fs.pathExists(dep.file)) {
|
|
214
|
+
if (dep.bridge) {
|
|
215
|
+
if (!await fs.pathExists(dep.bridge)) {
|
|
216
|
+
this.warnings.push(dep.message);
|
|
217
|
+
}
|
|
218
|
+
} else {
|
|
219
|
+
this.warnings.push(dep.message);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Check bridge file presence for common platforms
|
|
225
|
+
const bridgeChecks = [
|
|
226
|
+
{ file: 'CLAUDE.md', platform: 'claude', label: 'Claude Code' },
|
|
227
|
+
{ file: 'AGENTS.md', platform: 'codex', label: 'Codex CLI' },
|
|
228
|
+
{ file: 'GEMINI.md', platform: 'gemini', label: 'Gemini CLI' },
|
|
229
|
+
{ file: 'CONVENTIONS.md', platform: 'aider', label: 'Aider' },
|
|
230
|
+
{ file: '.windsurfrules', platform: 'windsurf', label: 'Windsurf' },
|
|
231
|
+
{ file: '.github/copilot-instructions.md', platform: 'copilot', label: 'GitHub Copilot' },
|
|
232
|
+
];
|
|
233
|
+
|
|
234
|
+
let anyBridge = false;
|
|
235
|
+
for (const check of bridgeChecks) {
|
|
236
|
+
if (await fs.pathExists(check.file)) {
|
|
237
|
+
this.info.push(`✓ ${check.label} bridge file: ${check.file}`);
|
|
238
|
+
anyBridge = true;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (!anyBridge) {
|
|
243
|
+
this.warnings.push('No platform bridge files found. Run: ck <platform> (e.g., ck claude, ck cursor)');
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
displayResults(options) {
|
|
248
|
+
console.log('─'.repeat(60));
|
|
249
|
+
|
|
250
|
+
if (this.errors.length > 0) {
|
|
251
|
+
console.log(chalk.red(`\n❌ Errors (${this.errors.length}):`));
|
|
252
|
+
this.errors.forEach(error => {
|
|
253
|
+
console.log(chalk.red(` • ${error}`));
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (this.warnings.length > 0) {
|
|
258
|
+
console.log(chalk.yellow(`\n⚠️ Warnings (${this.warnings.length}):`));
|
|
259
|
+
this.warnings.forEach(warning => {
|
|
260
|
+
console.log(chalk.yellow(` • ${warning}`));
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (this.info.length > 0 && options.verbose) {
|
|
265
|
+
console.log(chalk.blue(`\nℹ️ Info (${this.info.length}):`));
|
|
266
|
+
this.info.forEach(info => {
|
|
267
|
+
console.log(chalk.blue(` • ${info}`));
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
console.log('\n' + '─'.repeat(60));
|
|
272
|
+
|
|
273
|
+
// Summary
|
|
274
|
+
if (this.errors.length === 0 && this.warnings.length === 0) {
|
|
275
|
+
console.log(chalk.green('\n✅ All checks passed!'));
|
|
276
|
+
} else if (this.errors.length === 0) {
|
|
277
|
+
console.log(chalk.yellow(`\n⚠️ ${this.warnings.length} warning(s) found (use --strict to fail on warnings)`));
|
|
278
|
+
} else {
|
|
279
|
+
console.log(chalk.red(`\n❌ ${this.errors.length} error(s) found`));
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
async function check(options) {
|
|
285
|
+
const cmd = new CheckCommand();
|
|
286
|
+
await cmd.run(options);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
module.exports = check;
|
|
290
|
+
|
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
const fs = require('fs-extra');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const yaml = require('js-yaml');
|
|
5
|
+
const http = require('http');
|
|
6
|
+
const { execSync } = require('child_process');
|
|
7
|
+
|
|
8
|
+
class DashboardCommand {
|
|
9
|
+
constructor() {
|
|
10
|
+
this.port = 3001;
|
|
11
|
+
this.metrics = {};
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async run(options = {}) {
|
|
15
|
+
console.log(chalk.magenta('📊 Starting ContextKit Dashboard\n'));
|
|
16
|
+
|
|
17
|
+
if (!await fs.pathExists('.contextkit/config.yml')) {
|
|
18
|
+
console.log(chalk.red('❌ ContextKit not installed'));
|
|
19
|
+
console.log(chalk.yellow(' Run: contextkit install'));
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Collect metrics
|
|
24
|
+
await this.collectMetrics();
|
|
25
|
+
|
|
26
|
+
// Start server
|
|
27
|
+
if (options.server !== false) {
|
|
28
|
+
this.startServer(options.port || this.port);
|
|
29
|
+
} else {
|
|
30
|
+
// Just display metrics
|
|
31
|
+
this.displayMetrics();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async collectMetrics() {
|
|
36
|
+
this.metrics = {
|
|
37
|
+
standards: {},
|
|
38
|
+
corrections: {},
|
|
39
|
+
policy: {},
|
|
40
|
+
freshness: {},
|
|
41
|
+
files: {}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// Load config
|
|
45
|
+
const config = await this.loadConfig();
|
|
46
|
+
this.metrics.config = config;
|
|
47
|
+
|
|
48
|
+
// Check standards files
|
|
49
|
+
const standardsFiles = [
|
|
50
|
+
'standards/code-style.md',
|
|
51
|
+
'standards/testing.md',
|
|
52
|
+
'standards/architecture.md',
|
|
53
|
+
'standards/ai-guidelines.md',
|
|
54
|
+
'standards/workflows.md',
|
|
55
|
+
'standards/glossary.md'
|
|
56
|
+
];
|
|
57
|
+
|
|
58
|
+
for (const file of standardsFiles) {
|
|
59
|
+
const filePath = `.contextkit/${file}`;
|
|
60
|
+
if (await fs.pathExists(filePath)) {
|
|
61
|
+
const stats = await fs.stat(filePath);
|
|
62
|
+
const daysSinceUpdate = (Date.now() - stats.mtime.getTime()) / (1000 * 60 * 60 * 24);
|
|
63
|
+
|
|
64
|
+
this.metrics.standards[file] = {
|
|
65
|
+
exists: true,
|
|
66
|
+
lastModified: stats.mtime.toISOString(),
|
|
67
|
+
daysSinceUpdate: Math.floor(daysSinceUpdate),
|
|
68
|
+
size: stats.size
|
|
69
|
+
};
|
|
70
|
+
} else {
|
|
71
|
+
this.metrics.standards[file] = {
|
|
72
|
+
exists: false
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Parse corrections log
|
|
78
|
+
if (await fs.pathExists('.contextkit/corrections.md')) {
|
|
79
|
+
const correctionsContent = await fs.readFile('.contextkit/corrections.md', 'utf-8');
|
|
80
|
+
|
|
81
|
+
// Count sessions
|
|
82
|
+
const sessionMatches = correctionsContent.matchAll(/### (\d{4}-\d{2}-\d{2})/g);
|
|
83
|
+
const sessions = Array.from(sessionMatches);
|
|
84
|
+
this.metrics.corrections.totalSessions = sessions.length;
|
|
85
|
+
|
|
86
|
+
// Count rule updates
|
|
87
|
+
const ruleUpdateMatches = correctionsContent.matchAll(/#### Rule Updates[\s\S]*?(?=####|###|##|$)/g);
|
|
88
|
+
let ruleUpdates = 0;
|
|
89
|
+
for (const match of ruleUpdateMatches) {
|
|
90
|
+
const updates = match[0].match(/^-/gm);
|
|
91
|
+
if (updates) ruleUpdates += updates.length;
|
|
92
|
+
}
|
|
93
|
+
this.metrics.corrections.ruleUpdates = ruleUpdates;
|
|
94
|
+
|
|
95
|
+
// Count AI behavior issues
|
|
96
|
+
const behaviorMatches = correctionsContent.matchAll(/\[HIGH\]/g);
|
|
97
|
+
const mediumMatches = correctionsContent.matchAll(/\[MEDIUM\]/g);
|
|
98
|
+
const lowMatches = correctionsContent.matchAll(/\[LOW\]/g);
|
|
99
|
+
|
|
100
|
+
this.metrics.corrections.highPriority = Array.from(behaviorMatches).length;
|
|
101
|
+
this.metrics.corrections.mediumPriority = Array.from(mediumMatches).length;
|
|
102
|
+
this.metrics.corrections.lowPriority = Array.from(lowMatches).length;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Load policy
|
|
106
|
+
if (await fs.pathExists('.contextkit/policies/policy.yml')) {
|
|
107
|
+
const policyContent = await fs.readFile('.contextkit/policies/policy.yml', 'utf-8');
|
|
108
|
+
this.metrics.policy = yaml.load(policyContent);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Check product context
|
|
112
|
+
const productFiles = [
|
|
113
|
+
'product/mission.md',
|
|
114
|
+
'product/roadmap.md',
|
|
115
|
+
'product/decisions.md',
|
|
116
|
+
'product/context.md'
|
|
117
|
+
];
|
|
118
|
+
|
|
119
|
+
for (const file of productFiles) {
|
|
120
|
+
const filePath = `.contextkit/${file}`;
|
|
121
|
+
this.metrics.files[file] = await fs.pathExists(filePath);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async loadConfig() {
|
|
126
|
+
try {
|
|
127
|
+
const configContent = await fs.readFile('.contextkit/config.yml', 'utf-8');
|
|
128
|
+
return yaml.load(configContent);
|
|
129
|
+
} catch (error) {
|
|
130
|
+
return {};
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
displayMetrics() {
|
|
135
|
+
console.log(chalk.blue('\n📊 ContextKit Metrics\n'));
|
|
136
|
+
console.log('─'.repeat(60));
|
|
137
|
+
|
|
138
|
+
// Standards freshness
|
|
139
|
+
console.log(chalk.bold('\n📚 Standards Freshness:'));
|
|
140
|
+
const standards = Object.entries(this.metrics.standards);
|
|
141
|
+
const existing = standards.filter(([_, data]) => data.exists);
|
|
142
|
+
const outdated = existing.filter(([_, data]) => data.daysSinceUpdate > 90);
|
|
143
|
+
|
|
144
|
+
console.log(chalk.green(` ${existing.length}/${standards.length} standards files exist`));
|
|
145
|
+
if (outdated.length > 0) {
|
|
146
|
+
console.log(chalk.yellow(` ⚠️ ${outdated.length} files outdated (>90 days)`));
|
|
147
|
+
outdated.forEach(([file, data]) => {
|
|
148
|
+
console.log(chalk.dim(` - ${file}: ${data.daysSinceUpdate} days old`));
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Corrections log
|
|
153
|
+
if (this.metrics.corrections.totalSessions > 0) {
|
|
154
|
+
console.log(chalk.bold('\n📝 Corrections Log:'));
|
|
155
|
+
console.log(chalk.cyan(` Total Sessions: ${this.metrics.corrections.totalSessions}`));
|
|
156
|
+
console.log(chalk.cyan(` Rule Updates: ${this.metrics.corrections.ruleUpdates || 0}`));
|
|
157
|
+
console.log(chalk.red(` High Priority Issues: ${this.metrics.corrections.highPriority || 0}`));
|
|
158
|
+
console.log(chalk.yellow(` Medium Priority Issues: ${this.metrics.corrections.mediumPriority || 0}`));
|
|
159
|
+
console.log(chalk.green(` Low Priority Issues: ${this.metrics.corrections.lowPriority || 0}`));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Product context
|
|
163
|
+
console.log(chalk.bold('\n📦 Product Context:'));
|
|
164
|
+
const productFiles = Object.entries(this.metrics.files);
|
|
165
|
+
const existingProduct = productFiles.filter(([_, exists]) => exists);
|
|
166
|
+
console.log(chalk.cyan(` ${existingProduct.length}/${productFiles.length} product files exist`));
|
|
167
|
+
|
|
168
|
+
// Policy
|
|
169
|
+
if (this.metrics.policy.enforcement) {
|
|
170
|
+
console.log(chalk.bold('\n⚖️ Policy Enforcement:'));
|
|
171
|
+
const enforcement = this.metrics.policy.enforcement;
|
|
172
|
+
if (enforcement.testing) {
|
|
173
|
+
console.log(chalk.cyan(` Testing: ${enforcement.testing.numbered_cases || 'not set'}`));
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
console.log('\n' + '─'.repeat(60));
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
startServer(port) {
|
|
181
|
+
const server = http.createServer((req, res) => {
|
|
182
|
+
if (req.url === '/' || req.url === '/index.html') {
|
|
183
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
184
|
+
res.end(this.generateDashboardHTML());
|
|
185
|
+
} else if (req.url === '/api/metrics') {
|
|
186
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
187
|
+
res.end(JSON.stringify(this.metrics, null, 2));
|
|
188
|
+
} else {
|
|
189
|
+
res.writeHead(404);
|
|
190
|
+
res.end('Not found');
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
server.listen(port, () => {
|
|
195
|
+
console.log(chalk.green(`\n✅ Dashboard running at http://localhost:${port}`));
|
|
196
|
+
console.log(chalk.blue(' Press Ctrl+C to stop\n'));
|
|
197
|
+
|
|
198
|
+
// Try to open browser
|
|
199
|
+
try {
|
|
200
|
+
const platform = process.platform;
|
|
201
|
+
const command = platform === 'darwin' ? 'open' : platform === 'win32' ? 'start' : 'xdg-open';
|
|
202
|
+
execSync(`${command} http://localhost:${port}`);
|
|
203
|
+
} catch (error) {
|
|
204
|
+
// Ignore if can't open browser
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
generateDashboardHTML() {
|
|
210
|
+
const metrics = this.metrics;
|
|
211
|
+
const standardsCount = Object.values(metrics.standards).filter(s => s.exists).length;
|
|
212
|
+
const totalStandards = Object.keys(metrics.standards).length;
|
|
213
|
+
const freshnessPercent = Math.round((standardsCount / totalStandards) * 100);
|
|
214
|
+
|
|
215
|
+
return `<!DOCTYPE html>
|
|
216
|
+
<html lang="en">
|
|
217
|
+
<head>
|
|
218
|
+
<meta charset="UTF-8">
|
|
219
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
220
|
+
<title>ContextKit Dashboard</title>
|
|
221
|
+
<style>
|
|
222
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
223
|
+
body {
|
|
224
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
|
225
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
226
|
+
padding: 20px;
|
|
227
|
+
min-height: 100vh;
|
|
228
|
+
}
|
|
229
|
+
.container {
|
|
230
|
+
max-width: 1200px;
|
|
231
|
+
margin: 0 auto;
|
|
232
|
+
}
|
|
233
|
+
h1 {
|
|
234
|
+
color: white;
|
|
235
|
+
margin-bottom: 30px;
|
|
236
|
+
text-align: center;
|
|
237
|
+
font-size: 2.5em;
|
|
238
|
+
}
|
|
239
|
+
.grid {
|
|
240
|
+
display: grid;
|
|
241
|
+
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
|
242
|
+
gap: 20px;
|
|
243
|
+
margin-bottom: 20px;
|
|
244
|
+
}
|
|
245
|
+
.card {
|
|
246
|
+
background: white;
|
|
247
|
+
border-radius: 10px;
|
|
248
|
+
padding: 20px;
|
|
249
|
+
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
|
250
|
+
}
|
|
251
|
+
.card h2 {
|
|
252
|
+
color: #333;
|
|
253
|
+
margin-bottom: 15px;
|
|
254
|
+
font-size: 1.3em;
|
|
255
|
+
}
|
|
256
|
+
.stat {
|
|
257
|
+
font-size: 2.5em;
|
|
258
|
+
font-weight: bold;
|
|
259
|
+
color: #667eea;
|
|
260
|
+
margin: 10px 0;
|
|
261
|
+
}
|
|
262
|
+
.stat-label {
|
|
263
|
+
color: #666;
|
|
264
|
+
font-size: 0.9em;
|
|
265
|
+
}
|
|
266
|
+
.progress-bar {
|
|
267
|
+
width: 100%;
|
|
268
|
+
height: 30px;
|
|
269
|
+
background: #e0e0e0;
|
|
270
|
+
border-radius: 15px;
|
|
271
|
+
overflow: hidden;
|
|
272
|
+
margin: 10px 0;
|
|
273
|
+
}
|
|
274
|
+
.progress-fill {
|
|
275
|
+
height: 100%;
|
|
276
|
+
background: linear-gradient(90deg, #667eea, #764ba2);
|
|
277
|
+
transition: width 0.3s ease;
|
|
278
|
+
display: flex;
|
|
279
|
+
align-items: center;
|
|
280
|
+
justify-content: center;
|
|
281
|
+
color: white;
|
|
282
|
+
font-weight: bold;
|
|
283
|
+
}
|
|
284
|
+
.list {
|
|
285
|
+
list-style: none;
|
|
286
|
+
}
|
|
287
|
+
.list li {
|
|
288
|
+
padding: 8px 0;
|
|
289
|
+
border-bottom: 1px solid #eee;
|
|
290
|
+
}
|
|
291
|
+
.list li:last-child {
|
|
292
|
+
border-bottom: none;
|
|
293
|
+
}
|
|
294
|
+
.badge {
|
|
295
|
+
display: inline-block;
|
|
296
|
+
padding: 4px 8px;
|
|
297
|
+
border-radius: 4px;
|
|
298
|
+
font-size: 0.8em;
|
|
299
|
+
font-weight: bold;
|
|
300
|
+
}
|
|
301
|
+
.badge-success { background: #4caf50; color: white; }
|
|
302
|
+
.badge-warning { background: #ff9800; color: white; }
|
|
303
|
+
.badge-danger { background: #f44336; color: white; }
|
|
304
|
+
.badge-info { background: #2196f3; color: white; }
|
|
305
|
+
</style>
|
|
306
|
+
</head>
|
|
307
|
+
<body>
|
|
308
|
+
<div class="container">
|
|
309
|
+
<h1>🎵 ContextKit Dashboard</h1>
|
|
310
|
+
|
|
311
|
+
<div class="grid">
|
|
312
|
+
<div class="card">
|
|
313
|
+
<h2>📚 Standards Freshness</h2>
|
|
314
|
+
<div class="stat">${freshnessPercent}%</div>
|
|
315
|
+
<div class="stat-label">${standardsCount} of ${totalStandards} files exist</div>
|
|
316
|
+
<div class="progress-bar">
|
|
317
|
+
<div class="progress-fill" style="width: ${freshnessPercent}%">${freshnessPercent}%</div>
|
|
318
|
+
</div>
|
|
319
|
+
</div>
|
|
320
|
+
|
|
321
|
+
<div class="card">
|
|
322
|
+
<h2>📝 Corrections Log</h2>
|
|
323
|
+
<div class="stat">${metrics.corrections.totalSessions || 0}</div>
|
|
324
|
+
<div class="stat-label">Total Sessions</div>
|
|
325
|
+
<ul class="list">
|
|
326
|
+
<li><span class="badge badge-danger">HIGH</span> ${metrics.corrections.highPriority || 0} issues</li>
|
|
327
|
+
<li><span class="badge badge-warning">MEDIUM</span> ${metrics.corrections.mediumPriority || 0} issues</li>
|
|
328
|
+
<li><span class="badge badge-success">LOW</span> ${metrics.corrections.lowPriority || 0} issues</li>
|
|
329
|
+
<li><span class="badge badge-info">Updates</span> ${metrics.corrections.ruleUpdates || 0} rule updates</li>
|
|
330
|
+
</ul>
|
|
331
|
+
</div>
|
|
332
|
+
|
|
333
|
+
<div class="card">
|
|
334
|
+
<h2>📦 Product Context</h2>
|
|
335
|
+
<ul class="list">
|
|
336
|
+
${Object.entries(metrics.files).map(([file, exists]) =>
|
|
337
|
+
`<li>${exists ? '✅' : '❌'} ${file.split('/').pop()}</li>`
|
|
338
|
+
).join('')}
|
|
339
|
+
</ul>
|
|
340
|
+
</div>
|
|
341
|
+
|
|
342
|
+
<div class="card">
|
|
343
|
+
<h2>⚖️ Policy</h2>
|
|
344
|
+
${metrics.policy.enforcement ? `
|
|
345
|
+
<ul class="list">
|
|
346
|
+
${metrics.policy.enforcement.testing ?
|
|
347
|
+
`<li>Testing: <span class="badge badge-info">${metrics.policy.enforcement.testing.numbered_cases || 'not set'}</span></li>` : ''}
|
|
348
|
+
${metrics.policy.enforcement.code_style ?
|
|
349
|
+
`<li>Code Style: <span class="badge badge-info">${metrics.policy.enforcement.code_style.typescript_strict || 'not set'}</span></li>` : ''}
|
|
350
|
+
</ul>
|
|
351
|
+
` : '<p>No policy configured</p>'}
|
|
352
|
+
</div>
|
|
353
|
+
</div>
|
|
354
|
+
|
|
355
|
+
<div class="card">
|
|
356
|
+
<h2>📊 Standards Files</h2>
|
|
357
|
+
<ul class="list">
|
|
358
|
+
${Object.entries(metrics.standards).map(([file, data]) => {
|
|
359
|
+
if (!data.exists) return `<li>❌ ${file}</li>`;
|
|
360
|
+
const days = data.daysSinceUpdate || 0;
|
|
361
|
+
const badge = days > 90 ? 'badge-warning' : days > 30 ? 'badge-info' : 'badge-success';
|
|
362
|
+
return `<li>✅ ${file} <span class="badge ${badge}">${days} days old</span></li>`;
|
|
363
|
+
}).join('')}
|
|
364
|
+
</ul>
|
|
365
|
+
</div>
|
|
366
|
+
</div>
|
|
367
|
+
|
|
368
|
+
<script>
|
|
369
|
+
// Auto-refresh every 30 seconds
|
|
370
|
+
setTimeout(() => location.reload(), 30000);
|
|
371
|
+
</script>
|
|
372
|
+
</body>
|
|
373
|
+
</html>`;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
async function dashboard(options) {
|
|
378
|
+
const cmd = new DashboardCommand();
|
|
379
|
+
await cmd.run(options);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
module.exports = dashboard;
|
|
383
|
+
|