# Code prompt vocabulary

Source: https://codewiki.com/ai-era/code-prompt-vocabulary/

> - **what**: Code prompt vocabulary names the observable contract, valid states, failures, and structural boundaries of a requested change.
> - **trap**: A familiar word such as “clean up,” “handle,” or “API” can hide several incompatible changes, and an agent may choose a plausible one.
> - **fix**: Name the exact symbol and caller, then state inputs, outputs, invariants, failure behavior, allowed structure, and acceptance evidence.

## What it is and why it exists

Code prompt vocabulary is the set of engineering terms used to describe a change without prescribing every line of its implementation. It gives names to what callers can observe, what must remain true, how failure appears, and where responsibility belongs. Those names help a developer and a coding agent distinguish changes that sound similar in everyday language.

“Make the order helper robust” leaves the target, valid input, failure result, compatibility requirement, and proof of completion open. The agent must fill those gaps from nearby code and statistical habit. It can produce clean, reasonable code that solves a different problem because the prompt never separated intent from implementation choice.

Precision doesn't mean filling a prompt with jargon. A technical term is useful only when both sides can connect it to a concrete symbol, behavior, or test. “Preserve the public function signature” is precise when the function is named; “use better abstractions” is still subjective even though it sounds technical.

The central distinction is between observable contract and internal structure. A caller observes accepted inputs, returned values, errors, side effects, ordering, and timing guarantees. It usually doesn't observe whether the implementation uses one helper or three, unless reflection, performance, stack traces, or repository conventions make that structure part of the effective contract.

An API contract is broader than an HTTP endpoint. A function exported from a module, a command-line program's flags and exit codes, an event payload, and a database migration interface all have consumers. A prompt should name those consumers because the same edit can be harmless inside a private helper and breaking at a published boundary.

You use this vocabulary when asking for a feature, bug fix, refactor, test, review, or explanation. It matters most when the repository admits several locally reasonable designs. The terms reduce ambiguity; examples and executable checks then prove that the chosen meaning matches the task.

### Four questions behind a change

Most code requests become clearer when they answer four different questions. Keeping the questions separate prevents an implementation preference from quietly replacing required behavior.

| Question | Vocabulary | Concrete form |
| --- | --- | --- |
| What may callers do? | interface, signature, parameter, return shape | `reserveSeats(inventory, requested)` returns a tagged result |
| What must stay true? | precondition, postcondition, invariant | available seats never become negative |
| How does trouble appear? | failure mode, exception, error value, timeout | invalid quantity returns `INVALID_QUANTITY` |
| Where should logic live? | boundary, responsibility, dependency, pure helper | delivery stays behind an injected function |

A fifth question concerns evidence: how will a reviewer know the change is correct? Acceptance criteria, examples, tests, type checks, and commands answer it. “Done” is a conclusion derived from that evidence, not another synonym for “the code looks sensible.”

### Vocabulary at the right granularity

Name the narrowest stable thing you actually care about. If the requirement concerns JSON fields, say “response shape,” not merely “API.” If it concerns whether a second identical request repeats a charge, say “idempotent effect,” not merely “safe retry.”

Conversely, don't demand an internal pattern when only behavior matters. Asking for a factory, strategy, or class can block a smaller implementation that satisfies the same contract. State structural constraints when they protect ownership, testability, performance, security, or an established repository boundary.

## How it works

A useful code prompt behaves like a small change contract. It identifies the target, describes the current and desired observable behavior, states what must not change, limits the edit surface, and names the verification. The agent can still investigate and choose details inside that frame.

The words work as coordinates rather than magic commands. “Parameter” points to a function definition, “argument” points to a call, “invariant” points across several states, and “failure mode” points to a path where the operation cannot deliver its normal result. Attaching each term to repository evidence removes most of its ambiguity.

### Interface vocabulary

A function signature describes the callable surface: parameter names and kinds, defaults, and, in typed code, annotations or declared types. A parameter is a named input slot in the definition. An argument is the value or expression supplied by a particular call.

This difference matters in prompts. “Rename the `timeoutMs` parameter to `timeout` without updating callers” is contradictory if callers use the name as a keyword. “At the `loadProfile(userId)` call site, pass the authenticated user's ID as the `userId` argument” identifies both sides of the binding.

Use these interface terms deliberately:

- **Public API:** a surface external consumers are allowed to depend on; name the consumer population.
- **Signature:** the callable's declared input surface; state whether changing it is allowed.
- **Return shape:** the fields, alternatives, or object type a successful call returns.
- **Protocol:** a set of operations or messages that participants must understand.
- **Call site:** a place that invokes the target; ask whether all static and dynamic callers were found.

“Preserve the interface” is incomplete unless the prompt names which parts are stable. Positional calls may tolerate a parameter rename that keyword calls do not. A new optional field may be compatible for tolerant consumers but fail a strict schema validator.

### Behavioral vocabulary

A precondition is something that must be true before an operation is valid. A postcondition is something the operation promises after successful completion. An invariant must remain true across every relevant public transition, not just on the happy path.

For a class, such a state rule is a class invariant. For a module or workflow, “state invariant” is usually the clearer phrase. State the rule as a predicate when possible: `available >= 0` is easier to test than “inventory stays valid.”

Side effect means an observable interaction beyond returning a value, such as writing a file, changing shared state, logging, sending a message, or calling a remote service. “Make it a pure function” asks for no such effect and for a result determined by explicit inputs. If time, randomness, environment variables, or hidden caches matter, either make them dependencies or name them as allowed inputs.

Ordering and multiplicity are part of behavior too. “Send the receipt after the transaction commits, at most once per order ID” is stronger than “send a receipt.” For retryable work, distinguish an idempotent effect from a function that merely returns the same value twice.

### Failure vocabulary

A failure mode is one specific way the operation can fail: invalid input, missing data, conflict, timeout, cancellation, dependency rejection, or partial completion. Naming the mode isn't enough; state its representation and which side effects may already have happened.

Separate expected domain failure from programmer error and infrastructure failure. “Return `{ ok: false, error: 'INSUFFICIENT_SEATS' }` when demand exceeds availability, throw `TypeError` for malformed inventory, and propagate delivery rejection unchanged” gives three paths distinct meanings. “Handle errors” gives an agent permission to swallow, wrap, log, retry, or translate all of them.

Failure semantics often include recovery rules:

- **Fail fast:** reject before side effects when a precondition is false.
- **Fail closed:** deny or preserve the safer state when validation is uncertain.
- **Atomic:** expose either the complete transition or none of it at the named boundary.
- **Retryable:** identify which failures permit another attempt and whether the effect is idempotent.
- **Best effort:** continue selected independent work while reporting what failed.

These terms aren't interchangeable. A batch can be best effort across records while each record update remains atomic. A timeout can be retryable only when the request carries an idempotency key or another deduplication mechanism.

### Structural vocabulary

An architecture boundary confines ownership, data access, change, or failure impact to a named part of the system. A dependency crosses such a boundary when one component calls another. A seam is a place where that dependency can be replaced for testing or variation.

“Extract validation into a pure helper, keep I/O in the command handler, and inject the sender through the existing `dependencies` object” describes responsibilities and allowed dependency direction. It doesn't dictate helper names or line-by-line control flow. The agent knows what can move and what must remain at the edge.

Common structural verbs have different effects:

| Verb | Requested transformation | Contract warning |
| --- | --- | --- |
| rename | change a symbol's name | find reflection, strings, imports, and keyword callers |
| extract | move logic behind a new unit | preserve evaluation order and side effects |
| inline | replace a named unit with its body | preserve reuse, recursion, and visibility needs |
| wrap | add behavior around an existing operation | define ordering and error propagation |
| replace | substitute one implementation or dependency | state compatibility and migration scope |
| deprecate | keep working while discouraging new use | define warning, replacement, and removal plan |

A refactor changes internal structure while preserving named observable behavior. If output, failure behavior, timing guarantee, or supported call shape intentionally changes, the task isn't only a refactor. Split the behavior change from the structural cleanup so each can be reviewed against its own evidence.

