Separate PromptKit messages from terminal context

This commit is contained in:
2026-10-26 03:26:14 +00:00
parent c0e0010683
commit 37dfea5b25
5 changed files with 218 additions and 145 deletions

View File

@@ -1,6 +1,6 @@
# Invocation reporting implementation plan # Invocation reporting implementation plan
Status: Stages 1–12 complete. Stage 13 is next. Status: Stages 1–13 complete. Stage 14 is next.
## Objective and authority ## Objective and authority
@@ -496,7 +496,7 @@ old invocation API and is scheduled for migration in Stage 17.
## Stage 13: Migrate PromptKit acquisition adapters ## Stage 13: Migrate PromptKit acquisition adapters
Status: pending. Status: complete.
Primary files: [adapters.go](../../integrations/ckprompt/adapters.go), Primary files: [adapters.go](../../integrations/ckprompt/adapters.go),
[validation.go](../../integrations/ckprompt/validation.go), [validation.go](../../integrations/ckprompt/validation.go),
@@ -521,6 +521,18 @@ Validation: establish correct forwarding, passive data, separated context,
and supported error identity. Preserve offline fixtures and existing builder/ and supported error identity. Preserve offline fixtures and existing builder/
acquisition lifecycle protections. No live provider or credentials are needed. acquisition lifecycle protections. No live provider or credentials are needed.
PromptKit engine loading now separates independent warning batches from native
terminal records and carries both through catalogs, inspection, requirements,
ConfigScope comparison, concrete request validation, and smoke acquisition.
Native rendering retains scope/side headings, source legends, and diagnostic
locations; original errors and reports returned with errors remain available.
Offline adapter tests cover mixed record separation, writer propagation,
comparison-side context, error identity, and existing request lifetimes. The
ckprompt suite cannot execute yet: compiler diagnostics identify legacy
reporting calls in `command.go`, `smoke_command.go`, and their tests, which are
scheduled for Stages 14 and 15. No compile errors were reported in the migrated
adapter files; Stage 20 will run the assembled suite.
## Stage 14: Migrate PromptKit listing and inspection commands ## Stage 14: Migrate PromptKit listing and inspection commands
Status: pending. Status: pending.

View File

