Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Caution Review failedThe pull request is closed. WalkthroughUpdates CI to a matrix workflow (ruff, pyrefly, coverage, Codecov) and adds a Release workflow that validates versions, builds, generates release notes, creates GitHub Releases, and publishes to PyPI. Introduces extensive templating APIs for rendering and project creation and a config normalization change that validates and persists cleaned libraries. Other edits are formatting and test adjustments. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor Dev as Developer
participant GH as GitHub Actions (Release)
participant Py as Python Tooling
participant GHRel as GitHub Release
participant PyPI as PyPI
Dev->>GH: Push tag vX.Y.Z / workflow_dispatch
GH->>Py: Setup Python, install build tools
GH->>Py: Read package __version__
GH->>GH: Compare tag vs package version
alt mismatch
GH-->>Dev: Fail job (version mismatch)
else match
GH->>Py: Lint (ruff), type-check (pyrefly), run tests (pytest + coverage)
GH->>Py: Build sdist/wheel
GH->>Py: Twine check
GH->>GH: Generate release notes (commits, PRs)
GH->>GHRel: Create draft release with artifacts
GH->>PyPI: Publish package
PyPI-->>GH: Publish result
end
sequenceDiagram
autonumber
actor User
participant T as Template Dir
participant U as Template Utils
participant P as Project Dir
participant H as Post-create Hook (optional)
User->>U: create_project_from_template(T, P, vars, metadata, dry_run/skip_hooks)
U->>U: find_all_templates / read metadata
U->>U: Scan files, apply excludes, enforce MAX_PATH
U->>U: Render filenames (render_filename)
alt text file
U->>U: render_template_file (variables) -> write
else binary/KiCad special
U->>U: process_kicad_* or copy as-is -> write
end
opt hook present and not skipped
U->>H: run_post_create_hook(P, vars, metadata)
H-->>U: Completion
end
U-->>User: Return success (honors dry-run)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 💡 Knowledge Base configuration:
You can enable these sources in your CodeRabbit configuration. 📒 Files selected for processing (4)
✨ Finishing Touches
🧪 Generate unit tests
Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
kicad_lib_manager/commands/update/command.py (2)
119-132: False positives: change detection triggers whenever any content exists.check_for_library_changes returns labels based on presence of files under symbols/footprints/templates, not newly added ones. This will prompt users to run setup after every pull that fetched anything, even if no new libraries were added.
Proposed fix: derive “added” paths from Git and infer categories from those paths.
- # Check if there are new library files that would require setup - if "Already up to date" not in output: - changes_require_setup = check_for_library_changes( - result.stdout, lib_path - ) + # Check if newly ADDED library files/directories require setup + if "Already up to date" not in output: + changes_require_setup = check_for_library_changes( + result.stdout, lib_path + )And update implementation below (see dedicated diff on check_for_library_changes).
178-238: Detect newly added libraries via git diff instead of filesystem presence.Rework to parse added paths between previous and current HEAD and only flag categories when additions occur under symbols/.kicad_sym, footprints//.kicad_mod (within .pretty), or templates//metadata.yaml.
Apply:
def check_for_library_changes(git_output, lib_path): @@ - changes = [] - - # Check git output for new files in key directories - pattern = r"[^\s]+\s+\|\s+\d+ [+]+(?:-+)?\s+(?:symbols|footprints|templates|3d)" - if re.search(pattern, git_output, re.IGNORECASE): - # This is a basic heuristic that indicates changes in library directories - # For more accuracy, we'll check the actual directories - pass - - # Check for specific library file types - symbols_path = lib_path / "symbols" - footprints_path = lib_path / "footprints" - templates_path = lib_path / "templates" - - # Look for symbol libraries (.kicad_sym files) - if ( - symbols_path.exists() - and symbols_path.is_dir() - and any( - f.name.endswith(".kicad_sym") for f in symbols_path.glob("**/*.kicad_sym") - ) - ): - changes.append("symbols") - - # Look for footprint libraries (.pretty directories) - if ( - footprints_path.exists() - and footprints_path.is_dir() - and any( - f.is_dir() and f.name.endswith(".pretty") - for f in footprints_path.glob("**/*.pretty") - ) - ): - changes.append("footprints") - - # Look for project templates (directories with metadata.yaml) - if ( - templates_path.exists() - and templates_path.is_dir() - and any( - (f / "metadata.yaml").exists() - for f in templates_path.glob("*") - if f.is_dir() - ) - ): - changes.append("templates") - - return changes + changes: list[str] = [] + + # Try to compute added paths between previous and current HEAD + diff_specs = [ + ["git", "-C", str(lib_path), "diff", "--name-status", "--diff-filter=A", "HEAD@{1}", "HEAD"], + ["git", "-C", str(lib_path), "diff", "--name-status", "--diff-filter=A", "ORIG_HEAD", "HEAD"], + ] + added_paths: set[str] = set() + for cmd in diff_specs: + try: + rp = subprocess.run(cmd, capture_output=True, text=True, check=True, timeout=30) + for line in rp.stdout.splitlines(): + # Format: A<TAB>path/to/file + if not line: + continue + status, _, path = line.partition("\t") + if status.strip().upper() == "A" and path: + added_paths.add(path.strip()) + if added_paths: + break # we got a usable spec + except subprocess.CalledProcessError: + continue + except subprocess.TimeoutExpired: + continue + + # Infer categories from added paths + for p in added_paths: + # symbols: new .kicad_sym files under symbols/ + if p.startswith("symbols/") and p.endswith(".kicad_sym"): + changes.append("symbols") + # footprints: any added .kicad_mod within a *.pretty/ directory + elif p.startswith("footprints/") and ".pretty/" in p and p.endswith(".kicad_mod"): + changes.append("footprints") + # templates: a new metadata.yaml within templates/<name>/ + elif p.startswith("templates/") and p.endswith("/metadata.yaml"): + changes.append("templates") + + # Deduplicate while preserving order + seen = set() + deduped = [c for c in changes if not (c in seen or seen.add(c))] + return dedupedkicad_lib_manager/utils/template.py (2)
446-456: Fix Path vs. str comparisons blocking main KiCad file detection/processing.
main_*_fileare stored as str whilefile_pathis Path, so equality checks never match; special naming and processing paths are skipped.Apply:
- main_project_file = str(Path(root) / file) + main_project_file = Path(root) / file @@ - elif ( - file_lower.endswith(KICAD_SCHEMATIC_EXT) and not main_schematic_file - ): - main_schematic_file = str(Path(root) / file) + elif ( + file_lower.endswith(KICAD_SCHEMATIC_EXT) and not main_schematic_file + ): + main_schematic_file = Path(root) / file @@ - elif file_lower.endswith(KICAD_PCB_EXT) and not main_pcb_file: - main_pcb_file = str(Path(root) / file) + elif file_lower.endswith(KICAD_PCB_EXT) and not main_pcb_file: + main_pcb_file = Path(root) / fileOptional (if you prefer strings): convert
file_pathtostr(...)at its declaration instead.Also applies to: 541-556, 561-574
598-633: Generated post-create hook misses imports and will crash at runtime.
The script usesclickandPathwithout importing them.Apply:
script_content = '''""" Post-creation hook for template. This script runs after the template has been used to create a new project. """ +import click +from pathlib import Path + def post_create(context):
🧹 Nitpick comments (21)
kicad_lib_manager/commands/unpin/command.py (1)
49-55: Avoid shadowing built-in name and set explicit file encoding.
- The parameter named
allshadows Python’s built-inall(). Consider renaming the Click destination (e.g.,@click.option("--all", "unpin_all", is_flag=True, ...)) and updating references accordingly.- When reading/writing JSON config files, prefer
open(encoding="utf-8")to avoid locale-dependent decoding issues.kicad_lib_manager/commands/template/command.py (1)
820-825: Make boolean precedence explicit in exclusion check.
andbinds tighter thanor, so it currently works, but adding grouping removes any ambiguity for future readers.- if ( - gitignore_spec - and gitignore_spec.match_file(git_path) - or additional_spec - and additional_spec.match_file(git_path) - ): + if ( + (gitignore_spec and gitignore_spec.match_file(git_path)) + or (additional_spec and additional_spec.match_file(git_path)) + ):kicad_lib_manager/utils/file_ops.py (1)
10-12: LGTM on signature wrap; consider guarding empty encodings.Formatting-only change looks good. Minor: if a caller passes an empty list for
encodings, this function returns""silently. Consider raisingValueErrorwhenencodings == []to avoid masking errors.kicad_lib_manager/commands/update/command.py (4)
91-95: Don’t count dry-run executions as “updated.”Incrementing updated_count in dry-run skews the summary. Either don’t change counters in dry-run or track a separate would_update_count.
Apply:
if dry_run: click.echo(f" Dry run: would execute 'git pull' in {lib_path}") - updated_count += 1 + # No-op in dry-run: do not change counters continue
98-104: Add a timeout to git pull to avoid hanging processes.A hung git process will block the command. Add a sane timeout (e.g., 120s) and handle TimeoutExpired.
result = subprocess.run( ["git", "pull"], cwd=lib_path, capture_output=True, text=True, - check=False, + check=False, + timeout=120, )Also add an except subprocess.TimeoutExpired to report and increment failed_count.
111-118: Avoid parsing “Already up to date” strings; compare commits instead.Git messages vary by version/locale. More robust: capture old_head = git rev-parse HEAD before pull, then compare to new head after pull to decide updated vs up-to-date.
153-165: Programmatic click invocation: prefer main(..., standalone_mode=False).setup_cmd.invoke with a bare Context can miss Click’s option handling and may swallow exceptions differently. Use setup_cmd.main(standalone_mode=False) or call the underlying callback directly if exported.
Example:
- ctx = click.Context(setup_cmd) - setup_cmd.invoke(ctx) + setup_cmd.main(standalone_mode=False)Verify tests still pass and options default as expected.
README.md (1)
48-55: Optional: add Release and CI badges to reflect new workflows.Given this PR adds CI and Release automation, consider adding badges for build status and latest GitHub release to the header.
kicad_lib_manager/utils/metadata.py (1)
39-41: Use logging instead of print for error reporting.Printing in libraries can be noisy and hard to trace. Prefer logging.getLogger(name).warning/error so callers can configure handlers.
Also applies to: 62-63, 89-90, 111-112
kicad_lib_manager/config.py (2)
66-69: Deep-copy DEFAULT_CONFIG to avoid shared mutable state.DEFAULT_CONFIG contains a list (libraries). dict.copy() is shallow; deep-copy prevents accidental mutation of module-level defaults.
+import copy @@ def __init__(self): """Initialize configuration with default values""" - self._config = DEFAULT_CONFIG.copy() + self._config = copy.deepcopy(DEFAULT_CONFIG) self._load_config()
301-307: Persist cleanup after removing invalid entries.You already save when needs_save is True; consider also saving when libraries_raw wasn’t a list and you reset to [] for consistency.
kicad_lib_manager/utils/git_utils.py (1)
33-38: Add timeouts to subprocess.run to harden against hung git processes.Add timeout (e.g., 10s) to both rev-parse calls and handle TimeoutExpired with a clear RuntimeError.
rp = subprocess.run( ["git", "-C", str(repo_path), "rev-parse", "--git-path", "hooks"], capture_output=True, text=True, - check=True, + check=True, + timeout=10, )And similarly for the fallback rev-parse --git-common-dir.
kicad_lib_manager/commands/add_hook/command.py (1)
93-95: Write hook file with explicit UTF-8 and normalized newlines.
Prevents locale-dependent encoding and CRLF surprises on Windows.Apply:
- with post_merge_hook.open("w") as f: + with post_merge_hook.open("w", encoding="utf-8", newline="\n") as f: f.write(new_content)kicad_lib_manager/utils/template.py (1)
67-69: Use explicit UTF-8 when reading/writing text files.
Avoids platform-default encoding pitfalls.- with Path(gitignore_file).open() as f: + with Path(gitignore_file).open(encoding="utf-8") as f: lines = f.readlines() @@ - with Path(metadata_file).open() as f: + with Path(metadata_file).open(encoding="utf-8") as f: metadata = yaml.safe_load(f) @@ - with Path(metadata_file).open("w") as f: + with Path(metadata_file).open("w", encoding="utf-8") as f: yaml.dump(metadata, f, default_flow_style=False)Also applies to: 123-125, 297-299
.github/workflows/python-package.yml (3)
21-21: Trim trailing spaces to satisfy yamllint.
These blanks have spaces; remove them to keep CI lint clean.Also applies to: 26-26, 31-31, 36-36, 40-40, 44-44
22-26: Enable pip cache for faster runs.- - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + cache: pip
11-13: Bound job time to avoid stuck runners.test: runs-on: ${{ matrix.os }} + timeout-minutes: 20.github/workflows/release.yml (1)
14-17: Strip trailing spaces and add final newline.
Yamllint flags these; easy cleanup to keep workflows tidy.Also applies to: 21-21, 26-26, 31-31, 36-36, 45-45, 47-47, 52-52, 54-54, 62-62, 66-66, 71-71, 79-79, 87-87, 90-90, 94-94, 97-97, 99-99, 102-102, 105-105, 108-108, 110-110, 112-112, 117-117, 122-122, 131-131, 135-135, 140-140, 152-152, 157-157
tests/test_unpin_command.py (3)
53-60: Prefer patching Path.open + mock_open for robustness.Path.open may call io.open; patching builtins.open can miss it on some Python versions. Patch pathlib.Path.open with mock_open to make the test resilient.
Apply within this block:
- with patch("builtins.open") as mock_open: - mock_open.return_value.__enter__.return_value.read.return_value = ( - '{"session": {"pinned_symbol_libs": [], "pinned_fp_libs": []}}' - ) + from unittest.mock import mock_open as u_mock_open + m = u_mock_open( + read_data='{"session": {"pinned_symbol_libs": [], "pinned_fp_libs": []}}' + ) + with patch("pathlib.Path.open", m):If you prefer, add the import once at file top instead.
73-78: Repeat the Path.open mock pattern here.Use pathlib.Path.open + mock_open as suggested above to avoid relying on builtins.open.
- with patch("builtins.open") as mock_open: - mock_open.return_value.__enter__.return_value.read.return_value = '{"session": {"pinned_symbol_libs": ["lib1"], "pinned_fp_libs": []}}' + from unittest.mock import mock_open as u_mock_open + m = u_mock_open( + read_data='{"session": {"pinned_symbol_libs": ["lib1"], "pinned_fp_libs": []}}' + ) + with patch("pathlib.Path.open", m):
91-96: Repeat the Path.open mock pattern for the footprints case.- with patch("builtins.open") as mock_open: - mock_open.return_value.__enter__.return_value.read.return_value = '{"session": {"pinned_symbol_libs": [], "pinned_fp_libs": ["lib1"]}}' + from unittest.mock import mock_open as u_mock_open + m = u_mock_open( + read_data='{"session": {"pinned_symbol_libs": [], "pinned_fp_libs": ["lib1"]}}' + ) + with patch("pathlib.Path.open", m):
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (19)
.github/workflows/python-package.yml(1 hunks).github/workflows/release.yml(1 hunks)README.md(2 hunks)kicad_lib_manager/commands/add_hook/command.py(1 hunks)kicad_lib_manager/commands/config/command.py(2 hunks)kicad_lib_manager/commands/setup/command.py(0 hunks)kicad_lib_manager/commands/status/command.py(4 hunks)kicad_lib_manager/commands/template/command.py(1 hunks)kicad_lib_manager/commands/unpin/command.py(1 hunks)kicad_lib_manager/commands/update/command.py(2 hunks)kicad_lib_manager/config.py(6 hunks)kicad_lib_manager/utils/file_ops.py(1 hunks)kicad_lib_manager/utils/git_utils.py(4 hunks)kicad_lib_manager/utils/metadata.py(1 hunks)kicad_lib_manager/utils/template.py(2 hunks)tests/test_config_commands.py(1 hunks)tests/test_git_utils.py(4 hunks)tests/test_unpin_command.py(1 hunks)tests/test_update_command.py(2 hunks)
💤 Files with no reviewable changes (1)
- kicad_lib_manager/commands/setup/command.py
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-05T07:17:34.953Z
Learnt from: barisgit
PR: barisgit/KiLM#3
File: kicad_lib_manager/__init__.py:5-5
Timestamp: 2025-09-05T07:17:34.953Z
Learning: In the KiLM project, different version numbers are intentionally maintained separately: the main Python package version in kicad_lib_manager/__init__.py, the docs site version in docs/package.json, and example versions in documentation files are not meant to be synchronized.
Applied to files:
README.md
🧬 Code graph analysis (5)
tests/test_config_commands.py (1)
tests/test_update_command.py (1)
mock_config(21-29)
kicad_lib_manager/commands/status/command.py (1)
kicad_lib_manager/config.py (1)
get(66-70)
tests/test_unpin_command.py (1)
kicad_lib_manager/commands/unpin/command.py (1)
unpin(49-189)
kicad_lib_manager/commands/add_hook/command.py (1)
kicad_lib_manager/utils/git_utils.py (2)
merge_hook_content(86-120)create_kilm_hook_content(123-141)
kicad_lib_manager/commands/update/command.py (2)
kicad_lib_manager/library_manager.py (1)
add_libraries(176-335)kicad_lib_manager/commands/init/command.py (1)
init(58-252)
🪛 YAMLlint (1.37.1)
.github/workflows/python-package.yml
[error] 21-21: trailing spaces
(trailing-spaces)
[error] 26-26: trailing spaces
(trailing-spaces)
[error] 31-31: trailing spaces
(trailing-spaces)
[error] 36-36: trailing spaces
(trailing-spaces)
[error] 40-40: trailing spaces
(trailing-spaces)
[error] 44-44: trailing spaces
(trailing-spaces)
.github/workflows/release.yml
[error] 21-21: trailing spaces
(trailing-spaces)
[warning] 23-23: wrong indentation: expected 6 but found 4
(indentation)
[error] 26-26: trailing spaces
(trailing-spaces)
[error] 31-31: trailing spaces
(trailing-spaces)
[error] 36-36: trailing spaces
(trailing-spaces)
[error] 45-45: trailing spaces
(trailing-spaces)
[error] 47-47: trailing spaces
(trailing-spaces)
[error] 52-52: trailing spaces
(trailing-spaces)
[error] 54-54: trailing spaces
(trailing-spaces)
[error] 62-62: trailing spaces
(trailing-spaces)
[error] 66-66: trailing spaces
(trailing-spaces)
[error] 71-71: trailing spaces
(trailing-spaces)
[error] 79-79: trailing spaces
(trailing-spaces)
[error] 87-87: trailing spaces
(trailing-spaces)
[error] 90-90: trailing spaces
(trailing-spaces)
[error] 94-94: trailing spaces
(trailing-spaces)
[error] 97-97: trailing spaces
(trailing-spaces)
[error] 99-99: trailing spaces
(trailing-spaces)
[error] 102-102: trailing spaces
(trailing-spaces)
[error] 105-105: trailing spaces
(trailing-spaces)
[error] 108-108: trailing spaces
(trailing-spaces)
[error] 110-110: trailing spaces
(trailing-spaces)
[error] 112-112: trailing spaces
(trailing-spaces)
[error] 117-117: trailing spaces
(trailing-spaces)
[error] 122-122: trailing spaces
(trailing-spaces)
[error] 131-131: trailing spaces
(trailing-spaces)
[error] 135-135: trailing spaces
(trailing-spaces)
[error] 140-140: trailing spaces
(trailing-spaces)
[error] 152-152: trailing spaces
(trailing-spaces)
[error] 157-157: no new line character at the end of file
(new-line-at-end-of-file)
🪛 LanguageTool
README.md
[grammar] ~50-~50: There might be a mistake here.
Context: ...ristovnik.me)** | Guide | Description | |-------|-------------| | [Getting Start...
(QB_NEW_EN)
[grammar] ~51-~51: There might be a mistake here.
Context: ... | Description | |-------|-------------| | [Getting Started](https://kilm.aristov...
(QB_NEW_EN)
[grammar] ~52-~52: There might be a mistake here.
Context: ...onsumer workflows with Git integration | | [Configuration](https://kilm.aristovni...
(QB_NEW_EN)
[grammar] ~53-~53: There might be a mistake here.
Context: ...iLM and KiCad configuration management | | [CLI Reference](https://kilm.aristovni...
(QB_NEW_EN)
[grammar] ~54-~54: There might be a mistake here.
Context: ...te command documentation with examples | | [Development](https://kilm.aristovnik....
(QB_NEW_EN)
🔇 Additional comments (30)
kicad_lib_manager/commands/unpin/command.py (1)
53-55: LGTM on the multi-line UsageError.Readability improved; behavior unchanged.
kicad_lib_manager/commands/config/command.py (2)
295-297: LGTM: clearer multi-line error echo.Keeps the same stderr behavior and exit semantics.
355-357: LGTM: parenthesized condition reads better.The logic is unchanged and less error-prone to future edits.
kicad_lib_manager/commands/status/command.py (1)
29-33: LGTM: multiline guards improve readability.The checks for config presence, current library marker, and current library display are clearer with no semantic change.
Also applies to: 55-57, 75-77, 92-94
README.md (2)
11-11: Docs heading tweak looks good.Emoji removal and bolded “Official Documentation” reads clean. No issues.
44-44: Nice: clearer callout for installation guide.The blockquote improves scannability.
kicad_lib_manager/utils/metadata.py (1)
15-17: Formatting-only change: LGTM.kicad_lib_manager/config.py (3)
21-21: Tiny style change: LGTM.
128-129: Minor: comprehension reflow is fine.No semantic change; readability is acceptable.
267-271: Good guardrail for malformed configs.Warning with type info helps users recover.
kicad_lib_manager/utils/git_utils.py (1)
100-118: merge_hook_content replacement is correct and idempotent.Using exact marker bounds and re-inserting kilm_content (which includes markers) is sound. Split/join on "\n" is consistent.
kicad_lib_manager/commands/add_hook/command.py (1)
80-82: LGTM: multiline call improves readability.
No behavioral change.kicad_lib_manager/utils/template.py (2)
701-701: LGTM: signature formatting only.Also applies to: 738-738
416-459: Sanity-check: top-level file discovery assumes “root or one subdir”.
If main files live deeper, they won’t be detected. Confirm this constraint is intentional.Would you like me to add a fallback that scans all depths but prefers shallow matches?
.github/workflows/python-package.yml (1)
45-51: Codecov v4 token requirements.
Public repos are fine; private need CODECOV_TOKEN. Make sure org/repo visibility matches intended behavior..github/workflows/release.yml (2)
111-131: Validate install instructions and wheel name match your distribution name.
The notes usekilm, but the package module iskicad_lib_manager. Avoid confusing users.If the project publishes as
kicad-lib-manager, update pip/pipx commands and wheel filename pattern accordingly. I can generate the edits once you confirm the canonical package name.
154-157: Ensure PyPI Trusted Publisher is configured for this repo.
OIDC publish requires PyPI project linkage; otherwise this step fails.tests/test_git_utils.py (1)
78-78: LGTM: consistent patch decorators with double quotes.Also applies to: 93-93, 110-110, 125-125
tests/test_update_command.py (2)
26-28: LGTM: reformatted monkeypatch for readability; behavior unchanged.
174-176: LGTM: consistent multiline monkeypatch; no semantic changes.tests/test_config_commands.py (1)
44-46: LGTM: multiline monkeypatch improves readability; same target and effect.tests/test_unpin_command.py (9)
22-26: LGTM: assertion still matches current Click error message; exit code 2 is correct.
30-34: LGTM: same mutual-exclusivity check, message preserved.
38-44: LGTM: multiline invocation formatting only; assertions unchanged.
48-51: LGTM: mocking find_kicad_config is appropriate for isolating filesystem.
62-62: LGTM: invocation remains the same; allowing exit_code in [0, 1] keeps test tolerant.
68-71: LGTM: consistent mocking of config path.
80-80: LGTM: symbols-only path remains covered; tolerant exit code assertion OK.
86-89: LGTM: same isolation pattern for footprints-only case.
98-98: LGTM: footprints-only invocation; tolerant exit code assertion OK.
| - name: Type check with pyrefly | ||
| run: | | ||
| pyrefly | ||
|
|
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
Confirm pyrefly is installed in dev extras or add explicit install.
Without it, the job will fail at type-check step.
Run:
🏁 Script executed:
#!/bin/bash
# Verify pyrefly presence in dev dependencies
rg -nP '^\s*dev\s*=\s*\[.*pyrefly' -n pyproject.toml || rg -n 'pyrefly' -n requirements* || python -c 'import pyrefly' 2>/dev/null || echo "pyrefly missing"Length of output: 213
Add installation for pyrefly in CI workflow
The pyrefly command isn’t installed by default (confirmed missing from dev dependencies), so the type-check step will fail. In .github/workflows/python-package.yml (around lines 37–40), before running pyrefly, either:
-
Install it explicitly:
- run: pip install pyrefly
-
Or ensure it’s listed in your project’s dev dependencies (e.g.
pyproject.tomlextras orrequirements-dev.txt).
🧰 Tools
🪛 YAMLlint (1.37.1)
[error] 40-40: trailing spaces
(trailing-spaces)
🤖 Prompt for AI Agents
.github/workflows/python-package.yml around lines 37 to 40: the workflow runs
the pyrefly command but pyrefly is not guaranteed to be installed in the CI
environment; update the job to install pyrefly before running it (for example
add a step that runs pip install pyrefly) or ensure pyrefly is added to the
project's dev dependencies (pyproject.toml extras or requirements-dev.txt) and
install those dev deps in the workflow prior to executing pyrefly.
| PACKAGE_VERSION=$(python -c "import kicad_lib_manager; print(kicad_lib_manager.__version__)") | ||
|
|
||
| if [[ "$TAG_VERSION" != "$PACKAGE_VERSION" ]]; then | ||
| echo "Version mismatch: tag=$TAG_VERSION, package=$PACKAGE_VERSION" | ||
| exit 1 | ||
| fi | ||
|
|
||
| echo "VERSION=$TAG_VERSION" >> $GITHUB_ENV | ||
|
|
There was a problem hiding this comment.
🛠️ Refactor suggestion
Don’t import package to read version during CI.
Import may fail before deps are installed; parse the version file instead.
- PACKAGE_VERSION=$(python -c "import kicad_lib_manager; print(kicad_lib_manager.__version__)")
+ PACKAGE_VERSION=$(python - <<'PY'
+from pathlib import Path
+import re
+m = re.search(r"__version__\s*=\s*['\"]([^'\"]+)['\"]",
+ Path("kicad_lib_manager/__init__.py").read_text(encoding="utf-8"))
+print(m.group(1) if m else "")
+PY
+)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| PACKAGE_VERSION=$(python -c "import kicad_lib_manager; print(kicad_lib_manager.__version__)") | |
| if [[ "$TAG_VERSION" != "$PACKAGE_VERSION" ]]; then | |
| echo "Version mismatch: tag=$TAG_VERSION, package=$PACKAGE_VERSION" | |
| exit 1 | |
| fi | |
| echo "VERSION=$TAG_VERSION" >> $GITHUB_ENV | |
| PACKAGE_VERSION=$(python - <<'PY' | |
| from pathlib import Path | |
| import re | |
| m = re.search(r"__version__\s*=\s*['\"]([^'\"]+)['\"]", | |
| Path("kicad_lib_manager/__init__.py").read_text(encoding="utf-8")) | |
| print(m.group(1) if m else "") | |
| PY | |
| ) | |
| if [[ "$TAG_VERSION" != "$PACKAGE_VERSION" ]]; then | |
| echo "Version mismatch: tag=$TAG_VERSION, package=$PACKAGE_VERSION" | |
| exit 1 | |
| fi | |
| echo "VERSION=$TAG_VERSION" >> $GITHUB_ENV |
🧰 Tools
🪛 YAMLlint (1.37.1)
[error] 47-47: trailing spaces
(trailing-spaces)
[error] 52-52: trailing spaces
(trailing-spaces)
[error] 54-54: trailing spaces
(trailing-spaces)
🤖 Prompt for AI Agents
In .github/workflows/release.yml around lines 46 to 54, the workflow imports the
package to get its version which can fail when dependencies are not yet
installed; instead read and parse the package's version source file (e.g., the
module's __init__.py or dedicated version file) directly to extract the
__version__ string without importing. Replace the python import step with a
shell-safe extraction that opens the version file, finds the line defining
__version__, parses the quoted version value, assigns it to PACKAGE_VERSION,
then perform the TAG_VERSION vs PACKAGE_VERSION check and write VERSION to
GITHUB_ENV as before.
| # Get PR information | ||
| PRS=$(gh pr list --state merged --search "merged:>${PREVIOUS_TAG:-1970-01-01}" --json number,title,url --jq '.[] | "- #\(.number): \(.title) (\(.url))"' 2>/dev/null || echo "") | ||
|
|
||
| # Generate release notes | ||
| cat > release_notes.md << EOF | ||
| ## What's Changed in v${VERSION} | ||
|
|
||
| ### Commits | ||
| $(echo "$COMMITS" | sed 's/^/- /') | ||
|
|
||
| EOF | ||
|
|
||
| if [ ! -z "$PRS" ]; then | ||
| cat >> release_notes.md << EOF | ||
|
|
||
| ### Pull Requests | ||
| $(echo "$PRS") | ||
|
|
There was a problem hiding this comment.
Fix PR query: using tag name in merged:> filter is invalid.
gh pr list --search merged:> expects a date, not a tag.
Apply:
- # Get PR information
- PRS=$(gh pr list --state merged --search "merged:>${PREVIOUS_TAG:-1970-01-01}" --json number,title,url --jq '.[] | "- #\(.number): \(.title) (\(.url))"' 2>/dev/null || echo "")
+ # Compute a date to query PRs merged since previous tag
+ if [ -n "$PREVIOUS_TAG" ]; then
+ SINCE=$(git show -s --format=%cI "$PREVIOUS_TAG")
+ else
+ SINCE="1970-01-01"
+ fi
+ # Get PR information since that date
+ PRS=$(gh pr list --state merged --search "merged:>$SINCE" --json number,title,url --jq '.[] | "- #\(.number): \(.title) (\(.url))"' 2>/dev/null || echo "")📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| # Get PR information | |
| PRS=$(gh pr list --state merged --search "merged:>${PREVIOUS_TAG:-1970-01-01}" --json number,title,url --jq '.[] | "- #\(.number): \(.title) (\(.url))"' 2>/dev/null || echo "") | |
| # Generate release notes | |
| cat > release_notes.md << EOF | |
| ## What's Changed in v${VERSION} | |
| ### Commits | |
| $(echo "$COMMITS" | sed 's/^/- /') | |
| EOF | |
| if [ ! -z "$PRS" ]; then | |
| cat >> release_notes.md << EOF | |
| ### Pull Requests | |
| $(echo "$PRS") | |
| # Compute a date to query PRs merged since previous tag | |
| if [ -n "$PREVIOUS_TAG" ]; then | |
| SINCE=$(git show -s --format=%cI "$PREVIOUS_TAG") | |
| else | |
| SINCE="1970-01-01" | |
| fi | |
| # Get PR information since that date | |
| PRS=$(gh pr list --state merged --search "merged:>$SINCE" \ | |
| --json number,title,url \ | |
| --jq '.[] | "- #\(.number): \(.title) (\(.url))"' \ | |
| 2>/dev/null || echo "") | |
| # Generate release notes | |
| cat > release_notes.md << EOF | |
| ## What's Changed in v${VERSION} | |
| ### Commits | |
| $(echo "$COMMITS" | sed 's/^/- /') | |
| EOF | |
| if [ ! -z "$PRS" ]; then | |
| cat >> release_notes.md << EOF | |
| ### Pull Requests | |
| $(echo "$PRS") | |
🧰 Tools
🪛 YAMLlint (1.37.1)
[error] 90-90: trailing spaces
(trailing-spaces)
[error] 94-94: trailing spaces
(trailing-spaces)
[error] 97-97: trailing spaces
(trailing-spaces)
[error] 99-99: trailing spaces
(trailing-spaces)
[error] 102-102: trailing spaces
(trailing-spaces)
[error] 105-105: trailing spaces
(trailing-spaces)
🤖 Prompt for AI Agents
.github/workflows/release.yml around lines 88 to 105: the GH CLI search filter
uses merged:>${PREVIOUS_TAG} but gh expects a date, not a tag name; change the
workflow to derive the commit/tag date for PREVIOUS_TAG (e.g. via a git command
that outputs the tag/commit date) and then pass that ISO date into the gh pr
list --search "merged:>DATE" call so the search uses a valid date string; ensure
the derived date variable is set before the gh pr list invocation and used in
place of the tag.
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
kicad_lib_manager/config.py (1)
261-285: Persist normalization changes to disk (missing save()).
When _normalize_libraries_field() drops invalid entries, the in-memory config is updated but not persisted; a restart reintroduces bad data. Save when changes occur.Apply:
def _normalize_libraries_field(self) -> None: @@ - # Validate and clean up each library entry - normalized_libraries: List[LibraryDict] = [] + # Validate and clean up each library entry + normalized_libraries: List[LibraryDict] = [] + needs_save = False for lib in libraries: validated_lib = _validate_library_entry(lib) if validated_lib is not None: normalized_libraries.append(validated_lib) else: click.echo(f"Warning: Skipping invalid library entry: {lib}", err=True) - - self._config["libraries"] = normalized_libraries + needs_save = True + + if normalized_libraries != libraries: + self._config["libraries"] = normalized_libraries + if needs_save: + self.save()kicad_lib_manager/utils/template.py (1)
600-635: Hook script uses click without import.
post_create.py calls click.echo but doesn’t import click; will raise NameError at runtime.script_content = '''""" Post-creation hook for template. This script runs after the template has been used to create a new project. """ +import click + def post_create(context):
🧹 Nitpick comments (4)
kicad_lib_manager/config.py (1)
294-301: Also save when resetting non-list libraries.
If libraries is not a list, you reset it but don’t save. Persist this correction.- if not isinstance(libraries_raw, list): + if not isinstance(libraries_raw, list): click.echo( f"Warning: libraries field was {type(libraries_raw).__name__}, resetting to empty list", err=True, ) self._config["libraries"] = [] - return [] + self.save() + return []kicad_lib_manager/utils/template.py (3)
486-495: Safer exclude check: avoid substring matches.
Using “excl in rel_root” can exclude unintended paths (e.g., “invent” matching “env”). Compare path parts.- if any(excl in rel_root for excl in common_excludes): + if any(part in common_excludes for part in Path(rel_root).parts): continue
87-91: Exact-match when augmenting .gitignore patterns.
Substring matching can add duplicates; normalize and compare trimmed lines.- for pattern in common_patterns: - if not any(pattern in line for line in lines): - lines.append(pattern + "\n") + existing = {ln.strip() for ln in lines} + for pattern in common_patterns: + if pattern not in existing: + lines.append(pattern + "\n") + existing.add(pattern)
803-808: Use StrictUndefined for template string rendering (consistency with filenames/files).
This surfaces missing variables early.- try: - template = jinja2.Template(template_str) - return template.render(**variables) + try: + env = jinja2.Environment(undefined=jinja2.StrictUndefined) + template = env.from_string(template_str) + return template.render(**variables)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (3)
kicad_lib_manager/config.py(7 hunks)kicad_lib_manager/utils/template.py(7 hunks)tests/test_config_commands.py(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- tests/test_config_commands.py
🧰 Additional context used
🧬 Code graph analysis (1)
kicad_lib_manager/utils/template.py (1)
kicad_lib_manager/commands/template/command.py (3)
template(37-43)make(560-933)create(78-498)
🔇 Additional comments (3)
kicad_lib_manager/config.py (2)
66-69: LGTM: typed get() API looks good.
323-326: Typed constructor is clean.kicad_lib_manager/utils/template.py (1)
752-767: Confirm KiCad regex robustness.
S-expression formats vary; ensure the PCB sheetfile and schematic project regexes won’t over-match. Recommend tests with KiCad 7/8 sample files.I can add tests with sample .kicad_pro/.kicad_sch/.kicad_pcb to validate substitutions across versions. Want me to open a follow-up PR?
| if isinstance(lib, dict) and all(key in lib for key in ["name", "path", "type"]): | ||
| return _make_library_dict( | ||
| name=str(lib["name"]), | ||
| path=str(lib["path"]), | ||
| type_=str(lib["type"]), | ||
| ) | ||
| return None |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Enforce allowed library types and normalize path.
Guard against typos (e.g., “gihub”) and normalize paths once here.
- if isinstance(lib, dict) and all(key in lib for key in ["name", "path", "type"]):
- return _make_library_dict(
- name=str(lib["name"]),
- path=str(lib["path"]),
- type_=str(lib["type"]),
- )
+ if isinstance(lib, dict) and all(key in lib for key in ["name", "path", "type"]):
+ name = str(lib["name"])
+ # Normalize path separators; store as string
+ path = str(Path(str(lib["path"])))
+ type_val = str(lib["type"]).lower().strip()
+ if type_val not in {"github", "cloud"}:
+ click.echo(f"Warning: Invalid library type '{type_val}' for {name}", err=True)
+ return None
+ return _make_library_dict(name=name, path=path, type_=type_val)Committable suggestion skipped: line range outside the PR's diff.
| main_project_file: Optional[str] = None | ||
| main_schematic_file: Optional[str] = None | ||
| main_pcb_file: Optional[str] = None | ||
|
|
There was a problem hiding this comment.
Bug: Path vs str mismatch prevents main KiCad files from being templated.
main_project_file/main_schematic_file/main_pcb_file are stored as str but later compared to Path objects; comparisons always fail, so special handling never runs.
Apply:
- main_project_file: Optional[str] = None
- main_schematic_file: Optional[str] = None
- main_pcb_file: Optional[str] = None
+ main_project_file: Optional[Path] = None
+ main_schematic_file: Optional[Path] = None
+ main_pcb_file: Optional[Path] = None
@@
- if file_lower.endswith(KICAD_PROJECT_EXT) and not main_project_file:
- main_project_file = str(Path(root) / file_name)
+ if file_lower.endswith(KICAD_PROJECT_EXT) and not main_project_file:
+ main_project_file = Path(root) / file_name
click.echo(f"Found main project file: {file_name}")
@@
- elif (
- file_lower.endswith(KICAD_SCHEMATIC_EXT) and not main_schematic_file
- ):
- main_schematic_file = str(Path(root) / file_name)
+ elif (
+ file_lower.endswith(KICAD_SCHEMATIC_EXT) and not main_schematic_file
+ ):
+ main_schematic_file = Path(root) / file_name
click.echo(f"Found main schematic file: {file_name}")
@@
- elif file_lower.endswith(KICAD_PCB_EXT) and not main_pcb_file:
- main_pcb_file = str(Path(root) / file_name)
+ elif file_lower.endswith(KICAD_PCB_EXT) and not main_pcb_file:
+ main_pcb_file = Path(root) / file_name
click.echo(f"Found main PCB file: {file_name}")
@@
- if main_project_file and file_path == main_project_file:
+ if main_project_file and file_path == main_project_file:
target_file = (
template_content_dir / rel_root / "%{project_filename}.kicad_pro"
)
- elif main_schematic_file and file_path == main_schematic_file:
+ elif main_schematic_file and file_path == main_schematic_file:
target_file = (
template_content_dir / rel_root / "%{project_filename}.kicad_sch"
)
- elif main_pcb_file and file_path == main_pcb_file:
+ elif main_pcb_file and file_path == main_pcb_file:
target_file = (
template_content_dir / rel_root / "%{project_filename}.kicad_pcb"
)
@@
- if main_project_file and file_path == main_project_file:
+ if main_project_file and file_path == main_project_file:
process_kicad_project_file(
source_file, target_file, metadata.get("variables", {})
)
- elif main_schematic_file and file_path == main_schematic_file:
+ elif main_schematic_file and file_path == main_schematic_file:
process_kicad_schematic_file(source_file, target_file)
- elif main_pcb_file and file_path == main_pcb_file:
+ elif main_pcb_file and file_path == main_pcb_file:
process_kicad_pcb_file(source_file, target_file)Also applies to: 448-460, 543-554, 564-576
🤖 Prompt for AI Agents
In kicad_lib_manager/utils/template.py around lines 413-416 (and similarly at
448-460, 543-554, 564-576) the fields
main_project_file/main_schematic_file/main_pcb_file are annotated/stored as str
but later compared to Path objects causing comparisons to always fail; change
those fields to Optional[Path] (or Optional[Union[str, Path>] if needed for
backwards compatibility) and ensure any assignments convert incoming strings to
Path (Path(value)) or convert the Path to str only at I/O/serialization
boundaries so runtime comparisons use Path objects and the special-case handling
executes as intended.
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (1)
.github/workflows/python-package.yml (1)
37-40: Ensure pyrefly is installed before running type checkPrevious review noted pyrefly isn’t in dev deps; step will fail unless you install it explicitly.
Apply:
- name: Install dependencies run: | python -m pip install --upgrade pip - pip install -e .[dev] + pip install -e .[dev] + pip install pyreflyAlso applies to: 27-31
🧹 Nitpick comments (2)
.github/workflows/python-package.yml (2)
21-21: Remove trailing spaces flagged by yamllintLines 21, 26, 31, 36, 40, 44 have trailing spaces. Please strip them to satisfy YAML linters.
Also applies to: 26-26, 31-31, 36-36, 40-40, 44-44
23-26: Add pip cache for faster installsUse built-in caching to speed repeated CI runs.
Apply:
uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} + cache: 'pip'
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (3)
.github/workflows/python-package.yml(1 hunks)kicad_lib_manager/utils/git_utils.py(6 hunks)tests/test_git_utils.py(6 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- tests/test_git_utils.py
- kicad_lib_manager/utils/git_utils.py
🧰 Additional context used
🪛 YAMLlint (1.37.1)
.github/workflows/python-package.yml
[error] 21-21: trailing spaces
(trailing-spaces)
[error] 26-26: trailing spaces
(trailing-spaces)
[error] 31-31: trailing spaces
(trailing-spaces)
[error] 36-36: trailing spaces
(trailing-spaces)
[error] 40-40: trailing spaces
(trailing-spaces)
[error] 44-44: trailing spaces
(trailing-spaces)
🔇 Additional comments (3)
.github/workflows/python-package.yml (3)
3-8: Triggers look goodPush to main/feature branches and PRs to main are covered.
41-44: Pytest with coverage: LGTMCoverage XML generation fits the Codecov upload step.
32-36: No action needed: ruff is installed via dev extras
ruff >= 0.1.0 is declared in pyproject.toml, so the existingpip install -e .[dev]step already installs it.
| test: | ||
| runs-on: ${{ matrix.os }} | ||
| strategy: | ||
| fail-fast: false | ||
| matrix: | ||
| os: [ubuntu-latest, windows-latest, macos-latest] | ||
| python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] | ||
|
|
There was a problem hiding this comment.
🛠️ Refactor suggestion
Matrix includes Python 3.13; enable prereleases to avoid runner misses
3.13 may be RC/pre-release on some runners. Enable prereleases for setup-python.
Apply:
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
+ allow-prereleases: trueAlso applies to: 23-26
🤖 Prompt for AI Agents
.github/workflows/python-package.yml lines 11-18 (and likewise update lines
23-26): the workflow matrix includes Python 3.13 which may be a pre-release on
some runners; update the setup-python step to enable prerelease checking by
adding the setup-python input (check-latest: true) so the action can install
pre-release/RC versions. Modify the setup-python job step(s) to include
check-latest: true under with:, and apply the same change to the other repeated
setup-python step at lines 23-26.
.github/workflows/python-package.yml
Outdated
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
Codecov v4 inputs/token — verify and adjust
v4 commonly requires a token for private repos and may use files: instead of file:. Please verify and update.
Apply (if needed):
- - name: Upload coverage to Codecov
+ - name: Upload coverage to Codecov
if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.11'
uses: codecov/codecov-action@v4
with:
- file: ./coverage.xml
+ files: ./coverage.xml
fail_ci_if_error: false
+ token: ${{ secrets.CODECOV_TOKEN }}Queries to confirm:
🌐 Web query:
Does codecov/codecov-action@v4 require a CODECOV_TOKEN for uploads from GitHub Actions, and is the input key `files` (plural) or `file` (singular)?
💡 Result:
Short answer:
- Yes — codecov/codecov-action@v4 generally requires a CODECOV_TOKEN for uploads (unless you use OIDC or your repo/public-org is configured to allow tokenless uploads). [1][2]
- The input is files (plural); file (singular) is deprecated in favor of files. [1]
Sources: [1] codecov/codecov-action README (v4) — inputs and token/OIDC notes. [2] Codecov docs on tokens / tokenless uploads.
Update Codecov v4 action inputs
- Replace the deprecated
file:key withfiles: - Add
token: ${{ secrets.CODECOV_TOKEN }}(required for private repos; omit only if you’ve enabled tokenless OIDC uploads)
- - name: Upload coverage to Codecov
+ - name: Upload coverage to Codecov
if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.11'
uses: codecov/codecov-action@v4
with:
- file: ./coverage.xml
+ files: ./coverage.xml
+ token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: false📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| - name: Upload coverage to Codecov | |
| if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.11' | |
| uses: codecov/codecov-action@v4 | |
| with: | |
| file: ./coverage.xml | |
| fail_ci_if_error: false | |
| - name: Upload coverage to Codecov | |
| if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.11' | |
| uses: codecov/codecov-action@v4 | |
| with: | |
| files: ./coverage.xml | |
| token: ${{ secrets.CODECOV_TOKEN }} | |
| fail_ci_if_error: false |
🤖 Prompt for AI Agents
.github/workflows/python-package.yml around lines 45-50: the Codecov v4 action
inputs are outdated—replace the deprecated file: key with files: (accepts a glob
or list like ./coverage.xml) and add the token input using token: ${{
secrets.CODECOV_TOKEN }} (omit only if tokenless OIDC uploads are enabled);
ensure the files value is correct and keep fail_ci_if_error: false as before.
Add auto release on new version
Summary by CodeRabbit
New Features
Bug Fixes
Chores
Documentation
Tests