Skip to content

rank

Full name: tenets.cli.commands.rank

rank

Rank command - show ranked files without content.

This module provides the rank command for the tenets CLI, which allows users to see which files are most relevant to their query without displaying the actual content of those files. This is useful for previewing what would be included in a full distill operation or for generating file lists for automation.

Classes

Functions

rank

Python
rank(prompt: str = typer.Argument(..., help='Your query or task to rank files against'), path: Path = typer.Argument(Path(), help='Path to analyze (directory or files)'), format: str = typer.Option('markdown', '--format', '-f', help='Output format: markdown, json, xml, html, tree'), output: Optional[Path] = typer.Option(None, '--output', '-o', help='Save output to file instead of stdout'), mode: str = typer.Option('balanced', '--mode', '-m', help='Ranking mode: fast (keyword only), balanced (TF-IDF + structure), thorough (deep analysis)'), top: Optional[int] = typer.Option(None, '--top', '-t', help='Show only top N files'), min_score: Optional[float] = typer.Option(None, '--min-score', help='Minimum relevance score (0.0-1.0)'), max_files: Optional[int] = typer.Option(None, '--max-files', help='Maximum number of files to show'), tree_view: bool = typer.Option(False, '--tree', help='Show results as directory tree'), show_scores: bool = typer.Option(True, '--scores/--no-scores', help='Show relevance scores'), show_factors: bool = typer.Option(False, '--factors', help='Show ranking factor breakdown'), show_path: str = typer.Option('relative', '--path-style', help='Path display: relative, absolute, name'), include: Optional[str] = typer.Option(None, '--include', '-i', help="Include file patterns (e.g., '*.py,*.js')"), exclude: Optional[str] = typer.Option(None, '--exclude', '-e', help="Exclude file patterns (e.g., 'test_*,*.backup')"), include_tests: bool = typer.Option(False, '--include-tests', help='Include test files'), exclude_tests: bool = typer.Option(False, '--exclude-tests', help='Explicitly exclude test files'), no_git: bool = typer.Option(False, '--no-git', help='Disable git signals in ranking'), session: Optional[str] = typer.Option(None, '--session', '-s', help='Use session for stateful ranking'), show_stats: bool = typer.Option(False, '--stats', help='Show ranking statistics'), verbose: bool = typer.Option(False, '--verbose', '-v', help='Show detailed debug information'), copy: bool = typer.Option(False, '--copy', help='Copy file list to clipboard')) -> None

Rank files by relevance without showing their content.

This command runs the same intelligent ranking as 'distill' but only shows the list of relevant files, their scores, and optionally the ranking factors. Useful for understanding what files would be included in context or for feeding file lists to other tools.

PARAMETERDESCRIPTION
prompt

The query or task to rank files against.

TYPE:strDEFAULT:Argument(..., help='Your query or task to rank files against')

path

Path to analyze (directory or files).

TYPE:PathDEFAULT:Argument(Path(), help='Path to analyze (directory or files)')

format

Output format (markdown, json, xml, html, tree).

TYPE:strDEFAULT:Option('markdown', '--format', '-f', help='Output format: markdown, json, xml, html, tree')

output

Optional file path to save output.

TYPE:Optional[Path]DEFAULT:Option(None, '--output', '-o', help='Save output to file instead of stdout')

mode

Ranking algorithm mode (fast, balanced, thorough).

TYPE:strDEFAULT:Option('balanced', '--mode', '-m', help='Ranking mode: fast (keyword only), balanced (TF-IDF + structure), thorough (deep analysis)')

top

Show only top N files.

TYPE:Optional[int]DEFAULT:Option(None, '--top', '-t', help='Show only top N files')

min_score

Minimum relevance score threshold (0.0-1.0).

TYPE:Optional[float]DEFAULT:Option(None, '--min-score', help='Minimum relevance score (0.0-1.0)')

max_files

Maximum number of files to display.

TYPE:Optional[int]DEFAULT:Option(None, '--max-files', help='Maximum number of files to show')

tree_view

Whether to show results as directory tree.

TYPE:boolDEFAULT:Option(False, '--tree', help='Show results as directory tree')

show_scores

