gitacross 2.1.0__tar.gz → 2.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. {gitacross-2.1.0/src/gitacross.egg-info → gitacross-2.2.0}/PKG-INFO +16 -16
  2. {gitacross-2.1.0 → gitacross-2.2.0}/README.md +15 -15
  3. {gitacross-2.1.0 → gitacross-2.2.0}/pyproject.toml +1 -1
  4. {gitacross-2.1.0 → gitacross-2.2.0}/src/gitacross/__init__.py +4 -4
  5. {gitacross-2.1.0 → gitacross-2.2.0}/src/gitacross/cli.py +3 -3
  6. {gitacross-2.1.0 → gitacross-2.2.0}/src/gitacross/config.py +4 -13
  7. {gitacross-2.1.0 → gitacross-2.2.0}/src/gitacross/linter/fixer.py +26 -26
  8. {gitacross-2.1.0 → gitacross-2.2.0}/src/gitacross/linter/models.py +8 -8
  9. {gitacross-2.1.0 → gitacross-2.2.0}/src/gitacross/linter/validator.py +82 -88
  10. {gitacross-2.1.0 → gitacross-2.2.0}/src/gitacross/main.py +50 -50
  11. {gitacross-2.1.0 → gitacross-2.2.0}/src/gitacross/renderer.py +3 -3
  12. {gitacross-2.1.0 → gitacross-2.2.0/src/gitacross.egg-info}/PKG-INFO +16 -16
  13. {gitacross-2.1.0 → gitacross-2.2.0}/tests/test_integration.py +2 -11
  14. {gitacross-2.1.0 → gitacross-2.2.0}/tests/test_linter.py +4 -3
  15. {gitacross-2.1.0 → gitacross-2.2.0}/tests/test_sync_api.py +9 -9
  16. {gitacross-2.1.0 → gitacross-2.2.0}/LICENSE +0 -0
  17. {gitacross-2.1.0 → gitacross-2.2.0}/setup.cfg +0 -0
  18. {gitacross-2.1.0 → gitacross-2.2.0}/src/gitacross/__main__.py +0 -0
  19. {gitacross-2.1.0 → gitacross-2.2.0}/src/gitacross/git.py +0 -0
  20. {gitacross-2.1.0 → gitacross-2.2.0}/src/gitacross/linter/__init__.py +0 -0
  21. {gitacross-2.1.0 → gitacross-2.2.0}/src/gitacross/linter/keys.py +0 -0
  22. {gitacross-2.1.0 → gitacross-2.2.0}/src/gitacross/providers/__init__.py +0 -0
  23. {gitacross-2.1.0 → gitacross-2.2.0}/src/gitacross/providers/base.py +0 -0
  24. {gitacross-2.1.0 → gitacross-2.2.0}/src/gitacross/providers/gitea.py +0 -0
  25. {gitacross-2.1.0 → gitacross-2.2.0}/src/gitacross/providers/github.py +0 -0
  26. {gitacross-2.1.0 → gitacross-2.2.0}/src/gitacross/retry.py +0 -0
  27. {gitacross-2.1.0 → gitacross-2.2.0}/src/gitacross/source.py +0 -0
  28. {gitacross-2.1.0 → gitacross-2.2.0}/src/gitacross/state.py +0 -0
  29. {gitacross-2.1.0 → gitacross-2.2.0}/src/gitacross/target.py +0 -0
  30. {gitacross-2.1.0 → gitacross-2.2.0}/src/gitacross.egg-info/SOURCES.txt +0 -0
  31. {gitacross-2.1.0 → gitacross-2.2.0}/src/gitacross.egg-info/dependency_links.txt +0 -0
  32. {gitacross-2.1.0 → gitacross-2.2.0}/src/gitacross.egg-info/entry_points.txt +0 -0
  33. {gitacross-2.1.0 → gitacross-2.2.0}/src/gitacross.egg-info/requires.txt +0 -0
  34. {gitacross-2.1.0 → gitacross-2.2.0}/src/gitacross.egg-info/top_level.txt +0 -0
  35. {gitacross-2.1.0 → gitacross-2.2.0}/tests/test_config.py +0 -0
  36. {gitacross-2.1.0 → gitacross-2.2.0}/tests/test_git.py +0 -0
  37. {gitacross-2.1.0 → gitacross-2.2.0}/tests/test_providers.py +0 -0
  38. {gitacross-2.1.0 → gitacross-2.2.0}/tests/test_renderer.py +0 -0
  39. {gitacross-2.1.0 → gitacross-2.2.0}/tests/test_source.py +0 -0
  40. {gitacross-2.1.0 → gitacross-2.2.0}/tests/test_state.py +0 -0
  41. {gitacross-2.1.0 → gitacross-2.2.0}/tests/test_target.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gitacross
