You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The SDD pipeline over-decomposes. Demoable units land at ~100 net lines when an engineer's natural PR is ~500, and the tree creates a sub-issue per Unit even when the Unit holds a single task. A modest feature fans out into many sub-issues, each paying the full per-task pipeline cost (one sdd-execute run, one PR, one CI pipeline, one sdd-validate, one sdd-review, one merge). This is the same poor cost-to-diff ratio ADR 0022 began addressing, but a layer up.
Tree arithmetic
Full-path feature:
Feature (tracking issue)
├── spec sub-issue (1)
├── architecture sub-issue (1)
└── Unit sub-issue × N ← one per demoable unit, pure grouping container
└── task sub-issue × M ← the only execution targets
Sub-issue count = 2 + N + M. N (Unit count) is set by sdd-spec's demoable-unit split; M by sdd-triage decomposition. Reducing the count means shrinking N (larger units), shrinking M (fewer/larger tasks), and removing Units that group only one task.
Existing sizing knobs
Knob
Where
Default
Sizes
SDD_AGILE_MAX
sdd-spec step 3a (ADR 0012/0024)
800 net lines
whole-feature ceiling to collapse to a flat single PR, no tree
SDD_TRIAGE_MIN_TASK
sdd-triage step 5 (ADR 0022)
300 net lines
folds cohesive sibling tasks within one Unit
(none)
sdd-spec step 5
—
demoable unit size — no guidance
Root cause: three gaps
Gap A — units have no lower-bound sizing.sdd-spec step 5 says only "break the work into demoable units, split as the work naturally falls." No target size, no cohesion rule, so the model defaults to fine-grained ~100-line units. Direct cause of small units → high N → high M.
Gap B — ADR 0022 folding is intra-Unit and cohesion-gated. It folds two tasks only when their files in scope: overlap or they form a producer→consumer chain, and only within one Unit. The 300-line floor merely breaks ties after the cohesion gate. Two small unrelated tasks are never folded; it never reduces N or folds across Units.
Gap C — Units are mandatory grouping sub-issues.sdd-triage phase C (step 6) creates one Unit sub-issue per demoable unit even when the Unit holds a single task — a sub-issue with no execution purpose. Triage gate 3 (shared/sdd-gates.md) assumes "a Unit spans multiple tasks by design," but in practice Units are ~1:1 with tasks, making them pure overhead.
Secondary ejection: the 5 binary fastpath gates (no new dep, no migration, no telemetry/logging/auth boundary touch, no ADR-worthy decision) throw a 400-line feature into the full tree even though it is well under the 800 ceiling.
Plan — four levers (target ~400 net lines)
Lever 1 — Lower-bound unit sizing in sdd-spec (fixes Gap A)
Add to sdd-spec step 5: size a demoable unit to a reviewable PR, ~400 net lines target, and a unit-cohesion rule symmetric to ADR 0022's task rule — fold two prospective units into one when they share a file set or form a strict producer→consumer chain, unless a dependency edge forces them apart.
Wire SDD_SPEC_MIN_UNIT (default 400) through the same workflow_call input channel SDD_TRIAGE_MIN_TASK uses today: the sdd-spec wrapper maps ${{ vars.SDD_SPEC_MIN_UNIT }} into a min_unit input; the prompt reads ${{ inputs.min_unit }}. Unset → blank → 400. 0 disables, restoring natural-split behavior.
Add the symmetric advisory: the spec gate set gains an under-sized-unit Warning (companion to the existing per-unit checks in shared/sdd-gates.md).
Lever 2 — Collapse single-task Units in sdd-triage phase C (fixes Gap C; biggest count drop)
In phase C (step 6), when a Unit would contain exactly one task, skip the Unit create-issue and parent the task directly to the tracking issue (Feature → task). A Unit sub-issue is created only when it groups ≥2 tasks. The plan-comment preview (step 5) reflects the same collapse so phase C materializes exactly what was previewed (ADR 0010).
Cost — three tree-walkers assume Feature → Unit → task and must also handle Feature → task:
sdd-execute-{haiku,sonnet,opus}.md step 8 completion sweep ("when the last task under a Unit is closed it closes that Unit sub-issue; when a feature's spec, architecture, and every Unit sub-issue is closed it moves the feature to done") — must treat a feature-parented task as a leaf with no Unit to close.
.github/actions/sdd-cycle-detect/action.yml — the deterministic DAG walk.
sdd-validate gate 1 (shared/sdd-gates.md) — currently "walk the sub-issue list twice: once to enumerate Units, once from each Unit to enumerate tasks"; must enumerate feature-parented tasks too.
Also audit shared/sdd-interaction.md and shared/sdd-spike.md for Unit-structure assumptions.
Risk: medium — cross-cutting across 5+ files. Sequence after Lever 1.
Lever 3 — Raise SDD_TRIAGE_MIN_TASK default 300 → 400 (fixes Gap B partially)
Bump the default in ADR 0022, the sdd-triage.md step-5 fallback, and docs/sdd/install.md. Aligns the task floor with the unit target. Config-only.
Optional extension (defer): allow the floor to fold cohesive tasks across sibling Units within a feature, not just within one Unit. More invasive — changes the cohesion scope.
Lever 4 — Soften the fastpath ejection (ADR 0012/0024)
Make 1–2 of the 5 binary single-PR gates advisory rather than hard-eject. Candidate: a lone telemetry/logging touch, or a single minor boundary change, should not force a full tree on an otherwise sub-ceiling feature. Alternatively raise SDD_AGILE_MAX.
This is an ADR-level behavior change to the classifier semantics — needs its own ADR and validation runs.
Files: .github/workflows/sdd-spec.md step 3a, a new/amended ADR. Risk: highest — changes when features get a tree at all.
Sequencing
Levers 1 + 3 together — spec/config-level, reuse the existing variable-injection channel, set the ~400-line target at both unit and task level. Ship first.
Lever 2 — the structural count reduction; touches the tree-walkers. Ship after 1+3 with validation runs confirming the completion sweep, cycle-detect, and validate gates handle Feature → task.
Lever 4 — separate ADR.
Acceptance
sdd-spec step 5 states a ~400-line unit target + unit-cohesion rule; SDD_SPEC_MIN_UNIT flows wrapper → min_unit input → prompt; unset → 400, 0 disables.
SDD_TRIAGE_MIN_TASK default is 400; ADR 0022 and install docs updated.
A feature whose total scope is ~400 lines across a handful of files materializes as one task (no Unit grouping issue), not a multi-Unit tree.
After Lever 2: a Unit sub-issue exists only when it groups ≥2 tasks; the sdd-execute completion sweep, sdd-cycle-detect, and sdd-validate gate 1 all pass on a Feature → task tree with no intervening Unit.
Back each behavior change with an experiments: entry (metric: "aic") and a quality check that the larger units/tasks still produce coherent specs and reviewable PRs.
References
ADR 0022 — triage task bundling (the intra-Unit precedent this generalizes): decisions/0022-triage-task-sizing.md
Problem
The SDD pipeline over-decomposes. Demoable units land at ~100 net lines when an engineer's natural PR is ~500, and the tree creates a sub-issue per Unit even when the Unit holds a single task. A modest feature fans out into many sub-issues, each paying the full per-task pipeline cost (one
sdd-executerun, one PR, one CI pipeline, onesdd-validate, onesdd-review, one merge). This is the same poor cost-to-diff ratio ADR 0022 began addressing, but a layer up.Tree arithmetic
Full-path feature:
Sub-issue count = 2 + N + M. N (Unit count) is set by
sdd-spec's demoable-unit split; M bysdd-triagedecomposition. Reducing the count means shrinking N (larger units), shrinking M (fewer/larger tasks), and removing Units that group only one task.Existing sizing knobs
SDD_AGILE_MAXsdd-specstep 3a (ADR 0012/0024)SDD_TRIAGE_MIN_TASKsdd-triagestep 5 (ADR 0022)sdd-specstep 5Root cause: three gaps
Gap A — units have no lower-bound sizing.
sdd-specstep 5 says only "break the work into demoable units, split as the work naturally falls." No target size, no cohesion rule, so the model defaults to fine-grained ~100-line units. Direct cause of small units → high N → high M.Gap B — ADR 0022 folding is intra-Unit and cohesion-gated. It folds two tasks only when their
files in scope:overlap or they form a producer→consumer chain, and only within one Unit. The 300-line floor merely breaks ties after the cohesion gate. Two small unrelated tasks are never folded; it never reduces N or folds across Units.Gap C — Units are mandatory grouping sub-issues.
sdd-triagephase C (step 6) creates one Unit sub-issue per demoable unit even when the Unit holds a single task — a sub-issue with no execution purpose. Triage gate 3 (shared/sdd-gates.md) assumes "a Unit spans multiple tasks by design," but in practice Units are ~1:1 with tasks, making them pure overhead.Secondary ejection: the 5 binary fastpath gates (no new dep, no migration, no telemetry/logging/auth boundary touch, no ADR-worthy decision) throw a 400-line feature into the full tree even though it is well under the 800 ceiling.
Plan — four levers (target ~400 net lines)
Lever 1 — Lower-bound unit sizing in
sdd-spec(fixes Gap A)sdd-specstep 5: size a demoable unit to a reviewable PR, ~400 net lines target, and a unit-cohesion rule symmetric to ADR 0022's task rule — fold two prospective units into one when they share a file set or form a strict producer→consumer chain, unless a dependency edge forces them apart.SDD_SPEC_MIN_UNIT(default400) through the sameworkflow_callinput channelSDD_TRIAGE_MIN_TASKuses today: thesdd-specwrapper maps${{ vars.SDD_SPEC_MIN_UNIT }}into amin_unitinput; the prompt reads${{ inputs.min_unit }}. Unset → blank → 400.0disables, restoring natural-split behavior.shared/sdd-gates.md).Files:
.github/workflows/sdd-spec.md,wrappers/sdd-spec.yml,shared/sdd-gates.md,docs/sdd/install.md. Risk: low — spec/config-level, mirrors a proven pattern.Lever 2 — Collapse single-task Units in
sdd-triagephase C (fixes Gap C; biggest count drop)create-issueand parent the task directly to the tracking issue (Feature → task). A Unit sub-issue is created only when it groups ≥2 tasks. The plan-comment preview (step 5) reflects the same collapse so phase C materializes exactly what was previewed (ADR 0010).sdd-execute-{haiku,sonnet,opus}.mdstep 8 completion sweep ("when the last task under a Unit is closed it closes that Unit sub-issue; when a feature's spec, architecture, and every Unit sub-issue is closed it moves the feature to done") — must treat a feature-parented task as a leaf with no Unit to close..github/actions/sdd-cycle-detect/action.yml— the deterministic DAG walk.sdd-validategate 1 (shared/sdd-gates.md) — currently "walk the sub-issue list twice: once to enumerate Units, once from each Unit to enumerate tasks"; must enumerate feature-parented tasks too.shared/sdd-interaction.mdandshared/sdd-spike.mdfor Unit-structure assumptions.Risk: medium — cross-cutting across 5+ files. Sequence after Lever 1.
Lever 3 — Raise
SDD_TRIAGE_MIN_TASKdefault 300 → 400 (fixes Gap B partially)sdd-triage.mdstep-5 fallback, anddocs/sdd/install.md. Aligns the task floor with the unit target. Config-only.Files:
decisions/0022-triage-task-sizing.md,.github/workflows/sdd-triage.md,docs/sdd/install.md. Risk: lowest.Lever 4 — Soften the fastpath ejection (ADR 0012/0024)
SDD_AGILE_MAX.Files:
.github/workflows/sdd-spec.mdstep 3a, a new/amended ADR. Risk: highest — changes when features get a tree at all.Sequencing
Acceptance
sdd-specstep 5 states a ~400-line unit target + unit-cohesion rule;SDD_SPEC_MIN_UNITflows wrapper →min_unitinput → prompt; unset → 400,0disables.SDD_TRIAGE_MIN_TASKdefault is 400; ADR 0022 and install docs updated.sdd-executecompletion sweep,sdd-cycle-detect, andsdd-validategate 1 all pass on a Feature → task tree with no intervening Unit.experiments:entry (metric: "aic") and a quality check that the larger units/tasks still produce coherent specs and reviewable PRs.References
decisions/0022-triage-task-sizing.mddecisions/0012-fastpath.mddecisions/0010-plan-comment-before-tree.mddecisions/0008-phase-c-task-dedupe.md