Refactor plan =============== This plan groups the next xcookie cleanup work into chunks that are safe to review and land independently. The current code does not promise a stable Python public API, so these chunks lean toward cleaner internal names, intermediate models, and provider-neutral planning objects rather than compatibility wrappers. Chunk 0: generated-output safety net ------------------------------------ Add invariant tests before deep rewrites. These tests should exercise generated GitHub Actions, GitLab CI, and staged template files without waiting for hosted CI failures. * Parse generated YAML where possible. * Assert no wheel install target contains empty extras such as ``[]``. * Assert CI install extras are declared by the target project. * Assert shell-facing generated paths use POSIX slashes. * Assert GitHub and GitLab agree on shared policy: extras, strict/loose variants, wheelhouse names, and artifact names. * Add Windows-path simulation tests that run on any platform. Chunk 1: typed template registry and staging layer -------------------------------------------------- Replace raw template dictionaries and regex-style substitutions with explicit models. * Add ``TemplateInfo`` for template registry entries. * Add ``TemplateContext`` for replacement values. * Normalize raw registry dictionaries as soon as the registry is built. * Route staging through typed attributes instead of ad-hoc dictionary keys. * Replace regex-backed token substitution with simple ``str.replace`` over an explicit replacement map. * Normalize path substitutions that are rendered into portable shell/YAML text. This chunk reduces Windows escape bugs and makes template staging easier to inspect and type-check. Chunk 2: raw config versus resolved config ------------------------------------------ Separate CLI / pyproject inputs from inferred values consumed by builders. * Keep ``XCookieConfig`` as the user-facing scriptconfig object. * Add ``ResolvedXCookieConfig`` as a deterministic, builder-facing view. * Move tag normalization, OS normalization, repo/module/package name inference, author inference, Python-version expansion, and ``use_uv`` inference into the resolver. * Keep compatibility write-back only while older builders still read ``config[...]`` directly. Chunk 3: shared CI plan model ----------------------------- Prevent GitHub/GitLab drift by making both providers render from a common plan. Initial implementation status: * ``xcookie.builders.ci_plan.CIPlan`` owns provider-neutral CI policy for optional-dependency discovery, test variants, typecheck extras, and sdist test extras. * ``TestVariant`` records strict/loose mode, minimal/full scope, install extras, and the pyproject/uv resolution policy used by provider matrices. * GitHub Actions and GitLab CI both consume the shared plan for wheel-test variant extras instead of duplicating the same filtering policy. * ``common_ci`` keeps compatibility wrappers for older builder call sites while forwarding optional-dependency filtering and install-target formatting to the plan module. Remaining follow-up work for this chunk: * Add artifact and deployment plan records. * Move provider-specific matrix expansion details into dedicated renderer helper methods once the shared policy layer is stable. * Continue adding generated-output invariant tests around the plan so GitHub and GitLab cannot drift again. Chunk 4: thin provider renderers -------------------------------- After ``CIPlan`` exists, make GitHub and GitLab builders mostly syntax renderers. Initial implementation status: * ``GitHubActionsRenderer`` owns test/release workflow rendering and receives a single ``CIPlan`` for the whole workflow. * ``GitLabCIRenderer`` owns provider selection and threads the same ``CIPlan`` into pure-Python and binary GitLab render paths. * Module-level builder functions remain as thin wrappers around the renderer classes while existing template call sites are migrated. * Leaf job builders accept an optional plan, so tests and higher-level renderers can avoid reconstructing CI policy in multiple places. Remaining follow-up work for this chunk: * Move more provider-specific matrix expansion into renderer methods with small typed helper records. * Move deployment and artifact policy into the shared plan once those records exist. * Expand generated-output invariant tests so GitHub/GitLab renderers are compared on plan-derived content rather than exact full YAML text. Chunk 5: secret rotation plan ----------------------------- Make secret rotation testable without fake queues and shell inspection. * Add ``SecretPlan`` for provider, GPG transport, trusted publishing, setup command, GPG upload command, repo-secret upload command, and explanatory notes. * Test plan generation directly. * Keep ``cmd_queue`` and subprocess execution in a narrow execution layer. Chunk 6: generated CI helper commands ------------------------------------- Move fragile inline shell and ``python -c`` snippets into normal Python helpers. * Add ``python -m xcookie.ci_helpers`` commands for artifact discovery, install-target construction, module-path resolution, and artifact validation. * Unit-test the helpers directly. * Make generated CI call helpers instead of embedding complex quoting logic. Chunk 7: split ``TemplateApplier`` into focused modules ------------------------------------------------------- Make ``xcookie/main.py`` a small CLI / compatibility entrypoint. Suggested layout:: xcookie/config.py XCookieConfig and config loading xcookie/resolved_config.py ResolvedXCookieConfig xcookie/applier.py TemplateApplier orchestration xcookie/template_registry.py TemplateInfo and TemplateContext xcookie/staging.py staging, rendering, directives xcookie/secrets.py SecretPlan and rotation execution xcookie/ci_helpers.py generated CI helper commands xcookie/builders/ci_plan.py CIPlan and builder Chunk 8: inline typing pass --------------------------- Once the structure settles, add inline annotations throughout the refactored internals. * Use ``from __future__ import annotations`` in touched files. * Prefer dataclasses over loose dictionaries. * Use ``Literal`` for provider names, dependency modes, artifact kinds, and GPG transports. * Remove stub files or compatibility annotations that are no longer useful. Chunk 9: remove legacy paths ---------------------------- After the new paths are stable, delete compatibility and duplication. * Remove provider-specific extra filtering that moved into ``CIPlan``. * Remove regex substitution helpers from staging. * Remove shell-inspection test doubles made obsolete by ``SecretPlan``. * Clean up stale comments describing legacy behavior.