Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 107 additions & 0 deletions .github/extensions/aspnetcore-team-app/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# ASP.NET Core Team App

A project-scoped Copilot canvas for the repository's deterministic
`pr-attention-queue` skill. The first mode is intentionally narrow: it helps a
maintainer decide what to review now, what needs rescue, and what is ready to
merge without creating another notification feed.

## Behavior

- Loads live Blazor data by default and supports an explicit whole-repository
view.
- Keeps `ReviewNow` and `NeedsRescue` as separate primary lanes.
- Adds a dedicated inbox and `Worth reviewing now` summary that exposes the current eligible review digest.
- Surfaces `Recently opened community PRs` in the last seven-day window, including the newest ID and an expandable full recent inventory.
- Shows `Community attention` with `NeedsRescue` items first, followed by the engine-ordered inventory, plus a visible `Unclassified` count and full list.
- Separates ambiguous deterministic Review now candidates into **Verify discussion** rather than
presenting them as ordinary review work.
- Discloses bounded response evidence and canonical PR links without claiming `no-response` when the evidence is incomplete or ambiguous.
- Shows a compact `ReadyToMerge` strip and expandable secondary
classifications.
- Preserves the skill's scope, ordering, caps, next actors, reason codes,
blockers, warnings, and overflow.
- Keeps the last complete snapshot visible while a refresh runs or fails.
- Adds **My PR inbox** for the authenticated user across all open
`dotnet/aspnetcore` pull requests, with one card per personally signaled PR
and an expandable full inventory.
- Uses a compact selectable workspace with list rows on the left and a focused
selected-item detail pane on the right, so the canonical pull request can be
inspected from the inbox, queue, or personal views without duplicating the
full card everywhere.
- Separates direct review requests, team requests, notification reasons,
participation, mentions, changed-since-own-review, and evidenced replies in
participated review threads.
- Makes the Review handoff explicitly ask the foreground caller to copy any
applicable model/provider restrictions into the actual kickoff prompt before
opening the child session, then asks the child session to locate and use the
intended `review-pull-request` skill from already-available sources, carry
those restrictions forward when selecting workers, and stop with a setup
blocker if the intended skill cannot be discovered or honored. These prompt
instructions are not runtime enforcement, and review artifacts may be written
only in the session-state files directory without editing repository files.
- Exposes two explicit review destinations, **Review in new session** and
**Review in this session**, for every selected pull request regardless of its
queue classification. The selected item stays locked while a review is
routed, and the browser sends only the opaque item ID plus the validated
destination enum.
- Displays assessed, partial, unavailable, and unassessed coverage plus cold or
warm API metrics. Personal signals never grant queue eligibility or create a
separate follow-up lane; bot-authored and out-of-scope classifications remain
visible without suppressing an explicitly requested review.

The canvas does not classify or rank pull requests in JavaScript. It invokes
`Get-PRAttentionQueue.ps1` and validates the skill's versioned JSON contract.
Discussion verification is also supplied by the skill. It surfaces bounded top-level comment
evidence, current thread counts, and explicit truncation signals without changing the canonical
bucket or applying an opaque model judgment. A current unresolved inline thread is routed to
**Verify discussion** because the first-version query does not retrieve inline comment text;
resolved and outdated threads remain factual context rather than an ownership inference.

## Actions

Every visible item can open its canonical pull request in the app's browser.
Only Review now items with a clear bounded discussion assessment can start a new read-only review
session. **Verify discussion** items can be opened but must be interpreted by a human first.
`NeedsRescue`
items can start a new read-only investigation session. The browser sends only
an opaque item ID and action kind; the extension resolves repository, pull
request number, bucket, and URL from the current server-owned snapshot.

The Review handoff is source-only and read-only: the foreground caller must
copy applicable model/provider restrictions into the actual kickoff prompt
before opening the child session, and the child session must preserve those
restrictions in its worker selection. The intended review skill may be absent
from the PR checkout yet still available through installed session, user,
plugin, project, or target-checkout sources; if no available source can satisfy
it, the blocker is deliberate. It still stops with a setup blocker instead of
silently falling back to a generic review workflow, and it does not install,
copy, or fetch a hardcoded remote skill. Review artifacts remain confined to
the session-state files directory.

