Demonstrate requirements discovery in composed example

This commit is contained in:
2026-10-12 17:17:49 +00:00
parent b27cf02c0e
commit 3710492d2d
3 changed files with 110 additions and 2 deletions

View File

@@ -1,6 +1,6 @@
# Implement PromptKit requirements inspection
Status: in progress; Stages 1 through 4 are complete.
Status: in progress; Stages 1 through 5 are complete.
## Authority and working rules
@@ -365,6 +365,20 @@ Run the composed example's tests and both commands from its directory. Record
actual report semantics, not explanatory wording snapshots. Documentation of
these copyable commands belongs in Stage 6.
**Completed:** Extended the synthetic prompt in
`integrations/ckprompt/examples/composed/main.go` with the conditional
`notes` dependency while retaining the declared `topic` input. Added assembled
App coverage in `main_test.go` for selected spring identity, missing and
supplied `mode`, conditional versus required classification of undeclared
`notes`, incomplete versus complete discovery, and success for both reports.
`GOTOOLCHAIN=local go test ./examples/composed -run
'TestComposedRequirementsDiscoveryUsesSelectedCampaign$' -count=1` and
`GOTOOLCHAIN=local go test ./examples/composed` passed from
`integrations/ckprompt`. Both roadmap commands passed from
`integrations/ckprompt/examples/composed`: the first report was incomplete with
a missing `mode` issue and conditional `notes`; the second was complete with
`mode` supplied and required `notes`. No material limitations were encountered.
## Stage 6: Align canonical documentation with the delivered feature
**Outcome:** API, operator, and consumer documentation describes the implemented

View File

@@ -121,7 +121,7 @@ func loadPromptEngine(ctx context.Context, selection ckconfig.Selection, input *
chosen.PromptVersion: map[string]any{
"default_profile": chosen.ProfileID,
"inputs": []any{map[string]any{"name": "topic", "required": true, "content_type": "text/plain", "description": "Topic to brief"}},
"messages": []any{map[string]any{"role": "user", "content": "Write a concise briefing about {{.topic}}."}},
"messages": []any{map[string]any{"role": "user", "content": "Write a concise briefing about {{.topic}}. {{if .mode}}{{input \"notes\"}}{{end}}"}},
"output": map[string]any{"format": "text", "validation_mode": "none"},
},
}},

View File

@@ -117,6 +117,100 @@ func TestComposedPromptKitAndDomainCommandsRemainAvailable(t *testing.T) {
}
}
func TestComposedRequirementsDiscoveryUsesSelectedCampaign(t *testing.T) {
app, err := newApp()
if err != nil {
t.Fatal(err)
}
for _, test := range []struct {
name string
args []string
wantMode bool
wantModeIssue bool
}{
{
name: "conditional dependency is unresolved without mode",
args: []string{"promptkit", "requirements", "show", "briefing", "--campaign", "spring"},
wantModeIssue: true,
},
{
name: "supplied mode discovers conditional notes",
args: []string{"promptkit", "requirements", "show", "briefing", "--campaign", "spring", "--var", "mode=full", "--format", "yaml"},
wantMode: true,
},
} {
t.Run(test.name, func(t *testing.T) {
var output bytes.Buffer
outcome := app.Execute(context.Background(), test.args, commandkit.Streams{Out: &output, Err: io.Discard})
if outcome.Kind != commandkit.OutcomeSuccess || outcome.Status != 0 {
t.Fatalf("requirements outcome = %#v, report %q", outcome, output.String())
}
var report struct {
PromptID string `yaml:"prompt_id"`
PromptVersion string `yaml:"prompt_version"`
ProfileID string `yaml:"profile_id"`
Complete bool `yaml:"complete"`
Inputs []struct {
Name string `yaml:"name"`
Kind string `yaml:"kind"`
Declared bool `yaml:"declared"`
} `yaml:"inputs"`
Variables []struct {
Name string `yaml:"name"`
Supplied bool `yaml:"supplied"`
} `yaml:"variables"`
Issues []struct {
Code string `yaml:"code"`
InputName string `yaml:"input_name"`
VariableName string `yaml:"variable_name"`
} `yaml:"issues"`
}
if err := yaml.Unmarshal(output.Bytes(), &report); err != nil {
t.Fatalf("decode requirements report %q: %v", output.String(), err)
}
if report.PromptID != "briefing" || report.PromptVersion != "1" || report.ProfileID != "spring" {
t.Fatalf("requirements identity = %#v", report)
}
foundTopic, foundNotes, foundMode := false, false, false
for _, input := range report.Inputs {
switch input.Name {
case "topic":
foundTopic = input.Declared && input.Kind == "required"
case "notes":
foundNotes = true
wantKind := "conditional"
if test.wantMode {
wantKind = "required"
}
if input.Declared || input.Kind != wantKind {
t.Errorf("notes input classification = %#v; want undeclared %s", input, wantKind)
}
}
}
for _, variable := range report.Variables {
if variable.Name == "mode" {
foundMode = variable.Supplied == test.wantMode
}
}
if !foundTopic || !foundNotes || !foundMode {
t.Fatalf("requirements inventory topic=%t notes=%t mode=%t; report %#v", foundTopic, foundNotes, foundMode, report)
}
if test.wantModeIssue && (len(report.Issues) != 1 || report.Issues[0].Code != "missing_variable" || report.Issues[0].VariableName != "mode") {
t.Fatalf("missing mode issue = %#v", report.Issues)
}
if !test.wantModeIssue && len(report.Issues) != 0 {
t.Fatalf("requirements issues with mode supplied=%t: %#v", test.wantMode, report.Issues)
}
if report.Complete != test.wantMode {
t.Fatalf("requirements completeness = %t with mode supplied=%t; report %#v", report.Complete, test.wantMode, report)
}
})
}
}
func TestPromptKitScopeDiffAndCatalogsUseSelectedCampaign(t *testing.T) {
app, err := newApp()
if err != nil {