Skip to content

Reduce sub-issue count: larger demoable units + collapse single-task Units #272

@norrietaylor

Description

@norrietaylor

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-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).

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

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)

  • 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

  1. 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.
  2. 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.
  3. 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
  • ADR 0012 — fastpath (whole-feature collapse): decisions/0012-fastpath.md
  • ADR 0010 — plan-comment-before-tree (phase B/C, cycle-detect backstop): decisions/0010-plan-comment-before-tree.md
  • ADR 0008 — phase-C dedupe backstop: decisions/0008-phase-c-task-dedupe.md
  • sdd-triage over-decomposes: per-task PR overhead exceeds the work delivered #252 — the original over-decomposition report ADR 0022 answered

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions