From 629047d9a6673e18c2ee18c4a5d1642952875d97 Mon Sep 17 00:00:00 2001 From: PythonWoods Date: Thu, 2 Apr 2026 19:19:51 +0200 Subject: [PATCH] fix(sentinel): deduplicate configured plugins and polish IT release note --- docs/it/about/index.md | 2 +- src/zenzic/core/rules.py | 8 +++++++- tests/test_rules.py | 29 +++++++++++++++++++++++++++++ 3 files changed, 37 insertions(+), 2 deletions(-) diff --git a/docs/it/about/index.md b/docs/it/about/index.md index 7588564..d1ccb50 100644 --- a/docs/it/about/index.md +++ b/docs/it/about/index.md @@ -52,7 +52,7 @@ Sviluppato da [PythonWoods](https://github.com/PythonWoods), è progettato per e --- Storico completo delle release e policy della linea attiva. - La linea 0.4.x e stata abbandonata; la stabilizzazione attiva e 0.5.x. + La linea 0.4.x è stata abbandonata; la stabilizzazione attiva è la 0.5.x. [:lucide-arrow-right: Leggi](https://github.com/PythonWoods/zenzic/blob/main/CHANGELOG.md) diff --git a/src/zenzic/core/rules.py b/src/zenzic/core/rules.py index 6307379..1f5a888 100644 --- a/src/zenzic/core/rules.py +++ b/src/zenzic/core/rules.py @@ -774,7 +774,13 @@ def load_selected_rules(self, plugin_ids: Sequence[str]) -> list[BaseRule]: """ from zenzic.core.exceptions import PluginContractError # deferred: avoid circular import - requested = [pid.strip() for pid in plugin_ids if pid.strip()] + requested: list[str] = [] + seen: set[str] = set() + for pid in plugin_ids: + cleaned = pid.strip() + if cleaned and cleaned not in seen: + seen.add(cleaned) + requested.append(cleaned) if not requested: return [] diff --git a/tests/test_rules.py b/tests/test_rules.py index 3f8263a..039bbdc 100644 --- a/tests/test_rules.py +++ b/tests/test_rules.py @@ -15,6 +15,7 @@ AdaptiveRuleEngine, BaseRule, CustomRule, + PluginRegistry, RuleFinding, Violation, VSMBrokenLinkRule, @@ -264,6 +265,34 @@ def test_scan_docs_with_unknown_plugin_raises_contract_error(tmp_path: Path) -> scan_docs_references(tmp_path, config) +def test_plugin_registry_deduplicates_requested_plugin_ids( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Duplicate plugin IDs are loaded once while preserving declaration order.""" + + class _EP: + def __init__(self, name: str) -> None: + self.name = name + + registry = PluginRegistry() + monkeypatch.setattr( + registry, + "_entry_points", + lambda: [_EP("acme"), _EP("beta")], + ) + + loaded_names: list[str] = [] + + def _fake_load(ep: _EP) -> BaseRule: + loaded_names.append(ep.name) + return _PluginTodoRule() + + monkeypatch.setattr(registry, "_load_entry_point", _fake_load) + + _rules = registry.load_selected_rules(["acme", "acme", "beta", "acme"]) # noqa: F841 + assert loaded_names == ["acme", "beta"] + + # ─── Cross-adapter custom rules (Dev 4 mandate) ───────────────────────────────