Add human escalation path with severity levels (gt-1z3z)
Implements structured escalation channel for Gas Town: - gt escalate command with CRITICAL/HIGH/MEDIUM severity levels - Mayor startup check for pending escalations - Escalation beads with tag for audit trail - Mail routing to overseer with priority mapping - Documentation in docs/escalation.md 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
149
docs/escalation.md
Normal file
149
docs/escalation.md
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
# Gas Town Escalation Protocol
|
||||||
|
|
||||||
|
> Reference for human escalation path in Gas Town
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Gas Town agents can escalate issues to the human overseer when automated
|
||||||
|
resolution isn't possible. This provides a structured channel for:
|
||||||
|
|
||||||
|
- System-threatening errors (data corruption, security issues)
|
||||||
|
- Unresolvable conflicts (merge conflicts, ambiguous requirements)
|
||||||
|
- Design decisions requiring human judgment
|
||||||
|
- Edge cases in molecular algebra that can't proceed without guidance
|
||||||
|
|
||||||
|
## Severity Levels
|
||||||
|
|
||||||
|
| Level | Priority | Description | Examples |
|
||||||
|
|-------|----------|-------------|----------|
|
||||||
|
| **CRITICAL** | P0 (urgent) | System-threatening, immediate attention | Data corruption, security breach, system down |
|
||||||
|
| **HIGH** | P1 (high) | Important blocker, needs human soon | Unresolvable merge conflict, critical bug, ambiguous spec |
|
||||||
|
| **MEDIUM** | P2 (normal) | Standard escalation, human at convenience | Design decision needed, unclear requirements |
|
||||||
|
|
||||||
|
## Escalation Command
|
||||||
|
|
||||||
|
Any agent can escalate directly using `gt escalate`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Basic escalation (default: MEDIUM severity)
|
||||||
|
gt escalate "Database migration failed"
|
||||||
|
|
||||||
|
# Critical escalation - immediate attention
|
||||||
|
gt escalate -s CRITICAL "Data corruption detected in user table"
|
||||||
|
|
||||||
|
# High priority escalation
|
||||||
|
gt escalate -s HIGH "Merge conflict cannot be resolved automatically"
|
||||||
|
|
||||||
|
# With additional details
|
||||||
|
gt escalate -s MEDIUM "Need clarification on API design" -m "The spec mentions both REST and GraphQL..."
|
||||||
|
```
|
||||||
|
|
||||||
|
### What happens on escalation
|
||||||
|
|
||||||
|
1. **Bead created**: An escalation bead (tagged `escalation`) is created for audit trail
|
||||||
|
2. **Mail sent**: Mail is sent to the `overseer` (human operator) with appropriate priority
|
||||||
|
3. **Activity logged**: Event logged to the activity feed for visibility
|
||||||
|
|
||||||
|
## Escalation Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
Any Agent Overseer (Human)
|
||||||
|
| |
|
||||||
|
| gt escalate -s HIGH "msg" |
|
||||||
|
|----------------------------->|
|
||||||
|
| |
|
||||||
|
| [ESCALATION] msg (P1 mail) |
|
||||||
|
|----------------------------->|
|
||||||
|
| |
|
||||||
|
| Reviews & resolves |
|
||||||
|
| |
|
||||||
|
| bd close <esc-id> |
|
||||||
|
|<-----------------------------|
|
||||||
|
```
|
||||||
|
|
||||||
|
## Mayor Startup Check
|
||||||
|
|
||||||
|
On `gt prime`, the Mayor automatically checks for pending escalations:
|
||||||
|
|
||||||
|
```
|
||||||
|
## PENDING ESCALATIONS
|
||||||
|
|
||||||
|
There are 3 escalation(s) awaiting human attention:
|
||||||
|
|
||||||
|
CRITICAL: 1
|
||||||
|
HIGH: 1
|
||||||
|
MEDIUM: 1
|
||||||
|
|
||||||
|
[CRITICAL] Data corruption detected (gt-abc)
|
||||||
|
[HIGH] Merge conflict in auth module (gt-def)
|
||||||
|
[MEDIUM] API design clarification needed (gt-ghi)
|
||||||
|
|
||||||
|
**Action required:** Review escalations with `bd list --tag=escalation`
|
||||||
|
Close resolved ones with `bd close <id> --reason "resolution"`
|
||||||
|
```
|
||||||
|
|
||||||
|
## When to Escalate
|
||||||
|
|
||||||
|
### Agents SHOULD escalate when:
|
||||||
|
|
||||||
|
- **System errors**: Database corruption, disk full, network failures
|
||||||
|
- **Security issues**: Unauthorized access attempts, credential exposure
|
||||||
|
- **Unresolvable conflicts**: Merge conflicts that can't be auto-resolved
|
||||||
|
- **Ambiguous requirements**: Spec is unclear, multiple valid interpretations
|
||||||
|
- **Design decisions**: Architectural choices that need human judgment
|
||||||
|
- **Stuck loops**: Agent is stuck and can't make progress
|
||||||
|
|
||||||
|
### Agents should NOT escalate for:
|
||||||
|
|
||||||
|
- **Normal workflow**: Regular work that can proceed without human input
|
||||||
|
- **Recoverable errors**: Transient failures that will auto-retry
|
||||||
|
- **Information queries**: Questions that can be answered from context
|
||||||
|
|
||||||
|
## Molecular Algebra Edge Cases
|
||||||
|
|
||||||
|
All edge cases in molecular algebra should escalate rather than fail silently:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Example: Molecule step has conflicting dependencies
|
||||||
|
if hasConflictingDeps {
|
||||||
|
// Don't fail silently - escalate
|
||||||
|
exec.Command("gt", "escalate", "-s", "HIGH",
|
||||||
|
"Molecule step has conflicting dependencies: "+stepID).Run()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This ensures:
|
||||||
|
1. Issues are visible to humans
|
||||||
|
2. Audit trail exists for debugging
|
||||||
|
3. System doesn't silently break
|
||||||
|
|
||||||
|
## Viewing Escalations
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# List all open escalations
|
||||||
|
bd list --status=open --tag=escalation
|
||||||
|
|
||||||
|
# View specific escalation
|
||||||
|
bd show <escalation-id>
|
||||||
|
|
||||||
|
# Close resolved escalation
|
||||||
|
bd close <id> --reason "Resolved by fixing X"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Integration with Existing Flow
|
||||||
|
|
||||||
|
The escalation system integrates with the existing polecat exit flow:
|
||||||
|
|
||||||
|
| Exit Type | When to Use | Escalation? |
|
||||||
|
|-----------|-------------|-------------|
|
||||||
|
| `COMPLETED` | Work done successfully | No |
|
||||||
|
| `ESCALATED` | Hit blocker, needs human | Yes (automatic via `gt done --exit ESCALATED`) |
|
||||||
|
| `DEFERRED` | Work paused, will resume | No |
|
||||||
|
|
||||||
|
When a polecat uses `gt done --exit ESCALATED`:
|
||||||
|
1. Witness receives the notification
|
||||||
|
2. Witness can forward to Mayor with `ESCALATION:` subject
|
||||||
|
3. Mayor callback handler forwards to overseer
|
||||||
|
|
||||||
|
The new `gt escalate` command provides a more direct path that any agent can use,
|
||||||
|
with structured severity levels and audit trail.
|
||||||
@@ -222,6 +222,16 @@ gt mail send <addr> -s "Subject" -m "Body"
|
|||||||
gt mail send --human -s "..." # To overseer
|
gt mail send --human -s "..." # To overseer
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Escalation
|
||||||
|
```bash
|
||||||
|
gt escalate "topic" # Default: MEDIUM severity
|
||||||
|
gt escalate -s CRITICAL "msg" # Urgent, immediate attention
|
||||||
|
gt escalate -s HIGH "msg" # Important blocker
|
||||||
|
gt escalate -s MEDIUM "msg" -m "Details..."
|
||||||
|
```
|
||||||
|
|
||||||
|
See [escalation.md](escalation.md) for full protocol.
|
||||||
|
|
||||||
### Sessions
|
### Sessions
|
||||||
```bash
|
```bash
|
||||||
gt handoff # Request cycle (context-aware)
|
gt handoff # Request cycle (context-aware)
|
||||||
|
|||||||
254
internal/cmd/escalate.go
Normal file
254
internal/cmd/escalate.go
Normal file
@@ -0,0 +1,254 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/steveyegge/gastown/internal/events"
|
||||||
|
"github.com/steveyegge/gastown/internal/mail"
|
||||||
|
"github.com/steveyegge/gastown/internal/style"
|
||||||
|
"github.com/steveyegge/gastown/internal/workspace"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Escalation severity levels.
|
||||||
|
// These map to mail priorities and indicate urgency for human attention.
|
||||||
|
const (
|
||||||
|
// SeverityCritical (P0) - System-threatening issues requiring immediate human attention.
|
||||||
|
// Examples: data corruption, security breach, complete system failure.
|
||||||
|
SeverityCritical = "CRITICAL"
|
||||||
|
|
||||||
|
// SeverityHigh (P1) - Important blockers that need human attention soon.
|
||||||
|
// Examples: unresolvable merge conflicts, critical blocking bugs, ambiguous requirements.
|
||||||
|
SeverityHigh = "HIGH"
|
||||||
|
|
||||||
|
// SeverityMedium (P2) - Standard escalations for human attention at convenience.
|
||||||
|
// Examples: unclear requirements, design decisions needed, non-blocking issues.
|
||||||
|
SeverityMedium = "MEDIUM"
|
||||||
|
)
|
||||||
|
|
||||||
|
var escalateCmd = &cobra.Command{
|
||||||
|
Use: "escalate <topic>",
|
||||||
|
GroupID: GroupComm,
|
||||||
|
Short: "Escalate an issue to the human overseer",
|
||||||
|
Long: `Escalate an issue to the human overseer for attention.
|
||||||
|
|
||||||
|
This is the structured escalation channel for Gas Town. Any agent can use this
|
||||||
|
to request human intervention when automated resolution isn't possible.
|
||||||
|
|
||||||
|
Severity levels:
|
||||||
|
CRITICAL (P0) - System-threatening, immediate attention required
|
||||||
|
Examples: data corruption, security breach, system down
|
||||||
|
HIGH (P1) - Important blocker, needs human soon
|
||||||
|
Examples: unresolvable conflict, critical bug, ambiguous spec
|
||||||
|
MEDIUM (P2) - Standard escalation, human attention at convenience
|
||||||
|
Examples: design decision needed, unclear requirements
|
||||||
|
|
||||||
|
The escalation creates an audit trail bead and sends mail to the overseer
|
||||||
|
with appropriate priority. All molecular algebra edge cases should escalate
|
||||||
|
here rather than failing silently.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
gt escalate "Database migration failed"
|
||||||
|
gt escalate -s CRITICAL "Data corruption detected in user table"
|
||||||
|
gt escalate -s HIGH "Merge conflict cannot be resolved automatically"
|
||||||
|
gt escalate -s MEDIUM "Need clarification on API design" -m "Details here..."`,
|
||||||
|
Args: cobra.MinimumNArgs(1),
|
||||||
|
RunE: runEscalate,
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
escalateSeverity string
|
||||||
|
escalateMessage string
|
||||||
|
escalateDryRun bool
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
escalateCmd.Flags().StringVarP(&escalateSeverity, "severity", "s", SeverityMedium,
|
||||||
|
"Severity level: CRITICAL, HIGH, or MEDIUM")
|
||||||
|
escalateCmd.Flags().StringVarP(&escalateMessage, "message", "m", "",
|
||||||
|
"Additional details about the escalation")
|
||||||
|
escalateCmd.Flags().BoolVarP(&escalateDryRun, "dry-run", "n", false,
|
||||||
|
"Show what would be done without executing")
|
||||||
|
rootCmd.AddCommand(escalateCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func runEscalate(cmd *cobra.Command, args []string) error {
|
||||||
|
topic := strings.Join(args, " ")
|
||||||
|
|
||||||
|
// Validate severity
|
||||||
|
severity := strings.ToUpper(escalateSeverity)
|
||||||
|
if severity != SeverityCritical && severity != SeverityHigh && severity != SeverityMedium {
|
||||||
|
return fmt.Errorf("invalid severity '%s': must be CRITICAL, HIGH, or MEDIUM", escalateSeverity)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map severity to mail priority
|
||||||
|
var priority mail.Priority
|
||||||
|
switch severity {
|
||||||
|
case SeverityCritical:
|
||||||
|
priority = mail.PriorityUrgent
|
||||||
|
case SeverityHigh:
|
||||||
|
priority = mail.PriorityHigh
|
||||||
|
default:
|
||||||
|
priority = mail.PriorityNormal
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find workspace
|
||||||
|
townRoot, err := workspace.FindFromCwdOrError()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("not in a Gas Town workspace: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect agent identity
|
||||||
|
agentID, err := detectAgentIdentity()
|
||||||
|
if err != nil {
|
||||||
|
agentID = "unknown"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build mail subject with severity tag
|
||||||
|
subject := fmt.Sprintf("[%s] %s", severity, topic)
|
||||||
|
|
||||||
|
// Build mail body
|
||||||
|
var bodyParts []string
|
||||||
|
bodyParts = append(bodyParts, fmt.Sprintf("Escalated by: %s", agentID))
|
||||||
|
bodyParts = append(bodyParts, fmt.Sprintf("Severity: %s", severity))
|
||||||
|
if escalateMessage != "" {
|
||||||
|
bodyParts = append(bodyParts, "")
|
||||||
|
bodyParts = append(bodyParts, escalateMessage)
|
||||||
|
}
|
||||||
|
body := strings.Join(bodyParts, "\n")
|
||||||
|
|
||||||
|
// Dry run mode
|
||||||
|
if escalateDryRun {
|
||||||
|
fmt.Printf("Would create escalation:\n")
|
||||||
|
fmt.Printf(" Severity: %s\n", severity)
|
||||||
|
fmt.Printf(" Priority: %s\n", priority)
|
||||||
|
fmt.Printf(" Subject: %s\n", subject)
|
||||||
|
fmt.Printf(" Body:\n%s\n", indentText(body, " "))
|
||||||
|
fmt.Printf("Would send mail to: overseer\n")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create escalation bead for audit trail
|
||||||
|
beadID, err := createEscalationBead(topic, severity, agentID, escalateMessage)
|
||||||
|
if err != nil {
|
||||||
|
// Non-fatal - escalation mail is more important
|
||||||
|
style.PrintWarning("could not create escalation bead: %v", err)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("%s Created escalation bead: %s\n", style.Bold.Render("📋"), beadID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send mail to overseer
|
||||||
|
router := mail.NewRouter(townRoot)
|
||||||
|
msg := &mail.Message{
|
||||||
|
From: agentID,
|
||||||
|
To: "overseer",
|
||||||
|
Subject: subject,
|
||||||
|
Body: body,
|
||||||
|
Priority: priority,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := router.Send(msg); err != nil {
|
||||||
|
return fmt.Errorf("sending escalation mail: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log to activity feed
|
||||||
|
payload := events.EscalationPayload("", agentID, "overseer", topic)
|
||||||
|
payload["severity"] = severity
|
||||||
|
if beadID != "" {
|
||||||
|
payload["bead"] = beadID
|
||||||
|
}
|
||||||
|
_ = events.LogFeed(events.TypeEscalationSent, agentID, payload)
|
||||||
|
|
||||||
|
// Print confirmation with severity-appropriate styling
|
||||||
|
var emoji string
|
||||||
|
switch severity {
|
||||||
|
case SeverityCritical:
|
||||||
|
emoji = "🚨"
|
||||||
|
case SeverityHigh:
|
||||||
|
emoji = "⚠️"
|
||||||
|
default:
|
||||||
|
emoji = "📢"
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("%s Escalation sent to overseer [%s]\n", emoji, severity)
|
||||||
|
fmt.Printf(" Topic: %s\n", topic)
|
||||||
|
if beadID != "" {
|
||||||
|
fmt.Printf(" Bead: %s\n", beadID)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// detectAgentIdentity returns the current agent's identity string.
|
||||||
|
func detectAgentIdentity() (string, error) {
|
||||||
|
// Try GT_ROLE first
|
||||||
|
if role := os.Getenv("GT_ROLE"); role != "" {
|
||||||
|
return role, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to detect from cwd
|
||||||
|
agentID, _, _, err := resolveSelfTarget()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return agentID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// createEscalationBead creates a bead to track the escalation.
|
||||||
|
func createEscalationBead(topic, severity, from, details string) (string, error) {
|
||||||
|
// Use bd create to make the escalation bead
|
||||||
|
args := []string{
|
||||||
|
"create",
|
||||||
|
"--title", fmt.Sprintf("[ESCALATION] %s", topic),
|
||||||
|
"--type", "task", // Use task type since escalation isn't a standard type
|
||||||
|
"--priority", severityToBeadsPriority(severity),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add description with escalation metadata
|
||||||
|
desc := fmt.Sprintf("Escalation from: %s\nSeverity: %s\n", from, severity)
|
||||||
|
if details != "" {
|
||||||
|
desc += "\n" + details
|
||||||
|
}
|
||||||
|
args = append(args, "--description", desc)
|
||||||
|
|
||||||
|
// Add tag for filtering
|
||||||
|
args = append(args, "--tag", "escalation")
|
||||||
|
|
||||||
|
cmd := exec.Command("bd", args...)
|
||||||
|
out, err := cmd.Output()
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("bd create: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse bead ID from output (bd create outputs: "Created bead: gt-xxxxx")
|
||||||
|
output := strings.TrimSpace(string(out))
|
||||||
|
parts := strings.Split(output, ": ")
|
||||||
|
if len(parts) >= 2 {
|
||||||
|
return strings.TrimSpace(parts[len(parts)-1]), nil
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("could not parse bead ID from: %s", output)
|
||||||
|
}
|
||||||
|
|
||||||
|
// severityToBeadsPriority converts severity to beads priority string.
|
||||||
|
func severityToBeadsPriority(severity string) string {
|
||||||
|
switch severity {
|
||||||
|
case SeverityCritical:
|
||||||
|
return "0" // P0
|
||||||
|
case SeverityHigh:
|
||||||
|
return "1" // P1
|
||||||
|
default:
|
||||||
|
return "2" // P2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// indentText indents each line of text with the given prefix.
|
||||||
|
func indentText(text, prefix string) string {
|
||||||
|
lines := strings.Split(text, "\n")
|
||||||
|
for i, line := range lines {
|
||||||
|
lines[i] = prefix + line
|
||||||
|
}
|
||||||
|
return strings.Join(lines, "\n")
|
||||||
|
}
|
||||||
@@ -135,6 +135,11 @@ func runPrime(cmd *cobra.Command, args []string) error {
|
|||||||
// Run gt mail check --inject to inject any pending mail
|
// Run gt mail check --inject to inject any pending mail
|
||||||
runMailCheckInject(cwd)
|
runMailCheckInject(cwd)
|
||||||
|
|
||||||
|
// For Mayor, check for pending escalations
|
||||||
|
if ctx.Role == RoleMayor {
|
||||||
|
checkPendingEscalations(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
// Output startup directive for roles that should announce themselves
|
// Output startup directive for roles that should announce themselves
|
||||||
// Skip if in autonomous mode (slung work provides its own directive)
|
// Skip if in autonomous mode (slung work provides its own directive)
|
||||||
if !hasSlungWork {
|
if !hasSlungWork {
|
||||||
@@ -1298,3 +1303,90 @@ func ensureBeadsRedirect(ctx RoleContext) {
|
|||||||
// Note: We don't print a message here to avoid cluttering prime output
|
// Note: We don't print a message here to avoid cluttering prime output
|
||||||
// The redirect is silently restored
|
// The redirect is silently restored
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// checkPendingEscalations queries for open escalation beads and displays them prominently.
|
||||||
|
// This is called on Mayor startup to surface issues needing human attention.
|
||||||
|
func checkPendingEscalations(ctx RoleContext) {
|
||||||
|
// Query for open escalations using bd list with tag filter
|
||||||
|
cmd := exec.Command("bd", "list", "--status=open", "--tag=escalation", "--json")
|
||||||
|
cmd.Dir = ctx.WorkDir
|
||||||
|
|
||||||
|
var stdout, stderr bytes.Buffer
|
||||||
|
cmd.Stdout = &stdout
|
||||||
|
cmd.Stderr = &stderr
|
||||||
|
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
// Silently skip - escalation check is best-effort
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse JSON output
|
||||||
|
var escalations []struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Priority int `json:"priority"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Created string `json:"created"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(stdout.Bytes(), &escalations); err != nil || len(escalations) == 0 {
|
||||||
|
// No escalations or parse error
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count by severity
|
||||||
|
critical := 0
|
||||||
|
high := 0
|
||||||
|
medium := 0
|
||||||
|
for _, e := range escalations {
|
||||||
|
switch e.Priority {
|
||||||
|
case 0:
|
||||||
|
critical++
|
||||||
|
case 1:
|
||||||
|
high++
|
||||||
|
default:
|
||||||
|
medium++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display prominently
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Printf("%s\n\n", style.Bold.Render("## 🚨 PENDING ESCALATIONS"))
|
||||||
|
fmt.Printf("There are %d escalation(s) awaiting human attention:\n\n", len(escalations))
|
||||||
|
|
||||||
|
if critical > 0 {
|
||||||
|
fmt.Printf(" 🔴 CRITICAL: %d\n", critical)
|
||||||
|
}
|
||||||
|
if high > 0 {
|
||||||
|
fmt.Printf(" 🟠 HIGH: %d\n", high)
|
||||||
|
}
|
||||||
|
if medium > 0 {
|
||||||
|
fmt.Printf(" 🟡 MEDIUM: %d\n", medium)
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
|
||||||
|
// Show first few escalations
|
||||||
|
maxShow := 5
|
||||||
|
if len(escalations) < maxShow {
|
||||||
|
maxShow = len(escalations)
|
||||||
|
}
|
||||||
|
for i := 0; i < maxShow; i++ {
|
||||||
|
e := escalations[i]
|
||||||
|
severity := "MEDIUM"
|
||||||
|
switch e.Priority {
|
||||||
|
case 0:
|
||||||
|
severity = "CRITICAL"
|
||||||
|
case 1:
|
||||||
|
severity = "HIGH"
|
||||||
|
}
|
||||||
|
fmt.Printf(" • [%s] %s (%s)\n", severity, e.Title, e.ID)
|
||||||
|
}
|
||||||
|
if len(escalations) > maxShow {
|
||||||
|
fmt.Printf(" ... and %d more\n", len(escalations)-maxShow)
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
|
||||||
|
fmt.Println("**Action required:** Review escalations with `bd list --tag=escalation`")
|
||||||
|
fmt.Println("Close resolved ones with `bd close <id> --reason \"resolution\"`")
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user