
# Copilot Instructions — Intent Driven Engineering (Python)
- Mark Kendall
- 3 days ago
- 4 min read
# Copilot Instructions — Intent-Driven Engineering (Python)
Save the below as pythonintent.md and paste or have copilot read me and you will be IDE modo Intent Driven Engineering
> **For workshop students:** This file teaches Copilot how *we* engineer.
> Drop it at `.github/copilot-instructions.md` in any repo. Copilot Chat,
> agent mode, and code review will read it automatically.
-----
## The one rule that governs everything
**State intent before generating code.** Implementation is downstream of a
clearly-named goal, a constraint set, and a definition of done. If any of
those three are missing, surface the gap — do not guess.
When asked to write code, your first move is to restate, in one or two
sentences, *what problem this solves and for whom*. If the user’s request
doesn’t make that clear, ask before writing.
-----
## How to respond to a request
Follow this order. Skip steps only when the user explicitly says “just
write it.”
1. **Restate the intent.** One sentence. Plain language. No jargon.
1. **Name the constraints.** What must be true? (perf, compatibility,
style, dependencies allowed, dependencies forbidden)
1. **Name the definition of done.** What test, behavior, or output proves
this works?
1. **Propose the approach.** Two or three sentences. Mention the *shape* of
the solution, not the syntax.
1. **Then write the code.**
If the user pushes back on any of the first four, revise before coding.
-----
## Python conventions for this codebase
- **Python 3.11+.** Use modern syntax: `match` statements, `|` union
types, `Self`, `LiteralString` where appropriate.
- **Type hints are mandatory** on every function signature, including
return types. Use `from __future__ import annotations` at the top of
every module.
- **Prefer pure functions.** Side effects belong at the edges (I/O,
network, DB). Business logic stays pure and testable.
- **Dataclasses or Pydantic for data, not dicts.** A `dict[str, Any]`
flowing through three functions is a missing type.
- **Errors are values when reasonable.** Raise exceptions for *truly
exceptional* conditions; return `Result`-like objects or explicit
`None` for expected failure modes.
- **No bare `except`.** Catch the specific exception or don’t catch.
- **Standard library first.** Reach for a dependency only when stdlib
costs more than it saves. Justify new dependencies in the PR
description.
- **Format with `ruff format`. Lint with `ruff check`.** Both must pass
before the code is “done.”
-----
## Testing — the contract, not an afterthought
- **Every behavior change ships with a test.** No exceptions in this
workshop’s codebase.
- **Use `pytest`.** Parametrize aggressively. One test, many cases.
- **Test names describe behavior, not functions.**
Good: `test_returns_empty_list_when_input_is_blank`.
Bad: `test_parse`.
- **Arrange-Act-Assert, visibly.** Whitespace between the three sections.
- **Mock at boundaries, not internals.** If you’re mocking a function in
the same module, the design is wrong.
-----
## What “done” looks like
A change is done when **all** of these are true:
- [ ] The intent is captured in the commit message or PR description.
- [ ] Types pass `mypy --strict` (or `pyright` in strict mode).
- [ ] `ruff check` and `ruff format --check` pass.
- [ ] New behavior has a test; the test fails without the change.
- [ ] No new dependency was added without a one-line justification.
- [ ] The diff is the *smallest* one that achieves the intent.
-----
## Things to avoid (and what to do instead)
|Avoid |Do instead |
|----------------------------------------|-------------------------------------------------|
|Writing code before stating intent |Restate the goal in one sentence first |
|`dict[str, Any]` as a function parameter|Define a dataclass or Pydantic model |
|Catching `Exception` broadly |Catch the specific class; let others propagate |
|Adding a dependency for one helper |Write the helper; it’s usually 10 lines |
|Comments that restate the code |Comments that explain *why*, not *what* |
|Long functions |Functions named after the single thing they do |
|Cleverness |Boring code that a tired teammate can read at 5pm|
-----
## When the user asks for something ambiguous
Examples of ambiguity and the right response:
- *“Add caching.”* → Ask: cache what, keyed on what, invalidated when,
stored where, with what TTL?
- *“Make it faster.”* → Ask: what’s the current measurement, what’s the
target, and what’s the workload we’re optimizing for?
- *“Fix this bug.”* → Ask: what’s the observed behavior vs. the expected
behavior, and what’s the smallest reproduction?
- *“Refactor this.”* → Ask: what property of the current code is wrong,
and what would ‘better’ let us do that we can’t do now?
A good question is faster than a wrong answer.
-----
## Workshop-specific notes
- This codebase is a **teaching artifact**. Optimize for *legibility over
cleverness*. A student should be able to read any function and explain
it in plain language.
- When you generate an example, **annotate the intent in a docstring**
so students see the practice modeled, not just the output.
- If you’re tempted to suggest something “advanced” (metaclasses,
descriptors, monkey-patching), pause and propose the boring version
first. Show the advanced version only if the user asks why.
-----
*This file is the contract. If you find yourself wanting to violate it,
say so out loud and explain why — that conversation is the work.*

Comments