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

429 lines
9.9 KiB
Markdown

# 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
```bash
# 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
```bash
# 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
```bash
# 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:
```typescript
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:
```typescript
// 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:
```typescript
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
### Link Stack to Issues
Match issue IDs in PR titles/bodies:
```typescript
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:
```typescript
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:
```typescript
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:
```typescript
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:
```typescript
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:
```typescript
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:
```typescript
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:
```bash
# 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
```bash
# Verify Graphite initialized
gt repo init
# Verify on a branch
git branch
# Check for trunk configuration
gt repo --show
```
### PR Associations Missing
```bash
# 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