blog.siika.net rss

How I use Cursor

I have written most of my code in Cursor since the spring of 2023. Cursor is a fork of Visual Studio Code (VS Code) with GPT-4 integrated into the editor. Several people have asked what the day-to-day workflow looks like after eight months of use, so this post describes the workflow as it is now, including the part of it that I am less comfortable with.

The clipboard workflow

During the first months of the year, my workflow with a language model was built around the clipboard. I wrote a question in ChatGPT, realized that the model needed to see the function, and copied the function. The model then needed the type that the function returned, so I copied that as well. The answer was pasted back into the editor, the places where the model had guessed an identifier incorrectly were fixed by hand, and the cycle repeated.

The limiting factor was not the model. The limiting factor was the pipe between the model and the code, which consisted of a clipboard and my patience. Most of what passed for prompt engineering was deciding which two hundred lines to paste so that the model would stop inventing parts of my own codebase.

Selecting files as context

The feature that convinced me to switch was small. In the chat panel, opened with Cmd+L, typing @ followed by a file name adds that file to the context of the conversation. The question is then asked against that context.

@routes/invoices.ts @lib/money.ts
The total on the invoice page is off by the VAT amount when a line item
has a discount. Find where the discount is applied and fix the rounding.

The first time the model answered with the actual defect in the actual file, located in a function I had not referenced because I had not thought of it, the difference from the clipboard workflow was obvious. The context assembly I had been doing by hand for months was now done by the editor.

My rule of thumb for which files to reference is the following: the file being changed, the file that calls it, and whatever defines the types that cross that boundary. Three files is usually sufficient. Beyond five files the answers become less specific, partly because of the context window and partly because the model has more material to be vague about. Cursor also offers @Codebase, which runs an embedding search over the repository and selects files automatically. In my experience it is useful for questions of the form “where is X handled” and less useful for edits, because it tends to retrieve the wrong half of a module.

For small edits I do not use the chat at all. Selecting a block and pressing Cmd+K opens an inline prompt, and the model edits the selection in place. Typical instructions are “make this async”, “extract the validation into a function”, or “add the missing cases to this switch”. This is the single feature I would miss most if I returned to stock VS Code.

Reviewing the diff

Whichever entry point is used, the result is presented as a diff. Removed lines are shown in red, added lines in green, and each hunk has its own accept and reject controls. Early on I went through these one at a time: read the removed line, read the added line, decide.

This per-hunk review was where most of the value was. The model’s first proposal for the invoice rounding was to round each line and then sum, which is the same defect in the opposite direction. Rejecting that hunk, keeping the other two, and asking a follow-up question in the same thread produced the correct fix on the second attempt. The per-hunk review is the mechanism through which the conversation improves the result.

The review also enforced a standard of understanding on my own code. If I could not state why an added line was correct, I did not keep it. Reviewing a change that I did not write, in code that I did write, turned out to be an effective way to find the places where the original code was unclear.

The shift toward accepting everything

Above every diff there is an Accept all button. For the first months I treated it as something to avoid. More recently I notice that I press it often.

The progression is gradual. The change is a rename across one file and every hunk is the same rename, so accepting all is reasonable. The next change is in a test file that will be executed anyway, so accepting all and running the tests is reasonable. Eventually the diff is forty lines, the model has been correct in the previous nine cases, and accepting all is the default.

I reviewed my own behavior over the past week. Of the edits requested from Cursor, I estimate that a clear majority were accepted with one click and a glance at the shape of the diff rather than a line-by-line read. The review moved elsewhere: I run the code, inspect the result, and move on if the result is correct. That is a legitimate form of review, but it is a different one, and it was not a decision I remember making.

Two aspects of this concern me. First, “the tests pass” is a much weaker claim than “I read the change and it is correct”, and the gap between the two is where the model’s confident mistakes accumulate. Second, the per-hunk review was how I learned what the model is bad at. Accepting everything often enough stops that learning.

Two aspects make the concern smaller. The model is measurably better than it was in March, and most of the changes I accept in bulk are the kind I would also have skimmed in a human colleague’s pull request. The failure mode is not new. It is the same as with any trusted colleague: the review becomes lighter because it has been earned, up to the point where it should not have been.

Current practice

The adjustments I am making are modest.

I do not expect the direction to reverse. Each month the editor takes over more of the context selection and more of the application of changes, and the explicit review step becomes thinner. It is plausible that within a year the diff view is something opened only when a test fails. I am not sure that I would prefer that outcome, but given how I have used the Accept all button, I am also not sure that I would notice.