From 7a91f722fd0d769187aabb21c5931814c718739d Mon Sep 17 00:00:00 2001 From: Eric Rakestraw Date: Sat, 17 Oct 2026 18:46:47 +0000 Subject: [PATCH] Verify catalog capability lifecycles --- docs/roadmap/implementation.md | 11 +++- integrations/ckprompt/command_test.go | 73 +++++++++++++++++++++++++-- 2 files changed, 80 insertions(+), 4 deletions(-) diff --git a/docs/roadmap/implementation.md b/docs/roadmap/implementation.md index a419739..c156ba8 100644 --- a/docs/roadmap/implementation.md +++ b/docs/roadmap/implementation.md @@ -1,6 +1,6 @@ # Dependency upgrades, discovery, and smoke testing implementation plan -Status: Stages 1–7 complete; Stage 8 is next. +Status: Stages 1–8 complete; Stage 9 is next. ## Authority and baseline @@ -455,6 +455,15 @@ establish native namespace algorithms. **Complete when:** discovery exposes only declared capabilities and preserves the selected CommandKit invocation contracts. +**Completed:** 2026-10-17. Added independent installation and eager-capture +coverage for backend, namespace, and collection callbacks, including help +without provider acquisition and unavailable filters. Extended nil-loader issue +retention checks to all catalogs. Existing prompt/profile coverage plus the new +cases exercise default, named, unqualified, all, explicit-false, conflicting, +singleton, and repeatable-kind requests; selection, selector, state, inventory, +empty output, and delivery behavior remain protected. The full ckprompt suite +passed. + ## Stage 9: Add the optional smoke capability and native adapter **Primary files:** ckprompt `Suite`, new `smoke.go` or equivalent, adapter tests. diff --git a/integrations/ckprompt/command_test.go b/integrations/ckprompt/command_test.go index 49370cf..879209c 100644 --- a/integrations/ckprompt/command_test.go +++ b/integrations/ckprompt/command_test.go @@ -78,7 +78,8 @@ func TestCommandBuildsIndependentOptionalTrees(t *testing.T) { } invalid := FromEngine[int, int](nil) - invalid.ListPrompts, invalid.ListProfiles = nil, nil + invalid.ListPrompts, invalid.ListProfiles, invalid.ListBackends = nil, nil, nil + invalid.ListNamespaces, invalid.ListCollections = nil, nil invalid.InspectPrompt, invalid.InspectProfile = nil, nil invalid.InspectRequirements = nil if err := newPromptTestApp(t).Register(Command(invalid)); err == nil || !strings.Contains(err.Error(), "PromptKit engine loader is nil") { @@ -275,6 +276,68 @@ func TestCatalogCommandTreesAreIndependentAndOmitShow(t *testing.T) { } } +func TestAdditionalCatalogsInstallIndependentlyAndCaptureCallbacks(t *testing.T) { + for _, name := range []string{"backends", "namespaces", "collections"} { + t.Run(name, func(t *testing.T) { + calls := 0 + var namespaceRequest NamespaceRequest + suite := Suite[struct{}, struct{}]{} + args := []string{"promptkit", "list", name} + switch name { + case "backends": + suite.ListBackends = func(context.Context, CatalogRequest, struct{}, struct{}) (commandkit.Acquired[[]string], error) { + calls++ + return commandkit.Acquired[[]string]{Value: []string{"local"}}, nil + } + case "namespaces": + suite.ListNamespaces = func(_ context.Context, request NamespaceRequest, _, _ struct{}) (commandkit.Acquired[[]string], error) { + calls++ + namespaceRequest = request + return commandkit.Acquired[[]string]{Value: []string{"team"}}, nil + } + case "collections": + suite.ListCollections = func(context.Context, ckconfig.Selection, struct{}, struct{}) (commandkit.Acquired[[]promptkit.CollectionInfo], error) { + calls++ + return commandkit.Acquired[[]promptkit.CollectionInfo]{Value: []promptkit.CollectionInfo{}}, nil + } + } + app := newPromptTestApp(t, Command(suite)) + switch name { + case "backends": + suite.ListBackends = nil + case "namespaces": + suite.ListNamespaces = nil + case "collections": + suite.ListCollections = nil + } + + var help bytes.Buffer + outcome := app.Execute(context.Background(), []string{"help", "promptkit", "list"}, commandkit.Streams{Out: &help, Err: io.Discard}) + if outcome.Kind != commandkit.OutcomeSuccess || !strings.Contains(help.String(), "\n "+name+" ") || calls != 0 { + t.Fatalf("independent list help = (%#v, %q), calls=%d", outcome, help.String(), calls) + } + var unavailableArgs []string + switch name { + case "namespaces": + unavailableArgs = append(append([]string(nil), args...), "--namespace", "team") + case "collections": + unavailableArgs = append(append([]string(nil), args...), "--kind", "prompt") + } + if len(unavailableArgs) != 0 { + if got := app.Execute(context.Background(), unavailableArgs, commandkit.Streams{Out: io.Discard, Err: io.Discard}); got.Kind != commandkit.OutcomeUsage || calls != 0 { + t.Fatalf("unavailable %s filter = %#v, calls=%d", name, got, calls) + } + } + if got := app.Execute(context.Background(), args, commandkit.Streams{Out: io.Discard, Err: io.Discard}); got.Kind != commandkit.OutcomeSuccess || calls != 1 { + t.Fatalf("captured %s callback = (%#v, calls=%d)", name, got, calls) + } + if name == "namespaces" && (namespaceRequest.Selection.Config.Set || len(namespaceRequest.Kinds) != 0) { + t.Fatalf("omitted namespace selection = %#v", namespaceRequest) + } + }) + } +} + func TestDiscoveryCommandsForwardFiltersAndRenderCollections(t *testing.T) { var promptRequest, profileRequest, backendRequest CatalogRequest var namespaceRequest NamespaceRequest @@ -332,6 +395,10 @@ func TestDiscoveryCommandsForwardFiltersAndRenderCollections(t *testing.T) { if outcome.Kind != commandkit.OutcomeSuccess || profileRequest.Filter.Mode != promptkit.NamespaceUnqualified || profileRequest.Filter.Name != "" { t.Fatalf("profile list outcome/filter = (%#v, %#v)", outcome, profileRequest.Filter) } + outcome, _ = run("promptkit", "list", "backends", "--all-namespaces=false") + if outcome.Kind != commandkit.OutcomeSuccess || backendRequest.Filter.Mode != promptkit.NamespaceDefault { + t.Fatalf("explicit false backend filter = (%#v, %#v)", outcome, backendRequest.Filter) + } outcome, _ = run("promptkit", "list", "backends", "--all-namespaces") if outcome.Kind != commandkit.OutcomeSuccess || backendRequest.Filter.Mode != promptkit.NamespaceAll { t.Fatalf("backend list outcome/filter = (%#v, %#v)", outcome, backendRequest.Filter) @@ -358,8 +425,8 @@ func TestDiscoveryCommandsForwardFiltersAndRenderCollections(t *testing.T) { if outcome.Kind != commandkit.OutcomeUsage || providers != beforeConflict { t.Fatalf("conflicting namespace filters = %#v, provider calls %d -> %d", outcome, beforeConflict, providers) } - if providers != 5 { - t.Fatalf("catalog providers called = %d, want five", providers) + if providers != 6 { + t.Fatalf("catalog providers called = %d, want six", providers) } }