Move integration code into optional modules
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package commandkit
|
||||
package ckconfig
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -1,4 +1,4 @@
|
||||
package commandkit
|
||||
package ckconfig
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -1,4 +1,4 @@
|
||||
package commandkit_test
|
||||
package ckconfig_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
2
integrations/ckconfig/doc.go
Normal file
2
integrations/ckconfig/doc.go
Normal file
@@ -0,0 +1,2 @@
|
||||
// Package ckconfig provides CommandKit's optional ConfigKit integration.
|
||||
package ckconfig
|
||||
@@ -1,4 +1,4 @@
|
||||
package commandkit
|
||||
package ckconfig
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
9
integrations/ckconfig/go.mod
Normal file
9
integrations/ckconfig/go.mod
Normal 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
|
||||
)
|
||||
@@ -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
|
||||
@@ -1,4 +1,4 @@
|
||||
package commandkit
|
||||
package ckconfig
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -1,4 +1,4 @@
|
||||
package commandkit
|
||||
package ckconfig
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -1,4 +1,4 @@
|
||||
package commandkit
|
||||
package ckconfig
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -1,4 +1,4 @@
|
||||
package commandkit
|
||||
package ckconfig
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
53
integrations/ckprompt/cancellation_test.go
Normal file
53
integrations/ckprompt/cancellation_test.go
Normal 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())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
2
integrations/ckprompt/doc.go
Normal file
2
integrations/ckprompt/doc.go
Normal file
@@ -0,0 +1,2 @@
|
||||
// Package ckprompt integrates PromptKit inspection with CommandKit.
|
||||
package ckprompt
|
||||
10
integrations/ckprompt/go.mod
Normal file
10
integrations/ckprompt/go.mod
Normal 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
|
||||
)
|
||||
161
integrations/ckprompt/performance_test.go
Normal file
161
integrations/ckprompt/performance_test.go
Normal 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)])
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package commandkit
|
||||
package ckprompt
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -1,4 +1,4 @@
|
||||
package commandkit
|
||||
package ckprompt
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -1,4 +1,4 @@
|
||||
package commandkit_test
|
||||
package ckprompt_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -1,4 +1,4 @@
|
||||
package commandkit_test
|
||||
package ckprompt_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
85
integrations/ckprompt/registration_test.go
Normal file
85
integrations/ckprompt/registration_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user