issue — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited issue (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
File a new issue whose job is to convey why the work is needed and how the author intends to approach it — written for a reader who has never seen this part of the system. This is the authoring counterpart to the backlog skill: issue drafts and writes one issue (a new one, or an update to a known one), while backlog surveys what is already filed to find the next thing to work on. An issue describes work to be done, so unlike commit and prepare-review there is no diff to read from: the raw material is the author's intent, gathered up front.
Don't narrate your work. Every step below is an operating instruction, not a script to read aloud — follow the execute-quietly discipline: ${CLAUDE_PLUGIN_ROOT}/guides/execute-quietly.md. For this skill, the only things worth surfacing are a question you need answered, the drafted issue with its options, and the final URL.
Issue = a GitHub issue or a GitLab issue. Pick the forge tool by the origin remote.
%%{ init: { 'look': 'handDrawn' } }%%
flowchart TD
Start(["/issue"]) --> Mode{Issue ref given?}
subgraph "Step 1: Resolve the issue"
Mode -->|Yes| Update["Update: fetch current body as baseline"]
Mode -->|No| Create["Create: new issue"]
end
subgraph "Step 2: Gather intent"
Update --> Why["Ask the WHY + consumer + acceptance"]
Create --> Why
end
subgraph "Step 3: Guard against duplicates"
Why --> FromCreate{Creating a new issue?}
FromCreate -->|Yes, unsure| Check["Offer /backlog to check for a match"]
Check --> Match{Already tracked?}
Match -->|Yes| Reuse["Switch to update: fetch its body as baseline"]
end
subgraph "Step 4: Draft"
Tmpl["Honor forge template + anchor.* config"]
Tmpl --> Draft["Draft title + body"]
end
FromCreate -->|No, or new| Tmpl
Match -->|No, file new| Tmpl
Reuse --> Tmpl
subgraph "Step 5: Classify"
Draft --> Meta["Read the repo's labels + open milestones"]
Meta --> Fit{One clear fit?}
Fit -->|Several, or none obvious| Choose["Ask the author"]
end
subgraph "Step 6: Output"
Fit -->|Yes| Out{Disposition?}
Choose --> Out
Out -->|Write| Forge(["Create / update issue"])
Out -->|Copy| CopyOnly(["Print for paste"])
Out -->|Edit| Revise["Revise (review tool or chat)"] --> Draft
endBy default this operates on the repo backing the working directory — pick the forge from its origin remote (gh for GitHub, glab for GitLab). But an issue is often filed against a different repo than the one you're sitting in ("file this against payments-api", "open an issue in customer-svc"). Don't guess from cwd or improvise a -R from a half-remembered slug — resolve the name:
bash "${CLAUDE_PLUGIN_ROOT}/scripts/resolve-target.sh" <name>Act on TARGET_VIA:
TARGET_NOTE separates "no repo by that name" from "no authenticated forge to ask"). Fall back to the cwd origin. If the user clearly meant a repo that didn't resolve, say so rather than silently filing against the cwd repo.TARGET_CANDIDATES holds the matches as [{key,url,local}]. Present them with AskUserQuestion and let the user pick; proceed with the chosen entry.TARGET_FORGE picks the CLI (gh / glab).-R <TARGET_PROJECT> to the gh issue … calls.glab api projects/:fullpath/…, but :fullpath resolves from the cwd git dir — substitute the URL-encoded TARGET_PROJECT for :fullpath and add --hostname <TARGET_HOST> (required for self-hosted, harmless elsewhere).TARGET_LOCAL — the checkout, set when the target is the repo you're standing in. Only the template step needs it; create/update are pure-remote and work without it (the common case for a repo you named from elsewhere).Pick the forge per Target repo above (gh for GitHub, glab for GitLab).
$(mktemp -u /tmp/issue-current.XXXXXX).md); Step 6 diffs the draft against it: # GitHub
gh issue view <num> --json body --jq '.body' > <current-path> # GitLab
glab issue view <iid> --output json | jq -r '.description' > <current-path>There is no diff to mine, so the author's answers are the issue. Ask for what's missing — don't draft around a gap:
Wait for answers before drafting. If the only open item is the WHY, ask:
What problem does this solve, and why does it matter? A sentence or two is enough — and who's the primary consumer of the change?
This step runs only on the create path — skip it when updating a known issue. A duplicate issue splits the discussion, so before drafting a brand-new issue, make sure one doesn't already cover this need.
Finding and picking issues is the backlog skill's job — don't re-implement a forge search here. If it's unclear whether this need is already tracked, say so and offer to run backlog (scoped to a keyword or two distilled from the intent — the subject of the work, not the WHY prose) to survey open and closed issues first. If the user already knows it's new, or a quick look turns up nothing that genuinely overlaps, continue to Step 4 without further comment.
If it turns out the need is already tracked, this is an update, not a new issue: take that issue's number and switch to the update path — fetch its current body as the baseline (the Step 1 fetch), then draft against it.
Before drafting, check whether the project ships an issue template. This reads the repo's files, so it needs a local checkout — look under TARGET_LOCAL when a target resolved one (ls <TARGET_LOCAL>/.gitlab/issue_templates/*.md). When the target is remote-only (TARGET_LOCAL empty), skip template detection and note that a project template, if the repo has one, wasn't applied — don't block the issue on a checkout you don't have.
.gitlab/issue_templates/*.md (respect the configured default if more than one).github/ISSUE_TEMPLATE/*.md, or the legacy .github/ISSUE_TEMPLATE.md. A .yml issue form is a structured format — don't compose prose into it; surface it and let the author fill it in the web UI.If a template exists, it's the team's required scaffolding — compose into it, don't replace it. Fill the sections it defines, preserve its checklists and headings verbatim, and strip any "delete before publishing" instruction block after following its guidance. On a structure conflict the team template wins. The composition rules live in the "Honoring a project's forge template" section of ${CLAUDE_PLUGIN_ROOT}/templates/issue-description.md.
anchor.* configRead the project + global anchor.* keys once:
git config --get-regexp '^anchor\.' 2>/dev/null--get-regexp lowercases the section and the setting (anchor.cr.templateRepo comes back as anchor.cr.templaterepo) and leaves the qualifier as written; match the first two case-insensitively and the qualifier exactly. Apply the keys relevant to an issue; absent keys keep anchor's defaults — never invent a value:
<base-uri><id> from a bare id. No mention, no link.anchor's four verbosity defaults, which descend as the audience widens (issue 75 → commit 50 → CR 25 → release 10). An issue's audience is the few people who'll do the work, and background that would pad a release note saves them a conversation here. It is not a word budget — nothing is counted or truncated. Clamp an out-of-range or non-integer value into the 1–100 band and say so once rather than failing the draft.It abbreviates sections; it never removes one. Which sections an issue has is the template's call — anchor's own shape, or the team's when the project ships one — and a section that earns its place is present at every setting. Work down this order and stop where the draft balances where the setting asks: callouts and asides → the Proposed approach's explanation, down to its load-bearing decisions → Considerations, down to one sentence per concern → Context's second paragraph, then the first down to its why sentence. Acceptance criteria are never abbreviated at any setting: they state what done means, so they're the issue's floor the way deep links are a CR description's. A low setting buys fewer words, not louder ones.
anchor.reviewBudgetMins does not apply to issues. See ${CLAUDE_PLUGIN_ROOT}/guides/configuring.md for the full key set.
Draft a concise imperative title (under 72 characters), then the body following the section template in ${CLAUDE_PLUGIN_ROOT}/templates/issue-description.md: Context, Proposed approach, Acceptance criteria, and Considerations (optional). The template owns the shape; the discipline below owns the technique.
prepare-review uses. Establish the system/business context in a sentence or two before the detail.> **Term?** …), sparingly and only where a newcomer would be lost.anchor's mermaid conventions (hand-drawn look, no \n/<br> in labels).${CLAUDE_PLUGIN_ROOT}/guides/loaded-framing.md), no drift artifacts, no leaked deliberation, nothing the reader can already see.${CLAUDE_PLUGIN_ROOT}/guides/markdown-gotchas.md lists the traps (character escaping, nested fences, mermaid, <details>, tables in lists).An issue lands in someone's triage queue, so its metadata is part of filing it. Read what the project actually defines rather than naming a label from memory — an invented name is how a repo ends up with bugfix sitting next to bug. The listing calls for both forges are in the bundled forge cookbook (${CLAUDE_PLUGIN_ROOT}/guides/forge-cookbook.md), section "Labels and milestones".
Both listings are pure-remote, so a resolved target needs no checkout — retarget per Target repo: -R <TARGET_PROJECT> on gh label list and TARGET_PROJECT substituted for {owner}/{repo} in the gh api path; -R <TARGET_URL> on glab label list, --project <TARGET_PROJECT> on glab milestone list, --hostname <TARGET_HOST> on both.
Labels. The descriptions the repo ships on its labels are the triage taxonomy — match the issue against them and apply every label that plainly fits. Two cases go to the user with AskUserQuestion rather than being decided for them: several labels are plausible and choosing between them is a judgment about the work (bug vs enhancement for behavior someone considers wrong), or nothing in the set fits an issue a reader would expect to be labelled. No label is a legitimate answer to either.
Milestone. Only the open ones (GitLab: active) are candidates. Attach one where exactly one plausibly fits — the release the work has to ship in, or the milestone whose theme this work is part of. Where two or more fit, ask; where the repo has no open milestone, or none relates to this work, attach none and don't raise it.
On the update path, add only. Read what the issue already carries (gh issue view <num> --json labels,milestone / glab issue view <iid> --output json) and treat it as settled: propose a label the issue is missing, and a milestone only where it has none. Replacing or removing what someone already triaged is something the user asks for.
Write the drafted body to a temp file ($(mktemp -u /tmp/issue-draft.XXXXXX).md) — the literal /tmp is what a caller's Edit(//tmp/**) grant reaches (${CLAUDE_PLUGIN_ROOT}/guides/temp-paths.md).
Present the change — in your own message. Running a command does not show the user anything: a Bash tool's output goes to you, and the terminal collapses it to a +80 lines stub they'd have to expand. Asking them to approve off the back of that is asking them to approve blind. So whatever you present, it goes in the reply as text.
When updating an existing issue, diff the draft against the baseline captured in Step 1 and paste that diff into a fenced diff block in your message:
git --no-pager diff --no-index <current-path> <draft-path>When creating a new issue, display the full body in a fenced code block.
Either way, head the presentation with a line naming the write and linking where it lands, then the title, then the Step 5 metadata on one line. Emit them as markdown outside the fence, so the link is clickable. The first line takes the create form or the update form:
New [`<project>` issue](<issue list url>)
Update [`<project>` #<number>](<issue url>)
Title: <drafted title>
Labels: <labels> · Milestone: <milestone title>Say none on either half of the metadata line where Step 5 settled on none; it rides the one disposition question with the body rather than becoming a second gate.
Where the link points:
TARGET_URL where a target resolved, else the web URL of the cwd repo's origin, plus /issues on GitHub or /-/issues on GitLab.url (GitHub) / web_url (GitLab).Then ask the user how to proceed with the AskUserQuestion tool. Use header Disposition and these options (default first):
<draft-path>. On a 401/403 or similar auth failure, surface it and ask the user to refresh credentials — don't silently fall back to copy-only (per the fail-fast-on-auth rule).anchor assigns new issues to you, and applies the labels and milestone from Step 5 in the same write. The canonical invocations — including the glab api-then-glab issue update two-step GitLab needs for a file-sourced body, and the update-from-file forms — live in the bundled forge cookbook (${CLAUDE_PLUGIN_ROOT}/guides/forge-cookbook.md), sections "Issue create", "Issue description update from a file", and "Labels and milestones".
When a target resolved (see Target repo), retarget these off the cwd repo: add -R <TARGET_PROJECT> to the gh issue calls; on GitLab substitute the URL-encoded TARGET_PROJECT for :fullpath and add --hostname <TARGET_HOST> on the glab api calls, and -R <TARGET_URL> on glab issue update.
Repeat --label per label; drop a metadata flag entirely where Step 5 settled on none.
# GitHub — create
gh issue create --title "<title>" --body-file <draft-path> --assignee @me \
--label "<label>" --milestone "<milestone title>"
# GitHub — update (edit has no --label; --add-label adds without replacing)
gh issue edit <num> --body-file <draft-path> \
--add-label "<label>" --milestone "<milestone title>"# GitLab — create (API form so the body can come from a file), then the
# assignee and metadata in one porcelain follow-up
glab api -X POST projects/:fullpath/issues -F title="<title>" -F "description=@<draft-path>"
glab issue update <iid> --assignee <username> --label "<a,b>" --milestone "<milestone title>"
# GitLab — update
glab api -X PUT projects/:fullpath/issues/<iid> -F "description=@<draft-path>"
glab issue update <iid> --label "<a,b>" --milestone "<milestone title>"After the issue lands, print its URL, and announce it so a sibling that resolves its own work from tracker URLs can see the new issue without scraping one out of your prose:
bash "${CLAUDE_PLUGIN_ROOT}/scripts/announce.sh" issue.created \
"uri=<issue url>" "title=<title>"On a create only. Adding to an issue that already existed announces nothing: the artifact a subscriber records already exists, and what this run changed is in your report. The publisher exits 0 on every path, so this can never turn an issue that landed into a tool call that failed.
The review is the preferred edit surface but optional — it runs when a review tool is available. Which one follows the subject: filing a new issue leaves <current-path> empty, so the body is all new and edit mode takes it — it opens in the user's editor and whatever they save is the body. Updating an existing issue has a current body to diff against, so revdiff takes it and the user comments where you fold the comments in. A configured tool, or an editor with nowhere to open, overrides that.
Run it as the review loop in ${CLAUDE_PLUGIN_ROOT}/guides/running-a-review.md describes — probe, background launch, manifest, chat feedback while it's open, reading the verdict — opening the current body vs. the draft (when updating) or the draft alone:
bash "${CLAUDE_PLUGIN_ROOT}/scripts/review-diff.sh" --skill issue --probe \
--files <current-path> <draft-path>bash "${CLAUDE_PLUGIN_ROOT}/scripts/review-diff.sh" --skill issue --mode <REVIEW_MODE> --files \
<current-path> <draft-path> \
--title 'Issue body — proposed edits' \
--detail repo=<repo>The manifest names the repo, the issue (its number and title when updating one), the tool from the probe, and the sections the draft holds. After the table, the next thing you say is the verdict.
What each verdict leads to here: approved files the issue — the editedFields text with target: "issue-body" where edit mode returned one, the draft otherwise; changes-requested folds the comments in and re-presents; incomplete, no-verdict, and a missing verdict line file nothing and take the ladder in ${CLAUDE_PLUGIN_ROOT}/guides/review-fallback.md with the drafted body as the artifact.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.