repomatic.components 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 (REUSABLE_WORKFLOWS, BARE_INIT_COMPONENTS and the
others) are computed from this single registry at the bottom of this module.
- class repomatic.components.InitDefault(*values)[source]¶
Bases:
EnumHow
inittreats the component when no explicit CLI args are given.- INCLUDE = 1¶
Included by default (like changelog or workflows).
- EXCLUDE = 2¶
In the default set, but excluded unless
[tool.repomatic] includenames it (like labels or skills).
- EXPLICIT = 3¶
Only included when explicitly requested (like most tool configs).
- class repomatic.components.SyncMode(*values)[source]¶
Bases:
EnumHow a
ToolConfigComponentbehaves when the section already exists.- BOOTSTRAP = 1¶
Insert once, skip if section already exists (like ruff or pytest).
- ONGOING = 2¶
Replace template content on every sync, preserving local additions (like bumpversion).
- class repomatic.components.RepoScope(*values)[source]¶
Bases:
EnumWhich 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 621pyproject.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_ONLYcovers anything that needs Python code to be useful;PACKAGE_ONLYcovers 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.The workflow layer mirrors the same split.
repomatic.pyproject.is_python_project()is the default gate, sincesync-uv-lockandsync-dep-sourcesapply to a virtual project too.repomatic.pyproject.is_python_package()narrows a job to what has something to publish or version:sync-bumpversionis the one that needs it.Scope restrictions are defaults: they apply during bare
repomatic initbut 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].namepresent).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(traits)[source]¶
Whether this scope applies to a repository with traits.
- Parameters:
traits (
RepoTraits) – Whatinitread off the target repository.- Return type:
- class repomatic.components.RepoTraits(is_awesome=False, is_python=False, is_package=False, is_source=False)[source]¶
Bases:
objectThe traits of a repository that decide which
RepoScopeapplies.repomatic initreads them off the target tree once per run, and every scope check of that run asks the same instance.- is_python: bool = False¶
Whether
pyproject.tomldeclares a PEP 621[project]table, perrepomatic.pyproject.is_python_project().
- is_package: bool = False¶
Whether that project also builds a distributable package, per
repomatic.pyproject.is_python_package(). Always impliesis_python.
- is_source: bool = False¶
Whether the repository is the upstream source checkout, per
repomatic.upstream.is_source_repo(). No scope reads it:initreads it to never flag the bundled source files as stale.
- class repomatic.components.FileEntry(source, target='', file_id='', scope=RepoScope.ALL, config_key='', reusable=True, phase='', tree=False)[source]¶
Bases:
objectA single file managed within a component.
- 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 oftarget.
- config_key: str = ''¶
[tool.repomatic]key that gates this entry.Resolved by
is_enabled(), so it names a key the user writes, likedebug.sync. A key that names no field raises.
- tree: bool = False¶
Whether
sourceandtargetname directories, not files.A tree entry is copied wholesale, so a skill can ship
scripts/,references/andassets/alongside itsSKILL.mdexactly 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_buildrefuses 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 config enables this entry.
An entry with no
config_keyis always enabled.
- class repomatic.components.Component(name, description, init_default=InitDefault.INCLUDE, scope=RepoScope.ALL, files=(), config_key='', ephemeral=False, location_field='')[source]¶
Bases:
objectBase class for all init components.
- init_default: InitDefault = 1¶
How
inittreats 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.
- config_key: str = ''¶
[tool.repomatic]key that gates this component.Resolved like
FileEntry.config_key.
- 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 inittherefore 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 howsync-labelsstageslabels.tomlinto a temporary directory to hand tolabelmaker, leaving the working tree untouched.
- location_field: str = ''¶
Configfield holding this component’s destination, when the user can move it.Set for every component whose destination is configurable: the directories
subagentsandskillswrite into, and the single filepluginmerges 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).
- property file_ids: frozenset[str]¶
The identifiers a
component/fileselector may name.Empty for a component without file-level selection, like a tool config.
- is_enabled(config)[source]¶
Whether config enables this component.
A component with no
config_keyis always enabled.
- resolve_target(target, config)[source]¶
Rebase a declared target path onto this component’s configured location.
A no-op unless
location_fieldis 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 aFileEntry(or aRemovedAssettombstone), relative to the repository root and expressed against the default location.config (
Config|None) – The resolved configuration.Nonekeeps every target at its default location.
- Return type:
- Returns:
The target rebased onto the configured location, or target unchanged.
- class repomatic.components.BundledComponent(name, description, init_default=InitDefault.INCLUDE, scope=RepoScope.ALL, files=(), config_key='', ephemeral=False, location_field='')[source]¶
Bases:
ComponentFiles copied from
repomatic/data/to a target path.
- class repomatic.components.WorkflowComponent(name, description, init_default=InitDefault.INCLUDE, scope=RepoScope.ALL, files=(), config_key='', ephemeral=False, location_field='')[source]¶
Bases:
ComponentThin-caller generation and header sync.
- class repomatic.components.ToolConfigComponent(name, description, init_default=InitDefault.INCLUDE, scope=RepoScope.ALL, files=(), config_key='', ephemeral=False, location_field='', source_file='', tool_section='', sync_mode=SyncMode.BOOTSTRAP, customizable_entries=(), legacy_table='', preserved_keys=(), graft_identity_keys=(), overlay=False)[source]¶
Bases:
ComponentMerged into
pyproject.toml.Note
Nothing here declares where the section lands.
initappends it to the[tool]table, thenformat-pyprojectmoves it:pyproject-fmtsorts[tool.*]by its own known-tool order, which no per-component hint can override.- 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_keysflips that for named top-level keys.
- customizable_entries: tuple[tuple[str, str | None], ...] = ()¶
Template entries every repository is expected to replace with its own.
Only meaningful when
sync_modeisBOOTSTRAP, wherecheck_bootstrap_config_drift()reports what a seeded section never received. A placeholder the template tells the reader to change is absent from every correctly customized section, so reporting it would put a permanent warning on the one knob the template asked the repository to own.Each entry is
(dotted_path, None)for a whole key, or(dotted_path, member)for one item of a list.
- legacy_table: str = ''¶
Sub-table the tool reads in place of the section’s own keys.
Only meaningful when
sync_modeisBOOTSTRAP. pytest reads[tool.pytest.ini_options]as well as the native[tool.pytest]keys the template carries, which need pytest 9.0, and refuses a file holding both.check_bootstrap_config_drift()measures a section that keeps its keys in this sub-table against the sub-table, so it never reports them missing or asks for a native twin the tool would reject.
- preserved_keys: tuple[str, ...] = ()¶
Top-level keys whose existing values survive an ongoing sync.
Only meaningful when
sync_modeisONGOING. 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_modeisONGOING. 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_pathname the target file andreplacenames what the entry writes there, so a stale entry whosesearchpattern evolved (like one 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_modeisONGOING. 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 keyspyproject-fmtsorts into a fixed schema order. Emitting the owned keys as a leading block would lose topyproject-fmton the next format pass and churn an endless sync PR.With
overlayset, 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 apyproject-fmtfixpoint. A repo missing an owned key has it appended;pyproject-fmtcanonicalizes that one position once, after which steady-state syncs are no-ops.
- class repomatic.components.TemplateComponent(name, description, init_default=InitDefault.INCLUDE, scope=RepoScope.ALL, files=(), config_key='', ephemeral=False, location_field='')[source]¶
Bases:
ComponentDirectory tree (awesome-template).
- class repomatic.components.GeneratedComponent(name, description, init_default=InitDefault.INCLUDE, scope=RepoScope.ALL, files=(), config_key='', ephemeral=False, location_field='', target='')[source]¶
Bases:
ComponentProduced from code (changelog).
Unlike bundled components, generated components have no
filestuple. Thetargetfield records the output path so the auto-exclusion logic can detect stale copies on disk.
- class repomatic.components.RemovedAsset(component, target, removed_in, hashes=(), owned_dir='', successor='')[source]¶
Bases:
objectAn asset repomatic once shipped and has since dropped.
Note
Stale-file detection in
initonly inspects files still listed inCOMPONENTS. 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. EachRemovedAssetis a tombstone that letsinitfind and prune those orphans.initfinds 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 (itsuses:line references an upstream slug, seeUPSTREAM_REPO_SLUGS) with no extra downstream jobs.
Either way, a locally modified orphan is reported for manual review, never deleted. When
targetis already gone but the asset shipped as a folder, an emptyowned_dirleft behind is pruned on its own: it carries nothing anyone could lose.- target: str¶
Relative output path the asset occupied, in default-location form (like
.claude/skills/papaya-report/SKILL.mdor.github/workflows/papaya.yaml).Build skill and subagent targets with
_skill_target/_subagent_targetso they match the live registry: theskills.locationandsubagents.locationoverrides 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
7.11.0). Surfaced in the prune report.
- hashes: tuple[str, ...] = ()¶
Content gate for skills, agents and config files: the
digest()of every distinct content repomatic shipped for this asset. An on-disk file whose content has any of these digests is an untouched copy of some released version and is safe to delete. Listing one digest 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/papaya-report), for an asset shipped as a folder.A skill is a folder, so deleting its
SKILL.mdby any route other thaninit(a handrm, a repomatic old enough to unlink the file alone) leaves the folder behind, empty.targetno longer exists, so the tombstone never fires again and the fossil outlives every laterinit. 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).
- property content_gated: bool¶
Whether an orphan is judged by its content rather than its
uses:line.Every asset is content-gated except a workflow, whose thin caller carries no fixed content to compare.
- static digest(text)[source]¶
The hex SHA-256 of text in the form
initwrites it to disk.The text is normalized first, through
normalize_text(), so trailing whitespace a repository added after the write never reads as a local change.
- locate(path, config)[source]¶
Rebase path, in default-location form, onto the configured location.
Applies the same
*.locationoverride as the live targets ofcomponent, throughComponent.resolve_target(). A dropped component the registry no longer lists leaves path unchanged.
- repomatic.components.SKILL_FILENAME = 'SKILL.md'¶
Name the Agent Skills spec reserves for a skill’s entry point.
- repomatic.components.SKILL_SOURCE_ROOT = 'skills'¶
Directory under
repomatic/data/holding one folder per bundled skill.
- repomatic.components.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='', reusable=True, phase='', tree=False),), config_key='', 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='', reusable=True, phase='', tree=False),), config_key='', 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='', 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='', 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='', reusable=True, phase='', tree=False)), config_key='', 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='', 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='', 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='', 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='', 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='', 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='', 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='', reusable=True, phase='Maintenance', tree=True), FileEntry(source='skills/probe-workflow', target='.claude/skills/probe-workflow', file_id='probe-workflow', scope=<RepoScope.ALL: 1>, config_key='', reusable=True, phase='Quality', tree=True), FileEntry(source='skills/repomatic-audit', target='.claude/skills/repomatic-audit', file_id='repomatic-audit', scope=<RepoScope.ALL: 1>, config_key='', 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='', 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='', 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='', 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='', 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='', 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='', reusable=True, phase='Development', tree=True), FileEntry(source='skills/repomatic-upgrade', target='.claude/skills/repomatic-upgrade', file_id='repomatic-upgrade', scope=<RepoScope.ALL: 1>, config_key='', reusable=True, phase='Maintenance', 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='', 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='', 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='', reusable=True, phase='Maintenance', tree=True)), config_key='', 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='', reusable=True, phase='', tree=False), FileEntry(source='autolock.yaml', target='.github/workflows/autolock.yaml', file_id='autolock.yaml', scope=<RepoScope.ALL: 1>, config_key='', 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='', 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='', 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', reusable=True, phase='', tree=False), FileEntry(source='docs.yaml', target='.github/workflows/docs.yaml', file_id='docs.yaml', scope=<RepoScope.ALL: 1>, config_key='', reusable=True, phase='', tree=False), FileEntry(source='labels.yaml', target='.github/workflows/labels.yaml', file_id='labels.yaml', scope=<RepoScope.ALL: 1>, config_key='', reusable=True, phase='', tree=False), FileEntry(source='lint.yaml', target='.github/workflows/lint.yaml', file_id='lint.yaml', scope=<RepoScope.ALL: 1>, config_key='', 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', 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='', reusable=True, phase='', tree=False), FileEntry(source='tests.yaml', target='.github/workflows/tests.yaml', file_id='tests.yaml', scope=<RepoScope.ALL: 1>, config_key='', 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', reusable=True, phase='', tree=False)), config_key='', ephemeral=False, location_field=''), TemplateComponent(name='awesome-template', description='Boilerplate for awesome-* repositories', init_default=<InitDefault.INCLUDE: 1>, scope=<RepoScope.AWESOME_ONLY: 2>, files=(), config_key='awesome-template.sync', ephemeral=False, location_field=''), GeneratedComponent(name='changelog', description='Minimal changelog.md', init_default=<InitDefault.INCLUDE: 1>, scope=<RepoScope.PACKAGE_ONLY: 4>, files=(), config_key='', ephemeral=False, location_field='changelog_location', 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='', 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: 3>, scope=<RepoScope.ALL: 1>, files=(), config_key='', ephemeral=False, location_field='', source_file='uv.toml', tool_section='tool.uv', sync_mode=<SyncMode.ONGOING: 2>, customizable_entries=(), legacy_table='', 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='', ephemeral=False, location_field='', source_file='lychee.toml', tool_section='tool.lychee', sync_mode=<SyncMode.ONGOING: 2>, customizable_entries=(), legacy_table='', preserved_keys=(), graft_identity_keys=(), overlay=False), ToolConfigComponent(name='ruff', description='Ruff linter/formatter configuration', init_default=<InitDefault.EXPLICIT: 3>, scope=<RepoScope.ALL: 1>, files=(), config_key='', ephemeral=False, location_field='', source_file='ruff.toml', tool_section='tool.ruff', sync_mode=<SyncMode.BOOTSTRAP: 1>, customizable_entries=(), legacy_table='', preserved_keys=(), graft_identity_keys=(), overlay=False), ToolConfigComponent(name='pytest', description='Pytest test configuration', init_default=<InitDefault.EXPLICIT: 3>, scope=<RepoScope.ALL: 1>, files=(), config_key='', ephemeral=False, location_field='', source_file='pytest.toml', tool_section='tool.pytest', sync_mode=<SyncMode.BOOTSTRAP: 1>, customizable_entries=(('addopts', '--cov=.'),), legacy_table='ini_options', preserved_keys=(), graft_identity_keys=(), overlay=False), ToolConfigComponent(name='coverage', description='Coverage.py measurement and reporting configuration', init_default=<InitDefault.EXPLICIT: 3>, scope=<RepoScope.ALL: 1>, files=(), config_key='', ephemeral=False, location_field='', source_file='coverage.toml', tool_section='tool.coverage', sync_mode=<SyncMode.BOOTSTRAP: 1>, customizable_entries=(), legacy_table='', preserved_keys=(), graft_identity_keys=(), overlay=False), ToolConfigComponent(name='mypy', description='Mypy type checking configuration', init_default=<InitDefault.EXPLICIT: 3>, scope=<RepoScope.ALL: 1>, files=(), config_key='', ephemeral=False, location_field='', source_file='mypy.toml', tool_section='tool.mypy', sync_mode=<SyncMode.BOOTSTRAP: 1>, customizable_entries=(), legacy_table='', preserved_keys=(), graft_identity_keys=(), overlay=False), ToolConfigComponent(name='mdformat', description='mdformat Markdown formatter configuration', init_default=<InitDefault.EXPLICIT: 3>, scope=<RepoScope.ALL: 1>, files=(), config_key='', ephemeral=False, location_field='', source_file='mdformat.toml', tool_section='tool.mdformat', sync_mode=<SyncMode.BOOTSTRAP: 1>, customizable_entries=(), legacy_table='', preserved_keys=(), graft_identity_keys=(), overlay=False), ToolConfigComponent(name='bumpversion', description='bump-my-version configuration', init_default=<InitDefault.EXPLICIT: 3>, scope=<RepoScope.ALL: 1>, files=(), config_key='', ephemeral=False, location_field='', source_file='bumpversion.toml', tool_section='tool.bumpversion', sync_mode=<SyncMode.ONGOING: 2>, customizable_entries=(), legacy_table='', 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: 3>, scope=<RepoScope.ALL: 1>, files=(), config_key='', ephemeral=False, location_field='', source_file='typos.toml', tool_section='tool.typos', sync_mode=<SyncMode.ONGOING: 2>, customizable_entries=(), legacy_table='', preserved_keys=(), graft_identity_keys=(), overlay=False))¶
The component registry.
Single source of truth for all resources managed by the
initsubcommand. Every component declares its kind, selection default, file entries, and behavioral flags. All derived constants are computed from this tuple.
- repomatic.components.COMPONENTS_BY_NAME: dict[str, Component] = {'awesome-template': TemplateComponent(name='awesome-template', description='Boilerplate for awesome-* repositories', init_default=<InitDefault.INCLUDE: 1>, scope=<RepoScope.AWESOME_ONLY: 2>, files=(), config_key='awesome-template.sync', ephemeral=False, location_field=''), 'bumpversion': ToolConfigComponent(name='bumpversion', description='bump-my-version configuration', init_default=<InitDefault.EXPLICIT: 3>, scope=<RepoScope.ALL: 1>, files=(), config_key='', ephemeral=False, location_field='', source_file='bumpversion.toml', tool_section='tool.bumpversion', sync_mode=<SyncMode.ONGOING: 2>, customizable_entries=(), legacy_table='', 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='', ephemeral=False, location_field='changelog_location', target='changelog.md'), 'coverage': ToolConfigComponent(name='coverage', description='Coverage.py measurement and reporting configuration', init_default=<InitDefault.EXPLICIT: 3>, scope=<RepoScope.ALL: 1>, files=(), config_key='', ephemeral=False, location_field='', source_file='coverage.toml', tool_section='tool.coverage', sync_mode=<SyncMode.BOOTSTRAP: 1>, customizable_entries=(), legacy_table='', 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='', reusable=True, phase='', tree=False),), config_key='', 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='', ephemeral=False, location_field='', source_file='lychee.toml', tool_section='tool.lychee', sync_mode=<SyncMode.ONGOING: 2>, customizable_entries=(), legacy_table='', preserved_keys=(), graft_identity_keys=(), overlay=False), 'mdformat': ToolConfigComponent(name='mdformat', description='mdformat Markdown formatter configuration', init_default=<InitDefault.EXPLICIT: 3>, scope=<RepoScope.ALL: 1>, files=(), config_key='', ephemeral=False, location_field='', source_file='mdformat.toml', tool_section='tool.mdformat', sync_mode=<SyncMode.BOOTSTRAP: 1>, customizable_entries=(), legacy_table='', preserved_keys=(), graft_identity_keys=(), overlay=False), 'mypy': ToolConfigComponent(name='mypy', description='Mypy type checking configuration', init_default=<InitDefault.EXPLICIT: 3>, scope=<RepoScope.ALL: 1>, files=(), config_key='', ephemeral=False, location_field='', source_file='mypy.toml', tool_section='tool.mypy', sync_mode=<SyncMode.BOOTSTRAP: 1>, customizable_entries=(), legacy_table='', 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='', 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='', reusable=True, phase='', tree=False),), config_key='', ephemeral=False, location_field=''), 'pytest': ToolConfigComponent(name='pytest', description='Pytest test configuration', init_default=<InitDefault.EXPLICIT: 3>, scope=<RepoScope.ALL: 1>, files=(), config_key='', ephemeral=False, location_field='', source_file='pytest.toml', tool_section='tool.pytest', sync_mode=<SyncMode.BOOTSTRAP: 1>, customizable_entries=(('addopts', '--cov=.'),), legacy_table='ini_options', preserved_keys=(), graft_identity_keys=(), overlay=False), 'ruff': ToolConfigComponent(name='ruff', description='Ruff linter/formatter configuration', init_default=<InitDefault.EXPLICIT: 3>, scope=<RepoScope.ALL: 1>, files=(), config_key='', ephemeral=False, location_field='', source_file='ruff.toml', tool_section='tool.ruff', sync_mode=<SyncMode.BOOTSTRAP: 1>, customizable_entries=(), legacy_table='', 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='', 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='', 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='', 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='', 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='', 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='', 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='', reusable=True, phase='Maintenance', tree=True), FileEntry(source='skills/probe-workflow', target='.claude/skills/probe-workflow', file_id='probe-workflow', scope=<RepoScope.ALL: 1>, config_key='', reusable=True, phase='Quality', tree=True), FileEntry(source='skills/repomatic-audit', target='.claude/skills/repomatic-audit', file_id='repomatic-audit', scope=<RepoScope.ALL: 1>, config_key='', 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='', 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='', 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='', 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='', 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='', 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='', reusable=True, phase='Development', tree=True), FileEntry(source='skills/repomatic-upgrade', target='.claude/skills/repomatic-upgrade', file_id='repomatic-upgrade', scope=<RepoScope.ALL: 1>, config_key='', reusable=True, phase='Maintenance', 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='', 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='', 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='', reusable=True, phase='Maintenance', tree=True)), config_key='', 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='', 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='', 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='', reusable=True, phase='', tree=False)), config_key='', ephemeral=False, location_field='subagents_location'), 'typos': ToolConfigComponent(name='typos', description='Typos spell checker configuration', init_default=<InitDefault.EXPLICIT: 3>, scope=<RepoScope.ALL: 1>, files=(), config_key='', ephemeral=False, location_field='', source_file='typos.toml', tool_section='tool.typos', sync_mode=<SyncMode.ONGOING: 2>, customizable_entries=(), legacy_table='', preserved_keys=(), graft_identity_keys=(), overlay=False), 'uv': ToolConfigComponent(name='uv', description='uv resolver pin and dependency cooldown policy', init_default=<InitDefault.EXPLICIT: 3>, scope=<RepoScope.ALL: 1>, files=(), config_key='', ephemeral=False, location_field='', source_file='uv.toml', tool_section='tool.uv', sync_mode=<SyncMode.ONGOING: 2>, customizable_entries=(), legacy_table='', 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='', reusable=True, phase='', tree=False), FileEntry(source='autolock.yaml', target='.github/workflows/autolock.yaml', file_id='autolock.yaml', scope=<RepoScope.ALL: 1>, config_key='', 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='', 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='', 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', reusable=True, phase='', tree=False), FileEntry(source='docs.yaml', target='.github/workflows/docs.yaml', file_id='docs.yaml', scope=<RepoScope.ALL: 1>, config_key='', reusable=True, phase='', tree=False), FileEntry(source='labels.yaml', target='.github/workflows/labels.yaml', file_id='labels.yaml', scope=<RepoScope.ALL: 1>, config_key='', reusable=True, phase='', tree=False), FileEntry(source='lint.yaml', target='.github/workflows/lint.yaml', file_id='lint.yaml', scope=<RepoScope.ALL: 1>, config_key='', 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', 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='', reusable=True, phase='', tree=False), FileEntry(source='tests.yaml', target='.github/workflows/tests.yaml', file_id='tests.yaml', scope=<RepoScope.ALL: 1>, config_key='', 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', reusable=True, phase='', tree=False)), config_key='', ephemeral=False, location_field='')}¶
Index for O(1) component lookup by name.
- repomatic.components.REMOVED_ASSETS: tuple[RemovedAsset, ...] = (RemovedAsset(component='codecov', target='.github/codecov.yaml', removed_in='7.8.0', 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', hashes=('1f3e670c0b4c6687a8920fb3738a15fb82b8639b7825d81f76c55bc5784cdb08', 'adf62c78c539229d34d4d2518a9af7f39df44d599c60852784b0faa47a6defa9', '5cf481b4aec2bf98a4056757f41ef5fc50f808dbd7c8a43f1dea0b224ecb7f1f', '8a047d53d5449ea0b53517e2f63e126360050127342084b7a705f34fb735d818'), owned_dir='', successor='rules now live in repomatic.github.labels.DEFAULT_CONTENT_RULES'), RemovedAsset(component='labels', target='.github/labeller-file-based.yaml', removed_in='7.11.0', hashes=('9dc0948e23a3a83d2cec5f11e400c75992fb1ce326eb6c5811c1fc3bfe258b31', 'b216d370e4d2c6118f46d9bb2eacaf91392e6a6267a4f4857f44a698422cc860', '9a4feeb49c37ee7eba1d13957d26aaaa867c791ec12be8cd4197e7526bfbf963'), owned_dir='', successor='rules now live in repomatic.github.labels.DEFAULT_FILE_RULES'), RemovedAsset(component='workflows', target='.github/workflows/renovate.yaml', removed_in='7.0.0', 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).initprunes orphaned copies of these from downstream repos. Ordered by(component, target). A tombstone stays for the major releases a downstream repository may still upgrade from: the ones of assets dropped before7.0.0are gone.When you drop a bundled asset from
COMPONENTS, add an entry here so the removal propagates downstream on the nextinitinstead of leaving an orphan. List one digest per distinct content the asset shipped across its released lifetime, collected from the release tags where its data file existed:import subprocess from repomatic.components import RemovedAsset src = "repomatic/data/skills/papaya-report/SKILL.md" # The dropped data file. tags = subprocess.run( ["git", "tag", "--list", "v*"], capture_output=True, text=True, encoding="UTF-8", check=True, ).stdout.split() digests = {} for tag in tags: blob = subprocess.run( ["git", "show", f"{tag}:{src}"], capture_output=True, text=True, encoding="UTF-8", ) if blob.returncode == 0: digests.setdefault(RemovedAsset.digest(blob.stdout), tag) print(tuple(digests)) # Distinct contents, in first-shipped order.
A skill ships as a folder: give its
SKILL.mdastargetand its folder asowned_dir, both through_skill_targetand_skill_dir. Removed workflows are fingerprint-gated, not hashed: declare them through_removed_workflow, which omitshashes.
- repomatic.components.BARE_INIT_COMPONENTS: frozenset[str] = frozenset({'awesome-template', 'changelog', 'labels', 'lychee', 'plugin', 'publish-pypi-action', 'skills', 'subagents', 'workflows'})¶
Components a bare
repomatic initconsiders, before any exclusion.
- repomatic.components.DEFAULT_EXCLUDED_COMPONENTS: frozenset[str] = frozenset({'labels', 'plugin', 'skills', 'subagents'})¶
Components a bare
repomatic initexcludes unless the user includes them.
- repomatic.components.EPHEMERAL_COMPONENTS: frozenset[str] = frozenset({'labels'})¶
Components whose files are regenerated on demand (see
Component.ephemeral).A bare
repomatic initexcludes them, and[tool.repomatic] includecannot bring them back: only naming one on the CLI writes its files out.
- repomatic.components.EPHEMERAL_TARGETS: frozenset[str] = frozenset({'labels.toml'})¶
Target paths belonging to
Component.ephemeralcomponents.Written only when the component is named explicitly on the CLI, and never worth committing: whatever reads them regenerates them first.
inituses this to keep its closing “commit the generated files” advice off a run that produced nothing but scratch output.
- repomatic.components.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/probe-workflow', '.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/repomatic-upgrade', '.claude/skills/sphinx-docs-sync', '.claude/skills/translation-sync', '.claude/skills/upstream-audit', '.github/actions/publish-pypi/action.yaml', 'labels.toml'})¶
Target paths
repomatic initwrites verbatim from arepomatic/data/template.Every
BundledComponentcopies its bundled source byte-for-byte to the target, so downstream the file’s content (including any SHA-pinneduses:ref) is owned byrepomatic init.sync-action-pinsandsync-workflow-pinsskip these paths for the same reason they skipUPSTREAM_REPO_SLUGS: a pin the nextsync-repomaticoverwrites 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 (seerepomatic.deps.sync._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.components.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_calltriggers.
- repomatic.components.NON_REUSABLE_WORKFLOWS: frozenset[str] = frozenset({'tests.yaml'})¶
Workflows without
workflow_callthat cannot be used as thin callers.
- repomatic.components.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.components.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 generatedrelease.yamlcalls isRELEASE_ENGINE_WORKFLOWS.
- repomatic.components.RELEASE_ENGINE_WORKFLOWS: tuple[str, ...] = ('_release-build.yaml', '_release-engine.yaml')¶
Reusable workflows the generated
release.yamlreferences but thatrepomatic initnever materializes downstream.The
workflowscomponent deploys a generatedrelease.yaml(not a thin delegation): itsbuildjob calls_release-build.yamland itsreleasejob 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 notFileEntrytargets and never appear inALL_WORKFLOW_FILES.The release entry’s
FileEntrystill records_release-engine.yamlas itssource(seeWORKFLOW_SOURCES) so the generic backing-reusable tests and a downstreamrepomatic lintcan read it viaget_data_contentto check the engine lane forwards its secrets;_release-build.yamlis 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.components.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 auses:ref at a release tag, these are invisible outside this repository: they are notFileEntrytargets, carry norepomatic/data/symlink, and nothing resolves them at runtime. That is what lets their jobs drop thegithub.repository == 'kdeldycke/repomatic'guard every in-autofix.yamlupstream-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/registry.pyand friends). A workflow that merely behaves differently upstream does not: it still ships, so it still needs the runtime guard.
- repomatic.components.FILE_SELECTOR_COMPONENTS: tuple[str, ...] = ('labels', 'publish-pypi-action', 'subagents', 'skills', 'workflows')¶
Components that support file-level
component/fileselectors.
- repomatic.components.parse_component_entries(entries, *, context='entry')[source]¶
Parse component entries into full-component and file-level sets.
Bare names (no
/) must be component names fromCOMPONENTS_BY_NAME. Qualifiedcomponent/identifierentries target individual files. RaisesValueErroron unknown entries.Used by both the
excludeconfig path and the CLI positional selection, with context controlling error message wording.