The extension has no action that comments, labels, assigns, closes, merges,
rebases, edits files, commits, or pushes.

## Files

| File | Responsibility |
| --- | --- |
| `extension.mjs` | Canvas registration, runtime actions, session dispatch, and browser opening. |
| `queue.mjs` | Safe PowerShell invocation and JSON contract validation. |
| `personal.mjs` | Read-only GitHub-derived personal inbox collection and coverage. |
| `state.mjs` | Atomic snapshots, refresh coalescing, opaque IDs, and action eligibility. |
| `server.mjs` | Loopback HTTP/SSE server and same-origin request boundary. |
| `agent.mjs` | Fixed read-only review and rescue prompts. |
| `render.mjs` | Theme-token-based iframe UI. |
| `*.test.mjs` | Fixture-backed contract, state, security, action, and renderer tests. |

## Deliberate first-version limits

- No GitHub or repository mutation.
- No opaque quality or priority score.
- No inference that a timestamp-only author response makes a PR unconditionally review-ready.
- No claim that a current unresolved inline thread is semantically clear without its comment text.
- No automatic interpretation of truncated discussion history.
- No automatic polling.
- No issue triage, shipping, or repository-health modes yet.
- No testing, CI diagnosis, rebase, conflict resolution, or merge actions.
- No multi-account or durable cross-session snapshot storage.
128 changes: 128 additions & 0 deletions .github/extensions/aspnetcore-team-app/agent.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
export function buildAgentActionPrompt(kind, item, { destination } = {}) {
validateOperationalItem(item);

if (kind === "review") {
return buildReviewPrompt(item, destination ?? "new-session");
}

if (kind === "investigate-rescue") {
if (item.bucket !== "NeedsRescue") {
throw actionError("action_not_allowed", "Investigate rescue requires a Needs rescue item.");
}

return `Open a NEW pull-request session for ${item.repository}#${item.number}.

Use the open_pr_session tool with repo_full_name "${item.repository}", pr_number ${item.number}, and an autopilot kickoff containing these instructions:

Perform a READ-ONLY rescue investigation for ${item.repository}#${item.number}. Fetch the current pull request history, linked issue, human reviews and review requests, checks, mergeability, labels, ownership signals, and blockers. Recommend exactly one next path: review now, request author follow-up, restore maintainer ownership, ask the author to rebase, or close as no longer actionable. Support the recommendation with current evidence. Do not comment, label, assign, close, merge, edit files, commit, or push.`;
}

throw actionError("invalid_action", `Unsupported agent action: ${kind}`);
}

export function buildAgentActionLog(kind, item, { destination } = {}) {
validateOperationalItem(item);
if (kind === "review") {
return buildReviewLog(item, destination ?? "new-session");
}
if (kind === "investigate-rescue") {
return `Open read-only rescue investigation for ${item.repository}#${item.number}`;
}
throw actionError("invalid_action", `Unsupported agent action: ${kind}`);
}