Whether to display relevance scores.

TYPE:boolDEFAULT:Option(True, '--scores/--no-scores', help='Show relevance scores')

show_factors

Whether to show ranking factor breakdown.

TYPE:boolDEFAULT:Option(False, '--factors', help='Show ranking factor breakdown')

show_path

Path display style (relative, absolute, name).

TYPE:strDEFAULT:Option('relative', '--path-style', help='Path display: relative, absolute, name')

include

Include file patterns (comma-separated).

TYPE:Optional[str]DEFAULT:Option(None, '--include', '-i', help="Include file patterns (e.g., '*.py,*.js')")

exclude

Exclude file patterns (comma-separated).

TYPE:Optional[str]DEFAULT:Option(None, '--exclude', '-e', help="Exclude file patterns (e.g., 'test_*,*.backup')")

include_tests

Whether to include test files.

TYPE:boolDEFAULT:Option(False, '--include-tests', help='Include test files')

exclude_tests

Whether to explicitly exclude test files.

TYPE:boolDEFAULT:Option(False, '--exclude-tests', help='Explicitly exclude test files')

no_git

Whether to disable git signals in ranking.

TYPE:boolDEFAULT:Option(False, '--no-git', help='Disable git signals in ranking')

session

Optional session name for stateful ranking.

TYPE:Optional[str]DEFAULT:Option(None, '--session', '-s', help='Use session for stateful ranking')

show_stats

Whether to show ranking statistics.

TYPE:boolDEFAULT:Option(False, '--stats', help='Show ranking statistics')

verbose

Whether to show detailed debug information.

TYPE:boolDEFAULT:Option(False, '--verbose', '-v', help='Show detailed debug information')

copy

Whether to copy file list to clipboard.

TYPE:boolDEFAULT:Option(False, '--copy', help='Copy file list to clipboard')

RETURNSDESCRIPTION
None

None

RAISESDESCRIPTION
SystemExit

On error with exit code 1.

Examples:

Show top 10 most relevant files

tenets rank "implement OAuth2" --top 10

Show files above a score threshold

tenets rank "fix bug" . --min-score 0.3

Tree view with ranking factors

tenets rank "add caching" --tree --factors

Export as JSON for automation

tenets rank "review API" --format json -o ranked_files.json

Quick file list to clipboard

tenets rank "database queries" --top 20 --copy --no-scores

