Skip to content

T-Z51X-consolidate-frontmatter-regex-onto-shared-helper

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

The same FRONTMATTER_RE frontmatter-splitting regex is re-declared privately in ~17 modules across the model, ops, lease, and skills layers, even though plugin/lib/util/frontmatter.ts exists as the shared home. The duplication means a frontmatter-format change has to be made in a dozen places and they can silently drift. Routing every read through the one helper collapses it to a single source of truth.

LocationRole today
plugin/lib/util/frontmatter.tsThe intended shared home: FRONTMATTER_RE + extractFrontmatter / splitFrontmatter / parseFrontmatter
plugin/lib/model/ops/validate.tsRe-declares a private FRONTMATTER_RE to peek type:
plugin/lib/model/ops/audit.tsRe-declares FRONTMATTER_RE for schema_version / status / depends_on peeks
plugin/lib/model/entity.tsRe-declares FRONTMATTER_RE for template stripping
plugin/skills/task-ensure-ready/ensure_ready_mutate.tsRe-declares FRONTMATTER_RE for the read half of a read-modify-write
plugin/lib/services/lease/ops/task/transition.tsRe-declares FRONTMATTER_RE (read half of a lease transition) — ~13 more like it, all in Files to touch

Every module imports the frontmatter regex / extractor from plugin/lib/util/frontmatter.ts; no module re-declares FRONTMATTER_RE. Read-only sites use the shared extractFrontmatter / splitFrontmatter / parseFrontmatter. Read-modify-write sites keep their write logic (markdown -contract is read-only by design) but take the read half from the shared helper. A grep for a private FRONTMATTER_RE = outside plugin/lib/util/frontmatter.ts (and test files) returns nothing.

  1. Audit the call sites and classify each as read-only (peek a field, or split frontmatter + body) versus read-modify-write.
  2. For read-only sites, replace the private regex with util/frontmatter’s extractFrontmatter / splitFrontmatter / parseFrontmatter.
  3. For write sites (ensure_ready_mutate, transition, _update, migrate), import the shared regex / split for the read half and keep the write logic local.
  4. Delete every now-unused private FRONTMATTER_RE; run the full bun test suite and a sdlc entities validate / audit smoke; confirm no behavior change.
