ruvnet-kb-first 5.0.0
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 +674 -0
- package/SKILL.md +740 -0
- package/bin/kb-first.js +123 -0
- package/install/init-project.sh +435 -0
- package/install/install-global.sh +257 -0
- package/install/kb-first-autodetect.sh +108 -0
- package/install/kb-first-command.md +80 -0
- package/install/kb-first-skill.md +262 -0
- package/package.json +87 -0
- package/phases/00-assessment.md +529 -0
- package/phases/01-storage.md +194 -0
- package/phases/01.5-hooks-setup.md +521 -0
- package/phases/02-kb-creation.md +413 -0
- package/phases/03-persistence.md +125 -0
- package/phases/04-visualization.md +170 -0
- package/phases/05-integration.md +114 -0
- package/phases/06-scaffold.md +130 -0
- package/phases/07-build.md +493 -0
- package/phases/08-verification.md +597 -0
- package/phases/09-security.md +512 -0
- package/phases/10-documentation.md +613 -0
- package/phases/11-deployment.md +670 -0
- package/phases/testing.md +713 -0
- package/scripts/1.5-hooks-verify.sh +252 -0
- package/scripts/8.1-code-scan.sh +58 -0
- package/scripts/8.2-import-check.sh +42 -0
- package/scripts/8.3-source-returns.sh +52 -0
- package/scripts/8.4-startup-verify.sh +65 -0
- package/scripts/8.5-fallback-check.sh +63 -0
- package/scripts/8.6-attribution.sh +56 -0
- package/scripts/8.7-confidence.sh +56 -0
- package/scripts/8.8-gap-logging.sh +70 -0
- package/scripts/9-security-audit.sh +202 -0
- package/scripts/init-project.sh +395 -0
- package/scripts/verify-enforcement.sh +167 -0
- package/src/commands/hooks.js +361 -0
- package/src/commands/init.js +315 -0
- package/src/commands/phase.js +372 -0
- package/src/commands/score.js +380 -0
- package/src/commands/status.js +193 -0
- package/src/commands/verify.js +286 -0
- package/src/index.js +56 -0
- package/src/mcp-server.js +412 -0
- package/templates/attention-router.ts +534 -0
- package/templates/code-analysis.ts +683 -0
- package/templates/federated-kb-learner.ts +649 -0
- package/templates/gnn-engine.ts +1091 -0
- package/templates/intentions.md +277 -0
- package/templates/kb-client.ts +905 -0
- package/templates/schema.sql +303 -0
- package/templates/sona-config.ts +312 -0
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* KB-First Score Command
|
|
3
|
+
*
|
|
4
|
+
* Calculates the KB-First compliance score for the current project.
|
|
5
|
+
*
|
|
6
|
+
* Scoring Formula (100 points total):
|
|
7
|
+
* - KB Coverage (25): All code has KB citations
|
|
8
|
+
* - Phase Completion (25): All phases passed gates
|
|
9
|
+
* - Hook Compliance (15): Hooks installed and active
|
|
10
|
+
* - Gap Resolution (15): KB gaps addressed
|
|
11
|
+
* - Documentation (10): Docs complete
|
|
12
|
+
* - Security (10): Security audit passed
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import chalk from 'chalk';
|
|
16
|
+
import ora from 'ora';
|
|
17
|
+
import { existsSync, readFileSync, readdirSync, statSync } from 'fs';
|
|
18
|
+
import { join } from 'path';
|
|
19
|
+
import { globSync } from 'glob';
|
|
20
|
+
|
|
21
|
+
const SCORE_WEIGHTS = {
|
|
22
|
+
kbCoverage: 25,
|
|
23
|
+
phaseCompletion: 25,
|
|
24
|
+
hookCompliance: 15,
|
|
25
|
+
gapResolution: 15,
|
|
26
|
+
documentation: 10,
|
|
27
|
+
security: 10
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export async function scoreCommand(options) {
|
|
31
|
+
const cwd = process.cwd();
|
|
32
|
+
|
|
33
|
+
console.log('');
|
|
34
|
+
console.log(chalk.cyan('Calculating KB-First Compliance Score...'));
|
|
35
|
+
console.log('');
|
|
36
|
+
|
|
37
|
+
// Check if project is initialized
|
|
38
|
+
const configPath = join(cwd, '.ruvector', 'config.json');
|
|
39
|
+
if (!existsSync(configPath)) {
|
|
40
|
+
console.log(chalk.red('Error: Not a KB-First project.'));
|
|
41
|
+
console.log(chalk.gray('Run: kb-first init'));
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const config = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
46
|
+
const scores = {};
|
|
47
|
+
|
|
48
|
+
// 1. KB Coverage Score (25 points)
|
|
49
|
+
const spinner = ora('Checking KB coverage...').start();
|
|
50
|
+
scores.kbCoverage = await calculateKBCoverage(cwd);
|
|
51
|
+
spinner.succeed(`KB Coverage: ${scores.kbCoverage.score}/${SCORE_WEIGHTS.kbCoverage}`);
|
|
52
|
+
|
|
53
|
+
// 2. Phase Completion Score (25 points)
|
|
54
|
+
spinner.start('Checking phase completion...');
|
|
55
|
+
scores.phaseCompletion = await calculatePhaseCompletion(cwd, config);
|
|
56
|
+
spinner.succeed(`Phase Completion: ${scores.phaseCompletion.score}/${SCORE_WEIGHTS.phaseCompletion}`);
|
|
57
|
+
|
|
58
|
+
// 3. Hook Compliance Score (15 points)
|
|
59
|
+
spinner.start('Checking hook compliance...');
|
|
60
|
+
scores.hookCompliance = await calculateHookCompliance(cwd, config);
|
|
61
|
+
spinner.succeed(`Hook Compliance: ${scores.hookCompliance.score}/${SCORE_WEIGHTS.hookCompliance}`);
|
|
62
|
+
|
|
63
|
+
// 4. Gap Resolution Score (15 points)
|
|
64
|
+
spinner.start('Checking gap resolution...');
|
|
65
|
+
scores.gapResolution = await calculateGapResolution(cwd);
|
|
66
|
+
spinner.succeed(`Gap Resolution: ${scores.gapResolution.score}/${SCORE_WEIGHTS.gapResolution}`);
|
|
67
|
+
|
|
68
|
+
// 5. Documentation Score (10 points)
|
|
69
|
+
spinner.start('Checking documentation...');
|
|
70
|
+
scores.documentation = await calculateDocumentation(cwd);
|
|
71
|
+
spinner.succeed(`Documentation: ${scores.documentation.score}/${SCORE_WEIGHTS.documentation}`);
|
|
72
|
+
|
|
73
|
+
// 6. Security Score (10 points)
|
|
74
|
+
spinner.start('Checking security...');
|
|
75
|
+
scores.security = await calculateSecurity(cwd);
|
|
76
|
+
spinner.succeed(`Security: ${scores.security.score}/${SCORE_WEIGHTS.security}`);
|
|
77
|
+
|
|
78
|
+
// Calculate total
|
|
79
|
+
const totalScore = Object.values(scores).reduce((sum, s) => sum + s.score, 0);
|
|
80
|
+
const maxScore = Object.values(SCORE_WEIGHTS).reduce((sum, w) => sum + w, 0);
|
|
81
|
+
|
|
82
|
+
// Output
|
|
83
|
+
console.log('');
|
|
84
|
+
console.log(chalk.white('═══════════════════════════════════════════════════════════════'));
|
|
85
|
+
console.log(chalk.bold.white(' KB-FIRST COMPLIANCE SCORE'));
|
|
86
|
+
console.log(chalk.white('═══════════════════════════════════════════════════════════════'));
|
|
87
|
+
console.log('');
|
|
88
|
+
|
|
89
|
+
if (options.detailed) {
|
|
90
|
+
printDetailedScores(scores);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Total score with color coding
|
|
94
|
+
const scoreColor = totalScore >= 90 ? chalk.green :
|
|
95
|
+
totalScore >= 70 ? chalk.yellow :
|
|
96
|
+
chalk.red;
|
|
97
|
+
|
|
98
|
+
console.log(` Total Score: ${scoreColor.bold(`${totalScore}/${maxScore}`)}`);
|
|
99
|
+
console.log('');
|
|
100
|
+
|
|
101
|
+
// Grade
|
|
102
|
+
const grade = getGrade(totalScore);
|
|
103
|
+
console.log(` Grade: ${scoreColor.bold(grade.letter)} - ${grade.description}`);
|
|
104
|
+
console.log('');
|
|
105
|
+
|
|
106
|
+
// Recommendations
|
|
107
|
+
if (totalScore < 100) {
|
|
108
|
+
console.log(chalk.white(' Recommendations:'));
|
|
109
|
+
printRecommendations(scores);
|
|
110
|
+
console.log('');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// JSON output if requested
|
|
114
|
+
if (options.json) {
|
|
115
|
+
console.log(JSON.stringify({
|
|
116
|
+
totalScore,
|
|
117
|
+
maxScore,
|
|
118
|
+
grade: grade.letter,
|
|
119
|
+
scores
|
|
120
|
+
}, null, 2));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return totalScore;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async function calculateKBCoverage(cwd) {
|
|
127
|
+
const srcDir = join(cwd, 'src');
|
|
128
|
+
if (!existsSync(srcDir)) {
|
|
129
|
+
return { score: 0, details: 'No src directory found' };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const codeFiles = globSync('**/*.{ts,tsx,js,jsx,py}', { cwd: srcDir });
|
|
133
|
+
if (codeFiles.length === 0) {
|
|
134
|
+
return { score: SCORE_WEIGHTS.kbCoverage, details: 'No code files to check' };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
let filesWithCitation = 0;
|
|
138
|
+
for (const file of codeFiles) {
|
|
139
|
+
const content = readFileSync(join(srcDir, file), 'utf-8');
|
|
140
|
+
if (content.includes('KB-Generated:') || content.includes('Sources:')) {
|
|
141
|
+
filesWithCitation++;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const coverage = filesWithCitation / codeFiles.length;
|
|
146
|
+
const score = Math.round(coverage * SCORE_WEIGHTS.kbCoverage);
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
score,
|
|
150
|
+
details: `${filesWithCitation}/${codeFiles.length} files have KB citations`,
|
|
151
|
+
coverage: Math.round(coverage * 100)
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async function calculatePhaseCompletion(cwd, config) {
|
|
156
|
+
const completedPhases = config.phases?.completed || [];
|
|
157
|
+
const totalPhases = 12; // Phases 0-11
|
|
158
|
+
|
|
159
|
+
const score = Math.round((completedPhases.length / totalPhases) * SCORE_WEIGHTS.phaseCompletion);
|
|
160
|
+
|
|
161
|
+
return {
|
|
162
|
+
score,
|
|
163
|
+
details: `${completedPhases.length}/${totalPhases} phases completed`,
|
|
164
|
+
currentPhase: config.phases?.current || 0,
|
|
165
|
+
completedPhases
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async function calculateHookCompliance(cwd, config) {
|
|
170
|
+
if (!config.hooks?.enabled) {
|
|
171
|
+
return { score: 0, details: 'Hooks not enabled' };
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const hooksDir = join(cwd, '.ruvector', 'hooks');
|
|
175
|
+
let hookScore = 0;
|
|
176
|
+
|
|
177
|
+
// Check for hook files
|
|
178
|
+
const requiredHooks = ['pre-tool-use.py', 'post-tool-use.py'];
|
|
179
|
+
let foundHooks = 0;
|
|
180
|
+
|
|
181
|
+
for (const hook of requiredHooks) {
|
|
182
|
+
if (existsSync(join(hooksDir, hook))) {
|
|
183
|
+
foundHooks++;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Hooks exist (10 points)
|
|
188
|
+
hookScore += Math.round((foundHooks / requiredHooks.length) * 10);
|
|
189
|
+
|
|
190
|
+
// Hooks enabled in config (5 points)
|
|
191
|
+
if (config.hooks.preToolUse && config.hooks.postToolUse) {
|
|
192
|
+
hookScore += 5;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return {
|
|
196
|
+
score: Math.min(hookScore, SCORE_WEIGHTS.hookCompliance),
|
|
197
|
+
details: `${foundHooks}/${requiredHooks.length} hooks installed`,
|
|
198
|
+
enabled: config.hooks.enabled
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
async function calculateGapResolution(cwd) {
|
|
203
|
+
const gapLogPath = join(cwd, '.ruvector', 'gaps.jsonl');
|
|
204
|
+
|
|
205
|
+
if (!existsSync(gapLogPath)) {
|
|
206
|
+
return { score: SCORE_WEIGHTS.gapResolution, details: 'No gaps logged' };
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const gapContent = readFileSync(gapLogPath, 'utf-8').trim();
|
|
210
|
+
if (!gapContent) {
|
|
211
|
+
return { score: SCORE_WEIGHTS.gapResolution, details: 'No gaps logged' };
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const gaps = gapContent.split('\n').filter(Boolean).map(line => JSON.parse(line));
|
|
215
|
+
const unresolvedGaps = gaps.filter(g => !g.resolved);
|
|
216
|
+
|
|
217
|
+
// Deduct points for unresolved gaps (max 5 gaps = 0 points)
|
|
218
|
+
const gapPenalty = Math.min(unresolvedGaps.length, 5) * 3;
|
|
219
|
+
const score = Math.max(0, SCORE_WEIGHTS.gapResolution - gapPenalty);
|
|
220
|
+
|
|
221
|
+
return {
|
|
222
|
+
score,
|
|
223
|
+
details: `${unresolvedGaps.length} unresolved gaps`,
|
|
224
|
+
totalGaps: gaps.length,
|
|
225
|
+
unresolvedGaps: unresolvedGaps.length
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
async function calculateDocumentation(cwd) {
|
|
230
|
+
const requiredDocs = [
|
|
231
|
+
'README.md',
|
|
232
|
+
'docs/api.md',
|
|
233
|
+
'docs/architecture.md'
|
|
234
|
+
];
|
|
235
|
+
|
|
236
|
+
let foundDocs = 0;
|
|
237
|
+
for (const doc of requiredDocs) {
|
|
238
|
+
if (existsSync(join(cwd, doc))) {
|
|
239
|
+
foundDocs++;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Check README has basic sections
|
|
244
|
+
const readmePath = join(cwd, 'README.md');
|
|
245
|
+
let readmeScore = 0;
|
|
246
|
+
if (existsSync(readmePath)) {
|
|
247
|
+
const content = readFileSync(readmePath, 'utf-8');
|
|
248
|
+
if (content.includes('## Installation')) readmeScore += 1;
|
|
249
|
+
if (content.includes('## Usage')) readmeScore += 1;
|
|
250
|
+
if (content.includes('## API')) readmeScore += 1;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const docScore = Math.round((foundDocs / requiredDocs.length) * 7) + Math.min(readmeScore, 3);
|
|
254
|
+
|
|
255
|
+
return {
|
|
256
|
+
score: Math.min(docScore, SCORE_WEIGHTS.documentation),
|
|
257
|
+
details: `${foundDocs}/${requiredDocs.length} required docs found`,
|
|
258
|
+
foundDocs,
|
|
259
|
+
requiredDocs: requiredDocs.length
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
async function calculateSecurity(cwd) {
|
|
264
|
+
let securityScore = SCORE_WEIGHTS.security;
|
|
265
|
+
const issues = [];
|
|
266
|
+
|
|
267
|
+
// Check .gitignore for .env
|
|
268
|
+
const gitignorePath = join(cwd, '.gitignore');
|
|
269
|
+
if (existsSync(gitignorePath)) {
|
|
270
|
+
const content = readFileSync(gitignorePath, 'utf-8');
|
|
271
|
+
if (!content.includes('.env')) {
|
|
272
|
+
securityScore -= 3;
|
|
273
|
+
issues.push('.env not in .gitignore');
|
|
274
|
+
}
|
|
275
|
+
} else {
|
|
276
|
+
securityScore -= 3;
|
|
277
|
+
issues.push('No .gitignore file');
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Check for hardcoded secrets in src/
|
|
281
|
+
const srcDir = join(cwd, 'src');
|
|
282
|
+
if (existsSync(srcDir)) {
|
|
283
|
+
const files = globSync('**/*.{ts,tsx,js,jsx}', { cwd: srcDir });
|
|
284
|
+
for (const file of files.slice(0, 20)) { // Check first 20 files
|
|
285
|
+
const content = readFileSync(join(srcDir, file), 'utf-8');
|
|
286
|
+
if (/password\s*=\s*['"][^'"]{8,}['"]/.test(content) &&
|
|
287
|
+
!content.includes('process.env')) {
|
|
288
|
+
securityScore -= 2;
|
|
289
|
+
issues.push(`Potential hardcoded secret in ${file}`);
|
|
290
|
+
break;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Check npm audit (if package.json exists)
|
|
296
|
+
const packagePath = join(cwd, 'package.json');
|
|
297
|
+
if (existsSync(packagePath)) {
|
|
298
|
+
// Just check if npm audit would pass (simplified check)
|
|
299
|
+
const packageLock = join(cwd, 'package-lock.json');
|
|
300
|
+
if (!existsSync(packageLock)) {
|
|
301
|
+
securityScore -= 2;
|
|
302
|
+
issues.push('No package-lock.json for reproducible builds');
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return {
|
|
307
|
+
score: Math.max(0, securityScore),
|
|
308
|
+
details: issues.length ? issues.join(', ') : 'No security issues found',
|
|
309
|
+
issues
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function getGrade(score) {
|
|
314
|
+
if (score >= 98) return { letter: 'A+', description: 'Exceptional - Production Ready' };
|
|
315
|
+
if (score >= 93) return { letter: 'A', description: 'Excellent - Minor improvements possible' };
|
|
316
|
+
if (score >= 90) return { letter: 'A-', description: 'Very Good - Nearly production ready' };
|
|
317
|
+
if (score >= 87) return { letter: 'B+', description: 'Good - Some improvements needed' };
|
|
318
|
+
if (score >= 83) return { letter: 'B', description: 'Above Average - Multiple areas need work' };
|
|
319
|
+
if (score >= 80) return { letter: 'B-', description: 'Satisfactory - Several gaps to address' };
|
|
320
|
+
if (score >= 77) return { letter: 'C+', description: 'Fair - Significant work remaining' };
|
|
321
|
+
if (score >= 73) return { letter: 'C', description: 'Needs Work - Many areas incomplete' };
|
|
322
|
+
if (score >= 70) return { letter: 'C-', description: 'Below Average - Major gaps' };
|
|
323
|
+
return { letter: 'F', description: 'Incomplete - Not ready for review' };
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function printDetailedScores(scores) {
|
|
327
|
+
console.log(chalk.gray(' Category Breakdown:'));
|
|
328
|
+
console.log('');
|
|
329
|
+
|
|
330
|
+
for (const [category, data] of Object.entries(scores)) {
|
|
331
|
+
const maxScore = SCORE_WEIGHTS[category];
|
|
332
|
+
const percentage = Math.round((data.score / maxScore) * 100);
|
|
333
|
+
const bar = createProgressBar(percentage);
|
|
334
|
+
|
|
335
|
+
console.log(` ${formatCategoryName(category)}`);
|
|
336
|
+
console.log(` ${bar} ${data.score}/${maxScore} (${percentage}%)`);
|
|
337
|
+
console.log(chalk.gray(` ${data.details}`));
|
|
338
|
+
console.log('');
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function formatCategoryName(name) {
|
|
343
|
+
return name.replace(/([A-Z])/g, ' $1').replace(/^./, s => s.toUpperCase());
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function createProgressBar(percentage) {
|
|
347
|
+
const filled = Math.round(percentage / 5);
|
|
348
|
+
const empty = 20 - filled;
|
|
349
|
+
const color = percentage >= 80 ? chalk.green :
|
|
350
|
+
percentage >= 60 ? chalk.yellow :
|
|
351
|
+
chalk.red;
|
|
352
|
+
return color('█'.repeat(filled)) + chalk.gray('░'.repeat(empty));
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function printRecommendations(scores) {
|
|
356
|
+
const recommendations = [];
|
|
357
|
+
|
|
358
|
+
if (scores.kbCoverage.score < SCORE_WEIGHTS.kbCoverage) {
|
|
359
|
+
recommendations.push('Add KB citations to code files using kb_code_gen');
|
|
360
|
+
}
|
|
361
|
+
if (scores.phaseCompletion.score < SCORE_WEIGHTS.phaseCompletion) {
|
|
362
|
+
recommendations.push(`Complete Phase ${scores.phaseCompletion.currentPhase}: kb-first phase ${scores.phaseCompletion.currentPhase}`);
|
|
363
|
+
}
|
|
364
|
+
if (scores.hookCompliance.score < SCORE_WEIGHTS.hookCompliance) {
|
|
365
|
+
recommendations.push('Install KB-First hooks: kb-first hooks --install');
|
|
366
|
+
}
|
|
367
|
+
if (scores.gapResolution.score < SCORE_WEIGHTS.gapResolution) {
|
|
368
|
+
recommendations.push('Address unresolved KB gaps in .ruvector/gaps.jsonl');
|
|
369
|
+
}
|
|
370
|
+
if (scores.documentation.score < SCORE_WEIGHTS.documentation) {
|
|
371
|
+
recommendations.push('Add missing documentation (README, API docs, Architecture)');
|
|
372
|
+
}
|
|
373
|
+
if (scores.security.score < SCORE_WEIGHTS.security) {
|
|
374
|
+
recommendations.push('Fix security issues: kb-first verify --phase=9');
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
for (const rec of recommendations.slice(0, 5)) {
|
|
378
|
+
console.log(chalk.yellow(` - ${rec}`));
|
|
379
|
+
}
|
|
380
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* KB-First Status Command
|
|
3
|
+
*
|
|
4
|
+
* Shows the current status of a KB-First project.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
import { existsSync, readFileSync, readdirSync, statSync } from 'fs';
|
|
9
|
+
import { join } from 'path';
|
|
10
|
+
import { globSync } from 'glob';
|
|
11
|
+
|
|
12
|
+
const PHASES = [
|
|
13
|
+
{ num: 0, name: 'Assessment', subphases: 5 },
|
|
14
|
+
{ num: 1, name: 'KB Design', subphases: 5 },
|
|
15
|
+
{ num: 1.5, name: 'Hooks Setup', subphases: 4 },
|
|
16
|
+
{ num: 2, name: 'Schema Definition', subphases: 4 },
|
|
17
|
+
{ num: 3, name: 'KB Population', subphases: 5 },
|
|
18
|
+
{ num: 4, name: 'Scoring & Gaps', subphases: 5 },
|
|
19
|
+
{ num: 5, name: 'Integration', subphases: 4 },
|
|
20
|
+
{ num: 6, name: 'Testing', subphases: 5 },
|
|
21
|
+
{ num: 7, name: 'Optimization', subphases: 4 },
|
|
22
|
+
{ num: 8, name: 'Verification', subphases: 8 },
|
|
23
|
+
{ num: 9, name: 'Security', subphases: 6 },
|
|
24
|
+
{ num: 10, name: 'Documentation', subphases: 6 },
|
|
25
|
+
{ num: 11, name: 'Deployment', subphases: 6 }
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
export async function statusCommand(options) {
|
|
29
|
+
const cwd = process.cwd();
|
|
30
|
+
const projectName = cwd.split('/').pop();
|
|
31
|
+
|
|
32
|
+
console.log('');
|
|
33
|
+
console.log(chalk.cyan('╔═══════════════════════════════════════════════════════════════╗'));
|
|
34
|
+
console.log(chalk.cyan('║') + chalk.bold.white(' KB-First Project Status ') + chalk.cyan('║'));
|
|
35
|
+
console.log(chalk.cyan('╚═══════════════════════════════════════════════════════════════╝'));
|
|
36
|
+
console.log('');
|
|
37
|
+
|
|
38
|
+
// Check if initialized
|
|
39
|
+
const configPath = join(cwd, '.ruvector', 'config.json');
|
|
40
|
+
if (!existsSync(configPath)) {
|
|
41
|
+
console.log(chalk.red(' Not a KB-First project.'));
|
|
42
|
+
console.log('');
|
|
43
|
+
console.log(chalk.gray(' Initialize with: kb-first init'));
|
|
44
|
+
console.log('');
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const config = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
49
|
+
|
|
50
|
+
// Project Info
|
|
51
|
+
console.log(chalk.white(' Project Information'));
|
|
52
|
+
console.log(chalk.gray(' ─────────────────────────────────────────────────────────────'));
|
|
53
|
+
console.log(` Name: ${chalk.cyan(projectName)}`);
|
|
54
|
+
console.log(` Namespace: ${chalk.cyan(config.kbFirst?.namespace || 'not set')}`);
|
|
55
|
+
console.log(` Version: ${chalk.cyan(config.kbFirst?.version || '5.0.0')}`);
|
|
56
|
+
console.log(` Initialized: ${chalk.gray(config.kbFirst?.initialized || 'unknown')}`);
|
|
57
|
+
console.log('');
|
|
58
|
+
|
|
59
|
+
// Phase Progress
|
|
60
|
+
const currentPhase = config.phases?.current || 0;
|
|
61
|
+
const completedPhases = config.phases?.completed || [];
|
|
62
|
+
|
|
63
|
+
console.log(chalk.white(' Phase Progress'));
|
|
64
|
+
console.log(chalk.gray(' ─────────────────────────────────────────────────────────────'));
|
|
65
|
+
|
|
66
|
+
for (const phase of PHASES) {
|
|
67
|
+
const isCompleted = completedPhases.includes(phase.num);
|
|
68
|
+
const isCurrent = phase.num === currentPhase;
|
|
69
|
+
|
|
70
|
+
let status;
|
|
71
|
+
if (isCompleted) {
|
|
72
|
+
status = chalk.green('✓ Complete');
|
|
73
|
+
} else if (isCurrent) {
|
|
74
|
+
status = chalk.yellow('▶ Current');
|
|
75
|
+
} else if (phase.num < currentPhase) {
|
|
76
|
+
status = chalk.yellow('○ Partial');
|
|
77
|
+
} else {
|
|
78
|
+
status = chalk.gray('○ Pending');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const phaseNum = phase.num.toString().padStart(4, ' ');
|
|
82
|
+
console.log(` ${phaseNum}. ${phase.name.padEnd(20)} ${status}`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
console.log('');
|
|
86
|
+
|
|
87
|
+
// Quick Stats
|
|
88
|
+
console.log(chalk.white(' Quick Stats'));
|
|
89
|
+
console.log(chalk.gray(' ─────────────────────────────────────────────────────────────'));
|
|
90
|
+
|
|
91
|
+
// Count source files
|
|
92
|
+
const srcDir = join(cwd, 'src');
|
|
93
|
+
let codeFiles = 0;
|
|
94
|
+
let filesWithKB = 0;
|
|
95
|
+
|
|
96
|
+
if (existsSync(srcDir)) {
|
|
97
|
+
const files = globSync('**/*.{ts,tsx,js,jsx,py}', { cwd: srcDir });
|
|
98
|
+
codeFiles = files.length;
|
|
99
|
+
|
|
100
|
+
for (const file of files) {
|
|
101
|
+
const content = readFileSync(join(srcDir, file), 'utf-8');
|
|
102
|
+
if (content.includes('KB-Generated:') || content.includes('Sources:')) {
|
|
103
|
+
filesWithKB++;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
console.log(` Code Files: ${codeFiles}`);
|
|
109
|
+
console.log(` With KB Cite: ${filesWithKB} (${codeFiles > 0 ? Math.round((filesWithKB / codeFiles) * 100) : 0}%)`);
|
|
110
|
+
|
|
111
|
+
// Count KB gaps
|
|
112
|
+
const gapsPath = join(cwd, '.ruvector', 'gaps.jsonl');
|
|
113
|
+
let gapCount = 0;
|
|
114
|
+
if (existsSync(gapsPath)) {
|
|
115
|
+
const content = readFileSync(gapsPath, 'utf-8').trim();
|
|
116
|
+
gapCount = content ? content.split('\n').length : 0;
|
|
117
|
+
}
|
|
118
|
+
console.log(` KB Gaps: ${gapCount}`);
|
|
119
|
+
|
|
120
|
+
// Hooks status
|
|
121
|
+
const hooksEnabled = config.hooks?.enabled ? chalk.green('Enabled') : chalk.red('Disabled');
|
|
122
|
+
console.log(` Hooks: ${hooksEnabled}`);
|
|
123
|
+
|
|
124
|
+
console.log('');
|
|
125
|
+
|
|
126
|
+
// Detailed view
|
|
127
|
+
if (options.detailed) {
|
|
128
|
+
await showDetailedStatus(cwd, config);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Next Steps
|
|
132
|
+
console.log(chalk.white(' Next Steps'));
|
|
133
|
+
console.log(chalk.gray(' ─────────────────────────────────────────────────────────────'));
|
|
134
|
+
|
|
135
|
+
if (currentPhase < 11) {
|
|
136
|
+
console.log(chalk.cyan(` → Run: kb-first phase ${currentPhase}`));
|
|
137
|
+
console.log(chalk.gray(` Complete Phase ${currentPhase}: ${PHASES.find(p => p.num === currentPhase)?.name}`));
|
|
138
|
+
} else {
|
|
139
|
+
console.log(chalk.green(' → All phases complete!'));
|
|
140
|
+
console.log(chalk.gray(' Run: kb-first score --detailed'));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
console.log('');
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async function showDetailedStatus(cwd, config) {
|
|
147
|
+
console.log(chalk.white(' Detailed Status'));
|
|
148
|
+
console.log(chalk.gray(' ─────────────────────────────────────────────────────────────'));
|
|
149
|
+
|
|
150
|
+
// Directory structure
|
|
151
|
+
const expectedDirs = [
|
|
152
|
+
'.ruvector',
|
|
153
|
+
'.ruvector/hooks',
|
|
154
|
+
'src',
|
|
155
|
+
'src/kb',
|
|
156
|
+
'phases',
|
|
157
|
+
'scripts',
|
|
158
|
+
'docs'
|
|
159
|
+
];
|
|
160
|
+
|
|
161
|
+
console.log('');
|
|
162
|
+
console.log(chalk.gray(' Directory Structure:'));
|
|
163
|
+
for (const dir of expectedDirs) {
|
|
164
|
+
const exists = existsSync(join(cwd, dir));
|
|
165
|
+
const icon = exists ? chalk.green('✓') : chalk.red('✗');
|
|
166
|
+
console.log(` ${icon} ${dir}/`);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Scripts available
|
|
170
|
+
const scriptsDir = join(cwd, 'scripts');
|
|
171
|
+
if (existsSync(scriptsDir)) {
|
|
172
|
+
const scripts = readdirSync(scriptsDir).filter(f => f.endsWith('.sh'));
|
|
173
|
+
console.log('');
|
|
174
|
+
console.log(chalk.gray(` Verification Scripts: ${scripts.length}`));
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Phase documentation
|
|
178
|
+
const phasesDir = join(cwd, 'phases');
|
|
179
|
+
if (existsSync(phasesDir)) {
|
|
180
|
+
const phaseDocs = readdirSync(phasesDir).filter(f => f.endsWith('.md'));
|
|
181
|
+
console.log(chalk.gray(` Phase Documents: ${phaseDocs.length}`));
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Sessions
|
|
185
|
+
const sessionsPath = join(cwd, '.ruvector', 'sessions.jsonl');
|
|
186
|
+
if (existsSync(sessionsPath)) {
|
|
187
|
+
const content = readFileSync(sessionsPath, 'utf-8').trim();
|
|
188
|
+
const sessionCount = content ? content.split('\n').length : 0;
|
|
189
|
+
console.log(chalk.gray(` Sessions Logged: ${sessionCount}`));
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
console.log('');
|
|
193
|
+
}
|