Remove completed roadmap cleanup plans

This commit is contained in:
2026-06-15 12:52:18 +00:00
parent cd8d77b37c
commit 67b30dbad6
7 changed files with 25 additions and 1975 deletions

View File

@@ -1,612 +0,0 @@
# Code Quality And Deduplication Audit
## Executive Summary
The codebase is in good pre-release shape. The major architecture is coherent: configuration loading is centralized, external systems are behind adapters, reports and modules have explicit registries, state paths are mostly centralized, and the application orchestration is readable. I do not see a major architectural risk that requires a broad rewrite before the next release.
The top three cleanup targets are:
1. Generated-text/template dispatch is spread across `internal/report`, `internal/app`, `internal/generatedtext`, and `internal/reporttemplate`.
2. Final report completion logic is duplicated between the direct Markdown generation path and the generated-text-template path.
3. Report name, command name, config key, and alias resolution is implemented in multiple packages.
The codebase is ready for a limited cleanup pass. The highest-value work should be small, behavior-preserving centralization around catalogs, finalization helpers, and validation boundaries. Avoid a workflow engine, plugin system, or broad CLI redesign.
## Repository Map Reviewed
Reviewed source areas:
- `cmd/weatherreporter`: executable entry point.
- `internal/cli`: standard-library CLI parsing, command dispatch, inspect command setup, output handling.
- `internal/app`: end-to-end orchestration for fetch, facts, module snapshots, prompt input, generated text, report rendering, state, output copies, and notifications.
- `internal/config`: defaults, YAML loading, CLI overrides, report module overrides, secrets directory, distributor notification templates, validation.
- `internal/report`: report IDs, definitions, registry, valid-period resolution, batch definitions.
- `internal/module` and `internal/briefing`: module IDs, module registry, module builders, missing-data behavior, prompt-facing module output.
- `internal/facts`, `internal/forecast`, and `internal/weatherdata`: collected and derived fact contracts, forecast derivation, normalized upstream data.
- `internal/adapters/weatherapi`: HTTP source fan-out, source metadata, missing-source policy handling.
- `internal/adapters/scriptorium`: subprocess render/run adapter.
- `internal/adapters/distributor`: distributor upload/status adapter.
- `internal/state`: filesystem state store, artifact paths, metadata, snapshots, notification artifacts.
- `internal/generatedtext` and `internal/reporttemplate`: generated-text schemas, validation, render contexts, embedded templates.
- `internal/promptinput`, `internal/changes`, `internal/fileutil`, and `internal/timeutil`.
- `examples/config.yml`.
- Package-level tests across the inspected packages.
Reviewed documentation and intended interfaces:
- `README.md`.
- `docs/policy/architecture.md`.
- `docs/policy/development.md`.
- `docs/policy/documentation.md`.
- `docs/config.md`.
- `docs/cli.md`.
- `docs/operations.md`.
- Relevant `docs/internal/` and `docs/integrations/` documents.
- Existing roadmap files under `docs/roadmap/` for intended future interface direction.
Important areas not inspected deeply:
- Generated local `workspace/` artifacts were not audited as source code. They are useful examples of output shape, but they are not authoritative implementation.
- No nonexistent package areas such as `internal/stage`, `internal/storage`, `internal/artifacts`, `internal/manifest`, or `pkg` were reviewed because this repository does not currently define them.
Commands used for lightweight validation and repository mapping:
```sh
go list ./...
rg -n "func validateGeneratedText|func buildRenderContext|var templates|var schemas|reportKind|reportIDForCommand|reportIDForConfigKey|normalizeReportModules|ReportModuleOverrides|func \(b \*bundleBuilder\) fetch" internal cmd docs examples
```
No full test suite was run because this is a report-only task.
## High-Confidence Deduplication Opportunities
### Generated-Text And Template Dispatch Are Scattered
Affected files/packages:
- `internal/report`
- `internal/app/app.go`
- `internal/generatedtext`
- `internal/reporttemplate/reporttemplate.go`
Duplicated or near-duplicated behavior:
- Report definitions carry `GenerationMode`, `PromptID`, `GeneratedTextSchemaID`, and `TemplateID`.
- `internal/reporttemplate` has separate `templates` and `schemas` maps keyed by string IDs.
- `internal/app` switches on `GeneratedTextSchemaID` in `validateGeneratedText`.
- `internal/app` switches on `TemplateID` in `buildRenderContext`.
- `internal/generatedtext` has report-specific render-context builders that must stay aligned with the schema/template IDs.
Why it matters:
- Adding a new generated-text report requires edits in several packages. A missed registration can produce runtime failures that are hard to catch until a report is generated.
- Template ID, schema ID, generated-text type, validator, and render-context builder are a single policy decision but currently have several partial catalogs.
Recommended refactor:
- Add a narrow generated-text report catalog, likely in `internal/generatedtext` or `internal/reporttemplate`.
- The catalog should map a schema/template ID to:
- validator function;
- render-context builder;
- embedded template ID/path;
- embedded schema ID/path.
- Keep `internal/report.Definition` authoritative for which generated-text assets a report uses.
- Have `internal/app` resolve the generated-text handler once instead of owning report-specific switches.
- Keep the catalog explicit. Do not introduce reflection-heavy registration or a plugin system.
Suggested tests:
- Add a completeness test proving every report with generated-text-template mode has a registered validator, schema, template, and render-context builder.
- Keep existing hourly and tomorrow generated-text workflow tests.
- Add a negative test for an unsupported generated-text schema/template ID with a clear error.
Risk level: Medium. The refactor touches central generation flow, but it can be done with narrow adapters and existing tests.
### Final Report Completion Logic Is Duplicated Across Generation Modes
Affected files/packages:
- `internal/app/app.go`
- `internal/state`
- `internal/adapters/distributor`
Duplicated or near-duplicated behavior:
- Direct Markdown generation and generated-text-template generation both need to:
- produce a managed report artifact;
- update metadata;
- save final metadata;
- optionally copy to `--out` or `--out-dir`;
- optionally notify distributor;
- propagate notification failures as report failures;
- return the same result fields.
- The generated-text path has additional generated-text and render-context artifacts, but the finalization policy is the same after a managed Markdown path exists.
Why it matters:
- Output copy, metadata save, notification artifact, and result behavior are user-facing. If finalization changes, the fix must be made in more than one branch.
- This creates drift risk as additional template-based reports are added.
Recommended refactor:
- Add a small unexported app helper such as `finalizeRenderedReport`.
- Inputs should include the resolved report, run ID, metadata, managed report path, optional output request, notification config, and current result fields.
- The helper should not own the full workflow. It should only perform the common finalization after a report file exists.
- Preserve existing artifact paths and CLI behavior.
Suggested tests:
- Existing single-report tests for managed report path, `--out`, and `--out-dir`.
- Generated-text report tests proving metadata and notification debug artifacts are saved.
- Batch tests proving one report finalization failure does not stop independent reports, while aggregate status is nonzero.
Risk level: Medium. The behavior is central, but the target helper is small and can be tested around existing workflows.
### Report Name, Command Name, Config Key, And Alias Resolution Are Split
Affected files/packages:
- `internal/cli/root.go`
- `internal/app/app.go`
- `internal/config/reports.go`
- `internal/report`
Duplicated or near-duplicated behavior:
- CLI command names are parsed in `internal/cli`.
- App request kinds are mapped to report IDs in `internal/app`.
- Config report keys and aliases are mapped to report IDs in `internal/config`.
- Report definitions, artifact groups, batch output names, prompt IDs, and generation modes live in `internal/report`.
Why it matters:
- Public names and internal IDs are release-sensitive. Drift could make a report available through the CLI but unavailable through config, or vice versa.
- The clean break from older report IDs makes this more important because names also affect distributor paths and public URLs.
Recommended refactor:
- Keep `internal/report` as the canonical home for report identity policy.
- Add explicit report-owned helpers for canonical command/config/batch name resolution, for example:
- `ReportIDForCommandName(name string)`;
- `ReportIDForConfigKey(key string)`;
- `BatchReportsForCommandName(name string)`.
- Keep compatibility aliases only where the project intentionally supports them.
- Let `internal/cli` remain responsible for parsing flags and producing app requests, but avoid duplicating report-name policy there.
Suggested tests:
- Table tests for all supported CLI generate report names.
- Table tests for config aliases and unknown keys.
- Batch command tests for scheduled/default batch membership.
- A registry consistency test proving each CLI-exposed report maps to a registered report definition.
Risk level: Low to medium. The behavior is simple but public-facing.
### Report Module Override Normalization Runs In More Than One Place
Affected files/packages:
- `internal/config/load.go`
- `internal/config/validate.go`
- `internal/config/reports.go`
Duplicated or near-duplicated behavior:
- `Load` calls `normalizeReportModules`.
- `Validate` also calls `normalizeReportModules`.
- `ReportModuleOverrides` assumes usable report keys but silently skips unknown keys if called on a manually constructed or unvalidated `Config`.
Why it matters:
- Normalization currently appears idempotent, but it mutates config and decodes module options. Running it from both load and validation increases the chance of side effects or inconsistent future behavior.
- Silent skipping in `ReportModuleOverrides` is safe on the normal `Load` path but less safe for tests or programmatic callers that construct `Config` directly.
Recommended refactor:
- Split mutation from validation:
- `Load` should apply defaults, file contents, CLI overrides, secret loading, and normalizing mutations once.
- `Validate` should validate normalized config or call a non-mutating report-module validation helper.
- Consider changing `ReportModuleOverrides` to return `(map[report.ID][]module.ConfigItem, error)` or making it unexported behind a validated config path.
- If retaining the current method signature, add a comment documenting that it expects a validated config and add tests around invalid manual config behavior.
Suggested tests:
- Config tests proving module option normalization happens once and produces the same typed options.
- Tests for duplicate report aliases and unknown report keys.
- App-level test with manually constructed invalid report module overrides if the app continues to accept raw `Config`.
Risk level: Low. This is a contained config cleanup.
### Weather API Source Fetching Repeats The Same Lifecycle
Affected files/packages:
- `internal/adapters/weatherapi/client.go`
- `internal/weatherdata`
- `internal/forecast`
Duplicated or near-duplicated behavior:
- Each source fetch method repeats endpoint construction, query option handling, `data:null` or malformed handling, decoding, source metadata construction, and bundle assignment.
- The repeated flow is visible in observation, current, hourly, narrative, alerts, discussion, weather story, and SPC convective outlook fetch functions.
Why it matters:
- Source behavior has important differences: hourly is required, alerts treat `data:null` as checked/no active alerts, weather story has a minimal query, and SPC uses a source-specific endpoint. Those differences are valid, but the common lifecycle is still easy to drift.
- New sources are likely. Repeating source metadata, warning, and malformed-data handling for every source increases maintenance risk.
Recommended refactor:
- Introduce a small source fetch helper or source spec for the common lifecycle.
- Keep source-specific decode and special cases explicit.
- Do not build a generic ingestion framework or reflection-based decoder.
- The helper should centralize:
- source key;
- endpoint;
- query options;
- required/optional behavior;
- null-data policy;
- metadata timestamp/hash extraction;
- warning/error conversion.
Suggested tests:
- Existing fixture-server tests for source count, warnings, and endpoint paths.
- Dedicated tests for required hourly `data:null`, alerts `data:null`, optional source missing behavior, weather story metadata, and SPC overlap behavior.
- A new test proving source metadata shape remains consistent across helper-driven sources.
Risk level: Medium. The adapter is well covered, but source semantics are not all identical.
## Medium-Confidence Opportunities
### Template Module Extraction Has Repeated Optional-Stanza Plumbing
Affected files/packages:
- `internal/generatedtext/render_context.go`
Duplicated or near-duplicated behavior:
- Hourly and tomorrow render contexts both extract many of the same module stanzas from the same `module.Snapshot`.
- The repeated `optionalStanza` calls carry the same module IDs and error pattern.
Why it matters:
- This is not a major behavior risk today, but additional generated-text-template reports will repeat the same extraction pattern.
- It also makes template context changes noisier than necessary.
Recommended refactor:
- Add a small unexported snapshot lookup/cache type in `internal/generatedtext`.
- It can provide typed methods such as `CurrentConditions()`, `HourlyForecast()`, and `PrecipTiming()`.
- Keep report-specific context structs. Do not flatten all template contexts into one generic map.
Suggested tests:
- Existing render-context tests.
- A test that omitted optional modules become nil pointers and extraction errors include the module ID.
Risk level: Low.
### Generated-Text Validators Share Strict JSON Mechanics
Affected files/packages:
- `internal/generatedtext/hourly.go`
- `internal/generatedtext/tomorrow.go`
Duplicated or near-duplicated behavior:
- Report-specific validators perform strict JSON decoding, unknown-field rejection, string trimming, required-field validation, and canonical JSON output.
Why it matters:
- The report-specific validation rules should remain separate, but strict decoding mechanics should be consistent across all generated-text schemas.
- A new generated-text report will likely copy the same parsing pattern.
Recommended refactor:
- Add a small internal helper for strict single-object JSON decode and canonical re-marshal.
- Keep report-specific structs and semantic validation in each report file.
Suggested tests:
- Existing generated-text tests for unknown fields, missing required fields, and canonical output.
- Add one shared-helper test only if the helper has nontrivial behavior.
Risk level: Low.
### Distributor Notification Template Values Are Split Between Config And App
Affected files/packages:
- `internal/config/notify_templates.go`
- `internal/app/app.go`
Duplicated or near-duplicated behavior:
- Config owns template validation and rendering helpers.
- App constructs the concrete template value map, including location ID, report ID, run ID, artifact group, batch output name, bundle ID, and valid-period variables.
Why it matters:
- This split is mostly appropriate because app has report/run context. However, the list of allowed variables and the list of populated variables must stay aligned.
- Future notifier integrations could duplicate the same valid-period value construction.
Recommended refactor:
- Keep rendering/validation in `internal/config`.
- Move value construction into a named app helper with focused tests, or introduce a small config-owned `DistributorTemplateValues` constructor if it can avoid importing app concepts.
- Do not expose distributor package types outside the adapter.
Suggested tests:
- Existing config template validation tests.
- App tests for rendered pipeline ID, bundle ID, idempotency key, and bundle paths for hourly/tomorrow/daily periods.
Risk level: Low.
### Test Setup Is Repeated Across App, CLI, And Adapter Tests
Affected files/packages:
- `internal/app/*_test.go`
- `internal/cli/*_test.go`
- `internal/adapters/weatherapi/*_test.go`
- `internal/adapters/distributor/*_test.go`
Duplicated or near-duplicated behavior:
- Tests repeatedly create temporary configs, fake Scriptorium behavior, fake Weather API servers, generated workspace assertions, and notification expectations.
Why it matters:
- Future cleanup around report generation will be safer with focused helpers.
- The current duplication is not severe enough to justify a global test framework.
Recommended refactor:
- Add package-local helpers where duplication is already present:
- app test config builder;
- app fake renderer/notifier setup;
- CLI command invocation helper;
- Weather API fixture server helper.
- Avoid cross-package test utility packages unless duplication becomes materially worse.
Suggested tests:
- No new behavior tests are needed solely for helper extraction.
- Run affected package tests after mechanical helper cleanup.
Risk level: Low.
## Boundary And Responsibility Concerns
The primary package boundaries are sound:
- `internal/config` owns configuration, defaults, YAML decoding, secret loading, and validation.
- `internal/report` owns report identity and valid-period policy.
- `internal/briefing` owns module output construction.
- `internal/facts` owns collected-to-derived fact preparation.
- `internal/app` owns orchestration.
- `internal/state` owns workspace artifact paths and persistence.
- `internal/adapters/*` own external system details.
Areas with boundary ambiguity:
- Generated-text report registration is currently shared across report definitions, app switches, generated-text validators, and reporttemplate maps. A generated-text catalog would make this responsibility clearer without changing the package architecture.
- Report name resolution partly belongs to `internal/report` but is currently repeated in CLI, app, and config. The report package is the better canonical home because names, aliases, IDs, artifact groups, and batch membership are report identity policy.
- App owns distributor template value construction while config owns template validation. This is acceptable, but the value set should be explicitly named and tested because it is part of the notifier contract.
No serious external-system leakage was found:
- Distributor package types are confined to `internal/adapters/distributor`.
- Scriptorium subprocess execution is confined to `internal/adapters/scriptorium`.
- Weather API HTTP details are confined to `internal/adapters/weatherapi`.
## Path, Key, And Naming Construction Review
Path and artifact construction is mostly centralized:
- `internal/state` owns workspace artifact path construction.
- `internal/report.Definition` owns artifact groups and batch output names.
- Distributor bundle paths are rendered from validated config templates.
- `internal/fileutil` centralizes atomic file writes and copies.
Areas needing cleanup:
- Report command/config aliases should be centralized in or near `internal/report`.
- Generated-text template/schema IDs should be resolved through one catalog instead of separate maps and app switches.
- Distributor template values should be constructed in one named helper and tested as a contract.
Areas that do not need cleanup now:
- Managed workspace path shape appears clear and centralized enough.
- There is no object-store key or manifest key layer in this repository.
- Optional output copy behavior is app-level user-interface behavior and does not need to move into state.
## Resolution And Catalog Review
Strong catalogs:
- Report registry in `internal/report`.
- Module registry in `internal/briefing`.
- Module IDs in `internal/module`.
- State artifact paths in `internal/state`.
Weaker catalogs:
- Generated-text validators, schemas, render contexts, and templates are related but not registered together.
- Report public names and aliases are split between CLI, app, and config.
- Weather API sources are implemented as explicit methods without a shared source catalog. This is acceptable today but will become noisier as more sources are added.
Recommended centralization:
- First centralize generated-text/template resolution.
- Then centralize report public-name resolution.
- Consider a small Weather API source spec only when the next source is added or when changing source warning/provenance behavior.
## Config And Command-Loading Review
Config loading is mostly consistent:
- Defaults are applied before file decoding.
- CLI overrides are applied before validation.
- Secrets directory loading happens before final validation that needs environment-backed secrets.
- Validation is centralized in `internal/config`.
- CLI commands use standard-library parsing as intended.
Differences that appear intentional:
- `--out` and `--out-dir` are request-level output controls, not config defaults.
- Distributor token values come from environment/secrets rather than raw config.
- Inspect commands load config because workspace and notification-related state depend on config.
Likely accidental or cleanup-worthy differences:
- Report module normalization happens in both `Load` and `Validate`.
- `ReportModuleOverrides` silently ignores unknown report keys if it receives unvalidated config.
- Report name resolution is split across CLI/app/config instead of using report-owned name policy.
## State, Manifest, Or Progress Handling Review
The repository does not currently implement a manifest, checkpoint, resume, or progress subsystem. State is artifact-oriented:
- metadata;
- briefing snapshots;
- module snapshots;
- prompt/data packages;
- preflight output;
- generated text;
- render contexts;
- rendered reports;
- notification artifacts.
This is consistent with the current application size. I do not recommend adding a manifest/resume system before the next release.
Potential future cleanup:
- `LoadMetadataByRunID` currently discovers reports by scanning existing metadata through `ListReports`. This is acceptable for the current workspace size. If workspaces become large or inspect commands need to be faster, add a narrow run-index artifact or direct path helper then.
## Refactors To Avoid
Avoid these before the next release:
- A generic workflow engine for report generation.
- A plugin architecture for reports, modules, adapters, or notifiers.
- A Cobra migration.
- Per-module or per-report Go packages.
- A broad manifest/resume/progress subsystem.
- Reflection-heavy Weather API ingestion.
- A global test helper framework shared across all packages.
- Consolidating all adapter error handling into one generic wrapper.
- Replacing explicit report/module definitions with config-only definitions.
- Rewriting state storage around generated workspace scanning.
These would add abstraction before the implementation has enough repeated complexity to justify it.
## Recommended Implementation Sequence
1. Centralize generated-text/template catalog resolution.
- Goal: one explicit catalog for validator, schema, template, and render-context builder.
- Files: `internal/generatedtext`, `internal/reporttemplate`, `internal/app`, tests.
- Validation: generated-text package tests, reporttemplate tests, app workflow tests.
2. Extract final report finalization helper.
- Goal: one app helper for metadata save, output copy, notification, and result population after a managed Markdown report exists.
- Files: `internal/app`, app tests.
- Validation: app tests for direct Markdown and generated-text-template reports.
3. Centralize report public-name and alias resolution.
- Goal: make `internal/report` the canonical home for command/config/batch name mapping.
- Files: `internal/report`, `internal/cli`, `internal/app`, `internal/config`.
- Validation: CLI parser tests, config tests, report registry tests.
4. Split config report-module normalization from validation.
- Goal: avoid duplicate mutation and make invalid manual config behavior explicit.
- Files: `internal/config`, app config-loading tests.
- Validation: config tests and app tests using report module overrides.
5. Add a small generated-text snapshot lookup helper.
- Goal: reduce repeated optional-stanza extraction while preserving report-specific context structs.
- Files: `internal/generatedtext`.
- Validation: render-context tests.
6. Add a shared strict JSON helper for generated-text validators.
- Goal: keep JSON validation mechanics consistent without merging report semantics.
- Files: `internal/generatedtext`.
- Validation: generated-text validation tests.
7. Consider Weather API source lifecycle helper.
- Goal: centralize source metadata, null-data, warning, and decode lifecycle only if the helper remains explicit.
- Files: `internal/adapters/weatherapi`, adapter tests.
- Validation: Weather API fixture and missing-source policy tests.
8. Add package-local test helpers.
- Goal: reduce noisy repeated setup in app, CLI, and adapter tests.
- Files: test files only.
- Validation: affected package tests and full `go test ./...`.
9. Dead-code and stale-symbol sweep.
- Goal: remove any old aliases, unused helpers, or stale docs exposed by prior cleanup.
- Files: repository-wide as needed.
- Validation: `rg` stale-symbol checks, `go test ./...`, `git diff --check`.
## Test Strategy
Tests to add before refactoring:
- Generated-text catalog completeness test for all generated-text-template reports.
- Report public-name mapping tests for CLI names, config keys, aliases, and batch names.
- Config tests around duplicate/unknown report module overrides and normalized module options.
Tests to add during refactoring:
- Finalization helper tests through app workflows rather than direct private-helper tests.
- Generated-text validator tests for shared strict JSON behavior.
- Render-context tests proving omitted optional modules remain nil.
- Weather API source lifecycle tests if a source helper is introduced.
Focused commands for cleanup work:
```sh
go test ./internal/generatedtext ./internal/reporttemplate ./internal/app
go test ./internal/report ./internal/cli ./internal/config
go test ./internal/adapters/weatherapi
go test ./...
go run ./cmd/weatherreporter --help
git diff --check
```
Manual checks:
- Confirm public CLI syntax and report IDs remain stable unless intentionally changed.
- Confirm managed workspace paths and distributor bundle paths remain stable.
- Confirm no secret values appear in errors, metadata, debug artifacts, tests, or docs.
- Confirm non-roadmap documentation describes only implemented behavior.
## Appendix: Findings Not Worth Acting On
### Standard-Library CLI Flag Parsing Has Some Repetition
The CLI repeats small flag parsing blocks, but the commands have different argument shapes and error messages. A broad command framework would obscure behavior more than it would simplify the code. Keep the standard-library parser.
### Module Files Are Numerous But Clear
One module per focused file in `internal/briefing` is appropriate. Do not collapse modules into a generic map-based engine or split them into per-module packages.
### Report Files Are Explicit By Design
One report or report family per file in `internal/report` is a good navigation pattern. Do not replace these definitions with config-only report declarations before the report set stabilizes.
### Adapter Error Handling Should Remain Local
Scriptorium, Weather API, and distributor failures have different semantics. Shared error-wrapping helpers would likely erase useful context. Keep adapter-local error handling unless exact duplication becomes obvious.
### State Scanning Is Acceptable At Current Scale
The current metadata scan approach is simple and inspectable. A manifest or run index should wait until workspace size or performance makes it necessary.
### Prompt/Data Package Category Naming Is Presentation Policy
The module output category names are prompt-facing schema policy. They should remain explicit in the prompt-input/module layer rather than being generalized into a taxonomy engine.

