Repository navigation
extensions: add Azure DevOps Team load canvas 🤖🤖🤖 - #6323
anarchitect wants to merge 1 commit into
Conversation
Show API-visible Azure Boards ownership and Azure Repos review work through shared canvas actions. Keep profiles independent of workspaces, use process state categories and expose coverage failures. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
🔒 PR Risk Scan ResultsScanned 15 changed file(s).
✅ No matching risk patterns were detected in changed files. Skipped non-text or missing files
|
🚦 Submission status: 👀 Ready for reviewRisk tier: Why this tier
Automated checks
Review
Commands
Updated for |
|
🟡 Contributor Reputation Check: MEDIUM risk
Maintainers: please review this contributor before merging. |
🧩 Canvas smoke test✅ Passed · 0 error(s), 1 warning(s)
Plugin
|
| Field | Value |
|---|---|
| Description | Read-only Azure DevOps team open work canvas with scope pickers, ownership, PR reviews and explicit coverage. |
| Author | anarchitect — https://github.com/anarchitect |
| License | MIT |
| Keywords | azure-devops, azure-boards, azure-repos, team-load, work-item-ownership, pull-request-reviews, canvas |
| Extensions | ado-personal-team-load |
| Materialize | ✅ |
| Install | ✅ |
Changed plugin files
plugins/ado-personal-team-load/README.mdplugins/ado-personal-team-load/plugin.json
Extension ado-personal-team-load
| Field | Value |
|---|---|
| Registered by | ado-personal-team-load |
| Package | — |
| Preview | 1280×720, 42.7 KB |
| Modules | 9 (6 reachable from extension.mjs) |
| Files | 13 |
Permissions and capabilities (static analysis of modules reachable from extension.mjs)
- Spawns processes (node:child_process)
- File system access (node:fs)
- Network sockets / local server (node:http, node:net, ...)
- Host information (node:os)
- Reads environment variables (process.env)
Warnings
- tests.mjs: import "node:test" is not a Node.js builtin, host-provided package, or declared dependency (module is not reachable from extension.mjs)
Changed extension files
extensions/ado-personal-team-load/README.mdextensions/ado-personal-team-load/adapter.mjsextensions/ado-personal-team-load/assets/app.mjsextensions/ado-personal-team-load/assets/index.htmlextensions/ado-personal-team-load/assets/preview.pngextensions/ado-personal-team-load/assets/styles.cssextensions/ado-personal-team-load/extension.mjsextensions/ado-personal-team-load/hub.mjsextensions/ado-personal-team-load/model.mjsextensions/ado-personal-team-load/schemas.mjsextensions/ado-personal-team-load/server.mjsextensions/ado-personal-team-load/tests.mjsextensions/ado-personal-team-load/verify-live.mjs
Static checks compile modules without executing them. Minimum preview size is configurable with CANVAS_PREVIEW_MIN_WIDTH/CANVAS_PREVIEW_MIN_HEIGHT. Workflow run
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved critical issues expose private work data to other local accounts and can silently clear saved scope selections or freeze workload counts after partial refresh failures.
14 open issues
Update checked controls when clearing scope selections · New Publish newer partial results when no complete scan exists · New Require complete configuration for replacement operations · New Require a bootstrap capability before exposing private actions · New Clear the cached token after an HTTP 401 · New Run the organization fallback only when discovery finds none · New Preserve drafts and base revision during external updates · New Preserve keyboard focus across automatic refreshes · New Disambiguate repository labels across projects · New Keep resolved selections keyed by GUID when directory reads fail · New Avoid returning the full user directory in every SDK response · New Resolve saved GUIDs through the directory during discovery · New Keep catalogs and refresh results scoped to the requested organization · New Exclude container reviewers from person identities · New
What changed in this PR
Adds a reusable, read-only Azure DevOps canvas to the extension catalog for inspecting work ownership and PR review workload across selected projects.
Changes:
- Adds Azure CLI authentication, resource discovery and identity-based work counting.
- Provides scope pickers, expandable records, refresh controls, coverage reporting and persistent private profiles.
- Adds tests, live verification tooling, documentation and plugin registration.
| File | Description |
|---|---|
plugins/ado-personal-team-load/README.md |
Introduces the plugin and links its usage guide. |
plugins/ado-personal-team-load/plugin.json |
Defines plugin metadata and extension composition. |
extensions/ado-personal-team-load/verify-live.mjs |
Compares displayed counts with independent API reads. |
extensions/ado-personal-team-load/tests.mjs |
Covers counting, paging, persistence, actions and UI behavior. |
extensions/ado-personal-team-load/server.mjs |
Serves canvas assets and routes local actions. |
extensions/ado-personal-team-load/schemas.mjs |
Defines shared action schemas and validation. |
extensions/ado-personal-team-load/README.md |
Documents installation, authentication, counting and verification. |
extensions/ado-personal-team-load/model.mjs |
Computes identity-based totals and threshold labels. |
extensions/ado-personal-team-load/hub.mjs |
Manages profiles, discovery, refreshes and coverage state. |
extensions/ado-personal-team-load/extension.mjs |
Registers SDK actions and canvas lifecycle handlers. |
extensions/ado-personal-team-load/assets/styles.css |
Adds responsive layouts, themes and focus styling. |
extensions/ado-personal-team-load/assets/index.html |
Defines scope controls, workload panels and coverage details. |
extensions/ado-personal-team-load/assets/app.mjs |
Implements selections, rendering, actions and refresh polling. |
extensions/ado-personal-team-load/adapter.mjs |
Handles CLI tokens, Azure DevOps APIs, state mappings and paging. |
docs/README.plugins.md |
Adds the generated plugin catalog entry. |
.github/plugin/marketplace.json |
Registers the plugin in the marketplace. |
Review effort: Max (auto)
| $("organisation").addEventListener("change", () => { $("organisation-url").value = ""; chosenProjects.clear(); chosenRepositories.clear(); chosenPeople.clear(); }); | ||
| $("organisation-url").addEventListener("change", () => { chosenProjects.clear(); chosenRepositories.clear(); chosenPeople.clear(); }); |
| if (record.revision !== revision) throw new AdoError("conflict", "Scope changed during refresh. Refresh the new scope."); | ||
| refreshErrors = errors; | ||
| // Keep an earlier complete snapshot on failure, with an explicit stale marker. | ||
| if (!errors.length || !snapshot) { |
| config: object({ ...scopeProperties, repositories: ids(30), people: ids(100), | ||
| threshold: { type: "integer", minimum: 1, maximum: 1000 }, | ||
| refreshSeconds: { type: "integer", minimum: 60, maximum: 86400 } }), |
| if (req.method === "GET" && (path === "/" || assets.has(path))) { | ||
| const name = path === "/" ? "/index.html" : path; | ||
| res.setHeader("Content-Type", name.endsWith(".html") ? "text/html; charset=utf-8" : name.endsWith(".css") ? "text/css" : "text/javascript"); | ||
| res.end(assets.get(name).replace("{{CANVAS_KEY}}", key)); return; |
| throw new AdoError(response.status === 401 ? "authentication" : response.status === 403 ? "access" : "api", | ||
| `Azure DevOps returned HTTP ${response.status} for ${parsed.pathname}. No data from this request is counted.`); |
| for (const p of identities) if (p.id && p.descriptor && chosenPeople.has(p.id)) { chosenPeople.delete(p.id); chosenPeople.add(p.descriptor); } | ||
| const matches = identities.filter(p => `${p.displayName} ${p.uniqueName} ${p.id || p.descriptor}`.toLowerCase().includes(search)); | ||
| choices($("people"), matches.slice(0, 100), | ||
| chosenPeople, p => p.descriptor || p.id, p => p.displayName || p.id); |
| return known ? { ...row, displayName: known.displayName, uniqueName: known.uniqueName } : row; | ||
| }); | ||
| const stale = Boolean(snapshot && (Date.now() - Date.parse(snapshot.at) > c.refreshSeconds * 1000 || snapshot.retained)); | ||
| return { profile, config: c, revision: record.revision, catalog, ...result, people, |
| const observed = org === c.organisation ? (snapshot?.result.identities || []) : []; | ||
| let people = observed; | ||
| try { | ||
| people = await adapter.people(org, tenant); |
| catalog.repositories = knownRepos; | ||
| for (const p of result.identities) if (!catalog.people.some(x => x.id === p.id || (x.descriptor && x.descriptor === p.descriptor))) catalog.people.push(p); |
| identities.set(id, { id, displayName: p.displayName || id, uniqueName: p.uniqueName || "", descriptor: p.descriptor || "" }); | ||
| } | ||
| for (const w of workItems) addIdentity(w.owner); | ||
| for (const p of prs) { addIdentity(p.author); p.reviewers.forEach(addIdentity); } |



What this adds
A reusable Copilot canvas for Azure DevOps Services team open work. It reads Azure Boards ownership and Azure Repos pull requests through supported REST APIs using the existing Azure CLI identity.
The shared UI and SDK actions use the same dispatcher. Tokens remain server-side. The canvas makes no remote writes and does not estimate hours or Teams availability.
Why it is different
The interaction pattern is inspired by the pinned TinyClips Team load investigation. This contribution implements an Azure DevOps adapter and identity/state model; it has no TinyClips workspace assumption or preconfigured organisation, project or people.
Project-specific work item type/state categories determine which work is open. Work assigned to untracked people remains owned. Active drafts count, completed review votes and group requests do not count as outstanding individual reviews, and PR roles are deduplicated.
WIQL reads omit
$topbecause permission filtering can leave a short or empty truncated page before later readable records. Queries exceeding the 20,000-result limit split disjoint ID ranges, and detail batches use ASOF snapshots. Coverage is limited to the selected API-visible resources; hidden resources cannot be quantified.Validation
npm run plugin:validatepassed for all 111 plugins, including this one.npm run buildpassed and generated the marketplace entry and plugin README row.bash eng/fix-line-endings.shcompleted.node --test extensions/ado-personal-team-load/tests.mjspassed all 21 tests. Module syntax checks andgit diff --cached --checkpassed.open_canvas, SDK actions, provider restart and reopening the same instance/profile.The live verification scope had no active PRs. Nonempty PR roles, reviewer votes and PR paging are covered by isolated tests, while the live empty PR API reads were authorised.
Boundaries
This first version is read-only and supports Azure DevOps Services, not Azure DevOps Server. It intentionally omits remote editing, momentum charts, agent handoff and execution markers. PR/reviewer endpoints do not provide a shared transaction snapshot.
The extension adds no npm dependencies. The repository dependency restore reported existing audit advisories; this PR does not change package manifests or its lockfile.
Source, installation commands, API references and the independent verification command are documented in
extensions/ado-personal-team-load/README.md. The matching curated manifest is inplugins/ado-personal-team-load/plugin.json.AI-assisted implementation and validation, submitted at the user's request.