Move integration code into optional modules

This commit is contained in:
2026-10-11 01:06:21 +00:00
parent 0d23676144
commit 476e98a463
28 changed files with 348 additions and 285 deletions

View File

@@ -1,6 +1,6 @@
# Implement the optional integration modules and captured registration API
Status: decision-complete plan for the module/API transition; implementation pending.
Status: implementation in progress. Stage 1 is complete; subsequent work follows the ordered roadmap.
Execute the stages in order. Each stage is sized as a separate Luna coding-agent
assignment. This document authorizes no publication or changes to sibling
repositories.
@@ -150,8 +150,17 @@ compatibility relocation layer.
tidy partially refactored packages or add temporary root re-exports. Final
manifests and sums are reviewed after their owners compile.
**Completion evidence:** inspect source/test/example ownership and the three module
declarations. Explain any shared test split. Compilation is not required here.
**Completion evidence — complete:** configuration source/tests/benchmarks and the
simple example now belong to `integrations/ckconfig`; PromptKit source/tests/
benchmarks and the composed example belong to `integrations/ckprompt`. The root
contains the core package and generic dispatch metadata refresh test, with no
optional-library consumer. The three declarations use the selected module paths
and Go 1.26.0. The two configuration cancellation cases and three configuration
benchmarks moved to ckconfig; PromptKit cancellation and report benchmark moved
to ckp; the mixed registration/help refresh test moved to ckp while the generic
core refresh test remains in root. No compatibility relocation layer was added.
As specified, compilation is not required for this ownership step; manifests and
sums remain for review after the owners compile.
## Stage 2: Implement core-owned values and immutable declarations

View File

@@ -1,4 +1,4 @@
package commandkit_test
package ckconfig_test
import (
"bytes"
@@ -7,7 +7,6 @@ import (
"fmt"
commandkit "gitea.maximumdirect.net/eric/CommandKit"
configkit "gitea.maximumdirect.net/eric/ConfigKit"
"gitea.maximumdirect.net/eric/promptkit"
"strings"
"testing"
)
@@ -77,44 +76,3 @@ func TestAggregateCancellationRetainsCompletedValidationAndDiagnostics(t *testin
t.Fatalf("result=%#v calls=%d out=%q diagnostics=%q", result, calls, out.String(), diagnostics.String())
}
}
func TestDefinitionInspectionCancellationBoundaries(t *testing.T) {
for _, resource := range []string{"prompt", "profile"} {
for _, before := range []bool{true, false} {
t.Run(fmt.Sprintf("%s/before=%t", resource, before), func(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
if before {
cancel()
}
calls, contexts := 0, 0
diagnostics := []commandkit.Diagnostic{commandkit.MessageDiagnostic(configkit.SeverityWarning, "acquired warning")}
suite := commandkit.PromptKitSuite[struct{}]{NewContext: func() struct{} { contexts++; return struct{}{} },
InspectPrompt: func(context.Context, commandkit.Selection, struct{}, string, string) (commandkit.Acquired[*promptkit.PromptInspection], error) {
calls++
cancel()
return commandkit.Acquired[*promptkit.PromptInspection]{Value: &promptkit.PromptInspection{}, Diagnostics: diagnostics}, nil
},
InspectProfile: func(context.Context, commandkit.Selection, struct{}, string) (commandkit.Acquired[*promptkit.ProfileInspection], error) {
calls++
cancel()
return commandkit.Acquired[*promptkit.ProfileInspection]{Value: &promptkit.ProfileInspection{}, Diagnostics: diagnostics}, nil
},
}
app, _ := commandkit.New(commandkit.Identity{Name: "example"})
if err := commandkit.RegisterPromptKit(app, suite); err != nil {
t.Fatal(err)
}
var out, errs bytes.Buffer
result := app.Execute(ctx, []string{"promptkit", "show", resource, "example"}, commandkit.Streams{Out: &out, Err: &errs})
want := 1
if before {
want = 0
}
if result.Kind != commandkit.OutcomeOperational || !errors.Is(result.Cause, context.Canceled) || calls != want || contexts != want || out.Len() != 0 || (!before && !strings.Contains(errs.String(), "acquired warning")) {
t.Fatalf("result=%#v calls=%d contexts=%d out=%q diagnostics=%q", result, calls, contexts, out.String(), errs.String())
}
})
}
}
}

View File

@@ -1,4 +1,4 @@
package commandkit
package ckconfig
import (
"context"

View File

@@ -1,4 +1,4 @@
package commandkit
package ckconfig
import (
"bytes"

View File

@@ -1,4 +1,4 @@
package commandkit_test
package ckconfig_test
import (
"bytes"

View File

@@ -0,0 +1,2 @@
// Package ckconfig provides CommandKit's optional ConfigKit integration.
package ckconfig

View File

@@ -1,4 +1,4 @@
package commandkit
package ckconfig
import (
"bytes"

View File

@@ -0,0 +1,9 @@
module gitea.maximumdirect.net/eric/CommandKit/integrations/ckconfig
go 1.26.0
require (
gitea.maximumdirect.net/eric/CommandKit v0.2.0
gitea.maximumdirect.net/eric/ConfigKit v0.5.0
go.yaml.in/yaml/v4 v4.0.0-rc.6
)

View File

@@ -1,66 +1,15 @@
package commandkit
package ckconfig
import (
"bytes"
"context"
"fmt"
"io"
"strings"
"testing"
promptkit "gitea.maximumdirect.net/eric/promptkit"
)
var performanceFramedBytes []byte
func BenchmarkPerformancePromptReports(b *testing.B) {
for _, inputCount := range []int{4, 128} {
inspection := performancePromptInspection(inputCount)
app := performancePromptApp(b, inspection, performanceProfileInspection(nil))
for _, format := range []string{"text", "yaml"} {
name := fmt.Sprintf("prompt/inputs_%d/%s", inputCount, format)
args := []string{"promptkit", "show", "prompt", "briefing", "--format", format}
b.Run(name, func(b *testing.B) {
verifyPromptReport(b, app, args, format, "prompt_id")
streams := Streams{Out: io.Discard, Err: io.Discard}
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
if outcome := app.Execute(context.Background(), args, streams); outcome.Kind != OutcomeSuccess {
b.Fatalf("prompt report outcome = %#v", outcome)
}
}
})
}
}
for _, fixture := range []struct {
name string
extra map[string]any
}{
{name: "small", extra: performanceExtraParams(2, 3)},
{name: "large", extra: performanceExtraParams(24, 8)},
} {
profile := performanceProfileInspection(fixture.extra)
app := performancePromptApp(b, performancePromptInspection(4), profile)
for _, format := range []string{"text", "yaml"} {
name := fmt.Sprintf("profile/extra_%s/%s", fixture.name, format)
args := []string{"promptkit", "show", "profile", "baseline", "--format", format}
b.Run(name, func(b *testing.B) {
verifyPromptReport(b, app, args, format, "profile_id")
streams := Streams{Out: io.Discard, Err: io.Discard}
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
if outcome := app.Execute(context.Background(), args, streams); outcome.Kind != OutcomeSuccess {
b.Fatalf("profile report outcome = %#v", outcome)
}
}
})
}
}
}
func BenchmarkPerformanceYAMLFraming(b *testing.B) {
fixtures := []struct {
name string
@@ -160,84 +109,6 @@ func BenchmarkPerformanceConfigRegistration(b *testing.B) {
}
}
func performancePromptInspection(inputCount int) *promptkit.PromptInspection {
inputs := make([]promptkit.PromptInputDefinition, inputCount)
for index := range inputs {
inputs[index] = promptkit.PromptInputDefinition{
Name: fmt.Sprintf("input%03d", index),
Required: index%2 == 0,
ContentType: "text/plain",
Description: "A representative prompt input.",
}
}
return &promptkit.PromptInspection{
PromptID: "briefing",
PromptVersion: "1",
PromptHash: "fixed-hash",
DefaultProfileID: "baseline",
Inputs: inputs,
OutputContract: promptkit.OutputContract{
Format: promptkit.FormatText,
ValidationMode: promptkit.ValidationBasic,
RepairAttempts: 1,
},
}
}
func performanceProfileInspection(extra map[string]any) *promptkit.ProfileInspection {
return &promptkit.ProfileInspection{
ProfileID: "baseline",
EffectiveModelParams: promptkit.ExecutionTarget{
BackendID: "benchmark-backend",
Endpoint: "https://example.invalid/v1",
Model: "benchmark-model",
Temperature: 0.5,
MaxTokens: 512,
ExtraParams: extra,
},
}
}
func performanceExtraParams(keys, width int) map[string]any {
values := make(map[string]any, keys)
for keyIndex := 0; keyIndex < keys; keyIndex++ {
nested := make(map[string]any, width)
for valueIndex := 0; valueIndex < width; valueIndex++ {
nested[fmt.Sprintf("value%02d", valueIndex)] = fmt.Sprintf("sample-%02d-%02d", keyIndex, valueIndex)
}
values[fmt.Sprintf("group%02d", keyIndex)] = map[string]any{
"enabled": keyIndex%2 == 0,
"nested": nested,
"sequence": []any{
"first",
keyIndex,
map[string]any{"leaf": true},
},
}
}
return values
}
func performancePromptApp(tb testing.TB, prompt *promptkit.PromptInspection, profile *promptkit.ProfileInspection) *App {
tb.Helper()
app, err := New(Identity{Name: "bench"})
if err != nil {
tb.Fatal(err)
}
suite := PromptKitSuite[struct{}]{
InspectPrompt: func(context.Context, Selection, struct{}, string, string) (Acquired[*promptkit.PromptInspection], error) {
return Acquired[*promptkit.PromptInspection]{Value: prompt}, nil
},
InspectProfile: func(context.Context, Selection, struct{}, string) (Acquired[*promptkit.ProfileInspection], error) {
return Acquired[*promptkit.ProfileInspection]{Value: profile}, nil
},
}
if err := RegisterPromptKit(app, suite); err != nil {
tb.Fatal(err)
}
return app
}
func performanceNativeDocument(targetBytes int, directive bool) []byte {
preamble := "# generated native configuration\n"
if directive {
@@ -319,29 +190,6 @@ func performanceConfigApp(tb testing.TB, suite ConfigSuite[struct{}]) *App {
return app
}
func verifyPromptReport(tb testing.TB, app *App, args []string, format, marker string) {
tb.Helper()
var output bytes.Buffer
outcome := app.Execute(context.Background(), args, Streams{Out: &output, Err: io.Discard})
wantMarker := marker
if format == "text" {
if marker == "prompt_id" {
wantMarker = "Prompt ID:"
} else {
wantMarker = "Profile ID:"
}
}
if outcome.Kind != OutcomeSuccess || !strings.Contains(output.String(), wantMarker) {
tb.Fatalf("%s report outcome = %#v, output length %d", format, outcome, output.Len())
}
if format == "yaml" && !strings.Contains(output.String(), ":") {
tb.Fatalf("YAML report lacks mapping entries: %q", output.String())
}
if format == "text" && !strings.Contains(output.String(), " ID:") {
tb.Fatalf("text report has unexpected shape: %q", output.String()[:min(output.Len(), 80)])
}
}
func verifyConfigReport(tb testing.TB, app *App, args []string, minimumBytes int) {
tb.Helper()
var output bytes.Buffer

View File

@@ -1,4 +1,4 @@
package commandkit
package ckconfig
import (
"fmt"

View File

@@ -1,4 +1,4 @@
package commandkit
package ckconfig
import (
"bytes"

View File

@@ -1,4 +1,4 @@
package commandkit
package ckconfig
import (
"bytes"

View File

@@ -1,4 +1,4 @@
package commandkit
package ckconfig
import (
"bytes"

View File

@@ -0,0 +1,53 @@
package ckprompt_test
import (
"context"
"errors"
"fmt"
commandkit "gitea.maximumdirect.net/eric/CommandKit"
configkit "gitea.maximumdirect.net/eric/ConfigKit"
"gitea.maximumdirect.net/eric/promptkit"
"strings"
"testing"
)
func TestDefinitionInspectionCancellationBoundaries(t *testing.T) {
for _, resource := range []string{"prompt", "profile"} {
for _, before := range []bool{true, false} {
t.Run(fmt.Sprintf("%s/before=%t", resource, before), func(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
if before {
cancel()
}
calls, contexts := 0, 0
diagnostics := []commandkit.Diagnostic{commandkit.MessageDiagnostic(configkit.SeverityWarning, "acquired warning")}
suite := commandkit.PromptKitSuite[struct{}]{NewContext: func() struct{} { contexts++; return struct{}{} },
InspectPrompt: func(context.Context, commandkit.Selection, struct{}, string, string) (commandkit.Acquired[*promptkit.PromptInspection], error) {
calls++
cancel()
return commandkit.Acquired[*promptkit.PromptInspection]{Value: &promptkit.PromptInspection{}, Diagnostics: diagnostics}, nil
},
InspectProfile: func(context.Context, commandkit.Selection, struct{}, string) (commandkit.Acquired[*promptkit.ProfileInspection], error) {
calls++
cancel()
return commandkit.Acquired[*promptkit.ProfileInspection]{Value: &promptkit.ProfileInspection{}, Diagnostics: diagnostics}, nil
},
}
app, _ := commandkit.New(commandkit.Identity{Name: "example"})
if err := commandkit.RegisterPromptKit(app, suite); err != nil {
t.Fatal(err)
}
var out, errs bytes.Buffer
result := app.Execute(ctx, []string{"promptkit", "show", resource, "example"}, commandkit.Streams{Out: &out, Err: &errs})
want := 1
if before {
want = 0
}
if result.Kind != commandkit.OutcomeOperational || !errors.Is(result.Cause, context.Canceled) || calls != want || contexts != want || out.Len() != 0 || (!before && !strings.Contains(errs.String(), "acquired warning")) {
t.Fatalf("result=%#v calls=%d contexts=%d out=%q diagnostics=%q", result, calls, contexts, out.String(), errs.String())
}
})
}
}
}

View File

@@ -0,0 +1,2 @@
// Package ckprompt integrates PromptKit inspection with CommandKit.
package ckprompt

View File

@@ -0,0 +1,10 @@
module gitea.maximumdirect.net/eric/CommandKit/integrations/ckprompt
go 1.26.0
require (
gitea.maximumdirect.net/eric/CommandKit v0.2.0
gitea.maximumdirect.net/eric/CommandKit/integrations/ckconfig v0.1.0
gitea.maximumdirect.net/eric/promptkit v0.12.0
go.yaml.in/yaml/v4 v4.0.0-rc.6
)

View File

@@ -0,0 +1,161 @@
package ckprompt
import (
"bytes"
"context"
"fmt"
"io"
"strings"
"testing"
promptkit "gitea.maximumdirect.net/eric/promptkit"
)
func BenchmarkPerformancePromptReports(b *testing.B) {
for _, inputCount := range []int{4, 128} {
inspection := performancePromptInspection(inputCount)
app := performancePromptApp(b, inspection, performanceProfileInspection(nil))
for _, format := range []string{"text", "yaml"} {
name := fmt.Sprintf("prompt/inputs_%d/%s", inputCount, format)
args := []string{"promptkit", "show", "prompt", "briefing", "--format", format}
b.Run(name, func(b *testing.B) {
verifyPromptReport(b, app, args, format, "prompt_id")
streams := Streams{Out: io.Discard, Err: io.Discard}
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
if outcome := app.Execute(context.Background(), args, streams); outcome.Kind != OutcomeSuccess {
b.Fatalf("prompt report outcome = %#v", outcome)
}
}
})
}
}
for _, fixture := range []struct {
name string
extra map[string]any
}{
{name: "small", extra: performanceExtraParams(2, 3)},
{name: "large", extra: performanceExtraParams(24, 8)},
} {
profile := performanceProfileInspection(fixture.extra)
app := performancePromptApp(b, performancePromptInspection(4), profile)
for _, format := range []string{"text", "yaml"} {
name := fmt.Sprintf("profile/extra_%s/%s", fixture.name, format)
args := []string{"promptkit", "show", "profile", "baseline", "--format", format}
b.Run(name, func(b *testing.B) {
verifyPromptReport(b, app, args, format, "profile_id")
streams := Streams{Out: io.Discard, Err: io.Discard}
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
if outcome := app.Execute(context.Background(), args, streams); outcome.Kind != OutcomeSuccess {
b.Fatalf("profile report outcome = %#v", outcome)
}
}
})
}
}
}
func performancePromptInspection(inputCount int) *promptkit.PromptInspection {
inputs := make([]promptkit.PromptInputDefinition, inputCount)
for index := range inputs {
inputs[index] = promptkit.PromptInputDefinition{
Name: fmt.Sprintf("input%03d", index),
Required: index%2 == 0,
ContentType: "text/plain",
Description: "A representative prompt input.",
}
}
return &promptkit.PromptInspection{
PromptID: "briefing",
PromptVersion: "1",
PromptHash: "fixed-hash",
DefaultProfileID: "baseline",
Inputs: inputs,
OutputContract: promptkit.OutputContract{
Format: promptkit.FormatText,
ValidationMode: promptkit.ValidationBasic,
RepairAttempts: 1,
},
}
}
func performanceProfileInspection(extra map[string]any) *promptkit.ProfileInspection {
return &promptkit.ProfileInspection{
ProfileID: "baseline",
EffectiveModelParams: promptkit.ExecutionTarget{
BackendID: "benchmark-backend",
Endpoint: "https://example.invalid/v1",
Model: "benchmark-model",
Temperature: 0.5,
MaxTokens: 512,
ExtraParams: extra,
},
}
}
func performanceExtraParams(keys, width int) map[string]any {
values := make(map[string]any, keys)
for keyIndex := 0; keyIndex < keys; keyIndex++ {
nested := make(map[string]any, width)
for valueIndex := 0; valueIndex < width; valueIndex++ {
nested[fmt.Sprintf("value%02d", valueIndex)] = fmt.Sprintf("sample-%02d-%02d", keyIndex, valueIndex)
}
values[fmt.Sprintf("group%02d", keyIndex)] = map[string]any{
"enabled": keyIndex%2 == 0,
"nested": nested,
"sequence": []any{
"first",
keyIndex,
map[string]any{"leaf": true},
},
}
}
return values
}
func performancePromptApp(tb testing.TB, prompt *promptkit.PromptInspection, profile *promptkit.ProfileInspection) *App {
tb.Helper()
app, err := New(Identity{Name: "bench"})
if err != nil {
tb.Fatal(err)
}
suite := PromptKitSuite[struct{}]{
InspectPrompt: func(context.Context, Selection, struct{}, string, string) (Acquired[*promptkit.PromptInspection], error) {
return Acquired[*promptkit.PromptInspection]{Value: prompt}, nil
},
InspectProfile: func(context.Context, Selection, struct{}, string) (Acquired[*promptkit.ProfileInspection], error) {
return Acquired[*promptkit.ProfileInspection]{Value: profile}, nil
},
}
if err := RegisterPromptKit(app, suite); err != nil {
tb.Fatal(err)
}
return app
}
func verifyPromptReport(tb testing.TB, app *App, args []string, format, marker string) {
tb.Helper()
var output bytes.Buffer
outcome := app.Execute(context.Background(), args, Streams{Out: &output, Err: io.Discard})
wantMarker := marker
if format == "text" {
if marker == "prompt_id" {
wantMarker = "Prompt ID:"
} else {
wantMarker = "Profile ID:"
}
}
if outcome.Kind != OutcomeSuccess || !strings.Contains(output.String(), wantMarker) {
tb.Fatalf("%s report outcome = %#v, output length %d", format, outcome, output.Len())
}
if format == "yaml" && !strings.Contains(output.String(), ":") {
tb.Fatalf("YAML report lacks mapping entries: %q", output.String())
}
if format == "text" && !strings.Contains(output.String(), " ID:") {
tb.Fatalf("text report has unexpected shape: %q", output.String()[:min(output.Len(), 80)])
}
}

View File

@@ -1,4 +1,4 @@
package commandkit
package ckprompt
import (
"bytes"

View File

@@ -1,4 +1,4 @@
package commandkit
package ckprompt
import (
"context"

View File

@@ -1,4 +1,4 @@
package commandkit_test
package ckprompt_test
import (
"bytes"

View File

@@ -1,4 +1,4 @@
package commandkit_test
package ckprompt_test
import (
"bytes"

View File

@@ -0,0 +1,85 @@
package ckprompt_test
import (
"bytes"
"context"
commandkit "gitea.maximumdirect.net/eric/CommandKit"
"gitea.maximumdirect.net/eric/promptkit"
"io"
"strings"
"testing"
)
func TestBridgeTreeRefreshesAfterSuccessfulRegistration(t *testing.T) {
app, err := commandkit.New(commandkit.Identity{Name: "example"})
if err != nil {
t.Fatal(err)
}
var output bytes.Buffer
streams := commandkit.Streams{Out: &output, Err: io.Discard}
renderRootHelp := func() string {
t.Helper()
output.Reset()
outcome := app.Execute(context.Background(), []string{"--help"}, streams)
if outcome.Kind != commandkit.OutcomeSuccess {
t.Fatalf("root help outcome = %#v", outcome)
}
return output.String()
}
initial := renderRootHelp()
if strings.Contains(initial, "custom") || strings.Contains(initial, "config") || strings.Contains(initial, "promptkit") {
t.Fatalf("initial help unexpectedly lists future registrations: %q", initial)
}
custom, err := NewCommand(CommandSpec[struct{}]{Name: "custom", Run: func(Invocation, struct{}) Outcome { return commandkit.Success() }})
if err != nil {
t.Fatal(err)
}
if err := app.Register(custom); err != nil {
t.Fatal(err)
}
providerCalls := 0
if err := commandkit.RegisterConfig(app, commandkit.ConfigSuite[struct{}]{Scopes: []commandkit.ConfigScope[struct{}]{{
Name: "app",
Operations: []ConfigOperation{commandkit.ConfigShow},
Acquire: func(context.Context, commandkit.ConfigRequest, struct{}) (commandkit.Acquired[commandkit.ConfigView], error) {
providerCalls++
return commandkit.Acquired[commandkit.ConfigView]{}, nil
},
}}}); err != nil {
t.Fatal(err)
}
if err := commandkit.RegisterPromptKit(app, commandkit.PromptKitSuite[struct{}]{InspectPrompt: func(context.Context, commandkit.Selection, struct{}, string, string) (commandkit.Acquired[*promptkit.PromptInspection], error) {
providerCalls++
return commandkit.Acquired[*promptkit.PromptInspection]{}, nil
}}); err != nil {
t.Fatal(err)
}
updated := renderRootHelp()
for _, name := range []string{"custom", "config", "promptkit"} {
if !strings.Contains(updated, name) {
t.Errorf("updated help omits %q: %q", name, updated)
}
}
if providerCalls != 0 {
t.Fatalf("help invoked inspection or configuration callbacks %d times", providerCalls)
}
late, err := NewCommand(CommandSpec[struct{}]{Name: "late"})
if err != nil {
t.Fatal(err)
}
if err := app.Register(late, custom); err == nil {
t.Fatal("Register accepted a batch with an existing-name collision")
}
if err := app.Register(); err != nil {
t.Fatalf("empty registration failed: %v", err)
}
final := renderRootHelp()
if !strings.Contains(final, "custom") || !strings.Contains(final, "config") || !strings.Contains(final, "promptkit") || strings.Contains(final, "late") {
t.Fatalf("help after failed and empty registration = %q", final)
}
if outcome := app.Execute(context.Background(), []string{"custom"}, streams); outcome.Kind != commandkit.OutcomeSuccess {
t.Fatalf("new command did not parse after registration: %#v", outcome)
}
}

View File

@@ -94,77 +94,3 @@ func TestExecutionRefreshesHelpMetadataAfterRegistration(t *testing.T) {
t.Fatalf("usage after registration = %q", got)
}
}
func TestBridgeTreeRefreshesAfterSuccessfulRegistration(t *testing.T) {
app, err := New(Identity{Name: "example"})
if err != nil {
t.Fatal(err)
}
var output bytes.Buffer
streams := Streams{Out: &output, Err: io.Discard}
renderRootHelp := func() string {
t.Helper()
output.Reset()
outcome := app.Execute(context.Background(), []string{"--help"}, streams)
if outcome.Kind != OutcomeSuccess {
t.Fatalf("root help outcome = %#v", outcome)
}
return output.String()
}
initial := renderRootHelp()
if strings.Contains(initial, "custom") || strings.Contains(initial, "config") || strings.Contains(initial, "promptkit") {
t.Fatalf("initial help unexpectedly lists future registrations: %q", initial)
}
custom, err := NewCommand(CommandSpec[struct{}]{Name: "custom", Run: func(Invocation, struct{}) Outcome { return Success() }})
if err != nil {
t.Fatal(err)
}
if err := app.Register(custom); err != nil {
t.Fatal(err)
}
providerCalls := 0
if err := RegisterConfig(app, ConfigSuite[struct{}]{Scopes: []ConfigScope[struct{}]{{
Name: "app",
Operations: []ConfigOperation{ConfigShow},
Acquire: func(context.Context, ConfigRequest, struct{}) (Acquired[ConfigView], error) {
providerCalls++
return Acquired[ConfigView]{}, nil
},
}}}); err != nil {
t.Fatal(err)
}
if err := RegisterPromptKit(app, PromptKitSuite[struct{}]{InspectPrompt: func(context.Context, Selection, struct{}, string, string) (Acquired[*promptkit.PromptInspection], error) {
providerCalls++
return Acquired[*promptkit.PromptInspection]{}, nil
}}); err != nil {
t.Fatal(err)
}
updated := renderRootHelp()
for _, name := range []string{"custom", "config", "promptkit"} {
if !strings.Contains(updated, name) {
t.Errorf("updated help omits %q: %q", name, updated)
}
}
if providerCalls != 0 {
t.Fatalf("help invoked inspection or configuration callbacks %d times", providerCalls)
}
late, err := NewCommand(CommandSpec[struct{}]{Name: "late"})
if err != nil {
t.Fatal(err)
}
if err := app.Register(late, custom); err == nil {
t.Fatal("Register accepted a batch with an existing-name collision")
}
if err := app.Register(); err != nil {
t.Fatalf("empty registration failed: %v", err)
}
final := renderRootHelp()
if !strings.Contains(final, "custom") || !strings.Contains(final, "config") || !strings.Contains(final, "promptkit") || strings.Contains(final, "late") {
t.Fatalf("help after failed and empty registration = %q", final)
}
if outcome := app.Execute(context.Background(), []string{"custom"}, streams); outcome.Kind != OutcomeSuccess {
t.Fatalf("new command did not parse after registration: %#v", outcome)
}
}