Pair Programming With AI
Who is the author when two work together?
Chapter 9 — Pair Programming With AI
“Now out of the ground the LORD God had formed every beast of the field and every bird of the heavens and brought them to the man to see what he would call them. And whatever the man called every living creature, that was its name.” — Genesis 2:19
“A fast junior who is sometimes wrong is still a fast junior.” — what every senior engineer learns in their first week of supervising one
Why This Matters
Eight weeks ago, the rule for every project was no AI. You wrote your own loops, your own classes, your own tests. You debugged your own stack traces. You earned a midterm that proved — to you, to your grader, and to anyone who later reads your code — that you can produce the work without a machine writing it for you.
That rule ends today.
For the next eight weeks, AI is on. Every project from here forward expects you to use it. The grader expects you to log every prompt. The course expects you to ship more — and harder — work than Phase 1 asked for, because you have a faster collaborator at your elbow.
That is not a relaxation of the standard. It is a raising of the standard.
In Phase 1 you were a solo athlete. In Phase 2 you are a coach with one player on the field — a player who can sprint without tiring, who will try whatever you ask, who has read more code than any human alive, and who will be confidently wrong roughly fifteen percent of the time, with no inner sense of when. Your job is no longer “produce the code.” Your job is now “produce the system,” which means deciding what gets built, supervising the building, catching the mistakes the machine cannot catch in itself, and putting your name on the final product because you stand behind every line of it.
This chapter introduces the senior/junior model that the rest of the book runs on. We will name what AI is genuinely good at, what it is genuinely bad at, and what your specific job is in the partnership. Then we will hand you Project 9 — your first formal exercise of spec → AI → review.
Coach’s Note — Eight weeks of Phase 1 was not hazing. It was the prerequisite for this chapter to be safe. A student who skipped Phase 1 and started here would not be a “senior engineer working with a junior.” They would be a junior pretending to be a senior, which is exactly the failure mode the rest of the internet calls “vibe coding.” You did the reps. You earned this chapter. Now use it.
9.1 — The Senior/Junior Model
Picture a real engineering team. A senior developer is on their second decade in the industry. A junior developer started six months ago. Both are paid. Both are valuable. They do different jobs.
The junior writes the lines. They knock out the boring methods, the boilerplate, the obvious translations from spec to code. They are fast. They are willing. They will try whatever the senior asks. They do not push back much. They do not always know when they are wrong.
The senior writes the system. They decide which classes exist and which do not. They write the specification the junior implements against. They review the junior’s code before it ships. They catch the off-by-one. They notice that the junior called a library method that doesn’t exist. They sign off on every commit because their reputation is on the line, not the junior’s.
A senior who cannot review code is a useless senior. A junior who cannot write code is a useless junior. Phase 1 trained you to be both. Phase 2 trains you to act as the senior while the AI plays the junior.
The model:
| The senior (you) | The junior (AI) |
|---|---|
| Decides what to build | Implements what you decide |
| Writes the specification | Reads the specification |
| Designs the architecture | Implements within the architecture |
| Reviews every line | Produces lines fast |
| Owns the bugs | Will introduce bugs the senior catches |
| Tests the work | (Sometimes) writes the tests too, but the senior decides which tests are real |
| Is accountable for the final product | Is a tool, not an author |
There is one more line in the table, and it is the important one:
| Has taste | Mimics taste convincingly |
Taste is the senior’s irreplaceable contribution. Taste is “this function should not exist; collapse it into the caller.” Taste is “this name is wrong — processData tells me nothing.” Taste is “we don’t need a Map here; a Set would do.” Taste is what tells you the AI’s solution works and is still ugly. Taste is what an LLM can pattern-match but cannot generate from first principles, because taste lives in the engineer’s experience of having shipped, broken, debugged, and re-read code for years.
You are eight chapters into building taste. You are not done. But you have enough to start supervising a junior. Begin.
9.2 — What AI Is Genuinely Good At
A modern code-capable model — any competent one: Claude, GPT, Gemini, the others — is genuinely excellent at five things. Lean on it for these.
1. Mechanical translation
“Translate this Python function to Java.” “Convert this for-loop to a Stream.” “Rewrite this method to use the Builder pattern.” The semantic content is preserved; the syntactic surface changes. AI is very good at this. The bug rate is low because the work is mostly substitution against patterns the model has seen a million times.
2. Boilerplate
equals and hashCode overrides. Getters and setters. A JUnit 5 test class skeleton with imports, a @BeforeEach, and one empty @Test. A Comparator<Person> that sorts by last name then first name. These are mechanical, voluminous, and tedious. Ask the AI for them. That is exactly the work it is built to do, and the work that — if you do it by hand — slows you down without making you a better engineer.
3. Common idiomatic patterns
“Show me how to read all lines of a file in Java 17.” “What’s the idiomatic way to handle an optional value in Java?” “Give me a Java 17 record for an immutable Point.” Patterns the language has one widely-accepted shape for, the AI will produce that shape correctly almost every time. Idiomatic Java is exactly the kind of thing that’s all over its training data.
4. Unit tests for clear specifications
Give the AI a method signature, a written spec, and a few example inputs and expected outputs. Ask for a JUnit 5 test class. You will get one. The tests will mostly be reasonable. Some will be redundant. A few might be missing edge cases that you will need to add by hand. But the skeleton — @Test, assertEquals, assertThrows, naming conventions — will be correct and idiomatic.
5. Explaining unfamiliar code
“What does this 40-line method do?” is a question AI answers well. Useful when you inherit a codebase, useful when you’re reading a library you’ve never used, useful as a check on your own reading (Chapter 1’s skill, now amplified). Don’t trust the explanation blindly — verify against the code — but it gets you to the right hypothesis fast.
Coach’s Note — Notice what those five have in common. They are all places where the answer is largely already known in the global corpus of Java, and your job is to retrieve it accurately. AI is a retrieval-and-pattern engine before it is a reasoning engine. Where the answer is in its training data and matches your situation, it is genuinely useful. The trouble starts when the answer is not in its training data, or your situation is subtly different from the one in the data.
9.3 — What AI Is Genuinely Bad At
The same model, the same prompt style, on different problems, will fail. The failures cluster into five categories. Learn them so you recognize them when they happen.
1. Architecture and decomposition
“Build me a habit tracker” is a request for architectural decisions: how many classes, what their boundaries are, where state lives, how persistence works, what the public API of each component is. AI will produce an answer to that question. The answer will look plausible. It will frequently be wrong in ways that hurt you in week three when you try to extend the system. We will spend all of Chapter 12 on why architecture is the senior’s job. For now: the bigger and vaguer the request, the worse the AI’s answer.
2. Taste
AI will name a method processData because the prompt was about “processing data.” It will write a 60-line method when a 12-line one would be clearer. It will introduce three helper classes nobody asked for. It will use a Map<String, Object> where a record would be obviously better. None of these are bugs. They are taste failures. They compound. A codebase full of them is hard to work in.
3. Novel domain logic
If the problem is “compute the optimal book ordering for a confessional library that prioritizes Lutheran systematic theology then patristic sources then Anabaptist primary texts,” there is no general pattern in the training data. The AI will generate something that looks like reasonable logic. It will mostly not be the logic you actually want, because the logic you want lives in your head and on the spec you (the senior) wrote — not in the global text corpus.
4. Knowing when it is wrong
This is the failure that makes the other four dangerous. When AI is wrong, it sounds exactly as confident as when it is right. There is no internal “I am unsure here” signal in the output, no flagging, no hedging — or at least, no reliable hedging. A senior engineer who is unsure will say so. The AI, asked the same question, will produce a confident answer of slightly worse quality and not flag the drop.
5. “Common knowledge” facts about your specific codebase
The AI does not know that you have a class called Roster with a method addMember(String, LocalDate). It does not know that your project conventions use snake_case JSON keys and not camelCase. It does not know that your team decided last week that null is forbidden in public APIs. Unless you tell it those facts in the prompt, it will guess based on what’s typical — and “what’s typical” might not be what’s correct here.
9.4 — The Hallucinated API
The most spectacular failure mode of AI code generation deserves its own section because you will see it constantly.
A hallucinated API is a method name, class name, library, or function the AI confidently uses that does not exist. Sometimes it never existed. Sometimes it existed in a different language or a different version. Sometimes the AI invented it on the spot by combining a plausible-sounding class name with a plausible-sounding method name. The code looks right. The code reads right. The code does not compile.
Here is a real-shaped example. Suppose you ask the AI for code that reads a CSV file in Java 17. A bad answer might look like this:
// BAD AI OUTPUT — looks plausible, contains a hallucinated API
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
public class CsvReader {
public static List<String[]> readCsv(Path path) throws Exception {
return Files.readAllLines(path)
.stream()
.map(line -> line.splitCsv()) // <-- there is no splitCsv() on String
.toList();
}
}
Three of the four lines are perfectly correct Java 17. The fourth, line.splitCsv(), is invented. String has no method called splitCsv. The compiler will tell you so. The AI sounded confident; the compiler does not care.
A good answer might look like this — same problem, the same model, but a more direct prompt and a senior who knows what’s real (download CsvReader.java):
// GOOD AI OUTPUT — uses only real APIs
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
public class CsvReader {
public static List<String[]> readCsv(Path path) throws java.io.IOException {
return Files.readAllLines(path)
.stream()
.map(line -> line.split(",")) // String.split(String) is real
.toList();
}
}
The fix was trivial. The skill was catching the fake API in the first place. This is why the compiler is your friend in Phase 2 the way it was in Phase 1: it does not lie. If something does not compile, it does not exist.
Coach’s Note — Treat any unfamiliar method name in AI output as guilty until proven innocent. The two-second check is to look it up in the official Java 17 API docs (docs.oracle.com/en/java/javase/17/docs/api/) or to just try to compile. A method name you’ve never seen before is more likely to be hallucinated than to be a hidden gem you didn’t know about.
Java 17 specifically — every chapter from here on is Java 17 — has a finite, documented API. Hallucinations in this language are easy to catch because the spec is closed. The same approach to other languages (JavaScript with its sprawling ecosystem, Python with its hundreds of libraries) is harder.
9.5 — Plausible But Wrong
A subtler failure than the hallucinated API: the AI produces code that compiles, runs, and passes the example you showed it, but is wrong on inputs you didn’t think to mention.
Consider this prompt: “Write a Java method that returns the average of an integer array.”
A common AI answer:
// BAD AI OUTPUT — works on the obvious case, breaks elsewhere
public static double average(int[] nums) {
int sum = 0;
for (int n : nums) sum += n;
return sum / nums.length; // integer division!
}
Two bugs here, both plausible:
- Integer division.
sum / nums.lengthis integer division because both operands are integers.average(new int[]{1, 2})returns1.0, not1.5. Cast one operand todouble. - Empty array. If
nums.length == 0, you divide by zero and either crash (/ by zero) or — fordoubledivision — returnNaN. Either way, no defined behavior.
A good answer, prompted by a senior who specified the edge cases:
// GOOD AI OUTPUT — spec-driven, edge cases handled
/**
* Returns the arithmetic mean of the values in {@code nums}.
* @throws IllegalArgumentException if {@code nums} is null or empty.
*/
public static double average(int[] nums) {
if (nums == null || nums.length == 0) {
throw new IllegalArgumentException("average requires a non-empty array");
}
long sum = 0; // long to avoid overflow on large arrays
for (int n : nums) sum += n;
return (double) sum / nums.length;
}
The difference is not the AI’s “intelligence.” The difference is the spec the AI was given. The good answer was prompted with: “signature, return type, behavior on empty input, behavior on overflow.” The bad answer was prompted with: “average of an array.” Same model. Different senior.
This will be the lesson of Chapter 10 in full. For now, internalize the pattern: AI output that works on the example is necessary, not sufficient. Your job — the reviewer’s job — is to think of the inputs the AI did not.
9.6 — The Senior’s Three Jobs
Every Phase 2 interaction with the AI involves three roles only you can play. Skip any of them and you are not a senior; you are a vibe coder with a faster keyboard.
Job 1 — Spec
Before the AI touches the code, you write down what the code should do. Method signature. Inputs. Outputs. Error behavior. Examples. The spec is what tells the AI what success looks like; it is also what you will check the AI’s output against.
This is the skill from Chapter 2. Phase 2 lives or dies on Chapter 2. A weak spec produces weak AI output; a strong spec produces — usually — strong AI output. If you skipped Project 2, go back and finish it before Project 9. There are no shortcuts here.
Job 2 — Review
After the AI gives you code, you read every line. Not skim. Read. The same way you read a piece of unfamiliar code in Chapter 1: methodically, suspiciously, looking for what is missing as much as for what is there.
The questions to ask at this stage:
- Does every method called actually exist? (Hallucinated API check.)
- Does the code handle the edge cases I wrote in the spec? Null inputs? Empty collections? Boundary values?
- Are the names of variables and methods clear? Would the next person to read this file understand them?
- Is there extra cruft — helper classes I didn’t ask for, abstractions I don’t need?
- Does it follow the conventions of the rest of my codebase?
- Is this how I would have written it if I had time?
That last question is the one that separates “I accepted what the AI gave me” from “I directed the AI to give me what I wanted.”
Job 3 — Test
After review, you write the tests. Or more often: you write the tests before the code, then the AI produces code that passes them, then you write more tests to confirm the AI didn’t pass the first set by lucky accident. This is the discipline from Chapter 4 turned into a workflow.
A test is the senior’s instrument for trusting a junior. Without it, “the AI said the code works” is a faith claim. With it, “this test suite passes and exercises the cases I care about” is a verified claim. There is a meaningful difference between the two.
Coach’s Note — Phase 2 projects that come back broken almost always failed at Job 2 or Job 3 — the student skipped review, or skipped writing tests, or both. The student rarely failed at Job 1 because writing a spec at least gets you thinking. The dangerous part of partnership is the moment after the AI hands you code, when it’s tempting to copy-paste-ship. Don’t. Read. Test. Then ship.
9.7 — A Worked Example: EventLog
Here is one full cycle of the spec → AI → review workflow on a small class. We’ll use this same class for Project 9 (download EventLog.java and the test suite EventLogTest.java).
Step 1 — The spec (you write this)
EventLog — a chronological log of named events, grouped by category.
Public API:
- EventLog()
Creates an empty log.
- void log(String category, String description)
Records an event with the current timestamp.
Throws IllegalArgumentException if category or description is null/blank.
- List<Event> recent(String category, int n)
Returns up to n most recent events in the given category, newest first.
Returns an empty list (never null) if category has no events.
Throws IllegalArgumentException if n is negative.
- Set<String> categories()
Returns the set of categories that have at least one event.
Invariants:
- Events within a category are stored in time-ordered insertion sequence.
- No event is ever modified after being logged.
Event — immutable record:
- String category
- String description
- Instant timestamp
That spec is roughly 20 lines. It took you maybe ten minutes to write. It is the most important ten minutes of the hour you are about to spend on this class.
Step 2 — The prompt (you write this)
You are implementing a Java 17 class for me. Here is the complete spec.
Use only the standard library (java.time, java.util). Make Event a record.
EventLog should use a Map<String, List<Event>> internally. Throw
IllegalArgumentException with informative messages. Do not add features
the spec does not call for.
[paste spec from Step 1]
Notice the constraints: language version, allowed libraries, internal data structure, error type, the “no extra features” guard. Each one closes off a way the AI could go wrong.
Step 3 — The AI’s output (the AI produces this)
A reasonable AI response, given that prompt, looks like:
import java.time.Instant;
import java.util.*;
public class EventLog {
public record Event(String category, String description, Instant timestamp) {}
private final Map<String, List<Event>> events = new HashMap<>();
public void log(String category, String description) {
if (category == null || category.isBlank()) {
throw new IllegalArgumentException("category must be non-blank");
}
if (description == null || description.isBlank()) {
throw new IllegalArgumentException("description must be non-blank");
}
Event e = new Event(category, description, Instant.now());
events.computeIfAbsent(category, k -> new ArrayList<>()).add(e);
}
public List<Event> recent(String category, int n) {
if (n < 0) throw new IllegalArgumentException("n must be non-negative");
List<Event> all = events.getOrDefault(category, List.of());
int from = Math.max(0, all.size() - n);
List<Event> tail = new ArrayList<>(all.subList(from, all.size()));
Collections.reverse(tail);
return tail;
}
public Set<String> categories() {
return events.keySet();
}
}
Step 4 — The review (you do this)
Read every line. Note what’s right, what’s wrong, what’s missing.
Right:
Eventis a record. Immutable as the spec required.logcorrectly validates both args and usesInstant.now().computeIfAbsentis idiomatic and avoids a manualcontainsKeycheck.recenthandles “no events for this category” viagetOrDefault(..., List.of()). Returns empty, not null.nis validated.
Wrong or weak:
categories()returnsevents.keySet()— that’s a live view of the internal map. A caller could mutate it. Should returnSet.copyOf(events.keySet())orCollections.unmodifiableSet(events.keySet()).- The
recentmethod is correct but slightly clunky. Could be:return all.subList(from, all.size()).reversed();using Java 21’sList.reversed()— but we’re on Java 17, so the existing approach is fine. Keep it. - The spec says “events within a category are stored in time-ordered insertion sequence” — that invariant holds because we always append, but is it documented in code? No. Add a Javadoc note.
- No tests included. We didn’t ask for them in this prompt, but we need to write them next.
Missing edge cases worth a test:
recent(cat, 0)— should return empty list. The code handles it (from = all.size(), slice is empty). Test it anyway.recent(cat, n)wheren > size— should return all. The code handles it (from = max(0, ...)). Test it.logwith a category that already has events — confirms appending works.
You make the one real fix:
public Set<String> categories() {
return Set.copyOf(events.keySet());
}
Step 5 — The tests (you write these, or prompt the AI for them, then review)
import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.*;
import java.util.List;
import java.util.Set;
public class EventLogTest {
@Test
void newLogHasNoCategories() {
EventLog log = new EventLog();
assertTrue(log.categories().isEmpty());
}
@Test
void recentOnUnknownCategoryReturnsEmpty() {
EventLog log = new EventLog();
assertEquals(List.of(), log.recent("missing", 5));
}
@Test
void logRecordsAndRecentReturnsNewestFirst() {
EventLog log = new EventLog();
log.log("auth", "login");
log.log("auth", "logout");
List<EventLog.Event> events = log.recent("auth", 5);
assertEquals(2, events.size());
assertEquals("logout", events.get(0).description());
assertEquals("login", events.get(1).description());
}
@Test
void recentRespectsLimit() {
EventLog log = new EventLog();
for (int i = 0; i < 5; i++) log.log("x", "e" + i);
assertEquals(2, log.recent("x", 2).size());
}
@Test
void recentZeroReturnsEmpty() {
EventLog log = new EventLog();
log.log("x", "e0");
assertEquals(List.of(), log.recent("x", 0));
}
@Test
void logRejectsBlankCategory() {
EventLog log = new EventLog();
assertThrows(IllegalArgumentException.class, () -> log.log(" ", "msg"));
}
@Test
void recentRejectsNegativeN() {
EventLog log = new EventLog();
assertThrows(IllegalArgumentException.class, () -> log.recent("x", -1));
}
@Test
void categoriesCannotBeMutated() {
EventLog log = new EventLog();
log.log("x", "e");
Set<String> cats = log.categories();
assertThrows(UnsupportedOperationException.class, () -> cats.add("y"));
}
}
The last test is the one that catches the “live view” bug if we had not noticed it in review. This is why you write tests after a careful review, not instead of one.
That whole cycle — spec, prompt, AI output, review, tests — is one productive hour of senior engineering. You produced more code than you would have in an hour of solo coding, and you produced better code because the spec forced clarity before any line was written. That is the workflow Phase 2 trains.
9.8 — Who Is The Author?
This is where the chapter steps outside the technical to ask the question every honest engineer faces when AI enters their work.
A piece of code now exists in your project. You wrote the spec. The AI generated the implementation. You reviewed it, changed two lines, added eight tests. Whose work is it?
There is a thin answer and a thick answer.
The thin answer is academic-integrity flavored: as long as you disclose what the AI did, you are honest, and the work is “yours-with-AI-assistance.” That is true but not interesting.
The thick answer, the one the Christian engineering student should think about, draws on a tradition the church has had for a long time: the doctrine of secondary causes.
When God acts in the world, God often acts through something else. The rain that waters the crop is real rain, doing real watering — and at the same time the rain is one of the means by which God provides daily bread. The carpenter who builds the table is genuinely the builder of the table — and at the same time the carpenter’s skill, the wood, and the very breath in his lungs are gifts from God. The primary cause (God) operates through secondary causes (rain, carpenter, you) without diminishing the reality of either.
Genesis 2 gives the picture early. God forms the animals. Then God brings them to Adam to see what he would call them. Naming is human work. Genuinely human, genuinely creative, genuinely Adam’s. And the creatures Adam names are creatures God made. The collaboration does not reduce either party.
Work-with-AI is something like that. You are not God; the AI is not Adam. But the structure transfers. The AI is a tool you wield. You provide the spec, the judgment, the review, the accountability. The AI provides speed and pattern. The output is genuinely yours because you exercised authorship over the whole — the deciding, the directing, the verifying. The AI’s contribution does not erase your authorship any more than a sharp pencil erases the writer’s. But the pencil is also not nothing. The honest accounting is: I wrote this, with this tool, and here is what the tool did and where I directed it.
The vocation question — what is the work of a human in the age of competent machines? — is one Christians should not be the last to answer. The Lutheran tradition has the doctrine of vocation sitting right there: every honest work is a calling, and the worker’s calling is not invalidated when their tools improve. The blacksmith who used a hand bellows and the blacksmith who used a water-wheel-driven bellows were both blacksmiths. The engineer who wrote every line by hand and the engineer who directs an AI well are both engineers. The vocation is the direction, not the keystrokes.
What this means practically:
- You sign your work. Your name goes on the project. Not “me and the AI.” You.
- You are accountable for every line. If a bug ships, “the AI wrote it” is not a defense. You reviewed it; you approved it.
- You disclose honestly what the AI did. That’s what the
prompts.txtlog is. Not because the AI’s contribution is shameful, but because the discipline of honest disclosure is itself part of the work. It also helps the next student, the grader, and your future self. - You take the work seriously. Half the failures in early AI partnership come from students treating AI output as “free” — not their work, not worth reading. It is your work. Read it.
That last point is the whole chapter compressed into one line.
Coach’s Note — “Who is the author?” is not a question you have to settle abstractly before you start coding. You settle it the way the church has always settled questions of agency: by acting rightly within the relationship. Spec carefully. Review carefully. Test carefully. Disclose honestly. The authorship question takes care of itself when you do those four things.
9.9 — A Word About Specific AI Products
This book never tells you which AI to use. The reps work with any competent code-capable model — Claude, GPT, Gemini, and whatever the next thing is named. The course standardizes on a free-tier-friendly assistant; see Appendix A for the current recommendation.
What matters is not the tool. What matters is that you direct it like a senior. The same prompt sent to three different models will get three slightly different answers. None will be perfect. All will compile sometimes and fail to compile other times. All will hallucinate APIs on occasion. All will be vulnerable to the same review discipline. The skill transfers.
When this book uses the phrase “your AI assistant,” it means whichever model you are using for the course. The example prompts in this chapter and the next four chapters will work — to varying degrees of quality — across all of them. If one model is consistently worse at a class of problems than another, that’s worth noting in your prompts.txt log; the comparison itself is part of the craft.
9.10 — Common AI Pitfalls (Week 9 Edition)
Pitfall: You accept the AI’s first answer because it looks plausible. What’s happening: You’re skipping Job 2 (review). Fix: Read every line before you save the file. Out loud, if necessary.
Pitfall: The code compiles, your one example works, you ship. What’s happening: You’re treating “works on the example” as “works.” It isn’t. Fix: Write tests for the cases you didn’t show the AI — empty inputs, null inputs, boundary values, large inputs.
Pitfall: The AI gives you a method that calls some library function you’ve never seen. You assume it must be real because the AI sounds confident. What’s happening: Hallucinated API. Fix: Look up unfamiliar method names in the Java 17 docs. If it doesn’t compile, the compiler is telling you the truth.
Pitfall: You ask the AI to “build me a small habit tracker” and copy-paste whatever it produces. What’s happening: You delegated the architectural work — which is the senior’s job — to the junior. Fix: Decompose the problem yourself first. Ask the AI for individual components you’ve designed. Chapter 12 will make this discipline explicit.
Pitfall: You spend two hours getting the AI to fix a bug it keeps introducing. What’s happening: The AI is not converging because your prompts aren’t narrowing the problem. The skill of diagnostic prompting — Chapter 13 — isn’t there yet. Fix: Re-read the AI’s last answer carefully. Are you giving it the test output? The error message? The relevant context? If you’ve been at it for thirty minutes with no progress, stop prompting and fix it yourself — that information is also useful.
Pitfall: You don’t log a prompt because “it was just a quick question.”
What’s happening: Your prompts.txt is incomplete; your grader can’t see your process; you can’t trace later where a bad idea came from.
Fix: Log everything. Even the small stuff. Especially the small stuff. The discipline is the point.
9.11 — Reps
Open the exercises for the full set of twelve reps. They are slightly different from Phase 1’s reps — several of them involve prompting your AI assistant directly and comparing the output.
A taste:
Rep 1. Hand the AI a tiny spec (“a method that returns the second-largest element of an int array, or throws if the array has fewer than 2 elements”). Get an implementation. Find at least one thing to fix.
Rep 4. Ask the AI for a method that does not exist in the Java standard library, and watch the hallucination pattern emerge.
Rep 7. Take a piece of AI-generated code from earlier in the week and write five tests for it. Note which tests caught problems.
Full set in the exercises.
9.12 — This Week’s Project: Spec → AI → Review
You’re ready for Project 9: Spec → AI → Review, in Project 9.
The setup: you write a complete spec for EventLog (or a similar small class). You hand it to the AI. You review the result line by line. You write tests. You document every change you made and why.
The deliverables — for this project and every Phase 2 project — include a prompts.txt log of every prompt you sent the AI, in chronological order with timestamps. The grader reads it.
Three tiers:
- Normal — one clean cycle of spec → AI → review on a single class.
- Medium — identify three specific places the AI made plausible but wrong choices, and explain why each is wrong.
- Hard — repeat the exercise with a deliberately bad spec and document how the AI’s output degraded.
9.13 — Coach’s Final Word
This is the pivot chapter of the book. Until today, you were a solo athlete. Starting today, you are a coach on the field with a player who is faster than you, more willing than you, and — crucially — sometimes wrong in ways neither of you would catch by feel.
The next seven weeks are training to be the coach that player needs. Specify clearly so the player knows the play. Review fairly so the player knows the standard. Test honestly so neither of you is fooling the other. The skill is not “use AI.” Every student in your class can use AI by next Tuesday. The skill is “use AI well,” and a year from now that will be the difference between an entry-level developer who ships things their team can trust and one who doesn’t.
You did the eight weeks of Phase 1. That work is in your hands now. The AI does not replace it — it amplifies it. In the hands of a senior, AI makes good code faster. In the hands of someone who isn’t a senior, AI makes bad code faster. You earned the right to be in the first group. Stay there.
See you next week. Project 10 is when prompts get sharp.
Up next: Read the exercises and run every rep — many of them involve actually prompting your AI assistant. Then open Project 9. After that, Chapter 10 — Prompts as Specifications.