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.
Files changed (138) hide show
  1. mfcqi-0.0.1/.bandit +26 -0
  2. mfcqi-0.0.1/.env.example +28 -0
  3. mfcqi-0.0.1/.github/badges/mfcqi.json +7 -0
  4. mfcqi-0.0.1/.github/workflows/ci.yml +268 -0
  5. mfcqi-0.0.1/.github/workflows/mfcqi-badge.yml +109 -0
  6. mfcqi-0.0.1/.github/workflows/release.yml +350 -0
  7. mfcqi-0.0.1/.github/workflows/test-release.yml +211 -0
  8. mfcqi-0.0.1/.gitignore +46 -0
  9. mfcqi-0.0.1/.python-version +1 -0
  10. mfcqi-0.0.1/CONTRIBUTING.md +247 -0
  11. mfcqi-0.0.1/LICENSE +21 -0
  12. mfcqi-0.0.1/MANIFEST.in +21 -0
  13. mfcqi-0.0.1/Makefile +266 -0
  14. mfcqi-0.0.1/PKG-INFO +524 -0
  15. mfcqi-0.0.1/README.md +478 -0
  16. mfcqi-0.0.1/codecov.yml +27 -0
  17. mfcqi-0.0.1/docs/mfcqi.png +0 -0
  18. mfcqi-0.0.1/docs/research.md +1006 -0
  19. mfcqi-0.0.1/mypy.ini +33 -0
  20. mfcqi-0.0.1/pyproject.toml +107 -0
  21. mfcqi-0.0.1/ruff.toml +95 -0
  22. mfcqi-0.0.1/scripts/check_deps.py +61 -0
  23. mfcqi-0.0.1/src/mfcqi/__init__.py +12 -0
  24. mfcqi-0.0.1/src/mfcqi/__main__.py +12 -0
  25. mfcqi-0.0.1/src/mfcqi/analysis/__init__.py +1 -0
  26. mfcqi-0.0.1/src/mfcqi/analysis/config.py +97 -0
  27. mfcqi-0.0.1/src/mfcqi/analysis/diagnostics.py +79 -0
  28. mfcqi-0.0.1/src/mfcqi/analysis/engine.py +359 -0
  29. mfcqi-0.0.1/src/mfcqi/analysis/tools/__init__.py +1 -0
  30. mfcqi-0.0.1/src/mfcqi/analysis/tools/bandit_analyzer.py +136 -0
  31. mfcqi-0.0.1/src/mfcqi/analysis/tools/detect_secrets_analyzer.py +153 -0
  32. mfcqi-0.0.1/src/mfcqi/analysis/tools/pip_audit_analyzer.py +198 -0
  33. mfcqi-0.0.1/src/mfcqi/analysis/tools/pylint_analyzer.py +159 -0
  34. mfcqi-0.0.1/src/mfcqi/analysis/tools/ruff_analyzer.py +217 -0
  35. mfcqi-0.0.1/src/mfcqi/calculator.py +361 -0
  36. mfcqi-0.0.1/src/mfcqi/cli/__init__.py +3 -0
  37. mfcqi-0.0.1/src/mfcqi/cli/commands/__init__.py +3 -0
  38. mfcqi-0.0.1/src/mfcqi/cli/commands/analyze.py +152 -0
  39. mfcqi-0.0.1/src/mfcqi/cli/commands/analyze_helpers.py +170 -0
  40. mfcqi-0.0.1/src/mfcqi/cli/commands/badge.py +106 -0
  41. mfcqi-0.0.1/src/mfcqi/cli/commands/badge_templates.py +74 -0
  42. mfcqi-0.0.1/src/mfcqi/cli/commands/config.py +271 -0
  43. mfcqi-0.0.1/src/mfcqi/cli/commands/models.py +341 -0
  44. mfcqi-0.0.1/src/mfcqi/cli/main.py +44 -0
  45. mfcqi-0.0.1/src/mfcqi/cli/utils/__init__.py +3 -0
  46. mfcqi-0.0.1/src/mfcqi/cli/utils/config_manager.py +166 -0
  47. mfcqi-0.0.1/src/mfcqi/cli/utils/llm_handler.py +394 -0
  48. mfcqi-0.0.1/src/mfcqi/cli/utils/output.py +635 -0
  49. mfcqi-0.0.1/src/mfcqi/core/__init__.py +1 -0
  50. mfcqi-0.0.1/src/mfcqi/core/file_utils.py +74 -0
  51. mfcqi-0.0.1/src/mfcqi/core/metric.py +135 -0
  52. mfcqi-0.0.1/src/mfcqi/core/paradigm_detector.py +332 -0
  53. mfcqi-0.0.1/src/mfcqi/metrics/__init__.py +1 -0
  54. mfcqi-0.0.1/src/mfcqi/metrics/code_smell.py +198 -0
  55. mfcqi-0.0.1/src/mfcqi/metrics/cognitive.py +292 -0
  56. mfcqi-0.0.1/src/mfcqi/metrics/cohesion.py +176 -0
  57. mfcqi-0.0.1/src/mfcqi/metrics/complexity.py +263 -0
  58. mfcqi-0.0.1/src/mfcqi/metrics/coupling.py +226 -0
  59. mfcqi-0.0.1/src/mfcqi/metrics/dependency_security.py +156 -0
  60. mfcqi-0.0.1/src/mfcqi/metrics/dit.py +195 -0
  61. mfcqi-0.0.1/src/mfcqi/metrics/documentation.py +111 -0
  62. mfcqi-0.0.1/src/mfcqi/metrics/duplication.py +247 -0
  63. mfcqi-0.0.1/src/mfcqi/metrics/maintainability.py +109 -0
  64. mfcqi-0.0.1/src/mfcqi/metrics/mhf.py +129 -0
  65. mfcqi-0.0.1/src/mfcqi/metrics/rfc.py +184 -0
  66. mfcqi-0.0.1/src/mfcqi/metrics/secrets_exposure.py +153 -0
  67. mfcqi-0.0.1/src/mfcqi/metrics/security.py +747 -0
  68. mfcqi-0.0.1/src/mfcqi/metrics/type_safety.py +83 -0
  69. mfcqi-0.0.1/src/mfcqi/py.typed +0 -0
  70. mfcqi-0.0.1/src/mfcqi/quality_gates.py +154 -0
  71. mfcqi-0.0.1/src/mfcqi/smell_detection/__init__.py +25 -0
  72. mfcqi-0.0.1/src/mfcqi/smell_detection/aggregator.py +147 -0
  73. mfcqi-0.0.1/src/mfcqi/smell_detection/ast_test_smells.py +230 -0
  74. mfcqi-0.0.1/src/mfcqi/smell_detection/detector_base.py +65 -0
  75. mfcqi-0.0.1/src/mfcqi/smell_detection/models.py +87 -0
  76. mfcqi-0.0.1/src/mfcqi/smell_detection/pyexamine.py +265 -0
  77. mfcqi-0.0.1/src/mfcqi/templates/code_quality_analysis.j2 +127 -0
  78. mfcqi-0.0.1/src/mfcqi/templates/fallback_recommendations.j2 +126 -0
  79. mfcqi-0.0.1/tests/conftest.py +18 -0
  80. mfcqi-0.0.1/tests/fixtures/__init__.py +1 -0
  81. mfcqi-0.0.1/tests/fixtures/security_fixtures.py +566 -0
  82. mfcqi-0.0.1/tests/integration/__init__.py +3 -0
  83. mfcqi-0.0.1/tests/integration/test_cli_integration.py +346 -0
  84. mfcqi-0.0.1/tests/integration/test_llm_analysis.py +74 -0
  85. mfcqi-0.0.1/tests/integration/test_llm_providers.py.disabled +282 -0
  86. mfcqi-0.0.1/tests/schemas/sarif-schema-2.1.0.json +3389 -0
  87. mfcqi-0.0.1/tests/test_analysis_config.py +235 -0
  88. mfcqi-0.0.1/tests/test_analysis_diagnostics.py +269 -0
  89. mfcqi-0.0.1/tests/test_analyze_helpers.py +247 -0
  90. mfcqi-0.0.1/tests/test_ast_test_smell_detector.py +220 -0
  91. mfcqi-0.0.1/tests/test_badge_command.py +139 -0
  92. mfcqi-0.0.1/tests/test_bandit_api_integration.py +226 -0
  93. mfcqi-0.0.1/tests/test_bandit_integration.py +445 -0
  94. mfcqi-0.0.1/tests/test_cli_import_smoke.py +150 -0
  95. mfcqi-0.0.1/tests/test_code_duplication.py +170 -0
  96. mfcqi-0.0.1/tests/test_code_smell_data.py +131 -0
  97. mfcqi-0.0.1/tests/test_code_smell_density.py +178 -0
  98. mfcqi-0.0.1/tests/test_cognitive_complexity.py +347 -0
  99. mfcqi-0.0.1/tests/test_cognitive_complexity_hotspots.py +274 -0
  100. mfcqi-0.0.1/tests/test_comprehensive_integration.py +349 -0
  101. mfcqi-0.0.1/tests/test_config_command.py +198 -0
  102. mfcqi-0.0.1/tests/test_core_metric.py +221 -0
  103. mfcqi-0.0.1/tests/test_cyclomatic_complexity.py +117 -0
  104. mfcqi-0.0.1/tests/test_dependency_security.py +115 -0
  105. mfcqi-0.0.1/tests/test_detect_secrets_api_integration.py +209 -0
  106. mfcqi-0.0.1/tests/test_dit_metric.py +174 -0
  107. mfcqi-0.0.1/tests/test_documentation_coverage.py +281 -0
  108. mfcqi-0.0.1/tests/test_engine_context_building.py +384 -0
  109. mfcqi-0.0.1/tests/test_file_utils.py +138 -0
  110. mfcqi-0.0.1/tests/test_halstead_complexity.py +151 -0
  111. mfcqi-0.0.1/tests/test_llm_analysis_engine.py +233 -0
  112. mfcqi-0.0.1/tests/test_maintainability_index.py +178 -0
  113. mfcqi-0.0.1/tests/test_metrics_integration.py +185 -0
  114. mfcqi-0.0.1/tests/test_mfcqi_calculator.py +562 -0
  115. mfcqi-0.0.1/tests/test_mhf_metric.py +130 -0
  116. mfcqi-0.0.1/tests/test_models_command.py +64 -0
  117. mfcqi-0.0.1/tests/test_output.py +286 -0
  118. mfcqi-0.0.1/tests/test_paradigm_detector.py +512 -0
  119. mfcqi-0.0.1/tests/test_pip_audit_api_integration.py +71 -0
  120. mfcqi-0.0.1/tests/test_pyexamine_detector.py +177 -0
  121. mfcqi-0.0.1/tests/test_pylint_api_integration.py +253 -0
  122. mfcqi-0.0.1/tests/test_pylint_integration.py +331 -0
  123. mfcqi-0.0.1/tests/test_quality_gates.py +350 -0
  124. mfcqi-0.0.1/tests/test_real_world_fixtures.py +313 -0
  125. mfcqi-0.0.1/tests/test_rfc_metric.py +149 -0
  126. mfcqi-0.0.1/tests/test_ruff_integration.py +406 -0
  127. mfcqi-0.0.1/tests/test_sarif_integration.py +121 -0
  128. mfcqi-0.0.1/tests/test_sarif_output.py +256 -0
  129. mfcqi-0.0.1/tests/test_secrets_exposure.py +113 -0
  130. mfcqi-0.0.1/tests/test_security_comprehensive.py +123 -0
  131. mfcqi-0.0.1/tests/test_security_integration.py +106 -0
  132. mfcqi-0.0.1/tests/test_security_metric.py +468 -0
  133. mfcqi-0.0.1/tests/test_smell_aggregator.py +291 -0
  134. mfcqi-0.0.1/tests/test_smell_detection_integration.py +248 -0
  135. mfcqi-0.0.1/tests/test_smell_detector_base.py +107 -0
  136. mfcqi-0.0.1/tests/test_type_safety.py +190 -0
  137. mfcqi-0.0.1/tests/test_type_safety_integration.py +140 -0
  138. 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
@@ -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,7 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "label": "MFCQI",
4
+ "message": "0.88 (excellent)",
5
+ "color": "brightgreen",
6
+ "cacheSeconds": 3600
7
+ }
@@ -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
+ })