package bridge import ( "crypto/rand" "encoding/hex" "encoding/json" "fmt" "io" "log" "os" "os/exec" "path/filepath" "strings" "time" "archive/zip" ) // SummarizeResult holds artifact summary data type SummarizeResult struct { Files []string `json:"files"` Timestamp time.Time `json:"timestamp"` } // SummarizeConfig configures summarization behavior type SummarizeConfig struct { ArtifactsDir string OutputFile string } // ApplyPatch applies a patch file to a new branch func ApplyPatch(op PatchOperation) error { if op.PatchFile == "" || op.BranchName == "" { return fmt.Errorf("both --patch and --branch are required") } // create new branch cmd := exec.Command("git", "checkout", "-b", op.BranchName) if out, err := cmd.CombinedOutput(); err != nil { return fmt.Errorf("git checkout failed: %s: %w", string(out), err) } // apply patch cmd = exec.Command("git", "apply", op.PatchFile) if out, err := cmd.CombinedOutput(); err != nil { return fmt.Errorf("git apply failed: %s: %w", string(out), err) } log.Printf("[CI-Agent-Bridge] Patch applied to branch: %s", op.BranchName) return nil } // RevertBranch deletes a branch (hard reset) func RevertBranch(branch string) error { if branch == "" { return fmt.Errorf("--branch is required") } // switch to main first cmd := exec.Command("git", "checkout", "main") if out, err := cmd.CombinedOutput(); err != nil { return fmt.Errorf("git checkout main failed: %s: %w", string(out), err) } // delete branch cmd = exec.Command("git", "branch", "-D", branch) if out, err := cmd.CombinedOutput(); err != nil { return fmt.Errorf("git branch delete failed: %s: %w", string(out), err) } log.Printf("[CI-Agent-Bridge] Branch reverted: %s", branch) return nil } // RunPodmanCompose executes podman-compose up func RunPodmanCompose() error { log.Println("[CI-Agent-Bridge] Starting podman-compose...") cmd := exec.Command("podman-compose", "up", "-d") cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr return cmd.Run() } // ZipDir compresses a directory into a ZIP archive func ZipDir(srcDir, destZip string) error { zipFile, err := os.Create(destZip) if err != nil { return err } defer zipFile.Close() archive := zip.NewWriter(zipFile) defer archive.Close() return filepath.Walk(srcDir, func(path string, info os.FileInfo, err error) error { if err != nil { return err } // skip directories if info.IsDir() { return nil } // create file in archive relPath, _ := filepath.Rel(srcDir, path) zipEntry, err := archive.Create(relPath) if err != nil { return err } // copy file data file, err := os.Open(path) if err != nil { return err } defer file.Close() _, err = io.Copy(zipEntry, file) return err }) } // SummarizeArtifacts creates a JSON summary and ZIP of artifacts func SummarizeArtifacts(cfg SummarizeConfig) error { var files []string err := filepath.Walk(cfg.ArtifactsDir, func(path string, info os.FileInfo, err error) error { if err != nil { return err } if !info.IsDir() { relPath, _ := filepath.Rel(cfg.ArtifactsDir, path) files = append(files, relPath) } return nil }) if err != nil { return fmt.Errorf("failed to walk artifacts dir: %w", err) } result := SummarizeResult{ Files: files, Timestamp: time.Now(), } // write JSON summary data, _ := json.MarshalIndent(result, "", " ") if err := os.WriteFile(cfg.OutputFile, data, 0644); err != nil { return fmt.Errorf("failed to write summary: %w", err) } // create ZIP of artifacts zipPath := strings.TrimSuffix(cfg.OutputFile, filepath.Ext(cfg.OutputFile)) + ".zip" if err := ZipDir(cfg.ArtifactsDir, zipPath); err != nil { return fmt.Errorf("failed to create zip: %w", err) } log.Printf("[CI-Agent-Bridge] Artifacts summarized to %s (%d files)", cfg.OutputFile, len(files)) return nil } // PatchOperation holds patch application parameters type PatchOperation struct { PatchFile string BranchName string } // generateSecureBranchName creates a cryptographically secure random branch name func generateSecureBranchName() (string, error) { bytes := make([]byte, 16) if _, err := rand.Read(bytes); err != nil { return "", err } return "ai/" + hex.EncodeToString(bytes), nil }