Files
weatherreporter/docs/roadmap/implementation.md

35 KiB

LLM Profile Comparison Implementation Plan

Status: Ready for implementation.

Purpose And Authority

This document is the ordered implementation plan for the accepted LLM Profile Comparison Roadmap. Implement every stage in numeric order. The roadmap owns feature purpose, policy, scope, and target state; this plan owns implementation sequence, concrete interfaces, and stage exit criteria.

This is one feature delivered over ten bounded coding prompts. A stage may refactor code needed by a later stage, but it must leave the repository compiling, tested, and internally coherent. Do not expose an incomplete compare command before the stages that implement its full command contract.

Implementation Rules

Apply these rules in every stage:

  • Read docs/development.md, the task-specific documents it identifies, and all files under docs/policy/ before changing code. Treat those documents and the feature roadmap as binding.
  • Preserve the existing behavior of generate and run, including output precedence, report/date semantics, notification, debug capture, structured results, and exit behavior.
  • Keep Promptkit types and calls behind internal/adapters/promptkit and the dependency-neutral internal/promptexec interface. Do not import Promptkit from application, CLI, or comparison-artifact packages.
  • Keep comparison artifacts operator-owned and explicit. They are not durable application state and must never be discovered or consumed implicitly by a later invocation.
  • Do not add a Weatherreporter concurrency limit or semaphore. One shared Promptkit executor is safe for concurrent use, and Promptkit v0.5.0 owns its engine-local backend capacity.
  • Use dependency injection and deterministic fakes for application and CLI tests. Tests must be offline, credential-free, race-safe, and independent of completion timing and machine-specific paths.
  • Add regression coverage in the same stage as each behavior. Prefer contract-level assertions over incidental implementation detail, in accordance with docs/policy/testing.md.
  • Run gofmt on changed Go files and git diff --check in every stage. Run focused tests while developing, then GOWORK=off go test -count=1 ./... before completing the stage. Stages that add concurrency or filesystem replacement must also run the relevant packages with -race.
  • Do not commit, tag, push, or prepare a release unless the implementing prompt separately requests it.

Locked Implementation Contracts

The following choices make the stages decision-complete. Do not substitute a different command shape, artifact schema, publication algorithm, or concurrency policy without first updating the accepted roadmap and this plan.

Application Request And Result

Add this exported application request, using the existing application aliases and interfaces:

type ComparisonRequest struct {
	Config      config.Config
	Report      ReportKind
	ProfileIDs  []string
	WorkingDir  string
	OutputDir   string
	Replace     bool
	LLMDebugDir string
	Date        time.Time
	Clock       timeutil.Clock
	Collector   Collector
	Executor    promptexec.Executor
}

OutputDir is the optional exact bundle-directory override. Clock supplies the comparison identity plus start and finish timestamps and defaults to timeutil.SystemClock when nil. Date retains the existing report-date meaning.

Do not include a notifier in this request. Making notification structurally unavailable is clearer and safer than passing a disabled notifier through the comparison path.

Add app.CompareDetailed(context.Context, ComparisonRequest) returning a non-nil *app.ComparisonResult whenever report and destination resolution have progressed far enough to describe the attempted comparison. Use these field names and meanings:

type ComparisonResult struct {
	ComparisonID   string
	ReportID       report.ID
	ReportName     string
	PromptID       string
	PromptVersion  string
	PromptHash     string
	StartedAt      time.Time
	FinishedAt     time.Time
	Timezone       string
	ValidPeriod    timeutil.Period
	OutputDirectory string
	ManifestPath   string
	DataPackagePath string
	Total          int
	Succeeded      int
	Failed         int
	Results        []ComparisonProfileResult
}

type ComparisonProfileResult struct {
	Position         int
	ProfileID        string
	BackendID        string
	ModelName        string
	Status           string
	ValidationStatus promptexec.ValidationStatus
	ReportPath       string
	LLMDebugPath     string
	Error            *comparison.SafeError
}

OutputDirectory is absolute once destination resolution succeeds. Manifest and data-package paths are absolute and nonblank only after publication. Successful report paths and any returned debug paths are also absolute. Profile results always follow selection order.

Profile counts describe profile outcomes only. A publication failure after all profiles succeed therefore leaves total == succeeded and failed == 0, while the command-level status and returned error still report failure.

Reject fewer than two profile IDs, whitespace-only IDs, and exact case-sensitive duplicates. Preserve every accepted ID byte-for-byte and in request order; do not trim, case-fold, deduplicate, or merge it with config.promptkit.profile. Validate this invariant both in the CLI and at the application boundary.

Comparison And File Identity

Use the resolved report metadata's existing run ID to form:

comparison_<report-run-id>

This comparison ID is computed once from the injected clock and shared by the application result, manifest, and debug identities.

Derive a profile filename slug as follows:

  1. retain ASCII letters, ASCII digits, -, and _;
  2. replace each run of every other character, including dots, whitespace, separators, control characters, and non-ASCII characters, with one -;
  3. trim leading and trailing - and _;
  4. use profile if the result is empty; and
  5. truncate the result to 64 ASCII bytes, then trim trailing - and _ again, falling back to profile if necessary.

The report filename is:

<ordinal>-<slug>.md

The one-based ordinal width is the greater of two and the number of decimal digits in the profile count. Filename collisions after slug normalization are safe because the ordinal is authoritative. The exact profile ID remains the logical identity in results and the manifest.

Derive a default comparison directory by requiring the resolved report output name to end in the exact .md suffix, removing that suffix, and prefixing the remaining basename with comparison-. Resolve it beneath configured output.directory, or beneath the invocation working directory when no output directory is configured. An explicit --out-dir is the exact bundle directory, resolves relative to the invocation working directory, and completely overrides configured output. Return an absolute cleaned path.

Versioned Bundle Contract

Create a dependency-neutral internal/comparison package. It owns deterministic naming, the logical bundle and manifest types, validation, recognition of prior bundles, hashing, staging, and transactional publication. It must not import CLI, configuration, Promptkit, collectors, or report-generation packages.

Use the schema version:

weatherreporter.comparison.v1

The UTF-8 comparison.json document is indented by two spaces and ends with one newline. Its exact top-level JSON fields, in struct declaration order, are:

schemaVersion
comparisonId
startedAt
finishedAt
reportId
validPeriod
timezone
promptId
promptVersion
promptHash
dataPackage
total
succeeded
failed
results

validPeriod has start and end. dataPackage has path and sha256. Each ordered result has:

position
profileId
backendId       (omit when empty)
modelName
status
validationStatus (omit when unavailable)
reportPath      (omit for failures)
error           (omit for successes)

An error object has category and message. Status values are exactly succeeded and failed. A successful result must have a relative report path, must not have an error, and must have passed generated-text validation. A failed result must not have a report path and must have a safe error. Use the dependency-neutral string returned by promptexec.CategoryOf when available; use application for an unclassified application, validation, rendering, or debug-callback failure. Bound a manifest or normal-summary error message to 1,024 UTF-8 bytes without splitting an encoded rune. Never serialize wrapped causes, provider bodies, prompts, schemas, model output, endpoints, or credentials.

Encode all manifest timestamps using Go's standard time.Time JSON encoding. Comparison start and finish timestamps are UTC; valid-period start and end retain their resolved timezone offsets. Require nonzero timestamps, a finish that is not before the comparison start, and a valid-period end strictly after its start.

Use lowercase 64-character hexadecimal SHA-256 digests. data-package.yml contains the exact immutable byte slice supplied to every execution. A logical bundle contains that data package, ordered successful Markdown report bytes, and the complete manifest. Manifest counts and paths must agree exactly with the ordered results and logical bundle.

Destination Safety And Publication

Perform a read-only destination preflight before prompt inspection, weather collection, directory creation, or model execution. Reject:

  • a filesystem root;
  • the exact invocation working directory;
  • an existing regular file or non-directory;
  • an existing symlink, including a dangling symlink;
  • an uninspectable target; and
  • a nonempty directory unless --replace is present and it is a recognized current-schema Weatherreporter comparison bundle.

An absent target and an existing empty real directory are accepted without --replace. --replace is also harmless for those cases. Recognition must decode the manifest with unknown fields rejected, validate every manifest invariant, require data-package.yml and every declared successful report to be regular non-symlink files, verify the data-package digest, and require the directory entries to be exactly the manifest, data package, and declared successful reports. Extra files, subdirectories, links, missing files, invalid paths, duplicate paths, or a failed digest make the directory unrecognized. Never follow a manifest path outside the bundle.

Re-run the destination safety check immediately before publication. Build the entire logical bundle in memory, create the parent only when publishing, and write a private uniquely named sibling staging directory with directory mode 0700 and files mode 0600. Check cancellation while staging and immediately before the commit operation.

For an absent target, atomically rename the completed sibling staging directory to the target. For an existing empty or recognized target, rename the target to a unique sibling backup, rename staging to the target, and restore the backup if the second rename fails. If restoration also fails, retain the backup and return an actionable joined error that identifies its path. Once this rename transaction begins, finish commit or rollback without abandoning it because of a concurrent cancellation. The final pre-commit context check is the linearization point: cancellation observed before it prevents replacement; cancellation arriving after it does not retroactively undo a completed commit. Remove the backup after a successful replacement and clean staging artifacts on ordinary failures without scanning or modifying unrelated paths.

Shared Preparation And Concurrent Execution

Extract an immutable internal prepared-report value containing the resolved report, collection-derived facts, module snapshot, briefing metadata, generated-text handler/render inputs, source warnings, and the one serialized data-package byte sequence. Preparation resolves, collects, derives, snapshots, and marshals once.

Split the generated-text workflow into three internal responsibilities:

  1. deterministic shared preparation;
  2. one profile-specific Promptkit execution, validation, and in-memory Markdown rendering; and
  3. publication, with optional ordinary notification.

GenerateDetailed composes those responsibilities once and retains its current single-report behavior. Comparison never calls GenerateDetailed in a loop.

For comparison, inspect the exact prompt once, then inspect every profile sequentially in selection order, including effective backend, model, and credential availability, before weather collection. Initialize an explicitly requested debug writer before those operations so pre-execution diagnostics retain current behavior. Any preflight failure causes no collection, model calls, or bundle publication.

After shared preparation, start one goroutine per profile against one shared executor. Pass the same immutable data-package bytes and exact inspected prompt version to every call. Preallocate an ordered outcome slice and let each goroutine write only its own index; use synchronization to join all goroutines. One profile failure does not cancel peers. Parent cancellation propagates to every call, no additional calls are started after cancellation is observed, and all started goroutines are joined before return.

Give each execution a deterministic debug run identity:

<comparison-id>_<ordinal>-<slug>

The existing debug preparation artifact remains authoritative for the exact profile ID. Debug callback failure is a failure of only that profile and must not collide with or cancel peers.

After all non-cancelled outcomes finish, order them by original selection, capture one UTC finish timestamp, and construct the complete or coherent partial bundle. Publish partial bundles when one or more profiles fail, then return the non-nil result with a generic aggregate error so the CLI exits nonzero. Do not include provider detail in that aggregate error. Cancellation or deadline expiration publishes no bundle. No comparison path constructs or calls a Distributor notifier.

CLI Contract

Add:

weatherreporter compare REPORT [options]

Support daily, today, tomorrow, and hourly, with the same date policy as generate. Accept applicable existing --config, --units, --tz, --date, --llm-debug-dir, and --quiet flags plus a repeatable custom --profile, the exact-directory --out-dir, and boolean --replace. Do not accept --out, and do not use the configured default Promptkit profile as a selection. Construct exactly one executor per command invocation.

The normal stdout JSON summary has these fields in declaration order:

command
comparisonId
reportId
reportName
promptId
promptVersion
promptHash
status
startedAt
finishedAt
timezone
validPeriod
outputDirectory
manifestPath       (omit when unpublished)
dataPackagePath    (omit when unpublished)
total
succeeded
failed
results
error              (omit on complete success)

command is compare; command status is succeeded only when every profile succeeds and publication succeeds, otherwise failed. Each result mirrors the application profile result with absolute report and debug paths. Preserve profile order. A coherent partial bundle is still a failed command and exits nonzero. Failures before a structured result exists use the existing CLI error path without inventing a partial JSON object. --quiet suppresses normal stdout and routine progress output but never changes work, artifacts, or exit status.

Stage 1: Define Comparison Artifact And Naming Contracts

Goal

Introduce the dependency-neutral logical contract without altering any command or existing generation path.

Work

  1. Create internal/comparison with manifest, result, data-package reference, valid-period, safe-error, logical-bundle, and bundle-report types matching the locked schema.
  2. Add constants for the schema version, comparison.json, and data-package.yml, plus explicit succeeded and failed statuses.
  3. Implement profile-list validation, deterministic slugging, ordinal width, report filename construction, comparison ID construction, default directory name derivation, SHA-256 formatting, safe UTF-8 error truncation, manifest validation, and deterministic JSON encoding.
  4. Ensure validation rejects inconsistent counts, positions, statuses, paths, digests, timestamps, identities, successful results without reports, failures with reports, duplicate paths, non-basename artifact paths, and traversal-capable names.
  5. Keep all filesystem mutation and application orchestration out of this stage.

Tests And Exit Criteria

  • Table-test unusual profile IDs, empty slugs, separators, dots, controls, Unicode, length limits, normalized collisions, and profile counts above 99.
  • Round-trip the exact JSON field names, ordering, indentation, newline, optional fields, safe errors, hashes, and manifest invariants.
  • Confirm exact case-sensitive profile identity and duplicate handling.
  • Run GOWORK=off go test -count=1 ./internal/comparison and the repository-wide standard test command. The repository remains behaviorally unchanged.

Stage 2: Implement Safe Bundle Recognition And Transactional Publication

Goal

Complete the isolated filesystem boundary, including output resolution and whole-directory replacement, before application concurrency depends on it.

Work

  1. Add read-only destination planning and preflight to internal/comparison, parameterized by the absolute working directory, exact target, and replace authorization.
  2. Implement strict current-schema prior-bundle recognition with unknown JSON fields rejected, manifest validation, exact-entry checks, symlink rejection, regular-file checks, containment, and digest verification.
  3. Implement sibling staging, fixed permissions, cancellation checks, absent target publication, existing-target backup and replacement, rollback, and targeted cleanup according to the locked algorithm.
  4. Add an application output resolver, alongside existing output helpers, that applies explicit/configured/current-directory precedence and derives the default comparison name from the resolved report output name. Do not change ordinary output resolution.
  5. Return typed or inspectable errors sufficient for application and CLI code to add context without exposing filesystem internals as a public schema.

Tests And Exit Criteria

  • Cover relative and absolute explicit destinations, configured destination precedence, all report-derived names, and an invalid default .md suffix.
  • Cover absent, empty, regular-file, symlink, dangling-symlink, filesystem-root, exact-working-directory, unreadable, recognized, and unrecognized targets.
  • Cover extra entries, missing reports, subdirectories, manifest traversal, unknown fields, bad counts, digest mismatch, stale-report removal, successful replacement, second-rename failure with restoration, and cancellation before commit. Use narrow injected filesystem hooks only where real filesystem behavior cannot deterministically reach a consequential failure path.
  • Verify a failed publication leaves the prior recognized bundle byte-for-byte intact and does not touch sibling paths.
  • Run focused tests with -race, then the repository-wide standard tests.

Stage 3: Add Ordered Multi-Profile Preflight

Goal

Make prompt and profile validation reusable for comparison without collecting weather or executing a model.

Work

  1. Extend internal/app/prompt_inspection.go with a comparison-oriented request and result that inspect one resolved prompt exactly once and selected profiles sequentially in request order.
  2. Reuse the existing credential-lookup policy and return dependency-neutral prompt hash/version plus effective backend and model for every profile.
  3. Apply the locked application-level profile validation before any executor inspection.
  4. Fail on the first ordered preflight error with prompt/profile context, while ensuring no collection or execution occurs. Do not silently fall back to config.promptkit.profile.
  5. Leave existing single and batch inspection entry points intact unless a private shared helper can remove duplication without changing behavior.

