chore: import upstream snapshot with attribution
Test Vector Database Adaptors / Test MCP Vector DB Tools (push) Has been cancelled
Tests / Code Quality (Ruff & Mypy) (push) Has been cancelled
Tests / Fast Unit Tests (parallel) (macos-latest, 3.11) (push) Has been cancelled
Tests / Fast Unit Tests (parallel) (macos-latest, 3.12) (push) Has been cancelled
Tests / Tests (push) Has been cancelled
Docker Publish / Build and Push Docker Images (map[description:Skill Seekers CLI - Convert documentation to AI skills dockerfile:Dockerfile name:skill-seekers]) (push) Has been cancelled
Docker Publish / Build and Push Docker Images (map[description:Skill Seekers MCP Server - 25 tools for AI assistants dockerfile:Dockerfile.mcp name:skill-seekers-mcp]) (push) Has been cancelled
Docker Publish / Test Docker Images (push) Has been cancelled
Tests / Fast Unit Tests (parallel) (ubuntu-latest, 3.10) (push) Has been cancelled
Tests / Fast Unit Tests (parallel) (ubuntu-latest, 3.11) (push) Has been cancelled
Tests / Fast Unit Tests (parallel) (ubuntu-latest, 3.12) (push) Has been cancelled
Tests / Serial / Integration / E2E Tests (push) Has been cancelled
Tests / MCP Server Tests (push) Has been cancelled
Test Vector Database Adaptors / Test chroma Adaptor (push) Has been cancelled
Test Vector Database Adaptors / Test faiss Adaptor (push) Has been cancelled
Test Vector Database Adaptors / Test qdrant Adaptor (push) Has been cancelled
Test Vector Database Adaptors / Test weaviate Adaptor (push) Has been cancelled
Test Vector Database Adaptors / Test MCP Vector DB Tools (push) Has been cancelled
Tests / Code Quality (Ruff & Mypy) (push) Has been cancelled
Tests / Fast Unit Tests (parallel) (macos-latest, 3.11) (push) Has been cancelled
Tests / Fast Unit Tests (parallel) (macos-latest, 3.12) (push) Has been cancelled
Tests / Tests (push) Has been cancelled
Docker Publish / Build and Push Docker Images (map[description:Skill Seekers CLI - Convert documentation to AI skills dockerfile:Dockerfile name:skill-seekers]) (push) Has been cancelled
Docker Publish / Build and Push Docker Images (map[description:Skill Seekers MCP Server - 25 tools for AI assistants dockerfile:Dockerfile.mcp name:skill-seekers-mcp]) (push) Has been cancelled
Docker Publish / Test Docker Images (push) Has been cancelled
Tests / Fast Unit Tests (parallel) (ubuntu-latest, 3.10) (push) Has been cancelled
Tests / Fast Unit Tests (parallel) (ubuntu-latest, 3.11) (push) Has been cancelled
Tests / Fast Unit Tests (parallel) (ubuntu-latest, 3.12) (push) Has been cancelled
Tests / Serial / Integration / E2E Tests (push) Has been cancelled
Tests / MCP Server Tests (push) Has been cancelled
Test Vector Database Adaptors / Test chroma Adaptor (push) Has been cancelled
Test Vector Database Adaptors / Test faiss Adaptor (push) Has been cancelled
Test Vector Database Adaptors / Test qdrant Adaptor (push) Has been cancelled
Test Vector Database Adaptors / Test weaviate Adaptor (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
# Docker Image Publishing - Automated builds and pushes to Docker Hub
|
||||
# Security Note: Uses secrets for Docker Hub credentials. Matrix values are hardcoded.
|
||||
# Triggers: push/pull_request/workflow_dispatch only. No untrusted input.
|
||||
|
||||
name: Docker Publish
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
tags:
|
||||
- 'v*'
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
paths:
|
||||
- 'Dockerfile*'
|
||||
- 'docker-compose.yml'
|
||||
- 'src/**'
|
||||
- 'pyproject.toml'
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
DOCKER_REGISTRY: docker.io
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||
|
||||
jobs:
|
||||
build-and-push:
|
||||
name: Build and Push Docker Images
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
image:
|
||||
- name: skill-seekers
|
||||
dockerfile: Dockerfile
|
||||
description: "Skill Seekers CLI - Convert documentation to AI skills"
|
||||
- name: skill-seekers-mcp
|
||||
dockerfile: Dockerfile.mcp
|
||||
description: "Skill Seekers MCP Server - 25 tools for AI assistants"
|
||||
|
||||
env:
|
||||
IMAGE_NAME: ${{ matrix.image.name }}
|
||||
IMAGE_DOCKERFILE: ${{ matrix.image.dockerfile }}
|
||||
IMAGE_DESCRIPTION: ${{ matrix.image.description }}
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Extract metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: ${{ env.DOCKER_REGISTRY }}/${{ env.DOCKER_USERNAME }}/${{ env.IMAGE_NAME }}
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=ref,event=pr
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=semver,pattern={{major}}
|
||||
type=raw,value=latest,enable={{is_default_branch}}
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
file: ${{ env.IMAGE_DOCKERFILE }}
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
platforms: linux/amd64,linux/arm64
|
||||
|
||||
- name: Create image summary
|
||||
run: |
|
||||
echo "## 🐳 Docker Image: $IMAGE_NAME" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Description:** $IMAGE_DESCRIPTION" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Tags:**" >> $GITHUB_STEP_SUMMARY
|
||||
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "${{ steps.meta.outputs.tags }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
test-images:
|
||||
name: Test Docker Images
|
||||
needs: build-and-push
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'pull_request'
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Build CLI image
|
||||
run: |
|
||||
docker build -t skill-seekers:test -f Dockerfile .
|
||||
|
||||
- name: Test CLI image
|
||||
run: |
|
||||
echo "🧪 Testing CLI image..."
|
||||
docker run --rm skill-seekers:test skill-seekers --version
|
||||
docker run --rm skill-seekers:test skill-seekers --help
|
||||
|
||||
- name: Build MCP image
|
||||
run: |
|
||||
docker build -t skill-seekers-mcp:test -f Dockerfile.mcp .
|
||||
|
||||
- name: Test MCP image
|
||||
run: |
|
||||
echo "🧪 Testing MCP server image..."
|
||||
# Start MCP server in background
|
||||
docker run -d --name mcp-test -p 8765:8765 skill-seekers-mcp:test
|
||||
|
||||
# Wait for server to start
|
||||
sleep 10
|
||||
|
||||
# Check health
|
||||
curl -f http://localhost:8765/health || exit 1
|
||||
|
||||
# Stop container
|
||||
docker stop mcp-test
|
||||
docker rm mcp-test
|
||||
|
||||
- name: Test Docker Compose
|
||||
run: |
|
||||
echo "🧪 Testing Docker Compose..."
|
||||
docker compose config
|
||||
echo "✅ Docker Compose configuration valid"
|
||||
@@ -0,0 +1,171 @@
|
||||
# Security Note: This workflow uses workflow_dispatch inputs and pull_request events.
|
||||
# All untrusted inputs are accessed via environment variables (env:) as recommended.
|
||||
# No direct usage of github.event.issue/comment/review content in run: commands.
|
||||
|
||||
name: Quality Metrics Dashboard
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
skill_dir:
|
||||
description: 'Path to skill directory to analyze (e.g., output/react)'
|
||||
required: true
|
||||
type: string
|
||||
fail_threshold:
|
||||
description: 'Minimum quality score to pass (default: 70)'
|
||||
required: false
|
||||
default: '70'
|
||||
type: string
|
||||
pull_request:
|
||||
paths:
|
||||
- 'output/**'
|
||||
- 'configs/**'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Quality Metrics Analysis
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
env:
|
||||
SKILL_DIR_INPUT: ${{ github.event.inputs.skill_dir }}
|
||||
FAIL_THRESHOLD_INPUT: ${{ github.event.inputs.fail_threshold }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python 3.12
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.12'
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -e .
|
||||
|
||||
- name: Find skill directories
|
||||
id: find_skills
|
||||
run: |
|
||||
if [ -n "$SKILL_DIR_INPUT" ]; then
|
||||
# Manual trigger with specific directory
|
||||
echo "dirs=$SKILL_DIR_INPUT" >> $GITHUB_OUTPUT
|
||||
else
|
||||
# PR trigger - find all skill directories
|
||||
DIRS=$(find output -maxdepth 1 -type d -name "*" ! -name "output" | tr '\n' ' ' || echo "")
|
||||
if [ -z "$DIRS" ]; then
|
||||
echo "No skill directories found"
|
||||
echo "dirs=" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "dirs=$DIRS" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
fi
|
||||
|
||||
- name: Analyze quality metrics
|
||||
id: quality
|
||||
run: |
|
||||
DIRS="${{ steps.find_skills.outputs.dirs }}"
|
||||
THRESHOLD="${FAIL_THRESHOLD_INPUT:-70}"
|
||||
|
||||
if [ -z "$DIRS" ]; then
|
||||
echo "No directories to analyze"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
ALL_PASSED=true
|
||||
SUMMARY_FILE="quality_summary.md"
|
||||
|
||||
echo "# 📊 Quality Metrics Dashboard" > $SUMMARY_FILE
|
||||
echo "" >> $SUMMARY_FILE
|
||||
echo "**Threshold:** $THRESHOLD/100" >> $SUMMARY_FILE
|
||||
echo "" >> $SUMMARY_FILE
|
||||
|
||||
for skill_dir in $DIRS; do
|
||||
if [ ! -d "$skill_dir" ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
SKILL_NAME=$(basename "$skill_dir")
|
||||
echo "🔍 Analyzing $SKILL_NAME..."
|
||||
|
||||
# Run quality analysis
|
||||
python3 -c "
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from skill_seekers.cli.quality_metrics import QualityAnalyzer
|
||||
|
||||
skill_dir = Path('$skill_dir')
|
||||
threshold = float('$THRESHOLD')
|
||||
skill_name = '$SKILL_NAME'
|
||||
|
||||
analyzer = QualityAnalyzer(skill_dir)
|
||||
report = analyzer.generate_report()
|
||||
|
||||
formatted = analyzer.format_report(report)
|
||||
print(formatted)
|
||||
|
||||
with open(f'quality_{skill_name}.txt', 'w') as f:
|
||||
f.write(formatted)
|
||||
|
||||
score = report.overall_score.total_score
|
||||
grade = report.overall_score.grade
|
||||
status = 'PASS' if score >= threshold else 'FAIL'
|
||||
|
||||
summary_line = f'{status} **{skill_name}**: {grade} ({score:.1f}/100)'
|
||||
print(f'\n{summary_line}')
|
||||
|
||||
with open('quality_summary.md', 'a') as f:
|
||||
f.write(f'{summary_line}\n')
|
||||
|
||||
if score < threshold:
|
||||
print(f'::error file={skill_dir}/SKILL.md::Quality score {score:.1f} is below threshold {threshold}')
|
||||
sys.exit(1)
|
||||
elif score < 80:
|
||||
print(f'::warning file={skill_dir}/SKILL.md::Quality score {score:.1f} could be improved')
|
||||
else:
|
||||
print(f'::notice file={skill_dir}/SKILL.md::Quality score {score:.1f} - Excellent!')
|
||||
"
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
ALL_PASSED=false
|
||||
fi
|
||||
|
||||
echo "" >> $SUMMARY_FILE
|
||||
done
|
||||
|
||||
if [ "$ALL_PASSED" = false ]; then
|
||||
echo "❌ Some skills failed quality thresholds"
|
||||
exit 1
|
||||
else
|
||||
echo "✅ All skills passed quality thresholds"
|
||||
fi
|
||||
|
||||
- name: Upload quality reports
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: quality-metrics-reports
|
||||
path: quality_*.txt
|
||||
retention-days: 30
|
||||
continue-on-error: true
|
||||
|
||||
- name: Post summary to PR
|
||||
if: github.event_name == 'pull_request'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
const summary = fs.readFileSync('quality_summary.md', 'utf8');
|
||||
|
||||
github.rest.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: summary
|
||||
});
|
||||
continue-on-error: true
|
||||
|
||||
- name: Create dashboard summary
|
||||
run: |
|
||||
if [ -f "quality_summary.md" ]; then
|
||||
cat quality_summary.md >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
@@ -0,0 +1,125 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.10'
|
||||
cache: 'pip'
|
||||
|
||||
- name: Install uv
|
||||
run: |
|
||||
curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -r requirements.txt
|
||||
if [ -f skill_seeker_mcp/requirements.txt ]; then pip install -r skill_seeker_mcp/requirements.txt; fi
|
||||
# Install package in editable mode for tests (required for src/ layout)
|
||||
pip install -e .
|
||||
|
||||
- name: Run tests
|
||||
timeout-minutes: 30
|
||||
run: |
|
||||
pip install pytest-timeout
|
||||
python -m pytest tests/ -v \
|
||||
-m "not slow and not integration and not e2e and not network and not serial and not mcp_only" \
|
||||
--timeout=120
|
||||
|
||||
- name: Extract version from tag
|
||||
id: get_version
|
||||
run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Verify Python version
|
||||
run: |
|
||||
python --version
|
||||
python -c "import sys; assert sys.version_info >= (3, 10), f'Python {sys.version} is not >= 3.10'"
|
||||
|
||||
- name: Verify version consistency
|
||||
run: |
|
||||
TAG_VERSION="${{ steps.get_version.outputs.VERSION }}"
|
||||
PKG_VERSION=$(python -c "import skill_seekers; print(skill_seekers.__version__)")
|
||||
TOML_VERSION=$(grep -m1 '^version' pyproject.toml | sed 's/version *= *"\(.*\)"/\1/')
|
||||
echo "Tag version: $TAG_VERSION"
|
||||
echo "Package version: $PKG_VERSION"
|
||||
echo "TOML version: $TOML_VERSION"
|
||||
if [ "$TAG_VERSION" != "$PKG_VERSION" ]; then
|
||||
echo "::error::Version mismatch! Tag=$TAG_VERSION but package reports=$PKG_VERSION"
|
||||
exit 1
|
||||
fi
|
||||
if [ "$TAG_VERSION" != "$TOML_VERSION" ]; then
|
||||
echo "::error::Version mismatch! Tag=$TAG_VERSION but pyproject.toml has=$TOML_VERSION"
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ All versions match: $TAG_VERSION"
|
||||
|
||||
- name: Create Release Notes
|
||||
id: release_notes
|
||||
run: |
|
||||
if [ -f CHANGELOG.md ]; then
|
||||
# Extract changelog for this version (escape dots for exact match)
|
||||
VERSION="${{ steps.get_version.outputs.VERSION }}"
|
||||
ESCAPED_VERSION=$(echo "$VERSION" | sed 's/\./\\./g')
|
||||
sed -n "/## \[${ESCAPED_VERSION}\]/,/## \[/p" CHANGELOG.md | sed '$d' > release_notes.md
|
||||
fi
|
||||
# Fallback if extraction produced empty file or CHANGELOG.md missing
|
||||
if [ ! -s release_notes.md ]; then
|
||||
echo "Release v${{ steps.get_version.outputs.VERSION }}" > release_notes.md
|
||||
fi
|
||||
|
||||
- name: Check if release exists
|
||||
id: check_release
|
||||
run: |
|
||||
if gh release view ${{ github.ref_name }} > /dev/null 2>&1; then
|
||||
echo "exists=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "exists=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Create GitHub Release
|
||||
if: steps.check_release.outputs.exists == 'false'
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
name: v${{ steps.get_version.outputs.VERSION }}
|
||||
tag_name: ${{ github.ref_name }}
|
||||
body_path: release_notes.md
|
||||
draft: false
|
||||
prerelease: false
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Skip Release Creation
|
||||
if: steps.check_release.outputs.exists == 'true'
|
||||
run: |
|
||||
echo "ℹ️ Release ${{ github.ref_name }} already exists, skipping creation"
|
||||
echo "View at: https://github.com/${{ github.repository }}/releases/tag/${{ github.ref_name }}"
|
||||
|
||||
- name: Build package
|
||||
run: |
|
||||
uv build
|
||||
|
||||
- name: Publish to PyPI
|
||||
env:
|
||||
UV_PUBLISH_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
|
||||
run: |
|
||||
uv publish --token $UV_PUBLISH_TOKEN
|
||||
@@ -0,0 +1,197 @@
|
||||
# Automated Skill Updates - Runs weekly to refresh documentation
|
||||
# Security Note: Schedule triggers with hardcoded constants. Workflow_dispatch input
|
||||
# accessed via FRAMEWORKS_INPUT env variable (safe pattern).
|
||||
|
||||
name: Scheduled Skill Updates
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# Run every Sunday at 3 AM UTC
|
||||
- cron: '0 3 * * 0'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
frameworks:
|
||||
description: 'Frameworks to update (comma-separated or "all")'
|
||||
required: false
|
||||
default: 'all'
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
update-skills:
|
||||
name: Update ${{ matrix.framework }}
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# Popular frameworks to keep updated
|
||||
framework:
|
||||
- react
|
||||
- django
|
||||
- fastapi
|
||||
- godot
|
||||
- vue
|
||||
- flask
|
||||
|
||||
env:
|
||||
FRAMEWORK: ${{ matrix.framework }}
|
||||
FRAMEWORKS_INPUT: ${{ github.event.inputs.frameworks }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Set up Python 3.12
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.12'
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -e .
|
||||
|
||||
- name: Check if framework should be updated
|
||||
id: should_update
|
||||
run: |
|
||||
FRAMEWORKS_INPUT="${FRAMEWORKS_INPUT:-all}"
|
||||
|
||||
if [ "$FRAMEWORKS_INPUT" = "all" ] || [ -z "$FRAMEWORKS_INPUT" ]; then
|
||||
echo "update=true" >> $GITHUB_OUTPUT
|
||||
elif echo "$FRAMEWORKS_INPUT" | grep -q "$FRAMEWORK"; then
|
||||
echo "update=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "update=false" >> $GITHUB_OUTPUT
|
||||
echo "⏭️ Skipping $FRAMEWORK (not in update list)"
|
||||
fi
|
||||
|
||||
- name: Check for existing skill
|
||||
if: steps.should_update.outputs.update == 'true'
|
||||
id: check_existing
|
||||
run: |
|
||||
SKILL_DIR="output/$FRAMEWORK"
|
||||
if [ -d "$SKILL_DIR" ]; then
|
||||
echo "exists=true" >> $GITHUB_OUTPUT
|
||||
echo "📦 Found existing skill at $SKILL_DIR"
|
||||
else
|
||||
echo "exists=false" >> $GITHUB_OUTPUT
|
||||
echo "🆕 No existing skill found"
|
||||
fi
|
||||
|
||||
- name: Incremental update (if exists)
|
||||
if: steps.should_update.outputs.update == 'true' && steps.check_existing.outputs.exists == 'true'
|
||||
run: |
|
||||
echo "⚡ Performing incremental update for $FRAMEWORK..."
|
||||
|
||||
SKILL_DIR="output/$FRAMEWORK"
|
||||
|
||||
# Detect changes using incremental updater
|
||||
python3 -c "
|
||||
import sys, os
|
||||
from pathlib import Path
|
||||
|
||||
from skill_seekers.cli.incremental_updater import IncrementalUpdater
|
||||
|
||||
framework = os.environ['FRAMEWORK']
|
||||
skill_dir = Path(f'output/{framework}')
|
||||
|
||||
updater = IncrementalUpdater(skill_dir)
|
||||
changes = updater.detect_changes()
|
||||
|
||||
if changes.has_changes:
|
||||
print(f'Changes detected:')
|
||||
print(f' Added: {len(changes.added)}')
|
||||
print(f' Modified: {len(changes.modified)}')
|
||||
print(f' Deleted: {len(changes.deleted)}')
|
||||
updater.current_versions = updater._scan_documents()
|
||||
updater.save_current_versions()
|
||||
else:
|
||||
print('No changes detected, skill is up to date')
|
||||
"
|
||||
|
||||
- name: Full scrape (if new or manual)
|
||||
if: steps.should_update.outputs.update == 'true' && steps.check_existing.outputs.exists == 'false'
|
||||
run: |
|
||||
echo "📥 Performing full scrape for $FRAMEWORK..."
|
||||
|
||||
CONFIG_FILE="configs/${FRAMEWORK}.json"
|
||||
|
||||
if [ ! -f "$CONFIG_FILE" ]; then
|
||||
echo "⚠️ Config not found: $CONFIG_FILE"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Use streaming ingestion for large docs
|
||||
skill-seekers create "$CONFIG_FILE" --max-pages 200
|
||||
|
||||
- name: Generate quality report
|
||||
if: steps.should_update.outputs.update == 'true'
|
||||
run: |
|
||||
SKILL_DIR="output/$FRAMEWORK"
|
||||
|
||||
if [ ! -d "$SKILL_DIR" ]; then
|
||||
echo "⚠️ Skill directory not found"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "📊 Generating quality metrics..."
|
||||
|
||||
python3 -c "
|
||||
import sys, os
|
||||
from pathlib import Path
|
||||
|
||||
from skill_seekers.cli.quality_metrics import QualityAnalyzer
|
||||
|
||||
framework = os.environ['FRAMEWORK']
|
||||
skill_dir = Path(f'output/{framework}')
|
||||
|
||||
analyzer = QualityAnalyzer(skill_dir)
|
||||
report = analyzer.generate_report()
|
||||
|
||||
print(f'Quality Score: {report.overall_score.grade} ({report.overall_score.total_score:.1f}/100)')
|
||||
print(f' Completeness: {report.overall_score.completeness:.1f}%')
|
||||
print(f' Accuracy: {report.overall_score.accuracy:.1f}%')
|
||||
print(f' Coverage: {report.overall_score.coverage:.1f}%')
|
||||
print(f' Health: {report.overall_score.health:.1f}%')
|
||||
"
|
||||
|
||||
- name: Package for Claude
|
||||
if: steps.should_update.outputs.update == 'true'
|
||||
run: |
|
||||
SKILL_DIR="output/$FRAMEWORK"
|
||||
|
||||
if [ -d "$SKILL_DIR" ]; then
|
||||
echo "📦 Packaging $FRAMEWORK for Claude AI..."
|
||||
skill-seekers package "$SKILL_DIR" --target claude
|
||||
fi
|
||||
|
||||
- name: Upload updated skill
|
||||
if: steps.should_update.outputs.update == 'true'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ env.FRAMEWORK }}-skill-updated
|
||||
path: output/${{ env.FRAMEWORK }}.zip
|
||||
retention-days: 90
|
||||
|
||||
summary:
|
||||
name: Update Summary
|
||||
needs: update-skills
|
||||
runs-on: ubuntu-latest
|
||||
if: always()
|
||||
|
||||
steps:
|
||||
- name: Create summary
|
||||
run: |
|
||||
echo "## 🔄 Scheduled Skills Update" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Date:** $(date -u '+%Y-%m-%d %H:%M UTC')" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "### Updated Frameworks" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- React" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Django" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- FastAPI" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Godot" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Vue" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Flask" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Updated skills available in workflow artifacts." >> $GITHUB_STEP_SUMMARY
|
||||
@@ -0,0 +1,112 @@
|
||||
# Security Note: This workflow uses only push/pull_request/workflow_dispatch triggers.
|
||||
# Matrix values are hardcoded constants. No untrusted input is used in run: commands.
|
||||
|
||||
name: Test Vector Database Adaptors
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main, development ]
|
||||
paths:
|
||||
- 'src/skill_seekers/cli/adaptors/**'
|
||||
- 'src/skill_seekers/mcp/tools/vector_db_tools.py'
|
||||
- 'tests/test_*adaptor*.py'
|
||||
- 'tests/test_mcp_vector_dbs.py'
|
||||
- 'tests/test_docker_smoke.py'
|
||||
- 'tests/test_upload_integration.py'
|
||||
pull_request:
|
||||
branches: [ main, development ]
|
||||
paths:
|
||||
- 'src/skill_seekers/cli/adaptors/**'
|
||||
- 'src/skill_seekers/mcp/tools/vector_db_tools.py'
|
||||
- 'tests/test_*adaptor*.py'
|
||||
- 'tests/test_mcp_vector_dbs.py'
|
||||
- 'tests/test_docker_smoke.py'
|
||||
- 'tests/test_upload_integration.py'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
test-adaptors:
|
||||
name: Test ${{ matrix.adaptor }} Adaptor
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
adaptor: [weaviate, chroma, faiss, qdrant]
|
||||
python-version: ['3.10', '3.12']
|
||||
|
||||
env:
|
||||
ADAPTOR_NAME: ${{ matrix.adaptor }}
|
||||
PYTHON_VERSION: ${{ matrix.python-version }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install pytest
|
||||
pip install -e .
|
||||
|
||||
- name: Run adaptor tests
|
||||
run: |
|
||||
echo "🧪 Testing $ADAPTOR_NAME adaptor..."
|
||||
python -m pytest "tests/test_adaptors/test_${ADAPTOR_NAME}_adaptor.py" -v --tb=short
|
||||
|
||||
- name: Test adaptor integration
|
||||
run: |
|
||||
echo "🔗 Testing $ADAPTOR_NAME integration..."
|
||||
|
||||
# Create test skill
|
||||
mkdir -p test_skill/references
|
||||
echo "# Test Skill" > test_skill/SKILL.md
|
||||
echo "Test content" >> test_skill/SKILL.md
|
||||
echo "# Reference" > test_skill/references/ref.md
|
||||
|
||||
# Test adaptor packaging
|
||||
python3 -c "
|
||||
import os
|
||||
from pathlib import Path
|
||||
from skill_seekers.cli.adaptors import get_adaptor
|
||||
adaptor_name = os.environ['ADAPTOR_NAME']
|
||||
adaptor = get_adaptor(adaptor_name)
|
||||
package_path = adaptor.package(Path('test_skill'), Path('.'))
|
||||
print(f'Package created: {package_path}')
|
||||
assert package_path.exists(), 'Package file not created'
|
||||
print(f'Package size: {package_path.stat().st_size} bytes')
|
||||
"
|
||||
|
||||
- name: Upload test package
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: test-package-${{ env.ADAPTOR_NAME }}-py${{ env.PYTHON_VERSION }}
|
||||
path: test_skill-${{ env.ADAPTOR_NAME }}.json
|
||||
retention-days: 7
|
||||
|
||||
test-mcp-tools:
|
||||
name: Test MCP Vector DB Tools
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python 3.12
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.12'
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install pytest
|
||||
pip install -e .
|
||||
|
||||
- name: Run MCP vector DB tests
|
||||
run: |
|
||||
echo "🧪 Testing MCP vector database tools..."
|
||||
python -m pytest tests/test_mcp_vector_dbs.py -v --tb=short
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
name: Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main, development ]
|
||||
pull_request:
|
||||
branches: [ main, development ]
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: Code Quality (Ruff & Mypy)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Python 3.12
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.12'
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install "ruff==0.15.8" mypy
|
||||
pip install -e .
|
||||
- name: Run ruff linter
|
||||
run: ruff check src/ tests/ --output-format=github
|
||||
- name: Run ruff formatter check
|
||||
run: ruff format --check src/ tests/
|
||||
- name: Run mypy type checker
|
||||
run: mypy src/skill_seekers --show-error-codes --pretty
|
||||
continue-on-error: true
|
||||
|
||||
test-fast:
|
||||
name: Fast Unit Tests (parallel)
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
python-version: ['3.10', '3.11', '3.12']
|
||||
exclude:
|
||||
- os: macos-latest
|
||||
python-version: '3.10'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: Cache pip packages
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pip-
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -r requirements.txt
|
||||
pip install pytest-xdist pytest-timeout
|
||||
pip install -e .
|
||||
- name: Run fast tests (xdist parallel)
|
||||
run: |
|
||||
python -m pytest tests/ -n auto --dist=loadfile \
|
||||
-m "not slow and not integration and not e2e and not network and not serial and not mcp_only" \
|
||||
-q --timeout=120 --cov=src/skill_seekers --cov-report=xml --cov-report=term
|
||||
timeout-minutes: 15
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
file: ./coverage.xml
|
||||
flags: unittests
|
||||
name: codecov-umbrella
|
||||
fail_ci_if_error: false
|
||||
|
||||
test-serial:
|
||||
name: Serial / Integration / E2E Tests
|
||||
runs-on: ubuntu-latest
|
||||
needs: [test-fast]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Set up Python 3.12
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.12'
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -r requirements.txt
|
||||
pip install pytest-timeout
|
||||
pip install -e .
|
||||
- name: Run serial/integration/E2E tests
|
||||
run: |
|
||||
python -m pytest tests/ \
|
||||
--ignore=tests/test_bootstrap_skill_e2e.py \
|
||||
--ignore=tests/test_bootstrap_skill.py \
|
||||
-m "(integration or e2e or slow or network or serial) and not mcp_only" \
|
||||
-v --timeout=300
|
||||
timeout-minutes: 25
|
||||
|
||||
test-mcp:
|
||||
name: MCP Server Tests
|
||||
runs-on: ubuntu-latest
|
||||
needs: [test-fast]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Set up Python 3.12
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.12'
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -r requirements.txt
|
||||
pip install pytest-timeout
|
||||
pip install -e ".[mcp]"
|
||||
pip install -e .
|
||||
- name: Run MCP tests
|
||||
run: |
|
||||
python -m pytest tests/ \
|
||||
-m "mcp_only and not network" \
|
||||
-v --timeout=180
|
||||
timeout-minutes: 10
|
||||
|
||||
tests-complete:
|
||||
name: Tests
|
||||
needs: [lint, test-fast, test-serial, test-mcp]
|
||||
runs-on: ubuntu-latest
|
||||
if: always()
|
||||
steps:
|
||||
- name: Check all results
|
||||
run: |
|
||||
if [ "${{ needs.lint.result }}" != "success" ]; then
|
||||
echo "❌ Code quality checks failed!"
|
||||
exit 1
|
||||
fi
|
||||
if [ "${{ needs.test-fast.result }}" != "success" ]; then
|
||||
echo "❌ Fast tests failed!"
|
||||
exit 1
|
||||
fi
|
||||
if [ "${{ needs.test-serial.result }}" == "failure" ]; then
|
||||
echo "❌ Serial/integration tests failed!"
|
||||
exit 1
|
||||
fi
|
||||
if [ "${{ needs.test-mcp.result }}" == "failure" ]; then
|
||||
echo "❌ MCP tests failed!"
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ All checks passed!"
|
||||
@@ -0,0 +1,188 @@
|
||||
name: Vector Database Export
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
skill_name:
|
||||
description: 'Skill name to export (e.g., react, django, godot)'
|
||||
required: true
|
||||
type: string
|
||||
targets:
|
||||
description: 'Vector databases to export (comma-separated: weaviate,chroma,faiss,qdrant or "all")'
|
||||
required: true
|
||||
default: 'all'
|
||||
type: string
|
||||
config_path:
|
||||
description: 'Path to config file (optional, auto-detected from skill_name if not provided)'
|
||||
required: false
|
||||
type: string
|
||||
schedule:
|
||||
# Run weekly on Sunday at 2 AM UTC for popular frameworks
|
||||
- cron: '0 2 * * 0'
|
||||
|
||||
jobs:
|
||||
export:
|
||||
name: Export to Vector Databases
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# For scheduled runs, export popular frameworks
|
||||
skill: ${{ github.event_name == 'schedule' && fromJson('["react", "django", "godot", "fastapi"]') || fromJson(format('["{0}"]', github.event.inputs.skill_name)) }}
|
||||
|
||||
env:
|
||||
SKILL_NAME: ${{ matrix.skill }}
|
||||
TARGETS_INPUT: ${{ github.event.inputs.targets }}
|
||||
CONFIG_PATH_INPUT: ${{ github.event.inputs.config_path }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Set up Python 3.12
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.12'
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -e .
|
||||
|
||||
- name: Determine config path
|
||||
id: config
|
||||
run: |
|
||||
if [ -n "$CONFIG_PATH_INPUT" ]; then
|
||||
echo "path=$CONFIG_PATH_INPUT" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "path=configs/$SKILL_NAME.json" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Check if config exists
|
||||
id: check_config
|
||||
run: |
|
||||
CONFIG_FILE="${{ steps.config.outputs.path }}"
|
||||
if [ -f "$CONFIG_FILE" ]; then
|
||||
echo "exists=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "exists=false" >> $GITHUB_OUTPUT
|
||||
echo "⚠️ Config not found: $CONFIG_FILE"
|
||||
fi
|
||||
|
||||
- name: Scrape documentation
|
||||
if: steps.check_config.outputs.exists == 'true'
|
||||
run: |
|
||||
echo "📥 Scraping documentation for $SKILL_NAME..."
|
||||
skill-seekers create "${{ steps.config.outputs.path }}" --max-pages 100
|
||||
continue-on-error: true
|
||||
|
||||
- name: Determine export targets
|
||||
id: targets
|
||||
run: |
|
||||
TARGETS="${TARGETS_INPUT:-all}"
|
||||
if [ "$TARGETS" = "all" ]; then
|
||||
echo "list=weaviate chroma faiss qdrant" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "list=$(echo "$TARGETS" | tr ',' ' ')" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Export to vector databases
|
||||
if: steps.check_config.outputs.exists == 'true'
|
||||
env:
|
||||
EXPORT_TARGETS: ${{ steps.targets.outputs.list }}
|
||||
run: |
|
||||
SKILL_DIR="output/$SKILL_NAME"
|
||||
|
||||
if [ ! -d "$SKILL_DIR" ]; then
|
||||
echo "❌ Skill directory not found: $SKILL_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "📦 Exporting $SKILL_NAME to vector databases..."
|
||||
|
||||
for target in $EXPORT_TARGETS; do
|
||||
echo ""
|
||||
echo "🔹 Exporting to $target..."
|
||||
|
||||
# Use adaptor directly via CLI
|
||||
python3 -c "
|
||||
from pathlib import Path
|
||||
from skill_seekers.cli.adaptors import get_adaptor
|
||||
adaptor = get_adaptor('$target')
|
||||
package_path = adaptor.package(Path('$SKILL_DIR'), Path('output'))
|
||||
print(f'Exported to {package_path}')
|
||||
"
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "✅ $target export complete"
|
||||
else
|
||||
echo "❌ $target export failed"
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Generate quality report
|
||||
if: steps.check_config.outputs.exists == 'true'
|
||||
run: |
|
||||
SKILL_DIR="output/$SKILL_NAME"
|
||||
|
||||
if [ -d "$SKILL_DIR" ]; then
|
||||
echo "📊 Generating quality metrics..."
|
||||
|
||||
python3 -c "
|
||||
from pathlib import Path
|
||||
from skill_seekers.cli.quality_metrics import QualityAnalyzer
|
||||
analyzer = QualityAnalyzer(Path('$SKILL_DIR'))
|
||||
report = analyzer.generate_report()
|
||||
formatted = analyzer.format_report(report)
|
||||
print(formatted)
|
||||
with open('quality_report_${SKILL_NAME}.txt', 'w') as f:
|
||||
f.write(formatted)
|
||||
"
|
||||
fi
|
||||
continue-on-error: true
|
||||
|
||||
- name: Upload vector database exports
|
||||
if: steps.check_config.outputs.exists == 'true'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ env.SKILL_NAME }}-vector-exports
|
||||
path: |
|
||||
output/${{ env.SKILL_NAME }}-*.json
|
||||
retention-days: 30
|
||||
|
||||
- name: Upload quality report
|
||||
if: steps.check_config.outputs.exists == 'true'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ env.SKILL_NAME }}-quality-report
|
||||
path: quality_report_${{ env.SKILL_NAME }}.txt
|
||||
retention-days: 30
|
||||
continue-on-error: true
|
||||
|
||||
- name: Create export summary
|
||||
if: steps.check_config.outputs.exists == 'true'
|
||||
env:
|
||||
EXPORT_TARGETS: ${{ steps.targets.outputs.list }}
|
||||
run: |
|
||||
echo "## 📦 Vector Database Export Summary: $SKILL_NAME" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
for target in $EXPORT_TARGETS; do
|
||||
FILE="output/${SKILL_NAME}-${target}.json"
|
||||
if [ -f "$FILE" ]; then
|
||||
SIZE=$(du -h "$FILE" | cut -f1)
|
||||
echo "✅ **$target**: $SIZE" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "❌ **$target**: Export failed" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
done
|
||||
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
if [ -f "quality_report_${SKILL_NAME}.txt" ]; then
|
||||
echo "### 📊 Quality Metrics" >> $GITHUB_STEP_SUMMARY
|
||||
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
|
||||
head -30 "quality_report_${SKILL_NAME}.txt" >> $GITHUB_STEP_SUMMARY
|
||||
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
Reference in New Issue
Block a user