22 Commits

Author SHA1 Message Date
becf9b97b2 Merge pull request 'feat(netbox)!: gut server from 182 to 37 tools' (#457) from feat/netbox-gut-to-37-tools into development
Reviewed-on: #457
2026-02-09 02:28:27 +00:00
8129be5ef3 feat(netbox)!: gut server from 182 to 37 tools
BREAKING CHANGE: Removed circuits, tenancy, VPN, wireless modules entirely.
Stripped DCIM, IPAM, virtualization, extras to only essential tools.
Deleted NETBOX_ENABLED_MODULES filtering — no longer needed.

- Delete circuits.py, tenancy.py, vpn.py, wireless.py
- Strip dcim.py to sites, devices, interfaces only (11 tools)
- Strip ipam.py to IPs, prefixes, services only (10 tools)
- Strip virtualization.py to clusters, VMs, VM interfaces only (10 tools)
- Strip extras.py to tags, journal entries only (6 tools)
- Remove all module filtering code from config.py and server.py
- Rewrite README.md with accurate 37-tool documentation
- Update CHANGELOG.md with breaking change entry
- Token reduction: ~19,810 → ~3,700 (~81%)

Remaining work: Update cmdb-assistant plugin skills (10 files) in follow-up PR

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-08 21:25:43 -05:00
826f0ec674 Merge pull request 'fix(commands): wire dispatch files to real sub-command names via Skill tool' (#455) from fix/dispatch-routing into development
Reviewed-on: #455
2026-02-09 02:08:34 +00:00
5bbcee06ac fix(commands): wire dispatch files to real sub-command names via Skill tool
All dispatch files referenced display names (e.g., `/doc audit`) that don't
correspond to real slash commands. Claude Code resolves commands by filename,
so `/doc audit` loads `doc.md` with $ARGUMENTS but never routes to `doc-audit.md`.

Changes:
- Updated 24 dispatch files with explicit Skill tool routing
- Added "Command to Invoke" column with plugin-prefixed names
- Added $ARGUMENTS matching for automatic routing
- RFC dispatch file uses inline handling (no separate command files)
- Updated COMMANDS-CHEATSHEET.md with invocation methods
- Updated MIGRATION-v9.md with command name mapping explanation

Affects: 25 dispatch files across 12 plugins (core, data, saas, ops, debug)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-08 21:04:24 -05:00
93d93fcf09 fix(docs): update stale mcp_server references for gitea-mcp migration
- DEBUGGING-CHECKLIST: gitea test uses gitea_mcp.server (not mcp_server)
- debug-mcp skills: generic module placeholder, gitea exception noted
- Removed orphan .doc-guardian-queue from projman

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-08 19:55:19 -05:00
30e424306c Merge pull request 'feat: Marketplace v9.2.0 — dispatch fix + git-flow setup + gitea-mcp migration' (#453) from feat/v9.2.0-dispatch-setup-migration into development
Reviewed-on: #453
2026-02-09 00:22:58 +00:00
f2bc1fc5d4 feat(gitea): migrate to published gitea-mcp package from Gitea PyPI
mcp-servers/gitea/ thinned to venv wrapper — source code removed,
package installed from registry. run.sh updated from mcp_server.server
to gitea_mcp.server. requirements.txt points to Gitea PyPI.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 19:14:56 -05:00
f87fc2537d feat(git-flow): add /gitflow setup with auto-config and CLAUDE.md injection
New gitflow-setup.md command auto-detects Gitea system config from
~/.config/claude/gitea.env, configures workflow settings, and injects
Git Workflow section into project CLAUDE.md. Moved
claude-md-integration.md to skills/ for programmatic use.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 19:13:34 -05:00
271e0210a5 feat(commands): make dispatch files active command handlers
All 25 dispatch files now have name: in frontmatter with Workflow
section that displays sub-commands and prompts for selection.
Bare /noun invocation is now useful instead of a dead end.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 19:12:12 -05:00
bbce9ed321 Merge pull request 'fix(projman): correct Phase 2 setup to use setup-venvs.sh and .venv path' (#451) from fix/setup-workflows-venv-path into development
Reviewed-on: #451
2026-02-07 23:07:21 +00:00
16bf1b969d fix(projman): correct Phase 2 setup to use setup-venvs.sh and .venv path
- Replace hardcoded single-server venv/ creation with setup-venvs.sh call
- Fix directory name from venv/ to .venv/ matching actual scripts
- Add fallback manual steps if setup-venvs.sh fails
- Add verification step using --check flag

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 18:05:38 -05:00
1fe19b85e3 Merge pull request 'fix(schema): move domain to metadata.json (v9.1.2)' (#449) from fix/domain-schema-compliance into development
Reviewed-on: #449
2026-02-07 22:04:54 +00:00
e824b18e44 fix(schema): move domain field to metadata.json for Claude Code compatibility
BREAKING FIX: Claude Code's marketplace schema validator rejects
unrecognized keys. The 'domain' field added in v8.0.0 caused
'Failed to load marketplace' errors on all 20 plugin entries.

- Removed 'domain' from marketplace.json (all 3 profiles)
- Removed 'domain' from all 20 plugin.json files
- Added 'domain' to metadata.json for all 20 plugins
- Updated validate-marketplace.sh to read from metadata.json
- Updated docs (CANONICAL-PATHS.md, CLAUDE.md) to reference metadata.json
- 12 new metadata.json files created (plugins without mcp_servers)
- 8 existing metadata.json files updated with domain field

Version: 9.1.2

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 17:00:58 -05:00
f4e62dd1fb Merge pull request 'docs: documentation cleanup, version alignment, architecture doc (v9.1.0)' (#447) from fix/docs-cleanup-v9.1 into development 2026-02-07 21:41:21 +00:00
97362576dc docs(marketplace): README rewrite, scripts audit, stale reference cleanup (v9.1.1)
- README.md fully rewritten: clean domain-grouped plugin tables, accurate
  structure tree matching CANONICAL-PATHS, all 10 scripts and 7 docs listed
- CLAUDE.md structure tree updated to match (was showing only 12 plugins)
- Deleted scripts/check-venv.sh (dead code for unimplemented SessionStart hooks)
- Fixed stale .doc-guardian-queue references in doc-sync.md and sync-workflow.md
- Removed check-venv.sh from CANONICAL-PATHS.md, added missing consumer scripts
- Version bumped to 9.1.1

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 16:34:36 -05:00
a169399da7 docs(marketplace): documentation cleanup, version alignment, architecture doc
- Delete orphan files (.doc-guardian-queue, stale backup, switch-profile.sh)
- Delete stale doc folders (architecture/, designs/, prompts/)
- Create consolidated docs/ARCHITECTURE.md for v9.1.0
- Bump all 12 original plugin versions to 9.0.1
- Fix project-hygiene descriptions (no longer a hook)
- Normalize /rfc, /project, /adr command rows in all docs
- Update CANONICAL-PATHS.md, UPDATING.md, README.md, CLAUDE.md
- COMMANDS-CHEATSHEET.md expanded to one row per sub-command

Version: 9.1.0

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 16:16:18 -05:00
47d2ef2e07 Merge pull request 'fix: post-evolution cleanup — stale references and migration guide fixes (v9.0.1)' (#446) from fix/post-evolution-cleanup into development
Reviewed-on: #446
2026-02-07 20:26:23 +00:00
78429a709f fix(marketplace): correct stale hook references and migration guide errors
- claude-config-audit-settings.md: update hook inventory to post-Decision #29 state
- maintainer.md: remove PostToolUse references, update to current hook types
- settings-optimization.md: update review layer table and hooks.json format
- claude-config-optimize-settings.md: fix stale doc-guardian PostToolUse reference
- project-hygiene/claude-md-integration.md: rewrite for manual /hygiene check
- doc-guardian: update doc-sync.md and sync-workflow.md hook references
- MIGRATION-v9.md: mark deleted commands as Removed, not renamed
- projman/task-sizing.md: PostToolUse → PreToolUse in example
- scripts/setup.sh: /labels-sync → /labels sync
- docs/CONFIGURATION.md: doc-guardian "Commands and hooks" → "Commands only"
- docs/prompts/INDEX.md: add prompt execution index

Version: 9.0.1

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 15:23:57 -05:00
382a3a3af8 Merge pull request 'feat: Command consolidation + 8 new plugins (v8.1.0 → v9.0.0)' (#445) from feat/phase-1b-command-consolidation into development
Reviewed-on: #445
2026-02-06 20:02:12 +00:00
2d51df7a42 feat(marketplace): command consolidation + 8 new plugins (v8.1.0 → v9.0.0) [BREAKING]
Phase 1b: Rename all ~94 commands across 12 plugins to /<noun> <action>
sub-command pattern. Git-flow consolidated from 8→5 commands (commit
variants absorbed into --push/--merge/--sync flags). Dispatch files,
name: frontmatter, and cross-reference updates for all plugins.

Phase 2: Design documents for 8 new plugins in docs/designs/.

Phase 3: Scaffold 8 new plugins — saas-api-platform, saas-db-migrate,
saas-react-platform, saas-test-pilot, data-seed, ops-release-manager,
ops-deploy-pipeline, debug-mcp. Each with plugin.json, commands, agents,
skills, README, and claude-md-integration. Marketplace grows from 12→20.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 14:52:11 -05:00
5098422858 Merge pull request 'feat: Phase 1 evolution — domain metadata + hook migration (v8.0.0 → v8.1.0)' (#444) from feat/phase-1-evolution into development
Reviewed-on: #444
2026-02-06 17:31:18 +00:00
9ba2e660d3 feat(marketplace): hook migration, projman commands, optimizations [BREAKING]
Remove all SessionStart and PostToolUse hooks across the marketplace,
retaining only PreToolUse safety hooks and UserPromptSubmit quality hooks.
Add /project and /adr command families, /hygiene check, /cv status.
Create 7 new projman skills for project lifecycle management.
Remove /pm-debug, /suggest-version, /proposal-status commands.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 12:28:06 -05:00
437 changed files with 15792 additions and 15294 deletions

View File

@@ -6,12 +6,12 @@
}, },
"metadata": { "metadata": {
"description": "Project management plugins with Gitea and NetBox integrations", "description": "Project management plugins with Gitea and NetBox integrations",
"version": "8.0.0" "version": "9.1.2"
}, },
"plugins": [ "plugins": [
{ {
"name": "projman", "name": "projman",
"version": "7.1.0", "version": "9.0.1",
"description": "Sprint planning and project management with Gitea integration", "description": "Sprint planning and project management with Gitea integration",
"source": "./plugins/projman", "source": "./plugins/projman",
"author": { "author": {
@@ -20,9 +20,6 @@
}, },
"homepage": "https://gitea.hotserv.cloud/personal-projects/leo-claude-mktplace/src/branch/main/plugins/projman/README.md", "homepage": "https://gitea.hotserv.cloud/personal-projects/leo-claude-mktplace/src/branch/main/plugins/projman/README.md",
"repository": "https://gitea.hotserv.cloud/personal-projects/leo-claude-mktplace.git", "repository": "https://gitea.hotserv.cloud/personal-projects/leo-claude-mktplace.git",
"hooks": [
"./hooks/hooks.json"
],
"category": "development", "category": "development",
"tags": [ "tags": [
"sprint", "sprint",
@@ -30,12 +27,11 @@
"gitea", "gitea",
"project-management" "project-management"
], ],
"license": "MIT", "license": "MIT"
"domain": "core"
}, },
{ {
"name": "doc-guardian", "name": "doc-guardian",
"version": "7.1.0", "version": "9.0.1",
"description": "Automatic documentation drift detection and synchronization", "description": "Automatic documentation drift detection and synchronization",
"source": "./plugins/doc-guardian", "source": "./plugins/doc-guardian",
"author": { "author": {
@@ -44,21 +40,17 @@
}, },
"homepage": "https://gitea.hotserv.cloud/personal-projects/leo-claude-mktplace/src/branch/main/plugins/doc-guardian/README.md", "homepage": "https://gitea.hotserv.cloud/personal-projects/leo-claude-mktplace/src/branch/main/plugins/doc-guardian/README.md",
"repository": "https://gitea.hotserv.cloud/personal-projects/leo-claude-mktplace.git", "repository": "https://gitea.hotserv.cloud/personal-projects/leo-claude-mktplace.git",
"hooks": [
"./hooks/hooks.json"
],
"category": "productivity", "category": "productivity",
"tags": [ "tags": [
"documentation", "documentation",
"drift-detection", "drift-detection",
"sync" "sync"
], ],
"license": "MIT", "license": "MIT"
"domain": "core"
}, },
{ {
"name": "code-sentinel", "name": "code-sentinel",
"version": "7.1.0", "version": "9.0.1",
"description": "Security scanning and code refactoring tools", "description": "Security scanning and code refactoring tools",
"source": "./plugins/code-sentinel", "source": "./plugins/code-sentinel",
"author": { "author": {
@@ -76,13 +68,12 @@
"refactoring", "refactoring",
"vulnerabilities" "vulnerabilities"
], ],
"license": "MIT", "license": "MIT"
"domain": "core"
}, },
{ {
"name": "project-hygiene", "name": "project-hygiene",
"version": "7.1.0", "version": "9.0.1",
"description": "Post-task cleanup hook that removes temp files and manages orphaned files", "description": "Manual project hygiene checks — temp files, misplaced files, empty dirs, debug artifacts",
"source": "./plugins/project-hygiene", "source": "./plugins/project-hygiene",
"author": { "author": {
"name": "Leo Miranda", "name": "Leo Miranda",
@@ -90,21 +81,18 @@
}, },
"homepage": "https://gitea.hotserv.cloud/personal-projects/leo-claude-mktplace/src/branch/main/plugins/project-hygiene/README.md", "homepage": "https://gitea.hotserv.cloud/personal-projects/leo-claude-mktplace/src/branch/main/plugins/project-hygiene/README.md",
"repository": "https://gitea.hotserv.cloud/personal-projects/leo-claude-mktplace.git", "repository": "https://gitea.hotserv.cloud/personal-projects/leo-claude-mktplace.git",
"hooks": [
"./hooks/hooks.json"
],
"category": "productivity", "category": "productivity",
"tags": [ "tags": [
"cleanup", "cleanup",
"automation", "hygiene",
"hygiene" "maintenance",
"manual-check"
], ],
"license": "MIT", "license": "MIT"
"domain": "core"
}, },
{ {
"name": "cmdb-assistant", "name": "cmdb-assistant",
"version": "7.1.0", "version": "9.0.1",
"description": "NetBox CMDB integration with data quality validation and machine registration", "description": "NetBox CMDB integration with data quality validation and machine registration",
"source": "./plugins/cmdb-assistant", "source": "./plugins/cmdb-assistant",
"author": { "author": {
@@ -125,12 +113,11 @@
"data-quality", "data-quality",
"validation" "validation"
], ],
"license": "MIT", "license": "MIT"
"domain": "ops"
}, },
{ {
"name": "claude-config-maintainer", "name": "claude-config-maintainer",
"version": "7.1.0", "version": "9.0.1",
"description": "CLAUDE.md and settings.local.json optimization for Claude Code projects", "description": "CLAUDE.md and settings.local.json optimization for Claude Code projects",
"source": "./plugins/claude-config-maintainer", "source": "./plugins/claude-config-maintainer",
"author": { "author": {
@@ -139,21 +126,17 @@
}, },
"homepage": "https://gitea.hotserv.cloud/personal-projects/leo-claude-mktplace/src/branch/main/plugins/claude-config-maintainer/README.md", "homepage": "https://gitea.hotserv.cloud/personal-projects/leo-claude-mktplace/src/branch/main/plugins/claude-config-maintainer/README.md",
"repository": "https://gitea.hotserv.cloud/personal-projects/leo-claude-mktplace.git", "repository": "https://gitea.hotserv.cloud/personal-projects/leo-claude-mktplace.git",
"hooks": [
"./hooks/hooks.json"
],
"category": "development", "category": "development",
"tags": [ "tags": [
"claude-md", "claude-md",
"configuration", "configuration",
"optimization" "optimization"
], ],
"license": "MIT", "license": "MIT"
"domain": "core"
}, },
{ {
"name": "clarity-assist", "name": "clarity-assist",
"version": "7.1.0", "version": "9.0.1",
"description": "Prompt optimization and requirement clarification with ND-friendly accommodations", "description": "Prompt optimization and requirement clarification with ND-friendly accommodations",
"source": "./plugins/clarity-assist", "source": "./plugins/clarity-assist",
"author": { "author": {
@@ -172,12 +155,11 @@
"clarification", "clarification",
"nd-friendly" "nd-friendly"
], ],
"license": "MIT", "license": "MIT"
"domain": "core"
}, },
{ {
"name": "git-flow", "name": "git-flow",
"version": "7.1.0", "version": "9.0.1",
"description": "Git workflow automation with intelligent commit messages and branch management", "description": "Git workflow automation with intelligent commit messages and branch management",
"source": "./plugins/git-flow", "source": "./plugins/git-flow",
"author": { "author": {
@@ -196,12 +178,11 @@
"commits", "commits",
"branching" "branching"
], ],
"license": "MIT", "license": "MIT"
"domain": "core"
}, },
{ {
"name": "pr-review", "name": "pr-review",
"version": "7.1.0", "version": "9.0.1",
"description": "Multi-agent pull request review with confidence scoring and actionable feedback", "description": "Multi-agent pull request review with confidence scoring and actionable feedback",
"source": "./plugins/pr-review", "source": "./plugins/pr-review",
"author": { "author": {
@@ -210,9 +191,6 @@
}, },
"homepage": "https://gitea.hotserv.cloud/personal-projects/leo-claude-mktplace/src/branch/main/plugins/pr-review/README.md", "homepage": "https://gitea.hotserv.cloud/personal-projects/leo-claude-mktplace/src/branch/main/plugins/pr-review/README.md",
"repository": "https://gitea.hotserv.cloud/personal-projects/leo-claude-mktplace.git", "repository": "https://gitea.hotserv.cloud/personal-projects/leo-claude-mktplace.git",
"hooks": [
"./hooks/hooks.json"
],
"category": "development", "category": "development",
"tags": [ "tags": [
"code-review", "code-review",
@@ -220,12 +198,11 @@
"security", "security",
"quality" "quality"
], ],
"license": "MIT", "license": "MIT"
"domain": "core"
}, },
{ {
"name": "data-platform", "name": "data-platform",
"version": "7.1.0", "version": "9.0.1",
"description": "Data engineering tools with pandas, PostgreSQL/PostGIS, and dbt integration", "description": "Data engineering tools with pandas, PostgreSQL/PostGIS, and dbt integration",
"source": "./plugins/data-platform", "source": "./plugins/data-platform",
"author": { "author": {
@@ -234,9 +211,6 @@
}, },
"homepage": "https://gitea.hotserv.cloud/personal-projects/leo-claude-mktplace/src/branch/main/plugins/data-platform/README.md", "homepage": "https://gitea.hotserv.cloud/personal-projects/leo-claude-mktplace/src/branch/main/plugins/data-platform/README.md",
"repository": "https://gitea.hotserv.cloud/personal-projects/leo-claude-mktplace.git", "repository": "https://gitea.hotserv.cloud/personal-projects/leo-claude-mktplace.git",
"hooks": [
"./hooks/hooks.json"
],
"category": "data", "category": "data",
"tags": [ "tags": [
"pandas", "pandas",
@@ -246,12 +220,11 @@
"data-engineering", "data-engineering",
"etl" "etl"
], ],
"license": "MIT", "license": "MIT"
"domain": "data"
}, },
{ {
"name": "viz-platform", "name": "viz-platform",
"version": "7.1.0", "version": "9.0.1",
"description": "Visualization tools with Dash Mantine Components validation, Plotly charts, and theming", "description": "Visualization tools with Dash Mantine Components validation, Plotly charts, and theming",
"source": "./plugins/viz-platform", "source": "./plugins/viz-platform",
"author": { "author": {
@@ -260,9 +233,6 @@
}, },
"homepage": "https://gitea.hotserv.cloud/personal-projects/leo-claude-mktplace/src/branch/main/plugins/viz-platform/README.md", "homepage": "https://gitea.hotserv.cloud/personal-projects/leo-claude-mktplace/src/branch/main/plugins/viz-platform/README.md",
"repository": "https://gitea.hotserv.cloud/personal-projects/leo-claude-mktplace.git", "repository": "https://gitea.hotserv.cloud/personal-projects/leo-claude-mktplace.git",
"hooks": [
"./hooks/hooks.json"
],
"category": "visualization", "category": "visualization",
"tags": [ "tags": [
"dash", "dash",
@@ -273,12 +243,11 @@
"theming", "theming",
"dmc" "dmc"
], ],
"license": "MIT", "license": "MIT"
"domain": "data"
}, },
{ {
"name": "contract-validator", "name": "contract-validator",
"version": "7.1.0", "version": "9.0.1",
"description": "Cross-plugin compatibility validation and Claude.md agent verification", "description": "Cross-plugin compatibility validation and Claude.md agent verification",
"source": "./plugins/contract-validator", "source": "./plugins/contract-validator",
"author": { "author": {
@@ -287,9 +256,6 @@
}, },
"homepage": "https://gitea.hotserv.cloud/personal-projects/leo-claude-mktplace/src/branch/main/plugins/contract-validator/README.md", "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", "repository": "https://gitea.hotserv.cloud/personal-projects/leo-claude-mktplace.git",
"hooks": [
"./hooks/hooks.json"
],
"category": "development", "category": "development",
"tags": [ "tags": [
"validation", "validation",
@@ -299,8 +265,179 @@
"interfaces", "interfaces",
"cross-plugin" "cross-plugin"
], ],
"license": "MIT", "license": "MIT"
"domain": "core" },
{
"name": "saas-api-platform",
"version": "0.1.0",
"description": "REST and GraphQL API scaffolding for FastAPI and Express projects",
"source": "./plugins/saas-api-platform",
"author": {
"name": "Leo Miranda",
"email": "leobmiranda@gmail.com"
},
"homepage": "https://gitea.hotserv.cloud/personal-projects/leo-claude-mktplace/src/branch/main/plugins/saas-api-platform/README.md",
"repository": "https://gitea.hotserv.cloud/personal-projects/leo-claude-mktplace.git",
"category": "development",
"tags": [
"api",
"rest",
"graphql",
"fastapi",
"express",
"openapi"
],
"license": "MIT"
},
{
"name": "saas-db-migrate",
"version": "0.1.0",
"description": "Database migration management for Alembic, Prisma, and raw SQL",
"source": "./plugins/saas-db-migrate",
"author": {
"name": "Leo Miranda",
"email": "leobmiranda@gmail.com"
},
"homepage": "https://gitea.hotserv.cloud/personal-projects/leo-claude-mktplace/src/branch/main/plugins/saas-db-migrate/README.md",
"repository": "https://gitea.hotserv.cloud/personal-projects/leo-claude-mktplace.git",
"category": "development",
"tags": [
"database",
"migrations",
"alembic",
"prisma",
"sql",
"schema"
],
"license": "MIT"
},
{
"name": "saas-react-platform",
"version": "0.1.0",
"description": "React frontend development toolkit for Next.js and Vite projects",
"source": "./plugins/saas-react-platform",
"author": {
"name": "Leo Miranda",
"email": "leobmiranda@gmail.com"
},
"homepage": "https://gitea.hotserv.cloud/personal-projects/leo-claude-mktplace/src/branch/main/plugins/saas-react-platform/README.md",
"repository": "https://gitea.hotserv.cloud/personal-projects/leo-claude-mktplace.git",
"category": "development",
"tags": [
"react",
"nextjs",
"vite",
"typescript",
"frontend",
"components"
],
"license": "MIT"
},
{
"name": "saas-test-pilot",
"version": "0.1.0",
"description": "Test automation toolkit for pytest, Jest, Vitest, and Playwright",
"source": "./plugins/saas-test-pilot",
"author": {
"name": "Leo Miranda",
"email": "leobmiranda@gmail.com"
},
"homepage": "https://gitea.hotserv.cloud/personal-projects/leo-claude-mktplace/src/branch/main/plugins/saas-test-pilot/README.md",
"repository": "https://gitea.hotserv.cloud/personal-projects/leo-claude-mktplace.git",
"category": "development",
"tags": [
"testing",
"pytest",
"jest",
"vitest",
"playwright",
"coverage"
],
"license": "MIT"
},
{
"name": "data-seed",
"version": "0.1.0",
"description": "Test data generation and database seeding with relationship-aware profiles",
"source": "./plugins/data-seed",
"author": {
"name": "Leo Miranda",
"email": "leobmiranda@gmail.com"
},
"homepage": "https://gitea.hotserv.cloud/personal-projects/leo-claude-mktplace/src/branch/main/plugins/data-seed/README.md",
"repository": "https://gitea.hotserv.cloud/personal-projects/leo-claude-mktplace.git",
"category": "data",
"tags": [
"seed-data",
"test-data",
"faker",
"fixtures",
"database"
],
"license": "MIT"
},
{
"name": "ops-release-manager",
"version": "0.1.0",
"description": "Release management with semantic versioning, changelogs, and tag automation",
"source": "./plugins/ops-release-manager",
"author": {
"name": "Leo Miranda",
"email": "leobmiranda@gmail.com"
},
"homepage": "https://gitea.hotserv.cloud/personal-projects/leo-claude-mktplace/src/branch/main/plugins/ops-release-manager/README.md",
"repository": "https://gitea.hotserv.cloud/personal-projects/leo-claude-mktplace.git",
"category": "development",
"tags": [
"release",
"semver",
"changelog",
"versioning",
"tags"
],
"license": "MIT"
},
{
"name": "ops-deploy-pipeline",
"version": "0.1.0",
"description": "CI/CD deployment pipeline management for Docker Compose and systemd services",
"source": "./plugins/ops-deploy-pipeline",
"author": {
"name": "Leo Miranda",
"email": "leobmiranda@gmail.com"
},
"homepage": "https://gitea.hotserv.cloud/personal-projects/leo-claude-mktplace/src/branch/main/plugins/ops-deploy-pipeline/README.md",
"repository": "https://gitea.hotserv.cloud/personal-projects/leo-claude-mktplace.git",
"category": "infrastructure",
"tags": [
"deploy",
"docker-compose",
"systemd",
"caddy",
"cicd"
],
"license": "MIT"
},
{
"name": "debug-mcp",
"version": "0.1.0",
"description": "MCP server debugging, inspection, and development toolkit",
"source": "./plugins/debug-mcp",
"author": {
"name": "Leo Miranda",
"email": "leobmiranda@gmail.com"
},
"homepage": "https://gitea.hotserv.cloud/personal-projects/leo-claude-mktplace/src/branch/main/plugins/debug-mcp/README.md",
"repository": "https://gitea.hotserv.cloud/personal-projects/leo-claude-mktplace.git",
"category": "development",
"tags": [
"mcp",
"debugging",
"diagnostics",
"server",
"development"
],
"license": "MIT"
} }
] ]
} }

View File

@@ -1,249 +0,0 @@
# CLAUDE.md
This file provides guidance to Claude Code when working with code in this repository.
## Project Overview
**Repository:** leo-claude-mktplace
**Version:** 3.0.1
**Status:** Production Ready
A plugin marketplace for Claude Code containing:
| Plugin | Description | Version |
|--------|-------------|---------|
| `projman` | Sprint planning and project management with Gitea integration | 3.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 |
| `clarity-assist` | Prompt optimization with ND-friendly accommodations | 1.0.0 |
| `doc-guardian` | Automatic documentation drift detection and synchronization | 1.0.0 |
| `code-sentinel` | Security scanning and code refactoring tools | 1.0.0 |
| `claude-config-maintainer` | CLAUDE.md optimization and maintenance | 1.0.0 |
| `cmdb-assistant` | NetBox CMDB integration for infrastructure management | 1.0.0 |
| `project-hygiene` | Post-task cleanup automation via hooks | 0.1.0 |
## Quick Start
```bash
# Validate marketplace compliance
./scripts/validate-marketplace.sh
# Setup commands (in a target project with plugin installed)
/initial-setup # First time: full setup wizard
/project-init # New project: quick config
/project-sync # After repo move: sync config
# Run projman commands
/sprint-plan # Start sprint planning
/sprint-status # Check progress
/review # Pre-close code quality review
/test-check # Verify tests before close
/sprint-close # Complete sprint
```
## Repository Structure
```
leo-claude-mktplace/
├── .claude-plugin/
│ └── marketplace.json # Marketplace manifest
├── mcp-servers/ # SHARED MCP servers (v3.0.0+)
│ ├── gitea/ # Gitea MCP (issues, PRs, wiki)
│ └── netbox/ # NetBox MCP (CMDB)
├── plugins/
│ ├── projman/ # Sprint management
│ │ ├── .claude-plugin/plugin.json
│ │ ├── .mcp.json
│ │ ├── mcp-servers/gitea -> ../../../mcp-servers/gitea # SYMLINK
│ │ ├── commands/ # 12 commands (incl. setup)
│ │ ├── hooks/ # SessionStart mismatch detection
│ │ ├── agents/ # 4 agents
│ │ └── skills/label-taxonomy/
│ ├── git-flow/ # Git workflow automation
│ │ ├── .claude-plugin/plugin.json
│ │ ├── commands/ # 8 commands
│ │ └── agents/
│ ├── pr-review/ # Multi-agent PR review
│ │ ├── .claude-plugin/plugin.json
│ │ ├── .mcp.json
│ │ ├── mcp-servers/gitea -> ../../../mcp-servers/gitea # SYMLINK
│ │ ├── commands/ # 6 commands (incl. setup)
│ │ ├── hooks/ # SessionStart mismatch detection
│ │ └── agents/ # 5 agents
│ ├── clarity-assist/ # Prompt optimization (NEW v3.0.0)
│ │ ├── .claude-plugin/plugin.json
│ │ ├── commands/ # 2 commands
│ │ └── agents/
│ ├── doc-guardian/ # Documentation drift detection
│ ├── code-sentinel/ # Security scanning & refactoring
│ ├── claude-config-maintainer/
│ ├── cmdb-assistant/
│ └── project-hygiene/
├── scripts/
│ ├── setup.sh, post-update.sh
│ └── validate-marketplace.sh # Marketplace compliance validation
└── docs/
├── CANONICAL-PATHS.md # Single source of truth for paths
└── CONFIGURATION.md # Centralized configuration guide
```
## CRITICAL: Rules You MUST Follow
### File Operations
- **NEVER** create files in repository root unless listed in "Allowed Root Files"
- **NEVER** modify `.gitignore` without explicit permission
- **ALWAYS** use `.scratch/` for temporary/exploratory work
- **ALWAYS** verify paths against `docs/CANONICAL-PATHS.md` before creating files
### Plugin Development
- **plugin.json MUST be in `.claude-plugin/` directory** (not plugin root)
- **Every plugin MUST be listed in marketplace.json**
- **MCP servers are SHARED at root** with symlinks from plugins
- **MCP server venv path**: `${CLAUDE_PLUGIN_ROOT}/mcp-servers/{name}/.venv/bin/python`
- **CLI tools forbidden** - Use MCP tools exclusively (never `tea`, `gh`, etc.)
### Hooks (Valid Events Only)
`PreToolUse`, `PostToolUse`, `UserPromptSubmit`, `SessionStart`, `SessionEnd`, `Notification`, `Stop`, `SubagentStop`, `PreCompact`
**INVALID:** `task-completed`, `file-changed`, `git-commit-msg-needed`
### Allowed Root Files
`CLAUDE.md`, `README.md`, `LICENSE`, `CHANGELOG.md`, `.gitignore`, `.env.example`
### Allowed Root Directories
`.claude/`, `.claude-plugin/`, `.claude-plugins/`, `.scratch/`, `docs/`, `hooks/`, `mcp-servers/`, `plugins/`, `scripts/`
## Architecture
### Four-Agent Model (projman)
| Agent | Personality | Responsibilities |
|-------|-------------|------------------|
| **Planner** | Thoughtful, methodical | Sprint planning, architecture analysis, issue creation, lesson search |
| **Orchestrator** | Concise, action-oriented | Sprint execution, parallel batching, Git operations, lesson capture |
| **Executor** | Implementation-focused | Code implementation, branch management, MR creation |
| **Code Reviewer** | Thorough, practical | Pre-close quality review, security scan, test verification |
### MCP Server Tools (Gitea)
| Category | Tools |
|----------|-------|
| Issues | `list_issues`, `get_issue`, `create_issue`, `update_issue`, `add_comment` |
| Labels | `get_labels`, `suggest_labels`, `create_label` |
| Milestones | `list_milestones`, `get_milestone`, `create_milestone`, `update_milestone` |
| Dependencies | `list_issue_dependencies`, `create_issue_dependency`, `get_execution_order` |
| Wiki | `list_wiki_pages`, `get_wiki_page`, `create_wiki_page`, `create_lesson`, `search_lessons` |
| **Pull Requests** | `list_pull_requests`, `get_pull_request`, `get_pr_diff`, `get_pr_comments`, `create_pr_review`, `add_pr_comment` *(NEW v3.0.0)* |
| Validation | `validate_repo_org`, `get_branch_protection` |
### Hybrid Configuration
| Level | Location | Purpose |
|-------|----------|---------|
| System | `~/.config/claude/gitea.env` | Credentials (GITEA_API_URL, GITEA_API_TOKEN) |
| Project | `.env` in project root | Repository specification (GITEA_ORG, GITEA_REPO) |
**Note:** `GITEA_ORG` is at project level since different projects may belong to different organizations.
### Branch-Aware Security
| Branch Pattern | Mode | Capabilities |
|----------------|------|--------------|
| `development`, `feat/*` | Development | Full access |
| `staging` | Staging | Read-only code, can create issues |
| `main`, `master` | Production | Read-only, emergency only |
## Label Taxonomy
43 labels total: 27 organization + 16 repository
**Organization:** Agent/2, Complexity/3, Efforts/5, Priority/4, Risk/3, Source/4, Type/6
**Repository:** Component/9, Tech/7
Sync with `/labels-sync` command.
## Lessons Learned System
Stored in Gitea Wiki under `lessons-learned/sprints/`.
**Workflow:**
1. Orchestrator captures at sprint close via MCP tools
2. Planner searches at sprint start using `search_lessons`
3. Tags enable cross-project discovery
## Common Operations
### Adding a New Plugin
1. Create `plugins/{name}/.claude-plugin/plugin.json`
2. Add entry to `.claude-plugin/marketplace.json` with category, tags, license
3. Create `README.md` and `claude-md-integration.md`
4. If using MCP server, create symlink: `ln -s ../../../mcp-servers/{server} plugins/{name}/mcp-servers/{server}`
5. Run `./scripts/validate-marketplace.sh`
6. Update `CHANGELOG.md`
### Adding a Command to projman
1. Create `plugins/projman/commands/{name}.md`
2. Update `plugins/projman/README.md`
3. Update marketplace description if significant
### Validation
```bash
./scripts/validate-marketplace.sh # Validates all manifests
```
## Path Verification Protocol
**Before creating any file:**
1. Read `docs/CANONICAL-PATHS.md`
2. List all paths to be created/modified
3. Verify each against canonical paths
4. If not in canonical paths, STOP and ask
## Documentation Index
| Document | Purpose |
|----------|---------|
| `docs/CANONICAL-PATHS.md` | **Single source of truth** for paths |
| `docs/COMMANDS-CHEATSHEET.md` | All commands quick reference with workflow examples |
| `docs/CONFIGURATION.md` | Centralized setup guide |
| `docs/UPDATING.md` | Update guide for the marketplace |
| `plugins/projman/CONFIGURATION.md` | Quick reference (links to central) |
| `plugins/projman/README.md` | Projman full documentation |
## Versioning and Changelog Rules
### Version Display
**The marketplace version is displayed ONLY in the main `README.md` title.**
- Format: `# Leo Claude Marketplace - vX.Y.Z`
- Do NOT add version numbers to individual plugin documentation titles
- Do NOT add version numbers to configuration guides
- Do NOT add version numbers to CLAUDE.md or other docs
### Changelog Maintenance (MANDATORY)
**`CHANGELOG.md` is the authoritative source for version history.**
When releasing a new version:
1. Update main `README.md` title with new version
2. Update `CHANGELOG.md` with:
- Version number and date: `## [X.Y.Z] - YYYY-MM-DD`
- **Added**: New features, commands, files
- **Changed**: Modifications to existing functionality
- **Fixed**: Bug fixes
- **Removed**: Deleted features, files, deprecated items
3. Update `marketplace.json` metadata version
4. Update plugin `plugin.json` versions if plugin-specific changes
### Version Format
- Follow [Semantic Versioning](https://semver.org/): MAJOR.MINOR.PATCH
- MAJOR: Breaking changes
- MINOR: New features, backward compatible
- PATCH: Bug fixes, minor improvements
---
**Last Updated:** 2026-01-20

View File

@@ -1,66 +0,0 @@
# Doc Guardian Queue - cleared after sync on 2026-02-02
2026-02-02T11:41:00 | .claude-plugin | /home/lmiranda/claude-plugins-work/.claude-plugin/marketplace.json | CLAUDE.md .claude-plugin/marketplace.json
2026-02-02T13:35:48 | skills | /home/lmiranda/claude-plugins-work/plugins/projman/skills/sprint-approval.md | README.md
2026-02-02T13:36:03 | commands | /home/lmiranda/claude-plugins-work/plugins/projman/commands/sprint-start.md | docs/COMMANDS-CHEATSHEET.md README.md
2026-02-02T13:36:16 | agents | /home/lmiranda/claude-plugins-work/plugins/projman/agents/orchestrator.md | README.md CLAUDE.md
2026-02-02T13:39:07 | commands | /home/lmiranda/claude-plugins-work/plugins/projman/commands/rfc.md | docs/COMMANDS-CHEATSHEET.md README.md
2026-02-02T13:39:15 | commands | /home/lmiranda/claude-plugins-work/plugins/projman/commands/setup.md | docs/COMMANDS-CHEATSHEET.md README.md
2026-02-02T13:39:32 | skills | /home/lmiranda/claude-plugins-work/plugins/projman/skills/rfc-workflow.md | README.md
2026-02-02T13:43:14 | skills | /home/lmiranda/claude-plugins-work/plugins/projman/skills/rfc-templates.md | README.md
2026-02-02T13:44:55 | skills | /home/lmiranda/claude-plugins-work/plugins/projman/skills/sprint-lifecycle.md | README.md
2026-02-02T13:45:04 | skills | /home/lmiranda/claude-plugins-work/plugins/projman/skills/label-taxonomy/labels-reference.md | README.md
2026-02-02T13:45:14 | commands | /home/lmiranda/claude-plugins-work/plugins/projman/commands/sprint-plan.md | docs/COMMANDS-CHEATSHEET.md README.md
2026-02-02T13:45:48 | commands | /home/lmiranda/claude-plugins-work/plugins/projman/commands/review.md | docs/COMMANDS-CHEATSHEET.md README.md
2026-02-02T13:46:07 | commands | /home/lmiranda/claude-plugins-work/plugins/projman/commands/sprint-close.md | docs/COMMANDS-CHEATSHEET.md README.md
2026-02-02T13:46:21 | commands | /home/lmiranda/claude-plugins-work/plugins/projman/commands/sprint-status.md | docs/COMMANDS-CHEATSHEET.md README.md
2026-02-02T13:46:38 | agents | /home/lmiranda/claude-plugins-work/plugins/projman/agents/planner.md | README.md CLAUDE.md
2026-02-02T13:46:57 | agents | /home/lmiranda/claude-plugins-work/plugins/projman/agents/code-reviewer.md | README.md CLAUDE.md
2026-02-02T13:49:13 | commands | /home/lmiranda/claude-plugins-work/plugins/viz-platform/commands/design-gate.md | docs/COMMANDS-CHEATSHEET.md README.md
2026-02-02T13:49:24 | commands | /home/lmiranda/claude-plugins-work/plugins/data-platform/commands/data-gate.md | docs/COMMANDS-CHEATSHEET.md README.md
2026-02-02T13:49:35 | skills | /home/lmiranda/claude-plugins-work/plugins/projman/skills/domain-consultation.md | README.md
2026-02-02T13:50:04 | mcp-servers | /home/lmiranda/claude-plugins-work/mcp-servers/contract-validator/mcp_server/validation_tools.py | docs/COMMANDS-CHEATSHEET.md CLAUDE.md
2026-02-02T13:50:59 | mcp-servers | /home/lmiranda/claude-plugins-work/mcp-servers/contract-validator/mcp_server/server.py | docs/COMMANDS-CHEATSHEET.md CLAUDE.md
2026-02-02T13:51:32 | mcp-servers | /home/lmiranda/claude-plugins-work/mcp-servers/contract-validator/tests/test_validation_tools.py | docs/COMMANDS-CHEATSHEET.md CLAUDE.md
2026-02-02T13:51:49 | skills | /home/lmiranda/claude-plugins-work/plugins/contract-validator/skills/validation-rules.md | README.md
2026-02-02T13:52:07 | skills | /home/lmiranda/claude-plugins-work/plugins/contract-validator/skills/mcp-tools-reference.md | README.md
2026-02-02T13:59:09 | skills | /home/lmiranda/claude-plugins-work/plugins/projman/skills/progress-tracking.md | README.md
2026-02-02T14:01:34 | commands | /home/lmiranda/claude-plugins-work/plugins/projman/commands/test.md | docs/COMMANDS-CHEATSHEET.md README.md
2026-02-03T21:08:38 | commands | /home/lmiranda/claude-plugins-work/plugins/git-flow/commands/git-commit.md | docs/COMMANDS-CHEATSHEET.md README.md
2026-02-03T21:08:39 | commands | /home/lmiranda/claude-plugins-work/plugins/git-flow/commands/git-commit-push.md | docs/COMMANDS-CHEATSHEET.md README.md
2026-02-03T21:08:40 | commands | /home/lmiranda/claude-plugins-work/plugins/git-flow/commands/git-commit-merge.md | docs/COMMANDS-CHEATSHEET.md README.md
2026-02-03T21:08:41 | commands | /home/lmiranda/claude-plugins-work/plugins/git-flow/commands/git-commit-sync.md | docs/COMMANDS-CHEATSHEET.md README.md
2026-02-03T21:08:49 | commands | /home/lmiranda/claude-plugins-work/plugins/cmdb-assistant/commands/cmdb-setup.md | docs/COMMANDS-CHEATSHEET.md README.md
2026-02-03T21:08:50 | commands | /home/lmiranda/claude-plugins-work/plugins/pr-review/commands/project-init.md | docs/COMMANDS-CHEATSHEET.md README.md
2026-02-03T21:08:51 | skills | /home/lmiranda/claude-plugins-work/plugins/cmdb-assistant/skills/visual-header.md | README.md
2026-02-03T21:08:51 | commands | /home/lmiranda/claude-plugins-work/plugins/projman/commands/pm-review.md | docs/COMMANDS-CHEATSHEET.md README.md
2026-02-03T21:08:53 | commands | /home/lmiranda/claude-plugins-work/plugins/projman/commands/pm-test.md | docs/COMMANDS-CHEATSHEET.md README.md
2026-02-03T21:08:54 | skills | /home/lmiranda/claude-plugins-work/plugins/projman/skills/review-checklist.md | README.md
2026-02-03T21:08:55 | skills | /home/lmiranda/claude-plugins-work/plugins/projman/skills/visual-output.md | README.md
2026-02-03T21:08:58 | skills | /home/lmiranda/claude-plugins-work/plugins/projman/skills/setup-workflows.md | README.md
2026-02-03T21:08:59 | skills | /home/lmiranda/claude-plugins-work/plugins/git-flow/skills/sync-workflow.md | README.md
2026-02-03T21:09:00 | skills | /home/lmiranda/claude-plugins-work/plugins/git-flow/skills/commit-conventions.md | README.md
2026-02-03T21:09:00 | skills | /home/lmiranda/claude-plugins-work/plugins/git-flow/skills/merge-workflow.md | README.md
2026-02-03T21:09:08 | commands | /home/lmiranda/claude-plugins-work/plugins/projman/commands/pm-setup.md | docs/COMMANDS-CHEATSHEET.md README.md
2026-02-03T21:09:08 | commands | /home/lmiranda/claude-plugins-work/plugins/data-platform/commands/data-setup.md | docs/COMMANDS-CHEATSHEET.md README.md
2026-02-03T21:09:10 | commands | /home/lmiranda/claude-plugins-work/plugins/data-platform/commands/data-run.md | docs/COMMANDS-CHEATSHEET.md README.md
2026-02-03T21:09:10 | commands | /home/lmiranda/claude-plugins-work/plugins/contract-validator/commands/cv-setup.md | docs/COMMANDS-CHEATSHEET.md README.md
2026-02-03T21:09:11 | commands | /home/lmiranda/claude-plugins-work/plugins/projman/commands/pm-debug.md | docs/COMMANDS-CHEATSHEET.md README.md
2026-02-03T21:09:13 | agents | /home/lmiranda/claude-plugins-work/plugins/contract-validator/agents/full-validation.md | README.md CLAUDE.md
2026-02-03T21:09:14 | commands | /home/lmiranda/claude-plugins-work/plugins/data-platform/commands/data-ingest.md | docs/COMMANDS-CHEATSHEET.md README.md
2026-02-03T21:09:18 | commands | /home/lmiranda/claude-plugins-work/plugins/data-platform/commands/data-profile.md | docs/COMMANDS-CHEATSHEET.md README.md
2026-02-03T21:09:18 | commands | /home/lmiranda/claude-plugins-work/plugins/viz-platform/commands/viz-setup.md | docs/COMMANDS-CHEATSHEET.md README.md
2026-02-03T21:09:20 | commands | /home/lmiranda/claude-plugins-work/plugins/data-platform/commands/data-schema.md | docs/COMMANDS-CHEATSHEET.md README.md
2026-02-03T21:09:20 | commands | /home/lmiranda/claude-plugins-work/plugins/viz-platform/commands/viz-theme.md | docs/COMMANDS-CHEATSHEET.md README.md
2026-02-03T21:09:23 | commands | /home/lmiranda/claude-plugins-work/plugins/viz-platform/commands/viz-theme-new.md | docs/COMMANDS-CHEATSHEET.md README.md
2026-02-03T21:09:24 | commands | /home/lmiranda/claude-plugins-work/plugins/data-platform/commands/data-explain.md | docs/COMMANDS-CHEATSHEET.md README.md
2026-02-03T21:09:26 | commands | /home/lmiranda/claude-plugins-work/plugins/data-platform/commands/data-lineage.md | docs/COMMANDS-CHEATSHEET.md README.md
2026-02-03T21:09:26 | commands | /home/lmiranda/claude-plugins-work/plugins/viz-platform/commands/viz-theme-css.md | docs/COMMANDS-CHEATSHEET.md README.md
2026-02-03T21:09:29 | commands | /home/lmiranda/claude-plugins-work/plugins/viz-platform/commands/viz-chart.md | docs/COMMANDS-CHEATSHEET.md README.md
2026-02-03T21:09:32 | commands | /home/lmiranda/claude-plugins-work/plugins/viz-platform/commands/viz-chart-export.md | docs/COMMANDS-CHEATSHEET.md README.md
2026-02-03T21:09:33 | commands | /home/lmiranda/claude-plugins-work/plugins/data-platform/commands/data-review.md | docs/COMMANDS-CHEATSHEET.md README.md
2026-02-03T21:09:35 | commands | /home/lmiranda/claude-plugins-work/plugins/viz-platform/commands/viz-dashboard.md | docs/COMMANDS-CHEATSHEET.md README.md
2026-02-03T21:09:38 | commands | /home/lmiranda/claude-plugins-work/plugins/viz-platform/commands/viz-component.md | docs/COMMANDS-CHEATSHEET.md README.md
2026-02-03T21:09:40 | commands | /home/lmiranda/claude-plugins-work/plugins/viz-platform/commands/viz-breakpoints.md | docs/COMMANDS-CHEATSHEET.md README.md
2026-02-03T21:09:46 | commands | /home/lmiranda/claude-plugins-work/plugins/viz-platform/commands/design-review.md | docs/COMMANDS-CHEATSHEET.md README.md
2026-02-03T21:10:22 | commands | /home/lmiranda/claude-plugins-work/plugins/viz-platform/commands/accessibility-check.md | docs/COMMANDS-CHEATSHEET.md README.md
2026-02-04T21:32:01 | .claude-plugin | /home/lmiranda/claude-plugins-work/.claude-plugin/marketplace-lean.json | CLAUDE.md .claude-plugin/marketplace.json

View File

@@ -6,6 +6,207 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [Unreleased] ## [Unreleased]
### Changed — BREAKING
#### NetBox MCP Server: Gutted to 37 Tools (from 182)
Removed all tools not needed for tracking applications, services, databases, and VPS/servers.
**Deleted modules (entire files removed):**
- `circuits.py` — providers, circuits, terminations
- `tenancy.py` — tenants, contacts
- `vpn.py` — tunnels, IKE/IPSec, L2VPN
- `wireless.py` — WLANs, links, groups
**Deleted tool categories within remaining modules:**
- DCIM: regions, locations, racks, rack roles, manufacturers, device types, platforms, cables, console ports, front/rear ports, module bays, power panels, power feeds, virtual chassis, inventory items
- IPAM: VLANs, VLAN groups, VRFs, ASNs, RIRs, aggregates, available IPs, available prefixes
- Virtualization: cluster types, cluster groups, all delete operations
- Extras: custom fields, webhooks, config contexts, update/delete for tags
**Deleted infrastructure:**
- `NETBOX_ENABLED_MODULES` env var and all module filtering code
- `ALL_MODULES` constant, `PREFIX_TO_MODULE` dict, `_get_tool_module()` function
- Conditional module instantiation in server.py
**Token impact:** ~19,810 → ~3,700 tokens (~81% reduction)
**Remaining tools (37):**
- DCIM: sites (4), devices (4), interfaces (3) = 11
- IPAM: IPs (4), prefixes (3), services (3) = 10
- Virtualization: clusters (3), VMs (4), VM interfaces (3) = 10
- Extras: tags (3), journal entries (3) = 6
### Added
- **All plugins:** Dispatch files now active command handlers — bare `/noun` shows available sub-commands and prompts for selection instead of doing nothing
- **git-flow:** New `/gitflow setup` command — auto-detects Gitea system config, configures workflow settings, injects CLAUDE.md git-flow section automatically
- **git-flow:** `claude-md-integration.md` moved to `skills/` for programmatic use by setup command
- **project-hygiene:** New `hygiene.md` dispatch file for bare `/hygiene` invocation
### Changed
- **gitea MCP:** Switched from local source (`mcp_server/`) to published `gitea-mcp>=1.0.0` package from Gitea PyPI registry
- **gitea MCP:** Module namespace changed: `mcp_server.server``gitea_mcp.server`
- **mcp-servers/gitea/:** Thinned to venv wrapper — source code removed, package installed from registry
- **All dispatch files:** Renamed `## Sub-commands` to `## Available Commands`, added `## Workflow` section
### Fixed
- **All plugins:** Bare `/noun` commands no longer dead-end — they display sub-command menus and prompt for selection
## [9.1.2] - 2026-02-07
### Fixed
- **BREAKING FIX:** Removed `"domain"` field from all `marketplace.json` and `plugin.json` files — Claude Code's strict schema validator rejects unrecognized keys, causing `Failed to load marketplace` error
- Domain metadata moved to `metadata.json` per plugin (same pattern as `mcp_servers`)
- `validate-marketplace.sh` updated to read domain from `metadata.json` instead of `marketplace.json`/`plugin.json`
- All documentation updated to reference `metadata.json` as domain source
## [9.1.1] - 2026-02-07
### Changed
- README.md fully rewritten — clean structure with plugins grouped by domain, accurate structure tree, all 10 scripts, all 7 docs
- CLAUDE.md structure tree updated to match README (was showing only 12 plugins, 3 scripts, 2 docs)
- doc-guardian `/doc sync` and `sync-workflow.md` updated to remove stale `.doc-guardian-queue` references (queue file deleted in v8.1.0)
### Removed
- `scripts/check-venv.sh` — dead code designed for SessionStart hooks that were never implemented; functionality covered by `setup-venvs.sh`
## [9.1.0] - 2026-02-07
### Added
- `docs/ARCHITECTURE.md` — Consolidated architecture document covering all 20 plugins, 5 MCP servers, hook inventory, agent model, launch profiles, and per-plugin command reference
### Changed
- All 12 original plugin versions bumped to 9.0.1 in both `plugin.json` and `marketplace.json` (were at various pre-9.x versions)
- `project-hygiene` description updated from "Post-task cleanup hook" to "Manual project hygiene checks" in both manifests; removed "hooks" and "automation" keywords
- `CANONICAL-PATHS.md` refreshed for v9.1.0: added Phase 3 plugins, added ARCHITECTURE.md and MIGRATION-v9.md, removed stale hooks/ dirs, updated Domain table
- `UPDATING.md` updated with all 5 MCP servers
- `COMMANDS-CHEATSHEET.md` expanded /rfc, /project, /adr to individual rows per sub-command
- `README.md` documentation table and structure tree updated; command rows normalized; project-hygiene description corrected
- `CLAUDE.md` documentation index updated with ARCHITECTURE.md and MIGRATION-v9.md; plugin version table updated; /rfc, /project, /adr commands expanded
### Removed
- `.doc-guardian-queue` — orphan file from deleted PostToolUse hook
- `.claude/backups/CLAUDE.md.2026-01-22_132037` — v3.0.1 backup, superseded by git history
- `scripts/switch-profile.sh` — deprecated in favor of `claude-launch.sh`
- `docs/architecture/` — stale pre-v3.0.0 Draw.io specs (replaced by `docs/ARCHITECTURE.md`)
- `docs/designs/` — Phase 3 design specs (implemented as plugin scaffolds, now redundant)
- `docs/prompts/` — moved to Gitea Wiki
## [9.0.1] - 2026-02-06
### Fixed
- **claude-config-maintainer:** `claude-config-audit-settings.md` Step 4 referenced deleted hooks.json files (doc-guardian, project-hygiene, data-platform, contract-validator) — updated to current hook inventory (code-sentinel, git-flow, cmdb-assistant, clarity-assist)
- **claude-config-maintainer:** `maintainer.md` agent referenced project-hygiene PostToolUse hooks — updated to current hook types
- **claude-config-maintainer:** `claude-config-audit-settings.md` output format referenced doc-guardian review layer — updated to git-flow, cmdb-assistant, clarity-assist
- **claude-config-maintainer:** `claude-config-audit-settings.md` Mermaid diagram referenced doc-guardian — updated to git-flow
- **claude-config-maintainer:** `claude-config-optimize-settings.md` reviewed profile prerequisites referenced doc-guardian PostToolUse — updated to git-flow PreToolUse
- **project-hygiene:** `claude-md-integration.md` described PostToolUse hook behavior that was removed in v8.1.0 — rewritten for manual `/hygiene check` command
- **doc-guardian:** `doc-sync.md` referenced doc-guardian hooks — updated to reference `/doc audit`
- **doc-guardian:** `sync-workflow.md` referenced PostToolUse hook — updated to note removal per Decision #29
- **projman:** `task-sizing.md` example referenced PostToolUse — updated to PreToolUse
- **docs:** `MIGRATION-v9.md` listed `/pm-debug`, `/suggest-version`, `/proposal-status` as renamed to `/projman` sub-commands — corrected to show as **Removed** (these were deleted in v8.1.0, not renamed in v9.0.0)
- **docs:** `CONFIGURATION.md` listed doc-guardian as "Commands and hooks only" — corrected to "Commands only"
- **scripts:** `setup.sh` referenced old `/labels-sync` command — updated to `/labels sync`
## [9.0.0] - 2026-02-06
### Added
- **Phase 3: 8 new plugin scaffolds**
- `saas-api-platform` (domain: saas) — REST/GraphQL API scaffolding for FastAPI and Express. 6 commands, 2 agents, 5 skills
- `saas-db-migrate` (domain: saas) — Database migration management for Alembic, Prisma, and raw SQL. 6 commands, 2 agents, 5 skills
- `saas-react-platform` (domain: saas) — React frontend toolkit for Next.js and Vite projects. 6 commands, 2 agents, 6 skills
- `saas-test-pilot` (domain: saas) — Test automation for pytest, Jest, Vitest, and Playwright. 6 commands, 2 agents, 6 skills
- `data-seed` (domain: data) — Test data generation and database seeding. 5 commands, 2 agents, 5 skills
- `ops-release-manager` (domain: ops) — Release management with SemVer, changelogs, and tag automation. 6 commands, 2 agents, 5 skills
- `ops-deploy-pipeline` (domain: ops) — CI/CD deployment pipeline for Docker Compose and systemd. 6 commands, 2 agents, 6 skills
- `debug-mcp` (domain: debug) — MCP server debugging, inspection, and development toolkit. 5 commands, 1 agent, 5 skills
- 8 design documents in `docs/designs/` for all new plugins
---
## [9.0.0] - 2026-02-06
### BREAKING CHANGES
#### Command Consolidation (v9.0.0)
All commands renamed to `/<noun> <action>` sub-command pattern. Every command across all 12 plugins now follows this convention. See [MIGRATION-v9.md](./docs/MIGRATION-v9.md) for the complete old-to-new mapping.
**Key changes:**
- **projman:** `/sprint-plan``/sprint plan`, `/pm-setup``/projman setup`, `/pm-review``/sprint review`, `/pm-test``/sprint test`, `/labels-sync``/labels sync`
- **git-flow:** 8→5 commands. `/git-commit``/gitflow commit`. Three commit variants (`-push`, `-merge`, `-sync`) consolidated into `--push`/`--merge`/`--sync` flags. `/branch-start``/gitflow branch-start`, `/git-status``/gitflow status`, `/git-config``/gitflow config`
- **pr-review:** `/pr-review``/pr review`, `/project-init``/pr init`, `/project-sync``/pr sync`
- **clarity-assist:** `/clarify``/clarity clarify`, `/quick-clarify``/clarity quick-clarify`
- **doc-guardian:** `/doc-audit``/doc audit`, `/changelog-gen``/doc changelog-gen`, `/stale-docs``/doc stale-docs`
- **code-sentinel:** `/security-scan``/sentinel scan`, `/refactor``/sentinel refactor`
- **claude-config-maintainer:** `/config-analyze``/claude-config analyze` (all 8 commands prefixed)
- **contract-validator:** `/validate-contracts``/cv validate`, `/check-agent``/cv check-agent`
- **cmdb-assistant:** `/cmdb-search``/cmdb search`, `/change-audit``/cmdb change-audit`, `/ip-conflicts``/cmdb ip-conflicts`
- **data-platform:** `/data-ingest``/data ingest`, `/dbt-test``/data dbt-test`, `/lineage-viz``/data lineage-viz`
- **viz-platform:** `/accessibility-check``/viz accessibility-check`, `/design-gate``/viz design-gate`, `/design-review``/viz design-review`
### Added
- Dispatch files for all 12 plugins — each plugin now has a `<noun>.md` routing table listing all sub-commands
- `name:` frontmatter field added to all command files for sub-command resolution
- `docs/MIGRATION-v9.md` — Complete old-to-new command mapping for consumer migration
- `docs/COMMANDS-CHEATSHEET.md` — Full rewrite with v9.0.0 command names
### Changed
- All documentation updated with new command names: CLAUDE.md, README.md, CONFIGURATION.md, UPDATING.md, agent-workflow.spec.md, netbox/README.md
- All cross-plugin references updated (skills, agents, integration files)
- `marketplace.json` version bumped to 9.0.0
---
## [8.1.0] - 2026-02-06
### BREAKING CHANGES
#### Hook Migration (v8.1.0)
All `SessionStart` and `PostToolUse` hooks removed. Only `PreToolUse` safety hooks and `UserPromptSubmit` quality hooks remain. Plugins that relied on automatic startup checks or post-write automation must use manual commands instead.
### Added
- **projman:** 7 new skills — `source-analysis`, `project-charter`, `adr-conventions`, `epic-conventions`, `wbs`, `risk-register`, `sprint-roadmap`
- **projman:** `/project` command family — `initiation`, `plan`, `status`, `close` for full project lifecycle management
- **projman:** `/adr` command family — `create`, `list`, `update`, `supersede` for Architecture Decision Records
- **projman:** Expanded `wiki-conventions.md` with dependency headers, R&D notes, page naming patterns
- **projman:** Epic/* labels (5) and RnD/* labels (4) added to label taxonomy
- **project-hygiene:** `/hygiene check` manual command replacing PostToolUse hook
- **contract-validator:** `/cv status` marketplace-wide health check command
### Changed
- `verify-hooks.sh` rewritten to validate post-migration hook inventory (4 plugins, 5 hooks)
- `config-permissions-map.md` updated to reflect reduced hook inventory
- `settings-optimization.md` updated for current hook landscape
- `sprint-plan.md` no longer loads `token-budget-report.md` skill
- `sprint-close.md` loads `rfc-workflow.md` conditionally; manual CHANGELOG review replaces `/suggest-version`
- `planner.md` and `orchestrator.md` no longer reference domain consultation or domain gates
- Label taxonomy updated from 43 to 58 labels (added Status/4, Domain/2, Epic/5, RnD/4)
### Removed
- **hooks:** 8 hooks.json files deleted (projman, pr-review, doc-guardian, project-hygiene, claude-config-maintainer, viz-platform, data-platform, contract-validator SessionStart/PostToolUse hooks)
- **hooks:** Orphaned shell scripts deleted (startup-check.sh, notify.sh, cleanup.sh, enforce-rules.sh, schema-diff-check.sh, auto-validate.sh, breaking-change-check.sh)
- **projman:** `/pm-debug`, `/suggest-version`, `/proposal-status` commands deleted
- **projman:** `domain-consultation.md` skill deleted
- **cmdb-assistant:** SessionStart hook removed (PreToolUse hook retained)
--- ---
## [8.0.0] - 2026-02-06 ## [8.0.0] - 2026-02-06

234
CLAUDE.md
View File

@@ -128,43 +128,59 @@ These plugins exist in source but are **NOT relevant** to this project's workflo
| **data-platform** | For data engineering projects (pandas, PostgreSQL, dbt) | | **data-platform** | For data engineering projects (pandas, PostgreSQL, dbt) |
| **viz-platform** | For dashboard projects (Dash, Plotly) | | **viz-platform** | For dashboard projects (Dash, Plotly) |
| **cmdb-assistant** | For infrastructure projects (NetBox) | | **cmdb-assistant** | For infrastructure projects (NetBox) |
| **saas-api-platform** | For REST/GraphQL API projects (FastAPI, Express) |
| **saas-db-migrate** | For database migration projects (Alembic, Prisma) |
| **saas-react-platform** | For React frontend projects (Next.js, Vite) |
| **saas-test-pilot** | For test automation projects (pytest, Jest, Playwright) |
| **data-seed** | For test data generation and seeding |
| **ops-release-manager** | For release management workflows |
| **ops-deploy-pipeline** | For deployment pipeline management |
| **debug-mcp** | For MCP server debugging and development |
**Do NOT suggest** `/data-ingest`, `/data-profile`, `/viz-chart`, `/cmdb-*` commands - they don't apply here. **Do NOT suggest** `/data ingest`, `/data profile`, `/viz chart`, `/cmdb *`, `/api *`, `/db-migrate *`, `/react *`, `/test *`, `/seed *`, `/release *`, `/deploy *`, `/debug-mcp *` commands - they don't apply here.
### Key Distinction ### Key Distinction
| Context | Path | What To Do | | Context | Path | What To Do |
|---------|------|------------| |---------|------|------------|
| **Editing plugin source** | `~/claude-plugins-work/plugins/` | Modify code, add features | | **Editing plugin source** | `~/claude-plugins-work/plugins/` | Modify code, add features |
| **Using installed plugins** | `~/.claude/plugins/marketplaces/` | Run commands like `/sprint-plan` | | **Using installed plugins** | `~/.claude/plugins/marketplaces/` | Run commands like `/sprint plan` |
When user says "run /sprint-plan", use the INSTALLED plugin. When user says "run /sprint plan", use the INSTALLED plugin.
When user says "fix the sprint-plan command", edit the SOURCE code. When user says "fix the sprint plan command", edit the SOURCE code.
--- ---
## Project Overview ## Project Overview
**Repository:** leo-claude-mktplace **Repository:** leo-claude-mktplace
**Version:** 7.0.0 **Version:** 9.1.2
**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.3.0 | | `projman` | Sprint planning and project management with Gitea integration | 9.0.1 |
| `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 | 9.0.1 |
| `pr-review` | Multi-agent PR review with confidence scoring | 1.1.0 | | `pr-review` | Multi-agent PR review with confidence scoring | 9.0.1 |
| `clarity-assist` | Prompt optimization with ND-friendly accommodations | 1.0.0 | | `clarity-assist` | Prompt optimization with ND-friendly accommodations | 9.0.1 |
| `doc-guardian` | Automatic documentation drift detection and synchronization | 1.0.0 | | `doc-guardian` | Automatic documentation drift detection and synchronization | 9.0.1 |
| `code-sentinel` | Security scanning and code refactoring tools | 1.0.1 | | `code-sentinel` | Security scanning and code refactoring tools | 9.0.1 |
| `claude-config-maintainer` | CLAUDE.md optimization and maintenance | 1.0.0 | | `claude-config-maintainer` | CLAUDE.md optimization and maintenance | 9.0.1 |
| `cmdb-assistant` | NetBox CMDB integration for infrastructure management | 1.2.0 | | `cmdb-assistant` | NetBox CMDB integration for infrastructure management | 9.0.1 |
| `data-platform` | pandas, PostgreSQL, and dbt integration for data engineering | 1.3.0 | | `data-platform` | pandas, PostgreSQL, and dbt integration for data engineering | 9.0.1 |
| `viz-platform` | DMC validation, Plotly charts, and theming for dashboards | 1.1.0 | | `viz-platform` | DMC validation, Plotly charts, and theming for dashboards | 9.0.1 |
| `contract-validator` | Cross-plugin compatibility validation and agent verification | 1.1.0 | | `contract-validator` | Cross-plugin compatibility validation and agent verification | 9.0.1 |
| `project-hygiene` | Post-task cleanup automation via hooks | 0.1.0 | | `project-hygiene` | Manual project hygiene checks | 9.0.1 |
| `saas-api-platform` | REST/GraphQL API scaffolding for FastAPI and Express | 0.1.0 |
| `saas-db-migrate` | Database migration management for Alembic, Prisma, raw SQL | 0.1.0 |
| `saas-react-platform` | React frontend toolkit for Next.js and Vite | 0.1.0 |
| `saas-test-pilot` | Test automation for pytest, Jest, Vitest, Playwright | 0.1.0 |
| `data-seed` | Test data generation and database seeding | 0.1.0 |
| `ops-release-manager` | Release management with SemVer and changelog automation | 0.1.0 |
| `ops-deploy-pipeline` | Deployment pipeline for Docker Compose and systemd | 0.1.0 |
| `debug-mcp` | MCP server debugging and development toolkit | 0.1.0 |
## Quick Start ## Quick Start
@@ -180,16 +196,18 @@ A plugin marketplace for Claude Code containing:
| Category | Commands | | Category | Commands |
|----------|----------| |----------|----------|
| **Setup** | `/pm-setup` (modes: `--full`, `--quick`, `--sync`) | | **Setup** | `/projman setup` (modes: `--full`, `--quick`, `--sync`) |
| **Sprint** | `/sprint-plan`, `/sprint-start`, `/sprint-status` (with `--diagram`), `/sprint-close` | | **Sprint** | `/sprint plan`, `/sprint start`, `/sprint status` (with `--diagram`), `/sprint close` |
| **Quality** | `/pm-review`, `/pm-test` (modes: `run`, `gen`) | | **Quality** | `/sprint review`, `/sprint test` (modes: `run`, `gen`) |
| **Versioning** | `/suggest-version` | | **Project** | `/project initiation`, `/project plan`, `/project status`, `/project close` |
| **PR Review** | `/pr-review`, `/pr-summary`, `/pr-findings`, `/pr-diff` | | **ADR** | `/adr create`, `/adr list`, `/adr update`, `/adr supersede` |
| **Docs** | `/doc-audit`, `/doc-sync`, `/changelog-gen`, `/doc-coverage`, `/stale-docs` | | **RFC** | `/rfc create`, `/rfc list`, `/rfc review`, `/rfc approve`, `/rfc reject` |
| **Security** | `/security-scan`, `/refactor`, `/refactor-dry` | | **PR Review** | `/pr review`, `/pr summary`, `/pr findings`, `/pr diff` |
| **Config** | `/config-analyze`, `/config-optimize`, `/config-diff`, `/config-lint` | | **Docs** | `/doc audit`, `/doc sync`, `/doc changelog-gen`, `/doc coverage`, `/doc stale-docs` |
| **Validation** | `/validate-contracts`, `/check-agent`, `/list-interfaces`, `/dependency-graph` | | **Security** | `/sentinel scan`, `/sentinel refactor`, `/sentinel refactor-dry` |
| **Debug** | `/pm-debug` (modes: `report`, `review`) | | **Config** | `/claude-config analyze`, `/claude-config optimize`, `/claude-config diff`, `/claude-config lint` |
| **Validation** | `/cv validate`, `/cv check-agent`, `/cv list-interfaces`, `/cv dependency-graph`, `/cv status` |
| **Maintenance** | `/hygiene check` |
### Plugin Commands - NOT RELEVANT to This Project ### Plugin Commands - NOT RELEVANT to This Project
@@ -197,67 +215,82 @@ These commands are being developed but don't apply to this project's workflow:
| Category | Commands | For Projects Using | | Category | Commands | For Projects Using |
|----------|----------|-------------------| |----------|----------|-------------------|
| **Data** | `/data-ingest`, `/data-profile`, `/data-schema`, `/data-lineage`, `/dbt-test` | pandas, PostgreSQL, dbt | | **Data** | `/data ingest`, `/data profile`, `/data schema`, `/data lineage`, `/data dbt-test` | pandas, PostgreSQL, dbt |
| **Visualization** | `/viz-component`, `/viz-chart`, `/viz-dashboard`, `/viz-theme` | Dash, Plotly dashboards | | **Visualization** | `/viz component`, `/viz chart`, `/viz dashboard`, `/viz theme` | Dash, Plotly dashboards |
| **CMDB** | `/cmdb-search`, `/cmdb-device`, `/cmdb-sync` | NetBox infrastructure | | **CMDB** | `/cmdb search`, `/cmdb device`, `/cmdb sync` | NetBox infrastructure |
| **API** | `/api scaffold`, `/api validate`, `/api docs`, `/api middleware` | FastAPI, Express |
| **DB Migrate** | `/db-migrate generate`, `/db-migrate validate`, `/db-migrate plan` | Alembic, Prisma |
| **React** | `/react component`, `/react route`, `/react state`, `/react hook` | Next.js, Vite |
| **Testing** | `/test generate`, `/test coverage`, `/test fixtures`, `/test e2e` | pytest, Jest, Playwright |
| **Seeding** | `/seed generate`, `/seed profile`, `/seed apply` | Faker, test data |
| **Release** | `/release prepare`, `/release validate`, `/release tag` | SemVer releases |
| **Deploy** | `/deploy generate`, `/deploy validate`, `/deploy check` | Docker Compose, systemd |
| **Debug MCP** | `/debug-mcp status`, `/debug-mcp test`, `/debug-mcp logs` | MCP server development |
## Repository Structure ## Repository Structure
``` ```
leo-claude-mktplace/ leo-claude-mktplace/
├── .claude-plugin/ ├── .claude-plugin/ # Marketplace manifest
── marketplace.json # Marketplace manifest ── marketplace.json
│ ├── marketplace-lean.json # Lean profile (6 core plugins)
│ └── marketplace-full.json # Full profile (all plugins)
├── .mcp.json # MCP server configuration (all servers) ├── .mcp.json # MCP server configuration (all servers)
├── mcp-servers/ # SHARED MCP servers ├── mcp-servers/ # SHARED MCP servers
│ ├── gitea/ # Gitea MCP (issues, PRs, wiki) │ ├── gitea/ # Gitea (issues, PRs, wiki)
│ ├── netbox/ # NetBox MCP (CMDB) │ ├── netbox/ # NetBox (DCIM, IPAM)
│ ├── data-platform/ # pandas, PostgreSQL, dbt │ ├── data-platform/ # pandas, PostgreSQL, dbt
│ ├── viz-platform/ # DMC validation, charts, themes │ ├── viz-platform/ # DMC, Plotly, theming
│ └── contract-validator/ # Plugin compatibility validation │ └── contract-validator/ # Plugin compatibility validation
├── plugins/ ├── plugins/ # All plugins (20 total)
│ ├── projman/ # Sprint management │ ├── projman/ # [core] Sprint management
│ │ ├── .claude-plugin/plugin.json │ │ ├── .claude-plugin/plugin.json
│ │ ├── commands/ # 12 commands │ │ ├── commands/ # 19 commands
│ │ ├── hooks/ # SessionStart: mismatch detection
│ │ ├── agents/ # 4 agents │ │ ├── agents/ # 4 agents
│ │ └── skills/ # 17 reusable skill files │ │ └── skills/ # 23 reusable skill files
│ ├── git-flow/ # Git workflow automation │ ├── git-flow/ # [core] Git workflow automation
│ ├── .claude-plugin/plugin.json │ ├── pr-review/ # [core] PR review
│ ├── commands/ # 8 commands │ ├── clarity-assist/ # [core] Prompt optimization
│ └── agents/ ├── doc-guardian/ # [core] Documentation drift detection
│ ├── pr-review/ # Multi-agent PR review │ ├── code-sentinel/ # [core] Security scanning
│ ├── .claude-plugin/plugin.json │ ├── claude-config-maintainer/ # [core] CLAUDE.md optimization
│ ├── commands/ # 6 commands │ ├── contract-validator/ # [core] Cross-plugin validation
│ ├── hooks/ # SessionStart mismatch detection │ ├── project-hygiene/ # [core] Manual cleanup checks
│ └── agents/ # 5 agents ├── cmdb-assistant/ # [ops] NetBox CMDB integration
│ ├── clarity-assist/ # Prompt optimization │ ├── data-platform/ # [data] Data engineering
│ ├── .claude-plugin/plugin.json │ ├── viz-platform/ # [data] Visualization
│ ├── commands/ # 2 commands │ ├── data-seed/ # [data] Test data generation (scaffold)
│ └── agents/ ├── saas-api-platform/ # [saas] API scaffolding (scaffold)
│ ├── data-platform/ # Data engineering │ ├── saas-db-migrate/ # [saas] DB migrations (scaffold)
│ ├── .claude-plugin/plugin.json │ ├── saas-react-platform/ # [saas] React toolkit (scaffold)
│ ├── commands/ # 7 commands │ ├── saas-test-pilot/ # [saas] Test automation (scaffold)
│ ├── hooks/ # SessionStart PostgreSQL check │ ├── ops-release-manager/ # [ops] Release management (scaffold)
│ └── agents/ # 2 agents ├── ops-deploy-pipeline/ # [ops] Deployment pipeline (scaffold)
── viz-platform/ # Visualization ── debug-mcp/ # [debug] MCP debugging (scaffold)
├── .claude-plugin/plugin.json ├── scripts/ # Setup and maintenance
│ ├── commands/ # 7 commands │ ├── setup.sh # Initial setup (create venvs, config)
│ ├── hooks/ # SessionStart DMC check │ ├── post-update.sh # Post-update (clear cache, changelog)
│ └── agents/ # 3 agents ├── setup-venvs.sh # MCP server venv management (cache-based)
│ ├── doc-guardian/ # Documentation drift detection
│ ├── code-sentinel/ # Security scanning & refactoring
│ ├── claude-config-maintainer/
│ ├── cmdb-assistant/
│ ├── contract-validator/
│ └── project-hygiene/
├── scripts/
│ ├── setup.sh, post-update.sh
│ ├── validate-marketplace.sh # Marketplace compliance validation │ ├── validate-marketplace.sh # Marketplace compliance validation
│ ├── verify-hooks.sh # Verify all hooks are command type │ ├── verify-hooks.sh # Hook inventory verification
── check-venv.sh # Check MCP server venvs exist ── release.sh # Release automation with version bumping
└── docs/ │ ├── claude-launch.sh # Profile-based launcher
├── CANONICAL-PATHS.md # Single source of truth for paths ├── install-plugin.sh # Install plugin to consumer project
── CONFIGURATION.md # Centralized configuration guide ── list-installed.sh # Show installed plugins in a project
│ └── uninstall-plugin.sh # Remove plugin from consumer project
├── docs/ # Documentation
│ ├── ARCHITECTURE.md # System architecture & plugin reference
│ ├── CANONICAL-PATHS.md # Authoritative path reference
│ ├── COMMANDS-CHEATSHEET.md # All commands quick reference
│ ├── CONFIGURATION.md # Centralized setup guide
│ ├── DEBUGGING-CHECKLIST.md # Systematic troubleshooting guide
│ ├── MIGRATION-v9.md # v8.x to v9.0.0 migration guide
│ └── UPDATING.md # Update guide
├── CLAUDE.md # Project instructions for Claude Code
├── README.md
├── CHANGELOG.md
├── LICENSE
└── .gitignore
``` ```
## Architecture ## Architecture
@@ -366,17 +399,17 @@ Wiki-based Request for Comments system for tracking feature ideas from proposal
**Lifecycle:** Draft → Review → Approved → Implementing → Implemented **Lifecycle:** Draft → Review → Approved → Implementing → Implemented
**Integration with Sprint Planning:** **Integration with Sprint Planning:**
- `/sprint-plan` detects approved RFCs and offers selection - `/sprint plan` detects approved RFCs and offers selection
- `/sprint-close` updates RFC status on completion - `/sprint close` updates RFC status on completion
## Label Taxonomy ## Label Taxonomy
43 labels total: 27 organization + 16 repository 58 labels total: 31 organization + 27 repository
**Organization:** Agent/2, Complexity/3, Efforts/5, Priority/4, Risk/3, Source/4, Type/6 **Organization:** Agent/2, Complexity/3, Efforts/5, Priority/4, Risk/3, Source/4, Status/4, Type/6
**Repository:** Component/9, Tech/7 **Repository:** Component/9, Tech/7, Domain/2, Epic/5, RnD/4
Sync with `/labels-sync` command. Sync with `/labels sync` command.
## Lessons Learned System ## Lessons Learned System
@@ -391,19 +424,18 @@ Stored in Gitea Wiki under `lessons-learned/sprints/`.
### Adding a New Plugin ### Adding a New Plugin
1. Create `plugins/{name}/.claude-plugin/plugin.json` — must include `"domain"` field (`core`, `data`, `saas`, `ops`, or `debug`) 1. Create `plugins/{name}/.claude-plugin/plugin.json` (standard schema fields only — no custom fields)
2. Add entry to `.claude-plugin/marketplace.json` with category, tags, license, and `"domain"` field (must match plugin.json) 2. Create `plugins/{name}/.claude-plugin/metadata.json` — must include `"domain"` field (`core`, `data`, `saas`, `ops`, or `debug`)
3. Create `claude-md-integration.md` 3. Add entry to `.claude-plugin/marketplace.json` with category, tags, license (no custom fields — Claude Code schema is strict)
4. If using new MCP server, add to root `mcp-servers/` and update `.mcp.json` 4. Create `claude-md-integration.md`
5. Run `./scripts/validate-marketplace.sh` — rejects plugins without valid `domain` field 5. If using new MCP server, add to root `mcp-servers/` and update `.mcp.json`
6. Update `CHANGELOG.md` 6. Run `./scripts/validate-marketplace.sh` — rejects plugins without valid `domain` field
7. Update `CHANGELOG.md`
**Domain field is required (v8.0.0+):** **Domain field is required in metadata.json (v8.0.0+, moved from plugin.json in v9.1.2):**
```json ```json
{ {
"name": "plugin-name", "domain": "core"
"domain": "core",
...
} }
``` ```
@@ -414,8 +446,10 @@ Stored in Gitea Wiki under `lessons-learned/sprints/`.
| Domain | Plugins | | Domain | Plugins |
|--------|---------| |--------|---------|
| `core` | projman, git-flow, pr-review, code-sentinel, doc-guardian, clarity-assist, contract-validator, claude-config-maintainer, project-hygiene | | `core` | projman, git-flow, pr-review, code-sentinel, doc-guardian, clarity-assist, contract-validator, claude-config-maintainer, project-hygiene |
| `data` | data-platform, viz-platform | | `data` | data-platform, viz-platform, data-seed |
| `ops` | cmdb-assistant | | `saas` | saas-api-platform, saas-db-migrate, saas-react-platform, saas-test-pilot |
| `ops` | cmdb-assistant, ops-release-manager, ops-deploy-pipeline |
| `debug` | debug-mcp |
### Adding a Command to projman ### Adding a Command to projman
@@ -441,10 +475,12 @@ Stored in Gitea Wiki under `lessons-learned/sprints/`.
| Document | Purpose | | Document | Purpose |
|----------|---------| |----------|---------|
| `docs/ARCHITECTURE.md` | System architecture and plugin reference |
| `docs/CANONICAL-PATHS.md` | **Single source of truth** for paths | | `docs/CANONICAL-PATHS.md` | **Single source of truth** for paths |
| `docs/COMMANDS-CHEATSHEET.md` | All commands quick reference | | `docs/COMMANDS-CHEATSHEET.md` | All commands quick reference |
| `docs/CONFIGURATION.md` | Centralized setup guide | | `docs/CONFIGURATION.md` | Centralized setup guide |
| `docs/DEBUGGING-CHECKLIST.md` | Systematic troubleshooting guide | | `docs/DEBUGGING-CHECKLIST.md` | Systematic troubleshooting guide |
| `docs/MIGRATION-v9.md` | v8.x to v9.0.0 migration guide |
| `docs/UPDATING.md` | Update guide for the marketplace | | `docs/UPDATING.md` | Update guide for the marketplace |
| `plugins/projman/CONFIGURATION.md` | Projman quick reference (links to central) | | `plugins/projman/CONFIGURATION.md` | Projman quick reference (links to central) |
@@ -468,12 +504,12 @@ See `docs/DEBUGGING-CHECKLIST.md` for systematic troubleshooting.
| Symptom | Likely Cause | Fix | | Symptom | Likely Cause | Fix |
|---------|--------------|-----| |---------|--------------|-----|
| "X MCP servers failed" | Missing venv in installed path | `cd ~/.claude/plugins/marketplaces/leo-claude-mktplace && ./scripts/setup.sh` | | "X MCP servers failed" | Missing venv in installed path | `cd ~/.claude/plugins/marketplaces/leo-claude-mktplace && ./scripts/setup.sh` |
| MCP tools not available | Venv missing or .mcp.json misconfigured | Run `/pm-debug report` to diagnose | | MCP tools not available | Venv missing or .mcp.json misconfigured | Run `/cv status` to diagnose |
| Changes not taking effect | Editing source, not installed | Reinstall plugin or edit installed path | | Changes not taking effect | Editing source, not installed | Reinstall plugin or edit installed path |
**Debug Commands:** **Diagnostic Commands:**
- `/pm-debug report` - Run full diagnostics, create issue if needed - `/cv status` - Marketplace-wide health check (installation, MCP, configuration)
- `/pm-debug review` - Investigate and propose fixes - `/hygiene check` - Project file organization and cleanup check
## Versioning Workflow ## Versioning Workflow
@@ -527,4 +563,4 @@ The script will:
--- ---
**Last Updated:** 2026-02-03 **Last Updated:** 2026-02-07

479
README.md
View File

@@ -1,10 +1,57 @@
# Leo Claude Marketplace - v8.0.0 # Leo Claude Marketplace v9.1.2
A collection of Claude Code plugins for project management, infrastructure automation, and development workflows. A plugin marketplace for Claude Code providing sprint management, code review, security scanning, infrastructure automation, and development workflow tools. 20 plugins across 5 domains, backed by 5 shared MCP servers.
## Plugins
### Core (9 plugins — v9.0.1)
| Plugin | Description |
|--------|-------------|
| `projman` | Sprint planning and project management with Gitea integration |
| `git-flow` | Git workflow automation with intelligent commit messages and branch management |
| `pr-review` | Multi-agent pull request review with confidence scoring |
| `code-sentinel` | Security scanning and code refactoring tools |
| `doc-guardian` | Documentation drift detection and synchronization |
| `clarity-assist` | Prompt optimization with ND-friendly accommodations |
| `contract-validator` | Cross-plugin compatibility validation and agent verification |
| `claude-config-maintainer` | CLAUDE.md and settings.local.json optimization |
| `project-hygiene` | Manual project file cleanup checks |
### Data (3 plugins)
| Plugin | Version | Description |
|--------|---------|-------------|
| `data-platform` | 9.0.1 | pandas, PostgreSQL/PostGIS, and dbt integration |
| `viz-platform` | 9.0.1 | Dash Mantine Components validation, Plotly charts, and theming |
| `data-seed` | 0.1.0 — scaffold | Test data generation and database seeding |
### Ops (3 plugins)
| Plugin | Version | Description |
|--------|---------|-------------|
| `cmdb-assistant` | 9.0.1 | NetBox CMDB integration with data quality validation |
| `ops-release-manager` | 0.1.0 — scaffold | Release management with SemVer and changelog automation |
| `ops-deploy-pipeline` | 0.1.0 — scaffold | Deployment pipeline for Docker Compose and systemd |
### SaaS (4 plugins — v0.1.0 scaffolds)
| Plugin | Description |
|--------|-------------|
| `saas-api-platform` | REST/GraphQL API scaffolding for FastAPI and Express |
| `saas-db-migrate` | Database migration management for Alembic, Prisma, raw SQL |
| `saas-react-platform` | React frontend toolkit for Next.js and Vite |
| `saas-test-pilot` | Test automation for pytest, Jest, Vitest, Playwright |
### Debug (1 plugin — v0.1.0 scaffold)
| Plugin | Description |
|--------|-------------|
| `debug-mcp` | MCP server debugging, inspection, and development toolkit |
## Quick Start ## Quick Start
Use the launcher script to load only the plugins you need, reducing token overhead from ~22K to ~4-6K tokens: ### Launch with profiles
```bash ```bash
./scripts/claude-launch.sh [profile] [extra-args...] ./scripts/claude-launch.sh [profile] [extra-args...]
@@ -16,233 +63,98 @@ Use the launcher script to load only the plugins you need, reducing token overhe
| `review` | pr-review, code-sentinel | Lightweight code review | | `review` | pr-review, code-sentinel | Lightweight code review |
| `data` | data-platform, viz-platform | Data engineering and visualization | | `data` | data-platform, viz-platform | Data engineering and visualization |
| `infra` | cmdb-assistant | Infrastructure/CMDB management | | `infra` | cmdb-assistant | Infrastructure/CMDB management |
| `full` | All 12 plugins via marketplace.json | When you need everything | | `full` | All 20 plugins | When you need everything |
**Examples:**
```bash ```bash
./scripts/claude-launch.sh # Default sprint profile ./scripts/claude-launch.sh # Default sprint profile
./scripts/claude-launch.sh data --model opus # Data profile with Opus ./scripts/claude-launch.sh data --model opus # Data profile with Opus
./scripts/claude-launch.sh full # Load all plugins ./scripts/claude-launch.sh full # Load all plugins
``` ```
The script enables `ENABLE_TOOL_SEARCH=true` for MCP lazy loading. ### Common commands
## Plugins ```bash
/sprint plan # Plan a sprint with architecture analysis
/sprint start # Begin sprint execution
/gitflow commit --push # Commit with auto-generated message and push
/pr review # Full multi-agent PR review
/sentinel scan # Security audit
/doc audit # Check for documentation drift
/cv status # Marketplace health check
```
### Development & Project Management ## Repository Structure
#### [projman](./plugins/projman) ```
**Sprint Planning and Project Management** leo-claude-mktplace/
├── .claude-plugin/ # Marketplace manifest
AI-guided sprint planning with full Gitea integration. Transforms a proven 15-sprint workflow into a distributable plugin. │ ├── marketplace.json
│ ├── marketplace-lean.json # Lean profile (6 core plugins)
- Four-agent model: Planner, Orchestrator, Executor, Code Reviewer │ └── marketplace-full.json # Full profile (all plugins)
- Plan-then-batch execution: skills loaded once per phase, API calls batched for ~80% token savings ├── mcp-servers/ # Shared MCP servers
- Intelligent label suggestions from 43-label taxonomy │ ├── gitea/ # Gitea (issues, PRs, wiki)
- Lessons learned capture via Gitea Wiki │ ├── netbox/ # NetBox (DCIM, IPAM)
- Native issue dependencies with parallel execution │ ├── data-platform/ # pandas, PostgreSQL, dbt
- Milestone management for sprint organization │ ├── viz-platform/ # DMC, Plotly, theming
- Branch-aware security (development/staging/production) │ └── contract-validator/ # Plugin compatibility validation
- Pre-sprint-close code quality review and test verification ├── plugins/ # All plugins (20 total)
│ ├── projman/ # [core] Sprint management
**Commands:** `/sprint-plan`, `/sprint-start`, `/sprint-status`, `/sprint-close`, `/labels-sync`, `/pm-setup`, `/pm-review`, `/pm-test`, `/pm-debug`, `/suggest-version`, `/proposal-status`, `/rfc` │ ├── git-flow/ # [core] Git workflow automation
│ ├── pr-review/ # [core] PR review
#### [git-flow](./plugins/git-flow) │ ├── clarity-assist/ # [core] Prompt optimization
**Git Workflow Automation** │ ├── doc-guardian/ # [core] Documentation drift detection
│ ├── code-sentinel/ # [core] Security scanning
Smart git operations with intelligent commit messages and branch management. │ ├── claude-config-maintainer/ # [core] CLAUDE.md optimization
│ ├── contract-validator/ # [core] Cross-plugin validation
- Auto-generated conventional commit messages │ ├── project-hygiene/ # [core] Manual cleanup checks
- Multiple workflow styles (simple, feature-branch, pr-required, trunk-based) │ ├── cmdb-assistant/ # [ops] NetBox CMDB integration
- Branch naming enforcement │ ├── data-platform/ # [data] Data engineering
- Merge and cleanup automation │ ├── viz-platform/ # [data] Visualization
- Protected branch awareness │ ├── data-seed/ # [data] Test data generation (scaffold)
│ ├── saas-api-platform/ # [saas] API scaffolding (scaffold)
**Commands:** `/git-commit`, `/git-commit-push`, `/git-commit-merge`, `/git-commit-sync`, `/branch-start`, `/branch-cleanup`, `/git-status`, `/git-config` │ ├── saas-db-migrate/ # [saas] DB migrations (scaffold)
│ ├── saas-react-platform/ # [saas] React toolkit (scaffold)
#### [pr-review](./plugins/pr-review) │ ├── saas-test-pilot/ # [saas] Test automation (scaffold)
**Multi-Agent PR Review** │ ├── ops-release-manager/ # [ops] Release management (scaffold)
│ ├── ops-deploy-pipeline/ # [ops] Deployment pipeline (scaffold)
Comprehensive pull request review using specialized agents. │ └── debug-mcp/ # [debug] MCP debugging (scaffold)
├── scripts/ # Setup and maintenance
- Multi-agent review: Security, Performance, Maintainability, Tests │ ├── setup.sh # Initial setup (create venvs, config)
- Confidence scoring (only reports HIGH/MEDIUM confidence findings) │ ├── post-update.sh # Post-update (clear cache, changelog)
- Actionable feedback with suggested fixes │ ├── setup-venvs.sh # MCP server venv management (cache-based)
- Gitea integration for automated review submission │ ├── validate-marketplace.sh # Marketplace compliance validation
│ ├── verify-hooks.sh # Hook inventory verification
**Commands:** `/pr-review`, `/pr-summary`, `/pr-findings`, `/pr-diff`, `/pr-setup`, `/project-init`, `/project-sync` │ ├── release.sh # Release automation with version bumping
│ ├── claude-launch.sh # Profile-based launcher
#### [claude-config-maintainer](./plugins/claude-config-maintainer) │ ├── install-plugin.sh # Install plugin to consumer project
**CLAUDE.md and Settings Optimization** │ ├── list-installed.sh # Show installed plugins in a project
│ └── uninstall-plugin.sh # Remove plugin from consumer project
Analyze, optimize, and create CLAUDE.md configuration files. Audit and optimize settings.local.json permissions. ├── docs/ # Documentation
│ ├── ARCHITECTURE.md # System architecture & plugin reference
**Commands:** `/analyze`, `/optimize`, `/init`, `/config-diff`, `/config-lint`, `/config-audit-settings`, `/config-optimize-settings`, `/config-permissions-map` │ ├── CANONICAL-PATHS.md # Authoritative path reference
│ ├── COMMANDS-CHEATSHEET.md # All commands quick reference
#### [contract-validator](./plugins/contract-validator) │ ├── CONFIGURATION.md # Centralized setup guide
**Cross-Plugin Compatibility Validation** │ ├── DEBUGGING-CHECKLIST.md # Systematic troubleshooting guide
│ ├── MIGRATION-v9.md # v8.x to v9.0.0 migration guide
Validate plugin marketplaces for command conflicts, tool overlaps, and broken agent references. │ └── UPDATING.md # Update guide
├── CLAUDE.md # Project instructions for Claude Code
- Interface parsing from plugin README.md files ├── README.md
- Agent extraction from CLAUDE.md definitions ├── CHANGELOG.md
- Pairwise compatibility checks between all plugins ├── LICENSE
- Data flow validation for agent sequences └── .gitignore
- Markdown or JSON reports with actionable suggestions ```
**Commands:** `/validate-contracts`, `/check-agent`, `/list-interfaces`, `/dependency-graph`, `/cv-setup`
### Productivity
#### [clarity-assist](./plugins/clarity-assist)
**Prompt Optimization with ND Accommodations**
Transform vague requests into clear specifications using structured methodology.
- 4-D methodology: Deconstruct, Diagnose, Develop, Deliver
- ND-friendly question patterns (option-based, chunked)
- Conflict detection and escalation protocols
**Commands:** `/clarify`, `/quick-clarify`
#### [doc-guardian](./plugins/doc-guardian)
**Documentation Lifecycle Management**
Automatic documentation drift detection and synchronization.
**Commands:** `/doc-audit`, `/doc-sync`, `/changelog-gen`, `/doc-coverage`, `/stale-docs`
#### [project-hygiene](./plugins/project-hygiene)
**Post-Task Cleanup Automation**
Hook-based cleanup that runs after Claude completes work.
### Security
#### [code-sentinel](./plugins/code-sentinel)
**Security Scanning & Refactoring**
Security vulnerability detection and code refactoring tools.
**Commands:** `/security-scan`, `/refactor`, `/refactor-dry`
### Infrastructure
#### [cmdb-assistant](./plugins/cmdb-assistant)
**NetBox CMDB Integration**
Full CRUD operations for network infrastructure management directly from Claude Code.
**Commands:** `/cmdb-setup`, `/cmdb-search`, `/cmdb-device`, `/cmdb-ip`, `/cmdb-site`, `/cmdb-audit`, `/cmdb-register`, `/cmdb-sync`, `/cmdb-topology`, `/change-audit`, `/ip-conflicts`
### Data Engineering
#### [data-platform](./plugins/data-platform)
**pandas, PostgreSQL/PostGIS, and dbt Integration**
Comprehensive data engineering toolkit with persistent DataFrame storage.
- 14 pandas tools with Arrow IPC data_ref system
- 10 PostgreSQL/PostGIS tools with connection pooling
- 8 dbt tools with automatic pre-validation
- 100k row limit with chunking support
- Auto-detection of dbt projects
**Commands:** `/data-ingest`, `/data-profile`, `/data-schema`, `/data-explain`, `/data-lineage`, `/lineage-viz`, `/data-run`, `/dbt-test`, `/data-quality`, `/data-review`, `/data-gate`, `/data-setup`
### Visualization
#### [viz-platform](./plugins/viz-platform)
**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:** `/viz-chart`, `/viz-chart-export`, `/viz-dashboard`, `/viz-theme`, `/viz-theme-new`, `/viz-theme-css`, `/viz-component`, `/accessibility-check`, `/viz-breakpoints`, `/design-review`, `/design-gate`, `/viz-setup`
## Domain Advisory Pattern
The marketplace supports cross-plugin domain advisory integration:
- **Domain Detection**: projman automatically detects when issues involve specialized domains (frontend/viz, data engineering)
- **Acceptance Criteria**: Domain-specific acceptance criteria are added to issues during planning
- **Execution Gates**: Domain validation gates (`/design-gate`, `/data-gate`) run before issue completion
- **Extensible**: New domains can be added by creating advisory agents and gate commands
**Current Domains:**
| Domain | Plugin | Gate Command |
|--------|--------|--------------|
| Visualization | viz-platform | `/design-gate` |
| Data | data-platform | `/data-gate` |
## MCP Servers ## MCP Servers
MCP servers are **shared at repository root** and configured in `.mcp.json`. All MCP servers are shared at repository root and configured in `.mcp.json`.
### Gitea MCP Server (shared) | Server | Used By | External System |
|--------|---------|-----------------|
Full Gitea API integration for project management. | gitea | projman, pr-review | Gitea (issues, PRs, wiki, milestones) |
| netbox | cmdb-assistant | NetBox (DCIM, IPAM) |
| Category | Tools | | data-platform | data-platform | PostgreSQL, dbt |
|----------|-------| | viz-platform | viz-platform | DMC component registry |
| Issues | `list_issues`, `get_issue`, `create_issue`, `update_issue`, `add_comment`, `aggregate_issues` | | contract-validator | contract-validator | Internal validation |
| Labels | `get_labels`, `suggest_labels`, `create_label`, `create_label_smart` |
| Wiki | `list_wiki_pages`, `get_wiki_page`, `create_wiki_page`, `update_wiki_page`, `create_lesson`, `search_lessons` |
| Milestones | `list_milestones`, `get_milestone`, `create_milestone`, `update_milestone`, `delete_milestone` |
| Dependencies | `list_issue_dependencies`, `create_issue_dependency`, `remove_issue_dependency`, `get_execution_order` |
| **Pull Requests** | `list_pull_requests`, `get_pull_request`, `get_pr_diff`, `get_pr_comments`, `create_pr_review`, `add_pr_comment` |
| Validation | `validate_repo_org`, `get_branch_protection` |
### NetBox MCP Server (shared)
Comprehensive NetBox REST API integration for infrastructure management.
| Module | Coverage |
|--------|----------|
| DCIM | Sites, Racks, Devices, Interfaces, Cables |
| IPAM | Prefixes, IPs, VLANs, VRFs |
| Circuits | Providers, Circuits, Terminations |
| Virtualization | Clusters, VMs, Interfaces |
| Extras | Tags, Custom Fields, Audit Log |
### Data Platform MCP Server (shared)
pandas, PostgreSQL/PostGIS, and dbt integration for data engineering.
| Category | Tools |
|----------|-------|
| pandas | `read_csv`, `read_parquet`, `read_json`, `to_csv`, `to_parquet`, `describe`, `head`, `tail`, `filter`, `select`, `groupby`, `join`, `list_data`, `drop_data` |
| PostgreSQL | `pg_connect`, `pg_query`, `pg_execute`, `pg_tables`, `pg_columns`, `pg_schemas` |
| 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` |
### Viz Platform MCP Server (shared)
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)
Cross-plugin compatibility validation tools.
| Category | Tools |
|----------|-------|
| Parse | `parse_plugin_interface`, `parse_claude_md_agents` |
| Validation | `validate_compatibility`, `validate_agent_refs`, `validate_data_flow`, `validate_workflow_integration` |
| Report | `generate_compatibility_report`, `list_issues` |
## Installation ## Installation
@@ -252,16 +164,14 @@ Cross-plugin compatibility validation tools.
- Python 3.10+ - Python 3.10+
- Access to target services (Gitea, NetBox as needed) - Access to target services (Gitea, NetBox as needed)
### Add Marketplace to Claude Code ### Add marketplace to Claude Code
**Option 1 - CLI command (recommended):**
```bash ```bash
/plugin marketplace add https://gitea.hotserv.cloud/personal-projects/leo-claude-mktplace.git /plugin marketplace add https://gitea.hotserv.cloud/personal-projects/leo-claude-mktplace.git
``` ```
**Option 2 - Settings file (for team distribution):** Or add to `.claude/settings.json`:
Add to `.claude/settings.json` in your target project:
```json ```json
{ {
"extraKnownMarketplaces": { "extraKnownMarketplaces": {
@@ -275,124 +185,55 @@ Add to `.claude/settings.json` in your target project:
} }
``` ```
### Run Interactive Setup ### Setup MCP servers
After installing plugins, run the setup wizard: After installing, create Python venvs for MCP servers:
``` ```bash
/pm-setup cd ~/.claude/plugins/marketplaces/leo-claude-mktplace && ./scripts/setup.sh
``` ```
The wizard handles everything: Then restart Claude Code and run the interactive setup:
- Sets up MCP server (Python venv + dependencies)
- Creates system config (`~/.config/claude/gitea.env`)
- Guides you through adding your API token
- Detects and validates your repository via API
- Creates project config (`.env`)
**For new projects** (when system is already configured):
```
/project-init
```
**After moving a repository:**
```
/project-sync
```
See [docs/CONFIGURATION.md](./docs/CONFIGURATION.md) for manual setup and advanced options.
## Verifying Plugin Installation
After installing plugins, the `/plugin` command may show `(no content)` - this is normal Claude Code behavior and doesn't indicate an error.
**To verify a plugin is installed correctly:**
1. **Check installed plugins list:**
```
/plugin list
```
Look for `✔ plugin-name · Installed`
2. **Test a plugin command directly:**
```
/git-flow:git-status
/projman:sprint-status
/clarity-assist:clarify
```
If the command executes and shows output, the plugin is working.
3. **Check for loading errors:**
```
/plugin list
```
Look for any `Plugin Loading Errors` section - this indicates manifest issues.
**Command format:** All plugin commands use the format `/plugin-name:command-name`
| Plugin | Test Command |
|--------|--------------|
| git-flow | `/git-flow:git-status` |
| projman | `/projman:sprint-status` |
| pr-review | `/pr-review:pr-summary` |
| clarity-assist | `/clarity-assist:clarify` |
| doc-guardian | `/doc-guardian:doc-audit` |
| code-sentinel | `/code-sentinel:security-scan` |
| claude-config-maintainer | `/claude-config-maintainer:analyze` |
| cmdb-assistant | `/cmdb-assistant:cmdb-search` |
| data-platform | `/data-platform:data-ingest` |
| viz-platform | `/viz-platform:viz-chart` |
| contract-validator | `/contract-validator:validate-contracts` |
## Repository Structure
``` ```
leo-claude-mktplace/ /projman setup
├── .claude-plugin/ # Marketplace manifest ```
│ └── marketplace.json
├── mcp-servers/ # SHARED MCP servers (v3.0.0+) See [CONFIGURATION.md](./docs/CONFIGURATION.md) for manual setup and advanced options.
│ ├── gitea/ # Gitea MCP (issues, PRs, wiki)
│ ├── netbox/ # NetBox MCP (CMDB) ### Install to consumer projects
│ ├── data-platform/ # Data engineering (pandas, PostgreSQL, dbt)
│ ├── viz-platform/ # Visualization (DMC, Plotly, theming) ```bash
│ └── contract-validator/ # Cross-plugin validation (v5.0.0) ./scripts/install-plugin.sh <plugin-name> /path/to/project
├── plugins/ # All plugins ./scripts/list-installed.sh /path/to/project
│ ├── projman/ # Sprint management ./scripts/uninstall-plugin.sh <plugin-name> /path/to/project
│ ├── git-flow/ # Git workflow automation
│ ├── pr-review/ # PR review
│ ├── clarity-assist/ # Prompt optimization
│ ├── data-platform/ # Data engineering
│ ├── viz-platform/ # Visualization
│ ├── contract-validator/ # Cross-plugin validation (NEW)
│ ├── claude-config-maintainer/ # CLAUDE.md optimization
│ ├── cmdb-assistant/ # NetBox CMDB integration
│ ├── doc-guardian/ # Documentation drift detection
│ ├── code-sentinel/ # Security scanning
│ └── project-hygiene/ # Cleanup automation
├── docs/ # Documentation
│ ├── CANONICAL-PATHS.md # Path reference
│ └── CONFIGURATION.md # Setup guide
├── scripts/ # Setup scripts
└── CHANGELOG.md # Version history
``` ```
## Documentation ## Documentation
| Document | Description | | Document | Description |
|----------|-------------| |----------|-------------|
| [CLAUDE.md](./CLAUDE.md) | Main project instructions | | [CLAUDE.md](./CLAUDE.md) | Project instructions for Claude Code |
| [CONFIGURATION.md](./docs/CONFIGURATION.md) | Centralized setup guide | | [ARCHITECTURE.md](./docs/ARCHITECTURE.md) | System architecture and plugin reference |
| [COMMANDS-CHEATSHEET.md](./docs/COMMANDS-CHEATSHEET.md) | All commands quick reference | | [COMMANDS-CHEATSHEET.md](./docs/COMMANDS-CHEATSHEET.md) | All commands quick reference |
| [UPDATING.md](./docs/UPDATING.md) | Update guide for the marketplace | | [CONFIGURATION.md](./docs/CONFIGURATION.md) | Centralized setup guide |
| [CANONICAL-PATHS.md](./docs/CANONICAL-PATHS.md) | Authoritative path reference |
| [DEBUGGING-CHECKLIST.md](./docs/DEBUGGING-CHECKLIST.md) | Systematic troubleshooting guide | | [DEBUGGING-CHECKLIST.md](./docs/DEBUGGING-CHECKLIST.md) | Systematic troubleshooting guide |
| [UPDATING.md](./docs/UPDATING.md) | Update guide for the marketplace |
| [MIGRATION-v9.md](./docs/MIGRATION-v9.md) | v8.x to v9.0.0 migration guide |
| [CANONICAL-PATHS.md](./docs/CANONICAL-PATHS.md) | Authoritative path reference |
| [CHANGELOG.md](./CHANGELOG.md) | Version history | | [CHANGELOG.md](./CHANGELOG.md) | Version history |
## Validation
```bash
./scripts/validate-marketplace.sh # Marketplace compliance (manifests, domains, paths)
./scripts/verify-hooks.sh # Hook inventory (4 PreToolUse + 1 UserPromptSubmit)
```
## License ## License
MIT License MIT License
## Support ## Support
- **Issues**: Contact repository maintainer - **Repository**: https://gitea.hotserv.cloud/personal-projects/leo-claude-mktplace.git
- **Repository**: `https://gitea.hotserv.cloud/personal-projects/leo-claude-mktplace.git`

184
docs/ARCHITECTURE.md Normal file
View File

@@ -0,0 +1,184 @@
# Architecture — Leo Claude Marketplace v9.1.0
## Overview
Plugin marketplace for Claude Code. 20 plugins across 5 domains, 5 shared MCP servers,
4 PreToolUse safety hooks + 1 UserPromptSubmit quality hook.
## System Architecture
### Plugin Domains
| Domain | Purpose | Plugins |
|--------|---------|---------|
| core | Development workflow | projman, git-flow, pr-review, code-sentinel, doc-guardian, clarity-assist, contract-validator, claude-config-maintainer, project-hygiene |
| data | Data engineering | data-platform, viz-platform, data-seed |
| saas | SaaS development | saas-api-platform, saas-db-migrate, saas-react-platform, saas-test-pilot |
| ops | Operations | cmdb-assistant, ops-release-manager, ops-deploy-pipeline |
| debug | Diagnostics | debug-mcp |
### MCP Servers (Shared at Root)
| Server | Plugins Using It | External System |
|--------|-------------------|-----------------|
| gitea | projman, pr-review | Gitea (issues, PRs, wiki) — uses published `gitea-mcp` package |
| netbox | cmdb-assistant | NetBox (DCIM, IPAM) |
| data-platform | data-platform | PostgreSQL, dbt |
| viz-platform | viz-platform | DMC registry |
| contract-validator | contract-validator | (internal validation) |
### Hook Architecture
| Plugin | Event | Trigger | Script |
|--------|-------|---------|--------|
| code-sentinel | PreToolUse | Write\|Edit\|MultiEdit | security-check.sh |
| git-flow | PreToolUse | Bash (branch naming) | branch-check.sh |
| git-flow | PreToolUse | Bash (git commit) | commit-msg-check.sh |
| cmdb-assistant | PreToolUse | MCP create/update | validate-input.sh |
| clarity-assist | UserPromptSubmit | All prompts | vagueness-check.sh |
No other hook types permitted. All workflow automation is via explicit commands.
### Agent Model (projman)
| Agent | Model | Permission Mode | Role |
|-------|-------|-----------------|------|
| Planner | opus | default | Sprint planning, architecture analysis, issue creation |
| Orchestrator | sonnet | acceptEdits | Sprint execution, parallel batching, lesson capture |
| Executor | sonnet | bypassPermissions | Code implementation, branch management |
| Code Reviewer | opus | default | Pre-close quality review, security, tests |
### Config Hierarchy
| Level | Location | Contains |
|-------|----------|----------|
| System | ~/.config/claude/{service}.env | Credentials |
| Project | .env in project root | Repo-specific config |
### Branch Security
| Pattern | Access |
|---------|--------|
| development, feat/*, fix/* | Full |
| staging, stage/* | Read-only code, can create issues |
| main, master, prod/* | READ-ONLY. Emergency only. |
### Launch Profiles
| Profile | Plugins |
|---------|---------|
| sprint | projman, git-flow, pr-review, code-sentinel, doc-guardian, clarity-assist |
| data | data-platform, viz-platform, data-seed |
| saas | saas-api-platform, saas-react-platform, saas-db-migrate, saas-test-pilot |
| ops | cmdb-assistant, ops-release-manager, ops-deploy-pipeline |
| review | pr-review, code-sentinel |
| debug | debug-mcp |
| full | all plugins |
---
## Plugin Reference
### Core Domain
#### projman (v9.0.1)
Sprint planning and project management with Gitea integration.
- **Commands:** /sprint (plan|start|status|close|review|test), /project (initiation|plan|status|close), /adr (create|list|update|supersede), /rfc (create|list|review|approve|reject), /labels sync, /projman setup
- **Agents:** planner, orchestrator, executor, code-reviewer
- **MCP:** gitea
#### git-flow (v9.0.1)
Git workflow automation with smart commits and branch management.
- **Commands:** /gitflow (commit|branch-start|branch-cleanup|status|config)
- **Commit flags:** --push, --merge, --sync
- **Agents:** git-assistant
- **Hooks:** PreToolUse (branch-check.sh, commit-msg-check.sh)
#### pr-review (v9.0.1)
Multi-agent PR review with confidence scoring.
- **Commands:** /pr (review|summary|findings|diff|setup|init|sync)
- **Agents:** coordinator, security-reviewer, performance-analyst, maintainability-auditor, test-validator
- **MCP:** gitea
#### code-sentinel (v9.0.1)
Security scanning and code refactoring.
- **Commands:** /sentinel (scan|refactor|refactor-dry)
- **Agents:** security-reviewer, refactor-advisor
- **Hooks:** PreToolUse (security-check.sh)
#### doc-guardian (v9.0.1)
Documentation drift detection and synchronization.
- **Commands:** /doc (audit|sync|changelog-gen|coverage|stale-docs)
- **Agents:** doc-analyzer
#### clarity-assist (v9.0.1)
Prompt optimization with ND-friendly accommodations.
- **Commands:** /clarity (clarify|quick-clarify)
- **Agents:** clarity-coach
- **Hooks:** UserPromptSubmit (vagueness-check.sh)
#### contract-validator (v9.0.1)
Cross-plugin compatibility validation.
- **Commands:** /cv (validate|check-agent|list-interfaces|dependency-graph|setup|status)
- **Agents:** full-validation, agent-check
- **MCP:** contract-validator
#### claude-config-maintainer (v9.0.1)
CLAUDE.md and settings optimization.
- **Commands:** /claude-config (analyze|optimize|init|diff|lint|audit-settings|optimize-settings|permissions-map)
- **Agents:** maintainer
#### project-hygiene (v9.0.1)
Manual project file cleanup checks.
- **Commands:** /hygiene check (--fix flag for auto-fix)
### Data Domain
#### data-platform (v9.0.1)
pandas, PostgreSQL, and dbt integration.
- **Commands:** /data (ingest|profile|schema|explain|lineage|lineage-viz|run|dbt-test|quality|review|gate|setup)
- **Agents:** data-advisor, data-analysis, data-ingestion
- **MCP:** data-platform
#### viz-platform (v9.0.1)
DMC validation, Plotly charts, and theming.
- **Commands:** /viz (setup|chart|chart-export|dashboard|theme|theme-new|theme-css|component|accessibility-check|breakpoints|design-review|design-gate)
- **Agents:** design-reviewer, layout-builder, component-check, theme-setup
- **MCP:** viz-platform
#### data-seed (v0.1.0)
Test data generation and database seeding. *Scaffold — not yet implemented.*
### SaaS Domain
#### saas-api-platform (v0.1.0)
REST/GraphQL API scaffolding for FastAPI and Express. *Scaffold.*
#### saas-db-migrate (v0.1.0)
Database migration management for Alembic, Prisma, raw SQL. *Scaffold.*
#### saas-react-platform (v0.1.0)
React frontend toolkit for Next.js and Vite. *Scaffold.*
#### saas-test-pilot (v0.1.0)
Test automation for pytest, Jest, Vitest, Playwright. *Scaffold.*
### Ops Domain
#### cmdb-assistant (v9.0.1)
NetBox CMDB integration for infrastructure management.
- **Commands:** /cmdb (search|device|ip|site|audit|register|sync|topology|change-audit|ip-conflicts|setup)
- **Agents:** cmdb-assistant
- **MCP:** netbox
- **Hooks:** PreToolUse (validate-input.sh)
#### ops-release-manager (v0.1.0)
Release management with SemVer and changelog automation. *Scaffold.*
#### ops-deploy-pipeline (v0.1.0)
Deployment pipeline for Docker Compose and systemd. *Scaffold.*
### Debug Domain
#### debug-mcp (v0.1.0)
MCP server debugging and diagnostics. *Scaffold.*

View File

@@ -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-02-06 (v8.0.0) Last Updated: 2026-02-07 (v9.1.0)
--- ---
@@ -19,17 +19,13 @@ leo-claude-mktplace/
├── .mcp-full.json # Full profile MCP config (all servers) ├── .mcp-full.json # Full profile MCP config (all servers)
├── .scratch/ # Transient work (auto-cleaned) ├── .scratch/ # Transient work (auto-cleaned)
├── docs/ # All documentation ├── docs/ # All documentation
│ ├── architecture/ # Draw.io diagrams and specs │ ├── ARCHITECTURE.md # System architecture and plugin reference
│ ├── prompts/ # Shared prompt templates
│ │ └── INDEX.md # Prompt template index
│ ├── project-lessons-learned/ # Project-level lessons (not sprint-specific)
│ │ └── INDEX.md # Lessons index
│ ├── CANONICAL-PATHS.md # This file - single source of truth │ ├── CANONICAL-PATHS.md # This file - single source of truth
│ ├── COMMANDS-CHEATSHEET.md # All commands quick reference │ ├── COMMANDS-CHEATSHEET.md # All commands quick reference
│ ├── CONFIGURATION.md # Centralized configuration guide │ ├── CONFIGURATION.md # Centralized configuration guide
│ ├── DEBUGGING-CHECKLIST.md # Systematic troubleshooting guide │ ├── DEBUGGING-CHECKLIST.md # Systematic troubleshooting guide
│ ├── MIGRATION-v9.md # v8.x → v9.0.0 migration guide
│ └── UPDATING.md # Update guide │ └── UPDATING.md # Update guide
├── hooks/ # Shared hooks (if any)
├── mcp-servers/ # SHARED MCP servers (v3.0.0+) ├── mcp-servers/ # SHARED MCP servers (v3.0.0+)
│ ├── gitea/ # Gitea MCP server │ ├── gitea/ # Gitea MCP server
│ │ ├── mcp_server/ │ │ ├── mcp_server/
@@ -90,7 +86,6 @@ leo-claude-mktplace/
│ │ └── claude-md-integration.md │ │ └── claude-md-integration.md
│ ├── doc-guardian/ # Documentation drift detection │ ├── doc-guardian/ # Documentation drift detection
│ │ ├── .claude-plugin/ │ │ ├── .claude-plugin/
│ │ ├── hooks/
│ │ ├── commands/ │ │ ├── commands/
│ │ ├── agents/ │ │ ├── agents/
│ │ ├── skills/ │ │ ├── skills/
@@ -114,7 +109,7 @@ leo-claude-mktplace/
│ │ └── claude-md-integration.md │ │ └── claude-md-integration.md
│ ├── project-hygiene/ │ ├── project-hygiene/
│ │ ├── .claude-plugin/ │ │ ├── .claude-plugin/
│ │ ├── hooks/ │ │ ├── commands/
│ │ └── claude-md-integration.md │ │ └── claude-md-integration.md
│ ├── clarity-assist/ │ ├── clarity-assist/
│ │ ├── .claude-plugin/ │ │ ├── .claude-plugin/
@@ -138,29 +133,76 @@ leo-claude-mktplace/
│ │ ├── .claude-plugin/ │ │ ├── .claude-plugin/
│ │ ├── commands/ │ │ ├── commands/
│ │ ├── agents/ │ │ ├── agents/
│ │ ├── hooks/
│ │ └── claude-md-integration.md │ │ └── claude-md-integration.md
│ ├── contract-validator/ │ ├── contract-validator/
│ │ ├── .claude-plugin/ │ │ ├── .claude-plugin/
│ │ ├── commands/ │ │ ├── commands/
│ │ ├── agents/ │ │ ├── agents/
│ │ └── claude-md-integration.md │ │ └── claude-md-integration.md
── viz-platform/ ── viz-platform/
│ │ ├── .claude-plugin/
│ │ ├── commands/
│ │ ├── agents/
│ │ └── claude-md-integration.md
│ ├── saas-api-platform/ # REST/GraphQL API scaffolding (scaffold)
│ │ ├── .claude-plugin/
│ │ ├── commands/
│ │ ├── agents/
│ │ ├── skills/
│ │ └── claude-md-integration.md
│ ├── saas-db-migrate/ # Database migration management (scaffold)
│ │ ├── .claude-plugin/
│ │ ├── commands/
│ │ ├── agents/
│ │ ├── skills/
│ │ └── claude-md-integration.md
│ ├── saas-react-platform/ # React frontend toolkit (scaffold)
│ │ ├── .claude-plugin/
│ │ ├── commands/
│ │ ├── agents/
│ │ ├── skills/
│ │ └── claude-md-integration.md
│ ├── saas-test-pilot/ # Test automation (scaffold)
│ │ ├── .claude-plugin/
│ │ ├── commands/
│ │ ├── agents/
│ │ ├── skills/
│ │ └── claude-md-integration.md
│ ├── data-seed/ # Test data generation (scaffold)
│ │ ├── .claude-plugin/
│ │ ├── commands/
│ │ ├── agents/
│ │ ├── skills/
│ │ └── claude-md-integration.md
│ ├── ops-release-manager/ # Release management (scaffold)
│ │ ├── .claude-plugin/
│ │ ├── commands/
│ │ ├── agents/
│ │ ├── skills/
│ │ └── claude-md-integration.md
│ ├── ops-deploy-pipeline/ # Deployment pipeline (scaffold)
│ │ ├── .claude-plugin/
│ │ ├── commands/
│ │ ├── agents/
│ │ ├── skills/
│ │ └── claude-md-integration.md
│ └── debug-mcp/ # MCP debugging toolkit (scaffold)
│ ├── .claude-plugin/ │ ├── .claude-plugin/
│ ├── commands/ │ ├── commands/
│ ├── agents/ │ ├── agents/
│ ├── hooks/ │ ├── skills/
│ └── 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 (clear cache, show changelog) │ ├── post-update.sh # Post-update (clear cache, show changelog)
│ ├── check-venv.sh # Check if venvs exist (read-only) │ ├── setup-venvs.sh # Setup MCP server venvs (create only, never delete)
│ ├── validate-marketplace.sh # Marketplace compliance validation │ ├── validate-marketplace.sh # Marketplace compliance validation
│ ├── verify-hooks.sh # Verify all hooks use correct event types │ ├── verify-hooks.sh # Verify all hooks use correct event types
│ ├── setup-venvs.sh # Setup MCP server venvs (create only, never delete)
│ ├── release.sh # Release automation with version bumping │ ├── release.sh # Release automation with version bumping
│ ├── claude-launch.sh # Task-specific launcher with profile selection │ ├── claude-launch.sh # Task-specific launcher with profile selection
── switch-profile.sh # DEPRECATED: use claude-launch.sh instead ── install-plugin.sh # Install plugin to consumer project
│ ├── list-installed.sh # Show installed plugins in a project
│ └── uninstall-plugin.sh # Remove plugin from consumer project
├── CLAUDE.md ├── CLAUDE.md
├── README.md ├── README.md
├── LICENSE ├── LICENSE
@@ -219,9 +261,11 @@ MCP servers are **shared at repository root** and configured in `.mcp.json`.
|---------|---------|---------| |---------|---------|---------|
| MCP configuration | `.mcp.json` | `.mcp.json` (at repo root) | | MCP configuration | `.mcp.json` | `.mcp.json` (at repo root) |
| Shared MCP server | `mcp-servers/{server}/` | `mcp-servers/gitea/` | | Shared MCP server | `mcp-servers/{server}/` | `mcp-servers/gitea/` |
| MCP server code | `mcp-servers/{server}/mcp_server/` | `mcp-servers/gitea/mcp_server/` | | MCP server code | `mcp-servers/{server}/mcp_server/` | `mcp-servers/netbox/mcp_server/` |
| MCP venv (local) | `mcp-servers/{server}/.venv/` | `mcp-servers/gitea/.venv/` | | MCP venv (local) | `mcp-servers/{server}/.venv/` | `mcp-servers/gitea/.venv/` |
**Note:** `mcp-servers/gitea/` is a thin wrapper — source code is in the published `gitea-mcp` package (Gitea PyPI). Other MCP servers still have local source code.
**Note:** Plugins do NOT have their own `mcp-servers/` directories. All MCP servers are shared at root and configured via `.mcp.json`. **Note:** Plugins do NOT have their own `mcp-servers/` directories. All MCP servers are shared at root and configured via `.mcp.json`.
### MCP Venv Paths - CRITICAL ### MCP Venv Paths - CRITICAL
@@ -260,12 +304,13 @@ fi
| Type | Location | | Type | Location |
|------|----------| |------|----------|
| Architecture diagrams | `docs/architecture/` | | Architecture & plugin reference | `docs/ARCHITECTURE.md` |
| This file | `docs/CANONICAL-PATHS.md` | | This file | `docs/CANONICAL-PATHS.md` |
| Update guide | `docs/UPDATING.md` | | Update guide | `docs/UPDATING.md` |
| Configuration guide | `docs/CONFIGURATION.md` | | Configuration guide | `docs/CONFIGURATION.md` |
| Commands cheat sheet | `docs/COMMANDS-CHEATSHEET.md` | | Commands cheat sheet | `docs/COMMANDS-CHEATSHEET.md` |
| Debugging checklist | `docs/DEBUGGING-CHECKLIST.md` | | Debugging checklist | `docs/DEBUGGING-CHECKLIST.md` |
| Migration guide (v8→v9) | `docs/MIGRATION-v9.md` |
--- ---
@@ -341,24 +386,23 @@ All MCP servers are defined in `.mcp.json` at repository root:
## Domain Metadata ## Domain Metadata
### Domain Field Locations ### Domain Field Location
Both manifest files require a `domain` field (v8.0.0+): Domain metadata is stored in `metadata.json` (v9.1.2+, moved from plugin.json/marketplace.json for Claude Code schema compliance):
| Location | Field | Example | | Location | Field | Example |
|----------|-------|---------| |----------|-------|---------|
| `plugins/{name}/.claude-plugin/plugin.json` | `"domain": "core"` | `plugins/projman/.claude-plugin/plugin.json` | | `plugins/{name}/.claude-plugin/metadata.json` | `"domain": "core"` | `plugins/projman/.claude-plugin/metadata.json` |
| `.claude-plugin/marketplace.json` | `"domain": "core"` per plugin entry | `.claude-plugin/marketplace.json` |
### Allowed Domain Values ### Allowed Domain Values
| Domain | Purpose | Existing Plugins | | Domain | Purpose | Existing Plugins |
|--------|---------|-----------------| |--------|---------|-----------------|
| `core` | Development workflow plugins | projman, git-flow, pr-review, code-sentinel, doc-guardian, clarity-assist, contract-validator, claude-config-maintainer, project-hygiene | | `core` | Development workflow plugins | projman, git-flow, pr-review, code-sentinel, doc-guardian, clarity-assist, contract-validator, claude-config-maintainer, project-hygiene |
| `data` | Data engineering and visualization | data-platform, viz-platform | | `data` | Data engineering and visualization | data-platform, viz-platform, data-seed |
| `ops` | Operations and infrastructure | cmdb-assistant | | `ops` | Operations and infrastructure | cmdb-assistant, ops-release-manager, ops-deploy-pipeline |
| `saas` | SaaS application development | (Phase 2) | | `saas` | SaaS application development | saas-api-platform, saas-db-migrate, saas-react-platform, saas-test-pilot |
| `debug` | Debugging and diagnostics | (Phase 2) | | `debug` | Debugging and diagnostics | debug-mcp |
### Plugin Naming Convention ### Plugin Naming Convention
@@ -370,23 +414,15 @@ Both manifest files require a `domain` field (v8.0.0+):
```bash ```bash
# List all plugins in a domain # List all plugins in a domain
jq '.plugins[] | select(.domain=="saas") | .name' .claude-plugin/marketplace.json for p in plugins/*; do
d=$(jq -r '.domain // empty' "$p/.claude-plugin/metadata.json" 2>/dev/null)
[[ "$d" == "saas" ]] && basename "$p"
done
# Count plugins per domain # Count plugins per domain
jq '[.plugins[] | .domain] | group_by(.) | map({domain: .[0], count: length})' .claude-plugin/marketplace.json for p in plugins/*; do
``` jq -r '.domain // empty' "$p/.claude-plugin/metadata.json" 2>/dev/null
done | sort | uniq -c | sort -rn
### Future Plugin Path Patterns
```
plugins/saas-api-platform/
plugins/saas-db-migrate/
plugins/saas-react-platform/
plugins/saas-test-pilot/
plugins/data-seed/
plugins/ops-release-manager/
plugins/ops-deploy-pipeline/
plugins/debug-mcp/
``` ```
--- ---
@@ -395,6 +431,8 @@ plugins/debug-mcp/
| Date | Change | By | | Date | Change | By |
|------|--------|-----| |------|--------|-----|
| 2026-02-07 | v9.1.2: Moved domain field from plugin.json/marketplace.json to metadata.json for Claude Code schema compliance | Claude Code |
| 2026-02-07 | v9.1.0: Removed deleted dirs (architecture/, prompts/, project-lessons-learned/), added Phase 3 plugins, added ARCHITECTURE.md, MIGRATION-v9.md, updated Domain table, removed stale hooks/ dirs | Claude Code |
| 2026-02-06 | v8.0.0: Added domain metadata section, Phase 1a paths, future plugin paths | Claude Code | | 2026-02-06 | v8.0.0: Added domain metadata section, Phase 1a paths, future plugin paths | Claude Code |
| 2026-02-04 | v7.1.0: Added profile configs, prompts/, project-lessons-learned/, metadata.json, deprecated switch-profile.sh | Claude Code | | 2026-02-04 | v7.1.0: Added profile configs, prompts/, project-lessons-learned/, metadata.json, deprecated switch-profile.sh | Claude Code |
| 2026-01-30 | v5.5.0: Removed plugin-level mcp-servers symlinks - all MCP config now in root .mcp.json | Claude Code | | 2026-01-30 | v5.5.0: Removed plugin-level mcp-servers symlinks - all MCP config now in root .mcp.json | Claude Code |

View File

@@ -1,6 +1,24 @@
# Plugin Commands Cheat Sheet # Plugin Commands Cheat Sheet
Quick reference for all commands in the Leo Claude Marketplace. Quick reference for all commands in the Leo Claude Marketplace (v9.0.0+).
All commands follow the `/<noun> <action>` sub-command pattern.
## Invocation
Commands can be invoked in two ways:
1. **Via dispatch file:** `/doc audit` (routes through dispatch file to invoke `/doc-guardian:doc-audit`)
2. **Direct plugin-prefixed:** `/doc-guardian:doc-audit` (invokes command directly)
Both methods work identically. The dispatch file provides a user-friendly interface with `$ARGUMENTS` parsing, while the direct format bypasses the dispatcher.
If dispatch routing fails, use the direct plugin-prefixed format: `/<plugin-name>:<command-name>`.
**Examples:**
- `/sprint plan` → routes to `/projman:sprint-plan`
- `/doc audit` → routes to `/doc-guardian:doc-audit`
- `/pr review` → routes to `/pr-review:pr-review`
--- ---
@@ -8,98 +26,106 @@ Quick reference for all commands in the Leo Claude Marketplace.
| Plugin | Command | Auto | Manual | Description | | Plugin | Command | Auto | Manual | Description |
|--------|---------|:----:|:------:|-------------| |--------|---------|:----:|:------:|-------------|
| **projman** | `/sprint-plan` | | X | Start sprint planning with AI-guided architecture analysis and issue creation | | **projman** | `/sprint plan` | | X | Start sprint planning with AI-guided architecture analysis and issue creation |
| **projman** | `/sprint-start` | | X | Begin sprint execution with dependency analysis and parallel task coordination (requires approval or `--force`) | | **projman** | `/sprint start` | | X | Begin sprint execution with dependency analysis and parallel task coordination (requires approval or `--force`) |
| **projman** | `/sprint-status` | | X | Check current sprint progress (add `--diagram` for Mermaid visualization) | | **projman** | `/sprint status` | | X | Check current sprint progress (add `--diagram` for Mermaid visualization) |
| **projman** | `/pm-review` | | X | Pre-sprint-close code quality review (debug artifacts, security, error handling) | | **projman** | `/sprint review` | | X | Pre-sprint-close code quality review (debug artifacts, security, error handling) |
| **projman** | `/pm-test` | | X | Run tests (`/pm-test run`) or generate tests (`/pm-test gen <target>`) | | **projman** | `/sprint test` | | X | Run tests (`/sprint test run`) or generate tests (`/sprint test gen <target>`) |
| **projman** | `/sprint-close` | | X | Complete sprint and capture lessons learned to Gitea Wiki | | **projman** | `/sprint close` | | X | Complete sprint and capture lessons learned to Gitea Wiki |
| **projman** | `/labels-sync` | | X | Synchronize label taxonomy from Gitea | | **projman** | `/labels sync` | | X | Synchronize label taxonomy from Gitea |
| **projman** | `/pm-setup` | | X | Auto-detect mode or use `--full`, `--quick`, `--sync`, `--clear-cache` | | **projman** | `/projman setup` | | X | Auto-detect mode or use `--full`, `--quick`, `--sync`, `--clear-cache` |
| **projman** | *SessionStart hook* | X | | Detects git remote vs .env mismatch, warns to run `/pm-setup --sync` | | **projman** | `/rfc create` | | X | Create new RFC from conversation or spec |
| **projman** | `/pm-debug` | | X | Diagnostics (`/pm-debug report`) or investigate (`/pm-debug review`) | | **projman** | `/rfc list` | | X | List all RFCs grouped by status |
| **projman** | `/suggest-version` | | X | Analyze CHANGELOG and recommend semantic version bump | | **projman** | `/rfc review` | | X | Submit RFC for maintainer review |
| **projman** | `/proposal-status` | | X | View proposal and implementation hierarchy with status | | **projman** | `/rfc approve` | | X | Approve RFC for sprint planning |
| **projman** | `/rfc` | | X | RFC lifecycle management (`/rfc create\|list\|review\|approve\|reject`) | | **projman** | `/rfc reject` | | X | Reject RFC with documented reason |
| **git-flow** | `/git-commit` | | X | Create commit with auto-generated conventional message | | **projman** | `/project initiation` | | X | Discovery, source analysis, project charter |
| **git-flow** | `/git-commit-push` | | X | Commit and push to remote in one operation | | **projman** | `/project plan` | | X | WBS, risk register, sprint roadmap |
| **git-flow** | `/git-commit-merge` | | X | Commit current changes, then merge into target branch | | **projman** | `/project status` | | X | Project health check across all sprints |
| **git-flow** | `/git-commit-sync` | | X | Full sync: commit, push, and sync with upstream/base branch | | **projman** | `/project close` | | X | Final retrospective and archival |
| **git-flow** | `/branch-start` | | X | Create new feature/fix/chore branch with naming conventions | | **projman** | `/adr create` | | X | Create new Architecture Decision Record |
| **git-flow** | `/branch-cleanup` | | X | Remove merged branches locally and optionally on remote | | **projman** | `/adr list` | | X | List all ADRs with status |
| **git-flow** | `/git-status` | | X | Enhanced git status with recommendations | | **projman** | `/adr update` | | X | Update existing ADR |
| **git-flow** | `/git-config` | | X | Configure git-flow settings for the project | | **projman** | `/adr supersede` | | X | Supersede ADR with new decision |
| **pr-review** | `/pr-setup` | | X | Setup wizard for pr-review (shares Gitea MCP with projman) | | **git-flow** | `/gitflow commit` | | X | Create commit with auto-generated conventional message. Flags: `--push`, `--merge`, `--sync` |
| **pr-review** | `/project-init` | | X | Quick project setup for PR reviews | | **git-flow** | `/gitflow branch-start` | | X | Create new feature/fix/chore branch with naming conventions |
| **pr-review** | `/project-sync` | | X | Sync config with git remote after repo move/rename | | **git-flow** | `/gitflow branch-cleanup` | | X | Remove merged branches locally and optionally on remote |
| **pr-review** | *SessionStart hook* | X | | Detects git remote vs .env mismatch | | **git-flow** | `/gitflow status` | | X | Enhanced git status with recommendations |
| **pr-review** | `/pr-review` | | X | Full multi-agent PR review with confidence scoring | | **git-flow** | `/gitflow config` | | X | Configure git-flow settings for the project |
| **pr-review** | `/pr-summary` | | X | Quick summary of PR changes | | **pr-review** | `/pr setup` | | X | Setup wizard for pr-review (shares Gitea MCP with projman) |
| **pr-review** | `/pr-findings` | | X | List and filter review findings by category/severity | | **pr-review** | `/pr init` | | X | Quick project setup for PR reviews |
| **pr-review** | `/pr-diff` | | X | Formatted diff with inline review comments and annotations | | **pr-review** | `/pr sync` | | X | Sync config with git remote after repo move/rename |
| **clarity-assist** | `/clarify` | | X | Full 4-D prompt optimization with ND accommodations | | **pr-review** | `/pr review` | | X | Full multi-agent PR review with confidence scoring |
| **clarity-assist** | `/quick-clarify` | | X | Rapid single-pass clarification for simple requests | | **pr-review** | `/pr summary` | | X | Quick summary of PR changes |
| **doc-guardian** | `/doc-audit` | | X | Full documentation audit - scans for doc drift | | **pr-review** | `/pr findings` | | X | List and filter review findings by category/severity |
| **doc-guardian** | `/doc-sync` | | X | Synchronize pending documentation updates | | **pr-review** | `/pr diff` | | X | Formatted diff with inline review comments and annotations |
| **doc-guardian** | `/changelog-gen` | | X | Generate changelog from conventional commits | | **clarity-assist** | `/clarity clarify` | | X | Full 4-D prompt optimization with ND accommodations |
| **doc-guardian** | `/doc-coverage` | | X | Documentation coverage metrics by function/class | | **clarity-assist** | `/clarity quick-clarify` | | X | Rapid single-pass clarification for simple requests |
| **doc-guardian** | `/stale-docs` | | X | Flag documentation behind code changes | | **doc-guardian** | `/doc audit` | | X | Full documentation audit - scans for doc drift |
| **doc-guardian** | *PostToolUse hook* | X | | Silently detects doc drift on Write/Edit | | **doc-guardian** | `/doc sync` | | X | Synchronize pending documentation updates |
| **code-sentinel** | `/security-scan` | | X | Full security audit (SQL injection, XSS, secrets, etc.) | | **doc-guardian** | `/doc changelog-gen` | | X | Generate changelog from conventional commits |
| **code-sentinel** | `/refactor` | | X | Apply refactoring patterns to improve code | | **doc-guardian** | `/doc coverage` | | X | Documentation coverage metrics by function/class |
| **code-sentinel** | `/refactor-dry` | | X | Preview refactoring without applying changes | | **doc-guardian** | `/doc stale-docs` | | X | Flag documentation behind code changes |
| **code-sentinel** | `/sentinel scan` | | X | Full security audit (SQL injection, XSS, secrets, etc.) |
| **code-sentinel** | `/sentinel refactor` | | X | Apply refactoring patterns to improve code |
| **code-sentinel** | `/sentinel refactor-dry` | | X | Preview refactoring without applying changes |
| **code-sentinel** | *PreToolUse hook* | X | | Scans code before writing; blocks critical issues | | **code-sentinel** | *PreToolUse hook* | X | | Scans code before writing; blocks critical issues |
| **claude-config-maintainer** | `/config-analyze` | | X | Analyze CLAUDE.md for optimization opportunities | | **claude-config-maintainer** | `/claude-config analyze` | | X | Analyze CLAUDE.md for optimization opportunities |
| **claude-config-maintainer** | `/config-optimize` | | X | Optimize CLAUDE.md structure with preview/backup | | **claude-config-maintainer** | `/claude-config optimize` | | X | Optimize CLAUDE.md structure with preview/backup |
| **claude-config-maintainer** | `/config-init` | | X | Initialize new CLAUDE.md for a project | | **claude-config-maintainer** | `/claude-config init` | | X | Initialize new CLAUDE.md for a project |
| **claude-config-maintainer** | `/config-diff` | | X | Track CLAUDE.md changes over time with behavioral impact | | **claude-config-maintainer** | `/claude-config diff` | | X | Track CLAUDE.md changes over time with behavioral impact |
| **claude-config-maintainer** | `/config-lint` | | X | Lint CLAUDE.md for anti-patterns and best practices | | **claude-config-maintainer** | `/claude-config lint` | | X | Lint CLAUDE.md for anti-patterns and best practices |
| **claude-config-maintainer** | `/config-audit-settings` | | X | Audit settings.local.json permissions (100-point score) | | **claude-config-maintainer** | `/claude-config audit-settings` | | X | Audit settings.local.json permissions (100-point score) |
| **claude-config-maintainer** | `/config-optimize-settings` | | X | Optimize permissions (profiles, consolidation, dry-run) | | **claude-config-maintainer** | `/claude-config optimize-settings` | | X | Optimize permissions (profiles, consolidation, dry-run) |
| **claude-config-maintainer** | `/config-permissions-map` | | X | Visual review layer + permission coverage map | | **claude-config-maintainer** | `/claude-config permissions-map` | | X | Visual review layer + permission coverage map |
| **cmdb-assistant** | `/cmdb-setup` | | X | Setup wizard for NetBox MCP server | | **cmdb-assistant** | `/cmdb setup` | | X | Setup wizard for NetBox MCP server |
| **cmdb-assistant** | `/cmdb-search` | | X | Search NetBox for devices, IPs, sites | | **cmdb-assistant** | `/cmdb search` | | X | Search NetBox for devices, IPs, sites |
| **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 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 register` | | X | Register current machine into NetBox with running apps |
| **cmdb-assistant** | `/cmdb-sync` | | X | Sync machine state with NetBox (detect drift, update) | | **cmdb-assistant** | `/cmdb sync` | | X | Sync machine state with NetBox (detect drift, update) |
| **cmdb-assistant** | `/cmdb-topology` | | X | Infrastructure topology diagrams (rack, network, site views) | | **cmdb-assistant** | `/cmdb topology` | | X | Infrastructure topology diagrams (rack, network, site views) |
| **cmdb-assistant** | `/change-audit` | | X | NetBox audit trail queries with filtering | | **cmdb-assistant** | `/cmdb change-audit` | | X | NetBox audit trail queries with filtering |
| **cmdb-assistant** | `/ip-conflicts` | | X | Detect IP conflicts and overlapping prefixes | | **cmdb-assistant** | `/cmdb ip-conflicts` | | X | Detect IP conflicts and overlapping prefixes |
| **project-hygiene** | *PostToolUse hook* | X | | Removes temp files, warns about unexpected root files | | **project-hygiene** | `/hygiene check` | | X | Project file organization and cleanup check |
| **data-platform** | `/data-ingest` | | X | Load data from CSV, Parquet, JSON into DataFrame | | **data-platform** | `/data ingest` | | X | Load data from CSV, Parquet, JSON into DataFrame |
| **data-platform** | `/data-profile` | | X | Generate data profiling report with statistics | | **data-platform** | `/data profile` | | X | Generate data profiling report with statistics |
| **data-platform** | `/data-schema` | | X | Explore database schemas, tables, columns | | **data-platform** | `/data schema` | | X | Explore database schemas, tables, columns |
| **data-platform** | `/data-explain` | | X | Explain query execution plan | | **data-platform** | `/data explain` | | X | Explain query execution plan |
| **data-platform** | `/data-lineage` | | X | Show dbt model lineage and dependencies | | **data-platform** | `/data lineage` | | X | Show dbt model lineage and dependencies |
| **data-platform** | `/data-run` | | X | Run dbt models with validation | | **data-platform** | `/data run` | | X | Run dbt models with validation |
| **data-platform** | `/lineage-viz` | | X | dbt lineage visualization as Mermaid diagrams | | **data-platform** | `/data lineage-viz` | | X | dbt lineage visualization as Mermaid diagrams |
| **data-platform** | `/dbt-test` | | X | Formatted dbt test runner with summary and failure details | | **data-platform** | `/data dbt-test` | | X | Formatted dbt test runner with summary and failure details |
| **data-platform** | `/data-quality` | | X | DataFrame quality checks (nulls, duplicates, types, outliers) | | **data-platform** | `/data quality` | | X | DataFrame quality checks (nulls, duplicates, types, outliers) |
| **data-platform** | `/data-setup` | | X | Setup wizard for data-platform MCP servers | | **data-platform** | `/data review` | | X | Comprehensive data integrity audits |
| **data-platform** | *SessionStart hook* | X | | Checks PostgreSQL connection (non-blocking warning) | | **data-platform** | `/data gate` | | X | Binary pass/fail data integrity gates |
| **viz-platform** | `/viz-setup` | | X | Setup wizard for viz-platform MCP server | | **data-platform** | `/data setup` | | X | Setup wizard for data-platform MCP servers |
| **viz-platform** | `/viz-chart` | | X | Create Plotly charts with theme integration | | **viz-platform** | `/viz setup` | | X | Setup wizard for viz-platform MCP server |
| **viz-platform** | `/viz-dashboard` | | X | Create dashboard layouts with filters and grids | | **viz-platform** | `/viz chart` | | X | Create Plotly charts with theme integration |
| **viz-platform** | `/viz-theme` | | X | Apply existing theme to visualizations | | **viz-platform** | `/viz chart-export` | | X | Export charts to PNG, SVG, PDF via kaleido |
| **viz-platform** | `/viz-theme-new` | | X | Create new custom theme with design tokens | | **viz-platform** | `/viz dashboard` | | X | Create dashboard layouts with filters and grids |
| **viz-platform** | `/viz-theme-css` | | X | Export theme as CSS custom properties | | **viz-platform** | `/viz theme` | | X | Apply existing theme to visualizations |
| **viz-platform** | `/viz-component` | | X | Inspect DMC component props and validation | | **viz-platform** | `/viz theme-new` | | X | Create new custom theme with design tokens |
| **viz-platform** | `/viz-chart-export` | | X | Export charts to PNG, SVG, PDF via kaleido | | **viz-platform** | `/viz theme-css` | | X | Export theme as CSS custom properties |
| **viz-platform** | `/accessibility-check` | | X | Color blind validation (WCAG contrast ratios) | | **viz-platform** | `/viz component` | | X | Inspect DMC component props and validation |
| **viz-platform** | `/viz-breakpoints` | | X | Configure responsive layout breakpoints | | **viz-platform** | `/viz accessibility-check` | | X | Color blind validation (WCAG contrast ratios) |
| **viz-platform** | `/design-review` | | X | Detailed design system audits | | **viz-platform** | `/viz breakpoints` | | X | Configure responsive layout breakpoints |
| **viz-platform** | `/design-gate` | | X | Binary pass/fail design system validation gates | | **viz-platform** | `/viz design-review` | | X | Detailed design system audits |
| **viz-platform** | *SessionStart hook* | X | | Checks DMC version (non-blocking warning) | | **viz-platform** | `/viz design-gate` | | X | Binary pass/fail design system validation gates |
| **data-platform** | `/data-review` | | X | Comprehensive data integrity audits | | **contract-validator** | `/cv validate` | | X | Full marketplace compatibility validation |
| **data-platform** | `/data-gate` | | X | Binary pass/fail data integrity gates | | **contract-validator** | `/cv check-agent` | | X | Validate single agent definition |
| **contract-validator** | `/validate-contracts` | | X | Full marketplace compatibility validation | | **contract-validator** | `/cv list-interfaces` | | X | Show all plugin interfaces |
| **contract-validator** | `/check-agent` | | X | Validate single agent definition | | **contract-validator** | `/cv dependency-graph` | | X | Mermaid visualization of plugin dependencies |
| **contract-validator** | `/list-interfaces` | | X | Show all plugin interfaces | | **contract-validator** | `/cv setup` | | X | Setup wizard for contract-validator MCP |
| **contract-validator** | `/dependency-graph` | | X | Mermaid visualization of plugin dependencies | | **contract-validator** | `/cv status` | | X | Marketplace-wide health check (installation, MCP, configuration) |
| **contract-validator** | `/cv-setup` | | X | Setup wizard for contract-validator MCP |
---
## Migration from v8.x
All commands were renamed in v9.0.0 to follow `/<noun> <action>` pattern. See [MIGRATION-v9.md](./MIGRATION-v9.md) for the complete old-to-new mapping.
--- ---
@@ -107,7 +133,7 @@ Quick reference for all commands in the Leo Claude Marketplace.
| Category | Plugins | Primary Use | | Category | Plugins | Primary Use |
|----------|---------|-------------| |----------|---------|-------------|
| **Setup** | projman, pr-review, cmdb-assistant, data-platform, viz-platform, contract-validator | `/pm-setup`, `/pr-setup`, `/cmdb-setup`, `/data-setup`, `/viz-setup`, `/cv-setup` | | **Setup** | projman, pr-review, cmdb-assistant, data-platform, viz-platform, contract-validator | `/projman setup`, `/pr setup`, `/cmdb setup`, `/data setup`, `/viz setup`, `/cv setup` |
| **Task Planning** | projman, clarity-assist | Sprint management, requirement clarification | | **Task Planning** | projman, clarity-assist | Sprint management, requirement clarification |
| **Code Quality** | code-sentinel, pr-review | Security scanning, PR reviews | | **Code Quality** | code-sentinel, pr-review | Security scanning, PR reviews |
| **Documentation** | doc-guardian, claude-config-maintainer | Doc sync, CLAUDE.md maintenance | | **Documentation** | doc-guardian, claude-config-maintainer | Doc sync, CLAUDE.md maintenance |
@@ -116,7 +142,7 @@ Quick reference for all commands in the Leo Claude Marketplace.
| **Data Engineering** | data-platform | pandas, PostgreSQL, dbt operations | | **Data Engineering** | data-platform | pandas, PostgreSQL, dbt operations |
| **Visualization** | viz-platform | DMC validation, Plotly charts, theming | | **Visualization** | viz-platform | DMC validation, Plotly charts, theming |
| **Validation** | contract-validator | Cross-plugin compatibility checks | | **Validation** | contract-validator | Cross-plugin compatibility checks |
| **Maintenance** | project-hygiene | Automatic cleanup | | **Maintenance** | project-hygiene | Manual cleanup via `/hygiene check` |
--- ---
@@ -124,13 +150,10 @@ Quick reference for all commands in the Leo Claude Marketplace.
| Plugin | Hook Event | Behavior | | Plugin | Hook Event | Behavior |
|--------|------------|----------| |--------|------------|----------|
| **projman** | SessionStart | Checks git remote vs .env; warns if mismatch detected; suggests sprint planning if issues exist | | **code-sentinel** | PreToolUse (Write/Edit/MultiEdit) | Scans code before writing; blocks critical security issues |
| **pr-review** | SessionStart | Checks git remote vs .env; warns if mismatch detected | | **git-flow** | PreToolUse (Bash) | Validates branch naming and commit message conventions |
| **doc-guardian** | PostToolUse (Write/Edit) | Tracks documentation drift; auto-updates dependent docs | | **cmdb-assistant** | PreToolUse (MCP create/update) | Validates input data before NetBox writes |
| **code-sentinel** | PreToolUse (Write/Edit) | Scans for security issues; blocks critical vulnerabilities | | **clarity-assist** | UserPromptSubmit | Detects vague prompts and suggests clarification |
| **project-hygiene** | PostToolUse (Write/Edit) | Cleans temp files, warns about misplaced files |
| **data-platform** | SessionStart | Checks PostgreSQL connection; non-blocking warning if unavailable |
| **viz-platform** | SessionStart | Checks DMC version; non-blocking warning if mismatch detected |
--- ---
@@ -141,15 +164,15 @@ Quick reference for all commands in the Leo Claude Marketplace.
Full workflow from idea to implementation using RFCs: Full workflow from idea to implementation using RFCs:
``` ```
1. /clarify # Clarify the feature idea 1. /clarity clarify # Clarify the feature idea
2. /rfc create # Create RFC from clarified spec 2. /rfc create # Create RFC from clarified spec
... refine RFC content ... ... refine RFC content ...
3. /rfc review 0001 # Submit RFC for review 3. /rfc review 0001 # Submit RFC for review
... review discussion ... ... review discussion ...
4. /rfc approve 0001 # Approve RFC for implementation 4. /rfc approve 0001 # Approve RFC for implementation
5. /sprint-plan # Select approved RFC for sprint 5. /sprint plan # Select approved RFC for sprint
... implement feature ... ... implement feature ...
6. /sprint-close # Complete sprint, RFC marked Implemented 6. /sprint close # Complete sprint, RFC marked Implemented
``` ```
### Example 1: Starting a New Feature Sprint ### Example 1: Starting a New Feature Sprint
@@ -157,17 +180,17 @@ Full workflow from idea to implementation using RFCs:
A typical workflow for planning and executing a feature sprint: A typical workflow for planning and executing a feature sprint:
``` ```
1. /clarify # Clarify requirements if vague 1. /clarity clarify # Clarify requirements if vague
2. /sprint-plan # Plan the sprint with architecture analysis 2. /sprint plan # Plan the sprint with architecture analysis
3. /labels-sync # Ensure labels are up-to-date 3. /labels sync # Ensure labels are up-to-date
4. /sprint-start # Begin execution with dependency ordering 4. /sprint start # Begin execution with dependency ordering
5. /branch-start feat/... # Create feature branch 5. /gitflow branch-start feat/... # Create feature branch
... implement features ... ... implement features ...
6. /git-commit # Commit with conventional message 6. /gitflow commit # Commit with conventional message
7. /sprint-status --diagram # Check progress with visualization 7. /sprint status --diagram # Check progress with visualization
8. /pm-review # Pre-close quality review 8. /sprint review # Pre-close quality review
9. /pm-test run # Verify test coverage 9. /sprint test run # Verify test coverage
10. /sprint-close # Capture lessons learned 10. /sprint close # Capture lessons learned
``` ```
### Example 2: Daily Development Cycle ### Example 2: Daily Development Cycle
@@ -175,12 +198,12 @@ A typical workflow for planning and executing a feature sprint:
Quick daily workflow with git-flow: Quick daily workflow with git-flow:
``` ```
1. /git-status # Check current state 1. /gitflow status # Check current state
2. /branch-start fix/... # Start bugfix branch 2. /gitflow branch-start fix/... # Start bugfix branch
... make changes ... ... make changes ...
3. /git-commit # Auto-generate commit message 3. /gitflow commit # Auto-generate commit message
4. /git-commit-push # Push to remote 4. /gitflow commit --push # Commit and push to remote
5. /branch-cleanup # Clean merged branches 5. /gitflow branch-cleanup # Clean merged branches
``` ```
### Example 3: Pull Request Review Workflow ### Example 3: Pull Request Review Workflow
@@ -188,10 +211,10 @@ Quick daily workflow with git-flow:
Reviewing a PR before merge: Reviewing a PR before merge:
``` ```
1. /pr-summary # Quick overview of changes 1. /pr summary # Quick overview of changes
2. /pr-review # Full multi-agent review 2. /pr review # Full multi-agent review
3. /pr-findings # Filter findings by severity 3. /pr findings # Filter findings by severity
4. /security-scan # Deep security audit if needed 4. /sentinel scan # Deep security audit if needed
``` ```
### Example 4: Documentation Maintenance ### Example 4: Documentation Maintenance
@@ -199,10 +222,10 @@ Reviewing a PR before merge:
Keeping docs in sync: Keeping docs in sync:
``` ```
1. /doc-audit # Scan for documentation drift 1. /doc audit # Scan for documentation drift
2. /doc-sync # Apply pending updates 2. /doc sync # Apply pending updates
3. /config-analyze # Check CLAUDE.md health 3. /claude-config analyze # Check CLAUDE.md health
4. /config-optimize # Optimize if needed 4. /claude-config optimize # Optimize if needed
``` ```
### Example 5: Code Refactoring Session ### Example 5: Code Refactoring Session
@@ -210,11 +233,11 @@ Keeping docs in sync:
Safe refactoring with preview: Safe refactoring with preview:
``` ```
1. /refactor-dry # Preview opportunities 1. /sentinel refactor-dry # Preview opportunities
2. /security-scan # Baseline security check 2. /sentinel scan # Baseline security check
3. /refactor # Apply improvements 3. /sentinel refactor # Apply improvements
4. /pm-test run # Verify nothing broke 4. /sprint test run # Verify nothing broke
5. /git-commit # Commit with descriptive message 5. /gitflow commit # Commit with descriptive message
``` ```
### Example 6: Infrastructure Documentation ### Example 6: Infrastructure Documentation
@@ -222,10 +245,10 @@ Safe refactoring with preview:
Managing infrastructure with CMDB: Managing infrastructure with CMDB:
``` ```
1. /cmdb-search "server" # Find existing devices 1. /cmdb search "server" # Find existing devices
2. /cmdb-device view X # Check device details 2. /cmdb device view X # Check device details
3. /cmdb-ip list # List available IPs 3. /cmdb ip list # List available IPs
4. /cmdb-site view Y # Check site info 4. /cmdb site view Y # Check site info
``` ```
### Example 6b: Data Engineering Workflow ### Example 6b: Data Engineering Workflow
@@ -233,12 +256,12 @@ Managing infrastructure with CMDB:
Working with data pipelines: Working with data pipelines:
``` ```
1. /data-ingest file.csv # Load data into DataFrame 1. /data ingest file.csv # Load data into DataFrame
2. /data-profile # Generate data profiling report 2. /data profile # Generate data profiling report
3. /data-schema # Explore database schemas 3. /data schema # Explore database schemas
4. /data-lineage model_name # View dbt model dependencies 4. /data lineage model_name # View dbt model dependencies
5. /data-run model_name # Execute dbt models 5. /data run model_name # Execute dbt models
6. /data-explain "SELECT ..." # Analyze query execution plan 6. /data explain "SELECT ..." # Analyze query execution plan
``` ```
### Example 7: First-Time Setup (New Machine) ### Example 7: First-Time Setup (New Machine)
@@ -246,13 +269,13 @@ Working with data pipelines:
Setting up the marketplace for the first time: Setting up the marketplace for the first time:
``` ```
1. /pm-setup --full # Full setup: MCP + system config + project 1. /projman setup --full # Full setup: MCP + system config + project
# → Follow prompts for Gitea URL, org # → Follow prompts for Gitea URL, org
# → Add token manually when prompted # → Add token manually when prompted
# → Confirm repository name # → Confirm repository name
2. # Restart Claude Code session 2. # Restart Claude Code session
3. /labels-sync # Sync Gitea labels 3. /labels sync # Sync Gitea labels
4. /sprint-plan # Plan first sprint 4. /sprint plan # Plan first sprint
``` ```
### Example 8: New Project Setup (System Already Configured) ### Example 8: New Project Setup (System Already Configured)
@@ -260,22 +283,23 @@ Setting up the marketplace for the first time:
Adding a new project when system config exists: Adding a new project when system config exists:
``` ```
1. /pm-setup --quick # Quick project setup (auto-detected) 1. /projman setup --quick # Quick project setup (auto-detected)
# → Confirms detected repo name # → Confirms detected repo name
# → Creates .env # → Creates .env
2. /labels-sync # Sync Gitea labels 2. /labels sync # Sync Gitea labels
3. /sprint-plan # Plan first sprint 3. /sprint plan # Plan first sprint
``` ```
--- ---
## Quick Tips ## Quick Tips
- **Hooks run automatically** - doc-guardian and code-sentinel protect you without manual invocation - **Hooks run automatically** - code-sentinel and git-flow protect you without manual invocation
- **Use `/git-commit` over `git commit`** - generates better commit messages following conventions - **Use `/gitflow commit` over `git commit`** - generates better commit messages following conventions
- **Run `/pm-review` before `/sprint-close`** - catches issues before closing the sprint - **Run `/sprint review` before `/sprint close`** - catches issues before closing the sprint
- **Use `/clarify` for vague requests** - especially helpful for complex requirements - **Use `/clarity clarify` for vague requests** - especially helpful for complex requirements
- **`/refactor-dry` is safe** - always preview before applying refactoring changes - **`/sentinel refactor-dry` is safe** - always preview before applying refactoring changes
- **`/gitflow commit --push`** replaces the old `/git-commit-push` - fewer commands to remember
--- ---
@@ -296,4 +320,4 @@ Ensure credentials are configured in `~/.config/claude/gitea.env`, `~/.config/cl
--- ---
*Last Updated: 2026-02-02* *Last Updated: 2026-02-06*

View File

@@ -9,7 +9,7 @@ Centralized configuration documentation for all plugins and MCP servers in the L
**After installing the marketplace and plugins via Claude Code:** **After installing the marketplace and plugins via Claude Code:**
``` ```
/pm-setup /projman setup
``` ```
The interactive wizard auto-detects what's needed and handles everything except manually adding your API tokens. The interactive wizard auto-detects what's needed and handles everything except manually adding your API tokens.
@@ -25,8 +25,8 @@ The interactive wizard auto-detects what's needed and handles everything except
└─────────────────────────────────────────────────────────────────────────────┘ └─────────────────────────────────────────────────────────────────────────────┘
/pm-setup --full /projman setup --full
(or /pm-setup auto-detects) (or /projman setup auto-detects)
┌──────────────────────────────┼──────────────────────────────┐ ┌──────────────────────────────┼──────────────────────────────┐
▼ ▼ ▼ ▼ ▼ ▼
@@ -79,7 +79,7 @@ The interactive wizard auto-detects what's needed and handles everything except
┌───────────────┴───────────────┐ ┌───────────────┴───────────────┐
▼ ▼ ▼ ▼
/pm-setup --quick /pm-setup /projman setup --quick /projman setup
(explicit mode) (auto-detects mode) (explicit mode) (auto-detects mode)
│ │ │ │
│ ┌──────────┴──────────┐ │ ┌──────────┴──────────┐
@@ -109,7 +109,7 @@ The interactive wizard auto-detects what's needed and handles everything except
## What Runs Automatically vs User Interaction ## What Runs Automatically vs User Interaction
### `/pm-setup --full` - Full Setup ### `/projman setup --full` - Full Setup
| Phase | Type | What Happens | | Phase | Type | What Happens |
|-------|------|--------------| |-------|------|--------------|
@@ -121,7 +121,7 @@ The interactive wizard auto-detects what's needed and handles everything except
| **6. Project Config** | Automated | Creates `.env` file, checks `.gitignore` | | **6. Project Config** | Automated | Creates `.env` file, checks `.gitignore` |
| **7. Validation** | Automated | Tests API connectivity, shows summary | | **7. Validation** | Automated | Tests API connectivity, shows summary |
### `/pm-setup --quick` - Quick Project Setup ### `/projman setup --quick` - Quick Project Setup
| Phase | Type | What Happens | | Phase | Type | What Happens |
|-------|------|--------------| |-------|------|--------------|
@@ -136,10 +136,10 @@ The interactive wizard auto-detects what's needed and handles everything except
| Mode | When to Use | What It Does | | Mode | When to Use | What It Does |
|------|-------------|--------------| |------|-------------|--------------|
| `/pm-setup` | Any time | Auto-detects: runs full, quick, or sync as needed | | `/projman setup` | Any time | Auto-detects: runs full, quick, or sync as needed |
| `/pm-setup --full` | First time on a machine | Full setup: MCP server + system config + project config | | `/projman setup --full` | First time on a machine | Full setup: MCP server + system config + project config |
| `/pm-setup --quick` | Starting a new project | Quick setup: project config only (assumes system is ready) | | `/projman setup --quick` | Starting a new project | Quick setup: project config only (assumes system is ready) |
| `/pm-setup --sync` | After repo move/rename | Updates .env to match current git remote | | `/projman setup --sync` | After repo move/rename | Updates .env to match current git remote |
**Auto-detection logic:** **Auto-detection logic:**
1. No system config → **full** mode 1. No system config → **full** mode
@@ -148,9 +148,9 @@ The interactive wizard auto-detects what's needed and handles everything except
4. Both exist, match → already configured, offer to reconfigure 4. Both exist, match → already configured, offer to reconfigure
**Typical workflow:** **Typical workflow:**
1. Install plugin → run `/pm-setup` (auto-runs full mode) 1. Install plugin → run `/projman setup` (auto-runs full mode)
2. Start new project → run `/pm-setup` (auto-runs quick mode) 2. Start new project → run `/projman setup` (auto-runs quick mode)
3. Repository moved? → run `/pm-setup` (auto-runs sync mode) 3. Repository moved? → run `/projman setup` (auto-runs sync mode)
--- ---
@@ -182,7 +182,7 @@ This marketplace uses a **hybrid configuration** approach:
**Benefits:** **Benefits:**
- Single token per service (update once, use everywhere) - Single token per service (update once, use everywhere)
- Easy multi-project setup (just run `/pm-setup` in each project) - Easy multi-project setup (just run `/projman setup` in each project)
- Security (tokens never committed to git, never typed into AI chat) - Security (tokens never committed to git, never typed into AI chat)
- Project isolation (each project can override defaults) - Project isolation (each project can override defaults)
@@ -190,7 +190,7 @@ This marketplace uses a **hybrid configuration** approach:
## Prerequisites ## Prerequisites
Before running `/pm-setup`: Before running `/projman setup`:
1. **Python 3.10+** installed 1. **Python 3.10+** installed
```bash ```bash
@@ -213,7 +213,7 @@ Before running `/pm-setup`:
Run the setup wizard in Claude Code: Run the setup wizard in Claude Code:
``` ```
/pm-setup /projman setup
``` ```
The wizard will guide you through each step interactively and auto-detect the appropriate mode. The wizard will guide you through each step interactively and auto-detect the appropriate mode.
@@ -387,18 +387,18 @@ PR_REVIEW_AUTO_SUBMIT=false
| Plugin | System Config | Project Config | Setup Command | | Plugin | System Config | Project Config | Setup Command |
|--------|---------------|----------------|---------------| |--------|---------------|----------------|---------------|
| **projman** | gitea.env | .env (GITEA_REPO=owner/repo) | `/pm-setup` | | **projman** | gitea.env | .env (GITEA_REPO=owner/repo) | `/projman setup` |
| **pr-review** | gitea.env | .env (GITEA_REPO=owner/repo) | `/pr-setup` | | **pr-review** | gitea.env | .env (GITEA_REPO=owner/repo) | `/pr setup` |
| **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 | `/cmdb-setup` | | **cmdb-assistant** | netbox.env | None | `/cmdb setup` |
| **data-platform** | postgres.env | .env (optional) | `/data-setup` | | **data-platform** | postgres.env | .env (optional) | `/data setup` |
| **viz-platform** | None | .env (optional DMC_VERSION) | `/viz-setup` | | **viz-platform** | None | .env (optional DMC_VERSION) | `/viz 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 |
| **claude-config-maintainer** | None | None | None needed | | **claude-config-maintainer** | None | None | None needed |
| **contract-validator** | None | None | `/cv-setup` | | **contract-validator** | None | None | `/cv setup` |
--- ---
@@ -408,7 +408,7 @@ Once system-level config is set up, adding new projects is simple:
``` ```
cd ~/projects/new-project cd ~/projects/new-project
/pm-setup /projman setup
``` ```
The command auto-detects that system config exists and runs quick project setup. The command auto-detects that system config exists and runs quick project setup.
@@ -485,7 +485,7 @@ Not all plugins have MCP servers. The install script handles this automatically:
| projman | ✓ (via gitea) | Issue, wiki, PR tools | | projman | ✓ (via gitea) | Issue, wiki, PR tools |
| pr-review | ✓ (via gitea) | PR review tools | | pr-review | ✓ (via gitea) | PR review tools |
| git-flow | ✗ | Commands only | | git-flow | ✗ | Commands only |
| doc-guardian | ✗ | Commands and hooks only | | doc-guardian | ✗ | Commands only |
| code-sentinel | ✗ | Commands and hooks only | | code-sentinel | ✗ | Commands and hooks only |
| clarity-assist | ✗ | Commands only | | clarity-assist | ✗ | Commands only |
@@ -631,7 +631,7 @@ For agents with 8+ skills, use **phase-based loading** in the agent body text. T
### API Validation ### API Validation
When running `/pm-setup`, the command: When running `/projman setup`, the command:
1. **Detects** organization and repository from git remote URL 1. **Detects** organization and repository from git remote URL
2. **Validates** via Gitea API: `GET /api/v1/repos/{org}/{repo}` 2. **Validates** via Gitea API: `GET /api/v1/repos/{org}/{repo}`
@@ -646,7 +646,7 @@ When you start a Claude Code session, a hook automatically:
1. Reads `GITEA_REPO` (in `owner/repo` format) 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 `/pm-setup --sync` to update." 3. **Warns** if mismatch detected: "Repository location mismatch. Run `/projman setup --sync` to update."
This helps when you: This helps when you:
- Move a repository to a different organization - Move a repository to a different organization
@@ -668,7 +668,7 @@ curl -H "Authorization: token $GITEA_API_TOKEN" "$GITEA_API_URL/user"
In Claude Code, after restarting your session: In Claude Code, after restarting your session:
``` ```
/labels-sync /labels sync
``` ```
If this works, your setup is complete. If this works, your setup is complete.
@@ -741,7 +741,7 @@ cat .env
3. **Never type tokens into AI chat** 3. **Never type tokens into AI chat**
- Always edit config files directly in your editor - Always edit config files directly in your editor
- The `/pm-setup` wizard respects this - The `/projman setup` wizard respects this
4. **Rotate tokens periodically** 4. **Rotate tokens periodically**
- Every 6-12 months - Every 6-12 months

View File

@@ -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-28 Last Updated: 2026-02-08
--- ---
@@ -95,9 +95,9 @@ Manually test if the MCP server can start:
```bash ```bash
RUNTIME=~/.claude/plugins/marketplaces/leo-claude-mktplace RUNTIME=~/.claude/plugins/marketplaces/leo-claude-mktplace
# Test Gitea MCP # Test Gitea MCP (uses gitea-mcp package from registry)
cd $RUNTIME/mcp-servers/gitea cd $RUNTIME/mcp-servers/gitea
PYTHONPATH=. .venv/bin/python -c "from mcp_server.server import main; print('OK')" .venv/bin/python -c "from gitea_mcp.server import main; print('OK')"
# Test NetBox MCP # Test NetBox MCP
cd $RUNTIME/mcp-servers/netbox cd $RUNTIME/mcp-servers/netbox
@@ -279,8 +279,8 @@ Error: Could not find a suitable TLS CA certificate bundle, invalid path:
Use these commands for automated checking: Use these commands for automated checking:
- `/pm-debug report` - Run full diagnostics, create issue if problems found - `/cv status` - Marketplace-wide health check (installation, MCP, configuration)
- `/pm-debug review` - Investigate existing diagnostic issues and propose fixes - `/hygiene check` - Project file organization and cleanup check
--- ---

249
docs/MIGRATION-v9.md Normal file
View File

@@ -0,0 +1,249 @@
# Migration Guide: v8.x → v9.0.0
## Overview
v9.0.0 standardizes all commands to the `/<noun> <action>` sub-command pattern. Every command in the marketplace now follows this convention.
**Breaking change:** All old command names are removed. Update your workflows, scripts, and CLAUDE.md references.
---
## Command Invocation
The `/<noun> <action>` pattern is a **display convention** for user-friendly command invocation. Under the hood, Claude Code resolves commands by filename using hyphens.
### How It Works
| You Type | What Happens | Actual Command Loaded |
|----------|--------------|----------------------|
| `/doc audit` | Dispatch file `doc.md` receives `$ARGUMENTS="audit"` | Routes to `/doc-guardian:doc-audit` (file: `doc-audit.md`) |
| `/sprint plan` | Dispatch file `sprint.md` receives `$ARGUMENTS="plan"` | Routes to `/projman:sprint-plan` (file: `sprint-plan.md`) |
| `/doc-guardian:doc-audit` | Direct invocation (bypasses dispatch) | Loads `doc-audit.md` directly |
### Two Invocation Methods
1. **User-friendly (via dispatch):** `/doc audit` — space-separated, routes through dispatch file
2. **Direct (plugin-prefixed):** `/doc-guardian:doc-audit` — bypasses dispatch, invokes command directly
Both methods work identically. The dispatch file provides `$ARGUMENTS` parsing and a menu interface when invoked without arguments.
### Command Name Mapping
**Pattern:** Spaces in display names become hyphens in filenames.
| Display Name | Filename | Plugin-Prefixed |
|--------------|----------|-----------------|
| `/doc audit` | `doc-audit.md` | `/doc-guardian:doc-audit` |
| `/sprint plan` | `sprint-plan.md` | `/projman:sprint-plan` |
| `/pr review` | `pr-review.md` | `/pr-review:pr-review` |
| `/gitflow commit` | `gitflow-commit.md` | `/git-flow:gitflow-commit` |
If dispatch routing fails, use the direct plugin-prefixed format.
---
## Complete Command Mapping
### projman
| Old (v8.x) | New (v9.0.0) | Notes |
|-------------|--------------|-------|
| `/sprint-plan` | `/sprint plan` | |
| `/sprint-start` | `/sprint start` | |
| `/sprint-status` | `/sprint status` | |
| `/sprint-close` | `/sprint close` | |
| `/pm-review` | `/sprint review` | Moved under `/sprint` |
| `/pm-test` | `/sprint test` | Moved under `/sprint` |
| `/pm-setup` | `/projman setup` | Moved under `/projman` |
| `/pm-debug` | **Removed** | Deleted in v8.1.0 — migrated to `debug-mcp` plugin (Decision #11) |
| `/labels-sync` | `/labels sync` | |
| `/suggest-version` | **Removed** | Deleted in v8.1.0 — migrated to `ops-release-manager` plugin (Decision #18) |
| `/proposal-status` | **Removed** | Deleted in v8.1.0 — absorbed into `/project status` (Decision #19) |
| `/rfc <sub>` | `/rfc <sub>` | Unchanged |
| `/project <sub>` | `/project <sub>` | Unchanged |
| `/adr <sub>` | `/adr <sub>` | Unchanged |
### git-flow
| Old (v8.x) | New (v9.0.0) | Notes |
|-------------|--------------|-------|
| `/git-commit` | `/gitflow commit` | |
| `/git-commit-push` | `/gitflow commit --push` | **Consolidated** into flag |
| `/git-commit-merge` | `/gitflow commit --merge` | **Consolidated** into flag |
| `/git-commit-sync` | `/gitflow commit --sync` | **Consolidated** into flag |
| `/branch-start` | `/gitflow branch-start` | |
| `/branch-cleanup` | `/gitflow branch-cleanup` | |
| `/git-status` | `/gitflow status` | |
| `/git-config` | `/gitflow config` | |
**Note:** The three commit variants (`-push`, `-merge`, `-sync`) are now flags on `/gitflow commit`. This reduces 8 commands to 5.
### pr-review
| Old (v8.x) | New (v9.0.0) | Notes |
|-------------|--------------|-------|
| `/pr-review` | `/pr review` | |
| `/pr-summary` | `/pr summary` | |
| `/pr-findings` | `/pr findings` | |
| `/pr-diff` | `/pr diff` | |
| `/pr-setup` | `/pr setup` | |
| `/project-init` | `/pr init` | Renamed |
| `/project-sync` | `/pr sync` | Renamed |
### clarity-assist
| Old (v8.x) | New (v9.0.0) | Notes |
|-------------|--------------|-------|
| `/clarify` | `/clarity clarify` | |
| `/quick-clarify` | `/clarity quick-clarify` | |
### doc-guardian
| Old (v8.x) | New (v9.0.0) | Notes |
|-------------|--------------|-------|
| `/doc-audit` | `/doc audit` | |
| `/doc-sync` | `/doc sync` | |
| `/changelog-gen` | `/doc changelog-gen` | Moved under `/doc` |
| `/doc-coverage` | `/doc coverage` | |
| `/stale-docs` | `/doc stale-docs` | Moved under `/doc` |
### code-sentinel
| Old (v8.x) | New (v9.0.0) | Notes |
|-------------|--------------|-------|
| `/security-scan` | `/sentinel scan` | |
| `/refactor` | `/sentinel refactor` | |
| `/refactor-dry` | `/sentinel refactor-dry` | |
### claude-config-maintainer
| Old (v8.x) | New (v9.0.0) | Notes |
|-------------|--------------|-------|
| `/config-analyze` (or `/analyze`) | `/claude-config analyze` | |
| `/config-optimize` (or `/optimize`) | `/claude-config optimize` | |
| `/config-init` (or `/init`) | `/claude-config init` | |
| `/config-diff` | `/claude-config diff` | |
| `/config-lint` | `/claude-config lint` | |
| `/config-audit-settings` | `/claude-config audit-settings` | |
| `/config-optimize-settings` | `/claude-config optimize-settings` | |
| `/config-permissions-map` | `/claude-config permissions-map` | |
### contract-validator
| Old (v8.x) | New (v9.0.0) | Notes |
|-------------|--------------|-------|
| `/validate-contracts` | `/cv validate` | |
| `/check-agent` | `/cv check-agent` | |
| `/list-interfaces` | `/cv list-interfaces` | |
| `/dependency-graph` | `/cv dependency-graph` | |
| `/cv-setup` | `/cv setup` | |
| `/cv status` | `/cv status` | Unchanged |
### cmdb-assistant
| Old (v8.x) | New (v9.0.0) | Notes |
|-------------|--------------|-------|
| `/cmdb-setup` | `/cmdb setup` | |
| `/cmdb-search` | `/cmdb search` | |
| `/cmdb-device` | `/cmdb device` | |
| `/cmdb-ip` | `/cmdb ip` | |
| `/cmdb-site` | `/cmdb site` | |
| `/cmdb-audit` | `/cmdb audit` | |
| `/cmdb-register` | `/cmdb register` | |
| `/cmdb-sync` | `/cmdb sync` | |
| `/cmdb-topology` | `/cmdb topology` | |
| `/change-audit` | `/cmdb change-audit` | Moved under `/cmdb` |
| `/ip-conflicts` | `/cmdb ip-conflicts` | Moved under `/cmdb` |
### data-platform
| Old (v8.x) | New (v9.0.0) | Notes |
|-------------|--------------|-------|
| `/data-ingest` | `/data ingest` | |
| `/data-profile` | `/data profile` | |
| `/data-schema` | `/data schema` | |
| `/data-explain` | `/data explain` | |
| `/data-lineage` | `/data lineage` | |
| `/data-run` | `/data run` | |
| `/lineage-viz` | `/data lineage-viz` | Moved under `/data` |
| `/dbt-test` | `/data dbt-test` | Moved under `/data` |
| `/data-quality` | `/data quality` | |
| `/data-review` | `/data review` | |
| `/data-gate` | `/data gate` | |
| `/data-setup` | `/data setup` | |
### viz-platform
| Old (v8.x) | New (v9.0.0) | Notes |
|-------------|--------------|-------|
| `/viz-setup` | `/viz setup` | |
| `/viz-chart` | `/viz chart` | |
| `/viz-chart-export` | `/viz chart-export` | |
| `/viz-dashboard` | `/viz dashboard` | |
| `/viz-theme` | `/viz theme` | |
| `/viz-theme-new` | `/viz theme-new` | |
| `/viz-theme-css` | `/viz theme-css` | |
| `/viz-component` | `/viz component` | |
| `/accessibility-check` | `/viz accessibility-check` | Moved under `/viz` |
| `/viz-breakpoints` | `/viz breakpoints` | |
| `/design-review` | `/viz design-review` | Moved under `/viz` |
| `/design-gate` | `/viz design-gate` | Moved under `/viz` |
### project-hygiene
No changes — already used `/<noun> <action>` pattern.
| Command | Status |
|---------|--------|
| `/hygiene check` | Unchanged |
---
## Verifying Plugin Installation (v9.0.0)
Test commands use the new format:
| Plugin | Test Command |
|--------|--------------|
| git-flow | `/git-flow:gitflow-status` |
| projman | `/projman:sprint-status` |
| pr-review | `/pr-review:pr-summary` |
| clarity-assist | `/clarity-assist:clarity-clarify` |
| doc-guardian | `/doc-guardian:doc-audit` |
| code-sentinel | `/code-sentinel:sentinel-scan` |
| claude-config-maintainer | `/claude-config-maintainer:claude-config-analyze` |
| cmdb-assistant | `/cmdb-assistant:cmdb-search` |
| data-platform | `/data-platform:data-ingest` |
| viz-platform | `/viz-platform:viz-chart` |
| contract-validator | `/contract-validator:cv-validate` |
---
## CLAUDE.md Updates
If your project's CLAUDE.md references old command names, update them:
**Find old references:**
```bash
grep -rn '/sprint-plan\|/pm-setup\|/git-commit\|/pr-review\|/security-scan\|/config-analyze\|/validate-contracts\|/cmdb-search\|/data-ingest\|/viz-chart\b\|/clarify\b\|/doc-audit' CLAUDE.md
```
**Key patterns to search and replace:**
- `/sprint-plan``/sprint plan`
- `/pm-setup``/projman setup`
- `/pm-review``/sprint review`
- `/git-commit``/gitflow commit`
- `/pr-review``/pr review`
- `/security-scan``/sentinel scan`
- `/refactor``/sentinel refactor`
- `/config-analyze``/claude-config analyze`
- `/validate-contracts``/cv validate`
- `/clarify``/clarity clarify`
- `/doc-audit``/doc audit`
- `/cmdb-search``/cmdb search`
- `/data-ingest``/data ingest`
- `/viz-chart``/viz chart`
---
*Last Updated: 2026-02-06*

View File

@@ -38,7 +38,7 @@ cd ~/.claude/plugins/marketplaces/leo-claude-mktplace && ./scripts/setup.sh
## What the Post-Update Script Does ## What the Post-Update Script Does
1. **Updates Python dependencies** for MCP servers (gitea, netbox) 1. **Updates Python dependencies** for all 5 MCP servers (gitea, netbox, data-platform, viz-platform, contract-validator)
2. **Shows recent changelog entries** so you know what changed 2. **Shows recent changelog entries** so you know what changed
3. **Validates your configuration** is still compatible 3. **Validates your configuration** is still compatible
@@ -48,7 +48,7 @@ cd ~/.claude/plugins/marketplaces/leo-claude-mktplace && ./scripts/setup.sh
### When to Re-run Setup ### When to Re-run Setup
You typically **don't need** to re-run setup after updates. However, re-run your plugin's setup command (e.g., `/pm-setup`, `/pr-setup`, `/cmdb-setup`) if: You typically **don't need** to re-run setup after updates. However, re-run your plugin's setup command (e.g., `/projman setup`, `/pr setup`, `/cmdb setup`) if:
- Changelog mentions **new required environment variables** - Changelog mentions **new required environment variables**
- Changelog mentions **breaking changes** to configuration - Changelog mentions **breaking changes** to configuration
@@ -59,7 +59,7 @@ You typically **don't need** to re-run setup after updates. However, re-run your
If an update requires new project-level configuration: If an update requires new project-level configuration:
``` ```
/project-init /pr init
``` ```
This will detect existing settings and only add what's missing. This will detect existing settings and only add what's missing.
@@ -98,8 +98,8 @@ When updating, review if changes affect the setup workflow:
1. **Check for setup command changes:** 1. **Check for setup command changes:**
```bash ```bash
git diff HEAD~1 plugins/*/commands/*-setup.md git diff HEAD~1 plugins/*/commands/*-setup.md
git diff HEAD~1 plugins/*/commands/project-init.md git diff HEAD~1 plugins/*/commands/pr-init.md
git diff HEAD~1 plugins/*/commands/project-sync.md git diff HEAD~1 plugins/*/commands/pr-sync.md
``` ```
2. **Check for hook changes:** 2. **Check for hook changes:**
@@ -114,7 +114,7 @@ When updating, review if changes affect the setup workflow:
**If setup commands changed:** **If setup commands changed:**
- Review what's new (new validation steps, new prompts, etc.) - Review what's new (new validation steps, new prompts, etc.)
- Consider re-running your plugin's setup command or `/project-init` to benefit from improvements - Consider re-running your plugin's setup command or `/pr init` to benefit from improvements
- Existing configurations remain valid unless changelog notes breaking changes - Existing configurations remain valid unless changelog notes breaking changes
**If hooks changed:** **If hooks changed:**
@@ -123,7 +123,7 @@ When updating, review if changes affect the setup workflow:
**If configuration structure changed:** **If configuration structure changed:**
- Check if new variables are required - Check if new variables are required
- Run `/project-sync` if repository detection logic improved - Run `/pr sync` if repository detection logic improved
--- ---
@@ -142,7 +142,7 @@ deactivate
### Configuration no longer works ### Configuration no longer works
1. Check CHANGELOG.md for breaking changes 1. Check CHANGELOG.md for breaking changes
2. Run your plugin's setup command (e.g., `/pm-setup`) to re-validate and fix configuration 2. Run your plugin's setup command (e.g., `/projman setup`) to re-validate and fix configuration
3. Compare your config files with documentation in `docs/CONFIGURATION.md` 3. Compare your config files with documentation in `docs/CONFIGURATION.md`
### MCP server won't start after update ### MCP server won't start after update
@@ -157,10 +157,11 @@ cd ~/.claude/plugins/marketplaces/leo-claude-mktplace && ./scripts/setup.sh
If that doesn't work: If that doesn't work:
1. Check Python version: `python3 --version` (requires 3.10+) 1. Check Python version: `python3 --version` (requires 3.10+)
2. Verify venv exists in INSTALLED location: 2. Verify venvs exist in INSTALLED location:
```bash ```bash
ls ~/.claude/plugins/marketplaces/leo-claude-mktplace/mcp-servers/gitea/.venv for server in gitea netbox data-platform viz-platform contract-validator; do
ls ~/.claude/plugins/marketplaces/leo-claude-mktplace/mcp-servers/netbox/.venv ls ~/.claude/plugins/marketplaces/leo-claude-mktplace/mcp-servers/$server/.venv && echo "$server: OK" || echo "$server: MISSING"
done
``` ```
3. If missing, run setup.sh as shown above. 3. If missing, run setup.sh as shown above.
4. Restart Claude Code session 4. Restart Claude Code session

View File

@@ -1,271 +0,0 @@
# Agent Workflow - Draw.io Specification
**Target File:** `docs/architecture/agent-workflow.drawio`
**Purpose:** Shows when Planner, Orchestrator, Executor, and Code Reviewer agents trigger during sprint lifecycle.
**Diagram Type:** Swimlane / Sequence Diagram
---
## SWIMLANES
| ID | Label | Color | Position |
|----|-------|-------|----------|
| user-lane | User | #E3F2FD | 1 (leftmost) |
| planner-lane | Planner Agent | #4A90D9 | 2 |
| orchestrator-lane | Orchestrator Agent | #7CB342 | 3 |
| executor-lane | Executor Agent | #FF9800 | 4 |
| reviewer-lane | Code Reviewer Agent | #9C27B0 | 5 |
| gitea-lane | Gitea (Issues + Wiki) | #9E9E9E | 6 (rightmost) |
---
## PHASE 1: SPRINT PLANNING
### Nodes
| ID | Label | Type | Lane | Sequence |
|----|-------|------|------|----------|
| p1-start | /sprint-plan | rounded-rect | user-lane | 1 |
| p1-activate | Planner Activates | rectangle | planner-lane | 2 |
| p1-search-lessons | Search Lessons Learned | rectangle | planner-lane | 3 |
| p1-gitea-wiki-query | Query Past Lessons (Wiki) | rectangle | gitea-lane | 4 |
| p1-return-lessons | Return Relevant Lessons | rectangle | planner-lane | 5 |
| p1-clarify | Ask Clarifying Questions | diamond | planner-lane | 6 |
| p1-user-answers | Provide Answers | rectangle | user-lane | 7 |
| p1-create-issues | Create Issues with Labels | rectangle | planner-lane | 8 |
| p1-gitea-create | Store Issues | rectangle | gitea-lane | 9 |
| p1-plan-complete | Planning Complete | rounded-rect | planner-lane | 10 |
### Edges
| From | To | Label | Style |
|------|----|-------|-------|
| p1-start | p1-activate | invokes | solid |
| p1-activate | p1-search-lessons | | solid |
| p1-search-lessons | p1-gitea-wiki-query | REST API (search_lessons) | solid |
| p1-gitea-wiki-query | p1-return-lessons | lessons data | dashed |
| p1-return-lessons | p1-clarify | | solid |
| p1-clarify | p1-user-answers | questions | solid |
| p1-user-answers | p1-clarify | answers | dashed |
| p1-clarify | p1-create-issues | | solid |
| p1-create-issues | p1-gitea-create | REST API | solid |
| p1-gitea-create | p1-plan-complete | confirm | dashed |
---
## PHASE 2: SPRINT EXECUTION
### Nodes
| ID | Label | Type | Lane | Sequence |
|----|-------|------|------|----------|
| p2-start | /sprint-start | rounded-rect | user-lane | 11 |
| p2-orch-activate | Orchestrator Activates | rectangle | orchestrator-lane | 12 |
| p2-fetch-issues | Fetch Sprint Issues | rectangle | orchestrator-lane | 13 |
| p2-gitea-list | List Open Issues | rectangle | gitea-lane | 14 |
| p2-sequence | Sequence Work (Dependencies) | rectangle | orchestrator-lane | 15 |
| p2-dispatch | Dispatch Task | rectangle | orchestrator-lane | 16 |
| p2-exec-activate | Executor Activates | rectangle | executor-lane | 17 |
| p2-implement | Implement Task | rectangle | executor-lane | 18 |
| p2-update-status | Update Issue Status | rectangle | executor-lane | 19 |
| p2-gitea-update | Update Issue | rectangle | gitea-lane | 20 |
| p2-report | Report Completion | rectangle | executor-lane | 21 |
| p2-loop | More Tasks? | diamond | orchestrator-lane | 22 |
| p2-exec-complete | Execution Complete | rounded-rect | orchestrator-lane | 23 |
### Edges
| From | To | Label | Style |
|------|----|-------|-------|
| p2-start | p2-orch-activate | invokes | solid |
| p2-orch-activate | p2-fetch-issues | | solid |
| p2-fetch-issues | p2-gitea-list | REST API | solid |
| p2-gitea-list | p2-sequence | issues data | dashed |
| p2-sequence | p2-dispatch | parallel batching | solid |
| p2-dispatch | p2-exec-activate | execution prompt | solid |
| p2-exec-activate | p2-implement | | solid |
| p2-implement | p2-update-status | | solid |
| p2-update-status | p2-gitea-update | REST API | solid |
| p2-gitea-update | p2-report | confirm | dashed |
| p2-report | p2-loop | | solid |
| p2-loop | p2-dispatch | yes | solid |
| p2-loop | p2-exec-complete | no | solid |
---
## PHASE 2.5: CODE REVIEW (Pre-Close)
### Nodes
| ID | Label | Type | Lane | Sequence |
|----|-------|------|------|----------|
| p25-start | /review | rounded-rect | user-lane | 24 |
| p25-reviewer-activate | Code Reviewer Activates | rectangle | reviewer-lane | 25 |
| p25-scan-changes | Scan Recent Changes | rectangle | reviewer-lane | 26 |
| p25-check-quality | Check Code Quality | rectangle | reviewer-lane | 27 |
| p25-security-scan | Security Scan | rectangle | reviewer-lane | 28 |
| p25-report | Generate Review Report | rectangle | reviewer-lane | 29 |
| p25-complete | Review Complete | rounded-rect | reviewer-lane | 30 |
### Edges
| From | To | Label | Style |
|------|----|-------|-------|
| p25-start | p25-reviewer-activate | invokes | solid |
| p25-reviewer-activate | p25-scan-changes | | solid |
| p25-scan-changes | p25-check-quality | | solid |
| p25-check-quality | p25-security-scan | | solid |
| p25-security-scan | p25-report | | solid |
| p25-report | p25-complete | | solid |
---
## PHASE 3: SPRINT CLOSE
### Nodes
| ID | Label | Type | Lane | Sequence |
|----|-------|------|------|----------|
| p3-start | /sprint-close | rounded-rect | user-lane | 31 |
| p3-orch-activate | Orchestrator Activates | rectangle | orchestrator-lane | 32 |
| p3-review | Review Sprint | rectangle | orchestrator-lane | 33 |
| p3-gitea-status | Get Final Status | rectangle | gitea-lane | 34 |
| p3-capture | Capture Lessons Learned | rectangle | orchestrator-lane | 35 |
| p3-user-input | Confirm Lessons | diamond | user-lane | 36 |
| p3-create-wiki | Create Wiki Pages | rectangle | orchestrator-lane | 37 |
| p3-gitea-wiki-create | Store Lessons (Wiki) | rectangle | gitea-lane | 38 |
| p3-close-issues | Close Issues | rectangle | orchestrator-lane | 39 |
| p3-gitea-close | Mark Closed | rectangle | gitea-lane | 40 |
| p3-complete | Sprint Closed | rounded-rect | orchestrator-lane | 41 |
### Edges
| From | To | Label | Style |
|------|----|-------|-------|
| p3-start | p3-orch-activate | invokes | solid |
| p3-orch-activate | p3-review | | solid |
| p3-review | p3-gitea-status | REST API | solid |
| p3-gitea-status | p3-capture | status data | dashed |
| p3-capture | p3-user-input | proposed lessons | solid |
| p3-user-input | p3-create-wiki | confirmed | solid |
| p3-create-wiki | p3-gitea-wiki-create | REST API (create_lesson) | solid |
| p3-gitea-wiki-create | p3-close-issues | confirm | dashed |
| p3-close-issues | p3-gitea-close | REST API | solid |
| p3-gitea-close | p3-complete | confirm | dashed |
---
## LAYOUT NOTES
```
+--------+------------+---------------+------------+----------+------------------+
| User | Planner | Orchestrator | Executor | Reviewer | Gitea |
| | | | | | (Issues + Wiki) |
+--------+------------+---------------+------------+----------+------------------+
| | | | | | |
| PHASE 1: SPRINT PLANNING |
|-------------------------------------------------------------------------------|
| O | | | | | |
| | | | | | | |
| +---->| O | | | | |
| | | | | | | |
| | +----------|---------------|------------|--------->| O (Wiki Query) |
| | |<---------|---------------|------------|----------+ | |
| | | | | | | |
| | O<> | | | | |
| O<--->+ | | | | | |
| | | | | | | |
| | +----------|---------------|------------|--------->| O (Issues) |
| | O | | | | |
| | | | | | |
|-------------------------------------------------------------------------------|
| PHASE 2: SPRINT EXECUTION |
|-------------------------------------------------------------------------------|
| O | | | | | |
| | | | | | | |
| +-----|----------->| O | | | |
| | | | | | | |
| | | +-------------|------------|--------->| O (Issues) |
| | | |<------------|------------|----------+ | |
| | | | | | | |
| | | +------------>| O | | |
| | | | | | | |
| | | | +----------|--------->| O (Issues) |
| | | | |<---------|----------+ | |
| | | O<------------+ | | | |
| | | | | | | |
| | | O (loop) | | | |
| | | | | | |
|-------------------------------------------------------------------------------|
| PHASE 2.5: CODE REVIEW |
|-------------------------------------------------------------------------------|
| O | | | | | |
| | | | | | | |
| +-----|------------|---------------|----------->| O | |
| | | | | | | |
| | | | | O->O->O | |
| | | | | | | |
| | | | | O | |
| | | | | | |
|-------------------------------------------------------------------------------|
| PHASE 3: SPRINT CLOSE |
|-------------------------------------------------------------------------------|
| O | | | | | |
| | | | | | | |
| +-----|----------->| O | | | |
| | | +-------------|------------|--------->| O (Issues) |
| | | |<------------|------------|----------+ | |
| | | | | | | |
| O<----|-----------<+ | | | | |
| +-----|----------->| | | | | |
| | | +-------------|------------|--------->| O (Wiki Create) |
| | | |<------------|------------|----------+ | |
| | | +-------------|------------|--------->| O (Issues Close) |
| | | O | | | |
+--------+------------+---------------+------------+----------+------------------+
```
---
## COLOR LEGEND
| Color | Hex | Meaning |
|-------|-----|---------|
| Light Blue | #E3F2FD | User actions |
| Blue | #4A90D9 | Planner Agent |
| Green | #7CB342 | Orchestrator Agent |
| Orange | #FF9800 | Executor Agent |
| Purple | #9C27B0 | Code Reviewer Agent |
| Gray | #9E9E9E | External Services (Gitea) |
---
## SHAPE LEGEND
| Shape | Meaning |
|-------|---------|
| Rounded Rectangle | Start/End points (commands) |
| Rectangle | Process/Action |
| Diamond | Decision point |
| Cylinder | Data store (in component map) |
---
## ARROW LEGEND
| Style | Meaning |
|-------|---------|
| Solid | Action/Request |
| Dashed | Response/Data return |
---
## ARCHITECTURE NOTES
- **Gitea provides BOTH issue tracking AND wiki** (no separate wiki service)
- All wiki operations use Gitea REST API via MCP tools
- Lessons learned stored in Gitea Wiki under `lessons-learned/sprints/`
- MCP tools: `search_lessons`, `create_lesson`, `list_wiki_pages`, `get_wiki_page`
- Four-agent model: Planner, Orchestrator, Executor, Code Reviewer

View File

@@ -1,139 +0,0 @@
# Component Map - Draw.io Specification
**Target File:** `docs/architecture/component-map.drawio`
**Purpose:** Shows all plugins, MCP servers, hooks and their relationships.
---
## NODES
### Plugins (Blue - #4A90D9)
| ID | Label | Type | Color | Position |
|----|-------|------|-------|----------|
| projman | projman | rectangle | #4A90D9 | top-center |
| projman-pmo | projman-pmo (planned) | rectangle | #4A90D9 | top-right |
| project-hygiene | project-hygiene | rectangle | #4A90D9 | top-left |
| claude-config | claude-config-maintainer | rectangle | #4A90D9 | bottom-left |
| cmdb-assistant | cmdb-assistant | rectangle | #4A90D9 | bottom-right |
### MCP Servers (Green - #7CB342)
MCP servers are **bundled inside each plugin** that needs them.
| ID | Label | Type | Color | Position | Bundled In |
|----|-------|------|-------|----------|------------|
| gitea-mcp | Gitea MCP Server | rectangle | #7CB342 | middle-left | projman |
| netbox-mcp | NetBox MCP Server | rectangle | #7CB342 | middle-right | cmdb-assistant |
### External Systems (Gray - #9E9E9E)
| ID | Label | Type | Color | Position |
|----|-------|------|-------|----------|
| gitea-instance | Gitea\n(Issues + Wiki) | cylinder | #9E9E9E | bottom-left |
| netbox-instance | NetBox | cylinder | #9E9E9E | bottom-right |
### Configuration (Orange - #FF9800)
| ID | Label | Type | Color | Position |
|----|-------|------|-------|----------|
| system-config | System Config\n~/.config/claude/ | rectangle | #FF9800 | far-left |
| project-config | Project Config\n.env | rectangle | #FF9800 | far-right |
---
## EDGES
### Plugin to MCP Server Connections
| From | To | Label | Style | Arrow |
|------|----|-------|-------|-------|
| projman | gitea-mcp | bundled | solid | bidirectional |
| cmdb-assistant | netbox-mcp | bundled | solid | bidirectional |
### Plugin Dependencies
| From | To | Label | Style | Arrow |
|------|----|-------|-------|-------|
| projman-pmo | projman | depends on | dashed | forward |
### MCP Server to External System Connections
| From | To | Label | Style | Arrow |
|------|----|-------|-------|-------|
| gitea-mcp | gitea-instance | REST API | solid | forward |
| netbox-mcp | netbox-instance | REST API | solid | forward |
### Configuration Connections
| From | To | Label | Style | Arrow |
|------|----|-------|-------|-------|
| system-config | gitea-mcp | credentials | dashed | forward |
| system-config | netbox-mcp | credentials | dashed | forward |
| project-config | gitea-mcp | repo context | dashed | forward |
| project-config | netbox-mcp | site context | dashed | forward |
---
## GROUPS
| ID | Label | Contains | Style |
|----|-------|----------|-------|
| plugins-group | Plugins | projman, projman-pmo, project-hygiene, claude-config, cmdb-assistant | light blue border |
| external-group | External Services | gitea-instance, netbox-instance | light gray border |
| config-group | Configuration | system-config, project-config | light orange border |
---
## LAYOUT NOTES
```
+------------------------------------------------------------------+
| PLUGINS GROUP |
| +----------------+ +----------------+ +-------------------+ |
| | project- | | projman | | projman-pmo | |
| | hygiene | | [gitea-mcp] | | (planned) | |
| +----------------+ +-------+--------+ +-------------------+ |
| | |
| +----------------+ +-------------------+ |
| | claude-config | | cmdb-assistant | |
| | -maintainer | | [netbox-mcp] | |
| +----------------+ +--------+----------+ |
+------------------------------------------------------------------+
|
v
+------------------------------------------------------------------+
| EXTERNAL SERVICES GROUP |
| +-------------------+ +-------------------+ |
| | Gitea | | NetBox | |
| | (Issues + Wiki) | | | |
| +-------------------+ +-------------------+ |
+------------------------------------------------------------------+
CONFIG GROUP (left side): CONFIG GROUP (right side):
+-------------------+ +-------------------+
| System Config | | Project Config |
| ~/.config/claude/ | | .env |
+-------------------+ +-------------------+
```
---
## COLOR LEGEND
| Color | Hex | Meaning |
|-------|-----|---------|
| Blue | #4A90D9 | Plugins |
| Green | #7CB342 | MCP Servers (bundled in plugins) |
| Gray | #9E9E9E | External Systems |
| Orange | #FF9800 | Configuration |
---
## ARCHITECTURE NOTES
- MCP servers are **bundled inside plugins** (not shared at root)
- Gitea provides both issue tracking AND wiki (lessons learned)
- No separate Wiki.js - all wiki functionality uses Gitea Wiki
- Each plugin is self-contained for Claude Code caching

View File

View File

@@ -1,6 +0,0 @@
2026-02-03T14:09:25 | mcp-servers | /home/lmiranda/claude-plugins-work/mcp-servers/gitea/tests/test_config.py | docs/COMMANDS-CHEATSHEET.md CLAUDE.md
2026-02-03T14:09:33 | mcp-servers | /home/lmiranda/claude-plugins-work/mcp-servers/gitea/tests/test_gitea_client.py | docs/COMMANDS-CHEATSHEET.md CLAUDE.md
2026-02-03T14:10:22 | mcp-servers | /home/lmiranda/claude-plugins-work/mcp-servers/gitea/tests/test_issues.py | docs/COMMANDS-CHEATSHEET.md CLAUDE.md
2026-02-03T14:17:12 | mcp-servers | /home/lmiranda/claude-plugins-work/mcp-servers/gitea/README.md | docs/COMMANDS-CHEATSHEET.md CLAUDE.md
2026-02-03T14:18:27 | mcp-servers | /home/lmiranda/claude-plugins-work/mcp-servers/gitea/CHANGELOG.md | docs/COMMANDS-CHEATSHEET.md CLAUDE.md
2026-02-03T14:18:41 | mcp-servers | /home/lmiranda/claude-plugins-work/mcp-servers/gitea/TESTING.md | docs/COMMANDS-CHEATSHEET.md CLAUDE.md

View File

@@ -1,92 +0,0 @@
# Changelog
All notable changes to the Gitea MCP Server will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
## [1.3.0] - 2026-02-03
### Added
- Pull request tools (7 tools):
- `list_pull_requests` - List PRs from repository
- `get_pull_request` - Get specific PR details
- `get_pr_diff` - Get PR diff content
- `get_pr_comments` - Get comments on a PR
- `create_pr_review` - Create PR review (approve/request changes/comment)
- `add_pr_comment` - Add comment to PR
- `create_pull_request` - Create new pull request
- Label creation tools (3 tools):
- `create_label` - Create repo-level label
- `create_org_label` - Create organization-level label
- `create_label_smart` - Auto-detect org vs repo for label creation
- Validation tools (2 tools):
- `validate_repo_org` - Check if repo belongs to organization
- `get_branch_protection` - Get branch protection rules
### Changed
- Total tools increased from 20 to 36
- Updated test suite to 64 tests (was 42)
### Fixed
- Test fixtures updated to use `owner/repo` format
- Fixed aggregate_issues tests to pass required `org` argument
## [1.2.0] - 2026-01-28
### Added
- Milestone management tools (5 tools):
- `list_milestones` - List all milestones
- `get_milestone` - Get specific milestone
- `create_milestone` - Create new milestone
- `update_milestone` - Update existing milestone
- `delete_milestone` - Delete a milestone
- Issue dependency tools (4 tools):
- `list_issue_dependencies` - List blocking issues
- `create_issue_dependency` - Create dependency between issues
- `remove_issue_dependency` - Remove dependency
- `get_execution_order` - Calculate parallelizable execution order
## [1.1.0] - 2026-01-21
### Added
- Wiki and lessons learned tools (7 tools):
- `list_wiki_pages` - List all wiki pages
- `get_wiki_page` - Get specific wiki page content
- `create_wiki_page` - Create new wiki page
- `update_wiki_page` - Update existing wiki page
- `create_lesson` - Create lessons learned entry
- `search_lessons` - Search lessons by query/tags
- `allocate_rfc_number` - Get next available RFC number
- Automatic git remote URL detection for repository configuration
- Support for SSH, HTTPS, and HTTP git URL formats
### Changed
- Configuration now uses `owner/repo` format exclusively
- Removed separate `GITEA_OWNER` configuration (now derived from repo path)
## [1.0.0] - 2025-01-06
### Added
- Initial release with 8 core tools:
- `list_issues` - List issues from repository
- `get_issue` - Get specific issue details
- `create_issue` - Create new issue with labels
- `update_issue` - Update existing issue
- `add_comment` - Add comment to issue
- `get_labels` - Get all labels (org + repo)
- `suggest_labels` - Intelligent label suggestion
- `aggregate_issues` - Cross-repository issue aggregation (PMO mode)
- Hybrid configuration system (system + project level)
- Branch-aware security model
- Mode detection (project vs company/PMO)
- 42 unit tests with mocks
- Comprehensive documentation
[Unreleased]: https://github.com/owner/repo/compare/v1.3.0...HEAD
[1.3.0]: https://github.com/owner/repo/compare/v1.2.0...v1.3.0
[1.2.0]: https://github.com/owner/repo/compare/v1.1.0...v1.2.0
[1.1.0]: https://github.com/owner/repo/compare/v1.0.0...v1.1.0
[1.0.0]: https://github.com/owner/repo/releases/tag/v1.0.0

View File

@@ -1,490 +1,47 @@
# Gitea MCP Server # Gitea MCP Server (Marketplace Wrapper)
Model Context Protocol (MCP) server for Gitea integration with Claude Code. This directory provides the virtual environment for the `gitea-mcp` package.
## Overview ## Package
The Gitea MCP Server provides Claude Code with direct access to Gitea for issue management, label operations, and repository tracking. It supports both single-repository (project mode) and multi-repository (company/PMO mode) operations. **Source:** [gitea-mcp](https://gitea.hotserv.cloud/personal-projects/gitea-mcp)
**Registry:** Gitea PyPI at gitea.hotserv.cloud
**Version:** >=1.0.0
**Status**: ✅ Phase 1 Complete - Fully functional and tested ## Setup
## Features
### Core Functionality
- **Issue Management**: CRUD operations for Gitea issues
- **Label Taxonomy**: Dynamic 44-label system with intelligent suggestions
- **Mode Detection**: Automatic project vs company-wide mode detection
- **Branch-Aware Security**: Prevents accidental changes on production branches
- **Hybrid Configuration**: System-level credentials + project-level paths
- **PMO Support**: Multi-repository aggregation for organization-wide views
### Tools Provided (36 total)
#### Issue Management (6 tools)
| Tool | Description | Mode |
|------|-------------|------|
| `list_issues` | List issues from repository | Both |
| `get_issue` | Get specific issue details | Both |
| `create_issue` | Create new issue with labels | Both |
| `update_issue` | Update existing issue | Both |
| `add_comment` | Add comment to issue | Both |
| `aggregate_issues` | Cross-repository issue aggregation | PMO Only |
#### Label Management (5 tools)
| Tool | Description | Mode |
|------|-------------|------|
| `get_labels` | Get all labels (org + repo) | Both |
| `suggest_labels` | Intelligent label suggestion | Both |
| `create_label` | Create repo-level label | Both |
| `create_org_label` | Create organization-level label | Both |
| `create_label_smart` | Auto-detect org vs repo for label creation | Both |
#### Wiki & Lessons Learned (7 tools)
| Tool | Description | Mode |
|------|-------------|------|
| `list_wiki_pages` | List all wiki pages | Both |
| `get_wiki_page` | Get specific wiki page content | Both |
| `create_wiki_page` | Create new wiki page | Both |
| `update_wiki_page` | Update existing wiki page | Both |
| `create_lesson` | Create lessons learned entry | Both |
| `search_lessons` | Search lessons by query/tags | Both |
| `allocate_rfc_number` | Get next available RFC number | Both |
#### Milestone Management (5 tools)
| Tool | Description | Mode |
|------|-------------|------|
| `list_milestones` | List all milestones | Both |
| `get_milestone` | Get specific milestone | Both |
| `create_milestone` | Create new milestone | Both |
| `update_milestone` | Update existing milestone | Both |
| `delete_milestone` | Delete a milestone | Both |
#### Issue Dependencies (4 tools)
| Tool | Description | Mode |
|------|-------------|------|
| `list_issue_dependencies` | List blocking issues | Both |
| `create_issue_dependency` | Create dependency between issues | Both |
| `remove_issue_dependency` | Remove dependency | Both |
| `get_execution_order` | Calculate parallelizable execution order | Both |
#### Pull Request Tools (7 tools)
| Tool | Description | Mode |
|------|-------------|------|
| `list_pull_requests` | List PRs from repository | Both |
| `get_pull_request` | Get specific PR details | Both |
| `get_pr_diff` | Get PR diff content | Both |
| `get_pr_comments` | Get comments on a PR | Both |
| `create_pr_review` | Create PR review (approve/request changes) | Both |
| `add_pr_comment` | Add comment to PR | Both |
| `create_pull_request` | Create new pull request | Both |
#### Validation Tools (2 tools)
| Tool | Description | Mode |
|------|-------------|------|
| `validate_repo_org` | Check if repo belongs to organization | Both |
| `get_branch_protection` | Get branch protection rules | Both |
## Architecture
### Directory Structure
```
mcp-servers/gitea/
├── .venv/ # Python virtual environment
├── requirements.txt # Python dependencies
├── run.sh # Entry point script
├── mcp_server/
│ ├── __init__.py
│ ├── server.py # MCP server entry point (36 tools)
│ ├── config.py # Configuration loader with auto-detection
│ ├── gitea_client.py # Gitea API client
│ └── tools/
│ ├── __init__.py
│ ├── issues.py # Issue management tools
│ ├── labels.py # Label management tools
│ ├── wiki.py # Wiki & lessons learned tools
│ ├── milestones.py # Milestone management tools
│ ├── dependencies.py # Issue dependency tools
│ └── pull_requests.py # Pull request tools
├── tests/
│ ├── __init__.py
│ ├── test_config.py
│ ├── test_gitea_client.py
│ ├── test_issues.py
│ └── test_labels.py
├── README.md # This file
├── TESTING.md # Testing instructions
└── CHANGELOG.md # Version history
```
### Mode Detection
The server operates in two modes based on environment variables:
**Project Mode** (Single Repository):
- When `GITEA_REPO` is set
- Operates on single repository
- Used by `projman` plugin
**Company Mode** (Multi-Repository / PMO):
- When `GITEA_REPO` is NOT set
- Operates on all repositories in organization
- Used by `projman-pmo` plugin
### Branch-Aware Security
Operations are restricted based on the current Git branch:
| Branch | Read | Create Issue | Update/Comment |
|--------|------|--------------|----------------|
| `main`, `master`, `prod/*` | ✅ | ❌ | ❌ |
| `staging`, `stage/*` | ✅ | ✅ | ❌ |
| `development`, `develop`, `feat/*`, `dev/*` | ✅ | ✅ | ✅ |
## Installation
### Prerequisites
- Python 3.10 or higher
- Git repository (for branch detection)
- Access to Gitea instance with API token
### Step 1: Install Dependencies
```bash ```bash
cd mcp-servers/gitea
python3 -m venv .venv python3 -m venv .venv
source .venv/bin/activate # Linux/Mac source .venv/bin/activate
# or .venv\Scripts\activate # Windows
pip install -r requirements.txt pip install -r requirements.txt
``` ```
### Step 2: Configure System-Level Settings Or use the marketplace setup script:
Create `~/.config/claude/gitea.env`:
```bash ```bash
mkdir -p ~/.config/claude ./scripts/setup-venvs.sh gitea
cat > ~/.config/claude/gitea.env << EOF
GITEA_API_URL=https://gitea.example.com/api/v1
GITEA_API_TOKEN=your_gitea_token_here
EOF
chmod 600 ~/.config/claude/gitea.env
``` ```
### Step 3: Configure Project-Level Settings (Optional)
For project mode, create `.env` in your project root:
```bash
echo "GITEA_REPO=your-repo-name" > .env
echo ".env" >> .gitignore
```
For company/PMO mode, omit the `.env` file or don't set `GITEA_REPO`.
## Configuration ## Configuration
### System-Level Configuration See `~/.config/claude/gitea.env` for system-level config (API URL, token).
See project `.env` for project-level config (GITEA_ORG, GITEA_REPO).
**File**: `~/.config/claude/gitea.env` ## Updating
**Required Variables**:
- `GITEA_API_URL` - Gitea API endpoint (e.g., `https://gitea.example.com/api/v1`)
- `GITEA_API_TOKEN` - Personal access token with repo permissions
### Project-Level Configuration
**File**: `<project-root>/.env`
**Optional Variables**:
- `GITEA_REPO` - Repository in `owner/repo` format (enables project mode)
### Automatic Repository Detection
If `GITEA_REPO` is not set, the server auto-detects the repository from your git remote:
**Supported URL Formats**:
- SSH: `ssh://git@gitea.example.com:22/owner/repo.git`
- SSH short: `git@gitea.example.com:owner/repo.git`
- HTTPS: `https://gitea.example.com/owner/repo.git`
- HTTP: `http://gitea.example.com/owner/repo.git`
The repository is extracted as `owner/repo` format automatically.
### Project Directory Detection
The server finds your project directory using these strategies (in order):
1. `CLAUDE_PROJECT_DIR` environment variable (highest priority)
2. `PWD` environment variable (if `.git` or `.env` present)
3. Current working directory (if `.git` or `.env` present)
4. Falls back to company/PMO mode if no project found
### Generating Gitea API Token
1. Log into Gitea: https://gitea.example.com
2. Navigate to: **Settings****Applications****Manage Access Tokens**
3. Click **Generate New Token**
4. Configure token:
- **Token Name**: `claude-code-mcp`
- **Permissions**:
-`repo` (all) - Read/write repositories, issues, labels
-`read:org` - Read organization information and labels
-`read:user` - Read user information
5. Click **Generate Token**
6. Copy token immediately (shown only once)
7. Add to `~/.config/claude/gitea.env`
## Usage
### Running the MCP Server
```bash ```bash
cd mcp-servers/gitea
source .venv/bin/activate source .venv/bin/activate
python -m mcp_server.server pip install --upgrade gitea-mcp \
--extra-index-url https://gitea.hotserv.cloud/api/packages/personal-projects/pypi/simple
``` ```
The server communicates via JSON-RPC 2.0 over stdio. ## Features
### Integration with Claude Code Plugins The `gitea-mcp` package provides MCP tools for:
The MCP server is designed to be used by Claude Code plugins via `.mcp.json` configuration: - **Issues**: CRUD, comments, dependencies, execution order
- **Labels**: Get, suggest, create (org + repo level)
```json - **Milestones**: CRUD operations
{ - **Pull Requests**: List, get, diff, comments, reviews, create
"mcpServers": { - **Wiki**: Pages, lessons learned, RFC allocation
"gitea": { - **Validation**: Repository org check, branch protection
"command": "python",
"args": ["-m", "mcp_server.server"],
"cwd": "${CLAUDE_PLUGIN_ROOT}/../mcp-servers/gitea",
"env": {
"PYTHONPATH": "${CLAUDE_PLUGIN_ROOT}/../mcp-servers/gitea"
}
}
}
}
```
### Example Tool Calls
**List Issues**:
```python
from mcp_server.tools.issues import IssueTools
from mcp_server.gitea_client import GiteaClient
client = GiteaClient()
issue_tools = IssueTools(client)
issues = await issue_tools.list_issues(state='open', labels=['Type/Bug'])
```
**Suggest Labels**:
```python
from mcp_server.tools.labels import LabelTools
label_tools = LabelTools(client)
context = "Fix critical authentication bug in production API"
suggestions = await label_tools.suggest_labels(context)
# Returns: ['Type/Bug', 'Priority/Critical', 'Component/Auth', 'Component/API', ...]
```
## Testing
### Unit Tests
Run all 64 unit tests with mocks:
```bash
pytest tests/ -v
```
Expected: `64 passed`
### Integration Tests
Test with real Gitea instance:
```bash
python -c "
from mcp_server.gitea_client import GiteaClient
client = GiteaClient()
issues = client.list_issues(state='open')
print(f'Found {len(issues)} open issues')
"
```
### Full Testing Guide
See [TESTING.md](./TESTING.md) for comprehensive testing instructions.
## Label Taxonomy System
The system supports a dynamic 44-label taxonomy (28 org + 16 repo):
**Organization Labels (28)**:
- `Agent/*` (2) - Agent/Human, Agent/Claude
- `Complexity/*` (3) - Simple, Medium, Complex
- `Efforts/*` (5) - XS, S, M, L, XL
- `Priority/*` (4) - Low, Medium, High, Critical
- `Risk/*` (3) - Low, Medium, High
- `Source/*` (4) - Development, Staging, Production, Customer
- `Type/*` (6) - Bug, Feature, Refactor, Documentation, Test, Chore
**Repository Labels (16)**:
- `Component/*` (9) - Backend, Frontend, API, Database, Auth, Deploy, Testing, Docs, Infra
- `Tech/*` (7) - Python, JavaScript, Docker, PostgreSQL, Redis, Vue, FastAPI
Labels are fetched dynamically from Gitea and suggestions adapt to the current taxonomy.
## Security
### Token Storage
- Store tokens in `~/.config/claude/gitea.env`
- Set file permissions to `600` (read/write owner only)
- Never commit tokens to Git
- Use separate tokens for development and production
### Branch Detection
The MCP server implements defense-in-depth branch detection:
1. **MCP Tools**: Check branch before operations
2. **Agent Prompts**: Warn users about branch restrictions
3. **CLAUDE.md**: Provides additional context
### Input Validation
- All user input is validated before API calls
- Issue titles and descriptions are sanitized
- Label names are checked against taxonomy
- Repository names are validated
## Troubleshooting
### Common Issues
**Module not found**:
```bash
cd mcp-servers/gitea
source .venv/bin/activate
```
**Configuration not found**:
```bash
ls -la ~/.config/claude/gitea.env
# If missing, create it following installation steps
```
**Authentication failed**:
```bash
# Test token manually
curl -H "Authorization: token YOUR_TOKEN" \
https://gitea.example.com/api/v1/user
```
**Permission denied on branch**:
```bash
# Check current branch
git branch --show-current
# Switch to development branch
git checkout development
```
See [TESTING.md](./TESTING.md#troubleshooting) for more details.
## Development
### Project Structure
- `config.py` - Hybrid configuration loader with auto-detection
- `gitea_client.py` - Synchronous Gitea API client using requests
- `tools/issues.py` - Issue management with branch detection
- `tools/labels.py` - Label management and intelligent suggestions
- `tools/wiki.py` - Wiki pages and lessons learned
- `tools/milestones.py` - Milestone CRUD operations
- `tools/dependencies.py` - Issue dependency tracking
- `tools/pull_requests.py` - PR review and management
- `server.py` - MCP server with 36 tools over JSON-RPC 2.0 stdio
### Adding New Tools
1. Add method to `GiteaClient` (sync)
2. Add async wrapper to appropriate tool class
3. Register tool in `server.py` `setup_tools()`
4. Add unit tests
5. Update documentation
### Testing Philosophy
- **Unit tests**: Use mocks for fast feedback
- **Integration tests**: Use real Gitea API for validation
- **Branch detection**: Test all branch types
- **Mode detection**: Test both project and company modes
## Performance
### Caching
Labels are cached to reduce API calls:
```python
from functools import lru_cache
@lru_cache(maxsize=128)
def get_labels_cached(self, repo: str):
return self.get_labels(repo)
```
### Retry Logic
API calls include automatic retry with exponential backoff:
```python
@retry_on_failure(max_retries=3, delay=1)
def list_issues(self, state='open', labels=None, repo=None):
# Implementation
```
## Changelog
See [CHANGELOG.md](./CHANGELOG.md) for full version history.
### Recent Updates
- **v1.3.0** - Pull request tools (7 tools), label creation tools (3)
- **v1.2.0** - Milestone management (5 tools), issue dependencies (4 tools)
- **v1.1.0** - Wiki & lessons learned system (7 tools)
- **v1.0.0** - Initial release with core issue/label tools (8 tools)
## License
MIT License - Part of the Leo Claude Marketplace project.
## Related Documentation
- **Projman Documentation**: `plugins/projman/README.md`
- **Configuration Guide**: `plugins/projman/CONFIGURATION.md`
- **Testing Guide**: `TESTING.md`
## Support
For issues or questions:
1. Check [TESTING.md](./TESTING.md) troubleshooting section
2. Review [plugins/projman/README.md](../../README.md) for plugin documentation
3. Create an issue in the project repository
---
**Built for**: Leo Claude Marketplace - Project Management Plugins
**Tools**: 36
**Status**: ✅ Production Ready
**Last Updated**: 2026-02-03

View File

@@ -1,582 +0,0 @@
# Gitea MCP Server - Testing Guide
This document provides comprehensive testing instructions for the Gitea MCP Server implementation.
## Table of Contents
1. [Unit Tests](#unit-tests)
2. [Manual MCP Server Testing](#manual-mcp-server-testing)
3. [Integration Testing](#integration-testing)
4. [Configuration Setup for Testing](#configuration-setup-for-testing)
5. [Troubleshooting](#troubleshooting)
---
## Unit Tests
Unit tests use mocks to test all modules without requiring a real Gitea instance.
### Prerequisites
Ensure the virtual environment is activated and dependencies are installed:
```bash
cd mcp-servers/gitea
source .venv/bin/activate # Linux/Mac
# or .venv\Scripts\activate # Windows
```
### Running All Tests
Run all 64 unit tests:
```bash
pytest tests/ -v
```
Expected output:
```
============================== 64 passed ==============================
```
### Running Specific Test Files
Run tests for a specific module:
```bash
# Configuration tests
pytest tests/test_config.py -v
# Gitea client tests
pytest tests/test_gitea_client.py -v
# Issue tools tests
pytest tests/test_issues.py -v
# Label tools tests
pytest tests/test_labels.py -v
```
### Running Specific Tests
Run a single test:
```bash
pytest tests/test_config.py::test_load_system_config -v
```
### Test Coverage
Generate coverage report:
```bash
pytest --cov=mcp_server --cov-report=html tests/
# View coverage report
# Open htmlcov/index.html in your browser
```
Expected coverage: >80% for all modules
### Test Organization
**Configuration Tests** (`test_config.py`):
- System-level configuration loading
- Project-level configuration override
- Mode detection (project vs company)
- Missing configuration handling
**Gitea Client Tests** (`test_gitea_client.py`):
- API client initialization
- Issue CRUD operations
- Label retrieval
- PMO multi-repo operations
**Issue Tools Tests** (`test_issues.py`):
- Branch-aware security checks
- Async wrappers for sync client
- Permission enforcement
- PMO aggregation mode
**Label Tools Tests** (`test_labels.py`):
- Label retrieval (org + repo)
- Intelligent label suggestion
- Multi-category detection
---
## Manual MCP Server Testing
Test the MCP server manually using stdio communication.
### Step 1: Start the MCP Server
```bash
cd mcp-servers/gitea
source .venv/bin/activate
python -m mcp_server.server
```
The server will start and wait for JSON-RPC 2.0 messages on stdin.
### Step 2: Test Tool Listing
In another terminal, send a tool listing request:
```bash
echo '{"jsonrpc": "2.0", "id": 1, "method": "tools/list"}' | python -m mcp_server.server
```
Expected response:
```json
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"tools": [
{"name": "list_issues", "description": "List issues from Gitea repository", ...},
{"name": "get_issue", "description": "Get specific issue details", ...},
{"name": "create_issue", "description": "Create a new issue in Gitea", ...},
...
]
}
}
```
### Step 3: Test Tool Invocation
**Note:** Manual tool invocation requires proper configuration. See [Configuration Setup](#configuration-setup-for-testing).
Example: List issues
```bash
echo '{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "list_issues",
"arguments": {
"state": "open"
}
}
}' | python -m mcp_server.server
```
---
## Integration Testing
Test the MCP server with a real Gitea instance.
### Prerequisites
1. **Gitea Instance**: Access to https://gitea.example.com (or your Gitea instance)
2. **API Token**: Personal access token with required permissions
3. **Configuration**: Properly configured system and project configs
### Step 1: Configuration Setup
Create system-level configuration:
```bash
mkdir -p ~/.config/claude
cat > ~/.config/claude/gitea.env << EOF
GITEA_API_URL=https://gitea.example.com/api/v1
GITEA_API_TOKEN=your_gitea_token_here
GITEA_OWNER=bandit
EOF
chmod 600 ~/.config/claude/gitea.env
```
Create project-level configuration (for project mode testing):
```bash
cd /path/to/test/project
cat > .env << EOF
GITEA_REPO=test-repo
EOF
# Add to .gitignore
echo ".env" >> .gitignore
```
### Step 2: Generate Gitea API Token
1. Log into Gitea: https://gitea.example.com
2. Navigate to: **Settings****Applications****Manage Access Tokens**
3. Click **Generate New Token**
4. Token configuration:
- **Token Name:** `mcp-integration-test`
- **Required Permissions:**
-`repo` (all) - Read/write access to repositories, issues, labels
-`read:org` - Read organization information and labels
-`read:user` - Read user information
5. Click **Generate Token**
6. Copy the token immediately (shown only once)
7. Add to `~/.config/claude/gitea.env`
### Step 3: Verify Configuration
Test configuration loading:
```bash
cd mcp-servers/gitea
source .venv/bin/activate
python -c "
from mcp_server.config import GiteaConfig
config = GiteaConfig()
result = config.load()
print(f'API URL: {result[\"api_url\"]}')
print(f'Owner: {result[\"owner\"]}')
print(f'Repo: {result[\"repo\"]}')
print(f'Mode: {result[\"mode\"]}')
"
```
Expected output:
```
API URL: https://gitea.example.com/api/v1
Owner: bandit
Repo: test-repo (or None for company mode)
Mode: project (or company)
```
### Step 4: Test Gitea Client
Test basic Gitea API operations:
```bash
python -c "
from mcp_server.gitea_client import GiteaClient
client = GiteaClient()
# Test listing issues
print('Testing list_issues...')
issues = client.list_issues(state='open')
print(f'Found {len(issues)} open issues')
# Test getting labels
print('\\nTesting get_labels...')
labels = client.get_labels()
print(f'Found {len(labels)} repository labels')
# Test getting org labels
print('\\nTesting get_org_labels...')
org_labels = client.get_org_labels()
print(f'Found {len(org_labels)} organization labels')
print('\\n✅ All integration tests passed!')
"
```
### Step 5: Test Issue Creation (Optional)
**Warning:** This creates a real issue in Gitea. Use a test repository.
```bash
python -c "
from mcp_server.gitea_client import GiteaClient
client = GiteaClient()
# Create test issue
print('Creating test issue...')
issue = client.create_issue(
title='[TEST] MCP Server Integration Test',
body='This is a test issue created by the Gitea MCP Server integration tests.',
labels=['Type/Test']
)
print(f'Created issue #{issue[\"number\"]}: {issue[\"title\"]}')
# Clean up: Close the issue
print('\\nClosing test issue...')
client.update_issue(issue['number'], state='closed')
print('✅ Test issue closed')
"
```
### Step 6: Test MCP Server with Real API
Start the MCP server and test with real Gitea API:
```bash
cd mcp-servers/gitea
source .venv/bin/activate
# Run server with test script
python << 'EOF'
import asyncio
import json
from mcp_server.server import GiteaMCPServer
async def test_server():
server = GiteaMCPServer()
await server.initialize()
# Test list_issues
result = await server.issue_tools.list_issues(state='open')
print(f'Found {len(result)} open issues')
# Test get_labels
labels = await server.label_tools.get_labels()
print(f'Found {labels["total_count"]} total labels')
# Test suggest_labels
suggestions = await server.label_tools.suggest_labels(
"Fix critical bug in authentication"
)
print(f'Suggested labels: {", ".join(suggestions)}')
print('✅ All MCP server integration tests passed!')
asyncio.run(test_server())
EOF
```
### Step 7: Test PMO Mode (Optional)
Test company-wide mode (no GITEA_REPO):
```bash
# Temporarily remove GITEA_REPO
unset GITEA_REPO
python -c "
from mcp_server.gitea_client import GiteaClient
client = GiteaClient()
print(f'Running in {client.mode} mode')
# Test list_repos
print('\\nTesting list_repos...')
repos = client.list_repos()
print(f'Found {len(repos)} repositories')
# Test aggregate_issues
print('\\nTesting aggregate_issues...')
aggregated = client.aggregate_issues(state='open')
for repo_name, issues in aggregated.items():
print(f' {repo_name}: {len(issues)} open issues')
print('\\n✅ PMO mode tests passed!')
"
```
---
## Configuration Setup for Testing
### Minimal Configuration
**System-level** (`~/.config/claude/gitea.env`):
```bash
GITEA_API_URL=https://gitea.example.com/api/v1
GITEA_API_TOKEN=your_token_here
GITEA_OWNER=bandit
```
**Project-level** (`.env` in project root):
```bash
# For project mode
GITEA_REPO=test-repo
# For company mode (PMO), omit GITEA_REPO
```
### Verification
Verify configuration is correct:
```bash
# Check system config exists
ls -la ~/.config/claude/gitea.env
# Check permissions (should be 600)
stat -c "%a %n" ~/.config/claude/gitea.env
# Check content (without exposing token)
grep -v TOKEN ~/.config/claude/gitea.env
# Check project config (if using project mode)
cat .env
```
---
## Troubleshooting
### Common Issues
#### 1. Import Errors
**Error:**
```
ModuleNotFoundError: No module named 'mcp_server'
```
**Solution:**
```bash
# Ensure you're in the correct directory
cd mcp-servers/gitea
# Activate virtual environment
source .venv/bin/activate
# Verify installation
pip list | grep mcp
```
#### 2. Configuration Not Found
**Error:**
```
FileNotFoundError: System config not found: /home/user/.config/claude/gitea.env
```
**Solution:**
```bash
# Create system config
mkdir -p ~/.config/claude
cat > ~/.config/claude/gitea.env << EOF
GITEA_API_URL=https://gitea.example.com/api/v1
GITEA_API_TOKEN=your_token_here
GITEA_OWNER=bandit
EOF
chmod 600 ~/.config/claude/gitea.env
```
#### 3. Missing Required Configuration
**Error:**
```
ValueError: Missing required configuration: GITEA_API_TOKEN, GITEA_OWNER
```
**Solution:**
```bash
# Check configuration file
cat ~/.config/claude/gitea.env
# Ensure all required variables are present:
# - GITEA_API_URL
# - GITEA_API_TOKEN
# - GITEA_OWNER
```
#### 4. API Authentication Failed
**Error:**
```
requests.exceptions.HTTPError: 401 Client Error: Unauthorized
```
**Solution:**
```bash
# Test token manually
curl -H "Authorization: token YOUR_TOKEN" \
https://gitea.example.com/api/v1/user
# If fails, regenerate token in Gitea settings
```
#### 5. Permission Errors (Branch Detection)
**Error:**
```
PermissionError: Cannot create issues on branch 'main'
```
**Solution:**
```bash
# Check current branch
git branch --show-current
# Switch to development branch
git checkout development
# or
git checkout -b feat/test-feature
```
#### 6. Repository Not Specified
**Error:**
```
ValueError: Repository not specified
```
**Solution:**
```bash
# Add GITEA_REPO to project config
echo "GITEA_REPO=your-repo-name" >> .env
# Or specify repo in tool call
# (for PMO mode multi-repo operations)
```
### Debug Mode
Enable debug logging:
```bash
export LOG_LEVEL=DEBUG
python -m mcp_server.server
```
### Test Summary
After completing all tests, verify:
- ✅ All 64 unit tests pass
- ✅ MCP server starts without errors
- ✅ Configuration loads correctly
- ✅ Gitea API client connects successfully
- ✅ Issues can be listed from Gitea
- ✅ Labels can be retrieved
- ✅ Label suggestions work correctly
- ✅ Branch detection blocks writes on main/staging
- ✅ Mode detection works (project vs company)
---
## Success Criteria
Phase 1 is complete when:
1. **All unit tests pass** (64/64)
2. **MCP server starts without errors**
3. **Can list issues from Gitea**
4. **Can create issues with labels** (in development mode)
5. **Mode detection works** (project vs company)
6. **Branch detection prevents writes on main/staging**
7. **Configuration properly merges** system + project levels
---
## Next Steps
After completing testing:
1. **Document any issues** found during testing
2. **Create integration with projman plugin** (Phase 2)
3. **Test in real project workflow** (Phase 5)
4. **Performance optimization** (if needed)
5. **Production hardening** (Phase 8)
---
## Additional Resources
- **MCP Documentation**: https://docs.anthropic.com/claude/docs/mcp
- **Gitea API Documentation**: https://docs.gitea.io/en-us/api-usage/
- **Projman Documentation**: `plugins/projman/README.md`
- **Configuration Guide**: `plugins/projman/CONFIGURATION.md`
---
**Last Updated**: 2025-01-06 (Phase 1 Implementation)

View File

@@ -1,30 +0,0 @@
"""
Gitea MCP Server package.
Provides MCP tools for Gitea integration via JSON-RPC 2.0.
For external consumers (e.g., HTTP transport), use:
from mcp_server import get_tool_definitions, create_tool_dispatcher, GiteaClient
# Get tool schemas
tools = get_tool_definitions()
# Create dispatcher bound to a client
client = GiteaClient()
dispatch = create_tool_dispatcher(client)
result = await dispatch("list_issues", {"state": "open"})
"""
__version__ = "1.0.0"
from .tool_registry import get_tool_definitions, create_tool_dispatcher
from .gitea_client import GiteaClient
from .config import GiteaConfig
__all__ = [
"__version__",
"get_tool_definitions",
"create_tool_dispatcher",
"GiteaClient",
"GiteaConfig",
]

View File

@@ -1,227 +0,0 @@
"""
Configuration loader for Gitea MCP Server.
Implements hybrid configuration system:
- System-level: ~/.config/claude/gitea.env (credentials)
- Project-level: .env (repository specification)
- Auto-detection: Falls back to git remote URL parsing
"""
from pathlib import Path
from dotenv import load_dotenv
import os
import re
import subprocess
import logging
from typing import Dict, Optional
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class GiteaConfig:
"""Hybrid configuration loader with mode detection"""
def __init__(self):
self.api_url: Optional[str] = None
self.api_token: Optional[str] = None
self.repo: Optional[str] = None
self.mode: str = 'project'
def load(self) -> Dict[str, Optional[str]]:
"""
Load configuration from system and project levels.
Project-level configuration overrides system-level.
Returns:
Dict containing api_url, api_token, repo, mode
Raises:
FileNotFoundError: If system config is missing
ValueError: If required configuration is missing
"""
# Load system config
system_config = Path.home() / '.config' / 'claude' / 'gitea.env'
if system_config.exists():
load_dotenv(system_config)
logger.info(f"Loaded system configuration from {system_config}")
else:
raise FileNotFoundError(
f"System config not found: {system_config}\n"
"Create it with: mkdir -p ~/.config/claude && "
"cat > ~/.config/claude/gitea.env"
)
# Find project directory (MCP server cwd is plugin dir, not project dir)
project_dir = self._find_project_directory()
# Load project config (overrides system)
if project_dir:
project_config = project_dir / '.env'
if project_config.exists():
load_dotenv(project_config, override=True)
logger.info(f"Loaded project configuration from {project_config}")
# Extract values
self.api_url = os.getenv('GITEA_API_URL')
self.api_token = os.getenv('GITEA_API_TOKEN')
self.repo = os.getenv('GITEA_REPO') # Optional, must be owner/repo format
# Auto-detect repo from git remote if not specified
if not self.repo and project_dir:
self.repo = self._detect_repo_from_git(project_dir)
if self.repo:
logger.info(f"Auto-detected repository from git remote: {self.repo}")
# Detect mode
if self.repo:
self.mode = 'project'
logger.info(f"Running in project mode: {self.repo}")
else:
self.mode = 'company'
logger.info("Running in company-wide mode (PMO)")
# Validate required variables
self._validate()
return {
'api_url': self.api_url,
'api_token': self.api_token,
'repo': self.repo,
'mode': self.mode
}
def _validate(self) -> None:
"""
Validate that required configuration is present.
Raises:
ValueError: If required configuration is missing
"""
required = {
'GITEA_API_URL': self.api_url,
'GITEA_API_TOKEN': self.api_token
}
missing = [key for key, value in required.items() if not value]
if missing:
raise ValueError(
f"Missing required configuration: {', '.join(missing)}\n"
"Check your ~/.config/claude/gitea.env file"
)
def _find_project_directory(self) -> Optional[Path]:
"""
Find the user's project directory.
The MCP server runs with cwd set to the plugin directory, not the
user's project. We need to find the actual project directory using
various heuristics.
Returns:
Path to project directory, or None if not found
"""
# Strategy 1: Check CLAUDE_PROJECT_DIR environment variable
project_dir = os.getenv('CLAUDE_PROJECT_DIR')
if project_dir:
path = Path(project_dir)
if path.exists():
logger.info(f"Found project directory from CLAUDE_PROJECT_DIR: {path}")
return path
# Strategy 2: Check PWD (original working directory before cwd override)
pwd = os.getenv('PWD')
if pwd:
path = Path(pwd)
# Verify it has .git or .env (indicates a project)
if path.exists() and ((path / '.git').exists() or (path / '.env').exists()):
logger.info(f"Found project directory from PWD: {path}")
return path
# Strategy 3: Check current working directory
# This handles test scenarios and cases where cwd is actually the project
cwd = Path.cwd()
if (cwd / '.git').exists() or (cwd / '.env').exists():
logger.info(f"Found project directory from cwd: {cwd}")
return cwd
# Strategy 4: Check if GITEA_REPO is already set (user configured it)
# If so, we don't need to find the project directory for git detection
if os.getenv('GITEA_REPO'):
logger.debug("GITEA_REPO already set, skipping project directory detection")
return None
logger.debug("Could not determine project directory")
return None
def _detect_repo_from_git(self, project_dir: Optional[Path] = None) -> Optional[str]:
"""
Auto-detect repository from git remote origin URL.
Args:
project_dir: Directory to run git command from (defaults to cwd)
Supports URL formats:
- SSH: ssh://git@host:port/owner/repo.git
- SSH short: git@host:owner/repo.git
- HTTPS: https://host/owner/repo.git
- HTTP: http://host/owner/repo.git
Returns:
Repository in 'owner/repo' format, or None if detection fails
"""
try:
result = subprocess.run(
['git', 'remote', 'get-url', 'origin'],
capture_output=True,
text=True,
timeout=5,
cwd=str(project_dir) if project_dir else None
)
if result.returncode != 0:
logger.debug("No git remote 'origin' found")
return None
url = result.stdout.strip()
return self._parse_git_url(url)
except subprocess.TimeoutExpired:
logger.warning("Git command timed out")
return None
except FileNotFoundError:
logger.debug("Git not available")
return None
except Exception as e:
logger.debug(f"Failed to detect repo from git: {e}")
return None
def _parse_git_url(self, url: str) -> Optional[str]:
"""
Parse git URL to extract owner/repo.
Args:
url: Git remote URL
Returns:
Repository in 'owner/repo' format, or None if parsing fails
"""
# Remove .git suffix if present
url = re.sub(r'\.git$', '', url)
# SSH format: ssh://git@host:port/owner/repo
ssh_match = re.match(r'ssh://[^/]+/(.+/.+)$', url)
if ssh_match:
return ssh_match.group(1)
# SSH short format: git@host:owner/repo
ssh_short_match = re.match(r'git@[^:]+:(.+/.+)$', url)
if ssh_short_match:
return ssh_short_match.group(1)
# HTTPS/HTTP format: https://host/owner/repo
http_match = re.match(r'https?://[^/]+/(.+/.+)$', url)
if http_match:
return http_match.group(1)
logger.warning(f"Could not parse git URL: {url}")
return None

View File

@@ -1,849 +0,0 @@
"""
Gitea API client for interacting with Gitea API.
Provides synchronous methods for:
- Issue CRUD operations
- Label management
- Repository operations
- PMO multi-repo aggregation
- Wiki operations (lessons learned)
- Milestone management
- Issue dependencies
"""
import requests
import logging
import re
from typing import List, Dict, Optional
from .config import GiteaConfig
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class GiteaClient:
"""Client for interacting with Gitea API"""
def __init__(self):
"""Initialize Gitea client with configuration"""
config = GiteaConfig()
config_dict = config.load()
self.base_url = config_dict['api_url']
self.token = config_dict['api_token']
self.repo = config_dict.get('repo') # Optional default repo in owner/repo format
self.mode = config_dict['mode']
self.session = requests.Session()
self.session.headers.update({
'Authorization': f'token {self.token}',
'Content-Type': 'application/json'
})
logger.info(f"Gitea client initialized in {self.mode} mode")
def _parse_repo(self, repo: Optional[str] = None) -> tuple:
"""Parse owner/repo from input. Always requires 'owner/repo' format."""
target = repo or self.repo
if not target or '/' not in target:
raise ValueError("Use 'owner/repo' format (e.g. 'org/repo-name')")
parts = target.split('/', 1)
return parts[0], parts[1]
def list_issues(
self,
state: str = 'open',
labels: Optional[List[str]] = None,
milestone: Optional[str] = None,
repo: Optional[str] = None
) -> List[Dict]:
"""
List issues from Gitea repository.
Args:
state: Issue state (open, closed, all)
labels: Filter by labels
milestone: Filter by milestone title (exact match)
repo: Repository in 'owner/repo' format
Returns:
List of issue dictionaries
"""
owner, target_repo = self._parse_repo(repo)
url = f"{self.base_url}/repos/{owner}/{target_repo}/issues"
params = {'state': state}
if labels:
params['labels'] = ','.join(labels)
if milestone:
params['milestones'] = milestone
logger.info(f"Listing issues from {owner}/{target_repo} with state={state}")
response = self.session.get(url, params=params)
response.raise_for_status()
return response.json()
def get_issue(
self,
issue_number: int,
repo: Optional[str] = None
) -> Dict:
"""Get specific issue details."""
owner, target_repo = self._parse_repo(repo)
url = f"{self.base_url}/repos/{owner}/{target_repo}/issues/{issue_number}"
logger.info(f"Getting issue #{issue_number} from {owner}/{target_repo}")
response = self.session.get(url)
response.raise_for_status()
return response.json()
def create_issue(
self,
title: str,
body: str,
labels: Optional[List[str]] = None,
repo: Optional[str] = None
) -> Dict:
"""Create a new issue in Gitea."""
owner, target_repo = self._parse_repo(repo)
url = f"{self.base_url}/repos/{owner}/{target_repo}/issues"
data = {'title': title, 'body': body}
if labels:
label_ids = self._resolve_label_ids(labels, owner, target_repo)
data['labels'] = label_ids
logger.info(f"Creating issue in {owner}/{target_repo}: {title}")
response = self.session.post(url, json=data)
response.raise_for_status()
return response.json()
def _resolve_label_ids(self, label_names: List[str], owner: str, repo: str) -> List[int]:
"""Convert label names to label IDs."""
full_repo = f"{owner}/{repo}"
# Only fetch org labels if repo belongs to an organization
org_labels = []
if self.is_org_repo(full_repo):
org_labels = self.get_org_labels(owner)
repo_labels = self.get_labels(full_repo)
all_labels = org_labels + repo_labels
label_map = {label['name']: label['id'] for label in all_labels}
label_ids = []
for name in label_names:
if name in label_map:
label_ids.append(label_map[name])
else:
logger.warning(f"Label '{name}' not found, skipping")
return label_ids
def update_issue(
self,
issue_number: int,
title: Optional[str] = None,
body: Optional[str] = None,
state: Optional[str] = None,
labels: Optional[List[str]] = None,
milestone: Optional[int] = None,
repo: Optional[str] = None
) -> Dict:
"""
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)
url = f"{self.base_url}/repos/{owner}/{target_repo}/issues/{issue_number}"
data = {}
if title is not None:
data['title'] = title
if body is not None:
data['body'] = body
if state is not None:
data['state'] = state
if labels is not None:
data['labels'] = labels
if milestone is not None:
data['milestone'] = milestone
logger.info(f"Updating issue #{issue_number} in {owner}/{target_repo}")
response = self.session.patch(url, json=data)
response.raise_for_status()
return response.json()
def add_comment(
self,
issue_number: int,
comment: str,
repo: Optional[str] = None
) -> Dict:
"""Add comment to issue. Repo must be 'owner/repo' format."""
owner, target_repo = self._parse_repo(repo)
url = f"{self.base_url}/repos/{owner}/{target_repo}/issues/{issue_number}/comments"
data = {'body': comment}
logger.info(f"Adding comment to issue #{issue_number} in {owner}/{target_repo}")
response = self.session.post(url, json=data)
response.raise_for_status()
return response.json()
def get_labels(self, repo: Optional[str] = None) -> List[Dict]:
"""Get all labels from repository. Repo must be 'owner/repo' format."""
owner, target_repo = self._parse_repo(repo)
url = f"{self.base_url}/repos/{owner}/{target_repo}/labels"
logger.info(f"Getting labels from {owner}/{target_repo}")
response = self.session.get(url)
response.raise_for_status()
return response.json()
def get_org_labels(self, org: str) -> List[Dict]:
"""Get organization-level labels. Org is the organization name."""
url = f"{self.base_url}/orgs/{org}/labels"
logger.info(f"Getting organization labels for {org}")
response = self.session.get(url)
response.raise_for_status()
return response.json()
def list_repos(self, org: str) -> List[Dict]:
"""List all repositories in organization. Org is the organization name."""
url = f"{self.base_url}/orgs/{org}/repos"
logger.info(f"Listing all repositories for organization {org}")
response = self.session.get(url)
response.raise_for_status()
return response.json()
def aggregate_issues(
self,
org: str,
state: str = 'open',
labels: Optional[List[str]] = None
) -> Dict[str, List[Dict]]:
"""Fetch issues across all repositories in org."""
repos = self.list_repos(org)
aggregated = {}
logger.info(f"Aggregating issues across {len(repos)} repositories")
for repo in repos:
repo_name = repo['name']
try:
issues = self.list_issues(
state=state,
labels=labels,
repo=f"{org}/{repo_name}"
)
if issues:
aggregated[repo_name] = issues
logger.info(f"Found {len(issues)} issues in {repo_name}")
except Exception as e:
logger.error(f"Error fetching issues from {repo_name}: {e}")
return aggregated
# ========================================
# WIKI OPERATIONS (Lessons Learned)
# ========================================
def list_wiki_pages(self, repo: Optional[str] = None) -> List[Dict]:
"""List all wiki pages in repository."""
owner, target_repo = self._parse_repo(repo)
url = f"{self.base_url}/repos/{owner}/{target_repo}/wiki/pages"
logger.info(f"Listing wiki pages from {owner}/{target_repo}")
response = self.session.get(url)
response.raise_for_status()
return response.json()
def get_wiki_page(
self,
page_name: str,
repo: Optional[str] = None
) -> Dict:
"""Get a specific wiki page by name."""
from urllib.parse import quote
owner, target_repo = self._parse_repo(repo)
# URL-encode the page_name to handle special characters like ':'
encoded_page_name = quote(page_name, safe='')
url = f"{self.base_url}/repos/{owner}/{target_repo}/wiki/page/{encoded_page_name}"
logger.info(f"Getting wiki page '{page_name}' from {owner}/{target_repo}")
response = self.session.get(url)
response.raise_for_status()
return response.json()
def create_wiki_page(
self,
title: str,
content: str,
repo: Optional[str] = None
) -> Dict:
"""Create a new wiki page."""
owner, target_repo = self._parse_repo(repo)
url = f"{self.base_url}/repos/{owner}/{target_repo}/wiki/new"
data = {
'title': title,
'content_base64': self._encode_base64(content)
}
logger.info(f"Creating wiki page '{title}' in {owner}/{target_repo}")
response = self.session.post(url, json=data)
response.raise_for_status()
return response.json()
def update_wiki_page(
self,
page_name: str,
content: str,
repo: Optional[str] = None
) -> Dict:
"""Update an existing wiki page."""
from urllib.parse import quote
owner, target_repo = self._parse_repo(repo)
# URL-encode the page_name to handle special characters like ':'
encoded_page_name = quote(page_name, safe='')
url = f"{self.base_url}/repos/{owner}/{target_repo}/wiki/page/{encoded_page_name}"
data = {
'title': page_name, # CRITICAL: include title to preserve page name
'content_base64': self._encode_base64(content)
}
logger.info(f"Updating wiki page '{page_name}' in {owner}/{target_repo}")
response = self.session.patch(url, json=data)
response.raise_for_status()
return response.json()
def delete_wiki_page(
self,
page_name: str,
repo: Optional[str] = None
) -> bool:
"""Delete a wiki page."""
from urllib.parse import quote
owner, target_repo = self._parse_repo(repo)
# URL-encode the page_name to handle special characters like ':'
encoded_page_name = quote(page_name, safe='')
url = f"{self.base_url}/repos/{owner}/{target_repo}/wiki/page/{encoded_page_name}"
logger.info(f"Deleting wiki page '{page_name}' from {owner}/{target_repo}")
response = self.session.delete(url)
response.raise_for_status()
return True
def _encode_base64(self, content: str) -> str:
"""Encode content to base64 for wiki API."""
import base64
return base64.b64encode(content.encode('utf-8')).decode('utf-8')
def _decode_base64(self, content: str) -> str:
"""Decode base64 content from wiki API."""
import base64
return base64.b64decode(content.encode('utf-8')).decode('utf-8')
def search_wiki_pages(
self,
query: str,
repo: Optional[str] = None
) -> List[Dict]:
"""Search wiki pages by content (client-side filtering)."""
pages = self.list_wiki_pages(repo)
results = []
query_lower = query.lower()
for page in pages:
if query_lower in page.get('title', '').lower():
results.append(page)
return results
def create_lesson(
self,
title: str,
content: str,
tags: List[str],
category: str = "sprints",
repo: Optional[str] = None
) -> Dict:
"""Create a lessons learned entry in the wiki."""
# Sanitize title for wiki page name
page_name = f"lessons/{category}/{self._sanitize_page_name(title)}"
# Add tags as metadata at the end of content
full_content = f"{content}\n\n---\n**Tags:** {', '.join(tags)}"
return self.create_wiki_page(page_name, full_content, repo)
def search_lessons(
self,
query: Optional[str] = None,
tags: Optional[List[str]] = None,
repo: Optional[str] = None
) -> List[Dict]:
"""Search lessons learned by query and/or tags."""
pages = self.list_wiki_pages(repo)
results = []
for page in pages:
title = page.get('title', '')
# Filter to only lessons (pages starting with lessons/)
if not title.startswith('lessons/'):
continue
# If query provided, check if it matches title
if query:
if query.lower() not in title.lower():
continue
# Get full page content for tag matching if tags provided
if tags:
try:
full_page = self.get_wiki_page(title, repo)
content = self._decode_base64(full_page.get('content_base64', ''))
# Check if any tag is in the content
if not any(tag.lower() in content.lower() for tag in tags):
continue
except Exception:
continue
results.append(page)
return results
def _sanitize_page_name(self, title: str) -> str:
"""Convert title to valid wiki page name."""
# Replace spaces with hyphens, remove special chars
name = re.sub(r'[^\w\s-]', '', title)
name = re.sub(r'[\s]+', '-', name)
return name.lower()
# ========================================
# MILESTONE OPERATIONS
# ========================================
def list_milestones(
self,
state: str = 'open',
repo: Optional[str] = None
) -> List[Dict]:
"""List all milestones in repository."""
owner, target_repo = self._parse_repo(repo)
url = f"{self.base_url}/repos/{owner}/{target_repo}/milestones"
params = {'state': state}
logger.info(f"Listing milestones from {owner}/{target_repo}")
response = self.session.get(url, params=params)
response.raise_for_status()
return response.json()
def get_milestone(
self,
milestone_id: int,
repo: Optional[str] = None
) -> Dict:
"""Get a specific milestone by ID."""
owner, target_repo = self._parse_repo(repo)
url = f"{self.base_url}/repos/{owner}/{target_repo}/milestones/{milestone_id}"
logger.info(f"Getting milestone #{milestone_id} from {owner}/{target_repo}")
response = self.session.get(url)
response.raise_for_status()
return response.json()
def create_milestone(
self,
title: str,
description: Optional[str] = None,
due_on: Optional[str] = None,
repo: Optional[str] = None
) -> Dict:
"""Create a new milestone."""
owner, target_repo = self._parse_repo(repo)
url = f"{self.base_url}/repos/{owner}/{target_repo}/milestones"
data = {'title': title}
if description:
data['description'] = description
if due_on:
data['due_on'] = due_on
logger.info(f"Creating milestone '{title}' in {owner}/{target_repo}")
response = self.session.post(url, json=data)
response.raise_for_status()
return response.json()
def update_milestone(
self,
milestone_id: int,
title: Optional[str] = None,
description: Optional[str] = None,
state: Optional[str] = None,
due_on: Optional[str] = None,
repo: Optional[str] = None
) -> Dict:
"""Update an existing milestone."""
owner, target_repo = self._parse_repo(repo)
url = f"{self.base_url}/repos/{owner}/{target_repo}/milestones/{milestone_id}"
data = {}
if title is not None:
data['title'] = title
if description is not None:
data['description'] = description
if state is not None:
data['state'] = state
if due_on is not None:
data['due_on'] = due_on
logger.info(f"Updating milestone #{milestone_id} in {owner}/{target_repo}")
response = self.session.patch(url, json=data)
response.raise_for_status()
return response.json()
def delete_milestone(
self,
milestone_id: int,
repo: Optional[str] = None
) -> bool:
"""Delete a milestone."""
owner, target_repo = self._parse_repo(repo)
url = f"{self.base_url}/repos/{owner}/{target_repo}/milestones/{milestone_id}"
logger.info(f"Deleting milestone #{milestone_id} from {owner}/{target_repo}")
response = self.session.delete(url)
response.raise_for_status()
return True
# ========================================
# ISSUE DEPENDENCY OPERATIONS
# ========================================
def list_issue_dependencies(
self,
issue_number: int,
repo: Optional[str] = None
) -> List[Dict]:
"""List all dependencies for an issue (issues that block this one)."""
owner, target_repo = self._parse_repo(repo)
url = f"{self.base_url}/repos/{owner}/{target_repo}/issues/{issue_number}/dependencies"
logger.info(f"Listing dependencies for issue #{issue_number} in {owner}/{target_repo}")
response = self.session.get(url)
response.raise_for_status()
return response.json()
def create_issue_dependency(
self,
issue_number: int,
depends_on: int,
repo: Optional[str] = None
) -> Dict:
"""Create a dependency (issue_number depends on depends_on)."""
owner, target_repo = self._parse_repo(repo)
url = f"{self.base_url}/repos/{owner}/{target_repo}/issues/{issue_number}/dependencies"
data = {
'dependentIssue': {
'owner': owner,
'repo': target_repo,
'index': depends_on
}
}
logger.info(f"Creating dependency: #{issue_number} depends on #{depends_on} in {owner}/{target_repo}")
response = self.session.post(url, json=data)
response.raise_for_status()
return response.json()
def remove_issue_dependency(
self,
issue_number: int,
depends_on: int,
repo: Optional[str] = None
) -> bool:
"""Remove a dependency between issues."""
owner, target_repo = self._parse_repo(repo)
url = f"{self.base_url}/repos/{owner}/{target_repo}/issues/{issue_number}/dependencies"
data = {
'dependentIssue': {
'owner': owner,
'repo': target_repo,
'index': depends_on
}
}
logger.info(f"Removing dependency: #{issue_number} no longer depends on #{depends_on}")
response = self.session.delete(url, json=data)
response.raise_for_status()
return True
def list_issue_blocks(
self,
issue_number: int,
repo: Optional[str] = None
) -> List[Dict]:
"""List all issues that this issue blocks."""
owner, target_repo = self._parse_repo(repo)
url = f"{self.base_url}/repos/{owner}/{target_repo}/issues/{issue_number}/blocks"
logger.info(f"Listing issues blocked by #{issue_number} in {owner}/{target_repo}")
response = self.session.get(url)
response.raise_for_status()
return response.json()
# ========================================
# REPOSITORY VALIDATION
# ========================================
def get_repo_info(self, repo: Optional[str] = None) -> Dict:
"""Get repository information including owner type."""
owner, target_repo = self._parse_repo(repo)
url = f"{self.base_url}/repos/{owner}/{target_repo}"
logger.info(f"Getting repo info for {owner}/{target_repo}")
response = self.session.get(url)
response.raise_for_status()
return response.json()
def is_org_repo(self, repo: Optional[str] = None) -> bool:
"""
Check if repository belongs to an organization (not a user).
Uses the /orgs/{owner} endpoint to reliably detect organizations,
as the owner.type field in repo info may be null in some Gitea versions.
"""
owner, _ = self._parse_repo(repo)
return self._is_organization(owner)
def _is_organization(self, owner: str) -> bool:
"""
Check if an owner is an organization by querying the orgs endpoint.
Args:
owner: The owner name to check
Returns:
True if owner is an organization, False if user or unknown
"""
url = f"{self.base_url}/orgs/{owner}"
try:
response = self.session.get(url)
# 200 = organization exists, 404 = not an organization (user account)
return response.status_code == 200
except Exception as e:
logger.warning(f"Failed to check if {owner} is organization: {e}")
return False
def get_branch_protection(
self,
branch: str,
repo: Optional[str] = None
) -> Optional[Dict]:
"""Get branch protection rules for a branch."""
owner, target_repo = self._parse_repo(repo)
url = f"{self.base_url}/repos/{owner}/{target_repo}/branch_protections/{branch}"
logger.info(f"Getting branch protection for {branch} in {owner}/{target_repo}")
try:
response = self.session.get(url)
response.raise_for_status()
return response.json()
except requests.exceptions.HTTPError as e:
if e.response.status_code == 404:
return None # No protection rules
raise
def create_label(
self,
name: str,
color: str,
description: Optional[str] = None,
repo: Optional[str] = None
) -> Dict:
"""Create a new label in the repository."""
owner, target_repo = self._parse_repo(repo)
url = f"{self.base_url}/repos/{owner}/{target_repo}/labels"
data = {
'name': name,
'color': color.lstrip('#') # Remove # if present
}
if description:
data['description'] = description
logger.info(f"Creating label '{name}' in {owner}/{target_repo}")
response = self.session.post(url, json=data)
response.raise_for_status()
return response.json()
def create_org_label(
self,
org: str,
name: str,
color: str,
description: Optional[str] = None
) -> Dict:
"""
Create a new label at the organization level.
Organization labels are shared across all repositories in the org.
Use this for workflow labels (Type, Priority, Complexity, Effort, etc.)
Args:
org: Organization name
name: Label name (e.g., 'Type/Bug', 'Priority/High')
color: Hex color code (with or without #)
description: Optional label description
Returns:
Created label dictionary
"""
url = f"{self.base_url}/orgs/{org}/labels"
data = {
'name': name,
'color': color.lstrip('#') # Remove # if present
}
if description:
data['description'] = description
logger.info(f"Creating organization label '{name}' in {org}")
response = self.session.post(url, json=data)
response.raise_for_status()
return response.json()
# ========================================
# PULL REQUEST OPERATIONS
# ========================================
def list_pull_requests(
self,
state: str = 'open',
sort: str = 'recentupdate',
labels: Optional[List[str]] = None,
repo: Optional[str] = None
) -> List[Dict]:
"""
List pull requests from Gitea repository.
Args:
state: PR state (open, closed, all)
sort: Sort order (oldest, recentupdate, leastupdate, mostcomment, leastcomment, priority)
labels: Filter by labels
repo: Repository in 'owner/repo' format
Returns:
List of pull request dictionaries
"""
owner, target_repo = self._parse_repo(repo)
url = f"{self.base_url}/repos/{owner}/{target_repo}/pulls"
params = {'state': state, 'sort': sort}
if labels:
params['labels'] = ','.join(labels)
logger.info(f"Listing PRs from {owner}/{target_repo} with state={state}")
response = self.session.get(url, params=params)
response.raise_for_status()
return response.json()
def get_pull_request(
self,
pr_number: int,
repo: Optional[str] = None
) -> Dict:
"""Get specific pull request details."""
owner, target_repo = self._parse_repo(repo)
url = f"{self.base_url}/repos/{owner}/{target_repo}/pulls/{pr_number}"
logger.info(f"Getting PR #{pr_number} from {owner}/{target_repo}")
response = self.session.get(url)
response.raise_for_status()
return response.json()
def get_pr_diff(
self,
pr_number: int,
repo: Optional[str] = None
) -> str:
"""Get the diff for a pull request."""
owner, target_repo = self._parse_repo(repo)
url = f"{self.base_url}/repos/{owner}/{target_repo}/pulls/{pr_number}.diff"
logger.info(f"Getting diff for PR #{pr_number} from {owner}/{target_repo}")
response = self.session.get(url)
response.raise_for_status()
return response.text
def get_pr_comments(
self,
pr_number: int,
repo: Optional[str] = None
) -> List[Dict]:
"""Get comments on a pull request (uses issue comments endpoint)."""
owner, target_repo = self._parse_repo(repo)
# PRs share comment endpoint with issues in Gitea
url = f"{self.base_url}/repos/{owner}/{target_repo}/issues/{pr_number}/comments"
logger.info(f"Getting comments for PR #{pr_number} from {owner}/{target_repo}")
response = self.session.get(url)
response.raise_for_status()
return response.json()
def create_pr_review(
self,
pr_number: int,
body: str,
event: str = 'COMMENT',
comments: Optional[List[Dict]] = None,
repo: Optional[str] = None
) -> Dict:
"""
Create a review on a pull request.
Args:
pr_number: Pull request number
body: Review body/summary
event: Review action (APPROVE, REQUEST_CHANGES, COMMENT)
comments: Optional list of inline comments with path, position, body
repo: Repository in 'owner/repo' format
Returns:
Created review dictionary
"""
owner, target_repo = self._parse_repo(repo)
url = f"{self.base_url}/repos/{owner}/{target_repo}/pulls/{pr_number}/reviews"
data = {
'body': body,
'event': event
}
if comments:
data['comments'] = comments
logger.info(f"Creating review on PR #{pr_number} in {owner}/{target_repo}")
response = self.session.post(url, json=data)
response.raise_for_status()
return response.json()
def add_pr_comment(
self,
pr_number: int,
body: str,
repo: Optional[str] = None
) -> Dict:
"""Add a general comment to a pull request (uses issue comment endpoint)."""
owner, target_repo = self._parse_repo(repo)
# PRs share comment endpoint with issues in Gitea
url = f"{self.base_url}/repos/{owner}/{target_repo}/issues/{pr_number}/comments"
data = {'body': body}
logger.info(f"Adding comment to PR #{pr_number} in {owner}/{target_repo}")
response = self.session.post(url, json=data)
response.raise_for_status()
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()

View File

@@ -1,93 +0,0 @@
"""
MCP Server entry point for Gitea integration.
Provides Gitea tools to Claude Code via JSON-RPC 2.0 over stdio.
"""
import asyncio
import logging
from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp.types import Tool, TextContent
from .config import GiteaConfig
from .gitea_client import GiteaClient
from .tool_registry import get_tool_definitions, create_tool_dispatcher
# 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 GiteaMCPServer:
"""MCP Server for Gitea integration"""
def __init__(self):
self.server = Server("gitea-mcp")
self.config = None
self.client = None
self._dispatcher = None
async def initialize(self):
"""
Initialize server and load configuration.
Raises:
Exception: If initialization fails
"""
try:
config_loader = GiteaConfig()
self.config = config_loader.load()
self.client = GiteaClient()
self._dispatcher = create_tool_dispatcher(self.client)
logger.info(f"Gitea MCP Server initialized in {self.config['mode']} mode")
except Exception as e:
logger.error(f"Failed to initialize: {e}")
raise
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"""
return get_tool_definitions()
@self.server.call_tool()
async def call_tool(name: str, arguments: dict) -> list[TextContent]:
"""
Handle tool invocation.
Args:
name: Tool name
arguments: Tool arguments
Returns:
List of TextContent with results
"""
return await self._dispatcher(name, arguments)
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 = GiteaMCPServer()
await server.run()
if __name__ == "__main__":
asyncio.run(main())

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +0,0 @@
"""
MCP tools for Gitea integration.
This package provides MCP tool implementations for:
- Issue operations (issues.py)
- Label management (labels.py)
- Wiki operations (wiki.py)
- Milestone management (milestones.py)
- Issue dependencies (dependencies.py)
- Pull request operations (pull_requests.py)
"""

View File

@@ -1,216 +0,0 @@
"""
Issue dependency management tools for MCP server.
Provides async wrappers for issue dependency operations:
- List/create/remove dependencies
- Build dependency graphs for parallel execution
"""
import asyncio
import logging
from typing import List, Dict, Optional, Set, Tuple
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class DependencyTools:
"""Async wrappers for Gitea issue dependency operations"""
def __init__(self, gitea_client):
"""
Initialize dependency tools.
Args:
gitea_client: GiteaClient instance
"""
self.gitea = gitea_client
async def list_issue_dependencies(
self,
issue_number: int,
repo: Optional[str] = None
) -> List[Dict]:
"""
List all dependencies for an issue (issues that block this one).
Args:
issue_number: Issue number
repo: Repository in owner/repo format
Returns:
List of issues that this issue depends on
"""
loop = asyncio.get_event_loop()
return await loop.run_in_executor(
None,
lambda: self.gitea.list_issue_dependencies(issue_number, repo)
)
async def create_issue_dependency(
self,
issue_number: int,
depends_on: int,
repo: Optional[str] = None
) -> Dict:
"""
Create a dependency between issues.
Args:
issue_number: The issue that will depend on another
depends_on: The issue that blocks issue_number
repo: Repository in owner/repo format
Returns:
Created dependency information
"""
loop = asyncio.get_event_loop()
return await loop.run_in_executor(
None,
lambda: self.gitea.create_issue_dependency(issue_number, depends_on, repo)
)
async def remove_issue_dependency(
self,
issue_number: int,
depends_on: int,
repo: Optional[str] = None
) -> bool:
"""
Remove a dependency between issues.
Args:
issue_number: The issue that currently depends on another
depends_on: The issue being depended on
repo: Repository in owner/repo format
Returns:
True if removed successfully
"""
loop = asyncio.get_event_loop()
return await loop.run_in_executor(
None,
lambda: self.gitea.remove_issue_dependency(issue_number, depends_on, repo)
)
async def list_issue_blocks(
self,
issue_number: int,
repo: Optional[str] = None
) -> List[Dict]:
"""
List all issues that this issue blocks.
Args:
issue_number: Issue number
repo: Repository in owner/repo format
Returns:
List of issues blocked by this issue
"""
loop = asyncio.get_event_loop()
return await loop.run_in_executor(
None,
lambda: self.gitea.list_issue_blocks(issue_number, repo)
)
async def build_dependency_graph(
self,
issue_numbers: List[int],
repo: Optional[str] = None
) -> Dict[int, List[int]]:
"""
Build a dependency graph for a list of issues.
Args:
issue_numbers: List of issue numbers to analyze
repo: Repository in owner/repo format
Returns:
Dictionary mapping issue_number -> list of issues it depends on
"""
graph = {}
for issue_num in issue_numbers:
try:
deps = await self.list_issue_dependencies(issue_num, repo)
graph[issue_num] = [
d.get('number') or d.get('index')
for d in deps
if (d.get('number') or d.get('index')) in issue_numbers
]
except Exception as e:
logger.warning(f"Could not fetch dependencies for #{issue_num}: {e}")
graph[issue_num] = []
return graph
async def get_ready_tasks(
self,
issue_numbers: List[int],
completed: Set[int],
repo: Optional[str] = None
) -> List[int]:
"""
Get tasks that are ready to execute (no unresolved dependencies).
Args:
issue_numbers: List of all issue numbers in sprint
completed: Set of already completed issue numbers
repo: Repository in owner/repo format
Returns:
List of issue numbers that can be executed now
"""
graph = await self.build_dependency_graph(issue_numbers, repo)
ready = []
for issue_num in issue_numbers:
if issue_num in completed:
continue
deps = graph.get(issue_num, [])
# Task is ready if all its dependencies are completed
if all(dep in completed for dep in deps):
ready.append(issue_num)
return ready
async def get_execution_order(
self,
issue_numbers: List[int],
repo: Optional[str] = None
) -> List[List[int]]:
"""
Get a parallelizable execution order for issues.
Returns batches of issues that can be executed in parallel.
Each batch contains issues with no unresolved dependencies.
Args:
issue_numbers: List of all issue numbers
repo: Repository in owner/repo format
Returns:
List of batches, where each batch can be executed in parallel
"""
graph = await self.build_dependency_graph(issue_numbers, repo)
completed: Set[int] = set()
remaining = set(issue_numbers)
batches = []
while remaining:
# Find all tasks with no unresolved dependencies
batch = []
for issue_num in remaining:
deps = graph.get(issue_num, [])
if all(dep in completed for dep in deps):
batch.append(issue_num)
if not batch:
# Circular dependency detected
logger.error(f"Circular dependency detected! Remaining: {remaining}")
batch = list(remaining) # Force include remaining to avoid infinite loop
batches.append(batch)
completed.update(batch)
remaining -= set(batch)
return batches

View File

@@ -1,287 +0,0 @@
"""
Issue management tools for MCP server.
Provides async wrappers for issue CRUD operations with:
- Branch-aware security
- PMO multi-repo support
- Comprehensive error handling
"""
import asyncio
import os
import subprocess
import logging
from typing import List, Dict, Optional
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class IssueTools:
"""Async wrappers for Gitea issue operations with branch detection"""
def __init__(self, gitea_client):
"""
Initialize issue tools.
Args:
gitea_client: GiteaClient instance
"""
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:
"""
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:
Current branch name or 'unknown' if not in a git repo
"""
try:
project_dir = self._get_project_directory()
result = subprocess.run(
['git', 'rev-parse', '--abbrev-ref', 'HEAD'],
capture_output=True,
text=True,
check=True,
cwd=project_dir # Run git in project directory, not plugin directory
)
return result.stdout.strip()
except subprocess.CalledProcessError:
return "unknown"
def _check_branch_permissions(self, operation: str) -> bool:
"""
Check if operation is allowed on current branch.
Args:
operation: Operation name (list_issues, create_issue, etc.)
Returns:
True if operation is allowed, False otherwise
"""
branch = self._get_current_branch()
# Production branches (read-only except incidents)
if branch in ['main', 'master'] or branch.startswith('prod/'):
return operation in ['list_issues', 'get_issue', 'get_labels']
# Staging branches (read-only for code)
if branch == 'staging' or branch.startswith('stage/'):
return operation in ['list_issues', 'get_issue', 'get_labels', 'create_issue']
# Development branches (full access)
# 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
# Unknown branch - be restrictive
return False
async def list_issues(
self,
state: str = 'open',
labels: Optional[List[str]] = None,
milestone: Optional[str] = None,
repo: Optional[str] = None
) -> List[Dict]:
"""
List issues from repository (async wrapper).
Args:
state: Issue state (open, closed, all)
labels: Filter by labels
milestone: Filter by milestone title (exact match)
repo: Override configured repo (for PMO multi-repo)
Returns:
List of issue dictionaries
Raises:
PermissionError: If operation not allowed on current branch
"""
if not self._check_branch_permissions('list_issues'):
branch = self._get_current_branch()
raise PermissionError(
f"Cannot list issues on branch '{branch}'. "
f"Switch to a development branch."
)
loop = asyncio.get_event_loop()
return await loop.run_in_executor(
None,
lambda: self.gitea.list_issues(state, labels, milestone, repo)
)
async def get_issue(
self,
issue_number: int,
repo: Optional[str] = None
) -> Dict:
"""
Get specific issue details (async wrapper).
Args:
issue_number: Issue number
repo: Override configured repo (for PMO multi-repo)
Returns:
Issue dictionary
Raises:
PermissionError: If operation not allowed on current branch
"""
if not self._check_branch_permissions('get_issue'):
branch = self._get_current_branch()
raise PermissionError(
f"Cannot get issue on branch '{branch}'. "
f"Switch to a development branch."
)
loop = asyncio.get_event_loop()
return await loop.run_in_executor(
None,
lambda: self.gitea.get_issue(issue_number, repo)
)
async def create_issue(
self,
title: str,
body: str,
labels: Optional[List[str]] = None,
repo: Optional[str] = None
) -> Dict:
"""
Create new issue (async wrapper with branch check).
Args:
title: Issue title
body: Issue description
labels: List of label names
repo: Override configured repo (for PMO multi-repo)
Returns:
Created issue dictionary
Raises:
PermissionError: If operation not allowed on current branch
"""
if not self._check_branch_permissions('create_issue'):
branch = self._get_current_branch()
raise PermissionError(
f"Cannot create issues on branch '{branch}'. "
f"Switch to a development branch to create issues."
)
loop = asyncio.get_event_loop()
return await loop.run_in_executor(
None,
lambda: self.gitea.create_issue(title, body, labels, repo)
)
async def update_issue(
self,
issue_number: int,
title: Optional[str] = None,
body: Optional[str] = None,
state: Optional[str] = None,
labels: Optional[List[str]] = None,
milestone: Optional[int] = None,
repo: Optional[str] = None
) -> Dict:
"""
Update existing issue (async wrapper with branch check).
Args:
issue_number: Issue number
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: Override configured repo (for PMO multi-repo)
Returns:
Updated issue dictionary
Raises:
PermissionError: If operation not allowed on current branch
"""
if not self._check_branch_permissions('update_issue'):
branch = self._get_current_branch()
raise PermissionError(
f"Cannot update issues on branch '{branch}'. "
f"Switch to a development branch to update issues."
)
loop = asyncio.get_event_loop()
return await loop.run_in_executor(
None,
lambda: self.gitea.update_issue(issue_number, title, body, state, labels, milestone, repo)
)
async def add_comment(
self,
issue_number: int,
comment: str,
repo: Optional[str] = None
) -> Dict:
"""
Add comment to issue (async wrapper with branch check).
Args:
issue_number: Issue number
comment: Comment text
repo: Override configured repo (for PMO multi-repo)
Returns:
Created comment dictionary
Raises:
PermissionError: If operation not allowed on current branch
"""
if not self._check_branch_permissions('add_comment'):
branch = self._get_current_branch()
raise PermissionError(
f"Cannot add comments on branch '{branch}'. "
f"Switch to a development branch to add comments."
)
loop = asyncio.get_event_loop()
return await loop.run_in_executor(
None,
lambda: self.gitea.add_comment(issue_number, comment, repo)
)
async def aggregate_issues(
self,
org: str,
state: str = 'open',
labels: Optional[List[str]] = None
) -> Dict[str, List[Dict]]:
"""Aggregate issues across all repositories in org."""
if not self._check_branch_permissions('aggregate_issues'):
branch = self._get_current_branch()
raise PermissionError(f"Cannot aggregate issues on branch '{branch}'.")
loop = asyncio.get_event_loop()
return await loop.run_in_executor(
None,
lambda: self.gitea.aggregate_issues(org, state, labels)
)

View File

@@ -1,377 +0,0 @@
"""
Label management tools for MCP server.
Provides async wrappers for label operations with:
- Label taxonomy retrieval
- Intelligent label suggestion
- Dynamic label detection
"""
import asyncio
import logging
import re
from typing import List, Dict, Optional
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class LabelTools:
"""Async wrappers for Gitea label operations"""
def __init__(self, gitea_client):
"""
Initialize label tools.
Args:
gitea_client: GiteaClient instance
"""
self.gitea = gitea_client
async def get_labels(self, repo: Optional[str] = None) -> Dict[str, List[Dict]]:
"""Get all labels (org + repo if org-owned, repo-only if user-owned)."""
loop = asyncio.get_event_loop()
target_repo = repo or self.gitea.repo
if not target_repo or '/' not in target_repo:
raise ValueError("Use 'owner/repo' format (e.g. 'org/repo-name')")
# Check if repo belongs to an organization or user
is_org = await loop.run_in_executor(
None,
lambda: self.gitea.is_org_repo(target_repo)
)
org_labels = []
if is_org:
org = target_repo.split('/')[0]
org_labels = await loop.run_in_executor(
None,
lambda: self.gitea.get_org_labels(org)
)
repo_labels = await loop.run_in_executor(
None,
lambda: self.gitea.get_labels(target_repo)
)
return {
'organization': org_labels,
'repository': repo_labels,
'total_count': len(org_labels) + len(repo_labels)
}
async def suggest_labels(self, context: str, repo: Optional[str] = None) -> List[str]:
"""
Analyze context and suggest appropriate labels from repository's actual labels.
This method fetches actual labels from the repository and matches them
dynamically, supporting any label naming convention (slash, colon-space, etc.).
Args:
context: Issue title + description or sprint context
repo: Repository in 'owner/repo' format (optional, uses default if not provided)
Returns:
List of suggested label names that exist in the repository
"""
# Fetch actual labels from repository
target_repo = repo or self.gitea.repo
if not target_repo:
logger.warning("No repository specified, returning empty suggestions")
return []
try:
labels_data = await self.get_labels(target_repo)
all_labels = labels_data.get('organization', []) + labels_data.get('repository', [])
label_names = [label['name'] for label in all_labels]
except Exception as e:
logger.warning(f"Failed to fetch labels: {e}. Using fallback suggestions.")
label_names = []
# Build label lookup for dynamic matching
label_lookup = self._build_label_lookup(label_names)
suggested = []
context_lower = context.lower()
# Type detection (exclusive - only one)
type_label = None
if any(word in context_lower for word in ['bug', 'error', 'fix', 'broken', 'crash', 'fail']):
type_label = self._find_label(label_lookup, 'type', 'bug')
elif any(word in context_lower for word in ['refactor', 'extract', 'restructure', 'architecture', 'service extraction']):
type_label = self._find_label(label_lookup, 'type', 'refactor')
elif any(word in context_lower for word in ['feature', 'add', 'implement', 'new', 'create']):
type_label = self._find_label(label_lookup, 'type', 'feature')
elif any(word in context_lower for word in ['docs', 'documentation', 'readme', 'guide']):
type_label = self._find_label(label_lookup, 'type', 'documentation')
elif any(word in context_lower for word in ['test', 'testing', 'spec', 'coverage']):
type_label = self._find_label(label_lookup, 'type', 'test')
elif any(word in context_lower for word in ['chore', 'maintenance', 'update', 'upgrade']):
type_label = self._find_label(label_lookup, 'type', 'chore')
if type_label:
suggested.append(type_label)
# Priority detection
priority_label = None
if any(word in context_lower for word in ['critical', 'urgent', 'blocker', 'blocking', 'emergency']):
priority_label = self._find_label(label_lookup, 'priority', 'critical')
elif any(word in context_lower for word in ['high', 'important', 'asap', 'soon']):
priority_label = self._find_label(label_lookup, 'priority', 'high')
elif any(word in context_lower for word in ['low', 'nice-to-have', 'optional', 'later']):
priority_label = self._find_label(label_lookup, 'priority', 'low')
else:
priority_label = self._find_label(label_lookup, 'priority', 'medium')
if priority_label:
suggested.append(priority_label)
# Complexity detection
complexity_label = None
if any(word in context_lower for word in ['simple', 'trivial', 'easy', 'quick']):
complexity_label = self._find_label(label_lookup, 'complexity', 'simple')
elif any(word in context_lower for word in ['complex', 'difficult', 'challenging', 'intricate']):
complexity_label = self._find_label(label_lookup, 'complexity', 'complex')
else:
complexity_label = self._find_label(label_lookup, 'complexity', 'medium')
if complexity_label:
suggested.append(complexity_label)
# Effort detection (supports both "Effort" and "Efforts" naming)
effort_label = None
if any(word in context_lower for word in ['xs', 'tiny', '1 hour', '2 hours']):
effort_label = self._find_label(label_lookup, 'effort', 'xs')
elif any(word in context_lower for word in ['small', 's ', '1 day', 'half day']):
effort_label = self._find_label(label_lookup, 'effort', 's')
elif any(word in context_lower for word in ['medium', 'm ', '2 days', '3 days']):
effort_label = self._find_label(label_lookup, 'effort', 'm')
elif any(word in context_lower for word in ['large', 'l ', '1 week', '5 days']):
effort_label = self._find_label(label_lookup, 'effort', 'l')
elif any(word in context_lower for word in ['xl', 'extra large', '2 weeks', 'sprint']):
effort_label = self._find_label(label_lookup, 'effort', 'xl')
if effort_label:
suggested.append(effort_label)
# Component detection (based on keywords)
component_mappings = {
'backend': ['backend', 'server', 'api', 'database', 'service'],
'frontend': ['frontend', 'ui', 'interface', 'react', 'vue', 'component'],
'api': ['api', 'endpoint', 'rest', 'graphql', 'route'],
'database': ['database', 'db', 'sql', 'migration', 'schema', 'postgres'],
'auth': ['auth', 'authentication', 'login', 'oauth', 'token', 'session'],
'deploy': ['deploy', 'deployment', 'docker', 'kubernetes', 'ci/cd'],
'testing': ['test', 'testing', 'spec', 'jest', 'pytest', 'coverage'],
'docs': ['docs', 'documentation', 'readme', 'guide', 'wiki']
}
for component, keywords in component_mappings.items():
if any(keyword in context_lower for keyword in keywords):
label = self._find_label(label_lookup, 'component', component)
if label and label not in suggested:
suggested.append(label)
# Tech stack detection
tech_mappings = {
'python': ['python', 'fastapi', 'django', 'flask', 'pytest'],
'javascript': ['javascript', 'js', 'node', 'npm', 'yarn'],
'docker': ['docker', 'dockerfile', 'container', 'compose'],
'postgresql': ['postgres', 'postgresql', 'psql', 'sql'],
'redis': ['redis', 'cache', 'session store'],
'vue': ['vue', 'vuejs', 'nuxt'],
'fastapi': ['fastapi', 'pydantic', 'starlette']
}
for tech, keywords in tech_mappings.items():
if any(keyword in context_lower for keyword in keywords):
label = self._find_label(label_lookup, 'tech', tech)
if label and label not in suggested:
suggested.append(label)
# Source detection (based on git branch or context)
source_label = None
if 'development' in context_lower or 'dev/' in context_lower:
source_label = self._find_label(label_lookup, 'source', 'development')
elif 'staging' in context_lower or 'stage/' in context_lower:
source_label = self._find_label(label_lookup, 'source', 'staging')
elif 'production' in context_lower or 'prod' in context_lower:
source_label = self._find_label(label_lookup, 'source', 'production')
if source_label:
suggested.append(source_label)
# Risk detection
risk_label = None
if any(word in context_lower for word in ['breaking', 'breaking change', 'major', 'risky']):
risk_label = self._find_label(label_lookup, 'risk', 'high')
elif any(word in context_lower for word in ['safe', 'low risk', 'minor']):
risk_label = self._find_label(label_lookup, 'risk', 'low')
if risk_label:
suggested.append(risk_label)
logger.info(f"Suggested {len(suggested)} labels based on context and {len(label_names)} available labels")
return suggested
def _build_label_lookup(self, label_names: List[str]) -> Dict[str, Dict[str, str]]:
"""
Build a lookup dictionary for label matching.
Supports various label formats:
- Slash format: Type/Bug, Priority/High
- Colon-space format: Type: Bug, Priority: High
- Colon format: Type:Bug
Args:
label_names: List of actual label names from repository
Returns:
Nested dict: {category: {value: actual_label_name}}
"""
lookup: Dict[str, Dict[str, str]] = {}
for label in label_names:
# Try different separator patterns
# Pattern: Category<separator>Value
# Separators: /, : , :
match = re.match(r'^([^/:]+)(?:/|:\s*|:)(.+)$', label)
if match:
category = match.group(1).lower().rstrip('s') # Normalize: "Efforts" -> "effort"
value = match.group(2).lower()
if category not in lookup:
lookup[category] = {}
lookup[category][value] = label
return lookup
def _find_label(self, lookup: Dict[str, Dict[str, str]], category: str, value: str) -> Optional[str]:
"""
Find actual label name from lookup.
Args:
lookup: Label lookup dictionary
category: Category to search (e.g., 'type', 'priority')
value: Value to find (e.g., 'bug', 'high')
Returns:
Actual label name if found, None otherwise
"""
category_lower = category.lower().rstrip('s') # Normalize
value_lower = value.lower()
if category_lower in lookup and value_lower in lookup[category_lower]:
return lookup[category_lower][value_lower]
return None
# Organization-level label categories (workflow labels shared across repos)
ORG_LABEL_CATEGORIES = {'agent', 'complexity', 'effort', 'efforts', 'priority', 'risk', 'source', 'type'}
# Repository-level label categories (project-specific labels)
REPO_LABEL_CATEGORIES = {'component', 'tech'}
async def create_label_smart(
self,
name: str,
color: str,
description: Optional[str] = None,
repo: Optional[str] = None
) -> Dict:
"""
Create a label at the appropriate level (org or repo) based on category.
Skips if label already exists (checks both org and repo levels).
Organization labels: Agent, Complexity, Effort, Priority, Risk, Source, Type
Repository labels: Component, Tech
Args:
name: Label name (e.g., 'Type/Bug', 'Component/Backend')
color: Hex color code
description: Optional label description
repo: Repository in 'owner/repo' format
Returns:
Created label dictionary with 'level' key, or 'skipped' if already exists
"""
loop = asyncio.get_event_loop()
target_repo = repo or self.gitea.repo
if not target_repo or '/' not in target_repo:
raise ValueError("Use 'owner/repo' format (e.g. 'org/repo-name')")
owner = target_repo.split('/')[0]
is_org = await loop.run_in_executor(
None,
lambda: self.gitea.is_org_repo(target_repo)
)
# Fetch existing labels to check for duplicates
existing_labels = await self.get_labels(target_repo)
all_existing = existing_labels.get('organization', []) + existing_labels.get('repository', [])
existing_names = [label['name'].lower() for label in all_existing]
# Normalize the new label name for comparison
name_normalized = name.lower()
# Also check for format variations (Type/Bug vs Type: Bug)
name_variations = [name_normalized]
if '/' in name:
name_variations.append(name.replace('/', ': ').lower())
name_variations.append(name.replace('/', ':').lower())
elif ': ' in name:
name_variations.append(name.replace(': ', '/').lower())
elif ':' in name:
name_variations.append(name.replace(':', '/').lower())
# Check if label already exists in any format
for variation in name_variations:
if variation in existing_names:
logger.info(f"Label '{name}' already exists (found as '{variation}'), skipping")
return {
'name': name,
'skipped': True,
'reason': f"Label already exists",
'level': 'existing'
}
# Parse category from label name
category = None
if '/' in name:
category = name.split('/')[0].lower().rstrip('s')
elif ':' in name:
category = name.split(':')[0].strip().lower().rstrip('s')
# If it's an org repo and the category is an org-level category, create at org level
if is_org and category in self.ORG_LABEL_CATEGORIES:
result = await loop.run_in_executor(
None,
lambda: self.gitea.create_org_label(owner, name, color, description)
)
# Handle unexpected response types (API may return list or non-dict)
if not isinstance(result, dict):
logger.error(f"Unexpected API response type for org label: {type(result)} - {result}")
return {
'name': name,
'error': True,
'reason': f"API returned {type(result).__name__} instead of dict: {result}",
'level': 'organization'
}
result['level'] = 'organization'
result['skipped'] = False
logger.info(f"Created organization label '{name}' in {owner}")
else:
# Create at repo level
result = await loop.run_in_executor(
None,
lambda: self.gitea.create_label(name, color, description, target_repo)
)
# Handle unexpected response types (API may return list or non-dict)
if not isinstance(result, dict):
logger.error(f"Unexpected API response type for repo label: {type(result)} - {result}")
return {
'name': name,
'error': True,
'reason': f"API returned {type(result).__name__} instead of dict: {result}",
'level': 'repository'
}
result['level'] = 'repository'
result['skipped'] = False
logger.info(f"Created repository label '{name}' in {target_repo}")
return result

View File

@@ -1,145 +0,0 @@
"""
Milestone management tools for MCP server.
Provides async wrappers for milestone operations:
- CRUD operations for milestones
- Milestone-sprint relationship tracking
"""
import asyncio
import logging
from typing import List, Dict, Optional
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class MilestoneTools:
"""Async wrappers for Gitea milestone operations"""
def __init__(self, gitea_client):
"""
Initialize milestone tools.
Args:
gitea_client: GiteaClient instance
"""
self.gitea = gitea_client
async def list_milestones(
self,
state: str = 'open',
repo: Optional[str] = None
) -> List[Dict]:
"""
List all milestones in repository.
Args:
state: Milestone state (open, closed, all)
repo: Repository in owner/repo format
Returns:
List of milestone dictionaries
"""
loop = asyncio.get_event_loop()
return await loop.run_in_executor(
None,
lambda: self.gitea.list_milestones(state, repo)
)
async def get_milestone(
self,
milestone_id: int,
repo: Optional[str] = None
) -> Dict:
"""
Get a specific milestone by ID.
Args:
milestone_id: Milestone ID
repo: Repository in owner/repo format
Returns:
Milestone dictionary
"""
loop = asyncio.get_event_loop()
return await loop.run_in_executor(
None,
lambda: self.gitea.get_milestone(milestone_id, repo)
)
async def create_milestone(
self,
title: str,
description: Optional[str] = None,
due_on: Optional[str] = None,
repo: Optional[str] = None
) -> Dict:
"""
Create a new milestone.
Args:
title: Milestone title (e.g., "v2.0 Release", "Sprint 17")
description: Milestone description
due_on: Due date in ISO 8601 format (e.g., "2025-02-01T00:00:00Z")
repo: Repository in owner/repo format
Returns:
Created milestone dictionary
"""
loop = asyncio.get_event_loop()
return await loop.run_in_executor(
None,
lambda: self.gitea.create_milestone(title, description, due_on, repo)
)
async def update_milestone(
self,
milestone_id: int,
title: Optional[str] = None,
description: Optional[str] = None,
state: Optional[str] = None,
due_on: Optional[str] = None,
repo: Optional[str] = None
) -> Dict:
"""
Update an existing milestone.
Args:
milestone_id: Milestone ID
title: New title (optional)
description: New description (optional)
state: New state - 'open' or 'closed' (optional)
due_on: New due date (optional)
repo: Repository in owner/repo format
Returns:
Updated milestone dictionary
"""
loop = asyncio.get_event_loop()
return await loop.run_in_executor(
None,
lambda: self.gitea.update_milestone(
milestone_id, title, description, state, due_on, repo
)
)
async def delete_milestone(
self,
milestone_id: int,
repo: Optional[str] = None
) -> bool:
"""
Delete a milestone.
Args:
milestone_id: Milestone ID
repo: Repository in owner/repo format
Returns:
True if deleted successfully
"""
loop = asyncio.get_event_loop()
return await loop.run_in_executor(
None,
lambda: self.gitea.delete_milestone(milestone_id, repo)
)

View File

@@ -1,335 +0,0 @@
"""
Pull request management tools for MCP server.
Provides async wrappers for PR operations with:
- Branch-aware security
- PMO multi-repo support
- Comprehensive error handling
"""
import asyncio
import os
import subprocess
import logging
from typing import List, Dict, Optional
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class PullRequestTools:
"""Async wrappers for Gitea pull request operations with branch detection"""
def __init__(self, gitea_client):
"""
Initialize pull request tools.
Args:
gitea_client: GiteaClient instance
"""
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:
"""
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:
Current branch name or 'unknown' if not in a git repo
"""
try:
project_dir = self._get_project_directory()
result = subprocess.run(
['git', 'rev-parse', '--abbrev-ref', 'HEAD'],
capture_output=True,
text=True,
check=True,
cwd=project_dir # Run git in project directory, not plugin directory
)
return result.stdout.strip()
except subprocess.CalledProcessError:
return "unknown"
def _check_branch_permissions(self, operation: str) -> bool:
"""
Check if operation is allowed on current branch.
Args:
operation: Operation name (list_prs, create_review, etc.)
Returns:
True if operation is allowed, False otherwise
"""
branch = self._get_current_branch()
# Read-only operations allowed everywhere
read_ops = ['list_pull_requests', 'get_pull_request', 'get_pr_diff', 'get_pr_comments']
# Production branches (read-only)
if branch in ['main', 'master'] or branch.startswith('prod/'):
return operation in read_ops
# Staging branches (read-only for PRs, can comment)
if branch == 'staging' or branch.startswith('stage/'):
return operation in read_ops + ['add_pr_comment']
# Development branches (full access)
# 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
# Unknown branch - be restrictive
return operation in read_ops
async def list_pull_requests(
self,
state: str = 'open',
sort: str = 'recentupdate',
labels: Optional[List[str]] = None,
repo: Optional[str] = None
) -> List[Dict]:
"""
List pull requests from repository (async wrapper).
Args:
state: PR state (open, closed, all)
sort: Sort order
labels: Filter by labels
repo: Override configured repo (for PMO multi-repo)
Returns:
List of pull request dictionaries
Raises:
PermissionError: If operation not allowed on current branch
"""
if not self._check_branch_permissions('list_pull_requests'):
branch = self._get_current_branch()
raise PermissionError(
f"Cannot list PRs on branch '{branch}'. "
f"Switch to a development branch."
)
loop = asyncio.get_event_loop()
return await loop.run_in_executor(
None,
lambda: self.gitea.list_pull_requests(state, sort, labels, repo)
)
async def get_pull_request(
self,
pr_number: int,
repo: Optional[str] = None
) -> Dict:
"""
Get specific pull request details (async wrapper).
Args:
pr_number: Pull request number
repo: Override configured repo (for PMO multi-repo)
Returns:
Pull request dictionary
Raises:
PermissionError: If operation not allowed on current branch
"""
if not self._check_branch_permissions('get_pull_request'):
branch = self._get_current_branch()
raise PermissionError(
f"Cannot get PR on branch '{branch}'. "
f"Switch to a development branch."
)
loop = asyncio.get_event_loop()
return await loop.run_in_executor(
None,
lambda: self.gitea.get_pull_request(pr_number, repo)
)
async def get_pr_diff(
self,
pr_number: int,
repo: Optional[str] = None
) -> str:
"""
Get pull request diff (async wrapper).
Args:
pr_number: Pull request number
repo: Override configured repo (for PMO multi-repo)
Returns:
Diff as string
Raises:
PermissionError: If operation not allowed on current branch
"""
if not self._check_branch_permissions('get_pr_diff'):
branch = self._get_current_branch()
raise PermissionError(
f"Cannot get PR diff on branch '{branch}'. "
f"Switch to a development branch."
)
loop = asyncio.get_event_loop()
return await loop.run_in_executor(
None,
lambda: self.gitea.get_pr_diff(pr_number, repo)
)
async def get_pr_comments(
self,
pr_number: int,
repo: Optional[str] = None
) -> List[Dict]:
"""
Get comments on a pull request (async wrapper).
Args:
pr_number: Pull request number
repo: Override configured repo (for PMO multi-repo)
Returns:
List of comment dictionaries
Raises:
PermissionError: If operation not allowed on current branch
"""
if not self._check_branch_permissions('get_pr_comments'):
branch = self._get_current_branch()
raise PermissionError(
f"Cannot get PR comments on branch '{branch}'. "
f"Switch to a development branch."
)
loop = asyncio.get_event_loop()
return await loop.run_in_executor(
None,
lambda: self.gitea.get_pr_comments(pr_number, repo)
)
async def create_pr_review(
self,
pr_number: int,
body: str,
event: str = 'COMMENT',
comments: Optional[List[Dict]] = None,
repo: Optional[str] = None
) -> Dict:
"""
Create a review on a pull request (async wrapper with branch check).
Args:
pr_number: Pull request number
body: Review body/summary
event: Review action (APPROVE, REQUEST_CHANGES, COMMENT)
comments: Optional list of inline comments
repo: Override configured repo (for PMO multi-repo)
Returns:
Created review dictionary
Raises:
PermissionError: If operation not allowed on current branch
"""
if not self._check_branch_permissions('create_pr_review'):
branch = self._get_current_branch()
raise PermissionError(
f"Cannot create PR review on branch '{branch}'. "
f"Switch to a development branch to review PRs."
)
loop = asyncio.get_event_loop()
return await loop.run_in_executor(
None,
lambda: self.gitea.create_pr_review(pr_number, body, event, comments, repo)
)
async def add_pr_comment(
self,
pr_number: int,
body: str,
repo: Optional[str] = None
) -> Dict:
"""
Add a general comment to a pull request (async wrapper with branch check).
Args:
pr_number: Pull request number
body: Comment text
repo: Override configured repo (for PMO multi-repo)
Returns:
Created comment dictionary
Raises:
PermissionError: If operation not allowed on current branch
"""
if not self._check_branch_permissions('add_pr_comment'):
branch = self._get_current_branch()
raise PermissionError(
f"Cannot add PR comment on branch '{branch}'. "
f"Switch to a development or staging branch to comment on PRs."
)
loop = asyncio.get_event_loop()
return await loop.run_in_executor(
None,
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)
)

View File

@@ -1,187 +0,0 @@
"""
Wiki management tools for MCP server.
Provides async wrappers for wiki operations to support lessons learned:
- Page CRUD operations
- Lessons learned creation and search
- RFC number allocation
"""
import asyncio
import logging
import re
from typing import List, Dict, Optional
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class WikiTools:
"""Async wrappers for Gitea wiki operations"""
def __init__(self, gitea_client):
"""
Initialize wiki tools.
Args:
gitea_client: GiteaClient instance
"""
self.gitea = gitea_client
async def list_wiki_pages(self, repo: Optional[str] = None) -> List[Dict]:
"""List all wiki pages in repository."""
loop = asyncio.get_event_loop()
return await loop.run_in_executor(
None,
lambda: self.gitea.list_wiki_pages(repo)
)
async def get_wiki_page(
self,
page_name: str,
repo: Optional[str] = None
) -> Dict:
"""Get a specific wiki page by name."""
loop = asyncio.get_event_loop()
return await loop.run_in_executor(
None,
lambda: self.gitea.get_wiki_page(page_name, repo)
)
async def create_wiki_page(
self,
title: str,
content: str,
repo: Optional[str] = None
) -> Dict:
"""Create a new wiki page."""
loop = asyncio.get_event_loop()
return await loop.run_in_executor(
None,
lambda: self.gitea.create_wiki_page(title, content, repo)
)
async def update_wiki_page(
self,
page_name: str,
content: str,
repo: Optional[str] = None
) -> Dict:
"""Update an existing wiki page."""
loop = asyncio.get_event_loop()
return await loop.run_in_executor(
None,
lambda: self.gitea.update_wiki_page(page_name, content, repo)
)
async def delete_wiki_page(
self,
page_name: str,
repo: Optional[str] = None
) -> bool:
"""Delete a wiki page."""
loop = asyncio.get_event_loop()
return await loop.run_in_executor(
None,
lambda: self.gitea.delete_wiki_page(page_name, repo)
)
async def search_wiki_pages(
self,
query: str,
repo: Optional[str] = None
) -> List[Dict]:
"""Search wiki pages by title."""
loop = asyncio.get_event_loop()
return await loop.run_in_executor(
None,
lambda: self.gitea.search_wiki_pages(query, repo)
)
async def create_lesson(
self,
title: str,
content: str,
tags: List[str],
category: str = "sprints",
repo: Optional[str] = None
) -> Dict:
"""
Create a lessons learned entry in the wiki.
Args:
title: Lesson title (e.g., "Sprint 16 - Prevent Infinite Loops")
content: Lesson content in markdown
tags: List of tags for categorization
category: Category (sprints, patterns, architecture, etc.)
repo: Repository in owner/repo format
Returns:
Created wiki page
"""
loop = asyncio.get_event_loop()
return await loop.run_in_executor(
None,
lambda: self.gitea.create_lesson(title, content, tags, category, repo)
)
async def search_lessons(
self,
query: Optional[str] = None,
tags: Optional[List[str]] = None,
limit: int = 20,
repo: Optional[str] = None
) -> List[Dict]:
"""
Search lessons learned from previous sprints.
Args:
query: Search query (optional)
tags: Tags to filter by (optional)
limit: Maximum results (default 20)
repo: Repository in owner/repo format
Returns:
List of matching lessons
"""
loop = asyncio.get_event_loop()
results = await loop.run_in_executor(
None,
lambda: self.gitea.search_lessons(query, tags, repo)
)
return results[:limit]
async def allocate_rfc_number(self, repo: Optional[str] = None) -> Dict:
"""
Allocate the next available RFC number.
Scans existing wiki pages for RFC-NNNN pattern and returns
the next sequential number.
Args:
repo: Repository in owner/repo format
Returns:
Dict with 'next_number' (int) and 'formatted' (str like 'RFC-0001')
"""
pages = await self.list_wiki_pages(repo)
# Extract RFC numbers from page titles
rfc_numbers = []
rfc_pattern = re.compile(r'^RFC-(\d{4})')
for page in pages:
title = page.get('title', '')
match = rfc_pattern.match(title)
if match:
rfc_numbers.append(int(match.group(1)))
# Calculate next number
if rfc_numbers:
next_num = max(rfc_numbers) + 1
else:
next_num = 1
return {
'next_number': next_num,
'formatted': f'RFC-{next_num:04d}'
}

View File

@@ -1,43 +0,0 @@
[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "gitea-mcp-server"
version = "1.0.0"
description = "MCP Server for Gitea integration - provides issue, label, wiki, milestone, dependency, and PR tools"
readme = "README.md"
requires-python = ">=3.10"
license = {text = "MIT"}
authors = [
{ name = "Leo Miranda" }
]
keywords = ["mcp", "gitea", "claude", "tools"]
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
]
dependencies = [
"mcp>=0.9.0",
"python-dotenv>=1.0.0",
"requests>=2.31.0",
"pydantic>=2.5.0",
]
[project.optional-dependencies]
test = [
"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"]

View File

@@ -1,6 +1,2 @@
mcp>=0.9.0 # MCP SDK from Anthropic --extra-index-url https://gitea.hotserv.cloud/api/packages/personal-projects/pypi/simple
python-dotenv>=1.0.0 # Environment variable loading gitea-mcp>=1.0.0
requests>=2.31.0 # HTTP client for Gitea API
pydantic>=2.5.0 # Data validation
pytest>=7.4.3 # Testing framework
pytest-asyncio>=0.23.0 # Async testing support

View File

@@ -17,5 +17,4 @@ else
fi fi
cd "$SCRIPT_DIR" cd "$SCRIPT_DIR"
export PYTHONPATH="$SCRIPT_DIR" exec "$PYTHON" -m gitea_mcp.server "$@"
exec "$PYTHON" -m mcp_server.server "$@"

View File

@@ -1,259 +0,0 @@
"""
Unit tests for configuration loader.
"""
import pytest
from pathlib import Path
import os
from mcp_server.config import GiteaConfig
def test_load_system_config(tmp_path, monkeypatch):
"""Test loading system-level configuration"""
# Mock home directory
config_dir = tmp_path / '.config' / 'claude'
config_dir.mkdir(parents=True)
config_file = config_dir / 'gitea.env'
config_file.write_text(
"GITEA_API_URL=https://test.com/api/v1\n"
"GITEA_API_TOKEN=test_token\n"
"GITEA_OWNER=test_owner\n"
)
monkeypatch.setenv('HOME', str(tmp_path))
monkeypatch.chdir(tmp_path)
config = GiteaConfig()
result = config.load()
assert result['api_url'] == 'https://test.com/api/v1'
assert result['api_token'] == 'test_token'
assert result['mode'] == 'company' # No repo specified
assert result['repo'] is None
def test_project_config_override(tmp_path, monkeypatch):
"""Test that project config overrides system config"""
# Set up system config
system_config_dir = tmp_path / '.config' / 'claude'
system_config_dir.mkdir(parents=True)
system_config = system_config_dir / 'gitea.env'
system_config.write_text(
"GITEA_API_URL=https://test.com/api/v1\n"
"GITEA_API_TOKEN=test_token\n"
"GITEA_OWNER=test_owner\n"
)
# Set up project config
project_dir = tmp_path / 'project'
project_dir.mkdir()
project_config = project_dir / '.env'
project_config.write_text("GITEA_REPO=test_repo\n")
monkeypatch.setenv('HOME', str(tmp_path))
monkeypatch.chdir(project_dir)
config = GiteaConfig()
result = config.load()
assert result['repo'] == 'test_repo'
assert result['mode'] == 'project'
def test_missing_system_config(tmp_path, monkeypatch):
"""Test error handling for missing system configuration"""
monkeypatch.setenv('HOME', str(tmp_path))
monkeypatch.chdir(tmp_path)
with pytest.raises(FileNotFoundError) as exc_info:
config = GiteaConfig()
config.load()
assert "System config not found" in str(exc_info.value)
def test_missing_required_config(tmp_path, monkeypatch):
"""Test error handling for missing required variables"""
# Clear environment variables
for var in ['GITEA_API_URL', 'GITEA_API_TOKEN', 'GITEA_OWNER', 'GITEA_REPO']:
monkeypatch.delenv(var, raising=False)
# Create incomplete config
config_dir = tmp_path / '.config' / 'claude'
config_dir.mkdir(parents=True)
config_file = config_dir / 'gitea.env'
config_file.write_text(
"GITEA_API_URL=https://test.com/api/v1\n"
# Missing GITEA_API_TOKEN and GITEA_OWNER
)
monkeypatch.setenv('HOME', str(tmp_path))
monkeypatch.chdir(tmp_path)
with pytest.raises(ValueError) as exc_info:
config = GiteaConfig()
config.load()
assert "Missing required configuration" in str(exc_info.value)
def test_mode_detection_project(tmp_path, monkeypatch):
"""Test mode detection for project mode"""
config_dir = tmp_path / '.config' / 'claude'
config_dir.mkdir(parents=True)
config_file = config_dir / 'gitea.env'
config_file.write_text(
"GITEA_API_URL=https://test.com/api/v1\n"
"GITEA_API_TOKEN=test_token\n"
"GITEA_OWNER=test_owner\n"
"GITEA_REPO=test_repo\n"
)
monkeypatch.setenv('HOME', str(tmp_path))
monkeypatch.chdir(tmp_path)
config = GiteaConfig()
result = config.load()
assert result['mode'] == 'project'
assert result['repo'] == 'test_repo'
def test_mode_detection_company(tmp_path, monkeypatch):
"""Test mode detection for company mode (PMO)"""
# Clear environment variables, especially GITEA_REPO
for var in ['GITEA_API_URL', 'GITEA_API_TOKEN', 'GITEA_OWNER', 'GITEA_REPO']:
monkeypatch.delenv(var, raising=False)
config_dir = tmp_path / '.config' / 'claude'
config_dir.mkdir(parents=True)
config_file = config_dir / 'gitea.env'
config_file.write_text(
"GITEA_API_URL=https://test.com/api/v1\n"
"GITEA_API_TOKEN=test_token\n"
"GITEA_OWNER=test_owner\n"
# No GITEA_REPO
)
monkeypatch.setenv('HOME', str(tmp_path))
monkeypatch.chdir(tmp_path)
config = GiteaConfig()
result = config.load()
assert result['mode'] == 'company'
assert result['repo'] is None
# ========================================
# GIT URL PARSING TESTS
# ========================================
def test_parse_git_url_ssh_format():
"""Test parsing SSH format git URL"""
config = GiteaConfig()
# SSH with port: ssh://git@host:port/owner/repo.git
url = "ssh://git@hotserv.tailc9b278.ts.net:2222/personal-projects/personal-portfolio.git"
result = config._parse_git_url(url)
assert result == "personal-projects/personal-portfolio"
def test_parse_git_url_ssh_short_format():
"""Test parsing SSH short format git URL"""
config = GiteaConfig()
# SSH short: git@host:owner/repo.git
url = "git@github.com:owner/repo.git"
result = config._parse_git_url(url)
assert result == "owner/repo"
def test_parse_git_url_https_format():
"""Test parsing HTTPS format git URL"""
config = GiteaConfig()
# HTTPS: https://host/owner/repo.git
url = "https://gitea.hotserv.cloud/personal-projects/leo-claude-mktplace.git"
result = config._parse_git_url(url)
assert result == "personal-projects/leo-claude-mktplace"
def test_parse_git_url_http_format():
"""Test parsing HTTP format git URL"""
config = GiteaConfig()
# HTTP: http://host/owner/repo.git
url = "http://gitea.hotserv.cloud/personal-projects/repo.git"
result = config._parse_git_url(url)
assert result == "personal-projects/repo"
def test_parse_git_url_without_git_suffix():
"""Test parsing git URL without .git suffix"""
config = GiteaConfig()
url = "https://github.com/owner/repo"
result = config._parse_git_url(url)
assert result == "owner/repo"
def test_parse_git_url_invalid_format():
"""Test parsing invalid git URL returns None"""
config = GiteaConfig()
url = "not-a-valid-url"
result = config._parse_git_url(url)
assert result is None
def test_find_project_directory_from_env(tmp_path, monkeypatch):
"""Test finding project directory from CLAUDE_PROJECT_DIR env var"""
project_dir = tmp_path / 'my-project'
project_dir.mkdir()
(project_dir / '.git').mkdir()
monkeypatch.setenv('CLAUDE_PROJECT_DIR', str(project_dir))
config = GiteaConfig()
result = config._find_project_directory()
assert result == project_dir
def test_find_project_directory_from_cwd(tmp_path, monkeypatch):
"""Test finding project directory from cwd with .env file"""
project_dir = tmp_path / 'project'
project_dir.mkdir()
(project_dir / '.env').write_text("GITEA_REPO=test/repo")
monkeypatch.chdir(project_dir)
# Clear env vars that might interfere
monkeypatch.delenv('CLAUDE_PROJECT_DIR', raising=False)
monkeypatch.delenv('PWD', raising=False)
config = GiteaConfig()
result = config._find_project_directory()
assert result == project_dir
def test_find_project_directory_none_when_no_markers(tmp_path, monkeypatch):
"""Test returns None when no project markers found"""
empty_dir = tmp_path / 'empty'
empty_dir.mkdir()
monkeypatch.chdir(empty_dir)
monkeypatch.delenv('CLAUDE_PROJECT_DIR', raising=False)
monkeypatch.delenv('PWD', raising=False)
monkeypatch.delenv('GITEA_REPO', raising=False)
config = GiteaConfig()
result = config._find_project_directory()
assert result is None

View File

@@ -1,270 +0,0 @@
"""
Unit tests for Gitea API client.
"""
import pytest
from unittest.mock import Mock, patch, MagicMock
from mcp_server.gitea_client import GiteaClient
@pytest.fixture
def mock_config():
"""Fixture providing mocked configuration"""
with patch('mcp_server.gitea_client.GiteaConfig') as mock_cfg:
mock_instance = mock_cfg.return_value
mock_instance.load.return_value = {
'api_url': 'https://test.com/api/v1',
'api_token': 'test_token',
'repo': 'test_owner/test_repo', # Combined owner/repo format
'mode': 'project'
}
yield mock_cfg
@pytest.fixture
def gitea_client(mock_config):
"""Fixture providing GiteaClient instance with mocked config"""
return GiteaClient()
def test_client_initialization(gitea_client):
"""Test client initializes with correct configuration"""
assert gitea_client.base_url == 'https://test.com/api/v1'
assert gitea_client.token == 'test_token'
assert gitea_client.repo == 'test_owner/test_repo' # Combined format
assert gitea_client.mode == 'project'
assert 'Authorization' in gitea_client.session.headers
assert gitea_client.session.headers['Authorization'] == 'token test_token'
def test_list_issues(gitea_client):
"""Test listing issues"""
mock_response = Mock()
mock_response.json.return_value = [
{'number': 1, 'title': 'Test Issue 1'},
{'number': 2, 'title': 'Test Issue 2'}
]
mock_response.raise_for_status = Mock()
with patch.object(gitea_client.session, 'get', return_value=mock_response):
issues = gitea_client.list_issues(state='open')
assert len(issues) == 2
assert issues[0]['title'] == 'Test Issue 1'
gitea_client.session.get.assert_called_once()
def test_list_issues_with_labels(gitea_client):
"""Test listing issues with label filter"""
mock_response = Mock()
mock_response.json.return_value = [{'number': 1, 'title': 'Bug Issue'}]
mock_response.raise_for_status = Mock()
with patch.object(gitea_client.session, 'get', return_value=mock_response):
issues = gitea_client.list_issues(state='open', labels=['Type/Bug'])
gitea_client.session.get.assert_called_once()
call_args = gitea_client.session.get.call_args
assert call_args[1]['params']['labels'] == 'Type/Bug'
def test_get_issue(gitea_client):
"""Test getting specific issue"""
mock_response = Mock()
mock_response.json.return_value = {'number': 1, 'title': 'Test Issue'}
mock_response.raise_for_status = Mock()
with patch.object(gitea_client.session, 'get', return_value=mock_response):
issue = gitea_client.get_issue(1)
assert issue['number'] == 1
assert issue['title'] == 'Test Issue'
def test_create_issue(gitea_client):
"""Test creating new issue"""
mock_response = Mock()
mock_response.json.return_value = {
'number': 1,
'title': 'New Issue',
'body': 'Issue body'
}
mock_response.raise_for_status = Mock()
# Mock is_org_repo to avoid network call during label resolution
with patch.object(gitea_client, 'is_org_repo', return_value=True):
# Mock get_org_labels and get_labels for label resolution
with patch.object(gitea_client, 'get_org_labels', return_value=[{'name': 'Type/Bug', 'id': 1}]):
with patch.object(gitea_client, 'get_labels', return_value=[]):
with patch.object(gitea_client.session, 'post', return_value=mock_response):
issue = gitea_client.create_issue(
title='New Issue',
body='Issue body',
labels=['Type/Bug']
)
assert issue['title'] == 'New Issue'
gitea_client.session.post.assert_called_once()
def test_update_issue(gitea_client):
"""Test updating existing issue"""
mock_response = Mock()
mock_response.json.return_value = {
'number': 1,
'title': 'Updated Issue'
}
mock_response.raise_for_status = Mock()
with patch.object(gitea_client.session, 'patch', return_value=mock_response):
issue = gitea_client.update_issue(
issue_number=1,
title='Updated Issue'
)
assert issue['title'] == 'Updated Issue'
gitea_client.session.patch.assert_called_once()
def test_add_comment(gitea_client):
"""Test adding comment to issue"""
mock_response = Mock()
mock_response.json.return_value = {'body': 'Test comment'}
mock_response.raise_for_status = Mock()
with patch.object(gitea_client.session, 'post', return_value=mock_response):
comment = gitea_client.add_comment(1, 'Test comment')
assert comment['body'] == 'Test comment'
gitea_client.session.post.assert_called_once()
def test_get_labels(gitea_client):
"""Test getting repository labels"""
mock_response = Mock()
mock_response.json.return_value = [
{'name': 'Type/Bug'},
{'name': 'Priority/High'}
]
mock_response.raise_for_status = Mock()
with patch.object(gitea_client.session, 'get', return_value=mock_response):
labels = gitea_client.get_labels()
assert len(labels) == 2
assert labels[0]['name'] == 'Type/Bug'
def test_get_org_labels(gitea_client):
"""Test getting organization labels"""
mock_response = Mock()
mock_response.json.return_value = [
{'name': 'Type/Bug'},
{'name': 'Type/Feature'}
]
mock_response.raise_for_status = Mock()
with patch.object(gitea_client.session, 'get', return_value=mock_response):
labels = gitea_client.get_org_labels(org='test_owner')
assert len(labels) == 2
def test_list_repos(gitea_client):
"""Test listing organization repositories (PMO mode)"""
mock_response = Mock()
mock_response.json.return_value = [
{'name': 'repo1'},
{'name': 'repo2'}
]
mock_response.raise_for_status = Mock()
with patch.object(gitea_client.session, 'get', return_value=mock_response):
repos = gitea_client.list_repos(org='test_owner')
assert len(repos) == 2
assert repos[0]['name'] == 'repo1'
def test_aggregate_issues(gitea_client):
"""Test aggregating issues across repositories (PMO mode)"""
# Mock list_repos
gitea_client.list_repos = Mock(return_value=[
{'name': 'repo1'},
{'name': 'repo2'}
])
# Mock list_issues
gitea_client.list_issues = Mock(side_effect=[
[{'number': 1, 'title': 'Issue 1'}], # repo1
[{'number': 2, 'title': 'Issue 2'}] # repo2
])
aggregated = gitea_client.aggregate_issues(org='test_owner', state='open')
assert 'repo1' in aggregated
assert 'repo2' in aggregated
assert len(aggregated['repo1']) == 1
assert len(aggregated['repo2']) == 1
def test_no_repo_specified_error(gitea_client):
"""Test error when repository not specified or invalid format"""
# Create client without repo
with patch('mcp_server.gitea_client.GiteaConfig') as mock_cfg:
mock_instance = mock_cfg.return_value
mock_instance.load.return_value = {
'api_url': 'https://test.com/api/v1',
'api_token': 'test_token',
'repo': None, # No repo
'mode': 'company'
}
client = GiteaClient()
with pytest.raises(ValueError) as exc_info:
client.list_issues()
assert "Use 'owner/repo' format" in str(exc_info.value)
# ========================================
# ORGANIZATION DETECTION TESTS
# ========================================
def test_is_organization_true(gitea_client):
"""Test _is_organization returns True for valid organization"""
mock_response = Mock()
mock_response.status_code = 200
with patch.object(gitea_client.session, 'get', return_value=mock_response):
result = gitea_client._is_organization('personal-projects')
assert result is True
gitea_client.session.get.assert_called_once_with(
'https://test.com/api/v1/orgs/personal-projects'
)
def test_is_organization_false(gitea_client):
"""Test _is_organization returns False for user account"""
mock_response = Mock()
mock_response.status_code = 404
with patch.object(gitea_client.session, 'get', return_value=mock_response):
result = gitea_client._is_organization('lmiranda')
assert result is False
def test_is_org_repo_uses_orgs_endpoint(gitea_client):
"""Test is_org_repo uses /orgs endpoint instead of owner.type"""
mock_response = Mock()
mock_response.status_code = 200
with patch.object(gitea_client.session, 'get', return_value=mock_response):
result = gitea_client.is_org_repo('personal-projects/repo')
assert result is True
# Should call /orgs/personal-projects, not /repos/.../
gitea_client.session.get.assert_called_once_with(
'https://test.com/api/v1/orgs/personal-projects'
)

View File

@@ -1,163 +0,0 @@
"""
Unit tests for issue tools with branch detection.
"""
import pytest
from unittest.mock import Mock, patch, AsyncMock
from mcp_server.tools.issues import IssueTools
@pytest.fixture
def mock_gitea_client():
"""Fixture providing mocked Gitea client"""
client = Mock()
client.mode = 'project'
return client
@pytest.fixture
def issue_tools(mock_gitea_client):
"""Fixture providing IssueTools instance"""
return IssueTools(mock_gitea_client)
@pytest.mark.asyncio
async def test_list_issues_development_branch(issue_tools):
"""Test listing issues on development branch (allowed)"""
with patch.object(issue_tools, '_get_current_branch', return_value='feat/test-feature'):
issue_tools.gitea.list_issues = Mock(return_value=[{'number': 1}])
issues = await issue_tools.list_issues(state='open')
assert len(issues) == 1
issue_tools.gitea.list_issues.assert_called_once()
@pytest.mark.asyncio
async def test_create_issue_development_branch(issue_tools):
"""Test creating issue on development branch (allowed)"""
with patch.object(issue_tools, '_get_current_branch', return_value='development'):
issue_tools.gitea.create_issue = Mock(return_value={'number': 1})
issue = await issue_tools.create_issue('Test', 'Body')
assert issue['number'] == 1
issue_tools.gitea.create_issue.assert_called_once()
@pytest.mark.asyncio
async def test_create_issue_main_branch_blocked(issue_tools):
"""Test creating issue on main branch (blocked)"""
with patch.object(issue_tools, '_get_current_branch', return_value='main'):
with pytest.raises(PermissionError) as exc_info:
await issue_tools.create_issue('Test', 'Body')
assert "Cannot create issues on branch 'main'" in str(exc_info.value)
@pytest.mark.asyncio
async def test_create_issue_staging_branch_allowed(issue_tools):
"""Test creating issue on staging branch (allowed for documentation)"""
with patch.object(issue_tools, '_get_current_branch', return_value='staging'):
issue_tools.gitea.create_issue = Mock(return_value={'number': 1})
issue = await issue_tools.create_issue('Test', 'Body')
assert issue['number'] == 1
@pytest.mark.asyncio
async def test_update_issue_main_branch_blocked(issue_tools):
"""Test updating issue on main branch (blocked)"""
with patch.object(issue_tools, '_get_current_branch', return_value='main'):
with pytest.raises(PermissionError) as exc_info:
await issue_tools.update_issue(1, title='Updated')
assert "Cannot update issues on branch 'main'" in str(exc_info.value)
@pytest.mark.asyncio
async def test_list_issues_main_branch_allowed(issue_tools):
"""Test listing issues on main branch (allowed - read-only)"""
with patch.object(issue_tools, '_get_current_branch', return_value='main'):
issue_tools.gitea.list_issues = Mock(return_value=[{'number': 1}])
issues = await issue_tools.list_issues(state='open')
assert len(issues) == 1
@pytest.mark.asyncio
async def test_get_issue(issue_tools):
"""Test getting specific issue"""
with patch.object(issue_tools, '_get_current_branch', return_value='development'):
issue_tools.gitea.get_issue = Mock(return_value={'number': 1, 'title': 'Test'})
issue = await issue_tools.get_issue(1)
assert issue['number'] == 1
@pytest.mark.asyncio
async def test_add_comment(issue_tools):
"""Test adding comment to issue"""
with patch.object(issue_tools, '_get_current_branch', return_value='development'):
issue_tools.gitea.add_comment = Mock(return_value={'body': 'Test comment'})
comment = await issue_tools.add_comment(1, 'Test comment')
assert comment['body'] == 'Test comment'
@pytest.mark.asyncio
async def test_aggregate_issues_company_mode(issue_tools):
"""Test aggregating issues in company mode"""
issue_tools.gitea.mode = 'company'
with patch.object(issue_tools, '_get_current_branch', return_value='development'):
issue_tools.gitea.aggregate_issues = Mock(return_value={
'repo1': [{'number': 1}],
'repo2': [{'number': 2}]
})
aggregated = await issue_tools.aggregate_issues(org='test_owner')
assert 'repo1' in aggregated
assert 'repo2' in aggregated
@pytest.mark.asyncio
async def test_aggregate_issues_project_mode(issue_tools):
"""Test that aggregate_issues works in project mode with org argument"""
issue_tools.gitea.mode = 'project'
with patch.object(issue_tools, '_get_current_branch', return_value='development'):
issue_tools.gitea.aggregate_issues = Mock(return_value={
'repo1': [{'number': 1}]
})
# aggregate_issues now works in any mode when org is provided
aggregated = await issue_tools.aggregate_issues(org='test_owner')
assert 'repo1' in aggregated
def test_branch_detection():
"""Test branch detection logic"""
tools = IssueTools(Mock())
# Test development branches
with patch.object(tools, '_get_current_branch', return_value='development'):
assert tools._check_branch_permissions('create_issue') is True
with patch.object(tools, '_get_current_branch', return_value='feat/new-feature'):
assert tools._check_branch_permissions('create_issue') is True
# Test production branches
with patch.object(tools, '_get_current_branch', return_value='main'):
assert tools._check_branch_permissions('create_issue') is False
assert tools._check_branch_permissions('list_issues') is True
# Test staging branches
with patch.object(tools, '_get_current_branch', return_value='staging'):
assert tools._check_branch_permissions('create_issue') is True
assert tools._check_branch_permissions('update_issue') is False

View File

@@ -1,478 +0,0 @@
"""
Unit tests for label tools with suggestion logic.
"""
import pytest
from unittest.mock import Mock, patch
from mcp_server.tools.labels import LabelTools
@pytest.fixture
def mock_gitea_client():
"""Fixture providing mocked Gitea client"""
client = Mock()
client.repo = 'test_org/test_repo'
client.is_org_repo = Mock(return_value=True)
return client
@pytest.fixture
def label_tools(mock_gitea_client):
"""Fixture providing LabelTools instance"""
return LabelTools(mock_gitea_client)
@pytest.mark.asyncio
async def test_get_labels(label_tools):
"""Test getting all labels (org + repo)"""
label_tools.gitea.get_org_labels = Mock(return_value=[
{'name': 'Type/Bug'},
{'name': 'Type/Feature'}
])
label_tools.gitea.get_labels = Mock(return_value=[
{'name': 'Component/Backend'},
{'name': 'Component/Frontend'}
])
result = await label_tools.get_labels()
assert len(result['organization']) == 2
assert len(result['repository']) == 2
assert result['total_count'] == 4
# ========================================
# LABEL LOOKUP TESTS (NEW)
# ========================================
def test_build_label_lookup_slash_format():
"""Test building label lookup with slash format labels"""
mock_client = Mock()
mock_client.repo = 'test/repo'
tools = LabelTools(mock_client)
labels = ['Type/Bug', 'Type/Feature', 'Priority/High', 'Priority/Low']
lookup = tools._build_label_lookup(labels)
assert 'type' in lookup
assert 'bug' in lookup['type']
assert lookup['type']['bug'] == 'Type/Bug'
assert lookup['type']['feature'] == 'Type/Feature'
assert 'priority' in lookup
assert lookup['priority']['high'] == 'Priority/High'
def test_build_label_lookup_colon_space_format():
"""Test building label lookup with colon-space format labels"""
mock_client = Mock()
mock_client.repo = 'test/repo'
tools = LabelTools(mock_client)
labels = ['Type: Bug', 'Type: Feature', 'Priority: High', 'Effort: M']
lookup = tools._build_label_lookup(labels)
assert 'type' in lookup
assert 'bug' in lookup['type']
assert lookup['type']['bug'] == 'Type: Bug'
assert lookup['type']['feature'] == 'Type: Feature'
assert 'priority' in lookup
assert lookup['priority']['high'] == 'Priority: High'
# Test singular "Effort" (not "Efforts")
assert 'effort' in lookup
assert lookup['effort']['m'] == 'Effort: M'
def test_build_label_lookup_efforts_normalization():
"""Test that 'Efforts' is normalized to 'effort' for matching"""
mock_client = Mock()
mock_client.repo = 'test/repo'
tools = LabelTools(mock_client)
labels = ['Efforts/XS', 'Efforts/S', 'Efforts/M']
lookup = tools._build_label_lookup(labels)
# 'Efforts' should be normalized to 'effort'
assert 'effort' in lookup
assert lookup['effort']['xs'] == 'Efforts/XS'
def test_find_label():
"""Test finding labels from lookup"""
mock_client = Mock()
mock_client.repo = 'test/repo'
tools = LabelTools(mock_client)
lookup = {
'type': {'bug': 'Type: Bug', 'feature': 'Type: Feature'},
'priority': {'high': 'Priority: High', 'low': 'Priority: Low'}
}
assert tools._find_label(lookup, 'type', 'bug') == 'Type: Bug'
assert tools._find_label(lookup, 'priority', 'high') == 'Priority: High'
assert tools._find_label(lookup, 'type', 'nonexistent') is None
assert tools._find_label(lookup, 'nonexistent', 'bug') is None
# ========================================
# SUGGEST LABELS WITH DYNAMIC FORMAT TESTS
# ========================================
def _create_tools_with_labels(labels):
"""Helper to create LabelTools with mocked labels"""
import asyncio
mock_client = Mock()
mock_client.repo = 'test/repo'
mock_client.is_org_repo = Mock(return_value=False)
mock_client.get_labels = Mock(return_value=[{'name': l} for l in labels])
return LabelTools(mock_client)
@pytest.mark.asyncio
async def test_suggest_labels_with_slash_format():
"""Test label suggestion with slash format labels"""
labels = [
'Type/Bug', 'Type/Feature', 'Type/Refactor',
'Priority/Critical', 'Priority/High', 'Priority/Medium', 'Priority/Low',
'Complexity/Simple', 'Complexity/Medium', 'Complexity/Complex',
'Component/Auth'
]
tools = _create_tools_with_labels(labels)
context = "Fix critical bug in login authentication"
suggestions = await tools.suggest_labels(context)
assert 'Type/Bug' in suggestions
assert 'Priority/Critical' in suggestions
assert 'Component/Auth' in suggestions
@pytest.mark.asyncio
async def test_suggest_labels_with_colon_space_format():
"""Test label suggestion with colon-space format labels"""
labels = [
'Type: Bug', 'Type: Feature', 'Type: Refactor',
'Priority: Critical', 'Priority: High', 'Priority: Medium', 'Priority: Low',
'Complexity: Simple', 'Complexity: Medium', 'Complexity: Complex',
'Effort: XS', 'Effort: S', 'Effort: M', 'Effort: L', 'Effort: XL'
]
tools = _create_tools_with_labels(labels)
context = "Fix critical bug for tiny 1 hour fix"
suggestions = await tools.suggest_labels(context)
# Should return colon-space format labels
assert 'Type: Bug' in suggestions
assert 'Priority: Critical' in suggestions
assert 'Effort: XS' in suggestions
@pytest.mark.asyncio
async def test_suggest_labels_bug():
"""Test label suggestion for bug context"""
labels = [
'Type/Bug', 'Type/Feature',
'Priority/Critical', 'Priority/High', 'Priority/Medium', 'Priority/Low',
'Complexity/Simple', 'Complexity/Medium', 'Complexity/Complex',
'Component/Auth'
]
tools = _create_tools_with_labels(labels)
context = "Fix critical bug in login authentication"
suggestions = await tools.suggest_labels(context)
assert 'Type/Bug' in suggestions
assert 'Priority/Critical' in suggestions
assert 'Component/Auth' in suggestions
@pytest.mark.asyncio
async def test_suggest_labels_feature():
"""Test label suggestion for feature context"""
labels = ['Type/Feature', 'Priority/Medium', 'Complexity/Medium']
tools = _create_tools_with_labels(labels)
context = "Add new feature to implement user dashboard"
suggestions = await tools.suggest_labels(context)
assert 'Type/Feature' in suggestions
assert any('Priority' in label for label in suggestions)
@pytest.mark.asyncio
async def test_suggest_labels_refactor():
"""Test label suggestion for refactor context"""
labels = ['Type/Refactor', 'Priority/Medium', 'Complexity/Medium', 'Component/Backend']
tools = _create_tools_with_labels(labels)
context = "Refactor architecture to extract service layer"
suggestions = await tools.suggest_labels(context)
assert 'Type/Refactor' in suggestions
assert 'Component/Backend' in suggestions
@pytest.mark.asyncio
async def test_suggest_labels_documentation():
"""Test label suggestion for documentation context"""
labels = ['Type/Documentation', 'Priority/Medium', 'Complexity/Medium', 'Component/API', 'Component/Docs']
tools = _create_tools_with_labels(labels)
context = "Update documentation for API endpoints"
suggestions = await tools.suggest_labels(context)
assert 'Type/Documentation' in suggestions
assert 'Component/API' in suggestions or 'Component/Docs' in suggestions
@pytest.mark.asyncio
async def test_suggest_labels_priority():
"""Test priority detection in suggestions"""
labels = ['Type/Feature', 'Priority/Critical', 'Priority/High', 'Priority/Medium', 'Priority/Low', 'Complexity/Medium']
tools = _create_tools_with_labels(labels)
# Critical priority
context = "Urgent blocker in production"
suggestions = await tools.suggest_labels(context)
assert 'Priority/Critical' in suggestions
# High priority
context = "Important feature needed asap"
suggestions = await tools.suggest_labels(context)
assert 'Priority/High' in suggestions
# Low priority
context = "Nice-to-have optional improvement"
suggestions = await tools.suggest_labels(context)
assert 'Priority/Low' in suggestions
@pytest.mark.asyncio
async def test_suggest_labels_complexity():
"""Test complexity detection in suggestions"""
labels = ['Type/Feature', 'Priority/Medium', 'Complexity/Simple', 'Complexity/Medium', 'Complexity/Complex']
tools = _create_tools_with_labels(labels)
# Simple complexity
context = "Simple quick fix for typo"
suggestions = await tools.suggest_labels(context)
assert 'Complexity/Simple' in suggestions
# Complex complexity
context = "Complex challenging architecture redesign"
suggestions = await tools.suggest_labels(context)
assert 'Complexity/Complex' in suggestions
@pytest.mark.asyncio
async def test_suggest_labels_efforts():
"""Test efforts detection in suggestions"""
labels = ['Type/Feature', 'Priority/Medium', 'Complexity/Medium', 'Efforts/XS', 'Efforts/S', 'Efforts/M', 'Efforts/L', 'Efforts/XL']
tools = _create_tools_with_labels(labels)
# XS effort
context = "Tiny fix that takes 1 hour"
suggestions = await tools.suggest_labels(context)
assert 'Efforts/XS' in suggestions
# L effort
context = "Large feature taking 1 week"
suggestions = await tools.suggest_labels(context)
assert 'Efforts/L' in suggestions
@pytest.mark.asyncio
async def test_suggest_labels_components():
"""Test component detection in suggestions"""
labels = ['Type/Feature', 'Priority/Medium', 'Complexity/Medium', 'Component/Backend', 'Component/Frontend', 'Component/API', 'Component/Database']
tools = _create_tools_with_labels(labels)
# Backend component
context = "Update backend API service"
suggestions = await tools.suggest_labels(context)
assert 'Component/Backend' in suggestions
assert 'Component/API' in suggestions
# Frontend component
context = "Fix frontend UI component"
suggestions = await tools.suggest_labels(context)
assert 'Component/Frontend' in suggestions
# Database component
context = "Add database migration for schema"
suggestions = await tools.suggest_labels(context)
assert 'Component/Database' in suggestions
@pytest.mark.asyncio
async def test_suggest_labels_tech_stack():
"""Test tech stack detection in suggestions"""
labels = ['Type/Feature', 'Priority/Medium', 'Complexity/Medium', 'Tech/Python', 'Tech/FastAPI', 'Tech/Docker', 'Tech/PostgreSQL']
tools = _create_tools_with_labels(labels)
# Python
context = "Update Python FastAPI endpoint"
suggestions = await tools.suggest_labels(context)
assert 'Tech/Python' in suggestions
assert 'Tech/FastAPI' in suggestions
# Docker
context = "Fix Dockerfile configuration"
suggestions = await tools.suggest_labels(context)
assert 'Tech/Docker' in suggestions
# PostgreSQL
context = "Optimize PostgreSQL query"
suggestions = await tools.suggest_labels(context)
assert 'Tech/PostgreSQL' in suggestions
@pytest.mark.asyncio
async def test_suggest_labels_source():
"""Test source detection in suggestions"""
labels = ['Type/Feature', 'Priority/Medium', 'Complexity/Medium', 'Source/Development', 'Source/Staging', 'Source/Production']
tools = _create_tools_with_labels(labels)
# Development
context = "Issue found in development environment"
suggestions = await tools.suggest_labels(context)
assert 'Source/Development' in suggestions
# Production
context = "Critical production issue"
suggestions = await tools.suggest_labels(context)
assert 'Source/Production' in suggestions
@pytest.mark.asyncio
async def test_suggest_labels_risk():
"""Test risk detection in suggestions"""
labels = ['Type/Feature', 'Priority/Medium', 'Complexity/Medium', 'Risk/High', 'Risk/Low']
tools = _create_tools_with_labels(labels)
# High risk
context = "Breaking change to major API"
suggestions = await tools.suggest_labels(context)
assert 'Risk/High' in suggestions
# Low risk
context = "Safe minor update with low risk"
suggestions = await tools.suggest_labels(context)
assert 'Risk/Low' in suggestions
@pytest.mark.asyncio
async def test_suggest_labels_multiple_categories():
"""Test that suggestions span multiple categories"""
labels = [
'Type/Bug', 'Type/Feature',
'Priority/Critical', 'Priority/Medium',
'Complexity/Complex', 'Complexity/Medium',
'Component/Backend', 'Component/API', 'Component/Auth',
'Tech/FastAPI', 'Tech/PostgreSQL',
'Source/Production'
]
tools = _create_tools_with_labels(labels)
context = """
Urgent critical bug in production backend API service.
Need to fix broken authentication endpoint.
This is a complex issue requiring FastAPI and PostgreSQL expertise.
"""
suggestions = await tools.suggest_labels(context)
# Should have Type
assert any('Type/' in label for label in suggestions)
# Should have Priority
assert any('Priority/' in label for label in suggestions)
# Should have Component
assert any('Component/' in label for label in suggestions)
# Should have Tech
assert any('Tech/' in label for label in suggestions)
# Should have Source
assert any('Source/' in label for label in suggestions)
@pytest.mark.asyncio
async def test_suggest_labels_empty_repo():
"""Test suggestions when no repo specified and no labels available"""
mock_client = Mock()
mock_client.repo = None
tools = LabelTools(mock_client)
context = "Fix a bug"
suggestions = await tools.suggest_labels(context)
# Should return empty list when no repo
assert suggestions == []
@pytest.mark.asyncio
async def test_suggest_labels_no_matching_labels():
"""Test suggestions return empty when no matching labels exist"""
labels = ['Custom/Label', 'Other/Thing'] # No standard labels
tools = _create_tools_with_labels(labels)
context = "Fix a bug"
suggestions = await tools.suggest_labels(context)
# Should return empty list since no Type/Bug or similar exists
assert len(suggestions) == 0
@pytest.mark.asyncio
async def test_get_labels_org_owned_repo():
"""Test getting labels for organization-owned repository"""
mock_client = Mock()
mock_client.repo = 'myorg/myrepo'
mock_client.is_org_repo = Mock(return_value=True)
mock_client.get_org_labels = Mock(return_value=[
{'name': 'Type/Bug', 'id': 1},
{'name': 'Type/Feature', 'id': 2}
])
mock_client.get_labels = Mock(return_value=[
{'name': 'Component/Backend', 'id': 3}
])
tools = LabelTools(mock_client)
result = await tools.get_labels()
# Should fetch both org and repo labels
mock_client.is_org_repo.assert_called_once_with('myorg/myrepo')
mock_client.get_org_labels.assert_called_once_with('myorg')
mock_client.get_labels.assert_called_once_with('myorg/myrepo')
assert len(result['organization']) == 2
assert len(result['repository']) == 1
assert result['total_count'] == 3
@pytest.mark.asyncio
async def test_get_labels_user_owned_repo():
"""Test getting labels for user-owned repository (no org labels)"""
mock_client = Mock()
mock_client.repo = 'lmiranda/personal-portfolio'
mock_client.is_org_repo = Mock(return_value=False)
mock_client.get_labels = Mock(return_value=[
{'name': 'bug', 'id': 1},
{'name': 'enhancement', 'id': 2}
])
tools = LabelTools(mock_client)
result = await tools.get_labels()
# Should check if org repo
mock_client.is_org_repo.assert_called_once_with('lmiranda/personal-portfolio')
# Should NOT call get_org_labels for user-owned repos
mock_client.get_org_labels.assert_not_called()
# Should still get repo labels
mock_client.get_labels.assert_called_once_with('lmiranda/personal-portfolio')
assert len(result['organization']) == 0
assert len(result['repository']) == 2
assert result['total_count'] == 2

View File

@@ -1,19 +1,17 @@
# NetBox MCP Server # NetBox MCP Server
MCP (Model Context Protocol) server for comprehensive NetBox API integration with Claude Code. MCP (Model Context Protocol) server for essential NetBox API integration with Claude Code.
## Overview ## Overview
This MCP server provides Claude Code with full access to the NetBox REST API, enabling infrastructure management, documentation, and automation workflows. It covers all major NetBox application areas: This MCP server provides Claude Code with focused access to the NetBox REST API for tracking **servers, services, IP addresses, and databases**. It has been optimized to include only essential tools:
- **DCIM** - Sites, Locations, Racks, Devices, Interfaces, Cables, Power - **DCIM** - Sites, Devices (servers/VPS), Interfaces
- **IPAM** - IP Addresses, Prefixes, VLANs, VRFs, ASNs, Services - **IPAM** - IP Addresses, Prefixes, Services (applications/databases)
- **Circuits** - Providers, Circuits, Terminations
- **Virtualization** - Clusters, Virtual Machines, VM Interfaces - **Virtualization** - Clusters, Virtual Machines, VM Interfaces
- **Tenancy** - Tenants, Contacts, Contact Assignments - **Extras** - Tags, Journal Entries (audit/notes)
- **VPN** - Tunnels, IKE/IPSec Policies, L2VPN
- **Wireless** - Wireless LANs, Links, Groups **Total:** 37 tools (~3,700 tokens) — down from 182 tools (~19,810 tokens).
- **Extras** - Tags, Custom Fields, Webhooks, Config Contexts, Audit Log
## Installation ## Installation
@@ -49,312 +47,227 @@ EOF
### 3. Register with Claude Code ### 3. Register with Claude Code
Add to your Claude Code MCP configuration (`~/.config/claude/mcp.json` or project `.mcp.json`): Add to your Claude Code MCP configuration (`.claude/mcp.json` or project-level `.mcp.json`):
```json ```json
{ {
"mcpServers": { "mcpServers": {
"netbox": { "netbox": {
"command": "/path/to/mcp-servers/netbox/.venv/bin/python", "command": "/path/to/mcp-servers/netbox/.venv/bin/python",
"args": ["-m", "mcp_server.server"], "args": ["-m", "mcp_server"],
"cwd": "/path/to/mcp-servers/netbox" "cwd": "/path/to/mcp-servers/netbox"
} }
} }
} }
``` ```
**Windows:**
```json
{
"mcpServers": {
"netbox": {
"command": "C:\\path\\to\\mcp-servers\\netbox\\.venv\\Scripts\\python.exe",
"args": ["-m", "mcp_server"],
"cwd": "C:\\path\\to\\mcp-servers\\netbox"
}
}
}
```
## Available Tools (37 Total)
### DCIM: Sites, Devices, Interfaces (11 tools)
**Sites (4):**
| Tool | Description | Parameters |
|------|-------------|-----------|
| `dcim_list_sites` | List sites | `name`, `status` |
| `dcim_get_site` | Get site by ID | `id` (required) |
| `dcim_create_site` | Create site | `name`, `slug` (required), `status` |
| `dcim_update_site` | Update site | `id` (required), fields to update |
**Devices (4):**
| Tool | Description | Parameters |
|------|-------------|-----------|
| `dcim_list_devices` | List devices (servers/VPS) | `name`, `site_id`, `status`, `role_id` |
| `dcim_get_device` | Get device by ID | `id` (required) |
| `dcim_create_device` | Create device | `name`, `device_type`, `role`, `site` (required) |
| `dcim_update_device` | Update device | `id` (required), fields to update |
**Interfaces (3):**
| Tool | Description | Parameters |
|------|-------------|-----------|
| `dcim_list_interfaces` | List device interfaces | `device_id`, `name`, `type` |
| `dcim_get_interface` | Get interface by ID | `id` (required) |
| `dcim_create_interface` | Create interface | `device`, `name`, `type` (required) |
### IPAM: IPs, Prefixes, Services (10 tools)
**IP Addresses (4):**
| Tool | Description | Parameters |
|------|-------------|-----------|
| `ipam_list_ip_addresses` | List IP addresses | `address`, `device_id`, `status` |
| `ipam_get_ip_address` | Get IP by ID | `id` (required) |
| `ipam_create_ip_address` | Create IP address | `address` (required), `status`, `assigned_object_type` |
| `ipam_update_ip_address` | Update IP address | `id` (required), fields to update |
**Prefixes (3):**
| Tool | Description | Parameters |
|------|-------------|-----------|
| `ipam_list_prefixes` | List prefixes | `prefix`, `site_id`, `status` |
| `ipam_get_prefix` | Get prefix by ID | `id` (required) |
| `ipam_create_prefix` | Create prefix | `prefix` (required), `status`, `site` |
**Services (3):**
| Tool | Description | Parameters |
|------|-------------|-----------|
| `ipam_list_services` | List services (apps/databases) | `device_id`, `virtual_machine_id`, `name` |
| `ipam_get_service` | Get service by ID | `id` (required) |
| `ipam_create_service` | Create service | `name`, `ports`, `protocol` (required), `device`, `virtual_machine` |
### Virtualization: Clusters, VMs, VM Interfaces (10 tools)
**Clusters (3):**
| Tool | Description | Parameters |
|------|-------------|-----------|
| `virt_list_clusters` | List virtualization clusters | `name`, `site_id` |
| `virt_get_cluster` | Get cluster by ID | `id` (required) |
| `virt_create_cluster` | Create cluster | `name`, `type` (required), `site` |
**Virtual Machines (4):**
| Tool | Description | Parameters |
|------|-------------|-----------|
| `virt_list_vms` | List VMs | `name`, `cluster_id`, `site_id`, `status` |
| `virt_get_vm` | Get VM by ID | `id` (required) |
| `virt_create_vm` | Create VM | `name`, `cluster` (required), `vcpus`, `memory`, `disk` |
| `virt_update_vm` | Update VM | `id` (required), fields to update |
**VM Interfaces (3):**
| Tool | Description | Parameters |
|------|-------------|-----------|
| `virt_list_vm_ifaces` | List VM interfaces | `virtual_machine_id` |
| `virt_get_vm_iface` | Get VM interface by ID | `id` (required) |
| `virt_create_vm_iface` | Create VM interface | `virtual_machine`, `name` (required) |
### Extras: Tags, Journal Entries (6 tools)
**Tags (3):**
| Tool | Description | Parameters |
|------|-------------|-----------|
| `extras_list_tags` | List tags | `name` |
| `extras_get_tag` | Get tag by ID | `id` (required) |
| `extras_create_tag` | Create tag | `name`, `slug` (required), `color` |
**Journal Entries (3):**
| Tool | Description | Parameters |
|------|-------------|-----------|
| `extras_list_journal_entries` | List journal entries | `assigned_object_type`, `assigned_object_id` |
| `extras_get_journal_entry` | Get journal entry by ID | `id` (required) |
| `extras_create_journal_entry` | Create journal entry | `assigned_object_type`, `assigned_object_id`, `comments` (required), `kind` |
## Configuration ## Configuration
### Environment Variables All configuration is done via environment variables in `~/.config/claude/netbox.env`:
| Variable | Required | Default | Description | | Variable | Required | Default | Description |
|----------|----------|---------|-------------| |----------|----------|---------|-------------|
| `NETBOX_API_URL` | Yes | - | Full URL to NetBox API (e.g., `https://netbox.example.com/api`) | | `NETBOX_API_URL` | Yes | | NetBox API URL (e.g., https://netbox.example.com/api) |
| `NETBOX_API_TOKEN` | Yes | - | API authentication token | | `NETBOX_API_TOKEN` | Yes | | NetBox API token |
| `NETBOX_VERIFY_SSL` | No | `true` | Verify SSL certificates | | `NETBOX_VERIFY_SSL` | No | true | Verify SSL certificates |
| `NETBOX_TIMEOUT` | No | `30` | Request timeout in seconds | | `NETBOX_TIMEOUT` | No | 30 | Request timeout in seconds |
### Configuration Hierarchy
1. **System-level** (`~/.config/claude/netbox.env`): Credentials and defaults
2. **Project-level** (`.env` in current directory): Optional overrides
## Module Filtering (Token Optimization)
By default, the NetBox MCP server registers all 182 tools across 8 modules, consuming ~19,810 tokens of context. For most workflows, you only need a subset of modules.
### Configuration
Add `NETBOX_ENABLED_MODULES` to your `~/.config/claude/netbox.env`:
```bash
# Enable only specific modules (comma-separated)
NETBOX_ENABLED_MODULES=dcim,ipam,virtualization,extras
```
If unset, all modules are enabled (backward compatible).
### Available Modules
| Module | Tool Count | Description | cmdb-assistant Commands |
|--------|------------|-------------|------------------------|
| `dcim` | ~60 | Sites, devices, racks, interfaces, cables | `/cmdb-device`, `/cmdb-site`, `/cmdb-search`, `/cmdb-topology` |
| `ipam` | ~40 | IP addresses, prefixes, VLANs, VRFs | `/cmdb-ip`, `/ip-conflicts`, `/cmdb-search` |
| `virtualization` | ~20 | Clusters, VMs, VM interfaces | `/cmdb-search`, `/cmdb-audit`, `/cmdb-register` |
| `extras` | ~12 | Tags, journal entries, audit log | `/change-audit`, `/cmdb-register` |
| `circuits` | ~15 | Providers, circuits, terminations | — |
| `tenancy` | ~12 | Tenants, contacts | — |
| `vpn` | ~15 | Tunnels, IKE/IPSec policies, L2VPN | — |
| `wireless` | ~8 | Wireless LANs, links, groups | — |
### Recommended Configurations
**For cmdb-assistant users** (~43 tools, ~4,500 tokens):
```bash
NETBOX_ENABLED_MODULES=dcim,ipam,virtualization,extras
```
**Basic infrastructure** (~100 tools):
```bash
NETBOX_ENABLED_MODULES=dcim,ipam
```
**Full CMDB** (all modules, ~182 tools):
```bash
# Omit NETBOX_ENABLED_MODULES or set to all modules
NETBOX_ENABLED_MODULES=dcim,ipam,circuits,virtualization,tenancy,vpn,wireless,extras
```
### Startup Logging
On startup, the server logs enabled modules and tool count:
```
NetBox MCP Server initialized: 43 tools registered (modules: dcim, extras, ipam, virtualization)
```
### Disabled Tool Behavior
Calling a tool from a disabled module returns a clear error:
```
Tool 'circuits_list_circuits' is not available (module 'circuits' not enabled).
Enabled modules: dcim, extras, ipam, virtualization
```
## Available Tools
### DCIM (Data Center Infrastructure Management)
| Tool | Description |
|------|-------------|
| `dcim_list_sites` | List all sites |
| `dcim_get_site` | Get site details |
| `dcim_create_site` | Create a new site |
| `dcim_update_site` | Update a site |
| `dcim_delete_site` | Delete a site |
| `dcim_list_devices` | List all devices |
| `dcim_get_device` | Get device details |
| `dcim_create_device` | Create a new device |
| `dcim_update_device` | Update a device |
| `dcim_delete_device` | Delete a device |
| `dcim_list_interfaces` | List device interfaces |
| `dcim_create_interface` | Create an interface |
| `dcim_list_racks` | List all racks |
| `dcim_create_rack` | Create a new rack |
| `dcim_list_cables` | List all cables |
| `dcim_create_cable` | Create a cable connection |
| ... and many more |
### IPAM (IP Address Management)
| Tool | Description |
|------|-------------|
| `ipam_list_prefixes` | List IP prefixes |
| `ipam_create_prefix` | Create a prefix |
| `ipam_list_available_prefixes` | List available child prefixes |
| `ipam_create_available_prefix` | Auto-allocate a prefix |
| `ipam_list_ip_addresses` | List IP addresses |
| `ipam_create_ip_address` | Create an IP address |
| `ipam_list_available_ips` | List available IPs in prefix |
| `ipam_create_available_ip` | Auto-allocate an IP |
| `ipam_list_vlans` | List VLANs |
| `ipam_create_vlan` | Create a VLAN |
| `ipam_list_vrfs` | List VRFs |
| ... and many more |
### Circuits
| Tool | Description |
|------|-------------|
| `circuits_list_providers` | List circuit providers |
| `circuits_create_provider` | Create a provider |
| `circuits_list_circuits` | List circuits |
| `circuits_create_circuit` | Create a circuit |
| `circ_list_terminations` | List terminations |
| ... and more |
### Virtualization
| Tool | Description |
|------|-------------|
| `virt_list_clusters` | List clusters |
| `virt_create_cluster` | Create a cluster |
| `virt_list_vms` | List VMs |
| `virt_create_vm` | Create a VM |
| `virt_list_vm_ifaces` | List VM interfaces |
| ... and more |
### Tenancy
| Tool | Description |
|------|-------------|
| `tenancy_list_tenants` | List tenants |
| `tenancy_create_tenant` | Create a tenant |
| `tenancy_list_contacts` | List contacts |
| `tenancy_create_contact` | Create a contact |
| ... and more |
### VPN
| Tool | Description |
|------|-------------|
| `vpn_list_tunnels` | List VPN tunnels |
| `vpn_create_tunnel` | Create a tunnel |
| `vpn_list_l2vpns` | List L2VPNs |
| `vpn_list_ike_policies` | List IKE policies |
| `vpn_list_ipsec_policies` | List IPSec policies |
| ... and more |
### Wireless
| Tool | Description |
|------|-------------|
| `wlan_list_lans` | List wireless LANs |
| `wlan_create_lan` | Create a WLAN |
| `wlan_list_links` | List wireless links |
| ... and more |
### Extras
| Tool | Description |
|------|-------------|
| `extras_list_tags` | List all tags |
| `extras_create_tag` | Create a tag |
| `extras_list_custom_fields` | List custom fields |
| `extras_list_webhooks` | List webhooks |
| `extras_list_journal_entries` | List journal entries |
| `extras_create_journal_entry` | Create journal entry |
| `extras_list_object_changes` | View audit log |
| `extras_list_config_contexts` | List config contexts |
| ... and more |
## Usage Examples
### List all devices at a site
```
Use the dcim_list_devices tool with site_id filter to see all devices at site 5
```
### Create a new prefix and allocate IPs
```
1. Use ipam_create_prefix to create 10.0.1.0/24
2. Use ipam_list_available_ips with the prefix ID to see available addresses
3. Use ipam_create_available_ip to auto-allocate the next IP
```
### Document a new server
```
1. Use dcim_create_device to create the device
2. Use dcim_create_interface to add network interfaces
3. Use ipam_create_ip_address to assign IPs to interfaces
4. Use extras_create_journal_entry to add notes
```
### Audit recent changes
```
Use extras_list_object_changes to see recent modifications in NetBox
```
## Architecture ## Architecture
### Hybrid Configuration
- **System-level:** `~/.config/claude/netbox.env` (credentials)
- **Project-level:** `.env` (optional overrides)
### Tool Routing
Tool names follow the pattern `{module}_{action}_{resource}`:
- `dcim_list_sites` → DCIMTools.list_sites()
- `ipam_create_service` → IPAMTools.create_service()
- `virt_list_vms` → VirtualizationTools.list_virtual_machines()
Shortened names (virt_*) are mapped via TOOL_NAME_MAP to meet the 28-character MCP limit.
### Error Handling
All tools return JSON responses. Errors are caught and returned as:
```json
{
"error": "Error message",
"status_code": 404
}
``` ```
mcp-servers/netbox/
## Development
### Testing
```bash
# Test import
python -c "from mcp_server.server import NetBoxMCPServer; print('OK')"
# Test tool count
python -c "from mcp_server.server import TOOL_DEFINITIONS; print(f'{len(TOOL_DEFINITIONS)} tools')"
```
### File Structure
```
netbox/
├── mcp_server/ ├── mcp_server/
│ ├── __init__.py │ ├── __init__.py
│ ├── server.py # Main MCP server (37 TOOL_DEFINITIONS)
│ ├── config.py # Configuration loader │ ├── config.py # Configuration loader
│ ├── netbox_client.py # Generic HTTP client │ ├── netbox_client.py # HTTP client wrapper
│ ├── server.py # MCP server entry point
│ └── tools/ │ └── tools/
│ ├── __init__.py │ ├── __init__.py
│ ├── dcim.py # DCIM operations │ ├── dcim.py # Sites, Devices, Interfaces
│ ├── ipam.py # IPAM operations │ ├── ipam.py # IPs, Prefixes, Services
│ ├── circuits.py # Circuits operations │ ├── virtualization.py # Clusters, VMs, VM Interfaces
── virtualization.py ── extras.py # Tags, Journal Entries
│ ├── tenancy.py ├── .venv/ # Python virtual environment
│ ├── vpn.py
│ ├── wireless.py
│ └── extras.py
├── tests/
│ └── __init__.py
├── requirements.txt ├── requirements.txt
└── README.md └── README.md
``` ```
## API Coverage
This MCP server provides comprehensive coverage of the NetBox REST API v4.x:
- Full CRUD operations for all major models
- Filtering and search capabilities
- Special endpoints (available prefixes, available IPs)
- Pagination handling (automatic)
- Error handling with detailed messages
## Error Handling
The server returns detailed error messages from the NetBox API, including:
- Validation errors
- Authentication failures
- Not found errors
- Permission errors
## Security Notes
- API tokens should be kept secure and not committed to version control
- Use environment variables or the system config file for credentials
- SSL verification is enabled by default
- Consider using read-only tokens for query-only workflows
## Troubleshooting ## Troubleshooting
### Common Issues ### MCP Server Won't Start
1. **Connection refused**: Check `NETBOX_API_URL` is correct and accessible **Check configuration:**
2. **401 Unauthorized**: Verify your API token is valid ```bash
3. **SSL errors**: Set `NETBOX_VERIFY_SSL=false` for self-signed certs (not recommended for production) cat ~/.config/claude/netbox.env
4. **Timeout errors**: Increase `NETBOX_TIMEOUT` for slow connections
### Debug Mode
Enable debug logging:
```python
import logging
logging.basicConfig(level=logging.DEBUG)
``` ```
## Contributing **Test credentials:**
```bash
curl -H "Authorization: Token YOUR_TOKEN" https://netbox.example.com/api/
```
1. Follow the existing code patterns ### Tools Not Appearing in Claude
2. Add tests for new functionality
3. Update documentation for new tools **Verify MCP registration:**
4. Ensure compatibility with NetBox 4.x API ```bash
cat ~/.claude/mcp.json # or project-level .mcp.json
```
**Check MCP server logs:**
Claude Code will show MCP server stderr in the UI.
### Connection Errors
- Verify `NETBOX_API_URL` ends with `/api`
- Check firewall/network connectivity to NetBox instance
- Ensure API token has required permissions
## License ## License
MIT License - Part of the Leo Claude Marketplace. MIT License - See LICENSE file for details.
## Contributing
This MCP server is part of the leo-claude-mktplace project. For issues or contributions, refer to the main repository.

View File

@@ -9,17 +9,11 @@ from pathlib import Path
from dotenv import load_dotenv from dotenv import load_dotenv
import os import os
import logging import logging
from typing import Dict, List, Optional, Set from typing import Dict, Optional
logging.basicConfig(level=logging.INFO) logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
# All available NetBox modules
ALL_MODULES = frozenset([
'dcim', 'ipam', 'circuits', 'virtualization',
'tenancy', 'vpn', 'wireless', 'extras'
])
class NetBoxConfig: class NetBoxConfig:
"""Configuration loader for NetBox MCP Server""" """Configuration loader for NetBox MCP Server"""
@@ -29,7 +23,6 @@ class NetBoxConfig:
self.api_token: Optional[str] = None self.api_token: Optional[str] = None
self.verify_ssl: bool = True self.verify_ssl: bool = True
self.timeout: int = 30 self.timeout: int = 30
self.enabled_modules: Set[str] = set(ALL_MODULES)
def load(self) -> Dict[str, any]: def load(self) -> Dict[str, any]:
""" """
@@ -80,9 +73,6 @@ class NetBoxConfig:
self.timeout = 30 self.timeout = 30
logger.warning(f"Invalid NETBOX_TIMEOUT value '{timeout_str}', using default 30") logger.warning(f"Invalid NETBOX_TIMEOUT value '{timeout_str}', using default 30")
# Module filtering
self.enabled_modules = self._load_enabled_modules()
# Validate required variables # Validate required variables
self._validate() self._validate()
@@ -94,8 +84,7 @@ class NetBoxConfig:
'api_url': self.api_url, 'api_url': self.api_url,
'api_token': self.api_token, 'api_token': self.api_token,
'verify_ssl': self.verify_ssl, 'verify_ssl': self.verify_ssl,
'timeout': self.timeout, 'timeout': self.timeout
'enabled_modules': self.enabled_modules
} }
def _validate(self) -> None: def _validate(self) -> None:
@@ -117,40 +106,3 @@ class NetBoxConfig:
f"Missing required configuration: {', '.join(missing)}\n" f"Missing required configuration: {', '.join(missing)}\n"
"Check your ~/.config/claude/netbox.env file" "Check your ~/.config/claude/netbox.env file"
) )
def _load_enabled_modules(self) -> Set[str]:
"""
Load enabled modules from NETBOX_ENABLED_MODULES environment variable.
Format: Comma-separated list of module names.
Example: NETBOX_ENABLED_MODULES=dcim,ipam,virtualization,extras
Returns:
Set of enabled module names. If env var is unset/empty, returns all modules.
"""
modules_str = os.getenv('NETBOX_ENABLED_MODULES', '').strip()
if not modules_str:
logger.info("NETBOX_ENABLED_MODULES not set, all modules enabled (default)")
return set(ALL_MODULES)
# Parse comma-separated list, strip whitespace
requested = {m.strip().lower() for m in modules_str.split(',') if m.strip()}
# Validate module names
invalid = requested - ALL_MODULES
if invalid:
logger.warning(
f"Unknown modules in NETBOX_ENABLED_MODULES: {', '.join(sorted(invalid))}. "
f"Valid modules: {', '.join(sorted(ALL_MODULES))}"
)
# Return only valid modules
enabled = requested & ALL_MODULES
if not enabled:
logger.warning("No valid modules enabled, falling back to all modules")
return set(ALL_MODULES)
logger.info(f"Enabled modules: {', '.join(sorted(enabled))}")
return enabled

File diff suppressed because it is too large Load Diff

View File

@@ -1,20 +1,12 @@
"""NetBox MCP tools package.""" """NetBox MCP tools package."""
from .dcim import DCIMTools from .dcim import DCIMTools
from .ipam import IPAMTools from .ipam import IPAMTools
from .circuits import CircuitsTools
from .virtualization import VirtualizationTools from .virtualization import VirtualizationTools
from .tenancy import TenancyTools
from .vpn import VPNTools
from .wireless import WirelessTools
from .extras import ExtrasTools from .extras import ExtrasTools
__all__ = [ __all__ = [
'DCIMTools', 'DCIMTools',
'IPAMTools', 'IPAMTools',
'CircuitsTools',
'VirtualizationTools', 'VirtualizationTools',
'TenancyTools',
'VPNTools',
'WirelessTools',
'ExtrasTools', 'ExtrasTools',
] ]

View File

@@ -1,373 +0,0 @@
"""
Circuits tools for NetBox MCP Server.
Covers: Providers, Circuits, Circuit Types, Circuit Terminations, and related models.
"""
import logging
from typing import List, Dict, Optional, Any
from ..netbox_client import NetBoxClient
logger = logging.getLogger(__name__)
class CircuitsTools:
"""Tools for Circuits operations in NetBox"""
def __init__(self, client: NetBoxClient):
self.client = client
self.base_endpoint = 'circuits'
# ==================== Providers ====================
async def list_providers(
self,
name: Optional[str] = None,
slug: Optional[str] = None,
**kwargs
) -> List[Dict]:
"""List all circuit providers."""
params = {k: v for k, v in {'name': name, 'slug': slug, **kwargs}.items() if v is not None}
return self.client.list(f'{self.base_endpoint}/providers', params=params)
async def get_provider(self, id: int) -> Dict:
"""Get a specific provider by ID."""
return self.client.get(f'{self.base_endpoint}/providers', id)
async def create_provider(
self,
name: str,
slug: str,
asns: Optional[List[int]] = None,
description: Optional[str] = None,
**kwargs
) -> Dict:
"""Create a new provider."""
data = {'name': name, 'slug': slug, **kwargs}
if asns:
data['asns'] = asns
if description:
data['description'] = description
return self.client.create(f'{self.base_endpoint}/providers', data)
async def update_provider(self, id: int, **kwargs) -> Dict:
"""Update a provider."""
return self.client.patch(f'{self.base_endpoint}/providers', id, kwargs)
async def delete_provider(self, id: int) -> None:
"""Delete a provider."""
self.client.delete(f'{self.base_endpoint}/providers', id)
# ==================== Provider Accounts ====================
async def list_provider_accounts(
self,
provider_id: Optional[int] = None,
name: Optional[str] = None,
account: Optional[str] = None,
**kwargs
) -> List[Dict]:
"""List all provider accounts."""
params = {k: v for k, v in {
'provider_id': provider_id, 'name': name, 'account': account, **kwargs
}.items() if v is not None}
return self.client.list(f'{self.base_endpoint}/provider-accounts', params=params)
async def get_provider_account(self, id: int) -> Dict:
"""Get a specific provider account by ID."""
return self.client.get(f'{self.base_endpoint}/provider-accounts', id)
async def create_provider_account(
self,
provider: int,
account: str,
name: Optional[str] = None,
description: Optional[str] = None,
**kwargs
) -> Dict:
"""Create a new provider account."""
data = {'provider': provider, 'account': account, **kwargs}
if name:
data['name'] = name
if description:
data['description'] = description
return self.client.create(f'{self.base_endpoint}/provider-accounts', data)
async def update_provider_account(self, id: int, **kwargs) -> Dict:
"""Update a provider account."""
return self.client.patch(f'{self.base_endpoint}/provider-accounts', id, kwargs)
async def delete_provider_account(self, id: int) -> None:
"""Delete a provider account."""
self.client.delete(f'{self.base_endpoint}/provider-accounts', id)
# ==================== Provider Networks ====================
async def list_provider_networks(
self,
provider_id: Optional[int] = None,
name: Optional[str] = None,
**kwargs
) -> List[Dict]:
"""List all provider networks."""
params = {k: v for k, v in {
'provider_id': provider_id, 'name': name, **kwargs
}.items() if v is not None}
return self.client.list(f'{self.base_endpoint}/provider-networks', params=params)
async def get_provider_network(self, id: int) -> Dict:
"""Get a specific provider network by ID."""
return self.client.get(f'{self.base_endpoint}/provider-networks', id)
async def create_provider_network(
self,
provider: int,
name: str,
service_id: Optional[str] = None,
description: Optional[str] = None,
**kwargs
) -> Dict:
"""Create a new provider network."""
data = {'provider': provider, 'name': name, **kwargs}
if service_id:
data['service_id'] = service_id
if description:
data['description'] = description
return self.client.create(f'{self.base_endpoint}/provider-networks', data)
async def update_provider_network(self, id: int, **kwargs) -> Dict:
"""Update a provider network."""
return self.client.patch(f'{self.base_endpoint}/provider-networks', id, kwargs)
async def delete_provider_network(self, id: int) -> None:
"""Delete a provider network."""
self.client.delete(f'{self.base_endpoint}/provider-networks', id)
# ==================== Circuit Types ====================
async def list_circuit_types(
self,
name: Optional[str] = None,
slug: Optional[str] = None,
**kwargs
) -> List[Dict]:
"""List all circuit types."""
params = {k: v for k, v in {'name': name, 'slug': slug, **kwargs}.items() if v is not None}
return self.client.list(f'{self.base_endpoint}/circuit-types', params=params)
async def get_circuit_type(self, id: int) -> Dict:
"""Get a specific circuit type by ID."""
return self.client.get(f'{self.base_endpoint}/circuit-types', id)
async def create_circuit_type(
self,
name: str,
slug: str,
color: Optional[str] = None,
description: Optional[str] = None,
**kwargs
) -> Dict:
"""Create a new circuit type."""
data = {'name': name, 'slug': slug, **kwargs}
if color:
data['color'] = color
if description:
data['description'] = description
return self.client.create(f'{self.base_endpoint}/circuit-types', data)
async def update_circuit_type(self, id: int, **kwargs) -> Dict:
"""Update a circuit type."""
return self.client.patch(f'{self.base_endpoint}/circuit-types', id, kwargs)
async def delete_circuit_type(self, id: int) -> None:
"""Delete a circuit type."""
self.client.delete(f'{self.base_endpoint}/circuit-types', id)
# ==================== Circuit Groups ====================
async def list_circuit_groups(
self,
name: Optional[str] = None,
slug: Optional[str] = None,
**kwargs
) -> List[Dict]:
"""List all circuit groups."""
params = {k: v for k, v in {'name': name, 'slug': slug, **kwargs}.items() if v is not None}
return self.client.list(f'{self.base_endpoint}/circuit-groups', params=params)
async def get_circuit_group(self, id: int) -> Dict:
"""Get a specific circuit group by ID."""
return self.client.get(f'{self.base_endpoint}/circuit-groups', id)
async def create_circuit_group(
self,
name: str,
slug: str,
description: Optional[str] = None,
**kwargs
) -> Dict:
"""Create a new circuit group."""
data = {'name': name, 'slug': slug, **kwargs}
if description:
data['description'] = description
return self.client.create(f'{self.base_endpoint}/circuit-groups', data)
async def update_circuit_group(self, id: int, **kwargs) -> Dict:
"""Update a circuit group."""
return self.client.patch(f'{self.base_endpoint}/circuit-groups', id, kwargs)
async def delete_circuit_group(self, id: int) -> None:
"""Delete a circuit group."""
self.client.delete(f'{self.base_endpoint}/circuit-groups', id)
# ==================== Circuit Group Assignments ====================
async def list_circuit_group_assignments(
self,
group_id: Optional[int] = None,
circuit_id: Optional[int] = None,
**kwargs
) -> List[Dict]:
"""List all circuit group assignments."""
params = {k: v for k, v in {
'group_id': group_id, 'circuit_id': circuit_id, **kwargs
}.items() if v is not None}
return self.client.list(f'{self.base_endpoint}/circuit-group-assignments', params=params)
async def get_circuit_group_assignment(self, id: int) -> Dict:
"""Get a specific circuit group assignment by ID."""
return self.client.get(f'{self.base_endpoint}/circuit-group-assignments', id)
async def create_circuit_group_assignment(
self,
group: int,
circuit: int,
priority: Optional[str] = None,
**kwargs
) -> Dict:
"""Create a new circuit group assignment."""
data = {'group': group, 'circuit': circuit, **kwargs}
if priority:
data['priority'] = priority
return self.client.create(f'{self.base_endpoint}/circuit-group-assignments', data)
async def update_circuit_group_assignment(self, id: int, **kwargs) -> Dict:
"""Update a circuit group assignment."""
return self.client.patch(f'{self.base_endpoint}/circuit-group-assignments', id, kwargs)
async def delete_circuit_group_assignment(self, id: int) -> None:
"""Delete a circuit group assignment."""
self.client.delete(f'{self.base_endpoint}/circuit-group-assignments', id)
# ==================== Circuits ====================
async def list_circuits(
self,
cid: Optional[str] = None,
provider_id: Optional[int] = None,
provider_account_id: Optional[int] = None,
type_id: Optional[int] = None,
status: Optional[str] = None,
tenant_id: Optional[int] = None,
site_id: Optional[int] = None,
**kwargs
) -> List[Dict]:
"""List all circuits with optional filtering."""
params = {k: v for k, v in {
'cid': cid, 'provider_id': provider_id, 'provider_account_id': provider_account_id,
'type_id': type_id, 'status': status, 'tenant_id': tenant_id, 'site_id': site_id, **kwargs
}.items() if v is not None}
return self.client.list(f'{self.base_endpoint}/circuits', params=params)
async def get_circuit(self, id: int) -> Dict:
"""Get a specific circuit by ID."""
return self.client.get(f'{self.base_endpoint}/circuits', id)
async def create_circuit(
self,
cid: str,
provider: int,
type: int,
status: str = 'active',
provider_account: Optional[int] = None,
tenant: Optional[int] = None,
install_date: Optional[str] = None,
termination_date: Optional[str] = None,
commit_rate: Optional[int] = None,
description: Optional[str] = None,
**kwargs
) -> Dict:
"""Create a new circuit."""
data = {'cid': cid, 'provider': provider, 'type': type, 'status': status, **kwargs}
for key, val in [
('provider_account', provider_account), ('tenant', tenant),
('install_date', install_date), ('termination_date', termination_date),
('commit_rate', commit_rate), ('description', description)
]:
if val is not None:
data[key] = val
return self.client.create(f'{self.base_endpoint}/circuits', data)
async def update_circuit(self, id: int, **kwargs) -> Dict:
"""Update a circuit."""
return self.client.patch(f'{self.base_endpoint}/circuits', id, kwargs)
async def delete_circuit(self, id: int) -> None:
"""Delete a circuit."""
self.client.delete(f'{self.base_endpoint}/circuits', id)
# ==================== Circuit Terminations ====================
async def list_circuit_terminations(
self,
circuit_id: Optional[int] = None,
site_id: Optional[int] = None,
provider_network_id: Optional[int] = None,
term_side: Optional[str] = None,
**kwargs
) -> List[Dict]:
"""List all circuit terminations."""
params = {k: v for k, v in {
'circuit_id': circuit_id, 'site_id': site_id,
'provider_network_id': provider_network_id, 'term_side': term_side, **kwargs
}.items() if v is not None}
return self.client.list(f'{self.base_endpoint}/circuit-terminations', params=params)
async def get_circuit_termination(self, id: int) -> Dict:
"""Get a specific circuit termination by ID."""
return self.client.get(f'{self.base_endpoint}/circuit-terminations', id)
async def create_circuit_termination(
self,
circuit: int,
term_side: str,
site: Optional[int] = None,
provider_network: Optional[int] = None,
port_speed: Optional[int] = None,
upstream_speed: Optional[int] = None,
xconnect_id: Optional[str] = None,
pp_info: Optional[str] = None,
description: Optional[str] = None,
**kwargs
) -> Dict:
"""Create a new circuit termination."""
data = {'circuit': circuit, 'term_side': term_side, **kwargs}
for key, val in [
('site', site), ('provider_network', provider_network),
('port_speed', port_speed), ('upstream_speed', upstream_speed),
('xconnect_id', xconnect_id), ('pp_info', pp_info), ('description', description)
]:
if val is not None:
data[key] = val
return self.client.create(f'{self.base_endpoint}/circuit-terminations', data)
async def update_circuit_termination(self, id: int, **kwargs) -> Dict:
"""Update a circuit termination."""
return self.client.patch(f'{self.base_endpoint}/circuit-terminations', id, kwargs)
async def delete_circuit_termination(self, id: int) -> None:
"""Delete a circuit termination."""
self.client.delete(f'{self.base_endpoint}/circuit-terminations', id)
async def get_circuit_termination_paths(self, id: int) -> Dict:
"""Get cable paths for a circuit termination."""
return self.client.get(f'{self.base_endpoint}/circuit-terminations', f'{id}/paths')

View File

@@ -1,7 +1,7 @@
""" """
DCIM (Data Center Infrastructure Management) tools for NetBox MCP Server. DCIM (Data Center Infrastructure Management) tools for NetBox MCP Server.
Covers: Sites, Locations, Racks, Devices, Cables, Interfaces, and related models. Covers: Sites, Devices, and Interfaces only.
""" """
import logging import logging
from typing import List, Dict, Optional, Any from typing import List, Dict, Optional, Any
@@ -17,74 +17,6 @@ class DCIMTools:
self.client = client self.client = client
self.base_endpoint = 'dcim' self.base_endpoint = 'dcim'
# ==================== Regions ====================
async def list_regions(
self,
name: Optional[str] = None,
slug: Optional[str] = None,
parent_id: Optional[int] = None,
**kwargs
) -> List[Dict]:
"""List all regions with optional filtering."""
params = {k: v for k, v in {
'name': name, 'slug': slug, 'parent_id': parent_id, **kwargs
}.items() if v is not None}
return self.client.list(f'{self.base_endpoint}/regions', params=params)
async def get_region(self, id: int) -> Dict:
"""Get a specific region by ID."""
return self.client.get(f'{self.base_endpoint}/regions', id)
async def create_region(self, name: str, slug: str, parent: Optional[int] = None, **kwargs) -> Dict:
"""Create a new region."""
data = {'name': name, 'slug': slug, **kwargs}
if parent:
data['parent'] = parent
return self.client.create(f'{self.base_endpoint}/regions', data)
async def update_region(self, id: int, **kwargs) -> Dict:
"""Update a region."""
return self.client.patch(f'{self.base_endpoint}/regions', id, kwargs)
async def delete_region(self, id: int) -> None:
"""Delete a region."""
self.client.delete(f'{self.base_endpoint}/regions', id)
# ==================== Site Groups ====================
async def list_site_groups(
self,
name: Optional[str] = None,
slug: Optional[str] = None,
parent_id: Optional[int] = None,
**kwargs
) -> List[Dict]:
"""List all site groups with optional filtering."""
params = {k: v for k, v in {
'name': name, 'slug': slug, 'parent_id': parent_id, **kwargs
}.items() if v is not None}
return self.client.list(f'{self.base_endpoint}/site-groups', params=params)
async def get_site_group(self, id: int) -> Dict:
"""Get a specific site group by ID."""
return self.client.get(f'{self.base_endpoint}/site-groups', id)
async def create_site_group(self, name: str, slug: str, parent: Optional[int] = None, **kwargs) -> Dict:
"""Create a new site group."""
data = {'name': name, 'slug': slug, **kwargs}
if parent:
data['parent'] = parent
return self.client.create(f'{self.base_endpoint}/site-groups', data)
async def update_site_group(self, id: int, **kwargs) -> Dict:
"""Update a site group."""
return self.client.patch(f'{self.base_endpoint}/site-groups', id, kwargs)
async def delete_site_group(self, id: int) -> None:
"""Delete a site group."""
self.client.delete(f'{self.base_endpoint}/site-groups', id)
# ==================== Sites ==================== # ==================== Sites ====================
async def list_sites( async def list_sites(
@@ -142,359 +74,6 @@ class DCIMTools:
"""Update a site.""" """Update a site."""
return self.client.patch(f'{self.base_endpoint}/sites', id, kwargs) return self.client.patch(f'{self.base_endpoint}/sites', id, kwargs)
async def delete_site(self, id: int) -> None:
"""Delete a site."""
self.client.delete(f'{self.base_endpoint}/sites', id)
# ==================== Locations ====================
async def list_locations(
self,
name: Optional[str] = None,
slug: Optional[str] = None,
site_id: Optional[int] = None,
parent_id: Optional[int] = None,
**kwargs
) -> List[Dict]:
"""List all locations with optional filtering."""
params = {k: v for k, v in {
'name': name, 'slug': slug, 'site_id': site_id, 'parent_id': parent_id, **kwargs
}.items() if v is not None}
return self.client.list(f'{self.base_endpoint}/locations', params=params)
async def get_location(self, id: int) -> Dict:
"""Get a specific location by ID."""
return self.client.get(f'{self.base_endpoint}/locations', id)
async def create_location(
self,
name: str,
slug: str,
site: int,
parent: Optional[int] = None,
**kwargs
) -> Dict:
"""Create a new location."""
data = {'name': name, 'slug': slug, 'site': site, **kwargs}
if parent:
data['parent'] = parent
return self.client.create(f'{self.base_endpoint}/locations', data)
async def update_location(self, id: int, **kwargs) -> Dict:
"""Update a location."""
return self.client.patch(f'{self.base_endpoint}/locations', id, kwargs)
async def delete_location(self, id: int) -> None:
"""Delete a location."""
self.client.delete(f'{self.base_endpoint}/locations', id)
# ==================== Rack Roles ====================
async def list_rack_roles(self, name: Optional[str] = None, **kwargs) -> List[Dict]:
"""List all rack roles."""
params = {k: v for k, v in {'name': name, **kwargs}.items() if v is not None}
return self.client.list(f'{self.base_endpoint}/rack-roles', params=params)
async def get_rack_role(self, id: int) -> Dict:
"""Get a specific rack role by ID."""
return self.client.get(f'{self.base_endpoint}/rack-roles', id)
async def create_rack_role(self, name: str, slug: str, color: str = '9e9e9e', **kwargs) -> Dict:
"""Create a new rack role."""
data = {'name': name, 'slug': slug, 'color': color, **kwargs}
return self.client.create(f'{self.base_endpoint}/rack-roles', data)
async def update_rack_role(self, id: int, **kwargs) -> Dict:
"""Update a rack role."""
return self.client.patch(f'{self.base_endpoint}/rack-roles', id, kwargs)
async def delete_rack_role(self, id: int) -> None:
"""Delete a rack role."""
self.client.delete(f'{self.base_endpoint}/rack-roles', id)
# ==================== Rack Types ====================
async def list_rack_types(self, manufacturer_id: Optional[int] = None, **kwargs) -> List[Dict]:
"""List all rack types."""
params = {k: v for k, v in {'manufacturer_id': manufacturer_id, **kwargs}.items() if v is not None}
return self.client.list(f'{self.base_endpoint}/rack-types', params=params)
async def get_rack_type(self, id: int) -> Dict:
"""Get a specific rack type by ID."""
return self.client.get(f'{self.base_endpoint}/rack-types', id)
async def create_rack_type(
self,
manufacturer: int,
model: str,
slug: str,
form_factor: str = '4-post-frame',
width: int = 19,
u_height: int = 42,
**kwargs
) -> Dict:
"""Create a new rack type."""
data = {
'manufacturer': manufacturer, 'model': model, 'slug': slug,
'form_factor': form_factor, 'width': width, 'u_height': u_height, **kwargs
}
return self.client.create(f'{self.base_endpoint}/rack-types', data)
async def update_rack_type(self, id: int, **kwargs) -> Dict:
"""Update a rack type."""
return self.client.patch(f'{self.base_endpoint}/rack-types', id, kwargs)
async def delete_rack_type(self, id: int) -> None:
"""Delete a rack type."""
self.client.delete(f'{self.base_endpoint}/rack-types', id)
# ==================== Racks ====================
async def list_racks(
self,
name: Optional[str] = None,
site_id: Optional[int] = None,
location_id: Optional[int] = None,
status: Optional[str] = None,
role_id: Optional[int] = None,
tenant_id: Optional[int] = None,
**kwargs
) -> List[Dict]:
"""List all racks with optional filtering."""
params = {k: v for k, v in {
'name': name, 'site_id': site_id, 'location_id': location_id,
'status': status, 'role_id': role_id, 'tenant_id': tenant_id, **kwargs
}.items() if v is not None}
return self.client.list(f'{self.base_endpoint}/racks', params=params)
async def get_rack(self, id: int) -> Dict:
"""Get a specific rack by ID."""
return self.client.get(f'{self.base_endpoint}/racks', id)
async def create_rack(
self,
name: str,
site: int,
status: str = 'active',
location: Optional[int] = None,
role: Optional[int] = None,
tenant: Optional[int] = None,
rack_type: Optional[int] = None,
width: int = 19,
u_height: int = 42,
**kwargs
) -> Dict:
"""Create a new rack."""
data = {'name': name, 'site': site, 'status': status, 'width': width, 'u_height': u_height, **kwargs}
for key, val in [('location', location), ('role', role), ('tenant', tenant), ('rack_type', rack_type)]:
if val is not None:
data[key] = val
return self.client.create(f'{self.base_endpoint}/racks', data)
async def update_rack(self, id: int, **kwargs) -> Dict:
"""Update a rack."""
return self.client.patch(f'{self.base_endpoint}/racks', id, kwargs)
async def delete_rack(self, id: int) -> None:
"""Delete a rack."""
self.client.delete(f'{self.base_endpoint}/racks', id)
# ==================== Rack Reservations ====================
async def list_rack_reservations(
self,
rack_id: Optional[int] = None,
site_id: Optional[int] = None,
tenant_id: Optional[int] = None,
**kwargs
) -> List[Dict]:
"""List all rack reservations."""
params = {k: v for k, v in {
'rack_id': rack_id, 'site_id': site_id, 'tenant_id': tenant_id, **kwargs
}.items() if v is not None}
return self.client.list(f'{self.base_endpoint}/rack-reservations', params=params)
async def get_rack_reservation(self, id: int) -> Dict:
"""Get a specific rack reservation by ID."""
return self.client.get(f'{self.base_endpoint}/rack-reservations', id)
async def create_rack_reservation(
self,
rack: int,
units: List[int],
user: int,
description: str,
tenant: Optional[int] = None,
**kwargs
) -> Dict:
"""Create a new rack reservation."""
data = {'rack': rack, 'units': units, 'user': user, 'description': description, **kwargs}
if tenant:
data['tenant'] = tenant
return self.client.create(f'{self.base_endpoint}/rack-reservations', data)
async def update_rack_reservation(self, id: int, **kwargs) -> Dict:
"""Update a rack reservation."""
return self.client.patch(f'{self.base_endpoint}/rack-reservations', id, kwargs)
async def delete_rack_reservation(self, id: int) -> None:
"""Delete a rack reservation."""
self.client.delete(f'{self.base_endpoint}/rack-reservations', id)
# ==================== Manufacturers ====================
async def list_manufacturers(self, name: Optional[str] = None, **kwargs) -> List[Dict]:
"""List all manufacturers."""
params = {k: v for k, v in {'name': name, **kwargs}.items() if v is not None}
return self.client.list(f'{self.base_endpoint}/manufacturers', params=params)
async def get_manufacturer(self, id: int) -> Dict:
"""Get a specific manufacturer by ID."""
return self.client.get(f'{self.base_endpoint}/manufacturers', id)
async def create_manufacturer(self, name: str, slug: str, **kwargs) -> Dict:
"""Create a new manufacturer."""
data = {'name': name, 'slug': slug, **kwargs}
return self.client.create(f'{self.base_endpoint}/manufacturers', data)
async def update_manufacturer(self, id: int, **kwargs) -> Dict:
"""Update a manufacturer."""
return self.client.patch(f'{self.base_endpoint}/manufacturers', id, kwargs)
async def delete_manufacturer(self, id: int) -> None:
"""Delete a manufacturer."""
self.client.delete(f'{self.base_endpoint}/manufacturers', id)
# ==================== Device Types ====================
async def list_device_types(
self,
manufacturer_id: Optional[int] = None,
model: Optional[str] = None,
slug: Optional[str] = None,
**kwargs
) -> List[Dict]:
"""List all device types."""
params = {k: v for k, v in {
'manufacturer_id': manufacturer_id, 'model': model, 'slug': slug, **kwargs
}.items() if v is not None}
return self.client.list(f'{self.base_endpoint}/device-types', params=params)
async def get_device_type(self, id: int) -> Dict:
"""Get a specific device type by ID."""
return self.client.get(f'{self.base_endpoint}/device-types', id)
async def create_device_type(
self,
manufacturer: int,
model: str,
slug: str,
u_height: float = 1.0,
is_full_depth: bool = True,
**kwargs
) -> Dict:
"""Create a new device type."""
data = {
'manufacturer': manufacturer, 'model': model, 'slug': slug,
'u_height': u_height, 'is_full_depth': is_full_depth, **kwargs
}
return self.client.create(f'{self.base_endpoint}/device-types', data)
async def update_device_type(self, id: int, **kwargs) -> Dict:
"""Update a device type."""
return self.client.patch(f'{self.base_endpoint}/device-types', id, kwargs)
async def delete_device_type(self, id: int) -> None:
"""Delete a device type."""
self.client.delete(f'{self.base_endpoint}/device-types', id)
# ==================== Module Types ====================
async def list_module_types(self, manufacturer_id: Optional[int] = None, **kwargs) -> List[Dict]:
"""List all module types."""
params = {k: v for k, v in {'manufacturer_id': manufacturer_id, **kwargs}.items() if v is not None}
return self.client.list(f'{self.base_endpoint}/module-types', params=params)
async def get_module_type(self, id: int) -> Dict:
"""Get a specific module type by ID."""
return self.client.get(f'{self.base_endpoint}/module-types', id)
async def create_module_type(self, manufacturer: int, model: str, **kwargs) -> Dict:
"""Create a new module type."""
data = {'manufacturer': manufacturer, 'model': model, **kwargs}
return self.client.create(f'{self.base_endpoint}/module-types', data)
async def update_module_type(self, id: int, **kwargs) -> Dict:
"""Update a module type."""
return self.client.patch(f'{self.base_endpoint}/module-types', id, kwargs)
async def delete_module_type(self, id: int) -> None:
"""Delete a module type."""
self.client.delete(f'{self.base_endpoint}/module-types', id)
# ==================== Device Roles ====================
async def list_device_roles(self, name: Optional[str] = None, **kwargs) -> List[Dict]:
"""List all device roles."""
params = {k: v for k, v in {'name': name, **kwargs}.items() if v is not None}
return self.client.list(f'{self.base_endpoint}/device-roles', params=params)
async def get_device_role(self, id: int) -> Dict:
"""Get a specific device role by ID."""
return self.client.get(f'{self.base_endpoint}/device-roles', id)
async def create_device_role(
self,
name: str,
slug: str,
color: str = '9e9e9e',
vm_role: bool = False,
**kwargs
) -> Dict:
"""Create a new device role."""
data = {'name': name, 'slug': slug, 'color': color, 'vm_role': vm_role, **kwargs}
return self.client.create(f'{self.base_endpoint}/device-roles', data)
async def update_device_role(self, id: int, **kwargs) -> Dict:
"""Update a device role."""
return self.client.patch(f'{self.base_endpoint}/device-roles', id, kwargs)
async def delete_device_role(self, id: int) -> None:
"""Delete a device role."""
self.client.delete(f'{self.base_endpoint}/device-roles', id)
# ==================== Platforms ====================
async def list_platforms(self, name: Optional[str] = None, manufacturer_id: Optional[int] = None, **kwargs) -> List[Dict]:
"""List all platforms."""
params = {k: v for k, v in {'name': name, 'manufacturer_id': manufacturer_id, **kwargs}.items() if v is not None}
return self.client.list(f'{self.base_endpoint}/platforms', params=params)
async def get_platform(self, id: int) -> Dict:
"""Get a specific platform by ID."""
return self.client.get(f'{self.base_endpoint}/platforms', id)
async def create_platform(
self,
name: str,
slug: str,
manufacturer: Optional[int] = None,
**kwargs
) -> Dict:
"""Create a new platform."""
data = {'name': name, 'slug': slug, **kwargs}
if manufacturer:
data['manufacturer'] = manufacturer
return self.client.create(f'{self.base_endpoint}/platforms', data)
async def update_platform(self, id: int, **kwargs) -> Dict:
"""Update a platform."""
return self.client.patch(f'{self.base_endpoint}/platforms', id, kwargs)
async def delete_platform(self, id: int) -> None:
"""Delete a platform."""
self.client.delete(f'{self.base_endpoint}/platforms', id)
# ==================== Devices ==================== # ==================== Devices ====================
async def list_devices( async def list_devices(
@@ -565,34 +144,6 @@ class DCIMTools:
"""Update a device.""" """Update a device."""
return self.client.patch(f'{self.base_endpoint}/devices', id, kwargs) return self.client.patch(f'{self.base_endpoint}/devices', id, kwargs)
async def delete_device(self, id: int) -> None:
"""Delete a device."""
self.client.delete(f'{self.base_endpoint}/devices', id)
# ==================== Modules ====================
async def list_modules(self, device_id: Optional[int] = None, **kwargs) -> List[Dict]:
"""List all modules."""
params = {k: v for k, v in {'device_id': device_id, **kwargs}.items() if v is not None}
return self.client.list(f'{self.base_endpoint}/modules', params=params)
async def get_module(self, id: int) -> Dict:
"""Get a specific module by ID."""
return self.client.get(f'{self.base_endpoint}/modules', id)
async def create_module(self, device: int, module_bay: int, module_type: int, **kwargs) -> Dict:
"""Create a new module."""
data = {'device': device, 'module_bay': module_bay, 'module_type': module_type, **kwargs}
return self.client.create(f'{self.base_endpoint}/modules', data)
async def update_module(self, id: int, **kwargs) -> Dict:
"""Update a module."""
return self.client.patch(f'{self.base_endpoint}/modules', id, kwargs)
async def delete_module(self, id: int) -> None:
"""Delete a module."""
self.client.delete(f'{self.base_endpoint}/modules', id)
# ==================== Interfaces ==================== # ==================== Interfaces ====================
async def list_interfaces( async def list_interfaces(
@@ -636,300 +187,3 @@ class DCIMTools:
if val is not None: if val is not None:
data[key] = val data[key] = val
return self.client.create(f'{self.base_endpoint}/interfaces', data) return self.client.create(f'{self.base_endpoint}/interfaces', data)
async def update_interface(self, id: int, **kwargs) -> Dict:
"""Update an interface."""
return self.client.patch(f'{self.base_endpoint}/interfaces', id, kwargs)
async def delete_interface(self, id: int) -> None:
"""Delete an interface."""
self.client.delete(f'{self.base_endpoint}/interfaces', id)
# ==================== Console Ports ====================
async def list_console_ports(self, device_id: Optional[int] = None, **kwargs) -> List[Dict]:
"""List all console ports."""
params = {k: v for k, v in {'device_id': device_id, **kwargs}.items() if v is not None}
return self.client.list(f'{self.base_endpoint}/console-ports', params=params)
async def get_console_port(self, id: int) -> Dict:
"""Get a specific console port by ID."""
return self.client.get(f'{self.base_endpoint}/console-ports', id)
async def create_console_port(self, device: int, name: str, **kwargs) -> Dict:
"""Create a new console port."""
data = {'device': device, 'name': name, **kwargs}
return self.client.create(f'{self.base_endpoint}/console-ports', data)
async def update_console_port(self, id: int, **kwargs) -> Dict:
"""Update a console port."""
return self.client.patch(f'{self.base_endpoint}/console-ports', id, kwargs)
async def delete_console_port(self, id: int) -> None:
"""Delete a console port."""
self.client.delete(f'{self.base_endpoint}/console-ports', id)
# ==================== Console Server Ports ====================
async def list_console_server_ports(self, device_id: Optional[int] = None, **kwargs) -> List[Dict]:
"""List all console server ports."""
params = {k: v for k, v in {'device_id': device_id, **kwargs}.items() if v is not None}
return self.client.list(f'{self.base_endpoint}/console-server-ports', params=params)
async def get_console_server_port(self, id: int) -> Dict:
"""Get a specific console server port by ID."""
return self.client.get(f'{self.base_endpoint}/console-server-ports', id)
async def create_console_server_port(self, device: int, name: str, **kwargs) -> Dict:
"""Create a new console server port."""
data = {'device': device, 'name': name, **kwargs}
return self.client.create(f'{self.base_endpoint}/console-server-ports', data)
async def update_console_server_port(self, id: int, **kwargs) -> Dict:
"""Update a console server port."""
return self.client.patch(f'{self.base_endpoint}/console-server-ports', id, kwargs)
async def delete_console_server_port(self, id: int) -> None:
"""Delete a console server port."""
self.client.delete(f'{self.base_endpoint}/console-server-ports', id)
# ==================== Power Ports ====================
async def list_power_ports(self, device_id: Optional[int] = None, **kwargs) -> List[Dict]:
"""List all power ports."""
params = {k: v for k, v in {'device_id': device_id, **kwargs}.items() if v is not None}
return self.client.list(f'{self.base_endpoint}/power-ports', params=params)
async def get_power_port(self, id: int) -> Dict:
"""Get a specific power port by ID."""
return self.client.get(f'{self.base_endpoint}/power-ports', id)
async def create_power_port(self, device: int, name: str, **kwargs) -> Dict:
"""Create a new power port."""
data = {'device': device, 'name': name, **kwargs}
return self.client.create(f'{self.base_endpoint}/power-ports', data)
async def update_power_port(self, id: int, **kwargs) -> Dict:
"""Update a power port."""
return self.client.patch(f'{self.base_endpoint}/power-ports', id, kwargs)
async def delete_power_port(self, id: int) -> None:
"""Delete a power port."""
self.client.delete(f'{self.base_endpoint}/power-ports', id)
# ==================== Power Outlets ====================
async def list_power_outlets(self, device_id: Optional[int] = None, **kwargs) -> List[Dict]:
"""List all power outlets."""
params = {k: v for k, v in {'device_id': device_id, **kwargs}.items() if v is not None}
return self.client.list(f'{self.base_endpoint}/power-outlets', params=params)
async def get_power_outlet(self, id: int) -> Dict:
"""Get a specific power outlet by ID."""
return self.client.get(f'{self.base_endpoint}/power-outlets', id)
async def create_power_outlet(self, device: int, name: str, **kwargs) -> Dict:
"""Create a new power outlet."""
data = {'device': device, 'name': name, **kwargs}
return self.client.create(f'{self.base_endpoint}/power-outlets', data)
async def update_power_outlet(self, id: int, **kwargs) -> Dict:
"""Update a power outlet."""
return self.client.patch(f'{self.base_endpoint}/power-outlets', id, kwargs)
async def delete_power_outlet(self, id: int) -> None:
"""Delete a power outlet."""
self.client.delete(f'{self.base_endpoint}/power-outlets', id)
# ==================== Power Panels ====================
async def list_power_panels(self, site_id: Optional[int] = None, **kwargs) -> List[Dict]:
"""List all power panels."""
params = {k: v for k, v in {'site_id': site_id, **kwargs}.items() if v is not None}
return self.client.list(f'{self.base_endpoint}/power-panels', params=params)
async def get_power_panel(self, id: int) -> Dict:
"""Get a specific power panel by ID."""
return self.client.get(f'{self.base_endpoint}/power-panels', id)
async def create_power_panel(self, site: int, name: str, location: Optional[int] = None, **kwargs) -> Dict:
"""Create a new power panel."""
data = {'site': site, 'name': name, **kwargs}
if location:
data['location'] = location
return self.client.create(f'{self.base_endpoint}/power-panels', data)
async def update_power_panel(self, id: int, **kwargs) -> Dict:
"""Update a power panel."""
return self.client.patch(f'{self.base_endpoint}/power-panels', id, kwargs)
async def delete_power_panel(self, id: int) -> None:
"""Delete a power panel."""
self.client.delete(f'{self.base_endpoint}/power-panels', id)
# ==================== Power Feeds ====================
async def list_power_feeds(self, power_panel_id: Optional[int] = None, **kwargs) -> List[Dict]:
"""List all power feeds."""
params = {k: v for k, v in {'power_panel_id': power_panel_id, **kwargs}.items() if v is not None}
return self.client.list(f'{self.base_endpoint}/power-feeds', params=params)
async def get_power_feed(self, id: int) -> Dict:
"""Get a specific power feed by ID."""
return self.client.get(f'{self.base_endpoint}/power-feeds', id)
async def create_power_feed(
self,
power_panel: int,
name: str,
status: str = 'active',
type: str = 'primary',
supply: str = 'ac',
phase: str = 'single-phase',
voltage: int = 120,
amperage: int = 20,
**kwargs
) -> Dict:
"""Create a new power feed."""
data = {
'power_panel': power_panel, 'name': name, 'status': status,
'type': type, 'supply': supply, 'phase': phase,
'voltage': voltage, 'amperage': amperage, **kwargs
}
return self.client.create(f'{self.base_endpoint}/power-feeds', data)
async def update_power_feed(self, id: int, **kwargs) -> Dict:
"""Update a power feed."""
return self.client.patch(f'{self.base_endpoint}/power-feeds', id, kwargs)
async def delete_power_feed(self, id: int) -> None:
"""Delete a power feed."""
self.client.delete(f'{self.base_endpoint}/power-feeds', id)
# ==================== Cables ====================
async def list_cables(
self,
site_id: Optional[int] = None,
device_id: Optional[int] = None,
rack_id: Optional[int] = None,
type: Optional[str] = None,
status: Optional[str] = None,
**kwargs
) -> List[Dict]:
"""List all cables."""
params = {k: v for k, v in {
'site_id': site_id, 'device_id': device_id, 'rack_id': rack_id,
'type': type, 'status': status, **kwargs
}.items() if v is not None}
return self.client.list(f'{self.base_endpoint}/cables', params=params)
async def get_cable(self, id: int) -> Dict:
"""Get a specific cable by ID."""
return self.client.get(f'{self.base_endpoint}/cables', id)
async def create_cable(
self,
a_terminations: List[Dict],
b_terminations: List[Dict],
type: Optional[str] = None,
status: str = 'connected',
label: Optional[str] = None,
color: Optional[str] = None,
length: Optional[float] = None,
length_unit: Optional[str] = None,
**kwargs
) -> Dict:
"""
Create a new cable.
a_terminations and b_terminations are lists of dicts with:
- object_type: e.g., 'dcim.interface'
- object_id: ID of the object
"""
data = {
'a_terminations': a_terminations,
'b_terminations': b_terminations,
'status': status,
**kwargs
}
for key, val in [
('type', type), ('label', label), ('color', color),
('length', length), ('length_unit', length_unit)
]:
if val is not None:
data[key] = val
return self.client.create(f'{self.base_endpoint}/cables', data)
async def update_cable(self, id: int, **kwargs) -> Dict:
"""Update a cable."""
return self.client.patch(f'{self.base_endpoint}/cables', id, kwargs)
async def delete_cable(self, id: int) -> None:
"""Delete a cable."""
self.client.delete(f'{self.base_endpoint}/cables', id)
# ==================== Virtual Chassis ====================
async def list_virtual_chassis(self, **kwargs) -> List[Dict]:
"""List all virtual chassis."""
return self.client.list(f'{self.base_endpoint}/virtual-chassis', params=kwargs)
async def get_virtual_chassis(self, id: int) -> Dict:
"""Get a specific virtual chassis by ID."""
return self.client.get(f'{self.base_endpoint}/virtual-chassis', id)
async def create_virtual_chassis(self, name: str, domain: Optional[str] = None, **kwargs) -> Dict:
"""Create a new virtual chassis."""
data = {'name': name, **kwargs}
if domain:
data['domain'] = domain
return self.client.create(f'{self.base_endpoint}/virtual-chassis', data)
async def update_virtual_chassis(self, id: int, **kwargs) -> Dict:
"""Update a virtual chassis."""
return self.client.patch(f'{self.base_endpoint}/virtual-chassis', id, kwargs)
async def delete_virtual_chassis(self, id: int) -> None:
"""Delete a virtual chassis."""
self.client.delete(f'{self.base_endpoint}/virtual-chassis', id)
# ==================== Inventory Items ====================
async def list_inventory_items(self, device_id: Optional[int] = None, **kwargs) -> List[Dict]:
"""List all inventory items."""
params = {k: v for k, v in {'device_id': device_id, **kwargs}.items() if v is not None}
return self.client.list(f'{self.base_endpoint}/inventory-items', params=params)
async def get_inventory_item(self, id: int) -> Dict:
"""Get a specific inventory item by ID."""
return self.client.get(f'{self.base_endpoint}/inventory-items', id)
async def create_inventory_item(
self,
device: int,
name: str,
parent: Optional[int] = None,
manufacturer: Optional[int] = None,
part_id: Optional[str] = None,
serial: Optional[str] = None,
asset_tag: Optional[str] = None,
**kwargs
) -> Dict:
"""Create a new inventory item."""
data = {'device': device, 'name': name, **kwargs}
for key, val in [
('parent', parent), ('manufacturer', manufacturer),
('part_id', part_id), ('serial', serial), ('asset_tag', asset_tag)
]:
if val is not None:
data[key] = val
return self.client.create(f'{self.base_endpoint}/inventory-items', data)
async def update_inventory_item(self, id: int, **kwargs) -> Dict:
"""Update an inventory item."""
return self.client.patch(f'{self.base_endpoint}/inventory-items', id, kwargs)
async def delete_inventory_item(self, id: int) -> None:
"""Delete an inventory item."""
self.client.delete(f'{self.base_endpoint}/inventory-items', id)

View File

@@ -1,7 +1,7 @@
""" """
Extras tools for NetBox MCP Server. Extras tools for NetBox MCP Server.
Covers: Tags, Custom Fields, Custom Links, Webhooks, Journal Entries, and more. Covers: Tags and Journal Entries only.
""" """
import logging import logging
from typing import List, Dict, Optional, Any from typing import List, Dict, Optional, Any
@@ -50,209 +50,6 @@ class ExtrasTools:
data['description'] = description data['description'] = description
return self.client.create(f'{self.base_endpoint}/tags', data) return self.client.create(f'{self.base_endpoint}/tags', data)
async def update_tag(self, id: int, **kwargs) -> Dict:
"""Update a tag."""
return self.client.patch(f'{self.base_endpoint}/tags', id, kwargs)
async def delete_tag(self, id: int) -> None:
"""Delete a tag."""
self.client.delete(f'{self.base_endpoint}/tags', id)
# ==================== Custom Fields ====================
async def list_custom_fields(
self,
name: Optional[str] = None,
type: Optional[str] = None,
content_types: Optional[str] = None,
**kwargs
) -> List[Dict]:
"""List all custom fields."""
params = {k: v for k, v in {
'name': name, 'type': type, 'content_types': content_types, **kwargs
}.items() if v is not None}
return self.client.list(f'{self.base_endpoint}/custom-fields', params=params)
async def get_custom_field(self, id: int) -> Dict:
"""Get a specific custom field by ID."""
return self.client.get(f'{self.base_endpoint}/custom-fields', id)
async def create_custom_field(
self,
name: str,
content_types: List[str],
type: str = 'text',
label: Optional[str] = None,
description: Optional[str] = None,
required: bool = False,
filter_logic: str = 'loose',
default: Optional[Any] = None,
weight: int = 100,
validation_minimum: Optional[int] = None,
validation_maximum: Optional[int] = None,
validation_regex: Optional[str] = None,
choice_set: Optional[int] = None,
**kwargs
) -> Dict:
"""Create a new custom field."""
data = {
'name': name, 'content_types': content_types, 'type': type,
'required': required, 'filter_logic': filter_logic, 'weight': weight, **kwargs
}
for key, val in [
('label', label), ('description', description), ('default', default),
('validation_minimum', validation_minimum), ('validation_maximum', validation_maximum),
('validation_regex', validation_regex), ('choice_set', choice_set)
]:
if val is not None:
data[key] = val
return self.client.create(f'{self.base_endpoint}/custom-fields', data)
async def update_custom_field(self, id: int, **kwargs) -> Dict:
"""Update a custom field."""
return self.client.patch(f'{self.base_endpoint}/custom-fields', id, kwargs)
async def delete_custom_field(self, id: int) -> None:
"""Delete a custom field."""
self.client.delete(f'{self.base_endpoint}/custom-fields', id)
# ==================== Custom Field Choice Sets ====================
async def list_custom_field_choice_sets(self, name: Optional[str] = None, **kwargs) -> List[Dict]:
"""List all custom field choice sets."""
params = {k: v for k, v in {'name': name, **kwargs}.items() if v is not None}
return self.client.list(f'{self.base_endpoint}/custom-field-choice-sets', params=params)
async def get_custom_field_choice_set(self, id: int) -> Dict:
"""Get a specific custom field choice set by ID."""
return self.client.get(f'{self.base_endpoint}/custom-field-choice-sets', id)
async def create_custom_field_choice_set(
self,
name: str,
extra_choices: List[List[str]],
description: Optional[str] = None,
**kwargs
) -> Dict:
"""Create a new custom field choice set."""
data = {'name': name, 'extra_choices': extra_choices, **kwargs}
if description:
data['description'] = description
return self.client.create(f'{self.base_endpoint}/custom-field-choice-sets', data)
async def update_custom_field_choice_set(self, id: int, **kwargs) -> Dict:
"""Update a custom field choice set."""
return self.client.patch(f'{self.base_endpoint}/custom-field-choice-sets', id, kwargs)
async def delete_custom_field_choice_set(self, id: int) -> None:
"""Delete a custom field choice set."""
self.client.delete(f'{self.base_endpoint}/custom-field-choice-sets', id)
# ==================== Custom Links ====================
async def list_custom_links(
self,
name: Optional[str] = None,
content_types: Optional[str] = None,
**kwargs
) -> List[Dict]:
"""List all custom links."""
params = {k: v for k, v in {
'name': name, 'content_types': content_types, **kwargs
}.items() if v is not None}
return self.client.list(f'{self.base_endpoint}/custom-links', params=params)
async def get_custom_link(self, id: int) -> Dict:
"""Get a specific custom link by ID."""
return self.client.get(f'{self.base_endpoint}/custom-links', id)
async def create_custom_link(
self,
name: str,
content_types: List[str],
link_text: str,
link_url: str,
enabled: bool = True,
new_window: bool = False,
weight: int = 100,
group_name: Optional[str] = None,
button_class: str = 'outline-dark',
**kwargs
) -> Dict:
"""Create a new custom link."""
data = {
'name': name, 'content_types': content_types,
'link_text': link_text, 'link_url': link_url,
'enabled': enabled, 'new_window': new_window,
'weight': weight, 'button_class': button_class, **kwargs
}
if group_name:
data['group_name'] = group_name
return self.client.create(f'{self.base_endpoint}/custom-links', data)
async def update_custom_link(self, id: int, **kwargs) -> Dict:
"""Update a custom link."""
return self.client.patch(f'{self.base_endpoint}/custom-links', id, kwargs)
async def delete_custom_link(self, id: int) -> None:
"""Delete a custom link."""
self.client.delete(f'{self.base_endpoint}/custom-links', id)
# ==================== Webhooks ====================
async def list_webhooks(self, name: Optional[str] = None, **kwargs) -> List[Dict]:
"""List all webhooks."""
params = {k: v for k, v in {'name': name, **kwargs}.items() if v is not None}
return self.client.list(f'{self.base_endpoint}/webhooks', params=params)
async def get_webhook(self, id: int) -> Dict:
"""Get a specific webhook by ID."""
return self.client.get(f'{self.base_endpoint}/webhooks', id)
async def create_webhook(
self,
name: str,
payload_url: str,
content_types: List[str],
type_create: bool = True,
type_update: bool = True,
type_delete: bool = True,
type_job_start: bool = False,
type_job_end: bool = False,
enabled: bool = True,
http_method: str = 'POST',
http_content_type: str = 'application/json',
additional_headers: Optional[str] = None,
body_template: Optional[str] = None,
secret: Optional[str] = None,
ssl_verification: bool = True,
ca_file_path: Optional[str] = None,
**kwargs
) -> Dict:
"""Create a new webhook."""
data = {
'name': name, 'payload_url': payload_url, 'content_types': content_types,
'type_create': type_create, 'type_update': type_update, 'type_delete': type_delete,
'type_job_start': type_job_start, 'type_job_end': type_job_end,
'enabled': enabled, 'http_method': http_method,
'http_content_type': http_content_type, 'ssl_verification': ssl_verification, **kwargs
}
for key, val in [
('additional_headers', additional_headers), ('body_template', body_template),
('secret', secret), ('ca_file_path', ca_file_path)
]:
if val is not None:
data[key] = val
return self.client.create(f'{self.base_endpoint}/webhooks', data)
async def update_webhook(self, id: int, **kwargs) -> Dict:
"""Update a webhook."""
return self.client.patch(f'{self.base_endpoint}/webhooks', id, kwargs)
async def delete_webhook(self, id: int) -> None:
"""Delete a webhook."""
self.client.delete(f'{self.base_endpoint}/webhooks', id)
# ==================== Journal Entries ==================== # ==================== Journal Entries ====================
async def list_journal_entries( async def list_journal_entries(
@@ -288,273 +85,3 @@ class ExtrasTools:
'comments': comments, 'kind': kind, **kwargs 'comments': comments, 'kind': kind, **kwargs
} }
return self.client.create(f'{self.base_endpoint}/journal-entries', data) return self.client.create(f'{self.base_endpoint}/journal-entries', data)
async def update_journal_entry(self, id: int, **kwargs) -> Dict:
"""Update a journal entry."""
return self.client.patch(f'{self.base_endpoint}/journal-entries', id, kwargs)
async def delete_journal_entry(self, id: int) -> None:
"""Delete a journal entry."""
self.client.delete(f'{self.base_endpoint}/journal-entries', id)
# ==================== Config Contexts ====================
async def list_config_contexts(self, name: Optional[str] = None, **kwargs) -> List[Dict]:
"""List all config contexts."""
params = {k: v for k, v in {'name': name, **kwargs}.items() if v is not None}
return self.client.list(f'{self.base_endpoint}/config-contexts', params=params)
async def get_config_context(self, id: int) -> Dict:
"""Get a specific config context by ID."""
return self.client.get(f'{self.base_endpoint}/config-contexts', id)
async def create_config_context(
self,
name: str,
data: Dict[str, Any],
weight: int = 1000,
description: Optional[str] = None,
is_active: bool = True,
regions: Optional[List[int]] = None,
site_groups: Optional[List[int]] = None,
sites: Optional[List[int]] = None,
locations: Optional[List[int]] = None,
device_types: Optional[List[int]] = None,
roles: Optional[List[int]] = None,
platforms: Optional[List[int]] = None,
cluster_types: Optional[List[int]] = None,
cluster_groups: Optional[List[int]] = None,
clusters: Optional[List[int]] = None,
tenant_groups: Optional[List[int]] = None,
tenants: Optional[List[int]] = None,
tags: Optional[List[str]] = None,
**kwargs
) -> Dict:
"""Create a new config context."""
context_data = {
'name': name, 'data': data, 'weight': weight, 'is_active': is_active, **kwargs
}
for key, val in [
('description', description), ('regions', regions),
('site_groups', site_groups), ('sites', sites),
('locations', locations), ('device_types', device_types),
('roles', roles), ('platforms', platforms),
('cluster_types', cluster_types), ('cluster_groups', cluster_groups),
('clusters', clusters), ('tenant_groups', tenant_groups),
('tenants', tenants), ('tags', tags)
]:
if val is not None:
context_data[key] = val
return self.client.create(f'{self.base_endpoint}/config-contexts', context_data)
async def update_config_context(self, id: int, **kwargs) -> Dict:
"""Update a config context."""
return self.client.patch(f'{self.base_endpoint}/config-contexts', id, kwargs)
async def delete_config_context(self, id: int) -> None:
"""Delete a config context."""
self.client.delete(f'{self.base_endpoint}/config-contexts', id)
# ==================== Config Templates ====================
async def list_config_templates(self, name: Optional[str] = None, **kwargs) -> List[Dict]:
"""List all config templates."""
params = {k: v for k, v in {'name': name, **kwargs}.items() if v is not None}
return self.client.list(f'{self.base_endpoint}/config-templates', params=params)
async def get_config_template(self, id: int) -> Dict:
"""Get a specific config template by ID."""
return self.client.get(f'{self.base_endpoint}/config-templates', id)
async def create_config_template(
self,
name: str,
template_code: str,
description: Optional[str] = None,
environment_params: Optional[Dict[str, Any]] = None,
**kwargs
) -> Dict:
"""Create a new config template."""
data = {'name': name, 'template_code': template_code, **kwargs}
if description:
data['description'] = description
if environment_params:
data['environment_params'] = environment_params
return self.client.create(f'{self.base_endpoint}/config-templates', data)
async def update_config_template(self, id: int, **kwargs) -> Dict:
"""Update a config template."""
return self.client.patch(f'{self.base_endpoint}/config-templates', id, kwargs)
async def delete_config_template(self, id: int) -> None:
"""Delete a config template."""
self.client.delete(f'{self.base_endpoint}/config-templates', id)
# ==================== Export Templates ====================
async def list_export_templates(
self,
name: Optional[str] = None,
content_types: Optional[str] = None,
**kwargs
) -> List[Dict]:
"""List all export templates."""
params = {k: v for k, v in {
'name': name, 'content_types': content_types, **kwargs
}.items() if v is not None}
return self.client.list(f'{self.base_endpoint}/export-templates', params=params)
async def get_export_template(self, id: int) -> Dict:
"""Get a specific export template by ID."""
return self.client.get(f'{self.base_endpoint}/export-templates', id)
async def create_export_template(
self,
name: str,
content_types: List[str],
template_code: str,
description: Optional[str] = None,
mime_type: str = 'text/plain',
file_extension: Optional[str] = None,
as_attachment: bool = True,
**kwargs
) -> Dict:
"""Create a new export template."""
data = {
'name': name, 'content_types': content_types,
'template_code': template_code, 'mime_type': mime_type,
'as_attachment': as_attachment, **kwargs
}
if description:
data['description'] = description
if file_extension:
data['file_extension'] = file_extension
return self.client.create(f'{self.base_endpoint}/export-templates', data)
async def update_export_template(self, id: int, **kwargs) -> Dict:
"""Update an export template."""
return self.client.patch(f'{self.base_endpoint}/export-templates', id, kwargs)
async def delete_export_template(self, id: int) -> None:
"""Delete an export template."""
self.client.delete(f'{self.base_endpoint}/export-templates', id)
# ==================== Saved Filters ====================
async def list_saved_filters(
self,
name: Optional[str] = None,
content_types: Optional[str] = None,
**kwargs
) -> List[Dict]:
"""List all saved filters."""
params = {k: v for k, v in {
'name': name, 'content_types': content_types, **kwargs
}.items() if v is not None}
return self.client.list(f'{self.base_endpoint}/saved-filters', params=params)
async def get_saved_filter(self, id: int) -> Dict:
"""Get a specific saved filter by ID."""
return self.client.get(f'{self.base_endpoint}/saved-filters', id)
async def create_saved_filter(
self,
name: str,
slug: str,
content_types: List[str],
parameters: Dict[str, Any],
description: Optional[str] = None,
weight: int = 100,
enabled: bool = True,
shared: bool = True,
**kwargs
) -> Dict:
"""Create a new saved filter."""
data = {
'name': name, 'slug': slug, 'content_types': content_types,
'parameters': parameters, 'weight': weight,
'enabled': enabled, 'shared': shared, **kwargs
}
if description:
data['description'] = description
return self.client.create(f'{self.base_endpoint}/saved-filters', data)
async def update_saved_filter(self, id: int, **kwargs) -> Dict:
"""Update a saved filter."""
return self.client.patch(f'{self.base_endpoint}/saved-filters', id, kwargs)
async def delete_saved_filter(self, id: int) -> None:
"""Delete a saved filter."""
self.client.delete(f'{self.base_endpoint}/saved-filters', id)
# ==================== Image Attachments ====================
async def list_image_attachments(
self,
object_type: Optional[str] = None,
object_id: Optional[int] = None,
**kwargs
) -> List[Dict]:
"""List all image attachments."""
params = {k: v for k, v in {
'object_type': object_type, 'object_id': object_id, **kwargs
}.items() if v is not None}
return self.client.list(f'{self.base_endpoint}/image-attachments', params=params)
async def get_image_attachment(self, id: int) -> Dict:
"""Get a specific image attachment by ID."""
return self.client.get(f'{self.base_endpoint}/image-attachments', id)
async def delete_image_attachment(self, id: int) -> None:
"""Delete an image attachment."""
self.client.delete(f'{self.base_endpoint}/image-attachments', id)
# ==================== Object Changes (Audit Log) ====================
async def list_object_changes(
self,
user_id: Optional[int] = None,
changed_object_type: Optional[str] = None,
changed_object_id: Optional[int] = None,
action: Optional[str] = None,
**kwargs
) -> List[Dict]:
"""List all object changes (audit log)."""
params = {k: v for k, v in {
'user_id': user_id, 'changed_object_type': changed_object_type,
'changed_object_id': changed_object_id, 'action': action, **kwargs
}.items() if v is not None}
return self.client.list(f'{self.base_endpoint}/object-changes', params=params)
async def get_object_change(self, id: int) -> Dict:
"""Get a specific object change by ID."""
return self.client.get(f'{self.base_endpoint}/object-changes', id)
# ==================== Scripts ====================
async def list_scripts(self, **kwargs) -> List[Dict]:
"""List all available scripts."""
return self.client.list(f'{self.base_endpoint}/scripts', params=kwargs)
async def get_script(self, id: str) -> Dict:
"""Get a specific script by ID."""
return self.client.get(f'{self.base_endpoint}/scripts', id)
async def run_script(self, id: str, data: Dict[str, Any], commit: bool = True) -> Dict:
"""Run a script with the provided data."""
payload = {'data': data, 'commit': commit}
return self.client.create(f'{self.base_endpoint}/scripts/{id}', payload)
# ==================== Reports ====================
async def list_reports(self, **kwargs) -> List[Dict]:
"""List all available reports."""
return self.client.list(f'{self.base_endpoint}/reports', params=kwargs)
async def get_report(self, id: str) -> Dict:
"""Get a specific report by ID."""
return self.client.get(f'{self.base_endpoint}/reports', id)
async def run_report(self, id: str) -> Dict:
"""Run a report."""
return self.client.create(f'{self.base_endpoint}/reports/{id}', {})

View File

@@ -1,7 +1,7 @@
""" """
IPAM (IP Address Management) tools for NetBox MCP Server. IPAM (IP Address Management) tools for NetBox MCP Server.
Covers: IP Addresses, Prefixes, VLANs, VRFs, ASNs, and related models. Covers: IP Addresses, Prefixes, and Services only.
""" """
import logging import logging
from typing import List, Dict, Optional, Any from typing import List, Dict, Optional, Any
@@ -17,164 +17,6 @@ class IPAMTools:
self.client = client self.client = client
self.base_endpoint = 'ipam' self.base_endpoint = 'ipam'
# ==================== ASN Ranges ====================
async def list_asn_ranges(self, name: Optional[str] = None, **kwargs) -> List[Dict]:
"""List all ASN ranges."""
params = {k: v for k, v in {'name': name, **kwargs}.items() if v is not None}
return self.client.list(f'{self.base_endpoint}/asn-ranges', params=params)
async def get_asn_range(self, id: int) -> Dict:
"""Get a specific ASN range by ID."""
return self.client.get(f'{self.base_endpoint}/asn-ranges', id)
async def create_asn_range(self, name: str, slug: str, rir: int, start: int, end: int, **kwargs) -> Dict:
"""Create a new ASN range."""
data = {'name': name, 'slug': slug, 'rir': rir, 'start': start, 'end': end, **kwargs}
return self.client.create(f'{self.base_endpoint}/asn-ranges', data)
async def update_asn_range(self, id: int, **kwargs) -> Dict:
"""Update an ASN range."""
return self.client.patch(f'{self.base_endpoint}/asn-ranges', id, kwargs)
async def delete_asn_range(self, id: int) -> None:
"""Delete an ASN range."""
self.client.delete(f'{self.base_endpoint}/asn-ranges', id)
# ==================== ASNs ====================
async def list_asns(
self,
asn: Optional[int] = None,
rir_id: Optional[int] = None,
tenant_id: Optional[int] = None,
**kwargs
) -> List[Dict]:
"""List all ASNs."""
params = {k: v for k, v in {
'asn': asn, 'rir_id': rir_id, 'tenant_id': tenant_id, **kwargs
}.items() if v is not None}
return self.client.list(f'{self.base_endpoint}/asns', params=params)
async def get_asn(self, id: int) -> Dict:
"""Get a specific ASN by ID."""
return self.client.get(f'{self.base_endpoint}/asns', id)
async def create_asn(
self,
asn: int,
rir: int,
tenant: Optional[int] = None,
description: Optional[str] = None,
**kwargs
) -> Dict:
"""Create a new ASN."""
data = {'asn': asn, 'rir': rir, **kwargs}
if tenant:
data['tenant'] = tenant
if description:
data['description'] = description
return self.client.create(f'{self.base_endpoint}/asns', data)
async def update_asn(self, id: int, **kwargs) -> Dict:
"""Update an ASN."""
return self.client.patch(f'{self.base_endpoint}/asns', id, kwargs)
async def delete_asn(self, id: int) -> None:
"""Delete an ASN."""
self.client.delete(f'{self.base_endpoint}/asns', id)
# ==================== RIRs ====================
async def list_rirs(self, name: Optional[str] = None, **kwargs) -> List[Dict]:
"""List all RIRs (Regional Internet Registries)."""
params = {k: v for k, v in {'name': name, **kwargs}.items() if v is not None}
return self.client.list(f'{self.base_endpoint}/rirs', params=params)
async def get_rir(self, id: int) -> Dict:
"""Get a specific RIR by ID."""
return self.client.get(f'{self.base_endpoint}/rirs', id)
async def create_rir(self, name: str, slug: str, is_private: bool = False, **kwargs) -> Dict:
"""Create a new RIR."""
data = {'name': name, 'slug': slug, 'is_private': is_private, **kwargs}
return self.client.create(f'{self.base_endpoint}/rirs', data)
async def update_rir(self, id: int, **kwargs) -> Dict:
"""Update a RIR."""
return self.client.patch(f'{self.base_endpoint}/rirs', id, kwargs)
async def delete_rir(self, id: int) -> None:
"""Delete a RIR."""
self.client.delete(f'{self.base_endpoint}/rirs', id)
# ==================== Aggregates ====================
async def list_aggregates(
self,
prefix: Optional[str] = None,
rir_id: Optional[int] = None,
tenant_id: Optional[int] = None,
**kwargs
) -> List[Dict]:
"""List all aggregates."""
params = {k: v for k, v in {
'prefix': prefix, 'rir_id': rir_id, 'tenant_id': tenant_id, **kwargs
}.items() if v is not None}
return self.client.list(f'{self.base_endpoint}/aggregates', params=params)
async def get_aggregate(self, id: int) -> Dict:
"""Get a specific aggregate by ID."""
return self.client.get(f'{self.base_endpoint}/aggregates', id)
async def create_aggregate(
self,
prefix: str,
rir: int,
tenant: Optional[int] = None,
description: Optional[str] = None,
**kwargs
) -> Dict:
"""Create a new aggregate."""
data = {'prefix': prefix, 'rir': rir, **kwargs}
if tenant:
data['tenant'] = tenant
if description:
data['description'] = description
return self.client.create(f'{self.base_endpoint}/aggregates', data)
async def update_aggregate(self, id: int, **kwargs) -> Dict:
"""Update an aggregate."""
return self.client.patch(f'{self.base_endpoint}/aggregates', id, kwargs)
async def delete_aggregate(self, id: int) -> None:
"""Delete an aggregate."""
self.client.delete(f'{self.base_endpoint}/aggregates', id)
# ==================== Roles ====================
async def list_roles(self, name: Optional[str] = None, **kwargs) -> List[Dict]:
"""List all IPAM roles."""
params = {k: v for k, v in {'name': name, **kwargs}.items() if v is not None}
return self.client.list(f'{self.base_endpoint}/roles', params=params)
async def get_role(self, id: int) -> Dict:
"""Get a specific role by ID."""
return self.client.get(f'{self.base_endpoint}/roles', id)
async def create_role(self, name: str, slug: str, weight: int = 1000, **kwargs) -> Dict:
"""Create a new IPAM role."""
data = {'name': name, 'slug': slug, 'weight': weight, **kwargs}
return self.client.create(f'{self.base_endpoint}/roles', data)
async def update_role(self, id: int, **kwargs) -> Dict:
"""Update a role."""
return self.client.patch(f'{self.base_endpoint}/roles', id, kwargs)
async def delete_role(self, id: int) -> None:
"""Delete a role."""
self.client.delete(f'{self.base_endpoint}/roles', id)
# ==================== Prefixes ==================== # ==================== Prefixes ====================
async def list_prefixes( async def list_prefixes(
@@ -230,83 +72,6 @@ class IPAMTools:
data[key] = val data[key] = val
return self.client.create(f'{self.base_endpoint}/prefixes', data) return self.client.create(f'{self.base_endpoint}/prefixes', data)
async def update_prefix(self, id: int, **kwargs) -> Dict:
"""Update a prefix."""
return self.client.patch(f'{self.base_endpoint}/prefixes', id, kwargs)
async def delete_prefix(self, id: int) -> None:
"""Delete a prefix."""
self.client.delete(f'{self.base_endpoint}/prefixes', id)
async def list_available_prefixes(self, id: int) -> List[Dict]:
"""List available child prefixes within a prefix."""
return self.client.list(f'{self.base_endpoint}/prefixes/{id}/available-prefixes', paginate=False)
async def create_available_prefix(self, id: int, prefix_length: int, **kwargs) -> Dict:
"""Create a new prefix from available space."""
data = {'prefix_length': prefix_length, **kwargs}
return self.client.create(f'{self.base_endpoint}/prefixes/{id}/available-prefixes', data)
async def list_available_ips(self, id: int) -> List[Dict]:
"""List available IP addresses within a prefix."""
return self.client.list(f'{self.base_endpoint}/prefixes/{id}/available-ips', paginate=False)
async def create_available_ip(self, id: int, **kwargs) -> Dict:
"""Create a new IP address from available space in prefix."""
return self.client.create(f'{self.base_endpoint}/prefixes/{id}/available-ips', kwargs)
# ==================== IP Ranges ====================
async def list_ip_ranges(
self,
start_address: Optional[str] = None,
end_address: Optional[str] = None,
vrf_id: Optional[int] = None,
tenant_id: Optional[int] = None,
status: Optional[str] = None,
**kwargs
) -> List[Dict]:
"""List all IP ranges."""
params = {k: v for k, v in {
'start_address': start_address, 'end_address': end_address,
'vrf_id': vrf_id, 'tenant_id': tenant_id, 'status': status, **kwargs
}.items() if v is not None}
return self.client.list(f'{self.base_endpoint}/ip-ranges', params=params)
async def get_ip_range(self, id: int) -> Dict:
"""Get a specific IP range by ID."""
return self.client.get(f'{self.base_endpoint}/ip-ranges', id)
async def create_ip_range(
self,
start_address: str,
end_address: str,
status: str = 'active',
vrf: Optional[int] = None,
tenant: Optional[int] = None,
role: Optional[int] = None,
description: Optional[str] = None,
**kwargs
) -> Dict:
"""Create a new IP range."""
data = {'start_address': start_address, 'end_address': end_address, 'status': status, **kwargs}
for key, val in [('vrf', vrf), ('tenant', tenant), ('role', role), ('description', description)]:
if val is not None:
data[key] = val
return self.client.create(f'{self.base_endpoint}/ip-ranges', data)
async def update_ip_range(self, id: int, **kwargs) -> Dict:
"""Update an IP range."""
return self.client.patch(f'{self.base_endpoint}/ip-ranges', id, kwargs)
async def delete_ip_range(self, id: int) -> None:
"""Delete an IP range."""
self.client.delete(f'{self.base_endpoint}/ip-ranges', id)
async def list_available_ips_in_range(self, id: int) -> List[Dict]:
"""List available IP addresses within an IP range."""
return self.client.list(f'{self.base_endpoint}/ip-ranges/{id}/available-ips', paginate=False)
# ==================== IP Addresses ==================== # ==================== IP Addresses ====================
async def list_ip_addresses( async def list_ip_addresses(
@@ -368,271 +133,6 @@ class IPAMTools:
"""Update an IP address.""" """Update an IP address."""
return self.client.patch(f'{self.base_endpoint}/ip-addresses', id, kwargs) return self.client.patch(f'{self.base_endpoint}/ip-addresses', id, kwargs)
async def delete_ip_address(self, id: int) -> None:
"""Delete an IP address."""
self.client.delete(f'{self.base_endpoint}/ip-addresses', id)
# ==================== FHRP Groups ====================
async def list_fhrp_groups(
self,
protocol: Optional[str] = None,
group_id: Optional[int] = None,
**kwargs
) -> List[Dict]:
"""List all FHRP groups."""
params = {k: v for k, v in {'protocol': protocol, 'group_id': group_id, **kwargs}.items() if v is not None}
return self.client.list(f'{self.base_endpoint}/fhrp-groups', params=params)
async def get_fhrp_group(self, id: int) -> Dict:
"""Get a specific FHRP group by ID."""
return self.client.get(f'{self.base_endpoint}/fhrp-groups', id)
async def create_fhrp_group(
self,
protocol: str,
group_id: int,
auth_type: Optional[str] = None,
auth_key: Optional[str] = None,
**kwargs
) -> Dict:
"""Create a new FHRP group."""
data = {'protocol': protocol, 'group_id': group_id, **kwargs}
if auth_type:
data['auth_type'] = auth_type
if auth_key:
data['auth_key'] = auth_key
return self.client.create(f'{self.base_endpoint}/fhrp-groups', data)
async def update_fhrp_group(self, id: int, **kwargs) -> Dict:
"""Update an FHRP group."""
return self.client.patch(f'{self.base_endpoint}/fhrp-groups', id, kwargs)
async def delete_fhrp_group(self, id: int) -> None:
"""Delete an FHRP group."""
self.client.delete(f'{self.base_endpoint}/fhrp-groups', id)
# ==================== FHRP Group Assignments ====================
async def list_fhrp_group_assignments(self, group_id: Optional[int] = None, **kwargs) -> List[Dict]:
"""List all FHRP group assignments."""
params = {k: v for k, v in {'group_id': group_id, **kwargs}.items() if v is not None}
return self.client.list(f'{self.base_endpoint}/fhrp-group-assignments', params=params)
async def get_fhrp_group_assignment(self, id: int) -> Dict:
"""Get a specific FHRP group assignment by ID."""
return self.client.get(f'{self.base_endpoint}/fhrp-group-assignments', id)
async def create_fhrp_group_assignment(
self,
group: int,
interface_type: str,
interface_id: int,
priority: int = 100,
**kwargs
) -> Dict:
"""Create a new FHRP group assignment."""
data = {
'group': group, 'interface_type': interface_type,
'interface_id': interface_id, 'priority': priority, **kwargs
}
return self.client.create(f'{self.base_endpoint}/fhrp-group-assignments', data)
async def update_fhrp_group_assignment(self, id: int, **kwargs) -> Dict:
"""Update an FHRP group assignment."""
return self.client.patch(f'{self.base_endpoint}/fhrp-group-assignments', id, kwargs)
async def delete_fhrp_group_assignment(self, id: int) -> None:
"""Delete an FHRP group assignment."""
self.client.delete(f'{self.base_endpoint}/fhrp-group-assignments', id)
# ==================== VLAN Groups ====================
async def list_vlan_groups(
self,
name: Optional[str] = None,
site_id: Optional[int] = None,
**kwargs
) -> List[Dict]:
"""List all VLAN groups."""
params = {k: v for k, v in {'name': name, 'site_id': site_id, **kwargs}.items() if v is not None}
return self.client.list(f'{self.base_endpoint}/vlan-groups', params=params)
async def get_vlan_group(self, id: int) -> Dict:
"""Get a specific VLAN group by ID."""
return self.client.get(f'{self.base_endpoint}/vlan-groups', id)
async def create_vlan_group(
self,
name: str,
slug: str,
scope_type: Optional[str] = None,
scope_id: Optional[int] = None,
min_vid: int = 1,
max_vid: int = 4094,
**kwargs
) -> Dict:
"""Create a new VLAN group."""
data = {'name': name, 'slug': slug, 'min_vid': min_vid, 'max_vid': max_vid, **kwargs}
if scope_type:
data['scope_type'] = scope_type
if scope_id:
data['scope_id'] = scope_id
return self.client.create(f'{self.base_endpoint}/vlan-groups', data)
async def update_vlan_group(self, id: int, **kwargs) -> Dict:
"""Update a VLAN group."""
return self.client.patch(f'{self.base_endpoint}/vlan-groups', id, kwargs)
async def delete_vlan_group(self, id: int) -> None:
"""Delete a VLAN group."""
self.client.delete(f'{self.base_endpoint}/vlan-groups', id)
async def list_available_vlans(self, id: int) -> List[Dict]:
"""List available VLANs in a VLAN group."""
return self.client.list(f'{self.base_endpoint}/vlan-groups/{id}/available-vlans', paginate=False)
# ==================== VLANs ====================
async def list_vlans(
self,
vid: Optional[int] = None,
name: Optional[str] = None,
site_id: Optional[int] = None,
group_id: Optional[int] = None,
role_id: Optional[int] = None,
tenant_id: Optional[int] = None,
status: Optional[str] = None,
**kwargs
) -> List[Dict]:
"""List all VLANs with optional filtering."""
params = {k: v for k, v in {
'vid': vid, 'name': name, 'site_id': site_id, 'group_id': group_id,
'role_id': role_id, 'tenant_id': tenant_id, 'status': status, **kwargs
}.items() if v is not None}
return self.client.list(f'{self.base_endpoint}/vlans', params=params)
async def get_vlan(self, id: int) -> Dict:
"""Get a specific VLAN by ID."""
return self.client.get(f'{self.base_endpoint}/vlans', id)
async def create_vlan(
self,
vid: int,
name: str,
status: str = 'active',
site: Optional[int] = None,
group: Optional[int] = None,
role: Optional[int] = None,
tenant: Optional[int] = None,
description: Optional[str] = None,
**kwargs
) -> Dict:
"""Create a new VLAN."""
data = {'vid': vid, 'name': name, 'status': status, **kwargs}
for key, val in [
('site', site), ('group', group), ('role', role),
('tenant', tenant), ('description', description)
]:
if val is not None:
data[key] = val
return self.client.create(f'{self.base_endpoint}/vlans', data)
async def update_vlan(self, id: int, **kwargs) -> Dict:
"""Update a VLAN."""
return self.client.patch(f'{self.base_endpoint}/vlans', id, kwargs)
async def delete_vlan(self, id: int) -> None:
"""Delete a VLAN."""
self.client.delete(f'{self.base_endpoint}/vlans', id)
# ==================== VRFs ====================
async def list_vrfs(
self,
name: Optional[str] = None,
rd: Optional[str] = None,
tenant_id: Optional[int] = None,
**kwargs
) -> List[Dict]:
"""List all VRFs with optional filtering."""
params = {k: v for k, v in {
'name': name, 'rd': rd, 'tenant_id': tenant_id, **kwargs
}.items() if v is not None}
return self.client.list(f'{self.base_endpoint}/vrfs', params=params)
async def get_vrf(self, id: int) -> Dict:
"""Get a specific VRF by ID."""
return self.client.get(f'{self.base_endpoint}/vrfs', id)
async def create_vrf(
self,
name: str,
rd: Optional[str] = None,
tenant: Optional[int] = None,
enforce_unique: bool = True,
description: Optional[str] = None,
import_targets: Optional[List[int]] = None,
export_targets: Optional[List[int]] = None,
**kwargs
) -> Dict:
"""Create a new VRF."""
data = {'name': name, 'enforce_unique': enforce_unique, **kwargs}
for key, val in [
('rd', rd), ('tenant', tenant), ('description', description),
('import_targets', import_targets), ('export_targets', export_targets)
]:
if val is not None:
data[key] = val
return self.client.create(f'{self.base_endpoint}/vrfs', data)
async def update_vrf(self, id: int, **kwargs) -> Dict:
"""Update a VRF."""
return self.client.patch(f'{self.base_endpoint}/vrfs', id, kwargs)
async def delete_vrf(self, id: int) -> None:
"""Delete a VRF."""
self.client.delete(f'{self.base_endpoint}/vrfs', id)
# ==================== Route Targets ====================
async def list_route_targets(
self,
name: Optional[str] = None,
tenant_id: Optional[int] = None,
**kwargs
) -> List[Dict]:
"""List all route targets."""
params = {k: v for k, v in {'name': name, 'tenant_id': tenant_id, **kwargs}.items() if v is not None}
return self.client.list(f'{self.base_endpoint}/route-targets', params=params)
async def get_route_target(self, id: int) -> Dict:
"""Get a specific route target by ID."""
return self.client.get(f'{self.base_endpoint}/route-targets', id)
async def create_route_target(
self,
name: str,
tenant: Optional[int] = None,
description: Optional[str] = None,
**kwargs
) -> Dict:
"""Create a new route target."""
data = {'name': name, **kwargs}
if tenant:
data['tenant'] = tenant
if description:
data['description'] = description
return self.client.create(f'{self.base_endpoint}/route-targets', data)
async def update_route_target(self, id: int, **kwargs) -> Dict:
"""Update a route target."""
return self.client.patch(f'{self.base_endpoint}/route-targets', id, kwargs)
async def delete_route_target(self, id: int) -> None:
"""Delete a route target."""
self.client.delete(f'{self.base_endpoint}/route-targets', id)
# ==================== Services ==================== # ==================== Services ====================
async def list_services( async def list_services(
@@ -675,44 +175,3 @@ class IPAMTools:
if val is not None: if val is not None:
data[key] = val data[key] = val
return self.client.create(f'{self.base_endpoint}/services', data) return self.client.create(f'{self.base_endpoint}/services', data)
async def update_service(self, id: int, **kwargs) -> Dict:
"""Update a service."""
return self.client.patch(f'{self.base_endpoint}/services', id, kwargs)
async def delete_service(self, id: int) -> None:
"""Delete a service."""
self.client.delete(f'{self.base_endpoint}/services', id)
# ==================== Service Templates ====================
async def list_service_templates(self, name: Optional[str] = None, **kwargs) -> List[Dict]:
"""List all service templates."""
params = {k: v for k, v in {'name': name, **kwargs}.items() if v is not None}
return self.client.list(f'{self.base_endpoint}/service-templates', params=params)
async def get_service_template(self, id: int) -> Dict:
"""Get a specific service template by ID."""
return self.client.get(f'{self.base_endpoint}/service-templates', id)
async def create_service_template(
self,
name: str,
ports: List[int],
protocol: str,
description: Optional[str] = None,
**kwargs
) -> Dict:
"""Create a new service template."""
data = {'name': name, 'ports': ports, 'protocol': protocol, **kwargs}
if description:
data['description'] = description
return self.client.create(f'{self.base_endpoint}/service-templates', data)
async def update_service_template(self, id: int, **kwargs) -> Dict:
"""Update a service template."""
return self.client.patch(f'{self.base_endpoint}/service-templates', id, kwargs)
async def delete_service_template(self, id: int) -> None:
"""Delete a service template."""
self.client.delete(f'{self.base_endpoint}/service-templates', id)

View File

@@ -1,281 +0,0 @@
"""
Tenancy tools for NetBox MCP Server.
Covers: Tenants, Tenant Groups, Contacts, Contact Groups, and Contact Roles.
"""
import logging
from typing import List, Dict, Optional, Any
from ..netbox_client import NetBoxClient
logger = logging.getLogger(__name__)
class TenancyTools:
"""Tools for Tenancy operations in NetBox"""
def __init__(self, client: NetBoxClient):
self.client = client
self.base_endpoint = 'tenancy'
# ==================== Tenant Groups ====================
async def list_tenant_groups(
self,
name: Optional[str] = None,
slug: Optional[str] = None,
parent_id: Optional[int] = None,
**kwargs
) -> List[Dict]:
"""List all tenant groups."""
params = {k: v for k, v in {
'name': name, 'slug': slug, 'parent_id': parent_id, **kwargs
}.items() if v is not None}
return self.client.list(f'{self.base_endpoint}/tenant-groups', params=params)
async def get_tenant_group(self, id: int) -> Dict:
"""Get a specific tenant group by ID."""
return self.client.get(f'{self.base_endpoint}/tenant-groups', id)
async def create_tenant_group(
self,
name: str,
slug: str,
parent: Optional[int] = None,
description: Optional[str] = None,
**kwargs
) -> Dict:
"""Create a new tenant group."""
data = {'name': name, 'slug': slug, **kwargs}
if parent:
data['parent'] = parent
if description:
data['description'] = description
return self.client.create(f'{self.base_endpoint}/tenant-groups', data)
async def update_tenant_group(self, id: int, **kwargs) -> Dict:
"""Update a tenant group."""
return self.client.patch(f'{self.base_endpoint}/tenant-groups', id, kwargs)
async def delete_tenant_group(self, id: int) -> None:
"""Delete a tenant group."""
self.client.delete(f'{self.base_endpoint}/tenant-groups', id)
# ==================== Tenants ====================
async def list_tenants(
self,
name: Optional[str] = None,
slug: Optional[str] = None,
group_id: Optional[int] = None,
**kwargs
) -> List[Dict]:
"""List all tenants with optional filtering."""
params = {k: v for k, v in {
'name': name, 'slug': slug, 'group_id': group_id, **kwargs
}.items() if v is not None}
return self.client.list(f'{self.base_endpoint}/tenants', params=params)
async def get_tenant(self, id: int) -> Dict:
"""Get a specific tenant by ID."""
return self.client.get(f'{self.base_endpoint}/tenants', id)
async def create_tenant(
self,
name: str,
slug: str,
group: Optional[int] = None,
description: Optional[str] = None,
**kwargs
) -> Dict:
"""Create a new tenant."""
data = {'name': name, 'slug': slug, **kwargs}
if group:
data['group'] = group
if description:
data['description'] = description
return self.client.create(f'{self.base_endpoint}/tenants', data)
async def update_tenant(self, id: int, **kwargs) -> Dict:
"""Update a tenant."""
return self.client.patch(f'{self.base_endpoint}/tenants', id, kwargs)
async def delete_tenant(self, id: int) -> None:
"""Delete a tenant."""
self.client.delete(f'{self.base_endpoint}/tenants', id)
# ==================== Contact Groups ====================
async def list_contact_groups(
self,
name: Optional[str] = None,
slug: Optional[str] = None,
parent_id: Optional[int] = None,
**kwargs
) -> List[Dict]:
"""List all contact groups."""
params = {k: v for k, v in {
'name': name, 'slug': slug, 'parent_id': parent_id, **kwargs
}.items() if v is not None}
return self.client.list(f'{self.base_endpoint}/contact-groups', params=params)
async def get_contact_group(self, id: int) -> Dict:
"""Get a specific contact group by ID."""
return self.client.get(f'{self.base_endpoint}/contact-groups', id)
async def create_contact_group(
self,
name: str,
slug: str,
parent: Optional[int] = None,
description: Optional[str] = None,
**kwargs
) -> Dict:
"""Create a new contact group."""
data = {'name': name, 'slug': slug, **kwargs}
if parent:
data['parent'] = parent
if description:
data['description'] = description
return self.client.create(f'{self.base_endpoint}/contact-groups', data)
async def update_contact_group(self, id: int, **kwargs) -> Dict:
"""Update a contact group."""
return self.client.patch(f'{self.base_endpoint}/contact-groups', id, kwargs)
async def delete_contact_group(self, id: int) -> None:
"""Delete a contact group."""
self.client.delete(f'{self.base_endpoint}/contact-groups', id)
# ==================== Contact Roles ====================
async def list_contact_roles(
self,
name: Optional[str] = None,
slug: Optional[str] = None,
**kwargs
) -> List[Dict]:
"""List all contact roles."""
params = {k: v for k, v in {'name': name, 'slug': slug, **kwargs}.items() if v is not None}
return self.client.list(f'{self.base_endpoint}/contact-roles', params=params)
async def get_contact_role(self, id: int) -> Dict:
"""Get a specific contact role by ID."""
return self.client.get(f'{self.base_endpoint}/contact-roles', id)
async def create_contact_role(
self,
name: str,
slug: str,
description: Optional[str] = None,
**kwargs
) -> Dict:
"""Create a new contact role."""
data = {'name': name, 'slug': slug, **kwargs}
if description:
data['description'] = description
return self.client.create(f'{self.base_endpoint}/contact-roles', data)
async def update_contact_role(self, id: int, **kwargs) -> Dict:
"""Update a contact role."""
return self.client.patch(f'{self.base_endpoint}/contact-roles', id, kwargs)
async def delete_contact_role(self, id: int) -> None:
"""Delete a contact role."""
self.client.delete(f'{self.base_endpoint}/contact-roles', id)
# ==================== Contacts ====================
async def list_contacts(
self,
name: Optional[str] = None,
group_id: Optional[int] = None,
email: Optional[str] = None,
**kwargs
) -> List[Dict]:
"""List all contacts with optional filtering."""
params = {k: v for k, v in {
'name': name, 'group_id': group_id, 'email': email, **kwargs
}.items() if v is not None}
return self.client.list(f'{self.base_endpoint}/contacts', params=params)
async def get_contact(self, id: int) -> Dict:
"""Get a specific contact by ID."""
return self.client.get(f'{self.base_endpoint}/contacts', id)
async def create_contact(
self,
name: str,
group: Optional[int] = None,
title: Optional[str] = None,
phone: Optional[str] = None,
email: Optional[str] = None,
address: Optional[str] = None,
link: Optional[str] = None,
description: Optional[str] = None,
**kwargs
) -> Dict:
"""Create a new contact."""
data = {'name': name, **kwargs}
for key, val in [
('group', group), ('title', title), ('phone', phone),
('email', email), ('address', address), ('link', link),
('description', description)
]:
if val is not None:
data[key] = val
return self.client.create(f'{self.base_endpoint}/contacts', data)
async def update_contact(self, id: int, **kwargs) -> Dict:
"""Update a contact."""
return self.client.patch(f'{self.base_endpoint}/contacts', id, kwargs)
async def delete_contact(self, id: int) -> None:
"""Delete a contact."""
self.client.delete(f'{self.base_endpoint}/contacts', id)
# ==================== Contact Assignments ====================
async def list_contact_assignments(
self,
contact_id: Optional[int] = None,
role_id: Optional[int] = None,
object_type: Optional[str] = None,
object_id: Optional[int] = None,
**kwargs
) -> List[Dict]:
"""List all contact assignments."""
params = {k: v for k, v in {
'contact_id': contact_id, 'role_id': role_id,
'object_type': object_type, 'object_id': object_id, **kwargs
}.items() if v is not None}
return self.client.list(f'{self.base_endpoint}/contact-assignments', params=params)
async def get_contact_assignment(self, id: int) -> Dict:
"""Get a specific contact assignment by ID."""
return self.client.get(f'{self.base_endpoint}/contact-assignments', id)
async def create_contact_assignment(
self,
contact: int,
role: int,
object_type: str,
object_id: int,
priority: Optional[str] = None,
**kwargs
) -> Dict:
"""Create a new contact assignment."""
data = {
'contact': contact, 'role': role,
'object_type': object_type, 'object_id': object_id, **kwargs
}
if priority:
data['priority'] = priority
return self.client.create(f'{self.base_endpoint}/contact-assignments', data)
async def update_contact_assignment(self, id: int, **kwargs) -> Dict:
"""Update a contact assignment."""
return self.client.patch(f'{self.base_endpoint}/contact-assignments', id, kwargs)
async def delete_contact_assignment(self, id: int) -> None:
"""Delete a contact assignment."""
self.client.delete(f'{self.base_endpoint}/contact-assignments', id)

View File

@@ -1,7 +1,7 @@
""" """
Virtualization tools for NetBox MCP Server. Virtualization tools for NetBox MCP Server.
Covers: Clusters, Virtual Machines, VM Interfaces, and related models. Covers: Clusters, Virtual Machines, and VM Interfaces only.
""" """
import logging import logging
from typing import List, Dict, Optional, Any from typing import List, Dict, Optional, Any
@@ -17,80 +17,6 @@ class VirtualizationTools:
self.client = client self.client = client
self.base_endpoint = 'virtualization' self.base_endpoint = 'virtualization'
# ==================== Cluster Types ====================
async def list_cluster_types(
self,
name: Optional[str] = None,
slug: Optional[str] = None,
**kwargs
) -> List[Dict]:
"""List all cluster types."""
params = {k: v for k, v in {'name': name, 'slug': slug, **kwargs}.items() if v is not None}
return self.client.list(f'{self.base_endpoint}/cluster-types', params=params)
async def get_cluster_type(self, id: int) -> Dict:
"""Get a specific cluster type by ID."""
return self.client.get(f'{self.base_endpoint}/cluster-types', id)
async def create_cluster_type(
self,
name: str,
slug: str,
description: Optional[str] = None,
**kwargs
) -> Dict:
"""Create a new cluster type."""
data = {'name': name, 'slug': slug, **kwargs}
if description:
data['description'] = description
return self.client.create(f'{self.base_endpoint}/cluster-types', data)
async def update_cluster_type(self, id: int, **kwargs) -> Dict:
"""Update a cluster type."""
return self.client.patch(f'{self.base_endpoint}/cluster-types', id, kwargs)
async def delete_cluster_type(self, id: int) -> None:
"""Delete a cluster type."""
self.client.delete(f'{self.base_endpoint}/cluster-types', id)
# ==================== Cluster Groups ====================
async def list_cluster_groups(
self,
name: Optional[str] = None,
slug: Optional[str] = None,
**kwargs
) -> List[Dict]:
"""List all cluster groups."""
params = {k: v for k, v in {'name': name, 'slug': slug, **kwargs}.items() if v is not None}
return self.client.list(f'{self.base_endpoint}/cluster-groups', params=params)
async def get_cluster_group(self, id: int) -> Dict:
"""Get a specific cluster group by ID."""
return self.client.get(f'{self.base_endpoint}/cluster-groups', id)
async def create_cluster_group(
self,
name: str,
slug: str,
description: Optional[str] = None,
**kwargs
) -> Dict:
"""Create a new cluster group."""
data = {'name': name, 'slug': slug, **kwargs}
if description:
data['description'] = description
return self.client.create(f'{self.base_endpoint}/cluster-groups', data)
async def update_cluster_group(self, id: int, **kwargs) -> Dict:
"""Update a cluster group."""
return self.client.patch(f'{self.base_endpoint}/cluster-groups', id, kwargs)
async def delete_cluster_group(self, id: int) -> None:
"""Delete a cluster group."""
self.client.delete(f'{self.base_endpoint}/cluster-groups', id)
# ==================== Clusters ==================== # ==================== Clusters ====================
async def list_clusters( async def list_clusters(
@@ -134,14 +60,6 @@ class VirtualizationTools:
data[key] = val data[key] = val
return self.client.create(f'{self.base_endpoint}/clusters', data) return self.client.create(f'{self.base_endpoint}/clusters', data)
async def update_cluster(self, id: int, **kwargs) -> Dict:
"""Update a cluster."""
return self.client.patch(f'{self.base_endpoint}/clusters', id, kwargs)
async def delete_cluster(self, id: int) -> None:
"""Delete a cluster."""
self.client.delete(f'{self.base_endpoint}/clusters', id)
# ==================== Virtual Machines ==================== # ==================== Virtual Machines ====================
async def list_virtual_machines( async def list_virtual_machines(
@@ -201,10 +119,6 @@ class VirtualizationTools:
"""Update a virtual machine.""" """Update a virtual machine."""
return self.client.patch(f'{self.base_endpoint}/virtual-machines', id, kwargs) return self.client.patch(f'{self.base_endpoint}/virtual-machines', id, kwargs)
async def delete_virtual_machine(self, id: int) -> None:
"""Delete a virtual machine."""
self.client.delete(f'{self.base_endpoint}/virtual-machines', id)
# ==================== VM Interfaces ==================== # ==================== VM Interfaces ====================
async def list_vm_interfaces( async def list_vm_interfaces(
@@ -246,51 +160,3 @@ class VirtualizationTools:
if val is not None: if val is not None:
data[key] = val data[key] = val
return self.client.create(f'{self.base_endpoint}/interfaces', data) return self.client.create(f'{self.base_endpoint}/interfaces', data)
async def update_vm_interface(self, id: int, **kwargs) -> Dict:
"""Update a VM interface."""
return self.client.patch(f'{self.base_endpoint}/interfaces', id, kwargs)
async def delete_vm_interface(self, id: int) -> None:
"""Delete a VM interface."""
self.client.delete(f'{self.base_endpoint}/interfaces', id)
# ==================== Virtual Disks ====================
async def list_virtual_disks(
self,
virtual_machine_id: Optional[int] = None,
name: Optional[str] = None,
**kwargs
) -> List[Dict]:
"""List all virtual disks."""
params = {k: v for k, v in {
'virtual_machine_id': virtual_machine_id, 'name': name, **kwargs
}.items() if v is not None}
return self.client.list(f'{self.base_endpoint}/virtual-disks', params=params)
async def get_virtual_disk(self, id: int) -> Dict:
"""Get a specific virtual disk by ID."""
return self.client.get(f'{self.base_endpoint}/virtual-disks', id)
async def create_virtual_disk(
self,
virtual_machine: int,
name: str,
size: int,
description: Optional[str] = None,
**kwargs
) -> Dict:
"""Create a new virtual disk."""
data = {'virtual_machine': virtual_machine, 'name': name, 'size': size, **kwargs}
if description:
data['description'] = description
return self.client.create(f'{self.base_endpoint}/virtual-disks', data)
async def update_virtual_disk(self, id: int, **kwargs) -> Dict:
"""Update a virtual disk."""
return self.client.patch(f'{self.base_endpoint}/virtual-disks', id, kwargs)
async def delete_virtual_disk(self, id: int) -> None:
"""Delete a virtual disk."""
self.client.delete(f'{self.base_endpoint}/virtual-disks', id)

View File

@@ -1,428 +0,0 @@
"""
VPN tools for NetBox MCP Server.
Covers: Tunnels, Tunnel Groups, Tunnel Terminations, IKE/IPSec Policies, and L2VPN.
"""
import logging
from typing import List, Dict, Optional, Any
from ..netbox_client import NetBoxClient
logger = logging.getLogger(__name__)
class VPNTools:
"""Tools for VPN operations in NetBox"""
def __init__(self, client: NetBoxClient):
self.client = client
self.base_endpoint = 'vpn'
# ==================== Tunnel Groups ====================
async def list_tunnel_groups(
self,
name: Optional[str] = None,
slug: Optional[str] = None,
**kwargs
) -> List[Dict]:
"""List all tunnel groups."""
params = {k: v for k, v in {'name': name, 'slug': slug, **kwargs}.items() if v is not None}
return self.client.list(f'{self.base_endpoint}/tunnel-groups', params=params)
async def get_tunnel_group(self, id: int) -> Dict:
"""Get a specific tunnel group by ID."""
return self.client.get(f'{self.base_endpoint}/tunnel-groups', id)
async def create_tunnel_group(
self,
name: str,
slug: str,
description: Optional[str] = None,
**kwargs
) -> Dict:
"""Create a new tunnel group."""
data = {'name': name, 'slug': slug, **kwargs}
if description:
data['description'] = description
return self.client.create(f'{self.base_endpoint}/tunnel-groups', data)
async def update_tunnel_group(self, id: int, **kwargs) -> Dict:
"""Update a tunnel group."""
return self.client.patch(f'{self.base_endpoint}/tunnel-groups', id, kwargs)
async def delete_tunnel_group(self, id: int) -> None:
"""Delete a tunnel group."""
self.client.delete(f'{self.base_endpoint}/tunnel-groups', id)
# ==================== Tunnels ====================
async def list_tunnels(
self,
name: Optional[str] = None,
status: Optional[str] = None,
group_id: Optional[int] = None,
encapsulation: Optional[str] = None,
tenant_id: Optional[int] = None,
**kwargs
) -> List[Dict]:
"""List all tunnels with optional filtering."""
params = {k: v for k, v in {
'name': name, 'status': status, 'group_id': group_id,
'encapsulation': encapsulation, 'tenant_id': tenant_id, **kwargs
}.items() if v is not None}
return self.client.list(f'{self.base_endpoint}/tunnels', params=params)
async def get_tunnel(self, id: int) -> Dict:
"""Get a specific tunnel by ID."""
return self.client.get(f'{self.base_endpoint}/tunnels', id)
async def create_tunnel(
self,
name: str,
status: str = 'active',
encapsulation: str = 'ipsec-tunnel',
group: Optional[int] = None,
ipsec_profile: Optional[int] = None,
tenant: Optional[int] = None,
tunnel_id: Optional[int] = None,
description: Optional[str] = None,
**kwargs
) -> Dict:
"""Create a new tunnel."""
data = {'name': name, 'status': status, 'encapsulation': encapsulation, **kwargs}
for key, val in [
('group', group), ('ipsec_profile', ipsec_profile),
('tenant', tenant), ('tunnel_id', tunnel_id), ('description', description)
]:
if val is not None:
data[key] = val
return self.client.create(f'{self.base_endpoint}/tunnels', data)
async def update_tunnel(self, id: int, **kwargs) -> Dict:
"""Update a tunnel."""
return self.client.patch(f'{self.base_endpoint}/tunnels', id, kwargs)
async def delete_tunnel(self, id: int) -> None:
"""Delete a tunnel."""
self.client.delete(f'{self.base_endpoint}/tunnels', id)
# ==================== Tunnel Terminations ====================
async def list_tunnel_terminations(
self,
tunnel_id: Optional[int] = None,
role: Optional[str] = None,
**kwargs
) -> List[Dict]:
"""List all tunnel terminations."""
params = {k: v for k, v in {
'tunnel_id': tunnel_id, 'role': role, **kwargs
}.items() if v is not None}
return self.client.list(f'{self.base_endpoint}/tunnel-terminations', params=params)
async def get_tunnel_termination(self, id: int) -> Dict:
"""Get a specific tunnel termination by ID."""
return self.client.get(f'{self.base_endpoint}/tunnel-terminations', id)
async def create_tunnel_termination(
self,
tunnel: int,
role: str,
termination_type: str,
termination_id: int,
outside_ip: Optional[int] = None,
**kwargs
) -> Dict:
"""Create a new tunnel termination."""
data = {
'tunnel': tunnel, 'role': role,
'termination_type': termination_type, 'termination_id': termination_id, **kwargs
}
if outside_ip:
data['outside_ip'] = outside_ip
return self.client.create(f'{self.base_endpoint}/tunnel-terminations', data)
async def update_tunnel_termination(self, id: int, **kwargs) -> Dict:
"""Update a tunnel termination."""
return self.client.patch(f'{self.base_endpoint}/tunnel-terminations', id, kwargs)
async def delete_tunnel_termination(self, id: int) -> None:
"""Delete a tunnel termination."""
self.client.delete(f'{self.base_endpoint}/tunnel-terminations', id)
# ==================== IKE Proposals ====================
async def list_ike_proposals(self, name: Optional[str] = None, **kwargs) -> List[Dict]:
"""List all IKE proposals."""
params = {k: v for k, v in {'name': name, **kwargs}.items() if v is not None}
return self.client.list(f'{self.base_endpoint}/ike-proposals', params=params)
async def get_ike_proposal(self, id: int) -> Dict:
"""Get a specific IKE proposal by ID."""
return self.client.get(f'{self.base_endpoint}/ike-proposals', id)
async def create_ike_proposal(
self,
name: str,
authentication_method: str,
encryption_algorithm: str,
authentication_algorithm: str,
group: int,
sa_lifetime: Optional[int] = None,
description: Optional[str] = None,
**kwargs
) -> Dict:
"""Create a new IKE proposal."""
data = {
'name': name, 'authentication_method': authentication_method,
'encryption_algorithm': encryption_algorithm,
'authentication_algorithm': authentication_algorithm, 'group': group, **kwargs
}
if sa_lifetime:
data['sa_lifetime'] = sa_lifetime
if description:
data['description'] = description
return self.client.create(f'{self.base_endpoint}/ike-proposals', data)
async def update_ike_proposal(self, id: int, **kwargs) -> Dict:
"""Update an IKE proposal."""
return self.client.patch(f'{self.base_endpoint}/ike-proposals', id, kwargs)
async def delete_ike_proposal(self, id: int) -> None:
"""Delete an IKE proposal."""
self.client.delete(f'{self.base_endpoint}/ike-proposals', id)
# ==================== IKE Policies ====================
async def list_ike_policies(self, name: Optional[str] = None, **kwargs) -> List[Dict]:
"""List all IKE policies."""
params = {k: v for k, v in {'name': name, **kwargs}.items() if v is not None}
return self.client.list(f'{self.base_endpoint}/ike-policies', params=params)
async def get_ike_policy(self, id: int) -> Dict:
"""Get a specific IKE policy by ID."""
return self.client.get(f'{self.base_endpoint}/ike-policies', id)
async def create_ike_policy(
self,
name: str,
version: int,
mode: str,
proposals: List[int],
preshared_key: Optional[str] = None,
description: Optional[str] = None,
**kwargs
) -> Dict:
"""Create a new IKE policy."""
data = {'name': name, 'version': version, 'mode': mode, 'proposals': proposals, **kwargs}
if preshared_key:
data['preshared_key'] = preshared_key
if description:
data['description'] = description
return self.client.create(f'{self.base_endpoint}/ike-policies', data)
async def update_ike_policy(self, id: int, **kwargs) -> Dict:
"""Update an IKE policy."""
return self.client.patch(f'{self.base_endpoint}/ike-policies', id, kwargs)
async def delete_ike_policy(self, id: int) -> None:
"""Delete an IKE policy."""
self.client.delete(f'{self.base_endpoint}/ike-policies', id)
# ==================== IPSec Proposals ====================
async def list_ipsec_proposals(self, name: Optional[str] = None, **kwargs) -> List[Dict]:
"""List all IPSec proposals."""
params = {k: v for k, v in {'name': name, **kwargs}.items() if v is not None}
return self.client.list(f'{self.base_endpoint}/ipsec-proposals', params=params)
async def get_ipsec_proposal(self, id: int) -> Dict:
"""Get a specific IPSec proposal by ID."""
return self.client.get(f'{self.base_endpoint}/ipsec-proposals', id)
async def create_ipsec_proposal(
self,
name: str,
encryption_algorithm: str,
authentication_algorithm: str,
sa_lifetime_seconds: Optional[int] = None,
sa_lifetime_data: Optional[int] = None,
description: Optional[str] = None,
**kwargs
) -> Dict:
"""Create a new IPSec proposal."""
data = {
'name': name, 'encryption_algorithm': encryption_algorithm,
'authentication_algorithm': authentication_algorithm, **kwargs
}
for key, val in [
('sa_lifetime_seconds', sa_lifetime_seconds),
('sa_lifetime_data', sa_lifetime_data), ('description', description)
]:
if val is not None:
data[key] = val
return self.client.create(f'{self.base_endpoint}/ipsec-proposals', data)
async def update_ipsec_proposal(self, id: int, **kwargs) -> Dict:
"""Update an IPSec proposal."""
return self.client.patch(f'{self.base_endpoint}/ipsec-proposals', id, kwargs)
async def delete_ipsec_proposal(self, id: int) -> None:
"""Delete an IPSec proposal."""
self.client.delete(f'{self.base_endpoint}/ipsec-proposals', id)
# ==================== IPSec Policies ====================
async def list_ipsec_policies(self, name: Optional[str] = None, **kwargs) -> List[Dict]:
"""List all IPSec policies."""
params = {k: v for k, v in {'name': name, **kwargs}.items() if v is not None}
return self.client.list(f'{self.base_endpoint}/ipsec-policies', params=params)
async def get_ipsec_policy(self, id: int) -> Dict:
"""Get a specific IPSec policy by ID."""
return self.client.get(f'{self.base_endpoint}/ipsec-policies', id)
async def create_ipsec_policy(
self,
name: str,
proposals: List[int],
pfs_group: Optional[int] = None,
description: Optional[str] = None,
**kwargs
) -> Dict:
"""Create a new IPSec policy."""
data = {'name': name, 'proposals': proposals, **kwargs}
if pfs_group:
data['pfs_group'] = pfs_group
if description:
data['description'] = description
return self.client.create(f'{self.base_endpoint}/ipsec-policies', data)
async def update_ipsec_policy(self, id: int, **kwargs) -> Dict:
"""Update an IPSec policy."""
return self.client.patch(f'{self.base_endpoint}/ipsec-policies', id, kwargs)
async def delete_ipsec_policy(self, id: int) -> None:
"""Delete an IPSec policy."""
self.client.delete(f'{self.base_endpoint}/ipsec-policies', id)
# ==================== IPSec Profiles ====================
async def list_ipsec_profiles(self, name: Optional[str] = None, **kwargs) -> List[Dict]:
"""List all IPSec profiles."""
params = {k: v for k, v in {'name': name, **kwargs}.items() if v is not None}
return self.client.list(f'{self.base_endpoint}/ipsec-profiles', params=params)
async def get_ipsec_profile(self, id: int) -> Dict:
"""Get a specific IPSec profile by ID."""
return self.client.get(f'{self.base_endpoint}/ipsec-profiles', id)
async def create_ipsec_profile(
self,
name: str,
mode: str,
ike_policy: int,
ipsec_policy: int,
description: Optional[str] = None,
**kwargs
) -> Dict:
"""Create a new IPSec profile."""
data = {'name': name, 'mode': mode, 'ike_policy': ike_policy, 'ipsec_policy': ipsec_policy, **kwargs}
if description:
data['description'] = description
return self.client.create(f'{self.base_endpoint}/ipsec-profiles', data)
async def update_ipsec_profile(self, id: int, **kwargs) -> Dict:
"""Update an IPSec profile."""
return self.client.patch(f'{self.base_endpoint}/ipsec-profiles', id, kwargs)
async def delete_ipsec_profile(self, id: int) -> None:
"""Delete an IPSec profile."""
self.client.delete(f'{self.base_endpoint}/ipsec-profiles', id)
# ==================== L2VPN ====================
async def list_l2vpns(
self,
name: Optional[str] = None,
slug: Optional[str] = None,
type: Optional[str] = None,
tenant_id: Optional[int] = None,
**kwargs
) -> List[Dict]:
"""List all L2VPNs with optional filtering."""
params = {k: v for k, v in {
'name': name, 'slug': slug, 'type': type, 'tenant_id': tenant_id, **kwargs
}.items() if v is not None}
return self.client.list(f'{self.base_endpoint}/l2vpns', params=params)
async def get_l2vpn(self, id: int) -> Dict:
"""Get a specific L2VPN by ID."""
return self.client.get(f'{self.base_endpoint}/l2vpns', id)
async def create_l2vpn(
self,
name: str,
slug: str,
type: str,
identifier: Optional[int] = None,
tenant: Optional[int] = None,
description: Optional[str] = None,
import_targets: Optional[List[int]] = None,
export_targets: Optional[List[int]] = None,
**kwargs
) -> Dict:
"""Create a new L2VPN."""
data = {'name': name, 'slug': slug, 'type': type, **kwargs}
for key, val in [
('identifier', identifier), ('tenant', tenant), ('description', description),
('import_targets', import_targets), ('export_targets', export_targets)
]:
if val is not None:
data[key] = val
return self.client.create(f'{self.base_endpoint}/l2vpns', data)
async def update_l2vpn(self, id: int, **kwargs) -> Dict:
"""Update an L2VPN."""
return self.client.patch(f'{self.base_endpoint}/l2vpns', id, kwargs)
async def delete_l2vpn(self, id: int) -> None:
"""Delete an L2VPN."""
self.client.delete(f'{self.base_endpoint}/l2vpns', id)
# ==================== L2VPN Terminations ====================
async def list_l2vpn_terminations(
self,
l2vpn_id: Optional[int] = None,
**kwargs
) -> List[Dict]:
"""List all L2VPN terminations."""
params = {k: v for k, v in {'l2vpn_id': l2vpn_id, **kwargs}.items() if v is not None}
return self.client.list(f'{self.base_endpoint}/l2vpn-terminations', params=params)
async def get_l2vpn_termination(self, id: int) -> Dict:
"""Get a specific L2VPN termination by ID."""
return self.client.get(f'{self.base_endpoint}/l2vpn-terminations', id)
async def create_l2vpn_termination(
self,
l2vpn: int,
assigned_object_type: str,
assigned_object_id: int,
**kwargs
) -> Dict:
"""Create a new L2VPN termination."""
data = {
'l2vpn': l2vpn, 'assigned_object_type': assigned_object_type,
'assigned_object_id': assigned_object_id, **kwargs
}
return self.client.create(f'{self.base_endpoint}/l2vpn-terminations', data)
async def update_l2vpn_termination(self, id: int, **kwargs) -> Dict:
"""Update an L2VPN termination."""
return self.client.patch(f'{self.base_endpoint}/l2vpn-terminations', id, kwargs)
async def delete_l2vpn_termination(self, id: int) -> None:
"""Delete an L2VPN termination."""
self.client.delete(f'{self.base_endpoint}/l2vpn-terminations', id)

View File

@@ -1,166 +0,0 @@
"""
Wireless tools for NetBox MCP Server.
Covers: Wireless LANs, Wireless LAN Groups, and Wireless Links.
"""
import logging
from typing import List, Dict, Optional, Any
from ..netbox_client import NetBoxClient
logger = logging.getLogger(__name__)
class WirelessTools:
"""Tools for Wireless operations in NetBox"""
def __init__(self, client: NetBoxClient):
self.client = client
self.base_endpoint = 'wireless'
# ==================== Wireless LAN Groups ====================
async def list_wireless_lan_groups(
self,
name: Optional[str] = None,
slug: Optional[str] = None,
parent_id: Optional[int] = None,
**kwargs
) -> List[Dict]:
"""List all wireless LAN groups."""
params = {k: v for k, v in {
'name': name, 'slug': slug, 'parent_id': parent_id, **kwargs
}.items() if v is not None}
return self.client.list(f'{self.base_endpoint}/wireless-lan-groups', params=params)
async def get_wireless_lan_group(self, id: int) -> Dict:
"""Get a specific wireless LAN group by ID."""
return self.client.get(f'{self.base_endpoint}/wireless-lan-groups', id)
async def create_wireless_lan_group(
self,
name: str,
slug: str,
parent: Optional[int] = None,
description: Optional[str] = None,
**kwargs
) -> Dict:
"""Create a new wireless LAN group."""
data = {'name': name, 'slug': slug, **kwargs}
if parent:
data['parent'] = parent
if description:
data['description'] = description
return self.client.create(f'{self.base_endpoint}/wireless-lan-groups', data)
async def update_wireless_lan_group(self, id: int, **kwargs) -> Dict:
"""Update a wireless LAN group."""
return self.client.patch(f'{self.base_endpoint}/wireless-lan-groups', id, kwargs)
async def delete_wireless_lan_group(self, id: int) -> None:
"""Delete a wireless LAN group."""
self.client.delete(f'{self.base_endpoint}/wireless-lan-groups', id)
# ==================== Wireless LANs ====================
async def list_wireless_lans(
self,
ssid: Optional[str] = None,
group_id: Optional[int] = None,
vlan_id: Optional[int] = None,
tenant_id: Optional[int] = None,
status: Optional[str] = None,
auth_type: Optional[str] = None,
**kwargs
) -> List[Dict]:
"""List all wireless LANs with optional filtering."""
params = {k: v for k, v in {
'ssid': ssid, 'group_id': group_id, 'vlan_id': vlan_id,
'tenant_id': tenant_id, 'status': status, 'auth_type': auth_type, **kwargs
}.items() if v is not None}
return self.client.list(f'{self.base_endpoint}/wireless-lans', params=params)
async def get_wireless_lan(self, id: int) -> Dict:
"""Get a specific wireless LAN by ID."""
return self.client.get(f'{self.base_endpoint}/wireless-lans', id)
async def create_wireless_lan(
self,
ssid: str,
status: str = 'active',
group: Optional[int] = None,
vlan: Optional[int] = None,
tenant: Optional[int] = None,
auth_type: Optional[str] = None,
auth_cipher: Optional[str] = None,
auth_psk: Optional[str] = None,
description: Optional[str] = None,
**kwargs
) -> Dict:
"""Create a new wireless LAN."""
data = {'ssid': ssid, 'status': status, **kwargs}
for key, val in [
('group', group), ('vlan', vlan), ('tenant', tenant),
('auth_type', auth_type), ('auth_cipher', auth_cipher),
('auth_psk', auth_psk), ('description', description)
]:
if val is not None:
data[key] = val
return self.client.create(f'{self.base_endpoint}/wireless-lans', data)
async def update_wireless_lan(self, id: int, **kwargs) -> Dict:
"""Update a wireless LAN."""
return self.client.patch(f'{self.base_endpoint}/wireless-lans', id, kwargs)
async def delete_wireless_lan(self, id: int) -> None:
"""Delete a wireless LAN."""
self.client.delete(f'{self.base_endpoint}/wireless-lans', id)
# ==================== Wireless Links ====================
async def list_wireless_links(
self,
ssid: Optional[str] = None,
status: Optional[str] = None,
tenant_id: Optional[int] = None,
**kwargs
) -> List[Dict]:
"""List all wireless links with optional filtering."""
params = {k: v for k, v in {
'ssid': ssid, 'status': status, 'tenant_id': tenant_id, **kwargs
}.items() if v is not None}
return self.client.list(f'{self.base_endpoint}/wireless-links', params=params)
async def get_wireless_link(self, id: int) -> Dict:
"""Get a specific wireless link by ID."""
return self.client.get(f'{self.base_endpoint}/wireless-links', id)
async def create_wireless_link(
self,
interface_a: int,
interface_b: int,
ssid: Optional[str] = None,
status: str = 'connected',
tenant: Optional[int] = None,
auth_type: Optional[str] = None,
auth_cipher: Optional[str] = None,
auth_psk: Optional[str] = None,
description: Optional[str] = None,
**kwargs
) -> Dict:
"""Create a new wireless link."""
data = {'interface_a': interface_a, 'interface_b': interface_b, 'status': status, **kwargs}
for key, val in [
('ssid', ssid), ('tenant', tenant), ('auth_type', auth_type),
('auth_cipher', auth_cipher), ('auth_psk', auth_psk), ('description', description)
]:
if val is not None:
data[key] = val
return self.client.create(f'{self.base_endpoint}/wireless-links', data)
async def update_wireless_link(self, id: int, **kwargs) -> Dict:
"""Update a wireless link."""
return self.client.patch(f'{self.base_endpoint}/wireless-links', id, kwargs)
async def delete_wireless_link(self, id: int) -> None:
"""Delete a wireless link."""
self.client.delete(f'{self.base_endpoint}/wireless-links', id)

View File

@@ -0,0 +1,3 @@
{
"domain": "core"
}

View File

@@ -1,6 +1,6 @@
{ {
"name": "clarity-assist", "name": "clarity-assist",
"version": "1.2.0", "version": "9.0.1",
"description": "Prompt optimization and requirement clarification with ND-friendly accommodations", "description": "Prompt optimization and requirement clarification with ND-friendly accommodations",
"author": { "author": {
"name": "Leo Miranda", "name": "Leo Miranda",
@@ -18,6 +18,5 @@
], ],
"commands": [ "commands": [
"./commands/" "./commands/"
], ]
"domain": "core"
} }

View File

@@ -119,7 +119,7 @@ Track gathered information in a mental model:
### After Clarification ### After Clarification
Produce a clear specification (see /clarify command for format). Produce a clear specification (see /clarity clarify command for format).
## Example Session ## Example Session

View File

@@ -18,8 +18,8 @@ This project uses the clarity-assist plugin for requirement gathering.
| Command | Use Case | | Command | Use Case |
|---------|----------| |---------|----------|
| `/clarify` | Full 4-D methodology for complex requests | | `/clarity clarify` | Full 4-D methodology for complex requests |
| `/quick-clarify` | Rapid mode for simple disambiguation | | `/clarity quick-clarify` | Rapid mode for simple disambiguation |
### Communication Style ### Communication Style

View File

@@ -1,4 +1,8 @@
# /clarify - Full Prompt Optimization ---
name: clarity clarify
---
# /clarity clarify - Full Prompt Optimization
## Visual Output ## Visual Output

View File

@@ -1,4 +1,8 @@
# /quick-clarify - Rapid Clarification Mode ---
name: clarity quick-clarify
---
# /clarity quick-clarify - Rapid Clarification Mode
## Visual Output ## Visual Output
@@ -23,7 +27,7 @@ Single-pass clarification for requests that are mostly clear but need minor disa
- `skills/nd-accommodations.md` - ND-friendly question patterns - `skills/nd-accommodations.md` - ND-friendly question patterns
- `skills/clarification-techniques.md` - Echo and micro-summary techniques - `skills/clarification-techniques.md` - Echo and micro-summary techniques
- `skills/escalation-patterns.md` - When to escalate to full /clarify - `skills/escalation-patterns.md` - When to escalate to full `/clarity clarify`
## Workflow ## Workflow
@@ -37,7 +41,7 @@ No formal specification document needed. Proceed after brief confirmation, docum
## Escalation ## Escalation
If complexity emerges, offer to switch to full `/clarify`: If complexity emerges, offer to switch to full `/clarity clarify`:
``` ```
"This is more involved than it first appeared. Want me to switch "This is more involved than it first appeared. Want me to switch

View File

@@ -0,0 +1,31 @@
---
name: clarity
description: Prompt optimization and requirement clarification — type /clarity <action> for commands
---
# /clarity
Prompt optimization and requirement clarification with ND-friendly accommodations.
When invoked without a sub-command, display available actions and ask which to run.
## Available Commands
| Action | Command to Invoke | Description |
|--------|-------------------|-------------|
| `clarify` | `/clarity-assist:clarity-clarify` | Full 4-D methodology for complex requests |
| `quick-clarify` | `/clarity-assist:clarity-quick-clarify` | Rapid mode for simple disambiguation |
## Routing
If `$ARGUMENTS` is provided (e.g., user typed `/clarity clarify`):
1. Match the first word of `$ARGUMENTS` against the **Action** column above
2. **Invoke the corresponding command** from the "Command to Invoke" column using the Skill tool
3. Pass any remaining arguments to the invoked command
If no arguments provided:
1. Display the Available Commands table
2. Ask: "Which action would you like to run?"
3. When the user responds, invoke the matching command using the Skill tool
**Note:** Commands can also be invoked directly using their plugin-prefixed names (e.g., `/clarity-assist:clarity-clarify`)

View File

@@ -58,8 +58,8 @@ Our design philosophy centers on three principles:
### 3. Customizable Verbosity ### 3. Customizable Verbosity
**Detail Levels** **Detail Levels**
- `/clarify` - Full methodology for complex requests (more thorough, more questions) - `/clarity clarify` - Full methodology for complex requests (more thorough, more questions)
- `/quick-clarify` - Rapid mode for simple disambiguation (fewer questions, faster) - `/clarity quick-clarify` - Rapid mode for simple disambiguation (fewer questions, faster)
**User Control** **User Control**
- Users can always say "that's enough detail" to end questioning early - Users can always say "that's enough detail" to end questioning early
@@ -68,7 +68,7 @@ Our design philosophy centers on three principles:
### 4. Vagueness Detection ### 4. Vagueness Detection
The `UserPromptSubmit` hook automatically detects prompts that might benefit from clarification and gently suggests using `/clarify`. The `UserPromptSubmit` hook automatically detects prompts that might benefit from clarification and gently suggests using `/clarity clarify`.
**Detection Signals** **Detection Signals**
- Short prompts (< 10 words) without specific technical terms - Short prompts (< 10 words) without specific technical terms
@@ -156,10 +156,10 @@ This triggers vagueness detection because:
- No specific technical context - No specific technical context
- No measurable outcome - No measurable outcome
### After: Clarified with /clarify ### After: Clarified with /clarity clarify
``` ```
User: /clarify Make the app faster User: /clarity clarify Make the app faster
Claude: Let me help clarify what kind of performance improvements you're looking for. Claude: Let me help clarify what kind of performance improvements you're looking for.
@@ -235,7 +235,7 @@ Optimize initial page load time to under 2 seconds by addressing bundle size and
For simpler requests that just need minor disambiguation: For simpler requests that just need minor disambiguation:
``` ```
User: /quick-clarify Add a delete button to the user list User: /clarity quick-clarify Add a delete button to the user list
Claude: I'll add a delete button to each row in the user list. Claude: I'll add a delete button to each row in the user list.
@@ -286,14 +286,14 @@ export CLARITY_ASSIST_VAGUENESS_THRESHOLD=0.8
### If You're Feeling Overwhelmed ### If You're Feeling Overwhelmed
- Use `/quick-clarify` instead of `/clarify` for faster interactions - Use `/clarity quick-clarify` instead of `/clarity clarify` for faster interactions
- Say "let's focus on just one thing" to narrow scope - Say "let's focus on just one thing" to narrow scope
- Ask to "pause and summarize" at any point - Ask to "pause and summarize" at any point
- It's OK to say "I don't know" - the plugin will offer concrete alternatives - It's OK to say "I don't know" - the plugin will offer concrete alternatives
### If You Have Executive Function Challenges ### If You Have Executive Function Challenges
- Start with `/clarify` even for tasks you think are simple - it helps with planning - Start with `/clarity clarify` even for tasks you think are simple - it helps with planning
- The structured specification can serve as a checklist - The structured specification can serve as a checklist
- Use the scope boundaries to prevent scope creep - Use the scope boundaries to prevent scope creep

View File

@@ -240,7 +240,7 @@ if (( $(echo "$SCORE >= $THRESHOLD" | bc -l) )); then
# Gentle, non-blocking suggestion # Gentle, non-blocking suggestion
echo "$PREFIX Your prompt could benefit from more clarity." echo "$PREFIX Your prompt could benefit from more clarity."
echo "$PREFIX Consider running /clarify to refine your request." echo "$PREFIX Consider running /clarity clarify to refine your request."
echo "$PREFIX (Vagueness score: ${SCORE_PCT}% - this is a suggestion, not a block)" echo "$PREFIX (Vagueness score: ${SCORE_PCT}% - this is a suggestion, not a block)"
# Additional RFC suggestion if feature request detected # Additional RFC suggestion if feature request detected

View File

@@ -40,7 +40,7 @@ add the other parts. Sound good?"
## Choosing Initial Mode ## Choosing Initial Mode
### Use /quick-clarify When ### Use /clarity quick-clarify When
- Request is fairly clear, just one or two ambiguities - Request is fairly clear, just one or two ambiguities
- User is in a hurry - User is in a hurry
@@ -48,7 +48,7 @@ add the other parts. Sound good?"
- Simple feature additions or bug fixes - Simple feature additions or bug fixes
- Confidence is high (>90%) - Confidence is high (>90%)
### Use /clarify When ### Use /clarity clarify When
- Complex multi-step requests - Complex multi-step requests
- Requirements with multiple possible interpretations - Requirements with multiple possible interpretations

View File

@@ -0,0 +1,3 @@
{
"domain": "core"
}

View File

@@ -1,6 +1,6 @@
{ {
"name": "claude-config-maintainer", "name": "claude-config-maintainer",
"version": "1.2.0", "version": "9.0.1",
"description": "Maintains and optimizes CLAUDE.md and settings.local.json configuration files for Claude Code projects", "description": "Maintains and optimizes CLAUDE.md and settings.local.json configuration files for Claude Code projects",
"author": { "author": {
"name": "Leo Miranda", "name": "Leo Miranda",
@@ -20,6 +20,5 @@
], ],
"commands": [ "commands": [
"./commands/" "./commands/"
], ]
"domain": "core"
} }

View File

@@ -96,13 +96,13 @@ Use this mapping to identify active plugins:
| `gitea` | projman | | `gitea` | projman |
| `netbox` | cmdb-assistant | | `netbox` | cmdb-assistant |
Also check for hook-based plugins (project-hygiene uses `PostToolUse` hooks). Also check for hook-based plugins (code-sentinel, git-flow, cmdb-assistant use `PreToolUse` safety hooks; clarity-assist uses `UserPromptSubmit` quality hook).
**Step 2: Check CLAUDE.md for Plugin References** **Step 2: Check CLAUDE.md for Plugin References**
For each detected plugin, search CLAUDE.md for: For each detected plugin, search CLAUDE.md for:
- Plugin name mention (e.g., "projman", "cmdb-assistant") - Plugin name mention (e.g., "projman", "cmdb-assistant")
- Command references (e.g., `/sprint-plan`, `/cmdb-search`) - Command references (e.g., `/sprint plan`, `/cmdb search`)
- MCP tool mentions (e.g., `list_issues`, `dcim_list_devices`) - MCP tool mentions (e.g., `list_issues`, `dcim_list_devices`)
**Step 3: Load Integration Snippets** **Step 3: Load Integration Snippets**
@@ -151,7 +151,7 @@ Evaluate using `skills/settings-optimization.md`:
Before recommending auto-allow patterns, verify active review layers: Before recommending auto-allow patterns, verify active review layers:
1. Read `plugins/*/hooks/hooks.json` for each installed plugin 1. Read `plugins/*/hooks/hooks.json` for each installed plugin
2. Map hook types (PreToolUse, PostToolUse) to tool matchers (Write, Edit, Bash) 2. Map hook types (PreToolUse, UserPromptSubmit) to tool matchers (Write, Edit, MultiEdit, Bash, MCP patterns)
3. Confirm plugins are listed in `.claude-plugin/marketplace.json` 3. Confirm plugins are listed in `.claude-plugin/marketplace.json`
4. Only recommend auto-allow for scopes covered by ≥2 verified review layers 4. Only recommend auto-allow for scopes covered by ≥2 verified review layers

View File

@@ -6,14 +6,14 @@ This project uses the **claude-config-maintainer** plugin to analyze and optimiz
| Command | Description | | Command | Description |
|---------|-------------| |---------|-------------|
| `/config-analyze` | Analyze CLAUDE.md for optimization opportunities with 100-point scoring | | `/claude-config analyze` | Analyze CLAUDE.md for optimization opportunities with 100-point scoring |
| `/config-optimize` | Automatically optimize CLAUDE.md structure and content | | `/claude-config optimize` | Automatically optimize CLAUDE.md structure and content |
| `/config-init` | Initialize a new CLAUDE.md file for a project | | `/claude-config init` | Initialize a new CLAUDE.md file for a project |
| `/config-diff` | Track CLAUDE.md changes over time with behavioral impact analysis | | `/claude-config diff` | Track CLAUDE.md changes over time with behavioral impact analysis |
| `/config-lint` | Lint CLAUDE.md for anti-patterns and best practices (31 rules) | | `/claude-config lint` | Lint CLAUDE.md for anti-patterns and best practices (31 rules) |
| `/config-audit-settings` | Audit settings.local.json permissions with 100-point scoring | | `/claude-config audit-settings` | Audit settings.local.json permissions with 100-point scoring |
| `/config-optimize-settings` | Optimize permission patterns and apply named profiles | | `/claude-config optimize-settings` | Optimize permission patterns and apply named profiles |
| `/config-permissions-map` | Visual map of review layers and permission coverage | | `/claude-config permissions-map` | Visual map of review layers and permission coverage |
### CLAUDE.md Scoring System ### CLAUDE.md Scoring System
@@ -47,10 +47,10 @@ The settings audit uses a 100-point scoring system across four categories:
### Usage Guidelines ### Usage Guidelines
- Run `/config-analyze` periodically to assess CLAUDE.md quality - Run `/claude-config analyze` periodically to assess CLAUDE.md quality
- Run `/config-audit-settings` to check permission efficiency - Run `/claude-config audit-settings` to check permission efficiency
- Target a score of **70+/100** for effective Claude Code operation - Target a score of **70+/100** for effective Claude Code operation
- Address HIGH priority issues first when optimizing - Address HIGH priority issues first when optimizing
- Use `/config-init` when setting up new projects to start with best practices - Use `/claude-config init` when setting up new projects to start with best practices
- Use `/config-permissions-map` to visualize review layer coverage - Use `/claude-config permissions-map` to visualize review layer coverage
- Re-analyze after making changes to verify improvements - Re-analyze after making changes to verify improvements

View File

@@ -1,8 +1,9 @@
--- ---
name: claude-config analyze
description: Analyze CLAUDE.md for optimization opportunities and plugin integration description: Analyze CLAUDE.md for optimization opportunities and plugin integration
--- ---
# Analyze CLAUDE.md # /claude-config analyze
Analyze your CLAUDE.md and provide a scored report with recommendations. Analyze your CLAUDE.md and provide a scored report with recommendations.
@@ -20,7 +21,7 @@ Display: `CONFIG-MAINTAINER - CLAUDE.md Analysis`
## Usage ## Usage
``` ```
/config-analyze /claude-config analyze
``` ```
## Workflow ## Workflow

View File

@@ -1,9 +1,9 @@
--- ---
name: config-audit-settings name: claude-config audit-settings
description: Audit settings.local.json for permission optimization opportunities description: Audit settings.local.json for permission optimization opportunities
--- ---
# /config-audit-settings # /claude-config audit-settings
Audit Claude Code `settings.local.json` permissions with 100-point scoring across redundancy, coverage, safety alignment, and profile fit. Audit Claude Code `settings.local.json` permissions with 100-point scoring across redundancy, coverage, safety alignment, and profile fit.
@@ -24,8 +24,8 @@ Before executing, load:
## Usage ## Usage
``` ```
/config-audit-settings # Full audit with recommendations /claude-config audit-settings # Full audit with recommendations
/config-audit-settings --diagram # Include Mermaid diagram of review layer coverage /claude-config audit-settings --diagram # Include Mermaid diagram of review layer coverage
``` ```
## Workflow ## Workflow
@@ -62,21 +62,20 @@ Using `settings-optimization.md` Section 3, detect:
### Step 4: Detect Active Marketplace Hooks ### Step 4: Detect Active Marketplace Hooks
Read `plugins/*/hooks/hooks.json` files: Read `plugins/*/hooks/hooks.json` files (post-Decision #29 — only PreToolUse safety hooks and UserPromptSubmit quality hooks exist):
```bash ```bash
# Check each plugin's hooks # Check each plugin's hooks (exhaustive post-v8.1.0 inventory)
plugins/code-sentinel/hooks/hooks.json # PreToolUse security plugins/code-sentinel/hooks/hooks.json # PreToolUse: Write|Edit|MultiEdit → security-check.sh
plugins/doc-guardian/hooks/hooks.json # PostToolUse drift detection plugins/git-flow/hooks/hooks.json # PreToolUse: Bash → branch-check.sh, commit-msg-check.sh
plugins/project-hygiene/hooks/hooks.json # PostToolUse cleanup plugins/cmdb-assistant/hooks/hooks.json # PreToolUse: MCP create/update → validate-input.sh
plugins/data-platform/hooks/hooks.json # PostToolUse schema diff plugins/clarity-assist/hooks/hooks.json # UserPromptSubmit → vagueness-check.sh
plugins/contract-validator/hooks/hooks.json # Plugin validation
``` ```
Parse each to identify: Parse each to identify:
- Hook event type (PreToolUse, PostToolUse) - Hook event type (PreToolUse or UserPromptSubmit only — no other types should exist)
- Tool matchers (Write, Edit, MultiEdit, Bash) - Tool matchers (Write, Edit, MultiEdit, Bash, MCP patterns)
- Whether hook is command type (reliable) or prompt type (unreliable) - Whether hook is command type (must be — prompt type is forbidden)
### Step 5: Map Review Layers to Directory Scopes ### Step 5: Map Review Layers to Directory Scopes
@@ -118,9 +117,9 @@ Issues Found:
Active Review Layers Detected: Active Review Layers Detected:
✓ code-sentinel (PreToolUse: Write|Edit|MultiEdit) ✓ code-sentinel (PreToolUse: Write|Edit|MultiEdit)
doc-guardian (PostToolUse: Write|Edit|MultiEdit) git-flow (PreToolUse: Bash — branch naming + commit format)
project-hygiene (PostToolUse: Write|Edit) cmdb-assistant (PreToolUse: MCP create/update)
✗ data-platform schema-diff (not detected) ✓ clarity-assist (UserPromptSubmit: vagueness detection)
Recommendations: Recommendations:
1. [specific action with pattern] 1. [specific action with pattern]
@@ -128,9 +127,9 @@ Recommendations:
... ...
Follow-Up Actions: Follow-Up Actions:
1. Run /config-optimize-settings to apply recommendations 1. Run /claude-config optimize-settings to apply recommendations
2. Run /config-optimize-settings --dry-run to preview first 2. Run /claude-config optimize-settings --dry-run to preview first
3. Run /config-optimize-settings --profile=reviewed to apply profile 3. Run /claude-config optimize-settings --profile=reviewed to apply profile
``` ```
## Diagram Output (--diagram flag) ## Diagram Output (--diagram flag)
@@ -146,7 +145,7 @@ When `--diagram` is specified, generate a Mermaid flowchart showing:
**Color coding:** **Color coding:**
- PreToolUse hooks: Blue - PreToolUse hooks: Blue
- PostToolUse hooks: Green - UserPromptSubmit hooks: Green
- Sprint Approval: Amber - Sprint Approval: Amber
- PR Review: Purple - PR Review: Purple
@@ -161,7 +160,7 @@ flowchart LR
subgraph Review Layers subgraph Review Layers
CS[code-sentinel] CS[code-sentinel]
DG[doc-guardian] GF[git-flow]
PR[pr-review] PR[pr-review]
end end
@@ -172,18 +171,17 @@ flowchart LR
end end
W --> CS W --> CS
W --> DG
E --> CS E --> CS
E --> DG B --> GF
CS --> A CS --> A
DG --> A GF --> A
B --> P B --> P
classDef preHook fill:#e3f2fd classDef preHook fill:#e3f2fd
classDef postHook fill:#e8f5e9 classDef userPrompt fill:#e8f5e9
classDef prReview fill:#f3e5f5 classDef prReview fill:#f3e5f5
class CS preHook class CS preHook
class DG postHook class GF preHook
class PR prReview class PR prReview
``` ```

View File

@@ -1,8 +1,9 @@
--- ---
name: claude-config diff
description: Show diff between current CLAUDE.md and last commit description: Show diff between current CLAUDE.md and last commit
--- ---
# Compare CLAUDE.md Changes # /claude-config diff
Show differences between CLAUDE.md versions to track configuration drift. Show differences between CLAUDE.md versions to track configuration drift.
@@ -18,10 +19,10 @@ Display: `CONFIG-MAINTAINER - CLAUDE.md Diff`
## Usage ## Usage
``` ```
/config-diff # Working vs last commit /claude-config diff # Working vs last commit
/config-diff --commit=abc1234 # Working vs specific commit /claude-config diff --commit=abc1234 # Working vs specific commit
/config-diff --from=v1.0 --to=v2.0 # Compare two commits /claude-config diff --from=v1.0 --to=v2.0 # Compare two commits
/config-diff --section="Critical Rules" # Specific section only /claude-config diff --section="Critical Rules" # Specific section only
``` ```
## Workflow ## Workflow

View File

@@ -1,8 +1,9 @@
--- ---
name: claude-config init
description: Initialize a new CLAUDE.md file for a project description: Initialize a new CLAUDE.md file for a project
--- ---
# Initialize CLAUDE.md # /claude-config init
Create a new CLAUDE.md file tailored to your project. Create a new CLAUDE.md file tailored to your project.
@@ -19,9 +20,9 @@ Display: `CONFIG-MAINTAINER - CLAUDE.md Initialization`
## Usage ## Usage
``` ```
/config-init # Interactive /claude-config init # Interactive
/config-init --minimal # Minimal version /claude-config init --minimal # Minimal version
/config-init --comprehensive # Detailed version /claude-config init --comprehensive # Detailed version
``` ```
## Workflow ## Workflow

View File

@@ -1,8 +1,9 @@
--- ---
name: claude-config lint
description: Lint CLAUDE.md for common anti-patterns and best practices description: Lint CLAUDE.md for common anti-patterns and best practices
--- ---
# Lint CLAUDE.md # /claude-config lint
Check CLAUDE.md against best practices and detect common anti-patterns. Check CLAUDE.md against best practices and detect common anti-patterns.
@@ -18,9 +19,9 @@ Display: `CONFIG-MAINTAINER - CLAUDE.md Lint`
## Usage ## Usage
``` ```
/config-lint # Full lint /claude-config lint # Full lint
/config-lint --fix # Auto-fix issues /claude-config lint --fix # Auto-fix issues
/config-lint --rules=security # Check specific category /claude-config lint --rules=security # Check specific category
``` ```
## Workflow ## Workflow

View File

@@ -1,9 +1,9 @@
--- ---
name: config-optimize-settings name: claude-config optimize-settings
description: Optimize settings.local.json permissions based on audit recommendations description: Optimize settings.local.json permissions based on audit recommendations
--- ---
# /config-optimize-settings # /claude-config optimize-settings
Optimize Claude Code `settings.local.json` permission patterns and apply named profiles. Optimize Claude Code `settings.local.json` permission patterns and apply named profiles.
@@ -25,10 +25,10 @@ Before executing, load:
## Usage ## Usage
``` ```
/config-optimize-settings # Apply audit recommendations /claude-config optimize-settings # Apply audit recommendations
/config-optimize-settings --dry-run # Preview only, no changes /claude-config optimize-settings --dry-run # Preview only, no changes
/config-optimize-settings --profile=reviewed # Apply named profile /claude-config optimize-settings --profile=reviewed # Apply named profile
/config-optimize-settings --consolidate-only # Only merge/dedupe, no new rules /claude-config optimize-settings --consolidate-only # Only merge/dedupe, no new rules
``` ```
## Options ## Options
@@ -44,7 +44,7 @@ Before executing, load:
### Step 1: Run Audit Analysis ### Step 1: Run Audit Analysis
Execute the same analysis as `/config-audit-settings`: Execute the same analysis as `/claude-config audit-settings`:
1. Locate settings file 1. Locate settings file
2. Parse permission arrays 2. Parse permission arrays
3. Detect issues (duplicates, subsets, merge candidates, etc.) 3. Detect issues (duplicates, subsets, merge candidates, etc.)
@@ -171,7 +171,7 @@ Switching to reviewed profile...
Prerequisites verified: Prerequisites verified:
✓ code-sentinel hook active (PreToolUse) ✓ code-sentinel hook active (PreToolUse)
doc-guardian hook active (PostToolUse) git-flow hook active (PreToolUse)
✓ 2+ review layers detected ✓ 2+ review layers detected
This profile: This profile:
@@ -214,7 +214,7 @@ DRY RUN - No changes will be made
[... preview content ...] [... preview content ...]
To apply these changes, run: To apply these changes, run:
/config-optimize-settings /claude-config optimize-settings
``` ```
### Applied Output ### Applied Output

View File

@@ -1,8 +1,9 @@
--- ---
name: claude-config optimize
description: Optimize CLAUDE.md structure and content description: Optimize CLAUDE.md structure and content
--- ---
# Optimize CLAUDE.md # /claude-config optimize
Automatically optimize CLAUDE.md based on best practices. Automatically optimize CLAUDE.md based on best practices.
@@ -20,9 +21,9 @@ Display: `CONFIG-MAINTAINER - CLAUDE.md Optimization`
## Usage ## Usage
``` ```
/config-optimize # Full optimization /claude-config optimize # Full optimization
/config-optimize --condense # Reduce verbosity /claude-config optimize --condense # Reduce verbosity
/config-optimize --dry-run # Preview only /claude-config optimize --dry-run # Preview only
``` ```
## Workflow ## Workflow

View File

@@ -1,9 +1,9 @@
--- ---
name: config-permissions-map name: claude-config permissions-map
description: Generate visual map of review layers and permission coverage description: Generate visual map of review layers and permission coverage
--- ---
# /config-permissions-map # /claude-config permissions-map
Generate a Mermaid diagram showing the relationship between file operations, review layers, and permission status. Generate a Mermaid diagram showing the relationship between file operations, review layers, and permission status.
@@ -26,8 +26,8 @@ Also read: `/mnt/skills/user/mermaid-diagrams/SKILL.md` (for diagram requirement
## Usage ## Usage
``` ```
/config-permissions-map # Generate and display diagram /claude-config permissions-map # Generate and display diagram
/config-permissions-map --save # Save diagram to .mermaid file /claude-config permissions-map --save # Save diagram to .mermaid file
``` ```
## Workflow ## Workflow
@@ -38,15 +38,13 @@ Read all plugin hooks from the marketplace:
``` ```
plugins/code-sentinel/hooks/hooks.json plugins/code-sentinel/hooks/hooks.json
plugins/doc-guardian/hooks/hooks.json plugins/git-flow/hooks/hooks.json
plugins/project-hygiene/hooks/hooks.json
plugins/data-platform/hooks/hooks.json
plugins/contract-validator/hooks/hooks.json
plugins/cmdb-assistant/hooks/hooks.json plugins/cmdb-assistant/hooks/hooks.json
plugins/clarity-assist/hooks/hooks.json
``` ```
For each hook, extract: For each hook, extract:
- Event type (PreToolUse, PostToolUse, SessionStart, etc.) - Event type (PreToolUse, UserPromptSubmit)
- Tool matchers (Write, Edit, MultiEdit, Bash patterns) - Tool matchers (Write, Edit, MultiEdit, Bash patterns)
- Hook command/script - Hook command/script
@@ -54,12 +52,13 @@ For each hook, extract:
Create a mapping of which review layers cover which operations: Create a mapping of which review layers cover which operations:
| Operation | PreToolUse Hooks | PostToolUse Hooks | Other Gates | | Operation | PreToolUse Hooks | Other Gates |
|-----------|------------------|-------------------|-------------| |-----------|------------------|-------------|
| Write | code-sentinel | doc-guardian, project-hygiene | PR review | | Write | code-sentinel | PR review |
| Edit | code-sentinel | doc-guardian, project-hygiene | PR review | | Edit | code-sentinel | PR review |
| MultiEdit | code-sentinel | doc-guardian | PR review | | MultiEdit | code-sentinel | PR review |
| Bash(git *) | git-flow | — | — | | Bash(git *) | git-flow | — |
| MCP(netbox create/update) | cmdb-assistant | — |
### Step 3: Read Current Permissions ### Step 3: Read Current Permissions
@@ -94,13 +93,7 @@ flowchart LR
direction TB direction TB
CS[code-sentinel<br/>Security Scan] CS[code-sentinel<br/>Security Scan]
GF[git-flow<br/>Branch Check] GF[git-flow<br/>Branch Check]
end CA[clarity-assist<br/>Prompt Quality]
subgraph post[PostToolUse Hooks]
direction TB
DG[doc-guardian<br/>Drift Detection]
PH[project-hygiene<br/>Cleanup]
DP[data-platform<br/>Schema Diff]
end end
subgraph perm[Permission Status] subgraph perm[Permission Status]
@@ -111,26 +104,22 @@ flowchart LR
end end
W -->|intercepted| CS W -->|intercepted| CS
W -->|tracked| DG
E -->|intercepted| CS E -->|intercepted| CS
E -->|tracked| DG
BG -->|checked| GF BG -->|checked| GF
CS -->|passed| AA CS -->|passed| AA
DG -->|logged| AA
GF -->|valid| AA GF -->|valid| AA
BO -->|no hook| PR BO -->|no hook| PR
classDef preHook fill:#e3f2fd,stroke:#1976d2 classDef preHook fill:#e3f2fd,stroke:#1976d2
classDef postHook fill:#e8f5e9,stroke:#388e3c classDef quality fill:#fff3e0,stroke:#f57c00
classDef sprint fill:#fff3e0,stroke:#f57c00
classDef prReview fill:#f3e5f5,stroke:#7b1fa2 classDef prReview fill:#f3e5f5,stroke:#7b1fa2
classDef allowed fill:#c8e6c9,stroke:#2e7d32 classDef allowed fill:#c8e6c9,stroke:#2e7d32
classDef prompted fill:#fff9c4,stroke:#f9a825 classDef prompted fill:#fff9c4,stroke:#f9a825
classDef denied fill:#ffcdd2,stroke:#c62828 classDef denied fill:#ffcdd2,stroke:#c62828
class CS,GF preHook class CS,GF preHook
class DG,PH,DP postHook class CA quality
class AA allowed class AA allowed
class PR prompted class PR prompted
class DN denied class DN denied
@@ -195,11 +184,10 @@ Review Layer Status
PreToolUse Hooks (intercept before operation): PreToolUse Hooks (intercept before operation):
✓ code-sentinel — Write, Edit, MultiEdit ✓ code-sentinel — Write, Edit, MultiEdit
✓ git-flow — Bash(git checkout *), Bash(git commit *) ✓ git-flow — Bash(git checkout *), Bash(git commit *)
✓ cmdb-assistant — MCP(netbox create/update)
PostToolUse Hooks (track after operation): UserPromptSubmit Hooks (check prompt quality):
doc-guardian — Write, Edit, MultiEdit clarity-assist — vagueness detection
✓ project-hygiene — Write, Edit
✗ data-platform — not detected
Other Review Gates: Other Review Gates:
✓ Sprint Approval (projman milestone workflow) ✓ Sprint Approval (projman milestone workflow)
@@ -241,7 +229,6 @@ To view:
| Element | Color | Hex | | Element | Color | Hex |
|---------|-------|-----| |---------|-------|-----|
| PreToolUse hooks | Blue | #e3f2fd | | PreToolUse hooks | Blue | #e3f2fd |
| PostToolUse hooks | Green | #e8f5e9 |
| Sprint/Planning gates | Amber | #fff3e0 | | Sprint/Planning gates | Amber | #fff3e0 |
| PR Review | Purple | #f3e5f5 | | PR Review | Purple | #f3e5f5 |
| Auto-allowed | Light green | #c8e6c9 | | Auto-allowed | Light green | #c8e6c9 |

View File

@@ -0,0 +1,37 @@
---
name: claude-config
description: CLAUDE.md and settings optimization — type /claude-config <action> for commands
---
# /claude-config
CLAUDE.md and settings.local.json optimization for Claude Code projects.
When invoked without a sub-command, display available actions and ask which to run.
## Available Commands
| Action | Command to Invoke | Description |
|--------|-------------------|-------------|
| `analyze` | `/claude-config-maintainer:claude-config-analyze` | Analyze CLAUDE.md for optimization opportunities |
| `optimize` | `/claude-config-maintainer:claude-config-optimize` | Optimize CLAUDE.md structure with preview/backup |
| `init` | `/claude-config-maintainer:claude-config-init` | Initialize new CLAUDE.md for a project |
| `diff` | `/claude-config-maintainer:claude-config-diff` | Track CLAUDE.md changes over time with behavioral impact |
| `lint` | `/claude-config-maintainer:claude-config-lint` | Lint CLAUDE.md for anti-patterns and best practices |
| `audit-settings` | `/claude-config-maintainer:claude-config-audit-settings` | Audit settings.local.json permissions (100-point score) |
| `optimize-settings` | `/claude-config-maintainer:claude-config-optimize-settings` | Optimize permissions (profiles, consolidation, dry-run) |
| `permissions-map` | `/claude-config-maintainer:claude-config-permissions-map` | Visual review layer + permission coverage map |
## Routing
If `$ARGUMENTS` is provided (e.g., user typed `/claude-config analyze`):
1. Match the first word of `$ARGUMENTS` against the **Action** column above
2. **Invoke the corresponding command** from the "Command to Invoke" column using the Skill tool
3. Pass any remaining arguments to the invoked command
If no arguments provided:
1. Display the Available Commands table
2. Ask: "Which action would you like to run?"
3. When the user responds, invoke the matching command using the Skill tool
**Note:** Commands can also be invoked directly using their plugin-prefixed names (e.g., `/claude-config-maintainer:claude-config-analyze`)

View File

@@ -1,68 +0,0 @@
#!/bin/bash
# claude-config-maintainer: enforce mandatory behavior rules
# Checks if CLAUDE.md has the rules, adds them if missing
PREFIX="[claude-config-maintainer]"
# Find CLAUDE.md in current directory or parent
CLAUDE_MD=""
if [ -f "./CLAUDE.md" ]; then
CLAUDE_MD="./CLAUDE.md"
elif [ -f "../CLAUDE.md" ]; then
CLAUDE_MD="../CLAUDE.md"
fi
# If no CLAUDE.md found, exit silently
if [ -z "$CLAUDE_MD" ]; then
exit 0
fi
# Check if mandatory rules exist
if grep -q "MANDATORY BEHAVIOR RULES" "$CLAUDE_MD" 2>/dev/null; then
# Rules exist, all good
exit 0
fi
# Rules missing - add them
RULES='## ⛔ MANDATORY BEHAVIOR RULES - READ FIRST
**These rules are NON-NEGOTIABLE. Violating them wastes the user'\''s time and money.**
### 1. WHEN USER ASKS YOU TO CHECK SOMETHING - CHECK EVERYTHING
- Search ALL locations, not just where you think it is
- Check cache directories: `~/.claude/plugins/cache/`
- Check installed: `~/.claude/plugins/marketplaces/`
- Check source directories
- **NEVER say "no" or "that'\''s not the issue" without exhaustive verification**
### 2. WHEN USER SAYS SOMETHING IS WRONG - BELIEVE THEM
- The user knows their system better than you
- Investigate thoroughly before disagreeing
- **Your confidence is often wrong. User'\''s instincts are often right.**
### 3. NEVER SAY "DONE" WITHOUT VERIFICATION
- Run the actual command/script to verify
- Show the output to the user
- **"Done" means VERIFIED WORKING, not "I made changes"**
### 4. SHOW EXACTLY WHAT USER ASKS FOR
- If user asks for messages, show the MESSAGES
- If user asks for code, show the CODE
- **Do not interpret or summarize unless asked**
**FAILURE TO FOLLOW THESE RULES = WASTED USER TIME = UNACCEPTABLE**
---
'
# Create temp file with rules + existing content
{
head -1 "$CLAUDE_MD"
echo ""
echo "$RULES"
tail -n +2 "$CLAUDE_MD"
} > "${CLAUDE_MD}.tmp"
mv "${CLAUDE_MD}.tmp" "$CLAUDE_MD"
echo "$PREFIX Added mandatory behavior rules to CLAUDE.md"

View File

@@ -1,15 +0,0 @@
{
"hooks": {
"SessionStart": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/enforce-rules.sh"
}
]
}
]
}
}

View File

@@ -6,7 +6,7 @@ This skill defines how to analyze and present CLAUDE.md differences.
| Mode | Command | Description | | Mode | Command | Description |
|------|---------|-------------| |------|---------|-------------|
| Working vs HEAD | `/config-diff` | Uncommitted changes | | Working vs HEAD | `/claude-config diff` | Uncommitted changes |
| Working vs Commit | `--commit=REF` | Changes since specific point | | Working vs Commit | `--commit=REF` | Changes since specific point |
| Commit to Commit | `--from=X --to=Y` | Historical comparison | | Commit to Commit | `--from=X --to=Y` | Historical comparison |
| Branch Comparison | `--branch=NAME` | Cross-branch differences | | Branch Comparison | `--branch=NAME` | Cross-branch differences |

View File

@@ -119,14 +119,14 @@ This is the key section. Map upstream review processes to directory scopes:
| Directory Scope | Active Review Layers | Auto-Allow Recommendation | | Directory Scope | Active Review Layers | Auto-Allow Recommendation |
|----------------|---------------------|---------------------------| |----------------|---------------------|---------------------------|
| `plugins/*/commands/*.md` | Sprint approval, PR review, doc-guardian PostToolUse | `Write(plugins/*/commands/**)`3 layers cover this | | `plugins/*/commands/*.md` | Sprint approval, PR review | `Write(plugins/*/commands/**)`2 layers cover this |
| `plugins/*/skills/*.md` | Sprint approval, PR review | `Write(plugins/*/skills/**)` — 2 layers | | `plugins/*/skills/*.md` | Sprint approval, PR review | `Write(plugins/*/skills/**)` — 2 layers |
| `plugins/*/agents/*.md` | Sprint approval, PR review, contract-validator | `Write(plugins/*/agents/**)`3 layers | | `plugins/*/agents/*.md` | Sprint approval, PR review | `Write(plugins/*/agents/**)`2 layers |
| `mcp-servers/*/mcp_server/*.py` | Code-sentinel PreToolUse, sprint approval, PR review | `Write(mcp-servers/**)` + `Edit(mcp-servers/**)` — sentinel catches secrets | | `mcp-servers/*/mcp_server/*.py` | Code-sentinel PreToolUse, sprint approval, PR review | `Write(mcp-servers/**)` + `Edit(mcp-servers/**)` — sentinel catches secrets |
| `docs/*.md` | Doc-guardian PostToolUse, PR review | `Write(docs/**)` + `Edit(docs/**)` | | `docs/*.md` | PR review | `Write(docs/**)` + `Edit(docs/**)` — with caution flag |
| `.claude-plugin/*.json` | validate-marketplace.sh, PR review | `Write(.claude-plugin/**)` | | `.claude-plugin/*.json` | validate-marketplace.sh, PR review | `Write(.claude-plugin/**)` |
| `scripts/*.sh` | Code-sentinel, PR review | `Write(scripts/**)` — with caution flag | | `scripts/*.sh` | Code-sentinel, PR review | `Write(scripts/**)` — with caution flag |
| `CLAUDE.md`, `CHANGELOG.md`, `README.md` | Doc-guardian, PR review | `Write(CLAUDE.md)`, `Write(CHANGELOG.md)`, `Write(README.md)` | | `CLAUDE.md`, `CHANGELOG.md`, `README.md` | PR review | `Write(CLAUDE.md)`, `Write(CHANGELOG.md)`, `Write(README.md)` |
### Critical Rule: Hook Verification ### Critical Rule: Hook Verification
@@ -134,10 +134,11 @@ This is the key section. Map upstream review processes to directory scopes:
Read the relevant `plugins/*/hooks/hooks.json` file: Read the relevant `plugins/*/hooks/hooks.json` file:
- If code-sentinel's hook is missing or disabled, do NOT recommend auto-allowing `mcp-servers/**` writes - If code-sentinel's hook is missing or disabled, do NOT recommend auto-allowing `mcp-servers/**` writes
- If doc-guardian's hook is missing, do NOT recommend auto-allowing `docs/**` without caution - If git-flow's hook is missing, do NOT recommend auto-allowing `Bash(git *)` operations
- If cmdb-assistant's hook is missing, do NOT recommend auto-allowing MCP netbox create/update operations
- Count the number of verified review layers before making recommendations - Count the number of verified review layers before making recommendations
**Minimum threshold:** Recommend auto-allow only for scopes covered by ≥2 verified review layers. **Minimum threshold:** Only recommend auto-allow for scopes with ≥2 verified review layers.
--- ---
@@ -333,10 +334,9 @@ To verify which review layers are active, read these files:
| File | Hook Type | Tool Matcher | Purpose | | File | Hook Type | Tool Matcher | Purpose |
|------|-----------|--------------|---------| |------|-----------|--------------|---------|
| `plugins/code-sentinel/hooks/hooks.json` | PreToolUse | Write\|Edit\|MultiEdit | Blocks hardcoded secrets | | `plugins/code-sentinel/hooks/hooks.json` | PreToolUse | Write\|Edit\|MultiEdit | Blocks hardcoded secrets |
| `plugins/doc-guardian/hooks/hooks.json` | PostToolUse | Write\|Edit\|MultiEdit | Tracks documentation drift | | `plugins/git-flow/hooks/hooks.json` | PreToolUse | Bash | Branch naming + commit format |
| `plugins/project-hygiene/hooks/hooks.json` | PostToolUse | Write\|Edit | Cleanup tracking | | `plugins/cmdb-assistant/hooks/hooks.json` | PreToolUse | MCP create/update | NetBox input validation |
| `plugins/data-platform/hooks/hooks.json` | PostToolUse | Edit\|Write | Schema diff detection | | `plugins/clarity-assist/hooks/hooks.json` | UserPromptSubmit | (all prompts) | Vagueness detection |
| `plugins/cmdb-assistant/hooks/hooks.json` | PreToolUse | (if exists) | Input validation |
### Verification Process ### Verification Process
@@ -351,15 +351,20 @@ To verify which review layers are active, read these files:
```json ```json
{ {
"hooks": {
"PreToolUse": [
{
"matcher": "Write|Edit|MultiEdit",
"hooks": [ "hooks": [
{ {
"event": "PreToolUse",
"type": "command", "type": "command",
"command": "./hooks/security-check.sh", "command": "./hooks/security-check.sh"
"tools": ["Write", "Edit", "MultiEdit"]
} }
] ]
} }
]
}
}
``` ```
### Review Layer Count ### Review Layer Count
@@ -370,8 +375,8 @@ Count verified review layers for each scope:
|-------|-------------| |-------|-------------|
| Sprint approval | Check if projman plugin is installed (milestone workflow) | | Sprint approval | Check if projman plugin is installed (milestone workflow) |
| PR review | Check if pr-review plugin is installed | | PR review | Check if pr-review plugin is installed |
| code-sentinel PreToolUse | hooks.json exists with PreToolUse on Write/Edit | | code-sentinel PreToolUse | hooks.json exists with PreToolUse on Write/Edit/MultiEdit |
| doc-guardian PostToolUse | hooks.json exists with PostToolUse on Write/Edit | | git-flow PreToolUse | hooks.json exists with PreToolUse on Bash |
| contract-validator | Plugin installed + hooks present | | cmdb-assistant PreToolUse | hooks.json exists with PreToolUse on MCP create/update |
**Recommendation threshold:** Only recommend auto-allow for scopes with ≥2 verified layers. **Recommendation threshold:** Only recommend auto-allow for scopes with ≥2 verified layers.

View File

@@ -12,56 +12,56 @@ This skill defines the standard visual header for claude-config-maintainer comma
## Command-Specific Headers ## Command-Specific Headers
### /config-analyze ### /claude-config analyze
``` ```
+-----------------------------------------------------------------+ +-----------------------------------------------------------------+
| CONFIG-MAINTAINER - CLAUDE.md Analysis | | CONFIG-MAINTAINER - CLAUDE.md Analysis |
+-----------------------------------------------------------------+ +-----------------------------------------------------------------+
``` ```
### /config-optimize ### /claude-config optimize
``` ```
+-----------------------------------------------------------------+ +-----------------------------------------------------------------+
| CONFIG-MAINTAINER - CLAUDE.md Optimization | | CONFIG-MAINTAINER - CLAUDE.md Optimization |
+-----------------------------------------------------------------+ +-----------------------------------------------------------------+
``` ```
### /config-lint ### /claude-config lint
``` ```
+-----------------------------------------------------------------+ +-----------------------------------------------------------------+
| CONFIG-MAINTAINER - CLAUDE.md Lint | | CONFIG-MAINTAINER - CLAUDE.md Lint |
+-----------------------------------------------------------------+ +-----------------------------------------------------------------+
``` ```
### /config-diff ### /claude-config diff
``` ```
+-----------------------------------------------------------------+ +-----------------------------------------------------------------+
| CONFIG-MAINTAINER - CLAUDE.md Diff | | CONFIG-MAINTAINER - CLAUDE.md Diff |
+-----------------------------------------------------------------+ +-----------------------------------------------------------------+
``` ```
### /config-init ### /claude-config init
``` ```
+-----------------------------------------------------------------+ +-----------------------------------------------------------------+
| CONFIG-MAINTAINER - CLAUDE.md Initialization | | CONFIG-MAINTAINER - CLAUDE.md Initialization |
+-----------------------------------------------------------------+ +-----------------------------------------------------------------+
``` ```
### /config-audit-settings ### /claude-config audit-settings
``` ```
+-----------------------------------------------------------------+ +-----------------------------------------------------------------+
| CONFIG-MAINTAINER - Settings Audit | | CONFIG-MAINTAINER - Settings Audit |
+-----------------------------------------------------------------+ +-----------------------------------------------------------------+
``` ```
### /config-optimize-settings ### /claude-config optimize-settings
``` ```
+-----------------------------------------------------------------+ +-----------------------------------------------------------------+
| CONFIG-MAINTAINER - Settings Optimization | | CONFIG-MAINTAINER - Settings Optimization |
+-----------------------------------------------------------------+ +-----------------------------------------------------------------+
``` ```
### /config-permissions-map ### /claude-config permissions-map
``` ```
+-----------------------------------------------------------------+ +-----------------------------------------------------------------+
| CONFIG-MAINTAINER - Permissions Map | | CONFIG-MAINTAINER - Permissions Map |

View File

@@ -1 +1,6 @@
{"mcp_servers": ["netbox"]} {
"mcp_servers": [
"netbox"
],
"domain": "ops"
}

View File

@@ -1,6 +1,6 @@
{ {
"name": "cmdb-assistant", "name": "cmdb-assistant",
"version": "1.2.0", "version": "9.0.1",
"description": "NetBox CMDB integration with data quality validation - query, create, update, and manage network devices, IP addresses, sites, and more with best practices enforcement", "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",
@@ -21,6 +21,5 @@
], ],
"commands": [ "commands": [
"./commands/" "./commands/"
], ]
"domain": "ops"
} }

View File

@@ -97,13 +97,13 @@ ipam_list_prefixes prefix=<proposed-prefix>
| Command | Purpose | | Command | Purpose |
|---------|---------| |---------|---------|
| `/cmdb-search <query>` | Search across all CMDB objects | | `/cmdb search <query>` | Search across all CMDB objects |
| `/cmdb-device <action>` | Device CRUD operations | | `/cmdb device <action>` | Device CRUD operations |
| `/cmdb-ip <action>` | IP address and prefix management | | `/cmdb ip <action>` | IP address and prefix management |
| `/cmdb-site <action>` | Site and location management | | `/cmdb site <action>` | Site and location management |
| `/cmdb-audit [scope]` | Data quality analysis | | `/cmdb audit [scope]` | Data quality analysis |
| `/cmdb-register` | Register current machine | | `/cmdb register` | Register current machine |
| `/cmdb-sync` | Sync machine state with NetBox | | `/cmdb sync` | Sync machine state with NetBox |
| `/cmdb-topology <view>` | Generate infrastructure diagrams | | `/cmdb topology <view>` | Generate infrastructure diagrams |
| `/change-audit [filters]` | Audit NetBox changes | | `/cmdb change-audit [filters]` | Audit NetBox changes |
| `/ip-conflicts [scope]` | Detect IP conflicts | | `/cmdb ip-conflicts [scope]` | Detect IP conflicts |

View File

@@ -6,10 +6,10 @@ This project uses the **cmdb-assistant** plugin for NetBox CMDB integration to m
| Command | Description | | Command | Description |
|---------|-------------| |---------|-------------|
| `/cmdb-search` | Search across all NetBox objects | | `/cmdb search` | Search across all NetBox objects |
| `/cmdb-device` | Manage devices (create, update, list) | | `/cmdb device` | Manage devices (create, update, list) |
| `/cmdb-ip` | Manage IP addresses and prefixes | | `/cmdb ip` | Manage IP addresses and prefixes |
| `/cmdb-site` | Manage sites and locations | | `/cmdb site` | Manage sites and locations |
### MCP Tools Available ### MCP Tools Available

View File

@@ -1,8 +1,9 @@
--- ---
name: cmdb audit
description: Audit NetBox data quality and identify consistency issues description: Audit NetBox data quality and identify consistency issues
--- ---
# CMDB Data Quality Audit # /cmdb audit
Analyze NetBox data for quality issues and best practice violations. Analyze NetBox data for quality issues and best practice violations.
@@ -16,7 +17,7 @@ Analyze NetBox data for quality issues and best practice violations.
## Usage ## Usage
``` ```
/cmdb-audit [scope] /cmdb audit [scope]
``` ```
**Scopes:** **Scopes:**
@@ -49,9 +50,9 @@ Execute `skills/audit-workflow.md` which covers:
## Examples ## Examples
- `/cmdb-audit` - Full audit - `/cmdb audit` - Full audit
- `/cmdb-audit vms` - VM-specific checks - `/cmdb audit vms` - VM-specific checks
- `/cmdb-audit naming` - Naming conventions - `/cmdb audit naming` - Naming conventions
## User Request ## User Request

View File

@@ -1,8 +1,9 @@
--- ---
name: cmdb change-audit
description: Audit NetBox changes with filtering by date, user, or object type description: Audit NetBox changes with filtering by date, user, or object type
--- ---
# CMDB Change Audit # /cmdb change-audit
Query and analyze the NetBox audit log for change tracking and compliance. Query and analyze the NetBox audit log for change tracking and compliance.
@@ -15,7 +16,7 @@ Query and analyze the NetBox audit log for change tracking and compliance.
## Usage ## Usage
``` ```
/change-audit [filters] /cmdb change-audit [filters]
``` ```
**Filters:** **Filters:**
@@ -46,11 +47,11 @@ If user asks for "security audit" or "compliance report":
## Examples ## Examples
- `/change-audit` - Recent changes (last 24 hours) - `/cmdb change-audit` - Recent changes (last 24 hours)
- `/change-audit last 7 days` - Past week - `/cmdb change-audit last 7 days` - Past week
- `/change-audit by admin` - All changes by admin - `/cmdb change-audit by admin` - All changes by admin
- `/change-audit type dcim.device` - Device changes only - `/cmdb change-audit type dcim.device` - Device changes only
- `/change-audit action delete` - All deletions - `/cmdb change-audit action delete` - All deletions
## User Request ## User Request

View File

@@ -1,4 +1,8 @@
# CMDB Device Management ---
name: cmdb device
---
# /cmdb device
Manage network devices in NetBox. Manage network devices in NetBox.
@@ -10,7 +14,7 @@ Manage network devices in NetBox.
## Usage ## Usage
``` ```
/cmdb-device <action> [options] /cmdb device <action> [options]
``` ```
## Instructions ## Instructions
@@ -45,10 +49,10 @@ After creating a device, offer to:
## Examples ## Examples
- `/cmdb-device list` - `/cmdb device list`
- `/cmdb-device show core-router-01` - `/cmdb device show core-router-01`
- `/cmdb-device create web-server-03` - `/cmdb device create web-server-03`
- `/cmdb-device at headquarters` - `/cmdb device at headquarters`
## User Request ## User Request

View File

@@ -1,8 +1,9 @@
--- ---
name: cmdb ip-conflicts
description: Detect IP address conflicts and overlapping prefixes in NetBox description: Detect IP address conflicts and overlapping prefixes in NetBox
--- ---
# CMDB IP Conflict Detection # /cmdb ip-conflicts
Scan NetBox IPAM data to identify IP address conflicts and overlapping prefixes. Scan NetBox IPAM data to identify IP address conflicts and overlapping prefixes.
@@ -15,7 +16,7 @@ Scan NetBox IPAM data to identify IP address conflicts and overlapping prefixes.
## Usage ## Usage
``` ```
/ip-conflicts [scope] /cmdb ip-conflicts [scope]
``` ```
**Scopes:** **Scopes:**
@@ -49,9 +50,9 @@ Execute conflict detection from `skills/ip-management.md`:
## Examples ## Examples
- `/ip-conflicts` - Full scan - `/cmdb ip-conflicts` - Full scan
- `/ip-conflicts addresses` - Duplicate IPs only - `/cmdb ip-conflicts addresses` - Duplicate IPs only
- `/ip-conflicts vrf Production` - Scan specific VRF - `/cmdb ip-conflicts vrf Production` - Scan specific VRF
## User Request ## User Request

View File

@@ -1,4 +1,8 @@
# CMDB IP Management ---
name: cmdb ip
---
# /cmdb ip
Manage IP addresses and prefixes in NetBox. Manage IP addresses and prefixes in NetBox.
@@ -11,7 +15,7 @@ Manage IP addresses and prefixes in NetBox.
## Usage ## Usage
``` ```
/cmdb-ip <action> [options] /cmdb ip <action> [options]
``` ```
## Instructions ## Instructions
@@ -42,10 +46,10 @@ Execute operations from `skills/ip-management.md`.
## Examples ## Examples
- `/cmdb-ip prefixes` - `/cmdb ip prefixes`
- `/cmdb-ip available in 10.0.1.0/24` - `/cmdb ip available in 10.0.1.0/24`
- `/cmdb-ip allocate from 10.0.1.0/24` - `/cmdb ip allocate from 10.0.1.0/24`
- `/cmdb-ip assign 10.0.1.50/24 to web-server-01 eth0` - `/cmdb ip assign 10.0.1.50/24 to web-server-01 eth0`
## User Request ## User Request

View File

@@ -1,8 +1,9 @@
--- ---
name: cmdb register
description: Register the current machine into NetBox with all running applications description: Register the current machine into NetBox with all running applications
--- ---
# CMDB Machine Registration # /cmdb register
Register the current machine into NetBox, including hardware info, network interfaces, and running applications. Register the current machine into NetBox, including hardware info, network interfaces, and running applications.
@@ -17,7 +18,7 @@ Register the current machine into NetBox, including hardware info, network inter
## Usage ## Usage
``` ```
/cmdb-register [--site <site-name>] [--tenant <tenant-name>] [--role <role-name>] /cmdb register [--site <site-name>] [--tenant <tenant-name>] [--role <role-name>]
``` ```
**Options:** **Options:**
@@ -41,7 +42,7 @@ Execute `skills/device-registration.md` which covers:
| Error | Action | | Error | Action |
|-------|--------| |-------|--------|
| Device already exists | Suggest `/cmdb-sync` or ask to proceed | | Device already exists | Suggest `/cmdb sync` or ask to proceed |
| Site not found | List available sites, offer to create new | | Site not found | List available sites, offer to create new |
| Docker not available | Skip container registration, note in summary | | Docker not available | Skip container registration, note in summary |
| Permission denied | Note which operations failed, suggest fixes | | Permission denied | Note which operations failed, suggest fixes |

View File

@@ -1,4 +1,8 @@
# CMDB Search ---
name: cmdb search
---
# /cmdb search
## Visual Output ## Visual Output
@@ -17,7 +21,7 @@ Search NetBox for devices, IPs, sites, or any CMDB object.
## Usage ## Usage
``` ```
/cmdb-search <query> /cmdb search <query>
``` ```
## Instructions ## Instructions
@@ -37,9 +41,9 @@ For broad searches, query multiple endpoints and consolidate results.
## Examples ## Examples
- `/cmdb-search router` - Find all devices with "router" in the name - `/cmdb search router` - Find all devices with "router" in the name
- `/cmdb-search 10.0.1.0/24` - Find prefix and IPs within it - `/cmdb search 10.0.1.0/24` - Find prefix and IPs within it
- `/cmdb-search datacenter` - Find sites matching "datacenter" - `/cmdb search datacenter` - Find sites matching "datacenter"
## User Query ## User Query

View File

@@ -1,8 +1,9 @@
--- ---
name: cmdb setup
description: Interactive setup wizard for cmdb-assistant plugin description: Interactive setup wizard for cmdb-assistant plugin
--- ---
# CMDB Assistant Setup Wizard # /cmdb setup
Configure the cmdb-assistant plugin with NetBox integration. Configure the cmdb-assistant plugin with NetBox integration.
@@ -18,7 +19,7 @@ Configure the cmdb-assistant plugin with NetBox integration.
## Usage ## Usage
``` ```
/cmdb-setup /cmdb setup
``` ```
## Instructions ## Instructions
@@ -63,10 +64,10 @@ System Config: ~/.config/claude/netbox.env
Restart your Claude Code session for MCP tools. Restart your Claude Code session for MCP tools.
After restart, try: After restart, try:
- /cmdb-device <hostname> - /cmdb device <hostname>
- /cmdb-ip <address> - /cmdb ip <address>
- /cmdb-site <name> - /cmdb site <name>
- /cmdb-search <query> - /cmdb search <query>
``` ```
## User Request ## User Request

View File

@@ -1,4 +1,8 @@
# CMDB Site Management ---
name: cmdb site
---
# /cmdb site
Manage sites and locations in NetBox. Manage sites and locations in NetBox.
@@ -10,7 +14,7 @@ Manage sites and locations in NetBox.
## Usage ## Usage
``` ```
/cmdb-site <action> [options] /cmdb site <action> [options]
``` ```
## Instructions ## Instructions
@@ -40,10 +44,10 @@ Execute `skills/visual-header.md` with context "Site Management".
## Examples ## Examples
- `/cmdb-site list` - `/cmdb site list`
- `/cmdb-site show headquarters` - `/cmdb site show headquarters`
- `/cmdb-site create branch-office-nyc` - `/cmdb site create branch-office-nyc`
- `/cmdb-site racks at headquarters` - `/cmdb site racks at headquarters`
## User Request ## User Request

View File

@@ -1,8 +1,9 @@
--- ---
name: cmdb sync
description: Synchronize current machine state with existing NetBox record description: Synchronize current machine state with existing NetBox record
--- ---
# CMDB Machine Sync # /cmdb sync
Update an existing NetBox device record with the current machine state. Update an existing NetBox device record with the current machine state.
@@ -16,7 +17,7 @@ Update an existing NetBox device record with the current machine state.
## Usage ## Usage
``` ```
/cmdb-sync [--full] [--dry-run] /cmdb sync [--full] [--dry-run]
``` ```
**Options:** **Options:**
@@ -48,7 +49,7 @@ Execute `skills/sync-workflow.md` which covers:
| Error | Action | | Error | Action |
|-------|--------| |-------|--------|
| Device not found | Suggest `/cmdb-register` | | Device not found | Suggest `/cmdb register` |
| Permission denied | Note which failed, continue others | | Permission denied | Note which failed, continue others |
| Cluster not found | Offer to create or skip container sync | | Cluster not found | Offer to create or skip container sync |

View File

@@ -1,8 +1,9 @@
--- ---
name: cmdb topology
description: Generate infrastructure topology diagrams from NetBox data description: Generate infrastructure topology diagrams from NetBox data
--- ---
# CMDB Topology Visualization # /cmdb topology
Generate Mermaid diagrams showing infrastructure topology from NetBox. Generate Mermaid diagrams showing infrastructure topology from NetBox.
@@ -15,7 +16,7 @@ Generate Mermaid diagrams showing infrastructure topology from NetBox.
## Usage ## Usage
``` ```
/cmdb-topology <view> [scope] /cmdb topology <view> [scope]
``` ```
**Views:** **Views:**
@@ -43,11 +44,11 @@ Always provide:
## Examples ## Examples
- `/cmdb-topology rack server-rack-01` - Rack elevation - `/cmdb topology rack server-rack-01` - Rack elevation
- `/cmdb-topology network` - All network connections - `/cmdb topology network` - All network connections
- `/cmdb-topology network Home` - Network for Home site - `/cmdb topology network Home` - Network for Home site
- `/cmdb-topology site Headquarters` - Site overview - `/cmdb topology site Headquarters` - Site overview
- `/cmdb-topology full` - Full infrastructure - `/cmdb topology full` - Full infrastructure
## User Request ## User Request

View File

@@ -0,0 +1,39 @@
---
name: cmdb
description: NetBox CMDB infrastructure management — type /cmdb <action> for commands
---
# /cmdb
NetBox CMDB integration for infrastructure management.
When invoked without a sub-command, display available actions and ask which to run.
## Available Commands
| Action | Command to Invoke | Description |
|--------|-------------------|-------------|
| `search` | `/cmdb-assistant:cmdb-search` | Search NetBox for devices, IPs, sites |
| `device` | `/cmdb-assistant:cmdb-device` | Manage network devices (create, view, update, delete) |
| `ip` | `/cmdb-assistant:cmdb-ip` | Manage IP addresses and prefixes |
| `site` | `/cmdb-assistant:cmdb-site` | Manage sites, locations, racks, and regions |
| `audit` | `/cmdb-assistant:cmdb-audit` | Data quality analysis (VMs, devices, naming, roles) |
| `register` | `/cmdb-assistant:cmdb-register` | Register current machine into NetBox |
| `sync` | `/cmdb-assistant:cmdb-sync` | Sync machine state with NetBox (detect drift) |
| `topology` | `/cmdb-assistant:cmdb-topology` | Infrastructure topology diagrams |
| `change-audit` | `/cmdb-assistant:cmdb-change-audit` | NetBox audit trail queries with filtering |
| `ip-conflicts` | `/cmdb-assistant:cmdb-ip-conflicts` | Detect IP conflicts and overlapping prefixes |
| `setup` | `/cmdb-assistant:cmdb-setup` | Setup wizard for NetBox MCP server |
## Routing
If `$ARGUMENTS` is provided (e.g., user typed `/cmdb search`):
1. Match the first word of `$ARGUMENTS` against the **Action** column above
2. **Invoke the corresponding command** from the "Command to Invoke" column using the Skill tool
3. Pass any remaining arguments to the invoked command
If no arguments provided:
1. Display the Available Commands table
2. Ask: "Which action would you like to run?"
3. When the user responds, invoke the matching command using the Skill tool
**Note:** Commands can also be invoked directly using their plugin-prefixed names (e.g., `/cmdb-assistant:cmdb-search`)

View File

@@ -1,16 +1,5 @@
{ {
"hooks": { "hooks": {
"SessionStart": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/startup-check.sh"
}
]
}
],
"PreToolUse": [ "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", "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",

View File

@@ -1,83 +0,0 @@
#!/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
# Helper function to make authenticated API calls
# Token passed via curl config to avoid exposure in process listings
netbox_curl() {
local url="$1"
curl -s -K - <<EOF 2>/dev/null
-H "Authorization: Token ${NETBOX_API_TOKEN}"
-H "Accept: application/json"
url = "${url}"
EOF
}
# Quick API connectivity test (5s timeout)
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" -m 5 -K - <<EOF 2>/dev/null || echo "000"
-H "Authorization: Token ${NETBOX_API_TOKEN}"
-H "Accept: application/json"
url = "${NETBOX_API_URL}/"
EOF
)
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 -K - <<EOF 2>/dev/null || echo '{"count":0}'
-H "Authorization: Token ${NETBOX_API_TOKEN}"
-H "Accept: application/json"
url = "${NETBOX_API_URL}/virtualization/virtual-machines/?site__isnull=true&limit=1"
EOF
)
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 -K - <<EOF 2>/dev/null || echo '{"count":0}'
-H "Authorization: Token ${NETBOX_API_TOKEN}"
-H "Accept: application/json"
url = "${NETBOX_API_URL}/dcim/devices/?platform__isnull=true&limit=1"
EOF
)
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

View File

@@ -147,8 +147,8 @@ dcim_update_device id=X platform=Y
### Next Steps ### Next Steps
- Run `/cmdb-register` to properly register new machines - Run `/cmdb register` to properly register new machines
- Use `/cmdb-sync` to update existing registrations - Use `/cmdb sync` to update existing registrations
- Consider bulk updates via NetBox web UI for >10 items - Consider bulk updates via NetBox web UI for >10 items
``` ```

View File

@@ -25,7 +25,7 @@ Use commands from `system-discovery` skill to gather:
``` ```
dcim_list_devices name=<hostname> dcim_list_devices name=<hostname>
``` ```
If exists, suggest `/cmdb-sync` instead. If exists, suggest `/cmdb sync` instead.
2. **Verify/Create site:** 2. **Verify/Create site:**
``` ```
@@ -131,7 +131,7 @@ Add journal entry:
extras_create_journal_entry extras_create_journal_entry
assigned_object_type="dcim.device" assigned_object_type="dcim.device"
assigned_object_id=<device_id> 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" comments="Device registered via /cmdb register command\n\nDiscovered:\n- X network interfaces\n- Y IP addresses\n- Z Docker containers"
``` ```
## Summary Report Template ## Summary Report Template
@@ -162,8 +162,8 @@ extras_create_journal_entry
| media_jellyfin | Media Server | 2.0 | 2048MB | Active | | media_jellyfin | Media Server | 2.0 | 2048MB | Active |
### Next Steps ### Next Steps
- Run `/cmdb-sync` periodically to keep data current - Run `/cmdb sync` periodically to keep data current
- Run `/cmdb-audit` to check data quality - Run `/cmdb audit` to check data quality
- Add tags for classification - Add tags for classification
``` ```
@@ -171,7 +171,7 @@ extras_create_journal_entry
| Error | Action | | Error | Action |
|-------|--------| |-------|--------|
| Device already exists | Suggest `/cmdb-sync` or ask to proceed | | Device already exists | Suggest `/cmdb sync` or ask to proceed |
| Site not found | List available sites, offer to create new | | Site not found | List available sites, offer to create new |
| Docker not available | Skip container registration, note in summary | | Docker not available | Skip container registration, note in summary |
| Permission denied | Note which operations failed, suggest fixes | | Permission denied | Note which operations failed, suggest fixes |

Some files were not shown because too many files have changed in this diff Show More