Dev #72
No files matched your search
+6
-1
@@ -52,10 +52,11 @@ htmlcov/
|
|||||||
|
|
||||||
# Environment variables
|
# Environment variables
|
||||||
.env
|
.env
|
||||||
|
!.env.example
|
||||||
.env.local
|
.env.local
|
||||||
.env.backup-*
|
.env.backup-*
|
||||||
|
|
||||||
# API keys
|
# Keys
|
||||||
keys/
|
keys/
|
||||||
*.key
|
*.key
|
||||||
|
|
||||||
@@ -72,3 +73,7 @@ Thumbs.db
|
|||||||
|
|
||||||
# Audit logs (development only - production should use volumes)
|
# Audit logs (development only - production should use volumes)
|
||||||
audit.log
|
audit.log
|
||||||
|
|
||||||
|
# Local state
|
||||||
|
.data/
|
||||||
|
coverage.xml
|
||||||
+5
-22
@@ -1,42 +1,25 @@
|
|||||||
# Pre-commit hooks mirror `make lint` so local commits enforce the same checks as CI.
|
# Hooks mirror `make lint`. Install with: uv run pre-commit install (add pre-commit
|
||||||
# Installed via `make install-dev` (runs `pre-commit install`).
|
# yourself, it is not a project dependency) or run the same commands via `make lint`.
|
||||||
#
|
|
||||||
# Hooks use `language: system`, i.e. they invoke ruff/black/mypy from PATH rather than
|
|
||||||
# letting pre-commit manage isolated tool environments. This keeps versions identical to
|
|
||||||
# `make lint`/`make format` and lets mypy resolve the project's real dependencies.
|
|
||||||
# Requirement: commit with the dev virtualenv active (so requirements-dev.txt tools are on
|
|
||||||
# PATH). Outside an active venv the hooks will report the tools as "not found".
|
|
||||||
repos:
|
repos:
|
||||||
- repo: local
|
- repo: local
|
||||||
hooks:
|
hooks:
|
||||||
- id: ruff-check
|
- id: ruff-check
|
||||||
name: ruff check
|
name: ruff check
|
||||||
entry: ruff check
|
entry: uv run ruff check
|
||||||
language: system
|
language: system
|
||||||
types: [python]
|
types: [python]
|
||||||
args: [src/, tests/]
|
|
||||||
pass_filenames: false
|
pass_filenames: false
|
||||||
|
|
||||||
- id: ruff-format
|
- id: ruff-format
|
||||||
name: ruff format --check
|
name: ruff format --check
|
||||||
entry: ruff format --check
|
entry: uv run ruff format --check
|
||||||
language: system
|
language: system
|
||||||
types: [python]
|
types: [python]
|
||||||
args: [src/, tests/]
|
|
||||||
pass_filenames: false
|
|
||||||
|
|
||||||
- id: black
|
|
||||||
name: black --check
|
|
||||||
entry: black --check
|
|
||||||
language: system
|
|
||||||
types: [python]
|
|
||||||
args: [src/, tests/]
|
|
||||||
pass_filenames: false
|
pass_filenames: false
|
||||||
|
|
||||||
- id: mypy
|
- id: mypy
|
||||||
name: mypy
|
name: mypy
|
||||||
entry: mypy
|
entry: uv run mypy src
|
||||||
language: system
|
language: system
|
||||||
types: [python]
|
types: [python]
|
||||||
args: [src/]
|
|
||||||
pass_filenames: false
|
pass_filenames: false
|
||||||
@@ -1,89 +1,44 @@
|
|||||||
.PHONY: help install install-dev test lint format clean build run docker-build docker-up docker-down docker-logs generate-key rotate-key check-key-age validate-audit
|
.PHONY: help install lint format test test-integration run validate-audit docker-build clean
|
||||||
|
|
||||||
help:
|
help:
|
||||||
@echo "AegisGitea MCP - Available Commands"
|
@echo "AegisGitea MCP - available targets"
|
||||||
@echo ""
|
@echo " make install Install dependencies (uv sync)"
|
||||||
@echo "Development:"
|
@echo " make lint ruff check, ruff format --check, mypy"
|
||||||
@echo " make install Install production dependencies"
|
@echo " make format Format and autofix with ruff"
|
||||||
@echo " make install-dev Install development dependencies"
|
@echo " make test Run unit tests with coverage"
|
||||||
@echo " make test Run tests with coverage"
|
@echo " make test-integration Run integration tests (needs Docker)"
|
||||||
@echo " make lint Run linters (ruff, mypy)"
|
@echo " make run Run the server (uses .env)"
|
||||||
@echo " make format Format code with black"
|
@echo " make validate-audit Verify audit log hash-chain integrity"
|
||||||
@echo " make clean Remove build artifacts"
|
@echo " make docker-build Build the Docker image"
|
||||||
@echo ""
|
@echo " make clean Remove build artifacts and caches"
|
||||||
@echo "API Key Management:"
|
|
||||||
@echo " make generate-key Generate new API key"
|
|
||||||
@echo " make rotate-key Rotate existing API key"
|
|
||||||
@echo " make check-key-age Check API key age and expiration"
|
|
||||||
@echo ""
|
|
||||||
@echo "Local Execution:"
|
|
||||||
@echo " make run Run server locally (requires .env)"
|
|
||||||
@echo ""
|
|
||||||
@echo "Docker:"
|
|
||||||
@echo " make docker-build Build Docker image"
|
|
||||||
@echo " make docker-up Start containers"
|
|
||||||
@echo " make docker-down Stop containers"
|
|
||||||
@echo " make docker-logs View container logs"
|
|
||||||
@echo " make docker-restart Restart MCP container"
|
|
||||||
@echo ""
|
|
||||||
|
|
||||||
install:
|
install:
|
||||||
pip install -r requirements.txt
|
uv sync
|
||||||
|
|
||||||
install-dev:
|
|
||||||
pip install -r requirements-dev.txt
|
|
||||||
pre-commit install
|
|
||||||
|
|
||||||
test:
|
|
||||||
pytest tests/ -v --cov=aegis_gitea_mcp --cov-report=html --cov-report=term --cov-fail-under=80
|
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
ruff check src/ tests/
|
uv run ruff check .
|
||||||
ruff format --check src/ tests/
|
uv run ruff format --check .
|
||||||
black --check src/ tests/
|
uv run mypy src
|
||||||
mypy src/
|
|
||||||
|
|
||||||
format:
|
format:
|
||||||
black src/ tests/
|
uv run ruff format .
|
||||||
ruff check --fix src/ tests/
|
uv run ruff check --fix .
|
||||||
|
|
||||||
clean:
|
test:
|
||||||
rm -rf build/ dist/ *.egg-info .pytest_cache/ .coverage htmlcov/
|
uv run pytest
|
||||||
find . -type d -name __pycache__ -exec rm -rf {} +
|
|
||||||
find . -type f -name "*.pyc" -delete
|
|
||||||
|
|
||||||
build:
|
test-integration:
|
||||||
python -m build
|
AEGIS_INTEGRATION=1 uv run pytest -m integration
|
||||||
|
|
||||||
run:
|
run:
|
||||||
python3 -m aegis_gitea_mcp.server
|
uv run aegis-gitea-mcp serve
|
||||||
|
|
||||||
docker-build:
|
|
||||||
docker-compose build
|
|
||||||
|
|
||||||
docker-up:
|
|
||||||
docker-compose --profile prod up -d
|
|
||||||
|
|
||||||
docker-down:
|
|
||||||
docker-compose down
|
|
||||||
|
|
||||||
docker-logs:
|
|
||||||
docker-compose logs -f aegis-mcp
|
|
||||||
|
|
||||||
docker-restart:
|
|
||||||
docker-compose restart aegis-mcp
|
|
||||||
|
|
||||||
docker-shell:
|
|
||||||
docker-compose exec aegis-mcp /bin/bash
|
|
||||||
|
|
||||||
generate-key:
|
|
||||||
python3 scripts/generate_api_key.py
|
|
||||||
|
|
||||||
rotate-key:
|
|
||||||
python3 scripts/rotate_api_key.py
|
|
||||||
|
|
||||||
check-key-age:
|
|
||||||
python3 scripts/check_key_age.py
|
|
||||||
|
|
||||||
validate-audit:
|
validate-audit:
|
||||||
python3 scripts/validate_audit_log.py
|
uv run python scripts/validate_audit_log.py
|
||||||
|
|
||||||
|
docker-build:
|
||||||
|
docker build -f docker/Dockerfile -t aegis-gitea-mcp:dev .
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rf build/ dist/ *.egg-info .pytest_cache/ .mypy_cache/ .ruff_cache/ .coverage htmlcov/
|
||||||
|
find . -type d -name __pycache__ -not -path "./.venv/*" -exec rm -rf {} +
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
# PLAN — local stdio package + safe full-API coverage
|
|
||||||
|
|
||||||
Branch: `feat/local-package-and-full-coverage` (from `dev`). All PRs target `dev`.
|
|
||||||
Flow: HEAD -> custom branch -> dev -> main. Never push directly to dev/main.
|
|
||||||
|
|
||||||
Baseline (recorded Phase 0): 284 passed, 1 skipped, coverage 84.04%, threshold 80%.
|
|
||||||
|
|
||||||
## Phase checklist
|
|
||||||
|
|
||||||
- [x] Phase 0 — Branch from dev, baseline recorded, PLAN.md committed.
|
|
||||||
- [x] Phase 1 — Extract transport-agnostic core + shared tool registry (+ boundary test).
|
|
||||||
- [x] Phase 2 — stdio adapter (`stdio_app.py`) + packaging (core + `[server]` extra, 0.2.0).
|
|
||||||
- [x] Phase 3 — Resource-type-aware authorization (fail-closed).
|
|
||||||
- [x] Phase 4 — gitea_request classifier + known-path gate (unknown path => deny).
|
|
||||||
- [x] Phase 5 — Tests: authz matrix, write-mode bypass, classifier, stdio adapter, boundary.
|
|
||||||
- [x] Phase 6 — Docs & README (local vs server quickstart, authz model, packaging, CLAUDE/AGENTS).
|
|
||||||
- [ ] Phase 7 — `.gitea/workflows/publish.yml` (uv build + publish to Gitea registry on tag).
|
|
||||||
- [ ] Phase 8 — Verify green + coverage >= baseline, `uv build`, push, open PR into dev.
|
|
||||||
|
|
||||||
Note: version bumped to 0.2.0 (the app already reported 0.2.0; pyproject was 0.1.0).
|
|
||||||
TODO(authz): make `list_organizations` user-scoped (`/users/{login}/orgs`) so it can
|
|
||||||
be allowed rather than denied in service-PAT mode.
|
|
||||||
|
|
||||||
## Key deltas found during orientation
|
|
||||||
|
|
||||||
- No single tool registry today: definitions in `mcp_protocol.AVAILABLE_TOOLS`,
|
|
||||||
handlers in `server.TOOL_HANDLERS`. Phase 1 unifies them.
|
|
||||||
- `tools/raw_tools.py` imports `fastapi.HTTPException` — the only core->web import to break.
|
|
||||||
- Current authz is repo-only and lives in `server._verify_user_repository_access`.
|
|
||||||
- stdio mode must run with `AUTH_ENABLED=false` (config otherwise requires MCP_API_KEYS).
|
|
||||||
- `AGENTS.md` absent at root though CLAUDE.md cites it; create it from the contract.
|
|
||||||
+52
-51
@@ -1,14 +1,14 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "aegis-gitea-mcp"
|
name = "aegis-gitea-mcp"
|
||||||
version = "0.2.0"
|
version = "1.0.0.dev0"
|
||||||
description = "Security-first MCP server for controlled AI access to self-hosted Gitea (local stdio + public HTTP/OAuth)"
|
description = "MCP server exposing the complete Gitea API over stdio and streamable HTTP, with admin-token and per-user OAuth modes"
|
||||||
authors = [
|
authors = [
|
||||||
{name = "AegisGitea MCP Contributors"}
|
{name = "AegisGitea MCP Contributors"}
|
||||||
]
|
]
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.10"
|
requires-python = ">=3.10"
|
||||||
license = {text = "MIT"}
|
license = {text = "MIT"}
|
||||||
keywords = ["mcp", "gitea", "ai", "security", "audit"]
|
keywords = ["mcp", "gitea", "ai", "audit"]
|
||||||
classifiers = [
|
classifiers = [
|
||||||
"Development Status :: 3 - Alpha",
|
"Development Status :: 3 - Alpha",
|
||||||
"Intended Audience :: Developers",
|
"Intended Audience :: Developers",
|
||||||
@@ -17,46 +17,22 @@ classifiers = [
|
|||||||
"Programming Language :: Python :: 3.10",
|
"Programming Language :: Python :: 3.10",
|
||||||
"Programming Language :: Python :: 3.11",
|
"Programming Language :: Python :: 3.11",
|
||||||
"Programming Language :: Python :: 3.12",
|
"Programming Language :: Python :: 3.12",
|
||||||
|
"Programming Language :: Python :: 3.13",
|
||||||
]
|
]
|
||||||
|
|
||||||
# Core (default install) powers the local stdio transport. It deliberately
|
|
||||||
# excludes the web/OAuth stack so `uvx aegis-gitea-mcp` stays light; the HTTP
|
|
||||||
# server pulls those in via the [server] extra.
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"httpx>=0.26.0",
|
"mcp>=2.3,<3",
|
||||||
"pydantic>=2.5.0",
|
"httpx>=0.27",
|
||||||
"pydantic-settings>=2.1.0",
|
"pydantic>=2.7",
|
||||||
"PyYAML>=6.0.1",
|
# NoDecode (comma-separated list env vars) needs pydantic-settings 2.7.
|
||||||
"python-dotenv>=1.0.0",
|
"pydantic-settings>=2.7",
|
||||||
"structlog>=24.1.0",
|
"jsonschema>=4.20",
|
||||||
"mcp>=1.2.0",
|
"cryptography>=42",
|
||||||
]
|
"uvicorn>=0.31",
|
||||||
|
|
||||||
[project.optional-dependencies]
|
|
||||||
# The public HTTP/OAuth server (aegis-gitea-mcp-server) needs the web stack.
|
|
||||||
server = [
|
|
||||||
"fastapi>=0.109.0",
|
|
||||||
"uvicorn[standard]>=0.27.0",
|
|
||||||
"PyJWT[crypto]>=2.9.0",
|
|
||||||
"python-multipart>=0.0.9",
|
|
||||||
]
|
|
||||||
dev = [
|
|
||||||
"pytest>=7.4.0",
|
|
||||||
"pytest-asyncio>=0.23.0",
|
|
||||||
"pytest-cov>=4.1.0",
|
|
||||||
"pytest-httpx>=0.28.0",
|
|
||||||
"black>=24.1.0",
|
|
||||||
"ruff>=0.1.0",
|
|
||||||
"mypy>=1.8.0",
|
|
||||||
"pre-commit>=3.6.0",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
# Local stdio MCP server (default install, no web stack required).
|
aegis-gitea-mcp = "aegis_gitea_mcp.cli:main"
|
||||||
aegis-gitea-mcp = "aegis_gitea_mcp.stdio_app:main"
|
|
||||||
# Public HTTP/OAuth server; requires the [server] extra. The entry point guards
|
|
||||||
# against a missing web stack with an actionable message.
|
|
||||||
aegis-gitea-mcp-server = "aegis_gitea_mcp.server_entry:main"
|
|
||||||
|
|
||||||
[project.urls]
|
[project.urls]
|
||||||
Homepage = "https://git.hiddenden.cafe/Hiddenden/AegisGitea-MCP"
|
Homepage = "https://git.hiddenden.cafe/Hiddenden/AegisGitea-MCP"
|
||||||
@@ -64,24 +40,33 @@ Documentation = "https://git.hiddenden.cafe/Hiddenden/AegisGitea-MCP/src/branch/
|
|||||||
Repository = "https://git.hiddenden.cafe/Hiddenden/AegisGitea-MCP.git"
|
Repository = "https://git.hiddenden.cafe/Hiddenden/AegisGitea-MCP.git"
|
||||||
Issues = "https://git.hiddenden.cafe/Hiddenden/AegisGitea-MCP/issues"
|
Issues = "https://git.hiddenden.cafe/Hiddenden/AegisGitea-MCP/issues"
|
||||||
|
|
||||||
|
[dependency-groups]
|
||||||
|
dev = [
|
||||||
|
"pytest>=8",
|
||||||
|
"pytest-asyncio>=0.23",
|
||||||
|
"pytest-cov>=5",
|
||||||
|
"pytest-httpx>=0.30",
|
||||||
|
"ruff>=0.6",
|
||||||
|
"mypy>=1.10",
|
||||||
|
"types-jsonschema",
|
||||||
|
"pip-audit>=2.7",
|
||||||
|
]
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["setuptools>=68.0.0", "wheel"]
|
requires = ["hatchling"]
|
||||||
build-backend = "setuptools.build_meta"
|
build-backend = "hatchling.build"
|
||||||
|
|
||||||
[tool.setuptools]
|
[tool.hatch.build.targets.wheel]
|
||||||
package-dir = {"" = "src"}
|
packages = ["src/aegis_gitea_mcp"]
|
||||||
|
|
||||||
[tool.setuptools.packages.find]
|
[tool.hatch.build.targets.sdist]
|
||||||
where = ["src"]
|
include = ["src/aegis_gitea_mcp", "README.md", "LICENSE", "pyproject.toml"]
|
||||||
|
|
||||||
[tool.black]
|
|
||||||
line-length = 100
|
|
||||||
target-version = ['py310', 'py311', 'py312']
|
|
||||||
include = '\.pyi?$'
|
|
||||||
|
|
||||||
[tool.ruff]
|
[tool.ruff]
|
||||||
line-length = 100
|
line-length = 100
|
||||||
target-version = "py310"
|
target-version = "py310"
|
||||||
|
src = ["src", "tests"]
|
||||||
|
extend-exclude = ["docs", "spec"]
|
||||||
|
|
||||||
[tool.ruff.lint]
|
[tool.ruff.lint]
|
||||||
select = [
|
select = [
|
||||||
@@ -92,14 +77,27 @@ select = [
|
|||||||
"B", # flake8-bugbear
|
"B", # flake8-bugbear
|
||||||
"C4", # flake8-comprehensions
|
"C4", # flake8-comprehensions
|
||||||
"UP", # pyupgrade
|
"UP", # pyupgrade
|
||||||
|
"S", # flake8-bandit
|
||||||
|
"SIM", # flake8-simplify
|
||||||
|
"RUF", # ruff-specific
|
||||||
]
|
]
|
||||||
ignore = [
|
ignore = [
|
||||||
"E501", # line too long (handled by black)
|
"E501", # line length is handled by ruff format; long strings and URLs are fine
|
||||||
"B008", # do not perform function calls in argument defaults
|
"B008", # function calls in argument defaults (pydantic Field style)
|
||||||
]
|
]
|
||||||
|
|
||||||
[tool.ruff.lint.per-file-ignores]
|
[tool.ruff.lint.per-file-ignores]
|
||||||
"__init__.py" = ["F401"]
|
"__init__.py" = ["F401"]
|
||||||
|
"tests/**" = [
|
||||||
|
"S101", # assert is the point of tests
|
||||||
|
"S105", # hardcoded fake tokens in fixtures
|
||||||
|
"S106", # hardcoded fake tokens in fixtures
|
||||||
|
"S108", # fixture paths
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.ruff.format]
|
||||||
|
quote-style = "double"
|
||||||
|
line-ending = "lf"
|
||||||
|
|
||||||
[tool.mypy]
|
[tool.mypy]
|
||||||
python_version = "3.10"
|
python_version = "3.10"
|
||||||
@@ -119,11 +117,14 @@ module = "tests.*"
|
|||||||
disallow_untyped_defs = false
|
disallow_untyped_defs = false
|
||||||
|
|
||||||
[tool.pytest.ini_options]
|
[tool.pytest.ini_options]
|
||||||
minversion = "7.0"
|
minversion = "8.0"
|
||||||
addopts = "-ra -q --strict-markers --cov=aegis_gitea_mcp --cov-report=term-missing --cov-fail-under=80"
|
addopts = "-ra -q --strict-markers --cov=aegis_gitea_mcp --cov-report=term-missing --cov-fail-under=80"
|
||||||
testpaths = ["tests"]
|
testpaths = ["tests"]
|
||||||
pythonpath = ["src"]
|
pythonpath = ["src"]
|
||||||
asyncio_mode = "auto"
|
asyncio_mode = "auto"
|
||||||
|
markers = [
|
||||||
|
"integration: needs a live Gitea in Docker (opt-in via AEGIS_INTEGRATION=1)",
|
||||||
|
]
|
||||||
|
|
||||||
[tool.coverage.run]
|
[tool.coverage.run]
|
||||||
source = ["src"]
|
source = ["src"]
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
-r requirements.txt
|
|
||||||
|
|
||||||
pytest>=7.4.0
|
|
||||||
pytest-asyncio>=0.23.0
|
|
||||||
pytest-cov>=4.1.0
|
|
||||||
pytest-httpx>=0.28.0
|
|
||||||
black>=24.1.0
|
|
||||||
ruff>=0.1.0
|
|
||||||
mypy>=1.8.0
|
|
||||||
pre-commit>=3.6.0
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
fastapi>=0.109.0
|
|
||||||
uvicorn[standard]>=0.27.0
|
|
||||||
httpx>=0.26.0
|
|
||||||
pydantic>=2.5.0
|
|
||||||
pydantic-settings>=2.1.0
|
|
||||||
PyYAML>=6.0.1
|
|
||||||
python-dotenv>=1.0.0
|
|
||||||
python-multipart>=0.0.9
|
|
||||||
structlog>=24.1.0
|
|
||||||
PyJWT[crypto]>=2.9.0
|
|
||||||
mcp>=1.2.0
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
# Test runner script for AegisGitea MCP
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
echo "========================================="
|
|
||||||
echo "AegisGitea MCP - Test Suite"
|
|
||||||
echo "========================================="
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Colors
|
|
||||||
GREEN='\033[0;32m'
|
|
||||||
RED='\033[0;31m'
|
|
||||||
YELLOW='\033[1;33m'
|
|
||||||
NC='\033[0m' # No Color
|
|
||||||
|
|
||||||
# Check if virtual environment exists
|
|
||||||
if [ ! -d "venv" ]; then
|
|
||||||
echo -e "${YELLOW}Virtual environment not found. Creating...${NC}"
|
|
||||||
python3 -m venv venv
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Activate virtual environment
|
|
||||||
source venv/bin/activate
|
|
||||||
|
|
||||||
# Install dependencies
|
|
||||||
echo -e "${YELLOW}Installing dependencies...${NC}"
|
|
||||||
pip install -q -r requirements-dev.txt
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "========================================="
|
|
||||||
echo "Running Tests"
|
|
||||||
echo "========================================="
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Run tests with coverage
|
|
||||||
pytest tests/ \
|
|
||||||
-v \
|
|
||||||
--cov=aegis_gitea_mcp \
|
|
||||||
--cov-report=term-missing \
|
|
||||||
--cov-report=html \
|
|
||||||
--tb=short
|
|
||||||
|
|
||||||
TEST_EXIT_CODE=$?
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "========================================="
|
|
||||||
echo "Test Summary"
|
|
||||||
echo "========================================="
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
if [ $TEST_EXIT_CODE -eq 0 ]; then
|
|
||||||
echo -e "${GREEN}✓ All tests passed!${NC}"
|
|
||||||
echo ""
|
|
||||||
echo "Coverage report generated in htmlcov/"
|
|
||||||
echo "Open htmlcov/index.html in your browser to view detailed coverage."
|
|
||||||
else
|
|
||||||
echo -e "${RED}✗ Some tests failed${NC}"
|
|
||||||
echo ""
|
|
||||||
echo "Please review the errors above and fix them."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "========================================="
|
|
||||||
echo "Running Linters"
|
|
||||||
echo "========================================="
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Run ruff
|
|
||||||
echo "Running ruff..."
|
|
||||||
ruff check src/ tests/ || true
|
|
||||||
|
|
||||||
# Run mypy
|
|
||||||
echo "Running mypy..."
|
|
||||||
mypy src/ || true
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo -e "${GREEN}Test suite complete!${NC}"
|
|
||||||
Reference in new issue
Block a user