T-61OI-check-ancestry-flags-stale-base
Status: closed/done · Impact: medium · Complexity: small
Auto-generated from a /sdlc:task-work post-mortem. Review and
promote to open/ready before picking up.
plugin/skills/task-work/check_ancestry.ts returns clean whenever
no commit in origin/main..HEAD is a chore(tasks): start <other>
foreign commit. But that’s only one way the branch can be
contaminated — when local main was fast-forwarded mid-session by
an externally-merged PR, the task branch’s diff against
origin/main can contain restructured upstream files completely
unrelated to the task. The helper still prints clean, so the
operator skips the rebase, and Step 9’s PR-prep is the first to
discover the dirty diff. Cite
T-5VN7-pr-check-cursor-bootstrap-misses-existing-comments —
the PR’s diff carried a flat docs/decisions/0001-github-ref-leases.md
that had been restructured into a folder on origin/main, and the
ancestry helper’s clean verdict was misleading.
| Location | Role today |
|---|---|
plugin/skills/task-work/check_ancestry.ts | Walks git log origin/main..HEAD --format=%H%n%s, parses chore(tasks): start <basename> lines, returns clean or contaminated: [...]. Detects parallel-task contamination only. |
plugin/skills/task-work/SKILL.md (Step 9) | Calls the helper and treats clean as “diff against origin/main is mine alone, push.” |
plugin/skills/task-work/tests/check_ancestry.test.ts | Exercises the parallel-task contamination cases but not the stale-base case. |
The helper conflates two different definitions of “clean”:
- Parallel-task-clean — no foreign
chore(tasks): startcommits. - Diff-clean —
git diff origin/main...HEADtouches only files the current task owns.
Today’s clean verdict means (1) but Step 9 reads it as (2).
Proposed
Section titled “Proposed”check_ancestry.ts gains a second sub-check (or a separate verdict
field) that compares the diff against origin/main to a project-
inferred or task-declared scope. Possible shapes:
- A new verdict
clean-with-upstream-drift: <N> non-task files changedwhen (1) holds but (2) doesn’t. - A new flag like
--diff-modethat, when set, also asserts the diff set is non-empty and rooted in expected scope. - Step 9 prose updates to call out which verdict means “rebase before push.”
Approach intentionally left thin so the implementer can pick the shape that fits the helper’s existing CLI surface.
Approach
Section titled “Approach”- Reproduce the failure mode: branch off an old
main, fast-forward localmainto a newer commit onorigin/main, run the helper, observeclean, then inspectgit diff origin/main...HEADto see the contamination. - Decide between (a) extending the existing
clean/contaminatedenum with a new verdict and (b) adding an independent diff-vs-origin-main check that the helper runs alongside the ancestry walk. Both are workable; pick the one that requires the smallest Step 9 prose change. - Implement the diff check. Walk
git diff origin/main...HEAD --name-onlyand classify each file as “mine” (underdocs/planning/tasks/<this-basename>.md, or under a paths-touched list this task declares in its frontmatterfiles_to_touch:) versus “upstream drift.” - Update
tests/check_ancestry.test.tswith a fixture that exercises the stale-base case. - Update
plugin/skills/task-work/SKILL.mdStep 9 prose to read the new verdict.
Files to touch
Section titled “Files to touch”| Location | Kind | Change |
|---|---|---|
plugin/skills/task-work/check_ancestry.ts | modify | Add the diff-vs-origin-main scope check; widen the verdict enum or add a parallel field. |
plugin/skills/task-work/tests/check_ancestry.test.ts | modify | Add a fixture exercising stale-base contamination. |
plugin/skills/task-work/SKILL.md | modify | Step 9 prose interprets the new verdict and prescribes the rebase action. |
Acceptance criteria
Section titled “Acceptance criteria”- AC-1: A test fixture exercises a task branch where local
mainis behindorigin/mainsuch thatgit diff origin/main...HEADcontains files outside the task’s scope. The helper reports the drift (notclean). - AC-2: The existing “parallel-task contamination” path still
returns
contaminated: [...]unchanged — no regression in the pre-shipped behavior. - AC-3:
plugin/skills/task-work/SKILL.mdStep 9 reads the new verdict and prescribes the rebase action when the helper flags stale-base drift.
Out of scope
Section titled “Out of scope”- Auto-rebasing on detection. The helper still reports; Step 9 still performs the rebase. The change is only to the helper’s verdict granularity.
- Generalizing the helper into a reusable cross-skill branch-health diagnostic. The scope here is the task-work Step 9 contract.
Dependencies
Section titled “Dependencies”- none
Discovery context
Section titled “Discovery context”Spawned by /sdlc:task-work post-mortem of T-5VN7-pr-check-cursor-bootstrap-misses-existing-comments on 2026-05-23.
Dedup search (spawn-from-post-mortem)
Section titled “Dedup search (spawn-from-post-mortem)”Bullet: Local main got fast-forwarded mid-session (PR #97 merged externally), leaving the task
branch rooted off a stale main commit with foreign files in the diff against origin/main.
check_ancestry.py reported clean because it only flags chore(tasks): start
- 9 / closed/done / 2026-05-20-task-work-ancestry-helper-one-liner — Helper that prints feat-branch ancestry diff vs origin/main
- 6 / open/ready / 2026-05-21-task-work-step-4-branches-from-origin-main — Step 4 worktree-add branches from origin/main when local main is contaminated
- 5 / planning/draft / 2026-05-21-git-log-machine-parseable-format-convention — Convention doc for machine-parseable git log formats
- 3 / closed/done / 2026-05-19-co-locate-skill-specific-scripts — Co-locate skill-specific scripts under their skill dir; promote when shared
- 3 / closed/done / 2026-05-19-migrate-reorders-frontmatter-keys — Shared reorder_frontmatter_keys helper produces consistent key order across migrate paths Decision: SPAWNED
Post-mortem
Section titled “Post-mortem”Captured by /sdlc:task-work on 2026-06-04. PR: pending.
Acceptance criteria coverage
Section titled “Acceptance criteria coverage”- AC-1: auto —
bun test plugin/skills/task-work/tests/check_ancestry.test.ts; newstale base: ...fixture assertsclassifyreturnsstale-basewith the restructured upstream pair asdriftFiles, and the CLI printsstale-base: 2 upstream-drift files; rebase onto origin/main. - AC-2: auto — same suite; the three pre-shipped parallel-task cases (clean, single contaminant,
multiple contaminants) pass unchanged, plus a new
contaminated takes precedence over stale-base driftcase proves the verdict order. - AC-3: agent-manual — read the rendered
plugin/skills/task-work/SKILL.mdStep 9: thecleanbullet notes the diff-scope condition, a newstale-base:bullet explains the fast-forwarded-base mode, and item 3 splits into 3a (contaminated→rebase --onto) and 3b (stale-base→ plainrebase origin/main).
What worked
Section titled “What worked”- The two-dot-minus-three-dot diff heuristic was confirmed with a throwaway repro repo before any production code was written, so the implementation matched the verified mechanism on the first pass.
- Baseline-gated quality checks correctly subtracted out the 356 pre-existing corpus/test findings; the only “new-drift” flagged was spurious corpus-listing churn, none of it touching the three changed files.
Friction and automation gaps
Section titled “Friction and automation gaps”run_quality_checks.ts --diff-against-baselinedefaulted its--baseline-dirto the worktree’s.sdlc/quality-baselines/, but Step 3a captured the baseline under the main repo’s dir, so the first gate run exited 2 with “baseline not found” — the gate should resolve the baseline dir against the main checkout (worktree superproject) by default, the same waystart_task.ts/append_pr_url.tsresolve the main repo, so the Step 7 invocation doesn’t need a manual--baseline-diroverride. → T-44OO-plugin-scripts-self-discover-project-rootaudit_entities.tsaudits the live on-disk corpus, so uncommitted task-file edits in the main checkout shift its per-file- OKlisting and surface as phantomnew-drift:lines against a baseline captured from committedorigin/mainstate — the baseline gate flags benign- OKreordering as drift. The entity audit’s baseline diff should compare on the set of genuine DRIFT/ERROR findings (or the drift summary count), not on every per-file status line. → T-BCNP-quality-gate-ignores-summary-and-corpus-lines- ensure-ready’s
--cleanup-on-failis rejected byensure_ready_mutate.tson--mode pass(“only meaningful with —mode fail”), but task-work Step 5a’s prose says to pass both flags together — the skill prose and the script contract disagree, forcing the operator to drop the flag on the pass path. Either the script should accept-and-ignore--cleanup-on-failon pass, or Step 5a should only add it on the fail branch. → T-BOZ6-ensure-ready-cleanup-flag-pass-mode-contract
Spawned follow-up tasks
Section titled “Spawned follow-up tasks”- T-44OO-plugin-scripts-self-discover-project-root — linked existing; the baseline-dir-from-worktree friction (AC-2/AC-3 of that task already cover the fix; fifth post-mortem to land here).
- T-BCNP-quality-gate-ignores-summary-and-corpus-lines — linked existing; the audit-corpus-line phantom-drift friction (second independent observation supporting its open definition_gap).
- T-BOZ6-ensure-ready-cleanup-flag-pass-mode-contract
(https://github.com/sksizer/dev/pull/280) — spawned; the ensure-ready
--cleanup-on-failvs Step 5a--mode passcontract mismatch (Upstream-plugin / sdlc-meta).