From 84f074027cd11436cd0d15b7347d35a7c8ea183d Mon Sep 17 00:00:00 2001 From: Sanjay Singh Date: Wed, 2 Sep 2026 17:39:10 +0200 Subject: [PATCH 1/2] feat: package canvas as an installable apm package Move the GitHub Copilot canvas source to .apm/extensions/ai-engineer-coach/ and add a root apm.yml manifest, following the danielmeppiel/finops-workshop pattern requested in #163. Other projects can now install the dashboard canvas with: apm experimental enable canvas apm install microsoft/AI-Engineering-Coach --target copilot --trust-canvas-extensions .github/extensions/ai-engineer-coach/extension.mjs becomes a one-line forwarding stub to the .apm/ source, so cloning and building this repo directly keeps working with no apm dependency for local development. Resolves #163 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../extensions/ai-engineer-coach/README.md | 14 +- .../ai-engineer-coach/extension.mjs | 197 ++++++++++++++++++ .../ai-engineer-coach/extension.mjs | 197 +----------------- .gitignore | 3 + README.md | 18 +- apm.yml | 12 ++ 6 files changed, 249 insertions(+), 192 deletions(-) rename {.github => .apm}/extensions/ai-engineer-coach/README.md (61%) create mode 100644 .apm/extensions/ai-engineer-coach/extension.mjs create mode 100644 apm.yml diff --git a/.github/extensions/ai-engineer-coach/README.md b/.apm/extensions/ai-engineer-coach/README.md similarity index 61% rename from .github/extensions/ai-engineer-coach/README.md rename to .apm/extensions/ai-engineer-coach/README.md index 03412bb1..030cefab 100644 --- a/.github/extensions/ai-engineer-coach/README.md +++ b/.apm/extensions/ai-engineer-coach/README.md @@ -2,6 +2,15 @@ Runs the AI Engineer Coach dashboard as a canvas inside the GitHub Copilot app, reusing the exact webview bundle that ships in the VS Code extension. +This directory is the [`apm`](https://github.com/microsoft/apm) package source of truth for the +canvas (see the repo root [`apm.yml`](../../../apm.yml)). This repo loads it directly through a +one-line forwarding stub at [`.github/extensions/ai-engineer-coach/extension.mjs`](../../../.github/extensions/ai-engineer-coach/extension.mjs) +(the GitHub Copilot app only discovers canvases under `.github/extensions/`), so cloning and +building this repo works with no extra steps. Other projects can install the same canvas with +`apm install --target copilot --trust-canvas-extensions`, which deploys a copy of this +folder into their own `.github/extensions/ai-engineer-coach/`. Edit the canvas logic here, not +in the forwarding stub. + ## What it does - Opens a side-panel canvas titled **AI Engineer Coach**. @@ -30,7 +39,10 @@ Everything driven purely by your on-disk logs — Dashboard, Timeline, Coding Mo ## How it is wired -- `extension.mjs` owns build detection and a single `127.0.0.1` HTTP server, and declares the canvas via `createCanvas` / `joinSession`. +- `.github/extensions/ai-engineer-coach/extension.mjs` is a one-line forwarding stub (`import + "../../../.apm/extensions/ai-engineer-coach/extension.mjs";`) so the GitHub Copilot app's + project-canvas discovery — which only scans `.github/extensions/` — finds this package. +- `extension.mjs` (this directory) owns build detection and a single `127.0.0.1` HTTP server, and declares the canvas via `createCanvas` / `joinSession`. - `dist/canvas-host.cjs` (built from `src/canvas/host.ts`) provides the request handler: the dashboard shell, the asset routes, an SSE channel for parse progress, and the `/rpc` endpoint. - The dashboard talks to the host through an injected `acquireVsCodeApi` shim, so the webview code is unchanged between VS Code and canvas. diff --git a/.apm/extensions/ai-engineer-coach/extension.mjs b/.apm/extensions/ai-engineer-coach/extension.mjs new file mode 100644 index 00000000..2f98d72c --- /dev/null +++ b/.apm/extensions/ai-engineer-coach/extension.mjs @@ -0,0 +1,197 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See LICENSE in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +/* Canvas extension entry point. Serves the AI Engineer Coach dashboard as a + * Copilot app canvas. Owns a single loopback HTTP server and decides per request + * whether the project is built: if not, it serves a setup guide; once built, it + * hands every request to the bundled canvas host (dist/canvas-host.cjs). */ + +import http from "node:http"; +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { createRequire } from "node:module"; +import { joinSession, createCanvas } from "@github/copilot-sdk/extension"; + +const require = createRequire(import.meta.url); + +// Anchor to this file's location (.apm/extensions/ai-engineer-coach — the apm package +// source, loaded either via a forwarding stub at .github/extensions/ai-engineer-coach/ in +// this repo, or via a copy `apm install --target copilot` deploys into a consumer's +// .github/extensions/), not the process cwd: a forked canvas extension runs with cwd set +// to the Copilot home. +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const repoRoot = path.resolve(__dirname, "../../.."); +const distDir = path.join(repoRoot, "dist"); +const repoName = path.basename(repoRoot); + +const appJsPath = path.join(distDir, "webview", "app.js"); +const hostCjsPath = path.join(distDir, "canvas-host.cjs"); + +const BUILD_COMMAND = "npm install && npm run build"; + +function isBuilt() { + return fs.existsSync(appJsPath) && fs.existsSync(hostCjsPath); +} + +let host; +function ensureHost() { + if (host) return host; + if (!isBuilt()) return undefined; + // A stale or malformed build can throw on require/start; swallow it so the + // request handler falls through to the recoverable setup page instead of + // crashing the forked extension process. + try { + const { createCanvasHost } = require(hostCjsPath); + host = createCanvasHost({ distDir, repoName }); + host.start(); + return host; + } catch (err) { + host = undefined; + console.error(`AI Engineer Coach: failed to start canvas host. Run: ${BUILD_COMMAND}`, err); + return undefined; + } +} + +const server = http.createServer((req, res) => { + const url = (req.url || "/").split("?")[0]; + + if (url === "/status") { + res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" }); + res.end(JSON.stringify({ built: ensureHost() !== undefined })); + return; + } + + const active = ensureHost(); + if (active) { + active.handle(req, res); + return; + } + + if (url === "/" || url === "/index.html") { + res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" }); + res.end(setupHtml()); + return; + } + + res.writeHead(503, { "Content-Type": "text/plain" }); + res.end("Build required"); +}); + +server.listen(0, "127.0.0.1"); +await new Promise((resolve) => server.once("listening", resolve)); +const baseUrl = `http://127.0.0.1:${server.address().port}`; + +const canvas = createCanvas({ + id: "dashboard", + displayName: "AI Engineer Coach", + description: + "Local AI coding analytics dashboard: sessions, output, anti-patterns, and context health derived from your on-disk agent logs. Agent-dependent features are read-only in canvas mode.", + inputSchema: { + type: "object", + description: + "No input required. The dashboard reads your local AI coding session logs from disk.", + properties: {}, + additionalProperties: false, + }, + open() { + return { + url: baseUrl, + title: "AI Engineer Coach", + status: isBuilt() ? "Loading dashboard" : "Build required", + }; + }, +}); + +const session = await joinSession({ canvases: [canvas] }); +await session.log( + isBuilt() + ? `AI Engineer Coach canvas ready for session ${session.sessionId}.` + : `AI Engineer Coach canvas loaded, build required. Run: ${BUILD_COMMAND}`, +); + +function setupHtml() { + return ` + + + + +AI Engineer Coach — Setup + + + +
+ + + + +

