Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions pkg/cli/validators.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,33 +4,45 @@ import (
"errors"
"regexp"
"strings"

"github.com/githubnext/gh-aw/pkg/logger"
)

var validatorsLog = logger.New("cli:validators")

// workflowNameRegex validates workflow names contain only alphanumeric characters, hyphens, and underscores
var workflowNameRegex = regexp.MustCompile(`^[a-zA-Z0-9_-]+$`)

// ValidateWorkflowName checks if the provided workflow name is valid.
// It ensures the name is not empty and contains only alphanumeric characters, hyphens, and underscores.
func ValidateWorkflowName(s string) error {
validatorsLog.Printf("Validating workflow name: %s", s)
if s == "" {
validatorsLog.Print("Workflow name validation failed: empty name")
return errors.New("workflow name cannot be empty")
}
if !workflowNameRegex.MatchString(s) {
validatorsLog.Printf("Workflow name validation failed: invalid characters in %s", s)
return errors.New("workflow name must contain only alphanumeric characters, hyphens, and underscores")
}
validatorsLog.Printf("Workflow name validated successfully: %s", s)
return nil
}

// ValidateWorkflowIntent checks if the provided workflow intent is valid.
// It ensures the intent has meaningful content with at least 20 characters
// and is not just whitespace.
func ValidateWorkflowIntent(s string) error {
validatorsLog.Printf("Validating workflow intent: length=%d", len(s))
trimmed := strings.TrimSpace(s)
if len(trimmed) == 0 {
validatorsLog.Print("Workflow intent validation failed: empty content")
return errors.New("workflow instructions cannot be empty")
}
if len(trimmed) < 20 {
validatorsLog.Printf("Workflow intent validation failed: too short (%d chars)", len(trimmed))
return errors.New("please provide at least 20 characters of instructions")
}
validatorsLog.Printf("Workflow intent validated successfully: %d chars", len(trimmed))
return nil
}
15 changes: 13 additions & 2 deletions pkg/gitutil/gitutil.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,29 @@
package gitutil

import "strings"
import (
"strings"

"github.com/githubnext/gh-aw/pkg/logger"
)

var log = logger.New("gitutil:gitutil")

// IsAuthError checks if an error message indicates an authentication issue.
// This is used to detect when GitHub API calls fail due to missing or invalid credentials.
func IsAuthError(errMsg string) bool {
log.Printf("Checking if error is auth-related: %s", errMsg)
lowerMsg := strings.ToLower(errMsg)
return strings.Contains(lowerMsg, "gh_token") ||
isAuth := strings.Contains(lowerMsg, "gh_token") ||
strings.Contains(lowerMsg, "github_token") ||
strings.Contains(lowerMsg, "authentication") ||
strings.Contains(lowerMsg, "not logged into") ||
strings.Contains(lowerMsg, "unauthorized") ||
strings.Contains(lowerMsg, "forbidden") ||
strings.Contains(lowerMsg, "permission denied")
if isAuth {
log.Print("Detected authentication error")
}
return isAuth
}

// IsHexString checks if a string contains only hexadecimal characters.
Expand Down
11 changes: 11 additions & 0 deletions pkg/repoutil/repoutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,33 +4,44 @@ package repoutil
import (
"fmt"
"strings"

"github.com/githubnext/gh-aw/pkg/logger"
)

var log = logger.New("repoutil:repoutil")

// SplitRepoSlug splits a repository slug (owner/repo) into owner and repo parts.
// Returns an error if the slug format is invalid.
func SplitRepoSlug(slug string) (owner, repo string, err error) {
log.Printf("Splitting repo slug: %s", slug)
parts := strings.Split(slug, "/")
if len(parts) != 2 || parts[0] == "" || parts[1] == "" {
log.Printf("Invalid repo slug format: %s", slug)
return "", "", fmt.Errorf("invalid repo format: %s", slug)
}
log.Printf("Split result: owner=%s, repo=%s", parts[0], parts[1])
return parts[0], parts[1], nil
}

