Standalone lab
Build Your First GitHub Copilot Project
A guided build of a small, tested Python CLI using GitHub Copilot — writing the instruction file first, then letting Copilot work inside it, and verifying every step with commands you run yourself.
- Beginner
- Medium lab
- 5 min read
What you will be able to do
- Write a repository instruction file before writing code, and observe what changes in Copilot's output
- Use Copilot to implement a function against a specification you wrote first
- Generate tests with Copilot and identify the cases it did not think to cover
- Verify the result with commands rather than by reading the diff
Before you start
- Python 3.11 or later on your machine
- An editor with GitHub Copilot enabled, on any plan including Copilot Free
- A terminal, and comfort running a command in it
Preparation from the Academy: How to Get Started with GitHub Copilot, How to Create a copilot-instructions.md File, GitHub Copilot for Python
Most first projects with Copilot go the same way: you open an empty file, type a comment, accept what appears, and end up with something that runs. This lab does it in the other order — specification first, instruction file second, code third, and a verification step that can actually fail.
You will build a command-line tool that splits a bill between people, including the awkward part: the remainder when the total does not divide evenly.
That problem is chosen deliberately. It is small enough to finish, and it has a correct answer that a plausible-looking implementation gets wrong.
What you are building
A single command:
$ python -m billsplit 100.00 3
Each person pays: 33.34, 33.33, 33.33
Total: 100.00Three constraints make it non-trivial:
- The parts must sum exactly to the total. No lost or invented pennies.
- Money is decimal, not floating point.
- The remainder is distributed one cent at a time, not dumped on one person.
Step 1 — Create the project
mkdir billsplit && cd billsplit
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install pytest
mkdir billsplit tests
touch billsplit/__init__.pyStep 2 — Write the instruction file first
This is the step people skip, and it is the one that changes the output.
Create .github/copilot-instructions.md:
# Copilot instructions
This is a small Python 3.11+ command-line tool.
## Money
- Money is `Decimal`, never `float`. A float total is wrong before it is slow.
- Never distribute a remainder by rounding each share independently. Compute the
total in the smallest unit, divide, and hand out the remainder one unit at a
time.
- The parts of a split must sum exactly to the input total. This is testable and
must be tested.
## Style
- Type-annotate every signature including the return type.
- `from __future__ import annotations` at the top of every module.
- Raise `ValueError` with a message naming the bad input; never return `None`
to signal an error.
## Tests
- pytest. Test the cases a plausible implementation gets wrong: totals that do
not divide evenly, one person, zero amounts, and negative input.
- Assert exact `Decimal` values, never approximate comparisons.Step 3 — Write the specification, then ask for the code
Create billsplit/split.py containing only this:
"""Split a total into N parts that sum exactly to the total."""
from __future__ import annotations
from decimal import Decimal
def split(total: Decimal, people: int) -> list[Decimal]:
"""Split `total` between `people`, distributing any remainder one cent
at a time from the first share onward.
Raises ValueError if people < 1 or total is negative or not finite.
"""Now ask Copilot to fill it in:
Implement the split function according to its docstring and this repository’s instruction file. Work in whole cents internally. Do not round each share independently.
You should get something close to: convert to cents with
int(total * 100), use divmod by people, then add one cent to the first
remainder shares.
Step 4 — Add the command-line entry point
Create billsplit/__main__.py:
Add a main.py that reads a total and a person count from sys.argv, calls split, and prints the shares comma-separated followed by the total. Format every amount to exactly two decimal places. Exit with status 2 and a message on stderr for bad input.
Exit status 2 for a usage error is the convention the shell expects. A tool that exits 0 after failing is a tool that cannot be scripted.
Step 5 — Generate tests, then find what is missing
Write pytest tests for split() covering the cases named in the instruction file.
Now read them and ask what is not there. In our runs, Copilot reliably covers the even split and the uneven split, and reliably omits at least one of:
people = 1— the whole total goes to one person- a total of
0.00 - a negative total
- a total with more than two decimal places, such as
10.005
That last one is the interesting case, because there is no obviously correct answer and the docstring does not specify one. Decide what it should do, write that into the instruction file, and only then ask for the test.
Validation
Run these. All three must pass before you consider the lab done.
1. The tests pass:
pytest -q2. The parts always sum to the total. This is the property that matters, so check it exhaustively rather than trusting four examples:
python - <<'PY'
from decimal import Decimal
from billsplit.split import split
for cents in range(0, 2000):
total = Decimal(cents) / 100
for people in range(1, 13):
parts = split(total, people)
assert len(parts) == people, (total, people, parts)
assert sum(parts) == total, (total, people, parts, sum(parts))
print("24,000 splits checked — every one sums exactly.")
PY3. The command behaves:
python -m billsplit 100.00 3 # 33.34, 33.33, 33.33
python -m billsplit 10.00 4 # 2.50, 2.50, 2.50, 2.50
python -m billsplit 0.01 3 # 0.01, 0.00, 0.00
python -m billsplit 10.00 0 ; echo "exit=$?" # error, exit=2If step 2 raises an AssertionError, you have found a real bug — and you found
it with a command rather than by reading the code, which is the point.
Troubleshooting
Copilot ignores the instruction file. Almost always a path problem. The file
must be at .github/copilot-instructions.md relative to the workspace root your
editor has open. Opening the parent directory silently breaks it.
sum(parts) != total for some inputs. The implementation is rounding each
share instead of distributing whole cents. Ask Copilot to rewrite it working in
integer cents throughout, and re-run the exhaustive check.
Decimal(total * 100) gives something like 1000.0000000000001. The value
reached Decimal as a float. Construct from a string: Decimal("10.00"), and
parse sys.argv with Decimal(arg) directly rather than Decimal(float(arg)).
python -m billsplit says “No module named billsplit”. You are not in the
project root, or billsplit/__init__.py is missing.
Security considerations
Nothing here reaches a network, reads a credential or writes outside the project directory. Two habits worth starting now anyway:
- Add
.venv/and__pycache__/to.gitignorebefore your first commit. - Never paste real customer data into a prompt to “make the example realistic”. Prompt content leaves your machine.
Cleanup
Nothing was provisioned and nothing costs money. To remove the lab entirely:
deactivate
cd .. && rm -rf billsplitWhat you should take away
The instruction file did the work. The same prompts, in a repository without
.github/copilot-instructions.md, produce float arithmetic and independently
rounded shares — code that looks right, passes a casual reading, and loses a
penny on roughly one split in three.
The second habit is the validation step. “It should work” is not a check. Twenty-four thousand assertions is.
Sources
Every version-sensitive claim on this page was checked against first-party documentation. Only sources actually used are listed.
Finished the lab?
Stored in this browser. It appears on your progress dashboard, and syncs across devices if you sign in.
Was this lesson helpful?
We record which lesson you rated and whether it helped. Nothing identifies you — no account, no cookie, no session.