Skip to content

feat(bridge): gitbutler_absorb_smart — auto-sort files to branches by context #6

@gHashTag

Description

@gHashTag

TASK: Smart Absorb — Auto-sort files to virtual branches

Context

GitButler already has absorb (assigns hunks to existing commits/branches).
This task makes it smart: agent analyzes changed files, groups them by semantic context, and assigns each group to the correct virtual branch — or creates a new one.

PHI LOOP: edit spec → seal hash → gen → test → verdict → experience → skill commit → git commit


Tool spec

// Tool: gitbutler_absorb_smart
// Input:
interface AbsorbSmartInput {
  strategy: 'auto' | 'by-directory' | 'by-type' | 'by-feature';
  // auto: LLM decides grouping
  // by-directory: group by top-level dir (src/, tests/, docs/)
  // by-type: group by file extension (.rs, .ts, .md)
  // by-feature: LLM reads file content, infers feature context
  dryRun?: boolean; // default false — if true, return plan without executing
}

// Output:
interface AbsorbSmartResult {
  ok: boolean;
  plan: AbsorbGroup[];    // always returned (even if dryRun: false)
  executed: boolean;
  summary: string;        // human-readable: "Sorted 7 files into 3 branches"
}

interface AbsorbGroup {
  branch: string;         // target virtual branch name
  files: string[];        // file paths assigned to this branch
  action: 'existing' | 'create-new'; // use existing GB branch or create
  rationale: string;      // why these files go here
}

Algorithm

1. gitbutler_workspace_status() → get all changed files
2. IF strategy === 'auto' or 'by-feature':
   ├─ Read first 50 lines of each changed file
   └─ LLM prompt: "Group these files by feature/concern. Return JSON."
   ELSE:
   └─ Apply deterministic grouping (dir or extension)
3. Get existing GB virtual branches (but branch list)
4. Match groups to branches (fuzzy name match or LLM decision)
5. IF dryRun: return plan only
6. ELSE: for each group:
   ├─ if branch exists: gitbutler_stage(files) on that branch
   └─ if new branch needed: gitbutler_create_branch() then stage
7. Return AbsorbSmartResult

Example usage

# Dry run first (see the plan)
curl -X POST http://127.0.0.1:9200/mcp/tools/call \
  -H 'Content-Type: application/json' \
  -d '{"name":"gitbutler_absorb_smart","arguments":{"strategy":"auto","dryRun":true}}'

# Expected:
# {
#   "ok": true,
#   "plan": [
#     {"branch": "feat-auth", "files": ["src/auth.rs", "tests/auth_test.rs"], "action": "existing"},
#     {"branch": "fix-ui",   "files": ["src/ui/button.ts", "src/ui/modal.ts"],  "action": "create-new"}
#   ],
#   "executed": false,
#   "summary": "Plan: 4 files into 2 branches"
# }

# Execute
curl -X POST http://127.0.0.1:9200/mcp/tools/call \
  -H 'Content-Type: application/json' \
  -d '{"name":"gitbutler_absorb_smart","arguments":{"strategy":"auto"}}'

Chat usage example

User: "Sort my changes into branches automatically"
Agent:
1. gitbutler_absorb_smart({strategy: "auto", dryRun: true})
2. "I'll sort 7 files into 3 branches. Shall I proceed?"
3. User: "yes"
4. gitbutler_absorb_smart({strategy: "auto"})
5. ✅ Sorted 7 files: auth (3), ui (2), docs (2)

Tests required

// src/__tests__/absorb-smart.test.ts
describe('gitbutler_absorb_smart', () => {
  it('groups files by-directory correctly', ...)
  it('groups files by-type correctly', ...)
  it('dry-run returns plan without side effects', ...)
  it('creates new branch when no match exists', ...)
  it('returns structured error when workspace is clean', ...)
})

Laws

  • L1: Closes #6
  • L4: 5+ tests
  • L7: NO .sh

Success criteria

  • gitbutler_absorb_smart tool live on port 9200
  • dryRun: true returns plan without executing
  • All 3 strategies work: auto, by-directory, by-type
  • bun test green
  • experience log committed

φ² + 1/φ² = 3 | TRINITY | GO.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions