Triage Mentions Skill

Surface every place someone has pinged you — Slack, Jira, Confluence, GitHub — and help you respond without losing your mind.


Phase 1 — Preflight

Before doing anything else, verify the following MCP connectors are reachable:

If all connectors are unavailable, stop and tell the human: “No connectors are configured. Fix at least one and try again.”

If only some connectors are missing, warn the human which sources will be skipped, then proceed with the available ones.

Resolve the human’s identity once and cache for the session:


Phase 2 — Scan Sources

Run scans across all three sources. Each scan should look back 7 days by default (override if the human specifies a different window via skill args, e.g. /triage-mentions 14d).

Compute the lookback cutoff date as an absolute ISO 8601 timestamp at scan start (e.g. 2024-01-08T14:00:00Z). Use this value in all queries — do not rely on relative date helpers whose syntax varies across API versions.

Rate limiting note: Jira and Confluence share the same Atlassian OAuth token. Run their scans sequentially (Jira first, then Confluence) to avoid concurrent 429s. If a 429 is received, back off 10 seconds and retry once before surfacing the error.

2a. Slack Scan

Search for messages where the human is directly mentioned using their cached Slack member ID:

query: "@{slack_member_id}"

Use slack_search_public_and_private for full coverage. Fall back to slack_search_public if private search is unavailable.

For each result:

Limit to 50 results. If the raw search returns more, note the overflow count in the triage list header (e.g. Showing 50 of 83 — re-invoke with a shorter window to see the rest).

2b. Jira Scan

Run these two JQL queries sequentially:

Query 1 — direct mentions:

(comment ~ "accountId:{account_id}" OR description ~ "accountId:{account_id}") AND updatedDate >= "{cutoff_iso}" ORDER BY updated DESC

Query 2 — issues owned by the human with new activity:

(assignee = "{account_id}" OR reporter = "{account_id}") AND updatedDate >= "{cutoff_iso}" ORDER BY updated DESC

Note: use the cached {account_id}, not currentUser()currentUser() resolves to the MCP app’s OAuth identity, not the human.

For each result, fetch the issue via getJiraIssue to get the full comment list. Filter to comments that either:

Skip any issue where the human’s most recent comment is newer than all other comments — they have already responded.

Capture: issue key, summary, comment ID, author, comment text (truncated to 300 chars), date, direct link.

Limit to 30 issues total.

2c. Confluence Scan

Confluence does not store @mentions as plain text — they are ADF user-mention nodes. Full-text search on display name is lossy (misses most real mentions, may include false positives). Use this two-pronged approach and accept that results may be incomplete:

Query 1 — comments referencing the human’s account ID or display name:

type = "comment" AND (text ~ "{account_id}" OR text ~ "{display_name}") AND lastModified >= "{cutoff_iso}" ORDER BY lastModified DESC

Query 2 — pages the human contributes to with recent comment activity:

type = "page" AND space.type = "global" AND contributor = "{account_id}" AND lastModified >= "{cutoff_iso}" ORDER BY lastModified DESC

For each result, fetch the page comments via getConfluencePageFooterComments and getConfluencePageInlineComments. Filter to comments that:

Limitation note: ADF-encoded @mentions may not appear in full-text search results. When presenting Confluence results in Phase 3, prepend a one-line warning: ⚠ Confluence scan may miss some @mention notifications due to API limitations.

Capture: page title, page URL, comment ID, author, comment text (truncated to 300 chars), date.

Limit to 30 results.

2d. GitHub Scan

Search for GitHub notifications where the human is mentioned. Run these gh commands sequentially:

Query 1 — PR and issue review requests / mentions:

gh api "notifications?all=false&participating=true&per_page=50" \
  --paginate --jq '.[] | select(.updated_at >= "{cutoff_iso}")'

This returns notifications the human is subscribed to and participating in. Filter to those with reason of mention, review_requested, or comment.

Query 2 — PRs where the human is reviewed and new comments have arrived since their last review:

gh search prs \
  --involves=@me \
  --state=open \
  --updated=">={cutoff_date}" \
  --json number,title,url,repository,updatedAt \
  --limit 30

For each PR returned by Query 2, fetch comments since the human’s last activity:

gh api repos/{owner}/{repo}/pulls/{number}/comments \
  --jq '.[] | select(.created_at >= "{cutoff_iso}" and .user.login != "{gh_login}")'
gh api repos/{owner}/{repo}/issues/{number}/comments \
  --jq '.[] | select(.created_at >= "{cutoff_iso}" and .user.login != "{gh_login}")'

Query 3 — Issues where the human is mentioned or assigned with new comments:

gh search issues \
  --involves=@me \
  --state=open \
  --updated=">={cutoff_date}" \
  --json number,title,url,repository,updatedAt \
  --limit 30

For each item, capture: repo (owner/name), PR/issue number, title, comment author, comment text (truncated to 300 chars), URL to the specific comment, date.

Skip rule: Skip any PR/issue where the human’s most recent comment is newer than all other comments since the cutoff — they have already responded.

Limit to 50 total GitHub items. Use gh api for pagination where needed.


Phase 3 — Deduplicate and Triage List

