Files
promptkit/docs/roadmap/implementation.md

32 KiB

External Backend Catalogs Implementation Plan

Purpose

This document is the ordered implementation plan for the target state in external-backend-catalogs.md. It is written for a coding agent implementing one stage per prompt, in order. The feature roadmap owns the intended end state, compatibility policy, migration safety requirements, and non-goals; this document owns sequencing and concrete implementation decisions.

The work spans these three sibling repositories:

  • /home/eric/Workspace/promptkit with module path gitea.maximumdirect.net/eric/promptkit;
  • /home/eric/Workspace/promptkit-backend-openrouter with module path gitea.maximumdirect.net/eric/promptkit-backend-openrouter; and
  • /home/eric/Workspace/promptkit-backend-rakestrawhome with module path gitea.maximumdirect.net/eric/promptkit-backend-rakestrawhome.

Cross-Stage Constraints

Apply these constraints throughout every stage:

  • Read and follow each repository's AGENTS.md and contributor documentation before changing it. In Promptkit, always follow docs/development.md, docs/policy/architecture.md, docs/policy/documentation.md, and docs/policy/testing.md.
  • Implement exactly one stage per prompt. Start a stage only after every prior stage's completion criteria are satisfied and committed. Preserve unrelated work in all three worktrees and commit only the stage's intended files.
  • Keep Promptkit an importable Go library. Do not add a command, service, runtime catalog download, global registration, mutable registry, or public Promptkit catalog API.
  • Keep BackendOpenRouter and BackendRakestrawHome, their exact values, and all current consumer construction and source-precedence behavior unchanged.
  • Keep the external modules data-only. Their non-test package code may import only embed and io/fs; it must not import Promptkit, read environment variables, perform network calls, or expose mutable assets.
  • Use no committed go.work, go.work.sum, vendor tree, or replace directive. Temporary local workspaces may be used only for exploratory development and must be disabled for acceptance. Every dependency and release check must succeed with GOWORK=off through ordinary module resolution.
  • Keep all default tests deterministic, offline, and credential-free. Remote publication and module-resolution checks are release gates, not test-suite behavior.
  • Treat copying the Promptkit-maintained catalog assets into the LGPL-3.0 external repositories as an intentional copyright-holder relicensing decision. Preserve source provenance and record that decision in both catalog READMEs; do not imply that an ordinary dependency extraction alone changes an asset's license.
  • Update the canonical current-state documentation in the same commit that introduces or changes the behavior it describes. In particular, do not defer an implemented-package inventory or source-boundary update to a later stage merely because the feature has not reached runtime cutover.
  • Treat catalog content and compatibility as high-risk data-integrity work. Test strict decoding, duplicates, source ownership, immutable copies, precedence, and exact pre-extraction parity at their narrowest owners; do not repeat the same malformed-input matrix through the public facade.
  • Never include catalog file contents, extra-parameter values, environment values, or candidate secret values in new diagnostics. Errors may identify the catalog display name and repository-relative asset path.
  • Do not publish or move a tag that already exists. If a planned external module tag exists locally or remotely, verify that it identifies the exact intended release commit; otherwise stop and report the conflict.
  • Do not create a Promptkit release tag as part of these stages. The external modules must be released because Promptkit needs resolvable versions, but Promptkit release publication remains governed by docs/release.md after the feature is accepted.

Shared Catalog Asset Contract

