docs(wiki): sync from modules/ @ 8e0ae0d5
@@ -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 <title> | 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.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user