@@ -88,26 +88,33 @@ type Suite[I, S any] struct {
// CheckInput validates selected inspection, requirements, validation, smoke, // CheckInput validates selected inspection, requirements, validation, smoke,
// or catalog input before state creation. // or catalog input before state creation.
CheckInput func(ckconfig.Selection, I) error CheckInput func(ckconfig.Selection, I) error
// InspectPrompt inspects the selected prompt after input and state preparation. // InspectPrompt inspects the selected prompt after input and state
// preparation. Its acquisition carries independent loader diagnostics and
// terminal context alongside any native inspection result.
InspectPrompt func(context.Context, PromptRequest, I, S) (commandkit.Acquired[*promptkit.PromptInspection], error) InspectPrompt func(context.Context, PromptRequest, I, S) (commandkit.Acquired[*promptkit.PromptInspection], error)
// InspectProfile inspects the selected profile after input and state preparation. // InspectProfile inspects the selected profile after input and state
// preparation. Its acquisition carries independent loader diagnostics and
// terminal context alongside any native inspection result.
InspectProfile func(context.Context, ProfileRequest, I, S) (commandkit.Acquired[*promptkit.ProfileInspection], error) InspectProfile func(context.Context, ProfileRequest, I, S) (commandkit.Acquired[*promptkit.ProfileInspection], error)
// InspectRequirements inspects native PromptKit requirements after input and // InspectRequirements inspects native PromptKit requirements after input and
// state preparation. It is optional; a nil callback omits // state preparation. Its acquisition carries independent loader diagnostics
// `promptkit requirements show` without disabling validation or other // and terminal context alongside any native inspection result. It is
// capabilities. // optional; a nil callback omits `promptkit requirements show` without
// disabling validation or other capabilities.
InspectRequirements func(context.Context, RequirementsRequest, I, S) (commandkit.Acquired[*promptkit.RequirementInspection], error) InspectRequirements func(context.Context, RequirementsRequest, I, S) (commandkit.Acquired[*promptkit.RequirementInspection], error)
// ValidateRequest validates a concrete PromptKit request after input and // ValidateRequest validates a concrete PromptKit request after input and
// state preparation. It is optional; a nil callback omits // state preparation. It is optional; a nil callback omits
// `promptkit requirements validate` without disabling discovery or other // `promptkit requirements validate` without disabling discovery or other
// capabilities. The acquisition carries diagnostics and an empty value on // capabilities. The acquisition carries independent diagnostics, terminal
// success; PromptKit errors are returned unchanged where supported. // context, and an empty value on success; PromptKit errors are returned
// unchanged where supported.
ValidateRequest func(context.Context, ValidationRequest, I, S) (commandkit.Acquired[struct{}], error) ValidateRequest func(context.Context, ValidationRequest, I, S) (commandkit.Acquired[struct{}], error)
// SmokeTest runs one concrete native smoke-test operation after input and // SmokeTest runs one concrete native smoke-test operation after input and
// state preparation. It is optional; nil omits only the smoke-test command. // state preparation. It is optional; nil omits only the smoke-test command.
// The acquisition retains a native report even when the callback also // The acquisition retains a native report, independent diagnostics, and
// returns an error. For `test all`, CommandKit calls the captured callback // terminal context even when the callback also returns an error. For
// sequentially for each selected kind after preparing input and state once. // `test all`, CommandKit calls the captured callback sequentially for each
// selected kind after preparing input and state once.
SmokeTest func(context.Context, SmokeRequest, I, S) (commandkit.Acquired[*promptkit.SmokeTestReport], error) SmokeTest func(context.Context, SmokeRequest, I, S) (commandkit.Acquired[*promptkit.SmokeTestReport], error)
// ListPrompts lists prompt identities after input and state preparation. // ListPrompts lists prompt identities after input and state preparation.
// It is optional independently of the other catalogs; nil omits only this // It is optional independently of the other catalogs; nil omits only this
@@ -143,9 +150,10 @@ type Suite[I, S any] struct {
// selection. A nil builder forwards the seed request unchanged. A builder // selection. A nil builder forwards the seed request unchanged. A builder
// failure prevents engine loading and preserves its supported error identity. // failure prevents engine loading and preserves its supported error identity.
// Native validation errors are returned with supported identity, and // Native validation errors are returned with supported identity, and
// configuration diagnostics from engine loading remain in the acquisition. A // configuration diagnostics and terminal context from engine loading remain
// nil loader is retained as a suite wiring issue and does not install a // separate in the acquisition. A nil loader is retained as a suite wiring
// validation callback. Callback references and objects remain caller-owned. // issue and does not install a validation callback. Callback references and
// objects remain caller-owned.
func WithRequestValidation[I, S any]( func WithRequestValidation[I, S any](
suite Suite[I, S], suite Suite[I, S],
load func(context.Context, ckconfig.Selection, I, S) (*promptkit.Engine, error), load func(context.Context, ckconfig.Selection, I, S) (*promptkit.Engine, error),
@@ -175,34 +183,39 @@ func WithRequestValidation[I, S any](
return commandkit.Acquired[struct{}]{}, fmt.Errorf("PromptKit request validation after building: %w", err) return commandkit.Acquired[struct{}]{}, fmt.Errorf("PromptKit request validation after building: %w", err)
} }
engine, _, diagnostics, err := loadPromptKitEngine(ctx, request.Selection, input, state, "", load) engine, _, messages, err := loadPromptKitEngine(ctx, request.Selection, input, state, "", load)
if err != nil { if err != nil {
return commandkit.Acquired[struct{}]{Diagnostics: diagnostics}, err return commandkit.Acquired[struct{}]{Diagnostics: messages.diagnostics, Explanation: messages.explanation}, err
} }
if err := promptKitContextError(ctx); err != nil { if err := promptKitContextError(ctx); err != nil {
return commandkit.Acquired[struct{}]{Diagnostics: diagnostics}, fmt.Errorf("PromptKit request validation before native validation: %w", err) return commandkit.Acquired[struct{}]{Diagnostics: messages.diagnostics, Explanation: messages.explanation}, fmt.Errorf("PromptKit request validation before native validation: %w", err)
} }
if err := engine.ValidateRequest(ctx, concreteRequest); err != nil { if err := engine.ValidateRequest(ctx, concreteRequest); err != nil {
return commandkit.Acquired[struct{}]{Diagnostics: diagnostics}, err return commandkit.Acquired[struct{}]{Diagnostics: messages.diagnostics, Explanation: messages.explanation}, err
} }
if err := promptKitContextError(ctx); err != nil { if err := promptKitContextError(ctx); err != nil {
return commandkit.Acquired[struct{}]{Diagnostics: diagnostics}, fmt.Errorf("PromptKit request validation after native validation: %w", err) return commandkit.Acquired[struct{}]{Diagnostics: messages.diagnostics, Explanation: messages.explanation}, fmt.Errorf("PromptKit request validation after native validation: %w", err)
} }
return commandkit.Acquired[struct{}]{Diagnostics: diagnostics}, nil return commandkit.Acquired[struct{}]{Diagnostics: messages.diagnostics, Explanation: messages.explanation}, nil
} }
return suite return suite
} }
type engineLoader[I, S any] func(context.Context, ckconfig.Selection, I, S) (*promptkit.Engine, error) type engineLoader[I, S any] func(context.Context, ckconfig.Selection, I, S) (*promptkit.Engine, error)
type promptKitAcquisition struct {
diagnostics []commandkit.Diagnostic
explanation commandkit.Explanation
}
// ConfigScope adapts PromptKit's captured configuration inspection to the // ConfigScope adapts PromptKit's captured configuration inspection to the
// standard configuration commands, including native comparison when the scope // standard configuration commands, including native comparison when the scope
// declares Diff. The loader is not called during declaration construction or // declares Diff. The loader is not called during declaration construction or
// installation; it is called only when a config operation selects the // installation; it is called only when a config operation selects the
// promptkit scope, with that operation's input and shared state. PromptKit // promptkit scope, with that operation's input and shared state. PromptKit
// diagnostics and supported error identity are returned through ckconfig's // independent diagnostics, terminal context, and supported error identity are
// acquisition and comparison contracts. // returned through ckconfig's acquisition and comparison contracts.
func ConfigScope[I, S any](load func(context.Context, ckconfig.Selection, I, S) (*promptkit.Engine, error)) ckconfig.Scope[I, S] { func ConfigScope[I, S any](load func(context.Context, ckconfig.Selection, I, S) (*promptkit.Engine, error)) ckconfig.Scope[I, S] {
scope := ckconfig.Scope[I, S]{ scope := ckconfig.Scope[I, S]{
Name: "promptkit", Name: "promptkit",
@@ -214,9 +227,9 @@ func ConfigScope[I, S any](load func(context.Context, ckconfig.Selection, I, S)
} }
scope.Acquire = func(ctx context.Context, request ckconfig.Request, input I, state S) (commandkit.Acquired[ckconfig.View], error) { scope.Acquire = func(ctx context.Context, request ckconfig.Request, input I, state S) (commandkit.Acquired[ckconfig.View], error) {
_, inspection, diagnostics, err := loadPromptKitEngine(ctx, request.Selection, input, state, "", load) _, inspection, messages, err := loadPromptKitEngine(ctx, request.Selection, input, state, "", load)
if err != nil { if err != nil {
return commandkit.Acquired[ckconfig.View]{Diagnostics: diagnostics}, err return commandkit.Acquired[ckconfig.View]{Diagnostics: messages.diagnostics, Explanation: messages.explanation}, err
} }
return commandkit.Acquired[ckconfig.View]{ return commandkit.Acquired[ckconfig.View]{
Value: ckconfig.View{ Value: ckconfig.View{
@@ -224,7 +237,8 @@ func ConfigScope[I, S any](load func(context.Context, ckconfig.Selection, I, S)
Sources: inspection, Sources: inspection,
Explanation: inspection, Explanation: inspection,
}, },
Diagnostics: diagnostics, Diagnostics: messages.diagnostics,
Explanation: messages.explanation,
}, nil }, nil
} }
scope.Compare = func(ctx context.Context, request ckconfig.ComparisonRequest[I], state S) (commandkit.Acquired[[]configkit.Difference], error) { scope.Compare = func(ctx context.Context, request ckconfig.ComparisonRequest[I], state S) (commandkit.Acquired[[]configkit.Difference], error) {
@@ -232,31 +246,30 @@ func ConfigScope[I, S any](load func(context.Context, ckconfig.Selection, I, S)
return commandkit.Acquired[[]configkit.Difference]{}, fmt.Errorf("PromptKit scope %q comparison before left load: %w", scope.Name, err) return commandkit.Acquired[[]configkit.Difference]{}, fmt.Errorf("PromptKit scope %q comparison before left load: %w", scope.Name, err)
} }
_, leftInspection, leftDiagnostics, err := loadPromptKitEngine(ctx, request.Left.Selection, request.Left.Input, state, "left", load) _, leftInspection, messages, err := loadPromptKitEngine(ctx, request.Left.Selection, request.Left.Input, state, "left", load)
diagnostics := append([]commandkit.Diagnostic(nil), leftDiagnostics...)
if err != nil { if err != nil {
return commandkit.Acquired[[]configkit.Difference]{Diagnostics: diagnostics}, fmt.Errorf("PromptKit scope %q left comparison load: %w", scope.Name, err) return commandkit.Acquired[[]configkit.Difference]{Diagnostics: messages.diagnostics, Explanation: messages.explanation}, fmt.Errorf("PromptKit scope %q left comparison load: %w", scope.Name, err)
} }
if err := promptKitContextError(ctx); err != nil { if err := promptKitContextError(ctx); err != nil {
return commandkit.Acquired[[]configkit.Difference]{Diagnostics: diagnostics}, fmt.Errorf("PromptKit scope %q comparison after left load: %w", scope.Name, err) return commandkit.Acquired[[]configkit.Difference]{Diagnostics: messages.diagnostics, Explanation: messages.explanation}, fmt.Errorf("PromptKit scope %q comparison after left load: %w", scope.Name, err)
} }
_, rightInspection, rightDiagnostics, err := loadPromptKitEngine(ctx, request.Right.Selection, request.Right.Input, state, "right", load) _, rightInspection, rightMessages, err := loadPromptKitEngine(ctx, request.Right.Selection, request.Right.Input, state, "right", load)
diagnostics = append(diagnostics, rightDiagnostics...) messages = appendPromptKitAcquisition(messages, rightMessages)
if err != nil { if err != nil {
return commandkit.Acquired[[]configkit.Difference]{Diagnostics: diagnostics}, fmt.Errorf("PromptKit scope %q right comparison load: %w", scope.Name, err) return commandkit.Acquired[[]configkit.Difference]{Diagnostics: messages.diagnostics, Explanation: messages.explanation}, fmt.Errorf("PromptKit scope %q right comparison load: %w", scope.Name, err)
} }
if err := promptKitContextError(ctx); err != nil { if err := promptKitContextError(ctx); err != nil {
return commandkit.Acquired[[]configkit.Difference]{Diagnostics: diagnostics}, fmt.Errorf("PromptKit scope %q comparison before native diff: %w", scope.Name, err) return commandkit.Acquired[[]configkit.Difference]{Diagnostics: messages.diagnostics, Explanation: messages.explanation}, fmt.Errorf("PromptKit scope %q comparison before native diff: %w", scope.Name, err)
} }
differences, err := leftInspection.Diff(rightInspection) differences, err := leftInspection.Diff(rightInspection)
if err != nil { if err != nil {
return commandkit.Acquired[[]configkit.Difference]{Diagnostics: diagnostics}, fmt.Errorf("PromptKit scope %q native configuration diff: %w", scope.Name, err) return commandkit.Acquired[[]configkit.Difference]{Diagnostics: messages.diagnostics, Explanation: messages.explanation}, fmt.Errorf("PromptKit scope %q native configuration diff: %w", scope.Name, err)
} }
return commandkit.Acquired[[]configkit.Difference]{Value: differences, Diagnostics: diagnostics}, nil return commandkit.Acquired[[]configkit.Difference]{Value: differences, Diagnostics: messages.diagnostics, Explanation: messages.explanation}, nil
} }
return scope return scope
} }
@@ -280,8 +293,8 @@ func promptKitContextError(ctx context.Context) error {
// shared invocation state to the loader before calling PromptKit's public // shared invocation state to the loader before calling PromptKit's public
// listing or inspection method. Applications can narrow, wrap, or replace these // listing or inspection method. Applications can narrow, wrap, or replace these
// callbacks before Command captures them. A nil loader is retained as an // callbacks before Command captures them. A nil loader is retained as an
// adapter issue. Each callback returns loader diagnostics with the native // adapter issue. Each callback returns independent loader diagnostics and
// operation's value and error. // terminal context alongside the native operation's value and error.
func FromEngine[I, S any](load func(context.Context, ckconfig.Selection, I, S) (*promptkit.Engine, error)) Suite[I, S] { func FromEngine[I, S any](load func(context.Context, ckconfig.Selection, I, S) (*promptkit.Engine, error)) Suite[I, S] {
if load == nil { if load == nil {
return Suite[I, S]{issues: []error{errors.New("PromptKit engine loader is nil")}} return Suite[I, S]{issues: []error{errors.New("PromptKit engine loader is nil")}}
@@ -289,68 +302,68 @@ func FromEngine[I, S any](load func(context.Context, ckconfig.Selection, I, S) (
return Suite[I, S]{ return Suite[I, S]{
ListPrompts: func(ctx context.Context, request CatalogRequest, input I, state S) (commandkit.Acquired[[]promptkit.PromptIdentity], error) { ListPrompts: func(ctx context.Context, request CatalogRequest, input I, state S) (commandkit.Acquired[[]promptkit.PromptIdentity], error) {
engine, _, diagnostics, err := loadPromptKitEngine(ctx, request.Selection, input, state, "", load) engine, _, messages, err := loadPromptKitEngine(ctx, request.Selection, input, state, "", load)
if err != nil { if err != nil {
return commandkit.Acquired[[]promptkit.PromptIdentity]{Diagnostics: diagnostics}, err return commandkit.Acquired[[]promptkit.PromptIdentity]{Diagnostics: messages.diagnostics, Explanation: messages.explanation}, err
} }
prompts, err := engine.ListPrompts(ctx, request.Filter) prompts, err := engine.ListPrompts(ctx, request.Filter)
return commandkit.Acquired[[]promptkit.PromptIdentity]{Value: prompts, Diagnostics: diagnostics}, err return commandkit.Acquired[[]promptkit.PromptIdentity]{Value: prompts, Diagnostics: messages.diagnostics, Explanation: messages.explanation}, err
}, },
ListProfiles: func(ctx context.Context, request CatalogRequest, input I, state S) (commandkit.Acquired[[]string], error) { ListProfiles: func(ctx context.Context, request CatalogRequest, input I, state S) (commandkit.Acquired[[]string], error) {
engine, _, diagnostics, err := loadPromptKitEngine(ctx, request.Selection, input, state, "", load) engine, _, messages, err := loadPromptKitEngine(ctx, request.Selection, input, state, "", load)
if err != nil { if err != nil {
return commandkit.Acquired[[]string]{Diagnostics: diagnostics}, err return commandkit.Acquired[[]string]{Diagnostics: messages.diagnostics, Explanation: messages.explanation}, err
} }
profiles, err := engine.ListProfiles(ctx, request.Filter) profiles, err := engine.ListProfiles(ctx, request.Filter)
return commandkit.Acquired[[]string]{Value: profiles, Diagnostics: diagnostics}, err return commandkit.Acquired[[]string]{Value: profiles, Diagnostics: messages.diagnostics, Explanation: messages.explanation}, err
}, },
ListBackends: func(ctx context.Context, request CatalogRequest, input I, state S) (commandkit.Acquired[[]string], error) { ListBackends: func(ctx context.Context, request CatalogRequest, input I, state S) (commandkit.Acquired[[]string], error) {
engine, _, diagnostics, err := loadPromptKitEngine(ctx, request.Selection, input, state, "", load) engine, _, messages, err := loadPromptKitEngine(ctx, request.Selection, input, state, "", load)
if err != nil { if err != nil {
return commandkit.Acquired[[]string]{Diagnostics: diagnostics}, err return commandkit.Acquired[[]string]{Diagnostics: messages.diagnostics, Explanation: messages.explanation}, err
} }
backends, err := engine.ListBackends(ctx, request.Filter) backends, err := engine.ListBackends(ctx, request.Filter)
return commandkit.Acquired[[]string]{Value: backends, Diagnostics: diagnostics}, err return commandkit.Acquired[[]string]{Value: backends, Diagnostics: messages.diagnostics, Explanation: messages.explanation}, err
}, },
ListNamespaces: func(ctx context.Context, request NamespaceRequest, input I, state S) (commandkit.Acquired[[]string], error) { ListNamespaces: func(ctx context.Context, request NamespaceRequest, input I, state S) (commandkit.Acquired[[]string], error) {
engine, _, diagnostics, err := loadPromptKitEngine(ctx, request.Selection, input, state, "", load) engine, _, messages, err := loadPromptKitEngine(ctx, request.Selection, input, state, "", load)
if err != nil { if err != nil {
return commandkit.Acquired[[]string]{Diagnostics: diagnostics}, err return commandkit.Acquired[[]string]{Diagnostics: messages.diagnostics, Explanation: messages.explanation}, err
} }
namespaces, err := engine.ListNamespaces(ctx, request.Kinds...) namespaces, err := engine.ListNamespaces(ctx, request.Kinds...)
return commandkit.Acquired[[]string]{Value: namespaces, Diagnostics: diagnostics}, err return commandkit.Acquired[[]string]{Value: namespaces, Diagnostics: messages.diagnostics, Explanation: messages.explanation}, err
}, },
ListCollections: func(ctx context.Context, selection ckconfig.Selection, input I, state S) (commandkit.Acquired[[]promptkit.CollectionInfo], error) { ListCollections: func(ctx context.Context, selection ckconfig.Selection, input I, state S) (commandkit.Acquired[[]promptkit.CollectionInfo], error) {
engine, _, diagnostics, err := loadPromptKitEngine(ctx, selection, input, state, "", load) engine, _, messages, err := loadPromptKitEngine(ctx, selection, input, state, "", load)
if err != nil { if err != nil {
return commandkit.Acquired[[]promptkit.CollectionInfo]{Diagnostics: diagnostics}, err return commandkit.Acquired[[]promptkit.CollectionInfo]{Diagnostics: messages.diagnostics, Explanation: messages.explanation}, err
} }
collections, err := engine.ListCollections(ctx) collections, err := engine.ListCollections(ctx)
return commandkit.Acquired[[]promptkit.CollectionInfo]{Value: collections, Diagnostics: diagnostics}, err return commandkit.Acquired[[]promptkit.CollectionInfo]{Value: collections, Diagnostics: messages.diagnostics, Explanation: messages.explanation}, err
}, },
InspectPrompt: func(ctx context.Context, request PromptRequest, input I, state S) (commandkit.Acquired[*promptkit.PromptInspection], error) { InspectPrompt: func(ctx context.Context, request PromptRequest, input I, state S) (commandkit.Acquired[*promptkit.PromptInspection], error) {
engine, _, diagnostics, err := loadPromptKitEngine(ctx, request.Selection, input, state, "", load) engine, _, messages, err := loadPromptKitEngine(ctx, request.Selection, input, state, "", load)
if err != nil { if err != nil {
return commandkit.Acquired[*promptkit.PromptInspection]{Diagnostics: diagnostics}, err return commandkit.Acquired[*promptkit.PromptInspection]{Diagnostics: messages.diagnostics, Explanation: messages.explanation}, err
} }
inspection, err := engine.InspectPrompt(ctx, request.ID) inspection, err := engine.InspectPrompt(ctx, request.ID)
return commandkit.Acquired[*promptkit.PromptInspection]{Value: inspection, Diagnostics: diagnostics}, err return commandkit.Acquired[*promptkit.PromptInspection]{Value: inspection, Diagnostics: messages.diagnostics, Explanation: messages.explanation}, err
}, },
InspectProfile: func(ctx context.Context, request ProfileRequest, input I, state S) (commandkit.Acquired[*promptkit.ProfileInspection], error) { InspectProfile: func(ctx context.Context, request ProfileRequest, input I, state S) (commandkit.Acquired[*promptkit.ProfileInspection], error) {
engine, _, diagnostics, err := loadPromptKitEngine(ctx, request.Selection, input, state, "", load) engine, _, messages, err := loadPromptKitEngine(ctx, request.Selection, input, state, "", load)
if err != nil { if err != nil {
return commandkit.Acquired[*promptkit.ProfileInspection]{Diagnostics: diagnostics}, err return commandkit.Acquired[*promptkit.ProfileInspection]{Diagnostics: messages.diagnostics, Explanation: messages.explanation}, err
} }
inspection, err := engine.InspectProfile(ctx, request.ID) inspection, err := engine.InspectProfile(ctx, request.ID)
return commandkit.Acquired[*promptkit.ProfileInspection]{Value: inspection, Diagnostics: diagnostics}, err return commandkit.Acquired[*promptkit.ProfileInspection]{Value: inspection, Diagnostics: messages.diagnostics, Explanation: messages.explanation}, err
}, },
InspectRequirements: func(ctx context.Context, request RequirementsRequest, input I, state S) (commandkit.Acquired[*promptkit.RequirementInspection], error) { InspectRequirements: func(ctx context.Context, request RequirementsRequest, input I, state S) (commandkit.Acquired[*promptkit.RequirementInspection], error) {
engine, _, diagnostics, err := loadPromptKitEngine(ctx, request.Selection, input, state, "", load) engine, _, messages, err := loadPromptKitEngine(ctx, request.Selection, input, state, "", load)
if err != nil { if err != nil {
return commandkit.Acquired[*promptkit.RequirementInspection]{Diagnostics: diagnostics}, err return commandkit.Acquired[*promptkit.RequirementInspection]{Diagnostics: messages.diagnostics, Explanation: messages.explanation}, err
} }
inspection, err := engine.InspectRequirements(ctx, request.Inspection) inspection, err := engine.InspectRequirements(ctx, request.Inspection)
return commandkit.Acquired[*promptkit.RequirementInspection]{Value: inspection, Diagnostics: diagnostics}, err return commandkit.Acquired[*promptkit.RequirementInspection]{Value: inspection, Diagnostics: messages.diagnostics, Explanation: messages.explanation}, err
}, },
} }
} }
@@ -362,41 +375,44 @@ func loadPromptKitEngine[I, S any](
state S, state S,
side string, side string,
load engineLoader[I, S], load engineLoader[I, S],
) (*promptkit.Engine, promptkit.ConfigurationInspection, []commandkit.Diagnostic, error) { ) (*promptkit.Engine, promptkit.ConfigurationInspection, promptKitAcquisition, error) {
engine, err := load(ctx, selection, input, state) engine, err := load(ctx, selection, input, state)
if err != nil { if err != nil {
var loadErr *configkit.LoadError var loadErr *configkit.LoadError
if errors.As(err, &loadErr) { if errors.As(err, &loadErr) {
return nil, promptkit.ConfigurationInspection{}, promptKitDiagnostics("promptkit", side, loadErr.Sources(), loadErr.Diagnostics()), err messages := promptKitDiagnostics("promptkit", side, loadErr.Sources(), loadErr.Diagnostics())
return nil, promptkit.ConfigurationInspection{}, messages, err
} }
return nil, promptkit.ConfigurationInspection{}, nil, err return nil, promptkit.ConfigurationInspection{}, promptKitAcquisition{}, err
} }
if engine == nil { if engine == nil {
return nil, promptkit.ConfigurationInspection{}, nil, errors.New("PromptKit configuration loader returned a nil engine") return nil, promptkit.ConfigurationInspection{}, promptKitAcquisition{}, errors.New("PromptKit configuration loader returned a nil engine")
} }
inspection, err := engine.InspectConfiguration() inspection, err := engine.InspectConfiguration()
messages := promptKitDiagnostics("promptkit", side, inspection.Sources(), inspection.Diagnostics())
if err != nil { if err != nil {
return nil, promptkit.ConfigurationInspection{}, nil, err return nil, inspection, messages, err
} }
diagnostics := promptKitDiagnostics("promptkit", side, inspection.Sources(), inspection.Diagnostics())
if ctx != nil { if ctx != nil {
if err := ctx.Err(); err != nil { if err := ctx.Err(); err != nil {
return nil, promptkit.ConfigurationInspection{}, diagnostics, err return nil, promptkit.ConfigurationInspection{}, messages, err
} }
} }
return engine, inspection, diagnostics, nil return engine, inspection, messages, nil
} }
func promptKitDiagnostics(scope, side string, sources []configkit.SourceInfo, records []configkit.Diagnostic) []commandkit.Diagnostic { func appendPromptKitAcquisition(left, right promptKitAcquisition) promptKitAcquisition {
if len(records) == 0 { left.diagnostics = append(left.diagnostics, right.diagnostics...)
return nil left.explanation.Details = append(left.explanation.Details, right.explanation.Details...)
if left.explanation.Message == "" {
left.explanation.Message = right.explanation.Message
} }
severity := commandkit.SeverityWarning return left
for _, record := range records { }
if record.Severity == configkit.SeverityError {
severity = commandkit.SeverityError func promptKitDiagnostics(scope, side string, sources []configkit.SourceInfo, records []configkit.Diagnostic) promptKitAcquisition {
break if len(records) == 0 {
} return promptKitAcquisition{}
} }
// Snapshot provider-owned slices before deferring their report to invocation. // Snapshot provider-owned slices before deferring their report to invocation.
sources = append([]configkit.SourceInfo(nil), sources...) sources = append([]configkit.SourceInfo(nil), sources...)
@@ -408,27 +424,48 @@ func promptKitDiagnostics(scope, side string, sources []configkit.SourceInfo, re
} }
records[index].Related = append([]configkit.Location(nil), records[index].Related...) records[index].Related = append([]configkit.Location(nil), records[index].Related...)
} }
return []commandkit.Diagnostic{{ var warnings, failures []configkit.Diagnostic
Severity: severity, for _, record := range records {
Write: func(writer io.Writer) error { if record.Severity == configkit.SeverityError {
heading := "Configuration scope " + strconv.Quote(scope) failures = append(failures, record)
if side != "" { } else {
heading += " (" + side + ")" warnings = append(warnings, record)
} }
if _, err := fmt.Fprintf(writer, "%s:\n", heading); err != nil { }
messages := promptKitAcquisition{}
if len(warnings) > 0 {
messages.diagnostics = []commandkit.Diagnostic{{
Severity: commandkit.SeverityWarning,
Write: func(writer io.Writer) error {
return writePromptKitDiagnostics(writer, scope, side, sources, warnings)
},
}}
}
if len(failures) > 0 {
messages.explanation = commandkit.Explanation{Details: []commandkit.Detail{func(writer io.Writer) error {
return writePromptKitDiagnostics(writer, scope, side, sources, failures)
}}}
}
return messages
}
func writePromptKitDiagnostics(writer io.Writer, scope, side string, sources []configkit.SourceInfo, records []configkit.Diagnostic) error {
heading := "Configuration scope " + strconv.Quote(scope)
if side != "" {
heading += " (" + side + ")"
}
if _, err := fmt.Fprintf(writer, "%s:\n", heading); err != nil {
return err
}
if len(sources) > 0 {
if _, err := io.WriteString(writer, "Source IDs:\n"); err != nil {
return err
}
for _, source := range sources {
if _, err := fmt.Fprintf(writer, " %s: label=%s, path=%s\n", strconv.Quote(source.ID), strconv.Quote(source.Label), strconv.Quote(source.Path)); err != nil {
return err return err
} }
if len(sources) > 0 { }
if _, err := io.WriteString(writer, "Source IDs:\n"); err != nil { }
return err return configkit.WriteDiagnostics(writer, records)
}
for _, source := range sources {
if _, err := fmt.Fprintf(writer, " %s: label=%s, path=%s\n", strconv.Quote(source.ID), strconv.Quote(source.Label), strconv.Quote(source.Path)); err != nil {
return err
}
}
}
return configkit.WriteDiagnostics(writer, records)
},
}}
} }

View File

@@ -239,7 +239,7 @@ func TestWithSmokeTestingRetainsNilLoaderIssueAndChecksCancellationBeforeBuild(t
} }
} }
func TestWithSmokeTestingRetainsLoaderDiagnosticsAndErrorIdentity(t *testing.T) { func TestWithSmokeTestingRetainsLoaderExplanationAndErrorIdentity(t *testing.T) {
_, loadErr := configkit.Load[map[string]any](configkit.Bytes("invalid smoke configuration", []byte("broken: ["))) _, loadErr := configkit.Load[map[string]any](configkit.Bytes("invalid smoke configuration", []byte("broken: [")))
if loadErr == nil { if loadErr == nil {
t.Fatal("invalid configuration unexpectedly loaded") t.Fatal("invalid configuration unexpectedly loaded")
@@ -249,7 +249,7 @@ func TestWithSmokeTestingRetainsLoaderDiagnosticsAndErrorIdentity(t *testing.T)
}, nil) }, nil)
acquired, err := suite.SmokeTest(context.Background(), SmokeRequest{}, 0, 0) acquired, err := suite.SmokeTest(context.Background(), SmokeRequest{}, 0, 0)
var retained *configkit.LoadError var retained *configkit.LoadError
if !errors.Is(err, loadErr) || !errors.As(err, &retained) || retained != loadErr || acquired.Value != nil || len(acquired.Diagnostics) == 0 { if !errors.Is(err, loadErr) || !errors.As(err, &retained) || retained != loadErr || acquired.Value != nil || len(acquired.Explanation.Details) == 0 {
t.Fatalf("smoke load failure = (%#v, %v)", acquired, err) t.Fatalf("smoke load failure = (%#v, %v)", acquired, err)
} }
} }
@@ -394,7 +394,7 @@ func TestConfigScopeCompareStopsAfterLeftLoadFailure(t *testing.T) {
} }
} }
func TestConfigScopeCompareRetainsRightLoadDiagnosticsAndErrorIdentity(t *testing.T) { func TestConfigScopeCompareRetainsRightLoadExplanationAndErrorIdentity(t *testing.T) {
_, loadErr := configkit.Load[map[string]any](configkit.Bytes("invalid right prompt config", []byte("broken: ["))) _, loadErr := configkit.Load[map[string]any](configkit.Bytes("invalid right prompt config", []byte("broken: [")))
if loadErr == nil { if loadErr == nil {
t.Fatal("invalid right configuration unexpectedly loaded") t.Fatal("invalid right configuration unexpectedly loaded")
@@ -415,12 +415,12 @@ func TestConfigScopeCompareRetainsRightLoadDiagnosticsAndErrorIdentity(t *testin
acquired, err := scope.Compare(context.Background(), request, struct{}{}) acquired, err := scope.Compare(context.Background(), request, struct{}{})
var retained *configkit.LoadError var retained *configkit.LoadError
if !errors.Is(err, loadErr) || !errors.As(err, &retained) || retained != loadErr || len(calls) != 2 || len(acquired.Diagnostics) == 0 { if !errors.Is(err, loadErr) || !errors.As(err, &retained) || retained != loadErr || len(calls) != 2 || len(acquired.Explanation.Details) == 0 {
t.Fatalf("right failure = (%#v, %v), calls=%#v", acquired, err, calls) t.Fatalf("right failure = (%#v, %v), calls=%#v", acquired, err, calls)
} }
var output bytes.Buffer var output bytes.Buffer
if writeErr := acquired.Diagnostics[0].Write(&output); writeErr != nil || !strings.Contains(output.String(), `Configuration scope "promptkit" (right):`) { if writeErr := acquired.Explanation.Details[0](&output); writeErr != nil || !strings.Contains(output.String(), `Configuration scope "promptkit" (right):`) {
t.Fatalf("right diagnostic = %q, %v", output.String(), writeErr) t.Fatalf("right explanation = %q, %v", output.String(), writeErr)
} }
} }
@@ -534,7 +534,7 @@ func TestFromEngineInspectsRequirementsWithNativeRequest(t *testing.T) {
} }
} }
func TestFromEngineRequirementsRetainLoadDiagnosticsAndSelectionErrors(t *testing.T) { func TestFromEngineRequirementsRetainLoadExplanationAndSelectionErrors(t *testing.T) {
_, loadErr := configkit.Load[map[string]any](configkit.Bytes("invalid requirements configuration", []byte("broken: ["))) _, loadErr := configkit.Load[map[string]any](configkit.Bytes("invalid requirements configuration", []byte("broken: [")))
if loadErr == nil { if loadErr == nil {
t.Fatal("invalid configuration unexpectedly loaded") t.Fatal("invalid configuration unexpectedly loaded")
@@ -544,11 +544,8 @@ func TestFromEngineRequirementsRetainLoadDiagnosticsAndSelectionErrors(t *testin
}) })
acquired, err := loadFailure.InspectRequirements(context.Background(), RequirementsRequest{}, 0, 0) acquired, err := loadFailure.InspectRequirements(context.Background(), RequirementsRequest{}, 0, 0)
var retained *configkit.LoadError var retained *configkit.LoadError
if !errors.As(err, &retained) || retained != loadErr || len(acquired.Diagnostics) == 0 || acquired.Value != nil { if !errors.As(err, &retained) || retained != loadErr || len(acquired.Explanation.Details) == 0 || acquired.Value != nil {
t.Fatalf("requirements load failure = (%#v, %v), want original ConfigKit error and diagnostics", acquired, err) t.Fatalf("requirements load failure = (%#v, %v), want original ConfigKit error and terminal context", acquired, err)
}
if acquired.Diagnostics[0].Severity != commandkit.SeverityError {
t.Fatalf("load diagnostics = %#v, want error severity", acquired.Diagnostics)
} }
nilEngine := FromEngine[int, int](func(context.Context, ckconfig.Selection, int, int) (*promptkit.Engine, error) { nilEngine := FromEngine[int, int](func(context.Context, ckconfig.Selection, int, int) (*promptkit.Engine, error) {
@@ -651,7 +648,7 @@ func TestFromEngineListsNativeCatalogsWithInvocationValues(t *testing.T) {
} }
} }
func TestFromEngineCatalogsRetainLoadDiagnosticsAndErrorIdentity(t *testing.T) { func TestFromEngineCatalogsRetainLoadExplanationAndErrorIdentity(t *testing.T) {
_, loadErr := configkit.Load[map[string]any](configkit.Bytes("invalid catalog configuration", []byte("broken: ["))) _, loadErr := configkit.Load[map[string]any](configkit.Bytes("invalid catalog configuration", []byte("broken: [")))
if loadErr == nil { if loadErr == nil {
t.Fatal("invalid configuration unexpectedly loaded") t.Fatal("invalid configuration unexpectedly loaded")
@@ -666,42 +663,42 @@ func TestFromEngineCatalogsRetainLoadDiagnosticsAndErrorIdentity(t *testing.T) {
{name: "prompts", call: func() (int, error) { {name: "prompts", call: func() (int, error) {
acquired, err := suite.ListPrompts(context.Background(), CatalogRequest{}, 0, 0) acquired, err := suite.ListPrompts(context.Background(), CatalogRequest{}, 0, 0)
var retained *configkit.LoadError var retained *configkit.LoadError
if !errors.As(err, &retained) || retained != loadErr || len(acquired.Diagnostics) == 0 { if !errors.As(err, &retained) || retained != loadErr || len(acquired.Explanation.Details) == 0 {
t.Errorf("prompt load failure = (%#v, %v)", acquired, err) t.Errorf("prompt load failure = (%#v, %v)", acquired, err)
} }
return len(acquired.Diagnostics), err return len(acquired.Diagnostics) + len(acquired.Explanation.Details), err
}}, }},
{name: "profiles", call: func() (int, error) { {name: "profiles", call: func() (int, error) {
acquired, err := suite.ListProfiles(context.Background(), CatalogRequest{}, 0, 0) acquired, err := suite.ListProfiles(context.Background(), CatalogRequest{}, 0, 0)
var retained *configkit.LoadError var retained *configkit.LoadError
if !errors.As(err, &retained) || retained != loadErr || len(acquired.Diagnostics) == 0 { if !errors.As(err, &retained) || retained != loadErr || len(acquired.Explanation.Details) == 0 {
t.Errorf("profile load failure = (%#v, %v)", acquired, err) t.Errorf("profile load failure = (%#v, %v)", acquired, err)
} }
return len(acquired.Diagnostics), err return len(acquired.Diagnostics) + len(acquired.Explanation.Details), err
}}, }},
{name: "backends", call: func() (int, error) { {name: "backends", call: func() (int, error) {
acquired, err := suite.ListBackends(context.Background(), CatalogRequest{}, 0, 0) acquired, err := suite.ListBackends(context.Background(), CatalogRequest{}, 0, 0)
var retained *configkit.LoadError var retained *configkit.LoadError
if !errors.As(err, &retained) || retained != loadErr || len(acquired.Diagnostics) == 0 { if !errors.As(err, &retained) || retained != loadErr || len(acquired.Explanation.Details) == 0 {
t.Errorf("backend load failure = (%#v, %v)", acquired, err) t.Errorf("backend load failure = (%#v, %v)", acquired, err)
} }
return len(acquired.Diagnostics), err return len(acquired.Diagnostics) + len(acquired.Explanation.Details), err
}}, }},
{name: "namespaces", call: func() (int, error) { {name: "namespaces", call: func() (int, error) {
acquired, err := suite.ListNamespaces(context.Background(), NamespaceRequest{}, 0, 0) acquired, err := suite.ListNamespaces(context.Background(), NamespaceRequest{}, 0, 0)
var retained *configkit.LoadError var retained *configkit.LoadError
if !errors.As(err, &retained) || retained != loadErr || len(acquired.Diagnostics) == 0 { if !errors.As(err, &retained) || retained != loadErr || len(acquired.Explanation.Details) == 0 {
t.Errorf("namespace load failure = (%#v, %v)", acquired, err) t.Errorf("namespace load failure = (%#v, %v)", acquired, err)
} }
return len(acquired.Diagnostics), err return len(acquired.Diagnostics) + len(acquired.Explanation.Details), err
}}, }},
{name: "collections", call: func() (int, error) { {name: "collections", call: func() (int, error) {
acquired, err := suite.ListCollections(context.Background(), ckconfig.Selection{}, 0, 0) acquired, err := suite.ListCollections(context.Background(), ckconfig.Selection{}, 0, 0)
var retained *configkit.LoadError var retained *configkit.LoadError
if !errors.As(err, &retained) || retained != loadErr || len(acquired.Diagnostics) == 0 { if !errors.As(err, &retained) || retained != loadErr || len(acquired.Explanation.Details) == 0 {
t.Errorf("collection load failure = (%#v, %v)", acquired, err) t.Errorf("collection load failure = (%#v, %v)", acquired, err)
} }
return len(acquired.Diagnostics), err return len(acquired.Diagnostics) + len(acquired.Explanation.Details), err
}}, }},
} { } {
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
@@ -782,7 +779,7 @@ func TestAdapterAcquisitionPreservesLoadAndContextErrors(t *testing.T) {
} }
} }
func TestFromEngineRetainsConfigKitLoadDiagnostics(t *testing.T) { func TestFromEngineRetainsConfigKitLoadExplanation(t *testing.T) {
_, loadErr := configkit.Load[map[string]any](configkit.Bytes("invalid PromptKit config", []byte("broken: ["))) _, loadErr := configkit.Load[map[string]any](configkit.Bytes("invalid PromptKit config", []byte("broken: [")))
if loadErr == nil { if loadErr == nil {
t.Fatal("invalid configuration unexpectedly loaded") t.Fatal("invalid configuration unexpectedly loaded")
@@ -793,11 +790,8 @@ func TestFromEngineRetainsConfigKitLoadDiagnostics(t *testing.T) {
acquired, err := suite.InspectProfile(context.Background(), ProfileRequest{ID: "default"}, 0, 0) acquired, err := suite.InspectProfile(context.Background(), ProfileRequest{ID: "default"}, 0, 0)
var retained *configkit.LoadError var retained *configkit.LoadError
if !errors.As(err, &retained) || retained != loadErr || len(acquired.Diagnostics) == 0 { if !errors.As(err, &retained) || retained != loadErr || len(acquired.Explanation.Details) == 0 {
t.Fatalf("load failure = (%#v, %v), want original ConfigKit error and diagnostics", acquired, err) t.Fatalf("load failure = (%#v, %v), want original ConfigKit error and terminal context", acquired, err)
}
if acquired.Diagnostics[0].Severity != commandkit.SeverityError {
t.Fatalf("load diagnostics = %#v, want error severity", acquired.Diagnostics)
} }
} }
@@ -823,25 +817,55 @@ func TestPromptKitDiagnosticsRemainDeferredAndPropagateWriterErrors(t *testing.T
Severity: configkit.SeverityWarning, Severity: configkit.SeverityWarning,
Message: "safe warning", Message: "safe warning",
}} }}
diagnostics := promptKitDiagnostics("promptkit", "", sources, records) messages := promptKitDiagnostics("promptkit", "", sources, records)
if len(diagnostics) != 1 || diagnostics[0].Severity != commandkit.SeverityWarning { if len(messages.diagnostics) != 1 || messages.diagnostics[0].Severity != commandkit.SeverityWarning || len(messages.explanation.Details) != 0 {
t.Fatalf("diagnostics = %#v", diagnostics) t.Fatalf("diagnostic messages = %#v", messages)
} }
if err := diagnostics[0].Write(failingDiagnosticWriter{err: writeErr}); !errors.Is(err, writeErr) { if err := messages.diagnostics[0].Write(failingDiagnosticWriter{err: writeErr}); !errors.Is(err, writeErr) {
t.Fatalf("diagnostic write error = %v", err) t.Fatalf("diagnostic write error = %v", err)
} }
var output bytes.Buffer var output bytes.Buffer
if err := diagnostics[0].Write(&output); err != nil || !strings.Contains(output.String(), `Configuration scope "promptkit":`) || !strings.Contains(output.String(), "safe warning") || !strings.Contains(output.String(), `"source"`) { if err := messages.diagnostics[0].Write(&output); err != nil || !strings.Contains(output.String(), `Configuration scope "promptkit":`) || !strings.Contains(output.String(), "safe warning") || !strings.Contains(output.String(), `"source"`) {
t.Fatalf("diagnostic text = %q, %v", output.String(), err) t.Fatalf("diagnostic text = %q, %v", output.String(), err)
} }
sideDiagnostics := promptKitDiagnostics("promptkit", "left", sources, records) sideDiagnostics := promptKitDiagnostics("promptkit", "left", sources, records)
output.Reset() output.Reset()
if err := sideDiagnostics[0].Write(&output); err != nil || !strings.Contains(output.String(), `Configuration scope "promptkit" (left):`) { if err := sideDiagnostics.diagnostics[0].Write(&output); err != nil || !strings.Contains(output.String(), `Configuration scope "promptkit" (left):`) {
t.Fatalf("comparison-side diagnostic text = %q, %v", output.String(), err) t.Fatalf("comparison-side diagnostic text = %q, %v", output.String(), err)
} }
} }
func TestPromptKitDiagnosticsSeparateIndependentWarningsAndTerminalRecords(t *testing.T) {
sources := []configkit.SourceInfo{{ID: "source", Label: "safe label", Path: "config.yml"}}
primary := configkit.Location{SourceID: "source", Line: 3, Column: 2}
related := configkit.Location{SourceID: "base.yml", Line: 7, Column: 4}
messages := promptKitDiagnostics("promptkit", "right", sources, []configkit.Diagnostic{
{Severity: configkit.SeverityWarning, Message: "independent warning", Primary: &primary, Related: []configkit.Location{related}},
{Severity: configkit.SeverityError, Message: "terminal configuration error", Primary: &related, Related: []configkit.Location{primary}},
})
if len(messages.diagnostics) != 1 || len(messages.explanation.Details) != 1 {
t.Fatalf("mixed acquisition messages = %#v", messages)
}
writeErr := errors.New("terminal explanation writer failed")
if err := messages.explanation.Details[0](failingDiagnosticWriter{err: writeErr}); !errors.Is(err, writeErr) {
t.Fatalf("terminal explanation write error = %v", err)
}
var warning, terminal bytes.Buffer
if err := messages.diagnostics[0].Write(&warning); err != nil {
t.Fatal(err)
}
if err := messages.explanation.Details[0](&terminal); err != nil {
t.Fatal(err)
}
if got := warning.String(); !strings.Contains(got, "independent warning") || !strings.Contains(got, `Configuration scope "promptkit" (right):`) || !strings.Contains(got, `"source": label="safe label", path="config.yml"`) || !strings.Contains(got, "source:3:2") || !strings.Contains(got, "related: base.yml:7:4") || strings.Contains(got, "terminal configuration error") {
t.Fatalf("independent warning report = %q", got)
}
if got := terminal.String(); !strings.Contains(got, "terminal configuration error") || !strings.Contains(got, `Configuration scope "promptkit" (right):`) || !strings.Contains(got, `"source": label="safe label", path="config.yml"`) || !strings.Contains(got, "base.yml:7:4") || !strings.Contains(got, "related: source:3:2") || strings.Contains(got, "independent warning") {
t.Fatalf("terminal explanation report = %q", got)
}
}
type failingDiagnosticWriter struct{ err error } type failingDiagnosticWriter struct{ err error }
func (writer failingDiagnosticWriter) Write([]byte) (int, error) { return 0, writer.err } func (writer failingDiagnosticWriter) Write([]byte) (int, error) { return 0, writer.err }

View File

@@ -26,8 +26,8 @@ type SmokeRequest struct {
// invocation context. Its complete returned request is passed to PromptKit; // invocation context. Its complete returned request is passed to PromptKit;
// without a builder, the seed is forwarded unchanged. Reports and errors from // without a builder, the seed is forwarded unchanged. Reports and errors from
// PromptKit are both retained. A nil loader is kept as a suite wiring issue and // PromptKit are both retained. A nil loader is kept as a suite wiring issue and
// does not install a smoke-test callback. The callback also retains engine // does not install a smoke-test callback. The callback also retains independent
// loading diagnostics with the report and error. // engine diagnostics and terminal context with the report and error.
func WithSmokeTesting[I, S any]( func WithSmokeTesting[I, S any](
suite Suite[I, S], suite Suite[I, S],
load func(context.Context, ckconfig.Selection, I, S) (*promptkit.Engine, error), load func(context.Context, ckconfig.Selection, I, S) (*promptkit.Engine, error),
@@ -56,16 +56,16 @@ func WithSmokeTesting[I, S any](
return commandkit.Acquired[*promptkit.SmokeTestReport]{}, fmt.Errorf("PromptKit smoke test after building: %w", err) return commandkit.Acquired[*promptkit.SmokeTestReport]{}, fmt.Errorf("PromptKit smoke test after building: %w", err)
} }
engine, _, diagnostics, err := loadPromptKitEngine(ctx, request.Selection, input, state, "", load) engine, _, messages, err := loadPromptKitEngine(ctx, request.Selection, input, state, "", load)
if err != nil { if err != nil {
return commandkit.Acquired[*promptkit.SmokeTestReport]{Diagnostics: diagnostics}, err return commandkit.Acquired[*promptkit.SmokeTestReport]{Diagnostics: messages.diagnostics, Explanation: messages.explanation}, err
} }
if err := promptKitContextError(ctx); err != nil { if err := promptKitContextError(ctx); err != nil {
return commandkit.Acquired[*promptkit.SmokeTestReport]{Diagnostics: diagnostics}, fmt.Errorf("PromptKit smoke test before native operation: %w", err) return commandkit.Acquired[*promptkit.SmokeTestReport]{Diagnostics: messages.diagnostics, Explanation: messages.explanation}, fmt.Errorf("PromptKit smoke test before native operation: %w", err)
} }
report, err := engine.SmokeTest(ctx, concreteRequest) report, err := engine.SmokeTest(ctx, concreteRequest)
return commandkit.Acquired[*promptkit.SmokeTestReport]{Value: report, Diagnostics: diagnostics}, err return commandkit.Acquired[*promptkit.SmokeTestReport]{Value: report, Diagnostics: messages.diagnostics, Explanation: messages.explanation}, err
} }
return suite return suite
} }