Source code in tenets/cli/commands/rank.py
Python
def rank(
    prompt: str = typer.Argument(..., help="Your query or task to rank files against"),
    path: Path = typer.Argument(Path(), help="Path to analyze (directory or files)"),
    # Output options
    format: str = typer.Option(
        "markdown", "--format", "-f", help="Output format: markdown, json, xml, html, tree"
    ),
    output: Optional[Path] = typer.Option(
        None, "--output", "-o", help="Save output to file instead of stdout"
    ),
    # Ranking options
    mode: str = typer.Option(
        "balanced",  # Use same default as distill command for consistency
        "--mode",
        "-m",
        help="Ranking mode: fast (keyword only), balanced (TF-IDF + structure), thorough (deep analysis)",
    ),
    top: Optional[int] = typer.Option(None, "--top", "-t", help="Show only top N files"),
    min_score: Optional[float] = typer.Option(
        None, "--min-score", help="Minimum relevance score (0.0-1.0)"
    ),
    max_files: Optional[int] = typer.Option(
        None, "--max-files", help="Maximum number of files to show"
    ),
    # Display options
    tree_view: bool = typer.Option(False, "--tree", help="Show results as directory tree"),
    show_scores: bool = typer.Option(True, "--scores/--no-scores", help="Show relevance scores"),
    show_factors: bool = typer.Option(False, "--factors", help="Show ranking factor breakdown"),
    show_path: str = typer.Option(
        "relative", "--path-style", help="Path display: relative, absolute, name"
    ),
    # Filtering
    include: Optional[str] = typer.Option(
        None, "--include", "-i", help="Include file patterns (e.g., '*.py,*.js')"
    ),
    exclude: Optional[str] = typer.Option(
        None, "--exclude", "-e", help="Exclude file patterns (e.g., 'test_*,*.backup')"
    ),
    include_tests: bool = typer.Option(False, "--include-tests", help="Include test files"),
    exclude_tests: bool = typer.Option(
        False, "--exclude-tests", help="Explicitly exclude test files"
    ),
    # Features
    no_git: bool = typer.Option(False, "--no-git", help="Disable git signals in ranking"),
    session: Optional[str] = typer.Option(
        None, "--session", "-s", help="Use session for stateful ranking"
    ),
    # Info options
    show_stats: bool = typer.Option(False, "--stats", help="Show ranking statistics"),
    verbose: bool = typer.Option(False, "--verbose", "-v", help="Show detailed debug information"),
    copy: bool = typer.Option(False, "--copy", help="Copy file list to clipboard"),
) -> None:
    """Rank files by relevance without showing their content.

    This command runs the same intelligent ranking as 'distill' but only shows
    the list of relevant files, their scores, and optionally the ranking factors.
    Useful for understanding what files would be included in context or for
    feeding file lists to other tools.

    Args:
        prompt: The query or task to rank files against.
        path: Path to analyze (directory or files).
        format: Output format (markdown, json, xml, html, tree).
        output: Optional file path to save output.
        mode: Ranking algorithm mode (fast, balanced, thorough).
        top: Show only top N files.
        min_score: Minimum relevance score threshold (0.0-1.0).
        max_files: Maximum number of files to display.
        tree_view: Whether to show results as directory tree.
        show_scores: Whether to display relevance scores.
        show_factors: Whether to show ranking factor breakdown.
        show_path: Path display style (relative, absolute, name).
        include: Include file patterns (comma-separated).
        exclude: Exclude file patterns (comma-separated).
        include_tests: Whether to include test files.
        exclude_tests: Whether to explicitly exclude test files.
        no_git: Whether to disable git signals in ranking.
        session: Optional session name for stateful ranking.
        show_stats: Whether to show ranking statistics.
        verbose: Whether to show detailed debug information.
        copy: Whether to copy file list to clipboard.

    Returns:
        None

    Raises:
        SystemExit: On error with exit code 1.

    Examples:
        # Show top 10 most relevant files
        tenets rank "implement OAuth2" --top 10

        # Show files above a score threshold
        tenets rank "fix bug" . --min-score 0.3

        # Tree view with ranking factors
        tenets rank "add caching" --tree --factors

        # Export as JSON for automation
        tenets rank "review API" --format json -o ranked_files.json

        # Quick file list to clipboard
        tenets rank "database queries" --top 20 --copy --no-scores
    """
    # Initialize timer
    is_json_quiet: bool = format.lower() == "json" and not output
    timer: CommandTimer = CommandTimer(console, is_json_quiet)

    try:
        timer.start("Initializing ranking...")

        # Initialize tenets with same distiller pipeline
        tenets_instance: Tenets = Tenets()

        # Use the same distiller pipeline that the distill command uses
        # This ensures consistent ranking behavior

        # Show progress only for non-JSON formats
        if format.lower() != "json" or output:
            console.print(f"[yellow]Ranking files for: {prompt[:50]}...[/yellow]")

        # Use distiller's ranking pipeline by calling rank_files directly
        # This ensures we get the same sophisticated ranking as distill
        result: Any = tenets_instance.rank_files(
            prompt=prompt,
            paths=[path] if path else None,
            mode=mode,
            include_patterns=include.split(",") if include else None,
            exclude_patterns=exclude.split(",") if exclude else None,
            include_tests=include_tests if include_tests else None,
            exclude_tests=exclude_tests if exclude_tests else False,
            explain=show_factors,
        )

        ranked_files: List[FileAnalysis] = result.files

        # Apply threshold filtering if min_score is set
        if min_score:
            ranked_files = [
                f for f in ranked_files if getattr(f, "relevance_score", 0) >= min_score
            ]

        # Apply limits
        if top:
            ranked_files = ranked_files[:top]
        if max_files:
            ranked_files = ranked_files[:max_files]

        # Format output
        output_content: str = _format_ranked_files(
            ranked_files,
            format=format,
            tree_view=tree_view,
            show_scores=show_scores,
            show_factors=show_factors,
            show_path=show_path,
            prompt=prompt,
            stats=None,  # Stats not available from rank_files yet
        )

        # Output results
        if output:
            output.write_text(output_content, encoding="utf-8")
            console.print(f"[green]OK[/green] Saved ranking to {output}")
            # Offer to open HTML in browser
            if format == "html" and sys.stdin.isatty():
                if click.confirm("\nWould you like to open it in your browser now?", default=False):
                    import webbrowser

                    file_path: Path = output.resolve()
                    webbrowser.open(file_path.as_uri())
                    console.print("[green]OK[/green] Opened in browser")
        elif format in ["html", "xml", "json"]:
            # For HTML/XML/JSON, auto-save to a default file like distill does
            if sys.stdin.isatty():  # Interactive mode
                import re
                from datetime import datetime

                # Create filename from prompt
                safe_prompt: str = re.sub(r"[^\w\s-]", "", prompt[:30]).strip()
                safe_prompt = re.sub(r"[-\s]+", "-", safe_prompt)
                timestamp: str = datetime.now().strftime("%Y%m%d_%H%M%S")

                # Determine file extension
                ext: str = format.lower()
                if ext == "html":
                    ext = "html"
                elif ext == "xml":
                    ext = "xml"
                else:  # json
                    ext = "json"

                default_file: Path = Path(f"tenets_rank_{safe_prompt}_{timestamp}.{ext}")
                default_file.write_text(output_content, encoding="utf-8")

                console.print(
                    f"[green]OK[/green] {format.upper()} output saved to [cyan]{default_file}[/cyan]"
                )
                console.print(f"[dim]File size:[/dim] {len(output_content):,} bytes")

                # Offer to open in browser for HTML, or folder for XML/JSON
                if format == "html":
                    if click.confirm(
                        "\nWould you like to open it in your browser now?", default=False
                    ):
                        import webbrowser

                        file_path = default_file.resolve()
                        webbrowser.open(file_path.as_uri())
                        console.print("[green]OK[/green] Opened in browser")
                    else:
                        console.print(
                            "[cyan]Tip:[/cyan] Open the file in a browser or use --output to specify a different path"
                        )
                # For XML/JSON, offer to open the folder
                elif click.confirm(
                    f"\nWould you like to open the folder containing the {format.upper()} file?",
                    default=False,
                ):
                    import platform
                    import webbrowser

                    folder: Path = default_file.parent.resolve()
                    if platform.system() == "Windows":
                        import os

                        os.startfile(folder)
                    elif platform.system() == "Darwin":  # macOS
                        import subprocess

                        subprocess.run(["open", folder], check=False)
                    else:  # Linux
                        import subprocess

                        subprocess.run(["xdg-open", folder], check=False)
                    console.print(f"[green]OK[/green] Opened folder: {folder}")
            else:
                # Non-interactive mode: print raw output
                print(output_content)
        elif format == "markdown" or format == "tree":
            console.print(output_content)
        else:
            print(output_content)

        # Check if we should copy to clipboard
        do_copy: bool = copy
        try:
            # Check config flag for auto-copy (similar to distill command)
            cfg: Any = getattr(tenets_instance, "config", None)
            if cfg and getattr(getattr(cfg, "output", None), "copy_on_rank", False):
                do_copy = True
        except Exception:
            pass

        # Copy to clipboard if requested or config enabled
        if do_copy and pyperclip:
            # Create simple file list for clipboard
            clip_content: str
            if show_scores:
                clip_content = "\n".join(
                    f"{f.path} ({f.relevance_score:.3f})" for f in ranked_files
                )
            else:
                clip_content = "\n".join(str(f.path) for f in ranked_files)
            pyperclip.copy(clip_content)
            console.print("[green]OK[/green] Copied file list to clipboard")

        # Show stats if requested
        if show_stats:
            # Stats not available from rank_files yet
            console.print("[yellow]Stats not available yet[/yellow]")

        timer.stop()

    except Exception as e:
        console.print(f"[red]Error:[/red] {e!s}")
        if verbose:
            console.print_exception()
        sys.exit(1)