Add aliases to maintained examples

This commit is contained in:
2026-10-19 19:56:33 +00:00
parent c3bb3941d3
commit 666c006a89
8 changed files with 194 additions and 11 deletions

View File

@@ -25,7 +25,7 @@ integration.
## Examples
- [Core-only example](examples/core/main.go)
- [Core-only example](examples/core/README.md)
- [ConfigKit example](integrations/ckconfig/examples/simple/README.md)
- [Composed ConfigKit and PromptKit example](integrations/ckprompt/examples/composed/README.md)

View File

@@ -1,6 +1,6 @@
# Application-owned aliases implementation plan
Status: in progress; Stages 1–13 completed.
Status: in progress; Stages 1–14 completed.
## Authority, baseline, and scope
@@ -657,6 +657,17 @@ the examples remain complete, copyable artifacts. Explain local source testing
and forthcoming core dependency adoption in their developer-facing guidance;
do not claim published core v0.2.0 has alias support.
**Completed:** The core example now installs command and group shortcuts,
global and scoped flag aliases in a mixed declaration slice, and prints entered
and canonical invocation paths. The composed example installs collection,
PromptKit group, bundle, smoke, scoped format, and global help aliases alongside
its complete canonical declarations. Example tests cover shortcut and
canonical operations, help with zero PromptKit engine loads, and local smoke
report delivery. Both example READMEs explain local-source use and that
published core v0.2.0 lacks alias support. Verified with `go test ./... -count=1`
at the root and the complete ckprompt module suite under the local workspace;
all newly documented alias commands also ran successfully.
## Stage 15: Reconcile public and internal documentation
**Work**

22
examples/core/README.md Normal file
View File

@@ -0,0 +1,22 @@
# Core example
Run the example from the repository root:
```sh
go run ./examples/core hello --times=0 --tag=first
go run ./examples/core ws greet --count=2
go run ./examples/core ws --help
go run ./examples/core hello --hlep
```
The example installs a canonical `workspace greet` command together with a
command shortcut, a group shortcut, a scoped flag alias, and a global help
alias. Its declaration slice mixes the canonical group and routing rules as
`[]commandkit.Declaration`. The handler prints `Invocation.EnteredPath()` and
`Invocation.CanonicalPath()` so the alternate route and its canonical target
are visible in the output.
These aliases use the current local CommandKit source. Published CommandKit
v0.2.0 does not include alias declarations; consumers can use them after the
next core release. Repository contributors can run its test with
`go test ./examples/core` from the root.

View File

@@ -37,14 +37,21 @@ func newApp() (*commandkit.App, error) {
if input.Tags.Set {
tags = fmt.Sprintf("explicit %v", input.Tags.Value)
}
if _, err := fmt.Fprintf(invocation.Streams.Out, "Hello; count=%s; tags=%s\n", count, tags); err != nil {
if _, err := fmt.Fprintf(invocation.Streams.Out, "Hello; count=%s; tags=%s; entered=%q; canonical=%q\n", count, tags, invocation.EnteredPath(), invocation.CanonicalPath()); err != nil {
return commandkit.Failure(err)
}
return commandkit.Success()
},
})
workspace := commandkit.Group("workspace", "Workspace commands.", greet)
return commandkit.New(commandkit.Identity{Name: "core-example", Version: "1.0"}, workspace)
declarations := []commandkit.Declaration{
workspace,
commandkit.Alias("hello", "workspace greet"),
commandkit.Alias("ws", "workspace"),
commandkit.FlagAliasFor("workspace greet", "--times", "--count"),
commandkit.FlagAlias("--hlep", "--help"),
}
return commandkit.New(commandkit.Identity{Name: "core-example", Version: "1.0"}, declarations...)
}
func main() {

View File

@@ -0,0 +1,63 @@
package main
import (
"bytes"
"context"
"io"
"strings"
"testing"
commandkit "gitea.maximumdirect.net/eric/CommandKit"
)
func TestAliasesReachCoreExampleCommand(t *testing.T) {
app, err := newApp()
if err != nil {
t.Fatal(err)
}
for _, test := range []struct {
name string
args []string
entered string
canonical string
greeting string
}{
{name: "command shortcut and scoped flag", args: []string{"hello", "--times=0", "--tag", "one"}, entered: "hello", canonical: "workspace greet", greeting: "count=explicit 0; tags=explicit [one]"},
{name: "group shortcut", args: []string{"ws", "greet", "--count=2"}, entered: "ws greet", canonical: "workspace greet", greeting: "count=explicit 2"},
{name: "canonical command", args: []string{"workspace", "greet"}, entered: "workspace greet", canonical: "workspace greet", greeting: "count=default 1 (not supplied)"},
} {
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 {
t.Fatalf("Execute(%q) = %#v; output %q", test.args, outcome, output.String())
}
for _, fragment := range []string{
test.greeting,
"entered=\"" + test.entered + "\"",
"canonical=\"" + test.canonical + "\"",
} {
if !strings.Contains(output.String(), fragment) {
t.Errorf("output %q does not contain %q", output.String(), fragment)
}
}
})
}
}
func TestGlobalHelpAliasDoesNotRunCoreExampleCommand(t *testing.T) {
app, err := newApp()
if err != nil {
t.Fatal(err)
}
var output bytes.Buffer
outcome := app.Execute(context.Background(), []string{"hello", "--hlep"}, commandkit.Streams{Out: &output, Err: io.Discard})
if outcome.Kind != commandkit.OutcomeSuccess || !strings.Contains(output.String(), "--hlep → --help") || strings.Contains(output.String(), "Hello;") {
t.Fatalf("help alias outcome/output = (%#v, %q)", outcome, output.String())
}
output.Reset()
outcome = app.Execute(context.Background(), []string{"ws", "--help"}, commandkit.Streams{Out: &output, Err: io.Discard})
if outcome.Kind != commandkit.OutcomeSuccess || !strings.Contains(output.String(), "greet") || strings.Contains(output.String(), "Hello;") {
t.Fatalf("group shortcut help outcome/output = (%#v, %q)", outcome, output.String())
}
}

