T-FN18-migrate-scan-placeholders-to-markdown-contract
Status: closed/done · Impact: low · Complexity: small
plugin/lib/model/entities/task/ops/scan-placeholders.ts scans a task body
for spec-drift placeholders (TBD, <...>, empty table cells) with a
hand-rolled fence / heading / table state machine, while its sibling
parse-touchpoints.ts — same directory, same readiness gate — already reads
task bodies through markdown-contract’s parse(). Migrating it removes the
duplicate bespoke scanner and puts both readiness-gate ops on one markdown
front-end.
| Location | Role today |
|---|---|
plugin/lib/model/entities/task/ops/scan-placeholders.ts | Scans a task body for TBD / <...> / empty cells via bespoke FRONTMATTER_RE, H2_RE, FENCE_RE, TABLE_ROW_RE, maskInlineCode, and a line-walking scanBody |
plugin/lib/model/entities/task/ops/parse-touchpoints.ts | The precedent: reads the same task bodies via markdown-contract parse() + SectionNode, with code-fence awareness from the projection |
plugin/lib/model/entities/task/ops/gap-report.ts | Composes scan-placeholders into the deterministic readiness report |
Proposed
Section titled “Proposed”scan-placeholders.ts derives its sections and code-fence boundaries from
markdown-contract’s parse() projection (as parse-touchpoints.ts does),
keeping the placeholder-detection rules and the returned finding shape
identical. No bespoke H2_RE / FENCE_RE / TABLE_ROW_RE scanning
remains, and the readiness gate’s behavior is unchanged.
Approach
Section titled “Approach”- Read
parse-touchpoints.tsfor the establishedparse()+ section / fence pattern in this directory. - Reimplement
scan-placeholders.ts’s body traversal on the projection: iterate sections / blocks fromparse(), use the projection’s fenced-code boundaries instead ofFENCE_RE, and keep the placeholder predicates (TBDwhole-word, non-backticked<...>, empty required cells) and the returned finding shape unchanged. - Run the task-ops tests and
sdlc task gap-reportagainst a known-placeholder task and a clean task; confirm identical findings before and after.
Files to touch
Section titled “Files to touch”| Location | Kind | Change |
|---|---|---|
plugin/lib/model/entities/task/ops/scan-placeholders.ts | modify | Reimplement section / fence traversal over markdown-contract parse(); drop H2_RE / FENCE_RE / TABLE_ROW_RE / maskInlineCode |
plugin/lib/model/entities/task/ops/tests/ | modify | Confirm/extend tests pinning placeholder findings (including code-fence-masked cases) over the new backing |
Acceptance criteria
Section titled “Acceptance criteria”- AC-1:
scan-placeholders.tsno longer declaresH2_RE,FENCE_RE, orTABLE_ROW_RE; it derives sections and fenced-code regions from markdown-contractparse(). - AC-2: Placeholder detection is unchanged —
TBD(whole-word, case-insensitive), non-backticked<...>, and empty required-table cells are still flagged, and placeholders inside fenced / inline code are still NOT flagged — pinned by tests. - AC-3:
sdlc task gap-reportreturns identicalgaps[]for a fixed set of task fixtures before and after the change. - AC-4:
bun testpasses forplugin/lib/model/entities/task/ops/.
Out of scope
Section titled “Out of scope”scan-placeholders.ts’sFRONTMATTER_REsplit — folded into[T-Z51X-consolidate-frontmatter-regex-onto-shared-helper](/planning/tasks/consolidate-frontmatter-regex-onto-shared-helper/).- The LLM-judged corpus-assumption scan (
scan_corpus_assumptions.ts) — a separate path, not part ofgap-report.
Dependencies
Section titled “Dependencies”- None.
[T-Z51X-consolidate-frontmatter-regex-onto-shared-helper](/planning/tasks/consolidate-frontmatter-regex-onto-shared-helper/)also touches this file’sFRONTMATTER_RE; coordinate so the two don’t conflict.
Post-mortem
Section titled “Post-mortem”Captured by /sdlc:task-work on 2026-06-30. PR: pending.
Acceptance criteria coverage
Section titled “Acceptance criteria coverage”- AC-1: auto —
grepconfirmsH2_RE/FENCE_RE/TABLE_ROW_RE/TABLE_SEP_REdeclarations are gone;import { parse } from "markdown-contract"is present and drives section/fence/table derivation. - AC-2: auto — new
plugin/lib/model/entities/task/ops/tests/scan-placeholders.test.tspins the{section, phrase, line, snippet}finding shape:TBD(whole-word, case-insensitive),<...>, the(final name ...)/(or final ...)/(pick one)phrases, and empty cells flagged; fenced-code and inline-code placeholders not flagged. - AC-3: agent-manual — captured
scan-placeholders+gap-reportgaps[]/placeholders[]across 16 fixtures (crafted edge cases + 9 real task specs) before and after;diffbyte-identical. - AC-4: auto —
bun test ./plugin/lib/model/entities/task/ops/→ 124 pass, 0 fail.
What worked
Section titled “What worked”- The deterministic
gap-reportop gave an exact, scriptable before/after oracle for AC-3 — no judgment call needed to prove behavior was preserved. parse-touchpoints.tswas a clean precedent in the same directory; theparse()+SectionNodepattern transferred directly.
Friction and automation gaps
Section titled “Friction and automation gaps”- The baseline-gated quality gate flagged a false
new-drift:line — a dashboard server-startup log (/private<TMPDIR> <pid> http://127.0.0.1:<PORT>/) whose PID column is not normalized, so it differs every run (46758then80190vs baseline81431). It is unrelated to this change and reproduces on origin/main. Thequality runoutput normalizer should scrub the standalone PID column in that dashboard test line (or the test should suppress it) so the baseline diff stays deterministic. → T-BQRU-quality-normalize-ports-pids-timings (linked) - The Step 7 baseline-gated invocation resolves
--baseline-dirrelative to the worktree, but Step 3a captures the baseline in the main repo’s.sdlc/quality-baselines/; the gate erroredbaseline not founduntil--baseline-dir <main-repo>/.sdlc/quality-baselineswas passed explicitly. The skill’s Step 7 verbatim command omits that flag — task-work run from a worktree should pass the main-repo baseline dir by default. → T-44OO-plugin-scripts-self-discover-project-root (linked)
Spawned follow-up tasks
Section titled “Spawned follow-up tasks”- T-BQRU-quality-normalize-ports-pids-timings — linked (existing open/ready owner of the dashboard-line PID false-new-drift noise class; this post-mortem adds the standalone-table-column PID data point)
- T-44OO-plugin-scripts-self-discover-project-root — linked (existing active tracker for the worktree-vs-main baseline-dir silent-fallback; eighth post-mortem to surface it)