Skip to content

feat(notifications): output standard slice 1 — config-driven alert rendering#131

Merged
nfeuer merged 3 commits into
mainfrom
feat/output-standard
Jul 10, 2026
Merged

feat(notifications): output standard slice 1 — config-driven alert rendering#131
nfeuer merged 3 commits into
mainfrom
feat/output-standard

Conversation

@nfeuer

@nfeuer nfeuer commented Jul 10, 2026

Copy link
Copy Markdown
Owner

Problem

Automation alerts (e.g. the shirt price watch) reach Discord as raw JSON — _render_alert_content() was a json.dumps into a plain message.

Changes (spec_v3.md §25; design doc committed in this PR)

  • notifications/output_renderer.py — resolves automation_alert.<capability>automation_alert.default → generic key/value (never JSON) against config/output_formats.yaml (Jinja2 template + embed spec, semantic colours). Deterministic facts first; optional voice pass via new local-only task type format_user_output rewrites the description sentence, falling back to template text on any failure (fallback_activated).
  • Dispatcher takes an optional renderer; rendered text persists as alert_content, embed rides discord_dm/discord_channel. Legacy text path kept as the failure floor.
  • dispatch_dm/send_dm gain embed support (embed replaces content, matching _send), including the blackout queue.
  • Wiring: _try_build_output_renderer (non-fatal if config missing), voice pass through ctx.router.

Example (live-verified against qwen2.5:32b)

Before: Automation 'shirt on sale' alert:\nOutput: {"price_usd": 29.99, ...}
After (embed, green): 🛍️ Seraphina Gown — $29.99 — "The Seraphina Gown is now available at $29.99, below your set threshold of $34, and your size is in stock. Should you act now to secure it?" + price usd: 29.99 · in stock: ✓ · size available: ✓

Testing

  • 12 renderer unit tests (resolution order, missing-field tolerance, truncation, voice-pass fallback, real-config golden + template drift).
  • 2 dispatcher tests (renderer path, renderer-failure fallback). Full unit suite: 2255 passed; ruff clean.
  • Voice-pass prompt verified live on the local model (facts preserved, ~9s, ~$0.00003).

Follow-ups (logged in followups.md)

Slice 2: reminders + proactive prompts onto the renderer. Slice 3: digest. SMS length budget.

🤖 Generated with Claude Code

https://claude.ai/code/session_01K7Ppai9Xbhv5iEuGBwi3ty

nfeuer and others added 3 commits July 10, 2026 09:01
…ndering

Automation alerts reached Discord as raw JSON: the whole alert path
converged on _render_alert_content()'s json.dumps. Per the approved design
(docs/superpowers/specs/2026-07-10-output-standard-design.md, spec_v3.md §25):

- New notifications/output_renderer.py: OutputRenderer resolves a surface key
  (automation_alert.<capability> → automation_alert.default → generic
  key/value) to a Jinja2 template + Discord embed spec from
  config/output_formats.yaml. Deterministic template facts first; optional
  local-LLM voice pass (new task type format_user_output, local-only routing)
  rewrites the description sentence and falls back to template text on any
  failure with event_type="fallback_activated". No surface ever gets JSON.
- Formats shipped: product_watch, news_check, default; semantic embed
  colours (good_news/action_needed/failure/info).
- AutomationDispatcher takes an optional output_renderer; rendered text is
  persisted as alert_content and the embed rides discord_dm/discord_channel
  dispatches. Legacy plain-text path retained as the failure fallback.
- NotificationService.dispatch_dm and DonnaBot.send_dm accept an embed
  (embed replaces content, matching _send's existing contract), including
  the blackout/quiet-hours queue path.
- Wiring: _try_build_output_renderer in cli_wiring (non-fatal on missing
  config); voice pass routes through ctx.router. Voice pass verified live
  against qwen2.5:32b (facts preserved, 9s, ~$0.00003).
- Tests: 12 renderer unit tests incl. real-config golden + drift checks;
  2 dispatcher tests (renderer path, renderer-failure fallback); DM embed
  assertion updated. Full unit suite: 2255 passed. Follow-ups logged for
  slices 2–3 (reminders/proactive prompts, digest).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K7Ppai9Xbhv5iEuGBwi3ty
- BotProtocol.send_dm gains the optional embed param to match DonnaBot
  and NotificationService.dispatch_dm.
- _Tolerant subclasses dict[str, Any] (mypy type-arg).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K7Ppai9Xbhv5iEuGBwi3ty
Resolve conflict in docs/superpowers/specs/followups.md by keeping both
the calendar-OAuth (PR #130, now on main) and output-standard follow-up
entries.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0163xNY4AKcd3pHF8CYVGPH5
@nfeuer
nfeuer merged commit 3313e51 into main Jul 10, 2026
12 checks passed
@nfeuer
nfeuer deleted the feat/output-standard branch July 10, 2026 19:58
nfeuer pushed a commit that referenced this pull request Jul 10, 2026
Resolve conflict in docs/superpowers/specs/followups.md by keeping both
the output-standard (PR #131, now on main) and local-LLM-reliability
follow-up entries.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0163xNY4AKcd3pHF8CYVGPH5
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