Chapter 9 — Reps
Eleven reps. Done in order, they are Milestone 9 — the repository, the skeleton, and the pipeline, built one bone at a time.
Ground rules
- Everything happens in your own repository. Nothing here is a paper exercise. Every rep leaves a commit, a file, an issue, or a CI run behind.
- Commit after every rep, with a real message. By Friday your history is the record of this week’s work, and in Week 16 somebody reads it.
- Log your hours as you go, not on Sunday night from memory. Two decimal places, the real number, in the log you started in Week 1.
- AI is allowed and expected — and everything it writes gets read by you before it is staged. Any generated code that survives into a commit gets a dated line in
docs/ai-usage.md. - Stack-agnostic. Every command below is written in the Node/PostgreSQL shape of the PantryPilot example. Translate it into your stack; the shape of the rep does not change.
- If a rep exposes a defect in your Week-6 specification, do not silently fix the code and move on. Write the change request against the Week-8 baseline. That is the process working.
Block A — The skeleton
Rep 1 — Draw the request path, honestly
Open your technical specification and list every component one real request passes through. Fill in Part 1 of code/skeleton-trace.md and put it in your repository at docs/skeleton-trace.md.
| # | Hop | Component in my system | Real? | Evidence |
|---|----------------------|------------------------|-------|----------|
| 1 | Client / entry point | | | |
| 2 | Route / dispatch | | | |
| 3 | Service / domain | | | |
| 4 | Data store | | | |
| 5 | Response | | | |
| 6 | Rendered result | | | |
A hop is yes only if the production component runs. A mock is no. A hard-coded array is no. A TODO is no.
Produce: the committed hop table.
Reflect (3–4 sentences): which hop is the first no? Were you tempted to write yes for any of them, and what were you telling yourself when you were?
Rep 2 — Kill the first mock
Take that first no and make it real. Not feature-complete — real. If it is the data store, that means a real table, a real migration, and one seeded row. If it is the route, a real handler that a browser or client can actually reach.
Produce: one commit whose message names the hop, e.g. feat(data): seed one real item row so the list is not hard-coded.
Reflect: how long did it take, versus your Week-7 estimate for the same work? Write the delta into your hours log with a one-line note about why.
Rep 3 — Rewrite five bad commit messages
These are real, and you have written all five. Rewrite each into a subject line under about fifty characters, imperative mood, with a one-sentence body explaining why:
1. fix
2. update code
3. WIP
4. Final version 2 FINAL
5. changes requested by prof
Then run git log --oneline -20 on your own repository and rewrite — on paper, not with a rebase — the three worst subject lines you find.
Produce: ten rewritten messages in your notes; then use the good form for the rest of the week. Reflect: read your last ten subject lines as a list. Do they read like the changelog of a project or the diary of a tired person? Be specific about what gave it away.
Block B — Reproducibility
Rep 4 — Build the script contract
Create the script/ directory (or your stack’s equivalent) with five files, each of which does its whole job in one command:
mkdir -p script
touch script/setup script/lint script/test script/build script/start
chmod +x script/*
Fill them in. script/setup must go from a fresh clone to a runnable project — dependencies from the lock file, database created, migrations applied, one row seeded. If that is eleven steps, then the file has eleven lines and the stranger still types one command.
Produce: five executable scripts, committed, plus a ## Run it section in README.md that uses them.
Reflect: which script was hardest to write, and what does that tell you about which part of your setup is not actually reproducible yet?
Rep 5 — The stranger’s machine
Prove your own instructions. On a timer:
git clone <your repo> /tmp/clean-clone
cd /tmp/clean-clone
# now follow YOUR README, exactly as written. No shortcuts, no memory.
Rules: you may not use anything not in the README. You may not cd back to your working copy. If you hit a step your README does not mention, stop the timer, fix the README, and start over.
Produce: the elapsed time, and the list of every README fix you had to make. Reflect: how many times did you restart? Every restart is a defect a grader would have hit in Week 16 — and you just found it for free.
Rep 6 — Pin everything
Three checks:
# 1. Is the runtime version in a FILE, not in your head?
ls -a | grep -E '\.nvmrc|\.python-version|\.tool-versions' ; grep -n '^go ' go.mod 2>/dev/null
# 2. Is the lock file committed?
git ls-files | grep -E 'lock|\.sum$'
# 3. Does setup install FROM the lock file, or resolve fresh?
grep -rn 'npm install\|pip install -r\|poetry add' script/ || echo "good: no fresh-resolve installs"
Fix whatever fails. npm ci, not npm install. poetry install, not poetry add.
Produce: a commit that pins the runtime version in a file and installs from the lock file. Reflect: if a dependency of yours published a breaking release tonight, would your build still pass tomorrow? Answer with evidence, not optimism.
Block C — Continuous integration
Rep 7 — Ship the pipeline
Copy code/ci-starter.yml to .github/workflows/ci.yml, fill in the two marked blocks for your stack, and push it on a branch. Open a pull request. Watch it run.
Expect it to fail two or three times. That is the rep — each failure is a thing your laptop was hiding from you.
Produce: a green CI run on a pull request, and the URL of that run in docs/skeleton-trace.md.
Reflect: list every failure it took to get green. For each one, say whether it was a real portability problem or a workflow-syntax problem. The first kind is the valuable one — name what your laptop had that a clean machine does not.
Rep 8 — Break it on purpose, twice
Never trust a pipeline you have not seen fail.
- Push a deliberately failing assertion. Confirm the build goes red, open the log, and find the exact line that reports the failure.
- Revert it. Now push a deliberate lint error — an unused import, a bad format. Confirm red again.
- Fix both and get back to green.
If either push stayed green, your pipeline is decorative. Find out why before you go further; the usual culprits are || true, a suite collecting zero tests, or a lint config with everything disabled.
Produce: two red run URLs, the green run that followed, and the fix commits. Reflect: how many scrolls did it take to find the real error in the CI log? If it was more than a few, what would you change about your test output so that future-you finds it faster at midnight in Week 12?
Rep 9 — Make the skeleton walk in CI
Copy code/smoke-test.sh to script/smoke, change the three defaults to match your project, and wire it into the pipeline as the last stage.
Then the honesty test:
# stop the database (or rename the file, or point DATABASE_URL at nothing)
./script/smoke # this MUST fail
If the smoke test passes with the data store down, it is not testing the skeleton — it is testing that a process is alive. Fix the test, not the database.
Produce: a passing script/smoke in CI, plus the terminal output showing it fail with the data store stopped.
Reflect: in one sentence, what exactly does your smoke test prove that a unit test of the same code does not?
Block D — Traceability and secrets
Rep 10 — Issues that point back at requirements
Create at least eight issues on your board. Every title starts with a requirement identifier from your baselined specification. Every body carries the slice, the estimate from your Week-7 work breakdown, and acceptance criteria as checkboxes:
Title: [FR-7] Add an item to the pantry from the web form
Requirement: FR-7, requirements spec v1.1 (baselined Week 8)
Slice: web form -> POST /api/items -> ItemService.add -> items table -> 201 -> refresh
Estimate: 3.0 h (WBS task 4.2)
Acceptance criteria
- [ ] Submitting name + expiry adds exactly one row
- [ ] An empty name is rejected and writes no row
- [ ] `./script/test` covers the rejection case
Then fill Part 2 of docs/skeleton-trace.md — one row per requirement, with its issue, branch, and what verifies it.
Produce: eight-plus traced issues and a completed traceability table. Reflect: which requirements have no issue, and which have nothing verifying them? Those two lists are the honest state of your project. Write them down.
Rep 11 — The secrets audit and the rotation plan
Work code/secrets-checklist.md end to end:
# tracked right now?
git ls-files | grep -Ei '\.env$|\.pem$|\.key$|credentials' || echo "clean"
# anywhere in HISTORY?
git log --all --name-only --pretty=format: | sort -u \
| grep -Ei '\.env$|\.pem$|\.key$|credentials' || echo "clean"
Then: commit .gitignore if it is not first in history, commit .env.example with every variable the app reads, move any real key into your CI provider’s repository secrets, and confirm the app starts on .env.example values plus a local database.
Finally, write four lines in docs/runbook.md (start the file if it does not exist): where each credential comes from, how to rotate it, who to tell, and how long rotation takes. Write it now, while nothing is on fire.
Produce: the audit output pasted into your milestone notes, plus the committed .env.example and rotation plan.
Reflect: if a key of yours leaked at 2 a.m. tonight, what is the first thing you would do? If your answer is “delete the file and force-push,” reread 9.7 — the answer is rotate first.
Done? One Last Thing.
Rep 12 — The walk, recorded
This is the milestone in miniature. Do it end to end, without stopping:
git cloneyour own repository into a directory you have never used.- Run two commands from your README and nothing else.
- Load the page (or run the command) and watch one real request travel every hop.
- Record it — screen capture or a series of screenshots: the clone, the two commands, the result, the green CI run on the same commit.
- Update
docs/skeleton-trace.md: the date the skeleton first walked, the commit that did it, the green run URL, the red run URL, and every hop still honestly markedstub.
If any step needs a command that is not in your README, that is the finding. Fix the README, then start the recording over.
Reflect (a paragraph, in your notes — you will reuse it in the milestone): what did the skeleton teach you about your architecture that six weeks of specification did not? Name one specific thing you now know that you only believed on Friday of Week 8.
Up next: Milestone 9