the-grid-cc 1.7.12 → 1.7.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,981 @@
1
+ # Git Autonomy Protocol - Technical Design Document
2
+
3
+ **Version:** 1.0
4
+ **Author:** Grid Git Operator Program
5
+ **Date:** 2026-01-23
6
+
7
+ ---
8
+
9
+ ## Executive Summary
10
+
11
+ The Git Autonomy Protocol enables The Grid to manage git operations fully autonomously while maintaining safety guarantees. This document specifies the technical design for automatic branch creation, atomic commits, PR creation, conflict resolution, and safe push operations.
12
+
13
+ ---
14
+
15
+ ## Table of Contents
16
+
17
+ 1. [Design Principles](#design-principles)
18
+ 2. [Architecture Overview](#architecture-overview)
19
+ 3. [Branch Management](#branch-management)
20
+ 4. [Commit Protocol](#commit-protocol)
21
+ 5. [Push Automation](#push-automation)
22
+ 6. [Conflict Resolution](#conflict-resolution)
23
+ 7. [PR Automation](#pr-automation)
24
+ 8. [Safety Guarantees](#safety-guarantees)
25
+ 9. [Integration Points](#integration-points)
26
+ 10. [Configuration](#configuration)
27
+ 11. [Error Handling](#error-handling)
28
+ 12. [Future Enhancements](#future-enhancements)
29
+
30
+ ---
31
+
32
+ ## Design Principles
33
+
34
+ ### 1. Safety First
35
+ No autonomous operation should risk data loss or corrupt git history. Protected branch rules are enforced at the protocol level, not by convention.
36
+
37
+ ### 2. Atomic Operations
38
+ Each git operation is atomic and recoverable. Partial commits or incomplete pushes leave the repository in a known good state.
39
+
40
+ ### 3. Transparency
41
+ All git operations are logged and visible. Users can always see what The Grid did to their repository.
42
+
43
+ ### 4. Graceful Degradation
44
+ When autonomous operation fails (conflicts, auth issues), the system gracefully falls back to manual intervention with clear guidance.
45
+
46
+ ### 5. Convention Over Configuration
47
+ Sensible defaults enable zero-configuration usage while allowing customization for power users.
48
+
49
+ ---
50
+
51
+ ## Architecture Overview
52
+
53
+ ### Component Diagram
54
+
55
+ ```
56
+ +-------------------+
57
+ | Master Control |
58
+ | (Orchestrator) |
59
+ +--------+----------+
60
+ |
61
+ | spawns
62
+ v
63
+ +-------------------+ +------------------+
64
+ | Git Operator |<--->| Local Git Repo |
65
+ | (Agent) | +------------------+
66
+ +--------+----------+ |
67
+ | | remote
68
+ | uses v
69
+ v +------------------+
70
+ +-------------------+ | GitHub/GitLab |
71
+ | /grid:branch | | (Remote) |
72
+ | (Command) | +------------------+
73
+ +-------------------+
74
+ ```
75
+
76
+ ### Responsibility Matrix
77
+
78
+ | Component | Responsibilities |
79
+ |-----------|------------------|
80
+ | Master Control | Session lifecycle, spawning operators |
81
+ | Git Operator | All git operations, safety enforcement |
82
+ | /grid:branch | User-facing branch management commands |
83
+ | Executor | Requests commits via Git Operator |
84
+
85
+ ---
86
+
87
+ ## Branch Management
88
+
89
+ ### Automatic Branch Creation
90
+
91
+ When a Grid session starts on a protected branch, the system automatically creates a feature branch:
92
+
93
+ ```python
94
+ def ensure_feature_branch(cluster_name: str) -> str:
95
+ """Ensure we're on a feature branch, create if needed."""
96
+
97
+ current = get_current_branch()
98
+
99
+ # Already on feature branch
100
+ if not is_protected(current):
101
+ return current
102
+
103
+ # Generate branch name from cluster
104
+ slug = slugify(cluster_name) # "React Todo App" -> "react-todo-app"
105
+ branch_name = f"grid/{slug}"
106
+
107
+ # Handle existing branch with same name
108
+ if branch_exists(branch_name):
109
+ branch_name = f"{branch_name}-{timestamp()}"
110
+
111
+ # Create and switch
112
+ git_checkout_b(branch_name)
113
+
114
+ return branch_name
115
+ ```
116
+
117
+ ### Branch Naming Algorithm
118
+
119
+ ```python
120
+ def generate_branch_name(context: GridContext) -> str:
121
+ """Generate appropriate branch name for context."""
122
+
123
+ prefix = config.get("branch_prefix", "grid/")
124
+
125
+ if context.type == "cluster":
126
+ # Full project work
127
+ base = slugify(context.cluster_name)
128
+ elif context.type == "quick":
129
+ # Quick task
130
+ base = slugify(context.task_description[:30])
131
+ elif context.type == "debug":
132
+ # Debug session
133
+ base = f"fix-{slugify(context.symptoms[0][:20])}"
134
+ else:
135
+ # Fallback
136
+ base = f"work-{date_slug()}"
137
+
138
+ # Ensure uniqueness
139
+ candidate = f"{prefix}{base}"
140
+ if branch_exists(candidate):
141
+ candidate = f"{candidate}-{short_hash()}"
142
+
143
+ return candidate
144
+ ```
145
+
146
+ ### Protected Branch Detection
147
+
148
+ ```python
149
+ PROTECTED_BRANCHES = ["main", "master", "production", "release/*"]
150
+
151
+ def is_protected(branch: str) -> bool:
152
+ """Check if branch is protected."""
153
+
154
+ # Check config overrides
155
+ protected = config.get("protected_branches", PROTECTED_BRANCHES)
156
+
157
+ for pattern in protected:
158
+ if pattern.endswith("/*"):
159
+ # Wildcard match
160
+ prefix = pattern[:-2]
161
+ if branch.startswith(prefix):
162
+ return True
163
+ elif branch == pattern:
164
+ return True
165
+
166
+ return False
167
+ ```
168
+
169
+ ---
170
+
171
+ ## Commit Protocol
172
+
173
+ ### Atomic Commits per Thread
174
+
175
+ Each thread in a Grid plan produces exactly one commit:
176
+
177
+ ```python
178
+ def commit_thread(thread: Thread, files: List[str], message: CommitMessage) -> str:
179
+ """Create atomic commit for a single thread."""
180
+
181
+ # Validate files exist
182
+ for file in files:
183
+ if not os.path.exists(file):
184
+ raise CommitError(f"File not found: {file}")
185
+
186
+ # Stage files individually (NEVER git add .)
187
+ for file in files:
188
+ git_add(file)
189
+
190
+ # Verify staged matches expected
191
+ staged = get_staged_files()
192
+ if set(staged) != set(files):
193
+ raise CommitError(f"Staged files mismatch. Expected: {files}, Got: {staged}")
194
+
195
+ # Create commit
196
+ commit_hash = git_commit(format_message(message))
197
+
198
+ return commit_hash
199
+ ```
200
+
201
+ ### Commit Message Format
202
+
203
+ ```python
204
+ @dataclass
205
+ class CommitMessage:
206
+ type: str # feat, fix, refactor, etc.
207
+ scope: str # block-01, quick, debug
208
+ subject: str # Brief description (50 chars)
209
+ body: List[str] # Bullet points of changes
210
+
211
+ def format_message(msg: CommitMessage) -> str:
212
+ """Format commit message according to convention."""
213
+
214
+ # Header line
215
+ header = f"{msg.type}({msg.scope}): {msg.subject}"
216
+
217
+ if len(header) > 72:
218
+ raise CommitError(f"Header too long: {len(header)} chars (max 72)")
219
+
220
+ # Body
221
+ body_lines = [f"- {line}" for line in msg.body]
222
+ body = "\n".join(body_lines)
223
+
224
+ return f"{header}\n\n{body}"
225
+ ```
226
+
227
+ ### Commit Types Taxonomy
228
+
229
+ | Type | Description | Example |
230
+ |------|-------------|---------|
231
+ | `feat` | New feature or capability | `feat(auth): add JWT token refresh` |
232
+ | `fix` | Bug fix | `fix(api): handle null user response` |
233
+ | `refactor` | Code restructure, no behavior change | `refactor(utils): simplify date formatting` |
234
+ | `perf` | Performance improvement | `perf(db): add index on user_id` |
235
+ | `test` | Test additions/modifications | `test(auth): add login flow tests` |
236
+ | `docs` | Documentation only | `docs(api): update endpoint descriptions` |
237
+ | `chore` | Tooling, deps, config | `chore(deps): upgrade react to 19` |
238
+ | `style` | Formatting, no code change | `style(lint): apply prettier` |
239
+
240
+ ---
241
+
242
+ ## Push Automation
243
+
244
+ ### Push Timing Strategies
245
+
246
+ ```python
247
+ class PushStrategy(Enum):
248
+ IMMEDIATE = "immediate" # After every commit
249
+ WAVE = "wave" # After each wave completes
250
+ BLOCK = "block" # After each block completes
251
+ MANUAL = "manual" # Never auto-push
252
+
253
+ def should_push(strategy: PushStrategy, event: GridEvent) -> bool:
254
+ """Determine if we should push based on strategy and event."""
255
+
256
+ match strategy:
257
+ case PushStrategy.IMMEDIATE:
258
+ return event.type == "commit"
259
+ case PushStrategy.WAVE:
260
+ return event.type == "wave_complete"
261
+ case PushStrategy.BLOCK:
262
+ return event.type == "block_complete"
263
+ case PushStrategy.MANUAL:
264
+ return False
265
+ ```
266
+
267
+ ### Safe Push Protocol
268
+
269
+ ```python
270
+ def safe_push(branch: str, remote: str = "origin") -> PushResult:
271
+ """Push with safety checks."""
272
+
273
+ # Pre-flight checks
274
+ if is_protected(branch):
275
+ raise PushError("Cannot push to protected branch")
276
+
277
+ # Fetch current remote state
278
+ git_fetch(remote, branch)
279
+
280
+ # Analyze divergence
281
+ divergence = analyze_divergence(branch, f"{remote}/{branch}")
282
+
283
+ match divergence:
284
+ case Divergence.UP_TO_DATE:
285
+ return PushResult(status="nothing_to_push")
286
+
287
+ case Divergence.AHEAD:
288
+ # Safe to push
289
+ git_push(remote, branch)
290
+ return PushResult(status="pushed")
291
+
292
+ case Divergence.BEHIND:
293
+ # Need to pull first
294
+ git_pull_rebase(remote, branch)
295
+ git_push(remote, branch)
296
+ return PushResult(status="pulled_and_pushed")
297
+
298
+ case Divergence.DIVERGED:
299
+ # Attempt auto-merge
300
+ result = attempt_auto_merge(remote, branch)
301
+ if result.success:
302
+ git_push(remote, branch)
303
+ return PushResult(status="merged_and_pushed")
304
+ else:
305
+ return PushResult(status="conflict", conflicts=result.conflicts)
306
+ ```
307
+
308
+ ### Divergence Analysis
309
+
310
+ ```python
311
+ class Divergence(Enum):
312
+ UP_TO_DATE = "up_to_date"
313
+ AHEAD = "ahead"
314
+ BEHIND = "behind"
315
+ DIVERGED = "diverged"
316
+
317
+ def analyze_divergence(local: str, remote: str) -> Divergence:
318
+ """Analyze how local and remote have diverged."""
319
+
320
+ try:
321
+ base = git_merge_base(local, remote)
322
+ except GitError:
323
+ # Remote doesn't exist yet
324
+ return Divergence.AHEAD
325
+
326
+ local_head = git_rev_parse(local)
327
+ remote_head = git_rev_parse(remote)
328
+
329
+ if local_head == remote_head:
330
+ return Divergence.UP_TO_DATE
331
+ elif local_head == base:
332
+ return Divergence.BEHIND
333
+ elif remote_head == base:
334
+ return Divergence.AHEAD
335
+ else:
336
+ return Divergence.DIVERGED
337
+ ```
338
+
339
+ ---
340
+
341
+ ## Conflict Resolution
342
+
343
+ ### Conflict Detection
344
+
345
+ ```python
346
+ def detect_conflicts(ours: str, theirs: str) -> List[ConflictFile]:
347
+ """Detect which files would conflict in a merge."""
348
+
349
+ base = git_merge_base(ours, theirs)
350
+
351
+ # Use git merge-tree for dry-run conflict detection
352
+ result = git_merge_tree(base, ours, theirs)
353
+
354
+ conflicts = []
355
+ for file in parse_merge_tree_output(result):
356
+ if file.has_conflict:
357
+ conflicts.append(ConflictFile(
358
+ path=file.path,
359
+ ours_change=file.ours_change,
360
+ theirs_change=file.theirs_change,
361
+ conflict_type=classify_conflict(file)
362
+ ))
363
+
364
+ return conflicts
365
+ ```
366
+
367
+ ### Conflict Classification
368
+
369
+ ```python
370
+ class ConflictType(Enum):
371
+ BOTH_MODIFIED = "both_modified" # Same file changed differently
372
+ DELETE_MODIFY = "delete_modify" # One deleted, one modified
373
+ ADD_ADD = "add_add" # Both added same file
374
+ RENAME_RENAME = "rename_rename" # Both renamed differently
375
+
376
+ def classify_conflict(file: ConflictFile) -> ConflictType:
377
+ """Classify the type of conflict for resolution strategy."""
378
+
379
+ if file.ours_deleted and file.theirs_modified:
380
+ return ConflictType.DELETE_MODIFY
381
+ elif file.theirs_deleted and file.ours_modified:
382
+ return ConflictType.DELETE_MODIFY
383
+ elif file.ours_added and file.theirs_added:
384
+ return ConflictType.ADD_ADD
385
+ elif file.ours_renamed and file.theirs_renamed:
386
+ return ConflictType.RENAME_RENAME
387
+ else:
388
+ return ConflictType.BOTH_MODIFIED
389
+ ```
390
+
391
+ ### Auto-Resolution Strategies
392
+
393
+ ```python
394
+ def attempt_auto_resolve(conflict: ConflictFile) -> Optional[Resolution]:
395
+ """Attempt to auto-resolve a conflict if safe."""
396
+
397
+ # Only auto-resolve non-destructive cases
398
+ match conflict.type:
399
+ case ConflictType.BOTH_MODIFIED:
400
+ # Check if changes are in different regions
401
+ if non_overlapping_changes(conflict):
402
+ return merge_non_overlapping(conflict)
403
+ else:
404
+ return None # Manual resolution needed
405
+
406
+ case ConflictType.ADD_ADD:
407
+ # Both added same file - check if identical
408
+ if files_identical(conflict.ours, conflict.theirs):
409
+ return Resolution(keep="ours", reason="identical")
410
+ else:
411
+ return None
412
+
413
+ case _:
414
+ return None # All other cases need manual resolution
415
+ ```
416
+
417
+ ### Manual Resolution Workflow
418
+
419
+ ```python
420
+ def request_manual_resolution(conflicts: List[ConflictFile]) -> ConflictCheckpoint:
421
+ """Create checkpoint for manual conflict resolution."""
422
+
423
+ return ConflictCheckpoint(
424
+ type="conflict",
425
+ conflicts=[
426
+ {
427
+ "file": c.path,
428
+ "ours": c.ours_change,
429
+ "theirs": c.theirs_change,
430
+ "type": c.conflict_type.value
431
+ }
432
+ for c in conflicts
433
+ ],
434
+ options=[
435
+ Option("keep_ours", "Keep our changes, discard theirs"),
436
+ Option("keep_theirs", "Keep their changes, discard ours"),
437
+ Option("manual", "Open files and resolve manually"),
438
+ Option("abort", "Abort merge, keep current state")
439
+ ],
440
+ instructions="""
441
+ To resolve manually:
442
+ 1. Open conflicting files
443
+ 2. Look for <<<<<<< and >>>>>>> markers
444
+ 3. Edit to desired state
445
+ 4. Remove conflict markers
446
+ 5. Stage resolved files: git add {file}
447
+ 6. Continue: git merge --continue
448
+ """
449
+ )
450
+ ```
451
+
452
+ ---
453
+
454
+ ## PR Automation
455
+
456
+ ### PR Content Generation
457
+
458
+ ```python
459
+ def generate_pr_content(branch: str, base: str = "main") -> PRContent:
460
+ """Generate PR title, body, and metadata."""
461
+
462
+ # Gather commit information
463
+ commits = git_log(f"{base}..{branch}", format="%s")
464
+ commit_details = git_log(f"{base}..{branch}", format="%H %s")
465
+ files_changed = git_diff_stat(base, branch)
466
+
467
+ # Determine PR type from commits
468
+ pr_type = infer_pr_type(commits)
469
+
470
+ # Generate title
471
+ if len(commits) == 1:
472
+ title = commits[0]
473
+ else:
474
+ title = f"{pr_type}: {summarize_commits(commits)}"
475
+
476
+ # Generate body
477
+ body = generate_pr_body(
478
+ commits=commit_details,
479
+ files_changed=files_changed,
480
+ grid_metadata=read_grid_state()
481
+ )
482
+
483
+ return PRContent(title=title, body=body, labels=infer_labels(commits))
484
+ ```
485
+
486
+ ### PR Body Template
487
+
488
+ ```python
489
+ PR_BODY_TEMPLATE = """
490
+ ## Summary
491
+ {summary}
492
+
493
+ ## Changes
494
+ {commit_list}
495
+
496
+ ## Files Changed
497
+ {files_stat}
498
+
499
+ ## Grid Session
500
+ - **Cluster:** {cluster_name}
501
+ - **Blocks completed:** {blocks_completed}
502
+ - **Total commits:** {commit_count}
503
+
504
+ ## Test Plan
505
+ {test_plan}
506
+
507
+ ## Screenshots
508
+ {screenshots}
509
+
510
+ ---
511
+ *Automatically generated by The Grid*
512
+ """
513
+
514
+ def generate_pr_body(commits, files_changed, grid_metadata) -> str:
515
+ """Fill in PR body template."""
516
+
517
+ summary = generate_summary(commits)
518
+ commit_list = format_commit_list(commits)
519
+
520
+ # Extract test plan from Grid summaries
521
+ test_plan = extract_test_plan(grid_metadata)
522
+
523
+ # Check for screenshots
524
+ screenshots = find_screenshots()
525
+ screenshot_section = format_screenshots(screenshots) if screenshots else "_No UI changes_"
526
+
527
+ return PR_BODY_TEMPLATE.format(
528
+ summary=summary,
529
+ commit_list=commit_list,
530
+ files_stat=files_changed,
531
+ cluster_name=grid_metadata.get("cluster", "N/A"),
532
+ blocks_completed=grid_metadata.get("blocks_completed", "N/A"),
533
+ commit_count=len(commits),
534
+ test_plan=test_plan,
535
+ screenshots=screenshot_section
536
+ )
537
+ ```
538
+
539
+ ### GitHub CLI Integration
540
+
541
+ ```python
542
+ def create_pr(content: PRContent, draft: bool = False) -> PRResult:
543
+ """Create PR using GitHub CLI."""
544
+
545
+ # Ensure branch is pushed
546
+ ensure_pushed()
547
+
548
+ # Check for existing PR
549
+ existing = gh_pr_view()
550
+ if existing:
551
+ return PRResult(
552
+ status="exists",
553
+ number=existing.number,
554
+ url=existing.url
555
+ )
556
+
557
+ # Create PR
558
+ args = [
559
+ "gh", "pr", "create",
560
+ "--title", content.title,
561
+ "--body", content.body,
562
+ "--base", content.base
563
+ ]
564
+
565
+ if draft:
566
+ args.append("--draft")
567
+
568
+ if content.labels:
569
+ args.extend(["--label", ",".join(content.labels)])
570
+
571
+ result = subprocess.run(args, capture_output=True, text=True)
572
+
573
+ if result.returncode != 0:
574
+ raise PRError(result.stderr)
575
+
576
+ # Parse PR URL from output
577
+ pr_url = result.stdout.strip()
578
+ pr_number = int(pr_url.split("/")[-1])
579
+
580
+ return PRResult(status="created", number=pr_number, url=pr_url)
581
+ ```
582
+
583
+ ---
584
+
585
+ ## Safety Guarantees
586
+
587
+ ### Forbidden Operations Matrix
588
+
589
+ | Operation | Condition | Allowed |
590
+ |-----------|-----------|---------|
591
+ | `git push --force` | Never automatic | NO |
592
+ | `git push --force` | Explicit user confirmation | YES |
593
+ | `git reset --hard` | On unshared local branch | YES |
594
+ | `git reset --hard` | On shared/pushed branch | NO |
595
+ | `git branch -D` | Merged branches | YES |
596
+ | `git branch -D` | Unmerged branches | CONFIRM |
597
+ | Commit to main | Never | NO |
598
+ | Commit to feature | Always | YES |
599
+
600
+ ### Safety Enforcement
601
+
602
+ ```python
603
+ class SafetyGuard:
604
+ """Enforces safety rules for git operations."""
605
+
606
+ FORBIDDEN_FLAGS = ["--force", "-f", "--force-with-lease", "--no-verify"]
607
+
608
+ def validate_command(self, command: List[str]) -> ValidationResult:
609
+ """Validate a git command before execution."""
610
+
611
+ # Check for forbidden flags
612
+ for flag in self.FORBIDDEN_FLAGS:
613
+ if flag in command:
614
+ if not self.has_explicit_user_confirmation():
615
+ return ValidationResult(
616
+ allowed=False,
617
+ reason=f"Forbidden flag: {flag}",
618
+ requires_confirmation=True
619
+ )
620
+
621
+ # Check for protected branch operations
622
+ if self.is_protected_branch_operation(command):
623
+ return ValidationResult(
624
+ allowed=False,
625
+ reason="Operation on protected branch"
626
+ )
627
+
628
+ return ValidationResult(allowed=True)
629
+
630
+ def is_protected_branch_operation(self, command: List[str]) -> bool:
631
+ """Check if command operates on protected branch."""
632
+
633
+ if command[:2] == ["git", "checkout"] and len(command) > 2:
634
+ target = command[2]
635
+ if is_protected(target):
636
+ return False # Checkout is OK
637
+
638
+ if command[:2] == ["git", "commit"]:
639
+ current = get_current_branch()
640
+ if is_protected(current):
641
+ return True
642
+
643
+ return False
644
+ ```
645
+
646
+ ### Audit Logging
647
+
648
+ ```python
649
+ def audit_git_operation(operation: str, args: List[str], result: Any):
650
+ """Log git operation for audit trail."""
651
+
652
+ entry = {
653
+ "timestamp": datetime.now().isoformat(),
654
+ "operation": operation,
655
+ "args": args,
656
+ "result": str(result),
657
+ "branch": get_current_branch(),
658
+ "commit": get_current_commit(),
659
+ "user": os.environ.get("USER", "unknown")
660
+ }
661
+
662
+ # Append to audit log
663
+ audit_path = ".grid/git_audit.jsonl"
664
+ with open(audit_path, "a") as f:
665
+ f.write(json.dumps(entry) + "\n")
666
+ ```
667
+
668
+ ---
669
+
670
+ ## Integration Points
671
+
672
+ ### Master Control Integration
673
+
674
+ ```python
675
+ # MC spawns Git Operator at session start
676
+ def start_grid_session(request: str):
677
+ # ... planning ...
678
+
679
+ # Ensure feature branch
680
+ Task(
681
+ prompt=f"""
682
+ First, read ~/.claude/agents/grid-git-operator.md for your role.
683
+
684
+ SESSION START
685
+
686
+ Cluster: {cluster_name}
687
+
688
+ Ensure we're on an appropriate feature branch.
689
+ Report branch status.
690
+ """,
691
+ description="Git: Branch setup"
692
+ )
693
+ ```
694
+
695
+ ### Executor Integration
696
+
697
+ ```python
698
+ # Executor requests commit after thread completion
699
+ def complete_thread(thread: Thread, files: List[str]):
700
+ # ... implementation work ...
701
+
702
+ # Commit via Git Operator
703
+ Task(
704
+ prompt=f"""
705
+ First, read ~/.claude/agents/grid-git-operator.md for your role.
706
+
707
+ COMMIT THREAD
708
+
709
+ Thread: {thread.name}
710
+ Files: {files}
711
+ Type: {thread.commit_type}
712
+ Scope: {thread.block_id}
713
+ Description: {thread.description}
714
+
715
+ Create atomic commit.
716
+ """,
717
+ description=f"Git: Commit {thread.name}"
718
+ )
719
+ ```
720
+
721
+ ### Wave Completion Hook
722
+
723
+ ```python
724
+ # After wave completes, trigger push
725
+ def on_wave_complete(wave: Wave):
726
+ if config.get("auto_push") == "wave":
727
+ Task(
728
+ prompt=f"""
729
+ First, read ~/.claude/agents/grid-git-operator.md for your role.
730
+
731
+ WAVE COMPLETE
732
+
733
+ Wave: {wave.number}
734
+ Commits: {wave.commits}
735
+
736
+ Push to remote if configured.
737
+ """,
738
+ description=f"Git: Push wave {wave.number}"
739
+ )
740
+ ```
741
+
742
+ ---
743
+
744
+ ## Configuration
745
+
746
+ ### .grid/config.json Schema
747
+
748
+ ```json
749
+ {
750
+ "$schema": "http://json-schema.org/draft-07/schema#",
751
+ "type": "object",
752
+ "properties": {
753
+ "git": {
754
+ "type": "object",
755
+ "properties": {
756
+ "auto_branch": {
757
+ "type": "boolean",
758
+ "default": true,
759
+ "description": "Automatically create feature branch from protected branches"
760
+ },
761
+ "branch_prefix": {
762
+ "type": "string",
763
+ "default": "grid/",
764
+ "description": "Prefix for auto-created branches"
765
+ },
766
+ "auto_push": {
767
+ "type": "string",
768
+ "enum": ["immediate", "wave", "block", "manual"],
769
+ "default": "wave",
770
+ "description": "When to automatically push"
771
+ },
772
+ "auto_pr": {
773
+ "type": "boolean",
774
+ "default": false,
775
+ "description": "Automatically create PR on session complete"
776
+ },
777
+ "protected_branches": {
778
+ "type": "array",
779
+ "items": { "type": "string" },
780
+ "default": ["main", "master", "production"],
781
+ "description": "Branches that require PRs"
782
+ },
783
+ "default_base": {
784
+ "type": "string",
785
+ "default": "main",
786
+ "description": "Default base branch for PRs"
787
+ },
788
+ "commit_signing": {
789
+ "type": "boolean",
790
+ "default": false,
791
+ "description": "Sign commits with GPG"
792
+ },
793
+ "wip_commits": {
794
+ "type": "boolean",
795
+ "default": true,
796
+ "description": "Create WIP commits on session pause"
797
+ }
798
+ }
799
+ }
800
+ }
801
+ }
802
+ ```
803
+
804
+ ### Environment Variables
805
+
806
+ | Variable | Description | Default |
807
+ |----------|-------------|---------|
808
+ | `GRID_GIT_AUTO_PUSH` | Push strategy | `wave` |
809
+ | `GRID_GIT_AUTO_PR` | Auto-create PRs | `false` |
810
+ | `GRID_GIT_BRANCH_PREFIX` | Branch name prefix | `grid/` |
811
+ | `GRID_GIT_PROTECTED` | Comma-separated protected branches | `main,master` |
812
+
813
+ ---
814
+
815
+ ## Error Handling
816
+
817
+ ### Error Categories
818
+
819
+ ```python
820
+ class GitError(Exception):
821
+ """Base class for git errors."""
822
+ pass
823
+
824
+ class ProtectedBranchError(GitError):
825
+ """Attempted operation on protected branch."""
826
+ pass
827
+
828
+ class ConflictError(GitError):
829
+ """Merge conflict detected."""
830
+ def __init__(self, conflicts: List[ConflictFile]):
831
+ self.conflicts = conflicts
832
+
833
+ class AuthenticationError(GitError):
834
+ """Git authentication failed."""
835
+ pass
836
+
837
+ class RemoteError(GitError):
838
+ """Remote operation failed."""
839
+ pass
840
+ ```
841
+
842
+ ### Error Recovery
843
+
844
+ ```python
845
+ def handle_git_error(error: GitError) -> ErrorRecovery:
846
+ """Determine recovery strategy for git error."""
847
+
848
+ match error:
849
+ case ProtectedBranchError():
850
+ return ErrorRecovery(
851
+ action="create_branch",
852
+ message="Cannot commit to protected branch. Creating feature branch."
853
+ )
854
+
855
+ case ConflictError() as e:
856
+ return ErrorRecovery(
857
+ action="checkpoint",
858
+ message="Merge conflicts detected.",
859
+ checkpoint=request_manual_resolution(e.conflicts)
860
+ )
861
+
862
+ case AuthenticationError():
863
+ return ErrorRecovery(
864
+ action="checkpoint",
865
+ message="Git authentication required.",
866
+ checkpoint=request_auth_action()
867
+ )
868
+
869
+ case RemoteError():
870
+ return ErrorRecovery(
871
+ action="retry",
872
+ message="Remote operation failed. Will retry.",
873
+ retry_after=30
874
+ )
875
+ ```
876
+
877
+ ---
878
+
879
+ ## Future Enhancements
880
+
881
+ ### Planned Features
882
+
883
+ 1. **Worktree Support**
884
+ - Parallel development on multiple branches
885
+ - Each Grid session in isolated worktree
886
+
887
+ 2. **Stacked PRs**
888
+ - Create chains of dependent PRs
889
+ - Auto-update stack on changes
890
+
891
+ 3. **Bisect Integration**
892
+ - Automated git bisect for bug hunting
893
+ - Integration with Grid Debugger
894
+
895
+ 4. **Hooks System**
896
+ - Pre-commit hooks for Grid validation
897
+ - Post-commit hooks for notifications
898
+
899
+ 5. **Team Collaboration**
900
+ - Branch locking during Grid sessions
901
+ - Real-time collaboration awareness
902
+
903
+ ### Research Areas
904
+
905
+ 1. **Semantic Merge**
906
+ - Use AST analysis for smarter conflict resolution
907
+ - Language-aware merging
908
+
909
+ 2. **Predictive Conflicts**
910
+ - Warn about potential conflicts before they happen
911
+ - Suggest preemptive rebases
912
+
913
+ 3. **AI-Assisted Resolution**
914
+ - LLM-powered conflict resolution suggestions
915
+ - Context-aware merge decisions
916
+
917
+ ---
918
+
919
+ ## Appendix A: Command Reference
920
+
921
+ ### Git Operator Commands
922
+
923
+ | Command | Description |
924
+ |---------|-------------|
925
+ | `BRANCH CHECK` | Analyze current git state |
926
+ | `CREATE BRANCH {name}` | Create new feature branch |
927
+ | `COMMIT THREAD` | Create atomic commit for thread |
928
+ | `PUSH` | Push current branch to remote |
929
+ | `CREATE PR` | Create pull request |
930
+ | `RESOLVE CONFLICT` | Handle merge conflicts |
931
+
932
+ ### /grid:branch Commands
933
+
934
+ | Command | Description |
935
+ |---------|-------------|
936
+ | `/grid:branch` | Show branch status |
937
+ | `/grid:branch create {name}` | Create feature branch |
938
+ | `/grid:branch switch {name}` | Switch branches |
939
+ | `/grid:branch pr` | Create PR |
940
+ | `/grid:branch cleanup` | Delete merged branches |
941
+ | `/grid:branch list` | List Grid branches |
942
+ | `/grid:branch sync` | Sync with main |
943
+
944
+ ---
945
+
946
+ ## Appendix B: Commit Message Examples
947
+
948
+ ### Feature Commits
949
+
950
+ ```
951
+ feat(block-01): implement user authentication
952
+
953
+ - Add JWT token generation with RS256
954
+ - Create login and logout endpoints
955
+ - Implement refresh token rotation
956
+ - Add password hashing with bcrypt
957
+ ```
958
+
959
+ ### Fix Commits
960
+
961
+ ```
962
+ fix(api): handle null user response gracefully
963
+
964
+ - Add null check before accessing user properties
965
+ - Return 404 instead of 500 for missing users
966
+ - Add test case for null user scenario
967
+ ```
968
+
969
+ ### Refactor Commits
970
+
971
+ ```
972
+ refactor(utils): simplify date formatting utilities
973
+
974
+ - Extract common date patterns to constants
975
+ - Replace moment.js with native Intl.DateTimeFormat
976
+ - Remove unused timezone conversion functions
977
+ ```
978
+
979
+ ---
980
+
981
+ *End of Technical Design Document*