mcp-ticketer 0.1.28__tar.gz → 0.1.29__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of mcp-ticketer might be problematic. Click here for more details.
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/CHANGELOG.md +35 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/PKG-INFO +1 -1
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/src/mcp_ticketer/__version__.py +1 -1
- mcp_ticketer-0.1.29/src/mcp_ticketer/cli/diagnostics.py +492 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/src/mcp_ticketer/cli/main.py +43 -0
- mcp_ticketer-0.1.29/src/mcp_ticketer/cli/simple_health.py +219 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/src/mcp_ticketer/mcp/server.py +217 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/src/mcp_ticketer.egg-info/PKG-INFO +1 -1
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/src/mcp_ticketer.egg-info/SOURCES.txt +2 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/.claude/agents/.dependency_cache +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/.claude/agents/.mpm_deployment_state +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/.claude/agents/agent-manager.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/.claude/agents/agentic-coder-optimizer.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/.claude/agents/api_qa.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/.claude/agents/clerk-ops.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/.claude/agents/code_analyzer.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/.claude/agents/content-agent.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/.claude/agents/dart_engineer.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/.claude/agents/data_engineer.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/.claude/agents/documentation.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/.claude/agents/engineer.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/.claude/agents/gcp_ops_agent.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/.claude/agents/golang_engineer.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/.claude/agents/imagemagick.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/.claude/agents/java_engineer.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/.claude/agents/local_ops_agent.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/.claude/agents/memory_manager.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/.claude/agents/nextjs_engineer.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/.claude/agents/ops.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/.claude/agents/php-engineer.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/.claude/agents/product_owner.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/.claude/agents/project_organizer.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/.claude/agents/prompt-engineer.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/.claude/agents/python_engineer.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/.claude/agents/qa.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/.claude/agents/react_engineer.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/.claude/agents/refactoring_engineer.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/.claude/agents/research.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/.claude/agents/ruby-engineer.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/.claude/agents/rust_engineer.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/.claude/agents/security.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/.claude/agents/ticketing.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/.claude/agents/typescript_engineer.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/.claude/agents/vercel_ops_agent.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/.claude/agents/version_control.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/.claude/agents/web_qa.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/.claude/agents/web_ui.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/.claude/mcp.json +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/.claude/mcp.local.json +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/.claude-mpm/config/project.json +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/.claude-mpm/mcp_auto_config_preference.json +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/.claude-mpm/memories/README.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/.claude-mpm/memories/agentic_coder_optimizer_memories.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/.claude-mpm/memories/documentation_memories.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/.claude-mpm/memories/engineer_memories.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/.claude-mpm/memories/ops_memories.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/.claude-mpm/memories/project_knowledge_memories.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/.claude-mpm/memories/qa_memories.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/.claude-mpm/memories/research_memories.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/.claude-mpm/memories/version-control_memories.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/.claude-mpm/memories/workflows_memories.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/.claude.json +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/.coveragerc +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/.env.example +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/.github/workflows/docs.yml +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/.github/workflows/publish.yml +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/.github/workflows/test.yml +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/.mcp/config.json +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/.pre-commit-config.yaml +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/.python-version +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/.readthedocs.yaml +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/CLAUDE.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/CLAUDE_DESKTOP_SETUP.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/CODEX_INTEGRATION.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/CODE_STRUCTURE.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/CONFIG_RESOLUTION_FIX.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/CONTRIBUTING.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/CREDENTIAL_VALIDATION_FIX.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/ENV_DISCOVERY_COMPLETE.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/FIX_SUMMARY.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/HIERARCHY_IMPLEMENTATION_SUMMARY.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/IMPLEMENTATION_SUMMARY.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/JIRA_SETUP.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/LICENSE +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/LINEAR_SETUP.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/MANIFEST.in +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/MCP_CONFIGURATION_TEST_REPORT.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/Makefile +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/OPTIMIZATION_SUMMARY.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/PROJECT_INITIALIZATION_SUMMARY.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/QUEUE_SYSTEM.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/QUICK_START.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/README.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/RELEASE.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/RELEASING.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/SECURITY_SCAN_REPORT_v0.1.24.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/TEST_COVERAGE_REPORT.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/TEST_REPORT.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/TEST_RESULTS_SUMMARY.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/VERIFICATION_RESULTS.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/claude-desktop-config.json +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/debug_search.py +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/debug_test.py +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/docs/ADAPTERS.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/docs/AI_CLIENT_INTEGRATION.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/docs/API_REFERENCE.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/docs/CONFIGURATION.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/docs/CONFIG_RESOLUTION_FLOW.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/docs/DEVELOPER_GUIDE.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/docs/ENV_DISCOVERY.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/docs/MCP_INTEGRATION.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/docs/MIGRATION_GUIDE.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/docs/PROJECT_CONFIG.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/docs/PR_INTEGRATION.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/docs/QUICK_START_ENV.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/docs/USER_GUIDE.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/docs/_archive/README.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/docs/_build/_sources/ADAPTERS.md.txt +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/docs/_build/_sources/API_REFERENCE.md.txt +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/docs/_build/_sources/CONFIGURATION.md.txt +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/docs/_build/_sources/DEVELOPER_GUIDE.md.txt +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/docs/_build/_sources/MCP_INTEGRATION.md.txt +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/docs/_build/_sources/MIGRATION_GUIDE.md.txt +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/docs/_build/_sources/USER_GUIDE.md.txt +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/docs/_build/_sources/adapters/github.md.txt +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/docs/_build/_sources/adapters.rst.txt +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/docs/_build/_sources/api.rst.txt +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/docs/_build/_sources/cli.rst.txt +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/docs/_build/_sources/development.rst.txt +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/docs/_build/_sources/examples.rst.txt +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/docs/_build/_sources/index.rst.txt +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/docs/_build/_sources/installation.rst.txt +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/docs/_build/_sources/prd/mcp-ticketer-prd.md.txt +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/docs/adapters/github.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/docs/adapters.rst +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/docs/api.rst +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/docs/cli.rst +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/docs/conf.py +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/docs/development.rst +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/docs/examples.rst +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/docs/index.rst +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/docs/installation.rst +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/docs/prd/mcp-ticketer-prd.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/docs/requirements.txt +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/install.sh +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/mcp-ticketer +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/mcp-ticketer-server +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/mcp_server.sh +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/pyproject.toml +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/pytest.ini +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/requirements-dev.txt +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/requirements.txt +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/scripts/README.md +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/scripts/manage_version.py +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/setup.cfg +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/setup.py +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/src/mcp_ticketer/__init__.py +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/src/mcp_ticketer/adapters/__init__.py +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/src/mcp_ticketer/adapters/aitrackdown.py +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/src/mcp_ticketer/adapters/github.py +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/src/mcp_ticketer/adapters/hybrid.py +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/src/mcp_ticketer/adapters/jira.py +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/src/mcp_ticketer/adapters/linear.py +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/src/mcp_ticketer/cache/__init__.py +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/src/mcp_ticketer/cache/memory.py +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/src/mcp_ticketer/cli/__init__.py +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/src/mcp_ticketer/cli/auggie_configure.py +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/src/mcp_ticketer/cli/codex_configure.py +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/src/mcp_ticketer/cli/configure.py +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/src/mcp_ticketer/cli/discover.py +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/src/mcp_ticketer/cli/gemini_configure.py +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/src/mcp_ticketer/cli/mcp_configure.py +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/src/mcp_ticketer/cli/migrate_config.py +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/src/mcp_ticketer/cli/queue_commands.py +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/src/mcp_ticketer/cli/utils.py +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/src/mcp_ticketer/core/__init__.py +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/src/mcp_ticketer/core/adapter.py +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/src/mcp_ticketer/core/config.py +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/src/mcp_ticketer/core/env_discovery.py +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/src/mcp_ticketer/core/http_client.py +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/src/mcp_ticketer/core/mappers.py +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/src/mcp_ticketer/core/models.py +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/src/mcp_ticketer/core/project_config.py +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/src/mcp_ticketer/core/registry.py +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/src/mcp_ticketer/mcp/__init__.py +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/src/mcp_ticketer/py.typed +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/src/mcp_ticketer/queue/__init__.py +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/src/mcp_ticketer/queue/__main__.py +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/src/mcp_ticketer/queue/health_monitor.py +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/src/mcp_ticketer/queue/manager.py +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/src/mcp_ticketer/queue/queue.py +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/src/mcp_ticketer/queue/run_worker.py +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/src/mcp_ticketer/queue/ticket_registry.py +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/src/mcp_ticketer/queue/worker.py +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/src/mcp_ticketer.egg-info/dependency_links.txt +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/src/mcp_ticketer.egg-info/entry_points.txt +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/src/mcp_ticketer.egg-info/not-zip-safe +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/src/mcp_ticketer.egg-info/requires.txt +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/src/mcp_ticketer.egg-info/top_level.txt +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/test-tickets/tickets/task-20250924002724.json +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/test_all_adapters.py +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/test_api_usage.py +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/test_basic.py +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/test_codex_config.py +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/test_comprehensive.py +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/test_config_resolution.py +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/test_credential_validation.py +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/test_error_handling.py +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/test_github.py +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/test_github_token.py +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/test_jira.py +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/test_linear.py +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/test_linear_native.py +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/test_linear_teams.py +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/test_mcp_server_qa.py +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/test_optimizations.py +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/test_performance.py +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/test_pr_functionality.py +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/test_queue_system.py +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/test_serve_config.py +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/test_set_command.sh +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/tests/adapters/__init__.py +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/tests/adapters/test_aitrackdown.py +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/tests/conftest.py +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/tests/core/test_env_discovery.py +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/tests/e2e/test_complete_workflow.py +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/tests/e2e/test_hierarchy_validation.py +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/tests/e2e/test_state_transitions.py +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/tests/test_base_adapter.py +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/tests/test_models.py +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/tests/test_queue.py +0 -0
- {mcp_ticketer-0.1.28 → mcp_ticketer-0.1.29}/tox.ini +0 -0
|
@@ -6,6 +6,41 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.1.29] - 2025-10-24
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- **Comprehensive Diagnostics System**: Complete health monitoring and self-diagnosis capabilities
|
|
13
|
+
- `mcp-ticketer health`: Quick system health check command
|
|
14
|
+
- `mcp-ticketer diagnose`: Comprehensive system diagnostics with detailed analysis
|
|
15
|
+
- `system_health` MCP tool: AI agents can perform quick health checks
|
|
16
|
+
- `system_diagnose` MCP tool: AI agents can run comprehensive diagnostics
|
|
17
|
+
- **Intelligent Fallback System**: Graceful degradation when dependencies are missing
|
|
18
|
+
- Simple health checks that work without heavy dependencies
|
|
19
|
+
- Automatic fallback to lightweight diagnostics when full system fails
|
|
20
|
+
- Import protection for missing optional dependencies
|
|
21
|
+
- **Multi-Component Analysis**:
|
|
22
|
+
- Configuration validation and adapter testing
|
|
23
|
+
- Queue system health monitoring with failure rate analysis
|
|
24
|
+
- Environment variable detection and validation
|
|
25
|
+
- Installation verification and version checking
|
|
26
|
+
- Performance metrics and response time analysis
|
|
27
|
+
- **Rich Output Formats**:
|
|
28
|
+
- Colorized terminal output with status indicators
|
|
29
|
+
- JSON export for automation and integration
|
|
30
|
+
- File output for reporting and analysis
|
|
31
|
+
- Progressive disclosure (health → diagnose)
|
|
32
|
+
- **Exit Code Standards**: Proper exit codes for CI/CD integration (0=healthy, 1=critical, 2=warnings)
|
|
33
|
+
|
|
34
|
+
### Enhanced
|
|
35
|
+
- **MCP Server**: Added two new diagnostic tools for AI agent integration
|
|
36
|
+
- **Error Handling**: Improved graceful handling of missing dependencies and failed components
|
|
37
|
+
- **User Experience**: Clear, actionable recommendations for resolving detected issues
|
|
38
|
+
|
|
39
|
+
### Fixed
|
|
40
|
+
- **System Visibility**: Addresses the "60% failure rate" issue by providing comprehensive system monitoring
|
|
41
|
+
- **AI Agent Troubleshooting**: AI agents can now self-diagnose and identify system issues
|
|
42
|
+
- **Dependency Resilience**: System remains functional even when optional dependencies are missing
|
|
43
|
+
|
|
9
44
|
### Added
|
|
10
45
|
- WebSocket support for real-time updates
|
|
11
46
|
- Custom ticket templates
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mcp-ticketer
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.29
|
|
4
4
|
Summary: Universal ticket management interface for AI agents with MCP support
|
|
5
5
|
Author-email: MCP Ticketer Team <support@mcp-ticketer.io>
|
|
6
6
|
Maintainer-email: MCP Ticketer Team <support@mcp-ticketer.io>
|
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
"""Comprehensive diagnostics and self-diagnosis functionality for MCP Ticketer."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import json
|
|
5
|
+
import logging
|
|
6
|
+
import os
|
|
7
|
+
import sys
|
|
8
|
+
from datetime import datetime, timedelta
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any, Dict, List, Optional, Tuple
|
|
11
|
+
|
|
12
|
+
import typer
|
|
13
|
+
from rich.console import Console
|
|
14
|
+
from rich.panel import Panel
|
|
15
|
+
from rich.table import Table
|
|
16
|
+
from rich.text import Text
|
|
17
|
+
|
|
18
|
+
try:
|
|
19
|
+
from ..core.config import get_config
|
|
20
|
+
except ImportError:
|
|
21
|
+
# Fallback for missing dependencies
|
|
22
|
+
def get_config():
|
|
23
|
+
raise ImportError("Configuration system not available")
|
|
24
|
+
|
|
25
|
+
try:
|
|
26
|
+
from ..core.registry import AdapterRegistry
|
|
27
|
+
except ImportError:
|
|
28
|
+
AdapterRegistry = None
|
|
29
|
+
|
|
30
|
+
try:
|
|
31
|
+
from ..queue.manager import QueueManager
|
|
32
|
+
except ImportError:
|
|
33
|
+
QueueManager = None
|
|
34
|
+
|
|
35
|
+
console = Console()
|
|
36
|
+
logger = logging.getLogger(__name__)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class SystemDiagnostics:
|
|
40
|
+
"""Comprehensive system diagnostics and health reporting."""
|
|
41
|
+
|
|
42
|
+
def __init__(self):
|
|
43
|
+
try:
|
|
44
|
+
self.config = get_config()
|
|
45
|
+
except Exception as e:
|
|
46
|
+
self.config = None
|
|
47
|
+
console.print(f"⚠️ Could not load configuration: {e}")
|
|
48
|
+
|
|
49
|
+
try:
|
|
50
|
+
self.queue_manager = QueueManager() if QueueManager else None
|
|
51
|
+
except Exception as e:
|
|
52
|
+
self.queue_manager = None
|
|
53
|
+
console.print(f"⚠️ Could not initialize queue manager: {e}")
|
|
54
|
+
|
|
55
|
+
self.issues = []
|
|
56
|
+
self.warnings = []
|
|
57
|
+
self.successes = []
|
|
58
|
+
|
|
59
|
+
async def run_full_diagnosis(self) -> Dict[str, Any]:
|
|
60
|
+
"""Run complete system diagnosis and return detailed report."""
|
|
61
|
+
console.print("\n🔍 [bold blue]MCP Ticketer System Diagnosis[/bold blue]")
|
|
62
|
+
console.print("=" * 60)
|
|
63
|
+
|
|
64
|
+
report = {
|
|
65
|
+
"timestamp": datetime.now().isoformat(),
|
|
66
|
+
"version": self._get_version(),
|
|
67
|
+
"system_info": self._get_system_info(),
|
|
68
|
+
"configuration": await self._diagnose_configuration(),
|
|
69
|
+
"adapters": await self._diagnose_adapters(),
|
|
70
|
+
"queue_system": await self._diagnose_queue_system(),
|
|
71
|
+
"recent_logs": await self._analyze_recent_logs(),
|
|
72
|
+
"performance": await self._analyze_performance(),
|
|
73
|
+
"recommendations": self._generate_recommendations(),
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
self._display_diagnosis_summary(report)
|
|
77
|
+
return report
|
|
78
|
+
|
|
79
|
+
def _get_version(self) -> str:
|
|
80
|
+
"""Get current version information."""
|
|
81
|
+
try:
|
|
82
|
+
from ..__version__ import __version__
|
|
83
|
+
return __version__
|
|
84
|
+
except ImportError:
|
|
85
|
+
return "unknown"
|
|
86
|
+
|
|
87
|
+
def _get_system_info(self) -> Dict[str, Any]:
|
|
88
|
+
"""Gather system information."""
|
|
89
|
+
return {
|
|
90
|
+
"python_version": sys.version,
|
|
91
|
+
"platform": sys.platform,
|
|
92
|
+
"working_directory": str(Path.cwd()),
|
|
93
|
+
"config_path": str(self.config.config_file) if hasattr(self.config, 'config_file') else "unknown",
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async def _diagnose_configuration(self) -> Dict[str, Any]:
|
|
97
|
+
"""Diagnose configuration issues."""
|
|
98
|
+
console.print("\n📋 [yellow]Configuration Analysis[/yellow]")
|
|
99
|
+
|
|
100
|
+
config_status = {
|
|
101
|
+
"status": "healthy",
|
|
102
|
+
"adapters_configured": 0,
|
|
103
|
+
"default_adapter": None,
|
|
104
|
+
"issues": [],
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if not self.config:
|
|
108
|
+
issue = "Configuration system not available"
|
|
109
|
+
config_status["issues"].append(issue)
|
|
110
|
+
config_status["status"] = "critical"
|
|
111
|
+
self.issues.append(issue)
|
|
112
|
+
console.print(f"❌ {issue}")
|
|
113
|
+
return config_status
|
|
114
|
+
|
|
115
|
+
try:
|
|
116
|
+
# Check adapter configurations
|
|
117
|
+
adapters = self.config.get_enabled_adapters()
|
|
118
|
+
config_status["adapters_configured"] = len(adapters)
|
|
119
|
+
config_status["default_adapter"] = self.config.default_adapter
|
|
120
|
+
|
|
121
|
+
if not adapters:
|
|
122
|
+
issue = "No adapters configured"
|
|
123
|
+
config_status["issues"].append(issue)
|
|
124
|
+
config_status["status"] = "critical"
|
|
125
|
+
self.issues.append(issue)
|
|
126
|
+
console.print(f"❌ {issue}")
|
|
127
|
+
else:
|
|
128
|
+
console.print(f"✅ {len(adapters)} adapter(s) configured")
|
|
129
|
+
|
|
130
|
+
# Check each adapter configuration
|
|
131
|
+
for name, adapter_config in adapters.items():
|
|
132
|
+
try:
|
|
133
|
+
adapter_class = AdapterRegistry.get_adapter(adapter_config.type.value)
|
|
134
|
+
adapter = adapter_class(adapter_config.dict())
|
|
135
|
+
|
|
136
|
+
# Test adapter validation if available
|
|
137
|
+
if hasattr(adapter, 'validate_credentials'):
|
|
138
|
+
is_valid, error = adapter.validate_credentials()
|
|
139
|
+
if is_valid:
|
|
140
|
+
console.print(f"✅ {name}: credentials valid")
|
|
141
|
+
self.successes.append(f"{name} adapter configured correctly")
|
|
142
|
+
else:
|
|
143
|
+
issue = f"{name}: credential validation failed - {error}"
|
|
144
|
+
config_status["issues"].append(issue)
|
|
145
|
+
self.warnings.append(issue)
|
|
146
|
+
console.print(f"⚠️ {issue}")
|
|
147
|
+
else:
|
|
148
|
+
console.print(f"ℹ️ {name}: no credential validation available")
|
|
149
|
+
|
|
150
|
+
except Exception as e:
|
|
151
|
+
issue = f"{name}: configuration error - {str(e)}"
|
|
152
|
+
config_status["issues"].append(issue)
|
|
153
|
+
self.issues.append(issue)
|
|
154
|
+
console.print(f"❌ {issue}")
|
|
155
|
+
|
|
156
|
+
except Exception as e:
|
|
157
|
+
issue = f"Configuration loading failed: {str(e)}"
|
|
158
|
+
config_status["issues"].append(issue)
|
|
159
|
+
config_status["status"] = "critical"
|
|
160
|
+
self.issues.append(issue)
|
|
161
|
+
console.print(f"❌ {issue}")
|
|
162
|
+
|
|
163
|
+
return config_status
|
|
164
|
+
|
|
165
|
+
async def _diagnose_adapters(self) -> Dict[str, Any]:
|
|
166
|
+
"""Diagnose adapter functionality."""
|
|
167
|
+
console.print("\n🔌 [yellow]Adapter Diagnosis[/yellow]")
|
|
168
|
+
|
|
169
|
+
adapter_status = {
|
|
170
|
+
"total_adapters": 0,
|
|
171
|
+
"healthy_adapters": 0,
|
|
172
|
+
"failed_adapters": 0,
|
|
173
|
+
"adapter_details": {},
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
try:
|
|
177
|
+
adapters = self.config.get_enabled_adapters()
|
|
178
|
+
adapter_status["total_adapters"] = len(adapters)
|
|
179
|
+
|
|
180
|
+
for name, adapter_config in adapters.items():
|
|
181
|
+
details = {
|
|
182
|
+
"type": adapter_config.type.value,
|
|
183
|
+
"status": "unknown",
|
|
184
|
+
"last_test": None,
|
|
185
|
+
"error": None,
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
try:
|
|
189
|
+
adapter_class = AdapterRegistry.get_adapter(adapter_config.type.value)
|
|
190
|
+
adapter = adapter_class(adapter_config.dict())
|
|
191
|
+
|
|
192
|
+
# Test basic adapter functionality
|
|
193
|
+
test_start = datetime.now()
|
|
194
|
+
|
|
195
|
+
# Try to list tickets (non-destructive test)
|
|
196
|
+
try:
|
|
197
|
+
await adapter.list(limit=1)
|
|
198
|
+
details["status"] = "healthy"
|
|
199
|
+
details["last_test"] = test_start.isoformat()
|
|
200
|
+
adapter_status["healthy_adapters"] += 1
|
|
201
|
+
console.print(f"✅ {name}: operational")
|
|
202
|
+
except Exception as e:
|
|
203
|
+
details["status"] = "failed"
|
|
204
|
+
details["error"] = str(e)
|
|
205
|
+
adapter_status["failed_adapters"] += 1
|
|
206
|
+
issue = f"{name}: functionality test failed - {str(e)}"
|
|
207
|
+
self.issues.append(issue)
|
|
208
|
+
console.print(f"❌ {issue}")
|
|
209
|
+
|
|
210
|
+
except Exception as e:
|
|
211
|
+
details["status"] = "failed"
|
|
212
|
+
details["error"] = str(e)
|
|
213
|
+
adapter_status["failed_adapters"] += 1
|
|
214
|
+
issue = f"{name}: initialization failed - {str(e)}"
|
|
215
|
+
self.issues.append(issue)
|
|
216
|
+
console.print(f"❌ {issue}")
|
|
217
|
+
|
|
218
|
+
adapter_status["adapter_details"][name] = details
|
|
219
|
+
|
|
220
|
+
except Exception as e:
|
|
221
|
+
issue = f"Adapter diagnosis failed: {str(e)}"
|
|
222
|
+
self.issues.append(issue)
|
|
223
|
+
console.print(f"❌ {issue}")
|
|
224
|
+
|
|
225
|
+
return adapter_status
|
|
226
|
+
|
|
227
|
+
async def _diagnose_queue_system(self) -> Dict[str, Any]:
|
|
228
|
+
"""Diagnose queue system health."""
|
|
229
|
+
console.print("\n⚡ [yellow]Queue System Diagnosis[/yellow]")
|
|
230
|
+
|
|
231
|
+
queue_status = {
|
|
232
|
+
"worker_running": False,
|
|
233
|
+
"worker_pid": None,
|
|
234
|
+
"queue_stats": {},
|
|
235
|
+
"recent_failures": [],
|
|
236
|
+
"failure_rate": 0.0,
|
|
237
|
+
"health_score": 0,
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
try:
|
|
241
|
+
# Check worker status
|
|
242
|
+
worker_status = self.queue_manager.get_worker_status()
|
|
243
|
+
queue_status["worker_running"] = worker_status.get("running", False)
|
|
244
|
+
queue_status["worker_pid"] = worker_status.get("pid")
|
|
245
|
+
|
|
246
|
+
if queue_status["worker_running"]:
|
|
247
|
+
console.print(f"✅ Queue worker running (PID: {queue_status['worker_pid']})")
|
|
248
|
+
self.successes.append("Queue worker is running")
|
|
249
|
+
else:
|
|
250
|
+
issue = "Queue worker not running"
|
|
251
|
+
self.issues.append(issue)
|
|
252
|
+
console.print(f"❌ {issue}")
|
|
253
|
+
|
|
254
|
+
# Get queue statistics
|
|
255
|
+
stats = self.queue_manager.get_queue_stats()
|
|
256
|
+
queue_status["queue_stats"] = stats
|
|
257
|
+
|
|
258
|
+
total_items = stats.get("total", 0)
|
|
259
|
+
failed_items = stats.get("failed", 0)
|
|
260
|
+
|
|
261
|
+
if total_items > 0:
|
|
262
|
+
failure_rate = (failed_items / total_items) * 100
|
|
263
|
+
queue_status["failure_rate"] = failure_rate
|
|
264
|
+
|
|
265
|
+
if failure_rate > 50:
|
|
266
|
+
issue = f"High failure rate: {failure_rate:.1f}% ({failed_items}/{total_items})"
|
|
267
|
+
self.issues.append(issue)
|
|
268
|
+
console.print(f"❌ {issue}")
|
|
269
|
+
elif failure_rate > 20:
|
|
270
|
+
warning = f"Elevated failure rate: {failure_rate:.1f}% ({failed_items}/{total_items})"
|
|
271
|
+
self.warnings.append(warning)
|
|
272
|
+
console.print(f"⚠️ {warning}")
|
|
273
|
+
else:
|
|
274
|
+
console.print(f"✅ Queue failure rate: {failure_rate:.1f}% ({failed_items}/{total_items})")
|
|
275
|
+
|
|
276
|
+
# Calculate health score
|
|
277
|
+
health_score = 100
|
|
278
|
+
if not queue_status["worker_running"]:
|
|
279
|
+
health_score -= 50
|
|
280
|
+
health_score -= min(queue_status["failure_rate"], 50)
|
|
281
|
+
queue_status["health_score"] = max(0, health_score)
|
|
282
|
+
|
|
283
|
+
console.print(f"📊 Queue health score: {queue_status['health_score']}/100")
|
|
284
|
+
|
|
285
|
+
except Exception as e:
|
|
286
|
+
issue = f"Queue system diagnosis failed: {str(e)}"
|
|
287
|
+
self.issues.append(issue)
|
|
288
|
+
console.print(f"❌ {issue}")
|
|
289
|
+
|
|
290
|
+
return queue_status
|
|
291
|
+
|
|
292
|
+
async def _analyze_recent_logs(self) -> Dict[str, Any]:
|
|
293
|
+
"""Analyze recent log entries for issues."""
|
|
294
|
+
console.print("\n📝 [yellow]Recent Log Analysis[/yellow]")
|
|
295
|
+
|
|
296
|
+
log_analysis = {
|
|
297
|
+
"log_files_found": [],
|
|
298
|
+
"recent_errors": [],
|
|
299
|
+
"recent_warnings": [],
|
|
300
|
+
"patterns": {},
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
try:
|
|
304
|
+
# Look for common log locations
|
|
305
|
+
log_paths = [
|
|
306
|
+
Path.home() / ".mcp-ticketer" / "logs",
|
|
307
|
+
Path.cwd() / ".mcp-ticketer" / "logs",
|
|
308
|
+
Path("/var/log/mcp-ticketer"),
|
|
309
|
+
]
|
|
310
|
+
|
|
311
|
+
for log_path in log_paths:
|
|
312
|
+
if log_path.exists():
|
|
313
|
+
log_analysis["log_files_found"].append(str(log_path))
|
|
314
|
+
await self._analyze_log_directory(log_path, log_analysis)
|
|
315
|
+
|
|
316
|
+
if not log_analysis["log_files_found"]:
|
|
317
|
+
console.print("ℹ️ No log files found in standard locations")
|
|
318
|
+
else:
|
|
319
|
+
console.print(f"✅ Found logs in {len(log_analysis['log_files_found'])} location(s)")
|
|
320
|
+
|
|
321
|
+
except Exception as e:
|
|
322
|
+
issue = f"Log analysis failed: {str(e)}"
|
|
323
|
+
self.issues.append(issue)
|
|
324
|
+
console.print(f"❌ {issue}")
|
|
325
|
+
|
|
326
|
+
return log_analysis
|
|
327
|
+
|
|
328
|
+
async def _analyze_log_directory(self, log_path: Path, log_analysis: Dict[str, Any]):
|
|
329
|
+
"""Analyze logs in a specific directory."""
|
|
330
|
+
try:
|
|
331
|
+
for log_file in log_path.glob("*.log"):
|
|
332
|
+
if log_file.stat().st_mtime > (datetime.now() - timedelta(hours=24)).timestamp():
|
|
333
|
+
await self._parse_log_file(log_file, log_analysis)
|
|
334
|
+
except Exception as e:
|
|
335
|
+
self.warnings.append(f"Could not analyze logs in {log_path}: {str(e)}")
|
|
336
|
+
|
|
337
|
+
async def _parse_log_file(self, log_file: Path, log_analysis: Dict[str, Any]):
|
|
338
|
+
"""Parse individual log file for issues."""
|
|
339
|
+
try:
|
|
340
|
+
with open(log_file, 'r') as f:
|
|
341
|
+
lines = f.readlines()[-100:] # Last 100 lines
|
|
342
|
+
|
|
343
|
+
for line in lines:
|
|
344
|
+
if "ERROR" in line:
|
|
345
|
+
log_analysis["recent_errors"].append(line.strip())
|
|
346
|
+
elif "WARNING" in line:
|
|
347
|
+
log_analysis["recent_warnings"].append(line.strip())
|
|
348
|
+
|
|
349
|
+
except Exception as e:
|
|
350
|
+
self.warnings.append(f"Could not parse {log_file}: {str(e)}")
|
|
351
|
+
|
|
352
|
+
async def _analyze_performance(self) -> Dict[str, Any]:
|
|
353
|
+
"""Analyze system performance metrics."""
|
|
354
|
+
console.print("\n⚡ [yellow]Performance Analysis[/yellow]")
|
|
355
|
+
|
|
356
|
+
performance = {
|
|
357
|
+
"response_times": {},
|
|
358
|
+
"throughput": {},
|
|
359
|
+
"resource_usage": {},
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
try:
|
|
363
|
+
# Test basic operations performance
|
|
364
|
+
start_time = datetime.now()
|
|
365
|
+
|
|
366
|
+
# Test configuration loading
|
|
367
|
+
config_start = datetime.now()
|
|
368
|
+
_ = get_config()
|
|
369
|
+
config_time = (datetime.now() - config_start).total_seconds()
|
|
370
|
+
performance["response_times"]["config_load"] = config_time
|
|
371
|
+
|
|
372
|
+
if config_time > 1.0:
|
|
373
|
+
self.warnings.append(f"Slow configuration loading: {config_time:.2f}s")
|
|
374
|
+
|
|
375
|
+
console.print(f"📊 Configuration load time: {config_time:.3f}s")
|
|
376
|
+
|
|
377
|
+
except Exception as e:
|
|
378
|
+
issue = f"Performance analysis failed: {str(e)}"
|
|
379
|
+
self.issues.append(issue)
|
|
380
|
+
console.print(f"❌ {issue}")
|
|
381
|
+
|
|
382
|
+
return performance
|
|
383
|
+
|
|
384
|
+
def _generate_recommendations(self) -> List[str]:
|
|
385
|
+
"""Generate actionable recommendations based on diagnosis."""
|
|
386
|
+
recommendations = []
|
|
387
|
+
|
|
388
|
+
if self.issues:
|
|
389
|
+
recommendations.append("🚨 Critical issues detected - immediate attention required")
|
|
390
|
+
|
|
391
|
+
if any("Queue worker not running" in issue for issue in self.issues):
|
|
392
|
+
recommendations.append("• Restart queue worker: mcp-ticketer queue worker restart")
|
|
393
|
+
|
|
394
|
+
if any("failure rate" in issue.lower() for issue in self.issues):
|
|
395
|
+
recommendations.append("• Check queue system logs for error patterns")
|
|
396
|
+
recommendations.append("• Consider clearing failed queue items: mcp-ticketer queue clear --failed")
|
|
397
|
+
|
|
398
|
+
if any("No adapters configured" in issue for issue in self.issues):
|
|
399
|
+
recommendations.append("• Configure at least one adapter: mcp-ticketer init-aitrackdown")
|
|
400
|
+
|
|
401
|
+
if self.warnings:
|
|
402
|
+
recommendations.append("⚠️ Warnings detected - monitoring recommended")
|
|
403
|
+
|
|
404
|
+
if not self.issues and not self.warnings:
|
|
405
|
+
recommendations.append("✅ System appears healthy - no immediate action required")
|
|
406
|
+
|
|
407
|
+
return recommendations
|
|
408
|
+
|
|
409
|
+
def _display_diagnosis_summary(self, report: Dict[str, Any]):
|
|
410
|
+
"""Display a comprehensive diagnosis summary."""
|
|
411
|
+
console.print("\n" + "=" * 60)
|
|
412
|
+
console.print("📋 [bold green]DIAGNOSIS SUMMARY[/bold green]")
|
|
413
|
+
console.print("=" * 60)
|
|
414
|
+
|
|
415
|
+
# Overall health status
|
|
416
|
+
if self.issues:
|
|
417
|
+
status_color = "red"
|
|
418
|
+
status_text = "CRITICAL"
|
|
419
|
+
status_icon = "🚨"
|
|
420
|
+
elif self.warnings:
|
|
421
|
+
status_color = "yellow"
|
|
422
|
+
status_text = "WARNING"
|
|
423
|
+
status_icon = "⚠️"
|
|
424
|
+
else:
|
|
425
|
+
status_color = "green"
|
|
426
|
+
status_text = "HEALTHY"
|
|
427
|
+
status_icon = "✅"
|
|
428
|
+
|
|
429
|
+
console.print(f"\n{status_icon} [bold {status_color}]System Status: {status_text}[/bold {status_color}]")
|
|
430
|
+
|
|
431
|
+
# Statistics
|
|
432
|
+
stats_table = Table(show_header=True, header_style="bold blue")
|
|
433
|
+
stats_table.add_column("Component")
|
|
434
|
+
stats_table.add_column("Status")
|
|
435
|
+
stats_table.add_column("Details")
|
|
436
|
+
|
|
437
|
+
# Add component statuses
|
|
438
|
+
config_status = "✅ OK" if not any("configuration" in issue.lower() for issue in self.issues) else "❌ FAILED"
|
|
439
|
+
stats_table.add_row("Configuration", config_status, f"{report['configuration']['adapters_configured']} adapters")
|
|
440
|
+
|
|
441
|
+
queue_health = report['queue_system']['health_score']
|
|
442
|
+
queue_status = "✅ OK" if queue_health > 80 else "⚠️ DEGRADED" if queue_health > 50 else "❌ FAILED"
|
|
443
|
+
stats_table.add_row("Queue System", queue_status, f"{queue_health}/100 health score")
|
|
444
|
+
|
|
445
|
+
adapter_stats = report['adapters']
|
|
446
|
+
adapter_status = "✅ OK" if adapter_stats['failed_adapters'] == 0 else "❌ FAILED"
|
|
447
|
+
stats_table.add_row("Adapters", adapter_status, f"{adapter_stats['healthy_adapters']}/{adapter_stats['total_adapters']} healthy")
|
|
448
|
+
|
|
449
|
+
console.print(stats_table)
|
|
450
|
+
|
|
451
|
+
# Issues and recommendations
|
|
452
|
+
if self.issues:
|
|
453
|
+
console.print(f"\n🚨 [bold red]Critical Issues ({len(self.issues)}):[/bold red]")
|
|
454
|
+
for issue in self.issues:
|
|
455
|
+
console.print(f" • {issue}")
|
|
456
|
+
|
|
457
|
+
if self.warnings:
|
|
458
|
+
console.print(f"\n⚠️ [bold yellow]Warnings ({len(self.warnings)}):[/bold yellow]")
|
|
459
|
+
for warning in self.warnings:
|
|
460
|
+
console.print(f" • {warning}")
|
|
461
|
+
|
|
462
|
+
if report['recommendations']:
|
|
463
|
+
console.print(f"\n💡 [bold blue]Recommendations:[/bold blue]")
|
|
464
|
+
for rec in report['recommendations']:
|
|
465
|
+
console.print(f" {rec}")
|
|
466
|
+
|
|
467
|
+
console.print(f"\n📊 [bold]Summary:[/bold] {len(self.successes)} successes, {len(self.warnings)} warnings, {len(self.issues)} critical issues")
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
async def run_diagnostics(
|
|
471
|
+
output_file: Optional[str] = None,
|
|
472
|
+
json_output: bool = False,
|
|
473
|
+
) -> None:
|
|
474
|
+
"""Run comprehensive system diagnostics."""
|
|
475
|
+
diagnostics = SystemDiagnostics()
|
|
476
|
+
report = await diagnostics.run_full_diagnosis()
|
|
477
|
+
|
|
478
|
+
if output_file:
|
|
479
|
+
with open(output_file, 'w') as f:
|
|
480
|
+
json.dump(report, f, indent=2)
|
|
481
|
+
console.print(f"\n📄 Full report saved to: {output_file}")
|
|
482
|
+
|
|
483
|
+
if json_output:
|
|
484
|
+
console.print("\n" + json.dumps(report, indent=2))
|
|
485
|
+
|
|
486
|
+
# Return exit code based on issues
|
|
487
|
+
if diagnostics.issues:
|
|
488
|
+
raise typer.Exit(1) # Critical issues found
|
|
489
|
+
elif diagnostics.warnings:
|
|
490
|
+
raise typer.Exit(2) # Warnings found
|
|
491
|
+
else:
|
|
492
|
+
raise typer.Exit(0) # All good
|
|
@@ -22,6 +22,7 @@ from ..queue.ticket_registry import TicketRegistry
|
|
|
22
22
|
# Import adapters module to trigger registration
|
|
23
23
|
import mcp_ticketer.adapters # noqa: F401
|
|
24
24
|
from .configure import configure_wizard, set_adapter_config, show_current_config
|
|
25
|
+
from .diagnostics import run_diagnostics
|
|
25
26
|
from .discover import app as discover_app
|
|
26
27
|
from .migrate_config import migrate_config_command
|
|
27
28
|
from .queue_commands import app as queue_app
|
|
@@ -1260,6 +1261,48 @@ app.add_typer(queue_app, name="queue")
|
|
|
1260
1261
|
# Add discover command to main app
|
|
1261
1262
|
app.add_typer(discover_app, name="discover")
|
|
1262
1263
|
|
|
1264
|
+
# Add diagnostics command
|
|
1265
|
+
@app.command()
|
|
1266
|
+
def diagnose(
|
|
1267
|
+
output_file: Optional[str] = typer.Option(None, "--output", "-o", help="Save full report to file"),
|
|
1268
|
+
json_output: bool = typer.Option(False, "--json", help="Output report in JSON format"),
|
|
1269
|
+
simple: bool = typer.Option(False, "--simple", help="Use simple diagnostics (no heavy dependencies)"),
|
|
1270
|
+
) -> None:
|
|
1271
|
+
"""Run comprehensive system diagnostics and health check."""
|
|
1272
|
+
if simple:
|
|
1273
|
+
from .simple_health import simple_diagnose
|
|
1274
|
+
report = simple_diagnose()
|
|
1275
|
+
if output_file:
|
|
1276
|
+
import json
|
|
1277
|
+
with open(output_file, 'w') as f:
|
|
1278
|
+
json.dump(report, f, indent=2)
|
|
1279
|
+
console.print(f"\n📄 Report saved to: {output_file}")
|
|
1280
|
+
if json_output:
|
|
1281
|
+
import json
|
|
1282
|
+
console.print("\n" + json.dumps(report, indent=2))
|
|
1283
|
+
if report["issues"]:
|
|
1284
|
+
raise typer.Exit(1)
|
|
1285
|
+
else:
|
|
1286
|
+
try:
|
|
1287
|
+
asyncio.run(run_diagnostics(output_file=output_file, json_output=json_output))
|
|
1288
|
+
except Exception as e:
|
|
1289
|
+
console.print(f"⚠️ Full diagnostics failed: {e}")
|
|
1290
|
+
console.print("🔄 Falling back to simple diagnostics...")
|
|
1291
|
+
from .simple_health import simple_diagnose
|
|
1292
|
+
report = simple_diagnose()
|
|
1293
|
+
if report["issues"]:
|
|
1294
|
+
raise typer.Exit(1)
|
|
1295
|
+
|
|
1296
|
+
|
|
1297
|
+
@app.command()
|
|
1298
|
+
def health() -> None:
|
|
1299
|
+
"""Quick health check - shows system status summary."""
|
|
1300
|
+
from .simple_health import simple_health_check
|
|
1301
|
+
|
|
1302
|
+
result = simple_health_check()
|
|
1303
|
+
if result != 0:
|
|
1304
|
+
raise typer.Exit(result)
|
|
1305
|
+
|
|
1263
1306
|
# Create MCP configuration command group
|
|
1264
1307
|
mcp_app = typer.Typer(
|
|
1265
1308
|
name="mcp",
|