export function buildReviewPrompt(item, destination = "new-session") {
validateReviewItem(item);
const headSha = item.headSha;
const scope = `${item.repository}#${item.number}`;

if (destination === "this-session") {
return [
`Review ${scope} in this session (${item.url}).`,
"",
`The current PR head SHA is ${headSha}. Review the complete diff in repository context. Use this session's existing tools and workspace, but do not open a child PR session, do not change checkout, do not rebase, and do not edit files. If the current checkout differs from this head SHA, read the target PR remotely rather than changing checkout.`,
"",
commonReviewInstructions(),
"",
`You may write review artifacts only in the session-state files directory; do not edit repository files.`,
`Report only high-confidence correctness, security, reliability, or test-coverage findings with precise file and line evidence.`,
`Report the reviewed head SHA when identifiable; if it cannot be identified, report a setup blocker.`,
`Report the skill source/revision when identifiable; state when unavailable.`,
`Do not post or submit a GitHub review.`,
`Do not comment, approve, request changes, label, assign, close, merge, stage review comments, change statuses or branches, edit files, commit, or push.`,
].join("\n");
}

if (destination !== "new-session") {
throw actionError("invalid_destination", `Unsupported review destination: ${String(destination)}`);
}

return [
`Open or reuse a dedicated pull-request review session for ${scope} (${item.url}).`,
"",
`First call list_sessions_and_chats and look for a non-archived project session already linked to exactly ${scope}. If one exists, reuse it with send_session_message using immediate delivery and autopilot mode; send the complete review instructions below and do not create a duplicate session.`,
`If no exact session exists, use open_pr_session as described below. If that fails only because the upstream project origin cannot be verified, use list_projects to find an already-configured fork of the same repository and create_session there with the complete source-only review instructions below. Do not clone or add a project. If no suitable existing project is available, report the setup blocker.`,
`Before reusing or opening a session, copy any applicable explicit model/provider restrictions already available in your instructions into the actual message or kickoff.prompt you pass. If a known restriction cannot be carried forward or honored, stop and report a setup blocker. Do not invent restrictions or hardcode model names.`,
`After routing, report whether you reused a session, created a session, or encountered a blocker. Do not report success merely because a request was queued.`,
"",
`Use the open_pr_session tool with repo_full_name "${item.repository}", pr_number ${item.number}, and an autopilot kickoff containing these instructions:`,
"",
`The current PR head SHA is ${headSha}. Review the complete diff in repository context. Perform a thorough READ-ONLY code review of ${scope} against that head SHA. Fetch the current pull request and review its complete diff in repository context.`,
"",
commonReviewInstructions(),
"",
`You may write review artifacts only in the session-state files directory; do not edit repository files.`,
`Report only high-confidence correctness, security, reliability, or test-coverage findings with precise file and line evidence.`,
`Report the reviewed head SHA when identifiable; if it cannot be identified, report a setup blocker.`,
`Report the skill source/revision when identifiable; state when unavailable.`,
`Do not post or submit a GitHub review.`,
`Do not comment, approve, request changes, label, assign, close, merge, stage review comments, change statuses or branches, edit files, commit, or push.`,
].join("\n");
}

export function buildReviewLog(item, destination = "new-session") {
validateReviewItem(item);
if (destination === "this-session") {
return `Review in this session for ${item.repository}#${item.number}`;
}
if (destination === "new-session") {
return `Review in new session for ${item.repository}#${item.number}`;
}
throw actionError("invalid_destination", `Unsupported review destination: ${String(destination)}`);
}

function commonReviewInstructions() {
return [
`Locate and invoke the intended review-pull-request skill from already-available session, user, plugin, project, or target-checkout skill mechanisms. Use the exposed skill invocation tool when one is registered; otherwise follow the supported available-skill mechanism, without inventing a new API. If the intended skill is unavailable or incompatible with the read-only/model restrictions, stop and report a setup blocker rather than substituting a generic review workflow or installing tools.`,
`Preserve any applicable model/provider restrictions when selecting workers.`,
`Keep the session source-only: do not execute the target PR code, builds, or tests.`,
`Do not install, copy, or fetch a hardcoded remote skill.`,
].join("\n");
}

function validateOperationalItem(item) {
if (
!item
|| typeof item.repository !== "string"
|| !/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(item.repository)
|| !Number.isInteger(item.number)
|| item.number < 1
|| !["ReviewNow", "NeedsRescue", "ReadyToMerge", "WaitingOnAuthor", "WaitingOnCI",
"DesignDecision", "Draft", "Excluded"].includes(item.bucket)
) {
throw actionError("invalid_item", "Resolved queue item is invalid.");
}
}

function validateReviewItem(item) {
validateOperationalItem(item);
if (typeof item.headSha !== "string" || item.headSha.length === 0) {
throw actionError("invalid_item", "Resolved review item is missing a head SHA.");
}
}

function actionError(code, message) {
const error = new Error(message);
error.code = code;
return error;
}
Loading