diff --git a/.github/workflows/update-readme.yml b/.github/workflows/update-readme.yml
index a571e3b..0d772b8 100644
--- a/.github/workflows/update-readme.yml
+++ b/.github/workflows/update-readme.yml
@@ -42,7 +42,7 @@ jobs:
run: |
set -e
test -s README.md || { echo "README.md is empty"; exit 1; }
- python3 -c "import json,sys; d=json.load(open('README_DATA.json')); sys.exit(1 if not d.get('years') else 0)" \
+ python3 -c "import json,sys; d=json.load(open('README_DATA.json')); sys.exit(1 if not d.get('years') and not d.get('stats', {}).get('projects') else 0)" \
|| { echo "README_DATA.json is empty or invalid"; exit 1; }
- name: Commit and push changes
diff --git a/README.md b/README.md
index 0e0a883..1152133 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,9 @@
# OSS Contributions
-| Project | 🐛 Bug fixes | ✨ Features | ♻️ Refactors | 🔧 Chores | Total Merged |
-| :--- | :---: | :---: | :---: | :---: | :---: |
-|
| 19 | 2 | 1 | 2 | **24** |
+| Project | 🐛 Bug fixes | ✨ Features | ♻️ Refactors | ⚡ Performance | 🔧 Chores | 👷 CI | 🔨 Other | Total Merged |
+| :--- | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: |
+|
| 19 | 2 | 1 | 0 | 2 | 0 | 0 | **24** |
+|
| 111 | 77 | 37 | 2 | 82 | 60 | 57 | **500** |
# 2026
diff --git a/README_DATA.json b/README_DATA.json
index b03a7fc..46fe15f 100644
--- a/README_DATA.json
+++ b/README_DATA.json
@@ -18,10 +18,25 @@
"label": "Refactors",
"emoji": "♻️"
},
+ {
+ "category": "perf",
+ "label": "Performance",
+ "emoji": "⚡"
+ },
{
"category": "chore",
"label": "Chores",
"emoji": "🔧"
+ },
+ {
+ "category": "ci",
+ "label": "CI",
+ "emoji": "👷"
+ },
+ {
+ "category": "other",
+ "label": "Other",
+ "emoji": "🔨"
}
],
"projects": [
@@ -36,6 +51,25 @@
"refactor": 1,
"chore": 2
}
+ },
+ {
+ "name": "HDCharts",
+ "repo_url": "https://github.com/HDCharts/charts",
+ "logo_url": "https://github.com/HDCharts.png",
+ "total_merged": 500,
+ "categories": {
+ "fix": 111,
+ "feat": 77,
+ "refactor": 37,
+ "perf": 2,
+ "docs": 68,
+ "test": 3,
+ "chore": 82,
+ "build": 2,
+ "ci": 60,
+ "revert": 1,
+ "other": 57
+ }
}
]
},
diff --git a/oss-contributions.json b/oss-contributions.json
index 87f5487..250b86d 100644
--- a/oss-contributions.json
+++ b/oss-contributions.json
@@ -20,6 +20,16 @@
"Kilo-Org/kilocode",
"kilo-org/kilocode-legacy"
]
+ },
+ {
+ "name": "HDCharts",
+ "repos": [
+ "HDCharts/charts",
+ "HDCharts/charts-playground",
+ "HDCharts/charts-docs",
+ "HDCharts/charts-gif-recorder"
+ ],
+ "stats_only": true
}
]
}
diff --git a/src/cli.py b/src/cli.py
index 296eecd..6d51d78 100644
--- a/src/cli.py
+++ b/src/cli.py
@@ -25,9 +25,11 @@ def main():
print(f"Featured projects: {len(config['featured_projects'])}")
data, featured_repos = fetch_from_config(config)
- model = build_readme_model(data, featured_repos, config['stats_projects'])
+ model = build_readme_model(
+ data, featured_repos, config['stats_projects'], config['stats_only_repos']
+ )
- if _count_contributions(model) == 0:
+ if _count_contributions(model) == 0 and not model['stats']['projects']:
print("No contributions found; refusing to overwrite existing artifacts.")
return 1
diff --git a/src/config.py b/src/config.py
index a32b3cd..7cd9e9f 100644
--- a/src/config.py
+++ b/src/config.py
@@ -118,6 +118,12 @@ def load_config(config_file=None):
# Repos that get a per-category merged-PR counter at the top of the README; an
# entry may also be {"name": ..., "repos": [...]} to sum repos into one row.
"stats_projects": data.get("stats_projects", []),
+ "stats_only_repos": [
+ repo
+ for project in data.get("stats_projects", [])
+ if isinstance(project, dict) and project.get("stats_only")
+ for repo in project.get("repos", [])
+ ],
# None means "whoever the gh token belongs to".
"username": data.get("username"),
}
diff --git a/src/model.py b/src/model.py
index f89d951..44f6884 100644
--- a/src/model.py
+++ b/src/model.py
@@ -58,13 +58,14 @@ def fetch_from_config(config):
response is theirs; what is filtered here is which *repos* to keep.
"""
repos = config["repos"]
+ stats_only_repos = config.get("stats_only_repos", [])
# Empty statuses list means no filtering (show all)
allowed_statuses = set(config["statuses"]) or None
featured = config["featured_projects"]
username = config.get("username")
# Config may spell a repo in any casing; GitHub answers with canonical.
- tracked = {repo.lower() for repo in repos}
+ tracked = {repo.lower() for repo in [*repos, *stats_only_repos]}
print(f"Fetching pull requests authored by {username or 'the authenticated user'}...")
prs = fetch_authored_prs(username, query_states(allowed_statuses))
@@ -158,12 +159,12 @@ def build_stats(years, stats_projects):
used = {c for project in projects for c in project['categories']}
columns = [
{'category': c, 'label': CATEGORY_LABELS.get(c, 'Other'), 'emoji': CONVENTIONAL_EMOJI.get(c, DEFAULT_PR_EMOJI)}
- for c in category_order if c in used
+ for c in category_order if c in used and c not in {'build', 'docs', 'revert', 'test'}
]
return {'columns': columns, 'projects': projects}
-def build_readme_model(contributions_by_date, featured_repos, stats_projects=()):
+def build_readme_model(contributions_by_date, featured_repos, stats_projects=(), stats_only_repos=()):
featured_projects = []
if featured_repos:
sorted_featured = sorted(featured_repos.items(), key=lambda item: (item[1], item[0].lower()))
@@ -248,10 +249,25 @@ def build_readme_model(contributions_by_date, featured_repos, stats_projects=())
years.append({'year': year, 'months': months})
+ stats = build_stats(years, stats_projects)
+ stats_only = {repo.lower() for repo in stats_only_repos}
+ if stats_only:
+ for year in years:
+ visible_months = []
+ for month in year['months']:
+ month['rows'] = [
+ row for row in month['rows']
+ if row['repo_name'].lower() not in stats_only
+ ]
+ if month['rows']:
+ visible_months.append(month)
+ year['months'] = visible_months
+ years = [year for year in years if year['months']]
+
return {
'title': 'OSS Contributions',
'featured_projects': featured_projects,
- 'stats': build_stats(years, stats_projects),
+ 'stats': stats,
'years': years,
'status_legend': STATUS_LEGEND,
}