tests.test_init_project module¶
Tests for bundled configuration templates and repository initialization.
- tests.test_init_project.written_file_count(entry)[source]¶
Count the files one registry entry writes: a whole tree, else one.
A tree entry is a skill folder, which can ship
references/pages and other resources beside itsSKILL.md. A test that counted the entry itself would fail the day a skill gains one.- Return type:
- tests.test_init_project.toml_file(tmp_path: Path)[source]¶
Return a factory writing TOML content to a scratch
pyproject.toml.Replaces the
NamedTemporaryFile(delete=False)plus manualunlink()dance: pytest removestmp_pathwhether or not the test passes, so a failing assertion no longer leaks the file. Each call gets its own directory, so a test may seed several projects.
- tests.test_init_project.pin_build(monkeypatch: MonkeyPatch)[source]¶
Return a factory pinning the running build
initreports itself as.run_initandresolve_default_pinread the packageās__version__and__git_tag_sha__to decide which upstream ref to write downstream, so almost every pin test has to stage both.
- tests.test_init_project.test_all_component_types_handled()[source]¶
Verify every component in the registry has a handled type.
The type-driven dispatch loop in
run_inithandles these types. If a new component subclass is added without updating the dispatch, this test will catch it.- Return type:
- tests.test_init_project.test_composite_actions_keep_unmodified()[source]¶
Composite actions under
.github/actions/must setkeep_unmodified=True.GitHub Actions resolves
uses: ./.github/actions/Xanduses: owner/repo/.github/actions/X@refdirectly from the repo path. The action file must remain on disk even when byte-identical to the bundled default; otherwise the autofix workflow deletes it as āredundantā and breaks every caller (upstream and downstream).- Return type:
- tests.test_init_project.test_repo_scope_matches(scope, is_awesome, is_python, is_package, expected)[source]¶
Verify RepoScope.matches returns correct results for all combinations.
- Return type:
- tests.test_init_project.test_init_help_lists_all_components()[source]¶
Verify the init command help text lists every registered component.
- Return type:
- tests.test_init_project.test_supported_config_types()[source]¶
Verify that expected config types are registered as ToolConfigComponent.
- Return type:
- tests.test_init_project.test_config_type_has_required_fields()[source]¶
Verify that each tool config component has all required fields.
- Return type:
- tests.test_init_project.test_returns_non_empty_string(config_type)[source]¶
Verify that export_content returns a non-empty string.
- Return type:
- tests.test_init_project.test_returns_valid_toml(config_type)[source]¶
Verify that the returned content is valid TOML.
- Return type:
- tests.test_init_project.test_native_format_no_tool_prefix(config_type)[source]¶
Verify that native format does not have [tool.X] prefix.
- Return type:
- tests.test_init_project.test_unknown_file_raises_error()[source]¶
Verify that an unknown file raises ValueError.
- Return type:
- tests.test_init_project.test_exportable_file_loadable(filename)[source]¶
Verify every registered data file can be loaded (no dangling symlinks).
- Return type:
- tests.test_init_project.test_native_templates_separate_header(comp)[source]¶
Verify a templateās file-level header ends with a blank line.
The separator is what
repomatic.init_project._strip_header_commentskeys on to tell a header apart from a comment documenting the first key, the two being otherwise identical: a comment run at the top of the file. Running the header straight into a key hides the keyās own documentation behind the same rule that drops the header, so it never reaches a downstreampyproject.toml.- Return type:
- tests.test_init_project.test_strip_header_comments_keeps_key_comments(comp)[source]¶
Verify stripping removes the header and every comment below it survives.
The second half is the one that bites: skipping to the first non-comment line also passes the āheader is goneā check, while silently taking the first keyās documentation with it.
- Return type:
- tests.test_init_project.test_template_matches_own_pyproject(config_type)[source]¶
Verify bundled template stays in sync with repomaticās own config.
Template keys (minus intentional exclusions) must match the corresponding
[tool.*]section. List keys marked as superset require every template entry to appear in own config, but own config may have extras.Scope-specific configs (e.g., lychee for awesome repos) are excluded because their templates are intentionally different from repomaticās own.
- Return type:
- tests.test_init_project.test_template_setting(template, key, expected)[source]¶
A bundled template pins the setting every downstream repo inherits.
- Return type:
- tests.test_init_project.test_template_list_contains(template, key, member)[source]¶
A bundled template carries the entry downstream repos depend on.
- Return type:
- tests.test_init_project.test_pytest_template_registers_once_marker()[source]¶
The
oncemarker the test workflow filters on must be declared.- Return type:
- tests.test_init_project.test_bumpversion_template_has_required_settings()[source]¶
Bumpversion needs these three keys to run unattended in CI.
- Return type:
- tests.test_init_project.test_bumpversion_template_declares_files()[source]¶
A bumpversion config with no file patterns would bump nothing.
- Return type:
- tests.test_init_project.BARE_PYPROJECT = '[project]\nname = "test"\nversion = "0.1.0"\n'¶
A minimal PEP 621 project, the starting point for every merge test.
- tests.test_init_project.test_init_config_merges_template(toml_file, config_type, expected)[source]¶
Merging a template into a bare pyproject keeps its native TOML shape.
- Return type:
- tests.test_init_project.test_init_config_lychee_preserves_other_sections(toml_file)[source]¶
Lychee init merges [tool.lychee] without stripping unrelated sections.
- Return type:
- tests.test_init_project.test_adopted_ongoing_configs(tmp_path, content, expected)[source]¶
A section already on disk re-enters the bare-init set, if it is ONGOING.
EXPLICITkeepsinitfrom pushing a tool config onto a repository that never asked for one; it must not also stop the ongoing sync of a section the repository already carries.BOOTSTRAPtemplates stay out either way: the repository owns them outright after the first write.- Return type:
- tests.test_init_project.test_adopted_ongoing_configs_without_pyproject(tmp_path)[source]¶
A repository with no pyproject.toml adopts nothing.
- Return type:
- tests.test_init_project.test_adopted_ongoing_configs_are_explicit_and_ongoing(tmp_path)[source]¶
Whatever the helper returns is an EXPLICIT, ONGOING tool config.
Guards the invariant against a registry edit that flips a componentās
init_defaultorsync_modewithout revisiting this selection path.- Return type:
- tests.test_init_project.test_uv_component_uses_overlay_ongoing()[source]¶
The uv tool config is an ongoing overlay, not a full-section rebuild.
- Return type:
- tests.test_init_project.test_init_config_uv_overlay_noop_when_pins_match(toml_file)[source]¶
A [tool.uv] already carrying the canonical pins is a no-op.
The overlay updates owned keys in place, so a section whose pins already match the template returns no change. A regression to the rebuild-and-graft path would reorder the owned keys ahead of the projectās keys and return a diff, failing this fixpoint guard.
- Return type:
- tests.test_init_project.test_init_config_uv_overlay_updates_stale_pins_in_place(toml_file)[source]¶
Stale uv pins are updated in place, preserving order and local keys.
- Return type:
- tests.test_init_project.test_init_config_uv_overlay_appends_missing_pins(toml_file)[source]¶
A [tool.uv] lacking the pins gets both appended, local keys intact.
- Return type:
- tests.test_init_project.test_full_ruff_init(toml_file)[source]¶
Verify full ruff config initialization.
- Return type:
- tests.test_init_project.test_adds_config_to_empty_pyproject(toml_file)[source]¶
Verify that config is added to a pyproject.toml without the section.
- Return type:
- tests.test_init_project.test_adds_bumpversion_with_array_sections(toml_file)[source]¶
Verify that bumpversion [[files]] sections are transformed.
- Return type:
- tests.test_init_project.test_returns_none_if_section_exists(toml_file)[source]¶
Verify that None is returned if the section already exists.
- Return type:
- tests.test_init_project.test_preserves_existing_content(toml_file)[source]¶
Verify that existing content is preserved when adding config.
- Return type:
- tests.test_init_project.test_unknown_config_type_raises_error()[source]¶
Verify that an unknown config type raises TypeError.
- Return type:
- tests.test_init_project.test_default_version_pin()[source]¶
Verify version pin is derived correctly.
- tests.test_init_project.test_select_cooldown_pin_no_aged_predecessor()[source]¶
A fresh base with no cleared predecessor has nothing to step back to.
- tests.test_init_project.test_resolve_default_pin_aged_version_is_noop(monkeypatch, pin_build)[source]¶
An aged running version pins itself with its build SHA, no step-back.
Locks in the CI sync-repomatic invariant: init at the already-pinned version never moves the pin and never resolves a replacement SHA.
- tests.test_init_project.test_resolve_default_pin_steps_back_when_fresh(pin_build)[source]¶
A fresh running version steps the pin back, re-resolves the SHA, and warns.
The step-back note is appended to the caller-supplied warnings list so
run_initcan surface it in the init summary, not only in the log.
- tests.test_init_project.test_resolve_default_pin_aged_version_leaves_warnings_empty(pin_build)[source]¶
The no-op path appends no warning.
- tests.test_init_project.test_resolve_default_pin_dev_version_skips_datasource(monkeypatch, pin_build)[source]¶
An unreleased dev cut pins its base version without any network call.
- tests.test_init_project.test_resolve_default_pin_zero_window_skips_datasource(monkeypatch, pin_build)[source]¶
A disabled cooldown (0 days) pins the running version without a lookup.
- tests.test_init_project.test_resolve_default_pin_fails_open_when_datasource_empty(monkeypatch, pin_build)[source]¶
An unreachable datasource falls back to the running version.
- tests.test_init_project.test_highest_upstream_pin_none_without_refs(tmp_path)[source]¶
A repository carrying no upstream ref has no floor.
- tests.test_init_project.test_highest_upstream_pin_converges_stragglers(tmp_path)[source]¶
The repository-wide maximum wins, so a lagging file cannot drag the pin down.
- tests.test_init_project.test_highest_upstream_pin_prefers_a_hardened_ref(tmp_path)[source]¶
At equal versions the SHA-pinned ref wins, so the floor never unpins one.
- tests.test_init_project.test_highest_upstream_pin_reads_composite_actions(tmp_path)[source]¶
Composite-action refs count too:
release.yamlpins one alongside the lanes.
- tests.test_init_project.test_resolve_default_pin_lifecycle(label, base, floor, expected, monkeypatch)[source]¶
The cooldown gates an adoption and never regresses a committed pin.
The steady-state rows are the ones that matter in CI:
sync-repomaticrunsinitat the pinned version on every push, so a cooldown verdict recomputed there would downgrade the repository for a full window after each bump.
- tests.test_init_project.test_resolve_default_pin_matching_floor_skips_datasource(monkeypatch, pin_build)[source]¶
A pin equal to the running version is answered without a network call.
Every CI
sync-repomaticrun takes this path, so it must not depend on the GitHub releases datasource being reachable.
- tests.test_init_project.test_resolve_default_pin_matching_floor_keeps_on_disk_sha(pin_build)[source]¶
A build with no tag SHA of its own falls back to the pin already on disk.
Without the fallback, regenerating from a source checkout would rewrite a hardened
@<sha> # vX.Y.Zref into a bare tag pin, and nothing downstream re-hardens an upstream ref.
- tests.test_init_project.test_resolve_default_pin_held_at_floor_warns(pin_build)[source]¶
Declining to adopt a fresh release is reported, naming the kept version.
- tests.test_init_project.test_run_init_held_at_floor_leaves_workflows_untouched(tmp_path, pin_build)[source]¶
Declining the pin declines the caller content with it.
initrenders workflow bodies from the running wheel and substitutes only theuses:ref, so regenerating them under a held-back pin ships the new releaseās triggers,concurrencygroups andenv:against the old releaseās reusable-workflow surface. Half an adoption is worse than none: the caller half lands with nothing pinned to serve it.
- tests.test_init_project.test_run_init_first_adoption_writes_workflows_despite_step_back(tmp_path, pin_build)[source]¶
A repository with no pin yet still gets workflows when the cooldown bites.
The counterpart carve-out: holding the content back needs somewhere to stand, and a first-time adoption has no prior tree to keep. Skipping here would leave the repository with no workflows at all, so the pin/content skew stands as the lesser outcome.
- tests.test_init_project.test_run_init_keeps_a_fresh_pin_already_on_disk(tmp_path, monkeypatch, pin_build)[source]¶
Re-running
initat the pinned version regenerates without downgrading.End-to-end shape of the CI lifecycle: a maintainer adopts a release on its publication day, then
sync-repomaticre-runsinitat that very version on every push. The pin must survive untouched.
- tests.test_init_project.test_run_init_records_the_pin_move(tmp_path, pin_build)[source]¶
The result carries both halves of a pin move, and only a forward move.
A first adoption has no previous release, a regeneration at the pinned version moves nothing, and a rollback is deliberate: none of the three is an upgrade to review, which is what the closing hint keys on.
- tests.test_init_project.test_run_init_reads_no_pin_without_a_workflow(tmp_path, pin_build)[source]¶
A config-only run neither reads nor writes an upstream pin.
- tests.test_init_project.test_init_cli_prints_the_upgrade_launcher(tmp_path, pin_build)[source]¶
A moved pin ends the run with the command launching the upgrade review.
The launcher names both versions so the skill needs no discovery, and the audit follows it because the two answer different questions: what the release offers, then what the repository drifted from.
- tests.test_init_project.test_init_cli_skips_the_install_hint_once_the_skill_is_on_disk(tmp_path, pin_build)[source]¶
The install line is for repositories that never installed the skill.
- tests.test_init_project.test_init_cli_prints_no_launcher_without_a_pin_move(tmp_path, pin_build)[source]¶
A first adoption has no previous release to review against.
- tests.test_init_project.test_run_init_no_cooldown_skips_datasource(tmp_path, monkeypatch, pin_build)[source]¶
āno-cooldown pins the running version and never consults the datasource.
- tests.test_init_project.test_resolve_upgrade_target(tmp_path, monkeypatch, pin_build, floor, version, released, previous, held_back)[source]¶
- tests.test_init_project.test_resolve_upgrade_target_opt_outs(tmp_path, monkeypatch, pin_build, config)[source]¶
Both opt-outs skip the upgrade before any datasource lookup.
- tests.test_init_project.test_resolve_upgrade_target_skips_development_builds(tmp_path, monkeypatch, pin_build)[source]¶
A development build has no published release to compare against.
- tests.test_init_project.test_resolve_upgrade_target_without_a_cleared_release(tmp_path, monkeypatch, pin_build, candidates)[source]¶
No release past the window, or no release data, leaves the pin alone.
- tests.test_init_project.test_upgrade_target_in_process(pin_build)[source]¶
Only a target naming the running release skips the hand-off.
- tests.test_init_project.test_upgrade_command()[source]¶
The child runs the target with the parentās verdict and the window.
- tests.test_init_project.test_upgrade_command_without_a_window()[source]¶
A disabled window passes no cutoff, and the exemption keeps the target.
- tests.test_init_project.test_upgrade_report(tmp_path, monkeypatch)[source]¶
The report reads top-down: the move, what breaks, the run, the notes.
- tests.test_init_project.test_upgrade_report_without_a_move(tmp_path, monkeypatch, previous, adopted)[source]¶
A run that moved no pin forward reports nothing, and fetches nothing.
- tests.test_init_project.test_upgrade_report_without_breaking_entries(tmp_path, monkeypatch, notes, expected)[source]¶
- tests.test_init_project.test_init_upgrade_hands_off_to_the_newest_cleared_release(tmp_path, monkeypatch, pin_build)[source]¶
A newer release runs as a child, and the report names both ends.
- tests.test_init_project.test_init_upgrade_fails_with_the_child(tmp_path, monkeypatch, pin_build)[source]¶
A failed child fails the command, and no report claims an upgrade.
- tests.test_init_project.test_init_upgrade_runs_in_process_on_the_target(tmp_path, monkeypatch, pin_build)[source]¶
On the target already: no child, and no second cooldown verdict.
- tests.test_init_project.test_run_init_preserves_downstream_release_needs(tmp_path, pin_build)[source]¶
repomatic initcarries a consumerās ownneeds:edge across a sync.Asserted on the entry point
sync-repomaticactually runs, which is what a second renderer once made necessary: two copies of the same logic rendered the caller, only one was wired up, and the edge survived in the copy nothing called.
- tests.test_init_project.test_run_init_is_idempotent(tmp_path, pin_build)[source]¶
A second
initover its own output rewrites nothing.sync-repomaticre-runsiniton every push tomain, so any non- convergent render becomes an unbounded drift that opens a fresh PR forever. Checked with downstream extras in the tree, which is where the separator between the managed lanes and those extras is re-derived.
- tests.test_init_project.test_init_syncs_header_and_keeps_downstream_jobs(tmp_path, monkeypatch)[source]¶
A non-reusable workflow gets the canonical header, keeping its own jobs.
tests.yamlhas noworkflow_calltrigger, so it is never rendered as a caller: only everything above itsjobs:line is replaced.
- tests.test_init_project.test_init_skips_a_non_reusable_workflow_absent_downstream(tmp_path, monkeypatch)[source]¶
Header-only sync never creates a file, it only updates one already there.
The downstream repo owns the jobs of a non-reusable workflow, so there is nothing to write when it has not adopted the workflow at all.
- tests.test_init_project.test_init_realigns_an_inline_pin_in_downstream_jobs(tmp_path, monkeypatch, pinned)[source]¶
The inline pin follows the
uses:refsinitwrites, in either direction.The literal sits in a job body header-only sync never rewrites, so nothing else in this pass would touch it and the lint would go red until the next scheduled
sync-workflow-pinsrun.
- tests.test_init_project.test_init_adds_a_missing_cooldown_exemption(tmp_path, monkeypatch, pinned)[source]¶
A bare self-pin gets the exemption, or the command cannot resolve at all.
Every workflow exports a
UV_EXCLUDE_NEWER, and the pininitwrites moves in lockstep with theuses:refs regardless of the cooldown, so it routinely names a release younger than that window. A bareuvx 'repomatic==X.Y.Z'left behind here fails to install the very version just written.
- tests.test_init_project.test_init_warns_about_a_metadata_key_this_version_dropped(tmp_path, monkeypatch)[source]¶
A workflow asking for a retired key is reported before the commit.
An unknown key is a hard
UsageError, so the mismatch takes down the metadata job and every job hanging off it on the first push.
- tests.test_init_project.test_init_skips_the_metadata_key_check_behind_a_cooldown(tmp_path, monkeypatch)[source]¶
A pin held back to an older release is judged by no key set at all.
The older releaseās keys cannot be read from this process, and this versionās would blame a workflow that works.
Only the upstream toolkitās own literal is realigned, not every
==pin.
- tests.test_init_project.test_init_leaves_a_headerless_workflow_untouched(tmp_path, monkeypatch)[source]¶
Without a
jobs:line there is no boundary to splice the header onto.
- tests.test_init_project.test_init_header_sync_honors_paths_spec(tmp_path, monkeypatch)[source]¶
The synced header carries the repositoryās own
paths:filters.
- tests.test_init_project.test_run_init_cooldown_steps_back_in_generated_pin(tmp_path, monkeypatch, pin_build)[source]¶
A fresh running version lands the stepped-back tag in the thin caller.
- tests.test_init_project.test_init_default_components()[source]¶
Verify default selection includes expected components.
- tests.test_init_project.test_init_creates_all_default_files(tmp_path, monkeypatch)[source]¶
Verify all default component files are created (with no exclusions).
- tests.test_init_project.test_init_creates_changelog(tmp_path)[source]¶
Verify changelog is created with expected content.
- tests.test_init_project.test_init_creates_parent_dirs(tmp_path)[source]¶
Verify .github/workflows/ is created automatically.
- tests.test_init_project.test_init_workflow_paths_config_flows_to_generated_files(tmp_path)[source]¶
[tool.repomatic.workflow]paths knobs reach the generated workflows.End-to-end check that a
Configcarryingextra_paths,ignore_paths, and per-workflowpathsoverrides is honored by_init_workflowswhen rendering thin callers.
- tests.test_init_project.test_init_workflow_sync_disabled_leaves_workflows_untouched(tmp_path)[source]¶
[tool.repomatic.workflow] sync = falseskips workflow generation.Like the other
*.synctoggles, the opt-out writes nothing and leaves any existing workflow file untouched rather than overwriting or deleting it. Applies even whenworkflowsis named explicitly.
- tests.test_init_project.test_init_existing_changelog_skipped(tmp_path)[source]¶
Verify existing changelog is not overwritten.
- tests.test_init_project.test_init_idempotent(tmp_path)[source]¶
Verify a second run with no on-disk changes produces no updates.
Re-running
repomatic initagainst an unchanged tree must be a no-op: files identical to whatinitwould write are not reported as updated.
- tests.test_init_project.test_init_per_component_idempotent(tmp_path, component)[source]¶
Verify re-running a single-component init produces no spurious updates.
Locks in the per-component no-op behavior: when bundled content matches what is already on disk, the second run must not report any file as
updated. Regression guard for skills/workflows wrongly flagged as updated when their content was unchanged.
- tests.test_init_project.test_init_only_labels(tmp_path)[source]¶
Verify only label files are created.
- tests.test_init_project.test_init_only_skills(tmp_path)[source]¶
Verify only skill files are created.
Scope exclusions are bypassed when components are explicitly requested, so every skill is created, awesome-only ones included. The count is read off the registry rather than written here: a literal makes adding a skill fail a test that has nothing to say about it.
- tests.test_init_project.test_skills_consistency()[source]¶
Verify all .claude/skills/ directories have matching entries in code.
Guards against adding a skill definition to
.claude/skills/without registering it in the component registry,SKILL_PHASES, and therepomatic/data/symlinks.
- tests.test_init_project.test_every_skill_phase_is_orderable()[source]¶
list-skillsorders its rows bySKILL_PHASE_ORDER.index().The two constants are declared apart: one names the phase of each registry entry, the other the lifecycle order they render in. A phase in the first and not the second used to drop its skill from the listing without a word, and now raises instead, so the roster can no longer go quietly short.
- tests.test_init_project.test_init_only_workflows(tmp_path)[source]¶
Verify only workflow files are created.
Pinned to a default
Configrather than the ambient one: config discovery walks up from the working directory, so this repositoryās own[tool.repomatic]would otherwise decide which opt-in workflows the assertions below expect, and enabling one here would fail the test.
- tests.test_init_project.test_init_always_overwrites_managed_files(tmp_path)[source]¶
Verify managed files are always replaced on re-run.
- tests.test_init_project.test_init_changelog_never_overwritten(tmp_path)[source]¶
Verify an existing changelog.md is never overwritten.
- tests.test_init_project.test_init_tool_configs_no_pyproject(tmp_path)[source]¶
Verify warning when pyproject.toml is missing.
- tests.test_init_project.test_init_version_pinned(tmp_path)[source]¶
Verify generated workflows contain the specified version pin.
- tests.test_init_project.test_init_with_specific_tool_configs(tmp_path)[source]¶
Verify multiple tool configs are merged into pyproject.toml.
- tests.test_init_project.test_init_with_single_tool_config(tmp_path)[source]¶
Verify only requested tool config is merged.
- tests.test_init_project.test_syncs_bumpversion_template_keys(tmp_path)[source]¶
Verify template keys are synced into existing bumpversion config.
- Return type:
- tests.test_init_project.test_replaces_bumpversion_files_from_template(tmp_path)[source]¶
Verify [[tool.bumpversion.files]] entries come from the template.
- Return type:
- tests.test_init_project.test_preserves_other_pyproject_sections(tmp_path)[source]¶
Verify [project] and other sections are unchanged.
- Return type:
- tests.test_init_project.test_skips_already_up_to_date(tmp_path, toml_file)[source]¶
Verify config matching the template returns None (no changes needed).
- Return type:
- tests.test_init_project.test_bumpversion_template_is_pyproject_fmt_fixed_point(tmp_path)[source]¶
sync-bumpversionoutput must already be a fixed point of pyproject-fmt.sync-bumpversionrewrites[tool.bumpversion]from the bundled template, whileformat-pyprojectreorders the same keys with pyproject-fmt. When the templateās key order diverges from pyproject-fmtās, the two autofix jobs ping-pong: one PR imposes the template order, the next reverts it.The in-tree
pyproject.tomlhas been processed byformat-pyproject, so its[tool.bumpversion]block is the canonical pyproject-fmt output. Asserting the freshly merged template equals it textually (modulocurrent_version, the only preserved key) locks the order. Unliketest_template_matches_own_pyproject, which compares parsed dicts, this compares text, so it catches key-order drift.- Return type:
- tests.test_init_project.test_ongoing_sync_template_survives_pyproject_fmt(config_type, tmp_path)[source]¶
Templates re-synced into pyproject.toml must be pyproject-fmt fixed points.
A config that
repomatic initre-merges on every push (sync_mode=ONGOING, via thesync-bumpversionjob or a barerepomatic initinsync-repomatic) sharespyproject.tomlwithformat-pyproject. If its bundled template is not already in pyproject-fmtās canonical form, the two autofix jobs ping-pong: one rewrites the section, the next reformats it.This merges each template into a throwaway
pyproject.toml, runs the pinned pyproject-fmt over it, and asserts the[tool.X]block is unchanged. It is the category guard for the bumpversion ā format-pyproject loop, and also covers scope-specific templates (lychee, opted into a Python project) that have no in-tree section to compare against.Marked
once: it shells out to pyproject-fmt via uvx, so a single runner suffices. Skipped when pyproject-fmt cannot be fetched (offline local dev); CI is the enforcement point.- Return type:
- tests.test_init_project.test_ongoing_resync_preserves_local_string_style(config_type, tmp_path)[source]¶
Re-syncing an adopted config must not restyle what the project wrote.
The sibling
test_ongoing_sync_template_survives_pyproject_fmtmerges each template into an empty file, so it only ever sees the templateās own text. The ping-pong this guards needs a local addition: iterating a tomlrt array yields decoded values rather than the nodes carrying their source lexeme, so rebuilding the array re-emits every item in tomlrtās style. A local'base32 "[0-9a-z]{52}"', which pyproject-fmt writes as a literal string precisely because it contains a quote, came back as an escaped"base32 \"[0-9a-z]{52}\"". Same content, soformat-pyprojectrewrote it straight back, and the two unattended jobs opened pull requests undoing each other indefinitely.Offline by construction: it asserts the invariant that makes the file a fixed point rather than shelling out to pyproject-fmt to observe one.
- Return type:
- tests.test_init_project.test_replaces_old_changelog_pattern(tmp_path)[source]¶
Verify old changelog pattern without backticks is replaced by template.
- Return type:
- tests.test_init_project.test_bumpversion_update_idempotent(tmp_path)[source]¶
Verify running update twice produces the same result.
- Return type:
- tests.test_init_project.test_bumpversion_update_valid_toml(tmp_path)[source]¶
Verify updated pyproject.toml is still valid TOML.
- Return type:
- tests.test_init_project.test_preserves_local_array_entries(tmp_path)[source]¶
Verify local [[tool.bumpversion.files]] entries survive ongoing sync.
- Return type:
- tests.test_init_project.test_ongoing_sync_idempotent_with_local_entries(tmp_path)[source]¶
Verify ongoing sync with local entries is idempotent after first run.
- Return type:
- tests.test_init_project.test_ongoing_sync_no_duplicate_template_entries(tmp_path)[source]¶
Verify template entries are not duplicated when also present locally.
- Return type:
- tests.test_init_project.test_evolved_canonical_entry_superseded_not_duplicated(tmp_path)[source]¶
Verify a stale copy of a canonical entry is superseded, not duplicated.
The [project] version entry gained a regex anchor (so it cannot bleed into
[tool.nuitka]ās file-/product-version keys). A downstream repo still carrying the old unanchored form must converge on the single canonical anchored entry rather than ending up with both, which would break the bump.- Return type:
- tests.test_init_project.test_local_entries_preserved_via_update(tmp_path)[source]¶
Verify local array entries survive ongoing sync via dict comparison.
- Return type:
- tests.test_init_project.test_local_entry_comments_preserved(tmp_path)[source]¶
Verify comments between local [[files]] entries survive ongoing sync.
Regression test for the sync-bumpversion job stripping comments from downstream repos (e.g., click-extra#1595).
Note
tomlkit stores comments preceding the first local entry in the parent table body, not in the AoT. These are lost during section replacement. Comments between local entries survive because tomlkit attaches them to the preceding entryās trivia.
- Return type:
- tests.test_init_project.test_syncs_typos_identifiers_into_existing_section(tmp_path)[source]¶
Canonical identifiers merge into a pre-existing typos section.
Regression test for the BOOTSTRAP footgun: a downstream repo carrying its own
[tool.typos]never received the bundled proper-noun identifiers, because a BOOTSTRAP insert skipped the existing section and typos reads[tool.typos]natively (no bundled fallback at runtime).- Return type:
- tests.test_init_project.test_typos_preserves_local_inline_table_keys(tmp_path)[source]¶
Local keys inside a shared inline table survive alongside canonical ones.
- Return type:
- tests.test_init_project.test_typos_appends_local_excludes_after_template_ones(tmp_path)[source]¶
A local
files.extend-excludeentry survives after the canonical ones.- Return type:
- tests.test_init_project.test_typos_canonical_value_wins_on_conflict(tmp_path)[source]¶
Canonical capitalization overrides a wrong local target on shared keys.
- Return type:
- tests.test_init_project.test_typos_update_idempotent(tmp_path)[source]¶
The typos sync is a no-op on the second run.
- Return type:
- tests.test_init_project.LOCAL_ARRAY_ITEMS_WITH_COMMENTS = '[tool.typos]\ndefault.extend-ignore-re = [\n # Quoted from a storm forecast.\n "hail-warning",\n "rainbow", # Seen after the rain.\n]\n'¶
A local array lacking the canonical item, so the sync rebuilds it.
- tests.test_init_project.test_typos_carries_comments_on_local_array_items(tmp_path)[source]¶
A local
extend-ignore-reitem keeps its comments through a rebuild.A local array lacking a canonical item is rebuilt from the template, and each local item is appended to it as a decoded value: the comment block above the item and the comment after it have to be copied across.
- Return type:
- tests.test_init_project.test_typos_rebuilt_array_uses_pyproject_fmt_indent(tmp_path)[source]¶
A rebuilt multi-line array is indented the way
pyproject-fmtwrites it.The rebuild starts from the templateās single-line array. tomlrt breaks it into rows once a carried comment needs a line of its own, but at four spaces, which the
format-pyprojectjob re-indents to two in a pull request of its own.- Return type:
- tests.test_init_project.LOCAL_KEYS_WITH_COMMENTS = '[tool.typos]\n# Spelled the way the Lisbon office writes.\ndefault.locale = "en-gb" # Not the default "en".\n# Kept out of the forecast pages.\ncheck-filename = false # Station codes read as typos.\n'¶
Local-only keys whose comments live in the layout of their parent table.
- tests.test_init_project.test_typos_carries_comments_on_local_keys(tmp_path)[source]¶
A local key keeps the comment above it and the comment after it.
Both belong to the parent tableās layout rather than to the value, so a key grafted onto the rebuilt section arrives without them unless they are copied across. That holds for a key inside a table the template also defines (
default) and for one on the section itself.- Return type:
- tests.test_init_project.test_typos_sync_ignores_the_spaces_before_a_comment(tmp_path)[source]¶
A synced section stays a fixpoint once
pyproject-fmtrespaces it.A carried end-of-line comment is written one space after its value, and
pyproject-fmtmoves it to two. The next sync must not read that as a change, orsync-repomaticandformat-pyprojectundo each other forever.- Return type:
- tests.test_init_project.test_typos_merged_inline_tables_use_pyproject_fmt_spacing(tmp_path)[source]¶
Merged inline tables render
{ ... }, matching pyproject-fmt.A compact
{...}would be re-spaced by theformat-pyprojectautofix job, producing churn on every run.- Return type:
- tests.test_init_project.test_typos_skips_svg_content(tmp_path, monkeypatch)[source]¶
A word split across two
<text>runs survives a realfix-typospass.A terminal capture renders each run of same-styled characters as one
<text>element, so a word cut at a run boundary leaves a fragment the checker reads as a misspelling. Rewriting it in place doubles a letter and corrupts the picture, whichtype.svg.check-fileprevents.The plain-text control proves the dictionary still carries the fragment, so the SVG half cannot pass for the wrong reason once typos drops the entry.
- tests.test_init_project.test_init_default_excludes(tmp_path, monkeypatch)[source]¶
Verify default exclude skips agents, labels, and skills.
- tests.test_init_project.test_init_respects_exclude_components(tmp_path, monkeypatch)[source]¶
Verify exclude config skips listed components.
- tests.test_init_project.test_init_respects_exclude_workflow_files(tmp_path, monkeypatch)[source]¶
Verify exclude config with workflow file entries skips those workflows.
- tests.test_init_project.test_init_respects_exclude_skill_files(tmp_path, monkeypatch)[source]¶
Verify exclude config with skill file entries skips those skills.
- tests.test_init_project.test_init_changelog_excluded_for_awesome_repo(tmp_path, monkeypatch)[source]¶
Verify changelog.md is not created for awesome-* repos.
- tests.test_init_project.test_init_changelog_excluded_existing_for_awesome_repo(tmp_path, monkeypatch)[source]¶
Verify existing changelog.md is flagged as excluded for awesome-* repos.
- tests.test_init_project.test_init_scoped_component_excluded_existing_for_awesome_repo(tmp_path, monkeypatch)[source]¶
An existing out-of-scope component file is flagged as excluded, not created.
- tests.test_init_project.test_include_config_includes_all_skill_files(tmp_path, monkeypatch, repo_slug)[source]¶
Config
include = ["skills"]produces all skills regardless of repo type.
- tests.test_init_project.test_explicit_component_bypasses_scope_exclusion(tmp_path, monkeypatch)[source]¶
Explicit component request overrides scope exclusion.
repomatic init publish-pypi-actionin an awesome repo should produce.github/actions/publish-pypi/action.yamleven though the component hasscope=PACKAGE_ONLYand awesome repos build no distributable. Scope exclusions only apply during barerepomatic init(no explicit components).
- tests.test_init_project.test_include_config_bypasses_scope_exclusion(tmp_path, monkeypatch)[source]¶
Config
includebypasses scope exclusions, like CLI explicit naming.publish-pypi-actionis aBundledComponentwith files, scopedPACKAGE_ONLYandinit_default=INCLUDE. In an awesome repo, which builds no distributable, it would normally be scope-excluded. Withinclude, the scope bypass falls through to file-level checks (the file hasscope=ALL, so nothing is excluded).
- tests.test_init_project.test_include_bypasses_scope_for_tool_config(tmp_path, monkeypatch)[source]¶
Include bypasses scope for a
ToolConfigComponent.Lychee is
AWESOME_ONLY. In a non-awesome repo it would normally be scope-excluded. Withinclude, the scope bypass falls through to config-key and file-level checks, then the tool config is merged intopyproject.toml.
- tests.test_init_project.test_bare_init_applies_scope_exclusion(tmp_path, monkeypatch)[source]¶
Bare init without
includeor CLI components applies scope exclusions.
- tests.test_init_project.test_virtual_project_init_drops_exactly_the_release_lane(tmp_path, monkeypatch)[source]¶
A uv virtual project loses the release lane and keeps everything else.
Conformance test for the
PYTHON_ONLY/PACKAGE_ONLYsplit. Rather than naming the affected files (which would go stale the moment an entry moves between the two scopes), it inits the same repository twice, once as a distributable package and once as a virtual project, and asserts the difference is exactly the registryāsPACKAGE_ONLYpopulation.That pins both directions at once: nothing from the release lane survives into a repo that can never publish, and no
PYTHON_ONLYentry gets dragged out with it. A virtual project still locks dependencies and runs tests, so over-narrowing is as much a bug as under-narrowing.
- tests.test_init_project.test_file_level_include_bypasses_scope(tmp_path, monkeypatch)[source]¶
File-level
includeentry bypasses scope for that specific file only.
- tests.test_init_project.test_file_level_include_implies_parent_component(tmp_path, monkeypatch)[source]¶
File-level
includeimplicitly selects the parent component.include = ["skills/awesome-triage"]should select the skills component (which is default-excluded) and bypass scope for that specific file, without bypassing scope for other AWESOME_ONLY files.
- tests.test_init_project.test_exclude_overrides_include_scope_bypass(tmp_path, monkeypatch)[source]¶
Explicit
excludeentries take precedence overincludescope bypass.
- tests.test_init_project.test_publish_pypi_action_excluded_in_non_python_repo(tmp_path, monkeypatch)[source]¶
publish-pypi-actionis skipped when the repo is not a Python project.Reproduces the scenario where a non-Python repository (e.g., a dotfiles project) carries a
pyproject.tomlsolely for[tool.*]configuration. The composite action targets PyPI publishing, so it has no purpose there.
- tests.test_init_project.test_publish_pypi_action_included_in_python_repo(tmp_path, monkeypatch)[source]¶
publish-pypi-actionis installed when the repo is a Python project.
- tests.test_init_project.test_explicit_component_bypasses_python_only_scope(tmp_path, monkeypatch)[source]¶
Explicit CLI naming bypasses
RepoScope.PYTHON_ONLY.When the caller explicitly asks for
publish-pypi-action, the component is materialized regardless of repo type, matching the existing scope-bypass semantics for awesome-only components.
- tests.test_init_project.test_include_config_bypasses_python_only_scope(tmp_path, monkeypatch)[source]¶
[tool.repomatic] includebypassesRepoScope.PYTHON_ONLY.
- tests.test_init_project.test_init_respects_exclude_label_files(tmp_path, monkeypatch)[source]¶
Verify exclude config with label file entries skips those label files.
- tests.test_init_project.test_init_mixed_exclude(tmp_path, monkeypatch)[source]¶
Verify exclude with both component and file entries.
- tests.test_init_project.test_init_detects_excluded_component_files(tmp_path, monkeypatch)[source]¶
Verify init reports excluded component files that still exist on disk.
- tests.test_init_project.test_init_detects_excluded_skill_file(tmp_path, monkeypatch)[source]¶
Verify init reports a file-level excluded skill that still exists on disk.
- tests.test_init_project.test_init_detects_auto_excluded_awesome_triage(tmp_path, monkeypatch)[source]¶
Verify awesome-triage is detected as excluded for non-awesome repos.
When
includedoes not cover skills, scope exclusions still apply and stale awesome-only files are flagged inexcluded_existing.
- tests.test_init_project.test_resolve_target(component, field, default_dir, leaf, location, expected_dir)[source]¶
A declared target is rebased onto the configured location.
removeprefix(notstrip) normalizes the./the config default carries, so a location that is itself a hidden directory keeps its leading dot.
- tests.test_init_project.test_resolve_target_leaves_foreign_paths_alone(component)[source]¶
A target outside the componentās default location passes through.
- tests.test_init_project.test_resolve_target_without_config_is_noop(component)[source]¶
With no config there is nothing to rebase onto.
- tests.test_init_project.test_resolve_target_ignores_components_with_fixed_location()[source]¶
A component with no configurable location never rebases.
.github/workflows/is GitHubās path, not one the user may move, soworkflowsdeclares nolocation_fieldand its targets are literal.
- tests.test_init_project.test_init_skills_custom_location(tmp_path, monkeypatch)[source]¶
Verify skills are written to a custom location when configured.
- tests.test_init_project.test_init_agents_custom_location(tmp_path, monkeypatch)[source]¶
Verify agents are written to a custom location when configured.
- tests.test_init_project.test_init_detects_excluded_agent_custom_location(tmp_path, monkeypatch)[source]¶
Verify excluded agent detection works at custom locations.
- tests.test_init_project.test_init_detects_excluded_skill_custom_location(tmp_path, monkeypatch)[source]¶
Verify excluded skill detection works at custom locations.
- tests.test_init_project.test_init_detects_disabled_opt_in_workflow(tmp_path, monkeypatch)[source]¶
Verify disabled opt-in workflows on disk are detected as excluded.
- tests.test_init_project.test_init_cli_delete_excluded(tmp_path, monkeypatch)[source]¶
Verify ādelete-excluded deletes excluded files and cleans empty dirs.
- tests.test_init_project.test_init_cli_no_delete_excluded_warns(tmp_path, monkeypatch)[source]¶
Verify excluded files are reported but not deleted without ādelete-excluded.
- tests.test_init_project.test_removed_assets_sorted()[source]¶
REMOVED_ASSETS is ordered by (component, target).
- Return type:
- tests.test_init_project.test_removed_assets_no_collision_with_live_registry()[source]¶
No tombstone may point at a path a live component still ships.
Guards against re-adding an asset without dropping its tombstone, which would make
initprune a file it just wrote.- Return type:
- tests.test_init_project.test_removed_asset_metadata_valid(asset)[source]¶
Each tombstone has a valid gate, a bare version, and a known component.
- Return type:
- tests.test_init_project.test_detect_removed_assets_classifies_by_content(tmp_path, monkeypatch)[source]¶
_detect_removed_assetssorts orphans into prunable vs review by hash.- Return type:
- tests.test_init_project.test_detect_removed_assets_prunes_an_empty_owned_folder(tmp_path, monkeypatch)[source]¶
A skill folder outliving its
SKILL.mdis an orphan nothing else sees.- Return type:
- tests.test_init_project.test_init_cli_prunes_unmodified_removed_asset(tmp_path, monkeypatch)[source]¶
Bare
initdeletes an unmodified orphan and its emptied parent dir.- Return type:
- tests.test_init_project.test_init_cli_keeps_modified_removed_asset(tmp_path, monkeypatch)[source]¶
A locally modified orphan is reported for review, never auto-deleted.
- Return type:
- tests.test_init_project.test_init_cli_keep_removed_preserves_unmodified_orphan(tmp_path, monkeypatch)[source]¶
--keep-removedreports an unmodified orphan but does not delete it.- Return type:
- tests.test_init_project.test_detect_removed_assets_matches_any_shipped_hash(tmp_path, monkeypatch)[source]¶
A copy matching any released revision (not just the last) is prunable.
- Return type:
- tests.test_init_project.test_init_cli_delete_removed_modified_forces_deletion(tmp_path, monkeypatch)[source]¶
--delete-removed-modifieddeletes a locally modified orphan.- Return type:
- tests.test_init_project.test_init_cli_keep_removed_and_force_are_exclusive(tmp_path, monkeypatch)[source]¶
--keep-removedand--delete-removed-modifiedcannot combine.- Return type:
- tests.test_init_project.test_detect_prunes_thin_caller_any_slug(tmp_path, slug)[source]¶
A pure thin-caller for a removed workflow is prunable, whatever the era slug.
- Return type:
- tests.test_init_project.test_detect_reviews_customized_thin_caller(tmp_path)[source]¶
A thin-caller the user extended with extra jobs is reported, never pruned.
- Return type:
A userās own workflow that merely shares the name is left untouched.
- Return type:
- tests.test_init_project.test_init_cli_prunes_orphaned_workflow(tmp_path, monkeypatch)[source]¶
Bare init deletes a pure thin-caller for a removed reusable workflow.
- Return type:
- tests.test_init_project.test_removed_reusable_workflows_are_tombstoned()[source]¶
A reusable workflow dropped since the last release must be tombstoned.
A workflow with a
workflow_calltrigger generates downstream thin-callers; removing it without aRemovedAssetleaves orphaned thin-callers. Skips when git history or release tags are unavailable.- Return type:
- tests.test_init_project.test_removed_data_assets_are_tombstoned()[source]¶
A skill/agent data file dropped since the last release must be tombstoned.
Compares the bundled skill/agent data files at the most recent release tag against the current tree. A file present then but gone now is a removed asset that must have a matching
RemovedAssetentry, orinitwould leave orphans in downstream repos. Skips when git history or release tags are unavailable (shallow clone, no tags).- Return type:
- tests.test_init_project.test_shrinking_component_file_lists_are_tombstoned()[source]¶
A target a component stopped shipping must carry a tombstone.
The sibling tests above compare sources under
repomatic/data/, which only catches a bundled file being deleted. A component can orphan a downstream file without deleting anything: dropping aFileEntryfrom itsfilestuple stops shipping that target while the source lives on (or goes, independently).labelsdid exactly that, shedding the two.github/labeller-*.yamlentries whilelabels.tomlstayed.Nothing catches that on its own, because
_detect_removed_assetswalksREMOVED_ASSETSalone: it never diffs a componentās current tuple against what earlier versions wrote, so a shrunk tuple leaves a downstream orphan no laterinitwill ever look at.Comparing target sets across the previous release closes it. Skips when git history or release tags are unavailable (shallow clone, no tags).
Note
One-sided by construction: the previous registry is read as text, so the āshipped thenā set covers only
FileEntryliterals carrying an explicit target, while āshipped nowā is the fully built registry. A component whose entries are generated by a helper is therefore invisible on the old side. That direction is the safe one ā it can only miss a drop, never invent one ā but it does mean a green run here is not proof that no component shrank, only that no literal-target one did.- Return type:
- tests.test_init_project.test_init_explicit_components_bypass_exclude(tmp_path, monkeypatch)[source]¶
Verify explicit CLI components override exclusion.
- tests.test_init_project.test_init_exclude_unknown_component_raises(tmp_path, monkeypatch)[source]¶
Verify unknown component name in exclude raises ValueError.
- tests.test_init_project.test_init_exclude_unknown_file_raises(tmp_path, monkeypatch)[source]¶
Verify unknown file identifier in exclude raises ValueError.
- tests.test_init_project.test_init_include_unknown_component_raises(tmp_path, monkeypatch)[source]¶
Verify unknown component name in include raises ValueError.
- tests.test_init_project.test_init_include_overrides_default_exclusions(tmp_path, monkeypatch)[source]¶
Verify include overrides default exclusions additively.
- tests.test_init_project.test_init_include_cannot_materialize_ephemeral(tmp_path, monkeypatch, caplog)[source]¶
Verify
includecannot opt a bare init into an ephemeral component.
- tests.test_init_project.test_init_explicit_selection_materializes_ephemeral(tmp_path)[source]¶
Verify naming an ephemeral component explicitly still writes its files.
- tests.test_init_project.test_init_exclude_additive_to_defaults(tmp_path, monkeypatch)[source]¶
Verify user exclude is additive to default exclusions.
- tests.test_init_project.test_all_data_files_registered_in_exportable_files()[source]¶
Every non-infrastructure file in data/ must appear in EXPORTABLE_FILES.
- Return type:
- tests.test_init_project.test_every_data_file_maps_to_a_component()[source]¶
Every file in EXPORTABLE_FILES belongs to a registry component, tool runner default, or registered runtime fragment.
- Return type:
- tests.test_init_project.bundled_configurations()[source]¶
File names of every tool configuration the package ships.
The templates
repomatic initmerges into a repository, and the defaults the tool runner reads when a repository declares no configuration of its own. Sorted, becausepytest-xdistaborts a run whose workers disagree on the order of the test IDs.
- tests.test_init_project.test_bundled_configuration_reads_true_downstream(filename)[source]¶
A bundled tool configuration cites nothing a downstream repository lacks.
- Return type:
- tests.test_init_project.test_no_data_file_claimed_by_multiple_components()[source]¶
Each data filename must belong to at most one component.
- Return type:
- tests.test_init_project.test_valid_file_ids_cover_all_multi_file_components()[source]¶
Components with multiple files must report valid file identifiers.
- Return type:
- tests.test_init_project.test_workflow_file_ids_match_all_workflow_files()[source]¶
valid_file_ids(āworkflowsā) must match ALL_WORKFLOW_FILES.
- Return type:
- tests.test_init_project.test_component_file_ids_match_registry()[source]¶
valid_file_ids must return identifiers matching registry entries.
- Return type:
- tests.test_init_project.test_tool_components_have_no_file_ids()[source]¶
Tool components (ruff, pytest, etc.) do not support file-level exclusion.
- Return type:
- tests.test_init_project.test_workflow_files_target_workflow_dir()[source]¶
All workflow file entries must target .github/workflows/.
- Return type:
- tests.test_init_project.test_workflow_sources_are_yaml()[source]¶
All workflow source files must be .yaml.
- Return type:
- tests.test_init_project.test_skill_files_target_skill_dir()[source]¶
All skill entries must target the .claude/skills/{id} folder itself.
- Return type:
- tests.test_init_project.test_skill_sources_follow_naming_convention()[source]¶
Skill sources must be the bundled skills/{id} folder.
- Return type:
- tests.test_init_project.test_skill_file_id_matches_target_dir()[source]¶
Skill file_id must match the directory name in the target path.
- Return type:
- tests.test_init_project.test_no_target_prefix_mixing_within_component()[source]¶
Files within a component must not mix unrelated target directories.
- Return type:
- tests.test_init_project.test_tool_config_rejects_missing_source_file()[source]¶
ToolConfigComponent raises ValueError without source_file.
- Return type:
- tests.test_init_project.test_tool_config_rejects_missing_tool_section()[source]¶
ToolConfigComponent raises ValueError without tool_section.
- Return type:
- tests.test_init_project.test_tool_config_rejects_files()[source]¶
ToolConfigComponent raises ValueError with file entries.
- Return type:
- tests.test_init_project.test_file_ids_unique_within_component()[source]¶
File IDs must be unique within each component.
- Return type:
- tests.test_init_project.test_component_names_unique()[source]¶
Component names must be unique across the registry.
- Return type:
- tests.test_init_project.test_config_key_has_config_default()[source]¶
Entries with config_key must have an intentional config_default.
File entries default to
False(opt-in). Component entries default toTrue(opt-out). This test verifies the pairing exists ā not the specific default value.- Return type:
- tests.test_init_project.test_every_config_key_gate_actually_reaches_its_field()[source]¶
Every declared
config_keymust move its entry when the user flips it.A gate naming a key
Configcannot resolve does not fail: it silently falls through toconfig_default, which reads as a feature switched off rather than as a gate wired wrong.metrics.syncshipped that way, because the resolver flattened every dotted key to one attribute name and could not reach a key living on a nested schema.Written against the whole registry rather than against that one entry: the fault is a property of how a gate is spelled, so any future entry naming a nested key would repeat it.
- Return type:
- tests.test_init_project.test_tools_with_bundled_defaults_not_init_components()[source]¶
Tools with a bundled default_config must not also be init components.
The tool runner already falls back to the bundled config at runtime when no native config exists (Level 3 in resolve_config). Copying the same file into downstream repos via init would be redundant pollution.
- Return type:
- tests.test_init_project.test_init_reports_unmodified_configs(tmp_path, monkeypatch)[source]¶
Init reports native config files that match bundled defaults.
- tests.test_init_project.test_init_no_unmodified_when_different(tmp_path, monkeypatch)[source]¶
Init does not flag modified config files as unmodified.
- tests.test_init_project.test_parse_component_entries_accepts_valid_qualified_entries(entry)[source]¶
Qualified component/file entries are accepted by parse_component_entries.
- Return type:
- tests.test_init_project.test_parse_component_entries_accepts_all_bare_components(component)[source]¶
Every component name is accepted as a bare exclude entry.
- Return type:
- tests.test_init_project.test_parse_component_entries_bare_filename_without_component_fails()[source]¶
Bare filenames like ādebug.yamlā must fail ā qualified form required.
- Return type:
- tests.test_init_project.test_parse_component_entries_bare_skill_name_without_component_fails()[source]¶
Bare skill identifiers like ārepomatic-auditā must fail.
- Return type:
- tests.test_init_project.test_parse_component_entries_rejects_invalid_entries(entry, match)[source]¶
Invalid entries produce hard ValueError failures.
- Return type:
- tests.test_init_project.test_init_single_skill(tmp_path)[source]¶
Qualified entry creates only the specified skill.
- tests.test_init_project.test_init_multiple_qualified_same_component(tmp_path)[source]¶
Multiple qualified entries for same component creates all specified.
- tests.test_init_project.test_init_bare_overrides_qualified(tmp_path)[source]¶
Bare component name includes all files, ignoring qualified filter.
- tests.test_init_project.test_init_mixed_bare_and_qualified(tmp_path)[source]¶
Bare + qualified for different components work independently.
- tests.test_init_project.test_init_qualified_workflow(tmp_path)[source]¶
Single workflow file selection.
- tests.test_init_project.test_init_qualified_scope_bypassed_when_explicit(tmp_path)[source]¶
Explicit selection bypasses scope: awesome-triage created in non-awesome repo.
- tests.test_init_project.test_init_qualified_selection_context_in_error()[source]¶
Qualified selection uses āselectionā context in error messages.
- tests.test_init_project.test_init_awesome_template_not_auto_included_with_explicit_components(tmp_path)[source]¶
awesome-template is not auto-included when explicit components are given.
- tests.test_init_project.test_update_tool_config_output_contract(comp)[source]¶
Syncing any tool-config component yields well-formed, well-spaced TOML.
Three invariants the production path has no other guard for, checked on one sync per component:
Non-empty output. tomlrt 1.7.3, 1.7.4, and the main-branch SHA briefly pinned in
pyproject.tomleach shipped fixes for a shape wheretomlrt.dumpsreturned an empty string with no exception.Section spacing. The previous tomlkit implementation enforced this with a four-step regex normalization on the dumped string; the tomlrt rewrite dropped that stack and trusts the library, so a regression reintroducing
]\n[collisions or triple-blank gaps must fail here.A terminal newline. When the templateās prefix-strip drops the sourceās trailing newline, the last KV slot loses its EOL trivia and every regenerated PR diff grows a
\ No newline at end of file.
- Return type:
- tests.test_init_project.test_tomlrt_aot_only_section_overwrite_invariant()[source]¶
A populate-then-overwrite of an AoT-only section preserves the document.
Mirrors the
_graft_local_additions+ cross-doc assign path from_update_tool_config: parse a pyproject with a[project]preamble followed by an AoT-only target section, build a fresh detached section withTable.section()from an AoT-only template, populate it with the existing AoT entries, then assign the new section back into the document.Earlier tomlrt releases silently dropped the entire document body in this configuration:
tomlrt.dumps(doc)returned an empty string with no exception. The 1.7.5 changelog entry āOverwriting a key with a body-less section (one whose only child is an array-of-tables) no longer wipes the whole document on dumpā fixed it; this test pins the contract so a future tomlrt bump that regresses it fails CI instead of silently corrupting downstreampyproject.tomlfiles. No bundledToolConfigComponentships an AoT-only template today, so a regression would not otherwise surface through thesync-*autofix jobs until a user defined one.- Return type: