Skip to content

fix: Fuzzy matching fallback + CI/CD pipeline consolidation#114

Merged
docdyhr merged 4 commits intomasterfrom
fix/fuzzy-fallback-and-ci-improvements
Mar 17, 2026
Merged

fix: Fuzzy matching fallback + CI/CD pipeline consolidation#114
docdyhr merged 4 commits intomasterfrom
fix/fuzzy-fallback-and-ci-improvements

Conversation

@docdyhr
Copy link
Owner

@docdyhr docdyhr commented Mar 17, 2026

Summary

Fuzzy Matching Fix

  • compare_fuzzy, similarity_score, and partial_ratio now use difflib.SequenceMatcher as fallback when rapidfuzz/fuzzywuzzy are not installed
  • Check USE_RAPIDFUZZ/USE_FUZZYWUZZY flags instead of truthy _fuzz object
  • Fix test patches to also set flags (tests failed on CI where rapidfuzz IS installed)

CI/CD Pipeline Consolidation (10 → 8 workflows)

Before After Change
ci.yml + advanced-ci.yml ci.yml Merged — single unified CI with all jobs
publish-pypi.yml + release.yml release.yml Merged — single release pipeline with sigstore signing

Pipeline Improvements

  • Concurrency groups on all workflows — cancel stale PR runs, save runner minutes
  • Composite action (setup-python-deps) used consistently across all jobs
  • Post-release smoke tests — install from PyPI on all Python/OS combos, verify version + CLI + imports
  • Scoped triggers — lint/coverage/security/performance only run on master (CI workflow handles feature branches)
  • Artifact retention — 14d for CI, 30d for releases/security
  • Fixed Final Validation — handles skipped integration-tests gracefully
  • Fixed coverage threshold — retry path matched to primary (50%)
  • Unpinned patch versions — TruffleHog v3.93.8v3, dependabot/fetch-metadata v2.5.0v2
  • Replaced fragile polling in dependabot-auto-merge with gh pr checks --watch

README Updates

  • Test count: 1,136 → 2,158
  • Added Release Pipeline + PyPI badges

Test plan

  • All 2,158 tests pass locally (0 failures, 15 skipped)
  • All pre-commit hooks pass
  • All YAML files validate
  • CI pipeline validates on GitHub

Note on branch protection

The workflow file names changed (advanced-ci.yml deleted, ci.yml rebuilt). If branch protection rules reference specific check names from the old workflows, they may need to be updated to the new check names.

🤖 Generated with Claude Code

…nstalled

The fuzzy matching functions (compare_fuzzy, similarity_score, partial_ratio)
relied on _MinimalFuzz which only returned binary 0/100 scores. When rapidfuzz
is not installed, similar strings like "1.2.3" vs "1.2.4" incorrectly scored 0.

Now checks USE_RAPIDFUZZ/USE_FUZZYWUZZY flags instead of truthy _fuzz object,
and falls back to difflib.SequenceMatcher for proper fuzzy scoring.

Also includes:
- CI: Harden security step error handling in advanced-ci.yml
- CI: Raise coverage threshold from 15% to 50%
- CI: Replace fountainhead/action-wait-for-check with gh pr checks polling
- Enhanced matching: Add SequenceMatcher fallback for fuzz-less environments

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Mar 17, 2026

Reviewer's Guide

Implements a more realistic fuzzy-matching fallback using difflib.SequenceMatcher when rapidfuzz/fuzzywuzzy are unavailable, adjusts tests to the new scoring behavior, adds a character-level fallback in enhanced_matching, and hardens CI workflows by making security scans non-blocking, renaming artifacts, increasing coverage thresholds, and replacing a deprecated Dependabot wait-for-check action with a gh pr checks polling loop.

File-Level Changes

Change Details Files
Replace minimal fuzzy matching fallback with SequenceMatcher-based scoring across fuzzy helpers and update tests accordingly.
  • Import SequenceMatcher and use it as the default fallback in similarity_score, partial_ratio, and compare_fuzzy when rapidfuzz/fuzzywuzzy are not available or fail.
  • Change similarity_score and partial_ratio to only call _fuzz when USE_RAPIDFUZZ or USE_FUZZYWUZZY is true, logging and falling back to SequenceMatcher on errors.
  • Adjust fallback logic in partial_ratio to return 100 when one string contains the other before using SequenceMatcher for partial similarity.
  • Update tests to assert inequality thresholds (e.g., < 50, > 50) rather than exact 0/70 scores and to patch USE_RAPIDFUZZ when simulating broken fuzz implementations.
  • Modify compare_fuzzy tests for no-fuzz/minimal-fallback paths to expect low-but-nonzero SequenceMatcher scores instead of hard 0.
versiontracker/version/fuzzy.py
tests/test_version_fuzzy.py
Add character-level SequenceMatcher fallback in enhanced_matching when fuzz libraries are unavailable.
  • When no fuzz implementation is available, compute a SequenceMatcher-based score on normalized names and optionally on versions with spaces removed.
  • Append these character-level scores into the existing scores list so they participate in the overall similarity calculation.
versiontracker/enhanced_matching.py
Harden and adjust advanced CI security workflow to be non-blocking while still collecting reports.
  • Rename the security job to "Advanced Security Analysis" and its uploaded artifact to "advanced-security-reports".
  • Wrap Bandit invocations with
Increase coverage enforcement threshold in coverage workflow.
  • Raise pytest --cov-fail-under threshold from 15 to 50 to tighten coverage requirements in CI.
.github/workflows/coverage.yml
Modernize Dependabot auto-merge workflow to use gh pr checks instead of a deprecated wait-for-check action.
  • Remove fountainhead/action-wait-for-check steps that separately waited for "ci" and "security" checks.
  • Add a shell loop that calls gh pr checks on the PR URL up to 60 times with 30s intervals, failing immediately on any failing check, continuing while checks are pending, and timing out after ~30 minutes.
  • Export PR_URL and GITHUB_TOKEN to support gh CLI usage and drop the conditional auto-merge if-expression now that the step fails when checks fail.
.github/workflows/dependabot-auto-merge.yml

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@github-actions
Copy link

🔒 Security Analysis Report

Security Analysis Report

Generated: Tue Mar 17 08:18:45 UTC 2026
Repository: docdyhr/versiontracker
Commit: fc5907a

Bandit Security Scan

