diff --git a/06-branches-sandboxes-for-experiments.md b/06-branches-sandboxes-for-experiments.md index 6d6516e..8270f33 100644 --- a/06-branches-sandboxes-for-experiments.md +++ b/06-branches-sandboxes-for-experiments.md @@ -42,8 +42,8 @@ By the end of this module you can: 2. Let the AI make a bold, multi-commit change on a branch while `main` stays untouched and runnable. 3. Decide the experiment's fate and have the agent carry it out: **merge** it into `main` to keep it, or **delete the branch** to throw it away with zero trace. You make the call and check the result. -4. Read a merge conflict (the `<<<<<<<`/`=======`/`>>>>>>>` markers) and hand it to the AI to - resolve, then verify the resolution is right before the merge completes. +4. Recognize a merge conflict (the `<<<<<<<`/`=======`/`>>>>>>>` markers) when you see one, and + verify the AI's resolution even when the agent resolved it silently and you never saw a marker. 5. Tell the difference between a fast-forward merge and a merge commit, and know which one you got. --- @@ -187,13 +187,25 @@ Read it like this: Resolving isn't picking a side mechanically. It's deciding what the line *should* say. Often that's one side; sometimes it's a blend of both (here, a usage string that lists *both* `stats` and `purge`). This is the kind of bounded reasoning task the AI is good at: it sees both versions and the -surrounding code, so you hand it the conflict and let it produce the combined version. Once the file -is correct and marker-free, telling Git the conflict is settled is two more commands the agent runs -(`git add cli.py` to mark the file resolved, then `git commit` to complete the merge). +surrounding code. Once the file is correct and marker-free, telling Git the conflict is settled is +two more commands the agent runs (`git add cli.py` to mark the file resolved, then `git commit` to +complete the merge). -`git status` during a conflict is your map; it lists every file still "unmerged." Your job is the -verify: read the resolution, confirm it's what you meant, and check `git status` comes back clean. If -things go sideways, `git merge --abort` rewinds to before the merge with no harm done. +Here's the part that has changed under your feet, and it's the real lesson of this module's lab. The +markers above are what a conflict looks like *if you ever see one*. Tell a current frontier +editor-agent to "merge `feature/stats` into `feature/purge`" and it usually never stops: it reads +both sides, resolves the collision, completes the merge, and reports a clean result, all in one turn. +You never saw a marker. From your seat the conflict simply did not happen. That is convenient right +up until the silent resolution is wrong (it can keep the worse of the two sides, or blend them into a +line that satisfies neither), and now a bad merge is sitting in your history with nothing that looked +like an error. + +So the skill is no longer "edit the markers by hand." It is two things: **know what a conflict is** +(so you recognize one when an agent does surface it) and **check `git diff` after every merge** (so a +silent resolution can't slip a wrong line past you). `git status` during a conflict is your map; it +lists every file still "unmerged." If you want to *see* the markers before the agent touches them, +tell it to stop on conflict and show you (you'll do exactly that in the lab). And if things go +sideways, `git merge --abort` rewinds to before the merge with no harm done. --- @@ -213,12 +225,15 @@ Everything above is standard Git. Here's why it matters *more* in an AI-assisted - **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. -- **Conflicts are a great place to put the AI to work.** A merge conflict is a small, perfectly +- **The AI resolves conflicts so well you may never see one.** A merge conflict is a small, perfectly 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 - editor-integrated agent. You'll do exactly this in the lab. + the correct combined version. A current editor-agent is good enough at this that, told to "merge X + into Y," it usually resolves the collision and completes the merge in the same turn, no markers + shown, no question asked. That's the highest-hit-rate convenience of the tool and its sharpest trap: + you still decide whether the resolution is right (it can absolutely merge two changes into something + that satisfies neither), except now you might not even know there *was* a conflict to second-guess. + The defense is mechanical and non-negotiable: read `git diff` after every merge. You'll feel both + the convenience and the trap in the lab. --- @@ -227,8 +242,9 @@ Everything above is standard Git. Here's why it matters *more* in an AI-assisted **Lab language:** shell (Git commands), driving the `tasks-app` from Modules 1–2 with your 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. +You'll do three things: let the AI try a bold change on a branch, decide its fate, and then engineer +a merge conflict so you can see one once, undo it, and watch the AI resolve it silently while you do +the one job that's still yours: verify the result. **You'll need:** @@ -371,20 +387,22 @@ Merge conflicts have an outsized reputation for difficulty. You'll engineer a gu Both branches changed the same `usage:` line, each adding a *different* command. Git won't be able to auto-merge that line. -3. Now trigger the conflict. Tell the agent: +3. **Witness the conflict first.** If you tell a current agent to just "merge them," it will resolve + the collision and finish the merge in one turn, and you'll never see a marker (you'll do exactly + that in step 5, on purpose). So this once, ask it to stop and show you instead, the same way + Module 26 does it: - > *"You're on `feature/purge`. Merge `feature/stats` into it."* + > *"You're on `feature/purge`. Merge `feature/stats` into it. If it conflicts, stop and show me the + > conflict; do not resolve it yet."* - Git stops with a conflict. Confirm the conflict state yourself: + The merge stops on the usage line. Confirm the conflict state yourself, then open `cli.py` and find + the markers (your usage string will be longer (it carries the commands from earlier modules), but + the collision is exactly this: both branches appended a different new command to the same line): ```bash git status # cli.py listed under "Unmerged paths" ``` -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 - branches appended a different new command to it): - ```python <<<<<<< HEAD print("usage: python cli.py [add | list | done <index> | purge]") @@ -393,50 +411,63 @@ Merge conflicts have an outsized reputation for difficulty. You'll engineer a gu >>>>>>> feature/stats ``` - (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.) + This is the whole point of the step: *see one real conflict* so you can recognize the shape. `HEAD` + is your current branch (`feature/purge`); the block below the `=======` is what `feature/stats` + wants. (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.) -5. **Resolve it with the AI.** This is exactly the bounded task the agent is good at. Ask: +4. **Undo it.** You've seen the conflict; now rewind so the AI can handle it from scratch. Tell the + agent (or run it yourself, it's the safe-undo from the Key concepts section): - > *"`cli.py` has a merge conflict on the usage line. I want the final version to list BOTH the - > `stats` and `purge` commands. Resolve the conflict and remove the markers."* + > *"Abort the merge."* - It should produce a single, marker-free line listing both commands, e.g.: + ```bash + git merge --abort + git status # clean again, back on feature/purge, no merge in progress + ``` + + You're now exactly where you were before step 3, mid-experiment with two colliding branches and no + merge underway. + +5. **Now let the AI do it for real, and watch it auto-resolve.** This time, no stop-on-conflict guard. + Direct it the way you actually would in a real workflow: + + > *"You're on `feature/purge`. Merge `feature/stats` into it. The usage line collides; the final + > version should list BOTH the `stats` and `purge` commands."* + + Notice what happens: the agent hits the same conflict you just saw, resolves it, and completes the + merge in one turn. It probably never shows you a marker. From your seat the merge just "worked." It + should have produced a single, marker-free line listing both commands, e.g.: ```python 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 - 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: + **Here is the punchline of the whole module: you have no idea yet whether that's right, so verify.** + The conflict was invisible, which means a wrong resolution would have been invisible too. A resolver + can confidently drop one side, leave a stray marker, or "blend" the lines into something that runs + but means the wrong thing. The only thing standing between you and a silently-bad merge is the + `git diff` you run *after every merge*, conflict or not: ```bash - git diff # check ONLY what you intended changed; no markers remain + git diff HEAD~1 # what the merge actually changed; confirm no markers remain + git log --oneline --graph # the fork-and-join: this is a merge commit python cli.py # run with no args, see the merged usage string python cli.py stats # both commands actually work python cli.py purge ``` -6. Once you've verified the resolution, have the agent finish the merge: - - > *"The resolution looks right. Stage `cli.py` and complete the merge."* - - Then confirm the merge landed as a merge commit: - - ```bash - git log --oneline --graph # the fork-and-join: this is a merge commit - ``` - - You just resolved a real merge conflict: you directed it, the agent did the plumbing, and you - verified the result. The marker syntax is identical no matter the file or the project. Once you can - read those three lines and check the resolution, a conflict is a short, routine task. + If the usage line lists both commands and both run, the AI's silent resolution was correct. If it + dropped one, you just caught a bug that left no error message behind, which is precisely why the + check isn't optional. You directed the merge, the agent did the plumbing *and* the resolution, and + the verify was yours. That last part is the skill: not reading markers by hand, but knowing a + conflict can happen and checking the AI's work even when it never tells you one did. > **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 -> *You'll need*), then run it from inside the repo: +> manufacture one deterministically, then practice the witness-and-verify flow 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 *You'll need*), then run it from inside the repo: > > ```bash > cp ~/ai-workflow-course/the-workflow-course/modules/06-branches-sandboxes-for-experiments/lab/make-conflict.sh . @@ -463,11 +494,14 @@ The honest limits, so you don't over-trust the sandbox: 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 - 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; that's Module 10. +- **The AI can resolve a conflict into something plausible and wrong, and you may never know one + happened.** It sees both sides and the intent, which makes it good at this, but "good" isn't + "trusted." Worse, a current agent resolves silently: told to merge, it fixes the collision and + finishes the merge in one turn, so a resolution that runs cleanly but means the wrong thing + (silently keeping the worse of two changes, or merging two behaviors into one that satisfies + neither) leaves no marker, no prompt, no error behind. That invisibility is exactly *why* the + post-merge `git diff` is the safeguard, not optional ceremony: it's the only thing that surfaces a + conflict the agent already swallowed. 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 @@ -488,9 +522,9 @@ The honest limits, so you don't over-trust the sandbox: trace, and you have **merged** one in and seen it land on `main`. - You can explain, in one sentence, why creating a branch costs essentially nothing (it's a movable pointer, not a copy). -- You deliberately created a merge conflict, read the `<<<<<<<`/`=======`/`>>>>>>>` markers, had the - AI resolve it to a marker-free file that runs, verified the result, and let the agent complete the - merge. +- You saw a real merge conflict at least once (the `<<<<<<<`/`=======`/`>>>>>>>` markers), then let + the AI merge for real and resolve it silently, and you verified the result with `git diff` even + though no marker was ever shown to you, confirming the merged file runs. - You can name the limit: a branch isolates tracked files, not your database, ignored files, or the outside world.