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 # 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 ## 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 actual report semantics, not explanatory wording snapshots. Documentation of
these copyable commands belongs in Stage 6. 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 ## Stage 6: Align canonical documentation with the delivered feature
**Outcome:** API, operator, and consumer documentation describes the implemented **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{ chosen.PromptVersion: map[string]any{
"default_profile": chosen.ProfileID, "default_profile": chosen.ProfileID,
"inputs": []any{map[string]any{"name": "topic", "required": true, "content_type": "text/plain", "description": "Topic to brief"}}, "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"}, "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) { func TestPromptKitScopeDiffAndCatalogsUseSelectedCampaign(t *testing.T) {
app, err := newApp() app, err := newApp()
if err != nil { if err != nil {