10 KiB
gw-triage — Telegram report → Jira issue plugin
Date: 2026-07-03 Status: draft for review
Problem
Bugs and feature requests reported in the company Telegram chat get forgotten. We want a repeatable path from a screenshot + short message to a well-formed, correctly routed Jira issue — usable by all three developers regardless of which codebase the problem belongs to.
Solution overview
A Claude Code plugin distributed from a shared git repo that doubles as its own plugin marketplace. Two skills:
triage— screenshot + message → classified, deduplicated, Italian-language Jira issue created under the right epic, assigned to the right developer, with the screenshot attached.map-repo— run inside any company codebase; generates that repo's triage profile (knowledge file) and guides the developer to commit it back to the plugin repo.
Codebase knowledge lives in the plugin repo itself, so every install can route across all company codebases, not just the one it happens to run in.
Repo structure
gw-triage/
├── .claude-plugin/
│ ├── plugin.json name, version, description
│ └── marketplace.json repo doubles as its own marketplace
├── skills/
│ ├── triage/SKILL.md
│ └── map-repo/SKILL.md
├── knowledge/
│ ├── zeus.md generated during initial build (BE / BO / provisioning)
│ └── README.md profile format spec + template for the other repos
├── config/
│ └── routing.json Jira coordinates + team routing (see below)
├── docs/
│ └── design.md this document
└── README.md install steps, prerequisites, API token setup
Routing configuration (config/routing.json)
The deterministic part of triage. Real values (project key, epic keys, account IDs, cloudId, site URL) are fetched from Jira via the Atlassian MCP during the initial build and committed.
{
"site": "https://<site>.atlassian.net",
"cloudId": "<fetched>",
"projectKey": "<fetched>",
"language": "it",
"areas": {
"app-android": { "epic": "<key>", "assignee": { "name": "Fabrizio", "accountId": "<id>" } },
"app-ios": { "epic": "<key>", "assignee": { "name": "Antonio", "accountId": "<id>" } },
"app-shared": { "epic": "<key>", "assignee": { "name": "Fabrizio", "accountId": "<id>" } },
"backend": { "epic": "<key>", "assignee": { "name": "Giuseppe", "accountId": "<id>" } },
"backoffice": { "epic": "<key>", "assignee": { "name": "Giuseppe", "accountId": "<id>" } },
"infra": { "epic": "<key>", "assignee": { "name": "Giuseppe", "accountId": "<id>" } }
}
}
Routing rules encoded here:
| Signal | Area | Assignee |
|---|---|---|
| Android UI / device visible in report | app-android | Fabrizio |
| iOS UI / device visible in report | app-ios | Antonio |
| Flutter issue, platform not determinable | app-shared | Fabrizio (agreed fallback) |
| API errors, data wrong/missing, payments, chat/SignalR, auth | backend | Giuseppe |
| Blazor admin dashboard | backoffice | Giuseppe |
| TLS, nginx, deploys, environments down | infra | Giuseppe |
Multiple areas may point to the same epic (e.g. one "App" epic for all three app areas) — the schema allows it; the actual mapping is decided when the epics are fetched.
Knowledge profiles (knowledge/*.md)
One per repo, triage-oriented (not an architecture doc), roughly 100–200 lines:
- System identity — name, one-line responsibility, owning developer.
- User-visible surfaces — screens, endpoints, dashboards a reporter might screenshot.
- Error fingerprints — what failures from this system look like in a screenshot: error banner styles, localized error messages (it-it / en-us), HTTP/gRPC status codes, stack trace shapes, UI toolkit appearance.
- Symptom → area heuristics — e.g. "payment stuck on processing → backend (Stripe webhook / SignalR), not app".
- Domain glossary — the Italian terms reporters actually use (annunci, prenotazioni, segnalazioni, host…).
- "Not mine" pointers — symptoms that look like this system but belong elsewhere.
The Zeus profile is generated during the initial build, seeded from docs/context-builder.md plus a fresh exploration pass. The Flutter repo profile(s) are generated later by Fabrizio/Antonio running map-repo.
triage skill flow
- Preflight: verify the Atlassian MCP tools are available and authenticated, and whether
JIRA_EMAIL/JIRA_API_TOKENare set. If the MCP is missing/unauthenticated, stop and print the setup guide steps (in Italian) — the guide effectively appears on first opening for anyone with incomplete setup. If only the token vars are missing, continue but warn that attachments will be skipped (and show the token steps once). - Input: the Telegram message text + screenshot. Preferred: the screenshot dropped into the prompt as a file (drag & drop inserts a path — enables both analysis and attachment upload). Pasted clipboard images work as a degraded mode: classification happens, attachment is skipped with a warning. Message-only reports (no screenshot) are also valid.
- Load
config/routing.jsonand allknowledge/*.mdvia${CLAUDE_PLUGIN_ROOT}. - Classify: type (Bug vs feature → Task), area, platform. If confidence is low, ask one targeted question instead of guessing.
- Duplicate check: JQL search in the project (text ~ keywords, recent first) via the Atlassian MCP. If a likely match exists, show it and offer to comment on it instead of creating a new issue.
- Draft in Italian: concise imperative title; description with contesto, passi di riproduzione (inferred from screenshot where possible), comportamento atteso vs osservato, textual description of the screenshot; issue type; parent epic; assignee.
- Confirm: show the full draft, one yes/adjust checkpoint.
- Create via
createJiraIssue(project, type, summary, description, parent epic, assignee accountId). - Attach screenshot via Jira REST (see below). Skip with an explicit warning if the env vars are missing or the image was pasted rather than dropped.
- Report the issue key + browse URL.
Screenshot attachment (Jira REST)
The Atlassian remote MCP has no attachment upload, so the skill uses the plain REST API:
curl -s -X POST \
-H "X-Atlassian-Token: no-check" \
-u "$JIRA_EMAIL:$JIRA_API_TOKEN" \
-F "file=@<screenshot-path>" \
"<site>/rest/api/3/issue/<KEY>/attachments"
API token setup (goes in README, one-time per developer)
- Open https://id.atlassian.com/manage-profile/security/api-tokens (log in with your Atlassian account).
- Click Create API token, label it
gw-triage, set the expiry, and copy the token immediately — it's shown only once. - Export the credentials in your shell profile:
- bash/zsh:
export JIRA_EMAIL="you@company.com"andexport JIRA_API_TOKEN="<token>" - fish:
set -Ux JIRA_EMAIL you@company.comandset -Ux JIRA_API_TOKEN <token>
- bash/zsh:
- Verify:
curl -s -u "$JIRA_EMAIL:$JIRA_API_TOKEN" <site>/rest/api/3/myselfreturns your profile JSON.
Security note in README: the token acts as you on Jira — never commit it, and revoke it from the same page if leaked. When the token expires, triage keeps working; only attachments are skipped until a new token is exported.
Language policy: everything the team reads is Italian — the README (including these token steps), all interactive output of both skills (questions, drafts, confirmations, warnings, final reports), and the generated Jira issues. Model-facing internals stay in English: SKILL.md instruction text, routing.json, and the knowledge profiles.
map-repo skill flow
- Verify the current directory is a git repo; identify the repo name.
- Spawn an exploration agent to survey: README/docs, project structure, entry points, user-facing surfaces, error handling and localized error messages.
- Write
knowledge/<repo>.mdfollowing the format inknowledge/README.md. - Guide the developer to publish it: clone/pull the plugin repo, add the profile, bump the plugin patch version in
plugin.json, commit and push (direct push — three-person team). Others get it on their next plugin update.
Error handling / degraded modes
- Atlassian MCP missing or unauthenticated → stop with setup instructions (README covers install + auth).
- No knowledge profile matches the symptoms → classify on generic heuristics, state the lower confidence in the draft.
- Attachment env vars missing / pasted image → create the issue anyway, warn that the attachment was skipped and it can be dragged into Jira manually.
- Jira create call fails → surface the raw error; nothing partial to clean up (attachment only runs after successful creation).
Prerequisites (per developer)
The README opens with this sequence (in Italian):
- Install the Atlassian plugin — in Claude Code run
/plugin install atlassian@claude-plugins-official(the official Anthropic directory, available out of the box; alternatively browse with/plugin). - Authenticate with Atlassian — run
/mcp, selectatlassian, and complete the browser OAuth login with your company Atlassian account. Verify: the/mcpentry shows as connected. - Install gw-triage —
/plugin marketplace add <git-url-of-this-repo>then/plugin install gw-triage. - Screenshot attachments (optional but recommended) — export
JIRA_EMAIL/JIRA_API_TOKENas described in the token setup section.
Out of scope / future
- Telegram bot → headless Claude automation (revisit once classification quality is proven).
- Severity/priority inference.
- Attachment upload via MCP, if Atlassian ever exposes it.
Verification plan
- Dry-run
triageon 1–2 real past Telegram reports; check classification, routing, and draft quality. - One live end-to-end run: create a real issue with attachment, verify in Jira.
map-repodry-run on Zeus itself (output compared againstdocs/context-builder.md), later by Fabrizio/Antonio on the Flutter repo.