playbook/outfitter-agents/plugins/outfitter/skills/check-status/references/graphite.md

9.9 KiB

Graphite Integration

Tool-specific patterns for integrating Graphite (gt) stack visualization and PR management into status reports.

Overview

Graphite provides stack-aware version control with visual branch hierarchies and integrated PR management. Status reports should leverage stack structure for context-rich presentation.

Core Commands

Stack Visualization

# Get visual tree of stacked branches
gt log

# Output includes:
# - Branch hierarchy (parent/child relationships)
# - PR status per branch
# - Commit counts
# - Current branch indicator (◆)
# - Branch states (needs restack, needs submit, ready to merge)

Example Output:

◆ feature/auth-refactor (3) - #123 ✓ Ready to merge
├─ feature/add-jwt (2) - #122 ⏳ In progress
└─ feature/update-middleware (1) - #121 ⏸ Draft

Branch State

# Get current stack state as JSON
gt stack --json

# Returns:
# - Branch metadata (name, parent, children)
# - PR associations
# - Commit SHAs and messages
# - Sync status (ahead/behind trunk)

PR Submission Status

# Check if branches need submission
gt stack

# Shows branches with:
# - "needs submit" → changes not pushed to PR
# - "needs restack" → parent branch updated
# - "ready to merge" → approved, passing CI

Data Gathering

Stack Structure

Extract hierarchical branch relationships:

interface StackNode {
  branch: string;
  prNumber?: number;
  prStatus?: 'draft' | 'open' | 'ready' | 'merged';
  commitCount: number;
  parent?: string;
  children: string[];
  isCurrent: boolean;
  needsRestack: boolean;
  needsSubmit: boolean;
}

async function getStackStructure(): Promise<StackNode[]> {
  // Parse gt log output or gt stack --json
  const output = await exec('gt log');

  // Extract:
  // - Branch names and hierarchy
  // - PR numbers (from "#123" markers)
  // - Status indicators (✓ ⏳ ⏸)
  // - Commit counts (from "(N)" markers)
  // - Current branch (◆ marker)

  return parseStackTree(output);
}

PR Integration

Graphite automatically links branches to PRs:

// Get PR metadata for stack
async function getStackPRs(branches: string[]): Promise<PRMetadata[]> {
  // Option 1: Parse from gt log (includes basic status)
  // Option 2: Query GitHub directly with PR numbers
  // Option 3: Use gt pr status --json (if available)

  const prNumbers = branches
    .map(b => extractPRNumber(b))
    .filter(Boolean);

  // Fetch details from GitHub (see github.md)
  return fetchPRDetails(prNumbers);
}

Time Filtering

Graphite doesn't natively support time filtering, so filter results:

async function getRecentStackActivity(since: string): Promise<StackActivity> {
  // Get full stack
  const stack = await getStackStructure();

  // Parse time constraint
  const cutoff = parseTimeConstraint(since); // "-24h" → Date

  // Filter by git commit timestamps
  for (const node of stack) {
    const commits = await exec(`git log ${node.branch} --since="${cutoff}" --format="%H %s %cr"`);
    node.recentCommits = parseCommits(commits);
  }

  // Only show branches with activity
  return stack.filter(n => n.recentCommits.length > 0);
}

Presentation Templates

Stack Tree Format

📊 GRAPHITE STACK
{current_branch_name}

{tree visualization from gt log}

Stack Summary:
  Branches: {total} ({open} with PRs)
  Ready to merge: {ready_count}
  Needs attention: {needs_restack + needs_submit}

Stack-Aware PR Grouping

Organize PRs by stack position (bottom to top):

🔀 PULL REQUESTS (Stack-Aware)

Stack: {stack_name}
├─ PR #123: [feature/auth-refactor] Refactor authentication
│  CI: ✓ 3/3 passing | Reviews: ✓ 2 approved
│  Updated: 3 hours ago
│  └─ Ready to merge ✓
│
├─ PR #122: [feature/add-jwt] Add JWT token support
│  CI: ⏳ 2/3 passing | Reviews: 👀 1 change requested
│  Updated: 5 hours ago
│  └─ Depends on: PR #121
│
└─ PR #121: [feature/update-middleware] Update auth middleware
   CI: ✗ 1/3 failing | Reviews: ⏸ No reviews
   Updated: 1 day ago
   └─ Blocker: CI failing ◆◆

Attention Indicators

Highlight stack-specific issues:

⚠️  STACK ATTENTION NEEDED
◆◆ PR #121: Blocking entire stack (failing CI)
◆  Branch feature/add-jwt: Needs restack (parent updated)
◇  Branch feature/auth-refactor: Needs submit (local changes)

Cross-Referencing

Match issue IDs in PR titles/bodies:

function linkStackToIssues(stack: StackNode[], issues: Issue[]): void {
  for (const node of stack) {
    // Extract issue references from PR title
    // Pattern: "BLZ-123: Feature title" or "[BLZ-123] Feature title"
    const issueKeys = extractIssueKeys(node.prTitle);

    // Find matching issues
    node.relatedIssues = issues.filter(i => issueKeys.includes(i.key));
  }
}

Dependency Tracking

