grask 0.1.0rc1__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.
- grask-0.1.0rc1/LICENSE +21 -0
- grask-0.1.0rc1/PKG-INFO +233 -0
- grask-0.1.0rc1/README.md +212 -0
- grask-0.1.0rc1/pyproject.toml +78 -0
- grask-0.1.0rc1/src/grask/SKILL.md +62 -0
- grask-0.1.0rc1/src/grask/__init__.py +3 -0
- grask-0.1.0rc1/src/grask/ask.py +224 -0
- grask-0.1.0rc1/src/grask/capture.py +158 -0
- grask-0.1.0rc1/src/grask/capture_run.py +236 -0
- grask-0.1.0rc1/src/grask/cli.py +257 -0
- grask-0.1.0rc1/src/grask/dialogue.py +183 -0
- grask-0.1.0rc1/src/grask/hook.py +59 -0
- grask-0.1.0rc1/src/grask/llm.py +323 -0
- grask-0.1.0rc1/src/grask/probe.py +361 -0
- grask-0.1.0rc1/src/grask/py.typed +0 -0
- grask-0.1.0rc1/src/grask/seed.py +216 -0
- grask-0.1.0rc1/src/grask/select.py +64 -0
- grask-0.1.0rc1/src/grask/storage.py +403 -0
- grask-0.1.0rc1/src/grask/survey.py +136 -0
- grask-0.1.0rc1/src/grask/transcript.py +170 -0
- grask-0.1.0rc1/src/grask/triage.py +362 -0
- grask-0.1.0rc1/src/grask/triage_run.py +154 -0
grask-0.1.0rc1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Kumar Prateek
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
grask-0.1.0rc1/PKG-INFO
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: grask
|
|
3
|
+
Version: 0.1.0rc1
|
|
4
|
+
Summary: One question about your own code, at the end of a Claude Code session.
|
|
5
|
+
Keywords: claude-code,learning,code-comprehension,developer-tools
|
|
6
|
+
Author: Kumar Prateek
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Environment :: Console
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
15
|
+
Classifier: Typing :: Typed
|
|
16
|
+
Requires-Python: >=3.12
|
|
17
|
+
Project-URL: Homepage, https://github.com/imkp1/grask
|
|
18
|
+
Project-URL: Repository, https://github.com/imkp1/grask
|
|
19
|
+
Project-URL: Issues, https://github.com/imkp1/grask/issues
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
# grask
|
|
23
|
+
|
|
24
|
+
[](https://github.com/imkp1/grask/actions/workflows/ci.yml)
|
|
25
|
+
[](https://www.python.org/downloads/)
|
|
26
|
+
[](LICENSE)
|
|
27
|
+
[](#status)
|
|
28
|
+
|
|
29
|
+
One question about your own code, when you finish coding.
|
|
30
|
+
|
|
31
|
+
You can't tell the difference between understanding something and having watched it happen.
|
|
32
|
+
Shipping code with an agent used to force the issue — you couldn't ship what you didn't
|
|
33
|
+
understand, because it wouldn't run. That forcing function is gone.
|
|
34
|
+
|
|
35
|
+
grask watches your Claude Code sessions end, decides whether anything in one was worth
|
|
36
|
+
asking about, and — usually not — stays silent. When something was, it mints a single
|
|
37
|
+
multiple-choice question about the mechanism you shipped, and asks it the next time you
|
|
38
|
+
run `/grask`.
|
|
39
|
+
|
|
40
|
+
Most sessions produce nothing. That is the system working, not a failure to find something.
|
|
41
|
+
|
|
42
|
+
## Install
|
|
43
|
+
|
|
44
|
+
Requires Python 3.12+ and the [Claude Code CLI](https://claude.com/claude-code) already
|
|
45
|
+
installed and authenticated. grask has no runtime dependencies and needs no second API
|
|
46
|
+
key — it shells out to the `claude` binary you already use.
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
git clone https://github.com/imkp1/grask && cd grask
|
|
50
|
+
uv tool install .
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
This puts two commands on your PATH: `grask` (ask a question) and `grask-hook` (the
|
|
54
|
+
capture trigger). `pipx install .` works the same way. Not on PyPI yet — install from a
|
|
55
|
+
checkout.
|
|
56
|
+
|
|
57
|
+
Installing as a tool rather than `uv sync` is deliberate: both surfaces below invoke
|
|
58
|
+
`grask` by name, so it has to resolve without a path. Use `uv sync` only for working on
|
|
59
|
+
grask itself.
|
|
60
|
+
|
|
61
|
+
### Wire up the hook
|
|
62
|
+
|
|
63
|
+
Capture runs when a session ends. Add to your Claude Code `settings.json`:
|
|
64
|
+
|
|
65
|
+
```json
|
|
66
|
+
{
|
|
67
|
+
"hooks": {
|
|
68
|
+
"SessionEnd": [
|
|
69
|
+
{
|
|
70
|
+
"hooks": [
|
|
71
|
+
{ "type": "command", "command": "grask-hook" }
|
|
72
|
+
]
|
|
73
|
+
}
|
|
74
|
+
]
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
If you installed with `uv sync` instead, this has to be the absolute path to
|
|
80
|
+
`.venv/bin/grask-hook` — and it breaks the moment you move the checkout.
|
|
81
|
+
|
|
82
|
+
The hook reads the payload, spawns a detached worker, and returns immediately — it never
|
|
83
|
+
blocks the end of your session, and it never speaks. Failures go to
|
|
84
|
+
`~/.claude/grask/grask.log`, never to your terminal.
|
|
85
|
+
|
|
86
|
+
### Install the skill (optional)
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
grask skill --install # writes ~/.claude/skills/grask/SKILL.md
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
This gets you `/grask` inside Claude Code, using the native question UI. `--dir` targets a
|
|
93
|
+
different skills directory — `.claude/skills` next to a repo, for a project-level install.
|
|
94
|
+
Without the skill, `grask` on the command line does the same job in the terminal.
|
|
95
|
+
|
|
96
|
+
`grask skill` with no flags prints the file instead of writing it, if you want to read it
|
|
97
|
+
before it lands.
|
|
98
|
+
|
|
99
|
+
## Use
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
grask # ask the next pending question, or say there's nothing
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
You get one question, three or four options, and one line of orientation about which
|
|
106
|
+
session it came from. Pick a letter. Grading is mechanical — the answer key was minted
|
|
107
|
+
when the question was, so there is no model call, no latency, and no judge to argue with.
|
|
108
|
+
`enter` skips. `/wrong` rejects the premise if the question misreads what happened.
|
|
109
|
+
|
|
110
|
+
Questions expire after 7 days. A probe about work you did last week is a quiz.
|
|
111
|
+
|
|
112
|
+
## Privacy — read this before installing
|
|
113
|
+
|
|
114
|
+
grask reads your Claude Code transcripts, and it is not scoped to one project:
|
|
115
|
+
|
|
116
|
+
- **It reads every transcript under `~/.claude/projects/`**, across all your repositories.
|
|
117
|
+
- **It sends transcript content to a model** — your prompts, the agent's replies, and the
|
|
118
|
+
before/after text of edits — by shelling out to `claude -p`. That call runs under your
|
|
119
|
+
existing Claude Code authentication and is subject to whatever data policy your account
|
|
120
|
+
already has. No data goes anywhere else, and grask adds no telemetry.
|
|
121
|
+
- **It stores what it extracts locally**, in a SQLite database at `~/.claude/grask/`,
|
|
122
|
+
including verbatim quotes of things you typed.
|
|
123
|
+
|
|
124
|
+
Controls:
|
|
125
|
+
|
|
126
|
+
- `GRASK_HOME` relocates the database and log.
|
|
127
|
+
- The corpus tools take `--exclude` to skip projects by name substring, and `--root` to
|
|
128
|
+
point at a different transcript directory.
|
|
129
|
+
|
|
130
|
+
If any of your repositories are covered by an agreement that prohibits sending source to a
|
|
131
|
+
model, do not install the hook.
|
|
132
|
+
|
|
133
|
+
**Nothing derived from a real transcript belongs in this repository.** The tools write
|
|
134
|
+
under `GRASK_HOME` by default for exactly that reason, and `.gitignore` is a second line of
|
|
135
|
+
defence. This project has made that mistake once already.
|
|
136
|
+
|
|
137
|
+
## How it works
|
|
138
|
+
|
|
139
|
+
Four stages, cheapest first. Each one filters, so only what survives pays for the next.
|
|
140
|
+
|
|
141
|
+
| Stage | Module | Cost | Job |
|
|
142
|
+
|---|---|---|---|
|
|
143
|
+
| 0 — extract | `transcript.py` | free | Pull the developer's own turns out of a session log. Tool results, file snapshots, and injected skill text are not the developer thinking. Sessions with no human turns stop here. |
|
|
144
|
+
| 1 — triage | `triage.py` | one call | List every moment worth asking about, each anchored to a verbatim quote and the turn it came from. Sees turns and file *paths*, never file contents. Most sessions yield nothing. |
|
|
145
|
+
| — select | `select.py` | free | Rank the moments and pick one. Deliberately code, not prompt: a model asked to both find and choose picks arbitrarily, and the topic changed run to run on an unchanged session. |
|
|
146
|
+
| 2 — seed | `seed.py` | one call | State, as a falsifiable claim, what the developer may have accepted without understanding. Stored, so a better stage-3 prompt can re-ask the whole corpus later. |
|
|
147
|
+
| 3 — probe | `probe.py` | one call | Write one multiple-choice question about the mechanism, with the answer key and an explanation. |
|
|
148
|
+
|
|
149
|
+
Two rules are enforced in code rather than prompted for, because instruction is not a
|
|
150
|
+
control:
|
|
151
|
+
|
|
152
|
+
- **The evidence rule.** A triaged moment whose quote does not appear in the turn it names
|
|
153
|
+
is demoted to silence. Same for a seed quote that appears nowhere the developer typed.
|
|
154
|
+
- **The one-question rule.** A stem with two questions in it cannot have one correct
|
|
155
|
+
option, so it is rejected and regenerated, up to three attempts.
|
|
156
|
+
|
|
157
|
+
The question must also teach something portable. A question whose answer is "because this
|
|
158
|
+
file says so" is answerable only by whoever sat through the session and is worth nothing
|
|
159
|
+
once they close the file.
|
|
160
|
+
|
|
161
|
+
### Model selection
|
|
162
|
+
|
|
163
|
+
grask names no model. It calls `claude -p` with no `--model` flag, so every stage runs on
|
|
164
|
+
whatever you currently have selected, and there is no second credential to manage. Tools
|
|
165
|
+
are disallowed and slash commands disabled — grask sends one self-contained prompt and
|
|
166
|
+
wants one JSON object back, so letting it wander the repo would just cost money.
|
|
167
|
+
|
|
168
|
+
## Cost
|
|
169
|
+
|
|
170
|
+
grask's own prompts are small. The real cost is the Claude Code context inherited by
|
|
171
|
+
shelling out to `claude -p` at all, which scales with *your* configuration rather than with
|
|
172
|
+
grask — `--disable-slash-commands` exists in `llm.py` for precisely this reason and cuts a
|
|
173
|
+
call substantially on a large setup.
|
|
174
|
+
|
|
175
|
+
Stages 2 and 3 only run on sessions triage kept, which is a minority. The corpus tools
|
|
176
|
+
print an estimated range and refuse to spend without `--go`.
|
|
177
|
+
|
|
178
|
+
## Development
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
uv sync # working on grask itself, rather than installing it
|
|
182
|
+
uv run pytest # 221 tests, no network, no model calls, sub-second
|
|
183
|
+
uv run ruff check .
|
|
184
|
+
uv run mypy
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
The suite never calls a model. Every stage is injected as a plain callable, so the whole
|
|
188
|
+
pipeline is drivable by test doubles — that seam is why the tests are free and fast.
|
|
189
|
+
Tests marked `calibration` do call the real model and cost money; they are deselected by
|
|
190
|
+
default and run deliberately with `pytest -m calibration`.
|
|
191
|
+
|
|
192
|
+
### Corpus tools
|
|
193
|
+
|
|
194
|
+
Diagnostics for working on grask itself, not part of normal use. All of them write under
|
|
195
|
+
`GRASK_HOME`, never the working directory.
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
uv run python -m grask.survey # what's in the local transcript corpus (free)
|
|
199
|
+
uv run python -m grask.triage_run # run stage 1 over the corpus; costs money
|
|
200
|
+
uv run python -m grask.capture_run # run the full pipeline over past sessions; --go to spend
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
`capture_run` skips grask's own project by default. The sessions that end from now on are
|
|
204
|
+
overwhelmingly grask's own, and waiting for the queue to fill measures grask on grask.
|
|
205
|
+
|
|
206
|
+
## Status
|
|
207
|
+
|
|
208
|
+
Alpha, and honest about it. The capture pipeline, storage, both delivery surfaces, and
|
|
209
|
+
mechanical grading all work end to end.
|
|
210
|
+
|
|
211
|
+
Not built: resurfacing a missed question, and cross-session dedup — two sessions can
|
|
212
|
+
currently produce near-identical probes.
|
|
213
|
+
|
|
214
|
+
Design notes and the reasoning behind each decision are in
|
|
215
|
+
[`docs/design/grask-design.md`](docs/design/grask-design.md); [`IDEA.md`](IDEA.md) covers
|
|
216
|
+
what this is and why it might not work. [`docs/README.md`](docs/README.md) says which of
|
|
217
|
+
those documents are current and which are records.
|
|
218
|
+
|
|
219
|
+
## Contributing
|
|
220
|
+
|
|
221
|
+
Issues and pull requests are welcome — see [CONTRIBUTING.md](CONTRIBUTING.md) for the
|
|
222
|
+
three rules that are easy to break by accident, and [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md).
|
|
223
|
+
|
|
224
|
+
A question grask asked that was *bad* — wrong premise, wrong key, or testing nothing
|
|
225
|
+
portable — is the single most useful thing you can report. There is an issue template
|
|
226
|
+
for exactly that.
|
|
227
|
+
|
|
228
|
+
For anything where grask leaked, over-collected, or wrote outside `GRASK_HOME`, see
|
|
229
|
+
[SECURITY.md](SECURITY.md) and report it privately rather than in a public issue.
|
|
230
|
+
|
|
231
|
+
## License
|
|
232
|
+
|
|
233
|
+
MIT — see [LICENSE](LICENSE).
|
grask-0.1.0rc1/README.md
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
# grask
|
|
2
|
+
|
|
3
|
+
[](https://github.com/imkp1/grask/actions/workflows/ci.yml)
|
|
4
|
+
[](https://www.python.org/downloads/)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
[](#status)
|
|
7
|
+
|
|
8
|
+
One question about your own code, when you finish coding.
|
|
9
|
+
|
|
10
|
+
You can't tell the difference between understanding something and having watched it happen.
|
|
11
|
+
Shipping code with an agent used to force the issue — you couldn't ship what you didn't
|
|
12
|
+
understand, because it wouldn't run. That forcing function is gone.
|
|
13
|
+
|
|
14
|
+
grask watches your Claude Code sessions end, decides whether anything in one was worth
|
|
15
|
+
asking about, and — usually not — stays silent. When something was, it mints a single
|
|
16
|
+
multiple-choice question about the mechanism you shipped, and asks it the next time you
|
|
17
|
+
run `/grask`.
|
|
18
|
+
|
|
19
|
+
Most sessions produce nothing. That is the system working, not a failure to find something.
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
Requires Python 3.12+ and the [Claude Code CLI](https://claude.com/claude-code) already
|
|
24
|
+
installed and authenticated. grask has no runtime dependencies and needs no second API
|
|
25
|
+
key — it shells out to the `claude` binary you already use.
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
git clone https://github.com/imkp1/grask && cd grask
|
|
29
|
+
uv tool install .
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
This puts two commands on your PATH: `grask` (ask a question) and `grask-hook` (the
|
|
33
|
+
capture trigger). `pipx install .` works the same way. Not on PyPI yet — install from a
|
|
34
|
+
checkout.
|
|
35
|
+
|
|
36
|
+
Installing as a tool rather than `uv sync` is deliberate: both surfaces below invoke
|
|
37
|
+
`grask` by name, so it has to resolve without a path. Use `uv sync` only for working on
|
|
38
|
+
grask itself.
|
|
39
|
+
|
|
40
|
+
### Wire up the hook
|
|
41
|
+
|
|
42
|
+
Capture runs when a session ends. Add to your Claude Code `settings.json`:
|
|
43
|
+
|
|
44
|
+
```json
|
|
45
|
+
{
|
|
46
|
+
"hooks": {
|
|
47
|
+
"SessionEnd": [
|
|
48
|
+
{
|
|
49
|
+
"hooks": [
|
|
50
|
+
{ "type": "command", "command": "grask-hook" }
|
|
51
|
+
]
|
|
52
|
+
}
|
|
53
|
+
]
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
If you installed with `uv sync` instead, this has to be the absolute path to
|
|
59
|
+
`.venv/bin/grask-hook` — and it breaks the moment you move the checkout.
|
|
60
|
+
|
|
61
|
+
The hook reads the payload, spawns a detached worker, and returns immediately — it never
|
|
62
|
+
blocks the end of your session, and it never speaks. Failures go to
|
|
63
|
+
`~/.claude/grask/grask.log`, never to your terminal.
|
|
64
|
+
|
|
65
|
+
### Install the skill (optional)
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
grask skill --install # writes ~/.claude/skills/grask/SKILL.md
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
This gets you `/grask` inside Claude Code, using the native question UI. `--dir` targets a
|
|
72
|
+
different skills directory — `.claude/skills` next to a repo, for a project-level install.
|
|
73
|
+
Without the skill, `grask` on the command line does the same job in the terminal.
|
|
74
|
+
|
|
75
|
+
`grask skill` with no flags prints the file instead of writing it, if you want to read it
|
|
76
|
+
before it lands.
|
|
77
|
+
|
|
78
|
+
## Use
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
grask # ask the next pending question, or say there's nothing
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
You get one question, three or four options, and one line of orientation about which
|
|
85
|
+
session it came from. Pick a letter. Grading is mechanical — the answer key was minted
|
|
86
|
+
when the question was, so there is no model call, no latency, and no judge to argue with.
|
|
87
|
+
`enter` skips. `/wrong` rejects the premise if the question misreads what happened.
|
|
88
|
+
|
|
89
|
+
Questions expire after 7 days. A probe about work you did last week is a quiz.
|
|
90
|
+
|
|
91
|
+
## Privacy — read this before installing
|
|
92
|
+
|
|
93
|
+
grask reads your Claude Code transcripts, and it is not scoped to one project:
|
|
94
|
+
|
|
95
|
+
- **It reads every transcript under `~/.claude/projects/`**, across all your repositories.
|
|
96
|
+
- **It sends transcript content to a model** — your prompts, the agent's replies, and the
|
|
97
|
+
before/after text of edits — by shelling out to `claude -p`. That call runs under your
|
|
98
|
+
existing Claude Code authentication and is subject to whatever data policy your account
|
|
99
|
+
already has. No data goes anywhere else, and grask adds no telemetry.
|
|
100
|
+
- **It stores what it extracts locally**, in a SQLite database at `~/.claude/grask/`,
|
|
101
|
+
including verbatim quotes of things you typed.
|
|
102
|
+
|
|
103
|
+
Controls:
|
|
104
|
+
|
|
105
|
+
- `GRASK_HOME` relocates the database and log.
|
|
106
|
+
- The corpus tools take `--exclude` to skip projects by name substring, and `--root` to
|
|
107
|
+
point at a different transcript directory.
|
|
108
|
+
|
|
109
|
+
If any of your repositories are covered by an agreement that prohibits sending source to a
|
|
110
|
+
model, do not install the hook.
|
|
111
|
+
|
|
112
|
+
**Nothing derived from a real transcript belongs in this repository.** The tools write
|
|
113
|
+
under `GRASK_HOME` by default for exactly that reason, and `.gitignore` is a second line of
|
|
114
|
+
defence. This project has made that mistake once already.
|
|
115
|
+
|
|
116
|
+
## How it works
|
|
117
|
+
|
|
118
|
+
Four stages, cheapest first. Each one filters, so only what survives pays for the next.
|
|
119
|
+
|
|
120
|
+
| Stage | Module | Cost | Job |
|
|
121
|
+
|---|---|---|---|
|
|
122
|
+
| 0 — extract | `transcript.py` | free | Pull the developer's own turns out of a session log. Tool results, file snapshots, and injected skill text are not the developer thinking. Sessions with no human turns stop here. |
|
|
123
|
+
| 1 — triage | `triage.py` | one call | List every moment worth asking about, each anchored to a verbatim quote and the turn it came from. Sees turns and file *paths*, never file contents. Most sessions yield nothing. |
|
|
124
|
+
| — select | `select.py` | free | Rank the moments and pick one. Deliberately code, not prompt: a model asked to both find and choose picks arbitrarily, and the topic changed run to run on an unchanged session. |
|
|
125
|
+
| 2 — seed | `seed.py` | one call | State, as a falsifiable claim, what the developer may have accepted without understanding. Stored, so a better stage-3 prompt can re-ask the whole corpus later. |
|
|
126
|
+
| 3 — probe | `probe.py` | one call | Write one multiple-choice question about the mechanism, with the answer key and an explanation. |
|
|
127
|
+
|
|
128
|
+
Two rules are enforced in code rather than prompted for, because instruction is not a
|
|
129
|
+
control:
|
|
130
|
+
|
|
131
|
+
- **The evidence rule.** A triaged moment whose quote does not appear in the turn it names
|
|
132
|
+
is demoted to silence. Same for a seed quote that appears nowhere the developer typed.
|
|
133
|
+
- **The one-question rule.** A stem with two questions in it cannot have one correct
|
|
134
|
+
option, so it is rejected and regenerated, up to three attempts.
|
|
135
|
+
|
|
136
|
+
The question must also teach something portable. A question whose answer is "because this
|
|
137
|
+
file says so" is answerable only by whoever sat through the session and is worth nothing
|
|
138
|
+
once they close the file.
|
|
139
|
+
|
|
140
|
+
### Model selection
|
|
141
|
+
|
|
142
|
+
grask names no model. It calls `claude -p` with no `--model` flag, so every stage runs on
|
|
143
|
+
whatever you currently have selected, and there is no second credential to manage. Tools
|
|
144
|
+
are disallowed and slash commands disabled — grask sends one self-contained prompt and
|
|
145
|
+
wants one JSON object back, so letting it wander the repo would just cost money.
|
|
146
|
+
|
|
147
|
+
## Cost
|
|
148
|
+
|
|
149
|
+
grask's own prompts are small. The real cost is the Claude Code context inherited by
|
|
150
|
+
shelling out to `claude -p` at all, which scales with *your* configuration rather than with
|
|
151
|
+
grask — `--disable-slash-commands` exists in `llm.py` for precisely this reason and cuts a
|
|
152
|
+
call substantially on a large setup.
|
|
153
|
+
|
|
154
|
+
Stages 2 and 3 only run on sessions triage kept, which is a minority. The corpus tools
|
|
155
|
+
print an estimated range and refuse to spend without `--go`.
|
|
156
|
+
|
|
157
|
+
## Development
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
uv sync # working on grask itself, rather than installing it
|
|
161
|
+
uv run pytest # 221 tests, no network, no model calls, sub-second
|
|
162
|
+
uv run ruff check .
|
|
163
|
+
uv run mypy
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
The suite never calls a model. Every stage is injected as a plain callable, so the whole
|
|
167
|
+
pipeline is drivable by test doubles — that seam is why the tests are free and fast.
|
|
168
|
+
Tests marked `calibration` do call the real model and cost money; they are deselected by
|
|
169
|
+
default and run deliberately with `pytest -m calibration`.
|
|
170
|
+
|
|
171
|
+
### Corpus tools
|
|
172
|
+
|
|
173
|
+
Diagnostics for working on grask itself, not part of normal use. All of them write under
|
|
174
|
+
`GRASK_HOME`, never the working directory.
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
uv run python -m grask.survey # what's in the local transcript corpus (free)
|
|
178
|
+
uv run python -m grask.triage_run # run stage 1 over the corpus; costs money
|
|
179
|
+
uv run python -m grask.capture_run # run the full pipeline over past sessions; --go to spend
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
`capture_run` skips grask's own project by default. The sessions that end from now on are
|
|
183
|
+
overwhelmingly grask's own, and waiting for the queue to fill measures grask on grask.
|
|
184
|
+
|
|
185
|
+
## Status
|
|
186
|
+
|
|
187
|
+
Alpha, and honest about it. The capture pipeline, storage, both delivery surfaces, and
|
|
188
|
+
mechanical grading all work end to end.
|
|
189
|
+
|
|
190
|
+
Not built: resurfacing a missed question, and cross-session dedup — two sessions can
|
|
191
|
+
currently produce near-identical probes.
|
|
192
|
+
|
|
193
|
+
Design notes and the reasoning behind each decision are in
|
|
194
|
+
[`docs/design/grask-design.md`](docs/design/grask-design.md); [`IDEA.md`](IDEA.md) covers
|
|
195
|
+
what this is and why it might not work. [`docs/README.md`](docs/README.md) says which of
|
|
196
|
+
those documents are current and which are records.
|
|
197
|
+
|
|
198
|
+
## Contributing
|
|
199
|
+
|
|
200
|
+
Issues and pull requests are welcome — see [CONTRIBUTING.md](CONTRIBUTING.md) for the
|
|
201
|
+
three rules that are easy to break by accident, and [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md).
|
|
202
|
+
|
|
203
|
+
A question grask asked that was *bad* — wrong premise, wrong key, or testing nothing
|
|
204
|
+
portable — is the single most useful thing you can report. There is an issue template
|
|
205
|
+
for exactly that.
|
|
206
|
+
|
|
207
|
+
For anything where grask leaked, over-collected, or wrote outside `GRASK_HOME`, see
|
|
208
|
+
[SECURITY.md](SECURITY.md) and report it privately rather than in a public issue.
|
|
209
|
+
|
|
210
|
+
## License
|
|
211
|
+
|
|
212
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "grask"
|
|
3
|
+
version = "0.1.0-rc1"
|
|
4
|
+
description = "One question about your own code, at the end of a Claude Code session."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.12"
|
|
7
|
+
license = "MIT"
|
|
8
|
+
license-files = ["LICENSE"]
|
|
9
|
+
authors = [{ name = "Kumar Prateek" }]
|
|
10
|
+
keywords = ["claude-code", "learning", "code-comprehension", "developer-tools"]
|
|
11
|
+
classifiers = [
|
|
12
|
+
"Development Status :: 3 - Alpha",
|
|
13
|
+
"Environment :: Console",
|
|
14
|
+
"Intended Audience :: Developers",
|
|
15
|
+
"Programming Language :: Python :: 3.12",
|
|
16
|
+
"Programming Language :: Python :: 3.13",
|
|
17
|
+
"Topic :: Software Development :: Quality Assurance",
|
|
18
|
+
"Typing :: Typed",
|
|
19
|
+
]
|
|
20
|
+
# Nothing at runtime. grask shells out to the `claude` CLI the developer has
|
|
21
|
+
# already installed and authenticated, which is the whole of its model access.
|
|
22
|
+
dependencies = []
|
|
23
|
+
|
|
24
|
+
[project.urls]
|
|
25
|
+
Homepage = "https://github.com/imkp1/grask"
|
|
26
|
+
Repository = "https://github.com/imkp1/grask"
|
|
27
|
+
Issues = "https://github.com/imkp1/grask/issues"
|
|
28
|
+
|
|
29
|
+
[project.scripts]
|
|
30
|
+
grask = "grask.cli:main"
|
|
31
|
+
grask-hook = "grask.hook:main"
|
|
32
|
+
|
|
33
|
+
[build-system]
|
|
34
|
+
requires = ["uv_build>=0.11.31,<0.12.0"]
|
|
35
|
+
build-backend = "uv_build"
|
|
36
|
+
|
|
37
|
+
# `SKILL.md` lives inside the package rather than at the repo root, and
|
|
38
|
+
# `grask skill --install` reads it with importlib.resources. Anything outside
|
|
39
|
+
# `src/grask/` can only reach an sdist — and nobody installs an sdist's files,
|
|
40
|
+
# because pip and uv build a wheel from it first. A skill the user is told to
|
|
41
|
+
# copy has to be in the wheel or it does not exist for anyone who installed
|
|
42
|
+
# rather than cloned. CI asserts it is there.
|
|
43
|
+
|
|
44
|
+
[tool.pytest.ini_options]
|
|
45
|
+
# Calibration tests call the real model: they cost money and are not
|
|
46
|
+
# deterministic. Deselected by default so the ordinary suite stays free and
|
|
47
|
+
# offline; run them deliberately with `pytest -m calibration`.
|
|
48
|
+
addopts = "-m 'not calibration'"
|
|
49
|
+
markers = [
|
|
50
|
+
"calibration: measures real model behaviour, not our code. Costs money.",
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
[tool.ruff]
|
|
54
|
+
line-length = 100
|
|
55
|
+
target-version = "py312"
|
|
56
|
+
|
|
57
|
+
[tool.ruff.lint]
|
|
58
|
+
select = ["E", "F", "I", "UP", "B", "SIM"]
|
|
59
|
+
|
|
60
|
+
[tool.mypy]
|
|
61
|
+
python_version = "3.12"
|
|
62
|
+
files = ["src/grask"]
|
|
63
|
+
# The package ships py.typed, which is a promise to anyone importing it. These
|
|
64
|
+
# settings are what make that promise checkable rather than decorative.
|
|
65
|
+
strict = true
|
|
66
|
+
# The pipeline stages are injected as plain callables (`triage=`, `complete=`,
|
|
67
|
+
# `store_factory=`) so control flow is testable without spending money on a
|
|
68
|
+
# model. Annotating those parameters would pin test doubles to the production
|
|
69
|
+
# signature, which is the seam's whole point. Everything else is checked.
|
|
70
|
+
disallow_untyped_defs = false
|
|
71
|
+
disallow_incomplete_defs = false
|
|
72
|
+
|
|
73
|
+
[dependency-groups]
|
|
74
|
+
dev = [
|
|
75
|
+
"pytest>=9.1.1",
|
|
76
|
+
"ruff>=0.14",
|
|
77
|
+
"mypy>=1.14",
|
|
78
|
+
]
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: grask
|
|
3
|
+
description: Serve the next pending grask probe — a multiple-choice question about code the developer recently shipped — using the native question UI. Use when the user types /grask.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Serving a grask probe
|
|
7
|
+
|
|
8
|
+
Grask quizzes the developer on mechanisms they shipped without fully
|
|
9
|
+
understanding. You are the delivery surface only: you serve the question and
|
|
10
|
+
relay the answer. You never grade, never guess, and never see the answer key.
|
|
11
|
+
|
|
12
|
+
## Hard rules
|
|
13
|
+
|
|
14
|
+
- Never open or query grask's database directly. The two subcommands below are
|
|
15
|
+
the entire interface.
|
|
16
|
+
- Never speculate about which option is correct — not in text, not in labels,
|
|
17
|
+
not in previews, not before or after the pick. Grading happens in
|
|
18
|
+
`grask record`.
|
|
19
|
+
- One native question, one round. No confidence round, no follow-ups.
|
|
20
|
+
|
|
21
|
+
## Flow
|
|
22
|
+
|
|
23
|
+
1. Run:
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
grask serve --json
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
If the output is `{"pending": null}`, say there is nothing pending and stop.
|
|
30
|
+
If the command is not found, grask is not installed on this PATH — say so and
|
|
31
|
+
stop rather than hunting for a checkout to `cd` into.
|
|
32
|
+
|
|
33
|
+
2. Ask ONE native question, preview-style (like plan-mode option picks), built
|
|
34
|
+
entirely from the served JSON:
|
|
35
|
+
|
|
36
|
+
- `question`: a context line from `topic` and `created_at`, then the full
|
|
37
|
+
question text — e.g.
|
|
38
|
+
`from 2026-07-21 · idempotency of the retry path — What would happen if …?`
|
|
39
|
+
The question must be readable inside the picker itself; do not rely on
|
|
40
|
+
markdown printed before it.
|
|
41
|
+
- One option per stored option, in stored order:
|
|
42
|
+
- `label`: the letter plus the first few distinguishing words of the
|
|
43
|
+
option (e.g. `a) dedup to a no-op`). Keep labels short; they are not the
|
|
44
|
+
full text.
|
|
45
|
+
- `preview`: the full stored option text, verbatim and unabridged. The
|
|
46
|
+
side-by-side preview pane is where the developer reads the option.
|
|
47
|
+
- In exactly one option's preview (or the question text), append a footer
|
|
48
|
+
note: "Other" accepts `skip`, or `wrong: <what's off>` if the question
|
|
49
|
+
misreads what happened.
|
|
50
|
+
|
|
51
|
+
3. Record the result:
|
|
52
|
+
- Picked letter L: `grask record <probe_id> --pick L`
|
|
53
|
+
- Skipped: `grask record <probe_id> --skip`
|
|
54
|
+
- Premise rejected: `grask record <probe_id> --wrong --objection
|
|
55
|
+
"<their words>"` (omit `--objection` if they gave no reason).
|
|
56
|
+
|
|
57
|
+
Show the result: ✓ or ✗ from `outcome`, then the `explanation` verbatim. If
|
|
58
|
+
the command prints `{"error": ...}`, show the error and stop — do not retry
|
|
59
|
+
with different flags.
|
|
60
|
+
|
|
61
|
+
4. Run `serve` again. If another probe is pending, say so and offer to
|
|
62
|
+
continue — do not auto-serve it.
|