repomatic.registry module

Declarative registry of all components managed by the init subcommand.

Every resource the init subcommand can create, sync, or merge is declared here as a Component subclass instance in the COMPONENTS tuple. Each component carries all its metadata: what kind it is, whether it is selected by default, which files it manages, and any per-file properties like repo-scope gating or config keys.

All derived constants (ALL_COMPONENTS, REUSABLE_WORKFLOWS, SKILL_PHASES, etc.) are computed from this single registry at the bottom of this module.

repomatic.registry.GITHUB_YAML_PATTERNS: tuple[str, ...] = ('.github/workflows/*.yaml', '.github/workflows/*.yml', '.github/actions/**/*.yaml', '.github/actions/**/*.yml')

Globs matching every workflow and composite-action file of a repository.

Rooted at the repository root rather than at .github/, so the same patterns work against the current directory and against an arbitrary target tree. Both .yml and .yaml are listed because GitHub accepts either, whatever this project’s own long-extension convention prefers: a downstream repository is free to have picked the short one.

Shared by sync_ops._workflow_and_action_files, which reads the pins to bump, and init_project._highest_upstream_pin, which reads them to floor a new pin. The two must agree on which files carry a pin, or init would floor against a file sync-workflow-pins never bumps.

class repomatic.registry.InitDefault(*values)[source]

Bases: Enum

How init treats the component when no explicit CLI args are given.

INCLUDE = 1

Included by default (like changelog or workflows).

EXCLUDE = 2

In default set but excluded unless explicitly included (e.g., labels, skills).

AUTO = 3

Auto-included only for matching repos (e.g., awesome-template).

EXPLICIT = 4

Only included when explicitly requested (e.g., tool configs).

class repomatic.registry.SyncMode(*values)[source]

Bases: Enum

How a ToolConfigComponent behaves when the section already exists.

BOOTSTRAP = 1

Insert once, skip if section already exists (e.g., ruff, pytest).

ONGOING = 2

Replace template content on every sync, preserving local additions (e.g., bumpversion).

class repomatic.registry.RepoScope(*values)[source]

Bases: Enum

Which repository types a component or file entry applies to.

The classification has three axes: whether the repo is an awesome-* list, whether it carries a PEP 621 pyproject.toml, and whether that project is a distributable package. The first is mutually exclusive with the other two (awesome repos are content lists, not Python projects), so a single scope value suffices.

The Python axis is deliberately split in two. PYTHON_ONLY covers anything that needs Python code to be useful; PACKAGE_ONLY covers only what needs something to publish. A uv virtual project ([tool.uv] package = false) sits between the two: it locks dependencies and runs tests, but never ships a release. Collapsing the pair would hand every blog and docs site a PyPI publish action and a release workflow it can never run.

Scope restrictions are defaults: they apply during bare repomatic init but are bypassed when components are explicitly named on the CLI or covered by [tool.repomatic] include.

ALL = 1

Included in every repository type.

AWESOME_ONLY = 2

Only for awesome-* repositories.

PYTHON_ONLY = 3

Only for Python projects (PEP 621 [project].name present).

Use for anything a uv virtual project still wants: dependency locking, coverage config, test tooling.

PACKAGE_ONLY = 4

Only for Python projects that build a distributable package.

Strictly narrower than PYTHON_ONLY, excluding uv virtual projects. Use for the release lane: publishing, tagging, changelog upkeep.

matches(is_awesome, is_python, is_package)[source]

Whether this scope applies to the given repository traits.

Parameters:
Return type:

bool

class repomatic.registry.FileEntry(source, target='', file_id='', scope=RepoScope.ALL, config_key='', config_default=False, reusable=True, phase='', tree=False)[source]

Bases: object

A single file managed within a component.

source: str

Filename in repomatic/data/, or a directory when tree is set.

target: str = ''

Relative output path in the target repository. Defaults to source (root-level file).

file_id: str = ''

Identifier for file-level --include/--exclude. Defaults to the filename portion of target.

scope: RepoScope = 1

Which repository types get this file.

config_key: str = ''

[tool.repomatic] key that gates this entry.

config_default: bool = False

Value assumed when config_key is absent from config. False means opt-in (excluded unless enabled), True means opt-out (included unless disabled).

reusable: bool = True

Workflow-specific: supports workflow_call trigger.

phase: str = ''

Skill-specific: lifecycle phase for list-skills display.

tree: bool = False

Whether source and target name directories, not files.

A tree entry is copied wholesale, so a skill can ship scripts/, references/ and assets/ alongside its SKILL.md exactly as the Agent Skills spec describes, with no per-file registration.

Caution

Under repomatic/data/ a tree’s directories must be real and only its leaves may be symlinks back into the authoritative tree. uv_build refuses a symlinked directory in package data (Is a directory (os error 21)) and fails the whole wheel, while symlinked files are dereferenced into it normally.

is_enabled(config)[source]

Whether this entry is enabled by the given Config object.

See _config_enabled() for the resolution rule.

Parameters:

config (object) – A Config instance.

Return type:

bool

class repomatic.registry.Component(name, description, init_default=InitDefault.INCLUDE, scope=RepoScope.ALL, files=(), config_key='', config_default=True, keep_unmodified=False, ephemeral=False, location_field='')[source]

Bases: object

Base class for all init components.

name: str

Component name used on the CLI (e.g., "skills").

description: str

Human-readable description for help text.

init_default: InitDefault = 1

How init treats this component when no explicit CLI selection is made.

scope: RepoScope = 1

Which repository types get this component. Checked at the component level during auto-exclusion, complementing the file-level FileEntry.scope.

files: tuple[FileEntry, ...] = ()

File entries this component manages.

config_key: str = ''

[tool.repomatic] key that gates this component.

config_default: bool = True

Value assumed when config_key is absent from config. True means opt-out (included unless disabled).

keep_unmodified: bool = False

Preserve files on disk even when identical to the bundled default. When False, unmodified copies are flagged for cleanup by --delete-unmodified.

ephemeral: bool = False

Whether this component’s files are inputs regenerated on demand rather than repository content.

Every consumer of an ephemeral component dumps it right before reading it, so a copy in the working tree is never the one that gets used. Bare repomatic init therefore skips these components, and [tool.repomatic] include cannot opt into materializing them: only naming the component explicitly on the CLI (repomatic init labels) writes its files out, which is how sync-labels stages labels.toml into a temporary directory to hand to labelmaker, leaving the working tree untouched.

location_field: str = ''

Config field holding this component’s destination, when the user can move it.

Set for every component whose destination is configurable: the directories subagents and skills write into, and the single file plugin merges into. Declared targets are built against the default location, so a repo that overrode it needs each target rebased onto the configured one. resolve_target() performs that rebase, and leaving this empty means the targets are fixed (.github/workflows/ is GitHub’s, not ours to move).

is_enabled(config)[source]

Whether this component is enabled by the given Config object.

See _config_enabled() for the resolution rule.

Parameters:

config (object) – A Config instance.

Return type:

bool

resolve_target(target, config)[source]

Rebase a declared target path onto this component’s configured location.

A no-op unless location_field is set and the resolved config actually moves the destination, so every caller can route every target through this method instead of testing the component name first.

Handles both shapes a location may take. A directory location rebases the path under it; a file location (plugin) is the path, so it is replaced outright. Matching only the directory shape would leave a moved file reported at its default path, and stale-file detection would then hunt for an orphan the repository never wrote there.

Parameters:
  • target (str) – A path as declared on a FileEntry (or a RemovedAsset tombstone), relative to the repository root and expressed against the default location.

  • config (object) – A Config instance, or None.

Return type:

str

Returns:

The target rebased onto the configured location, or target unchanged.

class repomatic.registry.BundledComponent(name, description, init_default=InitDefault.INCLUDE, scope=RepoScope.ALL, files=(), config_key='', config_default=True, keep_unmodified=False, ephemeral=False, location_field='')[source]

Bases: Component

Files copied from repomatic/data/ to a target path.

class repomatic.registry.WorkflowComponent(name, description, init_default=InitDefault.INCLUDE, scope=RepoScope.ALL, files=(), config_key='', config_default=True, keep_unmodified=False, ephemeral=False, location_field='')[source]

Bases: Component

Thin-caller generation and header sync.

class repomatic.registry.ToolConfigComponent(name, description, init_default=InitDefault.INCLUDE, scope=RepoScope.ALL, files=(), config_key='', config_default=True, keep_unmodified=False, ephemeral=False, location_field='', source_file='', tool_section='', sync_mode=SyncMode.BOOTSTRAP, preserved_keys=(), graft_identity_keys=(), overlay=False)[source]

Bases: Component

Merged into pyproject.toml.

Note

Nothing here declares where the section lands. init appends it to the [tool] table, then format-pyproject moves it: pyproject-fmt sorts [tool.*] by its own known-tool order, which no per-component hint can override. This class used to carry insert_after / insert_before tuples for the purpose; they were read by no code and are gone.

source_file: str = ''

Filename in repomatic/data/.

tool_section: str = ''

The [tool.X] section name to check for existence.

sync_mode: SyncMode = 1

How this config behaves when the section already exists.

BOOTSTRAP: insert once, skip if the section is present. ONGOING: re-derive the section from the template on every sync while preserving local additions: keys the template omits, extra items in shared arrays, and extra keys in shared nested tables. The template wins on shared scalars; preserved_keys flips that for named top-level keys.

preserved_keys: tuple[str, ...] = ()

Top-level keys whose existing values survive an ongoing sync.

Only meaningful when sync_mode is ONGOING. During replacement, these keys keep their value from the existing config rather than being overwritten by the template placeholder.

graft_identity_keys: tuple[str, ...] = ()

Keys that identify the “slot” of an array-of-tables entry during a graft.

Only meaningful when sync_mode is ONGOING. When set, a local array-of-tables entry that shares its identity tuple (the values of these keys) with a template entry is treated as a stale copy of that canonical entry: the template wins and the local entry is dropped rather than appended as a duplicate. Local entries whose identity matches no template entry are genuinely local and survive. Leave empty to fall back to a plain union-by-value, which cannot tell an evolved canonical entry apart from a new local one.

For bumpversion, the slot is (filename | glob | key_path, replace): filename/glob/key_path name the target file and replace names what the entry writes there, so a stale entry whose search pattern evolved (e.g. gaining a regex anchor) still maps to the same slot.

overlay: bool = False

Treat the template as a partial section owning only its own keys.

Only meaningful when sync_mode is ONGOING. The default rebuild-and-graft sync rebuilds the whole section from the template and grafts local additions after it, so template keys always land first. That is wrong for a section the project mostly owns and a formatter reorders: [tool.uv], whose keys pyproject-fmt sorts into a fixed schema order. Emitting the owned keys as a leading block would lose to pyproject-fmt on the next format pass and churn an endless sync PR.

With overlay set, an ongoing sync instead updates only the template’s top-level keys in place within the existing section (the template value wins), preserving the existing key order and leaving every other key untouched. The merged section is therefore already a pyproject-fmt fixpoint. A repo missing an owned key has it appended; pyproject-fmt canonicalizes that one position once, after which steady-state syncs are no-ops.

property tool_name: str

The bare tool name, without the tool. table prefix.

The key this component’s section sits under inside a parsed [tool] table, derived once here rather than re-spelled by every consumer of tool_section.

class repomatic.registry.TemplateComponent(name, description, init_default=InitDefault.INCLUDE, scope=RepoScope.ALL, files=(), config_key='', config_default=True, keep_unmodified=False, ephemeral=False, location_field='')[source]

Bases: Component

Directory tree (awesome-template).

class repomatic.registry.GeneratedComponent(name, description, init_default=InitDefault.INCLUDE, scope=RepoScope.ALL, files=(), config_key='', config_default=True, keep_unmodified=False, ephemeral=False, location_field='', target='')[source]

Bases: Component

Produced from code (changelog).

Unlike bundled components, generated components have no files tuple. The target field records the output path so the auto-exclusion logic can detect stale copies on disk.

target: str = ''

Relative output path in the target repository.

class repomatic.registry.RemovedAsset(component, target, removed_in, hashes=(), owned_dir='', successor='')[source]

Bases: object

An asset repomatic once shipped and has since dropped.

Note

Stale-file detection in init only inspects files still listed in COMPONENTS. An asset removed from the registry (a renamed or consolidated skill, a retired workflow) becomes invisible to it, so downstream repos accumulate one orphan per upstream removal. Each RemovedAsset is a tombstone that lets init find and prune those orphans.

init finds an on-disk orphan and decides whether to prune it with one of two gates, depending on the component:

  • Content-gated (skills, agents, config files): the file is deleted only when its normalized content matches one of hashes (a version repomatic shipped), proving it is an untouched copy.

  • Fingerprint-gated (workflows): thin-callers are parameterized per repo (version pin, paths: filters), so they carry no fixed content. The file is deleted only when it is a repomatic-lineage thin-caller for this workflow (its uses: line references an upstream slug, see UPSTREAM_REPO_SLUGS) with no extra downstream jobs.

Either way, a locally modified orphan is reported for manual review, never deleted. When target is already gone but the asset shipped as a folder, an empty owned_dir left behind is pruned on its own: it carries nothing anyone could lose.

component: str

Component the asset belonged to (like "skills" or "workflows").

target: str

Relative output path the asset occupied, in default-location form (like .claude/skills/repomatic-release/SKILL.md or .github/workflows/label-sponsors.yaml).

Build skill and subagent targets with _skill_target / _subagent_target so they match the live registry: the skills.location and subagents.location overrides are re-applied at detection time. Workflow targets are literal (.github/workflows/ is fixed by GitHub).

removed_in: str

Bare package version that first stopped shipping the asset (like 6.21.0). Surfaced in the prune report.

hashes: tuple[str, ...] = ()

Content gate for skills and agents: the hex SHA-256 of every distinct normalized content repomatic shipped for this asset (content.rstrip() + “n”`, exactly as``init` writes it to disk). An on-disk file whose content hashes to any of these is an untouched copy of some released version and is safe to delete. Listing one hash per distinct released revision (not just the last) means a downstream repo that synced an older version is still recognized and pruned rather than flagged for review.

Empty for workflows, which are fingerprint-gated by their uses: line instead (see the class docstring).

owned_dir: str = ''

Directory the asset had to itself, in default-location form (like .claude/skills/repomatic-release), for an asset shipped as a folder.

A skill is a folder, so deleting its SKILL.md by any route other than init (a hand rm, a repomatic old enough to unlink the file alone) leaves the folder behind, empty. target no longer exists, so the tombstone never fires again and the fossil outlives every later init. Declaring the folder gives detection a second thing to look for. Empty for an asset that shipped as a lone file in a shared directory (a subagent, a workflow), whose parent must never be swept.

successor: str = ''

Optional human note describing what replaced the asset, shown in the report (like replaced by repomatic-ship).

repomatic.registry.WORKFLOW_TARGET_ROOT = '.github/workflows'

Directory GitHub reads workflow files from. Not configurable.

repomatic.registry.INSTALL_GUIDE_PATH = 'docs/install.md'

Install guide the release freeze pins download URLs in.

Shared by PrepareRelease, which rewrites those URLs, and check_install_guide_downloads(), which verifies the release they name actually carries the files.

repomatic.registry.SKILL_FILENAME = 'SKILL.md'

Name the Agent Skills spec reserves for a skill’s entry point.

repomatic.registry.SKILL_SOURCE_ROOT = 'skills'

Directory under repomatic/data/ holding one folder per bundled skill.

