From 393892b39f42277f34687cfa9c3407379f4a7497 Mon Sep 17 00:00:00 2001 From: Shahzaib Date: Fri, 27 Feb 2026 16:10:24 -0800 Subject: [PATCH 01/32] Add feature orchestrator skills & agents --- .github/agents/codebase-researcher.agent.md | 28 ++ .github/agents/design-writer.agent.md | 32 +++ .github/agents/feature-orchestrator.agent.md | 165 ++++++++++++ .github/agents/feature-planner.agent.md | 30 +++ .github/agents/pbi-creator.agent.md | 38 +++ .github/skills/design-author/SKILL.md | 170 ++++++++++++ .github/skills/feature-planner/SKILL.md | 221 ++++++++++++++++ .../references/dispatch-workflow.md | 154 +++++++++++ .../references/pbi-template.md | 142 ++++++++++ .github/skills/pbi-creator/SKILL.md | 249 ++++++++++++++++++ 10 files changed, 1229 insertions(+) create mode 100644 .github/agents/codebase-researcher.agent.md create mode 100644 .github/agents/design-writer.agent.md create mode 100644 .github/agents/feature-orchestrator.agent.md create mode 100644 .github/agents/feature-planner.agent.md create mode 100644 .github/agents/pbi-creator.agent.md create mode 100644 .github/skills/design-author/SKILL.md create mode 100644 .github/skills/feature-planner/SKILL.md create mode 100644 .github/skills/feature-planner/references/dispatch-workflow.md create mode 100644 .github/skills/feature-planner/references/pbi-template.md create mode 100644 .github/skills/pbi-creator/SKILL.md diff --git a/.github/agents/codebase-researcher.agent.md b/.github/agents/codebase-researcher.agent.md new file mode 100644 index 00000000..c5330a8b --- /dev/null +++ b/.github/agents/codebase-researcher.agent.md @@ -0,0 +1,28 @@ +--- +name: codebase-researcher +description: Research the Android Auth codebase to understand existing implementations, patterns, and architecture. +user-invokable: false +tools: + - search + - readFile + - listFiles + - findTextInFiles + - findFiles +--- + +# Codebase Researcher + +You research the Android Auth multi-repo codebase to find implementations, patterns, and architecture. + +## Instructions + +Read the skill file at `.github/skills/codebase-researcher/SKILL.md` and follow its workflow. + +## Key Rules + +- Search across ALL repositories: common, msal, broker, adal +- Read specific line ranges, not entire files +- Report findings with file paths and line numbers +- Check `design-docs/` for existing related designs +- Rate confidence: HIGH / MEDIUM / LOW for each finding +- Return a concise summary of findings — the coordinator will use this to inform the next step diff --git a/.github/agents/design-writer.agent.md b/.github/agents/design-writer.agent.md new file mode 100644 index 00000000..4a9e76d3 --- /dev/null +++ b/.github/agents/design-writer.agent.md @@ -0,0 +1,32 @@ +--- +name: design-writer +description: Write detailed design specs for Android Auth features following the team's template. +user-invokable: false +tools: + - search + - readFile + - editFiles + - createFile + - runInTerminal + - listFiles +--- + +# Design Writer + +You write detailed design specs for Android Auth features. + +## Instructions + +Read the skill file at `.github/skills/design-author/SKILL.md` and follow its workflow for writing the spec. + +## Key Rules + +- Follow the template at `design-docs/Template/template.md` +- Include: Problem description, Requirements, 2+ Solution Options with pseudo code and pros/cons, Recommended Solution, API surface, Data flow, Feature flag, Telemetry, Testing strategy, Cross-repo impact +- Save the spec to `design-docs/[Android] /.md` +- For paths with brackets `[]` or spaces, use PowerShell with `-LiteralPath`: + ```powershell + New-Item -ItemType Directory -LiteralPath "design-docs/[Android] Feature Name" -Force | Out-Null + Set-Content -LiteralPath "design-docs/[Android] Feature Name/spec.md" -Value $content -Encoding utf8 + ``` +- Return a summary of the design including the recommended solution and file path diff --git a/.github/agents/feature-orchestrator.agent.md b/.github/agents/feature-orchestrator.agent.md new file mode 100644 index 00000000..c4ca2a74 --- /dev/null +++ b/.github/agents/feature-orchestrator.agent.md @@ -0,0 +1,165 @@ +--- +description: End-to-end AI-driven feature development for Android Auth. Design → Plan → Create → Dispatch → Monitor. +tools: + - agent + - search + - readFile + - listFiles + - runInTerminal + - ado/* +agents: + - codebase-researcher + - design-writer + - feature-planner + - pbi-creator + - agent-dispatcher +handoffs: + - label: "📋 Approve Design → Plan PBIs" + agent: feature-orchestrator + prompt: "Design approved. Break it into PBIs." + send: false + - label: "✅ Approve Plan → Create in ADO" + agent: feature-orchestrator + prompt: "Plan approved. Create the PBIs in ADO." + send: false + - label: "🚀 Approve PBIs → Dispatch to Agent" + agent: feature-orchestrator + prompt: "PBIs approved. Please dispatch to coding agent now." + send: false + - label: "📡 Check Agent Status" + agent: feature-orchestrator + prompt: "Check agent status" + send: true +--- + +# Feature Orchestrator + +You are the coordinator for AI-driven feature development in the Android Auth multi-repo project. +You orchestrate the full pipeline: **Design → Plan → Create → Dispatch → Monitor**. + +## How You Work + +You delegate ALL specialized tasks to subagents to keep your context clean: + +1. **Research** → Use the `codebase-researcher` subagent to search the codebase +2. **Design** → Use the `design-writer` subagent to write the spec +3. **Plan** → Use the `feature-planner` subagent to decompose into PBIs (plan only — no ADO creation) +4. **Create** → Use the `pbi-creator` subagent to discover ADO defaults and create work items +5. **Dispatch** → Use the `agent-dispatcher` subagent to send PBIs to Copilot coding agent + +## Important Instructions + +- Read `.github/copilot-instructions.md` first — it's the master context for this project +- Read the relevant skill file for each phase (referenced below) +- Use subagents for all heavy work — keep your own context clean +- Present clear summaries after each subagent completes +- **Wait for user approval between phases** — never auto-proceed from Plan to Create or Create to Dispatch + +## Commands (detected from user prompt) + +Detect the user's intent from their message: +- If the message describes a new feature → run the **Full Flow** (design phase) +- If the message says "approved", "plan", "break into PBIs" → run the **Planning** phase +- If the message says "create the PBIs", "push to ADO" → run the **Creation** phase +- If the message says "dispatch", "send to agent" → run the **Dispatch** phase +- If the message says "status", "check", "monitor" → run the **Monitor** phase + +### Full Flow (default — new feature) +When the user describes a feature: + +Start with: +``` +## 🚀 Feature Orchestration Started + +**Feature**: [user's feature description] + +I'll walk you through: **Design** → **Plan** → **Create** → **Dispatch** → **Monitor** + +--- + +### Step 1: Writing Design Spec +``` + +Then: +1. Run the `codebase-researcher` subagent to research the current implementation +2. Run the `design-writer` subagent with the research results to write the design spec +3. Present a summary and wait for user approval to continue to planning + +### Planning Phase +When the user approves the design or says "plan" / "break into PBIs": + +Start with: +``` +## 🚀 Feature Orchestration: Planning + +**Pipeline**: ✅ Design → 📋 **Plan** → ○ Create → ○ Dispatch → ○ Monitor +``` + +1. Run the `feature-planner` subagent to decompose the feature into PBIs +2. The planner produces a structured plan with Summary Table + PBI Details +3. **Present the plan and STOP** — wait for developer approval before creating in ADO + +End with: +``` +### Next Step + +> Review the plan above. When ready, say **"create the PBIs"** to create them in Azure DevOps. +``` + +### Creation Phase +When the user approves the plan or says "create the PBIs": + +Start with: +``` +## 🚀 Feature Orchestration: Create + +**Pipeline**: ✅ Design → ✅ Plan → 📝 **Create** → ○ Dispatch → ○ Monitor +``` + +1. Run the `pbi-creator` subagent — it will: + - Discover ADO area paths and iterations from the developer's existing work items + - Present options for the developer to confirm + - Create all work items in ADO + - Link dependencies +2. Present the creation summary with AB# IDs + +End with: +``` +### Next Step + +> Say **"dispatch"** to send PBI-1 to Copilot coding agent. +``` + +### Dispatch Phase +When the user approves PBIs or says "dispatch": + +Start with: +``` +## 🚀 Feature Orchestration: Dispatch + +**Pipeline**: ✅ Design → ✅ Plan → ✅ Create → 🚀 **Dispatch** → ○ Monitor +``` + +Run the `agent-dispatcher` subagent to dispatch PBIs to Copilot coding agent. + +### Monitor Phase +When the user says "status" or "check": + +Start with: +``` +## 🚀 Feature Orchestration: Monitor + +**Pipeline**: ✅ Design → ✅ Plan → ✅ Create → ✅ Dispatch → 📡 **Monitor** +``` + +Check agent PR status by running terminal commands: +```bash +gh auth switch --user shahzaibj +gh pr list --repo "AzureAD/microsoft-authentication-library-common-for-android" --author "copilot-swe-agent[bot]" --state all --limit 5 +gh pr list --repo "AzureAD/microsoft-authentication-library-for-android" --author "copilot-swe-agent[bot]" --state all --limit 5 +``` + +## File Path Handling + +Design docs use brackets and spaces in folder names (e.g., `design-docs/[Android] Feature Name/`). +When working with these paths in PowerShell, always use `-LiteralPath` instead of `-Path`. diff --git a/.github/agents/feature-planner.agent.md b/.github/agents/feature-planner.agent.md new file mode 100644 index 00000000..1e8a516d --- /dev/null +++ b/.github/agents/feature-planner.agent.md @@ -0,0 +1,30 @@ +--- +name: feature-planner +description: Decompose features into repo-targeted PBIs for the Android Auth project. Produces a structured plan for developer review. +user-invokable: false +tools: + - search + - readFile + - listFiles + - findTextInFiles + - findFiles +--- + +# Feature Planner + +You decompose approved designs into detailed, repo-targeted PBIs for the Android Auth multi-repo project. + +## Instructions + +Read the skill file at `.github/skills/feature-planner/SKILL.md` and follow its workflow. + +## Key Rules + +- Read the approved design spec from `design-docs/` first +- One PBI per repo — never create a PBI spanning multiple repos +- PBI descriptions must be self-contained — no local file paths, no references to design-docs +- Use the PBI template at `.github/skills/feature-planner/references/pbi-template.md` +- Follow the **exact output format** defined in the skill (Summary Table + PBI Details with `
` blocks) +- Use `PBI-1`, `PBI-2` etc. for dependency references (not AB# IDs — those don't exist yet) +- **Do NOT create ADO work items** — that's handled by the `pbi-creator` agent/skill after the developer approves the plan +- Return the full structured plan for developer review diff --git a/.github/agents/pbi-creator.agent.md b/.github/agents/pbi-creator.agent.md new file mode 100644 index 00000000..d634bda5 --- /dev/null +++ b/.github/agents/pbi-creator.agent.md @@ -0,0 +1,38 @@ +--- +name: pbi-creator +description: Create Azure DevOps PBIs from an approved feature plan for the Android Auth project. +user-invokable: false +tools: + - search + - readFile + - ado/* +--- + +# PBI Creator + +You create Azure DevOps Product Backlog Items from an approved feature plan (produced by the +`feature-planner` agent/skill). + +## Instructions + +Read the skill file at `.github/skills/pbi-creator/SKILL.md` and follow its workflow. + +## Key Rules + +- **Parse the feature plan** from the chat context — extract titles, repos, priorities, + dependencies, tags, and HTML descriptions from the structured plan format +- **Discover ADO defaults first** — use `mcp_ado_wit_my_work_items` and + `mcp_ado_wit_get_work_items_batch_by_ids` to discover area paths, iteration paths, + and assignee from the developer's recent work items +- **Never hardcode area/iteration paths** — always discover from existing work items and + present options to the developer for confirmation +- Use `mcp_ado_work_list_iterations` with **`depth: 6`** (monthly sprints live at depth 6) +- Use `mcp_ado_wit_create_work_item` with these exact parameters: + - `project`: `"Engineering"` + - `workItemType`: `"Product Backlog Item"` + - `fields`: array of `{name, value}` objects +- Required fields: `System.Title`, `System.Description` (HTML, with `format: "Html"`), + `System.AreaPath`, `System.IterationPath`, `System.Tags` +- After creating all PBIs, resolve `PBI-N` references to `AB#` IDs in descriptions +- Link dependencies using `mcp_ado_wit_work_items_link` +- Return the AB# IDs, titles, repos, dependency order, and dispatch instructions diff --git a/.github/skills/design-author/SKILL.md b/.github/skills/design-author/SKILL.md new file mode 100644 index 00000000..6add4723 --- /dev/null +++ b/.github/skills/design-author/SKILL.md @@ -0,0 +1,170 @@ +--- +name: design-author +description: Create detailed design specs for Android Auth features and open them as PRs in the AuthLibrariesApiReview ADO repo. Use this skill when a developer describes a feature at a high level and wants a detailed design document / implementation spec created before coding begins. Triggers include "design this feature", "create a design spec", "write a design doc", "create an implementation plan", "I need a design review for", or any request to produce a formal design document for team review before implementation. +--- + +# Design Author + +Create detailed design specs for Android Auth features, save them locally in `design-docs/`, +and open PRs in the `AuthLibrariesApiReview` ADO repo for team review. + +## Prerequisites + +- `design-docs/` repo cloned locally (run `git droidSetup` or clone manually from + `https://dev.azure.com/IdentityDivision/DevEx/_git/AuthLibrariesApiReview`) +- ADO MCP Server running with `repositories` domain enabled (configured in `.vscode/mcp.json`) + +## Design Docs Context + +The `design-docs/` folder contains ~150+ design specs for the Android Auth platform. + +**Important caveats about existing designs:** +- Designs on `main` may be outdated — last-minute PR discussions often cause code to deviate. + Always verify proposed patterns against the **current codebase**, not just existing designs. +- Some designs exist only as unmerged PRs. Check open PRs in the repo for in-progress thinking. +- Use existing designs as **style reference and historical context**, not as ground truth for current behavior. + +## Design Spec Template + +Follow the repo's template at `design-docs/Template/template.md`. Key sections: + +1. **Title** — Feature name +2. **Applicable to and priority** — Platform table (focus on Android column) +3. **Components** — Which repos/modules (MSAL, Common, Broker, etc.) +4. **Problem description** — User problem, business context, examples +5. **Requirements (Must)** — Key functional requirements +6. **System Qualities (Must)** — Performance, telemetry, security, supportability +7. **Goals & Principles (Desired)** — Aspirational design goals +8. **Solution options** — Multiple options with pseudo code, pros/cons +9. **Solution Decision** — Recommended option with reasoning + +For Android-specific designs, also include: +- **API surface** — Public/internal classes, methods, parameters +- **Data flow** — Request/response flow across repos (MSAL → Common → Broker → eSTS) +- **IPC contract changes** — Any AIDL/Bundle schema changes +- **Feature flag** — Flag name and gating strategy +- **Telemetry** — Span names, attributes, success/failure signals +- **Testing strategy** — Unit test approach, instrumented test needs, E2E coverage +- **Rollout plan** — Feature flag stages, ECS configuration +- **Cross-repo impact** — Which repos need changes and in what order + +## Workflow + +### Step 1: Understand the Feature + +Gather from the developer: +1. What the feature does and why it's needed +2. Which auth flows it affects +3. Scope boundaries (in/out) +4. Any existing designs to reference (check `design-docs/` for related specs) + +### Step 2: Research the Codebase + +Use the `codebase-researcher` skill to: +- Understand how related functionality currently works +- Identify which repos/files would be affected +- Find existing patterns to follow (feature flags, error handling, telemetry, IPC contracts) +- Check for any existing design docs in `design-docs/` on the same topic + +### Step 3: Research Existing Designs + +Search the `design-docs/` folder for related designs: +``` +# Look for related designs +ls design-docs/ | grep -i "" +# Read relevant designs for patterns and prior art +``` + +Android-specific designs are prefixed with `[Android]`. Pay attention to: +- Similar feature designs for structural patterns +- The level of detail expected +- How they handle cross-repo concerns + +### Step 4: Write the Design Spec + +Create the spec following the template. The file should be created at: +``` +design-docs/[Android] /.md +``` + +Use the standard template sections. For the **Solution options** section: +- Always provide at least 2 options +- Include pseudo code / API signatures for each +- List concrete pros/cons +- Make a clear recommendation in the Solution Decision section + +### Step 5: Push as Draft PR for Review + +Immediately after writing the spec, create a branch, push, and open a **draft PR** so the +developer can use ADO's inline commenting UI for real review feedback. + +```bash +cd design-docs/ +git checkout -b design/ +git add "[Android] " +git commit -m "Add design spec: " +git push origin design/ +``` + +Open a draft PR: + +**Option A — Via ADO MCP Server** (if `repositories` tools are available): +Use the ADO MCP repository tools to create a pull request in the `DevEx` project, +`AuthLibrariesApiReview` repo, targeting the `main` branch. Set it as draft if the API supports it. + +**Option B — Via Azure DevOps web UI**: +Provide the developer with a direct link: +``` +https://dev.azure.com/IdentityDivision/DevEx/_git/AuthLibrariesApiReview/pullrequestcreate?sourceRef=design/&targetRef=main +``` +Remind the developer to mark it as **Draft** when creating. + +### Step 6: Present Summary to Developer + +```markdown +## Design Spec Draft PR Opened: [Feature Name] + +**Local file**: `design-docs/[Android] /.md` +**Branch**: `design/` +**Draft PR**: [link to PR] + +### Summary +[2-3 sentence summary of the proposed design] + +### Recommended Solution +[Brief description of the recommended option and why] + +### How to Review +1. Open the draft PR link above +2. Use ADO's inline commenting to leave feedback on specific lines +3. When done, say: **"address my design review comments"** +4. I'll read the PR comments via the ADO MCP server and update the spec accordingly + +When the team approves, say: **"Design is approved, proceed with implementation"** +``` + +### Step 7: Address PR Review Comments + +When the developer asks to address review comments: + +1. Use the ADO MCP Server repository tools to read PR thread comments +2. For each comment: + - Understand the feedback + - Edit the local design spec to address it + - Reply to the PR thread confirming the resolution +3. Commit and push the updates to the same branch +4. Report a summary of changes made + +### Step 8: Proceed to Implementation (on approval) + +When the developer confirms the design is approved: +1. The PR can be completed/merged in ADO +2. Hand off to the `feature-planner` skill for PBI decomposition + +## Integration with Feature Planner + +When the developer confirms the design is approved, the `feature-planner` skill should: +1. Read the approved design spec from `design-docs/` +2. Use it as the primary source for PBI decomposition +3. Reference the design doc PR link in each PBI description +4. Ensure PBI acceptance criteria align with the design's requirements diff --git a/.github/skills/feature-planner/SKILL.md b/.github/skills/feature-planner/SKILL.md new file mode 100644 index 00000000..fc0dfbbd --- /dev/null +++ b/.github/skills/feature-planner/SKILL.md @@ -0,0 +1,221 @@ +--- +name: feature-planner +description: Decompose high-level feature requests into detailed, repo-targeted PBIs for the Android Auth multi-repo project. Use this skill when a developer describes a feature at a high level and wants it broken down into actionable work items. Triggers include "plan this feature", "break this down into PBIs", "decompose this into tasks", "plan implementation for", or any request to turn a feature idea into structured development work items. This skill produces a plan for developer review — actual ADO work item creation is handled by the `pbi-creator` skill. +--- + +# Feature Planner + +Decompose high-level feature descriptions into detailed, repo-targeted PBIs for the Android Auth +multi-repo codebase. Produce a structured plan for developer review. + +**This skill does NOT create ADO work items.** It produces a structured PBI plan that the +developer reviews and approves. Once approved, the `pbi-creator` skill handles ADO work item +creation, iteration/area path discovery, and dependency linking. + +## Prerequisites + +- Developer provides: feature description and priority +- No ADO MCP tools required — this skill is pure planning + +## Repository Routing + +Determine which repo(s) each PBI targets based on the architectural layer: + +| Module | GitHub Repo | When to Target | +|--------|-------------|----------------| +| common | `AzureAD/microsoft-authentication-library-common-for-android` | Shared utilities, IPC logic, data models, base classes, command architecture, token cache, crypto, telemetry primitives, AIDL contracts | +| common4j | (same repo as common) | Pure Java/Kotlin shared logic with no Android dependency | +| msal | `AzureAD/microsoft-authentication-library-for-android` | Client-facing API, AcquireToken/AcquireTokenSilent flows, MSAL-specific controllers, public configuration | +| broker | `identity-authnz-teams/ad-accounts-for-android` | Broker-side auth processing, PRT acquisition/rotation, device registration, eSTS communication, IPC entry points | +| broker4j | (same repo as broker) | Pure Java/Kotlin broker business logic, Protobuf schemas | +| adal | `AzureAD/azure-activedirectory-library-for-android` | Legacy ADAL changes only (rare — maintenance mode, bug fixes only) | + +**Routing heuristic:** +1. If it touches IPC contracts, shared data models, or command architecture → `common` +2. If it's a client-facing API change or MSAL configuration → `msal` +3. If it handles token processing on the broker side, PRT, device registration → `broker` +4. If it's a pure Java utility with no Android dependency → `common4j` or `broker4j` +5. Most features span `common` + one consumer (`msal` or `broker`) — create separate PBIs for each + +## Workflow + +### Step 1: Check for Approved Design + +Before decomposing into PBIs, check if an approved design spec exists: + +1. Check `design-docs/` for a matching design: `ls design-docs/ | grep -i ""` +2. If a design exists and is approved (merged PR), use it as the primary source for decomposition. +3. If no design exists, ask the developer: + > "No design spec found for this feature. Would you like me to create one first using the + > `design-author` skill? This is recommended for features that span multiple repos or + > introduce new APIs." +4. If the developer wants a design first, hand off to the `design-author` skill and stop. +5. For small, single-repo changes (bug fixes, minor enhancements), skip design and proceed directly. + +### Step 2: Understand the Feature + +Gather from the developer: +1. **What** the feature does (functional description) +2. **Why** it's needed (user problem, business reason) +3. **Which flows** it affects (AcquireToken, AcquireTokenSilent, PRT, device registration, etc.) +4. **Scope boundaries** (what's in/out) + +If the feature description is vague, use the `prompt-refiner` skill to structure it first. + +### Step 3: Research Current Implementation + +Use the `codebase-researcher` skill to understand: +- How related functionality currently works +- Which repos/files would need changes +- Existing patterns to follow (feature flags, error handling, telemetry) +- Test patterns in the affected areas + +### Step 4: Decompose into PBIs + +Break the feature into PBIs following these rules: + +1. **One PBI per repo** — never create a PBI that spans multiple repos +2. **Dependency ordering** — if PBI-B depends on PBI-A, document the dependency explicitly +3. **Right-sized** — each PBI should be implementable by a single Copilot coding agent session (roughly 1-3 files changed, <500 lines) +4. **Self-contained description** — the PBI description must contain everything the coding agent needs to implement it without additional context +5. **No local file paths** — never reference `design-docs/` or other local workspace paths in PBI descriptions. The Copilot coding agent runs in a cloud environment with only the target repo cloned — it cannot access the super-repo, design docs, or other sub-repos. Inline all necessary context directly into the PBI description. + +### Step 5: Write PBI Descriptions + +Each PBI description MUST follow the template in `references/pbi-template.md`. + +Key sections: +- **Objective**: What to implement and where +- **Target Repository**: Explicit repo URL + base branch +- **Context**: Why this change is needed, how it fits in the feature +- **Technical Requirements**: Specific implementation guidance +- **Acceptance Criteria**: Concrete, verifiable checklist +- **Dependencies**: Use PBI numbers like "PBI-1", not AB# IDs (those don't exist yet). + The `pbi-creator` skill resolves these to AB# IDs after creation. +- **Files to Modify/Create**: Specific file paths when known + +### Step 6: Present Plan for Review + +Present the full plan using the **exact output format below**. This format is designed to be: +- **Human-readable**: developers can scan the summary table and expand individual PBIs for detail +- **Handoff-ready**: the `pbi-creator` skill can extract all fields needed to create ADO work items + +**IMPORTANT**: Use this exact format — the `pbi-creator` skill depends on it. + +#### Output Format + +The output MUST contain these sections in this order: + +**1. Header with metadata:** + +```markdown +## Feature Plan: [Feature Name] + +**Feature flag**: `[flag_name]` (or "N/A") +**Design PR**: [link] (or "N/A") +**Total PBIs**: [N] +``` + +**2. Dependency graph** (ASCII art showing parallel/sequential relationships): + +```markdown +### Dependency Graph + +PBI-1 (common) → PBI-2 (broker) + PBI-3 (msal) [parallel after PBI-1] + PBI-2 → PBI-4 (broker) +``` + +**3. Summary table** (one row per PBI, for quick scanning): + +```markdown +### Summary Table + +| PBI | Title | Repo | Module | Priority | Depends On | +|-----|-------|------|--------|----------|------------| +| PBI-1 | [title] | common | common4j + common | P1 | None | +| PBI-2 | [title] | broker | broker4j | P1 | PBI-1 | +| PBI-3 | [title] | msal | msal | P2 | PBI-1 | +``` + +**4. Dispatch order** (sequenced for the `pbi-dispatcher` skill): + +```markdown +### Dispatch Order + +1. Dispatch **PBI-1** first (no blockers) +2. After PBI-1 merges → dispatch **PBI-2** and **PBI-3** in parallel +3. After PBI-2 merges → dispatch **PBI-4** +``` + +**5. PBI details** — one block per PBI with metadata header + collapsible HTML description: + +Each PBI detail block MUST have this structure: + +```markdown +--- + +#### PBI-1: [Title] + +| Field | Value | +|-------|-------| +| **Repo** | `[org/repo-name]` | +| **Module** | `[module]` | +| **Priority** | P[1-3] | +| **Depends on** | None / PBI-X | +| **Tags** | `ai-generated; copilot-agent-ready; [feature-tag]` | + +
+Full Description (click to expand) + +{paste the full HTML description here, per pbi-template.md} + +
+``` + +**Why this structure?** +- The **metadata table** above the `
` block lets the developer quickly scan each PBI + without expanding the full description. +- The **`
` block** contains the complete HTML description that the `pbi-creator` skill + will extract verbatim and set as `System.Description` in ADO. +- The **Summary Table** gives the developer a bird's eye view to approve the breakdown before + seeing any details. + +**6. Notes** (cross-repo coordination, external team notifications, etc.): + +```markdown +### Notes + +- [Cross-repo coordination needed] +- [OneAuth team notification if applicable] +``` + +**7. Handoff prompt** — always end with: + +```markdown +### Next Step + +> Plan approved? Say **"create the PBIs"** to trigger the `pbi-creator` skill, +> which will discover your ADO area path and iteration, then create all work items. +``` + +## Common Patterns + +### Single-Repo Feature +Most bug fixes and small features only touch one repo. Create a single PBI. + +### Two-Repo Feature (Common + Consumer) +The most common multi-repo pattern: +1. PBI-1: Add shared logic/contract in `common` +2. PBI-2: Consume from `msal` or `broker` + +### Three-Repo Feature (Common + Broker + MSAL) +For end-to-end features affecting the full auth flow: +1. PBI-1: Add shared contract/data model in `common` +2. PBI-2: Implement broker-side processing (depends on PBI-1) +3. PBI-3: Implement MSAL client-side API (depends on PBI-1) +4. PBI-4: (optional) Integration test PBI + +### Feature Flag Convention +All PBIs for a feature should use the **same feature flag name** across repos: +- Flag name format: `ExperimentationFeatureFlag.` +- Include the flag name in each PBI description diff --git a/.github/skills/feature-planner/references/dispatch-workflow.md b/.github/skills/feature-planner/references/dispatch-workflow.md new file mode 100644 index 00000000..bdd8fd52 --- /dev/null +++ b/.github/skills/feature-planner/references/dispatch-workflow.md @@ -0,0 +1,154 @@ +--- +name: pbi-dispatcher +description: > + Dispatch Azure DevOps PBIs to GitHub Copilot coding agent for autonomous implementation. + Use this skill when PBIs have been created (by the feature-planner or manually) and you + want to send them to Copilot coding agent to generate PRs. Triggers include "dispatch PBIs + to agent", "assign to Copilot", "send work items to coding agent", "kick off agent + implementation", or any request to have Copilot coding agent pick up and implement + Azure DevOps work items. +--- + +# PBI Dispatcher + +Dispatch Azure DevOps PBIs to GitHub Copilot coding agent by creating GitHub Issues +and assigning them to `copilot-swe-agent[bot]`. + +## Prerequisites + +- **ADO MCP Server** must be running (for reading PBI details) +- **GitHub CLI** (`gh`) must be authenticated with access to target repos +- PBIs must exist in ADO with tag `copilot-agent-ready` +- Target repos must have Copilot coding agent enabled + +## Dispatch Workflow + +### Step 1: Identify PBIs to Dispatch + +Query ADO for PBIs ready for dispatch. Use the ADO MCP Server to: +1. List work items tagged `copilot-agent-ready` in `IdentityDivision/Engineering` +2. Filter by iteration/sprint if specified +3. Sort by dependency order (PBIs with no dependencies first) + +### Step 2: Parse Target Repository + +Extract the target repository from the PBI description. The PBI template includes a +"Target Repository" section with the format: +- `AzureAD/microsoft-authentication-library-common-for-android` → common +- `AzureAD/microsoft-authentication-library-for-android` → msal +- `identity-authnz-teams/ad-accounts-for-android` → broker +- `AzureAD/azure-activedirectory-library-for-android` → adal + +### Step 3: Check Dependencies + +Before dispatching a PBI: +1. Check if it has dependencies listed (other AB# IDs) +2. If dependencies exist, verify their PRs have been merged +3. If dependencies are not yet merged, skip and flag for later dispatch + +### Step 4: Create GitHub Issue + +For each ready PBI, create a GitHub Issue in the target repo: + +**Issue Title**: Same as PBI title + +**Issue Body** (template): +```markdown +## Auto-dispatched from Azure DevOps + +**Work Item**: Fixes AB# +**Priority**: +**Iteration**: + +--- + + + +--- + +> This issue was auto-created from ADO PBI AB# for Copilot coding agent dispatch. +> Do not modify this issue directly — it is managed by the dispatch pipeline. +``` + +### Step 5: Assign to Copilot Coding Agent + +After creating the issue, assign it to Copilot coding agent. + +**Method A — GitHub CLI** (recommended for interactive use): +```bash +# Create the issue and assign to Copilot in one step +gh issue create \ + --repo "OWNER/REPO" \ + --title "PBI Title" \ + --body "Issue body with Fixes AB#12345" \ + --assignee "copilot-swe-agent[bot]" +``` + +**Method B — GitHub REST API** (recommended for scripted automation): +```bash +# Step 1: Create issue +gh api /repos/OWNER/REPO/issues \ + --method POST \ + -f title="PBI Title" \ + -f body="Issue body" \ + | jq '.number' + +# Step 2: Assign to Copilot +gh api /repos/OWNER/REPO/issues/ISSUE_NUMBER/assignees \ + --method POST \ + --input - <<< '{ + "assignees": ["copilot-swe-agent[bot]"], + "agent_assignment": { + "target_repo": "OWNER/REPO", + "base_branch": "dev", + "custom_instructions": "Follow the repo copilot-instructions.md. PR title must include Fixes AB#." + } + }' +``` + +### Step 6: Update ADO Work Item + +After dispatching, update the ADO work item: +- State: `Active` (or `In Progress`) +- Add tag: `agent-dispatched` +- Add comment: `Dispatched to Copilot coding agent via GitHub Issue # in ` + +### Step 7: Report Dispatch Summary + +```markdown +## Dispatch Summary + +| AB# | Title | Repo | GitHub Issue | Agent Status | +|-----|-------|------|-------------|--------------| +| AB#12345 | [title] | common | #42 | Dispatched | +| AB#12346 | [title] | broker | — | Blocked (depends on AB#12345) | +| AB#12347 | [title] | msal | #18 | Dispatched | + +### Next Steps +- Monitor agent sessions at https://github.com/copilot/agents +- After AB#12345 PR merges, re-run dispatch for blocked PBIs +- Review generated PRs and use `@copilot` for feedback +``` + +## Batch Dispatch (Overnight Mode) + +For the "developer leaves, everything is done by morning" workflow: + +1. Run the orchestration script (`scripts/agent-pipeline/orchestrate.py`) +2. It dispatches all independent PBIs in parallel +3. Set up a cron job or scheduled GitHub Action to re-check dependencies every 2 hours +4. When a dependency PR merges, the next wave of PBIs is dispatched automatically + +## Review Feedback Loop + +After agent PRs are created, reviewers can iterate: +1. Leave review comments on the PR +2. Mention `@copilot` with specific feedback +3. Copilot coding agent picks up the feedback and pushes fixes +4. Repeat until the PR is approved + +Example feedback comment: +``` +@copilot This method should use the Logger class instead of android.util.Log. +Also please add unit tests for the error case when the IPC call throws DeadObjectException. +``` diff --git a/.github/skills/feature-planner/references/pbi-template.md b/.github/skills/feature-planner/references/pbi-template.md new file mode 100644 index 00000000..f1aea927 --- /dev/null +++ b/.github/skills/feature-planner/references/pbi-template.md @@ -0,0 +1,142 @@ +# PBI Description Template + +Use this template for every PBI created by the feature-planner skill. The description must be +self-contained — a Copilot coding agent should be able to implement the PBI using only this +description and the repo's `copilot-instructions.md`. + +--- + +## Template + +```html +

Objective

+

Implement [what] in [which module] that [does what] for [purpose].

+ +

Target Repository

+ + + + +
Repo[org/repo-name]
Base Branchdev
Module[common | common4j | msal | AADAuthenticator | broker4j | adal]
+ +

Context

+

[Why this change is needed. How it fits into the larger feature. What user problem it solves.]

+ +

Feature Flag

+

Wrap all new behavior behind ExperimentationFeatureFlag.[FLAG_NAME].

+ +

Technical Requirements

+
    +
  • [Specific implementation detail 1]
  • +
  • [Specific implementation detail 2]
  • +
  • [Pattern to follow — reference existing class/method if applicable]
  • +
  • [Error handling requirements]
  • +
  • [Telemetry/logging requirements]
  • +
+ +

Scope

+

In scope:

+
    +
  • [What to implement]
  • +
+

Out of scope:

+
    +
  • [What NOT to implement — important for constraining the agent]
  • +
+ +

Files to Modify/Create

+
    +
  • [path/to/existing/File.kt] — [what to change]
  • +
  • [path/to/new/File.kt] — [new file, what it contains]
  • +
+ +

Acceptance Criteria

+
    +
  • [ ] [Functional criterion 1]
  • +
  • [ ] [Functional criterion 2]
  • +
  • [ ] Feature flag integration: behavior is off when flag is disabled
  • +
  • [ ] Unit tests added for new/modified logic
  • +
  • [ ] No new lint warnings introduced
  • +
  • [ ] Compile check passes: ./gradlew :[module]:compile[Variant]Kotlin
  • +
  • [ ] Unit tests pass: ./gradlew :[module]:test[Variant]UnitTest
  • +
+ +

Dependencies

+