Stages 2 through 6 must use this exact contract in both external modules and Promptkit's private adapter:

  • The module root package is named openrouter or rakestrawhome, matching the backend. It exports only:

    const Root = "catalog"
    func FS() fs.FS
    

    FS returns the package's embedded filesystem as an fs.FS; callers cannot replace or mutate the embedded value. Both declarations require accurate GoDoc.

  • The embedded tree contains catalog/backend.json and one or more profile files below catalog/profiles/. Directories are allowed below profiles; every nondirectory entry there must be a regular .yml file. No other file, symlink, or special entry is part of the embedded tree.

  • backend.json is one strict JSON object with these required fields and no others:

    {
      "schema_version": 1,
      "id": "openrouter",
      "endpoint": "https://openrouter.ai/api/v1",
      "api_key_env": "OPENROUTER_API_KEY",
      "concurrency_limit": 16,
      "queue_capacity": 1024,
      "extra_params": null
    }
    

    The Rakestrawhome manifest changes id to rakestrawhome, endpoint to https://inference.ai.rakestrawhome.com/v1, api_key_env to RAKESTRAWHOME_INFERENCE_API_KEY, and concurrency_limit to 4; it keeps schema_version: 1, queue_capacity: 1024, and extra_params: null. extra_params may be a JSON object in later compatible catalog releases, but it remains null for the compatibility baseline.

  • Schema version 1 requires an integer version, a nonblank ID, endpoint, and environment-variable name, a positive integer concurrency limit, a nonnegative integer queue capacity whose sum with the concurrency limit fits in int, and extra_params equal to null or an object. Promptkit also applies all existing endpoint, environment-name, reserved-field, and bounded JSON-value validation.

  • OpenRouter owns every existing built-in profile except rakestrawhome-gemma-4-31b; Rakestrawhome owns exactly that profile at the initial release. Copy the YAML bytes without changing IDs or behavior.

  • Each catalog is self-contained: every base_profile must resolve inside the same module. A locally resolved profile must select the manifest's backend ID. Raw catalog profiles must not provide endpoint, api_key_env, or a raw API key; connection and credential-source metadata comes from the manifest.

  • Strict source decoding and the bounded JSON-value rules are the primary secret-safety controls. In addition, recursively reject case-insensitive catalog extra_params keys named api_key, apikey, authorization, credential, credentials, password, secret, token, or access_token. api_key_env is the sole permitted credential-related catalog field. Do not use value-pattern heuristics as a substitute for structural validation; retain Promptkit's repository credential scan as a separate acceptance check.

  • Both external modules start at v1.0.0. The two-function/constant asset surface and manifest schema are intentionally stable; normal profile-data changes use later semantic versions according to each module's compatibility policy.

Stage 1: Freeze The Pre-Extraction Compatibility Baseline

Status: Complete

Repository

/home/eric/Workspace/promptkit

Objective

Record the complete current built-in catalog before any data is copied or any runtime assembly changes. This stage must leave Promptkit using its existing hard-coded backend definitions and embedded profile repository.

Implementation

  1. Add testdata/builtin-catalog-v1.json as a reviewable, permanently frozen compatibility fixture. Represent both normalized backend definitions and every raw built-in profile. Include all fields whose zero, empty, nil, or explicit state affects behavior, including queue_capacity_set, base_profile, backend, endpoint, model, execution settings, api_key_env, api_key_required, and extra_params. Sort backends by ID and profiles by ID so diffs are deterministic.
  2. Add a focused compatibility test under internal/profile/builtin that reads the fixture, obtains normalized built-ins from internal/backend.Registry, discovers every current embedded profile, and compares complete semantic values. Compare both directions so a missing or extra backend/profile fails. Reuse the package's existing asset discovery and profile repository rather than adding production enumeration APIs in this stage.
  3. Do not provide an automatic golden-file update path. The snapshot describes the migration baseline and must change only by deliberate review of the fixture itself.

Tests And Validation

  1. Run the focused internal/backend and internal/profile/builtin tests.
  2. Run go test ./... and go test -race ./....
  3. Inspect the fixture diff against the current YAML and backend constants, confirm that it contains no credential value, and run the Promptkit credential scan from the development guide.

Completion Criteria

  • One deterministic fixture accounts for both current backends and every current raw built-in profile.
  • The fixture test fails for added, removed, or semantically changed catalog data.
  • Promptkit runtime construction and production code are unchanged.

Stage 2: Build And Release The OpenRouter Catalog Module

Status: Pending

Repository

/home/eric/Workspace/promptkit-backend-openrouter

Objective

Create the independently testable OpenRouter data module, publish its source commit, and release immutable tag v1.0.0 before Promptkit depends on it.