View File

@@ -1,618 +0,0 @@
# Cleanup Roadmap
## Purpose
This roadmap defines the staged cleanup work recommended by `docs/roadmap/audit.md`. It is written for an LLM coding agent that will implement the stages in order.
The cleanup is intended to simplify, centralize, and clarify implementation logic before the next major release without changing public CLI syntax, managed workspace paths, report IDs, prompt IDs, generated output semantics, or external integration behavior unless a stage explicitly says otherwise.
This file belongs under `docs/roadmap/` because it describes planned refactoring work. Non-roadmap documentation must not describe the planned behavior here until the corresponding code is implemented.
## Cleanup Principles
- Preserve implemented behavior unless a stage explicitly directs a change.
- Prefer narrow explicit helpers and registries over broad frameworks.
- Keep package boundaries from `docs/policy/architecture.md`:
- `internal/config` owns config loading, defaults, overrides, secrets, and validation.
- `internal/report` owns report identity, report definitions, valid periods, batches, output names, and comparison declarations.
- `internal/cli` owns command parsing and help text.
- `internal/app` owns orchestration.
- `internal/briefing` owns prompt-facing module builders and module registry.
- `internal/generatedtext` owns generated-text validation and render-context construction.
- `internal/reporttemplate` owns embedded templates and generated-text schema assets.
- external system types stay inside `internal/adapters/*`.
- Do not introduce Cobra, a workflow engine, plugin architecture, per-module packages, per-report packages, a manifest/resume system, or a global test framework.
- Keep tests focused near the package that owns the behavior.
- Run `gofmt -w` on changed Go files.
- Update implemented docs only in the stage that changes implemented behavior or package contracts.
## Decisions Locked
- Generated-text/template resolution should use one explicit catalog rather than app-owned switches plus separate template/schema maps.
- Report definitions remain authoritative for report IDs, prompt IDs, generation mode, template ID, generated-text schema ID, valid-period resolver, module composition, artifact group, and output naming.
- `internal/report` should become the canonical home for report public-name and alias policy used by CLI, config, and app request resolution.
- App orchestration should keep a direct, readable workflow. Extract only narrow helpers for repeated policies such as final report completion.
- Config report-module normalization should be a load-time mutation. Validation should not perform duplicate mutating normalization.
- `ReportModuleOverrides` should not silently ignore invalid report keys in any path an application caller can reach.
- Weather API source lifecycle cleanup should be explicit and source-aware. Do not use reflection or a generic ingestion framework.
- Test cleanup should use package-local helpers only.
- State remains artifact-oriented. Do not add a manifest, checkpoint, resume, or run-index subsystem in this cleanup sequence.
## Stage 1: Generated-Text And Template Catalog
Goal: make generated-text report asset resolution one explicit catalog so validators, schemas, templates, and render-context builders cannot drift independently.
### Scope
Create a narrow catalog for generated-text-template reports. The catalog should connect:
- generated-text schema ID;
- embedded JSON schema asset;
- generated-text validator;
- report template ID;
- embedded Markdown template asset;
- render-context builder.
### Implementation Guidance
- Prefer placing the primary catalog in `internal/generatedtext` if it owns validator and render-context function wiring.
- Keep embedded asset reading in `internal/reporttemplate`; do not move embedded template or schema files.
- Add an exported or package-internal lookup API with a small app-facing surface, for example:
- `generatedtext.LookupDefinition(def report.Definition)`;
- or `generatedtext.Lookup(schemaID, templateID string)`.
- The returned handler should expose methods or fields for:
- `Validate(data []byte) (any, []byte, error)`;
- `BuildRenderContext(metadata briefing.Metadata, snapshot module.Snapshot, facts app/report facts input, generated any) (any, error)`;
- schema/template IDs needed by `internal/reporttemplate`.
- Avoid importing `internal/app` into `internal/generatedtext`. If app-owned `ReportFacts` is currently needed, pass its constituent `facts.CollectedFacts` and `facts.DerivedFacts`.
- Remove `validateGeneratedText` and `buildRenderContext` switches from `internal/app` after the catalog owns this dispatch.
- Keep `internal/reporttemplate.Template`, `Schema`, and `Render` as small asset helpers unless the catalog cleanly replaces their maps.
- If `reporttemplate` keeps template/schema maps, add tests tying those maps to the generated-text catalog. Prefer one source of truth if this is straightforward.
### Files To Inspect
- `internal/app/app.go`
- `internal/generatedtext/hourly.go`
- `internal/generatedtext/tomorrow.go`
- `internal/generatedtext/render_context.go`
- `internal/reporttemplate/reporttemplate.go`
- `internal/report/*_report.go`
- `internal/report/definition.go`
- `internal/reporttemplate/templates/*.md.tmpl`
- `internal/reporttemplate/schemas/*.schema.json`
### Acceptance Criteria
- Adding a new generated-text-template report requires registering one generated-text catalog entry and adding the report definition/assets, not editing app-level switches.
- `internal/app` no longer switches on concrete generated-text schema IDs or template IDs.
- Unsupported schema/template combinations return actionable errors that include the report ID and the unsupported ID.
- Existing hourly and tomorrow report behavior is unchanged.
- No generated-text schema, template, prompt ID, or report ID changes are introduced.
### Tests
Add or update:
- Generated-text catalog completeness test proving every report with `GenerationGeneratedTextTemplate` has:
- a generated-text catalog entry;
- an embedded schema asset;
- an embedded template asset;
- a validator;
- a render-context builder.
- Negative test for unsupported generated-text schema/template IDs.
- Existing hourly and tomorrow validation tests.
- Existing reporttemplate render tests.
- App workflow tests for hourly and tomorrow reports.
Validation commands:
```sh
go test ./internal/generatedtext ./internal/reporttemplate ./internal/report ./internal/app
go test ./...
go run ./cmd/weatherreporter --help
git diff --check
```
Prompt size: This stage is suitable for one implementation prompt.
## Stage 2: Final Report Finalization Helper
Goal: centralize repeated metadata, output copy, notification, and result-population logic after a managed Markdown report exists.
### Scope
Extract a narrow app helper that runs after either generation mode has produced the managed Markdown report file. This helper should not own Weather API fetching, facts, modules, Scriptorium calls, generated-text validation, or template rendering.
### Implementation Guidance
- Add an unexported helper in `internal/app`, for example `finalizeRenderedReport`.
- Inputs should include the values already available in generation flow:
- context;
- loaded config;
- store;
- resolved report;
- RunID;
- metadata;
- managed report path;
- optional output path/output directory request;
- report result being assembled, or enough fields to return one.
- The helper should centralize:
- optional `--out` copy;
- optional `--out-dir` copy;
- final metadata updates related to report paths/output copy/notification;
- final metadata save;
- distributor notification invocation when enabled;
- notification debug artifact save behavior already implemented;
- notification failure propagation.
- Preserve current ordering:
- managed report is written before finalization;
- metadata is saved after final report path is known;
- notification happens only after successful report generation and final metadata save.
- Preserve current behavior that distributor uses the managed report path, never optional output copies.
- Keep notification request construction in app unless a later stage creates a smaller named helper for template values.
### Files To Inspect
- `internal/app/app.go`
- `internal/state/metadata.go`
- `internal/state/filesystem.go`
- `internal/adapters/distributor`
- `internal/fileutil`
- app tests covering generated reports, output copies, and notification artifacts.
### Acceptance Criteria
- Direct Markdown report generation and generated-text-template generation both use the same finalization helper.
- Managed report paths, optional output copy behavior, metadata JSON shape, notification artifacts, and batch behavior are unchanged.
- Single-report notification failure still returns an error.
- Batch generation still continues independent reports and returns aggregate failure when any report fails.
- The helper is small enough to read without becoming a workflow engine.
### Tests
Add or update:
- App test proving direct Markdown and generated-text-template reports both save final metadata consistently.
- App test proving `--out` and `--out-dir` still copy from the managed report path.
- App test proving notification receives the managed report path.
- App test proving notification failure fails a single report.
- Batch test proving notification failure marks only that report failed and the batch exits nonzero.
Validation commands:
```sh
go test ./internal/app ./internal/state ./internal/adapters/distributor
go test ./...
go run ./cmd/weatherreporter --help
git diff --check
```
Prompt size: This stage is suitable for one implementation prompt.
## Stage 3: Report Public Name And Alias Resolution
Goal: make `internal/report` the canonical source for public report names, config keys, aliases, and batch command membership.
### Scope
Move report-name resolution policy out of scattered CLI/app/config helpers and into report-owned helpers.
### Implementation Guidance
- Add report-owned helpers in `internal/report`, for example:
- `IDForCommandName(name string) (ID, error)`;
- `IDForConfigKey(key string) (ID, error)`;
- `BatchForCommandName(name string) ([]ID, error)`;
- `CommandNames() []string` if helpful for CLI help tests.
- Preserve current accepted names and aliases:
- CLI report names currently accepted by `generate`;
- config aliases currently accepted by `reports.*`;
- batch names currently accepted by `run`.
- Keep user-facing error messages concise and actionable. It is acceptable for exact wording to change if tests are updated and the error remains clear.
- Update `internal/cli` to use report-owned command-name resolution or to call app request helpers that use it.
- Update `internal/app` to remove private `reportIDForCommand` and `reportBatchForCommand` mappings if report-owned helpers can replace them.
- Update `internal/config` to remove private `reportIDForConfigKey` in favor of report-owned config-key resolution.
- Avoid creating new exported app request types solely for this cleanup.
### Files To Inspect
- `internal/report/definition.go`
- `internal/report/registry.go`
- `internal/report/period.go`
- `internal/report/*_report.go`
- `internal/cli/root.go`
- `internal/app/app.go`
- `internal/config/reports.go`
- `docs/cli.md`
- `docs/config.md`
### Acceptance Criteria
- Report identity, command name, config key, and batch membership policy are discoverable from `internal/report`.
- CLI/app/config no longer maintain independent report ID switch statements for the same names.
- All existing public command names and config aliases continue to work unless explicitly documented as removed in this stage. This cleanup should not remove aliases.
- Distributor path variables that use report ID or artifact group are unchanged.
### Tests
Add or update:
- Report tests for command-name-to-ID mapping.
- Report tests for config-key-to-ID mapping and aliases.
- Report tests for batch names and report order.
- CLI parser tests for every supported `generate` report name.
- Config tests for report override aliases and unknown keys.
- App tests for run batch command mapping if not covered through CLI.
Validation commands:
```sh
go test ./internal/report ./internal/cli ./internal/config ./internal/app
go test ./...
go run ./cmd/weatherreporter --help
git diff --check
```
Prompt size: This stage is suitable for one implementation prompt.
## Stage 4: Config Report Module Normalization
Goal: remove duplicate mutating normalization from config validation and make report module override extraction explicit and safe.
### Scope
Separate load-time config mutation from validation. Ensure report module overrides cannot silently ignore invalid report keys in app-facing paths.
### Implementation Guidance
- Keep configuration loading order:
1. built-in defaults;
2. config file;
3. CLI overrides;
4. report module normalization;
5. secrets directory loading;
6. validation.
- `Load` should continue returning a fully normalized and validated `Config`.
- Refactor `Validate` so it does not perform duplicate mutating normalization.
- Options:
- Preferred: split `normalizeReportModules` into a mutating load-time normalizer and a non-mutating validator used by `Validate`.
- Acceptable: make `Validate` require normalized config and document/test that public callers should use `Load` for full processing.
- Do not remove `config.Validate` unless all callers and tests can clearly use `Load`.
- Update `ReportModuleOverrides` so invalid report keys are not silently ignored in app paths.
- Preferred: change it to `ReportModuleOverrides() (map[report.ID][]module.ConfigItem, error)` and update callers.
- Acceptable: keep the signature only if `Config` gains an internal validated/normalized marker and the method clearly cannot be reached with invalid keys.
- Preserve module option strict YAML decoding and composition validation.
- Preserve current config file syntax.
### Files To Inspect
- `internal/config/load.go`
- `internal/config/validate.go`
- `internal/config/reports.go`
- `internal/config/config.go`
- `internal/config/config_test.go`
- `internal/app/app.go`
- `examples/config.yml`
- `docs/config.md`
### Acceptance Criteria
- Report module normalization happens once on the normal `Load` path.
- Validation no longer performs duplicate mutating normalization.
- App report registry construction handles report override errors explicitly.
- Unknown report keys in report module overrides cannot be silently skipped.
- Existing valid example config still loads.
- Existing CLI overrides still take precedence over config and defaults.
### Tests
Add or update:
- Config tests for normal load with typed module options.
- Config tests for direct validation of duplicate aliases and unknown report keys.
- Config tests proving `ReportModuleOverrides` returns or surfaces an error for invalid keys if called on invalid config.
- App test proving report registry construction fails clearly on invalid overrides when supplied programmatically.
- Existing example config load test.
Validation commands:
```sh
go test ./internal/config ./internal/app ./internal/cli
go test ./...
go run ./cmd/weatherreporter --help
git diff --check
```
Prompt size: This stage is suitable for one implementation prompt.
## Stage 5: Generated-Text Internal Helper Cleanup
Goal: reduce low-risk duplication inside `internal/generatedtext` after the catalog is in place.
### Scope
Add small helpers for repeated snapshot stanza extraction and strict JSON validation mechanics while preserving report-specific context structs and validation rules.
### Implementation Guidance
Snapshot lookup:
- Add an unexported snapshot lookup/cache type, for example `moduleSnapshotLookup`.
- It should wrap `module.Snapshot` and provide typed methods for repeated module stanzas.
- Keep report-specific context structs:
- `HourlyRenderContext`;
- `TomorrowRenderContext`;
- future report contexts.
- Do not replace typed context structs with a generic `map[string]any`.
- Preserve nil pointer behavior for omitted optional modules.
Strict JSON helper:
- Add a small helper for:
- single JSON object decode;
- `DisallowUnknownFields`;
- detection of trailing JSON tokens;
- canonical re-marshal of validated output.
- Keep report-specific required-field checks in `hourly.go`, `tomorrow.go`, and future report validators.
- Keep generated-text schema files unchanged unless tests show they are out of sync with validator behavior.
### Files To Inspect
- `internal/generatedtext/render_context.go`
- `internal/generatedtext/hourly.go`
- `internal/generatedtext/tomorrow.go`
- `internal/generatedtext/*_test.go`
- `internal/module`
- `internal/briefing`
### Acceptance Criteria
- Repeated optional stanza extraction is reduced without changing render-context JSON shape.
- Strict JSON decode behavior remains the same for hourly and tomorrow generated text.
- Unknown fields, missing required fields, trailing JSON, and invalid JSON errors remain actionable.
- Existing templates render unchanged.
### Tests
Add or update:
- Generated-text validation tests for unknown fields, trailing JSON, missing required fields, and canonical output.
- Render-context tests proving key module pointers are populated when present and nil when omitted.
- Render-context tests proving extraction errors name the module ID.
Validation commands:
```sh
go test ./internal/generatedtext ./internal/reporttemplate ./internal/app
go test ./...
go run ./cmd/weatherreporter --help
git diff --check
```
Prompt size: This stage is suitable for one implementation prompt.
## Stage 6: Weather API Source Lifecycle Helper
Goal: reduce repeated Weather API source fetch boilerplate while keeping source-specific semantics explicit.
### Scope
Introduce a small helper or source spec for shared source lifecycle behavior in `internal/adapters/weatherapi`.
### Implementation Guidance
- Centralize only common lifecycle mechanics:
- endpoint and query construction;
- envelope fetch;
- absent/malformed/null data handling according to source policy;
- source metadata construction;
- warning/error conversion;
- source hash handling.
- Keep source-specific decode functions explicit.
- Preserve these source-specific behaviors:
- hourly forecast remains required for normal report generation;
- alerts `data:null` means checked successfully with no active alerts;
- optional non-alert source `data:null` follows missing-source policy;
- weather story uses its current endpoint/query behavior;
- SPC convective outlooks use the current endpoint constant and overlap filtering downstream;
- current source warning and provenance JSON shapes remain unchanged.
- Do not use reflection, generics-heavy helpers, or a broad source framework.
- If the helper makes the code less readable, stop and keep the explicit source functions.
### Files To Inspect
- `internal/adapters/weatherapi/client.go`
- `internal/adapters/weatherapi/*_test.go`
- `internal/weatherdata`
- `internal/forecast`
- `docs/integrations/weatherapi.md`
- `docs/internal/weather-data.md`
### Acceptance Criteria
- Source fetch functions are shorter but still readable and source-aware.
- Existing source warning behavior is unchanged.
- Existing source metadata fields and hashes are unchanged.
- No Weather API transport details leak outside `internal/adapters/weatherapi`.
- No prompt/data package schema changes are introduced.
### Tests
Add or update:
- Fixture-server test proving all expected endpoints are still requested.
- Required hourly `data:null` failure test.
- Alerts `data:null` no-active-alerts test.
- Optional non-alert missing-source policy tests for `error`, `warn`, and `none` where currently covered.
- Weather story metadata test.
- SPC source test if current coverage depends on source count or warning count.
Validation commands:
```sh
go test ./internal/adapters/weatherapi ./internal/weatherdata ./internal/forecast
go test ./...
go run ./cmd/weatherreporter --help
git diff --check
```
Prompt size: This stage is suitable for one implementation prompt, but it should be skipped if the implementing agent cannot keep the helper narrow and explicit.
## Stage 7: Package-Local Test Helper Cleanup
Goal: reduce noisy repeated test setup without creating a cross-package test framework.
### Scope
Add package-local helpers only where tests already repeat substantial setup.
### Implementation Guidance
- In `internal/app` tests, consider helpers for:
- temporary config/workspace construction;
- fake renderer setup;
- fake notifier setup;
- Weather API test server setup;
- artifact path/glob assertions.
- In `internal/cli` tests, consider a command invocation helper that captures stdout/stderr and exit status.
- In `internal/adapters/weatherapi` tests, keep fixture server helpers local to the adapter package.
- In `internal/adapters/distributor` tests, keep fake upload/status clients local to the adapter package.
- Do not create `internal/testutil` or a global test helper package in this stage.
- Do not change test behavior or remove meaningful assertions while reducing setup.
### Files To Inspect
- `internal/app/*_test.go`
- `internal/cli/*_test.go`
- `internal/adapters/weatherapi/*_test.go`
- `internal/adapters/distributor/*_test.go`
- `internal/generatedtext/*_test.go`
- `internal/reporttemplate/*_test.go`
### Acceptance Criteria
- Test setup duplication is reduced in packages that have obvious repetition.
- Tests remain readable without hiding important workflow details.
- No production code changes are required for this stage unless an existing test-only seam is missing and justified.
- No global test helper package is introduced.
### Tests
This stage changes tests only unless a small test seam is needed. Run:
```sh
go test ./internal/app ./internal/cli ./internal/adapters/weatherapi ./internal/adapters/distributor
go test ./...
git diff --check
```
Prompt size: This stage is suitable for one implementation prompt.
## Stage 8: Documentation And Dead-Code Sweep
Goal: align implemented documentation with cleanup changes and remove stale symbols left behind by prior stages.
### Scope
Update docs only for implemented cleanup changes. Remove stale code, stale tests, and stale roadmap references that no longer describe future work.
### Implementation Guidance
- Update non-roadmap docs only where package contracts or contributor workflow changed:
- `docs/policy/development.md` if package responsibilities or validation commands changed;
- `docs/internal/app-orchestration.md` if finalization ordering is documented;
- `docs/internal/report-registry.md` if report name/catalog helpers are documented;
- `docs/internal/generated-text.md` or equivalent if generated-text catalog behavior is documented;
- `docs/internal/weather-data.md` if Weather API source lifecycle behavior changed.
- Do not document deferred or unimplemented cleanup outside `docs/roadmap/`.
- Keep `docs/roadmap/future.md` for deferred ideas only if they remain future work.
- Remove stale private helpers after all call sites are gone.
- Run stale-symbol searches for removed helpers and old dispatch names.
### Files To Inspect
- `docs/policy/development.md`
- `docs/internal/`
- `docs/config.md`
- `docs/cli.md`
- `docs/operations.md`
- `docs/roadmap/`
- production code touched by earlier stages
### Acceptance Criteria
- Non-roadmap docs describe implemented behavior only.
- No stale references remain to removed private app switches or old helper names.
- Roadmap docs do not duplicate implemented docs except to identify deferred future work.
- `go test ./...`, CLI help, and whitespace checks pass.
### Suggested Stale-Symbol Checks
Adjust names to match the actual implementation:
```sh
rg -n "validateGeneratedText|buildRenderContext|reportIDForCommand|reportBatchForCommand|reportIDForConfigKey" internal docs examples
rg -n "ReportModuleOverrides" internal docs examples
```
Validation commands:
```sh
go test ./...
go run ./cmd/weatherreporter --help
git diff --check
```
Prompt size: This stage is suitable for one implementation prompt.
## Deferred Refactors
Do not include these in the cleanup sequence:
- Generic workflow engine.
- Plugin architecture.
- Cobra migration.
- Per-module or per-report Go packages.
- Manifest, checkpoint, resume, progress, or run-index system.
- Reflection-heavy Weather API ingestion framework.
- Global test helper package.
- Generic adapter error wrapper.
- Config-only report definitions.
- Broad state-storage redesign around workspace scanning.
These may be revisited only if concrete new requirements make the current explicit structure materially expensive.
## Global Validation Checklist
Run after each implementation stage unless the stage documents a narrower test set:
```sh
go test ./...
go run ./cmd/weatherreporter --help
git diff --check
```
Additional checks after the full cleanup sequence:
```sh
go test ./internal/generatedtext ./internal/reporttemplate ./internal/app
go test ./internal/report ./internal/cli ./internal/config
go test ./internal/adapters/weatherapi ./internal/adapters/distributor ./internal/adapters/scriptorium
go test ./internal/briefing ./internal/module ./internal/facts ./internal/forecast ./internal/promptinput ./internal/changes ./internal/state
rg -n "validateGeneratedText|buildRenderContext|reportIDForCommand|reportBatchForCommand|reportIDForConfigKey" internal docs examples
```
Manual review items:
- Public CLI syntax remains stable.
- Managed workspace paths remain stable.
- Distributor bundle paths remain stable.
- Report IDs, prompt IDs, template IDs, and generated-text schema IDs remain stable unless a stage explicitly changed them.
- No secret values appear in errors, logs, metadata, notification artifacts, docs, examples, or tests.
- Non-roadmap documentation describes only implemented behavior.
## Open Questions
No open questions block implementation of this cleanup roadmap.
The only discretionary item is Stage 6. Recommended approach: implement a narrow Weather API source lifecycle helper only if it remains explicit and improves readability. Viable alternative: skip Stage 6 and keep the current source-specific methods until the next Weather API source is added. The alternative is acceptable because current source functions are readable and tested, and the risk is mostly future maintenance cost rather than current behavior drift.

View File

@@ -123,9 +123,6 @@ Revisit these only when new source types, report types, operational
requirements, or recurring maintenance costs make the duplication materially
more expensive:
- Weather API optional-source specification/helper refactor: consider when
additional Weather API sources make per-source fan-out, policy handling, and
provenance wiring repetitive enough to obscure adapter behavior.
- Broad briefing weather-signal consolidation: consider when multiple module
builders repeatedly derive the same weather signals and tests begin to need
coordinated fixture updates.

View File

@@ -1,441 +0,0 @@
# Tomorrow Report Implementation Roadmap
## Purpose
This roadmap defines the staged implementation plan for
`docs/roadmap/tomorrow.md`. It is written for an LLM coding agent that will
implement each stage in order. The conceptual target state, user intent, and
locked product decisions live in `docs/roadmap/tomorrow.md`; this file defines
the concrete implementation sequence.
This is future-work planning. Do not treat the behavior described here as
implemented until the corresponding code, tests, examples, and non-roadmap docs
are updated.
## Implementation Guardrails
- Preserve public CLI syntax: `weatherreporter generate tomorrow`.
- Make a clean pre-release break from report ID `daily_tomorrow`; do not add
compatibility aliases.
- Keep report identity, prompt IDs, template IDs, artifact groups, output names,
and comparison policy centralized in `internal/report`.
- Reuse the existing `generated_text_template` workflow implemented for Hourly.
- Keep Scriptorium details behind the existing adapter boundary.
- Keep Go responsible for deterministic facts, valid periods, module snapshots,
structured generated-text validation, and final Markdown template rendering.
- Keep templates responsible for wording and layout.
- Keep generated JSON schemas and Markdown templates as embedded asset files,
not inline Go strings.
- Preserve current managed artifact behavior except where report identity
intentionally changes from `daily_tomorrow` to `tomorrow`.
## Stage 1: Report Identity Split
Goal: make Tomorrow an independent report ID and artifact identity while
preserving the public `generate tomorrow` command.
Implementation:
- Replace `report.DailyTomorrow` with `report.Tomorrow` whose value is
`"tomorrow"`.
- Rename report-definition helpers and resolvers around the new identity:
`dailyTomorrowDefinition` to `tomorrowDefinition`,
`dailyTomorrowModules` to `tomorrowModules`, and
`resolveDailyTomorrow` to `resolveTomorrow`.
- Update `report.DefaultRegistry`, `Registry.All`, batch resolution, and tests
so the built-in report order contains `tomorrow` instead of
`daily_tomorrow`.
- Update `internal/app` so `app.ReportTomorrow` resolves to `report.Tomorrow`.
- Keep the valid period as the next local civil day.
- Keep evening batch behavior: `run evening` should still generate the Tomorrow
report.
- Change Tomorrow definition identity fields to:
- `ID: report.Tomorrow`
- `Name: "Tomorrow Report"`
- `ArtifactGroup: "tomorrow"`
- `BatchOutputName: "tomorrow.md"`
- `Generated: true`
- `CompatiblePriorIDs: []report.ID{report.Tomorrow}`
- `ComparisonStrategy: report.CompareSameValidDate`
- Keep the current Tomorrow module composition initially, renamed to
`tomorrowModules`, so the prompt data package continues to include the
daypart, precipitation, alert, SPC, AFD, weather story, tomorrow planning,
and hourly facts already available.
- Update all code and tests that assert `daily_tomorrow` paths, metadata,
RunIDs, prior compatibility, or registry IDs.
Acceptance criteria:
- No production-code references to `report.DailyTomorrow` or report ID
`daily_tomorrow` remain.
- `weatherreporter generate tomorrow` still parses and resolves successfully.
- Evening batch contains `tomorrow`.
- Managed workspace artifact paths and distributor identity values now use
`tomorrow`.
Suggested validation:
```bash
go test ./internal/report ./internal/app ./internal/cli ./internal/state
```
This stage is suitable for one implementation prompt.
## Stage 2: Tomorrow GeneratedText Contract
Goal: add structured Tomorrow LLM output validation and schema assets.
Implementation:
- Add `internal/generatedtext/tomorrow.go` with:
```go
type Tomorrow struct {
Summary string `json:"summary"`
ForecastDiscussion []string `json:"forecast_discussion"`
PrecipitationTiming string `json:"precipitation_timing,omitempty"`
Confidence string `json:"confidence,omitempty"`
}
```
- Add `generatedtext.ValidateTomorrow`.
- Use `json.Decoder.DisallowUnknownFields`.
- Reject multiple JSON values.
- Trim `Summary`, `PrecipitationTiming`, `Confidence`, and each
`ForecastDiscussion` paragraph.
- Drop blank discussion paragraphs after trimming, then require at least one
remaining paragraph.
- Reject blank `Summary`.
- Return normalized JSON with the same public field names.
- Add `internal/reporttemplate/schemas/tomorrow.generated_text.schema.json`.
The schema should:
- require `summary`;
- require `forecast_discussion`;
- define `forecast_discussion` as an array of strings with at least one item;
- allow optional `precipitation_timing` and `confidence`;
- reject additional properties.
- Add `internal/reporttemplate/prompts/tomorrow.generated_text.md` as the
maintained Scriptorium prompt source asset. This file is a source contract for
out-of-band Scriptorium prompt registration; weatherreporter does not need to
load prompt Markdown at runtime.
- Add Tomorrow to `internal/reporttemplate` schema lookup.
- Update app generated-text validation dispatch so it can return either
`generatedtext.Hourly` or `generatedtext.Tomorrow`. Prefer a small generic
dispatch shape such as:
```go
func validateGeneratedText(def report.Definition, data []byte) (any, []byte, error)
```
Then type-check the returned value in render-context dispatch.
Acceptance criteria:
- Tomorrow generated text rejects unknown fields, missing required fields,
blank summary, and no usable forecast discussion paragraphs.
- Optional `precipitation_timing` and `confidence` are trimmed and omitted from
normalized JSON when empty.
- Hourly generated text behavior is unchanged.
Suggested validation:
```bash
go test ./internal/generatedtext ./internal/reporttemplate ./internal/app
```
This stage is suitable for one implementation prompt.
## Stage 3: Daypart Presentation Fields
Goal: add only the daypart fields needed to keep the Tomorrow template
composable without moving prose construction into Go.
Implementation:
- Extend the `derived_daypart_summaries` module output with presentation
helpers that are facts, not complete sentences:
- `display_name`, for example `Morning`;
- `dominant_condition_lower`, for inline template text;
- `temperature_phrase_f`, such as `low 70s`, `upper 60s`, or
`upper 60s to mid-70s`;
- `mention_precipitation`, true when max PoP is at or above the existing
hourly forecast precipitation mention threshold, currently 20%;
- `max_pop_time_label`, using friendly local hour format such as `8:00 AM`
when max PoP time exists.
- Reuse the existing hourly forecast precipitation mention threshold constant
rather than adding a user config field in this change.
- Keep existing structured numeric fields in the module output.
- Do not add a prewritten `daypart_line` string.
- Do not add wind prose in this stage unless tests show the initial template
needs a specific structured wind fact. If wind wording is needed, add a
small structured wind field, not a full sentence.
Acceptance criteria:
- Daypart module output has enough structured fields for a readable Tomorrow
template.
- The output remains useful for YAML prompt packages.
- No generated prose sentence is hard-coded into the module.
Suggested validation:
```bash
go test ./internal/briefing ./internal/promptinput
```
This stage is suitable for one implementation prompt.
## Stage 4: Tomorrow Render Context And Template
Goal: render Tomorrow Markdown from structured generated text, module outputs,
and report metadata.
Implementation:
- Add a dedicated Tomorrow render context under `internal/generatedtext`, for
example:
```go
type TomorrowRenderContext struct {
Report TomorrowReportContext
GeneratedText Tomorrow
Modules TomorrowTemplateModules
Collected facts.CollectedFacts
Derived facts.DerivedFacts
}
```
- Add `TomorrowReportContext` with:
- `Title`, for example `Sunday's Weather`;
- `ForecastDate`;
- `ForecastDateLabel`, for example `Sunday, June 15, 2026`;
- `ForecastDayName`, for example `Sunday`;
- `GeneratedAt`;
- `GeneratedAtLabel`, for example
`Saturday, June 14, 2026 at 9:14 AM`;
- `ValidPeriod`;
- `Timezone`.
- Construct `Title` in Go, not in the template.
- Add `TomorrowTemplateModules` with pointer fields for the module outputs used
by the template:
- `Metadata`;
- `DerivedDailySummary`;
- `DerivedDaypartSummaries`;
- `PrecipTiming`;
- `AlertDigest`;
- `SPCConvectiveOutlooks`;
- `AreaForecastDiscussion`;
- `SPCConvectiveDiscussion`;
- `WeatherStory`;
- `TomorrowPlanning`.
- Add an ordered daypart slice for the template, derived from the configured
daypart order rather than ranging directly over a map. This can live in
`TomorrowTemplateModules`, for example `Dayparts []TomorrowDaypartContext`.
- Add `BuildTomorrowRenderContext`.
- Add `internal/reporttemplate/templates/tomorrow.md.tmpl`.
- Add Tomorrow to `internal/reporttemplate` template lookup.
- Template shape:
- title;
- forecast date;
- generated timestamp;
- `GeneratedText.Summary`;
- deterministic `Daypart Forecast` bullets in configured order;
- conditional `Precipitation Timing` only when precipitation windows exist;
- deterministic precipitation window bullets before optional
`GeneratedText.PrecipitationTiming`;
- `Forecast Discussion` with one paragraph per
`GeneratedText.ForecastDiscussion` item.
- Keep current conditions and hourly forecast available through the data
package and render context, but do not render them in the initial Tomorrow
template unless the template explicitly uses them.
Acceptance criteria:
- The template renders without map-order nondeterminism.
- Precipitation Timing is omitted when no precipitation windows exist.
- Forecast Discussion supports multiple paragraphs.
- Missing optional modules produce clean omission or fallback behavior, not
template execution errors.
Suggested validation:
```bash
go test ./internal/generatedtext ./internal/reporttemplate ./internal/briefing
```
This stage is suitable for one implementation prompt.
## Stage 5: App Workflow Integration
Goal: route Tomorrow through the generated-text-template workflow end to end.
Implementation:
- Change Tomorrow report definition to:
- `PromptID: "weather.tomorrow_generated_text"`
- `GenerationMode: report.GenerationModeGeneratedTextTemplate`
- `TemplateID: "tomorrow"`
- `GeneratedTextSchemaID: "tomorrow"`
- Update `internal/app.buildRenderContext` dispatch:
- hourly template requires `generatedtext.Hourly`;
- tomorrow template requires `generatedtext.Tomorrow`;
- unsupported type/template combinations return actionable errors.
- Ensure Scriptorium `run` writes raw generated text to a `.json` path for
Tomorrow, matching the existing generated-text-template workflow.
- Ensure normalized generated text, render context JSON, generated Markdown,
metadata, and final report artifacts are persisted through existing state
helpers.
- Ensure app errors include report ID `tomorrow` and RunID context.
Acceptance criteria:
- `weatherreporter generate tomorrow` no longer invokes Scriptorium for full
Markdown.
- The workflow validates Scriptorium JSON output, builds a Tomorrow render
context, and renders Markdown locally.
- Hourly generated-text-template behavior remains unchanged.
Suggested validation:
```bash
go test ./internal/app ./internal/state ./internal/adapters/scriptorium
```
This stage is suitable for one implementation prompt.
## Stage 6: CLI, State, Distributor, And Batch Behavior
Goal: update cross-package behavior affected by the report ID clean break.
Implementation:
- Update CLI tests and command-output expectations for `generate tomorrow`.
- Update state/path tests so managed artifacts, snapshots, data packages,
generated-text artifacts, render contexts, and report files use artifact group
`tomorrow`.
- Update batch tests:
- evening batch emits report ID `tomorrow`;
- batch output copy name remains `tomorrow.md`;
- partial-failure behavior is unchanged.
- Update Recent Changes tests:
- Tomorrow compares only against prior `tomorrow` snapshots;
- Daily Today no longer treats Tomorrow as a compatible prior unless the
implementation explicitly keeps that relationship for Daily Today only.
- Update distributor notification tests so rendered template variables use:
- `report_id=tomorrow`;
- `artifact_group=tomorrow`;
- `batch_output_name=tomorrow.md`.
- Update config tests so report module overrides use `reports.tomorrow`.
Do not accept `reports.daily_tomorrow` unless a future explicit
compatibility decision reverses the clean break.
Acceptance criteria:
- Public CLI syntax is stable.
- Persisted artifacts and distributor request context use the new report ID.
- Batch behavior is unchanged except for the new ID.
- No tests rely on `daily_tomorrow`.
Suggested validation:
```bash
go test ./internal/cli ./internal/app ./internal/state ./internal/config ./internal/report
```
This stage is suitable for one implementation prompt.
## Stage 7: Documentation And Examples
Goal: align implemented docs and maintained examples after the code change.
Implementation:
- Update non-roadmap docs only after the behavior is implemented.
- Inspect and update:
- `docs/cli.md`;
- `docs/config.md`;
- `docs/operations.md`;
- `docs/troubleshooting.md`, if new failure modes are introduced;
- `docs/internal/report-registry.md`;
- `docs/internal/generatedtext.md`;
- `docs/internal/reporttemplate.md`;
- `docs/internal/state.md`;
- `docs/templates.md`;
- relevant Scriptorium and distributor integration docs only if their
weatherreporter-facing contract changed.
- Update `examples/config.yml` if it references Tomorrow modules or
`reports.daily_tomorrow`.
- Keep future Today/Daily split language only under `docs/roadmap/`.
- Do not document unimplemented Today or generic Daily products as current
behavior.
Acceptance criteria:
- Non-roadmap docs describe implemented behavior only.
- Docs use report ID `tomorrow`.
- Examples load under current config validation.
- No stale user-facing references to `daily_tomorrow` remain outside historical
roadmap context.
Suggested validation:
```bash
rg -n "daily_tomorrow|Daily Tomorrow|weather.daily_report" README.md docs examples internal
git diff --check
```
This stage is suitable for one implementation prompt.
## Stage 8: Final Validation
Goal: verify the completed cutover as a coherent behavior change.
Run:
```bash
go test ./internal/report ./internal/generatedtext ./internal/reporttemplate ./internal/briefing ./internal/app ./internal/cli ./internal/state
go test ./...
go run ./cmd/weatherreporter --help
git diff --check
```
Also run targeted stale-symbol checks:
```bash
rg -n "DailyTomorrow|dailyTomorrow|daily_tomorrow" internal docs examples
rg -n "weather.tomorrow_generated_text|TemplateID:.*tomorrow|GeneratedTextSchemaID:.*tomorrow" internal docs
```
Acceptance criteria:
- Full test suite passes.
- Help output still shows `generate tomorrow`.
- No production-code stale `daily_tomorrow` symbols remain.
- Generated-text-template artifacts for Tomorrow are persisted in the same
categories as Hourly.
- Existing Hourly behavior still passes tests.
This stage is suitable for one implementation prompt.
## Open Questions
None block implementation. The required decisions are locked by
`docs/roadmap/tomorrow.md` and this implementation plan:
- Tomorrow uses report ID `tomorrow`.
- Tomorrow uses prompt ID `weather.tomorrow_generated_text`.
- Tomorrow uses template ID and generated-text schema ID `tomorrow`.
- Tomorrow forecast discussion is an array of paragraph strings.
- `daily_tomorrow` compatibility aliases are intentionally not preserved.
## Global Validation Checklist
- `go test ./...`
- `go run ./cmd/weatherreporter --help`
- `git diff --check`
- `rg -n "DailyTomorrow|dailyTomorrow|daily_tomorrow" internal docs examples`
- Confirm `weatherreporter generate tomorrow` uses structured JSON from
Scriptorium and renders final Markdown locally.
- Confirm distributor notification context uses `report_id=tomorrow` and
`artifact_group=tomorrow`.
- Confirm examples contain no unimplemented fields and no secrets.