3
- Version: 2.1.0
3
+ Version: 2.2.0
4
4
  Summary: Mirror releases and git commits across platforms (Gitea, GitHub, local) with transform pipelines.
5
5
  Author: Matthew Deik
6
6
  License-Expression: MIT
@@ -112,7 +112,7 @@ gitacross --config config.yml --lint
112
112
  gitacross --config config.yml
113
113
  ```
114
114
 
115
- Run it again later — releases that were already synced are skipped, so nothing is duplicated. Use `--project my-project` to sync a single project. See [CLI](#cli) for all flags, or the [Python API](#python-api) to drive GitAcross from code.
115
+ Run it again later — releases that were already synced are skipped, so nothing is duplicated. Use `--project-name my-project` to sync a single project. See [CLI](#cli) for all flags, or the [Python API](#python-api) to drive GitAcross from code.
116
116
 
117
117
  ## How it works
118
118
 
@@ -176,11 +176,11 @@ Copy the source release notes/body to the target release. Default `true`. Set at
176
176
 
177
177
  #### `release_description`
178
178
 
179
- Format the target release notes from a template (aliases: `release_notes_template`, `description_template`). Placeholders: `{body}`, `{description}`, `{tag}`, `{commit_sha}`, `{short_sha}`, `{project_name}`, `{name}`, `{source_date}`.
179
+ Format the target release notes from a template. Placeholders: `{body}`, `{description}`, `{tag}`, `{commit_sha}`, `{short_sha}`, `{project_name}`, `{name}`, `{source_date}`.
180
180
 
181
181
  #### `commit_message`
182
182
 
183
- Custom commit message for the target commits (alias: `commit_template`). Default: `"Release {tag}"` or `"Sync commit {short_sha}"`. Placeholders: `{tag}`, `{commit_sha}`, `{short_sha}`, `{project_name}`, `{name}`, `{source_date}`, `{body}`, `{description}`.
183
+ Custom commit message for the target commits. Default: `"Release {tag}"` or `"Sync commit {short_sha}"`. Placeholders: `{tag}`, `{commit_sha}`, `{short_sha}`, `{project_name}`, `{name}`, `{source_date}`, `{body}`, `{description}`.
184
184
 
185
185
  #### `sync_assets`
186
186
 
@@ -436,13 +436,13 @@ GitAcross can be driven from the command line or called directly from Python.
436
436
  ### CLI
437
437
 
438
438
  ```
439
- gitacross --config PATH [--project NAME] [--workdir PATH] [--dry-run] [--reset] [--lint] [--fix] [-v]
439
+ gitacross --config PATH [--project-name NAME] [--workdir PATH] [--dry-run] [--reset] [--lint] [--fix] [-v]
440
440
  ```
441
441
 
442
442
  | Flag | Description |
443
443
  |---|---|
444
444
  | `--config PATH` | Config file to use (required) |
445
- | `--project NAME` | Sync only this project |
445
+ | `--project-name NAME` | Sync only the project with this name |
446
446
  | `--workdir PATH` | Where state and cache live (default: `.gitsync`) |
447
447
  | `--dry-run` | Preview changes without committing or pushing |
448
448
  | `--reset` | Clear saved state and cache before running (fresh start) |
@@ -477,7 +477,7 @@ for r in results:
477
477
  # Preview only — nothing is committed or pushed
478
478
  results = gitacross.run(
479
479
  "config.yml",
480
- project="my-project",
480
+ project_name="my-project",
481
481
  dry_run=True,
482
482
  work_dir="/data/custom_dir",
483
483
  )
@@ -501,9 +501,9 @@ results = gitacross.run("config.yml", reset=True)
501
501
  ```python
502
502
  config = gitacross.Config("config.yml")
503
503
 
