From b54fcb500ea1f4bb860dbbb5f5f1ee9afce6564a Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Wed, 5 Aug 2026 06:20:04 -0700 Subject: [PATCH 1/8] Ask for a blocked decision instead of reporting it "Communicating with the User" required a numbered list for a question and said nothing about where the question goes. A summary is the wrong place: it reads as a report of finished work, so the one line still waiting on the user is the easiest in it to skim past, and the work sits owned by neither side while both believe the other has it. The new bullet requires a direct interactive prompt with selectable options, raised where the work stops rather than folded into a closing paragraph. The numbered list stays the fallback for an interface that offers no prompt, so the existing rule is narrowed rather than replaced. "Communicating with the User" is verbatim fleet law, so the fleet carries an older copy of the section until it is re-vendored. Co-authored-by: Claude Opus 5 (1M context) --- GOVERNANCE.md | 1 + 1 file changed, 1 insertion(+) diff --git a/GOVERNANCE.md b/GOVERNANCE.md index a4282ced..2f8c5532 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -316,6 +316,7 @@ Anti-pattern: don't keep flipping the code on the same style point. Flip the rul - **Reference every pull request as a clickable link.** When you mention a PR (in chat, a summary, or a report), render it as a markdown link to the PR (`[#123](https://github.com/OWNER/REPO/pull/123)`), never a bare `#123`. The same applies to issues and commits. - **Ask for input as a numbered list.** When you need the user to decide or answer, present the questions, and any options, as a numbered list so they can reply per number. A single inline question is fine, and two or more are always numbered. +- **Raise work blocked on the user as a direct interactive prompt.** When progress needs a decision, an authorization, or an answer only the user can give, ask for it through the interface's own prompt mechanism, with the choices presented as selectable options, at the point the work stops. Never leave it as prose in a summary: a handoff buried in a paragraph is a handoff that did not happen, because a summary reads as a report of finished work and the one line still waiting on the user is the easiest in it to skim past. The blocked item is the message, not a closing remark on a message about something else. This supersedes the numbered-list rule above wherever an interactive prompt is available, and the numbered list is the fallback where none is. ## Workflow YAML Conventions From 834a9a52658541e8db5f54c0aabde903c756ba67 Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Wed, 5 Aug 2026 06:34:47 -0700 Subject: [PATCH 2/8] Move the closing-keyword rule to the section that owns the branches The rule depends on develop-versus-default-branch mechanics, which "Branching Model" states and "Release Model" does not. An agent writing a feature PR body reaches for the branch section or the PR-title section, and the rule sat in neither, so the words are unchanged and only the home moves: it follows the promotion-execution traps, beside the other develop-to-main mechanics. Co-authored-by: Claude Opus 5 (1M context) --- GOVERNANCE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GOVERNANCE.md b/GOVERNANCE.md index 2f8c5532..77fd0c09 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -55,6 +55,7 @@ Agent-authored text illustrates with data the agent constructed, never with data - **Executing a `develop -> main` promotion safely, two traps both learned the hard way:** - **Never delete `develop`.** A promotion PR's head *is* `develop`, so `gh pr merge --delete-branch` (and the repo's "Automatically delete head branches" toggle, which is why that toggle is [kept off](./repo-config/settings.json)) deletes `develop` itself. Merge a promotion with a plain `gh pr merge --merge`, no `--delete-branch`. If `develop` is ever lost this way, restore it to the merged PR's head SHA, which is still reachable as the merge commit's second parent: `gh api -X POST "repos///git/refs" -f ref=refs/heads/develop -f sha="$(gh pr view --json headRefOid --jq .headRefOid)"`. - **Spurious EOL-only conflicts resolve by taking `develop`.** When develop declared workflow YAML as LF while main is still CRLF, `develop -> main` conflicts *whole-file* on those paths. develop's `required_linear_history` + PR rulesets forbid resolving on `develop` (no merge commit, no force-push), so resolve on a throwaway branch off `main`: `git checkout -b promote/develop-to-main origin/main && git merge origin/develop`, take develop's side for the EOL-conflicted files (`git checkout --theirs `) **after confirming each is content-identical modulo EOL or that develop is a strict superset** (`diff <(git show :2:f|tr -d '\r') <(git show :3:f|tr -d '\r')`), then open that branch -> `main`. Verify no genuine main-only content is dropped (build/test where the repo supports it). +- **Issue-closing keywords (`Closes #N`, `Fixes #N`) go in the `develop -> main` promotion PR, not the feature -> develop PR.** GitHub auto-closes an issue only when the closing keyword merges into the **default branch** (`main`). A feature/develop PR merges into `develop`, so the keyword never fires there. Reference the issue in the develop PR body if useful, but put the actual closing keyword on the promotion PR. If a develop PR merges with the keyword on it, the keyword does nothing and the issue stays open, so put it on the promotion PR body instead. Close the issue by hand citing the squash SHA only when the promotion has already merged without it. - **Both rulesets intentionally omit "Require branches to be up to date before merging".** The flag is off on `main` and on `develop`, for related but distinct reasons. - *Main:* the check is graph-based: it asks whether main's tip commit is reachable from develop, not whether the two branches have the same content. After any develop -> main release, main's tip is a brand-new merge commit that develop's history doesn't contain. Forward-only develop never adds it (no back-merge of main into develop), so the check would fail on every subsequent release. Other technical workarounds (rebasing develop onto main, or rewriting develop's history) exist but contradict the squash-only develop ruleset and the linearity invariant. - *Develop:* the check stalls bot auto-merge when two bot PRs against develop land within the same window. As soon as the first merges, the second flips to `mergeStateStatus: BEHIND` and GitHub's auto-merge will not fire while strict is on. The merge-bot only *enables* auto-merge on `opened`/`reopened` (see below) and never auto-updates bot branches, and Dependabot's rebase isn't real-time, so the second PR sits OPEN with all checks green indefinitely. Squash mechanics still rebase the diff onto develop's tip on merge, `required_linear_history` still enforces linearity, textual conflicts still block `mergeable: CONFLICTING`, and the required `Check pull request workflow status job` still gates merges. The only thing lost is pre-merge detection of *semantic-but-not-textual* conflicts, which the post-merge develop CI run catches anyway. @@ -90,7 +91,6 @@ The **two-phase model is the default**: PRs build fast, publishing is batched. S - **Bump `version.json` only for functional changes, by maintainer instruction.** Raise the major/minor when the work being introduced warrants a new semantic version (a new feature, a behavior or API change, a breaking change), and do it in the PR that introduces that work (typically on `develop`). Do **not** bump on a fixed cadence or mechanically after a release. NBGV advances the patch (git height) on every commit automatically, so a release always gets a fresh build version without any `version.json` edit. - **No post-release bump, and no develop-ahead requirement.** NBGV advances the patch (git height) on every commit, so a release always gets a fresh build version with no `version.json` edit and there is no `bump-version-X.Y` PR after a release. A `develop -> main` promotion carries whatever `version.json` is current: a promotion with a functional bump releases that new version on `main`, and a maintenance-only promotion carries the unchanged `version.json` and `main` advances only its NBGV height. - **Docs reference the 2-digit `major.minor` line, never a 3-digit build.** `README.md`, `HISTORY.md`, and release notes name the version as `Version 1.0` (the `version.json` floor). NBGV owns the patch/build position, so a concrete three-part number in a doc is both wrong (the real build height differs) and a maintenance trap. "Correcting" `1.0` to `1.0.0` is a defect, not a fix, and it has blocked a release. -- **Issue-closing keywords (`Closes #N`, `Fixes #N`) go in the `develop -> main` promotion PR, not the feature -> develop PR.** GitHub auto-closes an issue only when the closing keyword merges into the **default branch** (`main`). A feature/develop PR merges into `develop`, so the keyword never fires there. Reference the issue in the develop PR body if useful, but put the actual closing keyword on the promotion PR. If a develop PR merges with the keyword on it, the keyword does nothing and the issue stays open, so put it on the promotion PR body instead. Close the issue by hand citing the squash SHA only when the promotion has already merged without it. - **Wrapper repos that track an upstream release.** A repo wrapping an upstream release uses `check-upstream-version-task.yml`: a resolver command prints the upstream version(s) as a **JSON object of `name -> version`**, written to a committed state file at the **repo root beside `version.json`** (default `upstream-version.json`, since it is a build-input version source, not GitHub-platform config, so it does not belong under `.github/`), and opens a rolling App-signed bump PR per branch that the merge-bot auto-merges (`merge-upstream-version`). The object carries one key for the common single-version case (`{"version":"X"}`) or N keys for a wrapper that pins several upstream components (e.g. an image plus a companion tool), and the build reads each component by key, and the bump PR's title/body name only the keys that actually moved. Call it from a scheduled entry-point workflow and matrix only the branches that ship the version (a CI-only version uses `["develop"]`). A merged bump ships on the **next publish**, not immediately, which is the two-phase latency tradeoff. ## Operational Repositories From ade86107764eac338a8f93ee73d558d529766401 Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Wed, 5 Aug 2026 06:45:15 -0700 Subject: [PATCH 3/8] Make the prompt's options the actions they authorize The rule required an interactive prompt and left the options unspecified, which a prompt offering only ways to wait satisfies while clearing nothing: the user is asked to confirm that the block exists rather than to lift it, and the work sits exactly where prose left it. The options are now the actions themselves, with the unblocking one naming what it authorizes, so selecting it is the go-ahead. Where the agent may not perform that action itself, the option says who does. Co-authored-by: Claude Opus 5 (1M context) --- GOVERNANCE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GOVERNANCE.md b/GOVERNANCE.md index 77fd0c09..a9ab0898 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -316,7 +316,7 @@ Anti-pattern: don't keep flipping the code on the same style point. Flip the rul - **Reference every pull request as a clickable link.** When you mention a PR (in chat, a summary, or a report), render it as a markdown link to the PR (`[#123](https://github.com/OWNER/REPO/pull/123)`), never a bare `#123`. The same applies to issues and commits. - **Ask for input as a numbered list.** When you need the user to decide or answer, present the questions, and any options, as a numbered list so they can reply per number. A single inline question is fine, and two or more are always numbered. -- **Raise work blocked on the user as a direct interactive prompt.** When progress needs a decision, an authorization, or an answer only the user can give, ask for it through the interface's own prompt mechanism, with the choices presented as selectable options, at the point the work stops. Never leave it as prose in a summary: a handoff buried in a paragraph is a handoff that did not happen, because a summary reads as a report of finished work and the one line still waiting on the user is the easiest in it to skim past. The blocked item is the message, not a closing remark on a message about something else. This supersedes the numbered-list rule above wherever an interactive prompt is available, and the numbered list is the fallback where none is. +- **Raise work blocked on the user as a direct interactive prompt.** When progress needs a decision, an authorization, or an answer only the user can give, ask for it through the interface's own prompt mechanism, at the point the work stops. Never leave it as prose in a summary: a handoff buried in a paragraph is a handoff that did not happen, because a summary reads as a report of finished work and the one line still waiting on the user is the easiest in it to skim past. The blocked item is the message, not a closing remark on a message about something else. **The options offered are the actions themselves**, and the one that unblocks the work names the action it authorizes ("squash and merge it"), so selecting it is the go-ahead rather than a note to act on later. Offering only ways to wait is the same failure in interactive clothing, since a prompt whose every choice is inaction reports the block rather than clearing it, and where the agent may not perform the authorized action itself, the option says who does it. This supersedes the numbered-list rule above wherever an interactive prompt is available, and the numbered list is the fallback where none is. ## Workflow YAML Conventions From 9bfce3ba2124c3769bb9c9be2301e831f62e07a4 Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Wed, 5 Aug 2026 06:54:32 -0700 Subject: [PATCH 4/8] Write the link in the form the reading surface makes clickable The clickable-link rule named one syntax, and markdown link syntax is clickable only where markdown renders. On a surface that shows it as literal text, an interactive prompt's question and option text among them, following the rule as written produces a reference the reader has to retype, which is the outcome the rule exists to prevent. The form now follows the surface: the full URL where markdown does not render, and the test is whether it can be clicked where it is read. Co-authored-by: Claude Opus 5 (1M context) --- GOVERNANCE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GOVERNANCE.md b/GOVERNANCE.md index a9ab0898..9d34c742 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -314,7 +314,7 @@ Anti-pattern: don't keep flipping the code on the same style point. Flip the rul ## Communicating with the User -- **Reference every pull request as a clickable link.** When you mention a PR (in chat, a summary, or a report), render it as a markdown link to the PR (`[#123](https://github.com/OWNER/REPO/pull/123)`), never a bare `#123`. The same applies to issues and commits. +- **Reference every pull request as a clickable link.** When you mention a PR (in chat, a summary, or a report), render it as a markdown link to the PR (`[#123](https://github.com/OWNER/REPO/pull/123)`), never a bare `#123`. The same applies to issues and commits. **The form follows the surface.** Markdown link syntax is clickable only where markdown renders, so on a surface that shows it as literal text (an interactive prompt's question and option text, a plain terminal line), write the full URL on its own, which the surface itself makes clickable. The test is whether the reader can click it where it is read, not whether it was written in the syntax that works elsewhere, since a reference the reader has to retype is not a link. - **Ask for input as a numbered list.** When you need the user to decide or answer, present the questions, and any options, as a numbered list so they can reply per number. A single inline question is fine, and two or more are always numbered. - **Raise work blocked on the user as a direct interactive prompt.** When progress needs a decision, an authorization, or an answer only the user can give, ask for it through the interface's own prompt mechanism, at the point the work stops. Never leave it as prose in a summary: a handoff buried in a paragraph is a handoff that did not happen, because a summary reads as a report of finished work and the one line still waiting on the user is the easiest in it to skim past. The blocked item is the message, not a closing remark on a message about something else. **The options offered are the actions themselves**, and the one that unblocks the work names the action it authorizes ("squash and merge it"), so selecting it is the go-ahead rather than a note to act on later. Offering only ways to wait is the same failure in interactive clothing, since a prompt whose every choice is inaction reports the block rather than clearing it, and where the agent may not perform the authorized action itself, the option says who does it. This supersedes the numbered-list rule above wherever an interactive prompt is available, and the numbered list is the fallback where none is. From d0dd2cef24b51d698928368c34c0f56d61a12f8d Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Wed, 5 Aug 2026 06:59:47 -0700 Subject: [PATCH 5/8] Format the branch names and the markup name as the file does Two formatting inconsistencies the review round surfaced, in text this pull request already touches. The closing-keyword rule names branches bare ("the feature -> develop PR", "the develop PR body") while the section around its new home writes every branch as code. It arrived here byte-identical so the move stayed readable as a move, and the formatting is its own commit for the same reason. The clickable-link rule opened a sentence with "Markdown" while the bullet it extends writes "a markdown link" three words earlier. Prose in this file names the markup lowercase and reserves the capital for the extension and the linter, so the new sentence follows the bullet. Co-authored-by: Claude Opus 5 (1M context) --- GOVERNANCE.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/GOVERNANCE.md b/GOVERNANCE.md index 9d34c742..d072dc81 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -55,7 +55,7 @@ Agent-authored text illustrates with data the agent constructed, never with data - **Executing a `develop -> main` promotion safely, two traps both learned the hard way:** - **Never delete `develop`.** A promotion PR's head *is* `develop`, so `gh pr merge --delete-branch` (and the repo's "Automatically delete head branches" toggle, which is why that toggle is [kept off](./repo-config/settings.json)) deletes `develop` itself. Merge a promotion with a plain `gh pr merge --merge`, no `--delete-branch`. If `develop` is ever lost this way, restore it to the merged PR's head SHA, which is still reachable as the merge commit's second parent: `gh api -X POST "repos///git/refs" -f ref=refs/heads/develop -f sha="$(gh pr view --json headRefOid --jq .headRefOid)"`. - **Spurious EOL-only conflicts resolve by taking `develop`.** When develop declared workflow YAML as LF while main is still CRLF, `develop -> main` conflicts *whole-file* on those paths. develop's `required_linear_history` + PR rulesets forbid resolving on `develop` (no merge commit, no force-push), so resolve on a throwaway branch off `main`: `git checkout -b promote/develop-to-main origin/main && git merge origin/develop`, take develop's side for the EOL-conflicted files (`git checkout --theirs `) **after confirming each is content-identical modulo EOL or that develop is a strict superset** (`diff <(git show :2:f|tr -d '\r') <(git show :3:f|tr -d '\r')`), then open that branch -> `main`. Verify no genuine main-only content is dropped (build/test where the repo supports it). -- **Issue-closing keywords (`Closes #N`, `Fixes #N`) go in the `develop -> main` promotion PR, not the feature -> develop PR.** GitHub auto-closes an issue only when the closing keyword merges into the **default branch** (`main`). A feature/develop PR merges into `develop`, so the keyword never fires there. Reference the issue in the develop PR body if useful, but put the actual closing keyword on the promotion PR. If a develop PR merges with the keyword on it, the keyword does nothing and the issue stays open, so put it on the promotion PR body instead. Close the issue by hand citing the squash SHA only when the promotion has already merged without it. +- **Issue-closing keywords (`Closes #N`, `Fixes #N`) go in the `develop -> main` promotion PR, not the feature -> `develop` PR.** GitHub auto-closes an issue only when the closing keyword merges into the **default branch** (`main`). A feature -> `develop` PR merges into `develop`, so the keyword never fires there. Reference the issue in the `develop` PR body if useful, but put the actual closing keyword on the promotion PR. If a `develop` PR merges with the keyword on it, the keyword does nothing and the issue stays open, so put it on the promotion PR body instead. Close the issue by hand citing the squash SHA only when the promotion has already merged without it. - **Both rulesets intentionally omit "Require branches to be up to date before merging".** The flag is off on `main` and on `develop`, for related but distinct reasons. - *Main:* the check is graph-based: it asks whether main's tip commit is reachable from develop, not whether the two branches have the same content. After any develop -> main release, main's tip is a brand-new merge commit that develop's history doesn't contain. Forward-only develop never adds it (no back-merge of main into develop), so the check would fail on every subsequent release. Other technical workarounds (rebasing develop onto main, or rewriting develop's history) exist but contradict the squash-only develop ruleset and the linearity invariant. - *Develop:* the check stalls bot auto-merge when two bot PRs against develop land within the same window. As soon as the first merges, the second flips to `mergeStateStatus: BEHIND` and GitHub's auto-merge will not fire while strict is on. The merge-bot only *enables* auto-merge on `opened`/`reopened` (see below) and never auto-updates bot branches, and Dependabot's rebase isn't real-time, so the second PR sits OPEN with all checks green indefinitely. Squash mechanics still rebase the diff onto develop's tip on merge, `required_linear_history` still enforces linearity, textual conflicts still block `mergeable: CONFLICTING`, and the required `Check pull request workflow status job` still gates merges. The only thing lost is pre-merge detection of *semantic-but-not-textual* conflicts, which the post-merge develop CI run catches anyway. @@ -314,7 +314,7 @@ Anti-pattern: don't keep flipping the code on the same style point. Flip the rul ## Communicating with the User -- **Reference every pull request as a clickable link.** When you mention a PR (in chat, a summary, or a report), render it as a markdown link to the PR (`[#123](https://github.com/OWNER/REPO/pull/123)`), never a bare `#123`. The same applies to issues and commits. **The form follows the surface.** Markdown link syntax is clickable only where markdown renders, so on a surface that shows it as literal text (an interactive prompt's question and option text, a plain terminal line), write the full URL on its own, which the surface itself makes clickable. The test is whether the reader can click it where it is read, not whether it was written in the syntax that works elsewhere, since a reference the reader has to retype is not a link. +- **Reference every pull request as a clickable link.** When you mention a PR (in chat, a summary, or a report), render it as a markdown link to the PR (`[#123](https://github.com/OWNER/REPO/pull/123)`), never a bare `#123`. The same applies to issues and commits. **The form follows the surface.** A markdown link is clickable only where markdown renders, so on a surface that shows it as literal text (an interactive prompt's question and option text, a plain terminal line), write the full URL on its own, which the surface itself makes clickable. The test is whether the reader can click it where it is read, not whether it was written in the syntax that works elsewhere, since a reference the reader has to retype is not a link. - **Ask for input as a numbered list.** When you need the user to decide or answer, present the questions, and any options, as a numbered list so they can reply per number. A single inline question is fine, and two or more are always numbered. - **Raise work blocked on the user as a direct interactive prompt.** When progress needs a decision, an authorization, or an answer only the user can give, ask for it through the interface's own prompt mechanism, at the point the work stops. Never leave it as prose in a summary: a handoff buried in a paragraph is a handoff that did not happen, because a summary reads as a report of finished work and the one line still waiting on the user is the easiest in it to skim past. The blocked item is the message, not a closing remark on a message about something else. **The options offered are the actions themselves**, and the one that unblocks the work names the action it authorizes ("squash and merge it"), so selecting it is the go-ahead rather than a note to act on later. Offering only ways to wait is the same failure in interactive clothing, since a prompt whose every choice is inaction reports the block rather than clearing it, and where the agent may not perform the authorized action itself, the option says who does it. This supersedes the numbered-list rule above wherever an interactive prompt is available, and the numbered list is the fallback where none is. From 768e314a1431ac2f302c700494f1b63ed42ee3c6 Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Wed, 5 Aug 2026 07:09:53 -0700 Subject: [PATCH 6/8] Keep the URL out of the surface that will not link it The previous wording assumed a surface that shows markdown as literal text still links a bare URL. An interactive prompt does neither: the URL renders as plain text the reader has to select and copy, which is the outcome the clickable-link rule exists to prevent, so following the rule as written produced the failure it forbids. The link now goes in the message that accompanies the prompt, where markdown renders, and the prompt names the object plainly. Co-authored-by: Claude Opus 5 (1M context) --- GOVERNANCE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GOVERNANCE.md b/GOVERNANCE.md index d072dc81..f1894338 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -314,7 +314,7 @@ Anti-pattern: don't keep flipping the code on the same style point. Flip the rul ## Communicating with the User -- **Reference every pull request as a clickable link.** When you mention a PR (in chat, a summary, or a report), render it as a markdown link to the PR (`[#123](https://github.com/OWNER/REPO/pull/123)`), never a bare `#123`. The same applies to issues and commits. **The form follows the surface.** A markdown link is clickable only where markdown renders, so on a surface that shows it as literal text (an interactive prompt's question and option text, a plain terminal line), write the full URL on its own, which the surface itself makes clickable. The test is whether the reader can click it where it is read, not whether it was written in the syntax that works elsewhere, since a reference the reader has to retype is not a link. +- **Reference every pull request as a clickable link.** When you mention a PR (in chat, a summary, or a report), render it as a markdown link to the PR (`[#123](https://github.com/OWNER/REPO/pull/123)`), never a bare `#123`. The same applies to issues and commits. **The form follows the surface.** A markdown link is clickable only where markdown renders, and some surfaces link neither the markdown nor a bare URL, an interactive prompt's question and option text among them. Pasting a full URL into one of those does not rescue it, since the reader gets a string to copy, which is the outcome this rule exists to prevent. Put the clickable link in the message that accompanies the prompt, where markdown does render, and let the prompt name the object plainly (`#123`). The test is whether the reader can click it where it is read, not whether it was written in the syntax that works elsewhere. - **Ask for input as a numbered list.** When you need the user to decide or answer, present the questions, and any options, as a numbered list so they can reply per number. A single inline question is fine, and two or more are always numbered. - **Raise work blocked on the user as a direct interactive prompt.** When progress needs a decision, an authorization, or an answer only the user can give, ask for it through the interface's own prompt mechanism, at the point the work stops. Never leave it as prose in a summary: a handoff buried in a paragraph is a handoff that did not happen, because a summary reads as a report of finished work and the one line still waiting on the user is the easiest in it to skim past. The blocked item is the message, not a closing remark on a message about something else. **The options offered are the actions themselves**, and the one that unblocks the work names the action it authorizes ("squash and merge it"), so selecting it is the go-ahead rather than a note to act on later. Offering only ways to wait is the same failure in interactive clothing, since a prompt whose every choice is inaction reports the block rather than clearing it, and where the agent may not perform the authorized action itself, the option says who does it. This supersedes the numbered-list rule above wherever an interactive prompt is available, and the numbered list is the fallback where none is. From 836c1dd53229b7724d611077179ab1ebda51118b Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Wed, 5 Aug 2026 07:14:24 -0700 Subject: [PATCH 7/8] Scope the link rule's ban to the surface it was written for The bullet banned a bare reference outright and then required one in a prompt, so the rule contradicted itself and an agent following the opening sentence would produce the unclickable URL the addition exists to stop. The ban now applies where markdown renders, which is the surface the original sentence assumed, and the prompt case reads as the other half of one rule rather than as an exception to it. Co-authored-by: Claude Opus 5 (1M context) --- GOVERNANCE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GOVERNANCE.md b/GOVERNANCE.md index f1894338..41325507 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -314,7 +314,7 @@ Anti-pattern: don't keep flipping the code on the same style point. Flip the rul ## Communicating with the User -- **Reference every pull request as a clickable link.** When you mention a PR (in chat, a summary, or a report), render it as a markdown link to the PR (`[#123](https://github.com/OWNER/REPO/pull/123)`), never a bare `#123`. The same applies to issues and commits. **The form follows the surface.** A markdown link is clickable only where markdown renders, and some surfaces link neither the markdown nor a bare URL, an interactive prompt's question and option text among them. Pasting a full URL into one of those does not rescue it, since the reader gets a string to copy, which is the outcome this rule exists to prevent. Put the clickable link in the message that accompanies the prompt, where markdown does render, and let the prompt name the object plainly (`#123`). The test is whether the reader can click it where it is read, not whether it was written in the syntax that works elsewhere. +- **Reference every pull request as a clickable link.** When you mention a PR on a surface that renders markdown (chat, a summary, a report), render it as a markdown link to the PR (`[#123](https://github.com/OWNER/REPO/pull/123)`), never a bare `#123`. The same applies to issues and commits. **The form follows the surface.** Some surfaces link neither the markdown nor a bare URL, an interactive prompt's question and option text among them, and pasting a full URL into one of those does not rescue it, since the reader gets a string to copy, which is the outcome this rule exists to prevent. There the reference is a bare `#123`, and the clickable link goes in the message that accompanies the prompt. The test is whether the reader can click it where it is read, not whether it was written in the syntax that works elsewhere. - **Ask for input as a numbered list.** When you need the user to decide or answer, present the questions, and any options, as a numbered list so they can reply per number. A single inline question is fine, and two or more are always numbered. - **Raise work blocked on the user as a direct interactive prompt.** When progress needs a decision, an authorization, or an answer only the user can give, ask for it through the interface's own prompt mechanism, at the point the work stops. Never leave it as prose in a summary: a handoff buried in a paragraph is a handoff that did not happen, because a summary reads as a report of finished work and the one line still waiting on the user is the easiest in it to skim past. The blocked item is the message, not a closing remark on a message about something else. **The options offered are the actions themselves**, and the one that unblocks the work names the action it authorizes ("squash and merge it"), so selecting it is the go-ahead rather than a note to act on later. Offering only ways to wait is the same failure in interactive clothing, since a prompt whose every choice is inaction reports the block rather than clearing it, and where the agent may not perform the authorized action itself, the option says who does it. This supersedes the numbered-list rule above wherever an interactive prompt is available, and the numbered list is the fallback where none is. From 285d0ae1c34c47feefa5363f1a90965da3f47ee2 Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Wed, 5 Aug 2026 07:18:03 -0700 Subject: [PATCH 8/8] Name the thing the surface fails to link "Neither the markdown nor a bare URL" leans on the sentence before it to say what "the markdown" is. Naming the markdown link makes the pair read without the back-reference. Co-authored-by: Claude Opus 5 (1M context) --- GOVERNANCE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GOVERNANCE.md b/GOVERNANCE.md index 41325507..73e0c7b6 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -314,7 +314,7 @@ Anti-pattern: don't keep flipping the code on the same style point. Flip the rul ## Communicating with the User -- **Reference every pull request as a clickable link.** When you mention a PR on a surface that renders markdown (chat, a summary, a report), render it as a markdown link to the PR (`[#123](https://github.com/OWNER/REPO/pull/123)`), never a bare `#123`. The same applies to issues and commits. **The form follows the surface.** Some surfaces link neither the markdown nor a bare URL, an interactive prompt's question and option text among them, and pasting a full URL into one of those does not rescue it, since the reader gets a string to copy, which is the outcome this rule exists to prevent. There the reference is a bare `#123`, and the clickable link goes in the message that accompanies the prompt. The test is whether the reader can click it where it is read, not whether it was written in the syntax that works elsewhere. +- **Reference every pull request as a clickable link.** When you mention a PR on a surface that renders markdown (chat, a summary, a report), render it as a markdown link to the PR (`[#123](https://github.com/OWNER/REPO/pull/123)`), never a bare `#123`. The same applies to issues and commits. **The form follows the surface.** Some surfaces link neither a markdown link nor a bare URL, an interactive prompt's question and option text among them, and pasting a full URL into one of those does not rescue it, since the reader gets a string to copy, which is the outcome this rule exists to prevent. There the reference is a bare `#123`, and the clickable link goes in the message that accompanies the prompt. The test is whether the reader can click it where it is read, not whether it was written in the syntax that works elsewhere. - **Ask for input as a numbered list.** When you need the user to decide or answer, present the questions, and any options, as a numbered list so they can reply per number. A single inline question is fine, and two or more are always numbered. - **Raise work blocked on the user as a direct interactive prompt.** When progress needs a decision, an authorization, or an answer only the user can give, ask for it through the interface's own prompt mechanism, at the point the work stops. Never leave it as prose in a summary: a handoff buried in a paragraph is a handoff that did not happen, because a summary reads as a report of finished work and the one line still waiting on the user is the easiest in it to skim past. The blocked item is the message, not a closing remark on a message about something else. **The options offered are the actions themselves**, and the one that unblocks the work names the action it authorizes ("squash and merge it"), so selecting it is the go-ahead rather than a note to act on later. Offering only ways to wait is the same failure in interactive clothing, since a prompt whose every choice is inaction reports the block rather than clearing it, and where the agent may not perform the authorized action itself, the option says who does it. This supersedes the numbered-list rule above wherever an interactive prompt is available, and the numbered list is the fallback where none is.