### Compatibility vocabulary

A breaking change can make a previously valid consumer interaction fail or mean something different. Backward compatibility means the newer provider continues to satisfy interactions that were valid under the older contract. Both depend on which consumers and contract version are in scope.

Source compatibility, binary compatibility, wire compatibility, and behavioral compatibility are different claims. In JavaScript, changing a returned array to an iterable may preserve a `for...of` caller while breaking code that reads `.length` or serializes the value. Ask for the specific compatibility dimension instead of saying “don't break anything.”

When current behavior is poorly documented, a characterization test records what consumers can observe before structural work begins. It doesn't declare every old quirk desirable. The prompt should identify which recorded cases are protected and which defect is meant to change.

### Acceptance vocabulary

Acceptance criteria translate intent into decidable observations. Each criterion should name a setup, action, and result, or point to an existing command that already encodes them. Negative cases are important because many ambiguous prompts agree on the happy path.

A compact request can follow this sequence:

1. **Target:** name the file, symbol, endpoint, command, or consumer boundary.
2. **Behavior:** state successful inputs and outputs using concrete examples.
3. **Constraints:** name invariants, compatibility, ordering, and forbidden side effects.
4. **Failures:** define invalid, missing, conflicting, timeout, and dependency paths that matter.
5. **Structure:** state required boundaries and patterns only where they carry design intent.
6. **Evidence:** name tests, commands, diffs, and manual checks that decide completion.

Not every request needs all six parts. A one-line private rename may need only target, scope, and tests. A payment retry or authentication change needs explicit failure and side-effect semantics because a plausible default is too risky.

## Examples

These examples show what precise prompt vocabulary produces in code. They aren't transcripts from a particular model; each is a runnable target whose contract can be reviewed without guessing. Every output below was produced locally with Node 24.

### Naming a function contract

Suppose the request is to add `formatShipmentId(prefix, sequence)`. The prompt names a public signature, two preconditions, an exact return format, and distinct exception types. Those terms determine behavior at the boundary while leaving validation layout and string construction to the implementation.

<!-- quick -->

```javascript
// file: shipment_label.js
function formatShipmentId(prefix, sequence) {
  if (!/^[A-Z]{2,4}$/.test(prefix)) {
    throw new TypeError("prefix must be 2-4 uppercase letters");
  }
  if (!Number.isSafeInteger(sequence) || sequence < 0) {
    throw new RangeError("sequence must be a non-negative safe integer");
  }
  return `${prefix}-${String(sequence).padStart(6, "0")}`;
}

for (const [prefix, sequence] of [
  ["EU", 42],
  ["RET", 7],
  ["e", 3],
  ["EU", -1],
]) {
  try {
    console.log(formatShipmentId(prefix, sequence));
  } catch (error) {
    console.log(`${error.name}: ${error.message}`);
  }
}
```

```text
EU-000042
RET-000007
TypeError: prefix must be 2-4 uppercase letters
RangeError: sequence must be a non-negative safe integer
```


<!-- /quick -->

The six-digit rule here is minimum width, not maximum width; `padStart()` doesn't truncate a longer number. A prompt that means “exactly six digits” must also set an upper bound. The third break case exposes another decision: the regular expression rejects a non-string by returning false, so the code reports the same `TypeError` message as for a malformed string.

The parameter names are part of the definition, while `"EU"` and `42` are arguments at one call site. That distinction lets a follow-up request say “keep both parameters but add a call-site test with sequence `0`” without confusing an input slot with one supplied value.

### Protecting an invariant across failure

The seat reservation contract states `available >= 0` as an invariant. Expected domain failures return tagged error values, malformed stored state throws, and success returns a new inventory object rather than mutating the input. Those choices make the failed transitions observable and testable.

