diff --git a/AGENTS.md b/AGENTS.md index b5118c7..b6e98e2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -21,6 +21,7 @@ Version 0.1.0 is publicly released. Remote installation through GitHub CLI and l - Added deterministic scoring based on freshness, repository activity, stars, discussion size, labels, reactions, and maintainer participation. - Added recent claim detection for assignments and common claim phrases. +- Added a repeatable excluded-label search filter for removing blocked or noisy workflows. - Added tests, cross-platform CI, security notes, and contributor documentation. - Published the repository and v0.1.0 release with GitHub CLI extension discovery metadata. diff --git a/README.md b/README.md index 7101e5c..3ab1831 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,7 @@ Focus the search: ```bash gh issue-scout parser --language Python --min-stars 500 gh issue-scout --org home-assistant --label "help wanted" +gh issue-scout --label "good first issue" --exclude-label blocked gh issue-scout --repo rust-lang/rust --label E-easy --max-age 30 ``` @@ -48,6 +49,7 @@ Useful controls: ```text --claim-window DAYS Treat recent claim comments as active for this long +--exclude-label LABEL Exclude a label; repeat for multiple labels --include-claimed Show claimed candidates and their evidence --include-assigned Include assigned issues in the GitHub search --max-repo-idle DAYS Exclude repositories without a recent push diff --git a/issue_scout/cli.py b/issue_scout/cli.py index f737399..d81a7e7 100644 --- a/issue_scout/cli.py +++ b/issue_scout/cli.py @@ -22,6 +22,11 @@ def parser() -> argparse.ArgumentParser: ) command.add_argument("keywords", nargs="*", help="optional search terms") command.add_argument("--label", action="append", help="label to require; repeat for multiple labels") + command.add_argument( + "--exclude-label", + action="append", + help="label to exclude; repeat for multiple labels", + ) command.add_argument("--language", help="repository primary language") command.add_argument("--repo", help="limit to OWNER/REPOSITORY") command.add_argument("--org", help="limit to one organization") @@ -67,6 +72,7 @@ def main(argv: list[str] | None = None) -> int: search_query = build_query( keywords=arguments.keywords, labels=arguments.label, + excluded_labels=arguments.exclude_label, language=arguments.language, repository=arguments.repo, organization=arguments.org, diff --git a/issue_scout/query.py b/issue_scout/query.py index 6477a0a..7e477d3 100644 --- a/issue_scout/query.py +++ b/issue_scout/query.py @@ -17,6 +17,7 @@ def build_query( *, keywords: list[str] | None = None, labels: list[str] | None = None, + excluded_labels: list[str] | None = None, language: str | None = None, repository: str | None = None, organization: str | None = None, @@ -38,6 +39,8 @@ def build_query( parts.append("no:assignee") for label in labels or ["good first issue"]: parts.append(f"label:{quote_value(label)}") + for label in excluded_labels or []: + parts.append(f"-label:{quote_value(label)}") if language: parts.append(f"language:{quote_value(language)}") if repository: diff --git a/tests/test_query.py b/tests/test_query.py index 62c42ce..81b76c8 100644 --- a/tests/test_query.py +++ b/tests/test_query.py @@ -36,5 +36,14 @@ def test_repo_and_org_are_mutually_exclusive(self) -> None: with self.assertRaisesRegex(ValueError, "cannot be used together"): build_query(repository="owner/repo", organization="owner") + def test_excluded_labels_are_repeatable_and_safely_quoted(self) -> None: + query = build_query( + labels=["help wanted"], + excluded_labels=["blocked", "needs info"], + now=datetime(2026, 8, 29, tzinfo=timezone.utc), + ) + + self.assertIn('label:"help wanted" -label:blocked -label:"needs info"', query) + def test_quote_value_removes_embedded_quotes(self) -> None: self.assertEqual(quote_value('good "first" issue'), '"good first issue"')