markdown-flow 0.4.0__tar.gz → 0.4.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. {markdown_flow-0.4.0 → markdown_flow-0.4.1}/PKG-INFO +7 -3
  2. {markdown_flow-0.4.0 → markdown_flow-0.4.1}/README.md +6 -2
  3. {markdown_flow-0.4.0 → markdown_flow-0.4.1}/markdown_flow/__init__.py +1 -1
  4. {markdown_flow-0.4.0 → markdown_flow-0.4.1}/markdown_flow/constants.py +13 -0
  5. markdown_flow-0.4.1/markdown_flow/parser/document_blocks.py +357 -0
  6. markdown_flow-0.4.1/markdown_flow/parser/document_context.py +447 -0
  7. {markdown_flow-0.4.0 → markdown_flow-0.4.1}/markdown_flow/parser/preprocessor.py +38 -31
  8. {markdown_flow-0.4.0 → markdown_flow-0.4.1}/markdown_flow.egg-info/PKG-INFO +7 -3
  9. {markdown_flow-0.4.0 → markdown_flow-0.4.1}/markdown_flow.egg-info/SOURCES.txt +1 -0
  10. markdown_flow-0.4.0/markdown_flow/parser/document_context.py +0 -131
  11. {markdown_flow-0.4.0 → markdown_flow-0.4.1}/LICENSE +0 -0
  12. {markdown_flow-0.4.0 → markdown_flow-0.4.1}/markdown_flow/constants_system_prompt.py +0 -0
  13. {markdown_flow-0.4.0 → markdown_flow-0.4.1}/markdown_flow/core.py +0 -0
  14. {markdown_flow-0.4.0 → markdown_flow-0.4.1}/markdown_flow/enums.py +0 -0
  15. {markdown_flow-0.4.0 → markdown_flow-0.4.1}/markdown_flow/escaping.py +0 -0
  16. {markdown_flow-0.4.0 → markdown_flow-0.4.1}/markdown_flow/exceptions.py +0 -0
  17. {markdown_flow-0.4.0 → markdown_flow-0.4.1}/markdown_flow/formatter/__init__.py +0 -0
  18. {markdown_flow-0.4.0 → markdown_flow-0.4.1}/markdown_flow/formatter/classifier.py +0 -0
  19. {markdown_flow-0.4.0 → markdown_flow-0.4.1}/markdown_flow/formatter/format.py +0 -0
  20. {markdown_flow-0.4.0 → markdown_flow-0.4.1}/markdown_flow/formatter/patterns.py +0 -0
  21. {markdown_flow-0.4.0 → markdown_flow-0.4.1}/markdown_flow/formatter/sanitize.py +0 -0
  22. {markdown_flow-0.4.0 → markdown_flow-0.4.1}/markdown_flow/formatter/stream.py +0 -0
  23. {markdown_flow-0.4.0 → markdown_flow-0.4.1}/markdown_flow/formatter/types.py +0 -0
  24. {markdown_flow-0.4.0 → markdown_flow-0.4.1}/markdown_flow/higher_priority_prompt.md +0 -0
  25. {markdown_flow-0.4.0 → markdown_flow-0.4.1}/markdown_flow/llm.py +0 -0
  26. {markdown_flow-0.4.0 → markdown_flow-0.4.1}/markdown_flow/models.py +0 -0
  27. {markdown_flow-0.4.0 → markdown_flow-0.4.1}/markdown_flow/parser/__init__.py +0 -0
  28. {markdown_flow-0.4.0 → markdown_flow-0.4.1}/markdown_flow/parser/code_fence_utils.py +0 -0
  29. {markdown_flow-0.4.0 → markdown_flow-0.4.1}/markdown_flow/parser/html_comment_utils.py +0 -0
  30. {markdown_flow-0.4.0 → markdown_flow-0.4.1}/markdown_flow/parser/interaction.py +0 -0
  31. {markdown_flow-0.4.0 → markdown_flow-0.4.1}/markdown_flow/parser/json_parser.py +0 -0
  32. {markdown_flow-0.4.0 → markdown_flow-0.4.1}/markdown_flow/parser/output.py +0 -0
  33. {markdown_flow-0.4.0 → markdown_flow-0.4.1}/markdown_flow/parser/validation.py +0 -0
  34. {markdown_flow-0.4.0 → markdown_flow-0.4.1}/markdown_flow/parser/variable.py +0 -0
  35. {markdown_flow-0.4.0 → markdown_flow-0.4.1}/markdown_flow/providers/__init__.py +0 -0
  36. {markdown_flow-0.4.0 → markdown_flow-0.4.1}/markdown_flow/providers/config.py +0 -0
  37. {markdown_flow-0.4.0 → markdown_flow-0.4.1}/markdown_flow/providers/openai.py +0 -0
  38. {markdown_flow-0.4.0 → markdown_flow-0.4.1}/markdown_flow/system_prompt.md +0 -0
  39. {markdown_flow-0.4.0 → markdown_flow-0.4.1}/markdown_flow/tag_filter.py +0 -0
  40. {markdown_flow-0.4.0 → markdown_flow-0.4.1}/markdown_flow/variable_names.py +0 -0
  41. {markdown_flow-0.4.0 → markdown_flow-0.4.1}/markdown_flow.egg-info/dependency_links.txt +0 -0
  42. {markdown_flow-0.4.0 → markdown_flow-0.4.1}/markdown_flow.egg-info/top_level.txt +0 -0
  43. {markdown_flow-0.4.0 → markdown_flow-0.4.1}/pyproject.toml +0 -0
  44. {markdown_flow-0.4.0 → markdown_flow-0.4.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: markdown-flow
3
- Version: 0.4.0
3
+ Version: 0.4.1
4
4
  Summary: An agent library designed to parse and process MarkdownFlow documents
5
5
  Project-URL: Homepage, https://github.com/ai-shifu/markdown-flow-agent-py
6
6
  Project-URL: Bug Tracker, https://github.com/ai-shifu/markdown-flow-agent-py/issues
@@ -708,8 +708,12 @@ literal. Only a leading assignment marker sets the interaction's variable;
708
708
  markers elsewhere in its body remain literal option text. An unterminated
709
709
  marker keeps the remaining text opaque, without restarting at an inner `?[`.
710
710
 
711
- At document level, complete inline code and HTML are read before markers inside
712
- them. Complete variable examples there still support collection and replacement,
711
+ At document level, complete inline code, inline math, link resources, indented
712
+ code, and raw-text HTML bodies (`script`, `style`, `pre`, `textarea`) bound local
713
+ variable examples. Quote/list-contained fences and ordinary HTML blocks also keep
714
+ unfinished examples local. Reference-definition destinations and titles retain
715
+ their complete variable examples. Physical CRLF, LF and CR boundaries preserve
716
+ original offsets and marker text. Complete variable examples there still support collection and replacement,
713
717
  but an unclosed example cannot consume text beyond that markup span. Escaped
714
718
  document openers stay literal. A true raw marker owns its entire name before any
715
719
  code or HTML inside that name is considered; interaction bodies keep the raw
@@ -690,8 +690,12 @@ literal. Only a leading assignment marker sets the interaction's variable;
690
690
  markers elsewhere in its body remain literal option text. An unterminated
691
691
  marker keeps the remaining text opaque, without restarting at an inner `?[`.
692
692
 
693
- At document level, complete inline code and HTML are read before markers inside
694
- them. Complete variable examples there still support collection and replacement,
693
+ At document level, complete inline code, inline math, link resources, indented
694
+ code, and raw-text HTML bodies (`script`, `style`, `pre`, `textarea`) bound local
695
+ variable examples. Quote/list-contained fences and ordinary HTML blocks also keep
696
+ unfinished examples local. Reference-definition destinations and titles retain
697
+ their complete variable examples. Physical CRLF, LF and CR boundaries preserve
698
+ original offsets and marker text. Complete variable examples there still support collection and replacement,
695
699
  but an unclosed example cannot consume text beyond that markup span. Escaped
696
700
  document openers stay literal. A true raw marker owns its entire name before any
697
701
  code or HTML inside that name is considered; interaction bodies keep the raw
@@ -118,4 +118,4 @@ __all__ = [
118
118
  "format_variable_assignment",
119
119
  ]
120
120
 
121
- __version__ = "0.4.0"
121
+ __version__ = "0.4.1"
@@ -48,6 +48,19 @@ COMPILED_INLINE_HTML_REGEX = re.compile(
48
48
  r"|</[A-Za-z][A-Za-z0-9-]*" + _HTML_SPACE + r"*>"
49
49
  r"|<!--[\s\S]*?-->|<\?[\s\S]*?\?>|<![A-Z]+" + _HTML_SPACE + r"+[^>]*>|<!\[CDATA\[[\s\S]*?\]\]>)"
50
50
  )
51
+ COMPILED_INDENTED_CODE_REGEX = re.compile(r"(?: {4}| {0,3}\t)")
52
+ COMPILED_RAW_HTML_START_REGEX = re.compile(r" {0,3}<(script|style|pre|textarea)(?=[ \t\r\n\f>])", re.IGNORECASE)
53
+ COMPILED_RAW_HTML_END_REGEX = {name: re.compile(r"</" + name + r"[ \t\r\n]*>", re.IGNORECASE) for name in ("script", "style", "pre", "textarea")}
54
+ COMPILED_DOLLAR_RUN_REGEX = re.compile(r"\$+")
55
+ COMPILED_BLANK_LINE_REGEX = re.compile(r"(?:\r\n|\r(?!\n)|(?<!\r)\n)[ \t]*(?:\r\n|\r(?!\n)|(?<!\r)\n)")
56
+ COMPILED_ATX_START_REGEX = re.compile(r"^ {0,3}#{1,6}(?=[ \t\r\n]|$)", re.MULTILINE)
57
+ COMPILED_PHYSICAL_LINE_END_REGEX = re.compile(r"\r\n|\r|\n")
58
+ COMPILED_CONTAINER_LIST_REGEX = re.compile(r"(?:[-+*]|[0-9]{1,9}[.)])(?=[ \t])")
59
+ COMPILED_DOCUMENT_FENCE_REGEX = re.compile(r"(`{3,})([^`]*$)|(~{3,})(.*$)")
60
+ COMPILED_DOCUMENT_TAG_REGEX = re.compile(r" {0,3}</?([A-Za-z][A-Za-z0-9-]*)(?=[ \t/>]|$)")
61
+ COMPILED_REFERENCE_PREFIX_REGEX = re.compile(r" {0,3}\[((?:\\[^\r\n]|[^\]\\\r\n])+)]\:[ \t]*")
62
+ COMPILED_DOCUMENT_BREAK_REGEX = re.compile(r" {0,3}(?:(?:\*[ \t]*){3,}|(?:_[ \t]*){3,}|(?:-[ \t]*){3,})$")
63
+ COMPILED_SETEXT_REGEX = re.compile(r" {0,3}(?:=+|-+)[ \t]*$")
51
64
 
52
65
  # Canonical user_input key for non-assignment interactions (?[A|B] without a
53
66
  # variable). Clients should submit {DEFAULT_NON_ASSIGNMENT_INPUT_KEY: values};
@@ -0,0 +1,357 @@
1
+ """Physical Markdown block boundaries with original source offsets."""
2
+
3
+ from bisect import bisect_right
4
+ from collections.abc import Callable
5
+ from dataclasses import dataclass
6
+ from re import Pattern
7
+
8
+ from ..constants import (
9
+ COMPILED_ATX_START_REGEX,
10
+ COMPILED_CONTAINER_LIST_REGEX,
11
+ COMPILED_DOCUMENT_BREAK_REGEX,
12
+ COMPILED_DOCUMENT_FENCE_REGEX,
13
+ COMPILED_DOCUMENT_TAG_REGEX,
14
+ COMPILED_PHYSICAL_LINE_END_REGEX,
15
+ COMPILED_REFERENCE_PREFIX_REGEX,
16
+ COMPILED_SETEXT_REGEX,
17
+ )
18
+
19
+
20
+ HTML_BLOCK_TAGS = frozenset(
21
+ [
22
+ "address",
23
+ "article",
24
+ "aside",
25
+ "base",
26
+ "basefont",
27
+ "blockquote",
28
+ "body",
29
+ "caption",
30
+ "center",
31
+ "col",
32
+ "colgroup",
33
+ "dd",
34
+ "details",
35
+ "dialog",
36
+ "dir",
37
+ "div",
38
+ "dl",
39
+ "dt",
40
+ "fieldset",
41
+ "figcaption",
42
+ "figure",
43
+ "footer",
44
+ "form",
45
+ "frame",
46
+ "frameset",
47
+ "h1",
48
+ "h2",
49
+ "h3",
50
+ "h4",
51
+ "h5",
52
+ "h6",
53
+ "head",
54
+ "header",
55
+ "hr",
56
+ "html",
57
+ "iframe",
58
+ "legend",
59
+ "li",
60
+ "link",
61
+ "main",
62
+ "menu",
63
+ "menuitem",
64
+ "nav",
65
+ "noframes",
66
+ "ol",
67
+ "optgroup",
68
+ "option",
69
+ "p",
70
+ "param",
71
+ "search",
72
+ "section",
73
+ "summary",
74
+ "table",
75
+ "tbody",
76
+ "td",
77
+ "tfoot",
78
+ "th",
79
+ "thead",
80
+ "title",
81
+ "tr",
82
+ "track",
83
+ "ul",
84
+ ]
85
+ )
86
+
87
+
88
+ @dataclass(frozen=True)
89
+ class PhysicalLine:
90
+ """One CRLF, LF or CR line in the original source."""
91
+
92
+ start: int
93
+ content_end: int
94
+ end: int
95
+
96
+
97
+ def physical_lines(text: str) -> list[PhysicalLine]:
98
+ """Index physical Markdown lines without normalizing marker text."""
99
+ lines: list[PhysicalLine] = []
100
+ start = 0
101
+ for ending in COMPILED_PHYSICAL_LINE_END_REGEX.finditer(text):
102
+ lines.append(PhysicalLine(start, ending.start(), ending.end()))
103
+ start = ending.end()
104
+ lines.append(PhysicalLine(start, len(text), len(text)))
105
+ return lines
106
+
107
+
108
+ def container_opening(line: str) -> tuple[int, tuple[int, ...]]:
109
+ """Read only explicit quote/list prefixes, leaving ordinary indentation."""
110
+ at = 0
111
+ steps: list[int] = []
112
+ while at < len(line):
113
+ before_space = at
114
+ while at - before_space < 3 and at < len(line) and line[at] == " ":
115
+ at += 1
116
+ if at < len(line) and line[at] == ">":
117
+ steps.append(0)
118
+ at += 1
119
+ if at < len(line) and line[at] in " \t":
120
+ at += 1
121
+ continue
122
+ marker = COMPILED_CONTAINER_LIST_REGEX.match(line, at)
123
+ if marker is None:
124
+ return before_space, tuple(steps)
125
+ at = marker.end()
126
+ padding_start = at
127
+ while at < len(line) and line[at] in " \t":
128
+ at += 1
129
+ if at - padding_start > 4:
130
+ at = padding_start + 1
131
+ columns = 0
132
+ for character in line[before_space:at]:
133
+ columns += 4 - columns % 4 if character == "\t" else 1
134
+ steps.append(columns)
135
+ return at, tuple(steps)
136
+
137
+
138
+ def fence_opening(line: str) -> tuple[str, int, tuple[int, ...]] | None:
139
+ """Use zero for a quote step and a column count for a list step."""
140
+ at, steps = container_opening(line)
141
+ before = at
142
+ while at - before < 3 and at < len(line) and line[at] == " ":
143
+ at += 1
144
+ fence = COMPILED_DOCUMENT_FENCE_REGEX.match(line, at)
145
+ if fence is None:
146
+ return None
147
+ run = fence.group(1) or fence.group(3)
148
+ return run[0], len(run), steps
149
+
150
+
151
+ def container_content_start(line: str, steps: tuple[int, ...], *, partial: bool = False) -> int | None:
152
+ """Match the opening quote/list containers on a continuation line."""
153
+ at = 0
154
+ for step in steps:
155
+ if step == 0:
156
+ before = at
157
+ while at - before < 3 and at < len(line) and line[at] == " ":
158
+ at += 1
159
+ if at >= len(line) or line[at] != ">":
160
+ return at if partial and at == len(line) else None
161
+ at += 1
162
+ if at < len(line) and line[at] in " \t":
163
+ at += 1
164
+ else:
165
+ columns = 0
166
+ while columns < step:
167
+ if at >= len(line) or line[at] not in " \t":
168
+ return at if partial and at == len(line) else None
169
+ columns += 4 - columns % 4 if line[at] == "\t" else 1
170
+ at += 1
171
+ return at
172
+
173
+
174
+ @dataclass(frozen=True)
175
+ class BlockSpan:
176
+ """An owned literal region and its local example boundaries."""
177
+
178
+ end: int
179
+ opening_end: int
180
+ body_end: int
181
+ marker: str = ""
182
+ size: int = 0
183
+ steps: tuple[int, ...] = ()
184
+ closed: bool = False
185
+
186
+
187
+ class DocumentBlocks:
188
+ """Read the block boundaries needed by variable document scanners."""
189
+
190
+ def __init__(self, text: str, html: Pattern[str]) -> None:
191
+ """Index once using the caller's existing native HTML grammar."""
192
+ self.text = text
193
+ self.html_pattern = html
194
+ self.lines = physical_lines(text)
195
+ self.closed_fence_lines: set[int] = set()
196
+ self.container_paragraphs = self._container_paragraphs()
197
+ self.starts = [line.start for line in self.lines]
198
+ self.by_start = {line.start: index for index, line in enumerate(self.lines)}
199
+
200
+ def line_at(self, offset: int) -> PhysicalLine:
201
+ """Return the original physical line containing an offset."""
202
+ return self.lines[max(0, bisect_right(self.starts, offset) - 1)]
203
+
204
+ def blank(self, index: int) -> bool:
205
+ """Test Markdown blank padding rather than all Unicode whitespace."""
206
+ line = self.lines[index]
207
+ return not self.text[line.start : line.content_end].strip(" \t")
208
+
209
+ def fence(self, start: int, *, final: bool = True) -> BlockSpan | None:
210
+ """Read plain or quote/list-contained fenced code."""
211
+ index = self.by_start.get(start)
212
+ if index is None:
213
+ return None
214
+ first = self.lines[index]
215
+ opening = fence_opening(self.text[start : first.content_end])
216
+ if opening is None:
217
+ return None
218
+ marker, size, steps = opening
219
+ previous = first
220
+ for line in self.lines[index + 1 :]:
221
+ value = self.text[line.start : line.content_end]
222
+ content = container_content_start(value, steps)
223
+ if content is None:
224
+ if not final and line.end == line.content_end == len(self.text) and container_content_start(value, steps, partial=True) is not None:
225
+ return BlockSpan(len(self.text), first.end, len(self.text), marker, size, steps)
226
+ if not value.strip(" \t"):
227
+ continue
228
+ return BlockSpan(previous.content_end, first.end, previous.content_end, marker, size, steps)
229
+ candidate = value[content:].lstrip(" ")
230
+ if len(value[content:]) - len(candidate) <= 3:
231
+ run = candidate.rstrip(" \t")
232
+ if len(run) >= size and run == marker * len(run):
233
+ self.closed_fence_lines.add(line.start)
234
+ return BlockSpan(line.content_end, first.end, line.start, marker, size, steps, closed=True)
235
+ previous = line
236
+ return BlockSpan(len(self.text), first.end, len(self.text), marker, size, steps)
237
+
238
+ def ordinary_html(self, start: int) -> BlockSpan | None:
239
+ """Bound native type-six/seven HTML blocks at the first blank line."""
240
+ index = self.by_start.get(start)
241
+ if index is None:
242
+ return None
243
+ first = self.lines[index]
244
+ opening = COMPILED_DOCUMENT_TAG_REGEX.match(self.text[start : first.content_end])
245
+ if opening is None:
246
+ return None
247
+ tag_start = self.text.find("<", start, first.content_end)
248
+ block_tag = opening.group(1).lower() in HTML_BLOCK_TAGS
249
+ if not block_tag and index and not self.blank(index - 1):
250
+ return None
251
+ token = self.html_pattern.match(self.text, tag_start)
252
+ token_end = token.end() if token is not None else start
253
+ if not block_tag and (token is None or token_end > first.content_end or self.text[token_end : first.content_end].strip(" \t")):
254
+ return None
255
+ previous = self.line_at(token_end)
256
+ scan = max(index + 1, self.by_start.get(previous.start, index))
257
+ for at in range(scan, len(self.lines)):
258
+ line = self.lines[at]
259
+ if line.start >= token_end and self.blank(at):
260
+ return BlockSpan(previous.content_end, token_end, previous.content_end, closed=line.end > line.content_end)
261
+ previous = line
262
+ return BlockSpan(len(self.text), token_end, len(self.text))
263
+
264
+ def indented_may_start(self, index: int) -> bool:
265
+ """Allow code after a blank or a confirmed non-paragraph block."""
266
+ if index == 0 or self.blank(index - 1):
267
+ return True
268
+ previous = self.lines[index - 1]
269
+ value = self.text[previous.start : previous.content_end]
270
+ return bool(
271
+ previous.start in self.closed_fence_lines
272
+ or COMPILED_ATX_START_REGEX.match(value) is not None
273
+ or COMPILED_DOCUMENT_BREAK_REGEX.fullmatch(value) is not None
274
+ or (index > 1 and not self.blank(index - 2) and COMPILED_SETEXT_REGEX.fullmatch(value) is not None)
275
+ or (value.startswith("__MDFLOW_CODE_BLOCK_") and value.endswith("__"))
276
+ )
277
+
278
+ def reference_prefix(self, start: int) -> int | None:
279
+ """Read the bounded single-line label prefix of a reference definition."""
280
+ index = self.by_start.get(start)
281
+ if index is None:
282
+ return None
283
+ line = self.lines[index]
284
+ match = COMPILED_REFERENCE_PREFIX_REGEX.match(self.text[start : line.content_end])
285
+ if match is None or len(match.group(1)) > 999 or not match.group(1).strip(" \t"):
286
+ return None
287
+ return start + match.end()
288
+
289
+ def interaction_limit(self, start: int, marker_end: Callable[[int], int | None], *, final: bool = True) -> tuple[int, bool]:
290
+ """Stop incomplete document controls at a blank outside opaque markers."""
291
+ at = start
292
+ while at < len(self.text):
293
+ index = self.by_start.get(at)
294
+ if index is not None and self.blank(index) and (final or self.lines[index].end > self.lines[index].content_end):
295
+ return at, False
296
+ if self.text.startswith("{{", at):
297
+ end = marker_end(at)
298
+ if end is None:
299
+ return len(self.text), False
300
+ at = end
301
+ elif self.text[at] == "\\" and at + 1 < len(self.text) and self.text[at + 1] in "|/.]":
302
+ at += 2
303
+ elif self.text[at] == "]":
304
+ return at, True
305
+ else:
306
+ at += 1
307
+ return len(self.text), False
308
+
309
+ def _container_paragraphs(self) -> set[int]:
310
+ """Keep same-container and lazy continuation lines in one paragraph."""
311
+ boundaries: set[int] = set()
312
+ active: tuple[int, ...] = ()
313
+ paragraph = False
314
+ for index, line in enumerate(self.lines):
315
+ value = self.text[line.start : line.content_end]
316
+ if not value.strip(" \t"):
317
+ active, paragraph = (), False
318
+ continue
319
+ content = container_content_start(value, active) if active else 0
320
+ remaining = value[content:] if content is not None else value
321
+ base = active if content is not None else ()
322
+ after, steps = container_opening(remaining)
323
+ if steps and (not paragraph or self._explicit_interrupt(remaining)):
324
+ boundaries.add(index)
325
+ active = base + steps
326
+ remaining = remaining[after:]
327
+ if not remaining.strip(" \t"):
328
+ boundaries.add(index)
329
+ paragraph = False
330
+ else:
331
+ paragraph = True
332
+ if fence_opening(remaining) is not None or COMPILED_ATX_START_REGEX.match(remaining) is not None or COMPILED_DOCUMENT_BREAK_REGEX.fullmatch(remaining) is not None:
333
+ paragraph = False
334
+ return boundaries
335
+
336
+ @staticmethod
337
+ def _explicit_interrupt(value: str) -> bool:
338
+ at = 0
339
+ while at < min(3, len(value)) and value[at] == " ":
340
+ at += 1
341
+ if at < len(value) and value[at] == ">":
342
+ return True
343
+ marker = COMPILED_CONTAINER_LIST_REGEX.match(value, at)
344
+ if marker is None or (value[at].isdigit() and marker.group() not in ("1.", "1)")):
345
+ return False
346
+ return bool(value[marker.end() :].strip(" \t"))
347
+
348
+ def paragraph_interrupts(self, index: int) -> bool:
349
+ """Report a new quote/list paragraph without breaking its own inline spans."""
350
+ return index in self.container_paragraphs
351
+
352
+ def inline_block_interrupts(self, index: int) -> bool:
353
+ """Report confirmed thematic and ordinary-HTML paragraph interruptions."""
354
+ line = self.lines[index]
355
+ value = self.text[line.start : line.content_end]
356
+ tag = COMPILED_DOCUMENT_TAG_REGEX.match(value)
357
+ return COMPILED_DOCUMENT_BREAK_REGEX.fullmatch(value) is not None or (tag is not None and tag.group(1).lower() in HTML_BLOCK_TAGS)