Add PromptKit catalog YAML encoders
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
# Implement PromptKit comparison and catalogs
|
# Implement PromptKit comparison and catalogs
|
||||||
|
|
||||||
Status: Stages 1–5 complete; remaining stages are planned.
|
Status: Stages 1–6 complete; remaining stages are planned.
|
||||||
|
|
||||||
## Scope and authority
|
## Scope and authority
|
||||||
|
|
||||||
@@ -369,6 +369,19 @@ in Stage 8 once helpers are wired. Test decoded types, fields, sequence order,
|
|||||||
and empty inventories; incidental quoting style, private names, and indentation
|
and empty inventories; incidental quoting style, private names, and indentation
|
||||||
are not new contracts.
|
are not new contracts.
|
||||||
|
|
||||||
|
**Completion evidence (2026-10-12)**
|
||||||
|
|
||||||
|
- Added private YAML encoders that map native prompt identities to entries with
|
||||||
|
explicit string `id` and `version` fields and encode profile IDs as a bare
|
||||||
|
string sequence. Both return fully prepared bytes using the existing YAML
|
||||||
|
encoder; nil and allocated empty catalogs normalize to empty sequences.
|
||||||
|
- Unit coverage decodes prompt entries and profile values to check field names,
|
||||||
|
string types, order, duplicates, whitespace, and numeric-looking values. Nil
|
||||||
|
and allocated empty inputs both encode as `[]`.
|
||||||
|
- `GOTOOLCHAIN=local go test -count=1 .` passed in `integrations/ckprompt`.
|
||||||
|
Command-boundary schema coverage remains assigned to Stage 8; command wiring
|
||||||
|
and delivery remain assigned to Stage 7.
|
||||||
|
|
||||||
## Stage 7: Assemble and execute independent catalog commands
|
## Stage 7: Assemble and execute independent catalog commands
|
||||||
|
|
||||||
**Outcome:** catalog commands and optional named-show commands share existing
|
**Outcome:** catalog commands and optional named-show commands share existing
|
||||||
|
|||||||
@@ -322,6 +322,34 @@ func encodePromptYAML(writer io.Writer, value any) error {
|
|||||||
return encoder.Encode(value)
|
return encoder.Encode(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type promptCatalogEntry struct {
|
||||||
|
ID string `yaml:"id"`
|
||||||
|
Version string `yaml:"version"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodePromptCatalog(prompts []promptkit.PromptIdentity) ([]byte, error) {
|
||||||
|
entries := make([]promptCatalogEntry, len(prompts))
|
||||||
|
for index, prompt := range prompts {
|
||||||
|
entries[index] = promptCatalogEntry{ID: prompt.ID, Version: prompt.Version}
|
||||||
|
}
|
||||||
|
return encodePromptYAMLBytes(entries)
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeProfileCatalog(profiles []string) ([]byte, error) {
|
||||||
|
if profiles == nil {
|
||||||
|
profiles = []string{}
|
||||||
|
}
|
||||||
|
return encodePromptYAMLBytes(profiles)
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodePromptYAMLBytes(value any) ([]byte, error) {
|
||||||
|
var output bytes.Buffer
|
||||||
|
if err := encodePromptYAML(&output, value); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return output.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
func writePromptReport(invocation commandkit.Invocation, output []byte, diagnostics []commandkit.Diagnostic) commandkit.Outcome {
|
func writePromptReport(invocation commandkit.Invocation, output []byte, diagnostics []commandkit.Diagnostic) commandkit.Outcome {
|
||||||
if invocation.Context != nil {
|
if invocation.Context != nil {
|
||||||
if err := invocation.Context.Err(); err != nil {
|
if err := invocation.Context.Err(); err != nil {
|
||||||
|
|||||||
@@ -309,3 +309,75 @@ func TestProfileTextUsesCredentialNamesAndExplicitEmptyExtraParams(t *testing.T)
|
|||||||
t.Fatalf("report contains a credential value: %q", text)
|
t.Fatalf("report contains a credential value: %q", text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPromptCatalogYAMLPreservesSchemaStringsAndOrder(t *testing.T) {
|
||||||
|
want := []promptkit.PromptIdentity{
|
||||||
|
{ID: " briefing ", Version: " 03 "},
|
||||||
|
{ID: "001", Version: "1.10"},
|
||||||
|
{ID: " briefing ", Version: " 03 "},
|
||||||
|
}
|
||||||
|
output, err := encodePromptCatalog(want)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
var decoded []map[string]any
|
||||||
|
if err := yaml.Unmarshal(output, &decoded); err != nil {
|
||||||
|
t.Fatalf("decode prompt catalog %q: %v", output, err)
|
||||||
|
}
|
||||||
|
if len(decoded) != len(want) {
|
||||||
|
t.Fatalf("catalog entry count = %d, want %d: %q", len(decoded), len(want), output)
|
||||||
|
}
|
||||||
|
for index, entry := range decoded {
|
||||||
|
if len(entry) != 2 {
|
||||||
|
t.Errorf("entry %d fields = %#v, want only id and version", index, entry)
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCatalogYAMLEncodersRenderNilAndEmptyInventoriesAsSequences(t *testing.T) {
|
||||||
|
for _, test := range []struct {
|
||||||
|
name string
|
||||||
|
data func() ([]byte, error)
|
||||||
|
}{
|
||||||
|
{name: "nil prompts", data: func() ([]byte, error) { return encodePromptCatalog(nil) }},
|
||||||
|
{name: "empty prompts", data: func() ([]byte, error) { return encodePromptCatalog(make([]promptkit.PromptIdentity, 0)) }},
|
||||||
|
{name: "nil profiles", data: func() ([]byte, error) { return encodeProfileCatalog(nil) }},
|
||||||
|
{name: "empty profiles", data: func() ([]byte, error) { return encodeProfileCatalog(make([]string, 0)) }},
|
||||||
|
} {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
output, err := test.data()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(string(output)) != "[]" {
|
||||||
|
t.Fatalf("empty catalog YAML = %q, want empty sequence", output)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProfileCatalogYAMLPreservesStringValuesAndOrder(t *testing.T) {
|
||||||
|
want := []string{" default ", "001", "same", "same"}
|
||||||
|
output, err := encodeProfileCatalog(want)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
var decoded []any
|
||||||
|
if err := yaml.Unmarshal(output, &decoded); err != nil {
|
||||||
|
t.Fatalf("decode profile catalog %q: %v", output, err)
|
||||||
|
}
|
||||||
|
if len(decoded) != len(want) {
|
||||||
|
t.Fatalf("catalog entry count = %d, want %d: %q", len(decoded), len(want), output)
|
||||||
|
}
|
||||||
|
for index, item := range decoded {
|
||||||
|
value, ok := item.(string)
|
||||||
|
if !ok || value != want[index] {
|
||||||
|
t.Errorf("profile %d = %#v, want string %q", index, item, want[index])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user