504
- for project in config.projects:
505
- if project.enabled:
506
- gitacross.sync_project(project, ".gitsync", dry_run=False)
504
+ for project_config in config.projects:
505
+ if project_config.enabled:
506
+ gitacross.sync_project(project_config, ".gitsync", dry_run=False)
507
507
  ```
508
508
 
509
509
  </details>
@@ -513,7 +513,7 @@ for project in config.projects:
513
513
 
514
514
  ```python
515
515
  # ${VAR} tokens still resolve from the environment
516
- project = gitacross.ProjectConfig({
516
+ project_config = gitacross.ProjectConfig({
517
517
  "name": "my-project",
518
518
  "source": {
519
519
  "type": "gitea",
@@ -528,7 +528,7 @@ project = gitacross.ProjectConfig({
528
528
  "token": "${GITHUB_TOKEN}",
529
529
  },
530
530
  })
531
- gitacross.sync_project(project, ".gitsync")
531
+ gitacross.sync_project(project_config, ".gitsync")
532
532
  ```
533
533
 
534
534
  </details>
@@ -573,16 +573,16 @@ All public symbols are importable directly from `gitacross`:
573
573
 
574
574
  | Symbol | What it does |
575
575
  |---|---|
576
- | `run(config, project=None, dry_run=False, reset=False, work_dir=".gitsync")` | **Primary entry point.** Sync from a config — a `Config` instance, a path, or an open file object
577
- | `sync_project(project, work_dir=".gitsync", dry_run=False)` | Sync one project's new releases (respects `project.enabled`); state and cache live in `work_dir`. Returns dicts with `tag`, `source_commit`, `target_commit`, `source_date` |
576
+ | `run(config, project_name=None, dry_run=False, reset=False, work_dir=".gitsync")` | **Primary entry point.** Sync from a config — a `Config` instance, a path, or an open file object
577
+ | `sync_project(project_config, work_dir=".gitsync", dry_run=False)` | Sync one project's new releases (respects `project_config.enabled`); state and cache live in `work_dir`. Returns dicts with `tag`, `source_commit`, `target_commit`, `source_date` |
578
578
  | `lint_config(config, print_output=True)` | Lint a config (path or open file object) → `LintReport` |
579
579
  | `fix_config(config, write_back=True, print_output=True)` | Fix misplaced/redundant options (path only — writes back to the file) → `FixReport` |
580
580
  | `Config(config_source)` | Load a config from a path or open file object; exposes `.projects`. `Config.from_yaml_string(content)` loads a config from raw YAML text (`str` or `bytes`) — no file or stream needed |
581
581
  | `ProjectConfig(raw)` | Build one mirror project from a raw config dict (see the “No config file” example). Fields: `name`, `enabled`, `source`, `target`, `renderer`, `retry`, `preserve_description`, `sync_assets`, `stream_assets`, `commit_message`, `release_description` |
582
582
  | `ConfigLinter()` | Collect lint issues programmatically: `lint_file(config)`, `lint_yaml_string(content)`; results accumulate in `.issues` |
583
583
  | `ConfigFixer()` | Fix a config programmatically: `fix_yaml_string(content)` → `FixReport`; actions recorded in `.fixes` |
584
- | `LintIssue(severity, message, project=None, key=None)` | One lint finding |
585
- | `FixIssue(message, project=None)` | One applied fix |
584
+ | `LintIssue(severity, message, project_name=None, key=None)` | One lint finding |
585
+ | `FixIssue(message, project_name=None)` | One applied fix |
586
586
  | `LintReport(issues)` | Lint results: `.issues`, `.errors`, `.warnings`, `.redundant`, `.is_valid`, `.format_text()` |
587
587
  | `FixReport(fixes, content, is_valid, error=None)` | Fix results: `.fixes`, `.content`, `.is_valid`, `.error`, `.format_text()` |
588
588
  | `LintSeverity` | Severity levels used by `LintIssue`: `LintSeverity.ERROR`, `LintSeverity.WARNING`, `LintSeverity.REDUNDANT` |
@@ -83,7 +83,7 @@ gitacross --config config.yml --lint
83
83
  gitacross --config config.yml
84
84
  ```
85
85
 
86
- Run it again later — releases that were already synced are skipped, so nothing is duplicated. Use `--project my-project` to sync a single project. See [CLI](#cli) for all flags, or the [Python API](#python-api) to drive GitAcross from code.
86
+ Run it again later — releases that were already synced are skipped, so nothing is duplicated. Use `--project-name my-project` to sync a single project. See [CLI](#cli) for all flags, or the [Python API](#python-api) to drive GitAcross from code.
87
87
 
88
88
  ## How it works
89
89
 
@@ -147,11 +147,11 @@ Copy the source release notes/body to the target release. Default `true`. Set at
147
147
 
148
148
  #### `release_description`
149
149
 
150
- Format the target release notes from a template (aliases: `release_notes_template`, `description_template`). Placeholders: `{body}`, `{description}`, `{tag}`, `{commit_sha}`, `{short_sha}`, `{project_name}`, `{name}`, `{source_date}`.
150
+ Format the target release notes from a template. Placeholders: `{body}`, `{description}`, `{tag}`, `{commit_sha}`, `{short_sha}`, `{project_name}`, `{name}`, `{source_date}`.
151
151
 
152
152
  #### `commit_message`
153
153
 
154
- Custom commit message for the target commits (alias: `commit_template`). Default: `"Release {tag}"` or `"Sync commit {short_sha}"`. Placeholders: `{tag}`, `{commit_sha}`, `{short_sha}`, `{project_name}`, `{name}`, `{source_date}`, `{body}`, `{description}`.
154
+ Custom commit message for the target commits. Default: `"Release {tag}"` or `"Sync commit {short_sha}"`. Placeholders: `{tag}`, `{commit_sha}`, `{short_sha}`, `{project_name}`, `{name}`, `{source_date}`, `{body}`, `{description}`.
155
155
 
156
156
  #### `sync_assets`
157
157
 
@@ -407,13 +407,13 @@ GitAcross can be driven from the command line or called directly from Python.
407
407
  ### CLI
408
408
 
409
409
  ```
410
- gitacross --config PATH [--project NAME] [--workdir PATH] [--dry-run] [--reset] [--lint] [--fix] [-v]
410
+ gitacross --config PATH [--project-name NAME] [--workdir PATH] [--dry-run] [--reset] [--lint] [--fix] [-v]
411
411
  ```
412
412
 
413
413
  | Flag | Description |
414
414
  |---|---|
415
415
  | `--config PATH` | Config file to use (required) |
416
- | `--project NAME` | Sync only this project |
416
+ | `--project-name NAME` | Sync only the project with this name |
417
417
  | `--workdir PATH` | Where state and cache live (default: `.gitsync`) |
418
418
  | `--dry-run` | Preview changes without committing or pushing |
419
419
  | `--reset` | Clear saved state and cache before running (fresh start) |
@@ -448,7 +448,7 @@ for r in results:
448
448
  # Preview only — nothing is committed or pushed
449
449
  results = gitacross.run(
450
450
  "config.yml",
451
- project="my-project",
451
+ project_name="my-project",
452
452
  dry_run=True,
453
453
  work_dir="/data/custom_dir",
454
454
  )
@@ -472,9 +472,9 @@ results = gitacross.run("config.yml", reset=True)
472
472
  ```python
473
473
  config = gitacross.Config("config.yml")
474
474
 
475
- for project in config.projects:
476
- if project.enabled:
477
- gitacross.sync_project(project, ".gitsync", dry_run=False)
475
+ for project_config in config.projects:
476
+ if project_config.enabled:
477
+ gitacross.sync_project(project_config, ".gitsync", dry_run=False)
478
478
  ```
479
479
 
480
480
  </details>
@@ -484,7 +484,7 @@ for project in config.projects:
484
484
 
485
485
  ```python
486
486
  # ${VAR} tokens still resolve from the environment
487
- project = gitacross.ProjectConfig({
487
+ project_config = gitacross.ProjectConfig({
488
488
  "name": "my-project",
489
489
  "source": {
490
490
  "type": "gitea",
@@ -499,7 +499,7 @@ project = gitacross.ProjectConfig({
499
499
  "token": "${GITHUB_TOKEN}",
500
500
  },
501
501
  })
502
- gitacross.sync_project(project, ".gitsync")
502
+ gitacross.sync_project(project_config, ".gitsync")
503
503
  ```
504
504
 
505
505
  </details>
@@ -544,16 +544,16 @@ All public symbols are importable directly from `gitacross`:
544
544
 
545
545
  | Symbol | What it does |
546
546
  |---|---|
547
- | `run(config, project=None, dry_run=False, reset=False, work_dir=".gitsync")` | **Primary entry point.** Sync from a config — a `Config` instance, a path, or an open file object
548
- | `sync_project(project, work_dir=".gitsync", dry_run=False)` | Sync one project's new releases (respects `project.enabled`); state and cache live in `work_dir`. Returns dicts with `tag`, `source_commit`, `target_commit`, `source_date` |
547
+ | `run(config, project_name=None, dry_run=False, reset=False, work_dir=".gitsync")` | **Primary entry point.** Sync from a config — a `Config` instance, a path, or an open file object
548
+ | `sync_project(project_config, work_dir=".gitsync", dry_run=False)` | Sync one project's new releases (respects `project_config.enabled`); state and cache live in `work_dir`. Returns dicts with `tag`, `source_commit`, `target_commit`, `source_date` |
549
549
  | `lint_config(config, print_output=True)` | Lint a config (path or open file object) → `LintReport` |
550
550
  | `fix_config(config, write_back=True, print_output=True)` | Fix misplaced/redundant options (path only — writes back to the file) → `FixReport` |
551
551
  | `Config(config_source)` | Load a config from a path or open file object; exposes `.projects`. `Config.from_yaml_string(content)` loads a config from raw YAML text (`str` or `bytes`) — no file or stream needed |
552
552
  | `ProjectConfig(raw)` | Build one mirror project from a raw config dict (see the “No config file” example). Fields: `name`, `enabled`, `source`, `target`, `renderer`, `retry`, `preserve_description`, `sync_assets`, `stream_assets`, `commit_message`, `release_description` |
553
553
  | `ConfigLinter()` | Collect lint issues programmatically: `lint_file(config)`, `lint_yaml_string(content)`; results accumulate in `.issues` |
554
554
  | `ConfigFixer()` | Fix a config programmatically: `fix_yaml_string(content)` → `FixReport`; actions recorded in `.fixes` |
555
- | `LintIssue(severity, message, project=None, key=None)` | One lint finding |
556
- | `FixIssue(message, project=None)` | One applied fix |
555
+ | `LintIssue(severity, message, project_name=None, key=None)` | One lint finding |
556
+ | `FixIssue(message, project_name=None)` | One applied fix |
557
557
  | `LintReport(issues)` | Lint results: `.issues`, `.errors`, `.warnings`, `.redundant`, `.is_valid`, `.format_text()` |
558
558
  | `FixReport(fixes, content, is_valid, error=None)` | Fix results: `.fixes`, `.content`, `.is_valid`, `.error`, `.format_text()` |
559
559
  | `LintSeverity` | Severity levels used by `LintIssue`: `LintSeverity.ERROR`, `LintSeverity.WARNING`, `LintSeverity.REDUNDANT` |
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "gitacross"
7
- version = "2.1.0"
7
+ version = "2.2.0"
8
8
  description = "Mirror releases and git commits across platforms (Gitea, GitHub, local) with transform pipelines."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.8"
@@ -8,13 +8,13 @@ Quickstart (Python API)::
8
8
  results = gitacross.run("config.yml")
9
9
 
10
10
  # Sync a single project with dry-run mode:
11
- results = gitacross.run("config.yml", project="my-mirror", dry_run=True)
11
+ results = gitacross.run("config.yml", project_name="my-mirror", dry_run=True)
12
12
 
13
13
  # Lower-level: build objects yourself for full control:
14
14
  config = gitacross.Config("config.yml")
15
- for project in config.projects:
16
- if project.enabled:
17
- gitacross.sync_project(project, ".gitsync")
15
+ for project_config in config.projects:
16
+ if project_config.enabled:
17
+ gitacross.sync_project(project_config, ".gitsync")
18
18
 
19
19
  # Lint / fix a config file:
20
20
  report = gitacross.lint_config("config.yml")
@@ -26,7 +26,7 @@ def main():
26
26
 
27
27
  parser = argparse.ArgumentParser(description="Sync releases from source to target")
28
28
  _ = parser.add_argument("--config", required=True, help="Path to config.yml")
29
- _ = parser.add_argument("--project", help="Sync only this project (by name)")
29
+ _ = parser.add_argument("--project-name", help="Sync only this project (by name)")
30
30
  _ = parser.add_argument(
31
31
  "--dry-run", action="store_true", help="Print changes without pushing"
32
32
  )
@@ -58,7 +58,7 @@ def main():
58
58
  config_path = Path(cast(str, args.config))
59
59
  fix_flag = cast(bool, args.fix)
60
60
  lint_flag = cast(bool, args.lint)
61
- project_filter = None if args.project is None else cast(str, args.project)
61
+ project_name = None if args.project_name is None else cast(str, args.project_name)
62
62
  dry_run_flag = cast(bool, args.dry_run)
63
63
  reset_flag = cast(bool, args.reset)
64
64
  workdir = cast(str, args.workdir)
@@ -92,7 +92,7 @@ def main():
92
92
  try:
93
93
  results = run(
94
94
  config_path,
95
- project=project_filter,
95
+ project_name=project_name,
96
96
  dry_run=dry_run_flag,
97
97
  reset=reset_flag,
98
98
  work_dir=workdir,
@@ -22,10 +22,7 @@ VALID_PROJECT_KEYS = {
22
22
  "sync_assets",
23
23
  "stream_assets",
24
24
  "commit_message",
25
- "commit_template",
26
25
  "release_description",
27
- "release_notes_template",
28
- "description_template",
29
26
  }
30
27
 
31
28
  KNOWN_SOURCE_KEYS = {
@@ -256,15 +253,10 @@ class ProjectConfig:
256
253
  self.stream_assets = bool(raw.get("stream_assets", False))
257
254
 
258
255
  # commit_message — project-level commit message template (e.g. "chore(sync): {tag}")
259
- self.commit_message = raw.get("commit_message") or raw.get("commit_template") or None
256
+ self.commit_message = raw.get("commit_message") or None
260
257
 
261
258
  # release_description — project-level release description template
262
- self.release_description = (
263
- raw.get("release_description")
264
- or raw.get("release_notes_template")
265
- or raw.get("description_template")
266
- or None
267
- )
259
+ self.release_description = raw.get("release_description") or None
268
260
 
269
261
  def __repr__(self):
270
262
  return (
@@ -309,9 +301,8 @@ def _cascade(raw_project, raw_source, raw_target, keys, default):
309
301
  """Return the first value found for any of *keys* across project, source, target.
310
302
 
311
303
  Lookup order: project-level first (highest priority), then source-level,
312
- then target-level, then *default*. All alias keys are checked at each
313
- level before moving to the next — so a project-level alias wins over a
314
- source-level primary key.
304
+ then target-level, then *default*. The first *key* present at the first
305
+ level wins — so a project-level value beats a source-level one.
315
306
  """
316
307
  for raw in (raw_project, raw_source, raw_target):
317
308
  for k in keys:
@@ -18,8 +18,8 @@ class ConfigFixer:
18
18
  def __repr__(self):
19
19
  return f"ConfigFixer(fixes={len(self.fixes)})"
20
20
 
21
- def _add(self, message: str, project: str | None = None):
22
- self.fixes.append(FixIssue(message=message, project=project))
21
+ def _add(self, message: str, project_name: str | None = None):
22
+ self.fixes.append(FixIssue(message=message, project_name=project_name))
23
23
 
24
24
  def fix_yaml_string(self, content: str) -> FixReport:
25
25
  # Fresh accumulator — reusing an instance must not leak prior fixes
@@ -68,12 +68,12 @@ class ConfigFixer:
68
68
  p["source"][k] = val
69
69
  self._add(
70
70
  f"Moved '{k}: {val}' from project level into 'source:'",
71
- project=p_name,
71
+ project_name=p_name,
72
72
  )
73
73
  else:
74
74
  self._add(
75
75
  f"Removed duplicate project-level '{k}' (already present under source)",
76
- project=p_name,
76
+ project_name=p_name,
77
77
  )
78
78
 
79
79
  # 2. Misplaced project-level keys inside source -> move to project level
@@ -85,12 +85,12 @@ class ConfigFixer:
85
85
  p[k] = val
86
86
  self._add(
87
87
  f"Moved '{k}' from 'source:' to project level",
88
- project=p_name,
88
+ project_name=p_name,
89
89
  )
90
90
  else:
91
91
  self._add(
92
92
  f"Removed duplicate '{k}' from 'source:' (already present at project level)",
93
- project=p_name,
93
+ project_name=p_name,
94
94
  )
95
95
 
96
96
  # 3. Misplaced project keys in target -> move to project level
@@ -103,12 +103,12 @@ class ConfigFixer:
103
103
  p[k] = val
104
104
  self._add(
105
105
  f"Moved '{k}' from 'target:' to project level",
106
- project=p_name,
106
+ project_name=p_name,
107
107
  )
108
108
  else:
109
109
  self._add(
110
110
  f"Removed duplicate '{k}' from 'target:' (already present at project level)",
111
- project=p_name,
111
+ project_name=p_name,
112
112
  )
113
113
  elif k in KNOWN_SOURCE_KEYS:
114
114
  val = p["target"].pop(k)
@@ -118,32 +118,32 @@ class ConfigFixer:
118
118
  p["source"][k] = val
119
119
  self._add(
120
120
  f"Moved '{k}: {val}' from 'target:' into 'source:'",
121
- project=p_name,
121
+ project_name=p_name,
122
122
  )
123
123
  else:
124
124
  self._add(
125
125
  f"Removed duplicate '{k}' from 'target:' (already present under source)",
126
- project=p_name,
126
+ project_name=p_name,
127
127
  )
128
128
 
129
129
  # 4. Redundant project-level options
130
130
  if p.get("enabled") is True:
131
131
  del p["enabled"]
132
- self._add("Removed redundant 'enabled: true'", project=p_name)
132
+ self._add("Removed redundant 'enabled: true'", project_name=p_name)
133
133
 
134
134
  for key in ("preserve_description",):
135
135
  if p.get(key) is True:
136
136
  del p[key]
137
- self._add(f"Removed redundant '{key}: true'", project=p_name)
137
+ self._add(f"Removed redundant '{key}: true'", project_name=p_name)
138
138
 
139
139
  for key in ("sync_assets",):
140
140
  if p.get(key) is False:
141
141
  del p[key]
142
- self._add(f"Removed redundant '{key}: false'", project=p_name)
142
+ self._add(f"Removed redundant '{key}: false'", project_name=p_name)
143
143
 
144
144
  if p.get("stream_assets") is False:
145
145
  del p["stream_assets"]
146
- self._add("Removed redundant 'stream_assets: false'", project=p_name)
146
+ self._add("Removed redundant 'stream_assets: false'", project_name=p_name)
147
147
 
148
148
  # 3. Source endpoint fixes
149
149
  if isinstance(p.get("source"), dict):
@@ -151,31 +151,31 @@ class ConfigFixer:
151
151
  if src.get("mode") == "release":
152
152
  del src["mode"]
153
153
  self._add(
154
- "Removed redundant 'source.mode: release'", project=p_name
154
+ "Removed redundant 'source.mode: release'", project_name=p_name
155
155
  )
156
156
  if src.get("include_prereleases") is False:
157
157
  del src["include_prereleases"]
158
158
  self._add(
159
159
  "Removed redundant 'source.include_prereleases: false'",
160
- project=p_name,
160
+ project_name=p_name,
161
161
  )
162
162
  if src.get("include_drafts") is False:
163
163
  del src["include_drafts"]
164
164
  self._add(
165
165
  "Removed redundant 'source.include_drafts: false'",
166
- project=p_name,
166
+ project_name=p_name,
167
167
  )
168
168
  if src.get("sync_from") == "":
169
169
  del src["sync_from"]
170
170
  self._add(
171
171
  "Removed redundant 'source.sync_from: \"\"'",
172
- project=p_name,
172
+ project_name=p_name,
173
173
  )
174
174
  if src.get("tag_pattern") == "*":
175
175
  del src["tag_pattern"]
176
176
  self._add(
177
177
  "Removed redundant 'source.tag_pattern: \"*\"'",
178
- project=p_name,
178
+ project_name=p_name,
179
179
  )
180
180
 
181
181
  # 4. Target endpoint fixes
@@ -184,7 +184,7 @@ class ConfigFixer:
184
184
  if tgt.get("branch") == "main":
185
185
  del tgt["branch"]
186
186
  self._add(
187
- "Removed redundant 'target.branch: main'", project=p_name
187
+ "Removed redundant 'target.branch: main'", project_name=p_name
188
188
  )
189
189
 
190
190
  # 5. Retry fixes
@@ -198,11 +198,11 @@ class ConfigFixer:
198
198
  del p["retry"]
199
199
  self._add(
200
200
  "Removed default 'retry' configuration block",
201
- project=p_name,
201
+ project_name=p_name,
202
202
  )
203
203
  else:
204
204
  self._add(
205
- "Removed redundant retry defaults", project=p_name
205
+ "Removed redundant retry defaults", project_name=p_name
206
206
  )
207
207
 
208
208
  # 6. Renderer fixes
@@ -221,20 +221,20 @@ class ConfigFixer:
221
221
  del item["pattern"]
222
222
  self._add(
223
223
  f"Removed redundant 'pattern: literal' in {op_type} operation",
224
- project=p_name,
224
+ project_name=p_name,
225
225
  )
226
226
  if op_type == "replace":
227
227
  if item.get("case_sensitive") is True:
228
228
  del item["case_sensitive"]
229
229
  self._add(
230
230
  "Removed redundant 'case_sensitive: true' in replace operation",
231
- project=p_name,
231
+ project_name=p_name,
232
232
  )
233
233
  if item.get("match_case") is False:
234
234
  del item["match_case"]
235
235
  self._add(
236
236
  "Removed redundant 'match_case: false' in replace operation",
237
- project=p_name,
237
+ project_name=p_name,
238
238
  )
239
239
  if op_type == "validate" and item.get(
240
240
  "case_sensitive"
@@ -242,7 +242,7 @@ class ConfigFixer:
242
242
  del item["case_sensitive"]
243
243
  self._add(
244
244
  "Removed redundant 'case_sensitive: true' in validate operation",
245
- project=p_name,
245
+ project_name=p_name,
246
246
  )
247
247
 
248
248
 
@@ -21,13 +21,13 @@ class LintSeverity(Enum):
21
21
  class LintIssue:
22
22
  severity: LintSeverity
23
23
  message: str
24
- project: str | None = None
24
+ project_name: str | None = None
25
25
  key: str | None = None
26
26
 
27
27
  def __str__(self) -> str:
28
28
  prefix = f"[{self.severity.value}]"
29
- if self.project:
30
- return f" {prefix} (project '{self.project}'): {self.message}"
29
+ if self.project_name:
30
+ return f" {prefix} (project '{self.project_name}'): {self.message}"
31
31
  return f" {prefix} {self.message}"
32
32
 
33
33
 
@@ -63,7 +63,7 @@ class LintReport:
63
63
  # Group issues by project (None first)
64
64
  by_proj: dict[str | None, list[LintIssue]] = {}
65
65
  for issue in self.issues:
66
- by_proj.setdefault(issue.project, []).append(issue)
66
+ by_proj.setdefault(issue.project_name, []).append(issue)
67
67
 
68
68
  if None in by_proj:
69
69
  for issue in by_proj[None]:
@@ -98,11 +98,11 @@ _CleanDumper.add_representer(str, _str_presenter)
98
98
  @dataclass
99
99
  class FixIssue:
100
100
  message: str
101
- project: str | None = None
101
+ project_name: str | None = None
102
102
 
103
103
  def __str__(self) -> str:
104
- if self.project:
105
- return f" ✓ (project '{self.project}'): {self.message}"
104
+ if self.project_name:
105
+ return f" ✓ (project '{self.project_name}'): {self.message}"
106
106
  return f" ✓ {self.message}"
107
107
 
108
108
 
@@ -121,7 +121,7 @@ class FixReport:
121
121
  out = [f"Applied {len(self.fixes)} fix(es):"]
122
122
  by_proj: dict[str | None, list[FixIssue]] = {}
123
123
  for fix in self.fixes:
124
- by_proj.setdefault(fix.project, []).append(fix)
124
+ by_proj.setdefault(fix.project_name, []).append(fix)
125
125
 
126
126
  if None in by_proj:
127
127
  for fix in by_proj[None]: