chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: NomenAK
|
||||
patreon: # SuperClaude
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # superclaude
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
||||
polar: # Replace with a single Polar username
|
||||
buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
|
||||
thanks_dev: # Replace with a single thanks.dev username
|
||||
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||
@@ -0,0 +1,52 @@
|
||||
# Pull Request
|
||||
|
||||
## Summary
|
||||
|
||||
<!-- Briefly describe the purpose of this PR -->
|
||||
|
||||
## Changes
|
||||
|
||||
<!-- List the main changes -->
|
||||
-
|
||||
|
||||
## Related Issue
|
||||
|
||||
<!-- Reference related issue numbers if applicable -->
|
||||
Closes #
|
||||
|
||||
## Checklist
|
||||
|
||||
### Git Workflow
|
||||
- [ ] External contributors: Followed Fork → topic branch → upstream PR flow
|
||||
- [ ] Collaborators: Used topic branch (no direct commits to main)
|
||||
- [ ] Rebased on upstream/main (`git rebase upstream/main`, no conflicts)
|
||||
- [ ] Commit messages follow Conventional Commits (`feat:`, `fix:`, `docs:`, etc.)
|
||||
|
||||
### Code Quality
|
||||
- [ ] Changes are limited to a single purpose (not a mega-PR; aim for ~200 lines diff)
|
||||
- [ ] Follows existing code conventions and patterns
|
||||
- [ ] Added appropriate tests for new features/fixes
|
||||
- [ ] Lint/Format/Typecheck all pass
|
||||
- [ ] CI/CD pipeline succeeds (green status)
|
||||
|
||||
### Security
|
||||
- [ ] No secrets or credentials committed
|
||||
- [ ] Necessary files excluded via `.gitignore`
|
||||
- [ ] No breaking changes, or if so: `!` commit + MIGRATION.md documented
|
||||
|
||||
### Documentation
|
||||
- [ ] Updated documentation as needed (README, CLAUDE.md, docs/, etc.)
|
||||
- [ ] Added comments for complex logic
|
||||
- [ ] API changes are properly documented
|
||||
|
||||
## How to Test
|
||||
|
||||
<!-- Describe how to verify this PR works -->
|
||||
|
||||
## Screenshots (if applicable)
|
||||
|
||||
<!-- Attach screenshots for UI changes -->
|
||||
|
||||
## Notes
|
||||
|
||||
<!-- Anything you want reviewers to know, technical decisions, etc. -->
|
||||
@@ -0,0 +1,158 @@
|
||||
# GitHub Actions Workflows
|
||||
|
||||
This directory contains CI/CD workflows for SuperClaude Framework.
|
||||
|
||||
## Workflows
|
||||
|
||||
### 1. **test.yml** - Comprehensive Test Suite
|
||||
**Triggers**: Push/PR to `master` or `integration`, manual dispatch
|
||||
**Jobs**:
|
||||
- **test**: Run tests on Python 3.10, 3.11, 3.12
|
||||
- Install UV and dependencies
|
||||
- Run full test suite
|
||||
- Generate coverage report (Python 3.10 only)
|
||||
- Upload to Codecov
|
||||
- **lint**: Run ruff linter and format checker
|
||||
- **plugin-check**: Verify pytest plugin loads correctly
|
||||
- **doctor-check**: Run `superclaude doctor` health check
|
||||
- **test-summary**: Aggregate results from all jobs
|
||||
|
||||
**Status Badge**:
|
||||
```markdown
|
||||
[](https://github.com/SuperClaude-Org/SuperClaude_Framework/actions/workflows/test.yml)
|
||||
```
|
||||
|
||||
### 2. **quick-check.yml** - Fast PR Feedback
|
||||
**Triggers**: Pull requests to `master` or `integration`
|
||||
**Jobs**:
|
||||
- **quick-test**: Fast check on Python 3.10 only
|
||||
- Run unit tests only (faster)
|
||||
- Run linter
|
||||
- Check formatting
|
||||
- Verify plugin loads
|
||||
- 10 minute timeout
|
||||
|
||||
**Purpose**: Provide rapid feedback on PRs before running full test matrix.
|
||||
|
||||
### 3. **publish-pypi.yml** (Existing)
|
||||
**Triggers**: Manual or release tags
|
||||
**Purpose**: Publish package to PyPI
|
||||
|
||||
### 4. **readme-quality-check.yml** (Existing)
|
||||
**Triggers**: Push/PR affecting README files
|
||||
**Purpose**: Validate README quality and consistency
|
||||
|
||||
## Local Testing
|
||||
|
||||
Before pushing, run these commands locally:
|
||||
|
||||
```bash
|
||||
# Run full test suite
|
||||
uv run pytest -v
|
||||
|
||||
# Run with coverage
|
||||
uv run pytest --cov=superclaude --cov-report=term
|
||||
|
||||
# Run linter
|
||||
uv run ruff check src/ tests/
|
||||
|
||||
# Check formatting
|
||||
uv run ruff format --check src/ tests/
|
||||
|
||||
# Auto-fix formatting
|
||||
uv run ruff format src/ tests/
|
||||
|
||||
# Verify plugin loads
|
||||
uv run pytest --trace-config | grep superclaude
|
||||
|
||||
# Run doctor check
|
||||
uv run superclaude doctor --verbose
|
||||
```
|
||||
|
||||
## CI/CD Pipeline
|
||||
|
||||
```
|
||||
┌─────────────────────┐
|
||||
│ Push/PR Created │
|
||||
└──────────┬──────────┘
|
||||
│
|
||||
├─────────────────────────┐
|
||||
│ │
|
||||
┌──────▼──────┐ ┌───────▼────────┐
|
||||
│ Quick Check │ │ Full Test │
|
||||
│ (PR only) │ │ Matrix │
|
||||
│ │ │ │
|
||||
│ • Unit tests│ │ • Python 3.10 │
|
||||
│ • Lint │ │ • Python 3.11 │
|
||||
│ • Format │ │ • Python 3.12 │
|
||||
│ │ │ • Coverage │
|
||||
│ ~2-3 min │ │ • Lint │
|
||||
└─────────────┘ │ • Plugin check │
|
||||
│ • Doctor check │
|
||||
│ │
|
||||
│ ~5-8 min │
|
||||
└────────────────┘
|
||||
```
|
||||
|
||||
## Coverage Reporting
|
||||
|
||||
Coverage reports are generated for Python 3.10 and uploaded to Codecov.
|
||||
|
||||
To view coverage locally:
|
||||
```bash
|
||||
uv run pytest --cov=superclaude --cov-report=html
|
||||
open htmlcov/index.html
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Workflow fails with "UV not found"
|
||||
- UV is installed in each job via `curl -LsSf https://astral.sh/uv/install.sh | sh`
|
||||
- If installation fails, check UV's status page
|
||||
|
||||
### Tests fail locally but pass in CI (or vice versa)
|
||||
- Check Python version: `python --version`
|
||||
- Reinstall dependencies: `uv pip install -e ".[dev]"`
|
||||
- Clear caches: `rm -rf .pytest_cache .venv`
|
||||
|
||||
### Plugin not loading in CI
|
||||
- Verify entry point in `pyproject.toml`: `[project.entry-points.pytest11]`
|
||||
- Check plugin is installed: `uv run pytest --trace-config`
|
||||
|
||||
### Coverage upload fails
|
||||
- This is non-blocking (fail_ci_if_error: false)
|
||||
- Check Codecov token in repository secrets
|
||||
|
||||
## Maintenance
|
||||
|
||||
### Adding a New Workflow
|
||||
1. Create new `.yml` file in this directory
|
||||
2. Follow existing structure (checkout, setup-python, install UV)
|
||||
3. Add status badge to README.md if needed
|
||||
4. Document in this file
|
||||
|
||||
### Updating Python Versions
|
||||
1. Edit `matrix.python-version` in `test.yml`
|
||||
2. Update `pyproject.toml` classifiers
|
||||
3. Test locally with new version first
|
||||
|
||||
### Modifying Test Strategy
|
||||
- **quick-check.yml**: For fast PR feedback (unit tests only)
|
||||
- **test.yml**: For comprehensive validation (full matrix)
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Keep workflows fast**: Use caching, parallel jobs
|
||||
2. **Fail fast**: Use `-x` flag in pytest for quick-check
|
||||
3. **Clear names**: Job and step names should be descriptive
|
||||
4. **Version pinning**: Pin action versions (@v4, @v5)
|
||||
5. **Matrix testing**: Test on multiple Python versions
|
||||
6. **Non-blocking coverage**: Don't fail on coverage upload errors
|
||||
7. **Manual triggers**: Add `workflow_dispatch` for debugging
|
||||
|
||||
## Resources
|
||||
|
||||
- [GitHub Actions Documentation](https://docs.github.com/en/actions)
|
||||
- [UV Documentation](https://github.com/astral-sh/uv)
|
||||
- [Pytest Documentation](https://docs.pytest.org/)
|
||||
- [SuperClaude Testing Guide](../../docs/developer-guide/testing-debugging.md)
|
||||
@@ -0,0 +1,172 @@
|
||||
name: Publish to PyPI
|
||||
|
||||
on:
|
||||
# Trigger on new releases
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
# Allow manual triggering
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
target:
|
||||
description: 'Publication target'
|
||||
required: true
|
||||
default: 'testpypi'
|
||||
type: choice
|
||||
options:
|
||||
- testpypi
|
||||
- pypi
|
||||
|
||||
# Restrict permissions for security
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build-and-publish:
|
||||
name: Build and publish Python package
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
name: ${{ github.event_name == 'release' && 'pypi' || 'testpypi' }}
|
||||
url: ${{ github.event_name == 'release' && 'https://pypi.org/p/SuperClaude' || 'https://test.pypi.org/p/SuperClaude' }}
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
# Fetch full history for proper version detection
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
cache: 'pip'
|
||||
|
||||
- name: Install build dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
python -m pip install build twine toml
|
||||
|
||||
- name: Verify package structure
|
||||
run: |
|
||||
echo "📦 Checking package structure..."
|
||||
ls -la
|
||||
echo "🔍 Checking SuperClaude package..."
|
||||
ls -la src/superclaude/
|
||||
echo "🔍 Verifying src directory..."
|
||||
ls -la src/
|
||||
|
||||
# Verify version consistency
|
||||
echo "📋 Checking version consistency..."
|
||||
python -c "
|
||||
import toml
|
||||
import sys
|
||||
sys.path.insert(0, 'src')
|
||||
|
||||
# Load pyproject.toml version
|
||||
with open('pyproject.toml', 'r') as f:
|
||||
pyproject = toml.load(f)
|
||||
pyproject_version = pyproject['project']['version']
|
||||
|
||||
# Load package version
|
||||
from superclaude import __version__
|
||||
|
||||
print(f'pyproject.toml version: {pyproject_version}')
|
||||
print(f'Package version: {__version__}')
|
||||
|
||||
if pyproject_version != __version__:
|
||||
print('❌ Version mismatch!')
|
||||
sys.exit(1)
|
||||
else:
|
||||
print('✅ Versions match')
|
||||
"
|
||||
|
||||
- name: Clean previous builds
|
||||
run: |
|
||||
rm -rf dist/ build/ *.egg-info/
|
||||
|
||||
- name: Build package
|
||||
run: |
|
||||
echo "🔨 Building package..."
|
||||
python -m build
|
||||
echo "📦 Built files:"
|
||||
ls -la dist/
|
||||
|
||||
- name: Validate package
|
||||
run: |
|
||||
echo "🔍 Validating package..."
|
||||
python -m twine check dist/*
|
||||
|
||||
# Upload to TestPyPI for testing (manual trigger or non-release)
|
||||
- name: Upload to TestPyPI
|
||||
if: github.event_name == 'workflow_dispatch' && github.event.inputs.target == 'testpypi'
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
repository-url: https://test.pypi.org/legacy/
|
||||
password: ${{ secrets.TEST_PYPI_API_TOKEN }}
|
||||
print-hash: true
|
||||
|
||||
# Upload to production PyPI (only on releases)
|
||||
- name: Upload to PyPI
|
||||
if: github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.target == 'pypi')
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
password: ${{ secrets.PYPI_API_TOKEN }}
|
||||
print-hash: true
|
||||
|
||||
- name: Create deployment summary
|
||||
if: always()
|
||||
run: |
|
||||
echo "## 📦 SuperClaude Package Deployment" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Property | Value |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "|----------|-------|" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Target | ${{ github.event_name == 'release' && 'PyPI (Production)' || github.event.inputs.target || 'TestPyPI' }} |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Trigger | ${{ github.event_name }} |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Version | $(python -c 'import sys; sys.path.insert(0, \"src\"); from superclaude import __version__; print(__version__)') |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Commit | ${{ github.sha }} |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
if [ "${{ github.event_name }}" == "release" ]; then
|
||||
echo "🎉 **Production release published to PyPI!**" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Install with: \`pip install SuperClaude\`" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "🧪 **Test release published to TestPyPI**" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Test install with: \`pip install --index-url https://test.pypi.org/simple/ SuperClaude\`" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
test-installation:
|
||||
name: Test package installation
|
||||
needs: build-and-publish
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'workflow_dispatch' && github.event.inputs.target == 'testpypi'
|
||||
|
||||
steps:
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
|
||||
- name: Test installation from TestPyPI
|
||||
run: |
|
||||
echo "🧪 Testing installation from TestPyPI..."
|
||||
|
||||
# Wait a bit for the package to be available
|
||||
sleep 30
|
||||
|
||||
# Install from TestPyPI
|
||||
pip install --index-url https://test.pypi.org/simple/ \
|
||||
--extra-index-url https://pypi.org/simple/ \
|
||||
SuperClaude
|
||||
|
||||
# Test basic import
|
||||
python -c "
|
||||
import superclaude
|
||||
print(f'✅ Successfully imported SuperClaude v{superclaude.__version__}')
|
||||
|
||||
# Test CLI entry point
|
||||
import subprocess
|
||||
result = subprocess.run(['SuperClaude', '--version'], capture_output=True, text=True)
|
||||
print(f'✅ CLI version: {result.stdout.strip()}')
|
||||
"
|
||||
|
||||
echo "✅ Installation test completed successfully!"
|
||||
@@ -0,0 +1,140 @@
|
||||
name: Pull Sync from Framework
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 */6 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
sync-and-isolate:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- name: Checkout Plugin Repository (Target)
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
path: plugin-repo
|
||||
|
||||
- name: Check for Framework updates
|
||||
id: check-updates
|
||||
run: |
|
||||
FRAMEWORK_HEAD=$(git ls-remote https://github.com/SuperClaude-Org/SuperClaude_Framework HEAD | cut -f1)
|
||||
echo "Current framework HEAD: $FRAMEWORK_HEAD"
|
||||
echo "framework-head=$FRAMEWORK_HEAD" >> $GITHUB_OUTPUT
|
||||
|
||||
LAST_SYNCED=""
|
||||
if [ -f "plugin-repo/docs/.framework-sync-commit" ]; then
|
||||
LAST_SYNCED=$(cat plugin-repo/docs/.framework-sync-commit)
|
||||
echo "Last synced commit: $LAST_SYNCED"
|
||||
else
|
||||
echo "No previous sync state - will run sync"
|
||||
fi
|
||||
|
||||
if [ "$FRAMEWORK_HEAD" = "$LAST_SYNCED" ] && [ "${{ github.event_name }}" != "workflow_dispatch" ]; then
|
||||
echo "✅ Framework is up to date - skipping sync"
|
||||
echo "has-updates=false" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "🔄 Framework has updates - proceeding with sync"
|
||||
echo "has-updates=true" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Checkout Framework Repository (Source)
|
||||
if: steps.check-updates.outputs.has-updates == 'true'
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: SuperClaude-Org/SuperClaude_Framework
|
||||
path: framework-src
|
||||
|
||||
- name: Set up Python
|
||||
if: steps.check-updates.outputs.has-updates == 'true'
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.10'
|
||||
|
||||
- name: Run Transformation & Sync Logic
|
||||
if: steps.check-updates.outputs.has-updates == 'true'
|
||||
run: |
|
||||
cd plugin-repo
|
||||
python3 scripts/sync_from_framework.py
|
||||
|
||||
- name: Verify protected files are unchanged
|
||||
if: steps.check-updates.outputs.has-updates == 'true'
|
||||
working-directory: plugin-repo
|
||||
run: |
|
||||
# Note: plugin.json removed from list as it is updated by the MCP merge script
|
||||
PROTECTED=(
|
||||
"README.md" "README-ja.md" "README-zh.md"
|
||||
"BACKUP_GUIDE.md" "MIGRATION_GUIDE.md" "SECURITY.md"
|
||||
"CLAUDE.md" "LICENSE" ".gitignore"
|
||||
".claude-plugin/marketplace.json"
|
||||
"core/" "modes/"
|
||||
)
|
||||
VIOLATIONS=()
|
||||
for path in "${PROTECTED[@]}"; do
|
||||
if git diff --name-only HEAD -- "$path" | grep -q .; then
|
||||
VIOLATIONS+=("$path")
|
||||
fi
|
||||
done
|
||||
if [ ${#VIOLATIONS[@]} -gt 0 ]; then
|
||||
echo "🚨 PROTECTION VIOLATION: sync modified Plugin-owned files:"
|
||||
for v in "${VIOLATIONS[@]}"; do echo " • $v"; done
|
||||
echo ""
|
||||
echo "Fix: check SYNC_MAPPINGS in scripts/sync_from_framework.py"
|
||||
exit 1
|
||||
fi
|
||||
echo "🔒 Protection check passed — no Plugin-owned files were modified"
|
||||
|
||||
- name: Save framework sync state
|
||||
if: steps.check-updates.outputs.has-updates == 'true'
|
||||
run: |
|
||||
echo "${{ steps.check-updates.outputs.framework-head }}" > plugin-repo/docs/.framework-sync-commit
|
||||
echo "✅ Saved framework commit: ${{ steps.check-updates.outputs.framework-head }}"
|
||||
|
||||
- name: Commit Changes to Sync Branch
|
||||
if: steps.check-updates.outputs.has-updates == 'true'
|
||||
id: commit-changes
|
||||
working-directory: plugin-repo
|
||||
run: |
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
SYNC_BRANCH="framework-sync/$(date +'%Y-%m-%d-%H%M')"
|
||||
git checkout -b "$SYNC_BRANCH"
|
||||
|
||||
git add commands/ agents/ .claude-plugin/plugin.json plugin.json
|
||||
|
||||
if [ -f "docs/.framework-sync-commit" ]; then
|
||||
git add -f docs/.framework-sync-commit
|
||||
fi
|
||||
|
||||
if ! git diff --cached --quiet; then
|
||||
git commit -m "chore: automated sync from framework [${{ steps.check-updates.outputs.framework-head }}]"
|
||||
git push origin "$SYNC_BRANCH"
|
||||
echo "has-changes=true" >> $GITHUB_OUTPUT
|
||||
echo "sync-branch=$SYNC_BRANCH" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "No changes detected."
|
||||
echo "has-changes=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Create Pull Request for Review
|
||||
if: steps.commit-changes.outputs.has-changes == 'true'
|
||||
working-directory: plugin-repo
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
gh pr create \
|
||||
--title "chore: framework sync ${{ steps.check-updates.outputs.framework-head }}" \
|
||||
--body "## Automated Framework Sync
|
||||
|
||||
Synced from upstream framework commit: \`${{ steps.check-updates.outputs.framework-head }}\`
|
||||
|
||||
**Review required before merge.** This PR was created automatically by the framework sync workflow. Please review the changes to ensure no unexpected modifications were introduced.
|
||||
|
||||
---
|
||||
*Auto-generated by pull-sync-framework workflow*" \
|
||||
--base main \
|
||||
--head "${{ steps.commit-changes.outputs.sync-branch }}"
|
||||
@@ -0,0 +1,54 @@
|
||||
name: Quick Check
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [master, integration]
|
||||
|
||||
jobs:
|
||||
quick-test:
|
||||
name: Quick Test (Python 3.10)
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.10"
|
||||
|
||||
- name: Install UV
|
||||
run: |
|
||||
curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
uv pip install --system -e ".[dev]"
|
||||
|
||||
- name: Run unit tests only
|
||||
run: |
|
||||
pytest tests/unit/ -v --tb=short -x
|
||||
|
||||
- name: Run linter
|
||||
run: |
|
||||
ruff check src/ tests/
|
||||
|
||||
- name: Check formatting
|
||||
run: |
|
||||
ruff format --check src/ tests/
|
||||
|
||||
- name: Verify pytest plugin
|
||||
run: |
|
||||
pytest --trace-config 2>&1 | grep -q "superclaude"
|
||||
|
||||
- name: Summary
|
||||
if: success()
|
||||
run: |
|
||||
echo "✅ Quick checks passed!"
|
||||
echo " - Unit tests: PASSED"
|
||||
echo " - Linting: PASSED"
|
||||
echo " - Formatting: PASSED"
|
||||
echo " - Plugin: LOADED"
|
||||
@@ -0,0 +1,312 @@
|
||||
name: README Quality Check
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'README*.md'
|
||||
- 'Docs/**/*.md'
|
||||
push:
|
||||
branches: [main, master, develop]
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
readme-quality-check:
|
||||
name: Multi-language README Quality Assessment
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install requests beautifulsoup4 pyyaml
|
||||
|
||||
- name: Create quality checker script
|
||||
run: |
|
||||
cat > readme_checker.py << 'EOF'
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
SuperClaude Multi-language README Quality Checker
|
||||
Checks version sync, link validity, and structural consistency
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import requests
|
||||
import json
|
||||
from pathlib import Path
|
||||
from urllib.parse import urljoin
|
||||
|
||||
class READMEQualityChecker:
|
||||
def __init__(self):
|
||||
self.readme_files = ['README.md', 'README-zh.md', 'README-ja.md', 'README-kr.md']
|
||||
self.results = {
|
||||
'structure_consistency': [],
|
||||
'link_validation': [],
|
||||
'translation_sync': [],
|
||||
'overall_score': 0
|
||||
}
|
||||
|
||||
def check_structure_consistency(self):
|
||||
"""Check structural consistency"""
|
||||
print("🔍 Checking structural consistency...")
|
||||
|
||||
structures = {}
|
||||
for file in self.readme_files:
|
||||
if os.path.exists(file):
|
||||
with open(file, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
# Extract heading structure
|
||||
headers = re.findall(r'^#{1,6}\s+(.+)$', content, re.MULTILINE)
|
||||
structures[file] = len(headers)
|
||||
|
||||
# Compare structural differences
|
||||
line_counts = [structures.get(f, 0) for f in self.readme_files if f in structures]
|
||||
if line_counts:
|
||||
max_diff = max(line_counts) - min(line_counts)
|
||||
consistency_score = max(0, 100 - (max_diff * 5))
|
||||
|
||||
self.results['structure_consistency'] = {
|
||||
'score': consistency_score,
|
||||
'details': structures,
|
||||
'status': 'PASS' if consistency_score >= 90 else 'WARN'
|
||||
}
|
||||
|
||||
print(f"✅ Structural consistency: {consistency_score}/100")
|
||||
for file, count in structures.items():
|
||||
print(f" {file}: {count} headers")
|
||||
|
||||
def check_link_validation(self):
|
||||
"""Check link validity"""
|
||||
print("🔗 Checking link validity...")
|
||||
|
||||
all_links = {}
|
||||
broken_links = []
|
||||
|
||||
for file in self.readme_files:
|
||||
if os.path.exists(file):
|
||||
with open(file, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
# Extract all links
|
||||
links = re.findall(r'\[([^\]]+)\]\(([^)]+)\)', content)
|
||||
all_links[file] = []
|
||||
|
||||
for text, url in links:
|
||||
link_info = {'text': text, 'url': url, 'status': 'unknown'}
|
||||
|
||||
# Check local file links
|
||||
if not url.startswith(('http://', 'https://', '#')):
|
||||
if os.path.exists(url):
|
||||
link_info['status'] = 'valid'
|
||||
else:
|
||||
link_info['status'] = 'broken'
|
||||
broken_links.append(f"{file}: {url}")
|
||||
|
||||
# HTTP link check (simplified)
|
||||
elif url.startswith(('http://', 'https://')):
|
||||
try:
|
||||
# Only check key links to avoid excessive requests
|
||||
if any(domain in url for domain in ['github.com', 'pypi.org', 'npmjs.com']):
|
||||
response = requests.head(url, timeout=10, allow_redirects=True)
|
||||
link_info['status'] = 'valid' if response.status_code < 400 else 'broken'
|
||||
else:
|
||||
link_info['status'] = 'skipped'
|
||||
except:
|
||||
link_info['status'] = 'error'
|
||||
else:
|
||||
link_info['status'] = 'anchor'
|
||||
|
||||
all_links[file].append(link_info)
|
||||
|
||||
# Calculate link health score
|
||||
total_links = sum(len(links) for links in all_links.values())
|
||||
broken_count = len(broken_links)
|
||||
link_score = max(0, 100 - (broken_count * 10)) if total_links > 0 else 100
|
||||
|
||||
self.results['link_validation'] = {
|
||||
'score': link_score,
|
||||
'total_links': total_links,
|
||||
'broken_links': broken_count,
|
||||
'broken_list': broken_links[:10], # Show max 10
|
||||
'status': 'PASS' if link_score >= 80 else 'FAIL'
|
||||
}
|
||||
|
||||
print(f"✅ Link validity: {link_score}/100")
|
||||
print(f" Total links: {total_links}")
|
||||
print(f" Broken links: {broken_count}")
|
||||
|
||||
def check_translation_sync(self):
|
||||
"""Check translation sync"""
|
||||
print("🌍 Checking translation sync...")
|
||||
|
||||
if not all(os.path.exists(f) for f in self.readme_files):
|
||||
print("⚠️ Some README files are missing")
|
||||
self.results['translation_sync'] = {
|
||||
'score': 60,
|
||||
'status': 'WARN',
|
||||
'message': 'Some README files are missing'
|
||||
}
|
||||
return
|
||||
|
||||
# Check file modification times
|
||||
mod_times = {}
|
||||
for file in self.readme_files:
|
||||
mod_times[file] = os.path.getmtime(file)
|
||||
|
||||
# Calculate time difference (seconds)
|
||||
times = list(mod_times.values())
|
||||
time_diff = max(times) - min(times)
|
||||
|
||||
# Score based on time diff (within 7 days = synced)
|
||||
sync_score = max(0, 100 - (time_diff / (7 * 24 * 3600) * 20))
|
||||
|
||||
self.results['translation_sync'] = {
|
||||
'score': int(sync_score),
|
||||
'time_diff_days': round(time_diff / (24 * 3600), 2),
|
||||
'status': 'PASS' if sync_score >= 80 else 'WARN',
|
||||
'mod_times': {f: f"{os.path.getmtime(f):.0f}" for f in self.readme_files}
|
||||
}
|
||||
|
||||
print(f"✅ Translation sync: {int(sync_score)}/100")
|
||||
print(f" Max time difference: {round(time_diff / (24 * 3600), 1)} days")
|
||||
|
||||
def generate_report(self):
|
||||
"""Generate quality report"""
|
||||
print("\n📊 Generating quality report...")
|
||||
|
||||
# Calculate overall score
|
||||
scores = [
|
||||
self.results['structure_consistency'].get('score', 0),
|
||||
self.results['link_validation'].get('score', 0),
|
||||
self.results['translation_sync'].get('score', 0)
|
||||
]
|
||||
overall_score = sum(scores) // len(scores)
|
||||
self.results['overall_score'] = overall_score
|
||||
|
||||
# Generate GitHub Actions summary
|
||||
pipe = "|"
|
||||
table_header = f"{pipe} Check {pipe} Score {pipe} Status {pipe} Details {pipe}"
|
||||
table_separator = f"{pipe}----------|------|------|------|"
|
||||
table_row1 = f"{pipe} 📐 Structure {pipe} {self.results['structure_consistency'].get('score', 0)}/100 {pipe} {self.results['structure_consistency'].get('status', 'N/A')} {pipe} {len(self.results['structure_consistency'].get('details', {}))} files {pipe}"
|
||||
table_row2 = f"{pipe} 🔗 Links {pipe} {self.results['link_validation'].get('score', 0)}/100 {pipe} {self.results['link_validation'].get('status', 'N/A')} {pipe} {self.results['link_validation'].get('broken_links', 0)} broken {pipe}"
|
||||
table_row3 = f"{pipe} 🌍 Translation {pipe} {self.results['translation_sync'].get('score', 0)}/100 {pipe} {self.results['translation_sync'].get('status', 'N/A')} {pipe} {self.results['translation_sync'].get('time_diff_days', 0)} days diff {pipe}"
|
||||
|
||||
summary_parts = [
|
||||
"## 📊 README Quality Check Report",
|
||||
"",
|
||||
f"### 🏆 Overall Score: {overall_score}/100",
|
||||
"",
|
||||
table_header,
|
||||
table_separator,
|
||||
table_row1,
|
||||
table_row2,
|
||||
table_row3,
|
||||
"",
|
||||
"### 📋 Details",
|
||||
"",
|
||||
"**Structural consistency details:**"
|
||||
]
|
||||
summary = "\n".join(summary_parts)
|
||||
|
||||
for file, count in self.results['structure_consistency'].get('details', {}).items():
|
||||
summary += f"\n- `{file}`: {count} headings"
|
||||
|
||||
if self.results['link_validation'].get('broken_links'):
|
||||
summary += f"\n\n**Broken links:**\n"
|
||||
for link in self.results['link_validation']['broken_list']:
|
||||
summary += f"\n- ❌ {link}"
|
||||
|
||||
summary += f"\n\n### 🎯 Recommendations\n"
|
||||
|
||||
if overall_score >= 90:
|
||||
summary += "✅ Excellent quality! Keep it up."
|
||||
elif overall_score >= 70:
|
||||
summary += "⚠️ Good quality with room for improvement."
|
||||
else:
|
||||
summary += "🚨 Needs improvement! Please review the issues above."
|
||||
|
||||
# Write GitHub Actions summary
|
||||
github_step_summary = os.environ.get('GITHUB_STEP_SUMMARY')
|
||||
if github_step_summary:
|
||||
with open(github_step_summary, 'w', encoding='utf-8') as f:
|
||||
f.write(summary)
|
||||
|
||||
# Save detailed results
|
||||
with open('readme-quality-report.json', 'w', encoding='utf-8') as f:
|
||||
json.dump(self.results, f, indent=2, ensure_ascii=False)
|
||||
|
||||
print("✅ Report generated")
|
||||
|
||||
# Determine exit code based on score
|
||||
return 0 if overall_score >= 70 else 1
|
||||
|
||||
def run_all_checks(self):
|
||||
"""Run all checks"""
|
||||
print("🚀 Starting README quality check...\n")
|
||||
|
||||
self.check_structure_consistency()
|
||||
self.check_link_validation()
|
||||
self.check_translation_sync()
|
||||
|
||||
exit_code = self.generate_report()
|
||||
|
||||
print(f"\n🎯 Check complete! Score: {self.results['overall_score']}/100")
|
||||
return exit_code
|
||||
|
||||
if __name__ == "__main__":
|
||||
checker = READMEQualityChecker()
|
||||
exit_code = checker.run_all_checks()
|
||||
exit(exit_code)
|
||||
EOF
|
||||
|
||||
- name: Run README quality check
|
||||
run: python readme_checker.py
|
||||
|
||||
- name: Upload quality report
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: readme-quality-report
|
||||
path: readme-quality-report.json
|
||||
retention-days: 30
|
||||
|
||||
- name: Comment PR (if applicable)
|
||||
if: github.event_name == 'pull_request' && always() && github.event.pull_request.head.repo.full_name == github.repository
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
|
||||
if (fs.existsSync('readme-quality-report.json')) {
|
||||
const report = JSON.parse(fs.readFileSync('readme-quality-report.json', 'utf8'));
|
||||
|
||||
const score = report.overall_score;
|
||||
const emoji = score >= 90 ? '🏆' : score >= 70 ? '✅' : '⚠️';
|
||||
|
||||
const comment = `${emoji} **README Quality Check: ${score}/100**\n\n` +
|
||||
`📐 Structural consistency: ${report.structure_consistency?.score || 0}/100\n` +
|
||||
`🔗 Link validity: ${report.link_validation?.score || 0}/100\n` +
|
||||
`🌍 Translation sync: ${report.translation_sync?.score || 0}/100\n\n` +
|
||||
`See the Actions tab for the detailed report.`;
|
||||
|
||||
github.rest.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: comment
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
name: Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master, integration]
|
||||
pull_request:
|
||||
branches: [master, integration]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Test on Python ${{ matrix.python-version }}
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python-version: ["3.10", "3.11", "3.12"]
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
- name: Install UV
|
||||
run: |
|
||||
curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: Verify UV installation
|
||||
run: uv --version
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
uv pip install --system -e ".[dev]"
|
||||
uv pip list --system
|
||||
|
||||
- name: Verify package installation
|
||||
run: |
|
||||
python -c "import superclaude; print(f'SuperClaude {superclaude.__version__} installed')"
|
||||
python -c "import pytest_cov; print('pytest-cov is installed')"
|
||||
|
||||
- name: Run tests
|
||||
run: |
|
||||
pytest -v --tb=short --color=yes
|
||||
|
||||
- name: Run tests with coverage
|
||||
if: matrix.python-version == '3.10'
|
||||
run: |
|
||||
pytest --cov=superclaude --cov-report=xml --cov-report=term
|
||||
|
||||
- name: Upload coverage to Codecov
|
||||
if: matrix.python-version == '3.10'
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
file: ./coverage.xml
|
||||
flags: unittests
|
||||
name: codecov-umbrella
|
||||
fail_ci_if_error: false
|
||||
|
||||
lint:
|
||||
name: Lint and Format Check
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.10"
|
||||
|
||||
- name: Install UV
|
||||
run: |
|
||||
curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
uv pip install --system -e ".[dev]"
|
||||
|
||||
- name: Run ruff linter
|
||||
run: |
|
||||
ruff check src/ tests/
|
||||
|
||||
- name: Check ruff formatting
|
||||
run: |
|
||||
ruff format --check src/ tests/
|
||||
|
||||
plugin-check:
|
||||
name: Pytest Plugin Check
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.10"
|
||||
|
||||
- name: Install UV
|
||||
run: |
|
||||
curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
uv pip install --system -e ".[dev]"
|
||||
|
||||
- name: Verify pytest plugin loaded
|
||||
run: |
|
||||
pytest --trace-config 2>&1 | grep -q "superclaude" && echo "✅ Plugin loaded successfully" || (echo "❌ Plugin not loaded" && exit 1)
|
||||
|
||||
- name: Check available fixtures
|
||||
run: |
|
||||
pytest --fixtures | grep -E "(confidence_checker|self_check_protocol|reflexion_pattern|token_budget|pm_context)"
|
||||
|
||||
doctor-check:
|
||||
name: SuperClaude Doctor Check
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.10"
|
||||
|
||||
- name: Install UV
|
||||
run: |
|
||||
curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
uv pip install --system -e ".[dev]"
|
||||
|
||||
- name: Run doctor command
|
||||
run: |
|
||||
superclaude doctor --verbose
|
||||
|
||||
test-summary:
|
||||
name: Test Summary
|
||||
runs-on: ubuntu-latest
|
||||
needs: [test, lint, plugin-check, doctor-check]
|
||||
if: always()
|
||||
|
||||
steps:
|
||||
- name: Check test results
|
||||
run: |
|
||||
if [ "${{ needs.test.result }}" != "success" ]; then
|
||||
echo "❌ Tests failed"
|
||||
exit 1
|
||||
fi
|
||||
if [ "${{ needs.lint.result }}" != "success" ]; then
|
||||
echo "❌ Linting failed"
|
||||
exit 1
|
||||
fi
|
||||
if [ "${{ needs.plugin-check.result }}" != "success" ]; then
|
||||
echo "❌ Plugin check failed"
|
||||
exit 1
|
||||
fi
|
||||
if [ "${{ needs.doctor-check.result }}" != "success" ]; then
|
||||
echo "❌ Doctor check failed"
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ All checks passed!"
|
||||
Reference in New Issue
Block a user