Merge all results into a single list. Remove duplicates (same URL/ID). Apply this priority sort:

  1. Direct @mention of the human (highest priority)
  2. Reply to a thread/comment the human started
  3. New comment on an issue/page the human owns or is assigned to
  4. New comment on an issue/page the human has previously commented on (lower confidence — present as a separate section labeled “Also active”)

Present the list to the human grouped by source for readability:

Found {N} items requiring your attention ({date range}):
{If overflow: Showing {50} of {total} — re-invoke with a shorter window to see the rest.}
{If Confluence: ⚠ Confluence scan may miss some @mention notifications due to API limitations.}

── SLACK ──────────────────────────────────────────
[1] #channel-name — @author — "snippet of message..." (2h ago)
    https://...

── JIRA ───────────────────────────────────────────
[2] PROJ-123 — "Issue title" — @author commented: "snippet..." (1d ago)
    https://...

── CONFLUENCE ─────────────────────────────────────
[3] "Page title" — @author commented: "snippet..." (3d ago)
    https://...

── GITHUB ─────────────────────────────────────────
[4] owner/repo #123 — "PR title" — @author commented: "snippet..." (45m ago)
    https://...

── ALSO ACTIVE (lower confidence) ─────────────────
[4] JIRA  PROJ-456 — new comment, you commented here before (2d ago)
    https://...

Enter item numbers to address (e.g. "1 3 5"), "all", or "none":

Wait for the human’s selection before proceeding. If the human says “none”, stop cleanly. If they say “all”, select every item.


Phase 4 — Draft Responses

For each selected item, draft a response. Use all available context:

Tone by platform:

Write all drafts to a temporary file at:

/tmp/triage-mentions-{YYYYMMDD-HHMMSS}.md

(Include seconds to avoid collisions if the skill is re-invoked within the same minute.)

Use this exact structure for each item in the file:

---

## Item {N} — {SOURCE}: {brief title}

**Source:** {SLACK | JIRA | CONFLUENCE}
**From:** @{author}
**Where:** {channel name / issue key / page title}
**Link:** {direct URL}
**Date:** {date}
<!-- Internal: channel_id={channel_id} thread_ts={thread_ts} (Slack only) -->
<!-- Internal: gh_repo={owner/repo} gh_number={number} gh_comment_id={id} (GitHub only) -->

### Context

> {full quoted message or comment, verbatim, max 500 chars — truncate with "…" if longer}

### Draft Response

{Reply content here — first person, direct. Do NOT open with "Posted by Farty Bobo" — dispatch handles attribution.}

### Decision

APPROVE

<!-- Replace APPROVE with one of: APPROVE | DISMISS | RETRY: <your notes> -->
<!-- Parser looks for the first line in this section that matches APPROVE, DISMISS, or RETRY: -->

Note: the Decision section defaults to APPROVE so the human only has to change items they want to dismiss or retry — not approve every single one.

After writing the file, tell the human:

Drafts written to: /tmp/triage-mentions-{timestamp}.md

Open the file, review each draft, and set the Decision for each item:
  APPROVE  — post as-is (default)
  DISMISS  — skip this item
  RETRY: <your notes>  — revise and re-present before posting

Save the file, then tell me "done" to dispatch.

Do NOT proceed until the human explicitly says they’re done annotating.


Phase 5 — Parse Decisions

Re-read the triage file. For each item’s ### Decision section, find the first non-comment line that matches one of:

If no match is found (unannotated, blank, or unrecognized text), ask the human to clarify that specific item before proceeding with others.

RETRY handling:

After RETRY items are resolved, update the corresponding block in the triage file: replace the Draft Response with the final accepted version and change the Decision line to APPROVED (revised).

Tally: {A} approved, {D} dismissed, {R} retried


Phase 6 — Dispatch Approved Responses

Secret scan first: Before dispatching any item, scan both the ### Context block AND the ### Draft Response block for secrets — API keys, tokens, passwords, internal hostnames, .env values, connection strings. If found in either block, surface the finding to the human and pause that item’s dispatch — do not redact silently.

For each APPROVE item, route to the correct skill:

Source Dispatch method
SLACK Use slack_send_message with channel_id and thread_ts from the cached metadata (HTML comment in the file). Open the message with the identity disclosure line: _{your identity} on behalf of @{human_slack_handle}:_ then the draft body.
JIRA Invoke /comment-jira with the ticket ID and draft body. The skill handles ADF formatting and identity footer.
CONFLUENCE Invoke /comment-confluence with the page URL and draft body. The skill handles ADF formatting, identity footer, and space visibility check.
GITHUB Use gh api repos/{owner}/{repo}/issues/{number}/comments -f body="{body}" to post a PR or issue comment. Open the body with the identity disclosure line: _Posted by {your identity} on behalf of @{gh_login}._ then the draft body. For PR review comments on specific lines, use gh api repos/{owner}/{repo}/pulls/{number}/comments with appropriate path, line, and commit_id fields.

After dispatch, report outcomes:

Dispatch complete:
  ✓ [1] SLACK #channel — replied in thread
  ✓ [3] JIRA PROJ-456 — comment posted
  ✗ [2] CONFLUENCE "Page title" — FAILED: {error message}
  — [4] DISMISSED
  ~ [5] RETRY → APPROVED (revised) — posted

For any failures, surface the full error and ask the human how to proceed.


Guardrails