← back to jrschumacher__go-actions

Function bodies 88 total

All specs Real LLM only Function bodies
cmd.init function · go · L36-L41 (6 LOC)
cli/cmd/check.go
func init() {
	rootCmd.AddCommand(checkCmd)
	checkCmd.Flags().StringVar(&formatFlag, "format", "auto", "output format (auto, json, text)")
	checkCmd.Flags().BoolVar(&githubCommentFlag, "github-comment", false, "post results as PR comment (auto-enabled in GitHub Actions PR)")
	checkCmd.Flags().BoolVar(&noGithubComment, "no-github-comment", false, "disable PR comment even in GitHub Actions")
}
cmd.runCheck function · go · L43-L137 (95 LOC)
cli/cmd/check.go
func runCheck(cmd *cobra.Command, args []string) error {
	// Load config
	configPath := cfgFile
	if configPath == "" {
		var err error
		configPath, err = config.FindConfigFile()
		if err != nil {
			return fmt.Errorf("failed to find config file: %w", err)
		}
	}

	cfg, err := config.Load(configPath)
	if err != nil {
		return fmt.Errorf("failed to load config: %w", err)
	}

	// Determine output format
	format := formatFlag
	if format == "auto" {
		if cfg.Output.Format != "" && cfg.Output.Format != "auto" {
			format = cfg.Output.Format
		} else {
			format = "text"
		}
	}

	// Create formatter
	formatter := output.NewFormatter(format, os.Stdout)

	// Create runner
	r := runner.New(cfg)

	// Run checks
	var results *output.Results
	if len(args) == 0 {
		// Run all enabled checks
		checks := getEnabledCheckNames(cfg)
		if len(checks) == 0 {
			fmt.Fprintln(os.Stderr, "No checks enabled in configuration")
			return nil
		}

		if err := formatter.PrintProgress(checks); err != nil {
			retu
cmd.maybePostGitHubComment function · go · L140-L179 (40 LOC)
cli/cmd/check.go
func maybePostGitHubComment(results *output.Results) error {
	// Check if explicitly disabled
	if noGithubComment {
		return nil
	}

	// Detect GitHub Actions environment
	ghCtx := github.DetectGitHub()

	// Determine if we should post a comment
	shouldComment := githubCommentFlag // Explicit flag
	if !shouldComment && ghCtx != nil {
		// Auto-enable in GitHub Actions PR context with token
		shouldComment = ghCtx.EventName == "pull_request" &&
			ghCtx.Token != "" &&
			ghCtx.PRNumber > 0
	}

	if !shouldComment {
		return nil
	}

	// Validate we have what we need
	if ghCtx == nil {
		return fmt.Errorf("--github-comment requires GitHub Actions environment")
	}
	if ghCtx.Token == "" {
		return fmt.Errorf("GITHUB_TOKEN not set")
	}
	if ghCtx.PRNumber == 0 {
		return fmt.Errorf("not a pull request event")
	}

	// Format and post comment
	client := github.NewClient(ghCtx)
	commentBody := github.FormatComment(results)

	fmt.Fprintln(os.Stderr, "Posting results to PR comment...")
	return clie
cmd.getEnabledCheckNames function · go · L181-L198 (18 LOC)
cli/cmd/check.go
func getEnabledCheckNames(cfg *config.Config) []string {
	var checks []string

	if cfg.CI.Test.Enabled != nil && *cfg.CI.Test.Enabled {
		checks = append(checks, "test")
	}
	if cfg.CI.Lint.Enabled {
		checks = append(checks, "lint")
	}
	if cfg.CI.Security.Enabled {
		checks = append(checks, "security")
	}
	if cfg.CI.Benchmark.Enabled {
		checks = append(checks, "benchmark")
	}

	return checks
}
cmd.runInit function · go · L25-L60 (36 LOC)
cli/cmd/init.go
func runInit(cmd *cobra.Command, args []string) error {
	// Check if config already exists
	configPath := ".go-actions.yaml"
	if _, err := os.Stat(configPath); err == nil {
		return fmt.Errorf("config file already exists: %s", configPath)
	}

	// Create default config
	cfg := config.DefaultConfig()

	// Detect project settings
	if err := detectProjectSettings(cfg); err != nil {
		fmt.Fprintf(os.Stderr, "Warning: %v\n", err)
	}

	// Save config
	if err := config.Save(cfg, configPath); err != nil {
		return fmt.Errorf("failed to save config: %w", err)
	}

	fmt.Println("Created .go-actions.yaml with recommended settings")

	// Print what was detected
	if cfg.CI.Go.VersionFile != "" {
		fmt.Printf("Detected: Go version file: %s\n", cfg.CI.Go.VersionFile)
	}

	// Check for golangci-lint config
	if _, err := os.Stat(".golangci.yml"); err == nil {
		fmt.Println("Detected: golangci-lint config present")
	} else if _, err := os.Stat(".golangci.yaml"); err == nil {
		fmt.Println("Detected: golan
cmd.detectProjectSettings function · go · L62-L100 (39 LOC)
cli/cmd/init.go
func detectProjectSettings(cfg *config.Config) error {
	// Check for go.mod
	if _, err := os.Stat("go.mod"); err == nil {
		cfg.CI.Go.VersionFile = "go.mod"
	}

	// Check for .go-version
	if _, err := os.Stat(".go-version"); err == nil {
		cfg.CI.Go.VersionFile = ".go-version"
	}

	// Note: golangci-lint config detection is handled by the linter itself
	// Lint is enabled by default in the config

	// Look for test files to determine if testing is viable
	hasTests := false
	err := filepath.Walk(".", func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return nil // Skip errors
		}
		if info.IsDir() && (info.Name() == "vendor" || info.Name() == ".git") {
			return filepath.SkipDir
		}
		if filepath.Ext(path) == ".go" && filepath.Base(path) != "go.mod" {
			// Check if it's a test file
			if len(filepath.Base(path)) > 8 && filepath.Base(path)[len(filepath.Base(path))-8:] == "_test.go" {
				hasTests = true
				return filepath.SkipAll
			}
		}
		return nil
	})

	if 
cmd.Execute function · go · L25-L30 (6 LOC)
cli/cmd/root.go
func Execute() {
	if err := rootCmd.Execute(); err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
}
Repobility · severity-and-effort ranking · https://repobility.com
cmd.init function · go · L34-L39 (6 LOC)
cli/cmd/validate.go
func init() {
	rootCmd.AddCommand(validateCmd)
	validateCmd.Flags().BoolVar(&validateFixFlag, "fix", false, "auto-fix issues where possible")
	validateCmd.Flags().StringVar(&validateFormatFlag, "format", "text", "output format (text, json)")
	validateCmd.Flags().BoolVarP(&validateQuietFlag, "quiet", "q", false, "suppress progress output")
}
cmd.runValidate function · go · L41-L85 (45 LOC)
cli/cmd/validate.go
func runValidate(cmd *cobra.Command, args []string) error {
	v := validate.New(validate.Options{
		WorkingDir: ".",
		Fix:        validateFixFlag,
		Quiet:      validateQuietFlag || validateFormatFlag == "json",
	})

	result := v.Validate()

	// JSON output
	if validateFormatFlag == "json" {
		output := map[string]interface{}{
			"valid":    result.IsValid,
			"errors":   result.Errors,
			"warnings": result.Warnings,
		}
		data, _ := json.Marshal(output)
		fmt.Println(string(data))
		if !result.IsValid {
			os.Exit(1)
		}
		return nil
	}

	// Text output
	if result.IsValid {
		fmt.Println("✅ All validations passed!")
		return nil
	}

	fmt.Printf("❌ Validation failed with %d error(s):\n", len(result.Errors))
	for _, err := range result.Errors {
		fmt.Printf("  - %s\n", err)
	}

	if len(result.Warnings) > 0 {
		fmt.Printf("\n⚠️  %d warning(s):\n", len(result.Warnings))
		for _, warn := range result.Warnings {
			fmt.Printf("  - %s\n", warn)
		}
	}

	os.Exit(1)
	return nil
}
benchmark.ParseBenchmarkOutput function · go · L21-L51 (31 LOC)
cli/internal/benchmark/parser.go
func ParseBenchmarkOutput(output string) ([]Result, error) {
	var results []Result
	scanner := bufio.NewScanner(strings.NewReader(output))

	for scanner.Scan() {
		line := scanner.Text()

		// Skip lines that don't match benchmark output format
		if !strings.HasPrefix(line, "Benchmark") {
			continue
		}

		result, err := parseBenchmarkLine(line)
		if err != nil {
			// Log error but continue parsing other lines
			continue
		}

		results = append(results, result)
	}

	if err := scanner.Err(); err != nil {
		return nil, fmt.Errorf("error reading benchmark output: %w", err)
	}

	if len(results) == 0 {
		return nil, fmt.Errorf("no benchmark results found in output")
	}

	return results, nil
}
benchmark.parseBenchmarkLine function · go · L54-L95 (42 LOC)
cli/internal/benchmark/parser.go
func parseBenchmarkLine(line string) (Result, error) {
	matches := benchRegex.FindStringSubmatch(line)
	if len(matches) < 4 {
		return Result{}, fmt.Errorf("invalid benchmark line format: %s", line)
	}

	result := Result{
		Name: matches[1],
	}

	// Parse iterations
	iterations, err := strconv.Atoi(matches[2])
	if err != nil {
		return Result{}, fmt.Errorf("invalid iterations: %w", err)
	}
	result.Iterations = iterations

	// Parse ns/op
	nsPerOp, err := strconv.ParseFloat(matches[3], 64)
	if err != nil {
		return Result{}, fmt.Errorf("invalid ns/op: %w", err)
	}
	result.NsPerOp = nsPerOp

	// Parse optional B/op (bytes per operation)
	if len(matches) > 4 && matches[4] != "" {
		bytesPerOp, err := strconv.ParseFloat(matches[4], 64)
		if err == nil {
			result.BytesPerOp = int64(bytesPerOp)
		}
	}

	// Parse optional allocs/op (allocations per operation)
	if len(matches) > 5 && matches[5] != "" {
		allocsPerOp, err := strconv.ParseFloat(matches[5], 64)
		if err == nil {
			result.Allocs
benchmark.NewRunner function · go · L35-L47 (13 LOC)
cli/internal/benchmark/runner.go
func NewRunner(opts Options) (*Runner, error) {
	if err := validateBenchmarkArgs(opts.BenchmarkArgs); err != nil {
		return nil, err
	}

	args := parseBenchmarkArgs(opts.BenchmarkArgs)

	return &Runner{
		workingDir: opts.WorkingDirectory,
		args:       args,
		count:      opts.BenchmarkCount,
	}, nil
}
benchmark.Runner.Run method · go · L50-L82 (33 LOC)
cli/internal/benchmark/runner.go
func (r *Runner) Run() (*Output, error) {
	start := time.Now()
	output := &Output{
		RunCount: r.count,
		Success:  true,
	}

	// Collect all benchmark results across runs
	allResults := make(map[string][]Result)

	for i := 1; i <= r.count; i++ {
		results, err := r.runSingleBenchmark()
		if err != nil {
			output.Success = false
			output.Error = fmt.Sprintf("benchmark run %d/%d failed: %v", i, r.count, err)
			return output, err
		}

		// Group results by benchmark name
		for _, result := range results {
			allResults[result.Name] = append(allResults[result.Name], result)
		}
	}

	// Calculate statistics for each benchmark
	for name, results := range allResults {
		stats := calculateStats(name, results)
		output.Results = append(output.Results, stats)
	}

	output.Duration = time.Since(start)
	return output, nil
}
benchmark.Runner.runSingleBenchmark method · go · L85-L111 (27 LOC)
cli/internal/benchmark/runner.go
func (r *Runner) runSingleBenchmark() ([]Result, error) {
	// Build command: go test -bench <args> ./...
	cmdArgs := append([]string{"test"}, r.args...)
	cmdArgs = append(cmdArgs, "./...")

	cmd := exec.Command("go", cmdArgs...)
	if r.workingDir != "" {
		cmd.Dir = r.workingDir
	}

	var stdout, stderr bytes.Buffer
	cmd.Stdout = &stdout
	cmd.Stderr = &stderr

	err := cmd.Run()
	if err != nil {
		return nil, fmt.Errorf("go test failed: %w\nstderr: %s", err, stderr.String())
	}

	// Parse benchmark output
	results, err := ParseBenchmarkOutput(stdout.String())
	if err != nil {
		return nil, fmt.Errorf("failed to parse benchmark output: %w", err)
	}

	return results, nil
}
benchmark.validateBenchmarkArgs function · go · L114-L121 (8 LOC)
cli/internal/benchmark/runner.go
func validateBenchmarkArgs(args string) error {
	for _, pattern := range dangerousPatterns {
		if pattern.MatchString(args) {
			return fmt.Errorf("invalid benchmark arguments: contains potentially dangerous characters: %s", args)
		}
	}
	return nil
}
If a scraper extracted this row, it came from Repobility (https://repobility.com)
benchmark.parseBenchmarkArgs function · go · L125-L153 (29 LOC)
cli/internal/benchmark/runner.go
func parseBenchmarkArgs(argsString string) []string {
	var args []string
	var current strings.Builder
	inQuote := false
	var quoteChar rune

	for _, char := range argsString {
		if (char == '"' || char == '\'') && !inQuote {
			inQuote = true
			quoteChar = char
		} else if char == quoteChar && inQuote {
			inQuote = false
			quoteChar = 0
		} else if char == ' ' && !inQuote {
			if current.Len() > 0 {
				args = append(args, current.String())
				current.Reset()
			}
		} else {
			current.WriteRune(char)
		}
	}

	if current.Len() > 0 {
		args = append(args, current.String())
	}

	return args
}
benchmark.calculateStats function · go · L156-L198 (43 LOC)
cli/internal/benchmark/runner.go
func calculateStats(name string, results []Result) Stats {
	stats := Stats{
		Name: name,
		Runs: results,
		RunCount: len(results),
	}

	if len(results) == 0 {
		return stats
	}

	// Calculate min, max, and mean
	sum := 0.0
	stats.Min = results[0].NsPerOp
	stats.Max = results[0].NsPerOp

	for _, result := range results {
		nsPerOp := result.NsPerOp
		sum += nsPerOp

		if nsPerOp < stats.Min {
			stats.Min = nsPerOp
		}
		if nsPerOp > stats.Max {
			stats.Max = nsPerOp
		}
	}

	stats.Mean = sum / float64(len(results))

	// Calculate standard deviation
	if len(results) > 1 {
		varianceSum := 0.0
		for _, result := range results {
			diff := result.NsPerOp - stats.Mean
			varianceSum += diff * diff
		}
		variance := varianceSum / float64(len(results))
		stats.StdDev = math.Sqrt(variance)
	}

	return stats
}
benchmark.RunBenchmarks function · go · L201-L212 (12 LOC)
cli/internal/benchmark/runner.go
func RunBenchmarks(dir string, args string, count int) (*Output, error) {
	runner, err := NewRunner(Options{
		WorkingDirectory: dir,
		BenchmarkArgs:    args,
		BenchmarkCount:   count,
	})
	if err != nil {
		return nil, err
	}

	return runner.Run()
}
config.Load function · go · L12-L48 (37 LOC)
cli/internal/config/loader.go
func Load(path string) (*Config, error) {
	// Start with defaults
	cfg := DefaultConfig()

	// If no config file exists, return defaults
	if _, err := os.Stat(path); os.IsNotExist(err) {
		return cfg, nil
	}

	// Read the file
	data, err := os.ReadFile(path)
	if err != nil {
		return nil, fmt.Errorf("failed to read config file: %w", err)
	}

	// Parse YAML
	if err := yaml.Unmarshal(data, cfg); err != nil {
		return nil, fmt.Errorf("failed to parse config file: %w", err)
	}

	// Validate version
	if cfg.Version != 1 {
		return nil, fmt.Errorf("unsupported config version: %d (expected 1)", cfg.Version)
	}

	// Apply defaults for unset boolean pointers
	if cfg.CI.Test.Enabled == nil {
		trueVal := true
		cfg.CI.Test.Enabled = &trueVal
	}
	if cfg.CI.Test.Coverage.Enabled == nil {
		trueVal := true
		cfg.CI.Test.Coverage.Enabled = &trueVal
	}

	return cfg, nil
}
config.FindConfigFile function · go · L51-L75 (25 LOC)
cli/internal/config/loader.go
func FindConfigFile() (string, error) {
	cwd, err := os.Getwd()
	if err != nil {
		return "", fmt.Errorf("failed to get current directory: %w", err)
	}

	// Search up the directory tree
	dir := cwd
	for {
		configPath := filepath.Join(dir, ".go-actions.yaml")
		if _, err := os.Stat(configPath); err == nil {
			return configPath, nil
		}

		parent := filepath.Dir(dir)
		if parent == dir {
			// Reached root without finding config
			break
		}
		dir = parent
	}

	// No config found, return default path
	return filepath.Join(cwd, ".go-actions.yaml"), nil
}
config.Save function · go · L78-L89 (12 LOC)
cli/internal/config/loader.go
func Save(cfg *Config, path string) error {
	data, err := yaml.Marshal(cfg)
	if err != nil {
		return fmt.Errorf("failed to marshal config: %w", err)
	}

	if err := os.WriteFile(path, data, 0644); err != nil {
		return fmt.Errorf("failed to write config file: %w", err)
	}

	return nil
}
config.DefaultConfig function · go · L105-L140 (36 LOC)
cli/internal/config/types.go
func DefaultConfig() *Config {
	trueVal := true
	return &Config{
		Version: 1,
		CI: CIConfig{
			Test: TestConfig{
				Enabled: &trueVal,
				Args:    "-v -race ./...",
				Coverage: CoverageConfig{
					Enabled:   &trueVal,
					Threshold: 80.0,
				},
			},
			Lint: LintConfig{
				Enabled: true,
				Version: "v2.0.2",
				Args:    "run ./...",
			},
			Security: SecurityConfig{
				Enabled: true,
				Version: "latest",
				Args:    "./...",
				FailOn:  "high",
			},
			Benchmark: BenchmarkConfig{
				Enabled: false,
				Args:    "-bench=. -benchmem",
				Count:   5,
			},
		},
		Output: OutputConfig{
			Format:    "auto",
			Verbosity: "normal",
		},
	}
}
coverage.validateCoverageFileName function · go · L24-L38 (15 LOC)
cli/internal/coverage/extractor.go
func validateCoverageFileName(fileName string) error {
	// Only allow alphanumeric, dots, hyphens, and underscores
	safePattern := regexp.MustCompile(`^[\w.-]+$`)

	// Prevent path traversal
	if strings.Contains(fileName, "..") || strings.Contains(fileName, "/") || strings.Contains(fileName, "\\") {
		return fmt.Errorf("invalid coverage file name: %s. Path separators not allowed", fileName)
	}

	if !safePattern.MatchString(fileName) {
		return fmt.Errorf("invalid coverage file name: %s. Only alphanumeric characters, dots, hyphens, and underscores are allowed", fileName)
	}

	return nil
}
About: code-quality intelligence by Repobility · https://repobility.com
coverage.ExtractCoverage function · go · L41-L107 (67 LOC)
cli/internal/coverage/extractor.go
func ExtractCoverage(coverageFile string, threshold float64) (*CoverageResult, error) {
	// Validate coverage file name
	fileName := filepath.Base(coverageFile)
	if err := validateCoverageFileName(fileName); err != nil {
		return &CoverageResult{
			Percentage: 0,
			Threshold:  threshold,
			MeetsThreshold: false,
			Error:      err.Error(),
		}, err
	}

	// Check if coverage file exists
	if _, err := os.Stat(coverageFile); os.IsNotExist(err) {
		return &CoverageResult{
			Percentage: 0,
			Threshold:  threshold,
			MeetsThreshold: false,
			Error:      "coverage file not found",
		}, fmt.Errorf("coverage file not found: %s", coverageFile)
	}

	// Get absolute path for working directory
	workingDir := filepath.Dir(coverageFile)

	// Execute go tool cover with safe arguments
	cmd := exec.Command("go", "tool", "cover", fmt.Sprintf("-func=%s", fileName))
	cmd.Dir = workingDir

	output, err := cmd.CombinedOutput()
	if err != nil {
		errMsg := fmt.Sprintf("go tool cover failed: %v", err)
	
coverage.extractCoverageFromOutput function · go · L111-L139 (29 LOC)
cli/internal/coverage/extractor.go
func extractCoverageFromOutput(output string) (float64, error) {
	if output == "" {
		return 0, fmt.Errorf("empty coverage output")
	}

	scanner := bufio.NewScanner(strings.NewReader(output))
	for scanner.Scan() {
		line := scanner.Text()

		// Look for the line containing "total:"
		if strings.Contains(line, "total:") {
			// Extract the last field which should be the percentage
			fields := strings.Fields(line)
			if len(fields) == 0 {
				continue
			}

			// Get the last field (percentage)
			percentageStr := fields[len(fields)-1]
			return parseCoveragePercentage(percentageStr)
		}
	}

	if err := scanner.Err(); err != nil {
		return 0, fmt.Errorf("error reading coverage output: %w", err)
	}

	return 0, fmt.Errorf("total coverage line not found in output")
}
coverage.parseCoveragePercentage function · go · L142-L159 (18 LOC)
cli/internal/coverage/extractor.go
func parseCoveragePercentage(coverage string) (float64, error) {
	// Remove the % sign if present
	coverage = strings.TrimSpace(coverage)
	coverage = strings.TrimSuffix(coverage, "%")

	// Parse as float
	percentage, err := strconv.ParseFloat(coverage, 64)
	if err != nil {
		return 0, fmt.Errorf("failed to parse coverage percentage '%s': %w", coverage, err)
	}

	// Validate range
	if percentage < 0 || percentage > 100 {
		return 0, fmt.Errorf("invalid coverage percentage: %.2f (must be between 0 and 100)", percentage)
	}

	return percentage, nil
}
github.Client.ListComments method · go · L44-L71 (28 LOC)
cli/internal/github/client.go
func (c *Client) ListComments(owner, repo string, prNumber int) ([]Comment, error) {
	url := fmt.Sprintf("%s/repos/%s/%s/issues/%d/comments", c.apiURL, owner, repo, prNumber)

	req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, url, nil)
	if err != nil {
		return nil, fmt.Errorf("failed to create request: %w", err)
	}

	c.setHeaders(req)

	resp, err := c.client.Do(req)
	if err != nil {
		return nil, fmt.Errorf("failed to list comments: %w", err)
	}
	defer func() { _ = resp.Body.Close() }()

	if resp.StatusCode != http.StatusOK {
		body, _ := io.ReadAll(resp.Body)
		return nil, fmt.Errorf("GitHub API returned status %d: %s", resp.StatusCode, string(body))
	}

	var comments []Comment
	if err := json.NewDecoder(resp.Body).Decode(&comments); err != nil {
		return nil, fmt.Errorf("failed to decode response: %w", err)
	}

	return comments, nil
}
github.Client.CreateComment method · go · L74-L102 (29 LOC)
cli/internal/github/client.go
func (c *Client) CreateComment(owner, repo string, prNumber int, body string) error {
	url := fmt.Sprintf("%s/repos/%s/%s/issues/%d/comments", c.apiURL, owner, repo, prNumber)

	payload := map[string]string{"body": body}
	jsonData, err := json.Marshal(payload)
	if err != nil {
		return fmt.Errorf("failed to marshal payload: %w", err)
	}

	req, err := http.NewRequestWithContext(context.Background(), http.MethodPost, url, bytes.NewReader(jsonData))
	if err != nil {
		return fmt.Errorf("failed to create request: %w", err)
	}

	c.setHeaders(req)

	resp, err := c.client.Do(req)
	if err != nil {
		return fmt.Errorf("failed to create comment: %w", err)
	}
	defer func() { _ = resp.Body.Close() }()

	if resp.StatusCode != http.StatusCreated {
		body, _ := io.ReadAll(resp.Body)
		return fmt.Errorf("GitHub API returned status %d: %s", resp.StatusCode, string(body))
	}

	return nil
}
github.Client.UpdateComment method · go · L105-L133 (29 LOC)
cli/internal/github/client.go
func (c *Client) UpdateComment(owner, repo string, commentID int64, body string) error {
	url := fmt.Sprintf("%s/repos/%s/%s/issues/comments/%d", c.apiURL, owner, repo, commentID)

	payload := map[string]string{"body": body}
	jsonData, err := json.Marshal(payload)
	if err != nil {
		return fmt.Errorf("failed to marshal payload: %w", err)
	}

	req, err := http.NewRequestWithContext(context.Background(), http.MethodPatch, url, bytes.NewReader(jsonData))
	if err != nil {
		return fmt.Errorf("failed to create request: %w", err)
	}

	c.setHeaders(req)

	resp, err := c.client.Do(req)
	if err != nil {
		return fmt.Errorf("failed to update comment: %w", err)
	}
	defer func() { _ = resp.Body.Close() }()

	if resp.StatusCode != http.StatusOK {
		body, _ := io.ReadAll(resp.Body)
		return fmt.Errorf("GitHub API returned status %d: %s", resp.StatusCode, string(body))
	}

	return nil
}
github.Client.UpsertComment method · go · L136-L151 (16 LOC)
cli/internal/github/client.go
func (c *Client) UpsertComment(owner, repo string, prNumber int, body, marker string) error {
	comments, err := c.ListComments(owner, repo, prNumber)
	if err != nil {
		return fmt.Errorf("failed to list comments: %w", err)
	}

	// Find existing bot comment with marker
	for _, comment := range comments {
		if comment.User.Type == "Bot" && strings.Contains(comment.Body, marker) {
			return c.UpdateComment(owner, repo, comment.ID, body)
		}
	}

	// No existing comment found, create new
	return c.CreateComment(owner, repo, prNumber, body)
}
github.Client.setHeaders method · go · L154-L160 (7 LOC)
cli/internal/github/client.go
func (c *Client) setHeaders(req *http.Request) {
	req.Header.Set("Accept", "application/vnd.github.v3+json")
	req.Header.Set("Content-Type", "application/json")
	if c.token != "" {
		req.Header.Set("Authorization", "Bearer "+c.token)
	}
}
Want fix-PRs on findings? Install Repobility's GitHub App · github.com/apps/repobility-bot
github.titleCase function · go · L21-L26 (6 LOC)
cli/internal/github/comment.go
func titleCase(s string) string {
	if s == "" {
		return s
	}
	return strings.ToUpper(s[:1]) + s[1:]
}
github.formatStatusLine function · go · L62-L122 (61 LOC)
cli/internal/github/comment.go
func formatStatusLine(check output.CheckResult) string {
	var line string

	switch check.Name {
	case "test":
		icon := "✅"
		if check.Status == "fail" || check.Status == "error" {
			icon = "❌"
		}
		line = fmt.Sprintf("%s **Tests**", icon)
		if check.Coverage > 0 {
			line += fmt.Sprintf(" (%.1f%% coverage)", check.Coverage)
		} else if check.Status == "fail" {
			line += " (failed)"
		}

	case "lint":
		icon := "✅"
		if check.Status == "fail" || check.Status == "error" {
			icon = "🚨"
		}
		line = fmt.Sprintf("%s **Lint**", icon)
		if check.Status == "fail" || check.Status == "error" {
			line += " **- Issues Found!**"
		}

	case "security":
		icon := "✅"
		if check.Status == "fail" || check.Status == "error" {
			icon = "🚨"
		}
		line = fmt.Sprintf("%s **Security**", icon)
		if check.Status == "fail" || check.Status == "error" {
			count := check.Vulnerabilities
			plural := "ies"
			if count == 1 {
				plural = "y"
			}
			line += fmt.Sprintf(" **- %d Vulnerabilit%s Found!**", cou
github.formatDetailsSection function · go · L125-L140 (16 LOC)
cli/internal/github/comment.go
func formatDetailsSection(check output.CheckResult) string {
	var section strings.Builder

	switch check.Name {
	case "test":
		section.WriteString(formatTestDetails(check))
	case "lint":
		section.WriteString(formatLintDetails(check))
	case "security":
		section.WriteString(formatSecurityDetails(check))
	case "benchmark":
		section.WriteString(formatBenchmarkDetails(check))
	}

	return section.String()
}
github.formatTestDetails function · go · L143-L172 (30 LOC)
cli/internal/github/comment.go
func formatTestDetails(check output.CheckResult) string {
	var details strings.Builder

	if check.Status == "pass" {
		if check.Coverage > 0 {
			emoji := getCoverageEmoji(check.Coverage)
			coverageMsg := getCoverageMessage(check.Coverage)

			details.WriteString("<details><summary>Test Details</summary>\n\n")
			details.WriteString(fmt.Sprintf("**Coverage: %.1f%%**\n\n", check.Coverage))
			details.WriteString(fmt.Sprintf("%s %s\n\n", emoji, coverageMsg))
			details.WriteString("</details>\n\n")
		} else {
			details.WriteString("<details><summary>Test Details</summary>\n\n")
			details.WriteString("All tests passed successfully!\n\n")
			details.WriteString("</details>\n\n")
		}
	} else {
		details.WriteString("<details open><summary>Test Issues</summary>\n\n")
		details.WriteString("**Tests failed!**\n\n")
		if check.Message != "" {
			details.WriteString(fmt.Sprintf("**Error:** %s\n\n", check.Message))
		} else {
			details.WriteString("Please check the test logs for details.\n\n"
github.formatLintDetails function · go · L175-L201 (27 LOC)
cli/internal/github/comment.go
func formatLintDetails(check output.CheckResult) string {
	var details strings.Builder

	if check.Status == "pass" {
		details.WriteString("<details><summary>Lint Details</summary>\n\n")
		details.WriteString("Code quality checks passed!\n\n")
		details.WriteString("</details>\n\n")
	} else {
		details.WriteString("<details open><summary>🚨 Lint Issues Found</summary>\n\n")
		details.WriteString("**Code quality checks failed!**")

		if check.Message != "" {
			issuesOutput := check.Message
			// Truncate if too long (GitHub comment limit considerations)
			if len(issuesOutput) > MaxLintOutputLength {
				issuesOutput = issuesOutput[:MaxLintOutputLength] + "\n\n... (truncated, see workflow logs for full output)"
			}
			details.WriteString(fmt.Sprintf("\n\n```\n%s\n```\n", issuesOutput))
		} else {
			details.WriteString("\n\nPlease check the workflow logs for details.")
		}

		details.WriteString("\n\n</details>\n\n")
	}

	return details.String()
}
github.formatSecurityDetails function · go · L204-L232 (29 LOC)
cli/internal/github/comment.go
func formatSecurityDetails(check output.CheckResult) string {
	var details strings.Builder

	if check.Status == "pass" {
		details.WriteString("<details><summary>Security Details</summary>\n\n")
		details.WriteString("🛡️ No known vulnerabilities found in dependencies!\n\n")
		details.WriteString("**Tool:** govulncheck (Go Vulnerability Database)\n\n")
		details.WriteString("</details>\n\n")
	} else {
		count := check.Vulnerabilities
		plural := "ies"
		if count == 1 {
			plural = "y"
		}

		details.WriteString(fmt.Sprintf("<details open><summary>🚨 %d Vulnerabilit%s Found</summary>\n\n", count, plural))
		details.WriteString("**Security scan detected known CVEs in your dependencies!**")

		if check.Message != "" {
			details.WriteString(fmt.Sprintf("\n\n%s\n", check.Message))
		} else {
			details.WriteString("\n\nPlease check the workflow logs for details.")
		}

		details.WriteString("\n\n</details>\n\n")
	}

	return details.String()
}
github.formatBenchmarkDetails function · go · L235-L252 (18 LOC)
cli/internal/github/comment.go
func formatBenchmarkDetails(check output.CheckResult) string {
	var details strings.Builder

	if check.Status == "pass" {
		details.WriteString("<details><summary>Benchmark Details</summary>\n\n")
		details.WriteString("Benchmarks completed successfully!\n\n")
		details.WriteString("</details>\n\n")
	} else {
		details.WriteString("<details open><summary>Benchmark Issues</summary>\n\n")
		details.WriteString("**Benchmarks failed!**\n\n")
		if check.Message != "" {
			details.WriteString(fmt.Sprintf("**Error:** %s\n\n", check.Message))
		}
		details.WriteString("</details>\n\n")
	}

	return details.String()
}
github.getCoverageEmoji function · go · L255-L262 (8 LOC)
cli/internal/github/comment.go
func getCoverageEmoji(coverage float64) string {
	if coverage >= ExcellentCoverageThreshold {
		return "🎉"
	} else if coverage >= GoodCoverageThreshold {
		return "⚠️"
	}
	return "🚨"
}
Repobility · severity-and-effort ranking · https://repobility.com
github.getCoverageMessage function · go · L265-L272 (8 LOC)
cli/internal/github/comment.go
func getCoverageMessage(coverage float64) string {
	if coverage >= ExcellentCoverageThreshold {
		return "Excellent test coverage!"
	} else if coverage >= GoodCoverageThreshold {
		return "Good coverage, consider adding more tests."
	}
	return "Low test coverage detected. Please add more tests."
}
github.formatEmptyComment function · go · L275-L300 (26 LOC)
cli/internal/github/comment.go
func formatEmptyComment() string {
	return `# Go Actions Report

⏳ **Pending**

<details><summary>Details</summary>

No CI jobs have run yet. Results will appear here as jobs complete.

</details>

*🤖 This comment will update automatically as you push changes.*
*Generated by [go-actions](https://github.com/jrschumacher/go-actions)*`
}

// FormatProcessingComment returns a comment for when jobs are running
func FormatProcessingComment() string {
	return `# Go Actions Report

🔄 **Running...**

Validation is in progress. Results will appear here shortly.

*🤖 This comment will update automatically as jobs complete.*
*Generated by [go-actions](https://github.com/jrschumacher/go-actions)*`
}
github.DetectGitHub function · go · L27-L66 (40 LOC)
cli/internal/github/env.go
func DetectGitHub() *GitHubContext {
	// Check if running in GitHub Actions
	if os.Getenv("GITHUB_ACTIONS") != "true" {
		return nil
	}

	ctx := &GitHubContext{
		IsGitHubActions: true,
		EventName:       os.Getenv("GITHUB_EVENT_NAME"),
		Token:           os.Getenv("GITHUB_TOKEN"),
		Repository:      os.Getenv("GITHUB_REPOSITORY"),
		ServerURL:       getEnvOrDefault("GITHUB_SERVER_URL", "https://github.com"),
		APIURL:          getEnvOrDefault("GITHUB_API_URL", "https://api.github.com"),
	}

	// Parse repository into owner and repo
	if ctx.Repository != "" {
		parts := strings.SplitN(ctx.Repository, "/", 2)
		if len(parts) == 2 {
			ctx.Owner = parts[0]
			ctx.Repo = parts[1]
		}
	}

	// Parse PR number from event file
	if eventPath := os.Getenv("GITHUB_EVENT_PATH"); eventPath != "" {
		if prNum, err := parsePRNumberFromEvent(eventPath); err == nil {
			ctx.PRNumber = prNum
		}
	}

	// Parse run ID
	if runIDStr := os.Getenv("GITHUB_RUN_ID"); runIDStr != "" {
		if runID, err := strconv.
github.getEnvOrDefault function · go · L69-L74 (6 LOC)
cli/internal/github/env.go
func getEnvOrDefault(key, defaultValue string) string {
	if val := os.Getenv(key); val != "" {
		return val
	}
	return defaultValue
}
github.parsePRNumberFromEvent function · go · L77-L105 (29 LOC)
cli/internal/github/env.go
func parsePRNumberFromEvent(eventPath string) (int, error) {
	data, err := os.ReadFile(eventPath)
	if err != nil {
		return 0, fmt.Errorf("failed to read event file: %w", err)
	}

	var event struct {
		Number      int `json:"number"`
		PullRequest *struct {
			Number int `json:"number"`
		} `json:"pull_request"`
	}

	if err := json.Unmarshal(data, &event); err != nil {
		return 0, fmt.Errorf("failed to parse event JSON: %w", err)
	}

	// Try .pull_request.number first (for push events referencing a PR)
	if event.PullRequest != nil && event.PullRequest.Number > 0 {
		return event.PullRequest.Number, nil
	}

	// Fall back to .number (for pull_request events)
	if event.Number > 0 {
		return event.Number, nil
	}

	return 0, fmt.Errorf("no PR number found in event")
}
lint.ParseLintOutput function · go · L12-L32 (21 LOC)
cli/internal/lint/formatter.go
func ParseLintOutput(jsonFilePath string) (*Report, error) {
	data, err := os.ReadFile(jsonFilePath)
	if err != nil {
		if os.IsNotExist(err) {
			return nil, fmt.Errorf("lint output file not found: %s", jsonFilePath)
		}
		return nil, fmt.Errorf("failed to read lint output file: %w", err)
	}

	// Handle empty file
	if len(strings.TrimSpace(string(data))) == 0 {
		return &Report{Issues: []Issue{}}, nil
	}

	var report Report
	if err := json.Unmarshal(data, &report); err != nil {
		return nil, fmt.Errorf("failed to parse lint output JSON: %w", err)
	}

	return &report, nil
}
lint.GroupIssuesByLinter function · go · L35-L81 (47 LOC)
cli/internal/lint/formatter.go
func GroupIssuesByLinter(issues []Issue) []LinterGroup {
	grouped := make(map[string]*LinterGroup)

	for _, issue := range issues {
		linterName := issue.FromLinter
		if linterName == "" {
			linterName = "unknown"
		}

		if _, exists := grouped[linterName]; !exists {
			grouped[linterName] = &LinterGroup{
				Name:   linterName,
				Count:  0,
				Issues: []GroupedIssue{},
			}
		}

		group := grouped[linterName]
		group.Count++
		group.Issues = append(group.Issues, GroupedIssue{
			File:    issue.Pos.Filename,
			Line:    issue.Pos.Line,
			Column:  issue.Pos.Column,
			Message: issue.Text,
		})
	}

	// Convert map to slice
	var groups []LinterGroup
	for _, group := range grouped {
		// Sort issues within each group by file, then line
		sort.Slice(group.Issues, func(i, j int) bool {
			if group.Issues[i].File != group.Issues[j].File {
				return group.Issues[i].File < group.Issues[j].File
			}
			return group.Issues[i].Line < group.Issues[j].Line
		})
		groups = append(groups, *group
lint.FormatGroupedIssues function · go · L84-L154 (71 LOC)
cli/internal/lint/formatter.go
func FormatGroupedIssues(groups []LinterGroup, options FormatOptions) string {
	if len(groups) == 0 {
		return ""
	}

	var output strings.Builder
	issuesShown := 0
	lintersShown := 0
	totalLinters := len(groups)

	// Calculate total issues
	totalIssues := 0
	for _, group := range groups {
		totalIssues += group.Count
	}

	for _, group := range groups {
		// Check if we've hit the total issues limit
		if issuesShown >= options.MaxTotalIssues {
			remaining := totalIssues - issuesShown
			remainingLinters := totalLinters - lintersShown
			output.WriteString(fmt.Sprintf("\n*...and %d more issue%s from %d linter%s*\n",
				remaining,
				pluralize(remaining),
				remainingLinters,
				pluralize(remainingLinters)))
			break
		}

		lintersShown++

		// Format linter section
		linterTitle := fmt.Sprintf("**%s** (%d issue%s)", group.Name, group.Count, pluralize(group.Count))

		if options.UseCollapsible {
			// Use collapsible section for each linter
			isOpen := lintersShown == 1 // First lin
If a scraper extracted this row, it came from Repobility (https://repobility.com)
lint.FormatLintOutput function · go · L157-L204 (48 LOC)
cli/internal/lint/formatter.go
func FormatLintOutput(jsonFilePath string, options FormatOptions) (*FormattedOutput, error) {
	report, err := ParseLintOutput(jsonFilePath)
	if err != nil {
		return nil, err
	}

	if len(report.Issues) == 0 {
		return nil, nil
	}

	groups := GroupIssuesByLinter(report.Issues)
	formattedIssues := FormatGroupedIssues(groups, options)

	if formattedIssues == "" {
		return nil, nil
	}

	// Build header with total count
	totalIssues := len(report.Issues)
	var header strings.Builder
	header.WriteString(fmt.Sprintf("### 🚨 Lint Issues Found (%d issue%s)\n\n",
		totalIssues, pluralize(totalIssues)))

	// Add workflow logs link if provided
	if options.WorkflowLogsURL != "" {
		header.WriteString(fmt.Sprintf("[📋 View full logs](%s)\n\n", options.WorkflowLogsURL))
	}

	markdown := header.String() + formattedIssues

	// Determine if output was truncated
	truncated := false
	issueCount := 0
	for _, group := range groups {
		issueCount += min(len(group.Issues), options.MaxIssuesPerLinter)
		if issueC
lint.GetWorkflowLogsURL function · go · L207-L217 (11 LOC)
cli/internal/lint/formatter.go
func GetWorkflowLogsURL() string {
	serverURL := os.Getenv("GITHUB_SERVER_URL")
	repository := os.Getenv("GITHUB_REPOSITORY")
	runID := os.Getenv("GITHUB_RUN_ID")

	if serverURL != "" && repository != "" && runID != "" {
		return fmt.Sprintf("%s/%s/actions/runs/%s", serverURL, repository, runID)
	}

	return ""
}
lint.pluralize function · go · L220-L225 (6 LOC)
cli/internal/lint/formatter.go
func pluralize(count int) string {
	if count == 1 {
		return ""
	}
	return "s"
}
page 1 / 2next ›