Skip to content

chore: merge dev → main#1032

Merged
zbigniewsobiecki merged 33 commits intomainfrom
dev
Mar 24, 2026
Merged

chore: merge dev → main#1032
zbigniewsobiecki merged 33 commits intomainfrom
dev

Conversation

@zbigniewsobiecki
Copy link
Copy Markdown
Member

Summary

Merges 34 commits from dev into main. Key changes:

Bug fixes

Backend / engine architecture

Integrations

Dashboard / UX

Refactoring

Tests & docs

🤖 Generated with Claude Code

aaight and others added 30 commits March 23, 2026 17:27
…cts (#998)

Co-authored-by: Cascade Bot <bot@cascade.dev>
…/factories.ts (#999)

Co-authored-by: Cascade Bot <bot@cascade.dev>
Co-authored-by: Cascade Bot <bot@cascade.dev>
…c interface (#1001)

Introduces Sentry as the first alerting provider under a new 'alerting'
integration category, using provider-agnostic naming throughout so that
adding a second provider (PagerDuty, Datadog, etc.) requires no prompt
or interface changes.

## Provider-agnostic naming (the PM analogy)
Like how Trello "card" + JIRA "issue" are abstracted to "work item" in
the PM category, Sentry-specific language is hidden behind the alerting
namespace at every agent-facing layer:

- AgentInput fields: `alertIssueId`, `alertOrgId`, `alertIssueUrl`
- Gadgets: `GetAlertingIssue`, `GetAlertingEventDetail`, `ListAlertingEvents`
- CLI tools: `get-alerting-issue`, `get-alerting-event`, `list-alerting-events`
- Context step: `alertingIssue` (was `sentryIssue`)
- Agent YAML prompts: no Sentry-specific language

Sentry internals (client, types, HMAC verification) remain Sentry-named
as the abstraction boundary stops at the agent-facing interface.

## New capabilities
- `alerting:read` capability with all three gadgets (GetAlertingIssue,
  GetAlertingEventDetail, ListAlertingEvents) — ListAlertingEvents was
  previously missing despite having a CLI tool and core function
- Sentry webhook handler via `createWebhookHandler` factory, gaining
  webhook logging to `webhook_logs` and opt-in HMAC-SHA256 signature
  verification (`Sentry-Hook-Signature` header, raw hex format)
- `verifySentrySignature` in signatureVerification.ts (timing-safe)
- `verifySentryWebhookSignature` in webhookVerification.ts reads
  `webhook_secret` from the alerting integration credential

## Fixes
- `formatSentryEvent` cognitive complexity reduced from 34 to ~8 by
  extracting five section helpers (appendEventMeta, appendEventTags,
  appendEventRequest, appendEventUser, appendEventStacktrace)
- Removed dead `_client` caching variable from sentry/client.ts
- Removed Sentry branding from formatted output strings

## Tests
- 41 new/updated tests: verifySentrySignature (9), SentryIssueAlertTrigger
  (15), SentryMetricAlertTrigger (12), builtins registration (1 new + mock)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Cascade Bot <bot@cascade.dev>
)

* refactor(tests): migrate backend and gadget tests to shared mocks

* refactor(tests): migrate backend and gadget tests to shared mocks

---------

Co-authored-by: Cascade Bot <bot@cascade.dev>
…r factory (#1007)

Co-authored-by: Cascade Bot <bot@cascade.dev>
…outer adapter (#1008)

Co-authored-by: Cascade Bot <bot@cascade.dev>
…grations, reduce duplication (#1009)

Co-authored-by: Cascade Bot <bot@cascade.dev>
Co-authored-by: Cascade Bot <bot@cascade.dev>
…y DI (#1012)

Co-authored-by: Cascade Bot <bot@cascade.dev>
* feat(dashboard): redesign LLM Calls tab with activity visibility

Add a rich Activity column to the LLM Calls table so users can see
what each call was doing at a glance — without expanding every row.

**Parser layer (new)**
- Add `src/utils/llmResponseParser.ts` and frontend twin
  `web/src/lib/llm-response-parser.ts` that normalize all four engine
  response formats (Claude Code/OpenCode JSON arrays, Codex JSON object,
  LLMist gadget markup, raw text fallback) into a canonical
  `ParsedLlmResponse` with `blocks[]`, `toolNames[]`, and `textPreview`.
- `toolNames` carries one entry per invocation (NOT deduplicated) so
  ×N badge counts reflect actual call frequency.
- `summarizeInput` extracts human-readable summaries for well-known tools
  (Read/Write/Edit → file_path, Bash → command, Glob/Grep → pattern, etc.)
  with consistent 100-char truncation.
- Internal helpers (`flushGadgetArg`, `finalizeGadget`, `handleGadgetStart`,
  `processLlmistLine`, `processClaudeBlock`) extracted to module scope so
  each function stays under the cognitive complexity limit.

**API layer**
- `listLlmCallsMeta` now selects `response` alongside token/cost metadata.
- `listLlmCalls` router runs the parser server-side and returns `toolNames`
  + `textPreview` per call; raw `response` payloads are NOT forwarded to
  the client.

**UI — list view**
- New Activity column: colored tool-name badges (sky=read, amber=bash,
  emerald=write, violet=web/agent) with ×N counts + truncated text preview.
- `getToolStyle` extracted to `web/src/lib/tool-style.ts` (was duplicated
  in two components).
- Inter-call Δ Time column computed from `createdAt` timestamps.
- Summary stat cards (Total Calls, Input Tokens, Output Tokens, Cached %).
- Row expansion uses `<Fragment key>` to avoid React key warnings.
- Keyboard-accessible rows (`tabIndex={0}`, Enter/Space toggles).
- `isError` guard added alongside the existing `isLoading` guard.

**UI — detail view**
- Structured blocks view: text (muted bg), tool_use (colored badge +
  monospace inputSummary), thinking (collapsible `<details>`).
- "Raw" / "Structured" toggle; raw JSON parse is lazy (only runs when
  the pane is shown).
- Metadata bar with model (font-mono), time, tokens, cached, cost.
- `buildMetaItems` extracted to reduce component complexity; `MetaItem`
  type replaces fragile string-identity check for `font-mono`.
- `isError` guard added.

**Tests**
- New `tests/unit/utils/llmResponseParser.test.ts` covering all five
  format branches (Claude Code, Codex, LLMist, fallback, empty/null)
  plus edge cases (dedup, truncation, multi-line args, unknown blocks).
- New router test verifies `toolNames` and `textPreview` are extracted
  from a real Claude Code response payload, not just from `null` responses.

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

* fix(tests): update listLlmCallsMeta integration test for response field

The test asserted response was excluded from listLlmCallsMeta, but the
router now needs response to extract toolNames/textPreview server-side.
Update the test to reflect the new contract: request is still excluded,
response is now included.

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

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…tests (#1013)

Co-authored-by: Cascade Bot <bot@cascade.dev>
…sLifecycle, secretOrchestrator (#1014)

Co-authored-by: Cascade Bot <bot@cascade.dev>
…ivity (#1015)

Token counts in the LLM Calls tab were near-zero for Claude Code runs because
only usage.input_tokens (the uncached portion) was recorded. With Anthropic
prompt caching, the vast majority of tokens land in cache_read_input_tokens and
cache_creation_input_tokens instead.

Fix logClaudeCodeLlmCall:
- inputTokens = input_tokens + cache_read_input_tokens + cache_creation_input_tokens
- cachedTokens = cache_read_input_tokens (tokens served from cache)
- costUsd computed via calculateCost() using toPricingKey() which converts
  'claude-sonnet-4-5-20250929' → 'anthropic:claude-sonnet-4-5' for pricing lookup

Also refactor processAssistantMessage to extract processContentBlock helper,
reducing Biome cognitive complexity from 17 to under 15.

LLM Calls tab activity column improvements:
- listLlmCalls API now returns toolCalls [{name, inputSummary}] instead of flat
  toolNames [], surfacing the parsed input summary (file path, command, etc.) for
  each tool call without requiring row expansion
- thinkingChars added: total chars across all thinking blocks shown inline as
  "thinking (N chars)" in muted italic
- Each tool call rendered as [Badge] monospace-param inline in the list row

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…riven lifecycle hooks (#1016)

Co-authored-by: Cascade Bot <bot@cascade.dev>
… thinking inline (#1017)

- LlmCallDetail now defaults to raw JSON view (showRaw=true); structured view
  remains accessible via the toggle. Raw JSON is the unique content that isn't
  already visible in the list row, so it should be the default.
- Remove redundant metadata bar from detail panel (model/tokens/cost/time are
  already shown in the list columns).
- listLlmCalls API now also returns thinkingPreview (first 200 chars of thinking
  text, concatenated across blocks) alongside thinkingChars.
- List row Activity column shows the actual thinking text truncated, with char
  count suffix when the full text exceeds 200 chars, instead of just
  "thinking (N chars)".

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Cascade Bot <bot@cascade.dev>
…ss engines (#1019)

* feat(backends): add NativeToolEngine abstract base class for subprocess engines

* docs(backends): fix NativeToolEngine class-level JSDoc inaccuracies

Remove the erroneous bullet about buildSystemPrompt/buildTaskPrompt (those
functions are standalone imports used by each engine's own execute(), not a
base class responsibility). Also correct the abstract method name from
executeTurn() to execute() to match the actual contract.

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

---------

Co-authored-by: Cascade Bot <bot@cascade.dev>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
)

Co-authored-by: Cascade Bot <bot@cascade.dev>
…rationRegistry (#1021)

Co-authored-by: Cascade Bot <bot@cascade.dev>
… providers in unified registry (#1022)

Co-authored-by: Cascade Bot <bot@cascade.dev>
…ine directly (#1023)

Co-authored-by: Cascade Bot <bot@cascade.dev>
* feat(sentry): add worker-side Sentry webhook job dispatch

* fix(sentry): use pre-computed triggerResult and run agent in processSentryWebhook

- Short-circuit registry.dispatch when triggerResult is already provided,
  matching the router→worker contract followed by Trello, JIRA, and GitHub handlers
- After resolving the trigger result, call runAgentExecutionPipeline to actually
  execute the matched agent (fixes silent discard of matched triggers)
- Fix SentryJobData JSDoc comment: add 'issue' to eventType union to match
  router-side SentryJob type in src/router/queue.ts
- Update tests: assert registry.dispatch is NOT called when triggerResult is
  provided; add coverage for agent execution pipeline call

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

---------

Co-authored-by: Cascade Bot <bot@cascade.dev>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
…roles (#1025)

Co-authored-by: Cascade Bot <bot@cascade.dev>
Co-authored-by: Cascade Bot <bot@cascade.dev>
…tion class (#1027)

Co-authored-by: Cascade Bot <bot@cascade.dev>
zbigniewsobiecki and others added 3 commits March 24, 2026 13:03
…re already merged (#1028)

## Root cause

The backlog-manager declared the backlog blocked in two situations:

1. **LLM matching failure**: haiku couldn't match a short dependency reference
   ("SCMIntegration") in a backlog card's description to the full user-story
   title in the MERGED list ("As a developer, I want SCMIntegration and
   GitHubSCMIntegration…"). Stale "(not yet merged)" annotations in
   descriptions further anchored it toward "blocked".

2. **Missing re-trigger**: when blocking dependency cards were manually moved
   to MERGED in Trello (outside CASCADE's PR flow), no trigger fired, leaving
   the backlog incorrectly blocked indefinitely.

## Changes

**Prompt improvements** (`backlog-manager.eta`):
- Add explicit warning that "(not yet merged)" text is always stale — the
  MERGED list is the only source of truth
- Replace vague MERGED-check guidance with concrete substring/keyword matching
  instructions: if the dependency name appears anywhere in a MERGED title it
  is resolved
- Scope the conservative rule: be conservative when *detecting* dependencies,
  but generous when *resolving* them against MERGED

**Pipeline snapshot** (`contextSteps.ts`):
- Include URL in parentheses for DONE/MERGED items so the agent can do exact
  URL matching when descriptions reference PM links
- Guard against empty URL (render nothing rather than empty `()`)

**New trigger** (`status-changed.ts`, `register.ts`):
- Add `TrelloStatusChangedMergedTrigger`: re-triggers backlog-manager whenever
  any card is moved to the MERGED list, catching manually resolved dependencies
  that would otherwise leave the backlog stuck

**Agent definition** (`backlog-manager.yaml`):
- Remove misleading `targetStatus` parameter that implied independent control
  over backlog vs. merged firing (both always fire when enabled)
- Update label and description to accurately describe dual-list behaviour

**Tests**:
- Add `merged-status-changed.test.ts` with full parallel coverage to
  `backlog-status-changed.test.ts` (matches, non-matches, disabled trigger,
  handle result)
- Update pipeline snapshot test: rename "title-only" → "title-and-url",
  assert URL present, add empty-URL edge-case test
- Add missing `triggerEvent` assertion to backlog trigger test

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
… static schema pre-registration (#1029)

Co-authored-by: Cascade Bot <bot@cascade.dev>
…rray (#1031)

GitHub fires check_suite webhooks with pull_requests: [] when CI runs on
the refs/pull/{N}/head virtual ref rather than the named feature branch.
The CheckSuiteFailureTrigger's matches() guard silently rejected these,
so respond-to-ci never fired even when CI failed on implementer PRs.

Root cause confirmed via webhook log f0f88951 for PR #1030: conclusion
was "failure", head_branch was "refs/pull/1030/head", pull_requests was [].

Changes:
- Extract parsePrNumberFromRef() to utils.ts (removes duplication between
  the two trigger files, adds JSDoc explaining the merge-ref exclusion)
- Restrict the regex to refs/pull/{N}/head only — the /merge variant uses
  a synthetic merge-commit SHA that is not part of the PR branch history
- Update matches() in both CheckSuiteFailureTrigger and CheckSuiteSuccessTrigger
  to accept events where pull_requests is empty but head_branch parses as a PR ref
- Simplify prBranch resolution to always use prDetails.headRef (already
  fetched) instead of a conditional that picked the same value either way
- Fix head_branch type to string | null (GitHub sends null, not undefined,
  when the check suite is not associated with a named branch)
- Add respond-to-ci trigger example to docs/getting-started.md

Tests:
- New unit tests for the empty pull_requests + refs/pull/{N}/head path in
  both trigger test files (matches and handle coverage)
- New describe('parsePrNumberFromRef') block in github-utils.test.ts
  covering null/undefined/empty, plain branches, /merge rejection,
  valid /head refs, and partial-match rejection

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
@zbigniewsobiecki zbigniewsobiecki merged commit 544d531 into main Mar 24, 2026
14 of 15 checks passed
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