ruvnet-kb-first 5.0.0 → 6.1.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.
Files changed (4) hide show
  1. package/README.md +41 -23
  2. package/SKILL.md +153 -75
  3. package/package.json +1 -1
  4. package/src/mcp-server.js +902 -250
package/src/mcp-server.js CHANGED
@@ -1,412 +1,1064 @@
1
1
  /**
2
- * RuvNet KB-First MCP Server
2
+ * RuvNet KB-First MCP Server - Granular Score-Driven Architecture
3
+ * Version 6.1.0
3
4
  *
4
- * Model Context Protocol server for Claude Code integration.
5
- * Provides KB-First tools directly within Claude Code.
5
+ * PHILOSOPHY: Granular scoring drives discipline.
6
+ * - Score each KB dimension 1-100 (completeness, depth, comprehensiveness, accuracy, freshness)
7
+ * - Score each phase readiness 1-100
8
+ * - Generate enhancement plan based on gaps
9
+ * - User confirms before execution
10
+ * - Post-verify: did we hit predicted scores?
11
+ *
12
+ * 5 Tools:
13
+ * 1. kb_first_assess - Score ALL dimensions (KB quality + phase readiness)
14
+ * 2. kb_first_plan - Generate enhancement plan with predicted improvements
15
+ * 3. kb_first_confirm - User confirms readiness, locks in plan
16
+ * 4. kb_first_execute - Execute plan phase by phase
17
+ * 5. kb_first_verify - Post-verification: predicted vs actual, identify gaps
6
18
  *
7
19
  * Usage:
8
20
  * npx ruvnet-kb-first mcp
9
- * node src/mcp-server.js
10
21
  */
11
22
 
12
- import { createServer } from 'http';
13
- import { existsSync, readFileSync, writeFileSync } from 'fs';
23
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, statSync } from 'fs';
14
24
  import { join } from 'path';
25
+ import { globSync } from 'glob';
15
26
 
16
- // MCP Protocol Constants
17
27
  const MCP_VERSION = '0.1.0';
18
28
  const SERVER_NAME = 'ruvnet-kb-first';
19
- const SERVER_VERSION = '5.0.0';
29
+ const SERVER_VERSION = '6.1.0';
30
+
31
+ /**
32
+ * KB Quality Dimensions (each scored 1-100)
33
+ */
34
+ const KB_DIMENSIONS = {
35
+ completeness: {
36
+ name: 'Completeness',
37
+ description: 'Does the KB cover all necessary domain topics?',
38
+ weight: 20
39
+ },
40
+ depth: {
41
+ name: 'Depth',
42
+ description: 'Is each topic covered with sufficient detail?',
43
+ weight: 20
44
+ },
45
+ comprehensiveness: {
46
+ name: 'Comprehensiveness',
47
+ description: 'Are edge cases, exceptions, and nuances included?',
48
+ weight: 20
49
+ },
50
+ accuracy: {
51
+ name: 'Accuracy',
52
+ description: 'Is the information correct and up-to-date?',
53
+ weight: 20
54
+ },
55
+ freshness: {
56
+ name: 'Freshness',
57
+ description: 'How recently was the KB updated?',
58
+ weight: 10
59
+ },
60
+ attribution: {
61
+ name: 'Attribution',
62
+ description: 'Are sources and experts properly cited?',
63
+ weight: 10
64
+ }
65
+ };
66
+
67
+ /**
68
+ * Phase Definitions with readiness criteria
69
+ */
70
+ const PHASES = {
71
+ 0: {
72
+ name: 'Assessment',
73
+ criteria: ['Project scope documented', 'Domain complexity identified', 'KB-First suitability confirmed', 'Resources estimated']
74
+ },
75
+ 1: {
76
+ name: 'KB Design',
77
+ criteria: ['Domain concepts mapped', 'Taxonomy designed', 'Relationships defined', 'Query patterns planned']
78
+ },
79
+ 1.5: {
80
+ name: 'Hooks Setup',
81
+ criteria: ['Hooks installed', 'Configuration complete', 'Patterns trained', 'Verification passing']
82
+ },
83
+ 2: {
84
+ name: 'Schema Definition',
85
+ criteria: ['Tables created', 'Vector columns added', 'Indexes designed', 'Migrations written']
86
+ },
87
+ 3: {
88
+ name: 'KB Population',
89
+ criteria: ['Content collected', 'Data cleaned', 'Embeddings generated', 'Import validated']
90
+ },
91
+ 4: {
92
+ name: 'Scoring & Gaps',
93
+ criteria: ['Coverage analyzed', 'Quality scored', 'Gaps identified', 'Remediation planned']
94
+ },
95
+ 5: {
96
+ name: 'Integration',
97
+ criteria: ['Search API built', 'Code generation working', 'Citation system active', 'Gap logging enabled']
98
+ },
99
+ 6: {
100
+ name: 'Testing',
101
+ criteria: ['Unit tests written', 'Integration tests passing', 'Accuracy validated', 'Edge cases covered']
102
+ },
103
+ 7: {
104
+ name: 'Optimization',
105
+ criteria: ['Queries optimized', 'Indexes tuned', 'Caching implemented', 'Benchmarks passing']
106
+ },
107
+ 8: {
108
+ name: 'Verification',
109
+ criteria: ['Code scan clean', 'Imports verified', 'Sources return', 'Startup working', 'Fallbacks tested', 'Attribution valid', 'Confidence scores present', 'Gap logging active']
110
+ },
111
+ 9: {
112
+ name: 'Security',
113
+ criteria: ['Dependencies audited', 'OWASP checked', 'SQL injection tested', 'Auth reviewed', 'Secrets secured', 'APIs protected']
114
+ },
115
+ 10: {
116
+ name: 'Documentation',
117
+ criteria: ['README complete', 'API documented', 'Schema documented', 'Architecture documented', 'Operator guide written']
118
+ },
119
+ 11: {
120
+ name: 'Deployment',
121
+ criteria: ['Infrastructure ready', 'Environments configured', 'CI/CD built', 'Migrations run', 'Monitoring active', 'Go-live complete']
122
+ }
123
+ };
20
124
 
21
125
  /**
22
- * Available MCP Tools
126
+ * MCP Tools
23
127
  */
24
128
  const TOOLS = [
25
129
  {
26
- name: 'kb_first_init',
27
- description: 'Initialize KB-First structure in the current project',
130
+ name: 'kb_first_assess',
131
+ description: `Score ALL dimensions of KB quality and phase readiness (each 1-100).
132
+
133
+ KB Quality Dimensions:
134
+ - Completeness: Does KB cover all domain topics?
135
+ - Depth: Is each topic detailed enough?
136
+ - Comprehensiveness: Are edge cases included?
137
+ - Accuracy: Is information correct?
138
+ - Freshness: How recently updated?
139
+ - Attribution: Are sources cited?
140
+
141
+ Phase Readiness:
142
+ - Each of 12 phases scored 1-100 based on criteria completion
143
+
144
+ Returns granular scores that reveal exactly where gaps exist.
145
+ This is your BASELINE for planning.`,
28
146
  inputSchema: {
29
147
  type: 'object',
30
148
  properties: {
31
- force: {
32
- type: 'boolean',
33
- description: 'Overwrite existing configuration'
34
- },
35
- template: {
36
- type: 'string',
37
- enum: ['basic', 'api', 'fullstack'],
38
- description: 'Project template type'
39
- }
149
+ projectPath: { type: 'string', description: 'Path to project (default: current directory)' }
40
150
  }
41
151
  }
42
152
  },
43
153
  {
44
- name: 'kb_first_score',
45
- description: 'Calculate KB-First compliance score for the project',
154
+ name: 'kb_first_plan',
155
+ description: `Generate enhancement plan based on assessment scores.
156
+
157
+ Analyzes gaps (scores below threshold) and creates:
158
+ - Prioritized list of enhancements
159
+ - Predicted score improvements for each
160
+ - Estimated effort
161
+ - Execution order
162
+
163
+ The plan gives you a concrete game plan so you don't lose the thread.
164
+ Returns the plan for user review before execution.`,
46
165
  inputSchema: {
47
166
  type: 'object',
48
167
  properties: {
49
- detailed: {
50
- type: 'boolean',
51
- description: 'Show detailed breakdown'
52
- }
168
+ threshold: { type: 'number', description: 'Minimum acceptable score (default: 80)', default: 80 },
169
+ focusArea: { type: 'string', enum: ['kb', 'phases', 'all'], description: 'What to focus on', default: 'all' }
53
170
  }
54
171
  }
55
172
  },
56
173
  {
57
- name: 'kb_first_verify',
58
- description: 'Run KB-First verification checks',
174
+ name: 'kb_first_confirm',
175
+ description: `User confirms readiness to execute enhancement plan.
176
+
177
+ Shows the plan summary and asks for confirmation.
178
+ Once confirmed, the plan is locked and execution can begin.
179
+
180
+ This ensures user consent before making changes.`,
59
181
  inputSchema: {
60
182
  type: 'object',
61
183
  properties: {
62
- phase: {
63
- type: 'number',
64
- description: 'Verify specific phase (0-11)'
65
- },
66
- all: {
67
- type: 'boolean',
68
- description: 'Run all verification scripts'
69
- }
70
- }
184
+ confirmed: { type: 'boolean', description: 'User confirms readiness to proceed' }
185
+ },
186
+ required: ['confirmed']
71
187
  }
72
188
  },
73
189
  {
74
- name: 'kb_first_status',
75
- description: 'Show KB-First project status',
190
+ name: 'kb_first_execute',
191
+ description: `Execute the confirmed enhancement plan.
192
+
193
+ Works through the plan systematically:
194
+ - Shows current task
195
+ - Provides guidance for completion
196
+ - Tracks progress
197
+ - Updates predicted scores
198
+
199
+ Call repeatedly to work through each enhancement.`,
76
200
  inputSchema: {
77
201
  type: 'object',
78
202
  properties: {
79
- detailed: {
80
- type: 'boolean',
81
- description: 'Show detailed status'
82
- }
203
+ taskComplete: { type: 'boolean', description: 'Mark current task as complete' }
83
204
  }
84
205
  }
85
206
  },
86
207
  {
87
- name: 'kb_first_phase',
88
- description: 'Get information about a specific build phase',
89
- inputSchema: {
90
- type: 'object',
91
- properties: {
92
- phase: {
93
- type: 'number',
94
- description: 'Phase number (0, 1, 1.5, 2-11)'
95
- }
96
- },
97
- required: ['phase']
98
- }
99
- },
100
- {
101
- name: 'kb_first_hooks',
102
- description: 'Manage KB-First hooks',
208
+ name: 'kb_first_verify',
209
+ description: `Post-verification: Compare predicted vs actual scores.
210
+
211
+ Re-scores everything and compares to predictions:
212
+ - Which improvements were achieved?
213
+ - Which fell short?
214
+ - What gaps remain?
215
+ - What's the next priority?
216
+
217
+ This closes the loop and ensures you delivered what you promised.`,
103
218
  inputSchema: {
104
219
  type: 'object',
105
220
  properties: {
106
- action: {
107
- type: 'string',
108
- enum: ['install', 'verify', 'train', 'status'],
109
- description: 'Hook action to perform'
110
- }
111
- },
112
- required: ['action']
221
+ detailed: { type: 'boolean', description: 'Show detailed comparison', default: true }
222
+ }
113
223
  }
114
224
  }
115
225
  ];
116
226
 
117
227
  /**
118
- * Handle MCP tool calls
228
+ * Score KB Quality Dimensions (1-100 each)
119
229
  */
120
- async function handleToolCall(toolName, args) {
121
- const cwd = process.cwd();
230
+ function scoreKBDimensions(cwd) {
231
+ const scores = {};
232
+ const kbDir = join(cwd, 'src', 'kb');
233
+ const docsDir = join(cwd, 'docs');
234
+ const ruvectorDir = join(cwd, '.ruvector');
122
235
 
123
- switch (toolName) {
124
- case 'kb_first_init':
125
- return await handleInit(cwd, args);
236
+ // Count KB entries and docs
237
+ let kbEntries = 0;
238
+ let docFiles = 0;
239
+ let totalContent = 0;
126
240
 
127
- case 'kb_first_score':
128
- return await handleScore(cwd, args);
241
+ if (existsSync(kbDir)) {
242
+ try {
243
+ const files = readdirSync(kbDir);
244
+ kbEntries = files.length;
245
+ for (const f of files) {
246
+ try {
247
+ const content = readFileSync(join(kbDir, f), 'utf-8');
248
+ totalContent += content.length;
249
+ } catch {}
250
+ }
251
+ } catch {}
252
+ }
129
253
 
130
- case 'kb_first_verify':
131
- return await handleVerify(cwd, args);
254
+ if (existsSync(docsDir)) {
255
+ try {
256
+ docFiles = readdirSync(docsDir).filter(f => f.endsWith('.md')).length;
257
+ } catch {}
258
+ }
132
259
 
133
- case 'kb_first_status':
134
- return await handleStatus(cwd, args);
260
+ // Completeness: Based on number of KB entries and docs
261
+ // 0 entries = 0, 5 entries = 50, 10+ entries = 100
262
+ scores.completeness = {
263
+ score: Math.min(100, Math.max(0, kbEntries * 10 + docFiles * 10)),
264
+ reason: `${kbEntries} KB entries, ${docFiles} doc files`,
265
+ improvement: kbEntries < 10 ? `Add ${10 - kbEntries} more KB entries` : 'Adequate coverage'
266
+ };
135
267
 
136
- case 'kb_first_phase':
137
- return await handlePhase(cwd, args);
268
+ // Depth: Based on average content length
269
+ // < 500 chars avg = shallow, > 2000 = deep
270
+ const avgLength = kbEntries > 0 ? totalContent / kbEntries : 0;
271
+ scores.depth = {
272
+ score: Math.min(100, Math.max(0, Math.round(avgLength / 20))),
273
+ reason: `Average entry length: ${Math.round(avgLength)} chars`,
274
+ improvement: avgLength < 2000 ? 'Add more detail to KB entries' : 'Good depth'
275
+ };
138
276
 
139
- case 'kb_first_hooks':
140
- return await handleHooks(cwd, args);
277
+ // Comprehensiveness: Check for edge case documentation
278
+ let edgeCaseScore = 0;
279
+ const srcDir = join(cwd, 'src');
280
+ if (existsSync(srcDir)) {
281
+ try {
282
+ const files = globSync('**/*.{ts,tsx,js,jsx,py}', { cwd: srcDir });
283
+ for (const f of files) {
284
+ try {
285
+ const content = readFileSync(join(srcDir, f), 'utf-8');
286
+ if (content.includes('edge case') || content.includes('exception') || content.includes('fallback')) {
287
+ edgeCaseScore += 10;
288
+ }
289
+ } catch {}
290
+ }
291
+ } catch {}
292
+ }
293
+ scores.comprehensiveness = {
294
+ score: Math.min(100, edgeCaseScore + (kbEntries * 5)),
295
+ reason: `Edge case handling detected in ${Math.floor(edgeCaseScore / 10)} files`,
296
+ improvement: edgeCaseScore < 50 ? 'Document edge cases and exceptions' : 'Good coverage'
297
+ };
141
298
 
142
- default:
143
- return {
144
- error: `Unknown tool: ${toolName}`
145
- };
299
+ // Accuracy: Based on presence of verification/testing
300
+ let accuracyScore = 50; // Base score
301
+ if (existsSync(join(cwd, 'tests')) || existsSync(join(cwd, '__tests__'))) accuracyScore += 25;
302
+ if (existsSync(join(cwd, '.ruvector', 'config.json'))) accuracyScore += 15;
303
+ if (existsSync(join(cwd, 'CHANGELOG.md'))) accuracyScore += 10;
304
+ scores.accuracy = {
305
+ score: Math.min(100, accuracyScore),
306
+ reason: accuracyScore > 75 ? 'Tests and verification present' : 'Limited verification',
307
+ improvement: accuracyScore < 80 ? 'Add tests and validation' : 'Good accuracy controls'
308
+ };
309
+
310
+ // Freshness: Based on last modification
311
+ let freshnessScore = 0;
312
+ if (existsSync(ruvectorDir)) {
313
+ try {
314
+ const stat = statSync(ruvectorDir);
315
+ const daysSince = (Date.now() - stat.mtime.getTime()) / (1000 * 60 * 60 * 24);
316
+ if (daysSince < 1) freshnessScore = 100;
317
+ else if (daysSince < 7) freshnessScore = 80;
318
+ else if (daysSince < 30) freshnessScore = 50;
319
+ else if (daysSince < 90) freshnessScore = 25;
320
+ else freshnessScore = 10;
321
+ } catch {}
146
322
  }
147
- }
323
+ scores.freshness = {
324
+ score: freshnessScore,
325
+ reason: freshnessScore > 50 ? 'Recently updated' : 'Stale - needs refresh',
326
+ improvement: freshnessScore < 80 ? 'Update KB content' : 'Fresh'
327
+ };
328
+
329
+ // Attribution: Check for citations in code
330
+ let attributionScore = 0;
331
+ if (existsSync(srcDir)) {
332
+ try {
333
+ const files = globSync('**/*.{ts,tsx,js,jsx,py}', { cwd: srcDir });
334
+ let filesWithCitation = 0;
335
+ for (const f of files) {
336
+ try {
337
+ const content = readFileSync(join(srcDir, f), 'utf-8');
338
+ if (content.includes('KB-Generated:') || content.includes('Sources:') || content.includes('@kb-source')) {
339
+ filesWithCitation++;
340
+ }
341
+ } catch {}
342
+ }
343
+ attributionScore = files.length > 0 ? Math.round((filesWithCitation / files.length) * 100) : 100;
344
+ } catch {}
345
+ } else {
346
+ attributionScore = 100; // No code = not applicable
347
+ }
348
+ scores.attribution = {
349
+ score: attributionScore,
350
+ reason: `${attributionScore}% of code files have KB citations`,
351
+ improvement: attributionScore < 80 ? 'Add KB citations to code files' : 'Good attribution'
352
+ };
148
353
 
149
- async function handleInit(cwd, args) {
150
- const { initCommand } = await import('./commands/init.js');
354
+ return scores;
355
+ }
151
356
 
152
- // Run init (capture output)
357
+ /**
358
+ * Score Phase Readiness (1-100 each)
359
+ */
360
+ function scorePhaseReadiness(cwd) {
361
+ const scores = {};
153
362
  const configPath = join(cwd, '.ruvector', 'config.json');
154
- const wasInitialized = existsSync(configPath);
363
+ let config = { phases: { completed: [], gates: {} } };
155
364
 
156
- try {
157
- await initCommand({
158
- force: args.force || false,
159
- template: args.template || 'basic',
160
- hooks: true
161
- });
365
+ if (existsSync(configPath)) {
366
+ try {
367
+ config = JSON.parse(readFileSync(configPath, 'utf-8'));
368
+ } catch {}
369
+ }
162
370
 
163
- return {
164
- success: true,
165
- message: wasInitialized && !args.force
166
- ? 'Project already initialized'
167
- : 'KB-First project initialized successfully',
168
- configPath
169
- };
170
- } catch (error) {
171
- return {
172
- success: false,
173
- error: error.message
371
+ const completed = config.phases?.completed || [];
372
+ const gates = config.phases?.gates || {};
373
+
374
+ for (const [phaseNum, phaseInfo] of Object.entries(PHASES)) {
375
+ const num = parseFloat(phaseNum);
376
+ const isCompleted = completed.includes(num);
377
+ const criteriaCount = phaseInfo.criteria.length;
378
+
379
+ // Check which criteria are met
380
+ let metCriteria = 0;
381
+ const unmetCriteria = [];
382
+
383
+ for (const criterion of phaseInfo.criteria) {
384
+ // Simplified check - in real implementation, this would be more sophisticated
385
+ if (isCompleted || checkCriterion(cwd, num, criterion)) {
386
+ metCriteria++;
387
+ } else {
388
+ unmetCriteria.push(criterion);
389
+ }
390
+ }
391
+
392
+ const score = Math.round((metCriteria / criteriaCount) * 100);
393
+
394
+ scores[phaseNum] = {
395
+ name: phaseInfo.name,
396
+ score,
397
+ metCriteria,
398
+ totalCriteria: criteriaCount,
399
+ unmet: unmetCriteria,
400
+ completed: isCompleted
174
401
  };
175
402
  }
403
+
404
+ return scores;
176
405
  }
177
406
 
178
- async function handleScore(cwd, args) {
179
- const configPath = join(cwd, '.ruvector', 'config.json');
407
+ /**
408
+ * Check if a criterion is met (simplified)
409
+ */
410
+ function checkCriterion(cwd, phase, criterion) {
411
+ // Check for common indicators
412
+ const criterionLower = criterion.toLowerCase();
180
413
 
181
- if (!existsSync(configPath)) {
182
- return {
183
- success: false,
184
- error: 'Not a KB-First project. Run kb_first_init first.'
414
+ if (criterionLower.includes('documented')) {
415
+ return existsSync(join(cwd, 'docs')) || existsSync(join(cwd, 'README.md'));
416
+ }
417
+ if (criterionLower.includes('tests')) {
418
+ return existsSync(join(cwd, 'tests')) || existsSync(join(cwd, '__tests__'));
419
+ }
420
+ if (criterionLower.includes('config')) {
421
+ return existsSync(join(cwd, '.ruvector', 'config.json'));
422
+ }
423
+ if (criterionLower.includes('hooks')) {
424
+ return existsSync(join(cwd, '.ruvector', 'hooks'));
425
+ }
426
+ if (criterionLower.includes('schema') || criterionLower.includes('tables')) {
427
+ return existsSync(join(cwd, 'templates', 'schema.sql'));
428
+ }
429
+
430
+ return false;
431
+ }
432
+
433
+ /**
434
+ * Calculate overall weighted scores
435
+ */
436
+ function calculateOverallScores(kbScores, phaseScores) {
437
+ // KB Overall (weighted average)
438
+ let kbTotal = 0;
439
+ let kbWeightTotal = 0;
440
+ for (const [dim, info] of Object.entries(KB_DIMENSIONS)) {
441
+ if (kbScores[dim]) {
442
+ kbTotal += kbScores[dim].score * info.weight;
443
+ kbWeightTotal += info.weight;
444
+ }
445
+ }
446
+ const kbOverall = kbWeightTotal > 0 ? Math.round(kbTotal / kbWeightTotal) : 0;
447
+
448
+ // Phase Overall (average)
449
+ const phaseValues = Object.values(phaseScores);
450
+ const phaseOverall = phaseValues.length > 0
451
+ ? Math.round(phaseValues.reduce((sum, p) => sum + p.score, 0) / phaseValues.length)
452
+ : 0;
453
+
454
+ // Combined Overall
455
+ const overall = Math.round((kbOverall * 0.5) + (phaseOverall * 0.5));
456
+
457
+ return { kbOverall, phaseOverall, overall };
458
+ }
459
+
460
+ /**
461
+ * Tool Handlers
462
+ */
463
+ async function handleAssess(cwd, args) {
464
+ const kbScores = scoreKBDimensions(cwd);
465
+ const phaseScores = scorePhaseReadiness(cwd);
466
+ const overall = calculateOverallScores(kbScores, phaseScores);
467
+
468
+ // Save assessment
469
+ const ruvectorDir = join(cwd, '.ruvector');
470
+ if (!existsSync(ruvectorDir)) {
471
+ mkdirSync(ruvectorDir, { recursive: true });
472
+ }
473
+
474
+ const assessment = {
475
+ timestamp: new Date().toISOString(),
476
+ kb: kbScores,
477
+ phases: phaseScores,
478
+ overall
479
+ };
480
+
481
+ writeFileSync(join(ruvectorDir, 'assessment.json'), JSON.stringify(assessment, null, 2));
482
+
483
+ // Format for display
484
+ const kbSummary = {};
485
+ for (const [dim, data] of Object.entries(kbScores)) {
486
+ kbSummary[dim] = {
487
+ score: data.score,
488
+ reason: data.reason
185
489
  };
186
490
  }
187
491
 
188
- // Import and run score calculation
189
- const { scoreCommand } = await import('./commands/score.js');
492
+ const phaseSummary = {};
493
+ for (const [num, data] of Object.entries(phaseScores)) {
494
+ phaseSummary[`Phase ${num}: ${data.name}`] = {
495
+ score: data.score,
496
+ criteria: `${data.metCriteria}/${data.totalCriteria}`,
497
+ status: data.completed ? 'COMPLETE' : (data.score >= 80 ? 'READY' : 'GAPS')
498
+ };
499
+ }
190
500
 
191
- // Return score data
192
501
  return {
193
- success: true,
194
- message: 'Score calculated. Run `kb-first score --detailed` for full breakdown.',
195
- hint: 'Use CLI for detailed output'
502
+ action: 'ASSESSMENT_COMPLETE',
503
+ timestamp: assessment.timestamp,
504
+ overallScores: {
505
+ kb: `${overall.kbOverall}/100`,
506
+ phases: `${overall.phaseOverall}/100`,
507
+ combined: `${overall.overall}/100`
508
+ },
509
+ kbQuality: kbSummary,
510
+ phaseReadiness: phaseSummary,
511
+ nextStep: 'Run kb_first_plan to generate enhancement plan based on gaps'
196
512
  };
197
513
  }
198
514
 
199
- async function handleVerify(cwd, args) {
200
- const configPath = join(cwd, '.ruvector', 'config.json');
515
+ async function handlePlan(cwd, args) {
516
+ const assessmentPath = join(cwd, '.ruvector', 'assessment.json');
201
517
 
202
- if (!existsSync(configPath)) {
518
+ if (!existsSync(assessmentPath)) {
203
519
  return {
204
- success: false,
205
- error: 'Not a KB-First project. Run kb_first_init first.'
520
+ error: 'NO_ASSESSMENT',
521
+ message: 'No assessment found. Run kb_first_assess first.',
522
+ action: 'Run kb_first_assess to score KB and phase readiness'
206
523
  };
207
524
  }
208
525
 
209
- const phase = args.phase;
210
- const runAll = args.all;
526
+ const assessment = JSON.parse(readFileSync(assessmentPath, 'utf-8'));
527
+ const threshold = args.threshold || 80;
528
+ const focusArea = args.focusArea || 'all';
529
+
530
+ const enhancements = [];
531
+ let taskId = 1;
532
+
533
+ // Find KB gaps
534
+ if (focusArea === 'all' || focusArea === 'kb') {
535
+ for (const [dim, data] of Object.entries(assessment.kb)) {
536
+ if (data.score < threshold) {
537
+ const gap = threshold - data.score;
538
+ enhancements.push({
539
+ id: taskId++,
540
+ area: 'KB Quality',
541
+ dimension: KB_DIMENSIONS[dim]?.name || dim,
542
+ currentScore: data.score,
543
+ targetScore: threshold,
544
+ predictedImprovement: gap,
545
+ task: data.improvement,
546
+ priority: gap > 30 ? 'HIGH' : (gap > 15 ? 'MEDIUM' : 'LOW'),
547
+ effort: gap > 30 ? 'Large' : (gap > 15 ? 'Medium' : 'Small')
548
+ });
549
+ }
550
+ }
551
+ }
552
+
553
+ // Find Phase gaps
554
+ if (focusArea === 'all' || focusArea === 'phases') {
555
+ for (const [num, data] of Object.entries(assessment.phases)) {
556
+ if (data.score < threshold && !data.completed) {
557
+ const gap = threshold - data.score;
558
+ enhancements.push({
559
+ id: taskId++,
560
+ area: 'Phase Readiness',
561
+ dimension: `Phase ${num}: ${data.name}`,
562
+ currentScore: data.score,
563
+ targetScore: threshold,
564
+ predictedImprovement: gap,
565
+ task: `Complete: ${data.unmet.slice(0, 3).join(', ')}${data.unmet.length > 3 ? '...' : ''}`,
566
+ priority: gap > 30 ? 'HIGH' : (gap > 15 ? 'MEDIUM' : 'LOW'),
567
+ effort: gap > 30 ? 'Large' : (gap > 15 ? 'Medium' : 'Small')
568
+ });
569
+ }
570
+ }
571
+ }
572
+
573
+ // Sort by priority
574
+ const priorityOrder = { HIGH: 0, MEDIUM: 1, LOW: 2 };
575
+ enhancements.sort((a, b) => priorityOrder[a.priority] - priorityOrder[b.priority]);
576
+
577
+ // Calculate predicted totals
578
+ const predictedKBImprovement = enhancements
579
+ .filter(e => e.area === 'KB Quality')
580
+ .reduce((sum, e) => sum + e.predictedImprovement, 0);
581
+
582
+ const predictedPhaseImprovement = enhancements
583
+ .filter(e => e.area === 'Phase Readiness')
584
+ .reduce((sum, e) => sum + e.predictedImprovement, 0);
585
+
586
+ const plan = {
587
+ timestamp: new Date().toISOString(),
588
+ threshold,
589
+ baselineScores: assessment.overall,
590
+ enhancements,
591
+ predictions: {
592
+ kbImprovement: `+${Math.round(predictedKBImprovement / 6)}`, // Average across 6 dimensions
593
+ phaseImprovement: `+${Math.round(predictedPhaseImprovement / Object.keys(PHASES).length)}`,
594
+ tasksCount: enhancements.length,
595
+ highPriority: enhancements.filter(e => e.priority === 'HIGH').length,
596
+ mediumPriority: enhancements.filter(e => e.priority === 'MEDIUM').length,
597
+ lowPriority: enhancements.filter(e => e.priority === 'LOW').length
598
+ },
599
+ confirmed: false,
600
+ currentTaskIndex: 0
601
+ };
602
+
603
+ writeFileSync(join(cwd, '.ruvector', 'plan.json'), JSON.stringify(plan, null, 2));
211
604
 
212
605
  return {
213
- success: true,
214
- message: phase
215
- ? `Run: kb-first verify --phase=${phase}`
216
- : runAll
217
- ? 'Run: kb-first verify --all'
218
- : 'Run: kb-first verify',
219
- hint: 'Verification scripts require terminal execution'
606
+ action: 'PLAN_GENERATED',
607
+ summary: {
608
+ totalTasks: enhancements.length,
609
+ highPriority: plan.predictions.highPriority,
610
+ mediumPriority: plan.predictions.mediumPriority,
611
+ lowPriority: plan.predictions.lowPriority
612
+ },
613
+ predictedImprovements: {
614
+ kb: plan.predictions.kbImprovement,
615
+ phases: plan.predictions.phaseImprovement
616
+ },
617
+ enhancements: enhancements.map(e => ({
618
+ id: e.id,
619
+ priority: e.priority,
620
+ area: e.dimension,
621
+ current: e.currentScore,
622
+ target: e.targetScore,
623
+ task: e.task
624
+ })),
625
+ nextStep: 'Review the plan above. Run kb_first_confirm with confirmed=true when ready to proceed.'
220
626
  };
221
627
  }
222
628
 
223
- async function handleStatus(cwd, args) {
224
- const configPath = join(cwd, '.ruvector', 'config.json');
629
+ async function handleConfirm(cwd, args) {
630
+ const planPath = join(cwd, '.ruvector', 'plan.json');
631
+
632
+ if (!existsSync(planPath)) {
633
+ return {
634
+ error: 'NO_PLAN',
635
+ message: 'No plan found. Run kb_first_plan first.',
636
+ action: 'Run kb_first_plan to generate enhancement plan'
637
+ };
638
+ }
225
639
 
226
- if (!existsSync(configPath)) {
640
+ if (!args.confirmed) {
227
641
  return {
228
- initialized: false,
229
- error: 'Not a KB-First project'
642
+ action: 'CONFIRMATION_REQUIRED',
643
+ message: 'You must confirm with confirmed=true to proceed.',
644
+ hint: 'Review the plan from kb_first_plan, then confirm when ready.'
230
645
  };
231
646
  }
232
647
 
233
- const config = JSON.parse(readFileSync(configPath, 'utf-8'));
648
+ const plan = JSON.parse(readFileSync(planPath, 'utf-8'));
649
+ plan.confirmed = true;
650
+ plan.confirmedAt = new Date().toISOString();
651
+ plan.currentTaskIndex = 0;
652
+
653
+ writeFileSync(planPath, JSON.stringify(plan, null, 2));
654
+
655
+ const firstTask = plan.enhancements[0];
234
656
 
235
657
  return {
236
- initialized: true,
237
- namespace: config.kbFirst?.namespace,
238
- version: config.kbFirst?.version,
239
- currentPhase: config.phases?.current || 0,
240
- completedPhases: config.phases?.completed || [],
241
- hooksEnabled: config.hooks?.enabled || false
658
+ action: 'PLAN_CONFIRMED',
659
+ confirmedAt: plan.confirmedAt,
660
+ totalTasks: plan.enhancements.length,
661
+ message: 'Plan locked. Ready to execute.',
662
+ firstTask: firstTask ? {
663
+ id: firstTask.id,
664
+ priority: firstTask.priority,
665
+ area: firstTask.dimension,
666
+ task: firstTask.task,
667
+ currentScore: firstTask.currentScore,
668
+ targetScore: firstTask.targetScore
669
+ } : null,
670
+ nextStep: 'Run kb_first_execute to work through the plan'
242
671
  };
243
672
  }
244
673
 
245
- async function handlePhase(cwd, args) {
246
- const PHASES = {
247
- 0: { name: 'Assessment', subphases: 5 },
248
- 1: { name: 'KB Design', subphases: 5 },
249
- 1.5: { name: 'Hooks Setup', subphases: 4 },
250
- 2: { name: 'Schema Definition', subphases: 4 },
251
- 3: { name: 'KB Population', subphases: 5 },
252
- 4: { name: 'Scoring & Gaps', subphases: 5 },
253
- 5: { name: 'Integration', subphases: 4 },
254
- 6: { name: 'Testing', subphases: 5 },
255
- 7: { name: 'Optimization', subphases: 4 },
256
- 8: { name: 'Verification', subphases: 8 },
257
- 9: { name: 'Security', subphases: 6 },
258
- 10: { name: 'Documentation', subphases: 6 },
259
- 11: { name: 'Deployment', subphases: 6 }
260
- };
674
+ async function handleExecute(cwd, args) {
675
+ const planPath = join(cwd, '.ruvector', 'plan.json');
676
+
677
+ if (!existsSync(planPath)) {
678
+ return {
679
+ error: 'NO_PLAN',
680
+ message: 'No plan found. Run kb_first_plan first.'
681
+ };
682
+ }
683
+
684
+ const plan = JSON.parse(readFileSync(planPath, 'utf-8'));
685
+
686
+ if (!plan.confirmed) {
687
+ return {
688
+ error: 'PLAN_NOT_CONFIRMED',
689
+ message: 'Plan not confirmed. Run kb_first_confirm first.',
690
+ action: 'Run kb_first_confirm with confirmed=true'
691
+ };
692
+ }
261
693
 
262
- const phase = args.phase;
263
- const phaseInfo = PHASES[phase];
694
+ // Mark current task complete if requested
695
+ if (args.taskComplete && plan.currentTaskIndex < plan.enhancements.length) {
696
+ plan.enhancements[plan.currentTaskIndex].completed = true;
697
+ plan.enhancements[plan.currentTaskIndex].completedAt = new Date().toISOString();
698
+ plan.currentTaskIndex++;
699
+ writeFileSync(planPath, JSON.stringify(plan, null, 2));
700
+ }
264
701
 
265
- if (!phaseInfo) {
702
+ // Check if all done
703
+ if (plan.currentTaskIndex >= plan.enhancements.length) {
266
704
  return {
267
- error: `Unknown phase: ${phase}`,
268
- validPhases: Object.keys(PHASES)
705
+ action: 'EXECUTION_COMPLETE',
706
+ message: 'All tasks completed!',
707
+ completedTasks: plan.enhancements.length,
708
+ nextStep: 'Run kb_first_verify to compare predicted vs actual improvements'
269
709
  };
270
710
  }
271
711
 
712
+ const currentTask = plan.enhancements[plan.currentTaskIndex];
713
+ const completedCount = plan.enhancements.filter(e => e.completed).length;
714
+
272
715
  return {
273
- phase,
274
- name: phaseInfo.name,
275
- subphases: phaseInfo.subphases,
276
- command: `kb-first phase ${phase}`
716
+ action: 'EXECUTING',
717
+ progress: {
718
+ completed: completedCount,
719
+ total: plan.enhancements.length,
720
+ percent: Math.round((completedCount / plan.enhancements.length) * 100)
721
+ },
722
+ currentTask: {
723
+ id: currentTask.id,
724
+ priority: currentTask.priority,
725
+ area: currentTask.dimension,
726
+ task: currentTask.task,
727
+ currentScore: currentTask.currentScore,
728
+ targetScore: currentTask.targetScore,
729
+ predictedImprovement: `+${currentTask.predictedImprovement}`
730
+ },
731
+ guidance: getTaskGuidance(currentTask),
732
+ nextStep: 'Complete the task above, then run kb_first_execute with taskComplete=true'
277
733
  };
278
734
  }
279
735
 
280
- async function handleHooks(cwd, args) {
281
- const action = args.action;
736
+ function getTaskGuidance(task) {
737
+ // Provide specific guidance based on task type
738
+ if (task.area === 'KB Quality') {
739
+ switch (task.dimension) {
740
+ case 'Completeness':
741
+ return 'Add more KB entries covering missing domain topics. Each entry should be in src/kb/ directory.';
742
+ case 'Depth':
743
+ return 'Expand existing KB entries with more detail. Target 2000+ characters per entry.';
744
+ case 'Comprehensiveness':
745
+ return 'Document edge cases, exceptions, and nuances in your KB entries.';
746
+ case 'Accuracy':
747
+ return 'Add tests to validate KB content. Create a tests/ directory with validation tests.';
748
+ case 'Freshness':
749
+ return 'Update KB content with latest information. Touch .ruvector/ to update timestamps.';
750
+ case 'Attribution':
751
+ return 'Add KB-Generated: headers to code files citing their KB sources.';
752
+ default:
753
+ return task.task;
754
+ }
755
+ }
756
+ return task.task;
757
+ }
282
758
 
283
- return {
284
- success: true,
285
- action,
286
- command: `kb-first hooks --${action}`,
287
- hint: 'Hook management requires terminal execution'
759
+ async function handleVerify(cwd, args) {
760
+ const planPath = join(cwd, '.ruvector', 'plan.json');
761
+ const assessmentPath = join(cwd, '.ruvector', 'assessment.json');
762
+ const TARGET_SCORE = 98; // Recursive loop until we hit 98+
763
+
764
+ if (!existsSync(planPath) || !existsSync(assessmentPath)) {
765
+ return {
766
+ error: 'MISSING_DATA',
767
+ message: 'Missing plan or assessment. Run kb_first_assess and kb_first_plan first.'
768
+ };
769
+ }
770
+
771
+ const plan = JSON.parse(readFileSync(planPath, 'utf-8'));
772
+ const originalAssessment = JSON.parse(readFileSync(assessmentPath, 'utf-8'));
773
+
774
+ // Re-assess current state
775
+ const currentKB = scoreKBDimensions(cwd);
776
+ const currentPhases = scorePhaseReadiness(cwd);
777
+ const currentOverall = calculateOverallScores(currentKB, currentPhases);
778
+
779
+ // Compare predictions vs actual
780
+ const comparison = {
781
+ kb: {},
782
+ phases: {}
288
783
  };
784
+
785
+ // Compare KB dimensions
786
+ for (const [dim, original] of Object.entries(originalAssessment.kb)) {
787
+ const current = currentKB[dim];
788
+ const enhancement = plan.enhancements.find(e => e.dimension === KB_DIMENSIONS[dim]?.name);
789
+
790
+ comparison.kb[dim] = {
791
+ before: original.score,
792
+ after: current.score,
793
+ actual: current.score - original.score,
794
+ predicted: enhancement?.predictedImprovement || 0,
795
+ hit: current.score >= TARGET_SCORE
796
+ };
797
+ }
798
+
799
+ // Compare phases
800
+ for (const [num, original] of Object.entries(originalAssessment.phases)) {
801
+ const current = currentPhases[num];
802
+ const enhancement = plan.enhancements.find(e => e.dimension === `Phase ${num}: ${original.name}`);
803
+
804
+ comparison.phases[num] = {
805
+ name: original.name,
806
+ before: original.score,
807
+ after: current.score,
808
+ actual: current.score - original.score,
809
+ predicted: enhancement?.predictedImprovement || 0,
810
+ hit: current.score >= TARGET_SCORE
811
+ };
812
+ }
813
+
814
+ // Calculate summary
815
+ const kbHits = Object.values(comparison.kb).filter(c => c.hit).length;
816
+ const kbTotal = Object.keys(comparison.kb).length;
817
+ const phaseHits = Object.values(comparison.phases).filter(c => c.hit).length;
818
+ const phaseTotal = Object.keys(comparison.phases).length;
819
+
820
+ // Identify remaining gaps (anything below 98)
821
+ const remainingGaps = [];
822
+ for (const [dim, data] of Object.entries(comparison.kb)) {
823
+ if (data.after < TARGET_SCORE) {
824
+ remainingGaps.push({
825
+ area: 'KB Quality',
826
+ dimension: KB_DIMENSIONS[dim]?.name || dim,
827
+ currentScore: data.after,
828
+ targetScore: TARGET_SCORE,
829
+ gap: TARGET_SCORE - data.after
830
+ });
831
+ }
832
+ }
833
+ for (const [num, data] of Object.entries(comparison.phases)) {
834
+ if (data.after < TARGET_SCORE) {
835
+ remainingGaps.push({
836
+ area: 'Phase Readiness',
837
+ dimension: `Phase ${num}: ${data.name}`,
838
+ currentScore: data.after,
839
+ targetScore: TARGET_SCORE,
840
+ gap: TARGET_SCORE - data.after
841
+ });
842
+ }
843
+ }
844
+
845
+ // Track iteration count
846
+ let iterationCount = plan.iterationCount || 1;
847
+
848
+ // Save verification
849
+ const verification = {
850
+ timestamp: new Date().toISOString(),
851
+ iteration: iterationCount,
852
+ original: originalAssessment.overall,
853
+ current: currentOverall,
854
+ comparison,
855
+ remainingGaps,
856
+ targetScore: TARGET_SCORE,
857
+ targetMet: currentOverall.overall >= TARGET_SCORE
858
+ };
859
+ writeFileSync(join(cwd, '.ruvector', 'verification.json'), JSON.stringify(verification, null, 2));
860
+
861
+ const result = {
862
+ action: 'VERIFICATION_COMPLETE',
863
+ iteration: iterationCount,
864
+ targetScore: TARGET_SCORE,
865
+ summary: {
866
+ kbAt98Plus: `${kbHits}/${kbTotal}`,
867
+ phasesAt98Plus: `${phaseHits}/${phaseTotal}`,
868
+ overallImprovement: {
869
+ kb: `${originalAssessment.overall.kbOverall} → ${currentOverall.kbOverall} (${currentOverall.kbOverall - originalAssessment.overall.kbOverall >= 0 ? '+' : ''}${currentOverall.kbOverall - originalAssessment.overall.kbOverall})`,
870
+ phases: `${originalAssessment.overall.phaseOverall} → ${currentOverall.phaseOverall} (${currentOverall.phaseOverall - originalAssessment.overall.phaseOverall >= 0 ? '+' : ''}${currentOverall.phaseOverall - originalAssessment.overall.phaseOverall})`,
871
+ combined: `${originalAssessment.overall.overall} → ${currentOverall.overall} (${currentOverall.overall - originalAssessment.overall.overall >= 0 ? '+' : ''}${currentOverall.overall - originalAssessment.overall.overall})`
872
+ }
873
+ }
874
+ };
875
+
876
+ if (args.detailed) {
877
+ result.kbComparison = comparison.kb;
878
+ result.phaseComparison = comparison.phases;
879
+ }
880
+
881
+ // Check if we've hit the target
882
+ if (currentOverall.overall >= TARGET_SCORE && remainingGaps.length === 0) {
883
+ result.status = 'TARGET_ACHIEVED';
884
+ result.message = `🎯 All scores at ${TARGET_SCORE}+ after ${iterationCount} iteration(s)!`;
885
+ result.remainingGaps = 'None - all targets met!';
886
+ result.nextStep = 'Excellence achieved. Ready for production.';
887
+ } else {
888
+ // RECURSIVE: Auto-generate next plan
889
+ result.status = 'NEEDS_MORE_WORK';
890
+ result.message = `Score ${currentOverall.overall}/100 - target is ${TARGET_SCORE}. Generating next iteration plan...`;
891
+ result.remainingGaps = remainingGaps;
892
+
893
+ // Update assessment with current scores for next iteration
894
+ const newAssessment = {
895
+ timestamp: new Date().toISOString(),
896
+ kb: currentKB,
897
+ phases: currentPhases,
898
+ overall: currentOverall,
899
+ previousIteration: iterationCount
900
+ };
901
+ writeFileSync(join(cwd, '.ruvector', 'assessment.json'), JSON.stringify(newAssessment, null, 2));
902
+
903
+ // Auto-generate new plan for remaining gaps
904
+ const newEnhancements = remainingGaps.map((gap, idx) => ({
905
+ id: idx + 1,
906
+ area: gap.area,
907
+ dimension: gap.dimension,
908
+ currentScore: gap.currentScore,
909
+ targetScore: TARGET_SCORE,
910
+ predictedImprovement: gap.gap,
911
+ task: getImprovementTask(gap),
912
+ priority: gap.gap > 30 ? 'HIGH' : (gap.gap > 15 ? 'MEDIUM' : 'LOW'),
913
+ effort: gap.gap > 30 ? 'Large' : (gap.gap > 15 ? 'Medium' : 'Small')
914
+ }));
915
+
916
+ const newPlan = {
917
+ timestamp: new Date().toISOString(),
918
+ threshold: TARGET_SCORE,
919
+ iterationCount: iterationCount + 1,
920
+ baselineScores: currentOverall,
921
+ enhancements: newEnhancements,
922
+ predictions: {
923
+ tasksCount: newEnhancements.length,
924
+ highPriority: newEnhancements.filter(e => e.priority === 'HIGH').length,
925
+ mediumPriority: newEnhancements.filter(e => e.priority === 'MEDIUM').length,
926
+ lowPriority: newEnhancements.filter(e => e.priority === 'LOW').length
927
+ },
928
+ confirmed: false,
929
+ currentTaskIndex: 0
930
+ };
931
+ writeFileSync(join(cwd, '.ruvector', 'plan.json'), JSON.stringify(newPlan, null, 2));
932
+
933
+ result.newPlan = {
934
+ iteration: iterationCount + 1,
935
+ tasks: newEnhancements.length,
936
+ highPriority: newPlan.predictions.highPriority
937
+ };
938
+ result.nextStep = `Iteration ${iterationCount + 1} plan generated. Run kb_first_confirm with confirmed=true to continue.`;
939
+ }
940
+
941
+ return result;
942
+ }
943
+
944
+ /**
945
+ * Get improvement task based on gap
946
+ */
947
+ function getImprovementTask(gap) {
948
+ if (gap.area === 'KB Quality') {
949
+ switch (gap.dimension) {
950
+ case 'Completeness':
951
+ return `Add more KB entries to reach ${gap.targetScore}% coverage`;
952
+ case 'Depth':
953
+ return `Expand KB entries with more detail (target: 2500+ chars each)`;
954
+ case 'Comprehensiveness':
955
+ return `Document additional edge cases and exceptions`;
956
+ case 'Accuracy':
957
+ return `Add validation tests and verification`;
958
+ case 'Freshness':
959
+ return `Update KB content with latest information`;
960
+ case 'Attribution':
961
+ return `Add KB-Generated headers to remaining code files`;
962
+ default:
963
+ return `Improve ${gap.dimension} to ${gap.targetScore}`;
964
+ }
965
+ }
966
+ return `Complete remaining criteria for ${gap.dimension}`;
967
+ }
968
+
969
+ /**
970
+ * Handle MCP tool calls
971
+ */
972
+ async function handleToolCall(toolName, args) {
973
+ const cwd = args.projectPath || process.cwd();
974
+
975
+ switch (toolName) {
976
+ case 'kb_first_assess':
977
+ return await handleAssess(cwd, args);
978
+ case 'kb_first_plan':
979
+ return await handlePlan(cwd, args);
980
+ case 'kb_first_confirm':
981
+ return await handleConfirm(cwd, args);
982
+ case 'kb_first_execute':
983
+ return await handleExecute(cwd, args);
984
+ case 'kb_first_verify':
985
+ return await handleVerify(cwd, args);
986
+ default:
987
+ return { error: `Unknown tool: ${toolName}` };
988
+ }
289
989
  }
290
990
 
291
991
  /**
292
992
  * MCP Protocol Handler
293
993
  */
294
- function handleMCPMessage(message) {
994
+ async function handleMCPMessage(message) {
295
995
  const { jsonrpc, id, method, params } = message;
296
996
 
297
997
  if (jsonrpc !== '2.0') {
298
- return {
299
- jsonrpc: '2.0',
300
- id,
301
- error: { code: -32600, message: 'Invalid JSON-RPC version' }
302
- };
998
+ return { jsonrpc: '2.0', id, error: { code: -32600, message: 'Invalid JSON-RPC version' } };
303
999
  }
304
1000
 
305
1001
  switch (method) {
306
1002
  case 'initialize':
307
1003
  return {
308
- jsonrpc: '2.0',
309
- id,
1004
+ jsonrpc: '2.0', id,
310
1005
  result: {
311
1006
  protocolVersion: MCP_VERSION,
312
- serverInfo: {
313
- name: SERVER_NAME,
314
- version: SERVER_VERSION
315
- },
316
- capabilities: {
317
- tools: {}
318
- }
1007
+ serverInfo: { name: SERVER_NAME, version: SERVER_VERSION },
1008
+ capabilities: { tools: {} }
319
1009
  }
320
1010
  };
321
1011
 
322
1012
  case 'tools/list':
323
- return {
324
- jsonrpc: '2.0',
325
- id,
326
- result: { tools: TOOLS }
327
- };
1013
+ return { jsonrpc: '2.0', id, result: { tools: TOOLS } };
328
1014
 
329
1015
  case 'tools/call':
330
- return handleToolCallAsync(id, params.name, params.arguments);
1016
+ try {
1017
+ const result = await handleToolCall(params.name, params.arguments || {});
1018
+ return {
1019
+ jsonrpc: '2.0', id,
1020
+ result: { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }
1021
+ };
1022
+ } catch (error) {
1023
+ return { jsonrpc: '2.0', id, error: { code: -32000, message: error.message } };
1024
+ }
331
1025
 
332
1026
  default:
333
- return {
334
- jsonrpc: '2.0',
335
- id,
336
- error: { code: -32601, message: `Unknown method: ${method}` }
337
- };
338
- }
339
- }
340
-
341
- async function handleToolCallAsync(id, toolName, args) {
342
- try {
343
- const result = await handleToolCall(toolName, args || {});
344
- return {
345
- jsonrpc: '2.0',
346
- id,
347
- result: {
348
- content: [
349
- {
350
- type: 'text',
351
- text: JSON.stringify(result, null, 2)
352
- }
353
- ]
354
- }
355
- };
356
- } catch (error) {
357
- return {
358
- jsonrpc: '2.0',
359
- id,
360
- error: { code: -32000, message: error.message }
361
- };
1027
+ return { jsonrpc: '2.0', id, error: { code: -32601, message: `Unknown method: ${method}` } };
362
1028
  }
363
1029
  }
364
1030
 
365
1031
  /**
366
- * Start MCP Server (stdio mode)
1032
+ * Start MCP Server
367
1033
  */
368
1034
  export async function startMCPServer(options = {}) {
369
- console.error('RuvNet KB-First MCP Server starting...');
370
- console.error(`Version: ${SERVER_VERSION}`);
371
- console.error('Mode: stdio');
1035
+ console.error(`RuvNet KB-First MCP Server v${SERVER_VERSION}`);
1036
+ console.error('Architecture: Granular Score-Driven | Tools: 5 | Dimensions: 6 KB + 12 Phases');
1037
+ console.error('Workflow: Assess → Plan → Confirm → Execute → Verify');
372
1038
 
373
- // Read from stdin
374
1039
  let buffer = '';
375
-
376
1040
  process.stdin.setEncoding('utf-8');
1041
+
377
1042
  process.stdin.on('data', async (chunk) => {
378
1043
  buffer += chunk;
379
-
380
- // Try to parse complete JSON-RPC messages
381
1044
  const lines = buffer.split('\n');
382
1045
  buffer = lines.pop() || '';
383
1046
 
384
1047
  for (const line of lines) {
385
1048
  if (!line.trim()) continue;
386
-
387
1049
  try {
388
- const message = JSON.parse(line);
389
- const response = await handleMCPMessage(message);
390
-
391
- if (response) {
392
- process.stdout.write(JSON.stringify(response) + '\n');
393
- }
1050
+ const response = await handleMCPMessage(JSON.parse(line));
1051
+ if (response) process.stdout.write(JSON.stringify(response) + '\n');
394
1052
  } catch (error) {
395
1053
  console.error('Parse error:', error.message);
396
1054
  }
397
1055
  }
398
1056
  });
399
1057
 
400
- process.stdin.on('end', () => {
401
- console.error('MCP Server shutting down');
402
- process.exit(0);
403
- });
404
-
405
- // Keep process alive
1058
+ process.stdin.on('end', () => process.exit(0));
406
1059
  process.stdin.resume();
407
1060
  }
408
1061
 
409
- // Run if called directly
410
1062
  if (import.meta.url === `file://${process.argv[1]}`) {
411
1063
  startMCPServer();
412
1064
  }