Skip to content

feat(bg): B-0441.1 — backlog-ready notifier skeleton (parallel to B-0440.1; proactive layer)#3007

Merged
AceHack merged 2 commits into
mainfrom
otto-b0441-1-backlog-ready-notifier-skeleton-slice-1-impl-2026-05-13
May 13, 2026
Merged

feat(bg): B-0441.1 — backlog-ready notifier skeleton (parallel to B-0440.1; proactive layer)#3007
AceHack merged 2 commits into
mainfrom
otto-b0441-1-backlog-ready-notifier-skeleton-slice-1-impl-2026-05-13

Conversation

@AceHack
Copy link
Copy Markdown
Member

@AceHack AceHack commented May 13, 2026

Summary

Companion to PR #3006 (B-0440.1 — Standing-by detector). 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), the two services form a two-layer defense against the failure mode the human maintainer caught me on earlier today.

What lands

  • `tools/bg/backlog-ready-notifier.ts` (60 lines) — skeleton with no-op poll loop, 10min default interval
  • `tools/bg/backlog-ready-notifier.test.ts` — 3 tests, all pass

Test results

```
bun test
3 pass
0 fail
8 expect() calls
```

Two-layer defense pattern

Service Layer Trigger Output
B-0440 Standing-by detector (PR #3006) Reactive Idle threshold + cron fires Nudge: "pick work"
B-0441 Backlog-ready notifier (this PR) Proactive Queue-empty + rows-ready Assignment: "this row is ready"

Composes with

🤖 Generated with Claude Code

…p (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>
Copilot AI review requested due to automatic review settings May 13, 2026 17:33
@AceHack AceHack enabled auto-merge (squash) May 13, 2026 17:33
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a skeleton background service (B-0441.1) that will proactively surface ready-to-grind backlog rows to idle agents. This slice ships only the no-op poll loop and CLI scaffolding, mirroring the companion B-0440.1 standing-by detector.

Changes:

  • New tools/bg/backlog-ready-notifier.ts with NotifierConfig, pollOnce no-op, runNotifier loop, and --once / --poll-min CLI flags.
  • New tools/bg/backlog-ready-notifier.test.ts with 3 tests covering defaults, no-op poll, and single-iteration exit.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
tools/bg/backlog-ready-notifier.ts Skeleton notifier service with poll loop and CLI parsing.
tools/bg/backlog-ready-notifier.test.ts Bun tests for default config, poll result shape, and once mode.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3793af5dc8

ℹ️ 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".

Comment thread tools/bg/backlog-ready-notifier.ts Outdated
Comment thread tools/bg/backlog-ready-notifier.ts Outdated
AceHack added a commit that referenced this pull request May 13, 2026
…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>
…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>
AceHack added a commit that referenced this pull request May 13, 2026
…(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>
@AceHack AceHack merged commit 41e54b5 into main May 13, 2026
24 of 25 checks passed
@AceHack AceHack deleted the otto-b0441-1-backlog-ready-notifier-skeleton-slice-1-impl-2026-05-13 branch May 13, 2026 17:42
AceHack added a commit that referenced this pull request May 13, 2026
…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>
AceHack added a commit that referenced this pull request May 13, 2026
…3010)

results[0] returns T|undefined under noUncheckedIndexedAccess=true.
Add non-null assertion (!) since toHaveLength(1) on line above
already validates the array is non-empty; tsc can't see through
the Bun test framework's type signatures.

Fixes the non-required lint (tsc tools) failure introduced when
B-0440.1 (#3006) and B-0441.1 (#3007) landed skeleton test suites.

Co-authored-by: Claude <noreply@anthropic.com>
AceHack added a commit that referenced this pull request May 13, 2026
…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>
AceHack added a commit that referenced this pull request May 13, 2026
…ests pass)

Slice 2 of B-0441. The backlog-ready notifier now does REAL detection
by scanning docs/backlog/P*/B-*.md and classifying each row by:
- status: open (candidate)
- depends_on: all closed (ready)

Key design choices:
- Pure node:fs (readdirSync + readFileSync) — no shell, no spawn
- parseRow exposed for testability + reuse
- Adapter pattern (now + scanBacklog) — tests inject deterministic
  filesystems via fake adapter
- Configurable backlogDir via --backlog-dir flag (default: docs/backlog)
- candidateIds capped at first 10 to keep payload bounded
- Empty/missing depends_on treated as vacuously-satisfied (ready)
- Missing priority dirs skipped silently (graceful degradation)

Real-data smoke test (against current repo):
- 371 open rows
- 229 ready-to-grind
- Top candidates include B-0441 + B-0442 (the very rows whose impl
  slices we're shipping — recursive substrate working as designed)

Test results: 19 pass / 0 fail / 38 expect() calls (slice 1 had 3 / 8).

Future slices:
- 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: cron registration + integration tests

Composes with:
- B-0441 + B-0441.1 (PR #3007 — skeleton this extends)
- B-0440.1 + B-0440.2 (PR #3006 + #3011 — reactive companion)
- B-0442.1 (PR #3008 — drift-prevention companion)
- B-0400 (bus protocol — slice 4)
- PR #2999 (substrate-honest discipline triad — the rule this
  service enforces operationally)

Co-Authored-By: Claude <noreply@anthropic.com>
AceHack added a commit that referenced this pull request May 13, 2026
…ests pass) (#3012)

* feat(bg): B-0441.2 — backlog row scan (real readiness detection; 19 tests pass)

Slice 2 of B-0441. The backlog-ready notifier now does REAL detection
by scanning docs/backlog/P*/B-*.md and classifying each row by:
- status: open (candidate)
- depends_on: all closed (ready)

Key design choices:
- Pure node:fs (readdirSync + readFileSync) — no shell, no spawn
- parseRow exposed for testability + reuse
- Adapter pattern (now + scanBacklog) — tests inject deterministic
  filesystems via fake adapter
- Configurable backlogDir via --backlog-dir flag (default: docs/backlog)
- candidateIds capped at first 10 to keep payload bounded
- Empty/missing depends_on treated as vacuously-satisfied (ready)
- Missing priority dirs skipped silently (graceful degradation)

Real-data smoke test (against current repo):
- 371 open rows
- 229 ready-to-grind
- Top candidates include B-0441 + B-0442 (the very rows whose impl
  slices we're shipping — recursive substrate working as designed)

Test results: 19 pass / 0 fail / 38 expect() calls (slice 1 had 3 / 8).

Future slices:
- 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: cron registration + integration tests

Composes with:
- B-0441 + B-0441.1 (PR #3007 — skeleton this extends)
- B-0440.1 + B-0440.2 (PR #3006 + #3011 — reactive companion)
- B-0442.1 (PR #3008 — drift-prevention companion)
- B-0400 (bus protocol — slice 4)
- PR #2999 (substrate-honest discipline triad — the rule this
  service enforces operationally)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(bg): B-0441.2 — 4 Copilot findings (block-style YAML, superseded deps, dangling refs, unreachable branch)

1. P1: parseRow now handles BOTH inline-flow YAML (depends_on: [A, B])
   AND block-style lists (depends_on:\n  - A\n  - B). Split into
   parseDependsOn helper for clarity. Tests cover both styles.

2. Dependency satisfaction extended: matches tools/backlog/generate-
   index.ts checkboxFor() — a dep is satisfied iff its row is `closed`
   OR `superseded-by-*`. Test verifies superseded-as-satisfied.

3. Dangling dep references (dep ID not present in scan) are now
   surfaced explicitly in PollResult.note as a warning. Test
   verifies the warning fires.

4. Removed unreachable `else if (KNOWN_FLAGS.has(arg))` branch in
   parseArgs (all known flags are handled by explicit branches above).
   KNOWN_FLAGS now const-asserted array for the error-message join.

Bonus: switched from RegExp.exec() to String.match() to avoid the
project's security-hook false-positive on the word "exec".

Tests: 22 pass / 0 fail / 44 expect() calls (was 19 / 38).

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
AceHack added a commit that referenced this pull request May 13, 2026
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants