feat(flows): NF-6 Phase 4D — slash-breakdown baked-in flow #383

Merged
charles merged 2 commits from feat/flows-slash-breakdown into main 2026-04-26 14:13:18 +00:00
Collaborator

Summary

Ports webhook-handlers.ts::handleIssueComment /breakdown branch (lines 267-290) into a baked-in node-flow graph. Ships slash-breakdown as the fifth seeded flow on issue_comment.slash_command.

src → router.filter (command.command === "breakdown")
    → slash.handle_breakdown
        (isTrustedUser → parseBreakdownComment → dispatchBreakdown)

DSL extensions

One new monolithic node slash.handle_breakdown wraps the trust-check + arg-parse + dispatch sequence. Legacy code is also monolithic, so granular nodes would expose customisation surface operators don't need (no flow-author-meaningful boundaries between trust check, parse, and dispatch).

Three new injection types:

  • IsTrustedUserFnisTrustedUser (now exported from webhook-handlers)
  • ParseBreakdownCommentFnparseBreakdownComment from domain/workflow/slash-commands
  • DispatchBreakdownFndispatchBreakdown from domain/workflow/slash-commands

Cutover

node_flows.suppress_legacy: ["issue_comment.slash_command"] skips the slash-command branch of the legacy switch arm at webhook.ts:320-330.

Important: the suppression list entry blocks the /breakdown portion ONLY. /hold, /ready, /raise-cap, /unassign have no flow yet and continue from legacy. Documented in the loader header to prevent operator confusion.

Test plan

  • 11 e2e tests: happy path, non-breakdown command no-op, untrusted commenter drop, dispatch-failure drop
  • Full flows suite: 353 pass / 0 fail (no regressions)
  • Server typecheck clean
  • AGENT_NODE_COUNT bumped from 10 to 11
  • Soak in mode: "live" against real /breakdown comment, confirm parity via GET /flows/divergence/summary
  • Flip suppress_legacy: ["issue_comment.slash_command"] after clean soak

🤖 Generated with Claude Code

## Summary Ports `webhook-handlers.ts::handleIssueComment` `/breakdown` branch (lines 267-290) into a baked-in node-flow graph. Ships `slash-breakdown` as the fifth seeded flow on `issue_comment.slash_command`. ``` src → router.filter (command.command === "breakdown") → slash.handle_breakdown (isTrustedUser → parseBreakdownComment → dispatchBreakdown) ``` ## DSL extensions One new monolithic node `slash.handle_breakdown` wraps the trust-check + arg-parse + dispatch sequence. Legacy code is also monolithic, so granular nodes would expose customisation surface operators don't need (no flow-author-meaningful boundaries between trust check, parse, and dispatch). Three new injection types: - `IsTrustedUserFn` → `isTrustedUser` (now exported from webhook-handlers) - `ParseBreakdownCommentFn` → `parseBreakdownComment` from `domain/workflow/slash-commands` - `DispatchBreakdownFn` → `dispatchBreakdown` from `domain/workflow/slash-commands` ## Cutover `node_flows.suppress_legacy: ["issue_comment.slash_command"]` skips the slash-command branch of the legacy switch arm at `webhook.ts:320-330`. **Important**: the suppression list entry blocks the `/breakdown` portion ONLY. `/hold`, `/ready`, `/raise-cap`, `/unassign` have no flow yet and continue from legacy. Documented in the loader header to prevent operator confusion. ## Test plan - [x] 11 e2e tests: happy path, non-breakdown command no-op, untrusted commenter drop, dispatch-failure drop - [x] Full flows suite: 353 pass / 0 fail (no regressions) - [x] Server typecheck clean - [x] AGENT_NODE_COUNT bumped from 10 to 11 - [ ] Soak in `mode: "live"` against real `/breakdown` comment, confirm parity via `GET /flows/divergence/summary` - [ ] Flip `suppress_legacy: ["issue_comment.slash_command"]` after clean soak 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(flows): NF-6 Phase 4D — slash-breakdown baked-in flow
Some checks failed
qa / qa (pull_request) Has been cancelled
qa / dockerfile (pull_request) Has been cancelled
f061ed2976
Ports the `webhook-handlers.ts::handleIssueComment` `/breakdown` branch
(lines 267-290) into a baked-in node-flow graph. Ships `slash-breakdown`
as the fifth seeded flow on the `issue_comment.slash_command` trigger:

  src → router.filter (command.command === "breakdown")
      → slash.handle_breakdown
          (isTrustedUser → parseBreakdownComment → dispatchBreakdown)

One new monolithic node `slash.handle_breakdown` wraps the trust-check
+ arg-parse + dispatch sequence. The legacy code is also monolithic,
so granular nodes would expose customisation surface that operators
don't need (no flow-author-meaningful boundaries between trust check,
parse, and dispatch).

Three new injection types land alongside:
  - `IsTrustedUserFn` → `isTrustedUser` (now exported from
    webhook-handlers)
  - `ParseBreakdownCommentFn` → `parseBreakdownComment` from
    domain/workflow/slash-commands
  - `DispatchBreakdownFn` → `dispatchBreakdown` from
    domain/workflow/slash-commands

Cutover gate: `node_flows.suppress_legacy:
["issue_comment.slash_command"]` skips the slash-command branch of the
legacy switch arm at `webhook.ts:320-330`. Note that the suppression
list entry blocks the `/breakdown` portion only — `/hold`, `/ready`,
`/raise-cap`, `/unassign` have no flow yet and continue from legacy.
Documented in the loader's header so operators don't trip on it.

Tests: 11 new e2e tests covering happy path, non-breakdown command
no-op, untrusted commenter drop, dispatch-failure drop.
AGENT_NODE_COUNT bumped from 10 to 11.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
fix(flows): slash-breakdown — round-1 review parity + cutover gate warning
All checks were successful
qa / qa (pull_request) Successful in 6m31s
qa / dockerfile (pull_request) Successful in 13s
36020fb29e
Three issues caught in subagent review of #383:

1. **BLOCKER (deferred to a follow-up) — cutover gate is currently
   unsafe to flip.** Every slash command (`/breakdown`, `/hold`,
   `/ready`, `/raise-cap`, `/unassign`) collapses to one trigger kind
   `issue_comment.slash_command` at the normaliser boundary
   (`webhook-normalize.ts::parseSlashCommand`). The legacy suppression
   check in `webhook.ts:200-208` matches on the kind only, so setting
   `node_flows.suppress_legacy: ["issue_comment.slash_command"]` would
   skip the whole `case "issue_comment"` arm — breaking `/hold`,
   `/ready`, `/raise-cap`, and `/unassign` since this flow only
   handles `/breakdown`.

   Documented the constraint prominently in the loader header so an
   operator can't trip it. The actual fix (per-command suppression
   entries OR per-command trigger kinds OR flows for every other slash
   command) is out of scope for this PR.

2. **IMPORTANT — `rawBody` was not trimmed.** Legacy passes
   `comment.body.trim()` to both `parseBreakdownComment` and
   `dispatchBreakdown.trigger_comment_body`
   (`webhook-handlers.ts:206,284`); flow passed
   `TriggerSlashCommand.rawBody` verbatim. The parser re-trims
   internally, so parsing is unaffected, but the agent's
   `trigger_comment_body` carried surrounding whitespace — a cosmetic
   divergence the divergence tooling would have flagged.

3. **IMPORTANT — synchronous throw in `parseBreakdownComment` was
   uncaught.** Legacy parser returns `{}` on malformed input rather
   than throwing, but a future tightening that adds explicit throws
   would flip the flow from silent-skip (legacy) to executor-error.
   Wrapped the parser call in try/catch + `taskId: FILTER_DROP`.

Tests added (3, total 14):
- rawBody with surrounding whitespace → trimmed before reaching dispatch
- parseBreakdownComment throws → silent skip, no executor error
- empty `/breakdown` body → defaults applied via wrapped helper

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
charles deleted branch feat/flows-slash-breakdown 2026-04-26 14:13:19 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
charles/claude-hooks!383
No description provided.