Add PromptKit requirements command

This commit is contained in:
2026-10-12 17:05:33 +00:00
parent 0276930b12
commit 7dce7b816b
3 changed files with 322 additions and 14 deletions

View File

@@ -31,18 +31,31 @@ type catalogInput[I any] struct {
selected I
}
// Command captures the optional PromptKit catalog and named inspection
// callbacks and adds only supplied operations, generating list and show groups
// independently. An absent suite claims no namespace; attached construction
// issues remain reportable. It adds no configuration family; callers compose
// ConfigScope independently. Construction, installation, help, and version do
// not run suite factories or providers. Suite documents invocation input and
// state initialization, zero values, and caller ownership of returned objects.
type requirementsInput[I any] struct {
selection ckconfig.Selection
inspection promptkit.RequirementInspectionRequest
format string
captured commandkit.FlagValues[I]
selected I
}
type variableAssignment struct {
key string
value string
}
// Command captures the optional PromptKit catalog, named inspection, and
// requirements inspection callbacks and adds only supplied operations. An
// absent suite claims no namespace; attached construction issues remain
// reportable. It adds no configuration family; callers compose ConfigScope
// independently. Construction, installation, help, and version do not run
// suite factories or providers. Suite documents invocation input and state
// initialization, zero values, and caller ownership of returned objects.
// Catalog commands accept config/overlay selection and suite selectors, and
// render YAML with `yaml` as the only format and default. Named-show formats are
// unchanged.
func Command[I, S any](suite Suite[I, S]) commandkit.Command {
if suite.InspectPrompt == nil && suite.InspectProfile == nil && suite.ListPrompts == nil && suite.ListProfiles == nil {
if suite.InspectPrompt == nil && suite.InspectProfile == nil && suite.InspectRequirements == nil && suite.ListPrompts == nil && suite.ListProfiles == nil {
command := commandkit.NoCommand()
for _, issue := range suite.issues {
command = commandkit.WithError(command, issue)
@@ -51,7 +64,7 @@ func Command[I, S any](suite Suite[I, S]) commandkit.Command {
}
suite.Selectors = append([]commandkit.Flag[I](nil), suite.Selectors...)
rootCommands := make([]commandkit.Command, 0, 2)
rootCommands := make([]commandkit.Command, 0, 3)
showCommands := make([]commandkit.Command, 0, 2)
if suite.InspectPrompt != nil {
showCommands = append(showCommands, newDefinitionCommand(suite, "prompt", true))
@@ -72,7 +85,10 @@ func Command[I, S any](suite Suite[I, S]) commandkit.Command {
if len(listCommands) != 0 {
rootCommands = append(rootCommands, commandkit.Group("list", "List PromptKit catalogs.", listCommands...))
}
command := commandkit.RootGroup("promptkit", "Inspect and list PromptKit definitions.", rootCommands...)
if suite.InspectRequirements != nil {
rootCommands = append(rootCommands, commandkit.Group("requirements", "Inspect prompt requirements.", newRequirementsCommand(suite)))
}
command := commandkit.RootGroup("promptkit", "Inspect and list PromptKit definitions and requirements.", rootCommands...)
for _, issue := range suite.issues {
command = commandkit.WithError(command, issue)
}
@@ -131,6 +147,13 @@ func checkCatalogFormat(format string) error {
return nil
}
func checkRequirementsFormat(format string) error {
if format != "yaml" {
return fmt.Errorf("unsupported requirements format %q; expected yaml", format)
}
return nil
}
func prepareCatalogInput[I, S any](suite Suite[I, S], input *catalogInput[I]) (I, error) {
return prepareSuiteInput(suite, input.selection, input.captured)
}
@@ -186,6 +209,77 @@ func newDefinitionCommand[I, S any](suite Suite[I, S], name string, prompt bool)
return commandkit.NewCommand(spec)
}
func newRequirementsCommand[I, S any](suite Suite[I, S]) commandkit.Command {
flags := commandkit.CaptureFlags(suite.Selectors, func(input **requirementsInput[I]) *commandkit.FlagValues[I] {
if input == nil || *input == nil {
return nil
}
return &(*input).captured
})
flags = append(flags,
commandkit.ValueFlag(commandkit.FlagMeta{Name: "config", Usage: "Configuration input path."}, (*string)(nil), parseDefinitionString,
func(input **requirementsInput[I], value commandkit.Supplied[string]) {
(*input).selection.Config = value
}),
commandkit.ValueFlag(commandkit.FlagMeta{Name: "overlay", Usage: "Configuration overlay path."}, (*string)(nil), parseDefinitionString,
func(input **requirementsInput[I], value commandkit.Supplied[string]) {
(*input).selection.Overlay = value
}),
commandkit.ValueFlag(commandkit.FlagMeta{Name: "version", Usage: "Prompt version to inspect."}, stringDefault(""), parseDefinitionString,
func(input **requirementsInput[I], value commandkit.Supplied[string]) {
(*input).inspection.PromptVersion = value.Value
}),
commandkit.ValueFlag(commandkit.FlagMeta{Name: "profile", Usage: "Profile identity for discovery."}, stringDefault(""), parseDefinitionString,
func(input **requirementsInput[I], value commandkit.Supplied[string]) {
(*input).inspection.ProfileID = value.Value
}),
commandkit.ValueFlag(commandkit.FlagMeta{Name: "session-id", Usage: "Session context for discovery."}, stringDefault(""), parseDefinitionString,
func(input **requirementsInput[I], value commandkit.Supplied[string]) {
(*input).inspection.SessionID = value.Value
}),
commandkit.ValueFlag(commandkit.FlagMeta{Name: "format", Usage: "Report format: yaml."}, stringDefault("yaml"), parseDefinitionString,
func(input **requirementsInput[I], value commandkit.Supplied[string]) { (*input).format = value.Value }),
commandkit.RepeatFlag(commandkit.FlagMeta{Name: "var", Usage: "Caller variable assignment KEY=VALUE."}, []variableAssignment(nil), parseVariableAssignment,
func(input **requirementsInput[I], value commandkit.Supplied[[]variableAssignment]) {
variables := make(map[string]string, len(value.Value))
for _, assignment := range value.Value {
variables[assignment.key] = assignment.value
}
(*input).inspection.Vars = variables
}),
)
spec := commandkit.CommandSpec[*requirementsInput[I]]{
Name: "show",
Summary: "Inspect requirements for a named PromptKit prompt.",
NewInput: func() *requirementsInput[I] {
return &requirementsInput[I]{}
},
Flags: flags,
Arguments: []commandkit.Argument[*requirementsInput[I]]{
commandkit.ValueArgument(commandkit.ArgumentMeta{Name: "id", Usage: "Prompt ID."}, true, parseDefinitionString,
func(input **requirementsInput[I], value commandkit.Supplied[string]) {
(*input).inspection.PromptID = value.Value
}),
},
CheckInput: func(input *requirementsInput[I]) error {
if err := checkRequirementsFormat(input.format); err != nil {
return err
}
selected, err := prepareSuiteInput(suite, input.selection, input.captured)
if err != nil {
return err
}
input.selected = selected
return nil
},
Run: func(invocation commandkit.Invocation, input *requirementsInput[I]) commandkit.Outcome {
return runRequirementsInspection(invocation, suite, input)
},
}
return commandkit.NewCommand(spec)
}
func prepareDefinitionInput[I, S any](suite Suite[I, S], input *definitionInput[I]) (I, error) {
return prepareSuiteInput(suite, input.selection, input.captured)
}
@@ -446,6 +540,42 @@ func profileReportFromInspection(inspection *promptkit.ProfileInspection) profil
}
}
func runRequirementsInspection[I, S any](invocation commandkit.Invocation, suite Suite[I, S], input *requirementsInput[I]) commandkit.Outcome {
if invocation.Context != nil {
if err := invocation.Context.Err(); err != nil {
return definitionFailure(err, nil)
}
}
var state S
if suite.NewState != nil {
state = suite.NewState()
}
if invocation.Context != nil {
if err := invocation.Context.Err(); err != nil {
return definitionFailure(err, nil)
}
}
request := RequirementsRequest{Selection: input.selection, Inspection: input.inspection}
acquired, err := suite.InspectRequirements(invocation.Context, request, input.selected, state)
if err != nil {
return definitionFailure(err, acquired.Diagnostics)
}
if acquired.Value == nil {
return definitionFailure(errors.New("PromptKit requirements inspection returned nil"), acquired.Diagnostics)
}
if invocation.Context != nil {
if err := invocation.Context.Err(); err != nil {
return definitionFailure(err, acquired.Diagnostics)
}
}
output, err := encodePromptYAMLBytes(requirementsReportFromInspection(acquired.Value))
if err != nil {
return definitionFailure(err, acquired.Diagnostics)
}
return writePromptReport(invocation, output, acquired.Diagnostics)
}
func renderPromptReport(invocation commandkit.Invocation, report promptInspectionReport, format string, diagnostics []commandkit.Diagnostic) commandkit.Outcome {
var output bytes.Buffer
if format == "yaml" {
@@ -581,4 +711,12 @@ func checkDefinitionFormat(format string) error {
func parseDefinitionString(value string) (string, error) { return value, nil }
func parseVariableAssignment(raw string) (variableAssignment, error) {
key, value, found := strings.Cut(raw, "=")
if !found {
return variableAssignment{}, fmt.Errorf("variable assignment %q must contain =", raw)
}
return variableAssignment{key: key, value: value}, nil
}
func stringDefault(value string) *string { return &value }