```javascript
// file: reserve_seats.js
function reserveSeats(inventory, requested) {
  if (!Number.isSafeInteger(inventory.available) || inventory.available < 0) {
    throw new RangeError("inventory.available must be non-negative");
  }
  if (!Number.isSafeInteger(requested) || requested <= 0) {
    return { ok: false, error: "INVALID_QUANTITY" };
  }
  if (requested > inventory.available) {
    return { ok: false, error: "INSUFFICIENT_SEATS" };
  }
  return {
    ok: true,
    inventory: { ...inventory, available: inventory.available - requested },
  };
}

const initial = Object.freeze({ eventId: "conf-2026", available: 3 });
let current = initial;

for (const requested of [2, 2, 0]) {
  const result = reserveSeats(current, requested);
  console.log(JSON.stringify(result));
  if (result.ok) current = result.inventory;
}

console.log(`initial=${initial.available}, current=${current.available}`);
```

```text
{"ok":true,"inventory":{"eventId":"conf-2026","available":1}}
{"ok":false,"error":"INSUFFICIENT_SEATS"}
{"ok":false,"error":"INVALID_QUANTITY"}
initial=3, current=1
```

The first transition establishes the postcondition `current.available === 1`. The insufficient and invalid requests leave `current` unchanged, so the invariant survives both expected failure paths. Freezing `initial` is diagnostic support here; immutability is guaranteed by returning a new object, not by hoping every caller freezes its input.

If the actual requirement allowed partial reservations, the same words would produce the wrong behavior. The prompt must say whether a request for two seats when one remains fails atomically, reserves one and reports a remainder, or queues the demand. “Prevent negative inventory” alone doesn't select among them.

### Separating orchestration from side effects

The receipt task names `sendReceipt` as orchestration and keeps template loading and delivery behind injected dependency functions. The returned status is separate from the delivery side effect. This seam lets a test observe the exact message without accessing a network service.

```javascript
// file: send_receipt.js
async function sendReceipt(order, { loadTemplate, deliver }) {
  const template = await loadTemplate(order.locale);
  const body = template
    .replace("{customer}", order.customer)
    .replace("{total}", order.total);

  await deliver({
    to: order.email,
    subject: `Receipt ${order.id}`,
    body,
  });

  return { status: "sent", orderId: order.id };
}

const deliveries = [];
const dependencies = {
  loadTemplate: async (locale) =>
    locale === "fr" ? "Bonjour {customer}: {total}" : "Hello {customer}: {total}",
  deliver: async (message) => deliveries.push(message),
};

const result = await sendReceipt(
  {
    id: "A-17",
    customer: "Mina",
    email: "mina@example.test",
    locale: "fr",
    total: "24.00 EUR",
  },
  dependencies,
);

console.log(JSON.stringify(result));
console.log(JSON.stringify(deliveries));
```

```text
{"status":"sent","orderId":"A-17"}
[{"to":"mina@example.test","subject":"Receipt A-17","body":"Bonjour Mina: 24.00 EUR"}]
```

The boundary is precise enough to test, but the failure contract is still incomplete. A production prompt must say what happens when template loading or delivery rejects, whether delivery can be retried, and whether duplicate receipts are acceptable. The happy-path output cannot answer those product questions.

“Mock the email service” would couple the request to one testing technique. “Keep delivery behind an injected callable and assert the message passed across that seam” names the replaceable boundary and observation. A hand-written fake, spy, or framework mock can all satisfy it.

## Pitfalls

### Using umbrella verbs without an observation

> **Pitfall:** Verbs such as “fix,” “handle,” “support,” “optimize,” and “clean up” don't say what changes for a caller. An agent may add a fallback where the product requires rejection, or cache a result where freshness matters.

**Fix:** follow the verb with a before-and-after observation. Name at least one input, output or side effect, and one important negative case. Replace “handle missing users” with “return `null` for an absent user; propagate database failures and don't log a successful lookup.”

### Confusing behavior with a preferred implementation

> **Pitfall:** “Use a class to make retries reliable” names a structure but doesn't define reliability. The generated class can still duplicate side effects, retry permanent failures, or ignore cancellation.

