Skip to content

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.

LocationRole today
plugin/lib/model/entities/task/ops/scan-placeholders.tsScans 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.tsThe 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.tsComposes scan-placeholders into the deterministic readiness report

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.

  1. Read parse-touchpoints.ts for the established parse() + section / fence pattern in this directory.
  2. Reimplement scan-placeholders.ts’s body traversal on the projection: iterate sections / blocks from parse(), use the projection’s fenced-code boundaries instead of FENCE_RE, and keep the placeholder predicates (TBD whole-word, non-backticked <...>, empty required cells) and the returned finding shape unchanged.
  3. Run the task-ops tests and sdlc task gap-report against a known-placeholder task and a clean task; confirm identical findings before and after.
LocationKindChange
plugin/lib/model/entities/task/ops/scan-placeholders.tsmodifyReimplement section / fence traversal over markdown-contract parse(); drop H2_RE / FENCE_RE / TABLE_ROW_RE / maskInlineCode
plugin/lib/model/entities/task/ops/tests/modifyConfirm/extend tests pinning placeholder findings (including code-fence-masked cases) over the new backing
  • AC-1: scan-placeholders.ts no longer declares H2_RE, FENCE_RE, or TABLE_ROW_RE; it derives sections and fenced-code regions from markdown-contract parse().
  • 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-report returns identical gaps[] for a fixed set of task fixtures before and after the change.
  • AC-4: bun test passes for plugin/lib/model/entities/task/ops/.
  • scan-placeholders.ts’s FRONTMATTER_RE split — 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 of gap-report.
  • None. [T-Z51X-consolidate-frontmatter-regex-onto-shared-helper](/planning/tasks/consolidate-frontmatter-regex-onto-shared-helper/) also touches this file’s FRONTMATTER_RE; coordinate so the two don’t conflict.

Captured by /sdlc:task-work on 2026-06-30. PR: pending.

  • AC-1: auto — grep confirms H2_RE / FENCE_RE / TABLE_ROW_RE / TABLE_SEP_RE declarations 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.ts pins 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-report gaps[]/placeholders[] across 16 fixtures (crafted edge cases + 9 real task specs) before and after; diff byte-identical.
  • AC-4: auto — bun test ./plugin/lib/model/entities/task/ops/ → 124 pass, 0 fail.
  • The deterministic gap-report op gave an exact, scriptable before/after oracle for AC-3 — no judgment call needed to prove behavior was preserved.
  • parse-touchpoints.ts was a clean precedent in the same directory; the parse() + SectionNode pattern transferred directly.
  • 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 (46758 then 80190 vs baseline 81431). It is unrelated to this change and reproduces on origin/main. The quality run output 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-dir relative to the worktree, but Step 3a captures the baseline in the main repo’s .sdlc/quality-baselines/; the gate errored baseline not found until --baseline-dir <main-repo>/.sdlc/quality-baselines was 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)

← Back to Tasks