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.
Files changed (90) hide show
  1. {holt_cli-0.1.0 → holt_cli-0.2.0}/.gitignore +2 -0
  2. holt_cli-0.2.0/PKG-INFO +247 -0
  3. holt_cli-0.2.0/README.md +217 -0
  4. {holt_cli-0.1.0 → holt_cli-0.2.0}/pyproject.toml +38 -4
  5. holt_cli-0.2.0/src/holt/agent/asks.py +73 -0
  6. holt_cli-0.2.0/src/holt/agent/labels.py +93 -0
  7. {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/agent/landing.py +16 -9
  8. holt_cli-0.2.0/src/holt/agent/landing_detection.py +402 -0
  9. holt_cli-0.2.0/src/holt/agent/narration.py +194 -0
  10. holt_cli-0.2.0/src/holt/agent/people.py +210 -0
  11. holt_cli-0.2.0/src/holt/agent/pipeline.py +638 -0
  12. holt_cli-0.2.0/src/holt/agent/rates.py +306 -0
  13. holt_cli-0.2.0/src/holt/agent/replies.py +128 -0
  14. holt_cli-0.2.0/src/holt/agent/repo_kind_rules.py +408 -0
  15. holt_cli-0.2.0/src/holt/agent/signals.py +396 -0
  16. {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/agent/stages.py +240 -36
  17. holt_cli-0.2.0/src/holt/agent/verdict.py +519 -0
  18. holt_cli-0.2.0/src/holt/agent/verify.py +266 -0
  19. holt_cli-0.2.0/src/holt/cli.py +970 -0
  20. holt_cli-0.2.0/src/holt/credentials.py +116 -0
  21. {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/discover.py +74 -35
  22. holt_cli-0.2.0/src/holt/engine_version.py +14 -0
  23. holt_cli-0.2.0/src/holt/evidence/errors.py +50 -0
  24. {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/evidence/fixtures.py +18 -5
  25. holt_cli-0.2.0/src/holt/evidence/github_graphql.py +1043 -0
  26. {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/evidence/provider.py +6 -0
  27. {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/model.py +329 -81
  28. holt_cli-0.2.0/src/holt/models_help.py +18 -0
  29. holt_cli-0.2.0/src/holt/paths.py +61 -0
  30. {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/profile.py +6 -5
  31. holt_cli-0.2.0/src/holt/reponame.py +90 -0
  32. {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/report.py +84 -2
  33. holt_cli-0.2.0/src/holt/starter.py +927 -0
  34. {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/tui/app.py +54 -1
  35. {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/tui/commands.py +7 -2
  36. {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/tui/discovery.py +5 -7
  37. {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/tui/env.py +1 -1
  38. {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/tui/screens/assessment.py +28 -4
  39. {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/tui/screens/discover.py +8 -6
  40. holt_cli-0.2.0/src/holt/tui/screens/help.py +86 -0
  41. {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/tui/screens/home.py +53 -41
  42. {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/tui/screens/inspector.py +9 -0
  43. holt_cli-0.2.0/src/holt/tui/screens/token.py +91 -0
  44. {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/tui/session.py +65 -46
  45. {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/tui/store.py +11 -6
  46. {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/tui/theme.py +15 -0
  47. holt_cli-0.1.0/PKG-INFO +0 -198
  48. holt_cli-0.1.0/README.md +0 -168
  49. holt_cli-0.1.0/src/holt/agent/pipeline.py +0 -244
  50. holt_cli-0.1.0/src/holt/agent/signals.py +0 -220
  51. holt_cli-0.1.0/src/holt/agent/verdict.py +0 -226
  52. holt_cli-0.1.0/src/holt/agent/verify.py +0 -140
  53. holt_cli-0.1.0/src/holt/baseline.py +0 -89
  54. holt_cli-0.1.0/src/holt/baseline_matched.py +0 -116
  55. holt_cli-0.1.0/src/holt/cli.py +0 -616
  56. holt_cli-0.1.0/src/holt/evidence/github_graphql.py +0 -538
  57. {holt_cli-0.1.0 → holt_cli-0.2.0}/LICENSE +0 -0
  58. {holt_cli-0.1.0 → holt_cli-0.2.0}/NOTICE +0 -0
  59. {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/__init__.py +0 -0
  60. {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/agent/__init__.py +0 -0
  61. {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/agent/entry.py +0 -0
  62. {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/agent/findings.py +0 -0
  63. {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/agent/progression.py +0 -0
  64. {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/evidence/__init__.py +0 -0
  65. {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/evidence/redact.py +0 -0
  66. {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/issues.py +0 -0
  67. {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/tui/__init__.py +0 -0
  68. {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/tui/animation.py +0 -0
  69. {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/tui/clipboard.py +0 -0
  70. {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/tui/events.py +0 -0
  71. {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/tui/mascot.py +0 -0
  72. {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/tui/models.py +0 -0
  73. {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/tui/observe.py +0 -0
  74. {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/tui/screens/__init__.py +0 -0
  75. {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/tui/screens/confirm.py +0 -0
  76. {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/tui/screens/live.py +0 -0
  77. {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/tui/screens/models.py +0 -0
  78. {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/tui/screens/next_steps.py +0 -0
  79. {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/tui/screens/profile.py +0 -0
  80. {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/tui/visual.py +0 -0
  81. {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/tui/widgets/__init__.py +0 -0
  82. {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/tui/widgets/candidates.py +0 -0
  83. {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/tui/widgets/claims.py +0 -0
  84. {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/tui/widgets/disclosure.py +0 -0
  85. {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/tui/widgets/evidence.py +0 -0
  86. {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/tui/widgets/masthead.py +0 -0
  87. {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/tui/widgets/recent.py +0 -0
  88. {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/tui/widgets/scrolling.py +0 -0
  89. {holt_cli-0.1.0 → holt_cli-0.2.0}/src/holt/tui/widgets/stages.py +0 -0
  90. {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.
@@ -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
+ [![CI](https://github.com/holt-oss/holt/actions/workflows/ci.yml/badge.svg)](https://github.com/holt-oss/holt/actions/workflows/ci.yml)
34
+ [![PyPI](https://img.shields.io/pypi/v/holt-cli.svg?color=83a9ff)](https://pypi.org/project/holt-cli/)
35
+ [![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-69c7a6.svg)](https://github.com/holt-oss/holt/blob/main/pyproject.toml)
36
+ [![License: Apache-2.0](https://img.shields.io/badge/license-Apache--2.0-83a9ff.svg)](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
+ ![Holt answering for pallets/flask, then finding starter issues](https://raw.githubusercontent.com/holt-oss/holt/main/assets/demo.gif)
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
+ ![holt analyze pallets/flask in a terminal: Worth your time, what the evidence shows, where outsider work landed](https://raw.githubusercontent.com/holt-oss/holt/main/assets/cli-analyze.png)
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
+ ![The Holt terminal interface showing the assessment for pallets/flask](https://raw.githubusercontent.com/holt-oss/holt/main/assets/tui.png)
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
+ ![The Holt chip next to a repository name on GitHub, and Holt pick marks on the issue list](https://raw.githubusercontent.com/holt-oss/holt/main/extension/screenshots/desktop-light.png)
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
+ [![Holt](https://githolt.com/badge/owner/repo.svg)](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).
@@ -0,0 +1,217 @@
1
+ # Holt
2
+
3
+ [![CI](https://github.com/holt-oss/holt/actions/workflows/ci.yml/badge.svg)](https://github.com/holt-oss/holt/actions/workflows/ci.yml)
4
+ [![PyPI](https://img.shields.io/pypi/v/holt-cli.svg?color=83a9ff)](https://pypi.org/project/holt-cli/)
5
+ [![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-69c7a6.svg)](https://github.com/holt-oss/holt/blob/main/pyproject.toml)
6
+ [![License: Apache-2.0](https://img.shields.io/badge/license-Apache--2.0-83a9ff.svg)](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
+ ![Holt answering for pallets/flask, then finding starter issues](https://raw.githubusercontent.com/holt-oss/holt/main/assets/demo.gif)
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
+ ![holt analyze pallets/flask in a terminal: Worth your time, what the evidence shows, where outsider work landed](https://raw.githubusercontent.com/holt-oss/holt/main/assets/cli-analyze.png)
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
+ ![The Holt terminal interface showing the assessment for pallets/flask](https://raw.githubusercontent.com/holt-oss/holt/main/assets/tui.png)
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
+ ![The Holt chip next to a repository name on GitHub, and Holt pick marks on the issue list](https://raw.githubusercontent.com/holt-oss/holt/main/extension/screenshots/desktop-light.png)
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
+ [![Holt](https://githolt.com/badge/owner/repo.svg)](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.1.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://github.com/holt-oss/holt"
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 = ["pytest>=8.0"]
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]