diff --git a/docs/roadmap/implementation.md b/docs/roadmap/implementation.md index d00a1b5..28e9968 100644 --- a/docs/roadmap/implementation.md +++ b/docs/roadmap/implementation.md @@ -1,6 +1,6 @@ # Dependency upgrades, discovery, and smoke testing implementation plan -Status: Stages 1–4 complete; Stage 5 is next. +Status: Stages 1–5 complete; Stage 6 is next. ## Authority and baseline @@ -343,6 +343,16 @@ presentation, and error identity, not upstream detection algorithms. **Complete when:** the previous delivered feature set works through v0.16.0 without compatibility scaffolding or implicit catalogs. +**Completed:** 2026-10-17. PromptKit fixtures now use direct prompt definitions, +explicit profiles/backends, and distinct IDs for multiple prompts. ckprompt +tests use the ID-only inspection and validation requests and verify that the +removed version flags are unavailable. Semantic YAML tests cover absent, +present-empty, and nonempty versions in prompt inspection, requirements, and +catalog reports; PromptKit-specific requirement classification assertions were +removed from the affected adapter/consumer tests. The ckprompt suite passed, +and composed named inspection, requirements show, and request validation all +ran successfully against the migrated fixture. + ## Stage 6: Extend catalog contracts and engine adapters **Primary files:** ckprompt public request declarations, `Suite`, `FromEngine`, diff --git a/integrations/ckprompt/adapters_test.go b/integrations/ckprompt/adapters_test.go index 892634c..443fe30 100644 --- a/integrations/ckprompt/adapters_test.go +++ b/integrations/ckprompt/adapters_test.go @@ -19,31 +19,32 @@ import ( const adapterPrompt = `prompts: briefing: - "1": - default_profile: default - inputs: - - name: topic - required: true - content_type: text/plain - description: "Topic" - - name: audience - required: false - content_type: text/plain - description: "" - messages: - - role: user - content: "Hello" - output: - format: text - validation_mode: none + version: "1" + default_profile: default + inputs: + - name: topic + required: true + content_type: text/plain + description: "Topic" + - name: audience + required: false + content_type: text/plain + description: "" + messages: + - role: user + content: "Hello" + output: + format: text + validation_mode: none empty: - "1": - messages: - - role: user - content: "Empty inputs" - output: - format: text - validation_mode: none + version: "1" + default_profile: default + messages: + - role: user + content: "Empty inputs" + output: + format: text + validation_mode: none backends: local: endpoint: "https://example.invalid/v1" @@ -68,15 +69,15 @@ profiles: const adapterRequirementsPrompt = `prompts: briefing: - "1": - default_profile: default - session_id: '{{input "session-only"}} {{.session_variable}}' - messages: - - role: user - content: '{{if eq .audience "internal"}}{{input "internal-notes"}}{{else}}{{input "public-notes"}}{{end}} {{.audience}}' - output: - format: text - validation_mode: none + version: "1" + default_profile: default + session_id: '{{input "session-only"}} {{.session_variable}}' + messages: + - role: user + content: '{{if eq .audience "internal"}}{{input "internal-notes"}}{{else}}{{input "public-notes"}}{{end}} {{.audience}}' + output: + format: text + validation_mode: none backends: local: endpoint: "https://example.invalid/v1" @@ -319,22 +320,18 @@ func TestFromEnginePassesRequestsAndRetainsNilLoaderIssue(t *testing.T) { return engine, nil }) selection := ckconfig.Selection{Overlay: commandkit.Supplied[string]{Value: "overlay.yml", Set: true}} - version := commandkit.Supplied[string]{Value: "1", Set: true} - prompt, err := suite.InspectPrompt(wantContext, PromptRequest{Selection: selection, ID: "briefing", Version: version}, wantInput, wantState) - if err != nil || prompt.Value == nil || prompt.Value.PromptID != "briefing" || prompt.Value.PromptVersion != "1" || loaderCalls != 1 { + prompt, err := suite.InspectPrompt(wantContext, PromptRequest{Selection: selection, ID: "briefing"}, wantInput, wantState) + if err != nil || prompt.Value == nil || prompt.Value.PromptID != "briefing" || prompt.Value.PromptVersion == nil || *prompt.Value.PromptVersion != "1" || loaderCalls != 1 { t.Fatalf("prompt inspection = (%#v, %v), calls=%d", prompt, err, loaderCalls) } - defaultPrompt, err := suite.InspectPrompt(wantContext, PromptRequest{Selection: selection, ID: "briefing"}, wantInput, wantState) - if err != nil || defaultPrompt.Value == nil || defaultPrompt.Value.PromptVersion != "1" || loaderCalls != 2 { - t.Fatalf("empty version default = (%#v, %v), calls=%d", defaultPrompt, err, loaderCalls) + secondPrompt, err := suite.InspectPrompt(wantContext, PromptRequest{Selection: selection, ID: "briefing"}, wantInput, wantState) + if err != nil || secondPrompt.Value == nil || secondPrompt.Value.PromptID != "briefing" || loaderCalls != 2 { + t.Fatalf("ID-only prompt inspection = (%#v, %v), calls=%d", secondPrompt, err, loaderCalls) } profile, err := suite.InspectProfile(wantContext, ProfileRequest{Selection: selection, ID: "default"}, wantInput, wantState) if err != nil || profile.Value == nil || profile.Value.ProfileID != "default" || loaderCalls != 3 { t.Fatalf("profile inspection = (%#v, %v), calls=%d", profile, err, loaderCalls) } - if version.Set != true || version.Value != "1" { - t.Fatalf("custom callback request lost version presence: %#v", version) - } empty := FromEngine[int, int](nil) empty.ListPrompts = nil @@ -363,11 +360,10 @@ func TestFromEngineInspectsRequirementsWithNativeRequest(t *testing.T) { request := RequirementsRequest{ Selection: selection, Inspection: promptkit.RequirementInspectionRequest{ - PromptID: "briefing", - PromptVersion: "1", - ProfileID: "default", - Vars: map[string]string{"audience": "internal"}, - SessionID: "session-42", + PromptID: "briefing", + ProfileID: "default", + Vars: map[string]string{"audience": "internal"}, + SessionID: "session-42", }, } want, err := engine.InspectRequirements(wantContext, request.Inspection) @@ -387,26 +383,9 @@ func TestFromEngineInspectsRequirementsWithNativeRequest(t *testing.T) { if err != nil || acquired.Value == nil || !reflect.DeepEqual(acquired.Value, want) || loaderCalls != 1 { t.Fatalf("requirements inspection = (%#v, %v), calls=%d; want native result %#v", acquired, err, loaderCalls, want) } - if acquired.Value.PromptID != "briefing" || acquired.Value.PromptVersion != "1" || acquired.Value.ProfileID != "default" { + if acquired.Value.PromptID != "briefing" || acquired.Value.PromptVersion == nil || *acquired.Value.PromptVersion != "1" || acquired.Value.ProfileID != "default" { t.Fatalf("requirements selection = %#v", acquired.Value) } - inputs := make(map[string]promptkit.InputRequirement, len(acquired.Value.Inputs)) - for _, input := range acquired.Value.Inputs { - inputs[input.Name] = input - } - if inputs["internal-notes"].Kind != promptkit.RequirementRequired || inputs["public-notes"].Kind != promptkit.RequirementOptional { - t.Fatalf("caller variable did not reach native discovery: %#v", acquired.Value.Inputs) - } - if _, found := inputs["session-only"]; found { - t.Fatalf("session template was not bypassed by the supplied session ID: %#v", acquired.Value.Inputs) - } - variables := make(map[string]promptkit.VariableRequirement, len(acquired.Value.Variables)) - for _, variable := range acquired.Value.Variables { - variables[variable.Name] = variable - } - if !variables["audience"].Supplied { - t.Fatalf("caller variable presence was not forwarded: %#v", acquired.Value.Variables) - } } func TestFromEngineRequirementsRetainLoadDiagnosticsAndSelectionErrors(t *testing.T) { diff --git a/integrations/ckprompt/command_test.go b/integrations/ckprompt/command_test.go index 3ad797c..efcda80 100644 --- a/integrations/ckprompt/command_test.go +++ b/integrations/ckprompt/command_test.go @@ -6,6 +6,7 @@ import ( "errors" "io" "reflect" + "strconv" "strings" "testing" @@ -24,6 +25,8 @@ func newPromptTestApp(t *testing.T, commands ...commandkit.Command) *commandkit. return app } +func testStringPointer(value string) *string { return &value } + func TestCommandBuildsIndependentOptionalTrees(t *testing.T) { promptOnly := newPromptTestApp(t, Command(Suite[struct{}, struct{}]{InspectPrompt: func(context.Context, PromptRequest, struct{}, struct{}) (commandkit.Acquired[*promptkit.PromptInspection], error) { return commandkit.Acquired[*promptkit.PromptInspection]{}, errors.New("unused") @@ -121,8 +124,7 @@ func TestRequirementsCommandCapturesContextAndVariableAssignments(t *testing.T) providerCalls++ received, receivedInput, receivedState = request, value, gotState return commandkit.Acquired[*promptkit.RequirementInspection]{Value: &promptkit.RequirementInspection{ - PromptID: request.Inspection.PromptID, PromptVersion: request.Inspection.PromptVersion, - ProfileID: request.Inspection.ProfileID, Complete: true, + PromptID: request.Inspection.PromptID, ProfileID: request.Inspection.ProfileID, Complete: true, }}, nil }, } @@ -139,7 +141,7 @@ func TestRequirementsCommandCapturesContextAndVariableAssignments(t *testing.T) if familyOutcome.Kind != commandkit.OutcomeSuccess || !strings.Contains(familyHelp.String(), "requirements") || strings.Contains(familyHelp.String(), "\n show ") || strings.Contains(familyHelp.String(), "\n list ") { t.Fatalf("requirements-only family help = (%#v, %q)", familyOutcome, familyHelp.String()) } - for _, flag := range []string{"--config", "--overlay", "--version", "--profile", "--session-id", "--var", "--format", "--campaign"} { + for _, flag := range []string{"--config", "--overlay", "--profile", "--session-id", "--var", "--format", "--campaign"} { if !strings.Contains(help.String(), flag) { t.Errorf("requirements help omits %s: %q", flag, help.String()) } @@ -152,7 +154,7 @@ func TestRequirementsCommandCapturesContextAndVariableAssignments(t *testing.T) args := []string{ "promptkit", "requirements", "show", "briefing", "--campaign", "spring", "--config", "before.yml", "--config=", "--overlay", "base.yml", "--overlay=", - "--version", "1", "--version", "2", "--profile", "first", "--profile", "editorial", + "--profile", "first", "--profile", "editorial", "--session-id", "old-session", "--session-id", "session-42", "--format", "text", "--format", "yaml", "--var", "note=hello, world", "--var", "empty=", "--var", "token=a=b", "--var", "=value", "--var", "mode=brief", "--var", "mode=full", @@ -168,7 +170,7 @@ func TestRequirementsCommandCapturesContextAndVariableAssignments(t *testing.T) t.Fatalf("configuration selection = %#v", received.Selection) } wantVariables := map[string]string{"note": "hello, world", "empty": "", "token": "a=b", "": "value", "mode": "full"} - if received.Inspection.PromptID != "briefing" || received.Inspection.PromptVersion != "2" || received.Inspection.ProfileID != "editorial" || received.Inspection.SessionID != "session-42" || !reflect.DeepEqual(received.Inspection.Vars, wantVariables) { + if received.Inspection.PromptID != "briefing" || received.Inspection.ProfileID != "editorial" || received.Inspection.SessionID != "session-42" || !reflect.DeepEqual(received.Inspection.Vars, wantVariables) { t.Fatalf("native request = %#v, want vars %#v", received.Inspection, wantVariables) } if !receivedInput.campaign.Set || receivedInput.campaign.Value != "spring" || receivedState.call != 1 { @@ -184,7 +186,7 @@ func TestRequirementsCommandCapturesContextAndVariableAssignments(t *testing.T) if outcome.Kind != commandkit.OutcomeSuccess || newInputCalls != 2 || checkInputCalls != 2 || newStateCalls != 2 || providerCalls != 2 { t.Fatalf("second invocation = %#v, calls=%d/%d/%d/%d", outcome, newInputCalls, checkInputCalls, newStateCalls, providerCalls) } - if len(received.Inspection.Vars) != 0 || received.Inspection.PromptVersion != "" || received.Inspection.ProfileID != "" || received.Inspection.SessionID != "" { + if len(received.Inspection.Vars) != 0 || received.Inspection.ProfileID != "" || received.Inspection.SessionID != "" { t.Fatalf("second invocation retained native values: %#v", received.Inspection) } var defaultReport map[string]any @@ -299,7 +301,7 @@ func TestCatalogListUsesPreparedInputAndPreflightsFormat(t *testing.T) { ListPrompts: func(_ context.Context, selection ckconfig.Selection, value input, gotState state) (commandkit.Acquired[[]promptkit.PromptIdentity], error) { providerCalls++ received, receivedSelection, receivedState = value, selection, gotState - return commandkit.Acquired[[]promptkit.PromptIdentity]{Value: []promptkit.PromptIdentity{{ID: "briefing", Version: "1"}}}, nil + return commandkit.Acquired[[]promptkit.PromptIdentity]{Value: []promptkit.PromptIdentity{{ID: "briefing", Version: testStringPointer("1")}}}, nil }, } app := newPromptTestApp(t, Command(suite)) @@ -437,9 +439,10 @@ func TestCatalogNilFactoriesSupplyZeroValues(t *testing.T) { func TestCatalogCommandsRenderNativeValuesAndEmptySequences(t *testing.T) { prompts := []promptkit.PromptIdentity{ - {ID: "later", Version: "1.10"}, - {ID: "001", Version: " 03 "}, - {ID: "a: true", Version: "yes"}, + {ID: "later", Version: testStringPointer("1.10")}, + {ID: "001", Version: testStringPointer(" 03 ")}, + {ID: "a: true", Version: testStringPointer("yes")}, + {ID: "without-version"}, } profiles := []string{"later", "001", "a: true", "later"} suite := Suite[struct{}, struct{}]{ @@ -465,9 +468,16 @@ func TestCatalogCommandsRenderNativeValuesAndEmptySequences(t *testing.T) { } for index, entry := range promptEntries { id, idOK := entry["id"].(string) - version, versionOK := entry["version"].(string) - if len(entry) != 2 || !idOK || !versionOK || id != prompts[index].ID || version != prompts[index].Version { - t.Errorf("prompt entry %d = %#v, want string id/version %q/%q", index, entry, prompts[index].ID, prompts[index].Version) + if !idOK || id != prompts[index].ID { + t.Errorf("prompt entry %d = %#v, want ID %q", index, entry, prompts[index].ID) + } + version, versionExists := entry["version"] + if prompts[index].Version == nil { + if versionExists || len(entry) != 1 { + t.Errorf("prompt entry %d emitted absent version: %#v", index, entry) + } + } else if !versionExists || version != *prompts[index].Version || len(entry) != 2 { + t.Errorf("prompt entry %d version = %#v, want %q", index, version, *prompts[index].Version) } } @@ -565,7 +575,7 @@ func TestCatalogFailuresRetainDiagnosticsAndDeliveryState(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) canceled := newPromptTestApp(t, Command(Suite[struct{}, struct{}]{ListPrompts: func(context.Context, ckconfig.Selection, struct{}, struct{}) (commandkit.Acquired[[]promptkit.PromptIdentity], error) { cancel() - return commandkit.Acquired[[]promptkit.PromptIdentity]{Value: []promptkit.PromptIdentity{{ID: "briefing", Version: "1"}}, Diagnostics: []commandkit.Diagnostic{diagnostic}}, nil + return commandkit.Acquired[[]promptkit.PromptIdentity]{Value: []promptkit.PromptIdentity{{ID: "briefing", Version: testStringPointer("1")}}, Diagnostics: []commandkit.Diagnostic{diagnostic}}, nil }})) var output bytes.Buffer outcome = canceled.Execute(ctx, []string{"promptkit", "list", "prompts"}, commandkit.Streams{Out: &output, Err: io.Discard}) @@ -584,7 +594,7 @@ func TestCatalogFailuresRetainDiagnosticsAndDeliveryState(t *testing.T) { } { t.Run(test.name, func(t *testing.T) { writerApp := newPromptTestApp(t, Command(Suite[struct{}, struct{}]{ListPrompts: func(context.Context, ckconfig.Selection, struct{}, struct{}) (commandkit.Acquired[[]promptkit.PromptIdentity], error) { - return commandkit.Acquired[[]promptkit.PromptIdentity]{Value: []promptkit.PromptIdentity{{ID: "briefing", Version: "1"}}, Diagnostics: []commandkit.Diagnostic{diagnostic}}, nil + return commandkit.Acquired[[]promptkit.PromptIdentity]{Value: []promptkit.PromptIdentity{{ID: "briefing", Version: testStringPointer("1")}}, Diagnostics: []commandkit.Diagnostic{diagnostic}}, nil }})) outcome := writerApp.Execute(context.Background(), []string{"promptkit", "list", "prompts"}, commandkit.Streams{Out: test.writer, Err: io.Discard}) if outcome.Kind != commandkit.OutcomeOperational || !errors.Is(outcome.DeliveryError, test.want) || !errors.Is(outcome.Cause, test.want) || !outcome.ReportIncomplete || !hasCatalogWarning(outcome.Diagnostics) { @@ -663,22 +673,22 @@ func TestDefinitionInputAndStateLifetimesFollowChecks(t *testing.T) { }, InspectPrompt: func(ctx context.Context, request PromptRequest, input *inspectInput, state *runState) (commandkit.Acquired[*promptkit.PromptInspection], error) { callbacks++ - if ctx != wantContext || input != checked || request.ID != "briefing" || !request.Version.Set || request.Version.Value != "2" || state == nil { + if ctx != wantContext || input != checked || request.ID != "briefing" || state == nil { t.Errorf("prompt callback arguments = ctx=%v request=%#v input=%p state=%p", ctx, request, input, state) } - return commandkit.Acquired[*promptkit.PromptInspection]{Value: &promptkit.PromptInspection{PromptID: request.ID, PromptVersion: request.Version.Value}}, nil + return commandkit.Acquired[*promptkit.PromptInspection]{Value: &promptkit.PromptInspection{PromptID: request.ID}}, nil }, } app := newPromptTestApp(t, Command(suite)) wantContext = context.WithValue(context.Background(), struct{}{}, "context") var output bytes.Buffer - outcome := app.Execute(wantContext, []string{"promptkit", "show", "prompt", "briefing", "--campaign", "spring", "--config", "base.yml", "--overlay", "local.yml", "--version", "2"}, commandkit.Streams{Out: &output, Err: io.Discard}) + outcome := app.Execute(wantContext, []string{"promptkit", "show", "prompt", "briefing", "--campaign", "spring", "--config", "base.yml", "--overlay", "local.yml"}, commandkit.Streams{Out: &output, Err: io.Discard}) if outcome.Kind != commandkit.OutcomeSuccess || newInputs != 1 || newStates != 1 || checks != 1 || callbacks != 1 || conversions != 1 { t.Fatalf("execution outcome=%#v counts conversion/input/state/check/callback=%d/%d/%d/%d/%d report=%q", outcome, conversions, newInputs, newStates, checks, callbacks, output.String()) } rejectInput = true output.Reset() - outcome = app.Execute(wantContext, []string{"promptkit", "show", "prompt", "briefing", "--campaign", "spring", "--config", "base.yml", "--overlay", "local.yml", "--version", "2"}, commandkit.Streams{Out: &output, Err: io.Discard}) + outcome = app.Execute(wantContext, []string{"promptkit", "show", "prompt", "briefing", "--campaign", "spring", "--config", "base.yml", "--overlay", "local.yml"}, commandkit.Streams{Out: &output, Err: io.Discard}) if outcome.Kind != commandkit.OutcomeUsage || newInputs != 2 || newStates != 1 || checks != 2 || callbacks != 1 || conversions != 2 { t.Fatalf("rejected input reached state/callback: outcome=%#v counts=%d/%d/%d/%d/%d", outcome, conversions, newInputs, newStates, checks, callbacks) } @@ -694,7 +704,7 @@ func TestDefinitionReportsRetainNativeFieldsAndSafeCredentialReferences(t *testi }) app := newPromptTestApp(t, Command(suite)) var output bytes.Buffer - outcome := app.Execute(context.Background(), []string{"promptkit", "show", "prompt", "briefing", "--version", "1", "--format", "yaml"}, commandkit.Streams{Out: &output, Err: io.Discard}) + outcome := app.Execute(context.Background(), []string{"promptkit", "show", "prompt", "briefing", "--format", "yaml"}, commandkit.Streams{Out: &output, Err: io.Discard}) if outcome.Kind != commandkit.OutcomeSuccess || loadCalls != 1 { t.Fatalf("prompt report outcome=%#v load calls=%d report=%s", outcome, loadCalls, output.String()) } @@ -756,6 +766,75 @@ func TestDefinitionReportsRetainNativeFieldsAndSafeCredentialReferences(t *testi } } +func TestOptionalPromptVersionsPreserveAbsenceAndEmptyValues(t *testing.T) { + empty := "" + label := "release-1" + for _, test := range []struct { + name string + version *string + present bool + want string + }{ + {name: "absent"}, + {name: "present empty", version: &empty, present: true, want: ""}, + {name: "nonempty", version: &label, present: true, want: "release-1"}, + } { + t.Run(test.name, func(t *testing.T) { + prompt := promptReportFromInspection(&promptkit.PromptInspection{PromptID: "briefing", PromptVersion: test.version}) + promptBytes, err := encodePromptYAMLBytes(prompt) + if err != nil { + t.Fatal(err) + } + var promptReport map[string]any + if err := yaml.Unmarshal(promptBytes, &promptReport); err != nil { + t.Fatal(err) + } + assertOptionalVersion(t, promptReport, test.present, test.want) + text := formatPromptText(prompt) + if test.present && !strings.Contains(text, "Prompt version: "+strconv.Quote(test.want)+"\n") { + t.Errorf("text omitted present version %q: %q", test.want, text) + } + if !test.present && strings.Contains(text, "Prompt version:") { + t.Errorf("text emitted absent version: %q", text) + } + + requirementsBytes, err := encodePromptYAMLBytes(requirementsReportFromInspection(&promptkit.RequirementInspection{PromptID: "briefing", PromptVersion: test.version})) + if err != nil { + t.Fatal(err) + } + var requirementsReport map[string]any + if err := yaml.Unmarshal(requirementsBytes, &requirementsReport); err != nil { + t.Fatal(err) + } + assertOptionalVersion(t, requirementsReport, test.present, test.want) + + catalogBytes, err := encodePromptCatalog([]promptkit.PromptIdentity{{ID: "briefing", Version: test.version}}) + if err != nil { + t.Fatal(err) + } + var catalog []map[string]any + if err := yaml.Unmarshal(catalogBytes, &catalog); err != nil { + t.Fatal(err) + } + if len(catalog) != 1 || catalog[0]["id"] != "briefing" { + t.Fatalf("catalog = %#v", catalog) + } + assertOptionalVersion(t, catalog[0], test.present, test.want) + }) + } +} + +func assertOptionalVersion(t *testing.T, report map[string]any, present bool, want string) { + t.Helper() + version, found := report["version"] + if found != present { + t.Fatalf("version presence = %t in %#v, want %t", found, report, present) + } + if present && version != want { + t.Errorf("version = %#v, want %q", version, want) + } +} + func TestNativeLookupErrorsAndBlankIDsReachCallbacks(t *testing.T) { nativeErr := errors.New("native lookup failure") calls := 0 @@ -780,6 +859,18 @@ func TestNativeLookupErrorsAndBlankIDsReachCallbacks(t *testing.T) { } } +func TestNamedPromptInspectionRejectsRemovedVersionFlag(t *testing.T) { + calls := 0 + app := newPromptTestApp(t, Command(Suite[struct{}, struct{}]{InspectPrompt: func(_ context.Context, request PromptRequest, _, _ struct{}) (commandkit.Acquired[*promptkit.PromptInspection], error) { + calls++ + return commandkit.Acquired[*promptkit.PromptInspection]{Value: &promptkit.PromptInspection{PromptID: request.ID}}, nil + }})) + outcome := app.Execute(context.Background(), []string{"promptkit", "show", "prompt", "briefing", "--version", "1"}, commandkit.Streams{Out: io.Discard, Err: io.Discard}) + if outcome.Kind != commandkit.OutcomeUsage || calls != 0 { + t.Fatalf("removed version flag outcome = %#v, provider calls=%d", outcome, calls) + } +} + func TestCancelledInspectionDoesNotRender(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) called := 0 @@ -838,9 +929,9 @@ func TestProfileTextUsesCredentialNamesAndExplicitEmptyExtraParams(t *testing.T) func TestPromptCatalogYAMLPreservesSchemaStringsAndOrder(t *testing.T) { want := []promptkit.PromptIdentity{ - {ID: " briefing ", Version: " 03 "}, - {ID: "001", Version: "1.10"}, - {ID: " briefing ", Version: " 03 "}, + {ID: " briefing ", Version: testStringPointer(" 03 ")}, + {ID: "001", Version: testStringPointer("1.10")}, + {ID: " briefing ", Version: testStringPointer(" 03 ")}, } output, err := encodePromptCatalog(want) if err != nil { @@ -859,8 +950,8 @@ func TestPromptCatalogYAMLPreservesSchemaStringsAndOrder(t *testing.T) { } id, idOK := entry["id"].(string) version, versionOK := entry["version"].(string) - if !idOK || !versionOK || id != want[index].ID || version != want[index].Version { - t.Errorf("entry %d = %#v, want id/version %q/%q as strings", index, entry, want[index].ID, want[index].Version) + if !idOK || !versionOK || id != want[index].ID || want[index].Version == nil || version != *want[index].Version { + t.Errorf("entry %d = %#v, want id/version %q/%#v", index, entry, want[index].ID, want[index].Version) } } } @@ -917,11 +1008,14 @@ func TestRequirementsReportRendersEmptyInventoriesAndFalseValues(t *testing.T) { if err := yaml.Unmarshal(output, &report); err != nil { t.Fatalf("decode requirements report %q: %v", output, err) } - for _, key := range []string{"prompt_id", "prompt_version", "profile_id", "complete", "inputs", "variables", "issues"} { + for _, key := range []string{"prompt_id", "profile_id", "complete", "inputs", "variables", "issues"} { if _, ok := report[key]; !ok { t.Errorf("requirements report is missing %q: %q", key, output) } } + if _, ok := report["version"]; ok { + t.Errorf("absent prompt version was emitted: %q", output) + } if report["complete"] != false { t.Errorf("complete = %#v, want false", report["complete"]) } @@ -931,7 +1025,7 @@ func TestRequirementsReportRendersEmptyInventoriesAndFalseValues(t *testing.T) { t.Errorf("%s = %#v, want an empty sequence", key, report[key]) } } - for _, key := range []string{"prompt_id", "prompt_version", "profile_id"} { + for _, key := range []string{"prompt_id", "profile_id"} { if report[key] != "" { t.Errorf("%s = %#v, want an explicitly present empty string", key, report[key]) } @@ -943,7 +1037,7 @@ func TestRequirementsReportPreservesNativeFieldsAndMetadata(t *testing.T) { contentType := "true" inspection := &promptkit.RequirementInspection{ PromptID: "true", - PromptVersion: "001", + PromptVersion: testStringPointer("001"), ProfileID: "null", Complete: true, Inputs: []promptkit.InputRequirement{ @@ -973,7 +1067,7 @@ func TestRequirementsReportPreservesNativeFieldsAndMetadata(t *testing.T) { if err := yaml.Unmarshal(output, &report); err != nil { t.Fatalf("decode requirements report %q: %v", output, err) } - if report["complete"] != complete || report["prompt_id"] != "true" || report["prompt_version"] != "001" || report["profile_id"] != "null" { + if report["complete"] != complete || report["prompt_id"] != "true" || report["version"] != "001" || report["profile_id"] != "null" { t.Fatalf("selected context or completeness = %#v", report) } diff --git a/integrations/ckprompt/comparison_composition_test.go b/integrations/ckprompt/comparison_composition_test.go index fa4e9e9..7de0b59 100644 --- a/integrations/ckprompt/comparison_composition_test.go +++ b/integrations/ckprompt/comparison_composition_test.go @@ -225,13 +225,14 @@ func comparisonEngine(t *testing.T, greeting string) *promptkit.Engine { t.Helper() configuration := `prompts: briefing: - "1": - messages: - - role: user - content: "Hello" - output: - format: text - validation_mode: none + version: "1" + default_profile: default + messages: + - role: user + content: "Hello" + output: + format: text + validation_mode: none backends: local: endpoint: "https://example.invalid/v1" diff --git a/integrations/ckprompt/composition_test.go b/integrations/ckprompt/composition_test.go index 5e04e28..730e67b 100644 --- a/integrations/ckprompt/composition_test.go +++ b/integrations/ckprompt/composition_test.go @@ -70,13 +70,14 @@ func TestPublicConfigurationAndDefinitionFamiliesRemainIndependent(t *testing.T) inputs, err := promptkit.FilesystemInputs("composition fixture", fstest.MapFS{ "config/prompts.yml": &fstest.MapFile{Data: []byte(`prompts: briefing: - "1": - messages: - - role: user - content: "Hello" - output: - format: text - validation_mode: none + version: "1" + default_profile: default + messages: + - role: user + content: "Hello" + output: + format: text + validation_mode: none backends: local: endpoint: "https://example.invalid/v1" diff --git a/integrations/ckprompt/examples/composed/main.go b/integrations/ckprompt/examples/composed/main.go index 245a279..9072cd3 100644 --- a/integrations/ckprompt/examples/composed/main.go +++ b/integrations/ckprompt/examples/composed/main.go @@ -118,12 +118,11 @@ func loadPromptEngine(ctx context.Context, selection ckconfig.Selection, input * }, }}, "prompts": map[string]any{chosen.PromptID: map[string]any{ - 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}}. {{if .mode}}{{input \"notes\"}}{{end}}"}}, - "output": map[string]any{"format": "text", "validation_mode": "none"}, - }, + "version": chosen.PromptVersion, + "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}}. {{if .mode}}{{input \"notes\"}}{{end}}"}}, + "output": map[string]any{"format": "text", "validation_mode": "none"}, }}, }) return promptkit.NewEngine(promptkit.Config{Sources: promptkit.ConfigurationSources{Main: &configkit.Input{Source: engineConfig}}}) diff --git a/integrations/ckprompt/examples/composed/main_test.go b/integrations/ckprompt/examples/composed/main_test.go index d63dc88..ef38f53 100644 --- a/integrations/ckprompt/examples/composed/main_test.go +++ b/integrations/ckprompt/examples/composed/main_test.go @@ -127,21 +127,11 @@ func TestComposedRequirementsDiscoveryUsesSelectedCampaign(t *testing.T) { } for _, test := range []struct { - name string - args []string - wantMode bool - wantModeIssue bool + name string + args []string }{ - { - 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, - }, + {name: "without variables", args: []string{"promptkit", "requirements", "show", "briefing", "--campaign", "spring"}}, + {name: "with caller variable", args: []string{"promptkit", "requirements", "show", "briefing", "--campaign", "spring", "--var", "mode=full", "--format", "yaml"}}, } { t.Run(test.name, func(t *testing.T) { var output bytes.Buffer @@ -150,65 +140,17 @@ func TestComposedRequirementsDiscoveryUsesSelectedCampaign(t *testing.T) { 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"` - } + var report map[string]any 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 _, key := range []string{"prompt_id", "version", "profile_id", "complete", "inputs", "variables", "issues"} { + if _, found := report[key]; !found { + t.Errorf("requirements report is missing %q: %#v", key, report) } } - 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) + if report["prompt_id"] != "briefing" || report["version"] != "1" || report["profile_id"] != "spring" { + t.Fatalf("requirements report metadata = %#v", report) } }) } @@ -268,19 +210,18 @@ func TestBuildValidationRequestDefaultsOnlyAbsentVariables(t *testing.T) { inputs := map[string]promptkit.ArtifactRef{"topic": promptkit.File("fixtures/topic.txt")} explicit := map[string]string{"topic": "", "mode": "", "audience": ""} seed := ckprompt.ValidationRequest{Request: promptkit.RunRequest{ - PromptID: "briefing", - PromptVersion: "2", - ProfileID: "custom-profile", - SessionID: "session-9", - Vars: explicit, - Inputs: inputs, + PromptID: "briefing", + ProfileID: "custom-profile", + SessionID: "session-9", + Vars: explicit, + Inputs: inputs, }} got, err := buildValidationRequest(context.Background(), seed, input, cache) if err != nil { t.Fatal(err) } - if got.PromptID != seed.Request.PromptID || got.PromptVersion != seed.Request.PromptVersion || got.ProfileID != seed.Request.ProfileID || got.SessionID != seed.Request.SessionID { + if got.PromptID != seed.Request.PromptID || got.ProfileID != seed.Request.ProfileID || got.SessionID != seed.Request.SessionID { t.Fatalf("request identity/context changed: %#v", got) } if got.Vars["topic"] != "" || got.Vars["mode"] != "" || got.Vars["audience"] != "" { diff --git a/integrations/ckprompt/requirements_command_test.go b/integrations/ckprompt/requirements_command_test.go index 2ccb327..6087574 100644 --- a/integrations/ckprompt/requirements_command_test.go +++ b/integrations/ckprompt/requirements_command_test.go @@ -17,41 +17,40 @@ import ( const requirementsCommandPrompt = `prompts: briefing: - "2": - default_profile: editorial - session_id: '{{input "session-only"}} {{.session_variable}}' - messages: - - role: user - content: '{{if .mode}}{{input "notes"}}{{end}}' - output: - format: text - validation_mode: none + version: "2" + default_profile: editorial + session_id: '{{input "session-only"}} {{.session_variable}}' + messages: + - role: user + content: '{{if .mode}}{{input "notes"}}{{end}}' + output: + format: text + validation_mode: none required-variable: - "1": - default_profile: editorial - messages: - - role: user - content: '{{.required}}' - output: - format: text - validation_mode: none - ambiguous: - "1": - default_profile: editorial - messages: - - role: user - content: "First version" - output: - format: text - validation_mode: none - "2": - default_profile: editorial - messages: - - role: user - content: "Second version" - output: - format: text - validation_mode: none + version: "1" + default_profile: editorial + messages: + - role: user + content: '{{.required}}' + output: + format: text + validation_mode: none + ambiguous-first: + default_profile: editorial + messages: + - role: user + content: "First definition" + output: + format: text + validation_mode: none + ambiguous-second: + default_profile: editorial + messages: + - role: user + content: "Second definition" + output: + format: text + validation_mode: none backends: local: endpoint: "https://example.invalid/v1" @@ -101,7 +100,7 @@ func executeRequirementsAgainstNative(t *testing.T, engine *promptkit.Engine, re return native, outcome, actual, diagnostic.String() } -func TestRequirementsCommandMatchesNativePromptKitDiscovery(t *testing.T) { +func TestRequirementsCommandForwardsNativeRequestAndProjectsInspection(t *testing.T) { engine := requirementsCommandEngine(t) for _, test := range []struct { name string @@ -109,82 +108,50 @@ func TestRequirementsCommandMatchesNativePromptKitDiscovery(t *testing.T) { args []string }{ { - name: "partial conditional discovery", - request: promptkit.RequirementInspectionRequest{PromptID: "briefing", PromptVersion: "2", ProfileID: "editorial"}, - args: []string{"promptkit", "requirements", "show", "briefing", "--version", "2", "--profile", "editorial"}, - }, - { - name: "supplied branch variable", - request: promptkit.RequirementInspectionRequest{ - PromptID: "briefing", PromptVersion: "2", ProfileID: "editorial", Vars: map[string]string{"mode": "full"}, - }, - args: []string{"promptkit", "requirements", "show", "briefing", "--version", "2", "--profile", "editorial", "--var", "mode=full", "--format", "yaml"}, - }, - { - name: "complete with an unsupplied required variable", - request: promptkit.RequirementInspectionRequest{PromptID: "required-variable", PromptVersion: "1", ProfileID: "editorial"}, - args: []string{"promptkit", "requirements", "show", "required-variable", "--version", "1", "--profile", "editorial"}, - }, - { - name: "native version and default profile selection", - request: promptkit.RequirementInspectionRequest{PromptID: "briefing"}, - args: []string{"promptkit", "requirements", "show", "briefing"}, - }, - { - name: "session template discovery", + name: "conditional discovery", request: promptkit.RequirementInspectionRequest{PromptID: "briefing", ProfileID: "editorial"}, args: []string{"promptkit", "requirements", "show", "briefing", "--profile", "editorial"}, }, { - name: "session ID bypasses the session template", + name: "supplied branch variable", + request: promptkit.RequirementInspectionRequest{PromptID: "briefing", ProfileID: "editorial", Vars: map[string]string{"mode": "full"}}, + args: []string{"promptkit", "requirements", "show", "briefing", "--profile", "editorial", "--var", "mode=full", "--format", "yaml"}, + }, + { + name: "prompt with a required variable", + request: promptkit.RequirementInspectionRequest{PromptID: "required-variable", ProfileID: "editorial"}, + args: []string{"promptkit", "requirements", "show", "required-variable", "--profile", "editorial"}, + }, + { + name: "default profile selection", + request: promptkit.RequirementInspectionRequest{PromptID: "briefing"}, + args: []string{"promptkit", "requirements", "show", "briefing"}, + }, + { + name: "first distinct prompt ID", + request: promptkit.RequirementInspectionRequest{PromptID: "ambiguous-first", ProfileID: "editorial"}, + args: []string{"promptkit", "requirements", "show", "ambiguous-first", "--profile", "editorial"}, + }, + { + name: "second distinct prompt ID", + request: promptkit.RequirementInspectionRequest{PromptID: "ambiguous-second", ProfileID: "editorial"}, + args: []string{"promptkit", "requirements", "show", "ambiguous-second", "--profile", "editorial"}, + }, + { + name: "session template context", + request: promptkit.RequirementInspectionRequest{PromptID: "briefing", ProfileID: "editorial"}, + args: []string{"promptkit", "requirements", "show", "briefing", "--profile", "editorial"}, + }, + { + name: "session ID context", request: promptkit.RequirementInspectionRequest{PromptID: "briefing", ProfileID: "editorial", SessionID: "session-42"}, args: []string{"promptkit", "requirements", "show", "briefing", "--profile", "editorial", "--session-id", "session-42"}, }, } { t.Run(test.name, func(t *testing.T) { - native, outcome, _, _ := executeRequirementsAgainstNative(t, engine, test.request, test.args) + _, outcome, _, _ := executeRequirementsAgainstNative(t, engine, test.request, test.args) if outcome.Status != 0 { - t.Fatalf("partial or complete inspection returned status %d", outcome.Status) - } - switch test.name { - case "partial conditional discovery": - if native.Complete || len(native.Issues) == 0 || len(native.Inputs) == 0 { - t.Fatalf("partial discovery lost useful evidence: %#v", native) - } - case "supplied branch variable": - if !native.Complete || len(native.Issues) != 0 { - t.Fatalf("resolved discovery = %#v", native) - } - case "complete with an unsupplied required variable": - if !native.Complete || len(native.Variables) != 1 || native.Variables[0].Supplied { - t.Fatalf("complete discovery with unsupplied variable = %#v", native) - } - case "native version and default profile selection": - if native.PromptVersion != "2" || native.ProfileID != "editorial" { - t.Fatalf("native default selection = %#v", native) - } - case "session template discovery": - foundInput, foundVariable := false, false - for _, input := range native.Inputs { - foundInput = foundInput || input.Name == "session-only" - } - for _, variable := range native.Variables { - foundVariable = foundVariable || variable.Name == "session_variable" - } - if !foundInput || !foundVariable { - t.Fatalf("session template dependencies missing: %#v", native) - } - case "session ID bypasses the session template": - for _, input := range native.Inputs { - if input.Name == "session-only" { - t.Fatalf("session template input remained with session ID: %#v", native.Inputs) - } - } - for _, variable := range native.Variables { - if variable.Name == "session_variable" { - t.Fatalf("session template variable remained with session ID: %#v", native.Variables) - } - } + t.Fatalf("inspection returned status %d", outcome.Status) } }) } @@ -193,40 +160,38 @@ func TestRequirementsCommandMatchesNativePromptKitDiscovery(t *testing.T) { func TestRequirementsCommandPreservesNativeSelectionErrors(t *testing.T) { engine := requirementsCommandEngine(t) for _, test := range []struct { - name string - request promptkit.RequirementInspectionRequest - args []string - cause error + name string + args []string + cause error }{ { - name: "missing prompt", - request: promptkit.RequirementInspectionRequest{PromptID: "missing", PromptVersion: "2", ProfileID: "editorial"}, - args: []string{"promptkit", "requirements", "show", "missing", "--version", "2", "--profile", "editorial"}, - cause: promptkit.ErrPromptNotFound, + name: "missing prompt", + args: []string{"promptkit", "requirements", "show", "missing", "--profile", "editorial"}, + cause: promptkit.ErrPromptNotFound, }, { - name: "missing profile", - request: promptkit.RequirementInspectionRequest{PromptID: "briefing", PromptVersion: "2", ProfileID: "missing"}, - args: []string{"promptkit", "requirements", "show", "briefing", "--version", "2", "--profile", "missing"}, - cause: promptkit.ErrProfileNotFound, + name: "missing profile", + args: []string{"promptkit", "requirements", "show", "briefing", "--profile", "missing"}, + cause: promptkit.ErrProfileNotFound, }, { - name: "ambiguous prompt version", - request: promptkit.RequirementInspectionRequest{PromptID: "ambiguous", ProfileID: "editorial"}, - args: []string{"promptkit", "requirements", "show", "ambiguous", "--profile", "editorial"}, - cause: promptkit.ErrPromptLoad, + name: "removed prompt version flag", + args: []string{"promptkit", "requirements", "show", "briefing", "--version", "2", "--profile", "editorial"}, + cause: nil, }, } { t.Run(test.name, func(t *testing.T) { - _, nativeErr := engine.InspectRequirements(context.Background(), test.request) - if !errors.Is(nativeErr, test.cause) { - t.Fatalf("native selection error = %v, want %v", nativeErr, test.cause) - } app := newPromptTestApp(t, Command(FromEngine[int, int](func(context.Context, ckconfig.Selection, int, int) (*promptkit.Engine, error) { return engine, nil }))) var output bytes.Buffer outcome := app.Execute(context.Background(), test.args, commandkit.Streams{Out: &output, Err: io.Discard}) + if test.cause == nil { + if outcome.Kind != commandkit.OutcomeUsage || output.Len() != 0 { + t.Fatalf("removed flag outcome = %#v output=%q", outcome, output.String()) + } + return + } if outcome.Kind != commandkit.OutcomeOperational || outcome.Status != 1 || !errors.Is(outcome.Cause, test.cause) || output.Len() != 0 { t.Fatalf("native selection outcome = %#v output=%q", outcome, output.String()) } diff --git a/integrations/ckprompt/validation_command_test.go b/integrations/ckprompt/validation_command_test.go index 25584c1..ae97de9 100644 --- a/integrations/ckprompt/validation_command_test.go +++ b/integrations/ckprompt/validation_command_test.go @@ -64,13 +64,13 @@ func TestValidationCommandComposesWithRequirementsShow(t *testing.T) { outcome = app.Execute(context.Background(), []string{ "promptkit", "requirements", "validate", "briefing", "--config", "config.yml", "--overlay", "overlay.yml", "--campaign", "spring", - "--version", "v2", "--profile", "editorial", "--session-id", "session-1", + "--profile", "editorial", "--session-id", "session-1", "--var", "topic=roadmap", "--input", "notes=fixtures/notes=final.txt", "--format", "yaml", }, commandkit.Streams{Out: &output, Err: io.Discard}) if outcome.Kind != commandkit.OutcomeSuccess || outcome.Status != 0 || validateCalls != 1 { t.Fatalf("validation outcome = (%#v, %q), calls=%d", outcome, output.String(), validateCalls) } - if received.Selection != wantSelection || received.Request.PromptID != "briefing" || received.Request.PromptVersion != "v2" || received.Request.ProfileID != "editorial" || received.Request.SessionID != "session-1" { + if received.Selection != wantSelection || received.Request.PromptID != "briefing" || received.Request.ProfileID != "editorial" || received.Request.SessionID != "session-1" { t.Errorf("validation request = %#v", received) } if received.Request.Vars["topic"] != "roadmap" || len(received.Request.Vars) != 1 { @@ -197,7 +197,7 @@ func TestValidationCommandPreservesPresenceAndRepeatedAssignmentsPerInvocation(t firstArgs := []string{ "promptkit", "requirements", "validate", "briefing", "--config", "", "--overlay", "", - "--version", "", "--profile", "", "--session-id", "", "--campaign", "spring", + "--profile", "", "--session-id", "", "--campaign", "spring", "--var", "=", "--var", "phrase= two words, three ", "--var", "duplicate=first", "--var", "duplicate=last=value", "--input", "=", "--input", "file= path, with commas=and=equals ", "--input", "duplicate=first.txt", "--input", "duplicate=last=path.txt", } @@ -209,7 +209,7 @@ func TestValidationCommandPreservesPresenceAndRepeatedAssignmentsPerInvocation(t if first.Selection.Config.Value != "" || !first.Selection.Config.Set || first.Selection.Overlay.Value != "" || !first.Selection.Overlay.Set { t.Errorf("explicit empty selection = %#v", first.Selection) } - if first.Request.PromptVersion != "" || first.Request.ProfileID != "" || first.Request.SessionID != "" { + if first.Request.PromptID != "briefing" || first.Request.ProfileID != "" || first.Request.SessionID != "" { t.Errorf("empty scalar values = %#v", first.Request) } wantVars := map[string]string{"": "", "phrase": " two words, three ", "duplicate": "last=value"} @@ -233,7 +233,7 @@ func TestValidationCommandPreservesPresenceAndRepeatedAssignmentsPerInvocation(t t.Fatalf("second invocation = %#v, requests=%d", outcome, len(requests)) } second := requests[1] - if second.Selection.Config.Set || second.Selection.Overlay.Set || second.Request.PromptVersion != "" || second.Request.ProfileID != "" || second.Request.SessionID != "" || len(second.Request.Vars) != 0 || len(second.Request.Inputs) != 0 { + if second.Selection.Config.Set || second.Selection.Overlay.Set || second.Request.PromptID != "briefing" || second.Request.ProfileID != "" || second.Request.SessionID != "" || len(second.Request.Vars) != 0 || len(second.Request.Inputs) != 0 { t.Errorf("first invocation values leaked into second request: %#v", second) } if selectedInputs[1].campaign.Set || selectedInputs[1].campaign.Value != "" { @@ -256,6 +256,7 @@ func TestValidationCommandUsageAndInputFailuresStopProviderWork(t *testing.T) { {name: "extra operand", args: []string{"promptkit", "requirements", "validate", "briefing", "extra", "--campaign", "spring"}}, {name: "unsupported format", args: []string{"promptkit", "requirements", "validate", "briefing", "--campaign", "spring", "--format", "text"}}, {name: "unknown flag", args: []string{"promptkit", "requirements", "validate", "briefing", "--campaign", "spring", "--unknown", "value"}}, + {name: "removed prompt version flag", args: []string{"promptkit", "requirements", "validate", "briefing", "--campaign", "spring", "--version", "2"}}, {name: "malformed variable before valid assignment", args: []string{"promptkit", "requirements", "validate", "briefing", "--campaign", "spring", "--var", "broken", "--var", "mode=full"}}, {name: "malformed input before valid assignment", args: []string{"promptkit", "requirements", "validate", "briefing", "--campaign", "spring", "--input", "broken", "--input", "notes=file.txt"}}, {name: "missing required selector", args: []string{"promptkit", "requirements", "validate", "briefing"}},