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.
|
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,
|
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.
|
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
|
4. Recognize a merge conflict (the `<<<<<<<`/`=======`/`>>>>>>>` markers) when you see one, and
|
||||||
resolve, then verify the resolution is right before the merge completes.
|
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.
|
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
|
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`).
|
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
|
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
|
surrounding code. Once the file is correct and marker-free, telling Git the conflict is settled is
|
||||||
is correct and marker-free, telling Git the conflict is settled is two more commands the agent runs
|
two more commands the agent runs (`git add cli.py` to mark the file resolved, then `git commit` to
|
||||||
(`git add cli.py` to mark the file resolved, then `git commit` to complete the merge).
|
complete the merge).
|
||||||
|
|
||||||
`git status` during a conflict is your map; it lists every file still "unmerged." Your job is the
|
Here's the part that has changed under your feet, and it's the real lesson of this module's lab. The
|
||||||
verify: read the resolution, confirm it's what you meant, and check `git status` comes back clean. If
|
markers above are what a conflict looks like *if you ever see one*. Tell a current frontier
|
||||||
things go sideways, `git merge --abort` rewinds to before the merge with no harm done.
|
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
|
- **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
|
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
|
- **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
|
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
|
the correct combined version. A current editor-agent is good enough at this that, told to "merge X
|
||||||
its resolution is right (it can absolutely merge two changes into something that satisfies neither),
|
into Y," it usually resolves the collision and completes the merge in the same turn, no markers
|
||||||
but "explain this conflict and propose a resolution" is one of the highest-hit-rate uses of an
|
shown, no question asked. That's the highest-hit-rate convenience of the tool and its sharpest trap:
|
||||||
editor-integrated agent. You'll do exactly this in the lab.
|
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
|
**Lab language:** shell (Git commands), driving the `tasks-app` from Modules 1–2 with your
|
||||||
editor-integrated AI from Module 4.
|
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
|
You'll do three things: let the AI try a bold change on a branch, decide its fate, and then engineer
|
||||||
deliberately create and resolve a merge conflict, using the AI to help resolve it.
|
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:**
|
**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
|
Both branches changed the same `usage:` line, each adding a *different* command. Git won't be able
|
||||||
to auto-merge that line.
|
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
|
```bash
|
||||||
git status # cli.py listed under "Unmerged paths"
|
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
|
```python
|
||||||
<<<<<<< HEAD
|
<<<<<<< HEAD
|
||||||
print("usage: python cli.py [add <title> | list | done <index> | purge]")
|
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
|
>>>>>>> feature/stats
|
||||||
```
|
```
|
||||||
|
|
||||||
(The command bodies for `stats` and `purge` touch different lines, so Git merged *those* cleanly
|
This is the whole point of the step: *see one real conflict* so you can recognize the shape. `HEAD`
|
||||||
on its own; the only collision is the usage string both branches edited.)
|
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
|
> *"Abort the merge."*
|
||||||
> `stats` and `purge` commands. Resolve the conflict and remove the markers."*
|
|
||||||
|
|
||||||
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
|
```python
|
||||||
print("usage: python cli.py [add <title> | list | done <index> | stats | purge]")
|
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
|
**Here is the punchline of the whole module: you have no idea yet whether that's right, so verify.**
|
||||||
confidently drop one side, leave a stray marker, or "blend" the lines into something that runs but
|
The conflict was invisible, which means a wrong resolution would have been invisible too. A resolver
|
||||||
means the wrong thing. Read the result and run it:
|
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
|
```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 # run with no args, see the merged usage string
|
||||||
python cli.py stats # both commands actually work
|
python cli.py stats # both commands actually work
|
||||||
python cli.py purge
|
python cli.py purge
|
||||||
```
|
```
|
||||||
|
|
||||||
6. Once you've verified the resolution, have the agent finish the merge:
|
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
|
||||||
> *"The resolution looks right. Stage `cli.py` and complete the merge."*
|
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
|
||||||
Then confirm the merge landed as a merge commit:
|
conflict can happen and checking the AI's work even when it never tells you one did.
|
||||||
|
|
||||||
```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.
|
|
||||||
|
|
||||||
> **Guaranteed-conflict generator.** AI edits are nondeterministic, so if the agent didn't touch the
|
> **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
|
> 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`
|
> manufacture one deterministically, then practice the witness-and-verify flow on it. Copy it into
|
||||||
> first (the course's lab scripts live in the course repo, not in `tasks-app`; see Module 4's
|
> your `tasks-app` first (the course's lab scripts live in the course repo, not in `tasks-app`; see
|
||||||
> *You'll need*), then run it from inside the repo:
|
> Module 4's *You'll need*), then run it from inside the repo:
|
||||||
>
|
>
|
||||||
> ```bash
|
> ```bash
|
||||||
> cp ~/ai-workflow-course/the-workflow-course/modules/06-branches-sandboxes-for-experiments/lab/make-conflict.sh .
|
> 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
|
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
|
**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.
|
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
|
- **The AI can resolve a conflict into something plausible and wrong, and you may never know one
|
||||||
intent, which makes it good at this, but "good" isn't "trusted." A resolution that runs cleanly can
|
happened.** It sees both sides and the intent, which makes it good at this, but "good" isn't
|
||||||
still mean the wrong thing (silently keeping the worse of two changes, or merging two behaviors
|
"trusted." Worse, a current agent resolves silently: told to merge, it fixes the collision and
|
||||||
into one that satisfies neither). The `git diff` + run-it check in the lab isn't optional ceremony;
|
finishes the merge in one turn, so a resolution that runs cleanly but means the wrong thing
|
||||||
it's the actual safeguard. Reviewing AI output is its own discipline; that's Module 10.
|
(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
|
- **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
|
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
|
"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`.
|
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
|
- You can explain, in one sentence, why creating a branch costs essentially nothing (it's a movable
|
||||||
pointer, not a copy).
|
pointer, not a copy).
|
||||||
- You deliberately created a merge conflict, read the `<<<<<<<`/`=======`/`>>>>>>>` markers, had the
|
- You saw a real merge conflict at least once (the `<<<<<<<`/`=======`/`>>>>>>>` markers), then let
|
||||||
AI resolve it to a marker-free file that runs, verified the result, and let the agent complete the
|
the AI merge for real and resolve it silently, and you verified the result with `git diff` even
|
||||||
merge.
|
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
|
- You can name the limit: a branch isolates tracked files, not your database, ignored files, or the
|
||||||
outside world.
|
outside world.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user