refactor(msg): single space between keyword and message, improve config suggestions

Tightens keyword formatting (ok/FAIL/hint/etc.) from two spaces to one.
Makes config key suggestions more generous: normalises spaces to
underscores, matches against leaf segments, and uses substring matching.
Updates all golden files.
This commit is contained in:
Lewis Wynne 2026-02-11 23:47:59 +00:00
parent db607ac696
commit c9b448d508
47 changed files with 144 additions and 118 deletions

View file

@ -762,7 +762,7 @@ pda doctor
# ok Git initialised on main # ok Git initialised on main
# ok Git remote configured # ok Git remote configured
# ok Git in sync with remote # ok Git in sync with remote
# ok 3 store(s), 15 key(s), 2 secret(s), 4.2k total # ok 3 store(s), 15 key(s), 2 secret(s), 4.2k total size
# ok No issues found # ok No issues found
``` ```

View file

@ -2,6 +2,7 @@ package cmd
import ( import (
"reflect" "reflect"
"strings"
"github.com/agnivade/levenshtein" "github.com/agnivade/levenshtein"
) )
@ -69,16 +70,36 @@ func findConfigField(fields []ConfigField, key string) *ConfigField {
return nil return nil
} }
// suggestConfigKey returns Levenshtein-based suggestions for a mistyped config key. // suggestConfigKey returns suggestions for a mistyped config key. More generous
// than key/store suggestions since the config key space is small (~11 keys).
// Normalises spaces to underscores and matches against both the full dotted key
// and the leaf segment (part after the last dot).
func suggestConfigKey(fields []ConfigField, target string) []string { func suggestConfigKey(fields []ConfigField, target string) []string {
minThreshold := 1 normalized := strings.ReplaceAll(target, " ", "_")
maxThreshold := 4
threshold := min(max(len(target)/3, minThreshold), maxThreshold)
var suggestions []string var suggestions []string
for _, f := range fields { for _, f := range fields {
if levenshtein.ComputeDistance(target, f.Key) <= threshold { if matchesConfigKey(normalized, f.Key) {
suggestions = append(suggestions, f.Key) suggestions = append(suggestions, f.Key)
} }
} }
return suggestions return suggestions
} }
func matchesConfigKey(input, key string) bool {
// Substring match (either direction)
if strings.Contains(key, input) || strings.Contains(input, key) {
return true
}
// Levenshtein against full dotted key
if levenshtein.ComputeDistance(input, key) <= max(len(key)/3, 4) {
return true
}
// Levenshtein against leaf segment
if i := strings.LastIndex(key, "."); i >= 0 {
leaf := key[i+1:]
if levenshtein.ComputeDistance(input, leaf) <= max(len(leaf)/3, 1) {
return true
}
}
return false
}

View file

@ -279,7 +279,7 @@ func runDoctor(w io.Writer) bool {
if parseErrors > 0 { if parseErrors > 0 {
emit("FAIL", fmt.Sprintf("%d store(s), %d with parse errors", len(stores), parseErrors)) emit("FAIL", fmt.Sprintf("%d store(s), %d with parse errors", len(stores), parseErrors))
} else { } else {
emit("ok", fmt.Sprintf("%d store(s), %d key(s), %d secret(s), %s total", emit("ok", fmt.Sprintf("%d store(s), %d key(s), %d secret(s), %s total size",
len(stores), totalKeys, totalSecrets, formatSize(int(totalSize)))) len(stores), totalKeys, totalSecrets, formatSize(int(totalSize))))
} }
} }

View file

@ -7,7 +7,12 @@ default
$ pda config get git.auto_commit $ pda config get git.auto_commit
false false
# Unknown key with suggestion # Unknown key with suggestion (typo)
$ pda config get git.auto_comit --> FAIL $ pda config get git.auto_comit --> FAIL
FAIL unknown config key 'git.auto_comit' FAIL unknown config key 'git.auto_comit'
hint did you mean 'git.auto_commit'? hint did you mean 'git.auto_commit'?
# Unknown key with suggestion (leaf match, no prefix)
$ pda config get auto_commit --> FAIL
FAIL unknown config key 'auto_commit'
hint did you mean 'git.auto_commit'?