fix(modules-4,7,14): make the git demos/labs actually do what the prose claims
- M4 Part C now commits the reviewed `delete` change as the checkpoint, so Part D's `git restore .` returns to a delete-containing state (was wiping it). - M7 'watch it break': switch to an existing divergent branch so the "would be overwritten by checkout" refusal actually fires (git switch -c never did). - M7 Part C: demonstrate worktree isolation with existing add/list and distinct per-worktree data; move the new clear/count commands to after they exist. - M14 Part C: recover with `git revert HEAD` (Module 12, which precedes M14) so CI legitimately goes green; drop the wrong "Module 2's safety net" attribution. Closes #2 Closes #3 Closes #4 Closes #12 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TfzV5QvtPDz8LJS3Pu5VLT
This commit is contained in:
@@ -331,10 +331,24 @@ copy-paste loop back in Module 1, now done right.
|
||||
It should add tasks, delete one by index, and confirm the right task remains. If it fails, don't
|
||||
hand-fix it — tell the AI what broke and let it iterate (step 4 of the loop), then re-run.
|
||||
|
||||
8. **Commit the reviewed change — this is your new checkpoint.** It passed your own eyes and it
|
||||
passes the check, so lock it in:
|
||||
|
||||
```bash
|
||||
git add .
|
||||
git commit -m "Add delete command (made via editor/CLI agent)"
|
||||
git log --oneline
|
||||
```
|
||||
|
||||
You just shipped a reviewed, multi-file change made by an AI editing your files directly — and the
|
||||
copy-paste loop never entered into it. This commit is now the clean state `git restore .` falls
|
||||
back to in the next part.
|
||||
|
||||
### Part D — Practice the revert (do this even though it works)
|
||||
|
||||
8. You only trust an undo you've used. Prove the net is under you: ask the tool for a deliberately
|
||||
throwaway change —
|
||||
9. You only trust an undo you've used. Your tree is clean — you just committed in Part C, which is
|
||||
exactly the safe setup the one rule demands. Prove the net is under you: ask the tool for a
|
||||
deliberately throwaway change —
|
||||
|
||||
> *"Rename every variable in `tasks.py` to single letters."*
|
||||
|
||||
@@ -348,18 +362,18 @@ copy-paste loop back in Module 1, now done right.
|
||||
|
||||
That's the Module 2 safety net catching a Module 4 mistake. Internalize how cheap that was.
|
||||
|
||||
### Part E — Commit the good change
|
||||
### Part E — Confirm you're back at your good state
|
||||
|
||||
9. Now commit the `delete` feature you kept in Part C (Part D's mess is already gone):
|
||||
10. Nothing left to commit — the `delete` feature went in back in Part C, and Part D's throwaway is
|
||||
already gone. Confirm the reviewed multi-file commit is your latest and the tree is clean:
|
||||
|
||||
```bash
|
||||
git add .
|
||||
git commit -m "Add delete command (made via editor/CLI agent)"
|
||||
git log --oneline
|
||||
```
|
||||
```bash
|
||||
git log --oneline # "Add delete command…" is the latest commit
|
||||
git status # clean — the throwaway left no trace
|
||||
```
|
||||
|
||||
You just shipped a reviewed, multi-file change made by an AI editing your files directly — and the
|
||||
copy-paste loop never entered into it.
|
||||
That's the whole loop closed: a reviewed, multi-file change the AI made across both files is
|
||||
committed, and the mess you made on purpose vanished without touching it.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -56,22 +56,31 @@ That's fine when *you* are the only one standing on the floor. It falls apart th
|
||||
two things happening at once. Watch it break:
|
||||
|
||||
```bash
|
||||
# Agent A is mid-change on a feature branch — uncommitted edits in cli.py
|
||||
# Agent A added a `clear` command and committed it on its own branch:
|
||||
git switch -c feature/clear
|
||||
# ...agent A edits cli.py, hasn't committed...
|
||||
# ...agent A edits the usage line in cli.py to add `clear`...
|
||||
git commit -am "Add clear command"
|
||||
|
||||
# You want Agent B to start a different job, so you try to switch:
|
||||
# You start Agent B on a fresh branch off main; it begins editing the SAME
|
||||
# usage line to add `count`, and hasn't committed:
|
||||
git switch main
|
||||
git switch -c feature/count
|
||||
# ...agent B edits cli.py, hasn't committed...
|
||||
|
||||
# You try to hop the working directory back to Agent A's branch to check on it:
|
||||
git switch feature/clear
|
||||
# error: Your local changes to the following files would be overwritten by checkout:
|
||||
# cli.py
|
||||
# Please commit your changes or stash them before you switch branches.
|
||||
```
|
||||
|
||||
Git stops you — correctly. But now you're stuck choosing between bad options:
|
||||
Git stops you — correctly. Switching to `feature/clear` would overwrite Agent B's uncommitted edits
|
||||
to `cli.py` with Agent A's committed version of those same lines, so Git refuses rather than silently
|
||||
destroy the work. But now you're stuck choosing between bad options:
|
||||
|
||||
- **Commit half-finished work** just to get it out of the way (pollutes history, and Agent A's job
|
||||
isn't done).
|
||||
- **Stash it** (now Agent A's context lives in a stash you have to remember to pop, and Agent A — a
|
||||
- **Commit half-finished work** just to get it out of the way (pollutes history, and Agent B's
|
||||
`count` command isn't done).
|
||||
- **Stash it** (now Agent B's context lives in a stash you have to remember to pop, and Agent B — a
|
||||
long-running session that thinks its files are right there — is now editing files that silently
|
||||
changed under it).
|
||||
- **Run both agents on the same branch in the same folder** — and watch them overwrite each other's
|
||||
@@ -220,23 +229,40 @@ collide. Then you'll merge both back and clean up.
|
||||
|
||||
### Part A — Feel the collision (1 minute)
|
||||
|
||||
Before fixing it, reproduce the bottleneck from "Where branches alone run out." In your `tasks-app`:
|
||||
Before fixing it, reproduce the bottleneck from "Where branches alone run out." The wall only appears
|
||||
when both branches touch the **same line** of `cli.py` — one committed, one not — so we make each
|
||||
branch edit the usage line. (The `sed … > tmp && mv` is just a portable, copy-pasteable stand-in for
|
||||
the edit an agent would make.) In your `tasks-app`:
|
||||
|
||||
```bash
|
||||
cd ~/workflow-course/tasks-app
|
||||
git switch -c feature/scratch
|
||||
# make a fake uncommitted edit so the working dir is dirty:
|
||||
echo "# scratch" >> cli.py
|
||||
git switch -c feature/other
|
||||
|
||||
# Agent A's branch: add `clear` to the usage line and commit it.
|
||||
git switch -c feature/clear
|
||||
sed 's/done <index>/done <index> | clear/' cli.py > cli.tmp && mv cli.tmp cli.py
|
||||
git commit -am "Add clear command (demo)"
|
||||
|
||||
# Agent B's branch, off main: start adding `count` to the SAME line — leave it uncommitted.
|
||||
git switch main
|
||||
git switch -c feature/count
|
||||
sed 's/done <index>/done <index> | count/' cli.py > cli.tmp && mv cli.tmp cli.py
|
||||
|
||||
# Try to hop the working directory back to Agent A's branch:
|
||||
git switch feature/clear
|
||||
# error: Your local changes to the following files would be overwritten by checkout:
|
||||
# cli.py
|
||||
# Please commit your changes or stash them before you switch branches.
|
||||
```
|
||||
|
||||
Git refuses — it won't move you to another branch with uncommitted changes in the way. *That* is the
|
||||
wall. Clean up before continuing:
|
||||
Git refuses — moving the one working directory to `feature/clear` would overwrite Agent B's
|
||||
uncommitted edit with `feature/clear`'s committed version of that line. *That* is the wall: one
|
||||
directory can't hold two agents' in-progress work at once. These two branches existed only to feel
|
||||
the collision, so clean them up before continuing:
|
||||
|
||||
```bash
|
||||
git restore cli.py
|
||||
git restore cli.py # drop Agent B's uncommitted edit
|
||||
git switch main
|
||||
git branch -D feature/scratch feature/other
|
||||
git branch -D feature/clear feature/count # throw away the demo branches
|
||||
```
|
||||
|
||||
### Part B — Create two worktrees
|
||||
@@ -269,16 +295,19 @@ This is the part to actually *do simultaneously*, not one then the other.
|
||||
`lab/agent-a-prompt.md` — *add a `clear` command that removes all tasks.*
|
||||
2. Open `~/workflow-course/tasks-app-count` in a **second** editor/AI session. Give it the prompt in
|
||||
`lab/agent-b-prompt.md` — *add a `count` command that prints the number of pending tasks.*
|
||||
3. Let both work at the same time. While they run, prove the isolation from a third terminal:
|
||||
3. Let both work at the same time. While they run, prove the isolation from a third terminal — but
|
||||
use commands that **already exist**. (`clear` and `count` don't yet; the agents are still writing
|
||||
them.) Give each worktree its own task and list it:
|
||||
|
||||
```bash
|
||||
cd ~/workflow-course/tasks-app-clear && python cli.py clear # agent A's feature
|
||||
cd ~/workflow-course/tasks-app-count && python cli.py count # agent B's feature
|
||||
cd ~/workflow-course/tasks-app-clear && python cli.py add "from worktree A" && python cli.py list
|
||||
cd ~/workflow-course/tasks-app-count && python cli.py add "from worktree B" && python cli.py list
|
||||
```
|
||||
|
||||
Each app runs its own command in its own folder. Note that each worktree has its **own**
|
||||
`tasks.json` (it's gitignored runtime state, not shared history) — so the two running apps don't
|
||||
even share data. Total isolation.
|
||||
Each `list` shows only its own task — worktree A never sees "from worktree B" and vice versa. Each
|
||||
worktree has its **own** `tasks.json` (gitignored runtime state, not shared history), so the two
|
||||
running apps don't even share data. Separate files, separate state, while both agents work. Total
|
||||
isolation.
|
||||
|
||||
4. In each worktree, commit the agent's work on its own branch:
|
||||
|
||||
@@ -289,6 +318,16 @@ This is the part to actually *do simultaneously*, not one then the other.
|
||||
|
||||
Two agents, two commits, two branches — neither ever saw the other's files.
|
||||
|
||||
5. *Now* the new commands exist — run each in its own worktree to watch it work:
|
||||
|
||||
```bash
|
||||
cd ~/workflow-course/tasks-app-clear && python cli.py clear # agent A's new command
|
||||
cd ~/workflow-course/tasks-app-count && python cli.py count # agent B's new command
|
||||
```
|
||||
|
||||
`count` reports only worktree B's task — the one you added in step 3 — because B's `tasks.json` is
|
||||
the only state it can see. The isolation, one last time.
|
||||
|
||||
### Part D — Merge back and clean up
|
||||
|
||||
Bring both features home to `main` in your original worktree:
|
||||
@@ -354,8 +393,8 @@ Worktrees are sharp tools. The honest caveats:
|
||||
|
||||
**You're done when:**
|
||||
|
||||
- `git worktree list` showed three entries at once, and you ran a different command of the
|
||||
`tasks-app` from two different worktree folders.
|
||||
- `git worktree list` showed three entries at once, and you ran the `tasks-app` from two different
|
||||
worktree folders — adding a different task in each and watching each keep its own `tasks.json`.
|
||||
- You ran two AI sessions in parallel — each in its own worktree on its own branch — and confirmed
|
||||
neither touched the other's files (different folders, different `tasks.json`, different branch).
|
||||
- You merged both feature branches back into `main` (resolving a conflict if one appeared) and the
|
||||
|
||||
@@ -274,15 +274,21 @@ and watch CI stop it.
|
||||
`test_pending_excludes_completed_tasks` failed, with the assertion and the actual-vs-expected
|
||||
values. CI caught in seconds what a skim would have waved through.
|
||||
|
||||
9. Reproduce and fix:
|
||||
9. Reproduce and fix. The bad change is already committed *and pushed*, so `git restore` is no help
|
||||
here — it only discards *uncommitted* edits, and there are none. The team-safe undo for something
|
||||
already on shared history is `git revert` (Module 12): it writes a **new** commit that inverts the
|
||||
bad one, instead of rewriting history other people may have pulled.
|
||||
|
||||
```bash
|
||||
pytest -q # fails locally too — same command, same failure
|
||||
git restore tasks.py # throw away the bad change (Module 2's safety net)
|
||||
git commit -am "Revert: pending() must exclude completed tasks"
|
||||
git push # CI goes green again
|
||||
git revert HEAD # new commit that undoes "Simplify pending()" (Module 12)
|
||||
git push # CI re-runs on the fixed code and goes green again
|
||||
```
|
||||
|
||||
`git revert HEAD` opens an editor with a prefilled message (`Revert "Simplify pending()"`) — save
|
||||
and close it. The revert restores the correct `pending()`, the push triggers CI on the fixed code,
|
||||
and the run goes green.
|
||||
|
||||
10. *(Optional, to feel the linter tier.)* Add an obviously unused import to `cli.py`
|
||||
(`import os` at the top, unused), commit, and push. Watch the **Lint** step fail *before* the
|
||||
tests even run — the cheap check failing fast. Remove it and push again.
|
||||
|
||||
Reference in New Issue
Block a user