Skip to content

feat: worktree-first isolation with delta analysis for parallel session safety#997

Open
csillag wants to merge 15 commits intoobra:mainfrom
csillag:csillag/improve-brainstorming-isolation
Open

feat: worktree-first isolation with delta analysis for parallel session safety#997
csillag wants to merge 15 commits intoobra:mainfrom
csillag:csillag/improve-brainstorming-isolation

Conversation

@csillag
Copy link
Copy Markdown

@csillag csillag commented Mar 30, 2026

What problem are you trying to solve?

When running multiple AI coding sessions in parallel against the same repo, the brainstorming → writing-plans → execution chain works against a codebase that may change at any time. Session A brainstorms and writes a spec against code state X, but by the time execution finishes, Session B has merged changes that invalidate the spec, plan, or implementation. The agent merges anyway because it has no mechanism to detect the drift.

Detailed problem description: #989

What does this PR change?

Moves worktree creation to step 1 of brainstorming (before code analysis), adds .superpowers-session.json metadata tracking across the skill chain, and adds rebase + delta analysis with 3-level escalation to the finishing skill before merge.

Is this change appropriate for the core library?

Yes — this affects the fundamental skill chain used by all superpowers users. Parallel sessions are a natural workflow for any developer using AI coding agents. The fix is entirely within the existing skill infrastructure.

What alternatives did you consider?

Three approaches evaluated during brainstorming (documented in spec):

  1. Worktree-first (chosen) — freeze codebase at brainstorming start, validate before merge. Simplest mental model, strongest guarantees.
  2. Metadata-only tracking — don't move worktree creation, just record the analysis commit and detect staleness. Weaker: doesn't prevent drift, only detects it.
  3. Two-phase isolation — brainstorm on main (fresh view), worktree at plan-writing time. Leaves a gap between analysis and isolation.

Does this PR contain multiple unrelated changes?

No. All changes serve the same goal: isolate the skill chain from parallel session interference. The worktree-first change and delta analysis are the two halves of the same solution (isolate at start, validate before merge).

Existing PRs

All three related PRs address the narrower problem of the missing worktree step. This PR subsumes them by moving the step earlier (step 1 vs after design approval) and adding the full delta analysis system that none of them include.

Environment tested

Harness Harness version Model Model version/ID
Claude Code 2.1.87 Claude Opus 4.6 claude-opus-4-6

Evaluation

  • Initial prompt: "Please read Skill chain assumes frozen codebase — parallel sessions cause spec/plan staleness #989" followed by a full brainstorming session to design the solution
  • 5 manual eval sessions after changes, testing:
    • Level 0: no drift on base branch → clean merge (pass)
    • Level 1: implementation drift (sync→async migration) → delta plan created and executed (pass)
    • Level 2: spec drift (mirrored file deleted) → clarifying questions asked, spec updated (pass, after fixing Level 2 to include questions)
    • Level 3: fundamental drift (feature already implemented on main) → full restart recommended (pass)
    • Loop-until-clean: drift arriving during delta fix execution → second delta analysis catches it (pass)
  • Issues found and fixed during testing: agent exploring code before worktree creation, branch name collisions with orphaned worktrees, Level 2 not asking clarifying questions, delta analysis not looping

Rigor

  • If this is a skills change: I used superpowers:writing-skills and completed adversarial pressure testing (paste results below)
  • This change was tested adversarially, not just on the happy path
  • I did not modify carefully-tuned content (Red Flags table, rationalizations, "human partner" language) without extensive evals showing the change is an improvement

Note on writing-skills: We did not use the formal writing-skills RED-GREEN-REFACTOR cycle with subagent pressure testing. We tested iteratively through manual sessions, found and fixed rationalizations (agent skipping worktree, misclassifying drift levels, not looping), but did not document formal baseline behavior. We plan to run the full pressure testing cycle and update the PR with results before requesting final review.

Note on behavior-shaping content: This PR adds new behavior-shaping content: escalation level definitions, Level 2 clarifying question instructions, worktree-before-exploration warning. These were validated through iterative manual testing (5 sessions, multiple drift scenarios), not formal evals. Each addition was prompted by a specific observed failure — agent misclassified Level 2, agent skipped questions, agent explored before worktree creation. We plan to run formal pressure testing before requesting final review.

Human review

  • A human has reviewed the COMPLETE proposed diff before submission

csillag added 15 commits March 30, 2026 02:38
Addresses obra#989: skill chain assumes frozen codebase during parallel sessions.
Spec covers worktree creation at brainstorming start, session metadata
tracking, uniform entry logic for downstream skills, and delta analysis with
3-level escalation before merge.
9 tasks covering: brainstorming worktree creation, session metadata
entry logic for writing-plans/executing-plans/subagent-driven-development,
using-git-worktrees integration updates, rebase + delta analysis in
finishing skill, gitignore guidance, fallback mode, and cross-reference
verification. Addresses obra#989.
Creates isolated workspace before any code analysis. Writes
.superpowers-session.json to track base branch, base commit,
and session stage. Falls back to current directory on platforms
without worktree support. Addresses obra#989.
Adds 3-way metadata check: reuse existing worktree, continue in
fallback mode, or create worktree for standalone invocations.
Updates session stage to 'planning'. Addresses obra#989.
Adds 3-way metadata check before execution. Creates worktree only
for standalone invocations; reuses existing worktree when coming
through the brainstorming chain. Addresses obra#989.
Adds 3-way metadata check before execution. Creates worktree only
for standalone invocations; reuses existing worktree when coming
through the brainstorming chain. Addresses obra#989.
Brainstorming now calls this at Step 1 (not Phase 4). Downstream
skills only create worktrees for standalone invocations. Addresses obra#989.
…g skill

After tests pass, reads session metadata, rebases onto the original
base branch, and runs delta analysis. Classifies drift as implementation
(re-plan), spec (update spec + re-plan), or fundamental (restart
brainstorming). Merge conflicts escalate to at least level 2.
User confirms escalation before routing. Addresses obra#989.
Session metadata file is session-local and should not be committed.
Addresses obra#989.
In fallback mode (no isolation), skips rebase and runs delta analysis
by comparing all changes since session start. Addresses obra#989.
After any escalation fix completes, return to Step 2.5 for a fresh
rebase + delta analysis. Only proceed to merge options when Level 0
is reached. Prevents race condition where more changes land on the
base branch during user confirmation or delta fix execution.
Found during manual testing. Addresses obra#989.
Prevents collisions when the same topic is brainstormed multiple
times against the same commit. Branch name is now
superpowers/<topic>-<short-hash>-<random> with 4 random hex chars.
Found during manual testing. Addresses obra#989.
Instead of routing to brainstorming's present-design phase, Level 2
now reviews the spec against the base branch and asks the user
clarifying questions where drift creates ambiguity. Prevents the
agent from unilaterally updating the spec without user input.
Found during manual testing. Addresses obra#989.
Add explicit warning not to explore codebase before creating the
worktree. Agents were reading files on main before step 1, defeating
the isolation guarantee. Addresses obra#989.
In fallback mode (no worktree), the session file must be written
before any code exploration — the recorded commit hash marks the
snapshot point. Updated the critical warning to account for both
worktree and fallback paths. Addresses obra#989.
@csillag
Copy link
Copy Markdown
Author

csillag commented Mar 30, 2026

Here is the output of Claude Code session, when the finishing-a-development-branch skill

  • analyses drift on the base branch, identifies a Level 1 change, and updates the implementation accordingly.
  • then re-analyses, sees more drift but that is harmless, so it concludes.
                                                           
● Base branch is main, base commit is 9c107a3. Step 2.5: Delta analysis

● Bash(git diff 9c107a3cafa81a3acb32d12d25495baf958a1846..main)                                                                                                                                                 
  ⎿  diff --git a/docs/superpowers/plans/2026-03-30-async-transformer.md b/docs/superpowers/plans/2026-03-30-async-transformer.md
     new file mode 100644                                                                                                                                                                                       
     index 0000000..7a76ed4                                 
     … +71 lines (ctrl+o to expand)                                                                                                                                                                             
                                                                                                                                                                                                                
● Main has changed since we branched. Let me rebase and assess the impact.
                                                                                                                                                                                                                
● Bash(git rebase main 2>&1)                                                                                                                                                                                    
  ⎿  Rebasing (1/3)Rebasing (2/3)Rebasing (3/3)Successfully rebased and updated refs/heads/superpowers/add-lowercase-function-9c107a.
                                                                                                                                                                                                                
● Rebase succeeded cleanly. Now analyzing drift:            
                                                                                                                                                                                                                
  - src/transform.ts changed: toUpperCase is now async, returning Promise<string>                                                                                                                               
  - src/index.ts migrated to readline/promises with async main loop
  - Our src/lower-transform.ts mirrors the old synchronous signature                                                                                                                                            
                                                                                                                                                                                                                
  This is Level 1 — Implementation drift. The spec is still correct (we want a toLowerCase), but since the existing toUpperCase is now async, our toLowerCase should match the new async pattern to stay        
  consistent with the codebase.                                                                                                                                                                                 
                                                                                                                                                                                                                
  I recommend creating a small delta plan to update lower-transform.ts to use an async signature. Does that sound right?                                                                                        
   
✻ Brewed for 1m 16s                                                                                                                                                                                             
                                                            
