# Implement AI-named Ultrathink scratch branches, AI-authored commit messages, and final reintegration into the original branch

This ExecPlan is a living document. The sections `Progress`, `Surprises & Discoveries`, `Decision Log`, and `Outcomes & Retrospective` must be kept up to date as work proceeds.

This plan builds on the shipped behavior documented historically in `execplan/ultrathink-review-loop.md`, but it supersedes that file for all git-flow decisions. The current source tree in `src/` is the implementation baseline, and this document must remain self-contained as it is updated.

## Purpose / Big Picture

After this change, `/ultrathink <prompt>` will always run inside a dedicated temporary git branch whose name starts with `ultrathink/` and whose slug is generated by a small user-selected model. The same small model will generate the subject and description for every iteration commit, and, when the run ends normally with more than one iteration commit, it will also generate the final merge-commit title and body that reintegrate the work into the original branch.

The user-visible outcome must be easy to verify. In a clean git repository, the user should be able to start `/ultrathink <prompt>`, pick a naming model the first time if the project config does not already specify one, watch several git-backed iterations accumulate in `ultrathink/<slug>`, and then see one of three endings. If no iteration commit was created, Ultrathink should return to the original branch and delete the temporary branch. If exactly one iteration commit was created, Ultrathink should rebase that scratch branch onto the original branch and then fast-forward the original branch so the final history contains only that one ordinary commit and no merge commit. If two or more iteration commits were created, Ultrathink should merge the scratch branch back with a custom AI-authored merge commit that summarizes the full work. On successful reintegration, the temporary `ultrathink/<slug>` branch must be deleted.

The main reason this work matters is git history quality. The user wants to keep the detailed implementation history available in the graph on a side branch, but avoid cluttering the main branch with every intermediate checkpoint. The final Ultrathink summary message therefore must report the original branch, the scratch branch, the reintegration mode, whether the scratch branch was deleted, and the full list of commits that existed on the scratch branch with each commit’s title and description so the summary itself also serves as a work log.

## Progress

- [x] (2026-03-22 10:00 UTC+8) Read the brainstorming and execplan skill instructions, then re-read the full `PLANS.md` methodology before drafting this plan.
- [x] (2026-03-22 10:10 UTC+8) Inspected the current implementation in `src/config.ts`, `src/git.ts`, `src/index.ts`, `src/state.ts`, `src/ui.ts`, `src/review.ts`, and the README to confirm the shipped behavior still uses configurable git modes, fixed commit subjects, and no final reintegration step.
- [x] (2026-03-22 10:20 UTC+8) Inspected the Pi extension and SDK docs relevant to this feature, especially `docs/extensions.md`, `docs/models.md`, and `docs/sdk.md`, to confirm the available APIs for model selection, config persistence, and direct model calls via `complete()`.
- [x] (2026-03-22 10:35 UTC+8) Resolved the user-facing design decisions needed for implementation: dedicated scratch branches are mandatory; branch names must be `ultrathink/<ai-slug>` with no run-id suffix; naming-model selection happens once from Pi’s available-model list and is stored in `~/.pi/ultrathink.json`; one-commit runs return via rebase plus fast-forward; multi-commit runs return via a custom AI-authored merge commit; successful runs delete the scratch branch; reintegration conflicts preserve the scratch branch; and the completion summary must print every scratch-branch commit with title and description.
- [x] (2026-03-22 10:50 UTC+8) Drafted this implementation plan as a separate ExecPlan because the change replaces the original git-flow contract rather than adding a small isolated option.
- [x] (2026-03-22 11:40 UTC+8) Implemented the new naming-model config, persistence helpers, and first-run model-selection UX in `src/config.ts` and `src/naming.ts`.
- [x] (2026-03-22 12:10 UTC+8) Replaced the old git-mode logic with mandatory scratch-branch creation, AI-authored iteration commit metadata, and normal-completion reintegration into the original branch.
- [x] (2026-03-22 12:25 UTC+8) Expanded run state, persisted session entries, completion summary output, README documentation, and automated tests to cover the new workflow end to end.
- [x] (2026-03-22 12:32 UTC+8) Updated the scripted SDK demo to isolate the current extension and demonstrate multi-commit merge and single-commit rebase-plus-fast-forward outcomes without real model credentials.
- [x] (2026-03-22 12:35 UTC+8) Ran `npm run check` and `npm run demo`, then recorded the resulting behavior in this plan.

## Surprises & Discoveries

- Observation: The current config loader in `src/config.ts` only reads `~/.pi/ultrathink.json`; it has no helper to persist a newly selected naming model back to disk.
  Evidence: `src/config.ts` exports `loadUltrathinkConfig()` and validation helpers, but no write path.

- Observation: The earlier git implementation was organized around optional execution strategies and fixed local commit formatting rather than mandatory scratch-branch execution with model-generated metadata.
  Evidence: Before this change, the code path created templated iteration subjects like `ultrathink(<runId>): vN` and deferred the branch strategy to config instead of enforcing the branch-first workflow.

- Observation: The current completion summary only reports iteration labels and commit SHAs or notes; it does not preserve commit titles and bodies, and it has no concept of final reintegration into the original branch.
  Evidence: `src/ui.ts` currently formats iteration lines as `- vN: commit <sha>` or `- vN: <note>` and optionally prints the scratch branch name.

- Observation: Pi’s extension UI selector returns strings, which means the implementation should present model choices as stable `provider/id` strings and then resolve them back through `ctx.modelRegistry.find()`.
  Evidence: `ExtensionUIContext.select()` is typed as `select(title: string, options: string[], ...) => Promise<string | undefined>` in `node_modules/@mariozechner/pi-coding-agent/dist/core/extensions/types.d.ts`.

- Observation: The SDK demo cannot reuse the main scripted Ultrathink assistant model for metadata generation, because the metadata calls need deterministic JSON outputs rather than the same scripted coding-turn behavior.
  Evidence: `demo/fakeProvider.ts` currently registers exactly one model, `ultrathink-demo/scripted`, whose `streamSimple()` implementation emits assistant answers for Ultrathink turns rather than branch-slug or commit-message JSON.

- Observation: Moving Ultrathink config to `~/.pi/ultrathink.json` removes it from repository cleanliness checks and makes the naming-model choice shared across projects.
  Evidence: After the change, `src/config.ts` resolves the config path through the user home directory (or the test override env var), and `src/git.ts` no longer needs git pathspec exclusions for config writes.

- Observation: The SDK demo must disable default extension discovery, otherwise an already installed global `/ultrathink` command can override or compete with the local extension under test.
  Evidence: The first demo attempt produced the old `ultrathink(<runId>): vN` commit subjects until `demo/runDemo.ts` switched `DefaultResourceLoader` to `noExtensions: true` and injected only the local extension factory plus the demo provider extension.


## Decision Log

- Decision: Replace the previous optional git execution strategy with one mandatory git-backed strategy: every Ultrathink run starts on a dedicated scratch branch.
  Rationale: The user explicitly wants separate-branch execution for every run, not an optional mode. Preserving the older strategy as a supported runtime path would keep behavior the user is trying to remove.
  Date/Author: 2026-03-22 / Pi

- Decision: Name scratch branches `ultrathink/<sanitized-ai-slug>` with no run-id suffix.
  Rationale: The user explicitly rejected technical suffixes and wants the branch name itself to stay human-meaningful. The implementation should retry with new model-generated slugs rather than polluting the name with randomness.
  Date/Author: 2026-03-22 / Pi

- Decision: If a generated branch name already exists locally, ask the naming model for another slug and retry up to a small fixed limit such as five attempts.
  Rationale: This satisfies the user’s requirement to avoid run-id suffixes while still guaranteeing termination if the model keeps repeating itself.
  Date/Author: 2026-03-22 / Pi

- Decision: Add a project-local `naming` config section that stores the provider and model id of the small metadata model chosen by the user.
  Rationale: The user wants to choose the model the first time only, from Pi’s available model list, and then reuse that choice automatically in later runs.
  Date/Author: 2026-03-22 / Pi

- Decision: Generate branch slugs, iteration commit subjects and bodies, and final merge-commit subjects and bodies with the configured naming model by calling `complete()` directly.
  Rationale: The request is not to switch the whole Ultrathink run to a different model, only to offload naming and descriptive metadata to a small cheaper model.
  Date/Author: 2026-03-22 / Pi

- Decision: Require a git repository and a clean working tree at run start, regardless of historical `git.allowDirty` semantics.
  Rationale: The new branch-and-reintegrate workflow cannot safely distinguish pre-existing uncommitted work from Ultrathink-produced changes. Failing fast is safer than silently stashing or accidentally committing unrelated edits.
  Date/Author: 2026-03-22 / Pi