LocationKindChange
plugin/lib/util/frontmatter.tsmodifyEnsure it exports the regex + extract / split / parse the callers need
plugin/lib/model/ops/validate.tsmodifytype: peek via the shared extractor; drop the private regex
plugin/lib/model/ops/audit.tsmodifyField peeks via the shared extractor; drop the private regex
plugin/lib/model/ops/_update.tsmodifyRead half via the shared split; keep write; drop the private regex
plugin/lib/model/ops/migrate.tsmodifyRead half via the shared split; drop the private regex
plugin/lib/model/entity.tsmodifyTemplate-strip via the shared extractor; drop the private regex
plugin/lib/model/entities/task/ops/_task_doc.tsmodifyShared split; drop the private regex
plugin/lib/model/entities/task/ops/scan-placeholders.tsmodifyShared split; drop the private regex
plugin/lib/model/entities/task/claims/paths.tsmodifyShared extractor; drop the private regex
plugin/lib/model/entities/task/claims/quantifiers.tsmodifyShared extractor; drop the private regex
plugin/lib/services/lease/migration.tsmodifyRead half via the shared split; drop the private regex
plugin/lib/services/lease/ops/task/transition.tsmodifyRead half via the shared split; keep write; drop the private regex
plugin/lib/util/markdown_extract.tsmodifyUse the shared regex in stripFrontmatter; drop the private regex
.claude/skills/project-check/check_entities.tsmodifyShared extractor; drop the private regex
plugin/skills/task-ensure-ready/ensure_ready_mutate.tsmodifyRead half via the shared split; keep write; drop the private regex
plugin/skills/task-ensure-ready/scan_corpus_assumptions.tsmodifyShared split; drop the private regex
plugin/skills/task-work/dedup_search.tsmodifyShared split; drop the private regex
plugin/skills/task-work/start_task.tsmodifyShared split for the frontmatter read; drop the private regex
  • AC-1: grep -rn 'FRONTMATTER_RE *=' plugin/ .claude/skills/ returns matches only in plugin/lib/util/frontmatter.ts and *.test.ts files.
  • AC-2: Every module formerly declaring FRONTMATTER_RE imports the regex / extractor from plugin/lib/util/frontmatter.ts.
  • AC-3: bun test passes repo-wide and bun test ./.claude passes.
  • AC-4: sdlc entities validate and sdlc entities audit produce unchanged output on the current corpus (smoke check).
  • The non-frontmatter bespoke scanners (section / table / placeholder walks) — [T-K1A8-rebase-markdown-extract-on-markdown-contract](/planning/tasks/rebase-markdown-extract-on-markdown-contract/), [T-ZGO4-rebase-parse-operations-table-on-markdown-contract](/planning/tasks/rebase-parse-operations-table-on-markdown-contract/), [T-FN18-migrate-scan-placeholders-to-markdown-contract](/planning/tasks/migrate-scan-placeholders-to-markdown-contract/).
  • Frontmatter WRITE logic — markdown-contract is read-only (D-0007), so the read-modify-write sites keep their writers; only their read half consolidates.
  • None blocking. This touches files also touched by [T-K1A8-rebase-markdown-extract-on-markdown-contract](/planning/tasks/rebase-markdown-extract-on-markdown-contract/), [T-FN18-migrate-scan-placeholders-to-markdown-contract](/planning/tasks/migrate-scan-placeholders-to-markdown-contract/), and [T-4WR3-rebase-check-entities-markdown-reads-on-contract](/planning/tasks/rebase-check-entities-markdown-reads-on-contract/) (which drop their own FRONTMATTER_RE as a side effect). Land this last, or coordinate, so the set shrinks rather than conflicts.

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

  • AC-1: auto — grep -rn 'FRONTMATTER_RE *=' plugin/ .claude/skills/ returns only plugin/lib/util/frontmatter.ts and *.test.ts.
  • AC-2: auto — bunx tsc --noEmit clean (no undefined FRONTMATTER_RE references) plus the AC-1 grep proves every former site now imports from @lib/util/frontmatter.
  • AC-3: auto — bun test and bun test ./.claude green via the baseline-gated quality run; the only bun test finding (an environmental task-auto-define failure needing an origin remote) is pre-existing on the baseline, not introduced here.
  • AC-4: auto — entities validate / entities audit output is byte-identical with and without the change; the baseline diff shows the lone audit drift (T-TZQ7 schema_version) as pre-existing.
  • The shared plugin/lib/util/frontmatter.ts already exported everything the callers needed (FRONTMATTER_RE, extractFrontmatter, splitFrontmatter, parseFrontmatter), so the consolidation was pure caller-side rewiring with no helper change.
  • tsc --noEmit is a strong AC-2 proxy: any site that referenced FRONTMATTER_RE without declaring or importing it would fail the typecheck, so a clean tsc plus the AC-1 grep is decisive.
  • The baseline-gated quality gate cleanly separated the 554 pre-existing findings from branch-new drift, leaving a single line to triage.
  • Step 7’s baseline-gated gate reported FAIL new-drift=1, but the line was the dashboard service test’s PROJECT/PID/URL table row (a non-deterministic PID, 36982, the baseline normalizer doesn’t scrub in that table format) — a known false positive already filed as B-105D. The runner had to manually triage the gate failure as noise. Fix: extend normalizeFinding() to scrub the bare-PID column of the dashboard-list table (or quiet that test’s stdout under the gate) so the baseline gate stays trustworthy for branches that touch no dashboard code. → T-BQRU-quality-normalize-ports-pids-timings
  • The Step 6 background heartbeat loop was reaped at each turn boundary in this harness, letting the lease lapse during the long async implementation; the runner recovered with a one-shot sdlc lease heartbeat before the Step 10 transition. Fix: task-work’s heartbeat should survive async sub-agent waits (or the runner should re-heartbeat on every re-entry after a background-agent notification). → T-P6E5-task-work-heartbeat-survives-async-waits
  • Step 7’s quality run --diff-against-baseline defaults --baseline-dir to the worktree’s gitignored .sdlc/, but Step 3a captured the baseline in the main repo’s .sdlc/; the first gate invocation failed baseline not found until --baseline-dir <main-repo>/.sdlc/quality-baselines was passed explicitly. Fix: have the gate resolve the baseline dir against the config’s project root (main repo), not cwd, so a worktree run finds the baseline without an explicit override. → T-44OO-plugin-scripts-self-discover-project-root

← Back to Tasks