Show blocked/blocking relationships:

interface StackDependencies {
  branch: string;
  blockedBy: string[];  // Parent branches not merged
  blocking: string[];   // Child branches waiting
}

function analyzeStackDependencies(stack: StackNode[]): StackDependencies[] {
  return stack.map(node => ({
    branch: node.branch,
    blockedBy: node.parent && !isReadyToMerge(node.parent) ? [node.parent] : [],
    blocking: node.children.filter(child => isReadyToMerge(node) && !isReadyToMerge(child))
  }));
}

Best Practices

Efficient Queries

Minimize git/Graphite calls:

  1. Single gt log for stack structure
  2. Single git log --all --since for commit history
  3. Batch PR queries to GitHub (see github.md)

State Caching

Cache stack state to avoid repeated parsing:

interface StackCache {
  timestamp: Date;
  stack: StackNode[];
  ttl: number; // milliseconds
}

function getCachedStack(ttl = 60000): StackNode[] | null {
  const cache = loadCache();
  if (cache && Date.now() - cache.timestamp.getTime() < ttl) {
    return cache.stack;
  }
  return null;
}

Error Handling

Handle common Graphite errors:

try {
  const stack = await exec('gt log');
} catch (error) {
  if (error.message.includes('not a git repository')) {
    return null; // Gracefully skip Graphite section
  }
  if (error.message.includes('graphite not initialized')) {
    // Suggest: gt repo init
    return null;
  }
  throw error; // Unexpected error
}

Integration Points

With GitHub (see github.md)

Combine Graphite stack structure with GitHub PR details:

async function enrichStackWithGitHub(stack: StackNode[]): Promise<void> {
  const prNumbers = stack
    .map(n => n.prNumber)
    .filter(Boolean);

  const prDetails = await fetchGitHubPRs(prNumbers); // See github.md

  for (const node of stack) {
    const pr = prDetails.find(p => p.number === node.prNumber);
    if (pr) {
      node.ciStatus = pr.ciStatus;
      node.reviewStatus = pr.reviewStatus;
      node.updatedAt = pr.updatedAt;
    }
  }
}

With Linear (see linear.md)

Link Linear issues to stack branches:

async function linkStackToLinear(stack: StackNode[]): Promise<void> {
  // Extract all issue keys from PR titles
  const issueKeys = stack
    .flatMap(n => extractIssueKeys(n.prTitle || ''))
    .filter(Boolean);

  // Fetch Linear issues
  const issues = await fetchLinearIssues({ keys: issueKeys });

  // Annotate stack nodes
  for (const node of stack) {
    const keys = extractIssueKeys(node.prTitle || '');
    node.linearIssues = issues.filter(i => keys.includes(i.identifier));
  }
}

Common Patterns

Stack Health Score

Calculate stack quality metrics:

interface StackHealth {
  score: number; // 0-100
  issues: string[];
  readyToMerge: number;
  needsWork: number;
}

function calculateStackHealth(stack: StackNode[]): StackHealth {
  let score = 100;
  const issues: string[] = [];

  const needsRestack = stack.filter(n => n.needsRestack).length;
  const needsSubmit = stack.filter(n => n.needsSubmit).length;
  const failingCI = stack.filter(n => n.ciStatus === 'failing').length;
  const readyToMerge = stack.filter(n => n.prStatus === 'ready').length;

  score -= needsRestack * 10; // -10 per restack needed
  score -= needsSubmit * 5;   // -5 per submit needed
  score -= failingCI * 20;    // -20 per failing CI

  if (needsRestack) issues.push(`${needsRestack} branches need restack`);
  if (needsSubmit) issues.push(`${needsSubmit} branches need submit`);
  if (failingCI) issues.push(`${failingCI} PRs with failing CI`);

  return {
    score: Math.max(0, score),
    issues,
    readyToMerge,
    needsWork: needsRestack + needsSubmit + failingCI
  };
}

Stack Timeline

Show activity timeline across stack:

📅 STACK TIMELINE (Last 24 hours)

2 hours ago  │ PR #123 approved by @reviewer
3 hours ago  │ feature/auth-refactor: Pushed 2 commits
5 hours ago  │ PR #122: CI checks passing
1 day ago    │ feature/add-jwt: Created PR

CLI Reference

Essential Graphite commands for status reporting:

# Stack visualization
gt log                    # Visual tree
gt log --short            # Compact format
gt log --json             # Machine-readable

# Stack state
gt stack                  # Current stack info
gt stack --json           # Structured output

# Branch operations (for context)
gt upstack                # Show branches above
gt downstack              # Show branches below

# PR operations (for context)
gt pr status              # PR status for stack
gt submit --dry-run       # Preview what would be submitted

Troubleshooting

Stack Not Showing

# Verify Graphite initialized
gt repo init

# Verify on a branch
git branch

# Check for trunk configuration
gt repo --show

PR Associations Missing

# PRs might not be associated with branches
# Check with:
gt pr status

# Re-associate if needed:
gt pr submit

Performance Issues

Large stacks (>20 branches) can slow down:

  • Cache gt log output
  • Limit depth with gt log --depth 10
  • Filter to relevant branches only
  • Consider pagination for display