diff --git a/.gitconfig b/.gitconfig index 7afdef01..1c660757 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 -b dev 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 ..; \ diff --git a/.github/agents/agent-dispatcher.agent.md b/.github/agents/agent-dispatcher.agent.md new file mode 100644 index 00000000..0bdc15e8 --- /dev/null +++ b/.github/agents/agent-dispatcher.agent.md @@ -0,0 +1,53 @@ +--- +name: agent-dispatcher +description: Dispatch Azure DevOps PBIs to GitHub Copilot coding agent for implementation. +user-invokable: false +--- + +# 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. **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/codebase-researcher.agent.md b/.github/agents/codebase-researcher.agent.md new file mode 100644 index 00000000..5fb787d9 --- /dev/null +++ b/.github/agents/codebase-researcher.agent.md @@ -0,0 +1,31 @@ +--- +name: codebase-researcher +description: Research the Android Auth codebase to understand existing implementations, patterns, and architecture. +user-invokable: false +--- + +# 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 +- **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 new file mode 100644 index 00000000..f8c6add6 --- /dev/null +++ b/.github/agents/design-writer.agent.md @@ -0,0 +1,35 @@ +--- +name: design-writer +description: Write detailed design specs for Android Auth features following the team's template. +user-invokable: false +--- + +# 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` +- **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 new file mode 100644 index 00000000..915e0a5f --- /dev/null +++ b/.github/agents/feature-orchestrator.agent.md @@ -0,0 +1,320 @@ +--- +description: End-to-end AI-driven feature development for Android Auth. Design → Plan → Backlog → Dispatch → Monitor. +agents: + - codebase-researcher + - design-writer + - feature-planner + - pbi-creator + - agent-dispatcher +--- + +# 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 → Backlog → Dispatch → Monitor**. + +## How You Work + +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 — 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. **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 + +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 +- 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 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". + Use `askQuestion` for ALL user choices. +- **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: + ``` + 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) + +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", "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 + +### 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. + +### 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. + +**Resilience**: If any `state-utils.js` command fails (e.g., `node` not found, file permission +error, feature not found), **do NOT stop or ask the user about it**. Log the failure silently +and continue with the actual work. State tracking is a nice-to-have for the dashboard — the +core pipeline (design, plan, backlog, dispatch) must never be blocked by state tracking failures. + +Start with: +``` +## 🚀 Feature Orchestration Started + +**Feature**: [user's feature description] + +I'll walk you through: **Design** → **Plan** → **Backlog** → **Dispatch** → **Monitor** + +--- + +### Step 1: Writing Design Spec +``` + +Then: +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": + +Start with: +``` +## 🚀 Feature Orchestration: Planning + +**Pipeline**: ✅ Design → 📋 **Plan** → ○ Backlog → ○ Dispatch → ○ Monitor +``` + +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 + +After presenting the plan summary, use `askQuestion` to gate the next stage: +``` +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 +When the user approves the plan or says "backlog the PBIs" / "create the PBIs": + +Start with: +``` +## 🚀 Feature Orchestration: Backlog + +**Pipeline**: ✅ Design → ✅ Plan → 📝 **Backlog** → ○ Dispatch → ○ Monitor +``` + +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 and mark as Committed +3. Present the creation summary with AB# IDs + +After presenting the AB# summary, use `askQuestion` to gate the next stage: +``` +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 +When the user approves PBIs or says "dispatch": + +Start with: +``` +## 🚀 Feature Orchestration: Dispatch + +**Pipeline**: ✅ Design → ✅ Plan → ✅ Backlog → 🚀 **Dispatch** → ○ Monitor +``` + +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", "check", or asks about PR status: + +Start with: +``` +## 🚀 Feature Orchestration: Monitor + +**Pipeline**: ✅ Design → ✅ Plan → ✅ Backlog → ✅ Dispatch → 📡 **Monitor** +``` + +**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). + +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/`). +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..2e645af9 --- /dev/null +++ b/.github/agents/feature-planner.agent.md @@ -0,0 +1,24 @@ +--- +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 +--- + +# 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..f4383e93 --- /dev/null +++ b/.github/agents/pbi-creator.agent.md @@ -0,0 +1,40 @@ +--- +name: pbi-creator +description: Create Azure DevOps PBIs from an approved feature plan for the Android Auth project. +user-invokable: false +--- + +# 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 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 +- **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 +- **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 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 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..d9a027c2 --- /dev/null +++ b/.github/hooks/state-utils.js @@ -0,0 +1,273 @@ +#!/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 '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]); + 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(); + // Record phase timestamp for duration tracking + if (!feature.phaseTimestamps) { feature.phaseTimestamps = {}; } + feature.phaseTimestamps[args[1]] = 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 { + // Record initial phase timestamp + const initialStep = feature.step || 'idle'; + const phaseTimestamps = { [initialStep]: Date.now() }; + state.features.push({ ...feature, startedAt: Date.now(), updatedAt: Date.now(), phaseTimestamps }); + } + 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 })); 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":"