**Fix:** state retryable failure classes, attempt limit, backoff input, cancellation behavior, and idempotent effect first. Require a class only if object identity, protocol conformance, state ownership, or repository conventions make it part of the design constraint.

### Saying “keep the API unchanged” without naming consumers

> **Pitfall:** API can mean a function signature, JSON schema, HTTP status, event topic, CLI output, or all of them. An edit can preserve one surface while breaking another consumer through error text, field omission, or call ordering.

**Fix:** list the protected consumers and observable dimensions. For example: “Keep positional and keyword calls to `loadUser(id, timeout=...)`, preserve the returned fields and exception types, and don't change the CLI wrapper's exit codes.”

### Naming an invariant without its boundary

> **Pitfall:** “Balance never becomes negative” is ambiguous if pending holds, concurrent requests, or an external ledger can temporarily disagree. The implementation may enforce the rule at the wrong stage or only in memory.

**Fix:** name the authoritative state and observation point. Say “after every committed `withdraw` transaction, the row's available balance is non-negative; a rejected transaction writes neither the ledger entry nor the balance.” Add a concurrent test when operations can overlap.

### Treating exception, error result, and log as synonyms

> **Pitfall:** Asking to “return an error” can lead to a thrown exception, rejected promise, tagged value, sentinel, HTTP response, or logged message. Callers behave differently for each representation, and logging alone doesn't transfer failure to them.

**Fix:** specify the channel, type, stable code, message requirements, and propagation rule. Distinguish expected domain outcomes from invalid programmer input and unavailable dependencies. State whether any side effect may occur before the failure appears.

### Overloading the prompt with unexplained terms

> **Pitfall:** A dense request can combine “adapter,” “repository,” “atomic,” and “pure” even when the team uses those words differently. The vocabulary then creates false confidence rather than shared meaning.

**Fix:** attach each consequential term to a file, symbol, predicate, or example. Reuse names already present in repository documentation. If a term would change the architecture and its meaning isn't evidenced, ask the agent to report the ambiguity before editing.

<!-- deep -->

## Turning vocabulary into a change contract

A precise prompt still isn't a formal specification. Natural-language terms inherit repository conventions, and some requirements cannot be decided without product knowledge. The goal is to expose those decisions early enough that an agent can ask, investigate, or stop instead of hiding a guess in code.

### Observable behavior comes first

Start by drawing the consumer boundary. For each consumer, list what crosses it and what the consumer can distinguish. Two internal implementations are equivalent for the task only when all protected observations remain equivalent.

Useful observations include:

- accepted and rejected input domains, including empty, zero, maximum, duplicate, and malformed values;
- returned values, schemas, ordering, identity, mutability, and precision;
- exceptions, tagged failures, status codes, stderr, and exit codes;
- writes, network calls, messages, logs, metrics, and their ordering;
- latency, memory, or throughput only when a measured threshold belongs to the contract.

This list prevents “behavior-preserving” from becoming a vague blessing. It also shows where compatibility stops. A private helper's stack trace may be irrelevant, while a CLI's exact stderr can be consumed by tests or scripts and therefore needs an explicit decision.

### Invariants span transitions

An invariant is stronger than one expected result. It must survive construction, success, expected failure, dependency failure, cancellation, and any allowed concurrency. To make one actionable, name the state owner, transitions that can change it, and moments when observers may inspect it.

For `available >= 0`, ask what owns `available`, whether reservations are committed atomically, and whether pending holds count. Then test a successful decrement, an excessive request, a duplicate request, and overlapping requests at the actual storage boundary. A unit test around arithmetic alone can't prove the database transition.

Some rules are preconditions rather than invariants. “`requested` must be positive” constrains a call; it needn't be true of every object state. Some are postconditions: “on success, available decreases by exactly requested.” Naming the category makes the time boundary testable.

### Failure is a contract branch

Failure paths need the same precision as successful paths. Record the trigger, channel, stable identifier, retry rule, side effects already committed, and information safe to expose. This prevents an implementation from treating every caught exception as the same domain result.

| Failure question | Precise answer example |
| --- | --- |
| What triggers it? | requested seats exceed committed availability |
| How is it represented? | `{ ok: false, error: 'INSUFFICIENT_SEATS' }` |
| May the caller retry? | yes, after availability changes; identical retry has no effect |
| What already happened? | no inventory write and no confirmation message |
| What is observable? | stable error code; no internal database text |

“Propagate unchanged” and “translate” are useful opposites. Propagation keeps the dependency's failure identity and stack available to the caller. Translation maps it to a boundary-owned error, which needs an explicit cause policy so debugging information isn't silently destroyed or sensitive detail exposed.

### Structure needs a reason

Structural vocabulary should state ownership and dependency direction. A pure helper isolates deterministic policy; an adapter translates one interface into another; orchestration orders collaborators; a repository boundary owns persistence operations. These labels are valuable when the repository already gives them a stable meaning.

Before requiring a new abstraction, name the variation or risk it contains. An injected clock isolates time in tests. A delivery seam prevents domain policy from opening a network connection. A separate parser confines untrusted text validation. Without such a reason, adding a layer can increase navigation cost without clarifying the contract.

Internal names can still affect maintenance. Ask whether a new unit has one responsibility that can be stated without “and,” whether its dependencies point in the repository's accepted direction, and whether its interface is smaller than the implementation it hides. Those checks are more concrete than “make the architecture clean.”

### Compatibility is a set, not a slogan

To assess compatibility, define the set of previously valid interactions. A change is backward-compatible only relative to that set and its observations. Unknown consumers make certainty impossible, so a prompt can require search, deprecation, telemetry, or a migration rather than an unsupported guarantee.

Adding an optional parameter is source-compatible for many positional callers but may break reflection, generated bindings, or interface conformance. Adding a response field may be wire-compatible with tolerant decoders and incompatible with closed schemas. Reordering equivalent results may break snapshot tests or callers that display them directly.

State whether the task may introduce a compatibility shim. A shim can accept both old and new forms, emit a warning, and centralize translation during migration. It also creates a second path that needs a removal condition; “temporary” without an owner, deadline, or usage signal often becomes permanent.

### Evidence closes the vocabulary loop

Every important noun should lead to an inspection target, and every behavioral claim should lead to evidence. Signatures lead to definitions and call sites. Invariants lead to predicates across transitions. Failure modes lead to negative tests. Boundaries lead to imports and side-effect traces.

Use examples to disambiguate rules, then add counterexamples near the disputed edge. An example such as `formatShipmentId('EU', 42) === 'EU-000042'` fixes padding direction and width for one case. Counterexamples for lowercase prefixes, negative values, and seven-digit sequences reveal decisions that the example alone leaves open.

Generated tests can echo a misunderstanding, so compare each assertion with the original change contract. A test named “handles invalid input” proves little if it only asserts that nothing throws. Require the expected error channel, stable code, unchanged state, and absence of forbidden side effects.

The final review should be able to point from prompt term to repository fact:

1. Interface terms map to named definitions and consumers.
2. Behavioral terms map to predicates, examples, and negative cases.
3. Failure terms map to channels, recovery rules, and side-effect traces.
4. Structural terms map to files, dependency direction, and ownership.
5. Acceptance terms map to executed commands and observed outputs.

If one term has no such target, it is probably decorative or underspecified. Remove it, define it locally, or turn it into a question for the human who owns the product decision.

<!-- /deep -->

[Checkpoint: ai-era/code-prompt-vocabulary](https://codewiki.com/ai-era/code-prompt-vocabulary/#checkpoint)

## Further reading

- [GitHub Docs: Prompt engineering for GitHub Copilot Chat](https://docs.github.com/en/copilot/concepts/prompting/prompt-engineering)
- [Python documentation: parameter and argument terminology](https://docs.python.org/3/glossary.html#term-parameter)
- [Semantic Versioning 2.0.0: public API and incompatible changes](https://semver.org/)
- [RFC 9110: idempotent methods](https://www.rfc-editor.org/rfc/rfc9110.html#section-9.2.2)