Tests And Exit Criteria

  • Assert one prompt inspection, ordered profile inspections, effective backend/model preservation, exact profile IDs, and credential failures.
  • Assert invalid lists and any prompt/profile preflight failure perform no collection or execution using explicit fakes.
  • Run GOWORK=off go test -count=1 ./internal/app and repository-wide tests.

Stage 4: Extract Immutable Shared Report Preparation

Goal

Create the prepare-once boundary required for comparison while preserving all ordinary generation behavior.

Work

  1. Extract an unexported immutable prepared-report value in internal/app with the resolved report, facts, module snapshot, briefing metadata, handler and render inputs, source warnings, and exact serialized data-package bytes.
  2. Move current collection, derivation, snapshot, metadata, prompt-input, YAML marshal, and generated-text-handler construction into one preparation function.
  3. Refactor GenerateDetailed and batch generation to use that function once per generated report without altering external requests or results.
  4. Copy mutable inputs where necessary at the boundary so concurrent consumers cannot mutate shared maps or byte slices. Treat the prepared value as read-only after construction.
  5. Do not add comparison goroutines or the CLI command in this stage.

Tests And Exit Criteria

  • Preserve all existing generation and batch tests.
  • Add focused tests showing one collector call, one deterministic serialized package, stable warnings/metadata, and isolation from caller mutation.
  • Compare ordinary generated report bytes, result fields, debug preparation, notification, and failure behavior before and after the refactor through existing contract tests.
  • Run application tests and repository-wide tests.

Stage 5: Separate Profile Execution And In-Memory Rendering From Publication

Goal

Permit several profiles to consume one prepared report without duplicating or prematurely publishing output.

Work

  1. Extract a profile-specific internal function that accepts the immutable prepared report, exact prompt inspection, effective profile inspection, and optional deterministic debug reference.
  2. Have it invoke promptexec.Executor.Execute, preserve the existing preparation callback contract, validate generated text, build render context, and render Markdown to memory. Return a dependency-neutral outcome and bytes; do not write the report or notify.
  3. Extract ordinary single-report publication into a separate function that performs the existing context check, atomic file write, and optional Distributor notification.
  4. Recompose GenerateDetailed from prepare, execute/render, and publish. Keep its debug path, validation status, output path, notification, warnings, and error wrapping unchanged.
  5. Make debug callback bookkeeping local to one execution. Remove any shared mutable workflow field that would race when the execution function is used concurrently.

Tests And Exit Criteria

  • Prove execution/rendering alone performs no filesystem publication or notification.
  • Preserve ordinary success, validation failure, rendering failure, debug callback failure, cancellation, atomic output, and notification tests.
  • Confirm one ordinary generation still prepares and executes exactly once.
  • Run application tests with -race, then repository-wide tests.

Stage 6: Build The Concurrent Ordered Profile Execution Core

Goal

Implement and test concurrency independently of destination publication and CLI assembly.

Work

  1. Add a private comparison execution coordinator in internal/app that takes one immutable prepared report, one prompt inspection, ordered profile inspections, comparison identity, optional debug writer, and one shared executor.
  2. Preallocate ordered outcomes, start one goroutine per selected profile while observing parent cancellation, and join every started goroutine. Each goroutine may write only its own result slot.
  3. Pass the exact same data-package byte content and prompt version to every call. Do not recollect, rebuild, marshal, retry, or use GenerateDetailed.
  4. Continue peers after an individual execution, validation, rendering, or debug callback failure. Convert every failure to a bounded safe structured error while retaining a wrapped internal error for aggregate diagnosis.
  5. Use requested-profile order, not completion order, for outcomes and report filenames. Generate distinct debug identities from comparison ID, ordinal, and safe slug.
  6. On parent cancellation or deadline, propagate context, join all work, and mark non-successful outcomes safely. Do not construct a publishable bundle for a cancelled coordinator.

