fix: preserve source task sections and explicit empty notes
Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a07ff8-19d0-7820-b4d0-1353833cb7fc
This commit is contained in:
parent
ccb285fc40
commit
f767a1a245
4 changed files with 231 additions and 17 deletions
|
|
@ -30,7 +30,7 @@ from api.services.task_record_id_backfill import qualify_task_id
|
|||
|
||||
_WORK_RECORD_NAMESPACE = uuid.UUID("a4058507-5c4a-5a00-ab06-fffa4fb46009")
|
||||
_TASK_BLOCK_RE = re.compile(r"```task\s*\n(.*?)\n```", re.DOTALL)
|
||||
_HEADING_RE = re.compile(r"^(#{1,4})\s+(.+?)$", re.MULTILINE)
|
||||
_HEADING_RE = re.compile(r"^(#{1,6})\s+(.+?)$", re.MULTILINE)
|
||||
DEFAULT_FORGE_BASE = "https://forgejo.coulomb.social/coulomb"
|
||||
|
||||
|
||||
|
|
@ -272,12 +272,23 @@ def _split_frontmatter(text: str) -> tuple[dict, str]:
|
|||
return (meta if isinstance(meta, dict) else {}), body
|
||||
|
||||
|
||||
def _clean_task_description(raw: str) -> str | None:
|
||||
"""Match the file-authoritative consistency parser's section boundaries."""
|
||||
lines = raw.splitlines()
|
||||
while lines and (not lines[0].strip() or lines[0].strip() in {"---", "***", "___"}):
|
||||
lines.pop(0)
|
||||
while lines and (not lines[-1].strip() or lines[-1].strip() in {"---", "***", "___"}):
|
||||
lines.pop()
|
||||
return "\n".join(lines).strip() or None
|
||||
|
||||
|
||||
def _parse_tasks(body: str, workplan_id: str) -> list[DerivedTask]:
|
||||
headings = [
|
||||
(m.start(), m.group(2).strip()) for m in _HEADING_RE.finditer(body)
|
||||
(m.start(), len(m.group(1)), m.group(2).strip()) for m in _HEADING_RE.finditer(body)
|
||||
]
|
||||
out: list[DerivedTask] = []
|
||||
for m in _TASK_BLOCK_RE.finditer(body):
|
||||
task_matches = list(_TASK_BLOCK_RE.finditer(body))
|
||||
for index, m in enumerate(task_matches):
|
||||
try:
|
||||
block = yaml.safe_load(m.group(1).strip()) or {}
|
||||
except yaml.YAMLError:
|
||||
|
|
@ -287,15 +298,21 @@ def _parse_tasks(body: str, workplan_id: str) -> list[DerivedTask]:
|
|||
rid = str(block.get("id") or "").strip()
|
||||
if not rid:
|
||||
continue
|
||||
title = block.get("title")
|
||||
if not title:
|
||||
prev = [t for pos, t in headings if pos < m.start()]
|
||||
title = prev[-1] if prev else None
|
||||
following_headings = [pos for pos, _title in headings if pos > m.end()]
|
||||
description_end = min(following_headings) if following_headings else len(body)
|
||||
description = str(block.get("description") or "").strip()
|
||||
if not description:
|
||||
description = body[m.end() : description_end].strip()
|
||||
prev = [(pos, level, text) for pos, level, text in headings if pos < m.start()]
|
||||
heading = prev[-1] if prev else None
|
||||
title = block.get("title") or (heading[2] if heading else None)
|
||||
# Nested evidence belongs to this task. Stop at the next peer/ancestor
|
||||
# heading or task block, as the ordinary consistency path does.
|
||||
description_end = task_matches[index + 1].start() if index + 1 < len(task_matches) else len(body)
|
||||
following_heading = next((
|
||||
pos for pos, level, _text in headings
|
||||
if pos > m.end() and (heading is None or level <= heading[1])
|
||||
), None)
|
||||
if following_heading is not None:
|
||||
description_end = min(description_end, following_heading)
|
||||
description = _clean_task_description(body[m.end():description_end])
|
||||
if description is None and block.get("description") is not None:
|
||||
description = str(block["description"])
|
||||
out.append(
|
||||
DerivedTask(
|
||||
# A bare `T01` is not an identifier: it is unique only within
|
||||
|
|
@ -315,16 +332,16 @@ def _parse_tasks(body: str, workplan_id: str) -> list[DerivedTask]:
|
|||
title=title,
|
||||
status=(str(block["status"]).strip() if block.get("status") else None),
|
||||
priority=(str(block["priority"]).strip() if block.get("priority") else None),
|
||||
description=description or None,
|
||||
description=description,
|
||||
needs_human=bool(block.get("needs_human", False)),
|
||||
intervention_note=(
|
||||
str(block["intervention_note"]).strip()
|
||||
if block.get("intervention_note")
|
||||
str(block["intervention_note"])
|
||||
if block.get("intervention_note") is not None
|
||||
else None
|
||||
),
|
||||
blocking_reason=(
|
||||
str(block["blocking_reason"]).strip()
|
||||
if block.get("blocking_reason")
|
||||
str(block["blocking_reason"])
|
||||
if block.get("blocking_reason") is not None
|
||||
else None
|
||||
),
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue