Skip to content
Open
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
50 changes: 50 additions & 0 deletions internal/skilldoc/skill_guidance_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package skilldoc

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

func TestFlashdutySkillGuidesLargeListScans(t *testing.T) {
body := readSkillFile(t, "SKILL.md")
incident := readSkillFile(t, "reference", "incident.md")
insight := readSkillFile(t, "reference", "insight.md")

for _, want := range []string{
"Large list scans",
"redirect JSON to a temp file",
"Do not dump page-sized JSON",
} {
if !strings.Contains(body, want) {
t.Errorf("SKILL.md missing large-list guidance %q", want)
}
}
for _, want := range []string{
"Do not re-run `incident alerts`, `incident similar`, `incident timeline`, or `change list`",
"Page-sized scans go to files",
} {
if !strings.Contains(incident, want) {
t.Errorf("incident card missing large-list/summary guidance %q", want)
}
}
for _, want := range []string{
"For multi-dimensional reports, fetch once to a temp file",
"Do not run the same account-wide list again for each `jq` bucket",
} {
if !strings.Contains(insight, want) {
t.Errorf("insight card missing reusable JSON guidance %q", want)
}
}
}

func readSkillFile(t *testing.T, parts ...string) string {
t.Helper()
p := filepath.Join(append([]string{"..", "..", "skills", "flashduty"}, parts...)...)
b, err := os.ReadFile(p)
if err != nil {
t.Fatalf("read %s: %v", p, err)
}
return string(b)
}
2 changes: 2 additions & 0 deletions skills/flashduty/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ Append `--output-format toon` to read commands: it drops the per-row repeated ke

**Shape the payload before you fetch it.** For ID scans, counts, or "find the matching row" tasks, prefer `--fields` projections and compact list verbs over full detail dumps. Huge raw JSON dumps are a last resort, not a default.

**Large list scans: file first, summarize second.** When you truly need `--json` for `jq` over a page-sized list (`--limit 100`, broad channel/account windows, incident/alert/on-call reports), redirect JSON to a temp file and emit only counts or selected fields: `fduty ... --json > /tmp/fduty_rows.json && jq '...' /tmp/fduty_rows.json`. Do not dump page-sized JSON into the chat transcript, and do not run the same list again for each aggregation bucket.

**Empty result = authoritative not-found.** A filter returning `[]` means no such entity in scope — report it (optionally the 1–2 closest names) and stop. Do **not** brute-force (no shifted-keyword re-queries, no widening past caps, no full-dump grep). Never infer "feature not enabled" from an empty list, and never fabricate data absent from tool output.

**A result you did not fetch is "unknown", never "empty".** You may report a command's result — including "returned empty" or any count/list/finding — **only if that exact command appears in your tool-call history this turn**. If you did not run it, the honest answer is "未查询 / not queried", followed by the command to run. Writing "`incident similar` 返回空" or "无变更" for a command you never executed is fabrication, not a summary.
Expand Down
3 changes: 3 additions & 0 deletions skills/flashduty/reference/incident.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ bash <skill-dir>/scripts/incident-summary.sh <incident-id>

`<skill-dir>` is this skill's base directory — you were given it when the skill loaded (it is also the folder you read this card from). The script runs every command below and prints the results in one block, so each section of your summary is backed by real output and there is nothing to guess. (To tie post-mortems to *this* incident, re-run `incident post-mortem-list --channel-ids <channel-id>` with the `channel_id` from `detail`.)

Do not re-run `incident alerts`, `incident similar`, `incident timeline`, or `change list` after `incident-summary.sh` unless the script output is missing/truncated for that exact section or you need a narrower follow-up query.

If you fetch the pieces by hand instead, run **all six** — they are cheap reads:

```bash
Expand Down Expand Up @@ -364,6 +366,7 @@ List war rooms
- **`update` vs `reset`**: `update <id>` edits title/description/severity/custom fields. `reset <incident-id>` additionally supports `--impact`, `--root-cause`, `--resolution` (the AI narrative fields). Use `reset` for post-incident write-back.
- **If `list` returns a `total`, use it instead of page-walking.** For "how many incidents are Triggered / Processing / Closed", run one filtered `incident list --progress <bucket> ...` per bucket and read the returned `total`. Do not fetch page 1/2/3 just to derive counts the server already computed.
- **Use `--fields` to keep list scans compact.** When the goal is to identify matching incidents or collect IDs/numbers/titles, project only the needed columns first, then fetch one target incident with `detail` / `alerts` / `timeline`.
- **Page-sized scans go to files.** For analytics over many incidents, use `--json > /tmp/incidents.json` and run `jq` against the file. Print only the aggregate or selected fields; avoid raw `--limit 100` JSON/toon dumps in chat.
- **`--list` window cap**: `--since`/`--until` window must be < 31 days; `--limit` max 100. Empty result is authoritative — do not widen filters or retry.
- **`merge` is irreversible**: source incidents are absorbed into target permanently. Always list and confirm both IDs before running.
- **`remove --force`** bypasses the interactive confirmation prompt — never pass `--force` unless the user has explicitly said so.
Expand Down
3 changes: 3 additions & 0 deletions skills/flashduty/reference/insight.md
Original file line number Diff line number Diff line change
Expand Up @@ -328,13 +328,16 @@ Two families with **identical value syntax** but different flag names:

Both families accept: relative duration (`30d`, `24h`), `now`, `+7d`, a date, or Unix seconds — **except** `incident-export`, which takes **epoch seconds only** for `--start-time`/`--end-time` (flag type is int64).

For multi-dimensional reports, fetch once to a temp file, then run every `jq` aggregation against that same file. Do not run the same account-wide list again for each `jq` bucket; if you need status, severity, channel, and responder cuts, one JSON capture plus local aggregations is cheaper and more consistent.

## Gotchas

- **Two time-flag families.** Passing `--since` to an `--start-time` command (or vice-versa) fails with `unknown flag`. See the table above.
- **`incident-export --start-time`/`--end-time` are epoch seconds only**, not relative strings — use `$(date -v-30d +%s)`. All other `--start-time` commands accept `30d`/`now`.
- **`top-alerts --label`** only accepts `check` or `resource`. Any other value (e.g. `integration_name`) returns HTTP 400.
- **Export commands output raw CSV, not JSON.** Redirect to a file; dumping CSV into context burns tokens and is unreadable. No `--limit`/`--page` — exports emit the full filtered set.
- **`insight incidents` and `incident-list` are siblings**, not the same. `incidents` uses `--since`/`--until`, paginates, and is token-light. `incident-list` uses `--start-time`/`--end-time`, adds `--severities`/`--responder-ids`/`--query`/cursor (`--search-after-ctx`), and is the filterable variant.
- **Large `incident-list` pages belong in files.** For any `--limit 100` or broad-window `incident-list`, redirect JSON to `/tmp/*.json`, then print only totals or selected fields. Do not paste page-sized JSON/toon into the transcript.
- **All `insight` commands hit the OLAP backend.** HTTP 500 means the backend is down — report it, do not retry.
- **Empty result is authoritative.** A zero-row response means no matching data for that scope/window — do not widen filters or re-query with shifted keywords.
- **`--aggregate-unit`** (on `account`, `alert-topk-by-label`, `channel`, `responder`, `team` and their exports) splits results into time buckets: `day` / `week` / `month`. When set, the window must span ≥24 h; `day` additionally caps the range at 31 days.
Expand Down
Loading