diff --git a/misc/qet-mcp/qet_mcp.py b/misc/qet-mcp/qet_mcp.py index e6d392a1c..7c0b70431 100755 --- a/misc/qet-mcp/qet_mcp.py +++ b/misc/qet-mcp/qet_mcp.py @@ -1072,6 +1072,23 @@ def _user_collections() -> dict: return found +def _keep_tail(text: str, tail: int) -> str: + """The last `tail` characters of a run's output, but every line an edit + script reported through its marker kept whole, wherever it stands. + + A large qet_edit logs far more than `tail` characters; cut from the + front, it lost the capability report the script makes first, and the + tool then said the binary had never run the script.""" + if len(text) <= tail: + return text + marked = [ln for ln in text.splitlines() if "QETEDIT " in ln] + rest = text[-tail:] + if not marked: + return rest + kept = set(rest.splitlines()) + return "\n".join([ln for ln in marked if ln not in kept] + [rest]) + + def _run_qet(binary: str, args: list[str], timeout: int = 180, elements_dir: str | None = None, script: str | None = None, tail: int = 4000, @@ -1150,7 +1167,7 @@ def _run_qet(binary: str, args: list[str], timeout: int = 180, "hint": "a modal dialog during load will hang a headless " "run; check the project's format version"} result = {"ok": p.returncode == 0, "exit_code": p.returncode, - "stdout": p.stdout[-tail:], "stderr": p.stderr[-tail:]} + "stdout": _keep_tail(p.stdout, tail), "stderr": _keep_tail(p.stderr, tail)} # QElectroTech refuses --run when scripting is switched off, which # it is by default. Its own message is clear but French, and it # names a settings dialog that nobody driving this server is diff --git a/misc/qet-mcp/test_qet_mcp.py b/misc/qet-mcp/test_qet_mcp.py index 51a010cd6..e979676c8 100644 --- a/misc/qet-mcp/test_qet_mcp.py +++ b/misc/qet-mcp/test_qet_mcp.py @@ -6673,6 +6673,23 @@ class ZippedProjects(unittest.TestCase): self.assertNotEqual(*rotated[0]["orientation"]) +class TestKeepTail(unittest.TestCase): + """A large edit logs more than the kept tail; the script's own report + lines must survive the cut, or qet_edit says the script never ran.""" + + def test_short_output_is_untouched(self): + self.assertEqual(m._keep_tail("a\nb", 100), "a\nb") + + def test_marker_lines_survive_the_cut(self): + first = 'QETEDIT {"kind": "capabilities", "missing": []}' + text = first + "\n" + "x" * 1000 + "\n" + 'QETEDIT {"kind": "save", "result": true}' + kept = m._keep_tail(text, 100) + self.assertIn(first, kept) + self.assertIn('"kind": "save"', kept) + self.assertEqual(kept.count('"kind": "save"'), 1) + self.assertEqual(m._parse_script_output(kept).get("missing_methods"), []) + + if __name__ == "__main__": print(f"binary : {BINARY or '(none: integration tests skipped)'}") print(f"elements : {ELEMENTS or '(none)'}")