2026-10-11 01:43:02 +00:00
package ckprompt
import (
"context"
"errors"
"fmt"
"io"
"strconv"
commandkit "gitea.maximumdirect.net/eric/CommandKit"
"gitea.maximumdirect.net/eric/CommandKit/integrations/ckconfig"
configkit "gitea.maximumdirect.net/eric/ConfigKit"
promptkit "gitea.maximumdirect.net/eric/promptkit"
)
// PromptRequest selects one named prompt for inspection.
type PromptRequest struct {
2026-10-11 02:06:06 +00:00
// Selection carries the configuration and overlay values supplied to the command.
2026-10-11 01:43:02 +00:00
Selection ckconfig . Selection
2026-10-11 02:06:06 +00:00
// ID is the selected named prompt.
ID string
// Version carries the explicitly supplied prompt version, if present.
Version commandkit . Supplied [ string ]
2026-10-11 01:43:02 +00:00
}
// ProfileRequest selects one named profile for inspection.
type ProfileRequest struct {
2026-10-11 02:06:06 +00:00
// Selection carries the configuration and overlay values supplied to the command.
2026-10-11 01:43:02 +00:00
Selection ckconfig . Selection
2026-10-11 02:06:06 +00:00
// ID is the selected named profile.
ID string
2026-10-11 01:43:02 +00:00
}
2026-10-12 02:10:45 +00:00
// Suite describes application-owned input, shared state, and optional catalog
// and named inspection capabilities. Its private issues retain adapter
// construction failures.
2026-10-11 13:07:00 +00:00
// Factory results and callback effects remain caller-owned: Command does not
// deep-copy arbitrary objects or repair aliases returned by factories.
// Factories are not invoked during construction, installation, help, or version,
// and need not run after failure.
2026-10-11 01:43:02 +00:00
type Suite [ I , S any ] struct {
2026-10-11 13:07:00 +00:00
// NewInput is called once when definition input preparation is reached,
// before applying selectors and calling CheckInput.
// If nil, input is the zero value of I, including nil for pointer types.
2026-10-11 02:06:06 +00:00
NewInput func ( ) I
2026-10-11 13:07:00 +00:00
// NewState is called once when inspection is reached, after input checks
// pass. If nil, state is the zero value of S, including nil for pointer types.
2026-10-11 02:06:06 +00:00
NewState func ( ) S
2026-10-12 02:10:45 +00:00
// Selectors declares the original typed flags accepted by definition and
// catalog commands.
2026-10-11 02:06:06 +00:00
Selectors [ ] commandkit . Flag [ I ]
2026-10-12 02:10:45 +00:00
// CheckInput validates selected definition or catalog input before state
// creation.
2026-10-11 02:06:06 +00:00
CheckInput func ( ckconfig . Selection , I ) error
// InspectPrompt inspects the selected prompt after input and state preparation.
InspectPrompt func ( context . Context , PromptRequest , I , S ) ( commandkit . Acquired [ * promptkit . PromptInspection ] , error )
// InspectProfile inspects the selected profile after input and state preparation.
2026-10-11 01:43:02 +00:00
InspectProfile func ( context . Context , ProfileRequest , I , S ) ( commandkit . Acquired [ * promptkit . ProfileInspection ] , error )
2026-10-12 02:10:45 +00:00
// ListPrompts lists prompt identities after input and state preparation.
// It is optional; a nil callback does not install a prompt catalog command.
ListPrompts func ( context . Context , ckconfig . Selection , I , S ) ( commandkit . Acquired [ [ ] promptkit . PromptIdentity ] , error )
// ListProfiles lists profile IDs after input and state preparation.
// It is optional; a nil callback does not install a profile catalog command.
ListProfiles func ( context . Context , ckconfig . Selection , I , S ) ( commandkit . Acquired [ [ ] string ] , error )
2026-10-11 01:43:02 +00:00
issues [ ] error
}
type engineLoader [ I , S any ] func ( context . Context , ckconfig . Selection , I , S ) ( * promptkit . Engine , error )
// ConfigScope adapts PromptKit's captured configuration inspection to the
2026-10-11 02:06:06 +00:00
// standard configuration commands. The loader is not called during declaration
// construction or installation; it is called only when a config operation
// selects the promptkit scope, with that operation's input and shared state.
// PromptKit diagnostics and error identity are returned through ckconfig's
// acquisition contract.
2026-10-11 01:43:02 +00:00
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 ] {
Name : "promptkit" ,
Summary : "PromptKit engine configuration." ,
2026-10-12 02:01:10 +00:00
Operations : [ ] ckconfig . Operation { ckconfig . Validate , ckconfig . Show , ckconfig . Sources , ckconfig . Explain , ckconfig . Diff } ,
2026-10-11 01:43:02 +00:00
}
if load == nil {
return ckconfig . WithScopeError ( scope , errors . New ( "PromptKit configuration loader is nil" ) )
}
scope . Acquire = func ( ctx context . Context , request ckconfig . Request , input I , state S ) ( commandkit . Acquired [ ckconfig . View ] , error ) {
2026-10-12 01:55:08 +00:00
_ , inspection , diagnostics , err := loadPromptKitEngine ( ctx , request . Selection , input , state , "" , load )
2026-10-11 01:43:02 +00:00
if err != nil {
return commandkit . Acquired [ ckconfig . View ] { Diagnostics : diagnostics } , err
}
return commandkit . Acquired [ ckconfig . View ] {
Value : ckconfig . View {
Effective : inspection ,
Sources : inspection ,
Explanation : inspection ,
} ,
Diagnostics : diagnostics ,
} , nil
}
2026-10-12 02:01:10 +00:00
scope . Compare = func ( ctx context . Context , request ckconfig . ComparisonRequest [ I ] , state S ) ( commandkit . Acquired [ [ ] configkit . Difference ] , error ) {
if err := promptKitContextError ( ctx ) ; err != nil {
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 )
diagnostics := append ( [ ] commandkit . Diagnostic ( nil ) , leftDiagnostics ... )
if err != nil {
return commandkit . Acquired [ [ ] configkit . Difference ] { Diagnostics : diagnostics } , fmt . Errorf ( "PromptKit scope %q left comparison load: %w" , scope . Name , err )
}
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 )
}
_ , rightInspection , rightDiagnostics , err := loadPromptKitEngine ( ctx , request . Right . Selection , request . Right . Input , state , "right" , load )
diagnostics = append ( diagnostics , rightDiagnostics ... )
if err != nil {
return commandkit . Acquired [ [ ] configkit . Difference ] { Diagnostics : diagnostics } , fmt . Errorf ( "PromptKit scope %q right comparison load: %w" , scope . Name , err )
}
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 )
}
differences , err := leftInspection . Diff ( rightInspection )
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 ] { Value : differences , Diagnostics : diagnostics } , nil
}
2026-10-11 01:43:02 +00:00
return scope
}
2026-10-12 02:01:10 +00:00
func promptKitContextError ( ctx context . Context ) error {
if ctx == nil {
return nil
}
return ctx . Err ( )
}
2026-10-12 02:10:45 +00:00
// FromEngine creates prompt and profile catalog and inspection callbacks backed
// by a PromptKit engine loader. It does not call the loader during construction.
// Each selected operation passes its context, supplied selection, initialized
// input, and shared invocation state to the loader before calling PromptKit's
// public listing or inspection method. A nil loader is retained as an adapter
// issue.
2026-10-11 01:43:02 +00:00
func FromEngine [ I , S any ] ( load func ( context . Context , ckconfig . Selection , I , S ) ( * promptkit . Engine , error ) ) Suite [ I , S ] {
if load == nil {
return Suite [ I , S ] { issues : [ ] error { errors . New ( "PromptKit engine loader is nil" ) } }
}
return Suite [ I , S ] {
2026-10-12 02:10:45 +00:00
ListPrompts : func ( ctx context . Context , selection ckconfig . Selection , input I , state S ) ( commandkit . Acquired [ [ ] promptkit . PromptIdentity ] , error ) {
engine , _ , diagnostics , err := loadPromptKitEngine ( ctx , selection , input , state , "" , load )
if err != nil {
return commandkit . Acquired [ [ ] promptkit . PromptIdentity ] { Diagnostics : diagnostics } , err
}
prompts , err := engine . ListPrompts ( ctx )
return commandkit . Acquired [ [ ] promptkit . PromptIdentity ] { Value : prompts , Diagnostics : diagnostics } , err
} ,
ListProfiles : func ( ctx context . Context , selection ckconfig . Selection , input I , state S ) ( commandkit . Acquired [ [ ] string ] , error ) {
engine , _ , diagnostics , err := loadPromptKitEngine ( ctx , selection , input , state , "" , load )
if err != nil {
return commandkit . Acquired [ [ ] string ] { Diagnostics : diagnostics } , err
}
profiles , err := engine . ListProfiles ( ctx )
return commandkit . Acquired [ [ ] string ] { Value : profiles , Diagnostics : diagnostics } , err
} ,
2026-10-11 01:43:02 +00:00
InspectPrompt : func ( ctx context . Context , request PromptRequest , input I , state S ) ( commandkit . Acquired [ * promptkit . PromptInspection ] , error ) {
2026-10-12 01:55:08 +00:00
engine , _ , diagnostics , err := loadPromptKitEngine ( ctx , request . Selection , input , state , "" , load )
2026-10-11 01:43:02 +00:00
if err != nil {
return commandkit . Acquired [ * promptkit . PromptInspection ] { Diagnostics : diagnostics } , err
}
inspection , err := engine . InspectPrompt ( ctx , request . ID , request . Version . Value )
return commandkit . Acquired [ * promptkit . PromptInspection ] { Value : inspection , Diagnostics : diagnostics } , err
} ,
InspectProfile : func ( ctx context . Context , request ProfileRequest , input I , state S ) ( commandkit . Acquired [ * promptkit . ProfileInspection ] , error ) {
2026-10-12 01:55:08 +00:00
engine , _ , diagnostics , err := loadPromptKitEngine ( ctx , request . Selection , input , state , "" , load )
2026-10-11 01:43:02 +00:00
if err != nil {
return commandkit . Acquired [ * promptkit . ProfileInspection ] { Diagnostics : diagnostics } , err
}
inspection , err := engine . InspectProfile ( ctx , request . ID )
return commandkit . Acquired [ * promptkit . ProfileInspection ] { Value : inspection , Diagnostics : diagnostics } , err
} ,
}
}
func loadPromptKitEngine [ I , S any ] (
ctx context . Context ,
selection ckconfig . Selection ,
input I ,
state S ,
2026-10-12 01:55:08 +00:00
side string ,
2026-10-11 01:43:02 +00:00
load engineLoader [ I , S ] ,
) ( * promptkit . Engine , promptkit . ConfigurationInspection , [ ] commandkit . Diagnostic , error ) {
engine , err := load ( ctx , selection , input , state )
if err != nil {
var loadErr * configkit . LoadError
if errors . As ( err , & loadErr ) {
2026-10-12 01:55:08 +00:00
return nil , promptkit . ConfigurationInspection { } , promptKitDiagnostics ( "promptkit" , side , loadErr . Sources ( ) , loadErr . Diagnostics ( ) ) , err
2026-10-11 01:43:02 +00:00
}
return nil , promptkit . ConfigurationInspection { } , nil , err
}
if engine == nil {
return nil , promptkit . ConfigurationInspection { } , nil , errors . New ( "PromptKit configuration loader returned a nil engine" )
}
inspection , err := engine . InspectConfiguration ( )
if err != nil {
return nil , promptkit . ConfigurationInspection { } , nil , err
}
2026-10-12 01:55:08 +00:00
diagnostics := promptKitDiagnostics ( "promptkit" , side , inspection . Sources ( ) , inspection . Diagnostics ( ) )
if ctx != nil {
if err := ctx . Err ( ) ; err != nil {
return nil , promptkit . ConfigurationInspection { } , diagnostics , err
}
}
2026-10-11 01:43:02 +00:00
return engine , inspection , diagnostics , nil
}
2026-10-12 01:55:08 +00:00
func promptKitDiagnostics ( scope , side string , sources [ ] configkit . SourceInfo , records [ ] configkit . Diagnostic ) [ ] commandkit . Diagnostic {
2026-10-11 01:43:02 +00:00
if len ( records ) == 0 {
return nil
}
severity := commandkit . SeverityWarning
for _ , record := range records {
if record . Severity == configkit . SeverityError {
severity = commandkit . SeverityError
break
}
}
// Snapshot provider-owned slices before deferring their report to invocation.
sources = append ( [ ] configkit . SourceInfo ( nil ) , sources ... )
records = append ( [ ] configkit . Diagnostic ( nil ) , records ... )
2026-10-11 01:46:54 +00:00
for index := range records {
if records [ index ] . Primary != nil {
primary := * records [ index ] . Primary
records [ index ] . Primary = & primary
}
records [ index ] . Related = append ( [ ] configkit . Location ( nil ) , records [ index ] . Related ... )
}
2026-10-11 01:43:02 +00:00
return [ ] commandkit . Diagnostic { {
Severity : severity ,
Write : func ( writer io . Writer ) error {
2026-10-12 01:55:08 +00:00
heading := "Configuration scope " + strconv . Quote ( scope )
if side != "" {
heading += " (" + side + ")"
}
if _ , err := fmt . Fprintf ( writer , "%s:\n" , heading ) ; err != nil {
2026-10-11 01:43:02 +00:00
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 configkit . WriteDiagnostics ( writer , records )
} ,
} }
}