Milestones and review in the new loop (M2.1, PR C) #98

Merged
dongar merged 1 commits from loop-milestones into loop-core 2026-10-03 14:09:17 +00:00
Collaborator

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.ai/code/session_01PPQ14L3LikyefCMFBqEbKE

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
dev-agent added 1 commit 2026-10-02 23:53:02 +00:00
Milestones and review in the new loop (M2.1, PR C)
CI / checks (push) Skipped
CI / checks (pull_request) Successful in 1m48s
21744efac1
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
dongar approved these changes 2026-10-03 14:09:13 +00:00
dongar merged commit 510d395ead into loop-core 2026-10-03 14:09:17 +00:00
dongar deleted branch loop-milestones 2026-10-03 14:09:17 +00:00
Sign in to join this conversation.
No Reviewers
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: dev/nisse#98