Improve Mikado process: cycle discipline, reset rigor, --resume enhancements (#261)

## Summary
- **End-of-cycle prompting:** After closing a leaf node and pushing, the agent should prompt the user to review and suggest ending the session rather than rushing into the next leaf
- **Reset rigor:** Reinforced that errors during impl should trigger a branch reset + plan update (not fix-forward). Documented the `git log --oneline --not main` → `git reset --hard` → `git cherry-pick` pattern with clear threshold guidance
- **`--resume` shows PR number:** Queries the Forgejo API for open PRs matching the branch, displays number/title/URL and a hint to run `pr-comments`
- **`--resume` checks git stash:** Shows stash entries as a non-presumptive hint — informs without assuming they apply

## Test plan
- [ ] `mise run docs-mikado --resume` runs without errors (no active chains case)
- [ ] On a mikado branch with an open PR, verify PR info is shown
- [ ] With stashed work, verify stash entries are displayed
- [ ] Review agent-change-process.md for clarity

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Reviewed-on: https://forge.ops.eblu.me/eblume/blumeops/pulls/261
This commit is contained in:
Erich Blume 2026-02-23 21:03:27 -08:00
commit 9b4951bf94
3 changed files with 110 additions and 20 deletions

View file

@ -0,0 +1 @@
Improved Mikado C2 process: end-of-cycle session prompts, rigorous reset discipline with documented git patterns, and `--resume` now shows PR number and stash hints.

View file

@ -165,18 +165,35 @@ The `mikado-branch-invariant-check` commit-msg hook validates this convention an
- **Verify the change works** (deploy from branch, run tests, etc.) before closing
- Commit the card closure (`C2(<chain>): close ...`) — remove `status: active`
- Push to origin — this is the save point
4. **Repeat** until the chain is complete
5. **New agent sessions** pick up state by running `mise run docs-mikado --resume`
4. **End the cycle** — after pushing a closed leaf node, prompt the user to review the PR and suggest ending the session. Each closed leaf is a natural stopping point; the chain is designed to be resumed later. Don't rush into the next leaf without the user's go-ahead.
5. **Repeat** until the chain is complete
6. **New agent sessions** pick up state by running `mise run docs-mikado --resume`
### Discovering new prerequisites
### Discovering new prerequisites or errors
When you discover a new prerequisite during code work, you must restore the Mikado Branch Invariant:
When you discover a new prerequisite **or encounter an error** during code work, do not fix forward. The Mikado method's power comes from rigorous resets that keep the plan honest. You must restore the Mikado Branch Invariant:
1. **Reset the branch** back to the top of the Mikado commit stack — the last `C2(<chain>): plan` or `C2(<chain>): close` commit before your current `impl` commits
2. **Add a new commit** (`C2(<chain>): plan ...`) introducing the new prerequisite card (and updating `requires` on existing cards if needed)
3. **Replay the Mikado process** from the new state of the card stack
1. **Stash or note any in-progress work** you want to preserve
2. **Identify the reset point** — the last `plan` or `close` commit before your current `impl` commits:
```bash
git log --oneline mikado/<chain-stem> --not main
```
3. **Reset the branch** to that commit:
```bash
git reset --hard <reset-point-sha>
```
4. **Update the plan** — add a `plan` commit that captures what you learned:
- If you discovered a new prerequisite: add a new card and update `requires`
- If you hit an error: update the relevant card with what you learned, or introduce a new prerequisite card that addresses the root cause
5. **Replay valid work** by cherry-picking commits that still apply:
```bash
git cherry-pick <sha1> <sha2> ...
```
6. **Resume the Mikado process** from the new state of the card stack
**Saving work across resets:** It is acceptable to cherry-pick or rebase code commits from before the reset back onto the branch after adding the new card. This is a pragmatic exception — use it only when you are confident the saved work is still valid given the new prerequisite. When in doubt, redo the work from scratch.
**When to reset vs. fix forward:** If an `impl` commit introduces a bug that's a simple typo or one-liner, another `impl` commit is fine. But if the error reveals a gap in understanding, a missing prerequisite, or requires rethinking the approach — reset. The threshold is: "does this error teach us something that should be in the plan?" If yes, reset.
**Saving work across resets:** It is acceptable to cherry-pick code commits from before the reset back onto the branch after adding the new card. Use `git stash` for uncommitted work. This is a pragmatic exception — use it only when you are confident the saved work is still valid given the new prerequisite. When in doubt, redo the work from scratch.
### Completing a chain
@ -199,8 +216,10 @@ When starting a new session to continue C2 work:
2. Run `mise run docs-mikado --resume` — this will:
- Detect the current branch and match it to an active chain
- Show the chain state, ready leaf nodes, and current position in the invariant
- Show the PR number and URL if an open PR exists for the branch
- Warn about any stashed work in `git stash list`
- If on main, list active chains and suggest which to resume
3. Check PR comments with `mise run pr-comments <pr_number>`
3. Check PR comments with `mise run pr-comments <pr_number>` — use the PR number from the `--resume` output above
4. Pick the next ready leaf node and continue with a work cycle
### Build artifacts

View file

@ -1,7 +1,7 @@
#!/usr/bin/env -S uv run --script
# /// script
# requires-python = ">=3.12"
# dependencies = ["pyyaml>=6.0", "rich>=13.0.0", "typer>=0.15.0"]
# dependencies = ["httpx>=0.28.0", "pyyaml>=6.0", "rich>=13.0.0", "typer>=0.15.0"]
# ///
#MISE description="View active Mikado dependency chains for C2 changes"
#USAGE arg "[card]" help="Card stem to show chain for"
@ -238,6 +238,49 @@ def classify_branch_position(commits: list[dict]) -> str:
return "unknown"
FORGE_API = "https://forge.ops.eblu.me/api/v1"
def find_pr_for_branch(branch: str) -> dict | None:
"""Find an open PR for the given branch via the Forgejo API."""
import httpx
try:
resp = httpx.get(
f"{FORGE_API}/repos/eblume/blumeops/pulls",
params={"state": "open", "limit": 50},
timeout=10,
)
if resp.status_code != 200:
return None
for pr in resp.json():
if pr.get("head", {}).get("ref") == branch:
return {
"number": pr["number"],
"title": pr["title"],
"url": pr.get("html_url", ""),
}
except (httpx.HTTPError, KeyError):
pass
return None
def get_stash_list() -> list[str]:
"""Get the list of git stash entries."""
try:
result = subprocess.run(
["git", "stash", "list"],
capture_output=True,
text=True,
cwd=REPO_DIR,
)
if result.returncode == 0 and result.stdout.strip():
return result.stdout.strip().split("\n")
except FileNotFoundError:
pass
return []
def show_resume(
cards: dict[str, dict],
console: Console,
@ -350,12 +393,25 @@ def _show_chain_resume(
branch = card.get("branch") or current_branch
console.print()
console.print(
Panel(
f"[bold]{chain}[/bold] — {card['title']}\n"
# Look up PR for this branch
pr_info = find_pr_for_branch(branch) if branch else None
panel_lines = [
f"[bold]{chain}[/bold] — {card['title']}",
f"Branch: [green]{branch or 'not set'}[/green]",
title="Resuming Mikado Chain",
]
if pr_info:
panel_lines.append(
f"PR: [cyan]#{pr_info['number']}[/cyan] — {pr_info['title']}"
)
if pr_info["url"]:
panel_lines.append(f" {pr_info['url']}")
console.print(Panel("\n".join(panel_lines), title="Resuming Mikado Chain"))
if pr_info:
console.print(
f"[dim]Check PR comments: mise run pr-comments {pr_info['number']}[/dim]"
)
# Show branch position if we can read commits
@ -405,6 +461,20 @@ def _show_chain_resume(
else:
console.print("\n[dim]No ready leaf nodes — all leaves have unmet dependencies[/dim]")
# Check for stashed work
stashes = get_stash_list()
if stashes:
console.print(
f"\n[yellow]Note: {len(stashes)} stash entr{'y' if len(stashes) == 1 else 'ies'} found:[/yellow]"
)
for entry in stashes[:5]:
console.print(f" [dim]{entry}[/dim]")
if len(stashes) > 5:
console.print(f" [dim]... and {len(stashes) - 5} more[/dim]")
console.print(
"[dim]Review with: git stash list / git stash show[/dim]"
)
console.print()