feat: add lint check to bd preflight --check (bd-lfak.3)
- Add Skipped field to CheckResult for graceful handling of missing tools - Implement runLintCheck() that runs golangci-lint run ./... - Skip lint check gracefully if golangci-lint not in PATH - Update summary to show skipped count separately - Add test for skipped state 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> Executed-By: beads/crew/dave Rig: beads Role: crew
This commit is contained in:
committed by
Steve Yegge
parent
64c65974f0
commit
1b432ad9b6
+47
-4
@@ -14,6 +14,7 @@ import (
|
|||||||
type CheckResult struct {
|
type CheckResult struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Passed bool `json:"passed"`
|
Passed bool `json:"passed"`
|
||||||
|
Skipped bool `json:"skipped,omitempty"`
|
||||||
Output string `json:"output,omitempty"`
|
Output string `json:"output,omitempty"`
|
||||||
Command string `json:"command"`
|
Command string `json:"command"`
|
||||||
}
|
}
|
||||||
@@ -89,18 +90,29 @@ func runChecks(jsonOutput bool) {
|
|||||||
testResult := runTestCheck()
|
testResult := runTestCheck()
|
||||||
results = append(results, testResult)
|
results = append(results, testResult)
|
||||||
|
|
||||||
|
// Run lint check
|
||||||
|
lintResult := runLintCheck()
|
||||||
|
results = append(results, lintResult)
|
||||||
|
|
||||||
// Calculate overall result
|
// Calculate overall result
|
||||||
allPassed := true
|
allPassed := true
|
||||||
passCount := 0
|
passCount := 0
|
||||||
|
skipCount := 0
|
||||||
for _, r := range results {
|
for _, r := range results {
|
||||||
if r.Passed {
|
if r.Skipped {
|
||||||
|
skipCount++
|
||||||
|
} else if r.Passed {
|
||||||
passCount++
|
passCount++
|
||||||
} else {
|
} else {
|
||||||
allPassed = false
|
allPassed = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
summary := fmt.Sprintf("%d/%d checks passed", passCount, len(results))
|
runCount := len(results) - skipCount
|
||||||
|
summary := fmt.Sprintf("%d/%d checks passed", passCount, runCount)
|
||||||
|
if skipCount > 0 {
|
||||||
|
summary += fmt.Sprintf(" (%d skipped)", skipCount)
|
||||||
|
}
|
||||||
|
|
||||||
if jsonOutput {
|
if jsonOutput {
|
||||||
result := PreflightResult{
|
result := PreflightResult{
|
||||||
@@ -114,13 +126,18 @@ func runChecks(jsonOutput bool) {
|
|||||||
} else {
|
} else {
|
||||||
// Human-readable output
|
// Human-readable output
|
||||||
for _, r := range results {
|
for _, r := range results {
|
||||||
if r.Passed {
|
if r.Skipped {
|
||||||
|
fmt.Printf("⚠ %s (skipped)\n", r.Name)
|
||||||
|
} else if r.Passed {
|
||||||
fmt.Printf("✓ %s\n", r.Name)
|
fmt.Printf("✓ %s\n", r.Name)
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("✗ %s\n", r.Name)
|
fmt.Printf("✗ %s\n", r.Name)
|
||||||
}
|
}
|
||||||
fmt.Printf(" Command: %s\n", r.Command)
|
fmt.Printf(" Command: %s\n", r.Command)
|
||||||
if !r.Passed && r.Output != "" {
|
if r.Skipped && r.Output != "" {
|
||||||
|
// Show skip reason
|
||||||
|
fmt.Printf(" Reason: %s\n", r.Output)
|
||||||
|
} else if !r.Passed && r.Output != "" {
|
||||||
// Truncate output for terminal display
|
// Truncate output for terminal display
|
||||||
output := truncateOutput(r.Output, 500)
|
output := truncateOutput(r.Output, 500)
|
||||||
fmt.Printf(" Output:\n")
|
fmt.Printf(" Output:\n")
|
||||||
@@ -152,6 +169,32 @@ func runTestCheck() CheckResult {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// runLintCheck runs golangci-lint and returns the result.
|
||||||
|
func runLintCheck() CheckResult {
|
||||||
|
command := "golangci-lint run ./..."
|
||||||
|
|
||||||
|
// Check if golangci-lint is available
|
||||||
|
if _, err := exec.LookPath("golangci-lint"); err != nil {
|
||||||
|
return CheckResult{
|
||||||
|
Name: "Lint passes",
|
||||||
|
Passed: false,
|
||||||
|
Skipped: true,
|
||||||
|
Output: "golangci-lint not found in PATH",
|
||||||
|
Command: command,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := exec.Command("golangci-lint", "run", "./...")
|
||||||
|
output, err := cmd.CombinedOutput()
|
||||||
|
|
||||||
|
return CheckResult{
|
||||||
|
Name: "Lint passes",
|
||||||
|
Passed: err == nil,
|
||||||
|
Output: string(output),
|
||||||
|
Command: command,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// truncateOutput truncates output to maxLen characters, adding ellipsis if truncated.
|
// truncateOutput truncates output to maxLen characters, adding ellipsis if truncated.
|
||||||
func truncateOutput(s string, maxLen int) string {
|
func truncateOutput(s string, maxLen int) string {
|
||||||
if len(s) <= maxLen {
|
if len(s) <= maxLen {
|
||||||
|
|||||||
@@ -100,6 +100,32 @@ func TestPreflightResult_SomeFailed(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPreflightResult_WithSkipped(t *testing.T) {
|
||||||
|
results := PreflightResult{
|
||||||
|
Checks: []CheckResult{
|
||||||
|
{Name: "Tests pass", Passed: true, Command: "go test ./..."},
|
||||||
|
{Name: "Lint passes", Passed: false, Skipped: true, Command: "golangci-lint run", Output: "not installed"},
|
||||||
|
},
|
||||||
|
Passed: true,
|
||||||
|
Summary: "1/1 checks passed (1 skipped)",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skipped checks don't count as failures
|
||||||
|
if !results.Passed {
|
||||||
|
t.Error("Expected result to pass (skipped doesn't count as failure)")
|
||||||
|
}
|
||||||
|
|
||||||
|
skipCount := 0
|
||||||
|
for _, c := range results.Checks {
|
||||||
|
if c.Skipped {
|
||||||
|
skipCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if skipCount != 1 {
|
||||||
|
t.Errorf("Expected 1 skipped, got %d", skipCount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestTruncateOutput(t *testing.T) {
|
func TestTruncateOutput(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
|||||||
Reference in New Issue
Block a user