Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions internal/skilldoc/incident_comment_guidance_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package skilldoc

import (
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"testing"
)

func TestIncidentCardCommentWorkflow(t *testing.T) {
card, err := os.ReadFile(filepath.Join("..", "..", "skills", "flashduty", "reference", "incident.md"))
if err != nil {
t.Fatal(err)
}

body := string(card)
quotedHeredoc := "COMMENT=$(cat <<'FDUTY_COMMENT_7F3A9C2E_EOF'"
commentCommand := `fduty incident comment "$ID" --comment "$COMMENT"`
timelineCommand := `fduty incident timeline "$ID" --output-format toon`

previous := -1
for _, requirement := range []string{quotedHeredoc, commentCommand, timelineCommand} {
position := strings.Index(body, requirement)
if position == -1 {
t.Errorf("incident card is missing %q", requirement)
continue
}
if position <= previous {
t.Errorf("incident card must place %q after the previous workflow step", requirement)
}
previous = position
}

if !strings.Contains(body, "After every comment write, read back every target and verify the intended comment is present before reporting success.") {
t.Error("incident card must require content-fidelity verification after every comment write")
}
if !strings.Contains(body, "choose a fresh delimiter that is absent as a full line in the intended comment") {
t.Error("incident card must require a collision-free heredoc delimiter")
}
if strings.Contains(body, `fduty incident comment <incident-id> --comment "Root cause identified: DB failover. Fix deploying."`) {
t.Error("incident card must not retain the unsafe inline comment example")
}
}

func TestIncidentCommentQuotedHeredocPreservesMarkdown(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("Bash fixture is unavailable on Windows")
}

script := `fduty() {
printf '%s' "$5"
}

ID=64b64ca26f84f00000000000
COMMENT=$(cat <<'FDUTY_COMMENT_7F3A9C2E_EOF'
## Investigation
Use ` + "`kubectl get pod`" + ` to inspect the restart.
COMMENT_EOF
The follow-up is still pending.
FDUTY_COMMENT_7F3A9C2E_EOF
)
fduty incident comment "$ID" --comment "$COMMENT"
`

output, err := exec.Command("bash", "-c", script).CombinedOutput()
if err != nil {
t.Fatalf("Bash fixture failed: %v\n%s", err, output)
}

want := "## Investigation\nUse `kubectl get pod` to inspect the restart.\nCOMMENT_EOF\nThe follow-up is still pending."
if got := string(output); got != want {
t.Errorf("comment content changed:\nwant: %q\n got: %q", want, got)
}
}
14 changes: 12 additions & 2 deletions skills/flashduty/reference/incident.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,23 @@ fduty incident similar <incident-id> --limit 5 --output-format toon
# 5. Acknowledge ownership
fduty incident ack <incident-id>

# 6. Post a status comment
fduty incident comment <incident-id> --comment "Root cause identified: DB failover. Fix deploying."
# 6. Post a status comment safely, then read it back
ID=<incident-id>
# Before running, choose a fresh delimiter that is absent as a full line in the intended comment.
COMMENT=$(cat <<'FDUTY_COMMENT_7F3A9C2E_EOF'
Root cause identified: DB failover.
Fix deploying.
FDUTY_COMMENT_7F3A9C2E_EOF
)
fduty incident comment "$ID" --comment "$COMMENT"
fduty incident timeline "$ID" --output-format toon

# 7. Resolve with root-cause note
fduty incident resolve <incident-id> --root-cause "DB primary failover delay" --resolution "Failover completed; latency normal."
```

After every comment write, read back every target and verify the intended comment is present before reporting success. `Commented on ...` proves acceptance, not content fidelity.

## Hot flow — full fault analysis (read-only summary)

When asked to **summarize / analyze** an incident — 详情 + 关联告警 + 变更 + 时间线 + 相似故障 + 复盘 — `incident detail` does **not** contain the alerts / timeline / similar / post-mortem / change data; each is its own command. **Your first action must be the bundled script** — do not hand-pick one or two commands and write the rest from memory. One call fetches all six aspects:
Expand Down