- Decision: Normal completion means only `no-git-changes` or `max-iterations`. Only those stop reasons trigger automatic reintegration into the original branch.
  Rationale: User cancellation and git errors are abnormal endings. Auto-merging partially reviewed work after `Escape` or overlapping user input would be surprising and unsafe.
  Date/Author: 2026-03-22 / Pi

- Decision: If zero iteration commits were created, switch back to the original branch and delete the scratch branch without creating any new commit.
  Rationale: There is nothing useful to integrate into the original branch, but the temporary branch should still be cleaned up on successful termination.
  Date/Author: 2026-03-22 / Pi

- Decision: If exactly one iteration commit was created, reintegrate it by rebasing the scratch branch onto the original branch and then fast-forwarding the original branch.
  Rationale: The user explicitly asked for “not merge, but rebase” when only one commit remains. The result should be a linear main-branch history with a single ordinary commit.
  Date/Author: 2026-03-22 / Pi

- Decision: If two or more iteration commits were created, reintegrate them via `git merge --no-ff --no-commit` followed by `git commit -m <ai-subject> -m <ai-body>` on the original branch.
  Rationale: The user wants the detailed work to remain visible as a side history in `git log --graph`, but wants the main branch to receive one final descriptive merge commit.
  Date/Author: 2026-03-22 / Pi

- Decision: On reintegration conflict, abort the merge or rebase, leave the scratch branch intact, return to the original branch if possible, and surface a visible summary that manual resolution is required.
  Rationale: The user explicitly asked to preserve the branch on conflict. This avoids losing the side history and prevents half-applied merge state from becoming the only record of the work.
  Date/Author: 2026-03-22 / Pi

- Decision: The completion summary must enumerate all scratch-branch commits with SHA, title, and body even if the scratch branch was successfully deleted.
  Rationale: The user wants the final Ultrathink message itself to act as a human-readable report of the work completed.
  Date/Author: 2026-03-22 / Pi

## Outcomes & Retrospective
The feature is now implemented. `/ultrathink <prompt>` always starts from a dedicated `ultrathink/<ai-slug>` scratch branch, prompts for a naming model the first time when config is missing, stores that selection in `~/.pi/ultrathink.json`, generates iteration commit titles and bodies through the naming layer, and reintegrates successful runs back into the original branch automatically. Zero-commit runs delete the empty scratch branch, one-commit runs rebase and fast-forward without a merge commit, and multi-commit runs create one final descriptive merge commit before deleting the scratch branch.

The completion summary now reports the original branch, scratch branch, naming model, reintegration result, scratch-branch deletion status, every scratch-branch commit with SHA plus title/body, and the final merge commit when present. Abnormal endings such as user cancellation, interrupt cancellation, or reintegration conflicts preserve the scratch branch instead of silently merging partial work.

Validation passed. `npm run check` now succeeds with 10 tests covering first-run naming-model persistence, visible review prompting, single-commit reintegration, multi-commit merge reintegration, branch-name collision retries, and dirty-repo refusal. `npm run demo` now shows both the multi-commit merge path and the single-commit rebase-plus-fast-forward path using deterministic naming overrides and a scripted provider.

## Context and Orientation

This repository is a Pi extension package written in TypeScript and loaded directly from source. The core entry point is `src/index.ts`. It registers `/ultrathink`, creates the active run, ensures the naming model is configured, creates the scratch branch, watches `input` and `agent_end`, and queues visible review prompts after each committed iteration. `src/config.ts` loads and now also persists global `~/.pi/ultrathink.json` naming configuration. `src/naming.ts` owns naming-model selection plus branch-slug and commit-message generation. `src/git.ts` now enforces the scratch-branch workflow, creates AI-authored iteration commits, and finalizes successful runs back into the original branch. `src/state.ts` persists enriched run data into custom session entries. `src/ui.ts` renders the active status line and the expanded completion summary. `src/review.ts` still builds the fixed review prompt header and decides stop reasons. `test/support/fakePi.ts` provides a deterministic fake extension harness. `test/support/gitTestUtils.ts` spins up temporary git repositories with a real `git` binary. `demo/fakeProvider.ts` and `demo/runDemo.ts` provide an end-to-end scripted demo without real model credentials.

A “scratch branch” in this plan means the temporary branch where Ultrathink performs all file modifications and per-iteration commits. An “iteration commit” means one ordinary commit made after an assistant turn changed the repository. A “reintegration” means the final step that brings scratch-branch history back to the original branch after the run ends normally. A “fast-forward” means moving a branch pointer directly to a descendant commit without creating a new merge commit. A “merge commit” means a new commit with two parents that records the join between the original branch and the scratch branch.

The repository now contains the requested workflow end to end. Historical references to the earlier mode-based flow, direct current-branch commits, and fixed `ultrathink(<runId>): vN` commit subjects remain useful only for understanding what changed; they are no longer the source of truth. The source of truth is the current implementation in `src/`, the automated tests in `test/`, the demo in `demo/`, and the user-facing behavior documented in `README.md`.

This change introduced one subtle repository-level rule: `~/.pi/ultrathink.json` is global user configuration, not project work output. The implementation therefore keeps it outside repositories entirely, while tests and the demo use a temporary override path so they stay deterministic and isolated from the real user home directory.

## Milestones

### Milestone 1: Add naming-model configuration, persistence, and deterministic metadata generation

At the end of this milestone, the extension will know which small model to use for branch and commit naming, and that choice will persist in `~/.pi/ultrathink.json`. If the config already contains a valid naming model, `/ultrathink` will reuse it without asking. If not, the command will show a selector listing available Pi models and save the chosen `provider` plus `modelId`. The codebase will also contain a dedicated metadata-generation module that can ask the naming model for a branch slug or a commit subject/body pair and can be cleanly mocked in tests.

This milestone de-risks the biggest new dependency: directly invoking a second model from inside the extension without changing the main Ultrathink conversation model. The acceptance proof is a deterministic test that seeds fake available models, simulates a selector choice, persists the chosen model into `~/.pi/ultrathink.json`, and then reuses that config on the next run without asking again.

### Milestone 2: Replace the previous git flow with mandatory scratch-branch execution and AI-authored iteration commits

At the end of this milestone, every successful `/ultrathink` start in a clean repository will create `ultrathink/<ai-slug>`. If the first generated slug collides with an existing local branch, the naming module will request another slug until it finds a free one or reaches the configured retry cap. Each changed iteration will ask the naming model for a descriptive commit title and body before creating the commit, and the iteration record will persist that metadata alongside the commit SHA.

This milestone is independently verifiable by running a scripted test repository through two or three changed iterations and confirming that `git log --format=%s%n%b` shows model-generated subjects and bodies on the scratch branch rather than the current `ultrathink(<runId>): vN` template.

### Milestone 3: Add normal-completion reintegration and scratch-branch cleanup

At the end of this milestone, Ultrathink will no longer stop at “scratch branch exists with commits.” Instead, normal completion will attempt to return the work to the original branch automatically. Zero-commit runs will simply delete the scratch branch. One-commit runs will rebase the scratch branch onto the original branch and then fast-forward the original branch. Multi-commit runs will create one final AI-authored merge commit on the original branch and then delete the scratch branch. Conflict paths will abort safely and preserve the scratch branch.

This milestone is independently verifiable by running three scenarios: zero commits, one commit, and multiple commits. The observable result must be that the original branch ends with no new commit, one ordinary commit, or one merge commit respectively, while `git branch` no longer lists the scratch branch after success.

### Milestone 4: Expand the completion summary, docs, demo, and tests to match the new contract

At the end of this milestone, the completion summary shown inside Pi will serve as a durable work report. It will list every scratch-branch commit with SHA, title, and body, describe how the branch was reintegrated or preserved, and say whether the scratch branch was deleted. `README.md` and the demo will explain and demonstrate the new branch-first workflow. The test suite will cover first-run model selection, branch-name collision retries, normal reintegration modes, cancellation preservation, conflict preservation, and the new summary payload.

The acceptance proof is a passing `npm run check`, a passing `npm run demo`, and visible README examples that show the current branch-first git graph semantics.

## Plan of Work

Start by reshaping the configuration surface in `src/types.ts` and `src/config.ts`. Add a `NamingModelConfig` with `provider` and `modelId` string fields, then extend `UltrathinkConfig` to include an optional `naming` section. Keep parsing backward-compatible for older config files so current users do not get JSON errors, but make the runtime behavior branch-first regardless of legacy fields. Add a writer helper in `src/config.ts` that reads the raw JSON object when it exists, preserves unknown keys, updates only the Ultrathink naming fields, creates `~/.pi/` when missing, and writes the file back with stable indentation. This helper should be the only code path that persists the naming model selection.

