fix(capstone,module-11): correct squash-revert recovery, M11 lab honesty, relative dates

- Capstone Part F: sync local main after the forge squash-merge, then plain
  `git revert <squash-sha>` (NOT `-m 1`, which is only for true --no-ff merge
  commits — Module 12, left untouched). Break-then-recover finale preserved.
- Capstone: replace hardcoded due date 2026-07-15 with relative-date guidance.
- M11 Part C: list-then-use-shown-index instead of hardcoded `done 1` (breaks on
  a carried-forward tasks.json).
- M11 Part E: be honest that "agent opens a PR" needs forge integration — host
  CLI path or push-then-human-opens fallback; MCP forward-pointer (M20).

Closes #8
Closes #14
Closes #15
Closes #30

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TfzV5QvtPDz8LJS3Pu5VLT
This commit is contained in:
2026-06-22 16:15:51 -04:00
parent 391df7fc6d
commit 1ae199d31f
2 changed files with 70 additions and 23 deletions
+30 -12
View File
@@ -46,7 +46,7 @@ already standing; it doesn't re-pour the foundation.
Pick something small enough to finish in one sitting and real enough to touch the whole stack. We'll Pick something small enough to finish in one sitting and real enough to touch the whole stack. We'll
add **due dates**: add **due dates**:
- A task can carry an optional due date: `python cli.py add "file taxes" --due 2026-07-15`. - A task can carry an optional due date: `python cli.py add "file taxes" --due <YYYY-MM-DD>`.
- A new `overdue` command lists pending tasks whose due date has already passed. - A new `overdue` command lists pending tasks whose due date has already passed.
- The deployed service grows a matching `GET /overdue` endpoint, so the change is visible in the - The deployed service grows a matching `GET /overdue` endpoint, so the change is visible in the
running container, not just the CLI. running container, not just the CLI.
@@ -113,10 +113,12 @@ image from your `Dockerfile` (M16), tags it with the new commit SHA (immutable,
rolls back to the previous SHA. Hit `GET /overdue` on the running container. The feature is live, in a rolls back to the previous SHA. Hit `GET /overdue` on the running container. The feature is live, in a
reproducible artifact, behind a health check that can undo itself. reproducible artifact, behind a health check that can undo itself.
**If it goes wrong (M12).** Something slips past every gate eventually. A bad merge reverts cleanly **If it goes wrong (M12).** Something slips past every gate eventually. Because you squash-merged (one
with `git revert -m 1 <merge-sha>` — a new commit, safe on shared history, no rewriting what teammates commit on `main`, not a two-parent merge), a bad change reverts cleanly with plain
pulled (M12). A bad deploy is already handled by `deploy.sh`'s rollback to the last good SHA. Recovery `git revert <squash-sha>` — a new commit, safe on shared history, no rewriting what teammates pulled
is a discipline you rehearsed, not a panic. (M12). Skip the `-m 1` you saw in Module 12: that flag is only for true merge commits, the kind
`git merge --no-ff` makes, and a squash merge isn't one. A bad deploy is already handled by
`deploy.sh`'s rollback to the last good SHA. Recovery is a discipline you rehearsed, not a panic.
That's the whole motion. Notice what carried it: not the model. **The model wrote the diff; the That's the whole motion. Notice what carried it: not the model. **The model wrote the diff; the
workflow is everything that made the diff safe to merge and trivial to undo.** Swap the model next workflow is everything that made the diff safe to merge and trivial to undo.** Swap the model next
@@ -164,14 +166,18 @@ account, and a working Docker install.
committed instructions file (M5). If the agent reaches for a date library or hand-edits the JSON, committed instructions file (M5). If the agent reaches for a date library or hand-edits the JSON,
your file needs a line; that's signal, not failure. your file needs a line; that's signal, not failure.
4. Run it by hand to confirm it's real: 4. Run it by hand to confirm it's real. Choose the two dates relative to *your* today — one comfortably
in the future, one safely in the past — so the assertion below holds whenever you run this:
```bash ```bash
python cli.py add "file taxes" --due 2026-07-15 python cli.py add "file taxes" --due <a date a few months out> # future → NOT overdue
python cli.py add "renew domain" --due 2020-01-01 python cli.py add "renew domain" --due 2020-01-01 # past → overdue
python cli.py overdue # should list "renew domain", not "file taxes" python cli.py overdue # should list "renew domain", not "file taxes"
``` ```
> *Verify-before-publish: refresh the example due dates so the "future" one is still in the future
> at publish time — a hardcoded near-future date silently inverts this assertion once it passes.*
### Part C — Tests (M13) ### Part C — Tests (M13)
5. Have the AI extend `test_tasks.py`, then **read the test names** and confirm the boundaries are 5. Have the AI extend `test_tasks.py`, then **read the test names** and confirm the boundaries are
@@ -226,16 +232,28 @@ account, and a working Docker install.
### Part F — Rehearse recovery (M12) ### Part F — Rehearse recovery (M12)
11. Prove you can undo it. Find the merge commit and revert it on a throwaway branch, just to watch it 11. **Sync local `main` first.** The squash-merge in step 9 happened on the forge, so the new commit
work, then delete the branch: lives only on the remote — your local `main` is one behind. Pull it down and capture the SHA of
the squash commit you're about to rehearse undoing:
```bash
git switch main && git pull # bring the squash-merge commit into local main
git log --oneline -1 # the top line IS your squash commit — note its SHA
```
12. Prove you can undo it. Cut a throwaway branch off the freshly-synced `main` and revert that squash
commit, just to watch it work, then delete the branch:
```bash ```bash
git switch -c throwaway-revert-test git switch -c throwaway-revert-test
git revert -m 1 <merge-sha> # clean undo of the whole feature, as a new commit git revert <squash-sha> # plain revert: a squash merge is one ordinary commit, so no -m 1
pytest && git switch main && git branch -D throwaway-revert-test pytest && git switch main && git branch -D throwaway-revert-test
``` ```
You just confirmed the escape hatch is real *before* you ever need it in anger. No `-m 1` here, and nothing to "find": that flag is only for the two-parent merge commits Module 12
rehearsed with `git merge --no-ff`. A squash merge produces a single-parent commit, so plain
`git revert <squash-sha>` is the right undo. You just confirmed the escape hatch is real *before*
you ever need it in anger.
--- ---
@@ -267,8 +267,10 @@ loop, not the code, is what you're practicing.
issues and PRs. issues and PRs.
- Push access to that repo (it's yours, so you have it). - Push access to that repo (it's yours, so you have it).
- Your editor-integrated AI tool (Module 4). - Your editor-integrated AI tool (Module 4).
- Optionally, your host's CLI (`gh` for GitHub, `glab` for GitLab, `tea` for Gitea/Forgejo) — the web - Your host's CLI (`gh` for GitHub, `glab` for GitLab, `tea` for Gitea/Forgejo). The web UI covers the
UI works for everything here, so the CLI is convenience, not a requirement. whole human-driven loop (Parts AD), so there the CLI is just convenience. Part E is the exception:
for an *agent* to open the PR itself it has to reach the forge, which needs the CLI installed and
authenticated — or you take the no-CLI fallback that section spells out.
Starter artifacts are in this module's `lab/`: `issue.md` (the issue to file) and `pr-body.md` (the Starter artifacts are in this module's `lab/`: `issue.md` (the issue to file) and `pr-body.md` (the
PR description, including the load-bearing closing keyword). PR description, including the load-bearing closing keyword).
@@ -320,11 +322,16 @@ If the push went through, protection isn't on — fix that before continuing. Fe
the CLI), and it does what you asked. Run it: the CLI), and it does what you asked. Run it:
```bash ```bash
python cli.py add "keeper" ; python cli.py add "trash" ; python cli.py done 1 python cli.py add "keeper" ; python cli.py add "trash"
python cli.py list # note the index shown next to "trash"
python cli.py done <trash-index> # use the index "list" just printed — NOT a fixed 1
python cli.py clear-done # expect it to remove the completed one python cli.py clear-done # expect it to remove the completed one
python cli.py list # "keeper" remains, "trash" is gone python cli.py list # "keeper" remains, "trash" is gone
``` ```
Read the index off `list` rather than assuming it: `done` is positional, and your `tasks-app` has
been carrying tasks since Module 1, so "trash" won't reliably land at index 1.
5. Commit and push the branch: 5. Commit and push the branch:
```bash ```bash
@@ -363,16 +370,38 @@ If the push went through, protection isn't on — fix that before continuing. Fe
Run the loop one more time, but this time **let an agent be the contributor for steps 26.** File a Run the loop one more time, but this time **let an agent be the contributor for steps 26.** File a
second issue (e.g. "Add a `pending` command that lists only incomplete tasks" — the `TaskList.pending()` second issue (e.g. "Add a `pending` command that lists only incomplete tasks" — the `TaskList.pending()`
method already exists, so this is wiring only). Then prompt your agent: method already exists, so this is wiring only).
> "Take issue #43. Create a branch named `43-pending-command`, implement the feature, commit **First, a reality check the rest of the lab let you skip.** Two of those steps cross the forge
> referencing the issue with a closing keyword, push the branch, and open a PR into `main` whose boundary: the agent has to *read* issue #43 from the forge and *open* a PR back into it. Your Module 4
> description closes #43." editor agent only edits files and runs local commands — and `git push` publishes a branch, it does
**not** open a PR. The web UI you've been clicking can't be handed to the agent. So before you prompt,
give the agent a way to reach the forge. Pick one path:
Let the agent drive to the open-PR state. Then **you** are the human at the gate: review the diff, - **Full agent-opens-PR path (host CLI required).** Install and authenticate your host's CLI (`gh`,
and merge (or request changes) yourself. You've just watched the exact loop run with a non-human `glab`, or `tea`) so the agent can run, e.g., `gh pr create` itself. For *this* step the CLI is a
contributor — and felt precisely where you, the human, stayed in it. If you want the parallel-agents requirement, not the convenience it was in Parts AD. Then prompt the agent:
case, file two issues and run two agents in separate worktrees (Module 7), each on its own branch.
> "Take issue #43. Create a branch named `43-pending-command`, implement the feature, commit
> referencing the issue with a closing keyword, push the branch, and open a PR into `main` whose
> description closes #43."
- **No-CLI fallback (you open the PR).** Have the agent do everything local — branch, implement,
commit, push — and *you* open the PR in the web UI, reusing `lab/pr-body.md` and keeping the
`Closes #43` line. Prompt it the same way, but stop it at the push:
> "Take issue #43. Create a branch named `43-pending-command`, implement the feature, commit
> referencing the issue with a closing keyword, and push the branch. I'll open the PR."
Wiring an agent *directly* into the forge — so it reads issues and opens PRs with no human hand-off
and no CLI to shell out to — is what an MCP forge integration buys you in **Module 20**. Here you're
feeling the exact seam that module closes.
Either way, let the agent drive to the open-PR state. Then **you** are the human at the gate: review
the diff, and merge (or request changes) yourself. You've just watched the exact loop run with a
non-human contributor — and felt precisely where you, the human, stayed in it. If you want the
parallel-agents case, file two issues and run two agents in separate worktrees (Module 7), each on its
own branch.
--- ---