feat: Add configurable polecat branch naming (#825)
feat: Add configurable polecat branch naming
Adds polecat_branch_template configuration for custom branch naming patterns.
Template variables supported:
- {user}: git config user.name
- {year}/{month}: date (YY/MM format)
- {name}: polecat name
- {issue}: issue ID without prefix
- {description}: sanitized issue title
- {timestamp}: unique timestamp
Maintains backward compatibility - empty template uses existing format.
This commit is contained in:
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- **Configurable polecat branch naming** - Rigs can now customize polecat branch naming via `polecat_branch_template` configuration. Supports template variables: `{user}`, `{year}`, `{month}`, `{name}`, `{issue}`, `{description}`, `{timestamp}`. Defaults to existing behavior for backward compatibility.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- **Orphan cleanup skips valid tmux sessions** - `gt orphans kill` and automatic orphan cleanup now check for Claude processes belonging to valid Gas Town tmux sessions (gt-*/hq-*) before killing. This prevents false kills of witnesses, refineries, and deacon during startup when they may temporarily show TTY "?"
|
- **Orphan cleanup skips valid tmux sessions** - `gt orphans kill` and automatic orphan cleanup now check for Claude processes belonging to valid Gas Town tmux sessions (gt-*/hq-*) before killing. This prevents false kills of witnesses, refineries, and deacon during startup when they may temporarily show TTY "?"
|
||||||
|
|||||||
@@ -89,6 +89,58 @@ Debug routing: `BD_DEBUG_ROUTING=1 bd show <id>`
|
|||||||
|
|
||||||
Process state, PIDs, ephemeral data.
|
Process state, PIDs, ephemeral data.
|
||||||
|
|
||||||
|
### Rig-Level Configuration
|
||||||
|
|
||||||
|
Rigs support layered configuration through:
|
||||||
|
1. **Wisp layer** (`.beads-wisp/config/`) - transient, local overrides
|
||||||
|
2. **Rig identity bead labels** - persistent rig settings
|
||||||
|
3. **Town defaults** (`~/gt/settings/config.json`)
|
||||||
|
4. **System defaults** - compiled-in fallbacks
|
||||||
|
|
||||||
|
#### Polecat Branch Naming
|
||||||
|
|
||||||
|
Configure custom branch name templates for polecats:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Set via wisp (transient - for testing)
|
||||||
|
echo '{"polecat_branch_template": "adam/{year}/{month}/{description}"}' > \
|
||||||
|
~/gt/.beads-wisp/config/myrig.json
|
||||||
|
|
||||||
|
# Or set via rig identity bead labels (persistent)
|
||||||
|
bd update gt-rig-myrig --labels="polecat_branch_template:adam/{year}/{month}/{description}"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Template Variables:**
|
||||||
|
|
||||||
|
| Variable | Description | Example |
|
||||||
|
|----------|-------------|---------|
|
||||||
|
| `{user}` | From `git config user.name` | `adam` |
|
||||||
|
| `{year}` | Current year (YY format) | `26` |
|
||||||
|
| `{month}` | Current month (MM format) | `01` |
|
||||||
|
| `{name}` | Polecat name | `alpha` |
|
||||||
|
| `{issue}` | Issue ID without prefix | `123` (from `gt-123`) |
|
||||||
|
| `{description}` | Sanitized issue title | `fix-auth-bug` |
|
||||||
|
| `{timestamp}` | Unique timestamp | `1ks7f9a` |
|
||||||
|
|
||||||
|
**Default Behavior (backward compatible):**
|
||||||
|
|
||||||
|
When `polecat_branch_template` is empty or not set:
|
||||||
|
- With issue: `polecat/{name}/{issue}@{timestamp}`
|
||||||
|
- Without issue: `polecat/{name}-{timestamp}`
|
||||||
|
|
||||||
|
**Example Configurations:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# GitHub enterprise format
|
||||||
|
"adam/{year}/{month}/{description}"
|
||||||
|
|
||||||
|
# Simple feature branches
|
||||||
|
"feature/{issue}"
|
||||||
|
|
||||||
|
# Include polecat name for clarity
|
||||||
|
"work/{name}/{issue}"
|
||||||
|
```
|
||||||
|
|
||||||
## Formula Format
|
## Formula Format
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
|
|||||||
@@ -558,6 +558,17 @@ func (g *Git) Remotes() ([]string, error) {
|
|||||||
return strings.Split(out, "\n"), nil
|
return strings.Split(out, "\n"), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ConfigGet returns the value of a git config key.
|
||||||
|
// Returns empty string if the key is not set.
|
||||||
|
func (g *Git) ConfigGet(key string) (string, error) {
|
||||||
|
out, err := g.run("config", "--get", key)
|
||||||
|
if err != nil {
|
||||||
|
// git config --get returns exit code 1 if key not found
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Merge merges the given branch into the current branch.
|
// Merge merges the given branch into the current branch.
|
||||||
func (g *Git) Merge(branch string) error {
|
func (g *Git) Merge(branch string) error {
|
||||||
_, err := g.run("merge", branch)
|
_, err := g.run("merge", branch)
|
||||||
|
|||||||
@@ -227,6 +227,111 @@ type AddOptions struct {
|
|||||||
// Add creates a new polecat as a git worktree from the repo base.
|
// Add creates a new polecat as a git worktree from the repo base.
|
||||||
// Uses the shared bare repo (.repo.git) if available, otherwise mayor/rig.
|
// Uses the shared bare repo (.repo.git) if available, otherwise mayor/rig.
|
||||||
// This is much faster than a full clone and shares objects with all worktrees.
|
// This is much faster than a full clone and shares objects with all worktrees.
|
||||||
|
// buildBranchName creates a branch name using the configured template or default format.
|
||||||
|
// Supported template variables:
|
||||||
|
// - {user}: git config user.name
|
||||||
|
// - {year}: current year (YY format)
|
||||||
|
// - {month}: current month (MM format)
|
||||||
|
// - {name}: polecat name
|
||||||
|
// - {issue}: issue ID (without prefix)
|
||||||
|
// - {description}: sanitized issue title
|
||||||
|
// - {timestamp}: unique timestamp
|
||||||
|
//
|
||||||
|
// If no template is configured or template is empty, uses default format:
|
||||||
|
// - polecat/{name}/{issue}@{timestamp} when issue is available
|
||||||
|
// - polecat/{name}-{timestamp} otherwise
|
||||||
|
func (m *Manager) buildBranchName(name, issue string) string {
|
||||||
|
template := m.rig.GetStringConfig("polecat_branch_template")
|
||||||
|
|
||||||
|
// No template configured - use default behavior for backward compatibility
|
||||||
|
if template == "" {
|
||||||
|
timestamp := strconv.FormatInt(time.Now().UnixMilli(), 36)
|
||||||
|
if issue != "" {
|
||||||
|
return fmt.Sprintf("polecat/%s/%s@%s", name, issue, timestamp)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("polecat/%s-%s", name, timestamp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build template variables
|
||||||
|
vars := make(map[string]string)
|
||||||
|
|
||||||
|
// {user} - from git config user.name
|
||||||
|
if userName, err := m.git.ConfigGet("user.name"); err == nil && userName != "" {
|
||||||
|
vars["{user}"] = userName
|
||||||
|
} else {
|
||||||
|
vars["{user}"] = "unknown"
|
||||||
|
}
|
||||||
|
|
||||||
|
// {year} and {month}
|
||||||
|
now := time.Now()
|
||||||
|
vars["{year}"] = now.Format("06") // YY format
|
||||||
|
vars["{month}"] = now.Format("01") // MM format
|
||||||
|
|
||||||
|
// {name}
|
||||||
|
vars["{name}"] = name
|
||||||
|
|
||||||
|
// {timestamp}
|
||||||
|
vars["{timestamp}"] = strconv.FormatInt(now.UnixMilli(), 36)
|
||||||
|
|
||||||
|
// {issue} - issue ID without prefix
|
||||||
|
if issue != "" {
|
||||||
|
// Strip prefix (e.g., "gt-123" -> "123")
|
||||||
|
if idx := strings.Index(issue, "-"); idx >= 0 {
|
||||||
|
vars["{issue}"] = issue[idx+1:]
|
||||||
|
} else {
|
||||||
|
vars["{issue}"] = issue
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
vars["{issue}"] = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// {description} - try to get from beads if issue is set
|
||||||
|
if issue != "" {
|
||||||
|
if issueData, err := m.beads.Show(issue); err == nil && issueData.Title != "" {
|
||||||
|
// Sanitize title for branch name: lowercase, replace spaces/special chars with hyphens
|
||||||
|
desc := strings.ToLower(issueData.Title)
|
||||||
|
desc = strings.Map(func(r rune) rune {
|
||||||
|
if (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') {
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
return '-'
|
||||||
|
}, desc)
|
||||||
|
// Remove consecutive hyphens and trim
|
||||||
|
desc = strings.Trim(desc, "-")
|
||||||
|
for strings.Contains(desc, "--") {
|
||||||
|
desc = strings.ReplaceAll(desc, "--", "-")
|
||||||
|
}
|
||||||
|
// Limit length to keep branch names reasonable
|
||||||
|
if len(desc) > 40 {
|
||||||
|
desc = desc[:40]
|
||||||
|
}
|
||||||
|
vars["{description}"] = desc
|
||||||
|
} else {
|
||||||
|
vars["{description}"] = ""
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
vars["{description}"] = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace all variables in template
|
||||||
|
result := template
|
||||||
|
for key, value := range vars {
|
||||||
|
result = strings.ReplaceAll(result, key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up any remaining empty segments (e.g., "adam///" -> "adam")
|
||||||
|
parts := strings.Split(result, "/")
|
||||||
|
cleanParts := make([]string, 0, len(parts))
|
||||||
|
for _, part := range parts {
|
||||||
|
if part != "" {
|
||||||
|
cleanParts = append(cleanParts, part)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result = strings.Join(cleanParts, "/")
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
// Polecat state is derived from beads assignee field, not state.json.
|
// Polecat state is derived from beads assignee field, not state.json.
|
||||||
//
|
//
|
||||||
// Branch naming: Each polecat run gets a unique branch (polecat/<name>-<timestamp>).
|
// Branch naming: Each polecat run gets a unique branch (polecat/<name>-<timestamp>).
|
||||||
@@ -249,18 +354,8 @@ func (m *Manager) AddWithOptions(name string, opts AddOptions) (*Polecat, error)
|
|||||||
polecatDir := m.polecatDir(name)
|
polecatDir := m.polecatDir(name)
|
||||||
clonePath := filepath.Join(polecatDir, m.rig.Name)
|
clonePath := filepath.Join(polecatDir, m.rig.Name)
|
||||||
|
|
||||||
// Branch naming: include issue ID when available for better traceability.
|
// Build branch name using configured template or default format
|
||||||
// Format: polecat/<worker>/<issue>@<timestamp> when HookBead is set
|
branchName := m.buildBranchName(name, opts.HookBead)
|
||||||
// The @timestamp suffix ensures uniqueness if the same issue is re-slung.
|
|
||||||
// parseBranchName strips the @suffix to extract the issue ID.
|
|
||||||
timestamp := strconv.FormatInt(time.Now().UnixMilli(), 36)
|
|
||||||
var branchName string
|
|
||||||
if opts.HookBead != "" {
|
|
||||||
branchName = fmt.Sprintf("polecat/%s/%s@%s", name, opts.HookBead, timestamp)
|
|
||||||
} else {
|
|
||||||
// Fallback to timestamp format when no issue is known at spawn time
|
|
||||||
branchName = fmt.Sprintf("polecat/%s-%s", name, timestamp)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create polecat directory (polecats/<name>/)
|
// Create polecat directory (polecats/<name>/)
|
||||||
if err := os.MkdirAll(polecatDir, 0755); err != nil {
|
if err := os.MkdirAll(polecatDir, 0755); err != nil {
|
||||||
@@ -606,14 +701,7 @@ func (m *Manager) RepairWorktreeWithOptions(name string, force bool, opts AddOpt
|
|||||||
// Create fresh worktree with unique branch name, starting from origin's default branch
|
// Create fresh worktree with unique branch name, starting from origin's default branch
|
||||||
// Old branches are left behind - they're ephemeral (never pushed to origin)
|
// Old branches are left behind - they're ephemeral (never pushed to origin)
|
||||||
// and will be cleaned up by garbage collection
|
// and will be cleaned up by garbage collection
|
||||||
// Branch naming: include issue ID when available for better traceability.
|
branchName := m.buildBranchName(name, opts.HookBead)
|
||||||
timestamp := strconv.FormatInt(time.Now().UnixMilli(), 36)
|
|
||||||
var branchName string
|
|
||||||
if opts.HookBead != "" {
|
|
||||||
branchName = fmt.Sprintf("polecat/%s/%s@%s", name, opts.HookBead, timestamp)
|
|
||||||
} else {
|
|
||||||
branchName = fmt.Sprintf("polecat/%s-%s", name, timestamp)
|
|
||||||
}
|
|
||||||
if err := repoGit.WorktreeAddFromRef(newClonePath, branchName, startPoint); err != nil {
|
if err := repoGit.WorktreeAddFromRef(newClonePath, branchName, startPoint); err != nil {
|
||||||
return nil, fmt.Errorf("creating fresh worktree from %s: %w", startPoint, err)
|
return nil, fmt.Errorf("creating fresh worktree from %s: %w", startPoint, err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -654,3 +654,107 @@ func TestReconcilePoolWith_OrphanDoesNotBlockAllocation(t *testing.T) {
|
|||||||
t.Errorf("expected furiosa (orphan freed), got %q", name)
|
t.Errorf("expected furiosa (orphan freed), got %q", name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBuildBranchName(t *testing.T) {
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
|
||||||
|
// Initialize a git repo for config access
|
||||||
|
gitCmd := exec.Command("git", "init")
|
||||||
|
gitCmd.Dir = tmpDir
|
||||||
|
if err := gitCmd.Run(); err != nil {
|
||||||
|
t.Fatalf("git init: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set git user.name for testing
|
||||||
|
configCmd := exec.Command("git", "config", "user.name", "testuser")
|
||||||
|
configCmd.Dir = tmpDir
|
||||||
|
if err := configCmd.Run(); err != nil {
|
||||||
|
t.Fatalf("git config: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
template string
|
||||||
|
issue string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "default_with_issue",
|
||||||
|
template: "", // Empty template = default behavior
|
||||||
|
issue: "gt-123",
|
||||||
|
want: "polecat/alpha/gt-123@", // timestamp suffix varies
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "default_without_issue",
|
||||||
|
template: "",
|
||||||
|
issue: "",
|
||||||
|
want: "polecat/alpha-", // timestamp suffix varies
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "custom_template_user_year_month",
|
||||||
|
template: "{user}/{year}/{month}/fix",
|
||||||
|
issue: "",
|
||||||
|
want: "testuser/", // year/month will vary
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "custom_template_with_name",
|
||||||
|
template: "feature/{name}",
|
||||||
|
issue: "",
|
||||||
|
want: "feature/alpha",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "custom_template_with_issue",
|
||||||
|
template: "work/{issue}",
|
||||||
|
issue: "gt-456",
|
||||||
|
want: "work/456",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "custom_template_with_timestamp",
|
||||||
|
template: "feature/{name}-{timestamp}",
|
||||||
|
issue: "",
|
||||||
|
want: "feature/alpha-", // timestamp suffix varies
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
// Create rig with test template
|
||||||
|
r := &rig.Rig{
|
||||||
|
Name: "test-rig",
|
||||||
|
Path: tmpDir,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Override system defaults for this test if template is set
|
||||||
|
if tt.template != "" {
|
||||||
|
origDefault := rig.SystemDefaults["polecat_branch_template"]
|
||||||
|
rig.SystemDefaults["polecat_branch_template"] = tt.template
|
||||||
|
defer func() {
|
||||||
|
rig.SystemDefaults["polecat_branch_template"] = origDefault
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
g := git.NewGit(tmpDir)
|
||||||
|
m := NewManager(r, g, nil)
|
||||||
|
|
||||||
|
got := m.buildBranchName("alpha", tt.issue)
|
||||||
|
|
||||||
|
// For default templates, just check prefix since timestamp varies
|
||||||
|
if tt.template == "" {
|
||||||
|
if !strings.HasPrefix(got, tt.want) {
|
||||||
|
t.Errorf("buildBranchName() = %q, want prefix %q", got, tt.want)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// For custom templates with time-varying fields, check prefix
|
||||||
|
if strings.Contains(tt.template, "{year}") || strings.Contains(tt.template, "{month}") || strings.Contains(tt.template, "{timestamp}") {
|
||||||
|
if !strings.HasPrefix(got, tt.want) {
|
||||||
|
t.Errorf("buildBranchName() = %q, want prefix %q", got, tt.want)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if got != tt.want {
|
||||||
|
t.Errorf("buildBranchName() = %q, want %q", got, tt.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ var SystemDefaults = map[string]interface{}{
|
|||||||
"max_polecats": 10,
|
"max_polecats": 10,
|
||||||
"priority_adjustment": 0,
|
"priority_adjustment": 0,
|
||||||
"dnd": false,
|
"dnd": false,
|
||||||
|
"polecat_branch_template": "", // Empty = use default behavior (polecat/{name}/...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// StackingKeys defines which keys use stacking semantics (values add up).
|
// StackingKeys defines which keys use stacking semantics (values add up).
|
||||||
|
|||||||
@@ -23,22 +23,35 @@ just `gt done`.
|
|||||||
### The Self-Cleaning Model
|
### The Self-Cleaning Model
|
||||||
|
|
||||||
Polecats are **self-cleaning**. When you run `gt done`:
|
Polecats are **self-cleaning**. When you run `gt done`:
|
||||||
1. Your branch is pushed to origin
|
1. Syncs beads
|
||||||
2. An MR is created in the Refinery merge queue
|
2. Nukes your sandbox
|
||||||
3. Your sandbox gets nuked
|
3. Exits your session
|
||||||
4. Your session exits
|
4. **You cease to exist**
|
||||||
5. **You cease to exist**
|
|
||||||
|
|
||||||
The **Refinery** handles all merging to main. It serializes concurrent work, rebases,
|
There is no "idle" state. There is no "waiting for more work". Done means GONE.
|
||||||
and resolves conflicts. You NEVER push directly to main - that's the Refinery's job.
|
|
||||||
|
**Two workflow types:**
|
||||||
|
|
||||||
|
**1. Merge Queue Workflow (gastown, beads repos):**
|
||||||
|
- Push branch to origin
|
||||||
|
- `gt done` submits to Refinery merge queue
|
||||||
|
- Refinery handles merge to main
|
||||||
|
|
||||||
|
**2. PR Workflow (longeye and similar repos):**
|
||||||
|
- Create GitHub PR
|
||||||
|
- Monitor for CI and review feedback
|
||||||
|
- Address any issues
|
||||||
|
- `gt done` when PR is approved and CI green
|
||||||
|
- Maintainer or Refinery merges the PR
|
||||||
|
|
||||||
|
**In both cases, you still run `gt done` at the end.** The difference is WHEN:
|
||||||
|
- Merge queue: After implementation and tests pass
|
||||||
|
- PR workflow: After PR is approved and CI is green
|
||||||
|
|
||||||
**Polecats do NOT:**
|
**Polecats do NOT:**
|
||||||
- Push directly to main (`git push origin main` - WRONG)
|
- Push directly to main (`git push origin main` - WRONG)
|
||||||
- Create pull requests (you're not an external contributor)
|
- Merge their own PRs (maintainer or Refinery does this)
|
||||||
- Wait around to see if their work merges
|
- Wait around after running `gt done`
|
||||||
|
|
||||||
There is no "idle" state. There is no "waiting for more work". Done means GONE.
|
|
||||||
The Refinery will handle the merge. You will not be there to see it.
|
|
||||||
|
|
||||||
**If you have finished your implementation work, your ONLY next action is:**
|
**If you have finished your implementation work, your ONLY next action is:**
|
||||||
```bash
|
```bash
|
||||||
@@ -292,6 +305,49 @@ bd ready # See next step
|
|||||||
|
|
||||||
When all steps are done, the molecule gets squashed automatically when you run `gt done`.
|
When all steps are done, the molecule gets squashed automatically when you run `gt done`.
|
||||||
|
|
||||||
|
## PR Workflow (for repos that use pull requests)
|
||||||
|
|
||||||
|
**Applies to:** longeye and other repos that require code review via GitHub PRs
|
||||||
|
|
||||||
|
The **mol-polecat-work** molecule now includes PR creation and monitoring steps for
|
||||||
|
repos that use PR-based workflows. Here's what's different:
|
||||||
|
|
||||||
|
### Old Workflow (Merge Queue)
|
||||||
|
1. Implement → Test → `gt done` → Refinery merges
|
||||||
|
|
||||||
|
### New Workflow (PR-based)
|
||||||
|
1. Implement → Test
|
||||||
|
2. **Create PR** (with branch naming: adam/YY/M/description)
|
||||||
|
3. **Monitor PR** - Check CI and review status
|
||||||
|
4. **Address feedback** - Fix issues if CI fails or reviewers comment
|
||||||
|
5. **Verify ready** - Confirm CI green and PR approved
|
||||||
|
6. **`gt done`** - Mark complete and exit (maintainer will merge)
|
||||||
|
|
||||||
|
### Key Differences
|
||||||
|
|
||||||
|
**You keep the bead OPEN** during PR review. Don't mark complete until:
|
||||||
|
- ✓ All CI checks passing
|
||||||
|
- ✓ Review comments addressed
|
||||||
|
- ✓ PR approved by reviewer
|
||||||
|
|
||||||
|
**You may cycle multiple times** while waiting for review or fixing feedback:
|
||||||
|
```bash
|
||||||
|
# If context filling while waiting for review
|
||||||
|
gt handoff -s "Waiting for PR review" -m "Issue: <issue>
|
||||||
|
PR: #<number>
|
||||||
|
Status: <current state>
|
||||||
|
Next: Continue monitoring"
|
||||||
|
```
|
||||||
|
|
||||||
|
Your next session resumes from the current molecule step.
|
||||||
|
|
||||||
|
**Branch naming is critical:**
|
||||||
|
- Format: `adam/YY/M/<short-description>`
|
||||||
|
- Example: `adam/26/1/fix-social-media-parser`
|
||||||
|
- This matches the user convention for the repo
|
||||||
|
|
||||||
|
**You do NOT merge the PR** - a maintainer or Refinery does that.
|
||||||
|
|
||||||
## Before Signaling Done (MANDATORY)
|
## Before Signaling Done (MANDATORY)
|
||||||
|
|
||||||
> ⚠️ **CRITICAL**: Work is NOT complete until you run `gt done`
|
> ⚠️ **CRITICAL**: Work is NOT complete until you run `gt done`
|
||||||
|
|||||||
Reference in New Issue
Block a user