Skip to content

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.

LocationRole today
plugin/lib/services/quality/baseline.ts#captureRuns 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#runOneSpawns 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.tsThe 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.mdStep 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#commitToMainViaWorktreeExisting 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.

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.

  1. Add a helper in baseline.ts (co-located, modelled on commitToMainViaWorktree’s isolation shape) that, given projectRoot and sha, tries to materialise an ephemeral worktree at sha: resolve the main checkout (git -C <projectRoot> rev-parse --path-format=absolute --git-common-dir, take its parent); verify sha is 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 return null.
  2. In capture, call the helper before the verb loop. When it returns a worktree path, use that path as the cwd for every runOne; when it returns null, keep using projectRoot (the unchanged fallback).
  3. Wrap the verb loop in try { … } finally { … } so the ephemeral worktree is always torn down: git worktree remove --force <wt> then git worktree prune (a no-op when the helper returned null). Place the worktree under .sdlc/ — gitignored, node_modules resolves upward — exactly as commit-to-main.ts does, so dependency-resolving verbs still run.
  4. Keep reading the verb list from the caller’s sdlcYamlPath/key and keep writing the baseline into the caller’s baselineDir; only the verb-execution cwd moves to the SHA’s tree. Leave normalizeFinding, diff, prune, and the on-disk payload shape untouched.
  5. Add a test in quality_ops.test.ts: a non-committish label (e.g. testsha1) still captures via the projectRoot fallback (the existing roundtrip stays green), and a real committish is captured against its own worktree tree rather than a dirtied working tree.
  6. Update the task-work SKILL.md capture prose (Step 3a and the pre-PR dogfood) to state capture runs against the resolved origin/main tree, not the working directory — the “call it from <project-root>” note no longer implies the cwd is the captured corpus.
LocationKindChange
plugin/lib/services/quality/baseline.ts#capturemodifyMaterialise 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.tsmodifyAdd 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.mdmodifyCorrect the Step 3a and pre-PR-dogfood capture prose to say verbs run against the resolved origin/main tree, not the caller’s cwd.
  • AC-1: capture() runs its verbs with cwd set to an ephemeral worktree checked out at <sha> whenever <sha> resolves to a commit in projectRoot’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 (or projectRoot is not inside a git repo), capture() falls back to running verbs against projectRoot; the existing quality_ops.test.ts roundtrip tests that pass labels like testsha1 pass unchanged.
  • AC-3: The ephemeral worktree is created under <mainCheckout>/.sdlc/ and removed via git worktree remove --force in a finally; git worktree list shows no residual quality-baseline worktree after capture() returns.
  • AC-4: With local main diverged from origin/main, capturing at origin/main’s SHA and then running quality run --diff-against-baseline <sha> against an unchanged origin/main tree yields zero new-drift: lines — demonstrable.
  • AC-5: plugin/lib/services/quality/tests/quality_ops.test.ts passes under bun test.
  • Where baseline files are stored (the baselineDir / superproject .sdlc/ cluster: T-2GGI, T-P6OB, T-W2TM, T-4U2H) — an orthogonal axis whose failure mode is baseline not found; left untouched.
  • The normalizeFinding / diff masking logic and the on-disk baseline payload shape — unchanged.
  • Dependency install / worktree_init inside the ephemeral capture worktree — a separate concern; this task relies on node_modules resolving upward from the .sdlc/ placement, as commit-to-main.ts already does.
  • The quality baseline capture op’s input / CLI contract (plugin/lib/services/quality/ops/baseline/capture.ts) — the change lives in the core; the op signature is unchanged.
  • none

Spawned by /sdlc:spawn-task-pr on 2026-07-04 UTC from T-PDDK-gs-scaffold-package-and-tooling in the sksizer/dev repo.

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 as new-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.


← Back to Tasks