Next, add a new module, `src/naming.ts`, that owns the second-model workflow. This module should expose small prescriptive helpers: one to ensure the naming model exists in config or prompt the user to select it, one to generate a branch slug, one to generate an iteration commit message, and one to generate the final merge-commit message. Implement these helpers with `complete()` from `@mariozechner/pi-ai`, resolving the selected model through `ctx.modelRegistry.find()` and `ctx.modelRegistry.getApiKey()`. The prompts should ask the naming model to return strict JSON, not prose. The branch-slug response should contain only a short slug candidate. The commit-message responses should contain `subject` and `body`. The code should sanitize slugs into lowercase kebab-case, strip forbidden git-ref characters, reject empty subjects or bodies, and retry once or twice if the model returns malformed JSON. Keep this module isolated so tests can replace it with deterministic fakes.

Then rewrite the git lifecycle in `src/git.ts`. Replace `prepareGitRun()` with a scratch-branch-specific preparer that verifies three start conditions: the directory is inside a git repository, the current branch name is known, and the working tree is clean. Record the original branch name and original head SHA before creating the scratch branch. Add a branch-existence helper that checks `refs/heads/<name>`. Add a scratch-branch creation helper that retries branch-slug generation when a collision is detected. Update commit creation so `commitIterationIfChanged()` accepts a generated subject and body instead of building a fixed message internally. After staging `git add -A`, it should commit with the provided subject and body, truncate the body to `commitBodyMaxChars` after generation, and return the commit SHA plus the final subject and body actually written.

After that, implement reintegration helpers in `src/git.ts`. A new function such as `finalizeScratchBranchRun()` should take the original branch name, the scratch branch name, the original head SHA, the collected iteration records, and the generated merge-commit metadata when needed. It should branch on the number of iteration commits created during the run. For zero commits, checkout the original branch and delete the scratch branch. For one commit, remain on the scratch branch, run `git rebase <originalBranch>`, then checkout the original branch and run `git merge --ff-only <scratchBranch>`, then delete the scratch branch. For two or more commits, checkout the original branch, run `git merge --no-ff --no-commit <scratchBranch>`, then create the merge commit with the generated subject and body, then delete the scratch branch. On merge conflict, run `git merge --abort`, preserve the scratch branch, and return an integration result that clearly reports manual resolution is required. On rebase conflict, run `git rebase --abort`, checkout the original branch, preserve the scratch branch, and return the same kind of failure result.

Wire this lifecycle into `src/index.ts`. The command start path should now do three preparatory steps before sending the initial user prompt: load config, ensure the naming model is configured, and prepare the scratch branch. The active run state should capture the naming model, original branch, original head SHA, scratch branch, and an array of richer iteration records that include commit subject and body. In `agent_end`, when repository changes exist, call the naming module to generate commit metadata before creating the commit. In `finishRun`, branch on stop reason. If the stop reason is normal (`no-git-changes` or `max-iterations`), attempt final reintegration before sending the completion message. If the stop reason is cancellation or git error, skip reintegration and preserve the scratch branch. Make sure any failure during metadata generation or finalization produces a visible summary rather than leaving the session in silent limbo.

Update `src/state.ts` and `src/types.ts` to persist the richer metadata. The `IterationRecord` type should gain `commitSubject` and `commitBody`. The `ActiveRun` type should gain `namingModel`, `originalHeadSha`, `scratchBranchName`, and a new `finalization` summary object. Add a `FinalizationResult` type with stable fields such as `mode`, `success`, `scratchBranchDeleted`, `mergeCommitSha`, `mergeCommitSubject`, `mergeCommitBody`, and `error`. Persist these values in custom session entries so the completion summary can be built even after the scratch branch is deleted.

Rewrite `src/ui.ts` to describe the new workflow clearly. The active status text can stay simple, but the completion message must be more expressive. It should start with the stop reason, then describe the branch outcome in plain language, for example “Integrated `ultrathink/refactor-git-branching` back into `main` via fast-forward after rebase” or “Preserved scratch branch `ultrathink/refactor-git-branching` because rebase conflicted.” After that, print a `Scratch branch commits:` section that lists each iteration commit in chronological order with its short SHA, subject, and body. If a final merge commit was created, print it in a separate `Final merge commit:` section with SHA, subject, and body. This message must remain readable as plain text because the current extension uses a simple visible custom message, not a custom renderer.