�[?25l
�[2KWorking... �[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━�[0m �[35m  0%�[0m �[36m-:--:--�[0m
�[2KWorking... �[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━�[0m �[35m  0%�[0m �[36m-:--:--�[0m
�[2KWorking... �[91m━━�[0m�[90m╺�[0m�[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━�[0m �[35m  6%�[0m �[36m-:--:--�[0m
�[2KWorking... �[91m━━━━━━━━�[0m�[91m╸�[0m�[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━�[0m �[35m 22%�[0m �[36m0:00:01�[0m
�[2KWorking... �[91m━━━━━━━━━━━━━�[0m�[90m╺�[0m�[90m━━━━━━━━━━━━━━━━━━━━━━━━━━�[0m �[35m 33%�[0m �[36m0:00:01�[0m
�[2KWorking... �[91m━━━━━━━━━━━━━━�[0m�[90m╺�[0m�[90m━━━━━━━━━━━━━━━━━━━━━━━━━�[0m �[35m 35%�[0m �[36m0:00:01�[0m
�[2KWorking... �[91m━━━━━━━━━━━━━━━━━━━�[0m�[90m╺�[0m�[90m━━━━━━━━━━━━━━━━━━━━�[0m �[35m 48%�[0m �[36m0:00:01�[0m
�[2KWorking... �[91m━━━━━━━━━━━━━━━━━━━━━━━━�[0m�[90m╺�[0m�[90m━━━━━━━━━━━━━━━�[0m �[35m 61%�[0m �[36m0:00:01�[0m
�[2KWorking... �[91m━━━━━━━━━━━━━━━━━━━━━━━━━━━━�[0m�[90m╺�[0m�[90m━━━━━━━━━━━�[0m �[35m 70%�[0m �[36m0:00:01�[0m
�[2KWorking... �[91m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━�[0m�[90m╺�[0m�[90m━━━━━━━━�[0m �[35m 78%�[0m �[36m0:00:01�[0m
�[2KWorking... �[91m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━�[0m�[90m╺�[0m�[90m━━━━━━�[0m �[35m 83%�[0m �[36m0:00:01�[0m
�[2KWorking... �[91m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━�[0m�[90m╺�[0m�[90m━━━�[0m �[35m 91%�[0m �[36m0:00:01�[0m
�[2KWorking... �[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━�[0m �[35m100%�[0m �[33m0:00:01�[0m
�[2KWorking... �[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━�[0m �[35m100%�[0m �[33m0:00:01�[0m
�[?25hRun started:2026-03-17 08:18:47.084733+00:00

Test results:
>> Issue: [B608:hardcoded_sql_expressions] Possible SQL injection vector through string-based query construction.
   Severity: Medium   Confidence: Low
   CWE: CWE-89 (https://cwe.mitre.org/data/definitions/89.html)
   More Info: https://bandit.readthedocs.io/en/1.9.4/plugins/b608_hardcoded_sql_expressions.html
   Location: versiontracker/advanced_cache.py:610:24
609	                # Use f-string for better readability
610	                msg = f"Failed to delete from cache {key}: {e}"
611	                raise CacheError(msg) from e

--------------------------------------------------
>> Issue: [B404:blacklist] Consider possible security implications associated with the subprocess module.
   Severity: Low   Confidence: High
   CWE: CWE-78 (https://cwe.mitre.org/data/definitions/78.html)
   More Info: https://bandit.readthedocs.io/en/1.9.4/blacklists/blacklist_imports.html#b404-import-subprocess
   Location: versiontracker/config.py:174:16
173	                cmd = f"{path} --version"
174	                import subprocess
175	

--------------------------------------------------
>> Issue: [B603:subprocess_without_shell_equals_true] subprocess call - check for execution of untrusted input.
   Severity: Low   Confidence: High
   CWE: CWE-78 (https://cwe.mitre.org/data/definitions/78.html)
   More Info: https://bandit.readthedocs.io/en/1.9.4/plugins/b603_subprocess_without_shell_equals_true.html
   Location: versiontracker/config.py:177:29
176	                try:
177	                    result = subprocess.run(cmd.split(), capture_output=True, timeout=2, check=False)
178	                    returncode = result.returncode

--------------------------------------------------
>> Issue: [B110:try_except_pass] Try, Except, Pass detected.
   Severity: Low   Confidence: High
   CWE: CWE-703 (https://cwe.mitre.org/data/definitions/703.html)
   More Info: https://bandit.readthedocs.io/en/1.9.4/plugins/b110_try_except_pass.html
   Location: versiontracker/experimental/analytics.py:645:16
644	                    self.peak_cpu = max(self.peak_cpu, cpu_percent)
645	                except Exception:
646	                    pass
647	                time.sleep(0.05)

--------------------------------------------------
>> Issue: [B101:assert_used] Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
   Severity: Low   Confidence: High
   CWE: CWE-703 (https://cwe.mitre.org/data/definitions/703.html)
   More Info: https://bandit.readthedocs.io/en/1.9.4/plugins/b101_assert_used.html
   Location: versiontracker/handlers/outdated_handlers.py:494:8
493	        # Type assertion: apps and brews cannot be None here due to exit_code checks above
494	        assert apps is not None
495	        assert brews is not None

--------------------------------------------------
>> Issue: [B101:assert_used] Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
   Severity: Low   Confidence: High
   CWE: CWE-703 (https://cwe.mitre.org/data/definitions/703.html)
   More Info: https://bandit.readthedocs.io/en/1.9.4/plugins/b101_assert_used.html
   Location: versiontracker/handlers/outdated_handlers.py:495:8
494	        assert apps is not None
495	        assert brews is not None
496	        apps = _filter_applications(apps, brews, include_brews)

--------------------------------------------------
>> Issue: [B101:assert_used] Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
   Severity: Low   Confidence: High
   CWE: CWE-703 (https://cwe.mitre.org/data/definitions/703.html)
   More Info: https://bandit.readthedocs.io/en/1.9.4/plugins/b101_assert_used.html
   Location: versiontracker/handlers/outdated_handlers.py:512:8
511	        # Type assertion: outdated_info cannot be None here due to exit_code check
512	        assert outdated_info is not None
513	        # Type cast: outdated_info cannot be None here due to exit_code check above

--------------------------------------------------
>> Issue: [B404:blacklist] Consider possible security implications associated with the subprocess module.
   Severity: Low   Confidence: High
   CWE: CWE-78 (https://cwe.mitre.org/data/definitions/78.html)
   More Info: https://bandit.readthedocs.io/en/1.9.4/blacklists/blacklist_imports.html#b404-import-subprocess
   Location: versiontracker/homebrew.py:12:0
11	import re
12	import subprocess
13	import time

--------------------------------------------------
>> Issue: [B404:blacklist] Consider possible security implications associated with the subprocess module.
   Severity: Low   Confidence: High
   CWE: CWE-78 (https://cwe.mitre.org/data/definitions/78.html)
   More Info: https://bandit.readthedocs.io/en/1.9.4/blacklists/blacklist_imports.html#b404-import-subprocess
   Location: versiontracker/macos_integration.py:11:0
10	import os
11	import subprocess
12	from pathlib import Path

--------------------------------------------------
>> Issue: [B603:subprocess_without_shell_equals_true] subprocess call - check for execution of untrusted input.
   Severity: Low   Confidence: High
   CWE: CWE-78 (https://cwe.mitre.org/data/definitions/78.html)
   More Info: https://bandit.readthedocs.io/en/1.9.4/plugins/b603_subprocess_without_shell_equals_true.html
   Location: versiontracker/macos_integration.py:245:21
244	            # nosec B603 - osascript with controlled arguments
245	            result = subprocess.run(cmd, capture_output=True, text=True)
246	

--------------------------------------------------
>> Issue: [B404:blacklist] Consider possible security implications associated with the subprocess module.
   Severity: Low   Confidence: High
   CWE: CWE-78 (https://cwe.mitre.org/data/definitions/78.html)
   More Info: https://bandit.readthedocs.io/en/1.9.4/blacklists/blacklist_imports.html#b404-import-subprocess
   Location: versiontracker/menubar_app.py:8:0
7	import logging
8	import subprocess
9	import sys

--------------------------------------------------
>> Issue: [B404:blacklist] Consider possible security implications associated with the subprocess module.
   Severity: Low   Confidence: High
   CWE: CWE-78 (https://cwe.mitre.org/data/definitions/78.html)
   More Info: https://bandit.readthedocs.io/en/1.9.4/blacklists/blacklist_imports.html#b404-import-subprocess
   Location: versiontracker/plugins/example_plugins.py:322:16
321	            try:
322	                import subprocess
323	

--------------------------------------------------
>> Issue: [B607:start_process_with_partial_path] Starting a process with a partial executable path
   Severity: Low   Confidence: High
   CWE: CWE-78 (https://cwe.mitre.org/data/definitions/78.html)
   More Info: https://bandit.readthedocs.io/en/1.9.4/plugins/b607_start_process_with_partial_path.html
   Location: versiontracker/plugins/example_plugins.py:324:25
323	
324	                result = subprocess.run(["brew", "--version"], capture_output=True, text=True, timeout=5)
325	                if result.returncode == 0:

--------------------------------------------------
>> Issue: [B603:subprocess_without_shell_equals_true] subprocess call - check for execution of untrusted input.
   Severity: Low   Confidence: High
   CWE: CWE-78 (https://cwe.mitre.org/data/definitions/78.html)
   More Info: https://bandit.readthedocs.io/en/1.9.4/plugins/b603_subprocess_without_shell_equals_true.html
   Location: versiontracker/plugins/example_plugins.py:324:25
323	
324	                result = subprocess.run(["brew", "--version"], capture_output=True, text=True, timeout=5)
325	                if result.returncode == 0:

--------------------------------------------------
>> Issue: [B404:blacklist] Consider possible security implications associated with the subprocess module.
   Severity: Low   Confidence: High
   CWE: CWE-78 (https://cwe.mitre.org/data/definitions/78.html)
   More Info: https://bandit.readthedocs.io/en/1.9.4/blacklists/blacklist_imports.html#b404-import-subprocess
   Location: versiontracker/utils.py:15:0
14	import shutil
15	import subprocess
16	import sys

--------------------------------------------------
>> Issue: [B607:start_process_with_partial_path] Starting a process with a partial executable path
   Severity: Low   Confidence: High
   CWE: CWE-78 (https://cwe.mitre.org/data/definitions/78.html)
   More Info: https://bandit.readthedocs.io/en/1.9.4/plugins/b607_start_process_with_partial_path.html
   Location: versiontracker/utils.py:784:17
783	    try:
784	        result = subprocess.run(["which", "brew"], capture_output=True, text=True, timeout=5)
785	        return result.returncode == 0

--------------------------------------------------
>> Issue: [B603:subprocess_without_shell_equals_true] subprocess call - check for execution of untrusted input.
   Severity: Low   Confidence: High
   CWE: CWE-78 (https://cwe.mitre.org/data/definitions/78.html)
   More Info: https://bandit.readthedocs.io/en/1.9.4/plugins/b603_subprocess_without_shell_equals_true.html
   Location: versiontracker/utils.py:784:17
783	    try:
784	        result = subprocess.run(["which", "brew"], capture_output=True, text=True, timeout=5)
785	        return result.returncode == 0

--------------------------------------------------
>> Issue: [B607:start_process_with_partial_path] Starting a process with a partial executable path
   Severity: Low   Confidence: High
   CWE: CWE-78 (https://cwe.mitre.org/data/definitions/78.html)
   More Info: https://bandit.readthedocs.io/en/1.9.4/plugins/b607_start_process_with_partial_path.html
   Location: versiontracker/utils.py:800:17
799	    try:
800	        result = subprocess.run(["brew", "--prefix"], capture_output=True, text=True, timeout=5)
801	        if result.returncode == 0:

--------------------------------------------------
>> Issue: [B603:subprocess_without_shell_equals_true] subprocess call - check for execution of untrusted input.
   Severity: Low   Confidence: High
   CWE: CWE-78 (https://cwe.mitre.org/data/definitions/78.html)
   More Info: https://bandit.readthedocs.io/en/1.9.4/plugins/b603_subprocess_without_shell_equals_true.html
   Location: versiontracker/utils.py:800:17
799	    try:
800	        result = subprocess.run(["brew", "--prefix"], capture_output=True, text=True, timeout=5)
801	        if result.returncode == 0:

--------------------------------------------------
>> Issue: [B603:subprocess_without_shell_equals_true] subprocess call - check for execution of untrusted input.
   Severity: Low   Confidence: High
   CWE: CWE-78 (https://cwe.mitre.org/data/definitions/78.html)
   More Info: https://bandit.readthedocs.io/en/1.9.4/plugins/b603_subprocess_without_shell_equals_true.html
   Location: versiontracker/utils.py:833:15
832	    try:
833	        return subprocess.run(command, capture_output=True, text=True, timeout=timeout, check=check)
834	    except subprocess.TimeoutExpired as e:

--------------------------------------------------
>> Issue: [B404:blacklist] Consider possible security implications associated with the subprocess module.
   Severity: Low   Confidence: High
   CWE: CWE-78 (https://cwe.mitre.org/data/definitions/78.html)
   More Info: https://bandit.readthedocs.io/en/1.9.4/blacklists/blacklist_imports.html#b404-import-subprocess
   Location: versiontracker/version/batch.py:11:0
10	import logging
11	import subprocess
12	from concurrent.futures import ThreadPoolExecutor

--------------------------------------------------
>> Issue: [B404:blacklist] Consider possible security implications associated with the subprocess module.
   Severity: Low   Confidence: High
   CWE: CWE-78 (https://cwe.mitre.org/data/definitions/78.html)
   More Info: https://bandit.readthedocs.io/en/1.9.4/blacklists/blacklist_imports.html#b404-import-subprocess
   Location: versiontracker/version/homebrew.py:18:0
17	import logging
18	import subprocess
19	

--------------------------------------------------

Code scanned:
	Total lines of code: 15008
	Total lines skipped (#nosec): 0
	Total potential issues skipped due to specifically being disabled (e.g., #nosec BXXX): 33

Run metrics:
	Total issues (by severity):
		Undefined: 0
		Low: 21
		Medium: 1
		High: 0
	Total issues (by confidence):
		Undefined: 0
		Low: 1
		Medium: 0
		High: 21
Files skipped (0):

Safety Check Results



�[33m�[1m+===========================================================================================================================================================================================+�[0m


�[31m�[1mDEPRECATED: �[0m�[33m�[1mthis command (`check`) has been DEPRECATED, and will be unsupported beyond 01 June 2024.�[0m


�[32mWe highly encourage switching to the new �[0m�[32m�[1m`scan`�[0m�[32m command which is easier to use, more powerful, and can be set up to mimic the deprecated command if required.�[0m


�[33m�[1m+===========================================================================================================================================================================================+�[0m


+==============================================================================+

                               /$$$$$$            /$$
                              /$$__  $$          | $$
           /$$$$$$$  /$$$$$$ | $$  \__//$$$$$$  /$$$$$$   /$$   /$$
          /$$_____/ |____  $$| $$$$   /$$__  $$|_  $$_/  | $$  | $$
         |  $$$$$$   /$$$$$$$| $$_/  | $$$$$$$$  | $$    | $$  | $$
          \____  $$ /$$__  $$| $$    | $$_____/  | $$ /$$| $$  | $$
          /$$$$$$$/|  $$$$$$$| $$    |  $$$$$$$  |  $$$$/|  $$$$$$$
         |_______/  \_______/|__/     \_______/   \___/   \____  $$
                                                          /$$  | $$
                                                         |  $$$$$$/
  by safetycli.com                                        \______/

+==============================================================================+

 �[1mREPORT�[0m 

  Safety �[1mv3.7.0�[0m is scanning for �[1mVulnerabilities�[0m�[1m...�[0m
�[1m  Scanning dependencies�[0m in your �[1menvironment:�[0m

  -> /opt/hostedtoolcache/Python/3.13.12/x64/lib/python3.13/site-packages

  Using �[1mopen-source vulnerability database�[0m
�[1m  Found and scanned 106 packages�[0m
  Timestamp �[1m2026-03-17 08:18:48�[0m
�[1m  0�[0m�[1m vulnerabilities reported�[0m
�[1m  0�[0m�[1m vulnerabilities ignored�[0m
+==============================================================================+

 �[32m�[1mNo known security vulnerabilities reported.�[0m 

+==============================================================================+�[0m


�[33m�[1m+===========================================================================================================================================================================================+�[0m


�[31m�[1mDEPRECATED: �[0m�[33m�[1mthis command (`check`) has been DEPRECATED, and will be unsupported beyond 01 June 2024.�[0m


�[32mWe highly encourage switching to the new �[0m�[32m�[1m`scan`�[0m�[32m command which is easier to use, more powerful, and can be set up to mimic the deprecated command if required.�[0m


�[33m�[1m+===========================================================================================================================================================================================+�[0m


Pip-Audit Results

�[?25l�[32m-�[0m Collecting inputs
�[2K�[32m-�[0m Collecting inputs
�[2K�[32m-�[0m Collecting inputs
�[2K�[32m-�[0m Collecting inputs
�[2K�[32m\�[0m Collecting inputs
�[2K�[32m\�[0m Collecting inputs
�[2K�[32m\�[0m Collecting inputs
�[2K�[32m\�[0m Collecting aiohappyeyeballs (2.6.1)
�[2K�[32m\�[0m Collecting aiohappyeyeballs (2.6.1)
�[2K�[32m\�[0m Auditing aiohappyeyeballs (2.6.1)
�[2K�[32m\�[0m Collecting aiohttp (3.13.3)
�[2K�[32m\�[0m Auditing aiohttp (3.13.3)
�[2K�[32m\�[0m Collecting aiosignal (1.4.0)
�[2K�[32m\�[0m Auditing aiosignal (1.4.0)
�[2K�[32m\�[0m Collecting annotated-doc (0.0.4)
�[2K�[32m\�[0m Auditing annotated-doc (0.0.4)
�[2K�[32m\�[0m Collecting annotated-types (0.7.0)
�[2K�[32m\�[0m Auditing annotated-types (0.7.0)
�[2K�[32m\�[0m Collecting anyio (4.12.1)
�[2K�[32m\�[0m Auditing anyio (4.12.1)
�[2K�[32m\�[0m Collecting attrs (25.4.0)
�[2K�[32m\�[0m Auditing attrs (25.4.0)
�[2K�[32m\�[0m Collecting Authlib (1.6.9)
�[2K�[32m\�[0m Auditing Authlib (1.6.9)
�[2K�[32m\�[0m Collecting bandit (1.9.4)
�[2K�[32m\�[0m Auditing bandit (1.9.4)
�[2K�[32m\�[0m Collecting black (26.3.1)
�[2K�[32m\�[0m Auditing black (26.3.1)
�[2K�[32m\�[0m Collecting boolean.py (5.0)
�[2K�[32m\�[0m Auditing boolean.py (5.0)
�[2K�[32m\�[0m Collecting build (1.4.0)
�[2K�[32m\�[0m Auditing build (1.4.0)
�[2K�[32m\�[0m Collecting CacheControl (0.14.4)
�[2K�[32m\�[0m Auditing CacheControl (0.14.4)
�[2K�[32m\�[0m Collecting certifi (2026.2.25)
�[2K�[32m\�[0m Auditing certifi (2026.2.25)
�[2K�[32m\�[0m Collecting cffi (2.0.0)
�[2K�[32m\�[0m Auditing cffi (2.0.0)
�[2K�[32m\�[0m Collecting charset-normalizer (3.4.6)
�[2K�[32m\�[0m Auditing charset-normalizer (3.4.6)
�[2K�[32m|�[0m Collecting click (8.3.1)
�[2K�[32m|�[0m Auditing click (8.3.1)
�[2K�[32m|�[0m Collecting coverage (7.13.4)
�[2K�[32m|�[0m Auditing coverage (7.13.4)
�[2K�[32m|�[0m Collecting cryptography (46.0.5)
�[2K�[32m|�[0m Auditing cryptography (46.0.5)
�[2K�[32m|�[0m Collecting cyclonedx-python-lib (11.6.0)
�[2K�[32m|�[0m Auditing cyclonedx-python-lib (11.6.0)
�[2K�[32m|�[0m Collecting defusedxml (0.7.1)
�[2K�[32m|�[0m Auditing defusedxml (0.7.1)
�[2K�[32m|�[0m Collecting docutils (0.22.4)
�[2K�[32m|�[0m Auditing docutils (0.22.4)
�[2K�[32m|�[0m Collecting dparse (0.6.4)
�[2K�[32m|�[0m Auditing dparse (0.6.4)
�[2K�[32m|�[0m Collecting filelock (3.25.2)
�[2K�[32m|�[0m Auditing filelock (3.25.2)
�[2K�[32m|�[0m Collecting frozenlist (1.8.0)
�[2K�[32m|�[0m Collecting frozenlist (1.8.0)
�[2K�[32m|�[0m Auditing frozenlist (1.8.0)
�[2K�[32m|�[0m Collecting h11 (0.16.0)
�[2K�[32m|�[0m Auditing h11 (0.16.0)
�[2K�[32m|�[0m Collecting httpcore (1.0.9)
�[2K�[32m|�[0m Auditing httpcore (1.0.9)
�[2K�[32m|�[0m Collecting httpx (0.28.1)
�[2K�[32m|�[0m Auditing httpx (0.28.1)
�[2K�[32m|�[0m Collecting id (1.6.1)
�[2K�[32m|�[0m Auditing id (1.6.1)
�[2K�[32m|�[0m Collecting idna (3.11)
�[2K�[32m|�[0m Auditing idna (3.11)
�[2K�[32m|�[0m Collecting iniconfig (2.3.0)
�[2K�[32m|�[0m Auditing iniconfig (2.3.0)
�[2K�[32m|�[0m Collecting jaraco.classes (3.4.0)
�[2K�[32m|�[0m Auditing jaraco.classes (3.4.0)
�[2K�[32m|�[0m Collecting jaraco.context (6.1.1)
�[2K�[32m|�[0m Auditing jaraco.context (6.1.1)
�[2K�[32m|�[0m Collecting jaraco.functools (4.4.0)
�[2K�[32m|�[0m Auditing jaraco.functools (4.4.0)
�[2K�[32m|�[0m Collecting jeepney (0.9.0)
�[2K�[32m|�[0m Auditing jeepney (0.9.0)
�[2K�[32m|�[0m Collecting Jinja2 (3.1.6)
�[2K�[32m|�[0m Auditing Jinja2 (3.1.6)
�[2K�[32m|�[0m Collecting joblib (1.5.3)
�[2K�[32m|�[0m Auditing joblib (1.5.3)
�[2K�[32m|�[0m Collecting keyring (25.7.0)
�[2K�[32m|�[0m Auditing keyring (25.7.0)
�[2K�[32m|�[0m Collecting librt (0.8.1)
�[2K�[32m|�[0m Auditing librt (0.8.1)
�[2K�[32m|�[0m Collecting license-expression (30.4.4)
�[2K�[32m|�[0m Auditing license-expression (30.4.4)
�[2K�[32m|�[0m Collecting macversiontracker (0.9.0)
�[2K�[32m|�[0m Auditing macversiontracker (0.9.0)
�[2K�[32m|�[0m Collecting markdown-it-py (4.0.0)
�[2K�[32m|�[0m Auditing markdown-it-py (4.0.0)
�[2K�[32m|�[0m Collecting MarkupSafe (3.0.3)
�[2K�[32m|�[0m Auditing MarkupSafe (3.0.3)
�[2K�[32m|�[0m Collecting marshmallow (4.2.2)
�[2K�[32m|�[0m Auditing marshmallow (4.2.2)
�[2K�[32m|�[0m Collecting mdurl (0.1.2)
�[2K�[32m|�[0m Auditing mdurl (0.1.2)
�[2K�[32m|�[0m Collecting more-itertools (10.8.0)
�[2K�[32m|�[0m Auditing more-itertools (10.8.0)
�[2K�[32m|�[0m Collecting msgpack (1.1.2)
�[2K�[32m|�[0m Auditing msgpack (1.1.2)
�[2K�[32m|�[0m Collecting multidict (6.7.1)
�[2K�[32m|�[0m Auditing multidict (6.7.1)
�[2K�[32m|�[0m Collecting mypy (1.19.1)
�[2K�[32m|�[0m Auditing mypy (1.19.1)
�[2K�[32m|�[0m Collecting mypy_extensions (1.1.0)
�[2K�[32m|�[0m Collecting mypy_extensions (1.1.0)
�[2K�[32m|�[0m Auditing mypy_extensions (1.1.0)
�[2K�[32m|�[0m Collecting nh3 (0.3.3)
�[2K�[32m|�[0m Auditing nh3 (0.3.3)
�[2K�[32m|�[0m Collecting nltk (3.9.3)
�[2K�[32m|�[0m Auditing nltk (3.9.3)
�[2K�[32m|�[0m Collecting packageurl-python (0.17.6)
�[2K�[32m|�[0m Auditing packageurl-python (0.17.6)
�[2K�[32m|�[0m Collecting packaging (26.0)
�[2K�[32m|�[0m Auditing packaging (26.0)
�[2K�[32m|�[0m Collecting pathspec (1.0.4)
�[2K�[32m|�[0m Auditing pathspec (1.0.4)
�[2K�[32m|�[0m Collecting pip (26.0.1)
�[2K�[32m|�[0m Auditing pip (26.0.1)
�[2K�[32m|�[0m Collecting pip-api (0.0.34)
�[2K�[32m|�[0m Auditing pip-api (0.0.34)
�[2K�[32m|�[0m Collecting pip_audit (2.10.0)
�[2K�[32m|�[0m Auditing pip_audit (2.10.0)
�[2K�[32m|�[0m Collecting pip-requirements-parser (32.0.1)
�[2K�[32m|�[0m Auditing pip-requirements-parser (32.0.1)
�[2K�[32m|�[0m Collecting platformdirs (4.9.4)
�[2K�[32m|�[0m Auditing platformdirs (4.9.4)
�[2K�[32m|�[0m Collecting pluggy (1.6.0)
�[2K�[32m|�[0m Auditing pluggy (1.6.0)
�[2K�[32m|�[0m Collecting propcache (0.4.1)
�[2K�[32m|�[0m Auditing propcache (0.4.1)
�[2K�[32m|�[0m Collecting psutil (7.2.2)
�[2K�[32m|�[0m Auditing psutil (7.2.2)
�[2K�[32m|�[0m Collecting py-serializable (2.1.0)
�[2K�[32m|�[0m Auditing py-serializable (2.1.0)
�[2K�[32m|�[0m Collecting pycparser (3.0)
�[2K�[32m|�[0m Auditing pycparser (3.0)
�[2K�[32m|�[0m Collecting pydantic (2.12.5)
�[2K�[32m|�[0m Auditing pydantic (2.12.5)
�[2K�[32m|�[0m Collecting pydantic_core (2.41.5)
�[2K�[32m|�[0m Auditing pydantic_core (2.41.5)
�[2K�[32m|�[0m Collecting Pygments (2.19.2)
�[2K�[32m|�[0m Auditing Pygments (2.19.2)
�[2K�[32m|�[0m Collecting pyparsing (3.3.2)
�[2K�[32m|�[0m Auditing pyparsing (3.3.2)
�[2K�[32m|�[0m Collecting pyproject_hooks (1.2.0)
�[2K�[32m|�[0m Auditing pyproject_hooks (1.2.0)
�[2K�[32m|�[0m Collecting pytest (9.0.2)
�[2K�[32m|�[0m Auditing pytest (9.0.2)
�[2K�[32m|�[0m Collecting pytest-asyncio (1.3.0)
�[2K�[32m|�[0m Auditing pytest-asyncio (1.3.0)
�[2K�[32m|�[0m Collecting pytest-cov (7.0.0)
�[2K�[32m|�[0m Auditing pytest-cov (7.0.0)
�[2K�[32m|�[0m Collecting pytest-mock (3.15.1)
�[2K�[32m|�[0m Auditing pytest-mock (3.15.1)
�[2K�[32m|�[0m Collecting pytest-timeout (2.4.0)
�[2K�[32m|�[0m Auditing pytest-timeout (2.4.0)
�[2K�[32m|�[0m Collecting pytokens (0.4.1)
�[2K�[32m|�[0m Collecting pytokens (0.4.1)
�[2K�[32m|�[0m Auditing pytokens (0.4.1)
�[2K�[32m|�[0m Collecting PyYAML (6.0.3)
�[2K�[32m|�[0m Auditing PyYAML (6.0.3)
�[2K�[32m|�[0m Collecting readme_renderer (44.0)
�[2K�[32m|�[0m Auditing readme_renderer (44.0)
�[2K�[32m|�[0m Collecting regex (2026.2.28)
�[2K�[32m|�[0m Auditing regex (2026.2.28)
�[2K�[32m|�[0m Collecting requests (2.32.5)
�[2K�[32m|�[0m Auditing requests (2.32.5)
�[2K�[32m|�[0m Collecting requests-toolbelt (1.0.0)
�[2K�[32m|�[0m Auditing requests-toolbelt (1.0.0)
�[2K�[32m|�[0m Collecting rfc3986 (2.0.0)
�[2K�[32m|�[0m Auditing rfc3986 (2.0.0)
�[2K�[32m|�[0m Collecting rich (14.3.3)
�[2K�[32m|�[0m Auditing rich (14.3.3)
�[2K�[32m|�[0m Collecting ruamel.yaml (0.19.1)
�[2K�[32m|�[0m Auditing ruamel.yaml (0.19.1)
�[2K�[32m|�[0m Collecting ruff (0.15.6)
�[2K�[32m|�[0m Auditing ruff (0.15.6)
�[2K�[32m|�[0m Collecting safety (3.7.0)
�[2K�[32m|�[0m Auditing safety (3.7.0)
�[2K�[32m|�[0m Collecting safety-schemas (0.0.16)
�[2K�[32m|�[0m Auditing safety-schemas (0.0.16)
�[2K�[32m|�[0m Collecting SecretStorage (3.5.0)
�[2K�[32m|�[0m Auditing SecretStorage (3.5.0)
�[2K�[32m|�[0m Collecting setuptools (82.0.1)
�[2K�[32m|�[0m Auditing setuptools (82.0.1)
�[2K�[32m|�[0m Collecting shellingham (1.5.4)
�[2K�[32m|�[0m Auditing shellingham (1.5.4)
�[2K�[32m|�[0m Collecting sortedcontainers (2.4.0)
�[2K�[32m|�[0m Auditing sortedcontainers (2.4.0)
�[2K�[32m|�[0m Collecting stevedore (5.7.0)
�[2K�[32m|�[0m Auditing stevedore (5.7.0)
�[2K�[32m|�[0m Collecting tabulate (0.10.0)
�[2K�[32m|�[0m Auditing tabulate (0.10.0)
�[2K�[32m|�[0m Collecting tenacity (9.1.4)
�[2K�[32m|�[0m Auditing tenacity (9.1.4)
�[2K�[32m|�[0m Collecting tomli (2.4.0)
�[2K�[32m|�[0m Auditing tomli (2.4.0)
�[2K�[32m|�[0m Collecting tomli_w (1.2.0)
�[2K�[32m|�[0m Auditing tomli_w (1.2.0)
�[2K�[32m|�[0m Collecting tomlkit (0.14.0)
�[2K�[32m|�[0m Auditing tomlkit (0.14.0)
�[2K�[32m|�[0m Collecting tqdm (4.67.3)
�[2K�[32m|�[0m Auditing tqdm (4.67.3)
�[2K�[32m|�[0m Collecting twine (6.2.0)
�[2K�[32m|�[0m Auditing twine (6.2.0)
�[2K�[32m|�[0m Collecting typer (0.24.1)
�[2K�[32m|�[0m Auditing typer (0.24.1)
�[2K�[32m|�[0m Collecting types-PyYAML (6.0.12.20250915)
�[2K�[32m|�[0m Auditing types-PyYAML (6.0.12.20250915)
�[2K�[32m|�[0m Collecting typing_extensions (4.15.0)
�[2K�[32m|�[0m Collecting typing_extensions (4.15.0)
�[2K�[32m|�[0m Auditing typing_extensions (4.15.0)
�[2K�[32m|�[0m Collecting typing-inspection (0.4.2)
�[2K�[32m|�[0m Auditing typing-inspection (0.4.2)
�[2K�[32m|�[0m Collecting urllib3 (2.6.3)
�[2K�[32m|�[0m Auditing urllib3 (2.6.3)
�[2K�[32m|�[0m Collecting wheel (0.46.3)
�[2K�[32m|�[0m Auditing wheel (0.46.3)
�[2K�[32m|�[0m Collecting yarl (1.23.0)
�[2K�[32m|�[0m Auditing yarl (1.23.0)
�[2K�[32m|�[0m Auditing yarl (1.23.0)
�[?25h
�[1A�[2K```

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 2 issues, and left some high level feedback:

  • In compare_fuzzy, the condition now only checks USE_RAPIDFUZZ or USE_FUZZYWUZZY and then unconditionally calls _fuzz.ratio(...); consider mirroring the defensive pattern used in similarity_score (e.g. checking _fuzz and hasattr(_fuzz, 'ratio')) so that tests or callers that patch _fuzz to None don’t hit an AttributeError.
  • In enhanced_matching.py you added SequenceMatcher usage in calculate_similarity but did not add the corresponding from difflib import SequenceMatcher import in that module, which will raise a NameError at runtime.
  • The new gh pr checks loop in dependabot-auto-merge.yml assumes the GitHub CLI is available; you may want to add an explicit installation/setup step or a guard that fails with a clear message if gh is missing, to avoid silent CI breakage in environments where it isn’t preinstalled.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `compare_fuzzy`, the condition now only checks `USE_RAPIDFUZZ or USE_FUZZYWUZZY` and then unconditionally calls `_fuzz.ratio(...)`; consider mirroring the defensive pattern used in `similarity_score` (e.g. checking `_fuzz` and `hasattr(_fuzz, 'ratio')`) so that tests or callers that patch `_fuzz` to `None` don’t hit an `AttributeError`.
- In `enhanced_matching.py` you added `SequenceMatcher` usage in `calculate_similarity` but did not add the corresponding `from difflib import SequenceMatcher` import in that module, which will raise a `NameError` at runtime.
- The new `gh pr checks` loop in `dependabot-auto-merge.yml` assumes the GitHub CLI is available; you may want to add an explicit installation/setup step or a guard that fails with a clear message if `gh` is missing, to avoid silent CI breakage in environments where it isn’t preinstalled.

## Individual Comments

### Comment 1
<location path=".github/workflows/dependabot-auto-merge.yml" line_range="63-68" />
<code_context>
-          ref: ${{ github.event.pull_request.head.sha }}
-          timeoutSeconds: 900
-          intervalSeconds: 30
+      - name: Wait for CI and security checks
+        run: |
+          echo "Waiting for required checks to pass..."
+          # Use gh pr checks which respects branch protection required checks
+          for i in $(seq 1 60); do
+            STATUS=$(gh pr checks "$PR_URL" 2>&1 || true)
+            if echo "$STATUS" | grep -q "fail"; then
+              echo "Some checks failed:"
</code_context>
<issue_to_address>
**issue (bug_risk):** The `gh pr checks` output is treated as success for any case that doesn't contain the strings `fail` or `pending`, which can hide CLI/auth/config errors.

Because `gh pr checks "$PR_URL"` is wrapped in `|| true`, any CLI/auth/config error still yields a `STATUS` string. If that output doesn’t contain `fail` or `pending`, the loop will treat it as success and exit, so a broken `gh` setup or transient API issue could allow merges without truly validating checks.

It would be safer to treat `gh` failures and unexpected/empty output as failures—e.g., check the `gh` exit code explicitly, or use a known-success pattern (such as `gh pr checks --json` and checking status fields) and only consider recognized successful states as passing.
</issue_to_address>

### Comment 2
<location path=".github/workflows/advanced-ci.yml" line_range="161-164" />
<code_context>
+            echo '{"vulnerabilities":[]}' > safety-report.json
+          fi

       - name: Run pip-audit
         run: |
-          pip-audit --desc --format json --output pip-audit-report.json || \
-          (pip-audit --desc && echo '{"vulnerabilities":[]}' > pip-audit-report.json)
+          pip-audit --desc --format json --output pip-audit-report.json || true
+          pip-audit --desc || true

       - name: Upload security reports
</code_context>
<issue_to_address>
**issue (bug_risk):** `pip-audit-report.json` may not exist when upload-artifact runs, causing the step to fail.

Both `pip-audit` commands now allow failure (`|| true`), but there’s no longer a fallback to ensure `pip-audit-report.json` exists. If the JSON run fails before writing the file (e.g., `pip-audit` missing, network error), `pip-audit-report.json` won’t be created and `actions/upload-artifact` will fail. Please add a minimal fallback (e.g., `echo '{
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@codecov
Copy link

codecov bot commented Mar 17, 2026

Codecov Report

❌ Patch coverage is 92.85714% with 2 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
versiontracker/version/fuzzy.py 90.00% 0 Missing and 2 partials ⚠️

📢 Thoughts on this report? Let us know!

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 12f14913c5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

echo "Waiting for required checks to pass..."
# Use gh pr checks which respects branch protection required checks
for i in $(seq 1 60); do
STATUS=$(gh pr checks "$PR_URL" 2>&1 || true)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Stop swallowing gh pr checks errors in wait loop

The polling step masks every gh pr checks failure with || true, so transient API/auth errors are treated as normal output and can fall through to the else branch that prints “All checks passed!” and proceeds to gh pr merge --auto. In security-auto-merge, this means a failed status fetch can bypass the intended gate and enqueue auto-merge without actually verifying CI/security results.

Useful? React with 👍 / 👎.

- Fix test patches to also set USE_RAPIDFUZZ/USE_FUZZYWUZZY flags
  (tests failed on CI where rapidfuzz is installed)
- Remove duplicate security scanning from advanced-ci.yml
  (already handled by dedicated security.yml)
- Fix Final Validation to handle skipped integration-tests gracefully
- Fix coverage.yml retry threshold consistency (was 15%, now 50%)
- Unpin TruffleHog from patch version v3.93.8 to major tag v3
- Unpin dependabot/fetch-metadata from v2.5.0 to v2
- Replace fragile grep-based CI check polling with gh pr checks --watch
- Add artifact retention-days (14d for CI, 30d for releases/security)
- Scope workflow triggers to master branch to prevent duplicate runs
  across ci.yml, lint.yml, coverage.yml, security.yml, performance.yml

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@docdyhr docdyhr changed the title fix: Fuzzy matching fallback and CI improvements fix: Fuzzy matching fallback, CI/CD pipeline hardening and deduplication Mar 17, 2026
Workflow consolidation (10 → 8 workflows):
- Merge advanced-ci.yml into ci.yml as single unified CI workflow
  with setup, quality, test-matrix, build, integration, performance,
  error-handling, docs, plugin, and final-validation jobs
- Merge publish-pypi.yml into release.yml — single release pipeline
  with TestPyPI/PyPI publishing, sigstore signing, and GitHub Release
  attachment
- Delete advanced-ci.yml and publish-pypi.yml

Pipeline improvements:
- Add concurrency groups to all workflows (cancel stale PR runs)
- Use composite action (setup-python-deps) consistently across all jobs
- Add post-release smoke tests: install from PyPI on all Python/OS
  combinations, verify version, test CLI and core imports
- Add KeyboardInterrupt handling to test runner (from old ci.yml)
- Scope lint/coverage/security/performance triggers to master branch

README updates:
- Update test count badge (1,136 → 2,158)
- Add Release Pipeline workflow badge
- Add PyPI package badge

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@docdyhr docdyhr changed the title fix: Fuzzy matching fallback, CI/CD pipeline hardening and deduplication fix: Fuzzy matching fallback + CI/CD pipeline consolidation Mar 17, 2026
TruffleHog does not publish major version tags (v3), only full patch
tags (v3.93.8). Dependabot handles automatic version bumps.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-actions
Copy link

🔒 Security Analysis Report

Security Analysis Report

Generated: Tue Mar 17 10:15:04 UTC 2026
Repository: docdyhr/versiontracker
Commit: 758e00a

Bandit Security Scan

�[?25l
�[2KWorking... �[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━�[0m �[35m  0%�[0m �[36m-:--:--�[0m
�[2KWorking... �[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━�[0m �[35m  0%�[0m �[36m-:--:--�[0m
�[2KWorking... �[91m━━�[0m�[90m╺�[0m�[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━�[0m �[35m  6%�[0m �[36m-:--:--�[0m
�[2KWorking... �[91m━━━━━�[0m�[91m╸�[0m�[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━�[0m �[35m 15%�[0m �[36m0:00:02�[0m
�[2KWorking... �[91m━━━━━━━━�[0m�[91m╸�[0m�[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━�[0m �[35m 22%�[0m �[36m0:00:02�[0m
�[2KWorking... �[91m━━━━━━━━━━━━━�[0m�[90m╺�[0m�[90m━━━━━━━━━━━━━━━━━━━━━━━━━━�[0m �[35m 33%�[0m �[36m0:00:01�[0m
�[2KWorking... �[91m━━━━━━━━━━━━━━�[0m�[91m╸�[0m�[90m━━━━━━━━━━━━━━━━━━━━━━━━━�[0m �[35m 37%�[0m �[36m0:00:01�[0m
�[2KWorking... �[91m━━━━━━━━━━━━━━━━━━━�[0m�[90m╺�[0m�[90m━━━━━━━━━━━━━━━━━━━━�[0m �[35m 48%�[0m �[36m0:00:01�[0m
�[2KWorking... �[91m━━━━━━━━━━━━━━━━━━━━━━━━�[0m�[90m╺�[0m�[90m━━━━━━━━━━━━━━━�[0m �[35m 61%�[0m �[36m0:00:01�[0m
�[2KWorking... �[91m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━�[0m�[90m╺�[0m�[90m━━━━━━━━�[0m �[35m 78%�[0m �[36m0:00:01�[0m
�[2KWorking... �[91m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━�[0m�[90m╺�[0m�[90m━━━━━━�[0m �[35m 83%�[0m �[36m0:00:01�[0m
�[2KWorking... �[91m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━�[0m�[90m╺�[0m�[90m━━━�[0m �[35m 91%�[0m �[36m0:00:01�[0m
�[2KWorking... �[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━�[0m �[35m100%�[0m �[33m0:00:01�[0m
�[2KWorking... �[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━�[0m �[35m100%�[0m �[33m0:00:01�[0m
�[?25hRun started:2026-03-17 10:15:05.747543+00:00

Test results:
>> Issue: [B608:hardcoded_sql_expressions] Possible SQL injection vector through string-based query construction.
   Severity: Medium   Confidence: Low
   CWE: CWE-89 (https://cwe.mitre.org/data/definitions/89.html)
   More Info: https://bandit.readthedocs.io/en/1.9.4/plugins/b608_hardcoded_sql_expressions.html
   Location: versiontracker/advanced_cache.py:610:24
609	                # Use f-string for better readability
610	                msg = f"Failed to delete from cache {key}: {e}"
611	                raise CacheError(msg) from e

--------------------------------------------------
>> Issue: [B404:blacklist] Consider possible security implications associated with the subprocess module.
   Severity: Low   Confidence: High
   CWE: CWE-78 (https://cwe.mitre.org/data/definitions/78.html)
   More Info: https://bandit.readthedocs.io/en/1.9.4/blacklists/blacklist_imports.html#b404-import-subprocess
   Location: versiontracker/config.py:174:16
173	                cmd = f"{path} --version"
174	                import subprocess
175	

--------------------------------------------------
>> Issue: [B603:subprocess_without_shell_equals_true] subprocess call - check for execution of untrusted input.
   Severity: Low   Confidence: High
   CWE: CWE-78 (https://cwe.mitre.org/data/definitions/78.html)
   More Info: https://bandit.readthedocs.io/en/1.9.4/plugins/b603_subprocess_without_shell_equals_true.html
   Location: versiontracker/config.py:177:29
176	                try:
177	                    result = subprocess.run(cmd.split(), capture_output=True, timeout=2, check=False)
178	                    returncode = result.returncode

--------------------------------------------------
>> Issue: [B110:try_except_pass] Try, Except, Pass detected.
   Severity: Low   Confidence: High
   CWE: CWE-703 (https://cwe.mitre.org/data/definitions/703.html)
   More Info: https://bandit.readthedocs.io/en/1.9.4/plugins/b110_try_except_pass.html
   Location: versiontracker/experimental/analytics.py:645:16
644	                    self.peak_cpu = max(self.peak_cpu, cpu_percent)
645	                except Exception:
646	                    pass
647	                time.sleep(0.05)

--------------------------------------------------
>> Issue: [B101:assert_used] Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
   Severity: Low   Confidence: High
   CWE: CWE-703 (https://cwe.mitre.org/data/definitions/703.html)
   More Info: https://bandit.readthedocs.io/en/1.9.4/plugins/b101_assert_used.html
   Location: versiontracker/handlers/outdated_handlers.py:494:8
493	        # Type assertion: apps and brews cannot be None here due to exit_code checks above
494	        assert apps is not None
495	        assert brews is not None

--------------------------------------------------
>> Issue: [B101:assert_used] Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
   Severity: Low   Confidence: High
   CWE: CWE-703 (https://cwe.mitre.org/data/definitions/703.html)
   More Info: https://bandit.readthedocs.io/en/1.9.4/plugins/b101_assert_used.html
   Location: versiontracker/handlers/outdated_handlers.py:495:8
494	        assert apps is not None
495	        assert brews is not None
496	        apps = _filter_applications(apps, brews, include_brews)

--------------------------------------------------
>> Issue: [B101:assert_used] Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
   Severity: Low   Confidence: High
   CWE: CWE-703 (https://cwe.mitre.org/data/definitions/703.html)
   More Info: https://bandit.readthedocs.io/en/1.9.4/plugins/b101_assert_used.html
   Location: versiontracker/handlers/outdated_handlers.py:512:8
511	        # Type assertion: outdated_info cannot be None here due to exit_code check
512	        assert outdated_info is not None
513	        # Type cast: outdated_info cannot be None here due to exit_code check above

--------------------------------------------------
>> Issue: [B404:blacklist] Consider possible security implications associated with the subprocess module.
   Severity: Low   Confidence: High
   CWE: CWE-78 (https://cwe.mitre.org/data/definitions/78.html)
   More Info: https://bandit.readthedocs.io/en/1.9.4/blacklists/blacklist_imports.html#b404-import-subprocess
   Location: versiontracker/homebrew.py:12:0
11	import re
12	import subprocess
13	import time

--------------------------------------------------
>> Issue: [B404:blacklist] Consider possible security implications associated with the subprocess module.
   Severity: Low   Confidence: High
   CWE: CWE-78 (https://cwe.mitre.org/data/definitions/78.html)
   More Info: https://bandit.readthedocs.io/en/1.9.4/blacklists/blacklist_imports.html#b404-import-subprocess
   Location: versiontracker/macos_integration.py:11:0
10	import os
11	import subprocess
12	from pathlib import Path

--------------------------------------------------
>> Issue: [B603:subprocess_without_shell_equals_true] subprocess call - check for execution of untrusted input.
   Severity: Low   Confidence: High
   CWE: CWE-78 (https://cwe.mitre.org/data/definitions/78.html)
   More Info: https://bandit.readthedocs.io/en/1.9.4/plugins/b603_subprocess_without_shell_equals_true.html
   Location: versiontracker/macos_integration.py:245:21
244	            # nosec B603 - osascript with controlled arguments
245	            result = subprocess.run(cmd, capture_output=True, text=True)
246	

--------------------------------------------------
>> Issue: [B404:blacklist] Consider possible security implications associated with the subprocess module.
   Severity: Low   Confidence: High
   CWE: CWE-78 (https://cwe.mitre.org/data/definitions/78.html)
   More Info: https://bandit.readthedocs.io/en/1.9.4/blacklists/blacklist_imports.html#b404-import-subprocess
   Location: versiontracker/menubar_app.py:8:0
7	import logging
8	import subprocess
9	import sys

--------------------------------------------------
>> Issue: [B404:blacklist] Consider possible security implications associated with the subprocess module.
   Severity: Low   Confidence: High
   CWE: CWE-78 (https://cwe.mitre.org/data/definitions/78.html)
   More Info: https://bandit.readthedocs.io/en/1.9.4/blacklists/blacklist_imports.html#b404-import-subprocess
   Location: versiontracker/plugins/example_plugins.py:322:16
321	            try:
322	                import subprocess
323	

--------------------------------------------------
>> Issue: [B607:start_process_with_partial_path] Starting a process with a partial executable path
   Severity: Low   Confidence: High
   CWE: CWE-78 (https://cwe.mitre.org/data/definitions/78.html)
   More Info: https://bandit.readthedocs.io/en/1.9.4/plugins/b607_start_process_with_partial_path.html
   Location: versiontracker/plugins/example_plugins.py:324:25
323	
324	                result = subprocess.run(["brew", "--version"], capture_output=True, text=True, timeout=5)
325	                if result.returncode == 0:

--------------------------------------------------
>> Issue: [B603:subprocess_without_shell_equals_true] subprocess call - check for execution of untrusted input.
   Severity: Low   Confidence: High
   CWE: CWE-78 (https://cwe.mitre.org/data/definitions/78.html)
   More Info: https://bandit.readthedocs.io/en/1.9.4/plugins/b603_subprocess_without_shell_equals_true.html
   Location: versiontracker/plugins/example_plugins.py:324:25
323	
324	                result = subprocess.run(["brew", "--version"], capture_output=True, text=True, timeout=5)
325	                if result.returncode == 0:

--------------------------------------------------
>> Issue: [B404:blacklist] Consider possible security implications associated with the subprocess module.
   Severity: Low   Confidence: High
   CWE: CWE-78 (https://cwe.mitre.org/data/definitions/78.html)
   More Info: https://bandit.readthedocs.io/en/1.9.4/blacklists/blacklist_imports.html#b404-import-subprocess
   Location: versiontracker/utils.py:15:0
14	import shutil
15	import subprocess
16	import sys

--------------------------------------------------
>> Issue: [B607:start_process_with_partial_path] Starting a process with a partial executable path
   Severity: Low   Confidence: High
   CWE: CWE-78 (https://cwe.mitre.org/data/definitions/78.html)
   More Info: https://bandit.readthedocs.io/en/1.9.4/plugins/b607_start_process_with_partial_path.html
   Location: versiontracker/utils.py:784:17
783	    try:
784	        result = subprocess.run(["which", "brew"], capture_output=True, text=True, timeout=5)
785	        return result.returncode == 0

--------------------------------------------------
>> Issue: [B603:subprocess_without_shell_equals_true] subprocess call - check for execution of untrusted input.
   Severity: Low   Confidence: High
   CWE: CWE-78 (https://cwe.mitre.org/data/definitions/78.html)
   More Info: https://bandit.readthedocs.io/en/1.9.4/plugins/b603_subprocess_without_shell_equals_true.html
   Location: versiontracker/utils.py:784:17
783	    try:
784	        result = subprocess.run(["which", "brew"], capture_output=True, text=True, timeout=5)
785	        return result.returncode == 0

--------------------------------------------------
>> Issue: [B607:start_process_with_partial_path] Starting a process with a partial executable path
   Severity: Low   Confidence: High
   CWE: CWE-78 (https://cwe.mitre.org/data/definitions/78.html)
   More Info: https://bandit.readthedocs.io/en/1.9.4/plugins/b607_start_process_with_partial_path.html
   Location: versiontracker/utils.py:800:17
799	    try:
800	        result = subprocess.run(["brew", "--prefix"], capture_output=True, text=True, timeout=5)
801	        if result.returncode == 0:

--------------------------------------------------
>> Issue: [B603:subprocess_without_shell_equals_true] subprocess call - check for execution of untrusted input.
   Severity: Low   Confidence: High
   CWE: CWE-78 (https://cwe.mitre.org/data/definitions/78.html)
   More Info: https://bandit.readthedocs.io/en/1.9.4/plugins/b603_subprocess_without_shell_equals_true.html
   Location: versiontracker/utils.py:800:17
799	    try:
800	        result = subprocess.run(["brew", "--prefix"], capture_output=True, text=True, timeout=5)
801	        if result.returncode == 0:

--------------------------------------------------
>> Issue: [B603:subprocess_without_shell_equals_true] subprocess call - check for execution of untrusted input.
   Severity: Low   Confidence: High
   CWE: CWE-78 (https://cwe.mitre.org/data/definitions/78.html)
   More Info: https://bandit.readthedocs.io/en/1.9.4/plugins/b603_subprocess_without_shell_equals_true.html
   Location: versiontracker/utils.py:833:15
832	    try:
833	        return subprocess.run(command, capture_output=True, text=True, timeout=timeout, check=check)
834	    except subprocess.TimeoutExpired as e:

--------------------------------------------------
>> Issue: [B404:blacklist] Consider possible security implications associated with the subprocess module.
   Severity: Low   Confidence: High
   CWE: CWE-78 (https://cwe.mitre.org/data/definitions/78.html)
   More Info: https://bandit.readthedocs.io/en/1.9.4/blacklists/blacklist_imports.html#b404-import-subprocess
   Location: versiontracker/version/batch.py:11:0
10	import logging
11	import subprocess
12	from concurrent.futures import ThreadPoolExecutor

--------------------------------------------------
>> Issue: [B404:blacklist] Consider possible security implications associated with the subprocess module.
   Severity: Low   Confidence: High
   CWE: CWE-78 (https://cwe.mitre.org/data/definitions/78.html)
   More Info: https://bandit.readthedocs.io/en/1.9.4/blacklists/blacklist_imports.html#b404-import-subprocess
   Location: versiontracker/version/homebrew.py:18:0
17	import logging
18	import subprocess
19	

--------------------------------------------------

Code scanned:
	Total lines of code: 15008
	Total lines skipped (#nosec): 0
	Total potential issues skipped due to specifically being disabled (e.g., #nosec BXXX): 33

Run metrics:
	Total issues (by severity):
		Undefined: 0
		Low: 21
		Medium: 1
		High: 0
	Total issues (by confidence):
		Undefined: 0
		Low: 1
		Medium: 0
		High: 21
Files skipped (0):

Safety Check Results



�[33m�[1m+===========================================================================================================================================================================================+�[0m


�[31m�[1mDEPRECATED: �[0m�[33m�[1mthis command (`check`) has been DEPRECATED, and will be unsupported beyond 01 June 2024.�[0m


�[32mWe highly encourage switching to the new �[0m�[32m�[1m`scan`�[0m�[32m command which is easier to use, more powerful, and can be set up to mimic the deprecated command if required.�[0m


�[33m�[1m+===========================================================================================================================================================================================+�[0m


+==============================================================================+

                               /$$$$$$            /$$
                              /$$__  $$          | $$
           /$$$$$$$  /$$$$$$ | $$  \__//$$$$$$  /$$$$$$   /$$   /$$
          /$$_____/ |____  $$| $$$$   /$$__  $$|_  $$_/  | $$  | $$
         |  $$$$$$   /$$$$$$$| $$_/  | $$$$$$$$  | $$    | $$  | $$
          \____  $$ /$$__  $$| $$    | $$_____/  | $$ /$$| $$  | $$
          /$$$$$$$/|  $$$$$$$| $$    |  $$$$$$$  |  $$$$/|  $$$$$$$
         |_______/  \_______/|__/     \_______/   \___/   \____  $$
                                                          /$$  | $$
                                                         |  $$$$$$/
  by safetycli.com                                        \______/

+==============================================================================+

 �[1mREPORT�[0m 

  Safety �[1mv3.7.0�[0m is scanning for �[1mVulnerabilities�[0m�[1m...�[0m
�[1m  Scanning dependencies�[0m in your �[1menvironment:�[0m

  -> /opt/hostedtoolcache/Python/3.13.12/x64/lib/python3.13/site-packages

  Using �[1mopen-source vulnerability database�[0m
�[1m  Found and scanned 106 packages�[0m
  Timestamp �[1m2026-03-17 10:15:07�[0m
�[1m  0�[0m�[1m vulnerabilities reported�[0m
�[1m  0�[0m�[1m vulnerabilities ignored�[0m
+==============================================================================+

 �[32m�[1mNo known security vulnerabilities reported.�[0m 

+==============================================================================+�[0m


�[33m�[1m+===========================================================================================================================================================================================+�[0m


�[31m�[1mDEPRECATED: �[0m�[33m�[1mthis command (`check`) has been DEPRECATED, and will be unsupported beyond 01 June 2024.�[0m


�[32mWe highly encourage switching to the new �[0m�[32m�[1m`scan`�[0m�[32m command which is easier to use, more powerful, and can be set up to mimic the deprecated command if required.�[0m


�[33m�[1m+===========================================================================================================================================================================================+�[0m


Pip-Audit Results

�[?25l�[32m-�[0m Collecting inputs
�[2K�[32m-�[0m Collecting inputs
�[2K�[32m-�[0m Collecting inputs
�[2K�[32m-�[0m Collecting inputs
�[2K�[32m\�[0m Collecting inputs
�[2K�[32m\�[0m Collecting inputs
�[2K�[32m\�[0m Collecting inputs
�[2K�[32m\�[0m Collecting aiohappyeyeballs (2.6.1)
�[2K�[32m\�[0m Collecting aiohappyeyeballs (2.6.1)
�[2K�[32m\�[0m Auditing aiohappyeyeballs (2.6.1)
�[2K�[32m\�[0m Collecting aiohttp (3.13.3)
�[2K�[32m\�[0m Auditing aiohttp (3.13.3)
�[2K�[32m\�[0m Collecting aiosignal (1.4.0)
�[2K�[32m\�[0m Auditing aiosignal (1.4.0)
�[2K�[32m\�[0m Collecting annotated-doc (0.0.4)
�[2K�[32m\�[0m Auditing annotated-doc (0.0.4)
�[2K�[32m\�[0m Collecting annotated-types (0.7.0)
�[2K�[32m\�[0m Auditing annotated-types (0.7.0)
�[2K�[32m\�[0m Collecting anyio (4.12.1)
�[2K�[32m\�[0m Auditing anyio (4.12.1)
�[2K�[32m\�[0m Collecting attrs (25.4.0)
�[2K�[32m\�[0m Auditing attrs (25.4.0)
�[2K�[32m\�[0m Collecting Authlib (1.6.9)
�[2K�[32m\�[0m Auditing Authlib (1.6.9)
�[2K�[32m\�[0m Collecting bandit (1.9.4)
�[2K�[32m\�[0m Auditing bandit (1.9.4)
�[2K�[32m\�[0m Collecting black (26.3.1)
�[2K�[32m\�[0m Auditing black (26.3.1)
�[2K�[32m\�[0m Collecting boolean.py (5.0)
�[2K�[32m\�[0m Auditing boolean.py (5.0)
�[2K�[32m\�[0m Collecting build (1.4.0)
�[2K�[32m\�[0m Auditing build (1.4.0)
�[2K�[32m\�[0m Collecting CacheControl (0.14.4)
�[2K�[32m\�[0m Auditing CacheControl (0.14.4)
�[2K�[32m\�[0m Collecting certifi (2026.2.25)
�[2K�[32m\�[0m Auditing certifi (2026.2.25)
�[2K�[32m\�[0m Collecting cffi (2.0.0)
�[2K�[32m\�[0m Auditing cffi (2.0.0)
�[2K�[32m\�[0m Collecting charset-normalizer (3.4.6)
�[2K�[32m|�[0m Auditing charset-normalizer (3.4.6)
�[2K�[32m|�[0m Collecting click (8.3.1)
�[2K�[32m|�[0m Auditing click (8.3.1)
�[2K�[32m|�[0m Collecting coverage (7.13.4)
�[2K�[32m|�[0m Auditing coverage (7.13.4)
�[2K�[32m|�[0m Collecting cryptography (46.0.5)
�[2K�[32m|�[0m Auditing cryptography (46.0.5)
�[2K�[32m|�[0m Collecting cyclonedx-python-lib (11.6.0)
�[2K�[32m|�[0m Auditing cyclonedx-python-lib (11.6.0)
�[2K�[32m|�[0m Collecting defusedxml (0.7.1)
�[2K�[32m|�[0m Auditing defusedxml (0.7.1)
�[2K�[32m|�[0m Collecting docutils (0.22.4)
�[2K�[32m|�[0m Auditing docutils (0.22.4)
�[2K�[32m|�[0m Collecting dparse (0.6.4)
�[2K�[32m|�[0m Auditing dparse (0.6.4)
�[2K�[32m|�[0m Collecting filelock (3.25.2)
�[2K�[32m|�[0m Collecting filelock (3.25.2)
�[2K�[32m|�[0m Auditing filelock (3.25.2)
�[2K�[32m|�[0m Collecting frozenlist (1.8.0)
�[2K�[32m|�[0m Auditing frozenlist (1.8.0)
�[2K�[32m|�[0m Collecting h11 (0.16.0)
�[2K�[32m|�[0m Auditing h11 (0.16.0)
�[2K�[32m|�[0m Collecting httpcore (1.0.9)
�[2K�[32m|�[0m Auditing httpcore (1.0.9)
�[2K�[32m|�[0m Collecting httpx (0.28.1)
�[2K�[32m|�[0m Auditing httpx (0.28.1)
�[2K�[32m|�[0m Collecting id (1.6.1)
�[2K�[32m|�[0m Auditing id (1.6.1)
�[2K�[32m|�[0m Collecting idna (3.11)
�[2K�[32m|�[0m Auditing idna (3.11)
�[2K�[32m|�[0m Collecting iniconfig (2.3.0)
�[2K�[32m|�[0m Auditing iniconfig (2.3.0)
�[2K�[32m|�[0m Collecting jaraco.classes (3.4.0)
�[2K�[32m|�[0m Auditing jaraco.classes (3.4.0)
�[2K�[32m|�[0m Collecting jaraco.context (6.1.1)
�[2K�[32m|�[0m Auditing jaraco.context (6.1.1)
�[2K�[32m|�[0m Collecting jaraco.functools (4.4.0)
�[2K�[32m|�[0m Auditing jaraco.functools (4.4.0)
�[2K�[32m|�[0m Collecting jeepney (0.9.0)
�[2K�[32m|�[0m Auditing jeepney (0.9.0)
�[2K�[32m|�[0m Collecting Jinja2 (3.1.6)
�[2K�[32m|�[0m Auditing Jinja2 (3.1.6)
�[2K�[32m|�[0m Collecting joblib (1.5.3)
�[2K�[32m|�[0m Auditing joblib (1.5.3)
�[2K�[32m|�[0m Collecting keyring (25.7.0)
�[2K�[32m|�[0m Auditing keyring (25.7.0)
�[2K�[32m|�[0m Collecting librt (0.8.1)
�[2K�[32m|�[0m Auditing librt (0.8.1)
�[2K�[32m|�[0m Collecting license-expression (30.4.4)
�[2K�[32m|�[0m Auditing license-expression (30.4.4)
�[2K�[32m|�[0m Collecting macversiontracker (0.9.0)
�[2K�[32m|�[0m Auditing macversiontracker (0.9.0)
�[2K�[32m|�[0m Collecting markdown-it-py (4.0.0)
�[2K�[32m|�[0m Auditing markdown-it-py (4.0.0)
�[2K�[32m|�[0m Collecting MarkupSafe (3.0.3)
�[2K�[32m|�[0m Auditing MarkupSafe (3.0.3)
�[2K�[32m|�[0m Collecting marshmallow (4.2.2)
�[2K�[32m|�[0m Auditing marshmallow (4.2.2)
�[2K�[32m|�[0m Collecting mdurl (0.1.2)
�[2K�[32m|�[0m Auditing mdurl (0.1.2)
�[2K�[32m|�[0m Collecting more-itertools (10.8.0)
�[2K�[32m|�[0m Auditing more-itertools (10.8.0)
�[2K�[32m|�[0m Collecting msgpack (1.1.2)
�[2K�[32m|�[0m Auditing msgpack (1.1.2)
�[2K�[32m|�[0m Collecting multidict (6.7.1)
�[2K�[32m|�[0m Auditing multidict (6.7.1)
�[2K�[32m|�[0m Collecting mypy (1.19.1)
�[2K�[32m|�[0m Collecting mypy (1.19.1)
�[2K�[32m|�[0m Auditing mypy (1.19.1)
�[2K�[32m|�[0m Collecting mypy_extensions (1.1.0)
�[2K�[32m|�[0m Auditing mypy_extensions (1.1.0)
�[2K�[32m|�[0m Collecting nh3 (0.3.3)
�[2K�[32m|�[0m Auditing nh3 (0.3.3)
�[2K�[32m|�[0m Collecting nltk (3.9.3)
�[2K�[32m|�[0m Auditing nltk (3.9.3)
�[2K�[32m|�[0m Collecting packageurl-python (0.17.6)
�[2K�[32m|�[0m Auditing packageurl-python (0.17.6)
�[2K�[32m|�[0m Collecting packaging (26.0)
�[2K�[32m|�[0m Auditing packaging (26.0)
�[2K�[32m|�[0m Collecting pathspec (1.0.4)
�[2K�[32m|�[0m Auditing pathspec (1.0.4)
�[2K�[32m|�[0m Collecting pip (26.0.1)
�[2K�[32m|�[0m Auditing pip (26.0.1)
�[2K�[32m|�[0m Collecting pip-api (0.0.34)
�[2K�[32m|�[0m Auditing pip-api (0.0.34)
�[2K�[32m|�[0m Collecting pip_audit (2.10.0)
�[2K�[32m|�[0m Auditing pip_audit (2.10.0)
�[2K�[32m|�[0m Collecting pip-requirements-parser (32.0.1)
�[2K�[32m|�[0m Auditing pip-requirements-parser (32.0.1)
�[2K�[32m|�[0m Collecting platformdirs (4.9.4)
�[2K�[32m|�[0m Auditing platformdirs (4.9.4)
�[2K�[32m|�[0m Collecting pluggy (1.6.0)
�[2K�[32m|�[0m Auditing pluggy (1.6.0)
�[2K�[32m|�[0m Collecting propcache (0.4.1)
�[2K�[32m|�[0m Auditing propcache (0.4.1)
�[2K�[32m|�[0m Collecting psutil (7.2.2)
�[2K�[32m|�[0m Auditing psutil (7.2.2)
�[2K�[32m|�[0m Collecting py-serializable (2.1.0)
�[2K�[32m|�[0m Auditing py-serializable (2.1.0)
�[2K�[32m|�[0m Collecting pycparser (3.0)
�[2K�[32m|�[0m Auditing pycparser (3.0)
�[2K�[32m|�[0m Collecting pydantic (2.12.5)
�[2K�[32m|�[0m Auditing pydantic (2.12.5)
�[2K�[32m|�[0m Collecting pydantic_core (2.41.5)
�[2K�[32m|�[0m Auditing pydantic_core (2.41.5)
�[2K�[32m|�[0m Collecting Pygments (2.19.2)
�[2K�[32m|�[0m Auditing Pygments (2.19.2)
�[2K�[32m|�[0m Collecting pyparsing (3.3.2)
�[2K�[32m|�[0m Auditing pyparsing (3.3.2)
�[2K�[32m|�[0m Collecting pyproject_hooks (1.2.0)
�[2K�[32m|�[0m Auditing pyproject_hooks (1.2.0)
�[2K�[32m|�[0m Collecting pytest (9.0.2)
�[2K�[32m|�[0m Auditing pytest (9.0.2)
�[2K�[32m|�[0m Collecting pytest-asyncio (1.3.0)
�[2K�[32m|�[0m Auditing pytest-asyncio (1.3.0)
�[2K�[32m|�[0m Collecting pytest-cov (7.0.0)
�[2K�[32m|�[0m Auditing pytest-cov (7.0.0)
�[2K�[32m|�[0m Collecting pytest-mock (3.15.1)
�[2K�[32m|�[0m Auditing pytest-mock (3.15.1)
�[2K�[32m|�[0m Collecting pytest-timeout (2.4.0)
�[2K�[32m|�[0m Collecting pytest-timeout (2.4.0)
�[2K�[32m|�[0m Auditing pytest-timeout (2.4.0)
�[2K�[32m|�[0m Collecting pytokens (0.4.1)
�[2K�[32m|�[0m Auditing pytokens (0.4.1)
�[2K�[32m|�[0m Collecting PyYAML (6.0.3)
�[2K�[32m|�[0m Auditing PyYAML (6.0.3)
�[2K�[32m|�[0m Collecting readme_renderer (44.0)
�[2K�[32m|�[0m Auditing readme_renderer (44.0)
�[2K�[32m|�[0m Collecting regex (2026.2.28)
�[2K�[32m|�[0m Auditing regex (2026.2.28)
�[2K�[32m|�[0m Collecting requests (2.32.5)
�[2K�[32m|�[0m Auditing requests (2.32.5)
�[2K�[32m|�[0m Collecting requests-toolbelt (1.0.0)
�[2K�[32m|�[0m Auditing requests-toolbelt (1.0.0)
�[2K�[32m|�[0m Collecting rfc3986 (2.0.0)
�[2K�[32m|�[0m Auditing rfc3986 (2.0.0)
�[2K�[32m|�[0m Collecting rich (14.3.3)
�[2K�[32m|�[0m Auditing rich (14.3.3)
�[2K�[32m|�[0m Collecting ruamel.yaml (0.19.1)
�[2K�[32m|�[0m Auditing ruamel.yaml (0.19.1)
�[2K�[32m|�[0m Collecting ruff (0.15.6)
�[2K�[32m|�[0m Auditing ruff (0.15.6)
�[2K�[32m|�[0m Collecting safety (3.7.0)
�[2K�[32m|�[0m Auditing safety (3.7.0)
�[2K�[32m|�[0m Collecting safety-schemas (0.0.16)
�[2K�[32m|�[0m Auditing safety-schemas (0.0.16)
�[2K�[32m|�[0m Collecting SecretStorage (3.5.0)
�[2K�[32m|�[0m Auditing SecretStorage (3.5.0)
�[2K�[32m|�[0m Collecting setuptools (82.0.1)
�[2K�[32m|�[0m Auditing setuptools (82.0.1)
�[2K�[32m|�[0m Collecting shellingham (1.5.4)
�[2K�[32m|�[0m Auditing shellingham (1.5.4)
�[2K�[32m|�[0m Collecting sortedcontainers (2.4.0)
�[2K�[32m|�[0m Auditing sortedcontainers (2.4.0)
�[2K�[32m|�[0m Collecting stevedore (5.7.0)
�[2K�[32m|�[0m Auditing stevedore (5.7.0)
�[2K�[32m|�[0m Collecting tabulate (0.10.0)
�[2K�[32m|�[0m Auditing tabulate (0.10.0)
�[2K�[32m|�[0m Collecting tenacity (9.1.4)
�[2K�[32m|�[0m Auditing tenacity (9.1.4)
�[2K�[32m|�[0m Collecting tomli (2.4.0)
�[2K�[32m|�[0m Auditing tomli (2.4.0)
�[2K�[32m|�[0m Collecting tomli_w (1.2.0)
�[2K�[32m|�[0m Auditing tomli_w (1.2.0)
�[2K�[32m|�[0m Collecting tomlkit (0.14.0)
�[2K�[32m|�[0m Auditing tomlkit (0.14.0)
�[2K�[32m|�[0m Collecting tqdm (4.67.3)
�[2K�[32m|�[0m Auditing tqdm (4.67.3)
�[2K�[32m|�[0m Collecting twine (6.2.0)
�[2K�[32m|�[0m Auditing twine (6.2.0)
�[2K�[32m|�[0m Collecting typer (0.24.1)
�[2K�[32m|�[0m Auditing typer (0.24.1)
�[2K�[32m|�[0m Collecting types-PyYAML (6.0.12.20250915)
�[2K�[32m|�[0m Auditing types-PyYAML (6.0.12.20250915)
�[2K�[32m|�[0m Collecting typing_extensions (4.15.0)
�[2K�[32m|�[0m Collecting typing_extensions (4.15.0)
�[2K�[32m|�[0m Auditing typing_extensions (4.15.0)
�[2K�[32m|�[0m Collecting typing-inspection (0.4.2)
�[2K�[32m|�[0m Auditing typing-inspection (0.4.2)
�[2K�[32m|�[0m Collecting urllib3 (2.6.3)
�[2K�[32m|�[0m Auditing urllib3 (2.6.3)
�[2K�[32m|�[0m Collecting wheel (0.46.3)
�[2K�[32m|�[0m Auditing wheel (0.46.3)
�[2K�[32m|�[0m Collecting yarl (1.23.0)
�[2K�[32m|�[0m Auditing yarl (1.23.0)
�[2K�[32m|�[0m Auditing yarl (1.23.0)
�[?25h
�[1A�[2K```

@docdyhr docdyhr merged commit 7c4a7ec into master Mar 17, 2026
28 checks passed
@docdyhr docdyhr deleted the fix/fuzzy-fallback-and-ci-improvements branch March 17, 2026 12:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant