Check PromptKit selector collisions against installed flags

This commit is contained in:
2026-10-07 16:51:15 +00:00
parent c26d279b4f
commit a21dd3d89e
4 changed files with 53 additions and 3 deletions

View File

@@ -245,7 +245,7 @@ Both commands require one ID operand and accept `--config`, `--overlay`, and
`--format text|yaml` (default `text`). Prompt inspection also accepts singleton
`--version`, defaulting to the empty string. PromptKit owns version selection:
an omitted version works for a single-version ID, while an ambiguous ID is an
operational error. These commands do not accept `--scope`; their namespace is
operational error. These commands have no built-in `--scope` selection; their namespace is
`promptkit`.
Prompt YAML has these fields. Input order is preserved and an empty input list is

View File

@@ -69,6 +69,9 @@ architecture. The issues below affect behavior within the implemented structure.
- R-08 complete: unknown scopes and absent default app scopes report available
choices in standard scope order without runtime acquisition.
- R-09 complete: PromptKit selector collisions are checked against installed
standard flags; profile-only `version` and custom `scope` selectors are usable.
## Findings and priority
P1 means a supported invocation is blocked or its report content is corrupted.

View File

@@ -128,6 +128,10 @@ func RegisterPromptKit[C any](app *App, suite PromptKitSuite[C]) error {
func normalizePromptKitSuite[C any](suite PromptKitSuite[C]) (PromptKitSuite[C], error) {
normalized := suite
normalized.Selectors = append([]Selector[C](nil), suite.Selectors...)
standardNames := map[string]bool{"help": true}
for _, flag := range promptKitInputFlags(PromptKitSuite[C]{}, suite.InspectPrompt != nil) {
standardNames[flag.binding.meta.Name] = true
}
seenNames := make(map[string]struct{}, len(normalized.Selectors))
seenShorts := make(map[string]struct{}, len(normalized.Selectors))
for i, selector := range normalized.Selectors {
@@ -139,8 +143,7 @@ func normalizePromptKitSuite[C any](suite PromptKitSuite[C]) (PromptKitSuite[C],
if !validFlagName(meta.Name) {
return PromptKitSuite[C]{}, fmt.Errorf("PromptKit selector flag name %q is invalid", meta.Name)
}
switch meta.Name {
case "config", "overlay", "scope", "format", "version", "help":
if standardNames[meta.Name] {
return PromptKitSuite[C]{}, fmt.Errorf("PromptKit selector flag --%s conflicts with a standard flag", meta.Name)
}
if meta.Short == "h" {

View File

@@ -404,3 +404,47 @@ func sortedKeys(values map[string]any) []string {
}
return keys
}
func TestPromptKitSelectorNamesCollideOnlyWithInstalledFlags(t *testing.T) {
for _, name := range []string{"version", "scope"} {
var captured commandkit.Supplied[string]
suite := commandkit.PromptKitSuite[commandkit.Supplied[string]]{
Selectors: []commandkit.Selector[commandkit.Supplied[string]]{{Binding: commandkit.ValueFlag[commandkit.Supplied[string]](commandkit.FlagMeta{Name: name}, nil, func(s string) (string, error) { return s, nil }, func(c *commandkit.Supplied[string], v commandkit.Supplied[string]) { *c = v })}},
InspectProfile: func(_ context.Context, _ commandkit.Selection, c commandkit.Supplied[string], _ string) (commandkit.Acquired[*promptkit.ProfileInspection], error) {
captured = c
return commandkit.Acquired[*promptkit.ProfileInspection]{Value: &promptkit.ProfileInspection{}}, nil
},
}
app, _ := commandkit.New(commandkit.Identity{Name: "example"})
if err := commandkit.RegisterPromptKit(app, suite); err != nil {
t.Fatalf("%s: %v", name, err)
}
result := app.Execute(context.Background(), []string{"promptkit", "show", "profile", "example", "--" + name, "selected"}, commandkit.Streams{})
if result.Kind != commandkit.OutcomeSuccess || captured != (commandkit.Supplied[string]{Value: "selected", Set: true}) {
t.Fatalf("%s: result=%#v captured=%#v", name, result, captured)
}
suite.InspectPrompt = func(context.Context, commandkit.Selection, commandkit.Supplied[string], string, string) (commandkit.Acquired[*promptkit.PromptInspection], error) {
return commandkit.Acquired[*promptkit.PromptInspection]{Value: &promptkit.PromptInspection{}}, nil
}
other, _ := commandkit.New(commandkit.Identity{Name: "example"})
err := commandkit.RegisterPromptKit(other, suite)
if name == "version" && err == nil {
t.Fatal("accepted collision with installed prompt version flag")
}
if name == "scope" && err != nil {
t.Fatalf("scope selector conflicts with no installed flag: %v", err)
}
}
for _, name := range []string{"config", "overlay", "format", "help"} {
app, _ := commandkit.New(commandkit.Identity{Name: "example"})
suite := commandkit.PromptKitSuite[struct{}]{
InspectProfile: func(context.Context, commandkit.Selection, struct{}, string) (commandkit.Acquired[*promptkit.ProfileInspection], error) {
return commandkit.Acquired[*promptkit.ProfileInspection]{}, nil
},
Selectors: []commandkit.Selector[struct{}]{{Binding: commandkit.ValueFlag[struct{}](commandkit.FlagMeta{Name: name}, nil, func(s string) (string, error) { return s, nil }, func(*struct{}, commandkit.Supplied[string]) {})}},
}
if err := commandkit.RegisterPromptKit(app, suite); err == nil {
t.Fatalf("accepted standard flag collision --%s", name)
}
}
}