From cd34966b4fca20bac4ae65362934ab3fdbe8a3b6 Mon Sep 17 00:00:00 2001 From: kuyua9 <948423378@qq.com> Date: Fri, 15 May 2026 00:36:19 +0800 Subject: [PATCH 1/2] fix: save comparison HTML artifacts --- skills/last30days/scripts/last30days.py | 26 ++++++++++++++++++++++--- tests/test_html_render.py | 20 +++++++++++++++++++ 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/skills/last30days/scripts/last30days.py b/skills/last30days/scripts/last30days.py index d8cb7cf..9a9b1c7 100644 --- a/skills/last30days/scripts/last30days.py +++ b/skills/last30days/scripts/last30days.py @@ -97,11 +97,13 @@ def save_output( save_dir: str, suffix: str = "", synthesis_md: str | None = None, + topic_override: str | None = None, + rendered_content: str | None = None, ) -> Path: from datetime import datetime path = Path(save_dir).expanduser().resolve() path.mkdir(parents=True, exist_ok=True) - slug = slugify(report.topic) + slug = slugify(topic_override or report.topic) extension = "json" if emit == "json" else "html" if emit == "html" else "md" raw_label = "raw-html" if emit == "html" else "raw" suffix_part = f"-{suffix}" if suffix else "" @@ -110,7 +112,9 @@ def save_output( out_path = path / f"{slug}-{raw_label}{suffix_part}-{datetime.now().strftime('%Y-%m-%d')}.{extension}" # Markdown saves keep the complete debug artifact. JSON and HTML preserve # their requested wire format so file extensions match their content. - if emit in {"json", "html"}: + if rendered_content is not None: + content = rendered_content + elif emit in {"json", "html"}: content = emit_output(report, emit, synthesis_md=synthesis_md) else: content = render.render_full(report) @@ -171,6 +175,10 @@ def emit_comparison_output( raise SystemExit(f"Unsupported emit mode: {emit}") +def comparison_topic(entity_reports: list[tuple[str, schema.Report]]) -> str: + return " vs ".join(label for label, _ in entity_reports) + + def compute_save_path_display(save_dir: str, topic: str, suffix: str, emit: str) -> str: """Compute the user-friendly save path string that will be shown in the footer. @@ -875,8 +883,13 @@ def main() -> int: fun_level = config.get("FUN_LEVEL", "medium").lower() footer_save_path = None if args.save_dir: + save_topic = ( + comparison_topic(entity_reports) + if entity_reports and args.emit == "html" + else report.topic + ) footer_save_path = compute_save_path_display( - args.save_dir, report.topic, args.save_suffix or "", args.emit + args.save_dir, save_topic, args.save_suffix or "", args.emit ) # Signal to render_compact whether pre-research flags were supplied. @@ -911,12 +924,19 @@ def main() -> int: ) if args.save_dir: # Save the main topic's raw file (single-entity or comparison main). + save_topic = ( + comparison_topic(entity_reports) + if entity_reports and args.emit == "html" + else None + ) save_path = save_output( report, args.emit, args.save_dir, suffix=args.save_suffix or "", synthesis_md=synthesis_md, + topic_override=save_topic, + rendered_content=rendered if entity_reports and args.emit == "html" else None, ) sys.stderr.write(f"[last30days] Saved output to {save_path}\n") # Competitor / vs-mode: also save a per-entity raw file for each peer. diff --git a/tests/test_html_render.py b/tests/test_html_render.py index 780884d..70a32cf 100644 --- a/tests/test_html_render.py +++ b/tests/test_html_render.py @@ -277,6 +277,26 @@ class HtmlCliIntegrationTests(unittest.TestCase): path = cli.compute_save_path_display("/tmp", report.topic, "v3", "html") self.assertTrue(path.endswith("/ai-agent-frameworks-raw-html-v3.html")) + def test_save_output_can_persist_comparison_html(self): + reports = [ + ("OpenClaw", _report("OpenClaw", ["Containers"])), + ("Hermes", _report("Hermes", ["Memory"])), + ] + rendered = cli.emit_comparison_output(reports, "html") + with tempfile.TemporaryDirectory() as tmpdir: + path = cli.save_output( + reports[0][1], + "html", + tmpdir, + topic_override=cli.comparison_topic(reports), + rendered_content=rendered, + ) + self.assertEqual("openclaw-vs-hermes-raw-html.html", path.name) + saved = path.read_text(encoding="utf-8") + self.assertIn("last30days · OpenClaw vs Hermes", saved) + self.assertIn("comparing 2: OpenClaw, Hermes", saved) + self.assertNotIn("last30days · OpenClaw", saved) + if __name__ == "__main__": unittest.main() From 4d4ac97ffb35be03d91b26ce7cb2915e1c960792 Mon Sep 17 00:00:00 2001 From: Trevin Chow Date: Sat, 16 May 2026 19:32:49 -0700 Subject: [PATCH 2/2] refactor: hoist comparison-html gate into a single condition (Greptile DRY) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Greptile flagged that `entity_reports and args.emit == "html"` appeared in two places — once when computing the footer display path, again when calling save_output. The else-branches differ between the two callsites (the display needs `report.topic` as fallback; the save call needs `None` so save_output falls back to the report's own topic), so collapsing into one shared expression would be wrong, but hoisting just the condition into a single `is_comparison_html` bool eliminates the risk of drift while keeping the two callsites' fallback semantics distinct. --- skills/last30days/scripts/last30days.py | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/skills/last30days/scripts/last30days.py b/skills/last30days/scripts/last30days.py index 9a9b1c7..554c7c4 100644 --- a/skills/last30days/scripts/last30days.py +++ b/skills/last30days/scripts/last30days.py @@ -881,15 +881,15 @@ def main() -> int: pass fun_level = config.get("FUN_LEVEL", "medium").lower() + # Comparison HTML is the one case where the saved file's title and content + # have to be overridden away from the leading entity's report. Compute the + # gate once so the footer-display and save-output paths can't disagree. + is_comparison_html = bool(entity_reports) and args.emit == "html" footer_save_path = None if args.save_dir: - save_topic = ( - comparison_topic(entity_reports) - if entity_reports and args.emit == "html" - else report.topic - ) + save_topic_for_display = comparison_topic(entity_reports) if is_comparison_html else report.topic footer_save_path = compute_save_path_display( - args.save_dir, save_topic, args.save_suffix or "", args.emit + args.save_dir, save_topic_for_display, args.save_suffix or "", args.emit ) # Signal to render_compact whether pre-research flags were supplied. @@ -924,19 +924,14 @@ def main() -> int: ) if args.save_dir: # Save the main topic's raw file (single-entity or comparison main). - save_topic = ( - comparison_topic(entity_reports) - if entity_reports and args.emit == "html" - else None - ) save_path = save_output( report, args.emit, args.save_dir, suffix=args.save_suffix or "", synthesis_md=synthesis_md, - topic_override=save_topic, - rendered_content=rendered if entity_reports and args.emit == "html" else None, + topic_override=comparison_topic(entity_reports) if is_comparison_html else None, + rendered_content=rendered if is_comparison_html else None, ) sys.stderr.write(f"[last30days] Saved output to {save_path}\n") # Competitor / vs-mode: also save a per-entity raw file for each peer.