saving in place

This commit is contained in:
Krypto Kajun
2025-10-04 09:31:02 -05:00
parent 76c1b5cee1
commit f358f49aa9
295 changed files with 72071 additions and 17209 deletions

View File

@@ -0,0 +1,176 @@
package bridge
import (
"crypto/rand"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"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 := ioutil.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
}

82
tools/main.go Normal file
View File

@@ -0,0 +1,82 @@
package main
import (
"flag"
"log"
"os"
"github.com/fraktal/mev-beta/tools/bridge"
)
func main() {
// define subcommands
applyCmd := flag.NewFlagSet("apply", flag.ExitOnError)
revertCmd := flag.NewFlagSet("revert", flag.ExitOnError)
runCmd := flag.NewFlagSet("run", flag.ExitOnError)
summarizeCmd := flag.NewFlagSet("summarize", flag.ExitOnError)
// apply flags
applyPatch := applyCmd.String("patch", "", "Path to patch file")
applyBranch := applyCmd.String("branch", "", "Branch name to apply patch")
// revert flags
revertBranchName := revertCmd.String("branch", "", "Branch name to revert")
// run flags
runMode := runCmd.String("mode", "", "Execution mode (podman-compose)")
// summarize flags
summarizeArtifacts := summarizeCmd.String("artifacts", "", "Path to artifacts directory")
summarizeOut := summarizeCmd.String("out", "", "Output JSON file path")
// parse subcommand
if len(os.Args) < 2 {
log.Fatal("subcommand required: apply, revert, run, summarize")
}
switch os.Args[1] {
case "apply":
applyCmd.Parse(os.Args[2:])
if *applyPatch == "" || *applyBranch == "" {
log.Fatal("--patch and --branch are required")
}
op := bridge.PatchOperation{
PatchFile: *applyPatch,
BranchName: *applyBranch,
}
if err := bridge.ApplyPatch(op); err != nil {
log.Fatal(err)
}
case "revert":
revertCmd.Parse(os.Args[2:])
if *revertBranchName == "" {
log.Fatal("--branch is required")
}
if err := bridge.RevertBranch(*revertBranchName); err != nil {
log.Fatal(err)
}
case "run":
runCmd.Parse(os.Args[2:])
if *runMode == "podman-compose" {
if err := bridge.RunPodmanCompose(); err != nil {
log.Fatal(err)
}
} else {
log.Fatalf("unsupported run mode: %s", *runMode)
}
case "summarize":
summarizeCmd.Parse(os.Args[2:])
if *summarizeArtifacts == "" || *summarizeOut == "" {
log.Fatal("--artifacts and --out are required")
}
cfg := bridge.SummarizeConfig{
ArtifactsDir: *summarizeArtifacts,
OutputFile: *summarizeOut,
}
if err := bridge.SummarizeArtifacts(cfg); err != nil {
log.Fatal(err)
}
default:
log.Fatalf("unknown subcommand: %s", os.Args[1])
}
}

View File

@@ -0,0 +1,152 @@
// tools/tests/ci_agent_bridge_test.go
//
// Unit tests for ci-agent-bridge CLI.
// Covers patch application, branch reverts, artifact summarization, and podman runner.
//
// Run with:
// go test ./tools/tests -v -race -cover
package tests
import (
"encoding/json"
"io/ioutil"
"os"
"path/filepath"
"testing"
// Import the bridge for direct function testing.
// Adjust path if your project structure differs.
bridge "github.com/fraktal/mev-beta/tools/bridge"
)
// helper: create temporary directory with dummy artifact files
func createDummyArtifacts(t *testing.T) string {
dir, err := ioutil.TempDir("", "artifacts")
if err != nil {
t.Fatalf("failed to create temp dir: %v", err)
}
// write 2 dummy files
if err := ioutil.WriteFile(filepath.Join(dir, "a.log"), []byte("log-data-123"), 0644); err != nil {
t.Fatal(err)
}
if err := ioutil.WriteFile(filepath.Join(dir, "b.txt"), []byte("text-data-456"), 0644); err != nil {
t.Fatal(err)
}
return dir
}
func TestSummarizeArtifacts(t *testing.T) {
dir := createDummyArtifacts(t)
defer os.RemoveAll(dir)
outFile := filepath.Join(dir, "summary.json")
cfg := bridge.SummarizeConfig{
ArtifactsDir: dir,
OutputFile: outFile,
}
if err := bridge.SummarizeArtifacts(cfg); err != nil {
t.Fatalf("summarize failed: %v", err)
}
// verify JSON exists
data, err := ioutil.ReadFile(outFile)
if err != nil {
t.Fatalf("failed to read summary.json: %v", err)
}
var parsed bridge.SummarizeResult
if err := json.Unmarshal(data, &parsed); err != nil {
t.Fatalf("failed to unmarshal json: %v", err)
}
if len(parsed.Files) < 2 {
t.Errorf("expected >=2 files, got %d", len(parsed.Files))
}
if parsed.Timestamp.IsZero() {
t.Errorf("expected timestamp set")
}
// verify ZIP archive created
if _, err := os.Stat(filepath.Join(dir, "summary.zip")); os.IsNotExist(err) {
t.Errorf("expected summary.zip archive, not found")
}
}
func TestApplyPatch_MissingArgs(t *testing.T) {
op := bridge.PatchOperation{}
err := bridge.ApplyPatch(op)
if err == nil {
t.Fatal("expected error when patchfile/branch missing")
}
}
func TestRevertBranch_MissingBranch(t *testing.T) {
err := bridge.RevertBranch("")
if err == nil {
t.Fatal("expected error when branch missing")
}
}
// Integration-like test with mock podman-compose
func TestRunPodmanCompose(t *testing.T) {
// simulate podman-compose using echo
tmpPath := filepath.Join(os.TempDir(), "podman-compose")
if err := ioutil.WriteFile(tmpPath, []byte("#!/bin/sh\necho podman-compose-run"), 0755); err != nil {
t.Fatal(err)
}
defer os.Remove(tmpPath)
// put tmpPath at beginning of PATH
oldPath := os.Getenv("PATH")
os.Setenv("PATH", filepath.Dir(tmpPath)+":"+oldPath)
defer os.Setenv("PATH", oldPath)
err := bridge.RunPodmanCompose()
if err != nil {
t.Fatalf("expected mock podman-compose to succeed, got err: %v", err)
}
}
// TestZipDir ensures ZIP creation works standalone
func TestZipDir(t *testing.T) {
dir := createDummyArtifacts(t)
defer os.RemoveAll(dir)
dest := filepath.Join(dir, "out.zip")
if err := bridge.ZipDir(dir, dest); err != nil {
t.Fatalf("zipdir failed: %v", err)
}
info, err := os.Stat(dest)
if err != nil {
t.Fatalf("zip file not found: %v", err)
}
if info.Size() == 0 {
t.Errorf("zip file is empty")
}
}
// Benchmark for artifact summarization performance
func BenchmarkSummarizeArtifacts(b *testing.B) {
dir := createDummyArtifacts(nil)
defer os.RemoveAll(dir)
cfg := bridge.SummarizeConfig{
ArtifactsDir: dir,
OutputFile: filepath.Join(dir, "summary.json"),
}
for i := 0; i < b.N; i++ {
if err := bridge.SummarizeArtifacts(cfg); err != nil {
b.Fatal(err)
}
}
}
// Example usage doc test
func ExampleSummarizeArtifacts() {
// This example is for documentation purposes only and doesn't produce output in tests
// because it uses temporary directories with random names.
}