diff --git a/internal/skilldoc/incident_comment_guidance_test.go b/internal/skilldoc/incident_comment_guidance_test.go new file mode 100644 index 0000000..2dc7062 --- /dev/null +++ b/internal/skilldoc/incident_comment_guidance_test.go @@ -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 --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) + } +} diff --git a/skills/flashduty/reference/incident.md b/skills/flashduty/reference/incident.md index 4063413..8bafdae 100644 --- a/skills/flashduty/reference/incident.md +++ b/skills/flashduty/reference/incident.md @@ -56,13 +56,23 @@ fduty incident similar --limit 5 --output-format toon # 5. Acknowledge ownership fduty incident ack -# 6. Post a status comment -fduty incident comment --comment "Root cause identified: DB failover. Fix deploying." +# 6. Post a status comment safely, then read it back +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 --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: