@@ -1440,6 +1440,62 @@ async def test_late_reference_drift_hook_allows_reads_inside_planned_artifact_se |
| 1440 | 1440 | assert result.decision == HookDecision.CONTINUE |
| 1441 | 1441 | |
| 1442 | 1442 | |
| 1443 | +@pytest.mark.asyncio |
| 1444 | +async def test_late_reference_drift_hook_blocks_reference_reopen_after_study_and_first_output( |
| 1445 | + temp_dir: Path, |
| 1446 | +) -> None: |
| 1447 | + registry = create_default_registry(temp_dir) |
| 1448 | + policy = build_permission_policy( |
| 1449 | + active_mode=PermissionMode.WORKSPACE_WRITE, |
| 1450 | + workspace_root=temp_dir, |
| 1451 | + tool_requirements=registry.get_tool_requirements(), |
| 1452 | + ) |
| 1453 | + dod_store = DefinitionOfDoneStore(temp_dir) |
| 1454 | + dod = create_definition_of_done("Create a multi-file guide from a reference") |
| 1455 | + dod.status = "in_progress" |
| 1456 | + dod.completed_items = [ |
| 1457 | + "First, examine the existing reference guide structure to understand the format and cadence", |
| 1458 | + ] |
| 1459 | + plan_path = temp_dir / "implementation.md" |
| 1460 | + plan_path.write_text( |
| 1461 | + "# File Changes\n" |
| 1462 | + "- `guide/index.html`\n" |
| 1463 | + "- `guide/chapters/01-getting-started.html`\n" |
| 1464 | + "- `guide/chapters/02-installation.html`\n" |
| 1465 | + ) |
| 1466 | + dod.implementation_plan = str(plan_path) |
| 1467 | + guide_dir = temp_dir / "guide" / "chapters" |
| 1468 | + guide_dir.mkdir(parents=True, exist_ok=True) |
| 1469 | + (temp_dir / "guide" / "index.html").write_text("index") |
| 1470 | + dod_path = dod_store.save(dod) |
| 1471 | + session = FakeSession(active_dod_path=str(dod_path), messages=[]) |
| 1472 | + hook = LateReferenceDriftHook( |
| 1473 | + dod_store=dod_store, |
| 1474 | + project_root=temp_dir, |
| 1475 | + session=session, |
| 1476 | + ) |
| 1477 | + |
| 1478 | + result = await hook.pre_tool_use( |
| 1479 | + HookContext( |
| 1480 | + tool_call=ToolCall( |
| 1481 | + id="read-reference", |
| 1482 | + name="read", |
| 1483 | + arguments={"file_path": str(temp_dir / "reference" / "index.html")}, |
| 1484 | + ), |
| 1485 | + tool=registry.get("read"), |
| 1486 | + registry=registry, |
| 1487 | + permission_policy=policy, |
| 1488 | + source="native", |
| 1489 | + ) |
| 1490 | + ) |
| 1491 | + |
| 1492 | + assert result.decision == HookDecision.DENY |
| 1493 | + assert result.terminal_state == "blocked" |
| 1494 | + assert result.message is not None |
| 1495 | + assert "late reference drift" in result.message |
| 1496 | + assert "01-getting-started.html" in result.message |
| 1497 | + |
| 1498 | + |
| 1443 | 1499 | @pytest.mark.asyncio |
| 1444 | 1500 | async def test_late_reference_drift_hook_blocks_reference_reads_after_artifacts_exist( |
| 1445 | 1501 | temp_dir: Path, |