Lesson 2: Write the task contract, let .cursor/rules carry the rules
Objective
You can read TASK.md as a set of testable statements, trace each
statement to a test in tests/test_report_tool.py, and write durable rules
a Cursor session will pick up automatically.
Why this lesson exists
Vague requests produce vague code. “Handle bad rows better” gives an agent
permission to guess; “invalid rows are collected in errors with index and
reason, valid rows still produce a report” gives it a target and gives you
a way to check. In Cursor there is a second failure mode: a rule you typed
into one chat does not exist in the next. The contract fixes the task;
project rules fix the working rules.
The lesson
Open TASK.md. Notice what every line has in common: it names an
observable behavior, not an implementation. Four statements from the
contract, and the tests that pin them:
| Contract line | Test |
|---|
| “a JSON file loads into the same record list as CSV” | test_load_json_records_returns_list_of_dicts |
“invalid rows land in errors with index and reason; valid rows still aggregate” | test_invalid_records_are_isolated_with_reasons |
| “group totals are rounded to two decimals” | test_group_totals_are_rounded_to_two_decimals |
| “the report write is atomic and creates missing parents” | test_write_report_creates_missing_parent_directories |
Now the durable half. Cursor reads project rules from .cursor/rules/ —
each rule is an .mdc file whose front matter controls when it applies:
alwaysApply: true attaches it to every request, globs attaches it when
matching files are in play, and description lets the agent pull it in
when relevant. This course’s working rules belong in an always-on rule.
Tell the agent:
“Create .cursor/rules/flypython-course.mdc with alwaysApply: true
containing these working rules: only starter/report_tool.py may change;
tests/, solution/, scenario/ are read-only; one failing test group per
turn; run python verify.py starter after each; standard library only.”
Then open a new chat and ask it to summarize its working rules. If it
quotes yours back, the rule is applying; if not, check the front matter —
a missing or mistyped alwaysApply is the usual cause.
Exercise
Write one contract line for a script you actually own, using the same
shape: inputs, outputs, error cases, and “done means <command> exits 0”.
Then write two rules you would put in that project’s .cursor/rules/ and
decide which front-matter field controls each.
Checkpoint
Run python verify.py — this checkpoint’s claim code prints when
you can answer:
- Which
TASK.md line does test_invalid_records_are_isolated_with_reasons
pin, in your own words?
- Which mechanism carries rules across sessions — the chat prompt, or
.cursor/rules?
- What is the difference between a rule with
globs and a rule with
alwaysApply: true?
Expected evidence
Your drafted contract line, your two rules with their front-matter choice,
and the new chat’s summary of its working rules.