T-7FPF-baseline-capture-against-origin-main-tree
Status: open/ready · Impact: medium · Complexity: small
AUTO-DEFINED: this spec was best-effort machine-authored by /sdlc:task-auto-define on 2026-07-04. Review the Goal, Approach, Today, Files-to-touch, and Acceptance-criteria carefully before trusting it.
sdlc quality baseline capture <sha> runs its quality verbs against whatever
tree is checked out at the caller’s cwd, but labels the captured output with the
resolved origin/main SHA. When local main has diverged from origin/main
(commits landed locally but not pushed, or a completed migration), the baseline
reflects the local corpus while claiming to be origin/main’s — so the Step 7
baseline-gated diff subtracts against a mislabeled tree and emits false
new-drift: lines. Close the gap by capturing the baseline against the resolved
origin/main tree itself (an ephemeral worktree/checkout at the SHA) so the
corpus the verbs see matches the SHA the output is labeled with.
Quote from the originating post-mortem:
sdlc quality baseline capture <sha>runs its verbs against the caller’s cwd/working tree while labelling the output with origin/main’s SHA. When local main is ahead of or corpus-divergent from origin/main (a completed migration), the baseline-gated diff in task-work Step 7 emits false new-drift lines. Fix: capture the baseline against the resolved origin/main tree (an ephemeral worktree checked out at the SHA) so the tree matches the label.— from the post-mortem of T-PDDK-gs-scaffold-package-and-tooling
This is distinct from the in-flight baseline-dir cluster (tasks
T-2GGI-step7-baseline-dir-from-main-repo, T-P6OB-task-work-baseline-dir-main-repo,
T-W2TM-baseline-dir-resolves-from-superproject,
T-4U2H-quality-run-baseline-dir-from-worktree and siblings). That cluster fixes
where the baseline files live (the main-repo / superproject .sdlc/ vs the
worktree’s own .sdlc/) and its failure mode is baseline not found. This task
is a different axis — which corpus/tree the capture verbs execute against — and
its failure mode is false new-drift: when the capture tree differs from the
labeled SHA’s tree. The two fixes are orthogonal and can land independently.
| Location | Role today |
|---|---|
plugin/lib/services/quality/baseline.ts#capture | Runs every declared verb, then writes <baseline-dir>/<sha>.json. Executes the verbs against the projectRoot it is handed — the caller’s working tree — while labelling the payload sha: <sha>. Corpus and label are decoupled. |
plugin/lib/services/quality/baseline.ts#runOne | Spawns one verb via spawnSync(verb, { shell, cwd }); cwd is the projectRoot capture passed, i.e. the caller’s checked-out tree. |
plugin/lib/services/quality/ops/baseline/capture.ts | The op. Passes realResolve(ctx.projectRoot) (the caller’s cwd) as capture’s projectRoot and the positional <sha> as the label — the point where the two diverge. |
plugin/skills/task-work/SKILL.md | Step 3a resolves ORIGIN_MAIN_SHA=$(git rev-parse origin/main) and calls quality baseline capture "$ORIGIN_MAIN_SHA" from the project root; Step 7 gates HEAD against that baseline. When local main differs from origin/main, the captured corpus is the local tree but the label is origin/main’s SHA. |
plugin/lib/services/git/commit-to-main.ts#commitToMainViaWorktree | Existing isolation precedent: creates a throwaway detached worktree off a committish under <mainCheckout>/.sdlc/ (so node_modules resolves upward), does its work there, and removes the worktree in a finally. |
Proposed
Section titled “Proposed”sdlc quality baseline capture <sha> executes its verbs against the tree that
<sha> actually names — a read-only ephemeral git worktree checked out at
<sha> — so the captured corpus matches the SHA the payload is labelled with.
When <sha> cannot be resolved to a committish (an arbitrary label, or
projectRoot not inside a git repo), capture falls back to the current
behaviour — verbs against projectRoot — so the label-only usage keeps
working. The result: Step 7’s baseline-gated diff subtracts against
origin/main’s real tree and stops emitting false new-drift: lines when local
main has diverged.
Approach
Section titled “Approach”- Add a helper in
baseline.ts(co-located, modelled oncommitToMainViaWorktree’s isolation shape) that, givenprojectRootandsha, tries to materialise an ephemeral worktree atsha: resolve the main checkout (git -C <projectRoot> rev-parse --path-format=absolute --git-common-dir, take its parent); verifyshais a commit (git -C <mainCheckout> rev-parse --verify --quiet <sha>^{commit}); if it resolves,git worktree add --quiet --detach <mainCheckout>/.sdlc/quality-baseline-worktrees/<sha>.<pid>.<ts> <sha>and return that path; otherwise returnnull. - In
capture, call the helper before the verb loop. When it returns a worktree path, use that path as thecwdfor everyrunOne; when it returnsnull, keep usingprojectRoot(the unchanged fallback). - Wrap the verb loop in
try { … } finally { … }so the ephemeral worktree is always torn down:git worktree remove --force <wt>thengit worktree prune(a no-op when the helper returnednull). Place the worktree under.sdlc/— gitignored,node_modulesresolves upward — exactly ascommit-to-main.tsdoes, so dependency-resolving verbs still run. - Keep reading the verb list from the caller’s
sdlcYamlPath/keyand keep writing the baseline into the caller’sbaselineDir; only the verb-executioncwdmoves to the SHA’s tree. LeavenormalizeFinding,diff,prune, and the on-disk payload shape untouched. - Add a test in
quality_ops.test.ts: a non-committish label (e.g.testsha1) still captures via theprojectRootfallback (the existing roundtrip stays green), and a real committish is captured against its own worktree tree rather than a dirtied working tree. - Update the
task-workSKILL.md capture prose (Step 3a and the pre-PR dogfood) to state capture runs against the resolvedorigin/maintree, not the working directory — the “call it from<project-root>” note no longer implies the cwd is the captured corpus.
Files to touch
Section titled “Files to touch”| Location | Kind | Change |
|---|---|---|
plugin/lib/services/quality/baseline.ts#capture | modify | Materialise an ephemeral worktree at <sha> and run verbs there (fallback to projectRoot when <sha> is not a resolvable committish); add the co-located worktree helper; tear the worktree down in a finally. |
plugin/lib/services/quality/tests/quality_ops.test.ts | modify | Add coverage: a non-committish label falls back to projectRoot (roundtrip stays green); a committish <sha> is captured against its own worktree tree, not a dirtied working tree. |
plugin/skills/task-work/SKILL.md | modify | Correct the Step 3a and pre-PR-dogfood capture prose to say verbs run against the resolved origin/main tree, not the caller’s cwd. |
Acceptance criteria
Section titled “Acceptance criteria”- AC-1:
capture()runs its verbs withcwdset to an ephemeral worktree checked out at<sha>whenever<sha>resolves to a commit inprojectRoot’s repo — assertable by capturing with a working tree whose contents differ from<sha>and confirming the payload reflects<sha>’s tree rather than the working tree. - AC-2: When
<sha>is not a resolvable committish (orprojectRootis not inside a git repo),capture()falls back to running verbs againstprojectRoot; the existingquality_ops.test.tsroundtrip tests that pass labels liketestsha1pass unchanged. - AC-3: The ephemeral worktree is created under
<mainCheckout>/.sdlc/and removed viagit worktree remove --forcein afinally;git worktree listshows no residualquality-baselineworktree aftercapture()returns. - AC-4: With local
maindiverged fromorigin/main, capturing at origin/main’s SHA and then runningquality run --diff-against-baseline <sha>against an unchanged origin/main tree yields zeronew-drift:lines — demonstrable. - AC-5:
plugin/lib/services/quality/tests/quality_ops.test.tspasses underbun test.
Out of scope
Section titled “Out of scope”- Where baseline files are stored (the
baselineDir/ superproject.sdlc/cluster: T-2GGI, T-P6OB, T-W2TM, T-4U2H) — an orthogonal axis whose failure mode isbaseline not found; left untouched. - The
normalizeFinding/diffmasking logic and the on-disk baseline payload shape — unchanged. - Dependency install /
worktree_initinside the ephemeral capture worktree — a separate concern; this task relies onnode_modulesresolving upward from the.sdlc/placement, ascommit-to-main.tsalready does. - The
quality baseline captureop’s input / CLI contract (plugin/lib/services/quality/ops/baseline/capture.ts) — the change lives in the core; the op signature is unchanged.
Dependencies
Section titled “Dependencies”- none
Discovery context
Section titled “Discovery context”Spawned by /sdlc:spawn-task-pr on 2026-07-04 UTC from T-PDDK-gs-scaffold-package-and-tooling in the sksizer/dev repo.
Dedup search (spawn-from-post-mortem)
Section titled “Dedup search (spawn-from-post-mortem)”Linked from the post-mortem of T-1YSW-amend-d0001-solutions-tier on 2026-07-19 rather than spawning a duplicate. That post-mortem recorded the same root cause with fresh evidence:
quality baseline capture <sha>runs the verbs against the CURRENT working tree while keying the cache on<sha>. When main advanced between capture and gate, 10 findings in an untouched file surfaced asnew-drift:and failed the gate. Capture must materialize the SHA’s tree (an off-SHA worktree) rather than measuring cwd.
Rationale for the override: dedup_search.ts ranked only closed tasks
(T-44OO, T-TWZD, T-BQRU, T-H69K) above this one, because its keyword scoring
counts full-body occurrences and this task’s body is short. Manual corpus
review found T-7FPF’s Goal states the identical gap, so the link was made by
judgment against the script’s SPAWNED recommendation.