Python code scout: metrics + locally-generated summaries.
It’s useful for AI-agent priming because it extracts code into a dense signal - enough context for an agent to start acting like it “read the code.”
The summaries are actually quite lit; runs locally on your CPU (the model is <200MB) and is good at explaining what the code does, not just what it’s called.
- Scout on itself compresses 5,123 source tokens into 1,628.
- In another larger project: 22,962 into just 6,372! The agent could describe the whole thing technically, predict bugs, and suggest refactors with no additional context from source.
❯ python scout.py --help
usage: scout [-h] [--columns COLUMNS] [--list-columns] [--exclude-dirs EXCLUDE_DIRS] [--ai] [--json] [--mypy] [--jobs JOBS] [--cache CACHE] [--no-cache] [-q] [path]
Static analysis for AI agents
positional arguments:
path Path to analyze
options:
-h, --help show this help message and exit
--columns COLUMNS Columns to display (default: name,lines,summary,h_vol,calls,h_bugs,cc,mi)
--list-columns List all possible columns
--exclude-dirs EXCLUDE_DIRS
Extra dirs to exclude (comma-sep)
--ai AI summaries per symbol
--json JSON output
--mypy Run mypy
--jobs JOBS Parallel workers
--cache CACHE Cache file
--no-cache Disable cache
-q, --quiet Suppress progress
examples:
python scout.py --ai --columns "name,lines,summary,h_vol,calls,h_bugs,cc,mi" scout.py # default columnsOutput example
❯ python scout.py \
--ai \
--columns "name,lines,summary,h_vol,h_bugs,cc,mi,calls" \
scout.py
Loading AI model...
╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────── Project ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Files: 1 Symbols: 34 LOC: 483 (374 source) │
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
NAME LINES SUMMARY H_VOL H_BUGS CC MI CALLS
metric(name: str, desc: str='') 25-30 Decorator to register a metric function as a new metric . 0 0.00 1 100.00 3
metrics_for_columns(columns: List) -> List 32-40 Return metric names needed to produce requested columns . 4.80 0.00 4 73.90 4
_safe(default, fn) 42-44 Call a function and return the result if it fails . 0 0.00 2 100.00 1
_raw(code: str) 46-47 Return the raw code . 0 0.00 1 100.00 2
m_loc(code: str, _) 50-50 Return the number of lines in the code . 0 0.00 1 100.00 3
m_sloc(code: str, _) 53-55 Return the sloc of the code . 0 0.00 2 100.00 2
m_comments(code: str, _) 58-61 Return a dict of comments and the ratio of the comments . 11.60 0.00 2 75.30 4
m_blank(code: str, _) 64-66 Return the blank value of the node . 0 0.00 2 100.00 2
m_cc(code: str, _) 69-71 Return the complexity of the code . 0 0.00 2 100.00 3
m_mi(code: str, _) 74-75 Return the mi value of the given code . 0 0.00 1 100.00 5
m_halstead(code: str, _) 78-83 Return the Halstead total of a sequence of words . 2.00 0.00 2 77.92 8
m_sig(code: str, node) 86-99 Generate a signature for a function . 124.90 0.04 10 56.59 13
m_params(code: str, node) 102-104 Return the number of parameters in the function or async function . 11.60 0.00 3 76.89 3
m_nesting(code: str, node) 107-114 Return a dict of the nesting level of the given node . 22.50 0.01 2 67.55 6
m_calls(code: str, node) 117-118 Returns the number of calls in the given code . 0 0.00 3 100.00 4
m_branches(code: str, node) 121-122 Return the number of branches in the code . 0 0.00 3 100.00 4
m_loops(code: str, node) 125-126 Return a dictionary of loops . 0 0.00 3 100.00 4
m_doc(code: str, node) 129-132 Return a dict with the number of lines and comments in the node s docstring . 2.00 0.00 3 79.05 7
load_ai(quiet=False) 162-177 Load the AI model and return a function that can be used to summarize the sequence of tokens . 2.00 0.00 2 87.35 9
run_mypy(path: Path) -> Dict 182-191 Runs mypy and returns a dict of the number of errors warnings and issues . 57.40 0.02 7 64.03 6
_sha1(b: bytes) -> str 198-198 Return the SHA - 1 hash of a bytes object . 0 0.00 1 100.00 2
load_cache(p: Path) -> Dict 200-205 Load the cache from a JSON file . 11.60 0.00 4 71.19 4
save_cache(p: Path, d: Dict) 207-209 Save a dictionary to a JSON file . 0 0.00 2 100.00 2
should_skip(p: Path, excl: Set) -> bool 214-215 Returns True if p should be skipped . 4.80 0.00 2 88.42 1
extract_imports(tree: ast.AST) -> List 217-225 Extract imports from an ast . AST . 4.80 0.00 8 71.47 7
mod_to_path(root: Path, mod: str) -> Optional[Path] 227-231 Return the path of the module in root if it exists . 23.30 0.01 3 73.05 3
build_import_graph(root: Path, file_imports: Dict[str, List], files: Set) -> Dict[str, List] 233-240 Build a graph of import paths to files . 48.10 0.02 9 65.20 11
analyze_file(path: str, columns: List) -> Dict 245-269 Analyze a file and return a dict of metrics . 66.40 0.02 12 55.13 27
get_all_columns() -> List 274-280 Returns a list of all possible column names . 13.90 0.01 2 73.29 2
format_output(console: Console, symbols: List[Symbol], file_stats: List[FileStats], columns: 282-319 Formats the output of 99.90 0.03 20 66.67 26
List, show_summary: bool) FailureSummary .
format_json(symbols: List[Symbol], file_stats: List[FileStats], import_graph: Dict) 321-331 Formats a list of symbols and file stats into a JSON file . 0 0.00 5 100.00 6
main() 339-480 Entry point for the command line tool . 297.50 0.10 55 47.10 100
Symbol 138-145 A basic syntax tree for a sequence of tokens . 0 0.00 1 100.00 1
FileStats 148-157 Statistics for a n - language language grammar . 0 0.00 1 100.00 1
Columns / metrics (compact reference)
Each row is a column name you can request via --columns. “Source” indicates where the value comes from.
| Column(s) | Meaning | Source |
|---|---|---|
file, name, kind, lines |
Symbol identity + file + line span | Radon visitor metadata |
summary |
Short AI description per symbol | CodeT5 summarizer (--ai) |
fan_in, fan_out |
File dependency counts (in/out degree) | AST import parse → best-effort local import graph |
loc |
Total lines in snippet | splitlines() |
sloc, blank, comments, comment_ratio |
Raw code stats | radon.raw.analyze (+ ratio derived) |
cc |
Cyclomatic complexity | radon.complexity.cc_visit |
mi |
Maintainability index (0–100) | radon.metrics.mi_visit |
h_vol, h_diff, h_effort, h_time, h_bugs |
Halstead totals | radon.metrics.h_visit(...).total.* |
sig, params |
Function signature + param count | AST (ast.unparse, node.args) |
nesting |
Max nesting depth | AST walk (if/for/while/with/try) |
calls, branches, loops |
Call/branch/loop counts | AST walk (Call / If / For+While) |
has_doc, doc_lines |
Docstring presence + line count | AST (ast.get_docstring) |
scout.py --list-columnsprints everything supported by your build.