ruvnet-kb-first 6.1.0 → 6.3.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/SKILL.md +44 -6
- package/bin/kb-first.js +9 -6
- package/package.json +5 -5
- package/src/commands/dashboard.js +359 -0
- package/src/commands/init.js +138 -9
- package/src/commands/status.js +59 -5
- package/src/mcp-server.js +418 -12
package/SKILL.md
CHANGED
|
@@ -1,13 +1,51 @@
|
|
|
1
|
-
Updated: 2026-01-02 10:
|
|
1
|
+
Updated: 2026-01-02 10:35:00 EST | Version 6.2.0
|
|
2
2
|
Created: 2026-01-01 15:00:00 EST
|
|
3
3
|
|
|
4
|
-
# RuvNet KB-First Application Builder v6.
|
|
4
|
+
# RuvNet KB-First Application Builder v6.2
|
|
5
5
|
|
|
6
|
-
## Score-Driven Architecture: Scoring IS Enforcement
|
|
6
|
+
## Score-Driven Architecture: Scoring IS Enforcement + UX Excellence
|
|
7
7
|
|
|
8
|
-
**Version:** 6.
|
|
8
|
+
**Version:** 6.2.0
|
|
9
9
|
**NPM Package:** `ruvnet-kb-first`
|
|
10
|
-
**Philosophy:** Every operation requires baseline scoring. Every change shows delta. Negative delta BLOCKS progress. No shortcuts.
|
|
10
|
+
**Philosophy:** Every operation requires baseline scoring. Every change shows delta. Negative delta BLOCKS progress. No shortcuts. **NEW:** Applications must be excellent, not just functional.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## What's New in v6.2.0 - UX Excellence
|
|
15
|
+
|
|
16
|
+
| Feature | Description |
|
|
17
|
+
|---------|-------------|
|
|
18
|
+
| **UX Quality Dimension** | 7th KB dimension: Visual design, emotional appeal, user flow |
|
|
19
|
+
| **Phase 12: UX Quality Review** | Playwright-based end-user perspective audit |
|
|
20
|
+
| **`kb_first_ux_review` Tool** | Captures screenshots, checks versioning, critical review |
|
|
21
|
+
| **Version Display Check** | Verifies version in header/footer (major.minor.patch) |
|
|
22
|
+
| **Cache-Busting Check** | Ensures users see latest version, not cached content |
|
|
23
|
+
| **Critical Review Questions** | How good? How could it be better? Where falling down? What would excellent look like? |
|
|
24
|
+
| **Playwright Auto-Install** | Offers to install Playwright if not present |
|
|
25
|
+
|
|
26
|
+
### The 6 MCP Tools (v6.2)
|
|
27
|
+
|
|
28
|
+
| Tool | Purpose | When to Use |
|
|
29
|
+
|------|---------|-------------|
|
|
30
|
+
| `kb_first_assess` | Score ALL dimensions (7 KB + 13 Phases) | **FIRST** - before any work |
|
|
31
|
+
| `kb_first_plan` | Generate enhancement plan with predictions | After assess, shows gaps |
|
|
32
|
+
| `kb_first_confirm` | User confirms readiness to proceed | Before execution |
|
|
33
|
+
| `kb_first_execute` | Execute plan phase by phase | After confirmation |
|
|
34
|
+
| `kb_first_verify` | Compare predicted vs actual, recursive until 98+ | After execution |
|
|
35
|
+
| `kb_first_ux_review` | Playwright-based visual quality audit | **NEW** - for UX excellence |
|
|
36
|
+
|
|
37
|
+
### UX Quality Criteria (Scored in UX Review)
|
|
38
|
+
|
|
39
|
+
| Criterion | Weight | Checks |
|
|
40
|
+
|-----------|--------|--------|
|
|
41
|
+
| Version Display | 15% | Header/footer shows major.minor.patch |
|
|
42
|
+
| Cache Management | 10% | Version change detection, user notification |
|
|
43
|
+
| Visual Design Excellence | 20% | Typography, color, spacing, not generic AI look |
|
|
44
|
+
| Emotional Appeal | 15% | Creates confidence, celebrates success, softens errors |
|
|
45
|
+
| Loading States | 10% | Skeleton loaders, progress indicators, graceful |
|
|
46
|
+
| Error Handling UX | 10% | Clear messages, actionable, no technical jargon |
|
|
47
|
+
| User Flow | 10% | Intuitive navigation, clear CTAs, minimal friction |
|
|
48
|
+
| Accessibility | 10% | Keyboard nav, screen reader, contrast, focus |
|
|
11
49
|
|
|
12
50
|
---
|
|
13
51
|
|
|
@@ -22,7 +60,7 @@ Created: 2026-01-01 15:00:00 EST
|
|
|
22
60
|
| **Baseline Requirement** | Cannot start work without establishing baseline |
|
|
23
61
|
| **Automatic Refresh** | Baseline resets after each gate passage |
|
|
24
62
|
|
|
25
|
-
### The 4 MCP Tools
|
|
63
|
+
### The Original 4 MCP Tools (v6.0)
|
|
26
64
|
|
|
27
65
|
| Tool | Purpose | When to Use |
|
|
28
66
|
|------|---------|-------------|
|
package/bin/kb-first.js
CHANGED
|
@@ -58,6 +58,9 @@ program
|
|
|
58
58
|
.option('-f, --force', 'Overwrite existing configuration')
|
|
59
59
|
.option('-t, --template <type>', 'Template type (basic, api, fullstack)', 'basic')
|
|
60
60
|
.option('--no-hooks', 'Skip hook installation')
|
|
61
|
+
.option('--kb <schema>', 'Connect to existing KB schema (e.g., ask_ruvnet)')
|
|
62
|
+
.option('--kb-host <host>', 'KB database host', 'localhost')
|
|
63
|
+
.option('--kb-port <port>', 'KB database port', '5435')
|
|
61
64
|
.action(initCommand);
|
|
62
65
|
|
|
63
66
|
// Score command
|
|
@@ -113,11 +116,11 @@ program
|
|
|
113
116
|
await startMCPServer(options);
|
|
114
117
|
});
|
|
115
118
|
|
|
116
|
-
//
|
|
117
|
-
program.parse();
|
|
118
|
-
|
|
119
|
-
// Show help if no command provided
|
|
119
|
+
// Show dashboard if no command provided (before parsing)
|
|
120
120
|
if (!process.argv.slice(2).length) {
|
|
121
|
-
|
|
122
|
-
|
|
121
|
+
const { dashboardCommand } = await import('../src/commands/dashboard.js');
|
|
122
|
+
await dashboardCommand();
|
|
123
|
+
} else {
|
|
124
|
+
// Parse arguments
|
|
125
|
+
program.parse();
|
|
123
126
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ruvnet-kb-first",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.3.0",
|
|
4
4
|
"description": "RuvNet KB-First Application Builder - Build intelligent applications on expert knowledge",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -56,19 +56,19 @@
|
|
|
56
56
|
"glob": "^10.3.0",
|
|
57
57
|
"inquirer": "^9.2.0",
|
|
58
58
|
"ora": "^8.0.0",
|
|
59
|
-
"pg": "^8.
|
|
59
|
+
"pg": "^8.16.3",
|
|
60
60
|
"ruv-swarm": "^1.0.20",
|
|
61
61
|
"ruvector": "^0.1.82"
|
|
62
62
|
},
|
|
63
63
|
"devDependencies": {
|
|
64
|
+
"@types/jest": "^29.5.11",
|
|
64
65
|
"@types/node": "^20.10.0",
|
|
65
66
|
"@types/pg": "^8.10.9",
|
|
66
|
-
"typescript": "^5.3.0",
|
|
67
|
-
"eslint": "^8.55.0",
|
|
68
67
|
"@typescript-eslint/eslint-plugin": "^6.13.0",
|
|
69
68
|
"@typescript-eslint/parser": "^6.13.0",
|
|
69
|
+
"eslint": "^8.55.0",
|
|
70
70
|
"jest": "^29.7.0",
|
|
71
|
-
"
|
|
71
|
+
"typescript": "^5.3.0"
|
|
72
72
|
},
|
|
73
73
|
"engines": {
|
|
74
74
|
"node": ">=18.0.0"
|
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RuvNet-KB-First Dashboard
|
|
3
|
+
*
|
|
4
|
+
* Shows comprehensive status when running `ruvnet-kb-first` with no arguments.
|
|
5
|
+
* Displays KB metrics, application scores, and actionable recommendations.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import chalk from 'chalk';
|
|
9
|
+
import { existsSync, readFileSync } from 'fs';
|
|
10
|
+
import { join } from 'path';
|
|
11
|
+
import { globSync } from 'glob';
|
|
12
|
+
|
|
13
|
+
export async function dashboardCommand() {
|
|
14
|
+
const cwd = process.cwd();
|
|
15
|
+
const projectName = cwd.split('/').pop();
|
|
16
|
+
|
|
17
|
+
// Banner
|
|
18
|
+
console.log('');
|
|
19
|
+
console.log(chalk.cyan('╔════════════════════════════════════════════════════════════════════════╗'));
|
|
20
|
+
console.log(chalk.cyan('║') + chalk.bold.white(' RuvNet-KB-First Dashboard ') + chalk.cyan('║'));
|
|
21
|
+
console.log(chalk.cyan('║') + chalk.gray(' Build intelligent applications on expert knowledge ') + chalk.cyan('║'));
|
|
22
|
+
console.log(chalk.cyan('╚════════════════════════════════════════════════════════════════════════╝'));
|
|
23
|
+
console.log('');
|
|
24
|
+
|
|
25
|
+
// Check if initialized
|
|
26
|
+
const configPath = join(cwd, '.ruvector', 'config.json');
|
|
27
|
+
if (!existsSync(configPath)) {
|
|
28
|
+
console.log(chalk.yellow(' ⚠ Not initialized in this directory'));
|
|
29
|
+
console.log('');
|
|
30
|
+
console.log(chalk.white(' Quick Start:'));
|
|
31
|
+
console.log(chalk.cyan(' ruvnet-kb-first init --kb ask_ruvnet'));
|
|
32
|
+
console.log('');
|
|
33
|
+
console.log(chalk.gray(' This will:'));
|
|
34
|
+
console.log(chalk.gray(' • Connect to the RuvNet Knowledge Base (230K+ entries)'));
|
|
35
|
+
console.log(chalk.gray(' • Set up hooks to enforce KB-first development'));
|
|
36
|
+
console.log(chalk.gray(' • Enable comprehensive scoring for your application'));
|
|
37
|
+
console.log('');
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const config = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
42
|
+
|
|
43
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
44
|
+
// SECTION 1: KNOWLEDGE BASE STATUS
|
|
45
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
46
|
+
console.log(chalk.white.bold(' 📚 KNOWLEDGE BASE'));
|
|
47
|
+
console.log(chalk.gray(' ────────────────────────────────────────────────────────────────────'));
|
|
48
|
+
|
|
49
|
+
if (config.knowledgeBase?.connected) {
|
|
50
|
+
let kbStats = {
|
|
51
|
+
entries: config.knowledgeBase.entries,
|
|
52
|
+
isLive: false,
|
|
53
|
+
categories: 0,
|
|
54
|
+
avgEmbeddingSize: 0
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// Try to get live stats
|
|
58
|
+
try {
|
|
59
|
+
const pg = await import('pg');
|
|
60
|
+
const client = new pg.default.Client({
|
|
61
|
+
host: config.knowledgeBase.host,
|
|
62
|
+
port: config.knowledgeBase.port,
|
|
63
|
+
database: 'postgres',
|
|
64
|
+
user: 'postgres',
|
|
65
|
+
password: 'guruKB2025'
|
|
66
|
+
});
|
|
67
|
+
await client.connect();
|
|
68
|
+
|
|
69
|
+
// Entry count
|
|
70
|
+
const countResult = await client.query(
|
|
71
|
+
`SELECT COUNT(*) as count FROM ${config.knowledgeBase.schema}.architecture_docs`
|
|
72
|
+
);
|
|
73
|
+
kbStats.entries = parseInt(countResult.rows[0].count);
|
|
74
|
+
kbStats.isLive = true;
|
|
75
|
+
|
|
76
|
+
// Get category count (if source column exists)
|
|
77
|
+
try {
|
|
78
|
+
const catResult = await client.query(
|
|
79
|
+
`SELECT COUNT(DISTINCT source) as cats FROM ${config.knowledgeBase.schema}.architecture_docs`
|
|
80
|
+
);
|
|
81
|
+
kbStats.categories = parseInt(catResult.rows[0].cats);
|
|
82
|
+
} catch (e) {
|
|
83
|
+
// Ignore if source column doesn't exist
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
await client.end();
|
|
87
|
+
} catch (e) {
|
|
88
|
+
// Use cached values
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const statusIcon = kbStats.isLive ? chalk.green('●') : chalk.yellow('○');
|
|
92
|
+
const statusText = kbStats.isLive ? chalk.green('CONNECTED') : chalk.yellow('CACHED');
|
|
93
|
+
|
|
94
|
+
console.log(` ${statusIcon} Status: ${statusText}`);
|
|
95
|
+
console.log(` Schema: ${chalk.cyan(config.knowledgeBase.schema)}`);
|
|
96
|
+
console.log(` Entries: ${chalk.cyan(kbStats.entries.toLocaleString())}`);
|
|
97
|
+
if (kbStats.categories > 0) {
|
|
98
|
+
console.log(` Sources: ${chalk.cyan(kbStats.categories)}`);
|
|
99
|
+
}
|
|
100
|
+
console.log(` Host: ${chalk.gray(config.knowledgeBase.host + ':' + config.knowledgeBase.port)}`);
|
|
101
|
+
|
|
102
|
+
// KB Score (out of 100)
|
|
103
|
+
const kbScore = calculateKBScore(kbStats);
|
|
104
|
+
console.log('');
|
|
105
|
+
console.log(` ${chalk.white('KB Health Score:')} ${renderScoreBar(kbScore)} ${colorScore(kbScore)}/100`);
|
|
106
|
+
|
|
107
|
+
} else {
|
|
108
|
+
console.log(chalk.red(' ○ Status: NOT CONNECTED'));
|
|
109
|
+
console.log('');
|
|
110
|
+
console.log(chalk.yellow(' Run: ruvnet-kb-first init --kb ask_ruvnet'));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
console.log('');
|
|
114
|
+
|
|
115
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
116
|
+
// SECTION 2: APPLICATION SCORES
|
|
117
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
118
|
+
console.log(chalk.white.bold(' 📊 APPLICATION SCORES'));
|
|
119
|
+
console.log(chalk.gray(' ────────────────────────────────────────────────────────────────────'));
|
|
120
|
+
|
|
121
|
+
const scores = await calculateAllScores(cwd, config);
|
|
122
|
+
|
|
123
|
+
// Display each score category
|
|
124
|
+
for (const [category, data] of Object.entries(scores)) {
|
|
125
|
+
const label = formatLabel(category).padEnd(18);
|
|
126
|
+
const bar = renderScoreBar(data.percentage);
|
|
127
|
+
const scoreText = colorScore(data.percentage);
|
|
128
|
+
console.log(` ${label} ${bar} ${scoreText.padStart(4)}/100 ${chalk.gray(data.summary)}`);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Total Score
|
|
132
|
+
const totalScore = Math.round(
|
|
133
|
+
Object.values(scores).reduce((sum, s) => sum + s.percentage, 0) / Object.keys(scores).length
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
console.log('');
|
|
137
|
+
console.log(chalk.gray(' ────────────────────────────────────────────────────────────────────'));
|
|
138
|
+
const totalBar = renderScoreBar(totalScore);
|
|
139
|
+
console.log(` ${chalk.bold('OVERALL SCORE')} ${totalBar} ${colorScore(totalScore).padStart(4)}/100 ${getGrade(totalScore)}`);
|
|
140
|
+
console.log('');
|
|
141
|
+
|
|
142
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
143
|
+
// SECTION 3: PROJECT INFO
|
|
144
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
145
|
+
console.log(chalk.white.bold(' 📁 PROJECT'));
|
|
146
|
+
console.log(chalk.gray(' ────────────────────────────────────────────────────────────────────'));
|
|
147
|
+
console.log(` Name: ${chalk.cyan(projectName)}`);
|
|
148
|
+
console.log(` Namespace: ${chalk.cyan(config.kbFirst?.namespace || 'not set')}`);
|
|
149
|
+
console.log(` Phase: ${chalk.cyan(config.phases?.current || 0)}/11`);
|
|
150
|
+
console.log(` Hooks: ${config.hooks?.enabled ? chalk.green('Enabled') : chalk.red('Disabled')}`);
|
|
151
|
+
console.log('');
|
|
152
|
+
|
|
153
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
154
|
+
// SECTION 4: RECOMMENDATIONS
|
|
155
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
156
|
+
const recommendations = getRecommendations(scores, config);
|
|
157
|
+
|
|
158
|
+
if (recommendations.length > 0) {
|
|
159
|
+
console.log(chalk.white.bold(' 💡 RECOMMENDATIONS'));
|
|
160
|
+
console.log(chalk.gray(' ────────────────────────────────────────────────────────────────────'));
|
|
161
|
+
for (const rec of recommendations.slice(0, 5)) {
|
|
162
|
+
console.log(chalk.yellow(` → ${rec}`));
|
|
163
|
+
}
|
|
164
|
+
console.log('');
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
168
|
+
// SECTION 5: COMMANDS
|
|
169
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
170
|
+
console.log(chalk.white.bold(' ⌨️ COMMANDS'));
|
|
171
|
+
console.log(chalk.gray(' ────────────────────────────────────────────────────────────────────'));
|
|
172
|
+
console.log(chalk.cyan(' ruvnet-kb-first score --detailed') + chalk.gray(' Full score breakdown'));
|
|
173
|
+
console.log(chalk.cyan(' ruvnet-kb-first status --detailed') + chalk.gray(' Detailed project status'));
|
|
174
|
+
console.log(chalk.cyan(' ruvnet-kb-first verify') + chalk.gray(' Run verification checks'));
|
|
175
|
+
console.log(chalk.cyan(' ruvnet-kb-first phase <n>') + chalk.gray(' Execute build phase'));
|
|
176
|
+
console.log('');
|
|
177
|
+
|
|
178
|
+
// Final prompt
|
|
179
|
+
if (config.knowledgeBase?.connected) {
|
|
180
|
+
console.log(chalk.gray(' Ask Claude: "Review this app using RuvNet-KB-First and recommend improvements"'));
|
|
181
|
+
}
|
|
182
|
+
console.log('');
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function calculateKBScore(stats) {
|
|
186
|
+
let score = 0;
|
|
187
|
+
|
|
188
|
+
// Connection (30 points)
|
|
189
|
+
if (stats.isLive) score += 30;
|
|
190
|
+
else score += 15;
|
|
191
|
+
|
|
192
|
+
// Entry count (50 points)
|
|
193
|
+
if (stats.entries >= 100000) score += 50;
|
|
194
|
+
else if (stats.entries >= 50000) score += 40;
|
|
195
|
+
else if (stats.entries >= 10000) score += 30;
|
|
196
|
+
else if (stats.entries >= 1000) score += 20;
|
|
197
|
+
else score += 10;
|
|
198
|
+
|
|
199
|
+
// Categories (20 points)
|
|
200
|
+
if (stats.categories >= 50) score += 20;
|
|
201
|
+
else if (stats.categories >= 20) score += 15;
|
|
202
|
+
else if (stats.categories >= 10) score += 10;
|
|
203
|
+
else score += 5;
|
|
204
|
+
|
|
205
|
+
return Math.min(score, 100);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
async function calculateAllScores(cwd, config) {
|
|
209
|
+
const scores = {};
|
|
210
|
+
|
|
211
|
+
// 1. KB Coverage
|
|
212
|
+
const srcDir = join(cwd, 'src');
|
|
213
|
+
let kbCoverage = { percentage: 100, summary: 'No code yet' };
|
|
214
|
+
if (existsSync(srcDir)) {
|
|
215
|
+
const codeFiles = globSync('**/*.{ts,tsx,js,jsx,py}', { cwd: srcDir });
|
|
216
|
+
if (codeFiles.length > 0) {
|
|
217
|
+
let withCitation = 0;
|
|
218
|
+
for (const file of codeFiles) {
|
|
219
|
+
const content = readFileSync(join(srcDir, file), 'utf-8');
|
|
220
|
+
if (content.includes('KB-Generated:') || content.includes('Sources:')) {
|
|
221
|
+
withCitation++;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
const pct = Math.round((withCitation / codeFiles.length) * 100);
|
|
225
|
+
kbCoverage = { percentage: pct, summary: `${withCitation}/${codeFiles.length} files` };
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
scores.kbCoverage = kbCoverage;
|
|
229
|
+
|
|
230
|
+
// 2. Phase Progress
|
|
231
|
+
const completed = config.phases?.completed?.length || 0;
|
|
232
|
+
const phasePct = Math.round((completed / 12) * 100);
|
|
233
|
+
scores.phaseProgress = { percentage: phasePct, summary: `${completed}/12 complete` };
|
|
234
|
+
|
|
235
|
+
// 3. Hook Compliance
|
|
236
|
+
let hookPct = 0;
|
|
237
|
+
if (config.hooks?.enabled) {
|
|
238
|
+
const hooksDir = join(cwd, '.ruvector', 'hooks');
|
|
239
|
+
let found = 0;
|
|
240
|
+
if (existsSync(join(hooksDir, 'pre-tool-use.py'))) found++;
|
|
241
|
+
if (existsSync(join(hooksDir, 'post-tool-use.py'))) found++;
|
|
242
|
+
hookPct = found === 2 ? 100 : (found === 1 ? 50 : 0);
|
|
243
|
+
}
|
|
244
|
+
scores.hooks = { percentage: hookPct, summary: hookPct === 100 ? 'All installed' : 'Incomplete' };
|
|
245
|
+
|
|
246
|
+
// 4. KB Gaps
|
|
247
|
+
const gapPath = join(cwd, '.ruvector', 'gaps.jsonl');
|
|
248
|
+
let gapPct = 100;
|
|
249
|
+
let gapSummary = 'No gaps';
|
|
250
|
+
if (existsSync(gapPath)) {
|
|
251
|
+
const content = readFileSync(gapPath, 'utf-8').trim();
|
|
252
|
+
if (content) {
|
|
253
|
+
const gaps = content.split('\n').length;
|
|
254
|
+
gapPct = Math.max(0, 100 - (gaps * 10));
|
|
255
|
+
gapSummary = `${gaps} unresolved`;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
scores.kbGaps = { percentage: gapPct, summary: gapSummary };
|
|
259
|
+
|
|
260
|
+
// 5. Documentation
|
|
261
|
+
let docScore = 0;
|
|
262
|
+
if (existsSync(join(cwd, 'README.md'))) docScore += 40;
|
|
263
|
+
if (existsSync(join(cwd, 'docs', 'api.md'))) docScore += 30;
|
|
264
|
+
if (existsSync(join(cwd, 'docs', 'architecture.md'))) docScore += 30;
|
|
265
|
+
scores.documentation = { percentage: docScore, summary: docScore === 100 ? 'Complete' : 'Incomplete' };
|
|
266
|
+
|
|
267
|
+
// 6. Security
|
|
268
|
+
let secScore = 100;
|
|
269
|
+
const gitignore = join(cwd, '.gitignore');
|
|
270
|
+
if (!existsSync(gitignore)) {
|
|
271
|
+
secScore -= 30;
|
|
272
|
+
} else {
|
|
273
|
+
const content = readFileSync(gitignore, 'utf-8');
|
|
274
|
+
if (!content.includes('.env')) secScore -= 20;
|
|
275
|
+
}
|
|
276
|
+
if (!existsSync(join(cwd, 'package-lock.json')) && existsSync(join(cwd, 'package.json'))) {
|
|
277
|
+
secScore -= 20;
|
|
278
|
+
}
|
|
279
|
+
scores.security = { percentage: Math.max(0, secScore), summary: secScore === 100 ? 'Passed' : 'Issues found' };
|
|
280
|
+
|
|
281
|
+
return scores;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function renderScoreBar(percentage) {
|
|
285
|
+
const filled = Math.round(percentage / 5);
|
|
286
|
+
const empty = 20 - filled;
|
|
287
|
+
const color = percentage >= 80 ? chalk.green :
|
|
288
|
+
percentage >= 60 ? chalk.yellow :
|
|
289
|
+
percentage >= 40 ? chalk.hex('#FFA500') :
|
|
290
|
+
chalk.red;
|
|
291
|
+
return color('█'.repeat(filled)) + chalk.gray('░'.repeat(empty));
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function colorScore(score) {
|
|
295
|
+
const color = score >= 80 ? chalk.green :
|
|
296
|
+
score >= 60 ? chalk.yellow :
|
|
297
|
+
score >= 40 ? chalk.hex('#FFA500') :
|
|
298
|
+
chalk.red;
|
|
299
|
+
return color(score.toString());
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function formatLabel(key) {
|
|
303
|
+
const labels = {
|
|
304
|
+
kbCoverage: 'KB Coverage',
|
|
305
|
+
phaseProgress: 'Phase Progress',
|
|
306
|
+
hooks: 'Hooks',
|
|
307
|
+
kbGaps: 'KB Gaps',
|
|
308
|
+
documentation: 'Documentation',
|
|
309
|
+
security: 'Security'
|
|
310
|
+
};
|
|
311
|
+
return labels[key] || key;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function getGrade(score) {
|
|
315
|
+
if (score >= 95) return chalk.green.bold('A+');
|
|
316
|
+
if (score >= 90) return chalk.green.bold('A');
|
|
317
|
+
if (score >= 85) return chalk.green('A-');
|
|
318
|
+
if (score >= 80) return chalk.yellow.bold('B+');
|
|
319
|
+
if (score >= 75) return chalk.yellow('B');
|
|
320
|
+
if (score >= 70) return chalk.yellow('B-');
|
|
321
|
+
if (score >= 65) return chalk.hex('#FFA500')('C+');
|
|
322
|
+
if (score >= 60) return chalk.hex('#FFA500')('C');
|
|
323
|
+
if (score >= 55) return chalk.hex('#FFA500')('C-');
|
|
324
|
+
return chalk.red.bold('F');
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function getRecommendations(scores, config) {
|
|
328
|
+
const recs = [];
|
|
329
|
+
|
|
330
|
+
if (!config.knowledgeBase?.connected) {
|
|
331
|
+
recs.push('Connect to KB: ruvnet-kb-first init --kb ask_ruvnet --force');
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (scores.kbCoverage.percentage < 80) {
|
|
335
|
+
recs.push('Add KB citations to code files (use kb_code_gen before writing)');
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if (scores.phaseProgress.percentage < 50) {
|
|
339
|
+
recs.push(`Complete build phases: ruvnet-kb-first phase ${config.phases?.current || 0}`);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
if (scores.hooks.percentage < 100) {
|
|
343
|
+
recs.push('Install enforcement hooks: ruvnet-kb-first hooks --install');
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if (scores.kbGaps.percentage < 100) {
|
|
347
|
+
recs.push('Resolve KB gaps logged in .ruvector/gaps.jsonl');
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
if (scores.documentation.percentage < 100) {
|
|
351
|
+
recs.push('Add documentation: README.md, docs/api.md, docs/architecture.md');
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
if (scores.security.percentage < 100) {
|
|
355
|
+
recs.push('Fix security issues: ruvnet-kb-first verify --phase=9');
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
return recs;
|
|
359
|
+
}
|
package/src/commands/init.js
CHANGED
|
@@ -14,6 +14,22 @@ import inquirer from 'inquirer';
|
|
|
14
14
|
const __filename = fileURLToPath(import.meta.url);
|
|
15
15
|
const __dirname = dirname(__filename);
|
|
16
16
|
|
|
17
|
+
// Known KB schemas with descriptions
|
|
18
|
+
const KNOWN_KB_SCHEMAS = {
|
|
19
|
+
'ask_ruvnet': {
|
|
20
|
+
description: 'RuvNet comprehensive knowledge base (230K+ entries)',
|
|
21
|
+
path: '/Users/stuartkerr/Code/Ask-Ruvnet'
|
|
22
|
+
},
|
|
23
|
+
'retirewell': {
|
|
24
|
+
description: 'Retirement planning knowledge base',
|
|
25
|
+
path: '/Users/stuartkerr/Code/RetireWell'
|
|
26
|
+
},
|
|
27
|
+
'presentermode': {
|
|
28
|
+
description: 'Presentation and public speaking knowledge',
|
|
29
|
+
path: null
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
17
33
|
// Project structure template
|
|
18
34
|
const PROJECT_STRUCTURE = {
|
|
19
35
|
directories: [
|
|
@@ -30,12 +46,21 @@ const PROJECT_STRUCTURE = {
|
|
|
30
46
|
files: {
|
|
31
47
|
'.ruvector/config.json': {
|
|
32
48
|
kbFirst: {
|
|
33
|
-
version: '
|
|
49
|
+
version: '6.2.0',
|
|
34
50
|
initialized: new Date().toISOString(),
|
|
35
51
|
namespace: '',
|
|
36
52
|
minConfidence: 0.5,
|
|
37
53
|
gapLogging: true
|
|
38
54
|
},
|
|
55
|
+
knowledgeBase: {
|
|
56
|
+
schema: null,
|
|
57
|
+
host: 'localhost',
|
|
58
|
+
port: 5435,
|
|
59
|
+
database: 'postgres',
|
|
60
|
+
table: 'architecture_docs',
|
|
61
|
+
entries: 0,
|
|
62
|
+
connected: false
|
|
63
|
+
},
|
|
39
64
|
hooks: {
|
|
40
65
|
enabled: true,
|
|
41
66
|
preToolUse: true,
|
|
@@ -55,7 +80,7 @@ export async function initCommand(options) {
|
|
|
55
80
|
const projectName = cwd.split('/').pop().toLowerCase().replace(/[^a-z0-9]/g, '_');
|
|
56
81
|
|
|
57
82
|
console.log('');
|
|
58
|
-
console.log(chalk.cyan('Initializing KB-First project structure...'));
|
|
83
|
+
console.log(chalk.cyan('Initializing RuvNet-KB-First project structure...'));
|
|
59
84
|
console.log('');
|
|
60
85
|
|
|
61
86
|
// Check if already initialized
|
|
@@ -67,14 +92,42 @@ export async function initCommand(options) {
|
|
|
67
92
|
}
|
|
68
93
|
|
|
69
94
|
// Interactive prompts if not forcing
|
|
70
|
-
let config =
|
|
95
|
+
let config = JSON.parse(JSON.stringify(PROJECT_STRUCTURE.files['.ruvector/config.json']));
|
|
96
|
+
config.kbFirst.initialized = new Date().toISOString();
|
|
97
|
+
|
|
98
|
+
// Handle --kb flag for connecting to existing KB
|
|
99
|
+
let kbSchema = options.kb;
|
|
100
|
+
const kbHost = options.kbHost || 'localhost';
|
|
101
|
+
const kbPort = options.kbPort || '5435';
|
|
102
|
+
|
|
103
|
+
if (!options.force && !kbSchema) {
|
|
104
|
+
// Show available KBs if not specified
|
|
105
|
+
const kbChoices = [
|
|
106
|
+
{ name: 'None - Create new KB for this project', value: null },
|
|
107
|
+
...Object.entries(KNOWN_KB_SCHEMAS).map(([schema, info]) => ({
|
|
108
|
+
name: `${schema} - ${info.description}`,
|
|
109
|
+
value: schema
|
|
110
|
+
})),
|
|
111
|
+
{ name: 'Other - Enter custom schema name', value: '__custom__' }
|
|
112
|
+
];
|
|
71
113
|
|
|
72
|
-
if (!options.force) {
|
|
73
114
|
const answers = await inquirer.prompt([
|
|
115
|
+
{
|
|
116
|
+
type: 'list',
|
|
117
|
+
name: 'kbSchema',
|
|
118
|
+
message: 'Connect to existing Knowledge Base?',
|
|
119
|
+
choices: kbChoices
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
type: 'input',
|
|
123
|
+
name: 'customSchema',
|
|
124
|
+
message: 'Enter KB schema name:',
|
|
125
|
+
when: (answers) => answers.kbSchema === '__custom__'
|
|
126
|
+
},
|
|
74
127
|
{
|
|
75
128
|
type: 'input',
|
|
76
129
|
name: 'namespace',
|
|
77
|
-
message: '
|
|
130
|
+
message: 'Project namespace (for new content):',
|
|
78
131
|
default: projectName
|
|
79
132
|
},
|
|
80
133
|
{
|
|
@@ -96,12 +149,74 @@ export async function initCommand(options) {
|
|
|
96
149
|
}
|
|
97
150
|
]);
|
|
98
151
|
|
|
152
|
+
kbSchema = answers.kbSchema === '__custom__' ? answers.customSchema : answers.kbSchema;
|
|
99
153
|
config.kbFirst.namespace = answers.namespace || projectName;
|
|
100
154
|
config.hooks.enabled = answers.installHooks;
|
|
101
155
|
} else {
|
|
102
156
|
config.kbFirst.namespace = projectName;
|
|
103
157
|
}
|
|
104
158
|
|
|
159
|
+
// Connect to KB if specified
|
|
160
|
+
if (kbSchema) {
|
|
161
|
+
const spinner = ora(`Connecting to KB schema: ${kbSchema}...`).start();
|
|
162
|
+
|
|
163
|
+
try {
|
|
164
|
+
const pg = await import('pg');
|
|
165
|
+
const client = new pg.default.Client({
|
|
166
|
+
host: kbHost,
|
|
167
|
+
port: parseInt(kbPort),
|
|
168
|
+
database: 'postgres',
|
|
169
|
+
user: 'postgres',
|
|
170
|
+
password: 'guruKB2025'
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
await client.connect();
|
|
174
|
+
|
|
175
|
+
// Check if schema exists and count entries
|
|
176
|
+
const schemaCheck = await client.query(
|
|
177
|
+
`SELECT COUNT(*) as count FROM information_schema.schemata WHERE schema_name = $1`,
|
|
178
|
+
[kbSchema]
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
if (schemaCheck.rows[0].count === '0') {
|
|
182
|
+
spinner.fail(`KB schema '${kbSchema}' not found`);
|
|
183
|
+
await client.end();
|
|
184
|
+
console.log(chalk.yellow('\nAvailable schemas:'));
|
|
185
|
+
Object.entries(KNOWN_KB_SCHEMAS).forEach(([name, info]) => {
|
|
186
|
+
console.log(chalk.gray(` ${name} - ${info.description}`));
|
|
187
|
+
});
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Count entries
|
|
192
|
+
const countResult = await client.query(
|
|
193
|
+
`SELECT COUNT(*) as count FROM ${kbSchema}.architecture_docs`
|
|
194
|
+
);
|
|
195
|
+
const entryCount = parseInt(countResult.rows[0].count);
|
|
196
|
+
|
|
197
|
+
await client.end();
|
|
198
|
+
|
|
199
|
+
// Update config with KB connection
|
|
200
|
+
config.knowledgeBase = {
|
|
201
|
+
schema: kbSchema,
|
|
202
|
+
host: kbHost,
|
|
203
|
+
port: parseInt(kbPort),
|
|
204
|
+
database: 'postgres',
|
|
205
|
+
table: 'architecture_docs',
|
|
206
|
+
entries: entryCount,
|
|
207
|
+
connected: true,
|
|
208
|
+
connectedAt: new Date().toISOString()
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
spinner.succeed(`Connected to KB: ${kbSchema} (${entryCount.toLocaleString()} entries)`);
|
|
212
|
+
|
|
213
|
+
} catch (err) {
|
|
214
|
+
spinner.fail(`Failed to connect to KB: ${err.message}`);
|
|
215
|
+
console.log(chalk.yellow('\nMake sure the ruvector-kb container is running:'));
|
|
216
|
+
console.log(chalk.gray(' docker ps | grep ruvector-kb'));
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
105
220
|
// Create directories
|
|
106
221
|
const spinner = ora('Creating directory structure...').start();
|
|
107
222
|
|
|
@@ -192,8 +307,18 @@ export async function initCommand(options) {
|
|
|
192
307
|
|
|
193
308
|
// Summary
|
|
194
309
|
console.log('');
|
|
195
|
-
console.log(chalk.green('KB-First project initialized successfully!'));
|
|
310
|
+
console.log(chalk.green('RuvNet-KB-First project initialized successfully!'));
|
|
196
311
|
console.log('');
|
|
312
|
+
|
|
313
|
+
// Show KB connection status
|
|
314
|
+
if (config.knowledgeBase.connected) {
|
|
315
|
+
console.log(chalk.white('Knowledge Base:'));
|
|
316
|
+
console.log(chalk.green(` ✓ Connected to: ${config.knowledgeBase.schema}`));
|
|
317
|
+
console.log(chalk.gray(` ${config.knowledgeBase.entries.toLocaleString()} entries available`));
|
|
318
|
+
console.log(chalk.gray(` Host: ${config.knowledgeBase.host}:${config.knowledgeBase.port}`));
|
|
319
|
+
console.log('');
|
|
320
|
+
}
|
|
321
|
+
|
|
197
322
|
console.log(chalk.white('Project structure:'));
|
|
198
323
|
console.log(chalk.gray(' .ruvector/ - KB configuration and hooks'));
|
|
199
324
|
console.log(chalk.gray(' src/kb/ - Knowledge base modules'));
|
|
@@ -201,9 +326,13 @@ export async function initCommand(options) {
|
|
|
201
326
|
console.log(chalk.gray(' scripts/ - Verification scripts'));
|
|
202
327
|
console.log('');
|
|
203
328
|
console.log(chalk.white('Next steps:'));
|
|
204
|
-
console.log(chalk.cyan(' 1. Run: kb-first status'));
|
|
205
|
-
console.log(chalk.cyan(' 2. Start Phase 0: kb-first phase 0'));
|
|
206
|
-
|
|
329
|
+
console.log(chalk.cyan(' 1. Run: npx ruvnet-kb-first status'));
|
|
330
|
+
console.log(chalk.cyan(' 2. Start Phase 0: npx ruvnet-kb-first phase 0'));
|
|
331
|
+
if (config.knowledgeBase.connected) {
|
|
332
|
+
console.log(chalk.cyan(' 3. Ask Claude: "Review this app using RuvNet-KB-First and recommend improvements"'));
|
|
333
|
+
} else {
|
|
334
|
+
console.log(chalk.cyan(' 3. Build your KB in src/kb/'));
|
|
335
|
+
}
|
|
207
336
|
console.log('');
|
|
208
337
|
}
|
|
209
338
|
|
package/src/commands/status.js
CHANGED
|
@@ -31,22 +31,70 @@ export async function statusCommand(options) {
|
|
|
31
31
|
|
|
32
32
|
console.log('');
|
|
33
33
|
console.log(chalk.cyan('╔═══════════════════════════════════════════════════════════════╗'));
|
|
34
|
-
console.log(chalk.cyan('║') + chalk.bold.white('
|
|
34
|
+
console.log(chalk.cyan('║') + chalk.bold.white(' RuvNet-KB-First Project Status ') + chalk.cyan('║'));
|
|
35
35
|
console.log(chalk.cyan('╚═══════════════════════════════════════════════════════════════╝'));
|
|
36
36
|
console.log('');
|
|
37
37
|
|
|
38
38
|
// Check if initialized
|
|
39
39
|
const configPath = join(cwd, '.ruvector', 'config.json');
|
|
40
40
|
if (!existsSync(configPath)) {
|
|
41
|
-
console.log(chalk.red(' Not a KB-First project.'));
|
|
41
|
+
console.log(chalk.red(' Not a RuvNet-KB-First project.'));
|
|
42
42
|
console.log('');
|
|
43
|
-
console.log(chalk.gray(' Initialize with: kb-first init'));
|
|
43
|
+
console.log(chalk.gray(' Initialize with: npx ruvnet-kb-first init'));
|
|
44
|
+
console.log(chalk.gray(' With KB: npx ruvnet-kb-first init --kb ask_ruvnet'));
|
|
44
45
|
console.log('');
|
|
45
46
|
return;
|
|
46
47
|
}
|
|
47
48
|
|
|
48
49
|
const config = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
49
50
|
|
|
51
|
+
// Knowledge Base Connection (PROMINENT - this is the "Bible")
|
|
52
|
+
if (config.knowledgeBase?.connected) {
|
|
53
|
+
console.log(chalk.white(' Knowledge Base (Authority Source)'));
|
|
54
|
+
console.log(chalk.gray(' ─────────────────────────────────────────────────────────────'));
|
|
55
|
+
|
|
56
|
+
// Try to get live count
|
|
57
|
+
let liveCount = config.knowledgeBase.entries;
|
|
58
|
+
let isLive = false;
|
|
59
|
+
try {
|
|
60
|
+
const pg = await import('pg');
|
|
61
|
+
const client = new pg.default.Client({
|
|
62
|
+
host: config.knowledgeBase.host,
|
|
63
|
+
port: config.knowledgeBase.port,
|
|
64
|
+
database: 'postgres',
|
|
65
|
+
user: 'postgres',
|
|
66
|
+
password: 'guruKB2025'
|
|
67
|
+
});
|
|
68
|
+
await client.connect();
|
|
69
|
+
const result = await client.query(
|
|
70
|
+
`SELECT COUNT(*) as count FROM ${config.knowledgeBase.schema}.architecture_docs`
|
|
71
|
+
);
|
|
72
|
+
liveCount = parseInt(result.rows[0].count);
|
|
73
|
+
await client.end();
|
|
74
|
+
isLive = true;
|
|
75
|
+
} catch (e) {
|
|
76
|
+
// Use cached count
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const statusIcon = isLive ? chalk.green('●') : chalk.yellow('○');
|
|
80
|
+
console.log(` ${statusIcon} Schema: ${chalk.green(config.knowledgeBase.schema)}`);
|
|
81
|
+
console.log(` Entries: ${chalk.cyan(liveCount.toLocaleString())} ${isLive ? chalk.green('(live)') : chalk.yellow('(cached)')}`);
|
|
82
|
+
console.log(` Host: ${chalk.gray(config.knowledgeBase.host + ':' + config.knowledgeBase.port)}`);
|
|
83
|
+
console.log('');
|
|
84
|
+
console.log(chalk.gray(' Claude will use this KB to:'));
|
|
85
|
+
console.log(chalk.gray(' • Verify architecture patterns are correct'));
|
|
86
|
+
console.log(chalk.gray(' • Ensure optimal configurations are applied'));
|
|
87
|
+
console.log(chalk.gray(' • Identify missing best practices'));
|
|
88
|
+
console.log(chalk.gray(' • Generate code with proper KB citations'));
|
|
89
|
+
console.log('');
|
|
90
|
+
} else {
|
|
91
|
+
console.log(chalk.white(' Knowledge Base'));
|
|
92
|
+
console.log(chalk.gray(' ─────────────────────────────────────────────────────────────'));
|
|
93
|
+
console.log(chalk.yellow(' ○ Not connected'));
|
|
94
|
+
console.log(chalk.gray(' Reinitialize with: npx ruvnet-kb-first init --kb ask_ruvnet'));
|
|
95
|
+
console.log('');
|
|
96
|
+
}
|
|
97
|
+
|
|
50
98
|
// Project Info
|
|
51
99
|
console.log(chalk.white(' Project Information'));
|
|
52
100
|
console.log(chalk.gray(' ─────────────────────────────────────────────────────────────'));
|
|
@@ -132,12 +180,18 @@ export async function statusCommand(options) {
|
|
|
132
180
|
console.log(chalk.white(' Next Steps'));
|
|
133
181
|
console.log(chalk.gray(' ─────────────────────────────────────────────────────────────'));
|
|
134
182
|
|
|
183
|
+
if (config.knowledgeBase?.connected) {
|
|
184
|
+
console.log(chalk.cyan(' → Ask Claude: "Review this app using RuvNet-KB-First"'));
|
|
185
|
+
console.log(chalk.gray(' Claude will query the KB and analyze your code'));
|
|
186
|
+
console.log('');
|
|
187
|
+
}
|
|
188
|
+
|
|
135
189
|
if (currentPhase < 11) {
|
|
136
|
-
console.log(chalk.cyan(` → Run: kb-first phase ${currentPhase}`));
|
|
190
|
+
console.log(chalk.cyan(` → Run: npx ruvnet-kb-first phase ${currentPhase}`));
|
|
137
191
|
console.log(chalk.gray(` Complete Phase ${currentPhase}: ${PHASES.find(p => p.num === currentPhase)?.name}`));
|
|
138
192
|
} else {
|
|
139
193
|
console.log(chalk.green(' → All phases complete!'));
|
|
140
|
-
console.log(chalk.gray(' Run: kb-first score --detailed'));
|
|
194
|
+
console.log(chalk.gray(' Run: npx ruvnet-kb-first score --detailed'));
|
|
141
195
|
}
|
|
142
196
|
|
|
143
197
|
console.log('');
|
package/src/mcp-server.js
CHANGED
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* RuvNet KB-First MCP Server - Granular Score-Driven Architecture
|
|
3
|
-
* Version 6.
|
|
3
|
+
* Version 6.2.0
|
|
4
4
|
*
|
|
5
5
|
* PHILOSOPHY: Granular scoring drives discipline.
|
|
6
|
-
* - Score each KB dimension 1-100 (completeness, depth, comprehensiveness, accuracy, freshness)
|
|
7
|
-
* - Score each phase readiness 1-100
|
|
6
|
+
* - Score each KB dimension 1-100 (completeness, depth, comprehensiveness, accuracy, freshness, attribution, ux_quality)
|
|
7
|
+
* - Score each phase readiness 1-100 (including Phase 12: UX Quality Review)
|
|
8
8
|
* - Generate enhancement plan based on gaps
|
|
9
9
|
* - User confirms before execution
|
|
10
10
|
* - Post-verify: did we hit predicted scores?
|
|
11
|
+
* - Playwright UX Review: Visual quality audit from end-user perspective
|
|
11
12
|
*
|
|
12
|
-
*
|
|
13
|
-
* 1. kb_first_assess
|
|
14
|
-
* 2. kb_first_plan
|
|
15
|
-
* 3. kb_first_confirm
|
|
16
|
-
* 4. kb_first_execute
|
|
17
|
-
* 5. kb_first_verify
|
|
13
|
+
* 6 Tools:
|
|
14
|
+
* 1. kb_first_assess - Score ALL dimensions (KB quality + phase readiness)
|
|
15
|
+
* 2. kb_first_plan - Generate enhancement plan with predicted improvements
|
|
16
|
+
* 3. kb_first_confirm - User confirms readiness, locks in plan
|
|
17
|
+
* 4. kb_first_execute - Execute plan phase by phase
|
|
18
|
+
* 5. kb_first_verify - Post-verification: predicted vs actual, identify gaps
|
|
19
|
+
* 6. kb_first_ux_review - Playwright-based UX quality audit with screenshots
|
|
18
20
|
*
|
|
19
21
|
* Usage:
|
|
20
22
|
* npx ruvnet-kb-first mcp
|
|
@@ -26,7 +28,7 @@ import { globSync } from 'glob';
|
|
|
26
28
|
|
|
27
29
|
const MCP_VERSION = '0.1.0';
|
|
28
30
|
const SERVER_NAME = 'ruvnet-kb-first';
|
|
29
|
-
const SERVER_VERSION = '6.
|
|
31
|
+
const SERVER_VERSION = '6.2.0';
|
|
30
32
|
|
|
31
33
|
/**
|
|
32
34
|
* KB Quality Dimensions (each scored 1-100)
|
|
@@ -61,6 +63,11 @@ const KB_DIMENSIONS = {
|
|
|
61
63
|
name: 'Attribution',
|
|
62
64
|
description: 'Are sources and experts properly cited?',
|
|
63
65
|
weight: 10
|
|
66
|
+
},
|
|
67
|
+
ux_quality: {
|
|
68
|
+
name: 'UX Quality',
|
|
69
|
+
description: 'Is the user experience excellent? Visual design, emotional appeal, user flow, versioning display, loading states, error handling, accessibility.',
|
|
70
|
+
weight: 15
|
|
64
71
|
}
|
|
65
72
|
};
|
|
66
73
|
|
|
@@ -119,6 +126,21 @@ const PHASES = {
|
|
|
119
126
|
11: {
|
|
120
127
|
name: 'Deployment',
|
|
121
128
|
criteria: ['Infrastructure ready', 'Environments configured', 'CI/CD built', 'Migrations run', 'Monitoring active', 'Go-live complete']
|
|
129
|
+
},
|
|
130
|
+
12: {
|
|
131
|
+
name: 'UX Quality Review',
|
|
132
|
+
criteria: [
|
|
133
|
+
'Version displayed in header/footer (major.minor.patch)',
|
|
134
|
+
'Cache-busting implemented with version notifications',
|
|
135
|
+
'Visual design excellence (not just functional)',
|
|
136
|
+
'Emotional appeal and user psychology considered',
|
|
137
|
+
'Loading states are elegant',
|
|
138
|
+
'Error messages are helpful and actionable',
|
|
139
|
+
'User flow is intuitive and compelling',
|
|
140
|
+
'Playwright screenshots reviewed and critiqued',
|
|
141
|
+
'Accessibility verified (WCAG 2.1 AA)',
|
|
142
|
+
'Mobile responsiveness tested'
|
|
143
|
+
]
|
|
122
144
|
}
|
|
123
145
|
};
|
|
124
146
|
|
|
@@ -221,6 +243,53 @@ This closes the loop and ensures you delivered what you promised.`,
|
|
|
221
243
|
detailed: { type: 'boolean', description: 'Show detailed comparison', default: true }
|
|
222
244
|
}
|
|
223
245
|
}
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
name: 'kb_first_ux_review',
|
|
249
|
+
description: `Playwright-based UX Quality Audit - End-user perspective review.
|
|
250
|
+
|
|
251
|
+
Walks through the application as an end user, capturing screenshots.
|
|
252
|
+
Then critically reviews EACH screenshot:
|
|
253
|
+
|
|
254
|
+
For each screen:
|
|
255
|
+
- How good is this? (1-100 score)
|
|
256
|
+
- How could we make it better? (specific recommendations)
|
|
257
|
+
- Where is it falling down? (issues identified)
|
|
258
|
+
- What would EXCELLENT look like? (vision for improvement)
|
|
259
|
+
- Recommendations with priority
|
|
260
|
+
|
|
261
|
+
Also checks critical UX requirements:
|
|
262
|
+
- Version number displayed in header/footer (major.minor.patch)
|
|
263
|
+
- Cache-busting with version change notifications
|
|
264
|
+
- Loading states are elegant, not jarring
|
|
265
|
+
- Error messages are helpful and guide user to resolution
|
|
266
|
+
- Visual design creates emotional appeal, not just functional
|
|
267
|
+
- User psychology leveraged for compelling flow
|
|
268
|
+
|
|
269
|
+
This ensures the application isn't just functional—it's EXCELLENT.`,
|
|
270
|
+
inputSchema: {
|
|
271
|
+
type: 'object',
|
|
272
|
+
properties: {
|
|
273
|
+
appUrl: { type: 'string', description: 'URL of the running application to review' },
|
|
274
|
+
flows: {
|
|
275
|
+
type: 'array',
|
|
276
|
+
items: { type: 'string' },
|
|
277
|
+
description: 'User flows to test (e.g., ["login", "search", "checkout"])',
|
|
278
|
+
default: ['homepage', 'main_flow']
|
|
279
|
+
},
|
|
280
|
+
screenshotDir: {
|
|
281
|
+
type: 'string',
|
|
282
|
+
description: 'Directory to save screenshots',
|
|
283
|
+
default: '.ruvector/ux-review'
|
|
284
|
+
},
|
|
285
|
+
criticalReview: {
|
|
286
|
+
type: 'boolean',
|
|
287
|
+
description: 'Perform deep critical review of each screenshot',
|
|
288
|
+
default: true
|
|
289
|
+
}
|
|
290
|
+
},
|
|
291
|
+
required: ['appUrl']
|
|
292
|
+
}
|
|
224
293
|
}
|
|
225
294
|
];
|
|
226
295
|
|
|
@@ -351,6 +420,50 @@ function scoreKBDimensions(cwd) {
|
|
|
351
420
|
improvement: attributionScore < 80 ? 'Add KB citations to code files' : 'Good attribution'
|
|
352
421
|
};
|
|
353
422
|
|
|
423
|
+
// UX Quality: Based on presence of UX-related artifacts
|
|
424
|
+
let uxScore = 0;
|
|
425
|
+
const packagePath = join(cwd, 'package.json');
|
|
426
|
+
const uxReviewPath = join(cwd, '.ruvector', 'ux-review');
|
|
427
|
+
|
|
428
|
+
// Check for version in package.json
|
|
429
|
+
if (existsSync(packagePath)) {
|
|
430
|
+
try {
|
|
431
|
+
const pkg = JSON.parse(readFileSync(packagePath, 'utf-8'));
|
|
432
|
+
if (pkg.version) uxScore += 15; // Versioning exists
|
|
433
|
+
} catch {}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// Check for prior UX review
|
|
437
|
+
if (existsSync(uxReviewPath)) {
|
|
438
|
+
try {
|
|
439
|
+
const reviewFiles = readdirSync(uxReviewPath);
|
|
440
|
+
if (reviewFiles.length > 0) uxScore += 25; // UX review conducted
|
|
441
|
+
if (reviewFiles.some(f => f.includes('review-report'))) uxScore += 20; // Detailed review exists
|
|
442
|
+
} catch {}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// Check for accessibility considerations
|
|
446
|
+
if (existsSync(srcDir)) {
|
|
447
|
+
try {
|
|
448
|
+
const files = globSync('**/*.{tsx,jsx,html}', { cwd: srcDir });
|
|
449
|
+
let a11yScore = 0;
|
|
450
|
+
for (const f of files) {
|
|
451
|
+
try {
|
|
452
|
+
const content = readFileSync(join(srcDir, f), 'utf-8');
|
|
453
|
+
if (content.includes('aria-') || content.includes('role=')) a11yScore += 5;
|
|
454
|
+
if (content.includes('alt=') || content.includes('loading=')) a11yScore += 3;
|
|
455
|
+
} catch {}
|
|
456
|
+
}
|
|
457
|
+
uxScore += Math.min(40, a11yScore);
|
|
458
|
+
} catch {}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
scores.ux_quality = {
|
|
462
|
+
score: Math.min(100, uxScore),
|
|
463
|
+
reason: uxScore >= 60 ? 'UX review conducted, accessibility present' : 'Limited UX review artifacts',
|
|
464
|
+
improvement: uxScore < 80 ? 'Run kb_first_ux_review for Playwright-based UX audit' : 'Good UX coverage'
|
|
465
|
+
};
|
|
466
|
+
|
|
354
467
|
return scores;
|
|
355
468
|
}
|
|
356
469
|
|
|
@@ -941,6 +1054,296 @@ async function handleVerify(cwd, args) {
|
|
|
941
1054
|
return result;
|
|
942
1055
|
}
|
|
943
1056
|
|
|
1057
|
+
/**
|
|
1058
|
+
* UX Review Criteria for critical evaluation
|
|
1059
|
+
*/
|
|
1060
|
+
const UX_CRITERIA = {
|
|
1061
|
+
versioning: {
|
|
1062
|
+
name: 'Version Display',
|
|
1063
|
+
checks: ['Header shows version', 'Footer shows version', 'Format: major.minor.patch'],
|
|
1064
|
+
weight: 15
|
|
1065
|
+
},
|
|
1066
|
+
caching: {
|
|
1067
|
+
name: 'Cache Management',
|
|
1068
|
+
checks: ['Version change detection', 'User notification on updates', 'Force refresh capability'],
|
|
1069
|
+
weight: 10
|
|
1070
|
+
},
|
|
1071
|
+
visual_design: {
|
|
1072
|
+
name: 'Visual Design Excellence',
|
|
1073
|
+
checks: ['Professional typography', 'Cohesive color palette', 'Proper spacing/hierarchy', 'Not generic AI aesthetic'],
|
|
1074
|
+
weight: 20
|
|
1075
|
+
},
|
|
1076
|
+
emotional_appeal: {
|
|
1077
|
+
name: 'Emotional Appeal',
|
|
1078
|
+
checks: ['Creates confidence', 'Guides without confusion', 'Celebrates success states', 'Softens error states'],
|
|
1079
|
+
weight: 15
|
|
1080
|
+
},
|
|
1081
|
+
loading_states: {
|
|
1082
|
+
name: 'Loading States',
|
|
1083
|
+
checks: ['Skeleton loaders present', 'Progress indicators', 'No jarring transitions', 'Graceful degradation'],
|
|
1084
|
+
weight: 10
|
|
1085
|
+
},
|
|
1086
|
+
error_handling: {
|
|
1087
|
+
name: 'Error Handling UX',
|
|
1088
|
+
checks: ['Clear error messages', 'Actionable next steps', 'Recovery paths provided', 'No technical jargon'],
|
|
1089
|
+
weight: 10
|
|
1090
|
+
},
|
|
1091
|
+
user_flow: {
|
|
1092
|
+
name: 'User Flow',
|
|
1093
|
+
checks: ['Intuitive navigation', 'Clear call-to-actions', 'Logical progression', 'Minimal friction'],
|
|
1094
|
+
weight: 10
|
|
1095
|
+
},
|
|
1096
|
+
accessibility: {
|
|
1097
|
+
name: 'Accessibility',
|
|
1098
|
+
checks: ['Keyboard navigation', 'Screen reader compatible', 'Color contrast', 'Focus indicators'],
|
|
1099
|
+
weight: 10
|
|
1100
|
+
}
|
|
1101
|
+
};
|
|
1102
|
+
|
|
1103
|
+
/**
|
|
1104
|
+
* Detect available frontend-design skills
|
|
1105
|
+
*/
|
|
1106
|
+
function detectFrontendDesignSkills() {
|
|
1107
|
+
const homeDir = process.env.HOME || process.env.USERPROFILE || '';
|
|
1108
|
+
const skillsDir = join(homeDir, '.claude', 'skills');
|
|
1109
|
+
const skills = {
|
|
1110
|
+
preferred: null,
|
|
1111
|
+
available: [],
|
|
1112
|
+
recommendation: null
|
|
1113
|
+
};
|
|
1114
|
+
|
|
1115
|
+
// Check for frontend-design-Stu (preferred)
|
|
1116
|
+
if (existsSync(join(skillsDir, 'frontend-design-Stu.md'))) {
|
|
1117
|
+
skills.preferred = 'frontend-design-Stu';
|
|
1118
|
+
skills.available.push('frontend-design-Stu');
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
// Check for frontend-design
|
|
1122
|
+
if (existsSync(join(skillsDir, 'frontend-design.md'))) {
|
|
1123
|
+
if (!skills.preferred) {
|
|
1124
|
+
skills.preferred = 'frontend-design';
|
|
1125
|
+
}
|
|
1126
|
+
skills.available.push('frontend-design');
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
// Generate recommendation if no skills found
|
|
1130
|
+
if (skills.available.length === 0) {
|
|
1131
|
+
skills.recommendation = {
|
|
1132
|
+
message: 'No frontend-design skill installed. For UX excellence, install the Anthropic frontend-design skill.',
|
|
1133
|
+
installation: 'Visit: https://github.com/anthropics/claude-code-plugins and install frontend-design plugin',
|
|
1134
|
+
benefit: 'Creates distinctive, production-grade interfaces that avoid generic AI aesthetics'
|
|
1135
|
+
};
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
return skills;
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
/**
|
|
1142
|
+
* Handle UX Review with Playwright
|
|
1143
|
+
*/
|
|
1144
|
+
async function handleUXReview(cwd, args) {
|
|
1145
|
+
const { appUrl, flows = ['homepage', 'main_flow'], screenshotDir = '.ruvector/ux-review', criticalReview = true } = args;
|
|
1146
|
+
|
|
1147
|
+
// Check for frontend-design skills
|
|
1148
|
+
const designSkills = detectFrontendDesignSkills();
|
|
1149
|
+
|
|
1150
|
+
// Check if Playwright is installed
|
|
1151
|
+
let playwrightAvailable = false;
|
|
1152
|
+
try {
|
|
1153
|
+
await import('playwright');
|
|
1154
|
+
playwrightAvailable = true;
|
|
1155
|
+
} catch {
|
|
1156
|
+
// Playwright not installed
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
if (!playwrightAvailable) {
|
|
1160
|
+
return {
|
|
1161
|
+
action: 'PLAYWRIGHT_REQUIRED',
|
|
1162
|
+
status: 'INSTALLATION_NEEDED',
|
|
1163
|
+
message: 'Playwright is required for UX review but not installed.',
|
|
1164
|
+
installation: {
|
|
1165
|
+
command: 'npm install playwright && npx playwright install chromium',
|
|
1166
|
+
description: 'Installs Playwright and Chromium browser for screenshot capture',
|
|
1167
|
+
automated: 'Run this command, then re-run kb_first_ux_review'
|
|
1168
|
+
},
|
|
1169
|
+
designSkills: designSkills,
|
|
1170
|
+
alternative: {
|
|
1171
|
+
message: 'Alternatively, you can manually capture screenshots and place them in:',
|
|
1172
|
+
directory: join(cwd, screenshotDir),
|
|
1173
|
+
format: 'Name format: 01-homepage.png, 02-login.png, etc.',
|
|
1174
|
+
thenRun: 'Re-run kb_first_ux_review with criticalReview=true to analyze'
|
|
1175
|
+
}
|
|
1176
|
+
};
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
// Create screenshot directory
|
|
1180
|
+
const screenshotPath = join(cwd, screenshotDir);
|
|
1181
|
+
if (!existsSync(screenshotPath)) {
|
|
1182
|
+
mkdirSync(screenshotPath, { recursive: true });
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
// Capture screenshots using Playwright
|
|
1186
|
+
const screenshots = [];
|
|
1187
|
+
try {
|
|
1188
|
+
const { chromium } = await import('playwright');
|
|
1189
|
+
const browser = await chromium.launch();
|
|
1190
|
+
const context = await browser.newContext({
|
|
1191
|
+
viewport: { width: 1920, height: 1080 }
|
|
1192
|
+
});
|
|
1193
|
+
const page = await context.newPage();
|
|
1194
|
+
|
|
1195
|
+
// Navigate to app
|
|
1196
|
+
await page.goto(appUrl, { waitUntil: 'networkidle', timeout: 30000 });
|
|
1197
|
+
|
|
1198
|
+
// Capture initial screenshot
|
|
1199
|
+
const timestamp = Date.now();
|
|
1200
|
+
const homepagePath = join(screenshotPath, `01-homepage-${timestamp}.png`);
|
|
1201
|
+
await page.screenshot({ path: homepagePath, fullPage: true });
|
|
1202
|
+
screenshots.push({
|
|
1203
|
+
id: 1,
|
|
1204
|
+
name: 'Homepage',
|
|
1205
|
+
path: homepagePath,
|
|
1206
|
+
url: appUrl,
|
|
1207
|
+
viewport: 'desktop'
|
|
1208
|
+
});
|
|
1209
|
+
|
|
1210
|
+
// Check for version display
|
|
1211
|
+
const versionCheck = await page.evaluate(() => {
|
|
1212
|
+
const body = document.body.innerText;
|
|
1213
|
+
const versionPattern = /v?\d+\.\d+\.\d+/;
|
|
1214
|
+
const hasVersion = versionPattern.test(body);
|
|
1215
|
+
const header = document.querySelector('header')?.innerText || '';
|
|
1216
|
+
const footer = document.querySelector('footer')?.innerText || '';
|
|
1217
|
+
return {
|
|
1218
|
+
hasVersion,
|
|
1219
|
+
inHeader: versionPattern.test(header),
|
|
1220
|
+
inFooter: versionPattern.test(footer),
|
|
1221
|
+
foundVersion: body.match(versionPattern)?.[0] || null
|
|
1222
|
+
};
|
|
1223
|
+
});
|
|
1224
|
+
|
|
1225
|
+
// Mobile viewport screenshot
|
|
1226
|
+
await page.setViewportSize({ width: 375, height: 812 });
|
|
1227
|
+
await page.reload({ waitUntil: 'networkidle' });
|
|
1228
|
+
const mobilePath = join(screenshotPath, `02-homepage-mobile-${timestamp}.png`);
|
|
1229
|
+
await page.screenshot({ path: mobilePath, fullPage: true });
|
|
1230
|
+
screenshots.push({
|
|
1231
|
+
id: 2,
|
|
1232
|
+
name: 'Homepage Mobile',
|
|
1233
|
+
path: mobilePath,
|
|
1234
|
+
url: appUrl,
|
|
1235
|
+
viewport: 'mobile'
|
|
1236
|
+
});
|
|
1237
|
+
|
|
1238
|
+
await browser.close();
|
|
1239
|
+
|
|
1240
|
+
// Generate critical review for each screenshot
|
|
1241
|
+
const reviews = [];
|
|
1242
|
+
if (criticalReview) {
|
|
1243
|
+
for (const screenshot of screenshots) {
|
|
1244
|
+
reviews.push({
|
|
1245
|
+
screenshot: screenshot.name,
|
|
1246
|
+
path: screenshot.path,
|
|
1247
|
+
score: 0, // To be filled by Claude's analysis
|
|
1248
|
+
evaluation: {
|
|
1249
|
+
howGood: 'REQUIRES_VISUAL_ANALYSIS - Open screenshot and evaluate',
|
|
1250
|
+
couldBeBetter: [],
|
|
1251
|
+
fallingDown: [],
|
|
1252
|
+
excellentWouldLookLike: '',
|
|
1253
|
+
recommendations: []
|
|
1254
|
+
},
|
|
1255
|
+
criteria: Object.entries(UX_CRITERIA).map(([key, crit]) => ({
|
|
1256
|
+
name: crit.name,
|
|
1257
|
+
checks: crit.checks,
|
|
1258
|
+
score: 0,
|
|
1259
|
+
notes: 'Awaiting visual review'
|
|
1260
|
+
}))
|
|
1261
|
+
});
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
// Save review report
|
|
1266
|
+
const reviewReport = {
|
|
1267
|
+
timestamp: new Date().toISOString(),
|
|
1268
|
+
appUrl,
|
|
1269
|
+
versionCheck,
|
|
1270
|
+
screenshots,
|
|
1271
|
+
reviews,
|
|
1272
|
+
summary: {
|
|
1273
|
+
screenshotsCaptured: screenshots.length,
|
|
1274
|
+
versionDisplayed: versionCheck.hasVersion,
|
|
1275
|
+
versionInHeader: versionCheck.inHeader,
|
|
1276
|
+
versionInFooter: versionCheck.inFooter,
|
|
1277
|
+
requiresManualReview: true
|
|
1278
|
+
},
|
|
1279
|
+
criticalQuestions: [
|
|
1280
|
+
'How good is this? (Score each screen 1-100)',
|
|
1281
|
+
'How could we make it better?',
|
|
1282
|
+
'Where is it falling down?',
|
|
1283
|
+
'What would EXCELLENT look like?',
|
|
1284
|
+
'What are the specific recommendations?'
|
|
1285
|
+
]
|
|
1286
|
+
};
|
|
1287
|
+
|
|
1288
|
+
writeFileSync(join(screenshotPath, 'review-report.json'), JSON.stringify(reviewReport, null, 2));
|
|
1289
|
+
|
|
1290
|
+
return {
|
|
1291
|
+
action: 'UX_REVIEW_CAPTURED',
|
|
1292
|
+
status: 'SCREENSHOTS_READY',
|
|
1293
|
+
appUrl,
|
|
1294
|
+
versionCheck: {
|
|
1295
|
+
found: versionCheck.hasVersion,
|
|
1296
|
+
version: versionCheck.foundVersion,
|
|
1297
|
+
inHeader: versionCheck.inHeader,
|
|
1298
|
+
inFooter: versionCheck.inFooter,
|
|
1299
|
+
recommendation: !versionCheck.inHeader && !versionCheck.inFooter
|
|
1300
|
+
? '⚠️ Version not displayed in header/footer - ADD version display immediately'
|
|
1301
|
+
: '✓ Version displayed'
|
|
1302
|
+
},
|
|
1303
|
+
screenshots: screenshots.map(s => ({
|
|
1304
|
+
name: s.name,
|
|
1305
|
+
path: s.path,
|
|
1306
|
+
viewport: s.viewport
|
|
1307
|
+
})),
|
|
1308
|
+
nextSteps: {
|
|
1309
|
+
step1: 'Open each screenshot file and visually inspect',
|
|
1310
|
+
step2: 'Score each screen 1-100 for overall UX quality',
|
|
1311
|
+
step3: 'Document: What works? What fails? What would excellent look like?',
|
|
1312
|
+
step4: 'Generate specific recommendations with priority (HIGH/MEDIUM/LOW)',
|
|
1313
|
+
step5: 'Update review-report.json with scores and findings',
|
|
1314
|
+
step6: 'Re-run kb_first_assess to update UX Quality scores'
|
|
1315
|
+
},
|
|
1316
|
+
criticalQuestions: reviewReport.criticalQuestions,
|
|
1317
|
+
criteria: Object.entries(UX_CRITERIA).map(([key, crit]) => ({
|
|
1318
|
+
name: crit.name,
|
|
1319
|
+
checks: crit.checks,
|
|
1320
|
+
weight: `${crit.weight}%`
|
|
1321
|
+
})),
|
|
1322
|
+
reportPath: join(screenshotPath, 'review-report.json'),
|
|
1323
|
+
designSkills: {
|
|
1324
|
+
available: designSkills.available,
|
|
1325
|
+
useSkill: designSkills.preferred || null,
|
|
1326
|
+
recommendation: designSkills.recommendation,
|
|
1327
|
+
implementationGuidance: designSkills.preferred
|
|
1328
|
+
? `Use skill "${designSkills.preferred}" when implementing UX improvements. It creates distinctive, production-grade interfaces.`
|
|
1329
|
+
: 'Install frontend-design skill for UX improvements: https://github.com/anthropics/claude-code-plugins'
|
|
1330
|
+
}
|
|
1331
|
+
};
|
|
1332
|
+
} catch (error) {
|
|
1333
|
+
return {
|
|
1334
|
+
action: 'UX_REVIEW_ERROR',
|
|
1335
|
+
status: 'CAPTURE_FAILED',
|
|
1336
|
+
error: error.message,
|
|
1337
|
+
troubleshooting: {
|
|
1338
|
+
step1: 'Ensure the app is running at the specified URL',
|
|
1339
|
+
step2: 'Check if Playwright browsers are installed: npx playwright install chromium',
|
|
1340
|
+
step3: 'Try with a simpler URL first',
|
|
1341
|
+
step4: 'Manually capture screenshots and place in: ' + screenshotPath
|
|
1342
|
+
}
|
|
1343
|
+
};
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
|
|
944
1347
|
/**
|
|
945
1348
|
* Get improvement task based on gap
|
|
946
1349
|
*/
|
|
@@ -983,6 +1386,8 @@ async function handleToolCall(toolName, args) {
|
|
|
983
1386
|
return await handleExecute(cwd, args);
|
|
984
1387
|
case 'kb_first_verify':
|
|
985
1388
|
return await handleVerify(cwd, args);
|
|
1389
|
+
case 'kb_first_ux_review':
|
|
1390
|
+
return await handleUXReview(cwd, args);
|
|
986
1391
|
default:
|
|
987
1392
|
return { error: `Unknown tool: ${toolName}` };
|
|
988
1393
|
}
|
|
@@ -1033,8 +1438,9 @@ async function handleMCPMessage(message) {
|
|
|
1033
1438
|
*/
|
|
1034
1439
|
export async function startMCPServer(options = {}) {
|
|
1035
1440
|
console.error(`RuvNet KB-First MCP Server v${SERVER_VERSION}`);
|
|
1036
|
-
console.error('Architecture: Granular Score-Driven | Tools:
|
|
1037
|
-
console.error('Workflow: Assess → Plan → Confirm → Execute → Verify');
|
|
1441
|
+
console.error('Architecture: Granular Score-Driven | Tools: 6 | Dimensions: 7 KB + 13 Phases');
|
|
1442
|
+
console.error('Workflow: Assess → Plan → Confirm → Execute → Verify → UX Review');
|
|
1443
|
+
console.error('UX Review: Playwright-based visual quality audit from end-user perspective');
|
|
1038
1444
|
|
|
1039
1445
|
let buffer = '';
|
|
1040
1446
|
process.stdin.setEncoding('utf-8');
|