Implementation

  1. Initialize go.mod with module path gitea.maximumdirect.net/eric/promptkit-backend-openrouter and Go version 1.25.5. Name the root package openrouter.
  2. Add catalog/backend.json using the exact OpenRouter manifest from the shared contract. Copy every current OpenRouter-owned YAML asset from promptkit/internal/profile/builtin/assets/ into catalog/profiles/<provider>/ without editing its bytes. Record in the README that the Promptkit-maintained source assets are intentionally being distributed under this repository's LGPL-3.0 terms with authorization from their copyright holder, and identify Promptkit as their source provenance.
  3. Add the private embedded filesystem and the exact Root/FS public surface. Embed only catalog, return the embedded filesystem by value behind fs.FS, and add package GoDoc explaining that the module supplies immutable Promptkit catalog assets rather than runtime provider behavior.
  4. Replace the placeholder README with concise ownership, consumption, compatibility, and validation guidance. State that Promptkit owns parsing, runtime behavior, credentials, and consumer documentation; catalog releases own OpenRouter manifest/profile data. Document the roadmap's ID stability, additive profile, correction, deprecation/removal, and manifest-schema compatibility policy.
  5. Add docs/release.md with a source-only semantic-tag procedure. Require a clean synchronized main, no workspace/replacement/vendor tree, complete validation, an annotated tag, publication of only the selected tag, remote tag verification, and resolution from a temporary module with GOWORK=off.
  6. Add focused tests that use the exported FS and Root and verify the exact embedded layout, strict manifest shape and owner ID, at least one profile, unique trimmed profile IDs, backend selection equal to openrouter, no connection fields or raw API key in profiles, and no forbidden secret key in nested extra parameters. A test-only gopkg.in/yaml.v3 dependency is permitted for robust YAML-node inspection; confirm that the non-test root package's dependency graph remains standard-library-only.
  7. Run go mod tidy, commit the module with a short plain-English message, push main, create annotated tag v1.0.0, push only that tag, and perform the documented remote and temporary-module resolution checks. Never use a local replacement to satisfy the resolution check.

Tests And Validation

  • Run go test ./..., go test -race ./..., go vet ./..., and go build ./... with GOWORK=off.
  • Require gofmt -l to report no tracked Go files and run git diff --check before committing.
  • Confirm GOWORK=off go list -deps . contains no non-standard-library runtime package and scan tracked content for credentials.
  • After publication, resolve gitea.maximumdirect.net/eric/promptkit-backend-openrouter@v1.0.0 from a temporary module and verify the returned version.

Completion Criteria

  • The OpenRouter module contains its manifest and the complete copied profile set behind the exact immutable asset API.
  • Its validation is offline and its runtime package is standard-library-only.
  • main and annotated tag v1.0.0 are published and independently resolvable.

Stage 3: Build And Release The Rakestrawhome Catalog Module

Status: Pending

Repository

/home/eric/Workspace/promptkit-backend-rakestrawhome

Objective

Create and publish the matching Rakestrawhome data module without coupling its release history or package implementation to the OpenRouter module.

Implementation

  1. Repeat the module, package, immutable asset API, package documentation, README policy, and docs/release.md structure from Stage 2, using module path gitea.maximumdirect.net/eric/promptkit-backend-rakestrawhome, package name rakestrawhome, and Go version 1.25.5.
  2. Add the exact Rakestrawhome manifest from the shared contract. Copy only google/rakestrawhome-gemma-4-31b.yml into catalog/profiles/google/, without editing its bytes. Apply the same explicit LGPL-3.0 relicensing and Promptkit source-provenance statement as Stage 2.
  3. Apply the same focused asset tests, changing the expected backend to rakestrawhome and the initial profile set to the single owned profile. Keep the module independent: do not import or share code with either Promptkit or the OpenRouter module.
  4. Run the same validation and release sequence as Stage 2, commit and push main, publish annotated tag v1.0.0, and verify ordinary module resolution without a workspace or replacement.

Tests And Validation

Use the complete Stage 2 validation list with the Rakestrawhome module path. Inspect the copied profile against Promptkit's Stage 1 fixture before release.

Completion Criteria

  • The Rakestrawhome module contains exactly its owned backend and profile data behind the same stable asset contract.
  • Its tests, build, runtime dependency check, credential scan, and remote resolution all pass.
  • main and annotated tag v1.0.0 are published and independently resolvable.

Stage 4: Add Eager Immutable Profile Loading To Promptkit

Status: Pending

Repository

/home/eric/Workspace/promptkit

Objective

Add the reusable profile-package primitive needed to validate an immutable external catalog completely at engine construction, without changing the lazy point-in-time semantics of consumer-configured profile sources.

