From 117c5336ba1b4172072cc61e6cba71610d18e536 Mon Sep 17 00:00:00 2001 From: Eric Rakestraw Date: Sat, 1 Aug 2026 14:27:39 +0000 Subject: [PATCH] Finalize domain prompt profile roadmap --- docs/roadmap/domain-profiles.md | 40 +++++++-------- docs/roadmap/implementation.md | 15 ++---- internal/adapters/promptkit/adapter_test.go | 55 +++++++++++++++++++++ 3 files changed, 80 insertions(+), 30 deletions(-) diff --git a/docs/roadmap/domain-profiles.md b/docs/roadmap/domain-profiles.md index 1c35ff1..103dd95 100644 --- a/docs/roadmap/domain-profiles.md +++ b/docs/roadmap/domain-profiles.md @@ -1,6 +1,6 @@ # Domain-Specific Prompt Profiles Roadmap -Status: Accepted; implementation plan ready. +Status: Implemented. ## Purpose @@ -10,9 +10,9 @@ These logical profiles should give each report an appropriate default while allowing operators to replace any definition through the existing configured profile source. -This roadmap defines the scope, policy, and intended end state. The companion -[implementation plan](domain-profiles-implementation.md) owns the ordered work -needed to reach that state. +This roadmap records the scope, policy, and implemented end state. The +companion [implementation plan](implementation.md) records the ordered work +and verification used to reach it. ## User Intent @@ -29,22 +29,22 @@ The feature is intended to provide three related benefits: guarantee. A locally hosted lightweight model may still generate slowly on the available hardware. -## Current State +## Pre-Implementation Baseline -Daily, Today, Tomorrow, and Hourly each declare Promptkit's -`gemini-flash-latest` profile as their prompt default. The optional -`promptkit.profile` setting overrides that default for every selected report in +Before implementation, Daily, Today, Tomorrow, and Hourly each declared +Promptkit's `gemini-flash-latest` profile as their prompt default. The optional +`promptkit.profile` setting overrode that default for every selected report in an invocation. -Weatherreporter accepts either `promptkit.profile_file` or -`promptkit.profile_dir` and passes that source to Promptkit. A matching external -profile can override a Promptkit built-in profile, and the configured local -backend can support profiles that select `backend: local`. Endpoint-only -OpenAI-compatible profiles can also provide their own endpoint. +Weatherreporter accepted either `promptkit.profile_file` or +`promptkit.profile_dir` and passed that source to Promptkit. A matching external +profile could override a Promptkit built-in profile, and the configured local +backend could support profiles that select `backend: local`. Endpoint-only +OpenAI-compatible profiles could also provide their own endpoint. -Weatherreporter does not currently own or embed execution profiles. Promptkit -v0.5.0 now provides the fallback-profile layer needed to add them without -changing the existing operator-source precedence. +Weatherreporter did not own or embed execution profiles. Promptkit v0.5.0 +provided the fallback-profile layer used to add them without changing the +existing operator-source precedence. ## Prerequisite @@ -56,7 +56,7 @@ passed the repository test suite and an operator smoke test. Weatherreporter must continue to use only Promptkit's public API rather than depending on its internal packages or reproducing its profile repository behavior. -## Desired End State +## Implemented End State Weatherreporter embeds usable definitions for these exact logical profile IDs: @@ -204,7 +204,7 @@ The purpose is to choose an appropriate default for each tier, not to add a permanent benchmark framework or live-provider requirement to the ordinary test suite. Repository tests remain offline and deterministic. -## Scope +## Implemented Scope The completed feature includes: @@ -260,9 +260,9 @@ An external same-ID override is an operator-owned compatibility commitment. Weatherreporter may evolve its embedded definitions, but it must not rewrite or silently merge an operator file. -## Completion Criteria +## Completion Record -The roadmap is complete when: +The following conditions are satisfied: - a tagged Promptkit dependency supports the required fallback layer; - every operational prompt selects its assigned logical profile at exact diff --git a/docs/roadmap/implementation.md b/docs/roadmap/implementation.md index 1380085..2bd37c3 100644 --- a/docs/roadmap/implementation.md +++ b/docs/roadmap/implementation.md @@ -1,19 +1,14 @@ # Domain-Specific Prompt Profiles Implementation Plan -Status: Ready for implementation. +Status: Completed. ## Purpose And Authority -This document gives a coding agent the ordered work needed to implement the +This document records the completed implementation of the [domain-specific prompt profiles roadmap](domain-profiles.md). The roadmap is -authoritative for scope, user intent, policy choices, and the desired end -state. This plan is authoritative for implementation sequence, verification, -and stage exit gates. - -The target implementer is a `gpt-5.6-terra` coding agent using high reasoning. -Complete the stages in order. Each stage is scoped for one implementation -prompt and must leave the repository coherent, tested, and reviewable before -the next stage begins. +authoritative for scope, user intent, policy choices, and the implemented end +state. This plan records the implementation sequence, verification, and exit +gates used to reach it. This plan follows the repository's [architecture](../policy/architecture.md), diff --git a/internal/adapters/promptkit/adapter_test.go b/internal/adapters/promptkit/adapter_test.go index d2ad7fe..bd9535c 100644 --- a/internal/adapters/promptkit/adapter_test.go +++ b/internal/adapters/promptkit/adapter_test.go @@ -12,7 +12,10 @@ import ( "time" promptkit "gitea.maximumdirect.net/eric/promptkit" + "gitea.maximumdirect.net/eric/weatherreporter/internal/app" + appconfig "gitea.maximumdirect.net/eric/weatherreporter/internal/config" "gitea.maximumdirect.net/eric/weatherreporter/internal/promptexec" + "gitea.maximumdirect.net/eric/weatherreporter/internal/report" ) type fakeClient struct { @@ -158,6 +161,44 @@ func TestMaintainedWeatherLightLocalProfileExampleInspectsOffline(t *testing.T) assertProfile(t, adapter, "weather-light", "", "weather-local") } +func TestApplicationPreflightResolvesEmbeddedAndOverriddenProfilesOffline(t *testing.T) { + lookupEnv := func(string) (string, bool) { return "test-key", true } + inspect := func(t *testing.T, adapter *Adapter, id report.ID, profile string, wantID string, wantBackend string, wantModel string) { + t.Helper() + result, err := app.InspectPromptExecution(context.Background(), app.PromptInspectionRequest{ + Resolved: resolvedPromptProfile(t, id), + Executor: adapter, + Promptkit: appconfig.PromptkitConfig{Profile: profile}, + LookupEnv: lookupEnv, + }) + if err != nil { + t.Fatalf("InspectPromptExecution() error = %v", err) + } + if result.ProfileID != wantID || result.BackendID != wantBackend || result.ModelName != wantModel { + t.Fatalf("inspection = %#v, want profile/backend/model %q/%q/%q", result, wantID, wantBackend, wantModel) + } + } + + embedded, err := newAdapterForTest(Config{}, &fakeClient{}) + if err != nil { + t.Fatalf("newAdapterForTest(embedded) error = %v", err) + } + inspect(t, embedded, report.Hourly, "", "weather-light", "openrouter", "deepseek/deepseek-v4-flash") + inspect(t, embedded, report.Daily, "", "weather-balanced", "openrouter", "~google/gemini-flash-latest") + inspect(t, embedded, report.Today, "", "weather-balanced", "openrouter", "~google/gemini-flash-latest") + inspect(t, embedded, report.Tomorrow, "", "weather-balanced", "openrouter", "~google/gemini-flash-latest") + inspect(t, embedded, report.Daily, "weather-deep", "weather-deep", "openrouter", "~anthropic/claude-sonnet-latest") + + override, err := newAdapterForTest(Config{ProfileFile: writeProfileFile(t, `id: weather-light +endpoint: https://local.example/v1 +model: local-weather +`)}, &fakeClient{}) + if err != nil { + t.Fatalf("newAdapterForTest(override) error = %v", err) + } + inspect(t, override, report.Hourly, "", "weather-light", "", "local-weather") +} + func TestProfileResolutionFallsThroughOnlyWhenTheConfiguredIDIsAbsent(t *testing.T) { absentAdapter, err := New(Config{ProfileDirectory: testProfileDirectory(t, `id: other-profile backend: openrouter @@ -487,6 +528,20 @@ func writeProfileFile(t *testing.T, profile string) string { return path } +func resolvedPromptProfile(t *testing.T, id report.ID) report.Resolved { + t.Helper() + now := time.Date(2026, 5, 29, 12, 0, 0, 0, time.UTC) + request := report.ResolveRequest{Now: now, Location: time.UTC} + if id == report.Daily { + request.Date = now + } + resolved, err := report.DefaultRegistry().Resolve(id, request) + if err != nil { + t.Fatalf("Resolve(%q) error = %v", id, err) + } + return resolved +} + func testExecuteRequest() promptexec.ExecuteRequest { return promptexec.ExecuteRequest{ PromptID: "weather.daily_generated_text",