Skip to content

T-LWEH-entity-title-accessor

Status: closed/done · Impact: medium · Complexity: small

Three call sites hand-roll H1 extraction from entity bodies with regex/line scans, none fence-aware (a # heading inside a code fence matches), while the read layer’s projection already computes the fence-aware H1 (tree.root.name) and discards it. Surface the title on the read wrapper, give bulk scans a cheap fence-aware helper, and converge the read-only H1 sites. This is the honest residue of the “typed body plane” wave-2: a per-file assessment (2026-07-19) found every other body-parsing site LEAVE — their hand-rolled parsing exists for fidelity the typed plane cannot serve.

LocationRole today
apps/sdlc/lib/model/read.tsreadEntity runs contract.validate and returns the wrapper; the projection’s tree.root.name (fence-aware H1) is computed and thrown away.
apps/sdlc/lib/services/lease/ops/task/transition.tsextractTaskTitle: splitFrontmatter + /^# (.+)$/m first match — not fence-aware; feeds the handoff title.
apps/sdlc/lib/services/project/cleanup/index.tsloadTasks: per-file line scan for # -prefixed line — not fence-aware; feeds TaskRecord.headline.
apps/sdlc/skills/task-work/start_task.tsextractHeadline: third copy of the regex — deliberately NOT converged (welded to the byte-faithful start-mutation flow; see Out of scope).
  • EntityReadResult (both arms) carries title: string | null — the fence-aware first H1 off the projection’s tree.root.name, null when the document has none.
  • read.ts exports extractTitle(text): string | null — a cheap fence-aware first-H1 scan (tracks ``` fences, skips frontmatter) for BULK contexts where the full-contract wrapper is disproportionate, mirroring the readRawFrontmatter split.
  • transition.ts#extractTaskTitle and cleanup#loadTasks converge (read-only call sites; the fence-awareness change is a bug fix, not a behavior regression).
  • lib/model/README.md documents the title accessor next to the wrapper / bulk-primitive split.
  1. Add title to ReadResultBase in lib/model/read.ts (populated from result.tree.root.name, empty → null).
  2. Add extractTitle(text) — strip frontmatter via splitFrontmatter, walk lines tracking fence state, return the first # heading’s text trimmed, else null. Unit-test agreement with the projection on valid docs and on the fenced-# x case the old regexes get wrong.
  3. Converge transition.ts#extractTaskTitle and cleanup#loadTasks onto extractTitle. Export from lib/model/index.ts; note in README.
  4. Tests in lib/model/tests/read.test.ts: title on both wrapper arms; extractTitle fence case, no-H1 case, frontmatter-only case.
LocationKindChange
apps/sdlc/lib/model/read.tsmodifytitle on the wrapper; extractTitle bulk helper.
apps/sdlc/lib/model/index.tsmodifyExport extractTitle.
apps/sdlc/lib/services/lease/ops/task/transition.tsmodifyextractTaskTitleextractTitle.
apps/sdlc/lib/services/project/cleanup/index.tsmodifyloadTasks H1 scan → extractTitle.
apps/sdlc/lib/model/README.mdmodifyDocument the title accessor.
apps/sdlc/lib/model/tests/read.test.tsmodifyTitle + extractTitle coverage.
  • AC-1: readTask on a valid task yields title equal to the H1 on both arms; a document whose only # heading is inside a code fence yields title: null from both the wrapper and extractTitle (regression test — the old regexes matched it).
  • AC-2: transition.ts and cleanup/index.ts contain no local H1 regex/line scan (grep-verifiable); handoff title and TaskRecord.headline behavior on real task files is unchanged.
  • AC-3: bun test passes for apps/sdlc; tsc --noEmit clean.
  • parse-touchpoints / resolve-touchpoints / scan-placeholders / gap-report / scan_validate_ac / start_task.ts — per the 2026-07-19 body-plane assessment: their hand parsing preserves fidelity the typed plane discards (verbatim backticked cells, literal cell counts, the table/prose/bulleted-legacy/missing classification, raw line offsets and inline-code masking, PyYAML byte-faithful re-emission). start_task’s third H1 copy stays with its welded flow.
  • Exposing the full DocTree on the wrapper — deferred until a consumer needs it (gap-report needs the READY contract’s tree, not the birth-floor one the wrapper runs).
  • Dashboard typed reads (captured on the backlog separately).
  • none
  • Wave-2 follow-up to T-N9PM (PRs #928/#930). Scoped down from “converge body-plane consumers” after a per-file assessment returned LEAVE/PARTIAL on every candidate; the title accessor is the surviving clean win, and the fence-awareness fix is the latent-bug payoff.

← Back to Tasks