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:
+30
-12
@@ -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 A–D), 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 2–6.** File a
|
Run the loop one more time, but this time **let an agent be the contributor for steps 2–6.** 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 A–D. 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.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user