# LLM Profile Comparison Implementation Plan Status: Complete. ## Purpose And Authority This document is the ordered implementation plan for the accepted [LLM Profile Comparison Roadmap](profile-comparison.md). 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: ```go 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: ```go 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: ```text comparison_ ``` 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: ```text -.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: ```text 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: ```text 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: ```text 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: ```text _- ``` 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: ```text 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: ```text 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: ```sh 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.