Quack: the AI coding assistant that can't touch your code
This is a submission for the Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady What I Built Quack is a rubber duck that lives in your terminal. You tell it what's broken. It finds the handful of lines in your repo that matter, asks you one question about your own code, explains the lik

Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady 馃Ε
This is a submission for the Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady
Quack is a rubber duck that lives in your terminal.
You tell it what's broken. It finds the handful of lines in your repo that matter, asks you one question about your own code, explains the likely cause with a file:line, and writes a TASKS.md for you to work through. Then it stops.
It cannot edit your source files. That isn't a promise in the README, it's a check in the code: every write goes through one function that allows TASKS.md, QUACK_HISTORY.md and.quack/, and nothing else. Afterwards, quack verify proves nothing changed.
quack ask "why does session_valid reject valid sessions?" pytest -x 2>&1 | quack ask "why does this fail?" --stdin # files in the traceback are read first quack verify # PASS: no source files were modified. Enter fullscreen mode Exit fullscreen mode Most coding assistants optimise for the patch. I wanted the opposite for one specific moment: when I want to understand the bug, not outsource it. The part of debugging I'm happy to skip is the hunt (grep, open five files, forget which one had the clue). Quack does the hunt and leaves the thinking to me.
It fits the theme literally. A sloth conserves energy; Quack conserves tokens. Indexing costs zero, only small line windows are ever sent, and a repeated question is answered from a local cache for free.
The part of Quack that costs nothing, run for real on the source of pydantic 2.14.1 (107 files), with no model involved:
$ quack index files: 107 symbols: 2518 mentions: 33979 todos: 63 time: 569 ms tokens: 0 $ quack index --changed # nothing changed since files: 107 symbols: 2518 mentions: 33979 todos: 63 time: 7 ms tokens: 0 $ quack ask "how does model validation handle extra fields" --dry-run Dry run. The duck would read: pydantic/config.py (score 12.8768) pydantic/fields.py (score 12.4624) pydantic/root_model.py (score 12.2613) context: 9893 chars (~2473 tokens), depth 2, model ollama:gemma3:4b would write: TASKS.md, QUACK_HISTORY.md,.quack/ (index, cache, history) source files: never. $ quack verify PASS: no source files were modified. Enter fullscreen mode Exit fullscreen mode And the model half, on a repo I know: [[FILL: paste your real quack ask output here, with the footer line showing tokens and model]]
git clone [ https://gitlab.com/quack-tutor/quack-hopper] && cd quack-tutor pip install -e. ollama pull gemma3:4b quack -C tests/fixtures/mini_repo ask "why does session_valid reject valid sessions?" Enter fullscreen mode Exit fullscreen mode Code [ https://gitlab.com/quack-tutor/quack-hopper ]
MIT licensed. quack/ is the CLI, web/ is the Astro landing page, and tests/ holds 69 tests that run with no network and no model.
The open pieces. The default model is Gemma 3 (4B) running locally through Ollama, using Ollama's structured output with the Pydantic schema so the answer arrives as validated JSON. Everything else is the standard library: SQLite, regex, and a hand-written BM25.
Context is trimmed to fit a 5,000 token budget, and Quack warns if a run goes over. The targets below come from my build spec. The last column is measured.
"Measured" is the system prompt plus retrieved context for the pydantic question above, estimated at 4 characters per token, before the model's answer. At depth 3 the raw context was about 5,100 tokens, so Quack trimmed it to fit the budget. Real usage varies by model, so quack ask --json prints what your provider reports.
Beyond one model. Remote providers (OpenRouter, Anthropic, Groq, Gemini and a dozen others, plus any OpenAI-compatible endpoint) are supported but blocked until you opt in. quack compare "..." -w ollama -w openrouter:google/gemma-3-27b-it asks the same question of several models and lays the answers side by side.
How I worked. I wrote a build spec first (it's in the repo as BUILD_SPEC.json ) and built the project with an AI coding assistant against it, then tested and reviewed the result. The tests use a fake model and a local HTTP server, so the whole suite runs offline in about three seconds.
What it doesn't do well. Symbols come from regex, not a parser, so unusual syntax can be missed. There are no embeddings; retrieval is keyword-based. A 4B model will sometimes be wrong, which is why the answer carries an honest confidence level, the grounding step exists, and the prompt tells it to say "show me another file" instead of guessing.
Quack's whole job is reading code you haven't shipped yet: pre-launch features, customer fixtures, internal TODOs. That's exactly what people hesitate to paste into a hosted assistant.
With open weights on localhost, "nothing leaves your machine" is a property I can test, not a policy I have to trust. A test asserts that Quack refuses to contact any non-local host until you opt in, and quack verify --audit lists every place the program can write to disk. I could never write those tests against a closed API.
The trade-off is honest: a local 4B model is less sharp than a frontier one. Open innovation is what lets me choose that trade per project (local by default, a bigger open model when I decide to), instead of having it chosen for me.
The payoff is the hunt that no longer happens. For a bug in code I don't know well, the usual loop is: search, open files, read, guess, search again. Quack collapses that into one command and a short checklist, and the question it asks first often points at the right place before I've read the explanation.
What I can measure is the cost side. On a codebase I didn't write (pydantic 2.14.1, 107 files), Quack indexed everything in about half a second for zero tokens, and re-indexing after a change took 7 ms. Choosing the files worth reading and cutting them down to line windows is plain Python over a local SQLite file, so the only tokens spent are the ~2,800 in the final prompt at the default depth. What it saves you in debugging time is something you'll feel on your own repo, so I won't invent a stopwatch number for it.