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.
- package/README.md +41 -23
- package/SKILL.md +153 -75
- package/package.json +1 -1
- 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
|
-
*
|
|
5
|
-
*
|
|
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 {
|
|
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 = '
|
|
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
|
-
*
|
|
126
|
+
* MCP Tools
|
|
23
127
|
*/
|
|
24
128
|
const TOOLS = [
|
|
25
129
|
{
|
|
26
|
-
name: '
|
|
27
|
-
description:
|
|
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
|
-
|
|
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: '
|
|
45
|
-
description:
|
|
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
|
-
|
|
50
|
-
|
|
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: '
|
|
58
|
-
description:
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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: '
|
|
75
|
-
description:
|
|
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
|
-
|
|
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: '
|
|
88
|
-
description:
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
|
|
107
|
-
|
|
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
|
-
*
|
|
228
|
+
* Score KB Quality Dimensions (1-100 each)
|
|
119
229
|
*/
|
|
120
|
-
|
|
121
|
-
const
|
|
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
|
-
|
|
124
|
-
|
|
125
|
-
|
|
236
|
+
// Count KB entries and docs
|
|
237
|
+
let kbEntries = 0;
|
|
238
|
+
let docFiles = 0;
|
|
239
|
+
let totalContent = 0;
|
|
126
240
|
|
|
127
|
-
|
|
128
|
-
|
|
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
|
-
|
|
131
|
-
|
|
254
|
+
if (existsSync(docsDir)) {
|
|
255
|
+
try {
|
|
256
|
+
docFiles = readdirSync(docsDir).filter(f => f.endsWith('.md')).length;
|
|
257
|
+
} catch {}
|
|
258
|
+
}
|
|
132
259
|
|
|
133
|
-
|
|
134
|
-
|
|
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
|
-
|
|
137
|
-
|
|
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
|
-
|
|
140
|
-
|
|
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
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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
|
-
|
|
150
|
-
|
|
354
|
+
return scores;
|
|
355
|
+
}
|
|
151
356
|
|
|
152
|
-
|
|
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
|
-
|
|
363
|
+
let config = { phases: { completed: [], gates: {} } };
|
|
155
364
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
});
|
|
365
|
+
if (existsSync(configPath)) {
|
|
366
|
+
try {
|
|
367
|
+
config = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
368
|
+
} catch {}
|
|
369
|
+
}
|
|
162
370
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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
|
-
|
|
179
|
-
|
|
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 (
|
|
182
|
-
return
|
|
183
|
-
|
|
184
|
-
|
|
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
|
-
|
|
189
|
-
const
|
|
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
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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
|
|
200
|
-
const
|
|
515
|
+
async function handlePlan(cwd, args) {
|
|
516
|
+
const assessmentPath = join(cwd, '.ruvector', 'assessment.json');
|
|
201
517
|
|
|
202
|
-
if (!existsSync(
|
|
518
|
+
if (!existsSync(assessmentPath)) {
|
|
203
519
|
return {
|
|
204
|
-
|
|
205
|
-
|
|
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
|
|
210
|
-
const
|
|
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
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
:
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
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
|
|
224
|
-
const
|
|
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 (!
|
|
640
|
+
if (!args.confirmed) {
|
|
227
641
|
return {
|
|
228
|
-
|
|
229
|
-
|
|
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
|
|
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
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
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
|
|
246
|
-
const
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
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
|
-
|
|
263
|
-
|
|
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
|
|
702
|
+
// Check if all done
|
|
703
|
+
if (plan.currentTaskIndex >= plan.enhancements.length) {
|
|
266
704
|
return {
|
|
267
|
-
|
|
268
|
-
|
|
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
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
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
|
-
|
|
281
|
-
|
|
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
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
1032
|
+
* Start MCP Server
|
|
367
1033
|
*/
|
|
368
1034
|
export async function startMCPServer(options = {}) {
|
|
369
|
-
console.error(
|
|
370
|
-
console.error(
|
|
371
|
-
console.error('
|
|
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
|
|
389
|
-
|
|
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
|
}
|