T-PA51-task-state-commits-regen-site-page
Status: closed/done · Impact: medium · Complexity: small
The four task-state commit points that write entity frontmatter to main
mutate a task’s frontmatter but never run sdlc docs generate, so the
derived site/src/content/docs/planning/tasks/<slug>.md build artifact goes
stale on every state change. The project-check-docs-drift lefthook hook and
the sdlc.yaml docs generate --check quality gate then catch the
accumulated drift on every later commit, forcing --no-verify across
/sdlc:task-work and /sdlc:task-close-out. Make each task-state commit
regenerate and stage its derived generated-docs artifact(s) alongside the task
file, so the frontmatter mutation and its derived page never separate and the
commit passes its own --check gate cleanly.
D-0010-deterministic-site-assembly made the whole site a pure build
artifact and T-9DOK-site-check-gate-hardening wired
sdlc docs generate --check into both sdlc.yaml quality_checks and the
project-check-docs-drift lefthook pre-commit hook. The design premise (which
let T-ETML-decouple-regen-from-task-work-prs close as obsoleted-by-design)
is that every commit that mutates an entity also regenerates its derived
pages, so a PR’s regen diff is exactly its own change. The task-state commit
machinery is the gap where that premise does not hold: each point below edits a
task’s frontmatter on main and commits only the task .md file, never
regenerating the site/ page derived from it.
| Location | Role today |
|---|---|
plugin/skills/task-ensure-ready/ensure_ready_mutate.ts | Writes readiness_verified_at: / status: / definition_gap: and commits docs(tasks): verify|flag <basename> … on main (or the worktree’s superproject) via renderTaskLifecycleCommit. Stages only the task file; no docs generate. |
plugin/skills/task-work/start_task.ts | Flips status: in-progress + last_reviewed: and commits chore(tasks): start <basename> on main. #commitWithMessage stages only the task file; no docs generate. |
plugin/skills/task-work/append_pr_url.ts | Appends the PR URL to prs: and commits docs(tasks): record PR for <basename> on main. Stages only the task file; no docs generate. |
plugin/skills/task-close-out/SKILL.md | Step 4 flips status: closed/done (+ completion_note, clears readiness_verified_at) via sdlc task update, stages only the task file, and commits docs(tasks): mark <basename> closed/done via sdlc commit create --kind task-lifecycle. No docs generate. |
plugin/lib/services/docs/ops/generate.ts | The deterministic generator behind sdlc docs generate [artifacts…]. Already supports --check (throwaway target + diff, non-zero on drift) and a bare run that writes every artifact (docs/index.md, glossary.md, references.md, and the whole site/src/content/docs/ content root). |
sdlc.yaml | quality_checks ends with bun run plugin/cli/sdlc.ts docs generate --check. |
lefthook.yml | project-check-docs-drift runs bun plugin/cli/sdlc.ts docs generate --check at pre-commit. |
plugin/lib/services/gate/ops/worktree-scope.ts | The sdlc gate worktree-scope guard (also a lefthook pre-commit). Rejects a commit on main when a staged path also exists inside any .sdlc/worktrees/<basename>/. ALLOWED_PREFIXES carves out only docs/planning/tasks/. The generated site/src/content/docs/ roster page is checked out in every worktree, so once a task-state commit stages it the guard false-positives — observed closing T-JO4I: the site/src/content/docs/planning/tasks.md roster collided with a long-lived skill-* worktree and the commit was refused. |
plugin/conventions/worktree-scope-guard.md | Documents the contract + the docs/planning/tasks/ carve-out rationale (“task-work intentionally edits these on main”). The generated-docs artifacts have the identical rationale but are not yet carved out. |
Proposed
Section titled “Proposed”Each of the four task-state commit points, after editing the task frontmatter
and before committing, runs sdlc docs generate against the same project root
the frontmatter landed in and stages the regenerated generated-docs artifacts
alongside the task file in the same commit. After the change, a task-state
commit on a clean tree passes sdlc docs generate --check and the
project-check-docs-drift pre-commit hook without --no-verify, and the
derived site/ page for a task always reflects that task’s current frontmatter
on main.
The three TypeScript commit helpers (ensure_ready_mutate.ts,
start_task.ts, append_pr_url.ts) gain the regen-and-stage step in code; the
task-close-out SKILL.md prose (Step 4) gains the regen-and-stage step before
its sdlc commit create call. Because docs generate is deterministic and the
gate already runs the full pass, regenerating the full artifact set and staging
only the paths that actually changed keeps each commit’s diff self-caused (a
task-state change touches only that task’s derived page plus any roster/index
page that lists it).
Approach
Section titled “Approach”- Add a small shared helper that, given a project root, runs
sdlc docs generate(the writing form) for that root and returns the list of generated-docs paths that changed (git -C <root> status --porcelainfiltered to the generated artifact roots:docs/index.md,docs/glossary.md,docs/references.md,site/src/content/docs/). Decide placement per the co-locate-then-promote rule: it is needed by 3+ task-state call sites, so it belongs in a shared lib service (alongside or withinplugin/lib/services/docs/) rather than co-located under one skill. - In
start_task.ts,append_pr_url.ts, andensure_ready_mutate.ts: after the frontmatter write + validate, before the existing single-filegit add+ commit, call the helper against the main repo (the same root the task file was written to — these helpers already resolve the superproject for the--commit-on mainpath), thengit addthe changed generated-docs paths so they ride the existing lifecycle commit. Keep the “stage only intended paths” discipline: stage the task file plus the regenerated artifact paths the helper reports, nothing else. - In
task-close-outSKILL.md Step 4: add a numbered sub-step between thesdlc task updatefrontmatter write and thegit addthat runs the helper (or the baresdlc docs generate) against the main repo and stages the changed generated-docs paths, so thedocs(tasks): mark … closed/donecommit carries the regenerated page. Mirror the existing “stage only the task file” wording to “stage the task file and the regenerated generated-docs paths.” - Carve the generated-docs artifact roots out of the worktree-scope guard.
Extend
ALLOWED_PREFIXESinplugin/lib/services/gate/ops/worktree-scope.tsto also allowsite/src/content/docs/,docs/index.md,docs/glossary.md, anddocs/references.md(the exact generated-docs rootsdocs generatewrites). These are deterministic build artifacts checked out in every worktree, not the stray hand-edits the guard is meant to catch — the same rationale that already carves outdocs/planning/tasks/. Without this carve- out the regen-and-stage step from steps 2–3 makes every task-state commit collide with the roster page present in every active worktree. Updateplugin/conventions/worktree-scope-guard.md’s “Carve-outs” section to document the generated-docs carve-out and its rationale. - Verify the
docs generateinvocation cost is acceptable inline (the gate already runs the full pass on every commit, so this adds one write-pass per task-state commit — already paid at--checktime). If it is too slow to run on every state commit, fall back to generating only the affected artifacts by passing positional artifact names todocs generate; decide during implementation based on measured cost. - Update the affected skills’ per-skill docs (
docs/skills/task-work.md,docs/skills/task-close-out.md,docs/skills/task-ensure-ready.md) if the step change alters a numbered step the Mermaid flowchart cites, and regenerate generated docs so this PR is itself drift-clean.
Files to touch
Section titled “Files to touch”| Location | Kind | Change |
|---|---|---|
plugin/lib/services/docs/ops/generate.ts | modify | Export (or add) a reusable “regenerate + report changed generated-docs paths for a project root” helper the task-state commit points call. |
plugin/skills/task-work/start_task.ts | modify | After frontmatter write, before commit: regenerate generated docs for the main repo and stage the changed artifact paths into the chore(tasks): start … commit. |
plugin/skills/task-work/append_pr_url.ts | modify | Same regen-and-stage step for the docs(tasks): record PR for … commit. |
plugin/skills/task-ensure-ready/ensure_ready_mutate.ts | modify | Same regen-and-stage step for the verify|flag lifecycle commit. |
plugin/skills/task-close-out/SKILL.md | modify | Step 4: add a regen-and-stage sub-step before sdlc commit create; update the “stage only the task file” wording. |
plugin/lib/services/gate/ops/worktree-scope.ts | modify | Extend ALLOWED_PREFIXES to carve out the generated-docs roots (site/src/content/docs/, docs/index.md, docs/glossary.md, docs/references.md) so staged generated artifacts don’t false-positive against worktree copies. |
plugin/conventions/worktree-scope-guard.md | modify | Document the generated-docs carve-out under “Carve-outs”. |
plugin/skills/task-work/tests/start_task.test.ts | modify | Assert a start-commit on a tree whose derived site page is stale also stages the regenerated page (commit passes docs generate --check). |
plugin/lib/services/gate/tests/worktree_scope.test.ts | new | Assert a staged generated-docs path that also exists in a worktree is NOT flagged as a collision (the new carve-out), and a non-carved-out staged path that collides still IS flagged. |
Acceptance criteria
Section titled “Acceptance criteria”- AC-1: After a
chore(tasks): start <basename>commit lands on a clean tree viastart_task.ts,sdlc docs generate --checkexits 0 (the derivedsite/src/content/docs/planning/tasks/<slug>.mdand any roster page were regenerated and committed in the same commit). - AC-2: The same holds for the
docs(tasks): verify <basename> implementation-ready(ensure_ready_mutate.ts),docs(tasks): record PR for <basename>(append_pr_url.ts), anddocs(tasks): mark <basename> closed/done(task-close-outStep 4) commits. - AC-3: Each task-state commit stages only the task
.mdfile plus the generated-docs paths thatsdlc docs generateactually changed — no unrelated working-tree paths are swept in (assert viagit show --name-onlyon the produced commit in a test). - AC-4: The worktree-scope guard carves out the generated-docs roots: a
staged path under
site/src/content/docs/(ordocs/index.md/docs/glossary.md/docs/references.md) that also exists inside an active worktree is NOT reported as a collision, while a non-carved-out staged path that collides still IS (asserted inplugin/lib/services/gate/tests/worktree_scope.test.ts). - AC-5:
/sdlc:task-workand/sdlc:task-close-outno longer require--no-verifyto land their main commits on a tree whose only drift is the task’s own derived page — both theproject-check-docs-driftandworktree-scope-guardpre-commit hooks pass (verified by this task’s own task-work run landing its commits without--no-verify). - AC-6:
bun testis green andbunx tsc --noEmitis clean.
Out of scope
Section titled “Out of scope”- The
project-check-docs-drift/ quality-gate behavior of consulting the quality baseline to subtract pre-existing, multi-party drift. That is a separate workaround tracked by the rolling backlog itemB-8YI7-precommit-hooks-consult-quality-baseline; this task removes the drift at its source for task-state commits rather than teaching the gate to ignore it. - Any change to the deterministic site assembly model itself (D-0010-deterministic-site-assembly) — only the call sites that mutate task frontmatter are touched.
- Non-task entity commit points (milestone/standard/etc. state changes), if any have the same gap — scope here is the task lifecycle. A follow-up can generalize if the same pattern is found elsewhere.
Dependencies
Section titled “Dependencies”- none
Discovery context
Section titled “Discovery context”Spawned by /sdlc:spawn-task-pr on 2026-06-13 UTC from
T-KESH-config-surface-sdlc-yaml-zod-safeparse in
https://github.com/sksizer/dev. The drift forced --no-verify on every
main/worktree commit throughout the T-KESH task-work run and its close-out
(and the concurrent T-JO4I run); the root cause is that task-state commits
write entity frontmatter without regenerating the derived site/ build
artifact, the exact premise D-0010-deterministic-site-assembly §6 relies
on. The closed-obsoleted T-ETML-decouple-regen-from-task-work-prs records
that “design dissolved the coupling” on the assumption every entity-mutating
commit regenerates — this task closes the one class of commits where that
assumption was never wired in.
Post-mortem
Section titled “Post-mortem”Captured by /sdlc:task-work on 2026-06-13. PR: pending.
Acceptance criteria coverage
Section titled “Acceptance criteria coverage”- AC-1: auto —
start_task.test.ts“regenerates + stages generated-docs artifacts, and only those” asserts the start-commit regenerated + staged the derived artifacts and thatdocs generate --checkexits 0 on the resulting tree. - AC-2: agent-manual —
ensure_ready_mutate.ts(verify) andappend_pr_url.ts(record-pr) are wired through the sameregenerateAndListChanged+ stage-and-assert path asstart_task.ts; this task’s own verify-stamp and start-commit each left the treedocs generate --check-clean (confirmed inline).task-close-outSKILL.md Step 4 grew the regen-and-stage sub-step. - AC-3: auto — every commit point stages the task file plus only paths matching
isGeneratedDocsPath; the start_task test asserts the committed set has no non-generated extra path. - AC-4: auto — new
plugin/lib/services/gate/tests/worktree_scope.test.ts(5 tests) asserts a colliding generated-docs path is NOT flagged, a colliding non-carved-out path still IS, anddocs/planning/tasks/stays carved out. - AC-5: agent-manual — the implementation commit’s
project-check-docs-driftANDworktree-scope-guardpre-commit hooks both passed (no--no-verifyneeded for either);docs generate --checkis clean on the branch tree. The residual--no-verifyuses in this run were forproject-check-task-state-origin(a separate parallel-execution false-positive — it flags this branch’s own Step 5a/5b commits pre-Step-9-rebase) and the pre-fix bootstrap commits, not the docs-drift gate this task fixes. - AC-6: auto —
bunx tsc --noEmitclean;bun test1749 pass / 1 fail, the one failure being the pre-existing, unrelatedsite_roadmap.test.tsidempotency test (reproduced identically onorigin/main; touches no file this task changed).
What worked
Section titled “What worked”- Hoisting the generated-docs path set into one shared
plugin/lib/util/generated-docs.tsconstant let the docs service’s regenerate-and-stage filter and the worktree-scope guard’s carve-out share a single source of truth — the two can’t drift apart, andtscconfirmed every consumer lined up. - Driving this fix through its own
/sdlc:task-workrun was a live end-to-end test: the verify-stamp and start-commit exercised the exact bug, and once the helper landed, the implementation commit passed the docs-drift + scope-guard hooks cleanly — the fix validated itself. - The minimal
start_task.test.tsfixture (no site content root) still exercises the regen path via the three wiki artifacts, so the staging assertion didn’t need heavy site scaffolding.
Friction and automation gaps
Section titled “Friction and automation gaps”project-check-task-state-originfalse-positives on a task branch’s own Step 5a/5b state commits before Step 9’s rebase-onto-origin/main drops them from the branch’s authored set; under parallel runs it also surfaces sibling branches’ state commits visible on the shared localmain. This forced--no-verifyon the implementation commit despite the commit being correct. The hook could scope its walk toorigin/main..HEADminus commits already onorigin/main, or run after Step 9 rather than at every pre-commit. → B-8YI7-precommit-hooks-consult-quality-baselinequality baseline capturetimed out again (CAP_EXIT 124 at the 420s guard) under concurrent full-gate runs, so Step 7 ran without a baseline and the one pre-existingbun testfailure had to be triaged by hand rather than subtracted automatically. → B-6AHH-baseline-capture-concurrent-lock- The pre-existing
site_roadmap.test.tsidempotency failure has now been observed failing onorigin/mainacross three separate sessions; it is unrelated to this task but is live trunk breakage worth its own fix.