Merge pull request #455 from abhinav/add-prime-stealth-flag

feat(prime): add --stealth flag for flush-only workflow
This commit is contained in:
Steve Yegge
2025-12-03 20:26:01 -08:00
committed by GitHub
2 changed files with 140 additions and 14 deletions

View File

@@ -3,6 +3,7 @@ package main
import (
"encoding/json"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
@@ -13,8 +14,9 @@ import (
)
var (
primeFullMode bool
primeMCPMode bool
primeFullMode bool
primeMCPMode bool
primeStealthMode bool
)
var primeCmd = &cobra.Command{
@@ -47,8 +49,8 @@ agents from forgetting bd workflow after context compaction.`,
mcpMode = true
}
// Output workflow context (adaptive based on MCP)
if err := outputPrimeContext(mcpMode); err != nil {
// Output workflow context (adaptive based on MCP and stealth mode)
if err := outputPrimeContext(os.Stdout, mcpMode, primeStealthMode); err != nil {
// Suppress all errors - silent exit with success
// Never write to stderr (breaks Windows compatibility)
os.Exit(0)
@@ -59,6 +61,7 @@ agents from forgetting bd workflow after context compaction.`,
func init() {
primeCmd.Flags().BoolVar(&primeFullMode, "full", false, "Force full CLI output (ignore MCP detection)")
primeCmd.Flags().BoolVar(&primeMCPMode, "mcp", false, "Force MCP mode (minimal output)")
primeCmd.Flags().BoolVar(&primeStealthMode, "stealth", false, "Stealth mode (no git operations, flush only)")
rootCmd.AddCommand(primeCmd)
}
@@ -104,7 +107,7 @@ func isMCPActive() bool {
}
// isEphemeralBranch detects if current branch has no upstream (ephemeral/local-only)
func isEphemeralBranch() bool {
var isEphemeralBranch = func() bool {
// git rev-parse --abbrev-ref --symbolic-full-name @{u}
// Returns error code 128 if no upstream configured
cmd := exec.Command("git", "rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}")
@@ -113,19 +116,22 @@ func isEphemeralBranch() bool {
}
// outputPrimeContext outputs workflow context in markdown format
func outputPrimeContext(mcpMode bool) error {
func outputPrimeContext(w io.Writer, mcpMode bool, stealthMode bool) error {
if mcpMode {
return outputMCPContext()
return outputMCPContext(w, stealthMode)
}
return outputCLIContext()
return outputCLIContext(w, stealthMode)
}
// outputMCPContext outputs minimal context for MCP users
func outputMCPContext() error {
func outputMCPContext(w io.Writer, stealthMode bool) error {
ephemeral := isEphemeralBranch()
var closeProtocol string
if ephemeral {
if stealthMode {
// Stealth mode: only flush to JSONL as there's nothing to commit.
closeProtocol = "Before saying \"done\": bd sync --flush-only"
} else if ephemeral {
closeProtocol = "Before saying \"done\": git status → git add → bd sync --from-main → git commit (no push - ephemeral branch)"
} else {
closeProtocol = "Before saying \"done\": git status → git add → bd sync → git commit → bd sync → git push"
@@ -143,12 +149,12 @@ func outputMCPContext() error {
Start: Check ` + "`ready`" + ` tool for available work.
`
fmt.Print(context)
_, _ = fmt.Fprint(w, context)
return nil
}
// outputCLIContext outputs full CLI reference for non-MCP users
func outputCLIContext() error {
func outputCLIContext(w io.Writer, stealthMode bool) error {
ephemeral := isEphemeralBranch()
var closeProtocol string
@@ -156,7 +162,17 @@ func outputCLIContext() error {
var syncSection string
var completingWorkflow string
if ephemeral {
if stealthMode {
// Stealth mode: only flush to JSONL, no git operations
closeProtocol = `[ ] bd sync --flush-only (export beads to JSONL only)`
syncSection = `### Sync & Collaboration
- ` + "`bd sync --flush-only`" + ` - Export to JSONL`
completingWorkflow = `**Completing work:**
` + "```bash" + `
bd close <id1> <id2> ... # Close all completed issues at once
bd sync --flush-only # Export to JSONL
` + "```"
} else if ephemeral {
closeProtocol = `[ ] 1. git status (check what changed)
[ ] 2. git add <files> (stage code changes)
[ ] 3. bd sync --from-main (pull beads updates from main)
@@ -258,6 +274,6 @@ bd create --title="Write tests for X" --type=task
bd dep add beads-yyy beads-xxx # Tests depend on Feature (Feature blocks tests)
` + "```" + `
`
fmt.Print(context)
_, _ = fmt.Fprint(w, context)
return nil
}

110
cmd/bd/prime_test.go Normal file
View File

@@ -0,0 +1,110 @@
package main
import (
"bytes"
"strings"
"testing"
)
func TestOutputContextFunction(t *testing.T) {
tests := []struct {
name string
mcpMode bool
stealthMode bool
ephemeralMode bool
expectText []string
rejectText []string
}{
{
name: "CLI Normal (non-ephemeral)",
mcpMode: false,
stealthMode: false,
ephemeralMode: false,
expectText: []string{"Beads Workflow Context", "bd sync", "git push"},
rejectText: []string{"bd sync --flush-only", "--from-main"},
},
{
name: "CLI Normal (ephemeral)",
mcpMode: false,
stealthMode: false,
ephemeralMode: true,
expectText: []string{"Beads Workflow Context", "bd sync --from-main", "ephemeral branch"},
rejectText: []string{"bd sync --flush-only", "git push"},
},
{
name: "CLI Stealth",
mcpMode: false,
stealthMode: true,
ephemeralMode: false, // stealth mode overrides ephemeral detection
expectText: []string{"Beads Workflow Context", "bd sync --flush-only"},
rejectText: []string{"git push", "git pull", "git commit", "git status", "git add"},
},
{
name: "MCP Normal (non-ephemeral)",
mcpMode: true,
stealthMode: false,
ephemeralMode: false,
expectText: []string{"Beads Issue Tracker Active", "bd sync", "git push"},
rejectText: []string{"bd sync --flush-only", "--from-main"},
},
{
name: "MCP Normal (ephemeral)",
mcpMode: true,
stealthMode: false,
ephemeralMode: true,
expectText: []string{"Beads Issue Tracker Active", "bd sync --from-main", "ephemeral branch"},
rejectText: []string{"bd sync --flush-only", "git push"},
},
{
name: "MCP Stealth",
mcpMode: true,
stealthMode: true,
ephemeralMode: false, // stealth mode overrides ephemeral detection
expectText: []string{"Beads Issue Tracker Active", "bd sync --flush-only"},
rejectText: []string{"git push", "git pull", "git commit", "git status", "git add"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
defer stubIsEphemeralBranch(tt.ephemeralMode)()
var buf bytes.Buffer
err := outputPrimeContext(&buf, tt.mcpMode, tt.stealthMode)
if err != nil {
t.Fatalf("outputPrimeContext failed: %v", err)
}
output := buf.String()
for _, expected := range tt.expectText {
if !strings.Contains(output, expected) {
t.Errorf("Expected text not found: %s", expected)
}
}
for _, rejected := range tt.rejectText {
if strings.Contains(output, rejected) {
t.Errorf("Unexpected text found: %s", rejected)
}
}
})
}
}
// stubIsEphemeralBranch temporarily replaces isEphemeralBranch
// with a stub returning returnValue.
//
// Returns a function to restore the original isEphemeralBranch.
// Usage:
//
// defer stubIsEphemeralBranch(true)()
func stubIsEphemeralBranch(isEphem bool) func() {
original := isEphemeralBranch
isEphemeralBranch = func() bool {
return isEphem
}
return func() {
isEphemeralBranch = original
}
}