Extend the tests and demo last, but design the seams before writing the production code. `test/support/fakePi.ts` needs a queue for selector responses and a configurable fake model registry that can return available models, resolve a selected model string back to a fake model object, and provide fake API keys. The naming module should either be injectable or Vitest-mockable so the main extension tests can remain deterministic. Add a second scripted demo provider or a second model id in `demo/fakeProvider.ts` for metadata generation, because the existing demo model only knows how to emit coding-turn answers. Update `demo/runDemo.ts` so it preconfigures the naming model or drives the first-run selection path, then verifies the new branch/merge outcomes.

Finally, update `README.md` to document the new invariant that Ultrathink always uses a temporary branch. Explain the first-run naming-model picker, the AI-generated commit messages, the branch naming behavior, the zero-commit cleanup, the one-commit rebase-plus-fast-forward path, the multi-commit merge-commit path, the conflict-preservation behavior, and the fact that the completion summary prints every scratch-branch commit with title and description.

## Concrete Steps

All commands below assume the working directory is `/home/bot/projects/pi-ultrathink`.

1. Extend the shared types and config loader, and add config persistence.

    npm test -- --run test/ultrathink-command-spike.spec.ts

   Then implement the new config types and persistence helpers in `src/types.ts` and `src/config.ts`, and add focused tests for writing `~/.pi/ultrathink.json` without clobbering unrelated keys.

2. Add the naming module and first-run model selection.

    npm test -- --run test/ultrathink-orchestration.spec.ts

   Expected result after the new tests are added: one test proves that missing naming config triggers a model selector, writes the chosen `provider/modelId` to disk, and then starts the run; another proves that existing naming config skips the selector.

3. Replace the git preparation and iteration commit flow.

    npm test -- --run test/ultrathink-git.spec.ts

   Expected result after the new tests are added: the created branch is `ultrathink/<slug>`, branch-name collisions trigger slug retries, and changed iterations create commits with generated subjects and bodies rather than `ultrathink(<runId>): vN`.

4. Implement final reintegration and cleanup.

    npm test -- --run test/ultrathink-git.spec.ts

   Expected result after the new tests are added: zero-commit runs leave the original branch unchanged and delete the scratch branch; one-commit runs leave the original branch with one ordinary commit and no merge commit; multi-commit runs create a final merge commit with generated subject and body; reintegration conflicts preserve the scratch branch and report an error.

5. Update the UI summary, demo provider, demo script, and README.

    npm run demo

   Expected transcript shape for a multi-commit success case:

    > pi-ultrathink demo
    command: /ultrathink Fix the task and keep improving until stable
    scratch branch: ultrathink/refactor-git-merge-flow
    iteration v1: 1a2b3c4 Add initial repo cleanup
      Why:
      - Normalize the helper entrypoint.
      - Remove dead imports.
    iteration v2: 5d6e7f8 Tighten git finalization logic
      Why:
      - Separate normal completion from cancellation.
      - Abort safely on merge conflicts.
    final merge commit: 9a0b1c2 Integrate Ultrathink review branch for git finalization
    scratch branch deleted: yes

6. Run the full verification suite.

    npm run check

   Expected result: TypeScript passes and the Vitest suite covers model selection, scratch-branch naming, AI-authored iteration commits, reintegration modes, preserved-branch conflict paths, and the expanded completion summary.

7. Inspect the resulting git history manually in a throwaway repository.

    git log --oneline --decorate --graph --all
    git show --stat HEAD

   Expected result for a one-commit scenario: the original branch ends with one ordinary iteration commit and no merge commit. Expected result for a multi-commit scenario: the original branch ends with one descriptive merge commit, and the side branch history is visible in the graph even though the named scratch branch reference has been deleted.

8. Validate the interactive extension behavior directly.

    pi -e ./src/index.ts

   In the Pi session, run `/ultrathink <some prompt>` in a clean repository. On first run, expect a naming-model selector if `~/.pi/ultrathink.json` has no naming config. On completion, expect a summary that lists every scratch-branch commit with title and body.

## Validation and Acceptance

Validation is complete only when all of the following are true.

