fix: include hint in update JSON errors and explain skill-name charset

This commit is contained in:
zhangheng.023
2026-06-26 12:30:54 +08:00
parent 45399ca70f
commit 7eabb27272
4 changed files with 16 additions and 7 deletions

View File

@@ -190,14 +190,18 @@ func updateRun(opts *UpdateOptions) error {
// --- Output helpers ---
// reportError emits the failure on the requested surface: JSON mode prints the
// {ok:false, error:{type, message}} envelope to stdout and signals the typed
// error's exit code bare; human mode returns the typed error for the
// dispatcher to render.
// {ok:false, error:{type, message, hint?}} envelope to stdout and signals the
// typed error's exit code bare; human mode returns the typed error for the
// dispatcher to render. The hint is included only when the typed error carries
// one, so AI-agent/script consumers reading JSON get the same actionable
// guidance humans see on stderr.
func reportError(opts *UpdateOptions, io *cmdutil.IOStreams, errType string, typedErr errs.TypedError) error {
if opts.JSON {
output.PrintJson(io.Out, map[string]interface{}{
"ok": false, "error": map[string]interface{}{"type": errType, "message": typedErr.ProblemDetail().Message},
})
errObj := map[string]interface{}{"type": errType, "message": typedErr.ProblemDetail().Message}
if hint := typedErr.ProblemDetail().Hint; hint != "" {
errObj["hint"] = hint
}
output.PrintJson(io.Out, map[string]interface{}{"ok": false, "error": errObj})
return output.ErrBare(output.ExitCodeOf(typedErr))
}
return typedErr

View File

@@ -1681,6 +1681,10 @@ func TestUpdate_InvalidSkillsFlag_JSONExit2(t *testing.T) {
if !strings.Contains(stdout.String(), `"type": "validation"`) {
t.Fatalf("JSON output missing validation type: %s", stdout.String())
}
// spec §3.8: JSON validation errors must carry the actionable hint too.
if !strings.Contains(stdout.String(), `"hint"`) {
t.Fatalf("JSON output missing hint field: %s", stdout.String())
}
}
func TestUpdate_UnknownSkillResult_Exit2(t *testing.T) {

View File

@@ -72,7 +72,7 @@ func ParseSuiteSelection(rawNames []string) (*SuiteSelection, error) {
}
}
if len(invalid) > 0 {
return nil, fmt.Errorf("invalid skill name(s): %s", strings.Join(invalid, ", "))
return nil, fmt.Errorf("invalid skill name(s): %s (skill names use only letters, digits, and _ : -)", strings.Join(invalid, ", "))
}
sort.Strings(cleaned)
return &SuiteSelection{Skills: cleaned}, nil

View File

@@ -869,6 +869,7 @@ func TestParseSuiteSelection(t *testing.T) {
{name: "all mixed", input: []string{"all", "lark-im"}, wantErr: "cannot be combined"},
{name: "empty", input: []string{"", " "}, wantErr: "at least one"},
{name: "invalid name", input: []string{"bad name"}, wantErr: "invalid skill name"},
{name: "invalid name explains charset", input: []string{"bad name"}, wantErr: "letters, digits, and _ : -"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {