Feed Cursor the Right Context
order-api is still open. client.ts has the cache from lesson 5, reviewed and accepted in lesson 6. Today's request touches a different file, and it needs something neither of those lessons needed: a file Cursor has never read, because nobody has pointed it there yet.
The task: rate limit POST /orders
server.ts has three routes. POST /orders is the one that writes, and right now nothing stops a caller from hammering it. The fix is rate limiting, and your team already has an answer for this, because this isn't the first Express service anyone here has shipped. There's a src/middleware/rateLimiter.ts living in a sibling service, a small token-bucket limiter your team standardized on months ago specifically so nobody reinvents it per project.
Here's the file, so you can see what "the pattern" actually means before watching Cursor either find it or miss it:
// src/middleware/rateLimiter.ts (existing file, sibling service)
import type { Request, Response, NextFunction } from "express";
interface Bucket {
tokens: number;
lastRefill: number;
}
export function createRateLimiter(opts: { limit: number; windowMs: number }) {
const buckets = new Map<string, Bucket>();
return function rateLimiter(req: Request, res: Response, next: NextFunction) {
const key = req.ip ?? "unknown";
const now = Date.now();
const bucket = buckets.get(key) ?? { tokens: opts.limit, lastRefill: now };
const elapsed = now - bucket.lastRefill;
const refill = Math.floor(elapsed / opts.windowMs) * opts.limit;
bucket.tokens = Math.min(opts.limit, bucket.tokens + refill);
if (refill > 0) bucket.lastRefill = now;
if (bucket.tokens <= 0) {
buckets.set(key, bucket);
res.status(429).json({ error: "Too many requests" });
return;
}
bucket.tokens -= 1;
buckets.set(key, bucket);
next();
};
}Named export, no default, matching every convention .cursor/rules/order-api.mdc already states for this repo. A token bucket keyed by req.ip, refilled on a fixed window, returning a plain 429 with a JSON body. That's "the pattern." It is not written down anywhere Cursor indexed for order-api, because it lives in a different repo entirely. Knowing it exists is a fact in your head, not a fact in this project's index.
Say "the pattern" and watch Cursor guess
Open Composer in order-api and ask for the rate limiter without pointing at anything:
Add rate limiting to the POST /orders route in server.ts,
matching the pattern our team already uses elsewhere.
Cursor's index from lesson 2 makes every file in order-api searchable. It does not make files outside order-api visible, and "the pattern our team already uses elsewhere" is a phrase, not a pointer. Cursor answers the only way it can: by picking a reasonable, generic rate-limiting approach, because nothing in this prompt or this project tells it a specific one already exists. Run this prompt yourself against order-api and check what comes back against the token-bucket shape above: a fixed-window counter, a different library's middleware, a hand-rolled counter with no refill logic at all are all plausible outputs, and none of them is the pattern this team standardized on. Not wrong in isolation. Wrong for this team, because it isn't the pattern.
This is the same failure mode as lesson 5's "make this API faster," wearing a different coat. There, the prompt didn't say which file. Here, the prompt gestures at a source without supplying it. Both times, Cursor fills the gap with its own best guess instead of your team's actual answer, because a guess is all it has to work with.
Say @src/middleware/rateLimiter.ts and watch it match
Same request, one addition:
Add rate limiting to the POST /orders route in server.ts,
matching the pattern in @src/middleware/rateLimiter.ts.
Import createRateLimiter and apply it only to that route,
not the whole app.
@files is the most direct of Cursor's context mentions: you're naming one file and telling Cursor to read it before it writes anything. There's no search involved, no inference about which file might be relevant. You already know, so you say so.
The plausible diff imports createRateLimiter and wires it into exactly one route:
// src/server.ts
import express from "express";
import { OrderClient } from "./client";
import { createRateLimiter } from "./middleware/rateLimiter";
const app = express();
app.use(express.json());
const client = new OrderClient();
const ordersLimiter = createRateLimiter({ limit: 10, windowMs: 60_000 });
app.get("/orders/:id", (req, res) => {
res.json(client.get(req.params.id));
});
app.get("/customers/:id/orders", (req, res) => {
res.json(client.listForCustomer(req.params.id));
});
app.post("/orders", ordersLimiter, (req, res) => {
res.status(201).json({ received: true });
});
export default app;Check it the way lesson 6 taught you to: createRateLimiter is imported, not reimplemented inline, so a future fix to the limiter's logic only has to happen in one place. ordersLimiter is applied as middleware on POST /orders only, not app.use()'d globally, which matches "matching the pattern... not the whole app." Nothing about GET /orders/:id or GET /customers/:id/orders changed. The 429 shape from the middleware itself is untouched, because the request didn't ask you to change it, only to apply it. Same discipline as every diff before this one: you knew what to expect, so checking it took a minute instead of a re-read of the whole file.
It's easy to assume that because Cursor indexed order-api back in lesson 2, it already "knows" everything relevant to any request you make inside it. Indexing makes search possible. It doesn't make Cursor go run that search on your behalf for every prompt, and it definitely doesn't reach into a file it was never pointed at. An @-mention is the difference between "this is somewhere in scope" and "read this, specifically, right now."
The other four mention types
@files names one file. @codebase does the opposite: it tells Cursor to search across everything indexed in the current project when you don't know exactly which file has the answer. If you'd forgotten the rate limiter lived in a sibling file at all and just knew "we have a pattern for this somewhere," @codebase rate limit is the honest version of that half-memory, a directed search instead of a guess dressed up as one.
@docs points at an external documentation source Cursor has indexed, not your own repo. Ask it to apply ordersLimiter "per @docs Express middleware conventions" and Cursor pulls from Express's actual docs on app.use() and per-route middleware, rather than from whatever the model already believes about Express from training. Useful exactly when the authority you want cited is a real doc, not local code.
Two more exist and are worth knowing by name, not by depth here. @git pulls recent commits or blame into context, so you can ask Cursor to reason about who changed a line and why. @web runs a live web search when the answer isn't in your repo, your docs, or the model's training at all. Both are real tools with real uses later in a project's life. Neither one was the right tool for today's request, which is exactly the point: the mention you reach for should match where the answer actually lives, not be whichever one comes to mind first.
Checkpoint
What's ahead
The diff above looks right, and it is right, against this particular request. Next lesson is about the diffs that look just as clean and aren't: a bug seeded in the cache from lesson 5, invisible in the review itself, that only shows up once something actually runs.
A cache bug that survives a clean-looking diff, and what it takes to actually catch it.
Reply here and it goes straight to Rod. Same as replying to one of his emails.