T-OLTA-task-work-step7-worktree-baseline-dir
Status: open/ready · Impact: medium · Complexity: small
AUTO-DEFINED: this spec was best-effort machine-authored by /sdlc:task-auto-define on 2026-07-19 because the task is autonomy: autonomous/pr. Review the Goal, Approach, Today, Files-to-touch, and Acceptance-criteria carefully before trusting it.
/sdlc:task-work Step 7’s baseline-gated quality gate cannot find the
quality baseline when the task runs from a worktree, so the gate fails
open with baseline not found instead of diffing against the captured
baseline. The baseline lives in the main checkout but the worktree’s
default --baseline-dir resolves to the worktree’s own (empty) .sdlc/.
This task makes the gate resolve the baseline from a checkout-shared
location so it runs meaningfully from any worktree.
Spawned from the post-mortem of T-JV2A-adopt-augmented-books-desktop
in git@github.com:sksizer/dev.git:
task-work Step 7’s baseline-gated quality gate fails with ‘baseline not found’ when run from a worktree: the default
--baseline-dirresolves to the empty worktree.sdlc/instead of the main checkout’s.sdlc/quality-baselines/. Fix by having Step 7 pass an explicit--baseline-dirpointing at the main checkout, or capture the baseline into a checkout-shared location. Prior tasks T-5X6Y (superseded) and T-44OO (obsoleted) never shipped, so the bug is still live.
| Location | Role today |
|---|---|
apps/sdlc/lib/services/quality/ops/run.ts | The sdlc quality run op. When --diff-against-baseline is set and --baseline-dir is omitted, the baseline dir defaults to <projectRoot>/.sdlc/quality-baselines/; a missing <sha>.json there throws baseline not found. |
apps/sdlc/lib/config/load.ts#resolveProjectRoot | Resolves ctx.projectRoot by walking up from cwd to the first sdlc.yaml. A git worktree carries its own sdlc.yaml, so from a worktree this returns the worktree root — not the main checkout. |
apps/sdlc/skills/task-work/SKILL.md | Step 3a captures the baseline into the main checkout’s .sdlc/quality-baselines/ while still on main; Step 7 runs the gate from the worktree with no --baseline-dir, so the worktree-rooted default misses the captured file. |
apps/sdlc/lib/services/git/commit-to-main.ts#resolveMainCheckout | Existing resolver for the main checkout (git-common-dir’s parent), correct even from a worktree — the pattern this fix reuses. |
apps/sdlc/lib/util/git.ts#isLinkedWorktree | Shared worktree helpers (WORKTREES_DIR_REL, isLinkedWorktree) — the natural home for a shared main-checkout resolver. |
Proposed
Section titled “Proposed”Resolve the baseline directory from a checkout-shared location so the Step 7
gate finds the baseline captured on main from any linked worktree. When
--baseline-dir is not passed, sdlc quality run defaults to the main
checkout’s .sdlc/quality-baselines/ (the git-common-dir parent) instead of
the current worktree’s own .sdlc/. Capture (Step 3a, on main) and gate
(Step 7, in a worktree) then agree on one shared cache with no per-run path
plumbing, and an explicit --baseline-dir still overrides it.
Approach
Section titled “Approach”- Add an exported
mainCheckoutRoot(projectRoot: string): stringhelper toapps/sdlc/lib/util/git.tsthat runsgit -C <projectRoot> rev-parse --path-format=absolute --git-common-dirand returns the parent directory of the reported common.gitdir. On any non-zero exit or empty output (not a git repo, git unavailable) it returnsprojectRootunchanged. This centralizes the git-common-dir-parent logic already inlined incommit-to-main.tsanddocs/generate.ts. - In
apps/sdlc/lib/services/quality/ops/run.ts, change the baseline-dir default: whenargs.diffAgainstBaselineis set andargs.baselineDiris undefined, resolve torealResolve(join(mainCheckoutRoot(projectRoot), ".sdlc", "quality-baselines"))instead ofrealResolve(join(projectRoot, ".sdlc", "quality-baselines")). Import the helper from@lib/util/git. An explicit--baseline-dirstill wins (the branch is unchanged when the flag is present). - Keep the
baseline not founderror, ensuring it prints the resolved (checkout-shared) path so a genuine miss stays diagnosable. - Update
apps/sdlc/skills/task-work/SKILL.mdStep 3a’s note (the paragraph claiming the gate-time default is<project-root>/.sdlc/quality-baselines/): state that the gate-time default now resolves to the main checkout’s.sdlc/quality-baselines/(git-common-dir parent), which is why capturing on main and gating from a worktree agree. Leave Step 7’s invocation unchanged — it relies on the corrected default. - Add a test to
apps/sdlc/lib/services/quality/tests/quality_ops.test.ts: create a temp git repo, capture a baseline in the main checkout, add a linked worktree, runquality run --diff-against-baseline <sha>with the project root set to the worktree and no--baseline-dir, and assert the gate reads the main checkout’s baseline (nobaseline not found). - Run the project’s quality-check verbs (type-check plus the quality service test suite) and confirm they pass.
Files to touch
Section titled “Files to touch”| Location | Kind | Change |
|---|---|---|
apps/sdlc/lib/util/git.ts | modify | Add exported mainCheckoutRoot(projectRoot) helper (git-common-dir parent; falls back to projectRoot). |
apps/sdlc/lib/services/quality/ops/run.ts | modify | Default the --diff-against-baseline baseline dir to mainCheckoutRoot(projectRoot)/.sdlc/quality-baselines when --baseline-dir is omitted; import the helper. |
apps/sdlc/skills/task-work/SKILL.md | modify | Correct Step 3a’s gate-time-default note to describe the checkout-shared (main-checkout) baseline location. |
apps/sdlc/lib/services/quality/tests/quality_ops.test.ts | modify | Add a worktree baseline-resolution test asserting the gate finds the main-checkout baseline with no --baseline-dir. |
Acceptance criteria
Section titled “Acceptance criteria”- AC-1:
apps/sdlc/lib/util/git.tsexports amainCheckoutRootfunction that returns the git-common-dir parent for a linked worktree and returns the passedprojectRootunchanged when the path is not a git repo. - AC-2: In
apps/sdlc/lib/services/quality/ops/run.ts, when--diff-against-baselineis set and--baseline-diris omitted, the resolved baseline directory is<mainCheckoutRoot>/.sdlc/quality-baselines/rather than<projectRoot>/.sdlc/quality-baselines/. - AC-3: An explicit
--baseline-dirargument still overrides the default (the new resolution applies only when the flag is absent). - AC-4: A test in
apps/sdlc/lib/services/quality/tests/quality_ops.test.tscaptures a baseline in a main checkout, runssdlc quality run --diff-against-baseline <sha>from a linked worktree with no--baseline-dir, and asserts the gate resolves the main-checkout baseline instead of throwingbaseline not found. - AC-5:
apps/sdlc/skills/task-work/SKILL.mdStep 3a no longer claims the gate-time default is<project-root>/.sdlc/quality-baselines/; it describes the checkout-shared main-checkout location. - AC-6: The project’s quality-check verbs (type-check and the quality service test suite) pass on the change.
Out of scope
Section titled “Out of scope”- The alternative fix of having task-work Step 7 pass an explicit
--baseline-dirper run instead of correcting the op’s default resolution. - Backfilling or migrating baseline JSON files already written under existing worktrees’
.sdlc/quality-baselines/. - Changing the
sdlc quality baseline captureop, whose--baseline-diris a required argument (callers already pass an explicit, main-checkout-rooted path). - Refactoring the duplicate git-common-dir-parent logic in
commit-to-main.tsanddocs/generate.tsto call the new helper (a separate cleanup).
Dependencies
Section titled “Dependencies”- none
Discovery context
Section titled “Discovery context”Spawned by /sdlc:spawn-task-pr on 2026-07-19 UTC from
T-JV2A-adopt-augmented-books-desktop in git@github.com:sksizer/dev.git.
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 — a second independent observation of this gap, recorded there as already tracked here:
Step 7’s
--baseline-dirdefault resolves from the worktree, not the superproject, so the gate could not find the captured baseline until the flag was passed explicitly.
No duplicate spawned.
Dedup search (spawn-from-post-mortem, from T-B98V)
Section titled “Dedup search (spawn-from-post-mortem, from T-B98V)”Bullet: Step 7’s --diff-against-baseline gate resolved the baseline dir from the WORKTREE
(.sdlc/worktrees/<t>/.sdlc/quality-baselines/) while Step 3a captured into the main checkout —
needed a manual copy; exactly the gap T-OLTA-task-work-step7-worktree-baseline-dir already
tracks Keywords searched: t-olta-task-work-step7-worktree-baseline-dir, diff-against-baseline,
quality-baselines, worktrees, resolved, baseline, worktree, checkout Excluded: T-B98V-adopt-vimit
Top candidates (score / status / headline):
- 287 / closed/obsoleted / T-44OO-plugin-scripts-self-discover-project-root — Plugin scripts self-discover project root from cwd
- 172 / closed/done / T-TWZD-normalize-baseline-diff-nondeterministic-output — run_quality_checks —diff-against-baseline masks ephemeral tmpdir paths and transient SHAs before line-diffing
- 123 / open/ready / T-OLTA-task-work-step7-worktree-baseline-dir — task-work Step 7 resolves the quality baseline dir from a worktree
- 105 / closed/superseded / T-5X6Y-task-work-step7-explicit-baseline-dir — task-work Step 7 must pass —baseline-dir explicitly to defeat silent fallback in worktree
- 89 / closed/done / T-G834-move-plugin-runtime-state-to-sdlc-dir — Migrate sdlc plugin runtime state from .claude/ to .sdlc/ Decision: SPAWNED Rationale: Script decision overridden to LINKED-EXISTING — the bullet itself names this task as already tracking the gap, and PR #921 is open for it. Linked from the post-mortem of T-B98V-adopt-vimit on 2026-07-31; a third independent observation of this gap. No duplicate spawned.
Dedup search (spawn-from-post-mortem, from T-JOXA)
Section titled “Dedup search (spawn-from-post-mortem, from T-JOXA)”Bullet: Step 7’s gate invocation resolved —baseline-dir relative to the worktree (where no baseline exists) and failed with baseline not found, even though Step 3a wrote it under the main repo. The skill calls —baseline-dir optional in spirit; it is in fact required whenever the gate runs from a worktree, and the Step 7 snippet should pass it explicitly like the Step 3a snippet does. Keywords searched: baseline-dir, invocation, explicitly, resolved, relative, worktree, baseline, optional Excluded: T-JOXA-task-kind-field-and-leaf-dispatch Top candidates (score / status / headline):
- 363 / closed/obsoleted / T-44OO-plugin-scripts-self-discover-project-root — Plugin scripts self-discover project root from cwd
- 167 / closed/done / T-TWZD-normalize-baseline-diff-nondeterministic-output — run_quality_checks —diff-against-baseline masks ephemeral tmpdir paths and transient SHAs before line-diffing
- 128 / closed/superseded / T-5X6Y-task-work-step7-explicit-baseline-dir — task-work Step 7 must pass —baseline-dir explicitly to defeat silent fallback in worktree
- 128 / open/ready / T-OLTA-task-work-step7-worktree-baseline-dir — task-work Step 7 resolves the quality baseline dir from a worktree
- 99 / closed/done / T-2T20-task-work-backgrounds-quality-gate — task-work backgrounds the Step 7 quality gate and passes an explicit main-repo baseline-dir
Decision: SPAWNED
Rationale: Script decision overridden to LINKED-EXISTING — the scorer’s top three are all
closed (obsoleted / done / superseded) long files that win on raw keyword count; the live
match is rank 4, this task (open/ready, relevance-verified 2026-08-01). Linked from the
post-mortem of T-JOXA-task-kind-field-and-leaf-dispatch on 2026-08-03; a fourth
independent observation of this gap. Also confirmed live against the current tree:
apps/sdlc/skills/task-work/SKILL.md:406 still calls --baseline-dir “optional in spirit”
and Step 7’s invocation at line 831 still omits it. No duplicate spawned.
Dedup search (spawn-from-post-mortem, from T-HTN8)
Section titled “Dedup search (spawn-from-post-mortem, from T-HTN8)”Bullet: Step 7’s documented sdlc quality run --diff-against-baseline invocation omits
--baseline-dir, and the default resolves against the run’s project root — which in Step 7 is the
worktree, not the main checkout where Step 3a wrote the file. The first gate run died with
baseline not found: <worktree>/.sdlc/quality-baselines/<sha>.json after a full 11-verb pass.
Either the Step 7 example should carry --baseline-dir like Step 3a’s does, or the resolver should
fall back to the superproject when run from a worktree.
Keywords searched: diff-against-baseline, quality-baselines, baseline-dir, superproject,
documented, invocation, resolves, worktree
Excluded: T-HTN8-lease-gate-memo-fields
Top candidates (score / status / headline):
- 249 / closed/obsoleted / T-44OO-plugin-scripts-self-discover-project-root — Plugin scripts self-discover project root from cwd
- 89 / open/ready / T-OLTA-task-work-step7-worktree-baseline-dir — task-work Step 7 resolves the quality baseline dir from a worktree
- 87 / closed/superseded / T-5X6Y-task-work-step7-explicit-baseline-dir — task-work Step 7 must pass —baseline-dir explicitly to defeat silent fallback in worktree
- 67 / closed/done / T-2T20-task-work-backgrounds-quality-gate — task-work backgrounds the Step 7 quality gate and passes an explicit main-repo baseline-dir
- 64 / closed/done / T-TWZD-normalize-baseline-diff-nondeterministic-output — run_quality_checks —diff-against-baseline masks ephemeral tmpdir paths and transient SHAs before line-diffing
Decision: SPAWNED → overridden to LINKED-EXISTING
Rationale: This task (rank #2, open/ready) is the exact live owner — its headline is “task-work
Step 7 resolves the quality baseline dir from a worktree” and its Proposed already names both
remedies the bullet offers (an explicit --baseline-dir on Step 7, or defaulting the resolver to
the main checkout). The script’s #1 hit T-44OO is closed/obsoleted and #3 T-5X6Y is
closed/superseded by this task, so a spawn would be the fifth coverage of one gap. Linked from the
post-mortem of T-HTN8-lease-gate-memo-fields on 2026-08-03; a fourth independent observation,
this time with the concrete cost recorded — the gate died only AFTER a full 11-verb pass.
No duplicate spawned.