De-slop: remove every em-dash + banned words across all modules + capstone (#94)
Sync course wiki / sync-wiki (push) Successful in 4s
Sync course wiki / sync-wiki (push) Successful in 4s
Co-authored-by: claude <claude@jpaul.io> Co-committed-by: claude <claude@jpaul.io>
This commit was merged in pull request #94.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# Module 6 — Branches: Sandboxes for Experiments
|
||||
# Module 6: Branches as Sandboxes for Experiments
|
||||
|
||||
> **A branch is a disposable copy of your project where the AI can try anything — and `main` never
|
||||
> **A branch is a disposable copy of your project where the AI can try anything, and `main` never
|
||||
> finds out unless you decide it should.** This is what turns "let the agent attempt something bold"
|
||||
> from a gamble into a one-line decision: keep it or throw it away.
|
||||
|
||||
@@ -8,19 +8,19 @@
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- **Module 2 — Version Control as a Safety Net.** You can `init`, `commit`, read `git diff`/`git
|
||||
- **Module 2: Version Control as a Safety Net.** You can `init`, `commit`, read `git diff`/`git
|
||||
log`/`git status`, and `git restore` an unwanted change. Branches build directly on commits: a
|
||||
branch is just a label on the commit history you already understand.
|
||||
- **Module 3 — Version Control for Words.** You first met `git branch`, `git switch -c`, `git merge`,
|
||||
and `git branch -d` there — on a markdown doc, where a mistake costs nothing and the merge always
|
||||
- **Module 3: Version Control for Words.** You first met `git branch`, `git switch -c`, `git merge`,
|
||||
and `git branch -d` there, on a markdown doc, where a mistake costs nothing and the merge always
|
||||
fast-forwarded. This module takes those same verbs to *code*, where branches actually diverge and
|
||||
merges can conflict.
|
||||
- **Module 4 — Getting the AI Out of the Browser.** The AI now edits your real files directly from
|
||||
your editor. That's exactly the capability that makes branches matter — you're about to let it edit
|
||||
- **Module 4: Getting the AI Out of the Browser.** The AI now edits your real files directly from
|
||||
your editor. That's exactly the capability that makes branches matter; you're about to let it edit
|
||||
files *fast and confidently*, and you want a wall around the blast radius.
|
||||
- **Module 5 — Commit the AI's Config, Not Just the Code.** Your committed instructions file travels
|
||||
- **Module 5: Commit the AI's Config, Not Just the Code.** Your committed instructions file travels
|
||||
with the branch automatically, so an agent working on a branch inherits the same setup. (You'll see
|
||||
this for free in the lab — nothing to do, just notice it.)
|
||||
this for free in the lab; nothing to do, just notice it.)
|
||||
|
||||
Module 2's `git restore` undoes *uncommitted* changes back to your last checkpoint. This module is
|
||||
the next size up: isolating *a whole line of committed work* so you can keep or discard it as a unit.
|
||||
@@ -157,7 +157,7 @@ each, keep the winner, delete the loser. The branch is the unit of "maybe."
|
||||
|
||||
### Merge conflicts: when two changes collide
|
||||
|
||||
Most merges just work — Git is good at combining changes that touch *different* lines. A **conflict**
|
||||
Most merges just work; Git is good at combining changes that touch *different* lines. A **conflict**
|
||||
happens only when two branches changed **the same lines** in different ways, and Git refuses to
|
||||
guess which one you meant. It stops the merge and marks the collision *inside the file* so you can
|
||||
decide:
|
||||
@@ -172,8 +172,8 @@ decide:
|
||||
|
||||
Read it like this:
|
||||
|
||||
- `<<<<<<< HEAD` to `=======` is **your current branch's version** (the branch you're merging *into*
|
||||
— `main`, here).
|
||||
- `<<<<<<< HEAD` to `=======` is **your current branch's version** (the branch you're merging *into*,
|
||||
`main`, here).
|
||||
- `=======` to `>>>>>>> experiment` is **the incoming branch's version**.
|
||||
- Both markers and the divider are real text Git inserted into your file. Resolving means **editing
|
||||
the file so it contains the version you want and deleting all three marker lines.**
|
||||
@@ -196,19 +196,19 @@ things go sideways, `git merge --abort` rewinds to before the merge with no harm
|
||||
Everything above is standard Git. Here's why it matters *more* in an AI-assisted workflow, not less:
|
||||
|
||||
- **The branch is the blast-radius container for an autonomous attempt.** An agent editing your files
|
||||
directly (Module 4) is fast and confident — including when it's confidently wrong across four
|
||||
directly (Module 4) is fast and confident, including when it's confidently wrong across four
|
||||
files. On `main`, cleaning that up is a chore. On a branch, you delete the branch. The riskier and
|
||||
more autonomous the AI work, the more a branch earns its keep — which is why this concept underpins
|
||||
more autonomous the AI work, the more a branch earns its keep, which is why this concept underpins
|
||||
everything in Unit 5, where agents run with far less supervision.
|
||||
- **"Throw it away" is the feature, not the failure.** With copy-paste, a rejected AI attempt still
|
||||
cost you the manual work of pasting it in and the manual work of ripping it back out. With a
|
||||
branch, a rejected attempt costs *nothing* — `git branch -D` and it's as if it never happened. That
|
||||
branch, a rejected attempt costs *nothing*: `git branch -D` and it's as if it never happened. That
|
||||
flips the economics: you can let the AI try things you'd never risk if undoing were expensive.
|
||||
- **Compare, don't commit-and-hope.** Ask the AI for approach A on one branch and approach B on
|
||||
another. Run both. Keep the winner, delete the loser. You're using branches as cheap A/B
|
||||
experiments on implementation — something that's painful without them and trivial with them.
|
||||
experiments on implementation, something that's painful without them and trivial with them.
|
||||
- **Conflicts are a great place to put the AI to work.** A merge conflict is a small, perfectly
|
||||
bounded reasoning task: here are two versions of the same lines and the surrounding code — produce
|
||||
bounded reasoning task: here are two versions of the same lines and the surrounding code; produce
|
||||
the correct combined version. The AI can see both sides and the intent. You still decide whether
|
||||
its resolution is right (it can absolutely merge two changes into something that satisfies neither),
|
||||
but "explain this conflict and propose a resolution" is one of the highest-hit-rate uses of an
|
||||
@@ -222,20 +222,20 @@ Everything above is standard Git. Here's why it matters *more* in an AI-assisted
|
||||
editor-integrated AI from Module 4.
|
||||
|
||||
You'll do three things: let the AI try a bold change on a branch, decide its fate, and then
|
||||
deliberately create and resolve a merge conflict — using the AI to help resolve it.
|
||||
deliberately create and resolve a merge conflict, using the AI to help resolve it.
|
||||
|
||||
**You'll need:**
|
||||
|
||||
- The `tasks-app` Git repo from Module 2 (committed, clean working tree — run `git status` and make
|
||||
- The `tasks-app` Git repo from Module 2 (committed, clean working tree; run `git status` and make
|
||||
sure it says "nothing to commit").
|
||||
- Your editor-integrated AI from Module 4.
|
||||
- Git (you've had it since Module 2).
|
||||
|
||||
> Throughout, "ask your AI" now means your **editor-integrated** agent (Module 4) editing the files
|
||||
> directly — no more copy-paste. After it edits, you still read `git diff` before committing. That
|
||||
> directly, no more copy-paste. After it edits, you still read `git diff` before committing. That
|
||||
> habit doesn't go away; the branch just decides how *much* damage a bad diff can do.
|
||||
|
||||
### Part A — Branch it and let the AI go bold
|
||||
### Part A: Branch it and let the AI go bold
|
||||
|
||||
1. Make sure you're in the repo, then **tell the agent to set up the branch.** Ask:
|
||||
|
||||
@@ -289,13 +289,13 @@ deliberately create and resolve a merge conflict — using the AI to help resolv
|
||||
|
||||
Your bold change exists only on the branch. `main` never saw it, and that's the whole point.
|
||||
|
||||
### Part B — Decide its fate
|
||||
### Part B: Decide its fate
|
||||
|
||||
**The decision is yours; the execution is the agent's.** Pick the path that matches reality. Do at
|
||||
least one; ideally do **Path 2 (discard)** on this experiment so you feel how clean it is, then re-run
|
||||
Part A and do **Path 1 (keep)** so you've done both.
|
||||
|
||||
**Path 1 — Keep it (merge).** Tell the agent:
|
||||
**Path 1: Keep it (merge).** Tell the agent:
|
||||
|
||||
> *"Merge `experiment/priorities` into `main`, then delete the branch."*
|
||||
|
||||
@@ -307,7 +307,7 @@ python cli.py list # the feature is now on main
|
||||
git branch # experiment/priorities is gone
|
||||
```
|
||||
|
||||
**Path 2 — Throw it away (discard).** Tell the agent:
|
||||
**Path 2: Throw it away (discard).** Tell the agent:
|
||||
|
||||
> *"Switch to `main` and discard the `experiment/priorities` branch entirely."*
|
||||
|
||||
@@ -323,16 +323,16 @@ Notice what you did *not* do in Path 2: no file-by-file `restore`, no manual und
|
||||
diffs. The agent deleted a label and the entire experiment was gone. That's the economics shift: bold
|
||||
AI attempts become free to reject.
|
||||
|
||||
### Part C — Create a merge conflict and resolve it with the AI
|
||||
### Part C: Create a merge conflict and resolve it with the AI
|
||||
|
||||
Merge conflicts have an outsized reputation for difficulty. You'll engineer a guaranteed one by having
|
||||
**two branches change the same line in different ways**, then resolve it with the agent.
|
||||
|
||||
> **Starting state.** By now your `tasks-app` has accumulated commands from earlier modules, so your
|
||||
> `usage:` line is longer than the bare `[add <title> | list | done <index>]` you started with — and
|
||||
> `usage:` line is longer than the bare `[add <title> | list | done <index>]` you started with, and
|
||||
> that's fine. This lab works *regardless* of what's on that line, because the collision is just "two
|
||||
> branches each appended a different new command to the same usage line." To make it reproduce even on
|
||||
> a carried-forward app, we deliberately add two commands you **haven't** built yet — `stats` and
|
||||
> a carried-forward app, we deliberately add two commands you **haven't** built yet: `stats` and
|
||||
> `purge`. (Any two brand-new commands would do; the point is the same line, edited two ways.) The
|
||||
> marker examples below show the shape; your real markers will carry your fuller usage string.
|
||||
|
||||
@@ -376,7 +376,7 @@ Merge conflicts have an outsized reputation for difficulty. You'll engineer a gu
|
||||
```
|
||||
|
||||
4. Open `cli.py` and find the conflict markers around the usage line (your usage string will be
|
||||
longer — it carries the commands from earlier modules — but the collision is exactly this: both
|
||||
longer (it carries the commands from earlier modules), but the collision is exactly this: both
|
||||
branches appended a different new command to it):
|
||||
|
||||
```python
|
||||
@@ -388,7 +388,7 @@ Merge conflicts have an outsized reputation for difficulty. You'll engineer a gu
|
||||
```
|
||||
|
||||
(The command bodies for `stats` and `purge` touch different lines, so Git merged *those* cleanly
|
||||
on its own — the only collision is the usage string both branches edited.)
|
||||
on its own; the only collision is the usage string both branches edited.)
|
||||
|
||||
5. **Resolve it with the AI.** This is exactly the bounded task the agent is good at. Ask:
|
||||
|
||||
@@ -401,13 +401,13 @@ Merge conflicts have an outsized reputation for difficulty. You'll engineer a gu
|
||||
print("usage: python cli.py [add <title> | list | done <index> | stats | purge]")
|
||||
```
|
||||
|
||||
**Verify its work — this is the part the AI can get subtly wrong.** A conflict resolver can
|
||||
**Verify its work; this is the part the AI can get subtly wrong.** A conflict resolver can
|
||||
confidently drop one side, leave a stray marker, or "blend" the lines into something that runs but
|
||||
means the wrong thing. Read the result and run it:
|
||||
|
||||
```bash
|
||||
git diff # check ONLY what you intended changed; no markers remain
|
||||
python cli.py # run with no args — see the merged usage string
|
||||
python cli.py # run with no args, see the merged usage string
|
||||
python cli.py stats # both commands actually work
|
||||
python cli.py purge
|
||||
```
|
||||
@@ -429,7 +429,7 @@ Merge conflicts have an outsized reputation for difficulty. You'll engineer a gu
|
||||
> **Guaranteed-conflict generator.** AI edits are nondeterministic, so if the agent didn't touch the
|
||||
> same line on both branches and you *didn't* get a conflict in step 3, run the helper script to
|
||||
> manufacture one deterministically, then practice steps 4–6 on it. Copy it into your `tasks-app`
|
||||
> first (the course's lab scripts live in the course repo, not in `tasks-app` — see Module 4's
|
||||
> first (the course's lab scripts live in the course repo, not in `tasks-app`; see Module 4's
|
||||
> *You'll need*), then run it from inside the repo:
|
||||
>
|
||||
> ```bash
|
||||
@@ -448,20 +448,20 @@ Merge conflicts have an outsized reputation for difficulty. You'll engineer a gu
|
||||
The honest limits, so you don't over-trust the sandbox:
|
||||
|
||||
- **A branch isolates *files in the repo*, nothing else.** Switching branches rewrites your tracked
|
||||
files — it does **not** roll back a database the app wrote to, files Git is ignoring, running
|
||||
files; it does **not** roll back a database the app wrote to, files Git is ignoring, running
|
||||
processes, or anything outside version control. If your AI experiment ran a migration or wrote to
|
||||
`tasks.json` (which the Module 2 `.gitignore` excludes), deleting the branch won't undo *that*. The
|
||||
sandbox is the repo, not the world. (Real environment isolation is a later problem — containers,
|
||||
sandbox is the repo, not the world. (Real environment isolation is a later problem: containers,
|
||||
Module 16.)
|
||||
- **Branches are local until you push them.** Everything in this module lives on your laptop. A
|
||||
branch isn't shared, backed up, or visible to anyone else until there's a remote — that's
|
||||
branch isn't shared, backed up, or visible to anyone else until there's a remote; that's
|
||||
**Module 8**. Right now `git branch -D` deletes work that exists nowhere else, permanently. Treat
|
||||
an unpushed branch as exactly as fragile as the rest of your local-only repo.
|
||||
- **The AI can resolve a conflict into something plausible and wrong.** It sees both sides and the
|
||||
intent, which makes it good at this — but "good" isn't "trusted." A resolution that runs cleanly can
|
||||
intent, which makes it good at this, but "good" isn't "trusted." A resolution that runs cleanly can
|
||||
still mean the wrong thing (silently keeping the worse of two changes, or merging two behaviors
|
||||
into one that satisfies neither). The `git diff` + run-it check in the lab isn't optional ceremony;
|
||||
it's the actual safeguard. Reviewing AI output is its own discipline — Module 10.
|
||||
it's the actual safeguard. Reviewing AI output is its own discipline; that's Module 10.
|
||||
- **Long-lived branches drift and conflict harder.** The longer a branch lives away from `main`, the
|
||||
more `main` moves underneath it and the gnarlier the eventual merge. The defense is the same as
|
||||
"commit often": branch small, merge soon, delete promptly. A branch that's been open for three
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# make-conflict.sh — manufacture a guaranteed merge conflict to practice on.
|
||||
# make-conflict.sh: manufacture a guaranteed merge conflict to practice on.
|
||||
#
|
||||
# AI edits are nondeterministic, so the lab's organic conflict (two branches editing the same usage
|
||||
# line in cli.py) doesn't ALWAYS land. This script guarantees one: it creates two branches that each
|
||||
# append a different line to the same spot in README.md, then leaves you mid-merge with a real
|
||||
# conflict in your working tree. The resolution mechanic is identical to the code case in the lab —
|
||||
# conflict in your working tree. The resolution mechanic is identical to the code case in the lab:
|
||||
# read the <<<<<<< / ======= / >>>>>>> markers, edit to the version you want, remove the markers,
|
||||
# then `git add` + `git commit`.
|
||||
#
|
||||
|
||||
Reference in New Issue
Block a user