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.
| Location | Role today |
|---|---|
plugin/lib/util/frontmatter.ts | The intended shared home: FRONTMATTER_RE + extractFrontmatter / splitFrontmatter / parseFrontmatter |
plugin/lib/model/ops/validate.ts | Re-declares a private FRONTMATTER_RE to peek type: |
plugin/lib/model/ops/audit.ts | Re-declares FRONTMATTER_RE for schema_version / status / depends_on peeks |
plugin/lib/model/entity.ts | Re-declares FRONTMATTER_RE for template stripping |
plugin/skills/task-ensure-ready/ensure_ready_mutate.ts | Re-declares FRONTMATTER_RE for the read half of a read-modify-write |
plugin/lib/services/lease/ops/task/transition.ts | Re-declares FRONTMATTER_RE (read half of a lease transition) — ~13 more like it, all in Files to touch |
Proposed
Section titled “Proposed”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.
Approach
Section titled “Approach”- Audit the call sites and classify each as read-only (peek a field, or split frontmatter + body) versus read-modify-write.
- For read-only sites, replace the private regex with
util/frontmatter’sextractFrontmatter/splitFrontmatter/parseFrontmatter. - For write sites (
ensure_ready_mutate,transition,_update,migrate), import the shared regex / split for the read half and keep the write logic local. - Delete every now-unused private
FRONTMATTER_RE; run the fullbun testsuite and asdlc entities validate/auditsmoke; confirm no behavior change.
Files to touch
Section titled “Files to touch”| Location | Kind | Change |
|---|---|---|
plugin/lib/util/frontmatter.ts | modify | Ensure it exports the regex + extract / split / parse the callers need |
plugin/lib/model/ops/validate.ts | modify | type: peek via the shared extractor; drop the private regex |
plugin/lib/model/ops/audit.ts | modify | Field peeks via the shared extractor; drop the private regex |
plugin/lib/model/ops/_update.ts | modify | Read half via the shared split; keep write; drop the private regex |
plugin/lib/model/ops/migrate.ts | modify | Read half via the shared split; drop the private regex |
plugin/lib/model/entity.ts | modify | Template-strip via the shared extractor; drop the private regex |
plugin/lib/model/entities/task/ops/_task_doc.ts | modify | Shared split; drop the private regex |
plugin/lib/model/entities/task/ops/scan-placeholders.ts | modify | Shared split; drop the private regex |
plugin/lib/model/entities/task/claims/paths.ts | modify | Shared extractor; drop the private regex |
plugin/lib/model/entities/task/claims/quantifiers.ts | modify | Shared extractor; drop the private regex |
plugin/lib/services/lease/migration.ts | modify | Read half via the shared split; drop the private regex |
plugin/lib/services/lease/ops/task/transition.ts | modify | Read half via the shared split; keep write; drop the private regex |
plugin/lib/util/markdown_extract.ts | modify | Use the shared regex in stripFrontmatter; drop the private regex |
.claude/skills/project-check/check_entities.ts | modify | Shared extractor; drop the private regex |
plugin/skills/task-ensure-ready/ensure_ready_mutate.ts | modify | Read half via the shared split; keep write; drop the private regex |
plugin/skills/task-ensure-ready/scan_corpus_assumptions.ts | modify | Shared split; drop the private regex |
plugin/skills/task-work/dedup_search.ts | modify | Shared split; drop the private regex |
plugin/skills/task-work/start_task.ts | modify | Shared split for the frontmatter read; drop the private regex |
Acceptance criteria
Section titled “Acceptance criteria”- AC-1:
grep -rn 'FRONTMATTER_RE *=' plugin/ .claude/skills/returns matches only inplugin/lib/util/frontmatter.tsand*.test.tsfiles. - AC-2: Every module formerly declaring
FRONTMATTER_REimports the regex / extractor fromplugin/lib/util/frontmatter.ts. - AC-3:
bun testpasses repo-wide andbun test ./.claudepasses. - AC-4:
sdlc entities validateandsdlc entities auditproduce unchanged output on the current corpus (smoke check).
Out of scope
Section titled “Out of scope”- 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.
Dependencies
Section titled “Dependencies”- 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 ownFRONTMATTER_REas a side effect). Land this last, or coordinate, so the set shrinks rather than conflicts.
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 —
grep -rn 'FRONTMATTER_RE *=' plugin/ .claude/skills/returns onlyplugin/lib/util/frontmatter.tsand*.test.ts. - AC-2: auto —
bunx tsc --noEmitclean (no undefinedFRONTMATTER_REreferences) plus the AC-1 grep proves every former site now imports from@lib/util/frontmatter. - AC-3: auto —
bun testandbun test ./.claudegreen via the baseline-gatedquality run; the onlybun testfinding (an environmentaltask-auto-definefailure needing anoriginremote) is pre-existing on the baseline, not introduced here. - AC-4: auto —
entities validate/entities auditoutput is byte-identical with and without the change; the baseline diff shows the lone audit drift (T-TZQ7schema_version) as pre-existing.
What worked
Section titled “What worked”- The shared
plugin/lib/util/frontmatter.tsalready exported everything the callers needed (FRONTMATTER_RE,extractFrontmatter,splitFrontmatter,parseFrontmatter), so the consolidation was pure caller-side rewiring with no helper change. tsc --noEmitis a strong AC-2 proxy: any site that referencedFRONTMATTER_REwithout 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.
Friction and automation gaps
Section titled “Friction and automation gaps”- Step 7’s baseline-gated gate reported
FAIL new-drift=1, but the line was the dashboard service test’sPROJECT/PID/URLtable 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: extendnormalizeFinding()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 heartbeatbefore 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-baselinedefaults--baseline-dirto the worktree’s gitignored.sdlc/, but Step 3a captured the baseline in the main repo’s.sdlc/; the first gate invocation failedbaseline not founduntil--baseline-dir <main-repo>/.sdlc/quality-baselineswas 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
Spawned follow-up tasks
Section titled “Spawned follow-up tasks”- T-P6E5-task-work-heartbeat-survives-async-waits (https://github.com/sksizer/dev/pull/522) — task-work heartbeat survives async sub-agent waits; spawned (Upstream-plugin, sdlc-meta).
- T-BQRU-quality-normalize-ports-pids-timings — dashboard-list bare-PID baseline-normalizer gap; linked (active open/ready owner; supersedes abandoned B-105D).
- T-44OO-plugin-scripts-self-discover-project-root — worktree-vs-main baseline-dir self-discovery; linked (active tracker, eighth post-mortem on this friction).