package claudebox import ( "bytes" "context" "encoding/json" "fmt" "log/slog" "os/exec" "strings" ) // SDLCRunner executes SDLC CLI commands locally. type SDLCRunner struct { workDir string logger *slog.Logger } // SDLCRunnerConfig holds configuration for the SDLC runner. type SDLCRunnerConfig struct { // WorkDir is the default working directory. WorkDir string // Logger is an optional logger for debug output. Logger *slog.Logger } // NewSDLCRunner creates a new SDLC runner. func NewSDLCRunner(cfg SDLCRunnerConfig) *SDLCRunner { logger := cfg.Logger if logger == nil { logger = slog.Default() } return &SDLCRunner{ workDir: cfg.WorkDir, logger: logger, } } // SDLCResult contains the result of an SDLC command. type SDLCResult struct { Success bool Output string Data json.RawMessage // Parsed JSON from sdlc --json output Error error } // Run executes an SDLC CLI command. func (s *SDLCRunner) Run(ctx context.Context, workDir, command string, args []string) *SDLCResult { result := &SDLCResult{} // Ensure .sdlc/ is initialized if err := s.ensureInit(ctx, workDir); err != nil { // Log but continue - command might still work s.logger.Debug("sdlc init failed, continuing with command", "error", err, "work_dir", workDir) } // Build the command sdlcArgs := []string{command} sdlcArgs = append(sdlcArgs, args...) sdlcArgs = append(sdlcArgs, "--json") cmd := exec.CommandContext(ctx, "sdlc", sdlcArgs...) cmd.Dir = workDir var stdout, stderr bytes.Buffer cmd.Stdout = &stdout cmd.Stderr = &stderr if err := cmd.Run(); err != nil { result.Error = fmt.Errorf("%s: %s", err, stderr.String()) result.Output = stdout.String() return result } result.Success = true result.Output = stdout.String() // Try to parse JSON output output := strings.TrimSpace(stdout.String()) if output != "" && (output[0] == '{' || output[0] == '[') { result.Data = json.RawMessage(output) } return result } // ensureInit checks if .sdlc/ exists and runs `sdlc init` if it doesn't. func (s *SDLCRunner) ensureInit(ctx context.Context, workDir string) error { // Check if .sdlc/ directory exists cmd := exec.CommandContext(ctx, "test", "-d", workDir+"/.sdlc") if cmd.Run() == nil { return nil // Already initialized } // Run sdlc init initCmd := exec.CommandContext(ctx, "sdlc", "init", "--json") initCmd.Dir = workDir return initCmd.Run() }