Finishing without a start cuts the branch then (D4).
finish_milestone(summary), through the new loop/milestones.py:
It commits only the work (#80), pushes with the push credential, and opens a PR whose body says "Closes #N". This happens under an open_pr intent (L8).
An open PR for the branch is adopted; a closed one is the operator's answer, never reopened (the model's fix 2).
No changes is raised with the operator.
PR watching between runs:
operator conversation comments and inline review notes and review bodies (#69) reach the item as operator messages, such as on text.py:3: rename this; non-operators' notes are ignored (L11);
a merge or close-without-merge becomes a fact, and the driver continues (D4).
A review fix:finish_milestone on an open PR pushes the fix, then checks the PR. If it merged before the fix landed, the fix goes out as a new PR; if it was closed, the agent asks (D5, the model's fix 3).
Boot (L8): an interrupted publish is settled against Gitea. An open PR is adopted; otherwise the intent is aborted and the operator asked.
#70, the watch stall, doesn't arise in the new loop. It watches PRs from its regular tick, not with re-attached watch threads.
test_loop_milestones.py, 9 tests, with real git on the throwaway build host, a bare repository as the forge, and a fake Gitea whose merges really move the forge's main:
Fresh workspace: a stray branch, an unpushed commit, an uncommitted change and an artifact are set aside or removed, and an upstream change is picked up.
The whole path: issue → restatement → go → milestone → PR with only the work and "Closes #14" → operator comment → fix pushed and replied on the PR → merge → close_item closes the issue.
Guards: a second start_milestone refused; finishing without a start.
PR outcomes: a PR closed without merging hands back to the driver; a fix after a merge becomes a new PR containing only the fix.
Review notes: an inline note reaches the item, and a non-operator's doesn't.
Boot: a crashed publish is adopted, or aborted and asked.
M2.1, **PR C of 4: milestones and review in the new loop.** **Stacked on #97** (PR B); the base is `loop-core`.
## What's in it
- **A fresh workspace per work item** (D3, #62), on the item's first run:
- fetch;
- **set aside, never discard,** uncommitted changes, stray branches and unpushed commits on the default branch, as `set-aside/<time>/…` branches;
- check out a clean default branch from `origin`;
- `git clean -fdx`, so artifacts never survive;
- put the item on a branch of its own.
Anything set aside is posted in the item's thread and recorded as a fact.
- **`start_milestone(name)`** cuts `<item>-milestone-N-<rand>`.
- **A second start while one is open is refused.**
- **Finishing without a start** cuts the branch then (D4).
- **`finish_milestone(summary)`**, through the new `loop/milestones.py`:
- **It commits only the work** (#80), pushes with the push credential, and opens a PR whose body says **"Closes #N"**. This happens under an `open_pr` intent (L8).
- **An open PR for the branch is adopted; a closed one is the operator's answer, never reopened** (the model's fix 2).
- **No changes** is raised with the operator.
- **PR watching between runs:**
- operator conversation comments and **inline review notes and review bodies** (#69) reach the item as operator messages, such as `on text.py:3: rename this`; non-operators' notes are ignored (L11);
- a merge or close-without-merge becomes a fact, and the driver continues (D4).
- **A review fix:** `finish_milestone` on an open PR pushes the fix, then checks the PR. **If it merged before the fix landed, the fix goes out as a new PR;** if it was closed, the agent asks (D5, the model's fix 3).
- **Boot (L8):** an interrupted publish is settled against Gitea. An open PR is adopted; otherwise the intent is aborted and the operator asked.
- **#70, the watch stall,** doesn't arise in the new loop. It watches PRs from its regular tick, not with re-attached watch threads.
- **Gitea:** `pull_request` (including `merged`) and `review_notes`. **Git:** `fetch`, `local_branches`, `rename_branch`, `branch_at`, `reset_to`, `is_ancestor`.
## Tests
**`test_loop_milestones.py`, 9 tests,** with real git on the throwaway build host, a bare repository as the forge, and a fake Gitea whose merges really move the forge's `main`:
- **Fresh workspace:** a stray branch, an unpushed commit, an uncommitted change and an artifact are set aside or removed, and an upstream change is picked up.
- **The whole path:** issue → restatement → `go` → milestone → PR with only the work and "Closes #14" → operator comment → fix pushed and replied on the PR → merge → `close_item` closes the issue.
- **Guards:** a second `start_milestone` refused; finishing without a start.
- **PR outcomes:** a PR closed without merging hands back to the driver; a fix after a merge becomes a new PR containing only the fix.
- **Review notes:** an inline note reaches the item, and a non-operator's doesn't.
- **Boot:** a crashed publish is adopted, or aborted and asked.
589 unit tests pass.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE
A work item starts from a fresh workspace: fetch, set aside (never
discard) leftover changes, branches and unpushed commits, a clean default
branch, and a branch of its own (D3, #62). start_milestone cuts the
milestone's branch; finish_milestone commits only the work, pushes and
opens a PR that closes the issue, under an intent, adopting an open PR and
treating a closed one as the operator's answer (D4). Between runs the
loop watches each item's PR: operator comments and inline review notes
(#69) reach the item, a merge or close hands back to the driver. A review
fix is checked after it's pushed and goes out as a new PR if the PR merged
first (D5). At boot an interrupted publish is settled against Gitea (L8).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
M2.1, PR C of 4: milestones and review in the new loop. Stacked on #97 (PR B); the base is
loop-core.What's in it
A fresh workspace per work item (D3, #62), on the item's first run:
set-aside/<time>/…branches;origin;git clean -fdx, so artifacts never survive;Anything set aside is posted in the item's thread and recorded as a fact.
start_milestone(name)cuts<item>-milestone-N-<rand>.finish_milestone(summary), through the newloop/milestones.py:open_printent (L8).PR watching between runs:
on text.py:3: rename this; non-operators' notes are ignored (L11);A review fix:
finish_milestoneon an open PR pushes the fix, then checks the PR. If it merged before the fix landed, the fix goes out as a new PR; if it was closed, the agent asks (D5, the model's fix 3).Boot (L8): an interrupted publish is settled against Gitea. An open PR is adopted; otherwise the intent is aborted and the operator asked.
#70, the watch stall, doesn't arise in the new loop. It watches PRs from its regular tick, not with re-attached watch threads.
Gitea:
pull_request(includingmerged) andreview_notes. Git:fetch,local_branches,rename_branch,branch_at,reset_to,is_ancestor.Tests
test_loop_milestones.py, 9 tests, with real git on the throwaway build host, a bare repository as the forge, and a fake Gitea whose merges really move the forge'smain:go→ milestone → PR with only the work and "Closes #14" → operator comment → fix pushed and replied on the PR → merge →close_itemcloses the issue.start_milestonerefused; finishing without a start.589 unit tests pass.
🤖 Generated with Claude Code
https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE