@vizzly-testing/cli 0.13.0 → 0.13.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +552 -88
- package/claude-plugin/.claude-plugin/README.md +4 -0
- package/claude-plugin/.mcp.json +4 -0
- package/claude-plugin/CHANGELOG.md +27 -0
- package/claude-plugin/mcp/vizzly-docs-server/README.md +95 -0
- package/claude-plugin/mcp/vizzly-docs-server/docs-fetcher.js +110 -0
- package/claude-plugin/mcp/vizzly-docs-server/index.js +283 -0
- package/claude-plugin/mcp/vizzly-server/cloud-api-provider.js +26 -10
- package/claude-plugin/mcp/vizzly-server/index.js +14 -1
- package/claude-plugin/mcp/vizzly-server/local-tdd-provider.js +61 -28
- package/dist/cli.js +4 -4
- package/dist/commands/run.js +1 -1
- package/dist/commands/tdd-daemon.js +54 -8
- package/dist/commands/tdd.js +8 -8
- package/dist/container/index.js +34 -3
- package/dist/reporter/reporter-bundle.css +1 -1
- package/dist/reporter/reporter-bundle.iife.js +29 -59
- package/dist/server/handlers/tdd-handler.js +28 -63
- package/dist/server/http-server.js +473 -4
- package/dist/services/config-service.js +371 -0
- package/dist/services/project-service.js +245 -0
- package/dist/services/server-manager.js +4 -5
- package/dist/services/static-report-generator.js +208 -0
- package/dist/services/tdd-service.js +14 -6
- package/dist/types/reporter/src/components/ui/form-field.d.ts +16 -0
- package/dist/types/reporter/src/components/views/projects-view.d.ts +1 -0
- package/dist/types/reporter/src/components/views/settings-view.d.ts +1 -0
- package/dist/types/reporter/src/hooks/use-auth.d.ts +10 -0
- package/dist/types/reporter/src/hooks/use-config.d.ts +9 -0
- package/dist/types/reporter/src/hooks/use-projects.d.ts +10 -0
- package/dist/types/reporter/src/services/api-client.d.ts +7 -0
- package/dist/types/server/http-server.d.ts +1 -1
- package/dist/types/services/config-service.d.ts +98 -0
- package/dist/types/services/project-service.d.ts +103 -0
- package/dist/types/services/server-manager.d.ts +2 -1
- package/dist/types/services/static-report-generator.d.ts +25 -0
- package/dist/types/services/tdd-service.d.ts +2 -2
- package/dist/utils/console-ui.js +26 -2
- package/docs/tdd-mode.md +31 -15
- package/package.json +4 -4
|
@@ -130,6 +130,10 @@ The plugin provides an MCP server with direct access to Vizzly data:
|
|
|
130
130
|
### Local TDD Tools
|
|
131
131
|
- `detect_context` - Detect if using local TDD or cloud mode
|
|
132
132
|
- `get_tdd_status` - Get current TDD comparison results
|
|
133
|
+
- Parameters:
|
|
134
|
+
- `statusFilter` (optional): Filter by status - `'failed'`, `'new'`, `'passed'`, `'all'`, or `'summary'` (default)
|
|
135
|
+
- `limit` (optional): Maximum number of comparisons to return
|
|
136
|
+
- Default behavior (summary mode): Returns counts only without full comparison details for efficiency
|
|
133
137
|
- `read_comparison_details` - Detailed info for specific screenshot
|
|
134
138
|
- `accept_baseline` - Accept a screenshot as new baseline
|
|
135
139
|
- `reject_baseline` - Reject a baseline with reason
|
package/claude-plugin/.mcp.json
CHANGED
|
@@ -5,6 +5,33 @@ All notable changes to the Vizzly Claude Code plugin will be documented in this
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- 📚 **New MCP Server:** `vizzly-docs` - Easy access to Vizzly documentation
|
|
12
|
+
- `list_docs` - Browse all docs with optional category filtering
|
|
13
|
+
- `get_doc` - Retrieve full markdown content for any doc page
|
|
14
|
+
- `search_docs` - Keyword search across titles, descriptions, and categories
|
|
15
|
+
- `get_sidebar` - View complete documentation navigation structure
|
|
16
|
+
- Fetches from live docs.vizzly.dev site with intelligent caching
|
|
17
|
+
- ⚡️ **Performance:** `get_tdd_status` now supports filtering and pagination
|
|
18
|
+
- New `statusFilter` parameter: `'failed'`, `'new'`, `'passed'`, `'all'`, or `'summary'` (default)
|
|
19
|
+
- New `limit` parameter for capping number of comparisons returned
|
|
20
|
+
- Default behavior (summary mode) returns only counts for better token efficiency
|
|
21
|
+
- 🔧 **API Enhancement:** Cloud API provider now includes approval status and flaky screenshot detection
|
|
22
|
+
- Added approval status breakdown (pending/approved/rejected/auto_approved)
|
|
23
|
+
- Added flaky screenshot count
|
|
24
|
+
- Added hot spot coverage metadata for quick triage
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
- 🔧 **Internal:** `acceptBaseline()` in TDD service now accepts both comparison ID (string) or full comparison object
|
|
28
|
+
- Enables accepting baselines from report-data.json without in-memory lookup
|
|
29
|
+
- Fixes issue where accepting from dashboard wasn't working properly
|
|
30
|
+
|
|
31
|
+
### Fixed
|
|
32
|
+
- 🐛 Fixed path bug in local TDD provider: `screenshots/` → `current/` directory
|
|
33
|
+
- 🐛 Fixed API field mapping in cloud provider: API returns `result` not `status`
|
|
34
|
+
|
|
8
35
|
## [0.1.0] - 2025-10-18
|
|
9
36
|
|
|
10
37
|
### Added
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Vizzly Docs MCP Server
|
|
2
|
+
|
|
3
|
+
MCP server that provides Claude Code with easy access to Vizzly documentation.
|
|
4
|
+
|
|
5
|
+
## How It Works
|
|
6
|
+
|
|
7
|
+
This server fetches documentation from the deployed `docs.vizzly.dev` site, making it easy for LLMs to:
|
|
8
|
+
- Browse all available docs
|
|
9
|
+
- Search documentation by keyword
|
|
10
|
+
- Retrieve full markdown content
|
|
11
|
+
- Understand the documentation structure
|
|
12
|
+
|
|
13
|
+
## Tools
|
|
14
|
+
|
|
15
|
+
### `list_docs`
|
|
16
|
+
List all available documentation pages with optional category filtering.
|
|
17
|
+
|
|
18
|
+
**Arguments:**
|
|
19
|
+
- `category` (optional): Filter by category (e.g., "Integration > CLI", "Features")
|
|
20
|
+
|
|
21
|
+
**Example:**
|
|
22
|
+
```javascript
|
|
23
|
+
list_docs({ category: "CLI" })
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### `get_doc`
|
|
27
|
+
Get the full markdown content of a specific documentation page.
|
|
28
|
+
|
|
29
|
+
**Arguments:**
|
|
30
|
+
- `path` (required): The document path (e.g., "integration/cli/overview.mdx") or slug (e.g., "integration/cli/overview")
|
|
31
|
+
|
|
32
|
+
**Example:**
|
|
33
|
+
```javascript
|
|
34
|
+
get_doc({ path: "integration/cli/tdd-mode" })
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### `search_docs`
|
|
38
|
+
Search documentation by keyword. Searches in titles, descriptions, and categories.
|
|
39
|
+
|
|
40
|
+
**Arguments:**
|
|
41
|
+
- `query` (required): Search query
|
|
42
|
+
- `limit` (optional): Maximum number of results (default: 10)
|
|
43
|
+
|
|
44
|
+
**Example:**
|
|
45
|
+
```javascript
|
|
46
|
+
search_docs({ query: "parallel builds", limit: 5 })
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### `get_sidebar`
|
|
50
|
+
Get the complete sidebar navigation structure.
|
|
51
|
+
|
|
52
|
+
**Example:**
|
|
53
|
+
```javascript
|
|
54
|
+
get_sidebar()
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Architecture
|
|
58
|
+
|
|
59
|
+
The server works in a hybrid model:
|
|
60
|
+
1. **Index** - Fetches a pre-built JSON index from `docs.vizzly.dev/api/mcp-index.json`
|
|
61
|
+
2. **Content** - Fetches individual doc content on-demand from `docs.vizzly.dev/api/content/{path}`
|
|
62
|
+
3. **Caching** - Index is cached for 5 minutes to minimize network requests
|
|
63
|
+
|
|
64
|
+
This approach ensures:
|
|
65
|
+
- Fast browsing/searching (uses cached index)
|
|
66
|
+
- Always up-to-date content (fetched from live site)
|
|
67
|
+
- Minimal network overhead
|
|
68
|
+
- No local file dependencies
|
|
69
|
+
|
|
70
|
+
## Data Flow
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
Claude Code
|
|
74
|
+
↓
|
|
75
|
+
MCP Server (this)
|
|
76
|
+
↓
|
|
77
|
+
docs.vizzly.dev API
|
|
78
|
+
↓
|
|
79
|
+
Statically generated at build time (Astro)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Development
|
|
83
|
+
|
|
84
|
+
To test the server locally:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
# The server is automatically loaded by Claude Code when the plugin is active
|
|
88
|
+
# Check .mcp.json for configuration
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Files
|
|
92
|
+
|
|
93
|
+
- `index.js` - Main MCP server implementation
|
|
94
|
+
- `docs-fetcher.js` - Utilities for fetching and searching docs
|
|
95
|
+
- `README.md` - This file
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fetches documentation from docs.vizzly.dev
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
let BASE_URL = 'https://docs.vizzly.dev';
|
|
6
|
+
let INDEX_URL = `${BASE_URL}/api/mcp-index.json`;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Fetch the documentation index
|
|
10
|
+
*/
|
|
11
|
+
export async function fetchDocsIndex() {
|
|
12
|
+
try {
|
|
13
|
+
let response = await fetch(INDEX_URL);
|
|
14
|
+
|
|
15
|
+
if (!response.ok) {
|
|
16
|
+
throw new Error(`Failed to fetch docs index: ${response.status} ${response.statusText}`);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
let index = await response.json();
|
|
20
|
+
return index;
|
|
21
|
+
} catch (error) {
|
|
22
|
+
throw new Error(`Failed to load documentation index: ${error.message}`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Fetch the content of a specific document
|
|
28
|
+
*/
|
|
29
|
+
export async function fetchDocContent(path) {
|
|
30
|
+
// Normalize path (remove leading slash, ensure it doesn't end with .md/.mdx)
|
|
31
|
+
let cleanPath = path.replace(/^\//, '').replace(/\.(mdx?|md)$/, '');
|
|
32
|
+
|
|
33
|
+
let contentUrl = `${BASE_URL}/api/content/${cleanPath}`;
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
let response = await fetch(contentUrl);
|
|
37
|
+
|
|
38
|
+
if (!response.ok) {
|
|
39
|
+
// Try with .mdx extension if not found
|
|
40
|
+
if (response.status === 404 && !path.endsWith('.mdx')) {
|
|
41
|
+
contentUrl = `${BASE_URL}/api/content/${cleanPath}.mdx`;
|
|
42
|
+
response = await fetch(contentUrl);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (!response.ok) {
|
|
46
|
+
throw new Error(`Document not found: ${path} (${response.status})`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
let content = await response.text();
|
|
51
|
+
return content;
|
|
52
|
+
} catch (error) {
|
|
53
|
+
throw new Error(`Failed to fetch document content: ${error.message}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Search documents by query
|
|
59
|
+
* Simple client-side search through titles and descriptions
|
|
60
|
+
*/
|
|
61
|
+
export function searchDocs(docs, query, limit = 10) {
|
|
62
|
+
let lowerQuery = query.toLowerCase();
|
|
63
|
+
let terms = lowerQuery.split(/\s+/);
|
|
64
|
+
|
|
65
|
+
let results = docs
|
|
66
|
+
.map(doc => {
|
|
67
|
+
let titleLower = doc.title.toLowerCase();
|
|
68
|
+
let descLower = (doc.description || '').toLowerCase();
|
|
69
|
+
let categoryLower = doc.category.toLowerCase();
|
|
70
|
+
|
|
71
|
+
let score = 0;
|
|
72
|
+
|
|
73
|
+
// Exact phrase match in title (highest score)
|
|
74
|
+
if (titleLower.includes(lowerQuery)) {
|
|
75
|
+
score += 10;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Exact phrase match in description
|
|
79
|
+
if (descLower.includes(lowerQuery)) {
|
|
80
|
+
score += 5;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Exact phrase match in category
|
|
84
|
+
if (categoryLower.includes(lowerQuery)) {
|
|
85
|
+
score += 3;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Individual term matches
|
|
89
|
+
for (let term of terms) {
|
|
90
|
+
if (titleLower.includes(term)) score += 3;
|
|
91
|
+
if (descLower.includes(term)) score += 1;
|
|
92
|
+
if (categoryLower.includes(term)) score += 0.5;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return { doc, score };
|
|
96
|
+
})
|
|
97
|
+
.filter(result => result.score > 0)
|
|
98
|
+
.sort((a, b) => b.score - a.score)
|
|
99
|
+
.slice(0, limit);
|
|
100
|
+
|
|
101
|
+
// Normalize scores to 0-1 range
|
|
102
|
+
if (results.length > 0) {
|
|
103
|
+
let maxScore = results[0].score;
|
|
104
|
+
for (let result of results) {
|
|
105
|
+
result.score = result.score / maxScore;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return results;
|
|
110
|
+
}
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Vizzly Docs MCP Server
|
|
5
|
+
* Provides Claude Code with easy access to Vizzly documentation
|
|
6
|
+
*
|
|
7
|
+
* This server fetches docs from the deployed docs.vizzly.dev site,
|
|
8
|
+
* making it easy for LLMs to navigate and retrieve documentation content.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
12
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
13
|
+
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
14
|
+
import { fetchDocsIndex, fetchDocContent, searchDocs } from './docs-fetcher.js';
|
|
15
|
+
|
|
16
|
+
class VizzlyDocsMCPServer {
|
|
17
|
+
constructor() {
|
|
18
|
+
this.server = new Server(
|
|
19
|
+
{
|
|
20
|
+
name: 'vizzly-docs',
|
|
21
|
+
version: '1.0.0'
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
capabilities: {
|
|
25
|
+
tools: {}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
// Cache the index for the session
|
|
31
|
+
this.indexCache = null;
|
|
32
|
+
this.indexFetchTime = null;
|
|
33
|
+
this.CACHE_TTL = 5 * 60 * 1000; // 5 minutes
|
|
34
|
+
|
|
35
|
+
this.setupHandlers();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Get the docs index (with caching)
|
|
40
|
+
*/
|
|
41
|
+
async getIndex() {
|
|
42
|
+
let now = Date.now();
|
|
43
|
+
|
|
44
|
+
if (!this.indexCache || !this.indexFetchTime || now - this.indexFetchTime > this.CACHE_TTL) {
|
|
45
|
+
this.indexCache = await fetchDocsIndex();
|
|
46
|
+
this.indexFetchTime = now;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return this.indexCache;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
setupHandlers() {
|
|
53
|
+
// List available tools
|
|
54
|
+
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
55
|
+
tools: [
|
|
56
|
+
{
|
|
57
|
+
name: 'list_docs',
|
|
58
|
+
description:
|
|
59
|
+
'List all available Vizzly documentation pages. Returns title, description, category, and URL for each doc. Optionally filter by category.',
|
|
60
|
+
inputSchema: {
|
|
61
|
+
type: 'object',
|
|
62
|
+
properties: {
|
|
63
|
+
category: {
|
|
64
|
+
type: 'string',
|
|
65
|
+
description:
|
|
66
|
+
'Optional category filter (e.g., "Integration > CLI", "Features"). Case-insensitive partial match.'
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: 'get_doc',
|
|
73
|
+
description:
|
|
74
|
+
'Get the full markdown content of a specific documentation page. Returns the raw MDX/markdown with frontmatter. Use the path or slug from list_docs.',
|
|
75
|
+
inputSchema: {
|
|
76
|
+
type: 'object',
|
|
77
|
+
properties: {
|
|
78
|
+
path: {
|
|
79
|
+
type: 'string',
|
|
80
|
+
description:
|
|
81
|
+
'The document path (e.g., "integration/cli/overview.mdx") or slug (e.g., "integration/cli/overview"). Get this from list_docs or search_docs.'
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
required: ['path']
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
name: 'search_docs',
|
|
89
|
+
description:
|
|
90
|
+
'Search documentation by keyword. Searches in titles and descriptions. Returns matching docs with relevance scores.',
|
|
91
|
+
inputSchema: {
|
|
92
|
+
type: 'object',
|
|
93
|
+
properties: {
|
|
94
|
+
query: {
|
|
95
|
+
type: 'string',
|
|
96
|
+
description: 'Search query (e.g., "TDD mode", "authentication", "parallel builds")'
|
|
97
|
+
},
|
|
98
|
+
limit: {
|
|
99
|
+
type: 'number',
|
|
100
|
+
description: 'Maximum number of results to return (default: 10)',
|
|
101
|
+
default: 10
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
required: ['query']
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
name: 'get_sidebar',
|
|
109
|
+
description:
|
|
110
|
+
'Get the complete sidebar navigation structure. Useful for understanding how docs are organized and finding related pages.',
|
|
111
|
+
inputSchema: {
|
|
112
|
+
type: 'object',
|
|
113
|
+
properties: {}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
]
|
|
117
|
+
}));
|
|
118
|
+
|
|
119
|
+
// Handle tool calls
|
|
120
|
+
this.server.setRequestHandler(CallToolRequestSchema, async request => {
|
|
121
|
+
try {
|
|
122
|
+
switch (request.params.name) {
|
|
123
|
+
case 'list_docs':
|
|
124
|
+
return await this.handleListDocs(request.params.arguments);
|
|
125
|
+
|
|
126
|
+
case 'get_doc':
|
|
127
|
+
return await this.handleGetDoc(request.params.arguments);
|
|
128
|
+
|
|
129
|
+
case 'search_docs':
|
|
130
|
+
return await this.handleSearchDocs(request.params.arguments);
|
|
131
|
+
|
|
132
|
+
case 'get_sidebar':
|
|
133
|
+
return await this.handleGetSidebar();
|
|
134
|
+
|
|
135
|
+
default:
|
|
136
|
+
throw new Error(`Unknown tool: ${request.params.name}`);
|
|
137
|
+
}
|
|
138
|
+
} catch (error) {
|
|
139
|
+
return {
|
|
140
|
+
content: [
|
|
141
|
+
{
|
|
142
|
+
type: 'text',
|
|
143
|
+
text: `Error: ${error.message}`
|
|
144
|
+
}
|
|
145
|
+
],
|
|
146
|
+
isError: true
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async handleListDocs(args) {
|
|
153
|
+
let index = await this.getIndex();
|
|
154
|
+
let { category } = args || {};
|
|
155
|
+
|
|
156
|
+
let docs = index.docs;
|
|
157
|
+
|
|
158
|
+
// Filter by category if provided
|
|
159
|
+
if (category) {
|
|
160
|
+
let lowerCategory = category.toLowerCase();
|
|
161
|
+
docs = docs.filter(doc => doc.category.toLowerCase().includes(lowerCategory));
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Format response
|
|
165
|
+
let response = `# Vizzly Documentation (${docs.length} docs)\n\n`;
|
|
166
|
+
|
|
167
|
+
if (category) {
|
|
168
|
+
response += `Filtered by category: "${category}"\n\n`;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Group by category
|
|
172
|
+
let byCategory = {};
|
|
173
|
+
for (let doc of docs) {
|
|
174
|
+
if (!byCategory[doc.category]) {
|
|
175
|
+
byCategory[doc.category] = [];
|
|
176
|
+
}
|
|
177
|
+
byCategory[doc.category].push(doc);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
for (let [cat, catDocs] of Object.entries(byCategory)) {
|
|
181
|
+
response += `## ${cat}\n\n`;
|
|
182
|
+
for (let doc of catDocs) {
|
|
183
|
+
response += `- **${doc.title}**\n`;
|
|
184
|
+
response += ` - Path: \`${doc.path}\`\n`;
|
|
185
|
+
response += ` - Slug: \`${doc.slug}\`\n`;
|
|
186
|
+
if (doc.description) {
|
|
187
|
+
response += ` - ${doc.description}\n`;
|
|
188
|
+
}
|
|
189
|
+
response += ` - URL: ${doc.url}\n\n`;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return {
|
|
194
|
+
content: [
|
|
195
|
+
{
|
|
196
|
+
type: 'text',
|
|
197
|
+
text: response
|
|
198
|
+
}
|
|
199
|
+
]
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
async handleGetDoc(args) {
|
|
204
|
+
let { path } = args;
|
|
205
|
+
|
|
206
|
+
if (!path) {
|
|
207
|
+
throw new Error('path parameter is required');
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
let content = await fetchDocContent(path);
|
|
211
|
+
|
|
212
|
+
return {
|
|
213
|
+
content: [
|
|
214
|
+
{
|
|
215
|
+
type: 'text',
|
|
216
|
+
text: content
|
|
217
|
+
}
|
|
218
|
+
]
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async handleSearchDocs(args) {
|
|
223
|
+
let { query, limit = 10 } = args;
|
|
224
|
+
|
|
225
|
+
if (!query) {
|
|
226
|
+
throw new Error('query parameter is required');
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
let index = await this.getIndex();
|
|
230
|
+
let results = searchDocs(index.docs, query, limit);
|
|
231
|
+
|
|
232
|
+
let response = `# Search Results for "${query}"\n\n`;
|
|
233
|
+
response += `Found ${results.length} matching docs:\n\n`;
|
|
234
|
+
|
|
235
|
+
for (let result of results) {
|
|
236
|
+
response += `## ${result.doc.title}\n`;
|
|
237
|
+
response += `- **Category:** ${result.doc.category}\n`;
|
|
238
|
+
response += `- **Path:** \`${result.doc.path}\`\n`;
|
|
239
|
+
response += `- **Relevance:** ${Math.round(result.score * 100)}%\n`;
|
|
240
|
+
if (result.doc.description) {
|
|
241
|
+
response += `- **Description:** ${result.doc.description}\n`;
|
|
242
|
+
}
|
|
243
|
+
response += `- **URL:** ${result.doc.url}\n\n`;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return {
|
|
247
|
+
content: [
|
|
248
|
+
{
|
|
249
|
+
type: 'text',
|
|
250
|
+
text: response
|
|
251
|
+
}
|
|
252
|
+
]
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
async handleGetSidebar() {
|
|
257
|
+
let index = await this.getIndex();
|
|
258
|
+
|
|
259
|
+
let response = `# Vizzly Documentation Structure\n\n`;
|
|
260
|
+
response += JSON.stringify(index.sidebar, null, 2);
|
|
261
|
+
|
|
262
|
+
return {
|
|
263
|
+
content: [
|
|
264
|
+
{
|
|
265
|
+
type: 'text',
|
|
266
|
+
text: response
|
|
267
|
+
}
|
|
268
|
+
]
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
async run() {
|
|
273
|
+
let transport = new StdioServerTransport();
|
|
274
|
+
await this.server.connect(transport);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Start the server
|
|
279
|
+
let server = new VizzlyDocsMCPServer();
|
|
280
|
+
server.run().catch(error => {
|
|
281
|
+
console.error('Server error:', error);
|
|
282
|
+
process.exit(1);
|
|
283
|
+
});
|
|
@@ -49,27 +49,43 @@ export class CloudAPIProvider {
|
|
|
49
49
|
let { build } = data;
|
|
50
50
|
|
|
51
51
|
// Calculate comparison summary
|
|
52
|
+
// Note: API returns 'result' field (not 'status'), and doesn't have 'has_diff'
|
|
53
|
+
// result can be: 'identical', 'changed', 'new', 'removed', 'error', 'missing', 'returning'
|
|
52
54
|
let comparisons = build.comparisons || [];
|
|
55
|
+
|
|
56
|
+
// Calculate basic comparison summary
|
|
57
|
+
let changedComparisons = comparisons.filter((c) => c.result === 'changed' || (c.diff_percentage && c.diff_percentage > 0));
|
|
58
|
+
|
|
53
59
|
let summary = {
|
|
54
60
|
total: comparisons.length,
|
|
55
|
-
new: comparisons.filter((c) => c.
|
|
56
|
-
changed:
|
|
57
|
-
identical: comparisons.filter((c) =>
|
|
61
|
+
new: comparisons.filter((c) => c.result === 'new').length,
|
|
62
|
+
changed: changedComparisons.length,
|
|
63
|
+
identical: comparisons.filter((c) => c.result === 'identical' || (c.result !== 'new' && c.result !== 'changed' && (!c.diff_percentage || c.diff_percentage === 0))).length,
|
|
64
|
+
// Approval status breakdown
|
|
65
|
+
approval: {
|
|
66
|
+
pending: comparisons.filter((c) => c.approval_status === 'pending').length,
|
|
67
|
+
approved: comparisons.filter((c) => c.approval_status === 'approved').length,
|
|
68
|
+
rejected: comparisons.filter((c) => c.approval_status === 'rejected').length,
|
|
69
|
+
autoApproved: comparisons.filter((c) => c.approval_status === 'auto_approved').length
|
|
70
|
+
},
|
|
71
|
+
// Flaky screenshot count
|
|
72
|
+
flaky: comparisons.filter((c) => c.is_flaky).length
|
|
58
73
|
};
|
|
59
74
|
|
|
60
|
-
//
|
|
75
|
+
// Keep minimal for token efficiency - use read_comparison_details for full metadata
|
|
61
76
|
let failedComparisons = comparisons
|
|
62
|
-
.filter((c) => c.
|
|
77
|
+
.filter((c) => c.result === 'changed' || (c.diff_percentage && c.diff_percentage > 0))
|
|
63
78
|
.map((c) => ({
|
|
64
|
-
|
|
79
|
+
id: c.id,
|
|
80
|
+
name: c.current_name || c.name,
|
|
65
81
|
diffPercentage: c.diff_percentage,
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
82
|
+
approvalStatus: c.approval_status,
|
|
83
|
+
// Include hot spot coverage % for quick triage (single number)
|
|
84
|
+
hotSpotCoverage: c.analysis_metadata?.hot_spot_coverage || null
|
|
69
85
|
}));
|
|
70
86
|
|
|
71
87
|
let newComparisons = comparisons
|
|
72
|
-
.filter((c) => c.
|
|
88
|
+
.filter((c) => c.result === 'new')
|
|
73
89
|
.map((c) => ({
|
|
74
90
|
name: c.name,
|
|
75
91
|
currentUrl: c.current_screenshot?.original_url
|
|
@@ -131,6 +131,15 @@ class VizzlyMCPServer {
|
|
|
131
131
|
workingDirectory: {
|
|
132
132
|
type: 'string',
|
|
133
133
|
description: 'Path to project directory (optional, defaults to current directory)'
|
|
134
|
+
},
|
|
135
|
+
statusFilter: {
|
|
136
|
+
type: 'string',
|
|
137
|
+
description: 'Filter comparisons by status: "failed", "new", "passed", or "all". Defaults to "summary" (no comparisons, just counts)',
|
|
138
|
+
enum: ['failed', 'new', 'passed', 'all', 'summary']
|
|
139
|
+
},
|
|
140
|
+
limit: {
|
|
141
|
+
type: 'number',
|
|
142
|
+
description: 'Maximum number of comparisons to return (default: unlimited when statusFilter is set)'
|
|
134
143
|
}
|
|
135
144
|
}
|
|
136
145
|
}
|
|
@@ -556,7 +565,11 @@ class VizzlyMCPServer {
|
|
|
556
565
|
}
|
|
557
566
|
|
|
558
567
|
case 'get_tdd_status': {
|
|
559
|
-
let status = await this.localProvider.getTDDStatus(
|
|
568
|
+
let status = await this.localProvider.getTDDStatus(
|
|
569
|
+
args.workingDirectory,
|
|
570
|
+
args.statusFilter,
|
|
571
|
+
args.limit
|
|
572
|
+
);
|
|
560
573
|
return {
|
|
561
574
|
content: [
|
|
562
575
|
{
|