After this, you'll be able to write function stubs that reliably produce useful inline completions, instead of vague or wrong ones.
Before you start
Before diving in, complete Set Up Your Persistent Workspace so the model already knows your project context before you write your first stub.
The idea
Inline completions are pattern-matching engines. They predict what comes next based on what you've already written. The quality of the prediction is almost entirely a function of how much information you packed into the stub.
Here is the before and after: Two stubs for the same function. First: `function processOrders() {`. The model has no idea what Orders look like, what processing means, or what you expect to get back. It will hallucinate a plausible-looking body. Second: `function processOrders(orders: Order[]): { fulfilled: Order[]; failed: Order[] } {`. Now the model knows the input shape, the output shape, and the return structure. The completion it generates will use your actual types, respect the `fulfilled`/`failed` split, and almost certainly be usable.
Three things that make stubs completable. Typed parameters (the model sees exactly what it is working with). A descriptive name that signals intent, not just what (not `process` but `validateAndCharge`). A return type or a comment that describes the expected output. You do not need all three every time. Even one of them lifts the completion noticeably.
Try it (12 min)
Watch out for
Paste this into Claude:
I want to practice skeleton-driven completions. Here is a function stub I wrote that got a bad or vague completion: [paste your stub here, e.g. a plain `function doThing() {` with no types or detail]. Help me rewrite it as a completable stub: (1) Add typed parameters that describe the input shape. (2) Add a return type or an inline comment describing what the function produces. (3) If the name is vague, suggest a more intent-revealing name. Then explain why each change makes the completion more predictable. I will test the new stub in my editor and report whether the completion improved.What good looks like:
What a good response looks like:
Here is the rewritten stub for your `processOrders` function:
Before: `function processOrders() {`
After:
```typescript
/**
* Validates each order against inventory and marks it fulfilled or failed.
* Does not mutate the input array.
*/
function processOrders(orders: Order[]): { fulfilled: Order[]; failed: Order[] } {
```
Three changes and why each one matters. First, typed parameter `orders: Order[]`: the completion engine now knows the exact shape of what it is iterating over, including every field on your `Order` type. Second, return type `{ fulfilled: Order[]; failed: Order[] }`: the model knows it needs to split into two arrays, not return a boolean or a count. Third, the JSDoc comment: 'validates against inventory' and 'does not mutate' tells the model the business logic without requiring it to guess. When you trigger the completion now, you should see the body use your actual `Order` fields (like `order.id`, `order.quantity`) instead of invented ones. If it still invents fields, open your `Order` type definition file in another tab before completing.Go deeper (15 min)
Paste this into Claude:
Take a function you already wrote with AI completion. Strip it back to just the stub: signature, types, and a short comment. Delete the body. Ask your editor to complete it again from the stub only. Compare the two versions: the original completion and this one. Paste both here and tell me: which version is closer to what you actually needed, and why? If the second one is worse, what is missing from your stub that the first session had as surrounding context?
What good looks like:
What a good response looks like:
Here is the comparison you asked for: Original completion (from full session with surrounding context): The body used `this.inventoryService.check(order.sku)` and correctly referenced `order.customerId` for logging. It matched your actual service dependencies. Regenerated from clean stub only: The body invented `checkStock(item)` and used `order.itemId` instead of `order.sku`. Both wrong. The model did not have your `InventoryService` import or your `Order` type visible, so it guessed. What the original session had that the clean stub did not: your `InventoryService` import at the top of the file and 3 other functions in the same file that used `order.sku` as the identifier. The model pattern-matched from those. When you stripped the file to just the stub, that surrounding context disappeared. Fix for round 3: Add a one-line comment to the stub: `// Uses this.inventoryService.check(sku: string)` and a typed parameter `order: Order`. That gives the regeneration enough signal to get `order.sku` right without needing the full file context.
When this breaks
Claude can do it for you
Paste a vague stub into a chat with Claude and say: 'Rewrite this as a completable stub. Add typed parameters, a return type, and rename it if the name is vague. Explain each change.' You will get a template you can paste back into your editor before completing.
You can now
Rewrite a vague untyped stub into one with typed parameters, an intent-revealing name, and a return type or comment, and demonstrate that the resulting completion needs at most one or two lines of editing.
Key takeaways
The completion is only as good as the stub. Type your parameters, name your intent, declare your return shape. Then hit Tab.