// ParseGitHubURL extracts the owner and repo from a GitHub URL.
// Handles both SSH (git@github.com:owner/repo.git) and HTTPS (https://github.com/owner/repo.git) formats.
func ParseGitHubURL(url string) (owner, repo string, err error) {
log.Printf("Parsing GitHub URL: %s", url)
var repoPath string

// SSH format: git@github.com:owner/repo.git
if strings.HasPrefix(url, "git@github.com:") {
repoPath = strings.TrimPrefix(url, "git@github.com:")
log.Printf("Detected SSH format, extracted path: %s", repoPath)
} else if strings.Contains(url, "github.com/") {
// HTTPS format: https://github.com/owner/repo.git
parts := strings.Split(url, "github.com/")
if len(parts) >= 2 {
repoPath = parts[1]
log.Printf("Detected HTTPS format, extracted path: %s", repoPath)
}
} else {
log.Printf("URL does not match known GitHub formats: %s", url)
return "", "", fmt.Errorf("URL does not appear to be a GitHub repository: %s", url)
}

Expand Down
12 changes: 12 additions & 0 deletions pkg/workflow/github_tool_to_toolset.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@ package workflow
import (
_ "embed"
"encoding/json"

"github.com/githubnext/gh-aw/pkg/logger"
)

var githubToolToToolsetLog = logger.New("workflow:github_tool_to_toolset")

//go:embed data/github_tool_to_toolset.json
var githubToolToToolsetJSON []byte

Expand All @@ -23,7 +27,10 @@ func init() {
// ValidateGitHubToolsAgainstToolsets validates that all allowed GitHub tools have their
// corresponding toolsets enabled in the configuration
func ValidateGitHubToolsAgainstToolsets(allowedTools []string, enabledToolsets []string) error {
githubToolToToolsetLog.Printf("Validating GitHub tools against toolsets: allowed_tools=%d, enabled_toolsets=%d", len(allowedTools), len(enabledToolsets))

if len(allowedTools) == 0 {
githubToolToToolsetLog.Print("No tools to validate, skipping")
// No specific tools restricted, validation not needed
return nil
}
Expand All @@ -33,26 +40,31 @@ func ValidateGitHubToolsAgainstToolsets(allowedTools []string, enabledToolsets [
for _, toolset := range enabledToolsets {
enabledSet[toolset] = true
}
githubToolToToolsetLog.Printf("Enabled toolsets: %v", enabledToolsets)

// Track missing toolsets and which tools need them
missingToolsets := make(map[string][]string) // toolset -> list of tools that need it

for _, tool := range allowedTools {
requiredToolset, exists := GitHubToolToToolsetMap[tool]
if !exists {
githubToolToToolsetLog.Printf("Tool %s not found in mapping, skipping validation", tool)
// Tool not in our mapping - this could be a new tool or a typo
// We'll skip validation for unknown tools to avoid false positives
continue
}

if !enabledSet[requiredToolset] {
githubToolToToolsetLog.Printf("Tool %s requires missing toolset: %s", tool, requiredToolset)
missingToolsets[requiredToolset] = append(missingToolsets[requiredToolset], tool)
}
}

if len(missingToolsets) > 0 {
githubToolToToolsetLog.Printf("Validation failed: missing %d toolsets", len(missingToolsets))
return NewGitHubToolsetValidationError(missingToolsets)
}

githubToolToToolsetLog.Print("Validation successful: all tools have required toolsets")
return nil
}
11 changes: 10 additions & 1 deletion pkg/workflow/prompts.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,12 @@ import (
// hasPlaywrightTool checks if the playwright tool is enabled in the tools configuration
func hasPlaywrightTool(parsedTools *Tools) bool {
if parsedTools == nil {
log.Print("Checking for Playwright tool: no parsed tools provided")
return false
}
return parsedTools.Playwright != nil
hasPlaywright := parsedTools.Playwright != nil
log.Printf("Playwright tool enabled: %v", hasPlaywright)
return hasPlaywright
}

// ============================================================================
Expand All @@ -32,22 +35,28 @@ func hasPlaywrightTool(parsedTools *Tools) bool {

// hasCommentRelatedTriggers checks if the workflow has any comment-related event triggers
func (c *Compiler) hasCommentRelatedTriggers(data *WorkflowData) bool {
log.Printf("Checking for comment-related triggers: command_count=%d, on=%s", len(data.Command), data.On)

// Check for command trigger (which expands to comment events)
if len(data.Command) > 0 {
log.Print("Found command trigger, enabling comment-related features")
return true
}

if data.On == "" {
log.Print("No 'on' trigger defined")
return false
}

// Check for comment-related event types in the "on" configuration
commentEvents := []string{"issue_comment", "pull_request_review_comment", "pull_request_review"}
for _, event := range commentEvents {
if strings.Contains(data.On, event) {
log.Printf("Found comment-related event trigger: %s", event)
return true
}
}

log.Print("No comment-related triggers found")
return false
}