Skip to content

Commit 799b594

Browse files
committed
Remove --impact, --blast-radius, --edge-types from graph subcommand
Further simplification: graph subcommand now has only --format, --level, and --summary. Removed point analysis flags (--impact, --blast-radius) and --edge-types filter. The underlying analytics functions remain in the codebase for use by --diff mode. https://claude.ai/code/session_013MyvbfbFgB4vAtRn9NgEUD
1 parent 19eec4b commit 799b594

File tree

3 files changed

+4
-112
lines changed

3 files changed

+4
-112
lines changed

src/treemapper/cli.py

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -124,9 +124,6 @@ class GraphArgs:
124124
format: str = "mermaid"
125125
summary: bool = False
126126
level: str = "directory"
127-
edge_types: list[str] | None = None
128-
impact: str | None = None
129-
blast_radius: str | None = None
130127

131128

132129
@dataclass
@@ -240,13 +237,6 @@ def _build_graph_parser() -> argparse.ArgumentParser:
240237
default="directory",
241238
help="Granularity level for graph operations (default: directory)",
242239
)
243-
graph_parser.add_argument(
244-
"--edge-types",
245-
default=None,
246-
help="Comma-separated edge types to include (e.g., semantic,config)",
247-
)
248-
graph_parser.add_argument("--impact", default=None, metavar="FILE", help="Show impact subgraph for a file")
249-
graph_parser.add_argument("--blast-radius", default=None, metavar="FILE", help="Estimate blast radius for a file")
250240
return graph_parser
251241

252242

@@ -352,7 +342,6 @@ def _build_graph_parsed_args(args: argparse.Namespace) -> ParsedArgs:
352342
ignore_file = _resolve_ignore_file(args.ignore, root_dir)
353343
whitelist_file = _resolve_whitelist_file(args.whitelist, root_dir)
354344
verbosity = "error" if args.quiet else args.log_level
355-
edge_types = [t.strip() for t in args.edge_types.split(",")] if args.edge_types else None
356345

357346
return ParsedArgs(
358347
root_dir=root_dir,
@@ -373,9 +362,6 @@ def _build_graph_parsed_args(args: argparse.Namespace) -> ParsedArgs:
373362
format=args.format,
374363
summary=args.summary,
375364
level=args.level,
376-
edge_types=edge_types,
377-
impact=args.impact,
378-
blast_radius=args.blast_radius,
379365
),
380366
)
381367

src/treemapper/treemapper.py

Lines changed: 4 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -107,8 +107,7 @@ def _is_graph_mode(args: ParsedArgs) -> bool:
107107
def _format_cycles(g: GraphArgs, pg: Any) -> str:
108108
from .diffctx.graph_analytics import detect_cycles
109109

110-
edge_filter = set(g.edge_types) if g.edge_types else {"semantic"}
111-
cycles = detect_cycles(pg, level=g.level, edge_types=edge_filter)
110+
cycles = detect_cycles(pg, level=g.level, edge_types={"semantic"})
112111
if not cycles:
113112
return "No dependency cycles detected."
114113
lines = [f"{len(cycles)} dependency cycle(s) detected:\n"]
@@ -121,8 +120,7 @@ def _format_cycles(g: GraphArgs, pg: Any) -> str:
121120
def _format_hotspots(g: GraphArgs, pg: Any) -> str:
122121
from .diffctx.graph_analytics import hotspots
123122

124-
edge_filter = set(g.edge_types) if g.edge_types else set(_ARCHITECTURAL_EDGE_TYPES)
125-
hot = hotspots(pg, top=10, edge_types=edge_filter)
123+
hot = hotspots(pg, top=10, edge_types=set(_ARCHITECTURAL_EDGE_TYPES))
126124
lines = [f"Top {len(hot)} hotspots:"]
127125
for rank, (name, score, details) in enumerate(hot, 1):
128126
lines.append(f" {rank}. {name} score={score} out_degree={details['out_degree']} churn={details['churn']}")
@@ -132,8 +130,7 @@ def _format_hotspots(g: GraphArgs, pg: Any) -> str:
132130
def _format_metrics(g: GraphArgs, pg: Any) -> str:
133131
from .diffctx.graph_analytics import coupling_metrics
134132

135-
edge_filter = set(g.edge_types) if g.edge_types else set(_ARCHITECTURAL_EDGE_TYPES)
136-
metrics = coupling_metrics(pg, level=g.level, edge_types=edge_filter)
133+
metrics = coupling_metrics(pg, level=g.level, edge_types=set(_ARCHITECTURAL_EDGE_TYPES))
137134
lines = [f"Module metrics ({g.level} level):"]
138135
for m in metrics:
139136
flags = ""
@@ -148,52 +145,6 @@ def _format_metrics(g: GraphArgs, pg: Any) -> str:
148145
return "\n".join(lines)
149146

150147

151-
def _format_impact(g: GraphArgs, pg: Any) -> str:
152-
from pathlib import Path
153-
154-
from .diffctx.ppr import personalized_pagerank
155-
from .diffctx.project_graph import _relative_path
156-
157-
assert g.impact is not None
158-
seed_path = Path(g.impact).resolve()
159-
seed_fids = {fid for fid in pg.fragments if fid.path.resolve() == seed_path}
160-
if not seed_fids:
161-
logger.error("File '%s' not found in project graph", g.impact)
162-
sys.exit(1)
163-
scores = personalized_pagerank(
164-
pg.graph, seeds=set(seed_fids), alpha=0.5, seed_weights={fid: 1.0 / len(seed_fids) for fid in seed_fids}
165-
)
166-
ranked = sorted(scores.items(), key=lambda x: -x[1])
167-
lines = [f"Impact subgraph for {g.impact}:"]
168-
seen_files: set[str] = set()
169-
for fid, score in ranked[:30]:
170-
rel = _relative_path(fid.path, pg.root_dir)
171-
if rel not in seen_files:
172-
seen_files.add(rel)
173-
lines.append(f" {rel} relevance={score:.4f}")
174-
return "\n".join(lines)
175-
176-
177-
def _format_blast_radius(g: GraphArgs, pg: Any) -> str:
178-
from pathlib import Path
179-
180-
from .diffctx.graph_analytics import blast_radius
181-
182-
assert g.blast_radius is not None
183-
seed_path = Path(g.blast_radius).resolve()
184-
result = blast_radius(pg, seed_files=[seed_path])
185-
lines = [f"Blast radius for {g.blast_radius}:"]
186-
for key, entries in result.items():
187-
if key == "summary":
188-
lines.append(f"\n Summary: {', '.join(e[0] for e in entries)}")
189-
else:
190-
depth_num = key.replace("depth_", "")
191-
lines.append(f"\n Depth {depth_num}: {len(entries)} file(s)")
192-
for name, count in entries:
193-
lines.append(f" {name} ({count} fragment(s))")
194-
return "\n".join(lines)
195-
196-
197148
def _graph_to_string(pg: Any, fmt: str, level: str = "directory") -> str:
198149
from .diffctx.graph_analytics import quotient_graph, to_mermaid
199150
from .diffctx.graph_export import graph_to_graphml_string, graph_to_json_string
@@ -227,13 +178,8 @@ def _handle_graph_mode(args: ParsedArgs) -> str:
227178
parts.append(_format_cycles(g, pg))
228179
parts.append(_format_hotspots(g, pg))
229180
parts.append(_format_metrics(g, pg))
230-
if g.impact:
231-
parts.append(_format_impact(g, pg))
232-
if g.blast_radius:
233-
parts.append(_format_blast_radius(g, pg))
234181

235-
has_analysis_flag = any([g.summary, g.impact, g.blast_radius])
236-
if not has_analysis_flag:
182+
if not g.summary:
237183
parts.append(_graph_to_string(pg, g.format, level=g.level))
238184

239185
return "\n".join(parts) + "\n" if parts else ""

tests/test_graph.py

Lines changed: 0 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
from tests.framework.pygit2_backend import Pygit2Repo
1010
from treemapper.diffctx.graph_analytics import (
1111
QuotientGraph,
12-
blast_radius,
1312
coupling_metrics,
1413
detect_cycles,
1514
hotspots,
@@ -541,45 +540,6 @@ def test_directory_level_names(self, graph_project):
541540
assert any("src" in n for n in names)
542541

543542

544-
class TestGraphBlastRadius:
545-
def test_central_file_has_dependents(self, graph_project):
546-
pg = _build_graph(graph_project)
547-
models_path = graph_project / "src" / "models.py"
548-
result = blast_radius(pg, seed_files=[models_path])
549-
all_entries = []
550-
for key in result:
551-
if key.startswith("depth_"):
552-
all_entries.extend(result[key])
553-
if pg.edge_count > 0:
554-
assert len(all_entries) > 0
555-
556-
def test_leaf_file_zero_radius(self, graph_project):
557-
pg = _build_graph(graph_project)
558-
helpers_path = graph_project / "utils" / "helpers.py"
559-
result = blast_radius(pg, seed_files=[helpers_path])
560-
for key in result:
561-
if key.startswith("depth_"):
562-
for name, count in result[key]:
563-
assert not name.startswith("utils/helpers")
564-
565-
def test_summary_present(self, graph_project):
566-
pg = _build_graph(graph_project)
567-
models_path = graph_project / "src" / "models.py"
568-
result = blast_radius(pg, seed_files=[models_path])
569-
assert "summary" in result
570-
summary_text = " ".join(e[0] for e in result["summary"])
571-
assert "reachable_files" in summary_text
572-
assert "reachable_fragments" in summary_text
573-
574-
def test_nonexistent_file_empty(self, graph_project):
575-
pg = _build_graph(graph_project)
576-
fake_path = graph_project / "nonexistent.py"
577-
result = blast_radius(pg, seed_files=[fake_path])
578-
for key in result:
579-
if key.startswith("depth_"):
580-
assert len(result[key]) == 0
581-
582-
583543
class TestGraphExportGraphML:
584544
def test_valid_xml(self, graph_project):
585545
pg = _build_graph(graph_project)

0 commit comments

Comments
 (0)