github-rep 0.1.1__tar.gz → 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- github_rep-0.2.0/PKG-INFO +245 -0
- github_rep-0.2.0/README.md +213 -0
- {github_rep-0.1.1 → github_rep-0.2.0}/pyproject.toml +1 -1
- github_rep-0.2.0/src/github_rep/__init__.py +1 -0
- {github_rep-0.1.1 → github_rep-0.2.0}/src/github_rep/analyzer.py +90 -18
- {github_rep-0.1.1 → github_rep-0.2.0}/src/github_rep/api.py +1 -1
- {github_rep-0.1.1 → github_rep-0.2.0}/src/github_rep/cli.py +25 -31
- github_rep-0.2.0/src/github_rep.egg-info/PKG-INFO +245 -0
- {github_rep-0.1.1 → github_rep-0.2.0}/tests/test_analyzer.py +121 -10
- github_rep-0.1.1/PKG-INFO +0 -98
- github_rep-0.1.1/README.md +0 -66
- github_rep-0.1.1/src/github_rep/__init__.py +0 -3
- github_rep-0.1.1/src/github_rep.egg-info/PKG-INFO +0 -98
- {github_rep-0.1.1 → github_rep-0.2.0}/LICENSE +0 -0
- {github_rep-0.1.1 → github_rep-0.2.0}/setup.cfg +0 -0
- {github_rep-0.1.1 → github_rep-0.2.0}/src/github_rep.egg-info/SOURCES.txt +0 -0
- {github_rep-0.1.1 → github_rep-0.2.0}/src/github_rep.egg-info/dependency_links.txt +0 -0
- {github_rep-0.1.1 → github_rep-0.2.0}/src/github_rep.egg-info/entry_points.txt +0 -0
- {github_rep-0.1.1 → github_rep-0.2.0}/src/github_rep.egg-info/requires.txt +0 -0
- {github_rep-0.1.1 → github_rep-0.2.0}/src/github_rep.egg-info/top_level.txt +0 -0
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: github-rep
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Analyze a GitHub profile and get honest, actionable advice for building real reputation
|
|
5
|
+
Author-email: Basil Alshukaili <basilalshukaili@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/basilalshukaili/github-rep
|
|
8
|
+
Project-URL: Bug Tracker, https://github.com/basilalshukaili/github-rep/issues
|
|
9
|
+
Keywords: github,open-source,developer-tools,profile,reputation,cli
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Topic :: Software Development :: Version Control :: Git
|
|
20
|
+
Requires-Python: >=3.9
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Requires-Dist: requests>=2.31
|
|
24
|
+
Requires-Dist: rich>=13.0
|
|
25
|
+
Requires-Dist: typer>=0.9
|
|
26
|
+
Requires-Dist: python-dateutil>=2.8
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: pytest>=7; extra == "dev"
|
|
29
|
+
Requires-Dist: responses>=0.25; extra == "dev"
|
|
30
|
+
Requires-Dist: pytest-cov>=4; extra == "dev"
|
|
31
|
+
Dynamic: license-file
|
|
32
|
+
|
|
33
|
+
# github-rep
|
|
34
|
+
|
|
35
|
+
[](https://pypi.org/project/github-rep/)
|
|
36
|
+
[](https://pypi.org/project/github-rep/)
|
|
37
|
+
[](https://github.com/basilalshukaili/github-rep/actions/workflows/ci.yml)
|
|
38
|
+
[](LICENSE)
|
|
39
|
+
|
|
40
|
+
**Score any GitHub user's reputation across 11 honest signals and get a prioritized fix list — in under 30 seconds.**
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Why this exists
|
|
45
|
+
|
|
46
|
+
GitHub profiles are the de-facto developer resume. Most advice on building GitHub reputation is either
|
|
47
|
+
vague ("just contribute more") or gameable (spam-stars, low-effort PRs). This tool measures the signals
|
|
48
|
+
that actually matter to recruiters, maintainers, and other developers — and tells you exactly what to
|
|
49
|
+
fix first, ordered by impact.
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Install
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
pip install github-rep
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Requires Python 3.9+. No configuration needed — works unauthenticated (60 req/hr) or with a GitHub
|
|
60
|
+
token for 5000 req/hr.
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Quickstart
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
# Analyze any GitHub user
|
|
68
|
+
github-rep analyze-profile torvalds
|
|
69
|
+
|
|
70
|
+
# Use a token to avoid rate limits
|
|
71
|
+
export GITHUB_TOKEN=ghp_yourtoken
|
|
72
|
+
github-rep analyze-profile sindresorhus
|
|
73
|
+
|
|
74
|
+
# Machine-readable JSON output
|
|
75
|
+
github-rep analyze-profile gvanrossum --json
|
|
76
|
+
|
|
77
|
+
# Show all findings, including low-priority ones
|
|
78
|
+
github-rep analyze-profile octocat --verbose
|
|
79
|
+
|
|
80
|
+
# Compare multiple profiles side by side
|
|
81
|
+
github-rep compare torvalds gvanrossum sindresorhus
|
|
82
|
+
|
|
83
|
+
# Check your API rate limit
|
|
84
|
+
github-rep rate-limit
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## Sample output
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
$ github-rep analyze-profile torvalds
|
|
93
|
+
|
|
94
|
+
╭──────────────────────────────── GitHub Profile ─────────────────────────────╮
|
|
95
|
+
│ @torvalds | Linus Torvalds │
|
|
96
|
+
│ │
|
|
97
|
+
│ Followers: 305,509 | Public repos: 12 | Stars earned: 246,028 │
|
|
98
|
+
╰──────────────────────────────────────────────────────────────────────────────╯
|
|
99
|
+
Grade: B (78/100)
|
|
100
|
+
Tier: Active developer
|
|
101
|
+
|
|
102
|
+
Score Breakdown
|
|
103
|
+
┏━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━┓
|
|
104
|
+
┃ Dimension ┃ Score ┃ Max ┃
|
|
105
|
+
┡━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━┩
|
|
106
|
+
│ Profile Completeness │ 4 │ 10 │
|
|
107
|
+
│ Readme Quality │ 12 │ 15 │
|
|
108
|
+
│ Star Signal │ 20 │ 20 │
|
|
109
|
+
│ Contribution Streak │ 15 │ 15 │
|
|
110
|
+
│ Repo Diversity │ 7 │ 10 │
|
|
111
|
+
│ Description Quality │ 10 │ 10 │
|
|
112
|
+
│ Topic Tags │ 0 │ 5 │
|
|
113
|
+
│ Fork Ratio │ 5 │ 5 │
|
|
114
|
+
│ Recent Activity │ 5 │ 10 │
|
|
115
|
+
│ Release Cadence │ 0 │ 5 │
|
|
116
|
+
│ Profile Readme │ 0 │ 5 │
|
|
117
|
+
└──────────────────────────┴────────┴───────┘
|
|
118
|
+
|
|
119
|
+
Priority fixes:
|
|
120
|
+
[HIGH] Missing bio
|
|
121
|
+
Your bio is empty. It is the first thing visitors read.
|
|
122
|
+
Fix: Write 1-2 sentences: your focus, what you build, your superpower.
|
|
123
|
+
|
|
124
|
+
What is working:
|
|
125
|
+
[GOOD] Strong star signal (246028 total, top repo: 235147)
|
|
126
|
+
[GOOD] Active recent commits (0d ago)
|
|
127
|
+
[GOOD] All repos have descriptions
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Grades: **A** (≥80) · **B** (≥65) · **C** (≥50) · **D** (≥35) · **F**
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## The 11 scored dimensions
|
|
135
|
+
|
|
136
|
+
| # | Dimension | Max pts | What it measures |
|
|
137
|
+
|---|-----------|--------:|------------------|
|
|
138
|
+
| 1 | Profile Completeness | 10 | Name, bio, avatar, location, website / social link |
|
|
139
|
+
| 2 | README Quality | 15 | Top repo README length, code examples, install instructions |
|
|
140
|
+
| 3 | Star Signal | 20 | Total stars earned across all repos (log-scaled) |
|
|
141
|
+
| 4 | Contribution Streak | 15 | Days since last profile activity |
|
|
142
|
+
| 5 | Repo Diversity | 10 | Number of public repos and language breadth |
|
|
143
|
+
| 6 | Description Quality | 10 | Fraction of repos with meaningful descriptions |
|
|
144
|
+
| 7 | Topic Tags | 5 | Repos tagged with relevant GitHub topics |
|
|
145
|
+
| 8 | Fork Ratio | 5 | Proportion of original work vs. forked repos |
|
|
146
|
+
| 9 | Recent Activity | 10 | Repos with pushes in the last 90 days |
|
|
147
|
+
| 10 | **Release Cadence** | 5 | Published GitHub Releases on top repos |
|
|
148
|
+
| 11 | **Profile README** | 5 | Presence and quality of the username/username profile README |
|
|
149
|
+
|
|
150
|
+
**Total: 100 points.**
|
|
151
|
+
|
|
152
|
+
Dimensions 10 and 11 are new in v0.2.0 and measure signals that indicate a polished,
|
|
153
|
+
production-ready presence — publishing versioned releases and curating a profile page.
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## All flags
|
|
158
|
+
|
|
159
|
+
```
|
|
160
|
+
github-rep analyze-profile <username> [OPTIONS]
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
| Flag | Description |
|
|
164
|
+
|------|-------------|
|
|
165
|
+
| `--json` | Machine-readable JSON output (all scores + findings) |
|
|
166
|
+
| `--verbose` / `-v` | Show all findings including low-priority improvements |
|
|
167
|
+
| `--token` / `-t` | GitHub PAT (also reads `GITHUB_TOKEN` env var) |
|
|
168
|
+
| `--top N` | Number of top repos to deep-analyze (default: 10) |
|
|
169
|
+
| `--help` | Show help and exit |
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## JSON output
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
github-rep analyze-profile gvanrossum --json
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
```json
|
|
180
|
+
{
|
|
181
|
+
"username": "gvanrossum",
|
|
182
|
+
"total": 72,
|
|
183
|
+
"grade": "B",
|
|
184
|
+
"tier": "Active developer",
|
|
185
|
+
"breakdown": {
|
|
186
|
+
"profile_completeness": 8,
|
|
187
|
+
"readme_quality": 12,
|
|
188
|
+
...
|
|
189
|
+
},
|
|
190
|
+
"findings": [
|
|
191
|
+
{
|
|
192
|
+
"category": "topic_tags",
|
|
193
|
+
"severity": "medium",
|
|
194
|
+
"title": "Most repos have no topic tags",
|
|
195
|
+
"detail": "Missing from GitHub Explore category pages entirely.",
|
|
196
|
+
"fix": "Add topics to your top 3 repos today - GitHub UI, takes 2 minutes."
|
|
197
|
+
}
|
|
198
|
+
]
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## Caching
|
|
205
|
+
|
|
206
|
+
Results are cached for 5 minutes under `~/.cache/github-rep/` to avoid hitting rate limits when
|
|
207
|
+
running the tool multiple times. Delete the cache directory to force a fresh fetch.
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
## FAQ
|
|
212
|
+
|
|
213
|
+
**Do I need a GitHub token?**
|
|
214
|
+
No. Unauthenticated usage gets 60 API requests per hour — enough for a single profile analysis.
|
|
215
|
+
Set `GITHUB_TOKEN` to get 5000 requests per hour and avoid hitting limits when comparing many profiles.
|
|
216
|
+
|
|
217
|
+
**How is the score calculated?**
|
|
218
|
+
Each dimension is scored independently against fixed max points (totalling 100). There is no
|
|
219
|
+
machine learning or relative ranking — the score reflects the absolute presence or absence of
|
|
220
|
+
each signal.
|
|
221
|
+
|
|
222
|
+
**Can I game the score?**
|
|
223
|
+
You could game every individual metric, but you would also genuinely improve your GitHub presence
|
|
224
|
+
in the process. The signals are chosen because they correlate with real reputation value.
|
|
225
|
+
|
|
226
|
+
**Why does a well-known developer score lower than expected?**
|
|
227
|
+
Some high-reputation developers (including Linus Torvalds) score below 80 because they skip
|
|
228
|
+
signals like topic tags, profile READMEs, or GitHub Releases. The tool measures profile
|
|
229
|
+
hygiene signals, not absolute influence.
|
|
230
|
+
|
|
231
|
+
**How often should I re-run this?**
|
|
232
|
+
After each batch of improvements. Treat it like `npm audit` — run it, fix the findings, move on.
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## Contributing
|
|
237
|
+
|
|
238
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md). Bug reports and improvements welcome — open an issue on
|
|
239
|
+
[github.com/basilalshukaili/github-rep](https://github.com/basilalshukaili/github-rep/issues).
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
## License
|
|
244
|
+
|
|
245
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
# github-rep
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/github-rep/)
|
|
4
|
+
[](https://pypi.org/project/github-rep/)
|
|
5
|
+
[](https://github.com/basilalshukaili/github-rep/actions/workflows/ci.yml)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
|
|
8
|
+
**Score any GitHub user's reputation across 11 honest signals and get a prioritized fix list — in under 30 seconds.**
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Why this exists
|
|
13
|
+
|
|
14
|
+
GitHub profiles are the de-facto developer resume. Most advice on building GitHub reputation is either
|
|
15
|
+
vague ("just contribute more") or gameable (spam-stars, low-effort PRs). This tool measures the signals
|
|
16
|
+
that actually matter to recruiters, maintainers, and other developers — and tells you exactly what to
|
|
17
|
+
fix first, ordered by impact.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pip install github-rep
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Requires Python 3.9+. No configuration needed — works unauthenticated (60 req/hr) or with a GitHub
|
|
28
|
+
token for 5000 req/hr.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Quickstart
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
# Analyze any GitHub user
|
|
36
|
+
github-rep analyze-profile torvalds
|
|
37
|
+
|
|
38
|
+
# Use a token to avoid rate limits
|
|
39
|
+
export GITHUB_TOKEN=ghp_yourtoken
|
|
40
|
+
github-rep analyze-profile sindresorhus
|
|
41
|
+
|
|
42
|
+
# Machine-readable JSON output
|
|
43
|
+
github-rep analyze-profile gvanrossum --json
|
|
44
|
+
|
|
45
|
+
# Show all findings, including low-priority ones
|
|
46
|
+
github-rep analyze-profile octocat --verbose
|
|
47
|
+
|
|
48
|
+
# Compare multiple profiles side by side
|
|
49
|
+
github-rep compare torvalds gvanrossum sindresorhus
|
|
50
|
+
|
|
51
|
+
# Check your API rate limit
|
|
52
|
+
github-rep rate-limit
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Sample output
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
$ github-rep analyze-profile torvalds
|
|
61
|
+
|
|
62
|
+
╭──────────────────────────────── GitHub Profile ─────────────────────────────╮
|
|
63
|
+
│ @torvalds | Linus Torvalds │
|
|
64
|
+
│ │
|
|
65
|
+
│ Followers: 305,509 | Public repos: 12 | Stars earned: 246,028 │
|
|
66
|
+
╰──────────────────────────────────────────────────────────────────────────────╯
|
|
67
|
+
Grade: B (78/100)
|
|
68
|
+
Tier: Active developer
|
|
69
|
+
|
|
70
|
+
Score Breakdown
|
|
71
|
+
┏━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━┓
|
|
72
|
+
┃ Dimension ┃ Score ┃ Max ┃
|
|
73
|
+
┡━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━┩
|
|
74
|
+
│ Profile Completeness │ 4 │ 10 │
|
|
75
|
+
│ Readme Quality │ 12 │ 15 │
|
|
76
|
+
│ Star Signal │ 20 │ 20 │
|
|
77
|
+
│ Contribution Streak │ 15 │ 15 │
|
|
78
|
+
│ Repo Diversity │ 7 │ 10 │
|
|
79
|
+
│ Description Quality │ 10 │ 10 │
|
|
80
|
+
│ Topic Tags │ 0 │ 5 │
|
|
81
|
+
│ Fork Ratio │ 5 │ 5 │
|
|
82
|
+
│ Recent Activity │ 5 │ 10 │
|
|
83
|
+
│ Release Cadence │ 0 │ 5 │
|
|
84
|
+
│ Profile Readme │ 0 │ 5 │
|
|
85
|
+
└──────────────────────────┴────────┴───────┘
|
|
86
|
+
|
|
87
|
+
Priority fixes:
|
|
88
|
+
[HIGH] Missing bio
|
|
89
|
+
Your bio is empty. It is the first thing visitors read.
|
|
90
|
+
Fix: Write 1-2 sentences: your focus, what you build, your superpower.
|
|
91
|
+
|
|
92
|
+
What is working:
|
|
93
|
+
[GOOD] Strong star signal (246028 total, top repo: 235147)
|
|
94
|
+
[GOOD] Active recent commits (0d ago)
|
|
95
|
+
[GOOD] All repos have descriptions
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Grades: **A** (≥80) · **B** (≥65) · **C** (≥50) · **D** (≥35) · **F**
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## The 11 scored dimensions
|
|
103
|
+
|
|
104
|
+
| # | Dimension | Max pts | What it measures |
|
|
105
|
+
|---|-----------|--------:|------------------|
|
|
106
|
+
| 1 | Profile Completeness | 10 | Name, bio, avatar, location, website / social link |
|
|
107
|
+
| 2 | README Quality | 15 | Top repo README length, code examples, install instructions |
|
|
108
|
+
| 3 | Star Signal | 20 | Total stars earned across all repos (log-scaled) |
|
|
109
|
+
| 4 | Contribution Streak | 15 | Days since last profile activity |
|
|
110
|
+
| 5 | Repo Diversity | 10 | Number of public repos and language breadth |
|
|
111
|
+
| 6 | Description Quality | 10 | Fraction of repos with meaningful descriptions |
|
|
112
|
+
| 7 | Topic Tags | 5 | Repos tagged with relevant GitHub topics |
|
|
113
|
+
| 8 | Fork Ratio | 5 | Proportion of original work vs. forked repos |
|
|
114
|
+
| 9 | Recent Activity | 10 | Repos with pushes in the last 90 days |
|
|
115
|
+
| 10 | **Release Cadence** | 5 | Published GitHub Releases on top repos |
|
|
116
|
+
| 11 | **Profile README** | 5 | Presence and quality of the username/username profile README |
|
|
117
|
+
|
|
118
|
+
**Total: 100 points.**
|
|
119
|
+
|
|
120
|
+
Dimensions 10 and 11 are new in v0.2.0 and measure signals that indicate a polished,
|
|
121
|
+
production-ready presence — publishing versioned releases and curating a profile page.
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## All flags
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
github-rep analyze-profile <username> [OPTIONS]
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
| Flag | Description |
|
|
132
|
+
|------|-------------|
|
|
133
|
+
| `--json` | Machine-readable JSON output (all scores + findings) |
|
|
134
|
+
| `--verbose` / `-v` | Show all findings including low-priority improvements |
|
|
135
|
+
| `--token` / `-t` | GitHub PAT (also reads `GITHUB_TOKEN` env var) |
|
|
136
|
+
| `--top N` | Number of top repos to deep-analyze (default: 10) |
|
|
137
|
+
| `--help` | Show help and exit |
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## JSON output
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
github-rep analyze-profile gvanrossum --json
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
```json
|
|
148
|
+
{
|
|
149
|
+
"username": "gvanrossum",
|
|
150
|
+
"total": 72,
|
|
151
|
+
"grade": "B",
|
|
152
|
+
"tier": "Active developer",
|
|
153
|
+
"breakdown": {
|
|
154
|
+
"profile_completeness": 8,
|
|
155
|
+
"readme_quality": 12,
|
|
156
|
+
...
|
|
157
|
+
},
|
|
158
|
+
"findings": [
|
|
159
|
+
{
|
|
160
|
+
"category": "topic_tags",
|
|
161
|
+
"severity": "medium",
|
|
162
|
+
"title": "Most repos have no topic tags",
|
|
163
|
+
"detail": "Missing from GitHub Explore category pages entirely.",
|
|
164
|
+
"fix": "Add topics to your top 3 repos today - GitHub UI, takes 2 minutes."
|
|
165
|
+
}
|
|
166
|
+
]
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## Caching
|
|
173
|
+
|
|
174
|
+
Results are cached for 5 minutes under `~/.cache/github-rep/` to avoid hitting rate limits when
|
|
175
|
+
running the tool multiple times. Delete the cache directory to force a fresh fetch.
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## FAQ
|
|
180
|
+
|
|
181
|
+
**Do I need a GitHub token?**
|
|
182
|
+
No. Unauthenticated usage gets 60 API requests per hour — enough for a single profile analysis.
|
|
183
|
+
Set `GITHUB_TOKEN` to get 5000 requests per hour and avoid hitting limits when comparing many profiles.
|
|
184
|
+
|
|
185
|
+
**How is the score calculated?**
|
|
186
|
+
Each dimension is scored independently against fixed max points (totalling 100). There is no
|
|
187
|
+
machine learning or relative ranking — the score reflects the absolute presence or absence of
|
|
188
|
+
each signal.
|
|
189
|
+
|
|
190
|
+
**Can I game the score?**
|
|
191
|
+
You could game every individual metric, but you would also genuinely improve your GitHub presence
|
|
192
|
+
in the process. The signals are chosen because they correlate with real reputation value.
|
|
193
|
+
|
|
194
|
+
**Why does a well-known developer score lower than expected?**
|
|
195
|
+
Some high-reputation developers (including Linus Torvalds) score below 80 because they skip
|
|
196
|
+
signals like topic tags, profile READMEs, or GitHub Releases. The tool measures profile
|
|
197
|
+
hygiene signals, not absolute influence.
|
|
198
|
+
|
|
199
|
+
**How often should I re-run this?**
|
|
200
|
+
After each batch of improvements. Treat it like `npm audit` — run it, fix the findings, move on.
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## Contributing
|
|
205
|
+
|
|
206
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md). Bug reports and improvements welcome — open an issue on
|
|
207
|
+
[github.com/basilalshukaili/github-rep](https://github.com/basilalshukaili/github-rep/issues).
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
## License
|
|
212
|
+
|
|
213
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "github-rep"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.2.0"
|
|
8
8
|
description = "Analyze a GitHub profile and get honest, actionable advice for building real reputation"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "MIT" }
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.2.0"
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Profile analyzer: scores genuine reputation signals across
|
|
1
|
+
"""Profile analyzer: scores genuine reputation signals across 11 dimensions."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
@@ -67,7 +67,7 @@ class ProfileScore:
|
|
|
67
67
|
return "Just starting"
|
|
68
68
|
|
|
69
69
|
|
|
70
|
-
#
|
|
70
|
+
# -- Helpers -------------------------------------------------------------------
|
|
71
71
|
|
|
72
72
|
def _days_since(dt_str: Optional[str]) -> Optional[int]:
|
|
73
73
|
if not dt_str:
|
|
@@ -113,7 +113,6 @@ def _readme_score(readme_text: Optional[str]) -> Tuple[int, List[Finding]]:
|
|
|
113
113
|
else:
|
|
114
114
|
score += 12
|
|
115
115
|
|
|
116
|
-
# Code examples
|
|
117
116
|
if "```" in readme_text or "`" in readme_text:
|
|
118
117
|
score += 2
|
|
119
118
|
else:
|
|
@@ -124,7 +123,6 @@ def _readme_score(readme_text: Optional[str]) -> Tuple[int, List[Finding]]:
|
|
|
124
123
|
"Add a quick-start code block showing the most common use case.",
|
|
125
124
|
))
|
|
126
125
|
|
|
127
|
-
# Installation instructions
|
|
128
126
|
has_install = any(
|
|
129
127
|
kw in readme_text.lower()
|
|
130
128
|
for kw in ["install", "pip install", "npm install", "brew install", "cargo add"]
|
|
@@ -142,14 +140,14 @@ def _readme_score(readme_text: Optional[str]) -> Tuple[int, List[Finding]]:
|
|
|
142
140
|
return min(score, 15), findings
|
|
143
141
|
|
|
144
142
|
|
|
145
|
-
#
|
|
143
|
+
# -- Main analyzer -------------------------------------------------------------
|
|
146
144
|
|
|
147
145
|
def analyze(
|
|
148
146
|
username: str,
|
|
149
147
|
token: Optional[str] = None,
|
|
150
148
|
top_n: int = 10,
|
|
151
149
|
) -> ProfileScore:
|
|
152
|
-
"""Fetch GitHub data and compute a ProfileScore across
|
|
150
|
+
"""Fetch GitHub data and compute a ProfileScore across 11 dimensions.
|
|
153
151
|
|
|
154
152
|
Args:
|
|
155
153
|
username: GitHub username to analyze.
|
|
@@ -168,7 +166,7 @@ def analyze(
|
|
|
168
166
|
breakdown: Dict[str, int] = {}
|
|
169
167
|
findings: List[Finding] = []
|
|
170
168
|
|
|
171
|
-
#
|
|
169
|
+
# 1. Profile completeness (10 pts) ----------------------------------------
|
|
172
170
|
pc = 0
|
|
173
171
|
if user.get("bio"):
|
|
174
172
|
pc += 3
|
|
@@ -213,7 +211,7 @@ def analyze(
|
|
|
213
211
|
))
|
|
214
212
|
breakdown["profile_completeness"] = min(pc, 10)
|
|
215
213
|
|
|
216
|
-
#
|
|
214
|
+
# 2. README quality (15 pts) -----------------------------------------------
|
|
217
215
|
readme_text: Optional[str] = None
|
|
218
216
|
if top_repos:
|
|
219
217
|
best = top_repos[0]
|
|
@@ -226,7 +224,7 @@ def analyze(
|
|
|
226
224
|
breakdown["readme_quality"] = readme_pts
|
|
227
225
|
findings.extend(readme_findings)
|
|
228
226
|
|
|
229
|
-
#
|
|
227
|
+
# 3. Star signal (20 pts) --------------------------------------------------
|
|
230
228
|
total_stars = sum(r.get("stargazers_count", 0) for r in repos)
|
|
231
229
|
max_stars = max((r.get("stargazers_count", 0) for r in repos), default=0)
|
|
232
230
|
if total_stars == 0:
|
|
@@ -244,8 +242,7 @@ def analyze(
|
|
|
244
242
|
"star_signal", "medium",
|
|
245
243
|
f"Low star count ({total_stars} total)",
|
|
246
244
|
"Genuine stars come from genuine visibility.",
|
|
247
|
-
"Share in community
|
|
248
|
-
"(build log, lesson learned, solved problem).",
|
|
245
|
+
"Share in community when you have something useful to say.",
|
|
249
246
|
))
|
|
250
247
|
elif total_stars < 25:
|
|
251
248
|
star_pts = 10
|
|
@@ -262,7 +259,7 @@ def analyze(
|
|
|
262
259
|
))
|
|
263
260
|
breakdown["star_signal"] = star_pts
|
|
264
261
|
|
|
265
|
-
#
|
|
262
|
+
# 4. Contribution activity (15 pts) ----------------------------------------
|
|
266
263
|
days = _days_since(user.get("updated_at"))
|
|
267
264
|
if days is None or days > 180:
|
|
268
265
|
streak_pts = 0
|
|
@@ -291,7 +288,7 @@ def analyze(
|
|
|
291
288
|
))
|
|
292
289
|
breakdown["contribution_streak"] = streak_pts
|
|
293
290
|
|
|
294
|
-
#
|
|
291
|
+
# 5. Repo diversity (10 pts) -----------------------------------------------
|
|
295
292
|
n_repos = len(repos)
|
|
296
293
|
if n_repos == 0:
|
|
297
294
|
div_pts = 0
|
|
@@ -322,7 +319,7 @@ def analyze(
|
|
|
322
319
|
))
|
|
323
320
|
breakdown["repo_diversity"] = div_pts
|
|
324
321
|
|
|
325
|
-
#
|
|
322
|
+
# 6. Description quality (10 pts) ------------------------------------------
|
|
326
323
|
repos_missing_desc = [r for r in repos if not r.get("description")]
|
|
327
324
|
if repos_missing_desc:
|
|
328
325
|
pct = len(repos_missing_desc) / max(len(repos), 1)
|
|
@@ -343,7 +340,7 @@ def analyze(
|
|
|
343
340
|
))
|
|
344
341
|
breakdown["description_quality"] = desc_pts
|
|
345
342
|
|
|
346
|
-
#
|
|
343
|
+
# 7. Topic tags (5 pts) ----------------------------------------------------
|
|
347
344
|
repos_without_topics = [r for r in repos if not r.get("topics")]
|
|
348
345
|
if not repos_without_topics:
|
|
349
346
|
topic_pts = 5
|
|
@@ -370,7 +367,7 @@ def analyze(
|
|
|
370
367
|
))
|
|
371
368
|
breakdown["topic_tags"] = topic_pts
|
|
372
369
|
|
|
373
|
-
#
|
|
370
|
+
# 8. Fork ratio (5 pts) ----------------------------------------------------
|
|
374
371
|
all_repos_with_forks = client.get_repos(username, include_forks=True)
|
|
375
372
|
fork_count = sum(1 for r in all_repos_with_forks if r.get("fork"))
|
|
376
373
|
total_count = len(all_repos_with_forks)
|
|
@@ -389,7 +386,7 @@ def analyze(
|
|
|
389
386
|
fork_pts = 5
|
|
390
387
|
breakdown["fork_ratio"] = fork_pts
|
|
391
388
|
|
|
392
|
-
#
|
|
389
|
+
# 9. Recent activity quality (10 pts) --------------------------------------
|
|
393
390
|
recently_active = [
|
|
394
391
|
r for r in repos
|
|
395
392
|
if _days_since(r.get("pushed_at")) is not None
|
|
@@ -420,7 +417,82 @@ def analyze(
|
|
|
420
417
|
))
|
|
421
418
|
breakdown["recent_activity"] = ra_pts
|
|
422
419
|
|
|
423
|
-
#
|
|
420
|
+
# 10. Release cadence (5 pts) -- NEW ---------------------------------------
|
|
421
|
+
# Published releases signal versioned, production-ready software that
|
|
422
|
+
# users can subscribe to and depend on.
|
|
423
|
+
release_pts = 0
|
|
424
|
+
total_releases = 0
|
|
425
|
+
for repo in top_repos[:5]:
|
|
426
|
+
try:
|
|
427
|
+
releases = client.get(f"/repos/{username}/{repo['name']}/releases",
|
|
428
|
+
params={"per_page": 5})
|
|
429
|
+
total_releases += len(releases)
|
|
430
|
+
except Exception:
|
|
431
|
+
pass
|
|
432
|
+
if total_releases == 0:
|
|
433
|
+
release_pts = 0
|
|
434
|
+
if top_repos:
|
|
435
|
+
findings.append(Finding(
|
|
436
|
+
"release_cadence", "low",
|
|
437
|
+
"No published releases",
|
|
438
|
+
"GitHub Releases make your project feel production-ready and "
|
|
439
|
+
"let users subscribe to new versions.",
|
|
440
|
+
"Tag your first release: git tag v0.1.0 && git push --tags, "
|
|
441
|
+
"then create a GitHub release with changelog notes.",
|
|
442
|
+
))
|
|
443
|
+
elif total_releases < 3:
|
|
444
|
+
release_pts = 3
|
|
445
|
+
findings.append(Finding(
|
|
446
|
+
"release_cadence", "low",
|
|
447
|
+
f"Only {total_releases} published release(s)",
|
|
448
|
+
"A release cadence signals active maintenance.",
|
|
449
|
+
"Aim for a release whenever you ship a meaningful change.",
|
|
450
|
+
))
|
|
451
|
+
else:
|
|
452
|
+
release_pts = 5
|
|
453
|
+
findings.append(Finding(
|
|
454
|
+
"release_cadence", "good",
|
|
455
|
+
f"{total_releases} published releases across top repos",
|
|
456
|
+
"Regular releases signal a maintained, production-quality project.",
|
|
457
|
+
))
|
|
458
|
+
breakdown["release_cadence"] = release_pts
|
|
459
|
+
|
|
460
|
+
# 11. Profile README signal (5 pts) -- NEW ---------------------------------
|
|
461
|
+
# A profile README (special repo username/username) is displayed at the
|
|
462
|
+
# top of the GitHub profile page - the highest-visibility real estate.
|
|
463
|
+
profile_readme_pts = 0
|
|
464
|
+
try:
|
|
465
|
+
raw = client.get(f"/repos/{username}/{username}/readme")
|
|
466
|
+
profile_text = base64.b64decode(raw["content"]).decode("utf-8", errors="replace")
|
|
467
|
+
word_count = len(profile_text.split())
|
|
468
|
+
if word_count >= 100:
|
|
469
|
+
profile_readme_pts = 5
|
|
470
|
+
findings.append(Finding(
|
|
471
|
+
"profile_readme", "good",
|
|
472
|
+
f"Profile README exists and is substantial ({word_count} words)",
|
|
473
|
+
"A profile README is the highest-visibility real estate on GitHub.",
|
|
474
|
+
))
|
|
475
|
+
else:
|
|
476
|
+
profile_readme_pts = 2
|
|
477
|
+
findings.append(Finding(
|
|
478
|
+
"profile_readme", "low",
|
|
479
|
+
f"Profile README is short ({word_count} words)",
|
|
480
|
+
"You have a profile README but it could work harder for you.",
|
|
481
|
+
"Add your current focus, top project links, and how to contact you.",
|
|
482
|
+
))
|
|
483
|
+
except Exception:
|
|
484
|
+
profile_readme_pts = 0
|
|
485
|
+
findings.append(Finding(
|
|
486
|
+
"profile_readme", "medium",
|
|
487
|
+
"No profile README",
|
|
488
|
+
f"A profile README (create repo: {username}/{username}) is the first "
|
|
489
|
+
"thing visitors see. Prime real estate for your personal brand.",
|
|
490
|
+
f"Create a repo named exactly '{username}' with a README.md showing "
|
|
491
|
+
"your focus, skills, and top projects.",
|
|
492
|
+
))
|
|
493
|
+
breakdown["profile_readme"] = profile_readme_pts
|
|
494
|
+
|
|
495
|
+
# Aggregate ----------------------------------------------------------------
|
|
424
496
|
total = sum(breakdown.values())
|
|
425
497
|
severity_order = {"critical": 0, "high": 1, "medium": 2, "low": 3, "good": 4}
|
|
426
498
|
findings.sort(key=lambda f: severity_order.get(f.severity, 5))
|