From 21a2c63c06c2bf339367e9c1e6d0c50679f75e34 Mon Sep 17 00:00:00 2001 From: Mariola04 Date: Thu, 8 Jan 2026 11:15:43 +0000 Subject: [PATCH 01/33] redid keys logic, now the user can specify the model he wants to use --- .github/workflows/workflow-scanner.yml | 2 +- cmd/scanner/main.go | 109 ++++++++++++++----------- pkg/agent/agent.go | 57 ++++++++++--- pkg/agent/llm_processor.go | 12 ++- 4 files changed, 118 insertions(+), 62 deletions(-) diff --git a/.github/workflows/workflow-scanner.yml b/.github/workflows/workflow-scanner.yml index b6a33fd2..f8bd66cb 100644 --- a/.github/workflows/workflow-scanner.yml +++ b/.github/workflows/workflow-scanner.yml @@ -28,5 +28,5 @@ jobs: with: api-token: ${{ secrets.FS_API_TOKEN }} github-token: ${{ secrets.GH_PAT }} - llm-api-key: ${{ secrets.GEMINI_API_KEY }} + gemini-api-key: ${{ secrets.GEMINI_API_KEY }} target-branch: test-locally \ No newline at end of file diff --git a/cmd/scanner/main.go b/cmd/scanner/main.go index 9932896c..f7ae26d6 100644 --- a/cmd/scanner/main.go +++ b/cmd/scanner/main.go @@ -22,14 +22,17 @@ import ( ) type batchConfig struct { - repository string - provider string - githubToken string - gitlabToken string - llmAPIKey string - commitSHA string - sourceBase64 string - useGitClone bool + repository string + provider string + githubToken string + gitlabToken string + openaiKey string + anthropicKey string + geminiKey string + model string + commitSHA string + sourceBase64 string + useGitClone bool } var dummyVal = 5 @@ -42,7 +45,6 @@ func main() { config.repository, config.commitSHA, config.useGitClone) validateConfig(config) - setupLLMEnvironment(config.llmAPIKey) validateDaggerEnvironment() ctx := context.Background() @@ -71,11 +73,46 @@ func loadConfig() batchConfig { provider := strings.ToLower(os.Getenv("PROVIDER")) // optional override githubToken := os.Getenv("GITHUB_TOKEN") gitlabToken := os.Getenv("GITLAB_TOKEN") - llmAPIKey := os.Getenv("LLM_API_KEY") + openaiKey := os.Getenv("OPENAI_API_KEY") + anthropicKey := os.Getenv("ANTHROPIC_API_KEY") + geminiKey := os.Getenv("GEMINI_API_KEY") + model := os.Getenv("MODEL") commitSHA := os.Getenv("COMMIT_SHA") sourceBase64 := os.Getenv("SOURCE_BASE64") - useGitClone := sourceBase64 == "" && llmAPIKey != "" + // Check if any LLM key is available + hasLLMKey := openaiKey != "" || anthropicKey != "" || geminiKey != "" + useGitClone := sourceBase64 == "" && hasLLMKey + + // Validate API key formats to catch user errors early + if openaiKey != "" && !strings.HasPrefix(openaiKey, "sk-") { + log.Fatal("OPENAI_API_KEY appears to be invalid format (should start with 'sk-')") + } + if anthropicKey != "" && !strings.HasPrefix(anthropicKey, "sk-ant-") { + log.Fatal("ANTHROPIC_API_KEY appears to be invalid format (should start with 'sk-ant-')") + } + if geminiKey != "" { + if strings.HasPrefix(geminiKey, "sk-") { + if strings.HasPrefix(geminiKey, "sk-ant-") { + log.Fatal("GEMINI_API_KEY appears to be an Anthropic key (starts with 'sk-ant-'), please use anthropic-api-key input instead") + } + log.Fatal("GEMINI_API_KEY appears to be an OpenAI key (starts with 'sk-'), please use openai-api-key input instead") + } + if !strings.HasPrefix(geminiKey, "AIza") { + log.Fatal("GEMINI_API_KEY appears to be invalid format (should start with 'AIza')") + } + } + + // Set default model based on available API key if not specified + if model == "" { + if openaiKey != "" { + model = "gpt-4o" + } else if anthropicKey != "" { + model = "claude-3-5-sonnet" + } else if geminiKey != "" { + model = "gemini-2.0-flash" + } + } if provider == "" { if strings.Contains(repository, "gitlab.com") { @@ -86,14 +123,17 @@ func loadConfig() batchConfig { } return batchConfig{ - repository: repository, - provider: provider, - githubToken: githubToken, - gitlabToken: gitlabToken, - llmAPIKey: llmAPIKey, - commitSHA: commitSHA, - sourceBase64: sourceBase64, - useGitClone: useGitClone, + repository: repository, + provider: provider, + githubToken: githubToken, + gitlabToken: gitlabToken, + openaiKey: openaiKey, + anthropicKey: anthropicKey, + geminiKey: geminiKey, + model: model, + commitSHA: commitSHA, + sourceBase64: sourceBase64, + useGitClone: useGitClone, } } @@ -116,8 +156,8 @@ func validateConfig(config batchConfig) { log.Fatal("Missing SOURCE_BASE64 for legacy mode") } - if config.useGitClone && config.llmAPIKey == "" { - log.Fatal("Missing LLM_API_KEY for git clone mode") + if config.useGitClone && config.openaiKey == "" && config.anthropicKey == "" && config.geminiKey == "" { + log.Fatal("Missing API key for git clone mode (need OPENAI_API_KEY, ANTHROPIC_API_KEY, or GEMINI_API_KEY)") } } @@ -239,33 +279,6 @@ func decodeSourceData(dag *dagger.Client, sourceBase64 string) *dagger.Directory return dag.Directory().WithNewFile("workflows.tar.gz", string(sourceData)) } -func setupLLMEnvironment(llmAPIKey string) { - // Detect provider based on key format and set only the appropriate env var - var providerKey string - var providerName string - - if strings.HasPrefix(llmAPIKey, "sk-") { - providerKey = "OPENAI_API_KEY" - providerName = "OpenAI" - } else if strings.HasPrefix(llmAPIKey, "sk-ant-") { - providerKey = "ANTHROPIC_API_KEY" - providerName = "Anthropic" - } else if strings.HasPrefix(llmAPIKey, "AIza") { - providerKey = "GEMINI_API_KEY" - providerName = "Gemini" - } else { - // Default to OpenAI if format is unknown - providerKey = "OPENAI_API_KEY" - providerName = "OpenAI (default)" - log.Printf("Warning: Unknown API key format, defaulting to OpenAI") - } - - if err := os.Setenv(providerKey, llmAPIKey); err != nil { - log.Printf("Warning: Failed to set %s: %v", providerKey, err) - } else { - log.Printf("Set LLM environment for %s", providerName) - } -} func incrementUsage(repository string, success bool) error { serviceURL := os.Getenv("SERVICE_URL") diff --git a/pkg/agent/agent.go b/pkg/agent/agent.go index 03fd21ec..27018563 100644 --- a/pkg/agent/agent.go +++ b/pkg/agent/agent.go @@ -10,6 +10,7 @@ import ( "log" "os" "path/filepath" + "strings" internalDagger "workflow-scanner/internal/dagger" "workflow-scanner/pkg/dagger" ) @@ -120,33 +121,65 @@ func (agent *AgentImpl) findProjectRoot() (string, error) { } func (agent *AgentImpl) getLLMAPIKey() (string, error) { - llmAPIKey := os.Getenv("LLM_API_KEY") - if llmAPIKey == "" { - openaiKey := os.Getenv("OPENAI_API_KEY") - if openaiKey == "" { - return "", fmt.Errorf("LLM_API_KEY or OPENAI_API_KEY not found in environment") + // Check for specific API keys and validate their formats + if openaiKey := os.Getenv("OPENAI_API_KEY"); openaiKey != "" { + if !strings.HasPrefix(openaiKey, "sk-") { + return "", fmt.Errorf("OPENAI_API_KEY appears to be invalid format (should start with 'sk-')") } - llmAPIKey = openaiKey + return openaiKey, nil + } + if anthropicKey := os.Getenv("ANTHROPIC_API_KEY"); anthropicKey != "" { + if !strings.HasPrefix(anthropicKey, "sk-ant-") { + return "", fmt.Errorf("ANTHROPIC_API_KEY appears to be invalid format (should start with 'sk-ant-')") + } + return anthropicKey, nil + } + if geminiKey := os.Getenv("GEMINI_API_KEY"); geminiKey != "" { + if strings.HasPrefix(geminiKey, "sk-") { + if strings.HasPrefix(geminiKey, "sk-ant-") { + return "", fmt.Errorf("GEMINI_API_KEY appears to be an Anthropic key (starts with 'sk-ant-'), please use anthropic-api-key input instead") + } + return "", fmt.Errorf("GEMINI_API_KEY appears to be an OpenAI key (starts with 'sk-'), please use openai-api-key input instead") + } + if !strings.HasPrefix(geminiKey, "AIza") { + return "", fmt.Errorf("GEMINI_API_KEY appears to be invalid format (should start with 'AIza')") + } + return geminiKey, nil } - return llmAPIKey, nil + return "", fmt.Errorf("no API key found (need OPENAI_API_KEY, ANTHROPIC_API_KEY, or GEMINI_API_KEY)") } func (agent *AgentImpl) createLLMContainer(sourceWithPrompt *internalDagger.Directory, llmAPIKey, issues string) dagger.Container { - log.Printf("DEBUG: Using custom container approach with OpenAI API key") - log.Printf("DEBUG: Creating container with OpenAI API key (length: %d)", len(llmAPIKey)) + log.Printf("DEBUG: Using custom container approach with LLM API key") + log.Printf("DEBUG: Creating container with API key (length: %d)", len(llmAPIKey)) log.Printf("DEBUG: ZIZMOR issues length: %d", len(issues)) llmProcessorContent := GetLLMProcessorCode() - return agent.client.Container(). + container := agent.client.Container(). From("golang:1.25-alpine"). WithExec([]string{"apk", "add", "--no-cache", "git"}). - WithEnvVariable("OPENAI_API_KEY", llmAPIKey). WithEnvVariable("ZIZMOR_ISSUES", issues). WithDirectory("/workspace", sourceWithPrompt). WithWorkdir("/workspace"). - WithExec([]string{"sh", "-c", "echo 'DEBUG: Workspace contents:' && ls -la"}). + WithExec([]string{"sh", "-c", "echo 'DEBUG: Workspace contents:' && ls -la"}) + + // Set all API keys from environment + if openaiKey := os.Getenv("OPENAI_API_KEY"); openaiKey != "" { + container = container.WithEnvVariable("OPENAI_API_KEY", openaiKey) + } + if anthropicKey := os.Getenv("ANTHROPIC_API_KEY"); anthropicKey != "" { + container = container.WithEnvVariable("ANTHROPIC_API_KEY", anthropicKey) + } + if geminiKey := os.Getenv("GEMINI_API_KEY"); geminiKey != "" { + container = container.WithEnvVariable("GEMINI_API_KEY", geminiKey) + } + if model := os.Getenv("MODEL"); model != "" { + container = container.WithEnvVariable("MODEL", model) + } + + return container. WithExec([]string{"rm", "-f", "go.mod", "go.sum"}). WithExec([]string{"sh", "-c", "echo 'DEBUG: Initializing Go module' && go mod init llm-processor"}). WithExec([]string{"sh", "-c", "echo 'DEBUG: Getting OpenAI Go client' && go get github.com/sashabaranov/go-openai"}). diff --git a/pkg/agent/llm_processor.go b/pkg/agent/llm_processor.go index a6822c5c..7035e1c0 100644 --- a/pkg/agent/llm_processor.go +++ b/pkg/agent/llm_processor.go @@ -31,6 +31,9 @@ type LLMResponse struct { func main() { log.Println("DEBUG: Starting LLM processor") log.Printf("DEBUG: OPENAI_API_KEY length: %d", len(os.Getenv("OPENAI_API_KEY"))) + log.Printf("DEBUG: ANTHROPIC_API_KEY length: %d", len(os.Getenv("ANTHROPIC_API_KEY"))) + log.Printf("DEBUG: GEMINI_API_KEY length: %d", len(os.Getenv("GEMINI_API_KEY"))) + log.Printf("DEBUG: MODEL: %s", os.Getenv("MODEL")) log.Printf("DEBUG: ZIZMOR_ISSUES length: %d", len(os.Getenv("ZIZMOR_ISSUES"))) if err := processWorkflows(); err != nil { @@ -117,8 +120,15 @@ func callOpenAI(ctx context.Context, client *openai.Client, enhancedPrompt strin maxTokens = 4000 lowTemperature = 0.1 ) + + // Get model from environment or default to gpt-4o + model := os.Getenv("MODEL") + if model == "" { + model = "gpt-4o" + } + req := openai.ChatCompletionRequest{ - Model: openai.GPT4oMini, + Model: model, Messages: []openai.ChatCompletionMessage{ { Role: openai.ChatMessageRoleUser, From 673f22852dbec9cf823d0f141b796e091dcb50c1 Mon Sep 17 00:00:00 2001 From: Mariola04 Date: Thu, 8 Jan 2026 11:23:02 +0000 Subject: [PATCH 02/33] lint errors --- cmd/scanner/main.go | 103 ++++++++++++++++++++++++++------------------ pkg/agent/agent.go | 3 ++ 2 files changed, 63 insertions(+), 43 deletions(-) diff --git a/cmd/scanner/main.go b/cmd/scanner/main.go index f7ae26d6..f5ac912f 100644 --- a/cmd/scanner/main.go +++ b/cmd/scanner/main.go @@ -22,17 +22,17 @@ import ( ) type batchConfig struct { - repository string - provider string - githubToken string - gitlabToken string - openaiKey string - anthropicKey string - geminiKey string - model string - commitSHA string - sourceBase64 string - useGitClone bool + repository string + provider string + githubToken string + gitlabToken string + openaiKey string + anthropicKey string + geminiKey string + model string + commitSHA string + sourceBase64 string + useGitClone bool } var dummyVal = 5 @@ -83,8 +83,39 @@ func loadConfig() batchConfig { // Check if any LLM key is available hasLLMKey := openaiKey != "" || anthropicKey != "" || geminiKey != "" useGitClone := sourceBase64 == "" && hasLLMKey - + // Validate API key formats to catch user errors early + validateAPIKeyFormats(openaiKey, anthropicKey, geminiKey) + + // Set default model based on available API key if not specified + if model == "" { + model = getDefaultModel(openaiKey, anthropicKey, geminiKey) + } + + if provider == "" { + if strings.Contains(repository, "gitlab.com") { + provider = "gitlab" + } else { + provider = "github" + } + } + + return batchConfig{ + repository: repository, + provider: provider, + githubToken: githubToken, + gitlabToken: gitlabToken, + openaiKey: openaiKey, + anthropicKey: anthropicKey, + geminiKey: geminiKey, + model: model, + commitSHA: commitSHA, + sourceBase64: sourceBase64, + useGitClone: useGitClone, + } +} + +func validateAPIKeyFormats(openaiKey, anthropicKey, geminiKey string) { if openaiKey != "" && !strings.HasPrefix(openaiKey, "sk-") { log.Fatal("OPENAI_API_KEY appears to be invalid format (should start with 'sk-')") } @@ -102,39 +133,19 @@ func loadConfig() batchConfig { log.Fatal("GEMINI_API_KEY appears to be invalid format (should start with 'AIza')") } } - - // Set default model based on available API key if not specified - if model == "" { - if openaiKey != "" { - model = "gpt-4o" - } else if anthropicKey != "" { - model = "claude-3-5-sonnet" - } else if geminiKey != "" { - model = "gemini-2.0-flash" - } - } +} - if provider == "" { - if strings.Contains(repository, "gitlab.com") { - provider = "gitlab" - } else { - provider = "github" - } +func getDefaultModel(openaiKey, anthropicKey, geminiKey string) string { + if openaiKey != "" { + return "gpt-4o" } - - return batchConfig{ - repository: repository, - provider: provider, - githubToken: githubToken, - gitlabToken: gitlabToken, - openaiKey: openaiKey, - anthropicKey: anthropicKey, - geminiKey: geminiKey, - model: model, - commitSHA: commitSHA, - sourceBase64: sourceBase64, - useGitClone: useGitClone, + if anthropicKey != "" { + return "claude-3-5-sonnet" + } + if geminiKey != "" { + return "gemini-2.0-flash" } + return "" } func validateConfig(config batchConfig) { @@ -142,6 +153,11 @@ func validateConfig(config batchConfig) { log.Fatal("Missing required environment variable: REPOSITORY") } + validateProviderTokens(config) + validateModeRequirements(config) +} + +func validateProviderTokens(config batchConfig) { if config.provider == "gitlab" { if config.gitlabToken == "" { log.Fatal("Missing GITLAB_TOKEN for gitlab provider") @@ -151,7 +167,9 @@ func validateConfig(config batchConfig) { log.Fatal("Missing GITHUB_TOKEN for github provider") } } +} +func validateModeRequirements(config batchConfig) { if !config.useGitClone && config.sourceBase64 == "" { log.Fatal("Missing SOURCE_BASE64 for legacy mode") } @@ -279,7 +297,6 @@ func decodeSourceData(dag *dagger.Client, sourceBase64 string) *dagger.Directory return dag.Directory().WithNewFile("workflows.tar.gz", string(sourceData)) } - func incrementUsage(repository string, success bool) error { serviceURL := os.Getenv("SERVICE_URL") if serviceURL == "" { diff --git a/pkg/agent/agent.go b/pkg/agent/agent.go index 27018563..8f97fbdf 100644 --- a/pkg/agent/agent.go +++ b/pkg/agent/agent.go @@ -126,12 +126,14 @@ func (agent *AgentImpl) getLLMAPIKey() (string, error) { if !strings.HasPrefix(openaiKey, "sk-") { return "", fmt.Errorf("OPENAI_API_KEY appears to be invalid format (should start with 'sk-')") } + return openaiKey, nil } if anthropicKey := os.Getenv("ANTHROPIC_API_KEY"); anthropicKey != "" { if !strings.HasPrefix(anthropicKey, "sk-ant-") { return "", fmt.Errorf("ANTHROPIC_API_KEY appears to be invalid format (should start with 'sk-ant-')") } + return anthropicKey, nil } if geminiKey := os.Getenv("GEMINI_API_KEY"); geminiKey != "" { @@ -144,6 +146,7 @@ func (agent *AgentImpl) getLLMAPIKey() (string, error) { if !strings.HasPrefix(geminiKey, "AIza") { return "", fmt.Errorf("GEMINI_API_KEY appears to be invalid format (should start with 'AIza')") } + return geminiKey, nil } From 79de253a215bed22c7ebef179805154c09b82431 Mon Sep 17 00:00:00 2001 From: Mariola04 Date: Thu, 8 Jan 2026 11:27:40 +0000 Subject: [PATCH 03/33] . --- .github/workflows/workflow-scanner.yml | 2 +- cmd/scanner/main.go | 1 + pkg/agent/agent.go | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/workflow-scanner.yml b/.github/workflows/workflow-scanner.yml index f8bd66cb..4d218a20 100644 --- a/.github/workflows/workflow-scanner.yml +++ b/.github/workflows/workflow-scanner.yml @@ -28,5 +28,5 @@ jobs: with: api-token: ${{ secrets.FS_API_TOKEN }} github-token: ${{ secrets.GH_PAT }} - gemini-api-key: ${{ secrets.GEMINI_API_KEY }} + openai-api-key: ${{ secrets.OPENAI_API_KEY }} target-branch: test-locally \ No newline at end of file diff --git a/cmd/scanner/main.go b/cmd/scanner/main.go index f5ac912f..5430dcdd 100644 --- a/cmd/scanner/main.go +++ b/cmd/scanner/main.go @@ -145,6 +145,7 @@ func getDefaultModel(openaiKey, anthropicKey, geminiKey string) string { if geminiKey != "" { return "gemini-2.0-flash" } + return "" } diff --git a/pkg/agent/agent.go b/pkg/agent/agent.go index 8f97fbdf..37d4a35c 100644 --- a/pkg/agent/agent.go +++ b/pkg/agent/agent.go @@ -141,6 +141,7 @@ func (agent *AgentImpl) getLLMAPIKey() (string, error) { if strings.HasPrefix(geminiKey, "sk-ant-") { return "", fmt.Errorf("GEMINI_API_KEY appears to be an Anthropic key (starts with 'sk-ant-'), please use anthropic-api-key input instead") } + return "", fmt.Errorf("GEMINI_API_KEY appears to be an OpenAI key (starts with 'sk-'), please use openai-api-key input instead") } if !strings.HasPrefix(geminiKey, "AIza") { From e80b98f857836bb9cd0855aec98d79e88b7afa54 Mon Sep 17 00:00:00 2001 From: Mariola04 Date: Thu, 8 Jan 2026 11:33:19 +0000 Subject: [PATCH 04/33] fotgot to change targer branch --- .github/workflows/workflow-scanner.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/workflow-scanner.yml b/.github/workflows/workflow-scanner.yml index 4d218a20..10761c27 100644 --- a/.github/workflows/workflow-scanner.yml +++ b/.github/workflows/workflow-scanner.yml @@ -29,4 +29,4 @@ jobs: api-token: ${{ secrets.FS_API_TOKEN }} github-token: ${{ secrets.GH_PAT }} openai-api-key: ${{ secrets.OPENAI_API_KEY }} - target-branch: test-locally \ No newline at end of file + target-branch: keys-changes \ No newline at end of file From b9c889e4d36081da29c53b32df9b55418c8f3858 Mon Sep 17 00:00:00 2001 From: Mariola04 Date: Thu, 8 Jan 2026 11:39:12 +0000 Subject: [PATCH 05/33] test --- .github/workflows/workflow-scanner.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/workflow-scanner.yml b/.github/workflows/workflow-scanner.yml index 10761c27..dc0a83ad 100644 --- a/.github/workflows/workflow-scanner.yml +++ b/.github/workflows/workflow-scanner.yml @@ -24,7 +24,7 @@ jobs: - name: Run workflow scanner id: scanner - uses: Scalabit/workflow-scanner-action@other-repos + uses: Scalabit/workflow-scanner-action@master with: api-token: ${{ secrets.FS_API_TOKEN }} github-token: ${{ secrets.GH_PAT }} From 99c430bc6e84bc07159de528eda30bf71cc89ae7 Mon Sep 17 00:00:00 2001 From: Mariola04 Date: Thu, 8 Jan 2026 12:11:18 +0000 Subject: [PATCH 06/33] test with gemini to check key variance --- .github/workflows/workflow-scanner.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/workflow-scanner.yml b/.github/workflows/workflow-scanner.yml index dc0a83ad..226faa7c 100644 --- a/.github/workflows/workflow-scanner.yml +++ b/.github/workflows/workflow-scanner.yml @@ -24,9 +24,9 @@ jobs: - name: Run workflow scanner id: scanner - uses: Scalabit/workflow-scanner-action@master + uses: Scalabit/workflow-scanner-action@other-repos with: api-token: ${{ secrets.FS_API_TOKEN }} github-token: ${{ secrets.GH_PAT }} - openai-api-key: ${{ secrets.OPENAI_API_KEY }} + gemini-api-key: ${{ secrets.GEMINI_API_KEY }} target-branch: keys-changes \ No newline at end of file From 06ed433ed43b9499af6713b98363296dc5c654e5 Mon Sep 17 00:00:00 2001 From: Mariola04 Date: Thu, 8 Jan 2026 12:43:13 +0000 Subject: [PATCH 07/33] test gemini --- pkg/agent/llm_processor.go | 200 ++++++++++++++++++++++++++++++++++--- 1 file changed, 188 insertions(+), 12 deletions(-) diff --git a/pkg/agent/llm_processor.go b/pkg/agent/llm_processor.go index 7035e1c0..3450cb6b 100644 --- a/pkg/agent/llm_processor.go +++ b/pkg/agent/llm_processor.go @@ -5,13 +5,16 @@ func GetLLMProcessorCode() string { return `package main import ( + "bytes" "context" "encoding/json" "fmt" "io/ioutil" "log" + "net/http" "os" "path/filepath" + "regexp" "strings" "time" @@ -48,12 +51,6 @@ func processWorkflows() error { return err } - client, ctx, cancel, err := createOpenAIClient() - if err != nil { - return err - } - defer cancel() - workflowFiles, err := findWorkflowFiles() if err != nil { return fmt.Errorf("failed to find workflow files: %w", err) @@ -62,12 +59,29 @@ func processWorkflows() error { enhancedPrompt := buildEnhancedPrompt(promptContent, issues, workflowFiles) - resp, err := callOpenAI(ctx, client, enhancedPrompt) - if err != nil { - return err - } + // Determine which provider to use based on available API keys + if os.Getenv("OPENAI_API_KEY") != "" { + log.Println("DEBUG: Using OpenAI provider") + client, ctx, cancel, err := createOpenAIClient() + if err != nil { + return err + } + defer cancel() - return processOpenAIResponse(resp) + resp, err := callOpenAI(ctx, client, enhancedPrompt) + if err != nil { + return err + } + return processOpenAIResponse(resp) + } else if os.Getenv("GEMINI_API_KEY") != "" { + log.Println("DEBUG: Using Gemini provider") + return callGemini(enhancedPrompt) + } else if os.Getenv("ANTHROPIC_API_KEY") != "" { + log.Println("DEBUG: Using Anthropic provider") + return callAnthropic(enhancedPrompt) + } else { + return fmt.Errorf("no API key found (need OPENAI_API_KEY, ANTHROPIC_API_KEY, or GEMINI_API_KEY)") + } } func loadInputData() ([]byte, string, error) { @@ -233,5 +247,167 @@ func applyFileChange(change FileChange) error { log.Printf("Applied fix to %s", change.Path) return nil -}` +} + +func callGemini(enhancedPrompt string) error { + apiKey := os.Getenv("GEMINI_API_KEY") + model := os.Getenv("MODEL") + if model == "" { + model = "gemini-2.0-flash" + } + + log.Printf("DEBUG: Calling Gemini API with model: %s", model) + + // Gemini API request structure + requestBody := map[string]interface{}{ + "contents": []map[string]interface{}{ + { + "parts": []map[string]interface{}{ + { + "text": enhancedPrompt, + }, + }, + }, + }, + "generationConfig": map[string]interface{}{ + "temperature": 0.1, + "maxOutputTokens": 4000, + }, + } + + jsonData, err := json.Marshal(requestBody) + if err != nil { + return fmt.Errorf("failed to marshal request: %w", err) + } + + url := fmt.Sprintf("https://generativelanguage.googleapis.com/v1beta/models/%s:generateContent?key=%s", model, apiKey) + resp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonData)) + if err != nil { + return fmt.Errorf("failed to call Gemini API: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != 200 { + return fmt.Errorf("Gemini API returned status %d", resp.StatusCode) + } + + var response map[string]interface{} + if err := json.NewDecoder(resp.Body).Decode(&response); err != nil { + return fmt.Errorf("failed to decode Gemini response: %w", err) + } + + // Extract text from Gemini response + candidates, ok := response["candidates"].([]interface{}) + if !ok || len(candidates) == 0 { + return fmt.Errorf("no candidates in Gemini response") + } + + candidate := candidates[0].(map[string]interface{}) + content := candidate["content"].(map[string]interface{}) + parts := content["parts"].([]interface{}) + if len(parts) == 0 { + return fmt.Errorf("no parts in Gemini response") + } + + part := parts[0].(map[string]interface{}) + text := part["text"].(string) + + log.Printf("DEBUG: Gemini response received, length: %d", len(text)) + + // Parse and process the response using the same logic as OpenAI + var llmResponse LLMResponse + if err := parseJSONResponse(text, &llmResponse); err != nil { + return fmt.Errorf("failed to parse JSON from Gemini response: %w", err) + } + + return processGenericResponse(&llmResponse) +} + +func callAnthropic(enhancedPrompt string) error { + apiKey := os.Getenv("ANTHROPIC_API_KEY") + model := os.Getenv("MODEL") + if model == "" { + model = "claude-3-5-sonnet-20241022" + } + + log.Printf("DEBUG: Calling Anthropic API with model: %s", model) + + // Anthropic API request structure + requestBody := map[string]interface{}{ + "model": model, + "max_tokens": 4000, + "temperature": 0.1, + "messages": []map[string]interface{}{ + { + "role": "user", + "content": enhancedPrompt, + }, + }, + } + + jsonData, err := json.Marshal(requestBody) + if err != nil { + return fmt.Errorf("failed to marshal request: %w", err) + } + + req, err := http.NewRequest("POST", "https://api.anthropic.com/v1/messages", bytes.NewBuffer(jsonData)) + if err != nil { + return fmt.Errorf("failed to create request: %w", err) + } + + req.Header.Set("Content-Type", "application/json") + req.Header.Set("x-api-key", apiKey) + req.Header.Set("anthropic-version", "2023-06-01") + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return fmt.Errorf("failed to call Anthropic API: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != 200 { + return fmt.Errorf("Anthropic API returned status %d", resp.StatusCode) + } + + var response map[string]interface{} + if err := json.NewDecoder(resp.Body).Decode(&response); err != nil { + return fmt.Errorf("failed to decode Anthropic response: %w", err) + } + + // Extract text from Anthropic response + content, ok := response["content"].([]interface{}) + if !ok || len(content) == 0 { + return fmt.Errorf("no content in Anthropic response") + } + + contentItem := content[0].(map[string]interface{}) + text := contentItem["text"].(string) + + log.Printf("DEBUG: Anthropic response received, length: %d", len(text)) + + // Parse and process the response using the same logic as OpenAI + var llmResponse LLMResponse + if err := parseJSONResponse(text, &llmResponse); err != nil { + return fmt.Errorf("failed to parse JSON from Anthropic response: %w", err) + } + + return processGenericResponse(&llmResponse) +} + +func processGenericResponse(llmResponse *LLMResponse) error { + log.Printf("DEBUG: Applying %d file changes", len(llmResponse.FileChanges)) + for i, change := range llmResponse.FileChanges { + log.Printf("DEBUG: Applying change %d/%d to %s", i+1, len(llmResponse.FileChanges), change.Path) + if err := applyFileChange(change); err != nil { + log.Printf("Warning: Failed to apply change to %s: %v", change.Path, err) + } + } + + log.Printf("DEBUG: Returning explanation: %d chars", len(llmResponse.Explanation)) + fmt.Print(llmResponse.Explanation) + + return nil +} +` } From bbc705a6c1fbcb9fb4feb82fee8bab9d6f1d55a0 Mon Sep 17 00:00:00 2001 From: Mariola04 Date: Thu, 8 Jan 2026 13:55:06 +0000 Subject: [PATCH 08/33] test gemini again --- pkg/agent/llm_processor.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/agent/llm_processor.go b/pkg/agent/llm_processor.go index 3450cb6b..6d30c772 100644 --- a/pkg/agent/llm_processor.go +++ b/pkg/agent/llm_processor.go @@ -14,7 +14,6 @@ import ( "net/http" "os" "path/filepath" - "regexp" "strings" "time" From fa305fd6c9b12e9597f51f767a0e02b507637a37 Mon Sep 17 00:00:00 2001 From: Mariola04 Date: Thu, 8 Jan 2026 14:14:54 +0000 Subject: [PATCH 09/33] test gemini new key --- cmd/scanner/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/scanner/main.go b/cmd/scanner/main.go index 5430dcdd..3bfb12ab 100644 --- a/cmd/scanner/main.go +++ b/cmd/scanner/main.go @@ -364,7 +364,7 @@ func runScan(ctx context.Context, dag *dagger.Client, config batchConfig, source wrapperClient = github.NewWrapperIssueClientImpl(dag, config.githubToken) } - // Default to "main" if no target branch is specified in environment + // Default to "main" if no target branch is specified in env targetBranch := os.Getenv("TARGET_BRANCH") if targetBranch == "" { targetBranch = "main" From 4ab99dd17700a844c2c4d94614d43344f7550b7a Mon Sep 17 00:00:00 2001 From: JocaSantos-dev Date: Thu, 8 Jan 2026 14:29:49 +0000 Subject: [PATCH 10/33] trig --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 14965425..e5d094fd 100644 --- a/README.md +++ b/README.md @@ -123,4 +123,4 @@ This project is licensed under the terms included in the LICENSE file. ## Next steps - See if Docker image + entrypoint script, instead of composite, can be better. - Don't make this repo public until we remove the LLM KEY and PAT from secrets. -- See what are the possibilities of using GITHUB_TOKEN instead PAT_TOKEN. \ No newline at end of file +- See what are the possibilities of using GITHUB_TOKEN instead PAT_TOKEN. From 32a1c62901235a143541b44d45538122544d0d8e Mon Sep 17 00:00:00 2001 From: Mariola04 Date: Thu, 8 Jan 2026 15:02:13 +0000 Subject: [PATCH 11/33] test --- cmd/scanner/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/scanner/main.go b/cmd/scanner/main.go index 3bfb12ab..5430dcdd 100644 --- a/cmd/scanner/main.go +++ b/cmd/scanner/main.go @@ -364,7 +364,7 @@ func runScan(ctx context.Context, dag *dagger.Client, config batchConfig, source wrapperClient = github.NewWrapperIssueClientImpl(dag, config.githubToken) } - // Default to "main" if no target branch is specified in env + // Default to "main" if no target branch is specified in environment targetBranch := os.Getenv("TARGET_BRANCH") if targetBranch == "" { targetBranch = "main" From 35a0620767b60b669003d71b8ff5d5e286e8ea50 Mon Sep 17 00:00:00 2001 From: Mariola04 Date: Thu, 8 Jan 2026 15:08:04 +0000 Subject: [PATCH 12/33] test --- .github/workflows/workflow-scanner.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/workflow-scanner.yml b/.github/workflows/workflow-scanner.yml index 226faa7c..10761c27 100644 --- a/.github/workflows/workflow-scanner.yml +++ b/.github/workflows/workflow-scanner.yml @@ -28,5 +28,5 @@ jobs: with: api-token: ${{ secrets.FS_API_TOKEN }} github-token: ${{ secrets.GH_PAT }} - gemini-api-key: ${{ secrets.GEMINI_API_KEY }} + openai-api-key: ${{ secrets.OPENAI_API_KEY }} target-branch: keys-changes \ No newline at end of file From b31cd4931d9c921c146edeb0f06fc2439b8e2066 Mon Sep 17 00:00:00 2001 From: JocaSantos-dev Date: Thu, 8 Jan 2026 16:27:12 +0000 Subject: [PATCH 13/33] test --- .github/workflows/workflow-scanner.yml | 2 +- pkg/github/result.go | 72 +++++++++++++++++++++++--- pkg/github/results_test.go | 10 ++-- 3 files changed, 70 insertions(+), 14 deletions(-) diff --git a/.github/workflows/workflow-scanner.yml b/.github/workflows/workflow-scanner.yml index 10761c27..08dc63c4 100644 --- a/.github/workflows/workflow-scanner.yml +++ b/.github/workflows/workflow-scanner.yml @@ -29,4 +29,4 @@ jobs: api-token: ${{ secrets.FS_API_TOKEN }} github-token: ${{ secrets.GH_PAT }} openai-api-key: ${{ secrets.OPENAI_API_KEY }} - target-branch: keys-changes \ No newline at end of file + target-branch: improve-pr \ No newline at end of file diff --git a/pkg/github/result.go b/pkg/github/result.go index d41f85c7..b0123f66 100644 --- a/pkg/github/result.go +++ b/pkg/github/result.go @@ -1,6 +1,7 @@ package github import ( + "encoding/json" "fmt" "regexp" "strings" @@ -18,9 +19,6 @@ const ( | --- | ---: | %s -### LLM Summary -%s - --- **Validation:** %s %s @@ -95,6 +93,68 @@ func fixSummaryToTableRows(summary string) string { return strings.Join(rows, "") } +func formatRemainingIssues(finalValidation string) string { + if finalValidation == "" || finalValidation == "[]" || finalValidation == "[]\n" { + return "" + } + + var findings []zizmor.Finding + if err := json.Unmarshal([]byte(finalValidation), &findings); err != nil { + return fmt.Sprintf("**Manual review needed - some issues remain:**\n```json\n%s\n```", finalValidation) + } + + var result strings.Builder + result.WriteString("**Manual review needed - some issues remain:**\n\n") + + fileIssues := groupFindingsByFile(findings) + + for filePath, issues := range fileIssues { + result.WriteString(fmt.Sprintf("### 📄 `%s`\n", filePath)) + + for _, issue := range issues { + formatIssueDetails(&result, issue) + } + } + + return result.String() +} + +func groupFindingsByFile(findings []zizmor.Finding) map[string][]zizmor.Finding { + fileIssues := make(map[string][]zizmor.Finding) + for _, finding := range findings { + for _, loc := range finding.Locations { + if loc.Symbolic.Key.Local != nil { + filePath := loc.Symbolic.Key.Local.GivenPath + fileIssues[filePath] = append(fileIssues[filePath], finding) + + break + } + } + } + + return fileIssues +} + +func formatIssueDetails(result *strings.Builder, issue zizmor.Finding) { + result.WriteString(fmt.Sprintf("**Issue:** %s\n", issue.Desc)) + result.WriteString(fmt.Sprintf("**Severity:** %s\n", issue.Determinations.Severity)) + + for _, loc := range issue.Locations { + if loc.Concrete.Location.StartPoint.Row > 0 { + result.WriteString(fmt.Sprintf("**Location:** Line %d\n", loc.Concrete.Location.StartPoint.Row)) + + if loc.Symbolic.Annotation != "" { + result.WriteString(fmt.Sprintf("**Details:** %s\n", loc.Symbolic.Annotation)) + } + + break + } + } + + result.WriteString("**Manual Fix Needed:** Review the TODO comments added in the code changes for suggested fixes.\n") + result.WriteString("---\n") +} + func GetPrTitleBody(finalValidation string, zizmorFindings []zizmor.Finding, fixSummary string, llmOut string, summaryFindings string) (string, string) { var result Result success := finalValidation == "" || finalValidation == "[]" || finalValidation == "[]\n" @@ -104,7 +164,7 @@ func GetPrTitleBody(finalValidation string, zizmorFindings []zizmor.Finding, fix validationStatus = "**All security issues resolved!** No vulnerabilities detected." result = passed } else { - validationStatus = fmt.Sprintf("**Manual review needed - some issues remain:**\n```json\n%s\n```", finalValidation) + validationStatus = formatRemainingIssues(finalValidation) result = failed } @@ -113,7 +173,7 @@ func GetPrTitleBody(finalValidation string, zizmorFindings []zizmor.Finding, fix body := fmt.Sprintf(bodyFmt, len(zizmorFindings), tableRows, - llmOut, + // llmOut, result.status, result.text, validationStatus, @@ -127,4 +187,4 @@ func GetPrTitleBody(finalValidation string, zizmorFindings []zizmor.Finding, fix } return title, body -} +} \ No newline at end of file diff --git a/pkg/github/results_test.go b/pkg/github/results_test.go index 4a076358..ba3c9e46 100644 --- a/pkg/github/results_test.go +++ b/pkg/github/results_test.go @@ -27,9 +27,8 @@ func TestGetPrTitleBody(t *testing.T) { summaryFindings: "No issues in external dependencies", expectedTitle: "Security Audit & Fixes for GitHub Actions Workflows", expectedBodyParts: []string{ - "Complete Security Audit Report", + "Security Audit Summary", "Fixed 2 security issues automatically", - "Applied additional manual fixes", "PASSED", "All security issues resolved!", "No issues in external dependencies", @@ -78,7 +77,6 @@ func TestGetPrTitleBody(t *testing.T) { expectedBodyParts: []string{ "NEEDS REVIEW", "Manual review needed - some issues remain:", - `[{"desc": "remaining issue", "file": "test.yml"}]`, "Some external issues found", }, }, @@ -124,9 +122,7 @@ func TestGetPrTitleBody(t *testing.T) { } assert.Contains(t, body, "Auto-fixed by ZIZMOR") - assert.Contains(t, body, "Manual Security Fixes Applied") - assert.Contains(t, body, "Validation Report:") - assert.Contains(t, body, "External Dependencies Security Scan") + assert.Contains(t, body, "External Dependencies Scan") }) } -} +} \ No newline at end of file From 4f737900fa90cce4217d004507ee0224ed3934dc Mon Sep 17 00:00:00 2001 From: JocaSantos-dev Date: Thu, 8 Jan 2026 16:27:55 +0000 Subject: [PATCH 14/33] trig --- .github/workflows/workflow-scanner.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/workflow-scanner.yml b/.github/workflows/workflow-scanner.yml index 08dc63c4..9e9a27b6 100644 --- a/.github/workflows/workflow-scanner.yml +++ b/.github/workflows/workflow-scanner.yml @@ -1,7 +1,9 @@ name: Test Workflow Scanner on: - pull_request: + push: + branches: + - improve-pr jobs: test-scanner: From ea9147271086eaf5f3ca32f9ed694f889ab398ba Mon Sep 17 00:00:00 2001 From: JocaSantos-dev Date: Thu, 8 Jan 2026 16:40:16 +0000 Subject: [PATCH 15/33] debug --- pkg/github/result.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pkg/github/result.go b/pkg/github/result.go index b0123f66..fed1b8d0 100644 --- a/pkg/github/result.go +++ b/pkg/github/result.go @@ -3,6 +3,7 @@ package github import ( "encoding/json" "fmt" + "log" "regexp" "strings" "workflow-scanner/pkg/zizmor" @@ -100,9 +101,13 @@ func formatRemainingIssues(finalValidation string) string { var findings []zizmor.Finding if err := json.Unmarshal([]byte(finalValidation), &findings); err != nil { + log.Printf("ERROR: Failed to unmarshal ZIZMOR findings: %v", err) + log.Printf("DEBUG: Raw finalValidation (first 500 chars): %s", finalValidation[:min(500, len(finalValidation))]) return fmt.Sprintf("**Manual review needed - some issues remain:**\n```json\n%s\n```", finalValidation) } + log.Printf("DEBUG: Successfully parsed %d findings", len(findings)) + var result strings.Builder result.WriteString("**Manual review needed - some issues remain:**\n\n") From 91664f08877d689fea59d108d05f33ca65cecdc4 Mon Sep 17 00:00:00 2001 From: JocaSantos-dev Date: Thu, 8 Jan 2026 17:05:11 +0000 Subject: [PATCH 16/33] test2 --- pkg/github/result.go | 28 +++++++++++++++++++++------- pkg/github/results_test.go | 2 +- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/pkg/github/result.go b/pkg/github/result.go index fed1b8d0..d982b2ad 100644 --- a/pkg/github/result.go +++ b/pkg/github/result.go @@ -99,19 +99,17 @@ func formatRemainingIssues(finalValidation string) string { return "" } - var findings []zizmor.Finding - if err := json.Unmarshal([]byte(finalValidation), &findings); err != nil { - log.Printf("ERROR: Failed to unmarshal ZIZMOR findings: %v", err) - log.Printf("DEBUG: Raw finalValidation (first 500 chars): %s", finalValidation[:min(500, len(finalValidation))]) + allFindings := parseZizmorFindings(finalValidation) + if allFindings == nil { return fmt.Sprintf("**Manual review needed - some issues remain:**\n```json\n%s\n```", finalValidation) } - log.Printf("DEBUG: Successfully parsed %d findings", len(findings)) + log.Printf("DEBUG: Successfully parsed %d findings", len(allFindings)) var result strings.Builder result.WriteString("**Manual review needed - some issues remain:**\n\n") - fileIssues := groupFindingsByFile(findings) + fileIssues := groupFindingsByFile(allFindings) for filePath, issues := range fileIssues { result.WriteString(fmt.Sprintf("### 📄 `%s`\n", filePath)) @@ -124,6 +122,22 @@ func formatRemainingIssues(finalValidation string) string { return result.String() } +func parseZizmorFindings(finalValidation string) []zizmor.Finding { + var allFindings []zizmor.Finding + + cleanedInput := strings.TrimSuffix(finalValidation, "[]") + + if err := json.Unmarshal([]byte(cleanedInput), &allFindings); err != nil { + log.Printf("ERROR: Failed to unmarshal ZIZMOR findings: %v", err) + const maxLogChars = 500 + log.Printf("DEBUG: Raw input (first %d chars): %s", maxLogChars, finalValidation[:min(maxLogChars, len(finalValidation))]) + + return nil + } + + return allFindings +} + func groupFindingsByFile(findings []zizmor.Finding) map[string][]zizmor.Finding { fileIssues := make(map[string][]zizmor.Finding) for _, finding := range findings { @@ -192,4 +206,4 @@ func GetPrTitleBody(finalValidation string, zizmorFindings []zizmor.Finding, fix } return title, body -} \ No newline at end of file +} diff --git a/pkg/github/results_test.go b/pkg/github/results_test.go index ba3c9e46..3d0b352b 100644 --- a/pkg/github/results_test.go +++ b/pkg/github/results_test.go @@ -125,4 +125,4 @@ func TestGetPrTitleBody(t *testing.T) { assert.Contains(t, body, "External Dependencies Scan") }) } -} \ No newline at end of file +} From d2c2c2e91c5310901e3016fdc9837a7a37d0e088 Mon Sep 17 00:00:00 2001 From: JocaSantos-dev Date: Thu, 8 Jan 2026 17:17:13 +0000 Subject: [PATCH 17/33] fix: text was all in header mode --- pkg/github/result.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/github/result.go b/pkg/github/result.go index d982b2ad..c7c290cd 100644 --- a/pkg/github/result.go +++ b/pkg/github/result.go @@ -112,7 +112,7 @@ func formatRemainingIssues(finalValidation string) string { fileIssues := groupFindingsByFile(allFindings) for filePath, issues := range fileIssues { - result.WriteString(fmt.Sprintf("### 📄 `%s`\n", filePath)) + result.WriteString(fmt.Sprintf("📄 **%s**\n\n", filePath)) for _, issue := range issues { formatIssueDetails(&result, issue) From b2fed08f738519fae108237bc80ad23ba8bf7d5a Mon Sep 17 00:00:00 2001 From: JocaSantos-dev Date: Fri, 9 Jan 2026 10:37:04 +0000 Subject: [PATCH 18/33] fix2 --- pkg/github/result.go | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/pkg/github/result.go b/pkg/github/result.go index c7c290cd..26089ef5 100644 --- a/pkg/github/result.go +++ b/pkg/github/result.go @@ -155,23 +155,22 @@ func groupFindingsByFile(findings []zizmor.Finding) map[string][]zizmor.Finding } func formatIssueDetails(result *strings.Builder, issue zizmor.Finding) { - result.WriteString(fmt.Sprintf("**Issue:** %s\n", issue.Desc)) - result.WriteString(fmt.Sprintf("**Severity:** %s\n", issue.Determinations.Severity)) + result.WriteString(fmt.Sprintf("- **Issue:** %s\n", issue.Desc)) + result.WriteString(fmt.Sprintf("- **Severity:** %s\n", issue.Determinations.Severity)) for _, loc := range issue.Locations { if loc.Concrete.Location.StartPoint.Row > 0 { - result.WriteString(fmt.Sprintf("**Location:** Line %d\n", loc.Concrete.Location.StartPoint.Row)) + result.WriteString(fmt.Sprintf("- **Location:** Line %d\n", loc.Concrete.Location.StartPoint.Row)) if loc.Symbolic.Annotation != "" { - result.WriteString(fmt.Sprintf("**Details:** %s\n", loc.Symbolic.Annotation)) + result.WriteString(fmt.Sprintf("- **Details:** %s\n", loc.Symbolic.Annotation)) } break } } - result.WriteString("**Manual Fix Needed:** Review the TODO comments added in the code changes for suggested fixes.\n") - result.WriteString("---\n") + result.WriteString("- **Manual Fix Needed:** Review the TODO comments added in the code changes for suggested fixes.\n\n") } func GetPrTitleBody(finalValidation string, zizmorFindings []zizmor.Finding, fixSummary string, llmOut string, summaryFindings string) (string, string) { From dcbf1493814a1dafcd6afbe91baffd7b93c48343 Mon Sep 17 00:00:00 2001 From: JocaSantos-dev Date: Fri, 9 Jan 2026 10:48:29 +0000 Subject: [PATCH 19/33] test3 --- pkg/github/result.go | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/pkg/github/result.go b/pkg/github/result.go index 26089ef5..02b66c74 100644 --- a/pkg/github/result.go +++ b/pkg/github/result.go @@ -112,11 +112,12 @@ func formatRemainingIssues(finalValidation string) string { fileIssues := groupFindingsByFile(allFindings) for filePath, issues := range fileIssues { - result.WriteString(fmt.Sprintf("📄 **%s**\n\n", filePath)) + result.WriteString(fmt.Sprintf("📄 **%s**\n", filePath)) - for _, issue := range issues { - formatIssueDetails(&result, issue) + for i, issue := range issues { + formatIssueDetails(&result, issue, i == len(issues)-1) } + result.WriteString("\n") } return result.String() @@ -154,7 +155,7 @@ func groupFindingsByFile(findings []zizmor.Finding) map[string][]zizmor.Finding return fileIssues } -func formatIssueDetails(result *strings.Builder, issue zizmor.Finding) { +func formatIssueDetails(result *strings.Builder, issue zizmor.Finding, isLast bool) { result.WriteString(fmt.Sprintf("- **Issue:** %s\n", issue.Desc)) result.WriteString(fmt.Sprintf("- **Severity:** %s\n", issue.Determinations.Severity)) @@ -170,7 +171,10 @@ func formatIssueDetails(result *strings.Builder, issue zizmor.Finding) { } } - result.WriteString("- **Manual Fix Needed:** Review the TODO comments added in the code changes for suggested fixes.\n\n") + result.WriteString("- **Manual Fix Needed:** Review the TODO comments added in the code changes for suggested fixes.\n") + if !isLast { + result.WriteString("\n") + } } func GetPrTitleBody(finalValidation string, zizmorFindings []zizmor.Finding, fixSummary string, llmOut string, summaryFindings string) (string, string) { From 0374ef5b6596d80c5246ac60eebabb6d11dd593d Mon Sep 17 00:00:00 2001 From: JocaSantos-dev Date: Fri, 9 Jan 2026 11:11:51 +0000 Subject: [PATCH 20/33] test4 --- pkg/github/result.go | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/pkg/github/result.go b/pkg/github/result.go index 02b66c74..2c80869d 100644 --- a/pkg/github/result.go +++ b/pkg/github/result.go @@ -114,8 +114,8 @@ func formatRemainingIssues(finalValidation string) string { for filePath, issues := range fileIssues { result.WriteString(fmt.Sprintf("📄 **%s**\n", filePath)) - for i, issue := range issues { - formatIssueDetails(&result, issue, i == len(issues)-1) + for _, issue := range issues { + formatIssueDetails(&result, issue) } result.WriteString("\n") } @@ -155,7 +155,7 @@ func groupFindingsByFile(findings []zizmor.Finding) map[string][]zizmor.Finding return fileIssues } -func formatIssueDetails(result *strings.Builder, issue zizmor.Finding, isLast bool) { +func formatIssueDetails(result *strings.Builder, issue zizmor.Finding) { result.WriteString(fmt.Sprintf("- **Issue:** %s\n", issue.Desc)) result.WriteString(fmt.Sprintf("- **Severity:** %s\n", issue.Determinations.Severity)) @@ -172,9 +172,6 @@ func formatIssueDetails(result *strings.Builder, issue zizmor.Finding, isLast bo } result.WriteString("- **Manual Fix Needed:** Review the TODO comments added in the code changes for suggested fixes.\n") - if !isLast { - result.WriteString("\n") - } } func GetPrTitleBody(finalValidation string, zizmorFindings []zizmor.Finding, fixSummary string, llmOut string, summaryFindings string) (string, string) { From ccd93202ddd286f88f6e0a792609d7d0ad7013e4 Mon Sep 17 00:00:00 2001 From: JocaSantos-dev Date: Fri, 9 Jan 2026 11:20:36 +0000 Subject: [PATCH 21/33] test5 --- pkg/github/result.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/pkg/github/result.go b/pkg/github/result.go index 2c80869d..02b66c74 100644 --- a/pkg/github/result.go +++ b/pkg/github/result.go @@ -114,8 +114,8 @@ func formatRemainingIssues(finalValidation string) string { for filePath, issues := range fileIssues { result.WriteString(fmt.Sprintf("📄 **%s**\n", filePath)) - for _, issue := range issues { - formatIssueDetails(&result, issue) + for i, issue := range issues { + formatIssueDetails(&result, issue, i == len(issues)-1) } result.WriteString("\n") } @@ -155,7 +155,7 @@ func groupFindingsByFile(findings []zizmor.Finding) map[string][]zizmor.Finding return fileIssues } -func formatIssueDetails(result *strings.Builder, issue zizmor.Finding) { +func formatIssueDetails(result *strings.Builder, issue zizmor.Finding, isLast bool) { result.WriteString(fmt.Sprintf("- **Issue:** %s\n", issue.Desc)) result.WriteString(fmt.Sprintf("- **Severity:** %s\n", issue.Determinations.Severity)) @@ -172,6 +172,9 @@ func formatIssueDetails(result *strings.Builder, issue zizmor.Finding) { } result.WriteString("- **Manual Fix Needed:** Review the TODO comments added in the code changes for suggested fixes.\n") + if !isLast { + result.WriteString("\n") + } } func GetPrTitleBody(finalValidation string, zizmorFindings []zizmor.Finding, fixSummary string, llmOut string, summaryFindings string) (string, string) { From 6646e13567484d78bdc4ed69c5e8a78d9fbfbf14 Mon Sep 17 00:00:00 2001 From: JocaSantos-dev Date: Fri, 9 Jan 2026 11:30:22 +0000 Subject: [PATCH 22/33] test6 --- pkg/github/result.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pkg/github/result.go b/pkg/github/result.go index 02b66c74..d55e314e 100644 --- a/pkg/github/result.go +++ b/pkg/github/result.go @@ -115,7 +115,10 @@ func formatRemainingIssues(finalValidation string) string { result.WriteString(fmt.Sprintf("📄 **%s**\n", filePath)) for i, issue := range issues { - formatIssueDetails(&result, issue, i == len(issues)-1) + formatIssueDetails(&result, issue) + if i < len(issues)-1 { + result.WriteString("---\n\n") + } } result.WriteString("\n") } @@ -155,7 +158,7 @@ func groupFindingsByFile(findings []zizmor.Finding) map[string][]zizmor.Finding return fileIssues } -func formatIssueDetails(result *strings.Builder, issue zizmor.Finding, isLast bool) { +func formatIssueDetails(result *strings.Builder, issue zizmor.Finding) { result.WriteString(fmt.Sprintf("- **Issue:** %s\n", issue.Desc)) result.WriteString(fmt.Sprintf("- **Severity:** %s\n", issue.Determinations.Severity)) @@ -171,10 +174,7 @@ func formatIssueDetails(result *strings.Builder, issue zizmor.Finding, isLast bo } } - result.WriteString("- **Manual Fix Needed:** Review the TODO comments added in the code changes for suggested fixes.\n") - if !isLast { - result.WriteString("\n") - } + result.WriteString("- **Manual Fix Needed:** Review the TODO comments added in the code changes for suggested fixes.\n\n") } func GetPrTitleBody(finalValidation string, zizmorFindings []zizmor.Finding, fixSummary string, llmOut string, summaryFindings string) (string, string) { From 672883d4239f6fd3f9448ebbdb634f23643a0c48 Mon Sep 17 00:00:00 2001 From: JocaSantos-dev Date: Fri, 9 Jan 2026 11:40:58 +0000 Subject: [PATCH 23/33] feat: Improve description on autofixes --- pkg/github/result.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/pkg/github/result.go b/pkg/github/result.go index d55e314e..f306c095 100644 --- a/pkg/github/result.go +++ b/pkg/github/result.go @@ -15,7 +15,7 @@ const ( **Findings:** %d -### Files Auto-fixed by ZIZMOR +### Automatic Fixes Applied | File | Fixes | | --- | ---: | %s @@ -67,6 +67,10 @@ func fixSummaryToTableRows(summary string) string { if strings.HasPrefix(l, "Successfully applied") { continue } + // Skip unwanted lines + if l == "}" || l == "Fix Summary" || l == "]" || strings.TrimSpace(l) == "}" || strings.TrimSpace(l) == "]" { + continue + } const expectedRegexGroups = 3 if m := fixLineRe.FindStringSubmatch(l); len(m) == expectedRegexGroups { file := strings.TrimSpace(m[1]) @@ -114,11 +118,9 @@ func formatRemainingIssues(finalValidation string) string { for filePath, issues := range fileIssues { result.WriteString(fmt.Sprintf("📄 **%s**\n", filePath)) - for i, issue := range issues { + for _, issue := range issues { formatIssueDetails(&result, issue) - if i < len(issues)-1 { - result.WriteString("---\n\n") - } + result.WriteString("---\n\n") } result.WriteString("\n") } From c32a0d2044e7d1f44d38d14df0de4cb9e3494a5e Mon Sep 17 00:00:00 2001 From: JocaSantos-dev Date: Fri, 9 Jan 2026 12:07:48 +0000 Subject: [PATCH 24/33] feat: refactor external deps pr visual --- pkg/github/result.go | 197 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 165 insertions(+), 32 deletions(-) diff --git a/pkg/github/result.go b/pkg/github/result.go index f306c095..e5c0c1c5 100644 --- a/pkg/github/result.go +++ b/pkg/github/result.go @@ -32,7 +32,7 @@ const ( %s --- -*Automated security audit by ZIZMOR + AI analysis*` +*Automated security audit by AI analysis*` ) type Result struct { @@ -53,6 +53,38 @@ var ( var fixLineRe = regexp.MustCompile(`^\s*(.+?):\s*(\d+)`) +func shouldSkipLine(l string) bool { + if l == "" || strings.HasPrefix(l, "Successfully applied") { + return true + } + + return l == "}" || l == "Fix Summary" || l == "]" +} + +func parseTableRow(l string) string { + const expectedRegexGroups = 3 + if m := fixLineRe.FindStringSubmatch(l); len(m) == expectedRegexGroups { + file := strings.TrimSpace(m[1]) + count := m[2] + + return fmt.Sprintf("| %s | %s |\n", file, count) + } + + if idx := strings.Index(l, ":"); idx != -1 { + file := strings.TrimSpace(l[:idx]) + rest := strings.TrimSpace(l[idx+1:]) + numRe := regexp.MustCompile(`\d+`) + num := numRe.FindString(rest) + if num == "" { + return fmt.Sprintf("| %s | - |\n", file) + } + + return fmt.Sprintf("| %s | %s |\n", file, num) + } + + return fmt.Sprintf("| %s | - |\n", l) +} + func fixSummaryToTableRows(summary string) string { if strings.TrimSpace(summary) == "" { return "| (none) | 0 |\n" @@ -61,38 +93,10 @@ func fixSummaryToTableRows(summary string) string { rows := make([]string, 0, len(lines)) for _, l := range lines { l = strings.TrimSpace(l) - if l == "" { - continue - } - if strings.HasPrefix(l, "Successfully applied") { - continue - } - // Skip unwanted lines - if l == "}" || l == "Fix Summary" || l == "]" || strings.TrimSpace(l) == "}" || strings.TrimSpace(l) == "]" { + if shouldSkipLine(l) { continue } - const expectedRegexGroups = 3 - if m := fixLineRe.FindStringSubmatch(l); len(m) == expectedRegexGroups { - file := strings.TrimSpace(m[1]) - count := m[2] - rows = append(rows, fmt.Sprintf("| %s | %s |\n", file, count)) - - continue - } - if idx := strings.Index(l, ":"); idx != -1 { - file := strings.TrimSpace(l[:idx]) - rest := strings.TrimSpace(l[idx+1:]) - numRe := regexp.MustCompile(`\d+`) - num := numRe.FindString(rest) - if num == "" { - rows = append(rows, fmt.Sprintf("| %s | - |\n", file)) - } else { - rows = append(rows, fmt.Sprintf("| %s | %s |\n", file, num)) - } - - continue - } - rows = append(rows, fmt.Sprintf("| %s | - |\n", l)) + rows = append(rows, parseTableRow(l)) } return strings.Join(rows, "") @@ -179,6 +183,134 @@ func formatIssueDetails(result *strings.Builder, issue zizmor.Finding) { result.WriteString("- **Manual Fix Needed:** Review the TODO comments added in the code changes for suggested fixes.\n\n") } +type externalDepData struct { + repoStats map[string]int + repoFiles map[string][]string + repoDetails map[string][]string +} + +func parseExternalDependencyLines(lines []string) *externalDepData { + data := &externalDepData{ + repoStats: make(map[string]int), + repoFiles: make(map[string][]string), + repoDetails: make(map[string][]string), + } + + currentRepo := "" + currentFindingBlock := "" + + for _, line := range lines { + line = strings.TrimSpace(line) + if line == "" { + continue + } + + if isRepoHeader(line) { + currentRepo, currentFindingBlock = processRepoHeader(line, currentRepo, currentFindingBlock, data) + } else if strings.HasPrefix(line, "- File:") && currentRepo != "" { + currentFindingBlock = processFileLine(line, currentRepo, currentFindingBlock, data) + } + } + + if currentRepo != "" && currentFindingBlock != "" { + data.repoDetails[currentRepo] = append(data.repoDetails[currentRepo], currentFindingBlock) + } + + return data +} + +func isRepoHeader(line string) bool { + return strings.HasPrefix(line, "- **") && strings.Contains(line, "**:") +} + +func processRepoHeader(line, currentRepo, currentFindingBlock string, data *externalDepData) (string, string) { + if currentRepo != "" && currentFindingBlock != "" { + data.repoDetails[currentRepo] = append(data.repoDetails[currentRepo], currentFindingBlock) + currentFindingBlock = "" + } + + parts := strings.Split(line, "**:") + if len(parts) >= 1 { + currentRepo = strings.TrimPrefix(parts[0], "- **") + data.repoStats[currentRepo]++ + + if len(parts) > 1 { + desc := strings.TrimSpace(parts[1]) + currentFindingBlock = fmt.Sprintf("- **Issue:** %s\n", desc) + } + } + + return currentRepo, currentFindingBlock +} + +func processFileLine(line, currentRepo, currentFindingBlock string, data *externalDepData) string { + filePath := strings.TrimPrefix(line, "- File:") + filePath = strings.TrimSpace(filePath) + data.repoFiles[currentRepo] = append(data.repoFiles[currentRepo], filePath) + + return currentFindingBlock + fmt.Sprintf("- **File:** %s\n", filePath) +} + +func buildExternalSummaryTable(data *externalDepData) string { + var result strings.Builder + + result.WriteString("**Summary:** ") + totalFindings := 0 + for _, count := range data.repoStats { + totalFindings += count + } + result.WriteString(fmt.Sprintf("%d findings across %d actions\n\n", totalFindings, len(data.repoStats))) + + result.WriteString("| Action/Repo | Files | Findings |\n") + result.WriteString("| --- | ---: | ---: |\n") + + for repo, count := range data.repoStats { + fileCount := len(data.repoFiles[repo]) + if fileCount == 0 { + fileCount = 1 + } + result.WriteString(fmt.Sprintf("| %s | %d | %d |\n", repo, fileCount, count)) + } + + result.WriteString("\n") + + return result.String() +} + +func buildExternalDetailedFindings(data *externalDepData) string { + var result strings.Builder + + result.WriteString("
\n") + result.WriteString("📋 Detailed Findings (click to expand)\n\n") + + for repo, details := range data.repoDetails { + result.WriteString(fmt.Sprintf("#### 📦 %s\n\n", repo)) + for _, finding := range details { + result.WriteString(finding) + result.WriteString("\n---\n\n") + } + } + + result.WriteString("
\n") + + return result.String() +} + +func formatExternalDependencies(summaryFindings string) string { + if strings.TrimSpace(summaryFindings) == "" { + return "No external dependencies scanned." + } + + lines := strings.Split(summaryFindings, "\n") + data := parseExternalDependencyLines(lines) + + if len(data.repoStats) == 0 { + return summaryFindings + } + + return buildExternalSummaryTable(data) + buildExternalDetailedFindings(data) +} + func GetPrTitleBody(finalValidation string, zizmorFindings []zizmor.Finding, fixSummary string, llmOut string, summaryFindings string) (string, string) { var result Result success := finalValidation == "" || finalValidation == "[]" || finalValidation == "[]\n" @@ -193,6 +325,7 @@ func GetPrTitleBody(finalValidation string, zizmorFindings []zizmor.Finding, fix } tableRows := fixSummaryToTableRows(fixSummary) + formattedExternal := formatExternalDependencies(summaryFindings) body := fmt.Sprintf(bodyFmt, len(zizmorFindings), @@ -201,7 +334,7 @@ func GetPrTitleBody(finalValidation string, zizmorFindings []zizmor.Finding, fix result.status, result.text, validationStatus, - summaryFindings, + formattedExternal, ) // GitHub PR body limit is 65,536 characters From 3c34441b09b3a0e6dff816494bfc16c64a06771c Mon Sep 17 00:00:00 2001 From: JocaSantos-dev Date: Fri, 9 Jan 2026 12:19:10 +0000 Subject: [PATCH 25/33] test manual reviews with collapsiles --- pkg/github/result.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pkg/github/result.go b/pkg/github/result.go index e5c0c1c5..e5ea89ec 100644 --- a/pkg/github/result.go +++ b/pkg/github/result.go @@ -120,13 +120,15 @@ func formatRemainingIssues(finalValidation string) string { fileIssues := groupFindingsByFile(allFindings) for filePath, issues := range fileIssues { - result.WriteString(fmt.Sprintf("📄 **%s**\n", filePath)) + result.WriteString("
\n") + result.WriteString(fmt.Sprintf("📄 %s\n\n", filePath)) for _, issue := range issues { formatIssueDetails(&result, issue) result.WriteString("---\n\n") } - result.WriteString("\n") + + result.WriteString("
\n\n") } return result.String() From 45b826a2affa853be60a705c7c8487d1e31408d1 Mon Sep 17 00:00:00 2001 From: JocaSantos-dev Date: Fri, 9 Jan 2026 12:29:00 +0000 Subject: [PATCH 26/33] feat: improve external deps visual on the pr --- pkg/github/result.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/github/result.go b/pkg/github/result.go index e5ea89ec..cfc18624 100644 --- a/pkg/github/result.go +++ b/pkg/github/result.go @@ -286,11 +286,13 @@ func buildExternalDetailedFindings(data *externalDepData) string { result.WriteString("📋 Detailed Findings (click to expand)\n\n") for repo, details := range data.repoDetails { - result.WriteString(fmt.Sprintf("#### 📦 %s\n\n", repo)) + result.WriteString("
\n") + result.WriteString(fmt.Sprintf("📦 %s\n\n", repo)) for _, finding := range details { result.WriteString(finding) result.WriteString("\n---\n\n") } + result.WriteString("
\n\n") } result.WriteString("\n") From 9632aed6938e0a4b490669a0dc81800602eb61de Mon Sep 17 00:00:00 2001 From: JocaSantos-dev Date: Fri, 9 Jan 2026 12:34:32 +0000 Subject: [PATCH 27/33] feat: add expand comment info --- pkg/github/result.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/github/result.go b/pkg/github/result.go index cfc18624..ffbb02b2 100644 --- a/pkg/github/result.go +++ b/pkg/github/result.go @@ -121,7 +121,7 @@ func formatRemainingIssues(finalValidation string) string { for filePath, issues := range fileIssues { result.WriteString("
\n") - result.WriteString(fmt.Sprintf("📄 %s\n\n", filePath)) + result.WriteString(fmt.Sprintf("📄 %s (click to expand)\n\n", filePath)) for _, issue := range issues { formatIssueDetails(&result, issue) From ec4fff63e89c977b0401a39834d164006d97ee37 Mon Sep 17 00:00:00 2001 From: JocaSantos-dev Date: Mon, 12 Jan 2026 11:10:04 +0000 Subject: [PATCH 28/33] feat: fix UT with last changes --- pkg/github/results_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/github/results_test.go b/pkg/github/results_test.go index 3d0b352b..6be1aa17 100644 --- a/pkg/github/results_test.go +++ b/pkg/github/results_test.go @@ -32,7 +32,7 @@ func TestGetPrTitleBody(t *testing.T) { "PASSED", "All security issues resolved!", "No issues in external dependencies", - "Automated security audit by ZIZMOR + AI analysis", + "Automated security audit by AI analysis", }, }, { @@ -121,7 +121,7 @@ func TestGetPrTitleBody(t *testing.T) { "Expected body to contain: %s", expectedPart) } - assert.Contains(t, body, "Auto-fixed by ZIZMOR") + assert.Contains(t, body, "Automatic Fixes Applied") assert.Contains(t, body, "External Dependencies Scan") }) } From ff5a694ae8d9a0cf61d9d75f95e887415d2f1017 Mon Sep 17 00:00:00 2001 From: JocaSantos-dev Date: Mon, 12 Jan 2026 14:37:00 +0000 Subject: [PATCH 29/33] fix: UT main test --- main_test.go | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/main_test.go b/main_test.go index c1ec9e38..36160c77 100644 --- a/main_test.go +++ b/main_test.go @@ -36,7 +36,7 @@ func TestScanAndFixWorflowsImpl(t *testing.T) { // Step 1: Run ZIZMOR auto-fix mockZizmor.EXPECT(). RunZizmorAutoFix(gomock.Any(), mockDirectory). - Return(mockDirectory, "Fixed 2 security issues automatically", nil) + Return(mockDirectory, []zizmor.Finding{}, "Fixed 2 security issues automatically", nil) // Step 2: Check remaining issues - none found mockZizmor.EXPECT(). @@ -84,7 +84,7 @@ func TestScanAndFixWorflowsImpl(t *testing.T) { // Step 1: Run ZIZMOR auto-fix mockZizmor.EXPECT(). RunZizmorAutoFix(gomock.Any(), mockDirectory). - Return(mockDirectory, "Fixed some issues", nil) + Return(mockDirectory, []zizmor.Finding{}, "Fixed some issues", nil) // Step 2: Check remaining issues - some found remainingIssues := `[{"desc": "manual fix needed"}]` @@ -134,10 +134,7 @@ func TestScanAndFixWorflowsImpl(t *testing.T) { // Step 1: ZIZMOR auto-fix fails mockZizmor.EXPECT(). RunZizmorAutoFix(gomock.Any(), mockDirectory). - Return(nil, "", errors.New("ZIZMOR container failed")) - - // No other calls should happen after failure - + Return(nil, []zizmor.Finding{}, "", errors.New("ZIZMOR container failed")) return mockZizmor, mockAgent, mockGithub, mockDirectory }, expectedResult: "", @@ -156,7 +153,7 @@ func TestScanAndFixWorflowsImpl(t *testing.T) { // Step 1: Run ZIZMOR auto-fix mockZizmor.EXPECT(). RunZizmorAutoFix(gomock.Any(), mockDirectory). - Return(mockDirectory, "Fixed some issues", nil) + Return(mockDirectory, []zizmor.Finding{}, "Fixed some issues", nil) // Step 2: Check remaining issues - some found remainingIssues := `[{"desc": "complex issue"}]` @@ -193,7 +190,7 @@ func TestScanAndFixWorflowsImpl(t *testing.T) { // Step 1: Run ZIZMOR auto-fix mockZizmor.EXPECT(). RunZizmorAutoFix(gomock.Any(), mockDirectory). - Return(mockDirectory, "Fixed issues", nil) + Return(mockDirectory, []zizmor.Finding{}, "Fixed issues", nil) // Step 2: Check remaining issues - none mockZizmor.EXPECT(). From fa70f0fd5fa7435f266859668f3d535699347ae1 Mon Sep 17 00:00:00 2001 From: JocaSantos-dev Date: Mon, 12 Jan 2026 14:43:29 +0000 Subject: [PATCH 30/33] fix typo --- main_test.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/main_test.go b/main_test.go index 36160c77..74c9d2d1 100644 --- a/main_test.go +++ b/main_test.go @@ -36,7 +36,7 @@ func TestScanAndFixWorflowsImpl(t *testing.T) { // Step 1: Run ZIZMOR auto-fix mockZizmor.EXPECT(). RunZizmorAutoFix(gomock.Any(), mockDirectory). - Return(mockDirectory, []zizmor.Finding{}, "Fixed 2 security issues automatically", nil) + Return(mockDirectory, []zizmor.Finding{}, "Fixed 2 security issues automatically", nil) // Step 2: Check remaining issues - none found mockZizmor.EXPECT(). @@ -84,7 +84,7 @@ func TestScanAndFixWorflowsImpl(t *testing.T) { // Step 1: Run ZIZMOR auto-fix mockZizmor.EXPECT(). RunZizmorAutoFix(gomock.Any(), mockDirectory). - Return(mockDirectory, []zizmor.Finding{}, "Fixed some issues", nil) + Return(mockDirectory, []zizmor.Finding{}, "Fixed some issues", nil) // Step 2: Check remaining issues - some found remainingIssues := `[{"desc": "manual fix needed"}]` @@ -134,7 +134,7 @@ func TestScanAndFixWorflowsImpl(t *testing.T) { // Step 1: ZIZMOR auto-fix fails mockZizmor.EXPECT(). RunZizmorAutoFix(gomock.Any(), mockDirectory). - Return(nil, []zizmor.Finding{}, "", errors.New("ZIZMOR container failed")) + Return(nil, []zizmor.Finding{}, "", errors.New("ZIZMOR container failed")) return mockZizmor, mockAgent, mockGithub, mockDirectory }, expectedResult: "", @@ -153,7 +153,7 @@ func TestScanAndFixWorflowsImpl(t *testing.T) { // Step 1: Run ZIZMOR auto-fix mockZizmor.EXPECT(). RunZizmorAutoFix(gomock.Any(), mockDirectory). - Return(mockDirectory, []zizmor.Finding{}, "Fixed some issues", nil) + Return(mockDirectory, []zizmor.Finding{}, "Fixed some issues", nil) // Step 2: Check remaining issues - some found remainingIssues := `[{"desc": "complex issue"}]` @@ -190,7 +190,7 @@ func TestScanAndFixWorflowsImpl(t *testing.T) { // Step 1: Run ZIZMOR auto-fix mockZizmor.EXPECT(). RunZizmorAutoFix(gomock.Any(), mockDirectory). - Return(mockDirectory, []zizmor.Finding{}, "Fixed issues", nil) + Return(mockDirectory, []zizmor.Finding{}, "Fixed issues", nil) // Step 2: Check remaining issues - none mockZizmor.EXPECT(). From 2d223b70ddc0b5a5b0e1df2a91026dfeccc28a1e Mon Sep 17 00:00:00 2001 From: JocaSantos-dev Date: Tue, 13 Jan 2026 11:50:27 +0000 Subject: [PATCH 31/33] fix: resolve import cycle in pkg/zizmor and regenerate mocks --- mocks/client_mock.go | 14 ++++++++++ mocks/container_mock.go | 38 +++++++++++++++++++++++++++ mocks/env_mock.go | 42 ++++++++++++++++++++++++++++++ mocks/llm_mock.go | 16 ++++++++++++ pkg/zizmor/zizmor_output_parser.go | 4 +-- pkg/zizmor/zizmor_test.go | 8 +++--- 6 files changed, 116 insertions(+), 6 deletions(-) diff --git a/mocks/client_mock.go b/mocks/client_mock.go index c8729625..8ab7fddd 100644 --- a/mocks/client_mock.go +++ b/mocks/client_mock.go @@ -109,6 +109,20 @@ func (mr *MockClientMockRecorder) LLM(opts ...any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LLM", reflect.TypeOf((*MockClient)(nil).LLM), opts...) } +// SetSecret mocks base method. +func (m *MockClient) SetSecret(name, plaintext string) *dagger.Secret { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetSecret", name, plaintext) + ret0, _ := ret[0].(*dagger.Secret) + return ret0 +} + +// SetSecret indicates an expected call of SetSecret. +func (mr *MockClientMockRecorder) SetSecret(name, plaintext any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetSecret", reflect.TypeOf((*MockClient)(nil).SetSecret), name, plaintext) +} + // Workspace mocks base method. func (m *MockClient) Workspace(source *dagger.Directory) dagger0.Workspace { m.ctrl.T.Helper() diff --git a/mocks/container_mock.go b/mocks/container_mock.go index ccb20781..34e01c32 100644 --- a/mocks/container_mock.go +++ b/mocks/container_mock.go @@ -109,6 +109,25 @@ func (mr *MockContainerMockRecorder) WithDirectory(path, source any, opts ...any return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WithDirectory", reflect.TypeOf((*MockContainer)(nil).WithDirectory), varargs...) } +// WithEnvVariable mocks base method. +func (m *MockContainer) WithEnvVariable(name, value string, opts ...dagger.ContainerWithEnvVariableOpts) dagger0.Container { + m.ctrl.T.Helper() + varargs := []any{name, value} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "WithEnvVariable", varargs...) + ret0, _ := ret[0].(dagger0.Container) + return ret0 +} + +// WithEnvVariable indicates an expected call of WithEnvVariable. +func (mr *MockContainerMockRecorder) WithEnvVariable(name, value any, opts ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{name, value}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WithEnvVariable", reflect.TypeOf((*MockContainer)(nil).WithEnvVariable), varargs...) +} + // WithExec mocks base method. func (m *MockContainer) WithExec(args []string, opts ...dagger.ContainerWithExecOpts) dagger0.Container { m.ctrl.T.Helper() @@ -128,6 +147,25 @@ func (mr *MockContainerMockRecorder) WithExec(args any, opts ...any) *gomock.Cal return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WithExec", reflect.TypeOf((*MockContainer)(nil).WithExec), varargs...) } +// WithNewFile mocks base method. +func (m *MockContainer) WithNewFile(path, contents string, opts ...dagger.ContainerWithNewFileOpts) dagger0.Container { + m.ctrl.T.Helper() + varargs := []any{path, contents} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "WithNewFile", varargs...) + ret0, _ := ret[0].(dagger0.Container) + return ret0 +} + +// WithNewFile indicates an expected call of WithNewFile. +func (mr *MockContainerMockRecorder) WithNewFile(path, contents any, opts ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{path, contents}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WithNewFile", reflect.TypeOf((*MockContainer)(nil).WithNewFile), varargs...) +} + // WithWorkdir mocks base method. func (m *MockContainer) WithWorkdir(path string, opts ...dagger.ContainerWithWorkdirOpts) dagger0.Container { m.ctrl.T.Helper() diff --git a/mocks/env_mock.go b/mocks/env_mock.go index b77e9767..15d994b2 100644 --- a/mocks/env_mock.go +++ b/mocks/env_mock.go @@ -69,6 +69,48 @@ func (mr *MockEnvMockRecorder) Output(name any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Output", reflect.TypeOf((*MockEnv)(nil).Output), name) } +// WithDirectoryInput mocks base method. +func (m *MockEnv) WithDirectoryInput(name string, value *dagger.Directory, description string) dagger0.Env { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "WithDirectoryInput", name, value, description) + ret0, _ := ret[0].(dagger0.Env) + return ret0 +} + +// WithDirectoryInput indicates an expected call of WithDirectoryInput. +func (mr *MockEnvMockRecorder) WithDirectoryInput(name, value, description any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WithDirectoryInput", reflect.TypeOf((*MockEnv)(nil).WithDirectoryInput), name, value, description) +} + +// WithDirectoryOutput mocks base method. +func (m *MockEnv) WithDirectoryOutput(name, description string) dagger0.Env { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "WithDirectoryOutput", name, description) + ret0, _ := ret[0].(dagger0.Env) + return ret0 +} + +// WithDirectoryOutput indicates an expected call of WithDirectoryOutput. +func (mr *MockEnvMockRecorder) WithDirectoryOutput(name, description any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WithDirectoryOutput", reflect.TypeOf((*MockEnv)(nil).WithDirectoryOutput), name, description) +} + +// WithSecretInput mocks base method. +func (m *MockEnv) WithSecretInput(name string, secret *dagger.Secret, description string) dagger0.Env { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "WithSecretInput", name, secret, description) + ret0, _ := ret[0].(dagger0.Env) + return ret0 +} + +// WithSecretInput indicates an expected call of WithSecretInput. +func (mr *MockEnvMockRecorder) WithSecretInput(name, secret, description any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WithSecretInput", reflect.TypeOf((*MockEnv)(nil).WithSecretInput), name, secret, description) +} + // WithStringInput mocks base method. func (m *MockEnv) WithStringInput(name, value, description string) dagger0.Env { m.ctrl.T.Helper() diff --git a/mocks/llm_mock.go b/mocks/llm_mock.go index 053966c9..71370d38 100644 --- a/mocks/llm_mock.go +++ b/mocks/llm_mock.go @@ -10,6 +10,7 @@ package mocks import ( + context "context" reflect "reflect" dagger "workflow-scanner/internal/dagger" dagger0 "workflow-scanner/pkg/dagger" @@ -55,6 +56,21 @@ func (mr *MockLLMMockRecorder) Env() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Env", reflect.TypeOf((*MockLLM)(nil).Env)) } +// LastReply mocks base method. +func (m *MockLLM) LastReply(ctx context.Context) (string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "LastReply", ctx) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// LastReply indicates an expected call of LastReply. +func (mr *MockLLMMockRecorder) LastReply(ctx any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LastReply", reflect.TypeOf((*MockLLM)(nil).LastReply), ctx) +} + // WithEnv mocks base method. func (m *MockLLM) WithEnv(env dagger0.Env) dagger0.LLM { m.ctrl.T.Helper() diff --git a/pkg/zizmor/zizmor_output_parser.go b/pkg/zizmor/zizmor_output_parser.go index da26c0ab..af77c84c 100644 --- a/pkg/zizmor/zizmor_output_parser.go +++ b/pkg/zizmor/zizmor_output_parser.go @@ -95,8 +95,8 @@ func ParseZizmorOutput(input string) ([]Finding, string, error) { } fixSummary := "" - if lastIndex < len(input) { - fixSummary = strings.TrimSpace(input[lastIndex:]) + if lastIndex < len(input)-1 { + fixSummary = strings.TrimSpace(input[lastIndex+1:]) } return findings, fixSummary, nil diff --git a/pkg/zizmor/zizmor_test.go b/pkg/zizmor/zizmor_test.go index d2b99704..54a76def 100644 --- a/pkg/zizmor/zizmor_test.go +++ b/pkg/zizmor/zizmor_test.go @@ -93,7 +93,7 @@ func TestZizmorImpl_RunZizmorAutoFix(t *testing.T) { { name: "successful auto-fix execution", containerFindings: []Finding{}, - containerFixSummary: "Fixed 3 security vulnerabilities in workflows", + containerFixSummary: `{"desc": "test"} Fixed 3 security vulnerabilities in workflows`, containerError: nil, expectedOutput: "Fixed 3 security vulnerabilities in workflows", expectedError: "", @@ -101,7 +101,7 @@ func TestZizmorImpl_RunZizmorAutoFix(t *testing.T) { { name: "auto-fix with no changes", containerFindings: []Finding{}, - containerFixSummary: "No security issues found to fix", + containerFixSummary: `{"desc": "test"} No security issues found to fix`, containerError: nil, expectedOutput: "No security issues found to fix", expectedError: "", @@ -124,8 +124,8 @@ func TestZizmorImpl_RunZizmorAutoFix(t *testing.T) { mockContainer.EXPECT().WithWorkdir("/workspace").Return(mockContainer) // Setup the auto-fix execution - mockContainer.EXPECT().WithExec([]string{"sh", "-c", "zizmor --fix=all .github/workflows/ 2>&1 || true"}).Return(mockContainer) - mockContainer.EXPECT().Stdout(gomock.Any()).Return(tt.containerFindings, tt.containerFixSummary, tt.containerError) + mockContainer.EXPECT().WithExec([]string{"sh", "-c", "zizmor -q --format=json --fix=all .github/workflows/ 2>&1 || true"}).Return(mockContainer) + mockContainer.EXPECT().Stdout(gomock.Any()).Return(tt.containerFixSummary, tt.containerError) if tt.containerError == nil { mockContainer.EXPECT().Directory("/workspace").Return(resultDir) From ed756984a335cd142d7149c5dc69f134f0515211 Mon Sep 17 00:00:00 2001 From: JocaSantos-dev Date: Tue, 13 Jan 2026 11:56:20 +0000 Subject: [PATCH 32/33] fix: update mockgen path to generate mock in same package --- pkg/zizmor/zizmor.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/zizmor/zizmor.go b/pkg/zizmor/zizmor.go index 510658e8..c830d7d1 100644 --- a/pkg/zizmor/zizmor.go +++ b/pkg/zizmor/zizmor.go @@ -1,6 +1,6 @@ package zizmor -//go:generate mockgen -source=zizmor.go -destination=../../mocks/zizmor_mock.go -package=mocks Zizmor +//go:generate mockgen -source=zizmor.go -destination=zizmor_mock.go -package=zizmor Zizmor import ( "context" From 7e65e4614b4c87d31ea0064c85afa0615679545e Mon Sep 17 00:00:00 2001 From: JocaSantos-dev Date: Tue, 13 Jan 2026 12:08:40 +0000 Subject: [PATCH 33/33] refactor: agent testing, remove dagger dependency --- pkg/agent/agent_test.go | 35 +++++++---------------------------- 1 file changed, 7 insertions(+), 28 deletions(-) diff --git a/pkg/agent/agent_test.go b/pkg/agent/agent_test.go index 87e24e9b..58906395 100644 --- a/pkg/agent/agent_test.go +++ b/pkg/agent/agent_test.go @@ -99,40 +99,19 @@ func TestAgentImpl_FixRemainingIssues_LLMChain(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - // Test that we can read the prompt file and create error scenarios - // without requiring full Dagger integration - t.Run("prompt file not found", func(t *testing.T) { + // Test that we can verify early return without requiring full Dagger integration + t.Run("no issues requiring LLM fixes", func(t *testing.T) { mockClient := mocks.NewMockClient(ctrl) - mockEnv := mocks.NewMockEnv(ctrl) - mockWorkspace := mocks.NewMockWorkspace(ctrl) sourceDirectory := &internalDagger.Directory{} - // Set up the mocks for the initial setup that happens before prompt file reading - mockClient.EXPECT().Workspace(sourceDirectory).Return(mockWorkspace) - mockClient.EXPECT().Env().Return(mockEnv) - mockEnv.EXPECT().WithStringInput("zizmor_issues", `[{"desc": "security issue"}]`, gomock.Any()).Return(mockEnv) - mockEnv.EXPECT().WithStringInput("GO111MODULE", "on", gomock.Any()).Return(mockEnv) - mockEnv.EXPECT().WithStringInput("GOWORK", "off", gomock.Any()).Return(mockEnv) - mockEnv.EXPECT().WithWorkspaceInput("workspace", mockWorkspace, gomock.Any()).Return(mockEnv) - mockEnv.EXPECT().WithWorkspaceOutput("completed", gomock.Any()).Return(mockEnv) - mockEnv.EXPECT().WithStringOutput("explanations", gomock.Any()).Return(mockEnv) - - // Create a temporary directory without the prompt file - tempDir := t.TempDir() - originalWd, _ := os.Getwd() - defer os.Chdir(originalWd) - - // Change to temp directory so prompt file won't be found - os.Chdir(tempDir) - + // Pass empty issues to trigger early return (doesn't require Dagger infrastructure) agent := NewAgentImpl(mockClient) - actualDir, explanation, err := agent.fixRemainingIssuesImpl(context.Background(), sourceDirectory, `[{"desc": "security issue"}]`) + actualDir, explanation, err := agent.fixRemainingIssuesImpl(context.Background(), sourceDirectory, "[]") - // Should return error when prompt file can't be found - assert.Error(t, err) - assert.Contains(t, err.Error(), "failed to read prompt file") + // Should return successfully with no issues message + assert.NoError(t, err) assert.Equal(t, sourceDirectory, actualDir) - assert.Equal(t, "", explanation) + assert.Contains(t, explanation, "No remaining issues") }) t.Run("prompt file reading validates filesystem approach", func(t *testing.T) {