diff --git a/docs/how-to/agent-change-process.md b/docs/how-to/agent-change-process.md index fe7219a..e202079 100644 --- a/docs/how-to/agent-change-process.md +++ b/docs/how-to/agent-change-process.md @@ -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(): 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(): plan` or `C2(): close` commit before your current `impl` commits -2. **Add a new commit** (`C2(): 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/ --not main + ``` +3. **Reset the branch** to that commit: + ```bash + git reset --hard + ``` +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 ... + ``` +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 ` +3. Check PR comments with `mise run pr-comments ` — use the PR number from the `--resume` output above 4. Pick the next ready leaf node and continue with a work cycle ### Build artifacts diff --git a/mise-tasks/docs-mikado b/mise-tasks/docs-mikado index c993a1b..a9e6d8d 100755 --- a/mise-tasks/docs-mikado +++ b/mise-tasks/docs-mikado @@ -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,13 +393,26 @@ def _show_chain_resume( branch = card.get("branch") or current_branch console.print() - console.print( - Panel( - f"[bold]{chain}[/bold] — {card['title']}\n" - f"Branch: [green]{branch or 'not set'}[/green]", - title="Resuming Mikado Chain", + + # 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]", + ] + 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 if branch and current_branch == branch: @@ -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()