From 01ca59f82d25ee8c1824d85d9014f89af2cc0857 Mon Sep 17 00:00:00 2001 From: Shahzaib Date: Thu, 9 Jul 2026 15:58:52 -0700 Subject: [PATCH] s360-reporter: auto-fetch prior report via WorkIQ Graph (AB#3683195) Replace the copy/paste-first Step 0b with a Graph-fetch-first flow that pulls last week's S360 Weekly Report from the user's mailbox via the WorkIQ MCP's workiq-fetch passthrough (GET /me/messages). Skip messages within the current cycle and pick the most recent remaining email, then parse via the existing Step 3a rules. VS Code's input box was silently truncating large HTML pastes, which corrupted the new-vs-previously-tracked diff in Step 5 (Cesar's survey feedback). Fallback tier when Graph is unreachable: workiq-fetch -> mcp_workiq_ask_work_iq natural-language query -> sk_user paste as last resort. Step 3a is reframed as parser-only (invoked from Step 0b); Prerequisites and Edge Cases updated with $search/$filter incompatibility note. Fixes AB#3683195 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .github/skills/s360-reporter/SKILL.md | 136 ++++++++++++++++++-------- 1 file changed, 97 insertions(+), 39 deletions(-) diff --git a/.github/skills/s360-reporter/SKILL.md b/.github/skills/s360-reporter/SKILL.md index 198e1d82..82a6660b 100644 --- a/.github/skills/s360-reporter/SKILL.md +++ b/.github/skills/s360-reporter/SKILL.md @@ -16,7 +16,10 @@ HTML email report. - **S360 MCP Server** must be running (configured in `.vscode/mcp.json` as `s360-breeze-mcp`) - **ADO MCP Server** must be running (for PBI creation and lookup) - **M365 User MCP** (`m365-user`) — for dynamic team member discovery via org chart -- **WorkIQ MCP Server** (optional) — used as fallback for pulling last week's email if the user doesn't provide it +- **WorkIQ MCP Server** (`workiq`) — **required for the default flow**. Used in Step 0b to + auto-fetch last week's S360 report from the user's mailbox via a Microsoft Graph + passthrough (`workiq-fetch` on `/me/messages`). If WorkIQ is unavailable, the skill + falls back to a manual paste — see Step 0b for the full fallback tier - Read the **Outlook HTML report prompt** at `{{VSCODE_USER_PROMPTS_FOLDER}}/outlook-html-report.prompt.md` for HTML rendering rules before generating the report @@ -96,31 +99,81 @@ misses these. To capture them, dynamically discover team member aliases from the `Invoke-RestMethod -Uri "https://identitydivision.visualstudio.com/_apis/projects/Engineering/teams/{teamId}/members?api-version=7.1"` - Extract `uniqueName` values, strip `@microsoft.com` to get aliases -### Step 0b: Collect Last Week's Report +### Step 0b: Fetch Last Week's Report (Automatic) -Before fetching S360 data, ask the user if they have last week's S360 report available. -This is the **primary method** for determining "new this week" items, resolved items, -and pre-existing PBI assignments. Use the `ask_user` tool: +Before fetching S360 data, **automatically pull last week's S360 report from the +user's mailbox** via the WorkIQ MCP's Graph passthrough. This replaces the old +copy/paste flow — VS Code's input box silently truncated large HTML pastes, which +corrupted the "new vs. previously tracked" diff downstream. Do **not** prompt the +user for a paste unless every automatic method has failed. + +The parsed result of this step is the **previous report map**: +`title → { pbi, owner, slaState }`. It is consumed by: +- **Step 1e** — resolved items = items in the map but NOT in the current active set +- **Step 3** — existing PBIs = AB# numbers from the map +- **Step 5** — new items = items in the current active set but NOT in the map + +Try the following sources in order and stop at the first success. Do not ask the +user which one to try. + +#### Method 1 (default): WorkIQ Graph fetch + +Call the WorkIQ MCP's Graph passthrough tool (exposed as `workiq-fetch`; the runtime +tool identifier is typically `mcp_workiq_fetch`) with a Microsoft Graph query +against the current user's mailbox: ``` -question: "Do you have last week's S360 report to paste? This helps detect new/resolved items and avoid duplicate PBIs. You can paste the report text, or skip and I'll try to find it automatically." -choices: ["I'll paste it now", "Skip — find it automatically"] +GET /me/messages + ?$search="S360 Weekly Report" + &$select=id,subject,body,sentDateTime,receivedDateTime + &$top=5 ``` -**If the user pastes the report:** -1. Parse the pasted text for: - - **Item titles with owners** — each row in the report table - - **AB# references** — extract numeric ADO work item IDs (e.g., `AB#12345`, `Product Backlog Item 12345`, or `Bug 12345`) - - **ADO work item URLs** — links like `dev.azure.com/.../workitems/12345` - - **SLA states** — Missed SLA, Near SLA, In SLA -2. Build a **previous report map**: title → { pbi, owner, slaState } -3. Store this map for use in: - - **Step 1e** (resolved items = items in last week's map but NOT in current active set) - - **Step 3** (existing PBIs = AB# numbers from the map) - - **Step 5** (new items = items in current active set but NOT in last week's map) - -**If the user skips or doesn't respond:** -Fall back to automatic discovery in Step 3a (WorkIQ → Mail Search → proceed without). +**Important Graph quirks:** +- `$search` **cannot be combined with `$filter`** — do not try to add + `$filter=sentDateTime ge ...`. Do the date/subject narrowing in code after the + results come back. +- `$search` is quoted-string phrase match against the message index, so + `"S360 Weekly Report"` matches the standard subject fragment used by the team. +- `$select` keeps the payload small — the message body is still large HTML, so + omit any fields you don't need. +- Results are typically returned in relevance order, not date order — sort by + `sentDateTime` in code. + +After the call, in code: +1. Drop any message whose `sentDateTime` is within the current cycle (heuristic: + less than 3 days old — the current run is "this week's" report, so anything + that recent is either the run in progress or a same-cycle draft). +2. From the remaining messages, sort by `sentDateTime` descending and take the + most recent one — that is last week's report. +3. Extract `body.content` (HTML string) and parse it using the **prior-report + parser** (rules in Step 3a below). This gives you the previous report map. +4. Log a short one-line summary to the user so the pick is auditable, e.g.: + `Fetched prior report: "S360 Weekly Report — Jul 2" (sent 2026-07-02T18:14Z) — parsed 18 items.` + +#### Method 2 (fallback): WorkIQ natural-language query + +If Method 1 errors (tool missing, Graph 401/403, empty result), fall back to +`mcp_workiq_ask_work_iq`: + +``` +question: "Find the most recent email from the last 14 days with subject containing 'S360 Weekly Report' sent to androididentity@microsoft.com. Return the full email body content (HTML) including any AB# work item references." +``` + +Feed the returned body through the same prior-report parser (Step 3a). + +#### Method 3 (last resort): Manual paste + +Only if Methods 1 and 2 both fail, prompt the user with `ask_user`: + +``` +question: "I couldn't fetch last week's S360 report automatically (WorkIQ/Graph unavailable). If you have it, paste the report body here — otherwise skip and I'll proceed without prior-week data. Note: VS Code's input box can truncate large pastes; use a chat-window paste if you can, and warn me if the paste looks cut off." +choices: ["I'll paste it now", "Skip — proceed without prior data"] +``` + +If the user pastes, parse via Step 3a. If they skip or all methods fail, proceed +without a previous report map — Steps 3 and 5 already handle this case (see +"Fallback" note in the Resolved section). ### Step 1: Fetch S360 Data @@ -242,14 +295,14 @@ To populate the "Resolved Since Last Week" section, compare the current S360 ite against last week's report: 1. **Pull last week's S360 items** via one of these sources (in priority order): - a. **User-provided report** (from Step 0b) — if the user pasted last week's report, - use the parsed previous report map. This is the most reliable source. + a. **Fetched prior report** (from Step 0b) — the previous report map built by + the automatic Graph fetch (or one of its fallbacks) is the most reliable + source. Use it if present. b. Call `mcp_s360-breeze-m_search_resolved_s360_kpi_action_items` with the same `targetIds` and `assignedTo` used in 1a/1b. Cross-reference results against the - user-provided report if available — only include items that appear in BOTH sources. - c. If no user-provided report and the resolved search tool is unavailable, parse - last week's email (from Step 3a) and extract the item titles + AB# numbers. - d. If none of the above are available, skip this step. + Step 0b map if available — only include items that appear in BOTH sources. + c. If Step 0b produced no map and the resolved search tool is unavailable, skip + this step. 2. **Identify resolved items**: Items that appeared in last week's report but are NOT in the current active set (from 1c) are considered resolved. @@ -444,19 +497,15 @@ defects (especially security/compliance items), and the skill must match those just like it matches PBIs. **Do not restrict any lookup to `Product Backlog Item` only.** Every search and merge step below applies equally to both work-item types. -#### 3a: Pull last week's S360 email - -**Method 1: User-provided report** (from Step 0b) — If the user already pasted last -week's report, use the parsed map directly. This is the most reliable source and avoids -issues with Purview-encrypted emails or WorkIQ failures. **Skip Methods 2–3 entirely.** +#### 3a: Prior-report parser (invoked from Step 0b) -**Method 2: WorkIQ** (fallback if user skipped Step 0b) — Call `mcp_workiq_ask_work_iq`: -``` -question: "Find the most recent email from the last 7 days with subject containing 'S360 Weekly Report' sent to androididentity@microsoft.com. Return the full email body content including any AB# work item references." -``` +Step 0b is now responsible for **acquiring** the prior report body (via the Graph +fetch, WorkIQ NL fallback, or paste fallback). This subsection defines the +**parser** that Step 0b calls to turn that body into the previous report map. +Do not re-fetch here. -**Method 3: Ask user** (fallback if WorkIQ errors) — Use `ask_user` to ask the user -to paste last week's report content. +If, for some reason, Step 0b was skipped (e.g., an ad-hoc rerun mid-workflow), +run Step 0b now before continuing. Parse the email/report body for: - **AB# references** (e.g., `AB#12345`) — extract the number and the S360 item title nearby. @@ -469,7 +518,8 @@ Parse the email/report body for: Build a map of **S360 item title → AB# number** from the previous report. These are known-good PBI assignments from last week. -If all methods fail, skip this step and continue with Step 3b. Do not fail the workflow. +If Step 0b returned no map (all three fetch methods failed), skip this step and +continue with Step 3b. Do not fail the workflow. #### 3b: Search ADO for existing S360 work items (tag/title search) @@ -827,6 +877,14 @@ Within same SLA state, sort by `CurrentDueDate` ascending (earliest due first). Add a callout in the report noting that on-call items may be missing. - **S360 MCP auth failure**:Instruct user to restart MCP server via Command Palette → `MCP: Restart Server` → `s360-breeze-mcp` +- **WorkIQ Graph fetch (`workiq-fetch`) fails**: Fall through Step 0b's tier: + Graph fetch → `ask_work_iq` NL query → manual paste. Only if all three fail + should you proceed without a previous report map. +- **`$search` + `$filter` collision**: Microsoft Graph rejects requests that + combine `$search` with `$filter`. Never add a `$filter=sentDateTime ge …` + clause to the Step 0b query — do all date narrowing in code after the results + come back. Cycle-age skip (drop messages < 3 days old) is a code-side heuristic, + not a Graph query parameter. - **WorkIQ unavailable or no email found**: Skip Step 3a entirely; rely on S360 API field and ADO search only. Do not fail the workflow. - **WorkIQ returns emails older than 7 days**: The query is scoped to "last 7 days" to