mfcqi 0.0.1__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.
- mfcqi-0.0.1/.bandit +26 -0
- mfcqi-0.0.1/.env.example +28 -0
- mfcqi-0.0.1/.github/badges/mfcqi.json +7 -0
- mfcqi-0.0.1/.github/workflows/ci.yml +268 -0
- mfcqi-0.0.1/.github/workflows/mfcqi-badge.yml +109 -0
- mfcqi-0.0.1/.github/workflows/release.yml +350 -0
- mfcqi-0.0.1/.github/workflows/test-release.yml +211 -0
- mfcqi-0.0.1/.gitignore +46 -0
- mfcqi-0.0.1/.python-version +1 -0
- mfcqi-0.0.1/CONTRIBUTING.md +247 -0
- mfcqi-0.0.1/LICENSE +21 -0
- mfcqi-0.0.1/MANIFEST.in +21 -0
- mfcqi-0.0.1/Makefile +266 -0
- mfcqi-0.0.1/PKG-INFO +524 -0
- mfcqi-0.0.1/README.md +478 -0
- mfcqi-0.0.1/codecov.yml +27 -0
- mfcqi-0.0.1/docs/mfcqi.png +0 -0
- mfcqi-0.0.1/docs/research.md +1006 -0
- mfcqi-0.0.1/mypy.ini +33 -0
- mfcqi-0.0.1/pyproject.toml +107 -0
- mfcqi-0.0.1/ruff.toml +95 -0
- mfcqi-0.0.1/scripts/check_deps.py +61 -0
- mfcqi-0.0.1/src/mfcqi/__init__.py +12 -0
- mfcqi-0.0.1/src/mfcqi/__main__.py +12 -0
- mfcqi-0.0.1/src/mfcqi/analysis/__init__.py +1 -0
- mfcqi-0.0.1/src/mfcqi/analysis/config.py +97 -0
- mfcqi-0.0.1/src/mfcqi/analysis/diagnostics.py +79 -0
- mfcqi-0.0.1/src/mfcqi/analysis/engine.py +359 -0
- mfcqi-0.0.1/src/mfcqi/analysis/tools/__init__.py +1 -0
- mfcqi-0.0.1/src/mfcqi/analysis/tools/bandit_analyzer.py +136 -0
- mfcqi-0.0.1/src/mfcqi/analysis/tools/detect_secrets_analyzer.py +153 -0
- mfcqi-0.0.1/src/mfcqi/analysis/tools/pip_audit_analyzer.py +198 -0
- mfcqi-0.0.1/src/mfcqi/analysis/tools/pylint_analyzer.py +159 -0
- mfcqi-0.0.1/src/mfcqi/analysis/tools/ruff_analyzer.py +217 -0
- mfcqi-0.0.1/src/mfcqi/calculator.py +361 -0
- mfcqi-0.0.1/src/mfcqi/cli/__init__.py +3 -0
- mfcqi-0.0.1/src/mfcqi/cli/commands/__init__.py +3 -0
- mfcqi-0.0.1/src/mfcqi/cli/commands/analyze.py +152 -0
- mfcqi-0.0.1/src/mfcqi/cli/commands/analyze_helpers.py +170 -0
- mfcqi-0.0.1/src/mfcqi/cli/commands/badge.py +106 -0
- mfcqi-0.0.1/src/mfcqi/cli/commands/badge_templates.py +74 -0
- mfcqi-0.0.1/src/mfcqi/cli/commands/config.py +271 -0
- mfcqi-0.0.1/src/mfcqi/cli/commands/models.py +341 -0
- mfcqi-0.0.1/src/mfcqi/cli/main.py +44 -0
- mfcqi-0.0.1/src/mfcqi/cli/utils/__init__.py +3 -0
- mfcqi-0.0.1/src/mfcqi/cli/utils/config_manager.py +166 -0
- mfcqi-0.0.1/src/mfcqi/cli/utils/llm_handler.py +394 -0
- mfcqi-0.0.1/src/mfcqi/cli/utils/output.py +635 -0
- mfcqi-0.0.1/src/mfcqi/core/__init__.py +1 -0
- mfcqi-0.0.1/src/mfcqi/core/file_utils.py +74 -0
- mfcqi-0.0.1/src/mfcqi/core/metric.py +135 -0
- mfcqi-0.0.1/src/mfcqi/core/paradigm_detector.py +332 -0
- mfcqi-0.0.1/src/mfcqi/metrics/__init__.py +1 -0
- mfcqi-0.0.1/src/mfcqi/metrics/code_smell.py +198 -0
- mfcqi-0.0.1/src/mfcqi/metrics/cognitive.py +292 -0
- mfcqi-0.0.1/src/mfcqi/metrics/cohesion.py +176 -0
- mfcqi-0.0.1/src/mfcqi/metrics/complexity.py +263 -0
- mfcqi-0.0.1/src/mfcqi/metrics/coupling.py +226 -0
- mfcqi-0.0.1/src/mfcqi/metrics/dependency_security.py +156 -0
- mfcqi-0.0.1/src/mfcqi/metrics/dit.py +195 -0
- mfcqi-0.0.1/src/mfcqi/metrics/documentation.py +111 -0
- mfcqi-0.0.1/src/mfcqi/metrics/duplication.py +247 -0
- mfcqi-0.0.1/src/mfcqi/metrics/maintainability.py +109 -0
- mfcqi-0.0.1/src/mfcqi/metrics/mhf.py +129 -0
- mfcqi-0.0.1/src/mfcqi/metrics/rfc.py +184 -0
- mfcqi-0.0.1/src/mfcqi/metrics/secrets_exposure.py +153 -0
- mfcqi-0.0.1/src/mfcqi/metrics/security.py +747 -0
- mfcqi-0.0.1/src/mfcqi/metrics/type_safety.py +83 -0
- mfcqi-0.0.1/src/mfcqi/py.typed +0 -0
- mfcqi-0.0.1/src/mfcqi/quality_gates.py +154 -0
- mfcqi-0.0.1/src/mfcqi/smell_detection/__init__.py +25 -0
- mfcqi-0.0.1/src/mfcqi/smell_detection/aggregator.py +147 -0
- mfcqi-0.0.1/src/mfcqi/smell_detection/ast_test_smells.py +230 -0
- mfcqi-0.0.1/src/mfcqi/smell_detection/detector_base.py +65 -0
- mfcqi-0.0.1/src/mfcqi/smell_detection/models.py +87 -0
- mfcqi-0.0.1/src/mfcqi/smell_detection/pyexamine.py +265 -0
- mfcqi-0.0.1/src/mfcqi/templates/code_quality_analysis.j2 +127 -0
- mfcqi-0.0.1/src/mfcqi/templates/fallback_recommendations.j2 +126 -0
- mfcqi-0.0.1/tests/conftest.py +18 -0
- mfcqi-0.0.1/tests/fixtures/__init__.py +1 -0
- mfcqi-0.0.1/tests/fixtures/security_fixtures.py +566 -0
- mfcqi-0.0.1/tests/integration/__init__.py +3 -0
- mfcqi-0.0.1/tests/integration/test_cli_integration.py +346 -0
- mfcqi-0.0.1/tests/integration/test_llm_analysis.py +74 -0
- mfcqi-0.0.1/tests/integration/test_llm_providers.py.disabled +282 -0
- mfcqi-0.0.1/tests/schemas/sarif-schema-2.1.0.json +3389 -0
- mfcqi-0.0.1/tests/test_analysis_config.py +235 -0
- mfcqi-0.0.1/tests/test_analysis_diagnostics.py +269 -0
- mfcqi-0.0.1/tests/test_analyze_helpers.py +247 -0
- mfcqi-0.0.1/tests/test_ast_test_smell_detector.py +220 -0
- mfcqi-0.0.1/tests/test_badge_command.py +139 -0
- mfcqi-0.0.1/tests/test_bandit_api_integration.py +226 -0
- mfcqi-0.0.1/tests/test_bandit_integration.py +445 -0
- mfcqi-0.0.1/tests/test_cli_import_smoke.py +150 -0
- mfcqi-0.0.1/tests/test_code_duplication.py +170 -0
- mfcqi-0.0.1/tests/test_code_smell_data.py +131 -0
- mfcqi-0.0.1/tests/test_code_smell_density.py +178 -0
- mfcqi-0.0.1/tests/test_cognitive_complexity.py +347 -0
- mfcqi-0.0.1/tests/test_cognitive_complexity_hotspots.py +274 -0
- mfcqi-0.0.1/tests/test_comprehensive_integration.py +349 -0
- mfcqi-0.0.1/tests/test_config_command.py +198 -0
- mfcqi-0.0.1/tests/test_core_metric.py +221 -0
- mfcqi-0.0.1/tests/test_cyclomatic_complexity.py +117 -0
- mfcqi-0.0.1/tests/test_dependency_security.py +115 -0
- mfcqi-0.0.1/tests/test_detect_secrets_api_integration.py +209 -0
- mfcqi-0.0.1/tests/test_dit_metric.py +174 -0
- mfcqi-0.0.1/tests/test_documentation_coverage.py +281 -0
- mfcqi-0.0.1/tests/test_engine_context_building.py +384 -0
- mfcqi-0.0.1/tests/test_file_utils.py +138 -0
- mfcqi-0.0.1/tests/test_halstead_complexity.py +151 -0
- mfcqi-0.0.1/tests/test_llm_analysis_engine.py +233 -0
- mfcqi-0.0.1/tests/test_maintainability_index.py +178 -0
- mfcqi-0.0.1/tests/test_metrics_integration.py +185 -0
- mfcqi-0.0.1/tests/test_mfcqi_calculator.py +562 -0
- mfcqi-0.0.1/tests/test_mhf_metric.py +130 -0
- mfcqi-0.0.1/tests/test_models_command.py +64 -0
- mfcqi-0.0.1/tests/test_output.py +286 -0
- mfcqi-0.0.1/tests/test_paradigm_detector.py +512 -0
- mfcqi-0.0.1/tests/test_pip_audit_api_integration.py +71 -0
- mfcqi-0.0.1/tests/test_pyexamine_detector.py +177 -0
- mfcqi-0.0.1/tests/test_pylint_api_integration.py +253 -0
- mfcqi-0.0.1/tests/test_pylint_integration.py +331 -0
- mfcqi-0.0.1/tests/test_quality_gates.py +350 -0
- mfcqi-0.0.1/tests/test_real_world_fixtures.py +313 -0
- mfcqi-0.0.1/tests/test_rfc_metric.py +149 -0
- mfcqi-0.0.1/tests/test_ruff_integration.py +406 -0
- mfcqi-0.0.1/tests/test_sarif_integration.py +121 -0
- mfcqi-0.0.1/tests/test_sarif_output.py +256 -0
- mfcqi-0.0.1/tests/test_secrets_exposure.py +113 -0
- mfcqi-0.0.1/tests/test_security_comprehensive.py +123 -0
- mfcqi-0.0.1/tests/test_security_integration.py +106 -0
- mfcqi-0.0.1/tests/test_security_metric.py +468 -0
- mfcqi-0.0.1/tests/test_smell_aggregator.py +291 -0
- mfcqi-0.0.1/tests/test_smell_detection_integration.py +248 -0
- mfcqi-0.0.1/tests/test_smell_detector_base.py +107 -0
- mfcqi-0.0.1/tests/test_type_safety.py +190 -0
- mfcqi-0.0.1/tests/test_type_safety_integration.py +140 -0
- mfcqi-0.0.1/uv.lock +2766 -0
mfcqi-0.0.1/.bandit
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Bandit configuration for MFCQI project
|
|
2
|
+
[bandit]
|
|
3
|
+
exclude_dirs = [
|
|
4
|
+
"tests",
|
|
5
|
+
".venv",
|
|
6
|
+
"build",
|
|
7
|
+
"dist"
|
|
8
|
+
]
|
|
9
|
+
|
|
10
|
+
# Skip certain tests that are not relevant for our use case or are false positives
|
|
11
|
+
skips = [
|
|
12
|
+
"B101", # Test for use of assert
|
|
13
|
+
"B110", # Test for a try/except pass - used for optional dependency handling
|
|
14
|
+
"B404", # Test for subprocess import - we use subprocess securely with absolute paths
|
|
15
|
+
"B601", # paramiko calls (not used)
|
|
16
|
+
"B602", # subprocess popen with shell=true (controlled usage)
|
|
17
|
+
"B603", # subprocess without shell equals true (controlled usage)
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
# Severity level
|
|
21
|
+
# LOW, MEDIUM, HIGH
|
|
22
|
+
severity = MEDIUM
|
|
23
|
+
|
|
24
|
+
# Confidence level
|
|
25
|
+
# LOW, MEDIUM, HIGH
|
|
26
|
+
confidence = MEDIUM
|
mfcqi-0.0.1/.env.example
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# MFCQI Environment Variables
|
|
2
|
+
# Copy this file to .env and configure with your actual API keys
|
|
3
|
+
|
|
4
|
+
# OpenAI API Key (for GPT-4o, GPT-4o-mini models)
|
|
5
|
+
# Get your key at: https://platform.openai.com/api-keys
|
|
6
|
+
OPENAI_API_KEY=sk-proj-YOUR-OPENAI-API-KEY-HERE
|
|
7
|
+
|
|
8
|
+
# Anthropic API Key (for Claude 3.5 Sonnet, Claude 3 Haiku models)
|
|
9
|
+
# Get your key at: https://console.anthropic.com/settings/keys
|
|
10
|
+
ANTHROPIC_API_KEY=sk-ant-YOUR-ANTHROPIC-API-KEY-HERE
|
|
11
|
+
|
|
12
|
+
# Google API Key (for Gemini models - optional)
|
|
13
|
+
# Get your key at: https://makersuite.google.com/app/apikey
|
|
14
|
+
GOOGLE_API_KEY=YOUR-GOOGLE-API-KEY-HERE
|
|
15
|
+
|
|
16
|
+
# LangChain Tracing (optional - for debugging)
|
|
17
|
+
# Get your key at: https://smith.langchain.com/
|
|
18
|
+
LANGCHAIN_TRACING_V2=false
|
|
19
|
+
LANGCHAIN_API_KEY=YOUR-LANGCHAIN-API-KEY-HERE
|
|
20
|
+
LANGCHAIN_PROJECT=mfcqi-analysis
|
|
21
|
+
|
|
22
|
+
# Ollama Endpoint (optional - defaults to http://localhost:11434)
|
|
23
|
+
# Only needed if running Ollama on a different host/port
|
|
24
|
+
# OLLAMA_ENDPOINT=http://localhost:11434
|
|
25
|
+
|
|
26
|
+
# Note: API keys can also be stored securely using the CLI:
|
|
27
|
+
# mfcqi config setup
|
|
28
|
+
# This will use your system's keyring for secure storage
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [ main, master, develop ]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [ main, master, develop ]
|
|
8
|
+
workflow_dispatch:
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
test:
|
|
12
|
+
runs-on: ${{ matrix.os }}
|
|
13
|
+
strategy:
|
|
14
|
+
fail-fast: false
|
|
15
|
+
matrix:
|
|
16
|
+
os: [ubuntu-latest, macos-latest, windows-latest]
|
|
17
|
+
python-version: ['3.10', '3.11', '3.12', '3.13']
|
|
18
|
+
|
|
19
|
+
steps:
|
|
20
|
+
- name: Checkout code
|
|
21
|
+
uses: actions/checkout@v4
|
|
22
|
+
|
|
23
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
24
|
+
uses: actions/setup-python@v4
|
|
25
|
+
with:
|
|
26
|
+
python-version: ${{ matrix.python-version }}
|
|
27
|
+
|
|
28
|
+
- name: Install UV
|
|
29
|
+
uses: astral-sh/setup-uv@v3
|
|
30
|
+
with:
|
|
31
|
+
enable-cache: true
|
|
32
|
+
|
|
33
|
+
- name: Install dependencies
|
|
34
|
+
run: |
|
|
35
|
+
uv sync
|
|
36
|
+
# Verify installation
|
|
37
|
+
uv run python -c "import mfcqi; print('MFCQI installed successfully')"
|
|
38
|
+
|
|
39
|
+
- name: Install system dependencies (Ubuntu)
|
|
40
|
+
if: matrix.os == 'ubuntu-latest'
|
|
41
|
+
run: |
|
|
42
|
+
sudo apt-get update
|
|
43
|
+
sudo apt-get install -y bc # For numeric comparisons in scripts
|
|
44
|
+
|
|
45
|
+
- name: Install system dependencies (macOS)
|
|
46
|
+
if: matrix.os == 'macos-latest'
|
|
47
|
+
run: |
|
|
48
|
+
brew install bc # For numeric comparisons in scripts
|
|
49
|
+
|
|
50
|
+
- name: Run all quality checks with coverage
|
|
51
|
+
env:
|
|
52
|
+
MFCQI_DISABLE_KEYRING: "1"
|
|
53
|
+
run: |
|
|
54
|
+
echo "🔍 Running comprehensive quality checks..."
|
|
55
|
+
# Run format and type checks
|
|
56
|
+
uv run ruff format --check src/ tests/
|
|
57
|
+
uv run mypy --strict src/mfcqi/
|
|
58
|
+
# Run tests with coverage (make test-all uses sequential execution to avoid hangs)
|
|
59
|
+
echo "🧪 Running test suite with coverage..."
|
|
60
|
+
uv run pytest tests/ --cov=src/mfcqi --cov-branch --cov-report=xml --cov-report=term-missing -v
|
|
61
|
+
echo "✅ All quality checks passed"
|
|
62
|
+
|
|
63
|
+
- name: Verify coverage file
|
|
64
|
+
if: matrix.os == 'ubuntu-latest'
|
|
65
|
+
run: |
|
|
66
|
+
ls -lh coverage.xml
|
|
67
|
+
echo "Coverage file size: $(stat -c%s coverage.xml 2>/dev/null || stat -f%z coverage.xml) bytes"
|
|
68
|
+
|
|
69
|
+
- name: Upload coverage reports to Codecov
|
|
70
|
+
if: matrix.os == 'ubuntu-latest'
|
|
71
|
+
uses: codecov/codecov-action@v5
|
|
72
|
+
with:
|
|
73
|
+
token: ${{ secrets.CODECOV_TOKEN }}
|
|
74
|
+
files: ./coverage.xml
|
|
75
|
+
flags: unittests
|
|
76
|
+
name: codecov-${{ matrix.os }}-py${{ matrix.python-version }}
|
|
77
|
+
fail_ci_if_error: true
|
|
78
|
+
verbose: true
|
|
79
|
+
|
|
80
|
+
- name: Run MFCQI self-analysis (Unix)
|
|
81
|
+
if: matrix.os != 'windows-latest'
|
|
82
|
+
env:
|
|
83
|
+
MFCQI_DISABLE_KEYRING: "1"
|
|
84
|
+
run: |
|
|
85
|
+
echo "📊 Running MFCQI analysis on itself..."
|
|
86
|
+
MFCQI_SCORE=$(uv run mfcqi analyze src/mfcqi --format json --silent | python -c "
|
|
87
|
+
import sys, json
|
|
88
|
+
try:
|
|
89
|
+
data = json.load(sys.stdin)
|
|
90
|
+
print(data['mfcqi_score'])
|
|
91
|
+
except:
|
|
92
|
+
print('0.0')
|
|
93
|
+
")
|
|
94
|
+
|
|
95
|
+
echo "Current MFCQI Score: $MFCQI_SCORE"
|
|
96
|
+
|
|
97
|
+
# Check if score meets minimum threshold
|
|
98
|
+
THRESHOLD="0.75"
|
|
99
|
+
if command -v bc >/dev/null 2>&1; then
|
|
100
|
+
if (( $(echo "$MFCQI_SCORE < $THRESHOLD" | bc -l) )); then
|
|
101
|
+
echo "⚠️ Warning: MFCQI score ($MFCQI_SCORE) is below recommended threshold ($THRESHOLD)"
|
|
102
|
+
echo "Code quality should be improved before merging"
|
|
103
|
+
else
|
|
104
|
+
echo "✅ MFCQI score meets quality threshold"
|
|
105
|
+
fi
|
|
106
|
+
else
|
|
107
|
+
echo "ℹ️ bc not available, skipping threshold check"
|
|
108
|
+
fi
|
|
109
|
+
|
|
110
|
+
- name: Run MFCQI self-analysis (Windows)
|
|
111
|
+
if: matrix.os == 'windows-latest'
|
|
112
|
+
shell: bash
|
|
113
|
+
env:
|
|
114
|
+
MFCQI_DISABLE_KEYRING: "1"
|
|
115
|
+
run: |
|
|
116
|
+
echo "📊 Running MFCQI analysis on itself..."
|
|
117
|
+
MFCQI_SCORE=$(uv run mfcqi analyze src/mfcqi --format json --silent | python -c "import sys, json; data = json.load(sys.stdin); print(data.get('mfcqi_score', 0.0))")
|
|
118
|
+
echo "Current MFCQI Score: $MFCQI_SCORE"
|
|
119
|
+
echo "✅ MFCQI analysis complete"
|
|
120
|
+
|
|
121
|
+
- name: Upload test results
|
|
122
|
+
uses: actions/upload-artifact@v4
|
|
123
|
+
if: always()
|
|
124
|
+
with:
|
|
125
|
+
name: test-results-${{ matrix.os }}-py${{ matrix.python-version }}
|
|
126
|
+
path: |
|
|
127
|
+
pytest-report.xml
|
|
128
|
+
coverage.xml
|
|
129
|
+
htmlcov/
|
|
130
|
+
|
|
131
|
+
quality-gate:
|
|
132
|
+
runs-on: ubuntu-latest
|
|
133
|
+
needs: test
|
|
134
|
+
if: github.event_name == 'pull_request'
|
|
135
|
+
|
|
136
|
+
steps:
|
|
137
|
+
- name: Checkout code
|
|
138
|
+
uses: actions/checkout@v4
|
|
139
|
+
|
|
140
|
+
- name: Set up Python
|
|
141
|
+
uses: actions/setup-python@v4
|
|
142
|
+
with:
|
|
143
|
+
python-version: '3.10'
|
|
144
|
+
|
|
145
|
+
- name: Install UV
|
|
146
|
+
uses: astral-sh/setup-uv@v3
|
|
147
|
+
|
|
148
|
+
- name: Install dependencies
|
|
149
|
+
run: |
|
|
150
|
+
uv sync
|
|
151
|
+
# Verify installation
|
|
152
|
+
uv run python -c "import mfcqi; print('MFCQI installed successfully')"
|
|
153
|
+
|
|
154
|
+
- name: Download coverage artifact
|
|
155
|
+
uses: actions/download-artifact@v4
|
|
156
|
+
with:
|
|
157
|
+
name: test-results-ubuntu-latest-py3.10
|
|
158
|
+
path: ./coverage-data
|
|
159
|
+
|
|
160
|
+
- name: Generate quality report
|
|
161
|
+
id: quality
|
|
162
|
+
env:
|
|
163
|
+
MFCQI_DISABLE_KEYRING: "1"
|
|
164
|
+
run: |
|
|
165
|
+
# Run comprehensive analysis
|
|
166
|
+
echo "📊 Generating comprehensive quality report..."
|
|
167
|
+
|
|
168
|
+
# Get MFCQI score
|
|
169
|
+
MFCQI_OUTPUT=$(uv run mfcqi analyze src/mfcqi --format json)
|
|
170
|
+
MFCQI_SCORE=$(echo "$MFCQI_OUTPUT" | python -c "import sys, json; data=json.load(sys.stdin); print(data['mfcqi_score'])")
|
|
171
|
+
|
|
172
|
+
# Get test coverage from artifact (avoid re-running tests)
|
|
173
|
+
if [ -f ./coverage-data/coverage.xml ]; then
|
|
174
|
+
COVERAGE=$(python -c "import xml.etree.ElementTree as ET; tree = ET.parse('./coverage-data/coverage.xml'); root = tree.getroot(); print(root.attrib['line-rate'])" | awk '{printf "%.0f", $1*100}')
|
|
175
|
+
else
|
|
176
|
+
echo "⚠️ Coverage report not found, skipping coverage check"
|
|
177
|
+
COVERAGE="0"
|
|
178
|
+
fi
|
|
179
|
+
|
|
180
|
+
# Get security issues count
|
|
181
|
+
SECURITY_ISSUES=$(uv run bandit -r src/ -f json 2>/dev/null | python -c "import sys, json; data=json.load(sys.stdin); print(len(data.get('results', [])))" || echo "0")
|
|
182
|
+
|
|
183
|
+
# Store results
|
|
184
|
+
echo "mfcqi_score=$MFCQI_SCORE" >> $GITHUB_OUTPUT
|
|
185
|
+
echo "coverage=$COVERAGE" >> $GITHUB_OUTPUT
|
|
186
|
+
echo "security_issues=$SECURITY_ISSUES" >> $GITHUB_OUTPUT
|
|
187
|
+
|
|
188
|
+
# Determine overall status (lowered coverage threshold to 80%)
|
|
189
|
+
if (( $(echo "$MFCQI_SCORE >= 0.75" | bc -l) )) && (( $(echo "$COVERAGE >= 80" | bc -l) )) && [ "$SECURITY_ISSUES" -eq 0 ]; then
|
|
190
|
+
echo "status=passing" >> $GITHUB_OUTPUT
|
|
191
|
+
echo "emoji=✅" >> $GITHUB_OUTPUT
|
|
192
|
+
else
|
|
193
|
+
echo "status=warning" >> $GITHUB_OUTPUT
|
|
194
|
+
echo "emoji=⚠️" >> $GITHUB_OUTPUT
|
|
195
|
+
fi
|
|
196
|
+
|
|
197
|
+
- name: Comment on PR
|
|
198
|
+
uses: actions/github-script@v7
|
|
199
|
+
with:
|
|
200
|
+
script: |
|
|
201
|
+
const mfcqiScore = '${{ steps.quality.outputs.mfcqi_score }}';
|
|
202
|
+
const coverage = '${{ steps.quality.outputs.coverage }}';
|
|
203
|
+
const securityIssues = '${{ steps.quality.outputs.security_issues }}';
|
|
204
|
+
const status = '${{ steps.quality.outputs.status }}';
|
|
205
|
+
const emoji = '${{ steps.quality.outputs.emoji }}';
|
|
206
|
+
|
|
207
|
+
const body = `## ${emoji} Code Quality Report
|
|
208
|
+
|
|
209
|
+
| Metric | Score | Status |
|
|
210
|
+
|--------|-------|--------|
|
|
211
|
+
| **MFCQI Score** | ${mfcqiScore} | ${parseFloat(mfcqiScore) >= 0.75 ? '✅ Good' : '⚠️ Needs Work'} |
|
|
212
|
+
| **Test Coverage** | ${coverage}% | ${parseFloat(coverage) >= 80 ? '✅ Good' : '⚠️ Needs Work'} |
|
|
213
|
+
| **Security Issues** | ${securityIssues} | ${securityIssues === '0' ? '✅ Clean' : '⚠️ Issues Found'} |
|
|
214
|
+
|
|
215
|
+
### Quality Gates
|
|
216
|
+
- **MFCQI Score**: ${parseFloat(mfcqiScore) >= 0.75 ? '✅' : '❌'} Minimum 0.75 (Current: ${mfcqiScore})
|
|
217
|
+
- **Test Coverage**: ${parseFloat(coverage) >= 80 ? '✅' : '❌'} Minimum 80% (Current: ${coverage}%)
|
|
218
|
+
- **Security**: ${securityIssues === '0' ? '✅' : '❌'} Zero vulnerabilities (Current: ${securityIssues})
|
|
219
|
+
|
|
220
|
+
${status === 'passing' ?
|
|
221
|
+
'🎉 **All quality gates passed!** This PR maintains high code quality standards.' :
|
|
222
|
+
'⚠️ **Some quality gates need attention.** Please review the metrics above before merging.'
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
<details>
|
|
226
|
+
<summary>📊 View detailed MFCQI breakdown</summary>
|
|
227
|
+
|
|
228
|
+
Run \`mfcqi analyze src/mfcqi\` locally to see detailed metrics breakdown.
|
|
229
|
+
</details>
|
|
230
|
+
`;
|
|
231
|
+
|
|
232
|
+
github.rest.issues.createComment({
|
|
233
|
+
issue_number: context.issue.number,
|
|
234
|
+
owner: context.repo.owner,
|
|
235
|
+
repo: context.repo.repo,
|
|
236
|
+
body: body
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
build:
|
|
240
|
+
runs-on: ubuntu-latest
|
|
241
|
+
needs: test
|
|
242
|
+
|
|
243
|
+
steps:
|
|
244
|
+
- name: Checkout code
|
|
245
|
+
uses: actions/checkout@v4
|
|
246
|
+
|
|
247
|
+
- name: Set up Python
|
|
248
|
+
uses: actions/setup-python@v4
|
|
249
|
+
with:
|
|
250
|
+
python-version: '3.10'
|
|
251
|
+
|
|
252
|
+
- name: Install build dependencies
|
|
253
|
+
run: |
|
|
254
|
+
pip install build twine
|
|
255
|
+
|
|
256
|
+
- name: Build package
|
|
257
|
+
run: |
|
|
258
|
+
python -m build
|
|
259
|
+
|
|
260
|
+
- name: Check package
|
|
261
|
+
run: |
|
|
262
|
+
python -m twine check dist/*
|
|
263
|
+
|
|
264
|
+
- name: Upload build artifacts
|
|
265
|
+
uses: actions/upload-artifact@v4
|
|
266
|
+
with:
|
|
267
|
+
name: python-package
|
|
268
|
+
path: dist/
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
name: Update MFCQI Badge
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [ main, master ]
|
|
6
|
+
paths:
|
|
7
|
+
- 'src/**'
|
|
8
|
+
- 'tests/**'
|
|
9
|
+
- 'pyproject.toml'
|
|
10
|
+
pull_request:
|
|
11
|
+
branches: [ main, master ]
|
|
12
|
+
workflow_dispatch: # Allow manual trigger
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
update-badge:
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
permissions:
|
|
18
|
+
contents: write # Required for git push to update badge file
|
|
19
|
+
|
|
20
|
+
steps:
|
|
21
|
+
- uses: actions/checkout@v3
|
|
22
|
+
with:
|
|
23
|
+
token: ${{ secrets.GITHUB_TOKEN }}
|
|
24
|
+
|
|
25
|
+
- name: Set up Python
|
|
26
|
+
uses: actions/setup-python@v4
|
|
27
|
+
with:
|
|
28
|
+
python-version: '3.13'
|
|
29
|
+
|
|
30
|
+
- name: Install UV
|
|
31
|
+
uses: astral-sh/setup-uv@v3
|
|
32
|
+
with:
|
|
33
|
+
enable-cache: true
|
|
34
|
+
|
|
35
|
+
- name: Install dependencies
|
|
36
|
+
run: |
|
|
37
|
+
uv sync
|
|
38
|
+
|
|
39
|
+
- name: Calculate MFCQI Score
|
|
40
|
+
id: mfcqi
|
|
41
|
+
env:
|
|
42
|
+
MFCQI_DISABLE_KEYRING: "1"
|
|
43
|
+
run: |
|
|
44
|
+
# Save old score if badge exists
|
|
45
|
+
mkdir -p .github/badges
|
|
46
|
+
if [ -f .github/badges/mfcqi.json ]; then
|
|
47
|
+
OLD_SCORE=$(python -c "import json; data=json.load(open('.github/badges/mfcqi.json')); print(data['message'].split()[0])" 2>/dev/null || echo "0.0")
|
|
48
|
+
else
|
|
49
|
+
OLD_SCORE="0.0"
|
|
50
|
+
fi
|
|
51
|
+
echo "old_score=$OLD_SCORE" >> $GITHUB_OUTPUT
|
|
52
|
+
|
|
53
|
+
# Calculate new score and save to JSON
|
|
54
|
+
uv run mfcqi badge src/mfcqi -f json -o .github/badges/mfcqi.json
|
|
55
|
+
|
|
56
|
+
# Extract new score
|
|
57
|
+
NEW_SCORE=$(python -c "import json; data=json.load(open('.github/badges/mfcqi.json')); print(data['message'].split()[0])")
|
|
58
|
+
echo "score=$NEW_SCORE" >> $GITHUB_OUTPUT
|
|
59
|
+
|
|
60
|
+
# Calculate delta and determine arrow
|
|
61
|
+
DELTA=$(python -c "print(f'{float('$NEW_SCORE') - float('$OLD_SCORE'):.3f}')")
|
|
62
|
+
echo "delta=$DELTA" >> $GITHUB_OUTPUT
|
|
63
|
+
|
|
64
|
+
# Determine arrow and format delta
|
|
65
|
+
if (( $(echo "$DELTA > 0" | bc -l) )); then
|
|
66
|
+
echo "arrow=↑" >> $GITHUB_OUTPUT
|
|
67
|
+
echo "delta_formatted=(+$DELTA)" >> $GITHUB_OUTPUT
|
|
68
|
+
elif (( $(echo "$DELTA < 0" | bc -l) )); then
|
|
69
|
+
echo "arrow=↓" >> $GITHUB_OUTPUT
|
|
70
|
+
echo "delta_formatted=($DELTA)" >> $GITHUB_OUTPUT
|
|
71
|
+
else
|
|
72
|
+
echo "arrow=→" >> $GITHUB_OUTPUT
|
|
73
|
+
echo "delta_formatted=(±0.000)" >> $GITHUB_OUTPUT
|
|
74
|
+
fi
|
|
75
|
+
|
|
76
|
+
# Determine if this is good enough
|
|
77
|
+
if (( $(echo "$NEW_SCORE >= 0.70" | bc -l) )); then
|
|
78
|
+
echo "status=passing" >> $GITHUB_OUTPUT
|
|
79
|
+
else
|
|
80
|
+
echo "status=failing" >> $GITHUB_OUTPUT
|
|
81
|
+
fi
|
|
82
|
+
|
|
83
|
+
- name: Commit badge update
|
|
84
|
+
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
|
85
|
+
run: |
|
|
86
|
+
git config --local user.email "github-actions[bot]@users.noreply.github.com"
|
|
87
|
+
git config --local user.name "github-actions[bot]"
|
|
88
|
+
git add .github/badges/mfcqi.json
|
|
89
|
+
git diff --quiet && git diff --staged --quiet || (git commit -m "chore: Update MFCQI badge: ${{ steps.mfcqi.outputs.score }} ${{ steps.mfcqi.outputs.arrow }} ${{ steps.mfcqi.outputs.delta_formatted }}" && git push)
|
|
90
|
+
|
|
91
|
+
- name: Comment on PR
|
|
92
|
+
if: github.event_name == 'pull_request'
|
|
93
|
+
uses: actions/github-script@v6
|
|
94
|
+
with:
|
|
95
|
+
script: |
|
|
96
|
+
const score = '${{ steps.mfcqi.outputs.score }}';
|
|
97
|
+
const oldScore = '${{ steps.mfcqi.outputs.old_score }}';
|
|
98
|
+
const arrow = '${{ steps.mfcqi.outputs.arrow }}';
|
|
99
|
+
const deltaFormatted = '${{ steps.mfcqi.outputs.delta_formatted }}';
|
|
100
|
+
const status = '${{ steps.mfcqi.outputs.status }}';
|
|
101
|
+
const emoji = status === 'passing' ? '✅' : '⚠️';
|
|
102
|
+
const trendEmoji = arrow === '↑' ? '📈' : arrow === '↓' ? '📉' : '➡️';
|
|
103
|
+
|
|
104
|
+
github.rest.issues.createComment({
|
|
105
|
+
issue_number: context.issue.number,
|
|
106
|
+
owner: context.repo.owner,
|
|
107
|
+
repo: context.repo.repo,
|
|
108
|
+
body: `## ${emoji} MFCQI Score: ${score} ${arrow} ${deltaFormatted}\n\n${trendEmoji} **Previous:** ${oldScore}\n\nCode quality ${status === 'passing' ? 'meets' : 'does not meet'} the minimum threshold (0.70).`
|
|
109
|
+
})
|