holt-cli 0.1.0__py3-none-any.whl
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/__init__.py +0 -0
- holt/agent/__init__.py +0 -0
- holt/agent/entry.py +86 -0
- holt/agent/findings.py +49 -0
- holt/agent/landing.py +154 -0
- holt/agent/pipeline.py +244 -0
- holt/agent/progression.py +408 -0
- holt/agent/signals.py +220 -0
- holt/agent/stages.py +533 -0
- holt/agent/verdict.py +226 -0
- holt/agent/verify.py +140 -0
- holt/baseline.py +89 -0
- holt/baseline_matched.py +116 -0
- holt/cli.py +616 -0
- holt/discover.py +497 -0
- holt/evidence/__init__.py +3 -0
- holt/evidence/fixtures.py +154 -0
- holt/evidence/github_graphql.py +538 -0
- holt/evidence/provider.py +77 -0
- holt/evidence/redact.py +79 -0
- holt/issues.py +41 -0
- holt/model.py +516 -0
- holt/profile.py +126 -0
- holt/report.py +157 -0
- holt/tui/__init__.py +0 -0
- holt/tui/animation.py +84 -0
- holt/tui/app.py +294 -0
- holt/tui/clipboard.py +89 -0
- holt/tui/commands.py +134 -0
- holt/tui/discovery.py +305 -0
- holt/tui/env.py +49 -0
- holt/tui/events.py +245 -0
- holt/tui/mascot.py +121 -0
- holt/tui/models.py +590 -0
- holt/tui/observe.py +297 -0
- holt/tui/screens/__init__.py +35 -0
- holt/tui/screens/assessment.py +337 -0
- holt/tui/screens/confirm.py +62 -0
- holt/tui/screens/discover.py +444 -0
- holt/tui/screens/home.py +519 -0
- holt/tui/screens/inspector.py +106 -0
- holt/tui/screens/live.py +335 -0
- holt/tui/screens/models.py +393 -0
- holt/tui/screens/next_steps.py +425 -0
- holt/tui/screens/profile.py +129 -0
- holt/tui/session.py +711 -0
- holt/tui/store.py +458 -0
- holt/tui/theme.py +479 -0
- holt/tui/visual.py +33 -0
- holt/tui/widgets/__init__.py +0 -0
- holt/tui/widgets/candidates.py +78 -0
- holt/tui/widgets/claims.py +59 -0
- holt/tui/widgets/disclosure.py +121 -0
- holt/tui/widgets/evidence.py +121 -0
- holt/tui/widgets/masthead.py +122 -0
- holt/tui/widgets/recent.py +167 -0
- holt/tui/widgets/scrolling.py +38 -0
- holt/tui/widgets/stages.py +232 -0
- holt/types.py +48 -0
- holt_cli-0.1.0.dist-info/METADATA +198 -0
- holt_cli-0.1.0.dist-info/RECORD +65 -0
- holt_cli-0.1.0.dist-info/WHEEL +4 -0
- holt_cli-0.1.0.dist-info/entry_points.txt +2 -0
- holt_cli-0.1.0.dist-info/licenses/LICENSE +201 -0
- holt_cli-0.1.0.dist-info/licenses/NOTICE +4 -0
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
"""The stage list, and the moment Stage D removes a claim.
|
|
2
|
+
|
|
3
|
+
There is no progress bar here. A bar would have to invent a denominator — the
|
|
4
|
+
number of threads a stage will read is not known until it has read them — and a
|
|
5
|
+
bar that fills at a rate unrelated to the work is a lie told smoothly. What a
|
|
6
|
+
running stage shows instead is a spinner, which claims only that something is
|
|
7
|
+
happening, and an elapsed clock, which is a real number.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import time
|
|
13
|
+
|
|
14
|
+
from rich.text import Text
|
|
15
|
+
from textual.app import ComposeResult
|
|
16
|
+
from textual.containers import Vertical
|
|
17
|
+
from textual.widget import Widget
|
|
18
|
+
|
|
19
|
+
from holt.tui import animation, events, theme
|
|
20
|
+
from holt.tui.visual import Line
|
|
21
|
+
from holt.tui.widgets.evidence import cite
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class StageRow(Line):
|
|
25
|
+
"""One stage: a gutter mark, its name, and where it has got to.
|
|
26
|
+
|
|
27
|
+
Three states, and they are visually distinct without relying on colour:
|
|
28
|
+
pending is an em dash, running is a turning spinner and a clock, finished is
|
|
29
|
+
the stage's own one-line result.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(self, stage: str, **kwargs) -> None:
|
|
33
|
+
super().__init__(**kwargs)
|
|
34
|
+
self.stage = stage
|
|
35
|
+
self._model = ""
|
|
36
|
+
self._state = "pending"
|
|
37
|
+
self._status = ""
|
|
38
|
+
self._started = 0.0
|
|
39
|
+
self._elapsed = 0.0
|
|
40
|
+
self._tick = 0
|
|
41
|
+
self._timer = None
|
|
42
|
+
|
|
43
|
+
def on_mount(self) -> None:
|
|
44
|
+
self._repaint()
|
|
45
|
+
|
|
46
|
+
def started(self, model: str) -> None:
|
|
47
|
+
self._model = model
|
|
48
|
+
self._state = "running"
|
|
49
|
+
self._started = time.monotonic()
|
|
50
|
+
self._tick = 0
|
|
51
|
+
if self._timer is None and animation.enabled():
|
|
52
|
+
self._timer = self.set_interval(animation.SPINNER_INTERVAL, self._spin)
|
|
53
|
+
self._repaint()
|
|
54
|
+
|
|
55
|
+
def finished(self, summary: str, seconds: float) -> None:
|
|
56
|
+
self._state = "done"
|
|
57
|
+
self._status = summary or "done"
|
|
58
|
+
if self._started:
|
|
59
|
+
self._elapsed = time.monotonic() - self._started
|
|
60
|
+
self._stop()
|
|
61
|
+
self._repaint()
|
|
62
|
+
|
|
63
|
+
def _spin(self) -> None:
|
|
64
|
+
self._tick += 1
|
|
65
|
+
self._repaint()
|
|
66
|
+
|
|
67
|
+
def _stop(self) -> None:
|
|
68
|
+
if self._timer is not None:
|
|
69
|
+
self._timer.stop()
|
|
70
|
+
self._timer = None
|
|
71
|
+
|
|
72
|
+
def on_unmount(self) -> None:
|
|
73
|
+
self._stop()
|
|
74
|
+
|
|
75
|
+
def _repaint(self) -> None:
|
|
76
|
+
text = Text()
|
|
77
|
+
text.append(f"{events.stage_mark(self.stage):<2} ", style=theme.FAINT)
|
|
78
|
+
text.append(f"{events.stage_title(self.stage):<16}", style=theme.DIM)
|
|
79
|
+
|
|
80
|
+
if self._state == "pending":
|
|
81
|
+
text.append("—", style=theme.FAINT)
|
|
82
|
+
elif self._state == "running":
|
|
83
|
+
spin = animation.frame(self._tick) if animation.enabled() else "·"
|
|
84
|
+
text.append(f"{spin} ", style=theme.CITE)
|
|
85
|
+
elapsed = time.monotonic() - self._started if self._started else 0.0
|
|
86
|
+
text.append(animation.elapsed(elapsed), style=theme.FAINT)
|
|
87
|
+
else:
|
|
88
|
+
text.append(self._status)
|
|
89
|
+
if self._elapsed >= 0.05:
|
|
90
|
+
text.append(f" {animation.elapsed(self._elapsed)}", style=theme.FAINT)
|
|
91
|
+
self.update(text)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class StageList(Vertical):
|
|
95
|
+
"""Rows for the stages we expect, plus any the engine grows later.
|
|
96
|
+
|
|
97
|
+
A stage the engine adds appears at the end of the list with a blank gutter
|
|
98
|
+
mark rather than being dropped on the floor, so the interface reports the
|
|
99
|
+
pipeline that ran instead of the pipeline it was written against.
|
|
100
|
+
"""
|
|
101
|
+
|
|
102
|
+
def compose(self) -> ComposeResult:
|
|
103
|
+
for stage, _mark in events.STAGE_ORDER:
|
|
104
|
+
if stage in events.OPTIONAL_STAGES:
|
|
105
|
+
continue
|
|
106
|
+
yield StageRow(stage, id=f"stage-{stage}")
|
|
107
|
+
|
|
108
|
+
def row(self, stage: str) -> StageRow | None:
|
|
109
|
+
for row in self.query(StageRow):
|
|
110
|
+
if row.stage == stage:
|
|
111
|
+
return row
|
|
112
|
+
return None
|
|
113
|
+
|
|
114
|
+
def ensure(self, stage: str) -> StageRow:
|
|
115
|
+
existing = self.row(stage)
|
|
116
|
+
if existing is not None:
|
|
117
|
+
return existing
|
|
118
|
+
row = StageRow(stage)
|
|
119
|
+
self.mount(row)
|
|
120
|
+
animation.reveal(row)
|
|
121
|
+
return row
|
|
122
|
+
|
|
123
|
+
def settle(self) -> None:
|
|
124
|
+
"""Stop every spinner. Called when the run ends, however it ended.
|
|
125
|
+
|
|
126
|
+
Without this, a failure part-way through leaves the stage it died on
|
|
127
|
+
turning forever, which reads as still working.
|
|
128
|
+
"""
|
|
129
|
+
for row in self.query(StageRow):
|
|
130
|
+
if row._state == "running":
|
|
131
|
+
row._state = "pending"
|
|
132
|
+
row._stop()
|
|
133
|
+
row._repaint()
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class EmittedFinding(Line):
|
|
137
|
+
"""A claim a stage just made, with the ids it says support it.
|
|
138
|
+
|
|
139
|
+
Shown before Stage D has had its say, which is the point: the live view is
|
|
140
|
+
where you watch a claim arrive and then, sometimes, be taken away.
|
|
141
|
+
"""
|
|
142
|
+
|
|
143
|
+
def __init__(
|
|
144
|
+
self, event: events.FindingEmitted, repo: str | None = None, **kwargs
|
|
145
|
+
) -> None:
|
|
146
|
+
super().__init__(classes="finding", **kwargs)
|
|
147
|
+
self.event = event
|
|
148
|
+
self.repo = repo
|
|
149
|
+
|
|
150
|
+
def on_mount(self) -> None:
|
|
151
|
+
self.update(self._line())
|
|
152
|
+
animation.reveal(self)
|
|
153
|
+
|
|
154
|
+
def _line(self) -> Text:
|
|
155
|
+
event = self.event
|
|
156
|
+
text = Text()
|
|
157
|
+
if event.field == "thread_outcome" and isinstance(event.value, dict):
|
|
158
|
+
for eid in event.evidence_ids[:1]:
|
|
159
|
+
text.append_text(cite(eid, width=44, repo=self.repo))
|
|
160
|
+
text.append(" ")
|
|
161
|
+
text.append(str(event.value.get("outcome", "")), style=theme.DIM)
|
|
162
|
+
else:
|
|
163
|
+
text.append(f"{event.field} = {event.value}")
|
|
164
|
+
for eid in event.evidence_ids[:1]:
|
|
165
|
+
text.append(" ")
|
|
166
|
+
text.append_text(cite(eid, width=40, repo=self.repo))
|
|
167
|
+
return text
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class DroppedFinding(Widget):
|
|
171
|
+
"""Stage D removed a claim.
|
|
172
|
+
|
|
173
|
+
Given room rather than compressed to a line. This is the behaviour the whole
|
|
174
|
+
pipeline is arranged around, and the reason a reader can trust the rest of
|
|
175
|
+
the report: a claim the model made, whose evidence did not hold up, and which
|
|
176
|
+
was therefore dropped rather than softened into a hedge.
|
|
177
|
+
"""
|
|
178
|
+
|
|
179
|
+
def __init__(self, event: events.FindingDropped, **kwargs) -> None:
|
|
180
|
+
super().__init__(**kwargs)
|
|
181
|
+
self.event = event
|
|
182
|
+
|
|
183
|
+
def on_mount(self) -> None:
|
|
184
|
+
animation.reveal(self)
|
|
185
|
+
|
|
186
|
+
def compose(self) -> ComposeResult:
|
|
187
|
+
event = self.event
|
|
188
|
+
head = Text()
|
|
189
|
+
head.append("✗ ", style=theme.DROP)
|
|
190
|
+
head.append(f"{event.field} = {event.value!r}")
|
|
191
|
+
yield Line(head)
|
|
192
|
+
|
|
193
|
+
unquoted = getattr(event, "reason", "unresolved") == "unquoted"
|
|
194
|
+
for eid in event.cited:
|
|
195
|
+
row = Text(" ")
|
|
196
|
+
row.append("cited ", style=theme.DIM)
|
|
197
|
+
# An unquoted claim cites a thread that is perfectly real; it is the
|
|
198
|
+
# words that are not there, and colouring the id as unresolvable
|
|
199
|
+
# would say the opposite of what happened.
|
|
200
|
+
row.append_text(cite(eid, resolves=unquoted, width=50))
|
|
201
|
+
row.append(
|
|
202
|
+
" says no such thing" if unquoted else " does not resolve",
|
|
203
|
+
style=theme.DROP,
|
|
204
|
+
)
|
|
205
|
+
yield Line(row)
|
|
206
|
+
if not event.cited:
|
|
207
|
+
row = Text(" ")
|
|
208
|
+
row.append("cited ", style=theme.DIM)
|
|
209
|
+
row.append("no evidence at all", style=theme.DROP)
|
|
210
|
+
yield Line(row)
|
|
211
|
+
|
|
212
|
+
yield Line(
|
|
213
|
+
Text(
|
|
214
|
+
"Dropped, not softened. The quoted words are not in that thread, so "
|
|
215
|
+
"the claim does not reach the reader."
|
|
216
|
+
if unquoted else
|
|
217
|
+
"Dropped, not softened. The claim was asserted without a record that "
|
|
218
|
+
"carries it, so it does not reach the reader.",
|
|
219
|
+
style=theme.FAINT,
|
|
220
|
+
),
|
|
221
|
+
classes="drop-reason",
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def unknown(event: object) -> Line:
|
|
226
|
+
"""Anything this file has no specific rendering for.
|
|
227
|
+
|
|
228
|
+
An event added to the schema after this screen was written must still
|
|
229
|
+
produce a truthful line. Nothing in the UI may raise on an event it has not
|
|
230
|
+
seen before.
|
|
231
|
+
"""
|
|
232
|
+
return Line(Text(events.describe(event), style=theme.FAINT), classes="finding")
|
holt/types.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""Core types shared by the evidence layer, the agent stages, and the eval harness."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from datetime import UTC, datetime
|
|
7
|
+
from enum import Enum
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
# The temporal holdout boundary.
|
|
11
|
+
#
|
|
12
|
+
# The agent may only see records at or before T; labels are computed only from
|
|
13
|
+
# records after T. T sits just past the model's May 2026 training cutoff so that
|
|
14
|
+
# the label window (Jun-Aug 2026) falls outside training data entirely. Moving T
|
|
15
|
+
# earlier would drag the label window *into* training data, which is the leak
|
|
16
|
+
# that actually matters.
|
|
17
|
+
T_CUTOFF = datetime(2026, 6, 1, tzinfo=UTC)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class Window(str, Enum):
|
|
21
|
+
"""Which side of the holdout a provider is permitted to read."""
|
|
22
|
+
|
|
23
|
+
PRE_T = "pre_t"
|
|
24
|
+
POST_T = "post_t"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass(frozen=True, slots=True)
|
|
28
|
+
class EvidenceRecord:
|
|
29
|
+
"""A single retrieved fact, addressable by a stable id.
|
|
30
|
+
|
|
31
|
+
Every claim Holt makes must cite an ``evidence_id`` that resolves back to one
|
|
32
|
+
of these. Stage D drops any finding whose id does not.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
evidence_id: str
|
|
36
|
+
source: str
|
|
37
|
+
url: str
|
|
38
|
+
timestamp: datetime
|
|
39
|
+
payload: dict[str, Any]
|
|
40
|
+
|
|
41
|
+
def __post_init__(self) -> None:
|
|
42
|
+
if self.timestamp.tzinfo is None:
|
|
43
|
+
raise ValueError(
|
|
44
|
+
f"{self.evidence_id}: timestamp must be timezone-aware; "
|
|
45
|
+
"naive datetimes cannot be compared against the holdout boundary"
|
|
46
|
+
)
|
|
47
|
+
if not self.evidence_id:
|
|
48
|
+
raise ValueError("evidence_id must be non-empty")
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: holt-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Evidence-backed assessment of whether a GitHub repository is a viable opportunity for an external contributor.
|
|
5
|
+
Project-URL: Homepage, https://github.com/holt-oss/holt
|
|
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://github.com/holt-oss/holt/blob/main/LICENSE)
|
|
35
|
+
[](https://github.com/holt-oss/holt/blob/main/pyproject.toml)
|
|
36
|
+
|
|
37
|
+
**Choose an open-source repository that is worth your time.**
|
|
38
|
+
|
|
39
|
+
Holt reads recent GitHub contribution history and shows what happens when
|
|
40
|
+
outside contributors try to help: whether they receive a response, whether
|
|
41
|
+
substantive work gets reviewed and merged, and where newcomer contributions
|
|
42
|
+
actually land. It turns that evidence into a verdict you can inspect before
|
|
43
|
+
committing days or weeks to a project.
|
|
44
|
+
|
|
45
|
+

|
|
46
|
+
|
|
47
|
+
Holt is read-only. It never opens pull requests, posts comments, or contacts
|
|
48
|
+
maintainers. Every factual claim in a full report links back to the pull request,
|
|
49
|
+
review, or comment that supports it.
|
|
50
|
+
|
|
51
|
+
> Winner of **Most useful real-world workflow** at the micro1 Frontier
|
|
52
|
+
> Engineering Challenge. Holt is now maintained as an independent open-source
|
|
53
|
+
> product by [`holt-oss`](https://github.com/holt-oss).
|
|
54
|
+
|
|
55
|
+
## Install
|
|
56
|
+
|
|
57
|
+
Holt is pre-1.0. You need [`uv`](https://docs.astral.sh/uv/) and Python 3.11 or
|
|
58
|
+
newer.
|
|
59
|
+
|
|
60
|
+
```sh
|
|
61
|
+
uv tool install holt-cli
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Create a classic GitHub token with no scopes and expose it to Holt:
|
|
65
|
+
|
|
66
|
+
```sh
|
|
67
|
+
export GITHUB_TOKEN=your_token
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Start with the rules-only report. It calls no model and needs no model API key:
|
|
71
|
+
|
|
72
|
+
```sh
|
|
73
|
+
holt analyze pallets/flask --live --no-model
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
For a written report with cited findings and quotations, configure a supported
|
|
77
|
+
model provider and run the full analysis:
|
|
78
|
+
|
|
79
|
+
```sh
|
|
80
|
+
export OPENAI_API_KEY=your_key
|
|
81
|
+
holt analyze pallets/flask --live
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Run `holt models` to inspect or change the provider. OpenAI, Anthropic, Gemini,
|
|
85
|
+
Ollama, and OpenAI-compatible endpoints are supported.
|
|
86
|
+
|
|
87
|
+
## Use the terminal interface
|
|
88
|
+
|
|
89
|
+
Run Holt without a subcommand to open the interactive terminal interface:
|
|
90
|
+
|
|
91
|
+
```sh
|
|
92
|
+
holt
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
From there you can assess a repository, revisit past reports, inspect the
|
|
96
|
+
evidence behind a claim, compare candidates, and configure model providers.
|
|
97
|
+
|
|
98
|
+
## What you get
|
|
99
|
+
|
|
100
|
+
A repository assessment answers five practical questions:
|
|
101
|
+
|
|
102
|
+
- Do first-time contributors get meaningful work merged?
|
|
103
|
+
- How quickly does someone usually respond?
|
|
104
|
+
- Are pull requests reviewed by people or accepted mechanically?
|
|
105
|
+
- Which parts of the codebase have accepted outsider work?
|
|
106
|
+
- What specific evidence supports the verdict?
|
|
107
|
+
|
|
108
|
+
The headline is one of:
|
|
109
|
+
|
|
110
|
+
| Verdict | Meaning |
|
|
111
|
+
|---|---|
|
|
112
|
+
| **Worth your time** | There is evidence that outsiders can land useful work. |
|
|
113
|
+
| **Not worth your time** | The observed contribution path is a poor fit for the time you have. |
|
|
114
|
+
| **Not enough evidence** | Holt cannot support either conclusion from the available history. |
|
|
115
|
+
|
|
116
|
+
Treat the verdict as a filter, not an oracle. Open the cited evidence before
|
|
117
|
+
making a significant commitment.
|
|
118
|
+
|
|
119
|
+
## Commands
|
|
120
|
+
|
|
121
|
+
| Command | Purpose |
|
|
122
|
+
|---|---|
|
|
123
|
+
| `holt analyze <owner/repo> --live` | Assess one repository using current GitHub data. |
|
|
124
|
+
| `holt compare <repo>… --live` | Compare repositories side by side. |
|
|
125
|
+
| `holt profile` | Save the languages, topics, contribution type, and time budget you want. |
|
|
126
|
+
| `holt discover --live` | Find and screen repositories for that profile. |
|
|
127
|
+
| `holt next <repo> --as <login> --live` | Rank open issues after you have contributed to a project. |
|
|
128
|
+
| `holt models` | Inspect or change the model provider. |
|
|
129
|
+
| `holt tui` | Open the interactive terminal interface. |
|
|
130
|
+
|
|
131
|
+
See [USAGE.md](https://github.com/holt-oss/holt/blob/main/USAGE.md) for the
|
|
132
|
+
complete workflow and
|
|
133
|
+
[docs/COMMANDS.md](https://github.com/holt-oss/holt/blob/main/docs/COMMANDS.md)
|
|
134
|
+
for command details.
|
|
135
|
+
|
|
136
|
+
## How it works
|
|
137
|
+
|
|
138
|
+
Holt separates evidence collection, interpretation, verification, and the final
|
|
139
|
+
decision:
|
|
140
|
+
|
|
141
|
+
```text
|
|
142
|
+
GitHub history
|
|
143
|
+
↓
|
|
144
|
+
contribution signals + cited findings
|
|
145
|
+
↓
|
|
146
|
+
evidence verification
|
|
147
|
+
↓
|
|
148
|
+
deterministic verdict
|
|
149
|
+
↓
|
|
150
|
+
written explanation
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
The model may interpret threads and explain the result, but it does not choose
|
|
154
|
+
the verdict. Unsupported findings are removed before the decision is computed.
|
|
155
|
+
The same verified inputs therefore produce the same verdict.
|
|
156
|
+
|
|
157
|
+
The architecture and its boundaries are documented in
|
|
158
|
+
[docs/DESIGN.md](https://github.com/holt-oss/holt/blob/main/docs/DESIGN.md).
|
|
159
|
+
|
|
160
|
+
## Evaluation and limitations
|
|
161
|
+
|
|
162
|
+
Holt has been measured on two temporally held-out repository pools. On the
|
|
163
|
+
out-of-sample pool it reached 0.82 balanced accuracy and 0.63 Matthews
|
|
164
|
+
correlation, compared with 0.61 and 0.21 for a single prompt over repository
|
|
165
|
+
metadata and documentation. The committed fixtures and recordings make those
|
|
166
|
+
results reproducible.
|
|
167
|
+
|
|
168
|
+
The evaluation is evidence, not a guarantee. Repository cultures change, quiet
|
|
169
|
+
periods can look worse than they are, and no benchmark captures whether a
|
|
170
|
+
particular issue matches your skills. Holt is designed to show its work so you
|
|
171
|
+
can disagree with it intelligently.
|
|
172
|
+
|
|
173
|
+
- [Evaluation design and limitations](https://github.com/holt-oss/holt/blob/main/docs/EVALUATION.md)
|
|
174
|
+
- [Reproduce the published results](https://github.com/holt-oss/holt/blob/main/REPRODUCTION.md)
|
|
175
|
+
- [Recorded example analyses](https://github.com/holt-oss/holt/blob/main/trajectories/README.md)
|
|
176
|
+
|
|
177
|
+
## Contributing
|
|
178
|
+
|
|
179
|
+
Holt welcomes focused improvements to evidence providers, verdict rules,
|
|
180
|
+
terminal UX, documentation, evaluation, and platform support.
|
|
181
|
+
|
|
182
|
+
```sh
|
|
183
|
+
git clone https://github.com/holt-oss/holt.git
|
|
184
|
+
cd holt
|
|
185
|
+
uv sync
|
|
186
|
+
uv run pytest -rs
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Read [CONTRIBUTING.md](https://github.com/holt-oss/holt/blob/main/CONTRIBUTING.md)
|
|
190
|
+
before opening a pull request. Community participation is governed by the
|
|
191
|
+
[Code of Conduct](https://github.com/holt-oss/holt/blob/main/CODE_OF_CONDUCT.md),
|
|
192
|
+
and security reports follow
|
|
193
|
+
[SECURITY.md](https://github.com/holt-oss/holt/blob/main/SECURITY.md).
|
|
194
|
+
|
|
195
|
+
## License
|
|
196
|
+
|
|
197
|
+
Holt is licensed under the
|
|
198
|
+
[Apache License 2.0](https://github.com/holt-oss/holt/blob/main/LICENSE).
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
holt/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
holt/baseline.py,sha256=Ros5hIpPKezf7gLK_Gt8YfUNO_-sO68y-RmpQYs0v-M,3163
|
|
3
|
+
holt/baseline_matched.py,sha256=XLQVNXPAWtzruhCkWcLRl6VvIbhgr03IqtUEaCFnsaE,4791
|
|
4
|
+
holt/cli.py,sha256=lsCBohRH9jZ2C-Xev6uQxLupZdnr3qehS02TT-3ffnI,26167
|
|
5
|
+
holt/discover.py,sha256=mCFJwno7mOs66pNej-Msw3j3KKWPnoUFPlm9XNeLgoI,20786
|
|
6
|
+
holt/issues.py,sha256=250OcM1IPQ9UT7VgV2EHpfu74yLcM34OQ8Ipp1aujrY,1543
|
|
7
|
+
holt/model.py,sha256=wJXU64FRuLN4Jbk3MqqXRseN59tsxacMDqbsRAj5mf4,20112
|
|
8
|
+
holt/profile.py,sha256=iCwfkqMqA39EgtUWoQXM0jqhezkktct3WSFW2BxPh3s,4921
|
|
9
|
+
holt/report.py,sha256=PkZCUfEJyttRLEJz_LdqIehpC15WQ5uSGmL_9PP6YbM,6556
|
|
10
|
+
holt/types.py,sha256=wzRi6iQvs_aSYRrQjJl3K5fHPyvvAoK36o9yuLA47rE,1549
|
|
11
|
+
holt/agent/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
|
+
holt/agent/entry.py,sha256=F_ouy81cFmwIHeYbNw_xxwHSvVc7tyFivnlL9mx4IUg,3798
|
|
13
|
+
holt/agent/findings.py,sha256=t_XsTM7DhRT_pWb2oElIp9tfEDtdq6__obpuBJYeb1Y,1495
|
|
14
|
+
holt/agent/landing.py,sha256=cVQ2om1qNGivLXQAYmfC4HSWsinYG4G_qHPKwQNXTrU,5980
|
|
15
|
+
holt/agent/pipeline.py,sha256=eQqZ_NL2o-Fcia2enmWESU6dpQlmyxekXpsb6Ea7Kpk,10488
|
|
16
|
+
holt/agent/progression.py,sha256=4Rpjmo2qDFxbTfl8MDy9Y8TqPYCKzEanCo7YuwxDE80,15991
|
|
17
|
+
holt/agent/signals.py,sha256=H4qhVfN87Q-vuROZ_JQLJJ59THYH2nuwF5r9nusPlM8,9258
|
|
18
|
+
holt/agent/stages.py,sha256=lsLK7CPjo4o4PxBV4oOaJa3z_msBBu5xI3MTmdrfZWM,21153
|
|
19
|
+
holt/agent/verdict.py,sha256=10pC7aoQvJl9yZzpKtQ1QE4Y7KvQ7H7NZ8uEGHgmFOE,10268
|
|
20
|
+
holt/agent/verify.py,sha256=_PwvVYoRxQtOentE0502seEh78bR07goQ83We6Kf7-g,5760
|
|
21
|
+
holt/evidence/__init__.py,sha256=xbcUlLUkTIbO0eWf53MGAE6J7sul38Zqt5Xq2d1ztfM,126
|
|
22
|
+
holt/evidence/fixtures.py,sha256=We9lGRgKST6sJJ6c6GrIxLaxSZks1ODdEcnaUEgLtf0,5358
|
|
23
|
+
holt/evidence/github_graphql.py,sha256=EUaK_0BrR7Mvel8J378cOT-8fzan0DfsoXdyE7NyHmc,20789
|
|
24
|
+
holt/evidence/provider.py,sha256=zjT-Tn4wPfr5E7kf20qSjMOBX_LSzh4nO2bc-hpQhSg,3154
|
|
25
|
+
holt/evidence/redact.py,sha256=pRivtIIrnJlsc-V2KO_kzA15yapNPZGeu08cmPoNM3k,2793
|
|
26
|
+
holt/tui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
27
|
+
holt/tui/animation.py,sha256=IGjD6pQDijhZ2CVroffVSL5urXBJCXeGHnUABC9EH3s,3017
|
|
28
|
+
holt/tui/app.py,sha256=2irYvm_HKLaECM4oexhDsOn12C1AnktSzyMF4jhk2S0,12867
|
|
29
|
+
holt/tui/clipboard.py,sha256=ObyGDFj8c7ytvOFIzl7tmemfmn9XZ5wfYn-lNg59Bvo,3632
|
|
30
|
+
holt/tui/commands.py,sha256=Up2Ysz5YkvKCVie-29ay83fpYm-f7mK3XOF6bKxXlMA,5788
|
|
31
|
+
holt/tui/discovery.py,sha256=2BCdJ6Zp2KheaAI_D6shV7h-JedeqlfJNjlvo4h-ccY,11704
|
|
32
|
+
holt/tui/env.py,sha256=CQ5HB8DxO0uRkcK1rI0V2VscaDAEMOaMy8OYkqHvRzM,1697
|
|
33
|
+
holt/tui/events.py,sha256=T4qrzVckOqLzKn-H07Xu2nDDNjD1w-s723FtfoOCTwQ,7609
|
|
34
|
+
holt/tui/mascot.py,sha256=6OVYRekH9MDMXqDCQOtK0Tfqhbt2FRt-RdakXSZaYq8,4670
|
|
35
|
+
holt/tui/models.py,sha256=_NHxrW7oXq1YItLxr7paQ9aM1zCcOeEoGFu3SetC-gk,23351
|
|
36
|
+
holt/tui/observe.py,sha256=W0oEtTPnAp9YMOHNEFLdysUKC8bXI0kzrDevuln0Q_c,11355
|
|
37
|
+
holt/tui/session.py,sha256=m83m2y5y0AQ3CkWY8ORGidNKNG1MTp61aY6SVaTUNqU,30330
|
|
38
|
+
holt/tui/store.py,sha256=ZZqUlUM_t7Krq4FQ6vTTUd7lMK4rjGO2k64IBNGbwdM,17937
|
|
39
|
+
holt/tui/theme.py,sha256=O3MJeLcYVl7N2PyxINF5z2OpUE_eKycslispwy4KUy0,13067
|
|
40
|
+
holt/tui/visual.py,sha256=C8uGapA1DvxE2v5e9AYUYF1PX8hHymKbIhWBV2na1w8,1148
|
|
41
|
+
holt/tui/screens/__init__.py,sha256=wbGP0Q_YuK4JObPxtX4xrkCdTy5i_6FsEhWKrhSZWak,1051
|
|
42
|
+
holt/tui/screens/assessment.py,sha256=tHYSkaJqO461pkx0mW9aJBVN39QftVC3tgZD_DbqEVs,14319
|
|
43
|
+
holt/tui/screens/confirm.py,sha256=DHFKJfQDWlTJW30ZPMwVjXC1d0kwE8WLvZj4LH_99iM,1945
|
|
44
|
+
holt/tui/screens/discover.py,sha256=gZHLUYi2JMtd70gM3rhmIrhaB9V-nSN6IfPWg5Ag_6Q,18660
|
|
45
|
+
holt/tui/screens/home.py,sha256=5zVJMa6eFZJ9JsMOuSdUCyEV8HNCMI_Adx1DeSiQ1yc,23159
|
|
46
|
+
holt/tui/screens/inspector.py,sha256=PpX0gQiioT8LxMjuwwSMLKy75F4X1igOZWJ_sevNOCI,3953
|
|
47
|
+
holt/tui/screens/live.py,sha256=vLRQbSuK5tAATrZEy-4jjlFe3OCLSLLZw9X1mEK5Gao,14180
|
|
48
|
+
holt/tui/screens/models.py,sha256=7t-zQk-JK-LnjILwuQPIJL3Dru-zENfPTf9z-lmdILc,15376
|
|
49
|
+
holt/tui/screens/next_steps.py,sha256=dyyf06Ft3qqSnxLMKXrh0H0-yLPRJUF41jPIS76l4hg,18654
|
|
50
|
+
holt/tui/screens/profile.py,sha256=lK2SGllhUwFhmmqH1t7wa9nf3zvSC0NwAz_X7PPZHMo,4730
|
|
51
|
+
holt/tui/widgets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
52
|
+
holt/tui/widgets/candidates.py,sha256=pK77I3e0c0e0a-23OetG8ZszWuzptzfXX5gmnxG6FoI,2720
|
|
53
|
+
holt/tui/widgets/claims.py,sha256=UjCm354yQAM5CYcDj-pvGKN51coRUXtZ9VM_1tKGMew,2035
|
|
54
|
+
holt/tui/widgets/disclosure.py,sha256=U6kHO1ouosEe301kDQ_JqbgHtCR9yCOnbIPGZsC0kAc,4523
|
|
55
|
+
holt/tui/widgets/evidence.py,sha256=3znmJpQ32eSKBNqMorE2e345C7LS4VbBqDJgodYOFQ8,4181
|
|
56
|
+
holt/tui/widgets/masthead.py,sha256=fEK8ShfZquDU_wXQQBNJT772So7y9QQxW221koxsFks,4400
|
|
57
|
+
holt/tui/widgets/recent.py,sha256=upzuQcnbWaxYTmbSpERiltQe5hBbT_cIZ-vanwOq57I,6302
|
|
58
|
+
holt/tui/widgets/scrolling.py,sha256=X7NCOQ4kXpG7YPc5ndJ0htZrfT_ds03C-IIkZ0BPZdg,1747
|
|
59
|
+
holt/tui/widgets/stages.py,sha256=hlsIzDVhbUwXugYElAWgwn8eFRXPxGJ5nU6853HizHE,8153
|
|
60
|
+
holt_cli-0.1.0.dist-info/METADATA,sha256=-QyBSwCv_nHElq05s17pyGcNDs9Wf6O6aDkJ7z47w9U,7464
|
|
61
|
+
holt_cli-0.1.0.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
|
|
62
|
+
holt_cli-0.1.0.dist-info/entry_points.txt,sha256=nv3JMCxhB1SvX6AW7vjfHjez7bKJP6GiNHQoB3KrPQ0,39
|
|
63
|
+
holt_cli-0.1.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
64
|
+
holt_cli-0.1.0.dist-info/licenses/NOTICE,sha256=6qCav9tAXKnPlsDpGdbqDRJFONQnNBDGiz5xC3eersQ,96
|
|
65
|
+
holt_cli-0.1.0.dist-info/RECORD,,
|