repomatic.registry.COMPONENTS: tuple[Component, ...] = (BundledComponent(name='labels', description='Label definitions for labelmaker (labels.toml)', init_default=<InitDefault.EXCLUDE: 2>, scope=<RepoScope.ALL: 1>, files=(FileEntry(source='labels.toml', target='labels.toml', file_id='labels.toml', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=True, phase='', tree=False),), config_key='', config_default=True, keep_unmodified=False, ephemeral=True, location_field=''), BundledComponent(name='publish-pypi-action', description='Composite action that publishes to PyPI via Trusted Publishing (.github/actions/publish-pypi/)', init_default=<InitDefault.INCLUDE: 1>, scope=<RepoScope.PACKAGE_ONLY: 4>, files=(FileEntry(source='action-publish-pypi.yaml', target='.github/actions/publish-pypi/action.yaml', file_id='action.yaml', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=True, phase='', tree=False),), config_key='', config_default=True, keep_unmodified=True, ephemeral=False, location_field=''), BundledComponent(name='subagents', description='Agent subagent definitions (.claude/agents/)', init_default=<InitDefault.EXCLUDE: 2>, scope=<RepoScope.ALL: 1>, files=(FileEntry(source='agent-grunt-qa.md', target='.claude/agents/grunt-qa.md', file_id='grunt-qa', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=True, phase='', tree=False), FileEntry(source='agent-qa-engineer.md', target='.claude/agents/qa-engineer.md', file_id='qa-engineer', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=True, phase='', tree=False), FileEntry(source='agent-sphinx-docs.md', target='.claude/agents/sphinx-docs.md', file_id='sphinx-docs', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=True, phase='', tree=False)), config_key='', config_default=True, keep_unmodified=True, ephemeral=False, location_field='subagents_location'), BundledComponent(name='skills', description='Claude Code skill definitions (.claude/skills/)', init_default=<InitDefault.EXCLUDE: 2>, scope=<RepoScope.ALL: 1>, files=(FileEntry(source='skills/av-false-positive', target='.claude/skills/av-false-positive', file_id='av-false-positive', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=True, phase='Release', tree=True), FileEntry(source='skills/awesome-triage', target='.claude/skills/awesome-triage', file_id='awesome-triage', scope=<RepoScope.AWESOME_ONLY: 2>, config_key='', config_default=False, reusable=True, phase='Maintenance', tree=True), FileEntry(source='skills/babysit-ci', target='.claude/skills/babysit-ci', file_id='babysit-ci', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=True, phase='Quality', tree=True), FileEntry(source='skills/benchmark-update', target='.claude/skills/benchmark-update', file_id='benchmark-update', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=True, phase='Development', tree=True), FileEntry(source='skills/brand-assets', target='.claude/skills/brand-assets', file_id='brand-assets', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=True, phase='Development', tree=True), FileEntry(source='skills/file-bug-report', target='.claude/skills/file-bug-report', file_id='file-bug-report', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=True, phase='Maintenance', tree=True), FileEntry(source='skills/github-housekeeping', target='.claude/skills/github-housekeeping', file_id='github-housekeeping', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=True, phase='Maintenance', tree=True), FileEntry(source='skills/repomatic-audit', target='.claude/skills/repomatic-audit', file_id='repomatic-audit', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=True, phase='Maintenance', tree=True), FileEntry(source='skills/repomatic-changelog', target='.claude/skills/repomatic-changelog', file_id='repomatic-changelog', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=True, phase='Release', tree=True), FileEntry(source='skills/repomatic-deps', target='.claude/skills/repomatic-deps', file_id='repomatic-deps', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=True, phase='Development', tree=True), FileEntry(source='skills/repomatic-init', target='.claude/skills/repomatic-init', file_id='repomatic-init', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=True, phase='Setup', tree=True), FileEntry(source='skills/repomatic-ship', target='.claude/skills/repomatic-ship', file_id='repomatic-ship', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=True, phase='Release', tree=True), FileEntry(source='skills/repomatic-test-matrix', target='.claude/skills/repomatic-test-matrix', file_id='repomatic-test-matrix', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=True, phase='Quality', tree=True), FileEntry(source='skills/repomatic-topics', target='.claude/skills/repomatic-topics', file_id='repomatic-topics', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=True, phase='Development', tree=True), FileEntry(source='skills/sphinx-docs-sync', target='.claude/skills/sphinx-docs-sync', file_id='sphinx-docs-sync', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=True, phase='Maintenance', tree=True), FileEntry(source='skills/translation-sync', target='.claude/skills/translation-sync', file_id='translation-sync', scope=<RepoScope.AWESOME_ONLY: 2>, config_key='', config_default=False, reusable=True, phase='Maintenance', tree=True), FileEntry(source='skills/upstream-audit', target='.claude/skills/upstream-audit', file_id='upstream-audit', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=True, phase='Maintenance', tree=True)), config_key='', config_default=True, keep_unmodified=True, ephemeral=False, location_field='skills_location'), WorkflowComponent(name='workflows', description='Thin-caller workflow files', init_default=<InitDefault.INCLUDE: 1>, scope=<RepoScope.ALL: 1>, files=(FileEntry(source='autofix.yaml', target='.github/workflows/autofix.yaml', file_id='autofix.yaml', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=True, phase='', tree=False), FileEntry(source='autolock.yaml', target='.github/workflows/autolock.yaml', file_id='autolock.yaml', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=True, phase='', tree=False), FileEntry(source='cancel-runs.yaml', target='.github/workflows/cancel-runs.yaml', file_id='cancel-runs.yaml', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=True, phase='', tree=False), FileEntry(source='changelog.yaml', target='.github/workflows/changelog.yaml', file_id='changelog.yaml', scope=<RepoScope.PACKAGE_ONLY: 4>, config_key='', config_default=False, reusable=True, phase='', tree=False), FileEntry(source='debug.yaml', target='.github/workflows/debug.yaml', file_id='debug.yaml', scope=<RepoScope.PYTHON_ONLY: 3>, config_key='debug.sync', config_default=False, reusable=True, phase='', tree=False), FileEntry(source='docs.yaml', target='.github/workflows/docs.yaml', file_id='docs.yaml', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=True, phase='', tree=False), FileEntry(source='labels.yaml', target='.github/workflows/labels.yaml', file_id='labels.yaml', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=True, phase='', tree=False), FileEntry(source='lint.yaml', target='.github/workflows/lint.yaml', file_id='lint.yaml', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=True, phase='', tree=False), FileEntry(source='metrics.yaml', target='.github/workflows/metrics.yaml', file_id='metrics.yaml', scope=<RepoScope.ALL: 1>, config_key='metrics.sync', config_default=False, reusable=True, phase='', tree=False), FileEntry(source='_release-engine.yaml', target='.github/workflows/release.yaml', file_id='release.yaml', scope=<RepoScope.PACKAGE_ONLY: 4>, config_key='', config_default=False, reusable=True, phase='', tree=False), FileEntry(source='tests.yaml', target='.github/workflows/tests.yaml', file_id='tests.yaml', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=False, phase='', tree=False), FileEntry(source='unsubscribe.yaml', target='.github/workflows/unsubscribe.yaml', file_id='unsubscribe.yaml', scope=<RepoScope.ALL: 1>, config_key='notification.unsubscribe', config_default=False, reusable=True, phase='', tree=False)), config_key='', config_default=True, keep_unmodified=False, ephemeral=False, location_field=''), TemplateComponent(name='awesome-template', description='Boilerplate for awesome-* repositories', init_default=<InitDefault.AUTO: 3>, scope=<RepoScope.ALL: 1>, files=(), config_key='awesome-template.sync', config_default=True, keep_unmodified=False, ephemeral=False, location_field=''), GeneratedComponent(name='changelog', description='Minimal changelog.md', init_default=<InitDefault.INCLUDE: 1>, scope=<RepoScope.PACKAGE_ONLY: 4>, files=(), config_key='', config_default=True, keep_unmodified=False, ephemeral=False, location_field='', target='changelog.md'), GeneratedComponent(name='plugin', description='Claude Code plugin marketplace wiring (.claude/settings.json)', init_default=<InitDefault.EXCLUDE: 2>, scope=<RepoScope.ALL: 1>, files=(), config_key='', config_default=True, keep_unmodified=True, ephemeral=False, location_field='settings_location', target='.claude/settings.json'), ToolConfigComponent(name='uv', description='uv resolver pin and dependency cooldown policy', init_default=<InitDefault.EXPLICIT: 4>, scope=<RepoScope.ALL: 1>, files=(), config_key='', config_default=True, keep_unmodified=False, ephemeral=False, location_field='', source_file='uv.toml', tool_section='tool.uv', sync_mode=<SyncMode.ONGOING: 2>, preserved_keys=(), graft_identity_keys=(), overlay=True), ToolConfigComponent(name='lychee', description='Lychee link checker configuration', init_default=<InitDefault.INCLUDE: 1>, scope=<RepoScope.AWESOME_ONLY: 2>, files=(), config_key='', config_default=True, keep_unmodified=False, ephemeral=False, location_field='', source_file='lychee.toml', tool_section='tool.lychee', sync_mode=<SyncMode.ONGOING: 2>, preserved_keys=(), graft_identity_keys=(), overlay=False), ToolConfigComponent(name='ruff', description='Ruff linter/formatter configuration', init_default=<InitDefault.EXPLICIT: 4>, scope=<RepoScope.ALL: 1>, files=(), config_key='', config_default=True, keep_unmodified=False, ephemeral=False, location_field='', source_file='ruff.toml', tool_section='tool.ruff', sync_mode=<SyncMode.BOOTSTRAP: 1>, preserved_keys=(), graft_identity_keys=(), overlay=False), ToolConfigComponent(name='pytest', description='Pytest test configuration', init_default=<InitDefault.EXPLICIT: 4>, scope=<RepoScope.ALL: 1>, files=(), config_key='', config_default=True, keep_unmodified=False, ephemeral=False, location_field='', source_file='pytest.toml', tool_section='tool.pytest', sync_mode=<SyncMode.BOOTSTRAP: 1>, preserved_keys=(), graft_identity_keys=(), overlay=False), ToolConfigComponent(name='coverage', description='Coverage.py measurement and reporting configuration', init_default=<InitDefault.EXPLICIT: 4>, scope=<RepoScope.ALL: 1>, files=(), config_key='', config_default=True, keep_unmodified=False, ephemeral=False, location_field='', source_file='coverage.toml', tool_section='tool.coverage', sync_mode=<SyncMode.BOOTSTRAP: 1>, preserved_keys=(), graft_identity_keys=(), overlay=False), ToolConfigComponent(name='mypy', description='Mypy type checking configuration', init_default=<InitDefault.EXPLICIT: 4>, scope=<RepoScope.ALL: 1>, files=(), config_key='', config_default=True, keep_unmodified=False, ephemeral=False, location_field='', source_file='mypy.toml', tool_section='tool.mypy', sync_mode=<SyncMode.BOOTSTRAP: 1>, preserved_keys=(), graft_identity_keys=(), overlay=False), ToolConfigComponent(name='mdformat', description='mdformat Markdown formatter configuration', init_default=<InitDefault.EXPLICIT: 4>, scope=<RepoScope.ALL: 1>, files=(), config_key='', config_default=True, keep_unmodified=False, ephemeral=False, location_field='', source_file='mdformat.toml', tool_section='tool.mdformat', sync_mode=<SyncMode.BOOTSTRAP: 1>, preserved_keys=(), graft_identity_keys=(), overlay=False), ToolConfigComponent(name='bumpversion', description='bump-my-version configuration', init_default=<InitDefault.EXPLICIT: 4>, scope=<RepoScope.ALL: 1>, files=(), config_key='', config_default=True, keep_unmodified=False, ephemeral=False, location_field='', source_file='bumpversion.toml', tool_section='tool.bumpversion', sync_mode=<SyncMode.ONGOING: 2>, preserved_keys=('current_version',), graft_identity_keys=('filename', 'glob', 'key_path', 'replace'), overlay=False), ToolConfigComponent(name='typos', description='Typos spell checker configuration', init_default=<InitDefault.EXPLICIT: 4>, scope=<RepoScope.ALL: 1>, files=(), config_key='', config_default=True, keep_unmodified=False, ephemeral=False, location_field='', source_file='typos.toml', tool_section='tool.typos', sync_mode=<SyncMode.ONGOING: 2>, preserved_keys=(), graft_identity_keys=(), overlay=False))

