| name | mcs-lab-issue-filer |
| description | Convert a lab's findings.json into a GitHub issue body and file it against the active instance's lab repo (microsoft/mcs-labs by default). Checks for an existing open issue with matching labels and comments on it instead of duplicating. Invoked by mcs-lab-auditor after a lab completes with at least one judge-recorded finding above the confidence threshold. Should NOT be invoked directly by the user — it expects a fully-populated run directory and assumes the orchestrator has already filtered out cannot_verify and below-threshold findings.
|
| allowed-tools | ["Read","Write","Edit","Bash(gh issue list:*)","Bash(gh issue create:*)","Bash(gh issue comment:*)","Bash(gh issue view:*)"] |
mcs-lab-issue-filer (sub-skill)
Take the findings for one lab from one audit run and either (a) file a new GitHub issue or (b) comment on an existing open issue for the same lab. Then record the result in manifest.yml and audit-history.yml.
Inputs (assumed present)
runs/<run-id>/labs/<slug>/findings.json — list of finding records (see ../mcs-lab-auditor/references/finding-schema.md).
runs/<run-id>/manifest.yml — for the run-id, account user-id, plugin-version, etc.
<plugin>/config/judge-config.yml — for confidence thresholds, label config, dedupe behavior.
C:\Users\dewainr\mcs-labs\_labs\<slug>.md — for the lab title (front-matter).
Outputs
runs/<run-id>/labs/<slug>/issue-body.md — the rendered issue body (kept on disk for audit + retry).
- New or updated issue at
github.com/{repo}/issues/<n>.
- Updated
manifest.yml.labs[<slug>]: status: issue_filed, issue_url, issue_action: created|commented.
- Updated
runtime/audit-history.yml: appended entry per audit-log-schema.md.
Procedure
1. Load inputs
Read findings.json, the lab's front-matter for the title, the run manifest, and the judge config.
Filter findings:
- Drop any with
outcome: pass | transient | cannot_verify.
- Drop any with
confidence < judge-config.yml.confidence.min_to_include_in_issue (typically 0.5).
- If
flags.critique_pass_survived == false AND the critique was enabled, drop.
If the filtered list is empty, abort cleanly — the orchestrator should have logged this as a clean pass instead of invoking us. Return without creating the issue.
2. Group findings by severity and ordinal
Order findings by:
- Severity descending (
high → medium → low).
- Within severity, by step_id (lexical — preserves the lab's natural flow).
Compute summary counts: {high: N, medium: N, low: N} and the highest severity present.
3. Render the issue body
Write runs/<run-id>/labs/<slug>/issue-body.md using this template (variables in {braces}):
## Lab audit: `{slug}` — {lab_title}
**Audit run:** `{run_id}` · **Date:** {iso_timestamp}
**Test account tenant:** {tenant_hint} (workshop-issued)
**Findings:** {N} total — {high}H / {medium}M / {low}L
> Generated by `mcs-lab-auditor` v{plugin_version}. Detailed transcript and screenshots are stored locally at `~/.claude/plugins/mcs-lab-auditor/runtime/runs/{run_id}/labs/{slug}/`.
---
{for each finding}
### Finding {n} — `severity:{level}`{ if 0.5 <= confidence < 0.7: " (low confidence — please verify)" }
<!-- finding:fp:{fingerprint} -->
**Location:** `{scene_heading}` → step {step_ordinal_within_scene} of `_labs/{slug}.md`
**Instruction in lab (verbatim):**
> {instruction_excerpt}
**Expected (per lab):** {expected}
**Observed in UI:** {actual}
**Suggested correction:**
```diff
- {suggested_correction.original_text}
+ {suggested_correction.proposed_text}
Rationale: {suggested_correction.rationale}
Evidence: screenshots/{step_id}.png (in local run artifacts)
{ if console_errors or failed_network: }
Diagnostics:
{ console_errors as "- console: {msg}" lines }
{ failed_network as "- network: {status} {url}" lines }
{end for}
{ if any finding has flags.parser_warning: }
Static analysis findings (from the parser)
{ for each parser_warning finding: }
- Line {line_number} ({severity}): {expected} → suggests
{suggested_correction.proposed_text}.
{ end if }
How to reproduce locally
Re-audit just this lab against a fresh workshop account:
/audit-lab {slug}
The plugin is at ~/.claude/plugins/mcs-lab-auditor/. See its README for setup details.
```
4. Look for an existing open issue (de-duplication — UNCONDITIONAL)
This step is mandatory on every run. You may not skip it, and you may not file a new issue when a matching open issue exists. The user's standing rule: never recreate an issue that's already open. If the orchestrator's existing-state probe (runs/<run-id>/existing-state.yml) already resolved an open_issue.number for this slug, use that number directly and jump to step 6.
Otherwise, run two queries and union the results — issues filed before the lab:<slug> label existed will only match the loose query:
Strict (per-slug label present):
gh issue list \
--repo {repo} \
--state open \
--label "lab-audit" \
--label "lab:{slug}" \
--json number,title,url,createdAt \
--limit 10
Loose (slug appears in title, only lab-audit label required):
gh issue list \
--repo {repo} \
--state open \
--label "lab-audit" \
--search "{slug} in:title" \
--json number,title,url,createdAt \
--limit 10
If either query returns at least one result, treat the most recent (by createdAt) as the canonical open issue. Record the number and URL in manifest.yml.labs[<slug>].existing_issue and jump to step 6 (comment).
judge-config.yml.issues.on_duplicate is honored for one purpose only: choosing between "comment" (default — append findings) and "skip" (record issue_action: skipped_duplicate, write nothing). The legacy "create_anyway" value is deprecated and ignored — it is treated as "comment" with a warning logged to the transcript. Filing a new duplicate issue is never an option.
If the existing-state probe has not been run (e.g., issue-filer was invoked outside the orchestrator), perform the two queries above before deciding.
5. Create a new issue
Build the title:
Lab audit: {slug} — {N} finding(s) ({high}H/{medium}M/{low}L)
Build the labels:
labels = ["lab-audit", "lab:{slug}", "severity:{highest_severity}"]
if slug in judge-config.yml.non_deterministic_lab_slugs:
labels.append("non-deterministic-lab")
File the issue:
gh issue create \
--repo {repo} \
--title "{title}" \
--body-file "runs/{run_id}/labs/{slug}/issue-body.md" \
--label "{labels comma-joined}"
Capture stdout — gh prints the new issue URL on the last line. Save as issue_url. Set issue_action: created.
6. Comment on an existing issue (if step 4 found one)
6a. Finding-level fingerprint dedup
Before posting, fingerprint each finding in findings.json and the existing issue's body + every existing comment, then drop any finding whose fingerprint already appears.
Fingerprint algorithm (SHA-256 hex, first 12 chars):
fingerprint = sha256(
normalize(step_ref) + "|" +
outcome + "|" +
normalize(suggested_correction.original_text or instruction_excerpt)
)[:12]
normalize = lowercase, collapse whitespace, strip leading/trailing punctuation.
To extract existing fingerprints, fetch:
gh issue view <issue-number> --repo {repo} --json body,comments
Parse the body and every comments[].body. Each rendered finding includes a hidden HTML marker <!-- finding:fp:<12-char-hex> --> placed immediately after the finding heading (see step 3 template update). Collect every fingerprint found this way.
A finding is a duplicate if its computed fingerprint matches any extracted fingerprint. Drop duplicates from the comment payload entirely.
If after dedup the comment payload is empty (every finding already covered), do not post a comment. Record:
issue_action: skipped_no_new_findings
issue_url: <existing-issue-url>
and stop. The clean-pass behavior still applies — append an entry to runtime/audit-history.yml noting no new findings.
6b. Render the delta comment
Re-render issue-body.md using only the surviving (non-duplicate) findings. Prepend a one-line header:
> **Re-audit comment** — run `{run_id}` ({iso_timestamp}). {N} new finding(s) since the last update; duplicates from earlier comments suppressed.
Every finding heading must include the fingerprint marker so the next run can dedup against it:
### Finding {n} — `severity:{level}`
<!-- finding:fp:{fingerprint} -->
6c. Post the comment
gh issue comment <issue-number> \
--repo {repo} \
--body-file "runs/{run_id}/labs/{slug}/issue-body.md"
Capture the comment URL. Save as issue_url. Set issue_action: commented.
6d. Cross-link any open fix PR
If the orchestrator's existing-state probe resolved an open_pr for this slug, append (as a separate comment for clarity):
> Open fix PR for this lab: #{pr_number} (branch `{branch}`). This run's fixes (markdown corrections and/or refreshed screenshots) have been appended to that open PR rather than opening a duplicate — see the PR commits.
6e. Label hygiene (best-effort)
After commenting, also re-apply any missing labels with:
gh issue edit <issue-number> --repo {repo} --add-label "lab:{slug}" --add-label "severity:{highest}"
This backfills the per-slug label on issues that pre-date the labeling convention, so future strict-query dedup works without needing the loose query.
7. Record the outcome
Update runs/<run-id>/manifest.yml:
labs:
{slug}:
status: issue_filed
issue_url: <url>
issue_action: created | commented | skipped_duplicate
finished_at: <iso-timestamp>
Append to runtime/audit-history.yml per audit-log-schema.md:
- run_id: <run_id>
lab_slug: {slug}
...
status: issue_filed
issue_url: <url>
issue_action: <action>
findings_summary:
high: N
medium: N
low: N
cannot_verify: N
8. Failure handling
gh issue list returns an auth error → return status error, reason: gh_unavailable to the orchestrator. The rendered issue-body.md is still on disk for the user to file manually.
gh issue create returns rate-limited (HTTP 403) → wait 30s and retry once. If still failing, same fallback as above.
- Network failure → same fallback.
Do NOT swallow these errors silently. The user must be told that the issue body is on disk but wasn't filed.
Anti-patterns
- Do not modify findings.json in this skill. It's an input only.
- Do not call the LLM judge from this skill. Findings are already final by the time you get them.
- Do not include
cannot_verify findings in the issue body. They mean "the user's setup couldn't run this step" — not a lab bug.
- Do not include screenshots inline with
 — gh doesn't upload local files. Reference them by path; the maintainer can DM for the artifact if needed.
- Do not edit
_labs/<slug>.md. Suggested corrections are described, not applied.
- Never file a duplicate open issue for the same lab. If any open issue with the
lab-audit label matches the slug (via per-slug label OR title substring), you must comment on it — not create a new one. The on_duplicate: "create_anyway" config value is deprecated and treated as "comment".
- Never re-post a finding whose fingerprint is already present in the existing issue body or any of its comments. If every finding is a duplicate, skip the comment entirely and record
issue_action: skipped_no_new_findings.
- Never strip the
<!-- finding:fp:... --> markers from rendered issue bodies or comments. They are how future runs dedup against past output.