Implementation

  1. In internal/profile, add:

    type LoadedProfileMetadata struct {
        ID             string
        Path           string
        ExplicitFields []string
    }
    
    func LoadFSRepository(
        ctx context.Context,
        fsys fs.FS,
        root string,
    ) (Repository, []LoadedProfileMetadata, error)
    

    It must discover sorted YAML paths through internal/filecatalog, read each file once, require exactly one document, strictly decode the existing profile schema, reject raw API keys, normalize and validate each raw definition through the existing owners, and reject duplicate trimmed IDs. Return an immutable in-memory raw repository plus a newly allocated metadata slice sorted by normalized profile ID. Each metadata entry contains that ID, the safe root-relative source path, and a newly allocated sorted list of the exact top-level YAML field names present in the source document. An empty source returns an empty repository and metadata slice; the catalog adapter, not this generic primitive, decides whether emptiness is invalid. All three fields, the type, and the function are internal to the Promptkit module but require accurate GoDoc because they cross internal package boundaries.

  2. Refactor existing private decode/metadata logic only as needed so eager and point lookup share strict decoding, source-path context, raw-key rejection, normalization, and defensive JSON-value copying. Do not change NewFSRepository: configured and fallback consumer sources must retain fresh point-in-time reads and their current error-preserving fallback semantics. Derive ExplicitFields during the same source read and through the existing YAML-node metadata path; do not make the later catalog adapter reread or independently parse profile YAML.

  3. The returned repository must honor context cancellation before lookup, return ErrProfileNotFound for absence, and publish a fresh profile value with a deeply copied ExtraParams tree on every successful lookup. Do not pre-resolve base_profile; the raw repository must remain suitable for the existing outer resolving repository and consumer shadowing rules.

  4. Update docs/internal/sources.md in the same commit to describe the eager immutable loader as an implemented internal profile boundary. Make clear that configured consumer sources remain lazy and that engine assembly still uses the existing embedded built-ins at this stage.

Tests And Validation

  1. Add focused profile-package tests for sorted metadata, exact explicit-field presence including explicitly empty values, safe relative paths, strict malformed-input rejection, duplicate IDs, raw API-key rejection, empty input, cancellation, and defensive copies of metadata and profile values. Reuse representative existing fixtures and avoid repeating the entire profile rule matrix already owned by point lookup.
  2. Add a parity test showing that eager and ordinary FS repositories publish the same raw semantic value for representative standalone and derived profiles.
  3. Run go test ./internal/profile, go test -race ./internal/profile, and go test ./....

Completion Criteria

  • Promptkit can eagerly load and validate all raw profiles from an fs.FS without creating a second YAML contract implementation.
  • Returned metadata and profile values are caller-independent, and the metadata preserves the distinction between an absent field and an explicitly empty field.
  • Existing configured, fallback, in-memory, and built-in runtime behavior is unchanged.
  • The internal source document accurately distinguishes eager immutable loads from existing point-in-time consumer source lookup.

Stage 5: Add And Verify The Private Catalog Adapter

Status: Pending

Repository

/home/eric/Workspace/promptkit

Objective

Import the two published module versions and validate them through one private Promptkit adapter while retaining the existing embedded/hard-coded runtime source as the active implementation.

Implementation

  1. Add direct requirements at v1.0.0 for both external module paths and run go mod tidy with GOWORK=off. Do not add a replacement or workspace.

  2. Add internal/catalog with this internal contract:

    type Source struct {
        Name              string
        ExpectedBackendID string
        FS                fs.FS
        Root              string
    }
    
    type Set struct {
        Backends []domain.Backend
        Profiles profile.Repository
    }
    
    func Load(sources ...Source) (Set, error)
    

    Name is a safe maintainer-facing label used in errors; reject blank or duplicate names. Require nonnil filesystems, valid non-root asset roots, nonblank expected IDs, and at least one source. Return caller-independent backend values and one raw immutable composite profile repository.

  3. Strictly enforce the shared asset layout and manifest schema. Use json.Decoder.DisallowUnknownFields, require exactly one JSON value, use presence-aware raw fields so missing required fields differ from zero or null, reject unsupported schema versions, and check the manifest ID against ExpectedBackendID. When constructing domain.Backend, set QueueCapacitySet to true because schema version 1 requires an explicit queue_capacity; this ensures normalization preserves later compatible releases that intentionally select a non-default capacity.

  4. Rename the existing private backend normalizer to the internal exported backend.NormalizeDefinition and have both Registry and the catalog adapter call it. This remains inside Go's internal boundary and is not a Promptkit public API. Do not duplicate endpoint, environment-name, capacity, reserved-field, or bounded JSON-value policy in the adapter.

  5. For each source, call profile.LoadFSRepository on <root>/profiles. Reject an empty profile set. Use the returned explicit-field metadata to reject endpoint or api_key_env whenever the key is present, including when its YAML value is explicitly empty; do not infer source presence from the decoded profile's zero values. Validate every raw profile's nested extra parameters for forbidden secret keys. Resolve every metadata ID through a source-local profile.NewResolvingRepository; this both proves inheritance is self-contained and verifies that the final backend ID equals the manifest owner.

  6. Reject duplicate backend IDs and duplicate raw profile IDs across sources. Compose the already validated raw repositories in source order only after duplicate checks pass. Do not pre-resolve the returned composite: the root engine must later place consumer sources above it and apply one outer resolver to preserve base-profile shadowing semantics.

  7. Keep errors bounded and redacted. Wrap failures with the safe source name and relative path where available, but never include raw JSON/YAML values or extra-parameter content.

  8. Add an integration test that imports the released openrouter and rakestrawhome packages, supplies their FS()/Root values with expected backend IDs, loads the set, and compares it exactly with testdata/builtin-catalog-v1.json. Keep the Stage 1 test against the old source too; at this point both tests must pass while only the old source participates in engine construction.

  9. Update docs/policy/architecture.md, docs/internal/overview.md, and docs/internal/sources.md in the same commit. Describe internal/catalog as an implemented private validation/adapter boundary and the external modules as imported immutable test-verified sources, while stating accurately that root engine assembly still uses the original built-in runtime source until cutover.

