Document profile comparison workflow

This commit is contained in:
2026-08-02 06:10:15 +00:00
parent ccf6b66880
commit e0229d9c90
14 changed files with 322 additions and 35 deletions

View File

@@ -4,7 +4,9 @@ Weatherreporter is a Go CLI that turns normalized weather data into
human-facing Markdown reports.
It produces a Markdown report at an operator-owned destination and can upload
the completed output through Distributor.
the completed output through Distributor. It can also compare explicitly
selected Promptkit profiles against one shared prepared report and publish a
local comparison bundle.
## Quickstart
@@ -24,5 +26,6 @@ guide](docs/operations.md) for command and operating details.
- [CLI reference](docs/cli.md)
- [Configuration reference](docs/config.md)
- [Operations guide](docs/operations.md)
- [Comparison bundle contract](docs/integrations/comparison-bundle.md)
- [Development guide](docs/development.md)
- [Architecture policy](docs/policy/architecture.md)

View File

@@ -1,7 +1,8 @@
# Weatherreporter CLI
`weatherreporter` generates Markdown weather reports and runs report batches.
It has no command for inspecting prior runs or application-owned state.
`weatherreporter` generates Markdown weather reports, runs report batches, and
compares explicitly selected Promptkit profiles against one prepared report. It
has no command for inspecting prior runs or application-owned state.
## Shortest Useful Command
@@ -25,6 +26,7 @@ weatherreporter generate tomorrow [--config PATH] [--units VALUE] [--tz NAME] [-
weatherreporter generate hourly [--config PATH] [--units VALUE] [--tz NAME] [--out PATH] [--llm-debug-dir PATH] [--quiet]
weatherreporter run morning [--config PATH] [--units VALUE] [--tz NAME] [--out-dir PATH] [--llm-debug-dir PATH] [--quiet]
weatherreporter run evening [--config PATH] [--units VALUE] [--tz NAME] [--out-dir PATH] [--llm-debug-dir PATH] [--quiet]
weatherreporter compare REPORT --profile PROFILE --profile PROFILE [--config PATH] [--units VALUE] [--tz NAME] [--date YYYY-MM-DD] [--out-dir PATH] [--replace] [--llm-debug-dir PATH] [--quiet]
```
`weatherreporter --version` prints the version embedded in the executable.
@@ -38,10 +40,13 @@ builds report `development`.
| `generate tomorrow` | Uses the next local civil day and writes `tomorrow.md` by default. |
| `generate hourly` | Covers the next six hours in the effective report timezone and writes `hourly.md` by default. It does not accept `--date`, `--hours`, or `--duration`. |
| `run morning` and `run evening` | Run their defined report batches beneath the configured output directory, or the current directory when none is configured. `--out-dir` selects another directory. `--out` is not accepted. |
| `compare REPORT` | Accepts `daily`, `today`, `tomorrow`, or `hourly`. It requires at least two distinct, nonblank `--profile` values in their supplied order. Daily requires `--date`; Today accepts it optionally; Tomorrow and Hourly do not accept it. |
`generate` accepts the four report command names shown above. `run` accepts
only `morning` and `evening`. Batch membership and notification ordering are
described in the [operations guide](operations.md).
only `morning` and `evening`. `compare` always requires explicit profile
selection: `promptkit.profile` is not used as a comparison default. Batch
membership and notification ordering are described in the
[operations guide](operations.md).
## Output, Errors, And Quiet Mode
@@ -50,21 +55,25 @@ For `generate`, the report's default filename is placed beneath
PATH` selects one complete output file instead. A relative path is resolved
from the current directory; an absolute path is used as given. For a batch,
the configured directory has the same role and `--out-dir PATH` selects its
output directory instead. Successful summaries always report the resulting
absolute `outputPath` values. See the [configuration reference](config.md) for
the field's validation and path rules.
output directory instead. For `compare`, `--out-dir PATH` selects one exact
bundle directory; otherwise the report-derived comparison directory is placed
beneath the configured directory or current directory. `--replace` is required
to replace an existing nonempty recognized comparison bundle. See the
[configuration reference](config.md) for the field's validation and path rules
and the [comparison bundle contract](integrations/comparison-bundle.md) for the
bundle format.
Outputs are written atomically. A generation, rendering, write, or cancellation
failure before publication leaves an existing destination unchanged. A
notification failure occurs after publication, so the newly written output
remains available.
Action commands (`generate` and `run`) write a JSON summary to stdout unless
`--quiet` is set. `run` also writes compact per-report and batch status lines
to stderr. A pre-run error, such as an invalid flag, missing required argument,
or configuration-load failure, produces no partial JSON summary. When an
action fails after it has produced a result, its summary has `"status": "failed"`
and an `error` field.
Action commands (`generate`, `run`, and `compare`) write a JSON summary to
stdout unless `--quiet` is set. `run` also writes compact per-report and batch
status lines to stderr. A pre-run error, such as an invalid flag, missing
required argument, or configuration-load failure, produces no partial JSON
summary. When an action fails after it has produced a result, its summary has
`"status": "failed"` and an `error` field.
`--quiet` is supported by action commands only. It suppresses action summaries
and routine batch status output; it does not suppress command errors.
@@ -113,19 +122,41 @@ report=today status=succeeded output="/srv/weather/reports/today.md"
batch=morning total=2 succeeded=2 failed=0
```
### Compare Summary
A comparison summary contains these fields in this order: `command`,
`comparisonId`, `reportId`, `reportName`, `promptId`, `promptVersion`,
`promptHash`, `status`, `startedAt`, `finishedAt`, `timezone`, `validPeriod`,
`outputDirectory`, `manifestPath`, `dataPackagePath`, `total`, `succeeded`,
`failed`, `results`, and optional `error`. Published artifact paths and each
successful `results[].reportPath` are absolute. `results` preserves the
supplied profile order and each item contains `position`, `profileId`, optional
`backendId`, `modelName`, `status`, optional `validationStatus`, optional
`reportPath`, optional `llmDebugPath`, and optional safe `error`.
The comparison status is `succeeded` only when every selected profile succeeds
and the bundle is published. Individual profile failures still publish a
complete partial bundle and return a failed command result. Cancellation or a
failure before publication omits the artifact paths and returns a safe
top-level error; it does not expose provider diagnostics. See the
[comparison bundle contract](integrations/comparison-bundle.md) for durable
artifact fields and failure invariants.
## Flag Reference
| Flag | Accepted by | Meaning |
| --- | --- | --- |
| `-h`, `--help` | top level | Show help. |
| `--config PATH` | all commands | Load `PATH` instead of `/usr/local/etc/weatherreporter/config.yml`. |
| `--units VALUE` | `generate`, `run` | Override `weather_api.units` for this command. |
| `--tz NAME` | `generate`, `run` | Override `weather_api.timezone` for this command. |
| `--units VALUE` | `generate`, `run`, `compare` | Override `weather_api.units` for this command. |
| `--tz NAME` | `generate`, `run`, `compare` | Override `weather_api.timezone` for this command. |
| `--out PATH` | every `generate` command | Write the report to this complete file destination instead of the configured or current-directory default. |
| `--llm-debug-dir PATH` | every `generate` and `run` command | Write requested sensitive prompt diagnostics under this absolute path. |
| `--out-dir PATH` | `run morning`, `run evening` | Write batch reports beneath this directory instead of the configured or current-directory default. |
| `--quiet` | `generate`, `run` | Suppress action summaries and routine batch status output. |
| `--date YYYY-MM-DD` | `generate daily`, `generate today` | Required for Daily; optional for Today. |
| `--llm-debug-dir PATH` | every `generate`, `run`, and `compare` command | Write requested sensitive prompt diagnostics under this absolute path. |
| `--profile PROFILE` | `compare` | Select one explicit profile. Repeat at least twice with distinct, nonblank IDs. |
| `--out-dir PATH` | `run morning`, `run evening`, `compare` | Write batch reports beneath this directory, or select the exact comparison directory. |
| `--replace` | `compare` | Authorize replacement of a recognized nonempty comparison bundle. |
| `--quiet` | `generate`, `run`, `compare` | Suppress successful action output and routine batch status output. |
| `--date YYYY-MM-DD` | `generate daily`, `generate today`, `compare daily`, `compare today` | Required for Daily; optional for Today. |
Distributor notification is configured through `notify.distributor`; there are
no Distributor-specific CLI flags. See the [configuration reference](config.md).
@@ -138,4 +169,5 @@ weatherreporter generate today --out ./reports/today.md
weatherreporter generate hourly --out /srv/weather/hourly.md
weatherreporter generate today --llm-debug-dir /var/tmp/weatherreporter-debug
weatherreporter run morning --out-dir ./reports --llm-debug-dir /var/tmp/weatherreporter-debug
weatherreporter compare daily --date 2026-05-29 --profile weather-light --profile weather-balanced --out-dir ./comparison-daily-2026-05-29
```

View File

@@ -84,7 +84,7 @@ this directory, never in the YAML file.
### `output`
`output.directory` selects the ordinary operator-owned publication directory
for both individual reports and batches.
for individual reports, batches, and the default parent of comparison bundles.
| Field | Default | Rules |
| --- | --- | --- |
@@ -101,6 +101,8 @@ publication.
For one `generate` command, `--out` is a complete file destination and takes
precedence over `output.directory`. For `run`, `--out-dir` takes precedence.
For `compare`, `--out-dir` selects its exact bundle directory; without it, the
comparison's report-derived directory is placed beneath `output.directory`.
Those explicit flags do not inspect or rebase beneath the configured directory.
See the [CLI reference](cli.md) for command selection and the [operations
guide](operations.md) for publication and failure handling.
@@ -166,11 +168,11 @@ source keys are `observations`, `current`, `narrative`, `alerts`, `discussion`,
### `promptkit`
Promptkit configuration selects the executor and prompt/profile checks for
every `generate` and `run` command. A top-level `scriptorium:` configuration
every `generate`, `run`, and `compare` command. A top-level `scriptorium:` configuration
key is rejected with a migration error; it is not translated or ignored.
Prompt debug capture has no YAML setting. Use `--llm-debug-dir PATH` on an
individual `generate` or `run` command when explicitly needed.
individual `generate`, `run`, or `compare` command when explicitly needed.
| Field | Default | Rules |
| --- | --- | --- |

View File

@@ -21,14 +21,15 @@ boundaries and invariants.
| Adding, changing, reviewing, or deleting tests | [Testing policy](policy/testing.md) and focused package tests | The policy defines risk-based sufficiency, durable test boundaries, doubles, and test-maintenance criteria. |
| CLI commands, flags, output, quiet mode, or command wiring | [CLI reference](cli.md) and [CLI internals](internal/cli.md) | The reference owns the user contract; the internal guide owns command composition and output flow. |
| Configuration fields, defaults, loading, overrides, validation, or secrets | [Configuration reference](config.md), [architecture policy](policy/architecture.md), and tests under `internal/config` | These separate the user-visible contract, architectural rules, and executable behavior. |
| Top-level generation, batch, collection, output publication, or notification workflow | [App orchestration internals](internal/app-orchestration.md) | It owns workflow ordering, output publication, failure propagation, and orchestration invariants. |
| Top-level generation, batch, comparison, collection, output publication, or notification workflow | [App orchestration internals](internal/app-orchestration.md), [comparison execution internals](internal/comparison-execution.md), and [comparison publication internals](internal/comparison-publication.md) | They own workflow ordering, concurrent profile execution, output publication, failure propagation, and orchestration invariants. |
| Weather API transport, source envelopes, source warnings, or collection | [Weather API integration](integrations/weatherapi.md), [weather-data internals](internal/weather-data.md), and [collection internals](internal/collect.md) | These separate the external contract, normalized source facts, and app-facing collection behavior. |
| Forecast periods, weather derivation, collected facts, or derived facts | [Forecast derivation internals](internal/forecast-derivation.md) and [fact contracts](internal/facts.md) | They own deterministic derivation and the fact boundaries used by reports. |
| Report definitions, valid periods, report IDs, output naming, or batch composition | [Report registry internals](internal/report-registry.md) and [app orchestration internals](internal/app-orchestration.md) | Report definitions own selection and period rules; orchestration owns execution. |
| Module IDs, module composition, briefing values, or prompt-facing exports | [Module contract internals](internal/module.md), [module builder internals](internal/briefing.md), and [prompt-input internals](internal/prompt-input.md) | These own module contracts, value construction, and the curated prompt-package boundary. |
| Prompt execution, profiles, prompt inputs, or result handling | `internal/promptexec`, the Promptkit adapter, and [prompt-input internals](internal/prompt-input.md) | These separate the executor contract and input construction. |
| Prompt execution, profiles, prepared report inputs, or result handling | `internal/promptexec`, the Promptkit adapter, [prepared report internals](internal/prepared-report.md), and [prompt-input internals](internal/prompt-input.md) | These separate the executor contract, immutable preparation, and input construction. |
| Durable comparison bundles or their compatibility | [Comparison bundle contract](integrations/comparison-bundle.md) and [comparison publication internals](internal/comparison-publication.md) | The integration document owns the external schema; internals own how it is published. |
| Generated-text schemas, validation, render contexts, templates, or Markdown rendering | [Generated-text internals](internal/generatedtext.md), [report-template internals](internal/reporttemplate.md), and [report template guide](templates.md) | These own structured text, renderer implementation, and the maintainer-facing template surface. |
| Output destinations, atomic publication, prompt diagnosis, or legacy cleanup | [Operations guide](operations.md) and [App orchestration internals](internal/app-orchestration.md) | Operations owns operator workflows; app internals owns the implementation boundary. |
| Output destinations, atomic publication, prompt diagnosis, or legacy cleanup | [Operations guide](operations.md), [App orchestration internals](internal/app-orchestration.md), and [comparison publication internals](internal/comparison-publication.md) | Operations owns operator workflows; internals own implementation boundaries. |
| Distributor bundles, uploads, notification results, or failures | [Distributor adapter internals](internal/distributor-adapter.md), [Distributor integration contracts](integrations/distributor/), and [operations guide](operations.md) | These separate adapter behavior, external contracts, and operational lifecycle. |
| Maintained example configuration | [Configuration reference](config.md) and files under `examples/` | The reference owns field meaning; examples own complete copyable files. |
| Release preparation, tagging, publication, or verification | [Release procedure](release.md) | It owns version selection, release-note preparation, candidate validation, tag publication, CI behavior, and post-publication checks. |
@@ -44,7 +45,8 @@ present before introducing a new package or abstraction.
| --- | --- |
| `cmd/weatherreporter` | Binary entry point. |
| `internal/cli` | Command parsing, flags, help, output, and command wiring. |
| `internal/app` | Stateless generation, batches, collection coordination, output publication, and notification. |
| `internal/app` | Stateless generation, batches, comparisons, collection coordination, output publication, and notification. |
| `internal/comparison` | Comparison identities, logical bundles, guarded destinations, and atomic bundle publication. |
| `internal/config` | Configuration defaults, loading, precedence, secrets, and validation. |
| `internal/adapters` | Weather API, Promptkit, and Distributor boundaries. |
| `internal/weatherdata`, `internal/forecast`, `internal/facts` | Normalized source facts and deterministic derivation. |

View File

@@ -0,0 +1,92 @@
# Comparison Bundle Contract
A comparison bundle is the durable, flat artifact produced when one report is
executed with multiple explicit Promptkit profiles. This document is the
canonical contract for consumers of those bundles. Command invocation and JSON
action summaries belong to the [CLI reference](../cli.md); destination handling
and retention belong to the [operations guide](../operations.md).
## Version And Layout
The current and only supported manifest schema version is
`weatherreporter.comparison.v1`. A bundle directory contains exactly these
regular, non-symlinked files:
```text
comparison.json
data-package.yml
NN-profile-slug.md
```
`comparison.json` is the manifest and `data-package.yml` is the exact YAML
input supplied to every selected profile. There is one Markdown file for each
successful result and none for failed results. `NN` is the one-based selected
profile position, zero padded to at least two digits (and widened only when
needed for 100 or more profiles). The profile slug preserves ASCII letters,
digits, `-`, and `_`; each run of other characters becomes one `-`; edge `-`
and `_` characters are removed; the value is capped at 64 bytes; and an empty
slug becomes `profile`. Logical profile IDs remain authoritative in the
manifest.
All manifest paths are basenames relative to the bundle root. They never use
path separators, `.` or `..`. The CLI reports absolute paths only after a
bundle has been published.
## Manifest Schema
The manifest is UTF-8 JSON, encoded as two-space-indented JSON with one
trailing newline. Its fields appear in this order:
```text
schemaVersion, comparisonId, startedAt, finishedAt, reportId, validPeriod,
timezone, promptId, promptVersion, promptHash, dataPackage, total, succeeded,
failed, results
```
`validPeriod` contains `start` and `end`; it is a nonempty half-open period.
`dataPackage` contains `path` (always `data-package.yml`) and `sha256` (the
lowercase, 64-character SHA-256 digest of that file's exact bytes). `results`
is in the explicit profile-selection order. Its result-object fields appear in
this order:
```text
position, profileId, backendId, modelName, status, validationStatus,
reportPath, error
```
`startedAt` and `finishedAt` are nonzero UTC timestamps, and the latter is not
earlier than the former. `validPeriod` retains its resolved time offset.
`reportId`, `timezone`, prompt identity, model name, and comparison ID are
nonblank. `promptHash` and `dataPackage.sha256` are lowercase SHA-256 digests.
## Result Invariants
`total` is at least two and equals the number of results. Positions are
contiguous from one, profile IDs are distinct and nonblank, and
`succeeded + failed == total`.
A successful result has `status: "succeeded"`, `validationStatus: "passed"`,
a unique Markdown `reportPath`, and no `error`. A failed result has
`status: "failed"`, no `reportPath`, and an `error` object with nonblank
`category` and `message`. Its validation status is absent, `failed`, or
`skipped`. Error messages are valid UTF-8 and no longer than 1,024 bytes.
`backendId` and `validationStatus` are omitted when unavailable.
Every successful Markdown file is declared by exactly one successful result.
The directory contains no extra entries. Consumers can therefore verify the
data-package digest and the full manifest-to-file mapping without scanning a
larger workspace.
## Compatibility And Sensitivity
Weatherreporter recognizes a replaceable bundle only when it exactly satisfies
the current version, schema, file set, file types, relative-path rules, and
data-package digest. It rejects unknown manifest fields, multiple JSON values,
extra entries, symlinks, and future or otherwise unsupported versions. Treat a
bundle that fails recognition as an ordinary directory, not as a compatible
bundle.
The manifest contains safe operational provenance, but `data-package.yml` and
the generated Markdown can contain sensitive weather or location context. Do
not assume these artifacts are safe for public distribution. Handle retention,
access, and deletion according to the [operations guide](../operations.md).

View File

@@ -29,6 +29,22 @@ Profiles that require a direct API key are unsupported; a profile that reports `
Promptkit receives the YAML data package as an inline input and returns structured JSON that Weatherreporter validates before rendering its own Markdown template. Safe active provenance remains in memory. Content-rich diagnostics are opt-in through `--llm-debug-dir`; see [operations](../operations.md) for retention and permissions.
## Comparison Execution
For `compare`, Weatherreporter validates one exact prompt and every explicitly
selected profile before weather collection. It prepares one deterministic YAML
data package, retains immutable copies of the report inputs, and executes every
profile against the same exact data-package bytes. Each profile remains an
independent Promptkit execution: one provider or validation failure does not
stop its peers, while caller cancellation applies to every in-flight execution.
Weatherreporter starts selected profile executions concurrently and does not
add an application-level concurrency limit. Promptkit owns backend capacity and
any profile or backend concurrency policy. The durable comparison output and
its compatibility rules are defined by the
[comparison bundle contract](comparison-bundle.md); the user-facing command
contract is in the [CLI reference](../cli.md).
The generated-text schemas require `summary`, `forecast_discussion`, and `precipitation_timing`, and reject additional properties. Prompts return an empty string for `precipitation_timing` when the deterministic package contains no precipitation windows.
Prompt/profile configuration and the maintained local override example are owned by the [configuration reference](../config.md). Adapter construction and mapping are documented in the [Promptkit adapter internals](../internal/promptkit-adapter.md).

View File

@@ -1,6 +1,9 @@
# Application Orchestration Internals
`internal/app` owns stateless report generation, batch execution, atomic output publication, and notification coordination after `internal/cli` has parsed arguments and loaded configuration. The user contract is owned by the [CLI reference](../cli.md) and [operations guide](../operations.md).
`internal/app` owns stateless report generation, batch execution, comparison
orchestration, atomic output publication, and notification coordination after
`internal/cli` has parsed arguments and loaded configuration. The user contract
is owned by the [CLI reference](../cli.md) and [operations guide](../operations.md).
## Single-Report Flow
@@ -16,6 +19,23 @@ Failures return an active partial result with safe identity, profile, warning, v
Each item has an independent result. A failed item does not stop later items; successful items retain their published output paths. Per-report notification is suppressed during a batch. Batch notification runs only after every planned report has published successfully. It is skipped when any item failed. Batch result counters count report items only; a batch notification failure is represented by the top-level notification result and still produces a failed batch outcome.
## Comparisons
`CompareDetailed` validates ordered explicit profile IDs, resolves the report,
and preflights the exact bundle destination before initializing optional prompt
debugging, prompt inspection, or collection. It then inspects the one prompt
and every selected profile, collects once, and delegates shared report
construction to the prepared-report flow. It does not accept a notifier.
The comparison execution core starts each inspected profile independently,
keeps results in selection order, and waits for all started work. Independent
profile failures are recorded and do not stop peers. Context cancellation marks
unfinished work and prevents publication. Details of prepared values, execution
and debugging, and publication are documented in [prepared report
internals](prepared-report.md), [comparison execution
internals](comparison-execution.md), and [comparison publication
internals](comparison-publication.md).
## Boundaries And Verification
The package does not parse flags, load YAML, implement transport, construct provider SDKs, or define report-period policy. Prompt, profile, weather, and Distributor implementations remain behind project-owned contracts.

View File

@@ -0,0 +1,25 @@
# Comparison Execution Internals
The comparison execution core receives an already prepared report and an
already inspected, ordered profile list. It initializes an outcome for every
selected profile, launches each started profile in its own goroutine, and
waits for every started goroutine before returning. Results retain the supplied
selection order even though execution completes in an arbitrary order.
Every profile uses the exact inspected prompt identity and a private copy of
the same prepared data package. Provider, generated-text validation, rendering,
or debug-write failure becomes that profile's safe failed outcome and does not
cancel its peers. The application deliberately imposes no additional semaphore:
Promptkit owns backend capacity. Cancellation or a deadline marks unfinished
outcomes as skipped or failed, joins work, and prevents bundle publication.
When debugging is enabled, each execution receives a deterministic reference
derived from the comparison identity, ordered profile position, and safe
profile slug. This keeps concurrent captures separate. The debug writer itself
owns secure-root validation and file permissions; operational retention and
sensitivity are documented in the [operations guide](../operations.md).
The output result and its safe errors are converted into the durable contract
only by comparison publication. See [comparison publication
internals](comparison-publication.md) and the external [comparison bundle
contract](../integrations/comparison-bundle.md).

View File

@@ -0,0 +1,22 @@
# Comparison Publication Internals
`internal/comparison` separates the logical bundle from filesystem mechanics.
The application builds a validated manifest, exact shared data-package bytes,
and only the Markdown files for successful profiles. The durable layout,
schema, and compatibility rules are owned by the [comparison bundle
contract](../integrations/comparison-bundle.md).
Destination planning is read-only. It requires an exact absolute target that
is neither the filesystem root nor the working directory, rejects unsafe
symlinks and non-directories, accepts a missing or empty directory, and permits
replacement only for a recognized current bundle. Publication rechecks that
plan, writes a private sibling staging directory, and installs it atomically.
Replacement temporarily moves the recognized prior bundle aside and restores it
if installation fails.
The application preflights before prompt inspection and collection, then
preflights again before publication. A cancellation or any failure before the
commit leaves the prior destination untouched. Completed bundles include
partial profile results; comparison publication never coordinates Distributor
notification. Operator-facing lifecycle and cleanup are in the
[operations guide](../operations.md).

View File

@@ -0,0 +1,20 @@
# Prepared Report Internals
`internal/app` builds a `preparedReport` after collection and before profile
execution. This is the immutable boundary shared by ordinary report generation
and profile comparison; it is not a durable artifact.
Preparation builds report facts, the configured module snapshot, briefing
metadata, the curated prompt-input package, serialized YAML, and the
generated-text definition. It deep-copies mutable facts, snapshots, metadata,
and data-package bytes before returning them. Consumers receive independent
copies so one execution cannot change another's input or rendering context.
Single-report generation executes one prepared profile and publishes its
Markdown. Comparison prepares once, gives every selected profile the same YAML
bytes, and only then assembles the resulting logical bundle. The prompt-input
shape is owned by [prompt-input internals](prompt-input.md); profile execution
semantics are owned by [Promptkit integration](../integrations/promptkit.md).
Preparation failure has no publication side effects. Tests for this boundary
cover mutation isolation, byte equality, and reuse by both execution paths.

View File

@@ -72,6 +72,38 @@ For a single report, Distributor notification follows the atomic output write.
See the [configuration reference](config.md) for pipeline, bundle,
idempotency-key, and per-report path templates.
## Comparison Bundles
Use `compare` when an operator needs to evaluate explicit Promptkit profiles
against the same report input. The command writes one flat, operator-owned
bundle directory and never sends a Distributor notification. Command syntax,
profile validation, JSON output, and exit behavior belong to the
[CLI reference](cli.md); the durable file contract belongs to the
[comparison bundle contract](integrations/comparison-bundle.md).
The output destination follows the normal `output.directory` fallback. An
explicit `--out-dir` takes precedence and names the exact bundle directory,
not a parent to be combined with another name. The standard names are derived
from the report output name, such as `comparison-today` and
`comparison-daily-2026-05-29`; see the [configuration reference](config.md)
for output-directory resolution.
A comparison bundle contains the shared data package, a manifest, and one
Markdown file for every successful profile. Treat all of these files as
potentially sensitive: the data package and generated reports can contain
location or forecast context. Weatherreporter creates no application-owned
history, retention store, or cleanup job. Retain, archive, or remove only the
specific bundle directories your operating policy permits.
The destination is preflighted before prompt inspection and collection, then
rechecked immediately before an atomic publish. A missing or empty directory
is usable. A nonempty directory can be replaced only when `--replace` is given
and it is recognized as a current Weatherreporter comparison bundle; ordinary
directories, symlinks, and unsafe destinations are rejected. Cancellation and
all failures before publication preserve an existing bundle. Profile failures
are different: the command publishes a complete partial bundle, with failed
profiles represented in the manifest and no Markdown file for those profiles.
## Local Prompt Profile Override
Hourly normally selects the embedded `weather-light` profile. To use a local
@@ -99,9 +131,11 @@ weatherreporter generate today --llm-debug-dir /var/tmp/weatherreporter-debug
The directory must be absolute. Requested captures are written with restrictive
permissions beneath the supplied directory, organized by report and run. They
can contain rendered prompts and generated output, so limit access to trusted
operators and remove the captures when they are no longer needed. Normal output,
summaries, and routine logs omit that sensitive content. Debug capture is never
created for an ordinary command without `--llm-debug-dir`.
operators and remove the captures when they are no longer needed. Comparison
captures additionally identify each selected profile so concurrent executions
remain distinct. Normal output, summaries, and routine logs omit that sensitive
content. Debug capture is never created for an ordinary command without
`--llm-debug-dir`.
If capture creation or writing fails, the affected run fails rather than
silently continuing without the requested diagnostics.
@@ -111,8 +145,12 @@ silently continuing without the requested diagnostics.
Start with the command error and JSON summary. For a report generation failure,
the selected destination was not replaced; for a notification failure, inspect
the completed destination and the notification result. For a batch failure,
use the per-report statuses and retain successful output files. Enable explicit
debug capture only when content-rich Promptkit diagnostics are necessary.
use the per-report statuses and retain successful output files. For a comparison
failure, inspect the published manifest when its path is present: individual
profile failures retain their safe result and successful Markdown files, while
cancellation and pre-publication errors leave the prior destination unchanged.
Enable explicit debug capture only when content-rich Promptkit diagnostics are
necessary.
Weatherreporter does not retain runs for later inspection, resume failed work,
or provide automatic cleanup, archival, remote state, daemon operation, or

View File

@@ -15,6 +15,11 @@ and renders repository-owned Markdown in memory. Completed Markdown is
atomically published to an operator-owned output destination and may then be
uploaded through Distributor.
An explicit profile comparison prepares one report input once, executes the
same exact prompt and data package across selected profiles concurrently, and
atomically publishes one operator-owned comparison bundle. It remains local:
it does not create application state or send a Distributor notification.
The supported report products are Daily, Today, Tomorrow, and Hourly. A batch
collects once, validates its complete candidate prompt/profile set before
collection, then determines and validates every planned output destination
@@ -29,6 +34,8 @@ planned report succeeds.
- `internal/config` owns defaults, loading, validation, and secret loading.
- `internal/app` owns in-memory workflow order, partial results, atomic output
publication, and notification coordination through project-owned contracts.
- `internal/comparison` owns comparison identity, durable logical bundle
validation, safe destination recognition, and atomic bundle publication.
- Deterministic domain packages own weather derivation, report periods, modules,
generated-text validation, and template contexts.
- `internal/adapters/weatherapi`, `internal/adapters/promptkit`, and
@@ -47,6 +54,9 @@ directly.
before collection.
- Prompt and profile validation completes before weather collection. Raw output
is validated before template rendering.
- Comparison validates every explicit profile before collection, prepares one
immutable report input, and delegates backend capacity to Promptkit rather
than adding an application-wide execution limit.
- Generated text fills defined prose slots only. Deterministic facts remain
authoritative and repository-owned templates produce all Markdown output.
- Sensitive rendered prompts, schemas, input bodies, provider endpoints, and
@@ -64,11 +74,16 @@ directly.
does not remove a newly published output.
- Configuration or explicit CLI input selects that operator-owned destination;
it does not create an application-owned state boundary.
- Comparison bundles are flat, versioned operator outputs. Their guarded
replacement accepts only a recognized current bundle; cancellation and every
pre-publication failure preserve a prior bundle, while individual profile
failures can publish a complete partial bundle.
- Distributor uploads use only the published Markdown output, never a scan of
local files. Single notification follows publication; batch notification
follows publication of every selected report. Batch counters describe report
outcomes only; a failed batch notification is represented separately at the
batch level.
- Comparison never invokes Distributor notification.
- Default tests are deterministic, offline, and use Promptkit/provider fakes
rather than live provider calls. See the [testing policy](testing.md).

View File

@@ -1,6 +1,6 @@
# LLM Profile Comparison Implementation Plan
Status: Ready for implementation.
Status: Complete.
## Purpose And Authority

View File

@@ -1,6 +1,6 @@
# LLM Profile Comparison Roadmap
Status: Accepted; unimplemented.
Status: Implemented; retained temporarily for post-implementation review.
## Purpose