From 7af84fc1b3770807cb1c96f98e825613cebf5353 Mon Sep 17 00:00:00 2001 From: ysyneu Date: Tue, 21 Jul 2026 21:52:54 -0700 Subject: [PATCH 1/2] fix(skill): preserve incident comment content --- .../incident_comment_guidance_test.go | 72 +++++++++++++++++++ skills/flashduty/reference/incident.md | 13 +++- 2 files changed, 83 insertions(+), 2 deletions(-) create mode 100644 internal/skilldoc/incident_comment_guidance_test.go diff --git a/internal/skilldoc/incident_comment_guidance_test.go b/internal/skilldoc/incident_comment_guidance_test.go new file mode 100644 index 0000000..b1d7393 --- /dev/null +++ b/internal/skilldoc/incident_comment_guidance_test.go @@ -0,0 +1,72 @@ +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 <<'COMMENT_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, `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 <<'COMMENT_EOF' +## Investigation +Use ` + "`kubectl get pod`" + ` to inspect the restart. +The follow-up is still pending. +COMMENT_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.\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..ca0f6f4 100644 --- a/skills/flashduty/reference/incident.md +++ b/skills/flashduty/reference/incident.md @@ -56,13 +56,22 @@ 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= +COMMENT=$(cat <<'COMMENT_EOF' +Root cause identified: DB failover. +Fix deploying. +COMMENT_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: From 8373bce9c1f0128785cbefe006c10d4ed8da5df8 Mon Sep 17 00:00:00 2001 From: ysyneu Date: Tue, 21 Jul 2026 22:12:22 -0700 Subject: [PATCH 2/2] fix(skill): harden heredoc delimiter --- internal/skilldoc/incident_comment_guidance_test.go | 12 ++++++++---- skills/flashduty/reference/incident.md | 5 +++-- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/internal/skilldoc/incident_comment_guidance_test.go b/internal/skilldoc/incident_comment_guidance_test.go index b1d7393..2dc7062 100644 --- a/internal/skilldoc/incident_comment_guidance_test.go +++ b/internal/skilldoc/incident_comment_guidance_test.go @@ -16,7 +16,7 @@ func TestIncidentCardCommentWorkflow(t *testing.T) { } body := string(card) - quotedHeredoc := "COMMENT=$(cat <<'COMMENT_EOF'" + quotedHeredoc := "COMMENT=$(cat <<'FDUTY_COMMENT_7F3A9C2E_EOF'" commentCommand := `fduty incident comment "$ID" --comment "$COMMENT"` timelineCommand := `fduty incident timeline "$ID" --output-format toon` @@ -36,6 +36,9 @@ func TestIncidentCardCommentWorkflow(t *testing.T) { 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") } @@ -51,11 +54,12 @@ func TestIncidentCommentQuotedHeredocPreservesMarkdown(t *testing.T) { } ID=64b64ca26f84f00000000000 -COMMENT=$(cat <<'COMMENT_EOF' +COMMENT=$(cat <<'FDUTY_COMMENT_7F3A9C2E_EOF' ## Investigation Use ` + "`kubectl get pod`" + ` to inspect the restart. -The follow-up is still pending. COMMENT_EOF +The follow-up is still pending. +FDUTY_COMMENT_7F3A9C2E_EOF ) fduty incident comment "$ID" --comment "$COMMENT" ` @@ -65,7 +69,7 @@ fduty incident comment "$ID" --comment "$COMMENT" t.Fatalf("Bash fixture failed: %v\n%s", err, output) } - want := "## Investigation\nUse `kubectl get pod` to inspect the restart.\nThe follow-up is still pending." + 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 ca0f6f4..8bafdae 100644 --- a/skills/flashduty/reference/incident.md +++ b/skills/flashduty/reference/incident.md @@ -58,10 +58,11 @@ fduty incident ack # 6. Post a status comment safely, then read it back ID= -COMMENT=$(cat <<'COMMENT_EOF' +# 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. -COMMENT_EOF +FDUTY_COMMENT_7F3A9C2E_EOF ) fduty incident comment "$ID" --comment "$COMMENT" fduty incident timeline "$ID" --output-format toon