66 Commits

Author SHA1 Message Date
0d120bd041 Merge pull request 'feat: add plugin name prefixes to hooks and improve git-flow sync' (#105) from feat/hook-improvements-and-sync-prune into development 2026-01-22 21:14:32 +00:00
508832dae1 feat: add plugin name prefixes to hooks and improve git-flow sync
- Add [plugin-name] prefix to all hook messages for better identification
- Make doc-guardian hook notification-only (non-blocking)
- Add stale branch detection to /commit-sync with git fetch --prune
- Enhance /branch-cleanup to handle stale branches separately

Closes improvements for hook UX and git workflow

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-22 16:13:17 -05:00
0b23a02886 Merge pull request 'fix: add curl fallback to /debug-report when MCP tools unavailable' (#103) from fix/issue-100-debug-report-fallback into development
Reviewed-on: #103
2026-01-22 20:01:32 +00:00
71987ee537 fix: switch to development branch before adding issue comments
The MCP server's branch-aware security blocks write operations on
protected branches (main, fix/*, etc). After pushing a feature branch
and creating a PR, we must switch back to development before adding
comments to issues via MCP tools.
2026-01-22 14:28:01 -05:00
b7829dca05 fix: add curl fallback to /debug-report when MCP tools unavailable
When MCP tools are not available in a session (the very scenario
/debug-report is designed to diagnose), the command now falls back to:

1. Check for Gitea credentials at ~/.config/claude/gitea.env
2. Use curl + jq to create the issue via Gitea REST API
3. If no credentials, save report to local file for manual submission

Security measures:
- Uses mktemp -m 600 for restrictive file permissions
- Uses jq --rawfile for safe JSON construction (no command substitution)
- Proper cleanup of temporary files

Fixes #100

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-22 14:19:05 -05:00
9b0e9a69b1 Merge pull request 'development' (#102) from development into main
Reviewed-on: #102
2026-01-22 18:36:36 +00:00
ad0e14d07f Merge pull request 'feat: add debugging infrastructure and CLAUDE.md optimization' (#101) from feat/debugging-infrastructure into development
Reviewed-on: #101
2026-01-22 18:36:15 +00:00
7fd5fffedf feat: add debugging infrastructure and CLAUDE.md optimization
- Add docs/DEBUGGING-CHECKLIST.md with systematic troubleshooting guide
- Enhance SessionStart hooks to detect missing MCP venvs and warn users
- Add Installation Paths and Debugging sections to CLAUDE.md
- Add Plugin Commands by Category table to Quick Start
- Condense Versioning section for better readability
- Add scripts/check-venv.sh for programmatic venv checking
- Update docs/CANONICAL-PATHS.md with new files

Addresses issues #97, #98, #99

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-22 13:33:32 -05:00
620173eef6 Merge pull request 'Release: suggest_labels fix' (#96) from development into main 2026-01-22 15:54:28 +00:00
0fe4f62a30 Merge pull request 'fix: expose repo parameter in suggest_labels MCP tool' (#95) from fix/suggest-labels-repo-parameter into development 2026-01-22 15:54:26 +00:00
533810f018 fix: expose repo parameter in suggest_labels MCP tool
The suggest_labels tool accepted a repo parameter in the implementation
but didn't expose it in the MCP tool schema, causing it to always rely
on auto-detection which failed in some contexts.

Fixes #94

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-22 10:54:07 -05:00
2ee23a39d8 Merge pull request 'Release: Critical MCP setup documentation' (#93) from development into main 2026-01-22 15:35:36 +00:00
894c85bd54 Merge pull request 'docs: add critical setup instructions for installed marketplace' (#92) from docs/mcp-setup-critical-fix into development 2026-01-22 15:35:23 +00:00
01809a7367 docs: add critical setup instructions for installed marketplace
MCP servers fail when venvs don't exist in ~/.claude/plugins/marketplaces/.
Claude Code doesn't run setup.sh when installing marketplaces, so users
must run it manually.

Added:
- Critical warning section at top of UPDATING.md
- Step to run setup in installed location after updates
- Troubleshooting for "X MCP servers failed" error

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-22 10:35:07 -05:00
a20f1bfdf8 Merge pull request 'Release: pr-review MCP config fix' (#91) from development into main 2026-01-22 15:22:30 +00:00
7879e07815 Merge pull request 'fix: add missing PYTHONPATH env to pr-review MCP config' (#90) from fix/pr-review-mcp-pythonpath into development 2026-01-22 15:21:11 +00:00
eced0fbd07 fix: add missing PYTHONPATH env to pr-review MCP config
Aligns pr-review .mcp.json with projman by adding PYTHONPATH environment
variable. This inconsistency may have caused MCP server failures when
both plugins are loaded.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-22 10:20:27 -05:00
aa6d7f5866 Merge pull request 'Release: SessionStart hook fix' (#89) from development into main 2026-01-22 15:08:29 +00:00
3e5197779d Merge pull request 'fix: correct SessionStart hook structure in projman and pr-review' (#88) from fix/session-start-hook-structure into development 2026-01-22 15:08:11 +00:00
9206931a3c fix: correct SessionStart hook structure in projman and pr-review
Remove incorrect nested matcher/hooks structure from SessionStart hooks.
SessionStart events don't use matchers - that format is only for tool-based
hooks like PreToolUse/PostToolUse.

Fixes recurring "SessionStart:startup hook error" on Claude Code startup.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-22 10:07:12 -05:00
ff3be54f1c Merge pull request 'Release: Add debug commands documentation' (#87) from development into main 2026-01-22 13:11:35 +00:00
1b0f5f4973 Merge pull request 'docs: add debug commands to projman documentation' (#86) from docs/add-debug-commands-documentation into development 2026-01-22 13:11:08 +00:00
8ed0d8f207 docs: add debug commands to projman documentation
- Add /debug-report and /debug-review to projman README
- Add debug commands to COMMANDS-CHEATSHEET.md
- Remove obsolete doc-guardian Stop hook references from cheatsheet
- Update Architecture section with debug command files

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-22 08:10:33 -05:00
007b55916c Merge pull request 'Release: doc-guardian README fix' (#85) from development into main 2026-01-22 12:58:20 +00:00
eeef35aa61 Merge pull request 'fix: correct doc-guardian README to match actual implementation' (#84) from fix/doc-guardian-readme-accuracy into development 2026-01-22 12:44:50 +00:00
be2d989899 fix: correct doc-guardian README to match actual implementation
- Remove false claim about Stop hook (was removed in d2ad90d)
- Fix Solution section to accurately describe prompt-based behavior
- Remove misleading "queue" language since there's no persistent queue

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-22 07:42:51 -05:00
306143882a Merge pull request 'Release v3.1.0: Debug workflow commands + hook fix' (#83) from development into main
Reviewed-on: #83
2026-01-21 23:00:12 +00:00
0c07820b5a Merge pull request 'fix: remove broken Stop hook from doc-guardian' (#82) from fix/remove-broken-stop-hook into development 2026-01-21 22:56:50 +00:00
d2ad90d5bb fix: remove broken Stop hook from doc-guardian
The Stop hook referenced a non-existent "internal queue" for tracking
documentation drift. Each hook runs in isolation with no way to pass
data between invocations, so the queue concept couldn't work.

The hook was causing errors on every session end:
"Stop hook error: Prompt hook condition was not met..."

Changes:
- Removed the Stop hook entirely
- Updated PostToolUse hook to report drift immediately when found
  (instead of referencing non-existent queue)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-21 17:55:53 -05:00
642dca7062 Merge pull request 'Release v3.1.0: Debug workflow commands' (#81) from development into main
Reviewed-on: #81
2026-01-21 22:47:22 +00:00
faafced061 Merge pull request 'feat: add debug workflow commands and bump to v3.1.0' (#80) from feat/debug-workflow-v3.1.0 into development 2026-01-21 22:46:13 +00:00
c3df0f95e6 feat: add debug workflow commands and bump to v3.1.0
New commands for structured debugging workflow:

/debug-report (for test projects):
- Runs 5 diagnostic MCP tool tests
- Captures full project context (git remote, cwd, branch)
- Generates structured issue with hypothesis
- Creates issue in marketplace repo automatically

/debug-review (for marketplace repo):
- Lists open diagnostic issues for triage
- Maps errors to relevant code files
- MANDATORY: Reads files before proposing fixes
- Three human approval gates
- Creates feature branch, commits, PR with linking

Also includes:
- Dynamic label format detection in suggest_labels
- Rewritten labels-sync.md with explicit execution steps
- Version bump to 3.1.0

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-21 17:44:54 -05:00
f714957d83 Merge pull request 'Merge development into main: labels-sync repo detection fix' (#79) from development into main
Reviewed-on: #79
2026-01-21 22:33:11 +00:00
40af243229 Merge pull request 'fix: rewrite labels-sync.md with explicit repo detection steps' (#78) from fix/labels-sync-repo-detection into development 2026-01-21 22:26:12 +00:00
69b71fc7cf fix: rewrite labels-sync.md with explicit repo detection steps
ROOT CAUSE: The MCP server runs with cwd set to the plugin directory,
not the user's project. It cannot auto-detect the repository.

SOLUTION: The command documentation now explicitly instructs Claude to:
1. Run `git remote get-url origin` via Bash first
2. Parse the URL to extract owner/repo
3. Pass repo parameter to ALL MCP tool calls

Also removed the "Label Reference" section that was causing Claude
to ask about creating a local reference file.

Key changes:
- Added "CRITICAL: Execution Steps" section with numbered steps
- Added "DO NOT" section to prevent common mistakes
- Removed confusing reference file documentation
- Made all MCP tool calls show required repo parameter

Fixes #77

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-21 17:16:48 -05:00
5ad207520a Merge pull request 'Merge development into main: dynamic label format detection' (#76) from development into main
Reviewed-on: #76
2026-01-21 22:06:52 +00:00
78d77c1e0a Merge pull request 'fix: make suggest_labels detect actual label format from repository' (#75) from fix/dynamic-label-format into development 2026-01-21 22:04:55 +00:00
5cf43d5de2 fix: make suggest_labels detect actual label format from repository
The suggest_labels function now dynamically detects the label naming convention
used in the repository (slash format like Type/Bug or colon-space format like
Type: Bug) instead of hardcoding slash format.

Changes:
- Added _build_label_lookup() to parse and normalize label formats
- Added _find_label() to find actual labels from the lookup
- Updated suggest_labels() to accept optional repo parameter
- Labels are fetched first, then suggestions match actual names
- Supports Efforts/Effort normalization (handles singular/plural)

Fixes issue #73 sub-issue 3: label format mismatch

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-21 16:58:44 -05:00
51ef10633b Merge pull request 'fix: remove conflicting reference file instruction from labels-sync' (#74) from fix/labels-sync-no-reference-file into development 2026-01-21 21:50:14 +00:00
83094598c5 fix: remove conflicting reference file instruction from labels-sync
The command description still said "updates the local reference file"
which caused the agent to prompt about creating a reference file.

Updated to clarify: no local files are created or modified.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-21 16:47:57 -05:00
5da29c8e35 Merge pull request 'Release: fix project directory detection for MCP server' (#72) from development into main
Reviewed-on: #72
2026-01-21 21:41:55 +00:00
4f3560d121 Merge pull request 'fix: detect project directory correctly for git remote parsing' (#71) from fix/project-directory-detection into development 2026-01-21 21:38:24 +00:00
d5e521a759 fix: detect project directory correctly for git remote parsing
The MCP server runs with cwd set to the plugin directory, not the
user's project directory. This caused git remote auto-detection to
fail because it was looking at the wrong directory.

Changes:
- Added _find_project_directory() method with multiple strategies:
  1. CLAUDE_PROJECT_DIR environment variable
  2. PWD environment variable (if it has .git or .env)
  3. Current working directory (if it has .git or .env)
- Updated _detect_repo_from_git() to accept project_dir parameter
- Added 3 new tests for project directory detection

Fixes #70

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-21 16:25:14 -05:00
b2c51251f3 Merge pull request 'Release: labels-sync documentation improvements' (#69) from development into main
Reviewed-on: #69
2026-01-21 21:16:28 +00:00
71efa1aafa Merge pull request 'docs: clarify labels-sync command behavior' (#68) from docs/labels-sync-clarification into development 2026-01-21 21:14:14 +00:00
aa3ff016e2 docs: clarify labels-sync command behavior and remove misleading prompts
Updates labels-sync.md to:
- Remove [Y/n] prompts that suggested manual confirmation
- Clarify that execution is autonomous
- Document that labels are fetched dynamically (not from reference file)
- Update examples to show actual behavior
- Improve troubleshooting for common issues
- Add user-owned repo handling documentation

Related to #67

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-21 16:02:05 -05:00
4557d2ce40 Merge pull request 'Release: repo auto-detection and org validation fixes' (#66) from development into main
Reviewed-on: #66
2026-01-21 20:15:02 +00:00
d282a65fc6 Merge pull request 'fix: add repo auto-detection and improve org validation' (#65) from fix/repo-auto-detection-and-org-validation into development 2026-01-21 20:12:30 +00:00
ad56700059 fix: add repo auto-detection and improve org validation
1. Repo Auto-Detection (config.py):
   - Added _detect_repo_from_git() to parse git remote URL
   - Supports SSH, SSH short, HTTPS, HTTP URL formats
   - Falls back to git remote when GITEA_REPO not set

2. Organization Validation (gitea_client.py):
   - Changed is_org_repo() to use /orgs/{owner} endpoint
   - Added _is_organization() method for reliable org detection
   - Fixes issue where owner.type was null in Gitea API

3. Tests:
   - Added 6 tests for git URL parsing
   - Added 3 tests for org detection

Fixes #64

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-21 15:10:53 -05:00
df2f5ebb47 Merge pull request 'Release: fix user-owned repo labels' (#63) from development into main
Reviewed-on: #63
2026-01-21 19:48:24 +00:00
feb86b059f Merge pull request 'fix: handle user-owned repos in get_labels (skip org labels)' (#62) from fix/user-owned-repo-labels into development
Merge pull request #62 from fix/user-owned-repo-labels
2026-01-21 19:47:10 +00:00
c23e84f965 fix: handle user-owned repos in get_labels (skip org labels)
The get_labels tool was failing with 404 for user-owned repositories
because it always queried /api/v1/orgs/{owner}/labels, which only
works for organizations.

Changes:
- labels.py: Check is_org_repo() before fetching org labels
- gitea_client.py: Same fix in _resolve_label_ids()
- test_labels.py: Added tests for both org and user-owned repos

Fixes #61

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-21 14:40:30 -05:00
195ca5c10c Merge pull request 'Release v3.0.1' (#60) from development into main
Reviewed-on: #60
2026-01-21 17:07:45 +00:00
53f1b9662f Merge pull request 'chore: bump version to v3.0.1' (#59) from chore/version-bump-3.0.1 into development 2026-01-21 17:01:34 +00:00
eeffb9e853 chore: bump version to v3.0.1
Updates version references in:
- README.md title
- CHANGELOG.md (new entry with all changes since v3.0.0)
- marketplace.json metadata
- CLAUDE.md project overview

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-21 12:00:39 -05:00
6c142a9710 Merge pull request 'development' (#57) from development into main
Reviewed-on: #57
2026-01-21 16:56:12 +00:00
39105688a5 Merge pull request 'development' (#54) from development into main
Reviewed-on: #54
2026-01-21 16:24:26 +00:00
b5144de0cf Merge pull request 'development' (#52) from development into main
Reviewed-on: #52
2026-01-21 15:15:35 +00:00
a70df64cae Merge pull request 'development' (#50) from development into main
Reviewed-on: #50
2026-01-21 02:50:35 +00:00
394c91f8cf Merge pull request 'development' (#48) from development into main
Reviewed-on: #48
2026-01-21 02:13:48 +00:00
374912b463 Merge pull request 'development' (#46) from development into main
Reviewed-on: #46
2026-01-21 01:58:17 +00:00
50ebe83c0a Merge pull request 'development' (#44) from development into main
Reviewed-on: #44
2026-01-21 01:21:53 +00:00
4ede59e89a Merge pull request 'development' (#42) from development into main
Reviewed-on: #42
2026-01-20 22:31:17 +00:00
aa7bb8f1a4 Merge pull request 'development' (#40) from development into main
Reviewed-on: personal-projects/support-claude-mktplace#40
2026-01-20 22:14:18 +00:00
09d82b310e Merge pull request 'development' (#38) from development into main
Reviewed-on: personal-projects/support-claude-mktplace#38
2026-01-20 22:04:00 +00:00
1c694b6469 Merge pull request 'development' (#36) from development into main
Reviewed-on: personal-projects/support-claude-mktplace#36
2026-01-20 17:49:26 +00:00
29 changed files with 2139 additions and 398 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": "3.0.0" "version": "3.1.0"
}, },
"plugins": [ "plugins": [
{ {
"name": "projman", "name": "projman",
"version": "3.0.0", "version": "3.1.0",
"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": {

View File

@@ -4,6 +4,88 @@ All notable changes to the Leo Claude Marketplace will be documented in this fil
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [3.1.1] - 2026-01-22
### Added
- **git-flow:** `/commit-sync` now prunes stale remote-tracking branches with `git fetch --prune`
- **git-flow:** `/commit-sync` detects and reports local branches with deleted upstreams
- **git-flow:** `/branch-cleanup` now handles stale branches (upstream gone) separately from merged branches
- **git-flow:** New `GIT_CLEANUP_STALE` environment variable for stale branch cleanup control
### Changed
- **All hooks:** Added `[plugin-name]` prefix to all hook messages for better identification
- `[projman]`, `[pr-review]`, `[code-sentinel]`, `[doc-guardian]` prefixes
- **doc-guardian:** Hook now notification-only (no file reads or blocking operations)
- Suggests running `/doc-sync` instead of performing inline checks
- Significantly reduces workflow interruption
### Fixed
- doc-guardian hook no longer stalls workflow with deep file analysis
---
## [3.1.0] - 2026-01-21
### Added
#### Debug Workflow Commands (projman)
- **`/debug-report`** - Run diagnostics in test projects, create structured issues in marketplace
- Runs 5 diagnostic MCP tool tests with explicit repo parameter
- Captures full project context (git remote, cwd, branch)
- Generates structured issue with hypothesis and investigation steps
- Creates issue in configured marketplace repository automatically
- **`/debug-review`** - Investigate diagnostic issues with human approval gates
- Lists open diagnostic issues for triage
- Maps errors to relevant code files using error-to-file mapping
- MANDATORY: Reads relevant files before proposing any fix
- Three approval gates: investigation summary, fix approach, PR creation
- Creates feature branch, commits, and PR with proper linking
#### MCP Server Improvements
- Dynamic label format detection in `suggest_labels`
- Supports slash format (`Type/Bug`) and colon-space format (`Type: Bug`)
- Fetches actual labels from repo and matches suggestions to real format
- Handles Effort/Efforts singular/plural normalization
### Changed
- **`/labels-sync`** completely rewritten with explicit execution steps
- Step 1 now explicitly requires running `git remote get-url origin` via Bash
- All MCP tool calls show required `repo` parameter
- Added "DO NOT" section preventing common mistakes
- Removed confusing "Label Reference" section that caused file creation prompts
### Fixed
- MCP tools no longer fail with "Use 'owner/repo' format" error
- Root cause: MCP server is sandboxed and cannot auto-detect project directory
- Solution: Command documentation now instructs Claude to detect repo via Bash first
---
## [3.0.1] - 2026-01-21
### Added
- `/project-init` command for quick project setup when system is already configured
- `/project-sync` command to sync .env with git remote after repository move/rename
- SessionStart hooks for automatic mismatch detection between git remote and .env
- Interactive setup wizard (`/initial-setup`) redesigned to use Claude tools instead of bash script
### Changed
- `GITEA_ORG` moved from system-level to project-level configuration (different projects may belong to different organizations)
- Environment variables renamed to match MCP server expectations:
- `GITEA_URL``GITEA_API_URL` (must include `/api/v1`)
- `GITEA_TOKEN``GITEA_API_TOKEN`
- `NETBOX_URL``NETBOX_API_URL` (must include `/api`)
- `NETBOX_TOKEN``NETBOX_API_TOKEN`
- Setup commands now validate repository via Gitea API before saving configuration
- README.md simplified to show only wizard setup path (manual setup moved to CONFIGURATION.md)
### Fixed
- API URL paths in curl commands (removed redundant `/api/v1` since it's now in the URL variable)
- Documentation now correctly references environment variable names
---
## [3.0.0] - 2026-01-20 ## [3.0.0] - 2026-01-20
### Added ### Added

View File

@@ -5,7 +5,7 @@ This file provides guidance to Claude Code when working with code in this reposi
## Project Overview ## Project Overview
**Repository:** leo-claude-mktplace **Repository:** leo-claude-mktplace
**Version:** 3.0.0 **Version:** 3.0.1
**Status:** Production Ready **Status:** Production Ready
A plugin marketplace for Claude Code containing: A plugin marketplace for Claude Code containing:
@@ -28,19 +28,23 @@ A plugin marketplace for Claude Code containing:
# Validate marketplace compliance # Validate marketplace compliance
./scripts/validate-marketplace.sh ./scripts/validate-marketplace.sh
# Setup commands (in a target project with plugin installed) # After updates
/initial-setup # First time: full setup wizard ./scripts/post-update.sh # Rebuild venvs, verify symlinks
/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
``` ```
### Plugin Commands by Category
| Category | Commands |
|----------|----------|
| **Setup** | `/initial-setup`, `/project-init`, `/project-sync` |
| **Sprint** | `/sprint-plan`, `/sprint-start`, `/sprint-status`, `/sprint-close` |
| **Quality** | `/review`, `/test-check`, `/test-gen` |
| **PR Review** | `/pr-review:initial-setup`, `/pr-review:project-init` |
| **Docs** | `/doc-audit`, `/doc-sync` |
| **Security** | `/security-scan`, `/refactor`, `/refactor-dry` |
| **Config** | `/config-analyze`, `/config-optimize` |
| **Debug** | `/debug-report`, `/debug-review` |
## Repository Structure ## Repository Structure
``` ```
@@ -208,42 +212,47 @@ Stored in Gitea Wiki under `lessons-learned/sprints/`.
| Document | Purpose | | Document | Purpose |
|----------|---------| |----------|---------|
| `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 with workflow examples | | `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/UPDATING.md` | Update guide for the marketplace | | `docs/UPDATING.md` | Update guide for the marketplace |
| `plugins/projman/CONFIGURATION.md` | Quick reference (links to central) | | `plugins/projman/CONFIGURATION.md` | Projman quick reference (links to central) |
| `plugins/projman/README.md` | Projman full documentation | | `plugins/projman/README.md` | Projman full documentation |
## Versioning and Changelog Rules ## Installation Paths
### Version Display Understanding where files live is critical for debugging:
**The marketplace version is displayed ONLY in the main `README.md` title.**
- Format: `# Leo Claude Marketplace - vX.Y.Z` | Context | Path | Purpose |
- Do NOT add version numbers to individual plugin documentation titles |---------|------|---------|
- Do NOT add version numbers to configuration guides | **Source** | `~/claude-plugins-work/` | Development - edit here |
- Do NOT add version numbers to CLAUDE.md or other docs | **Installed** | `~/.claude/plugins/marketplaces/leo-claude-mktplace/` | Runtime - Claude uses this |
| **Cache** | `~/.claude/` | Plugin metadata and settings |
### Changelog Maintenance (MANDATORY) **Key insight:** Edits to source require reinstall/update to take effect at runtime.
**`CHANGELOG.md` is the authoritative source for version history.**
When releasing a new version: ## Debugging & Troubleshooting
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 See `docs/DEBUGGING-CHECKLIST.md` for systematic troubleshooting.
- Follow [Semantic Versioning](https://semver.org/): MAJOR.MINOR.PATCH
- MAJOR: Breaking changes **Common Issues:**
- MINOR: New features, backward compatible | Symptom | Likely Cause | Fix |
- PATCH: Bug fixes, minor improvements |---------|--------------|-----|
| "X MCP servers failed" | Missing venv in installed path | `cd ~/.claude/plugins/marketplaces/leo-claude-mktplace && ./scripts/setup.sh` |
| MCP tools not available | Symlink broken or venv missing | Run `/debug-report` to diagnose |
| Changes not taking effect | Editing source, not installed | Reinstall plugin or edit installed path |
**Debug Commands:**
- `/debug-report` - Run full diagnostics, create issue if needed
- `/debug-review` - Investigate and propose fixes
## Versioning Rules
- Version displayed ONLY in main `README.md` title: `# Leo Claude Marketplace - vX.Y.Z`
- `CHANGELOG.md` is authoritative for version history
- Follow [SemVer](https://semver.org/): MAJOR.MINOR.PATCH
- On release: Update README title → CHANGELOG → marketplace.json → plugin.json files
--- ---
**Last Updated:** 2026-01-20 **Last Updated:** 2026-01-22

View File

@@ -1,4 +1,4 @@
# Leo Claude Marketplace - v3.0.0 # Leo Claude Marketplace - v3.1.1
A collection of Claude Code plugins for project management, infrastructure automation, and development workflows. A collection of Claude Code plugins for project management, infrastructure automation, and development workflows.

View File

@@ -18,6 +18,7 @@ leo-claude-mktplace/
│ ├── architecture/ # Draw.io diagrams and specs │ ├── architecture/ # Draw.io diagrams and specs
│ ├── CANONICAL-PATHS.md # This file - single source of truth │ ├── CANONICAL-PATHS.md # This file - single source of truth
│ ├── CONFIGURATION.md # Centralized configuration guide │ ├── CONFIGURATION.md # Centralized configuration guide
│ ├── DEBUGGING-CHECKLIST.md # Systematic troubleshooting guide
│ ├── UPDATING.md # Update guide │ ├── UPDATING.md # Update guide
│ └── workflows/ # Workflow documentation │ └── workflows/ # Workflow documentation
├── hooks/ # Shared hooks (if any) ├── hooks/ # Shared hooks (if any)
@@ -103,6 +104,10 @@ leo-claude-mktplace/
│ ├── skills/ │ ├── 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)
│ ├── post-update.sh # Post-update (rebuild venvs, verify symlinks)
│ ├── check-venv.sh # Check if venvs exist (for hooks)
│ └── validate-marketplace.sh # Marketplace compliance validation
├── CLAUDE.md ├── CLAUDE.md
├── README.md ├── README.md
├── LICENSE ├── LICENSE
@@ -156,6 +161,7 @@ The symlink target is relative: `../../../mcp-servers/{server}`
| 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` |
--- ---

View File

@@ -20,6 +20,8 @@ Quick reference for all commands in the Leo Claude Marketplace.
| **projman** | `/project-sync` | | X | Sync config with git remote after repo move/rename | | **projman** | `/project-sync` | | X | Sync config with git remote after repo move/rename |
| **projman** | *SessionStart hook* | X | | Detects git remote vs .env mismatch, warns to run /project-sync | | **projman** | *SessionStart hook* | X | | Detects git remote vs .env mismatch, warns to run /project-sync |
| **projman** | `/test-gen` | | X | Generate comprehensive tests for specified code | | **projman** | `/test-gen` | | X | Generate comprehensive tests for specified code |
| **projman** | `/debug-report` | | X | Run diagnostics and create structured issue in marketplace |
| **projman** | `/debug-review` | | X | Investigate diagnostic issues and propose fixes with approval gates |
| **git-flow** | `/commit` | | X | Create commit with auto-generated conventional message | | **git-flow** | `/commit` | | X | Create commit with auto-generated conventional message |
| **git-flow** | `/commit-push` | | X | Commit and push to remote in one operation | | **git-flow** | `/commit-push` | | X | Commit and push to remote in one operation |
| **git-flow** | `/commit-merge` | | X | Commit current changes, then merge into target branch | | **git-flow** | `/commit-merge` | | X | Commit current changes, then merge into target branch |
@@ -40,7 +42,6 @@ Quick reference for all commands in the Leo Claude Marketplace.
| **doc-guardian** | `/doc-audit` | | X | Full documentation audit - scans for doc drift | | **doc-guardian** | `/doc-audit` | | X | Full documentation audit - scans for doc drift |
| **doc-guardian** | `/doc-sync` | | X | Synchronize pending documentation updates | | **doc-guardian** | `/doc-sync` | | X | Synchronize pending documentation updates |
| **doc-guardian** | *PostToolUse hook* | X | | Silently detects doc drift on Write/Edit | | **doc-guardian** | *PostToolUse hook* | X | | Silently detects doc drift on Write/Edit |
| **doc-guardian** | *Stop hook* | X | | Offers to sync docs at session end |
| **code-sentinel** | `/security-scan` | | X | Full security audit (SQL injection, XSS, secrets, etc.) | | **code-sentinel** | `/security-scan` | | X | Full security audit (SQL injection, XSS, secrets, etc.) |
| **code-sentinel** | `/refactor` | | X | Apply refactoring patterns to improve code | | **code-sentinel** | `/refactor` | | X | Apply refactoring patterns to improve code |
| **code-sentinel** | `/refactor-dry` | | X | Preview refactoring without applying changes | | **code-sentinel** | `/refactor-dry` | | X | Preview refactoring without applying changes |
@@ -78,7 +79,6 @@ Quick reference for all commands in the Leo Claude Marketplace.
| **projman** | SessionStart | Checks git remote vs .env; warns if mismatch detected | | **projman** | SessionStart | Checks git remote vs .env; warns if mismatch detected |
| **pr-review** | SessionStart | Checks git remote vs .env; warns if mismatch detected | | **pr-review** | SessionStart | Checks git remote vs .env; warns if mismatch detected |
| **doc-guardian** | PostToolUse (Write/Edit) | Silently tracks documentation drift | | **doc-guardian** | PostToolUse (Write/Edit) | Silently tracks documentation drift |
| **doc-guardian** | Stop | Prompts to sync if drift detected |
| **code-sentinel** | PreToolUse (Write/Edit) | Scans for security issues; blocks critical vulnerabilities | | **code-sentinel** | PreToolUse (Write/Edit) | Scans for security issues; blocks critical vulnerabilities |
| **project-hygiene** | PostToolUse (Write/Edit) | Cleans temp files, warns about misplaced files | | **project-hygiene** | PostToolUse (Write/Edit) | Cleans temp files, warns about misplaced files |
@@ -214,4 +214,4 @@ Ensure credentials are configured in `~/.config/claude/gitea.env` or `~/.config/
--- ---
*Last Updated: 2026-01-21* *Last Updated: 2026-01-22*

213
docs/DEBUGGING-CHECKLIST.md Normal file
View File

@@ -0,0 +1,213 @@
# Debugging Checklist for Marketplace Troubleshooting
**Purpose:** Systematic approach to diagnose and fix plugin loading issues.
Last Updated: 2026-01-22
---
## Step 1: Identify the Loading Path
Claude Code loads plugins from different locations depending on context:
| Location | Path | When Used |
|----------|------|-----------|
| **Source** | `~/claude-plugins-work/` | When developing in this directory |
| **Installed** | `~/.claude/plugins/marketplaces/leo-claude-mktplace/` | After marketplace install |
| **Cache** | `~/.claude/` | Plugin metadata, settings |
**Determine which path Claude is using:**
```bash
# Check if installed marketplace exists
ls -la ~/.claude/plugins/marketplaces/leo-claude-mktplace/
# Check Claude's current plugin loading
cat ~/.claude/settings.local.json | grep -A5 "mcpServers"
```
**Key insight:** If you're editing source but Claude uses installed, your changes won't take effect.
---
## Step 2: Verify Files Exist at Runtime Location
Check the files Claude will actually load:
```bash
# For installed marketplace
RUNTIME=~/.claude/plugins/marketplaces/leo-claude-mktplace
# Check MCP server exists
ls -la $RUNTIME/mcp-servers/gitea/
ls -la $RUNTIME/mcp-servers/netbox/
# Check plugin manifests
ls -la $RUNTIME/plugins/projman/.claude-plugin/plugin.json
ls -la $RUNTIME/plugins/pr-review/.claude-plugin/plugin.json
# Check .mcp.json files
cat $RUNTIME/plugins/projman/.mcp.json
```
---
## Step 3: Verify Virtual Environments Exist
**This is the most common failure point after installation.**
MCP servers require Python venvs to exist at the INSTALLED location:
```bash
RUNTIME=~/.claude/plugins/marketplaces/leo-claude-mktplace
# Check venvs exist
ls -la $RUNTIME/mcp-servers/gitea/.venv/bin/python
ls -la $RUNTIME/mcp-servers/netbox/.venv/bin/python
# If missing, create them:
cd $RUNTIME && ./scripts/setup.sh
```
**Common error:** "X MCP servers failed to start" = venvs don't exist in installed path.
---
## Step 4: Verify Symlink Resolution
Plugins use symlinks to shared MCP servers. Verify they resolve correctly:
```bash
RUNTIME=~/.claude/plugins/marketplaces/leo-claude-mktplace
# Check symlinks exist and resolve
readlink -f $RUNTIME/plugins/projman/mcp-servers/gitea
readlink -f $RUNTIME/plugins/pr-review/mcp-servers/gitea
readlink -f $RUNTIME/plugins/cmdb-assistant/mcp-servers/netbox
# Should resolve to:
# $RUNTIME/mcp-servers/gitea
# $RUNTIME/mcp-servers/netbox
```
**If broken:** Symlinks are relative. If directory structure differs, they'll break.
---
## Step 5: Test MCP Server Startup
Manually test if the MCP server can start:
```bash
RUNTIME=~/.claude/plugins/marketplaces/leo-claude-mktplace
# Test Gitea MCP
cd $RUNTIME/mcp-servers/gitea
PYTHONPATH=. .venv/bin/python -c "from mcp_server.server import main; print('OK')"
# Test NetBox MCP
cd $RUNTIME/mcp-servers/netbox
PYTHONPATH=. .venv/bin/python -c "from mcp_server.server import main; print('OK')"
```
**If import fails:** Check requirements.txt installed, check Python version compatibility.
---
## Step 6: Verify Configuration Files
Check environment variables are set:
```bash
# System-level credentials (should exist)
cat ~/.config/claude/gitea.env
# Should contain: GITEA_API_URL, GITEA_API_TOKEN
cat ~/.config/claude/netbox.env
# Should contain: NETBOX_API_URL, NETBOX_API_TOKEN
# Project-level config (in target project)
cat /path/to/project/.env
# Should contain: GITEA_ORG, GITEA_REPO
```
---
## Step 7: Verify Hooks Configuration
Check hooks are valid:
```bash
RUNTIME=~/.claude/plugins/marketplaces/leo-claude-mktplace
# List all hooks.json files
find $RUNTIME/plugins -name "hooks.json" -exec echo "=== {} ===" \; -exec cat {} \;
# Verify hook events are valid
# Valid: PreToolUse, PostToolUse, UserPromptSubmit, SessionStart, SessionEnd,
# Notification, Stop, SubagentStop, PreCompact
# INVALID: task-completed, file-changed, git-commit-msg-needed
```
---
## Quick Diagnostic Commands
Run these to quickly identify issues:
```bash
RUNTIME=~/.claude/plugins/marketplaces/leo-claude-mktplace
echo "=== Installation Status ==="
[ -d "$RUNTIME" ] && echo "Installed: YES" || echo "Installed: NO"
echo -e "\n=== Virtual Environments ==="
[ -f "$RUNTIME/mcp-servers/gitea/.venv/bin/python" ] && echo "Gitea venv: OK" || echo "Gitea venv: MISSING"
[ -f "$RUNTIME/mcp-servers/netbox/.venv/bin/python" ] && echo "NetBox venv: OK" || echo "NetBox venv: MISSING"
echo -e "\n=== Symlinks ==="
[ -L "$RUNTIME/plugins/projman/mcp-servers/gitea" ] && echo "projman->gitea: OK" || echo "projman->gitea: MISSING"
[ -L "$RUNTIME/plugins/pr-review/mcp-servers/gitea" ] && echo "pr-review->gitea: OK" || echo "pr-review->gitea: MISSING"
[ -L "$RUNTIME/plugins/cmdb-assistant/mcp-servers/netbox" ] && echo "cmdb-assistant->netbox: OK" || echo "cmdb-assistant->netbox: MISSING"
echo -e "\n=== Config Files ==="
[ -f ~/.config/claude/gitea.env ] && echo "gitea.env: OK" || echo "gitea.env: MISSING"
[ -f ~/.config/claude/netbox.env ] && echo "netbox.env: OK" || echo "netbox.env: MISSING"
```
---
## Common Issues and Fixes
| Issue | Symptom | Fix |
|-------|---------|-----|
| Missing venvs | "X MCP servers failed" | `cd ~/.claude/plugins/marketplaces/leo-claude-mktplace && ./scripts/setup.sh` |
| Broken symlinks | MCP tools not available | Reinstall marketplace or manually recreate symlinks |
| Wrong path edits | Changes don't take effect | Edit installed path or reinstall after source changes |
| Missing credentials | MCP connection errors | Create `~/.config/claude/gitea.env` with API credentials |
| Invalid hook events | Hooks don't fire | Use only valid event names (see Step 7) |
---
## After Fixing Issues
1. **Restart Claude Code** - Plugins are loaded at startup
2. **Verify fix works** - Run a simple command that uses the MCP
3. **Document the issue** - If it's a new failure mode, add to this checklist
---
## Automated Diagnostics
Use these commands for automated checking:
- `/debug-report` - Run full diagnostics, create issue if problems found
- `/debug-review` - Investigate existing diagnostic issues and propose fixes
---
## Related Documentation
- `CLAUDE.md` - Installation Paths and Troubleshooting sections
- `docs/CONFIGURATION.md` - Setup and configuration guide
- `docs/UPDATING.md` - Update procedures

View File

@@ -4,15 +4,32 @@ This guide covers how to update your local installation when new versions are re
--- ---
## Quick Update ## ⚠️ CRITICAL: Run Setup in Installed Location
When Claude Code installs a marketplace, it copies files to `~/.claude/plugins/marketplaces/` but **does NOT create Python virtual environments**. You must run setup manually after installation or update.
**After installing or updating the marketplace:**
```bash ```bash
# 1. Pull latest changes cd ~/.claude/plugins/marketplaces/leo-claude-mktplace && ./scripts/setup.sh
```
This creates the required `.venv` directories for MCP servers. Without this step, **all MCP servers will fail to start**.
---
## Quick Update (Source Repository)
```bash
# 1. Pull latest changes to source
cd /path/to/leo-claude-mktplace cd /path/to/leo-claude-mktplace
git pull origin main git pull origin main
# 2. Run post-update script # 2. Run post-update script (updates source repo venvs)
./scripts/post-update.sh ./scripts/post-update.sh
# 3. CRITICAL: Run setup in installed marketplace location
cd ~/.claude/plugins/marketplaces/leo-claude-mktplace && ./scripts/setup.sh
``` ```
**Then restart your Claude Code session** to load any changes. **Then restart your Claude Code session** to load any changes.
@@ -132,10 +149,34 @@ deactivate
### MCP server won't start after update ### MCP server won't start after update
**Most common cause:** Virtual environments don't exist in the installed marketplace.
```bash
# Fix: Run setup in installed location
cd ~/.claude/plugins/marketplaces/leo-claude-mktplace && ./scripts/setup.sh
```
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: `ls mcp-servers/gitea/.venv` 2. Verify venv exists in INSTALLED location:
3. Restart Claude Code session ```bash
4. Check logs for specific errors ls ~/.claude/plugins/marketplaces/leo-claude-mktplace/mcp-servers/gitea/.venv
ls ~/.claude/plugins/marketplaces/leo-claude-mktplace/mcp-servers/netbox/.venv
```
3. If missing, the symlinks won't resolve. Run setup.sh as shown above.
4. Restart Claude Code session
5. Check logs for specific errors
### "X MCP servers failed" on startup
This almost always means the venvs don't exist in the installed marketplace:
```bash
cd ~/.claude/plugins/marketplaces/leo-claude-mktplace && ./scripts/setup.sh
```
Then restart Claude Code.
### New commands not available ### New commands not available

View File

@@ -4,10 +4,13 @@ Configuration loader for Gitea MCP Server.
Implements hybrid configuration system: Implements hybrid configuration system:
- System-level: ~/.config/claude/gitea.env (credentials) - System-level: ~/.config/claude/gitea.env (credentials)
- Project-level: .env (repository specification) - Project-level: .env (repository specification)
- Auto-detection: Falls back to git remote URL parsing
""" """
from pathlib import Path from pathlib import Path
from dotenv import load_dotenv from dotenv import load_dotenv
import os import os
import re
import subprocess
import logging import logging
from typing import Dict, Optional from typing import Dict, Optional
@@ -48,8 +51,12 @@ class GiteaConfig:
"cat > ~/.config/claude/gitea.env" "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) # Load project config (overrides system)
project_config = Path.cwd() / '.env' if project_dir:
project_config = project_dir / '.env'
if project_config.exists(): if project_config.exists():
load_dotenv(project_config, override=True) load_dotenv(project_config, override=True)
logger.info(f"Loaded project configuration from {project_config}") logger.info(f"Loaded project configuration from {project_config}")
@@ -59,6 +66,12 @@ class GiteaConfig:
self.api_token = os.getenv('GITEA_API_TOKEN') self.api_token = os.getenv('GITEA_API_TOKEN')
self.repo = os.getenv('GITEA_REPO') # Optional, must be owner/repo format 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 # Detect mode
if self.repo: if self.repo:
self.mode = 'project' self.mode = 'project'
@@ -96,3 +109,119 @@ class GiteaConfig:
f"Missing required configuration: {', '.join(missing)}\n" f"Missing required configuration: {', '.join(missing)}\n"
"Check your ~/.config/claude/gitea.env file" "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

@@ -110,8 +110,14 @@ class GiteaClient:
def _resolve_label_ids(self, label_names: List[str], owner: str, repo: str) -> List[int]: def _resolve_label_ids(self, label_names: List[str], owner: str, repo: str) -> List[int]:
"""Convert label names to label IDs.""" """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) org_labels = self.get_org_labels(owner)
repo_labels = self.get_labels(f"{owner}/{repo}")
repo_labels = self.get_labels(full_repo)
all_labels = org_labels + repo_labels all_labels = org_labels + repo_labels
label_map = {label['name']: label['id'] for label in all_labels} label_map = {label['name']: label['id'] for label in all_labels}
label_ids = [] label_ids = []
@@ -548,10 +554,33 @@ class GiteaClient:
return response.json() return response.json()
def is_org_repo(self, repo: Optional[str] = None) -> bool: def is_org_repo(self, repo: Optional[str] = None) -> bool:
"""Check if repository belongs to an organization (not a user).""" """
info = self.get_repo_info(repo) Check if repository belongs to an organization (not a user).
owner_type = info.get('owner', {}).get('type', '')
return owner_type.lower() == 'organization' 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( def get_branch_protection(
self, self,

View File

@@ -220,6 +220,10 @@ class GiteaMCPServer:
"context": { "context": {
"type": "string", "type": "string",
"description": "Issue title + description or sprint context" "description": "Issue title + description or sprint context"
},
"repo": {
"type": "string",
"description": "Repository name (owner/repo format)"
} }
}, },
"required": ["context"] "required": ["context"]

View File

@@ -8,6 +8,7 @@ Provides async wrappers for label operations with:
""" """
import asyncio import asyncio
import logging import logging
import re
from typing import List, Dict, Optional from typing import List, Dict, Optional
logging.basicConfig(level=logging.INFO) logging.basicConfig(level=logging.INFO)
@@ -27,15 +28,22 @@ class LabelTools:
self.gitea = gitea_client self.gitea = gitea_client
async def get_labels(self, repo: Optional[str] = None) -> Dict[str, List[Dict]]: async def get_labels(self, repo: Optional[str] = None) -> Dict[str, List[Dict]]:
"""Get all labels (org + repo). Repo must be 'owner/repo' format.""" """Get all labels (org + repo if org-owned, repo-only if user-owned)."""
loop = asyncio.get_event_loop() loop = asyncio.get_event_loop()
target_repo = repo or self.gitea.repo target_repo = repo or self.gitea.repo
if not target_repo or '/' not in target_repo: if not target_repo or '/' not in target_repo:
raise ValueError("Use 'owner/repo' format (e.g. 'org/repo-name')") raise ValueError("Use 'owner/repo' format (e.g. 'org/repo-name')")
org = target_repo.split('/')[0] # 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( org_labels = await loop.run_in_executor(
None, None,
lambda: self.gitea.get_org_labels(org) lambda: self.gitea.get_org_labels(org)
@@ -52,107 +60,202 @@ class LabelTools:
'total_count': len(org_labels) + len(repo_labels) 'total_count': len(org_labels) + len(repo_labels)
} }
async def suggest_labels(self, context: str) -> List[str]: async def suggest_labels(self, context: str, repo: Optional[str] = None) -> List[str]:
""" """
Analyze context and suggest appropriate labels. 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: Args:
context: Issue title + description or sprint context context: Issue title + description or sprint context
repo: Repository in 'owner/repo' format (optional, uses default if not provided)
Returns: Returns:
List of suggested label names 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 = [] suggested = []
context_lower = context.lower() context_lower = context.lower()
# Type detection (exclusive - only one) # Type detection (exclusive - only one)
type_label = None
if any(word in context_lower for word in ['bug', 'error', 'fix', 'broken', 'crash', 'fail']): if any(word in context_lower for word in ['bug', 'error', 'fix', 'broken', 'crash', 'fail']):
suggested.append('Type/Bug') type_label = self._find_label(label_lookup, 'type', 'bug')
elif any(word in context_lower for word in ['refactor', 'extract', 'restructure', 'architecture', 'service extraction']): elif any(word in context_lower for word in ['refactor', 'extract', 'restructure', 'architecture', 'service extraction']):
suggested.append('Type/Refactor') type_label = self._find_label(label_lookup, 'type', 'refactor')
elif any(word in context_lower for word in ['feature', 'add', 'implement', 'new', 'create']): elif any(word in context_lower for word in ['feature', 'add', 'implement', 'new', 'create']):
suggested.append('Type/Feature') type_label = self._find_label(label_lookup, 'type', 'feature')
elif any(word in context_lower for word in ['docs', 'documentation', 'readme', 'guide']): elif any(word in context_lower for word in ['docs', 'documentation', 'readme', 'guide']):
suggested.append('Type/Documentation') type_label = self._find_label(label_lookup, 'type', 'documentation')
elif any(word in context_lower for word in ['test', 'testing', 'spec', 'coverage']): elif any(word in context_lower for word in ['test', 'testing', 'spec', 'coverage']):
suggested.append('Type/Test') type_label = self._find_label(label_lookup, 'type', 'test')
elif any(word in context_lower for word in ['chore', 'maintenance', 'update', 'upgrade']): elif any(word in context_lower for word in ['chore', 'maintenance', 'update', 'upgrade']):
suggested.append('Type/Chore') type_label = self._find_label(label_lookup, 'type', 'chore')
if type_label:
suggested.append(type_label)
# Priority detection # Priority detection
priority_label = None
if any(word in context_lower for word in ['critical', 'urgent', 'blocker', 'blocking', 'emergency']): if any(word in context_lower for word in ['critical', 'urgent', 'blocker', 'blocking', 'emergency']):
suggested.append('Priority/Critical') priority_label = self._find_label(label_lookup, 'priority', 'critical')
elif any(word in context_lower for word in ['high', 'important', 'asap', 'soon']): elif any(word in context_lower for word in ['high', 'important', 'asap', 'soon']):
suggested.append('Priority/High') priority_label = self._find_label(label_lookup, 'priority', 'high')
elif any(word in context_lower for word in ['low', 'nice-to-have', 'optional', 'later']): elif any(word in context_lower for word in ['low', 'nice-to-have', 'optional', 'later']):
suggested.append('Priority/Low') priority_label = self._find_label(label_lookup, 'priority', 'low')
else: else:
suggested.append('Priority/Medium') priority_label = self._find_label(label_lookup, 'priority', 'medium')
if priority_label:
suggested.append(priority_label)
# Complexity detection # Complexity detection
complexity_label = None
if any(word in context_lower for word in ['simple', 'trivial', 'easy', 'quick']): if any(word in context_lower for word in ['simple', 'trivial', 'easy', 'quick']):
suggested.append('Complexity/Simple') complexity_label = self._find_label(label_lookup, 'complexity', 'simple')
elif any(word in context_lower for word in ['complex', 'difficult', 'challenging', 'intricate']): elif any(word in context_lower for word in ['complex', 'difficult', 'challenging', 'intricate']):
suggested.append('Complexity/Complex') complexity_label = self._find_label(label_lookup, 'complexity', 'complex')
else: else:
suggested.append('Complexity/Medium') complexity_label = self._find_label(label_lookup, 'complexity', 'medium')
if complexity_label:
suggested.append(complexity_label)
# Efforts detection # 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']): if any(word in context_lower for word in ['xs', 'tiny', '1 hour', '2 hours']):
suggested.append('Efforts/XS') effort_label = self._find_label(label_lookup, 'effort', 'xs')
elif any(word in context_lower for word in ['small', 's ', '1 day', 'half day']): elif any(word in context_lower for word in ['small', 's ', '1 day', 'half day']):
suggested.append('Efforts/S') effort_label = self._find_label(label_lookup, 'effort', 's')
elif any(word in context_lower for word in ['medium', 'm ', '2 days', '3 days']): elif any(word in context_lower for word in ['medium', 'm ', '2 days', '3 days']):
suggested.append('Efforts/M') effort_label = self._find_label(label_lookup, 'effort', 'm')
elif any(word in context_lower for word in ['large', 'l ', '1 week', '5 days']): elif any(word in context_lower for word in ['large', 'l ', '1 week', '5 days']):
suggested.append('Efforts/L') effort_label = self._find_label(label_lookup, 'effort', 'l')
elif any(word in context_lower for word in ['xl', 'extra large', '2 weeks', 'sprint']): elif any(word in context_lower for word in ['xl', 'extra large', '2 weeks', 'sprint']):
suggested.append('Efforts/XL') effort_label = self._find_label(label_lookup, 'effort', 'xl')
if effort_label:
suggested.append(effort_label)
# Component detection (based on keywords) # Component detection (based on keywords)
component_keywords = { component_mappings = {
'Component/Backend': ['backend', 'server', 'api', 'database', 'service'], 'backend': ['backend', 'server', 'api', 'database', 'service'],
'Component/Frontend': ['frontend', 'ui', 'interface', 'react', 'vue', 'component'], 'frontend': ['frontend', 'ui', 'interface', 'react', 'vue', 'component'],
'Component/API': ['api', 'endpoint', 'rest', 'graphql', 'route'], 'api': ['api', 'endpoint', 'rest', 'graphql', 'route'],
'Component/Database': ['database', 'db', 'sql', 'migration', 'schema', 'postgres'], 'database': ['database', 'db', 'sql', 'migration', 'schema', 'postgres'],
'Component/Auth': ['auth', 'authentication', 'login', 'oauth', 'token', 'session'], 'auth': ['auth', 'authentication', 'login', 'oauth', 'token', 'session'],
'Component/Deploy': ['deploy', 'deployment', 'docker', 'kubernetes', 'ci/cd'], 'deploy': ['deploy', 'deployment', 'docker', 'kubernetes', 'ci/cd'],
'Component/Testing': ['test', 'testing', 'spec', 'jest', 'pytest', 'coverage'], 'testing': ['test', 'testing', 'spec', 'jest', 'pytest', 'coverage'],
'Component/Docs': ['docs', 'documentation', 'readme', 'guide', 'wiki'] 'docs': ['docs', 'documentation', 'readme', 'guide', 'wiki']
} }
for label, keywords in component_keywords.items(): for component, keywords in component_mappings.items():
if any(keyword in context_lower for keyword in keywords): 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) suggested.append(label)
# Tech stack detection # Tech stack detection
tech_keywords = { tech_mappings = {
'Tech/Python': ['python', 'fastapi', 'django', 'flask', 'pytest'], 'python': ['python', 'fastapi', 'django', 'flask', 'pytest'],
'Tech/JavaScript': ['javascript', 'js', 'node', 'npm', 'yarn'], 'javascript': ['javascript', 'js', 'node', 'npm', 'yarn'],
'Tech/Docker': ['docker', 'dockerfile', 'container', 'compose'], 'docker': ['docker', 'dockerfile', 'container', 'compose'],
'Tech/PostgreSQL': ['postgres', 'postgresql', 'psql', 'sql'], 'postgresql': ['postgres', 'postgresql', 'psql', 'sql'],
'Tech/Redis': ['redis', 'cache', 'session store'], 'redis': ['redis', 'cache', 'session store'],
'Tech/Vue': ['vue', 'vuejs', 'nuxt'], 'vue': ['vue', 'vuejs', 'nuxt'],
'Tech/FastAPI': ['fastapi', 'pydantic', 'starlette'] 'fastapi': ['fastapi', 'pydantic', 'starlette']
} }
for label, keywords in tech_keywords.items(): for tech, keywords in tech_mappings.items():
if any(keyword in context_lower for keyword in keywords): 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) suggested.append(label)
# Source detection (based on git branch or context) # Source detection (based on git branch or context)
source_label = None
if 'development' in context_lower or 'dev/' in context_lower: if 'development' in context_lower or 'dev/' in context_lower:
suggested.append('Source/Development') source_label = self._find_label(label_lookup, 'source', 'development')
elif 'staging' in context_lower or 'stage/' in context_lower: elif 'staging' in context_lower or 'stage/' in context_lower:
suggested.append('Source/Staging') source_label = self._find_label(label_lookup, 'source', 'staging')
elif 'production' in context_lower or 'prod' in context_lower: elif 'production' in context_lower or 'prod' in context_lower:
suggested.append('Source/Production') source_label = self._find_label(label_lookup, 'source', 'production')
if source_label:
suggested.append(source_label)
# Risk detection # Risk detection
risk_label = None
if any(word in context_lower for word in ['breaking', 'breaking change', 'major', 'risky']): if any(word in context_lower for word in ['breaking', 'breaking change', 'major', 'risky']):
suggested.append('Risk/High') risk_label = self._find_label(label_lookup, 'risk', 'high')
elif any(word in context_lower for word in ['safe', 'low risk', 'minor']): elif any(word in context_lower for word in ['safe', 'low risk', 'minor']):
suggested.append('Risk/Low') 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") logger.info(f"Suggested {len(suggested)} labels based on context and {len(label_names)} available labels")
return suggested 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

View File

@@ -149,3 +149,112 @@ def test_mode_detection_company(tmp_path, monkeypatch):
assert result['mode'] == 'company' assert result['mode'] == 'company'
assert result['repo'] is None 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

@@ -222,3 +222,47 @@ def test_no_repo_specified_error(gitea_client):
client.list_issues() client.list_issues()
assert "Repository not specified" in str(exc_info.value) assert "Repository not specified" 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

@@ -10,7 +10,8 @@ from mcp_server.tools.labels import LabelTools
def mock_gitea_client(): def mock_gitea_client():
"""Fixture providing mocked Gitea client""" """Fixture providing mocked Gitea client"""
client = Mock() client = Mock()
client.repo = 'test_repo' client.repo = 'test_org/test_repo'
client.is_org_repo = Mock(return_value=True)
return client return client
@@ -39,10 +40,141 @@ async def test_get_labels(label_tools):
assert result['total_count'] == 4 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 @pytest.mark.asyncio
async def test_suggest_labels_bug(): async def test_suggest_labels_bug():
"""Test label suggestion for bug context""" """Test label suggestion for bug context"""
tools = LabelTools(Mock()) 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" context = "Fix critical bug in login authentication"
suggestions = await tools.suggest_labels(context) suggestions = await tools.suggest_labels(context)
@@ -55,7 +187,8 @@ async def test_suggest_labels_bug():
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_suggest_labels_feature(): async def test_suggest_labels_feature():
"""Test label suggestion for feature context""" """Test label suggestion for feature context"""
tools = LabelTools(Mock()) labels = ['Type/Feature', 'Priority/Medium', 'Complexity/Medium']
tools = _create_tools_with_labels(labels)
context = "Add new feature to implement user dashboard" context = "Add new feature to implement user dashboard"
suggestions = await tools.suggest_labels(context) suggestions = await tools.suggest_labels(context)
@@ -67,7 +200,8 @@ async def test_suggest_labels_feature():
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_suggest_labels_refactor(): async def test_suggest_labels_refactor():
"""Test label suggestion for refactor context""" """Test label suggestion for refactor context"""
tools = LabelTools(Mock()) labels = ['Type/Refactor', 'Priority/Medium', 'Complexity/Medium', 'Component/Backend']
tools = _create_tools_with_labels(labels)
context = "Refactor architecture to extract service layer" context = "Refactor architecture to extract service layer"
suggestions = await tools.suggest_labels(context) suggestions = await tools.suggest_labels(context)
@@ -79,7 +213,8 @@ async def test_suggest_labels_refactor():
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_suggest_labels_documentation(): async def test_suggest_labels_documentation():
"""Test label suggestion for documentation context""" """Test label suggestion for documentation context"""
tools = LabelTools(Mock()) labels = ['Type/Documentation', 'Priority/Medium', 'Complexity/Medium', 'Component/API', 'Component/Docs']
tools = _create_tools_with_labels(labels)
context = "Update documentation for API endpoints" context = "Update documentation for API endpoints"
suggestions = await tools.suggest_labels(context) suggestions = await tools.suggest_labels(context)
@@ -91,7 +226,8 @@ async def test_suggest_labels_documentation():
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_suggest_labels_priority(): async def test_suggest_labels_priority():
"""Test priority detection in suggestions""" """Test priority detection in suggestions"""
tools = LabelTools(Mock()) labels = ['Type/Feature', 'Priority/Critical', 'Priority/High', 'Priority/Medium', 'Priority/Low', 'Complexity/Medium']
tools = _create_tools_with_labels(labels)
# Critical priority # Critical priority
context = "Urgent blocker in production" context = "Urgent blocker in production"
@@ -112,7 +248,8 @@ async def test_suggest_labels_priority():
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_suggest_labels_complexity(): async def test_suggest_labels_complexity():
"""Test complexity detection in suggestions""" """Test complexity detection in suggestions"""
tools = LabelTools(Mock()) labels = ['Type/Feature', 'Priority/Medium', 'Complexity/Simple', 'Complexity/Medium', 'Complexity/Complex']
tools = _create_tools_with_labels(labels)
# Simple complexity # Simple complexity
context = "Simple quick fix for typo" context = "Simple quick fix for typo"
@@ -128,7 +265,8 @@ async def test_suggest_labels_complexity():
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_suggest_labels_efforts(): async def test_suggest_labels_efforts():
"""Test efforts detection in suggestions""" """Test efforts detection in suggestions"""
tools = LabelTools(Mock()) 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 # XS effort
context = "Tiny fix that takes 1 hour" context = "Tiny fix that takes 1 hour"
@@ -144,7 +282,8 @@ async def test_suggest_labels_efforts():
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_suggest_labels_components(): async def test_suggest_labels_components():
"""Test component detection in suggestions""" """Test component detection in suggestions"""
tools = LabelTools(Mock()) labels = ['Type/Feature', 'Priority/Medium', 'Complexity/Medium', 'Component/Backend', 'Component/Frontend', 'Component/API', 'Component/Database']
tools = _create_tools_with_labels(labels)
# Backend component # Backend component
context = "Update backend API service" context = "Update backend API service"
@@ -166,7 +305,8 @@ async def test_suggest_labels_components():
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_suggest_labels_tech_stack(): async def test_suggest_labels_tech_stack():
"""Test tech stack detection in suggestions""" """Test tech stack detection in suggestions"""
tools = LabelTools(Mock()) labels = ['Type/Feature', 'Priority/Medium', 'Complexity/Medium', 'Tech/Python', 'Tech/FastAPI', 'Tech/Docker', 'Tech/PostgreSQL']
tools = _create_tools_with_labels(labels)
# Python # Python
context = "Update Python FastAPI endpoint" context = "Update Python FastAPI endpoint"
@@ -188,7 +328,8 @@ async def test_suggest_labels_tech_stack():
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_suggest_labels_source(): async def test_suggest_labels_source():
"""Test source detection in suggestions""" """Test source detection in suggestions"""
tools = LabelTools(Mock()) labels = ['Type/Feature', 'Priority/Medium', 'Complexity/Medium', 'Source/Development', 'Source/Staging', 'Source/Production']
tools = _create_tools_with_labels(labels)
# Development # Development
context = "Issue found in development environment" context = "Issue found in development environment"
@@ -204,7 +345,8 @@ async def test_suggest_labels_source():
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_suggest_labels_risk(): async def test_suggest_labels_risk():
"""Test risk detection in suggestions""" """Test risk detection in suggestions"""
tools = LabelTools(Mock()) labels = ['Type/Feature', 'Priority/Medium', 'Complexity/Medium', 'Risk/High', 'Risk/Low']
tools = _create_tools_with_labels(labels)
# High risk # High risk
context = "Breaking change to major API" context = "Breaking change to major API"
@@ -220,7 +362,15 @@ async def test_suggest_labels_risk():
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_suggest_labels_multiple_categories(): async def test_suggest_labels_multiple_categories():
"""Test that suggestions span multiple categories""" """Test that suggestions span multiple categories"""
tools = LabelTools(Mock()) 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 = """ context = """
Urgent critical bug in production backend API service. Urgent critical bug in production backend API service.
@@ -244,3 +394,85 @@ async def test_suggest_labels_multiple_categories():
# Should have Source # Should have Source
assert any('Source/' in label for label in suggestions) 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

@@ -6,7 +6,7 @@
"hooks": [ "hooks": [
{ {
"type": "prompt", "type": "prompt",
"prompt": "SECURITY CHECK - Before writing this code, scan for these patterns:\n\n**Critical (BLOCK if found):**\n- eval(), exec() with user input\n- SQL string concatenation (SQL injection)\n- shell=True with user input (command injection)\n- Hardcoded secrets (API keys, passwords, tokens)\n- Pickle/marshal deserialization of untrusted data\n- innerHTML/dangerouslySetInnerHTML with user content (XSS)\n\n**Warning (WARN but allow):**\n- subprocess without input validation\n- File operations without path sanitization\n- HTTP requests without timeout\n- Broad exception catches (except:)\n- Debug/print statements with sensitive data\n\n**Response:**\n- If CRITICAL found: STOP, explain the issue, suggest safe alternative\n- If WARNING found: Note it briefly, proceed with suggestion\n- If clean: Proceed silently (say nothing)\n\nDo NOT announce clean scans. Only speak if issues found." "prompt": "[code-sentinel] SECURITY CHECK - Before writing this code, scan for these patterns:\n\n**Critical (BLOCK if found):**\n- eval(), exec() with user input\n- SQL string concatenation (SQL injection)\n- shell=True with user input (command injection)\n- Hardcoded secrets (API keys, passwords, tokens)\n- Pickle/marshal deserialization of untrusted data\n- innerHTML/dangerouslySetInnerHTML with user content (XSS)\n\n**Warning (WARN but allow):**\n- subprocess without input validation\n- File operations without path sanitization\n- HTTP requests without timeout\n- Broad exception catches (except:)\n- Debug/print statements with sensitive data\n\n**Response:**\n- If CRITICAL found: STOP with '[code-sentinel] BLOCKED:', explain the issue, suggest safe alternative\n- If WARNING found: Note briefly with '[code-sentinel] WARNING:', proceed with suggestion\n- If clean: Proceed silently (say nothing)\n\nDo NOT announce clean scans. Only speak if issues found."
} }
] ]
} }

View File

@@ -11,10 +11,10 @@ Documentation gets outdated. Functions get renamed, configs change, versions bum
## Solution ## Solution
doc-guardian watches your code changes and automatically: doc-guardian monitors your code changes via hooks:
1. Detects when changes affect documentation 1. Detects when changes might affect documentation
2. Queues updates silently (doesn't interrupt your flow) 2. Alerts you to potential drift (doesn't interrupt your flow)
3. Syncs all doc changes in a single commit when ready 3. Provides commands to audit and sync docs when ready
## Commands ## Commands
@@ -25,8 +25,7 @@ doc-guardian watches your code changes and automatically:
## Hooks ## Hooks
- **PostToolUse (Write\|Edit)**: Silently checks if code changes affect docs - **PostToolUse (Write|Edit)**: Silently checks if code changes affect docs
- **Stop**: Reminds you of pending doc updates before session ends
## What It Detects ## What It Detects

View File

@@ -6,18 +6,7 @@
"hooks": [ "hooks": [
{ {
"type": "prompt", "type": "prompt",
"prompt": "A file was just modified. Silently check if this change affects any documentation:\n\n1. If a code file changed: check if README, CLAUDE.md, docstrings, or API docs reference the modified functions/classes/configs\n2. If drift detected: add to internal queue (do NOT interrupt user flow)\n3. At natural breakpoints or when user runs /doc-sync: report pending doc updates\n\nDo NOT announce this check unless drift is found. Work silently." "prompt": "[doc-guardian] QUICK drift check (DO NOT block workflow):\n\n1. ONLY check if the modified file is referenced in README.md, CLAUDE.md, or API docs in the SAME directory\n2. Do NOT read files or perform deep analysis - just note potential drift based on file name/path\n3. If potential drift: output a single line like '[doc-guardian] Note: {filename} changed - may affect {doc}. Run /doc-sync to verify.'\n4. If no obvious drift: say nothing\n\nIMPORTANT: This is notification-only. Do NOT read documentation files, do NOT make changes, do NOT use any tools. Just a quick mental check based on the file path."
}
]
}
],
"Stop": [
{
"matcher": ".*",
"hooks": [
{
"type": "prompt",
"prompt": "Before ending, check if there are pending documentation updates queued by doc-guardian. If yes, ask user: 'I detected documentation drift in X files. Run /doc-sync to update, or skip for now?'"
} }
] ]
} }

View File

@@ -1,12 +1,19 @@
# /branch-cleanup - Clean Merged Branches # /branch-cleanup - Clean Merged and Stale Branches
## Purpose ## Purpose
Remove branches that have been merged, both locally and optionally on remote. Remove branches that have been merged OR whose remote tracking branch no longer exists, both locally and optionally on remote.
## Behavior ## Behavior
### Step 1: Identify Merged Branches ### Step 1: Prune Remote Refs
```bash
# Remove stale remote-tracking references
git fetch --prune
```
### Step 2: Identify Branches for Cleanup
```bash ```bash
# Find merged local branches # Find merged local branches
@@ -14,19 +21,26 @@ git branch --merged <base-branch>
# Find merged remote branches # Find merged remote branches
git branch -r --merged <base-branch> git branch -r --merged <base-branch>
# Find local branches with deleted upstreams (stale)
git branch -vv | grep ': gone]'
``` ```
### Step 2: Present Findings ### Step 3: Present Findings
``` ```
Found 5 merged branches: Found branches for cleanup:
Local: Merged (safe to delete):
- feat/login-page (merged 3 days ago) - feat/login-page (merged 3 days ago)
- fix/typo-header (merged 1 week ago) - fix/typo-header (merged 1 week ago)
- chore/deps-update (merged 2 weeks ago) - chore/deps-update (merged 2 weeks ago)
Remote: Stale (remote deleted):
- feat/old-feature (upstream gone)
- fix/already-merged (upstream gone)
Remote (merged into base):
- origin/feat/login-page - origin/feat/login-page
- origin/fix/typo-header - origin/fix/typo-header
@@ -36,35 +50,40 @@ Protected (won't delete):
- staging - staging
Delete these branches? Delete these branches?
1. Delete all (local + remote) 1. Delete all (local merged + stale + remote)
2. Delete local only 2. Delete merged only (skip stale)
3. Let me pick which ones 3. Delete stale only (upstream gone)
4. Cancel 4. Let me pick which ones
5. Cancel
``` ```
### Step 3: Execute Cleanup ### Step 4: Execute Cleanup
```bash ```bash
# Delete local # Delete merged local branches
git branch -d <branch-name> git branch -d <branch-name>
# Delete remote # Delete stale local branches (force needed since no upstream)
git branch -D <stale-branch-name>
# Delete remote branches
git push origin --delete <branch-name> git push origin --delete <branch-name>
``` ```
### Step 4: Report ### Step 5: Report
``` ```
Cleanup complete: Cleanup complete:
Deleted local: 3 branches Deleted local (merged): 3 branches
Deleted local (stale): 2 branches
Deleted remote: 2 branches Deleted remote: 2 branches
Skipped: 0 branches Skipped: 0 branches
Remaining local branches: Remaining local branches:
- main - main
- development - development
- feat/current-work (not merged) - feat/current-work (not merged, has upstream)
``` ```
## Environment Variables ## Environment Variables
@@ -74,20 +93,24 @@ Remaining local branches:
| `GIT_DEFAULT_BASE` | `development` | Base branch for merge detection | | `GIT_DEFAULT_BASE` | `development` | Base branch for merge detection |
| `GIT_PROTECTED_BRANCHES` | `main,master,development,staging,production` | Never delete these | | `GIT_PROTECTED_BRANCHES` | `main,master,development,staging,production` | Never delete these |
| `GIT_AUTO_DELETE_REMOTE` | `false` | Auto-delete remote branches | | `GIT_AUTO_DELETE_REMOTE` | `false` | Auto-delete remote branches |
| `GIT_CLEANUP_STALE` | `true` | Include stale branches (upstream gone) in cleanup |
## Safety ## Safety
- Never deletes protected branches - Never deletes protected branches
- Warns about unmerged branches - Warns about unmerged branches that still have upstreams
- Confirms before deleting remote branches - Confirms before deleting remote branches
- Uses `-d` (safe delete) not `-D` (force delete) - Uses `-d` (safe delete) for merged branches
- Uses `-D` (force delete) only for stale branches with confirmation
- Stale branches are highlighted separately for review
## Output ## Output
On success: On success:
``` ```
Cleaned up: Cleaned up:
Local: 3 branches deleted Local (merged): 3 branches deleted
Local (stale): 2 branches deleted
Remote: 2 branches deleted Remote: 2 branches deleted
Repository is tidy! Repository is tidy!

View File

@@ -2,7 +2,7 @@
## Purpose ## Purpose
Full sync operation: commit local changes, push to remote, and sync with upstream/base branch. Full sync operation: commit local changes, push to remote, sync with upstream/base branch, and clean up stale remote-tracking branches.
## Behavior ## Behavior
@@ -19,8 +19,8 @@ Push committed changes to remote branch.
Pull latest from base branch and rebase/merge: Pull latest from base branch and rebase/merge:
```bash ```bash
# Fetch all # Fetch all with prune (removes stale remote-tracking refs)
git fetch --all git fetch --all --prune
# Rebase on base branch # Rebase on base branch
git rebase origin/<base-branch> git rebase origin/<base-branch>
@@ -29,7 +29,26 @@ git rebase origin/<base-branch>
git push --force-with-lease git push --force-with-lease
``` ```
### Step 4: Report Status ### Step 4: Detect Stale Local Branches
Check for local branches tracking deleted remotes:
```bash
# Find local branches with gone upstreams
git branch -vv | grep ': gone]'
```
If stale branches found, report them:
```
Stale local branches (remote deleted):
- feat/old-feature (was tracking origin/feat/old-feature)
- fix/merged-bugfix (was tracking origin/fix/merged-bugfix)
Run /branch-cleanup to remove these branches.
```
### Step 5: Report Status
``` ```
Sync complete: Sync complete:
@@ -40,6 +59,10 @@ Base: development @ xyz7890 (synced)
Your branch is up-to-date with development. Your branch is up-to-date with development.
No conflicts detected. No conflicts detected.
Cleanup:
Remote refs pruned: 2
Stale local branches: 2 (run /branch-cleanup to remove)
``` ```
## Environment Variables ## Environment Variables
@@ -48,6 +71,7 @@ No conflicts detected.
|----------|---------|-------------| |----------|---------|-------------|
| `GIT_DEFAULT_BASE` | `development` | Branch to sync with | | `GIT_DEFAULT_BASE` | `development` | Branch to sync with |
| `GIT_SYNC_STRATEGY` | `rebase` | How to incorporate upstream changes | | `GIT_SYNC_STRATEGY` | `rebase` | How to incorporate upstream changes |
| `GIT_AUTO_PRUNE` | `true` | Auto-prune stale remote refs on sync |
## Conflict Handling ## Conflict Handling
@@ -76,4 +100,5 @@ Pushed to: origin/feat/password-reset
Synced with: development (xyz7890) Synced with: development (xyz7890)
Status: Clean, up-to-date Status: Clean, up-to-date
Stale branches: None (or N found - run /branch-cleanup)
``` ```

View File

@@ -3,7 +3,10 @@
"gitea": { "gitea": {
"command": "${CLAUDE_PLUGIN_ROOT}/mcp-servers/gitea/.venv/bin/python", "command": "${CLAUDE_PLUGIN_ROOT}/mcp-servers/gitea/.venv/bin/python",
"args": ["-m", "mcp_server.server"], "args": ["-m", "mcp_server.server"],
"cwd": "${CLAUDE_PLUGIN_ROOT}/mcp-servers/gitea" "cwd": "${CLAUDE_PLUGIN_ROOT}/mcp-servers/gitea",
"env": {
"PYTHONPATH": "${CLAUDE_PLUGIN_ROOT}/mcp-servers/gitea"
}
} }
} }
} }

View File

@@ -1,14 +1,9 @@
{ {
"hooks": { "hooks": {
"SessionStart": [ "SessionStart": [
{
"matcher": ".*",
"hooks": [
{ {
"type": "prompt", "type": "prompt",
"prompt": "Silently check if the project's git remote matches the .env configuration:\n\n1. Read .env to get GITEA_ORG and GITEA_REPO values\n2. Run: git remote get-url origin\n3. Extract org and repo from the remote URL\n4. Compare with .env values\n\nIf mismatch detected:\n- Display a brief warning: 'Repository location mismatch detected. Git remote shows <org>/<repo> but .env has <old-org>/<old-repo>. Run /project-sync to update.'\n\nIf match or no .env exists: Stay silent, do not interrupt.\n\nThis check should be quick and non-blocking." "prompt": "[pr-review] Perform silent startup checks:\n\n1. Check if MCP venvs exist at the installed marketplace location. If the marketplace is installed but venvs are missing, warn the user: '[pr-review] MCP venvs missing - run setup.sh from installed marketplace location'.\n\n2. Check if the project git remote matches .env configuration (GITEA_ORG/GITEA_REPO). If mismatch, warn: '[pr-review] Git remote mismatch - run /project-sync'.\n\nStay silent if all checks pass or not applicable. Be quick and non-blocking."
}
]
} }
] ]
} }

View File

@@ -1,6 +1,6 @@
{ {
"name": "projman", "name": "projman",
"version": "3.0.0", "version": "3.1.0",
"description": "Sprint planning and project management with Gitea integration", "description": "Sprint planning and project management with Gitea integration",
"author": { "author": {
"name": "Leo Miranda", "name": "Leo Miranda",

View File

@@ -178,7 +178,9 @@ Sync configuration with current git remote.
**When to use:** After moving or renaming a repository **When to use:** After moving or renaming a repository
**Note:** A SessionStart hook automatically detects mismatches and warns you to run `/project-sync`. **Note:** A SessionStart hook automatically checks for:
1. Missing MCP venvs at the installed marketplace location (warns to run setup.sh)
2. Repository config mismatches (warns to run `/project-sync`)
### `/review` ### `/review`
Pre-sprint-close code quality review. Pre-sprint-close code quality review.
@@ -232,6 +234,39 @@ Generate tests for specified code.
**When to use:** When adding new code that needs test coverage **When to use:** When adding new code that needs test coverage
## Debug Workflow Commands
These commands enable a cross-repository debugging workflow between your project and the marketplace.
### `/debug-report`
Run diagnostics and create structured issue in marketplace repository.
**What it does:**
- Runs MCP tool diagnostics (validate_repo_org, get_labels, list_issues, etc.)
- Captures error messages and hypothesis
- Creates a structured issue in the marketplace repository
- Tags with `Source: Diagnostic` label
**When to use:** When MCP tools fail in your project, run this to report the issue to the marketplace for investigation.
### `/debug-review`
Investigate diagnostic issues and propose fixes with human approval.
**What it does:**
- Fetches open diagnostic issues from marketplace
- Lets you select which issue to investigate
- Maps errors to relevant source files
- Reads code and analyzes root cause
- Proposes fixes with THREE mandatory approval gates
- Creates PR with fix after approval
**Approval Gates:**
1. Analysis confirmation - Does the investigation match your understanding?
2. Fix approach - Proceed with proposed changes?
3. PR creation - Create pull request?
**When to use:** In the marketplace repo, to investigate and fix issues reported by `/debug-report`.
## Code Quality Commands ## Code Quality Commands
The `/review` and `/test-check` commands complement the Executor agent by catching issues before work is marked complete. Run both commands before `/sprint-close` for a complete quality check. The `/review` and `/test-check` commands complement the Executor agent by catching issues before work is marked complete. Run both commands before `/sprint-close` for a complete quality check.
@@ -447,9 +482,13 @@ projman/
│ ├── sprint-close.md │ ├── sprint-close.md
│ ├── labels-sync.md │ ├── labels-sync.md
│ ├── initial-setup.md │ ├── initial-setup.md
│ ├── project-init.md
│ ├── project-sync.md
│ ├── review.md │ ├── review.md
│ ├── test-check.md │ ├── test-check.md
── test-gen.md ── test-gen.md
│ ├── debug-report.md
│ └── debug-review.md
├── agents/ # Agent prompts ├── agents/ # Agent prompts
│ ├── planner.md │ ├── planner.md
│ ├── orchestrator.md │ ├── orchestrator.md

View File

@@ -0,0 +1,342 @@
---
description: Run diagnostics and create structured issue in marketplace repository
---
# Debug Report
Run diagnostic checks on projman MCP tools and create a structured issue in the marketplace repository for investigation.
## Prerequisites
Your project `.env` must have:
```env
PROJMAN_MARKETPLACE_REPO=personal-projects/leo-claude-mktplace
```
If not configured, ask the user for the marketplace repository path.
## CRITICAL: Execution Steps
You MUST follow these steps in order. Do NOT skip any step.
### Step 1: Gather Project Context
Run these Bash commands to capture project information:
```bash
# Get git remote URL
git remote get-url origin
# Get current branch
git branch --show-current
# Get working directory
pwd
```
Parse the git remote to extract `REPO_NAME` in `owner/repo` format.
Store all values:
- `PROJECT_REPO`: The detected owner/repo
- `GIT_REMOTE`: Full git remote URL
- `CURRENT_BRANCH`: Current branch name
- `WORKING_DIR`: Current working directory
### Step 2: Read Marketplace Configuration
```bash
grep PROJMAN_MARKETPLACE_REPO .env
```
Store as `MARKETPLACE_REPO`. If not found, ask the user.
### Step 3: Run Diagnostic Suite
Run each MCP tool with explicit `repo` parameter. Record success/failure and full response.
**Test 1: validate_repo_org**
```
mcp__plugin_projman_gitea__validate_repo_org(repo=PROJECT_REPO)
```
Expected: `{is_organization: true/false}`
**Test 2: get_labels**
```
mcp__plugin_projman_gitea__get_labels(repo=PROJECT_REPO)
```
Expected: `{organization: [...], repository: [...], total_count: N}`
**Test 3: list_issues**
```
mcp__plugin_projman_gitea__list_issues(repo=PROJECT_REPO, state="open")
```
Expected: Array of issues
**Test 4: list_milestones**
```
mcp__plugin_projman_gitea__list_milestones(repo=PROJECT_REPO)
```
Expected: Array of milestones
**Test 5: suggest_labels**
```
mcp__plugin_projman_gitea__suggest_labels(context="Test bug fix for authentication")
```
Expected: Array of label names matching repo's format
For each test, record:
- Tool name
- Exact parameters used
- Status: PASS or FAIL
- Response or error message
### Step 4: Analyze Results
Count failures and categorize errors:
| Category | Indicators |
|----------|------------|
| Parameter Format | "owner/repo format", "missing parameter" |
| Authentication | "401", "403", "unauthorized" |
| Not Found | "404", "not found" |
| Network | "connection", "timeout", "ECONNREFUSED" |
| Logic | Unexpected response format, wrong data |
For each failure, write a hypothesis about the likely cause.
### Step 5: Generate Issue Content
Use this exact template:
```markdown
## Diagnostic Report
**Generated**: [ISO timestamp]
**Command Tested**: [What the user was trying to run, or "general diagnostic"]
**Reporter**: Claude Code via /debug-report
## Project Context
| Field | Value |
|-------|-------|
| Repository | `[PROJECT_REPO]` |
| Git Remote | `[GIT_REMOTE]` |
| Working Directory | `[WORKING_DIR]` |
| Current Branch | `[CURRENT_BRANCH]` |
## Diagnostic Results
### Test 1: validate_repo_org
**Call**: `validate_repo_org(repo="[PROJECT_REPO]")`
**Status**: [PASS/FAIL]
**Response**:
```json
[full response or error]
```
### Test 2: get_labels
**Call**: `get_labels(repo="[PROJECT_REPO]")`
**Status**: [PASS/FAIL]
**Response**:
```json
[full response or error - truncate if very long]
```
[... repeat for each test ...]
## Summary
- **Total Tests**: 5
- **Passed**: [N]
- **Failed**: [N]
### Failed Tools
[List each failed tool with its error]
### Error Category
[Check applicable categories]
### Hypothesis
[Your analysis of what's wrong and why]
### Suggested Investigation
1. [First file/function to check]
2. [Second file/function to check]
3. [etc.]
## Reproduction Steps
1. Navigate to `[WORKING_DIR]`
2. Run `[command that was being tested]`
3. Observe error at step [X]
---
*Generated by /debug-report - Labels: Type: Bug, Source: Diagnostic, Agent: Claude*
```
### Step 6: Create Issue in Marketplace
**First, check if MCP tools are available.** Attempt to use an MCP tool. If you receive "tool not found", "not in function list", or similar error, the MCP server is not accessible in this session - use the curl fallback.
#### Option A: MCP Available (preferred)
```
mcp__plugin_projman_gitea__create_issue(
repo=MARKETPLACE_REPO,
title="[Diagnostic] [summary of main failure]",
body=[generated content from Step 5],
labels=["Type: Bug", "Source: Diagnostic", "Agent: Claude"]
)
```
If labels don't exist, create issue without labels.
#### Option B: MCP Unavailable - Use curl Fallback
If MCP tools are not available (the very issue you may be diagnosing), use this fallback:
**1. Check for Gitea credentials:**
```bash
if [[ -f ~/.config/claude/gitea.env ]]; then
source ~/.config/claude/gitea.env
echo "Credentials found. API URL: $GITEA_API_URL"
else
echo "No credentials at ~/.config/claude/gitea.env"
fi
```
**2. If credentials exist, create issue via curl with proper JSON escaping:**
Create secure temp files and save content:
```bash
# Create temp files with restrictive permissions
DIAG_TITLE=$(mktemp -p /tmp -m 600 diag-title.XXXXXX)
DIAG_BODY=$(mktemp -p /tmp -m 600 diag-body.XXXXXX)
DIAG_PAYLOAD=$(mktemp -p /tmp -m 600 diag-payload.XXXXXX)
# Save title
echo "[Diagnostic] [summary of main failure]" > "$DIAG_TITLE"
# Save body (paste Step 5 content) - heredoc delimiter prevents shell expansion
cat > "$DIAG_BODY" << 'DIAGNOSTIC_EOF'
[Paste the full issue content from Step 5 here]
DIAGNOSTIC_EOF
```
Construct JSON safely using jq's --rawfile (avoids command substitution):
```bash
# Build JSON payload using jq with --rawfile for safe content handling
jq -n \
--rawfile title "$DIAG_TITLE" \
--rawfile body "$DIAG_BODY" \
'{title: ($title | rtrimstr("\n")), body: $body}' > "$DIAG_PAYLOAD"
# Create issue using the JSON file
curl -s -X POST "${GITEA_API_URL}/repos/${MARKETPLACE_REPO}/issues" \
-H "Authorization: token ${GITEA_API_TOKEN}" \
-H "Content-Type: application/json" \
-d @"$DIAG_PAYLOAD" | jq '.html_url // .'
# Secure cleanup
rm -f "$DIAG_TITLE" "$DIAG_BODY" "$DIAG_PAYLOAD"
```
**3. If no credentials found, save report locally:**
```bash
REPORT_FILE=$(mktemp -p /tmp -m 600 diagnostic-report-XXXXXX.md)
cat > "$REPORT_FILE" << 'DIAGNOSTIC_EOF'
[Paste the full issue content from Step 5 here]
DIAGNOSTIC_EOF
echo "Report saved to: $REPORT_FILE"
```
Then inform the user:
```
MCP tools are unavailable and no Gitea credentials found at ~/.config/claude/gitea.env.
Diagnostic report saved to: [REPORT_FILE]
To create the issue manually:
1. Configure credentials: See docs/CONFIGURATION.md
2. Or create issue directly at: http://gitea.hotserv.cloud/[MARKETPLACE_REPO]/issues/new
```
### Step 7: Report to User
Display summary:
```
Debug Report Complete
=====================
Project: [PROJECT_REPO]
Tests Run: 5
Passed: [N]
Failed: [N]
Failed Tools:
- [tool1]: [brief error]
- [tool2]: [brief error]
Issue Created: [issue URL]
Next Steps:
1. Switch to marketplace repo: cd [marketplace path]
2. Run: /debug-review
3. Select issue #[N] to investigate
```
## DO NOT
- **DO NOT** attempt to fix anything - only report
- **DO NOT** create issues if all tests pass (just report success)
- **DO NOT** skip any diagnostic test
- **DO NOT** call MCP tools without the `repo` parameter
- **DO NOT** ask user questions during execution - run autonomously
## If All Tests Pass
If all 5 tests pass, report success without creating an issue:
```
Debug Report Complete
=====================
Project: [PROJECT_REPO]
Tests Run: 5
Passed: 5
Failed: 0
All diagnostics passed. No issues to report.
If you're experiencing a specific problem, please describe it
and I can create a manual bug report.
```
## Troubleshooting
**PROJMAN_MARKETPLACE_REPO not configured**
- Ask user: "What is the marketplace repository? (e.g., personal-projects/leo-claude-mktplace)"
- Store for this session and remind user to add to .env
**Cannot detect project repository**
- Check if in a git repository: `git rev-parse --git-dir`
- If not a git repo, ask user for the repository path
**MCP tools not available**
- Use the curl fallback in Step 6, Option B
- Requires Gitea credentials at `~/.config/claude/gitea.env`
- If no credentials, report will be saved locally for manual submission

View File

@@ -0,0 +1,394 @@
---
description: Investigate diagnostic issues and propose fixes with human approval
---
# Debug Review
Investigate diagnostic issues created by `/debug-report`, read relevant code, and propose fixes with human approval at each step.
## CRITICAL: This Command Requires Human Approval
This command has THREE mandatory approval gates. You MUST stop and wait for user confirmation at each gate before proceeding.
## Execution Steps
### Step 1: Detect Repository
Run Bash to get the current repository:
```bash
git remote get-url origin
```
Parse to extract `REPO_NAME` in `owner/repo` format.
### Step 2: Fetch Diagnostic Issues
```
mcp__plugin_projman_gitea__list_issues(
repo=REPO_NAME,
state="open",
labels=["Source: Diagnostic"]
)
```
If no issues with that label, try without label filter and look for issues with "[Diagnostic]" in title.
### Step 3: Display Issue List
Show the user available issues:
```
Debug Review
============
Open Diagnostic Issues:
#80 - [Diagnostic] get_labels fails without repo parameter
Created: 2026-01-21 | Labels: Type: Bug, Source: Diagnostic
#77 - [Diagnostic] MCP tools require explicit repo parameter
Created: 2026-01-21 | Labels: Type: Bug, Source: Diagnostic
No diagnostic issues? Showing recent bugs:
#75 - [Bug] Some other issue
Created: 2026-01-20
```
### Step 4: User Selects Issue
Use AskUserQuestion:
```
Which issue would you like to investigate?
Options: [List issue numbers]
```
Wait for user selection.
### Step 5: Fetch Full Issue Details
```
mcp__plugin_projman_gitea__get_issue(repo=REPO_NAME, issue_number=SELECTED)
```
### Step 6: Parse Diagnostic Report
Extract from the issue body:
1. **Failed Tools**: Which MCP tools failed
2. **Error Messages**: Exact error text
3. **Hypothesis**: Reporter's analysis
4. **Suggested Investigation**: Files to check
5. **Project Context**: Repo, branch, cwd where error occurred
If the issue doesn't follow the diagnostic template, extract what information is available.
### Step 7: Map Errors to Code Files
Use this mapping to identify relevant files:
**By Tool Name:**
| Tool | Primary Files |
|------|---------------|
| `validate_repo_org` | `mcp-servers/gitea/mcp_server/gitea_client.py` |
| `get_labels` | `mcp-servers/gitea/mcp_server/tools/labels.py` |
| `suggest_labels` | `mcp-servers/gitea/mcp_server/tools/labels.py` |
| `list_issues` | `mcp-servers/gitea/mcp_server/tools/issues.py` |
| `create_issue` | `mcp-servers/gitea/mcp_server/tools/issues.py` |
| `list_milestones` | `mcp-servers/gitea/mcp_server/gitea_client.py` |
**By Error Pattern:**
| Error Contains | Check Files |
|----------------|-------------|
| "owner/repo format" | `config.py`, `gitea_client.py` |
| "404" + "orgs" | `gitea_client.py` (is_org_repo method) |
| "401", "403" | `config.py` (token loading) |
| "No repository" | Command `.md` file (repo detection step) |
**By Command:**
| Command | Documentation File |
|---------|-------------------|
| `/labels-sync` | `plugins/projman/commands/labels-sync.md` |
| `/sprint-plan` | `plugins/projman/commands/sprint-plan.md` |
| `/sprint-start` | `plugins/projman/commands/sprint-start.md` |
| `/debug-report` | `plugins/projman/commands/debug-report.md` |
### Step 8: Read Relevant Files (MANDATORY)
You MUST read the identified files before proposing any fix.
For each relevant file:
1. Read the file using the Read tool
2. Find the specific function/method mentioned in the error
3. Understand the code path that leads to the error
4. Note any related code that might be affected
Display snippets of relevant code to the user:
```
Reading relevant files...
┌─ mcp-servers/gitea/mcp_server/tools/labels.py (lines 29-40) ────────┐
│ │
│ async def get_labels(self, repo: Optional[str] = None): │
│ target_repo = repo or self.gitea.repo │
│ if not target_repo or '/' not in target_repo: │
│ raise ValueError("Use 'owner/repo' format...") │
│ │
└──────────────────────────────────────────────────────────────────────┘
```
### Step 9: Present Investigation Summary
Summarize what you found:
```
Investigation Summary
=====================
ISSUE: #80 - get_labels fails without repo parameter
FAILED TOOLS:
• get_labels - "Use 'owner/repo' format"
CODE ANALYSIS:
1. labels.py:get_labels() requires repo parameter
- Line 30: `target_repo = repo or self.gitea.repo`
- Line 31-32: Raises ValueError if no repo
2. labels-sync.md documents Step 1 for repo detection
- Lines 13-26: Instructs to run `git remote get-url origin`
- This step may not be followed by executing Claude
ROOT CAUSE HYPOTHESIS:
The command documentation (labels-sync.md) correctly instructs
repo detection, but the executing Claude may be skipping Step 1
and calling MCP tools directly.
Evidence:
• Error indicates repo parameter was not passed
• labels-sync.md has correct instructions
• MCP server cannot auto-detect (sandboxed environment)
LIKELY FIX:
Option A: Make Step 1 more prominent in labels-sync.md
Option B: Add validation that repo was detected before proceeding
Option C: [Other based on analysis]
```
## APPROVAL GATE 1
```
Does this analysis match your understanding of the problem?
[Y] Yes, proceed to propose fix
[N] No, let me clarify
[R] Read more files first
```
**STOP HERE AND WAIT FOR USER RESPONSE**
Do NOT proceed until user approves.
### Step 10: Propose Fix Approach
Based on the analysis, propose a specific fix:
```
Proposed Fix
============
APPROACH: [A/B/C from above]
CHANGES NEEDED:
1. File: plugins/projman/commands/labels-sync.md
Change: Add warning box after Step 1 emphasizing repo must be detected
2. File: [if applicable]
Change: [description]
RATIONALE:
[Explain why this fix addresses the root cause]
RISKS:
[Any potential issues with this approach]
```
## APPROVAL GATE 2
```
Proceed with this fix approach?
[Y] Yes, implement it
[N] No, try different approach
[M] Modify the approach (tell me what to change)
```
**STOP HERE AND WAIT FOR USER RESPONSE**
Do NOT implement until user approves.
### Step 11: Implement Fix
Only after user approves:
1. Create feature branch:
```bash
git checkout -b fix/issue-[NUMBER]-[brief-description]
```
2. Make the code changes using Edit tool
3. Run relevant tests if they exist:
```bash
cd mcp-servers/gitea && .venv/bin/python -m pytest tests/ -v
```
4. Show the changes to user:
```bash
git diff
```
### Step 12: Present Changes
```
Changes Implemented
===================
Branch: fix/issue-80-labels-sync-instructions
Files Modified:
• plugins/projman/commands/labels-sync.md (+15, -3)
Diff Summary:
[Show git diff output]
Test Results:
• 23 passed, 0 failed (or N/A if no tests)
```
## APPROVAL GATE 3
```
Create PR with these changes?
[Y] Yes, create PR
[N] No, I want to modify something
[D] Discard changes
```
**STOP HERE AND WAIT FOR USER RESPONSE**
Do NOT create PR until user approves.
### Step 13: Create PR
Only after user approves:
1. Commit changes:
```bash
git add -A
git commit -m "fix: [description]
[Longer explanation]
Fixes #[ISSUE_NUMBER]
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>"
```
2. Push branch:
```bash
git push -u origin fix/issue-[NUMBER]-[brief-description]
```
3. Create PR via API or MCP tools
4. **Switch back to development branch** (required for MCP issue operations):
```bash
git checkout development
```
5. Add comment to original issue:
```
mcp__plugin_projman_gitea__add_comment(
repo=REPO_NAME,
issue_number=ISSUE_NUMBER,
comment="Fix proposed in PR #[PR_NUMBER]\n\nChanges:\n- [summary]\n\nPlease test after merge and report back."
)
```
### Step 14: Report Completion
```
Debug Review Complete
=====================
Issue: #80 - get_labels fails without repo parameter
Status: Fix Proposed
PR Created: #81 - fix: improve labels-sync repo detection instructions
URL: http://gitea.hotserv.cloud/.../pulls/81
Next Steps:
1. Review and merge PR #81
2. In test project, pull latest plugin version
3. Run /debug-report to verify fix
4. If passing, close issue #80
```
## DO NOT
- **DO NOT** skip reading relevant files - this is MANDATORY
- **DO NOT** proceed past approval gates without user confirmation
- **DO NOT** guess at fixes without evidence from code
- **DO NOT** close issues - let user verify fix works first
- **DO NOT** commit directly to development or main branches
## If Investigation Finds No Bug
Sometimes investigation reveals the issue is:
- User error (didn't follow documented steps)
- Configuration issue (missing .env vars)
- Already fixed in a newer version
In this case:
```
Investigation Summary
=====================
FINDING: This does not appear to be a code bug.
ANALYSIS:
[Explanation of what you found]
RECOMMENDATION:
[ ] Close issue as "not a bug" - user error
[ ] Close issue as "duplicate" of #[X]
[ ] Add documentation to prevent confusion
[ ] Other: [specify]
Would you like me to add a comment explaining this finding?
```
## Error-to-File Quick Reference
```
Error Message → File to Check
─────────────────────────────────────────────────────────────────
"Use 'owner/repo' format" → config.py, gitea_client.py
"404 Client Error.*orgs" → gitea_client.py (_is_organization)
"No repository specified" → Command .md file (Step 1)
"401 Unauthorized" → config.py (token loading)
"labels not found" → labels.py, gitea_client.py
"create local.*file" → Command .md file (DO NOT section)
```

View File

@@ -1,47 +1,95 @@
--- ---
description: Synchronize label taxonomy from Gitea and update suggestion logic description: Fetch and validate label taxonomy from Gitea, create missing required labels
--- ---
# Sync Label Taxonomy from Gitea # Sync Label Taxonomy from Gitea
This command synchronizes the label taxonomy from Gitea (organization + repository labels) and updates the local reference file used by the label suggestion logic. This command fetches the current label taxonomy from Gitea (organization + repository labels), validates that required labels exist, and creates any missing ones.
## Why Label Sync Matters ## CRITICAL: Execution Steps
The label taxonomy is **dynamic** - new labels may be added to Gitea over time: You MUST follow these steps in order. Do NOT skip any step.
- Organization-level labels (shared across all repos)
- Repository-specific labels (unique to this project)
**Dynamic approach:** Never hardcode labels. Always fetch from Gitea and adapt suggestions accordingly. ### Step 1: Detect Repository from Git Remote
## What This Command Does Run this Bash command to get the git remote URL:
1. **Validate Repository** - Verify repo belongs to an organization using `validate_repo_org` ```bash
2. **Fetch Current Labels** - Uses `get_labels` MCP tool to fetch all labels (org + repo) git remote get-url origin
3. **Compare with Local Reference** - Checks against `skills/label-taxonomy/labels-reference.md` ```
4. **Detect Changes** - Identifies new, removed, or modified labels
5. **Explain Changes** - Shows what changed and why it matters
6. **Create Missing Labels** - Uses `create_label` for required labels that don't exist
7. **Update Reference** - Updates the local labels-reference.md file
8. **Confirm Update** - Asks for user confirmation before updating
## MCP Tools Used Parse the output to extract `owner/repo`:
- SSH format `ssh://git@host:port/owner/repo.git` → extract `owner/repo`
- SSH short `git@host:owner/repo.git` → extract `owner/repo`
- HTTPS `https://host/owner/repo.git` → extract `owner/repo`
**Gitea Tools:** Store this as `REPO_NAME` for all subsequent MCP calls.
- `get_labels` - Fetch all labels (organization + repository)
- `create_label` - Create missing required labels
- `validate_repo_org` - Verify repository belongs to organization
## Required Label Categories ### Step 2: Validate Repository Organization
At minimum, these label categories must exist: Call MCP tool with the detected repo:
```
mcp__plugin_projman_gitea__validate_repo_org(repo=REPO_NAME)
```
This determines if the owner is an organization or user account.
### Step 3: Fetch Labels from Gitea
Call MCP tool with the detected repo:
```
mcp__plugin_projman_gitea__get_labels(repo=REPO_NAME)
```
This returns both organization labels (if org-owned) and repository labels.
### Step 4: Display Current Taxonomy
Show the user:
- Total organization labels count
- Total repository labels count
- Labels grouped by category (Type/*, Priority/*, etc.)
### Step 5: Check Required Labels
Verify these required label categories exist:
- **Type/***: Bug, Feature, Refactor, Documentation, Test, Chore - **Type/***: Bug, Feature, Refactor, Documentation, Test, Chore
- **Priority/***: Low, Medium, High, Critical - **Priority/***: Low, Medium, High, Critical
- **Complexity/***: Simple, Medium, Complex - **Complexity/***: Simple, Medium, Complex
- **Efforts/***: XS, S, M, L, XL - **Effort/***: XS, S, M, L, XL (note: may be "Effort" or "Efforts")
If any required labels are missing, the command will offer to create them. ### Step 6: Create Missing Labels (if any)
For each missing required label, call:
```
mcp__plugin_projman_gitea__create_label(repo=REPO_NAME, name="Type: Bug", color="d73a4a")
```
Use the label format that matches existing labels in the repo (slash `/` or colon-space `: `).
### Step 7: Report Results
Summarize what was found and created.
## DO NOT
- **DO NOT** call MCP tools without the `repo` parameter - they will fail
- **DO NOT** create any local files - this command only interacts with Gitea
- **DO NOT** ask the user questions - execute autonomously
- **DO NOT** create a "labels reference file" - labels are fetched dynamically from Gitea
## MCP Tools Used
All tools require the `repo` parameter in `owner/repo` format:
| Tool | Purpose |
|------|---------|
| `validate_repo_org(repo=...)` | Check if owner is organization or user |
| `get_labels(repo=...)` | Fetch all labels (org + repo) |
| `create_label(repo=..., name=..., color=...)` | Create missing labels |
## Expected Output ## Expected Output
@@ -49,212 +97,63 @@ If any required labels are missing, the command will offer to create them.
Label Taxonomy Sync Label Taxonomy Sync
=================== ===================
Validating repository organization... Detecting repository from git remote...
Repository: bandit/your-repo-name Repository: personal-projects/your-repo-name
Organization: bandit Owner type: Organization
Fetching labels from Gitea... Fetching labels from Gitea...
Current Label Taxonomy: Current Label Taxonomy:
- Organization Labels: 28 - Organization Labels: 27
- Repository Labels: 16 - Repository Labels: 16
- Total: 44 labels - Total: 43 labels
Comparing with local reference... Organization Labels by Category:
Type/*: 6 labels
Priority/*: 4 labels
Complexity/*: 3 labels
Effort/*: 5 labels
...
Changes Detected: Repository Labels by Category:
NEW: Type/Performance (org-level) Component/*: 9 labels
Description: Performance optimization tasks Tech/*: 7 labels
Color: #FF6B6B
Suggestion: Add to suggestion logic for performance-related work
NEW: Tech/Redis (repo-level)
Description: Redis-related technology
Color: #DC143C
Suggestion: Add to suggestion logic for caching and data store work
MODIFIED: Priority/Critical
Change: Color updated from #D73A4A to #FF0000
Impact: Visual only, no logic change needed
REMOVED: Component/Legacy
Reason: Component deprecated and removed from codebase
Impact: Remove from suggestion logic
Required Labels Check: Required Labels Check:
Type/*: 6/6 present Type/*: 6/6 present
Priority/*: 4/4 present Priority/*: 4/4 present
Complexity/*: 3/3 present Complexity/*: 3/3 present
Efforts/*: 5/5 present Effort/*: 5/5 present
Summary: All required labels present. Label taxonomy is ready for use.
- 2 new labels added
- 1 label modified (color only)
- 1 label removed
- Total labels: 44 -> 45
- All required labels present
Update local reference file?
[Y/n]
``` ```
## Label Taxonomy Structure ## Label Format Detection
Labels are organized by namespace: Labels may use different naming conventions:
- Slash format: `Type/Bug`, `Priority/High`
- Colon-space format: `Type: Bug`, `Priority: High`
**Organization Labels (28):** When creating missing labels, match the format used by existing labels in the repository.
- `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):** ## Troubleshooting
- `Component/*` (9): Backend, Frontend, API, Database, Auth, Deploy, Testing, Docs, Infra
- `Tech/*` (7): Python, JavaScript, Docker, PostgreSQL, Redis, Vue, FastAPI
## Local Reference File **Error: Use 'owner/repo' format**
- You forgot to pass the `repo` parameter to the MCP tool
- Go back to Step 1 and detect the repo from git remote
The command updates `skills/label-taxonomy/labels-reference.md` with: **Empty organization labels**
- If owner is a user account (not org), organization labels will be empty
- This is expected - user accounts only have repository-level labels
```markdown **Git remote not found**
# Label Taxonomy Reference - Ensure you're running in a directory with a git repository
- Check that the `origin` remote is configured
Last synced: 2025-01-18 14:30 UTC
Source: Gitea (bandit/your-repo-name)
## Organization Labels (28)
### Agent (2)
- Agent/Human - Work performed by human developers
- Agent/Claude - Work performed by Claude Code
### Type (6)
- Type/Bug - Bug fixes and error corrections
- Type/Feature - New features and enhancements
- Type/Refactor - Code restructuring and architectural changes
- Type/Documentation - Documentation updates
- Type/Test - Testing-related work
- Type/Chore - Maintenance and tooling tasks
...
## Repository Labels (16)
### Component (9)
- Component/Backend - Backend service code
- Component/Frontend - User interface code
- Component/API - API endpoints and contracts
...
## Suggestion Logic
When suggesting labels, consider:
**Type Detection:**
- Keywords "bug", "fix", "error" -> Type/Bug
- Keywords "feature", "add", "implement" -> Type/Feature
- Keywords "refactor", "extract", "restructure" -> Type/Refactor
...
```
## When to Run ## When to Run
Run `/labels-sync` when: Run `/labels-sync` when:
- Setting up the plugin for the first time - Setting up the plugin for the first time
- You notice missing labels in suggestions - You notice missing labels in suggestions
- New labels are added to Gitea (announced by team) - New labels are added to Gitea
- Quarterly maintenance (check for changes)
- After major taxonomy updates - After major taxonomy updates
## Integration with Other Commands
The updated taxonomy is used by:
- `/sprint-plan` - Planner agent uses `suggest_labels` with current taxonomy
- All commands that create or update issues
## Example Usage
```
User: /labels-sync
Validating repository organization...
Repository: bandit/your-repo-name
Fetching labels from Gitea...
Current Label Taxonomy:
- Organization Labels: 28
- Repository Labels: 16
- Total: 44 labels
Comparing with local reference...
No changes detected. Label taxonomy is up to date.
Last synced: 2025-01-18 14:30 UTC
```
```
User: /labels-sync
Fetching labels from Gitea...
Changes Detected:
NEW: Type/Performance
NEW: Tech/Redis
Required Labels Check:
MISSING: Complexity/Simple
MISSING: Complexity/Medium
MISSING: Complexity/Complex
Would you like me to create the missing required labels? [Y/n] y
Creating missing labels...
Created: Complexity/Simple
Created: Complexity/Medium
Created: Complexity/Complex
Update local reference file? [Y/n] y
Label taxonomy updated successfully!
Suggestion logic updated with new labels
New labels available for use:
- Type/Performance
- Tech/Redis
- Complexity/Simple
- Complexity/Medium
- Complexity/Complex
```
## Troubleshooting
**Error: Cannot fetch labels from Gitea**
- Check your Gitea configuration in `~/.config/claude/gitea.env`
- Verify your API token has `read:org` and `repo` permissions
- Ensure you're connected to the network
**Error: Repository is not under an organization**
- This plugin requires repositories to belong to an organization
- Transfer the repository to an organization or create one
**Error: Permission denied to update reference file**
- Check file permissions on `skills/label-taxonomy/labels-reference.md`
- Ensure you have write access to the plugin directory
**No changes detected but labels seem wrong**
- The reference file may be manually edited - review it
- Try forcing a re-sync by deleting the reference file first
- Check if you're comparing against the correct repository
## Best Practices
1. **Sync at sprint start** - Ensure labels are current before planning
2. **Review changes** - Always review what changed before confirming
3. **Create missing required labels** - Don't skip this step
4. **Update planning** - After sync, consider if new labels affect current sprint
5. **Communicate changes** - Let team know when new labels are available

View File

@@ -1,14 +1,9 @@
{ {
"hooks": { "hooks": {
"SessionStart": [ "SessionStart": [
{
"matcher": ".*",
"hooks": [
{ {
"type": "prompt", "type": "prompt",
"prompt": "Silently check if the project's git remote matches the .env configuration:\n\n1. Read .env to get GITEA_ORG and GITEA_REPO values\n2. Run: git remote get-url origin\n3. Extract org and repo from the remote URL\n4. Compare with .env values\n\nIf mismatch detected:\n- Display a brief warning: 'Repository location mismatch detected. Git remote shows <org>/<repo> but .env has <old-org>/<old-repo>. Run /project-sync to update.'\n\nIf match or no .env exists: Stay silent, do not interrupt.\n\nThis check should be quick and non-blocking." "prompt": "[projman] Perform silent startup checks:\n\n1. Check if MCP venvs exist at the installed marketplace location. If the marketplace is installed but venvs are missing, warn the user: '[projman] MCP venvs missing - run setup.sh from installed marketplace location'.\n\n2. Check if the project git remote matches .env configuration (GITEA_ORG/GITEA_REPO). If mismatch, warn: '[projman] Git remote mismatch - run /project-sync'.\n\nStay silent if all checks pass or not applicable. Be quick and non-blocking."
}
]
} }
] ]
} }

37
scripts/check-venv.sh Executable file
View File

@@ -0,0 +1,37 @@
#!/usr/bin/env bash
#
# check-venv.sh - Check if MCP server venvs exist in installed marketplace
#
# Usage: ./scripts/check-venv.sh
#
# Exit codes:
# 0 - All venvs exist (or not installed via marketplace)
# 1 - Venvs missing, needs setup
#
# This script is designed to be called from SessionStart hooks
# to enable self-healing MCP server setup.
set -euo pipefail
# Installed marketplace location
MKTPLACE="$HOME/.claude/plugins/marketplaces/leo-claude-mktplace"
# If not installed via marketplace, exit silently
if [[ ! -d "$MKTPLACE" ]]; then
exit 0
fi
# Check if gitea venv exists
if [[ ! -f "$MKTPLACE/mcp-servers/gitea/.venv/bin/python" ]]; then
echo "SETUP_NEEDED"
exit 1
fi
# Check if netbox venv exists
if [[ ! -f "$MKTPLACE/mcp-servers/netbox/.venv/bin/python" ]]; then
echo "SETUP_NEEDED"
exit 1
fi
# All good
exit 0