ruvnet-kb-first 6.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/package.json +1 -1
- package/src/mcp-server.js +789 -471
package/src/mcp-server.js
CHANGED
|
@@ -1,670 +1,988 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* RuvNet KB-First MCP Server - Score-Driven Architecture
|
|
3
|
-
* Version 6.
|
|
2
|
+
* RuvNet KB-First MCP Server - Granular Score-Driven Architecture
|
|
3
|
+
* Version 6.1.0
|
|
4
4
|
*
|
|
5
|
-
* PHILOSOPHY:
|
|
6
|
-
* -
|
|
7
|
-
* -
|
|
8
|
-
* -
|
|
9
|
-
* -
|
|
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?
|
|
10
11
|
*
|
|
11
|
-
*
|
|
12
|
-
* 1. kb_first_assess
|
|
13
|
-
* 2.
|
|
14
|
-
* 3.
|
|
15
|
-
* 4.
|
|
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
|
|
16
18
|
*
|
|
17
19
|
* Usage:
|
|
18
20
|
* npx ruvnet-kb-first mcp
|
|
19
|
-
* node src/mcp-server.js
|
|
20
21
|
*/
|
|
21
22
|
|
|
22
23
|
import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, statSync } from 'fs';
|
|
23
24
|
import { join } from 'path';
|
|
24
25
|
import { globSync } from 'glob';
|
|
25
26
|
|
|
26
|
-
// MCP Protocol Constants
|
|
27
27
|
const MCP_VERSION = '0.1.0';
|
|
28
28
|
const SERVER_NAME = 'ruvnet-kb-first';
|
|
29
|
-
const SERVER_VERSION = '6.
|
|
29
|
+
const SERVER_VERSION = '6.1.0';
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
|
-
*
|
|
33
|
-
* These are the ONLY metrics that matter
|
|
32
|
+
* KB Quality Dimensions (each scored 1-100)
|
|
34
33
|
*/
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
coverage: 10, // Domain coverage completeness
|
|
41
|
-
embeddings: 10, // Vectors generated
|
|
42
|
-
freshness: 10 // Recent updates
|
|
43
|
-
}
|
|
34
|
+
const KB_DIMENSIONS = {
|
|
35
|
+
completeness: {
|
|
36
|
+
name: 'Completeness',
|
|
37
|
+
description: 'Does the KB cover all necessary domain topics?',
|
|
38
|
+
weight: 20
|
|
44
39
|
},
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
gapResolution: 10, // Gaps identified and resolved
|
|
50
|
-
testCoverage: 10, // Tests exist and pass
|
|
51
|
-
security: 5 // Security basics in place
|
|
52
|
-
}
|
|
40
|
+
depth: {
|
|
41
|
+
name: 'Depth',
|
|
42
|
+
description: 'Is each topic covered with sufficient detail?',
|
|
43
|
+
weight: 20
|
|
53
44
|
},
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
|
61
64
|
}
|
|
62
65
|
};
|
|
63
66
|
|
|
64
67
|
/**
|
|
65
|
-
* Phase
|
|
68
|
+
* Phase Definitions with readiness criteria
|
|
66
69
|
*/
|
|
67
70
|
const PHASES = {
|
|
68
|
-
0:
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
+
}
|
|
84
123
|
};
|
|
85
124
|
|
|
86
125
|
/**
|
|
87
|
-
* MCP Tools
|
|
126
|
+
* MCP Tools
|
|
88
127
|
*/
|
|
89
128
|
const TOOLS = [
|
|
90
129
|
{
|
|
91
130
|
name: 'kb_first_assess',
|
|
92
|
-
description: `
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
-
|
|
96
|
-
-
|
|
97
|
-
-
|
|
98
|
-
|
|
99
|
-
|
|
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.`,
|
|
100
146
|
inputSchema: {
|
|
101
147
|
type: 'object',
|
|
102
148
|
properties: {
|
|
103
|
-
|
|
104
|
-
saveBaseline: { type: 'boolean', description: 'Save as baseline for delta comparison', default: true }
|
|
149
|
+
projectPath: { type: 'string', description: 'Path to project (default: current directory)' }
|
|
105
150
|
}
|
|
106
151
|
}
|
|
107
152
|
},
|
|
108
153
|
{
|
|
109
|
-
name: '
|
|
110
|
-
description: `
|
|
111
|
-
REQUIRES: kb_first_assess must be run first to establish baseline.
|
|
154
|
+
name: 'kb_first_plan',
|
|
155
|
+
description: `Generate enhancement plan based on assessment scores.
|
|
112
156
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
|
118
162
|
|
|
119
|
-
|
|
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.`,
|
|
120
165
|
inputSchema: {
|
|
121
166
|
type: 'object',
|
|
122
167
|
properties: {
|
|
123
|
-
|
|
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' }
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
{
|
|
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.`,
|
|
181
|
+
inputSchema: {
|
|
182
|
+
type: 'object',
|
|
183
|
+
properties: {
|
|
184
|
+
confirmed: { type: 'boolean', description: 'User confirms readiness to proceed' }
|
|
124
185
|
},
|
|
125
|
-
required: ['
|
|
186
|
+
required: ['confirmed']
|
|
126
187
|
}
|
|
127
188
|
},
|
|
128
189
|
{
|
|
129
|
-
name: '
|
|
130
|
-
description: `
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
-
|
|
134
|
-
-
|
|
135
|
-
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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.`,
|
|
139
200
|
inputSchema: {
|
|
140
201
|
type: 'object',
|
|
141
202
|
properties: {
|
|
142
|
-
|
|
203
|
+
taskComplete: { type: 'boolean', description: 'Mark current task as complete' }
|
|
143
204
|
}
|
|
144
205
|
}
|
|
145
206
|
},
|
|
146
207
|
{
|
|
147
|
-
name: '
|
|
148
|
-
description: `
|
|
149
|
-
|
|
150
|
-
-
|
|
151
|
-
-
|
|
152
|
-
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
- nextPhase: number (if can proceed)
|
|
158
|
-
|
|
159
|
-
THIS IS THE HARD GATE. No bypassing.`,
|
|
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.`,
|
|
160
218
|
inputSchema: {
|
|
161
219
|
type: 'object',
|
|
162
220
|
properties: {
|
|
163
|
-
|
|
164
|
-
}
|
|
165
|
-
required: ['phase']
|
|
221
|
+
detailed: { type: 'boolean', description: 'Show detailed comparison', default: true }
|
|
222
|
+
}
|
|
166
223
|
}
|
|
167
224
|
}
|
|
168
225
|
];
|
|
169
226
|
|
|
170
227
|
/**
|
|
171
|
-
*
|
|
228
|
+
* Score KB Quality Dimensions (1-100 each)
|
|
172
229
|
*/
|
|
173
|
-
function
|
|
174
|
-
const scores = {
|
|
175
|
-
kb: { total: 0, max: 40, components: {} },
|
|
176
|
-
app: { total: 0, max: 40, components: {} },
|
|
177
|
-
process: { total: 0, max: 20, components: {} },
|
|
178
|
-
total: 0,
|
|
179
|
-
max: 100,
|
|
180
|
-
grade: 'F',
|
|
181
|
-
timestamp: new Date().toISOString()
|
|
182
|
-
};
|
|
183
|
-
|
|
184
|
-
// ===== KB SCORE (40 points) =====
|
|
185
|
-
const ruvectorDir = join(cwd, '.ruvector');
|
|
230
|
+
function scoreKBDimensions(cwd) {
|
|
231
|
+
const scores = {};
|
|
186
232
|
const kbDir = join(cwd, 'src', 'kb');
|
|
233
|
+
const docsDir = join(cwd, 'docs');
|
|
234
|
+
const ruvectorDir = join(cwd, '.ruvector');
|
|
187
235
|
|
|
188
|
-
// KB
|
|
236
|
+
// Count KB entries and docs
|
|
189
237
|
let kbEntries = 0;
|
|
238
|
+
let docFiles = 0;
|
|
239
|
+
let totalContent = 0;
|
|
240
|
+
|
|
190
241
|
if (existsSync(kbDir)) {
|
|
191
242
|
try {
|
|
192
243
|
const files = readdirSync(kbDir);
|
|
193
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
|
+
}
|
|
194
251
|
} catch {}
|
|
195
252
|
}
|
|
196
|
-
scores.kb.components.entries = Math.min(10, Math.floor(kbEntries / 5) * 2);
|
|
197
253
|
|
|
198
|
-
// KB Coverage (10 points) - based on documented domains
|
|
199
|
-
const docsDir = join(cwd, 'docs');
|
|
200
|
-
let domainDocs = 0;
|
|
201
254
|
if (existsSync(docsDir)) {
|
|
202
255
|
try {
|
|
203
|
-
|
|
204
|
-
domainDocs = files.filter(f => f.endsWith('.md')).length;
|
|
256
|
+
docFiles = readdirSync(docsDir).filter(f => f.endsWith('.md')).length;
|
|
205
257
|
} catch {}
|
|
206
258
|
}
|
|
207
|
-
scores.kb.components.coverage = Math.min(10, domainDocs * 2);
|
|
208
259
|
|
|
209
|
-
//
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
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
|
+
};
|
|
267
|
+
|
|
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
|
+
};
|
|
276
|
+
|
|
277
|
+
// Comprehensiveness: Check for edge case documentation
|
|
278
|
+
let edgeCaseScore = 0;
|
|
279
|
+
const srcDir = join(cwd, 'src');
|
|
280
|
+
if (existsSync(srcDir)) {
|
|
213
281
|
try {
|
|
214
|
-
const
|
|
215
|
-
|
|
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
|
+
}
|
|
216
291
|
} catch {}
|
|
217
292
|
}
|
|
218
|
-
scores.
|
|
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
|
+
};
|
|
298
|
+
|
|
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
|
+
};
|
|
219
309
|
|
|
220
|
-
//
|
|
221
|
-
let
|
|
310
|
+
// Freshness: Based on last modification
|
|
311
|
+
let freshnessScore = 0;
|
|
222
312
|
if (existsSync(ruvectorDir)) {
|
|
223
313
|
try {
|
|
224
314
|
const stat = statSync(ruvectorDir);
|
|
225
|
-
const
|
|
226
|
-
if (
|
|
227
|
-
else if (
|
|
228
|
-
else if (
|
|
229
|
-
else
|
|
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;
|
|
230
321
|
} catch {}
|
|
231
322
|
}
|
|
232
|
-
scores.
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
const srcDir = join(cwd, 'src');
|
|
323
|
+
scores.freshness = {
|
|
324
|
+
score: freshnessScore,
|
|
325
|
+
reason: freshnessScore > 50 ? 'Recently updated' : 'Stale - needs refresh',
|
|
326
|
+
improvement: freshnessScore < 80 ? 'Update KB content' : 'Fresh'
|
|
327
|
+
};
|
|
238
328
|
|
|
239
|
-
//
|
|
240
|
-
let
|
|
241
|
-
let filesWithCitation = 0;
|
|
329
|
+
// Attribution: Check for citations in code
|
|
330
|
+
let attributionScore = 0;
|
|
242
331
|
if (existsSync(srcDir)) {
|
|
243
332
|
try {
|
|
244
|
-
|
|
245
|
-
|
|
333
|
+
const files = globSync('**/*.{ts,tsx,js,jsx,py}', { cwd: srcDir });
|
|
334
|
+
let filesWithCitation = 0;
|
|
335
|
+
for (const f of files) {
|
|
246
336
|
try {
|
|
247
|
-
const content = readFileSync(join(srcDir,
|
|
337
|
+
const content = readFileSync(join(srcDir, f), 'utf-8');
|
|
248
338
|
if (content.includes('KB-Generated:') || content.includes('Sources:') || content.includes('@kb-source')) {
|
|
249
339
|
filesWithCitation++;
|
|
250
340
|
}
|
|
251
341
|
} catch {}
|
|
252
342
|
}
|
|
343
|
+
attributionScore = files.length > 0 ? Math.round((filesWithCitation / files.length) * 100) : 100;
|
|
253
344
|
} catch {}
|
|
345
|
+
} else {
|
|
346
|
+
attributionScore = 100; // No code = not applicable
|
|
254
347
|
}
|
|
255
|
-
|
|
256
|
-
|
|
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
|
+
};
|
|
353
|
+
|
|
354
|
+
return scores;
|
|
355
|
+
}
|
|
257
356
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
357
|
+
/**
|
|
358
|
+
* Score Phase Readiness (1-100 each)
|
|
359
|
+
*/
|
|
360
|
+
function scorePhaseReadiness(cwd) {
|
|
361
|
+
const scores = {};
|
|
362
|
+
const configPath = join(cwd, '.ruvector', 'config.json');
|
|
363
|
+
let config = { phases: { completed: [], gates: {} } };
|
|
364
|
+
|
|
365
|
+
if (existsSync(configPath)) {
|
|
262
366
|
try {
|
|
263
|
-
|
|
264
|
-
gapCount = content ? content.split('\n').length : 0;
|
|
367
|
+
config = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
265
368
|
} catch {}
|
|
266
369
|
}
|
|
267
|
-
scores.app.components.gapResolution = Math.max(0, 10 - gapCount);
|
|
268
370
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
for (const
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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
|
+
}
|
|
276
390
|
}
|
|
277
|
-
}
|
|
278
|
-
const testFiles = existsSync(srcDir) ? globSync('**/*.{test,spec}.{ts,tsx,js,jsx}', { cwd: srcDir }) : [];
|
|
279
|
-
scores.app.components.testCoverage = hasTests ? 5 : 0;
|
|
280
|
-
scores.app.components.testCoverage += Math.min(5, testFiles.length);
|
|
281
391
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
secScore -= 3;
|
|
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
|
|
401
|
+
};
|
|
293
402
|
}
|
|
294
|
-
scores.app.components.security = Math.max(0, secScore);
|
|
295
403
|
|
|
296
|
-
|
|
404
|
+
return scores;
|
|
405
|
+
}
|
|
297
406
|
|
|
298
|
-
|
|
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();
|
|
299
413
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
if (existsSync(configPath)) {
|
|
303
|
-
try {
|
|
304
|
-
const config = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
305
|
-
completedPhases = config.phases?.completed || [];
|
|
306
|
-
} catch {}
|
|
414
|
+
if (criterionLower.includes('documented')) {
|
|
415
|
+
return existsSync(join(cwd, 'docs')) || existsSync(join(cwd, 'README.md'));
|
|
307
416
|
}
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
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'));
|
|
318
428
|
}
|
|
319
|
-
scores.process.components.gatesPassed = Math.min(5, Math.round((gatesPassed / totalPhases) * 5));
|
|
320
429
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
if (existsSync(join(cwd, 'README.md'))) docScore += 2;
|
|
324
|
-
if (existsSync(join(cwd, 'docs', 'api.md')) || existsSync(join(cwd, 'docs', 'API.md'))) docScore += 1;
|
|
325
|
-
if (existsSync(join(cwd, 'docs', 'architecture.md'))) docScore += 1;
|
|
326
|
-
if (existsSync(join(cwd, 'CHANGELOG.md'))) docScore += 1;
|
|
327
|
-
scores.process.components.documentation = Math.min(5, docScore);
|
|
430
|
+
return false;
|
|
431
|
+
}
|
|
328
432
|
|
|
329
|
-
|
|
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;
|
|
330
447
|
|
|
331
|
-
//
|
|
332
|
-
|
|
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;
|
|
333
453
|
|
|
334
|
-
//
|
|
335
|
-
|
|
336
|
-
else if (scores.total >= 93) scores.grade = 'A';
|
|
337
|
-
else if (scores.total >= 90) scores.grade = 'A-';
|
|
338
|
-
else if (scores.total >= 87) scores.grade = 'B+';
|
|
339
|
-
else if (scores.total >= 83) scores.grade = 'B';
|
|
340
|
-
else if (scores.total >= 80) scores.grade = 'B-';
|
|
341
|
-
else if (scores.total >= 70) scores.grade = 'C';
|
|
342
|
-
else if (scores.total >= 60) scores.grade = 'D';
|
|
343
|
-
else scores.grade = 'F';
|
|
454
|
+
// Combined Overall
|
|
455
|
+
const overall = Math.round((kbOverall * 0.5) + (phaseOverall * 0.5));
|
|
344
456
|
|
|
345
|
-
return
|
|
457
|
+
return { kbOverall, phaseOverall, overall };
|
|
346
458
|
}
|
|
347
459
|
|
|
348
460
|
/**
|
|
349
461
|
* Tool Handlers
|
|
350
462
|
*/
|
|
351
|
-
async function
|
|
352
|
-
const
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
}
|
|
360
|
-
writeFileSync(join(ruvectorDir, 'baseline.json'), JSON.stringify(scores, null, 2));
|
|
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 });
|
|
361
472
|
}
|
|
362
473
|
|
|
363
|
-
const
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
grade: scores.grade,
|
|
369
|
-
summary: {
|
|
370
|
-
kb: `${scores.kb.total}/${scores.kb.max}`,
|
|
371
|
-
app: `${scores.app.total}/${scores.app.max}`,
|
|
372
|
-
process: `${scores.process.total}/${scores.process.max}`
|
|
373
|
-
},
|
|
374
|
-
nextStep: 'Run kb_first_phase to begin work, then kb_first_delta to measure improvement'
|
|
474
|
+
const assessment = {
|
|
475
|
+
timestamp: new Date().toISOString(),
|
|
476
|
+
kb: kbScores,
|
|
477
|
+
phases: phaseScores,
|
|
478
|
+
overall
|
|
375
479
|
};
|
|
376
480
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
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
|
|
382
489
|
};
|
|
383
490
|
}
|
|
384
491
|
|
|
385
|
-
|
|
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
|
+
}
|
|
500
|
+
|
|
501
|
+
return {
|
|
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'
|
|
512
|
+
};
|
|
386
513
|
}
|
|
387
514
|
|
|
388
|
-
async function
|
|
389
|
-
const
|
|
390
|
-
const phaseInfo = PHASES[phase];
|
|
515
|
+
async function handlePlan(cwd, args) {
|
|
516
|
+
const assessmentPath = join(cwd, '.ruvector', 'assessment.json');
|
|
391
517
|
|
|
392
|
-
if (!
|
|
518
|
+
if (!existsSync(assessmentPath)) {
|
|
393
519
|
return {
|
|
394
|
-
error:
|
|
395
|
-
|
|
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'
|
|
396
523
|
};
|
|
397
524
|
}
|
|
398
525
|
|
|
399
|
-
|
|
400
|
-
const
|
|
401
|
-
|
|
402
|
-
|
|
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
|
+
}
|
|
403
552
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
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
|
+
});
|
|
410
569
|
}
|
|
411
|
-
}
|
|
570
|
+
}
|
|
412
571
|
}
|
|
413
572
|
|
|
414
|
-
//
|
|
415
|
-
const
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
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
|
|
432
601
|
};
|
|
433
602
|
|
|
603
|
+
writeFileSync(join(cwd, '.ruvector', 'plan.json'), JSON.stringify(plan, null, 2));
|
|
604
|
+
|
|
434
605
|
return {
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
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.'
|
|
447
626
|
};
|
|
448
627
|
}
|
|
449
628
|
|
|
450
|
-
async function
|
|
451
|
-
const
|
|
629
|
+
async function handleConfirm(cwd, args) {
|
|
630
|
+
const planPath = join(cwd, '.ruvector', 'plan.json');
|
|
452
631
|
|
|
453
|
-
if (!existsSync(
|
|
632
|
+
if (!existsSync(planPath)) {
|
|
454
633
|
return {
|
|
455
|
-
error: '
|
|
456
|
-
message: 'No
|
|
457
|
-
action: 'Run
|
|
634
|
+
error: 'NO_PLAN',
|
|
635
|
+
message: 'No plan found. Run kb_first_plan first.',
|
|
636
|
+
action: 'Run kb_first_plan to generate enhancement plan'
|
|
458
637
|
};
|
|
459
638
|
}
|
|
460
639
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
640
|
+
if (!args.confirmed) {
|
|
641
|
+
return {
|
|
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.'
|
|
645
|
+
};
|
|
466
646
|
}
|
|
467
647
|
|
|
468
|
-
const
|
|
648
|
+
const plan = JSON.parse(readFileSync(planPath, 'utf-8'));
|
|
649
|
+
plan.confirmed = true;
|
|
650
|
+
plan.confirmedAt = new Date().toISOString();
|
|
651
|
+
plan.currentTaskIndex = 0;
|
|
469
652
|
|
|
470
|
-
|
|
471
|
-
total: current.total - baseline.total,
|
|
472
|
-
kb: current.kb.total - baseline.kb.total,
|
|
473
|
-
app: current.app.total - baseline.app.total,
|
|
474
|
-
process: current.process.total - baseline.process.total
|
|
475
|
-
};
|
|
653
|
+
writeFileSync(planPath, JSON.stringify(plan, null, 2));
|
|
476
654
|
|
|
477
|
-
const
|
|
478
|
-
const canProceed = delta.total >= 0;
|
|
655
|
+
const firstTask = plan.enhancements[0];
|
|
479
656
|
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
},
|
|
493
|
-
|
|
494
|
-
total: delta.total > 0 ? `+${delta.total}` : `${delta.total}`,
|
|
495
|
-
kb: delta.kb > 0 ? `+${delta.kb}` : `${delta.kb}`,
|
|
496
|
-
app: delta.app > 0 ? `+${delta.app}` : `${delta.app}`,
|
|
497
|
-
process: delta.process > 0 ? `+${delta.process}` : `${delta.process}`
|
|
498
|
-
}
|
|
657
|
+
return {
|
|
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'
|
|
499
671
|
};
|
|
672
|
+
}
|
|
500
673
|
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
result.action = 'Fix issues causing score regression, then run kb_first_delta again.';
|
|
504
|
-
} else {
|
|
505
|
-
result.action = 'Run kb_first_gate to verify phase completion and proceed.';
|
|
506
|
-
}
|
|
674
|
+
async function handleExecute(cwd, args) {
|
|
675
|
+
const planPath = join(cwd, '.ruvector', 'plan.json');
|
|
507
676
|
|
|
508
|
-
if (
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
after: current.kb.components
|
|
513
|
-
},
|
|
514
|
-
app: {
|
|
515
|
-
before: baseline.app.components,
|
|
516
|
-
after: current.app.components
|
|
517
|
-
},
|
|
518
|
-
process: {
|
|
519
|
-
before: baseline.process.components,
|
|
520
|
-
after: current.process.components
|
|
521
|
-
}
|
|
677
|
+
if (!existsSync(planPath)) {
|
|
678
|
+
return {
|
|
679
|
+
error: 'NO_PLAN',
|
|
680
|
+
message: 'No plan found. Run kb_first_plan first.'
|
|
522
681
|
};
|
|
523
682
|
}
|
|
524
683
|
|
|
525
|
-
|
|
526
|
-
}
|
|
684
|
+
const plan = JSON.parse(readFileSync(planPath, 'utf-8'));
|
|
527
685
|
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
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
|
+
}
|
|
531
693
|
|
|
532
|
-
if
|
|
533
|
-
|
|
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));
|
|
534
700
|
}
|
|
535
701
|
|
|
536
|
-
// Check
|
|
537
|
-
|
|
538
|
-
if (!existsSync(baselinePath)) {
|
|
702
|
+
// Check if all done
|
|
703
|
+
if (plan.currentTaskIndex >= plan.enhancements.length) {
|
|
539
704
|
return {
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
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'
|
|
544
709
|
};
|
|
545
710
|
}
|
|
546
711
|
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
712
|
+
const currentTask = plan.enhancements[plan.currentTaskIndex];
|
|
713
|
+
const completedCount = plan.enhancements.filter(e => e.completed).length;
|
|
714
|
+
|
|
715
|
+
return {
|
|
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'
|
|
733
|
+
};
|
|
734
|
+
}
|
|
735
|
+
|
|
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
|
+
}
|
|
558
755
|
}
|
|
756
|
+
return task.task;
|
|
757
|
+
}
|
|
559
758
|
|
|
560
|
-
|
|
561
|
-
const
|
|
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+
|
|
562
763
|
|
|
563
|
-
if (
|
|
764
|
+
if (!existsSync(planPath) || !existsSync(assessmentPath)) {
|
|
564
765
|
return {
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
phase,
|
|
568
|
-
phaseName: phaseInfo.name,
|
|
569
|
-
baseline: baseline.total,
|
|
570
|
-
current: current.total,
|
|
571
|
-
delta
|
|
766
|
+
error: 'MISSING_DATA',
|
|
767
|
+
message: 'Missing plan or assessment. Run kb_first_assess and kb_first_plan first.'
|
|
572
768
|
};
|
|
573
769
|
}
|
|
574
770
|
|
|
575
|
-
|
|
576
|
-
const
|
|
577
|
-
let config = { phases: { current: 0, completed: [], gates: {} } };
|
|
578
|
-
if (existsSync(configPath)) {
|
|
579
|
-
try {
|
|
580
|
-
config = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
581
|
-
} catch {}
|
|
582
|
-
}
|
|
771
|
+
const plan = JSON.parse(readFileSync(planPath, 'utf-8'));
|
|
772
|
+
const originalAssessment = JSON.parse(readFileSync(assessmentPath, 'utf-8'));
|
|
583
773
|
|
|
584
|
-
//
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
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: {}
|
|
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
|
+
};
|
|
605
797
|
}
|
|
606
798
|
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
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
|
|
614
811
|
};
|
|
615
812
|
}
|
|
616
813
|
|
|
617
|
-
//
|
|
618
|
-
|
|
619
|
-
|
|
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
|
+
}
|
|
620
843
|
}
|
|
621
|
-
config.phases.gates[phaseInfo.gate] = true;
|
|
622
844
|
|
|
623
|
-
//
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
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));
|
|
627
860
|
|
|
628
|
-
|
|
629
|
-
|
|
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;
|
|
630
879
|
}
|
|
631
880
|
|
|
632
|
-
//
|
|
633
|
-
|
|
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));
|
|
634
932
|
|
|
635
|
-
|
|
636
|
-
|
|
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
|
+
}
|
|
637
940
|
|
|
638
|
-
return
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
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}`;
|
|
651
967
|
}
|
|
652
968
|
|
|
653
969
|
/**
|
|
654
970
|
* Handle MCP tool calls
|
|
655
971
|
*/
|
|
656
972
|
async function handleToolCall(toolName, args) {
|
|
657
|
-
const cwd = process.cwd();
|
|
973
|
+
const cwd = args.projectPath || process.cwd();
|
|
658
974
|
|
|
659
975
|
switch (toolName) {
|
|
660
976
|
case 'kb_first_assess':
|
|
661
|
-
return await
|
|
662
|
-
case '
|
|
663
|
-
return await
|
|
664
|
-
case '
|
|
665
|
-
return await
|
|
666
|
-
case '
|
|
667
|
-
return await
|
|
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);
|
|
668
986
|
default:
|
|
669
987
|
return { error: `Unknown tool: ${toolName}` };
|
|
670
988
|
}
|
|
@@ -711,12 +1029,12 @@ async function handleMCPMessage(message) {
|
|
|
711
1029
|
}
|
|
712
1030
|
|
|
713
1031
|
/**
|
|
714
|
-
* Start MCP Server
|
|
1032
|
+
* Start MCP Server
|
|
715
1033
|
*/
|
|
716
1034
|
export async function startMCPServer(options = {}) {
|
|
717
1035
|
console.error(`RuvNet KB-First MCP Server v${SERVER_VERSION}`);
|
|
718
|
-
console.error('Architecture: Score-Driven | Tools:
|
|
719
|
-
console.error('
|
|
1036
|
+
console.error('Architecture: Granular Score-Driven | Tools: 5 | Dimensions: 6 KB + 12 Phases');
|
|
1037
|
+
console.error('Workflow: Assess → Plan → Confirm → Execute → Verify');
|
|
720
1038
|
|
|
721
1039
|
let buffer = '';
|
|
722
1040
|
process.stdin.setEncoding('utf-8');
|