View File

@@ -1,276 +0,0 @@
# Tomorrow Report Roadmap
## Purpose
This roadmap defines the target state for making Tomorrow an independent
generated-text-template report. The feature is not implemented yet, so this
document lives under `docs/roadmap/`.
## Intent
Tomorrow should become its own report product, not a variant of the Daily
Report. The current CLI command `weatherreporter generate tomorrow` should
remain, but the internal report ID, prompt ID, template, schema, workspace
paths, and distributor identity should use `tomorrow`.
The report should combine deterministic daypart and precipitation facts with
LLM prose for the high-level summary, optional precipitation context, and
forecast discussion. The resulting Markdown should be predictable and
template-driven, similar to the implemented Hourly Report.
Longer term, Today, Tomorrow, and Daily may all become separate report products
with different prompts, templates, and deterministic sections. This roadmap
starts that split with Tomorrow.
## Target Report Shape
Example structure:
```markdown
# Sunday's Weather
**Forecast Date:** Sunday, June 15, 2026
**Generated:** Saturday, June 14, 2026 at 9:14 AM
<GeneratedText summary>
## Daypart Forecast
- **Overnight:** <deterministic daypart line>
- **Morning:** <deterministic daypart line>
- **Midday:** <deterministic daypart line>
- **Afternoon:** <deterministic daypart line>
- **Evening:** <deterministic daypart line>
## Precipitation Timing
- **1:00 AM** to **5:00 AM**: Precipitation is expected during this period. The peak precipitation chance is 59% at 2:00 AM.
- <optional GeneratedText precipitation_timing>
## Forecast Discussion
<GeneratedText forecast_discussion paragraphs>
```
`Precipitation Timing` should render only when at least one precipitation
window exists for the valid period. The threshold for precipitation windows
remains the existing precipitation-window threshold, currently 40%.
## Locked Decisions
- Replace report ID `daily_tomorrow` with `tomorrow`.
- Do not preserve compatibility aliases for `daily_tomorrow`; this is a
pre-release clean break.
- Keep public CLI syntax: `weatherreporter generate tomorrow`.
- Use generated-text-template generation for Tomorrow, not full Markdown
generation by Scriptorium.
- Use a dedicated Scriptorium prompt ID, template ID, and schema ID:
- prompt ID: `weather.tomorrow_generated_text`
- template ID: `tomorrow`
- generated-text schema ID: `tomorrow`
- Use `ArtifactGroup: "tomorrow"` and `BatchOutputName: "tomorrow.md"`.
- Use `CompatiblePriorIDs: []report.ID{report.Tomorrow}`.
- Keep valid-period behavior: Tomorrow covers the next local civil day.
- Keep Morning/Evening batch behavior unless explicitly changed later; evening
batch should still include Tomorrow.
- Future Today/Daily split is out of scope for this roadmap.
## GeneratedText Contract
Add a Tomorrow GeneratedText schema:
```json
{
"summary": "string",
"forecast_discussion": ["string"],
"precipitation_timing": "string",
"confidence": "string"
}
```
Required:
- `summary`
- `forecast_discussion`
Optional:
- `precipitation_timing`
- `confidence`
`forecast_discussion` should be an array of paragraph strings so Scriptorium
can return multi-paragraph discussion without embedding paragraph delimiters in
one string. Empty or whitespace-only discussion paragraphs should be rejected or
trimmed out during validation; after trimming, at least one paragraph is
required.
`confidence` may be validated and persisted but does not need to render in the
initial template.
## Template Context
Add a dedicated Tomorrow render context rather than reusing Hourly context
types.
Recommended top-level shape:
```go
type TomorrowRenderContext struct {
Report TomorrowReportContext
GeneratedText Tomorrow
Modules TomorrowTemplateModules
Collected facts.CollectedFacts
Derived facts.DerivedFacts
}
```
`TomorrowReportContext` should include:
- `Title`: for example `Sunday's Weather`
- `ForecastDate`: canonical local forecast date if useful
- `ForecastDateLabel`: for example `Sunday, June 15, 2026`
- `ForecastDayName`: for example `Sunday`
- `GeneratedAt`
- `GeneratedAtLabel`: for example `Saturday, June 14, 2026 at 9:14 AM`
- `ValidPeriod`
- `Timezone`
Do not derive the possessive title in the template. Go should provide `Title`
so wording is consistent and easy to test.
`TomorrowTemplateModules` should expose the module outputs needed by the
template:
- `Metadata`
- `DerivedDailySummary`
- `DerivedDaypartSummaries`
- `PrecipTiming`
- `AlertDigest`
- `SPCConvectiveOutlooks`
- `AreaForecastDiscussion`
- `SPCConvectiveDiscussion`
- `WeatherStory`
- `TomorrowPlanning`, if still useful
Current conditions and hourly forecast can remain in the module snapshot and
data package if useful for Scriptorium, but they do not need to render in the
initial Tomorrow template unless a later design calls for them.
## Daypart Forecast
The Daypart Forecast should be deterministic but composable. Avoid adding a
single prewritten Go `DaypartLine` string that makes template wording rigid.
Add presentation-friendly fields to `derived_daypart_summaries` only where they
avoid awkward template logic. Likely useful fields:
- display name, such as `Overnight` or `Morning`;
- lower-case dominant condition text for inline sentences;
- rounded temperature range phrase if available;
- precipitation mention flag using the existing hourly line mention threshold
concept, currently 20%;
- max precipitation probability and friendly max time;
- optional wind phrase or wind range only if deterministic wind wording is
clearly needed.
The initial implementation may keep daypart bullet wording simple. It should be
easy to revise the template text without editing Go unless new facts are
needed.
## Implementation Plan
1. Report identity split
- Rename `report.DailyTomorrow` to `report.Tomorrow` with ID `tomorrow`.
- Update registry order, resolver references, CLI mapping, batch selection,
state metadata expectations, docs, and tests.
- Preserve `weatherreporter generate tomorrow`.
- Accept that workspace paths, RunIDs, distributor bundle IDs, and report
URLs change from `daily_tomorrow` to `tomorrow`.
2. GeneratedText contract
- Add `generatedtext.Tomorrow`, validation, normalized JSON output, and
tests.
- Add `tomorrow.generated_text.schema.json`.
- Add `internal/reporttemplate/prompts/tomorrow.generated_text.md` as the
maintained prompt source asset.
- Update app validation dispatch to use the Tomorrow schema.
3. Template and render context
- Add `internal/reporttemplate/templates/tomorrow.md.tmpl`.
- Add Tomorrow template/schema lookup entries.
- Add `BuildTomorrowRenderContext`.
- Update app render-context dispatch for template ID `tomorrow`.
- Persist render context in the existing generated-text-template workflow.
4. Module presentation fields
- Add only the daypart presentation fields needed by the template.
- Reuse the existing precipitation window hour-label fields.
- Keep deterministic weather derivation in Go and wording/layout in the
template.
5. Report definition conversion
- Change Tomorrow report definition to:
- `PromptID: "weather.tomorrow_generated_text"`
- `GenerationMode: generated_text_template`
- `TemplateID: "tomorrow"`
- `GeneratedTextSchemaID: "tomorrow"`
- `ArtifactGroup: "tomorrow"`
- `BatchOutputName: "tomorrow.md"`
- compatible prior IDs containing only `tomorrow`
- Review module composition and keep only modules used by the prompt,
template, or future inspection value.
6. Documentation and examples
- After implementation, update non-roadmap docs for implemented behavior:
`docs/cli.md`, `docs/operations.md`, `docs/internal/report-registry.md`,
`docs/internal/generatedtext.md`, `docs/internal/reporttemplate.md`, and
`docs/templates.md`.
- Update examples that refer to `reports.tomorrow` or report module
overrides if the config key changes.
## Test Plan
- Report tests:
- registry contains `tomorrow`, not `daily_tomorrow`;
- `generate tomorrow` resolves report ID `tomorrow`;
- valid period remains next local civil day;
- evening batch still includes Tomorrow;
- RunID and artifact paths use `tomorrow`.
- GeneratedText tests:
- `summary` and non-empty `forecast_discussion` are required;
- `forecast_discussion` trims paragraph strings and rejects/omits blanks;
- optional `precipitation_timing` and `confidence` normalize correctly;
- unknown fields are rejected.
- Template tests:
- title renders as `<weekday>'s Weather`;
- forecast date and generated labels render;
- daypart bullets render in configured daypart order;
- precipitation section is omitted when no precipitation windows exist;
- precipitation section includes deterministic windows and optional LLM text
when windows exist;
- forecast discussion renders multiple paragraphs.
- App/CLI workflow tests:
- `weatherreporter generate tomorrow` uses structured Scriptorium output and
the template renderer;
- raw generated text, validated generated text, render context, report, and
metadata artifacts are persisted;
- optional `--out` behavior remains unchanged;
- distributor notification uses report ID/artifact group `tomorrow`.
Validation commands:
```bash
go test ./internal/report ./internal/generatedtext ./internal/reporttemplate ./internal/briefing ./internal/app ./internal/cli ./internal/state
go test ./...
go run ./cmd/weatherreporter --help
git diff --check
```
## Open Questions
None block implementation. Recommended defaults are:
- make `forecast_discussion` an array of strings for Tomorrow;
- keep Hourly GeneratedText unchanged for now;
- do not add Daily Today or generic Daily report splits in this change;
- do not preserve `daily_tomorrow` compatibility aliases.

View File

@@ -23,32 +23,32 @@ type validator func([]byte) (any, []byte, error)
type renderContextBuilder func(report.ID, string, briefing.Metadata, module.Snapshot, facts.CollectedFacts, facts.DerivedFacts, any) (any, error)
type catalogEntry struct {
schemaID string
templateID string
validate validator
buildRenderContext renderContextBuilder
schemaID string
templateID string
validate validator
renderContextBuilder renderContextBuilder
}
type Handler struct {
reportID report.ID
schemaID string
templateID string
validate validator
buildRenderContext renderContextBuilder
reportID report.ID
schemaID string
templateID string
validate validator
renderContextBuilder renderContextBuilder
}
var catalog = []catalogEntry{
{
schemaID: schemaIDHourly,
templateID: templateIDHourly,
validate: validateHourly,
buildRenderContext: buildHourlyContext,
schemaID: schemaIDHourly,
templateID: templateIDHourly,
validate: validateHourly,
renderContextBuilder: buildHourlyContext,
},
{
schemaID: schemaIDTomorrow,
templateID: templateIDTomorrow,
validate: validateTomorrow,
buildRenderContext: buildTomorrowContext,
schemaID: schemaIDTomorrow,
templateID: templateIDTomorrow,
validate: validateTomorrow,
renderContextBuilder: buildTomorrowContext,
},
}
@@ -67,11 +67,11 @@ func LookupDefinition(definition report.Definition) (Handler, error) {
}
if entry.schemaID == definition.GeneratedTextSchemaID && entry.templateID == definition.TemplateID {
return Handler{
reportID: definition.ID,
schemaID: entry.schemaID,
templateID: entry.templateID,
validate: entry.validate,
buildRenderContext: entry.buildRenderContext,
reportID: definition.ID,
schemaID: entry.schemaID,
templateID: entry.templateID,
validate: entry.validate,
renderContextBuilder: entry.renderContextBuilder,
}, nil
}
}
@@ -116,10 +116,10 @@ func (h Handler) Validate(data []byte) (any, []byte, error) {
}
func (h Handler) BuildRenderContext(metadata briefing.Metadata, snapshot module.Snapshot, collected facts.CollectedFacts, derived facts.DerivedFacts, generated any) (any, error) {
if h.buildRenderContext == nil {
if h.renderContextBuilder == nil {
return nil, fmt.Errorf("render-context builder is not registered for template %q on report %q", h.templateID, h.reportID)
}
return h.buildRenderContext(h.reportID, h.templateID, metadata, snapshot, collected, derived, generated)
return h.renderContextBuilder(h.reportID, h.templateID, metadata, snapshot, collected, derived, generated)
}
func (h Handler) Render(data any) ([]byte, error) {

View File

@@ -26,7 +26,7 @@ func TestCatalogCompleteForGeneratedTextTemplateReports(t *testing.T) {
if handler.validate == nil {
t.Fatal("validator is nil")
}
if handler.buildRenderContext == nil {
if handler.renderContextBuilder == nil {
t.Fatal("render-context builder is nil")
}
if schema, err := handler.Schema(); err != nil {