The component registry.

Single source of truth for all resources managed by the init subcommand. Every component declares its kind, selection default, file entries, and behavioral flags. All derived constants are computed from this tuple.

repomatic.registry.COMPONENTS_BY_NAME: dict[str, Component] = {'awesome-template': TemplateComponent(name='awesome-template', description='Boilerplate for awesome-* repositories', init_default=<InitDefault.AUTO: 3>, scope=<RepoScope.ALL: 1>, files=(), config_key='awesome-template.sync', config_default=True, keep_unmodified=False, ephemeral=False, location_field=''), 'bumpversion': ToolConfigComponent(name='bumpversion', description='bump-my-version configuration', init_default=<InitDefault.EXPLICIT: 4>, scope=<RepoScope.ALL: 1>, files=(), config_key='', config_default=True, keep_unmodified=False, ephemeral=False, location_field='', source_file='bumpversion.toml', tool_section='tool.bumpversion', sync_mode=<SyncMode.ONGOING: 2>, preserved_keys=('current_version',), graft_identity_keys=('filename', 'glob', 'key_path', 'replace'), overlay=False), 'changelog': GeneratedComponent(name='changelog', description='Minimal changelog.md', init_default=<InitDefault.INCLUDE: 1>, scope=<RepoScope.PACKAGE_ONLY: 4>, files=(), config_key='', config_default=True, keep_unmodified=False, ephemeral=False, location_field='', target='changelog.md'), 'coverage': ToolConfigComponent(name='coverage', description='Coverage.py measurement and reporting configuration', init_default=<InitDefault.EXPLICIT: 4>, scope=<RepoScope.ALL: 1>, files=(), config_key='', config_default=True, keep_unmodified=False, ephemeral=False, location_field='', source_file='coverage.toml', tool_section='tool.coverage', sync_mode=<SyncMode.BOOTSTRAP: 1>, preserved_keys=(), graft_identity_keys=(), overlay=False), 'labels': BundledComponent(name='labels', description='Label definitions for labelmaker (labels.toml)', init_default=<InitDefault.EXCLUDE: 2>, scope=<RepoScope.ALL: 1>, files=(FileEntry(source='labels.toml', target='labels.toml', file_id='labels.toml', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=True, phase='', tree=False),), config_key='', config_default=True, keep_unmodified=False, ephemeral=True, location_field=''), 'lychee': ToolConfigComponent(name='lychee', description='Lychee link checker configuration', init_default=<InitDefault.INCLUDE: 1>, scope=<RepoScope.AWESOME_ONLY: 2>, files=(), config_key='', config_default=True, keep_unmodified=False, ephemeral=False, location_field='', source_file='lychee.toml', tool_section='tool.lychee', sync_mode=<SyncMode.ONGOING: 2>, preserved_keys=(), graft_identity_keys=(), overlay=False), 'mdformat': ToolConfigComponent(name='mdformat', description='mdformat Markdown formatter configuration', init_default=<InitDefault.EXPLICIT: 4>, scope=<RepoScope.ALL: 1>, files=(), config_key='', config_default=True, keep_unmodified=False, ephemeral=False, location_field='', source_file='mdformat.toml', tool_section='tool.mdformat', sync_mode=<SyncMode.BOOTSTRAP: 1>, preserved_keys=(), graft_identity_keys=(), overlay=False), 'mypy': ToolConfigComponent(name='mypy', description='Mypy type checking configuration', init_default=<InitDefault.EXPLICIT: 4>, scope=<RepoScope.ALL: 1>, files=(), config_key='', config_default=True, keep_unmodified=False, ephemeral=False, location_field='', source_file='mypy.toml', tool_section='tool.mypy', sync_mode=<SyncMode.BOOTSTRAP: 1>, preserved_keys=(), graft_identity_keys=(), overlay=False), 'plugin': GeneratedComponent(name='plugin', description='Claude Code plugin marketplace wiring (.claude/settings.json)', init_default=<InitDefault.EXCLUDE: 2>, scope=<RepoScope.ALL: 1>, files=(), config_key='', config_default=True, keep_unmodified=True, ephemeral=False, location_field='settings_location', target='.claude/settings.json'), 'publish-pypi-action': BundledComponent(name='publish-pypi-action', description='Composite action that publishes to PyPI via Trusted Publishing (.github/actions/publish-pypi/)', init_default=<InitDefault.INCLUDE: 1>, scope=<RepoScope.PACKAGE_ONLY: 4>, files=(FileEntry(source='action-publish-pypi.yaml', target='.github/actions/publish-pypi/action.yaml', file_id='action.yaml', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=True, phase='', tree=False),), config_key='', config_default=True, keep_unmodified=True, ephemeral=False, location_field=''), 'pytest': ToolConfigComponent(name='pytest', description='Pytest test configuration', init_default=<InitDefault.EXPLICIT: 4>, scope=<RepoScope.ALL: 1>, files=(), config_key='', config_default=True, keep_unmodified=False, ephemeral=False, location_field='', source_file='pytest.toml', tool_section='tool.pytest', sync_mode=<SyncMode.BOOTSTRAP: 1>, preserved_keys=(), graft_identity_keys=(), overlay=False), 'ruff': ToolConfigComponent(name='ruff', description='Ruff linter/formatter configuration', init_default=<InitDefault.EXPLICIT: 4>, scope=<RepoScope.ALL: 1>, files=(), config_key='', config_default=True, keep_unmodified=False, ephemeral=False, location_field='', source_file='ruff.toml', tool_section='tool.ruff', sync_mode=<SyncMode.BOOTSTRAP: 1>, preserved_keys=(), graft_identity_keys=(), overlay=False), 'skills': BundledComponent(name='skills', description='Claude Code skill definitions (.claude/skills/)', init_default=<InitDefault.EXCLUDE: 2>, scope=<RepoScope.ALL: 1>, files=(FileEntry(source='skills/av-false-positive', target='.claude/skills/av-false-positive', file_id='av-false-positive', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=True, phase='Release', tree=True), FileEntry(source='skills/awesome-triage', target='.claude/skills/awesome-triage', file_id='awesome-triage', scope=<RepoScope.AWESOME_ONLY: 2>, config_key='', config_default=False, reusable=True, phase='Maintenance', tree=True), FileEntry(source='skills/babysit-ci', target='.claude/skills/babysit-ci', file_id='babysit-ci', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=True, phase='Quality', tree=True), FileEntry(source='skills/benchmark-update', target='.claude/skills/benchmark-update', file_id='benchmark-update', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=True, phase='Development', tree=True), FileEntry(source='skills/brand-assets', target='.claude/skills/brand-assets', file_id='brand-assets', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=True, phase='Development', tree=True), FileEntry(source='skills/file-bug-report', target='.claude/skills/file-bug-report', file_id='file-bug-report', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=True, phase='Maintenance', tree=True), FileEntry(source='skills/github-housekeeping', target='.claude/skills/github-housekeeping', file_id='github-housekeeping', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=True, phase='Maintenance', tree=True), FileEntry(source='skills/repomatic-audit', target='.claude/skills/repomatic-audit', file_id='repomatic-audit', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=True, phase='Maintenance', tree=True), FileEntry(source='skills/repomatic-changelog', target='.claude/skills/repomatic-changelog', file_id='repomatic-changelog', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=True, phase='Release', tree=True), FileEntry(source='skills/repomatic-deps', target='.claude/skills/repomatic-deps', file_id='repomatic-deps', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=True, phase='Development', tree=True), FileEntry(source='skills/repomatic-init', target='.claude/skills/repomatic-init', file_id='repomatic-init', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=True, phase='Setup', tree=True), FileEntry(source='skills/repomatic-ship', target='.claude/skills/repomatic-ship', file_id='repomatic-ship', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=True, phase='Release', tree=True), FileEntry(source='skills/repomatic-test-matrix', target='.claude/skills/repomatic-test-matrix', file_id='repomatic-test-matrix', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=True, phase='Quality', tree=True), FileEntry(source='skills/repomatic-topics', target='.claude/skills/repomatic-topics', file_id='repomatic-topics', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=True, phase='Development', tree=True), FileEntry(source='skills/sphinx-docs-sync', target='.claude/skills/sphinx-docs-sync', file_id='sphinx-docs-sync', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=True, phase='Maintenance', tree=True), FileEntry(source='skills/translation-sync', target='.claude/skills/translation-sync', file_id='translation-sync', scope=<RepoScope.AWESOME_ONLY: 2>, config_key='', config_default=False, reusable=True, phase='Maintenance', tree=True), FileEntry(source='skills/upstream-audit', target='.claude/skills/upstream-audit', file_id='upstream-audit', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=True, phase='Maintenance', tree=True)), config_key='', config_default=True, keep_unmodified=True, ephemeral=False, location_field='skills_location'), 'subagents': BundledComponent(name='subagents', description='Agent subagent definitions (.claude/agents/)', init_default=<InitDefault.EXCLUDE: 2>, scope=<RepoScope.ALL: 1>, files=(FileEntry(source='agent-grunt-qa.md', target='.claude/agents/grunt-qa.md', file_id='grunt-qa', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=True, phase='', tree=False), FileEntry(source='agent-qa-engineer.md', target='.claude/agents/qa-engineer.md', file_id='qa-engineer', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=True, phase='', tree=False), FileEntry(source='agent-sphinx-docs.md', target='.claude/agents/sphinx-docs.md', file_id='sphinx-docs', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=True, phase='', tree=False)), config_key='', config_default=True, keep_unmodified=True, ephemeral=False, location_field='subagents_location'), 'typos': ToolConfigComponent(name='typos', description='Typos spell checker configuration', init_default=<InitDefault.EXPLICIT: 4>, scope=<RepoScope.ALL: 1>, files=(), config_key='', config_default=True, keep_unmodified=False, ephemeral=False, location_field='', source_file='typos.toml', tool_section='tool.typos', sync_mode=<SyncMode.ONGOING: 2>, preserved_keys=(), graft_identity_keys=(), overlay=False), 'uv': ToolConfigComponent(name='uv', description='uv resolver pin and dependency cooldown policy', init_default=<InitDefault.EXPLICIT: 4>, scope=<RepoScope.ALL: 1>, files=(), config_key='', config_default=True, keep_unmodified=False, ephemeral=False, location_field='', source_file='uv.toml', tool_section='tool.uv', sync_mode=<SyncMode.ONGOING: 2>, preserved_keys=(), graft_identity_keys=(), overlay=True), 'workflows': WorkflowComponent(name='workflows', description='Thin-caller workflow files', init_default=<InitDefault.INCLUDE: 1>, scope=<RepoScope.ALL: 1>, files=(FileEntry(source='autofix.yaml', target='.github/workflows/autofix.yaml', file_id='autofix.yaml', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=True, phase='', tree=False), FileEntry(source='autolock.yaml', target='.github/workflows/autolock.yaml', file_id='autolock.yaml', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=True, phase='', tree=False), FileEntry(source='cancel-runs.yaml', target='.github/workflows/cancel-runs.yaml', file_id='cancel-runs.yaml', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=True, phase='', tree=False), FileEntry(source='changelog.yaml', target='.github/workflows/changelog.yaml', file_id='changelog.yaml', scope=<RepoScope.PACKAGE_ONLY: 4>, config_key='', config_default=False, reusable=True, phase='', tree=False), FileEntry(source='debug.yaml', target='.github/workflows/debug.yaml', file_id='debug.yaml', scope=<RepoScope.PYTHON_ONLY: 3>, config_key='debug.sync', config_default=False, reusable=True, phase='', tree=False), FileEntry(source='docs.yaml', target='.github/workflows/docs.yaml', file_id='docs.yaml', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=True, phase='', tree=False), FileEntry(source='labels.yaml', target='.github/workflows/labels.yaml', file_id='labels.yaml', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=True, phase='', tree=False), FileEntry(source='lint.yaml', target='.github/workflows/lint.yaml', file_id='lint.yaml', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=True, phase='', tree=False), FileEntry(source='metrics.yaml', target='.github/workflows/metrics.yaml', file_id='metrics.yaml', scope=<RepoScope.ALL: 1>, config_key='metrics.sync', config_default=False, reusable=True, phase='', tree=False), FileEntry(source='_release-engine.yaml', target='.github/workflows/release.yaml', file_id='release.yaml', scope=<RepoScope.PACKAGE_ONLY: 4>, config_key='', config_default=False, reusable=True, phase='', tree=False), FileEntry(source='tests.yaml', target='.github/workflows/tests.yaml', file_id='tests.yaml', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=False, phase='', tree=False), FileEntry(source='unsubscribe.yaml', target='.github/workflows/unsubscribe.yaml', file_id='unsubscribe.yaml', scope=<RepoScope.ALL: 1>, config_key='notification.unsubscribe', config_default=False, reusable=True, phase='', tree=False)), config_key='', config_default=True, keep_unmodified=False, ephemeral=False, location_field='')}

