fix(doctor): filter bd "Note:" messages from custom types check (#381)
* fix(doctor): filter bd "Note:" messages from custom types check bd outputs "Note: No git repository initialized..." to stdout when running outside a git repo, which was contaminating the custom types parsing and causing false warnings. - Use Output() instead of CombinedOutput() to avoid stderr - Filter out lines starting with "Note:" from stdout Co-Authored-By: Claude <noreply@anthropic.com> * test(doctor): add unit tests for custom types Note: filtering Extract parseConfigOutput helper function and add tests verifying that bd "Note:" informational messages are properly filtered from config output. Tests fail without the fix and pass with it. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: julianknutsen <julianknutsen@users.noreply.github> Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -581,9 +581,10 @@ func (c *CustomTypesCheck) Run(ctx *CheckContext) *CheckResult {
|
||||
}
|
||||
|
||||
// Get current custom types configuration
|
||||
// Use Output() not CombinedOutput() to avoid capturing bd's stderr messages
|
||||
cmd := exec.Command("bd", "config", "get", "types.custom")
|
||||
cmd.Dir = ctx.TownRoot
|
||||
output, err := cmd.CombinedOutput()
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
// If config key doesn't exist, types are not configured
|
||||
c.townRoot = ctx.TownRoot
|
||||
@@ -600,8 +601,8 @@ func (c *CustomTypesCheck) Run(ctx *CheckContext) *CheckResult {
|
||||
}
|
||||
}
|
||||
|
||||
// Parse configured types
|
||||
configuredTypes := strings.TrimSpace(string(output))
|
||||
// Parse configured types, filtering out bd "Note:" messages that may appear in stdout
|
||||
configuredTypes := parseConfigOutput(output)
|
||||
configuredSet := make(map[string]bool)
|
||||
for _, t := range strings.Split(configuredTypes, ",") {
|
||||
configuredSet[strings.TrimSpace(t)] = true
|
||||
@@ -640,6 +641,18 @@ func (c *CustomTypesCheck) Run(ctx *CheckContext) *CheckResult {
|
||||
}
|
||||
}
|
||||
|
||||
// parseConfigOutput extracts the config value from bd output, filtering out
|
||||
// informational messages like "Note: ..." that bd may emit to stdout.
|
||||
func parseConfigOutput(output []byte) string {
|
||||
for _, line := range strings.Split(string(output), "\n") {
|
||||
line = strings.TrimSpace(line)
|
||||
if line != "" && !strings.HasPrefix(line, "Note:") {
|
||||
return line
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Fix registers the missing custom types.
|
||||
func (c *CustomTypesCheck) Fix(ctx *CheckContext) error {
|
||||
cmd := exec.Command("bd", "config", "set", "types.custom", constants.BeadsCustomTypes)
|
||||
|
||||
@@ -3,7 +3,10 @@ package doctor
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/steveyegge/gastown/internal/constants"
|
||||
)
|
||||
|
||||
func TestSessionHookCheck_UsesSessionStartScript(t *testing.T) {
|
||||
@@ -224,3 +227,90 @@ func TestSessionHookCheck_Run(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestParseConfigOutput(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "simple value",
|
||||
input: "agent,role,rig,convoy,slot\n",
|
||||
want: "agent,role,rig,convoy,slot",
|
||||
},
|
||||
{
|
||||
name: "value with trailing newlines",
|
||||
input: "agent,role,rig,convoy,slot\n\n",
|
||||
want: "agent,role,rig,convoy,slot",
|
||||
},
|
||||
{
|
||||
name: "Note prefix filtered",
|
||||
input: "Note: No git repository initialized - running without background sync\nagent,role,rig,convoy,slot\n",
|
||||
want: "agent,role,rig,convoy,slot",
|
||||
},
|
||||
{
|
||||
name: "multiple Note prefixes filtered",
|
||||
input: "Note: First note\nNote: Second note\nagent,role,rig,convoy,slot\n",
|
||||
want: "agent,role,rig,convoy,slot",
|
||||
},
|
||||
{
|
||||
name: "empty output",
|
||||
input: "",
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "only whitespace",
|
||||
input: " \n \n",
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "Note with different casing is not filtered",
|
||||
input: "note: lowercase should not match\n",
|
||||
want: "note: lowercase should not match",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := parseConfigOutput([]byte(tt.input))
|
||||
if got != tt.want {
|
||||
t.Errorf("parseConfigOutput() = %q, want %q", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCustomTypesCheck_ParsesOutputWithNotePrefix(t *testing.T) {
|
||||
// This test verifies that CustomTypesCheck correctly parses bd output
|
||||
// that contains "Note:" informational messages before the actual config value.
|
||||
// Without proper filtering, the check would see "Note: ..." as the config value
|
||||
// and incorrectly report all custom types as missing.
|
||||
|
||||
// Test the parsing logic directly - this simulates bd outputting:
|
||||
// "Note: No git repository initialized - running without background sync"
|
||||
// followed by the actual config value
|
||||
output := "Note: No git repository initialized - running without background sync\n" + constants.BeadsCustomTypes + "\n"
|
||||
parsed := parseConfigOutput([]byte(output))
|
||||
|
||||
if parsed != constants.BeadsCustomTypes {
|
||||
t.Errorf("parseConfigOutput failed to filter Note: prefix\ngot: %q\nwant: %q", parsed, constants.BeadsCustomTypes)
|
||||
}
|
||||
|
||||
// Verify that all required types are found in the parsed output
|
||||
configuredSet := make(map[string]bool)
|
||||
for _, typ := range strings.Split(parsed, ",") {
|
||||
configuredSet[strings.TrimSpace(typ)] = true
|
||||
}
|
||||
|
||||
var missing []string
|
||||
for _, required := range constants.BeadsCustomTypesList() {
|
||||
if !configuredSet[required] {
|
||||
missing = append(missing, required)
|
||||
}
|
||||
}
|
||||
|
||||
if len(missing) > 0 {
|
||||
t.Errorf("After parsing, missing types: %v", missing)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user