Skip to content
Merged
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
103 changes: 103 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,108 @@
# GitHub Copilot Instructions for LanguageTags

The **canonical guide is [AGENTS.md](../AGENTS.md)** at the repo root — read it first. It covers branching, PR review etiquette, workflow YAML conventions, and the release pipeline.

This file is intentionally focused: the GitHub Copilot Review Runbook (provider-specific mechanics behind the review-loop contract defined in AGENTS.md), followed by the LanguageTags-specific code conventions and public-API contract notes that VS Code's AI generators pick up directly from this path.

For C# style rules, see [`CODESTYLE.md`](../CODESTYLE.md) at the repo root. Do not duplicate those rules here.

## GitHub Copilot Review Runbook

Use this section for provider-specific mechanics. The expected review loop *contract* (request review on every push, verify head-SHA coverage, triage findings, reply + resolve, escalate when stuck) is defined in [AGENTS.md → PR Review Etiquette](../AGENTS.md#pr-review-etiquette). This section only describes how to make GitHub Copilot reliably execute it.

### Triggering and Polling

Auto-review on push is configured (via the branch ruleset's `copilot_code_review` rule with `review_on_push: true`) but fires inconsistently in practice — treat it as best-effort, not guaranteed. Request review explicitly through the GitHub PR UI (request `Copilot` as a reviewer) after every push.

**Do NOT post `@Copilot review` as a PR comment.** That comment triggers the Copilot *coding agent* (`copilot-swe-agent[bot]`), which makes code changes rather than posting a review.

Known non-working request paths (don't rely on them):

- `POST /requested_reviewers` with `reviewers=[Copilot]` can return 200 but no-op.
- `copilot-pull-request-reviewer` as a requested reviewer slug returns 422.
- GraphQL `requestReviews` rejects Copilot's bot node.

### Verify Review Covered Current Head

Before merging, confirm Copilot reviewed the current PR head SHA. Copilot may respond as either a formal review (carries an exact commit SHA) or an issue comment (no SHA — use the most recent Copilot comment for manual confirmation). Check both.

```sh
PR_HEAD=$(gh pr view <N> --json headRefOid --jq '.headRefOid')

# 1. Formal review — exact SHA match.
gh pr view <N> --json reviews --jq \
'.reviews[] | select(.author.login=="copilot-pull-request-reviewer") | .commit.oid' \
| grep -q "$PR_HEAD" && echo "covered via formal review"

# 2. Issue comment — show the most recent Copilot comment for manual confirmation.
gh api repos/ptr727/LanguageTags/issues/<N>/comments --jq \
'[.[] | select(.user.login=="copilot-pull-request-reviewer")] | last | {created_at, body: .body[:200]}'
```

Coverage is confirmed when (1) exits 0. For issue comments (path 2), body content is the only reliable signal — `created_at` is not: `git log -1 --format=%cI` is the **commit** timestamp, not the push timestamp, so amended or rebased commits can have an earlier timestamp and an older Copilot comment could satisfy a time check even though Copilot never saw the current head. Treat path (2) as confirmed only when the comment body explicitly refers to the current changes.

### Bounded Retry Workflow

If a review did not run on the current head, retry:

1. Wait briefly and check head-SHA coverage (see above).
1. Request review again via the GitHub PR UI.
1. Retry up to two more times (three total).
1. If still missing, mark review as blocked and escalate to the user/maintainer with what was attempted.

### Reply and Thread Resolution Workflow

List unresolved threads. Use `first: 100` with cursor-based pagination; if `hasNextPage` is true, re-run with `after: "<endCursor>"` to retrieve the next page:

```sh
gh api graphql -f query='
{
repository(owner: "ptr727", name: "LanguageTags") {
pullRequest(number: <N>) {
reviewThreads(first: 100) {
nodes {
id isResolved path
comments(first: 1) { nodes { author { login } body } }
}
pageInfo { hasNextPage endCursor }
}
}
}
}' | jq '
.data.repository.pullRequest.reviewThreads |
(.pageInfo | "hasNextPage=\(.hasNextPage) endCursor=\(.endCursor)"),
(.nodes[] | select(.isResolved == false))
'
```

Reply on a thread, then resolve it:

```sh
gh api graphql -f query='
mutation($threadId: ID!, $body: String!) {
addPullRequestReviewThreadReply(input: { pullRequestReviewThreadId: $threadId, body: $body }) {
comment { id }
}
}' -F threadId="PRRT_..." -F body="Fixed in <SHA>: <one-line summary>."

gh api graphql -f query='
mutation($threadId: ID!) {
resolveReviewThread(input: { threadId: $threadId }) { thread { id isResolved } }
}' -F threadId="PRRT_..."
```

Issue-level Copilot comments (those in `issues/<N>/comments`) have no resolution action — GitHub provides no API or UI to resolve them. Reply if the finding warrants it; no resolution step is needed or possible.

Reply-body conventions:

- Accepted bug/style fix: include fixing commit SHA and a one-line summary.
- Declined style comment: cite the rule (AGENTS.md or CODESTYLE.md) and the existing-tree precedent.
- Declined architecture proposal: one-sentence rationale.

After the final push, sweep-resolve stale older threads for removed code paths.

---

## Project Overview

**LanguageTags** is a C# .NET library for handling ISO 639-2, ISO 639-3, and RFC 5646 / BCP 47 language tags. The project serves two primary purposes:
Expand Down
97 changes: 74 additions & 23 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -1,23 +1,74 @@
# https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
version: 2
updates:

# main
- package-ecosystem: "nuget"
target-branch: "main"
directory: "/"
schedule:
interval: "daily"
groups:
nuget-deps:
patterns:
- "*"
- package-ecosystem: "github-actions"
target-branch: "main"
directory: "/"
schedule:
interval: "daily"
groups:
actions-deps:
patterns:
- "*"
# https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
#
# Every ecosystem appears **twice**: once with `target-branch: "main"`
# and once with `target-branch: "develop"`. Dependabot will open
# parallel PRs against each branch, so both stay current on
# dependency versions independently of the develop → main release
# cadence.
#
# Why dual-target and not develop-only:
# - `develop` is the integration branch and ships content forward to
# `main` through merge-commit releases, but the time between releases
# can be long (a feature branch may sit on develop for weeks).
# - Consumers (NuGet.org, GitHub releases) pull from `main` directly.
# If `main` only got dependency bumps via the next develop → main
# release, those consumers would ship outdated code in the interim.
# - The codegen workflow takes the same dual-target shape for the same
# reason — see .github/workflows/run-codegen-pull-request-task.yml.
#
# The merge-bot's `case` statement in
# .github/workflows/merge-bot-pull-request.yml dispatches the merge
# method per base ref (squash on develop, merge on main) so both bases
# auto-merge cleanly. `develop` remains strictly forward-only: there
# are no main → develop back-merges; each branch absorbs its own
# Dependabot PRs and codegen PRs independently.
#
# Security update PRs (CVE-driven) are opened by Dependabot against
# the repo default branch (`main`) regardless of any `target-branch`
# config — the `case` statement handles them in the same code path.
version: 2
updates:

# ----- nuget -----

- package-ecosystem: "nuget"
target-branch: "main"
directory: "/"
schedule:
interval: "daily"
groups:
nuget-deps:
patterns:
- "*"

- package-ecosystem: "nuget"
target-branch: "develop"
directory: "/"
schedule:
interval: "daily"
groups:
nuget-deps:
patterns:
- "*"

# ----- github-actions -----

- package-ecosystem: "github-actions"
target-branch: "main"
directory: "/"
schedule:
interval: "daily"
groups:
actions-deps:
patterns:
- "*"

- package-ecosystem: "github-actions"
target-branch: "develop"
directory: "/"
schedule:
interval: "daily"
groups:
actions-deps:
patterns:
- "*"
60 changes: 29 additions & 31 deletions .github/workflows/build-datebadge-task.yml
Original file line number Diff line number Diff line change
@@ -1,31 +1,29 @@
name: Build BYOB date badge task

env:
IS_MAIN_BRANCH: ${{ endsWith(github.ref, 'refs/heads/main') }}

on:
workflow_call:

jobs:

date-badge:
name: Build BYOB date badge job
runs-on: ubuntu-latest

steps:

- name: Get current date step
id: date
run: |
echo "date=$(date)" >> $GITHUB_OUTPUT

- name: Build BYOB date badge step
if: ${{ env.IS_MAIN_BRANCH == 'true' }}
uses: RubbaBoy/BYOB@v1
with:
name: lastbuild
label: "Last Build"
icon: "github"
status: ${{ steps.date.outputs.date }}
color: "blue"
github_token: ${{ secrets.GITHUB_TOKEN }}
name: Build BYOB date badge task

on:
workflow_call:

jobs:

date-badge:
name: Build BYOB date badge job
runs-on: ubuntu-latest

steps:

- name: Get current date step
id: date
run: |
set -euo pipefail
echo "date=$(date)" >> $GITHUB_OUTPUT

- name: Build BYOB date badge step
if: ${{ github.ref_name == 'main' }}
uses: RubbaBoy/BYOB@a4919104bc0ec7cfd7f113e42c405cc45246f2a4 # v1
with:
name: lastbuild
label: "Last Build"
icon: "github"
status: ${{ steps.date.outputs.date }}
color: "blue"
github_token: ${{ secrets.GITHUB_TOKEN }}
Loading
Loading