Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,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 an optional issue-activity cutoff through `--updated-within`.
- Added tests, cross-platform CI, security notes, and contributor documentation.
- Published the repository and v0.1.0 release with GitHub CLI extension discovery metadata.

Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ Focus the search:
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
gh issue-scout --repo rust-lang/rust --label E-easy --max-age 30 --updated-within 7
```

Generate output for another tool:
Expand All @@ -53,6 +53,7 @@ Useful controls:
--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
--updated-within DAYS Require recent activity on the issue itself
--query QUALIFIERS Append raw GitHub search qualifiers
--show-query Print the exact generated search query
--open Open the top result in a browser
Expand Down
6 changes: 6 additions & 0 deletions issue_scout/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ def parser() -> argparse.ArgumentParser:
command.add_argument("--query", help="append raw GitHub search qualifiers")
command.add_argument("--min-stars", type=int, default=50, help="minimum repository stars (default: 50)")
command.add_argument("--max-age", type=int, default=90, help="maximum issue age in days (default: 90)")
command.add_argument(
"--updated-within",
type=int,
help="require issue activity within this many days",
)
command.add_argument(
"--max-repo-idle",
type=int,
Expand Down Expand Up @@ -77,6 +82,7 @@ def main(argv: list[str] | None = None) -> int:
repository=arguments.repo,
organization=arguments.org,
max_age_days=arguments.max_age,
updated_within_days=arguments.updated_within,
include_assigned=arguments.include_assigned,
extra_query=arguments.query,
)
Expand Down
6 changes: 6 additions & 0 deletions issue_scout/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ def build_query(
repository: str | None = None,
organization: str | None = None,
max_age_days: int = 90,
updated_within_days: int | None = None,
include_assigned: bool = False,
extra_query: str | None = None,
now: datetime | None = None,
Expand All @@ -31,6 +32,8 @@ def build_query(
raise ValueError("--repo and --org cannot be used together")
if max_age_days < 1:
raise ValueError("--max-age must be at least 1 day")
if updated_within_days is not None and updated_within_days < 1:
raise ValueError("--updated-within must be at least 1 day")

current = now or datetime.now(timezone.utc)
created_after = (current - timedelta(days=max_age_days)).date().isoformat()
Expand All @@ -48,6 +51,9 @@ def build_query(
if organization:
parts.append(f"org:{quote_value(organization)}")
parts.append(f"created:>={created_after}")
if updated_within_days is not None:
updated_after = (current - timedelta(days=updated_within_days)).date().isoformat()
parts.append(f"updated:>={updated_after}")
if keywords:
parts.extend(keyword.strip() for keyword in keywords if keyword.strip())
if extra_query:
Expand Down
12 changes: 12 additions & 0 deletions tests/test_query.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,17 @@ def test_excluded_labels_are_repeatable_and_safely_quoted(self) -> None:

self.assertIn('label:"help wanted" -label:blocked -label:"needs info"', query)

def test_updated_within_adds_activity_cutoff(self) -> None:
query = build_query(
updated_within_days=7,
now=datetime(2026, 8, 29, tzinfo=timezone.utc),
)

self.assertIn("created:>=2026-05-31 updated:>=2026-08-22", query)

def test_updated_within_rejects_non_positive_days(self) -> None:
with self.assertRaisesRegex(ValueError, "--updated-within must be at least 1 day"):
build_query(updated_within_days=0)

def test_quote_value_removes_embedded_quotes(self) -> None:
self.assertEqual(quote_value('good "first" issue'), '"good first issue"')