View File

@@ -213,7 +213,7 @@ func TestWithRequestValidationBuilderOutputReachesNativeValidation(t *testing.T)
} }
} }
func TestWithRequestValidationRetainsNilEngineAndLoadDiagnostics(t *testing.T) { func TestWithRequestValidationRetainsNilEngineAndLoadExplanation(t *testing.T) {
request := ValidationRequest{Request: promptkit.RunRequest{PromptID: "empty", ProfileID: "default"}} request := ValidationRequest{Request: promptkit.RunRequest{PromptID: "empty", ProfileID: "default"}}
nilEngineSuite := WithRequestValidation(Suite[struct{}, struct{}]{}, func(context.Context, ckconfig.Selection, struct{}, struct{}) (*promptkit.Engine, error) { nilEngineSuite := WithRequestValidation(Suite[struct{}, struct{}]{}, func(context.Context, ckconfig.Selection, struct{}, struct{}) (*promptkit.Engine, error) {
return nil, nil return nil, nil
@@ -232,8 +232,8 @@ func TestWithRequestValidationRetainsNilEngineAndLoadDiagnostics(t *testing.T) {
}, nil) }, nil)
acquired, err = loadFailure.ValidateRequest(context.Background(), request, struct{}{}, struct{}{}) acquired, err = loadFailure.ValidateRequest(context.Background(), request, struct{}{}, struct{}{})
var retained *configkit.LoadError var retained *configkit.LoadError
if !errors.Is(err, loadErr) || !errors.As(err, &retained) || retained != loadErr || len(acquired.Diagnostics) == 0 { if !errors.Is(err, loadErr) || !errors.As(err, &retained) || retained != loadErr || len(acquired.Explanation.Details) == 0 {
t.Fatalf("load failure = (%#v, %v), want original ConfigKit error and diagnostics", acquired, err) t.Fatalf("load failure = (%#v, %v), want original ConfigKit error and terminal context", acquired, err)
} }
} }
@@ -289,7 +289,7 @@ func TestWithRequestValidationCancellationBoundaries(t *testing.T) {
}, nil) }, nil)
acquired, err := suite.ValidateRequest(ctx, request, 0, 0) acquired, err := suite.ValidateRequest(ctx, request, 0, 0)
var retained *configkit.LoadError var retained *configkit.LoadError
if !errors.Is(err, loadErr) || !errors.As(err, &retained) || retained != loadErr || len(acquired.Diagnostics) == 0 || loaderCalls != 1 { if !errors.Is(err, loadErr) || !errors.As(err, &retained) || retained != loadErr || len(acquired.Explanation.Details) == 0 || loaderCalls != 1 {
t.Fatalf("cancelled load = (%#v, %v), loader calls=%d", acquired, err, loaderCalls) t.Fatalf("cancelled load = (%#v, %v), loader calls=%d", acquired, err, loaderCalls)
} }
}) })