Skip to content

Repository files navigation

Code Trainer

Get faster at writing the code you already understand.

Reading a solution teaches you the idea. It does not teach your hands the syntax, and in an interview it is your hands that are being watched. Code Trainer is a VS Code extension that makes you type every solution out — no pasting, no autocomplete unless you ask for it — and measures how fast and how accurately you do it.

Install it in one command:

curl -fsSL https://raw.githubusercontent.com/jairoFernandez/code-coach/main/scripts/install.sh | bash

Then reload the window and open the Code Trainer icon in the activity bar. Not on the Marketplace — see Installing it for what the script does and how to pin a version.

It ships with a catalogue of interview problems, generates fresh ones with a local Ollama model, and tracks which topics you keep failing so the next exercise targets them.

Practice modes

Mode Ghost text IntelliSense Paste What it trains
Typing target the full solution off off raw speed and accuracy
Assisted AI, after a 900ms pause after a 700ms pause, costs points off fluency with help
TDD off off off test-first discipline
Blind off off off pure recall

In typing target the reference solution is shown as dimmed ghost text and only the correct character advances — a wrong key is swallowed and counted against accuracy. In TDD the failing tests open first, a watcher starts in the terminal, and you write the implementation with no assistance at all.

Paste is blocked in every mode. That's the point.

Problem catalogue

The Code Trainer icon in the activity bar opens the catalogue, grouped by interview pattern — the unit you actually study for interviews. Difficulty is shown per problem (E/M/H), and the pattern header tracks your progress.

Patterns, in study order: arrays & hashing, two pointers, sliding window, stack, binary search, linked list, trees, heap, backtracking, graphs, dynamic programming, intervals, greedy, design.

It ships with 13 hand-written problems. Clicking one asks for the language and mode, then starts a session.

Growing the catalogue

Three ways, under Add problems in the sidebar:

Command What it does
Generate with the model Pick a pattern, difficulty and count; the model writes them
From a statement Paste a problem you already have; the model writes the tests and solution around it
Import / export Load or share a JSON pack — the format is documented in the command

From a statement is two steps. The first run opens a scratch document; write or paste the statement there, then run it again (or use Code Trainer: Add Problem From a Statement from the palette) to build the exercise. A modal prompt would have sat on top of the editor, leaving nowhere to type.

Export covers your own problems, the bundled catalogue, or both — the bundled ones are the best worked example of the pack format if you want to write your own.

Retries are repairs, not redos. When an attempt fails, the model is shown its own output and the exact complaint — a missing field, or the failing test output — and asked to correct it. Re-asking the original question just reproduces the same mistake. Measured: gpt-oss:20b recovered three missing fields from a broken reply in 29s.

Watch it work. Generation opens the Code Trainer output channel and logs every step: when the model starts responding, how long the reply took, each test run per language with its verdict, and the failing output when something is rejected. A run that takes minutes is then legible rather than a spinner.

Everything is verified by running it. A generated or imported problem is materialised to a scratch directory and its tests are executed twice: against the reference solution (must pass) and against the starter (must fail). Anything else is rejected, and generation retries up to three times before giving up.

That two-sided check is the point. A model will happily emit tests that don't compile, assert the wrong answer, or pass against an empty function — none of which any static check catches. Measured on a real run: qwen2.5-coder:0.5b produced an unusable problem in 2.8s and was rejected; gpt-oss:20b produced a working one in 54s and was accepted. Use a model of at least 7B or expect to wait through retries.

Only the languages that verify are saved, so a problem never ships with one working implementation and one broken one.

Ask the coach

The Ask view below the catalogue is a chat with the local model. It is given the problem statement, the reference solution and the code you have actually written, so it can answer "what's wrong with my approach?" concretely. It is told to hint rather than hand over the answer unless you ask outright.

The conversation has two surfaces:

  • The sidebar lists your conversations, most recent first, with a preview and a relative timestamp. Clicking one opens it in the panel and marks the row selected — it does not expand inline, since the panel is already showing those messages. Each conversation can be deleted on its own. There is no input box here: a textarea and a send button cost more vertical space than the sidebar has.
  • The panel (Ask the Coach, or the button in the sidebar) is where you type. It opens beside the code, reads top-to-bottom, and has room for code blocks.

Both read the same history from the host, so a reply asked for in the panel appears in the sidebar as it arrives — including the "thinking…" state.

Conversations are stored in the framework's mini-ORM (src/models/ChatTurn.ts, global provider). Each has its own identity and is labelled by the problem it was started on, so one problem can hold as many conversations as you like — New conversation always starts a fresh one. Questions asked with no exercise open go to a "General questions" thread. They survive a window reload, and Clear only empties the one in view.

Opening a past conversation pins it until you press ← Current or start a new exercise, so browsing history never redirects a question to the wrong thread.

The conversation resets when you start a new exercise.

Commands

Command Keybinding
Code Trainer: Start Practice Session cmd+alt+p
Code Trainer: Run Exercise Tests cmd+alt+t
Code Trainer: Debug Exercise Tests cmd+alt+d
Code Trainer: Finish Practice Session
Code Trainer: Open Dashboard
Code Trainer: Settings
(catalogue) Open Problem click

The status bar shows live WPM, accuracy and progress, and turns amber when accuracy drops below 85%. Click it for the action menu. Between sessions it shows your day streak instead, and turns amber when a streak you already have has not been fed today.

Goals and streaks

The dashboard's Habit card is about consistency rather than speed: your current and best day streak, how many days you have practised in total, and the last 30 days as a calendar.

A day counts when you finish a session — passing is not required. A day spent losing to a hard problem is still a day of practice, and breaking your streak over it would be exactly the wrong incentive. Days are your local calendar days, so a late-night session and an early-morning one are two days.

The daily goal is problems or minutes, and either one satisfies it: some days are one hard problem over half an hour, others are six quick drills. Edit it with Edit goal on the card; set a half to zero to ignore it. The default is 1 problem or 15 minutes.

The streak also appears above the problem list in the sidebar, and an AI agent can read it through get_practice_habits.

Debugging an exercise

Debug Exercise Tests (cmd+alt+d, or the Debug button in the dashboard) runs the exercise's tests under the debugger: set breakpoints in your file, and the tests drive it. Debug runs are counted in the session metrics but never scored against — stepping through your own attempt is learning, not cheating.

TypeScript and JavaScript debug through Bun's debug adapter (the tests import bun:test, so Node cannot run them) and need the Bun for Visual Studio Code extension — the command offers to install it. Python uses the Python extension's debugpy running pytest.

Not available in typing target: mid-transcription the file does not compile yet, so there is nothing to debug.

Languages

TypeScript, JavaScript (bun test) and Python (pytest). Adding a language means one entry in src/lib/languages.ts — nothing else branches on language.

Ollama

Exercises, ghost text and post-session coaching all come from a local model. Configure it in settings:

{
  "codetrainer.llm.provider": "ollama",               // or "openai"
  "codetrainer.llm.baseUrl": "http://localhost:11434",
  "codetrainer.llm.model": "",                        // empty = auto-select an installed model
  "codetrainer.llm.apiKey": ""                        // only for remote endpoints
}

Changing the model. Run Code Trainer: Settings — from the palette, the sidebar, or the status bar menu — and pick from the models actually installed on your endpoint. The chat header has the same dropdown. The endpoint URL and everything else is editable there too, or in the Settings UI under codetrainer.llm.

Model names need their tag. Ollama treats qwen2.5-coder and qwen2.5-coder:0.5b as different models, and asking for the untagged name 404s. Leave the setting empty to auto-select, or pick from the dropdown in the dashboard — it lists only models you have actually pulled. If the configured model is missing, the client resolves it to an installed one and the dashboard dot turns amber.

Use a coding model of reasonable size. A 0.5b model returns JSON whose code fields are nonsense; the generator validates the output and falls back to the built-in exercise bank rather than putting junk in your editor, but you'll get much better exercises from something like qwen2.5-coder:7b and up.

If Ollama is unreachable everything still works — you get the built-in exercises, no ghost text, and no coaching.

Problem language

Statements and hints can be shown in English or Spanish — pick with Problem language in the sidebar, or codetrainer.problemLanguage.

Only prose is translated. Function names, tests and reference solutions stay in English: translating them would break the test runner, and interviews are conducted against English identifiers anyway.

Problems ship in English. Translate missing problems uses the model to fill in the rest; translations are stored separately from the problems, so a bundled problem can be translated without being duplicated.

Connect an AI agent (MCP)

Connect an AI agent in the sidebar gives you a one-line command to wire Claude Code — or Codex, Kimi, anything speaking MCP — into your catalogue and practice data.

Tool Purpose
get_practice_stats Pass rate and accuracy per pattern, what you've never attempted, recent sessions
get_practice_habits Day streak, progress against your daily goal, per-day activity
list_problems The whole catalogue, filterable by pattern, difficulty or language
get_problem One problem in full — statement, starter, solution, tests
create_problem Add a problem; its tests are run before it is saved
get_storage_info Where the data lives, and whether anything is recorded

Worth asking it:

I have an interview in two weeks. Look at my stats, work out what's missing, and build a day-by-day plan — creating any problems I don't have yet.