View File

@@ -15,8 +15,11 @@ go run . promptkit list profiles --config fixtures/after.yml --campaign autumn -
go run . promptkit list backends --campaign spring
go run . promptkit list namespaces --campaign spring
go run . promptkit list collections --campaign spring
go run . list collections --campaign spring
go run . pk list bundles --campaign spring
go run . promptkit list prompts --namespace field-notes --campaign spring
go run . promptkit show prompt briefing --campaign spring --format yaml
go run . pk show prompt briefing --campaign spring --fmt yaml
go run . promptkit show profile spring --campaign spring
go run . promptkit requirements show briefing --campaign spring
go run . promptkit requirements show briefing --campaign spring --var mode=full --format yaml
@@ -24,10 +27,19 @@ go run . promptkit requirements validate briefing --campaign spring --input topi
go run . promptkit requirements validate briefing --campaign spring --input topic=fixtures/topic.txt --var mode=full --input notes=fixtures/notes.txt --format yaml
go run . promptkit requirements validate briefing --campaign spring
go run . promptkit test text_inference --campaign spring --prompt briefing --profile spring
go run . smoke text_inference --campaign spring --prompt briefing --profile spring
go run . promptkit test all --campaign spring --prompt briefing --profile spring
go run . pk --hlep
go run . session restore session-42 --campaign spring
```
`newApp` installs application-owned command and flag aliases alongside the
complete `ckconfig` and `ckprompt` declarations. `list collections` and
`promptkit list bundles` both reach the canonical collection command; `pk`
shortens the PromptKit group. `--fmt` is scoped to named prompt inspection and
`--hlep` is available globally. The `smoke` shortcut retains the canonical
`promptkit test KIND` operand, including the special `all` aggregate.
The sources and session store are small in-memory fixtures. `--campaign` is a
typed selector shared by the app and PromptKit configuration scopes. Their
`ckconfig.Suite[*inspectionInput, *runCache]` uses `NewInput` to initialize each
@@ -92,3 +104,12 @@ required `topic` artifact is not supplied. The builder defaults variables from
the selected campaign but leaves artifact bindings to the operator. The
`requirements show` commands remain discovery reports: partial discovery can
succeed even when a concrete request would fail validation.
The alias examples use the local CommandKit source because the composed
module's published core dependency, v0.2.0, predates alias declarations. From
the repository root, add the three local modules to `go.work` as described in
the [developer guide](../../../../docs/development.md#documentation-validation),
then run `go test ./...` from this example directory. This checks the example
against the current local core and integration sources; it does not claim that
published v0.2.0 supports aliases. The next core dependency adoption is handled
after the core release is prepared.

View File

@@ -200,6 +200,10 @@ func newApp() (*commandkit.App, error) {
}
func newAppWithLLMClient(client promptkit.LLMClient) (*commandkit.App, error) {
return newAppWithEngineLoader(client, loadPromptEngine)
}
func newAppWithEngineLoader(client promptkit.LLMClient, engineLoader func(context.Context, ckconfig.Selection, *inspectionInput, *runCache) (*promptkit.Engine, error)) (*commandkit.App, error) {
if client == nil {
client = &exampleLLMClient{}
}
@@ -210,7 +214,7 @@ func newAppWithLLMClient(client promptkit.LLMClient) (*commandkit.App, error) {
stateFactory := func() *runCache { return &runCache{llmClient: client} }
appScope := ckconfig.FromConfigKit("app", "Application settings.", loadApplicationConfig)
engineScope := ckprompt.ConfigScope(loadPromptEngine)
engineScope := ckprompt.ConfigScope(engineLoader)
configCommand := ckconfig.Command(ckconfig.Suite[*inspectionInput, *runCache]{
NewInput: inputFactory,
NewState: stateFactory,
@@ -227,12 +231,12 @@ func newAppWithLLMClient(client promptkit.LLMClient) (*commandkit.App, error) {
Scopes: []ckconfig.Scope[*inspectionInput, *runCache]{appScope, engineScope},
})
definitions := ckprompt.FromEngine(loadPromptEngine)
definitions := ckprompt.FromEngine(engineLoader)
definitions.NewInput = inputFactory
definitions.NewState = stateFactory
definitions.Selectors = []commandkit.Flag[*inspectionInput]{campaignSelector()}
definitions = ckprompt.WithRequestValidation(definitions, loadPromptEngine, buildValidationRequest)
definitions = ckprompt.WithSmokeTesting(definitions, loadPromptEngine, nil)
definitions = ckprompt.WithRequestValidation(definitions, engineLoader, buildValidationRequest)
definitions = ckprompt.WithSmokeTesting(definitions, engineLoader, nil)
definitionCommand := ckprompt.Command(definitions)
store := map[string]session{
@@ -270,12 +274,18 @@ func newAppWithLLMClient(client promptkit.LLMClient) (*commandkit.App, error) {
return commandkit.Success()
},
})
return commandkit.New(
commandkit.Identity{Name: "composed-example", Version: "1.0"},
declarations := []commandkit.Declaration{
configCommand,
definitionCommand,
commandkit.Group("session", "Manage saved sessions.", restore),
)
commandkit.Alias("list collections", "promptkit list collections"),
commandkit.Alias("promptkit list bundles", "promptkit list collections"),
commandkit.Alias("pk", "promptkit"),
commandkit.Alias("smoke", "promptkit test"),
commandkit.FlagAliasFor("promptkit show prompt", "--fmt", "--format"),
commandkit.FlagAlias("--hlep", "--help"),
}
return commandkit.New(commandkit.Identity{Name: "composed-example", Version: "1.0"}, declarations...)
}
func parseString(raw string) (string, error) { return raw, nil }

View File

@@ -5,6 +5,7 @@ import (
"context"
"io"
"reflect"
"strings"
"testing"
commandkit "gitea.maximumdirect.net/eric/CommandKit"
@@ -350,6 +351,54 @@ func TestComposedCatalogsAndSmokeUseInjectedClientOnlyForInference(t *testing.T)
}
}
func TestComposedAliasesReachCanonicalCommands(t *testing.T) {
clientCalls := 0
engineLoads := 0
engineLoader := func(ctx context.Context, selection ckconfig.Selection, input *inspectionInput, cache *runCache) (*promptkit.Engine, error) {
engineLoads++
return loadPromptEngine(ctx, selection, input, cache)
}
app, err := newAppWithEngineLoader(&exampleLLMClient{calls: &clientCalls}, engineLoader)
if err != nil {
t.Fatal(err)
}
var output bytes.Buffer
outcome := app.Execute(context.Background(), []string{"pk", "--hlep"}, commandkit.Streams{Out: &output, Err: io.Discard})
if outcome.Kind != commandkit.OutcomeSuccess || !strings.Contains(output.String(), "--hlep → --help") || strings.Contains(output.String(), "field-notes") || engineLoads != 0 || clientCalls != 0 {
t.Fatalf("group help alias outcome/output/engine loads/client calls = (%#v, %q, %d, %d)", outcome, output.String(), engineLoads, clientCalls)
}
for _, args := range [][]string{
{"list", "collections", "--campaign", "spring"},
{"pk", "list", "bundles", "--campaign", "spring"},
{"promptkit", "list", "collections", "--campaign", "spring"},
} {
output.Reset()
outcome = app.Execute(context.Background(), args, commandkit.Streams{Out: &output, Err: io.Discard})
if outcome.Kind != commandkit.OutcomeSuccess || !strings.Contains(output.String(), "field-notes") {
t.Fatalf("collection route %q outcome/report = (%#v, %q)", args, outcome, output.String())
}
}
for _, args := range [][]string{
{"pk", "show", "prompt", "briefing", "--campaign", "spring", "--fmt", "yaml"},
{"promptkit", "show", "prompt", "briefing", "--campaign", "spring", "--format", "yaml"},
} {
output.Reset()
outcome = app.Execute(context.Background(), args, commandkit.Streams{Out: &output, Err: io.Discard})
if outcome.Kind != commandkit.OutcomeSuccess || !strings.Contains(output.String(), "prompt_id: briefing") {
t.Fatalf("prompt inspection route %q outcome/report = (%#v, %q)", args, outcome, output.String())
}
}
output.Reset()
outcome = app.Execute(context.Background(), []string{"smoke", "text_inference", "--campaign", "spring", "--prompt", "briefing", "--profile", "spring"}, commandkit.Streams{Out: &output, Err: io.Discard})
if outcome.Kind != commandkit.OutcomeSuccess || !strings.Contains(output.String(), "kind: text_inference") || !strings.Contains(output.String(), "completed: true") || clientCalls != 1 {
t.Fatalf("smoke shortcut outcome/report/client calls = (%#v, %q, %d)", outcome, output.String(), clientCalls)
}
}
func composedReportValue(t *testing.T, data []byte, category string, fields ...string) *yaml.Node {
t.Helper()
var document yaml.Node