First, the command only starts in a real, clean git repository. Starting in a non-git directory or with a dirty working tree must fail fast with a visible, comprehensible message, and must not create or switch branches.

Second, first-run naming-model selection is observable. If `~/.pi/ultrathink.json` lacks naming config, the extension must show a selector built from Pi’s available models. After the user picks one model, the config file must persist that choice and later runs must reuse it without asking again.

Third, branch naming is both human-readable and collision-safe. Scratch branches must be named `ultrathink/<slug>` with no run-id suffix. If a candidate name already exists, the extension must ask the naming model for another slug and retry until a free name is found or the retry limit is reached.

Fourth, iteration commits must be AI-authored. In a changed-iteration scenario, running `git log --format=%s%n%b` on the scratch-branch commits must show model-generated subjects and descriptions. The old fixed `ultrathink(<runId>): vN` subjects must no longer appear in new runs.

Fifth, normal completion must reintegrate automatically according to commit count. Zero-commit runs produce no new commit on the original branch and delete the scratch branch. One-commit runs leave one ordinary commit on the original branch and no merge commit. Multi-commit runs leave one descriptive merge commit on the original branch that summarizes the work.

Sixth, abnormal completion must preserve work rather than surprise-merge it. If the user cancels the run, if metadata generation fails, or if final merge or rebase conflicts occur, the summary must say the scratch branch was preserved and why. The original branch must not receive a partially applied merge commit.

Seventh, the completion summary must serve as a work log. It must list the scratch branch name, the original branch name, the reintegration mode or preservation reason, whether the scratch branch was deleted, and every scratch-branch commit with SHA, subject, and body. If a final merge commit was created, the summary must list its SHA, subject, and body too.

Eighth, the scripted demo must exercise the new contract without real model credentials. `npm run demo` must prove that metadata generation, branch creation, iteration commits, and final reintegration all work with deterministic fake models.

## Idempotence and Recovery

The implementation steps must be repeatable. Re-running the test suite or demo must not depend on stale scratch branches because every temporary repository used by tests or the demo is freshly created under the system temp directory.

The config writer must be safe to run multiple times. If `~/.pi/ultrathink.json` already exists, it should preserve unknown keys and only update the Ultrathink naming fields. If the file does not exist, it should create `~/.pi/` and write a valid JSON object. If parsing the existing file fails, the command should stop with a readable error rather than silently overwriting the broken config.

The git workflow must always leave the repository in a comprehensible state. On successful zero-commit cleanup, one-commit reintegration, or multi-commit merge, the current checkout should end on the original branch and the scratch branch reference should be deleted. On rebase conflict, the code should run `git rebase --abort`, return to the original branch, and preserve the scratch branch. On merge conflict, the code should run `git merge --abort`, stay on the original branch, and preserve the scratch branch. In both failure paths, the summary must print the preserved scratch branch name so the user can continue manually.

If metadata generation fails after a scratch branch was created but before reintegration, the branch should remain as evidence and as a manual recovery point. The extension should not attempt to fabricate fallback commit text locally because the user explicitly asked for model-generated branch names and commit descriptions.

## Artifacts and Notes

The most important implementation artifacts should be short, human-verifiable snippets rather than large patches.

Expected completion-summary excerpt for a one-commit success case:

    Ultrathink run 20260322T... finished because the latest iteration produced no repository changes, so the loop stopped.
    Original branch: main
    Scratch branch: ultrathink/fix-config-persistence
    Reintegration: rebased scratch branch and fast-forwarded main
    Scratch branch deleted: yes
    Scratch branch commits:
    - a1b2c3d Fix Ultrathink config persistence
      - Write naming-model selection back to ~/.pi/ultrathink.json so later projects reuse it.
      - Preserve unrelated config keys.

Expected completion-summary excerpt for a multi-commit success case:

    Ultrathink run 20260322T... finished because the latest iteration produced no repository changes, so the loop stopped.
    Original branch: main
    Scratch branch: ultrathink/refine-git-reintegration
    Reintegration: merged scratch branch back into main
    Scratch branch deleted: yes
    Scratch branch commits:
    - 1111111 Add branch finalization state
      - Track original head SHA.
      - Persist merge outcome.
    - 2222222 Separate merge and rebase completion paths
      - Use fast-forward for one-commit runs.
      - Use AI-authored merge commits for multi-commit runs.
    Final merge commit:
    - 3333333 Integrate Ultrathink git finalization improvements
      - Preserve detailed side-branch history while keeping main readable.
      - Report every scratch-branch commit in the final summary.