A capable agent writes far better problems than a local 7B model, and the verification is identical: tests must pass against the solution and fail against the starter, or nothing is saved. Your catalogue cannot be filled with broken exercises, whoever wrote them.

How it works. The server is a separate process that reads and writes the same JSON files the extension's ORM uses — no port, no daemon, no sync. It cannot drive a live practice session; that stays in the editor, which is where typing belongs. Problems it adds appear after a window reload.

Configure it once. The path you are given is in VS Code's global storage, not inside the extension folder — extension directories carry the version (…/jairoFernandez.code-trainer-0.0.1/) and are replaced on every update, which would silently break the config. The extension refreshes the copy at the stable path each time it starts.

Performance tracking

Every finished session is recorded (in globalState, so it follows you across workspaces). The dashboard shows average and best WPM, accuracy, pass rate, a WPM trend sparkline, and a weakest topics table ranked by pass rate then accuracy. Those weak topics are fed straight back into the next exercise generation.

Testing

bun run test            # 375 tests
bun run test:coverage   # with a coverage report
bun run test:watch

Tests live in test/unit. bun run package runs them first, so a broken build cannot be published.

Coverage sits around 77% of functions / 84% of lines, and is reported rather than enforced: src/mcp/store.ts is exercised by spawning the built MCP server as a subprocess, which the instrumenter cannot observe, so a threshold would fail every run instead of catching a regression.

test/helpers/ holds copies of pure functions lifted out of src — importing the real module would drag in vscode. helper-drift.test.ts pins each copy to its original, so a copy cannot silently go stale.

Installing it

The extension is not on the Marketplace. Each tagged release publishes a .vsix to GitHub Releases, and this installs the latest one:

curl -fsSL https://raw.githubusercontent.com/jairoFernandez/code-coach/main/scripts/install.sh | bash

It finds your editor CLI (code, cursor, codium, …), downloads the .vsix, checks it against the published SHA-256, and installs it. To pin a version or choose the editor, pass options through:

curl -fsSL https://raw.githubusercontent.com/jairoFernandez/code-coach/main/scripts/install.sh | bash -s -- --version 0.1.2 --editor cursor

Then reload the window — Cmd+Shift+PDeveloper: Reload Window.

Prefer not to pipe a script into a shell? Download the .vsix from the releases page and install it directly:

code --install-extension code-trainer-0.1.2.vsix

Building it yourself

To build from source and run in your own editor rather than a development host:

make install     # test, build, package, and install the .vsix

Then reload the window — Cmd+Shift+PDeveloper: Reload Window.

make on its own lists every target. The ones you are likely to want:

Target What it does
make install Build a production .vsix and install it
make install-release Install the latest published release instead of building
make reinstall Remove the installed copy first — use after changing the id or contributions
make uninstall Remove it from your editor
make package Build the .vsix without installing
make run Open a development host instead of installing
make clean Delete dist/ and any packaged .vsix

make package skips the rebuild when nothing under src/ has changed. For a fork of VS Code, pass its CLI: make install CODE=cursor.

Releasing

Every push and pull request runs CI: typecheck, unit tests, a production build and a vsce package, with the resulting .vsix kept as a build artefact.

Cutting a release is a tag:

git tag v0.1.3 && git push origin v0.1.3

That builds, packages and publishes the .vsix and its SHA-256 to GitHub Releases, which is what scripts/install.sh installs from. The Release workflow can also be run by hand from the Actions tab with a version — it creates the tag itself in that case.

The tag is what decides the shipped version; package.json keeps its plain numeric version in git, and the release stamps the tag's version in at build time (see scripts/stamp-version.ts).

Development

bun install
bun run dev      # watch both the extension and the webview, then press F5
bun run launch   # or: build once and open an Extension Development Host
bun run package  # produce a .vsix

bun run dev only builds — F5 (or bun run launch) is what opens the dev host. Reload the dev host with Cmd+R after a change.

Built on vsceasy. The editor-surface primitives it uses — defineCompletion, defineInlineCompletion, defineTypingGuard, defineDecoration, defineTerminal and the LLM client — were added to the framework for this project; see its docs/guides/editor-surface.md.

Layout

src/
├── completions/hints.ts          # non-invasive IntelliSense (delay + gate)
├── inlineCompletions/predict.ts  # Ollama-backed ghost text
├── typingGuards/practice.ts      # enforces the practice mode, blocks paste
├── decorations/target.ts         # renders the typing target
├── terminals/runner.ts           # test runner
├── statusBars/wpm.ts             # live scoreboard
├── commands/                     # start / run tests / finish
├── panels/dashboard.ts           # dashboard RPC
├── stores/session.ts             # the live session + metrics engine
└── lib/                          # languages, exercises, generator, history, runner

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages