DEVX-90: fix: classify .gitea/** as user-facing for devx, support glob in user_facing_overrides
Post-merge / detect-type (push) Successful in 8s
Post-merge / validate-commit-msg (push) Successful in 8s
Post-merge / vikunja (push) Successful in 11s
Post-merge / sync-wiki (push) Successful in 16s
Post-merge / configure-repo (push) Successful in 10s
Build Images / detect-type (push) Successful in 31s
Post-merge / release (push) Successful in 23s
Post-merge / badges (push) Successful in 28s
Post-merge / publish (push) Successful in 15s
Build Images / build-and-push (push) Successful in 2m46s
Build Images / cleanup (push) Successful in 1m29s

This commit was merged in pull request #142.
This commit is contained in:
2026-06-27 22:14:05 +00:00
parent 014ab0b63f
commit 6149167ba2
3 changed files with 28 additions and 13 deletions
+6 -2
View File
@@ -139,8 +139,12 @@ infrastructure_overrides = [
] ]
# User-facing overrides — safety override for broad infrastructure patterns # User-facing overrides — safety override for broad infrastructure patterns
# (empty — add when an infrastructure pattern is too broad) # devx workflow files (.gitea/**) are reference implementations that
user_facing_overrides = [] # downstream repos (grm, infra) copy from. Changes to them affect how
# consumer projects run their CI, so they must trigger a release.
user_facing_overrides = [
".gitea/**",
]
# Tag patterns — additional categories for CI conditional execution # Tag patterns — additional categories for CI conditional execution
# Orthogonal to release impact (user-facing vs infrastructure) # Orthogonal to release impact (user-facing vs infrastructure)
+9 -8
View File
@@ -393,14 +393,15 @@ class ChangeClassifier:
tags = self._compute_tags(file_path) tags = self._compute_tags(file_path)
# 1. User-facing overrides (highest priority — safety) # 1. User-facing overrides (highest priority — safety)
if file_path in self._user_overrides: for pattern in self._user_overrides:
return FileClassification( if _matches_glob(file_path, pattern):
path=file_path, return FileClassification(
is_user_facing=True, path=file_path,
reason="User-facing override (safety override)", is_user_facing=True,
matched_rule="user_facing_overrides", reason=f"User-facing override (matches '{pattern}')",
tags=tags, matched_rule="user_facing_overrides",
) tags=tags,
)
# 2. Infrastructure overrides # 2. Infrastructure overrides
if file_path in self._infra_overrides: if file_path in self._infra_overrides:
+13 -3
View File
@@ -264,6 +264,16 @@ class TestChangeClassifier:
assert fc.is_user_facing assert fc.is_user_facing
assert fc.matched_rule == "user_facing_overrides" assert fc.matched_rule == "user_facing_overrides"
def test_user_facing_override_glob_matches_nested(self) -> None:
"""User-facing overrides support glob patterns like infrastructure."""
classifier = self._make_classifier(
infrastructure=[".gitea/**"],
user_facing_overrides=[".gitea/**"],
)
fc = classifier.classify_file(".gitea/workflows/ci.yml")
assert fc.is_user_facing
assert fc.matched_rule == "user_facing_overrides"
def test_user_facing_override_beats_infrastructure_override(self) -> None: def test_user_facing_override_beats_infrastructure_override(self) -> None:
"""User-facing overrides beat infrastructure overrides (safety first).""" """User-facing overrides beat infrastructure overrides (safety first)."""
classifier = self._make_classifier( classifier = self._make_classifier(
@@ -489,7 +499,7 @@ class TestMain:
@patch("devx.ci.classify_changes.get_changed_files") @patch("devx.ci.classify_changes.get_changed_files")
@patch("devx.ci.classify_changes.get_latest_tag", return_value="v0.3.0") @patch("devx.ci.classify_changes.get_latest_tag", return_value="v0.3.0")
def test_workflow_only_exits_2(self, mock_tag: MagicMock, mock_changes: MagicMock) -> None: def test_workflow_only_exits_2(self, mock_tag: MagicMock, mock_changes: MagicMock) -> None:
mock_changes.return_value = [".gitea/workflows/ci.yml", "docs/index.md"] mock_changes.return_value = ["docs/index.md", "README.md"]
runner = CliRunner() runner = CliRunner()
result = runner.invoke(main, []) result = runner.invoke(main, [])
assert result.exit_code == 2 assert result.exit_code == 2
@@ -572,7 +582,7 @@ class TestMain:
@patch("devx.ci.classify_changes.get_changed_files") @patch("devx.ci.classify_changes.get_changed_files")
@patch("devx.ci.classify_changes.get_latest_tag", return_value="v0.3.0") @patch("devx.ci.classify_changes.get_latest_tag", return_value="v0.3.0")
def test_quiet_workflow_only(self, mock_tag: MagicMock, mock_changes: MagicMock) -> None: def test_quiet_workflow_only(self, mock_tag: MagicMock, mock_changes: MagicMock) -> None:
mock_changes.return_value = [".gitea/workflows/ci.yml"] mock_changes.return_value = ["docs/index.md"]
runner = CliRunner() runner = CliRunner()
result = runner.invoke(main, ["--quiet"]) result = runner.invoke(main, ["--quiet"])
assert result.exit_code == 0 assert result.exit_code == 0
@@ -631,7 +641,7 @@ class TestMain:
@patch("devx.ci.classify_changes.get_changed_files") @patch("devx.ci.classify_changes.get_changed_files")
@patch("devx.ci.classify_changes.get_latest_tag", return_value="v0.3.0") @patch("devx.ci.classify_changes.get_latest_tag", return_value="v0.3.0")
def test_check_user_facing_false(self, mock_tag: MagicMock, mock_changes: MagicMock) -> None: def test_check_user_facing_false(self, mock_tag: MagicMock, mock_changes: MagicMock) -> None:
mock_changes.return_value = [".gitea/workflows/ci.yml", "tests/test_foo.py"] mock_changes.return_value = ["docs/index.md", "tests/test_foo.py"]
runner = CliRunner() runner = CliRunner()
result = runner.invoke(main, ["--check", "user-facing", "--quiet"]) result = runner.invoke(main, ["--check", "user-facing", "--quiet"])
assert result.exit_code == 0 assert result.exit_code == 0