From 9faaf9db50e2a96ed327f307dd965a4a10bfd7a6 Mon Sep 17 00:00:00 2001 From: zhang shuhao Date: Sun, 25 Jan 2026 13:54:29 +0800 Subject: [PATCH 01/15] chore: add quickstart and hooks --- hooks/pre-commit | 65 +++++++ hooks/pre-push | 207 ++++++++++++++++++++ quickstart.sh | 490 ++++++++++++++++++----------------------------- 3 files changed, 460 insertions(+), 302 deletions(-) create mode 100755 hooks/pre-commit create mode 100755 hooks/pre-push diff --git a/hooks/pre-commit b/hooks/pre-commit new file mode 100755 index 0000000..97d941d --- /dev/null +++ b/hooks/pre-commit @@ -0,0 +1,65 @@ +#!/bin/bash +# Pre-commit hook for sageVDB +# Runs basic code quality checks before allowing commits + +set -e + +# Colors +RED='\033[0;31m' +YELLOW='\033[1;33m' +GREEN='\033[0;32m' +CYAN='\033[0;36m' +NC='\033[0m' + +echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" +echo -e "${CYAN}🔍 Running pre-commit checks...${NC}" +echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" + +# Check for trailing whitespace +echo -e "${YELLOW}Checking for trailing whitespace...${NC}" +if git diff --cached --check --diff-filter=ACM; then + echo -e "${GREEN}✓ No trailing whitespace${NC}" +else + echo -e "${RED}✗ Found trailing whitespace. Please fix before committing.${NC}" + exit 1 +fi + +# Check for large files (>5MB) +echo -e "${YELLOW}Checking for large files...${NC}" +max_size=5242880 # 5MB in bytes +large_files=$(git diff --cached --name-only | while read file; do + if [ -f "$file" ]; then + size=$(stat -f%z "$file" 2>/dev/null || stat -c%s "$file" 2>/dev/null || echo 0) + if [ "$size" -gt "$max_size" ]; then + echo "$file ($((size/1024/1024))MB)" + fi + fi +done) + +if [ -n "$large_files" ]; then + echo -e "${RED}✗ Large files detected (>5MB):${NC}" + echo "$large_files" + echo -e "${YELLOW}Consider using Git LFS or excluding these files.${NC}" + exit 1 +else + echo -e "${GREEN}✓ No large files${NC}" +fi + +# Check for common debug statements in Python files +echo -e "${YELLOW}Checking for debug statements...${NC}" +debug_found=false +git diff --cached --name-only --diff-filter=ACM | grep '\.py$' | while read file; do + if git diff --cached "$file" | grep -E '^\+.*\b(print|pdb\.set_trace|breakpoint)\(' > /dev/null; then + if [ "$debug_found" = false ]; then + echo -e "${YELLOW}⚠ Warning: Debug statements found in:${NC}" + debug_found=true + fi + echo " - $file" + fi +done + +echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" +echo -e "${GREEN}✓ Pre-commit checks passed!${NC}" +echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" + +exit 0 diff --git a/hooks/pre-push b/hooks/pre-push new file mode 100755 index 0000000..35a2f10 --- /dev/null +++ b/hooks/pre-push @@ -0,0 +1,207 @@ +#!/bin/bash +# Pre-push hook managed by sage-pypi-publisher +# Auto-detects version updates and offers to build/upload to PyPI + +set -e + +# Colors +RED='\033[0;31m' +YELLOW='\033[1;33m' +GREEN='\033[0;32m' +CYAN='\033[0;36m' +BLUE='\033[0;34m' +NC='\033[0m' + +PACKAGE_NAME=$(grep -oP '^name = "\K[^"]+' pyproject.toml 2>/dev/null || echo "unknown") + +check_pypi_version_exists() { + if [ "$PACKAGE_NAME" = "unknown" ]; then + return 2 + fi + if command -v python3 &> /dev/null; then + python3 - "$PACKAGE_NAME" "$CURRENT_VERSION" <<'PY' +import json +import sys +import urllib.request + +package = sys.argv[1] +version = sys.argv[2] +try: + with urllib.request.urlopen(f"https://pypi.org/pypi/{package}/json", timeout=5) as resp: + data = json.load(resp) + releases = data.get("releases", {}) + sys.exit(0 if version in releases else 1) +except Exception: + sys.exit(2) +PY + return $? + elif command -v curl &> /dev/null; then + curl -fsSL "https://pypi.org/pypi/${PACKAGE_NAME}/json" | grep -q "\"${CURRENT_VERSION}\"" + return $? + else + return 2 + fi +} + +CURRENT_VERSION=$(grep -oP '^version = "\K[^"]+' pyproject.toml 2>/dev/null || echo "unknown") +if [ "$CURRENT_VERSION" = "unknown" ]; then + echo -e "${YELLOW}No pyproject.toml found, skipping version check${NC}" + exit 0 +fi + +if ! git rev-parse HEAD~1 >/dev/null 2>&1; then + echo -e "${YELLOW}First commit detected, skipping version check${NC}" + exit 0 +fi + +VERSION_UPDATED=false +for i in {1..5}; do + if git diff HEAD~$i HEAD -- pyproject.toml 2>/dev/null | grep -q '^[+-]version = '; then + VERSION_UPDATED=true + break + fi +done + +if [ "$VERSION_UPDATED" = true ]; then + echo -e "${GREEN}✓ Version updated to ${CURRENT_VERSION}${NC}" + PYPI_CHECK_RESULT=0 + check_pypi_version_exists + PYPI_CHECK_RESULT=$? + if [ "$PYPI_CHECK_RESULT" -eq 0 ]; then + echo -e "${YELLOW}⚠ ${PACKAGE_NAME} ${CURRENT_VERSION} already exists on PyPI${NC}" + echo -e "${BLUE}What would you like to do?${NC}" + echo -e " ${GREEN}[u]${NC} Update version now (interactive)" + echo -e " ${YELLOW}[y]${NC} Continue anyway" + echo -e " ${RED}[c]${NC} Cancel push" + echo -n "Your choice [u/y/c]: " + read -r pypi_resp /dev/null || true + + if command -v sage-pypi-publisher &> /dev/null; then + echo -e "${GREEN}Using sage-pypi-publisher (auto-detects build type)...${NC}" + if sage-pypi-publisher build . --upload --no-dry-run; then + echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" + echo -e "${GREEN}✓ Successfully uploaded ${CURRENT_VERSION} to PyPI${NC}" + echo -e "${GREEN}🔗 https://pypi.org/project/isage-vdb/${CURRENT_VERSION}/${NC}" + echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" + else + echo -e "${RED}✗ Failed to upload to PyPI${NC}" + echo -e "${YELLOW}Continue push anyway? [y/N]${NC}" + read -r cont /dev/null || true + if command -v sage-pypi-publisher &> /dev/null; then + sage-pypi-publisher build . --upload --no-dry-run + echo -e "${GREEN}✓ Uploaded ${new_version} to PyPI${NC}" + fi + fi + exit 0 + elif [[ "$response" =~ ^[Yy]$ ]]; then + echo -e "${YELLOW}Continuing without version update${NC}" + else + echo -e "${YELLOW}Push cancelled${NC}" + exit 1 + fi +fi + +exit 0 diff --git a/quickstart.sh b/quickstart.sh index 1ec2d80..13d9766 100755 --- a/quickstart.sh +++ b/quickstart.sh @@ -1,311 +1,197 @@ -#!/usr/bin/env bash +#!/bin/bash +# SageVDB Quickstart Script +# Sets up development environment and git hooks -# SAGE GitHub Manager - Quick Start Installation Script -# This script helps you quickly set up sage-github-manager on your system +set -e -set -e # Exit on error - -# Colors for output +# Colors RED='\033[0;31m' -GREEN='\033[0;32m' YELLOW='\033[1;33m' +GREEN='\033[0;32m' +CYAN='\033[0;36m' BLUE='\033[0;34m' -NC='\033[0m' # No Color - -# Helper functions -print_header() { - echo -e "${BLUE}========================================${NC}" - echo -e "${BLUE}$1${NC}" - echo -e "${BLUE}========================================${NC}" -} - -print_success() { - echo -e "${GREEN}✓ $1${NC}" -} - -print_error() { - echo -e "${RED}✗ $1${NC}" -} - -print_warning() { - echo -e "${YELLOW}⚠ $1${NC}" -} - -print_info() { - echo -e "${BLUE}ℹ $1${NC}" -} - -# Check if command exists -command_exists() { - command -v "$1" >/dev/null 2>&1 -} - -# Main installation process -main() { - print_header "SAGE GitHub Manager Quick Start" - echo "" - echo "This script will install sage-github-manager and set up your environment." - echo "" - - # Step 1: Check prerequisites - print_header "Step 1: Checking Prerequisites" - - # Check Python version - if command_exists python3; then - PYTHON_VERSION=$(python3 --version | cut -d' ' -f2) - PYTHON_MAJOR=$(echo $PYTHON_VERSION | cut -d'.' -f1) - PYTHON_MINOR=$(echo $PYTHON_VERSION | cut -d'.' -f2) - - if [ "$PYTHON_MAJOR" -ge 3 ] && [ "$PYTHON_MINOR" -ge 10 ]; then - print_success "Python $PYTHON_VERSION found (required: 3.10+)" - else - print_error "Python 3.10+ required, found $PYTHON_VERSION" - echo "Please install Python 3.10 or higher: https://www.python.org/downloads/" - exit 1 - fi +BOLD='\033[1m' +NC='\033[0m' + +# Print banner +echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" +echo -e "${BOLD}${BLUE} ____ __ ______ ____ ${NC}" +echo -e "${BOLD}${BLUE} / __/__ ___ ___ / / / __ / / __ )${NC}" +echo -e "${BOLD}${BLUE} _\\ \/ _ \/ _ \/ -_) / / / / / / / __ |${NC}" +echo -e "${BOLD}${BLUE}/___/\\___/\\_, /\\__/ /_/ /_/ /_/ /____/ ${NC}" +echo -e "${BOLD}${BLUE} /___/ ${NC}" +echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" +echo -e "${GREEN}${BOLD}SageVDB Quickstart Setup${NC}" +echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" +echo "" + +# Detect project root +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$SCRIPT_DIR" + +echo -e "${BLUE}📂 Project root: ${NC}$PROJECT_ROOT" +echo "" + +# Step 1: Install git hooks +echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" +echo -e "${YELLOW}${BOLD}Step 1: Installing Git Hooks${NC}" +echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" + +HOOKS_DIR="$PROJECT_ROOT/.git/hooks" +TEMPLATE_DIR="$PROJECT_ROOT/hooks" + +if [ ! -d "$HOOKS_DIR" ]; then + echo -e "${RED}✗ Git repository not initialized${NC}" + echo -e "${YELLOW}Run: git init${NC}" + exit 1 +fi + +# Install pre-commit hook +if [ -f "$TEMPLATE_DIR/pre-commit" ]; then + cp "$TEMPLATE_DIR/pre-commit" "$HOOKS_DIR/pre-commit" + chmod +x "$HOOKS_DIR/pre-commit" + echo -e "${GREEN}✓ Installed pre-commit hook${NC}" +else + echo -e "${YELLOW}⚠ pre-commit template not found, skipping${NC}" +fi + +# Install pre-push hook +if [ -f "$TEMPLATE_DIR/pre-push" ]; then + cp "$TEMPLATE_DIR/pre-push" "$HOOKS_DIR/pre-push" + chmod +x "$HOOKS_DIR/pre-push" + echo -e "${GREEN}✓ Installed pre-push hook${NC}" +else + echo -e "${YELLOW}⚠ pre-push template not found, skipping${NC}" +fi + +echo "" + +# Step 2: Check dependencies +echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" +echo -e "${YELLOW}${BOLD}Step 2: Checking Dependencies${NC}" +echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" + +# Check for CMake +if command -v cmake &> /dev/null; then + CMAKE_VERSION=$(cmake --version | head -n1 | cut -d' ' -f3) + echo -e "${GREEN}✓ CMake found: ${NC}v$CMAKE_VERSION" +else + echo -e "${RED}✗ CMake not found${NC}" + echo -e "${YELLOW} Install: sudo apt install cmake # or brew install cmake${NC}" +fi + +# Check for C++ compiler +if command -v g++ &> /dev/null; then + GCC_VERSION=$(g++ --version | head -n1 | awk '{print $NF}') + echo -e "${GREEN}✓ g++ found: ${NC}v$GCC_VERSION" +elif command -v clang++ &> /dev/null; then + CLANG_VERSION=$(clang++ --version | head -n1 | awk '{print $NF}') + echo -e "${GREEN}✓ clang++ found: ${NC}v$CLANG_VERSION" +else + echo -e "${RED}✗ C++ compiler not found${NC}" + echo -e "${YELLOW} Install: sudo apt install build-essential # or xcode-select --install${NC}" +fi + +# Check for Python +if command -v python3 &> /dev/null; then + PYTHON_VERSION=$(python3 --version | awk '{print $2}') + echo -e "${GREEN}✓ Python found: ${NC}v$PYTHON_VERSION" +else + echo -e "${RED}✗ Python not found${NC}" +fi + +# Check for sage-pypi-publisher +if command -v sage-pypi-publisher &> /dev/null; then + echo -e "${GREEN}✓ sage-pypi-publisher found${NC}" +else + echo -e "${YELLOW}⚠ sage-pypi-publisher not found${NC}" + echo -e "${YELLOW} Optional for PyPI publishing: pip install sage-pypi-publisher${NC}" +fi + +echo "" + +# Step 3: Build instructions +echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" +echo -e "${YELLOW}${BOLD}Step 3: Build Options${NC}" +echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" + +echo -e "${BLUE}Would you like to build the project now?${NC}" +echo -e " ${GREEN}[y]${NC} Yes, configure and build" +echo -e " ${YELLOW}[n]${NC} No, I'll build manually later" +echo -n "Your choice [y/n]: " +read -r BUILD_NOW + +if [[ "$BUILD_NOW" =~ ^[Yy]$ ]]; then + echo "" + echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" + echo -e "${GREEN}🔨 Building SageVDB...${NC}" + echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" + + if [ -f "$PROJECT_ROOT/build.sh" ]; then + echo -e "${YELLOW}Using build.sh script...${NC}" + cd "$PROJECT_ROOT" + bash build.sh else - print_error "Python 3 not found" - echo "Please install Python 3.10+: https://www.python.org/downloads/" - exit 1 - fi + echo -e "${YELLOW}Configuring with CMake...${NC}" + cmake -B "$PROJECT_ROOT/build" -S "$PROJECT_ROOT" \ + -DCMAKE_BUILD_TYPE=Release \ + -DBUILD_TESTS=ON - # Check pip - if command_exists pip3; then - print_success "pip3 found" - else - print_error "pip3 not found" - echo "Please install pip3: https://pip.pypa.io/en/stable/installation/" - exit 1 - fi + echo -e "${YELLOW}Building...${NC}" + cmake --build "$PROJECT_ROOT/build" -j$(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4) - # Check git (optional but recommended) - if command_exists git; then - print_success "git found" - else - print_warning "git not found (optional, but recommended for updates)" + echo -e "${GREEN}✓ Build complete${NC}" fi - - echo "" - - # Step 2: Install package - print_header "Step 2: Installing sage-github-manager" - - # Check if already in project directory - if [ -f "pyproject.toml" ] && grep -q "sage-github" "pyproject.toml" 2>/dev/null; then - print_info "Already in sage-github-manager directory" - PROJECT_DIR="$(pwd)" - else - print_error "Not in sage-github-manager directory" - echo "Please run this script from the project root directory:" - echo " cd /path/to/sage-github-manager" - echo " bash quickstart.sh" - exit 1 - fi - - # Check if already in a virtual environment - if [ -n "$VIRTUAL_ENV" ]; then - print_success "Already in virtual environment: $(basename $VIRTUAL_ENV)" - elif [ -n "$CONDA_DEFAULT_ENV" ]; then - print_success "Already in conda environment: $CONDA_DEFAULT_ENV" - else - # Not in a virtual environment, ask if they want to create one - read -p "$(echo -e ${YELLOW}Not in a virtual environment. Create a new venv? [Y/n]: ${NC})" CREATE_VENV - CREATE_VENV=${CREATE_VENV:-Y} - - if [[ "$CREATE_VENV" =~ ^[Yy]$ ]]; then - if [ -d "venv" ]; then - print_info "Virtual environment already exists, using existing one" - else - print_info "Creating virtual environment..." - python3 -m venv venv - print_success "Virtual environment created" - fi - - print_info "Activating virtual environment..." - source venv/bin/activate - print_success "Virtual environment activated" - else - print_warning "Installing without virtual environment (not recommended)" - fi - fi - - # Install package with dependencies - print_info "Installing package and dependencies (this may take a minute)..." - pip3 install -e ".[dev]" --quiet - print_success "Package installed successfully" - - # Install pre-commit hooks - if command_exists pre-commit; then - print_info "Installing pre-commit hooks..." - pre-commit install > /dev/null 2>&1 - print_success "Pre-commit hooks installed" - else - print_warning "pre-commit not found, skipping hooks installation" - fi - - echo "" - - # Step 3: Configure environment - print_header "Step 3: Configuring Environment" - - # Check for existing .env file - if [ -f ".env" ]; then - print_info ".env file already exists" - read -p "$(echo -e ${YELLOW}Do you want to update it? [y/N]: ${NC})" UPDATE_ENV - UPDATE_ENV=${UPDATE_ENV:-N} - - if [[ ! "$UPDATE_ENV" =~ ^[Yy]$ ]]; then - print_info "Skipping .env configuration" - SKIP_ENV=true - fi - fi - - if [ "$SKIP_ENV" != "true" ]; then - print_info "Setting up GitHub configuration..." - echo "" - echo "You need a GitHub Personal Access Token (PAT) with 'repo' scope." - echo "Create one at: https://github.com/settings/tokens/new" - echo "" - - read -p "Enter your GitHub Token (or press Enter to skip): " GITHUB_TOKEN - read -p "Enter GitHub Owner (default: intellistream): " GITHUB_OWNER - GITHUB_OWNER=${GITHUB_OWNER:-intellistream} - read -p "Enter GitHub Repo (default: SAGE): " GITHUB_REPO - GITHUB_REPO=${GITHUB_REPO:-SAGE} - - # Create .env file - cat > .env << EOF -# GitHub Configuration -GITHUB_TOKEN=${GITHUB_TOKEN} -GITHUB_OWNER=${GITHUB_OWNER} -GITHUB_REPO=${GITHUB_REPO} - -# Optional: OpenAI API Key for AI features -# OPENAI_API_KEY=sk-... - -# Optional: Anthropic API Key for Claude -# ANTHROPIC_API_KEY=sk-ant-... -EOF - - print_success ".env file created" - - if [ -z "$GITHUB_TOKEN" ]; then - print_warning "No GitHub token provided. You'll need to set it later:" - echo " export GITHUB_TOKEN=your_token_here" - echo " or edit .env file" - fi - fi - - echo "" - - # Step 4: Verify installation - print_header "Step 4: Verifying Installation" - - if command_exists github-manager; then - print_success "github-manager command available" - VERSION=$(github-manager --version 2>/dev/null || echo "unknown") - print_info "Version: $VERSION" - else - print_error "github-manager command not found" - echo "Try running: pip3 install -e '.[dev]'" - exit 1 - fi - - # Test basic functionality - if [ -n "$GITHUB_TOKEN" ]; then - print_info "Testing GitHub connection..." - export GITHUB_TOKEN - export GITHUB_OWNER - export GITHUB_REPO - - if github-manager download --help >/dev/null 2>&1; then - print_success "Basic command test passed" - else - print_warning "Command test failed, but installation completed" - fi - fi - - echo "" - - # Step 5: Success message and next steps - print_header "Installation Complete! 🎉" - echo "" - print_success "sage-github-manager is ready to use!" - echo "" - echo -e "${GREEN}╔════════════════════════════════════════════════════════════════╗${NC}" - echo -e "${GREEN}║ QUICK START GUIDE ║${NC}" - echo -e "${GREEN}╚════════════════════════════════════════════════════════════════╝${NC}" - echo "" - print_info "Configuration (Required):" - echo "" - - # Show environment activation if needed - if [[ "$CREATE_VENV" =~ ^[Yy]$ ]] && [ -z "$CONDA_DEFAULT_ENV" ]; then - echo -e " ${YELLOW}▶${NC} Activate virtual environment:" - echo " source venv/bin/activate" - echo "" - fi - - if [ -z "$GITHUB_TOKEN" ]; then - echo -e " ${YELLOW}▶${NC} Set your GitHub token:" - echo " export GITHUB_TOKEN=ghp_xxxxxxxxxxxxx" - echo " export GITHUB_OWNER=intellistream" - echo " export GITHUB_REPO=SAGE" - echo "" - echo -e " ${BLUE}Or create .env file:${NC}" - echo " cat > .env << EOF" - echo "GITHUB_TOKEN=ghp_xxxxxxxxxxxxx" - echo "GITHUB_OWNER=intellistream" - echo "GITHUB_REPO=SAGE" - echo "EOF" - echo "" - fi - - print_info "Basic Commands:" - echo "" - echo -e " ${GREEN}1.${NC} Download SAGE issues:" - echo -e " ${BLUE}github-manager download${NC}" - echo "" - echo -e " ${GREEN}2.${NC} List open issues:" - echo -e " ${BLUE}github-manager list --state open${NC}" - echo -e " ${BLUE}github-manager list --label bug --assignee yourname${NC}" - echo "" - echo -e " ${GREEN}3.${NC} View analytics:" - echo -e " ${BLUE}github-manager analytics${NC}" - echo "" - echo -e " ${GREEN}4.${NC} Export data:" - echo -e " ${BLUE}github-manager export issues.csv${NC}" - echo -e " ${BLUE}github-manager export report.md -f markdown${NC}" - echo "" - echo -e " ${GREEN}5.${NC} Batch operations:" - echo -e " ${BLUE}github-manager batch-close --label wontfix --dry-run${NC}" - echo -e " ${BLUE}github-manager batch-label --add reviewed --label bug${NC}" - echo "" - echo -e " ${GREEN}6.${NC} AI features (requires API key):" - echo -e " ${BLUE}github-manager summarize --issue 123${NC}" - echo -e " ${BLUE}github-manager detect-duplicates${NC}" - echo -e " ${BLUE}github-manager suggest-labels --issue 456${NC}" - echo "" - - print_info "Documentation:" - echo " - Quick Start: docs/QUICK_START.md" - echo " - FAQ: docs/FAQ.md" - echo " - Examples: examples/" - echo "" - - print_info "Development:" - echo " - Run tests: pytest" - echo " - Format code: ruff format ." - echo " - Lint code: ruff check ." - echo "" - - if [ -f ".env" ]; then - print_warning "Remember: .env file contains sensitive data (not committed to git)" - fi - - echo "" - print_header "Happy Issue Managing! 🚀" -} - -# Run main function -main "$@" +else + echo -e "${YELLOW}Skipping build. To build later, run:${NC}" + echo -e " ${CYAN}./build.sh${NC}" + echo -e "${YELLOW}Or manually:${NC}" + echo -e " ${CYAN}cmake -B build -S . -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTS=ON${NC}" + echo -e " ${CYAN}cmake --build build -j\$(nproc)${NC}" +fi + +echo "" + +# Step 4: Python package setup +echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" +echo -e "${YELLOW}${BOLD}Step 4: Python Package Setup (Optional)${NC}" +echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" + +echo -e "${BLUE}Install Python package in development mode?${NC}" +echo -e " ${GREEN}[y]${NC} Yes, install with pip install -e ." +echo -e " ${YELLOW}[n]${NC} No, skip Python setup" +echo -n "Your choice [y/n]: " +read -r INSTALL_PY + +if [[ "$INSTALL_PY" =~ ^[Yy]$ ]]; then + echo -e "${YELLOW}Installing in editable mode...${NC}" + cd "$PROJECT_ROOT" + pip install -e . + echo -e "${GREEN}✓ Python package installed${NC}" +else + echo -e "${YELLOW}Skipping Python package install${NC}" +fi + +echo "" + +# Summary +echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" +echo -e "${GREEN}${BOLD}✓ Setup Complete!${NC}" +echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" +echo "" +echo -e "${BLUE}${BOLD}Next Steps:${NC}" +echo -e " ${CYAN}1.${NC} Run tests: ${CYAN}cd build && ctest --verbose${NC}" +echo -e " ${CYAN}2.${NC} Try examples: ${CYAN}python examples/python_persistence_example.py${NC}" +echo -e " ${CYAN}3.${NC} Read docs: ${CYAN}cat README.md${NC}" +echo "" +echo -e "${YELLOW}${BOLD}Git Hooks Installed:${NC}" +echo -e " ${GREEN}•${NC} pre-commit: Checks code quality before commits" +echo -e " ${GREEN}•${NC} pre-push: Manages version updates and PyPI publishing" +echo "" +echo -e "${BLUE}${BOLD}Useful Commands:${NC}" +echo -e " ${CYAN}./build.sh${NC} - Quick rebuild" +echo -e " ${CYAN}sage-pypi-publisher build${NC} - Build distribution packages" +echo -e " ${CYAN}sage-pypi-publisher publish${NC} - Build and publish to PyPI" +echo "" +echo -e "${GREEN}Happy coding! 🚀${NC}" +echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" From 3fef5f18b1ce00b7eedf3ca0c2da932d7163ff00 Mon Sep 17 00:00:00 2001 From: zhang shuhao Date: Wed, 11 Feb 2026 11:20:00 +0800 Subject: [PATCH 02/15] chore: add unified pre-push hook with post-push PyPI publish --- hooks/pre-push | 353 ++++++++++++++++++++++++++++++------------------- 1 file changed, 216 insertions(+), 137 deletions(-) diff --git a/hooks/pre-push b/hooks/pre-push index 35a2f10..c815a3e 100755 --- a/hooks/pre-push +++ b/hooks/pre-push @@ -1,8 +1,13 @@ #!/bin/bash -# Pre-push hook managed by sage-pypi-publisher -# Auto-detects version updates and offers to build/upload to PyPI +# Pre-push hook — version check + post-push PyPI publish +# +# Flow: +# 1. Check version status (fast) +# 2. Interactive prompts if needed (version bump, publish decision) +# 3. Exit 0 → git push proceeds immediately +# 4. Background job publishes to PyPI after push finishes -set -e +# No set -e: we handle errors explicitly to avoid blocking push # Colors RED='\033[0;31m' @@ -10,50 +15,147 @@ YELLOW='\033[1;33m' GREEN='\033[0;32m' CYAN='\033[0;36m' BLUE='\033[0;34m' +DIM='\033[2m' NC='\033[0m' -PACKAGE_NAME=$(grep -oP '^name = "\K[^"]+' pyproject.toml 2>/dev/null || echo "unknown") +WANT_PUBLISH=false +REPO_DIR="$(pwd)" +REPO_NAME="$(basename "$REPO_DIR")" +PUBLISH_LOG="/tmp/${REPO_NAME}-publish-$$.log" -check_pypi_version_exists() { - if [ "$PACKAGE_NAME" = "unknown" ]; then - return 2 - fi - if command -v python3 &> /dev/null; then - python3 - "$PACKAGE_NAME" "$CURRENT_VERSION" <<'PY' -import json -import sys -import urllib.request - -package = sys.argv[1] -version = sys.argv[2] -try: - with urllib.request.urlopen(f"https://pypi.org/pypi/{package}/json", timeout=5) as resp: - data = json.load(resp) - releases = data.get("releases", {}) - sys.exit(0 if version in releases else 1) -except Exception: - sys.exit(2) -PY - return $? - elif command -v curl &> /dev/null; then - curl -fsSL "https://pypi.org/pypi/${PACKAGE_NAME}/json" | grep -q "\"${CURRENT_VERSION}\"" - return $? +# Safe read: falls back to default if /dev/tty is unavailable (SSH drop, IDE, etc.) +safe_read() { + local varname="$1" + local default="$2" + if [ -t 0 ] || [ -c /dev/tty ] 2>/dev/null; then + read -r "$varname" /dev/null || eval "$varname=\"$default\"" else - return 2 + eval "$varname=\"$default\"" fi } -CURRENT_VERSION=$(grep -oP '^version = "\K[^"]+' pyproject.toml 2>/dev/null || echo "unknown") -if [ "$CURRENT_VERSION" = "unknown" ]; then - echo -e "${YELLOW}No pyproject.toml found, skipping version check${NC}" +# Auto-find _version.py in repo +find_version_files() { + find . -maxdepth 4 -name '_version.py' -not -path '*/node_modules/*' -not -path '*/.git/*' -not -path '*/dist/*' -not -path '*/.egg-info/*' -not -path '*/build/*' 2>/dev/null +} + +# Function to update version (supports both static and dynamic versioning) +update_version() { + local old_version="$1" + local new_version="$2" + local updated=false + + # Try to update pyproject.toml (static version) + if grep -q '^version = "' pyproject.toml 2>/dev/null; then + sed -i "s/version = \"${old_version}\"/version = \"${new_version}\"/" pyproject.toml + git add pyproject.toml + updated=true + fi + + # Try to update _version.py (dynamic version) + while IFS= read -r VERSION_FILE; do + if [ -f "$VERSION_FILE" ] && grep -q '__version__ = "' "$VERSION_FILE" 2>/dev/null; then + sed -i "s/__version__ = \"${old_version}\"/__version__ = \"${new_version}\"/" "$VERSION_FILE" + git add "$VERSION_FILE" + updated=true + fi + done < <(find_version_files) + + if [ "$updated" = false ]; then + echo -e "${RED}✗ Failed to update version (no version file found)${NC}" + return 1 + fi + + return 0 +} + +# Schedule PyPI publish to run AFTER push completes (background) +schedule_post_push_publish() { + local version="$1" + local package="$2" + + if ! command -v sage-pypi-publisher &> /dev/null; then + echo -e "${YELLOW}⚠ sage-pypi-publisher not found, skipping auto-publish${NC}" + echo -e "${DIM} Install: pip install isage-pypi-publisher${NC}" + return + fi + + echo -e "${GREEN}📦 PyPI publish scheduled (runs after push)${NC}" + echo -e "${DIM} Log: tail -f ${PUBLISH_LOG}${NC}" + + # Fork background job - waits for git push (parent of pre-push) to finish + ( + GIT_PID="$PPID" + while kill -0 "$GIT_PID" 2>/dev/null; do + sleep 1 + done + sleep 1 + + cd "$REPO_DIR" || exit 1 + + { + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo "📦 Post-push: Building ${package} ${version}..." + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + + rm -rf dist/ build/ *.egg-info 2>/dev/null || true + + if sage-pypi-publisher build . --upload --no-dry-run; then + echo "" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo "✓ Successfully uploaded ${package} ${version} to PyPI" + echo "🔗 https://pypi.org/project/${package}/${version}/" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + else + echo "" + echo "✗ Failed to upload to PyPI (exit code: $?)" + echo " Re-run manually: sage-pypi-publisher build . --upload --no-dry-run" + fi + } >> "$PUBLISH_LOG" 2>&1 + + # Print summary to terminal (user sees it after push output) + if grep -q "Successfully uploaded" "$PUBLISH_LOG" 2>/dev/null; then + echo -e "\n${GREEN}✓ PyPI: ${package} ${version} published${NC}" + else + echo -e "\n${RED}✗ PyPI publish failed. See: ${PUBLISH_LOG}${NC}" + fi + ) & + disown +} + +# --- Main Logic --- + +# Extract package name from pyproject.toml +if [ ! -f pyproject.toml ]; then + exit 0 +fi + +PACKAGE_NAME=$(grep -oP '^name = "\K[^"]+' pyproject.toml 2>/dev/null || echo "unknown") + +# Try to get version from pyproject.toml (static version) +CURRENT_VERSION=$(grep -oP '^version = "\K[^"]+' pyproject.toml 2>/dev/null || true) + +# If not found, try _version.py (dynamic version) +if [ -z "$CURRENT_VERSION" ] || [ "$CURRENT_VERSION" = "unknown" ]; then + while IFS= read -r VERSION_FILE; do + if [ -f "$VERSION_FILE" ]; then + CURRENT_VERSION=$(grep -oP '__version__ = "\K[^"]+' "$VERSION_FILE" 2>/dev/null || true) + if [ -n "$CURRENT_VERSION" ]; then + break + fi + fi + done < <(find_version_files) +fi + +if [ -z "$CURRENT_VERSION" ] || [ "$CURRENT_VERSION" = "unknown" ]; then exit 0 fi if ! git rev-parse HEAD~1 >/dev/null 2>&1; then - echo -e "${YELLOW}First commit detected, skipping version check${NC}" exit 0 fi +# Check if version was updated in recent commits VERSION_UPDATED=false for i in {1..5}; do if git diff HEAD~$i HEAD -- pyproject.toml 2>/dev/null | grep -q '^[+-]version = '; then @@ -62,146 +164,123 @@ for i in {1..5}; do fi done +if [ "$VERSION_UPDATED" = false ]; then + VERSION_FILES_FOR_DIFF=$(find_version_files | tr '\n' ' ') + if [ -n "$VERSION_FILES_FOR_DIFF" ]; then + for i in {1..5}; do + if git diff HEAD~$i HEAD -- $VERSION_FILES_FOR_DIFF 2>/dev/null | grep -q '^[+-]__version__ = '; then + VERSION_UPDATED=true + break + fi + done + fi +fi + +# --- Version Updated Path --- if [ "$VERSION_UPDATED" = true ]; then - echo -e "${GREEN}✓ Version updated to ${CURRENT_VERSION}${NC}" - PYPI_CHECK_RESULT=0 - check_pypi_version_exists - PYPI_CHECK_RESULT=$? + echo -e "${GREEN}✓ [${REPO_NAME}] Version updated to ${CURRENT_VERSION}${NC}" + + # Quick PyPI check (5s timeout, non-blocking) + PYPI_CHECK_RESULT=1 + if [ "$PACKAGE_NAME" != "unknown" ] && command -v python3 &> /dev/null; then + python3 - "$PACKAGE_NAME" "$CURRENT_VERSION" <<'PY' && PYPI_CHECK_RESULT=0 || PYPI_CHECK_RESULT=$? +import json, sys, urllib.request +try: + with urllib.request.urlopen(f"https://pypi.org/pypi/{sys.argv[1]}/json", timeout=5) as r: + sys.exit(0 if sys.argv[2] in json.load(r).get("releases", {}) else 1) +except Exception: + sys.exit(2) +PY + fi + if [ "$PYPI_CHECK_RESULT" -eq 0 ]; then - echo -e "${YELLOW}⚠ ${PACKAGE_NAME} ${CURRENT_VERSION} already exists on PyPI${NC}" - echo -e "${BLUE}What would you like to do?${NC}" - echo -e " ${GREEN}[u]${NC} Update version now (interactive)" - echo -e " ${YELLOW}[y]${NC} Continue anyway" - echo -e " ${RED}[c]${NC} Cancel push" - echo -n "Your choice [u/y/c]: " - read -r pypi_resp /dev/null || true - - if command -v sage-pypi-publisher &> /dev/null; then - echo -e "${GREEN}Using sage-pypi-publisher (auto-detects build type)...${NC}" - if sage-pypi-publisher build . --upload --no-dry-run; then - echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" - echo -e "${GREEN}✓ Successfully uploaded ${CURRENT_VERSION} to PyPI${NC}" - echo -e "${GREEN}🔗 https://pypi.org/project/isage-vdb/${CURRENT_VERSION}/${NC}" - echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" - else - echo -e "${RED}✗ Failed to upload to PyPI${NC}" - echo -e "${YELLOW}Continue push anyway? [y/N]${NC}" - read -r cont /dev/null || true - if command -v sage-pypi-publisher &> /dev/null; then - sage-pypi-publisher build . --upload --no-dry-run - echo -e "${GREEN}✓ Uploaded ${new_version} to PyPI${NC}" - fi + old_version="$CURRENT_VERSION" + if update_version "$CURRENT_VERSION" "$new_version"; then + git commit -m "chore: bump version to ${new_version}" + CURRENT_VERSION="$new_version" + echo -e "${GREEN}✓ ${old_version} → ${new_version}${NC}" + else + exit 1 + fi + + echo -e "${BLUE}📦 Publish ${CURRENT_VERSION} to PyPI after push? [Y/n]:${NC}" + safe_read upload_resp "y" + if [[ ! "$upload_resp" =~ ^[Nn]$ ]]; then + WANT_PUBLISH=true fi - exit 0 elif [[ "$response" =~ ^[Yy]$ ]]; then - echo -e "${YELLOW}Continuing without version update${NC}" + : # continue else echo -e "${YELLOW}Push cancelled${NC}" exit 1 fi fi +# Schedule background publish if requested (runs AFTER push completes) +if [ "$WANT_PUBLISH" = true ]; then + schedule_post_push_publish "$CURRENT_VERSION" "$PACKAGE_NAME" +fi + +# Exit 0 → push proceeds immediately, never blocked by build/upload exit 0 From d4282d3e6befd44fa9d79173696f42d26effba6d Mon Sep 17 00:00:00 2001 From: zhang shuhao Date: Tue, 24 Feb 2026 10:34:30 +0800 Subject: [PATCH 03/15] refactor: simplify hooks - post-commit auto-bumps, pre-push only publishes --- hooks/post-commit | 89 +++++++++++++++++ hooks/pre-push | 249 ++++++++++++++-------------------------------- 2 files changed, 162 insertions(+), 176 deletions(-) create mode 100755 hooks/post-commit diff --git a/hooks/post-commit b/hooks/post-commit new file mode 100755 index 0000000..4f8c904 --- /dev/null +++ b/hooks/post-commit @@ -0,0 +1,89 @@ +#!/usr/bin/env bash +# Post-commit hook for sageLLM repositories +# Automatically bumps BUILD version (X.Y.Z.BUILD → X.Y.Z.BUILD+1) after each commit. +# Version source of truth: src//_version.py +# +# This runs AFTER commit, amends it with the bumped version, +# so the pre-push hook never needs to handle version bumping. + +set -eo pipefail + +# Recursion guard: skip if we're already inside an amend +if [ -f ".git/SAGE_POST_COMMIT_RUNNING" ]; then + exit 0 +fi + +# Allow disabling via environment variable +if [ "${SAGE_SKIP_VERSION_BUMP:-0}" = "1" ]; then + exit 0 +fi + +# Colors +BLUE='\033[0;34m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +# Bump BUILD digit: X.Y.Z.N → X.Y.Z.(N+1), X.Y.Z → X.Y.Z.1 +bump_version() { + local v="$1" + if [[ "$v" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then + echo "${BASH_REMATCH[1]}.${BASH_REMATCH[2]}.${BASH_REMATCH[3]}.$((BASH_REMATCH[4] + 1))" + return 0 + fi + if [[ "$v" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then + echo "${BASH_REMATCH[1]}.${BASH_REMATCH[2]}.${BASH_REMATCH[3]}.1" + return 0 + fi + return 1 +} + +# Find _version.py — single source of truth +find_version_file() { + find src -maxdepth 4 -name '_version.py' \ + -not -path '*/.git/*' \ + -not -path '*/dist/*' \ + -not -path '*/.egg-info/*' \ + -not -path '*/build/*' \ + 2>/dev/null | head -1 +} + +VERSION_FILE=$(find_version_file) +if [ -z "$VERSION_FILE" ]; then + exit 0 # Not a Python package repo, nothing to do +fi + +# Check if _version.py was already changed in this commit +if git diff --name-only HEAD~1 HEAD 2>/dev/null | grep -q '_version.py'; then + exit 0 # Developer manually bumped version, don't override +fi + +# Read current version from _version.py +current_version=$(grep -oP '__version__ = "\K[^"]+' "$VERSION_FILE" 2>/dev/null || true) +if [ -z "$current_version" ]; then + exit 0 +fi + +# Calculate new version +if ! new_version=$(bump_version "$current_version"); then + echo -e "${YELLOW}⚠️ Could not auto-bump version (invalid format: $current_version)${NC}" + exit 0 +fi + +echo -e "${BLUE}📦 Auto-bumping version: $current_version → $new_version${NC}" + +# Lock to prevent recursion +touch .git/SAGE_POST_COMMIT_RUNNING + +# Update _version.py (single source of truth) +sed -i "s/__version__ = \"${current_version}\"/__version__ = \"${new_version}\"/" "$VERSION_FILE" +git add "$VERSION_FILE" + +# Amend commit with version bump (--no-verify to skip re-triggering hooks) +git commit --amend --no-edit --no-verify + +# Cleanup +rm -f .git/SAGE_POST_COMMIT_RUNNING + +echo -e "${GREEN}✓ Version bumped to $new_version (commit amended)${NC}" +exit 0 diff --git a/hooks/pre-push b/hooks/pre-push index c815a3e..0b711dd 100755 --- a/hooks/pre-push +++ b/hooks/pre-push @@ -1,13 +1,23 @@ #!/bin/bash -# Pre-push hook — version check + post-push PyPI publish +# Pre-push hook — block main push + optional PyPI publish # # Flow: -# 1. Check version status (fast) -# 2. Interactive prompts if needed (version bump, publish decision) -# 3. Exit 0 → git push proceeds immediately -# 4. Background job publishes to PyPI after push finishes +# 1. Block direct push to main branch +# 2. Display current version +# 3. Check if version already exists on PyPI +# 4. Offer to publish after push completes (background job) +# +# Version bumping is handled entirely by the post-commit hook. +# This hook NEVER bumps version or creates additional commits. +# → Single push only, no double-push. + +# Recursion guard (unused now, kept for safety) +if [ "${_SAGELLM_PP_RUNNING:-0}" = "1" ]; then + exit 0 +fi -# No set -e: we handle errors explicitly to avoid blocking push +# Publish mode: "private" for most repos, "public" for sage-pypi-publisher +PUBLISH_MODE=public # Colors RED='\033[0;31m' @@ -18,12 +28,26 @@ BLUE='\033[0;34m' DIM='\033[2m' NC='\033[0m' -WANT_PUBLISH=false REPO_DIR="$(pwd)" REPO_NAME="$(basename "$REPO_DIR")" PUBLISH_LOG="/tmp/${REPO_NAME}-publish-$$.log" -# Safe read: falls back to default if /dev/tty is unavailable (SSH drop, IDE, etc.) +# --- Block direct push to main --- +block_main_push=false +while read -r local_ref local_sha remote_ref remote_sha; do + if [ "$local_ref" = "refs/heads/main" ] || [ "$remote_ref" = "refs/heads/main" ]; then + block_main_push=true + break + fi +done + +if [ "$block_main_push" = true ]; then + echo -e "${RED}✗ Direct push to main is forbidden${NC}" + echo -e "${YELLOW} Please push to a feature branch and merge via PR.${NC}" + exit 1 +fi + +# Safe read: falls back to default if /dev/tty is unavailable (SSH, IDE, etc.) safe_read() { local varname="$1" local default="$2" @@ -34,43 +58,19 @@ safe_read() { fi } -# Auto-find _version.py in repo -find_version_files() { - find . -maxdepth 4 -name '_version.py' -not -path '*/node_modules/*' -not -path '*/.git/*' -not -path '*/dist/*' -not -path '*/.egg-info/*' -not -path '*/build/*' 2>/dev/null -} - -# Function to update version (supports both static and dynamic versioning) -update_version() { - local old_version="$1" - local new_version="$2" - local updated=false - - # Try to update pyproject.toml (static version) - if grep -q '^version = "' pyproject.toml 2>/dev/null; then - sed -i "s/version = \"${old_version}\"/version = \"${new_version}\"/" pyproject.toml - git add pyproject.toml - updated=true - fi - - # Try to update _version.py (dynamic version) - while IFS= read -r VERSION_FILE; do - if [ -f "$VERSION_FILE" ] && grep -q '__version__ = "' "$VERSION_FILE" 2>/dev/null; then - sed -i "s/__version__ = \"${old_version}\"/__version__ = \"${new_version}\"/" "$VERSION_FILE" - git add "$VERSION_FILE" - updated=true - fi - done < <(find_version_files) - - if [ "$updated" = false ]; then - echo -e "${RED}✗ Failed to update version (no version file found)${NC}" - return 1 - fi - - return 0 +# Find _version.py — single source of truth +find_version_file() { + find . -maxdepth 4 -name '_version.py' \ + -not -path '*/node_modules/*' \ + -not -path '*/.git/*' \ + -not -path '*/dist/*' \ + -not -path '*/.egg-info/*' \ + -not -path '*/build/*' \ + 2>/dev/null | head -1 } -# Schedule PyPI publish to run AFTER push completes (background) -schedule_post_push_publish() { +# Schedule PyPI publish as background job after push completes +schedule_publish() { local version="$1" local package="$2" @@ -83,8 +83,8 @@ schedule_post_push_publish() { echo -e "${GREEN}📦 PyPI publish scheduled (runs after push)${NC}" echo -e "${DIM} Log: tail -f ${PUBLISH_LOG}${NC}" - # Fork background job - waits for git push (parent of pre-push) to finish ( + # Wait for parent git push to finish GIT_PID="$PPID" while kill -0 "$GIT_PID" 2>/dev/null; do sleep 1 @@ -100,7 +100,7 @@ schedule_post_push_publish() { rm -rf dist/ build/ *.egg-info 2>/dev/null || true - if sage-pypi-publisher build . --upload --no-dry-run; then + if sage-pypi-publisher build . --upload --no-dry-run --mode ${PUBLISH_MODE}; then echo "" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "✓ Successfully uploaded ${package} ${version} to PyPI" @@ -109,7 +109,7 @@ schedule_post_push_publish() { else echo "" echo "✗ Failed to upload to PyPI (exit code: $?)" - echo " Re-run manually: sage-pypi-publisher build . --upload --no-dry-run" + echo " Re-run manually: sage-pypi-publisher build . --upload --no-dry-run --mode ${PUBLISH_MODE}" fi } >> "$PUBLISH_LOG" 2>&1 @@ -125,162 +125,59 @@ schedule_post_push_publish() { # --- Main Logic --- -# Extract package name from pyproject.toml if [ ! -f pyproject.toml ]; then exit 0 fi PACKAGE_NAME=$(grep -oP '^name = "\K[^"]+' pyproject.toml 2>/dev/null || echo "unknown") -# Try to get version from pyproject.toml (static version) -CURRENT_VERSION=$(grep -oP '^version = "\K[^"]+' pyproject.toml 2>/dev/null || true) - -# If not found, try _version.py (dynamic version) -if [ -z "$CURRENT_VERSION" ] || [ "$CURRENT_VERSION" = "unknown" ]; then - while IFS= read -r VERSION_FILE; do - if [ -f "$VERSION_FILE" ]; then - CURRENT_VERSION=$(grep -oP '__version__ = "\K[^"]+' "$VERSION_FILE" 2>/dev/null || true) - if [ -n "$CURRENT_VERSION" ]; then - break - fi - fi - done < <(find_version_files) +# Get version from _version.py (single source of truth) +VERSION_FILE=$(find_version_file) +CURRENT_VERSION="" +if [ -n "$VERSION_FILE" ]; then + CURRENT_VERSION=$(grep -oP '__version__ = "\K[^"]+' "$VERSION_FILE" 2>/dev/null || true) fi -if [ -z "$CURRENT_VERSION" ] || [ "$CURRENT_VERSION" = "unknown" ]; then +if [ -z "$CURRENT_VERSION" ]; then exit 0 fi -if ! git rev-parse HEAD~1 >/dev/null 2>&1; then - exit 0 -fi - -# Check if version was updated in recent commits -VERSION_UPDATED=false -for i in {1..5}; do - if git diff HEAD~$i HEAD -- pyproject.toml 2>/dev/null | grep -q '^[+-]version = '; then - VERSION_UPDATED=true - break - fi -done - -if [ "$VERSION_UPDATED" = false ]; then - VERSION_FILES_FOR_DIFF=$(find_version_files | tr '\n' ' ') - if [ -n "$VERSION_FILES_FOR_DIFF" ]; then - for i in {1..5}; do - if git diff HEAD~$i HEAD -- $VERSION_FILES_FOR_DIFF 2>/dev/null | grep -q '^[+-]__version__ = '; then - VERSION_UPDATED=true - break - fi - done - fi -fi - -# --- Version Updated Path --- -if [ "$VERSION_UPDATED" = true ]; then - echo -e "${GREEN}✓ [${REPO_NAME}] Version updated to ${CURRENT_VERSION}${NC}" +echo -e "${GREEN}✓ [${REPO_NAME}] Version: ${CURRENT_VERSION}${NC}" - # Quick PyPI check (5s timeout, non-blocking) - PYPI_CHECK_RESULT=1 - if [ "$PACKAGE_NAME" != "unknown" ] && command -v python3 &> /dev/null; then - python3 - "$PACKAGE_NAME" "$CURRENT_VERSION" <<'PY' && PYPI_CHECK_RESULT=0 || PYPI_CHECK_RESULT=$? +# Quick PyPI check (5s timeout, non-blocking) +PYPI_CHECK_RESULT=1 +if [ "$PACKAGE_NAME" != "unknown" ] && command -v python3 &> /dev/null; then + python3 - "$PACKAGE_NAME" "$CURRENT_VERSION" <<'PY' && PYPI_CHECK_RESULT=0 || PYPI_CHECK_RESULT=$? import json, sys, urllib.request try: with urllib.request.urlopen(f"https://pypi.org/pypi/{sys.argv[1]}/json", timeout=5) as r: sys.exit(0 if sys.argv[2] in json.load(r).get("releases", {}) else 1) +except urllib.error.HTTPError as e: + sys.exit(1 if e.code == 404 else 2) +except (urllib.error.URLError, OSError, TimeoutError): + sys.exit(2) except Exception: sys.exit(2) PY - fi - - if [ "$PYPI_CHECK_RESULT" -eq 0 ]; then - echo -e "${YELLOW}⚠ ${PACKAGE_NAME} ${CURRENT_VERSION} already on PyPI${NC}" - echo -e " ${GREEN}[u]${NC} Update version ${YELLOW}[y]${NC} Continue ${RED}[c]${NC} Cancel" - echo -n "Choice [u/y/c]: " - safe_read pypi_resp "y" - - if [[ "$pypi_resp" =~ ^[Uu]$ ]]; then - echo -e "${YELLOW}Current: ${BLUE}${CURRENT_VERSION}${NC}" - echo -n "New version: " - safe_read new_version "" - - if [[ ! "$new_version" =~ ^[0-9]+\.[0-9]+\.[0-9]+(\.[0-9]+)?$ ]]; then - echo -e "${RED}✗ Invalid format (expected X.Y.Z or X.Y.Z.N)${NC}" - exit 1 - fi - - old_version="$CURRENT_VERSION" - if update_version "$CURRENT_VERSION" "$new_version"; then - git commit -m "chore: bump version to ${new_version}" - CURRENT_VERSION="$new_version" - echo -e "${GREEN}✓ ${old_version} → ${new_version}${NC}" - else - exit 1 - fi - elif [[ "$pypi_resp" =~ ^[Cc]$ ]]; then - echo -e "${YELLOW}Push cancelled${NC}" - exit 1 - fi - elif [ "$PYPI_CHECK_RESULT" -eq 2 ]; then - echo -e "${DIM}(PyPI check skipped - network unavailable)${NC}" - fi +fi - # Ask about publishing - echo -e "${BLUE}📦 Publish ${CURRENT_VERSION} to PyPI after push? [Y/n/c]:${NC}" +if [ "$PYPI_CHECK_RESULT" -eq 0 ]; then + echo -e "${YELLOW}⚠ ${PACKAGE_NAME} ${CURRENT_VERSION} already on PyPI — skipping publish${NC}" +elif [ "$PYPI_CHECK_RESULT" -eq 2 ]; then + echo -e "${DIM}(PyPI check skipped — network/timeout issue)${NC}" + echo -e "${BLUE}📦 Publish ${CURRENT_VERSION} to PyPI after push? [Y/n]:${NC}" safe_read response "y" - - if [[ "$response" =~ ^[Cc]$ ]]; then - echo -e "${YELLOW}Push cancelled${NC}" - exit 1 - elif [[ "$response" =~ ^[Nn]$ ]]; then - echo -e "${DIM}Skipping PyPI${NC}" - else - WANT_PUBLISH=true + if [[ ! "$response" =~ ^[Nn]$ ]]; then + schedule_publish "$CURRENT_VERSION" "$PACKAGE_NAME" fi - -# --- Version NOT Updated Path --- else - echo -e "${YELLOW}⚠ [${REPO_NAME}] Version not updated (current: ${CURRENT_VERSION})${NC}" - echo -e " ${GREEN}[u]${NC} Update version ${YELLOW}[y]${NC} Continue ${RED}[n]${NC} Cancel" - echo -n "Choice [u/y/n]: " + # Version not on PyPI — offer to publish + echo -e "${BLUE}📦 Publish ${CURRENT_VERSION} to PyPI after push? [Y/n]:${NC}" safe_read response "y" - - if [[ "$response" =~ ^[Uu]$ ]]; then - echo -e "${YELLOW}Current: ${BLUE}${CURRENT_VERSION}${NC}" - echo -n "New version: " - safe_read new_version "" - - if [[ ! "$new_version" =~ ^[0-9]+\.[0-9]+\.[0-9]+(\.[0-9]+)?$ ]]; then - echo -e "${RED}✗ Invalid format (expected X.Y.Z or X.Y.Z.N)${NC}" - exit 1 - fi - - old_version="$CURRENT_VERSION" - if update_version "$CURRENT_VERSION" "$new_version"; then - git commit -m "chore: bump version to ${new_version}" - CURRENT_VERSION="$new_version" - echo -e "${GREEN}✓ ${old_version} → ${new_version}${NC}" - else - exit 1 - fi - - echo -e "${BLUE}📦 Publish ${CURRENT_VERSION} to PyPI after push? [Y/n]:${NC}" - safe_read upload_resp "y" - if [[ ! "$upload_resp" =~ ^[Nn]$ ]]; then - WANT_PUBLISH=true - fi - elif [[ "$response" =~ ^[Yy]$ ]]; then - : # continue - else - echo -e "${YELLOW}Push cancelled${NC}" - exit 1 + if [[ ! "$response" =~ ^[Nn]$ ]]; then + schedule_publish "$CURRENT_VERSION" "$PACKAGE_NAME" fi fi -# Schedule background publish if requested (runs AFTER push completes) -if [ "$WANT_PUBLISH" = true ]; then - schedule_post_push_publish "$CURRENT_VERSION" "$PACKAGE_NAME" -fi - # Exit 0 → push proceeds immediately, never blocked by build/upload exit 0 From 79805548de17f89a58d8b4eca81e1cb00c29010c Mon Sep 17 00:00:00 2001 From: zhang shuhao Date: Tue, 24 Feb 2026 13:41:55 +0800 Subject: [PATCH 04/15] chore: add/update quickstart.sh with post-commit hook support --- pyproject.toml | 14 ++++---------- quickstart.sh | 7 +++++++ 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 6dde9f4..00b8993 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,9 +8,7 @@ version = "0.1.0" description = "GitHub Issues Management Tool - Download, analyze, and manage GitHub Issues with AI" readme = "README.md" requires-python = ">=3.10" -authors = [ - { name = "IntelliStream Team", email = "shuhao_zhang@hust.edu.cn" }, -] +authors = [{ name = "IntelliStream Team", email = "shuhao_zhang@hust.edu.cn" }] keywords = [ "github", "issues", @@ -32,7 +30,7 @@ classifiers = [ "Programming Language :: Python :: 3.12", "License :: OSI Approved :: MIT License", ] -license = {text = "MIT"} +license = { text = "MIT" } dependencies = [ "typer>=0.15.0,<1.0.0", @@ -53,6 +51,7 @@ dev = [ "pre-commit>=4.0.0", "types-requests>=2.31.0", "types-PyYAML>=6.0.0", + "sage-pypi-publisher>=0.1.0", ] [project.scripts] @@ -87,12 +86,7 @@ warn_unused_ignores = true # Coverage configuration [tool.coverage.run] source = ["src/sage_github"] -omit = [ - "*/tests/*", - "*/test_*.py", - "*/__pycache__/*", - "*/site-packages/*", -] +omit = ["*/tests/*", "*/test_*.py", "*/__pycache__/*", "*/site-packages/*"] [tool.coverage.report] exclude_lines = [ diff --git a/quickstart.sh b/quickstart.sh index 13d9766..c18c8c5 100755 --- a/quickstart.sh +++ b/quickstart.sh @@ -64,6 +64,13 @@ else echo -e "${YELLOW}⚠ pre-push template not found, skipping${NC}" fi +# Install post-commit hook (auto-bump version) +if [ -f "$TEMPLATE_DIR/post-commit" ]; then + cp "$TEMPLATE_DIR/post-commit" "$HOOKS_DIR/post-commit" + chmod +x "$HOOKS_DIR/post-commit" + echo -e "${GREEN}✓ Installed post-commit hook${NC}" +fi + echo "" # Step 2: Check dependencies From 81a7a1851f81ab525421c08fb34c7f4a2bcd6c0b Mon Sep 17 00:00:00 2001 From: zhang shuhao Date: Tue, 24 Feb 2026 15:35:42 +0800 Subject: [PATCH 05/15] chore(agents): standardize copilot-instructions and agent tools to canonical full-tool list --- .github/agents/sage-github.agent.md | 318 ++------------------- .github/copilot-instructions.md | 412 ++-------------------------- 2 files changed, 37 insertions(+), 693 deletions(-) diff --git a/.github/agents/sage-github.agent.md b/.github/agents/sage-github.agent.md index 23f1006..7329e2d 100644 --- a/.github/agents/sage-github.agent.md +++ b/.github/agents/sage-github.agent.md @@ -1,307 +1,23 @@ --- -description: 'Expert assistant for managing SAGE project GitHub Issues using sage-github-manager CLI tool' -tools: ['vscode', 'execute', 'read', 'agent', 'edit', 'search', 'web', 'copilot-container-tools/*', 'pylance-mcp-server/*', 'todo', 'github.vscode-pull-request-github/copilotCodingAgent', 'github.vscode-pull-request-github/issue_fetch', 'github.vscode-pull-request-github/suggest-fix', 'github.vscode-pull-request-github/searchSyntax', 'github.vscode-pull-request-github/doSearch', 'github.vscode-pull-request-github/renderIssues', 'github.vscode-pull-request-github/activePullRequest', 'github.vscode-pull-request-github/openPullRequest', 'ms-python.python/getPythonEnvironmentInfo', 'ms-python.python/getPythonExecutableCommand', 'ms-python.python/installPythonPackage', 'ms-python.python/configurePythonEnvironment'] +name: sage-github-manager +description: Agent for GitHub issue management CLI behavior, storage, and command UX. +argument-hint: Provide command path, issue behavior, and expected CLI output. +tools: ['vscode', 'execute', 'read', 'agent', 'edit', 'search', 'web', 'todo', 'vscode.mermaid-chat-features/renderMermaidDiagram', 'github.vscode-pull-request-github/issue_fetch', 'github.vscode-pull-request-github/suggest-fix', 'github.vscode-pull-request-github/searchSyntax', 'github.vscode-pull-request-github/doSearch', 'github.vscode-pull-request-github/renderIssues', 'github.vscode-pull-request-github/activePullRequest', 'github.vscode-pull-request-github/openPullRequest', 'ms-azuretools.vscode-containers/containerToolsConfig', 'ms-python.python/getPythonEnvironmentInfo', 'ms-python.python/getPythonExecutableCommand', 'ms-python.python/installPythonPackage', 'ms-python.python/configurePythonEnvironment', 'ms-toolsai.jupyter/configureNotebook', 'ms-toolsai.jupyter/listNotebookPackages', 'ms-toolsai.jupyter/installNotebookPackages', 'ms-vscode.cpp-devtools/Build_CMakeTools', 'ms-vscode.cpp-devtools/RunCtest_CMakeTools', 'ms-vscode.cpp-devtools/ListBuildTargets_CMakeTools', 'ms-vscode.cpp-devtools/ListTests_CMakeTools'] --- -# SAGE GitHub Issues Management Agent +# Sage GitHub Manager Agent -## Purpose +## Scope +- CLI and orchestration in `src/sage_github/`. +- Persistence and helper modules under same package. -This agent specializes in helping you **manage GitHub Issues for the SAGE project** (intellistream/SAGE) using the sage-github-manager CLI tool. The tool provides powerful commands for downloading, analyzing, and managing issues with AI-powered features. +## Rules +- Keep fail-fast behavior; no silent fallbacks. +- Keep dependency declarations in `pyproject.toml`. +- Preserve UX contracts for `github-manager` / `gh-issues`. +- Keep helpers single-purpose and typed. -**Primary Focus**: Issue management, analytics, and workflow automation for SAGE project. -**Secondary Focus**: Developing and maintaining the sage-github-manager tool itself. - -## When to Use This Agent - -Use this agent when you need to: - -✅ **Manage SAGE Issues** -- Download and sync SAGE repository issues locally -- List, filter, and search issues -- View analytics and generate reports -- Track issue trends and contributor activity - -✅ **Batch Operations** -- Close multiple issues matching criteria -- Apply labels to issue groups -- Assign issues to team members -- Update milestones and priorities - -✅ **AI-Powered Analysis** -- Summarize long issue discussions -- Detect duplicate issues -- Auto-suggest relevant labels -- Generate roadmaps and release notes - -✅ **Export & Reporting** -- Export to CSV for spreadsheet analysis -- Generate Markdown reports for documentation -- Create JSON exports for integrations -- Build custom dashboards - -✅ **Tool Development** (Secondary) -- Add new features to sage-github-manager -- Fix bugs and improve performance -- Write tests and documentation -- Enhance CLI commands - -## What This Agent Won't Do - -❌ **Will NOT:** -- Use fallback logic that hides errors (project-wide rule) -- Install dependencies manually with `pip install` (must use pyproject.toml) -- Create configuration in `.sage/issues/` (uses `~/.github-manager/`) -- Compromise on code quality standards -- Operate on repositories other than SAGE without explicit configuration - -## Ideal Inputs - -**For Issue Management:** -- "Download all open SAGE issues" -- "Show me bugs labeled 'priority:high'" -- "Generate analytics report for last month" -- "Close all issues with label 'wontfix'" -- "Export issues for v2.0 milestone to CSV" - -**For Development:** -- "Add a command to filter issues by assignee" -- "Fix the bug in export module" -- "Add tests for the new analytics feature" -- "Update documentation for batch commands" - -## Expected Outputs - -**For Issue Management:** -- Ready-to-execute CLI commands -- Workflow suggestions for common tasks -- Analytics insights and recommendations -- Export files in requested formats - -**For Development:** -- Clean, tested code following project standards -- Type-annotated functions with docstrings -- Test coverage for new features -- Updated documentation - -## Quick Start Guide - -### Initial Setup -```bash -# Install the tool -pip install -e ".[dev]" - -# Configure for SAGE project -export GITHUB_TOKEN=ghp_xxxxxxxxxxxxx -export GITHUB_OWNER=intellistream -export GITHUB_REPO=SAGE - -# Download SAGE issues -github-manager download -``` - -### Common Commands -```bash -# List issues -github-manager list --state open -github-manager list --label bug --assignee shuhao - -# Analytics -github-manager analytics -github-manager analytics --timeframe 30days - -# Export -github-manager export --format csv --output sage_issues.csv -github-manager export --format markdown --output ROADMAP.md - -# Batch operations -github-manager batch close --label "wontfix" -github-manager batch label --add "reviewed" --label "bug" - -# AI features -github-manager summarize --issue 123 -github-manager detect-duplicates -github-manager suggest-labels --issue 456 -``` - -## Workflow Examples - -### Daily Issue Triage -```bash -# Sync latest issues -github-manager download - -# Check new issues -github-manager list --state open --sort created --limit 20 - -# View overall health -github-manager analytics -``` - -### Sprint Planning -```bash -# List issues for next sprint -github-manager list --milestone "v2.0" --state open - -# Generate sprint report -github-manager export --format markdown --filter milestone=v2.0 - -# Assign issues to team -github-manager batch assign --milestone "v2.0" --assignee shuhao -``` - -### Release Preparation -```bash -# Check release blockers -github-manager list --label "release-blocker" - -# Mark resolved issues -github-manager batch label --add "resolved" --milestone "v1.5" - -# Generate release notes -github-manager export --format csv --output release_report.csv -``` - -## Critical Project Rules - -### NO FALLBACK LOGIC -```python -# ❌ NEVER do this -try: - config = load_config() -except FileNotFoundError: - config = {} # Hides missing config - -# ✅ Always do this -config = load_config() # Let exceptions propagate -``` - -### Dependencies in pyproject.toml -```toml -# ✅ All dependencies declared here -dependencies = [ - "requests>=2.31.0,<3.0.0", - "typer>=0.15.0,<1.0.0", -] -``` - -### Configuration Storage -- **Correct**: `~/.github-manager/` -- **Wrong**: `.sage/issues/` - -### CLI Commands -- **Correct**: `github-manager ` or `gh-issues ` -- **Wrong**: `sage-github ` - -## Key Project Files - -``` -src/sage_github/ - cli_main.py # Entry point - cli.py # Typer CLI commands - config.py # Configuration management - manager.py # Core GitHubManager - issue_data_manager.py # Data persistence - helpers/ - download_issues.py # Download SAGE issues - organize_issues.py # Organization & filtering - ai_analyzer.py # AI-powered analysis - sync_issues.py # Sync with GitHub - create_issue.py # Create new issues - get_boards.py # Project board integration - github_helper.py # GitHub API utilities - ... - -tests/ # Test files -pyproject.toml # Dependencies & config -``` - -## Response Style - -### For Issue Management (Primary) -- **Actionable**: Provide exact commands that work immediately -- **Contextual**: Understand SAGE project workflow -- **Educational**: Explain what each command does -- **Efficient**: Suggest batch operations over manual work - -### For Development (Secondary) -- **Quality-focused**: Enforce code standards -- **Test-driven**: Tests accompany features -- **Clear**: Reference specific files and line numbers -- **Comprehensive**: Complete implementations, not partial solutions - -## Progress Reporting - -### Issue Management Tasks -``` -✓ Downloaded 156 SAGE issues -✓ Found 23 open bugs -✓ Generated analytics report -✓ Exported to sage_issues.csv -``` - -### Development Tasks -``` -✓ Created new helper module: src/sage_github/helpers/export.py -✓ Added tests: tests/test_export.py -✓ Ran quality checks (passed) -✓ All tests passed (95% coverage) -``` - -## When to Ask for Help - -The agent asks for clarification when: -- SAGE repository access requires additional permissions -- Ambiguous filter criteria for batch operations -- Multiple implementation approaches for new features -- Breaking changes needed for existing workflows -- External API changes affect functionality - -## Environment Setup - -```bash -# Required environment variables -export GITHUB_TOKEN=ghp_xxxxxxxxxxxxx # GitHub PAT with repo access -export GITHUB_OWNER=intellistream # SAGE organization -export GITHUB_REPO=SAGE # SAGE repository - -# Optional: Create .env file -cat > .env << EOF -GITHUB_TOKEN=ghp_xxxxxxxxxxxxx -GITHUB_OWNER=intellistream -GITHUB_REPO=SAGE -EOF -``` - -## Development Quick Reference - -```bash -# Setup -pip install -e ".[dev]" -pre-commit install - -# Testing -pytest # Run all tests -pytest --cov=sage_github # With coverage - -# Code Quality -ruff format . # Format code -ruff check --fix . # Lint code -mypy src/sage_github # Type check -pre-commit run --all-files # All checks - -# Development -pytest tests/test_config.py -v # Specific test -pytest -k "test_download" -v # Pattern match -``` - -## Architecture Principles - -1. **SAGE-Focused**: Designed specifically for SAGE project workflows -2. **Single Responsibility**: Each helper module handles one aspect -3. **Configuration First**: All settings through config.py -4. **Data Persistence**: Local JSON storage for offline access -5. **Rich Output**: Colored, formatted terminal output -6. **Type Safety**: All functions have type annotations -7. **AI Integration**: LLM-powered analysis and insights -8. **Batch Efficiency**: Minimize API calls, maximize throughput +## Workflow +1. Implement focused fix/feature. +2. Add/update tests under `tests/`. +3. Validate lint/type/tests before handoff. diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 1cc0ebd..2dee0ad 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -1,392 +1,20 @@ -# GitHub Issues Manager - Copilot Instructions - -## Overview - -**sage-github-manager** is a Python 3.10+ CLI tool specifically designed to **manage GitHub Issues for the SAGE project** (intellistream/SAGE). This tool was extracted from the SAGE project to provide a standalone, reusable issue management system with AI-powered analytics and automation capabilities. - -### Primary Purpose -- **Issue Management**: Download, organize, and track SAGE project issues locally -- **Analytics & Insights**: Generate reports, identify patterns, and prioritize work -- **Batch Operations**: Efficiently manage multiple issues (close, label, assign, etc.) -- **AI-Powered Analysis**: Summarize issues, detect duplicates, suggest labels -- **Workflow Automation**: Sync issues, create roadmaps, export reports - -### Target Users -- Project maintainers managing SAGE repository issues -- Contributors needing local issue tracking and organization -- Developers building custom issue management workflows - -## Project Structure - -``` -sage-github-manager/ -├── src/sage_github/ # Source code -│ ├── cli_main.py # CLI entry point -│ ├── cli.py # Typer CLI commands -│ ├── config.py # Configuration management -│ ├── manager.py # Core GitHubManager class -│ ├── issue_data_manager.py # Issue data persistence -│ └── helpers/ # 13 helper modules -│ ├── download.py # Issue downloading -│ ├── analytics.py # Analytics & reporting -│ ├── batch.py # Batch operations -│ ├── export.py # Data export (JSON/CSV/Markdown) -│ ├── filter.py # Issue filtering -│ ├── formatters.py # Output formatting -│ ├── integrations.py # External integrations -│ ├── network.py # Network utilities -│ ├── priority.py # Priority management -│ ├── search.py # Issue search -│ ├── summary.py # AI summaries -│ ├── sync.py # Data synchronization -│ └── validation.py # Data validation -├── tests/ # Test files -│ ├── test_basic.py -│ ├── test_config.py -│ └── ... -├── examples/ # Usage examples -├── docs/ # Documentation -├── pyproject.toml # Package configuration -├── .pre-commit-config.yaml # Pre-commit hooks -├── pytest.ini # Test configuration -└── ruff.toml # Linting configuration -``` - -## CRITICAL Coding Principles - -### ✅ Key Design Decisions - -1. **Configuration Storage**: Uses `~/.github-manager/` directory (NOT `.sage/issues/`) -2. **CLI Command**: `github-manager ` or `gh-issues ` -3. **Environment Variables**: `GITHUB_TOKEN`, `GITHUB_OWNER`, `GITHUB_REPO` -4. **Data Storage**: JSON files in `~/.github-manager/data///` - -### ❌ NO FALLBACK LOGIC - PROJECT-WIDE RULE - -**NEVER use try-except fallback patterns anywhere in the codebase.** - -#### ❌ BAD Examples (Do NOT do this): - -```python -# Configuration loading -try: - config = load_config() -except FileNotFoundError: - config = {} # ❌ NO - hides missing config - -# Environment variables -token = os.getenv("GITHUB_TOKEN") or "default" # ❌ NO - hides missing token -``` - -#### ✅ GOOD Examples (Do this instead): - -```python -# Let exceptions propagate with clear error messages -config = load_config() # FileNotFoundError if missing - -# Environment variables - explicit check -token = os.environ["GITHUB_TOKEN"] # KeyError if missing - -# Or provide helpful error messages -if "GITHUB_TOKEN" not in os.environ: - raise ValueError( - "GITHUB_TOKEN not set. Please set it with: export GITHUB_TOKEN=your_token" - ) -``` - -**Rationale**: Fail fast, fail loud. Silent fallbacks hide bugs and make debugging harder. - -### ❌ NEVER MANUAL PIP INSTALL - ALWAYS USE pyproject.toml - -**ALL dependencies MUST be declared in pyproject.toml. NEVER use manual `pip install` commands.** - -#### ❌ FORBIDDEN: -```bash -pip install requests -pip install typer==0.15.0 -``` - -#### ✅ CORRECT: -```toml -# In pyproject.toml -dependencies = [ - "requests>=2.31.0,<3.0.0", - "typer>=0.15.0,<1.0.0", -] -``` - -```bash -pip install -e ".[dev]" # Install with dependencies -``` - -## Installation & Setup - -### Development Setup - -```bash -# Clone repository -cd /home/shuhao/sage-github-manager - -# Install with dev dependencies -pip install -e ".[dev]" - -# Install pre-commit hooks -pre-commit install - -# Set up environment variables -export GITHUB_TOKEN=your_github_token -export GITHUB_OWNER=your_org -export GITHUB_REPO=your_repo -``` - -### Environment Variables - -Create `.env` file (gitignored): -```bash -GITHUB_TOKEN=ghp_xxxxxxxxxxxxx -GITHUB_OWNER=intellistream -GITHUB_REPO=SAGE -``` - -## Usage Examples - -### Managing SAGE Project Issues - -```bash -# Initial setup for SAGE project -export GITHUB_TOKEN=ghp_xxxxxxxxxxxxx -export GITHUB_OWNER=intellistream -export GITHUB_REPO=SAGE - -# Download all SAGE issues -github-manager download - -# ✅ List issues with filters (IMPLEMENTED) -github-manager list --state open --label bug -github-manager list --assignee shuhao -github-manager list --milestone "v2.0" --sort created --limit 20 -github-manager list --label "priority:high" --label "bug" # Multiple labels - -# Show analytics for SAGE issues -github-manager analytics -# Shows: issue distribution, activity trends, contributor stats - -# ✅ Export SAGE issues for reporting (IMPLEMENTED) -github-manager export sage_issues.csv --state open -github-manager export issues.json -f json --label bug -github-manager export roadmap.md -f markdown --template roadmap -github-manager export report.md -f markdown --template report --milestone "v2.0" - -# ✅ Batch operations on SAGE issues (IMPLEMENTED) -github-manager batch-close --label "wontfix" --dry-run # 预览模式 -github-manager batch-label --add "priority:high" --label "bug" -github-manager batch-assign -a shuhao --label "p0" -github-manager batch-milestone "v3.0" --state open - -# ❌ AI-powered features (NOT YET IMPLEMENTED) -# github-manager summarize --issue 123 -# github-manager detect-duplicates -# github-manager suggest-labels --issue 456 -``` - -### Typical Workflows - -1. **Daily Issue Triage** - ```bash - github-manager download # Sync latest issues - github-manager list --state open --sort created --limit 20 # ✅ WORKING - github-manager analytics # Check issue health metrics - ``` - -2. **Sprint Planning** - ```bash - github-manager list --milestone "v2.0" --state open # ✅ WORKING - github-manager export sprint.md -f markdown --milestone "v2.0" --template roadmap # ✅ WORKING - # github-manager batch assign --milestone "v2.0" # ❌ NOT YET IMPLEMENTED - ``` - -3. **Release Preparation** - ```bash - github-manager list --label "release-blocker" # ✅ WORKING - github-manager export release_report.csv --state closed --milestone "v1.5" # ✅ WORKING - # github-manager batch label --add "resolved" --milestone "v1.5" # ❌ NOT YET IMPLEMENTED - ``` - -## Testing & Quality - -### Running Tests - -```bash -# All tests -pytest - -# With coverage -pytest --cov=sage_github --cov-report=html - -# Specific test -pytest tests/test_config.py -v -``` - -### Code Quality - -```bash -# Run all checks (format + lint) -pre-commit run --all-files - -# Format code -ruff format . - -# Lint code -ruff check --fix . - -# Type checking -mypy src/sage_github -``` - -### Pre-commit Hooks - -Pre-commit hooks will automatically run on every commit: -- Trailing whitespace removal -- End-of-file fixer -- YAML/JSON/TOML validation -- Ruff formatting and linting -- Type checking (mypy) - -To skip hooks (not recommended): -```bash -git commit --no-verify -m "message" -``` - -## Common Development Tasks - -### Adding a New Feature - -1. Create a new module in `src/sage_github/helpers/` if needed -2. Update `manager.py` or `cli.py` with new functionality -3. Add tests in `tests/` -4. Update documentation in `docs/` -5. Run quality checks: `pre-commit run --all-files` -6. Run tests: `pytest` - -### Adding a New Dependency - -1. Add to `pyproject.toml` under `dependencies` or `dev` optional dependencies -2. Reinstall: `pip install -e ".[dev]"` -3. Update requirements in documentation if needed - -### Debugging - -```python -# Enable debug logging -import logging -logging.basicConfig(level=logging.DEBUG) - -# Use rich console for better output -from rich.console import Console -console = Console() -console.print("[bold red]Debug info here[/]") -``` - -## Key Locations - -``` -src/sage_github/ - cli_main.py # Entry point (github-manager command) - cli.py # Typer CLI commands definition - config.py # Configuration management - manager.py # Core GitHubManager class - issue_data_manager.py # Data persistence layer - helpers/ # Feature modules (13 files) -tests/ # Test files - test_basic.py # Basic functionality tests - test_config.py # Configuration tests -examples/ # Usage examples - basic_usage.py # Simple usage example - advanced_usage.py # Advanced features example -docs/ # Documentation - FAQ.md # Frequently asked questions - QUICK_START.md # Quick start guide -.pre-commit-config.yaml # Pre-commit hooks -pytest.ini # Pytest configuration -ruff.toml # Ruff linting rules -pyproject.toml # Package metadata & dependencies -``` - -## Configuration Files - -### .pre-commit-config.yaml -Pre-commit hooks for code quality (ruff, mypy, file checks) - -### pytest.ini -Test configuration (cache dir, markers, coverage) - -### ruff.toml -Linting and formatting rules (line length 100, Python 3.10+) - -### pyproject.toml -Package metadata, dependencies, tool configurations - -## Common Issues & Solutions - -| Issue | Solution | -|-------|----------| -| `GITHUB_TOKEN not set` | Export environment variable: `export GITHUB_TOKEN=ghp_xxx` | -| `ModuleNotFoundError` | Install package: `pip install -e ".[dev]"` | -| Pre-commit hooks fail | Run `pre-commit run --all-files` and fix issues | -| Tests fail | Check that GITHUB_TOKEN is set, run `pytest -v` for details | -| Import errors | Ensure running from project root, package installed in editable mode | - -## Response Style for Copilot - -- **Issue Management First**: When user asks about issues, focus on using the tool, not developing it -- **SAGE Project Context**: Understand this tool is specifically for managing SAGE (intellistream/SAGE) issues -- **Be concise but comprehensive**: Provide exact commands that can be copy-pasted -- **Reference specific files and line numbers** when relevant for development tasks -- **Emphasize code quality and testing** when adding features -- **Follow the NO FALLBACK LOGIC principle** strictly -- **Always declare dependencies in pyproject.toml** - -## Two Primary Use Cases - -### 1. **Using the Tool** (Primary) -When user wants to: -- Manage SAGE project issues -- Run analytics or generate reports -- Batch operations on issues -- Export or sync issue data - -**Response Style**: Provide ready-to-use CLI commands, explain options, suggest workflows. - -### 2. **Developing the Tool** (Secondary) -When user wants to: -- Add new features to sage-github-manager -- Fix bugs in the codebase -- Improve testing or documentation -- Modify CLI commands - -**Response Style**: Guide code changes, enforce quality standards, provide implementation details. - -## Architecture Principles - -1. **Single Responsibility**: Each helper module handles one aspect -2. **Configuration First**: All settings go through config.py -3. **Data Persistence**: Use IssueDataManager for all data operations -4. **Rich Output**: Use Rich library for terminal output -5. **Type Hints**: All functions should have type annotations -6. **Error Messages**: Clear, actionable error messages -7. **Testing**: Every feature should have tests - -## When Helping Users - -### Issue Management Tasks (Primary Focus) -1. **Understand SAGE project context**: This tool manages SAGE repository issues -2. **Guide on workflows**: Provide commands for daily triage, sprint planning, releases -3. **Suggest best practices**: Analytics before decisions, batch ops for efficiency -4. **Troubleshoot usage**: Check env vars, data sync, filter syntax - -### Development Tasks (Secondary Focus) -1. **Check configuration first**: Many issues are due to missing environment variables -2. **Guide on testing**: Tests should be run from project root -3. **Enforce quality standards**: Ruff formatting (line 100), type hints, no fallback logic -4. **Provide context**: Reference documentation when relevant -5. **Debug systematically**: Check env vars → Check installation → Check file paths +# GitHub Issues Manager Copilot Instructions + +## Scope +- `sage-github-manager` is a standalone CLI for GitHub issue management. +- Main usage targets SAGE issue workflows, but code remains reusable. + +## Critical rules +- No fallback logic: fail fast with explicit error messages. +- Keep dependencies declared in `pyproject.toml`; no ad-hoc manual dependency drift. +- Preserve config/data conventions under `~/.github-manager/...`. +- Keep typed APIs and clear CLI behavior (`github-manager` / `gh-issues`). + +## Implementation focus +- CLI in `src/sage_github/cli.py`; orchestration in `manager.py`; persistence in `issue_data_manager.py`. +- Helpers in `src/sage_github/helpers/` should stay single-purpose. + +## Workflow +1. Implement focused changes and keep command UX stable. +2. Add tests under `tests/` for new behavior. +3. Run lint/type/test checks before handoff. From 580c851431a492c0dadf4d13cf3793235eafaadf Mon Sep 17 00:00:00 2001 From: zhang shuhao Date: Tue, 24 Feb 2026 22:41:28 +0800 Subject: [PATCH 06/15] chore: enforce no-new-venv policy in copilot/agent instructions --- .github/agents/sage-github.agent.md | 1 + .github/copilot-instructions.md | 1 + .vscode/README.md | 17 ++++++----------- .vscode/settings.json | 1 - 4 files changed, 8 insertions(+), 12 deletions(-) diff --git a/.github/agents/sage-github.agent.md b/.github/agents/sage-github.agent.md index 7329e2d..e6b2a22 100644 --- a/.github/agents/sage-github.agent.md +++ b/.github/agents/sage-github.agent.md @@ -14,6 +14,7 @@ tools: ['vscode', 'execute', 'read', 'agent', 'edit', 'search', 'web', 'todo', ' ## Rules - Keep fail-fast behavior; no silent fallbacks. - Keep dependency declarations in `pyproject.toml`. +- Do not create new local virtual environments (`venv`/`.venv`); use the existing configured Python environment. - Preserve UX contracts for `github-manager` / `gh-issues`. - Keep helpers single-purpose and typed. diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 2dee0ad..dd673f6 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -7,6 +7,7 @@ ## Critical rules - No fallback logic: fail fast with explicit error messages. - Keep dependencies declared in `pyproject.toml`; no ad-hoc manual dependency drift. +- Do not create new local virtual environments (`venv`/`.venv`) in this repo; use the existing configured Python environment. - Preserve config/data conventions under `~/.github-manager/...`. - Keep typed APIs and clear CLI behavior (`github-manager` / `gh-issues`). diff --git a/.vscode/README.md b/.vscode/README.md index dcbad06..5d35048 100644 --- a/.vscode/README.md +++ b/.vscode/README.md @@ -8,7 +8,7 @@ **用途**: VS Code 工作区设置 **配置内容**: -- ✅ Python 解释器路径 (`.venv/bin/python`) +- ✅ Python 解释器路径(使用当前已配置环境) - ✅ Pylance 类型检查 (basic 模式) - ✅ Pytest 测试框架集成 - ✅ Ruff 作为默认格式化器 @@ -78,16 +78,12 @@ VS Code 打开项目时会自动提示安装这些扩展。 打开项目后,VS Code 会自动: 1. **提示安装推荐扩展** - 点击 "Install All" 安装所有推荐扩展 -2. **检测 Python 解释器** - 选择 `.venv/bin/python` (如果存在) +2. **检测 Python 解释器** - 选择当前已配置的 Conda/Python 环境 3. **加载 Copilot 指令** - 从 `.github/copilot-instructions.md` 加载项目上下文 -如果没有虚拟环境,运行: +如需安装开发依赖,请在当前已配置环境中执行: ```bash -python -m venv .venv -source .venv/bin/activate # Linux/Mac -# 或 -.venv\Scripts\activate # Windows -pip install -e ".[dev]" +python -m pip install -e ".[dev]" ``` ## ⚙️ 自动化功能 @@ -133,8 +129,7 @@ pip install -e ".[dev]" ### Python 解释器未找到 - 按 `Ctrl+Shift+P` → "Python: Select Interpreter" -- 选择 `.venv/bin/python` -- 如果没有虚拟环境,先创建: `python -m venv .venv` +- 选择当前已配置环境的解释器(例如 Conda 环境) ### Ruff 格式化不工作 - 确保已安装 Ruff 扩展: `charliermarsh.ruff` @@ -142,7 +137,7 @@ pip install -e ".[dev]" - 重新加载 VS Code 窗口 ### 测试发现失败 -- 确保已安装开发依赖: `pip install -e ".[dev]"` +- 确保已安装开发依赖: `python -m pip install -e ".[dev]"` - 检查 `pytest.ini` 配置 - 查看 "Python" 输出面板的错误信息 diff --git a/.vscode/settings.json b/.vscode/settings.json index df95bbf..94ba2d6 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,4 @@ { - "python.defaultInterpreterPath": "${workspaceFolder}/.venv/bin/python", "python.analysis.typeCheckingMode": "basic", "python.analysis.autoImportCompletions": true, "python.analysis.diagnosticMode": "workspace", From ac918656366fd80348cdb803908d73d9a4c6b8ab Mon Sep 17 00:00:00 2001 From: shuhao zhang Date: Sat, 28 Feb 2026 11:33:04 +0800 Subject: [PATCH 07/15] chore: remove AI-generated temporary summary/report docs --- DOCUMENTATION_UPDATE_SUMMARY.md | 326 -------------------------------- docs/EXTRACTION_SUMMARY.md | 275 --------------------------- docs/PROJECT_SUMMARY.md | 290 ---------------------------- 3 files changed, 891 deletions(-) delete mode 100644 DOCUMENTATION_UPDATE_SUMMARY.md delete mode 100644 docs/EXTRACTION_SUMMARY.md delete mode 100644 docs/PROJECT_SUMMARY.md diff --git a/DOCUMENTATION_UPDATE_SUMMARY.md b/DOCUMENTATION_UPDATE_SUMMARY.md deleted file mode 100644 index 4223620..0000000 --- a/DOCUMENTATION_UPDATE_SUMMARY.md +++ /dev/null @@ -1,326 +0,0 @@ -# 文档完善总结报告 - -**日期**: 2024-01-03 -**分支**: main-dev -**提交**: 3f52aad (HEAD) - -## 📊 文档更新统计 - -### 更新的文档 - -| 文件 | 原行数 | 新行数 | 增加 | 状态 | -|------|-------|-------|-----|------| -| **README.md** | ~362 | 522 | +160 | ✅ 更新 | -| **docs/AI_FEATURES.md** | 0 | 508 | +508 | ✨ 新建 | -| **docs/QUICK_START.md** | 207 | 494 | +287 | ✅ 更新 | -| **docs/FAQ.md** | 193 | 877 | +684 | ✅ 更新 | -| **docs/PROJECT_SUMMARY.md** | 238 | 290 | +52 | ✅ 更新 | - -**总计**: 从 ~1,000 行 增加到 **2,691 行** (+1,691 行,**+169%**) - -### Git 提交记录 - -``` -3f52aad docs: Update PROJECT_SUMMARY.md with new features -4cdbe94 docs: Comprehensive documentation update -694ab9d test: Add comprehensive test suites for export and batch commands -8bc5634 feat: Add quickstart script and comprehensive list command tests -``` - -## 📝 主要更新内容 - -### 1. README.md 更新 (+160行) - -**新增章节**: -- ✨ **Quick Start** - 添加 quickstart.sh 一键安装说明 -- ✨ **List Issues** - 完整的列表和过滤命令文档 -- ✨ **Export Issues** - CSV/JSON/Markdown 导出指南 -- ✨ **Batch Operations** - 批量操作命令详解 -- ✨ **AI-Powered Features** - AI 功能使用说明 - -**改进内容**: -- 所有新命令的完整示例 -- 安全功能说明 (dry-run, 确认提示) -- 使用场景和最佳实践 -- 成本分析和优化建议 - -### 2. docs/AI_FEATURES.md 新建 (+508行) - -**全新的 AI 功能综合指南**,包括: - -#### 核心内容 -- **Setup**: OpenAI/Anthropic API 密钥配置详解 -- **4 大功能详解**: - 1. Summarize - 总结长讨论 (使用场景、输出示例) - 2. Detect Duplicates - 自动检测重复 (相似度阈值、最佳实践) - 3. Suggest Labels - 智能标签推荐 (配置、规则) - 4. Comprehensive AI Analysis - 全面分析报告 - -#### 高级内容 -- **成本分析表**: 各操作的 API 调用次数和预估费用 -- **性能优化**: 缓存策略、批处理、成本控制 -- **配置文件**: `ai_config.yaml` 完整配置示例 -- **故障排除**: 常见错误和解决方案 -- **最佳实践**: - - 日常维护例程 - - 团队协作建议 - - 隐私和安全注意事项 - - 质量控制指南 - -#### 实用工具 -- **两个完整脚本示例**: - - `daily_triage.sh` - 每日分类工作流 - - `sprint_report.sh` - Sprint 规划报告生成 - -### 3. docs/QUICK_START.md 重写 (+287行) - -**从简单入门指南扩展为完整教程**: - -#### 安装部分 -- ✅ 两种安装方式 (quickstart.sh 自动化 vs 手动) -- ✅ GitHub Token 获取的详细步骤 (带截图说明) -- ✅ 三种配置方法 (环境变量、文件、.env) - -#### 初始配置 -- ✅ 仓库设置验证流程 -- ✅ 连接测试步骤 -- ✅ 首次下载指导 - -#### 四大核心命令 -1. **List Issues** - 所有过滤器示例 -2. **View Analytics** - 统计输出说明 -3. **Export Issues** - 三种格式的详细用法 -4. **Batch Operations** - 安全操作指南 - -#### 四大工作流程 -1. **Daily Issue Triage** - 每日分类流程 -2. **Sprint Planning** - Sprint 规划流程 -3. **Release Preparation** - 发布准备流程 -4. **Issue Cleanup** - 问题清理流程 - -#### 新增内容 -- AI 功能快速入门 -- 高级操作 (sync, organize, team) -- Python API 使用示例 -- 目录结构说明 -- 10个常见问题的解决方案 -- 完整的安装脚本示例 - -### 4. docs/FAQ.md 扩展 (+684行) - -**从 193 行扩展到 877 行,增加 4.5 倍**: - -#### 新增 Q&A 类别 - -**Installation & Setup** (8 个问题) -- 安装方法对比 -- GitHub Token 详细获取步骤 -- 多仓库使用方法 - -**Basic Usage** (6 个问题) -- list 命令详解 -- export 命令详解 -- batch 命令详解 -- 同步频率建议 - -**AI Features** (3 个问题) -- 功能对比 (需要/不需要 API key) -- 成本分析表 -- 优化建议 - -**Troubleshooting** (5 个问题) -- Token 问题诊断 -- 连接失败排查 -- 命令未找到 -- Rate limit 处理 - -**Advanced Usage** (4 个问题) -- 自定义标签配置 -- CI/CD 集成 (GitHub Actions 示例) -- 自动化 cron 任务 -- 从其他工具迁移 - -**Performance** (2 个问题) -- 下载时间基准测试 -- 性能优化技巧 - -**Integration** (3 个问题) -- Slack/Discord webhook 示例 -- Notion/Obsidian 集成 -- API 集成指南 - -**Security** (2 个问题) -- Token 安全存储 -- Fine-grained tokens 使用 - -**Comparison** (2 个问题) -- 与 GitHub CLI 对比表 -- 使用场景建议 - -**Miscellaneous** (5 个问题) -- download vs sync 区别 -- 自定义字段导出 -- GitHub Enterprise 支持 -- 自定义模板贡献 - -### 5. docs/PROJECT_SUMMARY.md 更新 (+52行) - -**更新内容**: -- ✅ 核心功能列表 (添加新功能标记) -- ✅ 完整的 CLI 命令表 (19 个命令,带状态) -- ✅ 快速示例章节 -- ✅ 最近完成的功能清单 -- ✅ 规划功能更新 - -## 🎯 文档质量提升 - -### 覆盖范围 - -| 功能模块 | 文档状态 | 详细程度 | -|---------|---------|---------| -| **Installation** | ✅ 完整 | 自动化脚本 + 手动步骤 + 故障排除 | -| **List Command** | ✅ 完整 | 所有过滤器 + 排序 + 限制 + 组合示例 | -| **Export Command** | ✅ 完整 | 3种格式 + 3种模板 + 过滤组合 | -| **Batch Operations** | ✅ 完整 | 4个命令 + dry-run + 安全实践 | -| **AI Features** | ✅ 完整 | 独立指南 (508行) + 成本分析 + 最佳实践 | -| **Troubleshooting** | ✅ 完整 | 10+ 常见问题 + 详细解决方案 | -| **Workflows** | ✅ 完整 | 4个完整工作流程 + 脚本示例 | -| **Integration** | ✅ 完整 | CI/CD + Slack/Discord + Notion/Obsidian | - -### 用户体验改进 - -#### 新用户 (First-time users) -- ✅ **One-line installation**: `bash quickstart.sh` -- ✅ **Step-by-step guide**: QUICK_START.md 提供完整流程 -- ✅ **Troubleshooting**: FAQ 覆盖所有常见错误 -- ✅ **Quick wins**: 基础命令在 5 分钟内上手 - -#### 高级用户 (Power users) -- ✅ **AI Features Guide**: 508 行专业文档 -- ✅ **Cost Optimization**: 详细的成本分析和优化策略 -- ✅ **Automation**: CI/CD 模板和 cron 任务示例 -- ✅ **Custom Integration**: Slack/Discord/Notion 集成指南 - -#### 开发者 (Contributors) -- ✅ **Project Structure**: 完整的目录结构说明 -- ✅ **API Documentation**: Python API 使用示例 -- ✅ **Testing Guide**: 测试套件和覆盖率指南 -- ✅ **Development Setup**: 开发环境配置步骤 - -### 文档特点 - -1. **实用性**: - - ✅ 每个功能都有可复制粘贴的命令示例 - - ✅ 真实场景的工作流程脚本 - - ✅ 常见问题的完整解决方案 - -2. **完整性**: - - ✅ 从安装到高级用法的完整覆盖 - - ✅ 所有新功能都有详细文档 - - ✅ 包含性能、成本、安全性指南 - -3. **易读性**: - - ✅ 使用 emoji 标记重要信息 - - ✅ 表格对比不同选项 - - ✅ 代码块带有注释说明 - - ✅ 层次分明的章节结构 - -4. **可维护性**: - - ✅ 集中的命令参考 (README.md) - - ✅ 模块化的专题指南 (AI_FEATURES.md) - - ✅ 版本跟踪 (状态标记) - -## 📈 影响评估 - -### 对用户的影响 - -**新用户**: -- 🚀 安装时间从 "摸索 30 分钟" 降低到 "5 分钟上手" -- 📚 有完整的学习路径 (QUICK_START → README → AI_FEATURES) -- ❓ FAQ 覆盖 90% 的常见问题 - -**现有用户**: -- 🎯 所有新功能都有详细文档 -- 💡 发现之前不知道的高级功能 -- 🔧 学会自动化和集成技巧 - -**企业用户**: -- 📊 成本分析帮助预算规划 -- 🔒 安全最佳实践指南 -- 🤖 CI/CD 集成模板 - -### 对项目的影响 - -**可发现性** (Discoverability): -- ✅ 搜索引擎友好 (详细的功能描述) -- ✅ 完整的命令索引 -- ✅ 丰富的使用示例 - -**采用率** (Adoption): -- ✅ 降低学习曲线 -- ✅ 提供即用模板 -- ✅ 明确的使用场景 - -**支持负担** (Support): -- ✅ FAQ 减少重复问题 -- ✅ 故障排除降低 issue 数量 -- ✅ 清晰的错误消息引用 - -## 🔄 后续建议 - -### 短期 (1-2 周) -1. ✅ 添加截图到 QUICK_START.md (GitHub Token 获取步骤) -2. ✅ 创建视频教程 (5 分钟快速入门) -3. ✅ 添加更多真实案例到 AI_FEATURES.md - -### 中期 (1 个月) -1. ✅ 收集用户反馈,优化文档 -2. ✅ 翻译成中文版本 (考虑 SAGE 用户群) -3. ✅ 创建 Wiki 页面整合所有文档 - -### 长期 (持续) -1. ✅ 保持文档与代码同步 -2. ✅ 每个新功能同时更新文档 -3. ✅ 定期审查和更新示例 - -## 📊 统计摘要 - -``` -总文档行数: 4,583 行 -更新文档数: 4 个 -新建文档数: 1 个 -文档增长率: +169% (README + 新增文档) - -覆盖的命令: 19 个 (100%) -示例代码块: 150+ 个 -工作流程: 4 个完整流程 -故障排除: 15+ 常见问题 -集成指南: 6 种工具 (CI/CD, Slack, Discord, Notion, etc.) -``` - -## ✅ 完成清单 - -- [x] 更新 README.md (主文档) -- [x] 创建 AI_FEATURES.md (专题指南) -- [x] 重写 QUICK_START.md (入门教程) -- [x] 扩展 FAQ.md (常见问题) -- [x] 更新 PROJECT_SUMMARY.md (项目总览) -- [x] 所有文档通过 pre-commit 检查 -- [x] 提交所有更改到 git (3 个 commits) - -## 🎉 总结 - -本次文档完善工作**大幅提升了 sage-github-manager 的可用性和专业性**: - -1. **从 0 到 1**: 新增 AI_FEATURES.md (508 行) 填补了 AI 功能的文档空白 -2. **从少到多**: FAQ.md 从 193 行扩展到 877 行 (+354%) -3. **从简到详**: QUICK_START.md 从简单入门扩展为完整教程 (+138%) -4. **从旧到新**: README.md 添加所有新功能文档 (+44%) - -现在,**无论是新用户、高级用户还是企业用户,都能找到他们需要的信息**,并且能够快速上手和深入使用 sage-github-manager 的所有功能。 - ---- - -**生成时间**: 2024-01-03 -**工具**: sage-github-manager Documentation Team -**版本**: 0.1.0 (main-dev branch) diff --git a/docs/EXTRACTION_SUMMARY.md b/docs/EXTRACTION_SUMMARY.md deleted file mode 100644 index 7cb771b..0000000 --- a/docs/EXTRACTION_SUMMARY.md +++ /dev/null @@ -1,275 +0,0 @@ -# GitHub Issues Manager - 独立项目摘要 - -## 项目概述 - -✅ **已成功将 sage-dev 中的 GitHub 管理功能提取为独立项目!** - -项目名称:**sage-github-manager** -项目位置:`/home/shuhao/sage-github-manager/` - -## 项目来源 - -从 SAGE 项目中提取: -- 原路径:`packages/sage-tools/src/sage/tools/dev/issues/` -- 包含所有 GitHub Issues 管理核心功能 -- 完全独立,无 SAGE 特定依赖 - -## 项目统计 - -- **总文件数**:102 个文件 -- **Python 文件**:26 个 -- **代码行数**:约 9,044 行 -- **Git 提交**:已初始化,1 个初始 commit - -## 目录结构 - -``` -sage-github-manager/ -├── src/sage_github/ # 核心包 (26 Python 文件) -│ ├── __init__.py # 包初始化 -│ ├── cli.py # CLI 命令 (505 行) -│ ├── cli_main.py # CLI 入口 -│ ├── config.py # 配置管理 (独立适配) -│ ├── manager.py # 核心管理器 -│ ├── issue_data_manager.py # 数据管理 -│ ├── tests.py # 测试套件 -│ └── helpers/ # 辅助工具 (13 个文件) -│ ├── download_issues.py -│ ├── sync_issues.py -│ ├── ai_analyzer.py -│ ├── organize_issues.py -│ ├── get_team_members.py -│ └── ... (其他辅助工具) -├── tests/ # 测试 (3 个文件) -├── examples/ # 示例 (2 个文件) -├── docs/ # 文档 (3 个文件) -├── pyproject.toml # 项目配置 -├── setup.py # 安装脚本 -├── README.md # 主文档 (约 300 行) -├── LICENSE # MIT 许可证 -├── CHANGELOG.md # 版本历史 -├── CONTRIBUTING.md # 贡献指南 -├── MANIFEST.in # 包清单 -└── .gitignore # Git 忽略规则 -``` - -## 核心功能 - -### 1. Issues 下载与同步 -- ✅ 从 GitHub API 下载 Issues -- ✅ 支持过滤(open/closed/all) -- ✅ 双向同步 -- ✅ 增量更新 - -### 2. 统计与分析 -- ✅ Issues 统计报告 -- ✅ 标签分布分析 -- ✅ 分配情况统计 -- ✅ 作者贡献分析 - -### 3. AI 功能 -- ✅ AI 智能分析 -- ✅ 重复 Issue 检测 -- ✅ 标签优化建议 -- ✅ 优先级评估 - -### 4. 团队管理 -- ✅ 团队成员追踪 -- ✅ 自动分配规则 -- ✅ 工作负载分析 -- ✅ 团队统计 - -### 5. 项目管理 -- ✅ 自动整理 Issues -- ✅ 基于时间线的组织 -- ✅ 错误分配检测 -- ✅ 批量操作 - -## CLI 命令 - -安装后可用的命令: -- `github-manager` - 主命令 -- `gh-issues` - 快捷别名 - -### 命令列表 - -| 命令 | 功能 | -|------|------| -| `status` | 显示配置和连接状态 | -| `download` | 下载 Issues | -| `stats` | 生成统计报告 | -| `team` | 团队管理和分析 | -| `ai` | AI 智能分析 | -| `sync` | 同步到 GitHub | -| `organize` | 整理 Issues | -| `project` | 项目管理 | -| `create` | 创建新 Issue | -| `config` | 显示配置 | -| `test` | 运行测试 | - -## Python API - -```python -from sage_github import IssuesConfig, IssuesManager - -# 配置 -config = IssuesConfig( - github_owner="your-org", - github_repo="your-repo" -) - -# 管理器 -manager = IssuesManager() -issues = manager.load_issues() -manager.show_statistics() -``` - -## 关键适配 - -### 从 SAGE 中移除的依赖 - -1. **移除 SAGE 特定导入** - - ❌ `from sage.common.config.output_paths import get_sage_paths` - - ✅ 使用独立的 `.github-manager/` 目录 - -2. **简化配置系统** - - ❌ 复杂的 SAGE 路径配置 - - ✅ 简单的项目根目录 + `.github-manager/` - -3. **更新命令名称** - - ❌ `sage-dev issues ` - - ✅ `github-manager ` - -4. **环境变量** - - `GITHUB_OWNER` - 仓库所有者 - - `GITHUB_REPO` - 仓库名称 - - `GITHUB_TOKEN` / `GH_TOKEN` / `GIT_TOKEN` - GitHub token - -## 安装方法 - -### 开发安装 -```bash -cd /home/shuhao/sage-github-manager -pip install -e ".[dev]" -``` - -### 测试安装 -```bash -# 运行测试 -pytest - -# 查看命令 -github-manager --help -``` - -## 文档 - -### 主文档 -- `README.md` - 完整功能文档和使用指南 -- `docs/QUICK_START.md` - 5分钟快速开始 -- `docs/FAQ.md` - 常见问题解答 -- `docs/PROJECT_SUMMARY.md` - 项目技术总结 - -### 开发文档 -- `CONTRIBUTING.md` - 贡献指南 -- `CHANGELOG.md` - 版本历史 -- `LICENSE` - MIT 许可证 - -### 示例代码 -- `examples/basic_usage.py` - 基础用法 -- `examples/advanced_usage.py` - 高级用法 - -## 下一步 - -### 1. 本地测试 -```bash -cd /home/shuhao/sage-github-manager -pip install -e . -github-manager --help -``` - -### 2. 创建 GitHub 仓库 -```bash -# 在 GitHub 上创建仓库:intellistream/sage-github-manager -git remote add origin https://github.com/intellistream/sage-github-manager.git -git branch -M main -git push -u origin main -``` - -### 3. 发布到 PyPI (可选) -```bash -# 构建 -python -m build - -# 上传到 TestPyPI (测试) -twine upload --repository testpypi dist/* - -# 上传到 PyPI (正式) -twine upload dist/* -``` - -### 4. 添加 GitHub Actions -创建 `.github/workflows/` 用于: -- 自动测试 -- 代码质量检查 -- 自动发布到 PyPI - -### 5. 添加功能增强 -- Web UI 界面 -- 实时 Webhooks -- 多仓库管理 -- 自定义插件系统 - -## 项目状态 - -- ✅ 代码提取完成 -- ✅ 独立配置系统 -- ✅ CLI 命令适配 -- ✅ 文档完善 -- ✅ 测试文件创建 -- ✅ 示例代码创建 -- ✅ Git 初始化 -- ⏳ 本地测试待完成 -- ⏳ GitHub 仓库待创建 -- ⏳ PyPI 发布待完成 - -## 技术栈 - -- **语言**: Python 3.10+ -- **CLI 框架**: Typer -- **终端美化**: Rich -- **HTTP 客户端**: Requests -- **模板引擎**: Jinja2 -- **配置**: YAML/JSON -- **测试**: Pytest -- **代码格式**: Black, isort -- **类型检查**: Mypy - -## 许可证 - -MIT License - 允许商业和个人使用 - -## 联系方式 - -- **团队**: IntelliStream Team -- **邮箱**: shuhao_zhang@hust.edu.cn -- **仓库**: (待创建) https://github.com/intellistream/sage-github-manager - -## 总结 - -🎉 **成功将 GitHub Issues 管理功能从 SAGE 项目中完全提取为独立项目!** - -项目包含: -- ✅ 完整的功能代码 (9,044 行) -- ✅ 独立的配置系统 -- ✅ 全面的文档 -- ✅ CLI 和 Python API -- ✅ 测试和示例 -- ✅ Git 版本控制 - -下一步只需要: -1. 本地测试确认功能正常 -2. 创建 GitHub 仓库并推送 -3. 可选:发布到 PyPI - -项目已经完全独立,可以正常使用和分发! diff --git a/docs/PROJECT_SUMMARY.md b/docs/PROJECT_SUMMARY.md deleted file mode 100644 index 9e0d29f..0000000 --- a/docs/PROJECT_SUMMARY.md +++ /dev/null @@ -1,290 +0,0 @@ -# GitHub Issues Manager - Project Summary - -## Overview - -**GitHub Issues Manager** is a comprehensive command-line tool and Python library for managing GitHub Issues at scale. Extracted from the SAGE project, it provides powerful features for downloading, analyzing, organizing, and managing GitHub Issues with AI-powered capabilities. - -## Origin - -This project was extracted from the [SAGE project](https://github.com/intellistream/SAGE)'s `sage-tools` package (`packages/sage-tools/src/sage/tools/dev/issues/`) and made into a standalone, independent tool. - -## Key Features - -### Core Functionality -- **Download & Sync**: Full bidirectional synchronization with GitHub -- **Data Management**: Unified data storage with multiple view formats -- **List & Filter**: Rich filtering by state, labels, assignees, milestones, authors -- **Export**: CSV, JSON, and Markdown exports with templates -- **Statistics**: Comprehensive analytics and reporting -- **Team Management**: Track team members and assignments - -### Advanced Features ✨ NEW -- **Batch Operations**: Efficiently manage multiple issues at once - - Batch close issues with filters - - Batch add/remove labels - - Batch assign to users - - Batch set milestones - - Dry-run mode for safety -- **AI Analysis**: Intelligent issue management - - Summarize long issue discussions - - Detect duplicate issues automatically - - Auto-suggest relevant labels - - Comprehensive pattern analysis -- **Auto-organization**: Timeline-based issue organization -- **Project Management**: Automated issue assignment and tracking -- **Custom Views**: JSON, Markdown, and metadata formats - -## Project Structure - -``` -sage-github-manager/ -├── src/sage_github/ # Main package -│ ├── __init__.py # Package initialization -│ ├── cli.py # CLI commands -│ ├── cli_main.py # CLI entry point -│ ├── config.py # Configuration management -│ ├── manager.py # Core manager class -│ ├── issue_data_manager.py # Data management -│ ├── tests.py # Test suite -│ └── helpers/ # Helper utilities -│ ├── download_issues.py -│ ├── sync_issues.py -│ ├── ai_analyzer.py -│ ├── organize_issues.py -│ ├── get_team_members.py -│ └── ... (other helpers) -├── tests/ # Test suite -│ ├── test_basic.py -│ ├── test_config.py -│ └── __init__.py -├── examples/ # Usage examples -│ ├── basic_usage.py -│ └── advanced_usage.py -├── docs/ # Documentation -│ ├── FAQ.md -│ └── QUICK_START.md -├── pyproject.toml # Project configuration -├── setup.py # Setup script -├── README.md # Main documentation -├── LICENSE # MIT License -├── CHANGELOG.md # Version history -├── CONTRIBUTING.md # Contribution guidelines -├── MANIFEST.in # Package manifest -└── .gitignore # Git ignore rules -``` - -## Dependencies - -### Core Dependencies -- `typer` - CLI framework -- `rich` - Terminal output formatting -- `requests` - HTTP library for GitHub API -- `click` - Command-line interface utilities -- `jinja2` - Template engine -- `pyyaml` - YAML parsing - -### Development Dependencies -- `pytest` - Testing framework -- `pytest-cov` - Coverage reporting -- `black` - Code formatting -- `isort` - Import sorting -- `mypy` - Type checking - -## Key Adaptations from SAGE - -### 1. Removed SAGE Dependencies -- Removed `sage.common.config.output_paths` -- Replaced with `.github-manager/` directory structure -- Self-contained configuration system - -### 2. Simplified Imports -- Changed from `sage.tools.dev.issues` to `sage_github` -- Updated all internal imports -- Made package fully standalone - -### 3. CLI Changes -- Command changed from `sage-dev issues` to `github-manager` -- Added alternative `gh-issues` command -- Updated help text and branding - -### 4. Configuration -- Environment variables: `GITHUB_OWNER`, `GITHUB_REPO` -- Token sources: `GITHUB_TOKEN`, `GH_TOKEN`, `GIT_TOKEN` -- Default paths: `.github-manager/` instead of `.sage/issues/` - -## Installation Methods - -### Development Installation -```bash -git clone https://github.com/intellistream/sage-github-manager.git -cd sage-github-manager -pip install -e ".[dev]" -``` - -### User Installation (PyPI - Coming Soon) -```bash -pip install sage-github-manager -``` - -## CLI Commands - -| Command | Description | Status | -|---------|-------------|--------| -| `github-manager status` | Show configuration and connection status | ✅ | -| `github-manager download` | Download issues from GitHub | ✅ | -| `github-manager list` | List issues with rich filtering | ✅ NEW | -| `github-manager export` | Export to CSV/JSON/Markdown | ✅ NEW | -| `github-manager batch-close` | Batch close issues | ✅ NEW | -| `github-manager batch-label` | Batch add/remove labels | ✅ NEW | -| `github-manager batch-assign` | Batch assign issues | ✅ NEW | -| `github-manager batch-milestone` | Batch set milestone | ✅ NEW | -| `github-manager summarize` | AI-powered issue summarization | ✅ NEW | -| `github-manager detect-duplicates` | Find duplicate issues | ✅ NEW | -| `github-manager suggest-labels` | Auto-suggest labels | ✅ NEW | -| `github-manager analytics` | Generate statistics report | ✅ | -| `github-manager team` | Team management and analysis | ✅ | -| `github-manager ai` | AI-powered analysis | ✅ | -| `github-manager sync` | Sync with GitHub | ✅ | -| `github-manager organize` | Organize issues by status | ✅ | -| `github-manager project` | Project management | ✅ | -| `github-manager config` | Show configuration | ✅ | -| `github-manager test` | Run test suite | ✅ | - -### Quick Examples - -```bash -# List issues with filtering -github-manager list --state open --label bug --sort comments - -# Export to different formats -github-manager export issues.csv -github-manager export ROADMAP.md -f markdown --template roadmap - -# Batch operations -github-manager batch-close --label wontfix --dry-run -github-manager batch-label --add priority:high --label bug - -# AI features -github-manager summarize --issue 123 -github-manager detect-duplicates -github-manager suggest-labels --issue 456 -``` - -## Python API - -```python -from sage_github import IssuesConfig, IssuesManager - -# Configuration -config = IssuesConfig( - github_owner="your-org", - github_repo="your-repo" -) - -# Manager -manager = IssuesManager() -issues = manager.load_issues() -manager.show_statistics() -``` - -## Data Storage - -### Directory Structure -``` -.github-manager/ -├── workspace/ -│ ├── data/ # JSON: issue_{number}.json -│ ├── views/ -│ │ ├── markdown/ # Human-readable .md files -│ │ ├── metadata/ # Structured metadata -│ │ └── summaries/ # AI summaries -│ └── cache/ # Temporary files -├── output/ # Generated reports -│ └── statistics_*.json -└── metadata/ # Configuration - ├── settings.json - ├── team_config.py - ├── boards_metadata.json - └── update_history.json -``` - -## Testing - -```bash -# Run all tests -pytest - -# Run with coverage -pytest --cov=sage_github --cov-report=html - -# Run specific test -pytest tests/test_config.py -v -``` - -## Release Process - -1. Update version in `src/sage_github/__init__.py` -2. Update `CHANGELOG.md` -3. Run tests: `pytest` -4. Run linting: `black src/ tests/ && isort src/ tests/` -5. Build: `python -m build` -6. Tag release: `git tag v0.1.0` -7. Push: `git push origin v0.1.0` -8. Publish to PyPI: `twine upload dist/*` - -## Future Enhancements - -### Recently Completed ✅ -- [x] List command with rich filtering -- [x] Export to CSV/JSON/Markdown with templates -- [x] Batch operations (close, label, assign, milestone) -- [x] AI summarization for issues -- [x] Duplicate detection -- [x] Auto label suggestions -- [x] Comprehensive test suite (98.2% pass rate) -- [x] Quick start installation script - -### Planned Features -- [ ] Web UI dashboard -- [ ] Real-time webhooks support -- [ ] Multi-repository management in single view -- [ ] Custom plugins system -- [ ] Advanced date-range filtering (`--created-after`, `--closed-before`) -- [ ] Interactive TUI (Text User Interface) -- [ ] Integration with GitHub Projects (beta) - -### Under Consideration -- [ ] GitHub Actions integration templates -- [ ] Slack/Discord notification bots -- [ ] Automated issue triage workflows -- [ ] Machine learning for priority prediction -- [ ] GraphQL API support -- [ ] Self-hosted AI models (privacy-focused) -- [ ] Issue templates and automation rules - -## Contributing - -See [CONTRIBUTING.md](../CONTRIBUTING.md) for detailed guidelines. - -## License - -MIT License - see [LICENSE](../LICENSE) for details. - -## Acknowledgments - -- Extracted from [SAGE project](https://github.com/intellistream/SAGE) -- Developed by IntelliStream Team -- Inspired by GitHub CLI and project management best practices - -## Contact - -- **Author**: IntelliStream Team -- **Email**: shuhao_zhang@hust.edu.cn -- **Repository**: https://github.com/intellistream/sage-github-manager -- **Issues**: https://github.com/intellistream/sage-github-manager/issues - -## Version - -Current version: **0.1.0** - -Initial standalone release extracted from SAGE project. From 070b76db52b0f7320898d51cf0eab2c261c3090c Mon Sep 17 00:00:00 2001 From: shuhao zhang Date: Sat, 28 Feb 2026 15:23:41 +0800 Subject: [PATCH 08/15] docs(copilot,agents): add polyrepo coordination rules --- .github/agents/sage-github.agent.md | 6 ++++++ .github/copilot-instructions.md | 7 +++++++ 2 files changed, 13 insertions(+) diff --git a/.github/agents/sage-github.agent.md b/.github/agents/sage-github.agent.md index e6b2a22..27cf4c1 100644 --- a/.github/agents/sage-github.agent.md +++ b/.github/agents/sage-github.agent.md @@ -22,3 +22,9 @@ tools: ['vscode', 'execute', 'read', 'agent', 'edit', 'search', 'web', 'todo', ' 1. Implement focused fix/feature. 2. Add/update tests under `tests/`. 3. Validate lint/type/tests before handoff. + +## Polyrepo coordination rules + +- Treat this repository as the only local source tree; do not assume sibling repositories exist. +- If a task spans multiple repositories, implement only this repo and explicitly list follow-up repo/version-bump actions. +- Do not create `venv`/`.venv`; always use the existing configured Python environment. diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index dd673f6..e1f2162 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -19,3 +19,10 @@ 1. Implement focused changes and keep command UX stable. 2. Add tests under `tests/` for new behavior. 3. Run lint/type/test checks before handoff. + +## Polyrepo coordination (mandatory) + +- This repository is an independent SAGE sub-repository and is developed/released independently. +- Do not assume sibling source directories exist locally in `intellistream/SAGE`. +- For cross-repo rollout, publish this repo/package first, then bump the version pin in `SAGE/packages/sage/pyproject.toml` when applicable. +- Do not add local editable installs of other SAGE sub-packages in setup scripts or docs. From 0ceee440653b7fbe91415915fc99988a7ed8d7ee Mon Sep 17 00:00:00 2001 From: shuhao zhang Date: Sun, 1 Mar 2026 01:30:43 +0800 Subject: [PATCH 09/15] chore: unify hooks (canonical pre-commit, post-commit, pre-push) --- hooks/post-commit | 55 ++++++++++----- hooks/pre-commit | 110 ++++++++++++++++++----------- hooks/pre-push | 175 ++++++++++++++++++++++++++++++++++------------ 3 files changed, 235 insertions(+), 105 deletions(-) diff --git a/hooks/post-commit b/hooks/post-commit index 4f8c904..cdd868c 100755 --- a/hooks/post-commit +++ b/hooks/post-commit @@ -1,19 +1,20 @@ #!/usr/bin/env bash -# Post-commit hook for sageLLM repositories -# Automatically bumps BUILD version (X.Y.Z.BUILD → X.Y.Z.BUILD+1) after each commit. -# Version source of truth: src//_version.py +# Post-commit hook — auto-bump BUILD version digit after each commit # -# This runs AFTER commit, amends it with the bumped version, -# so the pre-push hook never needs to handle version bumping. +# Version format: X.Y.Z → X.Y.Z.1 (first build counter) +# X.Y.Z.N → X.Y.Z.N+1 +# +# Single source of truth: src//_version.py (or root if no src/) +# This hook amends the commit in-place; pre-push hook never bumps version. set -eo pipefail -# Recursion guard: skip if we're already inside an amend +# Recursion guard if [ -f ".git/SAGE_POST_COMMIT_RUNNING" ]; then exit 0 fi -# Allow disabling via environment variable +# Kill-switch if [ "${SAGE_SKIP_VERSION_BUMP:-0}" = "1" ]; then exit 0 fi @@ -38,33 +39,45 @@ bump_version() { return 1 } -# Find _version.py — single source of truth +# Find _version.py — search src/ first (standard layout), then root (non-src repos) find_version_file() { - find src -maxdepth 4 -name '_version.py' \ + local found + found=$(find src -maxdepth 4 -name '_version.py' \ + -not -path '*/.git/*' \ + -not -path '*/dist/*' \ + -not -path '*/.egg-info/*' \ + -not -path '*/build/*' \ + 2>/dev/null | head -1) + if [ -n "$found" ]; then + echo "$found" + return 0 + fi + # Fallback: root-level search (for repos without src/ layout) + find . -maxdepth 3 -name '_version.py' \ -not -path '*/.git/*' \ -not -path '*/dist/*' \ -not -path '*/.egg-info/*' \ -not -path '*/build/*' \ + -not -path '*/node_modules/*' \ + -not -path '*/_cmake_test_compile/*' \ 2>/dev/null | head -1 } VERSION_FILE=$(find_version_file) if [ -z "$VERSION_FILE" ]; then - exit 0 # Not a Python package repo, nothing to do + exit 0 # No _version.py found — not a tracked Python package fi -# Check if _version.py was already changed in this commit +# If developer manually touched _version.py in this commit, skip auto-bump if git diff --name-only HEAD~1 HEAD 2>/dev/null | grep -q '_version.py'; then - exit 0 # Developer manually bumped version, don't override + exit 0 fi -# Read current version from _version.py current_version=$(grep -oP '__version__ = "\K[^"]+' "$VERSION_FILE" 2>/dev/null || true) if [ -z "$current_version" ]; then exit 0 fi -# Calculate new version if ! new_version=$(bump_version "$current_version"); then echo -e "${YELLOW}⚠️ Could not auto-bump version (invalid format: $current_version)${NC}" exit 0 @@ -72,17 +85,25 @@ fi echo -e "${BLUE}📦 Auto-bumping version: $current_version → $new_version${NC}" -# Lock to prevent recursion +# Lock to prevent recursion when we amend touch .git/SAGE_POST_COMMIT_RUNNING # Update _version.py (single source of truth) sed -i "s/__version__ = \"${current_version}\"/__version__ = \"${new_version}\"/" "$VERSION_FILE" git add "$VERSION_FILE" -# Amend commit with version bump (--no-verify to skip re-triggering hooks) +# If this repo has a lock_deps.sh script, sync dependency bounds too (sagellm meta) +if [ -f "scripts/lock_deps.sh" ]; then + echo -e "${BLUE}🔒 Syncing dep bounds + constraints.txt...${NC}" + bash scripts/lock_deps.sh --update-bounds > /dev/null 2>&1 || { + echo -e "${YELLOW}⚠️ lock_deps.sh --update-bounds failed (non-fatal)${NC}" + } + git add constraints.txt src/sagellm/constraints.txt 2>/dev/null || true +fi + +# Amend commit with the version bump (no new prompt) git commit --amend --no-edit --no-verify -# Cleanup rm -f .git/SAGE_POST_COMMIT_RUNNING echo -e "${GREEN}✓ Version bumped to $new_version (commit amended)${NC}" diff --git a/hooks/pre-commit b/hooks/pre-commit index 97d941d..766e957 100755 --- a/hooks/pre-commit +++ b/hooks/pre-commit @@ -1,8 +1,15 @@ -#!/bin/bash -# Pre-commit hook for sageVDB -# Runs basic code quality checks before allowing commits +#!/usr/bin/env bash +# Pre-commit hook — code quality checks before commit +# +# Checks: +# 1. Trailing whitespace +# 2. Merge conflict markers +# 3. Large files (>5 MB) +# 4. Auto-fix + lint staged Python files with ruff (gracefully skipped if absent) +# 5. Hardcoded API keys in staged Python files +# 6. Debug statements (pdb/breakpoint) in staged Python files -set -e +set -eo pipefail # Colors RED='\033[0;31m' @@ -11,55 +18,74 @@ GREEN='\033[0;32m' CYAN='\033[0;36m' NC='\033[0m' -echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" echo -e "${CYAN}🔍 Running pre-commit checks...${NC}" -echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" -# Check for trailing whitespace -echo -e "${YELLOW}Checking for trailing whitespace...${NC}" -if git diff --cached --check --diff-filter=ACM; then - echo -e "${GREEN}✓ No trailing whitespace${NC}" -else - echo -e "${RED}✗ Found trailing whitespace. Please fix before committing.${NC}" +staged_files=$(git diff --cached --name-only --diff-filter=ACM 2>/dev/null || true) +if [ -z "$staged_files" ]; then + echo -e "${GREEN}✓ No staged changes${NC}" + exit 0 +fi + +# --- Trailing whitespace --- +if ! git diff --cached --check --diff-filter=ACM 2>/dev/null; then + echo -e "${RED}✗ Trailing whitespace found. Fix before committing.${NC}" exit 1 fi +echo -e "${GREEN}✓ No trailing whitespace${NC}" -# Check for large files (>5MB) -echo -e "${YELLOW}Checking for large files...${NC}" -max_size=5242880 # 5MB in bytes -large_files=$(git diff --cached --name-only | while read file; do - if [ -f "$file" ]; then - size=$(stat -f%z "$file" 2>/dev/null || stat -c%s "$file" 2>/dev/null || echo 0) - if [ "$size" -gt "$max_size" ]; then - echo "$file ($((size/1024/1024))MB)" - fi - fi -done) +# --- Merge conflict markers --- +if git diff --cached 2>/dev/null | grep -qE '^\+(<<<<<<<|=======|>>>>>>>)'; then + echo -e "${RED}✗ Merge conflict markers found in staged changes${NC}" + exit 1 +fi +echo -e "${GREEN}✓ No conflict markers${NC}" -if [ -n "$large_files" ]; then - echo -e "${RED}✗ Large files detected (>5MB):${NC}" - echo "$large_files" - echo -e "${YELLOW}Consider using Git LFS or excluding these files.${NC}" +# --- Large files (>5 MB) --- +max_size=5242880 +large="" +while IFS= read -r f; do + [ -f "$f" ] || continue + sz=$(stat -c%s "$f" 2>/dev/null || stat -f%z "$f" 2>/dev/null || echo 0) + [ "$sz" -gt "$max_size" ] && large="${large} $f ($((sz / 1024 / 1024))MB)\n" +done <<< "$staged_files" +if [ -n "$large" ]; then + echo -e "${RED}✗ Large files (>5MB) staged:${NC}" + printf "%b" "$large" exit 1 -else - echo -e "${GREEN}✓ No large files${NC}" fi +echo -e "${GREEN}✓ No large files${NC}" -# Check for common debug statements in Python files -echo -e "${YELLOW}Checking for debug statements...${NC}" -debug_found=false -git diff --cached --name-only --diff-filter=ACM | grep '\.py$' | while read file; do - if git diff --cached "$file" | grep -E '^\+.*\b(print|pdb\.set_trace|breakpoint)\(' > /dev/null; then - if [ "$debug_found" = false ]; then - echo -e "${YELLOW}⚠ Warning: Debug statements found in:${NC}" - debug_found=true +# --- Ruff: auto-fix + lint staged Python files --- +STAGED_PY=$(echo "$staged_files" | grep '\.py$' || true) +if [ -n "$STAGED_PY" ]; then + if command -v ruff &>/dev/null; then + echo -e "${YELLOW}📝 Auto-fixing staged Python files...${NC}" + echo "$STAGED_PY" | xargs ruff format 2>/dev/null || true + echo "$STAGED_PY" | xargs ruff check --fix 2>/dev/null || true + # Re-stage auto-fixed files + echo "$STAGED_PY" | xargs git add + echo "🔍 Final lint check on entire repo..." + if ! ruff check .; then + echo -e "${RED}✗ ruff check failed. Run 'ruff check --fix .' to fix.${NC}" + exit 1 fi - echo " - $file" + echo -e "${GREEN}✓ Lint check passed${NC}" + else + echo -e "${YELLOW}⚠️ ruff not found, skipping lint${NC}" fi -done +fi + +# --- Hardcoded API keys --- +if [ -n "$STAGED_PY" ] && echo "$STAGED_PY" | xargs grep -l "api[_-]key\s*=\s*['\"]sk-" 2>/dev/null | grep -q .; then + echo -e "${RED}✗ Hardcoded API keys detected in staged Python files!${NC}" + exit 1 +fi -echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" -echo -e "${GREEN}✓ Pre-commit checks passed!${NC}" -echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" +# --- Debug statements --- +if [ -n "$STAGED_PY" ] && echo "$STAGED_PY" | xargs grep -l "import pdb\|breakpoint()" 2>/dev/null | grep -q .; then + echo -e "${YELLOW}⚠️ Debug statements found (pdb/breakpoint). Use --no-verify to skip.${NC}" + exit 1 +fi +echo -e "${GREEN}✓ All pre-commit checks passed!${NC}" exit 0 diff --git a/hooks/pre-push b/hooks/pre-push index 0b711dd..e5fe9a3 100755 --- a/hooks/pre-push +++ b/hooks/pre-push @@ -1,22 +1,20 @@ #!/bin/bash -# Pre-push hook — block main push + optional PyPI publish +# Pre-push hook — version check + post-push PyPI publish # # Flow: # 1. Block direct push to main branch -# 2. Display current version -# 3. Check if version already exists on PyPI -# 4. Offer to publish after push completes (background job) +# 2. Only proceed for main-dev pushes (other branches: skip cleanly) +# 3. Auto-bump version if unchanged in recent commits +# 4. Exit 0 → git push proceeds immediately +# 5. Background job publishes to PyPI after push finishes (if token present) # -# Version bumping is handled entirely by the post-commit hook. -# This hook NEVER bumps version or creates additional commits. -# → Single push only, no double-push. +# Version bumping: auto-increments last segment if version unchanged. +# This hook may create one version-bump commit before the push. -# Recursion guard (unused now, kept for safety) -if [ "${_SAGELLM_PP_RUNNING:-0}" = "1" ]; then - exit 0 -fi +# Recursion guard +if [ "${_SAGE_PP_RUNNING:-0}" = "1" ]; then exit 0; fi -# Publish mode: "private" for most repos, "public" for sage-pypi-publisher +# Publish mode: "public" → publish openly; "private" → internal only PUBLISH_MODE=public # Colors @@ -28,23 +26,29 @@ BLUE='\033[0;34m' DIM='\033[2m' NC='\033[0m' +WANT_PUBLISH=false REPO_DIR="$(pwd)" REPO_NAME="$(basename "$REPO_DIR")" PUBLISH_LOG="/tmp/${REPO_NAME}-publish-$$.log" +PUSHING_MAIN_DEV=false +PUSH_LOCAL_SHA="" -# --- Block direct push to main --- -block_main_push=false +# --- Block direct push to main; detect main-dev push --- while read -r local_ref local_sha remote_ref remote_sha; do if [ "$local_ref" = "refs/heads/main" ] || [ "$remote_ref" = "refs/heads/main" ]; then - block_main_push=true - break + echo -e "${RED}✗ Direct push to main is forbidden${NC}" + echo -e "${YELLOW} Please push to main-dev first, then merge via PR.${NC}" + exit 1 + fi + if [ "$local_ref" = "refs/heads/main-dev" ] || [ "$remote_ref" = "refs/heads/main-dev" ]; then + PUSHING_MAIN_DEV=true + PUSH_LOCAL_SHA="$local_sha" fi done -if [ "$block_main_push" = true ]; then - echo -e "${RED}✗ Direct push to main is forbidden${NC}" - echo -e "${YELLOW} Please push to a feature branch and merge via PR.${NC}" - exit 1 +# Version check + publish only applies to main-dev pushes +if [ "$PUSHING_MAIN_DEV" != true ]; then + exit 0 fi # Safe read: falls back to default if /dev/tty is unavailable (SSH, IDE, etc.) @@ -58,15 +62,64 @@ safe_read() { fi } -# Find _version.py — single source of truth -find_version_file() { +# Find all _version.py files in repo +find_version_files() { find . -maxdepth 4 -name '_version.py' \ -not -path '*/node_modules/*' \ -not -path '*/.git/*' \ -not -path '*/dist/*' \ -not -path '*/.egg-info/*' \ -not -path '*/build/*' \ - 2>/dev/null | head -1 + 2>/dev/null +} + +# Update version in pyproject.toml (static) and/or _version.py (dynamic) +update_version() { + local old_version="$1" + local new_version="$2" + local updated=false + + if grep -q '^version = "' pyproject.toml 2>/dev/null; then + sed -i "s/version = \"${old_version}\"/version = \"${new_version}\"/" pyproject.toml + git add pyproject.toml + updated=true + fi + + while IFS= read -r VERSION_FILE; do + if [ -f "$VERSION_FILE" ] && grep -q '__version__ = "' "$VERSION_FILE" 2>/dev/null; then + sed -i "s/__version__ = \"${old_version}\"/__version__ = \"${new_version}\"/" "$VERSION_FILE" + git add "$VERSION_FILE" + updated=true + fi + done < <(find_version_files) + + if [ "$updated" = false ]; then + echo -e "${RED}✗ Failed to update version (no version file found)${NC}" + return 1 + fi + + return 0 +} + +# Auto-increment the last version component by 1 (X.Y.Z → X.Y.Z+1, X.Y.Z.N → X.Y.Z.N+1) +bump_patch() { + local v="$1" + local IFS='.' + read -ra parts <<< "$v" + local last_idx=$(( ${#parts[@]} - 1 )) + parts[$last_idx]=$(( parts[$last_idx] + 1 )) + echo "${parts[*]}" +} + +# Check if PyPI token is available (no interaction needed) +has_pypi_token() { + [ -n "${TWINE_PASSWORD:-}" ] || [ -n "${TWINE_TOKEN:-}" ] || [ -n "${UV_PUBLISH_TOKEN:-}" ] && return 0 + if [ -f "$HOME/.pypirc" ] && grep -q "^\[pypi\]" "$HOME/.pypirc" 2>/dev/null; then + if grep -A5 "^\[pypi\]" "$HOME/.pypirc" | grep -qE "^(password|token)\s*=" 2>/dev/null; then + return 0 + fi + fi + return 1 } # Schedule PyPI publish as background job after push completes @@ -84,7 +137,6 @@ schedule_publish() { echo -e "${DIM} Log: tail -f ${PUBLISH_LOG}${NC}" ( - # Wait for parent git push to finish GIT_PID="$PPID" while kill -0 "$GIT_PID" 2>/dev/null; do sleep 1 @@ -93,6 +145,17 @@ schedule_publish() { cd "$REPO_DIR" || exit 1 + # Verify the push actually landed before publishing + remote_sha="$(git ls-remote origin "refs/heads/main-dev" | awk '{print $1}')" + if [ -n "$PUSH_LOCAL_SHA" ] && [ -n "$remote_sha" ] && [ "$remote_sha" != "$PUSH_LOCAL_SHA" ]; then + { + echo "⏭ Skip publish: push SHA mismatch" + echo " expected=${PUSH_LOCAL_SHA}" + echo " remote=${remote_sha}" + } >> "$PUBLISH_LOG" 2>&1 + exit 0 + fi + { echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "📦 Post-push: Building ${package} ${version}..." @@ -109,11 +172,10 @@ schedule_publish() { else echo "" echo "✗ Failed to upload to PyPI (exit code: $?)" - echo " Re-run manually: sage-pypi-publisher build . --upload --no-dry-run --mode ${PUBLISH_MODE}" + echo " Re-run: sage-pypi-publisher build . --upload --no-dry-run --mode ${PUBLISH_MODE}" fi } >> "$PUBLISH_LOG" 2>&1 - # Print summary to terminal (user sees it after push output) if grep -q "Successfully uploaded" "$PUBLISH_LOG" 2>/dev/null; then echo -e "\n${GREEN}✓ PyPI: ${package} ${version} published${NC}" else @@ -131,22 +193,33 @@ fi PACKAGE_NAME=$(grep -oP '^name = "\K[^"]+' pyproject.toml 2>/dev/null || echo "unknown") -# Get version from _version.py (single source of truth) -VERSION_FILE=$(find_version_file) +# Get version: prefer _version.py (dynamic), fallback to pyproject.toml (static) CURRENT_VERSION="" -if [ -n "$VERSION_FILE" ]; then - CURRENT_VERSION=$(grep -oP '__version__ = "\K[^"]+' "$VERSION_FILE" 2>/dev/null || true) -fi +while IFS= read -r VERSION_FILE; do + if [ -f "$VERSION_FILE" ]; then + CURRENT_VERSION=$(grep -oP '__version__ = "\K[^"]+' "$VERSION_FILE" 2>/dev/null || true) + [ -n "$CURRENT_VERSION" ] && break + fi +done < <(find_version_files) if [ -z "$CURRENT_VERSION" ]; then + CURRENT_VERSION=$(grep -oP '^version = "\K[^"]+' pyproject.toml 2>/dev/null || true) +fi + +if [ -z "$CURRENT_VERSION" ] || [ "$CURRENT_VERSION" = "unknown" ]; then exit 0 fi -echo -e "${GREEN}✓ [${REPO_NAME}] Version: ${CURRENT_VERSION}${NC}" +# --- Version / Publish --- +# post-commit already bumps the BUILD digit on every commit, so by the time +# we push the version is always current. The only thing pre-push needs to +# guard against is re-pushing a version that was already published to PyPI +# (e.g. after a failed push/publish on a previous attempt). # Quick PyPI check (5s timeout, non-blocking) +# Exit codes: 0=version exists on PyPI, 1=not found, 2=network/other error PYPI_CHECK_RESULT=1 -if [ "$PACKAGE_NAME" != "unknown" ] && command -v python3 &> /dev/null; then +if [ "$PACKAGE_NAME" != "unknown" ] && command -v python3 &>/dev/null; then python3 - "$PACKAGE_NAME" "$CURRENT_VERSION" <<'PY' && PYPI_CHECK_RESULT=0 || PYPI_CHECK_RESULT=$? import json, sys, urllib.request try: @@ -162,21 +235,31 @@ PY fi if [ "$PYPI_CHECK_RESULT" -eq 0 ]; then - echo -e "${YELLOW}⚠ ${PACKAGE_NAME} ${CURRENT_VERSION} already on PyPI — skipping publish${NC}" -elif [ "$PYPI_CHECK_RESULT" -eq 2 ]; then - echo -e "${DIM}(PyPI check skipped — network/timeout issue)${NC}" - echo -e "${BLUE}📦 Publish ${CURRENT_VERSION} to PyPI after push? [Y/n]:${NC}" - safe_read response "y" - if [[ ! "$response" =~ ^[Nn]$ ]]; then - schedule_publish "$CURRENT_VERSION" "$PACKAGE_NAME" + new_version=$(bump_patch "$CURRENT_VERSION") + old_version="$CURRENT_VERSION" + echo -e "${YELLOW}⚠ ${PACKAGE_NAME} ${CURRENT_VERSION} already on PyPI — auto-bumping: ${old_version} → ${new_version}${NC}" + if update_version "$CURRENT_VERSION" "$new_version"; then + git commit -m "chore: bump version to ${new_version}" + CURRENT_VERSION="$new_version" + echo -e "${GREEN}✓ Bumped to ${new_version}${NC}" + else + exit 1 fi +elif [ "$PYPI_CHECK_RESULT" -eq 2 ]; then + echo -e "${DIM}(PyPI check skipped — network/timeout)${NC}" +fi + +echo -e "${GREEN}✓ [${REPO_NAME}] Version: ${CURRENT_VERSION}${NC}" + +if has_pypi_token; then + echo -e "${BLUE}📦 Auto-publishing ${CURRENT_VERSION} to PyPI (token found)...${NC}" + WANT_PUBLISH=true else - # Version not on PyPI — offer to publish - echo -e "${BLUE}📦 Publish ${CURRENT_VERSION} to PyPI after push? [Y/n]:${NC}" - safe_read response "y" - if [[ ! "$response" =~ ^[Nn]$ ]]; then - schedule_publish "$CURRENT_VERSION" "$PACKAGE_NAME" - fi + echo -e "${DIM} (no PyPI token — skipping publish. Add token to ~/.pypirc or set TWINE_PASSWORD)${NC}" +fi + +if [ "$WANT_PUBLISH" = true ]; then + schedule_publish "$CURRENT_VERSION" "$PACKAGE_NAME" fi # Exit 0 → push proceeds immediately, never blocked by build/upload From c8953a38ba2e0b623f1985a01633cb7bec026a8d Mon Sep 17 00:00:00 2001 From: shuhao zhang Date: Sun, 1 Mar 2026 01:30:48 +0800 Subject: [PATCH 10/15] chore: bump version to 0.1.1 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 00b8993..0b9c906 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "sage-github-manager" -version = "0.1.0" +version = "0.1.1" description = "GitHub Issues Management Tool - Download, analyze, and manage GitHub Issues with AI" readme = "README.md" requires-python = ">=3.10" From ede1aa680e504a688123238ff57525a870ef2ae6 Mon Sep 17 00:00:00 2001 From: shuhao zhang Date: Sun, 1 Mar 2026 08:36:22 +0800 Subject: [PATCH 11/15] chore: enforce never-create-.venv policy in copilot instructions --- .github/copilot-instructions.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index e1f2162..c9b7a43 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -26,3 +26,10 @@ - Do not assume sibling source directories exist locally in `intellistream/SAGE`. - For cross-repo rollout, publish this repo/package first, then bump the version pin in `SAGE/packages/sage/pyproject.toml` when applicable. - Do not add local editable installs of other SAGE sub-packages in setup scripts or docs. + +## 🚫 NEVER_CREATE_DOT_VENV_MANDATORY + +- 永远不要创建 `.venv` 或 `venv`(无任何例外)。 +- NEVER create `.venv`/`venv` in this repository under any circumstance. +- 必须复用当前已配置的非-venv Python 环境(如现有 conda 环境)。 +- If any script/task suggests creating a virtualenv, skip that step and continue with the existing environment. From f9e7de8d0fdf27f9df5f309df16a01e6ea9ac32b Mon Sep 17 00:00:00 2001 From: shuhao zhang Date: Sun, 1 Mar 2026 20:28:49 +0800 Subject: [PATCH 12/15] build: simplify install modes to standard/full/dev; dev includes full via self-ref --- pyproject.toml | 3 + quickstart.sh | 262 ++++++++++++++++++------------------------------- 2 files changed, 98 insertions(+), 167 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 0b9c906..2d61d0e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,7 +43,10 @@ dependencies = [ ] [project.optional-dependencies] +full = [] dev = [ + # dev includes [full] — no need for a separate extras install + "sage-github-manager[full]", "pytest>=7.0.0", "pytest-cov>=4.0.0", "ruff>=0.14.6", diff --git a/quickstart.sh b/quickstart.sh index c18c8c5..679c9b8 100755 --- a/quickstart.sh +++ b/quickstart.sh @@ -1,10 +1,25 @@ -#!/bin/bash -# SageVDB Quickstart Script -# Sets up development environment and git hooks +#!/usr/bin/env bash +# quickstart.sh — sage-github-manager dev environment setup +# +# Usage: +# ./quickstart.sh # dev mode (default): hooks + .[dev] (includes [full]) +# ./quickstart.sh --full # optional backends only: .[full] +# ./quickstart.sh --standard # core deps only: no extras +# ./quickstart.sh --yes # non-interactive (assume yes) +# ./quickstart.sh --doctor # diagnose environment issues +# +# Install matrix: +# (default / --dev) pip install -e .[dev] ← includes [full] via self-ref +# --full pip install -e .[full] +# --standard pip install -e . +# +# Rules: +# - NEVER creates a new venv. Must be called in an existing non-venv environment. +# - Installs hooks via direct copy from hooks/. set -e -# Colors +# ─── Colors ───────────────────────────────────────────────────────────────── RED='\033[0;31m' YELLOW='\033[1;33m' GREEN='\033[0;32m' @@ -13,192 +28,105 @@ BLUE='\033[0;34m' BOLD='\033[1m' NC='\033[0m' -# Print banner -echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" -echo -e "${BOLD}${BLUE} ____ __ ______ ____ ${NC}" -echo -e "${BOLD}${BLUE} / __/__ ___ ___ / / / __ / / __ )${NC}" -echo -e "${BOLD}${BLUE} _\\ \/ _ \/ _ \/ -_) / / / / / / / __ |${NC}" -echo -e "${BOLD}${BLUE}/___/\\___/\\_, /\\__/ /_/ /_/ /_/ /____/ ${NC}" -echo -e "${BOLD}${BLUE} /___/ ${NC}" -echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" -echo -e "${GREEN}${BOLD}SageVDB Quickstart Setup${NC}" -echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" -echo "" +# ─── Arguments ──────────────────────────────────────────────────────────────── +EXTRAS="[dev]" # default — dev includes [full] via pyproject self-reference +DOCTOR=false +YES=false +for arg in "$@"; do + case "$arg" in + --doctor) DOCTOR=true ;; + --standard) EXTRAS="" ;; + --full) EXTRAS="[full]" ;; + --dev) EXTRAS="[dev]" ;; + --yes|-y) YES=true ;; + esac +done -# Detect project root SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$SCRIPT_DIR" -echo -e "${BLUE}📂 Project root: ${NC}$PROJECT_ROOT" -echo "" - -# Step 1: Install git hooks echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" -echo -e "${YELLOW}${BOLD}Step 1: Installing Git Hooks${NC}" +echo -e "${BOLD}${BLUE} sage-github-manager — Quick Start${NC}" echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" - -HOOKS_DIR="$PROJECT_ROOT/.git/hooks" -TEMPLATE_DIR="$PROJECT_ROOT/hooks" - -if [ ! -d "$HOOKS_DIR" ]; then - echo -e "${RED}✗ Git repository not initialized${NC}" - echo -e "${YELLOW}Run: git init${NC}" - exit 1 -fi - -# Install pre-commit hook -if [ -f "$TEMPLATE_DIR/pre-commit" ]; then - cp "$TEMPLATE_DIR/pre-commit" "$HOOKS_DIR/pre-commit" - chmod +x "$HOOKS_DIR/pre-commit" - echo -e "${GREEN}✓ Installed pre-commit hook${NC}" -else - echo -e "${YELLOW}⚠ pre-commit template not found, skipping${NC}" -fi - -# Install pre-push hook -if [ -f "$TEMPLATE_DIR/pre-push" ]; then - cp "$TEMPLATE_DIR/pre-push" "$HOOKS_DIR/pre-push" - chmod +x "$HOOKS_DIR/pre-push" - echo -e "${GREEN}✓ Installed pre-push hook${NC}" -else - echo -e "${YELLOW}⚠ pre-push template not found, skipping${NC}" -fi - -# Install post-commit hook (auto-bump version) -if [ -f "$TEMPLATE_DIR/post-commit" ]; then - cp "$TEMPLATE_DIR/post-commit" "$HOOKS_DIR/post-commit" - chmod +x "$HOOKS_DIR/post-commit" - echo -e "${GREEN}✓ Installed post-commit hook${NC}" -fi - echo "" -# Step 2: Check dependencies -echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" -echo -e "${YELLOW}${BOLD}Step 2: Checking Dependencies${NC}" -echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" - -# Check for CMake -if command -v cmake &> /dev/null; then - CMAKE_VERSION=$(cmake --version | head -n1 | cut -d' ' -f3) - echo -e "${GREEN}✓ CMake found: ${NC}v$CMAKE_VERSION" -else - echo -e "${RED}✗ CMake not found${NC}" - echo -e "${YELLOW} Install: sudo apt install cmake # or brew install cmake${NC}" -fi - -# Check for C++ compiler -if command -v g++ &> /dev/null; then - GCC_VERSION=$(g++ --version | head -n1 | awk '{print $NF}') - echo -e "${GREEN}✓ g++ found: ${NC}v$GCC_VERSION" -elif command -v clang++ &> /dev/null; then - CLANG_VERSION=$(clang++ --version | head -n1 | awk '{print $NF}') - echo -e "${GREEN}✓ clang++ found: ${NC}v$CLANG_VERSION" -else - echo -e "${RED}✗ C++ compiler not found${NC}" - echo -e "${YELLOW} Install: sudo apt install build-essential # or xcode-select --install${NC}" +# ─── Doctor ──────────────────────────────────────────────────────────────────── +if [ "$DOCTOR" = true ]; then + echo -e "${BOLD}${BLUE}Environment Diagnosis${NC}" + echo "" + echo -e "${YELLOW}Python:${NC} $(python3 --version 2>/dev/null || echo 'NOT FOUND')" + echo -e "${YELLOW}Conda env:${NC} ${CONDA_DEFAULT_ENV:-none}" + echo -e "${YELLOW}Venv:${NC} ${VIRTUAL_ENV:-none}" + echo -e "${YELLOW}ruff:${NC} $(ruff --version 2>/dev/null || echo 'NOT FOUND')" + echo -e "${YELLOW}pytest:${NC} $(pytest --version 2>/dev/null || echo 'NOT FOUND')" + echo "" + echo -e "${YELLOW}Git hooks installed:${NC}" + for h in pre-commit pre-push post-commit; do + if [ -f "$PROJECT_ROOT/.git/hooks/$h" ]; then + echo -e " ${GREEN}✓ $h${NC}" + else + echo -e " ${RED}✗ $h${NC}" + fi + done + exit 0 fi -# Check for Python -if command -v python3 &> /dev/null; then - PYTHON_VERSION=$(python3 --version | awk '{print $2}') - echo -e "${GREEN}✓ Python found: ${NC}v$PYTHON_VERSION" -else - echo -e "${RED}✗ Python not found${NC}" +# ─── Step 0: Require an active non-venv environment ──────────────────────────── +if [ -n "$VIRTUAL_ENV" ]; then + echo -e "${RED} ❌ Detected Python venv: $VIRTUAL_ENV${NC}" + echo -e "${YELLOW} → This repository forbids venv/.venv usage.${NC}" + echo -e "${YELLOW} → Please deactivate the venv and use Conda or a system Python.${NC}" + exit 1 fi -# Check for sage-pypi-publisher -if command -v sage-pypi-publisher &> /dev/null; then - echo -e "${GREEN}✓ sage-pypi-publisher found${NC}" +# ─── Step 1/3: Python version check ────────────────────────────────────────────── +echo -e "${YELLOW}${BOLD}Step 1/3: Checking Python environment${NC}" +PYTHON_VERSION=$(python3 -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')" 2>/dev/null || echo "unknown") +echo -e " Python version: ${CYAN}${PYTHON_VERSION}${NC}" +if python3 -c "import sys; exit(0 if sys.version_info >= (3,10) else 1)" 2>/dev/null; then + echo -e " ${GREEN}✓ Python ≥ 3.10${NC}" else - echo -e "${YELLOW}⚠ sage-pypi-publisher not found${NC}" - echo -e "${YELLOW} Optional for PyPI publishing: pip install sage-pypi-publisher${NC}" + echo -e " ${RED}✗ Python 3.10+ required (found ${PYTHON_VERSION})${NC}" + exit 1 fi - echo "" -# Step 3: Build instructions -echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" -echo -e "${YELLOW}${BOLD}Step 3: Build Options${NC}" -echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" - -echo -e "${BLUE}Would you like to build the project now?${NC}" -echo -e " ${GREEN}[y]${NC} Yes, configure and build" -echo -e " ${YELLOW}[n]${NC} No, I'll build manually later" -echo -n "Your choice [y/n]: " -read -r BUILD_NOW - -if [[ "$BUILD_NOW" =~ ^[Yy]$ ]]; then - echo "" - echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" - echo -e "${GREEN}🔨 Building SageVDB...${NC}" - echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" - - if [ -f "$PROJECT_ROOT/build.sh" ]; then - echo -e "${YELLOW}Using build.sh script...${NC}" - cd "$PROJECT_ROOT" - bash build.sh - else - echo -e "${YELLOW}Configuring with CMake...${NC}" - cmake -B "$PROJECT_ROOT/build" -S "$PROJECT_ROOT" \ - -DCMAKE_BUILD_TYPE=Release \ - -DBUILD_TESTS=ON - - echo -e "${YELLOW}Building...${NC}" - cmake --build "$PROJECT_ROOT/build" -j$(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4) - - echo -e "${GREEN}✓ Build complete${NC}" - fi +# ─── Step 2/3: Install Git Hooks ───────────────────────────────────────────────── +echo -e "${YELLOW}${BOLD}Step 2/3: Installing Git hooks${NC}" +if [ -d "$PROJECT_ROOT/hooks" ]; then + installed=0 + for hook_src in "$PROJECT_ROOT/hooks"/*; do + hook_name=$(basename "$hook_src") + hook_dst="$PROJECT_ROOT/.git/hooks/$hook_name" + cp "$hook_src" "$hook_dst" + chmod +x "$hook_dst" + echo -e " ${GREEN}✓ $hook_name${NC}" + installed=$((installed + 1)) + done + echo -e "${GREEN}✓ $installed hook(s) installed${NC}" else - echo -e "${YELLOW}Skipping build. To build later, run:${NC}" - echo -e " ${CYAN}./build.sh${NC}" - echo -e "${YELLOW}Or manually:${NC}" - echo -e " ${CYAN}cmake -B build -S . -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTS=ON${NC}" - echo -e " ${CYAN}cmake --build build -j\$(nproc)${NC}" + echo -e "${YELLOW}⚠ hooks/ directory not found — skipping${NC}" fi - echo "" -# Step 4: Python package setup -echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" -echo -e "${YELLOW}${BOLD}Step 4: Python Package Setup (Optional)${NC}" -echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" - -echo -e "${BLUE}Install Python package in development mode?${NC}" -echo -e " ${GREEN}[y]${NC} Yes, install with pip install -e ." -echo -e " ${YELLOW}[n]${NC} No, skip Python setup" -echo -n "Your choice [y/n]: " -read -r INSTALL_PY - -if [[ "$INSTALL_PY" =~ ^[Yy]$ ]]; then - echo -e "${YELLOW}Installing in editable mode...${NC}" - cd "$PROJECT_ROOT" - pip install -e . - echo -e "${GREEN}✓ Python package installed${NC}" +# ─── Step 3/3: Install package ──────────────────────────────────────────────────── +echo -e "${YELLOW}${BOLD}Step 3/3: Installing package (editable)${NC}" +if [ -n "$EXTRAS" ]; then + echo -e " ${CYAN}pip install -e .$EXTRAS${NC}" + pip install -e ".$EXTRAS" else - echo -e "${YELLOW}Skipping Python package install${NC}" + echo -e " ${CYAN}pip install -e .${NC} (standard — no extras)" + pip install -e . fi - +echo -e "${GREEN}✓ Package installed in editable mode${EXTRAS:+ with extras: $EXTRAS}${NC}" echo "" -# Summary -echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" -echo -e "${GREEN}${BOLD}✓ Setup Complete!${NC}" -echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" +echo -e "${GREEN}${BOLD}✓ Setup complete!${NC}" echo "" -echo -e "${BLUE}${BOLD}Next Steps:${NC}" -echo -e " ${CYAN}1.${NC} Run tests: ${CYAN}cd build && ctest --verbose${NC}" -echo -e " ${CYAN}2.${NC} Try examples: ${CYAN}python examples/python_persistence_example.py${NC}" -echo -e " ${CYAN}3.${NC} Read docs: ${CYAN}cat README.md${NC}" +echo -e "${BLUE}${BOLD}Next steps:${NC}" +echo -e " ${CYAN}pytest tests/${NC} — run tests" +echo -e " ${CYAN}ruff check src/${NC} — lint" +echo -e " ${CYAN}./quickstart.sh --full${NC} — reinstall with optional backends" +echo -e " ${CYAN}./quickstart.sh --standard${NC} — install core deps only (no extras)" +echo -e " ${CYAN}./quickstart.sh --doctor${NC} — diagnose environment" echo "" -echo -e "${YELLOW}${BOLD}Git Hooks Installed:${NC}" -echo -e " ${GREEN}•${NC} pre-commit: Checks code quality before commits" -echo -e " ${GREEN}•${NC} pre-push: Manages version updates and PyPI publishing" -echo "" -echo -e "${BLUE}${BOLD}Useful Commands:${NC}" -echo -e " ${CYAN}./build.sh${NC} - Quick rebuild" -echo -e " ${CYAN}sage-pypi-publisher build${NC} - Build distribution packages" -echo -e " ${CYAN}sage-pypi-publisher publish${NC} - Build and publish to PyPI" -echo "" -echo -e "${GREEN}Happy coding! 🚀${NC}" -echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" From 05bd04cb4c04322cc566faed74da0f9e5b5e8ab0 Mon Sep 17 00:00:00 2001 From: shuhao zhang Date: Sun, 1 Mar 2026 20:56:39 +0800 Subject: [PATCH 13/15] chore: update pre-push hook; update development docs --- docs/DEVELOPMENT.md | 4 ++-- docs/IMPLEMENTATION_PROGRESS.md | 2 +- docs/MISSING_FEATURES.md | 6 +++--- hooks/pre-push | 16 +++++++++++++--- 4 files changed, 19 insertions(+), 9 deletions(-) diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index 6840695..771dbb4 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -388,8 +388,8 @@ pre-commit run --all-files # Update hooks pre-commit autoupdate -# Skip hooks (not recommended) -git commit --no-verify +# Run hooks only on changed files +pre-commit run ``` ### Tests Fail diff --git a/docs/IMPLEMENTATION_PROGRESS.md b/docs/IMPLEMENTATION_PROGRESS.md index f3aa8d1..b89fbda 100644 --- a/docs/IMPLEMENTATION_PROGRESS.md +++ b/docs/IMPLEMENTATION_PROGRESS.md @@ -169,7 +169,7 @@ github-manager suggest-labels --issue 456 |--------|-------| | Total Python Files | 20 | | Lines of Code | ~5,000 | -| Test Coverage | TBD (tests not yet written) | +| Test Coverage | Test suite available (run `pytest` for current metrics) | | Helper Modules | 13 | | CLI Commands | 8 (download, list, export, team, analytics, stats, show, web) | diff --git a/docs/MISSING_FEATURES.md b/docs/MISSING_FEATURES.md index 602b7b2..cc4ac68 100644 --- a/docs/MISSING_FEATURES.md +++ b/docs/MISSING_FEATURES.md @@ -36,7 +36,7 @@ github-manager list --label "priority:high" --assignee shuhao - `src/sage_github/helpers/filter_issues.py` - Create filter helper - `src/sage_github/manager.py` - Add `list_issues()` method -**Status**: � Completed +**Status**: 🟢 Completed **Implementation Date**: 2026-01-03 @@ -120,7 +120,7 @@ github-manager batch-milestone "v3.0" --label "feature" - `src/sage_github/helpers/batch_operations.py` - Create batch operations helper - `src/sage_github/manager.py` - Add batch methods -**Status**: � Completed +**Status**: 🟢 Completed **Implementation Date**: 2026-01-03 @@ -168,7 +168,7 @@ github-manager ai-analyze # Keep general analysis - `src/sage_github/cli.py` - Add dedicated commands - `src/sage_github/helpers/ai_analyzer.py` - Refactor for specific operations -**Status**: � Completed +**Status**: 🟢 Completed **Implementation Date**: 2026-01-03 diff --git a/hooks/pre-push b/hooks/pre-push index e5fe9a3..c6790a4 100755 --- a/hooks/pre-push +++ b/hooks/pre-push @@ -113,12 +113,22 @@ bump_patch() { # Check if PyPI token is available (no interaction needed) has_pypi_token() { - [ -n "${TWINE_PASSWORD:-}" ] || [ -n "${TWINE_TOKEN:-}" ] || [ -n "${UV_PUBLISH_TOKEN:-}" ] && return 0 - if [ -f "$HOME/.pypirc" ] && grep -q "^\[pypi\]" "$HOME/.pypirc" 2>/dev/null; then - if grep -A5 "^\[pypi\]" "$HOME/.pypirc" | grep -qE "^(password|token)\s*=" 2>/dev/null; then + if [ -n "${TWINE_PASSWORD:-}" ] || [ -n "${TWINE_TOKEN:-}" ] || [ -n "${UV_PUBLISH_TOKEN:-}" ]; then + return 0 + fi + + if [ -f "$HOME/.pypirc" ]; then + if awk ''' + BEGIN { in_pypi = 0; found = 0 } + /^[[:space:]]*\[pypi\][[:space:]]*$/ { in_pypi = 1; next } + /^[[:space:]]*\[[^]]+\][[:space:]]*$/ { in_pypi = 0 } + in_pypi && /^[[:space:]]*(password|token)[[:space:]]*=[[:space:]]*.+$/ { found = 1; exit } + END { exit(found ? 0 : 1) } + ''' "$HOME/.pypirc" 2>/dev/null; then return 0 fi fi + return 1 } From d647cb97aa56f2c3fcb51a6d3dd805bedfbfced9 Mon Sep 17 00:00:00 2001 From: shuhao zhang Date: Mon, 2 Mar 2026 02:00:39 +0800 Subject: [PATCH 14/15] fix(hooks): robust publisher command fallback --- hooks/pre-push | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/hooks/pre-push b/hooks/pre-push index c6790a4..2870e64 100755 --- a/hooks/pre-push +++ b/hooks/pre-push @@ -132,14 +132,36 @@ has_pypi_token() { return 1 } +# Resolve publisher CLI command (binary first, then python -m fallback) +resolve_publisher_cmd() { + if command -v sage-pypi-publisher &> /dev/null; then + PUBLISH_CMD=(sage-pypi-publisher) + return 0 + fi + + if command -v python3 &> /dev/null; then + if python3 - <<'PY_HOOK' >/dev/null 2>&1 +import importlib.util +import sys +sys.exit(0 if importlib.util.find_spec("pypi_publisher") else 1) +PY_HOOK + then + PUBLISH_CMD=(python3 -m pypi_publisher.cli) + return 0 + fi + fi + + return 1 +} + # Schedule PyPI publish as background job after push completes schedule_publish() { local version="$1" local package="$2" - if ! command -v sage-pypi-publisher &> /dev/null; then + if ! resolve_publisher_cmd; then echo -e "${YELLOW}⚠ sage-pypi-publisher not found, skipping auto-publish${NC}" - echo -e "${DIM} Install: pip install isage-pypi-publisher${NC}" + echo -e "${DIM} Install: python -m pip install isage-pypi-publisher${NC}" return fi @@ -173,7 +195,7 @@ schedule_publish() { rm -rf dist/ build/ *.egg-info 2>/dev/null || true - if sage-pypi-publisher build . --upload --no-dry-run --mode ${PUBLISH_MODE}; then + if "${PUBLISH_CMD[@]}" build . --upload --no-dry-run --mode "${PUBLISH_MODE}"; then echo "" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "✓ Successfully uploaded ${package} ${version} to PyPI" From faab1ab0381c9522fff67166bb3b3baab8082882 Mon Sep 17 00:00:00 2001 From: shuhao zhang Date: Tue, 3 Mar 2026 00:47:12 +0800 Subject: [PATCH 15/15] fix: resolve CI failures - mypy errors, datetime.UTC compat, settings.json creation - config.py: add settings.json creation in _ensure_default_metadata_files() - organize_issues.py: replace datetime.UTC (Python 3.11+) with timezone.utc (3.10 compat) - manager.py: add dict[str, Any] annotations to issue_data and stats dicts - batch_operations.py: accept token as str | None, fix no-any-return in _get_milestones - issue_data_manager.py: add dict[str, Any] annotation, fix no-any-return - sync_issues.py: add Any import, fix no-redef fallback import, annotate payload - github_helper.py, get_paths.py, create_issue.py, ai_analyzer.py, get_boards.py, get_team_members.py: add type: ignore[no-redef] on fallback imports - ai_helper.py: str() casts for Any returns, fix union-attr, annotate results dict - tests.py: wrap returns in bool() to satisfy no-any-return Fixes: mypy 48 errors -> 0, test_metadata_files_creation failure --- src/sage_github/config.py | 15 +++++++++++++-- src/sage_github/helpers/ai_analyzer.py | 2 +- src/sage_github/helpers/ai_helper.py | 8 ++++---- src/sage_github/helpers/batch_operations.py | 11 ++++++----- src/sage_github/helpers/create_issue.py | 4 ++-- src/sage_github/helpers/get_boards.py | 2 +- src/sage_github/helpers/get_paths.py | 2 +- src/sage_github/helpers/get_team_members.py | 2 +- src/sage_github/helpers/github_helper.py | 2 +- src/sage_github/helpers/organize_issues.py | 4 ++-- src/sage_github/helpers/sync_issues.py | 7 ++++--- src/sage_github/issue_data_manager.py | 4 ++-- src/sage_github/manager.py | 4 ++-- src/sage_github/tests.py | 6 +++--- 14 files changed, 43 insertions(+), 30 deletions(-) diff --git a/src/sage_github/config.py b/src/sage_github/config.py index 729dccc..65b7c31 100644 --- a/src/sage_github/config.py +++ b/src/sage_github/config.py @@ -149,7 +149,7 @@ def _load_config_json(self) -> dict: with open(config_path, encoding="utf-8") as f: config = json.load(f) # print(f"✅ 已加载配置: {config_path}") - return config + return config # type: ignore[no-any-return] except Exception as e: print(f"⚠️ 加载配置文件失败 {config_path}: {e}") @@ -235,6 +235,17 @@ def _ensure_default_metadata_files(self): with open(assignments_file, "w", encoding="utf-8") as f: json.dump(default_assignments, f, indent=2, ensure_ascii=False) + # settings.json + settings_file = self.metadata_path / "settings.json" + if not settings_file.exists(): + default_settings = { + "sync_update_history": True, + "auto_backup": True, + "verbose_output": False, + } + with open(settings_file, "w", encoding="utf-8") as f: + json.dump(default_settings, f, indent=2, ensure_ascii=False) + def _load_github_token(self) -> str | None: """加载GitHub Token""" @@ -303,7 +314,7 @@ def get_repo_info(self) -> dict: headers=headers, ) response.raise_for_status() - return response.json() + return response.json() # type: ignore[no-any-return] # 兼容性别名 diff --git a/src/sage_github/helpers/ai_analyzer.py b/src/sage_github/helpers/ai_analyzer.py index 6865807..910a1e8 100644 --- a/src/sage_github/helpers/ai_analyzer.py +++ b/src/sage_github/helpers/ai_analyzer.py @@ -20,7 +20,7 @@ except ImportError: # 如果相对导入失败,使用绝对导入 sys.path.insert(0, str(SCRIPT_DIR.parent)) - from config import IssuesConfig + from config import IssuesConfig # type: ignore[no-redef] config = IssuesConfig() diff --git a/src/sage_github/helpers/ai_helper.py b/src/sage_github/helpers/ai_helper.py index ea55ffc..3f4354a 100644 --- a/src/sage_github/helpers/ai_helper.py +++ b/src/sage_github/helpers/ai_helper.py @@ -89,7 +89,7 @@ def summarize_issue(self, issue: dict[str, Any], max_length: int = 200) -> str | body = issue.get("body", "") if not body: - return title + return str(title) prompt = f"""请用中文简洁总结以下 GitHub Issue(不超过{max_length}字): @@ -135,7 +135,7 @@ def _summarize_with_openai(self, prompt: str, max_length: int) -> str | None: temperature=0.3, ) - return response.choices[0].message.content.strip() + return str(response.choices[0].message.content or "").strip() except Exception as e: console.print(f"❌ [red]OpenAI API 调用失败: {e}[/red]") return None @@ -153,7 +153,7 @@ def _summarize_with_claude(self, prompt: str, max_length: int) -> str | None: messages=[{"role": "user", "content": prompt}], ) - return message.content[0].text.strip() + return str(message.content[0].text).strip() # type: ignore[union-attr] except Exception as e: console.print(f"❌ [red]Claude API 调用失败: {e}[/red]") return None @@ -277,7 +277,7 @@ def analyze_issues_batch( Returns: 分析结果字典 """ - results = {"total": len(issues), "processed": 0, "failed": 0, "data": []} + results: dict[str, Any] = {"total": len(issues), "processed": 0, "failed": 0, "data": []} for issue in issues: try: diff --git a/src/sage_github/helpers/batch_operations.py b/src/sage_github/helpers/batch_operations.py index f63280c..9c77a56 100644 --- a/src/sage_github/helpers/batch_operations.py +++ b/src/sage_github/helpers/batch_operations.py @@ -20,7 +20,7 @@ class BatchOperations: 所有操作支持 dry-run 模式和确认提示。 """ - def __init__(self, owner: str, repo: str, token: str): + def __init__(self, owner: str, repo: str, token: str | None): """初始化批量操作管理器 Args: @@ -31,10 +31,11 @@ def __init__(self, owner: str, repo: str, token: str): self.owner = owner self.repo = repo self.token = token - self.headers = { - "Authorization": f"token {token}", + self.headers: dict[str, str] = { "Accept": "application/vnd.github.v3+json", } + if token: + self.headers["Authorization"] = f"token {token}" self.base_url = f"https://api.github.com/repos/{owner}/{repo}" def _update_issue(self, issue_number: int, **kwargs) -> bool: @@ -60,7 +61,7 @@ def _get_milestones(self) -> list[dict[str, Any]]: url = f"{self.base_url}/milestones" response = requests.get(url, headers=self.headers, params={"state": "all"}, timeout=30) if response.status_code == 200: - return response.json() + return response.json() # type: ignore[no-any-return] return [] def close_issues( @@ -452,7 +453,7 @@ def _get_milestone_id(self, milestone_title: str) -> int | None: milestones = self._get_milestones() for milestone in milestones: if milestone["title"] == milestone_title: - return milestone["number"] + return milestone["number"] # type: ignore[no-any-return] return None def _show_preview_table(self, issues: list[dict[str, Any]], operation: str) -> None: diff --git a/src/sage_github/helpers/create_issue.py b/src/sage_github/helpers/create_issue.py index b377129..b8b96c9 100644 --- a/src/sage_github/helpers/create_issue.py +++ b/src/sage_github/helpers/create_issue.py @@ -18,7 +18,7 @@ except ImportError: # 如果相对导入失败,使用绝对导入 sys.path.insert(0, str(Path(__file__).parent.parent)) - from config import IssuesConfig + from config import IssuesConfig # type: ignore[no-redef] config = IssuesConfig() @@ -189,7 +189,7 @@ def load_from_file(file_path: str) -> dict | None: """从文件加载issue数据""" try: with open(file_path, encoding="utf-8") as f: - return json.load(f) + return json.load(f) # type: ignore[no-any-return] except Exception as e: print(f"❌ 读取文件失败: {e}") return None diff --git a/src/sage_github/helpers/get_boards.py b/src/sage_github/helpers/get_boards.py index 92eb518..a0e2ba4 100755 --- a/src/sage_github/helpers/get_boards.py +++ b/src/sage_github/helpers/get_boards.py @@ -25,7 +25,7 @@ except ImportError: # 如果相对导入失败,使用绝对导入 sys.path.insert(0, str(Path(__file__).parent.parent)) - from config import IssuesConfig + from config import IssuesConfig # type: ignore[no-redef] class BoardsMetadataGenerator: diff --git a/src/sage_github/helpers/get_paths.py b/src/sage_github/helpers/get_paths.py index 0e51c52..cc76b1a 100644 --- a/src/sage_github/helpers/get_paths.py +++ b/src/sage_github/helpers/get_paths.py @@ -14,7 +14,7 @@ except ImportError: # 如果相对导入失败,使用绝对导入 sys.path.insert(0, str(Path(__file__).parent.parent)) - from config import IssuesConfig + from config import IssuesConfig # type: ignore[no-redef] def main(): config = IssuesConfig() diff --git a/src/sage_github/helpers/get_team_members.py b/src/sage_github/helpers/get_team_members.py index 94c5dd2..aaf03b2 100644 --- a/src/sage_github/helpers/get_team_members.py +++ b/src/sage_github/helpers/get_team_members.py @@ -25,7 +25,7 @@ except ImportError: # Fallback: add parent directory to path sys.path.insert(0, str(Path(__file__).parent.parent)) - from config import IssuesConfig as Config + from config import IssuesConfig as Config # type: ignore[no-redef] def find_token(): diff --git a/src/sage_github/helpers/github_helper.py b/src/sage_github/helpers/github_helper.py index 3fbb93a..63f16ea 100644 --- a/src/sage_github/helpers/github_helper.py +++ b/src/sage_github/helpers/github_helper.py @@ -18,7 +18,7 @@ except ImportError: # 如果相对导入失败,使用绝对导入 sys.path.insert(0, str(Path(__file__).parent.parent)) - from config import IssuesConfig + from config import IssuesConfig # type: ignore[no-redef] class GitHubProjectManager: diff --git a/src/sage_github/helpers/organize_issues.py b/src/sage_github/helpers/organize_issues.py index c3ff9e1..1b9588d 100644 --- a/src/sage_github/helpers/organize_issues.py +++ b/src/sage_github/helpers/organize_issues.py @@ -16,7 +16,7 @@ """ import argparse -from datetime import UTC, datetime, timedelta +from datetime import datetime, timedelta, timezone import json from pathlib import Path import sys @@ -83,7 +83,7 @@ def get_closed_issues(self): def categorize_issues(self, issues): """根据关闭时间分类issues""" - now = datetime.now(UTC) + now = datetime.now(timezone.utc) one_week_ago = now - timedelta(days=7) one_month_ago = now - timedelta(days=30) diff --git a/src/sage_github/helpers/sync_issues.py b/src/sage_github/helpers/sync_issues.py index 336cbdb..ee072e8 100755 --- a/src/sage_github/helpers/sync_issues.py +++ b/src/sage_github/helpers/sync_issues.py @@ -26,6 +26,7 @@ import re import sys import time +from typing import Any from github_helper import GitHubProjectManager import requests @@ -42,8 +43,8 @@ except ImportError: # 如果相对导入失败,使用绝对导入 sys.path.insert(0, str(SCRIPT_DIR.parent)) - from config import IssuesConfig - from issue_data_manager import IssueDataManager + from config import IssuesConfig # type: ignore[no-redef] + from issue_data_manager import IssueDataManager # type: ignore[no-redef] # Import github_helper directly sys.path.insert(0, str(SCRIPT_DIR)) @@ -72,7 +73,7 @@ def graphql_request( variables: dict | None = None, retries: int = 2, ): - payload = {"query": query} + payload: dict[str, Any] = {"query": query} if variables is not None: payload["variables"] = variables attempt = 0 diff --git a/src/sage_github/issue_data_manager.py b/src/sage_github/issue_data_manager.py index 90cbc9c..6bf65cb 100644 --- a/src/sage_github/issue_data_manager.py +++ b/src/sage_github/issue_data_manager.py @@ -222,7 +222,7 @@ def get_issue(self, issue_number: int) -> dict[str, Any] | None: return None with open(data_file, encoding="utf-8") as f: - return json.load(f) + return json.load(f) # type: ignore[no-any-return] except Exception as e: print(f"❌ 读取Issue #{issue_number} 数据失败: {e}") return None @@ -582,7 +582,7 @@ def _parse_old_markdown_file(self, md_file: Path) -> dict | None: content = f.read() lines = content.split("\n") - issue_data = {} + issue_data: dict[str, Any] = {} # 从文件名提取信息 filename = md_file.name diff --git a/src/sage_github/manager.py b/src/sage_github/manager.py index cf6949b..e3678e7 100644 --- a/src/sage_github/manager.py +++ b/src/sage_github/manager.py @@ -166,7 +166,7 @@ def _parse_markdown_issue(self, content: str, filename: str) -> dict[str, Any]: lines = content.split("\n") # Initialize issue data - issue_data = { + issue_data: dict[str, Any] = { "title": "", "body": content, "state": "open", # default @@ -250,7 +250,7 @@ def _parse_markdown_issue(self, content: str, filename: str) -> dict[str, Any]: def _generate_statistics(self, issues: list[dict[str, Any]]) -> dict[str, Any]: """Generate statistics from issues data.""" - stats = { + stats: dict[str, Any] = { "total": len(issues), "open": 0, "closed": 0, diff --git a/src/sage_github/tests.py b/src/sage_github/tests.py index de01ec2..a3605ac 100644 --- a/src/sage_github/tests.py +++ b/src/sage_github/tests.py @@ -89,7 +89,7 @@ def test_github_connection(self) -> bool: return True # 使用manager的内置连接测试 - return self.manager.test_github_connection() + return bool(self.manager.test_github_connection()) except Exception as e: # 在CI环境中,网络相关的失败是可以容忍的 if os.environ.get("CI") == "true": @@ -117,7 +117,7 @@ def test_stats_generation(self) -> bool: try: # 使用manager的统计功能 success = self.manager.show_statistics() - return success + return bool(success) except Exception as e: console.print(f"❌ 统计生成测试失败: {e}") return False @@ -185,7 +185,7 @@ def run_test(self, test_name: str, test_func) -> bool: console.print(f" {status}") self.test_results.append((test_name, result, "")) - return result + return bool(result) except Exception as e: console.print(f" ❌ ERROR: {e}") self.test_results.append((test_name, False, str(e)))