Expected completion-summary excerpt for a preserved-branch conflict case:

    Ultrathink run 20260322T... finished because the configured iteration limit was reached.
    Original branch: main
    Scratch branch: ultrathink/refactor-merge-recovery
    Reintegration: failed during rebase; scratch branch preserved for manual resolution
    Scratch branch deleted: no
    Error: git rebase main reported conflicts

## Interfaces and Dependencies

In `src/types.ts`, define or update these stable shapes:

    export interface NamingModelConfig {
      provider: string;
      modelId: string;
    }

    export interface GeneratedCommitMessage {
      subject: string;
      body: string;
    }

    export interface FinalizationResult {
      mode: "none" | "cleanup" | "rebase-fast-forward" | "merge-commit" | "preserved";
      success: boolean;
      scratchBranchDeleted: boolean;
      mergeCommitSha?: string;
      mergeCommitSubject?: string;
      mergeCommitBody?: string;
      error?: string;
    }

    export interface IterationRecord {
      iteration: number;
      label: string;
      answerDigest: string;
      previousDigest?: string;
      commitCreated: boolean;
      commitSha?: string;
      commitParentSha?: string;
      commitSubject?: string;
      commitBody?: string;
      stopReason?: StopReason;
      commitNote?: string;
    }

    export interface ActiveRun {
      runId: string;
      originalPromptText: string;
      iteration: number;
      maxIterations: number;
      previousDigest?: string;
      reviewBaseSha?: string;
      originalHeadSha?: string;
      originalBranchName?: string;
      scratchBranchName?: string;
      namingModel?: NamingModelConfig;
      awaitingExtensionFollowUp: boolean;
      expectedPromptText?: string;
      cancelRequested?: "user";
      continuationPromptTemplate: string;
      commitBodyMaxChars?: number;
      gitBaseline?: GitSnapshot;
      iterations: IterationRecord[];
      finalization?: FinalizationResult;
      startedAt: string;
    }

In `src/config.ts`, the loaded config should gain:

    naming?: {
      provider: string;
      modelId: string;
    }

and a writer helper with behavior equivalent to:

    saveUltrathinkNamingConfig(naming: NamingModelConfig): Promise<void>

In `src/naming.ts`, define the prescriptive runtime interface:

    export interface NamingRuntime {
      ensureNamingModel(ctx: ExtensionCommandContext, cwd: string, config: UltrathinkConfig): Promise<NamingModelConfig | null>;
      generateBranchSlug(args: { ctx: ExtensionContext; config: NamingModelConfig; promptText: string; existingBranchNames: string[]; }): Promise<string>;
      generateIterationCommitMessage(args: { ctx: ExtensionContext; config: NamingModelConfig; promptText: string; iteration: number; assistantOutput: string; diffSummary: string; changedFiles: string[]; }): Promise<GeneratedCommitMessage>;
      generateMergeCommitMessage(args: { ctx: ExtensionContext; config: NamingModelConfig; promptText: string; scratchBranchName: string; commits: Array<{ sha: string; subject: string; body: string }>; diffSummary: string; }): Promise<GeneratedCommitMessage>;
    }

In `src/git.ts`, provide helpers with stable responsibilities equivalent to:

    prepareScratchBranchRun(...): Promise<{ originalBranchName: string; originalHeadSha: string; scratchBranchName: string; baseline: GitSnapshot; }>
    prepareIterationCommit(args: { cwd: string; exec: ExecLike }): Promise<PendingCommitResult>
    commitPreparedIteration(args: { cwd: string; subject: string; body: string; commitBodyMaxChars?: number; exec: ExecLike }): Promise<CommitIterationResult>
    finalizeScratchBranchRun(...): Promise<FinalizationResult>

Use `complete()` from `@mariozechner/pi-ai` for metadata generation, and use `ctx.modelRegistry.find()`, `ctx.modelRegistry.getAvailable()`, and `ctx.modelRegistry.getApiKey()` from Pi’s extension context to resolve and authorize the naming model.

Plan revision note: created this new ExecPlan on 2026-03-22 to replace the earlier mode-based git design with the user-requested scratch-branch-only workflow, AI-generated branch and commit metadata, final reintegration into the original branch, and scratch-branch cleanup or preservation rules.