[List any PBIs that must be completed before this one, with AB# IDs]

+

Or: "None — this PBI can be implemented independently."

+ +

Additional Context

+

[Links to design docs, related PRs, or existing patterns to follow]

+``` + +--- + +## Example: Adding Retry Logic to IPC Token Request + +```html +

Objective

+

Add automatic retry with exponential backoff for failed IPC token requests in the Common +module to improve reliability when the Broker process is temporarily unavailable.

+ +

Target Repository

+ + + + +
RepoAzureAD/microsoft-authentication-library-common-for-android
Base Branchdev
Modulecommon
+ +

Context

+

Users experience intermittent IPC failures when the Broker process is starting up or under +heavy load. Adding retry logic at the IPC layer in Common will transparently improve +reliability for all consumers (MSAL, OneAuth) without requiring changes in each client.

+ +

Feature Flag

+

Wrap all new behavior behind ExperimentationFeatureFlag.IPC_RETRY_ENABLED.

+ +

Technical Requirements

+
    +
  • Add retry logic in the IPC strategy classes (BrokerAccountManagerStrategy or similar)
  • +
  • Use exponential backoff: 500ms, 1s, 2s (3 attempts max)
  • +
  • Only retry on transient IPC errors (DeadObjectException, RemoteException), not on auth errors
  • +
  • Log each retry attempt using the Logger class with correlation ID
  • +
  • Add telemetry span attribute for retry count
  • +
  • Follow existing error handling patterns in the IPC layer
  • +
+ +

Scope

+

In scope:

+
    +
  • Retry logic with exponential backoff
  • +
  • Logging and telemetry for retries
  • +
  • Feature flag gating
  • +
+

Out of scope:

+
    +
  • UI-level retry indication
  • +
  • Per-operation retry configuration
  • +
  • Changes in MSAL or Broker repos
  • +
+ +

Files to Modify/Create

+
    +
  • common/src/main/java/com/microsoft/identity/common/internal/broker/ipc/IpcRetryPolicy.kt — New file: retry policy with exponential backoff
  • +
  • common/src/main/java/com/microsoft/identity/common/internal/broker/ipc/BrokerAccountManagerStrategy.java — Wrap IPC calls with retry policy
  • +
  • common/src/test/java/com/microsoft/identity/common/internal/broker/ipc/IpcRetryPolicyTest.kt — New file: unit tests
  • +
+ +

Acceptance Criteria

+
    +
  • [ ] Retries on DeadObjectException and RemoteException
  • +
  • [ ] Does not retry on AuthenticationException or other auth errors
  • +
  • [ ] Respects max retry count of 3
  • +
  • [ ] Backoff timing: 500ms, 1s, 2s
  • +
  • [ ] Feature flag disables all retry behavior when off
  • +
  • [ ] Logger output includes correlation ID and retry attempt number
  • +
  • [ ] Unit tests cover: success first try, retry-then-success, max-retries-exceeded
  • +
  • [ ] Compile check passes: ./gradlew :common:compileDebugKotlin
  • +
  • [ ] Unit tests pass: ./gradlew :common:testDebugUnitTest
  • +
+ +

Dependencies

+

None — this PBI can be implemented independently.

+``` diff --git a/.github/skills/pbi-creator/SKILL.md b/.github/skills/pbi-creator/SKILL.md new file mode 100644 index 00000000..c6c44b4c --- /dev/null +++ b/.github/skills/pbi-creator/SKILL.md @@ -0,0 +1,249 @@ +--- +name: pbi-creator +description: Create Azure DevOps work items from a feature plan produced by the `feature-planner` skill. Handles ADO metadata discovery (area path, iteration, assignee), work item creation, and dependency linking. Use this skill when PBIs have been planned and approved, and you need to create them in ADO. Triggers include "create the PBIs", "create work items", "push PBIs to ADO", or approval of a feature plan. +--- + +# PBI Creator + +Create Azure DevOps work items from a feature plan produced by the `feature-planner` skill. +Handles ADO metadata discovery, work item creation, and dependency linking. + +## Prerequisites + +- **ADO MCP Server** must be running (configured in `.vscode/mcp.json`) with `work-items` domain +- A **feature plan** must exist in the current chat context (produced by the `feature-planner` skill) + — the plan follows the structured output format with Summary Table, PBI Details, etc. + +## Workflow + +### Step 1: Parse the Feature Plan + +Read the feature plan from the chat context. Extract for each PBI: +- **Title** — from the `#### PBI-N: [Title]` header +- **Repo** — from the metadata table `Repo` field +- **Module** — from the metadata table `Module` field +- **Priority** — from the metadata table `Priority` field (P1→1, P2→2, P3→3) +- **Depends on** — from the metadata table `Depends on` field (PBI-N references) +- **Tags** — from the metadata table `Tags` field +- **HTML Description** — from the `
` block (the full HTML content) + +If a feature plan is not found in context, ask the developer: +> "I don't see a feature plan in our conversation. Either: +> 1. Run the `feature-planner` skill first (say 'plan this feature') +> 2. Or paste the PBI details and I'll create the work items" + +### Step 2: Discover ADO Defaults + +**Do this BEFORE asking the developer for preferences.** This ensures you present valid +options and avoid path errors. + +**Discovery sequence:** +1. Call `mcp_ado_wit_my_work_items` to get the developer's recent work items. +2. Call `mcp_ado_wit_get_work_items_batch_by_ids` on 3-5 recent items. +3. Extract from the responses: + - `System.AreaPath` → collect **all unique area paths** with frequency counts + - `System.IterationPath` → note the format pattern + - `System.AssignedTo` → use as default assignee +4. Call `mcp_ado_work_list_iterations` with **`depth: 6`** (monthly sprints are at depth 6; + `depth: 4` will miss them). +5. Filter to upcoming/current iterations matching the discovered format. + +### Step 3: Present Options for Confirmation + +Present the discovered options to the developer. Use `ask_questions` with selections: + +**Area path:** +- If multiple unique area paths found, present them as options with frequency counts. +- If only one found, show as recommended default. +- Example: + > "Your recent work items use these area paths: + > 1. `Engineering\Auth Client\Broker\Android` (3 items) ← recommended + > 2. `Engineering\Auth Client\MSAL\Android` (1 item) + > Which area path for these PBIs?" + +**Iteration:** +- Show upcoming iterations that match the discovered format. +- Example: + > "Available upcoming iterations: + > 1. `Engineering\CY26\CY26H1\CY26Q2\Monthly\CY26Q2_M4_Apr` + > 2. `Engineering\CY26\CY26H1\CY26Q2\Monthly\CY26Q2_M5_May` + > Which iteration?" + +**Assignee:** +- Show the discovered assignee as recommended default. + +### Step 3.5: Parent Feature Work Item + +Before creating PBIs, ask the developer if the PBIs should be parented to a Feature work item. +This keeps the ADO backlog organized and makes sprint planning easier. + +**Ask the developer:** +> "Should these PBIs be parented to a Feature work item?" +> 1. **Link to existing Feature** — "Provide the Feature AB# ID (e.g., AB#12345)" +> 2. **Create a new Feature** — I'll create one titled '[Feature Name]' and parent all PBIs to it +> 3. **No parent** — create PBIs as standalone items + +**If creating a new Feature:** +Use `mcp_ado_wit_create_work_item` with: +```json +{ + "project": "Engineering", + "workItemType": "Feature", + "fields": [ + {"name": "System.Title", "value": "[Feature Name from plan header]"}, + {"name": "System.Description", "value": "

[Brief feature description from plan]

", "format": "Html"}, + {"name": "System.AreaPath", "value": "[developer-confirmed area path]"}, + {"name": "System.IterationPath", "value": "[developer-confirmed iteration]"}, + {"name": "System.AssignedTo", "value": "[developer-confirmed assignee]"}, + {"name": "System.Tags", "value": "ai-generated"} + ] +} +``` +Record the Feature ID for use in Step 4. + +**If linking to existing Feature:** +Verify the Feature exists by calling `mcp_ado_wit_get_work_item` with the provided ID. +Record the Feature ID for use in Step 4. + +### Step 4: Create Work Items in ADO + +Use `mcp_ado_wit_create_work_item` for each PBI. Create them in **dependency order** +(PBIs with no dependencies first). + +**CRITICAL tool parameters:** +- `project`: `"Engineering"` +- `workItemType`: `"Product Backlog Item"` +- `fields`: An array of `{name, value}` objects + +**Field format:** +```json +{ + "project": "Engineering", + "workItemType": "Product Backlog Item", + "fields": [ + {"name": "System.Title", "value": "[title from plan]"}, + {"name": "System.Description", "value": "[HTML from
block]", "format": "Html"}, + {"name": "System.AreaPath", "value": "[developer-confirmed area path]"}, + {"name": "System.IterationPath", "value": "[developer-confirmed iteration]"}, + {"name": "System.AssignedTo", "value": "[developer-confirmed assignee]"}, + {"name": "Microsoft.VSTS.Common.Priority", "value": "[priority number]"}, + {"name": "System.Tags", "value": "[tags from plan]"} + ] +} +``` + +**Common mistakes to avoid:** +- Do NOT use top-level params like `title`, `description`, `areaPath` — they don't exist +- Do NOT use `type` — the param is called `workItemType` +- The `description` field value must be **HTML** (not Markdown), with `"format": "Html"` +- Tags are semicolon-separated: `"ai-generated; copilot-agent-ready"` +- Area/iteration paths use backslashes: `"Engineering\\Auth Client\\Broker\\Android"` +- **Never hardcode paths** — always use values confirmed by the developer in Step 3 + +**After each PBI is created:** +- Record the returned `id` (AB# number) +- Map `PBI-N` → `AB#[id]` for dependency resolution + +### Step 5: Resolve Dependencies + Parent Links + +After all PBIs are created and you have the PBI-N → AB# mapping: + +1. **Update descriptions**: For each PBI whose description references `PBI-N` in the + Dependencies section, update the description to use the actual `AB#[id]`. + Use `mcp_ado_wit_update_work_item` to patch the description. + +2. **Link dependencies**: Use `mcp_ado_wit_work_items_link` to create predecessor links: + ```json + { + "updates": [ + {"id": [dependent_id], "linkToId": [dependency_id], "type": "predecessor", + "comment": "[Dependent title] depends on [Dependency title]"} + ] + } + ``` + +3. **Parent to Feature** (if Feature ID was recorded in Step 3.5): + Use `mcp_ado_wit_add_child_work_items` to parent all PBIs to the Feature: + ```json + { + "parentId": [feature_id], + "childWorkItemIds": [pbi_id_1, pbi_id_2, pbi_id_3] + } + ``` + If this tool is unavailable, use `mcp_ado_wit_work_items_link` with `type: "child"` instead. + +### Step 5.5: Mark PBIs as Committed + +After all PBIs are created, linked, and parented, update their state to **Committed** +so they appear in sprint planning. + +Use `mcp_ado_wit_update_work_items_batch` (if available) or `mcp_ado_wit_update_work_item` +for each PBI: +```json +{ + "id": [pbi_id], + "fields": [ + {"name": "System.State", "value": "Committed"} + ] +} +``` + +Also update the Feature work item (if created) to Committed state. + +### Step 6: Report Summary + +Present the results: + +```markdown +## PBIs Created: [Feature Name] + +### Work Items + +| PBI | AB# | Title | Repo | Depends On | State | Link | +|-----|-----|-------|------|------------|-------|------| +| PBI-1 | AB#12345 | [title] | common | — | Committed | [link] | +| PBI-2 | AB#12346 | [title] | broker | AB#12345 | Committed | [link] | +| PBI-3 | AB#12347 | [title] | msal | AB#12345 | Committed | [link] | + +### ADO Settings Used + +- **Parent Feature**: AB#12340 `[Feature title]` (or "None") +- **Area Path**: `[confirmed path]` +- **Iteration**: `[confirmed path]` +- **Assigned to**: `[confirmed assignee]` +- **State**: Committed + +### Dispatch Order + +1. Dispatch **AB#12345** first (no blockers) +2. After AB#12345 merges → dispatch **AB#12346** and **AB#12347** in parallel + +### Next Step + +> Say **"dispatch"** to send PBI-1 to Copilot coding agent via the `pbi-dispatcher` skill. +``` + +## MCP Server Failure Recovery + +If ADO MCP tools become unavailable mid-workflow: +1. Ask the developer to restart the MCP server: + Command Palette → `MCP: Restart Server` → `ado` +2. If tools still don't load, recommend starting a **new chat session** — MCP tools sometimes + don't reconnect to existing sessions after a restart. +3. **Preserve progress**: Note which PBIs were already created (with AB# IDs) so the new + session can continue from where it left off without duplicating work items. +4. In the new session, the developer can say: + > "Continue creating PBIs for [feature]. PBI-1 already created as AB#12345. Create PBI-2 onwards." + +## Edge Cases + +### Plan has a single PBI +Skip dependency linking. Create one work item and report. + +### Developer wants different area paths per PBI +If PBIs target different teams (e.g., one in Common, one in MSAL), ask if they want different +area paths. Present the discovered options for each PBI individually. + +### Developer modifies the plan before approving +If the developer asks for changes to the plan (add/remove PBIs, change descriptions), defer back +to the `feature-planner` skill to regenerate the plan, then return here for creation. From ff82c264f2f7120e4ee85b0d659cac882414ca84 Mon Sep 17 00:00:00 2001 From: Shahzaib Date: Fri, 27 Feb 2026 16:27:03 -0800 Subject: [PATCH 02/32] Add pbi dispatcher skill and subagent --- .github/agents/agent-dispatcher.agent.md | 49 +++++ .github/agents/feature-orchestrator.agent.md | 6 +- .github/skills/pbi-dispatcher/SKILL.md | 186 +++++++++++++++++++ 3 files changed, 239 insertions(+), 2 deletions(-) create mode 100644 .github/agents/agent-dispatcher.agent.md create mode 100644 .github/skills/pbi-dispatcher/SKILL.md diff --git a/.github/agents/agent-dispatcher.agent.md b/.github/agents/agent-dispatcher.agent.md new file mode 100644 index 00000000..b8b46b55 --- /dev/null +++ b/.github/agents/agent-dispatcher.agent.md @@ -0,0 +1,49 @@ +--- +name: agent-dispatcher +description: Dispatch Azure DevOps PBIs to GitHub Copilot coding agent for implementation. +user-invokable: false +tools: + - readFile + - runInTerminal + - ado/* +--- + +# Agent Dispatcher + +You dispatch PBIs to GitHub Copilot coding agent for autonomous implementation. + +## Instructions + +Read the skill file at `.github/skills/pbi-dispatcher/SKILL.md` and follow its workflow. + +## Key Rules + +1. **Discover gh accounts first** — follow the GitHub Account Discovery sequence in the skill: + - Check `.github/developer-local.json` + - Fall back to `gh auth status` + - Fall back to prompting the developer + - **Never hardcode GitHub usernames** + +2. **Switch gh account** before any GitHub operations using the discovered usernames: + - `AzureAD/*` repos → `gh auth switch --user ` + - `identity-authnz-teams/*` repos → `gh auth switch --user ` + +2. **Read the full PBI** from ADO using `mcp_ado_wit_get_work_item` before dispatching + +3. **Dispatch using `gh agent-task create`** with the FULL PBI description: + ```powershell + gh auth switch --user + $prompt = "" + gh agent-task create $prompt --repo "OWNER/REPO" --base dev + ``` + +4. **Fallback** if `agent-task create` fails: + ```powershell + gh issue create --repo "OWNER/REPO" --title "..." --body "..." + # Then assign: + echo '{"assignees":["copilot-swe-agent[bot]"]}' | gh api /repos/OWNER/REPO/issues/NUMBER/assignees --method POST --input - + ``` + +5. **Respect dependencies** — don't dispatch if dependent PBIs haven't been implemented yet + +6. Return the dispatch summary with AB# IDs, repos, PR numbers (if available), and status diff --git a/.github/agents/feature-orchestrator.agent.md b/.github/agents/feature-orchestrator.agent.md index c4ca2a74..6ee2f1fa 100644 --- a/.github/agents/feature-orchestrator.agent.md +++ b/.github/agents/feature-orchestrator.agent.md @@ -152,9 +152,11 @@ Start with: **Pipeline**: ✅ Design → ✅ Plan → ✅ Create → ✅ Dispatch → 📡 **Monitor** ``` -Check agent PR status by running terminal commands: +Check agent PR status by running terminal commands. +First discover the developer's GitHub username (check `.github/developer-local.json`, +fall back to `gh auth status`, then prompt if needed): ```bash -gh auth switch --user shahzaibj +gh auth switch --user gh pr list --repo "AzureAD/microsoft-authentication-library-common-for-android" --author "copilot-swe-agent[bot]" --state all --limit 5 gh pr list --repo "AzureAD/microsoft-authentication-library-for-android" --author "copilot-swe-agent[bot]" --state all --limit 5 ``` diff --git a/.github/skills/pbi-dispatcher/SKILL.md b/.github/skills/pbi-dispatcher/SKILL.md new file mode 100644 index 00000000..fdc4bc0c --- /dev/null +++ b/.github/skills/pbi-dispatcher/SKILL.md @@ -0,0 +1,186 @@ +--- +name: pbi-dispatcher +description: Dispatch Azure DevOps PBIs to GitHub Copilot coding agent for autonomous implementation. Use this skill when PBIs have been created (by the `pbi-creator` skill or manually) and you want to send them to Copilot coding agent to generate PRs. Triggers include "dispatch PBIs to agent", "assign to Copilot", "send work items to coding agent", "kick off agent implementation", "dispatch these work items", or any request to have Copilot coding agent implement ADO work items. +--- + +# PBI Dispatcher + +Dispatch Azure DevOps PBIs to GitHub Copilot coding agent by creating GitHub Issues in the +target repos and assigning them to `copilot-swe-agent[bot]`. + +## Prerequisites + +- **ADO MCP Server** running (for reading PBI details) +- **GitHub CLI** (`gh`) authenticated with accounts for target repos +- PBIs in ADO with tag `copilot-agent-ready` +- Copilot coding agent enabled on target repos + +## GitHub Account Discovery + +**CRITICAL**: Before dispatching, you must determine which `gh` CLI accounts to use. +**Never hardcode GitHub usernames** — they vary per developer. + +### Discovery Sequence + +Follow these steps **in order**. Stop at the first one that succeeds: + +**Step 0: Verify `gh` CLI is installed:** +```powershell +gh --version +``` +If this fails (command not found), offer to install it for the developer: +> "GitHub CLI (`gh`) is not installed. Want me to install it for you?" +> 1. **Yes, install it** (recommended) +> 2. **No, I'll install it myself** + +If the developer agrees, run the appropriate install command: +- **Windows**: `winget install --id GitHub.cli -e --accept-source-agreements --accept-package-agreements` +- **macOS**: `brew install gh` + +After installation completes, verify with `gh --version`, then prompt authentication: +> "gh CLI installed! Now you need to sign in: +> ``` +> gh auth login --hostname github.com +> ``` +> Run this, complete the auth flow, then say 'continue'." + +**Step 1: Check `.github/developer-local.json`** (fastest — developer already configured): +```powershell +$config = Get-Content ".github/developer-local.json" -Raw -ErrorAction SilentlyContinue | ConvertFrom-Json +$publicUser = $config.github_accounts.AzureAD +$emuUser = $config.github_accounts.'identity-authnz-teams' +``` + +**Step 2: Discover from `gh auth status`** (zero-config if logged in): +```powershell +$ghStatus = gh auth status 2>&1 +# Parse output for logged-in accounts on each host +# Look for lines like: "Logged in to github.com account " +``` +Map accounts to orgs: +- Non-EMU account (no `_` suffix) → `AzureAD/*` repos +- EMU account (ends with `_microsoft` or similar) → `identity-authnz-teams/*` repos + +**Step 3: Prompt the developer** (fallback — save for next time): +If neither Step 1 nor Step 2 yields both accounts, ask: +> "I need your GitHub usernames for dispatching: +> 1. **Public GitHub** (for AzureAD/* repos like common, msal, adal): ___ +> 2. **GitHub Enterprise / EMU** (for identity-authnz-teams/* repos like broker): ___" + +After receiving the answer, offer to save: +> "Save these to `.github/developer-local.json` so you don't have to enter them again? (Y/n)" + +If yes, write the config file: +```json +{ + "github_accounts": { + "AzureAD": "", + "identity-authnz-teams": "" + } +} +``` + +**Step 4: Not signed in at all** — if `gh auth status` shows no accounts: +> "You're not signed in to GitHub CLI. Please run: +> ``` +> gh auth login --hostname github.com +> ``` +> Then try dispatching again." + +Do NOT attempt to proceed without valid accounts — fail fast with clear instructions. + +## Repo Routing + +| Target in PBI | GitHub Repo | Account Type | +|---------------|-------------|--------------| +| common / common4j | `AzureAD/microsoft-authentication-library-common-for-android` | Public (AzureAD) | +| msal | `AzureAD/microsoft-authentication-library-for-android` | Public (AzureAD) | +| broker / broker4j / AADAuthenticator | `identity-authnz-teams/ad-accounts-for-android` | EMU (identity-authnz-teams) | +| adal | `AzureAD/azure-activedirectory-library-for-android` | Public (AzureAD) | + +## Workflow + +### 1. Read PBIs from ADO +Use ADO MCP Server tools to list/get work items tagged `copilot-agent-ready`. Read the full +PBI description — it will be needed for the dispatch prompt. + +### 2. Check Dependencies +For each PBI, check if its dependencies (other AB# IDs) have merged PRs. Skip blocked PBIs. + +### 3. Switch gh Account + Dispatch to Copilot Agent + +For each ready PBI: + +**Step 1: Switch to the correct gh account** (using the discovered username from above): +```bash +# For AzureAD/* repos (common, msal, adal): +gh auth switch --user + +# For identity-authnz-teams/* repos (broker): +gh auth switch --user +``` + +**Step 2: Dispatch using `gh agent-task create` (PREFERRED — requires gh v2.80+):** + +Write the full PBI description to a temp file and pipe it as the prompt. This avoids +shell escaping issues and ensures the full context reaches the agent: + +```bash +# Write PBI description to temp file +echo "" > /tmp/pbi-prompt.txt + +# Create agent task with full PBI content +gh agent-task create "$(cat /tmp/pbi-prompt.txt)" \ + --repo "OWNER/REPO" \ + --base dev +``` + +On Windows PowerShell: +```powershell +$prompt = @" + +"@ +$prompt | Set-Content -Path "$env:TEMP\pbi-prompt.txt" +gh agent-task create (Get-Content "$env:TEMP\pbi-prompt.txt" -Raw) --repo "OWNER/REPO" --base dev +``` + +**IMPORTANT for prompt content:** +- Include the FULL PBI description (Objective, Context, Technical Requirements, Acceptance Criteria) +- Include `Fixes AB#` so the PR links to the ADO work item +- Include `Follow .github/copilot-instructions.md strictly` as a reminder +- Do NOT include local file paths (design-docs/, etc.) — the agent can't access them +- Do NOT truncate — the full description IS the implementation spec + +**Step 3 (FALLBACK — if `gh agent-task create` fails):** + +Create a GitHub Issue and assign to Copilot: +```bash +gh issue create \ + --repo "OWNER/REPO" \ + --title "[PBI Title]" \ + --body "[Full PBI description with 'Fixes AB#ID']" +``` +Then assign via API (extract issue number from the URL output): +```bash +echo '{"assignees":["copilot-swe-agent[bot]"],"agent_assignment":{"target_repo":"OWNER/REPO","base_branch":"dev","custom_instructions":"Follow copilot-instructions.md. PR title must include Fixes AB#ID."}}' | gh api /repos/OWNER/REPO/issues/ISSUE_NUMBER/assignees --method POST --input - +``` + +### 4. Update ADO State +Mark the ADO work item as `Active`, add tag `agent-dispatched`. + +### 5. Report Summary +Output a dispatch summary table with AB#, repo, dispatch method, and status. + +## Batch Dispatch Script + +For overnight automation, use [`scripts/agent-pipeline/orchestrate.py`](../../../scripts/agent-pipeline/orchestrate.py). +This script handles dependency ordering, parallel dispatch, and ADO state updates. + +## Review Feedback Loop + +After PRs are created, use `@copilot` in PR comments to iterate: +``` +@copilot Please use the Logger class instead of android.util.Log. +Add unit tests for the error case. +``` From 728f27403ec690d47aa93b8b508efbbeac952e71 Mon Sep 17 00:00:00 2001 From: Shahzaib Date: Fri, 27 Feb 2026 16:27:10 -0800 Subject: [PATCH 03/32] Update .gitignore --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index 384ebb0e..23dfe910 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ azuresample oneauth tsl msalcpp +design-docs # Gradle files .gradle/ @@ -30,6 +31,9 @@ build/ gradle.properties local.properties +# Developer-local config (GitHub accounts, personal settings — never commit) +.github/developer-local.json + #nodejs node_modules From 6fb963c07bbb04947430fc4617fc880ffb3c0f95 Mon Sep 17 00:00:00 2001 From: Shahzaib Date: Fri, 27 Feb 2026 16:27:15 -0800 Subject: [PATCH 04/32] Update .gitconfig --- .gitconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitconfig b/.gitconfig index 7afdef01..90540b16 100644 --- a/.gitconfig +++ b/.gitconfig @@ -24,6 +24,7 @@ git clone -b main https://github.com/Azure-Samples/ms-identity-ciam-native-auth-android-sample.git nativeauthsample; \ git clone -b dev https://office.visualstudio.com/DefaultCollection/OneAuth/_git/OneAuth oneauth; \ git clone -b develop https://github.com/AzureAD/microsoft-authentication-library-for-cpp.git msalcpp; \ + git clone https://IdentityDivision@dev.azure.com/IdentityDivision/DevEx/_git/AuthLibrariesApiReview design-docs; \ cd msal; git submodule init; git submodule update; cd ..; \ cd adal; git submodule init; git submodule update; cd ..; \ cd broker; git submodule init; git submodule update; cd ..; \ From 9f3a40d640d56044b4c28ed388bda638aa399986 Mon Sep 17 00:00:00 2001 From: Shahzaib Date: Sat, 28 Feb 2026 15:29:43 -0800 Subject: [PATCH 05/32] Update skills --- .github/skills/design-author/SKILL.md | 119 +++++++++++++++++------- .github/skills/design-reviewer/SKILL.md | 109 ++++++++++++++++++++++ .github/skills/feature-planner/SKILL.md | 24 +++-- .github/skills/pbi-creator/SKILL.md | 82 +++++++++------- 4 files changed, 255 insertions(+), 79 deletions(-) create mode 100644 .github/skills/design-reviewer/SKILL.md diff --git a/.github/skills/design-author/SKILL.md b/.github/skills/design-author/SKILL.md index 6add4723..88f277fc 100644 --- a/.github/skills/design-author/SKILL.md +++ b/.github/skills/design-author/SKILL.md @@ -93,40 +93,15 @@ Use the standard template sections. For the **Solution options** section: - List concrete pros/cons - Make a clear recommendation in the Solution Decision section -### Step 5: Push as Draft PR for Review +### Step 5: Present Design for Review -Immediately after writing the spec, create a branch, push, and open a **draft PR** so the -developer can use ADO's inline commenting UI for real review feedback. - -```bash -cd design-docs/ -git checkout -b design/ -git add "[Android] " -git commit -m "Add design spec: " -git push origin design/ -``` - -Open a draft PR: - -**Option A — Via ADO MCP Server** (if `repositories` tools are available): -Use the ADO MCP repository tools to create a pull request in the `DevEx` project, -`AuthLibrariesApiReview` repo, targeting the `main` branch. Set it as draft if the API supports it. - -**Option B — Via Azure DevOps web UI**: -Provide the developer with a direct link: -``` -https://dev.azure.com/IdentityDivision/DevEx/_git/AuthLibrariesApiReview/pullrequestcreate?sourceRef=design/&targetRef=main -``` -Remind the developer to mark it as **Draft** when creating. - -### Step 6: Present Summary to Developer +After writing the spec, **STOP and present choices to the developer**. Do NOT auto-create +a PR or auto-proceed. Present the design summary and these explicit options: ```markdown -## Design Spec Draft PR Opened: [Feature Name] +## Design Spec Written: [Feature Name] **Local file**: `design-docs/[Android] /.md` -**Branch**: `design/` -**Draft PR**: [link to PR] ### Summary [2-3 sentence summary of the proposed design] @@ -134,18 +109,89 @@ Remind the developer to mark it as **Draft** when creating. ### Recommended Solution [Brief description of the recommended option and why] +--- + +### What would you like to do? + +1. **Review locally first** — I'll open the spec in the editor for you. Use the **+ + icons in the gutter** to add review comments on specific lines, then click the + **status bar button** (bottom right) to submit them. + +2. **Approve and skip PR** — Move directly to PBI planning without creating a design PR. + Say: **"design approved, plan the PBIs"** + +3. **Approve and open draft PR** — Push to AuthLibrariesApiReview repo as a **draft** PR + for team review. + Say: **"open a draft PR"** + +4. **Approve and publish PR** — Push and open a **published** (non-draft) PR for team review. + Say: **"open and publish the PR"** + +5. **Request changes** — Tell me what to change and I'll update the spec. +``` + +**MANDATORY**: Wait for the developer to explicitly choose one of these options. +Do NOT auto-select any option. + +### Step 5a: Local Review Workflow (if developer chooses option 1) + +Open the spec file in the editor for the developer: +```powershell +code "design-docs/[Android] /.md" +``` + +Then tell the developer: +> "The spec is open in the editor. Here's how to review: +> 1. Click the **+ icon** in the gutter next to any line to add a comment +> 2. Type your comment and click **Add Comment** +> 3. Comments auto-collapse — click the line indicator to expand +> 4. When done, click the **status bar button** at the bottom right +> (it shows ‘💬 N Review Comments — Click to Submit’) +> 5. This sends your comments to chat and I'll address each one" + +When the developer submits review comments (via the status bar), the design-reviewer +skill will be triggered automatically. After addressing, return to Step 5 +(present choices again). + +### Step 5b: Push and Create PR (if developer chooses option 3 or 4) + +**Branch naming**: Use the developer's alias (discovered from `git config user.email` or +`.github/developer-local.json`) as the branch prefix: +```powershell +$alias = (git config user.email) -replace '@.*', '' +git checkout -b "$alias/design-" +``` + +```bash +cd design-docs/ +git add "[Android] " +git commit -m "Add design spec: " +git push origin $BRANCH_NAME +``` + +**Create PR via ADO MCP Server** (if `repositories` tools are available): +- Set `isDraft: true` for option 3 (draft), `isDraft: false` for option 4 (published) +- **PR description**: Use actual line breaks or HTML formatting, NOT literal `\n` escape sequences +- Target branch: `main` (or `dev` depending on the repo's default) + +Present the PR link and review instructions: +```markdown +### PR Created +**PR**: [link to PR] +**Status**: Draft / Published + ### How to Review -1. Open the draft PR link above -2. Use ADO's inline commenting to leave feedback on specific lines +1. Open the PR link above +2. Use ADO's inline commenting to leave feedback 3. When done, say: **"address my design review comments"** -4. I'll read the PR comments via the ADO MCP server and update the spec accordingly +4. I'll read the PR comments via the ADO MCP server and update the spec -When the team approves, say: **"Design is approved, proceed with implementation"** +When the team approves, say: **"design approved, plan the PBIs"** ``` -### Step 7: Address PR Review Comments +### Step 6: Address PR Review Comments -When the developer asks to address review comments: +When the developer asks to address review comments (from ADO PR): 1. Use the ADO MCP Server repository tools to read PR thread comments 2. For each comment: @@ -154,8 +200,9 @@ When the developer asks to address review comments: - Reply to the PR thread confirming the resolution 3. Commit and push the updates to the same branch 4. Report a summary of changes made +5. Return to Step 5 (present choices again) -### Step 8: Proceed to Implementation (on approval) +### Step 7: Proceed to Implementation (on approval) When the developer confirms the design is approved: 1. The PR can be completed/merged in ADO diff --git a/.github/skills/design-reviewer/SKILL.md b/.github/skills/design-reviewer/SKILL.md new file mode 100644 index 00000000..21348aff --- /dev/null +++ b/.github/skills/design-reviewer/SKILL.md @@ -0,0 +1,109 @@ +--- +name: design-reviewer +description: Address review comments on design spec markdown files. Use this skill when a developer has added review comments (via the VS Code Comment API or manually) and wants the AI to address them. Triggers include "address review comments", "handle my review", "review comments on", or any request to process inline review feedback on a design spec. +--- + +# Design Reviewer + +Address review comments on design spec files. + +## How Comments Are Stored + +Comments are stored in a single well-known file: +``` +.github/design-reviews/reviews.json +``` + +The file is a JSON dictionary keyed by relative spec path: +```json +{ + "reviews": { + "design-docs/[Android] Feature Name/spec.md": [ + { "line": 30, "text": "Why is this needed?", "lineContent": "the line text" }, + { "line": 55, "text": "Is this backed by data?", "lineContent": "..." } + ], + "design-docs/[Android] Other Feature/spec.md": [ + { "line": 10, "text": "Clarify this", "lineContent": "..." } + ] + } +} +``` + +## Workflow + +### Step 1: Read Review Comments + +1. Read `.github/design-reviews/reviews.json` +2. If a specific spec path was mentioned in the prompt (e.g., "on `design-docs/.../spec.md`"), + only process comments for that spec. Otherwise process all specs in the file. +3. If the reviews file doesn't exist or has no comments, tell the user: + > "No review comments found. Add comments using the gutter icons in the editor." + +### Step 2: Read Spec Context + +For each comment, read ±5 lines around the comment's line number in the spec file. +This ensures you address the comment with full awareness of context. + +### Step 3: Evaluate Each Comment + +| Comment Type | How to Identify | Action | +|-------------|----------------|--------| +| **Genuine issue** | Points out a bug, inaccuracy, missing info | Update the spec | +| **Improvement** | Suggests better approach, more detail | Update if it improves clarity | +| **Question** | "why?", "what?", "how?" | Answer clearly. Update spec only if the answer should be documented | +| **Challenge** | "Are you sure?", "Is this correct?" | Verify against codebase. Update if wrong, explain if correct | +| **Acknowledgment** | "lol", "nice", "👍" | Acknowledge briefly, no change | + +### Step 4: Apply Changes + +For each comment requiring a spec update: +1. Read the current content around the target line +2. Make the edit using `replace_string_in_file` + +### Step 5: Clean Up reviews.json + +**IMPORTANT**: After addressing all comments for a spec, remove that spec's entry +from `reviews.json`. This prevents comments from being re-processed. + +Read the current `reviews.json`, delete the key for the addressed spec(s), and write +the file back. If no reviews remain, delete the file entirely. + +```python +# Pseudocode for cleanup: +reviews = read_json(".github/design-reviews/reviews.json") +del reviews["reviews"]["design-docs/.../spec.md"] # remove addressed spec +if len(reviews["reviews"]) == 0: + delete_file(".github/design-reviews/reviews.json") +else: + write_json(".github/design-reviews/reviews.json", reviews) +``` + +### Step 6: Present Summary + +Use this exact format: + +```markdown +## Review Comments Addressed + +--- + +### Comment 1: Line N — "[short quote]" + +**Type**: Question / Issue / Improvement / Acknowledgment + +**Action**: [What was done or why no change was needed] + +--- + +### Comment 2: Line N — "..." + +**Type**: ... + +**Action**: ... +``` + +**Rules:** +- Use `###` heading for EVERY comment — never a table +- Use `---` separators between comments +- If a comment was addressed by editing the spec, mention what changed +- If no change needed, explain why diff --git a/.github/skills/feature-planner/SKILL.md b/.github/skills/feature-planner/SKILL.md index fc0dfbbd..e933ac37 100644 --- a/.github/skills/feature-planner/SKILL.md +++ b/.github/skills/feature-planner/SKILL.md @@ -147,7 +147,7 @@ PBI-1 (common) → PBI-2 (broker) + PBI-3 (msal) [parallel after PBI-1] 3. After PBI-2 merges → dispatch **PBI-4** ``` -**5. PBI details** — one block per PBI with metadata header + collapsible HTML description: +**5. PBI details** — one block per PBI with metadata header + full description: Each PBI detail block MUST have this structure: @@ -164,22 +164,26 @@ Each PBI detail block MUST have this structure: | **Depends on** | None / PBI-X | | **Tags** | `ai-generated; copilot-agent-ready; [feature-tag]` | -
-Full Description (click to expand) +##### Description -{paste the full HTML description here, per pbi-template.md} - -
+[Write the full PBI description here in PLAIN MARKDOWN — NOT HTML. +This section should contain: Objective, Context, Technical Requirements, +Acceptance Criteria, Files to Modify, Dependencies — per pbi-template.md. +The pbi-creator skill will convert this to HTML when creating ADO work items.] ``` **Why this structure?** -- The **metadata table** above the `
` block lets the developer quickly scan each PBI - without expanding the full description. -- The **`
` block** contains the complete HTML description that the `pbi-creator` skill - will extract verbatim and set as `System.Description` in ADO. +- The **metadata table** above the description lets the developer quickly scan each PBI. +- The **description in plain markdown** renders cleanly in VS Code chat (unlike HTML tags + like `
` or `

` which show as raw text in chat). +- The `pbi-creator` skill converts the markdown description to HTML when creating ADO work items. - The **Summary Table** gives the developer a bird's eye view to approve the breakdown before seeing any details. +**IMPORTANT**: Do NOT use HTML tags (`
`, ``, `

`, `

`, `

    `, etc.) +in the plan output. VS Code chat renders markdown only — HTML tags appear as raw text and +make the output unreadable. Use standard markdown formatting instead. + **6. Notes** (cross-repo coordination, external team notifications, etc.): ```markdown diff --git a/.github/skills/pbi-creator/SKILL.md b/.github/skills/pbi-creator/SKILL.md index c6c44b4c..9fa7aa98 100644 --- a/.github/skills/pbi-creator/SKILL.md +++ b/.github/skills/pbi-creator/SKILL.md @@ -25,7 +25,14 @@ Read the feature plan from the chat context. Extract for each PBI: - **Priority** — from the metadata table `Priority` field (P1→1, P2→2, P3→3) - **Depends on** — from the metadata table `Depends on` field (PBI-N references) - **Tags** — from the metadata table `Tags` field -- **HTML Description** — from the `
    ` block (the full HTML content) +- **Description** — from the `##### Description` section (in plain markdown). + **Convert to HTML** before setting as `System.Description` in ADO: + - `## Heading` → `

    Heading

    ` + - `**bold**` → `bold` + - `- item` → `
    • item
    ` + - `` `code` `` → `code` + - Paragraphs → `

    text

    ` + - Or use a simple approach: wrap the entire markdown in `
    ` tags if conversion is complex.
     
     If a feature plan is not found in context, ask the developer:
     > "I don't see a feature plan in our conversation. Either:
    @@ -50,38 +57,47 @@ options and avoid path errors.
     
     ### Step 3: Present Options for Confirmation
     
    -Present the discovered options to the developer. Use `ask_questions` with selections:
    -
    -**Area path:**
    -- If multiple unique area paths found, present them as options with frequency counts.
    -- If only one found, show as recommended default.
    -- Example:
    -  > "Your recent work items use these area paths:
    -  > 1. `Engineering\Auth Client\Broker\Android` (3 items) ← recommended
    -  > 2. `Engineering\Auth Client\MSAL\Android` (1 item)
    -  > Which area path for these PBIs?"
    -
    -**Iteration:**
    -- Show upcoming iterations that match the discovered format.
    -- Example:
    -  > "Available upcoming iterations:
    -  > 1. `Engineering\CY26\CY26H1\CY26Q2\Monthly\CY26Q2_M4_Apr`
    -  > 2. `Engineering\CY26\CY26H1\CY26Q2\Monthly\CY26Q2_M5_May`
    -  > Which iteration?"
    -
    -**Assignee:**
    -- Show the discovered assignee as recommended default.
    -
    -### Step 3.5: Parent Feature Work Item
    -
    -Before creating PBIs, ask the developer if the PBIs should be parented to a Feature work item.
    -This keeps the ADO backlog organized and makes sprint planning easier.
    -
    -**Ask the developer:**
    -> "Should these PBIs be parented to a Feature work item?"
    -> 1. **Link to existing Feature** — "Provide the Feature AB# ID (e.g., AB#12345)"
    -> 2. **Create a new Feature** — I'll create one titled '[Feature Name]' and parent all PBIs to it
    -> 3. **No parent** — create PBIs as standalone items
    +**MANDATORY**: You MUST present options and wait for the developer to confirm before proceeding.
    +Do NOT make assumptions. Do NOT auto-select defaults. Always ask.
    +**Use the `askQuestion` tool** to present clickable MCQ-style options whenever possible.
    +
    +Present the discovered options to the developer using `ask_questions` or clear prompts:
    +
    +**Ask ONE question at a time** using the `askQuestion` tool. Do NOT batch multiple
    +settings into a single question. Present each as a separate clickable prompt,
    +wait for the answer, then ask the next one.
    +
    +**Question 1 — Area path** (ALWAYS ask, even if only one found):
    +Use `askQuestion` with options like:
    +- `Engineering\Auth Client\Broker\Android` (3 items)
    +- `Engineering\Auth Client\MSAL\Android` (1 item)
    +- Other (enter custom)
    +
    +WAIT for answer before proceeding.
    +
    +**Question 2 — Iteration** (ALWAYS present a list — never assume):
    +Use `askQuestion` with options like:
    +- `Engineering\CY26\CY26H1\CY26Q2\Monthly\CY26Q2_M4_Apr`
    +- `Engineering\CY26\CY26H1\CY26Q2\Monthly\CY26Q2_M5_May`
    +- `Engineering\CY26\CY26H1\CY26Q2\Monthly\CY26Q2_M6_Jun`
    +- Other
    +
    +WAIT for answer before proceeding.
    +
    +**Question 3 — Assignee** (confirm):
    +Use `askQuestion` with options like:
    +- `shjameel@microsoft.com` (discovered from recent work items)
    +- Someone else
    +
    +WAIT for answer before proceeding.
    +
    +**Question 4 — Parent Feature**:
    +Use `askQuestion` with options:
    +- Link to existing Feature (provide AB# ID)
    +- Create a new Feature titled '[Feature Name]'
    +- No parent — standalone PBIs
    +
    +WAIT for answer before proceeding to Step 4.
     
     **If creating a new Feature:**
     Use `mcp_ado_wit_create_work_item` with:
    
    From 556272f5d77edb4cece00e1c666bb9b38d377b25 Mon Sep 17 00:00:00 2001
    From: Shahzaib 
    Date: Sat, 28 Feb 2026 15:30:08 -0800
    Subject: [PATCH 06/32] Add custom sub-agents
    
    ---
     .github/agents/agent-dispatcher.agent.md     |   4 -
     .github/agents/codebase-researcher.agent.md  |  17 ++-
     .github/agents/design-writer.agent.md        |  27 ++--
     .github/agents/feature-orchestrator.agent.md | 148 ++++++++++++++-----
     .github/agents/feature-planner.agent.md      |   6 -
     .github/agents/pbi-creator.agent.md          |  20 +--
     6 files changed, 147 insertions(+), 75 deletions(-)
    
    diff --git a/.github/agents/agent-dispatcher.agent.md b/.github/agents/agent-dispatcher.agent.md
    index b8b46b55..611d0e1e 100644
    --- a/.github/agents/agent-dispatcher.agent.md
    +++ b/.github/agents/agent-dispatcher.agent.md
    @@ -2,10 +2,6 @@
     name: agent-dispatcher
     description: Dispatch Azure DevOps PBIs to GitHub Copilot coding agent for implementation.
     user-invokable: false
    -tools:
    -  - readFile
    -  - runInTerminal
    -  - ado/*
     ---
     
     # Agent Dispatcher
    diff --git a/.github/agents/codebase-researcher.agent.md b/.github/agents/codebase-researcher.agent.md
    index c5330a8b..5fb787d9 100644
    --- a/.github/agents/codebase-researcher.agent.md
    +++ b/.github/agents/codebase-researcher.agent.md
    @@ -2,12 +2,6 @@
     name: codebase-researcher
     description: Research the Android Auth codebase to understand existing implementations, patterns, and architecture.
     user-invokable: false
    -tools:
    -  - search
    -  - readFile
    -  - listFiles
    -  - findTextInFiles
    -  - findFiles
     ---
     
     # Codebase Researcher
    @@ -25,4 +19,13 @@ Read the skill file at `.github/skills/codebase-researcher/SKILL.md` and follow
     - Report findings with file paths and line numbers
     - Check `design-docs/` for existing related designs
     - Rate confidence: HIGH / MEDIUM / LOW for each finding
    -- Return a concise summary of findings — the coordinator will use this to inform the next step
    +- **CRITICAL: Return COMPREHENSIVE, DETAILED output** — your findings are the primary
    +  context for subsequent steps (design writing, PBI planning). Include:
    +  - Specific file paths with line numbers
    +  - Class names, method signatures, key code snippets
    +  - Architectural observations (how components connect)
    +  - Existing patterns to follow (feature flags, decorators, error handling)
    +  - Related design docs found and their key decisions
    +  - Test patterns in the affected areas
    +  Do NOT return a brief summary. Be thorough — the design-writer relies entirely on
    +  your output and cannot search the codebase itself.
    diff --git a/.github/agents/design-writer.agent.md b/.github/agents/design-writer.agent.md
    index 4a9e76d3..f8c6add6 100644
    --- a/.github/agents/design-writer.agent.md
    +++ b/.github/agents/design-writer.agent.md
    @@ -2,13 +2,6 @@
     name: design-writer
     description: Write detailed design specs for Android Auth features following the team's template.
     user-invokable: false
    -tools:
    -  - search
    -  - readFile
    -  - editFiles
    -  - createFile
    -  - runInTerminal
    -  - listFiles
     ---
     
     # Design Writer
    @@ -24,9 +17,19 @@ Read the skill file at `.github/skills/design-author/SKILL.md` and follow its wo
     - Follow the template at `design-docs/Template/template.md`
     - Include: Problem description, Requirements, 2+ Solution Options with pseudo code and pros/cons, Recommended Solution, API surface, Data flow, Feature flag, Telemetry, Testing strategy, Cross-repo impact
     - Save the spec to `design-docs/[Android] /.md`
    -- For paths with brackets `[]` or spaces, use PowerShell with `-LiteralPath`:
    -  ```powershell
    -  New-Item -ItemType Directory -LiteralPath "design-docs/[Android] Feature Name" -Force | Out-Null
    -  Set-Content -LiteralPath "design-docs/[Android] Feature Name/spec.md" -Value $content -Encoding utf8
    -  ```
    +- **After writing the spec, STOP and present 5 explicit choices** using the `askQuestion`
    +  tool to show a clickable MCQ-style UI:
    +  1. Review locally first — open the file in editor, tell them to use gutter comment icons
    +     and the status bar submit button
    +  2. Approve design and skip PR — move directly to PBI planning
    +  3. Approve design and open a **draft** PR to AuthLibrariesApiReview
    +  4. Approve design and open a **published** PR to AuthLibrariesApiReview
    +  5. Request changes to the design
    +  **Use `askQuestion` for this — do NOT present options as plain text.**
    +  **Do NOT auto-create a PR. Do NOT auto-proceed. Wait for the developer's explicit choice.**
    +  If the developer chooses option 1, open the file with `code ""` and explain
    +  how to use the gutter icons and status bar.
    +- **Branch naming**: Use the developer's alias from `git config user.email` (strip @domain). Example: `shjameel/design-push-notifications`
    +- **PR description**: Use actual line breaks or HTML formatting, NOT literal `\n` escape sequences
    +- For paths with brackets `[]` or spaces, use PowerShell with `-LiteralPath`
     - Return a summary of the design including the recommended solution and file path
    diff --git a/.github/agents/feature-orchestrator.agent.md b/.github/agents/feature-orchestrator.agent.md
    index 6ee2f1fa..89e7edab 100644
    --- a/.github/agents/feature-orchestrator.agent.md
    +++ b/.github/agents/feature-orchestrator.agent.md
    @@ -1,35 +1,11 @@
     ---
     description: End-to-end AI-driven feature development for Android Auth. Design → Plan → Create → Dispatch → Monitor.
    -tools:
    -  - agent
    -  - search
    -  - readFile
    -  - listFiles
    -  - runInTerminal
    -  - ado/*
     agents:
       - codebase-researcher
       - design-writer
       - feature-planner
       - pbi-creator
       - agent-dispatcher
    -handoffs:
    -  - label: "📋 Approve Design → Plan PBIs"
    -    agent: feature-orchestrator
    -    prompt: "Design approved. Break it into PBIs."
    -    send: false
    -  - label: "✅ Approve Plan → Create in ADO"
    -    agent: feature-orchestrator
    -    prompt: "Plan approved. Create the PBIs in ADO."
    -    send: false
    -  - label: "🚀 Approve PBIs → Dispatch to Agent"
    -    agent: feature-orchestrator
    -    prompt: "PBIs approved. Please dispatch to coding agent now."
    -    send: false
    -  - label: "📡 Check Agent Status"
    -    agent: feature-orchestrator
    -    prompt: "Check agent status"
    -    send: true
     ---
     
     # Feature Orchestrator
    @@ -39,14 +15,42 @@ You orchestrate the full pipeline: **Design → Plan → Create → Dispatch →
     
     ## How You Work
     
    -You delegate ALL specialized tasks to subagents to keep your context clean:
    +You coordinate AI-driven feature development by delegating to specialized subagents.
    +Keep your own context clean — you are the **conductor**, not the performer.
     
    -1. **Research** → Use the `codebase-researcher` subagent to search the codebase
    -2. **Design** → Use the `design-writer` subagent to write the spec
    -3. **Plan** → Use the `feature-planner` subagent to decompose into PBIs (plan only — no ADO creation)
    +1. **Research** → Use the `codebase-researcher` subagent — but instruct it to produce **detailed, comprehensive output** (see below)
    +2. **Design** → Use the `design-writer` subagent — pass the full research output in its prompt
    +3. **Plan** → Use the `feature-planner` subagent — pass the design spec content in its prompt
     4. **Create** → Use the `pbi-creator` subagent to discover ADO defaults and create work items
     5. **Dispatch** → Use the `agent-dispatcher` subagent to send PBIs to Copilot coding agent
     
    +### Critical: Subagent Output Quality
    +
    +Subagents return only a summary to you. If that summary is thin, subsequent steps lack context.
    +**Always instruct subagents to produce rich, detailed output.** Include this in every research
    +subagent prompt:
    +
    +> "Return COMPREHENSIVE findings. Your output is the primary context for the next step.
    +> Include: specific file paths with line numbers, class/method names, code snippets of
    +> key patterns, architectural observations, and existing test patterns. Do NOT summarize
    +> briefly — be thorough. The design-writer will rely entirely on your findings."
    +
    +### Context Handoff Between Steps
    +
    +**Every subagent starts with a clean context.** It's YOUR job to pass the right information.
    +If you skip context, the subagent will produce poor output or re-do work.
    +
    +| Handoff | What to pass in the subagent prompt |
    +|---------|-------------------------------------|
    +| **→ codebase-researcher** | Feature description + specific areas to investigate |
    +| **→ design-writer** | Feature description + FULL research subagent output (verbatim, not re-summarized) |
    +| **→ feature-planner** | FULL research findings + design spec content (read from disk — include requirements, solution decision, cross-repo impact, files to modify, feature flag, telemetry, testing strategy) |
    +| **→ pbi-creator** | The FULL plan output from the planner (summary table + all PBI details with descriptions) |
    +| **→ agent-dispatcher** | AB# IDs and target repos from the creation step |
    +
    +**NEVER re-summarize** subagent output when passing it to the next step. Pass it **verbatim**.
    +Re-summarizing loses the details that make subsequent steps successful.
    +
     ## Important Instructions
     
     - Read `.github/copilot-instructions.md` first — it's the master context for this project
    @@ -54,6 +58,20 @@ You delegate ALL specialized tasks to subagents to keep your context clean:
     - Use subagents for all heavy work — keep your own context clean
     - Present clear summaries after each subagent completes
     - **Wait for user approval between phases** — never auto-proceed from Plan to Create or Create to Dispatch
    +- **Interactive choices**: Whenever you need to present options to the user (design review
    +  choices, area path selection, iteration selection, etc.), use the `askQuestion` tool
    +  to show a clickable MCQ-style UI. Do NOT present options as plain text with "Say X".
    +  Use `askQuestion` for ALL user choices.
    +- **Next-step callout**: Always end your response with a visible next-step prompt.
    +  The `SessionStart` hook injects a `NEXT_STEP_PROMPT` instruction via `additionalContext`
    +  that tells you exactly what to render. If present, follow it. If not, use:
    +  
    +  ```markdown
    +  ---
    +  > **Next step**: Say **"[next action phrase]"** to continue.
    +  ```
    +  
    +  This gives users a clear, clickable-looking instruction in chat for what to say next.
     
     ## Commands (detected from user prompt)
     
    @@ -67,6 +85,13 @@ Detect the user's intent from their message:
     ### Full Flow (default — new feature)
     When the user describes a feature:
     
    +**Step 0: Register the feature in orchestrator state** (for dashboard tracking):
    +Run this terminal command FIRST, before any subagents:
    +```powershell
    +node .github/hooks/state-utils.js add-feature "{\"name\": \"\", \"step\": \"designing\"}"
    +```
    +This creates the feature entry so the dashboard shows it immediately.
    +
     Start with:
     ```
     ## 🚀 Feature Orchestration Started
    @@ -81,9 +106,32 @@ I'll walk you through: **Design** → **Plan** → **Create** → **Dispatch** 
     ```
     
     Then:
    -1. Run the `codebase-researcher` subagent to research the current implementation
    -2. Run the `design-writer` subagent with the research results to write the design spec
    -3. Present a summary and wait for user approval to continue to planning
    +1. **Run `codebase-researcher` subagent** with a detailed prompt:
    +   ```
    +   Research [feature description] in the Android Auth codebase. Return COMPREHENSIVE
    +   findings — your output is the primary context for writing the design spec.
    +
    +   Search for:
    +   - Existing implementations related to this feature across all repos (MSAL, Common, Broker)
    +   - Patterns to follow (feature flags, IPC, telemetry, decorators)
    +   - Related design docs in design-docs/
    +   - Key source files and their architecture
    +
    +   Include in your response: specific file paths with line numbers, class/method names,
    +   code snippets of key patterns, architectural observations, and test patterns.
    +   Be thorough — the design-writer relies entirely on your findings.
    +   ```
    +
    +2. **Pass the FULL research output** to the `design-writer` subagent:
    +   ```
    +   Write a design spec for: [feature description]
    +
    +   Here are the comprehensive research findings from the codebase:
    +   [paste the ENTIRE research subagent output here — do NOT summarize or truncate]
    +   ```
    +
    +3. Design-writer will write the spec and present 5 choices to the developer
    +4. Present the design-writer's summary and wait for user approval
     
     ### Planning Phase
     When the user approves the design or says "plan" / "break into PBIs":
    @@ -95,9 +143,23 @@ Start with:
     **Pipeline**: ✅ Design → 📋 **Plan** → ○ Create → ○ Dispatch → ○ Monitor
     ```
     
    -1. Run the `feature-planner` subagent to decompose the feature into PBIs
    -2. The planner produces a structured plan with Summary Table + PBI Details
    -3. **Present the plan and STOP** — wait for developer approval before creating in ADO
    +1. **Read the approved design spec** from `design-docs/`
    +2. **Pass BOTH the research findings AND the design spec** to the `feature-planner` subagent:
    +   ```
    +   Decompose this feature into repo-targeted PBIs.
    +
    +   ## Research Findings
    +   [paste the FULL codebase-researcher output from earlier — verbatim]
    +
    +   ## Design Spec
    +   [paste the FULL design spec content — requirements, solution decision,
    +   cross-repo impact, files to modify, feature flag, telemetry, testing strategy.
    +   Read it from disk if needed.]
    +   ```
    +   The planner needs BOTH — research tells it what exists in the code,
    +   the design tells it what needs to change.
    +3. The planner produces a structured plan with Summary Table + PBI Details
    +4. **Present the plan and STOP** — wait for developer approval before creating in ADO
     
     End with:
     ```
    @@ -116,12 +178,24 @@ Start with:
     **Pipeline**: ✅ Design → ✅ Plan → 📝 **Create** → ○ Dispatch → ○ Monitor
     ```
     
    -1. Run the `pbi-creator` subagent — it will:
    +1. **Pass the FULL plan** to the `pbi-creator` subagent:
    +   ```
    +   Create these PBIs in Azure DevOps.
    +
    +   ## Feature Plan
    +   [paste the FULL feature-planner output — summary table, dependency graph,
    +   dispatch order, AND all PBI details with their complete descriptions.
    +   Do NOT truncate or summarize.]
    +   ```
    +   The pbi-creator needs every PBI's title, repo, module, priority,
    +   dependencies, tags, and full description to create the work items.
    +2. The pbi-creator will:
        - Discover ADO area paths and iterations from the developer's existing work items
        - Present options for the developer to confirm
    +   - Ask about parent Feature work item
        - Create all work items in ADO
    -   - Link dependencies
    -2. Present the creation summary with AB# IDs
    +   - Link dependencies and mark as Committed
    +3. Present the creation summary with AB# IDs
     
     End with:
     ```
    diff --git a/.github/agents/feature-planner.agent.md b/.github/agents/feature-planner.agent.md
    index 1e8a516d..2e645af9 100644
    --- a/.github/agents/feature-planner.agent.md
    +++ b/.github/agents/feature-planner.agent.md
    @@ -2,12 +2,6 @@
     name: feature-planner
     description: Decompose features into repo-targeted PBIs for the Android Auth project. Produces a structured plan for developer review.
     user-invokable: false
    -tools:
    -  - search
    -  - readFile
    -  - listFiles
    -  - findTextInFiles
    -  - findFiles
     ---
     
     # Feature Planner
    diff --git a/.github/agents/pbi-creator.agent.md b/.github/agents/pbi-creator.agent.md
    index d634bda5..f4383e93 100644
    --- a/.github/agents/pbi-creator.agent.md
    +++ b/.github/agents/pbi-creator.agent.md
    @@ -2,10 +2,6 @@
     name: pbi-creator
     description: Create Azure DevOps PBIs from an approved feature plan for the Android Auth project.
     user-invokable: false
    -tools:
    -  - search
    -  - readFile
    -  - ado/*
     ---
     
     # PBI Creator
    @@ -20,19 +16,25 @@ Read the skill file at `.github/skills/pbi-creator/SKILL.md` and follow its work
     ## Key Rules
     
     - **Parse the feature plan** from the chat context — extract titles, repos, priorities,
    -  dependencies, tags, and HTML descriptions from the structured plan format
    +  dependencies, tags, and descriptions from the structured plan format
     - **Discover ADO defaults first** — use `mcp_ado_wit_my_work_items` and
       `mcp_ado_wit_get_work_items_batch_by_ids` to discover area paths, iteration paths,
       and assignee from the developer's recent work items
    -- **Never hardcode area/iteration paths** — always discover from existing work items and
    -  present options to the developer for confirmation
    +- **Never hardcode area/iteration paths** — always discover from existing work items
    +- **MANDATORY CONFIRMATIONS** — you MUST ask the developer and wait for their response before
    +  proceeding on ALL of these. **Use the `askQuestion` tool** to present clickable options:
    +  1. **Area path**: Present discovered options as clickable choices
    +  2. **Iteration**: Present discovered options as clickable choices
    +  3. **Assignee**: Confirm the discovered assignee
    +  4. **Parent Feature**: Ask if PBIs should be parented to a Feature work item
    +  Do NOT present options as plain text. Use `askQuestion` for interactive selection.
     - Use `mcp_ado_work_list_iterations` with **`depth: 6`** (monthly sprints live at depth 6)
     - Use `mcp_ado_wit_create_work_item` with these exact parameters:
       - `project`: `"Engineering"`
       - `workItemType`: `"Product Backlog Item"`
       - `fields`: array of `{name, value}` objects
    -- Required fields: `System.Title`, `System.Description` (HTML, with `format: "Html"`),
    -  `System.AreaPath`, `System.IterationPath`, `System.Tags`
    +- **Convert markdown to HTML** for `System.Description` field (with `format: "Html"`)
     - After creating all PBIs, resolve `PBI-N` references to `AB#` IDs in descriptions
     - Link dependencies using `mcp_ado_wit_work_items_link`
    +- Mark all PBIs as **Committed** state after creation
     - Return the AB# IDs, titles, repos, dependency order, and dispatch instructions
    
    From 096b693515eddc3fc7a62844cc6e33118d5b6025 Mon Sep 17 00:00:00 2001
    From: Shahzaib 
    Date: Sat, 28 Feb 2026 15:39:26 -0800
    Subject: [PATCH 07/32] Update feature-orchestrator.agent.md
    
    ---
     .github/agents/feature-orchestrator.agent.md | 26 ++++++++++----------
     1 file changed, 13 insertions(+), 13 deletions(-)
    
    diff --git a/.github/agents/feature-orchestrator.agent.md b/.github/agents/feature-orchestrator.agent.md
    index 89e7edab..6699625a 100644
    --- a/.github/agents/feature-orchestrator.agent.md
    +++ b/.github/agents/feature-orchestrator.agent.md
    @@ -1,5 +1,5 @@
     ---
    -description: End-to-end AI-driven feature development for Android Auth. Design → Plan → Create → Dispatch → Monitor.
    +description: End-to-end AI-driven feature development for Android Auth. Design → Plan → Backlog → Dispatch → Monitor.
     agents:
       - codebase-researcher
       - design-writer
    @@ -11,7 +11,7 @@ agents:
     # Feature Orchestrator
     
     You are the coordinator for AI-driven feature development in the Android Auth multi-repo project.
    -You orchestrate the full pipeline: **Design → Plan → Create → Dispatch → Monitor**.
    +You orchestrate the full pipeline: **Design → Plan → Backlog → Dispatch → Monitor**.
     
     ## How You Work
     
    @@ -21,7 +21,7 @@ Keep your own context clean — you are the **conductor**, not the performer.
     1. **Research** → Use the `codebase-researcher` subagent — but instruct it to produce **detailed, comprehensive output** (see below)
     2. **Design** → Use the `design-writer` subagent — pass the full research output in its prompt
     3. **Plan** → Use the `feature-planner` subagent — pass the design spec content in its prompt
    -4. **Create** → Use the `pbi-creator` subagent to discover ADO defaults and create work items
    +4. **Backlog** → Use the `pbi-creator` subagent to discover ADO defaults and create work items in ADO
     5. **Dispatch** → Use the `agent-dispatcher` subagent to send PBIs to Copilot coding agent
     
     ### Critical: Subagent Output Quality
    @@ -57,7 +57,7 @@ Re-summarizing loses the details that make subsequent steps successful.
     - Read the relevant skill file for each phase (referenced below)
     - Use subagents for all heavy work — keep your own context clean
     - Present clear summaries after each subagent completes
    -- **Wait for user approval between phases** — never auto-proceed from Plan to Create or Create to Dispatch
    +- **Wait for user approval between phases** — never auto-proceed from Plan to Backlog or Backlog to Dispatch
     - **Interactive choices**: Whenever you need to present options to the user (design review
       choices, area path selection, iteration selection, etc.), use the `askQuestion` tool
       to show a clickable MCQ-style UI. Do NOT present options as plain text with "Say X".
    @@ -78,7 +78,7 @@ Re-summarizing loses the details that make subsequent steps successful.
     Detect the user's intent from their message:
     - If the message describes a new feature → run the **Full Flow** (design phase)
     - If the message says "approved", "plan", "break into PBIs" → run the **Planning** phase
    -- If the message says "create the PBIs", "push to ADO" → run the **Creation** phase
    +- If the message says "create the PBIs", "backlog", "push to ADO" → run the **Backlog** phase
     - If the message says "dispatch", "send to agent" → run the **Dispatch** phase
     - If the message says "status", "check", "monitor" → run the **Monitor** phase
     
    @@ -98,7 +98,7 @@ Start with:
     
     **Feature**: [user's feature description]
     
    -I'll walk you through: **Design** → **Plan** → **Create** → **Dispatch** → **Monitor**
    +I'll walk you through: **Design** → **Plan** → **Backlog** → **Dispatch** → **Monitor**
     
     ---
     
    @@ -140,7 +140,7 @@ Start with:
     ```
     ## 🚀 Feature Orchestration: Planning
     
    -**Pipeline**: ✅ Design → 📋 **Plan** → ○ Create → ○ Dispatch → ○ Monitor
    +**Pipeline**: ✅ Design → 📋 **Plan** → ○ Backlog → ○ Dispatch → ○ Monitor
     ```
     
     1. **Read the approved design spec** from `design-docs/`
    @@ -165,17 +165,17 @@ End with:
     ```
     ### Next Step
     
    -> Review the plan above. When ready, say **"create the PBIs"** to create them in Azure DevOps.
    +> Review the plan above. When ready, say **"backlog the PBIs"** to create them in Azure DevOps.
     ```
     
     ### Creation Phase
    -When the user approves the plan or says "create the PBIs":
    +When the user approves the plan or says "backlog the PBIs" / "create the PBIs":
     
     Start with:
     ```
    -## 🚀 Feature Orchestration: Create
    +## 🚀 Feature Orchestration: Backlog
     
    -**Pipeline**: ✅ Design → ✅ Plan → 📝 **Create** → ○ Dispatch → ○ Monitor
    +**Pipeline**: ✅ Design → ✅ Plan → 📝 **Backlog** → ○ Dispatch → ○ Monitor
     ```
     
     1. **Pass the FULL plan** to the `pbi-creator` subagent:
    @@ -211,7 +211,7 @@ Start with:
     ```
     ## 🚀 Feature Orchestration: Dispatch
     
    -**Pipeline**: ✅ Design → ✅ Plan → ✅ Create → 🚀 **Dispatch** → ○ Monitor
    +**Pipeline**: ✅ Design → ✅ Plan → ✅ Backlog → 🚀 **Dispatch** → ○ Monitor
     ```
     
     Run the `agent-dispatcher` subagent to dispatch PBIs to Copilot coding agent.
    @@ -223,7 +223,7 @@ Start with:
     ```
     ## 🚀 Feature Orchestration: Monitor
     
    -**Pipeline**: ✅ Design → ✅ Plan → ✅ Create → ✅ Dispatch → 📡 **Monitor**
    +**Pipeline**: ✅ Design → ✅ Plan → ✅ Backlog → ✅ Dispatch → 📡 **Monitor**
     ```
     
     Check agent PR status by running terminal commands.
    
    From 62ada6546022c4fcfde9781b0a6eaf3b88a56840 Mon Sep 17 00:00:00 2001
    From: Shahzaib 
    Date: Sat, 28 Feb 2026 15:39:36 -0800
    Subject: [PATCH 08/32] Update copilot-instructions.md
    
    ---
     .github/copilot-instructions.md | 29 +++++++++++++++++++++++++++++
     1 file changed, 29 insertions(+)
    
    diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md
    index c4c67d62..dfaa6a55 100644
    --- a/.github/copilot-instructions.md
    +++ b/.github/copilot-instructions.md
    @@ -124,5 +124,34 @@ For complex investigation tasks, use these skills (read the skill file for detai
     | **codebase-researcher** | `.github/skills/codebase-researcher/SKILL.md` | "where is X implemented", "how does Y work", "trace the flow of", data flow investigation |
     | **incident-investigator** | `.github/skills/incident-investigator/SKILL.md` | IcM incidents, customer-reported issues, authentication failures |
     | **kusto-analyst** | `.github/skills/kusto-analyst/SKILL.md` | "query Kusto", "analyze telemetry", "check android_spans", eSTS correlation, latency investigation |
    +| **feature-planner** | `.github/skills/feature-planner/SKILL.md` | "plan this feature", "break this down into PBIs", "decompose this into tasks", feature decomposition |
    +| **pbi-creator** | `.github/skills/pbi-creator/SKILL.md` | "create the PBIs", "create work items", "push PBIs to ADO", approved plan → ADO work items |
    +| **design-author** | `.github/skills/design-author/SKILL.md` | "design this feature", "create a design spec", "write a design doc", "create an implementation plan" |
    +| **design-reviewer** | `.github/skills/design-reviewer/SKILL.md` | "address review comments", "handle my review", "review comments on" |
    +| **pbi-dispatcher** | `.github/skills/pbi-dispatcher/SKILL.md` | "dispatch PBIs to agent", "assign to Copilot", "send work items to coding agent" |
    +
    +## 13. Azure DevOps Integration
    +
    +This project uses Azure DevOps (`IdentityDivision/Engineering`). The **Azure DevOps MCP Server** is configured in `.vscode/mcp.json` for work item management. Always check to see if the Azure DevOps MCP server has a tool relevant to the user's request.
    +
    +### 13.1 AI-Driven Development Pipeline
    +This project supports an AI-driven development workflow:
    +1. **Design**: Use the `design-author` skill to create a detailed design spec in the `AuthLibrariesApiReview` ADO repo and open a PR for team review
    +2. **Plan**: After design approval, use the `feature-planner` skill to decompose the approved design into repo-targeted PBIs. Developer reviews and approves the plan.
    +3. **Backlog**: After plan approval, use the `pbi-creator` skill to discover ADO defaults (area path, iteration) and create work items in ADO with dependency links.
    +4. **Dispatch**: Use the `pbi-dispatcher` skill or `scripts/agent-pipeline/orchestrate.py` to assign PBIs to GitHub Copilot coding agent
    +5. **Implement**: Copilot coding agent creates PRs in the target repos (`msal`, `common`, `broker`, `adal`)
    +6. **Review**: Use `@copilot` in PR comments for automated feedback iteration
    +
    +### 13.2 Design Docs
    +The `design-docs/` folder contains the `AuthLibrariesApiReview` ADO repo (cloned via `git droidSetup`). It holds ~150+ design specs for the Android Auth platform. **Designs may be outdated** — always verify against the current codebase. Use them as historical context and style reference.
    +
    +### 13.2 Repository Routing (for multi-repo features)
    +| Module | GitHub Repo |
    +|--------|-------------|
    +| common / common4j | `AzureAD/microsoft-authentication-library-common-for-android` |
    +| msal | `AzureAD/microsoft-authentication-library-for-android` |
    +| broker / broker4j | `identity-authnz-teams/ad-accounts-for-android` (GHE) |
    +| adal | `AzureAD/azure-activedirectory-library-for-android` |
     
     ---
    \ No newline at end of file
    
    From 7c54688011e8c85fd2914e056ea4981364c98f9a Mon Sep 17 00:00:00 2001
    From: Shahzaib 
    Date: Sat, 28 Feb 2026 16:39:19 -0800
    Subject: [PATCH 09/32] Update SKILL.md
    
    ---
     .github/skills/pbi-creator/SKILL.md | 10 +++++++---
     1 file changed, 7 insertions(+), 3 deletions(-)
    
    diff --git a/.github/skills/pbi-creator/SKILL.md b/.github/skills/pbi-creator/SKILL.md
    index 9fa7aa98..1666da23 100644
    --- a/.github/skills/pbi-creator/SKILL.md
    +++ b/.github/skills/pbi-creator/SKILL.md
    @@ -53,7 +53,10 @@ options and avoid path errors.
        - `System.AssignedTo` → use as default assignee
     4. Call `mcp_ado_work_list_iterations` with **`depth: 6`** (monthly sprints are at depth 6;
        `depth: 4` will miss them).
    -5. Filter to upcoming/current iterations matching the discovered format.
    +5. Filter iterations to **current month or future only** — discard any iteration whose end date
    +   is in the past. Compare iteration dates against today's date. For monthly iterations with
    +   names like `CY26Q1_M2_Feb`, parse the month/year and exclude months before the current one.
    +   **Never suggest past iterations as options.**
     
     ### Step 3: Present Options for Confirmation
     
    @@ -76,9 +79,10 @@ Use `askQuestion` with options like:
     WAIT for answer before proceeding.
     
     **Question 2 — Iteration** (ALWAYS present a list — never assume):
    +Only show iterations for the **current month or later**. Never include past months.
     Use `askQuestion` with options like:
    -- `Engineering\CY26\CY26H1\CY26Q2\Monthly\CY26Q2_M4_Apr`
    -- `Engineering\CY26\CY26H1\CY26Q2\Monthly\CY26Q2_M5_May`
    +- `Engineering\CY26\CY26H1\CY26Q2\Monthly\CY26Q2_M4_Apr`: Current month
    +- `Engineering\CY26\CY26H1\CY26Q2\Monthly\CY26Q2_M5_May`: Next month
     - `Engineering\CY26\CY26H1\CY26Q2\Monthly\CY26Q2_M6_Jun`
     - Other
     
    
    From 9b17ace8dc0788202d8c0aeb1723c2c0c76edccf Mon Sep 17 00:00:00 2001
    From: Shahzaib 
    Date: Sun, 1 Mar 2026 07:59:19 -0800
    Subject: [PATCH 10/32] Update orchestrator agent
    
    ---
     .github/agents/agent-dispatcher.agent.md     | 10 ++-
     .github/agents/feature-orchestrator.agent.md | 87 ++++++++++++++++----
     2 files changed, 78 insertions(+), 19 deletions(-)
    
    diff --git a/.github/agents/agent-dispatcher.agent.md b/.github/agents/agent-dispatcher.agent.md
    index 611d0e1e..0bdc15e8 100644
    --- a/.github/agents/agent-dispatcher.agent.md
    +++ b/.github/agents/agent-dispatcher.agent.md
    @@ -42,4 +42,12 @@ Read the skill file at `.github/skills/pbi-dispatcher/SKILL.md` and follow its w
     
     5. **Respect dependencies** — don't dispatch if dependent PBIs haven't been implemented yet
     
    -6. Return the dispatch summary with AB# IDs, repos, PR numbers (if available), and status
    +6. **Report dispatch results** back in detail. For each dispatched PBI, include:
    +   - The AB# ID
    +   - The target repo
    +   - The PR number and URL (if available from the `gh agent-task create` output)
    +   - The session URL (if available)
    +   
    +   The orchestrator will use this information to update dashboard state and artifacts.
    +
    +7. Return the dispatch summary with AB# IDs, repos, PR numbers (if available), and status
    diff --git a/.github/agents/feature-orchestrator.agent.md b/.github/agents/feature-orchestrator.agent.md
    index 6699625a..acd6ecbb 100644
    --- a/.github/agents/feature-orchestrator.agent.md
    +++ b/.github/agents/feature-orchestrator.agent.md
    @@ -62,16 +62,19 @@ Re-summarizing loses the details that make subsequent steps successful.
       choices, area path selection, iteration selection, etc.), use the `askQuestion` tool
       to show a clickable MCQ-style UI. Do NOT present options as plain text with "Say X".
       Use `askQuestion` for ALL user choices.
    -- **Next-step callout**: Always end your response with a visible next-step prompt.
    -  The `SessionStart` hook injects a `NEXT_STEP_PROMPT` instruction via `additionalContext`
    -  that tells you exactly what to render. If present, follow it. If not, use:
    -  
    -  ```markdown
    -  ---
    -  > **Next step**: Say **"[next action phrase]"** to continue.
    +- **Stage transitions**: After completing a stage and presenting the summary, use the
    +  `askQuestion` tool to offer the user a clear, clickable choice to proceed. Example:
       ```
    -  
    -  This gives users a clear, clickable-looking instruction in chat for what to say next.
    +  askQuestion({
    +    question: "Design spec is ready. What would you like to do?",
    +    options: [
    +      { label: "📋 Plan PBIs", description: "Decompose the design into repo-targeted work items" },
    +      { label: "✏️ Revise Design", description: "Make changes to the design spec first" }
    +    ]
    +  })
    +  ```
    +  **NEVER** end a stage with a plain-text instruction like `> Say "plan"`. Always use
    +  `askQuestion` so the user gets a clickable UI to advance to the next stage.
     
     ## Commands (detected from user prompt)
     
    @@ -88,10 +91,32 @@ When the user describes a feature:
     **Step 0: Register the feature in orchestrator state** (for dashboard tracking):
     Run this terminal command FIRST, before any subagents:
     ```powershell
    -node .github/hooks/state-utils.js add-feature "{\"name\": \"\", \"step\": \"designing\"}"
    +node .github/hooks/state-utils.js add-feature '{"name": "", "step": "designing"}'
     ```
     This creates the feature entry so the dashboard shows it immediately.
     
    +### State Tracking Commands
    +
    +Use `state-utils.js` to keep the dashboard in sync. The feature identifier can be the
    +**feature name** (e.g., "IPC Retry with Exponential Backoff") — no need to track the auto-generated ID.
    +
    +**IMPORTANT**: When running these commands in PowerShell, always use **single quotes** around
    +JSON arguments. Do NOT use `\"` escaped double quotes — PowerShell mangles them.
    +Use: `'{"key": "value"}'` NOT `"{\"key\": \"value\"}"`.
    +
    +| When | Command |
    +|------|---------|
    +| **Design done** | `node .github/hooks/state-utils.js set-step "" design_review` |
    +| | `node .github/hooks/state-utils.js set-design "" '{"docPath":"","status":"approved"}'` |
    +| **Plan done** | `node .github/hooks/state-utils.js set-step "" plan_review` |
    +| **Backlog done** | `node .github/hooks/state-utils.js set-step "" backlog_review` |
    +| | For each PBI: `node .github/hooks/state-utils.js add-pbi "" '{"adoId":,"title":"...","module":"...","status":"Committed","dependsOn":[]}'` |
    +| **Dispatch done** | `node .github/hooks/state-utils.js set-step "" monitoring` |
    +| | For each PR: `node .github/hooks/state-utils.js add-agent-pr "" '{"repo":"...","prNumber":,"prUrl":"...","status":"open"}'` |
    +
    +**Run these commands after each phase completes** so the sidebar dashboard and feature detail
    +panel stay up to date with the correct step and artifacts.
    +
     Start with:
     ```
     ## 🚀 Feature Orchestration Started
    @@ -161,11 +186,15 @@ Start with:
     3. The planner produces a structured plan with Summary Table + PBI Details
     4. **Present the plan and STOP** — wait for developer approval before creating in ADO
     
    -End with:
    +After presenting the plan summary, use `askQuestion` to gate the next stage:
     ```
    -### Next Step
    -
    -> Review the plan above. When ready, say **"backlog the PBIs"** to create them in Azure DevOps.
    +askQuestion({
    +  question: "PBI plan is ready for review. What next?",
    +  options: [
    +    { label: "✅ Backlog in ADO", description: "Create these PBIs as work items in Azure DevOps" },
    +    { label: "✏️ Revise Plan", description: "Adjust the PBI breakdown before creating" }
    +  ]
    +})
     ```
     
     ### Creation Phase
    @@ -197,11 +226,15 @@ Start with:
        - Link dependencies and mark as Committed
     3. Present the creation summary with AB# IDs
     
    -End with:
    +After presenting the AB# summary, use `askQuestion` to gate the next stage:
     ```
    -### Next Step
    -
    -> Say **"dispatch"** to send PBI-1 to Copilot coding agent.
    +askQuestion({
    +  question: "PBIs are backlogged in ADO. What next?",
    +  options: [
    +    { label: "🚀 Dispatch to Copilot Agent", description: "Send PBI-1 to Copilot coding agent for implementation" },
    +    { label: "⏸ Pause", description: "I'll dispatch later" }
    +  ]
    +})
     ```
     
     ### Dispatch Phase
    @@ -216,6 +249,24 @@ Start with:
     
     Run the `agent-dispatcher` subagent to dispatch PBIs to Copilot coding agent.
     
    +**After the dispatcher finishes**, update state and record each dispatched PR:
    +```powershell
    +node .github/hooks/state-utils.js set-step "" monitoring
    +# For each dispatched PBI that created a PR/session:
    +node .github/hooks/state-utils.js add-agent-pr "" '{"repo":"","prNumber":,"prUrl":"","status":"open","title":""}'
    +```
    +
    +Then use `askQuestion` to gate the next stage:
    +```
    +askQuestion({
    +  question: "PBIs dispatched to Copilot coding agent. What next?",
    +  options: [
    +    { label: "📡 Monitor Agent PRs", description: "Check the status of agent-created pull requests" },
    +    { label: "⏸ Done for now", description: "I'll check status later" }
    +  ]
    +})
    +```
    +
     ### Monitor Phase
     When the user says "status" or "check":
     
    
    From 6b0accf6140f29bfbf797cb651a92101ea5238cc Mon Sep 17 00:00:00 2001
    From: Shahzaib 
    Date: Sun, 1 Mar 2026 08:53:43 -0800
    Subject: [PATCH 11/32] Update SKILL.md
    
    ---
     .github/skills/pbi-creator/SKILL.md | 86 +++++++++++++++++++----------
     1 file changed, 56 insertions(+), 30 deletions(-)
    
    diff --git a/.github/skills/pbi-creator/SKILL.md b/.github/skills/pbi-creator/SKILL.md
    index 1666da23..aec6cdde 100644
    --- a/.github/skills/pbi-creator/SKILL.md
    +++ b/.github/skills/pbi-creator/SKILL.md
    @@ -64,44 +64,70 @@ options and avoid path errors.
     Do NOT make assumptions. Do NOT auto-select defaults. Always ask.
     **Use the `askQuestion` tool** to present clickable MCQ-style options whenever possible.
     
    -Present the discovered options to the developer using `ask_questions` or clear prompts:
    +**Batch ALL questions into a SINGLE `askQuestion` call** with multiple questions.
    +This gives the user a smooth flow where answering one immediately shows the next.
    +Do NOT make separate `askQuestion` calls for each question — combine them all into one call
    +with 4 questions (Area Path, Iteration, Assignee, Parent). Example:
     
    -**Ask ONE question at a time** using the `askQuestion` tool. Do NOT batch multiple
    -settings into a single question. Present each as a separate clickable prompt,
    -wait for the answer, then ask the next one.
    +```
    +askQuestion({
    +  questions: [
    +    {
    +      header: "Area Path",
    +      question: "Which area path for these PBIs?",
    +      options: [
    +        { label: "Engineering\\Auth Client\\Broker\\Android", description: "Your most recent work items use this path", recommended: true },
    +        { label: "Engineering\\Auth Client\\MSAL\\Android" }
    +      ],
    +      allowFreeformInput: true
    +    },
    +    {
    +      header: "Iteration",
    +      question: "Which iteration? (Current date: )",
    +      options: [
    +        { label: "CY26Q2_M4_Apr (next month)", description: "Engineering\\CY26\\CY26H1\\CY26Q2\\Monthly\\CY26Q2_M4_Apr", recommended: true },
    +        { label: "CY26Q2_M5_May", description: "Engineering\\CY26\\CY26H1\\CY26Q2\\Monthly\\CY26Q2_M5_May" }
    +      ],
    +      allowFreeformInput: true
    +    },
    +    {
    +      header: "Assignee",
    +      question: "Who should be assigned?",
    +      options: [
    +        { label: "shjameel@microsoft.com", description: "Discovered from recent work items", recommended: true }
    +      ],
    +      allowFreeformInput: true
    +    },
    +    {
    +      header: "Parent",
    +      question: "Link PBIs to a parent Feature work item?",
    +      options: [
    +        { label: "Create new Feature", description: "Create a new Feature work item titled ''" },
    +        { label: "No parent", description: "No parent Feature — standalone PBIs" }
    +      ],
    +      allowFreeformInput: true
    +    }
    +  ]
    +})
    +```
     
    -**Question 1 — Area path** (ALWAYS ask, even if only one found):
    -Use `askQuestion` with options like:
    -- `Engineering\Auth Client\Broker\Android` (3 items)
    -- `Engineering\Auth Client\MSAL\Android` (1 item)
    -- Other (enter custom)
    +### Question details:
     
    -WAIT for answer before proceeding.
    +**Area Path** (ALWAYS ask, even if only one found):
    +- Show all unique area paths discovered from recent work items with frequency counts
    +- Mark the most common one as `recommended: true`
     
    -**Question 2 — Iteration** (ALWAYS present a list — never assume):
    +**Iteration** (ALWAYS present a list — never assume):
     Only show iterations for the **current month or later**. Never include past months.
    -Use `askQuestion` with options like:
    -- `Engineering\CY26\CY26H1\CY26Q2\Monthly\CY26Q2_M4_Apr`: Current month
    -- `Engineering\CY26\CY26H1\CY26Q2\Monthly\CY26Q2_M5_May`: Next month
    -- `Engineering\CY26\CY26H1\CY26Q2\Monthly\CY26Q2_M6_Jun`
    -- Other
    -
    -WAIT for answer before proceeding.
    -
    -**Question 3 — Assignee** (confirm):
    -Use `askQuestion` with options like:
    -- `shjameel@microsoft.com` (discovered from recent work items)
    -- Someone else
    +- Mark the current/next month as `recommended: true`
     
    -WAIT for answer before proceeding.
    +**Assignee** (confirm):
    +- Show the discovered assignee as `recommended: true`
     
    -**Question 4 — Parent Feature**:
    -Use `askQuestion` with options:
    -- Link to existing Feature (provide AB# ID)
    -- Create a new Feature titled '[Feature Name]'
    -- No parent — standalone PBIs
    +**Parent Feature**:
    +- Offer to create a new Feature, link to an existing one, or no parent
     
    -WAIT for answer before proceeding to Step 4.
    +Wait for all answers before proceeding to Step 4.
     
     **If creating a new Feature:**
     Use `mcp_ado_wit_create_work_item` with:
    
    From 36c0064bdb97e5bbfd30342034b209779a7954cf Mon Sep 17 00:00:00 2001
    From: Shahzaib 
    Date: Sun, 1 Mar 2026 09:39:11 -0800
    Subject: [PATCH 12/32] Add extension
    
    ---
     extensions/feature-orchestrator/README.md     |  45 ++
     .../feature-orchestrator/package-lock.json    |  58 ++
     extensions/feature-orchestrator/package.json  | 119 +++
     .../feature-orchestrator/src/dashboard.ts     | 395 ++++++++++
     .../feature-orchestrator/src/designReview.ts  | 484 ++++++++++++
     .../feature-orchestrator/src/extension.ts     |  86 ++
     .../feature-orchestrator/src/featureDetail.ts | 742 ++++++++++++++++++
     extensions/feature-orchestrator/src/tools.ts  | 158 ++++
     extensions/feature-orchestrator/tsconfig.json |  17 +
     9 files changed, 2104 insertions(+)
     create mode 100644 extensions/feature-orchestrator/README.md
     create mode 100644 extensions/feature-orchestrator/package-lock.json
     create mode 100644 extensions/feature-orchestrator/package.json
     create mode 100644 extensions/feature-orchestrator/src/dashboard.ts
     create mode 100644 extensions/feature-orchestrator/src/designReview.ts
     create mode 100644 extensions/feature-orchestrator/src/extension.ts
     create mode 100644 extensions/feature-orchestrator/src/featureDetail.ts
     create mode 100644 extensions/feature-orchestrator/src/tools.ts
     create mode 100644 extensions/feature-orchestrator/tsconfig.json
    
    diff --git a/extensions/feature-orchestrator/README.md b/extensions/feature-orchestrator/README.md
    new file mode 100644
    index 00000000..16df7a10
    --- /dev/null
    +++ b/extensions/feature-orchestrator/README.md
    @@ -0,0 +1,45 @@
    +# Feature Orchestrator Extension
    +
    +VS Code Chat Participant (`@orchestrator`) for AI-driven feature development across the Android Auth multi-repo project.
    +
    +## Usage
    +
    +In VS Code Copilot Chat (Agent Mode):
    +
    +```
    +@orchestrator implement IPC retry logic for broker communication
    +```
    +
    +Or use individual commands:
    +
    +```
    +@orchestrator /design add retry logic to IPC calls
    +@orchestrator /plan
    +@orchestrator /dispatch
    +@orchestrator /status
    +```
    +
    +## Flow
    +
    +1. `@orchestrator ` — runs the full flow: design → plan → dispatch
    +2. `@orchestrator /design ` — just write a design spec
    +3. `@orchestrator /plan` — break approved design into PBIs (uses ADO MCP)
    +4. `@orchestrator /dispatch` — dispatch PBIs to Copilot coding agent
    +5. `@orchestrator /status` — check agent PR status across repos
    +
    +## Development
    +
    +```bash
    +cd extensions/feature-orchestrator
    +npm install
    +npm run compile
    +```
    +
    +Then press F5 to launch the Extension Development Host.
    +
    +## Architecture
    +
    +- `participant.ts` — Chat Participant handler, routes commands to workflow steps
    +- `workflow.ts` — State machine: idle → designing → design_review → planning → plan_review → dispatching → monitoring → done
    +- `skills.ts` — Reads `.github/skills/` files and builds LLM prompts
    +- `tools.ts` — Wrappers for `gh` CLI, account switching, agent dispatch
    diff --git a/extensions/feature-orchestrator/package-lock.json b/extensions/feature-orchestrator/package-lock.json
    new file mode 100644
    index 00000000..4187e18f
    --- /dev/null
    +++ b/extensions/feature-orchestrator/package-lock.json
    @@ -0,0 +1,58 @@
    +{
    +  "name": "feature-orchestrator",
    +  "version": "0.2.0",
    +  "lockfileVersion": 3,
    +  "requires": true,
    +  "packages": {
    +    "": {
    +      "name": "feature-orchestrator",
    +      "version": "0.2.0",
    +      "devDependencies": {
    +        "@types/node": "^20.0.0",
    +        "@types/vscode": "^1.100.0",
    +        "typescript": "^5.5.0"
    +      },
    +      "engines": {
    +        "vscode": "^1.100.0"
    +      }
    +    },
    +    "node_modules/@types/node": {
    +      "version": "20.19.35",
    +      "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.35.tgz",
    +      "integrity": "sha512-Uarfe6J91b9HAUXxjvSOdiO2UPOKLm07Q1oh0JHxoZ1y8HoqxDAu3gVrsrOHeiio0kSsoVBt4wFrKOm0dKxVPQ==",
    +      "dev": true,
    +      "license": "MIT",
    +      "dependencies": {
    +        "undici-types": "~6.21.0"
    +      }
    +    },
    +    "node_modules/@types/vscode": {
    +      "version": "1.109.0",
    +      "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.109.0.tgz",
    +      "integrity": "sha512-0Pf95rnwEIwDbmXGC08r0B4TQhAbsHQ5UyTIgVgoieDe4cOnf92usuR5dEczb6bTKEp7ziZH4TV1TRGPPCExtw==",
    +      "dev": true,
    +      "license": "MIT"
    +    },
    +    "node_modules/typescript": {
    +      "version": "5.9.3",
    +      "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
    +      "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
    +      "dev": true,
    +      "license": "Apache-2.0",
    +      "bin": {
    +        "tsc": "bin/tsc",
    +        "tsserver": "bin/tsserver"
    +      },
    +      "engines": {
    +        "node": ">=14.17"
    +      }
    +    },
    +    "node_modules/undici-types": {
    +      "version": "6.21.0",
    +      "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
    +      "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
    +      "dev": true,
    +      "license": "MIT"
    +    }
    +  }
    +}
    diff --git a/extensions/feature-orchestrator/package.json b/extensions/feature-orchestrator/package.json
    new file mode 100644
    index 00000000..82942be1
    --- /dev/null
    +++ b/extensions/feature-orchestrator/package.json
    @@ -0,0 +1,119 @@
    +{
    +  "name": "feature-orchestrator",
    +  "displayName": "Android Auth Feature Orchestrator",
    +  "description": "AI-driven feature development orchestrator for the Android Auth multi-repo project. Dashboard + @orchestrator chat participant.",
    +  "version": "0.3.0",
    +  "publisher": "AzureAD",
    +  "engines": {
    +    "vscode": "^1.100.0"
    +  },
    +  "categories": [
    +    "AI"
    +  ],
    +  "activationEvents": ["onLanguage:markdown"],
    +  "main": "./out/extension.js",
    +  "contributes": {
    +    "viewsContainers": {
    +      "activitybar": [
    +        {
    +          "id": "feature-orchestrator",
    +          "title": "Feature Orchestrator",
    +          "icon": "$(rocket)"
    +        }
    +      ]
    +    },
    +    "views": {
    +      "feature-orchestrator": [
    +        {
    +          "type": "webview",
    +          "id": "orchestrator.dashboard",
    +          "name": "Dashboard"
    +        }
    +      ]
    +    },
    +    "commands": [
    +      {
    +        "command": "orchestrator.refreshDashboard",
    +        "title": "Refresh Dashboard",
    +        "icon": "$(refresh)",
    +        "category": "Orchestrator"
    +      },
    +      {
    +        "command": "orchestrator.newFeature",
    +        "title": "New Feature",
    +        "icon": "$(add)",
    +        "category": "Orchestrator"
    +      },
    +      {
    +        "command": "orchestrator.openFeatureDetail",
    +        "title": "Open Feature Detail",
    +        "category": "Orchestrator"
    +      },
    +      {
    +        "command": "orchestrator.submitDesignReview",
    +        "title": "Submit Review Comments (Current File)",
    +        "icon": "$(comment)",
    +        "category": "Orchestrator"
    +      },
    +      {
    +        "command": "orchestrator.submitAllReviews",
    +        "title": "Submit All Review Comments",
    +        "icon": "$(comment-discussion)",
    +        "category": "Orchestrator"
    +      },
    +      {
    +        "command": "orchestrator.clearReviewComments",
    +        "title": "Clear Review Comments (Current File)",
    +        "category": "Orchestrator"
    +      },
    +      {
    +        "command": "orchestrator.reviewComment.add",
    +        "title": "Add Comment"
    +      },
    +      {
    +        "command": "orchestrator.reviewComment.delete",
    +        "title": "Delete Comment",
    +        "icon": "$(trash)"
    +      }
    +    ],
    +    "menus": {
    +      "view/title": [
    +        {
    +          "command": "orchestrator.refreshDashboard",
    +          "when": "view == orchestrator.dashboard",
    +          "group": "navigation"
    +        },
    +        {
    +          "command": "orchestrator.newFeature",
    +          "when": "view == orchestrator.dashboard",
    +          "group": "navigation"
    +        }
    +      ],
    +      "comments/commentThread/context": [
    +        {
    +          "command": "orchestrator.reviewComment.add",
    +          "group": "inline",
    +          "when": "commentController == orchestrator.designReview"
    +        }
    +      ],
    +      "comments/comment/title": [
    +        {
    +          "command": "orchestrator.reviewComment.delete",
    +          "group": "inline",
    +          "when": "commentController == orchestrator.designReview && comment == reviewComment"
    +        }
    +      ]
    +    }
    +  },
    +  "scripts": {
    +    "vscode:prepublish": "npm run compile",
    +    "compile": "tsc -p ./",
    +    "watch": "tsc -watch -p ./",
    +    "lint": "eslint src --ext ts"
    +  },
    +  "devDependencies": {
    +    "@types/node": "^20.0.0",
    +    "@types/vscode": "^1.100.0",
    +    "typescript": "^5.5.0"
    +  }
    +}
    diff --git a/extensions/feature-orchestrator/src/dashboard.ts b/extensions/feature-orchestrator/src/dashboard.ts
    new file mode 100644
    index 00000000..ade3a1a0
    --- /dev/null
    +++ b/extensions/feature-orchestrator/src/dashboard.ts
    @@ -0,0 +1,395 @@
    +import * as vscode from 'vscode';
    +import * as fs from 'fs';
    +import * as path from 'path';
    +import * as os from 'os';
    +import { getAgentPRs, switchGhAccount } from './tools';
    +
    +interface FeatureState {
    +    id: string;
    +    name: string;
    +    prompt: string;
    +    step: string;
    +    designDocPath?: string;
    +    designPrUrl?: string;
    +    pbis: Array<{ adoId: number; title: string; targetRepo: string; status: string }>;
    +    agentSessions: Array<{ repo: string; prNumber: number; prUrl: string; status: string }>;
    +    startedAt: number;
    +    updatedAt: number;
    +    pendingAction?: {
    +        completedAgent: string;
    +        nextStep: string;
    +        timestamp: number;
    +    };
    +}
    +
    +interface OrchestratorState {
    +    version: number;
    +    features: FeatureState[];
    +    lastUpdated: number;
    +}
    +
    +interface AgentPr {
    +    repo: string;
    +    number: number;
    +    title: string;
    +    state: string;
    +    url: string;
    +    createdAt: string;
    +}
    +
    +export class DashboardViewProvider implements vscode.WebviewViewProvider {
    +    public static readonly viewType = 'orchestrator.dashboard';
    +
    +    private view?: vscode.WebviewView;
    +    private refreshInterval?: NodeJS.Timeout;
    +    private fileWatcher?: vscode.FileSystemWatcher;
    +
    +    constructor(private readonly context: vscode.ExtensionContext) {}
    +
    +    resolveWebviewView(
    +        webviewView: vscode.WebviewView,
    +        _context: vscode.WebviewViewResolveContext,
    +        _token: vscode.CancellationToken
    +    ): void {
    +        this.view = webviewView;
    +        webviewView.webview.options = { enableScripts: true };
    +
    +        webviewView.webview.onDidReceiveMessage(async (message) => {
    +            switch (message.command) {
    +                case 'refresh':
    +                    await this.refresh();
    +                    break;
    +                case 'openUrl':
    +                    vscode.env.openExternal(vscode.Uri.parse(message.url));
    +                    break;
    +                case 'openAgent': {
    +                    // Open a new chat with the custom agent (not the old @orchestrator participant)
    +                    await vscode.commands.executeCommand('workbench.action.chat.newChat');
    +                    const query = `@feature-orchestrator ${message.prompt || ''}`.trim();
    +                    vscode.commands.executeCommand('workbench.action.chat.open', { query });
    +                    break;
    +                }
    +                case 'openFeatureDetail':
    +                    vscode.commands.executeCommand('orchestrator.openFeatureDetail', message.featureId);
    +                    break;
    +                case 'removeFeature':
    +                    this.removeFeatureFromState(message.featureId);
    +                    await this.refresh();
    +                    break;
    +            }
    +        });
    +
    +        this.refresh();
    +
    +        // Auto-refresh every 30 seconds
    +        this.refreshInterval = setInterval(() => this.refresh(), 30000);
    +
    +        // Watch state file for changes (hooks write to it)
    +        const stateFilePath = this.getStateFilePath();
    +        if (stateFilePath) {
    +            const stateDir = path.dirname(stateFilePath);
    +            // Ensure the directory exists so the watcher can be set up
    +            if (!fs.existsSync(stateDir)) {
    +                fs.mkdirSync(stateDir, { recursive: true });
    +            }
    +            const pattern = new vscode.RelativePattern(
    +                vscode.Uri.file(stateDir),
    +                'state.json'
    +            );
    +            this.fileWatcher = vscode.workspace.createFileSystemWatcher(pattern);
    +            this.fileWatcher.onDidChange(() => {
    +                this.refresh();
    +                this.checkForPendingAction();
    +            });
    +            this.fileWatcher.onDidCreate(() => {
    +                this.refresh();
    +                this.checkForPendingAction();
    +            });
    +        }
    +
    +        webviewView.onDidDispose(() => {
    +            if (this.refreshInterval) { clearInterval(this.refreshInterval); }
    +            this.fileWatcher?.dispose();
    +        });
    +    }
    +
    +    async refresh(): Promise {
    +        if (!this.view) { return; }
    +
    +        const state = this.readStateFile();
    +        let agentPrs: AgentPr[] = [];
    +        try {
    +            agentPrs = await this.fetchAllAgentPrs();
    +        } catch { /* ignore */ }
    +
    +        this.view.webview.html = this.getHtml(state, agentPrs);
    +    }
    +
    +    /**
    +     * Check if a subagent just completed and show a clickable notification
    +     * with a button to proceed to the next pipeline step.
    +     *
    +     * The SubagentStop hook writes a `pendingAction` field to the active feature
    +     * in orchestrator-state.json. We consume it here and clear it after showing
    +     * the notification to avoid duplicate prompts.
    +     */
    +    private checkForPendingAction(): void {
    +        const state = this.readStateFile();
    +        const feature = state.features?.find(f => f.pendingAction);
    +        if (!feature?.pendingAction) { return; }
    +
    +        const action = feature.pendingAction;
    +        const staleMs = Date.now() - (action.timestamp || 0);
    +        if (staleMs > 60000) {
    +            // Ignore stale actions older than 60 seconds
    +            return;
    +        }
    +
    +        // Map next step to a notification message + button label + chat prompt
    +        const stepActions: Record = {
    +            'design_review': {
    +                message: `✅ Design spec written for "${feature.name}". Ready to plan PBIs.`,
    +                button: '📋 Plan PBIs',
    +                prompt: 'The design has been approved. Break it down into PBIs.',
    +            },
    +            'plan_review': {
    +                message: `✅ PBI plan created for "${feature.name}". Review and create in ADO.`,
    +                button: '✅ Create in ADO',
    +                prompt: 'Plan approved. Create the PBIs in ADO.',
    +            },
    +            'backlog_review': {
    +                message: `✅ PBIs backlogged in ADO for "${feature.name}". Ready to dispatch.`,
    +                button: '🚀 Dispatch to Agent',
    +                prompt: 'PBIs approved. Dispatch to Copilot coding agent.',
    +            },
    +            'monitoring': {
    +                message: `✅ PBIs dispatched for "${feature.name}". Agents are working.`,
    +                button: '📡 Check Status',
    +                prompt: 'Check agent status.',
    +            },
    +        };
    +
    +        const cfg = stepActions[action.nextStep];
    +        if (!cfg) { return; }
    +
    +        // Show VS Code notification with clickable button
    +        vscode.window.showInformationMessage(cfg.message, cfg.button).then(selection => {
    +            if (selection === cfg.button) {
    +                // Open chat with the next-step prompt pre-filled
    +                vscode.commands.executeCommand('workbench.action.chat.open', {
    +                    query: `@feature-orchestrator ${cfg.prompt}`,
    +                });
    +            }
    +        });
    +
    +        // Clear the pendingAction so we don't show duplicate notifications
    +        delete feature.pendingAction;
    +        const filePath = this.getStateFilePath();
    +        if (filePath) {
    +            state.lastUpdated = Date.now();
    +            fs.writeFileSync(filePath, JSON.stringify(state, null, 2), 'utf-8');
    +        }
    +    }
    +
    +    private getStateFilePath(): string | null {
    +        return path.join(os.homedir(), '.android-auth-orchestrator', 'state.json');
    +    }
    +
    +    private readStateFile(): OrchestratorState {
    +        const filePath = this.getStateFilePath();
    +        if (!filePath || !fs.existsSync(filePath)) {
    +            return { version: 1, features: [], lastUpdated: 0 };
    +        }
    +        try {
    +            return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
    +        } catch {
    +            return { version: 1, features: [], lastUpdated: 0 };
    +        }
    +    }
    +
    +    private removeFeatureFromState(featureId: string): void {
    +        const filePath = this.getStateFilePath();
    +        if (!filePath) { return; }
    +        const state = this.readStateFile();
    +        state.features = state.features.filter(f => f.id !== featureId);
    +        state.lastUpdated = Date.now();
    +        fs.writeFileSync(filePath, JSON.stringify(state, null, 2), 'utf-8');
    +    }
    +
    +    private async fetchAllAgentPrs(): Promise {
    +        const repos = [
    +            { slug: 'AzureAD/microsoft-authentication-library-common-for-android', label: 'common' },
    +            { slug: 'AzureAD/microsoft-authentication-library-for-android', label: 'msal' },
    +            { slug: 'AzureAD/azure-activedirectory-library-for-android', label: 'adal' },
    +        ];
    +        const allPrs: AgentPr[] = [];
    +        for (const repo of repos) {
    +            try {
    +                await switchGhAccount(repo.slug);
    +                const json = await getAgentPRs(repo.slug);
    +                const prs = JSON.parse(json);
    +                for (const pr of prs) {
    +                    allPrs.push({
    +                        repo: repo.label, number: pr.number, title: pr.title,
    +                        state: pr.state, url: pr.url, createdAt: pr.createdAt,
    +                    });
    +                }
    +            } catch { /* skip */ }
    +        }
    +        return allPrs;
    +    }
    +
    +    private getHtml(state: OrchestratorState, agentPrs: AgentPr[]): string {
    +        const stepConfig: Record = {
    +            'idle': { icon: '⏳', label: 'Ready', nextAgent: 'design-author', nextLabel: '▶ Start Design', nextPrompt: '' },
    +            'designing': { icon: '📝', label: 'Writing Design...' },
    +            'design_review': { icon: '👀', label: 'Awaiting Design Approval', nextAgent: 'feature-planner', nextLabel: '📋 Approve → Plan PBIs', nextPrompt: 'The design spec has been approved. Break it down into PBIs.' },
    +            'planning': { icon: '📋', label: 'Planning PBIs...' },
    +            'plan_review': { icon: '👀', label: 'Awaiting Plan Approval', nextAgent: 'pbi-creator', nextLabel: '✅ Approve → Backlog in ADO', nextPrompt: 'Plan approved. Create the PBIs in Azure DevOps.' },
    +            'backlogging': { icon: '📝', label: 'Adding to Backlog...' },
    +            'backlog_review': { icon: '👀', label: 'PBIs Backlogged — Review', nextAgent: 'agent-dispatcher', nextLabel: '🚀 Dispatch to Agent', nextPrompt: 'PBIs approved. Dispatch to Copilot coding agent.' },
    +            'dispatching': { icon: '🚀', label: 'Dispatching...' },
    +            'monitoring': { icon: '📡', label: 'Agents Working', nextAgent: 'agent-monitor', nextLabel: '👁 Check Status', nextPrompt: 'Check the status of all agent PRs.' },
    +            'done': { icon: '✅', label: 'Complete' },
    +        };
    +
    +        // Normalize step names: map aliases/past-tense/legacy names to canonical keys
    +        const stepAliases: Record = {
    +            'designed': 'design_review', 'design_complete': 'design_review',
    +            'planned': 'plan_review', 'plan_complete': 'plan_review',
    +            'backlogged': 'backlog_review', 'created': 'backlog_review', 'creating': 'backlogging', 'create_review': 'backlog_review',
    +            'dispatched': 'monitoring', 'dispatch_complete': 'monitoring',
    +            'complete': 'done', 'completed': 'done',
    +        };
    +
    +        const stateColors: Record = {
    +            'OPEN': '#238636', 'MERGED': '#8957e5', 'CLOSED': '#da3633',
    +        };
    +
    +        const featuresHtml = state.features.length === 0
    +            ? `
    +
    🚀
    +

    No features tracked

    +

    Click + above or type @feature-orchestrator in chat

    +
    ` + : state.features.map(f => { + const normalizedStep = stepAliases[f.step] || f.step; + const cfg = stepConfig[normalizedStep] || stepConfig['idle']; + const progressSteps = ['designing', 'design_review', 'planning', 'plan_review', 'backlogging', 'backlog_review', 'dispatching', 'monitoring', 'done']; + const currentIdx = progressSteps.indexOf(normalizedStep); + + const progressDots = progressSteps.map((_s, i) => + `
    ` + ).join(''); + + const actionBtn = cfg.nextAgent + ? `` + : `
    ${cfg.icon} ${cfg.label}
    `; + + // Artifact summary counts + const artifacts = (f as any).artifacts; + const pbiCount = artifacts?.pbis?.length || f.pbis?.length || 0; + const prCount = artifacts?.agentPrs?.length || f.agentSessions?.length || 0; + const hasDesign = !!artifacts?.design || !!f.designDocPath; + const artifactPills: string[] = []; + if (hasDesign) { artifactPills.push('📄 Design'); } + if (pbiCount > 0) { artifactPills.push(`📋 ${pbiCount} PBI${pbiCount > 1 ? 's' : ''}`); } + if (prCount > 0) { artifactPills.push(`🤖 ${prCount} PR${prCount > 1 ? 's' : ''}`); } + const artifactSummary = artifactPills.length > 0 + ? `
    ${artifactPills.join(' · ')}
    ` + : ''; + + return ` +
    +
    + ${cfg.icon} + ${this.escapeHtml(f.name)} + +
    +
    ${progressDots}
    + ${actionBtn} + ${artifactSummary} +
    ${this.timeAgo(f.updatedAt)}
    +
    `; + }).join(''); + + const prsHtml = agentPrs.length === 0 + ? '

    No agent PRs found

    ' + : agentPrs.slice(0, 10).map(pr => ` +
    + + ${pr.repo} #${pr.number} + ${this.escapeHtml(pr.title).substring(0, 30)} +
    `).join(''); + + return ` + + +

    Features

    + ${featuresHtml} +
    +

    Agent PRs

    + ${prsHtml} +
    + + +`; + } + + private escapeHtml(s: string): string { + return s.replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); + } + + private escapeAttr(s: string): string { + return s.replace(/'/g, "\\'").replace(/"/g, '"'); + } + + private timeAgo(ts: number): string { + if (!ts) { return ''; } + const s = Math.floor((Date.now() - ts) / 1000); + if (s < 60) { return 'just now'; } + if (s < 3600) { return `${Math.floor(s / 60)}m ago`; } + if (s < 86400) { return `${Math.floor(s / 3600)}h ago`; } + return `${Math.floor(s / 86400)}d ago`; + } +} diff --git a/extensions/feature-orchestrator/src/designReview.ts b/extensions/feature-orchestrator/src/designReview.ts new file mode 100644 index 00000000..924789ae --- /dev/null +++ b/extensions/feature-orchestrator/src/designReview.ts @@ -0,0 +1,484 @@ +import * as vscode from 'vscode'; +import * as fs from 'fs'; +import * as path from 'path'; + +/** + * Design Review v5 — Single reviews.json, scoped submit, proper state management. + * + * Architecture: + * - Comments stored in `.github/design-reviews/reviews.json` (dict keyed by relative spec path) + * - Status bar button submits comments for the CURRENT file only + * - Command palette "Submit All" submits all pending reviews + * - Agent removes entries from reviews.json after addressing them + * - Extension clears editor comments after submit + */ + +// ─── Types ──────────────────────────────────────────────────────── + +interface ReviewComment { + line: number; + text: string; + lineContent: string; +} + +interface ReviewsFile { + reviews: Record; // key = relative spec path +} + +// ─── Constants ─────────────────────────────────────────────────── + +const REVIEWS_DIR = '.github/design-reviews'; +const REVIEWS_FILENAME = 'reviews.json'; + +// ─── Main Controller ───────────────────────────────────────────── + +export class DesignReviewController implements vscode.Disposable { + private commentController: vscode.CommentController; + private allThreads: vscode.CommentThread[] = []; + private loadedKeys = new Set(); // "relPath::line::text" prevents duplicate restore + private statusBarItem: vscode.StatusBarItem; + private disposables: vscode.Disposable[] = []; + + constructor(private readonly context: vscode.ExtensionContext) { + this.commentController = vscode.comments.createCommentController( + 'orchestrator.designReview', + 'Design Review' + ); + this.commentController.commentingRangeProvider = { + provideCommentingRanges(document: vscode.TextDocument): vscode.Range[] { + if (document.languageId !== 'markdown') { return []; } + var ranges: vscode.Range[] = []; + for (var i = 0; i < document.lineCount; i++) { + ranges.push(new vscode.Range(i, 0, i, 0)); + } + return ranges; + } + }; + this.disposables.push(this.commentController); + + this.statusBarItem = vscode.window.createStatusBarItem( + vscode.StatusBarAlignment.Right, 1000 + ); + this.statusBarItem.command = 'orchestrator.submitDesignReview'; + this.disposables.push(this.statusBarItem); + } + + register(): void { + this.disposables.push( + vscode.commands.registerCommand('orchestrator.reviewComment.add', (reply: vscode.CommentReply) => { + this.addComment(reply); + }) + ); + this.disposables.push( + vscode.commands.registerCommand('orchestrator.reviewComment.delete', (comment: EditorComment) => { + this.deleteComment(comment); + }) + ); + this.disposables.push( + vscode.commands.registerCommand('orchestrator.submitDesignReview', () => { + this.submitCurrentFile(); + }) + ); + this.disposables.push( + vscode.commands.registerCommand('orchestrator.submitAllReviews', () => { + this.submitAll(); + }) + ); + this.disposables.push( + vscode.commands.registerCommand('orchestrator.clearReviewComments', () => { + this.clearCurrentFile(); + }) + ); + this.disposables.push( + vscode.languages.registerCodeLensProvider( + { language: 'markdown' }, + new ReviewCodeLensProvider(this) + ) + ); + this.disposables.push( + vscode.window.onDidChangeActiveTextEditor(() => this.updateStatusBar()) + ); + this.disposables.push( + vscode.workspace.onDidOpenTextDocument(doc => { + if (doc.languageId === 'markdown') { this.restoreComments(doc); } + }) + ); + + // Restore for already-open docs + for (var doc of vscode.workspace.textDocuments) { + if (doc.languageId === 'markdown') { this.restoreComments(doc); } + } + + this.updateStatusBar(); + this.context.subscriptions.push(...this.disposables); + } + + // ─── Reviews file I/O ──────────────────────────────────────── + + private getReviewsFilePath(): string | null { + var root = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath; + if (!root) { return null; } + var dir = path.join(root, REVIEWS_DIR); + if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } + return path.join(dir, REVIEWS_FILENAME); + } + + private readReviews(): ReviewsFile { + var filePath = this.getReviewsFilePath(); + if (!filePath || !fs.existsSync(filePath)) { + return { reviews: {} }; + } + try { + return JSON.parse(fs.readFileSync(filePath, 'utf-8')); + } catch { + return { reviews: {} }; + } + } + + private writeReviews(data: ReviewsFile): void { + var filePath = this.getReviewsFilePath(); + if (!filePath) { return; } + // Clean up empty entries + var keys = Object.keys(data.reviews); + for (var i = 0; i < keys.length; i++) { + if (data.reviews[keys[i]].length === 0) { + delete data.reviews[keys[i]]; + } + } + if (Object.keys(data.reviews).length === 0) { + // Delete file if no reviews remain + if (fs.existsSync(filePath)) { fs.unlinkSync(filePath); } + } else { + fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf-8'); + } + } + + private getRelativePath(uri: vscode.Uri): string { + var root = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath || ''; + return path.relative(root, uri.fsPath).replace(/\\/g, '/'); + } + + // ─── Persist current file's comments to reviews.json ───────── + + private persistForFile(docUri: vscode.Uri): void { + var relPath = this.getRelativePath(docUri); + var data = this.readReviews(); + var comments: ReviewComment[] = []; + + for (var i = 0; i < this.allThreads.length; i++) { + var thread = this.allThreads[i]; + if (thread.uri.toString() !== docUri.toString()) { continue; } + for (var j = 0; j < thread.comments.length; j++) { + var c = thread.comments[j] as EditorComment; + comments.push({ + line: c.line, + text: typeof c.body === 'string' ? c.body : (c.body as vscode.MarkdownString).value, + lineContent: c.lineContent, + }); + } + } + + data.reviews[relPath] = comments; + this.writeReviews(data); + } + + // ─── Add comment ───────────────────────────────────────────── + + private addComment(reply: vscode.CommentReply): void { + var thread = reply.thread; + var doc = vscode.workspace.textDocuments.find( + function(d) { return d.uri.toString() === thread.uri.toString(); } + ); + var line = thread.range?.start.line ?? 0; + var lineContent = doc ? doc.lineAt(line).text.trim() : ''; + + var comment = new EditorComment(reply.text, line, lineContent); + + thread.comments = [...thread.comments, comment]; + thread.collapsibleState = vscode.CommentThreadCollapsibleState.Collapsed; + thread.label = thread.comments.length + ' comment' + (thread.comments.length > 1 ? 's' : ''); + + if (this.allThreads.indexOf(thread) === -1) { + this.allThreads.push(thread); + } + + var relPath = this.getRelativePath(thread.uri); + this.loadedKeys.add(relPath + '::' + line + '::' + reply.text); + + this.persistForFile(thread.uri); + this.updateStatusBar(); + } + + // ─── Delete comment ────────────────────────────────────────── + + private deleteComment(commentToDelete: EditorComment): void { + for (var i = 0; i < this.allThreads.length; i++) { + var thread = this.allThreads[i]; + var filtered = thread.comments.filter(function(c) { + return c !== commentToDelete; + }); + + if (filtered.length < thread.comments.length) { + var relPath = this.getRelativePath(thread.uri); + this.loadedKeys.delete(relPath + '::' + commentToDelete.line + '::' + + (typeof commentToDelete.body === 'string' ? commentToDelete.body : '')); + + if (filtered.length === 0) { + var uri = thread.uri; + thread.dispose(); + this.allThreads.splice(i, 1); + this.persistForFile(uri); + } else { + thread.comments = filtered; + thread.label = filtered.length + ' comment' + (filtered.length > 1 ? 's' : ''); + this.persistForFile(thread.uri); + } + this.updateStatusBar(); + return; + } + } + } + + // ─── Restore from reviews.json ─────────────────────────────── + + private restoreComments(document: vscode.TextDocument): void { + var data = this.readReviews(); + var relPath = this.getRelativePath(document.uri); + var comments = data.reviews[relPath]; + if (!comments || comments.length === 0) { return; } + + for (var i = 0; i < comments.length; i++) { + var rc = comments[i]; + var key = relPath + '::' + rc.line + '::' + rc.text; + if (this.loadedKeys.has(key)) { continue; } + this.loadedKeys.add(key); + + var line = Math.min(rc.line, document.lineCount - 1); + var thread = this.commentController.createCommentThread( + document.uri, + new vscode.Range(line, 0, line, 0), + [] + ); + + var comment = new EditorComment(rc.text, rc.line, rc.lineContent); + thread.comments = [comment]; + thread.collapsibleState = vscode.CommentThreadCollapsibleState.Collapsed; + thread.label = '1 comment'; + this.allThreads.push(thread); + } + + this.updateStatusBar(); + } + + // ─── Submit: current file only ─────────────────────────────── + + private async submitCurrentFile(): Promise { + var editor = vscode.window.activeTextEditor; + if (!editor) { + vscode.window.showInformationMessage('Open a markdown file first.'); + return; + } + + var relPath = this.getRelativePath(editor.document.uri); + var data = this.readReviews(); + var comments = data.reviews[relPath]; + + if (!comments || comments.length === 0) { + vscode.window.showInformationMessage( + 'No review comments on this file. Click the + icon in the gutter to add.' + ); + return; + } + + // Build short prompt — comments stay in reviews.json for the agent to read + var prompt = 'Use the design-reviewer skill on `' + relPath + '`.'; + + // Clear editor comments for this file + this.clearThreadsForUri(editor.document.uri); + this.updateStatusBar(); + + // Open chat + await this.openChat(prompt); + } + + // ─── Submit: all files ─────────────────────────────────────── + + private async submitAll(): Promise { + var data = this.readReviews(); + var specPaths = Object.keys(data.reviews); + + if (specPaths.length === 0) { + vscode.window.showInformationMessage('No pending review comments.'); + return; + } + + var prompt = 'Use the design-reviewer skill.'; + + // Clear all editor comments + for (var i = 0; i < this.allThreads.length; i++) { + this.allThreads[i].dispose(); + } + this.allThreads = []; + this.loadedKeys.clear(); + this.updateStatusBar(); + + await this.openChat(prompt); + } + + // ─── Clear: current file ───────────────────────────────────── + + private clearCurrentFile(): void { + var editor = vscode.window.activeTextEditor; + if (!editor) { return; } + + var docUri = editor.document.uri; + this.clearThreadsForUri(docUri); + + // Remove from reviews.json + var relPath = this.getRelativePath(docUri); + var data = this.readReviews(); + delete data.reviews[relPath]; + this.writeReviews(data); + + this.updateStatusBar(); + vscode.window.showInformationMessage('Review comments cleared for this file.'); + } + + // ─── Helpers ───────────────────────────────────────────────── + + private clearThreadsForUri(docUri: vscode.Uri): void { + var relPath = this.getRelativePath(docUri); + var remaining: vscode.CommentThread[] = []; + + for (var i = 0; i < this.allThreads.length; i++) { + var thread = this.allThreads[i]; + if (thread.uri.toString() === docUri.toString()) { + // Remove loaded keys for this thread's comments + for (var j = 0; j < thread.comments.length; j++) { + var c = thread.comments[j] as EditorComment; + var text = typeof c.body === 'string' ? c.body : ''; + this.loadedKeys.delete(relPath + '::' + c.line + '::' + text); + } + thread.dispose(); + } else { + remaining.push(thread); + } + } + this.allThreads = remaining; + } + + private async openChat(prompt: string): Promise { + try { + await vscode.commands.executeCommand('workbench.action.chat.open', { + query: prompt, + }); + } catch { + await vscode.env.clipboard.writeText(prompt); + await vscode.commands.executeCommand('workbench.action.chat.open'); + vscode.window.showInformationMessage( + 'Review prompt copied to clipboard. Paste (Ctrl+V) in chat and send.' + ); + } + } + + // ─── Status Bar ────────────────────────────────────────────── + + private updateStatusBar(): void { + var editor = vscode.window.activeTextEditor; + if (editor && editor.document.languageId === 'markdown') { + var count = this.getCommentCountForUri(editor.document.uri); + if (count > 0) { + this.statusBarItem.text = '$(comment-discussion) ' + count + + ' Review Comment' + (count > 1 ? 's' : '') + ' \u2014 Click to Submit'; + this.statusBarItem.backgroundColor = new vscode.ThemeColor('statusBarItem.warningBackground'); + this.statusBarItem.tooltip = 'Submit review comments for this file to chat'; + } else { + this.statusBarItem.text = '$(comment) Design Review'; + this.statusBarItem.backgroundColor = undefined; + this.statusBarItem.tooltip = 'Add review comments using the + icon in the gutter'; + } + this.statusBarItem.show(); + } else { + this.statusBarItem.hide(); + } + } + + private getCommentCountForUri(docUri: vscode.Uri): number { + var count = 0; + for (var i = 0; i < this.allThreads.length; i++) { + if (this.allThreads[i].uri.toString() === docUri.toString()) { + count += this.allThreads[i].comments.length; + } + } + return count; + } + + getTotalCommentCount(): number { + var count = 0; + for (var i = 0; i < this.allThreads.length; i++) { + count += this.allThreads[i].comments.length; + } + return count; + } + + getActiveFileCommentCount(): number { + var editor = vscode.window.activeTextEditor; + if (!editor) { return 0; } + return this.getCommentCountForUri(editor.document.uri); + } + + dispose(): void { + for (var i = 0; i < this.disposables.length; i++) { this.disposables[i].dispose(); } + for (var i = 0; i < this.allThreads.length; i++) { this.allThreads[i].dispose(); } + } +} + +// ─── Editor Comment class ──────────────────────────────────────── + +class EditorComment implements vscode.Comment { + body: string | vscode.MarkdownString; + mode = vscode.CommentMode.Preview; + author: vscode.CommentAuthorInformation = { name: 'You' }; + contextValue = 'reviewComment'; + + constructor( + body: string, + public readonly line: number, + public readonly lineContent: string, + ) { + this.body = body; + } +} + +// ─── CodeLens Provider ─────────────────────────────────────────── + +class ReviewCodeLensProvider implements vscode.CodeLensProvider { + constructor(private readonly controller: DesignReviewController) {} + + provideCodeLenses(document: vscode.TextDocument): vscode.CodeLens[] { + if (document.languageId !== 'markdown') { return []; } + + var count = this.controller.getActiveFileCommentCount(); + var topRange = new vscode.Range(0, 0, 0, 0); + var lenses: vscode.CodeLens[] = []; + + if (count > 0) { + lenses.push(new vscode.CodeLens(topRange, { + title: '\u{1F4AC} ' + count + ' review comment' + (count > 1 ? 's' : '') + + ' \u2014 Submit Review to Chat', + command: 'orchestrator.submitDesignReview', + })); + lenses.push(new vscode.CodeLens(topRange, { + title: '\u{1F5D1} Clear Comments', + command: 'orchestrator.clearReviewComments', + })); + } else { + lenses.push(new vscode.CodeLens(topRange, { + title: '\u{1F4AC} Add review comments using the + icon in the gutter', + command: '', + })); + } + + return lenses; + } +} diff --git a/extensions/feature-orchestrator/src/extension.ts b/extensions/feature-orchestrator/src/extension.ts new file mode 100644 index 00000000..7758f806 --- /dev/null +++ b/extensions/feature-orchestrator/src/extension.ts @@ -0,0 +1,86 @@ +import * as vscode from 'vscode'; +import { DashboardViewProvider } from './dashboard'; +import { DesignReviewController } from './designReview'; +import { FeatureDetailPanel } from './featureDetail'; + +export function activate(context: vscode.ExtensionContext) { + // Register the sidebar dashboard (reads from orchestrator-state.json) + const dashboardProvider = new DashboardViewProvider(context); + context.subscriptions.push( + vscode.window.registerWebviewViewProvider( + DashboardViewProvider.viewType, + dashboardProvider + ) + ); + + // Register the design review commenting system + const designReview = new DesignReviewController(context); + designReview.register(); + + // Commands + context.subscriptions.push( + vscode.commands.registerCommand('orchestrator.refreshDashboard', () => { + dashboardProvider.refresh(); + }) + ); + + context.subscriptions.push( + vscode.commands.registerCommand('orchestrator.newFeature', async () => { + const panel = vscode.window.createWebviewPanel( + 'orchestrator.newFeature', 'New Feature', + vscode.ViewColumn.Active, { enableScripts: true } + ); + panel.webview.html = getNewFeatureHtml(); + panel.webview.onDidReceiveMessage(async (message) => { + if (message.command === 'submit') { + panel.dispose(); + // Open the custom agent (not the old @orchestrator participant) + await vscode.commands.executeCommand('workbench.action.chat.newChat'); + vscode.commands.executeCommand('workbench.action.chat.open', { + query: `@feature-orchestrator ${message.prompt}`, + }); + } + }); + }) + ); + + context.subscriptions.push( + vscode.commands.registerCommand('orchestrator.openFeatureDetail', (featureId: string) => { + FeatureDetailPanel.show(context, featureId); + }) + ); + + console.log('Feature Orchestrator extension activated'); +} + +function getNewFeatureHtml(): string { + return ` + +

    🚀 New Feature

    +

    Describe the feature. The design-author agent will research the codebase and create a detailed design spec.

    + + +
    + Examples:
    + Add retry logic to IPC calls · Implement certificate-based auth · Add PRT latency telemetry +
    + +`; +} + +export function deactivate() {} diff --git a/extensions/feature-orchestrator/src/featureDetail.ts b/extensions/feature-orchestrator/src/featureDetail.ts new file mode 100644 index 00000000..dc4e1cec --- /dev/null +++ b/extensions/feature-orchestrator/src/featureDetail.ts @@ -0,0 +1,742 @@ +import * as vscode from 'vscode'; +import * as fs from 'fs'; +import * as path from 'path'; +import * as os from 'os'; +import { runCommand, switchGhAccount } from './tools'; + +/** + * Artifact types that can be tracked per feature. + */ +export interface DesignArtifact { + docPath: string; // workspace-relative path to the design doc + prUrl?: string; // ADO PR URL for the design review + status: 'draft' | 'in-review' | 'approved'; +} + +export interface PbiArtifact { + adoId: number; // AB# work item ID + title: string; + targetRepo: string; // e.g. "AzureAD/microsoft-authentication-library-common-for-android" + module: string; // e.g. "common", "msal", "broker" + adoUrl: string; // full ADO URL + status: 'new' | 'committed' | 'active' | 'resolved' | 'closed'; + priority?: number; + dependsOn?: number[]; // AB# IDs this PBI depends on + agentPr?: AgentPrArtifact; // linked PR from coding agent +} + +export interface AgentPrArtifact { + repo: string; + prNumber: number; + prUrl: string; + status: 'open' | 'merged' | 'closed' | 'draft'; + title?: string; +} + +export interface FeatureArtifacts { + design?: DesignArtifact; + pbis: PbiArtifact[]; + agentPrs: AgentPrArtifact[]; +} + +const ADO_ORG = 'IdentityDivision'; +const ADO_PROJECT = 'Engineering'; + +/** + * Opens a detail panel for a specific feature, showing all tracked artifacts. + */ +export class FeatureDetailPanel { + public static readonly viewType = 'orchestrator.featureDetail'; + private static panels: Map = new Map(); + + static show(context: vscode.ExtensionContext, featureId: string): void { + // Reuse existing panel for this feature if open + const existing = FeatureDetailPanel.panels.get(featureId); + if (existing) { + existing.reveal(vscode.ViewColumn.One); + return; + } + + const state = FeatureDetailPanel.readState(); + const feature = state.features?.find((f: any) => f.id === featureId); + if (!feature) { + vscode.window.showWarningMessage(`Feature "${featureId}" not found in state.`); + return; + } + + const panel = vscode.window.createWebviewPanel( + FeatureDetailPanel.viewType, + `Feature: ${feature.name}`, + vscode.ViewColumn.One, + { enableScripts: true, retainContextWhenHidden: true } + ); + + let autoRefreshInterval: NodeJS.Timeout | undefined; + + FeatureDetailPanel.panels.set(featureId, panel); + panel.onDidDispose(() => { + FeatureDetailPanel.panels.delete(featureId); + if (autoRefreshInterval) { clearInterval(autoRefreshInterval); } + }); + + panel.webview.html = FeatureDetailPanel.getHtml(feature); + + panel.webview.onDidReceiveMessage(async (message) => { + switch (message.command) { + case 'openUrl': + vscode.env.openExternal(vscode.Uri.parse(message.url)); + break; + case 'openFile': { + const folders = vscode.workspace.workspaceFolders; + if (folders) { + const uri = vscode.Uri.file(path.join(folders[0].uri.fsPath, message.path)); + vscode.commands.executeCommand('vscode.open', uri); + } + break; + } + case 'continueInChat': { + // Build a context-rich prompt summarizing the feature's current state + const freshState2 = FeatureDetailPanel.readState(); + const feat = freshState2.features?.find((f: any) => f.id === featureId); + let contextPrompt = `Continue working on feature: "${feat?.name || 'Unknown'}".\n`; + contextPrompt += `Current step: ${feat?.step || 'unknown'}.\n`; + // Include PBI context if available + const pbiList = feat?.artifacts?.pbis || feat?.pbis || []; + if (pbiList.length > 0) { + contextPrompt += `PBIs: ${pbiList.map((p: any) => `${p.id || 'AB#' + p.adoId} (${p.title})`).join(', ')}.\n`; + } + // Include design doc path if available + const designPath = feat?.artifacts?.design?.docPath || feat?.designDocPath; + if (designPath) { + contextPrompt += `Design doc: ${designPath}\n`; + } + await vscode.commands.executeCommand('workbench.action.chat.newChat'); + vscode.commands.executeCommand('workbench.action.chat.open', { + query: `@feature-orchestrator ${contextPrompt}`, + }); + break; + } + case 'refresh': + // Fetch live statuses from GitHub, update state, and re-render + panel.webview.postMessage({ command: 'refreshing', status: true }); + try { + await FeatureDetailPanel.refreshLiveStatuses(featureId); + } catch (e) { + console.error('[FeatureDetail] Live refresh error:', e); + } + { + const freshState = FeatureDetailPanel.readState(); + const freshFeature = freshState.features?.find((f: any) => f.id === featureId); + if (freshFeature) { + panel.webview.html = FeatureDetailPanel.getHtml(freshFeature); + } + } + break; + } + }); + + // Periodic auto-refresh every 5 minutes (fetches live PR + PBI statuses) + autoRefreshInterval = setInterval(async () => { + try { + await FeatureDetailPanel.refreshLiveStatuses(featureId); + const updated = FeatureDetailPanel.readState(); + const updatedFeature = updated.features?.find((f: any) => f.id === featureId); + if (updatedFeature) { + panel.webview.html = FeatureDetailPanel.getHtml(updatedFeature); + } + } catch { /* silent */ } + }, 300000); // 5 minutes + + // Watch for state file changes + const stateDir = path.join(os.homedir(), '.android-auth-orchestrator'); + if (!fs.existsSync(stateDir)) { + fs.mkdirSync(stateDir, { recursive: true }); + } + const pattern = new vscode.RelativePattern(vscode.Uri.file(stateDir), 'state.json'); + const watcher = vscode.workspace.createFileSystemWatcher(pattern); + watcher.onDidChange(() => { + const updated = FeatureDetailPanel.readState(); + const updatedFeature = updated.features?.find((f: any) => f.id === featureId); + if (updatedFeature) { + panel.webview.html = FeatureDetailPanel.getHtml(updatedFeature); + } + }); + panel.onDidDispose(() => watcher.dispose()); + } + + private static readState(): any { + const filePath = path.join(os.homedir(), '.android-auth-orchestrator', 'state.json'); + if (!fs.existsSync(filePath)) { return { features: [] }; } + try { return JSON.parse(fs.readFileSync(filePath, 'utf-8')); } + catch { return { features: [] }; } + } + + private static writeState(state: any): void { + const dir = path.join(os.homedir(), '.android-auth-orchestrator'); + if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } + state.lastUpdated = Date.now(); + fs.writeFileSync(path.join(dir, 'state.json'), JSON.stringify(state, null, 2), 'utf-8'); + } + + /** + * Fetch live PR statuses from GitHub and PBI statuses from ADO, + * then write updated data back to state.json. + */ + private static async refreshLiveStatuses(featureId: string): Promise { + const state = FeatureDetailPanel.readState(); + const feature = state.features?.find((f: any) => f.id === featureId); + if (!feature) { return; } + + let changed = false; + + // Repo slug mapping: short names → full GitHub slugs + const repoSlugs: Record = { + 'common': 'AzureAD/microsoft-authentication-library-common-for-android', + 'msal': 'AzureAD/microsoft-authentication-library-for-android', + 'adal': 'AzureAD/azure-activedirectory-library-for-android', + 'broker': 'identity-authnz-teams/ad-accounts-for-android', + }; + + // --- Refresh Agent PRs from GitHub --- + const agentPrs = feature.artifacts?.agentPrs || []; + if (agentPrs.length > 0) { + // Group PRs by org to minimize gh account switches + const prsByOrg: Record = {}; + for (const pr of agentPrs) { + const repoSlug = repoSlugs[pr.repo] || pr.repo; + const org = repoSlug.split('/')[0] || 'AzureAD'; + if (!prsByOrg[org]) { prsByOrg[org] = []; } + prsByOrg[org].push({ pr, repoSlug }); + } + + for (const [org, prs] of Object.entries(prsByOrg)) { + try { + // Switch account once per org + await switchGhAccount(`${org}/dummy`); + } catch { + console.error(`[FeatureDetail] Failed to switch gh account for ${org}`); + continue; + } + + for (const { pr, repoSlug } of prs) { + try { + const prNumber = pr.prNumber || pr.number; + if (!prNumber) { continue; } + + const json = await runCommand( + `gh pr view ${prNumber} --repo "${repoSlug}" --json state,title,url,comments,reviews`, + undefined, 15000 // 15s timeout + ); + const prData = JSON.parse(json); + + const stateMap: Record = { + 'OPEN': 'open', 'MERGED': 'merged', 'CLOSED': 'closed', + }; + const newStatus = stateMap[prData.state] || prData.state?.toLowerCase() || pr.status; + + if (newStatus !== pr.status || prData.title !== pr.title) { + pr.status = newStatus; + if (prData.title) { pr.title = prData.title; } + if (prData.url) { pr.prUrl = prData.url; } + changed = true; + } + + // Count review comments (from reviews + comments) + const reviews = prData.reviews || []; + const comments = prData.comments || []; + const totalComments = reviews.length + comments.length; + // Count resolved: reviews/comments with state RESOLVED or DISMISSED + const resolvedComments = reviews.filter((r: any) => r.state === 'APPROVED' || r.state === 'DISMISSED').length; + if (pr.totalComments !== totalComments || pr.resolvedComments !== resolvedComments) { + pr.totalComments = totalComments; + pr.resolvedComments = resolvedComments; + changed = true; + } + } catch (e) { + console.error(`[FeatureDetail] Failed to refresh PR #${pr.prNumber}:`, e); + } + } + } + } + + // --- Refresh PBI statuses from ADO via az CLI --- + const pbis = feature.artifacts?.pbis || []; + if (pbis.length > 0) { + try { + // Check if az CLI is available and authenticated (quick test) + await runCommand('az account show --only-show-errors -o none', undefined, 5000); + + for (const pbi of pbis) { + try { + const adoId = pbi.adoId; + if (!adoId) { continue; } + + const json = await runCommand( + `az boards work-item show --id ${adoId} --org "https://dev.azure.com/IdentityDivision" --only-show-errors -o json`, + undefined, 15000 // 15s timeout + ); + const wiData = JSON.parse(json); + const fields = wiData.fields || {}; + + const newState = fields['System.State']; + if (newState && newState !== pbi.status) { + pbi.status = newState; + changed = true; + } + // Also update title if it changed + const newTitle = fields['System.Title']; + if (newTitle && newTitle !== pbi.title) { + pbi.title = newTitle; + changed = true; + } + } catch (e) { + console.error(`[FeatureDetail] Failed to refresh PBI AB#${pbi.adoId}:`, e); + } + } + + // Also sync to legacy pbis array + if (changed && feature.pbis) { + for (const legacyPbi of feature.pbis) { + const artPbi = pbis.find((p: any) => p.adoId === legacyPbi.adoId); + if (artPbi) { + legacyPbi.status = artPbi.status; + legacyPbi.title = artPbi.title; + } + } + } + } catch { + // az CLI not available or not authenticated — skip PBI refresh silently + console.log('[FeatureDetail] az CLI not available, skipping PBI status refresh'); + } + } + + if (changed) { + feature.updatedAt = Date.now(); + FeatureDetailPanel.writeState(state); + } + } + + private static getHtml(feature: any): string { + const artifacts: FeatureArtifacts = feature.artifacts || { pbis: [], agentPrs: [] }; + const design = artifacts.design; + const rawPbis: any[] = artifacts.pbis || feature.pbis || []; + const agentPrs = artifacts.agentPrs || feature.agentSessions || []; + + // Normalize PBI fields — state may use different field names depending on how it was written + const pbis = rawPbis.map((p: any) => { + // adoId can be: p.adoId (number), p.id ("AB#12345"), or missing + let adoId: string | number = p.adoId || p.id || '?'; + if (typeof adoId === 'string') { + adoId = adoId.replace(/^AB#/, ''); // strip "AB#" prefix for URL building + } + // dependsOn can be: array of numbers, array of "AB#NNN" strings, or missing + let dependsOn: string[] = []; + if (Array.isArray(p.dependsOn)) { + dependsOn = p.dependsOn.map((d: any) => String(d).replace(/^AB#/, '')); + } + return { + adoId, + displayId: p.id || (p.adoId ? `AB#${p.adoId}` : '?'), // what to show in UI + title: p.title || '', + module: p.module || p.repo || p.targetRepo || '', + adoUrl: p.adoUrl || `https://dev.azure.com/${ADO_ORG}/${ADO_PROJECT}/_workitems/edit/${adoId}`, + status: p.status || 'new', + dependsOn, + priority: p.priority, + }; + }); + + const stepConfig: Record = { + 'idle': { icon: '⏳', label: 'Ready', color: '#8b949e' }, + 'designing': { icon: '📝', label: 'Writing Design', color: '#58a6ff' }, + 'design_review': { icon: '👀', label: 'Design Review', color: '#d29922' }, + 'planning': { icon: '📋', label: 'Planning PBIs', color: '#58a6ff' }, + 'plan_review': { icon: '👀', label: 'Plan Review', color: '#d29922' }, + 'backlogging': { icon: '📝', label: 'Adding to Backlog', color: '#58a6ff' }, + 'backlog_review': { icon: '👀', label: 'Backlog Review', color: '#d29922' }, + 'dispatching': { icon: '🚀', label: 'Dispatching', color: '#58a6ff' }, + 'monitoring': { icon: '📡', label: 'Monitoring Agents', color: '#3fb950' }, + 'done': { icon: '✅', label: 'Complete', color: '#3fb950' }, + }; + + // Normalize step names: map aliases/past-tense/legacy names to canonical keys + const stepAliases: Record = { + 'designed': 'design_review', 'design_complete': 'design_review', + 'planned': 'plan_review', 'plan_complete': 'plan_review', + 'backlogged': 'backlog_review', 'created': 'backlog_review', 'creating': 'backlogging', 'create_review': 'backlog_review', + 'dispatched': 'monitoring', 'dispatch_complete': 'monitoring', + 'complete': 'done', 'completed': 'done', + }; + const normalizedStep = stepAliases[feature.step] || feature.step; + + const cfg = stepConfig[normalizedStep] || stepConfig['idle']; + + const pipelineStages = [ + { key: 'designing', label: 'Design' }, + { key: 'planning', label: 'Plan' }, + { key: 'backlogging', label: 'Backlog' }, + { key: 'dispatching', label: 'Dispatch' }, + { key: 'monitoring', label: 'Monitor' }, + ]; + const stageOrder = ['idle', 'designing', 'design_review', 'planning', 'plan_review', 'backlogging', 'backlog_review', 'dispatching', 'monitoring', 'done']; + const currentIdx = stageOrder.indexOf(normalizedStep); + + const pipelineHtml = pipelineStages.map((stage, i) => { + // Each stage maps to 2 entries in stageOrder (active + review), roughly at i*2+1 + const stageIdx = i * 2 + 1; + const isActive = currentIdx >= stageIdx && currentIdx < stageIdx + 2; + const isDone = currentIdx >= stageIdx + 2; + const cls = isDone ? 'stage done' : isActive ? 'stage active' : 'stage'; + return `
    ${isDone ? '✅' : isActive ? '🔵' : '○'} ${stage.label}
    `; + }).join('
    '); + + // Design section + const designHtml = design + ? `
    +
    📄 Design Spec
    +
    + ${design.docPath ? `` : ''} + ${design.prUrl ? `` : ''} +
    Status: ${design.status}
    +
    +
    ` + : (feature.designDocPath + ? `
    +
    📄 Design Spec
    +
    + + ${feature.designPrUrl ? `` : ''} +
    +
    ` + : '
    📄 Design Spec

    Not yet created

    '); + + // PBIs section + const hasDeps = pbis.some((p: any) => p.dependsOn && p.dependsOn.length > 0); + + // Compute topological order for implementation sequence + const pbiOrder = new Map(); + if (pbis.length > 0) { + // Build adjacency: each PBI's dependencies + const resolved = new Set(); + const remaining = pbis.map((p: any) => ({ id: String(p.adoId), deps: (p.dependsOn || []).map(String) })); + let order = 1; + let maxIter = pbis.length + 1; // safety limit + while (remaining.length > 0 && maxIter-- > 0) { + const ready = remaining.filter(r => r.deps.every((d: string) => resolved.has(d))); + if (ready.length === 0) { + // Cycle or unresolved deps — assign remaining in original order + for (const r of remaining) { pbiOrder.set(r.id, order++); } + break; + } + for (const r of ready) { + pbiOrder.set(r.id, order++); + resolved.add(r.id); + remaining.splice(remaining.indexOf(r), 1); + } + } + } + + const pbisHtml = pbis.length > 0 + ? `
    +
    📋 Product Backlog Items ${pbis.length}
    +
    + + ${hasDeps ? '' : ''} + + ${pbis.map((p: any) => { + const statusClass = (p.status || 'new').toLowerCase().replace(/\s/g, '-'); + const depsCell = hasDeps + ? `` + : ''; + const orderNum = pbiOrder.get(String(p.adoId)) || '—'; + return ` + + + + + ${depsCell} + + `; + }).join('')} + +
    OrderAB#TitleRepoDepends OnStatus
    ${(p.dependsOn || []).map((d: string) => `AB#${d}`).join(', ') || '—'}
    ${orderNum}${escapeHtml(String(p.displayId))}${escapeHtml(p.title || '')}${escapeHtml(p.module || '')}${escapeHtml(p.status || 'new')}
    +
    +
    ` + : '
    📋 Product Backlog Items

    No PBIs yet

    '; + + // Agent PRs section + const prsHtml = agentPrs.length > 0 + ? `
    +
    🤖 Agent Pull Requests ${agentPrs.length}
    +
    + + + + ${agentPrs.map((pr: any) => { + const prUrl = pr.prUrl || pr.url || '#'; + const statusColor: Record = { open: '#3fb950', merged: '#8957e5', closed: '#da3633', draft: '#8b949e' }; + const status = (pr.status || pr.state || 'open').toLowerCase(); + const totalComments = pr.totalComments ?? '—'; + const resolvedComments = pr.resolvedComments ?? 0; + const commentsDisplay = totalComments === '—' ? '—' + : `${resolvedComments}/${totalComments}`; + return ` + + + + + + `; + }).join('')} + +
    PRRepoTitleCommentsStatus
    #${pr.prNumber || pr.number || '?'}${escapeHtml(pr.repo || '')}${escapeHtml(pr.title || '')}${commentsDisplay}${status}
    +
    +
    ` + : '
    🤖 Agent Pull Requests

    No agent PRs yet

    '; + + const timeAgo = (ts: number) => { + if (!ts) return 'unknown'; + const s = Math.floor((Date.now() - ts) / 1000); + if (s < 60) return 'just now'; + if (s < 3600) return `${Math.floor(s / 60)}m ago`; + if (s < 86400) return `${Math.floor(s / 3600)}h ago`; + return `${Math.floor(s / 86400)}d ago`; + }; + + return ` + + +
    +
    ${cfg.icon}
    +
    +

    ${escapeHtml(feature.name)}

    +
    Started ${timeAgo(feature.startedAt)} · Updated ${timeAgo(feature.updatedAt)}
    +
    +
    + + +
    +
    + + ${feature.prompt ? `
    ${escapeHtml(feature.prompt)}
    ` : ''} + +
    ${pipelineHtml}
    + +
    +
    ${cfg.icon} ${cfg.label}
    +
    + +
    Artifacts
    + ${designHtml} + ${pbisHtml} + ${prsHtml} + + +`; + } +} + +function escapeHtml(s: string): string { + return s.replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); +} + +function escapeAttr(s: string): string { + return s.replace(/'/g, "\\'").replace(/"/g, '"').replace(/\\/g, '\\\\'); +} diff --git a/extensions/feature-orchestrator/src/tools.ts b/extensions/feature-orchestrator/src/tools.ts new file mode 100644 index 00000000..3d597ce3 --- /dev/null +++ b/extensions/feature-orchestrator/src/tools.ts @@ -0,0 +1,158 @@ +import * as vscode from 'vscode'; +import * as cp from 'child_process'; +import * as fs from 'fs'; +import * as path from 'path'; + +/** + * Run a terminal command and return the output. + */ +export function runCommand(command: string, cwd?: string, timeoutMs?: number): Promise { + return new Promise((resolve, reject) => { + const options: cp.ExecOptions = { + cwd: cwd ?? vscode.workspace.workspaceFolders?.[0]?.uri.fsPath, + maxBuffer: 1024 * 1024, // 1MB + timeout: timeoutMs ?? 60000, + }; + + cp.exec(command, options, (error, stdout, stderr) => { + if (error) { + reject(new Error(`Command failed: ${command}\n${stderr?.toString() || error.message}`)); + return; + } + resolve(stdout?.toString().trim() ?? ''); + }); + }); +} + +/** + * Resolve GitHub account mapping for the current developer. + * Discovery sequence: + * 1. .github/developer-local.json + * 2. gh auth status (parse logged-in accounts) + * 3. Prompt the developer (via VS Code input box) + */ +async function resolveGhAccounts(): Promise> { + const workspaceRoot = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath; + + // Step 1: Check .github/developer-local.json + if (workspaceRoot) { + const configPath = path.join(workspaceRoot, '.github', 'developer-local.json'); + if (fs.existsSync(configPath)) { + try { + const config = JSON.parse(fs.readFileSync(configPath, 'utf-8')); + const accounts = config.github_accounts; + if (accounts?.AzureAD && accounts?.['identity-authnz-teams']) { + return accounts; + } + } catch { + // Fall through to discovery + } + } + } + + // Step 2: Discover from gh auth status + try { + // First verify gh is installed + try { + await runCommand('gh --version'); + } catch { + // gh not installed — offer to install + const install = await vscode.window.showWarningMessage( + 'GitHub CLI (gh) is not installed. It\'s required for dispatching PBIs to Copilot coding agent.', + 'Install now', + 'I\'ll install manually' + ); + if (install === 'Install now') { + const terminal = vscode.window.createTerminal('Install gh CLI'); + terminal.show(); + if (process.platform === 'win32') { + terminal.sendText('winget install --id GitHub.cli -e --accept-source-agreements --accept-package-agreements'); + } else if (process.platform === 'darwin') { + terminal.sendText('brew install gh'); + } else { + terminal.sendText('echo "Please install gh CLI: https://cli.github.com"'); + } + vscode.window.showInformationMessage( + 'Installing gh CLI... After installation, run `gh auth login` in a terminal, then retry.' + ); + } + throw new Error('gh CLI not installed. Install it and run `gh auth login`, then retry.'); + } + + const status = await runCommand('gh auth status 2>&1'); + const accounts: Record = {}; + for (const line of status.split('\n')) { + const match = line.match(/account\s+(\S+)/); + if (match) { + const username = match[1]; + if (username.includes('_')) { + accounts['identity-authnz-teams'] = username; + } else { + accounts['AzureAD'] = username; + } + } + } + if (accounts['AzureAD'] && accounts['identity-authnz-teams']) { + return accounts; + } + } catch { + // Fall through to prompt + } + + // Step 3: Prompt the developer + const publicUser = await vscode.window.showInputBox({ + prompt: 'Enter your public GitHub username (for AzureAD/* repos like common, msal)', + placeHolder: 'e.g., myusername', + }); + const emuUser = await vscode.window.showInputBox({ + prompt: 'Enter your GitHub EMU username (for identity-authnz-teams/* repos like broker)', + placeHolder: 'e.g., myusername_microsoft', + }); + + if (!publicUser || !emuUser) { + throw new Error('GitHub usernames are required for dispatching. Please configure them.'); + } + + const accounts: Record = { + 'AzureAD': publicUser, + 'identity-authnz-teams': emuUser, + }; + + // Offer to save + if (workspaceRoot) { + const save = await vscode.window.showQuickPick(['Yes', 'No'], { + placeHolder: 'Save to .github/developer-local.json for next time?', + }); + if (save === 'Yes') { + const configPath = path.join(workspaceRoot, '.github', 'developer-local.json'); + const config = { github_accounts: accounts }; + fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', 'utf-8'); + } + } + + return accounts; +} + +/** + * Switch the gh CLI to the correct account for a given repo. + */ +export async function switchGhAccount(repo: string): Promise { + const org = repo.split('/')[0]; + const accountMap = await resolveGhAccounts(); + + const account = accountMap[org]; + if (account) { + await runCommand(`gh auth switch --user ${account}`); + } +} + +/** + * Check the status of Copilot agent PRs for a repo. + */ +export async function getAgentPRs(repo: string): Promise { + await switchGhAccount(repo); + + return runCommand( + `gh pr list --repo "${repo}" --author "copilot-swe-agent[bot]" --state all --limit 10 --json number,title,state,createdAt,url` + ); +} diff --git a/extensions/feature-orchestrator/tsconfig.json b/extensions/feature-orchestrator/tsconfig.json new file mode 100644 index 00000000..f06cd30b --- /dev/null +++ b/extensions/feature-orchestrator/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "ES2022", + "outDir": "out", + "rootDir": "src", + "lib": ["ES2022"], + "sourceMap": true, + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true + }, + "include": ["src"], + "exclude": ["node_modules", "out"] +} From 889f463247842f591df7312f937ee6724850c63d Mon Sep 17 00:00:00 2001 From: Shahzaib Date: Sun, 1 Mar 2026 09:39:27 -0800 Subject: [PATCH 13/32] Add hooks --- .github/hooks/orchestrator.json | 18 +++ .github/hooks/state-utils.js | 254 ++++++++++++++++++++++++++++++++ .github/hooks/subagent-start.js | 97 ++++++++++++ .github/hooks/subagent-stop.js | 86 +++++++++++ 4 files changed, 455 insertions(+) create mode 100644 .github/hooks/orchestrator.json create mode 100644 .github/hooks/state-utils.js create mode 100644 .github/hooks/subagent-start.js create mode 100644 .github/hooks/subagent-stop.js diff --git a/.github/hooks/orchestrator.json b/.github/hooks/orchestrator.json new file mode 100644 index 00000000..adec1e5c --- /dev/null +++ b/.github/hooks/orchestrator.json @@ -0,0 +1,18 @@ +{ + "hooks": { + "SubagentStart": [ + { + "type": "command", + "command": "node .github/hooks/subagent-start.js", + "timeout": 10 + } + ], + "SubagentStop": [ + { + "type": "command", + "command": "node .github/hooks/subagent-stop.js", + "timeout": 10 + } + ] + } +} diff --git a/.github/hooks/state-utils.js b/.github/hooks/state-utils.js new file mode 100644 index 00000000..84f2742f --- /dev/null +++ b/.github/hooks/state-utils.js @@ -0,0 +1,254 @@ +#!/usr/bin/env node +/** + * Shared state file utilities for the Feature Orchestrator. + * + * The state file (state.json) lives at ~/.android-auth-orchestrator/ and is + * read/written by both hooks (via this CLI script) and the VS Code extension. + * + * Usage from hooks: + * node .github/hooks/state-utils.js get → prints full state JSON + * node .github/hooks/state-utils.js get-feature → prints one feature + * node .github/hooks/state-utils.js set-step → updates a feature's step + * node .github/hooks/state-utils.js add-feature → adds/updates a feature + * node .github/hooks/state-utils.js set-agent-info → sets agent session info + * node .github/hooks/state-utils.js set-design → sets design artifact + * node .github/hooks/state-utils.js add-pbi → adds a PBI artifact + * node .github/hooks/state-utils.js add-agent-pr → adds an agent PR artifact + * + * State file schema: + * { + * "version": 2, + * "features": [ + * { + * "id": "feature--", + * "name": "Short feature name", + * "prompt": "Original user prompt", + * "step": "idle|designing|design_review|planning|plan_review|backlogging|backlog_review|dispatching|monitoring|done", + * "artifacts": { + * "design": { "docPath": "design-docs/.../spec.md", "prUrl": "https://...", "status": "draft|in-review|approved" }, + * "pbis": [ + * { "adoId": 12345, "title": "...", "targetRepo": "AzureAD/...", "module": "common", "adoUrl": "https://...", "status": "new|committed|active|resolved|closed", "priority": 1 } + * ], + * "agentPrs": [ + * { "repo": "common", "prNumber": 2916, "prUrl": "https://...", "status": "open|merged|closed|draft", "title": "..." } + * ] + * }, + * "designDocPath": "design-docs/.../spec.md", + * "designPrUrl": "https://dev.azure.com/...", + * "pbis": [ + * { "adoId": 12345, "title": "...", "targetRepo": "AzureAD/...", "dependsOn": [], "status": "pending" } + * ], + * "agentSessions": [ + * { "repo": "AzureAD/...", "prNumber": 2916, "prUrl": "https://...", "sessionUrl": "https://...", "status": "in_progress" } + * ], + * "startedAt": 1740000000000, + * "updatedAt": 1740000000000 + * } + * ], + * "lastUpdated": 1740000000000 + * } + */ + +const fs = require('fs'); +const path = require('path'); + +const os = require('os'); + +// State file lives in user's home directory (not workspace root) +const STATE_DIR = path.join(os.homedir(), '.android-auth-orchestrator'); +const STATE_FILE = path.join(STATE_DIR, 'state.json'); + +function ensureStateDir() { + if (!fs.existsSync(STATE_DIR)) { + fs.mkdirSync(STATE_DIR, { recursive: true }); + } +} + +function readState() { + if (!fs.existsSync(STATE_FILE)) { + return { version: 1, features: [], lastUpdated: Date.now() }; + } + try { + return JSON.parse(fs.readFileSync(STATE_FILE, 'utf-8')); + } catch { + return { version: 1, features: [], lastUpdated: Date.now() }; + } +} + +function writeState(state) { + ensureStateDir(); + state.lastUpdated = Date.now(); + fs.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2), 'utf-8'); +} + +/** + * Find a feature by ID or name (case-insensitive). + * The agent may pass either the auto-generated ID (feature-17723...) or the + * human-readable name ("IPC Retry with Exponential Backoff"). Support both. + * If multiple features match by name, return the most recently updated one. + */ +function findFeature(state, identifier) { + // Try exact ID match first + const byId = state.features.find(f => f.id === identifier); + if (byId) return byId; + + // Try exact name match (case-insensitive) + const lower = identifier.toLowerCase(); + const byName = state.features + .filter(f => f.name && f.name.toLowerCase() === lower) + .sort((a, b) => (b.updatedAt || 0) - (a.updatedAt || 0)); + if (byName.length > 0) return byName[0]; + + // Try partial name match as a last resort + const partial = state.features + .filter(f => f.name && f.name.toLowerCase().includes(lower)) + .sort((a, b) => (b.updatedAt || 0) - (a.updatedAt || 0)); + return partial[0] || null; +} + +// CLI +const [, , command, ...args] = process.argv; + +switch (command) { + case 'get': { + console.log(JSON.stringify(readState(), null, 2)); + break; + } + case 'get-feature': { + const state = readState(); + const feature = findFeature(state, args[0]); + console.log(JSON.stringify(feature || null, null, 2)); + break; + } + case 'set-step': { + const state = readState(); + const feature = findFeature(state, args[0]); + if (feature) { + feature.step = args[1]; + feature.updatedAt = Date.now(); + writeState(state); + console.log(JSON.stringify({ ok: true, id: args[0], step: args[1] })); + } else { + console.log(JSON.stringify({ ok: false, error: 'Feature not found' })); + } + break; + } + case 'add-feature': { + const state = readState(); + const feature = JSON.parse(args[0]); + // Auto-generate ID if not provided + if (!feature.id) { + feature.id = 'feature-' + Date.now() + '-' + Math.random().toString(36).slice(2, 6); + } + // Ensure required fields have defaults + if (!feature.pbis) feature.pbis = []; + if (!feature.agentSessions) feature.agentSessions = []; + if (!feature.prompt) feature.prompt = ''; + // Also match by name when deduplicating + const idx = state.features.findIndex(f => f.id === feature.id || (f.name && feature.name && f.name.toLowerCase() === feature.name.toLowerCase())); + if (idx >= 0) { + state.features[idx] = { ...state.features[idx], ...feature, updatedAt: Date.now() }; + } else { + state.features.push({ ...feature, startedAt: Date.now(), updatedAt: Date.now() }); + } + writeState(state); + console.log(JSON.stringify({ ok: true, id: feature.id })); + break; + } + case 'set-agent-info': { + const state = readState(); + const feature = findFeature(state, args[0]); + if (feature) { + const info = JSON.parse(args[1]); + if (!feature.agentSessions) feature.agentSessions = []; + feature.agentSessions.push(info); + // Also add to artifacts.agentPrs + if (!feature.artifacts) feature.artifacts = { pbis: [], agentPrs: [] }; + if (!feature.artifacts.agentPrs) feature.artifacts.agentPrs = []; + feature.artifacts.agentPrs.push({ + repo: info.repo, + prNumber: info.prNumber || info.number, + prUrl: info.prUrl || info.url, + status: info.status || 'open', + title: info.title || '', + }); + feature.updatedAt = Date.now(); + writeState(state); + console.log(JSON.stringify({ ok: true })); + } + break; + } + case 'set-design': { + const state = readState(); + const feature = findFeature(state, args[0]); + if (feature) { + const design = JSON.parse(args[1]); + if (!feature.artifacts) feature.artifacts = { pbis: [], agentPrs: [] }; + feature.artifacts.design = design; + // Also set legacy fields for backward compat + if (design.docPath) feature.designDocPath = design.docPath; + if (design.prUrl) feature.designPrUrl = design.prUrl; + feature.updatedAt = Date.now(); + writeState(state); + console.log(JSON.stringify({ ok: true })); + } else { + console.log(JSON.stringify({ ok: false, error: 'Feature not found' })); + } + break; + } + case 'add-pbi': { + const state = readState(); + const feature = findFeature(state, args[0]); + if (feature) { + const pbi = JSON.parse(args[1]); + if (!feature.artifacts) feature.artifacts = { pbis: [], agentPrs: [] }; + if (!feature.artifacts.pbis) feature.artifacts.pbis = []; + // Avoid duplicates by adoId + const existingIdx = feature.artifacts.pbis.findIndex(p => p.adoId === pbi.adoId); + if (existingIdx >= 0) { + feature.artifacts.pbis[existingIdx] = { ...feature.artifacts.pbis[existingIdx], ...pbi }; + } else { + feature.artifacts.pbis.push(pbi); + } + // Also maintain legacy pbis array + if (!feature.pbis) feature.pbis = []; + const legacyIdx = feature.pbis.findIndex(p => p.adoId === pbi.adoId); + if (legacyIdx >= 0) { + feature.pbis[legacyIdx] = { ...feature.pbis[legacyIdx], ...pbi }; + } else { + feature.pbis.push({ adoId: pbi.adoId, title: pbi.title, targetRepo: pbi.targetRepo, status: pbi.status || 'pending' }); + } + feature.updatedAt = Date.now(); + writeState(state); + console.log(JSON.stringify({ ok: true, pbiCount: feature.artifacts.pbis.length })); + } else { + console.log(JSON.stringify({ ok: false, error: 'Feature not found' })); + } + break; + } + case 'add-agent-pr': { + const state = readState(); + const feature = findFeature(state, args[0]); + if (feature) { + const pr = JSON.parse(args[1]); + if (!feature.artifacts) feature.artifacts = { pbis: [], agentPrs: [] }; + if (!feature.artifacts.agentPrs) feature.artifacts.agentPrs = []; + // Avoid duplicates by prNumber+repo + const existingIdx = feature.artifacts.agentPrs.findIndex(p => p.prNumber === pr.prNumber && p.repo === pr.repo); + if (existingIdx >= 0) { + feature.artifacts.agentPrs[existingIdx] = { ...feature.artifacts.agentPrs[existingIdx], ...pr }; + } else { + feature.artifacts.agentPrs.push(pr); + } + feature.updatedAt = Date.now(); + writeState(state); + console.log(JSON.stringify({ ok: true, prCount: feature.artifacts.agentPrs.length })); + } else { + console.log(JSON.stringify({ ok: false, error: 'Feature not found' })); + } + break; + } + default: + console.error('Usage: state-utils.js [args]'); + process.exit(1); +} diff --git a/.github/hooks/subagent-start.js b/.github/hooks/subagent-start.js new file mode 100644 index 00000000..55d90836 --- /dev/null +++ b/.github/hooks/subagent-start.js @@ -0,0 +1,97 @@ +#!/usr/bin/env node +/** + * SubagentStart hook — injects orchestrator context into subagent sessions. + * + * SCOPE: This hook runs only when the orchestrator invokes a subagent, + * NOT for regular Agent Mode sessions. It injects active feature context + * so subagents are aware of the pipeline state. + */ + +var fs = require('fs'); +var path = require('path'); + +// Read stdin (hook input) +var hookInput = {}; +try { + hookInput = JSON.parse(fs.readFileSync(0, 'utf-8')); +} catch (e) { + // no stdin +} + +var os = require('os'); + +var stateFile = path.join(os.homedir(), '.android-auth-orchestrator', 'state.json'); + +function readState() { + if (!fs.existsSync(stateFile)) { + return { version: 1, features: [], lastUpdated: Date.now() }; + } + try { + return JSON.parse(fs.readFileSync(stateFile, 'utf-8')); + } catch (e) { + return { version: 1, features: [], lastUpdated: Date.now() }; + } +} + +var additionalContext = ''; + +try { + var state = readState(); + + // Check if there's an active feature being tracked by the orchestrator. + // If yes, inject its context. If no, just add basic workspace info. + // We do NOT auto-create feature entries here — that would pollute state + // for every normal Agent Mode session. Feature entries are created by + // the orchestrator agent itself (via the state-utils.js CLI). + var activeFeature = null; + if (state.features && state.features.length > 0) { + activeFeature = state.features + .filter(function(f) { return f.step !== 'done' && f.step !== 'idle'; }) + .sort(function(a, b) { return (b.updatedAt || 0) - (a.updatedAt || 0); })[0] || null; + } + + if (activeFeature) { + // Orchestrator session — inject full feature context + var parts = [ + 'Active feature: "' + activeFeature.name + '"', + 'Current step: ' + activeFeature.step, + ]; + + if (activeFeature.designDocPath) { + parts.push('Design doc: ' + activeFeature.designDocPath); + } + + if (activeFeature.pbis && activeFeature.pbis.length > 0) { + var pbiSummary = activeFeature.pbis + .map(function(p) { return 'AB#' + p.adoId + ' (' + p.targetRepo + ') [' + p.status + ']'; }) + .join(', '); + parts.push('PBIs: ' + pbiSummary); + } + + additionalContext = parts.join('. ') + '.'; + } + + // Add basic workspace info (cwd is the workspace root when hooks run) + var root = process.cwd(); + var skillsDir = path.join(root, '.github', 'skills'); + var skills = fs.existsSync(skillsDir) + ? fs.readdirSync(skillsDir).join(', ') + : 'none'; + var hasDesignDocs = fs.existsSync(path.join(root, 'design-docs')); + + additionalContext += ' Android Auth workspace. Skills: ' + skills + '.'; + if (hasDesignDocs) { + additionalContext += ' Design docs available at design-docs/.'; + } + +} catch (e) { + additionalContext = 'Android Auth workspace (state read error: ' + e.message + ')'; +} + +// Output +console.log(JSON.stringify({ + hookSpecificOutput: { + hookEventName: 'SubagentStart', + additionalContext: additionalContext.trim(), + } +})); diff --git a/.github/hooks/subagent-stop.js b/.github/hooks/subagent-stop.js new file mode 100644 index 00000000..36556e46 --- /dev/null +++ b/.github/hooks/subagent-stop.js @@ -0,0 +1,86 @@ +#!/usr/bin/env node +/** + * SubagentStop hook — advances orchestrator state when a subagent completes. + * + * Maps subagent names to pipeline steps, writes the next step to + * orchestrator-state.json. The VS Code extension watches this file + * and renders a clickable "next step" notification button. + */ + +const fs = require('fs'); +const path = require('path'); + +// Read stdin (hook input) +let hookInput = {}; +try { + hookInput = JSON.parse(fs.readFileSync(0, 'utf-8')); +} catch { /* no stdin */ } + +// Don't interfere if this is a re-entry +if (hookInput.stop_hook_active) { + console.log(JSON.stringify({ continue: true })); + process.exit(0); +} + +const agentType = hookInput.agent_type || ''; + +// Only handle subagents that are part of our orchestrator pipeline +var ourAgents = ['codebase-researcher', 'design-writer', 'feature-planner', 'pbi-creator', 'agent-dispatcher']; +if (ourAgents.indexOf(agentType) === -1) { + // Not one of our subagents — let it pass without modifying state + console.log(JSON.stringify({ continue: true })); + process.exit(0); +} + +// Map subagent names to the next pipeline step +const agentToNextStep = { + 'codebase-researcher': null, // research is intermediate, no step change + 'design-writer': 'design_review', + 'feature-planner': 'plan_review', + 'pbi-creator': 'backlog_review', + 'agent-dispatcher': 'monitoring', +}; + +const nextStep = agentToNextStep[agentType]; + +if (!nextStep) { + // Not a tracked subagent, let it pass + console.log(JSON.stringify({ continue: true })); + process.exit(0); +} + +const os = require('os'); + +const stateFile = path.join(os.homedir(), '.android-auth-orchestrator', 'state.json'); + +try { + if (fs.existsSync(stateFile)) { + const state = JSON.parse(fs.readFileSync(stateFile, 'utf-8')); + + // Find the most recently updated in-progress feature + const activeFeature = state.features + ?.filter(f => f.step !== 'done' && f.step !== 'idle') + ?.sort((a, b) => (b.updatedAt || 0) - (a.updatedAt || 0))?.[0]; + + if (activeFeature) { + activeFeature.step = nextStep; + activeFeature.updatedAt = Date.now(); + + // Write a "pendingAction" field that the extension will consume + // to show a clickable notification button + activeFeature.pendingAction = { + completedAgent: agentType, + nextStep: nextStep, + timestamp: Date.now(), + }; + + state.lastUpdated = Date.now(); + fs.writeFileSync(stateFile, JSON.stringify(state, null, 2), 'utf-8'); + } + } +} catch (e) { + console.error('SubagentStop hook error:', e.message); +} + +// Always allow the subagent to stop normally +console.log(JSON.stringify({ continue: true })); From 275b05aa963aa68f1fe1aa3baa412fbe8a111e06 Mon Sep 17 00:00:00 2001 From: Shahzaib Date: Sun, 1 Mar 2026 09:39:34 -0800 Subject: [PATCH 14/32] Update .gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 23dfe910..7adea6eb 100644 --- a/.gitignore +++ b/.gitignore @@ -34,6 +34,9 @@ local.properties # Developer-local config (GitHub accounts, personal settings — never commit) .github/developer-local.json +# Design review comments (personal, per-developer) +.github/design-reviews/ + #nodejs node_modules From abe80375bf171abd362b99d3e4a18634b5573477 Mon Sep 17 00:00:00 2001 From: Shahzaib Date: Sun, 1 Mar 2026 09:51:17 -0800 Subject: [PATCH 15/32] Allow manually entering design-doc, PBI and PRs to feature --- .../feature-orchestrator/src/featureDetail.ts | 280 +++++++++++++++++- 1 file changed, 273 insertions(+), 7 deletions(-) diff --git a/extensions/feature-orchestrator/src/featureDetail.ts b/extensions/feature-orchestrator/src/featureDetail.ts index dc4e1cec..5d5e8d48 100644 --- a/extensions/feature-orchestrator/src/featureDetail.ts +++ b/extensions/feature-orchestrator/src/featureDetail.ts @@ -132,6 +132,18 @@ export class FeatureDetailPanel { } } break; + + case 'addDesign': + await FeatureDetailPanel.handleAddDesign(featureId, panel); + break; + + case 'addPbi': + await FeatureDetailPanel.handleAddPbi(featureId, panel); + break; + + case 'addAgentPr': + await FeatureDetailPanel.handleAddAgentPr(featureId, panel); + break; } }); @@ -316,6 +328,244 @@ export class FeatureDetailPanel { } } + // ---- Manual artifact entry handlers ---- + + /** + * Let user manually add a design spec — browse for a local file or paste an ADO PR URL. + */ + private static async handleAddDesign(featureId: string, panel: vscode.WebviewPanel): Promise { + const choice = await vscode.window.showQuickPick( + [ + { label: '📄 Browse for local file', description: 'Select a markdown file from design-docs/', value: 'file' }, + { label: '🔗 Enter ADO PR URL', description: 'Paste a design review PR link', value: 'pr' }, + ], + { placeHolder: 'How do you want to add the design spec?' } + ); + if (!choice) { return; } + + const state = FeatureDetailPanel.readState(); + const feature = state.features?.find((f: any) => f.id === featureId); + if (!feature) { return; } + if (!feature.artifacts) { feature.artifacts = { pbis: [], agentPrs: [] }; } + + if (choice.value === 'file') { + const folders = vscode.workspace.workspaceFolders; + const defaultUri = folders + ? vscode.Uri.file(path.join(folders[0].uri.fsPath, 'design-docs')) + : undefined; + + const uris = await vscode.window.showOpenDialog({ + defaultUri, + canSelectMany: false, + filters: { 'Markdown': ['md'] }, + title: 'Select Design Spec', + }); + if (!uris || uris.length === 0) { return; } + + // Make path workspace-relative + let docPath = uris[0].fsPath; + if (folders) { + const wsRoot = folders[0].uri.fsPath; + if (docPath.startsWith(wsRoot)) { + docPath = docPath.substring(wsRoot.length + 1).replace(/\\/g, '/'); + } + } + + feature.artifacts.design = { + docPath, + status: feature.artifacts.design?.status || 'approved', + prUrl: feature.artifacts.design?.prUrl, + }; + feature.designDocPath = docPath; + } else { + const prUrl = await vscode.window.showInputBox({ + prompt: 'Paste the ADO PR URL for the design review', + placeHolder: 'https://dev.azure.com/IdentityDivision/Engineering/_git/AuthLibrariesApiReview/pullrequest/...', + }); + if (!prUrl) { return; } + + if (!feature.artifacts.design) { + // Ask for the doc path too + const docPath = await vscode.window.showInputBox({ + prompt: 'Design doc path (workspace-relative, or leave empty)', + placeHolder: 'design-docs/[Android] Feature Name/spec.md', + }); + feature.artifacts.design = { + docPath: docPath || '', + prUrl, + status: 'in-review', + }; + if (docPath) { feature.designDocPath = docPath; } + } else { + feature.artifacts.design.prUrl = prUrl; + } + feature.designPrUrl = prUrl; + } + + feature.updatedAt = Date.now(); + FeatureDetailPanel.writeState(state); + // Re-render + const fresh = FeatureDetailPanel.readState().features?.find((f: any) => f.id === featureId); + if (fresh) { panel.webview.html = FeatureDetailPanel.getHtml(fresh); } + } + + /** + * Let user manually add a PBI by AB# ID — fetches details from ADO. + */ + private static async handleAddPbi(featureId: string, panel: vscode.WebviewPanel): Promise { + const idInput = await vscode.window.showInputBox({ + prompt: 'Enter the ADO work item ID (AB# number)', + placeHolder: 'e.g., 3531353', + validateInput: (v) => /^\d+$/.test(v.replace(/^AB#/i, '')) ? null : 'Enter a numeric ID (e.g., 3531353)', + }); + if (!idInput) { return; } + + const adoId = parseInt(idInput.replace(/^AB#/i, ''), 10); + + const state = FeatureDetailPanel.readState(); + const feature = state.features?.find((f: any) => f.id === featureId); + if (!feature) { return; } + if (!feature.artifacts) { feature.artifacts = { pbis: [], agentPrs: [] }; } + if (!feature.artifacts.pbis) { feature.artifacts.pbis = []; } + + // Check for duplicate + if (feature.artifacts.pbis.some((p: any) => p.adoId === adoId)) { + vscode.window.showInformationMessage(`AB#${adoId} is already tracked.`); + return; + } + + // Try to fetch details from ADO + let title = `Work Item ${adoId}`; + let pbiStatus = 'New'; + let module = ''; + + try { + const json = await runCommand( + `az boards work-item show --id ${adoId} --org "https://dev.azure.com/IdentityDivision" --only-show-errors -o json`, + undefined, 15000 + ); + const wi = JSON.parse(json); + const fields = wi.fields || {}; + title = fields['System.Title'] || title; + pbiStatus = fields['System.State'] || pbiStatus; + // Try to infer module from tags or area path + const tags: string = fields['System.Tags'] || ''; + const areaPath: string = fields['System.AreaPath'] || ''; + if (areaPath.includes('Broker')) { module = 'broker'; } + else if (areaPath.includes('MSAL')) { module = 'msal'; } + else if (areaPath.includes('Common') || areaPath.includes('common')) { module = 'common'; } + else if (tags.toLowerCase().includes('common')) { module = 'common'; } + } catch { + // az CLI not available — ask user for details + const userTitle = await vscode.window.showInputBox({ + prompt: `Could not fetch from ADO. Enter the PBI title:`, + placeHolder: 'PBI title', + }); + if (userTitle) { title = userTitle; } + + const userModule = await vscode.window.showQuickPick( + ['common', 'msal', 'broker', 'adal'], + { placeHolder: 'Which repo/module?' } + ); + if (userModule) { module = userModule; } + } + + feature.artifacts.pbis.push({ + adoId, + title, + module, + targetRepo: module, + status: pbiStatus, + adoUrl: `https://dev.azure.com/IdentityDivision/Engineering/_workitems/edit/${adoId}`, + }); + + // Also add to legacy pbis + if (!feature.pbis) { feature.pbis = []; } + feature.pbis.push({ adoId, title, targetRepo: module, status: pbiStatus }); + + feature.updatedAt = Date.now(); + FeatureDetailPanel.writeState(state); + const fresh = FeatureDetailPanel.readState().features?.find((f: any) => f.id === featureId); + if (fresh) { panel.webview.html = FeatureDetailPanel.getHtml(fresh); } + vscode.window.showInformationMessage(`Added AB#${adoId}: ${title}`); + } + + /** + * Let user manually add an agent PR by repo + PR number — fetches details from GitHub. + */ + private static async handleAddAgentPr(featureId: string, panel: vscode.WebviewPanel): Promise { + const repoChoice = await vscode.window.showQuickPick( + [ + { label: 'common', description: 'AzureAD/microsoft-authentication-library-common-for-android', value: 'AzureAD/microsoft-authentication-library-common-for-android' }, + { label: 'msal', description: 'AzureAD/microsoft-authentication-library-for-android', value: 'AzureAD/microsoft-authentication-library-for-android' }, + { label: 'broker', description: 'identity-authnz-teams/ad-accounts-for-android', value: 'identity-authnz-teams/ad-accounts-for-android' }, + { label: 'adal', description: 'AzureAD/azure-activedirectory-library-for-android', value: 'AzureAD/azure-activedirectory-library-for-android' }, + ], + { placeHolder: 'Which repo is the PR in?' } + ); + if (!repoChoice) { return; } + + const prInput = await vscode.window.showInputBox({ + prompt: 'Enter the PR number', + placeHolder: 'e.g., 2922', + validateInput: (v) => /^\d+$/.test(v.replace(/^#/, '')) ? null : 'Enter a numeric PR number', + }); + if (!prInput) { return; } + + const prNumber = parseInt(prInput.replace(/^#/, ''), 10); + + const state = FeatureDetailPanel.readState(); + const feature = state.features?.find((f: any) => f.id === featureId); + if (!feature) { return; } + if (!feature.artifacts) { feature.artifacts = { pbis: [], agentPrs: [] }; } + if (!feature.artifacts.agentPrs) { feature.artifacts.agentPrs = []; } + + // Check for duplicate + if (feature.artifacts.agentPrs.some((p: any) => p.prNumber === prNumber && (p.repo === repoChoice.label || p.repo === repoChoice.value))) { + vscode.window.showInformationMessage(`PR #${prNumber} in ${repoChoice.label} is already tracked.`); + return; + } + + // Try to fetch details from GitHub + let prTitle = `PR #${prNumber}`; + let prStatus = 'open'; + let prUrl = `https://github.com/${repoChoice.value}/pull/${prNumber}`; + + try { + await switchGhAccount(repoChoice.value); + const json = await runCommand( + `gh pr view ${prNumber} --repo "${repoChoice.value}" --json state,title,url`, + undefined, 15000 + ); + const prData = JSON.parse(json); + const stateMap: Record = { 'OPEN': 'open', 'MERGED': 'merged', 'CLOSED': 'closed' }; + prTitle = prData.title || prTitle; + prStatus = stateMap[prData.state] || prData.state?.toLowerCase() || prStatus; + prUrl = prData.url || prUrl; + } catch { + // gh CLI not available — use defaults + const userTitle = await vscode.window.showInputBox({ + prompt: 'Could not fetch from GitHub. Enter the PR title:', + placeHolder: 'PR title', + }); + if (userTitle) { prTitle = userTitle; } + } + + feature.artifacts.agentPrs.push({ + repo: repoChoice.label, + prNumber, + prUrl, + status: prStatus, + title: prTitle, + }); + + feature.updatedAt = Date.now(); + FeatureDetailPanel.writeState(state); + const fresh = FeatureDetailPanel.readState().features?.find((f: any) => f.id === featureId); + if (fresh) { panel.webview.html = FeatureDetailPanel.getHtml(fresh); } + vscode.window.showInformationMessage(`Added PR #${prNumber}: ${prTitle}`); + } + private static getHtml(feature: any): string { const artifacts: FeatureArtifacts = feature.artifacts || { pbis: [], agentPrs: [] }; const design = artifacts.design; @@ -393,7 +643,7 @@ export class FeatureDetailPanel { // Design section const designHtml = design ? `
    -
    📄 Design Spec
    +
    📄 Design Spec
    ${design.docPath ? `` : ''} ${design.prUrl ? `` : ''} @@ -402,13 +652,13 @@ export class FeatureDetailPanel {
    ` : (feature.designDocPath ? `
    -
    📄 Design Spec
    +
    📄 Design Spec
    ${feature.designPrUrl ? `` : ''}
    ` - : '
    📄 Design Spec

    Not yet created

    '); + : '
    📄 Design Spec

    Not yet created

    '); // PBIs section const hasDeps = pbis.some((p: any) => p.dependsOn && p.dependsOn.length > 0); @@ -438,7 +688,7 @@ export class FeatureDetailPanel { const pbisHtml = pbis.length > 0 ? `
    -
    📋 Product Backlog Items ${pbis.length}
    +
    📋 Product Backlog Items ${pbis.length}
    ${hasDeps ? '' : ''} @@ -462,12 +712,12 @@ export class FeatureDetailPanel {
    OrderAB#TitleRepoDepends OnStatus
    ` - : '
    📋 Product Backlog Items

    No PBIs yet

    '; + : '
    📋 Product Backlog Items

    No PBIs yet

    '; // Agent PRs section const prsHtml = agentPrs.length > 0 ? `
    -
    🤖 Agent Pull Requests ${agentPrs.length}
    +
    🤖 Agent Pull Requests ${agentPrs.length}
    @@ -492,7 +742,7 @@ export class FeatureDetailPanel {
    PRRepoTitleCommentsStatus
    ` - : '
    🤖 Agent Pull Requests

    No agent PRs yet

    '; + : '
    🤖 Agent Pull Requests

    No agent PRs yet

    '; const timeAgo = (ts: number) => { if (!ts) return 'unknown'; @@ -608,6 +858,19 @@ body { align-items: center; gap: 8px; } +.add-btn { + margin-left: auto; + background: none; + border: 1px solid var(--vscode-widget-border); + color: var(--vscode-descriptionForeground); + border-radius: 4px; + padding: 1px 7px; + font-size: 12px; + cursor: pointer; + opacity: 0.6; + transition: opacity 0.2s; +} +.add-btn:hover { opacity: 1; border-color: var(--vscode-focusBorder); color: var(--vscode-foreground); } .artifact-body { padding: 12px 14px; } .artifact-row { margin-bottom: 6px; display: flex; align-items: center; gap: 8px; } .label { color: var(--vscode-descriptionForeground); font-size: 11px; min-width: 70px; text-transform: uppercase; letter-spacing: 0.5px; } @@ -716,6 +979,9 @@ a:hover { text-decoration: underline; } function openUrl(url) { vscode.postMessage({ command: 'openUrl', url }); } function openFile(p) { vscode.postMessage({ command: 'openFile', path: p }); } function continueInChat() { vscode.postMessage({ command: 'continueInChat' }); } + function addDesign() { vscode.postMessage({ command: 'addDesign' }); } + function addPbi() { vscode.postMessage({ command: 'addPbi' }); } + function addAgentPr() { vscode.postMessage({ command: 'addAgentPr' }); } function refresh() { const btn = document.getElementById('refreshBtn'); if (btn) { btn.textContent = '↻ Refreshing...'; btn.disabled = true; } From ff0fa14693d5322a5bad9b621f7fcd8f8229d1d1 Mon Sep 17 00:00:00 2001 From: Shahzaib Date: Sun, 1 Mar 2026 10:41:08 -0800 Subject: [PATCH 16/32] Update state-utils.js --- .github/hooks/state-utils.js | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/.github/hooks/state-utils.js b/.github/hooks/state-utils.js index 84f2742f..8e69f0c7 100644 --- a/.github/hooks/state-utils.js +++ b/.github/hooks/state-utils.js @@ -114,6 +114,19 @@ switch (command) { console.log(JSON.stringify(readState(), null, 2)); break; } + case 'list-features': { + const state = readState(); + const features = state.features.map(f => ({ + id: f.id, + name: f.name, + step: f.step, + pbis: (f.artifacts?.pbis || f.pbis || []).length, + prs: (f.artifacts?.agentPrs || f.agentSessions || []).length, + updatedAt: new Date(f.updatedAt).toISOString(), + })); + console.log(JSON.stringify(features, null, 2)); + break; + } case 'get-feature': { const state = readState(); const feature = findFeature(state, args[0]); @@ -249,6 +262,6 @@ switch (command) { break; } default: - console.error('Usage: state-utils.js [args]'); + console.error('Usage: state-utils.js [args]'); process.exit(1); } From 0b45db3210e81170f702495a1230d594d64dc1e6 Mon Sep 17 00:00:00 2001 From: Shahzaib Date: Sun, 1 Mar 2026 10:41:36 -0800 Subject: [PATCH 17/32] Add prompt files --- .github/agents/feature-orchestrator.agent.md | 37 ++++++++++++--- .github/prompts/feature-backlog.prompt.md | 33 +++++++++++++ .github/prompts/feature-continue.prompt.md | 41 ++++++++++++++++ .github/prompts/feature-design.prompt.md | 31 +++++++++++++ .github/prompts/feature-dispatch.prompt.md | 32 +++++++++++++ .github/prompts/feature-plan.prompt.md | 32 +++++++++++++ .github/prompts/feature-status.prompt.md | 49 ++++++++++++++++++++ 7 files changed, 248 insertions(+), 7 deletions(-) create mode 100644 .github/prompts/feature-backlog.prompt.md create mode 100644 .github/prompts/feature-continue.prompt.md create mode 100644 .github/prompts/feature-design.prompt.md create mode 100644 .github/prompts/feature-dispatch.prompt.md create mode 100644 .github/prompts/feature-plan.prompt.md create mode 100644 .github/prompts/feature-status.prompt.md diff --git a/.github/agents/feature-orchestrator.agent.md b/.github/agents/feature-orchestrator.agent.md index acd6ecbb..710d0512 100644 --- a/.github/agents/feature-orchestrator.agent.md +++ b/.github/agents/feature-orchestrator.agent.md @@ -268,7 +268,7 @@ askQuestion({ ``` ### Monitor Phase -When the user says "status" or "check": +When the user says "status", "check", or asks about PR status: Start with: ``` @@ -277,15 +277,38 @@ Start with: **Pipeline**: ✅ Design → ✅ Plan → ✅ Backlog → ✅ Dispatch → 📡 **Monitor** ``` -Check agent PR status by running terminal commands. +**Step 1: Read feature state** — get the tracked PRs from `state-utils.js`: +```powershell +node .github/hooks/state-utils.js get-feature "" +``` +This returns the feature's `artifacts.agentPrs` array with repo, PR number, URL, and status. +**Only check the PRs listed in the feature state — do NOT scan all repos for all PRs.** + +**Step 2: Check each tracked PR** via `gh`: First discover the developer's GitHub username (check `.github/developer-local.json`, -fall back to `gh auth status`, then prompt if needed): -```bash -gh auth switch --user -gh pr list --repo "AzureAD/microsoft-authentication-library-common-for-android" --author "copilot-swe-agent[bot]" --state all --limit 5 -gh pr list --repo "AzureAD/microsoft-authentication-library-for-android" --author "copilot-swe-agent[bot]" --state all --limit 5 +fall back to `gh auth status`, then prompt if needed). + +For each PR in `artifacts.agentPrs`: +```powershell +gh auth switch --user +gh pr view --repo "" --json state,title,url,statusCheckRollup,additions,deletions,changedFiles,isDraft ``` +Repo slug mapping: +- `common` → `AzureAD/microsoft-authentication-library-common-for-android` +- `msal` → `AzureAD/microsoft-authentication-library-for-android` +- `broker` → `identity-authnz-teams/ad-accounts-for-android` +- `adal` → `AzureAD/azure-activedirectory-library-for-android` + +**Step 3: Present results** as a table with: PR #, repo, title, status, checks, +/- lines. + +**Step 4: Update state** with latest PR statuses: +```powershell +node .github/hooks/state-utils.js add-agent-pr "" '{"repo":"...","prNumber":,"prUrl":"...","status":"","title":"..."}' +``` + +End with: "Use `@copilot` in PR comments to iterate with the coding agent." + ## File Path Handling Design docs use brackets and spaces in folder names (e.g., `design-docs/[Android] Feature Name/`). diff --git a/.github/prompts/feature-backlog.prompt.md b/.github/prompts/feature-backlog.prompt.md new file mode 100644 index 00000000..aaba9f1c --- /dev/null +++ b/.github/prompts/feature-backlog.prompt.md @@ -0,0 +1,33 @@ +--- +agent: feature-orchestrator +description: "Create approved PBIs as work items in Azure DevOps" +--- + +# Backlog Phase + +Read the PBI creator skill from #file:.github/skills/pbi-creator/SKILL.md + +## Your Task + +You are in the **Backlog** phase. The plan has been approved and you need to create PBIs in ADO. + +**Step 1**: Read the feature state: +```powershell +node .github/hooks/state-utils.js get-feature "" +``` + +**Step 2**: Follow the **Creation Phase** instructions from the orchestrator agent: +1. Pass the FULL plan to the `pbi-creator` subagent +2. The pbi-creator will discover ADO defaults, present options via `askQuestion`, and create work items +3. Present the creation summary with AB# IDs +4. Use `askQuestion` to gate the next stage + +**Pipeline**: ✅ Design → ✅ Plan → 📝 **Backlog** → ○ Dispatch → ○ Monitor + +After PBIs are created, update state for EACH PBI: +```powershell +node .github/hooks/state-utils.js set-step "" backlog_review +node .github/hooks/state-utils.js add-pbi "" '{"adoId":,"title":"...","module":"...","status":"Committed","dependsOn":[]}' +``` + +**IMPORTANT**: Use single quotes for JSON args in PowerShell. diff --git a/.github/prompts/feature-continue.prompt.md b/.github/prompts/feature-continue.prompt.md new file mode 100644 index 00000000..6c805315 --- /dev/null +++ b/.github/prompts/feature-continue.prompt.md @@ -0,0 +1,41 @@ +--- +agent: feature-orchestrator +description: "Resume working on a feature from its current pipeline step" +--- + +# Continue Feature + +## Your Task + +Resume working on a feature. The user will provide the feature name below. + +**Step 1**: Read the feature state: +```powershell +node .github/hooks/state-utils.js get-feature "" +``` + +**Step 2**: Determine the current step from the `step` field and resume from there: + +| Step | What to do | +|------|-----------| +| `designing` | Continue writing the design spec | +| `design_review` | Design is done — ask user if they want to plan PBIs. Use `askQuestion`. | +| `planning` | Continue planning PBIs | +| `plan_review` | Plan is done — ask user if they want to backlog in ADO. Use `askQuestion`. | +| `backlogging` | Continue creating PBIs in ADO | +| `backlog_review` | PBIs created — ask user if they want to dispatch. Use `askQuestion`. | +| `dispatching` | Continue dispatching | +| `monitoring` | Check PR status (follow Monitor phase instructions) | + +**Step 3**: Show the pipeline progress header: +``` +## 🚀 Feature Orchestration: [Phase Name] + +**Feature**: [feature name] +**Pipeline**: [show ✅/📋/○ for each stage based on current step] +``` + +Read `.github/copilot-instructions.md` for project context. + +**IMPORTANT**: Use single quotes for JSON args in PowerShell. +Always update state after completing a phase step. diff --git a/.github/prompts/feature-design.prompt.md b/.github/prompts/feature-design.prompt.md new file mode 100644 index 00000000..8ce891f0 --- /dev/null +++ b/.github/prompts/feature-design.prompt.md @@ -0,0 +1,31 @@ +--- +agent: feature-orchestrator +description: "Start a new feature: research the codebase and write a design spec" +--- + +# Design Phase + +## Your Task + +You are in the **Design** phase. The user will describe a feature below. + +**Step 0**: Register the feature in state: +```powershell +node .github/hooks/state-utils.js add-feature '{"name": "", "step": "designing"}' +``` + +Then follow the **Full Flow** instructions from the orchestrator agent: +1. Run the `codebase-researcher` subagent with a detailed prompt +2. Pass the FULL research output to the `design-writer` subagent +3. Present the design summary and use `askQuestion` to offer next steps + +**Pipeline**: 📝 **Design** → ○ Plan → ○ Backlog → ○ Dispatch → ○ Monitor + +Read `.github/copilot-instructions.md` for project context. + +**IMPORTANT**: Use single quotes for JSON args in PowerShell. +After the design is complete, update state: +```powershell +node .github/hooks/state-utils.js set-step "" design_review +node .github/hooks/state-utils.js set-design "" '{"docPath":"","status":"approved"}' +``` diff --git a/.github/prompts/feature-dispatch.prompt.md b/.github/prompts/feature-dispatch.prompt.md new file mode 100644 index 00000000..c04487b7 --- /dev/null +++ b/.github/prompts/feature-dispatch.prompt.md @@ -0,0 +1,32 @@ +--- +agent: feature-orchestrator +description: "Dispatch PBIs to GitHub Copilot coding agent for implementation" +--- + +# Dispatch Phase + +Read the dispatcher skill from #file:.github/skills/pbi-dispatcher/SKILL.md + +## Your Task + +You are in the **Dispatch** phase. PBIs have been created in ADO and you need to dispatch them. + +**Step 1**: Read the feature state to get PBI details: +```powershell +node .github/hooks/state-utils.js get-feature "" +``` + +**Step 2**: Follow the **Dispatch Phase** instructions from the orchestrator agent: +1. Run the `agent-dispatcher` subagent to dispatch PBIs to Copilot coding agent +2. Record each dispatched PR in state +3. Use `askQuestion` to gate the next stage + +**Pipeline**: ✅ Design → ✅ Plan → ✅ Backlog → 🚀 **Dispatch** → ○ Monitor + +After dispatch, update state: +```powershell +node .github/hooks/state-utils.js set-step "" monitoring +node .github/hooks/state-utils.js add-agent-pr "" '{"repo":"