fix(uploads): exclude indented bold lines from outlines (#6194)

* fix(uploads): exclude indented bold lines from outlines

* refactor(uploads): name the indented-code pattern
This commit is contained in:
KennySimpson 2026-10-04 14:54:08 +08:00 committed by GitHub
parent 20bd989fc1
commit 830de11c66
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 66 additions and 2 deletions

View File

@ -1907,8 +1907,8 @@ AIO directory listings discard missing shell sessions so the next request can re
After a dropped connection, directory listings and persistent shell commands report an
unknown outcome without replaying the operation; later calls use a fresh session.
Uploaded Markdown outlines recognize ATX heading syntax, clean closing markers with a linear suffix scan, and skip fenced code examples, so hashtags and code comments do not
crowd out real document sections from the agent's heading preview.
Uploaded Markdown outlines recognize ATX heading syntax, clean closing markers with a linear suffix scan, and skip fenced and indented code examples, so hashtags and code comments do not
crowd out real document sections from the agent's heading preview. Indented bold examples are also excluded; PDF-style bold headings with up to three leading spaces remain supported.
UTF-8 Markdown files with or without a byte-order mark (BOM) produce the same
outlines and fallback previews, with original line numbers preserved.
Outline titles are limited to 200 characters and fallback previews to 2,000

View File

@ -32,6 +32,12 @@ optional leading UTF-8 BOM cannot hide a first-line heading or code fence, or
occupy a preview line. Preserve physical line numbers, embedded U+FEFF
characters, and the original file bytes.
All outline heading styles skip root-level indented code (four leading spaces
or a tab after up to three spaces), including PDF-style single and split bold
headings. Keep legitimate bold headings with up to three spaces and the shared
conversion-companion path working. Regression coverage lives in
`tests/test_file_outline_indented_bold.py`.
### Active Content MIME Types
`text_detection.py::_is_active_content_mime_type` is the shared download-safety

View File

@ -51,6 +51,10 @@ _CODE_FENCE_RE = re.compile(r"^ {0,3}(`{3,}|~{3,})(.*)$")
# Match the original indentation so indented code cannot become a heading.
_ATX_HEADING_RE = re.compile(r"^ {0,3}#{1,6}(?:[ \t]+(.*))?$")
# Root-level indented code starts at four columns, including
# a tab after up to three spaces. Do not strip it into a heading.
_INDENTED_CODE_RE = re.compile(r"^( {4}| {0,3}\t)")
def _strip_atx_closing_hashes(raw: str) -> str:
"""Remove a whitespace-separated terminal hash run in linear time."""
@ -141,6 +145,9 @@ def extract_outline(md_path: Path) -> list[dict]:
fence_length = len(marker)
continue
if _INDENTED_CODE_RE.match(line):
continue
stripped = line.strip()
if not stripped:
continue

View File

@ -0,0 +1,51 @@
"""Indented bold examples must not displace document headings in outlines."""
import pytest
from deerflow.uploads.companions import register_companion
from deerflow.utils.file_outline import MAX_OUTLINE_ENTRIES, extract_outline, extract_outline_for_file
@pytest.mark.parametrize("indent", [" ", "\t", " \t", " \t", " \t"])
@pytest.mark.parametrize("text", ["**PART II**", "**1** **Code example**"])
def test_indented_bold_code_preserves_real_heading_lines(tmp_path, indent, text):
path = tmp_path / "guide.md"
path.write_text(f"# Overview\n\n{indent}{text}\n\n## Findings\n", encoding="utf-8")
assert extract_outline(path) == [{"title": "Overview", "line": 1}, {"title": "Findings", "line": 5}]
@pytest.mark.parametrize("indent", [" ", "\t"])
@pytest.mark.parametrize("text", ["**PART II**", "**1** **Code example**"])
def test_indented_bold_examples_cannot_exhaust_outline_slots(tmp_path, indent, text):
path = tmp_path / "guide.md"
path.write_text((indent + text + "\n") * (MAX_OUTLINE_ENTRIES + 1) + "\n# Actual findings\n", encoding="utf-8")
assert extract_outline(path) == [{"title": "Actual findings", "line": MAX_OUTLINE_ENTRIES + 3}]
@pytest.mark.parametrize("indent", ["", " ", " ", " "])
def test_bold_headings_with_up_to_three_spaces_are_preserved(tmp_path, indent):
path = tmp_path / "report.md"
path.write_text(f"{indent}**PART II**\n{indent}**1** **概述**\n", encoding="utf-8")
assert extract_outline(path) == [{"title": "PART II", "line": 1}, {"title": "1 概述", "line": 2}]
def test_owned_conversion_uses_the_same_indentation_rule(tmp_path):
uploads = tmp_path / "thread/user-data/uploads"
uploads.mkdir(parents=True)
original = uploads / "report.pdf"
markdown = uploads / "report.md"
original.write_bytes(b"synthetic source fixture; converter not invoked")
markdown.write_text(" **PART II**\n\n# Actual section\n", encoding="utf-8")
register_companion(original, markdown)
assert extract_outline_for_file(original) == ([{"title": "Actual section", "line": 3}], [])
def test_existing_fenced_code_and_heading_styles_are_preserved(tmp_path):
path = tmp_path / "guide.md"
path.write_text("~~~\n**PART II**\n~~~\n# Section\n**PART III**\n**2** **Details**\n", encoding="utf-8")
assert extract_outline(path) == [{"title": "Section", "line": 4}, {"title": "PART III", "line": 5}, {"title": "2 Details", "line": 6}]