holt-cli 0.1.0__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.
- {holt_cli-0.1.0 → holt_cli-0.2.0}/.gitignore +2 -0
- holt_cli-0.2.0/PKG-INFO +247 -0
- holt_cli-0.2.0/README.md +217 -0
- {holt_cli-0.1.0 → holt_cli-0.2.0}/pyproject.toml +38 -4
- holt_cli-0.2.0/src/holt/agent/asks.py +73 -0
- holt_cli-0.2.0/src/holt/agent/labels.py +93 -0
- {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/agent/landing.py +16 -9
- holt_cli-0.2.0/src/holt/agent/landing_detection.py +402 -0
- holt_cli-0.2.0/src/holt/agent/narration.py +194 -0
- holt_cli-0.2.0/src/holt/agent/people.py +210 -0
- holt_cli-0.2.0/src/holt/agent/pipeline.py +638 -0
- holt_cli-0.2.0/src/holt/agent/rates.py +306 -0
- holt_cli-0.2.0/src/holt/agent/replies.py +128 -0
- holt_cli-0.2.0/src/holt/agent/repo_kind_rules.py +408 -0
- holt_cli-0.2.0/src/holt/agent/signals.py +396 -0
- {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/agent/stages.py +240 -36
- holt_cli-0.2.0/src/holt/agent/verdict.py +519 -0
- holt_cli-0.2.0/src/holt/agent/verify.py +266 -0
- holt_cli-0.2.0/src/holt/cli.py +970 -0
- holt_cli-0.2.0/src/holt/credentials.py +116 -0
- {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/discover.py +74 -35
- holt_cli-0.2.0/src/holt/engine_version.py +14 -0
- holt_cli-0.2.0/src/holt/evidence/errors.py +50 -0
- {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/evidence/fixtures.py +18 -5
- holt_cli-0.2.0/src/holt/evidence/github_graphql.py +1043 -0
- {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/evidence/provider.py +6 -0
- {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/model.py +329 -81
- holt_cli-0.2.0/src/holt/models_help.py +18 -0
- holt_cli-0.2.0/src/holt/paths.py +61 -0
- {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/profile.py +6 -5
- holt_cli-0.2.0/src/holt/reponame.py +90 -0
- {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/report.py +84 -2
- holt_cli-0.2.0/src/holt/starter.py +927 -0
- {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/tui/app.py +54 -1
- {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/tui/commands.py +7 -2
- {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/tui/discovery.py +5 -7
- {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/tui/env.py +1 -1
- {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/tui/screens/assessment.py +28 -4
- {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/tui/screens/discover.py +8 -6
- holt_cli-0.2.0/src/holt/tui/screens/help.py +86 -0
- {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/tui/screens/home.py +53 -41
- {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/tui/screens/inspector.py +9 -0
- holt_cli-0.2.0/src/holt/tui/screens/token.py +91 -0
- {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/tui/session.py +65 -46
- {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/tui/store.py +11 -6
- {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/tui/theme.py +15 -0
- holt_cli-0.1.0/PKG-INFO +0 -198
- holt_cli-0.1.0/README.md +0 -168
- holt_cli-0.1.0/src/holt/agent/pipeline.py +0 -244
- holt_cli-0.1.0/src/holt/agent/signals.py +0 -220
- holt_cli-0.1.0/src/holt/agent/verdict.py +0 -226
- holt_cli-0.1.0/src/holt/agent/verify.py +0 -140
- holt_cli-0.1.0/src/holt/baseline.py +0 -89
- holt_cli-0.1.0/src/holt/baseline_matched.py +0 -116
- holt_cli-0.1.0/src/holt/cli.py +0 -616
- holt_cli-0.1.0/src/holt/evidence/github_graphql.py +0 -538
- {holt_cli-0.1.0 → holt_cli-0.2.0}/LICENSE +0 -0
- {holt_cli-0.1.0 → holt_cli-0.2.0}/NOTICE +0 -0
- {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/__init__.py +0 -0
- {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/agent/__init__.py +0 -0
- {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/agent/entry.py +0 -0
- {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/agent/findings.py +0 -0
- {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/agent/progression.py +0 -0
- {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/evidence/__init__.py +0 -0
- {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/evidence/redact.py +0 -0
- {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/issues.py +0 -0
- {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/tui/__init__.py +0 -0
- {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/tui/animation.py +0 -0
- {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/tui/clipboard.py +0 -0
- {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/tui/events.py +0 -0
- {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/tui/mascot.py +0 -0
- {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/tui/models.py +0 -0
- {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/tui/observe.py +0 -0
- {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/tui/screens/__init__.py +0 -0
- {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/tui/screens/confirm.py +0 -0
- {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/tui/screens/live.py +0 -0
- {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/tui/screens/models.py +0 -0
- {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/tui/screens/next_steps.py +0 -0
- {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/tui/screens/profile.py +0 -0
- {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/tui/visual.py +0 -0
- {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/tui/widgets/__init__.py +0 -0
- {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/tui/widgets/candidates.py +0 -0
- {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/tui/widgets/claims.py +0 -0
- {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/tui/widgets/disclosure.py +0 -0
- {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/tui/widgets/evidence.py +0 -0
- {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/tui/widgets/masthead.py +0 -0
- {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/tui/widgets/recent.py +0 -0
- {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/tui/widgets/scrolling.py +0 -0
- {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/tui/widgets/stages.py +0 -0
- {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/types.py +0 -0
|
@@ -14,6 +14,8 @@ node_modules/
|
|
|
14
14
|
# Worktrees are checkouts, not content. `git add -A` swept one in once.
|
|
15
15
|
.claude/worktrees/
|
|
16
16
|
.claude/settings.local.json
|
|
17
|
+
# Maintainer plugin choices (third-party marketplaces); copied into worktrees via .worktreeinclude
|
|
18
|
+
.claude/settings.json
|
|
17
19
|
|
|
18
20
|
# Live TUI runs record here, deliberately outside fixtures/trajectories/ so a
|
|
19
21
|
# session can never rewrite the recordings the eval harness replays.
|
holt_cli-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: holt-cli
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Evidence-backed assessment of whether a GitHub repository is a viable opportunity for an external contributor.
|
|
5
|
+
Project-URL: Homepage, https://githolt.com
|
|
6
|
+
Project-URL: Documentation, https://github.com/holt-oss/holt/tree/main/docs
|
|
7
|
+
Project-URL: Repository, https://github.com/holt-oss/holt.git
|
|
8
|
+
Project-URL: Issues, https://github.com/holt-oss/holt/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/holt-oss/holt/blob/main/CHANGELOG.md
|
|
10
|
+
Project-URL: Security, https://github.com/holt-oss/holt/security/policy
|
|
11
|
+
Author-email: Aahil Khan <aahilminookhan@gmail.com>
|
|
12
|
+
Maintainer-email: Aahil Khan <aahilminookhan@gmail.com>
|
|
13
|
+
License-Expression: Apache-2.0
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
License-File: NOTICE
|
|
16
|
+
Keywords: cli,contributors,github,open-source,repository-analysis
|
|
17
|
+
Classifier: Development Status :: 3 - Alpha
|
|
18
|
+
Classifier: Environment :: Console
|
|
19
|
+
Classifier: Intended Audience :: Developers
|
|
20
|
+
Classifier: Operating System :: OS Independent
|
|
21
|
+
Classifier: Programming Language :: Python :: 3
|
|
22
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
23
|
+
Classifier: Topic :: Software Development
|
|
24
|
+
Requires-Python: >=3.11
|
|
25
|
+
Requires-Dist: anthropic>=0.60
|
|
26
|
+
Requires-Dist: httpx>=0.27
|
|
27
|
+
Requires-Dist: openai>=3.6.0
|
|
28
|
+
Requires-Dist: textual>=0.80
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
# Holt
|
|
32
|
+
|
|
33
|
+
[](https://github.com/holt-oss/holt/actions/workflows/ci.yml)
|
|
34
|
+
[](https://pypi.org/project/holt-cli/)
|
|
35
|
+
[](https://github.com/holt-oss/holt/blob/main/pyproject.toml)
|
|
36
|
+
[](https://github.com/holt-oss/holt/blob/main/LICENSE)
|
|
37
|
+
|
|
38
|
+
**Find an open-source project that will actually merge your first PR.**
|
|
39
|
+
|
|
40
|
+
Paste a GitHub repository. Holt reads its recent pull requests and tells you,
|
|
41
|
+
in plain English, whether newcomers get replies, get merged, and where their
|
|
42
|
+
work lands. The answer is one of **Worth your time**, **Not worth your time**
|
|
43
|
+
or **Not enough evidence**, with links to the pull requests behind it.
|
|
44
|
+
|
|
45
|
+

|
|
46
|
+
|
|
47
|
+
<!-- HOLT_SITE_URL: the product lives at https://githolt.com -->
|
|
48
|
+
**[Try it in your browser](https://githolt.com)** · no install, no
|
|
49
|
+
account · [Install the CLI](#2-the-command-line) ·
|
|
50
|
+
[Get the browser extension](#3-the-browser-extension)
|
|
51
|
+
|
|
52
|
+
Holt is read-only. It never opens pull requests, posts comments or contacts
|
|
53
|
+
maintainers, on any surface.
|
|
54
|
+
|
|
55
|
+
## Three ways to use it
|
|
56
|
+
|
|
57
|
+
### 1. The web app
|
|
58
|
+
|
|
59
|
+
<!-- HOLT_SITE_URL -->
|
|
60
|
+
Open **<https://githolt.com>** and paste a repository. Or take any GitHub
|
|
61
|
+
link and **swap hub for holt**: `github.com` becomes `githolt.com`, and you
|
|
62
|
+
land on the report.
|
|
63
|
+
|
|
64
|
+
```text
|
|
65
|
+
https://github.com/home-assistant/core
|
|
66
|
+
https://githolt.com/home-assistant/core
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
No repository in mind? **Find a project** asks which languages you read and how
|
|
70
|
+
much time you have, then lists welcoming repositories with open starter issues.
|
|
71
|
+
|
|
72
|
+
<table>
|
|
73
|
+
<tr>
|
|
74
|
+
<td><img src="https://raw.githubusercontent.com/holt-oss/holt/main/assets/web-report-light-desktop.jpg" alt="The report for home-assistant/core: Worth your time, with the counts behind it and where to start" width="640"></td>
|
|
75
|
+
<td><img src="https://raw.githubusercontent.com/holt-oss/holt/main/assets/web-find-dark-phone.jpg" alt="Find a project on a phone: pick languages and time, get welcoming repositories" width="200"></td>
|
|
76
|
+
</tr>
|
|
77
|
+
</table>
|
|
78
|
+
|
|
79
|
+
### 2. The command line
|
|
80
|
+
|
|
81
|
+
The same engine runs on your machine, from PyPI. It needs Python 3.11 or newer;
|
|
82
|
+
use whichever installer you have (all three work in PowerShell too):
|
|
83
|
+
|
|
84
|
+
```sh
|
|
85
|
+
pipx install holt-cli
|
|
86
|
+
uv tool install holt-cli
|
|
87
|
+
pip install --user holt-cli
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Give it a GitHub token once. Holt reads only public data, so
|
|
91
|
+
[create a token](https://github.com/settings/tokens/new?description=holt) with
|
|
92
|
+
every box unticked, then run `holt token` and paste it. If you use the GitHub
|
|
93
|
+
CLI, Holt picks up `gh auth token` and you can skip this.
|
|
94
|
+
|
|
95
|
+
```sh
|
|
96
|
+
holt start --lang python # starter issues in repositories that merge newcomers
|
|
97
|
+
holt analyze home-assistant/core # is this one worth your time?
|
|
98
|
+
holt # the interactive terminal interface
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
No AI key is needed. Without one you get the rules-only report: the answer and
|
|
102
|
+
the numbers behind it, free. For a written explanation that quotes the threads
|
|
103
|
+
it relied on, `holt models` sets up a model; Gemini's free tier is the cheapest
|
|
104
|
+
start.
|
|
105
|
+
|
|
106
|
+

|
|
107
|
+
|
|
108
|
+
| Command | Purpose |
|
|
109
|
+
|---|---|
|
|
110
|
+
| `holt analyze <owner/repo>` | Assess one repository. `--days N` for the time you have, `--json` for machine-readable output. |
|
|
111
|
+
| `holt start` | Starter issues, across repositories (`--lang`, `--topic`, `--hacktoberfest`) or in one. |
|
|
112
|
+
| `holt compare <repo>…` | Several repositories side by side. |
|
|
113
|
+
| `holt next <repo> --as <login>` | After your first merge: open issues near the files you already changed. |
|
|
114
|
+
| `holt profile`, `holt discover --live` | Say what you want to work on, then search for it. |
|
|
115
|
+
| `holt token`, `holt models` | Save a GitHub token; optionally set up a model. |
|
|
116
|
+
|
|
117
|
+
**The terminal interface.** Bare `holt` opens it: type a repository, watch the
|
|
118
|
+
run, open any piece of evidence on GitHub with `o`, and come back to past
|
|
119
|
+
assessments. Press `?` on any screen for help.
|
|
120
|
+
|
|
121
|
+

|
|
122
|
+
|
|
123
|
+
[docs/USAGE.md](https://github.com/holt-oss/holt/blob/main/docs/USAGE.md)
|
|
124
|
+
walks through the whole workflow and
|
|
125
|
+
[docs/COMMANDS.md](https://github.com/holt-oss/holt/blob/main/docs/COMMANDS.md)
|
|
126
|
+
lists every flag.
|
|
127
|
+
|
|
128
|
+
### 3. The browser extension
|
|
129
|
+
|
|
130
|
+
**Holt for GitHub** adds a small chip next to the repository name on
|
|
131
|
+
github.com (**Holt: Worth your time · 15 of 100 newcomer PRs merged**) and
|
|
132
|
+
marks the issues Holt would pick first. Clicking the chip opens the full
|
|
133
|
+
report. It sends only the `owner/repo` you are looking at, keeps nothing, and
|
|
134
|
+
never writes to GitHub.
|
|
135
|
+
|
|
136
|
+

|
|
137
|
+
|
|
138
|
+
It is not on the extension stores yet. To try it, build it and load it
|
|
139
|
+
unpacked in Chrome, Edge, Brave or Firefox:
|
|
140
|
+
[extension/README.md](https://github.com/holt-oss/holt/blob/main/extension/README.md).
|
|
141
|
+
|
|
142
|
+
## Hacktoberfest
|
|
143
|
+
|
|
144
|
+
Every October the site has a seasonal page, **[/hacktoberfest](https://githolt.com/hacktoberfest)**:
|
|
145
|
+
repositories taking part that actually merge newcomers' work, with starter
|
|
146
|
+
issues by language, and a few tips so your pull request doesn't get ignored.
|
|
147
|
+
From the command line:
|
|
148
|
+
|
|
149
|
+
```sh
|
|
150
|
+
holt start --hacktoberfest --lang python
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## For maintainers: a README badge
|
|
154
|
+
|
|
155
|
+
Show newcomers they are welcome. Paste this into your README, with your own
|
|
156
|
+
`owner/repo`:
|
|
157
|
+
|
|
158
|
+
```markdown
|
|
159
|
+
[](https://githolt.com/owner/repo)
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
The badge shows Holt's current answer for your repository and links to the
|
|
163
|
+
report. It updates when the report does; you never have to touch it again.
|
|
164
|
+
|
|
165
|
+
## How the verdict works
|
|
166
|
+
|
|
167
|
+
| Answer | Meaning |
|
|
168
|
+
|---|---|
|
|
169
|
+
| **Worth your time** | Outsiders get replies and land real work here, fast enough for the time you have. |
|
|
170
|
+
| **Not worth your time** | The record says a newcomer's week is unlikely to go anywhere here. |
|
|
171
|
+
| **Not enough evidence** | Too little outside activity to call it either way. That is an answer, not an error. |
|
|
172
|
+
|
|
173
|
+
**Rules decide. AI only explains.** The answer comes from fixed rules applied
|
|
174
|
+
to counted evidence: who tried, who got merged, how fast the first reply came,
|
|
175
|
+
and whether anyone actually reviewed the work. An AI model can add a written
|
|
176
|
+
explanation that quotes the threads it relied on, and every claim it makes is
|
|
177
|
+
checked against the record before you see it. Unsupported claims are dropped.
|
|
178
|
+
The model never chooses the answer.
|
|
179
|
+
[docs/DESIGN.md](https://github.com/holt-oss/holt/blob/main/docs/DESIGN.md)
|
|
180
|
+
has the argument.
|
|
181
|
+
|
|
182
|
+
## Where it doesn't work
|
|
183
|
+
|
|
184
|
+
Holt is a filter, not an oracle. Know the limits:
|
|
185
|
+
|
|
186
|
+
- **It isn't always right.** Open the linked pull requests before you commit
|
|
187
|
+
a week; the evidence is there so you can check it yourself.
|
|
188
|
+
- **It reads the past.** Repository cultures change, and a quiet month can
|
|
189
|
+
look worse than it is.
|
|
190
|
+
- **It is about your time, not their quality.** A superb project with a deep
|
|
191
|
+
review queue can still be a poor place for a first pull request.
|
|
192
|
+
- **Counts, not conversations.** Without a model, Holt can't tell you what a
|
|
193
|
+
thread said or who was welcoming, only what happened.
|
|
194
|
+
|
|
195
|
+
Holt started as a benchmarked competition entry; that evaluation is now
|
|
196
|
+
historical research rather than a live product claim.
|
|
197
|
+
[docs/research/EVALUATION.md](https://github.com/holt-oss/holt/blob/main/docs/research/EVALUATION.md)
|
|
198
|
+
has the design and the full numbers, and
|
|
199
|
+
[docs/research/REPRODUCTION.md](https://github.com/holt-oss/holt/blob/main/docs/research/REPRODUCTION.md)
|
|
200
|
+
reproduces them from a clone with no API key, no token and no money.
|
|
201
|
+
|
|
202
|
+
## The repository
|
|
203
|
+
|
|
204
|
+
| Path | What |
|
|
205
|
+
|---|---|
|
|
206
|
+
| `src/holt/` | The engine, the CLI and the terminal interface (`holt-cli` on PyPI) |
|
|
207
|
+
| `web/` | The web app |
|
|
208
|
+
| `server/` | The HTTP API the web app calls |
|
|
209
|
+
| `extension/` | The browser extension |
|
|
210
|
+
| `docs/` | Everything else, starting from [docs/README.md](https://github.com/holt-oss/holt/blob/main/docs/README.md) |
|
|
211
|
+
|
|
212
|
+
## Contributing
|
|
213
|
+
|
|
214
|
+
Holt is small, and one maintainer runs it. First pull requests are welcome.
|
|
215
|
+
[CONTRIBUTING.md](https://github.com/holt-oss/holt/blob/main/CONTRIBUTING.md)
|
|
216
|
+
starts with a 15-minute path to your first one, and issues labelled
|
|
217
|
+
[good first issue](https://github.com/holt-oss/holt/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22)
|
|
218
|
+
are sized for it. Participation is governed by the
|
|
219
|
+
[Code of Conduct](https://github.com/holt-oss/holt/blob/main/CODE_OF_CONDUCT.md);
|
|
220
|
+
security reports follow
|
|
221
|
+
[SECURITY.md](https://github.com/holt-oss/holt/blob/main/SECURITY.md).
|
|
222
|
+
|
|
223
|
+
Working on Holt regularly? [docs/DEV-WORKFLOW.md](https://github.com/holt-oss/holt/blob/main/docs/DEV-WORKFLOW.md)
|
|
224
|
+
is the one-page guide: run the web app locally, preview a branch on staging,
|
|
225
|
+
and what CI and deploys expect.
|
|
226
|
+
|
|
227
|
+
> Holt started as the winner of **Most useful real-world workflow** at the
|
|
228
|
+
> micro1 Frontier Engineering Challenge.
|
|
229
|
+
|
|
230
|
+
## Licensing
|
|
231
|
+
|
|
232
|
+
Holt uses two licenses, split by directory:
|
|
233
|
+
|
|
234
|
+
- **The engine, CLI, terminal interface and browser extension**
|
|
235
|
+
(`src/holt/`, `extension/`, everything else at the repo root) are
|
|
236
|
+
[Apache License 2.0](https://github.com/holt-oss/holt/blob/main/LICENSE).
|
|
237
|
+
Install the CLI from PyPI, embed the engine, or fork it under Apache terms.
|
|
238
|
+
- **The web app and its server** (`web/`, `server/`) are
|
|
239
|
+
[GNU AGPL-3.0](https://github.com/holt-oss/holt/blob/main/web/LICENSE). If
|
|
240
|
+
you run a modified version of the web app or server as a network service,
|
|
241
|
+
the AGPL requires you to make your modified source available to the
|
|
242
|
+
people using it.
|
|
243
|
+
|
|
244
|
+
Apache-licensed contributions from before the split remain Apache-2.0 and
|
|
245
|
+
are compatible with the AGPL side, so nothing already in the project needed
|
|
246
|
+
anyone's permission to move. If you're contributing new code, see
|
|
247
|
+
[CONTRIBUTING.md](https://github.com/holt-oss/holt/blob/main/CONTRIBUTING.md#licensing).
|
holt_cli-0.2.0/README.md
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
# Holt
|
|
2
|
+
|
|
3
|
+
[](https://github.com/holt-oss/holt/actions/workflows/ci.yml)
|
|
4
|
+
[](https://pypi.org/project/holt-cli/)
|
|
5
|
+
[](https://github.com/holt-oss/holt/blob/main/pyproject.toml)
|
|
6
|
+
[](https://github.com/holt-oss/holt/blob/main/LICENSE)
|
|
7
|
+
|
|
8
|
+
**Find an open-source project that will actually merge your first PR.**
|
|
9
|
+
|
|
10
|
+
Paste a GitHub repository. Holt reads its recent pull requests and tells you,
|
|
11
|
+
in plain English, whether newcomers get replies, get merged, and where their
|
|
12
|
+
work lands. The answer is one of **Worth your time**, **Not worth your time**
|
|
13
|
+
or **Not enough evidence**, with links to the pull requests behind it.
|
|
14
|
+
|
|
15
|
+

|
|
16
|
+
|
|
17
|
+
<!-- HOLT_SITE_URL: the product lives at https://githolt.com -->
|
|
18
|
+
**[Try it in your browser](https://githolt.com)** · no install, no
|
|
19
|
+
account · [Install the CLI](#2-the-command-line) ·
|
|
20
|
+
[Get the browser extension](#3-the-browser-extension)
|
|
21
|
+
|
|
22
|
+
Holt is read-only. It never opens pull requests, posts comments or contacts
|
|
23
|
+
maintainers, on any surface.
|
|
24
|
+
|
|
25
|
+
## Three ways to use it
|
|
26
|
+
|
|
27
|
+
### 1. The web app
|
|
28
|
+
|
|
29
|
+
<!-- HOLT_SITE_URL -->
|
|
30
|
+
Open **<https://githolt.com>** and paste a repository. Or take any GitHub
|
|
31
|
+
link and **swap hub for holt**: `github.com` becomes `githolt.com`, and you
|
|
32
|
+
land on the report.
|
|
33
|
+
|
|
34
|
+
```text
|
|
35
|
+
https://github.com/home-assistant/core
|
|
36
|
+
https://githolt.com/home-assistant/core
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
No repository in mind? **Find a project** asks which languages you read and how
|
|
40
|
+
much time you have, then lists welcoming repositories with open starter issues.
|
|
41
|
+
|
|
42
|
+
<table>
|
|
43
|
+
<tr>
|
|
44
|
+
<td><img src="https://raw.githubusercontent.com/holt-oss/holt/main/assets/web-report-light-desktop.jpg" alt="The report for home-assistant/core: Worth your time, with the counts behind it and where to start" width="640"></td>
|
|
45
|
+
<td><img src="https://raw.githubusercontent.com/holt-oss/holt/main/assets/web-find-dark-phone.jpg" alt="Find a project on a phone: pick languages and time, get welcoming repositories" width="200"></td>
|
|
46
|
+
</tr>
|
|
47
|
+
</table>
|
|
48
|
+
|
|
49
|
+
### 2. The command line
|
|
50
|
+
|
|
51
|
+
The same engine runs on your machine, from PyPI. It needs Python 3.11 or newer;
|
|
52
|
+
use whichever installer you have (all three work in PowerShell too):
|
|
53
|
+
|
|
54
|
+
```sh
|
|
55
|
+
pipx install holt-cli
|
|
56
|
+
uv tool install holt-cli
|
|
57
|
+
pip install --user holt-cli
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Give it a GitHub token once. Holt reads only public data, so
|
|
61
|
+
[create a token](https://github.com/settings/tokens/new?description=holt) with
|
|
62
|
+
every box unticked, then run `holt token` and paste it. If you use the GitHub
|
|
63
|
+
CLI, Holt picks up `gh auth token` and you can skip this.
|
|
64
|
+
|
|
65
|
+
```sh
|
|
66
|
+
holt start --lang python # starter issues in repositories that merge newcomers
|
|
67
|
+
holt analyze home-assistant/core # is this one worth your time?
|
|
68
|
+
holt # the interactive terminal interface
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
No AI key is needed. Without one you get the rules-only report: the answer and
|
|
72
|
+
the numbers behind it, free. For a written explanation that quotes the threads
|
|
73
|
+
it relied on, `holt models` sets up a model; Gemini's free tier is the cheapest
|
|
74
|
+
start.
|
|
75
|
+
|
|
76
|
+

|
|
77
|
+
|
|
78
|
+
| Command | Purpose |
|
|
79
|
+
|---|---|
|
|
80
|
+
| `holt analyze <owner/repo>` | Assess one repository. `--days N` for the time you have, `--json` for machine-readable output. |
|
|
81
|
+
| `holt start` | Starter issues, across repositories (`--lang`, `--topic`, `--hacktoberfest`) or in one. |
|
|
82
|
+
| `holt compare <repo>…` | Several repositories side by side. |
|
|
83
|
+
| `holt next <repo> --as <login>` | After your first merge: open issues near the files you already changed. |
|
|
84
|
+
| `holt profile`, `holt discover --live` | Say what you want to work on, then search for it. |
|
|
85
|
+
| `holt token`, `holt models` | Save a GitHub token; optionally set up a model. |
|
|
86
|
+
|
|
87
|
+
**The terminal interface.** Bare `holt` opens it: type a repository, watch the
|
|
88
|
+
run, open any piece of evidence on GitHub with `o`, and come back to past
|
|
89
|
+
assessments. Press `?` on any screen for help.
|
|
90
|
+
|
|
91
|
+

|
|
92
|
+
|
|
93
|
+
[docs/USAGE.md](https://github.com/holt-oss/holt/blob/main/docs/USAGE.md)
|
|
94
|
+
walks through the whole workflow and
|
|
95
|
+
[docs/COMMANDS.md](https://github.com/holt-oss/holt/blob/main/docs/COMMANDS.md)
|
|
96
|
+
lists every flag.
|
|
97
|
+
|
|
98
|
+
### 3. The browser extension
|
|
99
|
+
|
|
100
|
+
**Holt for GitHub** adds a small chip next to the repository name on
|
|
101
|
+
github.com (**Holt: Worth your time · 15 of 100 newcomer PRs merged**) and
|
|
102
|
+
marks the issues Holt would pick first. Clicking the chip opens the full
|
|
103
|
+
report. It sends only the `owner/repo` you are looking at, keeps nothing, and
|
|
104
|
+
never writes to GitHub.
|
|
105
|
+
|
|
106
|
+

|
|
107
|
+
|
|
108
|
+
It is not on the extension stores yet. To try it, build it and load it
|
|
109
|
+
unpacked in Chrome, Edge, Brave or Firefox:
|
|
110
|
+
[extension/README.md](https://github.com/holt-oss/holt/blob/main/extension/README.md).
|
|
111
|
+
|
|
112
|
+
## Hacktoberfest
|
|
113
|
+
|
|
114
|
+
Every October the site has a seasonal page, **[/hacktoberfest](https://githolt.com/hacktoberfest)**:
|
|
115
|
+
repositories taking part that actually merge newcomers' work, with starter
|
|
116
|
+
issues by language, and a few tips so your pull request doesn't get ignored.
|
|
117
|
+
From the command line:
|
|
118
|
+
|
|
119
|
+
```sh
|
|
120
|
+
holt start --hacktoberfest --lang python
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## For maintainers: a README badge
|
|
124
|
+
|
|
125
|
+
Show newcomers they are welcome. Paste this into your README, with your own
|
|
126
|
+
`owner/repo`:
|
|
127
|
+
|
|
128
|
+
```markdown
|
|
129
|
+
[](https://githolt.com/owner/repo)
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
The badge shows Holt's current answer for your repository and links to the
|
|
133
|
+
report. It updates when the report does; you never have to touch it again.
|
|
134
|
+
|
|
135
|
+
## How the verdict works
|
|
136
|
+
|
|
137
|
+
| Answer | Meaning |
|
|
138
|
+
|---|---|
|
|
139
|
+
| **Worth your time** | Outsiders get replies and land real work here, fast enough for the time you have. |
|
|
140
|
+
| **Not worth your time** | The record says a newcomer's week is unlikely to go anywhere here. |
|
|
141
|
+
| **Not enough evidence** | Too little outside activity to call it either way. That is an answer, not an error. |
|
|
142
|
+
|
|
143
|
+
**Rules decide. AI only explains.** The answer comes from fixed rules applied
|
|
144
|
+
to counted evidence: who tried, who got merged, how fast the first reply came,
|
|
145
|
+
and whether anyone actually reviewed the work. An AI model can add a written
|
|
146
|
+
explanation that quotes the threads it relied on, and every claim it makes is
|
|
147
|
+
checked against the record before you see it. Unsupported claims are dropped.
|
|
148
|
+
The model never chooses the answer.
|
|
149
|
+
[docs/DESIGN.md](https://github.com/holt-oss/holt/blob/main/docs/DESIGN.md)
|
|
150
|
+
has the argument.
|
|
151
|
+
|
|
152
|
+
## Where it doesn't work
|
|
153
|
+
|
|
154
|
+
Holt is a filter, not an oracle. Know the limits:
|
|
155
|
+
|
|
156
|
+
- **It isn't always right.** Open the linked pull requests before you commit
|
|
157
|
+
a week; the evidence is there so you can check it yourself.
|
|
158
|
+
- **It reads the past.** Repository cultures change, and a quiet month can
|
|
159
|
+
look worse than it is.
|
|
160
|
+
- **It is about your time, not their quality.** A superb project with a deep
|
|
161
|
+
review queue can still be a poor place for a first pull request.
|
|
162
|
+
- **Counts, not conversations.** Without a model, Holt can't tell you what a
|
|
163
|
+
thread said or who was welcoming, only what happened.
|
|
164
|
+
|
|
165
|
+
Holt started as a benchmarked competition entry; that evaluation is now
|
|
166
|
+
historical research rather than a live product claim.
|
|
167
|
+
[docs/research/EVALUATION.md](https://github.com/holt-oss/holt/blob/main/docs/research/EVALUATION.md)
|
|
168
|
+
has the design and the full numbers, and
|
|
169
|
+
[docs/research/REPRODUCTION.md](https://github.com/holt-oss/holt/blob/main/docs/research/REPRODUCTION.md)
|
|
170
|
+
reproduces them from a clone with no API key, no token and no money.
|
|
171
|
+
|
|
172
|
+
## The repository
|
|
173
|
+
|
|
174
|
+
| Path | What |
|
|
175
|
+
|---|---|
|
|
176
|
+
| `src/holt/` | The engine, the CLI and the terminal interface (`holt-cli` on PyPI) |
|
|
177
|
+
| `web/` | The web app |
|
|
178
|
+
| `server/` | The HTTP API the web app calls |
|
|
179
|
+
| `extension/` | The browser extension |
|
|
180
|
+
| `docs/` | Everything else, starting from [docs/README.md](https://github.com/holt-oss/holt/blob/main/docs/README.md) |
|
|
181
|
+
|
|
182
|
+
## Contributing
|
|
183
|
+
|
|
184
|
+
Holt is small, and one maintainer runs it. First pull requests are welcome.
|
|
185
|
+
[CONTRIBUTING.md](https://github.com/holt-oss/holt/blob/main/CONTRIBUTING.md)
|
|
186
|
+
starts with a 15-minute path to your first one, and issues labelled
|
|
187
|
+
[good first issue](https://github.com/holt-oss/holt/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22)
|
|
188
|
+
are sized for it. Participation is governed by the
|
|
189
|
+
[Code of Conduct](https://github.com/holt-oss/holt/blob/main/CODE_OF_CONDUCT.md);
|
|
190
|
+
security reports follow
|
|
191
|
+
[SECURITY.md](https://github.com/holt-oss/holt/blob/main/SECURITY.md).
|
|
192
|
+
|
|
193
|
+
Working on Holt regularly? [docs/DEV-WORKFLOW.md](https://github.com/holt-oss/holt/blob/main/docs/DEV-WORKFLOW.md)
|
|
194
|
+
is the one-page guide: run the web app locally, preview a branch on staging,
|
|
195
|
+
and what CI and deploys expect.
|
|
196
|
+
|
|
197
|
+
> Holt started as the winner of **Most useful real-world workflow** at the
|
|
198
|
+
> micro1 Frontier Engineering Challenge.
|
|
199
|
+
|
|
200
|
+
## Licensing
|
|
201
|
+
|
|
202
|
+
Holt uses two licenses, split by directory:
|
|
203
|
+
|
|
204
|
+
- **The engine, CLI, terminal interface and browser extension**
|
|
205
|
+
(`src/holt/`, `extension/`, everything else at the repo root) are
|
|
206
|
+
[Apache License 2.0](https://github.com/holt-oss/holt/blob/main/LICENSE).
|
|
207
|
+
Install the CLI from PyPI, embed the engine, or fork it under Apache terms.
|
|
208
|
+
- **The web app and its server** (`web/`, `server/`) are
|
|
209
|
+
[GNU AGPL-3.0](https://github.com/holt-oss/holt/blob/main/web/LICENSE). If
|
|
210
|
+
you run a modified version of the web app or server as a network service,
|
|
211
|
+
the AGPL requires you to make your modified source available to the
|
|
212
|
+
people using it.
|
|
213
|
+
|
|
214
|
+
Apache-licensed contributions from before the split remain Apache-2.0 and
|
|
215
|
+
are compatible with the AGPL side, so nothing already in the project needed
|
|
216
|
+
anyone's permission to move. If you're contributing new code, see
|
|
217
|
+
[CONTRIBUTING.md](https://github.com/holt-oss/holt/blob/main/CONTRIBUTING.md#licensing).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "holt-cli"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.2.0"
|
|
4
4
|
description = "Evidence-backed assessment of whether a GitHub repository is a viable opportunity for an external contributor."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.11"
|
|
@@ -39,7 +39,7 @@ dependencies = [
|
|
|
39
39
|
holt = "holt.cli:main"
|
|
40
40
|
|
|
41
41
|
[project.urls]
|
|
42
|
-
Homepage = "https://
|
|
42
|
+
Homepage = "https://githolt.com"
|
|
43
43
|
Documentation = "https://github.com/holt-oss/holt/tree/main/docs"
|
|
44
44
|
Repository = "https://github.com/holt-oss/holt.git"
|
|
45
45
|
Issues = "https://github.com/holt-oss/holt/issues"
|
|
@@ -47,7 +47,17 @@ Changelog = "https://github.com/holt-oss/holt/blob/main/CHANGELOG.md"
|
|
|
47
47
|
Security = "https://github.com/holt-oss/holt/security/policy"
|
|
48
48
|
|
|
49
49
|
[dependency-groups]
|
|
50
|
-
dev
|
|
50
|
+
# holt-server is the web API in server/. It rides in the dev group so one
|
|
51
|
+
# `uv sync` gives a working tree for both, and its tests run with the rest.
|
|
52
|
+
# Dependency groups are never published, so the PyPI package is unaffected.
|
|
53
|
+
# pytest-xdist runs the suite across cores: `uv run pytest -n auto` (CI does).
|
|
54
|
+
dev = ["pytest>=8.0", "pytest-xdist>=3.8", "holt-server"]
|
|
55
|
+
|
|
56
|
+
[tool.uv.workspace]
|
|
57
|
+
members = ["server"]
|
|
58
|
+
|
|
59
|
+
[tool.uv.sources]
|
|
60
|
+
holt-server = { workspace = true }
|
|
51
61
|
|
|
52
62
|
[build-system]
|
|
53
63
|
requires = ["hatchling"]
|
|
@@ -63,6 +73,30 @@ packages = ["src/holt"]
|
|
|
63
73
|
only-include = ["src/holt"]
|
|
64
74
|
|
|
65
75
|
[tool.pytest.ini_options]
|
|
66
|
-
testpaths = ["tests", "eval"]
|
|
76
|
+
testpaths = ["tests", "eval", "server/tests"]
|
|
67
77
|
# eval/ lives at the repo root, outside the installed src/holt package.
|
|
68
78
|
pythonpath = ["."]
|
|
79
|
+
|
|
80
|
+
[tool.ruff]
|
|
81
|
+
target-version = "py311"
|
|
82
|
+
extend-exclude = ["fixtures", "trajectories", "web", "extension", "e2e", "website"]
|
|
83
|
+
|
|
84
|
+
[tool.ruff.lint]
|
|
85
|
+
# Ruff's classic core set: syntax errors, undefined or unused names, and a few
|
|
86
|
+
# statement-level mistakes. Widen it one rule family at a time, fixing as you go.
|
|
87
|
+
select = ["E4", "E7", "E9", "F"]
|
|
88
|
+
|
|
89
|
+
[tool.ruff.lint.per-file-ignores]
|
|
90
|
+
# eval/ and scripts/ are frozen research material from the competition.
|
|
91
|
+
"eval/**" = ["E7", "F541", "F401", "F841"]
|
|
92
|
+
"scripts/**" = ["E741"]
|
|
93
|
+
# Baseline: findings present when ruff was introduced. Each is a follow-up;
|
|
94
|
+
# remove the entry once the file is clean.
|
|
95
|
+
"src/holt/discover.py" = ["F841"]
|
|
96
|
+
"src/holt/tui/screens/assessment.py" = ["F541"]
|
|
97
|
+
"src/holt/tui/screens/models.py" = ["F401"]
|
|
98
|
+
"tests/test_discover.py" = ["F401"]
|
|
99
|
+
"tests/test_evidence_bounds.py" = ["F401", "F811"]
|
|
100
|
+
"tests/test_models.py" = ["F401"]
|
|
101
|
+
"tests/test_tui_discovery.py" = ["F401"]
|
|
102
|
+
"tests/test_tui_observe.py" = ["F401", "E402"]
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""What a project asks of a contributor before it will take a pull request.
|
|
2
|
+
|
|
3
|
+
Read from evidence Holt already has, and only where it is unambiguous:
|
|
4
|
+
|
|
5
|
+
- a CLA (Contributor License Agreement): a CLA bot commented on outside pull
|
|
6
|
+
requests in the sample. Not from CONTRIBUTING: free-programming-books has a
|
|
7
|
+
"Contributor License Agreement" heading that only means "you agree to the
|
|
8
|
+
licence", with nothing to sign;
|
|
9
|
+
- a DCO sign-off: CONTRIBUTING names the "Developer Certificate of Origin";
|
|
10
|
+
- an issue first: CONTRIBUTING says, in so many words, to open an issue or
|
|
11
|
+
discuss the change before sending a pull request.
|
|
12
|
+
|
|
13
|
+
These never touch the verdict. They are advice for the next step, and each
|
|
14
|
+
one links to where it was read so the reader can check it. Where nothing is
|
|
15
|
+
found, nothing is said: the absence of a match is not a claim that the project
|
|
16
|
+
asks for nothing.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import re
|
|
22
|
+
from collections.abc import Iterable
|
|
23
|
+
from dataclasses import dataclass
|
|
24
|
+
|
|
25
|
+
from holt.types import EvidenceRecord
|
|
26
|
+
|
|
27
|
+
# CLA bots seen on the golden set: linux-foundation-easycla, google-cla,
|
|
28
|
+
# meta-cla, python-cla-bot, CLAassistant. Whole-word "cla", so `cclauss` and
|
|
29
|
+
# `clarfonthey` are people.
|
|
30
|
+
_CLA_BOT = re.compile(r"easycla|claassistant|cla-assistant|(?:^|[-_])cla(?:[-_](?:bot|checker|assistant))?(?:\[bot\])?$",
|
|
31
|
+
re.I)
|
|
32
|
+
_DCO_DOC = re.compile(r"developer\s+certificate\s+of\s+origin", re.I)
|
|
33
|
+
_ISSUE_FIRST_DOC = re.compile(
|
|
34
|
+
r"\b(?:open|file|create|raise)\s+an\s+issue\s+first\b"
|
|
35
|
+
r"|\bdiscuss\s+(?:it|your\s+(?:idea|change|proposal)|the\s+change|this)\s+first\b"
|
|
36
|
+
r"|\bbefore\s+(?:opening|submitting|sending|starting)\s+(?:a|your|any)\s+(?:pull\s+request|PR)"
|
|
37
|
+
r"[^.\n]{0,40}?\b(?:open|file|create)\s+an\s+issue\b",
|
|
38
|
+
re.I,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass(frozen=True, slots=True)
|
|
43
|
+
class Ask:
|
|
44
|
+
code: str # "cla", "dco" or "issue_first"
|
|
45
|
+
url: str # where it was read: a bot's comment, or CONTRIBUTING
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def is_cla_bot(login: str | None) -> bool:
|
|
49
|
+
return bool(login) and bool(_CLA_BOT.search(login or ""))
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def read(records: Iterable[EvidenceRecord], outsider_keys: set[str] | None = None) -> list[Ask]:
|
|
53
|
+
"""Every ask found, CLA first. `outsider_keys` limits the CLA-bot check to
|
|
54
|
+
those pull requests (a bot greeting staff says nothing about outsiders)."""
|
|
55
|
+
records = list(records)
|
|
56
|
+
found: dict[str, Ask] = {}
|
|
57
|
+
for r in records:
|
|
58
|
+
if ":comment:" not in r.evidence_id and ":review:" not in r.evidence_id:
|
|
59
|
+
continue
|
|
60
|
+
key = ":".join(r.evidence_id.split(":")[:2])
|
|
61
|
+
if outsider_keys is not None and key not in outsider_keys:
|
|
62
|
+
continue
|
|
63
|
+
if is_cla_bot(r.payload.get("author")) and r.url:
|
|
64
|
+
found.setdefault("cla", Ask("cla", r.url))
|
|
65
|
+
break
|
|
66
|
+
doc = next((r for r in records if r.evidence_id.endswith(":contributing")), None)
|
|
67
|
+
text = (doc.payload.get("text") or "") if doc is not None else ""
|
|
68
|
+
if doc is not None and doc.url and text:
|
|
69
|
+
if _DCO_DOC.search(text):
|
|
70
|
+
found.setdefault("dco", Ask("dco", doc.url))
|
|
71
|
+
if _ISSUE_FIRST_DOC.search(text):
|
|
72
|
+
found.setdefault("issue_first", Ask("issue_first", doc.url))
|
|
73
|
+
return [found[c] for c in ("cla", "dco", "issue_first") if c in found]
|