Tests And Validation

  1. At internal/catalog, use testing/fstest.MapFS tables for nil/invalid sources, layout violations, strict/trailing/missing/unsupported manifests, backend normalization failures, empty profiles, malformed profiles, cross-source duplicates, missing/cyclic/cross-catalog bases, owner mismatch, prohibited connection fields, secret-like nested keys, defensive copies, and redacted source-aware errors.
  2. Keep exhaustive YAML rules in internal/profile and backend invariants in internal/backend; adapter tests must prove delegation and assembly, not duplicate those packages' full matrices.
  3. Run focused tests for internal/profile, internal/backend, and internal/catalog, followed by go test ./..., go test -race ./..., go vet ./..., and go build ./....
  4. With GOWORK=off, verify go list -m all selects exactly v1.0.0 for both catalogs and go mod verify succeeds.

Completion Criteria

  • Both released external catalogs load through one strict private adapter and exactly match the frozen baseline.
  • Every maintained profile is eagerly validated locally and across the assembled set before publication.
  • Promptkit still executes exclusively from its original built-in data source.
  • The current-state architecture and internal inventory describe this transitional implemented boundary without claiming that cutover is complete.

Stage 6: Cut Promptkit Over To The External Catalogs

Status: Pending

Repository

/home/eric/Workspace/promptkit

Objective

Make the validated external modules Promptkit's sole runtime owners for the maintained backends and profiles, then remove every duplicate production copy from Promptkit in the same committed cutover.

Implementation

  1. In root engine assembly, import the two external packages with unambiguous aliases and construct catalog.Source values in deterministic OpenRouter, Rakestrawhome order. Use safe display names plus backend.OpenRouterID/backend.RakestrawHomeID as the expected IDs.
  2. Load the maintained catalog.Set during NewEngine after options and the required prompt source have been validated, but before constructing the backend registry, capacity manager, or runner. Map any load failure to ErrInvalidConfig with the prefix failed to load maintained catalogs and preserve redacted catalog/path context. Add no new public error identity.
  3. Change backend.NewRegistry to accept maintained definitions and consumer additions as separate slices. Normalize and copy both through the same path, insert maintained definitions first, and reject every duplicate across or within the two groups. This preserves the rule that a consumer cannot replace a maintained ID without retaining hard-coded maintained definitions inside the registry package.
  4. Change newProfileRepository to accept the maintained raw repository as its lowest-precedence source. Preserve this exact overlay order: in-memory, ordinary configured file/FS/directory, application fallback, maintained external catalog. Continue to wrap the complete raw overlay in exactly one profile.NewResolvingRepository.
  5. Delete internal/profile/builtin, including its embedded YAML assets, and delete builtInBackends plus the OpenRouter/Rakestrawhome endpoint, environment, and concurrency constants from internal/backend. Retain the internal backend ID constants because the root public constants still alias them, and retain the generic default queue-capacity policy used for consumer registrations.
  6. Move the Stage 1 compatibility assertion to internal/catalog and make it compare only the external loaded set to the frozen fixture. Remove tests whose sole purpose was the deleted duplicate source; retain or relocate distinct compatibility, reserved-ID, profile completeness, and native Rakestrawhome behavior coverage.
  7. Update registry and engine tests for explicit maintained definitions. Add only focused assembled-engine coverage needed to prove that ordinary construction includes both catalogs, consumer backend IDs cannot replace either maintained ID, consumer profiles still override catalog profiles, inherited base lookup still observes the complete precedence chain, and capacity/credential/endpoint behavior matches the compatibility fixture. Reuse existing public contract tests wherever they already protect these outcomes.
  8. Confirm with repository search and go list -deps that no production Go file embeds the old assets or hard-codes the extracted endpoints, environment-variable names, concurrency values, or profile model catalog. The frozen test fixture and canonical consumer documentation are the only permitted Promptkit copies of compatibility data.
  9. Update docs/policy/architecture.md, docs/internal/overview.md, and docs/internal/sources.md in the cutover commit: remove the transitional old-runtime description, remove internal/profile/builtin, and describe explicit root assembly plus eager maintained-catalog validation and the unchanged outer consumer overlay/resolution boundary.
  10. Update docs/formats.md where its current wording says the catalog is embedded or hard-coded. It remains the canonical consumer owner of the built-ins supplied by a Promptkit release, so retain the exact backend and profile tables after confirming them against the compatibility fixture. Do not duplicate pinned module versions there; go.mod owns them.

Tests And Validation

  • Run focused backend, catalog, profile, root engine, capacity, and public contract tests.
  • Run the complete Promptkit maintainer workflow from docs/development.md, including ordinary/race tests, vet, build, both offline examples, Go formatting, Markdown links, workspace/replacement/vendor guards, whitespace checks, ignored-file review, credential scan, and full diff/status inspection.
  • Run the complete workflow with GOWORK=off and no provider credentials.

Completion Criteria

  • Every new engine validates and uses both external catalogs automatically.
  • Public constants, effective backend/profile values, source precedence, inheritance, capacity, credentials, and consumer additions match the frozen baseline and existing contracts.
  • Promptkit has one runtime data owner: no embedded profile catalog or hard-coded extracted backend definition remains.

Stage 7: Update Current-State And Release Documentation

Status: Pending

Repository

/home/eric/Workspace/promptkit

Objective

Make durable documentation describe the implemented dependency boundary, provide concise consumer-facing release guidance, and perform final acceptance across all three clean repositories.

Implementation

  1. Review the current-state changes made in Stages 4 through 6 against the final implementation. Correct any stale transitional language and ensure docs/policy/architecture.md, docs/internal/overview.md, docs/internal/sources.md, and docs/formats.md link to canonical owners instead of duplicating the manifest or complete catalog.
  2. Update docs/development.md with a task-specific reading-guide row for external catalog or maintained built-in changes. Route contributors to the internal source document, the format reference, testing policy, both module repositories, and each module's release procedure.
  3. Do not add a versionless supplemental release document during feature implementation. Record for the later Promptkit release-preparation pass that its versioned docs/releases/vMAJOR.MINOR.PATCH.md document should state that the release adds two independently versioned data dependencies, preserves the public API and configuration, requires no consumer migration, and guarantees only the catalog versions selected and tested by that Promptkit release. That document must link to canonical current-state documentation rather than restating its contracts.
  4. Update each external repository README only if the final implemented paths or links changed during integration. Do not turn either README into a parallel Promptkit consumer manual.
  5. Mark every stage in this plan complete only after its committed state and validation evidence exist. Leave Promptkit release tagging to the normal release procedure; the future annotated Promptkit tag message must include the dependency-boundary summary and the absence of consumer migration.

Tests And Validation

  1. Run the full Promptkit maintainer workflow, including the local Markdown link validator, with GOWORK=off.
  2. In each external module, run its full test/race/vet/build, formatting, hygiene, credential, and ordinary module-resolution checks.
  3. From a temporary module, download both external v1.0.0 versions and the current Promptkit commit's module dependencies without a workspace or replacement. Promptkit itself need not be tagged in this stage.
  4. Inspect all three worktrees and their committed diffs. Require each to be clean and confirm that Promptkit's go.mod/go.sum identify the published catalog versions.

Completion Criteria

  • Durable current-state documents accurately describe the external asset boundary without duplicating implementation-plan detail.
  • The later versioned release-document requirements are explicit without creating a release note before a Promptkit version has been selected.
  • All completion criteria in the feature roadmap hold, all three repositories are clean, and the complete offline validation passes without credentials, a workspace, a replacement, or provider network access.

Open Questions

None. The implementation decisions required by this roadmap are fixed above.