❯ yes                                                                                                                                                                                                           
      
  Read 2 files (ctrl+o to expand)                                                                                                                                                                               
                                                            
● Write(docs/superpowers/plans/2026-03-30-add-lowercase-function-delta.md)                                                                                                                                      
  ⎿  Wrote 43 lines to docs/superpowers/plans/2026-03-30-add-lowercase-function-delta.md
      1 # Add Lowercase Function — Delta Plan (Async Migration)                                                                                                                                                 
      2                                                                                                                                                                                                         
      3 > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (       
        `- [ ]`) syntax for tracking.                                                                                                                                                                           
      4                                                     
      5 **Goal:** Update `toLowerCase` to async signature, matching the updated `toUpperCase` in `src/transform.ts`.                                                                                            
      6                                                     
      7 **Architecture:** Change `toLowerCase` from sync to async, mirroring the async pattern now used by `toUpperCase`.
      8
      9 **Tech Stack:** TypeScript
     … +33 lines (ctrl+o to expand)

● Executing the delta plan now.

  Step 1: Update toLowerCase to async                                                                                                                                                                           
   
● Update(src/lower-transform.ts)                                                                                                                                                                                
  ⎿  Added 1 line, removed 1 line                           
      1 -export function toLowerCase(input: string): string {                                                                                                                                                   
      1 +export async function toLowerCase(input: string): Promise<string> {                                                                                                                             
      2    return input.toLowerCase();                                                                                                                                                                          
      3  }                                                  

● Step 2: Verify it compiles

● Bash(npx tsc --noEmit)                                                                                                                                                                                        
  ⎿  (No output)
                                                                                                                                                                                                                
● Step 3: Verify full build                                 
                                                                                                                                                                                                                
● Bash(make build)                                                                                                                                                                                              
  ⎿  npx tsc 
                                                                                                                                                                                                                
● Step 4: Commit                                            
                                                                                                                                                                                                                
● Bash(git add src/lower-transform.ts docs/superpowers/plans/2026-03-30-add-lowercase-function-delta.md && git commit -m "$(cat <<'EOF'                                                                         
      refactor: make toLowerCase asyn…)
  ⎿  [superpowers/add-lowercase-function-9c107a 867fbcf] refactor: make toLowerCase async to match toUpperCase                                                                                                  
      2 files changed, 44 insertions(+), 1 deletion(-)      
      create mode 100644 docs/superpowers/plans/2026-03-30-add-lowercase-function-delta.md                                                                                                                      
                                                            
● Now re-running delta analysis to confirm Level 0.                                                                                                                                                             
   
● Bash(git fetch 2>/dev/null; git diff $(git merge-base HEAD main)..main --stat)                                                                                                                                
  ⎿   src/index.ts | 2 +-                                   
      1 file changed, 1 insertion(+), 1 deletion(-)                                                                                                                                                             
                                                                                                                                                                                                                
● Bash(git diff $(git merge-base HEAD main)..main)                                                                                                                                                              
  ⎿  diff --git a/src/index.ts b/src/index.ts                                                                                                                                                                   
     index 894649c..bc24d70 100644                                                                                                                                                                              
     --- a/src/index.ts                                                                                                                                                                                         
     … +9 lines (ctrl+o to expand)                                                                                                                                                                              
                                                                                                                                                                                                                
● Level 0 — no meaningful drift. The change is to index.ts output formatting, unrelated to our work. Proceeding to options.                                                                                     
                                                                                                                                                                                                                
  Implementation complete. What would you like to do?                                                                                                                                                           
                                                                                                                                                                                                                
  1. Merge back to main locally                                                                                                                                                                                 
  2. Push and create a Pull Request                                                                                                                                                                             
  3. Keep the branch as-is (I'll handle it later)                                                                                                                                                               
  4. Discard this work                                                                                                                                                                                          
                                                                                                                                                                                                                
  Which option?                                      

@csillag
Copy link
Copy Markdown
Author

csillag commented Mar 30, 2026

Here we catch a Level 2 drift before merging (where our new code still compiles but now it makes no sense), and successfully go back to clarifying questions


  Step 1: Verify tests

● Bash(npx tsc --noEmit)
  ⎿  (No output)

● Clean compile.

  Step 1.5: Read session metadata

  Read 1 file (ctrl+o to expand)

● Base branch: main, base commit: b43f566.

  Step 2.5: Rebase and delta analysis

● Bash(git rebase main)
  ⎿  Rebasing (1/3)Rebasing (2/3)Rebasing (3/3)Successfully rebased and updated refs
     /heads/superpowers/fetch-helper-b43f56-1ba1.

● Bash(git diff b43f566580c5ecb286c98a073b9fe12c9bcad4d8..main)
  ⎿  diff --git a/src/index.ts b/src/index.ts
     index 894649c..b70592f 100644
     --- a/src/index.ts
     … +36 lines (ctrl+o to expand)

● The delta is non-empty and has changed since last time. There's a new comment in
  transform.ts:

  // Please note: this function can't be converted for async, for ... reaons.
  export function toUpperCase(input: string): string {

  Delta analysis: Level 2 — Spec drift. Our spec states that fetchSuffix() "is intended to
  be called from toUpperCase() in a future change." However, main now explicitly says
  toUpperCase can't be converted to async. Since fetchSuffix() is async, calling it from a
  synchronous toUpperCase() and awaiting the result won't be possible as originally
  intended.

  Our current deliverable (src/fetch.ts as a standalone function) is unaffected — it
  compiles and works fine. But the motivating assumption in the spec is now contradicted by
  the codebase.

  The base branch has changed since this session started. The changes partially invalidate
  the spec's stated intent. How would you like to proceed?

  1. Update the spec to remove the future-integration mention and just treat fetchSuffix()
  as a standalone helper (then continue to merge options)
  2. Rethink the approach — if you still want to integrate with toUpperCase, we may need to
  brainstorm an alternative design
  3. Ignore it — merge as-is, deal with integration later

✻ Cogitated for 42s


❯ 2                                                                                         

● Skill(superpowers:brainstorming)
  ⎿  Successfully loaded skill

● We already have an active worktree, so I'll reuse it. Let me set up the brainstorming
  tasks and explore the current state.
                                                                                            
● Let me look at the current state of main's files.       
                                                                                            
● Bash(git show main:src/transform.ts)                                                      
  ⎿  // Please note: this function can't be converted for async, for ... reaons.            
                                                                                            
     export function toUpperCase(input: string): string {                                   
     … +2 lines (ctrl+o to expand)                                                          
                                                                                            
● Bash(git show main:src/index.ts)                                                          
  ⎿  import * as readline from "readline";                                                  
     import { toUpperCase } from "./transform";                                             
     … +13 lines (ctrl+o to expand)                                                         
                                                                                   
● Here's the situation: toUpperCase is now synchronous and explicitly marked as not         
  convertible to async. The CLI uses callback-style readline. Your original goal was to     
  append the fetched string to the output of toUpperCase.
                                                                                            
  Given that toUpperCase can't be async, where would you like the fetched string to be      
  appended? Options:                         
                                                                                            
  1. In index.ts — call fetchSuffix() at the call site (inside the prompt() callback),      
  append the result to toUpperCase(answer) before printing
  2. In a new wrapper function — create a new async function (e.g., toUpperCaseWithSuffix())
   that calls both toUpperCase and fetchSuffix, leaving toUpperCase untouched      
  3. Something else you have in mind?                                                       
                                                                                   
✻ Sautéed for 38s                                                                           
                    

@csillag
Copy link
Copy Markdown
Author

csillag commented Mar 30, 2026

And here is a Level 3 result - fundamentally changed circumstances

● I'm using the finishing-a-development-branch skill to complete this work.

  Step 1: Verify tests — Per memory, this project has no test suite. Skipping.

  Step 1.5: Read session metadata

  Read 1 file (ctrl+o to expand)

● Base branch: revert-to-sync-no-async, base commit: 9c107a3.

  Step 2.5: Rebase and Delta Analysis

● Bash(git diff 9c107a3..revert-to-sync-no-async --stat)
  ⎿   src/transform.ts | 4 ++++
      1 file changed, 4 insertions(+)

● Bash(git diff 9c107a3..revert-to-sync-no-async)
  ⎿  diff --git a/src/transform.ts b/src/transform.ts
     index 8b52565..c8a492b 100644
     --- a/src/transform.ts
     … +9 lines (ctrl+o to expand)

● This is significant. The base branch already has a toLowerCase function added — but it was
   added directly to src/transform.ts, whereas our spec calls for it in a separate file
  src/transform-lowercase.ts.

  This is Level 3 — Fundamental drift: another session already implemented what we were
  building, just in a different location.

  I recommend restarting the brainstorming process from scratch with full re-analysis of the
   codebase, since the core requirement (adding a lowercase function) has already been
  fulfilled on the base branch — just not in a separate file as you specified.

  Alternatively, you might consider this work moot, or you might want to pivot to moving the
   existing function to its own file instead. How would you like to proceed?


@csillag
Copy link
Copy Markdown
Author

csillag commented Mar 30, 2026

I have updated the PR description to match the prescribed template. (Forry for missing it the first time.)

@csillag
Copy link
Copy Markdown
Author

csillag commented Apr 1, 2026

Any feedback on this, @obra ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant