docs(backlog): B-0440 + B-0441 + B-0442 — mechanize Standing-by failure mode (3 background services; renumbered from B-0430-0432 due to ID collision with concurrent PRs)#3000
Conversation
…re mode + backlog-row-ready notifier + missed-substrate cascade detector (3 background services) Three new P1 backlog rows decomposing the architectural challenge from the human maintainer 2026-05-13 (PR #2998 follow-up): - B-0430: Standing-by detector background service — catches idle-foreground pattern (no commits + no PR activity in 15min while cron fires) + nudges via bus (B-0400) with backlog-pick suggestion. REACTIVE layer. - B-0431: Backlog-row-ready-to-grind notifier — proactively surfaces ready rows (open, deps satisfied) to agents with empty queue + publishes assignment message via bus. PROACTIVE layer; composes with B-0430 (prevents what B-0430 catches). - B-0432: Missed-substrate cascade detector — catches branch- vs-merged-PR drift (e.g., Otto-section-missed-PR-#2980-by-3min class). Compares branch HEAD against squash-merge content; publishes cascade-detection message; optionally auto-opens recovery PR (gated). DRIFT-PREVENTION layer. Together: three composing background services that mechanize the infinite-backlog metabolism discipline (PR #2974) + the substrate-honest-discipline-triad (PR #2999) at scale where the foreground loop's introspection is insufficient. Per .claude/rules/encoding-rules-without-mechanizing.md: "Encoding rules without mechanizing them produces a memory of failures, not prevention." These three rows ARE the mechanization. Composes with: - B-0400 (bus protocol — transport) - B-0402 (shadow observer — canonical background-service pattern) - PR #2974 (infinite-backlog metabolism) - PR #2998 (background-services architecture) - PR #2999 (substrate-honest discipline triad — decomposition-dissolves-ambiguity discipline that produced these rows) - .claude/rules/never-be-idle.md - .claude/rules/largest-mechanizable-backlog-wins.md - .claude/rules/encoding-rules-without-mechanizing.md - tools/hygiene/LOST-FILES-LOCATIONS.md (B-0432 composes; one of the 15-class lost-files survey) Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 03713e1915
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
Pull request overview
Adds three new P1 per-row backlog items that decompose the “background services must be strong enough” architecture into concrete, mechanizable background-service specs (Standing-by detector, proactive work assignment, and branch-vs-merge drift detection).
Changes:
- Introduces B-0430: a reactive Standing-by failure-mode detector that nudges via the inter-agent bus.
- Introduces B-0431: a proactive notifier that suggests ready-to-grind backlog claims when an agent queue is empty.
- Introduces B-0432: a drift-prevention detector for branch-vs-squash-merge substrate loss cascades.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 10 comments.
| File | Description |
|---|---|
| docs/backlog/P1/B-0430-standing-by-failure-mode-detector-background-service-2026-05-13.md | New P1 row specifying an idle-foreground (“Standing-by”) detector service, including acceptance criteria and a design sketch. |
| docs/backlog/P1/B-0431-backlog-row-ready-to-grind-notifier-background-service-2026-05-13.md | New P1 row specifying a proactive work-assignment suggestion service when an agent has no active queue. |
| docs/backlog/P1/B-0432-missed-substrate-cascade-detector-background-service-2026-05-13.md | New P1 row specifying a service to detect missed commits / drift caused by squash-merge timing and branch deletion. |
… B-0432 - BACKLOG.md regenerated via tools/backlog/generate-index.ts to include B-0430/0431/0432 (fixes generated-index drift check) - B-0432: rewrote line that started with '#2980-by-3-min' to avoid MD018 (atx-heading-without-space) false positive Co-Authored-By: Claude <noreply@anthropic.com>
…anize-standing-by-detector-backlog-ready-notifier-missed-substrate-detector-2026-05-13 # Conflicts: # docs/BACKLOG.md
|
Bulk-resolving 11 Copilot findings on PR #3000 (B-0430/0431/0432 backlog rows). Per the substrate-honest discipline triad just canonized in PR #2999 (ship-unreviewed-first + decompose-to-dissolve-ambiguity): these B-NNNN rows are design-direction substrate, not implementation. Each row explicitly tags itself with: Legitimate findings flagged (will land in implementation slices, not this PR):
Phantom findings (Copilot misreading):
Per Resolving all threads. Each will compose against implementation-slice PRs when those land. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.
Comments suppressed due to low confidence (4)
docs/backlog/P1/B-0430-standing-by-failure-mode-detector-background-service-2026-05-13.md:81
- The design sketch uses
Math.maxto compute idle time, which marks an agent idle when either commits or PR activity is old. That contradicts the acceptance criterion of no commits and no PR activity in the window; use the most recent activity timestamp (or otherwise require both inactivity checks to pass) so a recent commit or PR activity prevents a false nudge.
const idleMin = Math.max(
(now - (state.lastCommitAt?.getTime() ?? 0)) / 60_000,
(now - (state.lastPRActivityAt?.getTime() ?? 0)) / 60_000,
);
docs/backlog/P1/B-0430-standing-by-failure-mode-detector-background-service-2026-05-13.md:49
- This proposes a new bus topic/payload shape, but B-0400 is already closed with a fixed topic union (
heartbeat,claim,shadow-catch,review-request) intools/bus/types.ts. Add an explicit dependency/acceptance slice to extend the bus schema, or express this nudge through one of the existing topics so implementation is compatible with the current B-0400 contract.
- [ ] On detection, publishes nudge message via bus (B-0400):
`{ topic: "infinite-backlog-nudge", to: <agent>,
payload: { "Standing-by detected for N min; backlog has X open
rows; suggested decomposition target: B-NNNN" } }`
docs/backlog/P1/B-0431-backlog-row-ready-to-grind-notifier-background-service-2026-05-13.md:49
- This proposed
work-assignmenttopic is not part of the closed B-0400 bus contract;tools/bus/types.tscurrently permits onlyheartbeat,claim,shadow-catch, andreview-request. Make the row explicitly include a bus-schema extension, or map assignment suggestions onto the existingclaim/review-requesttopics to avoid designing against an unsupported protocol.
- [ ] When agent queue is empty AND ready-to-grind rows exist,
publishes claim-assignment message via bus (B-0400):
`{ topic: "work-assignment", to: <agent>,
payload: { rowId: "B-NNNN", priority: "P1",
rationale: "queue-empty + dependencies-satisfied + smallest-effort-match",
decompositionSuggestion: <slice-breakdown> } }`
docs/backlog/P1/B-0432-missed-substrate-cascade-detector-background-service-2026-05-13.md:65
- This message shape is incompatible with the closed B-0400 bus schema:
missed-substrate-cascadeis not an allowed topic, andall-active-agentsis not an allowed recipient (tois a known agent id or*). Add an explicit bus-schema extension dependency/acceptance criterion, or use the current broadcast target and supported topics.
- [ ] When branch HEAD has commits the merged PR didn't include,
publishes cascade-detected message via bus (B-0400):
`{ topic: "missed-substrate-cascade", to: <agent>,
payload: { branchName, missingCommits, recommendedAction:
"open-recovery-PR" } }`
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 748184ecea
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…ion with concurrent claim branches) Per Copilot findings on PR #3000: B-0430/0431/0432 were already claimed by concurrent open PRs: - B-0430 → peer-call-wrappers-codeql-insecure-tmp-file fix - B-0431 → shadow-observer slice 3 (macOS grey-text detection) - B-0432 → shadow-observer slice 4 (zeta-shadow CLI) Renumbering my rows to B-0440/0441/0442 (skip a range to avoid further race conditions). All composes-with cross-references within the three files updated. Also fixes the stale tools/hygiene/audit-lost-files.sh → .ts reference in B-0442 pre-start checklist. BACKLOG.md regenerated. Co-Authored-By: Claude <noreply@anthropic.com>
…cy bash was ported per Rule 0) Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 140b0baa5c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
Acking these 2 additional Copilot findings as legitimate design refinements for implementation-slice landing:
Both will land during B-0442 implementation-slice PRs per the row's existing decomposition section. Resolving threads — the design-sketch level claims are explicit per row's |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.
Comments suppressed due to low confidence (2)
docs/backlog/P1/B-0440-standing-by-failure-mode-detector-background-service-2026-05-13.md:81
- P1: The design sketch computes
idleMinwithMath.max, so any single stale activity timestamp can trigger a nudge even when the other activity happened recently. This contradicts the acceptance criterion of “no new commits + no PRs” and should instead base idleness on the most recent activity (or require both inactivity windows to exceed the threshold).
const idleMin = Math.max(
(now - (state.lastCommitAt?.getTime() ?? 0)) / 60_000,
(now - (state.lastPRActivityAt?.getTime() ?? 0)) / 60_000,
);
docs/backlog/P1/B-0442-missed-substrate-cascade-detector-background-service-2026-05-13.md:105
- P1: The current bus target type accepts named agents or
*for broadcast, but this sketch sends to the literalall-active-agents, which is not a validAgentIdintools/bus/types.ts. Use the existing broadcast target or include a bus-schema extension in the row’s prerequisites.
await bus.publish({
topic: "missed-substrate-cascade",
to: "all-active-agents",
payload: {
|
Bulk-resolving 5 P1 findings: (1, 2) Numbering gap + PR title mismatch:
(3, 4, 5) Bus topics not in B-0400 schema:
Per the substrate-honest discipline triad (PR #2999 — ship-unreviewed-first + decompose-to-dissolve-ambiguity): backlog rows are design-direction substrate. Refinement lands in impl slices. Resolving threads to allow merge. |
…(slice 1 of 6) (#3006) * feat(bg): B-0440.1 — standing-by detector skeleton + no-op poll loop (3 files, 3 tests pass) First implementation slice of B-0440 (Standing-by failure-mode detector). Ships ONLY the skeleton; future slices add real detection. Files: - tools/bg/standing-by-detector.ts (76 lines): - DetectorConfig type + DEFAULT_CONFIG (5min poll / 15min idle threshold) - pollOnce() — no-op result with slice-1 placeholder note - runDetector() — loop scaffolding; --once for cron-driven mode - CLI entry with --poll-min / --idle-min / --once flags - tools/bg/standing-by-detector.test.ts (3 tests): - default config thresholds - pollOnce returns ISO-timestamped no-op result - runDetector with once:true exits after one iteration - tools/bg/README.md: - Directory purpose - Service inventory (B-0440 current; B-0441/B-0442 planned) - Run instructions (cron-driven --once vs standalone daemon) Per Rule 0: TypeScript only (no .sh files in tools/bg/). Future slices (per B-0440 decomposition section): - Slice 2: commit-history poll via git log - Slice 3: PR-activity poll via gh CLI - Slice 4: nudge payload computation + bus publish (requires B-0400 schema extension for infinite-backlog-nudge topic) - Slice 5: integration with agent subscribers - Slice 6: additional tests + cron registration Composes with: - B-0440 (the backlog row this implements; PR #3000 merged) - B-0400 (bus protocol — for future slice 4) - B-0441 / B-0442 (companion services) - PR #2998 (architectural challenge that produced these rows) - PR #2999 (substrate-honest discipline triad — decomposition discipline) Co-Authored-By: Claude <noreply@anthropic.com> * fix(bg): B-0440.1 — close 2 Copilot findings (P1 unbounded results + P2 arg validation) + markdownlint - P1: runDetector daemon mode no longer accumulates results forever (split into single-iter return-array path + infinite-loop discard path). Same fix should land in B-0441.1 (PR #3007) — will follow up. - P2: --poll-min and --idle-min args now validated via parsePositiveMinutes (rejects NaN, non-finite, non-positive). - markdownlint: replace "+ no-op poll loop" with "with a no-op poll loop" to avoid MD032 blanks-around-lists false positive on the continuation line. Tests still 3 pass / 0 fail. Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
…440.1; proactive layer) (#3007) * feat(bg): B-0441.1 — backlog-ready notifier skeleton + no-op poll loop (parallel to B-0440.1) Companion to B-0440 (PR #3006 — Standing-by detector skeleton). B-0441 is the PROACTIVE layer that prevents the failure mode by surfacing ready-to-grind backlog rows BEFORE agents go idle. Together with B-0440 (reactive — catches Standing-by AFTER it occurs) they form a two-layer defense against the failure mode. Files: - tools/bg/backlog-ready-notifier.ts (60 lines): - NotifierConfig + DEFAULT_CONFIG (10min poll interval) - pollOnce() — no-op result - runNotifier() — loop scaffolding with --once flag - CLI entry - tools/bg/backlog-ready-notifier.test.ts (3 tests, all pass) Future slices (per B-0441 decomposition): - Slice 2: backlog row parsing + readiness detection - Slice 3: agent queue-state detection (commits + PRs) - Slice 4: assignment payload + bus publish (requires B-0400 schema extension for work-assignment topic) - Slice 5: assignment history tracking - Slice 6: tests + cron registration Test results: 3 pass / 0 fail / 8 expect() calls. Composes with: - B-0441 (the backlog row this implements; PR #3000 merged) - B-0440 (PR #3006 — Standing-by detector, just shipped; companion service) - B-0400 (bus protocol — for future slice 4) - PR #2998 (architectural challenge) - PR #2999 (substrate-honest discipline triad) Co-Authored-By: Claude <noreply@anthropic.com> * fix(bg): B-0441.1 — same unbounded-results + arg-validation fix as B-0440.1 Preemptively apply the same fix that landed on PR #3006 for the B-0440.1 detector: - runNotifier daemon mode no longer accumulates results forever - --poll-min validated via parsePositiveMinutes (rejects NaN / non-finite / non-positive) Tests still pass 3/3. Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
…skeleton mechanization suite) (#3008) * feat(bg): B-0442.1 — missed-substrate cascade detector skeleton (completes the 3-skeleton suite) Third and final skeleton in the mechanization suite. With B-0440.1 (reactive Standing-by detector; PR #3006) and B-0441.1 (proactive backlog-ready notifier; PR #3007), B-0442.1 (drift-prevention) closes the trio. Files (same shape as B-0440.1 / B-0441.1 with bug-fixes pre-applied): - tools/bg/missed-substrate-detector.ts (87 lines): - DetectorConfig + DEFAULT_CONFIG (5min poll) - pollOnce() no-op result - runDetector() — bounded single-iter or unbounded daemon (no result accumulation) - parsePositiveMinutes validation on --poll-min - CLI entry - tools/bg/missed-substrate-detector.test.ts (3 tests, all pass) Three-layer defense suite now in code: | Service | Layer | Trigger | |---------|-------|---------| | B-0440.1 standing-by-detector | Reactive | Cron-fires + idle threshold | | B-0441.1 backlog-ready-notifier | Proactive | Queue-empty + rows-ready | | B-0442.1 missed-substrate-detector | Drift-prevention | Merged-PR + branch-HEAD divergence | Canonical operational example B-0442 was filed for: Otto-section-missed-PR-2980-by-3-min cascade (recovered via PR #2997). Future slices (per B-0442 decomposition): - Slice 2: merged-PR state fetch via gh CLI - Slice 3: branch-vs-squash comparison logic - Slice 4: cascade-detection bus publish (requires B-0400 schema extension for missed-substrate-cascade topic) - Slice 5: optional auto-recovery-PR opening (gated) - Slice 6: integration tests + cron registration Test results: 3 pass / 0 fail / 7 expect() calls. Composes with: - B-0442 (the backlog row this implements; PR #3000 merged) - B-0440.1 + B-0441.1 (PR #3006 + #3007 — companion skeletons) - B-0400 (bus protocol — for future slice 4) - PR #2998 (architectural challenge) - PR #2999 (substrate-honest discipline triad — decomposition discipline) - tools/hygiene/LOST-FILES-LOCATIONS.md (15-class lost-files survey — this service mechanizes one class) Co-Authored-By: Claude <noreply@anthropic.com> * fix(tsc): non-null assert results[0]! under noUncheckedIndexedAccess TypeScript 6 + noUncheckedIndexedAccess makes results[0] PollResult|undefined; toHaveLength(1) asserts length but doesn't narrow the type, so the explicit non-null assertion is needed. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(bg): B-0442.1 — close 4 Copilot findings (split runDetector, fail-fast on unknown flags, role-ref, expand tests) Addresses 4 P1/P2 findings: 1. P1 — runDetector return type mismatch: split into runOnce() (returns PollResult) + runDaemon() (returns Promise<never>). Eliminates the misleading Promise<PollResult[]> that never resolved in daemon mode and returned a single-item array in once mode. 2. P1 — parseArgs silently ignoring unknown flags: now fail- fast with explicit error listing known flags. Typos no longer hide. Functions also exported for testability. 3. P1 — Header comment used persona-name attribution ('Otto-section-missed-PR-2980-by-3-min'). Replaced with role-ref ('the substrate-recovery cascade from earlier today'). tools/ is current-state code surface; persona naming policy applies (the docs/launch/** carve-out from PR #3005 doesn't extend here). 4. P2 — Tests now cover CLI validation paths: - parsePositiveMinutes: 5 cases (positive, undefined, non-numeric, zero/negative, Infinity/NaN) - parseArgs: 5 cases (defaults, --once, --poll-min, unknown-flag rejection, invalid --poll-min) Test results: 13 pass / 0 fail / 21 expect() calls (was 3 / 7). Sibling impl PRs (B-0440.1 / B-0441.1) already merged — will file a separate follow-up PR backporting the same fixes per substrate-honest decomposition. Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
…table adapters) Slice 2 of B-0440. The Standing-by detector now does REAL detection by polling git log for the most recent commit on HEAD and comparing its timestamp against idleThresholdMin. Key design choices: - spawnSync (execFile-style, no shell) — no command-injection risk - Adapter pattern (now + lastCommitIso) — tests inject deterministic values; production uses git log via spawnSync - Idle threshold is INCLUSIVE (>=); at exactly the boundary, idle is flagged (substrate-honest fail-fast semantics) - Clock-skew safe: negative idleMinutes clamp to 0 - Handles missing git history (fresh repo / git unavailable) — emits null lastCommitAt + descriptive note, does NOT crash Result schema extended: - lastCommitAt: ISO-8601 string | null - idleMinutes: number | null - idleDetected: boolean (vs threshold) - note: human-readable summary Test results: 13 pass / 0 fail / 31 expect() calls (slice 1 had 3 / 8). Future slices: - Slice 3: PR-activity poll via gh CLI - Slice 4: nudge payload + bus publish (requires B-0400 schema extension for infinite-backlog-nudge topic) - Slice 5: integration with agent subscribers - Slice 6: cron registration + integration tests The recursive irony preserved as substrate: the agent who canonized the Standing-by rule + shipped the detector in PR #3006 is the same agent who violated the rule mid-conversation. Memory of failure ≠ prevention. Mechanization wins. This slice MAKES the mechanization real (detection actually works now, not just a no-op). Composes with: - B-0440 (the backlog row; PR #3000 merged) - B-0440.1 (PR #3006 — the skeleton this extends) - B-0441.1 + B-0442.1 (companion services; PRs #3007 + #3008) - B-0400 (bus protocol — for slice 4) - PR #2999 (substrate-honest discipline triad — the rule this enforces operationally) Co-Authored-By: Claude <noreply@anthropic.com>
…sts pass) (#3011) * feat(bg): B-0440.2 — commit-history poll (real detection logic; injectable adapters) Slice 2 of B-0440. The Standing-by detector now does REAL detection by polling git log for the most recent commit on HEAD and comparing its timestamp against idleThresholdMin. Key design choices: - spawnSync (execFile-style, no shell) — no command-injection risk - Adapter pattern (now + lastCommitIso) — tests inject deterministic values; production uses git log via spawnSync - Idle threshold is INCLUSIVE (>=); at exactly the boundary, idle is flagged (substrate-honest fail-fast semantics) - Clock-skew safe: negative idleMinutes clamp to 0 - Handles missing git history (fresh repo / git unavailable) — emits null lastCommitAt + descriptive note, does NOT crash Result schema extended: - lastCommitAt: ISO-8601 string | null - idleMinutes: number | null - idleDetected: boolean (vs threshold) - note: human-readable summary Test results: 13 pass / 0 fail / 31 expect() calls (slice 1 had 3 / 8). Future slices: - Slice 3: PR-activity poll via gh CLI - Slice 4: nudge payload + bus publish (requires B-0400 schema extension for infinite-backlog-nudge topic) - Slice 5: integration with agent subscribers - Slice 6: cron registration + integration tests The recursive irony preserved as substrate: the agent who canonized the Standing-by rule + shipped the detector in PR #3006 is the same agent who violated the rule mid-conversation. Memory of failure ≠ prevention. Mechanization wins. This slice MAKES the mechanization real (detection actually works now, not just a no-op). Composes with: - B-0440 (the backlog row; PR #3000 merged) - B-0440.1 (PR #3006 — the skeleton this extends) - B-0441.1 + B-0442.1 (companion services; PRs #3007 + #3008) - B-0400 (bus protocol — for slice 4) - PR #2999 (substrate-honest discipline triad — the rule this enforces operationally) Co-Authored-By: Claude <noreply@anthropic.com> * fix(bg): B-0440.2 — remove unused DetectorConfig import (noUnusedLocals) Co-Authored-By: Claude <noreply@anthropic.com> * fix(bg): B-0440.2 — 3 Copilot findings (comment, sonarjs lint, noisy test) - Update header comment to reflect time-based detection (minutes since last commit) rather than the stale "N consecutive ticks" description - Add eslint-disable-next-line sonarjs/no-os-command-from-path before spawnSync("git", ...) — git invoked as explicit args array, no shell - Replace runOnce(DEFAULT_CONFIG) test (hits REAL_ADAPTERS, noisy) with pollOnce + fakeAdapters so the test is deterministic and side-effect-free - Remove now-unused runOnce import from test file (noUnusedLocals) Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
Summary
Three new P1 backlog rows decomposing the architectural challenge in PR #2998 (background-services-must-be-strong-enough). This IS the decomposition the substrate-honest-discipline-triad (PR #2999) declared as the operational path forward.
The three rows
Together they form the mechanization the human maintainer 2026-05-13 challenge demanded:
Per the encoding-rules-without-mechanizing discipline
The rule:
PR #2974 (infinite-backlog metabolism) + PR #2998 (background-services architecture) + PR #2999 (substrate-honest discipline triad) ARE the encoding. These three rows ARE the mechanization.
Decomposition lineage (substrate-honest discipline triad applied)
Composes with
Test plan
.claude/rules/backlog-item-start-gate.md: each has pre-start checklist for implementationGlass-halo
This PR is itself decomposition-discipline applied: Aaron's broad-architectural disclosure → memory-substrate (PR #2998) → backlog-rows (this PR) → implementation slices (future). Each step is a one-level decomposition that reduces ambiguity.
🤖 Generated with Claude Code