Build required

+

This project has not been built yet. Build it once, then this panel loads the full dashboard automatically.

+
+ ${BUILD_COMMAND} + +
+
    +
  1. Open a terminal in the repository root (${repoName}).
  2. +
  3. Run the command above to install dependencies and build.
  4. +
  5. This panel detects the build and reloads on its own.
  6. +
+
+ + Watching for a completed build... +
+
+ Skill Finder, Learning quizzes, and context review need the local VS Code agent. They appear read-only in canvas mode. +
+
+ + +`; +} diff --git a/.github/extensions/ai-engineer-coach/extension.mjs b/.github/extensions/ai-engineer-coach/extension.mjs index 8479c369..0500abff 100644 --- a/.github/extensions/ai-engineer-coach/extension.mjs +++ b/.github/extensions/ai-engineer-coach/extension.mjs @@ -3,192 +3,11 @@ * Licensed under the MIT License. See LICENSE in the project root for license information. *--------------------------------------------------------------------------------------------*/ -/* Canvas extension entry point. Serves the AI Engineer Coach dashboard as a - * Copilot app canvas. Owns a single loopback HTTP server and decides per request - * whether the project is built: if not, it serves a setup guide; once built, it - * hands every request to the bundled canvas host (dist/canvas-host.cjs). */ - -import http from "node:http"; -import fs from "node:fs"; -import path from "node:path"; -import { fileURLToPath } from "node:url"; -import { createRequire } from "node:module"; -import { joinSession, createCanvas } from "@github/copilot-sdk/extension"; - -const require = createRequire(import.meta.url); - -// Anchor to this file's location (.github/extensions/ai-engineer-coach), not the -// process cwd: a forked canvas extension runs with cwd set to the Copilot home. -const __dirname = path.dirname(fileURLToPath(import.meta.url)); -const repoRoot = path.resolve(__dirname, "../../.."); -const distDir = path.join(repoRoot, "dist"); -const repoName = path.basename(repoRoot); - -const appJsPath = path.join(distDir, "webview", "app.js"); -const hostCjsPath = path.join(distDir, "canvas-host.cjs"); - -const BUILD_COMMAND = "npm install && npm run build"; - -function isBuilt() { - return fs.existsSync(appJsPath) && fs.existsSync(hostCjsPath); -} - -let host; -function ensureHost() { - if (host) return host; - if (!isBuilt()) return undefined; - // A stale or malformed build can throw on require/start; swallow it so the - // request handler falls through to the recoverable setup page instead of - // crashing the forked extension process. - try { - const { createCanvasHost } = require(hostCjsPath); - host = createCanvasHost({ distDir, repoName }); - host.start(); - return host; - } catch (err) { - host = undefined; - console.error(`AI Engineer Coach: failed to start canvas host. Run: ${BUILD_COMMAND}`, err); - return undefined; - } -} - -const server = http.createServer((req, res) => { - const url = (req.url || "/").split("?")[0]; - - if (url === "/status") { - res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" }); - res.end(JSON.stringify({ built: ensureHost() !== undefined })); - return; - } - - const active = ensureHost(); - if (active) { - active.handle(req, res); - return; - } - - if (url === "/" || url === "/index.html") { - res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" }); - res.end(setupHtml()); - return; - } - - res.writeHead(503, { "Content-Type": "text/plain" }); - res.end("Build required"); -}); - -server.listen(0, "127.0.0.1"); -await new Promise((resolve) => server.once("listening", resolve)); -const baseUrl = `http://127.0.0.1:${server.address().port}`; - -const canvas = createCanvas({ - id: "dashboard", - displayName: "AI Engineer Coach", - description: - "Local AI coding analytics dashboard: sessions, output, anti-patterns, and context health derived from your on-disk agent logs. Agent-dependent features are read-only in canvas mode.", - inputSchema: { - type: "object", - description: - "No input required. The dashboard reads your local AI coding session logs from disk.", - properties: {}, - additionalProperties: false, - }, - open() { - return { - url: baseUrl, - title: "AI Engineer Coach", - status: isBuilt() ? "Loading dashboard" : "Build required", - }; - }, -}); - -const session = await joinSession({ canvases: [canvas] }); -await session.log( - isBuilt() - ? `AI Engineer Coach canvas ready for session ${session.sessionId}.` - : `AI Engineer Coach canvas loaded, build required. Run: ${BUILD_COMMAND}`, -); - -function setupHtml() { - return ` - - - - -AI Engineer Coach — Setup - - - -
- - - - -

Build required

-

This project has not been built yet. Build it once, then this panel loads the full dashboard automatically.

-
- ${BUILD_COMMAND} - -
-
    -
  1. Open a terminal in the repository root (${repoName}).
  2. -
  3. Run the command above to install dependencies and build.
  4. -
  5. This panel detects the build and reloads on its own.
  6. -
-
- - Watching for a completed build... -
-
- Skill Finder, Learning quizzes, and context review need the local VS Code agent. They appear read-only in canvas mode. -
-
- - -`; -} +/* Local-dev loader stub. The GitHub Copilot app only discovers project canvases under + * .github/extensions//extension.mjs, but this repo's canvas is authored as an apm + * package (see ../../../apm.yml) with its source of truth under .apm/extensions/. This stub + * forwards to that source so cloning + building this repo directly (without `apm install`) + * keeps working. Do not edit the canvas logic here — edit + * ../../../.apm/extensions/ai-engineer-coach/extension.mjs instead. */ + +import "../../../.apm/extensions/ai-engineer-coach/extension.mjs"; diff --git a/.gitignore b/.gitignore index 529efec3..3606a9b5 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,6 @@ marketing/ # Playwright test results test-results/ + +# apm — installed dependency tree (created by `apm install` when apm.yml declares dependencies) +apm_modules/ diff --git a/README.md b/README.md index db9bd48b..46ab126d 100644 --- a/README.md +++ b/README.md @@ -123,9 +123,23 @@ After install: The same dashboard also runs as a canvas inside the GitHub Copilot app, so you do not need VS Code to use it. -A canvas is an interactive side panel in the GitHub Copilot app. Rather than replying only in chat, the agent can open a canvas to show rich, task-specific UI that you view and interact with directly while you keep working. Extensions register their own canvases, and this repo ships one named **AI Engineer Coach** under [`.github/extensions/ai-engineer-coach/`](.github/extensions/ai-engineer-coach/). It reuses the exact webview bundle from the VS Code extension and parses your local session logs in process, so nothing leaves your machine. +A canvas is an interactive side panel in the GitHub Copilot app. Rather than replying only in chat, the agent can open a canvas to show rich, task-specific UI that you view and interact with directly while you keep working. This repo ships one named **AI Engineer Coach**, packaged as an [`apm`](https://github.com/microsoft/apm) package under [`.apm/extensions/ai-engineer-coach/`](.apm/extensions/ai-engineer-coach/). It reuses the exact webview bundle from the VS Code extension and parses your local session logs in process, so nothing leaves your machine. -To open it: +### Install into another project (apm) + +From any other project you have open in the GitHub Copilot app: + +```bash +# one-time: turn on apm's experimental canvas support +apm experimental enable canvas + +# install this repo's canvas into the current project +apm install microsoft/AI-Engineering-Coach --target copilot --trust-canvas-extensions +``` + +Relaunch the GitHub Copilot app, then open the **AI Engineer Coach** canvas. `--trust-canvas-extensions` is required because the canvas is executable Node.js code. + +### Run it from this repo directly 1. Clone this repo and open it as a project in the GitHub Copilot app. 2. Build the project once: diff --git a/apm.yml b/apm.yml new file mode 100644 index 00000000..006dd200 --- /dev/null +++ b/apm.yml @@ -0,0 +1,12 @@ +name: ai-engineer-coach +version: 0.1.0 +description: Local AI coding analytics dashboard — sessions, output, anti-patterns, and context health derived from your on-disk agent logs. Read-only, zero telemetry. +author: microsoft +# Copilot-only: the canvas under .apm/extensions/ is a GitHub Copilot CLI/app extension. +targets: + - copilot +dependencies: + apm: [] + mcp: [] +includes: auto +scripts: {} From 6e64bf373da41236ff69970582d25f3884786624 Mon Sep 17 00:00:00 2001 From: Sanjay Singh Date: Wed, 2 Sep 2026 17:51:19 +0200 Subject: [PATCH 2/2] chore(deps): add 7-day cooldown to npm Dependabot updates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mirrors the existing github-actions cooldown so Dependabot never proposes bumping to an npm package version published less than 7 days ago. This addresses the root cause of local 'npm install' 404s against the corporate npm proxy (e.g. zod@4.5.4, published within days of this repo's Dependabot bump): packages that fresh aren't mirrored by the proxy yet. Reverted the ad-hoc package.json/lockfile version pins from this session — hand-editing them would embed this sandbox's internal-only proxy tarball URLs into package-lock.json's resolved fields, breaking installs for every other environment (CI and contributors use the public npm registry directly). The cooldown is the correct, environment-agnostic fix; the proxy will have mirrored a version by the time Dependabot's next update PR uses it. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .github/dependabot.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 97836541..58978436 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,5 +1,7 @@ updates: - directory: / + cooldown: + default-days: 7 groups: dev-dependencies: dependency-type: development