Tests And Exit Criteria

  • Use a barrier-based fake executor to prove overlap without sleeps and to observe more than one in-flight call.
  • Assert the exact prompt version and byte-for-byte identical data package for eight to twelve profiles, deterministic result order under reversed completion, and no data races.
  • Assert one failure does not cancel peers, cancellation reaches all started calls and joins them, and debug references are distinct and deterministic.
  • Do not duplicate Promptkit's internal capacity tests. Retain adapter tests showing that Weatherreporter delegates prepared execution through one concurrency-safe engine.
  • Run GOWORK=off go test -race -count=1 ./internal/app ./internal/adapters/promptkit, then repository-wide tests.

Stage 7: Assemble Application-Level Comparison And Bundle Publication

Goal

Expose the complete application use case with safe partial publication, aggregate failure behavior, and no notification.

Work

  1. Add the locked exported application request, result, profile-result, and CompareDetailed entry point.
  2. Orchestrate in this exact order: validate request; resolve report/date and comparison identity; resolve and preflight destination; initialize optional debug writing; inspect prompt and every profile; collect and prepare once; execute profiles concurrently; capture finish time; build manifest and logical bundle; re-preflight and publish.
  3. Build data-package.yml from the exact prepared bytes and include only successful rendered reports. Derive manifest and application results from the same ordered outcome source so they cannot drift.
  4. On one or more profile failures, publish the coherent partial bundle, set ordered result errors and counts, then return the non-nil result plus a generic aggregate error such as comparison completed with N failed profiles.
  5. On cancellation, deadline, preflight, preparation, or publication failure, publish nothing new. Preserve a recognized prior bundle. Return the most complete safe result available and an error with useful operation context.
  6. Populate absolute manifest, data-package, successful-report, and debug paths only when those artifacts exist. Keep manifest paths relative basenames.
  7. Ensure no Distributor notifier is accepted, created, or invoked anywhere in this path.

Tests And Exit Criteria

  • Add assembled application tests for complete success, mixed profile failure, all-profile failure, preflight failure, collection/preparation failure, cancellation, publication failure, and authorized replacement.
  • Assert prompt inspection once, profile inspection before collection, weather collection once, serialized package reuse, completion-independent order, exact counts/digest/paths, generic aggregate errors, and no model calls before all preflight succeeds.
  • Assert complete and partial comparison never notify and that cancellation or publication failure preserves an existing bundle.
  • Run application and comparison packages with -race, then repository-wide tests.

Stage 8: Add Compare Command Parsing And Request Construction

Goal

Introduce the complete command syntax and validation while keeping execution injected and testable.

Work

  1. Extend internal/cli.Runner with an injectable comparison function, default it to app.CompareDetailed, and add a private comparison command parser and request-construction path. Do not route it from the public root switch until Stage 9 can provide the complete summary and exit contract.
  2. Implement a repeatable flag value that preserves every --profile token in exact command-line order.
  3. Parse applicable common flags plus --out-dir and --replace; reject missing/unknown reports, unsupported date use, fewer than two profiles, whitespace-only profiles, exact duplicates, unexpected positional arguments, and unsupported flags such as --out.
  4. Reuse existing configuration loading, units/timezone overrides, report/date resolution semantics, working-directory handling, and debug-root behavior.
  5. Pass explicit output override and ordered profiles to the application. Do not merge the configured default profile. Construct one Promptkit executor only after syntax/config validation and before the application call.
  6. Return the application result and error through a private CLI execution seam for Stage 9 to format. Do not add public root routing or help text in this stage, so users cannot reach a command with an incomplete output contract.

Tests And Exit Criteria

  • Table-test report/date behavior, repeated profiles and ordering, invalid lists, output precedence inputs, --replace, common overrides, and rejected flags/arguments.
  • Assert invalid syntax and configuration do not construct an executor or call the application.
  • Call the private comparison execution seam in tests and assert one valid invocation constructs one executor and one request with the exact selections and paths.
  • Run CLI tests and repository-wide tests.

Stage 9: Complete CLI Summaries, Quiet Mode, And Exit Behavior

Goal

Finish the end-to-end user-facing command contract, including coherent partial failure reporting.

Work

  1. Add comparison summary and ordered profile-summary types with the exact locked JSON field order and omitempty behavior.
  2. Add public root routing and root help text now that parsing, execution, formatting, and exit behavior can land as one complete command.
  3. Map complete, partial, cancelled, and publication-failed application results into one normal stdout object whenever a structured result exists. Use absolute returned paths and safe structured errors; do not serialize wrapped internal or provider details.
  4. Return success only for complete execution and publication. Return the application error after writing a failed structured summary so the process exits nonzero. Preserve the existing unstructured error path for failures before a result exists.
  5. Implement --quiet as output suppression only. It must not alter execution, comparison artifacts, replacement, error return, or exit status.

Tests And Exit Criteria

  • Golden or exact-structure tests cover successful, partial, all-failed, pre-execution, cancelled, and publication-failed summaries, field omission, ordered results, and safe errors.
  • CLI integration tests assert stdout/stderr discipline, quiet behavior, nonzero partial failure, default and explicit output paths, and one executor per invocation.
  • Run CLI and application tests with -race, then repository-wide tests.

Stage 10: Reconcile Canonical Documentation And Run The Final Gate

Goal

Document only the implemented behavior in its canonical owners, remove roadmap-only language from current-state documentation, and perform the full repository validation gate.

Work

  1. Update docs/cli.md with authoritative syntax, flags, profile validation, JSON summary, quiet behavior, and exit semantics.
  2. Update docs/operations.md with the prompt-comparison workflow, output destination precedence by link to docs/config.md, sensitivity guidance, flat bundle lifecycle, guarded replacement, partial results, cancellation, and cleanup expectations. Do not duplicate the complete CLI reference.
  3. Add docs/integrations/comparison-bundle.md as the canonical durable schema and layout contract. Document the exact version, JSON fields, filenames, path relativity, hashes, success/failure invariants, compatibility boundary, and sensitive-content caveat.
  4. Update docs/integrations/promptkit.md with the consumer-visible prepare-once/execute-many boundary and Promptkit-owned backend capacity, linking upstream rather than duplicating its concurrency reference.
  5. Add or update focused docs/internal/ documents for application comparison orchestration, prepared report flow, concurrent execution, prompt debugging, and bundle publication mechanics. Link to canonical CLI, configuration, operations, integration, and architecture owners instead of restating them.
  6. Update docs/policy/architecture.md for the implemented stateless, concurrency, notification, and publication invariants; update docs/development.md for new package/file routing; update docs/policy/documentation.md only if the existing ownership table no longer assigns every introduced topic correctly.
  7. Check README and other current-state documents for navigation or stale claims. Add only short orientation and canonical links where useful.
  8. Mark the feature roadmap Implemented; retained temporarily for post-implementation review and this plan Complete. Retain both until the requested implementation review moves durable facts to their owners and the maintainer authorizes roadmap cleanup.

Tests And Exit Criteria

  • Verify all changed relative links and fenced examples, and search for stale claims that comparison is unavailable or planned.

  • Confirm current-state docs do not duplicate full command, configuration, schema, or implementation contracts outside their canonical owners.

  • Run the release-equivalent local gate:

    set -eu
    test -z "$(git ls-files go.work go.work.sum)"
    test ! -e vendor
    if grep -Eq '^[[:space:]]*replace([[:space:]]|\()' go.mod; then
      printf '%s\n' 'go.mod contains a replacement' >&2
      exit 1
    fi
    GOWORK=off go test -count=1 ./...
    GOWORK=off go test -race -count=1 ./...
    GOWORK=off go vet ./...
    GOWORK=off go build ./...
    GOWORK=off go mod tidy -diff
    unformatted="$(git ls-files '*.go' | while IFS= read -r file; do gofmt -l "$file"; done)"
    test -z "$unformatted"
    git diff --check
    
  • Run GOWORK=off go run ./cmd/weatherreporter --help; confirm output matches docs/cli.md without requiring credentials or network access.

  • Inspect the final diff for accidental generated artifacts, secrets, workspaces, vendored dependencies, release notes, or unrelated changes.

Open Questions

None. The accepted feature roadmap and the locked contracts in this plan define the decisions required to implement all ten stages.