From 11a05799d3b104b73da200eed35a55e4f09ff31b Mon Sep 17 00:00:00 2001 From: lmiranda Date: Fri, 23 Jan 2026 11:25:14 -0500 Subject: [PATCH 1/6] docs: sync documentation with codebase MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - CLAUDE.md: Update version 3.0.1 → 3.1.2, projman 3.0.0 → 3.1.0, command count 12 → 13 - README.md: Add debug-report/debug-review to projman commands, add DEBUGGING-CHECKLIST.md to docs table - CANONICAL-PATHS.md: Update version, remove non-existent docs/workflows/, add COMMANDS-CHEATSHEET.md - projman/README.md: Fix "Three-Agent" → "Four-Agent", update architecture to show symlink - pr-review/README.md: Add missing setup commands (initial-setup, project-init, project-sync) - cmdb-assistant/README.md: Add initial-setup.md to architecture - project-hygiene/README.md: Fix invalid hook event name (task-completed → PostToolUse) - doc-guardian/plugin.json: Add missing commands field - code-sentinel/plugin.json: Add missing commands field Co-Authored-By: Claude Opus 4.5 --- CLAUDE.md | 8 ++++---- README.md | 3 ++- docs/CANONICAL-PATHS.md | 7 +++---- plugins/cmdb-assistant/README.md | 1 + .../code-sentinel/.claude-plugin/plugin.json | 3 ++- .../doc-guardian/.claude-plugin/plugin.json | 3 ++- plugins/pr-review/README.md | 3 +++ plugins/project-hygiene/README.md | 2 +- plugins/projman/README.md | 18 +++--------------- 9 files changed, 21 insertions(+), 27 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 0bd4a48..3d9f720 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -5,14 +5,14 @@ This file provides guidance to Claude Code when working with code in this reposi ## Project Overview **Repository:** leo-claude-mktplace -**Version:** 3.0.1 +**Version:** 3.1.2 **Status:** Production Ready A plugin marketplace for Claude Code containing: | Plugin | Description | Version | |--------|-------------|---------| -| `projman` | Sprint planning and project management with Gitea integration | 3.0.0 | +| `projman` | Sprint planning and project management with Gitea integration | 3.1.0 | | `git-flow` | Git workflow automation with smart commits and branch management | 1.0.0 | | `pr-review` | Multi-agent PR review with confidence scoring | 1.0.0 | | `clarity-assist` | Prompt optimization with ND-friendly accommodations | 1.0.0 | @@ -59,7 +59,7 @@ leo-claude-mktplace/ │ │ ├── .claude-plugin/plugin.json │ │ ├── .mcp.json │ │ ├── mcp-servers/gitea -> ../../../mcp-servers/gitea # SYMLINK -│ │ ├── commands/ # 12 commands (incl. setup) +│ │ ├── commands/ # 13 commands (incl. setup, debug) │ │ ├── hooks/ # SessionStart mismatch detection │ │ ├── agents/ # 4 agents │ │ └── skills/label-taxonomy/ @@ -255,4 +255,4 @@ See `docs/DEBUGGING-CHECKLIST.md` for systematic troubleshooting. --- -**Last Updated:** 2026-01-22 +**Last Updated:** 2026-01-23 diff --git a/README.md b/README.md index aa5ae0e..d9124de 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ AI-guided sprint planning with full Gitea integration. Transforms a proven 15-sp - Branch-aware security (development/staging/production) - Pre-sprint-close code quality review and test verification -**Commands:** `/sprint-plan`, `/sprint-start`, `/sprint-status`, `/sprint-close`, `/labels-sync`, `/initial-setup`, `/project-init`, `/project-sync`, `/review`, `/test-check`, `/test-gen` +**Commands:** `/sprint-plan`, `/sprint-start`, `/sprint-status`, `/sprint-close`, `/labels-sync`, `/initial-setup`, `/project-init`, `/project-sync`, `/review`, `/test-check`, `/test-gen`, `/debug-report`, `/debug-review` #### [git-flow](./plugins/git-flow/README.md) *NEW in v3.0.0* **Git Workflow Automation** @@ -257,6 +257,7 @@ leo-claude-mktplace/ | [COMMANDS-CHEATSHEET.md](./docs/COMMANDS-CHEATSHEET.md) | All commands quick reference | | [UPDATING.md](./docs/UPDATING.md) | Update guide for the marketplace | | [CANONICAL-PATHS.md](./docs/CANONICAL-PATHS.md) | Authoritative path reference | +| [DEBUGGING-CHECKLIST.md](./docs/DEBUGGING-CHECKLIST.md) | Systematic troubleshooting guide | | [CHANGELOG.md](./CHANGELOG.md) | Version history | ## License diff --git a/docs/CANONICAL-PATHS.md b/docs/CANONICAL-PATHS.md index b97e87a..3ef5dd3 100644 --- a/docs/CANONICAL-PATHS.md +++ b/docs/CANONICAL-PATHS.md @@ -2,7 +2,7 @@ **This file defines ALL valid paths in this repository. No exceptions. No inference. No assumptions.** -Last Updated: 2026-01-20 (v3.0.0) +Last Updated: 2026-01-23 (v3.1.2) --- @@ -17,10 +17,10 @@ leo-claude-mktplace/ ├── docs/ # All documentation │ ├── architecture/ # Draw.io diagrams and specs │ ├── CANONICAL-PATHS.md # This file - single source of truth +│ ├── COMMANDS-CHEATSHEET.md # All commands quick reference │ ├── CONFIGURATION.md # Centralized configuration guide │ ├── DEBUGGING-CHECKLIST.md # Systematic troubleshooting guide -│ ├── UPDATING.md # Update guide -│ └── workflows/ # Workflow documentation +│ └── UPDATING.md # Update guide ├── hooks/ # Shared hooks (if any) ├── mcp-servers/ # SHARED MCP servers (v3.0.0+) │ ├── gitea/ # Gitea MCP server @@ -156,7 +156,6 @@ The symlink target is relative: `../../../mcp-servers/{server}` | Type | Location | |------|----------| | Architecture diagrams | `docs/architecture/` | -| Workflow docs | `docs/workflows/` | | This file | `docs/CANONICAL-PATHS.md` | | Update guide | `docs/UPDATING.md` | | Configuration guide | `docs/CONFIGURATION.md` | diff --git a/plugins/cmdb-assistant/README.md b/plugins/cmdb-assistant/README.md index 6c2a4b2..394e587 100644 --- a/plugins/cmdb-assistant/README.md +++ b/plugins/cmdb-assistant/README.md @@ -111,6 +111,7 @@ cmdb-assistant/ │ └── plugin.json # Plugin manifest ├── .mcp.json # MCP server configuration ├── commands/ +│ ├── initial-setup.md # Setup wizard │ ├── cmdb-search.md # Search command │ ├── cmdb-device.md # Device management │ ├── cmdb-ip.md # IP management diff --git a/plugins/code-sentinel/.claude-plugin/plugin.json b/plugins/code-sentinel/.claude-plugin/plugin.json index fe3b34e..bf57dfa 100644 --- a/plugins/code-sentinel/.claude-plugin/plugin.json +++ b/plugins/code-sentinel/.claude-plugin/plugin.json @@ -9,5 +9,6 @@ "homepage": "https://gitea.hotserv.cloud/personal-projects/leo-claude-mktplace/src/branch/main/plugins/code-sentinel/README.md", "repository": "https://gitea.hotserv.cloud/personal-projects/leo-claude-mktplace.git", "license": "MIT", - "keywords": ["security", "refactoring", "code-quality", "static-analysis", "hooks"] + "keywords": ["security", "refactoring", "code-quality", "static-analysis", "hooks"], + "commands": ["./commands/"] } diff --git a/plugins/doc-guardian/.claude-plugin/plugin.json b/plugins/doc-guardian/.claude-plugin/plugin.json index 468e199..edec5f9 100644 --- a/plugins/doc-guardian/.claude-plugin/plugin.json +++ b/plugins/doc-guardian/.claude-plugin/plugin.json @@ -9,5 +9,6 @@ "homepage": "https://gitea.hotserv.cloud/personal-projects/leo-claude-mktplace/src/branch/main/plugins/doc-guardian/README.md", "repository": "https://gitea.hotserv.cloud/personal-projects/leo-claude-mktplace.git", "license": "MIT", - "keywords": ["documentation", "sync", "drift-detection", "automation", "hooks"] + "keywords": ["documentation", "sync", "drift-detection", "automation", "hooks"], + "commands": ["./commands/"] } diff --git a/plugins/pr-review/README.md b/plugins/pr-review/README.md index 3d17a88..d882dfc 100644 --- a/plugins/pr-review/README.md +++ b/plugins/pr-review/README.md @@ -13,6 +13,9 @@ pr-review conducts comprehensive code reviews using specialized agents for secur | `/pr-review ` | Full multi-agent review | | `/pr-summary ` | Quick summary without full review | | `/pr-findings ` | Filter findings by category/confidence | +| `/initial-setup` | Full interactive setup wizard | +| `/project-init` | Quick project setup (system already configured) | +| `/project-sync` | Sync configuration with current git remote | ## Review Agents diff --git a/plugins/project-hygiene/README.md b/plugins/project-hygiene/README.md index abf5a5b..826d252 100644 --- a/plugins/project-hygiene/README.md +++ b/plugins/project-hygiene/README.md @@ -20,7 +20,7 @@ claude plugin install project-hygiene ## How It Works -The plugin registers a `task-completed` hook that runs after Claude completes any task. It: +The plugin registers a `PostToolUse` hook (on Write and Edit tools) that runs after Claude modifies files. It: 1. Scans for and deletes known temporary file patterns 2. Removes temporary directories (`__pycache__`, `.pytest_cache`, etc.) diff --git a/plugins/projman/README.md b/plugins/projman/README.md index ccaaa76..264c493 100644 --- a/plugins/projman/README.md +++ b/plugins/projman/README.md @@ -13,7 +13,7 @@ Projman transforms a proven 15-sprint workflow into a distributable Claude Code - **Milestones** - Sprint milestone management and tracking - **Lessons Learned** - Systematic capture and search via Gitea Wiki - **Branch-Aware Security** - Prevents accidental changes on production branches -- **Three-Agent Model** - Planner, Orchestrator, and Executor agents +- **Four-Agent Model** - Planner, Orchestrator, Executor, and Code Reviewer agents - **CLI Tools Blocked** - All operations via MCP tools only (no `tea` or `gh`) ## Quick Start @@ -461,20 +461,8 @@ projman/ ├── .claude-plugin/ │ └── plugin.json # Plugin manifest ├── .mcp.json # MCP server configuration -├── mcp-servers/ # Bundled MCP server -│ └── gitea/ -│ ├── .venv/ -│ ├── requirements.txt -│ ├── mcp_server/ -│ │ ├── server.py -│ │ ├── gitea_client.py -│ │ └── tools/ -│ │ ├── issues.py -│ │ ├── labels.py -│ │ ├── wiki.py -│ │ ├── milestones.py -│ │ └── dependencies.py -│ └── tests/ +├── mcp-servers/ +│ └── gitea -> ../../../mcp-servers/gitea # SYMLINK to shared MCP server ├── commands/ # Slash commands │ ├── sprint-plan.md │ ├── sprint-start.md From 6c24bcbb9130dbb516a35cc82389ea372d6be044 Mon Sep 17 00:00:00 2001 From: lmiranda Date: Fri, 23 Jan 2026 11:48:54 -0500 Subject: [PATCH 2/6] fix(doc-guardian): replace prompt hook with command hook Prompt hooks are unreliable - Claude ignores instructions and generates verbose analysis despite explicit FORBIDDEN rules. Command hooks guarantee the exact output we want. - Add notify.sh script that only outputs for config file changes - Change hooks.json from prompt type to command type - Script exits silently for non-config files (no blocking) Fixes #110 Co-Authored-By: Claude Opus 4.5 --- plugins/doc-guardian/hooks/hooks.json | 4 ++-- plugins/doc-guardian/hooks/notify.sh | 23 +++++++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) create mode 100755 plugins/doc-guardian/hooks/notify.sh diff --git a/plugins/doc-guardian/hooks/hooks.json b/plugins/doc-guardian/hooks/hooks.json index d76de92..2a58ee4 100644 --- a/plugins/doc-guardian/hooks/hooks.json +++ b/plugins/doc-guardian/hooks/hooks.json @@ -5,8 +5,8 @@ "matcher": "Write|Edit|MultiEdit", "hooks": [ { - "type": "prompt", - "prompt": "STRICT OUTPUT RULES - FOLLOW EXACTLY:\n\n1. Your output MUST start with '[doc-guardian]' - NO EXCEPTIONS\n2. Output ONLY ONE of these two responses:\n - If file is in commands/, agents/, skills/, or hooks/ directories: '[doc-guardian] Config file modified. Run /doc-sync when ready.'\n - Otherwise: say absolutely nothing (empty response)\n\n3. FORBIDDEN - You must NEVER:\n - Analyze file contents\n - Report specific issues or errors\n - Use words like 'drift', 'inconsistent', 'error', 'warning', 'problem'\n - Output more than 15 words\n - Stop or block the workflow\n\n4. After your single-line output (or silence), IMMEDIATELY continue with the user's task\n\nThis is a passive notification only. Never analyze. Never block. Never elaborate." + "type": "command", + "command": "${CLAUDE_PLUGIN_ROOT}/hooks/notify.sh" } ] } diff --git a/plugins/doc-guardian/hooks/notify.sh b/plugins/doc-guardian/hooks/notify.sh new file mode 100755 index 0000000..f7c1bbc --- /dev/null +++ b/plugins/doc-guardian/hooks/notify.sh @@ -0,0 +1,23 @@ +#!/bin/bash +# doc-guardian notification hook +# Outputs a single notification for config file changes, nothing otherwise +# This is a command hook - guaranteed not to block workflow + +# Read tool input from stdin (JSON with file_path) +INPUT=$(cat) + +# Extract file_path from JSON input +FILE_PATH=$(echo "$INPUT" | grep -o '"file_path"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"file_path"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/') + +# If no file_path found, exit silently +if [ -z "$FILE_PATH" ]; then + exit 0 +fi + +# Check if file is in a config directory (commands/, agents/, skills/, hooks/) +if echo "$FILE_PATH" | grep -qE '/(commands|agents|skills|hooks)/'; then + echo "[doc-guardian] Config file modified. Run /doc-sync when ready." +fi + +# Exit silently for all other files (no output = no blocking) +exit 0 From bc136fab7e6a14d4e5be53b736c8529bb7565a10 Mon Sep 17 00:00:00 2001 From: lmiranda Date: Fri, 23 Jan 2026 11:51:31 -0500 Subject: [PATCH 3/6] docs: update CHANGELOG with actual fix for #110 Prompt hook approach didn't work - Claude ignores instructions. Real fix was switching to command hook type. Co-Authored-By: Claude Opus 4.5 --- CHANGELOG.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 123c057..c2d52fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,10 +14,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Resolves issue where commits to protected branches would fail on push ### Changed -- **doc-guardian:** Hook completely rewritten to be truly non-blocking - - Removed all analysis logic that could trigger workflow stoppage - - Now outputs only minimal notification for config file changes - - Forbidden words list prevents accidental blocking output +- **doc-guardian:** Hook switched from `prompt` type to `command` type + - Prompt hooks unreliable - Claude ignores explicit instructions + - New `notify.sh` bash script guarantees exact output behavior + - Only notifies for config file changes (commands/, agents/, skills/, hooks/) + - Silent exit for all other files - no blocking possible - **All hooks:** Stricter plugin prefix enforcement - All prompts now mandate `[plugin-name]` prefix with "NO EXCEPTIONS" rule - Simplified output formats with word limits @@ -25,8 +26,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Fixed - Protected branch workflow: Claude no longer commits directly to protected branches and then fails on push (fixes #109) -- doc-guardian hook no longer blocks workflow with drift analysis (fixes #110) -- Hook messages now consistently show plugin name prefix (fixes #110) +- doc-guardian hook no longer blocks workflow - switched to command hook that can't be overridden by model (fixes #110) --- From b017db83a158f4d9a93012f1aaf2dd6d41fff40c Mon Sep 17 00:00:00 2001 From: lmiranda Date: Fri, 23 Jan 2026 12:15:14 -0500 Subject: [PATCH 4/6] feat(gitea): add organization-level label creation - Add create_org_label() method to gitea_client.py for org-level labels - Add create_label_smart() to labels.py that auto-detects correct level - Register both tools in server.py - Update labels-sync.md to use create_label_smart Label level detection: - Organization: Type/*, Priority/*, Complexity/*, Effort/*, Risk/*, Source/*, Agent/* - Repository: Component/*, Tech/* Co-Authored-By: Claude Opus 4.5 --- mcp-servers/gitea/mcp_server/gitea_client.py | 34 ++++++++++ mcp-servers/gitea/mcp_server/server.py | 70 +++++++++++++++++++- mcp-servers/gitea/mcp_server/tools/labels.py | 67 +++++++++++++++++++ plugins/projman/commands/labels-sync.md | 12 +++- 4 files changed, 179 insertions(+), 4 deletions(-) diff --git a/mcp-servers/gitea/mcp_server/gitea_client.py b/mcp-servers/gitea/mcp_server/gitea_client.py index bf88e09..3e077af 100644 --- a/mcp-servers/gitea/mcp_server/gitea_client.py +++ b/mcp-servers/gitea/mcp_server/gitea_client.py @@ -621,6 +621,40 @@ class GiteaClient: response.raise_for_status() return response.json() + def create_org_label( + self, + org: str, + name: str, + color: str, + description: Optional[str] = None + ) -> Dict: + """ + Create a new label at the organization level. + + Organization labels are shared across all repositories in the org. + Use this for workflow labels (Type, Priority, Complexity, Effort, etc.) + + Args: + org: Organization name + name: Label name (e.g., 'Type/Bug', 'Priority/High') + color: Hex color code (with or without #) + description: Optional label description + + Returns: + Created label dictionary + """ + url = f"{self.base_url}/orgs/{org}/labels" + data = { + 'name': name, + 'color': color.lstrip('#') # Remove # if present + } + if description: + data['description'] = description + logger.info(f"Creating organization label '{name}' in {org}") + response = self.session.post(url, json=data) + response.raise_for_status() + return response.json() + # ======================================== # PULL REQUEST OPERATIONS # ======================================== diff --git a/mcp-servers/gitea/mcp_server/server.py b/mcp-servers/gitea/mcp_server/server.py index 3bfbba6..5b58190 100644 --- a/mcp-servers/gitea/mcp_server/server.py +++ b/mcp-servers/gitea/mcp_server/server.py @@ -622,13 +622,65 @@ class GiteaMCPServer: ), Tool( name="create_label", - description="Create a new label in the repository", + description="Create a new label in the repository (for repo-specific labels like Component/*, Tech/*)", inputSchema={ "type": "object", "properties": { "name": { "type": "string", - "description": "Label name" + "description": "Label name (e.g., 'Component/Backend', 'Tech/Python')" + }, + "color": { + "type": "string", + "description": "Label color (hex code)" + }, + "description": { + "type": "string", + "description": "Label description" + }, + "repo": { + "type": "string", + "description": "Repository name (owner/repo format)" + } + }, + "required": ["name", "color"] + } + ), + Tool( + name="create_org_label", + description="Create a new label at organization level (for workflow labels like Type/*, Priority/*, Complexity/*, Effort/*)", + inputSchema={ + "type": "object", + "properties": { + "org": { + "type": "string", + "description": "Organization name" + }, + "name": { + "type": "string", + "description": "Label name (e.g., 'Type/Bug', 'Priority/High')" + }, + "color": { + "type": "string", + "description": "Label color (hex code)" + }, + "description": { + "type": "string", + "description": "Label description" + } + }, + "required": ["org", "name", "color"] + } + ), + Tool( + name="create_label_smart", + description="Create a label at the appropriate level (org or repo) based on category. Org: Type/*, Priority/*, Complexity/*, Effort/*, Risk/*, Source/*, Agent/*. Repo: Component/*, Tech/*", + inputSchema={ + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Label name (e.g., 'Type/Bug', 'Component/Backend')" }, "color": { "type": "string", @@ -880,6 +932,20 @@ class GiteaMCPServer: arguments.get('description'), arguments.get('repo') ) + elif name == "create_org_label": + result = self.client.create_org_label( + arguments['org'], + arguments['name'], + arguments['color'], + arguments.get('description') + ) + elif name == "create_label_smart": + result = await self.label_tools.create_label_smart( + arguments['name'], + arguments['color'], + arguments.get('description'), + arguments.get('repo') + ) # Pull Request tools elif name == "list_pull_requests": result = await self.pr_tools.list_pull_requests(**arguments) diff --git a/mcp-servers/gitea/mcp_server/tools/labels.py b/mcp-servers/gitea/mcp_server/tools/labels.py index 23fde00..3fb87a7 100644 --- a/mcp-servers/gitea/mcp_server/tools/labels.py +++ b/mcp-servers/gitea/mcp_server/tools/labels.py @@ -259,3 +259,70 @@ class LabelTools: return lookup[category_lower][value_lower] return None + + # Organization-level label categories (workflow labels shared across repos) + ORG_LABEL_CATEGORIES = {'agent', 'complexity', 'effort', 'efforts', 'priority', 'risk', 'source', 'type'} + + # Repository-level label categories (project-specific labels) + REPO_LABEL_CATEGORIES = {'component', 'tech'} + + async def create_label_smart( + self, + name: str, + color: str, + description: Optional[str] = None, + repo: Optional[str] = None + ) -> Dict: + """ + Create a label at the appropriate level (org or repo) based on category. + + Organization labels: Agent, Complexity, Effort, Priority, Risk, Source, Type + Repository labels: Component, Tech + + Args: + name: Label name (e.g., 'Type/Bug', 'Component/Backend') + color: Hex color code + description: Optional label description + repo: Repository in 'owner/repo' format + + Returns: + Created label dictionary with 'level' key indicating where it was created + """ + loop = asyncio.get_event_loop() + + target_repo = repo or self.gitea.repo + if not target_repo or '/' not in target_repo: + raise ValueError("Use 'owner/repo' format (e.g. 'org/repo-name')") + + # Parse category from label name + category = None + if '/' in name: + category = name.split('/')[0].lower().rstrip('s') + elif ':' in name: + category = name.split(':')[0].strip().lower().rstrip('s') + + # Determine level + owner = target_repo.split('/')[0] + is_org = await loop.run_in_executor( + None, + lambda: self.gitea.is_org_repo(target_repo) + ) + + # If it's an org repo and the category is an org-level category, create at org level + if is_org and category in self.ORG_LABEL_CATEGORIES: + result = await loop.run_in_executor( + None, + lambda: self.gitea.create_org_label(owner, name, color, description) + ) + result['level'] = 'organization' + logger.info(f"Created organization label '{name}' in {owner}") + else: + # Create at repo level + result = await loop.run_in_executor( + None, + lambda: self.gitea.create_label(name, color, description, target_repo) + ) + result['level'] = 'repository' + logger.info(f"Created repository label '{name}' in {target_repo}") + + return result diff --git a/plugins/projman/commands/labels-sync.md b/plugins/projman/commands/labels-sync.md index eec495d..02b2004 100644 --- a/plugins/projman/commands/labels-sync.md +++ b/plugins/projman/commands/labels-sync.md @@ -62,12 +62,20 @@ Verify these required label categories exist: ### Step 6: Create Missing Labels (if any) -For each missing required label, call: +Use `create_label_smart` which automatically creates labels at the correct level: +- **Organization level**: Type/*, Priority/*, Complexity/*, Effort/*, Risk/*, Source/*, Agent/* +- **Repository level**: Component/*, Tech/* ``` -mcp__plugin_projman_gitea__create_label(repo=REPO_NAME, name="Type: Bug", color="d73a4a") +mcp__plugin_projman_gitea__create_label_smart(repo=REPO_NAME, name="Type/Bug", color="d73a4a") ``` +This automatically detects whether to create at org or repo level based on the category. + +**Alternative (explicit control):** +- Org labels: `create_org_label(org="org-name", name="Type/Bug", color="d73a4a")` +- Repo labels: `create_label(repo=REPO_NAME, name="Component/Backend", color="5319e7")` + Use the label format that matches existing labels in the repo (slash `/` or colon-space `: `). ### Step 7: Report Results From e1f133565540a842532db0bc443f6dadaa695487 Mon Sep 17 00:00:00 2001 From: lmiranda Date: Fri, 23 Jan 2026 12:16:06 -0500 Subject: [PATCH 5/6] fix(code-sentinel): replace prompt hook with command hook Same fix as doc-guardian - prompt hooks unreliable. Command hook guarantees exact behavior. - Add security-check.sh that skips config/doc files silently - Only checks code files for hardcoded secrets - Outputs with [code-sentinel] prefix Co-Authored-By: Claude Opus 4.5 --- plugins/code-sentinel/hooks/hooks.json | 4 +- plugins/code-sentinel/hooks/security-check.sh | 62 +++++++++++++++++++ 2 files changed, 64 insertions(+), 2 deletions(-) create mode 100755 plugins/code-sentinel/hooks/security-check.sh diff --git a/plugins/code-sentinel/hooks/hooks.json b/plugins/code-sentinel/hooks/hooks.json index d362360..e03ac7d 100644 --- a/plugins/code-sentinel/hooks/hooks.json +++ b/plugins/code-sentinel/hooks/hooks.json @@ -5,8 +5,8 @@ "matcher": "Write|Edit|MultiEdit", "hooks": [ { - "type": "prompt", - "prompt": "[code-sentinel] SECURITY CHECK - STRICT OUTPUT FORMAT:\n\nSKIP entirely for: *.md, *.json, *.yml, *.yaml, *.txt, README, CHANGELOG, LICENSE, docs/*\n\nFor CODE files (.py, .js, .ts, .sh, etc.), check for:\n- eval()/exec() with user input\n- SQL string concatenation\n- shell=True with user input\n- Hardcoded secrets (actual keys/passwords, not placeholders)\n- Pickle/marshal deserialization of untrusted data\n- innerHTML/dangerouslySetInnerHTML with user content\n\nOutput Format (MANDATORY):\n- Critical found: '[code-sentinel] BLOCKED: {10 words max}'\n- Warning found: '[code-sentinel] Warning: {brief reason}. Proceeding.'\n- Clean/config files: Say nothing (empty response)\n\nALL outputs MUST start with '[code-sentinel]' - NO EXCEPTIONS.\nNEVER block docs/config. NEVER do lengthy analysis." + "type": "command", + "command": "${CLAUDE_PLUGIN_ROOT}/hooks/security-check.sh" } ] } diff --git a/plugins/code-sentinel/hooks/security-check.sh b/plugins/code-sentinel/hooks/security-check.sh new file mode 100755 index 0000000..643c5d6 --- /dev/null +++ b/plugins/code-sentinel/hooks/security-check.sh @@ -0,0 +1,62 @@ +#!/bin/bash +# code-sentinel security check hook +# Checks for obvious security issues in code files, skips config/docs +# Command hook - guaranteed predictable behavior + +# Read tool input from stdin +INPUT=$(cat) + +# Extract file_path from JSON input +FILE_PATH=$(echo "$INPUT" | grep -o '"file_path"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"file_path"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/') + +# If no file_path, exit silently +if [ -z "$FILE_PATH" ]; then + exit 0 +fi + +# SKIP config/doc files entirely - exit silently +case "$FILE_PATH" in + *.md|*.json|*.yml|*.yaml|*.txt|*.toml|*.ini|*.cfg|*.conf) + exit 0 + ;; + */docs/*|*/README*|*/CHANGELOG*|*/LICENSE*) + exit 0 + ;; + */.claude/*|*/.github/*|*/.vscode/*) + exit 0 + ;; +esac + +# For code files, extract content to check +# For Edit tool: check new_string +# For Write tool: check content +CONTENT=$(echo "$INPUT" | grep -o '"new_string"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"new_string"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/') +if [ -z "$CONTENT" ]; then + CONTENT=$(echo "$INPUT" | grep -o '"content"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"content"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/') +fi + +# If no content to check, exit silently +if [ -z "$CONTENT" ]; then + exit 0 +fi + +# Check for hardcoded secrets patterns (obvious cases only) +if echo "$CONTENT" | grep -qiE '(api[_-]?key|api[_-]?secret|password|passwd|secret[_-]?key|auth[_-]?token)[[:space:]]*[=:][[:space:]]*["\x27][A-Za-z0-9+/=_-]{20,}["\x27]'; then + echo "[code-sentinel] BLOCKED: Hardcoded secret detected" + exit 1 +fi + +# Check for AWS keys pattern +if echo "$CONTENT" | grep -qE 'AKIA[A-Z0-9]{16}'; then + echo "[code-sentinel] BLOCKED: AWS access key detected" + exit 1 +fi + +# Check for private key headers +if echo "$CONTENT" | grep -qE '\-\-\-\-\-BEGIN (RSA |DSA |EC |OPENSSH )?PRIVATE KEY\-\-\-\-\-'; then + echo "[code-sentinel] BLOCKED: Private key detected" + exit 1 +fi + +# All other cases: exit silently (allow the edit) +exit 0 From da6e81260eccfaf823e1468be8e866def961223a Mon Sep 17 00:00:00 2001 From: lmiranda Date: Fri, 23 Jan 2026 12:18:40 -0500 Subject: [PATCH 6/6] fix(hooks): convert ALL hooks to command type with proper prefixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ALL hooks now use command type (bash scripts) instead of prompt type. Prompt hooks are unreliable - model ignores instructions. Changes: - projman: SessionStart → startup-check.sh with [projman] prefix - pr-review: SessionStart → startup-check.sh with [pr-review] prefix - project-hygiene: cleanup.sh now has [project-hygiene] prefix - doc-guardian: already fixed (notify.sh with [doc-guardian] prefix) - code-sentinel: already fixed (security-check.sh with [code-sentinel] prefix) All hook output now guaranteed to have plugin name prefix. Co-Authored-By: Claude Opus 4.5 --- plugins/pr-review/hooks/hooks.json | 4 +- plugins/pr-review/hooks/startup-check.sh | 30 ++ plugins/project-hygiene/hooks/cleanup.sh | 371 ++--------------------- plugins/projman/hooks/hooks.json | 4 +- plugins/projman/hooks/startup-check.sh | 30 ++ 5 files changed, 81 insertions(+), 358 deletions(-) create mode 100755 plugins/pr-review/hooks/startup-check.sh create mode 100755 plugins/projman/hooks/startup-check.sh diff --git a/plugins/pr-review/hooks/hooks.json b/plugins/pr-review/hooks/hooks.json index bf8e411..529b5ec 100644 --- a/plugins/pr-review/hooks/hooks.json +++ b/plugins/pr-review/hooks/hooks.json @@ -2,8 +2,8 @@ "hooks": { "SessionStart": [ { - "type": "prompt", - "prompt": "STARTUP CHECK - STRICT OUTPUT FORMAT:\n\nALL outputs MUST start with '[pr-review]' - NO EXCEPTIONS.\n\nPerform quick silent checks:\n1. If MCP venvs missing: '[pr-review] MCP venvs missing - run setup.sh from installed marketplace'\n2. If git remote != .env config: '[pr-review] Git remote mismatch - run /pr-review:project-sync'\n\nIf all checks pass: Say nothing (empty response)\n\nRules:\n- NEVER output without '[pr-review]' prefix\n- Keep messages under 20 words\n- Be quick and non-blocking\n- Do not read files or perform deep analysis" + "type": "command", + "command": "${CLAUDE_PLUGIN_ROOT}/hooks/startup-check.sh" } ] } diff --git a/plugins/pr-review/hooks/startup-check.sh b/plugins/pr-review/hooks/startup-check.sh new file mode 100755 index 0000000..89e184d --- /dev/null +++ b/plugins/pr-review/hooks/startup-check.sh @@ -0,0 +1,30 @@ +#!/bin/bash +# pr-review startup check hook +# Checks for common issues at session start +# All output MUST have [pr-review] prefix + +PREFIX="[pr-review]" + +# Check if MCP venv exists +PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$(dirname "$(dirname "$(realpath "$0")")")}" +VENV_PATH="$PLUGIN_ROOT/mcp-servers/gitea/.venv/bin/python" + +if [[ ! -f "$VENV_PATH" ]]; then + echo "$PREFIX MCP venvs missing - run setup.sh from installed marketplace" + exit 0 +fi + +# Check git remote vs .env config (only if .env exists) +if [[ -f ".env" ]]; then + CONFIGURED_REPO=$(grep -E "^GITEA_REPO=" .env 2>/dev/null | cut -d'=' -f2 | tr -d '"' || true) + if [[ -n "$CONFIGURED_REPO" ]]; then + CURRENT_REMOTE=$(git remote get-url origin 2>/dev/null | sed 's/.*[:/]\([^/]*\/[^.]*\).*/\1/' || true) + if [[ -n "$CURRENT_REMOTE" && "$CONFIGURED_REPO" != "$CURRENT_REMOTE" ]]; then + echo "$PREFIX Git remote mismatch - run /pr-review:project-sync" + exit 0 + fi + fi +fi + +# All checks passed - say nothing +exit 0 diff --git a/plugins/project-hygiene/hooks/cleanup.sh b/plugins/project-hygiene/hooks/cleanup.sh index 5206948..2aa5763 100755 --- a/plugins/project-hygiene/hooks/cleanup.sh +++ b/plugins/project-hygiene/hooks/cleanup.sh @@ -1,365 +1,28 @@ #!/bin/bash # project-hygiene cleanup hook -# Runs after task completion to clean up temp files and manage orphans +# Runs after file edits to clean up temp files +# All output MUST have [project-hygiene] prefix set -euo pipefail -# Configuration +PREFIX="[project-hygiene]" + +# Read tool input from stdin (discard - we don't need it for cleanup) +cat > /dev/null + PROJECT_ROOT="${PROJECT_ROOT:-.}" -PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$(dirname "$(dirname "$(realpath "$0")")")}" -CONFIG_FILE="${PROJECT_ROOT}/.hygiene.json" -LOG_DIR="${PROJECT_ROOT}/.dev/logs" -SCRATCH_DIR="${PROJECT_ROOT}/.dev/scratch" -LOG_FILE="${LOG_DIR}/hygiene-$(date +%Y%m%d-%H%M%S).log" - -# Default allowed root files (can be overridden by .hygiene.json) -DEFAULT_ALLOWED_ROOT=( - ".git" - ".gitignore" - ".gitattributes" - ".editorconfig" - ".env" - ".env.example" - ".env.local" - ".nvmrc" - ".node-version" - ".python-version" - ".ruby-version" - ".tool-versions" - "README.md" - "LICENSE" - "CHANGELOG.md" - "CONTRIBUTING.md" - "CLAUDE.md" - "package.json" - "package-lock.json" - "yarn.lock" - "pnpm-lock.yaml" - "Makefile" - "Dockerfile" - "docker-compose.yml" - "docker-compose.yaml" - "Cargo.toml" - "Cargo.lock" - "go.mod" - "go.sum" - "requirements.txt" - "setup.py" - "pyproject.toml" - "poetry.lock" - "Gemfile" - "Gemfile.lock" - "tsconfig.json" - "jsconfig.json" - ".eslintrc*" - ".prettierrc*" - "vite.config.*" - "webpack.config.*" - "rollup.config.*" - ".hygiene.json" -) - -# Temp file patterns to delete -TEMP_PATTERNS=( - "*.tmp" - "*.bak" - "*.swp" - "*.swo" - "*~" - ".DS_Store" - "Thumbs.db" - "*.log" - "*.orig" - "*.pyc" - "*.pyo" -) - -# Directory patterns to delete -TEMP_DIRS=( - "__pycache__" - ".pytest_cache" - ".mypy_cache" - ".ruff_cache" - "node_modules/.cache" - ".next/cache" - ".nuxt/.cache" - ".turbo" - "*.egg-info" - ".eggs" - "dist" - "build" -) - -# Orphan patterns to identify -ORPHAN_PATTERNS=( - "test_*.py" - "debug_*" - "*_backup.*" - "*_old.*" - "*_bak.*" - "*.backup" - "temp_*" - "tmp_*" -) - -# Initialize DELETED_COUNT=0 -WARNED_COUNT=0 -ORPHAN_COUNT=0 -MOVE_ORPHANS=false - -# Logging function -log() { - local msg="[$(date +%H:%M:%S)] $1" - echo "$msg" - if [[ -f "$LOG_FILE" ]]; then - echo "$msg" >> "$LOG_FILE" - fi -} - -log_action() { - local action="$1" - local target="$2" - log " $action: $target" -} - -# Load project-local config if exists -load_config() { - if [[ -f "$CONFIG_FILE" ]]; then - log "Loading config from $CONFIG_FILE" - - # Check if move_orphans is enabled - if command -v jq &>/dev/null; then - MOVE_ORPHANS=$(jq -r '.move_orphans // false' "$CONFIG_FILE" 2>/dev/null || echo "false") - - # Load additional allowed root files - local extra_allowed - extra_allowed=$(jq -r '.allowed_root_files // [] | .[]' "$CONFIG_FILE" 2>/dev/null || true) - if [[ -n "$extra_allowed" ]]; then - while IFS= read -r file; do - DEFAULT_ALLOWED_ROOT+=("$file") - done <<< "$extra_allowed" - fi - - # Load additional temp patterns - local extra_temp - extra_temp=$(jq -r '.temp_patterns // [] | .[]' "$CONFIG_FILE" 2>/dev/null || true) - if [[ -n "$extra_temp" ]]; then - while IFS= read -r pattern; do - TEMP_PATTERNS+=("$pattern") - done <<< "$extra_temp" - fi - - # Load ignore patterns (files to never touch) - IGNORE_PATTERNS=() - local ignore - ignore=$(jq -r '.ignore_patterns // [] | .[]' "$CONFIG_FILE" 2>/dev/null || true) - if [[ -n "$ignore" ]]; then - while IFS= read -r pattern; do - IGNORE_PATTERNS+=("$pattern") - done <<< "$ignore" - fi - else - log "Warning: jq not installed, using default config" - fi - fi -} - -# Check if file should be ignored -should_ignore() { - local file="$1" - local basename - basename=$(basename "$file") - - for pattern in "${IGNORE_PATTERNS[@]:-}"; do - if [[ "$basename" == $pattern ]] || [[ "$file" == $pattern ]]; then - return 0 - fi - done - return 1 -} - -# Check if file is in allowed root list -is_allowed_root() { - local file="$1" - local basename - basename=$(basename "$file") - - for allowed in "${DEFAULT_ALLOWED_ROOT[@]}"; do - # Support wildcards in allowed patterns - if [[ "$basename" == $allowed ]]; then - return 0 - fi - done - return 1 -} - -# Check if file matches orphan pattern -is_orphan() { - local file="$1" - local basename - basename=$(basename "$file") - - for pattern in "${ORPHAN_PATTERNS[@]}"; do - if [[ "$basename" == $pattern ]]; then - return 0 - fi - done - return 1 -} - -# Setup directories -setup_dirs() { - mkdir -p "$LOG_DIR" - if [[ "$MOVE_ORPHANS" == "true" ]]; then - mkdir -p "$SCRATCH_DIR" - fi - - # Start log file - echo "=== Project Hygiene Cleanup ===" > "$LOG_FILE" - echo "Started: $(date)" >> "$LOG_FILE" - echo "Project: $PROJECT_ROOT" >> "$LOG_FILE" - echo "" >> "$LOG_FILE" -} - -# Delete temp files -cleanup_temp_files() { - log "Cleaning temp files..." - - for pattern in "${TEMP_PATTERNS[@]}"; do - while IFS= read -r -d '' file; do - if should_ignore "$file"; then - continue - fi - rm -f "$file" - log_action "DELETED" "$file" - ((DELETED_COUNT++)) - done < <(find "$PROJECT_ROOT" -name "$pattern" -type f -print0 2>/dev/null || true) - done -} - -# Delete temp directories -cleanup_temp_dirs() { - log "Cleaning temp directories..." - - for pattern in "${TEMP_DIRS[@]}"; do - while IFS= read -r -d '' dir; do - if should_ignore "$dir"; then - continue - fi - rm -rf "$dir" - log_action "DELETED DIR" "$dir" - ((DELETED_COUNT++)) - done < <(find "$PROJECT_ROOT" -name "$pattern" -type d -print0 2>/dev/null || true) - done -} - -# Warn about unexpected root files -check_root_files() { - log "Checking root files..." - - local unexpected_files=() +# Silently delete temp files +for pattern in "*.tmp" "*.bak" "*.swp" "*~" ".DS_Store"; do while IFS= read -r -d '' file; do - local basename - basename=$(basename "$file") + rm -f "$file" 2>/dev/null && ((DELETED_COUNT++)) || true + done < <(find "$PROJECT_ROOT" -name "$pattern" -type f -print0 2>/dev/null || true) +done - # Skip directories - [[ -d "$file" ]] && continue +# Only output if we deleted something +if [[ $DELETED_COUNT -gt 0 ]]; then + echo "$PREFIX Cleaned $DELETED_COUNT temp files" +fi - # Skip if in allowed list - is_allowed_root "$basename" && continue - - # Skip if should be ignored - should_ignore "$basename" && continue - - unexpected_files+=("$basename") - log_action "WARNING" "Unexpected root file: $basename" - ((WARNED_COUNT++)) - done < <(find "$PROJECT_ROOT" -maxdepth 1 -print0 2>/dev/null || true) - - if [[ ${#unexpected_files[@]} -gt 0 ]]; then - log "" - log "⚠️ Unexpected files in project root:" - for f in "${unexpected_files[@]}"; do - log " - $f" - done - fi -} - -# Identify and handle orphaned files -handle_orphans() { - log "Checking for orphaned files..." - - local orphan_files=() - - for pattern in "${ORPHAN_PATTERNS[@]}"; do - while IFS= read -r -d '' file; do - if should_ignore "$file"; then - continue - fi - - orphan_files+=("$file") - - if [[ "$MOVE_ORPHANS" == "true" ]]; then - local dest="${SCRATCH_DIR}/$(basename "$file")" - # Handle duplicates - if [[ -f "$dest" ]]; then - dest="${SCRATCH_DIR}/$(date +%Y%m%d%H%M%S)_$(basename "$file")" - fi - mv "$file" "$dest" - log_action "MOVED" "$file -> $dest" - else - log_action "ORPHAN" "$file" - fi - ((ORPHAN_COUNT++)) - done < <(find "$PROJECT_ROOT" -name "$pattern" -type f -print0 2>/dev/null || true) - done - - if [[ ${#orphan_files[@]} -gt 0 && "$MOVE_ORPHANS" != "true" ]]; then - log "" - log "📦 Orphaned files found (enable move_orphans in .hygiene.json to auto-move):" - for f in "${orphan_files[@]}"; do - log " - $f" - done - fi -} - -# Summary -print_summary() { - log "" - log "=== Cleanup Summary ===" - log " Deleted: $DELETED_COUNT items" - log " Warnings: $WARNED_COUNT unexpected root files" - log " Orphans: $ORPHAN_COUNT files" - if [[ "$MOVE_ORPHANS" == "true" ]]; then - log " Orphans moved to: $SCRATCH_DIR" - fi - log " Log file: $LOG_FILE" - log "" -} - -# Main -main() { - cd "$PROJECT_ROOT" || exit 1 - - load_config - setup_dirs - - log "Starting project hygiene cleanup..." - log "" - - cleanup_temp_files - cleanup_temp_dirs - check_root_files - handle_orphans - - print_summary - - # Exit with warning code if issues found - if [[ $WARNED_COUNT -gt 0 || $ORPHAN_COUNT -gt 0 ]]; then - exit 0 # Still success, but logged warnings - fi -} - -main "$@" +exit 0 diff --git a/plugins/projman/hooks/hooks.json b/plugins/projman/hooks/hooks.json index 248fad5..529b5ec 100644 --- a/plugins/projman/hooks/hooks.json +++ b/plugins/projman/hooks/hooks.json @@ -2,8 +2,8 @@ "hooks": { "SessionStart": [ { - "type": "prompt", - "prompt": "STARTUP CHECK - STRICT OUTPUT FORMAT:\n\nALL outputs MUST start with '[projman]' - NO EXCEPTIONS.\n\nPerform quick silent checks:\n1. If MCP venvs missing: '[projman] MCP venvs missing - run setup.sh from installed marketplace'\n2. If git remote != .env config: '[projman] Git remote mismatch - run /project-sync'\n\nIf all checks pass: Say nothing (empty response)\n\nRules:\n- NEVER output without '[projman]' prefix\n- Keep messages under 20 words\n- Be quick and non-blocking\n- Do not read files or perform deep analysis" + "type": "command", + "command": "${CLAUDE_PLUGIN_ROOT}/hooks/startup-check.sh" } ] } diff --git a/plugins/projman/hooks/startup-check.sh b/plugins/projman/hooks/startup-check.sh new file mode 100755 index 0000000..3c795b5 --- /dev/null +++ b/plugins/projman/hooks/startup-check.sh @@ -0,0 +1,30 @@ +#!/bin/bash +# projman startup check hook +# Checks for common issues at session start +# All output MUST have [projman] prefix + +PREFIX="[projman]" + +# Check if MCP venv exists +PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$(dirname "$(dirname "$(realpath "$0")")")}" +VENV_PATH="$PLUGIN_ROOT/mcp-servers/gitea/.venv/bin/python" + +if [[ ! -f "$VENV_PATH" ]]; then + echo "$PREFIX MCP venvs missing - run setup.sh from installed marketplace" + exit 0 +fi + +# Check git remote vs .env config (only if .env exists) +if [[ -f ".env" ]]; then + CONFIGURED_REPO=$(grep -E "^GITEA_REPO=" .env 2>/dev/null | cut -d'=' -f2 | tr -d '"' || true) + if [[ -n "$CONFIGURED_REPO" ]]; then + CURRENT_REMOTE=$(git remote get-url origin 2>/dev/null | sed 's/.*[:/]\([^/]*\/[^.]*\).*/\1/' || true) + if [[ -n "$CURRENT_REMOTE" && "$CONFIGURED_REPO" != "$CURRENT_REMOTE" ]]; then + echo "$PREFIX Git remote mismatch - run /project-sync" + exit 0 + fi + fi +fi + +# All checks passed - say nothing +exit 0