From 03021cae54ff09a9c9ad477415b27d9351a59c6e Mon Sep 17 00:00:00 2001 From: Ashwani Kharwar Date: Tue, 1 Sep 2026 15:46:49 +0530 Subject: [PATCH] feat: include line numbers in flow results --- tests/test_flow_engine.py | 22 +++++++++++++++++++++- tldrgraph/flow_engine.py | 14 +++++++++++++- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/tests/test_flow_engine.py b/tests/test_flow_engine.py index 546e893..8cb45e2 100644 --- a/tests/test_flow_engine.py +++ b/tests/test_flow_engine.py @@ -384,14 +384,34 @@ def test_trace_path_keeps_the_keys_cli_consumes(engine): def test_format_node_step_shape_is_unchanged(engine): step = engine._format_node_step(API_CTRL) - assert set(step) == {"id", "label", "layer_id", "layer", "file", "is_test", "intent", "input_fields", "output_fields", "fields"} + assert set(step) == { + "id", "label", "layer_id", "layer", "file", "source_location", "line", + "is_test", "intent", "input_fields", "output_fields", "fields", + } assert step["label"] == "OrdersController" + assert step["source_location"] == "L1" + assert step["line"] == 1 def test_render_markdown_table_still_renders(engine): table = FlowEngine.render_markdown_table(engine.trace_path("DeskView")["steps"]) assert "Component / Symbol" in table assert "OrdersController" in table + assert "backend/src/orders/orders.controller.ts:1" in table + + +def test_render_markdown_table_omits_line_suffix_when_unknown(): + table = FlowEngine.render_markdown_table([ + { + "layer": L3, + "label": "NoLineService", + "intent": "No line data", + "file": "backend/src/no-line.service.ts", + "line": None, + } + ]) + assert "backend/src/no-line.service.ts" in table + assert "backend/src/no-line.service.ts:None" not in table def test_export_flows_yaml_round_trips(engine, tmp_path): diff --git a/tldrgraph/flow_engine.py b/tldrgraph/flow_engine.py index 6a11f5e..149c7b9 100644 --- a/tldrgraph/flow_engine.py +++ b/tldrgraph/flow_engine.py @@ -32,6 +32,7 @@ from .hierarchy import is_test_node from .layers import get_registry, layer_id_of from .vector_store import LocalVectorStore +from .visualizer.source import parse_line_number class FlowEngine: @@ -189,6 +190,7 @@ def _format_node_step(self, node_id: str) -> Dict[str, Any]: input_fields = node_data.get("input_fields", []) output_fields = node_data.get("output_fields", []) fields = node_data.get("fields", []) or (list(input_fields) + list(output_fields)) + source_location = node_data.get("source_location") is_test = node_data.get("is_test") if is_test is None: is_test = is_test_node(node_data.get("file", ""), node_data.get("label", "")) @@ -198,6 +200,8 @@ def _format_node_step(self, node_id: str) -> Dict[str, Any]: "layer_id": self._layer_id_of(node_id), "layer": node_data.get("layer", "Unknown"), "file": node_data.get("file", ""), + "source_location": source_location, + "line": parse_line_number(source_location), "is_test": bool(is_test), "intent": node_data.get("intent") or node_data.get("summary", ""), "input_fields": input_fields, @@ -215,6 +219,14 @@ def export_flows_yaml(self, flows: List[Dict[str, Any]], filename: str = ".tldrg @staticmethod def render_markdown_table(steps: List[Dict[str, Any]]) -> str: headers = ["Layer", "Component / Symbol", "Intent & Action", "Input Fields", "Output Fields", "File Location"] + + def file_location(step: Dict[str, Any]) -> str: + file_path = step.get("file", "") + line = step.get("line") + if file_path and line: + return f"{file_path}:{line}" + return file_path + rows = [ [ s.get("layer", ""), @@ -222,7 +234,7 @@ def render_markdown_table(steps: List[Dict[str, Any]]) -> str: s.get("intent", ""), ", ".join(s.get("input_fields", [])) if s.get("input_fields") else (", ".join(s.get("fields", [])) if s.get("fields") else "-"), ", ".join(s.get("output_fields", [])) if s.get("output_fields") else "-", - s.get("file", ""), + file_location(s), ] for s in steps ]