tenseleyflow/loader / a6df5b8

Browse files

Strengthen repeated HTML write retries

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
a6df5b89cf530e0875e611f9b276a367d9efd423
Parents
6d1d55c
Tree
b4d9e2d

2 changed files

StatusFile+-
M src/loader/runtime/repair.py 36 0
M tests/test_repair.py 60 0
src/loader/runtime/repair.pymodified
@@ -442,6 +442,13 @@ class ResponseRepairer:
442442
         )
443443
         if html_starter_line:
444444
             lines.append(html_starter_line)
445
+        html_payload_line = self._known_minimal_html_payload_line(
446
+            concrete_target,
447
+            outline_label=outline_label,
448
+            retry_number=retry_number,
449
+        )
450
+        if html_payload_line:
451
+            lines.append(html_payload_line)
445452
         if (
446453
             not compact_retry
447454
             and _should_encourage_initial_version(
@@ -1244,6 +1251,13 @@ class ResponseRepairer:
12441251
         )
12451252
         if html_starter_line:
12461253
             lines.append(html_starter_line)
1254
+        html_payload_line = self._known_minimal_html_payload_line(
1255
+            target,
1256
+            outline_label=outline_label,
1257
+            retry_number=retry_number,
1258
+        )
1259
+        if html_payload_line:
1260
+            lines.append(html_payload_line)
12471261
 
12481262
     def _infer_pending_item_output_target(
12491263
         self,
@@ -1491,6 +1505,28 @@ class ResponseRepairer:
14911505
             "sections with short body text, and a back link to `../index.html`."
14921506
         )
14931507
 
1508
+    def _known_minimal_html_payload_line(
1509
+        self,
1510
+        target: Path,
1511
+        *,
1512
+        outline_label: str | None,
1513
+        retry_number: int,
1514
+    ) -> str | None:
1515
+        if retry_number < 5:
1516
+            return None
1517
+        if target.suffix.lower() not in {".html", ".htm"}:
1518
+            return None
1519
+
1520
+        label = outline_label.strip() if outline_label and outline_label.strip() else target.stem
1521
+        return (
1522
+            "If blanking continues, use this minimal starter payload shape inside the `write` call now: "
1523
+            f"`<!DOCTYPE html><html lang=\"en\"><head><meta charset=\"UTF-8\"><meta name=\"viewport\" "
1524
+            f"content=\"width=device-width, initial-scale=1.0\"><title>{label}</title></head><body>"
1525
+            f"<div class=\"container\"><h1>{label}</h1><p>...</p><h2>Overview</h2><p>...</p>"
1526
+            f"<h2>Key Steps</h2><p>...</p><p><a href=\"../index.html\">← Back to Main Guide Index</a></p>"
1527
+            "</div></body></html>` and refine it later."
1528
+        )
1529
+
14941530
     def _best_known_root_html_scaffold(self, target: Path) -> Path | None:
14951531
         normalized_target = target.expanduser().resolve(strict=False)
14961532
         if normalized_target.suffix.lower() not in {".html", ".htm"}:
tests/test_repair.pymodified
@@ -1355,6 +1355,66 @@ def test_late_first_substantive_retry_stays_lean(
13551355
     assert "Write a compact but real initial version of this file now" not in decision.retry_message
13561356
 
13571357
 
1358
+def test_repeated_first_substantive_retry_includes_minimal_payload_shape(
1359
+    temp_dir: Path,
1360
+) -> None:
1361
+    context = build_context(
1362
+        temp_dir=temp_dir,
1363
+        use_react=False,
1364
+    )
1365
+    repairer = ResponseRepairer(context)
1366
+
1367
+    guide_root = temp_dir / "guides" / "nginx"
1368
+    chapters = guide_root / "chapters"
1369
+    chapters.mkdir(parents=True)
1370
+    index_path = guide_root / "index.html"
1371
+    index_path.write_text(
1372
+        "\n".join(
1373
+            [
1374
+                "<html>",
1375
+                '<a href="chapters/01-introduction.html">Chapter 1: Introduction to Nginx</a>',
1376
+                "</html>",
1377
+            ]
1378
+        )
1379
+        + "\n"
1380
+    )
1381
+
1382
+    implementation_plan = temp_dir / "implementation.md"
1383
+    implementation_plan.write_text(
1384
+        "\n".join(
1385
+            [
1386
+                "# Implementation Plan",
1387
+                "",
1388
+                "## File Changes",
1389
+                f"- `{guide_root}/`",
1390
+                f"- `{chapters}/`",
1391
+                f"- `{index_path}`",
1392
+                "",
1393
+            ]
1394
+        )
1395
+    )
1396
+
1397
+    dod = create_definition_of_done("Create a multi-file nginx guide.")
1398
+    dod.implementation_plan = str(implementation_plan)
1399
+    dod.touched_files.append(str(index_path))
1400
+    dod.completed_items.append("Develop the main index.html file with proper structure")
1401
+    dod.pending_items.append("Create chapter files following the established pattern")
1402
+
1403
+    decision = repairer.handle_empty_response(
1404
+        task="Create a multi-file nginx guide.",
1405
+        original_task=None,
1406
+        empty_retry_count=5,
1407
+        max_empty_retries=6,
1408
+        dod=dod,
1409
+    )
1410
+
1411
+    assert decision.should_continue is True
1412
+    assert decision.retry_message is not None
1413
+    assert "If blanking continues, use this minimal starter payload shape" in decision.retry_message
1414
+    assert "<title>Chapter 1: Introduction to Nginx</title>" in decision.retry_message
1415
+    assert "../index.html" in decision.retry_message
1416
+
1417
+
13581418
 def test_first_substantive_retry_activates_on_first_empty_turn(
13591419
     temp_dir: Path,
13601420
 ) -> None: