From fc206ea4f3e34463395ed692b6b3804875e785ce Mon Sep 17 00:00:00 2001 From: Wilson Freitas Date: Sun, 22 Mar 2026 09:43:18 -0300 Subject: [PATCH] Add skills for managing PyPI project update dates and syncing README.md --- .claude/skills/review-pr/SKILL.md | 108 ++++++++++++ .claude/skills/sync-site/SKILL.md | 79 +++++++++ .claude/skills/update-pypi-dates/README.md | 132 +++++++++++++++ .claude/skills/update-pypi-dates/SKILL.md | 66 ++++++++ .../scripts/check_pypi_dates.py | 154 ++++++++++++++++++ .../scripts/update_pypi_dates.sh | 27 +++ 6 files changed, 566 insertions(+) create mode 100644 .claude/skills/review-pr/SKILL.md create mode 100644 .claude/skills/sync-site/SKILL.md create mode 100644 .claude/skills/update-pypi-dates/README.md create mode 100644 .claude/skills/update-pypi-dates/SKILL.md create mode 100755 .claude/skills/update-pypi-dates/scripts/check_pypi_dates.py create mode 100755 .claude/skills/update-pypi-dates/scripts/update_pypi_dates.sh diff --git a/.claude/skills/review-pr/SKILL.md b/.claude/skills/review-pr/SKILL.md new file mode 100644 index 0000000..81ec7c8 --- /dev/null +++ b/.claude/skills/review-pr/SKILL.md @@ -0,0 +1,108 @@ +--- +name: review-pr +description: Review and validate pull requests that add new library entries to the awesome-quant README.md. Use this skill whenever the user asks to review PRs, check contributions, validate submissions, triage pull requests, or mentions anything about incoming entries or additions to the curated list. Also triggers for "review pr", "check prs", "merge contributions", or any PR-related workflow in this repo. +--- + +# Review PR Skill + +Review one open pull request at a time for the awesome-quant curated list. Always start with the oldest unreviewed open PR. + +## Workflow + +### Step 1: Find the oldest open PR + +Use the GitHub MCP tools to list open pull requests sorted by creation date (ascending). Pick the oldest one. Show the user which PR you're reviewing (number, title, author, creation date). + +### Step 2: Check for merge conflicts + +Use the GitHub MCP tools to read the PR details. If the PR has merge conflicts, report this to the user and stop — the contributor needs to resolve conflicts before review can proceed. Leave a polite comment asking them to rebase. + +### Step 3: Fetch the diff and extract added lines + +Read the PR diff. Focus only on changes to `README.md`. If the PR modifies files other than `README.md` (like `parse.py`, `site/`, etc.), flag this as unusual — most contributions should only touch `README.md`. + +### Step 4: Validate each added entry + +For every new line added to `README.md`, check the following: + +#### 4a. Entry format + +Each entry MUST match this exact pattern: + +``` +- [Project Name](https://url) - Short description ending with a period. +``` + +Specifically: +- Starts with `- ` (dash + space) +- Followed by a markdown link `[Name](URL)` +- Followed by ` - ` (space, dash, space) +- Followed by a description that ends with a period `.` + +The regex used by `parse.py` to extract entries is: `^\s*- \[(.*)\]\((.*)\) - (.*)$` + +If the entry doesn't match, report exactly what's wrong (missing period, wrong separator, etc.). + +#### 4b. URL validation + +- **GitHub URLs are preferred.** If the URL points to `github.com`, that's ideal — no warning needed. +- **Non-GitHub URLs**: If the URL points somewhere else (PyPI, personal site, docs site, etc.), flag it with a warning: "This is not a GitHub URL. GitHub repos are preferred because they allow automated tracking of activity and archive status. Consider whether a GitHub link exists for this project." +- Check that the URL looks well-formed (starts with `https://`). + +#### 4c. Section placement + +Look at which `##` (language) and `###` (category) heading the entry was added under. Evaluate whether the project fits that section based on its description and URL: + +- Does the project's language match the section? (e.g., a Python library should be under `## Python`) +- Does the project's purpose match the category? (e.g., a backtesting framework should be under `### Trading & Backtesting`, not `### Indicators`) +- If the placement seems wrong, suggest a better section. + +The current sections in the README are: + +**Python**: Numerical Libraries & Data Structures, Financial Instruments and Pricing, Indicators, Trading & Backtesting, Risk Analysis, Factor Analysis, Sentiment Analysis, Quant Research Environment, Time Series, Calendars, Data Sources, Excel Integration, Visualization + +**R**: Numerical Libraries & Data Structures, Data Sources, Financial Instruments and Pricing, Trading, Backtesting, Risk Analysis, Factor Analysis, Time Series, Calendars + +**Other languages**: Matlab, Julia, Java, JavaScript, Haskell, Scala, Ruby, Elixir/Erlang, Golang, CPP, CSharp, Rust + +**Cross-language**: Frameworks, Reproducing Works Training & Books + +If the project doesn't fit any existing section, suggest the closest match or recommend creating a new subsection (rare). + +#### 4d. Duplicate check + +Grep the current `README.md` for the project name and URL to ensure it's not already listed. + +### Step 5: Summarize findings + +Present a clear summary: + +``` +PR #: +Author: <author> +Created: <date> + +Entries reviewed: <count> + +<For each entry> + - [Name](URL) - Description + Format: OK / ISSUE: <details> + URL: GitHub / WARNING: Non-GitHub URL (<domain>) + Section: OK (<section>) / SUGGESTION: Move to <section> + Duplicate: No / YES: Already listed at line <N> +</For each entry> + +Conflicts: None / YES: Needs rebase + +Verdict: APPROVE / NEEDS CHANGES +``` + +### Step 6: Take action + +- **If everything passes**: Ask the user for confirmation, then approve and merge the PR. +- **If there are issues**: Leave a constructive review comment on the PR listing what needs to be fixed. Be polite and specific — these are open-source contributors. + +When leaving comments, be friendly and grateful for the contribution. Example tone: +> Thanks for the contribution! A couple of things to address before we can merge: +> - The description should end with a period. +> - Consider linking to the GitHub repo instead of the docs site so we can track activity. diff --git a/.claude/skills/sync-site/SKILL.md b/.claude/skills/sync-site/SKILL.md new file mode 100644 index 0000000..a45594a --- /dev/null +++ b/.claude/skills/sync-site/SKILL.md @@ -0,0 +1,79 @@ +--- +name: sync-site +description: Sync site/index.qmd with README.md content. Use this skill whenever README.md has been updated and the site needs to reflect those changes — after merging PRs, editing entries, adding libraries, or any modification to README.md. Also triggers for "update site", "sync site", "deploy site", "update qmd", or mentions of site/index.qmd being out of date. +--- + +# Sync Site + +Update `site/index.qmd` to match the current `README.md` content, then commit and push. + +## How it works + +`site/index.qmd` is composed of two parts: + +1. **YAML frontmatter** (lines 1-11) — Quarto metadata that never changes. Preserve it exactly as-is. +2. **Body content** — Everything from `## Python` to the end of the file. This must match `README.md` from the `## Python` heading onward. + +The preamble in README.md (title, badges, language table of contents) is intentionally excluded from `index.qmd` because the Quarto frontmatter replaces it. + +## Steps + +### 1. Extract the frontmatter from index.qmd + +Read `site/index.qmd` and capture everything up to and including the closing `---` and the blank line + description + badge lines that follow it (lines 1-16). + +The header looks like this: +``` +--- +title: "Awesome Quant" +... +--- + +A curated list of insanely awesome libraries, packages and resources for Quants (Quantitative Finance). + +[![](https://awesome.re/badge.svg)](https://awesome.re) +``` + +### 2. Extract the body from README.md + +Read `README.md` and capture everything starting from the line `## Python` (inclusive) to the end of the file. + +### 3. Combine and write + +Concatenate the frontmatter header and the README body, then write to `site/index.qmd`. + +Use a shell script for reliability: + +```bash +# Extract line number where "## Python" starts in README.md +START=$(grep -n '^## Python' README.md | head -1 | cut -d: -f1) + +# Extract frontmatter + preamble from index.qmd (up to the badge line) +END=$(grep -n 'awesome.re/badge' site/index.qmd | head -1 | cut -d: -f1) + +# Combine: header from qmd + body from README +{ head -n "$END" site/index.qmd; echo; tail -n +"$START" README.md; } > site/index.qmd.tmp +mv site/index.qmd.tmp site/index.qmd +``` + +### 4. Verify + +Run a quick diff to confirm the update looks correct: + +```bash +# Show line count to sanity-check +wc -l site/index.qmd + +# Optionally diff the body portion to confirm they match +diff <(tail -n +"$START" README.md) <(sed -n "$((END+2)),\$p" site/index.qmd) +``` + +### 5. Commit and push + +Stage, commit, and push the change: + +```bash +git add site/index.qmd +git commit -m "Sync site/index.qmd with README.md" +git push origin master +``` diff --git a/.claude/skills/update-pypi-dates/README.md b/.claude/skills/update-pypi-dates/README.md new file mode 100644 index 0000000..4dcd620 --- /dev/null +++ b/.claude/skills/update-pypi-dates/README.md @@ -0,0 +1,132 @@ +# Update PyPI Dates Skill + +Automatically check PyPI projects for their latest update dates and keep README.md entries current. + +## Overview + +This skill monitors Python packages listed in awesome-quant and updates their entries with the latest release dates from PyPI. This ensures that outdated project information is easily identifiable. + +## Features + +- ✅ Checks 4 PyPI projects: qfrm, chinesestockapi, tushare, metatrader5 +- ✅ Fetches latest release dates from PyPI API +- ✅ Updates README.md entries with `(Last updated: YYYY-MM-DD)` format +- ✅ Commits changes automatically when updates found +- ✅ No dependencies beyond Python standard library + +## Usage + +### Invoke from Claude Code + +```bash +/update-pypi-dates +``` + +Or call the script directly: + +```bash +./.claude/skills/update-pypi-dates/scripts/check_pypi_dates.py +``` + +### Manual Python Execution + +```bash +python3 .claude/skills/update-pypi-dates/scripts/check_pypi_dates.py +``` + +### Bash Wrapper + +```bash +./.claude/skills/update-pypi-dates/scripts/update_pypi_dates.sh +``` + +## How It Works + +1. **Queries PyPI API** for each tracked package +2. **Extracts last release date** from the releases data +3. **Updates README.md** entries with format: `(Last updated: 2024-08-27)` +4. **Commits changes** if any updates were found + +## Adding New Packages + +To track additional PyPI packages, edit the `PYPI_PROJECTS` dictionary in `scripts/check_pypi_dates.py`: + +```python +PYPI_PROJECTS = { + "display_name": "pypi_package_name", + # Add new entries here +} +``` + +Then update the skill description if needed. + +## Output Examples + +### No Changes +``` +📦 Checking PyPI projects for last updated dates... + +Checking qfrm... + ✓ 2015-12-12 (no change) + +✓ All packages up to date! +``` + +### With Updates +``` +📦 Checking PyPI projects for last updated dates... + +Checking tushare... + ✓ 2024-08-27 (no change) +Checking metatrader5... + ✅ Updated to 2026-02-20 + +📝 Found 1 update(s): + - metatrader5: 2026-02-20 + +✅ Updated README.md +``` + +## Configuration + +### Periodic Execution + +Set up a cron job to run weekly: + +```bash +# Add to crontab (runs Monday at 2 AM) +0 2 * * 1 cd /path/to/awesome-quant && ./.claude/skills/update-pypi-dates/scripts/update_pypi_dates.sh +``` + +Or use Claude Code's scheduling: + +```bash +/loop 1w /update-pypi-dates +``` + +## Files + +- `SKILL.md` - Skill definition and documentation +- `scripts/check_pypi_dates.py` - Main Python script +- `scripts/update_pypi_dates.sh` - Bash wrapper with git integration +- `README.md` - This file + +## Requirements + +- Python 3.6+ +- Network access to PyPI API +- Git (for automatic commits) + +## Error Handling + +- Network timeouts: Gracefully skipped with warning +- Package not found: Skipped with message +- API errors: Logged and continued with other packages + +## Future Enhancements + +- [ ] Support for more PyPI packages +- [ ] GitHub Actions workflow integration +- [ ] Email notifications on updates +- [ ] Comparison with published versions in README +- [ ] Support for other package registries (npm, Ruby Gems, etc.) diff --git a/.claude/skills/update-pypi-dates/SKILL.md b/.claude/skills/update-pypi-dates/SKILL.md new file mode 100644 index 0000000..7d24279 --- /dev/null +++ b/.claude/skills/update-pypi-dates/SKILL.md @@ -0,0 +1,66 @@ +--- +name: update-pypi-dates +description: Check PyPI projects for last updated dates and update README.md entries. Use this skill to keep PyPI project information current with latest release dates from PyPI. Run periodically to maintain up-to-date metadata on Python packages listed in awesome-quant. +--- + +# Update PyPI Dates + +Automatically check PyPI projects for their latest update dates and update corresponding README.md entries with the "(Last updated: YYYY-MM-DD)" information. + +## How it works + +The skill maintains a list of PyPI projects in awesome-quant and: +1. Queries the PyPI JSON API for each package +2. Extracts the last release date +3. Updates README.md entries with the new date if changed +4. Commits changes to git if updates were made + +## PyPI Projects to Track + +Current projects tracked: +- qfrm +- chinesestockapi +- tushare +- metatrader5 + +## Steps + +### 1. Fetch PyPI data for each project + +For each PyPI project, query: `https://pypi.org/pypi/{package_name}/json` + +Extract the last release date from the releases data. + +### 2. Update README.md entries + +For each project with a new date: +- Find the entry in README.md +- Update or add the "(Last updated: YYYY-MM-DD)" note +- Use the exact entry format: `(Last updated: 2024-08-27)` + +### 3. Verify changes + +Show the user: +- Which packages were checked +- Which packages have new update dates +- The differences (old date → new date) + +### 4. Commit if changes detected + +If any updates were made: +```bash +git add README.md non-github-urls.md +git commit -m "Update PyPI project last updated dates" +git push origin master +``` + +## Usage + +The user can: +- Run the skill directly: `/update-pypi-dates` +- Schedule it to run weekly/monthly via cron +- Include it in CI/CD pipelines to keep data fresh + +## Script + +Use `scripts/check_pypi_dates.sh` for the core update logic. diff --git a/.claude/skills/update-pypi-dates/scripts/check_pypi_dates.py b/.claude/skills/update-pypi-dates/scripts/check_pypi_dates.py new file mode 100755 index 0000000..0db142d --- /dev/null +++ b/.claude/skills/update-pypi-dates/scripts/check_pypi_dates.py @@ -0,0 +1,154 @@ +#!/usr/bin/env python3 +""" +Check PyPI projects for last updated dates and update README.md entries. +""" + +import json +import re +import subprocess +import sys +from datetime import datetime +from pathlib import Path +from typing import Dict, Optional, Tuple +from urllib.request import urlopen + +# PyPI projects to track +PYPI_PROJECTS = { + "qfrm": "qfrm", + "chinesestockapi": "chinesestockapi", + "tushare": "tushare", + "metatrader5": "MetaTrader5", +} + +README_PATH = Path(__file__).parent.parent.parent.parent.parent / "README.md" + + +def get_pypi_last_updated(package_name: str) -> Optional[str]: + """ + Fetch the last updated date for a PyPI package. + + Returns: date string in format "YYYY-MM-DD" or None if not found + """ + try: + url = f"https://pypi.org/pypi/{package_name}/json" + with urlopen(url, timeout=5) as response: + data = json.loads(response.read().decode()) + + # Get releases (ordered dict, latest first after sorting) + releases = data.get("releases", {}) + if not releases: + return None + + # Find the latest release with upload time + for version in sorted(releases.keys(), reverse=True): + release_data = releases[version] + if release_data and len(release_data) > 0: + upload_time = release_data[0].get("upload_time_iso_8601") + if upload_time: + # Extract just the date part (YYYY-MM-DD) + return upload_time.split("T")[0] + + return None + except Exception as e: + print(f" ❌ Error fetching {package_name}: {e}", file=sys.stderr) + return None + + +def find_readme_entry(readme_content: str, package_display_name: str) -> Tuple[Optional[int], Optional[str]]: + """ + Find a README entry for a package. + + Returns: (line_number, match_text) or (None, None) + """ + # Pattern to match the entry - look for [package_name](pypi.org...) + pattern = rf"\- \[{re.escape(package_display_name)}\]\(https://pypi\.org/project/\w+/\)[^\n]*" + + for line_num, line in enumerate(readme_content.split("\n"), 1): + if re.search(pattern, line): + return line_num, line.strip() + + return None, None + + +def update_readme_entry(readme_content: str, package_display_name: str, new_date: str) -> Tuple[str, bool]: + """ + Update or add the last updated date to a README entry. + + Returns: (updated_content, was_changed) + """ + lines = readme_content.split("\n") + line_num, entry_text = find_readme_entry(readme_content, package_display_name) + + if line_num is None: + return readme_content, False + + old_line = lines[line_num - 1] + + # Check if entry already has a date + existing_date_pattern = r"\(Last updated: \d{4}-\d{2}-\d{2}\)" + existing_match = re.search(existing_date_pattern, old_line) + + if existing_match: + old_date = existing_match.group(0) + new_date_str = f"(Last updated: {new_date})" + if old_date == new_date_str: + return readme_content, False # No change needed + new_line = old_line.replace(old_date, new_date_str) + else: + # Add date to the end of the entry (before period if present) + if old_line.endswith("."): + new_line = old_line[:-1] + f" (Last updated: {new_date})." + else: + new_line = old_line + f" (Last updated: {new_date})" + + lines[line_num - 1] = new_line + return "\n".join(lines), True + + +def main(): + """Check PyPI projects and update README.md.""" + if not README_PATH.exists(): + print(f"❌ README.md not found at {README_PATH}") + sys.exit(1) + + readme_content = README_PATH.read_text(encoding="utf-8") + updated_content = readme_content + changes_made = [] + + print("📦 Checking PyPI projects for last updated dates...\n") + + for display_name, pypi_name in PYPI_PROJECTS.items(): + print(f"Checking {display_name}...") + + last_updated = get_pypi_last_updated(pypi_name) + if last_updated is None: + print(f" ⚠️ Could not find last updated date") + continue + + updated_content, was_changed = update_readme_entry( + updated_content, display_name, last_updated + ) + + if was_changed: + print(f" ✅ Updated to {last_updated}") + changes_made.append((display_name, last_updated)) + else: + print(f" ✓ {last_updated} (no change)") + + print() + + if changes_made: + print(f"📝 Found {len(changes_made)} update(s):") + for pkg_name, date in changes_made: + print(f" - {pkg_name}: {date}") + + README_PATH.write_text(updated_content, encoding="utf-8") + print(f"\n✅ Updated {README_PATH.name}") + return 0 + else: + print("✓ All packages up to date!") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/.claude/skills/update-pypi-dates/scripts/update_pypi_dates.sh b/.claude/skills/update-pypi-dates/scripts/update_pypi_dates.sh new file mode 100755 index 0000000..23022d1 --- /dev/null +++ b/.claude/skills/update-pypi-dates/scripts/update_pypi_dates.sh @@ -0,0 +1,27 @@ +#!/bin/bash +# Wrapper script to check PyPI dates and update README.md + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/../../../../.." && pwd)" + +cd "$PROJECT_ROOT" || exit 1 + +echo "🚀 Starting PyPI date update check..." +python3 "$SCRIPT_DIR/check_pypi_dates.py" +exit_code=$? + +if [ $exit_code -eq 0 ]; then + # Check if there are any changes to commit + if git diff --quiet README.md; then + echo "✓ No changes needed" + else + echo "" + echo "📝 Changes detected, committing..." + git add README.md + git commit -m "Update PyPI project last updated dates" + git push origin master + echo "✅ Changes pushed to remote" + fi +fi + +exit $exit_code