Skip to content

T-KDJ6-per-commit-typecheck-before-push

Status: closed/superseded · Impact: medium · Complexity: small

AUTO-DEFINED: this spec was best-effort machine-authored by /sdlc:task-auto-define on 2026-08-03, driven by /sdlc:spawn-task-pr —drive-to-ready. Review the Goal, Approach, Today, Files-to-touch, and Acceptance-criteria carefully before trusting it.

/sdlc:task-work gates the PR on the branch tip only, so an intermediate commit can fail typecheck while the tip passes — a bisect-hostile hole that today only human inspection catches. Close it by sweeping a declared subset of the project’s quality verbs over each commit the branch adds, before the push.

Splitting a branch into per-acceptance-criterion commits left an intermediate commit that did not typecheck — a barrel re-export landed one commit before its definition. It was caught by human inspection, not by tooling. A per-commit typecheck sweep over the branch’s commits, run before push, would catch this class mechanically.

T-5G81-lease-payload-gates

LocationRole today
apps/sdlc/skills/task-work/SKILL.mdStep 7 runs sdlc quality run against the worktree’s tip tree; Step 9 rebases and re-runs that same tip-only gate; Step 10 pushes. No step evaluates an intermediate commit.
apps/sdlc/lib/services/quality/ops/run.tsThe sdlc quality run op — reads the quality_checks: verb list and runs each verb once, in one working tree.
apps/sdlc/skills/task-work/check_ancestry.tsThe only per-commit walk task-work performs before pushing; it classifies commit subjects for contamination and never executes a check at a commit.
apps/sdlc/lib/config/load.ts#SdlcConfigSchemaZod schema for sdlc.yaml. Declares quality_checks, worktree_init, commit_worktree_init; no key names a per-commit subset.
apps/sdlc/lib/config/sdlc_yaml.ts#SCHEMA_OWNED_KEYSThe set of verb-list keys loadVerbList schema-validates before reading.
apps/sdlc/lib/services/git/arm-worktree.ts#armCommitWorktreeExisting primitive that arms an ephemeral detached worktree by running the commit_worktree_init: verbs — the reusable “check out a commit and make it runnable” step.
apps/sdlc/conventions/sdlc-yaml.mdReference documentation for each sdlc.yaml key.
sdlc.yamlThis repo’s config. quality_checks: already lists bunx tsc --noEmit; nothing selects a per-commit subset.

sdlc quality sweep-commits is a new deterministic op that replays a declared subset of quality verbs at each commit a branch adds, and /sdlc:task-work Step 9 runs it after the ancestry rebase and before Step 10’s push, gating the push on its exit code. The subset is declared per project as a new per_commit_checks: key in sdlc.yaml; when that key is absent or empty the op reports a skip and exits 0, so projects that have not opted in are unaffected. In this repo the subset is bunx tsc --noEmit, so the barrel-re-export-before-definition failure that motivated this task fails the sweep at the offending SHA instead of surviving to review.

  1. Add per_commit_checks to SdlcConfigSchema in apps/sdlc/lib/config/load.ts as an optional list of non-empty shell-verb strings, and to SCHEMA_OWNED_KEYS in apps/sdlc/lib/config/sdlc_yaml.ts so loadVerbList schema-validates it. Absent, null, and empty all mean “sweep nothing”.
  2. Add the op module apps/sdlc/lib/services/quality/ops/sweep-commits.ts (auto-registered by the services/<noun>/ops/<verb>.ts directory convention, giving the verb sdlc quality sweep-commits). Flags: --base <ref> defaulting to origin/main, --config <path> defaulting to the project root’s sdlc.yaml, and the standard --output text|json.
  3. In the op, load the verb list first. When it is empty, print SWEEP-COMMITS-SKIPPED reason=no-per-commit-checks and exit 0 without touching git.
  4. Otherwise enumerate the sweep range with git rev-list --reverse <base>..HEAD through the command seam (apps/sdlc/lib/util/command.ts), then drop the last entry — the tip tree is already covered by Step 7’s sdlc quality run gate, so re-running it here would only double the cost.
  5. For each remaining SHA, in order: git worktree add --detach into a temporary directory at that SHA, arm it with armCommitWorktree, run each declared verb in that directory via runChecked, then remove the worktree with git worktree remove --force in a finally so a thrown verb still cleans up. Stop at the first failing pair and print SWEEP-COMMITS-FAIL sha=<sha> verb=<verb> plus the verb’s captured output, exit 1. When the range is exhausted clean, print SWEEP-COMMITS-OK commits=<n> verbs=<m> and exit 0.
  6. Add a sub-step to Step 9 of apps/sdlc/skills/task-work/SKILL.md, placed after the ancestry rebase and the quality re-run and before the “ready to push” sub-step, that invokes the op un-piped and states that a non-zero exit blocks the push until the operator amends or reorders the offending commit.
  7. Document per_commit_checks: in apps/sdlc/conventions/sdlc-yaml.md alongside the other verb-list keys, naming the skip-when-empty default and the cost tradeoff that keeps the list a strict subset of quality_checks:.
  8. Declare per_commit_checks: in this repo’s sdlc.yaml with the single verb bunx tsc --noEmit.
  9. Extend apps/sdlc/lib/services/quality/tests/quality_ops.test.ts with coverage for the three outcomes (skip, clean sweep, failing middle commit).
LocationKindChange
apps/sdlc/lib/services/quality/ops/sweep-commits.tsnewThe quality sweep-commits op: range enumeration, per-commit ephemeral worktree, verb execution, first-failure reporting.
apps/sdlc/lib/config/load.tsmodifyAdd the optional per_commit_checks verb-list field to SdlcConfigSchema.
apps/sdlc/lib/config/sdlc_yaml.tsmodifyAdd per_commit_checks to SCHEMA_OWNED_KEYS so the key is schema-validated on read.
apps/sdlc/skills/task-work/SKILL.mdmodifyStep 9 gains the sweep sub-step; a non-zero exit blocks Step 10’s push.
apps/sdlc/conventions/sdlc-yaml.mdmodifyDocument the per_commit_checks: key and its skip-when-empty default.
sdlc.yamlmodifyDeclare per_commit_checks: with bunx tsc --noEmit.
apps/sdlc/lib/services/quality/tests/quality_ops.test.tsmodifyCover skip, clean sweep, and failing-middle-commit outcomes.
  • AC-1: With per_commit_checks: declared, sdlc quality sweep-commits --base origin/main exits non-zero and prints SWEEP-COMMITS-FAIL sha=<sha> verb=<verb> naming the first commit in git rev-list --reverse origin/main..HEAD~1 that fails a declared verb, and exits 0 printing SWEEP-COMMITS-OK commits=<n> verbs=<m> when none fail.
  • AC-2: With per_commit_checks: absent or empty, the op exits 0 printing SWEEP-COMMITS-SKIPPED reason=no-per-commit-checks, and git worktree list reports the same entries before and after the run.
  • AC-3: apps/sdlc/skills/task-work/SKILL.md Step 9 invokes sdlc quality sweep-commits after the ancestry rebase and before the push sub-step, and states that a non-zero exit blocks the push.
  • AC-4: A regression test in apps/sdlc/lib/services/quality/tests/quality_ops.test.ts builds a throwaway repo whose middle commit fails a declared verb, and asserts the op’s stdout names that commit’s SHA.
  • AC-5: bunx tsc --noEmit and bun test apps/sdlc both pass on the branch tip.
  • A CI-side sweep on the PR head. This task gates locally, before push; a GitHub Actions equivalent is separate work.
  • Running the whole quality_checks: list at each commit. Only the declared per_commit_checks: subset is swept, so the sweep stays cheap enough to run on every push.
  • Auto-amending, auto-squashing, or auto-reordering a commit the sweep rejects — the operator decides the fix.
  • The response-commit path in /sdlc:pr-respond; wiring the sweep there is a follow-up once the op exists.
  • none

Spawned by /sdlc:spawn-task-pr on 2026-08-03 UTC from T-5G81-lease-payload-gates in git@github.com:sksizer/dev.git.


← Back to Tasks