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
74 changes: 46 additions & 28 deletions desloppify/app/commands/show/scope.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
QueueBuildOptions,
build_work_queue,
)
from desloppify.engine._work_queue.helpers import scope_matches
from desloppify.engine._work_queue.ranking import build_issue_items


@dataclass(frozen=True)
Expand Down Expand Up @@ -197,39 +199,55 @@ def load_matches(
status_filter: str,
chronic: bool,
) -> list[dict[str, Any]]:
"""Load matching issues from the ranked queue.
"""Load matching issues for an exploratory show query.

When a scope is provided, search both execution and backlog partitions
so scoped queries (e.g. ``show advocacy_language``) surface findings
even when a different lifecycle phase (like initial review) owns the
execution partition.
Unlike execution queues, show should surface persisted matching issues even
when a detector has a higher standalone confidence threshold.
"""
from desloppify.engine.work_queue import build_work_queue_for_visibility, QueueVisibility

base_opts = QueueBuildOptions(
count=None,
issue_map = state.get("work_items") or state.get("issues", {})
if not isinstance(issue_map, dict) or not issue_map:
queue = build_work_queue(
state,
options=QueueBuildOptions(
count=None,
scope=scope,
status=status_filter,
include_subjective=False,
chronic=chronic,
),
)
return [item for item in queue.get("items", []) if item.get("kind") == "issue"]
return build_issue_items(
state,
scan_path=state.get("scan_path"),
status_filter=status_filter,
scope=scope,
status=status_filter,
include_subjective=False,
chronic=chronic,
forced_ids=_matching_issue_ids_for_scope(state, scope),
)
queue = build_work_queue(state, options=base_opts)
matches = [
item for item in queue.get("items", []) if item.get("kind") == "issue"
]
# When scoped and execution partition returned nothing, check backlog.
if not matches and scope and status_filter == "open":
backlog_queue = build_work_queue_for_visibility(
state,
options=base_opts,
visibility=QueueVisibility.BACKLOG,
)
matches = [
item
for item in backlog_queue.get("items", [])
if item.get("kind") == "issue"
]
return matches


def _matching_issue_ids_for_scope(
state: StateModel,
scope: str | None,
) -> set[str]:
"""Return persisted IDs matching a show scope, bypassing queue thresholds."""
issue_map = state.get("work_items") or state.get("issues", {})
if not isinstance(issue_map, dict):
return set()
if not scope:
return {issue_id for issue_id in issue_map if isinstance(issue_id, str)}

matched: set[str] = set()
for issue_id, issue in issue_map.items():
if not isinstance(issue_id, str) or not isinstance(issue, dict):
continue
item = dict(issue)
item["id"] = issue_id
item.setdefault("kind", "issue")
if scope_matches(item, scope):
matched.add(issue_id)
return matched


def resolve_noise(
Expand Down
4 changes: 0 additions & 4 deletions desloppify/engine/work_queue.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,19 @@
from desloppify.engine._work_queue.core import (
QueueBuildOptions,
WorkQueueResult,
_build_work_queue_with_visibility as build_work_queue_for_visibility,
build_work_queue,
)
from desloppify.engine._work_queue.issues import list_open_review_issues
from desloppify.engine._work_queue.models import QueueVisibility
from desloppify.engine._work_queue.ranking import group_queue_items
from desloppify.engine._work_queue.synthetic_workflow import (
build_deferred_disposition_item,
)

__all__ = [
"QueueBuildOptions",
"QueueVisibility",
"WorkQueueResult",
"build_deferred_disposition_item",
"build_work_queue",
"build_work_queue_for_visibility",
"group_queue_items",
"list_open_review_issues",
]
Original file line number Diff line number Diff line change
Expand Up @@ -209,8 +209,9 @@ def test_bad_content(self, mock_download, _mock_local, _mock_colorize, capsys):
assert "doesn't look like a skill document" in out

@patch("desloppify.app.commands.update_skill.colorize", side_effect=lambda t, _c: t)
@patch("desloppify.app.commands.update_skill.cmd._read_local_docs_file", return_value=None)
@patch("desloppify.app.commands.update_skill._download")
def test_successful_dedicated_install(self, mock_download, _mock_colorize, capsys, tmp_path):
def test_successful_dedicated_install(self, mock_download, _mock_local, _mock_colorize, capsys, tmp_path):
skill_content = "# Skill\n<!-- desloppify-skill-version: 1 -->\nContent"
mock_download.side_effect = lambda f: {
"SKILL.md": skill_content,
Expand All @@ -230,9 +231,10 @@ def test_successful_dedicated_install(self, mock_download, _mock_colorize, capsy
assert "Updated" in out

@patch("desloppify.app.commands.update_skill.colorize", side_effect=lambda t, _c: t)
@patch("desloppify.app.commands.update_skill.cmd._read_local_docs_file", return_value=None)
@patch("desloppify.app.commands.update_skill._download")
def test_successful_dedicated_install_rovodev(
self, mock_download, _mock_colorize, capsys, tmp_path
self, mock_download, _mock_local, _mock_colorize, capsys, tmp_path
):
"""Per-project `update-skill rovodev` writes the dedicated `.rovodev/...` file."""
skill_content = "# Skill\n<!-- desloppify-skill-version: 1 -->\nContent"
Expand All @@ -257,8 +259,9 @@ def test_successful_dedicated_install_rovodev(
assert "Updated" in out

@patch("desloppify.app.commands.update_skill.colorize", side_effect=lambda t, _c: t)
@patch("desloppify.app.commands.update_skill.cmd._read_local_docs_file", return_value=None)
@patch("desloppify.app.commands.update_skill._download")
def test_successful_shared_install(self, mock_download, _mock_colorize, capsys, tmp_path):
def test_successful_shared_install(self, mock_download, _mock_local, _mock_colorize, capsys, tmp_path):
"""Non-dedicated install (e.g. windsurf) replaces section in existing file."""
skill_content = "# Skill\n<!-- desloppify-skill-version: 1 -->\nContent"
mock_download.side_effect = lambda f: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,4 @@ def test_treesitter_language_pack_is_capped_below_incompatible_release() -> None
if str(spec).startswith("tree-sitter-language-pack")
]

assert language_pack_specs == ["tree-sitter-language-pack>=0.3,<1.8"]
assert language_pack_specs == ["tree-sitter-language-pack>=0.3,<1.6.3"]
10 changes: 10 additions & 0 deletions desloppify/tests/lang/common/test_bash_unused_imports.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@

import textwrap

import pytest

from desloppify.languages._framework.treesitter import is_available

# Skip all tests if tree-sitter-language-pack is not installed.
# Matches the guard pattern used by other test_treesitter modules.
pytestmark = pytest.mark.skipif(
not is_available(), reason="tree-sitter-language-pack not installed"
)


def _detect(tmp_path, contents: str):
from desloppify.languages._framework.treesitter.analysis.unused_imports import (
Expand Down
12 changes: 10 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,11 @@ Issues = "https://github.com/peteromallet/desloppify/issues"
[project.optional-dependencies]
treesitter = [
"tree-sitter>=0.21",
"tree-sitter-language-pack>=0.3,<1.8",
# 1.6.3 was a breaking refactor by upstream: the public module moved from
# `tree_sitter_language_pack` to `_native`, removing the top-level package
# that desloppify's parser bootstrap imports. Pin out until the API is
# restored (or desloppify migrates to the new import path).
"tree-sitter-language-pack>=0.3,<1.6.3",
]
csharp-xml = ["defusedxml>=0.7.0"]
python-security = ["bandit>=1.7.8"]
Expand All @@ -45,7 +49,11 @@ plan-yaml = ["PyYAML>=6.0"]
full = [
"defusedxml>=0.7.0",
"tree-sitter>=0.21",
"tree-sitter-language-pack>=0.3,<1.8",
# 1.6.3 was a breaking refactor by upstream: the public module moved from
# `tree_sitter_language_pack` to `_native`, removing the top-level package
# that desloppify's parser bootstrap imports. Pin out until the API is
# restored (or desloppify migrates to the new import path).
"tree-sitter-language-pack>=0.3,<1.6.3",
"bandit>=1.7.8",
"Pillow>=9.0.0",
"PyYAML>=6.0",
Expand Down
Loading