Capture PromptKit selectors before retaining callbacks
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
Reviewed on 2026-10-11, against committed implementation
|
||||
`cc574915ee17e2d738f27874d2c8857aa1f51a47`. The implementation sprint starts
|
||||
after planning commit `0d23676144ab1ce5047924e98f1ab6a2a9d6aeb6`.
|
||||
All issues below are open. P1 identifies a functional contract violation; P2
|
||||
Completion status and evidence are recorded per issue below. P1 identifies a functional contract violation; P2
|
||||
identifies architecture or documentation corrections; P3 identifies smaller
|
||||
maintenance corrections. These priorities do not add publication gates.
|
||||
|
||||
@@ -38,7 +38,14 @@ provider caches, or permanent validation gates to resolve these issues.
|
||||
|
||||
## R1: Capture PromptKit selector handles before retaining the suite
|
||||
|
||||
**Status:** Open. **Priority:** P1.
|
||||
**Status:** Completed (2026-10-11). **Priority:** P1.
|
||||
|
||||
**Completed work:** the enabled family copies its selector-handle collection
|
||||
once, preserving original identities for both generated commands and retained
|
||||
callbacks. Empty-suite behavior is unchanged. The public consumer regression
|
||||
replaces a source element after capture and exercises prompt/profile inspection
|
||||
in two apps reusing the declaration. It failed before the fix and passes after
|
||||
it; the complete ckprompt suite passes offline with Go 1.26.8.
|
||||
|
||||
**Evidence:** [ckprompt/command.go](../../integrations/ckprompt/command.go),
|
||||
`Command`, `newDefinitionCommand`, and `prepareDefinitionInput`.
|
||||
|
||||
@@ -37,6 +37,7 @@ func Command[I, S any](suite Suite[I, S]) commandkit.Command {
|
||||
return command
|
||||
}
|
||||
|
||||
suite.Selectors = append([]commandkit.Flag[I](nil), suite.Selectors...)
|
||||
showCommands := make([]commandkit.Command, 0, 2)
|
||||
if suite.InspectPrompt != nil {
|
||||
showCommands = append(showCommands, newDefinitionCommand(suite, "prompt", true))
|
||||
|
||||
@@ -17,6 +17,55 @@ import (
|
||||
type compositionInput struct{ Campaign commandkit.Supplied[string] }
|
||||
type compositionState struct{ token int }
|
||||
|
||||
func TestPublicDefinitionCommandsCaptureSelectorHandles(t *testing.T) {
|
||||
newFlag := func() commandkit.Flag[compositionInput] {
|
||||
return commandkit.ValueFlag(commandkit.FlagMeta{Name: "campaign", Required: true}, (*string)(nil),
|
||||
func(value string) (string, error) { return value, nil },
|
||||
func(input *compositionInput, value commandkit.Supplied[string]) { input.Campaign = value })
|
||||
}
|
||||
selectors := []commandkit.Flag[compositionInput]{newFlag()}
|
||||
calls := 0
|
||||
checkInput := func(input compositionInput) {
|
||||
t.Helper()
|
||||
calls++
|
||||
if !input.Campaign.Set || input.Campaign.Value != "spring" {
|
||||
t.Errorf("captured selector value = %#v", input.Campaign)
|
||||
}
|
||||
}
|
||||
declaration := ckprompt.Command(ckprompt.Suite[compositionInput, struct{}]{
|
||||
Selectors: selectors,
|
||||
InspectPrompt: func(_ context.Context, request ckprompt.PromptRequest, input compositionInput, _ struct{}) (commandkit.Acquired[*promptkit.PromptInspection], error) {
|
||||
checkInput(input)
|
||||
return commandkit.Acquired[*promptkit.PromptInspection]{Value: &promptkit.PromptInspection{PromptID: request.ID}}, nil
|
||||
},
|
||||
InspectProfile: func(_ context.Context, request ckprompt.ProfileRequest, input compositionInput, _ struct{}) (commandkit.Acquired[*promptkit.ProfileInspection], error) {
|
||||
checkInput(input)
|
||||
return commandkit.Acquired[*promptkit.ProfileInspection]{Value: &promptkit.ProfileInspection{ProfileID: request.ID}}, nil
|
||||
},
|
||||
})
|
||||
first, err := commandkit.New(commandkit.Identity{Name: "first"}, declaration)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// A separately constructed lookalike must not replace the captured binding.
|
||||
selectors[0] = newFlag()
|
||||
second, err := commandkit.New(commandkit.Identity{Name: "second"}, declaration)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, app := range []*commandkit.App{first, second} {
|
||||
for _, resource := range []string{"prompt", "profile"} {
|
||||
outcome := app.Execute(context.Background(), []string{"promptkit", "show", resource, "briefing", "--campaign", "spring"}, commandkit.Streams{Out: io.Discard, Err: io.Discard})
|
||||
if outcome.Kind != commandkit.OutcomeSuccess {
|
||||
t.Fatalf("%s after selector edit: %#v", resource, outcome)
|
||||
}
|
||||
}
|
||||
}
|
||||
if calls != 4 {
|
||||
t.Fatalf("inspection calls = %d, want 4", calls)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPublicConfigurationAndDefinitionFamiliesRemainIndependent(t *testing.T) {
|
||||
inputs, err := promptkit.FilesystemInputs("composition fixture", fstest.MapFS{
|
||||
"config/prompts.yml": &fstest.MapFile{Data: []byte(`prompts:
|
||||
|
||||
Reference in New Issue
Block a user