Index for O(1) component lookup by name.

repomatic.registry.REMOVED_ASSETS: tuple[RemovedAsset, ...] = (RemovedAsset(component='codecov', target='.github/codecov.yaml', removed_in='7.8.0.dev0', hashes=('e8e96bfead62334599f4ec4c0448f2376352629789a70a76ae6fc3746ff7057b',), owned_dir='', successor='coverage is now gated by pytest --cov-fail-under'), RemovedAsset(component='labels', target='.github/labeller-content-based.yaml', removed_in='7.11.0.dev0', hashes=('1f3e670c0b4c6687a8920fb3738a15fb82b8639b7825d81f76c55bc5784cdb08', 'adf62c78c539229d34d4d2518a9af7f39df44d599c60852784b0faa47a6defa9', '5cf481b4aec2bf98a4056757f41ef5fc50f808dbd7c8a43f1dea0b224ecb7f1f', '8a047d53d5449ea0b53517e2f63e126360050127342084b7a705f34fb735d818'), owned_dir='', successor='rules now live in repomatic.labels.DEFAULT_CONTENT_RULES'), RemovedAsset(component='labels', target='.github/labeller-file-based.yaml', removed_in='7.11.0.dev0', hashes=('9dc0948e23a3a83d2cec5f11e400c75992fb1ce326eb6c5811c1fc3bfe258b31', 'b216d370e4d2c6118f46d9bb2eacaf91392e6a6267a4f4857f44a698422cc860', '9a4feeb49c37ee7eba1d13957d26aaaa867c791ec12be8cd4197e7526bfbf963'), owned_dir='', successor='rules now live in repomatic.labels.DEFAULT_FILE_RULES'), RemovedAsset(component='skills', target='.claude/skills/gha-changelog/SKILL.md', removed_in='6.0.0', hashes=('2c178a58e1106f08aa6e540cd022eff12c4e954942ec5d794282c7b640adf768',), owned_dir='.claude/skills/gha-changelog', successor='renamed to repomatic-changelog'), RemovedAsset(component='skills', target='.claude/skills/gha-deps/SKILL.md', removed_in='6.0.0', hashes=('d0bcb44f81335f4aabcadb82085f5048be12db252fc0a1f8c6bda8d9e5292efd',), owned_dir='.claude/skills/gha-deps', successor='renamed to repomatic-deps'), RemovedAsset(component='skills', target='.claude/skills/gha-init/SKILL.md', removed_in='6.0.0', hashes=('0f4f23f424c73774dd6253d9cb547e7a1d52ed64266c93b5b7271f4bee492a25',), owned_dir='.claude/skills/gha-init', successor='renamed to repomatic-init'), RemovedAsset(component='skills', target='.claude/skills/gha-lint/SKILL.md', removed_in='6.0.0', hashes=('7079f4d79c6347b03b4788de97db2e1839006b606e9dbacbfeb51e9cca04db20',), owned_dir='.claude/skills/gha-lint', successor='now handled by lint.yaml on every push'), RemovedAsset(component='skills', target='.claude/skills/gha-metadata/SKILL.md', removed_in='6.0.0', hashes=('74c6f7d3574236d20aa7011b92f174abd2f8fdda162131e7f61851dfee7145fa',), owned_dir='.claude/skills/gha-metadata', successor='now handled by the repomatic metadata CLI command'), RemovedAsset(component='skills', target='.claude/skills/gha-release/SKILL.md', removed_in='6.0.0', hashes=('99a466bc4d377bb056c5696de8f0eae2b025b34505ac951d504bee55a42bdd1c',), owned_dir='.claude/skills/gha-release', successor='replaced by repomatic-ship'), RemovedAsset(component='skills', target='.claude/skills/gha-sync/SKILL.md', removed_in='6.0.0', hashes=('f856f143db3f0ad37adb6c80b89c33efa5112e1307927ff3331f82857a71fef4',), owned_dir='.claude/skills/gha-sync', successor='now handled by autofix.yaml on every push'), RemovedAsset(component='skills', target='.claude/skills/gha-test/SKILL.md', removed_in='6.0.0', hashes=('4a00dac78e0ca3c598c2a3ae6e649f354f73e754c5aaea531d8409f1eff23434',), owned_dir='.claude/skills/gha-test', successor='now handled by tests.yaml on every push'), RemovedAsset(component='skills', target='.claude/skills/repokit-changelog/SKILL.md', removed_in='6.0.1', hashes=('6e176d9d0090afb9d9a10035e4c6721fff8fac4a1c313010fc04a7ab631be399',), owned_dir='.claude/skills/repokit-changelog', successor='renamed to repomatic-changelog'), RemovedAsset(component='skills', target='.claude/skills/repokit-deps/SKILL.md', removed_in='6.0.1', hashes=('577687ae8481cc67b992497ee0de9fb38c0f26cd20a9b907a4bf78f834803cc0',), owned_dir='.claude/skills/repokit-deps', successor='renamed to repomatic-deps'), RemovedAsset(component='skills', target='.claude/skills/repokit-init/SKILL.md', removed_in='6.0.1', hashes=('c68a9108ead81c4bb5b33912770155f6a587188ca72c8ba8d08f7283fdcad281',), owned_dir='.claude/skills/repokit-init', successor='renamed to repomatic-init'), RemovedAsset(component='skills', target='.claude/skills/repokit-lint/SKILL.md', removed_in='6.0.1', hashes=('1c05f0fb8c5ff8eed38ac02af2fff016e931fdf8866fd93a3fc6c61f84d4df52',), owned_dir='.claude/skills/repokit-lint', successor='now handled by lint.yaml on every push'), RemovedAsset(component='skills', target='.claude/skills/repokit-metadata/SKILL.md', removed_in='6.0.1', hashes=('0322f70cdd8e53d03fce2befbf904be1f0dc5596b79e41557ce8ec788a202cff',), owned_dir='.claude/skills/repokit-metadata', successor='now handled by the repomatic metadata CLI command'), RemovedAsset(component='skills', target='.claude/skills/repokit-release/SKILL.md', removed_in='6.0.1', hashes=('a6ceb0394f084f481765bb834f275af0cb1cf58a9383059358ceec50ea87b93a',), owned_dir='.claude/skills/repokit-release', successor='replaced by repomatic-ship'), RemovedAsset(component='skills', target='.claude/skills/repokit-sync/SKILL.md', removed_in='6.0.1', hashes=('412811337a541b6c4518e588240ce2cb13f3f476bcd311f32edcf04394e17ade',), owned_dir='.claude/skills/repokit-sync', successor='now handled by autofix.yaml on every push'), RemovedAsset(component='skills', target='.claude/skills/repokit-test/SKILL.md', removed_in='6.0.1', hashes=('63f0b532f379aa4400eea5a6284c3004ddc09749c8f476f4ea5a5e8ce3c4716f',), owned_dir='.claude/skills/repokit-test', successor='now handled by tests.yaml on every push'), RemovedAsset(component='skills', target='.claude/skills/repomatic-lint/SKILL.md', removed_in='6.21.0', hashes=('11131553c99adb7daf880b6b19b84e4d4573eedbe7b951092aa7d4a1f9357aab', 'd72cada008b46db93eff0b7a167f1f57346c528ec317fca73857205895fb1395', '058b9cc3248cd1d537d8fbf7a0c1133e3107c6ed405859457e88625b9301d3d8', '7ec6520cba0a14af07ed1bb4e2f0388109ac8db0509ca92ffa0829cf2967bd11'), owned_dir='.claude/skills/repomatic-lint', successor='now handled by lint.yaml on every push'), RemovedAsset(component='skills', target='.claude/skills/repomatic-metadata/SKILL.md', removed_in='6.3.0', hashes=('e94ba4246c0bf56b8dfb6a7e4d3ea2e9521c000e8322130b1746e7a54d3f260b', '58c6eec756177f445893366960464c2d5872de994a692399440df0eb30b11e35'), owned_dir='.claude/skills/repomatic-metadata', successor='now handled by the repomatic metadata CLI command'), RemovedAsset(component='skills', target='.claude/skills/repomatic-release/SKILL.md', removed_in='6.21.0', hashes=('0ecfa8ff5d55b33394d83bce76d39015450403124ff63e131fea14adf685c00b', '8546a42c1ea44b2a4fa0ed1bc49f71eaf8be3b5656a323ee93957ea1fdb0bb38', '778783f3ef6093d9892a4772fc312747155b399e18ba33f416fa9b138897b43d', 'b076cae374b3104f50996cf8b92eae6f53ec9546d3b0fab2c033c90cb1e8a107', '8e93d723827042e90acbe22d038516400bcd743bf39f3fb45a65c115008a97d0'), owned_dir='.claude/skills/repomatic-release', successor='replaced by repomatic-ship'), RemovedAsset(component='skills', target='.claude/skills/repomatic-sync/SKILL.md', removed_in='6.21.0', hashes=('3b36a8b4fc76282c280f6cc19fdc24aa826db8a81ee91a66737b24cb921c84d9', '1460738708f7e878c17ef578a7fad14710962a5fd7e7789f3bc08ae6bc49247b', '54a2b2aa40799c05d666295ee0a1f4d65946605c5397a006185123e4c2e9f1d0', '771d4e15efab4739fb00a7c1ba20495e063025842beb2e54d84207e1410f40a1', '687c7f9cae7271ee56f4d35b754325ba7a2c3b13537eee057679cc160e39471e', 'ceaf3141599850847ee51b2e4f85c76a4cae130a01b2a4fd820dd3b5c0dd0dc0', '91add2c0b7686f64f810bb86fa70c3ac99d3940b37ba6fbe57c01a4d427cc902'), owned_dir='.claude/skills/repomatic-sync', successor='now handled by autofix.yaml on every push'), RemovedAsset(component='skills', target='.claude/skills/repomatic-test/SKILL.md', removed_in='6.21.0', hashes=('8bc5f054507b369f9be34dd4a34183e00b6a8e0186c34d4deb385032e6682e1a', 'cb987bfe342c2d00ea1a6226585238f19bc5a351a678124f7e6225d5c6122c2c', '17bae80a4b98518b6037518ad340a60d117d35a4fa26725fa2ab685ebd23e8dd'), owned_dir='.claude/skills/repomatic-test', successor='now handled by tests.yaml on every push'), RemovedAsset(component='workflows', target='.github/workflows/label-sponsors.yaml', removed_in='4.25.0', hashes=(), owned_dir='', successor='merged into labels.yaml'), RemovedAsset(component='workflows', target='.github/workflows/labeller-content-based.yaml', removed_in='4.25.0', hashes=(), owned_dir='', successor='merged into labels.yaml'), RemovedAsset(component='workflows', target='.github/workflows/labeller-file-based.yaml', removed_in='4.25.0', hashes=(), owned_dir='', successor='merged into labels.yaml'), RemovedAsset(component='workflows', target='.github/workflows/renovate.yaml', removed_in='7.0.0.dev0', hashes=(), owned_dir='', successor='replaced by self-hosted sync-tool-versions, sync-action-pins, and sync-workflow-pins'))

Tombstones for assets repomatic has dropped (see RemovedAsset).

init prunes orphaned copies of these from downstream repos. Ordered by (component, target).

When you drop a bundled asset from COMPONENTS, add an entry here so the removal propagates downstream on the next init instead of leaving an orphan. List one hash per distinct content the asset shipped across its released lifetime, collected from the release tags where its data file existed:

import hashlib, subprocess

src = "repomatic/data/skill-repomatic-release.md"  # the dropped data file
tags = subprocess.run(
    ["git", "tag", "--list", "v*"], capture_output=True, text=True, check=True
).stdout.split()
hashes = {}
for tag in tags:
    blob = subprocess.run(
        ["git", "show", f"{tag}:{src}"],
        capture_output=True, text=True, encoding="UTF-8",
    )
    if blob.returncode == 0:
        normalized = blob.stdout.rstrip() + "\n"
        hashes.setdefault(hashlib.sha256(normalized.encode("UTF-8")).hexdigest(), tag)
print(tuple(hashes))  # distinct contents, in first-shipped order

Removed workflows are fingerprint-gated, not hashed: omit hashes and give the workflow’s downstream path as target (.github/workflows/{name}).

repomatic.registry.DEFAULT_REPO: str = 'kdeldycke/repomatic'

Default upstream repository for reusable workflows.

repomatic.registry.is_awesome_repo(name)[source]

Whether a repository name marks an awesome-* curated list.

The one spelling of the prefix test: repository-trait detection, the broken-links label choice and the awesome-template auto-inclusion all classify on it.

Return type:

bool

repomatic.registry.package_of(repo)[source]

The package name an owner/repo slug implies: its repository half.

The one spelling of that derivation, shared by every check and rewriter that maps a --upstream-repo value onto the inline package==X.Y.Z pin it governs.

Return type:

str

repomatic.registry.UPSTREAM_PACKAGE: str = 'repomatic'

Distribution name of the upstream toolkit, derived from DEFAULT_REPO.

The freeze, cooldown-exemption, and lint code that handles the uses: refs and the inline self-pin all key on this name: deriving it here keeps the writer/checker pairs in lockstep and makes a rename a one-line change.

repomatic.registry.UPSTREAM_REPO_SLUGS: tuple[str, ...] = ('kdeldycke/repomatic', 'kdeldycke/repokit', 'kdeldycke/workflows')

Upstream repository slugs across the project’s renames, current first.

A downstream thin-caller’s uses: line references whichever slug was current when it was generated. Workflow-tombstone detection matches against all of them (current first, since most callers are recent) so an orphaned thin-caller is recognized regardless of which era set it up.

repomatic.registry.UPSTREAM_SOURCE_GLOB: str = 'repomatic/**'

Path glob for the upstream source directory in canonical workflows.

Canonical workflow paths: filters use this glob to match source code changes. In downstream repos, this is replaced with the project’s own source directory.

repomatic.registry.UPSTREAM_SOURCE_PREFIX: str = 'repomatic/'

Path prefix for upstream-specific files in canonical workflows.

Paths starting with this prefix (but not matching UPSTREAM_SOURCE_GLOB) are dropped in downstream thin callers because they reference files that only exist in the upstream repository (like repomatic/data/labels.toml).

repomatic.registry.SKILL_PHASE_ORDER: tuple[str, ...] = ('Setup', 'Development', 'Quality', 'Maintenance', 'Release')

Canonical display order for lifecycle phases in list-skills output.

repomatic.registry.SKILL_LIST_HEADER_DEFS: tuple[tuple[str, str], ...] = (('Phase', 'phase'), ('Skill', 'skill'), ('Description', 'description'))

Column definitions for the repomatic list-skills table.

Lives beside skill_catalog(), whose triples these columns render, so the two cannot drift apart. The columns mirror the hand-maintained roster of docs/agent-skills.md, which the page renders this command right above.

repomatic.registry.ALL_COMPONENTS: dict[str, str] = {'awesome-template': 'Boilerplate for awesome-* repositories', 'bumpversion': 'bump-my-version configuration', 'changelog': 'Minimal changelog.md', 'coverage': 'Coverage.py measurement and reporting configuration', 'labels': 'Label definitions for labelmaker (labels.toml)', 'lychee': 'Lychee link checker configuration', 'mdformat': 'mdformat Markdown formatter configuration', 'mypy': 'Mypy type checking configuration', 'plugin': 'Claude Code plugin marketplace wiring (.claude/settings.json)', 'publish-pypi-action': 'Composite action that publishes to PyPI via Trusted Publishing (.github/actions/publish-pypi/)', 'pytest': 'Pytest test configuration', 'ruff': 'Ruff linter/formatter configuration', 'skills': 'Claude Code skill definitions (.claude/skills/)', 'subagents': 'Agent subagent definitions (.claude/agents/)', 'typos': 'Typos spell checker configuration', 'uv': 'uv resolver pin and dependency cooldown policy', 'workflows': 'Thin-caller workflow files'}

All available init components.

repomatic.registry.EPHEMERAL_TARGETS: frozenset[str] = frozenset({'labels.toml'})

Target paths belonging to Component.ephemeral components.

Written only when the component is named explicitly on the CLI, and never worth committing: whatever reads them regenerates them first. init uses this to keep its closing “commit the generated files” advice off a run that produced nothing but scratch output.

repomatic.registry.BUNDLED_VERBATIM_TARGETS: frozenset[str] = frozenset({'.claude/agents/grunt-qa.md', '.claude/agents/qa-engineer.md', '.claude/agents/sphinx-docs.md', '.claude/skills/av-false-positive', '.claude/skills/awesome-triage', '.claude/skills/babysit-ci', '.claude/skills/benchmark-update', '.claude/skills/brand-assets', '.claude/skills/file-bug-report', '.claude/skills/github-housekeeping', '.claude/skills/repomatic-audit', '.claude/skills/repomatic-changelog', '.claude/skills/repomatic-deps', '.claude/skills/repomatic-init', '.claude/skills/repomatic-ship', '.claude/skills/repomatic-test-matrix', '.claude/skills/repomatic-topics', '.claude/skills/sphinx-docs-sync', '.claude/skills/translation-sync', '.claude/skills/upstream-audit', '.github/actions/publish-pypi/action.yaml', 'labels.toml'})

Target paths repomatic init writes verbatim from a repomatic/data/ template.

Every BundledComponent copies its bundled source byte-for-byte to the target, so downstream the file’s content (including any SHA-pinned uses: ref) is owned by repomatic init. sync-action-pins and sync-workflow-pins skip these paths for the same reason they skip UPSTREAM_REPO_SLUGS: a pin the next sync-repomatic overwrites turns the two pull requests into a ping-pong, the bump PR and the init-revert PR chasing each other. The skip lifts inside the source repo, where each bundled source is a symlink to its in-tree target and the pin is a normal source-of-truth ref (see repomatic.sync_ops._pinnable_files). Generated workflows (WorkflowComponent) are deliberately absent: they carry only upstream-slug refs (already skipped) and may host downstream-authored extra jobs whose third-party pins the bumpers should keep current.

repomatic.registry.REUSABLE_WORKFLOWS: tuple[str, ...] = ('autofix.yaml', 'autolock.yaml', 'cancel-runs.yaml', 'changelog.yaml', 'debug.yaml', 'docs.yaml', 'labels.yaml', 'lint.yaml', 'metrics.yaml', 'release.yaml', 'unsubscribe.yaml')

Workflow filenames that support workflow_call triggers.

repomatic.registry.NON_REUSABLE_WORKFLOWS: frozenset[str] = frozenset({'tests.yaml'})

Workflows without workflow_call that cannot be used as thin callers.

repomatic.registry.ALL_WORKFLOW_FILES: tuple[str, ...] = ('autofix.yaml', 'autolock.yaml', 'cancel-runs.yaml', 'changelog.yaml', 'debug.yaml', 'docs.yaml', 'labels.yaml', 'lint.yaml', 'metrics.yaml', 'release.yaml', 'tests.yaml', 'unsubscribe.yaml')

All workflow filenames (reusable and non-reusable).

repomatic.registry.WORKFLOW_SOURCES: dict[str, str] = {'autofix.yaml': 'autofix.yaml', 'autolock.yaml': 'autolock.yaml', 'cancel-runs.yaml': 'cancel-runs.yaml', 'changelog.yaml': 'changelog.yaml', 'debug.yaml': 'debug.yaml', 'docs.yaml': 'docs.yaml', 'labels.yaml': 'labels.yaml', 'lint.yaml': 'lint.yaml', 'metrics.yaml': 'metrics.yaml', 'release.yaml': '_release-engine.yaml', 'tests.yaml': 'tests.yaml', 'unsubscribe.yaml': 'unsubscribe.yaml'}

Maps each workflow’s downstream file_id to its bundled source filename.

For most workflows source == file_id. The release entry is the exception: its downstream artifact is release.yaml, whose backing reusable engine is _release-engine.yaml (the lane the generic “is this a reusable workflow” tests inspect). The full set of reusable lanes the generated release.yaml calls is RELEASE_ENGINE_WORKFLOWS.

repomatic.registry.RELEASE_ENGINE_WORKFLOWS: tuple[str, ...] = ('_release-build.yaml', '_release-engine.yaml')

Reusable workflows the generated release.yaml references but that repomatic init never materializes downstream.

The workflows component deploys a generated release.yaml (not a thin delegation): its build job calls _release-build.yaml and its release job calls _release-engine.yaml, each via {repo}/.github/workflows/<lane>@<tag> resolved from this repo at the release tag rather than copied into the downstream tree. These lanes live in .github/workflows/ here (and at every release tag) but are not FileEntry targets and never appear in ALL_WORKFLOW_FILES.

The release entry’s FileEntry still records _release-engine.yaml as its source (see WORKFLOW_SOURCES) so the generic backing-reusable tests and a downstream repomatic lint can read it via get_data_content to check the engine lane forwards its secrets; _release-build.yaml is not bundled because nothing reads it at runtime (the build lane declares no secrets). Naming both lanes here lets stale-file detection and the data-symlink rules treat them as a group instead of special-casing each by hand.

repomatic.registry.SELF_MAINTENANCE_WORKFLOWS: frozenset[str] = frozenset({'self-maintenance.yaml'})

Workflows that maintain this package’s own source and never ship downstream.

Unlike RELEASE_ENGINE_WORKFLOWS, which downstream repos still reach remotely through a uses: ref at a release tag, these are invisible outside this repository: they are not FileEntry targets, carry no repomatic/data/ symlink, and nothing resolves them at runtime. That is what lets their jobs drop the github.repository == 'kdeldycke/repomatic' guard every in-autofix.yaml upstream-only step needs, and pick a schedule without spending downstream CI.

A workflow belongs here when its write domain is a path that exists only in this repository (repomatic/tooling/tool_registry.py and friends). A workflow that merely behaves differently upstream does not: it still ships, so it still needs the runtime guard.

repomatic.registry.SKILL_PHASES: dict[str, str] = {'av-false-positive': 'Release', 'awesome-triage': 'Maintenance', 'babysit-ci': 'Quality', 'benchmark-update': 'Development', 'brand-assets': 'Development', 'file-bug-report': 'Maintenance', 'github-housekeeping': 'Maintenance', 'repomatic-audit': 'Maintenance', 'repomatic-changelog': 'Release', 'repomatic-deps': 'Development', 'repomatic-init': 'Setup', 'repomatic-ship': 'Release', 'repomatic-test-matrix': 'Quality', 'repomatic-topics': 'Development', 'sphinx-docs-sync': 'Maintenance', 'translation-sync': 'Maintenance', 'upstream-audit': 'Maintenance'}

Maps skill names to lifecycle phases for display grouping.

repomatic.registry.skill_catalog()[source]

Read every bundled skill’s display metadata off its frontmatter.

Return type:

list[tuple[str, str, str]]

Returns:

One (phase, name, description) tuple per bundled skill, in registry order, with the description’s trailing period stripped for table display. Phases are keyed by the registry file_id, not the frontmatter name, so a skill renamed in frontmatter still lands in its phase.

repomatic.registry.FILE_SELECTOR_COMPONENTS: tuple[str, ...] = ('labels', 'publish-pypi-action', 'subagents', 'skills', 'workflows')

Components that support file-level component/file selectors.

repomatic.registry.COMPONENT_HELP_TABLE: str = '    labels                 Label definitions for labelmaker (labels.toml)\n    publish-pypi-action    Composite action that publishes to PyPI via Trusted Publishing (.github/actions/publish-pypi/)\n    subagents              Agent subagent definitions (.claude/agents/)\n    skills                 Claude Code skill definitions (.claude/skills/)\n    workflows              Thin-caller workflow files\n    awesome-template       Boilerplate for awesome-* repositories\n    changelog              Minimal changelog.md\n    plugin                 Claude Code plugin marketplace wiring (.claude/settings.json)\n    uv                     uv resolver pin and dependency cooldown policy\n    lychee                 Lychee link checker configuration\n    ruff                   Ruff linter/formatter configuration\n    pytest                 Pytest test configuration\n    coverage               Coverage.py measurement and reporting configuration\n    mypy                   Mypy type checking configuration\n    mdformat               mdformat Markdown formatter configuration\n    bumpversion            bump-my-version configuration\n    typos                  Typos spell checker configuration'

Formatted component table for CLI help text.

repomatic.registry.valid_file_ids(component)[source]

Return valid file identifiers for a component.

Components with file entries report their declared file_id values. Returns an empty set for components without file-level selection (e.g., changelog, tool configs).

Return type:

frozenset[str]

repomatic.registry.excluded_rel_path(component, file_id)[source]

Map a component and file identifier to its relative output path.

Returns None when the identifier cannot be resolved (e.g., for tool config components that have no file-level exclusion support).

Return type:

str | None

repomatic.registry.parse_component_entries(entries, *, context='entry')[source]

Parse component entries into full-component and file-level sets.

Bare names (no /) must be component names from ALL_COMPONENTS. Qualified component/identifier entries target individual files. Raises ValueError on unknown entries.

Used by both the exclude config path and the CLI positional selection, with context controlling error message wording.

Parameters:

context (str) – Label for error messages (e.g., "exclude", "selection").

Return type:

tuple[set[str], dict[str, set[str]]]

Returns:

(full_components, file_selections) where file_selections maps component names to sets of file identifiers.