From 06b9f8f308e2b0818e594d08188257b621d9fafd Mon Sep 17 00:00:00 2001 From: claude Date: Mon, 22 Jun 2026 15:35:51 -0400 Subject: [PATCH] Running-example consistency: paths, tasks.json, command collisions (#7,#10,#11) (#57) Co-authored-by: claude Co-committed-by: claude --- .../README.md | 16 ++- .../lab/verify.sh | 6 +- modules/05-commit-the-ai-config/README.md | 6 +- .../README.md | 65 +++++---- .../lab/make-conflict.sh | 5 +- .../README.md | 130 ++++++++++-------- .../lab/agent-a-prompt.md | 16 +-- .../lab/agent-b-prompt.md | 10 +- .../lab/cleanup-worktrees.sh | 13 +- .../lab/setup-worktrees.sh | 15 +- .../README.md | 1 + .../README.md | 19 ++- .../README.md | 4 +- .../README.md | 22 ++- 14 files changed, 192 insertions(+), 136 deletions(-) diff --git a/modules/04-getting-the-ai-out-of-the-browser/README.md b/modules/04-getting-the-ai-out-of-the-browser/README.md index 36bbf06..1f2fa4c 100644 --- a/modules/04-getting-the-ai-out-of-the-browser/README.md +++ b/modules/04-getting-the-ai-out-of-the-browser/README.md @@ -269,7 +269,11 @@ copy-paste loop back in Module 1, now done right. CLI. Use the "How to choose" criteria above; any tool that shows diffs and has an approval mode is fine. - Your model/provider credentials for that tool. -- The verify script in this module's `lab/verify.sh`. +- The verify script in this module's `lab/verify.sh`. **Convention for every lab script from here on:** + the course's scripts live in the course repo under `modules/NN/lab/`, but your `tasks-app` is a + separate folder (Module 1) — so when a step runs one, **copy the script into `tasks-app` first, then + run it by name**. (Same copy-it-in move you used for the instructions file in Module 5; use the real + path to wherever you unzipped the course in place of `/path/to/`.) ### Part A — Wire it up and confirm it can read @@ -322,10 +326,11 @@ copy-paste loop back in Module 1, now done right. gone. 7. **Verify it runs.** Use the provided script, which exercises the new command end to end across - both files: + both files. Copy it into `tasks-app` first (see *You'll need*), then run it from there: ```bash - bash lab/verify.sh + cp /path/to/modules/04-getting-the-ai-out-of-the-browser/lab/verify.sh . + bash verify.sh ``` It should add tasks, delete one by index, and confirm the right task remains. If it fails, don't @@ -357,7 +362,7 @@ copy-paste loop back in Module 1, now done right. ```bash git restore . git diff # empty — the AI's mess is gone, byte for byte - bash lab/verify.sh # still passes — you're back at your good state + bash verify.sh # still passes — you're back at your good state (you copied it in at step 7) ``` That's the Module 2 safety net catching a Module 4 mistake. Internalize how cheap that was. @@ -413,7 +418,8 @@ Be honest about the limits of working this way: - An agentic editor or CLI tool is wired to your `tasks-app` repo and correctly answers "what does this project do and which files is it in?" from the actual files — no pasting. - You have a committed `delete` command that you watched the AI write across **both** `tasks.py` and - `cli.py`, that you reviewed with `git diff` before committing, and that `bash lab/verify.sh` passes. + `cli.py`, that you reviewed with `git diff` before committing, and that `bash verify.sh` passes + (after copying `verify.sh` into `tasks-app`). - You have, on purpose, let the AI make a change and then erased it with `git restore .`, watching `git diff` go empty. - You can explain, in one sentence, why letting an AI edit your files directly is safe — and your diff --git a/modules/04-getting-the-ai-out-of-the-browser/lab/verify.sh b/modules/04-getting-the-ai-out-of-the-browser/lab/verify.sh index 875bb43..65c756c 100755 --- a/modules/04-getting-the-ai-out-of-the-browser/lab/verify.sh +++ b/modules/04-getting-the-ai-out-of-the-browser/lab/verify.sh @@ -7,9 +7,11 @@ # and the other two remain. This is a behavior check on the multi-file change — it does not # care HOW the AI implemented it, only that `delete` works end to end. # -# Run it from inside your tasks-app project directory: -# bash lab/verify.sh +# Copy this into your tasks-app project directory, then run it from there: +# cp /path/to/modules/04-getting-the-ai-out-of-the-browser/lab/verify.sh . +# bash verify.sh # +# (It self-locates cli.py, so it also still works if you run it in place as `bash lab/verify.sh`.) # It saves and restores your real tasks.json, so your actual task list is left untouched. set -euo pipefail diff --git a/modules/05-commit-the-ai-config/README.md b/modules/05-commit-the-ai-config/README.md index bda7f2b..81eb93a 100644 --- a/modules/05-commit-the-ai-config/README.md +++ b/modules/05-commit-the-ai-config/README.md @@ -207,9 +207,11 @@ editor-integrated AI (Module 4) for the part where the AI obeys the file. ### Part B — Watch the AI obey it 4. Start a **fresh** AI session in your editor (so it picks up the file cleanly) and give it a task - that the instructions constrain. For example: + that the instructions constrain. Pick a command your app doesn't have yet (so this is a real + feature, not a re-add) — for example: - > *"Add a `clear` command that removes all tasks. Then confirm it works."* + > *"Add a `search ` command that lists only the tasks whose title contains `term`. Then + > confirm it works."* 5. Watch for the file taking effect. A correctly-configured agent should, without you saying any of it this time: diff --git a/modules/06-branches-sandboxes-for-experiments/README.md b/modules/06-branches-sandboxes-for-experiments/README.md index e41bf33..dee1146 100644 --- a/modules/06-branches-sandboxes-for-experiments/README.md +++ b/modules/06-branches-sandboxes-for-experiments/README.md @@ -175,9 +175,9 @@ decide: ```python <<<<<<< HEAD - print("usage: python cli.py [add | list | done <index> | count]") + print("usage: python cli.py [add <title> | list | done <index> | stats]") ======= - print("usage: python cli.py [add <title> | list | done <index> | clear]") + print("usage: python cli.py [add <title> | list | done <index> | purge]") >>>>>>> experiment ``` @@ -190,7 +190,7 @@ Read it like this: the file so it contains the version you want and deleting all three marker lines.** You're not picking a side mechanically — you're 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* `count` and `clear`). +side, sometimes it's a blend of both (here: a usage string that lists *both* `stats` and `purge`). Then you tell Git the conflict is settled: ```bash @@ -324,44 +324,52 @@ attempts become free to reject. Now the skill everyone fears and nobody should. You'll engineer a guaranteed conflict by having **two branches change the same line in different ways**, then resolve it. -1. Make sure you're on a clean `main`. Create the first branch and have the AI add a `count` command: +> **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 +> 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 +> `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. + +1. Make sure you're on a clean `main`. Create the first branch and have the AI add a `stats` command: ```bash git switch main - git switch -c feature/count + git switch -c feature/stats ``` - Ask the AI: *"Add a `count` command to `cli.py` that prints how many tasks are pending, and update - the usage string to include it."* Then: + Ask the AI: *"Add a `stats` command to `cli.py` that prints how many tasks are total, done, and + pending, and update the usage string to include it."* Then: ```bash git diff # confirm it edited the usage line + added the command - git add . && git commit -m "Add count command" + git add . && git commit -m "Add stats command" ``` 2. Switch back to `main` and create a *different* branch that touches **the same usage line**: ```bash git switch main - git switch -c feature/clear + git switch -c feature/purge ``` - Ask the AI: *"Add a `clear` command to `cli.py` that deletes all tasks, and update the usage - string to include it."* Then: + Ask the AI: *"Add a `purge` command to `cli.py` that removes all completed (done) tasks, and update + the usage string to include it."* Then: ```bash git diff # it also edited the usage line — this is the collision to come - git add . && git commit -m "Add clear command" + git add . && git commit -m "Add purge command" ``` Both branches changed the same `usage:` line, each adding a *different* command to it. Git will not be able to auto-merge that line. -3. Merge them and watch it conflict. Merge `feature/count` into `feature/clear` (you're on - `feature/clear`): +3. Merge them and watch it conflict. Merge `feature/stats` into `feature/purge` (you're on + `feature/purge`): ```bash - git merge feature/count + git merge feature/stats ``` Git stops with a conflict and tells you which file is unmerged. Confirm: @@ -370,28 +378,30 @@ Now the skill everyone fears and nobody should. You'll engineer a guaranteed con git status # cli.py listed under "Unmerged paths" ``` -4. Open `cli.py` and find the conflict markers around the usage line: +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> | clear]") + print("usage: python cli.py [add <title> | list | done <index> | purge]") ======= - print("usage: python cli.py [add <title> | list | done <index> | count]") - >>>>>>> feature/count + print("usage: python cli.py [add <title> | list | done <index> | stats]") + >>>>>>> feature/stats ``` - (The command bodies for `count` and `clear` touch different lines, so Git merged *those* cleanly + (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.** With your editor-integrated agent, this is its sweet spot. Ask: > *"`cli.py` has a merge conflict on the usage line. I want the final version to list BOTH the - > `count` and `clear` commands. Resolve the conflict and remove the markers."* + > `stats` and `purge` commands. Resolve the conflict and remove the markers."* It should produce a single, marker-free line listing both commands, e.g.: ```python - print("usage: python cli.py [add <title> | list | done <index> | count | clear]") + 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 @@ -401,8 +411,8 @@ Now the skill everyone fears and nobody should. You'll engineer a guaranteed con ```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 count # both commands actually work - python cli.py clear + python cli.py stats # both commands actually work + python cli.py purge ``` 6. Tell Git the conflict is settled and complete the merge: @@ -419,10 +429,13 @@ Now the skill everyone fears and nobody should. You'll engineer a guaranteed con > **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: +> 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: > > ```bash -> bash modules/06-branches-sandboxes-for-experiments/lab/make-conflict.sh +> cp /path/to/modules/06-branches-sandboxes-for-experiments/lab/make-conflict.sh . +> bash make-conflict.sh > ``` > > It creates two branches that both edit the same line of `README.md`, leaving you mid-conflict with diff --git a/modules/06-branches-sandboxes-for-experiments/lab/make-conflict.sh b/modules/06-branches-sandboxes-for-experiments/lab/make-conflict.sh index 7452220..8732ea9 100644 --- a/modules/06-branches-sandboxes-for-experiments/lab/make-conflict.sh +++ b/modules/06-branches-sandboxes-for-experiments/lab/make-conflict.sh @@ -9,8 +9,9 @@ # read the <<<<<<< / ======= / >>>>>>> markers, edit to the version you want, remove the markers, # then `git add` + `git commit`. # -# Run it from anywhere inside your tasks-app repo: -# bash modules/06-branches-sandboxes-for-experiments/lab/make-conflict.sh +# Copy it into your tasks-app repo, then run it from inside the repo: +# cp /path/to/modules/06-branches-sandboxes-for-experiments/lab/make-conflict.sh . +# bash make-conflict.sh # # It is non-destructive to your real work: it only touches README.md on two throwaway practice # branches and refuses to run if your working tree is dirty. diff --git a/modules/07-worktrees-running-agents-in-parallel/README.md b/modules/07-worktrees-running-agents-in-parallel/README.md index c81742e..7dc4376 100644 --- a/modules/07-worktrees-running-agents-in-parallel/README.md +++ b/modules/07-worktrees-running-agents-in-parallel/README.md @@ -56,30 +56,30 @@ 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 added a `clear` command and committed it on its own branch: -git switch -c feature/clear -# ...agent A edits the usage line in cli.py to add `clear`... -git commit -am "Add clear command" +# Agent A added a `wipe` command and committed it on its own branch: +git switch -c feature/wipe +# ...agent A edits the usage line in cli.py to add `wipe`... +git commit -am "Add wipe command" # You start Agent B on a fresh branch off main; it begins editing the SAME -# usage line to add `count`, and hasn't committed: +# usage line to add `remaining`, and hasn't committed: git switch main -git switch -c feature/count +git switch -c feature/remaining # ...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 +git switch feature/wipe # 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. Switching to `feature/clear` would overwrite Agent B's uncommitted edits +Git stops you — correctly. Switching to `feature/wipe` 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 B's - `count` command isn't done). + `remaining` 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). @@ -95,17 +95,18 @@ repository, each with its own checked-out branch.** One repo, many checkouts. ```bash cd ~/workflow-course/tasks-app # your existing repo from Module 2 -git worktree add ../tasks-app-count -b feature/count +git worktree add ../tasks-app-remaining -b feature/remaining ``` -That command creates a brand-new folder, `~/workflow-course/tasks-app-count`, containing a full -checkout of your project on a new branch `feature/count`. Your original folder is untouched, still -on its own branch. You now have two real directories you can `cd` into, edit, and run independently: +That command creates a brand-new folder, `~/workflow-course/tasks-app-remaining`, containing a full +checkout of your project on a new branch `feature/remaining`. Your original folder is untouched, +still on its own branch. You now have two real directories you can `cd` into, edit, and run +independently: ``` ~/workflow-course/ - tasks-app/ ← the "main" worktree, on (say) main - tasks-app-count/ ← a "linked" worktree, on feature/count + tasks-app/ ← the "main" worktree, on (say) main + tasks-app-remaining/ ← a "linked" worktree, on feature/remaining ``` Both are backed by **one** repository. There is a single `.git` — a single object store, a single @@ -127,7 +128,7 @@ because there's only one store. Think of the shared object store as the project's single, settled past — every commit, on every branch, in one place. Each worktree is a different *present moment* checked out of that past: this -folder is "the project as of `feature/count`," that folder is "the project as of `main`." They all +folder is "the project as of `feature/remaining`," that folder is "the project as of `main`." They all write to the same past (commits go to the shared store), but each lives in its own present (its own files on disk). @@ -149,9 +150,9 @@ git worktree prune # forget worktrees whose folders were ```bash $ git worktree list -/home/you/workflow-course/tasks-app a1b2c3d [main] -/home/you/workflow-course/tasks-app-count d4e5f6a [feature/count] -/home/you/workflow-course/tasks-app-clear 7g8h9i0 [feature/clear] +/home/you/workflow-course/tasks-app a1b2c3d [main] +/home/you/workflow-course/tasks-app-remaining d4e5f6a [feature/remaining] +/home/you/workflow-course/tasks-app-wipe 7g8h9i0 [feature/wipe] ``` Three folders, one repo, three branches checked out simultaneously. No stashing, no switching, no @@ -197,7 +198,7 @@ behave: once — a feature here, a bugfix there, a doc update in a third. The constraint was never the model; it was that they'd trip over one repo. Worktrees remove the constraint. - **Each worktree is its own durable memory (Module 2).** A fresh agent dropped into - `tasks-app-count` reads `git status` / `git diff` / `git log` and gets *that branch's* ground + `tasks-app-remaining` reads `git status` / `git diff` / `git log` and gets *that branch's* ground truth — not a blur of three agents' half-finished work. Per-agent isolation makes per-agent "where were we?" actually answerable. - **It keeps parallel AI output reviewable.** Each agent's work lands as its own branch with its own @@ -214,8 +215,10 @@ to run two agents and watch them eat each other's homework. **Lab language:** shell (Git commands), plus two AI edit sessions on the `tasks-app`. In this lab you'll run **two AI sessions at the same time** on the same project — one adding a -`clear` command, one adding a `count` command — each in its own worktree, and watch them *not* -collide. Then you'll merge both back and clean up. +`wipe` command, one adding a `remaining` command — each in its own worktree, and watch them *not* +collide. Then you'll merge both back and clean up. (We use two commands your carried-forward +`tasks-app` doesn't have yet, so neither agent re-adds something that already exists — the lesson is +the parallel isolation, not the commands.) **You'll need:** @@ -226,7 +229,10 @@ collide. Then you'll merge both back and clean up. - **Two** editor-integrated AI sessions you can run at once (Module 4) — two editor windows, or two terminal AI sessions. If you only have a browser chat, you can still do the lab; just treat each worktree folder as a separate copy-paste context. -- The starter scripts and prompts in this module's `lab/` folder. +- The starter scripts and prompts in this module's `lab/` folder. As established in Module 4, the + course's lab scripts live in the course repo under `modules/NN/lab/`, while `tasks-app` is a + separate folder — so **copy the scripts into `tasks-app` and run them by name** (`bash + setup-worktrees.sh`), using your real course path in place of `/path/to/`. ### Part A — Feel the collision (1 minute) @@ -238,71 +244,75 @@ the edit an agent would make.) In your `tasks-app`: ```bash cd ~/workflow-course/tasks-app -# 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 A's branch: add `wipe` to the usage line and commit it. +git switch -c feature/wipe +sed 's/done <index>/done <index> | wipe/' cli.py > cli.tmp && mv cli.tmp cli.py +git commit -am "Add wipe command (demo)" -# Agent B's branch, off main: start adding `count` to the SAME line — leave it uncommitted. +# Agent B's branch, off main: start adding `remaining` 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 +git switch -c feature/remaining +sed 's/done <index>/done <index> | remaining/' 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 +git switch feature/wipe # 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 — 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 +(The `sed` matches `done <index>`, which is still in your usage line no matter how many commands +you've added since Module 1, and inserts a new one right after it — so both branches edit the same +line.) Git refuses — moving the one working directory to `feature/wipe` would overwrite Agent B's +uncommitted edit with `feature/wipe`'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 # drop Agent B's uncommitted edit +git restore cli.py # drop Agent B's uncommitted edit git switch main -git branch -D feature/clear feature/count # throw away the demo branches +git branch -D feature/wipe feature/remaining # throw away the demo branches ``` ### Part B — Create two worktrees -From inside `tasks-app`, run the setup script (or run the commands by hand): +Copy the setup script into `tasks-app` (see *You'll need*), then run it from inside the repo (or run +the commands by hand): ```bash -bash modules/07-worktrees-running-agents-in-parallel/lab/setup-worktrees.sh +cp /path/to/modules/07-worktrees-running-agents-in-parallel/lab/setup-worktrees.sh . +bash setup-worktrees.sh ``` It runs: ```bash -git worktree add ../tasks-app-clear -b feature/clear -git worktree add ../tasks-app-count -b feature/count +git worktree add ../tasks-app-wipe -b feature/wipe +git worktree add ../tasks-app-remaining -b feature/remaining git worktree list ``` You now have three folders backed by one repo. Confirm: ```bash -git worktree list # should show main + feature/clear + feature/count +git worktree list # should show main + feature/wipe + feature/remaining ``` ### Part C — Run two AI sessions in parallel This is the part to actually *do simultaneously*, not one then the other. -1. Open `~/workflow-course/tasks-app-clear` in one editor/AI session. Give it the prompt in - `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.* +1. Open `~/workflow-course/tasks-app-wipe` in one editor/AI session. Give it the prompt in + `lab/agent-a-prompt.md` — *add a `wipe` command that removes all tasks.* +2. Open `~/workflow-course/tasks-app-remaining` in a **second** editor/AI session. Give it the prompt + in `lab/agent-b-prompt.md` — *add a `remaining` 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 — 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: + use commands that **already exist**. (`wipe` and `remaining` 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 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 + cd ~/workflow-course/tasks-app-wipe && python cli.py add "from worktree A" && python cli.py list + cd ~/workflow-course/tasks-app-remaining && python cli.py add "from worktree B" && python cli.py list ``` Each `list` shows only its own task — worktree A never sees "from worktree B" and vice versa. Each @@ -313,8 +323,8 @@ This is the part to actually *do simultaneously*, not one then the other. 4. In each worktree, commit the agent's work on its own branch: ```bash - cd ~/workflow-course/tasks-app-clear && git add . && git commit -m "Add clear command" - cd ~/workflow-course/tasks-app-count && git add . && git commit -m "Add count command" + cd ~/workflow-course/tasks-app-wipe && git add . && git commit -m "Add wipe command" + cd ~/workflow-course/tasks-app-remaining && git add . && git commit -m "Add remaining command" ``` Two agents, two commits, two branches — neither ever saw the other's files. @@ -322,12 +332,12 @@ This is the part to actually *do simultaneously*, not one then the other. 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 + cd ~/workflow-course/tasks-app-wipe && python cli.py wipe # agent A's new command + cd ~/workflow-course/tasks-app-remaining && python cli.py remaining # 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. + `remaining` counts a single pending task — the one you added to worktree B 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 @@ -336,8 +346,8 @@ Bring both features home to `main` in your original worktree: ```bash cd ~/workflow-course/tasks-app git switch main -git merge feature/clear -git merge feature/count +git merge feature/wipe +git merge feature/remaining ``` Both commits are already in the shared object store, so there's nothing to fetch — the merges are @@ -346,10 +356,12 @@ their `elif` branch in the same spot. That's expected, and it's a *merge-time* e parallel-work collision — resolve it with the exact skill from Module 6, then `python cli.py list` to confirm both commands work. -Now tear down the worktrees: +Now tear down the worktrees (copy the cleanup script into `tasks-app` the same way, then run it from +inside the repo): ```bash -bash modules/07-worktrees-running-agents-in-parallel/lab/cleanup-worktrees.sh +cp /path/to/modules/07-worktrees-running-agents-in-parallel/lab/cleanup-worktrees.sh . +bash cleanup-worktrees.sh git worktree list # only the main worktree remains ``` @@ -366,7 +378,7 @@ Worktrees are sharp tools. The honest caveats: (`fatal: 'main' is already checked out at ...`). This is a feature, not a bug — it's exactly what stops two agents from writing the same branch — but it surprises people. One branch, one worktree. - **Uncommitted work is *not* shared.** Only commits go to the shared store. The edits sitting - modified-but-uncommitted in `tasks-app-count` exist *only* in that folder. If you + modified-but-uncommitted in `tasks-app-remaining` exist *only* in that folder. If you `git worktree remove` a dirty worktree, Git refuses unless you pass `--force` — and `--force` throws that uncommitted work away for good. Commit before you remove. - **Cleanup is a two-part chore.** Deleting a worktree folder with `rm -rf` does *not* tell Git it's diff --git a/modules/07-worktrees-running-agents-in-parallel/lab/agent-a-prompt.md b/modules/07-worktrees-running-agents-in-parallel/lab/agent-a-prompt.md index 93d3d8d..6dc6efa 100644 --- a/modules/07-worktrees-running-agents-in-parallel/lab/agent-a-prompt.md +++ b/modules/07-worktrees-running-agents-in-parallel/lab/agent-a-prompt.md @@ -1,15 +1,15 @@ -# Agent A prompt — the `clear` command +# Agent A prompt — the `wipe` command -Paste this into the AI session you've pointed at the `tasks-app-clear` worktree folder. +Paste this into the AI session you've pointed at the `tasks-app-wipe` worktree folder. --- -Add a `clear` command to this task app that removes **all** tasks. +Add a `wipe` command to this task app that removes **all** tasks. -- Put the deletion logic on `TaskList` in `tasks.py` (a `clear()` method that empties the list), - and wire a `clear` command into the dispatch in `cli.py` that calls it and saves. -- Running `python cli.py clear` should empty the list and print a short confirmation like - `cleared all tasks`. -- After `clear`, `python cli.py list` should print `(no tasks yet)`. +- Put the deletion logic on `TaskList` in `tasks.py` (a `wipe()` method that empties the list), + and wire a `wipe` command into the dispatch in `cli.py` that calls it and saves. +- Running `python cli.py wipe` should empty the list and print a short confirmation like + `wiped all tasks`. +- After `wipe`, `python cli.py list` should print `(no tasks yet)`. Make the change, then stop — I'll review the diff and commit it myself. diff --git a/modules/07-worktrees-running-agents-in-parallel/lab/agent-b-prompt.md b/modules/07-worktrees-running-agents-in-parallel/lab/agent-b-prompt.md index d68b145..bf13372 100644 --- a/modules/07-worktrees-running-agents-in-parallel/lab/agent-b-prompt.md +++ b/modules/07-worktrees-running-agents-in-parallel/lab/agent-b-prompt.md @@ -1,14 +1,14 @@ -# Agent B prompt — the `count` command +# Agent B prompt — the `remaining` command -Paste this into the AI session you've pointed at the `tasks-app-count` worktree folder. +Paste this into the AI session you've pointed at the `tasks-app-remaining` worktree folder. --- -Add a `count` command to this task app that prints how many tasks are still pending. +Add a `remaining` command to this task app that prints how many tasks are still pending. - Reuse the existing `pending()` method on `TaskList` in `tasks.py`; don't reimplement it. -- Wire a `count` command into the dispatch in `cli.py`. -- Running `python cli.py count` should print something like `2 pending` (the number of tasks not +- Wire a `remaining` command into the dispatch in `cli.py`. +- Running `python cli.py remaining` should print something like `2 pending` (the number of tasks not marked done). Make the change, then stop — I'll review the diff and commit it myself. diff --git a/modules/07-worktrees-running-agents-in-parallel/lab/cleanup-worktrees.sh b/modules/07-worktrees-running-agents-in-parallel/lab/cleanup-worktrees.sh index cabd328..7fecc2b 100644 --- a/modules/07-worktrees-running-agents-in-parallel/lab/cleanup-worktrees.sh +++ b/modules/07-worktrees-running-agents-in-parallel/lab/cleanup-worktrees.sh @@ -1,23 +1,24 @@ #!/usr/bin/env bash # # Module 7 lab — tear down the two worktrees created by setup-worktrees.sh. -# Run from INSIDE your tasks-app repo: +# Copy this into your tasks-app repo, then run it from inside: # -# bash modules/07-worktrees-running-agents-in-parallel/lab/cleanup-worktrees.sh +# cp /path/to/modules/07-worktrees-running-agents-in-parallel/lab/cleanup-worktrees.sh . +# bash cleanup-worktrees.sh # # `git worktree remove` deletes the folder AND clears Git's record of it; `prune` mops up any # worktrees whose folders were deleted by hand (which leaves a stale record otherwise). # # NOTE: --force discards UNCOMMITTED work in a worktree. Commit (or merge) before cleaning up. -# This script assumes you already merged feature/clear and feature/count back into main. +# This script assumes you already merged feature/wipe and feature/remaining back into main. # set -euo pipefail ROOT="$(git rev-parse --show-toplevel)" PARENT="$(cd "$ROOT/.." && pwd)" -git worktree remove "$PARENT/tasks-app-clear" --force 2>/dev/null || true -git worktree remove "$PARENT/tasks-app-count" --force 2>/dev/null || true +git worktree remove "$PARENT/tasks-app-wipe" --force 2>/dev/null || true +git worktree remove "$PARENT/tasks-app-remaining" --force 2>/dev/null || true git worktree prune echo @@ -26,4 +27,4 @@ git worktree list echo echo "If you merged both branches you can also delete them:" -echo " git branch -d feature/clear feature/count" +echo " git branch -d feature/wipe feature/remaining" diff --git a/modules/07-worktrees-running-agents-in-parallel/lab/setup-worktrees.sh b/modules/07-worktrees-running-agents-in-parallel/lab/setup-worktrees.sh index 29b46bb..bddfbe6 100644 --- a/modules/07-worktrees-running-agents-in-parallel/lab/setup-worktrees.sh +++ b/modules/07-worktrees-running-agents-in-parallel/lab/setup-worktrees.sh @@ -1,15 +1,16 @@ #!/usr/bin/env bash # # Module 7 lab — create two linked worktrees off the tasks-app repo, each on its own branch. -# Run this from INSIDE your tasks-app repo (the one you git-init'd in Module 2): +# Copy this into your tasks-app repo (the one you git-init'd in Module 2), then run it from inside: # -# bash modules/07-worktrees-running-agents-in-parallel/lab/setup-worktrees.sh +# cp /path/to/modules/07-worktrees-running-agents-in-parallel/lab/setup-worktrees.sh . +# bash setup-worktrees.sh # # It places the new worktree folders next to the repo, so you end up with: # -# <parent>/tasks-app (your existing repo, on its current branch) -# <parent>/tasks-app-clear (new worktree on branch feature/clear) -# <parent>/tasks-app-count (new worktree on branch feature/count) +# <parent>/tasks-app (your existing repo, on its current branch) +# <parent>/tasks-app-wipe (new worktree on branch feature/wipe) +# <parent>/tasks-app-remaining (new worktree on branch feature/remaining) # set -euo pipefail @@ -17,8 +18,8 @@ set -euo pipefail ROOT="$(git rev-parse --show-toplevel)" PARENT="$(cd "$ROOT/.." && pwd)" -git worktree add "$PARENT/tasks-app-clear" -b feature/clear -git worktree add "$PARENT/tasks-app-count" -b feature/count +git worktree add "$PARENT/tasks-app-wipe" -b feature/wipe +git worktree add "$PARENT/tasks-app-remaining" -b feature/remaining echo echo "Worktrees created. One repo, three checked-out branches:" diff --git a/modules/10-reviewing-code-you-didnt-write/README.md b/modules/10-reviewing-code-you-didnt-write/README.md index 8c56592..daee0f7 100644 --- a/modules/10-reviewing-code-you-didnt-write/README.md +++ b/modules/10-reviewing-code-you-didnt-write/README.md @@ -206,6 +206,7 @@ real change, then review a diff the "AI" produced and catch the trap planted in ```bash mkdir -p ~/workflow-course/review-lab && cd ~/workflow-course/review-lab cp /path/to/modules/10-reviewing-code-you-didnt-write/lab/tasks-app/*.py . + printf 'tasks.json\n__pycache__/\n' > .gitignore # keep generated runtime state out of your review diffs (Module 2) git init -qb main && git add . && git commit -qm "base: tasks-app" # -b main so the git switch main / git diff main.. steps below resolve python cli.py add "write the review module" diff --git a/modules/20-mcp-servers-giving-the-ai-hands/README.md b/modules/20-mcp-servers-giving-the-ai-hands/README.md index b787870..2f3eca6 100644 --- a/modules/20-mcp-servers-giving-the-ai-hands/README.md +++ b/modules/20-mcp-servers-giving-the-ai-hands/README.md @@ -330,15 +330,21 @@ That's the entire client/server loop, end to end, with zero code you wrote. Now > *"Add a task: review the Module 20 lab."* It should call `add_task("review the Module 20 lab")`. Then **verify the effect outside the AI**, - which is the whole point — the change is real: + which is the whole point — the change is real. Verify it the way you'd verify any runtime effect: + by reading the *state*, not the repo: ```bash python cli.py list # the new task is there, because the server wrote the same tasks.json - git diff # the change shows up in your repo, exactly like any other edit (Module 2) + cat tasks.json # the raw state the server changed, end to end ``` - The AI just changed real state in a real system through a tool call. No copy-paste, no script you - ran by hand, no pasting `tasks.json` into a chat. That's "hands." + The AI just changed real state in a real system through a tool call. Notice what you did *not* + reach for: `git diff`. `tasks.json` is deliberately gitignored (Module 2's `.gitignore` treats it + as generated runtime state, not source), so `git diff` stays empty here — and that's correct, not a + bug. The proof the task list changed is the live state (`python cli.py list` / `cat tasks.json`), + not version control; runtime data the app owns is exactly the kind of thing you keep *out* of + history. No copy-paste, no script you ran by hand, no pasting `tasks.json` into a chat. That's + "hands." 7. (Optional, to feel the discovery point.) Edit the docstring on `add_task` to be vague — change it to just `"""Adds something."""` — reload, and try the same request. Notice the AI gets *less* @@ -392,8 +398,9 @@ The honest caveats — and one of them is large enough that it gets its own modu - You built `tasks_mcp_server.py`, wired it into your tool, and saw the `tasks` server report as connected with `list_tasks` and `add_task` available. - You asked the AI a question and it answered by **calling a tool** against the live system, and you - asked it to add a task and then **verified the change outside the AI** with `python cli.py list` - and `git diff`. + asked it to add a task and then **verified the change outside the AI** by reading the runtime state + (`python cli.py list` / `cat tasks.json`) — not `git diff`, because `tasks.json` is deliberately + gitignored (Module 2). - You can explain the client/server model in one breath — *servers expose tools/resources/prompts; the client (your agentic tool) discovers and calls them on the AI's behalf* — and why "it's a protocol, not a vendor feature" means your server survives a model swap. diff --git a/modules/22-securing-third-party-mcp-and-skills/README.md b/modules/22-securing-third-party-mcp-and-skills/README.md index cc070bc..94b6244 100644 --- a/modules/22-securing-third-party-mcp-and-skills/README.md +++ b/modules/22-securing-third-party-mcp-and-skills/README.md @@ -294,10 +294,10 @@ normal question) and the attacker (you plant content the agent reads). # the tool it is NOT exposed (a write) — in a least-privilege setup this path is simply absent ``` - Then clean up the planted task so your repo is honest again (Module 2): + Then clean up the planted state so your repo is honest again (Module 2): ```bash - git restore tasks.json # or: python cli.py and delete it, then commit a clean state + rm tasks.json # tasks.json is gitignored runtime state — nothing tracked to restore, so just delete it; the app recreates it empty on the next run ``` --- diff --git a/modules/26-orchestrating-multiple-agents/README.md b/modules/26-orchestrating-multiple-agents/README.md index 4afc961..8bfcb30 100644 --- a/modules/26-orchestrating-multiple-agents/README.md +++ b/modules/26-orchestrating-multiple-agents/README.md @@ -273,7 +273,10 @@ thing you're waiting on. three. Browser-only still works; treat each worktree as a separate copy-paste context, but you'll feel the coordination cost more sharply (which is fine — that's the lesson). - The starter files in this module's `lab/` folder: `orchestration-plan.md`, `fan-out.sh`, - `status.sh`, `cleanup.sh`, and three prompts under `lab/agent-prompts/`. + `status.sh`, `cleanup.sh`, and three prompts under `lab/agent-prompts/`. As established back in + Module 4, the course's lab scripts live in the course repo while `tasks-app` is a separate folder — + so **copy the scripts into `tasks-app` and run them by name** (`bash fan-out.sh`), using your real + course path in place of `/path/to/`. ### Part A — Plan the split before you launch anything (this is the lab) @@ -294,10 +297,11 @@ thing you're waiting on. ### Part B — Fan out -3. From inside `tasks-app`, create a worktree per issue: +3. From inside `tasks-app`, copy this module's lab scripts in and create a worktree per issue: ```bash - bash modules/26-orchestrating-multiple-agents/lab/fan-out.sh + cp /path/to/modules/26-orchestrating-multiple-agents/lab/*.sh . # fan-out.sh, status.sh, cleanup.sh + bash fan-out.sh ``` It runs, in effect: @@ -318,10 +322,11 @@ thing you're waiting on. - `tasks-app-43-docs` ← `lab/agent-prompts/agent-43-docs.md` - `tasks-app-44-clear` ← `lab/agent-prompts/agent-44-clear.md` - While they run, watch the fleet from a fourth terminal: + While they run, watch the fleet from a fourth terminal (run from inside `tasks-app`, where you + copied the scripts in step 3): ```bash - bash modules/26-orchestrating-multiple-agents/lab/status.sh + bash status.sh ``` It prints each worktree, its branch, and how many commits/changes are in flight — your fleet @@ -369,7 +374,12 @@ thing you're waiting on. git add cli.py && git commit ``` -9. Close the issues (Module 11 closes them automatically if the PRs referenced them). +9. Close the issues (Module 11 closes them automatically if the PRs referenced them). Then tear the + fleet down (from inside `tasks-app`): + + ```bash + bash cleanup.sh + ``` ### Part D — Score the orchestration honestly