Compare commits
115 Commits
20458add3f
...
v5.2.0
| Author | SHA1 | Date | |
|---|---|---|---|
| 74198743ab | |||
| cde5c67134 | |||
| baad41da98 | |||
| d57bff184e | |||
| f6d9fcaae2 | |||
| 8d94bb606c | |||
| b175d4d890 | |||
| 6973f657d7 | |||
| a0d1b38c6e | |||
| c5f68256c5 | |||
| 9698e8724d | |||
| f3e1f42413 | |||
| 8a957b1b69 | |||
| 6e90064160 | |||
| 5c4e97a3f6 | |||
| 351be5a40d | |||
| 67944a7e1c | |||
| e37653f956 | |||
| 235e72d3d7 | |||
| ba8e86e31c | |||
| 67f330be6c | |||
| 445b744196 | |||
| ad73c526b7 | |||
| 26310d05f0 | |||
| 459550e7d3 | |||
| a69a4d19d0 | |||
| f2a62627d0 | |||
| 0abf510ec0 | |||
| 008187a0a4 | |||
| 4bd15e5deb | |||
| 8234683bc3 | |||
| 5b3da8da85 | |||
| 894e015c01 | |||
| a66a2bc519 | |||
| b8851a0ae3 | |||
| aee199e6cf | |||
| 223a2d626a | |||
| b7fce0fafd | |||
| 551c60fb45 | |||
| af6a42b2ac | |||
| 7cae21f7c9 | |||
| 8048fba931 | |||
| 1b36ca77ab | |||
| eb85ea31bb | |||
| 8627d9e968 | |||
| 3da9adf44e | |||
| bcb24ae641 | |||
| c8ede3c30b | |||
| fb1c664309 | |||
| 90f19dfc0f | |||
| 75492b0d38 | |||
| 54bb347ee1 | |||
| 51bcc26ea9 | |||
| d813147ca7 | |||
| dbb6d46fa4 | |||
| e7050e2ad8 | |||
| 206f1c378e | |||
| 35380594b4 | |||
| 0055c9ecf2 | |||
| a74a048898 | |||
| 37676d4645 | |||
| 7492cfad66 | |||
| 59db9ea0b0 | |||
| 9234cf1add | |||
| a21199d3db | |||
| 1abda1ca0f | |||
| 0118bc7b9b | |||
| bbb822db16 | |||
| 08e1dcb1f5 | |||
| ec7141a5aa | |||
| 1b029d97b8 | |||
| 4ed3ed7e14 | |||
| c5232bd7bf | |||
| f9e23fd6eb | |||
| 457ed9c9ff | |||
| dadb4d3576 | |||
| ba771f100f | |||
| 2b9cb5defd | |||
| 34227126c2 | |||
| ef94602eba | |||
| 155e7be399 | |||
| 1fb9e6cece | |||
| f2cf082ba8 | |||
| d580464f4a | |||
| fe6b354ee2 | |||
| ec965dc8ee | |||
| cb07a382ea | |||
| 8f450c0e7b | |||
| fdee539371 | |||
| 0e9187c5a9 | |||
| 46af00019c | |||
| 2b041cb771 | |||
| 0fc40d0fda | |||
| 68f50fed55 | |||
| 9d5615409c | |||
| 48ce693bb5 | |||
| 74531e06d0 | |||
| bc0282b5f8 | |||
| db67d3cc76 | |||
| a933edeef1 | |||
| 1df9573f7a | |||
| 35d5f14003 | |||
| 5321b2929e | |||
| 05aa50d409 | |||
| 54e8e694b1 | |||
| 8ec7cbb1e9 | |||
| 3519a96d06 | |||
| f809c672b5 | |||
| 5d205c9c13 | |||
| efb83e0f28 | |||
| 7bedfa2c65 | |||
| 42ab4f13cf | |||
| 77dc122079 | |||
| ce774bcc6f | |||
| b3abe863af |
@@ -6,7 +6,7 @@
|
|||||||
},
|
},
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"description": "Project management plugins with Gitea and NetBox integrations",
|
"description": "Project management plugins with Gitea and NetBox integrations",
|
||||||
"version": "4.1.0"
|
"version": "5.2.0"
|
||||||
},
|
},
|
||||||
"plugins": [
|
"plugins": [
|
||||||
{
|
{
|
||||||
@@ -75,8 +75,8 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "cmdb-assistant",
|
"name": "cmdb-assistant",
|
||||||
"version": "1.0.0",
|
"version": "1.1.0",
|
||||||
"description": "NetBox CMDB integration for infrastructure management",
|
"description": "NetBox CMDB integration with data quality validation and machine registration",
|
||||||
"source": "./plugins/cmdb-assistant",
|
"source": "./plugins/cmdb-assistant",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Leo Miranda",
|
"name": "Leo Miranda",
|
||||||
@@ -86,7 +86,7 @@
|
|||||||
"repository": "https://gitea.hotserv.cloud/personal-projects/leo-claude-mktplace.git",
|
"repository": "https://gitea.hotserv.cloud/personal-projects/leo-claude-mktplace.git",
|
||||||
"mcpServers": ["./.mcp.json"],
|
"mcpServers": ["./.mcp.json"],
|
||||||
"category": "infrastructure",
|
"category": "infrastructure",
|
||||||
"tags": ["cmdb", "netbox", "dcim", "ipam"],
|
"tags": ["cmdb", "netbox", "dcim", "ipam", "data-quality", "validation"],
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -181,6 +181,22 @@
|
|||||||
"category": "visualization",
|
"category": "visualization",
|
||||||
"tags": ["dash", "plotly", "mantine", "charts", "dashboards", "theming", "dmc"],
|
"tags": ["dash", "plotly", "mantine", "charts", "dashboards", "theming", "dmc"],
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "contract-validator",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Cross-plugin compatibility validation and Claude.md agent verification",
|
||||||
|
"source": "./plugins/contract-validator",
|
||||||
|
"author": {
|
||||||
|
"name": "Leo Miranda",
|
||||||
|
"email": "leobmiranda@gmail.com"
|
||||||
|
},
|
||||||
|
"homepage": "https://gitea.hotserv.cloud/personal-projects/leo-claude-mktplace/src/branch/main/plugins/contract-validator/README.md",
|
||||||
|
"repository": "https://gitea.hotserv.cloud/personal-projects/leo-claude-mktplace.git",
|
||||||
|
"mcpServers": ["./.mcp.json"],
|
||||||
|
"category": "development",
|
||||||
|
"tags": ["validation", "contracts", "compatibility", "agents", "interfaces", "cross-plugin"],
|
||||||
|
"license": "MIT"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -31,6 +31,8 @@ venv/
|
|||||||
ENV/
|
ENV/
|
||||||
env/
|
env/
|
||||||
.venv/
|
.venv/
|
||||||
|
.venv
|
||||||
|
**/.venv
|
||||||
|
|
||||||
# PyCharm
|
# PyCharm
|
||||||
.idea/
|
.idea/
|
||||||
|
|||||||
151
CHANGELOG.md
151
CHANGELOG.md
@@ -4,7 +4,150 @@ All notable changes to the Leo Claude Marketplace will be documented in this fil
|
|||||||
|
|
||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
|
|
||||||
## [Unreleased]
|
## [5.2.0] - 2026-01-28
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
#### Sprint 5: Documentation (V5.2.0 Plugin Enhancements)
|
||||||
|
Documentation and guides for the plugin enhancements initiative.
|
||||||
|
|
||||||
|
**git-flow v1.2.0:**
|
||||||
|
- **Branching Strategy Guide** (`docs/BRANCHING-STRATEGY.md`) - Complete documentation of `development -> staging -> main` promotion flow with Mermaid diagrams
|
||||||
|
|
||||||
|
**clarity-assist v1.2.0:**
|
||||||
|
- **ND Support Guide** (`docs/ND-SUPPORT.md`) - Documentation of neurodivergent accommodations, features, and usage examples
|
||||||
|
|
||||||
|
**Gitea MCP Server:**
|
||||||
|
- **`update_issue` milestone parameter** - Can now assign/change milestones programmatically
|
||||||
|
|
||||||
|
**Sprint Completed:**
|
||||||
|
- Milestone: Sprint 5 - Documentation (closed 2026-01-28)
|
||||||
|
- Issues: #266, #267, #268, #269
|
||||||
|
- Wiki: [Change V5.2.0: Plugin Enhancements (Sprint 5 Documentation)](https://gitea.hotserv.cloud/personal-projects/leo-claude-mktplace/wiki/Change-V5.2.0%3A-Plugin-Enhancements-%28Sprint-5-Documentation%29)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### Sprint 4: Commands (V5.2.0 Plugin Enhancements)
|
||||||
|
Implementation of 18 new user-facing commands across 8 plugins.
|
||||||
|
|
||||||
|
**projman v3.3.0:**
|
||||||
|
- **`/sprint-diagram`** - Generate Mermaid diagram of sprint issues with dependencies and status
|
||||||
|
|
||||||
|
**pr-review v1.1.0:**
|
||||||
|
- **`/pr-diff`** - Formatted diff with inline review comments and annotations
|
||||||
|
- **Confidence threshold config** - `PR_REVIEW_CONFIDENCE_THRESHOLD` env var (default: 0.7)
|
||||||
|
|
||||||
|
**data-platform v1.2.0:**
|
||||||
|
- **`/data-quality`** - DataFrame quality checks (nulls, duplicates, types, outliers) with pass/warn/fail scoring
|
||||||
|
- **`/lineage-viz`** - dbt lineage visualization as Mermaid diagrams
|
||||||
|
- **`/dbt-test`** - Formatted dbt test runner with summary and failure details
|
||||||
|
|
||||||
|
**viz-platform v1.1.0:**
|
||||||
|
- **`/chart-export`** - Export charts to PNG, SVG, PDF via kaleido
|
||||||
|
- **`/accessibility-check`** - Color blind validation (WCAG contrast ratios)
|
||||||
|
- **`/breakpoints`** - Responsive layout breakpoint configuration
|
||||||
|
- **New MCP tools**: `chart_export`, `accessibility_validate_colors`, `accessibility_validate_theme`, `accessibility_suggest_alternative`, `layout_set_breakpoints`
|
||||||
|
- **New dependency**: kaleido>=0.2.1 for chart rendering
|
||||||
|
|
||||||
|
**contract-validator v1.2.0:**
|
||||||
|
- **`/dependency-graph`** - Mermaid visualization of plugin dependencies with data flow
|
||||||
|
|
||||||
|
**doc-guardian v1.1.0:**
|
||||||
|
- **`/changelog-gen`** - Generate changelog from conventional commits
|
||||||
|
- **`/doc-coverage`** - Documentation coverage metrics by function/class
|
||||||
|
- **`/stale-docs`** - Flag documentation behind code changes
|
||||||
|
|
||||||
|
**claude-config-maintainer v1.1.0:**
|
||||||
|
- **`/config-diff`** - Track CLAUDE.md changes over time with behavioral impact analysis
|
||||||
|
- **`/config-lint`** - 31 lint rules for CLAUDE.md (security, structure, content, format, best practices)
|
||||||
|
|
||||||
|
**cmdb-assistant v1.2.0:**
|
||||||
|
- **`/cmdb-topology`** - Infrastructure topology diagrams (rack, network, site views)
|
||||||
|
- **`/change-audit`** - NetBox audit trail queries with filtering
|
||||||
|
- **`/ip-conflicts`** - Detect IP conflicts and overlapping prefixes
|
||||||
|
|
||||||
|
**Sprint Completed:**
|
||||||
|
- Milestone: Sprint 4 - Commands (closed 2026-01-28)
|
||||||
|
- Issues: #241-#258 (18/18 closed)
|
||||||
|
- Wiki: [Change V5.2.0: Plugin Enhancements (Sprint 4 Commands)](https://gitea.hotserv.cloud/personal-projects/leo-claude-mktplace/wiki/Change-V5.2.0%3A-Plugin-Enhancements-%28Sprint-4-Commands%29)
|
||||||
|
- Lessons: [Sprint 4 - Plugin Commands Implementation](https://gitea.hotserv.cloud/personal-projects/leo-claude-mktplace/wiki/lessons/sprints/sprint-4---plugin-commands-implementation)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- **MCP:** Project directory detection - all run.sh scripts now capture `CLAUDE_PROJECT_DIR` from PWD before changing directories
|
||||||
|
- **Docs:** Added Gitea auto-close behavior and MCP session restart notes to DEBUGGING-CHECKLIST.md
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### Sprint 3: Hooks (V5.2.0 Plugin Enhancements)
|
||||||
|
Implementation of 6 foundational hooks across 4 plugins.
|
||||||
|
|
||||||
|
**git-flow v1.1.0:**
|
||||||
|
- **Commit message enforcement hook** - PreToolUse hook validates conventional commit format on all `git commit` commands (not just `/commit`). Blocks invalid commits with format guidance.
|
||||||
|
- **Branch name validation hook** - PreToolUse hook validates branch naming on `git checkout -b` and `git switch -c`. Enforces `type/description` format, lowercase, max 50 chars.
|
||||||
|
|
||||||
|
**clarity-assist v1.1.0:**
|
||||||
|
- **Vagueness detection hook** - UserPromptSubmit hook detects vague prompts and suggests `/clarify` when ambiguity, missing context, or unclear scope detected.
|
||||||
|
|
||||||
|
**data-platform v1.1.0:**
|
||||||
|
- **Schema diff detection hook** - PostToolUse hook monitors edits to schema files (dbt models, SQL migrations). Warns on breaking changes (column removal, type narrowing, constraint addition).
|
||||||
|
|
||||||
|
**contract-validator v1.1.0:**
|
||||||
|
- **SessionStart auto-validate hook** - Smart validation that only runs when plugin files changed since last check. Detects interface compatibility issues at session start.
|
||||||
|
- **Breaking change detection hook** - PostToolUse hook monitors plugin interface files (README.md, plugin.json). Warns when changes would break consumers.
|
||||||
|
|
||||||
|
**Sprint Completed:**
|
||||||
|
- Milestone: Sprint 3 - Hooks (closed 2026-01-28)
|
||||||
|
- Issues: #225, #226, #227, #228, #229, #230
|
||||||
|
- Wiki: [Change V5.2.0: Plugin Enhancements Proposal](https://gitea.hotserv.cloud/personal-projects/leo-claude-mktplace/wiki/Change-V5.2.0:-Plugin-Enhancements-Proposal)
|
||||||
|
- Lessons: Background agent permissions, agent runaway detection, MCP branch detection bug
|
||||||
|
|
||||||
|
### Known Issues
|
||||||
|
- **MCP Bug #231:** Branch detection in Gitea MCP runs from installed plugin directory, not user's project directory. Workaround: close issues via Gitea web UI.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### Gitea MCP Server - create_pull_request Tool
|
||||||
|
- **`create_pull_request`**: Create new pull requests via MCP
|
||||||
|
- Parameters: title, body, head (source branch), base (target branch), labels
|
||||||
|
- Branch-aware security: only allowed on development/feature branches
|
||||||
|
- Completes the PR lifecycle (was previously missing - only had list/get/review/comment)
|
||||||
|
|
||||||
|
#### cmdb-assistant v1.1.0 - Data Quality Validation
|
||||||
|
- **SessionStart Hook**: Tests NetBox API connectivity at session start
|
||||||
|
- Warns if VMs exist without site assignment
|
||||||
|
- Warns if devices exist without platform
|
||||||
|
- Non-blocking: displays warning, doesn't prevent work
|
||||||
|
- **PreToolUse Hook**: Validates input parameters before VM/device operations
|
||||||
|
- Warns about missing site, tenant, platform
|
||||||
|
- Non-blocking: suggests best practices without blocking
|
||||||
|
- **`/cmdb-audit` Command**: Comprehensive data quality analysis
|
||||||
|
- Scopes: all, vms, devices, naming, roles
|
||||||
|
- Identifies Critical/High/Medium/Low issues
|
||||||
|
- Provides prioritized remediation recommendations
|
||||||
|
- **`/cmdb-register` Command**: Register current machine into NetBox
|
||||||
|
- Discovers system info: hostname, platform, hardware, network interfaces
|
||||||
|
- Discovers running apps: Docker containers, systemd services
|
||||||
|
- Creates device with interfaces, IPs, and sets primary IP
|
||||||
|
- Creates cluster and VMs for Docker containers
|
||||||
|
- **`/cmdb-sync` Command**: Sync machine state with NetBox
|
||||||
|
- Compares current state with NetBox record
|
||||||
|
- Shows diff of changes (interfaces, IPs, containers)
|
||||||
|
- Updates with user confirmation
|
||||||
|
- Supports --full and --dry-run flags
|
||||||
|
- **NetBox Best Practices Skill**: Reference documentation
|
||||||
|
- Dependency order for object creation
|
||||||
|
- Naming conventions (`{role}-{site}-{number}`, `{env}-{app}-{number}`)
|
||||||
|
- Role consolidation guidance
|
||||||
|
- Site/tenant/platform assignment requirements
|
||||||
|
- **Agent Enhancement**: Updated cmdb-assistant agent with validation requirements
|
||||||
|
- Proactive suggestions for missing fields
|
||||||
|
- Naming convention checks
|
||||||
|
- Dependency order enforcement
|
||||||
|
- Duplicate prevention
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [5.0.0] - 2026-01-26
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
@@ -15,12 +158,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||||||
- Static JSON registry approach for fast, deterministic validation
|
- Static JSON registry approach for fast, deterministic validation
|
||||||
- **Chart Tools** (2 tools): `chart_create`, `chart_configure_interaction`
|
- **Chart Tools** (2 tools): `chart_create`, `chart_configure_interaction`
|
||||||
- Plotly-based visualization with theme token support
|
- Plotly-based visualization with theme token support
|
||||||
- **Layout Tools** (3 tools): `layout_create`, `layout_add_filter`, `layout_set_grid`
|
- **Layout Tools** (5 tools): `layout_create`, `layout_add_filter`, `layout_set_grid`, `layout_get`, `layout_add_section`
|
||||||
- Dashboard composition with responsive grid system
|
- Dashboard composition with responsive grid system
|
||||||
- **Theme Tools** (4 tools): `theme_create`, `theme_extend`, `theme_validate`, `theme_export_css`
|
- **Theme Tools** (6 tools): `theme_create`, `theme_extend`, `theme_validate`, `theme_export_css`, `theme_list`, `theme_activate`
|
||||||
- Design token-based theming system
|
- Design token-based theming system
|
||||||
- Dual storage: user-level (`~/.config/claude/themes/`) and project-level
|
- Dual storage: user-level (`~/.config/claude/themes/`) and project-level
|
||||||
- **Page Tools** (3 tools): `page_create`, `page_add_navbar`, `page_set_auth`
|
- **Page Tools** (5 tools): `page_create`, `page_add_navbar`, `page_set_auth`, `page_list`, `page_get_app_config`
|
||||||
- Multi-page Dash app structure generation
|
- Multi-page Dash app structure generation
|
||||||
- **Commands**: `/chart`, `/dashboard`, `/theme`, `/theme-new`, `/theme-css`, `/component`, `/initial-setup`
|
- **Commands**: `/chart`, `/dashboard`, `/theme`, `/theme-new`, `/theme-css`, `/component`, `/initial-setup`
|
||||||
- **Agents**: `theme-setup`, `layout-builder`, `component-check`
|
- **Agents**: `theme-setup`, `layout-builder`, `component-check`
|
||||||
|
|||||||
@@ -50,14 +50,14 @@ See `docs/DEBUGGING-CHECKLIST.md` for details on cache timing.
|
|||||||
## Project Overview
|
## Project Overview
|
||||||
|
|
||||||
**Repository:** leo-claude-mktplace
|
**Repository:** leo-claude-mktplace
|
||||||
**Version:** 4.0.0
|
**Version:** 5.1.0
|
||||||
**Status:** Production Ready
|
**Status:** Production Ready
|
||||||
|
|
||||||
A plugin marketplace for Claude Code containing:
|
A plugin marketplace for Claude Code containing:
|
||||||
|
|
||||||
| Plugin | Description | Version |
|
| Plugin | Description | Version |
|
||||||
|--------|-------------|---------|
|
|--------|-------------|---------|
|
||||||
| `projman` | Sprint planning and project management with Gitea integration | 3.1.0 |
|
| `projman` | Sprint planning and project management with Gitea integration | 3.2.0 |
|
||||||
| `git-flow` | Git workflow automation with smart commits and branch management | 1.0.0 |
|
| `git-flow` | Git workflow automation with smart commits and branch management | 1.0.0 |
|
||||||
| `pr-review` | Multi-agent PR review with confidence scoring | 1.0.0 |
|
| `pr-review` | Multi-agent PR review with confidence scoring | 1.0.0 |
|
||||||
| `clarity-assist` | Prompt optimization with ND-friendly accommodations | 1.0.0 |
|
| `clarity-assist` | Prompt optimization with ND-friendly accommodations | 1.0.0 |
|
||||||
@@ -67,6 +67,7 @@ A plugin marketplace for Claude Code containing:
|
|||||||
| `cmdb-assistant` | NetBox CMDB integration for infrastructure management | 1.0.0 |
|
| `cmdb-assistant` | NetBox CMDB integration for infrastructure management | 1.0.0 |
|
||||||
| `data-platform` | pandas, PostgreSQL, and dbt integration for data engineering | 1.0.0 |
|
| `data-platform` | pandas, PostgreSQL, and dbt integration for data engineering | 1.0.0 |
|
||||||
| `viz-platform` | DMC validation, Plotly charts, and theming for dashboards | 1.0.0 |
|
| `viz-platform` | DMC validation, Plotly charts, and theming for dashboards | 1.0.0 |
|
||||||
|
| `contract-validator` | Cross-plugin compatibility validation and agent verification | 1.0.0 |
|
||||||
| `project-hygiene` | Post-task cleanup automation via hooks | 0.1.0 |
|
| `project-hygiene` | Post-task cleanup automation via hooks | 0.1.0 |
|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
@@ -93,6 +94,7 @@ A plugin marketplace for Claude Code containing:
|
|||||||
| **Config** | `/config-analyze`, `/config-optimize` |
|
| **Config** | `/config-analyze`, `/config-optimize` |
|
||||||
| **Data** | `/ingest`, `/profile`, `/schema`, `/explain`, `/lineage`, `/run` |
|
| **Data** | `/ingest`, `/profile`, `/schema`, `/explain`, `/lineage`, `/run` |
|
||||||
| **Visualization** | `/component`, `/chart`, `/dashboard`, `/theme`, `/theme-new`, `/theme-css` |
|
| **Visualization** | `/component`, `/chart`, `/dashboard`, `/theme`, `/theme-new`, `/theme-css` |
|
||||||
|
| **Validation** | `/validate-contracts`, `/check-agent`, `/list-interfaces` |
|
||||||
| **Debug** | `/debug-report`, `/debug-review` |
|
| **Debug** | `/debug-report`, `/debug-review` |
|
||||||
|
|
||||||
## Repository Structure
|
## Repository Structure
|
||||||
@@ -104,6 +106,7 @@ leo-claude-mktplace/
|
|||||||
├── mcp-servers/ # SHARED MCP servers (v3.0.0+)
|
├── mcp-servers/ # SHARED MCP servers (v3.0.0+)
|
||||||
│ ├── gitea/ # Gitea MCP (issues, PRs, wiki)
|
│ ├── gitea/ # Gitea MCP (issues, PRs, wiki)
|
||||||
│ ├── netbox/ # NetBox MCP (CMDB)
|
│ ├── netbox/ # NetBox MCP (CMDB)
|
||||||
|
│ ├── data-platform/ # pandas, PostgreSQL, dbt
|
||||||
│ └── viz-platform/ # DMC validation, charts, themes
|
│ └── viz-platform/ # DMC validation, charts, themes
|
||||||
├── plugins/
|
├── plugins/
|
||||||
│ ├── projman/ # Sprint management
|
│ ├── projman/ # Sprint management
|
||||||
|
|||||||
71
README.md
71
README.md
@@ -1,4 +1,4 @@
|
|||||||
# Leo Claude Marketplace - v4.1.0
|
# Leo Claude Marketplace - v5.2.0
|
||||||
|
|
||||||
A collection of Claude Code plugins for project management, infrastructure automation, and development workflows.
|
A collection of Claude Code plugins for project management, infrastructure automation, and development workflows.
|
||||||
|
|
||||||
@@ -19,7 +19,7 @@ AI-guided sprint planning with full Gitea integration. Transforms a proven 15-sp
|
|||||||
- Branch-aware security (development/staging/production)
|
- Branch-aware security (development/staging/production)
|
||||||
- Pre-sprint-close code quality review and test verification
|
- Pre-sprint-close code quality review and test verification
|
||||||
|
|
||||||
**Commands:** `/sprint-plan`, `/sprint-start`, `/sprint-status`, `/sprint-close`, `/labels-sync`, `/initial-setup`, `/project-init`, `/project-sync`, `/review`, `/test-check`, `/test-gen`, `/debug-report`, `/debug-review`
|
**Commands:** `/sprint-plan`, `/sprint-start`, `/sprint-status`, `/sprint-close`, `/labels-sync`, `/initial-setup`, `/project-init`, `/project-sync`, `/review`, `/test-check`, `/test-gen`, `/debug-report`, `/debug-review`, `/suggest-version`, `/proposal-status`
|
||||||
|
|
||||||
#### [git-flow](./plugins/git-flow/README.md) *NEW in v3.0.0*
|
#### [git-flow](./plugins/git-flow/README.md) *NEW in v3.0.0*
|
||||||
**Git Workflow Automation**
|
**Git Workflow Automation**
|
||||||
@@ -53,6 +53,19 @@ Analyze, optimize, and create CLAUDE.md configuration files for Claude Code proj
|
|||||||
|
|
||||||
**Commands:** `/config-analyze`, `/config-optimize`, `/config-init`
|
**Commands:** `/config-analyze`, `/config-optimize`, `/config-init`
|
||||||
|
|
||||||
|
#### [contract-validator](./plugins/contract-validator/README.md) *NEW in v5.0.0*
|
||||||
|
**Cross-Plugin Compatibility Validation**
|
||||||
|
|
||||||
|
Validate plugin marketplaces for command conflicts, tool overlaps, and broken agent references.
|
||||||
|
|
||||||
|
- Interface parsing from plugin README.md files
|
||||||
|
- Agent extraction from CLAUDE.md definitions
|
||||||
|
- Pairwise compatibility checks between all plugins
|
||||||
|
- Data flow validation for agent sequences
|
||||||
|
- Markdown or JSON reports with actionable suggestions
|
||||||
|
|
||||||
|
**Commands:** `/validate-contracts`, `/check-agent`, `/list-interfaces`, `/initial-setup`
|
||||||
|
|
||||||
### Productivity
|
### Productivity
|
||||||
|
|
||||||
#### [clarity-assist](./plugins/clarity-assist/README.md) *NEW in v3.0.0*
|
#### [clarity-assist](./plugins/clarity-assist/README.md) *NEW in v3.0.0*
|
||||||
@@ -94,11 +107,11 @@ Security vulnerability detection and code refactoring tools.
|
|||||||
|
|
||||||
Full CRUD operations for network infrastructure management directly from Claude Code.
|
Full CRUD operations for network infrastructure management directly from Claude Code.
|
||||||
|
|
||||||
**Commands:** `/initial-setup`, `/cmdb-search`, `/cmdb-device`, `/cmdb-ip`, `/cmdb-site`
|
**Commands:** `/initial-setup`, `/cmdb-search`, `/cmdb-device`, `/cmdb-ip`, `/cmdb-site`, `/cmdb-audit`, `/cmdb-register`, `/cmdb-sync`
|
||||||
|
|
||||||
### Data Engineering
|
### Data Engineering
|
||||||
|
|
||||||
#### [data-platform](./plugins/data-platform/README.md) *NEW*
|
#### [data-platform](./plugins/data-platform/README.md) *NEW in v4.0.0*
|
||||||
**pandas, PostgreSQL/PostGIS, and dbt Integration**
|
**pandas, PostgreSQL/PostGIS, and dbt Integration**
|
||||||
|
|
||||||
Comprehensive data engineering toolkit with persistent DataFrame storage.
|
Comprehensive data engineering toolkit with persistent DataFrame storage.
|
||||||
@@ -111,6 +124,22 @@ Comprehensive data engineering toolkit with persistent DataFrame storage.
|
|||||||
|
|
||||||
**Commands:** `/ingest`, `/profile`, `/schema`, `/explain`, `/lineage`, `/run`
|
**Commands:** `/ingest`, `/profile`, `/schema`, `/explain`, `/lineage`, `/run`
|
||||||
|
|
||||||
|
### Visualization
|
||||||
|
|
||||||
|
#### [viz-platform](./plugins/viz-platform/README.md) *NEW in v4.0.0*
|
||||||
|
**Dash Mantine Components Validation and Theming**
|
||||||
|
|
||||||
|
Visualization toolkit with version-locked component validation and design token theming.
|
||||||
|
|
||||||
|
- 3 DMC tools with static JSON registry (prevents prop hallucination)
|
||||||
|
- 2 Chart tools with Plotly and theme integration
|
||||||
|
- 5 Layout tools for dashboard composition
|
||||||
|
- 6 Theme tools with design token system
|
||||||
|
- 5 Page tools for multi-page app structure
|
||||||
|
- Dual theme storage: user-level and project-level
|
||||||
|
|
||||||
|
**Commands:** `/chart`, `/dashboard`, `/theme`, `/theme-new`, `/theme-css`, `/component`, `/initial-setup`
|
||||||
|
|
||||||
## MCP Servers
|
## MCP Servers
|
||||||
|
|
||||||
MCP servers are **shared at repository root** with **symlinks** from plugins that use them.
|
MCP servers are **shared at repository root** with **symlinks** from plugins that use them.
|
||||||
@@ -141,7 +170,7 @@ Comprehensive NetBox REST API integration for infrastructure management.
|
|||||||
| Virtualization | Clusters, VMs, Interfaces |
|
| Virtualization | Clusters, VMs, Interfaces |
|
||||||
| Extras | Tags, Custom Fields, Audit Log |
|
| Extras | Tags, Custom Fields, Audit Log |
|
||||||
|
|
||||||
### Data Platform MCP Server (shared) *NEW*
|
### Data Platform MCP Server (shared) *NEW in v4.0.0*
|
||||||
|
|
||||||
pandas, PostgreSQL/PostGIS, and dbt integration for data engineering.
|
pandas, PostgreSQL/PostGIS, and dbt integration for data engineering.
|
||||||
|
|
||||||
@@ -152,6 +181,28 @@ pandas, PostgreSQL/PostGIS, and dbt integration for data engineering.
|
|||||||
| PostGIS | `st_tables`, `st_geometry_type`, `st_srid`, `st_extent` |
|
| PostGIS | `st_tables`, `st_geometry_type`, `st_srid`, `st_extent` |
|
||||||
| dbt | `dbt_parse`, `dbt_run`, `dbt_test`, `dbt_build`, `dbt_compile`, `dbt_ls`, `dbt_docs_generate`, `dbt_lineage` |
|
| dbt | `dbt_parse`, `dbt_run`, `dbt_test`, `dbt_build`, `dbt_compile`, `dbt_ls`, `dbt_docs_generate`, `dbt_lineage` |
|
||||||
|
|
||||||
|
### Viz Platform MCP Server (shared) *NEW in v4.0.0*
|
||||||
|
|
||||||
|
Dash Mantine Components validation and visualization tools.
|
||||||
|
|
||||||
|
| Category | Tools |
|
||||||
|
|----------|-------|
|
||||||
|
| DMC | `list_components`, `get_component_props`, `validate_component` |
|
||||||
|
| Chart | `chart_create`, `chart_configure_interaction` |
|
||||||
|
| Layout | `layout_create`, `layout_add_filter`, `layout_set_grid`, `layout_get`, `layout_add_section` |
|
||||||
|
| Theme | `theme_create`, `theme_extend`, `theme_validate`, `theme_export_css`, `theme_list`, `theme_activate` |
|
||||||
|
| Page | `page_create`, `page_add_navbar`, `page_set_auth`, `page_list`, `page_get_app_config` |
|
||||||
|
|
||||||
|
### Contract Validator MCP Server (shared) *NEW in v5.0.0*
|
||||||
|
|
||||||
|
Cross-plugin compatibility validation tools.
|
||||||
|
|
||||||
|
| Category | Tools |
|
||||||
|
|----------|-------|
|
||||||
|
| Parse | `parse_plugin_interface`, `parse_claude_md_agents` |
|
||||||
|
| Validation | `validate_compatibility`, `validate_agent_refs`, `validate_data_flow` |
|
||||||
|
| Report | `generate_compatibility_report`, `list_issues` |
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
@@ -249,6 +300,8 @@ After installing plugins, the `/plugin` command may show `(no content)` - this i
|
|||||||
| claude-config-maintainer | `/claude-config-maintainer:config-analyze` |
|
| claude-config-maintainer | `/claude-config-maintainer:config-analyze` |
|
||||||
| cmdb-assistant | `/cmdb-assistant:cmdb-search` |
|
| cmdb-assistant | `/cmdb-assistant:cmdb-search` |
|
||||||
| data-platform | `/data-platform:ingest` |
|
| data-platform | `/data-platform:ingest` |
|
||||||
|
| viz-platform | `/viz-platform:chart` |
|
||||||
|
| contract-validator | `/contract-validator:validate-contracts` |
|
||||||
|
|
||||||
## Repository Structure
|
## Repository Structure
|
||||||
|
|
||||||
@@ -259,13 +312,17 @@ leo-claude-mktplace/
|
|||||||
├── mcp-servers/ # SHARED MCP servers (v3.0.0+)
|
├── mcp-servers/ # SHARED MCP servers (v3.0.0+)
|
||||||
│ ├── gitea/ # Gitea MCP (issues, PRs, wiki)
|
│ ├── gitea/ # Gitea MCP (issues, PRs, wiki)
|
||||||
│ ├── netbox/ # NetBox MCP (CMDB)
|
│ ├── netbox/ # NetBox MCP (CMDB)
|
||||||
│ └── data-platform/ # Data engineering (pandas, PostgreSQL, dbt)
|
│ ├── data-platform/ # Data engineering (pandas, PostgreSQL, dbt)
|
||||||
|
│ ├── viz-platform/ # Visualization (DMC, Plotly, theming)
|
||||||
|
│ └── contract-validator/ # Cross-plugin validation (v5.0.0)
|
||||||
├── plugins/ # All plugins
|
├── plugins/ # All plugins
|
||||||
│ ├── projman/ # Sprint management
|
│ ├── projman/ # Sprint management
|
||||||
│ ├── git-flow/ # Git workflow automation
|
│ ├── git-flow/ # Git workflow automation
|
||||||
│ ├── pr-review/ # PR review
|
│ ├── pr-review/ # PR review
|
||||||
│ ├── clarity-assist/ # Prompt optimization
|
│ ├── clarity-assist/ # Prompt optimization
|
||||||
│ ├── data-platform/ # Data engineering (NEW)
|
│ ├── data-platform/ # Data engineering
|
||||||
|
│ ├── viz-platform/ # Visualization
|
||||||
|
│ ├── contract-validator/ # Cross-plugin validation (NEW)
|
||||||
│ ├── claude-config-maintainer/ # CLAUDE.md optimization
|
│ ├── claude-config-maintainer/ # CLAUDE.md optimization
|
||||||
│ ├── cmdb-assistant/ # NetBox CMDB integration
|
│ ├── cmdb-assistant/ # NetBox CMDB integration
|
||||||
│ ├── doc-guardian/ # Documentation drift detection
|
│ ├── doc-guardian/ # Documentation drift detection
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
**This file defines ALL valid paths in this repository. No exceptions. No inference. No assumptions.**
|
**This file defines ALL valid paths in this repository. No exceptions. No inference. No assumptions.**
|
||||||
|
|
||||||
Last Updated: 2026-01-23 (v3.1.2)
|
Last Updated: 2026-01-27 (v5.1.0)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -37,8 +37,40 @@ leo-claude-mktplace/
|
|||||||
│ │ │ └── pull_requests.py # NEW in v3.0.0
|
│ │ │ └── pull_requests.py # NEW in v3.0.0
|
||||||
│ │ ├── requirements.txt
|
│ │ ├── requirements.txt
|
||||||
│ │ └── .venv/
|
│ │ └── .venv/
|
||||||
│ └── netbox/ # NetBox MCP server
|
│ ├── netbox/ # NetBox MCP server
|
||||||
|
│ │ ├── mcp_server/
|
||||||
|
│ │ ├── requirements.txt
|
||||||
|
│ │ └── .venv/
|
||||||
|
│ ├── data-platform/ # Data engineering MCP (NEW v4.0.0)
|
||||||
|
│ │ ├── mcp_server/
|
||||||
|
│ │ │ ├── server.py
|
||||||
|
│ │ │ ├── pandas_tools.py
|
||||||
|
│ │ │ ├── postgres_tools.py
|
||||||
|
│ │ │ └── dbt_tools.py
|
||||||
|
│ │ ├── requirements.txt
|
||||||
|
│ │ └── .venv/
|
||||||
|
│ ├── contract-validator/ # Contract validation MCP (NEW v5.0.0)
|
||||||
|
│ │ ├── mcp_server/
|
||||||
|
│ │ │ ├── server.py
|
||||||
|
│ │ │ ├── parse_tools.py
|
||||||
|
│ │ │ ├── validation_tools.py
|
||||||
|
│ │ │ └── report_tools.py
|
||||||
|
│ │ ├── tests/
|
||||||
|
│ │ ├── requirements.txt
|
||||||
|
│ │ └── .venv/
|
||||||
|
│ └── viz-platform/ # Visualization MCP (NEW v4.1.0)
|
||||||
│ ├── mcp_server/
|
│ ├── mcp_server/
|
||||||
|
│ │ ├── server.py
|
||||||
|
│ │ ├── config.py
|
||||||
|
│ │ ├── component_registry.py
|
||||||
|
│ │ ├── dmc_tools.py
|
||||||
|
│ │ ├── chart_tools.py
|
||||||
|
│ │ ├── layout_tools.py
|
||||||
|
│ │ ├── theme_tools.py
|
||||||
|
│ │ ├── theme_store.py
|
||||||
|
│ │ └── page_tools.py
|
||||||
|
│ ├── registry/ # DMC component JSON registries
|
||||||
|
│ ├── tests/ # 94 tests
|
||||||
│ ├── requirements.txt
|
│ ├── requirements.txt
|
||||||
│ └── .venv/
|
│ └── .venv/
|
||||||
├── plugins/ # ALL plugins
|
├── plugins/ # ALL plugins
|
||||||
@@ -94,20 +126,50 @@ leo-claude-mktplace/
|
|||||||
│ │ ├── agents/
|
│ │ ├── agents/
|
||||||
│ │ ├── skills/
|
│ │ ├── skills/
|
||||||
│ │ └── claude-md-integration.md
|
│ │ └── claude-md-integration.md
|
||||||
│ └── pr-review/ # NEW in v3.0.0
|
│ ├── pr-review/ # NEW in v3.0.0
|
||||||
|
│ │ ├── .claude-plugin/
|
||||||
|
│ │ ├── .mcp.json
|
||||||
|
│ │ ├── mcp-servers/
|
||||||
|
│ │ │ └── gitea -> ../../../mcp-servers/gitea # SYMLINK
|
||||||
|
│ │ ├── commands/
|
||||||
|
│ │ ├── agents/
|
||||||
|
│ │ ├── skills/
|
||||||
|
│ │ └── claude-md-integration.md
|
||||||
|
│ ├── data-platform/ # NEW in v4.0.0
|
||||||
|
│ │ ├── .claude-plugin/
|
||||||
|
│ │ ├── .mcp.json
|
||||||
|
│ │ ├── mcp-servers/
|
||||||
|
│ │ │ └── data-platform -> ../../../mcp-servers/data-platform # SYMLINK
|
||||||
|
│ │ ├── commands/
|
||||||
|
│ │ ├── agents/
|
||||||
|
│ │ ├── hooks/
|
||||||
|
│ │ └── claude-md-integration.md
|
||||||
|
│ ├── contract-validator/ # NEW in v5.0.0
|
||||||
|
│ │ ├── .claude-plugin/
|
||||||
|
│ │ ├── .mcp.json
|
||||||
|
│ │ ├── mcp-servers/
|
||||||
|
│ │ │ └── contract-validator -> ../../../mcp-servers/contract-validator # SYMLINK
|
||||||
|
│ │ ├── commands/
|
||||||
|
│ │ ├── agents/
|
||||||
|
│ │ └── claude-md-integration.md
|
||||||
|
│ └── viz-platform/ # NEW in v4.1.0
|
||||||
│ ├── .claude-plugin/
|
│ ├── .claude-plugin/
|
||||||
│ ├── .mcp.json
|
│ ├── .mcp.json
|
||||||
│ ├── mcp-servers/
|
│ ├── mcp-servers/
|
||||||
│ │ └── gitea -> ../../../mcp-servers/gitea # SYMLINK
|
│ │ └── viz-platform -> ../../../mcp-servers/viz-platform # SYMLINK
|
||||||
│ ├── commands/
|
│ ├── commands/
|
||||||
│ ├── agents/
|
│ ├── agents/
|
||||||
│ ├── skills/
|
│ ├── hooks/
|
||||||
│ └── claude-md-integration.md
|
│ └── claude-md-integration.md
|
||||||
├── scripts/ # Setup and maintenance scripts
|
├── scripts/ # Setup and maintenance scripts
|
||||||
│ ├── setup.sh # Initial setup (create venvs, config templates)
|
│ ├── setup.sh # Initial setup (create venvs, config templates)
|
||||||
│ ├── post-update.sh # Post-update (rebuild venvs, verify symlinks)
|
│ ├── post-update.sh # Post-update (rebuild venvs, verify symlinks)
|
||||||
│ ├── check-venv.sh # Check if venvs exist (for hooks)
|
│ ├── check-venv.sh # Check if venvs exist (for hooks)
|
||||||
│ └── validate-marketplace.sh # Marketplace compliance validation
|
│ ├── validate-marketplace.sh # Marketplace compliance validation
|
||||||
|
│ ├── verify-hooks.sh # Verify all hooks use correct event types
|
||||||
|
│ ├── setup-venvs.sh # Setup/repair MCP server venvs
|
||||||
|
│ ├── venv-repair.sh # Repair broken venv symlinks
|
||||||
|
│ └── release.sh # Release automation with version bumping
|
||||||
├── CLAUDE.md
|
├── CLAUDE.md
|
||||||
├── README.md
|
├── README.md
|
||||||
├── LICENSE
|
├── LICENSE
|
||||||
@@ -226,6 +288,9 @@ MCP servers are now **shared at repository root** with **symlinks** from plugins
|
|||||||
plugins/projman/mcp-servers/gitea -> ../../../mcp-servers/gitea
|
plugins/projman/mcp-servers/gitea -> ../../../mcp-servers/gitea
|
||||||
plugins/cmdb-assistant/mcp-servers/netbox -> ../../../mcp-servers/netbox
|
plugins/cmdb-assistant/mcp-servers/netbox -> ../../../mcp-servers/netbox
|
||||||
plugins/pr-review/mcp-servers/gitea -> ../../../mcp-servers/gitea
|
plugins/pr-review/mcp-servers/gitea -> ../../../mcp-servers/gitea
|
||||||
|
plugins/data-platform/mcp-servers/data-platform -> ../../../mcp-servers/data-platform
|
||||||
|
plugins/viz-platform/mcp-servers/viz-platform -> ../../../mcp-servers/viz-platform
|
||||||
|
plugins/contract-validator/mcp-servers/contract-validator -> ../../../mcp-servers/contract-validator
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -234,6 +299,9 @@ plugins/pr-review/mcp-servers/gitea -> ../../../mcp-servers/gitea
|
|||||||
|
|
||||||
| Date | Change | By |
|
| Date | Change | By |
|
||||||
|------|--------|-----|
|
|------|--------|-----|
|
||||||
|
| 2026-01-26 | v5.0.0: Added contract-validator plugin and MCP server | Claude Code |
|
||||||
|
| 2026-01-26 | v4.1.0: Added viz-platform plugin and MCP server | Claude Code |
|
||||||
|
| 2026-01-25 | v4.0.0: Added data-platform plugin and MCP server | Claude Code |
|
||||||
| 2026-01-20 | v3.0.0: MCP servers moved to root with symlinks | Claude Code |
|
| 2026-01-20 | v3.0.0: MCP servers moved to root with symlinks | Claude Code |
|
||||||
| 2026-01-20 | v3.0.0: Added clarity-assist, git-flow, pr-review plugins | Claude Code |
|
| 2026-01-20 | v3.0.0: Added clarity-assist, git-flow, pr-review plugins | Claude Code |
|
||||||
| 2026-01-20 | v3.0.0: Added docs/CONFIGURATION.md | Claude Code |
|
| 2026-01-20 | v3.0.0: Added docs/CONFIGURATION.md | Claude Code |
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ Quick reference for all commands in the Leo Claude Marketplace.
|
|||||||
| **projman** | `/debug-report` | | X | Run diagnostics and create structured issue in marketplace |
|
| **projman** | `/debug-report` | | X | Run diagnostics and create structured issue in marketplace |
|
||||||
| **projman** | `/debug-review` | | X | Investigate diagnostic issues and propose fixes with approval gates |
|
| **projman** | `/debug-review` | | X | Investigate diagnostic issues and propose fixes with approval gates |
|
||||||
| **projman** | `/suggest-version` | | X | Analyze CHANGELOG and recommend semantic version bump |
|
| **projman** | `/suggest-version` | | X | Analyze CHANGELOG and recommend semantic version bump |
|
||||||
|
| **projman** | `/proposal-status` | | X | View proposal and implementation hierarchy with status |
|
||||||
| **git-flow** | `/commit` | | X | Create commit with auto-generated conventional message |
|
| **git-flow** | `/commit` | | X | Create commit with auto-generated conventional message |
|
||||||
| **git-flow** | `/commit-push` | | X | Commit and push to remote in one operation |
|
| **git-flow** | `/commit-push` | | X | Commit and push to remote in one operation |
|
||||||
| **git-flow** | `/commit-merge` | | X | Commit current changes, then merge into target branch |
|
| **git-flow** | `/commit-merge` | | X | Commit current changes, then merge into target branch |
|
||||||
@@ -55,6 +56,9 @@ Quick reference for all commands in the Leo Claude Marketplace.
|
|||||||
| **cmdb-assistant** | `/cmdb-device` | | X | Manage network devices (create, view, update, delete) |
|
| **cmdb-assistant** | `/cmdb-device` | | X | Manage network devices (create, view, update, delete) |
|
||||||
| **cmdb-assistant** | `/cmdb-ip` | | X | Manage IP addresses and prefixes |
|
| **cmdb-assistant** | `/cmdb-ip` | | X | Manage IP addresses and prefixes |
|
||||||
| **cmdb-assistant** | `/cmdb-site` | | X | Manage sites, locations, racks, and regions |
|
| **cmdb-assistant** | `/cmdb-site` | | X | Manage sites, locations, racks, and regions |
|
||||||
|
| **cmdb-assistant** | `/cmdb-audit` | | X | Data quality analysis (VMs, devices, naming, roles) |
|
||||||
|
| **cmdb-assistant** | `/cmdb-register` | | X | Register current machine into NetBox with running apps |
|
||||||
|
| **cmdb-assistant** | `/cmdb-sync` | | X | Sync machine state with NetBox (detect drift, update) |
|
||||||
| **project-hygiene** | *PostToolUse hook* | X | | Removes temp files, warns about unexpected root files |
|
| **project-hygiene** | *PostToolUse hook* | X | | Removes temp files, warns about unexpected root files |
|
||||||
| **data-platform** | `/ingest` | | X | Load data from CSV, Parquet, JSON into DataFrame |
|
| **data-platform** | `/ingest` | | X | Load data from CSV, Parquet, JSON into DataFrame |
|
||||||
| **data-platform** | `/profile` | | X | Generate data profiling report with statistics |
|
| **data-platform** | `/profile` | | X | Generate data profiling report with statistics |
|
||||||
@@ -64,6 +68,14 @@ Quick reference for all commands in the Leo Claude Marketplace.
|
|||||||
| **data-platform** | `/run` | | X | Run dbt models with validation |
|
| **data-platform** | `/run` | | X | Run dbt models with validation |
|
||||||
| **data-platform** | `/initial-setup` | | X | Setup wizard for data-platform MCP servers |
|
| **data-platform** | `/initial-setup` | | X | Setup wizard for data-platform MCP servers |
|
||||||
| **data-platform** | *SessionStart hook* | X | | Checks PostgreSQL connection (non-blocking warning) |
|
| **data-platform** | *SessionStart hook* | X | | Checks PostgreSQL connection (non-blocking warning) |
|
||||||
|
| **viz-platform** | `/initial-setup` | | X | Setup wizard for viz-platform MCP server |
|
||||||
|
| **viz-platform** | `/chart` | | X | Create Plotly charts with theme integration |
|
||||||
|
| **viz-platform** | `/dashboard` | | X | Create dashboard layouts with filters and grids |
|
||||||
|
| **viz-platform** | `/theme` | | X | Apply existing theme to visualizations |
|
||||||
|
| **viz-platform** | `/theme-new` | | X | Create new custom theme with design tokens |
|
||||||
|
| **viz-platform** | `/theme-css` | | X | Export theme as CSS custom properties |
|
||||||
|
| **viz-platform** | `/component` | | X | Inspect DMC component props and validation |
|
||||||
|
| **viz-platform** | *SessionStart hook* | X | | Checks DMC version (non-blocking warning) |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -78,6 +90,7 @@ Quick reference for all commands in the Leo Claude Marketplace.
|
|||||||
| **Git Operations** | git-flow | Commits, branches, workflow automation |
|
| **Git Operations** | git-flow | Commits, branches, workflow automation |
|
||||||
| **Infrastructure** | cmdb-assistant | NetBox CMDB management |
|
| **Infrastructure** | cmdb-assistant | NetBox CMDB management |
|
||||||
| **Data Engineering** | data-platform | pandas, PostgreSQL, dbt operations |
|
| **Data Engineering** | data-platform | pandas, PostgreSQL, dbt operations |
|
||||||
|
| **Visualization** | viz-platform | DMC validation, Plotly charts, theming |
|
||||||
| **Maintenance** | project-hygiene | Automatic cleanup |
|
| **Maintenance** | project-hygiene | Automatic cleanup |
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -92,6 +105,7 @@ Quick reference for all commands in the Leo Claude Marketplace.
|
|||||||
| **code-sentinel** | PreToolUse (Write/Edit) | Scans for security issues; blocks critical vulnerabilities |
|
| **code-sentinel** | PreToolUse (Write/Edit) | Scans for security issues; blocks critical vulnerabilities |
|
||||||
| **project-hygiene** | PostToolUse (Write/Edit) | Cleans temp files, warns about misplaced files |
|
| **project-hygiene** | PostToolUse (Write/Edit) | Cleans temp files, warns about misplaced files |
|
||||||
| **data-platform** | SessionStart | Checks PostgreSQL connection; non-blocking warning if unavailable |
|
| **data-platform** | SessionStart | Checks PostgreSQL connection; non-blocking warning if unavailable |
|
||||||
|
| **viz-platform** | SessionStart | Checks DMC version; non-blocking warning if mismatch detected |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -234,9 +248,10 @@ Some plugins require MCP server connectivity:
|
|||||||
| pr-review | Gitea | PR operations and reviews |
|
| pr-review | Gitea | PR operations and reviews |
|
||||||
| cmdb-assistant | NetBox | Infrastructure CMDB |
|
| cmdb-assistant | NetBox | Infrastructure CMDB |
|
||||||
| data-platform | pandas, PostgreSQL, dbt | DataFrames, database queries, dbt builds |
|
| data-platform | pandas, PostgreSQL, dbt | DataFrames, database queries, dbt builds |
|
||||||
|
| viz-platform | viz-platform | DMC validation, charts, layouts, themes, pages |
|
||||||
|
|
||||||
Ensure credentials are configured in `~/.config/claude/gitea.env`, `~/.config/claude/netbox.env`, or `~/.config/claude/postgres.env`.
|
Ensure credentials are configured in `~/.config/claude/gitea.env`, `~/.config/claude/netbox.env`, or `~/.config/claude/postgres.env`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
*Last Updated: 2026-01-25*
|
*Last Updated: 2026-01-26*
|
||||||
|
|||||||
@@ -171,8 +171,7 @@ This marketplace uses a **hybrid configuration** approach:
|
|||||||
│ PROJECT-LEVEL (once per project) │
|
│ PROJECT-LEVEL (once per project) │
|
||||||
│ <project-root>/.env │
|
│ <project-root>/.env │
|
||||||
├─────────────────────────────────────────────────────────────────┤
|
├─────────────────────────────────────────────────────────────────┤
|
||||||
│ GITEA_ORG │ Organization for this project │
|
│ GITEA_REPO │ Repository as owner/repo format │
|
||||||
│ GITEA_REPO │ Repository name for this project │
|
|
||||||
│ GIT_WORKFLOW_STYLE │ (optional) Override system default │
|
│ GIT_WORKFLOW_STYLE │ (optional) Override system default │
|
||||||
│ PR_REVIEW_* │ (optional) PR review settings │
|
│ PR_REVIEW_* │ (optional) PR review settings │
|
||||||
└─────────────────────────────────────────────────────────────────┘
|
└─────────────────────────────────────────────────────────────────┘
|
||||||
@@ -262,8 +261,7 @@ In each project root:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
cat > .env << 'EOF'
|
cat > .env << 'EOF'
|
||||||
GITEA_ORG=your-organization
|
GITEA_REPO=your-organization/your-repo-name
|
||||||
GITEA_REPO=your-repo-name
|
|
||||||
EOF
|
EOF
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -307,7 +305,7 @@ GITEA_API_TOKEN=your_gitea_token_here
|
|||||||
| `GITEA_API_URL` | Gitea API endpoint (with `/api/v1`) | `https://gitea.example.com/api/v1` |
|
| `GITEA_API_URL` | Gitea API endpoint (with `/api/v1`) | `https://gitea.example.com/api/v1` |
|
||||||
| `GITEA_API_TOKEN` | Personal access token | `abc123...` |
|
| `GITEA_API_TOKEN` | Personal access token | `abc123...` |
|
||||||
|
|
||||||
**Note:** `GITEA_ORG` is configured at the project level (see below) since different projects may belong to different organizations.
|
**Note:** `GITEA_REPO` is configured at the project level in `owner/repo` format since different projects may belong to different organizations.
|
||||||
|
|
||||||
**Generating a Gitea Token:**
|
**Generating a Gitea Token:**
|
||||||
1. Log into Gitea → **User Icon** → **Settings**
|
1. Log into Gitea → **User Icon** → **Settings**
|
||||||
@@ -362,9 +360,8 @@ GIT_CO_AUTHOR=true
|
|||||||
Create `.env` in each project root:
|
Create `.env` in each project root:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Required for projman, pr-review
|
# Required for projman, pr-review (use owner/repo format)
|
||||||
GITEA_ORG=your-organization
|
GITEA_REPO=your-organization/your-repo-name
|
||||||
GITEA_REPO=your-repo-name
|
|
||||||
|
|
||||||
# Optional: Override git-flow defaults
|
# Optional: Override git-flow defaults
|
||||||
GIT_WORKFLOW_STYLE=pr-required
|
GIT_WORKFLOW_STYLE=pr-required
|
||||||
@@ -377,8 +374,7 @@ PR_REVIEW_AUTO_SUBMIT=false
|
|||||||
|
|
||||||
| Variable | Required | Description |
|
| Variable | Required | Description |
|
||||||
|----------|----------|-------------|
|
|----------|----------|-------------|
|
||||||
| `GITEA_ORG` | Yes | Gitea organization for this project |
|
| `GITEA_REPO` | Yes | Repository in `owner/repo` format (e.g., `my-org/my-repo`) |
|
||||||
| `GITEA_REPO` | Yes | Repository name (must match Gitea exactly) |
|
|
||||||
| `GIT_WORKFLOW_STYLE` | No | Override system default |
|
| `GIT_WORKFLOW_STYLE` | No | Override system default |
|
||||||
| `PR_REVIEW_*` | No | PR review settings |
|
| `PR_REVIEW_*` | No | PR review settings |
|
||||||
|
|
||||||
@@ -388,11 +384,13 @@ PR_REVIEW_AUTO_SUBMIT=false
|
|||||||
|
|
||||||
| Plugin | System Config | Project Config | Setup Commands |
|
| Plugin | System Config | Project Config | Setup Commands |
|
||||||
|--------|---------------|----------------|----------------|
|
|--------|---------------|----------------|----------------|
|
||||||
| **projman** | gitea.env | .env (GITEA_ORG, GITEA_REPO) | `/initial-setup`, `/project-init`, `/project-sync` |
|
| **projman** | gitea.env | .env (GITEA_REPO=owner/repo) | `/initial-setup`, `/project-init`, `/project-sync` |
|
||||||
| **pr-review** | gitea.env | .env (GITEA_ORG, GITEA_REPO) | `/initial-setup`, `/project-init`, `/project-sync` |
|
| **pr-review** | gitea.env | .env (GITEA_REPO=owner/repo) | `/initial-setup`, `/project-init`, `/project-sync` |
|
||||||
| **git-flow** | git-flow.env (optional) | .env (optional) | None needed |
|
| **git-flow** | git-flow.env (optional) | .env (optional) | None needed |
|
||||||
| **clarity-assist** | None | None | None needed |
|
| **clarity-assist** | None | None | None needed |
|
||||||
| **cmdb-assistant** | netbox.env | None | `/initial-setup` |
|
| **cmdb-assistant** | netbox.env | None | `/initial-setup` |
|
||||||
|
| **data-platform** | postgres.env | .env (optional) | `/initial-setup` |
|
||||||
|
| **viz-platform** | None | .env (optional DMC_VERSION) | `/initial-setup` |
|
||||||
| **doc-guardian** | None | None | None needed |
|
| **doc-guardian** | None | None | None needed |
|
||||||
| **code-sentinel** | None | None | None needed |
|
| **code-sentinel** | None | None | None needed |
|
||||||
| **project-hygiene** | None | None | None needed |
|
| **project-hygiene** | None | None | None needed |
|
||||||
@@ -439,7 +437,7 @@ This catches typos and permission issues before saving configuration.
|
|||||||
|
|
||||||
When you start a Claude Code session, a hook automatically:
|
When you start a Claude Code session, a hook automatically:
|
||||||
|
|
||||||
1. Reads `GITEA_ORG` and `GITEA_REPO` from `.env`
|
1. Reads `GITEA_REPO` (in `owner/repo` format) from `.env`
|
||||||
2. Compares with current `git remote get-url origin`
|
2. Compares with current `git remote get-url origin`
|
||||||
3. **Warns** if mismatch detected: "Repository location mismatch. Run `/project-sync` to update."
|
3. **Warns** if mismatch detected: "Repository location mismatch. Run `/project-sync` to update."
|
||||||
|
|
||||||
@@ -518,7 +516,8 @@ deactivate
|
|||||||
# Check project .env
|
# Check project .env
|
||||||
cat .env
|
cat .env
|
||||||
|
|
||||||
# Verify GITEA_REPO matches the Gitea repository name exactly
|
# Verify GITEA_REPO is in owner/repo format and matches Gitea exactly
|
||||||
|
# Example: GITEA_REPO=my-org/my-repo
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
**Purpose:** Systematic approach to diagnose and fix plugin loading issues.
|
**Purpose:** Systematic approach to diagnose and fix plugin loading issues.
|
||||||
|
|
||||||
Last Updated: 2026-01-22
|
Last Updated: 2026-01-28
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -128,7 +128,7 @@ cat ~/.config/claude/netbox.env
|
|||||||
|
|
||||||
# Project-level config (in target project)
|
# Project-level config (in target project)
|
||||||
cat /path/to/project/.env
|
cat /path/to/project/.env
|
||||||
# Should contain: GITEA_ORG, GITEA_REPO
|
# Should contain: GITEA_REPO=owner/repo (e.g., my-org/my-repo)
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -186,6 +186,47 @@ echo -e "\n=== Config Files ==="
|
|||||||
| Wrong path edits | Changes don't take effect | Edit installed path or reinstall after source changes |
|
| Wrong path edits | Changes don't take effect | Edit installed path or reinstall after source changes |
|
||||||
| Missing credentials | MCP connection errors | Create `~/.config/claude/gitea.env` with API credentials |
|
| Missing credentials | MCP connection errors | Create `~/.config/claude/gitea.env` with API credentials |
|
||||||
| Invalid hook events | Hooks don't fire | Use only valid event names (see Step 7) |
|
| Invalid hook events | Hooks don't fire | Use only valid event names (see Step 7) |
|
||||||
|
| Gitea issues not closing | Merged to non-default branch | Manually close issues (see below) |
|
||||||
|
| MCP changes not taking effect | Session caching | Restart Claude Code session (see below) |
|
||||||
|
|
||||||
|
### Gitea Auto-Close Behavior
|
||||||
|
|
||||||
|
**Issue:** Using `Closes #XX` or `Fixes #XX` in commit/PR messages does NOT auto-close issues when merging to `development`.
|
||||||
|
|
||||||
|
**Root Cause:** Gitea only auto-closes issues when merging to the **default branch** (typically `main` or `master`). Merging to `development`, `staging`, or any other branch will NOT trigger auto-close.
|
||||||
|
|
||||||
|
**Workaround:**
|
||||||
|
1. Use the Gitea MCP tool to manually close issues after merging to development:
|
||||||
|
```
|
||||||
|
mcp__plugin_projman_gitea__update_issue(issue_number=XX, state="closed")
|
||||||
|
```
|
||||||
|
2. Or close issues via the Gitea web UI
|
||||||
|
3. The auto-close keywords will still work when the changes are eventually merged to `main`
|
||||||
|
|
||||||
|
**Recommendation:** Include the `Closes #XX` keywords in commits anyway - they'll work when the final merge to `main` happens.
|
||||||
|
|
||||||
|
### MCP Session Restart Requirement
|
||||||
|
|
||||||
|
**Issue:** Changes to MCP servers, hooks, or plugin configuration don't take effect immediately.
|
||||||
|
|
||||||
|
**Root Cause:** Claude Code loads MCP tools and plugin configuration at session start. These are cached in session memory and not reloaded dynamically.
|
||||||
|
|
||||||
|
**What requires a session restart:**
|
||||||
|
- MCP server code changes (Python files in `mcp-servers/`)
|
||||||
|
- Changes to `.mcp.json` files
|
||||||
|
- Changes to `hooks/hooks.json`
|
||||||
|
- Changes to `plugin.json`
|
||||||
|
- Adding new MCP tools or modifying tool signatures
|
||||||
|
|
||||||
|
**What does NOT require a restart:**
|
||||||
|
- Command/skill markdown files (`.md`) - these are read on invocation
|
||||||
|
- Agent markdown files - read when agent is invoked
|
||||||
|
|
||||||
|
**Correct workflow after plugin changes:**
|
||||||
|
1. Make changes to source files
|
||||||
|
2. Run `./scripts/verify-hooks.sh` to validate
|
||||||
|
3. Inform user: "Please restart Claude Code for changes to take effect"
|
||||||
|
4. **Do NOT clear cache mid-session** - see "Cache Clearing" section
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
3
mcp-servers/contract-validator/mcp_server/__init__.py
Normal file
3
mcp-servers/contract-validator/mcp_server/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
"""Contract Validator MCP Server - Cross-plugin compatibility validation."""
|
||||||
|
|
||||||
|
__version__ = "1.0.0"
|
||||||
415
mcp-servers/contract-validator/mcp_server/parse_tools.py
Normal file
415
mcp-servers/contract-validator/mcp_server/parse_tools.py
Normal file
@@ -0,0 +1,415 @@
|
|||||||
|
"""
|
||||||
|
Parse tools for extracting interfaces from plugin documentation.
|
||||||
|
|
||||||
|
Provides structured extraction of:
|
||||||
|
- Plugin interfaces from README.md (commands, agents, tools)
|
||||||
|
- Agent definitions from CLAUDE.md (tool sequences, workflows)
|
||||||
|
"""
|
||||||
|
import re
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Optional
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
|
class ToolInfo(BaseModel):
|
||||||
|
"""Information about a single tool"""
|
||||||
|
name: str
|
||||||
|
category: Optional[str] = None
|
||||||
|
description: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
class CommandInfo(BaseModel):
|
||||||
|
"""Information about a plugin command"""
|
||||||
|
name: str
|
||||||
|
description: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
class AgentInfo(BaseModel):
|
||||||
|
"""Information about a plugin agent"""
|
||||||
|
name: str
|
||||||
|
description: Optional[str] = None
|
||||||
|
tools: list[str] = []
|
||||||
|
|
||||||
|
|
||||||
|
class PluginInterface(BaseModel):
|
||||||
|
"""Structured plugin interface extracted from README"""
|
||||||
|
plugin_name: str
|
||||||
|
description: Optional[str] = None
|
||||||
|
commands: list[CommandInfo] = []
|
||||||
|
agents: list[AgentInfo] = []
|
||||||
|
tools: list[ToolInfo] = []
|
||||||
|
tool_categories: dict[str, list[str]] = {}
|
||||||
|
features: list[str] = []
|
||||||
|
|
||||||
|
|
||||||
|
class ClaudeMdAgent(BaseModel):
|
||||||
|
"""Agent definition extracted from CLAUDE.md"""
|
||||||
|
name: str
|
||||||
|
personality: Optional[str] = None
|
||||||
|
responsibilities: list[str] = []
|
||||||
|
tool_refs: list[str] = []
|
||||||
|
workflow_steps: list[str] = []
|
||||||
|
|
||||||
|
|
||||||
|
class ParseTools:
|
||||||
|
"""Tools for parsing plugin documentation"""
|
||||||
|
|
||||||
|
async def parse_plugin_interface(self, plugin_path: str) -> dict:
|
||||||
|
"""
|
||||||
|
Parse plugin README.md to extract interface declarations.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
plugin_path: Path to plugin directory or README.md file
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Structured interface with commands, agents, tools, etc.
|
||||||
|
"""
|
||||||
|
# Resolve path to README
|
||||||
|
path = Path(plugin_path)
|
||||||
|
if path.is_dir():
|
||||||
|
readme_path = path / "README.md"
|
||||||
|
else:
|
||||||
|
readme_path = path
|
||||||
|
|
||||||
|
if not readme_path.exists():
|
||||||
|
return {
|
||||||
|
"error": f"README.md not found at {readme_path}",
|
||||||
|
"plugin_path": plugin_path
|
||||||
|
}
|
||||||
|
|
||||||
|
content = readme_path.read_text()
|
||||||
|
plugin_name = self._extract_plugin_name(content, path)
|
||||||
|
|
||||||
|
interface = PluginInterface(
|
||||||
|
plugin_name=plugin_name,
|
||||||
|
description=self._extract_description(content),
|
||||||
|
commands=self._extract_commands(content),
|
||||||
|
agents=self._extract_agents_from_readme(content),
|
||||||
|
tools=self._extract_tools(content),
|
||||||
|
tool_categories=self._extract_tool_categories(content),
|
||||||
|
features=self._extract_features(content)
|
||||||
|
)
|
||||||
|
|
||||||
|
return interface.model_dump()
|
||||||
|
|
||||||
|
async def parse_claude_md_agents(self, claude_md_path: str) -> dict:
|
||||||
|
"""
|
||||||
|
Parse CLAUDE.md to extract agent definitions and tool sequences.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
claude_md_path: Path to CLAUDE.md file
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of agents with their tool sequences
|
||||||
|
"""
|
||||||
|
path = Path(claude_md_path)
|
||||||
|
|
||||||
|
if not path.exists():
|
||||||
|
return {
|
||||||
|
"error": f"CLAUDE.md not found at {path}",
|
||||||
|
"claude_md_path": claude_md_path
|
||||||
|
}
|
||||||
|
|
||||||
|
content = path.read_text()
|
||||||
|
agents = self._extract_agents_from_claude_md(content)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"file": str(path),
|
||||||
|
"agents": [a.model_dump() for a in agents],
|
||||||
|
"agent_count": len(agents)
|
||||||
|
}
|
||||||
|
|
||||||
|
def _extract_plugin_name(self, content: str, path: Path) -> str:
|
||||||
|
"""Extract plugin name from content or path"""
|
||||||
|
# Try to get from H1 header
|
||||||
|
match = re.search(r'^#\s+(.+?)(?:\s+Plugin|\s*$)', content, re.MULTILINE)
|
||||||
|
if match:
|
||||||
|
name = match.group(1).strip()
|
||||||
|
# Handle cases like "# data-platform Plugin"
|
||||||
|
name = re.sub(r'\s*Plugin\s*$', '', name, flags=re.IGNORECASE)
|
||||||
|
return name
|
||||||
|
|
||||||
|
# Fall back to directory name
|
||||||
|
if path.is_dir():
|
||||||
|
return path.name
|
||||||
|
return path.parent.name
|
||||||
|
|
||||||
|
def _extract_description(self, content: str) -> Optional[str]:
|
||||||
|
"""Extract plugin description from first paragraph after title"""
|
||||||
|
# Get content after H1, before first H2
|
||||||
|
match = re.search(r'^#\s+.+?\n\n(.+?)(?=\n##|\n\n##|\Z)', content, re.MULTILINE | re.DOTALL)
|
||||||
|
if match:
|
||||||
|
desc = match.group(1).strip()
|
||||||
|
# Take first paragraph only
|
||||||
|
desc = desc.split('\n\n')[0].strip()
|
||||||
|
return desc
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _extract_commands(self, content: str) -> list[CommandInfo]:
|
||||||
|
"""Extract commands from Commands section"""
|
||||||
|
commands = []
|
||||||
|
|
||||||
|
# Find Commands section
|
||||||
|
commands_section = self._extract_section(content, "Commands")
|
||||||
|
if not commands_section:
|
||||||
|
return commands
|
||||||
|
|
||||||
|
# Parse table format: | Command | Description |
|
||||||
|
# Only match actual command names (start with / or alphanumeric)
|
||||||
|
table_pattern = r'\|\s*`?(/[a-z][-a-z0-9]*)`?\s*\|\s*([^|]+)\s*\|'
|
||||||
|
for match in re.finditer(table_pattern, commands_section):
|
||||||
|
cmd_name = match.group(1).strip()
|
||||||
|
desc = match.group(2).strip()
|
||||||
|
|
||||||
|
# Skip header row and separators
|
||||||
|
if cmd_name.lower() in ('command', 'commands') or cmd_name.startswith('-'):
|
||||||
|
continue
|
||||||
|
|
||||||
|
commands.append(CommandInfo(
|
||||||
|
name=cmd_name,
|
||||||
|
description=desc
|
||||||
|
))
|
||||||
|
|
||||||
|
# Also look for ### `/command-name` format (with backticks)
|
||||||
|
cmd_header_pattern = r'^###\s+`(/[a-z][-a-z0-9]*)`\s*\n(.+?)(?=\n###|\n##|\Z)'
|
||||||
|
for match in re.finditer(cmd_header_pattern, commands_section, re.MULTILINE | re.DOTALL):
|
||||||
|
cmd_name = match.group(1).strip()
|
||||||
|
desc_block = match.group(2).strip()
|
||||||
|
# Get first line or paragraph as description
|
||||||
|
desc = desc_block.split('\n')[0].strip()
|
||||||
|
|
||||||
|
# Don't duplicate if already found in table
|
||||||
|
if not any(c.name == cmd_name for c in commands):
|
||||||
|
commands.append(CommandInfo(name=cmd_name, description=desc))
|
||||||
|
|
||||||
|
# Also look for ### /command-name format (without backticks)
|
||||||
|
cmd_header_pattern2 = r'^###\s+(/[a-z][-a-z0-9]*)\s*\n(.+?)(?=\n###|\n##|\Z)'
|
||||||
|
for match in re.finditer(cmd_header_pattern2, commands_section, re.MULTILINE | re.DOTALL):
|
||||||
|
cmd_name = match.group(1).strip()
|
||||||
|
desc_block = match.group(2).strip()
|
||||||
|
# Get first line or paragraph as description
|
||||||
|
desc = desc_block.split('\n')[0].strip()
|
||||||
|
|
||||||
|
# Don't duplicate if already found in table
|
||||||
|
if not any(c.name == cmd_name for c in commands):
|
||||||
|
commands.append(CommandInfo(name=cmd_name, description=desc))
|
||||||
|
|
||||||
|
return commands
|
||||||
|
|
||||||
|
def _extract_agents_from_readme(self, content: str) -> list[AgentInfo]:
|
||||||
|
"""Extract agents from Agents section in README"""
|
||||||
|
agents = []
|
||||||
|
|
||||||
|
# Find Agents section
|
||||||
|
agents_section = self._extract_section(content, "Agents")
|
||||||
|
if not agents_section:
|
||||||
|
return agents
|
||||||
|
|
||||||
|
# Parse table format: | Agent | Description |
|
||||||
|
# Only match actual agent names (alphanumeric with dashes/underscores)
|
||||||
|
table_pattern = r'\|\s*`?([a-z][-a-z0-9_]*)`?\s*\|\s*([^|]+)\s*\|'
|
||||||
|
for match in re.finditer(table_pattern, agents_section):
|
||||||
|
agent_name = match.group(1).strip()
|
||||||
|
desc = match.group(2).strip()
|
||||||
|
|
||||||
|
# Skip header row and separators
|
||||||
|
if agent_name.lower() in ('agent', 'agents') or agent_name.startswith('-'):
|
||||||
|
continue
|
||||||
|
|
||||||
|
agents.append(AgentInfo(name=agent_name, description=desc))
|
||||||
|
|
||||||
|
return agents
|
||||||
|
|
||||||
|
def _extract_tools(self, content: str) -> list[ToolInfo]:
|
||||||
|
"""Extract tool list from Tools Summary or similar section"""
|
||||||
|
tools = []
|
||||||
|
|
||||||
|
# Find Tools Summary section
|
||||||
|
tools_section = self._extract_section(content, "Tools Summary")
|
||||||
|
if not tools_section:
|
||||||
|
tools_section = self._extract_section(content, "Tools")
|
||||||
|
if not tools_section:
|
||||||
|
tools_section = self._extract_section(content, "MCP Server Tools")
|
||||||
|
|
||||||
|
if not tools_section:
|
||||||
|
return tools
|
||||||
|
|
||||||
|
# Parse category headers: ### category (N tools)
|
||||||
|
category_pattern = r'###\s*(.+?)\s*(?:\((\d+)\s*tools?\))?\s*\n([^#]+)'
|
||||||
|
for match in re.finditer(category_pattern, tools_section):
|
||||||
|
category = match.group(1).strip()
|
||||||
|
tool_list_text = match.group(3).strip()
|
||||||
|
|
||||||
|
# Extract tool names from backtick lists
|
||||||
|
tool_names = re.findall(r'`([a-z_]+)`', tool_list_text)
|
||||||
|
for name in tool_names:
|
||||||
|
tools.append(ToolInfo(name=name, category=category))
|
||||||
|
|
||||||
|
# Also look for inline tool lists without categories
|
||||||
|
inline_pattern = r'`([a-z_]+)`'
|
||||||
|
all_tool_names = set(t.name for t in tools)
|
||||||
|
for match in re.finditer(inline_pattern, tools_section):
|
||||||
|
name = match.group(1)
|
||||||
|
if name not in all_tool_names:
|
||||||
|
tools.append(ToolInfo(name=name))
|
||||||
|
all_tool_names.add(name)
|
||||||
|
|
||||||
|
return tools
|
||||||
|
|
||||||
|
def _extract_tool_categories(self, content: str) -> dict[str, list[str]]:
|
||||||
|
"""Extract tool categories with their tool lists"""
|
||||||
|
categories = {}
|
||||||
|
|
||||||
|
tools_section = self._extract_section(content, "Tools Summary")
|
||||||
|
if not tools_section:
|
||||||
|
tools_section = self._extract_section(content, "Tools")
|
||||||
|
if not tools_section:
|
||||||
|
return categories
|
||||||
|
|
||||||
|
# Parse category headers: ### category (N tools)
|
||||||
|
category_pattern = r'###\s*(.+?)\s*(?:\((\d+)\s*tools?\))?\s*\n([^#]+)'
|
||||||
|
for match in re.finditer(category_pattern, tools_section):
|
||||||
|
category = match.group(1).strip()
|
||||||
|
tool_list_text = match.group(3).strip()
|
||||||
|
|
||||||
|
# Extract tool names from backtick lists
|
||||||
|
tool_names = re.findall(r'`([a-z_]+)`', tool_list_text)
|
||||||
|
if tool_names:
|
||||||
|
categories[category] = tool_names
|
||||||
|
|
||||||
|
return categories
|
||||||
|
|
||||||
|
def _extract_features(self, content: str) -> list[str]:
|
||||||
|
"""Extract features from Features section"""
|
||||||
|
features = []
|
||||||
|
|
||||||
|
features_section = self._extract_section(content, "Features")
|
||||||
|
if not features_section:
|
||||||
|
return features
|
||||||
|
|
||||||
|
# Parse bullet points
|
||||||
|
bullet_pattern = r'^[-*]\s+\*\*(.+?)\*\*'
|
||||||
|
for match in re.finditer(bullet_pattern, features_section, re.MULTILINE):
|
||||||
|
features.append(match.group(1).strip())
|
||||||
|
|
||||||
|
return features
|
||||||
|
|
||||||
|
def _extract_section(self, content: str, section_name: str) -> Optional[str]:
|
||||||
|
"""Extract content of a markdown section by header name"""
|
||||||
|
# Match ## Section Name - include all content until next ## (same level or higher)
|
||||||
|
pattern = rf'^##\s+{re.escape(section_name)}(?:\s*\([^)]*\))?\s*\n(.*?)(?=\n##[^#]|\Z)'
|
||||||
|
match = re.search(pattern, content, re.MULTILINE | re.DOTALL | re.IGNORECASE)
|
||||||
|
if match:
|
||||||
|
return match.group(1).strip()
|
||||||
|
|
||||||
|
# Try ### level - include content until next ## or ###
|
||||||
|
pattern = rf'^###\s+{re.escape(section_name)}(?:\s*\([^)]*\))?\s*\n(.*?)(?=\n##|\n###[^#]|\Z)'
|
||||||
|
match = re.search(pattern, content, re.MULTILINE | re.DOTALL | re.IGNORECASE)
|
||||||
|
if match:
|
||||||
|
return match.group(1).strip()
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _extract_agents_from_claude_md(self, content: str) -> list[ClaudeMdAgent]:
|
||||||
|
"""Extract agent definitions from CLAUDE.md"""
|
||||||
|
agents = []
|
||||||
|
|
||||||
|
# Look for Four-Agent Model section specifically
|
||||||
|
# Match section headers like "### Four-Agent Model (projman)" or "## Four-Agent Model"
|
||||||
|
agent_model_match = re.search(
|
||||||
|
r'^##[#]?\s+Four-Agent Model.*?\n(.*?)(?=\n##[^#]|\Z)',
|
||||||
|
content, re.MULTILINE | re.DOTALL
|
||||||
|
)
|
||||||
|
agent_model_section = agent_model_match.group(1) if agent_model_match else None
|
||||||
|
|
||||||
|
if agent_model_section:
|
||||||
|
# Parse agent table within this section
|
||||||
|
# | **Planner** | Thoughtful, methodical | Sprint planning, ... |
|
||||||
|
# Match rows where first cell starts with ** (bold) and contains a capitalized word
|
||||||
|
agent_table_pattern = r'\|\s*\*\*([A-Z][a-zA-Z\s]+?)\*\*\s*\|\s*([^|]+)\s*\|\s*([^|]+)\s*\|'
|
||||||
|
|
||||||
|
for match in re.finditer(agent_table_pattern, agent_model_section):
|
||||||
|
agent_name = match.group(1).strip()
|
||||||
|
personality = match.group(2).strip()
|
||||||
|
responsibilities = match.group(3).strip()
|
||||||
|
|
||||||
|
# Skip header rows and separator rows
|
||||||
|
if agent_name.lower() in ('agent', 'agents', '---', '-', ''):
|
||||||
|
continue
|
||||||
|
if 'personality' in personality.lower() or '---' in personality:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Skip if personality looks like tool names (contains backticks)
|
||||||
|
if '`' in personality:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Extract tool references from responsibilities
|
||||||
|
tool_refs = re.findall(r'`([a-z_]+)`', responsibilities)
|
||||||
|
|
||||||
|
# Split responsibilities by comma
|
||||||
|
resp_list = [r.strip() for r in responsibilities.split(',')]
|
||||||
|
|
||||||
|
agents.append(ClaudeMdAgent(
|
||||||
|
name=agent_name,
|
||||||
|
personality=personality,
|
||||||
|
responsibilities=resp_list,
|
||||||
|
tool_refs=tool_refs
|
||||||
|
))
|
||||||
|
|
||||||
|
# Also look for agents table in ## Agents section
|
||||||
|
agents_section = self._extract_section(content, "Agents")
|
||||||
|
if agents_section:
|
||||||
|
# Parse table: | Agent | Description |
|
||||||
|
table_pattern = r'\|\s*`?([a-z][-a-z0-9_]+)`?\s*\|\s*([^|]+)\s*\|'
|
||||||
|
for match in re.finditer(table_pattern, agents_section):
|
||||||
|
agent_name = match.group(1).strip()
|
||||||
|
desc = match.group(2).strip()
|
||||||
|
|
||||||
|
# Skip header rows
|
||||||
|
if agent_name.lower() in ('agent', 'agents', '---', '-'):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Check if agent already exists
|
||||||
|
if not any(a.name.lower() == agent_name.lower() for a in agents):
|
||||||
|
agents.append(ClaudeMdAgent(
|
||||||
|
name=agent_name,
|
||||||
|
responsibilities=[desc] if desc else []
|
||||||
|
))
|
||||||
|
|
||||||
|
# Look for workflow sections to enrich agent data
|
||||||
|
workflow_section = self._extract_section(content, "Workflow")
|
||||||
|
if workflow_section:
|
||||||
|
# Parse numbered steps
|
||||||
|
step_pattern = r'^\d+\.\s+(.+?)$'
|
||||||
|
workflow_steps = re.findall(step_pattern, workflow_section, re.MULTILINE)
|
||||||
|
|
||||||
|
# Associate workflow steps with agents mentioned
|
||||||
|
for agent in agents:
|
||||||
|
for step in workflow_steps:
|
||||||
|
if agent.name.lower() in step.lower():
|
||||||
|
agent.workflow_steps.append(step)
|
||||||
|
# Extract any tool references in the step
|
||||||
|
step_tools = re.findall(r'`([a-z_]+)`', step)
|
||||||
|
agent.tool_refs.extend(t for t in step_tools if t not in agent.tool_refs)
|
||||||
|
|
||||||
|
# Look for agent-specific sections (### Planner Agent)
|
||||||
|
agent_section_pattern = r'^###?\s+([A-Z][a-z]+(?:\s+[A-Z][a-z]+)?)\s+Agent\s*\n(.*?)(?=\n##|\n###|\Z)'
|
||||||
|
for match in re.finditer(agent_section_pattern, content, re.MULTILINE | re.DOTALL):
|
||||||
|
agent_name = match.group(1).strip()
|
||||||
|
section_content = match.group(2).strip()
|
||||||
|
|
||||||
|
# Check if agent already exists
|
||||||
|
existing = next((a for a in agents if a.name.lower() == agent_name.lower()), None)
|
||||||
|
if existing:
|
||||||
|
# Add tool refs from this section
|
||||||
|
tool_refs = re.findall(r'`([a-z_]+)`', section_content)
|
||||||
|
existing.tool_refs.extend(t for t in tool_refs if t not in existing.tool_refs)
|
||||||
|
else:
|
||||||
|
tool_refs = re.findall(r'`([a-z_]+)`', section_content)
|
||||||
|
agents.append(ClaudeMdAgent(
|
||||||
|
name=agent_name,
|
||||||
|
tool_refs=tool_refs
|
||||||
|
))
|
||||||
|
|
||||||
|
return agents
|
||||||
337
mcp-servers/contract-validator/mcp_server/report_tools.py
Normal file
337
mcp-servers/contract-validator/mcp_server/report_tools.py
Normal file
@@ -0,0 +1,337 @@
|
|||||||
|
"""
|
||||||
|
Report tools for generating compatibility reports and listing issues.
|
||||||
|
|
||||||
|
Provides:
|
||||||
|
- generate_compatibility_report: Full marketplace validation report
|
||||||
|
- list_issues: Filtered issue listing
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
from datetime import datetime
|
||||||
|
from typing import Optional
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
from .parse_tools import ParseTools
|
||||||
|
from .validation_tools import ValidationTools, IssueSeverity, IssueType, ValidationIssue
|
||||||
|
|
||||||
|
|
||||||
|
class ReportSummary(BaseModel):
|
||||||
|
"""Summary statistics for a report"""
|
||||||
|
total_plugins: int = 0
|
||||||
|
total_commands: int = 0
|
||||||
|
total_agents: int = 0
|
||||||
|
total_tools: int = 0
|
||||||
|
total_issues: int = 0
|
||||||
|
errors: int = 0
|
||||||
|
warnings: int = 0
|
||||||
|
info: int = 0
|
||||||
|
|
||||||
|
|
||||||
|
class ReportTools:
|
||||||
|
"""Tools for generating reports and listing issues"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.parse_tools = ParseTools()
|
||||||
|
self.validation_tools = ValidationTools()
|
||||||
|
|
||||||
|
async def generate_compatibility_report(
|
||||||
|
self,
|
||||||
|
marketplace_path: str,
|
||||||
|
format: str = "markdown"
|
||||||
|
) -> dict:
|
||||||
|
"""
|
||||||
|
Generate a comprehensive compatibility report for all plugins.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
marketplace_path: Path to marketplace root directory
|
||||||
|
format: Output format ("markdown" or "json")
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Full compatibility report with all findings
|
||||||
|
"""
|
||||||
|
marketplace = Path(marketplace_path)
|
||||||
|
plugins_dir = marketplace / "plugins"
|
||||||
|
|
||||||
|
if not plugins_dir.exists():
|
||||||
|
return {
|
||||||
|
"error": f"Plugins directory not found at {plugins_dir}",
|
||||||
|
"marketplace_path": marketplace_path
|
||||||
|
}
|
||||||
|
|
||||||
|
# Discover all plugins
|
||||||
|
plugins = []
|
||||||
|
for item in plugins_dir.iterdir():
|
||||||
|
if item.is_dir() and (item / ".claude-plugin").exists():
|
||||||
|
plugins.append(item)
|
||||||
|
|
||||||
|
if not plugins:
|
||||||
|
return {
|
||||||
|
"error": "No plugins found in marketplace",
|
||||||
|
"marketplace_path": marketplace_path
|
||||||
|
}
|
||||||
|
|
||||||
|
# Parse all plugin interfaces
|
||||||
|
interfaces = {}
|
||||||
|
all_issues = []
|
||||||
|
summary = ReportSummary(total_plugins=len(plugins))
|
||||||
|
|
||||||
|
for plugin_path in plugins:
|
||||||
|
interface = await self.parse_tools.parse_plugin_interface(str(plugin_path))
|
||||||
|
if "error" not in interface:
|
||||||
|
interfaces[interface["plugin_name"]] = interface
|
||||||
|
summary.total_commands += len(interface.get("commands", []))
|
||||||
|
summary.total_agents += len(interface.get("agents", []))
|
||||||
|
summary.total_tools += len(interface.get("tools", []))
|
||||||
|
|
||||||
|
# Run pairwise compatibility checks
|
||||||
|
plugin_names = list(interfaces.keys())
|
||||||
|
compatibility_results = []
|
||||||
|
|
||||||
|
for i, name_a in enumerate(plugin_names):
|
||||||
|
for name_b in plugin_names[i+1:]:
|
||||||
|
path_a = plugins_dir / self._find_plugin_dir(plugins_dir, name_a)
|
||||||
|
path_b = plugins_dir / self._find_plugin_dir(plugins_dir, name_b)
|
||||||
|
|
||||||
|
result = await self.validation_tools.validate_compatibility(
|
||||||
|
str(path_a), str(path_b)
|
||||||
|
)
|
||||||
|
|
||||||
|
if "error" not in result:
|
||||||
|
compatibility_results.append(result)
|
||||||
|
all_issues.extend(result.get("issues", []))
|
||||||
|
|
||||||
|
# Parse CLAUDE.md if exists
|
||||||
|
claude_md = marketplace / "CLAUDE.md"
|
||||||
|
agents_from_claude = []
|
||||||
|
if claude_md.exists():
|
||||||
|
agents_result = await self.parse_tools.parse_claude_md_agents(str(claude_md))
|
||||||
|
if "error" not in agents_result:
|
||||||
|
agents_from_claude = agents_result.get("agents", [])
|
||||||
|
|
||||||
|
# Validate each agent
|
||||||
|
for agent in agents_from_claude:
|
||||||
|
agent_result = await self.validation_tools.validate_agent_refs(
|
||||||
|
agent["name"],
|
||||||
|
str(claude_md),
|
||||||
|
[str(p) for p in plugins]
|
||||||
|
)
|
||||||
|
if "error" not in agent_result:
|
||||||
|
all_issues.extend(agent_result.get("issues", []))
|
||||||
|
|
||||||
|
# Count issues by severity
|
||||||
|
for issue in all_issues:
|
||||||
|
severity = issue.get("severity", "info")
|
||||||
|
if isinstance(severity, str):
|
||||||
|
severity_str = severity.lower()
|
||||||
|
else:
|
||||||
|
severity_str = severity.value if hasattr(severity, 'value') else str(severity).lower()
|
||||||
|
|
||||||
|
if "error" in severity_str:
|
||||||
|
summary.errors += 1
|
||||||
|
elif "warning" in severity_str:
|
||||||
|
summary.warnings += 1
|
||||||
|
else:
|
||||||
|
summary.info += 1
|
||||||
|
|
||||||
|
summary.total_issues = len(all_issues)
|
||||||
|
|
||||||
|
# Generate report
|
||||||
|
if format == "json":
|
||||||
|
return {
|
||||||
|
"generated_at": datetime.now().isoformat(),
|
||||||
|
"marketplace_path": marketplace_path,
|
||||||
|
"summary": summary.model_dump(),
|
||||||
|
"plugins": interfaces,
|
||||||
|
"compatibility_checks": compatibility_results,
|
||||||
|
"claude_md_agents": agents_from_claude,
|
||||||
|
"all_issues": all_issues
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
# Generate markdown report
|
||||||
|
report = self._generate_markdown_report(
|
||||||
|
marketplace_path,
|
||||||
|
summary,
|
||||||
|
interfaces,
|
||||||
|
compatibility_results,
|
||||||
|
agents_from_claude,
|
||||||
|
all_issues
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
"generated_at": datetime.now().isoformat(),
|
||||||
|
"marketplace_path": marketplace_path,
|
||||||
|
"summary": summary.model_dump(),
|
||||||
|
"report": report
|
||||||
|
}
|
||||||
|
|
||||||
|
def _find_plugin_dir(self, plugins_dir: Path, plugin_name: str) -> str:
|
||||||
|
"""Find plugin directory by name (handles naming variations)"""
|
||||||
|
# Try exact match first
|
||||||
|
for item in plugins_dir.iterdir():
|
||||||
|
if item.is_dir():
|
||||||
|
if item.name.lower() == plugin_name.lower():
|
||||||
|
return item.name
|
||||||
|
# Check plugin.json for name
|
||||||
|
plugin_json = item / ".claude-plugin" / "plugin.json"
|
||||||
|
if plugin_json.exists():
|
||||||
|
import json
|
||||||
|
try:
|
||||||
|
data = json.loads(plugin_json.read_text())
|
||||||
|
if data.get("name", "").lower() == plugin_name.lower():
|
||||||
|
return item.name
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
return plugin_name
|
||||||
|
|
||||||
|
def _generate_markdown_report(
|
||||||
|
self,
|
||||||
|
marketplace_path: str,
|
||||||
|
summary: ReportSummary,
|
||||||
|
interfaces: dict,
|
||||||
|
compatibility_results: list,
|
||||||
|
agents: list,
|
||||||
|
issues: list
|
||||||
|
) -> str:
|
||||||
|
"""Generate markdown formatted report"""
|
||||||
|
lines = [
|
||||||
|
"# Contract Validation Report",
|
||||||
|
"",
|
||||||
|
f"**Generated:** {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}",
|
||||||
|
f"**Marketplace:** `{marketplace_path}`",
|
||||||
|
"",
|
||||||
|
"## Summary",
|
||||||
|
"",
|
||||||
|
f"| Metric | Count |",
|
||||||
|
f"|--------|-------|",
|
||||||
|
f"| Plugins | {summary.total_plugins} |",
|
||||||
|
f"| Commands | {summary.total_commands} |",
|
||||||
|
f"| Agents | {summary.total_agents} |",
|
||||||
|
f"| Tools | {summary.total_tools} |",
|
||||||
|
f"| **Issues** | **{summary.total_issues}** |",
|
||||||
|
f"| - Errors | {summary.errors} |",
|
||||||
|
f"| - Warnings | {summary.warnings} |",
|
||||||
|
f"| - Info | {summary.info} |",
|
||||||
|
"",
|
||||||
|
]
|
||||||
|
|
||||||
|
# Plugin details
|
||||||
|
lines.extend([
|
||||||
|
"## Plugins",
|
||||||
|
"",
|
||||||
|
])
|
||||||
|
|
||||||
|
for name, interface in interfaces.items():
|
||||||
|
cmds = len(interface.get("commands", []))
|
||||||
|
agents_count = len(interface.get("agents", []))
|
||||||
|
tools = len(interface.get("tools", []))
|
||||||
|
lines.append(f"### {name}")
|
||||||
|
lines.append("")
|
||||||
|
lines.append(f"- Commands: {cmds}")
|
||||||
|
lines.append(f"- Agents: {agents_count}")
|
||||||
|
lines.append(f"- Tools: {tools}")
|
||||||
|
lines.append("")
|
||||||
|
|
||||||
|
# Compatibility results
|
||||||
|
if compatibility_results:
|
||||||
|
lines.extend([
|
||||||
|
"## Compatibility Checks",
|
||||||
|
"",
|
||||||
|
])
|
||||||
|
|
||||||
|
for result in compatibility_results:
|
||||||
|
status = "✓" if result.get("compatible", True) else "✗"
|
||||||
|
lines.append(f"### {result['plugin_a']} ↔ {result['plugin_b']} {status}")
|
||||||
|
lines.append("")
|
||||||
|
|
||||||
|
if result.get("shared_tools"):
|
||||||
|
lines.append(f"- Shared tools: `{', '.join(result['shared_tools'])}`")
|
||||||
|
if result.get("issues"):
|
||||||
|
for issue in result["issues"]:
|
||||||
|
sev = issue.get("severity", "info")
|
||||||
|
if hasattr(sev, 'value'):
|
||||||
|
sev = sev.value
|
||||||
|
lines.append(f"- [{sev.upper()}] {issue['message']}")
|
||||||
|
lines.append("")
|
||||||
|
|
||||||
|
# Issues section
|
||||||
|
if issues:
|
||||||
|
lines.extend([
|
||||||
|
"## All Issues",
|
||||||
|
"",
|
||||||
|
"| Severity | Type | Message |",
|
||||||
|
"|----------|------|---------|",
|
||||||
|
])
|
||||||
|
|
||||||
|
for issue in issues:
|
||||||
|
sev = issue.get("severity", "info")
|
||||||
|
itype = issue.get("issue_type", "unknown")
|
||||||
|
msg = issue.get("message", "")
|
||||||
|
|
||||||
|
if hasattr(sev, 'value'):
|
||||||
|
sev = sev.value
|
||||||
|
if hasattr(itype, 'value'):
|
||||||
|
itype = itype.value
|
||||||
|
|
||||||
|
# Truncate message for table
|
||||||
|
msg_short = msg[:60] + "..." if len(msg) > 60 else msg
|
||||||
|
lines.append(f"| {sev} | {itype} | {msg_short} |")
|
||||||
|
|
||||||
|
lines.append("")
|
||||||
|
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
async def list_issues(
|
||||||
|
self,
|
||||||
|
marketplace_path: str,
|
||||||
|
severity: str = "all",
|
||||||
|
issue_type: str = "all"
|
||||||
|
) -> dict:
|
||||||
|
"""
|
||||||
|
List validation issues with optional filtering.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
marketplace_path: Path to marketplace root directory
|
||||||
|
severity: Filter by severity ("error", "warning", "info", "all")
|
||||||
|
issue_type: Filter by type ("missing_tool", "interface_mismatch", etc., "all")
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Filtered list of issues
|
||||||
|
"""
|
||||||
|
# Generate full report first
|
||||||
|
report = await self.generate_compatibility_report(marketplace_path, format="json")
|
||||||
|
|
||||||
|
if "error" in report:
|
||||||
|
return report
|
||||||
|
|
||||||
|
all_issues = report.get("all_issues", [])
|
||||||
|
|
||||||
|
# Filter by severity
|
||||||
|
if severity != "all":
|
||||||
|
filtered = []
|
||||||
|
for issue in all_issues:
|
||||||
|
issue_sev = issue.get("severity", "info")
|
||||||
|
if hasattr(issue_sev, 'value'):
|
||||||
|
issue_sev = issue_sev.value
|
||||||
|
if isinstance(issue_sev, str) and severity.lower() in issue_sev.lower():
|
||||||
|
filtered.append(issue)
|
||||||
|
all_issues = filtered
|
||||||
|
|
||||||
|
# Filter by type
|
||||||
|
if issue_type != "all":
|
||||||
|
filtered = []
|
||||||
|
for issue in all_issues:
|
||||||
|
itype = issue.get("issue_type", "unknown")
|
||||||
|
if hasattr(itype, 'value'):
|
||||||
|
itype = itype.value
|
||||||
|
if isinstance(itype, str) and issue_type.lower() in itype.lower():
|
||||||
|
filtered.append(issue)
|
||||||
|
all_issues = filtered
|
||||||
|
|
||||||
|
return {
|
||||||
|
"marketplace_path": marketplace_path,
|
||||||
|
"filters": {
|
||||||
|
"severity": severity,
|
||||||
|
"issue_type": issue_type
|
||||||
|
},
|
||||||
|
"total_issues": len(all_issues),
|
||||||
|
"issues": all_issues
|
||||||
|
}
|
||||||
274
mcp-servers/contract-validator/mcp_server/server.py
Normal file
274
mcp-servers/contract-validator/mcp_server/server.py
Normal file
@@ -0,0 +1,274 @@
|
|||||||
|
"""
|
||||||
|
MCP Server entry point for Contract Validator.
|
||||||
|
|
||||||
|
Provides cross-plugin compatibility validation and Claude.md agent verification
|
||||||
|
tools to Claude Code via JSON-RPC 2.0 over stdio.
|
||||||
|
"""
|
||||||
|
import asyncio
|
||||||
|
import logging
|
||||||
|
import json
|
||||||
|
from mcp.server import Server
|
||||||
|
from mcp.server.stdio import stdio_server
|
||||||
|
from mcp.types import Tool, TextContent
|
||||||
|
|
||||||
|
from .parse_tools import ParseTools
|
||||||
|
from .validation_tools import ValidationTools
|
||||||
|
from .report_tools import ReportTools
|
||||||
|
|
||||||
|
# Suppress noisy MCP validation warnings on stderr
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
logging.getLogger("root").setLevel(logging.ERROR)
|
||||||
|
logging.getLogger("mcp").setLevel(logging.ERROR)
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class ContractValidatorMCPServer:
|
||||||
|
"""MCP Server for cross-plugin compatibility validation"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.server = Server("contract-validator-mcp")
|
||||||
|
self.parse_tools = ParseTools()
|
||||||
|
self.validation_tools = ValidationTools()
|
||||||
|
self.report_tools = ReportTools()
|
||||||
|
|
||||||
|
async def initialize(self):
|
||||||
|
"""Initialize server."""
|
||||||
|
logger.info("Contract Validator MCP Server initialized")
|
||||||
|
|
||||||
|
def setup_tools(self):
|
||||||
|
"""Register all available tools with the MCP server"""
|
||||||
|
|
||||||
|
@self.server.list_tools()
|
||||||
|
async def list_tools() -> list[Tool]:
|
||||||
|
"""Return list of available tools"""
|
||||||
|
tools = [
|
||||||
|
# Parse tools (to be implemented in #186)
|
||||||
|
Tool(
|
||||||
|
name="parse_plugin_interface",
|
||||||
|
description="Parse plugin README.md to extract interface declarations (inputs, outputs, tools)",
|
||||||
|
inputSchema={
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"plugin_path": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Path to plugin directory or README.md"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["plugin_path"]
|
||||||
|
}
|
||||||
|
),
|
||||||
|
Tool(
|
||||||
|
name="parse_claude_md_agents",
|
||||||
|
description="Parse Claude.md to extract agent definitions and their tool sequences",
|
||||||
|
inputSchema={
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"claude_md_path": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Path to CLAUDE.md file"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["claude_md_path"]
|
||||||
|
}
|
||||||
|
),
|
||||||
|
# Validation tools (to be implemented in #187)
|
||||||
|
Tool(
|
||||||
|
name="validate_compatibility",
|
||||||
|
description="Validate compatibility between two plugin interfaces",
|
||||||
|
inputSchema={
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"plugin_a": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Path to first plugin"
|
||||||
|
},
|
||||||
|
"plugin_b": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Path to second plugin"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["plugin_a", "plugin_b"]
|
||||||
|
}
|
||||||
|
),
|
||||||
|
Tool(
|
||||||
|
name="validate_agent_refs",
|
||||||
|
description="Validate that all tool references in an agent definition exist",
|
||||||
|
inputSchema={
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"agent_name": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Name of agent to validate"
|
||||||
|
},
|
||||||
|
"claude_md_path": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Path to CLAUDE.md containing agent"
|
||||||
|
},
|
||||||
|
"plugin_paths": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {"type": "string"},
|
||||||
|
"description": "Paths to available plugins"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["agent_name", "claude_md_path"]
|
||||||
|
}
|
||||||
|
),
|
||||||
|
Tool(
|
||||||
|
name="validate_data_flow",
|
||||||
|
description="Validate data flow through an agent's tool sequence",
|
||||||
|
inputSchema={
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"agent_name": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Name of agent to validate"
|
||||||
|
},
|
||||||
|
"claude_md_path": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Path to CLAUDE.md containing agent"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["agent_name", "claude_md_path"]
|
||||||
|
}
|
||||||
|
),
|
||||||
|
# Report tools (to be implemented in #188)
|
||||||
|
Tool(
|
||||||
|
name="generate_compatibility_report",
|
||||||
|
description="Generate a comprehensive compatibility report for all plugins",
|
||||||
|
inputSchema={
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"marketplace_path": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Path to marketplace root directory"
|
||||||
|
},
|
||||||
|
"format": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["markdown", "json"],
|
||||||
|
"default": "markdown",
|
||||||
|
"description": "Output format"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["marketplace_path"]
|
||||||
|
}
|
||||||
|
),
|
||||||
|
Tool(
|
||||||
|
name="list_issues",
|
||||||
|
description="List validation issues with optional filtering",
|
||||||
|
inputSchema={
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"marketplace_path": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Path to marketplace root directory"
|
||||||
|
},
|
||||||
|
"severity": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["error", "warning", "info", "all"],
|
||||||
|
"default": "all",
|
||||||
|
"description": "Filter by severity"
|
||||||
|
},
|
||||||
|
"issue_type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["missing_tool", "interface_mismatch", "optional_dependency", "undeclared_output", "all"],
|
||||||
|
"default": "all",
|
||||||
|
"description": "Filter by issue type"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["marketplace_path"]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
]
|
||||||
|
return tools
|
||||||
|
|
||||||
|
@self.server.call_tool()
|
||||||
|
async def call_tool(name: str, arguments: dict) -> list[TextContent]:
|
||||||
|
"""Handle tool invocation."""
|
||||||
|
try:
|
||||||
|
# All tools return placeholder responses for now
|
||||||
|
# Actual implementation will be added in issues #186, #187, #188
|
||||||
|
|
||||||
|
if name == "parse_plugin_interface":
|
||||||
|
result = await self._parse_plugin_interface(**arguments)
|
||||||
|
elif name == "parse_claude_md_agents":
|
||||||
|
result = await self._parse_claude_md_agents(**arguments)
|
||||||
|
elif name == "validate_compatibility":
|
||||||
|
result = await self._validate_compatibility(**arguments)
|
||||||
|
elif name == "validate_agent_refs":
|
||||||
|
result = await self._validate_agent_refs(**arguments)
|
||||||
|
elif name == "validate_data_flow":
|
||||||
|
result = await self._validate_data_flow(**arguments)
|
||||||
|
elif name == "generate_compatibility_report":
|
||||||
|
result = await self._generate_compatibility_report(**arguments)
|
||||||
|
elif name == "list_issues":
|
||||||
|
result = await self._list_issues(**arguments)
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Unknown tool: {name}")
|
||||||
|
|
||||||
|
return [TextContent(
|
||||||
|
type="text",
|
||||||
|
text=json.dumps(result, indent=2, default=str)
|
||||||
|
)]
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Tool {name} failed: {e}")
|
||||||
|
return [TextContent(
|
||||||
|
type="text",
|
||||||
|
text=json.dumps({"error": str(e)}, indent=2)
|
||||||
|
)]
|
||||||
|
|
||||||
|
# Parse tool implementations (Issue #186)
|
||||||
|
|
||||||
|
async def _parse_plugin_interface(self, plugin_path: str) -> dict:
|
||||||
|
"""Parse plugin interface from README.md"""
|
||||||
|
return await self.parse_tools.parse_plugin_interface(plugin_path)
|
||||||
|
|
||||||
|
async def _parse_claude_md_agents(self, claude_md_path: str) -> dict:
|
||||||
|
"""Parse agents from CLAUDE.md"""
|
||||||
|
return await self.parse_tools.parse_claude_md_agents(claude_md_path)
|
||||||
|
|
||||||
|
# Validation tool implementations (Issue #187)
|
||||||
|
|
||||||
|
async def _validate_compatibility(self, plugin_a: str, plugin_b: str) -> dict:
|
||||||
|
"""Validate compatibility between plugins"""
|
||||||
|
return await self.validation_tools.validate_compatibility(plugin_a, plugin_b)
|
||||||
|
|
||||||
|
async def _validate_agent_refs(self, agent_name: str, claude_md_path: str, plugin_paths: list = None) -> dict:
|
||||||
|
"""Validate agent tool references"""
|
||||||
|
return await self.validation_tools.validate_agent_refs(agent_name, claude_md_path, plugin_paths)
|
||||||
|
|
||||||
|
async def _validate_data_flow(self, agent_name: str, claude_md_path: str) -> dict:
|
||||||
|
"""Validate agent data flow"""
|
||||||
|
return await self.validation_tools.validate_data_flow(agent_name, claude_md_path)
|
||||||
|
|
||||||
|
# Report tool implementations (Issue #188)
|
||||||
|
|
||||||
|
async def _generate_compatibility_report(self, marketplace_path: str, format: str = "markdown") -> dict:
|
||||||
|
"""Generate comprehensive compatibility report"""
|
||||||
|
return await self.report_tools.generate_compatibility_report(marketplace_path, format)
|
||||||
|
|
||||||
|
async def _list_issues(self, marketplace_path: str, severity: str = "all", issue_type: str = "all") -> dict:
|
||||||
|
"""List validation issues with filtering"""
|
||||||
|
return await self.report_tools.list_issues(marketplace_path, severity, issue_type)
|
||||||
|
|
||||||
|
async def run(self):
|
||||||
|
"""Run the MCP server"""
|
||||||
|
await self.initialize()
|
||||||
|
self.setup_tools()
|
||||||
|
|
||||||
|
async with stdio_server() as (read_stream, write_stream):
|
||||||
|
await self.server.run(
|
||||||
|
read_stream,
|
||||||
|
write_stream,
|
||||||
|
self.server.create_initialization_options()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
"""Main entry point"""
|
||||||
|
server = ContractValidatorMCPServer()
|
||||||
|
await server.run()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
asyncio.run(main())
|
||||||
338
mcp-servers/contract-validator/mcp_server/validation_tools.py
Normal file
338
mcp-servers/contract-validator/mcp_server/validation_tools.py
Normal file
@@ -0,0 +1,338 @@
|
|||||||
|
"""
|
||||||
|
Validation tools for checking cross-plugin compatibility and agent references.
|
||||||
|
|
||||||
|
Provides:
|
||||||
|
- validate_compatibility: Compare two plugin interfaces
|
||||||
|
- validate_agent_refs: Check agent tool references exist
|
||||||
|
- validate_data_flow: Verify data flow through agent sequences
|
||||||
|
"""
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Optional
|
||||||
|
from pydantic import BaseModel
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
from .parse_tools import ParseTools, PluginInterface, ClaudeMdAgent
|
||||||
|
|
||||||
|
|
||||||
|
class IssueSeverity(str, Enum):
|
||||||
|
ERROR = "error"
|
||||||
|
WARNING = "warning"
|
||||||
|
INFO = "info"
|
||||||
|
|
||||||
|
|
||||||
|
class IssueType(str, Enum):
|
||||||
|
MISSING_TOOL = "missing_tool"
|
||||||
|
INTERFACE_MISMATCH = "interface_mismatch"
|
||||||
|
OPTIONAL_DEPENDENCY = "optional_dependency"
|
||||||
|
UNDECLARED_OUTPUT = "undeclared_output"
|
||||||
|
INVALID_SEQUENCE = "invalid_sequence"
|
||||||
|
|
||||||
|
|
||||||
|
class ValidationIssue(BaseModel):
|
||||||
|
"""A single validation issue"""
|
||||||
|
severity: IssueSeverity
|
||||||
|
issue_type: IssueType
|
||||||
|
message: str
|
||||||
|
location: Optional[str] = None
|
||||||
|
suggestion: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
class CompatibilityResult(BaseModel):
|
||||||
|
"""Result of compatibility check between two plugins"""
|
||||||
|
plugin_a: str
|
||||||
|
plugin_b: str
|
||||||
|
compatible: bool
|
||||||
|
shared_tools: list[str] = []
|
||||||
|
a_only_tools: list[str] = []
|
||||||
|
b_only_tools: list[str] = []
|
||||||
|
issues: list[ValidationIssue] = []
|
||||||
|
|
||||||
|
|
||||||
|
class AgentValidationResult(BaseModel):
|
||||||
|
"""Result of agent reference validation"""
|
||||||
|
agent_name: str
|
||||||
|
valid: bool
|
||||||
|
tool_refs_found: list[str] = []
|
||||||
|
tool_refs_missing: list[str] = []
|
||||||
|
issues: list[ValidationIssue] = []
|
||||||
|
|
||||||
|
|
||||||
|
class DataFlowResult(BaseModel):
|
||||||
|
"""Result of data flow validation"""
|
||||||
|
agent_name: str
|
||||||
|
valid: bool
|
||||||
|
flow_steps: list[str] = []
|
||||||
|
issues: list[ValidationIssue] = []
|
||||||
|
|
||||||
|
|
||||||
|
class ValidationTools:
|
||||||
|
"""Tools for validating plugin compatibility and agent references"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.parse_tools = ParseTools()
|
||||||
|
|
||||||
|
async def validate_compatibility(self, plugin_a: str, plugin_b: str) -> dict:
|
||||||
|
"""
|
||||||
|
Validate compatibility between two plugin interfaces.
|
||||||
|
|
||||||
|
Compares tools, commands, and agents to identify overlaps and gaps.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
plugin_a: Path to first plugin directory
|
||||||
|
plugin_b: Path to second plugin directory
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Compatibility report with shared tools, unique tools, and issues
|
||||||
|
"""
|
||||||
|
# Parse both plugins
|
||||||
|
interface_a = await self.parse_tools.parse_plugin_interface(plugin_a)
|
||||||
|
interface_b = await self.parse_tools.parse_plugin_interface(plugin_b)
|
||||||
|
|
||||||
|
# Check for parse errors
|
||||||
|
if "error" in interface_a:
|
||||||
|
return {
|
||||||
|
"error": f"Failed to parse plugin A: {interface_a['error']}",
|
||||||
|
"plugin_a": plugin_a,
|
||||||
|
"plugin_b": plugin_b
|
||||||
|
}
|
||||||
|
if "error" in interface_b:
|
||||||
|
return {
|
||||||
|
"error": f"Failed to parse plugin B: {interface_b['error']}",
|
||||||
|
"plugin_a": plugin_a,
|
||||||
|
"plugin_b": plugin_b
|
||||||
|
}
|
||||||
|
|
||||||
|
# Extract tool names
|
||||||
|
tools_a = set(t["name"] for t in interface_a.get("tools", []))
|
||||||
|
tools_b = set(t["name"] for t in interface_b.get("tools", []))
|
||||||
|
|
||||||
|
# Find overlaps and differences
|
||||||
|
shared = tools_a & tools_b
|
||||||
|
a_only = tools_a - tools_b
|
||||||
|
b_only = tools_b - tools_a
|
||||||
|
|
||||||
|
issues = []
|
||||||
|
|
||||||
|
# Check for potential naming conflicts
|
||||||
|
if shared:
|
||||||
|
issues.append(ValidationIssue(
|
||||||
|
severity=IssueSeverity.WARNING,
|
||||||
|
issue_type=IssueType.INTERFACE_MISMATCH,
|
||||||
|
message=f"Both plugins define tools with same names: {list(shared)}",
|
||||||
|
location=f"{interface_a['plugin_name']} and {interface_b['plugin_name']}",
|
||||||
|
suggestion="Ensure tools with same names have compatible interfaces"
|
||||||
|
))
|
||||||
|
|
||||||
|
# Check command overlaps
|
||||||
|
cmds_a = set(c["name"] for c in interface_a.get("commands", []))
|
||||||
|
cmds_b = set(c["name"] for c in interface_b.get("commands", []))
|
||||||
|
shared_cmds = cmds_a & cmds_b
|
||||||
|
|
||||||
|
if shared_cmds:
|
||||||
|
issues.append(ValidationIssue(
|
||||||
|
severity=IssueSeverity.ERROR,
|
||||||
|
issue_type=IssueType.INTERFACE_MISMATCH,
|
||||||
|
message=f"Command name conflict: {list(shared_cmds)}",
|
||||||
|
location=f"{interface_a['plugin_name']} and {interface_b['plugin_name']}",
|
||||||
|
suggestion="Rename conflicting commands to avoid ambiguity"
|
||||||
|
))
|
||||||
|
|
||||||
|
result = CompatibilityResult(
|
||||||
|
plugin_a=interface_a["plugin_name"],
|
||||||
|
plugin_b=interface_b["plugin_name"],
|
||||||
|
compatible=len([i for i in issues if i.severity == IssueSeverity.ERROR]) == 0,
|
||||||
|
shared_tools=list(shared),
|
||||||
|
a_only_tools=list(a_only),
|
||||||
|
b_only_tools=list(b_only),
|
||||||
|
issues=issues
|
||||||
|
)
|
||||||
|
|
||||||
|
return result.model_dump()
|
||||||
|
|
||||||
|
async def validate_agent_refs(
|
||||||
|
self,
|
||||||
|
agent_name: str,
|
||||||
|
claude_md_path: str,
|
||||||
|
plugin_paths: list[str] = None
|
||||||
|
) -> dict:
|
||||||
|
"""
|
||||||
|
Validate that all tool references in an agent definition exist.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
agent_name: Name of the agent to validate
|
||||||
|
claude_md_path: Path to CLAUDE.md containing the agent
|
||||||
|
plugin_paths: Optional list of plugin paths to check for tools
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Validation result with found/missing tools and issues
|
||||||
|
"""
|
||||||
|
# Parse CLAUDE.md for agents
|
||||||
|
agents_result = await self.parse_tools.parse_claude_md_agents(claude_md_path)
|
||||||
|
|
||||||
|
if "error" in agents_result:
|
||||||
|
return {
|
||||||
|
"error": agents_result["error"],
|
||||||
|
"agent_name": agent_name
|
||||||
|
}
|
||||||
|
|
||||||
|
# Find the specific agent
|
||||||
|
agent = None
|
||||||
|
for a in agents_result.get("agents", []):
|
||||||
|
if a["name"].lower() == agent_name.lower():
|
||||||
|
agent = a
|
||||||
|
break
|
||||||
|
|
||||||
|
if not agent:
|
||||||
|
return {
|
||||||
|
"error": f"Agent '{agent_name}' not found in {claude_md_path}",
|
||||||
|
"agent_name": agent_name,
|
||||||
|
"available_agents": [a["name"] for a in agents_result.get("agents", [])]
|
||||||
|
}
|
||||||
|
|
||||||
|
# Collect all available tools from plugins
|
||||||
|
available_tools = set()
|
||||||
|
if plugin_paths:
|
||||||
|
for plugin_path in plugin_paths:
|
||||||
|
interface = await self.parse_tools.parse_plugin_interface(plugin_path)
|
||||||
|
if "error" not in interface:
|
||||||
|
for tool in interface.get("tools", []):
|
||||||
|
available_tools.add(tool["name"])
|
||||||
|
|
||||||
|
# Check agent tool references
|
||||||
|
tool_refs = set(agent.get("tool_refs", []))
|
||||||
|
found = tool_refs & available_tools if available_tools else tool_refs
|
||||||
|
missing = tool_refs - available_tools if available_tools else set()
|
||||||
|
|
||||||
|
issues = []
|
||||||
|
|
||||||
|
# Report missing tools
|
||||||
|
for tool in missing:
|
||||||
|
issues.append(ValidationIssue(
|
||||||
|
severity=IssueSeverity.ERROR,
|
||||||
|
issue_type=IssueType.MISSING_TOOL,
|
||||||
|
message=f"Agent '{agent_name}' references tool '{tool}' which is not found",
|
||||||
|
location=claude_md_path,
|
||||||
|
suggestion=f"Check if tool '{tool}' exists or fix the reference"
|
||||||
|
))
|
||||||
|
|
||||||
|
# Check if agent has no tool refs (might be incomplete)
|
||||||
|
if not tool_refs:
|
||||||
|
issues.append(ValidationIssue(
|
||||||
|
severity=IssueSeverity.INFO,
|
||||||
|
issue_type=IssueType.UNDECLARED_OUTPUT,
|
||||||
|
message=f"Agent '{agent_name}' has no documented tool references",
|
||||||
|
location=claude_md_path,
|
||||||
|
suggestion="Consider documenting which tools this agent uses"
|
||||||
|
))
|
||||||
|
|
||||||
|
result = AgentValidationResult(
|
||||||
|
agent_name=agent_name,
|
||||||
|
valid=len([i for i in issues if i.severity == IssueSeverity.ERROR]) == 0,
|
||||||
|
tool_refs_found=list(found),
|
||||||
|
tool_refs_missing=list(missing),
|
||||||
|
issues=issues
|
||||||
|
)
|
||||||
|
|
||||||
|
return result.model_dump()
|
||||||
|
|
||||||
|
async def validate_data_flow(self, agent_name: str, claude_md_path: str) -> dict:
|
||||||
|
"""
|
||||||
|
Validate data flow through an agent's tool sequence.
|
||||||
|
|
||||||
|
Checks that each step's expected output can be used by the next step.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
agent_name: Name of the agent to validate
|
||||||
|
claude_md_path: Path to CLAUDE.md containing the agent
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Data flow validation result with steps and issues
|
||||||
|
"""
|
||||||
|
# Parse CLAUDE.md for agents
|
||||||
|
agents_result = await self.parse_tools.parse_claude_md_agents(claude_md_path)
|
||||||
|
|
||||||
|
if "error" in agents_result:
|
||||||
|
return {
|
||||||
|
"error": agents_result["error"],
|
||||||
|
"agent_name": agent_name
|
||||||
|
}
|
||||||
|
|
||||||
|
# Find the specific agent
|
||||||
|
agent = None
|
||||||
|
for a in agents_result.get("agents", []):
|
||||||
|
if a["name"].lower() == agent_name.lower():
|
||||||
|
agent = a
|
||||||
|
break
|
||||||
|
|
||||||
|
if not agent:
|
||||||
|
return {
|
||||||
|
"error": f"Agent '{agent_name}' not found in {claude_md_path}",
|
||||||
|
"agent_name": agent_name,
|
||||||
|
"available_agents": [a["name"] for a in agents_result.get("agents", [])]
|
||||||
|
}
|
||||||
|
|
||||||
|
issues = []
|
||||||
|
flow_steps = []
|
||||||
|
|
||||||
|
# Extract workflow steps
|
||||||
|
workflow_steps = agent.get("workflow_steps", [])
|
||||||
|
responsibilities = agent.get("responsibilities", [])
|
||||||
|
|
||||||
|
# Build flow from workflow steps or responsibilities
|
||||||
|
steps = workflow_steps if workflow_steps else responsibilities
|
||||||
|
|
||||||
|
for i, step in enumerate(steps):
|
||||||
|
flow_steps.append(f"Step {i+1}: {step}")
|
||||||
|
|
||||||
|
# Check for data flow patterns
|
||||||
|
tool_refs = agent.get("tool_refs", [])
|
||||||
|
|
||||||
|
# Known data flow patterns
|
||||||
|
# e.g., data-platform produces data_ref, viz-platform consumes it
|
||||||
|
known_producers = {
|
||||||
|
"read_csv": "data_ref",
|
||||||
|
"read_parquet": "data_ref",
|
||||||
|
"pg_query": "data_ref",
|
||||||
|
"filter": "data_ref",
|
||||||
|
"groupby": "data_ref",
|
||||||
|
}
|
||||||
|
|
||||||
|
known_consumers = {
|
||||||
|
"describe": "data_ref",
|
||||||
|
"head": "data_ref",
|
||||||
|
"tail": "data_ref",
|
||||||
|
"to_csv": "data_ref",
|
||||||
|
"to_parquet": "data_ref",
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check if agent uses tools that require data_ref
|
||||||
|
has_producer = any(t in known_producers for t in tool_refs)
|
||||||
|
has_consumer = any(t in known_consumers for t in tool_refs)
|
||||||
|
|
||||||
|
if has_consumer and not has_producer:
|
||||||
|
issues.append(ValidationIssue(
|
||||||
|
severity=IssueSeverity.WARNING,
|
||||||
|
issue_type=IssueType.INTERFACE_MISMATCH,
|
||||||
|
message=f"Agent '{agent_name}' uses tools that consume data_ref but no producer found",
|
||||||
|
location=claude_md_path,
|
||||||
|
suggestion="Ensure a data loading tool (read_csv, pg_query, etc.) is used before data consumers"
|
||||||
|
))
|
||||||
|
|
||||||
|
# Check for empty workflow
|
||||||
|
if not steps and not tool_refs:
|
||||||
|
issues.append(ValidationIssue(
|
||||||
|
severity=IssueSeverity.INFO,
|
||||||
|
issue_type=IssueType.UNDECLARED_OUTPUT,
|
||||||
|
message=f"Agent '{agent_name}' has no documented workflow or tool sequence",
|
||||||
|
location=claude_md_path,
|
||||||
|
suggestion="Consider documenting the agent's workflow steps"
|
||||||
|
))
|
||||||
|
|
||||||
|
result = DataFlowResult(
|
||||||
|
agent_name=agent_name,
|
||||||
|
valid=len([i for i in issues if i.severity == IssueSeverity.ERROR]) == 0,
|
||||||
|
flow_steps=flow_steps,
|
||||||
|
issues=issues
|
||||||
|
)
|
||||||
|
|
||||||
|
return result.model_dump()
|
||||||
41
mcp-servers/contract-validator/pyproject.toml
Normal file
41
mcp-servers/contract-validator/pyproject.toml
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
[build-system]
|
||||||
|
requires = ["setuptools>=61.0", "wheel"]
|
||||||
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "contract-validator-mcp"
|
||||||
|
version = "1.0.0"
|
||||||
|
description = "MCP Server for cross-plugin compatibility validation and agent verification"
|
||||||
|
readme = "README.md"
|
||||||
|
license = {text = "MIT"}
|
||||||
|
requires-python = ">=3.10"
|
||||||
|
authors = [
|
||||||
|
{name = "Leo Miranda"}
|
||||||
|
]
|
||||||
|
classifiers = [
|
||||||
|
"Development Status :: 4 - Beta",
|
||||||
|
"Intended Audience :: Developers",
|
||||||
|
"License :: OSI Approved :: MIT License",
|
||||||
|
"Programming Language :: Python :: 3",
|
||||||
|
"Programming Language :: Python :: 3.10",
|
||||||
|
"Programming Language :: Python :: 3.11",
|
||||||
|
"Programming Language :: Python :: 3.12",
|
||||||
|
]
|
||||||
|
dependencies = [
|
||||||
|
"mcp>=0.9.0",
|
||||||
|
"pydantic>=2.5.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[project.optional-dependencies]
|
||||||
|
dev = [
|
||||||
|
"pytest>=7.4.3",
|
||||||
|
"pytest-asyncio>=0.23.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.setuptools.packages.find]
|
||||||
|
where = ["."]
|
||||||
|
include = ["mcp_server*"]
|
||||||
|
|
||||||
|
[tool.pytest.ini_options]
|
||||||
|
asyncio_mode = "auto"
|
||||||
|
testpaths = ["tests"]
|
||||||
9
mcp-servers/contract-validator/requirements.txt
Normal file
9
mcp-servers/contract-validator/requirements.txt
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# MCP SDK
|
||||||
|
mcp>=0.9.0
|
||||||
|
|
||||||
|
# Utilities
|
||||||
|
pydantic>=2.5.0
|
||||||
|
|
||||||
|
# Testing
|
||||||
|
pytest>=7.4.3
|
||||||
|
pytest-asyncio>=0.23.0
|
||||||
21
mcp-servers/contract-validator/run.sh
Executable file
21
mcp-servers/contract-validator/run.sh
Executable file
@@ -0,0 +1,21 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Capture original working directory before any cd operations
|
||||||
|
# This should be the user's project directory when launched by Claude Code
|
||||||
|
export CLAUDE_PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$PWD}"
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
CACHE_VENV="$HOME/.cache/claude-mcp-venvs/leo-claude-mktplace/contract-validator/.venv"
|
||||||
|
LOCAL_VENV="$SCRIPT_DIR/.venv"
|
||||||
|
|
||||||
|
if [[ -f "$CACHE_VENV/bin/python" ]]; then
|
||||||
|
PYTHON="$CACHE_VENV/bin/python"
|
||||||
|
elif [[ -f "$LOCAL_VENV/bin/python" ]]; then
|
||||||
|
PYTHON="$LOCAL_VENV/bin/python"
|
||||||
|
else
|
||||||
|
echo "ERROR: No venv found. Run: ./scripts/setup-venvs.sh" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd "$SCRIPT_DIR"
|
||||||
|
export PYTHONPATH="$SCRIPT_DIR"
|
||||||
|
exec "$PYTHON" -m mcp_server.server "$@"
|
||||||
1
mcp-servers/contract-validator/tests/__init__.py
Normal file
1
mcp-servers/contract-validator/tests/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
# Tests for contract-validator MCP server
|
||||||
193
mcp-servers/contract-validator/tests/test_parse_tools.py
Normal file
193
mcp-servers/contract-validator/tests/test_parse_tools.py
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
"""
|
||||||
|
Unit tests for parse tools.
|
||||||
|
"""
|
||||||
|
import pytest
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def parse_tools():
|
||||||
|
"""Create ParseTools instance"""
|
||||||
|
from mcp_server.parse_tools import ParseTools
|
||||||
|
return ParseTools()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def sample_readme(tmp_path):
|
||||||
|
"""Create a sample README.md for testing"""
|
||||||
|
readme = tmp_path / "README.md"
|
||||||
|
readme.write_text("""# Test Plugin
|
||||||
|
|
||||||
|
A test plugin for validation.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Feature One**: Does something
|
||||||
|
- **Feature Two**: Does something else
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
|---------|-------------|
|
||||||
|
| `/test-cmd` | Test command |
|
||||||
|
| `/another-cmd` | Another test command |
|
||||||
|
|
||||||
|
## Agents
|
||||||
|
|
||||||
|
| Agent | Description |
|
||||||
|
|-------|-------------|
|
||||||
|
| `test-agent` | A test agent |
|
||||||
|
|
||||||
|
## Tools Summary
|
||||||
|
|
||||||
|
### Category A (3 tools)
|
||||||
|
`tool_a`, `tool_b`, `tool_c`
|
||||||
|
|
||||||
|
### Category B (2 tools)
|
||||||
|
`tool_d`, `tool_e`
|
||||||
|
""")
|
||||||
|
return str(tmp_path)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def sample_claude_md(tmp_path):
|
||||||
|
"""Create a sample CLAUDE.md for testing"""
|
||||||
|
claude_md = tmp_path / "CLAUDE.md"
|
||||||
|
claude_md.write_text("""# CLAUDE.md
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
### Four-Agent Model (test)
|
||||||
|
|
||||||
|
| Agent | Personality | Responsibilities |
|
||||||
|
|-------|-------------|------------------|
|
||||||
|
| **Planner** | Thoughtful | Planning via `create_issue`, `search_lessons` |
|
||||||
|
| **Executor** | Focused | Implementation via `write`, `edit` |
|
||||||
|
|
||||||
|
## Workflow
|
||||||
|
|
||||||
|
1. Planner creates issues
|
||||||
|
2. Executor implements code
|
||||||
|
""")
|
||||||
|
return str(claude_md)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_parse_plugin_interface_basic(parse_tools, sample_readme):
|
||||||
|
"""Test basic plugin interface parsing"""
|
||||||
|
result = await parse_tools.parse_plugin_interface(sample_readme)
|
||||||
|
|
||||||
|
assert "error" not in result
|
||||||
|
# Plugin name extraction strips "Plugin" suffix
|
||||||
|
assert result["plugin_name"] == "Test"
|
||||||
|
assert "A test plugin" in result["description"]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_parse_plugin_interface_commands(parse_tools, sample_readme):
|
||||||
|
"""Test command extraction from README"""
|
||||||
|
result = await parse_tools.parse_plugin_interface(sample_readme)
|
||||||
|
|
||||||
|
commands = result["commands"]
|
||||||
|
assert len(commands) == 2
|
||||||
|
assert commands[0]["name"] == "/test-cmd"
|
||||||
|
assert commands[1]["name"] == "/another-cmd"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_parse_plugin_interface_agents(parse_tools, sample_readme):
|
||||||
|
"""Test agent extraction from README"""
|
||||||
|
result = await parse_tools.parse_plugin_interface(sample_readme)
|
||||||
|
|
||||||
|
agents = result["agents"]
|
||||||
|
assert len(agents) == 1
|
||||||
|
assert agents[0]["name"] == "test-agent"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_parse_plugin_interface_tools(parse_tools, sample_readme):
|
||||||
|
"""Test tool extraction from README"""
|
||||||
|
result = await parse_tools.parse_plugin_interface(sample_readme)
|
||||||
|
|
||||||
|
tools = result["tools"]
|
||||||
|
tool_names = [t["name"] for t in tools]
|
||||||
|
assert "tool_a" in tool_names
|
||||||
|
assert "tool_b" in tool_names
|
||||||
|
assert "tool_e" in tool_names
|
||||||
|
assert len(tools) >= 5
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_parse_plugin_interface_categories(parse_tools, sample_readme):
|
||||||
|
"""Test tool category extraction"""
|
||||||
|
result = await parse_tools.parse_plugin_interface(sample_readme)
|
||||||
|
|
||||||
|
categories = result["tool_categories"]
|
||||||
|
assert "Category A" in categories
|
||||||
|
assert "Category B" in categories
|
||||||
|
assert "tool_a" in categories["Category A"]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_parse_plugin_interface_features(parse_tools, sample_readme):
|
||||||
|
"""Test feature extraction"""
|
||||||
|
result = await parse_tools.parse_plugin_interface(sample_readme)
|
||||||
|
|
||||||
|
features = result["features"]
|
||||||
|
assert "Feature One" in features
|
||||||
|
assert "Feature Two" in features
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_parse_plugin_interface_not_found(parse_tools, tmp_path):
|
||||||
|
"""Test error when README not found"""
|
||||||
|
result = await parse_tools.parse_plugin_interface(str(tmp_path / "nonexistent"))
|
||||||
|
|
||||||
|
assert "error" in result
|
||||||
|
assert "not found" in result["error"].lower()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_parse_claude_md_agents(parse_tools, sample_claude_md):
|
||||||
|
"""Test agent extraction from CLAUDE.md"""
|
||||||
|
result = await parse_tools.parse_claude_md_agents(sample_claude_md)
|
||||||
|
|
||||||
|
assert "error" not in result
|
||||||
|
assert result["agent_count"] == 2
|
||||||
|
|
||||||
|
agents = result["agents"]
|
||||||
|
agent_names = [a["name"] for a in agents]
|
||||||
|
assert "Planner" in agent_names
|
||||||
|
assert "Executor" in agent_names
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_parse_claude_md_tool_refs(parse_tools, sample_claude_md):
|
||||||
|
"""Test tool reference extraction from agents"""
|
||||||
|
result = await parse_tools.parse_claude_md_agents(sample_claude_md)
|
||||||
|
|
||||||
|
agents = {a["name"]: a for a in result["agents"]}
|
||||||
|
planner = agents["Planner"]
|
||||||
|
|
||||||
|
assert "create_issue" in planner["tool_refs"]
|
||||||
|
assert "search_lessons" in planner["tool_refs"]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_parse_claude_md_not_found(parse_tools, tmp_path):
|
||||||
|
"""Test error when CLAUDE.md not found"""
|
||||||
|
result = await parse_tools.parse_claude_md_agents(str(tmp_path / "CLAUDE.md"))
|
||||||
|
|
||||||
|
assert "error" in result
|
||||||
|
assert "not found" in result["error"].lower()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_parse_plugin_with_direct_file(parse_tools, sample_readme):
|
||||||
|
"""Test parsing with direct file path instead of directory"""
|
||||||
|
readme_path = Path(sample_readme) / "README.md"
|
||||||
|
result = await parse_tools.parse_plugin_interface(str(readme_path))
|
||||||
|
|
||||||
|
assert "error" not in result
|
||||||
|
# Plugin name extraction strips "Plugin" suffix
|
||||||
|
assert result["plugin_name"] == "Test"
|
||||||
261
mcp-servers/contract-validator/tests/test_report_tools.py
Normal file
261
mcp-servers/contract-validator/tests/test_report_tools.py
Normal file
@@ -0,0 +1,261 @@
|
|||||||
|
"""
|
||||||
|
Unit tests for report tools.
|
||||||
|
"""
|
||||||
|
import pytest
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def report_tools():
|
||||||
|
"""Create ReportTools instance"""
|
||||||
|
from mcp_server.report_tools import ReportTools
|
||||||
|
return ReportTools()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def sample_marketplace(tmp_path):
|
||||||
|
"""Create a sample marketplace structure"""
|
||||||
|
import json
|
||||||
|
|
||||||
|
plugins_dir = tmp_path / "plugins"
|
||||||
|
plugins_dir.mkdir()
|
||||||
|
|
||||||
|
# Plugin 1
|
||||||
|
plugin1 = plugins_dir / "plugin-one"
|
||||||
|
plugin1.mkdir()
|
||||||
|
plugin1_meta = plugin1 / ".claude-plugin"
|
||||||
|
plugin1_meta.mkdir()
|
||||||
|
(plugin1_meta / "plugin.json").write_text(json.dumps({"name": "plugin-one"}))
|
||||||
|
(plugin1 / "README.md").write_text("""# plugin-one
|
||||||
|
|
||||||
|
First test plugin.
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
|---------|-------------|
|
||||||
|
| `/cmd-one` | Command one |
|
||||||
|
|
||||||
|
## Tools Summary
|
||||||
|
|
||||||
|
### Tools (2 tools)
|
||||||
|
`tool_a`, `tool_b`
|
||||||
|
""")
|
||||||
|
|
||||||
|
# Plugin 2
|
||||||
|
plugin2 = plugins_dir / "plugin-two"
|
||||||
|
plugin2.mkdir()
|
||||||
|
plugin2_meta = plugin2 / ".claude-plugin"
|
||||||
|
plugin2_meta.mkdir()
|
||||||
|
(plugin2_meta / "plugin.json").write_text(json.dumps({"name": "plugin-two"}))
|
||||||
|
(plugin2 / "README.md").write_text("""# plugin-two
|
||||||
|
|
||||||
|
Second test plugin.
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
|---------|-------------|
|
||||||
|
| `/cmd-two` | Command two |
|
||||||
|
|
||||||
|
## Tools Summary
|
||||||
|
|
||||||
|
### Tools (2 tools)
|
||||||
|
`tool_c`, `tool_d`
|
||||||
|
""")
|
||||||
|
|
||||||
|
# Plugin 3 (with conflict)
|
||||||
|
plugin3 = plugins_dir / "plugin-three"
|
||||||
|
plugin3.mkdir()
|
||||||
|
plugin3_meta = plugin3 / ".claude-plugin"
|
||||||
|
plugin3_meta.mkdir()
|
||||||
|
(plugin3_meta / "plugin.json").write_text(json.dumps({"name": "plugin-three"}))
|
||||||
|
(plugin3 / "README.md").write_text("""# plugin-three
|
||||||
|
|
||||||
|
Third test plugin with conflict.
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
|---------|-------------|
|
||||||
|
| `/cmd-one` | Conflicting command |
|
||||||
|
|
||||||
|
## Tools Summary
|
||||||
|
|
||||||
|
### Tools (1 tool)
|
||||||
|
`tool_e`
|
||||||
|
""")
|
||||||
|
|
||||||
|
return str(tmp_path)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def marketplace_no_plugins(tmp_path):
|
||||||
|
"""Create marketplace with no plugins"""
|
||||||
|
plugins_dir = tmp_path / "plugins"
|
||||||
|
plugins_dir.mkdir()
|
||||||
|
return str(tmp_path)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def marketplace_no_dir(tmp_path):
|
||||||
|
"""Create path without plugins directory"""
|
||||||
|
return str(tmp_path)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_generate_report_json_format(report_tools, sample_marketplace):
|
||||||
|
"""Test JSON format report generation"""
|
||||||
|
result = await report_tools.generate_compatibility_report(
|
||||||
|
sample_marketplace, "json"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert "error" not in result
|
||||||
|
assert "generated_at" in result
|
||||||
|
assert "summary" in result
|
||||||
|
assert "plugins" in result
|
||||||
|
assert result["summary"]["total_plugins"] == 3
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_generate_report_markdown_format(report_tools, sample_marketplace):
|
||||||
|
"""Test markdown format report generation"""
|
||||||
|
result = await report_tools.generate_compatibility_report(
|
||||||
|
sample_marketplace, "markdown"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert "error" not in result
|
||||||
|
assert "report" in result
|
||||||
|
assert "# Contract Validation Report" in result["report"]
|
||||||
|
assert "## Summary" in result["report"]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_generate_report_finds_conflicts(report_tools, sample_marketplace):
|
||||||
|
"""Test that report finds command conflicts"""
|
||||||
|
result = await report_tools.generate_compatibility_report(
|
||||||
|
sample_marketplace, "json"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert "error" not in result
|
||||||
|
assert result["summary"]["errors"] > 0
|
||||||
|
assert result["summary"]["total_issues"] > 0
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_generate_report_counts_correctly(report_tools, sample_marketplace):
|
||||||
|
"""Test summary counts are correct"""
|
||||||
|
result = await report_tools.generate_compatibility_report(
|
||||||
|
sample_marketplace, "json"
|
||||||
|
)
|
||||||
|
|
||||||
|
summary = result["summary"]
|
||||||
|
assert summary["total_plugins"] == 3
|
||||||
|
assert summary["total_commands"] == 3 # 3 commands total
|
||||||
|
assert summary["total_tools"] == 5 # a, b, c, d, e
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_generate_report_no_plugins(report_tools, marketplace_no_plugins):
|
||||||
|
"""Test error when no plugins found"""
|
||||||
|
result = await report_tools.generate_compatibility_report(
|
||||||
|
marketplace_no_plugins, "json"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert "error" in result
|
||||||
|
assert "no plugins" in result["error"].lower()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_generate_report_no_plugins_dir(report_tools, marketplace_no_dir):
|
||||||
|
"""Test error when plugins directory doesn't exist"""
|
||||||
|
result = await report_tools.generate_compatibility_report(
|
||||||
|
marketplace_no_dir, "json"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert "error" in result
|
||||||
|
assert "not found" in result["error"].lower()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_list_issues_all(report_tools, sample_marketplace):
|
||||||
|
"""Test listing all issues"""
|
||||||
|
result = await report_tools.list_issues(sample_marketplace, "all", "all")
|
||||||
|
|
||||||
|
assert "error" not in result
|
||||||
|
assert "issues" in result
|
||||||
|
assert result["total_issues"] > 0
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_list_issues_filter_by_severity(report_tools, sample_marketplace):
|
||||||
|
"""Test filtering issues by severity"""
|
||||||
|
all_result = await report_tools.list_issues(sample_marketplace, "all", "all")
|
||||||
|
error_result = await report_tools.list_issues(sample_marketplace, "error", "all")
|
||||||
|
|
||||||
|
# Error count should be less than or equal to all
|
||||||
|
assert error_result["total_issues"] <= all_result["total_issues"]
|
||||||
|
|
||||||
|
# All issues should have error severity
|
||||||
|
for issue in error_result["issues"]:
|
||||||
|
sev = issue.get("severity", "")
|
||||||
|
if hasattr(sev, 'value'):
|
||||||
|
sev = sev.value
|
||||||
|
assert "error" in str(sev).lower()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_list_issues_filter_by_type(report_tools, sample_marketplace):
|
||||||
|
"""Test filtering issues by type"""
|
||||||
|
result = await report_tools.list_issues(
|
||||||
|
sample_marketplace, "all", "interface_mismatch"
|
||||||
|
)
|
||||||
|
|
||||||
|
# All issues should have matching type
|
||||||
|
for issue in result["issues"]:
|
||||||
|
itype = issue.get("issue_type", "")
|
||||||
|
if hasattr(itype, 'value'):
|
||||||
|
itype = itype.value
|
||||||
|
assert "interface_mismatch" in str(itype).lower()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_list_issues_combined_filters(report_tools, sample_marketplace):
|
||||||
|
"""Test combined severity and type filters"""
|
||||||
|
result = await report_tools.list_issues(
|
||||||
|
sample_marketplace, "error", "interface_mismatch"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert "error" not in result
|
||||||
|
# Should have command conflict errors
|
||||||
|
assert result["total_issues"] > 0
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_report_markdown_has_all_sections(report_tools, sample_marketplace):
|
||||||
|
"""Test markdown report contains all expected sections"""
|
||||||
|
result = await report_tools.generate_compatibility_report(
|
||||||
|
sample_marketplace, "markdown"
|
||||||
|
)
|
||||||
|
|
||||||
|
report = result["report"]
|
||||||
|
assert "## Summary" in report
|
||||||
|
assert "## Plugins" in report
|
||||||
|
# Compatibility section only if there are checks
|
||||||
|
assert "Plugin One" in report or "plugin-one" in report.lower()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_report_includes_suggestions(report_tools, sample_marketplace):
|
||||||
|
"""Test that issues include suggestions"""
|
||||||
|
result = await report_tools.generate_compatibility_report(
|
||||||
|
sample_marketplace, "json"
|
||||||
|
)
|
||||||
|
|
||||||
|
issues = result.get("all_issues", [])
|
||||||
|
# Find an issue with a suggestion
|
||||||
|
issues_with_suggestions = [
|
||||||
|
i for i in issues
|
||||||
|
if i.get("suggestion")
|
||||||
|
]
|
||||||
|
assert len(issues_with_suggestions) > 0
|
||||||
256
mcp-servers/contract-validator/tests/test_validation_tools.py
Normal file
256
mcp-servers/contract-validator/tests/test_validation_tools.py
Normal file
@@ -0,0 +1,256 @@
|
|||||||
|
"""
|
||||||
|
Unit tests for validation tools.
|
||||||
|
"""
|
||||||
|
import pytest
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def validation_tools():
|
||||||
|
"""Create ValidationTools instance"""
|
||||||
|
from mcp_server.validation_tools import ValidationTools
|
||||||
|
return ValidationTools()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def plugin_a(tmp_path):
|
||||||
|
"""Create first test plugin"""
|
||||||
|
plugin_dir = tmp_path / "plugin-a"
|
||||||
|
plugin_dir.mkdir()
|
||||||
|
(plugin_dir / ".claude-plugin").mkdir()
|
||||||
|
|
||||||
|
readme = plugin_dir / "README.md"
|
||||||
|
readme.write_text("""# Plugin A
|
||||||
|
|
||||||
|
Test plugin A.
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
|---------|-------------|
|
||||||
|
| `/setup-a` | Setup A |
|
||||||
|
| `/shared-cmd` | Shared command |
|
||||||
|
|
||||||
|
## Tools Summary
|
||||||
|
|
||||||
|
### Core (2 tools)
|
||||||
|
`tool_one`, `tool_two`
|
||||||
|
""")
|
||||||
|
return str(plugin_dir)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def plugin_b(tmp_path):
|
||||||
|
"""Create second test plugin"""
|
||||||
|
plugin_dir = tmp_path / "plugin-b"
|
||||||
|
plugin_dir.mkdir()
|
||||||
|
(plugin_dir / ".claude-plugin").mkdir()
|
||||||
|
|
||||||
|
readme = plugin_dir / "README.md"
|
||||||
|
readme.write_text("""# Plugin B
|
||||||
|
|
||||||
|
Test plugin B.
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
|---------|-------------|
|
||||||
|
| `/setup-b` | Setup B |
|
||||||
|
| `/shared-cmd` | Shared command (conflict!) |
|
||||||
|
|
||||||
|
## Tools Summary
|
||||||
|
|
||||||
|
### Core (2 tools)
|
||||||
|
`tool_two`, `tool_three`
|
||||||
|
""")
|
||||||
|
return str(plugin_dir)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def plugin_no_conflict(tmp_path):
|
||||||
|
"""Create plugin with no conflicts"""
|
||||||
|
plugin_dir = tmp_path / "plugin-c"
|
||||||
|
plugin_dir.mkdir()
|
||||||
|
(plugin_dir / ".claude-plugin").mkdir()
|
||||||
|
|
||||||
|
readme = plugin_dir / "README.md"
|
||||||
|
readme.write_text("""# Plugin C
|
||||||
|
|
||||||
|
Test plugin C.
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
|---------|-------------|
|
||||||
|
| `/unique-cmd` | Unique command |
|
||||||
|
|
||||||
|
## Tools Summary
|
||||||
|
|
||||||
|
### Core (1 tool)
|
||||||
|
`unique_tool`
|
||||||
|
""")
|
||||||
|
return str(plugin_dir)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def claude_md_with_agents(tmp_path):
|
||||||
|
"""Create CLAUDE.md with agent definitions"""
|
||||||
|
claude_md = tmp_path / "CLAUDE.md"
|
||||||
|
claude_md.write_text("""# CLAUDE.md
|
||||||
|
|
||||||
|
### Four-Agent Model
|
||||||
|
|
||||||
|
| Agent | Personality | Responsibilities |
|
||||||
|
|-------|-------------|------------------|
|
||||||
|
| **TestAgent** | Careful | Uses `tool_one`, `tool_two`, `missing_tool` |
|
||||||
|
| **ValidAgent** | Thorough | Uses `tool_one` only |
|
||||||
|
| **EmptyAgent** | Unknown | General tasks |
|
||||||
|
""")
|
||||||
|
return str(claude_md)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_validate_compatibility_command_conflict(validation_tools, plugin_a, plugin_b):
|
||||||
|
"""Test detection of command name conflicts"""
|
||||||
|
result = await validation_tools.validate_compatibility(plugin_a, plugin_b)
|
||||||
|
|
||||||
|
assert "error" not in result
|
||||||
|
assert result["compatible"] is False
|
||||||
|
|
||||||
|
# Find the command conflict issue
|
||||||
|
error_issues = [i for i in result["issues"] if i["severity"].value == "error"]
|
||||||
|
assert len(error_issues) > 0
|
||||||
|
assert any("/shared-cmd" in str(i["message"]) for i in error_issues)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_validate_compatibility_tool_overlap(validation_tools, plugin_a, plugin_b):
|
||||||
|
"""Test detection of tool name overlaps"""
|
||||||
|
result = await validation_tools.validate_compatibility(plugin_a, plugin_b)
|
||||||
|
|
||||||
|
assert "tool_two" in result["shared_tools"]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_validate_compatibility_unique_tools(validation_tools, plugin_a, plugin_b):
|
||||||
|
"""Test identification of unique tools per plugin"""
|
||||||
|
result = await validation_tools.validate_compatibility(plugin_a, plugin_b)
|
||||||
|
|
||||||
|
assert "tool_one" in result["a_only_tools"]
|
||||||
|
assert "tool_three" in result["b_only_tools"]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_validate_compatibility_no_conflict(validation_tools, plugin_a, plugin_no_conflict):
|
||||||
|
"""Test compatible plugins"""
|
||||||
|
result = await validation_tools.validate_compatibility(plugin_a, plugin_no_conflict)
|
||||||
|
|
||||||
|
assert "error" not in result
|
||||||
|
assert result["compatible"] is True
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_validate_compatibility_missing_plugin(validation_tools, plugin_a, tmp_path):
|
||||||
|
"""Test error when plugin not found"""
|
||||||
|
result = await validation_tools.validate_compatibility(
|
||||||
|
plugin_a,
|
||||||
|
str(tmp_path / "nonexistent")
|
||||||
|
)
|
||||||
|
|
||||||
|
assert "error" in result
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_validate_agent_refs_with_missing_tools(validation_tools, claude_md_with_agents, plugin_a):
|
||||||
|
"""Test detection of missing tool references"""
|
||||||
|
result = await validation_tools.validate_agent_refs(
|
||||||
|
"TestAgent",
|
||||||
|
claude_md_with_agents,
|
||||||
|
[plugin_a]
|
||||||
|
)
|
||||||
|
|
||||||
|
assert "error" not in result
|
||||||
|
assert result["valid"] is False
|
||||||
|
assert "missing_tool" in result["tool_refs_missing"]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_validate_agent_refs_valid_agent(validation_tools, claude_md_with_agents, plugin_a):
|
||||||
|
"""Test valid agent with all tools found"""
|
||||||
|
result = await validation_tools.validate_agent_refs(
|
||||||
|
"ValidAgent",
|
||||||
|
claude_md_with_agents,
|
||||||
|
[plugin_a]
|
||||||
|
)
|
||||||
|
|
||||||
|
assert "error" not in result
|
||||||
|
assert result["valid"] is True
|
||||||
|
assert "tool_one" in result["tool_refs_found"]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_validate_agent_refs_empty_agent(validation_tools, claude_md_with_agents, plugin_a):
|
||||||
|
"""Test agent with no tool references"""
|
||||||
|
result = await validation_tools.validate_agent_refs(
|
||||||
|
"EmptyAgent",
|
||||||
|
claude_md_with_agents,
|
||||||
|
[plugin_a]
|
||||||
|
)
|
||||||
|
|
||||||
|
assert "error" not in result
|
||||||
|
# Should have info issue about undocumented references
|
||||||
|
info_issues = [i for i in result["issues"] if i["severity"].value == "info"]
|
||||||
|
assert len(info_issues) > 0
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_validate_agent_refs_agent_not_found(validation_tools, claude_md_with_agents, plugin_a):
|
||||||
|
"""Test error when agent not found"""
|
||||||
|
result = await validation_tools.validate_agent_refs(
|
||||||
|
"NonexistentAgent",
|
||||||
|
claude_md_with_agents,
|
||||||
|
[plugin_a]
|
||||||
|
)
|
||||||
|
|
||||||
|
assert "error" in result
|
||||||
|
assert "not found" in result["error"].lower()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_validate_data_flow_valid(validation_tools, tmp_path):
|
||||||
|
"""Test data flow validation with valid flow"""
|
||||||
|
claude_md = tmp_path / "CLAUDE.md"
|
||||||
|
claude_md.write_text("""# CLAUDE.md
|
||||||
|
|
||||||
|
### Four-Agent Model
|
||||||
|
|
||||||
|
| Agent | Personality | Responsibilities |
|
||||||
|
|-------|-------------|------------------|
|
||||||
|
| **DataAgent** | Analytical | Load with `read_csv`, analyze with `describe`, export with `to_csv` |
|
||||||
|
""")
|
||||||
|
|
||||||
|
result = await validation_tools.validate_data_flow("DataAgent", str(claude_md))
|
||||||
|
|
||||||
|
assert "error" not in result
|
||||||
|
assert result["valid"] is True
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_validate_data_flow_missing_producer(validation_tools, tmp_path):
|
||||||
|
"""Test data flow with consumer but no producer"""
|
||||||
|
claude_md = tmp_path / "CLAUDE.md"
|
||||||
|
claude_md.write_text("""# CLAUDE.md
|
||||||
|
|
||||||
|
### Four-Agent Model
|
||||||
|
|
||||||
|
| Agent | Personality | Responsibilities |
|
||||||
|
|-------|-------------|------------------|
|
||||||
|
| **BadAgent** | Careless | Just runs `describe`, `head`, `tail` without loading |
|
||||||
|
""")
|
||||||
|
|
||||||
|
result = await validation_tools.validate_data_flow("BadAgent", str(claude_md))
|
||||||
|
|
||||||
|
assert "error" not in result
|
||||||
|
# Should have warning about missing producer
|
||||||
|
warning_issues = [i for i in result["issues"] if i["severity"].value == "warning"]
|
||||||
|
assert len(warning_issues) > 0
|
||||||
@@ -330,7 +330,7 @@ class PandasTools:
|
|||||||
return {'error': f'DataFrame not found: {data_ref}'}
|
return {'error': f'DataFrame not found: {data_ref}'}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
filtered = df.query(condition)
|
filtered = df.query(condition).reset_index(drop=True)
|
||||||
result_name = name or f"{data_ref}_filtered"
|
result_name = name or f"{data_ref}_filtered"
|
||||||
return self._check_and_store(
|
return self._check_and_store(
|
||||||
filtered,
|
filtered,
|
||||||
|
|||||||
21
mcp-servers/data-platform/run.sh
Executable file
21
mcp-servers/data-platform/run.sh
Executable file
@@ -0,0 +1,21 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Capture original working directory before any cd operations
|
||||||
|
# This should be the user's project directory when launched by Claude Code
|
||||||
|
export CLAUDE_PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$PWD}"
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
CACHE_VENV="$HOME/.cache/claude-mcp-venvs/leo-claude-mktplace/data-platform/.venv"
|
||||||
|
LOCAL_VENV="$SCRIPT_DIR/.venv"
|
||||||
|
|
||||||
|
if [[ -f "$CACHE_VENV/bin/python" ]]; then
|
||||||
|
PYTHON="$CACHE_VENV/bin/python"
|
||||||
|
elif [[ -f "$LOCAL_VENV/bin/python" ]]; then
|
||||||
|
PYTHON="$LOCAL_VENV/bin/python"
|
||||||
|
else
|
||||||
|
echo "ERROR: No venv found. Run: ./scripts/setup-venvs.sh" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd "$SCRIPT_DIR"
|
||||||
|
export PYTHONPATH="$SCRIPT_DIR"
|
||||||
|
exec "$PYTHON" -m mcp_server.server "$@"
|
||||||
@@ -135,9 +135,24 @@ class GiteaClient:
|
|||||||
body: Optional[str] = None,
|
body: Optional[str] = None,
|
||||||
state: Optional[str] = None,
|
state: Optional[str] = None,
|
||||||
labels: Optional[List[str]] = None,
|
labels: Optional[List[str]] = None,
|
||||||
|
milestone: Optional[int] = None,
|
||||||
repo: Optional[str] = None
|
repo: Optional[str] = None
|
||||||
) -> Dict:
|
) -> Dict:
|
||||||
"""Update existing issue. Repo must be 'owner/repo' format."""
|
"""
|
||||||
|
Update existing issue.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
issue_number: Issue number to update
|
||||||
|
title: New title (optional)
|
||||||
|
body: New body (optional)
|
||||||
|
state: New state - 'open' or 'closed' (optional)
|
||||||
|
labels: New labels (optional)
|
||||||
|
milestone: Milestone ID to assign (optional)
|
||||||
|
repo: Repository in 'owner/repo' format
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Updated issue dictionary
|
||||||
|
"""
|
||||||
owner, target_repo = self._parse_repo(repo)
|
owner, target_repo = self._parse_repo(repo)
|
||||||
url = f"{self.base_url}/repos/{owner}/{target_repo}/issues/{issue_number}"
|
url = f"{self.base_url}/repos/{owner}/{target_repo}/issues/{issue_number}"
|
||||||
data = {}
|
data = {}
|
||||||
@@ -149,6 +164,8 @@ class GiteaClient:
|
|||||||
data['state'] = state
|
data['state'] = state
|
||||||
if labels is not None:
|
if labels is not None:
|
||||||
data['labels'] = labels
|
data['labels'] = labels
|
||||||
|
if milestone is not None:
|
||||||
|
data['milestone'] = milestone
|
||||||
logger.info(f"Updating issue #{issue_number} in {owner}/{target_repo}")
|
logger.info(f"Updating issue #{issue_number} in {owner}/{target_repo}")
|
||||||
response = self.session.patch(url, json=data)
|
response = self.session.patch(url, json=data)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
@@ -787,3 +804,42 @@ class GiteaClient:
|
|||||||
response = self.session.post(url, json=data)
|
response = self.session.post(url, json=data)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
return response.json()
|
return response.json()
|
||||||
|
|
||||||
|
def create_pull_request(
|
||||||
|
self,
|
||||||
|
title: str,
|
||||||
|
body: str,
|
||||||
|
head: str,
|
||||||
|
base: str,
|
||||||
|
labels: Optional[List[str]] = None,
|
||||||
|
repo: Optional[str] = None
|
||||||
|
) -> Dict:
|
||||||
|
"""
|
||||||
|
Create a new pull request.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
title: PR title
|
||||||
|
body: PR description/body
|
||||||
|
head: Source branch name (the branch with changes)
|
||||||
|
base: Target branch name (the branch to merge into)
|
||||||
|
labels: Optional list of label names
|
||||||
|
repo: Repository in 'owner/repo' format
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Created pull request dictionary
|
||||||
|
"""
|
||||||
|
owner, target_repo = self._parse_repo(repo)
|
||||||
|
url = f"{self.base_url}/repos/{owner}/{target_repo}/pulls"
|
||||||
|
data = {
|
||||||
|
'title': title,
|
||||||
|
'body': body,
|
||||||
|
'head': head,
|
||||||
|
'base': base
|
||||||
|
}
|
||||||
|
if labels:
|
||||||
|
label_ids = self._resolve_label_ids(labels, owner, target_repo)
|
||||||
|
data['labels'] = label_ids
|
||||||
|
logger.info(f"Creating PR '{title}' in {owner}/{target_repo}: {head} -> {base}")
|
||||||
|
response = self.session.post(url, json=data)
|
||||||
|
response.raise_for_status()
|
||||||
|
return response.json()
|
||||||
|
|||||||
@@ -168,6 +168,10 @@ class GiteaMCPServer:
|
|||||||
"items": {"type": "string"},
|
"items": {"type": "string"},
|
||||||
"description": "New labels"
|
"description": "New labels"
|
||||||
},
|
},
|
||||||
|
"milestone": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Milestone ID to assign"
|
||||||
|
},
|
||||||
"repo": {
|
"repo": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Repository name (for PMO mode)"
|
"description": "Repository name (for PMO mode)"
|
||||||
@@ -844,6 +848,41 @@ class GiteaMCPServer:
|
|||||||
},
|
},
|
||||||
"required": ["pr_number", "body"]
|
"required": ["pr_number", "body"]
|
||||||
}
|
}
|
||||||
|
),
|
||||||
|
Tool(
|
||||||
|
name="create_pull_request",
|
||||||
|
description="Create a new pull request",
|
||||||
|
inputSchema={
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"title": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "PR title"
|
||||||
|
},
|
||||||
|
"body": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "PR description/body"
|
||||||
|
},
|
||||||
|
"head": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Source branch name (the branch with changes)"
|
||||||
|
},
|
||||||
|
"base": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Target branch name (the branch to merge into)"
|
||||||
|
},
|
||||||
|
"labels": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {"type": "string"},
|
||||||
|
"description": "Optional list of label names"
|
||||||
|
},
|
||||||
|
"repo": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Repository name (owner/repo format)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["title", "body", "head", "base"]
|
||||||
|
}
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -959,6 +998,8 @@ class GiteaMCPServer:
|
|||||||
result = await self.pr_tools.create_pr_review(**arguments)
|
result = await self.pr_tools.create_pr_review(**arguments)
|
||||||
elif name == "add_pr_comment":
|
elif name == "add_pr_comment":
|
||||||
result = await self.pr_tools.add_pr_comment(**arguments)
|
result = await self.pr_tools.add_pr_comment(**arguments)
|
||||||
|
elif name == "create_pull_request":
|
||||||
|
result = await self.pr_tools.create_pull_request(**arguments)
|
||||||
else:
|
else:
|
||||||
raise ValueError(f"Unknown tool: {name}")
|
raise ValueError(f"Unknown tool: {name}")
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ Provides async wrappers for issue CRUD operations with:
|
|||||||
- Comprehensive error handling
|
- Comprehensive error handling
|
||||||
"""
|
"""
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
import logging
|
import logging
|
||||||
from typing import List, Dict, Optional
|
from typing import List, Dict, Optional
|
||||||
@@ -27,19 +28,34 @@ class IssueTools:
|
|||||||
"""
|
"""
|
||||||
self.gitea = gitea_client
|
self.gitea = gitea_client
|
||||||
|
|
||||||
|
def _get_project_directory(self) -> Optional[str]:
|
||||||
|
"""
|
||||||
|
Get the user's project directory from environment.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Project directory path or None if not set
|
||||||
|
"""
|
||||||
|
return os.environ.get('CLAUDE_PROJECT_DIR')
|
||||||
|
|
||||||
def _get_current_branch(self) -> str:
|
def _get_current_branch(self) -> str:
|
||||||
"""
|
"""
|
||||||
Get current git branch.
|
Get current git branch from user's project directory.
|
||||||
|
|
||||||
|
Uses CLAUDE_PROJECT_DIR environment variable to determine the correct
|
||||||
|
directory for git operations, avoiding the bug where git runs from
|
||||||
|
the installed plugin directory instead of the user's project.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Current branch name or 'unknown' if not in a git repo
|
Current branch name or 'unknown' if not in a git repo
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
|
project_dir = self._get_project_directory()
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
['git', 'rev-parse', '--abbrev-ref', 'HEAD'],
|
['git', 'rev-parse', '--abbrev-ref', 'HEAD'],
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
text=True,
|
text=True,
|
||||||
check=True
|
check=True,
|
||||||
|
cwd=project_dir # Run git in project directory, not plugin directory
|
||||||
)
|
)
|
||||||
return result.stdout.strip()
|
return result.stdout.strip()
|
||||||
except subprocess.CalledProcessError:
|
except subprocess.CalledProcessError:
|
||||||
@@ -66,7 +82,13 @@ class IssueTools:
|
|||||||
return operation in ['list_issues', 'get_issue', 'get_labels', 'create_issue']
|
return operation in ['list_issues', 'get_issue', 'get_labels', 'create_issue']
|
||||||
|
|
||||||
# Development branches (full access)
|
# Development branches (full access)
|
||||||
if branch in ['development', 'develop'] or branch.startswith(('feat/', 'feature/', 'dev/')):
|
# Include all common feature/fix branch patterns
|
||||||
|
dev_prefixes = (
|
||||||
|
'feat/', 'feature/', 'dev/',
|
||||||
|
'fix/', 'bugfix/', 'hotfix/',
|
||||||
|
'chore/', 'refactor/', 'docs/', 'test/'
|
||||||
|
)
|
||||||
|
if branch in ['development', 'develop'] or branch.startswith(dev_prefixes):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# Unknown branch - be restrictive
|
# Unknown branch - be restrictive
|
||||||
@@ -178,6 +200,7 @@ class IssueTools:
|
|||||||
body: Optional[str] = None,
|
body: Optional[str] = None,
|
||||||
state: Optional[str] = None,
|
state: Optional[str] = None,
|
||||||
labels: Optional[List[str]] = None,
|
labels: Optional[List[str]] = None,
|
||||||
|
milestone: Optional[int] = None,
|
||||||
repo: Optional[str] = None
|
repo: Optional[str] = None
|
||||||
) -> Dict:
|
) -> Dict:
|
||||||
"""
|
"""
|
||||||
@@ -189,6 +212,7 @@ class IssueTools:
|
|||||||
body: New body (optional)
|
body: New body (optional)
|
||||||
state: New state - 'open' or 'closed' (optional)
|
state: New state - 'open' or 'closed' (optional)
|
||||||
labels: New labels (optional)
|
labels: New labels (optional)
|
||||||
|
milestone: Milestone ID to assign (optional)
|
||||||
repo: Override configured repo (for PMO multi-repo)
|
repo: Override configured repo (for PMO multi-repo)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@@ -207,7 +231,7 @@ class IssueTools:
|
|||||||
loop = asyncio.get_event_loop()
|
loop = asyncio.get_event_loop()
|
||||||
return await loop.run_in_executor(
|
return await loop.run_in_executor(
|
||||||
None,
|
None,
|
||||||
lambda: self.gitea.update_issue(issue_number, title, body, state, labels, repo)
|
lambda: self.gitea.update_issue(issue_number, title, body, state, labels, milestone, repo)
|
||||||
)
|
)
|
||||||
|
|
||||||
async def add_comment(
|
async def add_comment(
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ Provides async wrappers for PR operations with:
|
|||||||
- Comprehensive error handling
|
- Comprehensive error handling
|
||||||
"""
|
"""
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
import logging
|
import logging
|
||||||
from typing import List, Dict, Optional
|
from typing import List, Dict, Optional
|
||||||
@@ -27,19 +28,34 @@ class PullRequestTools:
|
|||||||
"""
|
"""
|
||||||
self.gitea = gitea_client
|
self.gitea = gitea_client
|
||||||
|
|
||||||
|
def _get_project_directory(self) -> Optional[str]:
|
||||||
|
"""
|
||||||
|
Get the user's project directory from environment.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Project directory path or None if not set
|
||||||
|
"""
|
||||||
|
return os.environ.get('CLAUDE_PROJECT_DIR')
|
||||||
|
|
||||||
def _get_current_branch(self) -> str:
|
def _get_current_branch(self) -> str:
|
||||||
"""
|
"""
|
||||||
Get current git branch.
|
Get current git branch from user's project directory.
|
||||||
|
|
||||||
|
Uses CLAUDE_PROJECT_DIR environment variable to determine the correct
|
||||||
|
directory for git operations, avoiding the bug where git runs from
|
||||||
|
the installed plugin directory instead of the user's project.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Current branch name or 'unknown' if not in a git repo
|
Current branch name or 'unknown' if not in a git repo
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
|
project_dir = self._get_project_directory()
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
['git', 'rev-parse', '--abbrev-ref', 'HEAD'],
|
['git', 'rev-parse', '--abbrev-ref', 'HEAD'],
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
text=True,
|
text=True,
|
||||||
check=True
|
check=True,
|
||||||
|
cwd=project_dir # Run git in project directory, not plugin directory
|
||||||
)
|
)
|
||||||
return result.stdout.strip()
|
return result.stdout.strip()
|
||||||
except subprocess.CalledProcessError:
|
except subprocess.CalledProcessError:
|
||||||
@@ -69,7 +85,13 @@ class PullRequestTools:
|
|||||||
return operation in read_ops + ['add_pr_comment']
|
return operation in read_ops + ['add_pr_comment']
|
||||||
|
|
||||||
# Development branches (full access)
|
# Development branches (full access)
|
||||||
if branch in ['development', 'develop'] or branch.startswith(('feat/', 'feature/', 'dev/')):
|
# Include all common feature/fix branch patterns
|
||||||
|
dev_prefixes = (
|
||||||
|
'feat/', 'feature/', 'dev/',
|
||||||
|
'fix/', 'bugfix/', 'hotfix/',
|
||||||
|
'chore/', 'refactor/', 'docs/', 'test/'
|
||||||
|
)
|
||||||
|
if branch in ['development', 'develop'] or branch.startswith(dev_prefixes):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# Unknown branch - be restrictive
|
# Unknown branch - be restrictive
|
||||||
@@ -272,3 +294,42 @@ class PullRequestTools:
|
|||||||
None,
|
None,
|
||||||
lambda: self.gitea.add_pr_comment(pr_number, body, repo)
|
lambda: self.gitea.add_pr_comment(pr_number, body, repo)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def create_pull_request(
|
||||||
|
self,
|
||||||
|
title: str,
|
||||||
|
body: str,
|
||||||
|
head: str,
|
||||||
|
base: str,
|
||||||
|
labels: Optional[List[str]] = None,
|
||||||
|
repo: Optional[str] = None
|
||||||
|
) -> Dict:
|
||||||
|
"""
|
||||||
|
Create a new pull request (async wrapper with branch check).
|
||||||
|
|
||||||
|
Args:
|
||||||
|
title: PR title
|
||||||
|
body: PR description/body
|
||||||
|
head: Source branch name (the branch with changes)
|
||||||
|
base: Target branch name (the branch to merge into)
|
||||||
|
labels: Optional list of label names
|
||||||
|
repo: Override configured repo (for PMO multi-repo)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Created pull request dictionary
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
PermissionError: If operation not allowed on current branch
|
||||||
|
"""
|
||||||
|
if not self._check_branch_permissions('create_pull_request'):
|
||||||
|
branch = self._get_current_branch()
|
||||||
|
raise PermissionError(
|
||||||
|
f"Cannot create PR on branch '{branch}'. "
|
||||||
|
f"Switch to a development or feature branch to create PRs."
|
||||||
|
)
|
||||||
|
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
return await loop.run_in_executor(
|
||||||
|
None,
|
||||||
|
lambda: self.gitea.create_pull_request(title, body, head, base, labels, repo)
|
||||||
|
)
|
||||||
|
|||||||
21
mcp-servers/gitea/run.sh
Executable file
21
mcp-servers/gitea/run.sh
Executable file
@@ -0,0 +1,21 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Capture original working directory before any cd operations
|
||||||
|
# This should be the user's project directory when launched by Claude Code
|
||||||
|
export CLAUDE_PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$PWD}"
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
CACHE_VENV="$HOME/.cache/claude-mcp-venvs/leo-claude-mktplace/gitea/.venv"
|
||||||
|
LOCAL_VENV="$SCRIPT_DIR/.venv"
|
||||||
|
|
||||||
|
if [[ -f "$CACHE_VENV/bin/python" ]]; then
|
||||||
|
PYTHON="$CACHE_VENV/bin/python"
|
||||||
|
elif [[ -f "$LOCAL_VENV/bin/python" ]]; then
|
||||||
|
PYTHON="$LOCAL_VENV/bin/python"
|
||||||
|
else
|
||||||
|
echo "ERROR: No venv found. Run: ./scripts/setup-venvs.sh" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd "$SCRIPT_DIR"
|
||||||
|
export PYTHONPATH="$SCRIPT_DIR"
|
||||||
|
exec "$PYTHON" -m mcp_server.server "$@"
|
||||||
21
mcp-servers/netbox/run.sh
Executable file
21
mcp-servers/netbox/run.sh
Executable file
@@ -0,0 +1,21 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Capture original working directory before any cd operations
|
||||||
|
# This should be the user's project directory when launched by Claude Code
|
||||||
|
export CLAUDE_PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$PWD}"
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
CACHE_VENV="$HOME/.cache/claude-mcp-venvs/leo-claude-mktplace/netbox/.venv"
|
||||||
|
LOCAL_VENV="$SCRIPT_DIR/.venv"
|
||||||
|
|
||||||
|
if [[ -f "$CACHE_VENV/bin/python" ]]; then
|
||||||
|
PYTHON="$CACHE_VENV/bin/python"
|
||||||
|
elif [[ -f "$LOCAL_VENV/bin/python" ]]; then
|
||||||
|
PYTHON="$LOCAL_VENV/bin/python"
|
||||||
|
else
|
||||||
|
echo "ERROR: No venv found. Run: ./scripts/setup-venvs.sh" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd "$SCRIPT_DIR"
|
||||||
|
export PYTHONPATH="$SCRIPT_DIR"
|
||||||
|
exec "$PYTHON" -m mcp_server.server "$@"
|
||||||
115
mcp-servers/viz-platform/README.md
Normal file
115
mcp-servers/viz-platform/README.md
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
# viz-platform MCP Server
|
||||||
|
|
||||||
|
Model Context Protocol (MCP) server for Dash Mantine Components validation and visualization tools.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This MCP server provides 21 tools for:
|
||||||
|
- **DMC Validation**: Version-locked component registry prevents Claude from hallucinating invalid props
|
||||||
|
- **Chart Creation**: Plotly-based visualization with theme integration
|
||||||
|
- **Layout Composition**: Dashboard layouts with responsive grids
|
||||||
|
- **Theme Management**: Design token-based theming system
|
||||||
|
- **Page Structure**: Multi-page Dash app generation
|
||||||
|
|
||||||
|
## Tools
|
||||||
|
|
||||||
|
### DMC Tools (3)
|
||||||
|
|
||||||
|
| Tool | Description |
|
||||||
|
|------|-------------|
|
||||||
|
| `list_components` | List available DMC components by category |
|
||||||
|
| `get_component_props` | Get valid props, types, and defaults for a component |
|
||||||
|
| `validate_component` | Validate component definition before use |
|
||||||
|
|
||||||
|
### Chart Tools (2)
|
||||||
|
|
||||||
|
| Tool | Description |
|
||||||
|
|------|-------------|
|
||||||
|
| `chart_create` | Create Plotly chart (line, bar, scatter, pie, histogram, area, heatmap) |
|
||||||
|
| `chart_configure_interaction` | Configure chart interactions (zoom, pan, hover) |
|
||||||
|
|
||||||
|
### Layout Tools (5)
|
||||||
|
|
||||||
|
| Tool | Description |
|
||||||
|
|------|-------------|
|
||||||
|
| `layout_create` | Create dashboard layout structure |
|
||||||
|
| `layout_add_filter` | Add filter components to layout |
|
||||||
|
| `layout_set_grid` | Configure responsive grid settings |
|
||||||
|
| `layout_get` | Retrieve layout configuration |
|
||||||
|
| `layout_add_section` | Add sections to layout |
|
||||||
|
|
||||||
|
### Theme Tools (6)
|
||||||
|
|
||||||
|
| Tool | Description |
|
||||||
|
|------|-------------|
|
||||||
|
| `theme_create` | Create new theme with design tokens |
|
||||||
|
| `theme_extend` | Extend existing theme with overrides |
|
||||||
|
| `theme_validate` | Validate theme completeness |
|
||||||
|
| `theme_export_css` | Export theme as CSS custom properties |
|
||||||
|
| `theme_list` | List available themes |
|
||||||
|
| `theme_activate` | Set active theme for visualizations |
|
||||||
|
|
||||||
|
### Page Tools (5)
|
||||||
|
|
||||||
|
| Tool | Description |
|
||||||
|
|------|-------------|
|
||||||
|
| `page_create` | Create new page structure |
|
||||||
|
| `page_add_navbar` | Add navigation bar to page |
|
||||||
|
| `page_set_auth` | Configure page authentication |
|
||||||
|
| `page_list` | List available pages |
|
||||||
|
| `page_get_app_config` | Get full app configuration |
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
### Environment Variables
|
||||||
|
|
||||||
|
| Variable | Required | Description |
|
||||||
|
|----------|----------|-------------|
|
||||||
|
| `DMC_VERSION` | No | Dash Mantine Components version (auto-detected if installed) |
|
||||||
|
| `VIZ_DEFAULT_THEME` | No | Default theme name |
|
||||||
|
| `CLAUDE_PROJECT_DIR` | No | Project directory for theme storage |
|
||||||
|
|
||||||
|
### Theme Storage
|
||||||
|
|
||||||
|
Themes can be stored at two levels:
|
||||||
|
- **User-level**: `~/.config/claude/themes/`
|
||||||
|
- **Project-level**: `{project}/.viz-platform/themes/`
|
||||||
|
|
||||||
|
Project-level themes take precedence.
|
||||||
|
|
||||||
|
## Component Registry
|
||||||
|
|
||||||
|
The server uses a static JSON registry for DMC component validation:
|
||||||
|
- Pre-generated from DMC source code
|
||||||
|
- Version-tagged (e.g., `dmc_2_5.json`)
|
||||||
|
- Prevents hallucination of non-existent props
|
||||||
|
- Fast, deterministic validation
|
||||||
|
|
||||||
|
Registry files are stored in `registry/` directory.
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
|
||||||
|
94 tests with coverage:
|
||||||
|
- `test_config.py`: 82% coverage
|
||||||
|
- `test_component_registry.py`: 92% coverage
|
||||||
|
- `test_dmc_tools.py`: 88% coverage
|
||||||
|
- `test_chart_tools.py`: 68% coverage
|
||||||
|
- `test_theme_tools.py`: 99% coverage
|
||||||
|
|
||||||
|
Run tests:
|
||||||
|
```bash
|
||||||
|
cd mcp-servers/viz-platform
|
||||||
|
source .venv/bin/activate
|
||||||
|
pytest tests/ -v
|
||||||
|
```
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
- Python 3.10+
|
||||||
|
- FastMCP
|
||||||
|
- plotly
|
||||||
|
- dash-mantine-components (optional, for version detection)
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
This MCP server is used by the `viz-platform` plugin. See [plugins/viz-platform/README.md](../../plugins/viz-platform/README.md) for usage instructions.
|
||||||
479
mcp-servers/viz-platform/mcp_server/accessibility_tools.py
Normal file
479
mcp-servers/viz-platform/mcp_server/accessibility_tools.py
Normal file
@@ -0,0 +1,479 @@
|
|||||||
|
"""
|
||||||
|
Accessibility validation tools for color blindness and WCAG compliance.
|
||||||
|
|
||||||
|
Provides tools for validating color palettes against color blindness
|
||||||
|
simulations and WCAG contrast requirements.
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
import math
|
||||||
|
from typing import Dict, List, Optional, Any, Tuple
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
# Color-blind safe palettes
|
||||||
|
SAFE_PALETTES = {
|
||||||
|
"categorical": {
|
||||||
|
"name": "Paul Tol's Qualitative",
|
||||||
|
"colors": ["#4477AA", "#EE6677", "#228833", "#CCBB44", "#66CCEE", "#AA3377", "#BBBBBB"],
|
||||||
|
"description": "Distinguishable for all types of color blindness"
|
||||||
|
},
|
||||||
|
"ibm": {
|
||||||
|
"name": "IBM Design",
|
||||||
|
"colors": ["#648FFF", "#785EF0", "#DC267F", "#FE6100", "#FFB000"],
|
||||||
|
"description": "IBM's accessible color palette"
|
||||||
|
},
|
||||||
|
"okabe_ito": {
|
||||||
|
"name": "Okabe-Ito",
|
||||||
|
"colors": ["#E69F00", "#56B4E9", "#009E73", "#F0E442", "#0072B2", "#D55E00", "#CC79A7", "#000000"],
|
||||||
|
"description": "Optimized for all color vision deficiencies"
|
||||||
|
},
|
||||||
|
"tableau_colorblind": {
|
||||||
|
"name": "Tableau Colorblind 10",
|
||||||
|
"colors": ["#006BA4", "#FF800E", "#ABABAB", "#595959", "#5F9ED1",
|
||||||
|
"#C85200", "#898989", "#A2C8EC", "#FFBC79", "#CFCFCF"],
|
||||||
|
"description": "Industry-standard accessible palette"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Simulation matrices for color blindness (LMS color space transformation)
|
||||||
|
# These approximate how colors appear to people with different types of color blindness
|
||||||
|
SIMULATION_MATRICES = {
|
||||||
|
"deuteranopia": {
|
||||||
|
# Green-blind (most common)
|
||||||
|
"severity": "common",
|
||||||
|
"population": "6% males, 0.4% females",
|
||||||
|
"description": "Difficulty distinguishing red from green (green-blind)",
|
||||||
|
"matrix": [
|
||||||
|
[0.625, 0.375, 0.0],
|
||||||
|
[0.700, 0.300, 0.0],
|
||||||
|
[0.0, 0.300, 0.700]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"protanopia": {
|
||||||
|
# Red-blind
|
||||||
|
"severity": "common",
|
||||||
|
"population": "2.5% males, 0.05% females",
|
||||||
|
"description": "Difficulty distinguishing red from green (red-blind)",
|
||||||
|
"matrix": [
|
||||||
|
[0.567, 0.433, 0.0],
|
||||||
|
[0.558, 0.442, 0.0],
|
||||||
|
[0.0, 0.242, 0.758]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"tritanopia": {
|
||||||
|
# Blue-blind (rare)
|
||||||
|
"severity": "rare",
|
||||||
|
"population": "0.01% total",
|
||||||
|
"description": "Difficulty distinguishing blue from yellow",
|
||||||
|
"matrix": [
|
||||||
|
[0.950, 0.050, 0.0],
|
||||||
|
[0.0, 0.433, 0.567],
|
||||||
|
[0.0, 0.475, 0.525]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class AccessibilityTools:
|
||||||
|
"""
|
||||||
|
Color accessibility validation tools.
|
||||||
|
|
||||||
|
Validates colors for WCAG compliance and color blindness accessibility.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, theme_store=None):
|
||||||
|
"""
|
||||||
|
Initialize accessibility tools.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
theme_store: Optional ThemeStore for theme color extraction
|
||||||
|
"""
|
||||||
|
self.theme_store = theme_store
|
||||||
|
|
||||||
|
def _hex_to_rgb(self, hex_color: str) -> Tuple[int, int, int]:
|
||||||
|
"""Convert hex color to RGB tuple."""
|
||||||
|
hex_color = hex_color.lstrip('#')
|
||||||
|
if len(hex_color) == 3:
|
||||||
|
hex_color = ''.join([c * 2 for c in hex_color])
|
||||||
|
return tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))
|
||||||
|
|
||||||
|
def _rgb_to_hex(self, rgb: Tuple[int, int, int]) -> str:
|
||||||
|
"""Convert RGB tuple to hex color."""
|
||||||
|
return '#{:02x}{:02x}{:02x}'.format(
|
||||||
|
max(0, min(255, int(rgb[0]))),
|
||||||
|
max(0, min(255, int(rgb[1]))),
|
||||||
|
max(0, min(255, int(rgb[2])))
|
||||||
|
)
|
||||||
|
|
||||||
|
def _get_relative_luminance(self, rgb: Tuple[int, int, int]) -> float:
|
||||||
|
"""
|
||||||
|
Calculate relative luminance per WCAG 2.1.
|
||||||
|
|
||||||
|
https://www.w3.org/WAI/GL/wiki/Relative_luminance
|
||||||
|
"""
|
||||||
|
def channel_luminance(value: int) -> float:
|
||||||
|
v = value / 255
|
||||||
|
return v / 12.92 if v <= 0.03928 else ((v + 0.055) / 1.055) ** 2.4
|
||||||
|
|
||||||
|
r, g, b = rgb
|
||||||
|
return (
|
||||||
|
0.2126 * channel_luminance(r) +
|
||||||
|
0.7152 * channel_luminance(g) +
|
||||||
|
0.0722 * channel_luminance(b)
|
||||||
|
)
|
||||||
|
|
||||||
|
def _get_contrast_ratio(self, color1: str, color2: str) -> float:
|
||||||
|
"""
|
||||||
|
Calculate contrast ratio between two colors per WCAG 2.1.
|
||||||
|
|
||||||
|
Returns ratio between 1:1 and 21:1.
|
||||||
|
"""
|
||||||
|
rgb1 = self._hex_to_rgb(color1)
|
||||||
|
rgb2 = self._hex_to_rgb(color2)
|
||||||
|
|
||||||
|
l1 = self._get_relative_luminance(rgb1)
|
||||||
|
l2 = self._get_relative_luminance(rgb2)
|
||||||
|
|
||||||
|
lighter = max(l1, l2)
|
||||||
|
darker = min(l1, l2)
|
||||||
|
|
||||||
|
return (lighter + 0.05) / (darker + 0.05)
|
||||||
|
|
||||||
|
def _simulate_color_blindness(
|
||||||
|
self,
|
||||||
|
hex_color: str,
|
||||||
|
deficiency_type: str
|
||||||
|
) -> str:
|
||||||
|
"""
|
||||||
|
Simulate how a color appears with a specific color blindness type.
|
||||||
|
|
||||||
|
Uses linear RGB transformation approximation.
|
||||||
|
"""
|
||||||
|
if deficiency_type not in SIMULATION_MATRICES:
|
||||||
|
return hex_color
|
||||||
|
|
||||||
|
rgb = self._hex_to_rgb(hex_color)
|
||||||
|
matrix = SIMULATION_MATRICES[deficiency_type]["matrix"]
|
||||||
|
|
||||||
|
# Apply transformation matrix
|
||||||
|
r = rgb[0] * matrix[0][0] + rgb[1] * matrix[0][1] + rgb[2] * matrix[0][2]
|
||||||
|
g = rgb[0] * matrix[1][0] + rgb[1] * matrix[1][1] + rgb[2] * matrix[1][2]
|
||||||
|
b = rgb[0] * matrix[2][0] + rgb[1] * matrix[2][1] + rgb[2] * matrix[2][2]
|
||||||
|
|
||||||
|
return self._rgb_to_hex((r, g, b))
|
||||||
|
|
||||||
|
def _get_color_distance(self, color1: str, color2: str) -> float:
|
||||||
|
"""
|
||||||
|
Calculate perceptual color distance (CIE76 approximation).
|
||||||
|
|
||||||
|
Returns a value where < 20 means colors may be hard to distinguish.
|
||||||
|
"""
|
||||||
|
rgb1 = self._hex_to_rgb(color1)
|
||||||
|
rgb2 = self._hex_to_rgb(color2)
|
||||||
|
|
||||||
|
# Simple Euclidean distance in RGB space (approximation)
|
||||||
|
# For production, should use CIEDE2000
|
||||||
|
return math.sqrt(
|
||||||
|
(rgb1[0] - rgb2[0]) ** 2 +
|
||||||
|
(rgb1[1] - rgb2[1]) ** 2 +
|
||||||
|
(rgb1[2] - rgb2[2]) ** 2
|
||||||
|
)
|
||||||
|
|
||||||
|
async def accessibility_validate_colors(
|
||||||
|
self,
|
||||||
|
colors: List[str],
|
||||||
|
check_types: Optional[List[str]] = None,
|
||||||
|
min_contrast_ratio: float = 4.5
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Validate a list of colors for accessibility.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
colors: List of hex colors to validate
|
||||||
|
check_types: Color blindness types to check (default: all)
|
||||||
|
min_contrast_ratio: Minimum WCAG contrast ratio (default: 4.5 for AA)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict with:
|
||||||
|
- issues: List of accessibility issues found
|
||||||
|
- simulations: How colors appear under each deficiency
|
||||||
|
- recommendations: Suggestions for improvement
|
||||||
|
- safe_palettes: Color-blind safe palette suggestions
|
||||||
|
"""
|
||||||
|
check_types = check_types or list(SIMULATION_MATRICES.keys())
|
||||||
|
issues = []
|
||||||
|
simulations = {}
|
||||||
|
|
||||||
|
# Normalize colors
|
||||||
|
normalized_colors = [c.upper() if c.startswith('#') else f'#{c.upper()}' for c in colors]
|
||||||
|
|
||||||
|
# Simulate each color blindness type
|
||||||
|
for deficiency in check_types:
|
||||||
|
if deficiency not in SIMULATION_MATRICES:
|
||||||
|
continue
|
||||||
|
|
||||||
|
simulated = [self._simulate_color_blindness(c, deficiency) for c in normalized_colors]
|
||||||
|
simulations[deficiency] = {
|
||||||
|
"original": normalized_colors,
|
||||||
|
"simulated": simulated,
|
||||||
|
"info": SIMULATION_MATRICES[deficiency]
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check if any color pairs become indistinguishable
|
||||||
|
for i in range(len(normalized_colors)):
|
||||||
|
for j in range(i + 1, len(normalized_colors)):
|
||||||
|
distance = self._get_color_distance(simulated[i], simulated[j])
|
||||||
|
if distance < 30: # Threshold for distinguishability
|
||||||
|
issues.append({
|
||||||
|
"type": "distinguishability",
|
||||||
|
"severity": "warning" if distance > 15 else "error",
|
||||||
|
"colors": [normalized_colors[i], normalized_colors[j]],
|
||||||
|
"affected_by": [deficiency],
|
||||||
|
"simulated_colors": [simulated[i], simulated[j]],
|
||||||
|
"distance": round(distance, 1),
|
||||||
|
"message": f"Colors may be hard to distinguish for {deficiency} ({SIMULATION_MATRICES[deficiency]['description']})"
|
||||||
|
})
|
||||||
|
|
||||||
|
# Check contrast ratios against white and black backgrounds
|
||||||
|
for color in normalized_colors:
|
||||||
|
white_contrast = self._get_contrast_ratio(color, "#FFFFFF")
|
||||||
|
black_contrast = self._get_contrast_ratio(color, "#000000")
|
||||||
|
|
||||||
|
if white_contrast < min_contrast_ratio and black_contrast < min_contrast_ratio:
|
||||||
|
issues.append({
|
||||||
|
"type": "contrast_ratio",
|
||||||
|
"severity": "error",
|
||||||
|
"colors": [color],
|
||||||
|
"white_contrast": round(white_contrast, 2),
|
||||||
|
"black_contrast": round(black_contrast, 2),
|
||||||
|
"required": min_contrast_ratio,
|
||||||
|
"message": f"Insufficient contrast against both white ({white_contrast:.1f}:1) and black ({black_contrast:.1f}:1) backgrounds"
|
||||||
|
})
|
||||||
|
|
||||||
|
# Generate recommendations
|
||||||
|
recommendations = self._generate_recommendations(issues)
|
||||||
|
|
||||||
|
# Calculate overall score
|
||||||
|
error_count = sum(1 for i in issues if i["severity"] == "error")
|
||||||
|
warning_count = sum(1 for i in issues if i["severity"] == "warning")
|
||||||
|
|
||||||
|
if error_count == 0 and warning_count == 0:
|
||||||
|
score = "A"
|
||||||
|
elif error_count == 0 and warning_count <= 2:
|
||||||
|
score = "B"
|
||||||
|
elif error_count <= 2:
|
||||||
|
score = "C"
|
||||||
|
else:
|
||||||
|
score = "D"
|
||||||
|
|
||||||
|
return {
|
||||||
|
"colors_checked": normalized_colors,
|
||||||
|
"overall_score": score,
|
||||||
|
"issue_count": len(issues),
|
||||||
|
"issues": issues,
|
||||||
|
"simulations": simulations,
|
||||||
|
"recommendations": recommendations,
|
||||||
|
"safe_palettes": SAFE_PALETTES
|
||||||
|
}
|
||||||
|
|
||||||
|
async def accessibility_validate_theme(
|
||||||
|
self,
|
||||||
|
theme_name: str
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Validate a theme's colors for accessibility.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
theme_name: Theme name to validate
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict with accessibility validation results
|
||||||
|
"""
|
||||||
|
if not self.theme_store:
|
||||||
|
return {
|
||||||
|
"error": "Theme store not configured",
|
||||||
|
"theme_name": theme_name
|
||||||
|
}
|
||||||
|
|
||||||
|
theme = self.theme_store.get_theme(theme_name)
|
||||||
|
if not theme:
|
||||||
|
available = self.theme_store.list_themes()
|
||||||
|
return {
|
||||||
|
"error": f"Theme '{theme_name}' not found. Available: {available}",
|
||||||
|
"theme_name": theme_name
|
||||||
|
}
|
||||||
|
|
||||||
|
# Extract colors from theme
|
||||||
|
colors = []
|
||||||
|
tokens = theme.get("tokens", {})
|
||||||
|
color_tokens = tokens.get("colors", {})
|
||||||
|
|
||||||
|
def extract_colors(obj, prefix=""):
|
||||||
|
"""Recursively extract color values."""
|
||||||
|
if isinstance(obj, str) and (obj.startswith('#') or len(obj) == 6):
|
||||||
|
colors.append(obj if obj.startswith('#') else f'#{obj}')
|
||||||
|
elif isinstance(obj, dict):
|
||||||
|
for key, value in obj.items():
|
||||||
|
extract_colors(value, f"{prefix}.{key}")
|
||||||
|
elif isinstance(obj, list):
|
||||||
|
for item in obj:
|
||||||
|
extract_colors(item, prefix)
|
||||||
|
|
||||||
|
extract_colors(color_tokens)
|
||||||
|
|
||||||
|
# Validate extracted colors
|
||||||
|
result = await self.accessibility_validate_colors(colors)
|
||||||
|
result["theme_name"] = theme_name
|
||||||
|
|
||||||
|
# Add theme-specific checks
|
||||||
|
primary = color_tokens.get("primary")
|
||||||
|
background = color_tokens.get("background", {})
|
||||||
|
text = color_tokens.get("text", {})
|
||||||
|
|
||||||
|
if primary and background:
|
||||||
|
bg_color = background.get("base") if isinstance(background, dict) else background
|
||||||
|
if bg_color:
|
||||||
|
contrast = self._get_contrast_ratio(primary, bg_color)
|
||||||
|
if contrast < 4.5:
|
||||||
|
result["issues"].append({
|
||||||
|
"type": "primary_contrast",
|
||||||
|
"severity": "error",
|
||||||
|
"colors": [primary, bg_color],
|
||||||
|
"ratio": round(contrast, 2),
|
||||||
|
"required": 4.5,
|
||||||
|
"message": f"Primary color has insufficient contrast ({contrast:.1f}:1) against background"
|
||||||
|
})
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
async def accessibility_suggest_alternative(
|
||||||
|
self,
|
||||||
|
color: str,
|
||||||
|
deficiency_type: str
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Suggest accessible alternative colors.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
color: Original hex color
|
||||||
|
deficiency_type: Type of color blindness to optimize for
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict with alternative color suggestions
|
||||||
|
"""
|
||||||
|
rgb = self._hex_to_rgb(color)
|
||||||
|
|
||||||
|
suggestions = []
|
||||||
|
|
||||||
|
# Suggest shifting hue while maintaining saturation and brightness
|
||||||
|
# For red-green deficiency, shift toward blue or yellow
|
||||||
|
if deficiency_type in ["deuteranopia", "protanopia"]:
|
||||||
|
# Shift toward blue
|
||||||
|
blue_shift = self._rgb_to_hex((
|
||||||
|
max(0, rgb[0] - 50),
|
||||||
|
max(0, rgb[1] - 30),
|
||||||
|
min(255, rgb[2] + 80)
|
||||||
|
))
|
||||||
|
suggestions.append({
|
||||||
|
"color": blue_shift,
|
||||||
|
"description": "Blue-shifted alternative",
|
||||||
|
"preserves": "approximate brightness"
|
||||||
|
})
|
||||||
|
|
||||||
|
# Shift toward yellow/orange
|
||||||
|
yellow_shift = self._rgb_to_hex((
|
||||||
|
min(255, rgb[0] + 50),
|
||||||
|
min(255, rgb[1] + 30),
|
||||||
|
max(0, rgb[2] - 80)
|
||||||
|
))
|
||||||
|
suggestions.append({
|
||||||
|
"color": yellow_shift,
|
||||||
|
"description": "Yellow-shifted alternative",
|
||||||
|
"preserves": "approximate brightness"
|
||||||
|
})
|
||||||
|
|
||||||
|
elif deficiency_type == "tritanopia":
|
||||||
|
# For blue-yellow deficiency, shift toward red or green
|
||||||
|
red_shift = self._rgb_to_hex((
|
||||||
|
min(255, rgb[0] + 60),
|
||||||
|
max(0, rgb[1] - 20),
|
||||||
|
max(0, rgb[2] - 40)
|
||||||
|
))
|
||||||
|
suggestions.append({
|
||||||
|
"color": red_shift,
|
||||||
|
"description": "Red-shifted alternative",
|
||||||
|
"preserves": "approximate brightness"
|
||||||
|
})
|
||||||
|
|
||||||
|
# Add safe palette suggestions
|
||||||
|
for palette_name, palette in SAFE_PALETTES.items():
|
||||||
|
# Find closest color in safe palette
|
||||||
|
min_distance = float('inf')
|
||||||
|
closest = None
|
||||||
|
for safe_color in palette["colors"]:
|
||||||
|
distance = self._get_color_distance(color, safe_color)
|
||||||
|
if distance < min_distance:
|
||||||
|
min_distance = distance
|
||||||
|
closest = safe_color
|
||||||
|
|
||||||
|
if closest:
|
||||||
|
suggestions.append({
|
||||||
|
"color": closest,
|
||||||
|
"description": f"From {palette['name']} palette",
|
||||||
|
"palette": palette_name
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
"original_color": color,
|
||||||
|
"deficiency_type": deficiency_type,
|
||||||
|
"suggestions": suggestions[:5] # Limit to 5 suggestions
|
||||||
|
}
|
||||||
|
|
||||||
|
def _generate_recommendations(self, issues: List[Dict[str, Any]]) -> List[str]:
|
||||||
|
"""Generate actionable recommendations based on issues."""
|
||||||
|
recommendations = []
|
||||||
|
|
||||||
|
# Check for distinguishability issues
|
||||||
|
distinguishability_issues = [i for i in issues if i["type"] == "distinguishability"]
|
||||||
|
if distinguishability_issues:
|
||||||
|
affected_types = set()
|
||||||
|
for issue in distinguishability_issues:
|
||||||
|
affected_types.update(issue.get("affected_by", []))
|
||||||
|
|
||||||
|
if "deuteranopia" in affected_types or "protanopia" in affected_types:
|
||||||
|
recommendations.append(
|
||||||
|
"Avoid using red and green as the only differentiators - "
|
||||||
|
"add patterns, shapes, or labels"
|
||||||
|
)
|
||||||
|
|
||||||
|
recommendations.append(
|
||||||
|
"Consider using a color-blind safe palette like Okabe-Ito or IBM Design"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check for contrast issues
|
||||||
|
contrast_issues = [i for i in issues if i["type"] in ["contrast_ratio", "primary_contrast"]]
|
||||||
|
if contrast_issues:
|
||||||
|
recommendations.append(
|
||||||
|
"Increase contrast by darkening colors for light backgrounds "
|
||||||
|
"or lightening for dark backgrounds"
|
||||||
|
)
|
||||||
|
recommendations.append(
|
||||||
|
"Use WCAG contrast checker tools to verify text readability"
|
||||||
|
)
|
||||||
|
|
||||||
|
# General recommendations
|
||||||
|
if len(issues) > 0:
|
||||||
|
recommendations.append(
|
||||||
|
"Add secondary visual cues (icons, patterns, labels) "
|
||||||
|
"to not rely solely on color"
|
||||||
|
)
|
||||||
|
|
||||||
|
if not recommendations:
|
||||||
|
recommendations.append(
|
||||||
|
"Color palette appears accessible! Consider adding patterns "
|
||||||
|
"for additional distinguishability"
|
||||||
|
)
|
||||||
|
|
||||||
|
return recommendations
|
||||||
@@ -3,11 +3,21 @@ Chart creation tools using Plotly.
|
|||||||
|
|
||||||
Provides tools for creating data visualizations with automatic theme integration.
|
Provides tools for creating data visualizations with automatic theme integration.
|
||||||
"""
|
"""
|
||||||
|
import base64
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
from typing import Dict, List, Optional, Any, Union
|
from typing import Dict, List, Optional, Any, Union
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# Check for kaleido availability
|
||||||
|
KALEIDO_AVAILABLE = False
|
||||||
|
try:
|
||||||
|
import kaleido
|
||||||
|
KALEIDO_AVAILABLE = True
|
||||||
|
except ImportError:
|
||||||
|
logger.debug("kaleido not installed - chart export will be unavailable")
|
||||||
|
|
||||||
|
|
||||||
# Default color palette based on Mantine theme
|
# Default color palette based on Mantine theme
|
||||||
DEFAULT_COLORS = [
|
DEFAULT_COLORS = [
|
||||||
@@ -395,3 +405,129 @@ class ChartTools:
|
|||||||
"figure": figure,
|
"figure": figure,
|
||||||
"interactions_added": []
|
"interactions_added": []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async def chart_export(
|
||||||
|
self,
|
||||||
|
figure: Dict[str, Any],
|
||||||
|
format: str = "png",
|
||||||
|
width: Optional[int] = None,
|
||||||
|
height: Optional[int] = None,
|
||||||
|
scale: float = 2.0,
|
||||||
|
output_path: Optional[str] = None
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Export a Plotly chart to a static image format.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
figure: Plotly figure JSON to export
|
||||||
|
format: Output format - png, svg, or pdf
|
||||||
|
width: Image width in pixels (default: from figure or 1200)
|
||||||
|
height: Image height in pixels (default: from figure or 800)
|
||||||
|
scale: Resolution scale factor (default: 2 for retina)
|
||||||
|
output_path: Optional file path to save the image
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict with:
|
||||||
|
- image_data: Base64-encoded image (if no output_path)
|
||||||
|
- file_path: Path to saved file (if output_path provided)
|
||||||
|
- format: Export format used
|
||||||
|
- dimensions: {width, height, scale}
|
||||||
|
- error: Error message if export failed
|
||||||
|
"""
|
||||||
|
# Validate format
|
||||||
|
valid_formats = ['png', 'svg', 'pdf']
|
||||||
|
format = format.lower()
|
||||||
|
if format not in valid_formats:
|
||||||
|
return {
|
||||||
|
"error": f"Invalid format '{format}'. Must be one of: {valid_formats}",
|
||||||
|
"format": format,
|
||||||
|
"image_data": None
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check kaleido availability
|
||||||
|
if not KALEIDO_AVAILABLE:
|
||||||
|
return {
|
||||||
|
"error": "kaleido package not installed. Install with: pip install kaleido",
|
||||||
|
"format": format,
|
||||||
|
"image_data": None,
|
||||||
|
"install_hint": "pip install kaleido"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Validate figure
|
||||||
|
if not figure or 'data' not in figure:
|
||||||
|
return {
|
||||||
|
"error": "Invalid figure: must contain 'data' key",
|
||||||
|
"format": format,
|
||||||
|
"image_data": None
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
import plotly.graph_objects as go
|
||||||
|
import plotly.io as pio
|
||||||
|
|
||||||
|
# Create Plotly figure object
|
||||||
|
fig = go.Figure(figure)
|
||||||
|
|
||||||
|
# Determine dimensions
|
||||||
|
layout = figure.get('layout', {})
|
||||||
|
export_width = width or layout.get('width') or 1200
|
||||||
|
export_height = height or layout.get('height') or 800
|
||||||
|
|
||||||
|
# Export to bytes
|
||||||
|
image_bytes = pio.to_image(
|
||||||
|
fig,
|
||||||
|
format=format,
|
||||||
|
width=export_width,
|
||||||
|
height=export_height,
|
||||||
|
scale=scale
|
||||||
|
)
|
||||||
|
|
||||||
|
result = {
|
||||||
|
"format": format,
|
||||||
|
"dimensions": {
|
||||||
|
"width": export_width,
|
||||||
|
"height": export_height,
|
||||||
|
"scale": scale,
|
||||||
|
"effective_width": int(export_width * scale),
|
||||||
|
"effective_height": int(export_height * scale)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Save to file or return base64
|
||||||
|
if output_path:
|
||||||
|
# Ensure directory exists
|
||||||
|
output_dir = os.path.dirname(output_path)
|
||||||
|
if output_dir and not os.path.exists(output_dir):
|
||||||
|
os.makedirs(output_dir, exist_ok=True)
|
||||||
|
|
||||||
|
# Add extension if missing
|
||||||
|
if not output_path.endswith(f'.{format}'):
|
||||||
|
output_path = f"{output_path}.{format}"
|
||||||
|
|
||||||
|
with open(output_path, 'wb') as f:
|
||||||
|
f.write(image_bytes)
|
||||||
|
|
||||||
|
result["file_path"] = output_path
|
||||||
|
result["file_size_bytes"] = len(image_bytes)
|
||||||
|
else:
|
||||||
|
# Return as base64
|
||||||
|
result["image_data"] = base64.b64encode(image_bytes).decode('utf-8')
|
||||||
|
result["data_uri"] = f"data:image/{format};base64,{result['image_data']}"
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
except ImportError as e:
|
||||||
|
logger.error(f"Chart export failed - missing dependency: {e}")
|
||||||
|
return {
|
||||||
|
"error": f"Missing dependency for export: {e}",
|
||||||
|
"format": format,
|
||||||
|
"image_data": None,
|
||||||
|
"install_hint": "pip install plotly kaleido"
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Chart export failed: {e}")
|
||||||
|
return {
|
||||||
|
"error": str(e),
|
||||||
|
"format": format,
|
||||||
|
"image_data": None
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,6 +10,46 @@ from uuid import uuid4
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
# Standard responsive breakpoints (Mantine/Bootstrap-aligned)
|
||||||
|
DEFAULT_BREAKPOINTS = {
|
||||||
|
"xs": {
|
||||||
|
"min_width": "0px",
|
||||||
|
"max_width": "575px",
|
||||||
|
"cols": 1,
|
||||||
|
"spacing": "xs",
|
||||||
|
"description": "Extra small devices (phones, portrait)"
|
||||||
|
},
|
||||||
|
"sm": {
|
||||||
|
"min_width": "576px",
|
||||||
|
"max_width": "767px",
|
||||||
|
"cols": 2,
|
||||||
|
"spacing": "sm",
|
||||||
|
"description": "Small devices (phones, landscape)"
|
||||||
|
},
|
||||||
|
"md": {
|
||||||
|
"min_width": "768px",
|
||||||
|
"max_width": "991px",
|
||||||
|
"cols": 6,
|
||||||
|
"spacing": "md",
|
||||||
|
"description": "Medium devices (tablets)"
|
||||||
|
},
|
||||||
|
"lg": {
|
||||||
|
"min_width": "992px",
|
||||||
|
"max_width": "1199px",
|
||||||
|
"cols": 12,
|
||||||
|
"spacing": "md",
|
||||||
|
"description": "Large devices (desktops)"
|
||||||
|
},
|
||||||
|
"xl": {
|
||||||
|
"min_width": "1200px",
|
||||||
|
"max_width": None,
|
||||||
|
"cols": 12,
|
||||||
|
"spacing": "lg",
|
||||||
|
"description": "Extra large devices (large desktops)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
# Layout templates
|
# Layout templates
|
||||||
TEMPLATES = {
|
TEMPLATES = {
|
||||||
"dashboard": {
|
"dashboard": {
|
||||||
@@ -365,3 +405,149 @@ class LayoutTools:
|
|||||||
}
|
}
|
||||||
for name, config in FILTER_TYPES.items()
|
for name, config in FILTER_TYPES.items()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async def layout_set_breakpoints(
|
||||||
|
self,
|
||||||
|
layout_ref: str,
|
||||||
|
breakpoints: Dict[str, Dict[str, Any]],
|
||||||
|
mobile_first: bool = True
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Configure responsive breakpoints for a layout.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
layout_ref: Layout name to configure
|
||||||
|
breakpoints: Breakpoint configuration dict:
|
||||||
|
{
|
||||||
|
"xs": {"cols": 1, "spacing": "xs"},
|
||||||
|
"sm": {"cols": 2, "spacing": "sm"},
|
||||||
|
"md": {"cols": 6, "spacing": "md"},
|
||||||
|
"lg": {"cols": 12, "spacing": "md"},
|
||||||
|
"xl": {"cols": 12, "spacing": "lg"}
|
||||||
|
}
|
||||||
|
mobile_first: If True, use min-width media queries (default)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict with:
|
||||||
|
- breakpoints: Complete breakpoint configuration
|
||||||
|
- css_media_queries: Generated CSS media queries
|
||||||
|
- mobile_first: Whether mobile-first approach is used
|
||||||
|
"""
|
||||||
|
# Validate layout exists
|
||||||
|
if layout_ref not in self._layouts:
|
||||||
|
return {
|
||||||
|
"error": f"Layout '{layout_ref}' not found. Create it first with layout_create.",
|
||||||
|
"breakpoints": None
|
||||||
|
}
|
||||||
|
|
||||||
|
layout = self._layouts[layout_ref]
|
||||||
|
|
||||||
|
# Validate breakpoint names
|
||||||
|
valid_breakpoints = ["xs", "sm", "md", "lg", "xl"]
|
||||||
|
for bp_name in breakpoints.keys():
|
||||||
|
if bp_name not in valid_breakpoints:
|
||||||
|
return {
|
||||||
|
"error": f"Invalid breakpoint '{bp_name}'. Must be one of: {valid_breakpoints}",
|
||||||
|
"breakpoints": layout.get("breakpoints")
|
||||||
|
}
|
||||||
|
|
||||||
|
# Merge with defaults
|
||||||
|
merged_breakpoints = {}
|
||||||
|
for bp_name in valid_breakpoints:
|
||||||
|
default = DEFAULT_BREAKPOINTS[bp_name].copy()
|
||||||
|
if bp_name in breakpoints:
|
||||||
|
default.update(breakpoints[bp_name])
|
||||||
|
merged_breakpoints[bp_name] = default
|
||||||
|
|
||||||
|
# Validate spacing values
|
||||||
|
valid_spacing = ["xs", "sm", "md", "lg", "xl"]
|
||||||
|
for bp_name, bp_config in merged_breakpoints.items():
|
||||||
|
if "spacing" in bp_config and bp_config["spacing"] not in valid_spacing:
|
||||||
|
return {
|
||||||
|
"error": f"Invalid spacing '{bp_config['spacing']}' for breakpoint '{bp_name}'. Must be one of: {valid_spacing}",
|
||||||
|
"breakpoints": layout.get("breakpoints")
|
||||||
|
}
|
||||||
|
|
||||||
|
# Validate column counts
|
||||||
|
for bp_name, bp_config in merged_breakpoints.items():
|
||||||
|
if "cols" in bp_config:
|
||||||
|
cols = bp_config["cols"]
|
||||||
|
if not isinstance(cols, int) or cols < 1 or cols > 24:
|
||||||
|
return {
|
||||||
|
"error": f"Invalid cols '{cols}' for breakpoint '{bp_name}'. Must be integer between 1 and 24.",
|
||||||
|
"breakpoints": layout.get("breakpoints")
|
||||||
|
}
|
||||||
|
|
||||||
|
# Generate CSS media queries
|
||||||
|
css_queries = self._generate_media_queries(merged_breakpoints, mobile_first)
|
||||||
|
|
||||||
|
# Store in layout
|
||||||
|
layout["breakpoints"] = merged_breakpoints
|
||||||
|
layout["mobile_first"] = mobile_first
|
||||||
|
layout["responsive_css"] = css_queries
|
||||||
|
|
||||||
|
return {
|
||||||
|
"layout_ref": layout_ref,
|
||||||
|
"breakpoints": merged_breakpoints,
|
||||||
|
"mobile_first": mobile_first,
|
||||||
|
"css_media_queries": css_queries
|
||||||
|
}
|
||||||
|
|
||||||
|
def _generate_media_queries(
|
||||||
|
self,
|
||||||
|
breakpoints: Dict[str, Dict[str, Any]],
|
||||||
|
mobile_first: bool
|
||||||
|
) -> List[str]:
|
||||||
|
"""Generate CSS media queries for breakpoints."""
|
||||||
|
queries = []
|
||||||
|
bp_order = ["xs", "sm", "md", "lg", "xl"]
|
||||||
|
|
||||||
|
if mobile_first:
|
||||||
|
# Use min-width queries (mobile-first)
|
||||||
|
for bp_name in bp_order[1:]: # Skip xs (base styles)
|
||||||
|
bp = breakpoints[bp_name]
|
||||||
|
min_width = bp.get("min_width", DEFAULT_BREAKPOINTS[bp_name]["min_width"])
|
||||||
|
if min_width and min_width != "0px":
|
||||||
|
queries.append(f"@media (min-width: {min_width}) {{ /* {bp_name} styles */ }}")
|
||||||
|
else:
|
||||||
|
# Use max-width queries (desktop-first)
|
||||||
|
for bp_name in reversed(bp_order[:-1]): # Skip xl (base styles)
|
||||||
|
bp = breakpoints[bp_name]
|
||||||
|
max_width = bp.get("max_width", DEFAULT_BREAKPOINTS[bp_name]["max_width"])
|
||||||
|
if max_width:
|
||||||
|
queries.append(f"@media (max-width: {max_width}) {{ /* {bp_name} styles */ }}")
|
||||||
|
|
||||||
|
return queries
|
||||||
|
|
||||||
|
async def layout_get_breakpoints(self, layout_ref: str) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Get the breakpoint configuration for a layout.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
layout_ref: Layout name
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict with breakpoint configuration
|
||||||
|
"""
|
||||||
|
if layout_ref not in self._layouts:
|
||||||
|
return {
|
||||||
|
"error": f"Layout '{layout_ref}' not found.",
|
||||||
|
"breakpoints": None
|
||||||
|
}
|
||||||
|
|
||||||
|
layout = self._layouts[layout_ref]
|
||||||
|
|
||||||
|
return {
|
||||||
|
"layout_ref": layout_ref,
|
||||||
|
"breakpoints": layout.get("breakpoints", DEFAULT_BREAKPOINTS.copy()),
|
||||||
|
"mobile_first": layout.get("mobile_first", True),
|
||||||
|
"css_media_queries": layout.get("responsive_css", [])
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_default_breakpoints(self) -> Dict[str, Any]:
|
||||||
|
"""Get the default breakpoint configuration."""
|
||||||
|
return {
|
||||||
|
"breakpoints": DEFAULT_BREAKPOINTS.copy(),
|
||||||
|
"description": "Standard responsive breakpoints aligned with Mantine/Bootstrap",
|
||||||
|
"mobile_first": True
|
||||||
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ from .chart_tools import ChartTools
|
|||||||
from .layout_tools import LayoutTools
|
from .layout_tools import LayoutTools
|
||||||
from .theme_tools import ThemeTools
|
from .theme_tools import ThemeTools
|
||||||
from .page_tools import PageTools
|
from .page_tools import PageTools
|
||||||
|
from .accessibility_tools import AccessibilityTools
|
||||||
|
|
||||||
# Suppress noisy MCP validation warnings on stderr
|
# Suppress noisy MCP validation warnings on stderr
|
||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
@@ -36,6 +37,7 @@ class VizPlatformMCPServer:
|
|||||||
self.layout_tools = LayoutTools()
|
self.layout_tools = LayoutTools()
|
||||||
self.theme_tools = ThemeTools()
|
self.theme_tools = ThemeTools()
|
||||||
self.page_tools = PageTools()
|
self.page_tools = PageTools()
|
||||||
|
self.accessibility_tools = AccessibilityTools(theme_store=self.theme_tools.store)
|
||||||
|
|
||||||
async def initialize(self):
|
async def initialize(self):
|
||||||
"""Initialize server and load configuration."""
|
"""Initialize server and load configuration."""
|
||||||
@@ -198,6 +200,46 @@ class VizPlatformMCPServer:
|
|||||||
}
|
}
|
||||||
))
|
))
|
||||||
|
|
||||||
|
# Chart export tool (Issue #247)
|
||||||
|
tools.append(Tool(
|
||||||
|
name="chart_export",
|
||||||
|
description=(
|
||||||
|
"Export a Plotly chart to static image format (PNG, SVG, PDF). "
|
||||||
|
"Requires kaleido package. Returns base64 image data or saves to file."
|
||||||
|
),
|
||||||
|
inputSchema={
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"figure": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Plotly figure JSON to export"
|
||||||
|
},
|
||||||
|
"format": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["png", "svg", "pdf"],
|
||||||
|
"description": "Output format (default: png)"
|
||||||
|
},
|
||||||
|
"width": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Image width in pixels (default: 1200)"
|
||||||
|
},
|
||||||
|
"height": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Image height in pixels (default: 800)"
|
||||||
|
},
|
||||||
|
"scale": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Resolution scale factor (default: 2 for retina)"
|
||||||
|
},
|
||||||
|
"output_path": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Optional file path to save image"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["figure"]
|
||||||
|
}
|
||||||
|
))
|
||||||
|
|
||||||
# Layout tools (Issue #174)
|
# Layout tools (Issue #174)
|
||||||
tools.append(Tool(
|
tools.append(Tool(
|
||||||
name="layout_create",
|
name="layout_create",
|
||||||
@@ -280,6 +322,36 @@ class VizPlatformMCPServer:
|
|||||||
}
|
}
|
||||||
))
|
))
|
||||||
|
|
||||||
|
# Responsive breakpoints tool (Issue #249)
|
||||||
|
tools.append(Tool(
|
||||||
|
name="layout_set_breakpoints",
|
||||||
|
description=(
|
||||||
|
"Configure responsive breakpoints for a layout. "
|
||||||
|
"Supports xs, sm, md, lg, xl breakpoints with mobile-first approach. "
|
||||||
|
"Each breakpoint can define cols, spacing, and other grid properties."
|
||||||
|
),
|
||||||
|
inputSchema={
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"layout_ref": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Layout name to configure"
|
||||||
|
},
|
||||||
|
"breakpoints": {
|
||||||
|
"type": "object",
|
||||||
|
"description": (
|
||||||
|
"Breakpoint config: {xs: {cols, spacing}, sm: {...}, md: {...}, lg: {...}, xl: {...}}"
|
||||||
|
)
|
||||||
|
},
|
||||||
|
"mobile_first": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Use mobile-first (min-width) media queries (default: true)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["layout_ref", "breakpoints"]
|
||||||
|
}
|
||||||
|
))
|
||||||
|
|
||||||
# Theme tools (Issue #175)
|
# Theme tools (Issue #175)
|
||||||
tools.append(Tool(
|
tools.append(Tool(
|
||||||
name="theme_create",
|
name="theme_create",
|
||||||
@@ -451,6 +523,77 @@ class VizPlatformMCPServer:
|
|||||||
}
|
}
|
||||||
))
|
))
|
||||||
|
|
||||||
|
# Accessibility tools (Issue #248)
|
||||||
|
tools.append(Tool(
|
||||||
|
name="accessibility_validate_colors",
|
||||||
|
description=(
|
||||||
|
"Validate colors for color blind accessibility. "
|
||||||
|
"Checks contrast ratios for deuteranopia, protanopia, tritanopia. "
|
||||||
|
"Returns issues, simulations, and accessible palette suggestions."
|
||||||
|
),
|
||||||
|
inputSchema={
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"colors": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {"type": "string"},
|
||||||
|
"description": "List of hex colors to validate (e.g., ['#228be6', '#40c057'])"
|
||||||
|
},
|
||||||
|
"check_types": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {"type": "string"},
|
||||||
|
"description": "Color blindness types to check: deuteranopia, protanopia, tritanopia (default: all)"
|
||||||
|
},
|
||||||
|
"min_contrast_ratio": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Minimum WCAG contrast ratio (default: 4.5 for AA)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["colors"]
|
||||||
|
}
|
||||||
|
))
|
||||||
|
|
||||||
|
tools.append(Tool(
|
||||||
|
name="accessibility_validate_theme",
|
||||||
|
description=(
|
||||||
|
"Validate a theme's colors for accessibility. "
|
||||||
|
"Extracts all colors from theme tokens and checks for color blind safety."
|
||||||
|
),
|
||||||
|
inputSchema={
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"theme_name": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Theme name to validate"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["theme_name"]
|
||||||
|
}
|
||||||
|
))
|
||||||
|
|
||||||
|
tools.append(Tool(
|
||||||
|
name="accessibility_suggest_alternative",
|
||||||
|
description=(
|
||||||
|
"Suggest accessible alternative colors for a given color. "
|
||||||
|
"Provides alternatives optimized for specific color blindness types."
|
||||||
|
),
|
||||||
|
inputSchema={
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"color": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Hex color to find alternatives for"
|
||||||
|
},
|
||||||
|
"deficiency_type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["deuteranopia", "protanopia", "tritanopia"],
|
||||||
|
"description": "Color blindness type to optimize for"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["color", "deficiency_type"]
|
||||||
|
}
|
||||||
|
))
|
||||||
|
|
||||||
return tools
|
return tools
|
||||||
|
|
||||||
@self.server.call_tool()
|
@self.server.call_tool()
|
||||||
@@ -524,6 +667,26 @@ class VizPlatformMCPServer:
|
|||||||
text=json.dumps(result, indent=2)
|
text=json.dumps(result, indent=2)
|
||||||
)]
|
)]
|
||||||
|
|
||||||
|
elif name == "chart_export":
|
||||||
|
figure = arguments.get('figure')
|
||||||
|
if not figure:
|
||||||
|
return [TextContent(
|
||||||
|
type="text",
|
||||||
|
text=json.dumps({"error": "figure is required"}, indent=2)
|
||||||
|
)]
|
||||||
|
result = await self.chart_tools.chart_export(
|
||||||
|
figure=figure,
|
||||||
|
format=arguments.get('format', 'png'),
|
||||||
|
width=arguments.get('width'),
|
||||||
|
height=arguments.get('height'),
|
||||||
|
scale=arguments.get('scale', 2.0),
|
||||||
|
output_path=arguments.get('output_path')
|
||||||
|
)
|
||||||
|
return [TextContent(
|
||||||
|
type="text",
|
||||||
|
text=json.dumps(result, indent=2)
|
||||||
|
)]
|
||||||
|
|
||||||
# Layout tools
|
# Layout tools
|
||||||
elif name == "layout_create":
|
elif name == "layout_create":
|
||||||
layout_name = arguments.get('name')
|
layout_name = arguments.get('name')
|
||||||
@@ -568,6 +731,23 @@ class VizPlatformMCPServer:
|
|||||||
text=json.dumps(result, indent=2)
|
text=json.dumps(result, indent=2)
|
||||||
)]
|
)]
|
||||||
|
|
||||||
|
elif name == "layout_set_breakpoints":
|
||||||
|
layout_ref = arguments.get('layout_ref')
|
||||||
|
breakpoints = arguments.get('breakpoints', {})
|
||||||
|
mobile_first = arguments.get('mobile_first', True)
|
||||||
|
if not layout_ref:
|
||||||
|
return [TextContent(
|
||||||
|
type="text",
|
||||||
|
text=json.dumps({"error": "layout_ref is required"}, indent=2)
|
||||||
|
)]
|
||||||
|
result = await self.layout_tools.layout_set_breakpoints(
|
||||||
|
layout_ref, breakpoints, mobile_first
|
||||||
|
)
|
||||||
|
return [TextContent(
|
||||||
|
type="text",
|
||||||
|
text=json.dumps(result, indent=2)
|
||||||
|
)]
|
||||||
|
|
||||||
# Theme tools
|
# Theme tools
|
||||||
elif name == "theme_create":
|
elif name == "theme_create":
|
||||||
theme_name = arguments.get('name')
|
theme_name = arguments.get('name')
|
||||||
@@ -669,6 +849,53 @@ class VizPlatformMCPServer:
|
|||||||
text=json.dumps(result, indent=2)
|
text=json.dumps(result, indent=2)
|
||||||
)]
|
)]
|
||||||
|
|
||||||
|
# Accessibility tools
|
||||||
|
elif name == "accessibility_validate_colors":
|
||||||
|
colors = arguments.get('colors')
|
||||||
|
if not colors:
|
||||||
|
return [TextContent(
|
||||||
|
type="text",
|
||||||
|
text=json.dumps({"error": "colors list is required"}, indent=2)
|
||||||
|
)]
|
||||||
|
result = await self.accessibility_tools.accessibility_validate_colors(
|
||||||
|
colors=colors,
|
||||||
|
check_types=arguments.get('check_types'),
|
||||||
|
min_contrast_ratio=arguments.get('min_contrast_ratio', 4.5)
|
||||||
|
)
|
||||||
|
return [TextContent(
|
||||||
|
type="text",
|
||||||
|
text=json.dumps(result, indent=2)
|
||||||
|
)]
|
||||||
|
|
||||||
|
elif name == "accessibility_validate_theme":
|
||||||
|
theme_name = arguments.get('theme_name')
|
||||||
|
if not theme_name:
|
||||||
|
return [TextContent(
|
||||||
|
type="text",
|
||||||
|
text=json.dumps({"error": "theme_name is required"}, indent=2)
|
||||||
|
)]
|
||||||
|
result = await self.accessibility_tools.accessibility_validate_theme(theme_name)
|
||||||
|
return [TextContent(
|
||||||
|
type="text",
|
||||||
|
text=json.dumps(result, indent=2)
|
||||||
|
)]
|
||||||
|
|
||||||
|
elif name == "accessibility_suggest_alternative":
|
||||||
|
color = arguments.get('color')
|
||||||
|
deficiency_type = arguments.get('deficiency_type')
|
||||||
|
if not color or not deficiency_type:
|
||||||
|
return [TextContent(
|
||||||
|
type="text",
|
||||||
|
text=json.dumps({"error": "color and deficiency_type are required"}, indent=2)
|
||||||
|
)]
|
||||||
|
result = await self.accessibility_tools.accessibility_suggest_alternative(
|
||||||
|
color, deficiency_type
|
||||||
|
)
|
||||||
|
return [TextContent(
|
||||||
|
type="text",
|
||||||
|
text=json.dumps(result, indent=2)
|
||||||
|
)]
|
||||||
|
|
||||||
raise ValueError(f"Unknown tool: {name}")
|
raise ValueError(f"Unknown tool: {name}")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ mcp>=0.9.0
|
|||||||
plotly>=5.18.0
|
plotly>=5.18.0
|
||||||
dash>=2.14.0
|
dash>=2.14.0
|
||||||
dash-mantine-components>=2.0.0
|
dash-mantine-components>=2.0.0
|
||||||
|
kaleido>=0.2.1 # For chart export (PNG, SVG, PDF)
|
||||||
|
|
||||||
# Utilities
|
# Utilities
|
||||||
python-dotenv>=1.0.0
|
python-dotenv>=1.0.0
|
||||||
|
|||||||
21
mcp-servers/viz-platform/run.sh
Executable file
21
mcp-servers/viz-platform/run.sh
Executable file
@@ -0,0 +1,21 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Capture original working directory before any cd operations
|
||||||
|
# This should be the user's project directory when launched by Claude Code
|
||||||
|
export CLAUDE_PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$PWD}"
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
CACHE_VENV="$HOME/.cache/claude-mcp-venvs/leo-claude-mktplace/viz-platform/.venv"
|
||||||
|
LOCAL_VENV="$SCRIPT_DIR/.venv"
|
||||||
|
|
||||||
|
if [[ -f "$CACHE_VENV/bin/python" ]]; then
|
||||||
|
PYTHON="$CACHE_VENV/bin/python"
|
||||||
|
elif [[ -f "$LOCAL_VENV/bin/python" ]]; then
|
||||||
|
PYTHON="$LOCAL_VENV/bin/python"
|
||||||
|
else
|
||||||
|
echo "ERROR: No venv found. Run: ./scripts/setup-venvs.sh" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd "$SCRIPT_DIR"
|
||||||
|
export PYTHONPATH="$SCRIPT_DIR"
|
||||||
|
exec "$PYTHON" -m mcp_server.server "$@"
|
||||||
195
mcp-servers/viz-platform/tests/test_accessibility_tools.py
Normal file
195
mcp-servers/viz-platform/tests/test_accessibility_tools.py
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
"""
|
||||||
|
Tests for accessibility validation tools.
|
||||||
|
"""
|
||||||
|
import pytest
|
||||||
|
from mcp_server.accessibility_tools import AccessibilityTools
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def tools():
|
||||||
|
"""Create AccessibilityTools instance."""
|
||||||
|
return AccessibilityTools()
|
||||||
|
|
||||||
|
|
||||||
|
class TestHexToRgb:
|
||||||
|
"""Tests for _hex_to_rgb method."""
|
||||||
|
|
||||||
|
def test_hex_to_rgb_6_digit(self, tools):
|
||||||
|
"""Test 6-digit hex conversion."""
|
||||||
|
assert tools._hex_to_rgb("#FF0000") == (255, 0, 0)
|
||||||
|
assert tools._hex_to_rgb("#00FF00") == (0, 255, 0)
|
||||||
|
assert tools._hex_to_rgb("#0000FF") == (0, 0, 255)
|
||||||
|
|
||||||
|
def test_hex_to_rgb_3_digit(self, tools):
|
||||||
|
"""Test 3-digit hex conversion."""
|
||||||
|
assert tools._hex_to_rgb("#F00") == (255, 0, 0)
|
||||||
|
assert tools._hex_to_rgb("#0F0") == (0, 255, 0)
|
||||||
|
assert tools._hex_to_rgb("#00F") == (0, 0, 255)
|
||||||
|
|
||||||
|
def test_hex_to_rgb_lowercase(self, tools):
|
||||||
|
"""Test lowercase hex conversion."""
|
||||||
|
assert tools._hex_to_rgb("#ff0000") == (255, 0, 0)
|
||||||
|
|
||||||
|
|
||||||
|
class TestContrastRatio:
|
||||||
|
"""Tests for _get_contrast_ratio method."""
|
||||||
|
|
||||||
|
def test_black_white_contrast(self, tools):
|
||||||
|
"""Test black on white has maximum contrast."""
|
||||||
|
ratio = tools._get_contrast_ratio("#000000", "#FFFFFF")
|
||||||
|
assert ratio == pytest.approx(21.0, rel=0.01)
|
||||||
|
|
||||||
|
def test_same_color_contrast(self, tools):
|
||||||
|
"""Test same color has minimum contrast."""
|
||||||
|
ratio = tools._get_contrast_ratio("#FF0000", "#FF0000")
|
||||||
|
assert ratio == pytest.approx(1.0, rel=0.01)
|
||||||
|
|
||||||
|
def test_symmetric_contrast(self, tools):
|
||||||
|
"""Test contrast ratio is symmetric."""
|
||||||
|
ratio1 = tools._get_contrast_ratio("#228be6", "#FFFFFF")
|
||||||
|
ratio2 = tools._get_contrast_ratio("#FFFFFF", "#228be6")
|
||||||
|
assert ratio1 == pytest.approx(ratio2, rel=0.01)
|
||||||
|
|
||||||
|
|
||||||
|
class TestColorBlindnessSimulation:
|
||||||
|
"""Tests for _simulate_color_blindness method."""
|
||||||
|
|
||||||
|
def test_deuteranopia_simulation(self, tools):
|
||||||
|
"""Test deuteranopia (green-blind) simulation."""
|
||||||
|
# Red and green should appear more similar
|
||||||
|
original_red = "#FF0000"
|
||||||
|
original_green = "#00FF00"
|
||||||
|
|
||||||
|
simulated_red = tools._simulate_color_blindness(original_red, "deuteranopia")
|
||||||
|
simulated_green = tools._simulate_color_blindness(original_green, "deuteranopia")
|
||||||
|
|
||||||
|
# They should be different from originals
|
||||||
|
assert simulated_red != original_red or simulated_green != original_green
|
||||||
|
|
||||||
|
def test_protanopia_simulation(self, tools):
|
||||||
|
"""Test protanopia (red-blind) simulation."""
|
||||||
|
simulated = tools._simulate_color_blindness("#FF0000", "protanopia")
|
||||||
|
# Should return a modified color
|
||||||
|
assert simulated.startswith("#")
|
||||||
|
assert len(simulated) == 7
|
||||||
|
|
||||||
|
def test_tritanopia_simulation(self, tools):
|
||||||
|
"""Test tritanopia (blue-blind) simulation."""
|
||||||
|
simulated = tools._simulate_color_blindness("#0000FF", "tritanopia")
|
||||||
|
# Should return a modified color
|
||||||
|
assert simulated.startswith("#")
|
||||||
|
assert len(simulated) == 7
|
||||||
|
|
||||||
|
def test_unknown_deficiency_returns_original(self, tools):
|
||||||
|
"""Test unknown deficiency type returns original color."""
|
||||||
|
color = "#FF0000"
|
||||||
|
simulated = tools._simulate_color_blindness(color, "unknown")
|
||||||
|
assert simulated == color
|
||||||
|
|
||||||
|
|
||||||
|
class TestAccessibilityValidateColors:
|
||||||
|
"""Tests for accessibility_validate_colors method."""
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_validate_single_color(self, tools):
|
||||||
|
"""Test validating a single color."""
|
||||||
|
result = await tools.accessibility_validate_colors(["#228be6"])
|
||||||
|
assert "colors_checked" in result
|
||||||
|
assert "overall_score" in result
|
||||||
|
assert "issues" in result
|
||||||
|
assert "safe_palettes" in result
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_validate_problematic_colors(self, tools):
|
||||||
|
"""Test similar colors trigger warnings."""
|
||||||
|
# Use colors that are very close in hue, which should be harder to distinguish
|
||||||
|
result = await tools.accessibility_validate_colors(["#FF5555", "#FF6666"])
|
||||||
|
# Similar colors should trigger distinguishability warnings
|
||||||
|
assert "issues" in result
|
||||||
|
# The validation should at least run without errors
|
||||||
|
assert "colors_checked" in result
|
||||||
|
assert len(result["colors_checked"]) == 2
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_validate_contrast_issue(self, tools):
|
||||||
|
"""Test low contrast colors trigger contrast warnings."""
|
||||||
|
# Yellow on white has poor contrast
|
||||||
|
result = await tools.accessibility_validate_colors(["#FFFF00"])
|
||||||
|
# Check for contrast issues (yellow may have issues with both black and white)
|
||||||
|
assert "issues" in result
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_validate_with_specific_types(self, tools):
|
||||||
|
"""Test validating for specific color blindness types."""
|
||||||
|
result = await tools.accessibility_validate_colors(
|
||||||
|
["#FF0000", "#00FF00"],
|
||||||
|
check_types=["deuteranopia"]
|
||||||
|
)
|
||||||
|
assert "simulations" in result
|
||||||
|
assert "deuteranopia" in result["simulations"]
|
||||||
|
assert "protanopia" not in result["simulations"]
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_overall_score(self, tools):
|
||||||
|
"""Test overall score is calculated."""
|
||||||
|
result = await tools.accessibility_validate_colors(["#228be6", "#ffffff"])
|
||||||
|
assert result["overall_score"] in ["A", "B", "C", "D"]
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_recommendations_generated(self, tools):
|
||||||
|
"""Test recommendations are generated for issues."""
|
||||||
|
result = await tools.accessibility_validate_colors(["#FF0000", "#00FF00"])
|
||||||
|
assert "recommendations" in result
|
||||||
|
assert len(result["recommendations"]) > 0
|
||||||
|
|
||||||
|
|
||||||
|
class TestAccessibilitySuggestAlternative:
|
||||||
|
"""Tests for accessibility_suggest_alternative method."""
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_suggest_alternative_deuteranopia(self, tools):
|
||||||
|
"""Test suggesting alternatives for deuteranopia."""
|
||||||
|
result = await tools.accessibility_suggest_alternative("#FF0000", "deuteranopia")
|
||||||
|
assert "original_color" in result
|
||||||
|
assert result["deficiency_type"] == "deuteranopia"
|
||||||
|
assert "suggestions" in result
|
||||||
|
assert len(result["suggestions"]) > 0
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_suggest_alternative_tritanopia(self, tools):
|
||||||
|
"""Test suggesting alternatives for tritanopia."""
|
||||||
|
result = await tools.accessibility_suggest_alternative("#0000FF", "tritanopia")
|
||||||
|
assert "suggestions" in result
|
||||||
|
assert len(result["suggestions"]) > 0
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_suggestions_include_safe_palettes(self, tools):
|
||||||
|
"""Test suggestions include colors from safe palettes."""
|
||||||
|
result = await tools.accessibility_suggest_alternative("#FF0000", "deuteranopia")
|
||||||
|
palette_suggestions = [
|
||||||
|
s for s in result["suggestions"]
|
||||||
|
if "palette" in s
|
||||||
|
]
|
||||||
|
assert len(palette_suggestions) > 0
|
||||||
|
|
||||||
|
|
||||||
|
class TestSafePalettes:
|
||||||
|
"""Tests for safe palette constants."""
|
||||||
|
|
||||||
|
def test_safe_palettes_exist(self, tools):
|
||||||
|
"""Test that safe palettes are defined."""
|
||||||
|
from mcp_server.accessibility_tools import SAFE_PALETTES
|
||||||
|
assert "categorical" in SAFE_PALETTES
|
||||||
|
assert "ibm" in SAFE_PALETTES
|
||||||
|
assert "okabe_ito" in SAFE_PALETTES
|
||||||
|
assert "tableau_colorblind" in SAFE_PALETTES
|
||||||
|
|
||||||
|
def test_safe_palettes_have_colors(self, tools):
|
||||||
|
"""Test that safe palettes have color lists."""
|
||||||
|
from mcp_server.accessibility_tools import SAFE_PALETTES
|
||||||
|
for palette_name, palette in SAFE_PALETTES.items():
|
||||||
|
assert "colors" in palette
|
||||||
|
assert len(palette["colors"]) > 0
|
||||||
|
# All colors should be valid hex
|
||||||
|
for color in palette["colors"]:
|
||||||
|
assert color.startswith("#")
|
||||||
@@ -90,6 +90,10 @@ After clarification, you receive a structured specification:
|
|||||||
[List of assumptions]
|
[List of assumptions]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
- [Neurodivergent Support Guide](docs/ND-SUPPORT.md) - How clarity-assist supports ND users with executive function challenges and cognitive differences
|
||||||
|
|
||||||
## Integration
|
## Integration
|
||||||
|
|
||||||
For CLAUDE.md integration instructions, see `claude-md-integration.md`.
|
For CLAUDE.md integration instructions, see `claude-md-integration.md`.
|
||||||
|
|||||||
328
plugins/clarity-assist/docs/ND-SUPPORT.md
Normal file
328
plugins/clarity-assist/docs/ND-SUPPORT.md
Normal file
@@ -0,0 +1,328 @@
|
|||||||
|
# Neurodivergent Support in clarity-assist
|
||||||
|
|
||||||
|
This document describes how clarity-assist is designed to support users with neurodivergent traits, including ADHD, autism, anxiety, and other conditions that affect executive function, sensory processing, or cognitive style.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
### Purpose
|
||||||
|
|
||||||
|
clarity-assist exists to help all users transform vague or incomplete requests into clear, actionable specifications. For neurodivergent users specifically, it addresses common challenges:
|
||||||
|
|
||||||
|
- **Executive function difficulties** - Breaking down complex tasks, getting started, managing scope
|
||||||
|
- **Working memory limitations** - Keeping track of context across long conversations
|
||||||
|
- **Decision fatigue** - Facing too many open-ended choices
|
||||||
|
- **Processing style differences** - Preferring structured, predictable interactions
|
||||||
|
- **Anxiety around uncertainty** - Needing clear expectations and explicit confirmation
|
||||||
|
|
||||||
|
### Philosophy
|
||||||
|
|
||||||
|
Our design philosophy centers on three principles:
|
||||||
|
|
||||||
|
1. **Reduce cognitive load** - Never force the user to hold too much in their head at once
|
||||||
|
2. **Provide structure** - Use consistent, predictable patterns for all interactions
|
||||||
|
3. **Respect different communication styles** - Accommodate rather than assume one "right" way to think
|
||||||
|
|
||||||
|
## Features for ND Users
|
||||||
|
|
||||||
|
### 1. Reduced Cognitive Load
|
||||||
|
|
||||||
|
**Prompt Simplification**
|
||||||
|
- The 4-D methodology (Deconstruct, Diagnose, Develop, Deliver) breaks down complex requests into manageable phases
|
||||||
|
- Users never need to specify everything upfront - clarification happens incrementally
|
||||||
|
|
||||||
|
**Task Breakdown**
|
||||||
|
- Large requests are decomposed into explicit components
|
||||||
|
- Dependencies and relationships are surfaced rather than left implicit
|
||||||
|
- Scope boundaries are clearly defined (in-scope vs. out-of-scope)
|
||||||
|
|
||||||
|
### 2. Structured Output
|
||||||
|
|
||||||
|
**Consistent Formatting**
|
||||||
|
- Every clarification session produces the same structured specification:
|
||||||
|
- Summary (1-2 sentences)
|
||||||
|
- Scope (In/Out)
|
||||||
|
- Requirements table (numbered, prioritized)
|
||||||
|
- Assumptions list
|
||||||
|
- This predictability reduces the mental effort of parsing responses
|
||||||
|
|
||||||
|
**Predictable Patterns**
|
||||||
|
- Questions always follow the same format
|
||||||
|
- Progress summaries appear at regular intervals
|
||||||
|
- Escalation (simple to complex) is always offered, never forced
|
||||||
|
|
||||||
|
**Bulleted Lists Over Prose**
|
||||||
|
- Requirements are presented as scannable lists, not paragraphs
|
||||||
|
- Options are numbered for easy reference
|
||||||
|
- Key information is highlighted with bold labels
|
||||||
|
|
||||||
|
### 3. Customizable Verbosity
|
||||||
|
|
||||||
|
**Detail Levels**
|
||||||
|
- `/clarify` - Full methodology for complex requests (more thorough, more questions)
|
||||||
|
- `/quick-clarify` - Rapid mode for simple disambiguation (fewer questions, faster)
|
||||||
|
|
||||||
|
**User Control**
|
||||||
|
- Users can always say "that's enough detail" to end questioning early
|
||||||
|
- The plugin offers to break sessions into smaller parts
|
||||||
|
- "Good enough for now" is explicitly validated as an acceptable outcome
|
||||||
|
|
||||||
|
### 4. Vagueness Detection
|
||||||
|
|
||||||
|
The `UserPromptSubmit` hook automatically detects prompts that might benefit from clarification and gently suggests using `/clarify`.
|
||||||
|
|
||||||
|
**Detection Signals**
|
||||||
|
- Short prompts (< 10 words) without specific technical terms
|
||||||
|
- Vague action phrases: "help me", "fix this", "make it better"
|
||||||
|
- Ambiguous scope words: "somehow", "something", "stuff", "etc."
|
||||||
|
- Open questions without context
|
||||||
|
|
||||||
|
**Non-Blocking Approach**
|
||||||
|
- The hook never prevents you from proceeding
|
||||||
|
- It provides a suggestion with a vagueness score (percentage)
|
||||||
|
- You can disable auto-suggestions entirely via environment variable
|
||||||
|
|
||||||
|
### 5. Focus Aids
|
||||||
|
|
||||||
|
**Task Prioritization**
|
||||||
|
- Requirements are tagged as Must/Should/Could/Won't (MoSCoW)
|
||||||
|
- Critical items are separated from nice-to-haves
|
||||||
|
- Scope creep is explicitly called out and deferred
|
||||||
|
|
||||||
|
**Context Switching Warnings**
|
||||||
|
- When questions touch multiple areas, the plugin acknowledges the complexity
|
||||||
|
- Offers to focus on one aspect at a time
|
||||||
|
- Summarizes frequently to rebuild context after interruptions
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
### The UserPromptSubmit Hook
|
||||||
|
|
||||||
|
When you submit a prompt, the vagueness detection hook (`hooks/vagueness-check.sh`) runs automatically:
|
||||||
|
|
||||||
|
```
|
||||||
|
User submits prompt
|
||||||
|
|
|
||||||
|
v
|
||||||
|
Hook reads prompt from stdin
|
||||||
|
|
|
||||||
|
v
|
||||||
|
Skip if: empty, starts with /, or contains file paths
|
||||||
|
|
|
||||||
|
v
|
||||||
|
Calculate vagueness score (0.0 - 1.0)
|
||||||
|
- Short prompts: +0.3
|
||||||
|
- Vague action phrases: +0.2
|
||||||
|
- Ambiguous scope words: +0.15
|
||||||
|
- Missing technical specifics: +0.2
|
||||||
|
- Short questions without context: +0.15
|
||||||
|
|
|
||||||
|
v
|
||||||
|
If score >= threshold (default 0.6):
|
||||||
|
- Output gentle suggestion with [clarity-assist] prefix
|
||||||
|
- Show vagueness percentage
|
||||||
|
|
|
||||||
|
v
|
||||||
|
Exit 0 (always non-blocking)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example Hook Output
|
||||||
|
|
||||||
|
```
|
||||||
|
[clarity-assist] Your prompt could benefit from more clarity.
|
||||||
|
[clarity-assist] Consider running /clarity-assist to refine your request.
|
||||||
|
[clarity-assist] (Vagueness score: 65% - this is a suggestion, not a block)
|
||||||
|
```
|
||||||
|
|
||||||
|
### The 4-D Methodology
|
||||||
|
|
||||||
|
| Phase | What Happens | ND Benefit |
|
||||||
|
|-------|--------------|------------|
|
||||||
|
| **Deconstruct** | Break request into components | Shows the full picture without overwhelming |
|
||||||
|
| **Diagnose** | Identify gaps and conflicts | Surfaces hidden assumptions |
|
||||||
|
| **Develop** | Ask structured questions | Option-based, chunked, contextual |
|
||||||
|
| **Deliver** | Produce specification | Consistent, scannable format |
|
||||||
|
|
||||||
|
## Usage Examples
|
||||||
|
|
||||||
|
### Before: Vague Request
|
||||||
|
|
||||||
|
```
|
||||||
|
User: Make the app faster
|
||||||
|
```
|
||||||
|
|
||||||
|
This triggers vagueness detection because:
|
||||||
|
- Short (4 words)
|
||||||
|
- Vague action ("make")
|
||||||
|
- No specific technical context
|
||||||
|
- No measurable outcome
|
||||||
|
|
||||||
|
### After: Clarified with /clarify
|
||||||
|
|
||||||
|
```
|
||||||
|
User: /clarify Make the app faster
|
||||||
|
|
||||||
|
Claude: Let me help clarify what kind of performance improvements you're looking for.
|
||||||
|
|
||||||
|
**What type of slowness are you experiencing?**
|
||||||
|
1. Initial page load takes too long
|
||||||
|
2. Interactions feel sluggish (clicking, typing)
|
||||||
|
3. API calls are slow
|
||||||
|
4. All of the above
|
||||||
|
5. Other
|
||||||
|
|
||||||
|
[I'm asking because each has very different solutions]
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
User: 1
|
||||||
|
|
||||||
|
Claude: Initial page load - got it.
|
||||||
|
|
||||||
|
**What's an acceptable load time for you?**
|
||||||
|
1. Under 1 second (aggressive)
|
||||||
|
2. Under 2 seconds (standard)
|
||||||
|
3. Under 3 seconds (relaxed)
|
||||||
|
4. I don't have a specific target
|
||||||
|
|
||||||
|
[This helps me know whether to suggest quick wins or a major optimization effort]
|
||||||
|
```
|
||||||
|
|
||||||
|
After 2-3 more questions, the user receives:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## Clarified Request
|
||||||
|
|
||||||
|
### Summary
|
||||||
|
Optimize initial page load time to under 2 seconds by addressing bundle size and initial render blocking resources.
|
||||||
|
|
||||||
|
### Scope
|
||||||
|
**In Scope:**
|
||||||
|
- Bundle analysis and splitting
|
||||||
|
- Lazy loading non-critical components
|
||||||
|
- Image optimization
|
||||||
|
- CSS/JS minification check
|
||||||
|
|
||||||
|
**Out of Scope:**
|
||||||
|
- Backend API optimization
|
||||||
|
- Database query performance
|
||||||
|
- CDN setup
|
||||||
|
|
||||||
|
### Requirements
|
||||||
|
| # | Requirement | Priority | Notes |
|
||||||
|
|---|-------------|----------|-------|
|
||||||
|
| 1 | Page loads in < 2s on 4G | Must | Measured via Lighthouse |
|
||||||
|
| 2 | Largest contentful paint < 1.5s | Should | Core Web Vital |
|
||||||
|
| 3 | No layout shift after load | Should | CLS < 0.1 |
|
||||||
|
|
||||||
|
### Assumptions
|
||||||
|
- Current load time is > 3 seconds (based on "too long" description)
|
||||||
|
- Target browsers: modern evergreen (Chrome, Firefox, Safari)
|
||||||
|
- Build tooling already configured (webpack/vite)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Common Patterns Detected
|
||||||
|
|
||||||
|
| Pattern | Example | What It Triggers |
|
||||||
|
|---------|---------|------------------|
|
||||||
|
| Missing context | "Fix the bug" | "Which bug? In which file or feature?" |
|
||||||
|
| Ambiguous scope | "Update the user stuff" | "What specifically needs updating?" |
|
||||||
|
| Unclear goals | "Make it better" | "Better in what way? Faster? Cleaner? More features?" |
|
||||||
|
| Implicit knowledge | "Do it like last time" | "I don't have context from previous sessions" |
|
||||||
|
| Conflicting requests | "Keep it simple but add everything" | "Which matters more: simplicity or completeness?" |
|
||||||
|
|
||||||
|
### Quick-Clarify Example
|
||||||
|
|
||||||
|
For simpler requests that just need minor disambiguation:
|
||||||
|
|
||||||
|
```
|
||||||
|
User: /quick-clarify Add a delete button to the user list
|
||||||
|
|
||||||
|
Claude: I'll add a delete button to each row in the user list.
|
||||||
|
|
||||||
|
**Should deletion require confirmation?**
|
||||||
|
1. Yes - show confirmation dialog (Recommended)
|
||||||
|
2. No - delete immediately
|
||||||
|
3. Soft delete - mark as deleted but keep data
|
||||||
|
|
||||||
|
[Asking because accidental deletions can be problematic]
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
User: 1
|
||||||
|
|
||||||
|
Claude: Got it. I'll implement a delete button with confirmation dialog. Starting now.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration Options
|
||||||
|
|
||||||
|
### Environment Variables
|
||||||
|
|
||||||
|
| Variable | Default | Description |
|
||||||
|
|----------|---------|-------------|
|
||||||
|
| `CLARITY_ASSIST_AUTO_SUGGEST` | `true` | Enable/disable automatic vagueness detection |
|
||||||
|
| `CLARITY_ASSIST_VAGUENESS_THRESHOLD` | `0.6` | Score threshold to trigger suggestion (0.0-1.0) |
|
||||||
|
|
||||||
|
### Disabling Auto-Suggestions
|
||||||
|
|
||||||
|
If you find the vagueness detection unhelpful, disable it in your shell profile or `.env`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export CLARITY_ASSIST_AUTO_SUGGEST=false
|
||||||
|
```
|
||||||
|
|
||||||
|
### Adjusting Sensitivity
|
||||||
|
|
||||||
|
To make detection more or less sensitive:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# More sensitive (suggests more often)
|
||||||
|
export CLARITY_ASSIST_VAGUENESS_THRESHOLD=0.4
|
||||||
|
|
||||||
|
# Less sensitive (only very vague prompts)
|
||||||
|
export CLARITY_ASSIST_VAGUENESS_THRESHOLD=0.8
|
||||||
|
```
|
||||||
|
|
||||||
|
## Tips for ND Users
|
||||||
|
|
||||||
|
### If You're Feeling Overwhelmed
|
||||||
|
|
||||||
|
- Use `/quick-clarify` instead of `/clarify` for faster interactions
|
||||||
|
- Say "let's focus on just one thing" to narrow scope
|
||||||
|
- Ask to "pause and summarize" at any point
|
||||||
|
- It's OK to say "I don't know" - the plugin will offer concrete alternatives
|
||||||
|
|
||||||
|
### If You Have Executive Function Challenges
|
||||||
|
|
||||||
|
- Start with `/clarify` even for tasks you think are simple - it helps with planning
|
||||||
|
- The structured specification can serve as a checklist
|
||||||
|
- Use the scope boundaries to prevent scope creep
|
||||||
|
|
||||||
|
### If You Prefer Detailed Structure
|
||||||
|
|
||||||
|
- The 4-D methodology provides a predictable framework
|
||||||
|
- All output follows consistent formatting
|
||||||
|
- Questions always offer numbered options
|
||||||
|
|
||||||
|
### If You Have Anxiety About Getting It Right
|
||||||
|
|
||||||
|
- The plugin validates "good enough for now" as acceptable
|
||||||
|
- You can always revisit and change earlier answers
|
||||||
|
- Assumptions are explicitly listed - nothing is hidden
|
||||||
|
|
||||||
|
## Accessibility Notes
|
||||||
|
|
||||||
|
- All output uses standard markdown that works with screen readers
|
||||||
|
- No time pressure - take as long as you need between responses
|
||||||
|
- Questions are designed to be answerable without deep context retrieval
|
||||||
|
- Visual patterns (bold, bullets, tables) create scannable structure
|
||||||
|
|
||||||
|
## Feedback
|
||||||
|
|
||||||
|
If you have suggestions for improving neurodivergent support in clarity-assist, please open an issue at:
|
||||||
|
|
||||||
|
https://gitea.hotserv.cloud/personal-projects/leo-claude-mktplace/issues
|
||||||
|
|
||||||
|
Include the label `clarity-assist` and describe:
|
||||||
|
- What challenge you faced
|
||||||
|
- What would have helped
|
||||||
|
- Any specific accommodations you'd like to see
|
||||||
10
plugins/clarity-assist/hooks/hooks.json
Normal file
10
plugins/clarity-assist/hooks/hooks.json
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"hooks": {
|
||||||
|
"UserPromptSubmit": [
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/vagueness-check.sh"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
216
plugins/clarity-assist/hooks/vagueness-check.sh
Executable file
216
plugins/clarity-assist/hooks/vagueness-check.sh
Executable file
@@ -0,0 +1,216 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# clarity-assist vagueness detection hook
|
||||||
|
# Analyzes user prompts for vagueness and suggests /clarity-assist when beneficial
|
||||||
|
# All output MUST have [clarity-assist] prefix
|
||||||
|
# This is a NON-BLOCKING hook - always exits 0
|
||||||
|
|
||||||
|
PREFIX="[clarity-assist]"
|
||||||
|
|
||||||
|
# Check if auto-suggest is enabled (default: true)
|
||||||
|
AUTO_SUGGEST="${CLARITY_ASSIST_AUTO_SUGGEST:-true}"
|
||||||
|
if [[ "$AUTO_SUGGEST" != "true" ]]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Threshold for vagueness score (default: 0.6)
|
||||||
|
THRESHOLD="${CLARITY_ASSIST_VAGUENESS_THRESHOLD:-0.6}"
|
||||||
|
|
||||||
|
# Read user prompt from stdin
|
||||||
|
PROMPT=""
|
||||||
|
if [[ -t 0 ]]; then
|
||||||
|
# No stdin available
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
PROMPT=$(cat)
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Skip empty prompts
|
||||||
|
if [[ -z "$PROMPT" ]]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Skip if prompt is a command (starts with /)
|
||||||
|
if [[ "$PROMPT" =~ ^[[:space:]]*/[a-zA-Z] ]]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Skip if prompt mentions specific files or paths
|
||||||
|
if [[ "$PROMPT" =~ \.(py|js|ts|sh|md|json|yaml|yml|txt|css|html|go|rs|java|c|cpp|h)([[:space:]]|$|[^a-zA-Z]) ]] || \
|
||||||
|
[[ "$PROMPT" =~ [/\\][a-zA-Z0-9_-]+[/\\] ]] || \
|
||||||
|
[[ "$PROMPT" =~ (src|lib|test|docs|plugins|hooks|commands)/ ]]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Initialize vagueness score
|
||||||
|
SCORE=0
|
||||||
|
|
||||||
|
# Count words in the prompt
|
||||||
|
WORD_COUNT=$(echo "$PROMPT" | wc -w | tr -d ' ')
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Vagueness Signal Detection
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
# Signal 1: Very short prompts (< 10 words) are often vague
|
||||||
|
if [[ "$WORD_COUNT" -lt 10 ]]; then
|
||||||
|
# But very short specific commands are OK
|
||||||
|
if [[ "$WORD_COUNT" -lt 3 ]]; then
|
||||||
|
# Extremely short - probably intentional or a command
|
||||||
|
:
|
||||||
|
else
|
||||||
|
SCORE=$(echo "$SCORE + 0.3" | bc)
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Signal 2: Vague action phrases (no specific outcome)
|
||||||
|
VAGUE_ACTIONS=(
|
||||||
|
"help me"
|
||||||
|
"help with"
|
||||||
|
"do something"
|
||||||
|
"work on"
|
||||||
|
"look at"
|
||||||
|
"check this"
|
||||||
|
"fix it"
|
||||||
|
"fix this"
|
||||||
|
"make it better"
|
||||||
|
"make this better"
|
||||||
|
"improve it"
|
||||||
|
"improve this"
|
||||||
|
"update this"
|
||||||
|
"update it"
|
||||||
|
"change it"
|
||||||
|
"change this"
|
||||||
|
"can you"
|
||||||
|
"could you"
|
||||||
|
"would you"
|
||||||
|
"please help"
|
||||||
|
)
|
||||||
|
|
||||||
|
PROMPT_LOWER=$(echo "$PROMPT" | tr '[:upper:]' '[:lower:]')
|
||||||
|
|
||||||
|
for phrase in "${VAGUE_ACTIONS[@]}"; do
|
||||||
|
if [[ "$PROMPT_LOWER" == *"$phrase"* ]]; then
|
||||||
|
SCORE=$(echo "$SCORE + 0.2" | bc)
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Signal 3: Ambiguous scope indicators
|
||||||
|
AMBIGUOUS_SCOPE=(
|
||||||
|
"somehow"
|
||||||
|
"something"
|
||||||
|
"somewhere"
|
||||||
|
"anything"
|
||||||
|
"whatever"
|
||||||
|
"stuff"
|
||||||
|
"things"
|
||||||
|
"etc"
|
||||||
|
"and so on"
|
||||||
|
)
|
||||||
|
|
||||||
|
for word in "${AMBIGUOUS_SCOPE[@]}"; do
|
||||||
|
if [[ "$PROMPT_LOWER" == *"$word"* ]]; then
|
||||||
|
SCORE=$(echo "$SCORE + 0.15" | bc)
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Signal 4: Missing context indicators (no reference to what/where)
|
||||||
|
# Check if prompt lacks specificity markers
|
||||||
|
HAS_SPECIFICS=false
|
||||||
|
|
||||||
|
# Specific technical terms suggest clarity
|
||||||
|
SPECIFIC_MARKERS=(
|
||||||
|
"function"
|
||||||
|
"class"
|
||||||
|
"method"
|
||||||
|
"variable"
|
||||||
|
"error"
|
||||||
|
"bug"
|
||||||
|
"test"
|
||||||
|
"api"
|
||||||
|
"endpoint"
|
||||||
|
"database"
|
||||||
|
"query"
|
||||||
|
"component"
|
||||||
|
"module"
|
||||||
|
"service"
|
||||||
|
"config"
|
||||||
|
"install"
|
||||||
|
"deploy"
|
||||||
|
"build"
|
||||||
|
"run"
|
||||||
|
"execute"
|
||||||
|
"create"
|
||||||
|
"delete"
|
||||||
|
"add"
|
||||||
|
"remove"
|
||||||
|
"implement"
|
||||||
|
"refactor"
|
||||||
|
"migrate"
|
||||||
|
"upgrade"
|
||||||
|
"debug"
|
||||||
|
"log"
|
||||||
|
"exception"
|
||||||
|
"stack"
|
||||||
|
"memory"
|
||||||
|
"performance"
|
||||||
|
"security"
|
||||||
|
"auth"
|
||||||
|
"token"
|
||||||
|
"session"
|
||||||
|
"route"
|
||||||
|
"controller"
|
||||||
|
"model"
|
||||||
|
"view"
|
||||||
|
"template"
|
||||||
|
"schema"
|
||||||
|
"migration"
|
||||||
|
"commit"
|
||||||
|
"branch"
|
||||||
|
"merge"
|
||||||
|
"pull"
|
||||||
|
"push"
|
||||||
|
)
|
||||||
|
|
||||||
|
for marker in "${SPECIFIC_MARKERS[@]}"; do
|
||||||
|
if [[ "$PROMPT_LOWER" == *"$marker"* ]]; then
|
||||||
|
HAS_SPECIFICS=true
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ "$HAS_SPECIFICS" == false ]] && [[ "$WORD_COUNT" -gt 3 ]]; then
|
||||||
|
SCORE=$(echo "$SCORE + 0.2" | bc)
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Signal 5: Question without context
|
||||||
|
if [[ "$PROMPT" =~ \?$ ]] && [[ "$WORD_COUNT" -lt 8 ]]; then
|
||||||
|
# Short questions without specifics are often vague
|
||||||
|
if [[ "$HAS_SPECIFICS" == false ]]; then
|
||||||
|
SCORE=$(echo "$SCORE + 0.15" | bc)
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Cap score at 1.0
|
||||||
|
if (( $(echo "$SCORE > 1.0" | bc -l) )); then
|
||||||
|
SCORE="1.0"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Output suggestion if score exceeds threshold
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
# Compare score to threshold using bc
|
||||||
|
if (( $(echo "$SCORE >= $THRESHOLD" | bc -l) )); then
|
||||||
|
# Format score as percentage for display
|
||||||
|
SCORE_PCT=$(echo "$SCORE * 100" | bc | cut -d'.' -f1)
|
||||||
|
|
||||||
|
# Gentle, non-blocking suggestion
|
||||||
|
echo "$PREFIX Your prompt could benefit from more clarity."
|
||||||
|
echo "$PREFIX Consider running /clarity-assist to refine your request."
|
||||||
|
echo "$PREFIX (Vagueness score: ${SCORE_PCT}% - this is a suggestion, not a block)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Always exit 0 - this hook is non-blocking
|
||||||
|
exit 0
|
||||||
@@ -37,6 +37,33 @@ Create a new CLAUDE.md tailored to your project.
|
|||||||
/config-init
|
/config-init
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### `/config-diff`
|
||||||
|
Show differences between current CLAUDE.md and previous versions.
|
||||||
|
|
||||||
|
```
|
||||||
|
/config-diff # Compare working copy vs last commit
|
||||||
|
/config-diff --commit=abc1234 # Compare against specific commit
|
||||||
|
/config-diff --from=v1.0 --to=v2.0 # Compare two commits
|
||||||
|
/config-diff --section="Critical Rules" # Focus on specific section
|
||||||
|
```
|
||||||
|
|
||||||
|
### `/config-lint`
|
||||||
|
Lint CLAUDE.md for common anti-patterns and best practices.
|
||||||
|
|
||||||
|
```
|
||||||
|
/config-lint # Run all lint checks
|
||||||
|
/config-lint --fix # Auto-fix fixable issues
|
||||||
|
/config-lint --rules=security # Check only security rules
|
||||||
|
/config-lint --severity=error # Show only errors
|
||||||
|
```
|
||||||
|
|
||||||
|
**Lint Rule Categories:**
|
||||||
|
- **Security (SEC)** - Hardcoded secrets, paths, credentials
|
||||||
|
- **Structure (STR)** - Header hierarchy, required sections
|
||||||
|
- **Content (CNT)** - Contradictions, duplicates, vague rules
|
||||||
|
- **Format (FMT)** - Consistency, code blocks, whitespace
|
||||||
|
- **Best Practice (BPR)** - Missing Quick Start, Critical Rules sections
|
||||||
|
|
||||||
## Best Practices
|
## Best Practices
|
||||||
|
|
||||||
A good CLAUDE.md should be:
|
A good CLAUDE.md should be:
|
||||||
|
|||||||
239
plugins/claude-config-maintainer/commands/config-diff.md
Normal file
239
plugins/claude-config-maintainer/commands/config-diff.md
Normal file
@@ -0,0 +1,239 @@
|
|||||||
|
---
|
||||||
|
description: Show diff between current CLAUDE.md and last commit
|
||||||
|
---
|
||||||
|
|
||||||
|
# Compare CLAUDE.md Changes
|
||||||
|
|
||||||
|
This command shows differences between your current CLAUDE.md file and previous versions, helping track configuration drift and review changes before committing.
|
||||||
|
|
||||||
|
## What This Command Does
|
||||||
|
|
||||||
|
1. **Detect CLAUDE.md Location** - Finds the project's CLAUDE.md file
|
||||||
|
2. **Compare Versions** - Shows diff against last commit or specified revision
|
||||||
|
3. **Highlight Sections** - Groups changes by affected sections
|
||||||
|
4. **Summarize Impact** - Explains what the changes mean for Claude's behavior
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
/config-diff
|
||||||
|
```
|
||||||
|
|
||||||
|
Compare against a specific commit:
|
||||||
|
|
||||||
|
```
|
||||||
|
/config-diff --commit=abc1234
|
||||||
|
/config-diff --commit=HEAD~3
|
||||||
|
```
|
||||||
|
|
||||||
|
Compare two specific commits:
|
||||||
|
|
||||||
|
```
|
||||||
|
/config-diff --from=abc1234 --to=def5678
|
||||||
|
```
|
||||||
|
|
||||||
|
Show only specific sections:
|
||||||
|
|
||||||
|
```
|
||||||
|
/config-diff --section="Critical Rules"
|
||||||
|
/config-diff --section="Quick Start"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Comparison Modes
|
||||||
|
|
||||||
|
### Default: Working vs Last Commit
|
||||||
|
Shows uncommitted changes to CLAUDE.md:
|
||||||
|
```
|
||||||
|
/config-diff
|
||||||
|
```
|
||||||
|
|
||||||
|
### Working vs Specific Commit
|
||||||
|
Shows changes since a specific point:
|
||||||
|
```
|
||||||
|
/config-diff --commit=v1.0.0
|
||||||
|
```
|
||||||
|
|
||||||
|
### Commit to Commit
|
||||||
|
Shows changes between two historical versions:
|
||||||
|
```
|
||||||
|
/config-diff --from=v1.0.0 --to=v2.0.0
|
||||||
|
```
|
||||||
|
|
||||||
|
### Branch Comparison
|
||||||
|
Shows CLAUDE.md differences between branches:
|
||||||
|
```
|
||||||
|
/config-diff --branch=main
|
||||||
|
/config-diff --from=feature-branch --to=main
|
||||||
|
```
|
||||||
|
|
||||||
|
## Expected Output
|
||||||
|
|
||||||
|
```
|
||||||
|
CLAUDE.md Diff Report
|
||||||
|
=====================
|
||||||
|
|
||||||
|
File: /path/to/project/CLAUDE.md
|
||||||
|
Comparing: Working copy vs HEAD (last commit)
|
||||||
|
Commit: abc1234 "Update build commands" (2 days ago)
|
||||||
|
|
||||||
|
Summary:
|
||||||
|
- Lines added: 12
|
||||||
|
- Lines removed: 5
|
||||||
|
- Net change: +7 lines
|
||||||
|
- Sections affected: 3
|
||||||
|
|
||||||
|
Section Changes:
|
||||||
|
----------------
|
||||||
|
|
||||||
|
## Quick Start [MODIFIED]
|
||||||
|
- Added new environment variable requirement
|
||||||
|
- Updated test command with coverage flag
|
||||||
|
|
||||||
|
## Critical Rules [ADDED CONTENT]
|
||||||
|
+ New rule: "Never modify database migrations directly"
|
||||||
|
|
||||||
|
## Architecture [UNCHANGED]
|
||||||
|
|
||||||
|
## Common Operations [MODIFIED]
|
||||||
|
- Removed deprecated deployment command
|
||||||
|
- Added new Docker workflow
|
||||||
|
|
||||||
|
Detailed Diff:
|
||||||
|
--------------
|
||||||
|
|
||||||
|
--- CLAUDE.md (HEAD)
|
||||||
|
+++ CLAUDE.md (working)
|
||||||
|
|
||||||
|
@@ -15,7 +15,10 @@
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
+export DATABASE_URL=postgres://... # Required
|
||||||
|
pip install -r requirements.txt
|
||||||
|
-pytest
|
||||||
|
+pytest --cov=src # Run with coverage
|
||||||
|
uvicorn main:app --reload
|
||||||
|
```
|
||||||
|
|
||||||
|
@@ -45,6 +48,7 @@
|
||||||
|
## Critical Rules
|
||||||
|
|
||||||
|
- Never modify `.env` files directly
|
||||||
|
+- Never modify database migrations directly
|
||||||
|
- Always run tests before committing
|
||||||
|
|
||||||
|
Behavioral Impact:
|
||||||
|
------------------
|
||||||
|
|
||||||
|
These changes will affect Claude's behavior:
|
||||||
|
|
||||||
|
1. [NEW REQUIREMENT] Claude will now export DATABASE_URL before running
|
||||||
|
2. [MODIFIED] Test command now includes coverage reporting
|
||||||
|
3. [NEW RULE] Claude will avoid direct migration modifications
|
||||||
|
|
||||||
|
Review: Do these changes reflect your intended configuration?
|
||||||
|
```
|
||||||
|
|
||||||
|
## Section-Focused View
|
||||||
|
|
||||||
|
When using `--section`, output focuses on specific areas:
|
||||||
|
|
||||||
|
```
|
||||||
|
/config-diff --section="Critical Rules"
|
||||||
|
|
||||||
|
CLAUDE.md Section Diff: Critical Rules
|
||||||
|
======================================
|
||||||
|
|
||||||
|
--- HEAD
|
||||||
|
+++ Working
|
||||||
|
|
||||||
|
## Critical Rules
|
||||||
|
|
||||||
|
- Never modify `.env` files directly
|
||||||
|
+- Never modify database migrations directly
|
||||||
|
+- Always use type hints in Python code
|
||||||
|
- Always run tests before committing
|
||||||
|
-- Keep functions under 50 lines
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
+ 2 rules added
|
||||||
|
- 1 rule removed
|
||||||
|
|
||||||
|
Impact: Claude will follow 2 new constraints and no longer enforce
|
||||||
|
the 50-line function limit.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Options
|
||||||
|
|
||||||
|
| Option | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| `--commit=REF` | Compare working copy against specific commit/tag |
|
||||||
|
| `--from=REF` | Starting point for comparison |
|
||||||
|
| `--to=REF` | Ending point for comparison (default: HEAD) |
|
||||||
|
| `--branch=NAME` | Compare against branch tip |
|
||||||
|
| `--section=NAME` | Show only changes to specific section |
|
||||||
|
| `--stat` | Show only statistics, no detailed diff |
|
||||||
|
| `--no-color` | Disable colored output |
|
||||||
|
| `--context=N` | Lines of context around changes (default: 3) |
|
||||||
|
|
||||||
|
## Understanding the Output
|
||||||
|
|
||||||
|
### Change Indicators
|
||||||
|
|
||||||
|
| Symbol | Meaning |
|
||||||
|
|--------|---------|
|
||||||
|
| `+` | Line added |
|
||||||
|
| `-` | Line removed |
|
||||||
|
| `@@` | Location marker showing line numbers |
|
||||||
|
| `[MODIFIED]` | Section has changes |
|
||||||
|
| `[ADDED]` | New section created |
|
||||||
|
| `[REMOVED]` | Section deleted |
|
||||||
|
| `[UNCHANGED]` | No changes to section |
|
||||||
|
|
||||||
|
### Impact Categories
|
||||||
|
|
||||||
|
- **NEW REQUIREMENT** - Claude will now need to do something new
|
||||||
|
- **REMOVED REQUIREMENT** - Claude no longer needs to do something
|
||||||
|
- **MODIFIED** - Existing behavior changed
|
||||||
|
- **NEW RULE** - New constraint added
|
||||||
|
- **RELAXED RULE** - Constraint removed or softened
|
||||||
|
|
||||||
|
## When to Use
|
||||||
|
|
||||||
|
Run `/config-diff` when:
|
||||||
|
- Before committing CLAUDE.md changes
|
||||||
|
- Reviewing what changed after pulling updates
|
||||||
|
- Debugging unexpected Claude behavior
|
||||||
|
- Auditing configuration changes over time
|
||||||
|
- Comparing configurations across branches
|
||||||
|
|
||||||
|
## Integration with Other Commands
|
||||||
|
|
||||||
|
| Workflow | Commands |
|
||||||
|
|----------|----------|
|
||||||
|
| Review before commit | `/config-diff` then `git commit` |
|
||||||
|
| After optimization | `/config-optimize` then `/config-diff` |
|
||||||
|
| Audit history | `/config-diff --from=v1.0.0 --to=HEAD` |
|
||||||
|
| Branch comparison | `/config-diff --branch=main` |
|
||||||
|
|
||||||
|
## Tips
|
||||||
|
|
||||||
|
1. **Review before committing** - Always check what changed
|
||||||
|
2. **Track behavioral changes** - Focus on rules and requirements sections
|
||||||
|
3. **Use section filtering** - Large files benefit from focused diffs
|
||||||
|
4. **Compare across releases** - Use tags to track major changes
|
||||||
|
5. **Check after merges** - Ensure CLAUDE.md didn't get conflict artifacts
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### "No changes detected"
|
||||||
|
- CLAUDE.md matches the comparison target
|
||||||
|
- Check if you're comparing the right commits
|
||||||
|
|
||||||
|
### "File not found in commit"
|
||||||
|
- CLAUDE.md didn't exist at that commit
|
||||||
|
- Use `git log -- CLAUDE.md` to find when it was created
|
||||||
|
|
||||||
|
### "Not a git repository"
|
||||||
|
- This command requires git history
|
||||||
|
- Initialize git or use file backup comparison instead
|
||||||
334
plugins/claude-config-maintainer/commands/config-lint.md
Normal file
334
plugins/claude-config-maintainer/commands/config-lint.md
Normal file
@@ -0,0 +1,334 @@
|
|||||||
|
---
|
||||||
|
description: Lint CLAUDE.md for common anti-patterns and best practices
|
||||||
|
---
|
||||||
|
|
||||||
|
# Lint CLAUDE.md
|
||||||
|
|
||||||
|
This command checks your CLAUDE.md file against best practices and detects common anti-patterns that can cause issues with Claude Code.
|
||||||
|
|
||||||
|
## What This Command Does
|
||||||
|
|
||||||
|
1. **Parse Structure** - Validates markdown structure and hierarchy
|
||||||
|
2. **Check Security** - Detects hardcoded paths, secrets, and sensitive data
|
||||||
|
3. **Validate Content** - Identifies anti-patterns and problematic instructions
|
||||||
|
4. **Verify Format** - Ensures consistent formatting and style
|
||||||
|
5. **Generate Report** - Provides actionable findings with fix suggestions
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
/config-lint
|
||||||
|
```
|
||||||
|
|
||||||
|
Lint with auto-fix:
|
||||||
|
|
||||||
|
```
|
||||||
|
/config-lint --fix
|
||||||
|
```
|
||||||
|
|
||||||
|
Check specific rules only:
|
||||||
|
|
||||||
|
```
|
||||||
|
/config-lint --rules=security,structure
|
||||||
|
```
|
||||||
|
|
||||||
|
## Linting Rules
|
||||||
|
|
||||||
|
### Security Rules (SEC)
|
||||||
|
|
||||||
|
| Rule | Description | Severity |
|
||||||
|
|------|-------------|----------|
|
||||||
|
| SEC001 | Hardcoded absolute paths | Warning |
|
||||||
|
| SEC002 | Potential secrets/API keys | Error |
|
||||||
|
| SEC003 | Hardcoded IP addresses | Warning |
|
||||||
|
| SEC004 | Exposed credentials patterns | Error |
|
||||||
|
| SEC005 | Hardcoded URLs with tokens | Error |
|
||||||
|
| SEC006 | Environment variable values (not names) | Warning |
|
||||||
|
|
||||||
|
### Structure Rules (STR)
|
||||||
|
|
||||||
|
| Rule | Description | Severity |
|
||||||
|
|------|-------------|----------|
|
||||||
|
| STR001 | Missing required sections | Error |
|
||||||
|
| STR002 | Invalid header hierarchy (h3 before h2) | Warning |
|
||||||
|
| STR003 | Orphaned content (text before first header) | Info |
|
||||||
|
| STR004 | Excessive nesting depth (>4 levels) | Warning |
|
||||||
|
| STR005 | Empty sections | Warning |
|
||||||
|
| STR006 | Missing section content | Warning |
|
||||||
|
|
||||||
|
### Content Rules (CNT)
|
||||||
|
|
||||||
|
| Rule | Description | Severity |
|
||||||
|
|------|-------------|----------|
|
||||||
|
| CNT001 | Contradictory instructions | Error |
|
||||||
|
| CNT002 | Vague or ambiguous rules | Warning |
|
||||||
|
| CNT003 | Overly long sections (>100 lines) | Info |
|
||||||
|
| CNT004 | Duplicate content | Warning |
|
||||||
|
| CNT005 | TODO/FIXME in production config | Warning |
|
||||||
|
| CNT006 | Outdated version references | Info |
|
||||||
|
| CNT007 | Broken internal links | Warning |
|
||||||
|
|
||||||
|
### Format Rules (FMT)
|
||||||
|
|
||||||
|
| Rule | Description | Severity |
|
||||||
|
|------|-------------|----------|
|
||||||
|
| FMT001 | Inconsistent header styles | Info |
|
||||||
|
| FMT002 | Inconsistent list markers | Info |
|
||||||
|
| FMT003 | Missing code block language | Info |
|
||||||
|
| FMT004 | Trailing whitespace | Info |
|
||||||
|
| FMT005 | Missing blank lines around headers | Info |
|
||||||
|
| FMT006 | Inconsistent indentation | Info |
|
||||||
|
|
||||||
|
### Best Practice Rules (BPR)
|
||||||
|
|
||||||
|
| Rule | Description | Severity |
|
||||||
|
|------|-------------|----------|
|
||||||
|
| BPR001 | No Quick Start section | Warning |
|
||||||
|
| BPR002 | No Critical Rules section | Warning |
|
||||||
|
| BPR003 | Instructions without examples | Info |
|
||||||
|
| BPR004 | Commands without explanation | Info |
|
||||||
|
| BPR005 | Rules without rationale | Info |
|
||||||
|
| BPR006 | Missing plugin integration docs | Info |
|
||||||
|
|
||||||
|
## Expected Output
|
||||||
|
|
||||||
|
```
|
||||||
|
CLAUDE.md Lint Report
|
||||||
|
=====================
|
||||||
|
|
||||||
|
File: /path/to/project/CLAUDE.md
|
||||||
|
Rules checked: 25
|
||||||
|
Time: 0.3s
|
||||||
|
|
||||||
|
Summary:
|
||||||
|
Errors: 2
|
||||||
|
Warnings: 5
|
||||||
|
Info: 3
|
||||||
|
|
||||||
|
Findings:
|
||||||
|
---------
|
||||||
|
|
||||||
|
[ERROR] SEC002: Potential secret detected (line 45)
|
||||||
|
│ api_key = "sk-1234567890abcdef"
|
||||||
|
│ ^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
└─ Hardcoded API key found. Use environment variable reference instead.
|
||||||
|
|
||||||
|
Suggested fix:
|
||||||
|
- api_key = "sk-1234567890abcdef"
|
||||||
|
+ api_key = $OPENAI_API_KEY # Set in environment
|
||||||
|
|
||||||
|
[ERROR] CNT001: Contradictory instructions (lines 23, 67)
|
||||||
|
│ Line 23: "Always run tests before committing"
|
||||||
|
│ Line 67: "Skip tests for documentation-only changes"
|
||||||
|
│
|
||||||
|
└─ These rules conflict. Clarify the exception explicitly.
|
||||||
|
|
||||||
|
Suggested fix:
|
||||||
|
+ "Always run tests before committing, except for documentation-only
|
||||||
|
+ changes (files in docs/ directory)"
|
||||||
|
|
||||||
|
[WARNING] SEC001: Hardcoded absolute path (line 12)
|
||||||
|
│ Database location: /home/user/data/myapp.db
|
||||||
|
│ ^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
└─ Absolute paths break portability. Use relative or variable.
|
||||||
|
|
||||||
|
Suggested fix:
|
||||||
|
- Database location: /home/user/data/myapp.db
|
||||||
|
+ Database location: ./data/myapp.db # Or $DATA_DIR/myapp.db
|
||||||
|
|
||||||
|
[WARNING] STR002: Invalid header hierarchy (line 34)
|
||||||
|
│ ### Subsection
|
||||||
|
│ (no preceding ## header)
|
||||||
|
│
|
||||||
|
└─ H3 header without parent H2. Add H2 or promote to H2.
|
||||||
|
|
||||||
|
[WARNING] CNT004: Duplicate content (lines 45-52, 89-96)
|
||||||
|
│ Same git workflow documented twice
|
||||||
|
│
|
||||||
|
└─ Remove duplicate or consolidate into single section.
|
||||||
|
|
||||||
|
[WARNING] STR005: Empty section (line 78)
|
||||||
|
│ ## Troubleshooting
|
||||||
|
│ (no content)
|
||||||
|
│
|
||||||
|
└─ Add content or remove empty section.
|
||||||
|
|
||||||
|
[WARNING] BPR002: No Critical Rules section
|
||||||
|
│ Missing "Critical Rules" or "Important Rules" section
|
||||||
|
│
|
||||||
|
└─ Add a section highlighting must-follow rules for Claude.
|
||||||
|
|
||||||
|
[INFO] FMT003: Missing code block language (line 56)
|
||||||
|
│ ```
|
||||||
|
│ npm install
|
||||||
|
│ ```
|
||||||
|
│
|
||||||
|
└─ Specify language for syntax highlighting: ```bash
|
||||||
|
|
||||||
|
[INFO] CNT003: Overly long section (lines 100-215)
|
||||||
|
│ "Architecture" section is 115 lines
|
||||||
|
│
|
||||||
|
└─ Consider breaking into subsections or condensing.
|
||||||
|
|
||||||
|
[INFO] FMT001: Inconsistent header styles
|
||||||
|
│ Line 10: "## Quick Start"
|
||||||
|
│ Line 25: "## Architecture:"
|
||||||
|
│ (colon suffix inconsistent)
|
||||||
|
│
|
||||||
|
└─ Standardize header format throughout document.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Auto-fixable: 4 issues (run with --fix)
|
||||||
|
Manual review required: 6 issues
|
||||||
|
|
||||||
|
Run `/config-lint --fix` to apply automatic fixes.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Options
|
||||||
|
|
||||||
|
| Option | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| `--fix` | Automatically fix auto-fixable issues |
|
||||||
|
| `--rules=LIST` | Check only specified rule categories |
|
||||||
|
| `--ignore=LIST` | Skip specified rules (e.g., `--ignore=FMT001,FMT002`) |
|
||||||
|
| `--severity=LEVEL` | Show only issues at or above level (error/warning/info) |
|
||||||
|
| `--format=FORMAT` | Output format: `text` (default), `json`, `sarif` |
|
||||||
|
| `--config=FILE` | Use custom lint configuration |
|
||||||
|
| `--strict` | Treat warnings as errors |
|
||||||
|
|
||||||
|
## Rule Categories
|
||||||
|
|
||||||
|
Use `--rules` to focus on specific areas:
|
||||||
|
|
||||||
|
```
|
||||||
|
/config-lint --rules=security # Only security checks
|
||||||
|
/config-lint --rules=structure # Only structure checks
|
||||||
|
/config-lint --rules=security,content # Multiple categories
|
||||||
|
```
|
||||||
|
|
||||||
|
Available categories:
|
||||||
|
- `security` - SEC rules
|
||||||
|
- `structure` - STR rules
|
||||||
|
- `content` - CNT rules
|
||||||
|
- `format` - FMT rules
|
||||||
|
- `bestpractice` - BPR rules
|
||||||
|
|
||||||
|
## Custom Configuration
|
||||||
|
|
||||||
|
Create `.claude-lint.json` in project root:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"rules": {
|
||||||
|
"SEC001": "warning",
|
||||||
|
"FMT001": "off",
|
||||||
|
"CNT003": {
|
||||||
|
"severity": "warning",
|
||||||
|
"maxLines": 150
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ignore": [
|
||||||
|
"FMT*"
|
||||||
|
],
|
||||||
|
"requiredSections": [
|
||||||
|
"Quick Start",
|
||||||
|
"Critical Rules",
|
||||||
|
"Project Overview"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Anti-Pattern Examples
|
||||||
|
|
||||||
|
### Hardcoded Secrets (SEC002)
|
||||||
|
```markdown
|
||||||
|
# BAD
|
||||||
|
API_KEY=sk-1234567890abcdef
|
||||||
|
|
||||||
|
# GOOD
|
||||||
|
API_KEY=$OPENAI_API_KEY # Set via environment
|
||||||
|
```
|
||||||
|
|
||||||
|
### Hardcoded Paths (SEC001)
|
||||||
|
```markdown
|
||||||
|
# BAD
|
||||||
|
Config file: /home/john/projects/myapp/config.yml
|
||||||
|
|
||||||
|
# GOOD
|
||||||
|
Config file: ./config.yml
|
||||||
|
Config file: $PROJECT_ROOT/config.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
### Contradictory Rules (CNT001)
|
||||||
|
```markdown
|
||||||
|
# BAD
|
||||||
|
- Always use TypeScript
|
||||||
|
- JavaScript files are acceptable for scripts
|
||||||
|
|
||||||
|
# GOOD
|
||||||
|
- Always use TypeScript for source code
|
||||||
|
- JavaScript (.js) is acceptable only for config files and scripts
|
||||||
|
```
|
||||||
|
|
||||||
|
### Vague Instructions (CNT002)
|
||||||
|
```markdown
|
||||||
|
# BAD
|
||||||
|
- Be careful with the database
|
||||||
|
|
||||||
|
# GOOD
|
||||||
|
- Never run DELETE without WHERE clause
|
||||||
|
- Always backup before migrations
|
||||||
|
```
|
||||||
|
|
||||||
|
### Invalid Hierarchy (STR002)
|
||||||
|
```markdown
|
||||||
|
# BAD
|
||||||
|
# Main Title
|
||||||
|
### Skipped Level
|
||||||
|
|
||||||
|
# GOOD
|
||||||
|
# Main Title
|
||||||
|
## Section
|
||||||
|
### Subsection
|
||||||
|
```
|
||||||
|
|
||||||
|
## When to Use
|
||||||
|
|
||||||
|
Run `/config-lint` when:
|
||||||
|
- Before committing CLAUDE.md changes
|
||||||
|
- During code review for CLAUDE.md modifications
|
||||||
|
- Setting up CI/CD checks for configuration files
|
||||||
|
- After major edits to catch introduced issues
|
||||||
|
- Periodically as maintenance check
|
||||||
|
|
||||||
|
## Integration with CI/CD
|
||||||
|
|
||||||
|
Add to your CI pipeline:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# GitHub Actions example
|
||||||
|
- name: Lint CLAUDE.md
|
||||||
|
run: claude /config-lint --strict --format=sarif > lint-results.sarif
|
||||||
|
|
||||||
|
- name: Upload SARIF
|
||||||
|
uses: github/codeql-action/upload-sarif@v2
|
||||||
|
with:
|
||||||
|
sarif_file: lint-results.sarif
|
||||||
|
```
|
||||||
|
|
||||||
|
## Tips
|
||||||
|
|
||||||
|
1. **Start with errors** - Fix errors before warnings
|
||||||
|
2. **Use --fix carefully** - Review auto-fixes before committing
|
||||||
|
3. **Configure per-project** - Different projects have different needs
|
||||||
|
4. **Integrate in CI** - Catch issues before they reach main
|
||||||
|
5. **Review periodically** - Run lint check monthly as maintenance
|
||||||
|
|
||||||
|
## Related Commands
|
||||||
|
|
||||||
|
| Command | Relationship |
|
||||||
|
|---------|--------------|
|
||||||
|
| `/config-analyze` | Deeper content analysis (complements lint) |
|
||||||
|
| `/config-optimize` | Applies fixes and improvements |
|
||||||
|
| `/config-diff` | Shows what changed (run lint before commit) |
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "cmdb-assistant",
|
"name": "cmdb-assistant",
|
||||||
"version": "1.0.0",
|
"version": "1.2.0",
|
||||||
"description": "NetBox CMDB integration for infrastructure management - query, create, update, and manage network devices, IP addresses, sites, and more",
|
"description": "NetBox CMDB integration with data quality validation - query, create, update, and manage network devices, IP addresses, sites, and more with best practices enforcement",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Leo Miranda",
|
"name": "Leo Miranda",
|
||||||
"email": "leobmiranda@gmail.com"
|
"email": "leobmiranda@gmail.com"
|
||||||
@@ -15,7 +15,9 @@
|
|||||||
"infrastructure",
|
"infrastructure",
|
||||||
"network",
|
"network",
|
||||||
"ipam",
|
"ipam",
|
||||||
"dcim"
|
"dcim",
|
||||||
|
"data-quality",
|
||||||
|
"validation"
|
||||||
],
|
],
|
||||||
"commands": ["./commands/"],
|
"commands": ["./commands/"],
|
||||||
"mcpServers": ["./.mcp.json"]
|
"mcpServers": ["./.mcp.json"]
|
||||||
|
|||||||
@@ -1,12 +1,8 @@
|
|||||||
{
|
{
|
||||||
"mcpServers": {
|
"mcpServers": {
|
||||||
"netbox": {
|
"netbox": {
|
||||||
"command": "${CLAUDE_PLUGIN_ROOT}/mcp-servers/netbox/.venv/bin/python",
|
"command": "${CLAUDE_PLUGIN_ROOT}/mcp-servers/netbox/run.sh",
|
||||||
"args": ["-m", "mcp_server.server"],
|
"args": []
|
||||||
"cwd": "${CLAUDE_PLUGIN_ROOT}/mcp-servers/netbox",
|
|
||||||
"env": {
|
|
||||||
"PYTHONPATH": "${CLAUDE_PLUGIN_ROOT}/mcp-servers/netbox"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,20 @@
|
|||||||
|
|
||||||
A Claude Code plugin for NetBox CMDB integration - query, create, update, and manage your network infrastructure directly from Claude Code.
|
A Claude Code plugin for NetBox CMDB integration - query, create, update, and manage your network infrastructure directly from Claude Code.
|
||||||
|
|
||||||
|
## What's New in v1.2.0
|
||||||
|
|
||||||
|
- **`/cmdb-topology`**: Generate Mermaid diagrams showing infrastructure topology (rack view, network view, site overview)
|
||||||
|
- **`/change-audit`**: Query and analyze NetBox audit log for change tracking and compliance
|
||||||
|
- **`/ip-conflicts`**: Detect IP address conflicts and overlapping prefixes
|
||||||
|
|
||||||
|
## What's New in v1.1.0
|
||||||
|
|
||||||
|
- **Data Quality Validation**: Hooks for SessionStart and PreToolUse that check data quality and warn about missing fields
|
||||||
|
- **Best Practices Skill**: Reference documentation for NetBox patterns (naming conventions, dependency order, role management)
|
||||||
|
- **`/cmdb-audit`**: Analyze data quality across VMs, devices, naming conventions, and roles
|
||||||
|
- **`/cmdb-register`**: Register the current machine into NetBox with all running applications (Docker containers, systemd services)
|
||||||
|
- **`/cmdb-sync`**: Synchronize existing machine state with NetBox (detect drift, update with confirmation)
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- **Full CRUD Operations**: Create, read, update, and delete across all NetBox modules
|
- **Full CRUD Operations**: Create, read, update, and delete across all NetBox modules
|
||||||
@@ -9,6 +23,9 @@ A Claude Code plugin for NetBox CMDB integration - query, create, update, and ma
|
|||||||
- **IP Management**: Allocate IPs, manage prefixes, track VLANs
|
- **IP Management**: Allocate IPs, manage prefixes, track VLANs
|
||||||
- **Infrastructure Documentation**: Document servers, network devices, and connections
|
- **Infrastructure Documentation**: Document servers, network devices, and connections
|
||||||
- **Audit Trail**: Review changes and maintain infrastructure history
|
- **Audit Trail**: Review changes and maintain infrastructure history
|
||||||
|
- **Data Quality Validation**: Proactive checks for missing site, tenant, platform assignments
|
||||||
|
- **Machine Registration**: Auto-discover and register servers with running applications
|
||||||
|
- **Drift Detection**: Sync machine state and detect changes over time
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
@@ -40,10 +57,17 @@ Add to your Claude Code plugins or marketplace configuration.
|
|||||||
|
|
||||||
| Command | Description |
|
| Command | Description |
|
||||||
|---------|-------------|
|
|---------|-------------|
|
||||||
|
| `/initial-setup` | Interactive setup wizard for NetBox MCP server |
|
||||||
| `/cmdb-search <query>` | Search for devices, IPs, sites, or any CMDB object |
|
| `/cmdb-search <query>` | Search for devices, IPs, sites, or any CMDB object |
|
||||||
| `/cmdb-device <action>` | Manage network devices (list, create, update, delete) |
|
| `/cmdb-device <action>` | Manage network devices (list, create, update, delete) |
|
||||||
| `/cmdb-ip <action>` | Manage IP addresses and prefixes |
|
| `/cmdb-ip <action>` | Manage IP addresses and prefixes |
|
||||||
| `/cmdb-site <action>` | Manage sites and locations |
|
| `/cmdb-site <action>` | Manage sites and locations |
|
||||||
|
| `/cmdb-audit [scope]` | Data quality analysis (all, vms, devices, naming, roles) |
|
||||||
|
| `/cmdb-register` | Register current machine into NetBox with running apps |
|
||||||
|
| `/cmdb-sync` | Sync machine state with NetBox (detect drift, update) |
|
||||||
|
| `/cmdb-topology <view>` | Generate Mermaid diagrams (rack, network, site, full) |
|
||||||
|
| `/change-audit [filters]` | Query NetBox audit log for change tracking |
|
||||||
|
| `/ip-conflicts [scope]` | Detect IP conflicts and overlapping prefixes |
|
||||||
|
|
||||||
## Agent
|
## Agent
|
||||||
|
|
||||||
@@ -103,6 +127,15 @@ This plugin provides access to the full NetBox API:
|
|||||||
- **Wireless**: WLANs, Wireless Links
|
- **Wireless**: WLANs, Wireless Links
|
||||||
- **Extras**: Tags, Custom Fields, Journal Entries, Audit Log
|
- **Extras**: Tags, Custom Fields, Journal Entries, Audit Log
|
||||||
|
|
||||||
|
## Hooks
|
||||||
|
|
||||||
|
| Event | Purpose |
|
||||||
|
|-------|---------|
|
||||||
|
| `SessionStart` | Test NetBox connectivity, report data quality issues |
|
||||||
|
| `PreToolUse` | Validate VM/device parameters before create/update |
|
||||||
|
|
||||||
|
Hooks are **non-blocking** - they emit warnings but never prevent operations.
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -115,13 +148,26 @@ cmdb-assistant/
|
|||||||
│ ├── cmdb-search.md # Search command
|
│ ├── cmdb-search.md # Search command
|
||||||
│ ├── cmdb-device.md # Device management
|
│ ├── cmdb-device.md # Device management
|
||||||
│ ├── cmdb-ip.md # IP management
|
│ ├── cmdb-ip.md # IP management
|
||||||
│ └── cmdb-site.md # Site management
|
│ ├── cmdb-site.md # Site management
|
||||||
|
│ ├── cmdb-audit.md # Data quality audit
|
||||||
|
│ ├── cmdb-register.md # Machine registration
|
||||||
|
│ ├── cmdb-sync.md # Machine sync
|
||||||
|
│ ├── cmdb-topology.md # Topology visualization (NEW)
|
||||||
|
│ ├── change-audit.md # Change audit log (NEW)
|
||||||
|
│ └── ip-conflicts.md # IP conflict detection (NEW)
|
||||||
|
├── hooks/
|
||||||
|
│ ├── hooks.json # Hook configuration
|
||||||
|
│ ├── startup-check.sh # SessionStart validation
|
||||||
|
│ └── validate-input.sh # PreToolUse validation
|
||||||
|
├── skills/
|
||||||
|
│ └── netbox-patterns/
|
||||||
|
│ └── SKILL.md # NetBox best practices reference
|
||||||
├── agents/
|
├── agents/
|
||||||
│ └── cmdb-assistant.md # Main assistant agent
|
│ └── cmdb-assistant.md # Main assistant agent
|
||||||
└── README.md
|
└── README.md
|
||||||
```
|
```
|
||||||
|
|
||||||
The plugin uses the shared NetBox MCP server at `../mcp-servers/netbox/`.
|
The plugin uses the shared NetBox MCP server at `mcp-servers/netbox/`.
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
|
|||||||
@@ -76,3 +76,132 @@ When presenting data:
|
|||||||
- Suggest corrective actions
|
- Suggest corrective actions
|
||||||
- For permission errors, note what access is needed
|
- For permission errors, note what access is needed
|
||||||
- For validation errors, explain required fields/formats
|
- For validation errors, explain required fields/formats
|
||||||
|
|
||||||
|
## Data Quality Validation
|
||||||
|
|
||||||
|
**IMPORTANT:** Load the `netbox-patterns` skill for best practice reference.
|
||||||
|
|
||||||
|
Before ANY create or update operation, validate against NetBox best practices:
|
||||||
|
|
||||||
|
### VM Operations
|
||||||
|
|
||||||
|
**Required checks before `virt_create_vm` or `virt_update_vm`:**
|
||||||
|
|
||||||
|
1. **Cluster/Site Assignment** - VMs must have either cluster or site
|
||||||
|
2. **Tenant Assignment** - Recommend if not provided
|
||||||
|
3. **Platform Assignment** - Recommend for OS tracking
|
||||||
|
4. **Naming Convention** - Check against `{env}-{app}-{number}` pattern
|
||||||
|
5. **Role Assignment** - Recommend appropriate role
|
||||||
|
|
||||||
|
**If user provides no site/tenant, ASK:**
|
||||||
|
|
||||||
|
> "This VM has no site or tenant assigned. NetBox best practices recommend:
|
||||||
|
> - **Site**: For location-based queries and power budgeting
|
||||||
|
> - **Tenant**: For resource isolation and ownership tracking
|
||||||
|
>
|
||||||
|
> Would you like me to:
|
||||||
|
> 1. Assign to an existing site/tenant (list available)
|
||||||
|
> 2. Create new site/tenant first
|
||||||
|
> 3. Proceed without (not recommended for production use)"
|
||||||
|
|
||||||
|
### Device Operations
|
||||||
|
|
||||||
|
**Required checks before `dcim_create_device` or `dcim_update_device`:**
|
||||||
|
|
||||||
|
1. **Site is REQUIRED** - Fail without it
|
||||||
|
2. **Platform Assignment** - Recommend for OS tracking
|
||||||
|
3. **Naming Convention** - Check against `{role}-{location}-{number}` pattern
|
||||||
|
4. **Role Assignment** - Ensure appropriate role selected
|
||||||
|
5. **After Creation** - Offer to set primary IP
|
||||||
|
|
||||||
|
### Cluster Operations
|
||||||
|
|
||||||
|
**Required checks before `virt_create_cluster`:**
|
||||||
|
|
||||||
|
1. **Site Scope** - Recommend assigning to site
|
||||||
|
2. **Cluster Type** - Ensure appropriate type selected
|
||||||
|
3. **Device Association** - Recommend linking to host device
|
||||||
|
|
||||||
|
### Role Management
|
||||||
|
|
||||||
|
**Before creating a new device role:**
|
||||||
|
|
||||||
|
1. List existing roles with `dcim_list_device_roles`
|
||||||
|
2. Check if a more general role already exists
|
||||||
|
3. Recommend role consolidation if >10 specific roles exist
|
||||||
|
|
||||||
|
**Example guidance:**
|
||||||
|
|
||||||
|
> "You're creating role 'nginx-web-server'. An existing 'web-server' role exists.
|
||||||
|
> Consider using 'web-server' and tracking nginx via the platform field instead.
|
||||||
|
> This reduces role fragmentation and improves maintainability."
|
||||||
|
|
||||||
|
## Dependency Order Enforcement
|
||||||
|
|
||||||
|
When creating multiple objects, follow this order:
|
||||||
|
|
||||||
|
```
|
||||||
|
1. Regions → Sites → Locations → Racks
|
||||||
|
2. Tenant Groups → Tenants
|
||||||
|
3. Manufacturers → Device Types
|
||||||
|
4. Device Roles, Platforms
|
||||||
|
5. Devices (with site, role, type)
|
||||||
|
6. Clusters (with type, optional site)
|
||||||
|
7. VMs (with cluster)
|
||||||
|
8. Interfaces → IP Addresses → Primary IP assignment
|
||||||
|
```
|
||||||
|
|
||||||
|
**CRITICAL Rules:**
|
||||||
|
- NEVER create a VM before its cluster exists
|
||||||
|
- NEVER create a device before its site exists
|
||||||
|
- NEVER create an interface before its device exists
|
||||||
|
- NEVER create an IP before its interface exists (if assigning)
|
||||||
|
|
||||||
|
## Naming Convention Enforcement
|
||||||
|
|
||||||
|
When user provides a name, check against patterns:
|
||||||
|
|
||||||
|
| Object Type | Pattern | Example |
|
||||||
|
|-------------|---------|---------|
|
||||||
|
| Device | `{role}-{site}-{number}` | `web-dc1-01` |
|
||||||
|
| VM | `{env}-{app}-{number}` or `{prefix}_{service}` | `prod-api-01` |
|
||||||
|
| Cluster | `{site}-{type}` | `dc1-vmware`, `home-docker` |
|
||||||
|
| Prefix | Include purpose in description | "Production /24 for web tier" |
|
||||||
|
|
||||||
|
**If name doesn't match patterns, warn:**
|
||||||
|
|
||||||
|
> "The name 'HotServ' doesn't follow naming conventions.
|
||||||
|
> Suggested: `prod-hotserv-01` or `hotserv-cloud-01`.
|
||||||
|
> Consistent naming improves searchability and automation compatibility.
|
||||||
|
> Proceed with original name? [Y/n]"
|
||||||
|
|
||||||
|
## Duplicate Prevention
|
||||||
|
|
||||||
|
Before creating objects, always check for existing duplicates:
|
||||||
|
|
||||||
|
```
|
||||||
|
# Before creating device
|
||||||
|
dcim_list_devices name=<proposed-name>
|
||||||
|
|
||||||
|
# Before creating VM
|
||||||
|
virt_list_vms name=<proposed-name>
|
||||||
|
|
||||||
|
# Before creating prefix
|
||||||
|
ipam_list_prefixes prefix=<proposed-prefix>
|
||||||
|
```
|
||||||
|
|
||||||
|
If duplicate found, inform user and suggest update instead of create.
|
||||||
|
|
||||||
|
## Available Commands
|
||||||
|
|
||||||
|
Users can invoke these commands for structured workflows:
|
||||||
|
|
||||||
|
| Command | Purpose |
|
||||||
|
|---------|---------|
|
||||||
|
| `/cmdb-search <query>` | Search across all CMDB objects |
|
||||||
|
| `/cmdb-device <action>` | Device CRUD operations |
|
||||||
|
| `/cmdb-ip <action>` | IP address and prefix management |
|
||||||
|
| `/cmdb-site <action>` | Site and location management |
|
||||||
|
| `/cmdb-audit [scope]` | Data quality analysis |
|
||||||
|
| `/cmdb-register` | Register current machine |
|
||||||
|
| `/cmdb-sync` | Sync machine state with NetBox |
|
||||||
|
|||||||
163
plugins/cmdb-assistant/commands/change-audit.md
Normal file
163
plugins/cmdb-assistant/commands/change-audit.md
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
---
|
||||||
|
description: Audit NetBox changes with filtering by date, user, or object type
|
||||||
|
---
|
||||||
|
|
||||||
|
# CMDB Change Audit
|
||||||
|
|
||||||
|
Query and analyze the NetBox audit log for change tracking and compliance.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
/change-audit [filters]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Filters:**
|
||||||
|
- `last <N> days/hours` - Changes within time period
|
||||||
|
- `by <username>` - Changes by specific user
|
||||||
|
- `type <object-type>` - Changes to specific object type
|
||||||
|
- `action <create|update|delete>` - Filter by action type
|
||||||
|
- `object <name>` - Search for changes to specific object
|
||||||
|
|
||||||
|
## Instructions
|
||||||
|
|
||||||
|
You are a change auditor that queries NetBox's object change log and generates audit reports.
|
||||||
|
|
||||||
|
### MCP Tools
|
||||||
|
|
||||||
|
Use these tools to query the audit log:
|
||||||
|
|
||||||
|
- `extras_list_object_changes` - List changes with filters:
|
||||||
|
- `user_id` - Filter by user ID
|
||||||
|
- `changed_object_type` - Filter by object type (e.g., "dcim.device", "ipam.ipaddress")
|
||||||
|
- `action` - Filter by action: "create", "update", "delete"
|
||||||
|
|
||||||
|
- `extras_get_object_change` - Get detailed change record by ID
|
||||||
|
|
||||||
|
### Common Object Types
|
||||||
|
|
||||||
|
| Category | Object Types |
|
||||||
|
|----------|--------------|
|
||||||
|
| DCIM | `dcim.device`, `dcim.interface`, `dcim.site`, `dcim.rack`, `dcim.cable` |
|
||||||
|
| IPAM | `ipam.ipaddress`, `ipam.prefix`, `ipam.vlan`, `ipam.vrf` |
|
||||||
|
| Virtualization | `virtualization.virtualmachine`, `virtualization.cluster` |
|
||||||
|
| Tenancy | `tenancy.tenant`, `tenancy.contact` |
|
||||||
|
|
||||||
|
### Workflow
|
||||||
|
|
||||||
|
1. **Parse user request** to determine filters
|
||||||
|
2. **Query object changes** using `extras_list_object_changes`
|
||||||
|
3. **Enrich data** by fetching detailed records if needed
|
||||||
|
4. **Analyze patterns** in the changes
|
||||||
|
5. **Generate report** in structured format
|
||||||
|
|
||||||
|
### Report Format
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## NetBox Change Audit Report
|
||||||
|
|
||||||
|
**Generated:** [timestamp]
|
||||||
|
**Period:** [date range or "All time"]
|
||||||
|
**Filters:** [applied filters]
|
||||||
|
|
||||||
|
### Summary
|
||||||
|
|
||||||
|
| Metric | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| Total Changes | X |
|
||||||
|
| Creates | Y |
|
||||||
|
| Updates | Z |
|
||||||
|
| Deletes | W |
|
||||||
|
| Unique Users | N |
|
||||||
|
| Object Types | M |
|
||||||
|
|
||||||
|
### Changes by Action
|
||||||
|
|
||||||
|
#### Created Objects (Y)
|
||||||
|
|
||||||
|
| Time | User | Object Type | Object | Details |
|
||||||
|
|------|------|-------------|--------|---------|
|
||||||
|
| 2024-01-15 14:30 | admin | dcim.device | server-01 | Created device |
|
||||||
|
| ... | ... | ... | ... | ... |
|
||||||
|
|
||||||
|
#### Updated Objects (Z)
|
||||||
|
|
||||||
|
| Time | User | Object Type | Object | Changed Fields |
|
||||||
|
|------|------|-------------|--------|----------------|
|
||||||
|
| 2024-01-15 15:00 | john | ipam.ipaddress | 10.0.1.50/24 | status, description |
|
||||||
|
| ... | ... | ... | ... | ... |
|
||||||
|
|
||||||
|
#### Deleted Objects (W)
|
||||||
|
|
||||||
|
| Time | User | Object Type | Object | Details |
|
||||||
|
|------|------|-------------|--------|---------|
|
||||||
|
| 2024-01-14 09:00 | admin | dcim.interface | eth2 | Removed from server-01 |
|
||||||
|
| ... | ... | ... | ... | ... |
|
||||||
|
|
||||||
|
### Changes by User
|
||||||
|
|
||||||
|
| User | Creates | Updates | Deletes | Total |
|
||||||
|
|------|---------|---------|---------|-------|
|
||||||
|
| admin | 5 | 10 | 2 | 17 |
|
||||||
|
| john | 3 | 8 | 0 | 11 |
|
||||||
|
|
||||||
|
### Changes by Object Type
|
||||||
|
|
||||||
|
| Object Type | Creates | Updates | Deletes | Total |
|
||||||
|
|-------------|---------|---------|---------|-------|
|
||||||
|
| dcim.device | 2 | 5 | 0 | 7 |
|
||||||
|
| ipam.ipaddress | 4 | 3 | 1 | 8 |
|
||||||
|
|
||||||
|
### Timeline
|
||||||
|
|
||||||
|
```
|
||||||
|
2024-01-15: ████████ 8 changes
|
||||||
|
2024-01-14: ████ 4 changes
|
||||||
|
2024-01-13: ██ 2 changes
|
||||||
|
```
|
||||||
|
|
||||||
|
### Notable Patterns
|
||||||
|
|
||||||
|
- **Bulk operations:** [Identify if many changes happened in short time]
|
||||||
|
- **Unusual activity:** [Flag unexpected deletions or after-hours changes]
|
||||||
|
- **Missing audit trail:** [Note if expected changes are not logged]
|
||||||
|
|
||||||
|
### Recommendations
|
||||||
|
|
||||||
|
1. [Any security or process recommendations based on findings]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Time Period Handling
|
||||||
|
|
||||||
|
When user specifies "last N days":
|
||||||
|
- The NetBox API may not have direct date filtering in `extras_list_object_changes`
|
||||||
|
- Fetch recent changes and filter client-side by the `time` field
|
||||||
|
- Note any limitations in the report
|
||||||
|
|
||||||
|
### Enriching Change Details
|
||||||
|
|
||||||
|
For detailed audit, use `extras_get_object_change` with the change ID to see:
|
||||||
|
- `prechange_data` - Object state before change
|
||||||
|
- `postchange_data` - Object state after change
|
||||||
|
- `request_id` - Links related changes in same request
|
||||||
|
|
||||||
|
### Security Audit Mode
|
||||||
|
|
||||||
|
If user asks for "security audit" or "compliance report":
|
||||||
|
1. Focus on deletions and permission-sensitive changes
|
||||||
|
2. Highlight changes to critical objects (firewalls, VRFs, prefixes)
|
||||||
|
3. Flag changes outside business hours
|
||||||
|
4. Identify users with high change counts
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
- `/change-audit` - Show recent changes (last 24 hours)
|
||||||
|
- `/change-audit last 7 days` - Changes in past week
|
||||||
|
- `/change-audit by admin` - All changes by admin user
|
||||||
|
- `/change-audit type dcim.device` - Device changes only
|
||||||
|
- `/change-audit action delete` - All deletions
|
||||||
|
- `/change-audit object server-01` - Changes to server-01
|
||||||
|
|
||||||
|
## User Request
|
||||||
|
|
||||||
|
$ARGUMENTS
|
||||||
195
plugins/cmdb-assistant/commands/cmdb-audit.md
Normal file
195
plugins/cmdb-assistant/commands/cmdb-audit.md
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
---
|
||||||
|
description: Audit NetBox data quality and identify consistency issues
|
||||||
|
---
|
||||||
|
|
||||||
|
# CMDB Data Quality Audit
|
||||||
|
|
||||||
|
Analyze NetBox data for quality issues and best practice violations.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
/cmdb-audit [scope]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Scopes:**
|
||||||
|
- `all` (default) - Full audit across all categories
|
||||||
|
- `vms` - Virtual machines only
|
||||||
|
- `devices` - Physical devices only
|
||||||
|
- `naming` - Naming convention analysis
|
||||||
|
- `roles` - Role fragmentation analysis
|
||||||
|
|
||||||
|
## Instructions
|
||||||
|
|
||||||
|
You are a data quality auditor for NetBox. Your job is to identify consistency issues and best practice violations.
|
||||||
|
|
||||||
|
**IMPORTANT:** Load the `netbox-patterns` skill for best practice reference.
|
||||||
|
|
||||||
|
### Phase 1: Data Collection
|
||||||
|
|
||||||
|
Run these MCP tool calls to gather data for analysis:
|
||||||
|
|
||||||
|
```
|
||||||
|
1. virt_list_vms (no filters - get all)
|
||||||
|
2. dcim_list_devices (no filters - get all)
|
||||||
|
3. virt_list_clusters (no filters)
|
||||||
|
4. dcim_list_sites
|
||||||
|
5. tenancy_list_tenants
|
||||||
|
6. dcim_list_device_roles
|
||||||
|
7. dcim_list_platforms
|
||||||
|
```
|
||||||
|
|
||||||
|
Store the results for analysis.
|
||||||
|
|
||||||
|
### Phase 2: Quality Checks
|
||||||
|
|
||||||
|
Analyze collected data for these issues by severity:
|
||||||
|
|
||||||
|
#### CRITICAL Issues (must fix immediately)
|
||||||
|
|
||||||
|
| Check | Detection |
|
||||||
|
|-------|-----------|
|
||||||
|
| VMs without cluster | `cluster` field is null AND `site` field is null |
|
||||||
|
| Devices without site | `site` field is null |
|
||||||
|
| Active devices without primary IP | `status=active` AND `primary_ip4` is null AND `primary_ip6` is null |
|
||||||
|
|
||||||
|
#### HIGH Issues (should fix soon)
|
||||||
|
|
||||||
|
| Check | Detection |
|
||||||
|
|-------|-----------|
|
||||||
|
| VMs without site | VM has no site (neither direct nor via cluster.site) |
|
||||||
|
| VMs without tenant | `tenant` field is null |
|
||||||
|
| Devices without platform | `platform` field is null |
|
||||||
|
| Clusters not scoped to site | `site` field is null on cluster |
|
||||||
|
| VMs without role | `role` field is null |
|
||||||
|
|
||||||
|
#### MEDIUM Issues (plan to address)
|
||||||
|
|
||||||
|
| Check | Detection |
|
||||||
|
|-------|-----------|
|
||||||
|
| Inconsistent naming | Names don't match patterns: devices=`{role}-{site}-{num}`, VMs=`{env}-{app}-{num}` |
|
||||||
|
| Role fragmentation | More than 10 device roles with <3 assignments each |
|
||||||
|
| Missing tags on production | Active resources without any tags |
|
||||||
|
| Mixed naming separators | Some names use `_`, others use `-` |
|
||||||
|
|
||||||
|
#### LOW Issues (informational)
|
||||||
|
|
||||||
|
| Check | Detection |
|
||||||
|
|-------|-----------|
|
||||||
|
| Docker containers as VMs | Cluster type is "Docker Compose" - document this modeling choice |
|
||||||
|
| VMs without description | `description` field is empty |
|
||||||
|
| Sites without physical address | `physical_address` is empty |
|
||||||
|
| Devices without serial | `serial` field is empty |
|
||||||
|
|
||||||
|
### Phase 3: Naming Convention Analysis
|
||||||
|
|
||||||
|
For naming scope, analyze patterns:
|
||||||
|
|
||||||
|
1. **Extract naming patterns** from existing objects
|
||||||
|
2. **Identify dominant patterns** (most common conventions)
|
||||||
|
3. **Flag outliers** that don't match dominant patterns
|
||||||
|
4. **Suggest standardization** based on best practices
|
||||||
|
|
||||||
|
**Expected Patterns:**
|
||||||
|
- Devices: `{role}-{location}-{number}` (e.g., `web-dc1-01`)
|
||||||
|
- VMs: `{prefix}_{service}` or `{env}-{app}-{number}` (e.g., `prod-api-01`)
|
||||||
|
- Clusters: `{site}-{type}` (e.g., `home-docker`)
|
||||||
|
|
||||||
|
### Phase 4: Role Analysis
|
||||||
|
|
||||||
|
For roles scope, analyze fragmentation:
|
||||||
|
|
||||||
|
1. **List all device roles** with assignment counts
|
||||||
|
2. **Identify single-use roles** (only 1 device/VM)
|
||||||
|
3. **Identify similar roles** that could be consolidated
|
||||||
|
4. **Suggest consolidation** based on patterns
|
||||||
|
|
||||||
|
**Red Flags:**
|
||||||
|
- More than 15 highly specific roles
|
||||||
|
- Roles with technology in name (use platform instead)
|
||||||
|
- Roles that duplicate functionality
|
||||||
|
|
||||||
|
### Phase 5: Report Generation
|
||||||
|
|
||||||
|
Present findings in this structure:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## CMDB Data Quality Audit Report
|
||||||
|
|
||||||
|
**Generated:** [timestamp]
|
||||||
|
**Scope:** [scope parameter]
|
||||||
|
|
||||||
|
### Summary
|
||||||
|
|
||||||
|
| Metric | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| Total VMs | X |
|
||||||
|
| Total Devices | Y |
|
||||||
|
| Total Clusters | Z |
|
||||||
|
| **Total Issues** | **N** |
|
||||||
|
|
||||||
|
| Severity | Count |
|
||||||
|
|----------|-------|
|
||||||
|
| Critical | A |
|
||||||
|
| High | B |
|
||||||
|
| Medium | C |
|
||||||
|
| Low | D |
|
||||||
|
|
||||||
|
### Critical Issues
|
||||||
|
|
||||||
|
[List each with specific object names and IDs]
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
- VM `HotServ` (ID: 1) - No cluster or site assignment
|
||||||
|
- Device `server-01` (ID: 5) - No site assignment
|
||||||
|
|
||||||
|
### High Issues
|
||||||
|
|
||||||
|
[List each with specific object names]
|
||||||
|
|
||||||
|
### Medium Issues
|
||||||
|
|
||||||
|
[Grouped by category with counts]
|
||||||
|
|
||||||
|
### Recommendations
|
||||||
|
|
||||||
|
1. **[Most impactful fix]** - affects N objects
|
||||||
|
2. **[Second priority]** - affects M objects
|
||||||
|
...
|
||||||
|
|
||||||
|
### Quick Fixes
|
||||||
|
|
||||||
|
Commands to fix common issues:
|
||||||
|
|
||||||
|
```
|
||||||
|
# Assign site to VM
|
||||||
|
virt_update_vm id=X site=Y
|
||||||
|
|
||||||
|
# Assign platform to device
|
||||||
|
dcim_update_device id=X platform=Y
|
||||||
|
```
|
||||||
|
|
||||||
|
### Next Steps
|
||||||
|
|
||||||
|
- Run `/cmdb-register` to properly register new machines
|
||||||
|
- Use `/cmdb-sync` to update existing registrations
|
||||||
|
- Consider bulk updates via NetBox web UI for >10 items
|
||||||
|
```
|
||||||
|
|
||||||
|
## Scope-Specific Instructions
|
||||||
|
|
||||||
|
### For `vms` scope:
|
||||||
|
Focus only on Virtual Machine checks. Skip device and role analysis.
|
||||||
|
|
||||||
|
### For `devices` scope:
|
||||||
|
Focus only on Device checks. Skip VM and cluster analysis.
|
||||||
|
|
||||||
|
### For `naming` scope:
|
||||||
|
Focus on naming convention analysis across all objects. Generate detailed pattern report.
|
||||||
|
|
||||||
|
### For `roles` scope:
|
||||||
|
Focus on role fragmentation analysis. Generate consolidation recommendations.
|
||||||
|
|
||||||
|
## User Request
|
||||||
|
|
||||||
|
$ARGUMENTS
|
||||||
322
plugins/cmdb-assistant/commands/cmdb-register.md
Normal file
322
plugins/cmdb-assistant/commands/cmdb-register.md
Normal file
@@ -0,0 +1,322 @@
|
|||||||
|
---
|
||||||
|
description: Register the current machine into NetBox with all running applications
|
||||||
|
---
|
||||||
|
|
||||||
|
# CMDB Machine Registration
|
||||||
|
|
||||||
|
Register the current machine into NetBox, including hardware info, network interfaces, and running applications (Docker containers, services).
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
/cmdb-register [--site <site-name>] [--tenant <tenant-name>] [--role <role-name>]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Options:**
|
||||||
|
- `--site <name>`: Site to assign (will prompt if not provided)
|
||||||
|
- `--tenant <name>`: Tenant for resource isolation (optional)
|
||||||
|
- `--role <name>`: Device role (default: auto-detect based on services)
|
||||||
|
|
||||||
|
## Instructions
|
||||||
|
|
||||||
|
You are registering the current machine into NetBox. This is a multi-phase process that discovers local system information and creates corresponding NetBox objects.
|
||||||
|
|
||||||
|
**IMPORTANT:** Load the `netbox-patterns` skill for best practice reference.
|
||||||
|
|
||||||
|
### Phase 1: System Discovery (via Bash)
|
||||||
|
|
||||||
|
Gather system information using these commands:
|
||||||
|
|
||||||
|
#### 1.1 Basic Device Info
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Hostname
|
||||||
|
hostname
|
||||||
|
|
||||||
|
# OS/Platform info
|
||||||
|
cat /etc/os-release 2>/dev/null || uname -a
|
||||||
|
|
||||||
|
# Hardware model (varies by system)
|
||||||
|
# Raspberry Pi:
|
||||||
|
cat /proc/device-tree/model 2>/dev/null || echo "Unknown"
|
||||||
|
|
||||||
|
# x86 systems:
|
||||||
|
cat /sys/class/dmi/id/product_name 2>/dev/null || echo "Unknown"
|
||||||
|
|
||||||
|
# Serial number
|
||||||
|
# Raspberry Pi:
|
||||||
|
cat /proc/device-tree/serial-number 2>/dev/null || cat /proc/cpuinfo | grep Serial | cut -d: -f2 | tr -d ' ' 2>/dev/null
|
||||||
|
|
||||||
|
# x86 systems:
|
||||||
|
cat /sys/class/dmi/id/product_serial 2>/dev/null || echo "Unknown"
|
||||||
|
|
||||||
|
# CPU info
|
||||||
|
nproc
|
||||||
|
|
||||||
|
# Memory (MB)
|
||||||
|
free -m | awk '/Mem:/ {print $2}'
|
||||||
|
|
||||||
|
# Disk (GB, root filesystem)
|
||||||
|
df -BG / | awk 'NR==2 {print $2}' | tr -d 'G'
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 1.2 Network Interfaces
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Get interfaces with IPs (JSON format)
|
||||||
|
ip -j addr show 2>/dev/null || ip addr show
|
||||||
|
|
||||||
|
# Get default gateway interface
|
||||||
|
ip route | grep default | awk '{print $5}' | head -1
|
||||||
|
|
||||||
|
# Get MAC addresses
|
||||||
|
ip -j link show 2>/dev/null || ip link show
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 1.3 Running Applications
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Docker containers (if docker available)
|
||||||
|
docker ps --format '{"name":"{{.Names}}","image":"{{.Image}}","status":"{{.Status}}","ports":"{{.Ports}}"}' 2>/dev/null || echo "Docker not available"
|
||||||
|
|
||||||
|
# Docker Compose projects (check common locations)
|
||||||
|
find ~/apps /home/*/apps -name "docker-compose.yml" -o -name "docker-compose.yaml" 2>/dev/null | head -20
|
||||||
|
|
||||||
|
# Systemd services (running)
|
||||||
|
systemctl list-units --type=service --state=running --no-pager --plain 2>/dev/null | grep -v "^UNIT" | head -30
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 2: Pre-Registration Checks (via MCP)
|
||||||
|
|
||||||
|
Before creating objects, verify prerequisites:
|
||||||
|
|
||||||
|
#### 2.1 Check if Device Already Exists
|
||||||
|
|
||||||
|
```
|
||||||
|
dcim_list_devices name=<hostname>
|
||||||
|
```
|
||||||
|
|
||||||
|
**If device exists:**
|
||||||
|
- Inform user and suggest `/cmdb-sync` instead
|
||||||
|
- Ask if they want to proceed with re-registration (will update existing)
|
||||||
|
|
||||||
|
#### 2.2 Verify/Create Site
|
||||||
|
|
||||||
|
If `--site` provided:
|
||||||
|
```
|
||||||
|
dcim_list_sites name=<site-name>
|
||||||
|
```
|
||||||
|
|
||||||
|
If site doesn't exist, ask user if they want to create it.
|
||||||
|
|
||||||
|
If no site provided, list available sites and ask user to choose:
|
||||||
|
```
|
||||||
|
dcim_list_sites
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2.3 Verify/Create Platform
|
||||||
|
|
||||||
|
Based on OS detected, check if platform exists:
|
||||||
|
```
|
||||||
|
dcim_list_platforms name=<platform-name>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Platform naming:**
|
||||||
|
- `Raspberry Pi OS (Bookworm)` for Raspberry Pi
|
||||||
|
- `Ubuntu 24.04 LTS` for Ubuntu
|
||||||
|
- `Debian 12` for Debian
|
||||||
|
- Use format: `{OS Name} {Version}`
|
||||||
|
|
||||||
|
If platform doesn't exist, create it:
|
||||||
|
```
|
||||||
|
dcim_create_platform name=<platform-name> slug=<slug>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2.4 Verify/Create Device Role
|
||||||
|
|
||||||
|
Based on detected services:
|
||||||
|
- If Docker containers found → `Docker Host`
|
||||||
|
- If only basic services → `Server`
|
||||||
|
- If specific role specified → Use that
|
||||||
|
|
||||||
|
```
|
||||||
|
dcim_list_device_roles name=<role-name>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 3: Device Registration (via MCP)
|
||||||
|
|
||||||
|
#### 3.1 Get/Create Manufacturer and Device Type
|
||||||
|
|
||||||
|
For Raspberry Pi:
|
||||||
|
```
|
||||||
|
dcim_list_manufacturers name="Raspberry Pi Foundation"
|
||||||
|
dcim_list_device_types manufacturer_id=X model="Raspberry Pi 4 Model B"
|
||||||
|
```
|
||||||
|
|
||||||
|
Create if not exists.
|
||||||
|
|
||||||
|
For generic x86:
|
||||||
|
```
|
||||||
|
dcim_list_manufacturers name=<detected-manufacturer>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3.2 Create Device
|
||||||
|
|
||||||
|
```
|
||||||
|
dcim_create_device
|
||||||
|
name=<hostname>
|
||||||
|
device_type=<device_type_id>
|
||||||
|
role=<role_id>
|
||||||
|
site=<site_id>
|
||||||
|
platform=<platform_id>
|
||||||
|
tenant=<tenant_id> # if provided
|
||||||
|
serial=<serial>
|
||||||
|
description="Registered via cmdb-assistant"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3.3 Create Interfaces
|
||||||
|
|
||||||
|
For each network interface discovered:
|
||||||
|
```
|
||||||
|
dcim_create_interface
|
||||||
|
device=<device_id>
|
||||||
|
name=<interface_name> # eth0, wlan0, tailscale0, etc.
|
||||||
|
type=<type> # 1000base-t, virtual, other
|
||||||
|
mac_address=<mac>
|
||||||
|
enabled=true
|
||||||
|
```
|
||||||
|
|
||||||
|
**Interface type mapping:**
|
||||||
|
- `eth*`, `enp*` → `1000base-t`
|
||||||
|
- `wlan*` → `ieee802.11ax` (or appropriate wifi type)
|
||||||
|
- `tailscale*`, `docker*`, `br-*` → `virtual`
|
||||||
|
- `lo` → skip (loopback)
|
||||||
|
|
||||||
|
#### 3.4 Create IP Addresses
|
||||||
|
|
||||||
|
For each IP on each interface:
|
||||||
|
```
|
||||||
|
ipam_create_ip_address
|
||||||
|
address=<ip/prefix> # e.g., "192.168.1.100/24"
|
||||||
|
assigned_object_type="dcim.interface"
|
||||||
|
assigned_object_id=<interface_id>
|
||||||
|
status="active"
|
||||||
|
description="Discovered via cmdb-register"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3.5 Set Primary IP
|
||||||
|
|
||||||
|
Identify primary IP (interface with default route):
|
||||||
|
```
|
||||||
|
dcim_update_device
|
||||||
|
id=<device_id>
|
||||||
|
primary_ip4=<primary_ip_id>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 4: Container Registration (via MCP)
|
||||||
|
|
||||||
|
If Docker containers were discovered:
|
||||||
|
|
||||||
|
#### 4.1 Create/Get Cluster Type
|
||||||
|
|
||||||
|
```
|
||||||
|
virt_list_cluster_types name="Docker Compose"
|
||||||
|
```
|
||||||
|
|
||||||
|
Create if not exists:
|
||||||
|
```
|
||||||
|
virt_create_cluster_type name="Docker Compose" slug="docker-compose"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 4.2 Create Cluster
|
||||||
|
|
||||||
|
For each Docker Compose project directory found:
|
||||||
|
```
|
||||||
|
virt_create_cluster
|
||||||
|
name=<project-name> # e.g., "apps-hotport"
|
||||||
|
type=<cluster_type_id>
|
||||||
|
site=<site_id>
|
||||||
|
description="Docker Compose stack on <hostname>"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 4.3 Create VMs for Containers
|
||||||
|
|
||||||
|
For each running container:
|
||||||
|
```
|
||||||
|
virt_create_vm
|
||||||
|
name=<container_name>
|
||||||
|
cluster=<cluster_id>
|
||||||
|
site=<site_id>
|
||||||
|
role=<role_id> # Map container function to role
|
||||||
|
status="active"
|
||||||
|
vcpus=<cpu_shares> # Default 1.0 if unknown
|
||||||
|
memory=<memory_mb> # Default 256 if unknown
|
||||||
|
disk=<disk_gb> # Default 5 if unknown
|
||||||
|
description=<container purpose>
|
||||||
|
comments=<image, ports, volumes info>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Container role mapping:**
|
||||||
|
- `*caddy*`, `*nginx*`, `*traefik*` → "Reverse Proxy"
|
||||||
|
- `*db*`, `*postgres*`, `*mysql*`, `*redis*` → "Database"
|
||||||
|
- `*webui*`, `*frontend*` → "Web Application"
|
||||||
|
- Others → Infer from image name or use generic "Container"
|
||||||
|
|
||||||
|
### Phase 5: Documentation
|
||||||
|
|
||||||
|
#### 5.1 Add Journal Entry
|
||||||
|
|
||||||
|
```
|
||||||
|
extras_create_journal_entry
|
||||||
|
assigned_object_type="dcim.device"
|
||||||
|
assigned_object_id=<device_id>
|
||||||
|
comments="Device registered via /cmdb-register command\n\nDiscovered:\n- X network interfaces\n- Y IP addresses\n- Z Docker containers"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 6: Summary Report
|
||||||
|
|
||||||
|
Present registration summary:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## Machine Registration Complete
|
||||||
|
|
||||||
|
### Device Created
|
||||||
|
- **Name:** <hostname>
|
||||||
|
- **Site:** <site>
|
||||||
|
- **Platform:** <platform>
|
||||||
|
- **Role:** <role>
|
||||||
|
- **ID:** <device_id>
|
||||||
|
- **URL:** https://netbox.example.com/dcim/devices/<id>/
|
||||||
|
|
||||||
|
### Network Interfaces
|
||||||
|
| Interface | Type | MAC | IP Address |
|
||||||
|
|-----------|------|-----|------------|
|
||||||
|
| eth0 | 1000base-t | aa:bb:cc:dd:ee:ff | 192.168.1.100/24 |
|
||||||
|
| tailscale0 | virtual | - | 100.x.x.x/32 |
|
||||||
|
|
||||||
|
### Primary IP: 192.168.1.100
|
||||||
|
|
||||||
|
### Docker Containers Registered (if applicable)
|
||||||
|
**Cluster:** <cluster_name> (ID: <cluster_id>)
|
||||||
|
|
||||||
|
| Container | Role | vCPUs | Memory | Status |
|
||||||
|
|-----------|------|-------|--------|--------|
|
||||||
|
| media_jellyfin | Media Server | 2.0 | 2048MB | Active |
|
||||||
|
| media_sonarr | Media Management | 1.0 | 512MB | Active |
|
||||||
|
|
||||||
|
### Next Steps
|
||||||
|
- Run `/cmdb-sync` periodically to keep data current
|
||||||
|
- Run `/cmdb-audit` to check data quality
|
||||||
|
- Add tags for classification (env:*, team:*, etc.)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
- **Device already exists:** Suggest `/cmdb-sync` or ask to proceed
|
||||||
|
- **Site not found:** List available sites, offer to create new
|
||||||
|
- **Docker not available:** Skip container registration, note in summary
|
||||||
|
- **Permission denied:** Note which operations failed, suggest fixes
|
||||||
|
|
||||||
|
## User Request
|
||||||
|
|
||||||
|
$ARGUMENTS
|
||||||
336
plugins/cmdb-assistant/commands/cmdb-sync.md
Normal file
336
plugins/cmdb-assistant/commands/cmdb-sync.md
Normal file
@@ -0,0 +1,336 @@
|
|||||||
|
---
|
||||||
|
description: Synchronize current machine state with existing NetBox record
|
||||||
|
---
|
||||||
|
|
||||||
|
# CMDB Machine Sync
|
||||||
|
|
||||||
|
Update an existing NetBox device record with the current machine state. Compares local system information with NetBox and applies changes.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
/cmdb-sync [--full] [--dry-run]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Options:**
|
||||||
|
- `--full`: Force refresh all fields, even unchanged ones
|
||||||
|
- `--dry-run`: Show what would change without applying updates
|
||||||
|
|
||||||
|
## Instructions
|
||||||
|
|
||||||
|
You are synchronizing the current machine's state with its NetBox record. This involves comparing current system state with stored data and updating differences.
|
||||||
|
|
||||||
|
**IMPORTANT:** Load the `netbox-patterns` skill for best practice reference.
|
||||||
|
|
||||||
|
### Phase 1: Device Lookup (via MCP)
|
||||||
|
|
||||||
|
First, find the existing device record:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Get current hostname
|
||||||
|
hostname
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
dcim_list_devices name=<hostname>
|
||||||
|
```
|
||||||
|
|
||||||
|
**If device not found:**
|
||||||
|
- Inform user: "Device '<hostname>' not found in NetBox"
|
||||||
|
- Suggest: "Run `/cmdb-register` to register this machine first"
|
||||||
|
- Exit sync
|
||||||
|
|
||||||
|
**If device found:**
|
||||||
|
- Store device ID and all current field values
|
||||||
|
- Fetch interfaces: `dcim_list_interfaces device_id=<device_id>`
|
||||||
|
- Fetch IPs: `ipam_list_ip_addresses device_id=<device_id>`
|
||||||
|
|
||||||
|
Also check for associated clusters/VMs:
|
||||||
|
```
|
||||||
|
virt_list_clusters # Look for cluster associated with this device
|
||||||
|
virt_list_vms cluster=<cluster_id> # If cluster found
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 2: Current State Discovery (via Bash)
|
||||||
|
|
||||||
|
Gather current system information (same as `/cmdb-register`):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Device info
|
||||||
|
hostname
|
||||||
|
cat /etc/os-release 2>/dev/null || uname -a
|
||||||
|
nproc
|
||||||
|
free -m | awk '/Mem:/ {print $2}'
|
||||||
|
df -BG / | awk 'NR==2 {print $2}' | tr -d 'G'
|
||||||
|
|
||||||
|
# Network interfaces with IPs
|
||||||
|
ip -j addr show 2>/dev/null || ip addr show
|
||||||
|
|
||||||
|
# Docker containers
|
||||||
|
docker ps --format '{"name":"{{.Names}}","image":"{{.Image}}","status":"{{.Status}}"}' 2>/dev/null || echo "[]"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 3: Comparison
|
||||||
|
|
||||||
|
Compare discovered state with NetBox record:
|
||||||
|
|
||||||
|
#### 3.1 Device Attributes
|
||||||
|
|
||||||
|
| Field | Compare |
|
||||||
|
|-------|---------|
|
||||||
|
| Platform | OS version changed? |
|
||||||
|
| Status | Still active? |
|
||||||
|
| Serial | Match? |
|
||||||
|
| Description | Keep existing |
|
||||||
|
|
||||||
|
#### 3.2 Network Interfaces
|
||||||
|
|
||||||
|
| Change Type | Detection |
|
||||||
|
|-------------|-----------|
|
||||||
|
| New interface | Interface exists locally but not in NetBox |
|
||||||
|
| Removed interface | Interface in NetBox but not locally |
|
||||||
|
| Changed MAC | MAC address different |
|
||||||
|
| Interface type | Type mismatch |
|
||||||
|
|
||||||
|
#### 3.3 IP Addresses
|
||||||
|
|
||||||
|
| Change Type | Detection |
|
||||||
|
|-------------|-----------|
|
||||||
|
| New IP | IP exists locally but not in NetBox |
|
||||||
|
| Removed IP | IP in NetBox but not locally (on this device) |
|
||||||
|
| Primary IP changed | Default route interface changed |
|
||||||
|
|
||||||
|
#### 3.4 Docker Containers
|
||||||
|
|
||||||
|
| Change Type | Detection |
|
||||||
|
|-------------|-----------|
|
||||||
|
| New container | Container running locally but no VM in cluster |
|
||||||
|
| Stopped container | VM exists but container not running |
|
||||||
|
| Resource change | vCPUs/memory different (if trackable) |
|
||||||
|
|
||||||
|
### Phase 4: Diff Report
|
||||||
|
|
||||||
|
Present changes to user:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## Sync Diff Report
|
||||||
|
|
||||||
|
**Device:** <hostname> (ID: <device_id>)
|
||||||
|
**NetBox URL:** https://netbox.example.com/dcim/devices/<id>/
|
||||||
|
|
||||||
|
### Device Attributes
|
||||||
|
| Field | NetBox Value | Current Value | Action |
|
||||||
|
|-------|--------------|---------------|--------|
|
||||||
|
| Platform | Ubuntu 22.04 | Ubuntu 24.04 | UPDATE |
|
||||||
|
| Status | active | active | - |
|
||||||
|
|
||||||
|
### Network Interfaces
|
||||||
|
|
||||||
|
#### New Interfaces (will create)
|
||||||
|
| Interface | Type | MAC | IPs |
|
||||||
|
|-----------|------|-----|-----|
|
||||||
|
| tailscale0 | virtual | - | 100.x.x.x/32 |
|
||||||
|
|
||||||
|
#### Removed Interfaces (will mark offline)
|
||||||
|
| Interface | Type | Reason |
|
||||||
|
|-----------|------|--------|
|
||||||
|
| eth1 | 1000base-t | Not found locally |
|
||||||
|
|
||||||
|
#### Changed Interfaces
|
||||||
|
| Interface | Field | Old | New |
|
||||||
|
|-----------|-------|-----|-----|
|
||||||
|
| eth0 | mac_address | aa:bb:cc:00:00:00 | aa:bb:cc:11:11:11 |
|
||||||
|
|
||||||
|
### IP Addresses
|
||||||
|
|
||||||
|
#### New IPs (will create)
|
||||||
|
- 192.168.1.150/24 on eth0
|
||||||
|
|
||||||
|
#### Removed IPs (will unassign)
|
||||||
|
- 192.168.1.100/24 from eth0
|
||||||
|
|
||||||
|
### Docker Containers
|
||||||
|
|
||||||
|
#### New Containers (will create VMs)
|
||||||
|
| Container | Image | Role |
|
||||||
|
|-----------|-------|------|
|
||||||
|
| media_lidarr | linuxserver/lidarr | Media Management |
|
||||||
|
|
||||||
|
#### Stopped Containers (will mark offline)
|
||||||
|
| Container | Last Status |
|
||||||
|
|-----------|-------------|
|
||||||
|
| media_bazarr | Exited |
|
||||||
|
|
||||||
|
### Summary
|
||||||
|
- **Updates:** X
|
||||||
|
- **Creates:** Y
|
||||||
|
- **Removals/Offline:** Z
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 5: User Confirmation
|
||||||
|
|
||||||
|
If not `--dry-run`:
|
||||||
|
|
||||||
|
```
|
||||||
|
The following changes will be applied:
|
||||||
|
- Update device platform to "Ubuntu 24.04"
|
||||||
|
- Create interface "tailscale0"
|
||||||
|
- Create IP "100.x.x.x/32" on tailscale0
|
||||||
|
- Create VM "media_lidarr" in cluster
|
||||||
|
- Mark VM "media_bazarr" as offline
|
||||||
|
|
||||||
|
Proceed with sync? [Y/n]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Use AskUserQuestion** to get confirmation.
|
||||||
|
|
||||||
|
### Phase 6: Apply Updates (via MCP)
|
||||||
|
|
||||||
|
Only if user confirms (or `--full` specified):
|
||||||
|
|
||||||
|
#### 6.1 Device Updates
|
||||||
|
|
||||||
|
```
|
||||||
|
dcim_update_device
|
||||||
|
id=<device_id>
|
||||||
|
platform=<new_platform_id>
|
||||||
|
# ... other changed fields
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 6.2 Interface Updates
|
||||||
|
|
||||||
|
**For new interfaces:**
|
||||||
|
```
|
||||||
|
dcim_create_interface
|
||||||
|
device=<device_id>
|
||||||
|
name=<interface_name>
|
||||||
|
type=<type>
|
||||||
|
mac_address=<mac>
|
||||||
|
enabled=true
|
||||||
|
```
|
||||||
|
|
||||||
|
**For removed interfaces:**
|
||||||
|
```
|
||||||
|
dcim_update_interface
|
||||||
|
id=<interface_id>
|
||||||
|
enabled=false
|
||||||
|
description="Marked offline by cmdb-sync - interface no longer present"
|
||||||
|
```
|
||||||
|
|
||||||
|
**For changed interfaces:**
|
||||||
|
```
|
||||||
|
dcim_update_interface
|
||||||
|
id=<interface_id>
|
||||||
|
mac_address=<new_mac>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 6.3 IP Address Updates
|
||||||
|
|
||||||
|
**For new IPs:**
|
||||||
|
```
|
||||||
|
ipam_create_ip_address
|
||||||
|
address=<ip/prefix>
|
||||||
|
assigned_object_type="dcim.interface"
|
||||||
|
assigned_object_id=<interface_id>
|
||||||
|
status="active"
|
||||||
|
```
|
||||||
|
|
||||||
|
**For removed IPs:**
|
||||||
|
```
|
||||||
|
ipam_update_ip_address
|
||||||
|
id=<ip_id>
|
||||||
|
assigned_object_type=null
|
||||||
|
assigned_object_id=null
|
||||||
|
description="Unassigned by cmdb-sync"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 6.4 Primary IP Update
|
||||||
|
|
||||||
|
If primary IP changed:
|
||||||
|
```
|
||||||
|
dcim_update_device
|
||||||
|
id=<device_id>
|
||||||
|
primary_ip4=<new_primary_ip_id>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 6.5 Container/VM Updates
|
||||||
|
|
||||||
|
**For new containers:**
|
||||||
|
```
|
||||||
|
virt_create_vm
|
||||||
|
name=<container_name>
|
||||||
|
cluster=<cluster_id>
|
||||||
|
status="active"
|
||||||
|
# ... other fields
|
||||||
|
```
|
||||||
|
|
||||||
|
**For stopped containers:**
|
||||||
|
```
|
||||||
|
virt_update_vm
|
||||||
|
id=<vm_id>
|
||||||
|
status="offline"
|
||||||
|
description="Container stopped - detected by cmdb-sync"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 7: Journal Entry
|
||||||
|
|
||||||
|
Document the sync:
|
||||||
|
|
||||||
|
```
|
||||||
|
extras_create_journal_entry
|
||||||
|
assigned_object_type="dcim.device"
|
||||||
|
assigned_object_id=<device_id>
|
||||||
|
comments="Device synced via /cmdb-sync command\n\nChanges applied:\n- <list of changes>"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 8: Summary Report
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## Sync Complete
|
||||||
|
|
||||||
|
**Device:** <hostname>
|
||||||
|
**Sync Time:** <timestamp>
|
||||||
|
|
||||||
|
### Changes Applied
|
||||||
|
- Updated platform: Ubuntu 22.04 → Ubuntu 24.04
|
||||||
|
- Created interface: tailscale0 (ID: X)
|
||||||
|
- Created IP: 100.x.x.x/32 (ID: Y)
|
||||||
|
- Created VM: media_lidarr (ID: Z)
|
||||||
|
- Marked VM offline: media_bazarr (ID: W)
|
||||||
|
|
||||||
|
### Current State
|
||||||
|
- **Interfaces:** 4 (3 active, 1 offline)
|
||||||
|
- **IP Addresses:** 5
|
||||||
|
- **Containers/VMs:** 8 (7 active, 1 offline)
|
||||||
|
|
||||||
|
### Next Sync
|
||||||
|
Run `/cmdb-sync` again after:
|
||||||
|
- Adding/removing Docker containers
|
||||||
|
- Changing network configuration
|
||||||
|
- OS upgrades
|
||||||
|
```
|
||||||
|
|
||||||
|
## Dry Run Mode
|
||||||
|
|
||||||
|
If `--dry-run` specified:
|
||||||
|
- Complete Phase 1-4 (lookup, discovery, compare, diff report)
|
||||||
|
- Skip Phase 5-8 (no confirmation, no updates, no journal)
|
||||||
|
- End with: "Dry run complete. No changes applied. Run without --dry-run to apply."
|
||||||
|
|
||||||
|
## Full Sync Mode
|
||||||
|
|
||||||
|
If `--full` specified:
|
||||||
|
- Skip user confirmation
|
||||||
|
- Update all fields even if unchanged (force refresh)
|
||||||
|
- Useful for ensuring NetBox matches current state exactly
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
- **Device not found:** Suggest `/cmdb-register`
|
||||||
|
- **Permission denied on updates:** Note which failed, continue with others
|
||||||
|
- **Cluster not found:** Offer to create or skip container sync
|
||||||
|
- **API errors:** Log error, continue with remaining updates
|
||||||
|
|
||||||
|
## User Request
|
||||||
|
|
||||||
|
$ARGUMENTS
|
||||||
182
plugins/cmdb-assistant/commands/cmdb-topology.md
Normal file
182
plugins/cmdb-assistant/commands/cmdb-topology.md
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
---
|
||||||
|
description: Generate infrastructure topology diagrams from NetBox data
|
||||||
|
---
|
||||||
|
|
||||||
|
# CMDB Topology Visualization
|
||||||
|
|
||||||
|
Generate Mermaid diagrams showing infrastructure topology from NetBox.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
/cmdb-topology <view> [scope]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Views:**
|
||||||
|
- `rack <rack-name>` - Rack elevation showing devices and positions
|
||||||
|
- `network [site]` - Network topology showing device connections via cables
|
||||||
|
- `site <site-name>` - Site overview with racks and device counts
|
||||||
|
- `full` - Full infrastructure overview
|
||||||
|
|
||||||
|
## Instructions
|
||||||
|
|
||||||
|
You are a topology visualization assistant that queries NetBox and generates Mermaid diagrams.
|
||||||
|
|
||||||
|
### View: Rack Elevation
|
||||||
|
|
||||||
|
Generate a rack view showing devices and their positions.
|
||||||
|
|
||||||
|
**Data Collection:**
|
||||||
|
1. Use `dcim_list_racks` to find the rack by name
|
||||||
|
2. Use `dcim_list_devices` with `rack_id` filter to get devices in rack
|
||||||
|
3. For each device, note: `position`, `u_height`, `face`, `name`, `role`
|
||||||
|
|
||||||
|
**Mermaid Output:**
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph TB
|
||||||
|
subgraph rack["Rack: <rack-name> (U<height>)"]
|
||||||
|
direction TB
|
||||||
|
u42["U42: empty"]
|
||||||
|
u41["U41: empty"]
|
||||||
|
u40["U40: server-01 (Server)"]
|
||||||
|
u39["U39: server-01 (cont.)"]
|
||||||
|
u38["U38: switch-01 (Switch)"]
|
||||||
|
%% ... continue for all units
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
**For devices spanning multiple U:**
|
||||||
|
- Mark the top U with device name and role
|
||||||
|
- Mark subsequent Us as "(cont.)" for the same device
|
||||||
|
- Empty Us should show "empty"
|
||||||
|
|
||||||
|
### View: Network Topology
|
||||||
|
|
||||||
|
Generate a network diagram showing device connections.
|
||||||
|
|
||||||
|
**Data Collection:**
|
||||||
|
1. Use `dcim_list_sites` if no site specified (get all)
|
||||||
|
2. Use `dcim_list_devices` with optional `site_id` filter
|
||||||
|
3. Use `dcim_list_cables` to get all connections
|
||||||
|
4. Use `dcim_list_interfaces` for each device to understand port names
|
||||||
|
|
||||||
|
**Mermaid Output:**
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph TD
|
||||||
|
subgraph site1["Site: Home"]
|
||||||
|
router1[("core-router-01<br/>Router")]
|
||||||
|
switch1[["dist-switch-01<br/>Switch"]]
|
||||||
|
server1["web-server-01<br/>Server"]
|
||||||
|
server2["db-server-01<br/>Server"]
|
||||||
|
end
|
||||||
|
|
||||||
|
router1 -->|"eth0 - eth1"| switch1
|
||||||
|
switch1 -->|"gi0/1 - eth0"| server1
|
||||||
|
switch1 -->|"gi0/2 - eth0"| server2
|
||||||
|
```
|
||||||
|
|
||||||
|
**Node shapes by role:**
|
||||||
|
- Router: `[(" ")]` (cylinder/database shape)
|
||||||
|
- Switch: `[[ ]]` (double brackets)
|
||||||
|
- Server: `[ ]` (rectangle)
|
||||||
|
- Firewall: `{{ }}` (hexagon)
|
||||||
|
- Other: `[ ]` (rectangle)
|
||||||
|
|
||||||
|
**Edge labels:** Show interface names on both ends (A-side - B-side)
|
||||||
|
|
||||||
|
### View: Site Overview
|
||||||
|
|
||||||
|
Generate a site-level view showing racks and summary counts.
|
||||||
|
|
||||||
|
**Data Collection:**
|
||||||
|
1. Use `dcim_get_site` to get site details
|
||||||
|
2. Use `dcim_list_racks` with `site_id` filter
|
||||||
|
3. Use `dcim_list_devices` with `site_id` filter for counts per rack
|
||||||
|
|
||||||
|
**Mermaid Output:**
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph TB
|
||||||
|
subgraph site["Site: Headquarters"]
|
||||||
|
subgraph row1["Row 1"]
|
||||||
|
rack1["Rack A1<br/>12/42 U used<br/>5 devices"]
|
||||||
|
rack2["Rack A2<br/>20/42 U used<br/>8 devices"]
|
||||||
|
end
|
||||||
|
subgraph row2["Row 2"]
|
||||||
|
rack3["Rack B1<br/>8/42 U used<br/>3 devices"]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
### View: Full Infrastructure
|
||||||
|
|
||||||
|
Generate a high-level view of all sites and their relationships.
|
||||||
|
|
||||||
|
**Data Collection:**
|
||||||
|
1. Use `dcim_list_regions` to get hierarchy
|
||||||
|
2. Use `dcim_list_sites` to get all sites
|
||||||
|
3. Use `dcim_list_devices` with status filter for counts
|
||||||
|
|
||||||
|
**Mermaid Output:**
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph TB
|
||||||
|
subgraph region1["Region: Americas"]
|
||||||
|
site1["Headquarters<br/>3 racks, 25 devices"]
|
||||||
|
site2["Branch Office<br/>1 rack, 5 devices"]
|
||||||
|
end
|
||||||
|
subgraph region2["Region: Europe"]
|
||||||
|
site3["EU Datacenter<br/>10 racks, 100 devices"]
|
||||||
|
end
|
||||||
|
|
||||||
|
site1 -.->|"WAN Link"| site3
|
||||||
|
```
|
||||||
|
|
||||||
|
### Output Format
|
||||||
|
|
||||||
|
Always provide:
|
||||||
|
|
||||||
|
1. **Summary** - Brief description of what the diagram shows
|
||||||
|
2. **Mermaid Code Block** - The diagram code in a fenced code block
|
||||||
|
3. **Legend** - Explanation of shapes and colors used
|
||||||
|
4. **Data Notes** - Any data quality issues (e.g., devices without position, missing cables)
|
||||||
|
|
||||||
|
**Example Output:**
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## Network Topology: Home Site
|
||||||
|
|
||||||
|
This diagram shows the network connections between 4 devices at the Home site.
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph TD
|
||||||
|
router1[("core-router<br/>Router")]
|
||||||
|
switch1[["main-switch<br/>Switch"]]
|
||||||
|
server1["homelab-01<br/>Server"]
|
||||||
|
|
||||||
|
router1 -->|"eth0 - gi0/24"| switch1
|
||||||
|
switch1 -->|"gi0/1 - eth0"| server1
|
||||||
|
```
|
||||||
|
|
||||||
|
**Legend:**
|
||||||
|
- Cylinder shape: Routers
|
||||||
|
- Double brackets: Switches
|
||||||
|
- Rectangle: Servers
|
||||||
|
|
||||||
|
**Data Notes:**
|
||||||
|
- 1 device (nas-01) has no cable connections documented
|
||||||
|
```
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
- `/cmdb-topology rack server-rack-01` - Show devices in server-rack-01
|
||||||
|
- `/cmdb-topology network` - Show all network connections
|
||||||
|
- `/cmdb-topology network Home` - Show network topology for Home site only
|
||||||
|
- `/cmdb-topology site Headquarters` - Show rack overview for Headquarters
|
||||||
|
- `/cmdb-topology full` - Show full infrastructure overview
|
||||||
|
|
||||||
|
## User Request
|
||||||
|
|
||||||
|
$ARGUMENTS
|
||||||
226
plugins/cmdb-assistant/commands/ip-conflicts.md
Normal file
226
plugins/cmdb-assistant/commands/ip-conflicts.md
Normal file
@@ -0,0 +1,226 @@
|
|||||||
|
---
|
||||||
|
description: Detect IP address conflicts and overlapping prefixes in NetBox
|
||||||
|
---
|
||||||
|
|
||||||
|
# CMDB IP Conflict Detection
|
||||||
|
|
||||||
|
Scan NetBox IPAM data to identify IP address conflicts and overlapping prefixes.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
/ip-conflicts [scope]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Scopes:**
|
||||||
|
- `all` (default) - Full scan of all IP data
|
||||||
|
- `addresses` - Check for duplicate IP addresses only
|
||||||
|
- `prefixes` - Check for overlapping prefixes only
|
||||||
|
- `vrf <name>` - Scan specific VRF only
|
||||||
|
- `prefix <cidr>` - Scan within specific prefix
|
||||||
|
|
||||||
|
## Instructions
|
||||||
|
|
||||||
|
You are an IP conflict detection specialist that analyzes NetBox IPAM data for conflicts and issues.
|
||||||
|
|
||||||
|
### Conflict Types to Detect
|
||||||
|
|
||||||
|
#### 1. Duplicate IP Addresses
|
||||||
|
|
||||||
|
Multiple IP address records with the same address (within same VRF).
|
||||||
|
|
||||||
|
**Detection:**
|
||||||
|
1. Use `ipam_list_ip_addresses` to get all addresses
|
||||||
|
2. Group by address + VRF combination
|
||||||
|
3. Flag groups with more than one record
|
||||||
|
|
||||||
|
**Exception:** Anycast addresses may legitimately appear multiple times - check the `role` field for "anycast".
|
||||||
|
|
||||||
|
#### 2. Overlapping Prefixes
|
||||||
|
|
||||||
|
Prefixes that contain the same address space (within same VRF).
|
||||||
|
|
||||||
|
**Detection:**
|
||||||
|
1. Use `ipam_list_prefixes` to get all prefixes
|
||||||
|
2. For each prefix pair in the same VRF, check if one contains the other
|
||||||
|
3. Legitimate hierarchies should have proper parent-child relationships
|
||||||
|
|
||||||
|
**Legitimate Overlaps:**
|
||||||
|
- Parent/child prefix hierarchy (e.g., 10.0.0.0/8 contains 10.0.1.0/24)
|
||||||
|
- Different VRFs (isolated routing tables)
|
||||||
|
- Marked as "container" status
|
||||||
|
|
||||||
|
#### 3. IPs Outside Their Prefix
|
||||||
|
|
||||||
|
IP addresses that don't fall within any defined prefix.
|
||||||
|
|
||||||
|
**Detection:**
|
||||||
|
1. For each IP address, find the most specific prefix that contains it
|
||||||
|
2. Flag IPs with no matching prefix
|
||||||
|
|
||||||
|
#### 4. Prefix Overlap Across VRFs (Informational)
|
||||||
|
|
||||||
|
Same prefix appearing in multiple VRFs - not necessarily a conflict, but worth noting.
|
||||||
|
|
||||||
|
### MCP Tools
|
||||||
|
|
||||||
|
- `ipam_list_ip_addresses` - Get all IP addresses with filters:
|
||||||
|
- `address` - Filter by specific address
|
||||||
|
- `vrf_id` - Filter by VRF
|
||||||
|
- `parent` - Filter by parent prefix
|
||||||
|
- `status` - Filter by status
|
||||||
|
|
||||||
|
- `ipam_list_prefixes` - Get all prefixes with filters:
|
||||||
|
- `prefix` - Filter by prefix CIDR
|
||||||
|
- `vrf_id` - Filter by VRF
|
||||||
|
- `within` - Find prefixes within a parent
|
||||||
|
- `contains` - Find prefixes containing an address
|
||||||
|
|
||||||
|
- `ipam_list_vrfs` - List VRFs for context
|
||||||
|
- `ipam_get_ip_address` - Get detailed IP info including assigned device/interface
|
||||||
|
- `ipam_get_prefix` - Get detailed prefix info
|
||||||
|
|
||||||
|
### Workflow
|
||||||
|
|
||||||
|
1. **Data Collection**
|
||||||
|
- Fetch all IP addresses (or filtered set)
|
||||||
|
- Fetch all prefixes (or filtered set)
|
||||||
|
- Fetch VRFs for context
|
||||||
|
|
||||||
|
2. **Duplicate Detection**
|
||||||
|
- Build address map: `{address+vrf: [records]}`
|
||||||
|
- Filter for entries with >1 record
|
||||||
|
|
||||||
|
3. **Overlap Detection**
|
||||||
|
- For each VRF, compare prefixes pairwise
|
||||||
|
- Check using CIDR math: does prefix A contain prefix B or vice versa?
|
||||||
|
- Ignore legitimate hierarchies (status=container)
|
||||||
|
|
||||||
|
4. **Orphan IP Detection**
|
||||||
|
- For each IP, find containing prefix
|
||||||
|
- Flag IPs with no prefix match
|
||||||
|
|
||||||
|
5. **Generate Report**
|
||||||
|
|
||||||
|
### Report Format
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## IP Conflict Detection Report
|
||||||
|
|
||||||
|
**Generated:** [timestamp]
|
||||||
|
**Scope:** [scope parameter]
|
||||||
|
|
||||||
|
### Summary
|
||||||
|
|
||||||
|
| Check | Status | Count |
|
||||||
|
|-------|--------|-------|
|
||||||
|
| Duplicate IPs | [PASS/FAIL] | X |
|
||||||
|
| Overlapping Prefixes | [PASS/FAIL] | Y |
|
||||||
|
| Orphan IPs | [PASS/FAIL] | Z |
|
||||||
|
| Total Issues | - | N |
|
||||||
|
|
||||||
|
### Critical Issues
|
||||||
|
|
||||||
|
#### Duplicate IP Addresses
|
||||||
|
|
||||||
|
| Address | VRF | Count | Assigned To |
|
||||||
|
|---------|-----|-------|-------------|
|
||||||
|
| 10.0.1.50/24 | Global | 2 | server-01 (eth0), server-02 (eth0) |
|
||||||
|
| 192.168.1.100/24 | Global | 2 | router-01 (gi0/1), switch-01 (vlan10) |
|
||||||
|
|
||||||
|
**Impact:** IP conflicts cause network connectivity issues. Devices will have intermittent connectivity.
|
||||||
|
|
||||||
|
**Resolution:**
|
||||||
|
- Determine which device should have the IP
|
||||||
|
- Update or remove the duplicate assignment
|
||||||
|
- Consider IP reservation to prevent future conflicts
|
||||||
|
|
||||||
|
#### Overlapping Prefixes
|
||||||
|
|
||||||
|
| Prefix 1 | Prefix 2 | VRF | Type |
|
||||||
|
|----------|----------|-----|------|
|
||||||
|
| 10.0.0.0/24 | 10.0.0.0/25 | Global | Unstructured overlap |
|
||||||
|
| 192.168.0.0/16 | 192.168.1.0/24 | Production | Missing container flag |
|
||||||
|
|
||||||
|
**Impact:** Overlapping prefixes can cause routing ambiguity and IP management confusion.
|
||||||
|
|
||||||
|
**Resolution:**
|
||||||
|
- For legitimate hierarchies: Mark parent prefix as status="container"
|
||||||
|
- For accidental overlaps: Consolidate or re-address one prefix
|
||||||
|
|
||||||
|
### Warnings
|
||||||
|
|
||||||
|
#### IPs Without Prefix
|
||||||
|
|
||||||
|
| Address | VRF | Assigned To | Nearest Prefix |
|
||||||
|
|---------|-----|-------------|----------------|
|
||||||
|
| 172.16.5.10/24 | Global | server-03 (eth0) | None found |
|
||||||
|
|
||||||
|
**Impact:** IPs without a prefix bypass IPAM allocation controls.
|
||||||
|
|
||||||
|
**Resolution:**
|
||||||
|
- Create appropriate prefix to contain the IP
|
||||||
|
- Or update IP to correct address within existing prefix
|
||||||
|
|
||||||
|
### Informational
|
||||||
|
|
||||||
|
#### Same Prefix in Multiple VRFs
|
||||||
|
|
||||||
|
| Prefix | VRFs | Purpose |
|
||||||
|
|--------|------|---------|
|
||||||
|
| 10.0.0.0/24 | Global, DMZ, Internal | [Check if intentional] |
|
||||||
|
|
||||||
|
### Statistics
|
||||||
|
|
||||||
|
| Metric | Value |
|
||||||
|
|--------|-------|
|
||||||
|
| Total IP Addresses | X |
|
||||||
|
| Total Prefixes | Y |
|
||||||
|
| Total VRFs | Z |
|
||||||
|
| Utilization (IPs/Prefix space) | W% |
|
||||||
|
|
||||||
|
### Remediation Commands
|
||||||
|
|
||||||
|
```
|
||||||
|
# Remove duplicate IP (keep server-01's assignment)
|
||||||
|
ipam_delete_ip_address id=123
|
||||||
|
|
||||||
|
# Mark prefix as container
|
||||||
|
ipam_update_prefix id=456 status=container
|
||||||
|
|
||||||
|
# Create missing prefix for orphan IP
|
||||||
|
ipam_create_prefix prefix=172.16.5.0/24 status=active
|
||||||
|
```
|
||||||
|
```
|
||||||
|
|
||||||
|
### CIDR Math Reference
|
||||||
|
|
||||||
|
For overlap detection, use these rules:
|
||||||
|
- Prefix A **contains** Prefix B if: A.network <= B.network AND A.broadcast >= B.broadcast
|
||||||
|
- Two prefixes **overlap** if: A.network <= B.broadcast AND B.network <= A.broadcast
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
- 10.0.0.0/8 contains 10.0.1.0/24 (legitimate hierarchy)
|
||||||
|
- 10.0.0.0/24 and 10.0.0.128/25 overlap (10.0.0.128/25 is within 10.0.0.0/24)
|
||||||
|
|
||||||
|
### Severity Levels
|
||||||
|
|
||||||
|
| Issue | Severity | Description |
|
||||||
|
|-------|----------|-------------|
|
||||||
|
| Duplicate IP (same interface type) | CRITICAL | Active conflict, causes outages |
|
||||||
|
| Duplicate IP (different roles) | HIGH | Potential conflict |
|
||||||
|
| Overlapping prefixes (same status) | HIGH | IPAM management issue |
|
||||||
|
| Overlapping prefixes (container ok) | LOW | May need status update |
|
||||||
|
| Orphan IP | MEDIUM | Bypasses IPAM controls |
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
- `/ip-conflicts` - Full scan for all conflicts
|
||||||
|
- `/ip-conflicts addresses` - Check only for duplicate IPs
|
||||||
|
- `/ip-conflicts prefixes` - Check only for overlapping prefixes
|
||||||
|
- `/ip-conflicts vrf Production` - Scan only Production VRF
|
||||||
|
- `/ip-conflicts prefix 10.0.0.0/8` - Scan within specific prefix range
|
||||||
|
|
||||||
|
## User Request
|
||||||
|
|
||||||
|
$ARGUMENTS
|
||||||
21
plugins/cmdb-assistant/hooks/hooks.json
Normal file
21
plugins/cmdb-assistant/hooks/hooks.json
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"hooks": {
|
||||||
|
"SessionStart": [
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/startup-check.sh"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"PreToolUse": [
|
||||||
|
{
|
||||||
|
"matcher": "mcp__plugin_cmdb-assistant_netbox__virt_create|mcp__plugin_cmdb-assistant_netbox__virt_update|mcp__plugin_cmdb-assistant_netbox__dcim_create|mcp__plugin_cmdb-assistant_netbox__dcim_update",
|
||||||
|
"hooks": [
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/validate-input.sh"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
66
plugins/cmdb-assistant/hooks/startup-check.sh
Executable file
66
plugins/cmdb-assistant/hooks/startup-check.sh
Executable file
@@ -0,0 +1,66 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# cmdb-assistant SessionStart hook
|
||||||
|
# Tests NetBox API connectivity and checks for data quality issues
|
||||||
|
# All output MUST have [cmdb-assistant] prefix
|
||||||
|
# Non-blocking: always exits 0
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
PREFIX="[cmdb-assistant]"
|
||||||
|
|
||||||
|
# Load NetBox configuration
|
||||||
|
NETBOX_CONFIG="$HOME/.config/claude/netbox.env"
|
||||||
|
|
||||||
|
if [[ ! -f "$NETBOX_CONFIG" ]]; then
|
||||||
|
echo "$PREFIX NetBox not configured - run /cmdb-assistant:initial-setup"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Source config
|
||||||
|
source "$NETBOX_CONFIG"
|
||||||
|
|
||||||
|
# Validate required variables
|
||||||
|
if [[ -z "${NETBOX_API_URL:-}" ]] || [[ -z "${NETBOX_API_TOKEN:-}" ]]; then
|
||||||
|
echo "$PREFIX Missing NETBOX_API_URL or NETBOX_API_TOKEN in config"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Quick API connectivity test (5s timeout)
|
||||||
|
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" -m 5 \
|
||||||
|
-H "Authorization: Token $NETBOX_API_TOKEN" \
|
||||||
|
-H "Accept: application/json" \
|
||||||
|
"${NETBOX_API_URL}/" 2>/dev/null || echo "000")
|
||||||
|
|
||||||
|
if [[ "$HTTP_CODE" == "000" ]]; then
|
||||||
|
echo "$PREFIX NetBox API unreachable (timeout/connection error)"
|
||||||
|
exit 0
|
||||||
|
elif [[ "$HTTP_CODE" != "200" ]]; then
|
||||||
|
echo "$PREFIX NetBox API returned HTTP $HTTP_CODE - check credentials"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for VMs without site assignment (data quality)
|
||||||
|
VMS_RESPONSE=$(curl -s -m 5 \
|
||||||
|
-H "Authorization: Token $NETBOX_API_TOKEN" \
|
||||||
|
-H "Accept: application/json" \
|
||||||
|
"${NETBOX_API_URL}/virtualization/virtual-machines/?site__isnull=true&limit=1" 2>/dev/null || echo '{"count":0}')
|
||||||
|
|
||||||
|
VMS_NO_SITE=$(echo "$VMS_RESPONSE" | grep -o '"count":[0-9]*' | cut -d: -f2 || echo "0")
|
||||||
|
|
||||||
|
if [[ "$VMS_NO_SITE" -gt 0 ]]; then
|
||||||
|
echo "$PREFIX $VMS_NO_SITE VMs without site assignment - run /cmdb-audit for details"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for devices without platform
|
||||||
|
DEVICES_RESPONSE=$(curl -s -m 5 \
|
||||||
|
-H "Authorization: Token $NETBOX_API_TOKEN" \
|
||||||
|
-H "Accept: application/json" \
|
||||||
|
"${NETBOX_API_URL}/dcim/devices/?platform__isnull=true&limit=1" 2>/dev/null || echo '{"count":0}')
|
||||||
|
|
||||||
|
DEVICES_NO_PLATFORM=$(echo "$DEVICES_RESPONSE" | grep -o '"count":[0-9]*' | cut -d: -f2 || echo "0")
|
||||||
|
|
||||||
|
if [[ "$DEVICES_NO_PLATFORM" -gt 0 ]]; then
|
||||||
|
echo "$PREFIX $DEVICES_NO_PLATFORM devices without platform - consider updating"
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit 0
|
||||||
79
plugins/cmdb-assistant/hooks/validate-input.sh
Executable file
79
plugins/cmdb-assistant/hooks/validate-input.sh
Executable file
@@ -0,0 +1,79 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# cmdb-assistant PreToolUse validation hook
|
||||||
|
# Validates input parameters for create/update operations
|
||||||
|
# NON-BLOCKING: Warns but allows operation to proceed (always exits 0)
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
PREFIX="[cmdb-assistant]"
|
||||||
|
|
||||||
|
# Read tool input from stdin
|
||||||
|
INPUT=$(cat)
|
||||||
|
|
||||||
|
# Extract tool name from the input
|
||||||
|
# Format varies, try to find tool_name or name field
|
||||||
|
TOOL_NAME=""
|
||||||
|
if echo "$INPUT" | grep -q '"tool_name"'; then
|
||||||
|
TOOL_NAME=$(echo "$INPUT" | grep -o '"tool_name"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"\([^"]*\)"$/\1/' || true)
|
||||||
|
elif echo "$INPUT" | grep -q '"name"'; then
|
||||||
|
TOOL_NAME=$(echo "$INPUT" | grep -o '"name"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"\([^"]*\)"$/\1/' || true)
|
||||||
|
fi
|
||||||
|
|
||||||
|
# If we can't determine the tool, exit silently
|
||||||
|
if [[ -z "$TOOL_NAME" ]]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# VM creation/update validation
|
||||||
|
if echo "$TOOL_NAME" | grep -qE "virt_create_vm|virt_create_virtual_machine|virt_update_vm|virt_update_virtual_machine"; then
|
||||||
|
WARNINGS=()
|
||||||
|
|
||||||
|
# Check for missing site
|
||||||
|
if ! echo "$INPUT" | grep -qE '"site"[[:space:]]*:[[:space:]]*[0-9]'; then
|
||||||
|
WARNINGS+=("no site assigned")
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for missing tenant
|
||||||
|
if ! echo "$INPUT" | grep -qE '"tenant"[[:space:]]*:[[:space:]]*[0-9]'; then
|
||||||
|
WARNINGS+=("no tenant assigned")
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for missing platform
|
||||||
|
if ! echo "$INPUT" | grep -qE '"platform"[[:space:]]*:[[:space:]]*[0-9]'; then
|
||||||
|
WARNINGS+=("no platform assigned")
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ${#WARNINGS[@]} -gt 0 ]]; then
|
||||||
|
echo "$PREFIX VM best practice: $(IFS=', '; echo "${WARNINGS[*]}") - consider assigning for data quality"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Device creation/update validation
|
||||||
|
if echo "$TOOL_NAME" | grep -qE "dcim_create_device|dcim_update_device"; then
|
||||||
|
WARNINGS=()
|
||||||
|
|
||||||
|
# Check for missing platform
|
||||||
|
if ! echo "$INPUT" | grep -qE '"platform"[[:space:]]*:[[:space:]]*[0-9]'; then
|
||||||
|
WARNINGS+=("no platform assigned")
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for missing tenant
|
||||||
|
if ! echo "$INPUT" | grep -qE '"tenant"[[:space:]]*:[[:space:]]*[0-9]'; then
|
||||||
|
WARNINGS+=("no tenant assigned")
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ${#WARNINGS[@]} -gt 0 ]]; then
|
||||||
|
echo "$PREFIX Device best practice: $(IFS=', '; echo "${WARNINGS[*]}") - consider assigning"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Cluster creation validation
|
||||||
|
if echo "$TOOL_NAME" | grep -qE "virt_create_cluster"; then
|
||||||
|
# Check for missing site scope
|
||||||
|
if ! echo "$INPUT" | grep -qE '"site"[[:space:]]*:[[:space:]]*[0-9]'; then
|
||||||
|
echo "$PREFIX Cluster best practice: no site scope - clusters should be scoped to a site"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Always allow operation (non-blocking)
|
||||||
|
exit 0
|
||||||
249
plugins/cmdb-assistant/skills/netbox-patterns/SKILL.md
Normal file
249
plugins/cmdb-assistant/skills/netbox-patterns/SKILL.md
Normal file
@@ -0,0 +1,249 @@
|
|||||||
|
---
|
||||||
|
description: NetBox best practices for data quality and consistency based on official NetBox Labs guidelines
|
||||||
|
---
|
||||||
|
|
||||||
|
# NetBox Best Practices Skill
|
||||||
|
|
||||||
|
Reference documentation for proper NetBox data modeling, following official NetBox Labs guidelines.
|
||||||
|
|
||||||
|
## CRITICAL: Dependency Order
|
||||||
|
|
||||||
|
Objects must be created in this order due to foreign key dependencies. Creating objects out of order results in validation errors.
|
||||||
|
|
||||||
|
```
|
||||||
|
1. ORGANIZATION (no dependencies)
|
||||||
|
├── Tenant Groups
|
||||||
|
├── Tenants (optional: Tenant Group)
|
||||||
|
├── Regions
|
||||||
|
├── Site Groups
|
||||||
|
└── Tags
|
||||||
|
|
||||||
|
2. SITES AND LOCATIONS
|
||||||
|
├── Sites (optional: Region, Site Group, Tenant)
|
||||||
|
└── Locations (requires: Site, optional: parent Location)
|
||||||
|
|
||||||
|
3. DCIM PREREQUISITES
|
||||||
|
├── Manufacturers
|
||||||
|
├── Device Types (requires: Manufacturer)
|
||||||
|
├── Platforms
|
||||||
|
├── Device Roles
|
||||||
|
└── Rack Roles
|
||||||
|
|
||||||
|
4. RACKS
|
||||||
|
└── Racks (requires: Site, optional: Location, Rack Role, Tenant)
|
||||||
|
|
||||||
|
5. DEVICES
|
||||||
|
├── Devices (requires: Device Type, Role, Site; optional: Rack, Location)
|
||||||
|
└── Interfaces (requires: Device)
|
||||||
|
|
||||||
|
6. VIRTUALIZATION
|
||||||
|
├── Cluster Types
|
||||||
|
├── Cluster Groups
|
||||||
|
├── Clusters (requires: Cluster Type, optional: Site)
|
||||||
|
├── Virtual Machines (requires: Cluster OR Site)
|
||||||
|
└── VM Interfaces (requires: Virtual Machine)
|
||||||
|
|
||||||
|
7. IPAM
|
||||||
|
├── VRFs (optional: Tenant)
|
||||||
|
├── Prefixes (optional: VRF, Site, Tenant)
|
||||||
|
├── IP Addresses (optional: VRF, Tenant, Interface)
|
||||||
|
└── VLANs (optional: Site, Tenant)
|
||||||
|
|
||||||
|
8. CONNECTIONS (last)
|
||||||
|
└── Cables (requires: endpoints)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key Rule:** NEVER create a VM before its cluster exists. NEVER create a device before its site exists.
|
||||||
|
|
||||||
|
## HIGH: Site Assignment
|
||||||
|
|
||||||
|
**All infrastructure objects should have a site:**
|
||||||
|
|
||||||
|
| Object Type | Site Requirement |
|
||||||
|
|-------------|------------------|
|
||||||
|
| Devices | **REQUIRED** |
|
||||||
|
| Racks | **REQUIRED** |
|
||||||
|
| VMs | RECOMMENDED (via cluster or direct) |
|
||||||
|
| Clusters | RECOMMENDED |
|
||||||
|
| Prefixes | RECOMMENDED |
|
||||||
|
| VLANs | RECOMMENDED |
|
||||||
|
|
||||||
|
**Why Sites Matter:**
|
||||||
|
- Location-based queries and filtering
|
||||||
|
- Power and capacity budgeting
|
||||||
|
- Physical inventory tracking
|
||||||
|
- Compliance and audit requirements
|
||||||
|
|
||||||
|
## HIGH: Tenant Usage
|
||||||
|
|
||||||
|
Use tenants for logical resource separation:
|
||||||
|
|
||||||
|
**When to Use Tenants:**
|
||||||
|
- Multi-team environments (assign resources to teams)
|
||||||
|
- Multi-customer scenarios (MSP, hosting)
|
||||||
|
- Cost allocation requirements
|
||||||
|
- Access control boundaries
|
||||||
|
|
||||||
|
**Apply Tenants To:**
|
||||||
|
- Sites (who owns the physical location)
|
||||||
|
- Devices (who operates the hardware)
|
||||||
|
- VMs (who owns the workload)
|
||||||
|
- Prefixes (who owns the IP space)
|
||||||
|
- VLANs (who owns the network segment)
|
||||||
|
|
||||||
|
## HIGH: Platform Tracking
|
||||||
|
|
||||||
|
Platforms track OS/runtime information for automation and lifecycle management.
|
||||||
|
|
||||||
|
**Platform Examples:**
|
||||||
|
| Device Type | Platform Examples |
|
||||||
|
|-------------|-------------------|
|
||||||
|
| Servers | Ubuntu 24.04, Windows Server 2022, RHEL 9 |
|
||||||
|
| Network | Cisco IOS 17.x, Junos 23.x, Arista EOS |
|
||||||
|
| Raspberry Pi | Raspberry Pi OS (Bookworm), Ubuntu Server ARM |
|
||||||
|
| Containers | Docker Container (as runtime indicator) |
|
||||||
|
|
||||||
|
**Benefits:**
|
||||||
|
- Vulnerability tracking (CVE correlation)
|
||||||
|
- Configuration management integration
|
||||||
|
- Lifecycle management (EOL tracking)
|
||||||
|
- Automation targeting
|
||||||
|
|
||||||
|
## MEDIUM: Tag Conventions
|
||||||
|
|
||||||
|
Use tags for cross-cutting classification that spans object types.
|
||||||
|
|
||||||
|
**Recommended Tag Patterns:**
|
||||||
|
|
||||||
|
| Pattern | Purpose | Examples |
|
||||||
|
|---------|---------|----------|
|
||||||
|
| `env:*` | Environment classification | `env:production`, `env:staging`, `env:development` |
|
||||||
|
| `app:*` | Application grouping | `app:web`, `app:database`, `app:monitoring` |
|
||||||
|
| `team:*` | Ownership | `team:platform`, `team:infra`, `team:devops` |
|
||||||
|
| `backup:*` | Backup policy | `backup:daily`, `backup:weekly`, `backup:none` |
|
||||||
|
| `monitoring:*` | Monitoring level | `monitoring:critical`, `monitoring:standard` |
|
||||||
|
|
||||||
|
**Tags vs Custom Fields:**
|
||||||
|
- Tags: Cross-object classification, multiple values, filtering
|
||||||
|
- Custom Fields: Object-specific structured data, single values, reporting
|
||||||
|
|
||||||
|
## MEDIUM: Naming Conventions
|
||||||
|
|
||||||
|
Consistent naming improves searchability and automation compatibility.
|
||||||
|
|
||||||
|
**Recommended Patterns:**
|
||||||
|
|
||||||
|
| Object Type | Pattern | Examples |
|
||||||
|
|-------------|---------|----------|
|
||||||
|
| Devices | `{role}-{location}-{number}` | `web-dc1-01`, `db-cloud-02`, `fw-home-01` |
|
||||||
|
| VMs | `{env}-{app}-{number}` | `prod-api-01`, `dev-worker-03` |
|
||||||
|
| Clusters | `{site}-{type}` | `dc1-vmware`, `home-docker` |
|
||||||
|
| Prefixes | Include purpose in description | "Production web tier /24" |
|
||||||
|
| VLANs | `{site}-{function}` | `dc1-mgmt`, `home-iot` |
|
||||||
|
|
||||||
|
**Avoid:**
|
||||||
|
- Inconsistent casing (mixing `HotServ` and `hotserv`)
|
||||||
|
- Mixed separators (mixing `hhl_cluster` and `media-cluster`)
|
||||||
|
- Generic names without context (`server1`, `vm2`)
|
||||||
|
- Special characters other than hyphen
|
||||||
|
|
||||||
|
## MEDIUM: Role Consolidation
|
||||||
|
|
||||||
|
Avoid role fragmentation - use general roles with platform/tags for specificity.
|
||||||
|
|
||||||
|
**Instead of:**
|
||||||
|
```
|
||||||
|
nginx-web-server
|
||||||
|
apache-web-server
|
||||||
|
web-server-frontend
|
||||||
|
web-server-api
|
||||||
|
```
|
||||||
|
|
||||||
|
**Use:**
|
||||||
|
```
|
||||||
|
web-server (role) + platform (nginx/apache) + tags (frontend, api)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Recommended Role Categories:**
|
||||||
|
|
||||||
|
| Category | Roles |
|
||||||
|
|----------|-------|
|
||||||
|
| Infrastructure | `hypervisor`, `storage-server`, `network-device`, `firewall` |
|
||||||
|
| Compute | `application-server`, `database-server`, `web-server`, `api-server` |
|
||||||
|
| Services | `container-host`, `load-balancer`, `monitoring-server`, `backup-server` |
|
||||||
|
| Development | `development-workstation`, `ci-runner`, `build-server` |
|
||||||
|
| Containers | `reverse-proxy`, `database`, `cache`, `queue`, `worker` |
|
||||||
|
|
||||||
|
## Docker Containers as VMs
|
||||||
|
|
||||||
|
NetBox's Virtualization module can model Docker containers:
|
||||||
|
|
||||||
|
**Approach:**
|
||||||
|
1. Create device for physical Docker host
|
||||||
|
2. Create cluster (type: "Docker Compose" or "Docker Swarm")
|
||||||
|
3. Associate cluster with host device
|
||||||
|
4. Create VMs for each container in the cluster
|
||||||
|
|
||||||
|
**VM Fields for Containers:**
|
||||||
|
- `name`: Container name (e.g., `media_jellyfin`)
|
||||||
|
- `role`: Container function (e.g., `Media Server`)
|
||||||
|
- `vcpus`: CPU limit/shares
|
||||||
|
- `memory`: Memory limit (MB)
|
||||||
|
- `disk`: Volume size estimate
|
||||||
|
- `description`: Container purpose
|
||||||
|
- `comments`: Image, ports, volumes, dependencies
|
||||||
|
|
||||||
|
**This is a pragmatic modeling choice** - containers aren't VMs, but the Virtualization module is the closest fit for tracking container workloads.
|
||||||
|
|
||||||
|
## Primary IP Workflow
|
||||||
|
|
||||||
|
To set a device/VM's primary IP:
|
||||||
|
|
||||||
|
1. Create interface on device/VM
|
||||||
|
2. Create IP address assigned to interface
|
||||||
|
3. Set IP as `primary_ip4` or `primary_ip6` on device/VM
|
||||||
|
|
||||||
|
**Why Primary IP Matters:**
|
||||||
|
- Used for device connectivity checks
|
||||||
|
- Displayed in device list views
|
||||||
|
- Used by automation tools (NAPALM, Ansible)
|
||||||
|
- Required for many integrations
|
||||||
|
|
||||||
|
## Data Quality Checklist
|
||||||
|
|
||||||
|
Before closing a sprint or audit:
|
||||||
|
|
||||||
|
- [ ] All VMs have site assignment (direct or via cluster)
|
||||||
|
- [ ] All VMs have tenant assignment
|
||||||
|
- [ ] All active devices have platform
|
||||||
|
- [ ] All active devices have primary IP
|
||||||
|
- [ ] Naming follows conventions
|
||||||
|
- [ ] No orphaned prefixes (allocated but unused)
|
||||||
|
- [ ] Tags applied consistently
|
||||||
|
- [ ] Clusters scoped to sites
|
||||||
|
- [ ] Roles not overly fragmented
|
||||||
|
|
||||||
|
## MCP Tool Reference
|
||||||
|
|
||||||
|
**Dependency Order for Creation:**
|
||||||
|
```
|
||||||
|
1. dcim_create_site
|
||||||
|
2. dcim_create_manufacturer
|
||||||
|
3. dcim_create_device_type
|
||||||
|
4. dcim_create_device_role
|
||||||
|
5. dcim_create_platform
|
||||||
|
6. dcim_create_device
|
||||||
|
7. virt_create_cluster_type
|
||||||
|
8. virt_create_cluster
|
||||||
|
9. virt_create_vm
|
||||||
|
10. dcim_create_interface / virt_create_vm_interface
|
||||||
|
11. ipam_create_ip_address
|
||||||
|
12. dcim_update_device (set primary_ip4)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Lookup Before Create:**
|
||||||
|
Always check if object exists before creating to avoid duplicates:
|
||||||
|
```
|
||||||
|
1. dcim_list_devices name=<hostname>
|
||||||
|
2. If exists, update; if not, create
|
||||||
|
```
|
||||||
22
plugins/contract-validator/.claude-plugin/plugin.json
Normal file
22
plugins/contract-validator/.claude-plugin/plugin.json
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"name": "contract-validator",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Cross-plugin compatibility validation and Claude.md agent verification",
|
||||||
|
"author": {
|
||||||
|
"name": "Leo Miranda",
|
||||||
|
"email": "leobmiranda@gmail.com"
|
||||||
|
},
|
||||||
|
"homepage": "https://gitea.hotserv.cloud/personal-projects/leo-claude-mktplace/src/branch/main/plugins/contract-validator/README.md",
|
||||||
|
"repository": "https://gitea.hotserv.cloud/personal-projects/leo-claude-mktplace.git",
|
||||||
|
"license": "MIT",
|
||||||
|
"keywords": [
|
||||||
|
"validation",
|
||||||
|
"contracts",
|
||||||
|
"compatibility",
|
||||||
|
"agents",
|
||||||
|
"interfaces",
|
||||||
|
"cross-plugin"
|
||||||
|
],
|
||||||
|
"commands": ["./commands/"],
|
||||||
|
"mcpServers": ["./.mcp.json"]
|
||||||
|
}
|
||||||
9
plugins/contract-validator/.mcp.json
Normal file
9
plugins/contract-validator/.mcp.json
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"mcpServers": {
|
||||||
|
"contract-validator": {
|
||||||
|
"type": "stdio",
|
||||||
|
"command": "${CLAUDE_PLUGIN_ROOT}/mcp-servers/contract-validator/run.sh",
|
||||||
|
"args": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
169
plugins/contract-validator/README.md
Normal file
169
plugins/contract-validator/README.md
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
# contract-validator Plugin
|
||||||
|
|
||||||
|
Cross-plugin compatibility validation and CLAUDE.md agent verification for Claude Code plugin marketplaces.
|
||||||
|
|
||||||
|
## Problem Statement
|
||||||
|
|
||||||
|
As plugin marketplaces grow, several compatibility issues emerge:
|
||||||
|
|
||||||
|
- **Command conflicts**: Multiple plugins defining the same slash command (e.g., `/initial-setup`)
|
||||||
|
- **Tool name overlaps**: Different plugins using identical tool names with incompatible interfaces
|
||||||
|
- **Undocumented dependencies**: Agents referencing tools that don't exist
|
||||||
|
- **Broken data flows**: Agent sequences that expect outputs not produced by prior steps
|
||||||
|
|
||||||
|
Contract-validator solves these by parsing plugin interfaces and validating compatibility before runtime.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Interface Parsing**: Extract commands, agents, and tools from plugin README.md files
|
||||||
|
- **Agent Extraction**: Parse CLAUDE.md Four-Agent Model tables and Agents sections
|
||||||
|
- **Compatibility Checks**: Pairwise validation between all plugins in a marketplace
|
||||||
|
- **Data Flow Validation**: Verify agent tool sequences have valid data producers/consumers
|
||||||
|
- **Dependency Visualization**: Generate Mermaid flowcharts showing plugin relationships
|
||||||
|
- **Comprehensive Reports**: Markdown or JSON reports with actionable suggestions
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
This plugin is part of the leo-claude-mktplace. Install via:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# From marketplace
|
||||||
|
claude plugins install leo-claude-mktplace/contract-validator
|
||||||
|
|
||||||
|
# Setup MCP server venv
|
||||||
|
cd ~/.claude/plugins/marketplaces/leo-claude-mktplace/mcp-servers/contract-validator
|
||||||
|
python -m venv .venv
|
||||||
|
source .venv/bin/activate
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
|---------|-------------|
|
||||||
|
| `/initial-setup` | Interactive setup wizard |
|
||||||
|
| `/validate-contracts` | Full marketplace compatibility validation |
|
||||||
|
| `/check-agent` | Validate single agent definition |
|
||||||
|
| `/list-interfaces` | Show all plugin interfaces |
|
||||||
|
| `/dependency-graph` | Generate Mermaid flowchart of plugin dependencies |
|
||||||
|
|
||||||
|
## Agents
|
||||||
|
|
||||||
|
| Agent | Description |
|
||||||
|
|-------|-------------|
|
||||||
|
| `full-validation` | Complete cross-plugin compatibility validation |
|
||||||
|
| `agent-check` | Single agent definition verification |
|
||||||
|
|
||||||
|
## Tools Summary
|
||||||
|
|
||||||
|
### Parse Tools (2)
|
||||||
|
- `parse_plugin_interface` - Extract interface from plugin README.md
|
||||||
|
- `parse_claude_md_agents` - Extract agents from CLAUDE.md
|
||||||
|
|
||||||
|
### Validation Tools (3)
|
||||||
|
- `validate_compatibility` - Check two plugins for conflicts
|
||||||
|
- `validate_agent_refs` - Verify agent tool references exist
|
||||||
|
- `validate_data_flow` - Check data flow through agent sequences
|
||||||
|
|
||||||
|
### Report Tools (2)
|
||||||
|
- `generate_compatibility_report` - Full marketplace validation report
|
||||||
|
- `list_issues` - Filter issues by severity or type
|
||||||
|
|
||||||
|
## Example Workflow
|
||||||
|
|
||||||
|
```
|
||||||
|
/validate-contracts ~/claude-plugins-work
|
||||||
|
|
||||||
|
# Output:
|
||||||
|
# Contract Validation Report
|
||||||
|
#
|
||||||
|
# | Metric | Count |
|
||||||
|
# |------------|-------|
|
||||||
|
# | Plugins | 12 |
|
||||||
|
# | Commands | 39 |
|
||||||
|
# | Tools | 32 |
|
||||||
|
# | **Issues** | **7** |
|
||||||
|
# | - Errors | 3 |
|
||||||
|
# | - Warnings | 0 |
|
||||||
|
# | - Info | 4 |
|
||||||
|
#
|
||||||
|
# ## Issues Found
|
||||||
|
# [ERROR] Command conflict: projman and data-platform both define /initial-setup
|
||||||
|
# [ERROR] Command conflict: projman and pr-review both define /initial-setup
|
||||||
|
# ...
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
/check-agent Planner ./CLAUDE.md
|
||||||
|
|
||||||
|
# Output:
|
||||||
|
# Agent: Planner
|
||||||
|
# Status: VALID
|
||||||
|
#
|
||||||
|
# Tool References Found (3):
|
||||||
|
# - create_issue ✓
|
||||||
|
# - search_lessons ✓
|
||||||
|
# - get_execution_order ✓
|
||||||
|
#
|
||||||
|
# Data Flow: No issues detected
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
/dependency-graph
|
||||||
|
|
||||||
|
# Output: Mermaid flowchart showing:
|
||||||
|
# - Plugins grouped by shared MCP servers
|
||||||
|
# - Data flow from data-platform to viz-platform
|
||||||
|
# - Required vs optional dependencies
|
||||||
|
# - Command counts per plugin
|
||||||
|
```
|
||||||
|
|
||||||
|
## Issue Types
|
||||||
|
|
||||||
|
| Type | Severity | Description |
|
||||||
|
|------|----------|-------------|
|
||||||
|
| `interface_mismatch` | ERROR | Command name conflict between plugins |
|
||||||
|
| `missing_tool` | ERROR | Agent references non-existent tool |
|
||||||
|
| `interface_mismatch` | WARNING | Tool name overlap (different plugins) |
|
||||||
|
| `optional_dependency` | WARNING | Agent uses tool from non-required plugin |
|
||||||
|
| `undeclared_output` | INFO | Agent has no documented tool references |
|
||||||
|
|
||||||
|
## Parsed Interface Structure
|
||||||
|
|
||||||
|
When parsing a plugin README.md, the following structure is extracted:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"plugin_name": "data-platform",
|
||||||
|
"description": "Data engineering tools...",
|
||||||
|
"commands": [
|
||||||
|
{"name": "/ingest", "description": "Load data..."}
|
||||||
|
],
|
||||||
|
"agents": [
|
||||||
|
{"name": "data-analysis", "description": "..."}
|
||||||
|
],
|
||||||
|
"tools": [
|
||||||
|
{"name": "read_csv", "category": "pandas"}
|
||||||
|
],
|
||||||
|
"tool_categories": {
|
||||||
|
"pandas": ["read_csv", "to_csv", ...],
|
||||||
|
"PostgreSQL": ["pg_query", ...]
|
||||||
|
},
|
||||||
|
"features": ["pandas Operations", "PostgreSQL/PostGIS", ...]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
### For Plugin Authors
|
||||||
|
|
||||||
|
1. **Use unique command names**: Prefix with plugin name if generic (e.g., `/data-setup` vs `/initial-setup`)
|
||||||
|
2. **Document all tools**: Include tool names in README.md with backticks
|
||||||
|
3. **Specify tool categories**: Use `### Category (N tools)` headers
|
||||||
|
4. **Declare agent tools**: List tools used by agents in their definitions
|
||||||
|
|
||||||
|
### For Marketplace Maintainers
|
||||||
|
|
||||||
|
1. **Run validation before merging**: Use `/validate-contracts` in CI/CD
|
||||||
|
2. **Review warnings**: Tool overlaps may indicate design issues
|
||||||
|
3. **Track issues over time**: Use JSON format for programmatic tracking
|
||||||
90
plugins/contract-validator/agents/agent-check.md
Normal file
90
plugins/contract-validator/agents/agent-check.md
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
# Agent Check Agent
|
||||||
|
|
||||||
|
You are an agent definition validator. Your role is to verify that a specific agent's tool references and data flow are valid.
|
||||||
|
|
||||||
|
## Capabilities
|
||||||
|
|
||||||
|
- Parse agent definitions from CLAUDE.md
|
||||||
|
- Validate tool references against available plugins
|
||||||
|
- Verify data flow patterns through agent sequences
|
||||||
|
- Provide detailed validation feedback
|
||||||
|
|
||||||
|
## Available Tools
|
||||||
|
|
||||||
|
### Parsing
|
||||||
|
- `parse_claude_md_agents` - Extract all agents from CLAUDE.md
|
||||||
|
- `parse_plugin_interface` - Extract interface from plugin (for available tools)
|
||||||
|
|
||||||
|
### Validation
|
||||||
|
- `validate_agent_refs` - Check agent tool references exist
|
||||||
|
- `validate_data_flow` - Verify data flow through agent sequence
|
||||||
|
|
||||||
|
### Reporting
|
||||||
|
- `list_issues` - Filter issues for this agent
|
||||||
|
|
||||||
|
## Workflow
|
||||||
|
|
||||||
|
1. **Locate the agent**:
|
||||||
|
- Use `parse_claude_md_agents` on specified CLAUDE.md
|
||||||
|
- Find agent by name (case-insensitive match)
|
||||||
|
- If not found, list available agents
|
||||||
|
|
||||||
|
2. **Gather available tools**:
|
||||||
|
- Scan plugins directory for available plugins
|
||||||
|
- For each plugin, use `parse_plugin_interface`
|
||||||
|
- Build set of all available tool names
|
||||||
|
|
||||||
|
3. **Validate tool references**:
|
||||||
|
- Use `validate_agent_refs` with agent name and plugin paths
|
||||||
|
- Report found tools (valid references)
|
||||||
|
- Report missing tools (errors)
|
||||||
|
- Suggest corrections for typos
|
||||||
|
|
||||||
|
4. **Validate data flow**:
|
||||||
|
- Use `validate_data_flow` to check sequence
|
||||||
|
- Verify data producers precede consumers
|
||||||
|
- Check for orphaned data references
|
||||||
|
- Identify potential flow issues
|
||||||
|
|
||||||
|
5. **Report findings**:
|
||||||
|
- Agent name and source file
|
||||||
|
- Responsibilities extracted
|
||||||
|
- Tool references: found vs missing
|
||||||
|
- Data flow validation results
|
||||||
|
- Suggestions for improvement
|
||||||
|
|
||||||
|
## Validation Rules
|
||||||
|
|
||||||
|
### Tool Reference Rules
|
||||||
|
- All referenced tools must exist in available plugins
|
||||||
|
- Tool names are case-sensitive
|
||||||
|
- Partial matches suggest typos
|
||||||
|
|
||||||
|
### Data Flow Rules
|
||||||
|
- Data producers (read_csv, pg_query, etc.) should precede consumers
|
||||||
|
- Data consumers (describe, head, to_csv, etc.) need valid data_ref
|
||||||
|
- Workflow steps should have logical sequence
|
||||||
|
|
||||||
|
## Issue Severities
|
||||||
|
|
||||||
|
- **ERROR**: Tool reference not found - agent will fail
|
||||||
|
- **WARNING**: Data flow issue - agent may produce unexpected results
|
||||||
|
- **INFO**: Undocumented reference - consider adding documentation
|
||||||
|
|
||||||
|
## Example Interaction
|
||||||
|
|
||||||
|
**User**: /check-agent Orchestrator
|
||||||
|
|
||||||
|
**Agent**:
|
||||||
|
1. Parses CLAUDE.md, finds Orchestrator agent
|
||||||
|
2. Extracts responsibilities: "Sprint execution, parallel batching, Git operations"
|
||||||
|
3. Finds tool refs: create_issue, update_issue, search_lessons
|
||||||
|
4. Validates against plugins: all tools found in projman/gitea
|
||||||
|
5. Validates data flow: no data producers/consumers used
|
||||||
|
6. Reports: "Agent Orchestrator: VALID - all 3 tool references found"
|
||||||
|
|
||||||
|
**User**: /check-agent InvalidAgent
|
||||||
|
|
||||||
|
**Agent**:
|
||||||
|
1. Parses CLAUDE.md, agent not found
|
||||||
|
2. Reports: "Agent 'InvalidAgent' not found. Available agents: Planner, Orchestrator, Executor, Code Reviewer"
|
||||||
87
plugins/contract-validator/agents/full-validation.md
Normal file
87
plugins/contract-validator/agents/full-validation.md
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
# Full Validation Agent
|
||||||
|
|
||||||
|
You are a contract validation specialist. Your role is to perform comprehensive cross-plugin compatibility validation for the entire marketplace.
|
||||||
|
|
||||||
|
## Capabilities
|
||||||
|
|
||||||
|
- Parse plugin interfaces from README.md files
|
||||||
|
- Parse agent definitions from CLAUDE.md files
|
||||||
|
- Validate cross-plugin compatibility
|
||||||
|
- Identify interface mismatches and conflicts
|
||||||
|
- Generate detailed validation reports
|
||||||
|
|
||||||
|
## Available Tools
|
||||||
|
|
||||||
|
### Parsing
|
||||||
|
- `parse_plugin_interface` - Extract interface from plugin README.md
|
||||||
|
- `parse_claude_md_agents` - Extract agents from CLAUDE.md
|
||||||
|
|
||||||
|
### Validation
|
||||||
|
- `validate_compatibility` - Check two plugins for conflicts
|
||||||
|
- `validate_agent_refs` - Verify agent tool references exist
|
||||||
|
- `validate_data_flow` - Check data flow through agent sequences
|
||||||
|
|
||||||
|
### Reporting
|
||||||
|
- `generate_compatibility_report` - Full marketplace report
|
||||||
|
- `list_issues` - Filter issues by severity/type
|
||||||
|
|
||||||
|
## Workflow
|
||||||
|
|
||||||
|
1. **Discover plugins**:
|
||||||
|
- Locate marketplace plugins directory
|
||||||
|
- Identify plugins by `.claude-plugin/` marker
|
||||||
|
- Build list of all plugins to validate
|
||||||
|
|
||||||
|
2. **Parse all interfaces**:
|
||||||
|
- For each plugin, use `parse_plugin_interface`
|
||||||
|
- Extract commands, agents, tools from README.md
|
||||||
|
- Track tool categories and features
|
||||||
|
|
||||||
|
3. **Run pairwise compatibility checks**:
|
||||||
|
- For each pair of plugins, use `validate_compatibility`
|
||||||
|
- Check for command name conflicts (ERROR)
|
||||||
|
- Check for tool name overlaps (WARNING)
|
||||||
|
- Identify interface mismatches
|
||||||
|
|
||||||
|
4. **Validate CLAUDE.md agents** (if present):
|
||||||
|
- Use `parse_claude_md_agents` on project CLAUDE.md
|
||||||
|
- For each agent, use `validate_agent_refs`
|
||||||
|
- Use `validate_data_flow` to check sequences
|
||||||
|
|
||||||
|
5. **Generate comprehensive report**:
|
||||||
|
- Use `generate_compatibility_report`
|
||||||
|
- Format: markdown for human review, JSON for programmatic use
|
||||||
|
- Include summary statistics and detailed findings
|
||||||
|
|
||||||
|
## Report Structure
|
||||||
|
|
||||||
|
### Summary
|
||||||
|
- Total plugins scanned
|
||||||
|
- Total commands, agents, tools found
|
||||||
|
- Issue counts by severity (error/warning/info)
|
||||||
|
|
||||||
|
### Compatibility Matrix
|
||||||
|
- Plugin pairs with conflicts
|
||||||
|
- Shared tools between plugins
|
||||||
|
- Unique tools per plugin
|
||||||
|
|
||||||
|
### Issues List
|
||||||
|
- ERROR: Command name conflicts (must fix)
|
||||||
|
- WARNING: Tool name overlaps (review needed)
|
||||||
|
- INFO: Undocumented references (improve docs)
|
||||||
|
|
||||||
|
### Recommendations
|
||||||
|
- Actionable suggestions per issue
|
||||||
|
- Priority order for fixes
|
||||||
|
|
||||||
|
## Example Interaction
|
||||||
|
|
||||||
|
**User**: /validate-contracts ~/claude-plugins-work
|
||||||
|
|
||||||
|
**Agent**:
|
||||||
|
1. Discovers 12 plugins in marketplace
|
||||||
|
2. Parses all README.md files
|
||||||
|
3. Runs 66 pairwise compatibility checks
|
||||||
|
4. Finds 3 errors, 4 warnings
|
||||||
|
5. Reports: "Command conflict: projman and data-platform both define /initial-setup"
|
||||||
|
6. Suggests: "Rename one command to avoid ambiguity"
|
||||||
152
plugins/contract-validator/claude-md-integration.md
Normal file
152
plugins/contract-validator/claude-md-integration.md
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
# contract-validator Plugin - CLAUDE.md Integration
|
||||||
|
|
||||||
|
Add this section to your marketplace or project's CLAUDE.md to enable contract validation features.
|
||||||
|
|
||||||
|
## Suggested CLAUDE.md Section
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## Contract Validation
|
||||||
|
|
||||||
|
This marketplace uses the contract-validator plugin for cross-plugin compatibility checks.
|
||||||
|
|
||||||
|
### Available Commands
|
||||||
|
|
||||||
|
| Command | Purpose |
|
||||||
|
|---------|---------|
|
||||||
|
| `/validate-contracts` | Full marketplace compatibility validation |
|
||||||
|
| `/check-agent` | Validate single agent definition |
|
||||||
|
| `/list-interfaces` | Show all plugin interfaces |
|
||||||
|
|
||||||
|
### Validation Workflow
|
||||||
|
|
||||||
|
Run before merging plugin changes:
|
||||||
|
|
||||||
|
1. `/validate-contracts` - Check for conflicts
|
||||||
|
2. Review errors (must fix) and warnings (should review)
|
||||||
|
3. Fix issues before merging
|
||||||
|
|
||||||
|
### Interface Documentation Standards
|
||||||
|
|
||||||
|
For plugins to be validated correctly, document interfaces in README.md:
|
||||||
|
|
||||||
|
**Commands Section:**
|
||||||
|
```markdown
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
|---------|-------------|
|
||||||
|
| `/my-command` | What it does |
|
||||||
|
```
|
||||||
|
|
||||||
|
**Tools Section:**
|
||||||
|
```markdown
|
||||||
|
## Tools Summary
|
||||||
|
|
||||||
|
### Category (N tools)
|
||||||
|
`tool_a`, `tool_b`, `tool_c`
|
||||||
|
```
|
||||||
|
|
||||||
|
**Agents Section:**
|
||||||
|
```markdown
|
||||||
|
## Agents
|
||||||
|
|
||||||
|
| Agent | Description |
|
||||||
|
|-------|-------------|
|
||||||
|
| `my-agent` | What it does |
|
||||||
|
```
|
||||||
|
```
|
||||||
|
|
||||||
|
## Declaring Agent Tool References
|
||||||
|
|
||||||
|
For agent validation to work, document tool usage in CLAUDE.md:
|
||||||
|
|
||||||
|
### Option 1: Four-Agent Model Table
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
### Four-Agent Model
|
||||||
|
|
||||||
|
| Agent | Personality | Responsibilities |
|
||||||
|
|-------|-------------|------------------|
|
||||||
|
| **Planner** | Methodical | Planning via `create_issue`, `search_lessons` |
|
||||||
|
```
|
||||||
|
|
||||||
|
### Option 2: Agent Sections
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
### Planner Agent
|
||||||
|
|
||||||
|
Uses these tools:
|
||||||
|
- `create_issue` - Create planning issues
|
||||||
|
- `search_lessons` - Find relevant lessons
|
||||||
|
```
|
||||||
|
|
||||||
|
## Best Practices for Plugin Authors
|
||||||
|
|
||||||
|
### Unique Command Names
|
||||||
|
|
||||||
|
Avoid generic names that may conflict:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# BAD - Will conflict with other plugins
|
||||||
|
| `/setup` | Setup wizard |
|
||||||
|
|
||||||
|
# GOOD - Plugin-specific prefix
|
||||||
|
| `/data-setup` | Data platform setup wizard |
|
||||||
|
```
|
||||||
|
|
||||||
|
### Document All Tools
|
||||||
|
|
||||||
|
Ensure every MCP tool is listed in README.md:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## Tools Summary
|
||||||
|
|
||||||
|
### pandas (14 tools)
|
||||||
|
`read_csv`, `read_parquet`, `read_json`, `to_csv`, `to_parquet`,
|
||||||
|
`describe`, `head`, `tail`, `filter`, `select`, `groupby`, `join`,
|
||||||
|
`list_data`, `drop_data`
|
||||||
|
```
|
||||||
|
|
||||||
|
### Specify Dependencies
|
||||||
|
|
||||||
|
If agents depend on tools from other plugins, document it:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
This agent uses tools from:
|
||||||
|
- `projman` - Issue management (`create_issue`, `update_issue`)
|
||||||
|
- `data-platform` - Data loading (`read_csv`, `describe`)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Typical Workflows
|
||||||
|
|
||||||
|
### Pre-Merge Validation
|
||||||
|
|
||||||
|
```
|
||||||
|
# Before merging new plugin
|
||||||
|
/validate-contracts
|
||||||
|
|
||||||
|
# Check specific agent after changes
|
||||||
|
/check-agent Orchestrator
|
||||||
|
```
|
||||||
|
|
||||||
|
### Plugin Development
|
||||||
|
|
||||||
|
```
|
||||||
|
# See what interfaces exist
|
||||||
|
/list-interfaces
|
||||||
|
|
||||||
|
# After adding new command, verify no conflicts
|
||||||
|
/validate-contracts
|
||||||
|
```
|
||||||
|
|
||||||
|
### CI/CD Integration
|
||||||
|
|
||||||
|
Add to your pipeline:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: Validate Plugin Contracts
|
||||||
|
run: |
|
||||||
|
claude --skill contract-validator:validate-contracts --args "${{ github.workspace }}"
|
||||||
|
```
|
||||||
51
plugins/contract-validator/commands/check-agent.md
Normal file
51
plugins/contract-validator/commands/check-agent.md
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
# /check-agent - Validate Agent Definition
|
||||||
|
|
||||||
|
Validate a single agent's tool references and data flow.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
/check-agent <agent_name> [claude_md_path]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Parameters
|
||||||
|
|
||||||
|
- `agent_name` (required): Name of the agent to validate (e.g., "Planner", "Orchestrator")
|
||||||
|
- `claude_md_path` (optional): Path to CLAUDE.md file. Defaults to `./CLAUDE.md`
|
||||||
|
|
||||||
|
## Workflow
|
||||||
|
|
||||||
|
1. **Parse agent definition**:
|
||||||
|
- Locate agent in CLAUDE.md (Four-Agent Model table or Agents section)
|
||||||
|
- Extract responsibilities, tool references, workflow steps
|
||||||
|
|
||||||
|
2. **Validate tool references**:
|
||||||
|
- Check each referenced tool exists in available plugins
|
||||||
|
- Report missing or misspelled tool names
|
||||||
|
- Suggest corrections for common mistakes
|
||||||
|
|
||||||
|
3. **Validate data flow**:
|
||||||
|
- Analyze sequence of tools in agent workflow
|
||||||
|
- Verify data producers precede data consumers
|
||||||
|
- Check for orphaned data references
|
||||||
|
|
||||||
|
4. **Report findings**:
|
||||||
|
- List all tool references found
|
||||||
|
- List any missing tools
|
||||||
|
- Data flow validation results
|
||||||
|
- Suggestions for improvement
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
```
|
||||||
|
/check-agent Planner
|
||||||
|
/check-agent Orchestrator ./CLAUDE.md
|
||||||
|
/check-agent data-analysis ~/project/CLAUDE.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## Available Tools
|
||||||
|
|
||||||
|
Use these MCP tools:
|
||||||
|
- `validate_agent_refs` - Check agent tool references exist
|
||||||
|
- `validate_data_flow` - Verify data flow through agent sequence
|
||||||
|
- `parse_claude_md_agents` - Parse all agents from CLAUDE.md
|
||||||
251
plugins/contract-validator/commands/dependency-graph.md
Normal file
251
plugins/contract-validator/commands/dependency-graph.md
Normal file
@@ -0,0 +1,251 @@
|
|||||||
|
# /dependency-graph - Generate Dependency Visualization
|
||||||
|
|
||||||
|
Generate a Mermaid flowchart showing plugin dependencies, data flows, and tool relationships.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
/dependency-graph [marketplace_path] [--format <mermaid|text>] [--show-tools]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Parameters
|
||||||
|
|
||||||
|
- `marketplace_path` (optional): Path to marketplace root. Defaults to current project root.
|
||||||
|
- `--format` (optional): Output format - `mermaid` (default) or `text`
|
||||||
|
- `--show-tools` (optional): Include individual tool nodes in the graph
|
||||||
|
|
||||||
|
## Workflow
|
||||||
|
|
||||||
|
1. **Discover plugins**:
|
||||||
|
- Scan plugins directory for all plugins with `.claude-plugin/` marker
|
||||||
|
- Parse each plugin's README.md to extract interface
|
||||||
|
- Parse CLAUDE.md for agent definitions and tool sequences
|
||||||
|
|
||||||
|
2. **Analyze dependencies**:
|
||||||
|
- Identify shared MCP servers (plugins using same server)
|
||||||
|
- Detect tool dependencies (which plugins produce vs consume data)
|
||||||
|
- Find agent tool references across plugins
|
||||||
|
- Categorize as required (ERROR if missing) or optional (WARNING if missing)
|
||||||
|
|
||||||
|
3. **Build dependency graph**:
|
||||||
|
- Create nodes for each plugin
|
||||||
|
- Create edges for:
|
||||||
|
- Shared MCP servers (bidirectional)
|
||||||
|
- Data producers -> consumers (directional)
|
||||||
|
- Agent tool dependencies (directional)
|
||||||
|
- Mark edges as optional or required
|
||||||
|
|
||||||
|
4. **Generate Mermaid output**:
|
||||||
|
- Create flowchart diagram syntax
|
||||||
|
- Style required dependencies with solid lines
|
||||||
|
- Style optional dependencies with dashed lines
|
||||||
|
- Group by MCP server or data flow
|
||||||
|
|
||||||
|
## Output Format
|
||||||
|
|
||||||
|
### Mermaid (default)
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
subgraph mcp_gitea["MCP: gitea"]
|
||||||
|
projman["projman"]
|
||||||
|
pr-review["pr-review"]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph mcp_data["MCP: data-platform"]
|
||||||
|
data-platform["data-platform"]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph mcp_viz["MCP: viz-platform"]
|
||||||
|
viz-platform["viz-platform"]
|
||||||
|
end
|
||||||
|
|
||||||
|
%% Data flow dependencies
|
||||||
|
data-platform -->|"data_ref (required)"| viz-platform
|
||||||
|
|
||||||
|
%% Optional dependencies
|
||||||
|
projman -.->|"lessons (optional)"| pr-review
|
||||||
|
|
||||||
|
%% Styling
|
||||||
|
classDef required stroke:#e74c3c,stroke-width:2px
|
||||||
|
classDef optional stroke:#f39c12,stroke-dasharray:5 5
|
||||||
|
```
|
||||||
|
|
||||||
|
### Text Format
|
||||||
|
|
||||||
|
```
|
||||||
|
DEPENDENCY GRAPH
|
||||||
|
================
|
||||||
|
|
||||||
|
Plugins: 12
|
||||||
|
MCP Servers: 4
|
||||||
|
Dependencies: 8 (5 required, 3 optional)
|
||||||
|
|
||||||
|
MCP Server Groups:
|
||||||
|
gitea: projman, pr-review
|
||||||
|
data-platform: data-platform
|
||||||
|
viz-platform: viz-platform
|
||||||
|
netbox: cmdb-assistant
|
||||||
|
|
||||||
|
Data Flow Dependencies:
|
||||||
|
data-platform -> viz-platform (data_ref) [REQUIRED]
|
||||||
|
data-platform -> data-platform (data_ref) [INTERNAL]
|
||||||
|
|
||||||
|
Cross-Plugin Tool Usage:
|
||||||
|
projman.Planner uses: create_issue, search_lessons
|
||||||
|
pr-review.reviewer uses: get_pr_diff, create_pr_review
|
||||||
|
```
|
||||||
|
|
||||||
|
## Dependency Types
|
||||||
|
|
||||||
|
| Type | Line Style | Meaning |
|
||||||
|
|------|------------|---------|
|
||||||
|
| Required | Solid (`-->`) | Plugin cannot function without this dependency |
|
||||||
|
| Optional | Dashed (`-.->`) | Plugin works but with reduced functionality |
|
||||||
|
| Internal | Dotted (`...>`) | Self-dependency within same plugin |
|
||||||
|
| Shared MCP | Double (`==>`) | Plugins share same MCP server instance |
|
||||||
|
|
||||||
|
## Known Data Flow Patterns
|
||||||
|
|
||||||
|
The command recognizes these producer/consumer relationships:
|
||||||
|
|
||||||
|
### Data Producers
|
||||||
|
- `read_csv`, `read_parquet`, `read_json` - File loaders
|
||||||
|
- `pg_query`, `pg_execute` - Database queries
|
||||||
|
- `filter`, `select`, `groupby`, `join` - Transformations
|
||||||
|
|
||||||
|
### Data Consumers
|
||||||
|
- `describe`, `head`, `tail` - Data inspection
|
||||||
|
- `to_csv`, `to_parquet` - File writers
|
||||||
|
- `chart_create` - Visualization
|
||||||
|
|
||||||
|
### Cross-Plugin Flows
|
||||||
|
- `data-platform` produces `data_ref` -> `viz-platform` consumes for charts
|
||||||
|
- `projman` produces issues -> `pr-review` references in reviews
|
||||||
|
- `gitea` wiki -> `projman` lessons learned
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Basic Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
/dependency-graph
|
||||||
|
```
|
||||||
|
|
||||||
|
Generates Mermaid diagram for current marketplace.
|
||||||
|
|
||||||
|
### With Tool Details
|
||||||
|
|
||||||
|
```
|
||||||
|
/dependency-graph --show-tools
|
||||||
|
```
|
||||||
|
|
||||||
|
Includes individual tool nodes showing which tools each plugin provides.
|
||||||
|
|
||||||
|
### Text Summary
|
||||||
|
|
||||||
|
```
|
||||||
|
/dependency-graph --format text
|
||||||
|
```
|
||||||
|
|
||||||
|
Outputs text-based summary suitable for CLAUDE.md inclusion.
|
||||||
|
|
||||||
|
### Specific Path
|
||||||
|
|
||||||
|
```
|
||||||
|
/dependency-graph ~/claude-plugins-work
|
||||||
|
```
|
||||||
|
|
||||||
|
Analyze marketplace at specified path.
|
||||||
|
|
||||||
|
## Integration with Other Commands
|
||||||
|
|
||||||
|
Use with `/validate-contracts` to:
|
||||||
|
1. Run `/dependency-graph` to visualize relationships
|
||||||
|
2. Run `/validate-contracts` to find issues in those relationships
|
||||||
|
3. Fix issues and regenerate graph to verify
|
||||||
|
|
||||||
|
## Available Tools
|
||||||
|
|
||||||
|
Use these MCP tools:
|
||||||
|
- `parse_plugin_interface` - Extract interface from plugin README.md
|
||||||
|
- `parse_claude_md_agents` - Extract agents and their tool sequences
|
||||||
|
- `generate_compatibility_report` - Get full interface data (JSON format for analysis)
|
||||||
|
- `validate_data_flow` - Verify data producer/consumer relationships
|
||||||
|
|
||||||
|
## Implementation Notes
|
||||||
|
|
||||||
|
### Detecting Shared MCP Servers
|
||||||
|
|
||||||
|
Check each plugin's `.mcp.json` file for server definitions:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# List all .mcp.json files in plugins
|
||||||
|
find plugins/ -name ".mcp.json" -exec cat {} \;
|
||||||
|
```
|
||||||
|
|
||||||
|
Plugins with identical MCP server names share that server.
|
||||||
|
|
||||||
|
### Identifying Data Flows
|
||||||
|
|
||||||
|
1. Parse tool categories from README.md
|
||||||
|
2. Map known producer tools to their output types
|
||||||
|
3. Map known consumer tools to their input requirements
|
||||||
|
4. Create edges where outputs match inputs
|
||||||
|
|
||||||
|
### Optional vs Required
|
||||||
|
|
||||||
|
- **Required**: Consumer tool has no default/fallback behavior
|
||||||
|
- **Optional**: Consumer works without producer (e.g., lessons search returns empty)
|
||||||
|
|
||||||
|
Determination is based on:
|
||||||
|
- Issue severity from `validate_data_flow` (ERROR = required, WARNING = optional)
|
||||||
|
- Tool documentation stating "requires" vs "uses if available"
|
||||||
|
|
||||||
|
## Sample Output
|
||||||
|
|
||||||
|
For the leo-claude-mktplace:
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
subgraph gitea_mcp["Shared MCP: gitea"]
|
||||||
|
projman["projman<br/>14 commands"]
|
||||||
|
pr-review["pr-review<br/>6 commands"]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph netbox_mcp["Shared MCP: netbox"]
|
||||||
|
cmdb-assistant["cmdb-assistant<br/>3 commands"]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph data_mcp["Shared MCP: data-platform"]
|
||||||
|
data-platform["data-platform<br/>7 commands"]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph viz_mcp["Shared MCP: viz-platform"]
|
||||||
|
viz-platform["viz-platform<br/>7 commands"]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph standalone["Standalone Plugins"]
|
||||||
|
doc-guardian["doc-guardian"]
|
||||||
|
code-sentinel["code-sentinel"]
|
||||||
|
clarity-assist["clarity-assist"]
|
||||||
|
git-flow["git-flow"]
|
||||||
|
claude-config-maintainer["claude-config-maintainer"]
|
||||||
|
contract-validator["contract-validator"]
|
||||||
|
end
|
||||||
|
|
||||||
|
%% Data flow: data-platform -> viz-platform
|
||||||
|
data-platform -->|"data_ref"| viz-platform
|
||||||
|
|
||||||
|
%% Cross-plugin: projman lessons -> pr-review context
|
||||||
|
projman -.->|"lessons"| pr-review
|
||||||
|
|
||||||
|
%% Styling
|
||||||
|
classDef mcpGroup fill:#e8f4fd,stroke:#2196f3
|
||||||
|
classDef standalone fill:#f5f5f5,stroke:#9e9e9e
|
||||||
|
classDef required stroke:#e74c3c,stroke-width:2px
|
||||||
|
classDef optional stroke:#f39c12,stroke-dasharray:5 5
|
||||||
|
|
||||||
|
class gitea_mcp,netbox_mcp,data_mcp,viz_mcp mcpGroup
|
||||||
|
class standalone standalone
|
||||||
|
```
|
||||||
152
plugins/contract-validator/commands/initial-setup.md
Normal file
152
plugins/contract-validator/commands/initial-setup.md
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
---
|
||||||
|
description: Interactive setup wizard for contract-validator plugin - verifies MCP server and shows capabilities
|
||||||
|
---
|
||||||
|
|
||||||
|
# Contract-Validator Setup Wizard
|
||||||
|
|
||||||
|
This command sets up the contract-validator plugin for cross-plugin compatibility validation.
|
||||||
|
|
||||||
|
## Important Context
|
||||||
|
|
||||||
|
- **This command uses Bash, Read, Write, and AskUserQuestion tools** - NOT MCP tools
|
||||||
|
- **MCP tools won't work until after setup + session restart**
|
||||||
|
- **No external credentials required** - this plugin validates local files only
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 1: Environment Validation
|
||||||
|
|
||||||
|
### Step 1.1: Check Python Version
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 --version
|
||||||
|
```
|
||||||
|
|
||||||
|
Requires Python 3.10+. If below, stop setup and inform user:
|
||||||
|
```
|
||||||
|
Python 3.10 or higher is required. Please install it and run /initial-setup again.
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 2: MCP Server Setup
|
||||||
|
|
||||||
|
### Step 2.1: Locate Contract-Validator MCP Server
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# If running from installed marketplace
|
||||||
|
ls -la ~/.claude/plugins/marketplaces/leo-claude-mktplace/mcp-servers/contract-validator/ 2>/dev/null || echo "NOT_FOUND_INSTALLED"
|
||||||
|
|
||||||
|
# If running from source
|
||||||
|
ls -la ~/claude-plugins-work/mcp-servers/contract-validator/ 2>/dev/null || echo "NOT_FOUND_SOURCE"
|
||||||
|
```
|
||||||
|
|
||||||
|
Determine which path exists and use that as the MCP server path.
|
||||||
|
|
||||||
|
### Step 2.2: Check Virtual Environment
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ls -la /path/to/mcp-servers/contract-validator/.venv/bin/python 2>/dev/null && echo "VENV_EXISTS" || echo "VENV_MISSING"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 2.3: Create Virtual Environment (if missing)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /path/to/mcp-servers/contract-validator && python3 -m venv .venv && source .venv/bin/activate && pip install --upgrade pip && pip install -r requirements.txt && deactivate
|
||||||
|
```
|
||||||
|
|
||||||
|
**If pip install fails:**
|
||||||
|
- Show the error to the user
|
||||||
|
- Suggest: "Check your internet connection and try again."
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 3: Validation
|
||||||
|
|
||||||
|
### Step 3.1: Verify MCP Server
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /path/to/mcp-servers/contract-validator && .venv/bin/python -c "from mcp_server.server import ContractValidatorMCPServer; print('MCP Server OK')"
|
||||||
|
```
|
||||||
|
|
||||||
|
If this fails, check the error and report it to the user.
|
||||||
|
|
||||||
|
### Step 3.2: Summary
|
||||||
|
|
||||||
|
Display:
|
||||||
|
|
||||||
|
```
|
||||||
|
╔════════════════════════════════════════════════════════════════╗
|
||||||
|
║ CONTRACT-VALIDATOR SETUP COMPLETE ║
|
||||||
|
╠════════════════════════════════════════════════════════════════╣
|
||||||
|
║ MCP Server: ✓ Ready ║
|
||||||
|
║ Parse Tools: ✓ Available (2 tools) ║
|
||||||
|
║ Validation Tools: ✓ Available (3 tools) ║
|
||||||
|
║ Report Tools: ✓ Available (2 tools) ║
|
||||||
|
╚════════════════════════════════════════════════════════════════╝
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 3.3: Session Restart Notice
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Session Restart Required**
|
||||||
|
|
||||||
|
Restart your Claude Code session for MCP tools to become available.
|
||||||
|
|
||||||
|
**After restart, you can:**
|
||||||
|
- Run `/validate-contracts` to check all plugins for compatibility issues
|
||||||
|
- Run `/check-agent` to validate a single agent definition
|
||||||
|
- Run `/list-interfaces` to see all plugin commands and tools
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Available Tools
|
||||||
|
|
||||||
|
| Category | Tools | Description |
|
||||||
|
|----------|-------|-------------|
|
||||||
|
| Parse | `parse_plugin_interface`, `parse_claude_md_agents` | Extract interfaces from README.md and agents from CLAUDE.md |
|
||||||
|
| Validation | `validate_compatibility`, `validate_agent_refs`, `validate_data_flow` | Check conflicts, tool references, and data flows |
|
||||||
|
| Report | `generate_compatibility_report`, `list_issues` | Generate reports and filter issues |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Available Commands
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
|---------|-------------|
|
||||||
|
| `/validate-contracts` | Full marketplace compatibility validation |
|
||||||
|
| `/check-agent` | Validate single agent definition |
|
||||||
|
| `/list-interfaces` | Show all plugin interfaces |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Use Cases
|
||||||
|
|
||||||
|
### 1. Pre-Release Validation
|
||||||
|
Run `/validate-contracts` before releasing a new marketplace version to catch:
|
||||||
|
- Command name conflicts between plugins
|
||||||
|
- Missing tool references in agents
|
||||||
|
- Broken data flows
|
||||||
|
|
||||||
|
### 2. Agent Development
|
||||||
|
Run `/check-agent` when creating or modifying agents to verify:
|
||||||
|
- All referenced tools exist
|
||||||
|
- Data flows are valid
|
||||||
|
- No undeclared dependencies
|
||||||
|
|
||||||
|
### 3. Plugin Audit
|
||||||
|
Run `/list-interfaces` to get a complete view of:
|
||||||
|
- All commands across plugins
|
||||||
|
- All tools available
|
||||||
|
- Potential overlap areas
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## No Configuration Required
|
||||||
|
|
||||||
|
This plugin doesn't require any configuration files. It reads plugin manifests and README files directly from the filesystem.
|
||||||
|
|
||||||
|
**Paths it scans:**
|
||||||
|
- Marketplace: `~/.claude/plugins/marketplaces/leo-claude-mktplace/plugins/`
|
||||||
|
- Source (if available): `~/claude-plugins-work/plugins/`
|
||||||
58
plugins/contract-validator/commands/list-interfaces.md
Normal file
58
plugins/contract-validator/commands/list-interfaces.md
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
# /list-interfaces - Show Plugin Interfaces
|
||||||
|
|
||||||
|
Display what each plugin in the marketplace produces and accepts.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
/list-interfaces [marketplace_path]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Parameters
|
||||||
|
|
||||||
|
- `marketplace_path` (optional): Path to marketplace root. Defaults to current project root.
|
||||||
|
|
||||||
|
## Workflow
|
||||||
|
|
||||||
|
1. **Discover plugins**:
|
||||||
|
- Scan plugins directory for all plugins with `.claude-plugin/` marker
|
||||||
|
- Read each plugin's README.md
|
||||||
|
|
||||||
|
2. **Parse interfaces**:
|
||||||
|
- Extract commands (slash commands offered by plugin)
|
||||||
|
- Extract agents (autonomous agents defined)
|
||||||
|
- Extract tools (MCP tools provided)
|
||||||
|
- Identify tool categories and features
|
||||||
|
|
||||||
|
3. **Display summary**:
|
||||||
|
- Table of plugins with command/agent/tool counts
|
||||||
|
- Detailed breakdown per plugin
|
||||||
|
- Tool categories and their contents
|
||||||
|
|
||||||
|
## Output Format
|
||||||
|
|
||||||
|
```
|
||||||
|
| Plugin | Commands | Agents | Tools |
|
||||||
|
|-------------|----------|--------|-------|
|
||||||
|
| projman | 12 | 4 | 26 |
|
||||||
|
| data-platform| 7 | 2 | 32 |
|
||||||
|
| ... | ... | ... | ... |
|
||||||
|
|
||||||
|
## projman
|
||||||
|
- Commands: /sprint-plan, /sprint-start, ...
|
||||||
|
- Agents: Planner, Orchestrator, Executor, Code Reviewer
|
||||||
|
- Tools: list_issues, create_issue, ...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
```
|
||||||
|
/list-interfaces
|
||||||
|
/list-interfaces ~/claude-plugins-work
|
||||||
|
```
|
||||||
|
|
||||||
|
## Available Tools
|
||||||
|
|
||||||
|
Use these MCP tools:
|
||||||
|
- `parse_plugin_interface` - Parse individual plugin README
|
||||||
|
- `generate_compatibility_report` - Get full interface data (JSON format)
|
||||||
50
plugins/contract-validator/commands/validate-contracts.md
Normal file
50
plugins/contract-validator/commands/validate-contracts.md
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
# /validate-contracts - Full Contract Validation
|
||||||
|
|
||||||
|
Run comprehensive cross-plugin compatibility validation for the entire marketplace.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
/validate-contracts [marketplace_path]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Parameters
|
||||||
|
|
||||||
|
- `marketplace_path` (optional): Path to marketplace root. Defaults to current project root.
|
||||||
|
|
||||||
|
## Workflow
|
||||||
|
|
||||||
|
1. **Discover plugins**:
|
||||||
|
- Scan plugins directory for all plugins with `.claude-plugin/` marker
|
||||||
|
- Parse each plugin's README.md to extract interface
|
||||||
|
|
||||||
|
2. **Run compatibility checks**:
|
||||||
|
- Perform pairwise compatibility validation between all plugins
|
||||||
|
- Check for command name conflicts
|
||||||
|
- Check for tool name overlaps
|
||||||
|
- Identify interface mismatches
|
||||||
|
|
||||||
|
3. **Validate CLAUDE.md agents**:
|
||||||
|
- Parse agent definitions from CLAUDE.md
|
||||||
|
- Validate all tool references exist
|
||||||
|
- Check data flow through agent sequences
|
||||||
|
|
||||||
|
4. **Generate report**:
|
||||||
|
- Summary statistics (plugins, commands, tools, issues)
|
||||||
|
- Detailed findings by severity (error, warning, info)
|
||||||
|
- Actionable suggestions for each issue
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
```
|
||||||
|
/validate-contracts
|
||||||
|
/validate-contracts ~/claude-plugins-work
|
||||||
|
```
|
||||||
|
|
||||||
|
## Available Tools
|
||||||
|
|
||||||
|
Use these MCP tools:
|
||||||
|
- `generate_compatibility_report` - Generate full marketplace report
|
||||||
|
- `list_issues` - Filter issues by severity or type
|
||||||
|
- `parse_plugin_interface` - Parse individual plugin interface
|
||||||
|
- `validate_compatibility` - Check two plugins for conflicts
|
||||||
195
plugins/contract-validator/hooks/auto-validate.sh
Executable file
195
plugins/contract-validator/hooks/auto-validate.sh
Executable file
@@ -0,0 +1,195 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# contract-validator SessionStart auto-validate hook
|
||||||
|
# Validates plugin contracts only when plugin files have changed since last check
|
||||||
|
# All output MUST have [contract-validator] prefix
|
||||||
|
|
||||||
|
PREFIX="[contract-validator]"
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Configuration
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
# Enable/disable auto-check (default: true)
|
||||||
|
AUTO_CHECK="${CONTRACT_VALIDATOR_AUTO_CHECK:-true}"
|
||||||
|
|
||||||
|
# Cache location for storing last check hash
|
||||||
|
CACHE_DIR="$HOME/.cache/claude-plugins/contract-validator"
|
||||||
|
HASH_FILE="$CACHE_DIR/last-check.hash"
|
||||||
|
|
||||||
|
# Marketplace location (installed plugins)
|
||||||
|
MARKETPLACE_PATH="$HOME/.claude/plugins/marketplaces/leo-claude-mktplace"
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Early exit if disabled
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
if [[ "$AUTO_CHECK" != "true" ]]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Smart mode: Check if plugin files have changed
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
# Function to compute hash of all plugin manifest files
|
||||||
|
compute_plugin_hash() {
|
||||||
|
local hash_input=""
|
||||||
|
|
||||||
|
if [[ -d "$MARKETPLACE_PATH/plugins" ]]; then
|
||||||
|
# Hash all plugin.json, hooks.json, and agent files
|
||||||
|
while IFS= read -r -d '' file; do
|
||||||
|
if [[ -f "$file" ]]; then
|
||||||
|
hash_input+="$(md5sum "$file" 2>/dev/null | cut -d' ' -f1)"
|
||||||
|
fi
|
||||||
|
done < <(find "$MARKETPLACE_PATH/plugins" \
|
||||||
|
\( -name "plugin.json" -o -name "hooks.json" -o -name "*.md" -path "*/agents/*" \) \
|
||||||
|
-print0 2>/dev/null | sort -z)
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Also include marketplace.json
|
||||||
|
if [[ -f "$MARKETPLACE_PATH/.claude-plugin/marketplace.json" ]]; then
|
||||||
|
hash_input+="$(md5sum "$MARKETPLACE_PATH/.claude-plugin/marketplace.json" 2>/dev/null | cut -d' ' -f1)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Compute final hash
|
||||||
|
echo "$hash_input" | md5sum | cut -d' ' -f1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Ensure cache directory exists
|
||||||
|
mkdir -p "$CACHE_DIR" 2>/dev/null
|
||||||
|
|
||||||
|
# Compute current hash
|
||||||
|
CURRENT_HASH=$(compute_plugin_hash)
|
||||||
|
|
||||||
|
# Check if we have a previous hash
|
||||||
|
if [[ -f "$HASH_FILE" ]]; then
|
||||||
|
PREVIOUS_HASH=$(cat "$HASH_FILE" 2>/dev/null)
|
||||||
|
|
||||||
|
# If hashes match, no changes - skip validation
|
||||||
|
if [[ "$CURRENT_HASH" == "$PREVIOUS_HASH" ]]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Run validation (hashes differ or no cache)
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
ISSUES_FOUND=0
|
||||||
|
WARNINGS=""
|
||||||
|
|
||||||
|
# Function to add warning
|
||||||
|
add_warning() {
|
||||||
|
WARNINGS+=" - $1"$'\n'
|
||||||
|
((ISSUES_FOUND++))
|
||||||
|
}
|
||||||
|
|
||||||
|
# 1. Check all installed plugins have valid plugin.json
|
||||||
|
if [[ -d "$MARKETPLACE_PATH/plugins" ]]; then
|
||||||
|
for plugin_dir in "$MARKETPLACE_PATH/plugins"/*/; do
|
||||||
|
if [[ -d "$plugin_dir" ]]; then
|
||||||
|
plugin_name=$(basename "$plugin_dir")
|
||||||
|
plugin_json="$plugin_dir/.claude-plugin/plugin.json"
|
||||||
|
|
||||||
|
if [[ ! -f "$plugin_json" ]]; then
|
||||||
|
add_warning "$plugin_name: missing .claude-plugin/plugin.json"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Basic JSON validation
|
||||||
|
if ! python3 -c "import json; json.load(open('$plugin_json'))" 2>/dev/null; then
|
||||||
|
add_warning "$plugin_name: invalid JSON in plugin.json"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check required fields
|
||||||
|
if ! python3 -c "
|
||||||
|
import json
|
||||||
|
with open('$plugin_json') as f:
|
||||||
|
data = json.load(f)
|
||||||
|
required = ['name', 'version', 'description']
|
||||||
|
missing = [r for r in required if r not in data]
|
||||||
|
if missing:
|
||||||
|
exit(1)
|
||||||
|
" 2>/dev/null; then
|
||||||
|
add_warning "$plugin_name: plugin.json missing required fields"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 2. Check hooks.json files are properly formatted
|
||||||
|
if [[ -d "$MARKETPLACE_PATH/plugins" ]]; then
|
||||||
|
while IFS= read -r -d '' hooks_file; do
|
||||||
|
plugin_name=$(basename "$(dirname "$(dirname "$hooks_file")")")
|
||||||
|
|
||||||
|
# Validate JSON
|
||||||
|
if ! python3 -c "import json; json.load(open('$hooks_file'))" 2>/dev/null; then
|
||||||
|
add_warning "$plugin_name: invalid JSON in hooks/hooks.json"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Validate hook structure
|
||||||
|
if ! python3 -c "
|
||||||
|
import json
|
||||||
|
with open('$hooks_file') as f:
|
||||||
|
data = json.load(f)
|
||||||
|
if 'hooks' not in data:
|
||||||
|
exit(1)
|
||||||
|
valid_events = ['PreToolUse', 'PostToolUse', 'UserPromptSubmit', 'SessionStart', 'SessionEnd', 'Notification', 'Stop', 'SubagentStop', 'PreCompact']
|
||||||
|
for event in data['hooks']:
|
||||||
|
if event not in valid_events:
|
||||||
|
exit(1)
|
||||||
|
for hook in data['hooks'][event]:
|
||||||
|
# Support both flat structure (type at top) and nested structure (matcher + hooks array)
|
||||||
|
if 'type' in hook:
|
||||||
|
# Flat structure: {type: 'command', command: '...'}
|
||||||
|
pass
|
||||||
|
elif 'matcher' in hook and 'hooks' in hook:
|
||||||
|
# Nested structure: {matcher: '...', hooks: [{type: 'command', ...}]}
|
||||||
|
for nested_hook in hook['hooks']:
|
||||||
|
if 'type' not in nested_hook:
|
||||||
|
exit(1)
|
||||||
|
else:
|
||||||
|
exit(1)
|
||||||
|
" 2>/dev/null; then
|
||||||
|
add_warning "$plugin_name: hooks.json has invalid structure or events"
|
||||||
|
fi
|
||||||
|
done < <(find "$MARKETPLACE_PATH/plugins" -path "*/hooks/hooks.json" -print0 2>/dev/null)
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 3. Check agent references are valid (agent files exist and are markdown)
|
||||||
|
if [[ -d "$MARKETPLACE_PATH/plugins" ]]; then
|
||||||
|
while IFS= read -r -d '' agent_file; do
|
||||||
|
plugin_name=$(basename "$(dirname "$(dirname "$agent_file")")")
|
||||||
|
agent_name=$(basename "$agent_file")
|
||||||
|
|
||||||
|
# Check file is not empty
|
||||||
|
if [[ ! -s "$agent_file" ]]; then
|
||||||
|
add_warning "$plugin_name: empty agent file $agent_name"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check file has markdown content (at least a header)
|
||||||
|
if ! grep -q '^#' "$agent_file" 2>/dev/null; then
|
||||||
|
add_warning "$plugin_name: agent $agent_name missing markdown header"
|
||||||
|
fi
|
||||||
|
done < <(find "$MARKETPLACE_PATH/plugins" -path "*/agents/*.md" -print0 2>/dev/null)
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Store new hash and report results
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
# Always store the new hash (even if issues found - we don't want to recheck)
|
||||||
|
echo "$CURRENT_HASH" > "$HASH_FILE"
|
||||||
|
|
||||||
|
# Report any issues found (non-blocking warning)
|
||||||
|
if [[ $ISSUES_FOUND -gt 0 ]]; then
|
||||||
|
echo "$PREFIX Plugin contract validation found $ISSUES_FOUND issue(s):"
|
||||||
|
echo "$WARNINGS"
|
||||||
|
echo "$PREFIX Run /validate-contracts for full details"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Always exit 0 (non-blocking)
|
||||||
|
exit 0
|
||||||
174
plugins/contract-validator/hooks/breaking-change-check.sh
Executable file
174
plugins/contract-validator/hooks/breaking-change-check.sh
Executable file
@@ -0,0 +1,174 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# contract-validator breaking change detection hook
|
||||||
|
# Warns when plugin interface changes might break consumers
|
||||||
|
# This is a PostToolUse hook - non-blocking, warnings only
|
||||||
|
|
||||||
|
PREFIX="[contract-validator]"
|
||||||
|
|
||||||
|
# Check if warnings are enabled (default: true)
|
||||||
|
if [[ "${CONTRACT_VALIDATOR_BREAKING_WARN:-true}" != "true" ]]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Read tool input from stdin
|
||||||
|
INPUT=$(cat)
|
||||||
|
|
||||||
|
# Extract file_path from JSON input
|
||||||
|
FILE_PATH=$(echo "$INPUT" | grep -o '"file_path"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"file_path"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/')
|
||||||
|
|
||||||
|
# If no file_path found, exit silently
|
||||||
|
if [ -z "$FILE_PATH" ]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if file is a plugin interface file
|
||||||
|
is_interface_file() {
|
||||||
|
local file="$1"
|
||||||
|
|
||||||
|
case "$file" in
|
||||||
|
*/plugin.json) return 0 ;;
|
||||||
|
*/.claude-plugin/plugin.json) return 0 ;;
|
||||||
|
*/hooks.json) return 0 ;;
|
||||||
|
*/hooks/hooks.json) return 0 ;;
|
||||||
|
*/.mcp.json) return 0 ;;
|
||||||
|
*/agents/*.md) return 0 ;;
|
||||||
|
*/commands/*.md) return 0 ;;
|
||||||
|
*/skills/*.md) return 0 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Exit if not an interface file
|
||||||
|
if ! is_interface_file "$FILE_PATH"; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if file exists and is in a git repo
|
||||||
|
if [[ ! -f "$FILE_PATH" ]]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Get the directory containing the file
|
||||||
|
FILE_DIR=$(dirname "$FILE_PATH")
|
||||||
|
FILE_NAME=$(basename "$FILE_PATH")
|
||||||
|
|
||||||
|
# Try to get the previous version from git
|
||||||
|
cd "$FILE_DIR" 2>/dev/null || exit 0
|
||||||
|
|
||||||
|
# Check if we're in a git repo
|
||||||
|
if ! git rev-parse --git-dir > /dev/null 2>&1; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Get previous version (HEAD version before current changes)
|
||||||
|
PREV_CONTENT=$(git show HEAD:"$FILE_PATH" 2>/dev/null || echo "")
|
||||||
|
|
||||||
|
# If no previous version, this is a new file - no breaking changes possible
|
||||||
|
if [ -z "$PREV_CONTENT" ]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Read current content
|
||||||
|
CURR_CONTENT=$(cat "$FILE_PATH" 2>/dev/null || echo "")
|
||||||
|
|
||||||
|
if [ -z "$CURR_CONTENT" ]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
BREAKING_CHANGES=()
|
||||||
|
|
||||||
|
# Detect breaking changes based on file type
|
||||||
|
case "$FILE_PATH" in
|
||||||
|
*/plugin.json|*/.claude-plugin/plugin.json)
|
||||||
|
# Check for removed or renamed fields in plugin.json
|
||||||
|
|
||||||
|
# Check if name changed
|
||||||
|
PREV_NAME=$(echo "$PREV_CONTENT" | grep -o '"name"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1)
|
||||||
|
CURR_NAME=$(echo "$CURR_CONTENT" | grep -o '"name"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1)
|
||||||
|
if [ -n "$PREV_NAME" ] && [ "$PREV_NAME" != "$CURR_NAME" ]; then
|
||||||
|
BREAKING_CHANGES+=("Plugin name changed - consumers may need updates")
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if version had major bump (semantic versioning)
|
||||||
|
PREV_VER=$(echo "$PREV_CONTENT" | grep -o '"version"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*"\([0-9]*\)\..*/\1/')
|
||||||
|
CURR_VER=$(echo "$CURR_CONTENT" | grep -o '"version"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*"\([0-9]*\)\..*/\1/')
|
||||||
|
if [ -n "$PREV_VER" ] && [ -n "$CURR_VER" ] && [ "$CURR_VER" -gt "$PREV_VER" ] 2>/dev/null; then
|
||||||
|
BREAKING_CHANGES+=("Major version bump detected - verify breaking changes documented")
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
|
||||||
|
*/hooks.json|*/hooks/hooks.json)
|
||||||
|
# Check for removed hook events
|
||||||
|
PREV_EVENTS=$(echo "$PREV_CONTENT" | grep -oE '"(PreToolUse|PostToolUse|UserPromptSubmit|SessionStart|SessionEnd|Notification|Stop|SubagentStop|PreCompact)"' | sort -u)
|
||||||
|
CURR_EVENTS=$(echo "$CURR_CONTENT" | grep -oE '"(PreToolUse|PostToolUse|UserPromptSubmit|SessionStart|SessionEnd|Notification|Stop|SubagentStop|PreCompact)"' | sort -u)
|
||||||
|
|
||||||
|
# Find removed events
|
||||||
|
REMOVED_EVENTS=$(comm -23 <(echo "$PREV_EVENTS") <(echo "$CURR_EVENTS") 2>/dev/null)
|
||||||
|
if [ -n "$REMOVED_EVENTS" ]; then
|
||||||
|
BREAKING_CHANGES+=("Hook events removed: $(echo $REMOVED_EVENTS | tr '\n' ' ')")
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for changed matchers
|
||||||
|
PREV_MATCHERS=$(echo "$PREV_CONTENT" | grep -o '"matcher"[[:space:]]*:[[:space:]]*"[^"]*"' | sort -u)
|
||||||
|
CURR_MATCHERS=$(echo "$CURR_CONTENT" | grep -o '"matcher"[[:space:]]*:[[:space:]]*"[^"]*"' | sort -u)
|
||||||
|
if [ "$PREV_MATCHERS" != "$CURR_MATCHERS" ]; then
|
||||||
|
BREAKING_CHANGES+=("Hook matchers changed - verify tool coverage")
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
|
||||||
|
*/.mcp.json)
|
||||||
|
# Check for removed MCP servers
|
||||||
|
PREV_SERVERS=$(echo "$PREV_CONTENT" | grep -o '"[^"]*"[[:space:]]*:' | grep -v "mcpServers" | sort -u)
|
||||||
|
CURR_SERVERS=$(echo "$CURR_CONTENT" | grep -o '"[^"]*"[[:space:]]*:' | grep -v "mcpServers" | sort -u)
|
||||||
|
|
||||||
|
REMOVED_SERVERS=$(comm -23 <(echo "$PREV_SERVERS") <(echo "$CURR_SERVERS") 2>/dev/null)
|
||||||
|
if [ -n "$REMOVED_SERVERS" ]; then
|
||||||
|
BREAKING_CHANGES+=("MCP servers removed - tools may be unavailable")
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
|
||||||
|
*/agents/*.md)
|
||||||
|
# Check if agent file was significantly reduced (might indicate removal of capabilities)
|
||||||
|
PREV_LINES=$(echo "$PREV_CONTENT" | wc -l)
|
||||||
|
CURR_LINES=$(echo "$CURR_CONTENT" | wc -l)
|
||||||
|
|
||||||
|
# If more than 50% reduction, warn
|
||||||
|
if [ "$PREV_LINES" -gt 10 ] && [ "$CURR_LINES" -lt $((PREV_LINES / 2)) ]; then
|
||||||
|
BREAKING_CHANGES+=("Agent definition significantly reduced - capabilities may be removed")
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if agent name/description changed in frontmatter
|
||||||
|
PREV_DESC=$(echo "$PREV_CONTENT" | head -20 | grep -i "description" | head -1)
|
||||||
|
CURR_DESC=$(echo "$CURR_CONTENT" | head -20 | grep -i "description" | head -1)
|
||||||
|
if [ -n "$PREV_DESC" ] && [ "$PREV_DESC" != "$CURR_DESC" ]; then
|
||||||
|
BREAKING_CHANGES+=("Agent description changed - verify consumer expectations")
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
|
||||||
|
*/commands/*.md|*/skills/*.md)
|
||||||
|
# Check if command/skill was significantly changed
|
||||||
|
PREV_LINES=$(echo "$PREV_CONTENT" | wc -l)
|
||||||
|
CURR_LINES=$(echo "$CURR_CONTENT" | wc -l)
|
||||||
|
|
||||||
|
if [ "$PREV_LINES" -gt 10 ] && [ "$CURR_LINES" -lt $((PREV_LINES / 2)) ]; then
|
||||||
|
BREAKING_CHANGES+=("Command/skill significantly reduced - behavior may change")
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Output warnings if any breaking changes detected
|
||||||
|
if [[ ${#BREAKING_CHANGES[@]} -gt 0 ]]; then
|
||||||
|
echo ""
|
||||||
|
echo "$PREFIX WARNING: Potential breaking changes in $(basename "$FILE_PATH")"
|
||||||
|
echo "$PREFIX ============================================"
|
||||||
|
for change in "${BREAKING_CHANGES[@]}"; do
|
||||||
|
echo "$PREFIX - $change"
|
||||||
|
done
|
||||||
|
echo "$PREFIX ============================================"
|
||||||
|
echo "$PREFIX Consider updating CHANGELOG and notifying consumers"
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Always exit 0 - non-blocking
|
||||||
|
exit 0
|
||||||
21
plugins/contract-validator/hooks/hooks.json
Normal file
21
plugins/contract-validator/hooks/hooks.json
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"hooks": {
|
||||||
|
"SessionStart": [
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/auto-validate.sh"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"PostToolUse": [
|
||||||
|
{
|
||||||
|
"matcher": "Edit|Write",
|
||||||
|
"hooks": [
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/breaking-change-check.sh"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
1
plugins/contract-validator/mcp-servers/contract-validator
Symbolic link
1
plugins/contract-validator/mcp-servers/contract-validator
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
../../../mcp-servers/contract-validator
|
||||||
@@ -2,9 +2,8 @@
|
|||||||
"mcpServers": {
|
"mcpServers": {
|
||||||
"data-platform": {
|
"data-platform": {
|
||||||
"type": "stdio",
|
"type": "stdio",
|
||||||
"command": "${CLAUDE_PLUGIN_ROOT}/mcp-servers/data-platform/.venv/bin/python",
|
"command": "${CLAUDE_PLUGIN_ROOT}/mcp-servers/data-platform/run.sh",
|
||||||
"args": ["-m", "mcp_server.server"],
|
"args": []
|
||||||
"cwd": "${CLAUDE_PLUGIN_ROOT}/mcp-servers/data-platform"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,10 +49,13 @@ DBT_PROFILES_DIR=~/.dbt
|
|||||||
| `/initial-setup` | Interactive setup wizard for PostgreSQL and dbt configuration |
|
| `/initial-setup` | Interactive setup wizard for PostgreSQL and dbt configuration |
|
||||||
| `/ingest` | Load data from files or database |
|
| `/ingest` | Load data from files or database |
|
||||||
| `/profile` | Generate data profile and statistics |
|
| `/profile` | Generate data profile and statistics |
|
||||||
|
| `/data-quality` | Data quality assessment with pass/warn/fail scoring |
|
||||||
| `/schema` | Show database/DataFrame schema |
|
| `/schema` | Show database/DataFrame schema |
|
||||||
| `/explain` | Explain dbt model lineage |
|
| `/explain` | Explain dbt model lineage |
|
||||||
| `/lineage` | Visualize data dependencies |
|
| `/lineage` | Visualize data dependencies (ASCII) |
|
||||||
|
| `/lineage-viz` | Generate Mermaid flowchart for dbt lineage |
|
||||||
| `/run` | Execute dbt models |
|
| `/run` | Execute dbt models |
|
||||||
|
| `/dbt-test` | Run dbt tests with formatted results |
|
||||||
|
|
||||||
## Agents
|
## Agents
|
||||||
|
|
||||||
|
|||||||
103
plugins/data-platform/commands/data-quality.md
Normal file
103
plugins/data-platform/commands/data-quality.md
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
# /data-quality - Data Quality Assessment
|
||||||
|
|
||||||
|
Comprehensive data quality check for DataFrames with pass/warn/fail scoring.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
/data-quality <data_ref> [--strict]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Workflow
|
||||||
|
|
||||||
|
1. **Get data reference**:
|
||||||
|
- If no data_ref provided, use `list_data` to show available options
|
||||||
|
- Validate the data_ref exists
|
||||||
|
|
||||||
|
2. **Null analysis**:
|
||||||
|
- Calculate null percentage per column
|
||||||
|
- **PASS**: < 5% nulls
|
||||||
|
- **WARN**: 5-20% nulls
|
||||||
|
- **FAIL**: > 20% nulls
|
||||||
|
|
||||||
|
3. **Duplicate detection**:
|
||||||
|
- Check for fully duplicated rows
|
||||||
|
- **PASS**: 0% duplicates
|
||||||
|
- **WARN**: < 1% duplicates
|
||||||
|
- **FAIL**: >= 1% duplicates
|
||||||
|
|
||||||
|
4. **Type consistency**:
|
||||||
|
- Identify mixed-type columns (object columns with mixed content)
|
||||||
|
- Flag columns that could be numeric but contain strings
|
||||||
|
- **PASS**: All columns have consistent types
|
||||||
|
- **FAIL**: Mixed types detected
|
||||||
|
|
||||||
|
5. **Outlier detection** (numeric columns):
|
||||||
|
- Use IQR method (values beyond 1.5 * IQR)
|
||||||
|
- Report percentage of outliers per column
|
||||||
|
- **PASS**: < 1% outliers
|
||||||
|
- **WARN**: 1-5% outliers
|
||||||
|
- **FAIL**: > 5% outliers
|
||||||
|
|
||||||
|
6. **Generate quality report**:
|
||||||
|
- Overall quality score (0-100)
|
||||||
|
- Per-column breakdown
|
||||||
|
- Recommendations for remediation
|
||||||
|
|
||||||
|
## Report Format
|
||||||
|
|
||||||
|
```
|
||||||
|
=== Data Quality Report ===
|
||||||
|
Dataset: sales_data
|
||||||
|
Rows: 10,000 | Columns: 15
|
||||||
|
Overall Score: 82/100 [PASS]
|
||||||
|
|
||||||
|
--- Column Analysis ---
|
||||||
|
| Column | Nulls | Dups | Type | Outliers | Status |
|
||||||
|
|--------------|-------|------|----------|----------|--------|
|
||||||
|
| customer_id | 0.0% | - | int64 | 0.2% | PASS |
|
||||||
|
| email | 2.3% | - | object | - | PASS |
|
||||||
|
| amount | 15.2% | - | float64 | 3.1% | WARN |
|
||||||
|
| created_at | 0.0% | - | datetime | - | PASS |
|
||||||
|
|
||||||
|
--- Issues Found ---
|
||||||
|
[WARN] Column 'amount': 15.2% null values (threshold: 5%)
|
||||||
|
[WARN] Column 'amount': 3.1% outliers detected
|
||||||
|
[FAIL] 1.2% duplicate rows detected (12 rows)
|
||||||
|
|
||||||
|
--- Recommendations ---
|
||||||
|
1. Investigate null values in 'amount' column
|
||||||
|
2. Review outliers in 'amount' - may be data entry errors
|
||||||
|
3. Remove or deduplicate 12 duplicate rows
|
||||||
|
```
|
||||||
|
|
||||||
|
## Options
|
||||||
|
|
||||||
|
| Flag | Description |
|
||||||
|
|------|-------------|
|
||||||
|
| `--strict` | Use stricter thresholds (WARN at 1% nulls, FAIL at 5%) |
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
```
|
||||||
|
/data-quality sales_data
|
||||||
|
/data-quality df_customers --strict
|
||||||
|
```
|
||||||
|
|
||||||
|
## Scoring
|
||||||
|
|
||||||
|
| Component | Weight | Scoring |
|
||||||
|
|-----------|--------|---------|
|
||||||
|
| Nulls | 30% | 100 - (avg_null_pct * 2) |
|
||||||
|
| Duplicates | 20% | 100 - (dup_pct * 50) |
|
||||||
|
| Type consistency | 25% | 100 if clean, 0 if mixed |
|
||||||
|
| Outliers | 25% | 100 - (avg_outlier_pct * 10) |
|
||||||
|
|
||||||
|
Final score: Weighted average, capped at 0-100
|
||||||
|
|
||||||
|
## Available Tools
|
||||||
|
|
||||||
|
Use these MCP tools:
|
||||||
|
- `describe` - Get statistical summary (for outlier detection)
|
||||||
|
- `head` - Preview data
|
||||||
|
- `list_data` - List available DataFrames
|
||||||
119
plugins/data-platform/commands/dbt-test.md
Normal file
119
plugins/data-platform/commands/dbt-test.md
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
# /dbt-test - Run dbt Tests
|
||||||
|
|
||||||
|
Execute dbt tests with formatted pass/fail results.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
/dbt-test [selection] [--warn-only]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Workflow
|
||||||
|
|
||||||
|
1. **Pre-validation** (MANDATORY):
|
||||||
|
- Use `dbt_parse` to validate project first
|
||||||
|
- If validation fails, show errors and STOP
|
||||||
|
|
||||||
|
2. **Execute tests**:
|
||||||
|
- Use `dbt_test` with provided selection
|
||||||
|
- Capture all test results
|
||||||
|
|
||||||
|
3. **Format results**:
|
||||||
|
- Group by test type (schema vs. data)
|
||||||
|
- Show pass/fail status with counts
|
||||||
|
- Display failure details
|
||||||
|
|
||||||
|
## Report Format
|
||||||
|
|
||||||
|
```
|
||||||
|
=== dbt Test Results ===
|
||||||
|
Project: my_project
|
||||||
|
Selection: tag:critical
|
||||||
|
|
||||||
|
--- Summary ---
|
||||||
|
Total: 24 tests
|
||||||
|
PASS: 22 (92%)
|
||||||
|
FAIL: 1 (4%)
|
||||||
|
WARN: 1 (4%)
|
||||||
|
SKIP: 0 (0%)
|
||||||
|
|
||||||
|
--- Schema Tests (18) ---
|
||||||
|
[PASS] unique_dim_customers_customer_id
|
||||||
|
[PASS] not_null_dim_customers_customer_id
|
||||||
|
[PASS] not_null_dim_customers_email
|
||||||
|
[PASS] accepted_values_dim_customers_status
|
||||||
|
[FAIL] relationships_fct_orders_customer_id
|
||||||
|
|
||||||
|
--- Data Tests (6) ---
|
||||||
|
[PASS] assert_positive_order_amounts
|
||||||
|
[PASS] assert_valid_dates
|
||||||
|
[WARN] assert_recent_orders (threshold: 7 days)
|
||||||
|
|
||||||
|
--- Failure Details ---
|
||||||
|
Test: relationships_fct_orders_customer_id
|
||||||
|
Type: schema (relationships)
|
||||||
|
Model: fct_orders
|
||||||
|
Message: 15 records failed referential integrity check
|
||||||
|
Query: SELECT * FROM fct_orders WHERE customer_id NOT IN (SELECT customer_id FROM dim_customers)
|
||||||
|
|
||||||
|
--- Warning Details ---
|
||||||
|
Test: assert_recent_orders
|
||||||
|
Type: data
|
||||||
|
Message: No orders in last 7 days (expected for dev environment)
|
||||||
|
Severity: warn
|
||||||
|
```
|
||||||
|
|
||||||
|
## Selection Syntax
|
||||||
|
|
||||||
|
| Pattern | Meaning |
|
||||||
|
|---------|---------|
|
||||||
|
| (none) | Run all tests |
|
||||||
|
| `model_name` | Tests for specific model |
|
||||||
|
| `+model_name` | Tests for model and upstream |
|
||||||
|
| `tag:critical` | Tests with tag |
|
||||||
|
| `test_type:schema` | Only schema tests |
|
||||||
|
| `test_type:data` | Only data tests |
|
||||||
|
|
||||||
|
## Options
|
||||||
|
|
||||||
|
| Flag | Description |
|
||||||
|
|------|-------------|
|
||||||
|
| `--warn-only` | Treat failures as warnings (don't fail CI) |
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
```
|
||||||
|
/dbt-test # Run all tests
|
||||||
|
/dbt-test dim_customers # Tests for specific model
|
||||||
|
/dbt-test tag:critical # Run critical tests only
|
||||||
|
/dbt-test +fct_orders # Test model and its upstream
|
||||||
|
```
|
||||||
|
|
||||||
|
## Test Types
|
||||||
|
|
||||||
|
### Schema Tests
|
||||||
|
Built-in tests defined in `schema.yml`:
|
||||||
|
- `unique` - No duplicate values
|
||||||
|
- `not_null` - No null values
|
||||||
|
- `accepted_values` - Value in allowed list
|
||||||
|
- `relationships` - Foreign key integrity
|
||||||
|
|
||||||
|
### Data Tests
|
||||||
|
Custom SQL tests in `tests/` directory:
|
||||||
|
- Return rows that fail the assertion
|
||||||
|
- Zero rows = pass, any rows = fail
|
||||||
|
|
||||||
|
## Exit Codes
|
||||||
|
|
||||||
|
| Code | Meaning |
|
||||||
|
|------|---------|
|
||||||
|
| 0 | All tests passed |
|
||||||
|
| 1 | One or more tests failed |
|
||||||
|
| 2 | dbt error (parse failure, etc.) |
|
||||||
|
|
||||||
|
## Available Tools
|
||||||
|
|
||||||
|
Use these MCP tools:
|
||||||
|
- `dbt_parse` - Pre-validation (ALWAYS RUN FIRST)
|
||||||
|
- `dbt_test` - Execute tests (REQUIRED)
|
||||||
|
- `dbt_build` - Alternative: run + test together
|
||||||
125
plugins/data-platform/commands/lineage-viz.md
Normal file
125
plugins/data-platform/commands/lineage-viz.md
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
# /lineage-viz - Mermaid Lineage Visualization
|
||||||
|
|
||||||
|
Generate Mermaid flowchart syntax for dbt model lineage.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
/lineage-viz <model_name> [--direction TB|LR] [--depth N]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Workflow
|
||||||
|
|
||||||
|
1. **Get lineage data**:
|
||||||
|
- Use `dbt_lineage` to fetch model dependencies
|
||||||
|
- Capture upstream sources and downstream consumers
|
||||||
|
|
||||||
|
2. **Build Mermaid graph**:
|
||||||
|
- Create nodes for each model/source
|
||||||
|
- Style nodes by materialization type
|
||||||
|
- Add directional arrows for dependencies
|
||||||
|
|
||||||
|
3. **Output**:
|
||||||
|
- Render Mermaid flowchart syntax
|
||||||
|
- Include copy-paste ready code block
|
||||||
|
|
||||||
|
## Output Format
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
subgraph Sources
|
||||||
|
raw_customers[(raw_customers)]
|
||||||
|
raw_orders[(raw_orders)]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph Staging
|
||||||
|
stg_customers[stg_customers]
|
||||||
|
stg_orders[stg_orders]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph Marts
|
||||||
|
dim_customers{{dim_customers}}
|
||||||
|
fct_orders{{fct_orders}}
|
||||||
|
end
|
||||||
|
|
||||||
|
raw_customers --> stg_customers
|
||||||
|
raw_orders --> stg_orders
|
||||||
|
stg_customers --> dim_customers
|
||||||
|
stg_orders --> fct_orders
|
||||||
|
dim_customers --> fct_orders
|
||||||
|
```
|
||||||
|
|
||||||
|
## Node Styles
|
||||||
|
|
||||||
|
| Materialization | Mermaid Shape | Example |
|
||||||
|
|-----------------|---------------|---------|
|
||||||
|
| source | Cylinder `[( )]` | `raw_data[(raw_data)]` |
|
||||||
|
| view | Rectangle `[ ]` | `stg_model[stg_model]` |
|
||||||
|
| table | Double braces `{{ }}` | `dim_model{{dim_model}}` |
|
||||||
|
| incremental | Hexagon `{{ }}` | `fct_model{{fct_model}}` |
|
||||||
|
| ephemeral | Dashed `[/ /]` | `tmp_model[/tmp_model/]` |
|
||||||
|
|
||||||
|
## Options
|
||||||
|
|
||||||
|
| Flag | Description |
|
||||||
|
|------|-------------|
|
||||||
|
| `--direction TB` | Top-to-bottom layout (default: LR = left-to-right) |
|
||||||
|
| `--depth N` | Limit lineage depth (default: unlimited) |
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
```
|
||||||
|
/lineage-viz dim_customers
|
||||||
|
/lineage-viz fct_orders --direction TB
|
||||||
|
/lineage-viz rpt_revenue --depth 2
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage Tips
|
||||||
|
|
||||||
|
1. **Paste in documentation**: Copy the output directly into README.md or docs
|
||||||
|
2. **GitHub/GitLab rendering**: Both platforms render Mermaid natively
|
||||||
|
3. **Mermaid Live Editor**: Paste at https://mermaid.live for interactive editing
|
||||||
|
|
||||||
|
## Example Output
|
||||||
|
|
||||||
|
For `/lineage-viz fct_orders`:
|
||||||
|
|
||||||
|
~~~markdown
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
%% Sources
|
||||||
|
raw_customers[(raw_customers)]
|
||||||
|
raw_orders[(raw_orders)]
|
||||||
|
raw_products[(raw_products)]
|
||||||
|
|
||||||
|
%% Staging
|
||||||
|
stg_customers[stg_customers]
|
||||||
|
stg_orders[stg_orders]
|
||||||
|
stg_products[stg_products]
|
||||||
|
|
||||||
|
%% Marts
|
||||||
|
dim_customers{{dim_customers}}
|
||||||
|
dim_products{{dim_products}}
|
||||||
|
fct_orders{{fct_orders}}
|
||||||
|
|
||||||
|
%% Dependencies
|
||||||
|
raw_customers --> stg_customers
|
||||||
|
raw_orders --> stg_orders
|
||||||
|
raw_products --> stg_products
|
||||||
|
stg_customers --> dim_customers
|
||||||
|
stg_products --> dim_products
|
||||||
|
stg_orders --> fct_orders
|
||||||
|
dim_customers --> fct_orders
|
||||||
|
dim_products --> fct_orders
|
||||||
|
|
||||||
|
%% Highlight target model
|
||||||
|
style fct_orders fill:#f96,stroke:#333,stroke-width:2px
|
||||||
|
```
|
||||||
|
~~~
|
||||||
|
|
||||||
|
## Available Tools
|
||||||
|
|
||||||
|
Use these MCP tools:
|
||||||
|
- `dbt_lineage` - Get model dependencies (REQUIRED)
|
||||||
|
- `dbt_ls` - List dbt resources
|
||||||
|
- `dbt_docs_generate` - Generate full manifest if needed
|
||||||
@@ -5,6 +5,17 @@
|
|||||||
"type": "command",
|
"type": "command",
|
||||||
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/startup-check.sh"
|
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/startup-check.sh"
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"PostToolUse": [
|
||||||
|
{
|
||||||
|
"matcher": "Edit|Write",
|
||||||
|
"hooks": [
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/schema-diff-check.sh"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
138
plugins/data-platform/hooks/schema-diff-check.sh
Executable file
138
plugins/data-platform/hooks/schema-diff-check.sh
Executable file
@@ -0,0 +1,138 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# data-platform schema diff detection hook
|
||||||
|
# Warns about potentially breaking schema changes
|
||||||
|
# This is a command hook - non-blocking, warnings only
|
||||||
|
|
||||||
|
PREFIX="[data-platform]"
|
||||||
|
|
||||||
|
# Check if warnings are enabled (default: true)
|
||||||
|
if [[ "${DATA_PLATFORM_SCHEMA_WARN:-true}" != "true" ]]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Read tool input from stdin (JSON with file_path)
|
||||||
|
INPUT=$(cat)
|
||||||
|
|
||||||
|
# Extract file_path from JSON input
|
||||||
|
FILE_PATH=$(echo "$INPUT" | grep -o '"file_path"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"file_path"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/')
|
||||||
|
|
||||||
|
# If no file_path found, exit silently
|
||||||
|
if [ -z "$FILE_PATH" ]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if file is a schema-related file
|
||||||
|
is_schema_file() {
|
||||||
|
local file="$1"
|
||||||
|
|
||||||
|
# Check file extension
|
||||||
|
case "$file" in
|
||||||
|
*.sql) return 0 ;;
|
||||||
|
*/migrations/*.py) return 0 ;;
|
||||||
|
*/migrations/*.sql) return 0 ;;
|
||||||
|
*/models/*.py) return 0 ;;
|
||||||
|
*/models/*.sql) return 0 ;;
|
||||||
|
*schema.prisma) return 0 ;;
|
||||||
|
*schema.graphql) return 0 ;;
|
||||||
|
*/dbt/models/*.sql) return 0 ;;
|
||||||
|
*/dbt/models/*.yml) return 0 ;;
|
||||||
|
*/alembic/versions/*.py) return 0 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Check directory patterns
|
||||||
|
if echo "$file" | grep -qE "(migrations?|schemas?|models)/"; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Exit if not a schema file
|
||||||
|
if ! is_schema_file "$FILE_PATH"; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Read the file content (if it exists and is readable)
|
||||||
|
if [[ ! -f "$FILE_PATH" ]]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
FILE_CONTENT=$(cat "$FILE_PATH" 2>/dev/null || echo "")
|
||||||
|
|
||||||
|
if [[ -z "$FILE_CONTENT" ]]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Detect breaking changes
|
||||||
|
BREAKING_CHANGES=()
|
||||||
|
|
||||||
|
# Check for DROP COLUMN
|
||||||
|
if echo "$FILE_CONTENT" | grep -qiE "DROP[[:space:]]+COLUMN"; then
|
||||||
|
BREAKING_CHANGES+=("DROP COLUMN detected - may break existing queries")
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for DROP TABLE
|
||||||
|
if echo "$FILE_CONTENT" | grep -qiE "DROP[[:space:]]+TABLE"; then
|
||||||
|
BREAKING_CHANGES+=("DROP TABLE detected - data loss risk")
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for DROP INDEX
|
||||||
|
if echo "$FILE_CONTENT" | grep -qiE "DROP[[:space:]]+INDEX"; then
|
||||||
|
BREAKING_CHANGES+=("DROP INDEX detected - may impact query performance")
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for ALTER TYPE / MODIFY COLUMN type changes
|
||||||
|
if echo "$FILE_CONTENT" | grep -qiE "ALTER[[:space:]]+.*(TYPE|COLUMN.*TYPE)"; then
|
||||||
|
BREAKING_CHANGES+=("Column type change detected - may cause data truncation")
|
||||||
|
fi
|
||||||
|
|
||||||
|
if echo "$FILE_CONTENT" | grep -qiE "MODIFY[[:space:]]+COLUMN"; then
|
||||||
|
BREAKING_CHANGES+=("MODIFY COLUMN detected - verify data compatibility")
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for adding NOT NULL to existing column
|
||||||
|
if echo "$FILE_CONTENT" | grep -qiE "ALTER[[:space:]]+.*SET[[:space:]]+NOT[[:space:]]+NULL"; then
|
||||||
|
BREAKING_CHANGES+=("Adding NOT NULL constraint - existing NULL values will fail")
|
||||||
|
fi
|
||||||
|
|
||||||
|
if echo "$FILE_CONTENT" | grep -qiE "ADD[[:space:]]+.*NOT[[:space:]]+NULL[^[:space:]]*[[:space:]]+DEFAULT"; then
|
||||||
|
# Adding NOT NULL with DEFAULT is usually safe - don't warn
|
||||||
|
:
|
||||||
|
elif echo "$FILE_CONTENT" | grep -qiE "ADD[[:space:]]+.*NOT[[:space:]]+NULL"; then
|
||||||
|
BREAKING_CHANGES+=("Adding NOT NULL column without DEFAULT - INSERT may fail")
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for RENAME TABLE/COLUMN
|
||||||
|
if echo "$FILE_CONTENT" | grep -qiE "RENAME[[:space:]]+(TABLE|COLUMN|TO)"; then
|
||||||
|
BREAKING_CHANGES+=("RENAME detected - update all references")
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for removing from Django/SQLAlchemy models (Python files)
|
||||||
|
if [[ "$FILE_PATH" == *.py ]]; then
|
||||||
|
if echo "$FILE_CONTENT" | grep -qE "^-[[:space:]]*[a-z_]+[[:space:]]*=.*Field\("; then
|
||||||
|
BREAKING_CHANGES+=("Model field removal detected in Python ORM")
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for Prisma schema changes
|
||||||
|
if [[ "$FILE_PATH" == *schema.prisma ]]; then
|
||||||
|
if echo "$FILE_CONTENT" | grep -qE "@relation.*onDelete.*Cascade"; then
|
||||||
|
BREAKING_CHANGES+=("Cascade delete detected - verify data safety")
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Output warnings if any breaking changes detected
|
||||||
|
if [[ ${#BREAKING_CHANGES[@]} -gt 0 ]]; then
|
||||||
|
echo ""
|
||||||
|
echo "$PREFIX WARNING: Potential breaking schema changes in $(basename "$FILE_PATH")"
|
||||||
|
echo "$PREFIX ============================================"
|
||||||
|
for change in "${BREAKING_CHANGES[@]}"; do
|
||||||
|
echo "$PREFIX - $change"
|
||||||
|
done
|
||||||
|
echo "$PREFIX ============================================"
|
||||||
|
echo "$PREFIX Review before deploying to production"
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Always exit 0 - non-blocking
|
||||||
|
exit 0
|
||||||
@@ -22,6 +22,9 @@ doc-guardian monitors your code changes via hooks:
|
|||||||
|---------|-------------|
|
|---------|-------------|
|
||||||
| `/doc-audit` | Full project scan - reports all drift without changing anything |
|
| `/doc-audit` | Full project scan - reports all drift without changing anything |
|
||||||
| `/doc-sync` | Apply all pending documentation updates in one commit |
|
| `/doc-sync` | Apply all pending documentation updates in one commit |
|
||||||
|
| `/changelog-gen` | Generate changelog from conventional commits in Keep-a-Changelog format |
|
||||||
|
| `/doc-coverage` | Calculate documentation coverage percentage for functions and classes |
|
||||||
|
| `/stale-docs` | Detect documentation files that are stale relative to their associated code |
|
||||||
|
|
||||||
## Hooks
|
## Hooks
|
||||||
|
|
||||||
@@ -33,6 +36,8 @@ doc-guardian monitors your code changes via hooks:
|
|||||||
- **Version Drift**: Python 3.9 in docs but 3.11 in pyproject.toml
|
- **Version Drift**: Python 3.9 in docs but 3.11 in pyproject.toml
|
||||||
- **Missing Docs**: Public functions without docstrings
|
- **Missing Docs**: Public functions without docstrings
|
||||||
- **Stale Examples**: CLI examples that no longer work
|
- **Stale Examples**: CLI examples that no longer work
|
||||||
|
- **Low Coverage**: Undocumented functions and classes
|
||||||
|
- **Stale Files**: Documentation that hasn't been updated alongside code changes
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ This project uses doc-guardian for automatic documentation synchronization.
|
|||||||
- Pending updates are queued silently during work
|
- Pending updates are queued silently during work
|
||||||
- Run `/doc-sync` to apply all pending documentation updates
|
- Run `/doc-sync` to apply all pending documentation updates
|
||||||
- Run `/doc-audit` for a full project documentation review
|
- Run `/doc-audit` for a full project documentation review
|
||||||
|
- Run `/changelog-gen` to generate changelog from conventional commits
|
||||||
|
- Run `/doc-coverage` to check documentation coverage metrics
|
||||||
|
- Run `/stale-docs` to find documentation that may be outdated
|
||||||
|
|
||||||
### Documentation Files Tracked
|
### Documentation Files Tracked
|
||||||
- README.md (root and subdirectories)
|
- README.md (root and subdirectories)
|
||||||
|
|||||||
109
plugins/doc-guardian/commands/changelog-gen.md
Normal file
109
plugins/doc-guardian/commands/changelog-gen.md
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
---
|
||||||
|
description: Generate changelog from conventional commits in Keep-a-Changelog format
|
||||||
|
---
|
||||||
|
|
||||||
|
# Changelog Generation
|
||||||
|
|
||||||
|
Generate a changelog entry from conventional commits.
|
||||||
|
|
||||||
|
## Process
|
||||||
|
|
||||||
|
1. **Identify Commit Range**
|
||||||
|
- Default: commits since last tag
|
||||||
|
- Optional: specify range (e.g., `v1.0.0..HEAD`)
|
||||||
|
- Detect if this is first release (no previous tags)
|
||||||
|
|
||||||
|
2. **Parse Conventional Commits**
|
||||||
|
Extract from commit messages following the pattern:
|
||||||
|
```
|
||||||
|
<type>(<scope>): <description>
|
||||||
|
|
||||||
|
[optional body]
|
||||||
|
|
||||||
|
[optional footer(s)]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Recognized Types:**
|
||||||
|
| Type | Changelog Section |
|
||||||
|
|------|------------------|
|
||||||
|
| `feat` | Added |
|
||||||
|
| `fix` | Fixed |
|
||||||
|
| `docs` | Documentation |
|
||||||
|
| `perf` | Performance |
|
||||||
|
| `refactor` | Changed |
|
||||||
|
| `style` | Changed |
|
||||||
|
| `test` | Testing |
|
||||||
|
| `build` | Build |
|
||||||
|
| `ci` | CI/CD |
|
||||||
|
| `chore` | Maintenance |
|
||||||
|
| `BREAKING CHANGE` | Breaking Changes |
|
||||||
|
|
||||||
|
3. **Group by Type**
|
||||||
|
Organize commits into Keep-a-Changelog sections:
|
||||||
|
- Breaking Changes (if any `!` suffix or `BREAKING CHANGE` footer)
|
||||||
|
- Added (feat)
|
||||||
|
- Changed (refactor, style, perf)
|
||||||
|
- Deprecated
|
||||||
|
- Removed
|
||||||
|
- Fixed (fix)
|
||||||
|
- Security
|
||||||
|
|
||||||
|
4. **Format Entries**
|
||||||
|
For each commit:
|
||||||
|
- Extract scope (if present) as prefix
|
||||||
|
- Use description as entry text
|
||||||
|
- Link to commit hash if repository URL available
|
||||||
|
- Include PR/issue references from footer
|
||||||
|
|
||||||
|
5. **Output Format**
|
||||||
|
```markdown
|
||||||
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Breaking Changes
|
||||||
|
- **scope**: Description of breaking change
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- **scope**: New feature description
|
||||||
|
- Another feature without scope
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- **scope**: Refactoring description
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- **scope**: Bug fix description
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
- Updated README with new examples
|
||||||
|
```
|
||||||
|
|
||||||
|
## Options
|
||||||
|
|
||||||
|
| Flag | Description | Default |
|
||||||
|
|------|-------------|---------|
|
||||||
|
| `--from <tag>` | Start from specific tag | Latest tag |
|
||||||
|
| `--to <ref>` | End at specific ref | HEAD |
|
||||||
|
| `--version <ver>` | Set version header | [Unreleased] |
|
||||||
|
| `--include-merge` | Include merge commits | false |
|
||||||
|
| `--group-by-scope` | Group by scope within sections | false |
|
||||||
|
|
||||||
|
## Integration
|
||||||
|
|
||||||
|
The generated output is designed to be copied directly into CHANGELOG.md:
|
||||||
|
- Follows [Keep a Changelog](https://keepachangelog.com) format
|
||||||
|
- Compatible with semantic versioning
|
||||||
|
- Excludes non-user-facing commits (chore, ci, test by default)
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
/changelog-gen
|
||||||
|
/changelog-gen --from v1.0.0 --version 1.1.0
|
||||||
|
/changelog-gen --include-merge --group-by-scope
|
||||||
|
```
|
||||||
|
|
||||||
|
## Non-Conventional Commits
|
||||||
|
|
||||||
|
Commits not following conventional format are:
|
||||||
|
- Listed under "Other" section
|
||||||
|
- Flagged for manual categorization
|
||||||
|
- Skipped if `--strict` flag is used
|
||||||
128
plugins/doc-guardian/commands/doc-coverage.md
Normal file
128
plugins/doc-guardian/commands/doc-coverage.md
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
---
|
||||||
|
description: Calculate documentation coverage percentage for functions and classes
|
||||||
|
---
|
||||||
|
|
||||||
|
# Documentation Coverage
|
||||||
|
|
||||||
|
Analyze codebase to calculate documentation coverage metrics.
|
||||||
|
|
||||||
|
## Process
|
||||||
|
|
||||||
|
1. **Scan Source Files**
|
||||||
|
Identify all documentable items:
|
||||||
|
|
||||||
|
**Python:**
|
||||||
|
- Functions (def)
|
||||||
|
- Classes
|
||||||
|
- Methods
|
||||||
|
- Module-level docstrings
|
||||||
|
|
||||||
|
**JavaScript/TypeScript:**
|
||||||
|
- Functions (function, arrow functions)
|
||||||
|
- Classes
|
||||||
|
- Methods
|
||||||
|
- JSDoc comments
|
||||||
|
|
||||||
|
**Other Languages:**
|
||||||
|
- Adapt patterns for Go, Rust, etc.
|
||||||
|
|
||||||
|
2. **Determine Documentation Status**
|
||||||
|
For each item, check:
|
||||||
|
- Has docstring/JSDoc comment
|
||||||
|
- Docstring is non-empty and meaningful (not just `pass` or `TODO`)
|
||||||
|
- Parameters are documented (for detailed mode)
|
||||||
|
- Return type is documented (for detailed mode)
|
||||||
|
|
||||||
|
3. **Calculate Metrics**
|
||||||
|
```
|
||||||
|
Coverage = (Documented Items / Total Items) * 100
|
||||||
|
```
|
||||||
|
|
||||||
|
**Levels:**
|
||||||
|
- Basic: Item has any docstring
|
||||||
|
- Standard: Docstring describes purpose
|
||||||
|
- Complete: All parameters and return documented
|
||||||
|
|
||||||
|
4. **Output Format**
|
||||||
|
```
|
||||||
|
## Documentation Coverage Report
|
||||||
|
|
||||||
|
### Summary
|
||||||
|
- Total documentable items: 156
|
||||||
|
- Documented: 142
|
||||||
|
- Coverage: 91.0%
|
||||||
|
|
||||||
|
### By Type
|
||||||
|
| Type | Total | Documented | Coverage |
|
||||||
|
|------|-------|------------|----------|
|
||||||
|
| Functions | 89 | 85 | 95.5% |
|
||||||
|
| Classes | 23 | 21 | 91.3% |
|
||||||
|
| Methods | 44 | 36 | 81.8% |
|
||||||
|
|
||||||
|
### By Directory
|
||||||
|
| Path | Total | Documented | Coverage |
|
||||||
|
|------|-------|------------|----------|
|
||||||
|
| src/api/ | 34 | 32 | 94.1% |
|
||||||
|
| src/utils/ | 28 | 28 | 100.0% |
|
||||||
|
| src/models/ | 45 | 38 | 84.4% |
|
||||||
|
| tests/ | 49 | 44 | 89.8% |
|
||||||
|
|
||||||
|
### Undocumented Items
|
||||||
|
- [ ] src/api/handlers.py:45 `create_order()`
|
||||||
|
- [ ] src/api/handlers.py:78 `update_order()`
|
||||||
|
- [ ] src/models/user.py:23 `UserModel.validate()`
|
||||||
|
```
|
||||||
|
|
||||||
|
## Options
|
||||||
|
|
||||||
|
| Flag | Description | Default |
|
||||||
|
|------|-------------|---------|
|
||||||
|
| `--path <dir>` | Scan specific directory | Project root |
|
||||||
|
| `--exclude <glob>` | Exclude files matching pattern | `**/test_*,**/*_test.*` |
|
||||||
|
| `--include-private` | Include private members (_prefixed) | false |
|
||||||
|
| `--include-tests` | Include test files | false |
|
||||||
|
| `--min-coverage <pct>` | Fail if below threshold | none |
|
||||||
|
| `--format <fmt>` | Output format (table, json, markdown) | table |
|
||||||
|
| `--detailed` | Check parameter/return docs | false |
|
||||||
|
|
||||||
|
## Thresholds
|
||||||
|
|
||||||
|
Common coverage targets:
|
||||||
|
| Level | Coverage | Description |
|
||||||
|
|-------|----------|-------------|
|
||||||
|
| Minimal | 60% | Basic documentation exists |
|
||||||
|
| Good | 80% | Most public APIs documented |
|
||||||
|
| Excellent | 95% | Comprehensive documentation |
|
||||||
|
|
||||||
|
## CI Integration
|
||||||
|
|
||||||
|
Use `--min-coverage` to enforce standards:
|
||||||
|
```bash
|
||||||
|
# Fail if coverage drops below 80%
|
||||||
|
claude /doc-coverage --min-coverage 80
|
||||||
|
```
|
||||||
|
|
||||||
|
Exit codes:
|
||||||
|
- 0: Coverage meets threshold (or no threshold set)
|
||||||
|
- 1: Coverage below threshold
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
/doc-coverage
|
||||||
|
/doc-coverage --path src/
|
||||||
|
/doc-coverage --min-coverage 85 --exclude "**/generated/**"
|
||||||
|
/doc-coverage --detailed --include-private
|
||||||
|
```
|
||||||
|
|
||||||
|
## Language Detection
|
||||||
|
|
||||||
|
File extensions mapped to documentation patterns:
|
||||||
|
| Extension | Language | Doc Format |
|
||||||
|
|-----------|----------|------------|
|
||||||
|
| .py | Python | Docstrings (""") |
|
||||||
|
| .js, .ts | JavaScript/TypeScript | JSDoc (/** */) |
|
||||||
|
| .go | Go | // comments above |
|
||||||
|
| .rs | Rust | /// doc comments |
|
||||||
|
| .rb | Ruby | # comments, YARD |
|
||||||
|
| .java | Java | Javadoc (/** */) |
|
||||||
143
plugins/doc-guardian/commands/stale-docs.md
Normal file
143
plugins/doc-guardian/commands/stale-docs.md
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
---
|
||||||
|
description: Detect documentation files that are stale relative to their associated code
|
||||||
|
---
|
||||||
|
|
||||||
|
# Stale Documentation Detection
|
||||||
|
|
||||||
|
Identify documentation files that may be outdated based on commit history.
|
||||||
|
|
||||||
|
## Process
|
||||||
|
|
||||||
|
1. **Map Documentation to Code**
|
||||||
|
Build relationships between docs and code:
|
||||||
|
|
||||||
|
| Doc File | Related Code |
|
||||||
|
|----------|--------------|
|
||||||
|
| README.md | All files in same directory |
|
||||||
|
| API.md | src/api/**/* |
|
||||||
|
| CLAUDE.md | Configuration files, scripts |
|
||||||
|
| docs/module.md | src/module/**/* |
|
||||||
|
| Component.md | Component.tsx, Component.css |
|
||||||
|
|
||||||
|
2. **Analyze Commit History**
|
||||||
|
For each doc file:
|
||||||
|
- Find last commit that modified the doc
|
||||||
|
- Find last commit that modified related code
|
||||||
|
- Count commits to code since doc was updated
|
||||||
|
|
||||||
|
3. **Calculate Staleness**
|
||||||
|
```
|
||||||
|
Commits Behind = Code Commits Since Doc Update
|
||||||
|
Days Behind = Days Since Doc Update - Days Since Code Update
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Apply Threshold**
|
||||||
|
Default: Flag if documentation is 10+ commits behind related code
|
||||||
|
|
||||||
|
**Staleness Levels:**
|
||||||
|
| Commits Behind | Level | Action |
|
||||||
|
|----------------|-------|--------|
|
||||||
|
| 0-5 | Fresh | No action needed |
|
||||||
|
| 6-10 | Aging | Review recommended |
|
||||||
|
| 11-20 | Stale | Update needed |
|
||||||
|
| 20+ | Critical | Immediate attention |
|
||||||
|
|
||||||
|
5. **Output Format**
|
||||||
|
```
|
||||||
|
## Stale Documentation Report
|
||||||
|
|
||||||
|
### Critical (20+ commits behind)
|
||||||
|
| File | Last Updated | Commits Behind | Related Code |
|
||||||
|
|------|--------------|----------------|--------------|
|
||||||
|
| docs/api.md | 2024-01-15 | 34 | src/api/**/* |
|
||||||
|
|
||||||
|
### Stale (11-20 commits behind)
|
||||||
|
| File | Last Updated | Commits Behind | Related Code |
|
||||||
|
|------|--------------|----------------|--------------|
|
||||||
|
| README.md | 2024-02-20 | 15 | package.json, src/index.ts |
|
||||||
|
|
||||||
|
### Aging (6-10 commits behind)
|
||||||
|
| File | Last Updated | Commits Behind | Related Code |
|
||||||
|
|------|--------------|----------------|--------------|
|
||||||
|
| CONTRIBUTING.md | 2024-03-01 | 8 | .github/*, scripts/* |
|
||||||
|
|
||||||
|
### Summary
|
||||||
|
- Critical: 1 file
|
||||||
|
- Stale: 1 file
|
||||||
|
- Aging: 1 file
|
||||||
|
- Fresh: 12 files
|
||||||
|
- Total documentation files: 15
|
||||||
|
```
|
||||||
|
|
||||||
|
## Options
|
||||||
|
|
||||||
|
| Flag | Description | Default |
|
||||||
|
|------|-------------|---------|
|
||||||
|
| `--threshold <n>` | Commits behind to flag as stale | 10 |
|
||||||
|
| `--days` | Use days instead of commits | false |
|
||||||
|
| `--path <dir>` | Scan specific directory | Project root |
|
||||||
|
| `--doc-pattern <glob>` | Pattern for doc files | `**/*.md,**/README*` |
|
||||||
|
| `--ignore <glob>` | Ignore specific docs | `CHANGELOG.md,LICENSE` |
|
||||||
|
| `--show-fresh` | Include fresh docs in output | false |
|
||||||
|
| `--format <fmt>` | Output format (table, json) | table |
|
||||||
|
|
||||||
|
## Relationship Detection
|
||||||
|
|
||||||
|
How docs are mapped to code:
|
||||||
|
|
||||||
|
1. **Same Directory**
|
||||||
|
- `src/api/README.md` relates to `src/api/**/*`
|
||||||
|
|
||||||
|
2. **Name Matching**
|
||||||
|
- `docs/auth.md` relates to `**/auth.*`, `**/auth/**`
|
||||||
|
|
||||||
|
3. **Explicit Links**
|
||||||
|
- Parse `[link](path)` in docs to find related files
|
||||||
|
|
||||||
|
4. **Import Analysis**
|
||||||
|
- Track which modules are referenced in code examples
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Create `.doc-guardian.yml` to customize mappings:
|
||||||
|
```yaml
|
||||||
|
stale-docs:
|
||||||
|
threshold: 10
|
||||||
|
mappings:
|
||||||
|
- doc: docs/deployment.md
|
||||||
|
code:
|
||||||
|
- Dockerfile
|
||||||
|
- docker-compose.yml
|
||||||
|
- .github/workflows/deploy.yml
|
||||||
|
- doc: ARCHITECTURE.md
|
||||||
|
code:
|
||||||
|
- src/**/*
|
||||||
|
ignore:
|
||||||
|
- CHANGELOG.md
|
||||||
|
- LICENSE
|
||||||
|
- vendor/**
|
||||||
|
```
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
/stale-docs
|
||||||
|
/stale-docs --threshold 5
|
||||||
|
/stale-docs --days --threshold 30
|
||||||
|
/stale-docs --path docs/ --show-fresh
|
||||||
|
```
|
||||||
|
|
||||||
|
## Integration with doc-audit
|
||||||
|
|
||||||
|
`/stale-docs` focuses specifically on commit-based staleness, while `/doc-audit` checks content accuracy. Use both for comprehensive documentation health:
|
||||||
|
|
||||||
|
```
|
||||||
|
/doc-audit # Check for broken references and content drift
|
||||||
|
/stale-docs # Check for files that may need review
|
||||||
|
```
|
||||||
|
|
||||||
|
## Exit Codes
|
||||||
|
|
||||||
|
- 0: No critical or stale documentation
|
||||||
|
- 1: Stale documentation found (useful for CI)
|
||||||
|
- 2: Critical documentation found
|
||||||
@@ -119,6 +119,10 @@ The git-assistant agent helps resolve merge conflicts with analysis and recommen
|
|||||||
→ Status: Clean, up-to-date
|
→ Status: Clean, up-to-date
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
- [Branching Strategy Guide](docs/BRANCHING-STRATEGY.md) - Detailed documentation of the `development -> staging -> main` promotion flow
|
||||||
|
|
||||||
## Integration
|
## Integration
|
||||||
|
|
||||||
For CLAUDE.md integration instructions, see `claude-md-integration.md`.
|
For CLAUDE.md integration instructions, see `claude-md-integration.md`.
|
||||||
|
|||||||
541
plugins/git-flow/docs/BRANCHING-STRATEGY.md
Normal file
541
plugins/git-flow/docs/BRANCHING-STRATEGY.md
Normal file
@@ -0,0 +1,541 @@
|
|||||||
|
# Branching Strategy
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This document defines the branching strategy used by git-flow to manage code changes across environments. The strategy ensures:
|
||||||
|
|
||||||
|
- **Stability**: Production code is always release-ready
|
||||||
|
- **Isolation**: Features are developed in isolation without affecting others
|
||||||
|
- **Traceability**: Clear history of what changed and when
|
||||||
|
- **Collaboration**: Multiple developers can work simultaneously without conflicts
|
||||||
|
|
||||||
|
The strategy follows a hierarchical promotion model: feature branches merge into development, which promotes to staging for testing, and finally to main for production release.
|
||||||
|
|
||||||
|
## Branch Types
|
||||||
|
|
||||||
|
### Main Branches
|
||||||
|
|
||||||
|
| Branch | Purpose | Stability | Direct Commits |
|
||||||
|
|--------|---------|-----------|----------------|
|
||||||
|
| `main` | Production releases | Highest | Never |
|
||||||
|
| `staging` | Pre-production testing | High | Never |
|
||||||
|
| `development` | Active development | Medium | Never |
|
||||||
|
|
||||||
|
### Feature Branches
|
||||||
|
|
||||||
|
Feature branches are short-lived branches created from `development` for implementing specific changes.
|
||||||
|
|
||||||
|
| Prefix | Purpose | Example |
|
||||||
|
|--------|---------|---------|
|
||||||
|
| `feat/` | New features | `feat/user-authentication` |
|
||||||
|
| `fix/` | Bug fixes | `fix/login-timeout-error` |
|
||||||
|
| `docs/` | Documentation only | `docs/api-reference` |
|
||||||
|
| `refactor/` | Code restructuring | `refactor/auth-module` |
|
||||||
|
| `test/` | Test additions/fixes | `test/auth-coverage` |
|
||||||
|
| `chore/` | Maintenance tasks | `chore/update-dependencies` |
|
||||||
|
|
||||||
|
### Special Branches
|
||||||
|
|
||||||
|
| Branch | Purpose | Created From | Merges Into |
|
||||||
|
|--------|---------|--------------|-------------|
|
||||||
|
| `hotfix/*` | Emergency production fixes | `main` | `main` AND `development` |
|
||||||
|
| `release/*` | Release preparation | `development` | `staging` then `main` |
|
||||||
|
|
||||||
|
## Branch Hierarchy
|
||||||
|
|
||||||
|
```
|
||||||
|
PRODUCTION
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌───────────────[ main ]───────────────┐
|
||||||
|
│ (stable releases) │
|
||||||
|
│ ▲ │
|
||||||
|
│ │ PR │
|
||||||
|
│ │ │
|
||||||
|
│ ┌───────┴───────┐ │
|
||||||
|
│ │ staging │ │
|
||||||
|
│ │ (testing) │ │
|
||||||
|
│ └───────▲───────┘ │
|
||||||
|
│ │ PR │
|
||||||
|
│ │ │
|
||||||
|
│ ┌─────────┴─────────┐ │
|
||||||
|
│ │ development │ │
|
||||||
|
│ │ (integration) │ │
|
||||||
|
│ └─────────▲─────────┘ │
|
||||||
|
│ ╱ │ ╲ │
|
||||||
|
│ PR╱ │PR ╲PR │
|
||||||
|
│ ╱ │ ╲ │
|
||||||
|
│ ┌─────┴──┐ ┌──┴───┐ ┌┴─────┐ │
|
||||||
|
│ │ feat/* │ │ fix/*│ │docs/*│ │
|
||||||
|
│ └────────┘ └──────┘ └──────┘ │
|
||||||
|
│ FEATURE BRANCHES │
|
||||||
|
└───────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## Workflow
|
||||||
|
|
||||||
|
### Feature Development
|
||||||
|
|
||||||
|
The standard workflow for implementing a new feature or fix:
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
gitGraph
|
||||||
|
commit id: "initial"
|
||||||
|
branch development
|
||||||
|
checkout development
|
||||||
|
commit id: "dev-base"
|
||||||
|
branch feat/new-feature
|
||||||
|
checkout feat/new-feature
|
||||||
|
commit id: "implement"
|
||||||
|
commit id: "tests"
|
||||||
|
commit id: "polish"
|
||||||
|
checkout development
|
||||||
|
merge feat/new-feature id: "PR merged"
|
||||||
|
commit id: "other-work"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Steps:**
|
||||||
|
|
||||||
|
1. **Create branch from development**
|
||||||
|
```bash
|
||||||
|
git checkout development
|
||||||
|
git pull origin development
|
||||||
|
git checkout -b feat/add-user-auth
|
||||||
|
```
|
||||||
|
Or use git-flow command:
|
||||||
|
```
|
||||||
|
/branch-start add user authentication
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Implement changes**
|
||||||
|
- Make commits following conventional commit format
|
||||||
|
- Keep commits atomic and focused
|
||||||
|
- Push regularly to remote
|
||||||
|
|
||||||
|
3. **Create Pull Request**
|
||||||
|
- Target: `development`
|
||||||
|
- Include description of changes
|
||||||
|
- Link related issues
|
||||||
|
|
||||||
|
4. **Review and merge**
|
||||||
|
- Address review feedback
|
||||||
|
- Squash or rebase as needed
|
||||||
|
- Merge when approved
|
||||||
|
|
||||||
|
5. **Cleanup**
|
||||||
|
- Delete feature branch after merge
|
||||||
|
```
|
||||||
|
/branch-cleanup
|
||||||
|
```
|
||||||
|
|
||||||
|
### Release Promotion
|
||||||
|
|
||||||
|
Promoting code from development to production:
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
gitGraph
|
||||||
|
commit id: "v1.0.0" tag: "v1.0.0"
|
||||||
|
branch development
|
||||||
|
checkout development
|
||||||
|
commit id: "feat-1"
|
||||||
|
commit id: "feat-2"
|
||||||
|
commit id: "fix-1"
|
||||||
|
branch staging
|
||||||
|
checkout staging
|
||||||
|
commit id: "staging-test"
|
||||||
|
checkout main
|
||||||
|
merge staging id: "v1.1.0" tag: "v1.1.0"
|
||||||
|
checkout development
|
||||||
|
merge main id: "sync"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Steps:**
|
||||||
|
|
||||||
|
1. **Prepare release**
|
||||||
|
- Ensure development is stable
|
||||||
|
- Update version numbers
|
||||||
|
- Update CHANGELOG.md
|
||||||
|
|
||||||
|
2. **Promote to staging**
|
||||||
|
```bash
|
||||||
|
git checkout staging
|
||||||
|
git merge development
|
||||||
|
git push origin staging
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Test in staging**
|
||||||
|
- Run integration tests
|
||||||
|
- Perform QA validation
|
||||||
|
- Fix any issues (merge fixes to development first, then re-promote)
|
||||||
|
|
||||||
|
4. **Promote to main**
|
||||||
|
```bash
|
||||||
|
git checkout main
|
||||||
|
git merge staging
|
||||||
|
git tag -a v1.1.0 -m "Release v1.1.0"
|
||||||
|
git push origin main --tags
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **Sync development**
|
||||||
|
```bash
|
||||||
|
git checkout development
|
||||||
|
git merge main
|
||||||
|
git push origin development
|
||||||
|
```
|
||||||
|
|
||||||
|
### Hotfix Handling
|
||||||
|
|
||||||
|
Emergency fixes for production issues:
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
gitGraph
|
||||||
|
commit id: "v1.0.0" tag: "v1.0.0"
|
||||||
|
branch development
|
||||||
|
checkout development
|
||||||
|
commit id: "ongoing-work"
|
||||||
|
checkout main
|
||||||
|
branch hotfix/critical-bug
|
||||||
|
checkout hotfix/critical-bug
|
||||||
|
commit id: "fix"
|
||||||
|
checkout main
|
||||||
|
merge hotfix/critical-bug id: "v1.0.1" tag: "v1.0.1"
|
||||||
|
checkout development
|
||||||
|
merge main id: "sync-fix"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Steps:**
|
||||||
|
|
||||||
|
1. **Create hotfix branch from main**
|
||||||
|
```bash
|
||||||
|
git checkout main
|
||||||
|
git pull origin main
|
||||||
|
git checkout -b hotfix/critical-security-fix
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Implement fix**
|
||||||
|
- Minimal, focused changes only
|
||||||
|
- Include tests for the fix
|
||||||
|
- Follow conventional commit format
|
||||||
|
|
||||||
|
3. **Merge to main**
|
||||||
|
```bash
|
||||||
|
git checkout main
|
||||||
|
git merge hotfix/critical-security-fix
|
||||||
|
git tag -a v1.0.1 -m "Hotfix: critical security fix"
|
||||||
|
git push origin main --tags
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Merge to development** (critical step)
|
||||||
|
```bash
|
||||||
|
git checkout development
|
||||||
|
git merge main
|
||||||
|
git push origin development
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **Delete hotfix branch**
|
||||||
|
```bash
|
||||||
|
git branch -d hotfix/critical-security-fix
|
||||||
|
git push origin --delete hotfix/critical-security-fix
|
||||||
|
```
|
||||||
|
|
||||||
|
## PR Requirements
|
||||||
|
|
||||||
|
### To development
|
||||||
|
|
||||||
|
| Requirement | Description |
|
||||||
|
|-------------|-------------|
|
||||||
|
| Passing tests | All CI tests must pass |
|
||||||
|
| Conventional commit | Message follows `type(scope): description` format |
|
||||||
|
| No conflicts | Branch must be rebased on latest development |
|
||||||
|
| Code review | At least one approval (recommended) |
|
||||||
|
|
||||||
|
### To staging
|
||||||
|
|
||||||
|
| Requirement | Description |
|
||||||
|
|-------------|-------------|
|
||||||
|
| All checks pass | CI, linting, tests, security scans |
|
||||||
|
| From development | Only PRs from development branch |
|
||||||
|
| Clean history | Squashed or rebased commits |
|
||||||
|
| Release notes | CHANGELOG updated with version |
|
||||||
|
|
||||||
|
### To main (Protected)
|
||||||
|
|
||||||
|
| Requirement | Description |
|
||||||
|
|-------------|-------------|
|
||||||
|
| Source restriction | PRs only from `staging` or `development` |
|
||||||
|
| All checks pass | Complete CI pipeline success |
|
||||||
|
| Approvals | Minimum 1-2 reviewer approvals |
|
||||||
|
| No direct push | Force push disabled |
|
||||||
|
| Version tag | Must include version tag on merge |
|
||||||
|
|
||||||
|
## Branch Flow Diagram
|
||||||
|
|
||||||
|
### Complete Lifecycle
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
subgraph Feature["Feature Development"]
|
||||||
|
A[Create feature branch] --> B[Implement changes]
|
||||||
|
B --> C[Push commits]
|
||||||
|
C --> D[Create PR to development]
|
||||||
|
D --> E{Review passed?}
|
||||||
|
E -->|No| B
|
||||||
|
E -->|Yes| F[Merge to development]
|
||||||
|
F --> G[Delete feature branch]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph Release["Release Cycle"]
|
||||||
|
H[development stable] --> I[Create PR to staging]
|
||||||
|
I --> J[Test in staging]
|
||||||
|
J --> K{Tests passed?}
|
||||||
|
K -->|No| L[Fix in development]
|
||||||
|
L --> H
|
||||||
|
K -->|Yes| M[Create PR to main]
|
||||||
|
M --> N[Tag release]
|
||||||
|
N --> O[Sync development]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph Hotfix["Hotfix Process"]
|
||||||
|
P[Critical bug in prod] --> Q[Create hotfix from main]
|
||||||
|
Q --> R[Implement fix]
|
||||||
|
R --> S[Merge to main + tag]
|
||||||
|
S --> T[Merge to development]
|
||||||
|
end
|
||||||
|
|
||||||
|
G --> H
|
||||||
|
O --> Feature
|
||||||
|
T --> Feature
|
||||||
|
```
|
||||||
|
|
||||||
|
### Release Cycle Timeline
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
gantt
|
||||||
|
title Release Cycle
|
||||||
|
dateFormat YYYY-MM-DD
|
||||||
|
section Feature Development
|
||||||
|
Feature A :a1, 2024-01-01, 5d
|
||||||
|
Feature B :a2, 2024-01-03, 4d
|
||||||
|
Bug Fix :a3, 2024-01-06, 2d
|
||||||
|
section Integration
|
||||||
|
Merge to development:b1, after a1, 1d
|
||||||
|
Integration testing :b2, after b1, 2d
|
||||||
|
section Release
|
||||||
|
Promote to staging :c1, after b2, 1d
|
||||||
|
QA testing :c2, after c1, 3d
|
||||||
|
Release to main :milestone, after c2, 0d
|
||||||
|
```
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Example 1: Adding a New Feature
|
||||||
|
|
||||||
|
**Scenario:** Add user password reset functionality
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Start from development
|
||||||
|
git checkout development
|
||||||
|
git pull origin development
|
||||||
|
|
||||||
|
# 2. Create feature branch
|
||||||
|
git checkout -b feat/password-reset
|
||||||
|
|
||||||
|
# 3. Make changes and commit
|
||||||
|
git add src/auth/password-reset.ts
|
||||||
|
git commit -m "feat(auth): add password reset request handler"
|
||||||
|
|
||||||
|
git add src/email/templates/reset-email.html
|
||||||
|
git commit -m "feat(email): add password reset email template"
|
||||||
|
|
||||||
|
git add tests/auth/password-reset.test.ts
|
||||||
|
git commit -m "test(auth): add password reset tests"
|
||||||
|
|
||||||
|
# 4. Push and create PR
|
||||||
|
git push -u origin feat/password-reset
|
||||||
|
# Create PR targeting development
|
||||||
|
|
||||||
|
# 5. After merge, cleanup
|
||||||
|
git checkout development
|
||||||
|
git pull origin development
|
||||||
|
git branch -d feat/password-reset
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example 2: Bug Fix
|
||||||
|
|
||||||
|
**Scenario:** Fix login timeout issue
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Create fix branch
|
||||||
|
git checkout development
|
||||||
|
git pull origin development
|
||||||
|
git checkout -b fix/login-timeout
|
||||||
|
|
||||||
|
# 2. Fix and commit
|
||||||
|
git add src/auth/session.ts
|
||||||
|
git commit -m "fix(auth): increase session timeout to 30 minutes
|
||||||
|
|
||||||
|
The default 5-minute timeout was causing frequent re-authentication.
|
||||||
|
Extended to 30 minutes based on user feedback.
|
||||||
|
|
||||||
|
Closes #456"
|
||||||
|
|
||||||
|
# 3. Push and create PR
|
||||||
|
git push -u origin fix/login-timeout
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example 3: Documentation Update
|
||||||
|
|
||||||
|
**Scenario:** Update API documentation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Create docs branch
|
||||||
|
git checkout development
|
||||||
|
git checkout -b docs/api-authentication
|
||||||
|
|
||||||
|
# 2. Update docs
|
||||||
|
git add docs/api/authentication.md
|
||||||
|
git commit -m "docs(api): document authentication endpoints
|
||||||
|
|
||||||
|
Add detailed documentation for:
|
||||||
|
- POST /auth/login
|
||||||
|
- POST /auth/logout
|
||||||
|
- POST /auth/refresh
|
||||||
|
- GET /auth/me"
|
||||||
|
|
||||||
|
# 3. Push and create PR
|
||||||
|
git push -u origin docs/api-authentication
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example 4: Emergency Hotfix
|
||||||
|
|
||||||
|
**Scenario:** Critical security vulnerability in production
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Create hotfix from main
|
||||||
|
git checkout main
|
||||||
|
git pull origin main
|
||||||
|
git checkout -b hotfix/sql-injection
|
||||||
|
|
||||||
|
# 2. Fix the issue
|
||||||
|
git add src/db/queries.ts
|
||||||
|
git commit -m "fix(security): sanitize SQL query parameters
|
||||||
|
|
||||||
|
CRITICAL: Addresses SQL injection vulnerability in user search.
|
||||||
|
All user inputs are now properly parameterized.
|
||||||
|
|
||||||
|
CVE: CVE-2024-XXXXX"
|
||||||
|
|
||||||
|
# 3. Merge to main with tag
|
||||||
|
git checkout main
|
||||||
|
git merge hotfix/sql-injection
|
||||||
|
git tag -a v2.1.1 -m "Hotfix: SQL injection vulnerability"
|
||||||
|
git push origin main --tags
|
||||||
|
|
||||||
|
# 4. Sync to development (IMPORTANT!)
|
||||||
|
git checkout development
|
||||||
|
git merge main
|
||||||
|
git push origin development
|
||||||
|
|
||||||
|
# 5. Cleanup
|
||||||
|
git branch -d hotfix/sql-injection
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example 5: Release Promotion
|
||||||
|
|
||||||
|
**Scenario:** Release version 2.2.0
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Ensure development is ready
|
||||||
|
git checkout development
|
||||||
|
git pull origin development
|
||||||
|
# Run full test suite
|
||||||
|
npm test
|
||||||
|
|
||||||
|
# 2. Update version and changelog
|
||||||
|
# Edit package.json, CHANGELOG.md
|
||||||
|
git add package.json CHANGELOG.md
|
||||||
|
git commit -m "chore(release): prepare v2.2.0"
|
||||||
|
|
||||||
|
# 3. Promote to staging
|
||||||
|
git checkout staging
|
||||||
|
git merge development
|
||||||
|
git push origin staging
|
||||||
|
|
||||||
|
# 4. After QA approval, promote to main
|
||||||
|
git checkout main
|
||||||
|
git merge staging
|
||||||
|
git tag -a v2.2.0 -m "Release v2.2.0"
|
||||||
|
git push origin main --tags
|
||||||
|
|
||||||
|
# 5. Sync development
|
||||||
|
git checkout development
|
||||||
|
git merge main
|
||||||
|
git push origin development
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
### Environment Variables
|
||||||
|
|
||||||
|
Configure the branching strategy via environment variables:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Default base branch for new features
|
||||||
|
GIT_DEFAULT_BASE=development
|
||||||
|
|
||||||
|
# Protected branches (comma-separated)
|
||||||
|
GIT_PROTECTED_BRANCHES=main,master,development,staging,production
|
||||||
|
|
||||||
|
# Workflow style
|
||||||
|
GIT_WORKFLOW_STYLE=feature-branch
|
||||||
|
|
||||||
|
# Auto-delete merged branches
|
||||||
|
GIT_AUTO_DELETE_MERGED=true
|
||||||
|
```
|
||||||
|
|
||||||
|
### Per-Project Configuration
|
||||||
|
|
||||||
|
Create `.git-flow.json` in project root:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"defaultBase": "development",
|
||||||
|
"protectedBranches": ["main", "staging", "development"],
|
||||||
|
"branchPrefixes": ["feat", "fix", "docs", "refactor", "test", "chore"],
|
||||||
|
"requirePR": {
|
||||||
|
"main": true,
|
||||||
|
"staging": true,
|
||||||
|
"development": false
|
||||||
|
},
|
||||||
|
"squashOnMerge": {
|
||||||
|
"development": true,
|
||||||
|
"staging": false,
|
||||||
|
"main": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
### Do
|
||||||
|
|
||||||
|
- Keep feature branches short-lived (< 1 week)
|
||||||
|
- Rebase feature branches on development regularly
|
||||||
|
- Write descriptive commit messages
|
||||||
|
- Delete branches after merging
|
||||||
|
- Tag all releases on main
|
||||||
|
- Always sync development after hotfixes
|
||||||
|
|
||||||
|
### Avoid
|
||||||
|
|
||||||
|
- Long-lived feature branches
|
||||||
|
- Direct commits to protected branches
|
||||||
|
- Force pushing to shared branches
|
||||||
|
- Merging untested code to staging
|
||||||
|
- Skipping the development sync after hotfixes
|
||||||
|
|
||||||
|
## Related Documentation
|
||||||
|
|
||||||
|
- [Branching Strategies Skill](/plugins/git-flow/skills/workflow-patterns/branching-strategies.md)
|
||||||
|
- [git-flow README](/plugins/git-flow/README.md)
|
||||||
|
- [Conventional Commits](https://www.conventionalcommits.org/)
|
||||||
102
plugins/git-flow/hooks/branch-check.sh
Executable file
102
plugins/git-flow/hooks/branch-check.sh
Executable file
@@ -0,0 +1,102 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# git-flow branch name validation hook
|
||||||
|
# Validates branch names follow the convention: <type>/<description>
|
||||||
|
# Command hook - guaranteed predictable behavior
|
||||||
|
|
||||||
|
# Read tool input from stdin (JSON format)
|
||||||
|
INPUT=$(cat)
|
||||||
|
|
||||||
|
# Extract command from JSON input
|
||||||
|
# The Bash tool sends {"command": "..."} format
|
||||||
|
COMMAND=$(echo "$INPUT" | grep -o '"command"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"command"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/')
|
||||||
|
|
||||||
|
# If no command found, exit silently (allow)
|
||||||
|
if [ -z "$COMMAND" ]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if this is a branch creation command
|
||||||
|
# Patterns: git checkout -b, git branch (without -d/-D), git switch -c/-C
|
||||||
|
IS_BRANCH_CREATE=false
|
||||||
|
BRANCH_NAME=""
|
||||||
|
|
||||||
|
# git checkout -b <branch>
|
||||||
|
if echo "$COMMAND" | grep -qE 'git\s+checkout\s+(-b|--branch)\s+'; then
|
||||||
|
IS_BRANCH_CREATE=true
|
||||||
|
BRANCH_NAME=$(echo "$COMMAND" | sed -n 's/.*git\s\+checkout\s\+\(-b\|--branch\)\s\+\([^ ]*\).*/\2/p')
|
||||||
|
fi
|
||||||
|
|
||||||
|
# git switch -c/-C <branch>
|
||||||
|
if echo "$COMMAND" | grep -qE 'git\s+switch\s+(-c|-C|--create|--force-create)\s+'; then
|
||||||
|
IS_BRANCH_CREATE=true
|
||||||
|
BRANCH_NAME=$(echo "$COMMAND" | sed -n 's/.*git\s\+switch\s\+\(-c\|-C\|--create\|--force-create\)\s\+\([^ ]*\).*/\2/p')
|
||||||
|
fi
|
||||||
|
|
||||||
|
# git branch <name> (without -d/-D/-m/-M which are delete/rename)
|
||||||
|
if echo "$COMMAND" | grep -qE 'git\s+branch\s+[^-]' && ! echo "$COMMAND" | grep -qE 'git\s+branch\s+(-d|-D|-m|-M|--delete|--move|--list|--show-current)'; then
|
||||||
|
IS_BRANCH_CREATE=true
|
||||||
|
BRANCH_NAME=$(echo "$COMMAND" | sed -n 's/.*git\s\+branch\s\+\([^ -][^ ]*\).*/\1/p')
|
||||||
|
fi
|
||||||
|
|
||||||
|
# If not a branch creation command, exit silently (allow)
|
||||||
|
if [ "$IS_BRANCH_CREATE" = false ]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# If we couldn't extract the branch name, exit silently (allow)
|
||||||
|
if [ -z "$BRANCH_NAME" ]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Remove any quotes from branch name
|
||||||
|
BRANCH_NAME=$(echo "$BRANCH_NAME" | tr -d '"' | tr -d "'")
|
||||||
|
|
||||||
|
# Skip validation for special branches
|
||||||
|
case "$BRANCH_NAME" in
|
||||||
|
main|master|develop|development|staging|release|hotfix)
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Allowed branch types
|
||||||
|
VALID_TYPES="feat|fix|chore|docs|refactor|test|perf|debug"
|
||||||
|
|
||||||
|
# Validate branch name format: <type>/<description>
|
||||||
|
# Description: lowercase letters, numbers, hyphens only, max 50 chars total
|
||||||
|
if ! echo "$BRANCH_NAME" | grep -qE "^($VALID_TYPES)/[a-z0-9][a-z0-9-]*$"; then
|
||||||
|
echo ""
|
||||||
|
echo "[git-flow] Branch name validation failed"
|
||||||
|
echo ""
|
||||||
|
echo "Branch: $BRANCH_NAME"
|
||||||
|
echo ""
|
||||||
|
echo "Expected format: <type>/<description>"
|
||||||
|
echo ""
|
||||||
|
echo "Valid types: feat, fix, chore, docs, refactor, test, perf, debug"
|
||||||
|
echo ""
|
||||||
|
echo "Description rules:"
|
||||||
|
echo " - Lowercase letters, numbers, and hyphens only"
|
||||||
|
echo " - Must start with letter or number"
|
||||||
|
echo " - No spaces or special characters"
|
||||||
|
echo ""
|
||||||
|
echo "Examples:"
|
||||||
|
echo " feat/add-user-auth"
|
||||||
|
echo " fix/login-timeout"
|
||||||
|
echo " chore/update-deps"
|
||||||
|
echo " docs/api-reference"
|
||||||
|
echo ""
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check total length (max 50 chars)
|
||||||
|
if [ ${#BRANCH_NAME} -gt 50 ]; then
|
||||||
|
echo ""
|
||||||
|
echo "[git-flow] Branch name too long"
|
||||||
|
echo ""
|
||||||
|
echo "Branch: $BRANCH_NAME (${#BRANCH_NAME} chars)"
|
||||||
|
echo "Maximum: 50 characters"
|
||||||
|
echo ""
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Valid branch name
|
||||||
|
exit 0
|
||||||
74
plugins/git-flow/hooks/commit-msg-check.sh
Executable file
74
plugins/git-flow/hooks/commit-msg-check.sh
Executable file
@@ -0,0 +1,74 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# git-flow commit message validation hook
|
||||||
|
# Validates git commit messages follow conventional commit format
|
||||||
|
# PreToolUse hook for Bash commands - type: command
|
||||||
|
|
||||||
|
# Read tool input from stdin
|
||||||
|
INPUT=$(cat)
|
||||||
|
|
||||||
|
# Use Python to properly parse JSON and extract the command
|
||||||
|
COMMAND=$(echo "$INPUT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('command',''))" 2>/dev/null)
|
||||||
|
|
||||||
|
# If no command or python failed, allow through
|
||||||
|
if [ -z "$COMMAND" ]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if it is a git commit command with -m flag
|
||||||
|
if ! echo "$COMMAND" | grep -qE 'git\s+commit.*-m'; then
|
||||||
|
# Not a git commit with -m, allow through
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Extract commit message - handle various quoting styles
|
||||||
|
# Try double quotes first
|
||||||
|
COMMIT_MSG=$(echo "$COMMAND" | sed -n 's/.*-m[[:space:]]*"\([^"]*\)".*/\1/p')
|
||||||
|
# If empty, try single quotes
|
||||||
|
if [ -z "$COMMIT_MSG" ]; then
|
||||||
|
COMMIT_MSG=$(echo "$COMMAND" | sed -n "s/.*-m[[:space:]]*'\\([^']*\\)'.*/\\1/p")
|
||||||
|
fi
|
||||||
|
# If still empty, try HEREDOC pattern
|
||||||
|
if [ -z "$COMMIT_MSG" ]; then
|
||||||
|
if echo "$COMMAND" | grep -qE -- '-m[[:space:]]+"\$\(cat <<'; then
|
||||||
|
# HEREDOC pattern - too complex to parse, allow through
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# If no message extracted, allow through
|
||||||
|
if [ -z "$COMMIT_MSG" ]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Validate conventional commit format
|
||||||
|
# Format: <type>(<scope>): <description>
|
||||||
|
# or: <type>: <description>
|
||||||
|
# Valid types: feat, fix, docs, style, refactor, perf, test, chore, build, ci
|
||||||
|
|
||||||
|
VALID_TYPES="feat|fix|docs|style|refactor|perf|test|chore|build|ci"
|
||||||
|
|
||||||
|
# Check if message matches conventional commit format
|
||||||
|
if echo "$COMMIT_MSG" | grep -qE "^($VALID_TYPES)(\([a-zA-Z0-9_-]+\))?:[[:space:]]+.+"; then
|
||||||
|
# Valid format
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Invalid format - output warning
|
||||||
|
echo "[git-flow] WARNING: Commit message does not follow conventional commit format"
|
||||||
|
echo ""
|
||||||
|
echo "Expected format: <type>(<scope>): <description>"
|
||||||
|
echo " or: <type>: <description>"
|
||||||
|
echo ""
|
||||||
|
echo "Valid types: feat, fix, docs, style, refactor, perf, test, chore, build, ci"
|
||||||
|
echo ""
|
||||||
|
echo "Examples:"
|
||||||
|
echo " feat(auth): add password reset functionality"
|
||||||
|
echo " fix: resolve login timeout issue"
|
||||||
|
echo " docs(readme): update installation instructions"
|
||||||
|
echo ""
|
||||||
|
echo "Your message: $COMMIT_MSG"
|
||||||
|
echo ""
|
||||||
|
echo "To proceed anyway, use /commit command which auto-generates valid messages."
|
||||||
|
|
||||||
|
# Exit with non-zero to block
|
||||||
|
exit 1
|
||||||
19
plugins/git-flow/hooks/hooks.json
Normal file
19
plugins/git-flow/hooks/hooks.json
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"hooks": {
|
||||||
|
"PreToolUse": [
|
||||||
|
{
|
||||||
|
"matcher": "Bash",
|
||||||
|
"hooks": [
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/branch-check.sh"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/commit-msg-check.sh"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,12 +1,8 @@
|
|||||||
{
|
{
|
||||||
"mcpServers": {
|
"mcpServers": {
|
||||||
"gitea": {
|
"gitea": {
|
||||||
"command": "${CLAUDE_PLUGIN_ROOT}/mcp-servers/gitea/.venv/bin/python",
|
"command": "${CLAUDE_PLUGIN_ROOT}/mcp-servers/gitea/run.sh",
|
||||||
"args": ["-m", "mcp_server.server"],
|
"args": []
|
||||||
"cwd": "${CLAUDE_PLUGIN_ROOT}/mcp-servers/gitea",
|
|
||||||
"env": {
|
|
||||||
"PYTHONPATH": "${CLAUDE_PLUGIN_ROOT}/mcp-servers/gitea"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ pr-review conducts comprehensive code reviews using specialized agents for secur
|
|||||||
| `/pr-review <pr#>` | Full multi-agent review |
|
| `/pr-review <pr#>` | Full multi-agent review |
|
||||||
| `/pr-summary <pr#>` | Quick summary without full review |
|
| `/pr-summary <pr#>` | Quick summary without full review |
|
||||||
| `/pr-findings <pr#>` | Filter findings by category/confidence |
|
| `/pr-findings <pr#>` | Filter findings by category/confidence |
|
||||||
|
| `/pr-diff <pr#>` | View diff with inline comment annotations |
|
||||||
| `/initial-setup` | Full interactive setup wizard |
|
| `/initial-setup` | Full interactive setup wizard |
|
||||||
| `/project-init` | Quick project setup (system already configured) |
|
| `/project-init` | Quick project setup (system already configured) |
|
||||||
| `/project-sync` | Sync configuration with current git remote |
|
| `/project-sync` | Sync configuration with current git remote |
|
||||||
@@ -51,14 +52,38 @@ Requires Gitea MCP server configuration.
|
|||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
|
Environment variables can be set in your project's `.env` file or shell environment.
|
||||||
|
|
||||||
|
| Variable | Default | Description |
|
||||||
|
|----------|---------|-------------|
|
||||||
|
| `PR_REVIEW_CONFIDENCE_THRESHOLD` | `0.7` | Minimum confidence score (0.0-1.0) for reporting findings. Findings below this threshold are filtered out to reduce noise. |
|
||||||
|
| `PR_REVIEW_AUTO_SUBMIT` | `false` | Automatically submit review to Gitea without confirmation prompt |
|
||||||
|
|
||||||
|
### Example Configuration
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Minimum confidence to report (default: 0.5)
|
# Project .env file
|
||||||
PR_REVIEW_CONFIDENCE_THRESHOLD=0.5
|
|
||||||
|
# Only show high-confidence findings (MEDIUM and HIGH)
|
||||||
|
PR_REVIEW_CONFIDENCE_THRESHOLD=0.7
|
||||||
|
|
||||||
# Auto-submit review to Gitea (default: false)
|
# Auto-submit review to Gitea (default: false)
|
||||||
PR_REVIEW_AUTO_SUBMIT=false
|
PR_REVIEW_AUTO_SUBMIT=false
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Confidence Threshold Details
|
||||||
|
|
||||||
|
The confidence threshold filters which findings appear in review output:
|
||||||
|
|
||||||
|
| Threshold | Effect |
|
||||||
|
|-----------|--------|
|
||||||
|
| `0.9` | Only definite issues (HIGH confidence) |
|
||||||
|
| `0.7` | Likely issues and above (MEDIUM+HIGH) - **recommended** |
|
||||||
|
| `0.5` | Include possible concerns (LOW+MEDIUM+HIGH) |
|
||||||
|
| `0.3` | Include speculative findings |
|
||||||
|
|
||||||
|
Lower thresholds show more findings but may include false positives. Higher thresholds reduce noise but may miss some valid concerns.
|
||||||
|
|
||||||
## Usage Examples
|
## Usage Examples
|
||||||
|
|
||||||
### Full Review
|
### Full Review
|
||||||
|
|||||||
@@ -120,10 +120,13 @@ Focus on findings that:
|
|||||||
|
|
||||||
### Respect Confidence Thresholds
|
### Respect Confidence Thresholds
|
||||||
|
|
||||||
Never report findings below 0.5 confidence. Be transparent about uncertainty:
|
Filter findings based on `PR_REVIEW_CONFIDENCE_THRESHOLD` (default: 0.7). Be transparent about uncertainty:
|
||||||
- 0.9+ → "This is definitely an issue"
|
- 0.9+ → "This is definitely an issue" (HIGH)
|
||||||
- 0.7-0.89 → "This is likely an issue"
|
- 0.7-0.89 → "This is likely an issue" (MEDIUM)
|
||||||
- 0.5-0.69 → "This might be an issue"
|
- 0.5-0.69 → "This might be an issue" (LOW)
|
||||||
|
- < threshold → Filtered from output
|
||||||
|
|
||||||
|
With the default threshold of 0.7, only MEDIUM and HIGH confidence findings are reported.
|
||||||
|
|
||||||
### Avoid Noise
|
### Avoid Noise
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ This project uses the pr-review plugin for automated code review.
|
|||||||
| `/pr-review <pr#>` | Full multi-agent review |
|
| `/pr-review <pr#>` | Full multi-agent review |
|
||||||
| `/pr-summary <pr#>` | Quick change summary |
|
| `/pr-summary <pr#>` | Quick change summary |
|
||||||
| `/pr-findings <pr#>` | Filter review findings |
|
| `/pr-findings <pr#>` | Filter review findings |
|
||||||
|
| `/pr-diff <pr#>` | View diff with inline comments |
|
||||||
|
|
||||||
### Review Categories
|
### Review Categories
|
||||||
|
|
||||||
@@ -26,11 +27,16 @@ Reviews analyze:
|
|||||||
|
|
||||||
### Confidence Threshold
|
### Confidence Threshold
|
||||||
|
|
||||||
Findings below 0.5 confidence are suppressed.
|
Configure via `PR_REVIEW_CONFIDENCE_THRESHOLD` (default: 0.7).
|
||||||
|
|
||||||
- HIGH (0.9+): Definite issue
|
| Range | Label | Action |
|
||||||
- MEDIUM (0.7-0.89): Likely issue
|
|-------|-------|--------|
|
||||||
- LOW (0.5-0.69): Possible concern
|
| 0.9 - 1.0 | HIGH | Must address |
|
||||||
|
| 0.7 - 0.89 | MEDIUM | Should address |
|
||||||
|
| 0.5 - 0.69 | LOW | Consider addressing |
|
||||||
|
| < threshold | (filtered) | Not reported |
|
||||||
|
|
||||||
|
With default threshold of 0.7, only MEDIUM and HIGH findings are shown.
|
||||||
|
|
||||||
### Verdict Rules
|
### Verdict Rules
|
||||||
|
|
||||||
|
|||||||
154
plugins/pr-review/commands/pr-diff.md
Normal file
154
plugins/pr-review/commands/pr-diff.md
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
# /pr-diff - Annotated PR Diff Viewer
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
Display the PR diff with inline annotations from review comments, making it easy to see what feedback has been given alongside the code changes.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
/pr-diff <pr-number> [--repo owner/repo] [--context <lines>]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
```
|
||||||
|
--repo <owner/repo> Override repository (default: from .env)
|
||||||
|
--context <n> Lines of context around changes (default: 3)
|
||||||
|
--no-comments Show diff without comment annotations
|
||||||
|
--file <pattern> Filter to specific files (glob pattern)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Behavior
|
||||||
|
|
||||||
|
### Step 1: Fetch PR Data
|
||||||
|
|
||||||
|
Using Gitea MCP tools:
|
||||||
|
1. `get_pr_diff` - Unified diff of all changes
|
||||||
|
2. `get_pr_comments` - All review comments on the PR
|
||||||
|
|
||||||
|
### Step 2: Parse and Annotate
|
||||||
|
|
||||||
|
Parse the diff and overlay comments at their respective file/line positions:
|
||||||
|
|
||||||
|
```
|
||||||
|
═══════════════════════════════════════════════════
|
||||||
|
PR #123 Diff - Add user authentication
|
||||||
|
═══════════════════════════════════════════════════
|
||||||
|
|
||||||
|
Branch: feat/user-auth → development
|
||||||
|
Files: 12 changed (+234 / -45)
|
||||||
|
|
||||||
|
───────────────────────────────────────────────────
|
||||||
|
src/api/users.ts (+85 / -12)
|
||||||
|
───────────────────────────────────────────────────
|
||||||
|
|
||||||
|
@@ -42,6 +42,15 @@ export async function getUser(id: string) {
|
||||||
|
42 │ const db = getDatabase();
|
||||||
|
43 │
|
||||||
|
44 │- const user = db.query("SELECT * FROM users WHERE id = " + id);
|
||||||
|
│ ┌─────────────────────────────────────────────────────────────
|
||||||
|
│ │ COMMENT by @reviewer (2h ago):
|
||||||
|
│ │ This is a SQL injection vulnerability. Use parameterized
|
||||||
|
│ │ queries instead: `db.query("SELECT * FROM users WHERE id = ?", [id])`
|
||||||
|
│ └─────────────────────────────────────────────────────────────
|
||||||
|
45 │+ const query = "SELECT * FROM users WHERE id = ?";
|
||||||
|
46 │+ const user = db.query(query, [id]);
|
||||||
|
47 │
|
||||||
|
48 │ if (!user) {
|
||||||
|
49 │ throw new NotFoundError("User not found");
|
||||||
|
50 │ }
|
||||||
|
|
||||||
|
@@ -78,3 +87,12 @@ export async function updateUser(id: string, data: UserInput) {
|
||||||
|
87 │+ // Validate input before update
|
||||||
|
88 │+ validateUserInput(data);
|
||||||
|
89 │+
|
||||||
|
90 │+ const result = db.query(
|
||||||
|
91 │+ "UPDATE users SET name = ?, email = ? WHERE id = ?",
|
||||||
|
92 │+ [data.name, data.email, id]
|
||||||
|
93 │+ );
|
||||||
|
│ ┌─────────────────────────────────────────────────────────────
|
||||||
|
│ │ COMMENT by @maintainer (1h ago):
|
||||||
|
│ │ Good use of parameterized query here!
|
||||||
|
│ │
|
||||||
|
│ │ REPLY by @author (30m ago):
|
||||||
|
│ │ Thanks! Applied the same pattern throughout.
|
||||||
|
│ └─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
───────────────────────────────────────────────────
|
||||||
|
src/components/LoginForm.tsx (+65 / -0) [NEW FILE]
|
||||||
|
───────────────────────────────────────────────────
|
||||||
|
|
||||||
|
@@ -0,0 +1,65 @@
|
||||||
|
1 │+import React, { useState } from 'react';
|
||||||
|
2 │+import { useAuth } from '../context/AuthContext';
|
||||||
|
3 │+
|
||||||
|
4 │+export function LoginForm() {
|
||||||
|
5 │+ const [email, setEmail] = useState('');
|
||||||
|
6 │+ const [password, setPassword] = useState('');
|
||||||
|
7 │+ const { login } = useAuth();
|
||||||
|
|
||||||
|
... (remaining diff content)
|
||||||
|
|
||||||
|
═══════════════════════════════════════════════════
|
||||||
|
Comment Summary: 5 comments, 2 resolved
|
||||||
|
═══════════════════════════════════════════════════
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 3: Filter by Confidence (Optional)
|
||||||
|
|
||||||
|
If `PR_REVIEW_CONFIDENCE_THRESHOLD` is set, also annotate with high-confidence findings from previous reviews:
|
||||||
|
|
||||||
|
```
|
||||||
|
44 │- const user = db.query("SELECT * FROM users WHERE id = " + id);
|
||||||
|
│ ┌─── REVIEW FINDING (0.95 HIGH) ─────────────────────────────
|
||||||
|
│ │ [SEC-001] SQL Injection Vulnerability
|
||||||
|
│ │ Use parameterized queries to prevent injection attacks.
|
||||||
|
│ └─────────────────────────────────────────────────────────────
|
||||||
|
│ ┌─── COMMENT by @reviewer ────────────────────────────────────
|
||||||
|
│ │ This is a SQL injection vulnerability...
|
||||||
|
│ └─────────────────────────────────────────────────────────────
|
||||||
|
```
|
||||||
|
|
||||||
|
## Output Formats
|
||||||
|
|
||||||
|
### Default (Annotated Diff)
|
||||||
|
|
||||||
|
Full diff with inline comments as shown above.
|
||||||
|
|
||||||
|
### Plain (--no-comments)
|
||||||
|
|
||||||
|
```
|
||||||
|
/pr-diff 123 --no-comments
|
||||||
|
|
||||||
|
# Standard unified diff output without annotations
|
||||||
|
```
|
||||||
|
|
||||||
|
### File Filter (--file)
|
||||||
|
|
||||||
|
```
|
||||||
|
/pr-diff 123 --file "src/api/*"
|
||||||
|
|
||||||
|
# Shows diff only for files matching pattern
|
||||||
|
```
|
||||||
|
|
||||||
|
## Use Cases
|
||||||
|
|
||||||
|
- **Review preparation**: See the full context of changes with existing feedback
|
||||||
|
- **Followup work**: Understand what was commented on and where
|
||||||
|
- **Discussion context**: View threaded conversations alongside the code
|
||||||
|
- **Progress tracking**: See which comments have been resolved
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
| Variable | Default | Description |
|
||||||
|
|----------|---------|-------------|
|
||||||
|
| `PR_REVIEW_CONFIDENCE_THRESHOLD` | `0.7` | Minimum confidence for showing review findings |
|
||||||
|
|
||||||
|
## Related Commands
|
||||||
|
|
||||||
|
| Command | Purpose |
|
||||||
|
|---------|---------|
|
||||||
|
| `/pr-summary` | Quick overview without diff |
|
||||||
|
| `/pr-review` | Full multi-agent review |
|
||||||
|
| `/pr-findings` | Filter review findings by category |
|
||||||
@@ -46,14 +46,16 @@ Collect findings from all agents, each with:
|
|||||||
|
|
||||||
### Step 4: Filter by Confidence
|
### Step 4: Filter by Confidence
|
||||||
|
|
||||||
Only display findings with confidence >= 0.5:
|
Filter findings based on `PR_REVIEW_CONFIDENCE_THRESHOLD` (default: 0.7):
|
||||||
|
|
||||||
| Confidence | Label | Description |
|
| Confidence | Label | Description |
|
||||||
|------------|-------|-------------|
|
|------------|-------|-------------|
|
||||||
| 0.9 - 1.0 | HIGH | Definite issue, must address |
|
| 0.9 - 1.0 | HIGH | Definite issue, must address |
|
||||||
| 0.7 - 0.89 | MEDIUM | Likely issue, should address |
|
| 0.7 - 0.89 | MEDIUM | Likely issue, should address |
|
||||||
| 0.5 - 0.69 | LOW | Possible concern, consider addressing |
|
| 0.5 - 0.69 | LOW | Possible concern, consider addressing |
|
||||||
| < 0.5 | (suppressed) | Too uncertain to report |
|
| < threshold | (filtered) | Below configured threshold |
|
||||||
|
|
||||||
|
**Note:** With the default threshold of 0.7, only MEDIUM and HIGH confidence findings are shown. Adjust `PR_REVIEW_CONFIDENCE_THRESHOLD` to include more or fewer findings.
|
||||||
|
|
||||||
### Step 5: Generate Report
|
### Step 5: Generate Report
|
||||||
|
|
||||||
@@ -135,5 +137,5 @@ Full review report with:
|
|||||||
|
|
||||||
| Variable | Default | Description |
|
| Variable | Default | Description |
|
||||||
|----------|---------|-------------|
|
|----------|---------|-------------|
|
||||||
| `PR_REVIEW_CONFIDENCE_THRESHOLD` | `0.5` | Minimum confidence to report |
|
| `PR_REVIEW_CONFIDENCE_THRESHOLD` | `0.7` | Minimum confidence to report (0.0-1.0) |
|
||||||
| `PR_REVIEW_AUTO_SUBMIT` | `false` | Auto-submit to Gitea |
|
| `PR_REVIEW_AUTO_SUBMIT` | `false` | Auto-submit to Gitea |
|
||||||
|
|||||||
@@ -73,10 +73,12 @@ Base confidence by pattern:
|
|||||||
|
|
||||||
## Threshold Configuration
|
## Threshold Configuration
|
||||||
|
|
||||||
The default threshold is 0.5. This can be adjusted:
|
The default threshold is 0.7 (showing MEDIUM and HIGH confidence findings). This can be adjusted:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
PR_REVIEW_CONFIDENCE_THRESHOLD=0.7 # Only high-confidence
|
PR_REVIEW_CONFIDENCE_THRESHOLD=0.9 # Only definite issues (HIGH)
|
||||||
|
PR_REVIEW_CONFIDENCE_THRESHOLD=0.7 # Likely issues and above (MEDIUM+HIGH) - default
|
||||||
|
PR_REVIEW_CONFIDENCE_THRESHOLD=0.5 # Include possible concerns (LOW+)
|
||||||
PR_REVIEW_CONFIDENCE_THRESHOLD=0.3 # Include more speculative
|
PR_REVIEW_CONFIDENCE_THRESHOLD=0.3 # Include more speculative
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "projman",
|
"name": "projman",
|
||||||
"version": "3.1.0",
|
"version": "3.2.0",
|
||||||
"description": "Sprint planning and project management with Gitea integration",
|
"description": "Sprint planning and project management with Gitea integration",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Leo Miranda",
|
"name": "Leo Miranda",
|
||||||
|
|||||||
@@ -1,12 +1,8 @@
|
|||||||
{
|
{
|
||||||
"mcpServers": {
|
"mcpServers": {
|
||||||
"gitea": {
|
"gitea": {
|
||||||
"command": "${CLAUDE_PLUGIN_ROOT}/mcp-servers/gitea/.venv/bin/python",
|
"command": "${CLAUDE_PLUGIN_ROOT}/mcp-servers/gitea/run.sh",
|
||||||
"args": ["-m", "mcp_server.server"],
|
"args": []
|
||||||
"cwd": "${CLAUDE_PLUGIN_ROOT}/mcp-servers/gitea",
|
|
||||||
"env": {
|
|
||||||
"PYTHONPATH": "${CLAUDE_PLUGIN_ROOT}/mcp-servers/gitea"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -278,6 +278,17 @@ Investigate diagnostic issues and propose fixes with human approval.
|
|||||||
|
|
||||||
**When to use:** In the marketplace repo, to investigate and fix issues reported by `/debug-report`.
|
**When to use:** In the marketplace repo, to investigate and fix issues reported by `/debug-report`.
|
||||||
|
|
||||||
|
### `/suggest-version`
|
||||||
|
Analyze CHANGELOG and recommend semantic version bump.
|
||||||
|
|
||||||
|
**What it does:**
|
||||||
|
- Reads CHANGELOG.md `[Unreleased]` section
|
||||||
|
- Analyzes changes to determine bump type (major/minor/patch)
|
||||||
|
- Applies SemVer rules: breaking changes → major, features → minor, fixes → patch
|
||||||
|
- Returns recommended version with rationale
|
||||||
|
|
||||||
|
**When to use:** Before creating a release to determine the appropriate version number.
|
||||||
|
|
||||||
## Code Quality Commands
|
## Code Quality Commands
|
||||||
|
|
||||||
The `/review` and `/test-check` commands complement the Executor agent by catching issues before work is marked complete. Run both commands before `/sprint-close` for a complete quality check.
|
The `/review` and `/test-check` commands complement the Executor agent by catching issues before work is marked complete. Run both commands before `/sprint-close` for a complete quality check.
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user