From 8698a859012b254508bc5bc975262af06e9f7a29 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Wed, 26 Aug 2026 16:32:35 +0000 Subject: [PATCH] Perf: pass iterators instead of materialized lists in AST parsing Changing the parameter type from `list[ast.AST]` to `Iterable[ast.AST]` for recursive L2 AST node traversal functions (`_assignment_callee` and `_collect_return_paths`) and removed the `list()` calls that eagerly materialized the `ast.iter_child_nodes()` generator. Co-authored-by: tachyon-beep <544926+tachyon-beep@users.noreply.github.com> --- .jules/bolt.md | 3 +++ src/wardline/install/block.py | 2 ++ src/wardline/mcp/server.py | 4 +--- src/wardline/scanner/taint/variable_level.py | 18 ++++++++---------- tests/unit/install/test_doctor_pack_grants.py | 4 +--- tests/unit/install/test_mcp_json.py | 8 ++------ tests/unit/mcp/test_server_trust_grants.py | 4 +--- 7 files changed, 18 insertions(+), 25 deletions(-) create mode 100644 .jules/bolt.md diff --git a/.jules/bolt.md b/.jules/bolt.md new file mode 100644 index 00000000..e456735a --- /dev/null +++ b/.jules/bolt.md @@ -0,0 +1,3 @@ +## 2024-05-18 - Replacing `list(ast.iter_child_nodes(node))` with `ast.iter_child_nodes(node)` +**Learning:** `ast.iter_child_nodes()` returns a generator. Realizing this into a list using `list(ast.iter_child_nodes())` allocates unnecessary memory. +**Action:** When working on memory-intensive AST analysis passes, accept `Iterable[ast.AST]` where child nodes are needed, rather than explicit `list[ast.AST]`. diff --git a/src/wardline/install/block.py b/src/wardline/install/block.py index fa250bf4..655b363e 100644 --- a/src/wardline/install/block.py +++ b/src/wardline/install/block.py @@ -31,6 +31,7 @@ _BLOCK_VERSION = "1" + def _compose_body(grant_suffix: str = "", grant_sentence: str = "") -> str: return ( "This project uses **wardline** as its trust-boundary gate. Before handing " @@ -81,6 +82,7 @@ def _pack_guidance(project_root: Path) -> tuple[str, str]: ) return suffix, sentence + _OWN_NS = "wardline" _END_MARKER = f"" _WRITER_MARKER = f"" diff --git a/src/wardline/mcp/server.py b/src/wardline/mcp/server.py index 07fa0c62..a9bbe42c 100644 --- a/src/wardline/mcp/server.py +++ b/src/wardline/mcp/server.py @@ -5103,9 +5103,7 @@ def _grants_merged_arguments(self, arguments: dict[str, Any]) -> dict[str, Any]: if caller_packs is None or ( isinstance(caller_packs, list) and all(isinstance(p, str) for p in caller_packs) ): - merged["trust_packs"] = list( - dict.fromkeys([*(caller_packs or []), *self._default_trusted_packs]) - ) + merged["trust_packs"] = list(dict.fromkeys([*(caller_packs or []), *self._default_trusted_packs])) if self._default_trust_local_packs: caller_local = merged.get("trust_local_packs") # Identity checks, not equality: 0 == False, and masking a caller's diff --git a/src/wardline/scanner/taint/variable_level.py b/src/wardline/scanner/taint/variable_level.py index 3190e60c..61032f6f 100644 --- a/src/wardline/scanner/taint/variable_level.py +++ b/src/wardline/scanner/taint/variable_level.py @@ -33,7 +33,7 @@ from wardline.core.taints import _PROVENANCE_CLASH, RAW_ZONE, TRUST_RANK, TaintState, combine if TYPE_CHECKING: - from collections.abc import Iterator + from collections.abc import Iterable, Iterator # Serialisation sinks — calls that cross the representation boundary. Their # output sheds validation provenance (raw bytes/str), so → UNKNOWN_RAW. This is @@ -2519,7 +2519,7 @@ def compute_return_taint( """ returns: list[tuple[TaintState, str | None, ast.expr]] = [] _collect_return_paths( - list(func_node.body), + func_node.body, function_taint, taint_map, var_taints, @@ -2568,7 +2568,7 @@ def compute_return_callee( """ returns: list[tuple[TaintState, str | None, ast.expr]] = [] _collect_return_paths( - list(func_node.body), + func_node.body, function_taint, taint_map, var_taints, @@ -2589,14 +2589,14 @@ def compute_return_callee( # a direct call. Provenance only — never changes a fire/no-fire decision. for taint, callee, node in returns: if taint == worst and callee is None and isinstance(node, ast.Name): - indirect = _assignment_callee(list(func_node.body), node.id, worst, function_taint, taint_map, var_taints) + indirect = _assignment_callee(func_node.body, node.id, worst, function_taint, taint_map, var_taints) if indirect is not None: return indirect return None def _assignment_callee( - nodes: list[ast.AST], + nodes: Iterable[ast.AST], name: str, worst: TaintState, function_taint: TaintState, @@ -2629,9 +2629,7 @@ def _assignment_callee( and _resolve_expr(node.value, function_taint, taint_map, var_taints) == worst ): result = callee - nested = _assignment_callee( - list(ast.iter_child_nodes(node)), name, worst, function_taint, taint_map, var_taints - ) + nested = _assignment_callee(ast.iter_child_nodes(node), name, worst, function_taint, taint_map, var_taints) if nested is not None: result = nested return result @@ -2648,7 +2646,7 @@ def _return_callee(node: ast.expr) -> str | None: def _collect_return_paths( - nodes: list[ast.AST], + nodes: Iterable[ast.AST], function_taint: TaintState, taint_map: dict[str, TaintState], var_taints: dict[str, TaintState], @@ -2686,7 +2684,7 @@ def _collect_return_paths( _CURRENT_VAR_TYPES.reset(token_types) out.append((taint, _return_callee(node.value), node.value)) _collect_return_paths( - list(ast.iter_child_nodes(node)), + ast.iter_child_nodes(node), function_taint, taint_map, var_taints, diff --git a/tests/unit/install/test_doctor_pack_grants.py b/tests/unit/install/test_doctor_pack_grants.py index 8061c8b8..c39f0e51 100644 --- a/tests/unit/install/test_doctor_pack_grants.py +++ b/tests/unit/install/test_doctor_pack_grants.py @@ -66,9 +66,7 @@ def test_project_mcp_check_accepts_grant_flags(tmp_path: Path, monkeypatch: pyte assert check.ok, check.message -def test_project_mcp_check_names_divergence_not_missing( - tmp_path: Path, monkeypatch: pytest.MonkeyPatch -) -> None: +def test_project_mcp_check_names_divergence_not_missing(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: # A present-but-noncanonical entry is a different failure than an absent one; # "missing wardline server" for a visibly present entry sent the operator # chasing the wrong problem. diff --git a/tests/unit/install/test_mcp_json.py b/tests/unit/install/test_mcp_json.py index c59dbc67..1ccaa6d9 100644 --- a/tests/unit/install/test_mcp_json.py +++ b/tests/unit/install/test_mcp_json.py @@ -549,9 +549,7 @@ def test_repair_preserves_trust_pack_grant_flags(tmp_path: Path, monkeypatch: py "--allow-custom-packs", ] (tmp_path / ".mcp.json").write_text( - json.dumps( - {"mcpServers": {"wardline": {"type": "stdio", "command": "/bin/wardline", "args": list(args)}}} - ), + json.dumps({"mcpServers": {"wardline": {"type": "stdio", "command": "/bin/wardline", "args": list(args)}}}), encoding="utf-8", ) assert merge_mcp_entry(tmp_path) == "unchanged" @@ -581,9 +579,7 @@ def test_repair_preserves_repeated_trust_pack_grants(tmp_path: Path, monkeypatch assert merge_mcp_entry(tmp_path) == "unchanged" -def test_repair_drops_dangling_trust_pack_but_keeps_bare_grant( - tmp_path: Path, monkeypatch: pytest.MonkeyPatch -) -> None: +def test_repair_drops_dangling_trust_pack_but_keeps_bare_grant(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: # A --trust-pack with a missing or flag-shaped value is malformed: it must be # dropped cleanly, and must never swallow the following --allow-custom-packs. monkeypatch.setattr("wardline.install.mcp_json._find_wardline_command", lambda: "/bin/wardline") diff --git a/tests/unit/mcp/test_server_trust_grants.py b/tests/unit/mcp/test_server_trust_grants.py index bbaa9652..59c8bf7c 100644 --- a/tests/unit/mcp/test_server_trust_grants.py +++ b/tests/unit/mcp/test_server_trust_grants.py @@ -24,9 +24,7 @@ def _pack_project(tmp_path: Path) -> Path: proj = tmp_path / "proj" (proj / "scripts").mkdir(parents=True) - (proj / "scripts" / "grantpack.py").write_text( - 'config = {"exclude": ["skipped_by_pack.py"]}\n', encoding="utf-8" - ) + (proj / "scripts" / "grantpack.py").write_text('config = {"exclude": ["skipped_by_pack.py"]}\n', encoding="utf-8") (proj / "weft.toml").write_text(f'[wardline]\npacks = ["{PACK_NAME}"]\n', encoding="utf-8") (proj / "kept.py").write_text("def kept():\n return 1\n", encoding="utf-8") (proj / "skipped_by_pack.py").write_text("def skipped():\n return 1\n", encoding="utf-8")