repomatic.init_project module

Bundled data files, configuration templates, and repository initialization.

Provides a unified interface for accessing bundled data files from repomatic/data/ and orchestrates repository bootstrapping via repomatic init.

Every component repomatic init accepts is declared in COMPONENTS, which carries each one’s description, default scope and target paths; repomatic init --help lists them. That tuple is the only roster: a list repeated here would silently fall behind it.

Selectors use the same component[/file] syntax as the exclude config option in [tool.repomatic]. Qualified entries like skills/repomatic-topics select a single file within a component.

repomatic.init_project.RUNTIME_FRAGMENTS: tuple[str, ...] = ('release.yaml', 'vt-trend-chart.js')

Bundled files loaded by repomatic at runtime, not deployed verbatim.

These files live in repomatic/data/ so they ship in the wheel and are discoverable via get_data_content(), but repomatic init never copies them as-is. release.yaml is the canonical caller repomatic.github.workflow_sync reads to assemble each downstream release.yaml, copying its jobs and rewriting the local uses: refs (see _generate_release_caller); the deployed release.yaml is generated, not this bundled copy. vt-trend-chart.js is the detections-chart script repomatic.release.binaries_page.render_chart_section splices into docs/binaries.md with its payload placeholders filled. New entries must be added explicitly so the data-file registry tests stay authoritative.

repomatic.init_project.EXPORTABLE_FILES: dict[str, str | None] = {'_release-engine.yaml': '.github/workflows/release.yaml', 'action-publish-pypi.yaml': '.github/actions/publish-pypi/action.yaml', 'actionlint.yaml': None, 'agent-grunt-qa.md': '.claude/agents/grunt-qa.md', 'agent-qa-engineer.md': '.claude/agents/qa-engineer.md', 'agent-sphinx-docs.md': '.claude/agents/sphinx-docs.md', 'autofix.yaml': '.github/workflows/autofix.yaml', 'autolock.yaml': '.github/workflows/autolock.yaml', 'bumpversion.toml': None, 'cancel-runs.yaml': '.github/workflows/cancel-runs.yaml', 'changelog.yaml': '.github/workflows/changelog.yaml', 'coverage.toml': None, 'debug.yaml': '.github/workflows/debug.yaml', 'docs.yaml': '.github/workflows/docs.yaml', 'labels.toml': 'labels.toml', 'labels.yaml': '.github/workflows/labels.yaml', 'lint.yaml': '.github/workflows/lint.yaml', 'lychee.toml': None, 'mdformat.toml': None, 'metrics.yaml': '.github/workflows/metrics.yaml', 'mypy.toml': None, 'pytest.toml': None, 'release.yaml': None, 'ruff.toml': None, 'tests.yaml': '.github/workflows/tests.yaml', 'typos.toml': None, 'unsubscribe.yaml': '.github/workflows/unsubscribe.yaml', 'uv.toml': None, 'vt-trend-chart.js': None, 'yamllint.yaml': None, 'zizmor.yaml': None}

Registry of all exportable files: maps filename to default output path.

None means the file is bundled but not directly written to a target path by repomatic init (used for pyproject.toml templates that need merging, tool-runner default configs, and runtime fragments).

repomatic.init_project.export_content(filename)[source]

Get the content of any exportable bundled file.

Parameters:

filename (str) – The filename (like “ruff.toml” or “release.yaml”).

Return type:

str

Returns:

Content of the file as a string.

Raises:
repomatic.init_project.init_config(config_type, pyproject_path=None)[source]

Initialize a configuration by merging it into pyproject.toml.

Reads the pyproject.toml file, checks if the tool section already exists, and if not, inserts the bundled template at the appropriate location.

The template is stored in native format (without [tool.X] prefix) and is parsed by tomlrt and added under the [tool] table.

Parameters:
  • config_type (str) – The configuration type (like "ruff" or "bumpversion").

  • pyproject_path (Path | None) – Path to pyproject.toml. Defaults to ./pyproject.toml.

Return type:

str | None

Returns:

The modified pyproject.toml content, or None if no changes needed.

Raises:

ValueError – If the config type is not supported.

repomatic.init_project.default_version_pin()[source]

Derive the default version pin from __version__.

Strips any .dev0 suffix and prefixes with v. For example, "5.10.0.dev0" becomes "v5.10.0".

Return type:

str

repomatic.init_project.resolve_default_pin(config, *, repo='kdeldycke/repomatic', today=None, warnings=None, floor=None)[source]

Resolve the upstream pin, holding a fresh release back by cooldown.

Returns the (version, commit_sha) init stamps into thin-caller uses: refs. In the common case, and on any datasource failure, this is the running repomatic version paired with its build-time SHA. Only when adopting a release still inside the [tool.repomatic] minimum-release-age window does the pin step back to the newest cooldown-cleared release (see _select_cooldown_pin()), resolving that tag’s SHA afresh.

Important

The cooldown may hold back an adoption. It may never rewrite a pin the repository already carries, which is what floor records.

init is the only writer of these refs: sync-action-pins skips every slug in UPSTREAM_REPO_SLUGS, and ACTION_PIN_RE does not even match a subpath-carrying reusable-workflow ref. So a downstream repository adopts a new repomatic release exactly one way: a human moves the pin, by hand or by running a newer init. Two things follow.

A pin equal to the running version is not a decision to gate. The CI sync-repomatic job runs init at the pinned version itself, so base equals floor on every sync; re-judging it there downgrades the repository once a week after each hand-bump, and fights the only upgrade path there is.

A pin below the running version is a skew. init renders caller content from the running version, so a ref naming an older release ships that content against an older reusable-workflow surface, which GitHub rejects as soon as the two disagree (see test_thin_caller_workflow_call_inputs_stay_minimal). Returning such a pin is therefore only half a decision: run_init() reads it back and, when the repository already carries workflows, skips regenerating them so the tree stays coherent at the pin it keeps. A first-time adoption has no tree to keep, so there the skew stands as the only alternative to writing no workflows at all.

Parameters:
  • config (Config) – Repomatic config supplying the minimum-release-age window.

  • repo (str) – Upstream owner/repo whose releases gate the pin.

  • today (date | None) – Reference date for the cooldown; defaults to the current UTC date.

  • warnings (list[str] | None) – When provided, a cooldown note is appended here (in addition to being logged), so run_init can surface it in the final init summary rather than only mid-run.

  • floor (UpstreamRefPin | None) – The highest upstream pin already committed downstream, from _highest_upstream_pin(). None for a repository carrying none, the one case the cooldown may step back freely.

Return type:

tuple[str, str | None]

Returns:

(version_pin, commit_sha). commit_sha is None when no SHA can be resolved, leaving a bare tag pin.

class repomatic.init_project.InitResult(created=<factory>, updated=<factory>, skipped=<factory>, excluded=<factory>, excluded_existing=<factory>, unmodified_configs=<factory>, removed_prunable=<factory>, removed_review=<factory>, warnings=<factory>)[source]

Bases: object

Result of a repository initialization run.

created: list[str]

Relative paths of newly created files.

updated: list[str]

Relative paths of existing files overwritten with new content.

skipped: list[str]

Relative paths of skipped (already existing) files.

excluded: list[str]

Exclude entries that were applied.

excluded_existing: list[str]

Relative paths of excluded files that still exist on disk.

unmodified_configs: list[str]

Relative paths of config files identical to bundled defaults.

removed_prunable: list[tuple[str, str]]

(relative_path, successor) for on-disk orphans of dropped assets whose content matches the last-shipped version (safe to auto-delete).

removed_review: list[tuple[str, str]]

(relative_path, successor) for on-disk orphans of dropped assets that differ from the last-shipped version (locally modified: reported for manual review, never auto-deleted).

warnings: list[str]

Warning messages emitted during initialization.

repomatic.init_project.prune_paths(paths, output_dir, *, prune_parents=True)[source]

Delete every path of an InitResult report section.

The deleting half of init’s delete flags (--delete-excluded, --delete-unmodified, the removed-asset pruning), kept beside run_init(), which produced the paths: the CLI decides which sections get deleted, this module owns the filesystem mutation.

Parameters:
  • paths (Sequence[str] | Sequence[tuple[str, str]]) – Bare relative paths, or (path, successor) pairs for the removed-asset sections.

  • output_dir (Path) – Repository root the paths are relative to.

  • prune_parents (bool) – Also remove parent directories left empty. On for the removed-asset and excluded sections, whose targets sit in directories repomatic itself created (.claude/skills/<name>/). Off for unmodified tool configs, which share .github/ and the repository root with files repomatic does not own.

Return type:

None

repomatic.init_project.adopted_ongoing_configs(output_dir)[source]

Return the ongoing tool configs whose section pyproject.toml already carries.

EXPLICIT governs adoption, not upkeep: it keeps a bare init from pushing [tool.typos] onto a repository that never asked for one. Once the section is there the repository has asked, so an ONGOING component rejoins the bare-init set and resumes tracking the bundled template.

Without this the two flags cancel out. The only sync that ever runs unattended is the bare init the sync-repomatic job calls, so an ONGOING section is otherwise re-derived only when a human types its component name, and a [tool.typos] written by hand sits indefinitely beside a bundled template it never adopts a single rule from.

BOOTSTRAP components stay out: their template is a starting point the repository owns outright after the first write, and re-selecting one would revert deliberate local edits.

Parameters:

output_dir (Path) – Repository root holding pyproject.toml.

Return type:

set[str]

Returns:

Component names to add to a bare init selection. Empty when the file is absent, unparsable, or carries no [tool] table.

repomatic.init_project.run_init(output_dir, components=(), version=None, cooldown=True, repo='kdeldycke/repomatic', repo_slug=None, config=None)[source]

Bootstrap a repository for use with kdeldycke/repomatic.

Creates thin-caller workflow files, exports configuration files, and generates a minimal changelog.md if missing. Managed files (workflows, configs, skills) are always overwritten. User-owned files (changelog.md, zizmor.yaml) are created once and never overwritten.

For awesome-* repositories, the awesome-template component is auto-included when no explicit component selection is made.

Note

Scope exclusions (RepoScope.AWESOME_ONLY, PYTHON_ONLY) and user-config exclusions ([tool.repomatic] exclude) only apply during bare repomatic init. When components are explicitly named on the CLI, scope is bypassed: the caller knows what they asked for. This allows workflows to materialize out-of-scope configs at runtime (like repomatic init publish-pypi-action in a non-Python repo).

Parameters:
  • output_dir (Path) – Root directory of the target repository.

  • components (Sequence[str]) – Components to initialize. Empty means all defaults. When non-empty, scope and user-config exclusions are bypassed.

  • version (str | None) – Version pin for upstream workflows (like v5.10.0). When None, derived from the running package version, gated by cooldown.

  • cooldown (bool) – When True (and version is unset), hold the derived pin back to the newest release past the [tool.repomatic] minimum-release-age window instead of pinning a fresh running version (see resolve_default_pin()). Ignored when version is explicit.

  • repo (str) – Upstream repository containing reusable workflows.

  • repo_slug (str | None) – Repository owner/name slug for awesome-template URL rewriting. Auto-detected via Metadata if not provided.

  • config (Config | None) – The resolved [tool.repomatic] configuration. Loaded from the current directory when omitted, so a caller working against another tree must pass the config it read from there.

Return type:

InitResult

Returns:

Summary of created, updated, skipped, and warned items.

repomatic.init_project.is_source_repo(output_dir)[source]

Detect whether output_dir is the repomatic source repository root.

Returns True when output_dir contains the repomatic Python package source tree (repomatic/__init__.py and repomatic/data/). Only the upstream source repo has these. This prevents auto-exclusion from deleting files that are the source of truth (skills, opt-in workflows, bundled configs).

Note

Detection is based on output_dir contents, not on __file__, because uvx --from . installs the package into a temp venv where __file__ no longer points to the source checkout.

Return type:

bool

repomatic.init_project.AWESOME_TEMPLATE_SLUG = 'kdeldycke/awesome-template'

Source slug embedded in bundled awesome-template files, rewritten at sync time.

repomatic.init_project.init_awesome_template(output_dir, repo_slug, result)[source]

Copy bundled awesome-template files and rewrite URLs.

Copies all files from the repomatic/data/awesome_template/ bundle into output_dir and rewrites kdeldycke/awesome-template URLs in .github/ markdown and YAML files to match repo_slug.

Every copied file is recorded on result by its own relative path, the way the skills and agents trees already are. The roll-up stays a log line: those lists are consumed as paths (--delete-excluded joins them against output_dir), so a "awesome-template (12 files)" summary sitting among them would be a path that resolves nowhere.

Parameters:
  • output_dir (Path) – Root directory of the target repository.

  • repo_slug (str) – Target owner/name slug for URL rewriting.

  • result (InitResult) – InitResult accumulator for created/updated files.

Return type:

None