66import sys
77import time
88from pathlib import Path
9- from typing import Optional
109
1110import typer
1211from rich .console import Console
1312from rich .panel import Panel
1413from rich .progress import Progress , SpinnerColumn , TextColumn
1514
16- from codectx .config .defaults import CACHE_DIR_NAME
17-
1815from codectx import __version__
16+ from codectx .config .defaults import CACHE_DIR_NAME
1917
2018app = typer .Typer (
2119 name = "codectx" ,
@@ -47,7 +45,7 @@ def analyze(
4745 "-o" ,
4846 help = "Output file path (default: CONTEXT.md)." ,
4947 ),
50- since : Optional [ str ] = typer .Option (
48+ since : str | None = typer .Option (
5149 None ,
5250 "--since" ,
5351 help = "Include recent changes since this date (e.g. '7 days ago')." ,
@@ -63,7 +61,7 @@ def analyze(
6361 "--no-git" ,
6462 help = "Skip git metadata collection." ,
6563 ),
66- query : Optional [ str ] = typer .Option (
64+ query : str | None = typer .Option (
6765 None ,
6866 "--query" ,
6967 "-q" ,
@@ -79,7 +77,7 @@ def analyze(
7977 "--layers" ,
8078 help = "Generate layered context output." ,
8179 ),
82- extra_roots : Optional [ list [Path ]] = typer .Option (
80+ extra_roots : list [Path ] | None = typer .Option (
8381 None ,
8482 "--extra-root" ,
8583 help = "Additional root directories for multi-root analysis." ,
@@ -180,7 +178,7 @@ def benchmark(
180178
181179 # Rank
182180 t0 = time .perf_counter ()
183- from codectx .ranker .git_meta import collect_git_metadata , collect_recent_changes
181+ from codectx .ranker .git_meta import collect_git_metadata
184182 from codectx .ranker .scorer import score_files
185183
186184 git_meta = collect_git_metadata (files , config .root , config .no_git )
@@ -299,22 +297,26 @@ def search(
299297) -> None :
300298 """Search the codebase semantically."""
301299 _setup_logging (verbose )
302-
300+
303301 try :
304302 from codectx .ranker .semantic import is_available , semantic_score
303+
305304 if not is_available ():
306- console .print ("[red]Semantic search is not installed. Run: pip install codectx[semantic][/]" )
305+ console .print (
306+ "[red]Semantic search is not installed. Run: pip install codectx[semantic][/]"
307+ )
307308 raise typer .Exit (1 )
308-
309- from codectx .walker import walk
310- from codectx .parser .treesitter import parse_files
311- from codectx .config .loader import load_config
309+
312310 import hashlib
311+
313312 from codectx .cache import Cache
314-
313+ from codectx .config .loader import load_config
314+ from codectx .parser .treesitter import parse_files
315+ from codectx .walker import walk
316+
315317 config = load_config (root )
316318 files = walk (config .root , config .extra_ignore )
317-
319+
318320 # Parse files with cache
319321 cache = Cache (config .root )
320322 parse_results = {}
@@ -329,9 +331,13 @@ def search(
329331 parse_results [f ] = cached
330332 else :
331333 uncached_files .append (f )
332-
334+
333335 if uncached_files :
334- with Progress (SpinnerColumn (), TextColumn ("[progress.description]{task.description}" ), transient = True ) as progress :
336+ with Progress (
337+ SpinnerColumn (),
338+ TextColumn ("[progress.description]{task.description}" ),
339+ transient = True ,
340+ ) as progress :
335341 progress .add_task ("Parsing uncached files..." , total = None )
336342 fresh = parse_files (uncached_files )
337343 for f , result in fresh .items ():
@@ -342,28 +348,30 @@ def search(
342348 fhash = ""
343349 cache .put_parse_result (f , fhash , result )
344350 cache .save ()
345-
351+
346352 cache_dir = config .root / ".codectx_cache" / "embeddings"
347353 cache_dir .mkdir (parents = True , exist_ok = True )
348-
349- with Progress (SpinnerColumn (), TextColumn ("[progress.description]{task.description}" ), transient = True ) as progress :
354+
355+ with Progress (
356+ SpinnerColumn (), TextColumn ("[progress.description]{task.description}" ), transient = True
357+ ) as progress :
350358 progress .add_task ("Computing semantic relevance..." , total = None )
351359 scores = semantic_score (query , files , parse_results , cache_dir )
352-
360+
353361 sorted_files = sorted (scores .items (), key = lambda x : x [1 ], reverse = True )
354-
362+
355363 console .print (f"\n [bold cyan]Search Results for:[/] '{ query } '\n " )
356-
364+
357365 found = False
358366 for f , score in sorted_files [:limit ]:
359367 if score > 0.0 :
360368 rel = f .relative_to (config .root )
361369 console .print (f"[bold green]{ rel } [/] (score: { score :.3f} )" )
362370 found = True
363-
371+
364372 if not found :
365373 console .print ("[yellow]No relevant files found.[/]" )
366-
374+
367375 except Exception as exc :
368376 console .print (f"[red]Error during search:[/] { exc } " )
369377 raise typer .Exit (1 )
@@ -453,14 +461,16 @@ def main(
453461
454462from dataclasses import dataclass
455463
464+
456465@dataclass
457466class PipelineMetrics :
458467 output_path : Path
459468 files_scanned : int
460469 original_tokens : int
461470 context_tokens : int
462471
463- def _run_pipeline (config : "object" ) -> PipelineMetrics :
472+
473+ def _run_pipeline (config : object ) -> PipelineMetrics :
464474 """Run the full codectx pipeline and return the output metrics."""
465475 import hashlib
466476
@@ -470,7 +480,7 @@ def _run_pipeline(config: "object") -> PipelineMetrics:
470480 from codectx .config .loader import Config
471481 from codectx .graph .builder import build_dependency_graph
472482 from codectx .output .formatter import format_context , write_context_file
473- from codectx .parser .treesitter import parse_file , parse_files
483+ from codectx .parser .treesitter import parse_files
474484 from codectx .ranker .git_meta import collect_git_metadata , collect_recent_changes
475485 from codectx .ranker .scorer import score_files
476486 from codectx .safety import confirm_sensitive_files , find_sensitive_files
@@ -607,6 +617,7 @@ def _run_pipeline(config: "object") -> PipelineMetrics:
607617
608618 if config .layers :
609619 from codectx .output .formatter import write_layer_files
620+
610621 write_layer_files (content_sections , config .root )
611622 output_path = config .root / "FULL_CONTEXT.md"
612623 write_context_file (content_sections , output_path )
@@ -620,6 +631,7 @@ def _run_pipeline(config: "object") -> PipelineMetrics:
620631 progress .update (task , description = "Done!" )
621632
622633 from codectx .compressor .budget import count_tokens
634+
623635 original_tokens = sum (count_tokens (pr .raw_source ) for pr in parse_results .values () if pr )
624636
625637 return PipelineMetrics (
0 commit comments