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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. 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.0.0
2
+ * RuvNet KB-First MCP Server - Granular Score-Driven Architecture
3
+ * Version 6.1.0
4
4
  *
5
- * PHILOSOPHY: Scoring IS the enforcement mechanism.
6
- * - Every operation requires baseline score first
7
- * - Every operation shows delta (before/after)
8
- * - Hard gates BLOCK on negative deltas
9
- * - No shortcuts - rigorous measurement drives quality
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
- * 4 Tools (not 7):
12
- * 1. kb_first_assess - Calculate baseline scores (KB + App + Process)
13
- * 2. kb_first_phase - Execute phase work with delta tracking
14
- * 3. kb_first_delta - Explicit before/after comparison
15
- * 4. kb_first_gate - Hard gate that blocks on negative delta
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.0.0';
29
+ const SERVER_VERSION = '6.1.0';
30
30
 
31
31
  /**
32
- * Score Categories (total 100 points)
33
- * These are the ONLY metrics that matter
32
+ * KB Quality Dimensions (each scored 1-100)
34
33
  */
35
- const SCORE_WEIGHTS = {
36
- kb: {
37
- weight: 40,
38
- components: {
39
- entries: 10, // KB has content
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
- app: {
46
- weight: 40,
47
- components: {
48
- kbCitations: 15, // Code files cite KB sources
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
- process: {
55
- weight: 20,
56
- components: {
57
- phaseCompletion: 10, // Phases properly completed
58
- gatesPassed: 5, // Hard gates verified
59
- documentation: 5 // Docs exist
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 definitions
68
+ * Phase Definitions with readiness criteria
66
69
  */
67
70
  const PHASES = {
68
- 0: { name: 'Assessment', gate: 'assessment_documented' },
69
- 1: { name: 'KB Design', gate: 'schema_designed' },
70
- 1.5: { name: 'Hooks Setup', gate: 'hooks_verified' },
71
- 2: { name: 'Schema Definition', gate: 'schema_created' },
72
- 3: { name: 'KB Population', gate: 'kb_score_50' },
73
- 4: { name: 'Scoring & Gaps', gate: 'kb_score_80' },
74
- 5: { name: 'Integration', gate: 'integration_tested' },
75
- 6: { name: 'Testing', gate: 'tests_passing' },
76
- 7: { name: 'Optimization', gate: 'performance_met' },
77
- 7.5: { name: 'Testing Gate', gate: 'coverage_80' },
78
- 8: { name: 'Verification', gate: 'all_checks_pass' },
79
- 9: { name: 'Security', gate: 'security_audit_passed' },
80
- 10: { name: 'Documentation', gate: 'docs_complete' },
81
- 11: { name: 'Deployment', gate: 'deployed' },
82
- 11.5: { name: 'Observability', gate: 'monitoring_active' },
83
- 12: { name: 'KB Operations', gate: 'operations_ready' }
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 - Score-Driven Architecture
126
+ * MCP Tools
88
127
  */
89
128
  const TOOLS = [
90
129
  {
91
130
  name: 'kb_first_assess',
92
- description: `Calculate comprehensive baseline scores for KB, App, and Process.
93
- ALWAYS RUN THIS FIRST before any work. Returns:
94
- - KB Score (40 points): entries, coverage, embeddings, freshness
95
- - App Score (40 points): citations, gap resolution, tests, security
96
- - Process Score (20 points): phases, gates, documentation
97
- - Total (100 points)
98
-
99
- This becomes your BASELINE for delta comparison.`,
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
- detailed: { type: 'boolean', description: 'Show component breakdown', default: true },
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: 'kb_first_phase',
110
- description: `Execute a phase with automatic delta tracking.
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
- Workflow:
114
- 1. Loads baseline score from last kb_first_assess
115
- 2. Shows phase requirements and sub-phases
116
- 3. Returns guidance for completing the phase
117
- 4. REMINDS you to run kb_first_delta when done
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
- Will WARN if baseline is stale (>1 hour old).`,
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
- phase: { type: 'number', description: 'Phase number (0-12, including 1.5, 7.5, 11.5)' }
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: ['phase']
186
+ required: ['confirmed']
126
187
  }
127
188
  },
128
189
  {
129
- name: 'kb_first_delta',
130
- description: `Compare current scores against baseline. THE ENFORCEMENT MECHANISM.
131
- Shows:
132
- - Baseline score (from kb_first_assess)
133
- - Current score (calculated now)
134
- - Delta (+ improvement or - regression)
135
- - VERDICT: PASS (positive delta) or FAIL (negative delta)
136
-
137
- If delta is negative, you CANNOT proceed to next phase.
138
- This prevents shortcuts and enforces rigor.`,
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
- showBreakdown: { type: 'boolean', description: 'Show which components changed', default: true }
203
+ taskComplete: { type: 'boolean', description: 'Mark current task as complete' }
143
204
  }
144
205
  }
145
206
  },
146
207
  {
147
- name: 'kb_first_gate',
148
- description: `Hard gate check for phase transition.
149
- BLOCKS progress if:
150
- - Delta is negative (score dropped)
151
- - Required gate condition not met
152
- - Baseline not established
153
-
154
- Returns:
155
- - canProceed: boolean
156
- - blockReason: string (if blocked)
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
- phase: { type: 'number', description: 'Phase to verify gate for' }
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
- * Calculate all scores
228
+ * Score KB Quality Dimensions (1-100 each)
172
229
  */
173
- function calculateScores(cwd) {
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 Entries (10 points)
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
- const files = readdirSync(docsDir);
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
- // KB Embeddings (10 points) - check for vector files or config
210
- const configPath = join(ruvectorDir, 'config.json');
211
- let hasEmbeddings = false;
212
- if (existsSync(configPath)) {
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 config = JSON.parse(readFileSync(configPath, 'utf-8'));
215
- hasEmbeddings = config.kbFirst?.embeddings === true || kbEntries > 0;
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.kb.components.embeddings = hasEmbeddings ? 10 : 0;
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
- // KB Freshness (10 points) - recent updates
221
- let freshness = 0;
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 daysSinceUpdate = (Date.now() - stat.mtime.getTime()) / (1000 * 60 * 60 * 24);
226
- if (daysSinceUpdate < 1) freshness = 10;
227
- else if (daysSinceUpdate < 7) freshness = 7;
228
- else if (daysSinceUpdate < 30) freshness = 4;
229
- else freshness = 0;
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.kb.components.freshness = freshness;
233
-
234
- scores.kb.total = Object.values(scores.kb.components).reduce((a, b) => a + b, 0);
235
-
236
- // ===== APP SCORE (40 points) =====
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
- // KB Citations (15 points)
240
- let codeFiles = [];
241
- let filesWithCitation = 0;
329
+ // Attribution: Check for citations in code
330
+ let attributionScore = 0;
242
331
  if (existsSync(srcDir)) {
243
332
  try {
244
- codeFiles = globSync('**/*.{ts,tsx,js,jsx,py,go,rs}', { cwd: srcDir });
245
- for (const file of codeFiles) {
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, file), 'utf-8');
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
- const citationPercent = codeFiles.length > 0 ? filesWithCitation / codeFiles.length : 1;
256
- scores.app.components.kbCitations = Math.round(citationPercent * 15);
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
- // Gap Resolution (10 points)
259
- const gapsPath = join(ruvectorDir, 'gaps.jsonl');
260
- let gapCount = 0;
261
- if (existsSync(gapsPath)) {
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
- const content = readFileSync(gapsPath, 'utf-8').trim();
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
- // Test Coverage (10 points)
270
- let hasTests = false;
271
- const testDirs = ['tests', 'test', '__tests__', 'src/__tests__'];
272
- for (const td of testDirs) {
273
- if (existsSync(join(cwd, td))) {
274
- hasTests = true;
275
- break;
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
- // Security (5 points)
283
- let secScore = 5;
284
- const gitignorePath = join(cwd, '.gitignore');
285
- if (existsSync(gitignorePath)) {
286
- try {
287
- const content = readFileSync(gitignorePath, 'utf-8');
288
- if (!content.includes('.env')) secScore -= 2;
289
- if (!content.includes('node_modules')) secScore -= 1;
290
- } catch {}
291
- } else {
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
- scores.app.total = Object.values(scores.app.components).reduce((a, b) => a + b, 0);
404
+ return scores;
405
+ }
297
406
 
298
- // ===== PROCESS SCORE (20 points) =====
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
- // Phase Completion (10 points)
301
- let completedPhases = [];
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
- const totalPhases = Object.keys(PHASES).length;
309
- scores.process.components.phaseCompletion = Math.round((completedPhases.length / totalPhases) * 10);
310
-
311
- // Gates Passed (5 points)
312
- let gatesPassed = 0;
313
- if (existsSync(configPath)) {
314
- try {
315
- const config = JSON.parse(readFileSync(configPath, 'utf-8'));
316
- gatesPassed = Object.values(config.phases?.gates || {}).filter(v => v === true).length;
317
- } catch {}
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
- // Documentation (5 points)
322
- let docScore = 0;
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
- scores.process.total = Object.values(scores.process.components).reduce((a, b) => a + b, 0);
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
- // ===== TOTAL =====
332
- scores.total = scores.kb.total + scores.app.total + scores.process.total;
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
- // Grade
335
- if (scores.total >= 98) scores.grade = 'A+';
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 scores;
457
+ return { kbOverall, phaseOverall, overall };
346
458
  }
347
459
 
348
460
  /**
349
461
  * Tool Handlers
350
462
  */
351
- async function handleKbFirstAssess(cwd, args) {
352
- const scores = calculateScores(cwd);
353
-
354
- // Save baseline
355
- if (args.saveBaseline !== false) {
356
- const ruvectorDir = join(cwd, '.ruvector');
357
- if (!existsSync(ruvectorDir)) {
358
- mkdirSync(ruvectorDir, { recursive: true });
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 result = {
364
- action: 'BASELINE_ESTABLISHED',
365
- timestamp: scores.timestamp,
366
- total: scores.total,
367
- max: scores.max,
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
- if (args.detailed !== false) {
378
- result.breakdown = {
379
- kb: scores.kb.components,
380
- app: scores.app.components,
381
- process: scores.process.components
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
- return result;
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 handleKbFirstPhase(cwd, args) {
389
- const phase = args.phase;
390
- const phaseInfo = PHASES[phase];
515
+ async function handlePlan(cwd, args) {
516
+ const assessmentPath = join(cwd, '.ruvector', 'assessment.json');
391
517
 
392
- if (!phaseInfo) {
518
+ if (!existsSync(assessmentPath)) {
393
519
  return {
394
- error: `Unknown phase: ${phase}`,
395
- validPhases: Object.entries(PHASES).map(([k, v]) => ({ phase: parseFloat(k), name: v.name }))
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
- // Check for baseline
400
- const baselinePath = join(cwd, '.ruvector', 'baseline.json');
401
- let baseline = null;
402
- let baselineWarning = null;
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
- if (existsSync(baselinePath)) {
405
- try {
406
- baseline = JSON.parse(readFileSync(baselinePath, 'utf-8'));
407
- const baselineAge = (Date.now() - new Date(baseline.timestamp).getTime()) / (1000 * 60);
408
- if (baselineAge > 60) {
409
- baselineWarning = `Baseline is ${Math.round(baselineAge)} minutes old. Consider running kb_first_assess for fresh baseline.`;
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
- } catch {}
570
+ }
412
571
  }
413
572
 
414
- // Phase-specific guidance
415
- const phaseGuidance = {
416
- 0: ['Document project scope', 'Identify domain complexity', 'Assess KB-First suitability', 'Estimate resources', 'Make go/no-go decision'],
417
- 1: ['Map domain concepts', 'Design taxonomy', 'Define relationships', 'Plan query patterns', 'Review with stakeholders'],
418
- 1.5: ['Install enforcement hooks', 'Configure hook behavior', 'Train on project patterns', 'Verify hooks work'],
419
- 2: ['Create database tables', 'Add vector columns', 'Design indexes', 'Write migration scripts'],
420
- 3: ['Collect domain content', 'Process and clean data', 'Generate embeddings', 'Import to KB', 'Validate entries'],
421
- 4: ['Analyze KB coverage', 'Calculate quality score', 'Identify gaps', 'Prioritize fixes', 'Create remediation plan'],
422
- 5: ['Build search API', 'Implement code generation', 'Add citation system', 'Enable gap logging'],
423
- 6: ['Write unit tests', 'Create integration tests', 'Test KB accuracy', 'Performance tests', 'Edge case testing'],
424
- 7: ['Optimize queries', 'Tune indexes', 'Add caching', 'Run benchmarks'],
425
- 7.5: ['Verify test coverage ≥80%', 'Run E2E suite', 'Load testing', 'Build regression suite'],
426
- 8: ['Run code scan', 'Check imports', 'Verify source returns', 'Test startup', 'Check fallbacks', 'Validate attribution', 'Test confidence', 'Review gap logs'],
427
- 9: ['Audit dependencies', 'Check OWASP Top 10', 'Test SQL injection', 'Review auth', 'Audit secrets', 'Secure APIs'],
428
- 10: ['Write README', 'Document API', 'Schema documentation', 'Architecture docs', 'Operator guide'],
429
- 11: ['Setup infrastructure', 'Configure environments', 'Build CI/CD', 'Run migrations', 'Setup monitoring', 'Go live'],
430
- 11.5: ['Setup OpenTelemetry', 'Build KB dashboard', 'Configure alerts', 'Write runbooks'],
431
- 12: ['Define gap triage', 'Setup expert review', 'Document KB updates', 'Version control', 'A/B testing']
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
- phase,
436
- name: phaseInfo.name,
437
- gate: phaseInfo.gate,
438
- baseline: baseline ? {
439
- score: baseline.total,
440
- grade: baseline.grade,
441
- timestamp: baseline.timestamp
442
- } : null,
443
- baselineWarning,
444
- tasks: phaseGuidance[phase] || [],
445
- reminder: '⚠️ IMPORTANT: Run kb_first_delta when phase work is complete to measure improvement',
446
- gateRequirement: `Gate "${phaseInfo.gate}" must be satisfied to proceed`
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 handleKbFirstDelta(cwd, args) {
451
- const baselinePath = join(cwd, '.ruvector', 'baseline.json');
629
+ async function handleConfirm(cwd, args) {
630
+ const planPath = join(cwd, '.ruvector', 'plan.json');
452
631
 
453
- if (!existsSync(baselinePath)) {
632
+ if (!existsSync(planPath)) {
454
633
  return {
455
- error: 'NO_BASELINE',
456
- message: 'No baseline found. Run kb_first_assess first to establish baseline.',
457
- action: 'Run kb_first_assess with saveBaseline=true'
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
- let baseline;
462
- try {
463
- baseline = JSON.parse(readFileSync(baselinePath, 'utf-8'));
464
- } catch {
465
- return { error: 'CORRUPT_BASELINE', message: 'Baseline file is corrupt. Run kb_first_assess again.' };
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 current = calculateScores(cwd);
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
- const delta = {
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 verdict = delta.total >= 0 ? 'PASS' : 'FAIL';
478
- const canProceed = delta.total >= 0;
655
+ const firstTask = plan.enhancements[0];
479
656
 
480
- const result = {
481
- verdict,
482
- canProceed,
483
- baseline: {
484
- score: baseline.total,
485
- grade: baseline.grade,
486
- timestamp: baseline.timestamp
487
- },
488
- current: {
489
- score: current.total,
490
- grade: current.grade,
491
- timestamp: current.timestamp
492
- },
493
- delta: {
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
- if (!canProceed) {
502
- result.blockReason = `Score dropped by ${Math.abs(delta.total)} points. You CANNOT proceed until score improves.`;
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 (args.showBreakdown !== false) {
509
- result.componentChanges = {
510
- kb: {
511
- before: baseline.kb.components,
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
- return result;
526
- }
684
+ const plan = JSON.parse(readFileSync(planPath, 'utf-8'));
527
685
 
528
- async function handleKbFirstGate(cwd, args) {
529
- const phase = args.phase;
530
- const phaseInfo = PHASES[phase];
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 (!phaseInfo) {
533
- return { error: `Unknown phase: ${phase}` };
694
+ // Mark current task complete if requested
695
+ if (args.taskComplete && plan.currentTaskIndex < plan.enhancements.length) {
696
+ plan.enhancements[plan.currentTaskIndex].completed = true;
697
+ plan.enhancements[plan.currentTaskIndex].completedAt = new Date().toISOString();
698
+ plan.currentTaskIndex++;
699
+ writeFileSync(planPath, JSON.stringify(plan, null, 2));
534
700
  }
535
701
 
536
- // Check baseline exists
537
- const baselinePath = join(cwd, '.ruvector', 'baseline.json');
538
- if (!existsSync(baselinePath)) {
702
+ // Check if all done
703
+ if (plan.currentTaskIndex >= plan.enhancements.length) {
539
704
  return {
540
- canProceed: false,
541
- blockReason: 'GATE_BLOCKED: No baseline established. Run kb_first_assess first.',
542
- phase,
543
- phaseName: phaseInfo.name
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
- // Check delta
548
- let baseline;
549
- try {
550
- baseline = JSON.parse(readFileSync(baselinePath, 'utf-8'));
551
- } catch {
552
- return {
553
- canProceed: false,
554
- blockReason: 'GATE_BLOCKED: Corrupt baseline. Run kb_first_assess again.',
555
- phase,
556
- phaseName: phaseInfo.name
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
- const current = calculateScores(cwd);
561
- const delta = current.total - baseline.total;
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 (delta < 0) {
764
+ if (!existsSync(planPath) || !existsSync(assessmentPath)) {
564
765
  return {
565
- canProceed: false,
566
- blockReason: `GATE_BLOCKED: Score regression detected (${delta} points). Fix issues before proceeding.`,
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
- // Check gate-specific conditions
576
- const configPath = join(cwd, '.ruvector', 'config.json');
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
- // Phase-specific gate checks
585
- let gateConditionMet = false;
586
- let gateMessage = '';
587
-
588
- switch (phaseInfo.gate) {
589
- case 'kb_score_50':
590
- gateConditionMet = current.kb.total >= 20; // 50% of 40
591
- gateMessage = gateConditionMet ? 'KB score ≥50%' : `KB score ${current.kb.total}/40 < 50%`;
592
- break;
593
- case 'kb_score_80':
594
- gateConditionMet = current.kb.total >= 32; // 80% of 40
595
- gateMessage = gateConditionMet ? 'KB score ≥80%' : `KB score ${current.kb.total}/40 < 80%`;
596
- break;
597
- case 'coverage_80':
598
- gateConditionMet = current.app.components.testCoverage >= 8; // 80% of 10
599
- gateMessage = gateConditionMet ? 'Test coverage ≥80%' : `Test coverage ${current.app.components.testCoverage}/10 < 80%`;
600
- break;
601
- default:
602
- // Default: pass if delta is non-negative
603
- gateConditionMet = true;
604
- gateMessage = 'Gate condition satisfied (positive delta)';
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
- if (!gateConditionMet) {
608
- return {
609
- canProceed: false,
610
- blockReason: `GATE_BLOCKED: ${gateMessage}`,
611
- phase,
612
- phaseName: phaseInfo.name,
613
- gate: phaseInfo.gate
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
- // Mark phase as completed
618
- if (!config.phases.completed.includes(parseFloat(phase))) {
619
- config.phases.completed.push(parseFloat(phase));
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
- // Determine next phase
624
- const phaseOrder = [0, 1, 1.5, 2, 3, 4, 5, 6, 7, 7.5, 8, 9, 10, 11, 11.5, 12];
625
- const currentIdx = phaseOrder.indexOf(parseFloat(phase));
626
- const nextPhase = currentIdx < phaseOrder.length - 1 ? phaseOrder[currentIdx + 1] : null;
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
- if (nextPhase !== null) {
629
- config.phases.current = nextPhase;
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
- // Save config
633
- writeFileSync(configPath, JSON.stringify(config, null, 2));
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
- // Save current scores as new baseline for next phase
636
- writeFileSync(baselinePath, JSON.stringify(current, null, 2));
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
- canProceed: true,
640
- phase,
641
- phaseName: phaseInfo.name,
642
- gateStatus: 'PASSED',
643
- gateMessage,
644
- scoreImprovement: delta > 0 ? `+${delta}` : '0',
645
- nextPhase,
646
- nextPhaseName: nextPhase !== null ? PHASES[nextPhase]?.name : 'PROJECT COMPLETE',
647
- action: nextPhase !== null
648
- ? `Run kb_first_assess to establish baseline for Phase ${nextPhase}: ${PHASES[nextPhase]?.name}`
649
- : 'All phases complete! Project ready for production.'
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 handleKbFirstAssess(cwd, args);
662
- case 'kb_first_phase':
663
- return await handleKbFirstPhase(cwd, args);
664
- case 'kb_first_delta':
665
- return await handleKbFirstDelta(cwd, args);
666
- case 'kb_first_gate':
667
- return await handleKbFirstGate(cwd, args);
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 (stdio mode)
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: 4 | Phases: 15');
719
- console.error('Philosophy: Scoring IS enforcement. No shortcuts.');
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');