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
8 changes: 5 additions & 3 deletions .agents/skills/pr-review-conduct/SKILL.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -124,7 +124,8 @@ reviewer's own words to identify it), give one bold verdict per finding (`Fixed
`Disproven`, or `No change needed`), state the `(N)` count the block gave so answers can be
checked against findings, and link the review round. **Read every round, not only the head.** A
suppressed finding does not retire when a later push supersedes it, it just stops showing up in a
head-scoped query while still unanswered.
head-scoped query while still unanswered. Post the answer with `scripts/pr_review.py comment`
from a hub checkout. Do not use a provider connector or reconstruct the GitHub mutation.

## Escalate to the maintainer when

Expand All@@ -138,7 +139,8 @@ head-scoped query while still unanswered.

This skill is the provider-agnostic contract. Use `scripts/pr_review.py` from a hub checkout for
the GitHub-specific API operations. `status` reports coverage, threads, body-only findings, and
shapes in one call. `wait` requests and polls in-process. `reply` resolves a thread by matching
the finding's own words instead of a line number a fix push can move. The repository's
shapes in one call. `wait` requests and polls in-process. `comment` posts a PR-conversation
answer after it reads the PR node ID. `reply` resolves a thread by matching the finding's own
words instead of a line number a fix push can move. The repository's
`.github/copilot-instructions.md` bootstraps Copilot into the `code-review` skill and its stable
coverage marker. Do not reconstruct the API operations by hand.
2 changes: 1 addition & 1 deletion .claude-plugin/fleet-skills/.source-digest
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
ac73ed965d3dfe48
a4b2a2c434e2ab63
Original file line numberDiff line numberDiff line change
Expand Up@@ -124,7 +124,8 @@ reviewer's own words to identify it), give one bold verdict per finding (`Fixed
`Disproven`, or `No change needed`), state the `(N)` count the block gave so answers can be
checked against findings, and link the review round. **Read every round, not only the head.** A
suppressed finding does not retire when a later push supersedes it, it just stops showing up in a
head-scoped query while still unanswered.
head-scoped query while still unanswered. Post the answer with `scripts/pr_review.py comment`
from a hub checkout. Do not use a provider connector or reconstruct the GitHub mutation.

## Escalate to the maintainer when

Expand All@@ -138,7 +139,8 @@ head-scoped query while still unanswered.

This skill is the provider-agnostic contract. Use `scripts/pr_review.py` from a hub checkout for
the GitHub-specific API operations. `status` reports coverage, threads, body-only findings, and
shapes in one call. `wait` requests and polls in-process. `reply` resolves a thread by matching
the finding's own words instead of a line number a fix push can move. The repository's
shapes in one call. `wait` requests and polls in-process. `comment` posts a PR-conversation
answer after it reads the PR node ID. `reply` resolves a thread by matching the finding's own
words instead of a line number a fix push can move. The repository's
`.github/copilot-instructions.md` bootstraps Copilot into the `code-review` skill and its stable
coverage marker. Do not reconstruct the API operations by hand.
7 changes: 4 additions & 3 deletions .github/copilot-instructions.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -38,9 +38,10 @@ For every review:
5. End the review body with the exact machine-readable marker required by the `code-review` skill.

The review automation is `scripts/pr_review.py`, run from a hub checkout. Use its `status`, `wait`,
and `reply --resolve` commands instead of reconstructing GraphQL queries or copying review
identifiers by hand. Its status gate verifies the current head, diff coverage, output shape,
inline threads, body-only findings, and required checks.
`comment`, and `reply --resolve` commands instead of reconstructing GraphQL queries or copying
review identifiers by hand. Use `comment` for a suppressed-finding answer in the pull request
conversation. Its status gate verifies the current head, diff coverage, output shape, inline
threads, body-only findings, and required checks.

A formal review with no findings is complete only when it covers the current head and states full
diff coverage. A refusal, partial or absent coverage statement, unrecognized output shape,
Expand Down
8 changes: 5 additions & 3 deletions .github/skills/pr-review-conduct/SKILL.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -124,7 +124,8 @@ reviewer's own words to identify it), give one bold verdict per finding (`Fixed
`Disproven`, or `No change needed`), state the `(N)` count the block gave so answers can be
checked against findings, and link the review round. **Read every round, not only the head.** A
suppressed finding does not retire when a later push supersedes it, it just stops showing up in a
head-scoped query while still unanswered.
head-scoped query while still unanswered. Post the answer with `scripts/pr_review.py comment`
from a hub checkout. Do not use a provider connector or reconstruct the GitHub mutation.

## Escalate to the maintainer when

Expand All@@ -138,7 +139,8 @@ head-scoped query while still unanswered.

This skill is the provider-agnostic contract. Use `scripts/pr_review.py` from a hub checkout for
the GitHub-specific API operations. `status` reports coverage, threads, body-only findings, and
shapes in one call. `wait` requests and polls in-process. `reply` resolves a thread by matching
the finding's own words instead of a line number a fix push can move. The repository's
shapes in one call. `wait` requests and polls in-process. `comment` posts a PR-conversation
answer after it reads the PR node ID. `reply` resolves a thread by matching the finding's own
words instead of a line number a fix push can move. The repository's
`.github/copilot-instructions.md` bootstraps Copilot into the `code-review` skill and its stable
coverage marker. Do not reconstruct the API operations by hand.
3 changes: 2 additions & 1 deletion GOVERNANCE.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -25,7 +25,8 @@ This section keeps the full rules and is surfaced at its decision moment by the
A state-changing GitHub call is the highest-blast-radius thing an agent does here: it runs under the maintainer's identity, so one wrong target writes to another owner's repository as the maintainer, an outward-facing and hard-to-reverse act. These rules bound every write (a git push, an API mutation, a comment, a label, a merge) on any platform, and they bound a write to a checkout on disk as well, since a blanket add or a hard reset in a working tree another task is using destroys work without ever reaching GitHub. Reads are unrestricted, and how far a local read can be trusted is governed under "Verification Discipline" rather than here. The bounds below are on writes.

- **Write only within the owner of the current project's repository.** Every state-changing call targets this project's `origin` or another repository under the same owner, which is the fleet the maintainer already administers. A broad or logged-in identity is capability, not permission: a token that *can* reach another owner's repository does not authorize writing to it. Writing under a **different owner** needs explicit human permission naming that repository, granted deliberately rather than assumed from a token's reach, and a "harmless test" write is still a write, so there is no probe exception. That boundary is where the harm sits, since the incident this rule exists for was a stray comment on a stranger's repository, not work across the maintainer's own projects. Reads from anywhere are fine.
- **Never fabricate, guess, or reuse an identifier passed to a write.** Every id a state-changing call consumes (a node id, a numeric id, a thread or comment id) is captured from a live query in the **same** session into a variable and passed from there. Do not hand-type an id, guess it, recall it from memory or an earlier session, or copy it from documentation or an example. Ids commonly resolve **globally**, so a wrong-but-valid id does not fail. It writes to the wrong target, in someone else's repository. If a query returns no id, stop rather than invent one to proceed.
- **Provider connectors are read-only for fleet work.** Use a provider's GitHub connector for reads where it helps. Perform each GitHub mutation through the documented hub tool, or through authenticated `gh` where no tool owns the operation. This gives Codex, Claude, opencode, and a terminal session one write path with the same checks. It also avoids a connector mutation that predictably lacks repository authorization while the verified `gh` session already has it. A provider-specific instruction may explain how to reach the common path. It never replaces that path with its own mutation surface.
- **Never fabricate, guess, or reuse an identifier passed to a write.** Capture every identifier a state-changing call consumes from a live query in the **same** session. This includes node, numeric, thread, and comment ids. Pass the captured value directly. Do not hand-type an id, recall it from another session, or copy it from documentation or an example. Ids commonly resolve **globally**, so a wrong-but-valid id does not fail. It writes to the wrong target, in someone else's repository. Apply the same rule to an identifier embedded in outward-facing text. Read the complete URL from the live object. Never construct a plausible link from an unverified id. If a query returns no id or URL, stop rather than invent one to proceed.
- **A write is never a probe, and a write's output is never suppressed.** Never fire a state-changing call to see whether it works: decide it should happen, make it happen, and read the result. Never append output-discarding redirection or a force-success tail to a mutation (for example `>/dev/null`, `2>/dev/null`, `&>/dev/null`, `|| true`, `|| :`, `|| echo`), because the write's output is exactly what must be read. A write that appears to fail is **verified, not assumed harmless**, because the operation may have succeeded on the server while the client reported an error, so confirm the actual state before retrying or moving on. The ban targets hiding a *failure*. An ad-hoc call's response is the only signal you get, so `>/dev/null 2>&1`, `|| true`, and `|| echo`, which swallow the error stream or force success, are never acceptable on one. A committed script under `set -e` is a narrow exception: it may send a write's *stdout* to `/dev/null` to drop the success-response noise, because stderr stays visible and a failed write still aborts loudly (the hub's own `repo-config/configure.sh` does exactly this, and a repository reaches it there rather than carrying a copy). The exception is stdout-only suppression inside a reviewed, fail-loud script, never `2>&1` or a force-success tail, and never an ad-hoc command.
- **A refused write is reported, never re-shaped, and the maintainer's say-so does not lift a refusal by the harness.** These are two different permissions and only one of them is the maintainer's to give. When the agent harness refuses a write, the maintainer authorizing it in conversation does not change the outcome, and the identical call is refused again, so a second attempt is not worth making and reading the second refusal as a flake is how an agent starts hunting for another shape of the same request. **That hunt is the failure this rule exists to stop.** Re-expressing a refused `gh` command as a raw `gh api -X POST` reaches the same endpoint with the same identity and the same blast radius, having defeated the one control that stopped it, and it is the more dangerous version because the agent believes it has permission. So a refused write is never re-attempted through a different API surface, a different tool, or a rephrasing, and it is never routed around by the agent writing itself a permission rule, which is self-authorization whatever the maintainer said. Two routes remain, both of them the maintainer's: they add the permission rule themselves, or they run the command themselves. Raise it as a blocked decision naming those two (see "Communicating with the User"), and where the work needs the result rather than the call, say what the agent will verify once the maintainer has run it. **A refusal is also a fact about the contract, not just about the session**: where a required verification can only be performed by a write the agent is refused, the document requiring it says so and names who runs it, since a check that is mandatory and unperformable is quietly dropped and then reported as done.
- **Each task runs in its own checkout, in its own directory, on its own feature branch.** The unit is the task rather than the agent, since one agent moving between two repositories meets the same hazard as two agents sharing one tree, and a rule written per agent permits exactly the case that goes wrong. The commands that cross the boundary are the ordinary ones rather than the reckless ones, and each is correct in isolation: a blanket `git add -A` sweeps another task's uncommitted work into the commit, a `git reset --hard` deletes it, and a branch switch carries it into an unrelated change. The mechanical habit that holds the rule up is that a mutating command takes an absolute path, or a `cd` to one in the same invocation, rather than the working directory it inherited, because a read in the wrong directory is a wasted call and a write there is damage.
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Route GitHub Writes Through Portable Tooling by ptr727 · Pull Request #808 · ptr727/ProjectTemplate · GitHub
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
8 changes: 5 additions & 3 deletions .agents/skills/pr-review-conduct/SKILL.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -124,7 +124,8 @@ reviewer's own words to identify it), give one bold verdict per finding (`Fixed
`Disproven`, or `No change needed`), state the `(N)` count the block gave so answers can be
checked against findings, and link the review round. **Read every round, not only the head.** A
suppressed finding does not retire when a later push supersedes it, it just stops showing up in a
head-scoped query while still unanswered.
head-scoped query while still unanswered. Post the answer with `scripts/pr_review.py comment`
from a hub checkout. Do not use a provider connector or reconstruct the GitHub mutation.

## Escalate to the maintainer when

Expand All@@ -138,7 +139,8 @@ head-scoped query while still unanswered.

This skill is the provider-agnostic contract. Use `scripts/pr_review.py` from a hub checkout for
the GitHub-specific API operations. `status` reports coverage, threads, body-only findings, and
shapes in one call. `wait` requests and polls in-process. `reply` resolves a thread by matching
the finding's own words instead of a line number a fix push can move. The repository's
shapes in one call. `wait` requests and polls in-process. `comment` posts a PR-conversation
answer after it reads the PR node ID. `reply` resolves a thread by matching the finding's own
words instead of a line number a fix push can move. The repository's
`.github/copilot-instructions.md` bootstraps Copilot into the `code-review` skill and its stable
coverage marker. Do not reconstruct the API operations by hand.
2 changes: 1 addition & 1 deletion .claude-plugin/fleet-skills/.source-digest
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
ac73ed965d3dfe48
a4b2a2c434e2ab63
Original file line numberDiff line numberDiff line change
Expand Up@@ -124,7 +124,8 @@ reviewer's own words to identify it), give one bold verdict per finding (`Fixed
`Disproven`, or `No change needed`), state the `(N)` count the block gave so answers can be
checked against findings, and link the review round. **Read every round, not only the head.** A
suppressed finding does not retire when a later push supersedes it, it just stops showing up in a
head-scoped query while still unanswered.
head-scoped query while still unanswered. Post the answer with `scripts/pr_review.py comment`
from a hub checkout. Do not use a provider connector or reconstruct the GitHub mutation.

## Escalate to the maintainer when

Expand All@@ -138,7 +139,8 @@ head-scoped query while still unanswered.

This skill is the provider-agnostic contract. Use `scripts/pr_review.py` from a hub checkout for
the GitHub-specific API operations. `status` reports coverage, threads, body-only findings, and
shapes in one call. `wait` requests and polls in-process. `reply` resolves a thread by matching
the finding's own words instead of a line number a fix push can move. The repository's
shapes in one call. `wait` requests and polls in-process. `comment` posts a PR-conversation
answer after it reads the PR node ID. `reply` resolves a thread by matching the finding's own
words instead of a line number a fix push can move. The repository's
`.github/copilot-instructions.md` bootstraps Copilot into the `code-review` skill and its stable
coverage marker. Do not reconstruct the API operations by hand.
7 changes: 4 additions & 3 deletions .github/copilot-instructions.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -38,9 +38,10 @@ For every review:
5. End the review body with the exact machine-readable marker required by the `code-review` skill.

The review automation is `scripts/pr_review.py`, run from a hub checkout. Use its `status`, `wait`,
and `reply --resolve` commands instead of reconstructing GraphQL queries or copying review
identifiers by hand. Its status gate verifies the current head, diff coverage, output shape,
inline threads, body-only findings, and required checks.
`comment`, and `reply --resolve` commands instead of reconstructing GraphQL queries or copying
review identifiers by hand. Use `comment` for a suppressed-finding answer in the pull request
conversation. Its status gate verifies the current head, diff coverage, output shape, inline
threads, body-only findings, and required checks.

A formal review with no findings is complete only when it covers the current head and states full
diff coverage. A refusal, partial or absent coverage statement, unrecognized output shape,
Expand Down
8 changes: 5 additions & 3 deletions .github/skills/pr-review-conduct/SKILL.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -124,7 +124,8 @@ reviewer's own words to identify it), give one bold verdict per finding (`Fixed
`Disproven`, or `No change needed`), state the `(N)` count the block gave so answers can be
checked against findings, and link the review round. **Read every round, not only the head.** A
suppressed finding does not retire when a later push supersedes it, it just stops showing up in a
head-scoped query while still unanswered.
head-scoped query while still unanswered. Post the answer with `scripts/pr_review.py comment`
from a hub checkout. Do not use a provider connector or reconstruct the GitHub mutation.

## Escalate to the maintainer when

Expand All@@ -138,7 +139,8 @@ head-scoped query while still unanswered.

This skill is the provider-agnostic contract. Use `scripts/pr_review.py` from a hub checkout for
the GitHub-specific API operations. `status` reports coverage, threads, body-only findings, and
shapes in one call. `wait` requests and polls in-process. `reply` resolves a thread by matching
the finding's own words instead of a line number a fix push can move. The repository's
shapes in one call. `wait` requests and polls in-process. `comment` posts a PR-conversation
answer after it reads the PR node ID. `reply` resolves a thread by matching the finding's own
words instead of a line number a fix push can move. The repository's
`.github/copilot-instructions.md` bootstraps Copilot into the `code-review` skill and its stable
coverage marker. Do not reconstruct the API operations by hand.
3 changes: 2 additions & 1 deletion GOVERNANCE.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -25,7 +25,8 @@ This section keeps the full rules and is surfaced at its decision moment by the
A state-changing GitHub call is the highest-blast-radius thing an agent does here: it runs under the maintainer's identity, so one wrong target writes to another owner's repository as the maintainer, an outward-facing and hard-to-reverse act. These rules bound every write (a git push, an API mutation, a comment, a label, a merge) on any platform, and they bound a write to a checkout on disk as well, since a blanket add or a hard reset in a working tree another task is using destroys work without ever reaching GitHub. Reads are unrestricted, and how far a local read can be trusted is governed under "Verification Discipline" rather than here. The bounds below are on writes.

- **Write only within the owner of the current project's repository.** Every state-changing call targets this project's `origin` or another repository under the same owner, which is the fleet the maintainer already administers. A broad or logged-in identity is capability, not permission: a token that *can* reach another owner's repository does not authorize writing to it. Writing under a **different owner** needs explicit human permission naming that repository, granted deliberately rather than assumed from a token's reach, and a "harmless test" write is still a write, so there is no probe exception. That boundary is where the harm sits, since the incident this rule exists for was a stray comment on a stranger's repository, not work across the maintainer's own projects. Reads from anywhere are fine.
- **Never fabricate, guess, or reuse an identifier passed to a write.** Every id a state-changing call consumes (a node id, a numeric id, a thread or comment id) is captured from a live query in the **same** session into a variable and passed from there. Do not hand-type an id, guess it, recall it from memory or an earlier session, or copy it from documentation or an example. Ids commonly resolve **globally**, so a wrong-but-valid id does not fail. It writes to the wrong target, in someone else's repository. If a query returns no id, stop rather than invent one to proceed.
- **Provider connectors are read-only for fleet work.** Use a provider's GitHub connector for reads where it helps. Perform each GitHub mutation through the documented hub tool, or through authenticated `gh` where no tool owns the operation. This gives Codex, Claude, opencode, and a terminal session one write path with the same checks. It also avoids a connector mutation that predictably lacks repository authorization while the verified `gh` session already has it. A provider-specific instruction may explain how to reach the common path. It never replaces that path with its own mutation surface.
- **Never fabricate, guess, or reuse an identifier passed to a write.** Capture every identifier a state-changing call consumes from a live query in the **same** session. This includes node, numeric, thread, and comment ids. Pass the captured value directly. Do not hand-type an id, recall it from another session, or copy it from documentation or an example. Ids commonly resolve **globally**, so a wrong-but-valid id does not fail. It writes to the wrong target, in someone else's repository. Apply the same rule to an identifier embedded in outward-facing text. Read the complete URL from the live object. Never construct a plausible link from an unverified id. If a query returns no id or URL, stop rather than invent one to proceed.
- **A write is never a probe, and a write's output is never suppressed.** Never fire a state-changing call to see whether it works: decide it should happen, make it happen, and read the result. Never append output-discarding redirection or a force-success tail to a mutation (for example `>/dev/null`, `2>/dev/null`, `&>/dev/null`, `|| true`, `|| :`, `|| echo`), because the write's output is exactly what must be read. A write that appears to fail is **verified, not assumed harmless**, because the operation may have succeeded on the server while the client reported an error, so confirm the actual state before retrying or moving on. The ban targets hiding a *failure*. An ad-hoc call's response is the only signal you get, so `>/dev/null 2>&1`, `|| true`, and `|| echo`, which swallow the error stream or force success, are never acceptable on one. A committed script under `set -e` is a narrow exception: it may send a write's *stdout* to `/dev/null` to drop the success-response noise, because stderr stays visible and a failed write still aborts loudly (the hub's own `repo-config/configure.sh` does exactly this, and a repository reaches it there rather than carrying a copy). The exception is stdout-only suppression inside a reviewed, fail-loud script, never `2>&1` or a force-success tail, and never an ad-hoc command.
- **A refused write is reported, never re-shaped, and the maintainer's say-so does not lift a refusal by the harness.** These are two different permissions and only one of them is the maintainer's to give. When the agent harness refuses a write, the maintainer authorizing it in conversation does not change the outcome, and the identical call is refused again, so a second attempt is not worth making and reading the second refusal as a flake is how an agent starts hunting for another shape of the same request. **That hunt is the failure this rule exists to stop.** Re-expressing a refused `gh` command as a raw `gh api -X POST` reaches the same endpoint with the same identity and the same blast radius, having defeated the one control that stopped it, and it is the more dangerous version because the agent believes it has permission. So a refused write is never re-attempted through a different API surface, a different tool, or a rephrasing, and it is never routed around by the agent writing itself a permission rule, which is self-authorization whatever the maintainer said. Two routes remain, both of them the maintainer's: they add the permission rule themselves, or they run the command themselves. Raise it as a blocked decision naming those two (see "Communicating with the User"), and where the work needs the result rather than the call, say what the agent will verify once the maintainer has run it. **A refusal is also a fact about the contract, not just about the session**: where a required verification can only be performed by a write the agent is refused, the document requiring it says so and names who runs it, since a check that is mandatory and unperformable is quietly dropped and then reported as done.
- **Each task runs in its own checkout, in its own directory, on its own feature branch.** The unit is the task rather than the agent, since one agent moving between two repositories meets the same hazard as two agents sharing one tree, and a rule written per agent permits exactly the case that goes wrong. The commands that cross the boundary are the ordinary ones rather than the reckless ones, and each is correct in isolation: a blanket `git add -A` sweeps another task's uncommitted work into the commit, a `git reset --hard` deletes it, and a branch switch carries it into an unrelated change. The mechanical habit that holds the rule up is that a mutating command takes an absolute path, or a `cd` to one in the same invocation, rather than the working directory it inherited, because a read in the wrong directory is a wasted call and a write there is damage.
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Route GitHub Writes Through Portable Tooling by ptr727 · Pull Request #808 · ptr727/ProjectTemplate · GitHub
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
8 changes: 5 additions & 3 deletions .agents/skills/pr-review-conduct/SKILL.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -124,7 +124,8 @@ reviewer's own words to identify it), give one bold verdict per finding (`Fixed
`Disproven`, or `No change needed`), state the `(N)` count the block gave so answers can be
checked against findings, and link the review round. **Read every round, not only the head.** A
suppressed finding does not retire when a later push supersedes it, it just stops showing up in a
head-scoped query while still unanswered.
head-scoped query while still unanswered. Post the answer with `scripts/pr_review.py comment`
from a hub checkout. Do not use a provider connector or reconstruct the GitHub mutation.

## Escalate to the maintainer when

Expand All@@ -138,7 +139,8 @@ head-scoped query while still unanswered.

This skill is the provider-agnostic contract. Use `scripts/pr_review.py` from a hub checkout for
the GitHub-specific API operations. `status` reports coverage, threads, body-only findings, and
shapes in one call. `wait` requests and polls in-process. `reply` resolves a thread by matching
the finding's own words instead of a line number a fix push can move. The repository's
shapes in one call. `wait` requests and polls in-process. `comment` posts a PR-conversation
answer after it reads the PR node ID. `reply` resolves a thread by matching the finding's own
words instead of a line number a fix push can move. The repository's
`.github/copilot-instructions.md` bootstraps Copilot into the `code-review` skill and its stable
coverage marker. Do not reconstruct the API operations by hand.
2 changes: 1 addition & 1 deletion .claude-plugin/fleet-skills/.source-digest
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
ac73ed965d3dfe48
a4b2a2c434e2ab63
Original file line numberDiff line numberDiff line change
Expand Up@@ -124,7 +124,8 @@ reviewer's own words to identify it), give one bold verdict per finding (`Fixed
`Disproven`, or `No change needed`), state the `(N)` count the block gave so answers can be
checked against findings, and link the review round. **Read every round, not only the head.** A
suppressed finding does not retire when a later push supersedes it, it just stops showing up in a
head-scoped query while still unanswered.
head-scoped query while still unanswered. Post the answer with `scripts/pr_review.py comment`
from a hub checkout. Do not use a provider connector or reconstruct the GitHub mutation.

## Escalate to the maintainer when

Expand All@@ -138,7 +139,8 @@ head-scoped query while still unanswered.

This skill is the provider-agnostic contract. Use `scripts/pr_review.py` from a hub checkout for
the GitHub-specific API operations. `status` reports coverage, threads, body-only findings, and
shapes in one call. `wait` requests and polls in-process. `reply` resolves a thread by matching
the finding's own words instead of a line number a fix push can move. The repository's
shapes in one call. `wait` requests and polls in-process. `comment` posts a PR-conversation
answer after it reads the PR node ID. `reply` resolves a thread by matching the finding's own
words instead of a line number a fix push can move. The repository's
`.github/copilot-instructions.md` bootstraps Copilot into the `code-review` skill and its stable
coverage marker. Do not reconstruct the API operations by hand.
7 changes: 4 additions & 3 deletions .github/copilot-instructions.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -38,9 +38,10 @@ For every review:
5. End the review body with the exact machine-readable marker required by the `code-review` skill.

The review automation is `scripts/pr_review.py`, run from a hub checkout. Use its `status`, `wait`,
and `reply --resolve` commands instead of reconstructing GraphQL queries or copying review
identifiers by hand. Its status gate verifies the current head, diff coverage, output shape,
inline threads, body-only findings, and required checks.
`comment`, and `reply --resolve` commands instead of reconstructing GraphQL queries or copying
review identifiers by hand. Use `comment` for a suppressed-finding answer in the pull request
conversation. Its status gate verifies the current head, diff coverage, output shape, inline
threads, body-only findings, and required checks.

A formal review with no findings is complete only when it covers the current head and states full
diff coverage. A refusal, partial or absent coverage statement, unrecognized output shape,
Expand Down
8 changes: 5 additions & 3 deletions .github/skills/pr-review-conduct/SKILL.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -124,7 +124,8 @@ reviewer's own words to identify it), give one bold verdict per finding (`Fixed
`Disproven`, or `No change needed`), state the `(N)` count the block gave so answers can be
checked against findings, and link the review round. **Read every round, not only the head.** A
suppressed finding does not retire when a later push supersedes it, it just stops showing up in a
head-scoped query while still unanswered.
head-scoped query while still unanswered. Post the answer with `scripts/pr_review.py comment`
from a hub checkout. Do not use a provider connector or reconstruct the GitHub mutation.

## Escalate to the maintainer when

Expand All@@ -138,7 +139,8 @@ head-scoped query while still unanswered.

This skill is the provider-agnostic contract. Use `scripts/pr_review.py` from a hub checkout for
the GitHub-specific API operations. `status` reports coverage, threads, body-only findings, and
shapes in one call. `wait` requests and polls in-process. `reply` resolves a thread by matching
the finding's own words instead of a line number a fix push can move. The repository's
shapes in one call. `wait` requests and polls in-process. `comment` posts a PR-conversation
answer after it reads the PR node ID. `reply` resolves a thread by matching the finding's own
words instead of a line number a fix push can move. The repository's
`.github/copilot-instructions.md` bootstraps Copilot into the `code-review` skill and its stable
coverage marker. Do not reconstruct the API operations by hand.
3 changes: 2 additions & 1 deletion GOVERNANCE.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -25,7 +25,8 @@ This section keeps the full rules and is surfaced at its decision moment by the
A state-changing GitHub call is the highest-blast-radius thing an agent does here: it runs under the maintainer's identity, so one wrong target writes to another owner's repository as the maintainer, an outward-facing and hard-to-reverse act. These rules bound every write (a git push, an API mutation, a comment, a label, a merge) on any platform, and they bound a write to a checkout on disk as well, since a blanket add or a hard reset in a working tree another task is using destroys work without ever reaching GitHub. Reads are unrestricted, and how far a local read can be trusted is governed under "Verification Discipline" rather than here. The bounds below are on writes.

- **Write only within the owner of the current project's repository.** Every state-changing call targets this project's `origin` or another repository under the same owner, which is the fleet the maintainer already administers. A broad or logged-in identity is capability, not permission: a token that *can* reach another owner's repository does not authorize writing to it. Writing under a **different owner** needs explicit human permission naming that repository, granted deliberately rather than assumed from a token's reach, and a "harmless test" write is still a write, so there is no probe exception. That boundary is where the harm sits, since the incident this rule exists for was a stray comment on a stranger's repository, not work across the maintainer's own projects. Reads from anywhere are fine.
- **Never fabricate, guess, or reuse an identifier passed to a write.** Every id a state-changing call consumes (a node id, a numeric id, a thread or comment id) is captured from a live query in the **same** session into a variable and passed from there. Do not hand-type an id, guess it, recall it from memory or an earlier session, or copy it from documentation or an example. Ids commonly resolve **globally**, so a wrong-but-valid id does not fail. It writes to the wrong target, in someone else's repository. If a query returns no id, stop rather than invent one to proceed.
- **Provider connectors are read-only for fleet work.** Use a provider's GitHub connector for reads where it helps. Perform each GitHub mutation through the documented hub tool, or through authenticated `gh` where no tool owns the operation. This gives Codex, Claude, opencode, and a terminal session one write path with the same checks. It also avoids a connector mutation that predictably lacks repository authorization while the verified `gh` session already has it. A provider-specific instruction may explain how to reach the common path. It never replaces that path with its own mutation surface.
- **Never fabricate, guess, or reuse an identifier passed to a write.** Capture every identifier a state-changing call consumes from a live query in the **same** session. This includes node, numeric, thread, and comment ids. Pass the captured value directly. Do not hand-type an id, recall it from another session, or copy it from documentation or an example. Ids commonly resolve **globally**, so a wrong-but-valid id does not fail. It writes to the wrong target, in someone else's repository. Apply the same rule to an identifier embedded in outward-facing text. Read the complete URL from the live object. Never construct a plausible link from an unverified id. If a query returns no id or URL, stop rather than invent one to proceed.
- **A write is never a probe, and a write's output is never suppressed.** Never fire a state-changing call to see whether it works: decide it should happen, make it happen, and read the result. Never append output-discarding redirection or a force-success tail to a mutation (for example `>/dev/null`, `2>/dev/null`, `&>/dev/null`, `|| true`, `|| :`, `|| echo`), because the write's output is exactly what must be read. A write that appears to fail is **verified, not assumed harmless**, because the operation may have succeeded on the server while the client reported an error, so confirm the actual state before retrying or moving on. The ban targets hiding a *failure*. An ad-hoc call's response is the only signal you get, so `>/dev/null 2>&1`, `|| true`, and `|| echo`, which swallow the error stream or force success, are never acceptable on one. A committed script under `set -e` is a narrow exception: it may send a write's *stdout* to `/dev/null` to drop the success-response noise, because stderr stays visible and a failed write still aborts loudly (the hub's own `repo-config/configure.sh` does exactly this, and a repository reaches it there rather than carrying a copy). The exception is stdout-only suppression inside a reviewed, fail-loud script, never `2>&1` or a force-success tail, and never an ad-hoc command.
- **A refused write is reported, never re-shaped, and the maintainer's say-so does not lift a refusal by the harness.** These are two different permissions and only one of them is the maintainer's to give. When the agent harness refuses a write, the maintainer authorizing it in conversation does not change the outcome, and the identical call is refused again, so a second attempt is not worth making and reading the second refusal as a flake is how an agent starts hunting for another shape of the same request. **That hunt is the failure this rule exists to stop.** Re-expressing a refused `gh` command as a raw `gh api -X POST` reaches the same endpoint with the same identity and the same blast radius, having defeated the one control that stopped it, and it is the more dangerous version because the agent believes it has permission. So a refused write is never re-attempted through a different API surface, a different tool, or a rephrasing, and it is never routed around by the agent writing itself a permission rule, which is self-authorization whatever the maintainer said. Two routes remain, both of them the maintainer's: they add the permission rule themselves, or they run the command themselves. Raise it as a blocked decision naming those two (see "Communicating with the User"), and where the work needs the result rather than the call, say what the agent will verify once the maintainer has run it. **A refusal is also a fact about the contract, not just about the session**: where a required verification can only be performed by a write the agent is refused, the document requiring it says so and names who runs it, since a check that is mandatory and unperformable is quietly dropped and then reported as done.
- **Each task runs in its own checkout, in its own directory, on its own feature branch.** The unit is the task rather than the agent, since one agent moving between two repositories meets the same hazard as two agents sharing one tree, and a rule written per agent permits exactly the case that goes wrong. The commands that cross the boundary are the ordinary ones rather than the reckless ones, and each is correct in isolation: a blanket `git add -A` sweeps another task's uncommitted work into the commit, a `git reset --hard` deletes it, and a branch switch carries it into an unrelated change. The mechanical habit that holds the rule up is that a mutating command takes an absolute path, or a `cd` to one in the same invocation, rather than the working directory it inherited, because a read in the wrong directory is a wasted call and a write there is damage.
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Route GitHub Writes Through Portable Tooling by ptr727 · Pull Request #808 · ptr727/ProjectTemplate · GitHub
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
8 changes: 5 additions & 3 deletions .agents/skills/pr-review-conduct/SKILL.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -124,7 +124,8 @@ reviewer's own words to identify it), give one bold verdict per finding (`Fixed
`Disproven`, or `No change needed`), state the `(N)` count the block gave so answers can be
checked against findings, and link the review round. **Read every round, not only the head.** A
suppressed finding does not retire when a later push supersedes it, it just stops showing up in a
head-scoped query while still unanswered.
head-scoped query while still unanswered. Post the answer with `scripts/pr_review.py comment`
from a hub checkout. Do not use a provider connector or reconstruct the GitHub mutation.

## Escalate to the maintainer when

Expand All@@ -138,7 +139,8 @@ head-scoped query while still unanswered.

This skill is the provider-agnostic contract. Use `scripts/pr_review.py` from a hub checkout for
the GitHub-specific API operations. `status` reports coverage, threads, body-only findings, and
shapes in one call. `wait` requests and polls in-process. `reply` resolves a thread by matching
the finding's own words instead of a line number a fix push can move. The repository's
shapes in one call. `wait` requests and polls in-process. `comment` posts a PR-conversation
answer after it reads the PR node ID. `reply` resolves a thread by matching the finding's own
words instead of a line number a fix push can move. The repository's
`.github/copilot-instructions.md` bootstraps Copilot into the `code-review` skill and its stable
coverage marker. Do not reconstruct the API operations by hand.
2 changes: 1 addition & 1 deletion .claude-plugin/fleet-skills/.source-digest
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
ac73ed965d3dfe48
a4b2a2c434e2ab63
Original file line numberDiff line numberDiff line change
Expand Up@@ -124,7 +124,8 @@ reviewer's own words to identify it), give one bold verdict per finding (`Fixed
`Disproven`, or `No change needed`), state the `(N)` count the block gave so answers can be
checked against findings, and link the review round. **Read every round, not only the head.** A
suppressed finding does not retire when a later push supersedes it, it just stops showing up in a
head-scoped query while still unanswered.
head-scoped query while still unanswered. Post the answer with `scripts/pr_review.py comment`
from a hub checkout. Do not use a provider connector or reconstruct the GitHub mutation.

## Escalate to the maintainer when

Expand All@@ -138,7 +139,8 @@ head-scoped query while still unanswered.

This skill is the provider-agnostic contract. Use `scripts/pr_review.py` from a hub checkout for
the GitHub-specific API operations. `status` reports coverage, threads, body-only findings, and
shapes in one call. `wait` requests and polls in-process. `reply` resolves a thread by matching
the finding's own words instead of a line number a fix push can move. The repository's
shapes in one call. `wait` requests and polls in-process. `comment` posts a PR-conversation
answer after it reads the PR node ID. `reply` resolves a thread by matching the finding's own
words instead of a line number a fix push can move. The repository's
`.github/copilot-instructions.md` bootstraps Copilot into the `code-review` skill and its stable
coverage marker. Do not reconstruct the API operations by hand.
7 changes: 4 additions & 3 deletions .github/copilot-instructions.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -38,9 +38,10 @@ For every review:
5. End the review body with the exact machine-readable marker required by the `code-review` skill.

The review automation is `scripts/pr_review.py`, run from a hub checkout. Use its `status`, `wait`,
and `reply --resolve` commands instead of reconstructing GraphQL queries or copying review
identifiers by hand. Its status gate verifies the current head, diff coverage, output shape,
inline threads, body-only findings, and required checks.
`comment`, and `reply --resolve` commands instead of reconstructing GraphQL queries or copying
review identifiers by hand. Use `comment` for a suppressed-finding answer in the pull request
conversation. Its status gate verifies the current head, diff coverage, output shape, inline
threads, body-only findings, and required checks.

A formal review with no findings is complete only when it covers the current head and states full
diff coverage. A refusal, partial or absent coverage statement, unrecognized output shape,
Expand Down
8 changes: 5 additions & 3 deletions .github/skills/pr-review-conduct/SKILL.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -124,7 +124,8 @@ reviewer's own words to identify it), give one bold verdict per finding (`Fixed
`Disproven`, or `No change needed`), state the `(N)` count the block gave so answers can be
checked against findings, and link the review round. **Read every round, not only the head.** A
suppressed finding does not retire when a later push supersedes it, it just stops showing up in a
head-scoped query while still unanswered.
head-scoped query while still unanswered. Post the answer with `scripts/pr_review.py comment`
from a hub checkout. Do not use a provider connector or reconstruct the GitHub mutation.

## Escalate to the maintainer when

Expand All@@ -138,7 +139,8 @@ head-scoped query while still unanswered.

This skill is the provider-agnostic contract. Use `scripts/pr_review.py` from a hub checkout for
the GitHub-specific API operations. `status` reports coverage, threads, body-only findings, and
shapes in one call. `wait` requests and polls in-process. `reply` resolves a thread by matching
the finding's own words instead of a line number a fix push can move. The repository's
shapes in one call. `wait` requests and polls in-process. `comment` posts a PR-conversation
answer after it reads the PR node ID. `reply` resolves a thread by matching the finding's own
words instead of a line number a fix push can move. The repository's
`.github/copilot-instructions.md` bootstraps Copilot into the `code-review` skill and its stable
coverage marker. Do not reconstruct the API operations by hand.
3 changes: 2 additions & 1 deletion GOVERNANCE.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -25,7 +25,8 @@ This section keeps the full rules and is surfaced at its decision moment by the
A state-changing GitHub call is the highest-blast-radius thing an agent does here: it runs under the maintainer's identity, so one wrong target writes to another owner's repository as the maintainer, an outward-facing and hard-to-reverse act. These rules bound every write (a git push, an API mutation, a comment, a label, a merge) on any platform, and they bound a write to a checkout on disk as well, since a blanket add or a hard reset in a working tree another task is using destroys work without ever reaching GitHub. Reads are unrestricted, and how far a local read can be trusted is governed under "Verification Discipline" rather than here. The bounds below are on writes.

- **Write only within the owner of the current project's repository.** Every state-changing call targets this project's `origin` or another repository under the same owner, which is the fleet the maintainer already administers. A broad or logged-in identity is capability, not permission: a token that *can* reach another owner's repository does not authorize writing to it. Writing under a **different owner** needs explicit human permission naming that repository, granted deliberately rather than assumed from a token's reach, and a "harmless test" write is still a write, so there is no probe exception. That boundary is where the harm sits, since the incident this rule exists for was a stray comment on a stranger's repository, not work across the maintainer's own projects. Reads from anywhere are fine.
- **Never fabricate, guess, or reuse an identifier passed to a write.** Every id a state-changing call consumes (a node id, a numeric id, a thread or comment id) is captured from a live query in the **same** session into a variable and passed from there. Do not hand-type an id, guess it, recall it from memory or an earlier session, or copy it from documentation or an example. Ids commonly resolve **globally**, so a wrong-but-valid id does not fail. It writes to the wrong target, in someone else's repository. If a query returns no id, stop rather than invent one to proceed.
- **Provider connectors are read-only for fleet work.** Use a provider's GitHub connector for reads where it helps. Perform each GitHub mutation through the documented hub tool, or through authenticated `gh` where no tool owns the operation. This gives Codex, Claude, opencode, and a terminal session one write path with the same checks. It also avoids a connector mutation that predictably lacks repository authorization while the verified `gh` session already has it. A provider-specific instruction may explain how to reach the common path. It never replaces that path with its own mutation surface.
- **Never fabricate, guess, or reuse an identifier passed to a write.** Capture every identifier a state-changing call consumes from a live query in the **same** session. This includes node, numeric, thread, and comment ids. Pass the captured value directly. Do not hand-type an id, recall it from another session, or copy it from documentation or an example. Ids commonly resolve **globally**, so a wrong-but-valid id does not fail. It writes to the wrong target, in someone else's repository. Apply the same rule to an identifier embedded in outward-facing text. Read the complete URL from the live object. Never construct a plausible link from an unverified id. If a query returns no id or URL, stop rather than invent one to proceed.
- **A write is never a probe, and a write's output is never suppressed.** Never fire a state-changing call to see whether it works: decide it should happen, make it happen, and read the result. Never append output-discarding redirection or a force-success tail to a mutation (for example `>/dev/null`, `2>/dev/null`, `&>/dev/null`, `|| true`, `|| :`, `|| echo`), because the write's output is exactly what must be read. A write that appears to fail is **verified, not assumed harmless**, because the operation may have succeeded on the server while the client reported an error, so confirm the actual state before retrying or moving on. The ban targets hiding a *failure*. An ad-hoc call's response is the only signal you get, so `>/dev/null 2>&1`, `|| true`, and `|| echo`, which swallow the error stream or force success, are never acceptable on one. A committed script under `set -e` is a narrow exception: it may send a write's *stdout* to `/dev/null` to drop the success-response noise, because stderr stays visible and a failed write still aborts loudly (the hub's own `repo-config/configure.sh` does exactly this, and a repository reaches it there rather than carrying a copy). The exception is stdout-only suppression inside a reviewed, fail-loud script, never `2>&1` or a force-success tail, and never an ad-hoc command.
- **A refused write is reported, never re-shaped, and the maintainer's say-so does not lift a refusal by the harness.** These are two different permissions and only one of them is the maintainer's to give. When the agent harness refuses a write, the maintainer authorizing it in conversation does not change the outcome, and the identical call is refused again, so a second attempt is not worth making and reading the second refusal as a flake is how an agent starts hunting for another shape of the same request. **That hunt is the failure this rule exists to stop.** Re-expressing a refused `gh` command as a raw `gh api -X POST` reaches the same endpoint with the same identity and the same blast radius, having defeated the one control that stopped it, and it is the more dangerous version because the agent believes it has permission. So a refused write is never re-attempted through a different API surface, a different tool, or a rephrasing, and it is never routed around by the agent writing itself a permission rule, which is self-authorization whatever the maintainer said. Two routes remain, both of them the maintainer's: they add the permission rule themselves, or they run the command themselves. Raise it as a blocked decision naming those two (see "Communicating with the User"), and where the work needs the result rather than the call, say what the agent will verify once the maintainer has run it. **A refusal is also a fact about the contract, not just about the session**: where a required verification can only be performed by a write the agent is refused, the document requiring it says so and names who runs it, since a check that is mandatory and unperformable is quietly dropped and then reported as done.
- **Each task runs in its own checkout, in its own directory, on its own feature branch.** The unit is the task rather than the agent, since one agent moving between two repositories meets the same hazard as two agents sharing one tree, and a rule written per agent permits exactly the case that goes wrong. The commands that cross the boundary are the ordinary ones rather than the reckless ones, and each is correct in isolation: a blanket `git add -A` sweeps another task's uncommitted work into the commit, a `git reset --hard` deletes it, and a branch switch carries it into an unrelated change. The mechanical habit that holds the rule up is that a mutating command takes an absolute path, or a `cd` to one in the same invocation, rather than the working directory it inherited, because a read in the wrong directory is a wasted call and a write there is damage.
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' Route GitHub Writes Through Portable Tooling by ptr727 · Pull Request #808 · ptr727/ProjectTemplate · GitHub
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
8 changes: 5 additions & 3 deletions .agents/skills/pr-review-conduct/SKILL.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -124,7 +124,8 @@ reviewer's own words to identify it), give one bold verdict per finding (`Fixed
`Disproven`, or `No change needed`), state the `(N)` count the block gave so answers can be
checked against findings, and link the review round. **Read every round, not only the head.** A
suppressed finding does not retire when a later push supersedes it, it just stops showing up in a
head-scoped query while still unanswered.
head-scoped query while still unanswered. Post the answer with `scripts/pr_review.py comment`
from a hub checkout. Do not use a provider connector or reconstruct the GitHub mutation.

## Escalate to the maintainer when

Expand All@@ -138,7 +139,8 @@ head-scoped query while still unanswered.

This skill is the provider-agnostic contract. Use `scripts/pr_review.py` from a hub checkout for
the GitHub-specific API operations. `status` reports coverage, threads, body-only findings, and
shapes in one call. `wait` requests and polls in-process. `reply` resolves a thread by matching
the finding's own words instead of a line number a fix push can move. The repository's
shapes in one call. `wait` requests and polls in-process. `comment` posts a PR-conversation
answer after it reads the PR node ID. `reply` resolves a thread by matching the finding's own
words instead of a line number a fix push can move. The repository's
`.github/copilot-instructions.md` bootstraps Copilot into the `code-review` skill and its stable
coverage marker. Do not reconstruct the API operations by hand.
2 changes: 1 addition & 1 deletion .claude-plugin/fleet-skills/.source-digest
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
ac73ed965d3dfe48
a4b2a2c434e2ab63
Original file line numberDiff line numberDiff line change
Expand Up@@ -124,7 +124,8 @@ reviewer's own words to identify it), give one bold verdict per finding (`Fixed
`Disproven`, or `No change needed`), state the `(N)` count the block gave so answers can be
checked against findings, and link the review round. **Read every round, not only the head.** A
suppressed finding does not retire when a later push supersedes it, it just stops showing up in a
head-scoped query while still unanswered.
head-scoped query while still unanswered. Post the answer with `scripts/pr_review.py comment`
from a hub checkout. Do not use a provider connector or reconstruct the GitHub mutation.

## Escalate to the maintainer when

Expand All@@ -138,7 +139,8 @@ head-scoped query while still unanswered.

This skill is the provider-agnostic contract. Use `scripts/pr_review.py` from a hub checkout for
the GitHub-specific API operations. `status` reports coverage, threads, body-only findings, and
shapes in one call. `wait` requests and polls in-process. `reply` resolves a thread by matching
the finding's own words instead of a line number a fix push can move. The repository's
shapes in one call. `wait` requests and polls in-process. `comment` posts a PR-conversation
answer after it reads the PR node ID. `reply` resolves a thread by matching the finding's own
words instead of a line number a fix push can move. The repository's
`.github/copilot-instructions.md` bootstraps Copilot into the `code-review` skill and its stable
coverage marker. Do not reconstruct the API operations by hand.
7 changes: 4 additions & 3 deletions .github/copilot-instructions.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -38,9 +38,10 @@ For every review:
5. End the review body with the exact machine-readable marker required by the `code-review` skill.

The review automation is `scripts/pr_review.py`, run from a hub checkout. Use its `status`, `wait`,
and `reply --resolve` commands instead of reconstructing GraphQL queries or copying review
identifiers by hand. Its status gate verifies the current head, diff coverage, output shape,
inline threads, body-only findings, and required checks.
`comment`, and `reply --resolve` commands instead of reconstructing GraphQL queries or copying
review identifiers by hand. Use `comment` for a suppressed-finding answer in the pull request
conversation. Its status gate verifies the current head, diff coverage, output shape, inline
threads, body-only findings, and required checks.

A formal review with no findings is complete only when it covers the current head and states full
diff coverage. A refusal, partial or absent coverage statement, unrecognized output shape,
Expand Down
8 changes: 5 additions & 3 deletions .github/skills/pr-review-conduct/SKILL.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -124,7 +124,8 @@ reviewer's own words to identify it), give one bold verdict per finding (`Fixed
`Disproven`, or `No change needed`), state the `(N)` count the block gave so answers can be
checked against findings, and link the review round. **Read every round, not only the head.** A
suppressed finding does not retire when a later push supersedes it, it just stops showing up in a
head-scoped query while still unanswered.
head-scoped query while still unanswered. Post the answer with `scripts/pr_review.py comment`
from a hub checkout. Do not use a provider connector or reconstruct the GitHub mutation.

## Escalate to the maintainer when

Expand All@@ -138,7 +139,8 @@ head-scoped query while still unanswered.

This skill is the provider-agnostic contract. Use `scripts/pr_review.py` from a hub checkout for
the GitHub-specific API operations. `status` reports coverage, threads, body-only findings, and
shapes in one call. `wait` requests and polls in-process. `reply` resolves a thread by matching
the finding's own words instead of a line number a fix push can move. The repository's
shapes in one call. `wait` requests and polls in-process. `comment` posts a PR-conversation
answer after it reads the PR node ID. `reply` resolves a thread by matching the finding's own
words instead of a line number a fix push can move. The repository's
`.github/copilot-instructions.md` bootstraps Copilot into the `code-review` skill and its stable
coverage marker. Do not reconstruct the API operations by hand.
3 changes: 2 additions & 1 deletion GOVERNANCE.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -25,7 +25,8 @@ This section keeps the full rules and is surfaced at its decision moment by the
A state-changing GitHub call is the highest-blast-radius thing an agent does here: it runs under the maintainer's identity, so one wrong target writes to another owner's repository as the maintainer, an outward-facing and hard-to-reverse act. These rules bound every write (a git push, an API mutation, a comment, a label, a merge) on any platform, and they bound a write to a checkout on disk as well, since a blanket add or a hard reset in a working tree another task is using destroys work without ever reaching GitHub. Reads are unrestricted, and how far a local read can be trusted is governed under "Verification Discipline" rather than here. The bounds below are on writes.

- **Write only within the owner of the current project's repository.** Every state-changing call targets this project's `origin` or another repository under the same owner, which is the fleet the maintainer already administers. A broad or logged-in identity is capability, not permission: a token that *can* reach another owner's repository does not authorize writing to it. Writing under a **different owner** needs explicit human permission naming that repository, granted deliberately rather than assumed from a token's reach, and a "harmless test" write is still a write, so there is no probe exception. That boundary is where the harm sits, since the incident this rule exists for was a stray comment on a stranger's repository, not work across the maintainer's own projects. Reads from anywhere are fine.
- **Never fabricate, guess, or reuse an identifier passed to a write.** Every id a state-changing call consumes (a node id, a numeric id, a thread or comment id) is captured from a live query in the **same** session into a variable and passed from there. Do not hand-type an id, guess it, recall it from memory or an earlier session, or copy it from documentation or an example. Ids commonly resolve **globally**, so a wrong-but-valid id does not fail. It writes to the wrong target, in someone else's repository. If a query returns no id, stop rather than invent one to proceed.
- **Provider connectors are read-only for fleet work.** Use a provider's GitHub connector for reads where it helps. Perform each GitHub mutation through the documented hub tool, or through authenticated `gh` where no tool owns the operation. This gives Codex, Claude, opencode, and a terminal session one write path with the same checks. It also avoids a connector mutation that predictably lacks repository authorization while the verified `gh` session already has it. A provider-specific instruction may explain how to reach the common path. It never replaces that path with its own mutation surface.
- **Never fabricate, guess, or reuse an identifier passed to a write.** Capture every identifier a state-changing call consumes from a live query in the **same** session. This includes node, numeric, thread, and comment ids. Pass the captured value directly. Do not hand-type an id, recall it from another session, or copy it from documentation or an example. Ids commonly resolve **globally**, so a wrong-but-valid id does not fail. It writes to the wrong target, in someone else's repository. Apply the same rule to an identifier embedded in outward-facing text. Read the complete URL from the live object. Never construct a plausible link from an unverified id. If a query returns no id or URL, stop rather than invent one to proceed.
- **A write is never a probe, and a write's output is never suppressed.** Never fire a state-changing call to see whether it works: decide it should happen, make it happen, and read the result. Never append output-discarding redirection or a force-success tail to a mutation (for example `>/dev/null`, `2>/dev/null`, `&>/dev/null`, `|| true`, `|| :`, `|| echo`), because the write's output is exactly what must be read. A write that appears to fail is **verified, not assumed harmless**, because the operation may have succeeded on the server while the client reported an error, so confirm the actual state before retrying or moving on. The ban targets hiding a *failure*. An ad-hoc call's response is the only signal you get, so `>/dev/null 2>&1`, `|| true`, and `|| echo`, which swallow the error stream or force success, are never acceptable on one. A committed script under `set -e` is a narrow exception: it may send a write's *stdout* to `/dev/null` to drop the success-response noise, because stderr stays visible and a failed write still aborts loudly (the hub's own `repo-config/configure.sh` does exactly this, and a repository reaches it there rather than carrying a copy). The exception is stdout-only suppression inside a reviewed, fail-loud script, never `2>&1` or a force-success tail, and never an ad-hoc command.
- **A refused write is reported, never re-shaped, and the maintainer's say-so does not lift a refusal by the harness.** These are two different permissions and only one of them is the maintainer's to give. When the agent harness refuses a write, the maintainer authorizing it in conversation does not change the outcome, and the identical call is refused again, so a second attempt is not worth making and reading the second refusal as a flake is how an agent starts hunting for another shape of the same request. **That hunt is the failure this rule exists to stop.** Re-expressing a refused `gh` command as a raw `gh api -X POST` reaches the same endpoint with the same identity and the same blast radius, having defeated the one control that stopped it, and it is the more dangerous version because the agent believes it has permission. So a refused write is never re-attempted through a different API surface, a different tool, or a rephrasing, and it is never routed around by the agent writing itself a permission rule, which is self-authorization whatever the maintainer said. Two routes remain, both of them the maintainer's: they add the permission rule themselves, or they run the command themselves. Raise it as a blocked decision naming those two (see "Communicating with the User"), and where the work needs the result rather than the call, say what the agent will verify once the maintainer has run it. **A refusal is also a fact about the contract, not just about the session**: where a required verification can only be performed by a write the agent is refused, the document requiring it says so and names who runs it, since a check that is mandatory and unperformable is quietly dropped and then reported as done.
- **Each task runs in its own checkout, in its own directory, on its own feature branch.** The unit is the task rather than the agent, since one agent moving between two repositories meets the same hazard as two agents sharing one tree, and a rule written per agent permits exactly the case that goes wrong. The commands that cross the boundary are the ordinary ones rather than the reckless ones, and each is correct in isolation: a blanket `git add -A` sweeps another task's uncommitted work into the commit, a `git reset --hard` deletes it, and a branch switch carries it into an unrelated change. The mechanical habit that holds the rule up is that a mutating command takes an absolute path, or a `cd` to one in the same invocation, rather than the working directory it inherited, because a read in the wrong directory is a wasted call and a write there is damage.
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Route GitHub Writes Through Portable Tooling by ptr727 · Pull Request #808 · ptr727/ProjectTemplate · GitHub
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
8 changes: 5 additions & 3 deletions .agents/skills/pr-review-conduct/SKILL.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -124,7 +124,8 @@ reviewer's own words to identify it), give one bold verdict per finding (`Fixed
`Disproven`, or `No change needed`), state the `(N)` count the block gave so answers can be
checked against findings, and link the review round. **Read every round, not only the head.** A
suppressed finding does not retire when a later push supersedes it, it just stops showing up in a
head-scoped query while still unanswered.
head-scoped query while still unanswered. Post the answer with `scripts/pr_review.py comment`
from a hub checkout. Do not use a provider connector or reconstruct the GitHub mutation.

## Escalate to the maintainer when

Expand All@@ -138,7 +139,8 @@ head-scoped query while still unanswered.

This skill is the provider-agnostic contract. Use `scripts/pr_review.py` from a hub checkout for
the GitHub-specific API operations. `status` reports coverage, threads, body-only findings, and
shapes in one call. `wait` requests and polls in-process. `reply` resolves a thread by matching
the finding's own words instead of a line number a fix push can move. The repository's
shapes in one call. `wait` requests and polls in-process. `comment` posts a PR-conversation
answer after it reads the PR node ID. `reply` resolves a thread by matching the finding's own
words instead of a line number a fix push can move. The repository's
`.github/copilot-instructions.md` bootstraps Copilot into the `code-review` skill and its stable
coverage marker. Do not reconstruct the API operations by hand.
2 changes: 1 addition & 1 deletion .claude-plugin/fleet-skills/.source-digest
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
ac73ed965d3dfe48
a4b2a2c434e2ab63
Original file line numberDiff line numberDiff line change
Expand Up@@ -124,7 +124,8 @@ reviewer's own words to identify it), give one bold verdict per finding (`Fixed
`Disproven`, or `No change needed`), state the `(N)` count the block gave so answers can be
checked against findings, and link the review round. **Read every round, not only the head.** A
suppressed finding does not retire when a later push supersedes it, it just stops showing up in a
head-scoped query while still unanswered.
head-scoped query while still unanswered. Post the answer with `scripts/pr_review.py comment`
from a hub checkout. Do not use a provider connector or reconstruct the GitHub mutation.

## Escalate to the maintainer when

Expand All@@ -138,7 +139,8 @@ head-scoped query while still unanswered.

This skill is the provider-agnostic contract. Use `scripts/pr_review.py` from a hub checkout for
the GitHub-specific API operations. `status` reports coverage, threads, body-only findings, and
shapes in one call. `wait` requests and polls in-process. `reply` resolves a thread by matching
the finding's own words instead of a line number a fix push can move. The repository's
shapes in one call. `wait` requests and polls in-process. `comment` posts a PR-conversation
answer after it reads the PR node ID. `reply` resolves a thread by matching the finding's own
words instead of a line number a fix push can move. The repository's
`.github/copilot-instructions.md` bootstraps Copilot into the `code-review` skill and its stable
coverage marker. Do not reconstruct the API operations by hand.
7 changes: 4 additions & 3 deletions .github/copilot-instructions.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -38,9 +38,10 @@ For every review:
5. End the review body with the exact machine-readable marker required by the `code-review` skill.

The review automation is `scripts/pr_review.py`, run from a hub checkout. Use its `status`, `wait`,
and `reply --resolve` commands instead of reconstructing GraphQL queries or copying review
identifiers by hand. Its status gate verifies the current head, diff coverage, output shape,
inline threads, body-only findings, and required checks.
`comment`, and `reply --resolve` commands instead of reconstructing GraphQL queries or copying
review identifiers by hand. Use `comment` for a suppressed-finding answer in the pull request
conversation. Its status gate verifies the current head, diff coverage, output shape, inline
threads, body-only findings, and required checks.

A formal review with no findings is complete only when it covers the current head and states full
diff coverage. A refusal, partial or absent coverage statement, unrecognized output shape,
Expand Down
8 changes: 5 additions & 3 deletions .github/skills/pr-review-conduct/SKILL.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -124,7 +124,8 @@ reviewer's own words to identify it), give one bold verdict per finding (`Fixed
`Disproven`, or `No change needed`), state the `(N)` count the block gave so answers can be
checked against findings, and link the review round. **Read every round, not only the head.** A
suppressed finding does not retire when a later push supersedes it, it just stops showing up in a
head-scoped query while still unanswered.
head-scoped query while still unanswered. Post the answer with `scripts/pr_review.py comment`
from a hub checkout. Do not use a provider connector or reconstruct the GitHub mutation.

## Escalate to the maintainer when

Expand All@@ -138,7 +139,8 @@ head-scoped query while still unanswered.

This skill is the provider-agnostic contract. Use `scripts/pr_review.py` from a hub checkout for
the GitHub-specific API operations. `status` reports coverage, threads, body-only findings, and
shapes in one call. `wait` requests and polls in-process. `reply` resolves a thread by matching
the finding's own words instead of a line number a fix push can move. The repository's
shapes in one call. `wait` requests and polls in-process. `comment` posts a PR-conversation
answer after it reads the PR node ID. `reply` resolves a thread by matching the finding's own
words instead of a line number a fix push can move. The repository's
`.github/copilot-instructions.md` bootstraps Copilot into the `code-review` skill and its stable
coverage marker. Do not reconstruct the API operations by hand.
3 changes: 2 additions & 1 deletion GOVERNANCE.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -25,7 +25,8 @@ This section keeps the full rules and is surfaced at its decision moment by the
A state-changing GitHub call is the highest-blast-radius thing an agent does here: it runs under the maintainer's identity, so one wrong target writes to another owner's repository as the maintainer, an outward-facing and hard-to-reverse act. These rules bound every write (a git push, an API mutation, a comment, a label, a merge) on any platform, and they bound a write to a checkout on disk as well, since a blanket add or a hard reset in a working tree another task is using destroys work without ever reaching GitHub. Reads are unrestricted, and how far a local read can be trusted is governed under "Verification Discipline" rather than here. The bounds below are on writes.

- **Write only within the owner of the current project's repository.** Every state-changing call targets this project's `origin` or another repository under the same owner, which is the fleet the maintainer already administers. A broad or logged-in identity is capability, not permission: a token that *can* reach another owner's repository does not authorize writing to it. Writing under a **different owner** needs explicit human permission naming that repository, granted deliberately rather than assumed from a token's reach, and a "harmless test" write is still a write, so there is no probe exception. That boundary is where the harm sits, since the incident this rule exists for was a stray comment on a stranger's repository, not work across the maintainer's own projects. Reads from anywhere are fine.
- **Never fabricate, guess, or reuse an identifier passed to a write.** Every id a state-changing call consumes (a node id, a numeric id, a thread or comment id) is captured from a live query in the **same** session into a variable and passed from there. Do not hand-type an id, guess it, recall it from memory or an earlier session, or copy it from documentation or an example. Ids commonly resolve **globally**, so a wrong-but-valid id does not fail. It writes to the wrong target, in someone else's repository. If a query returns no id, stop rather than invent one to proceed.
- **Provider connectors are read-only for fleet work.** Use a provider's GitHub connector for reads where it helps. Perform each GitHub mutation through the documented hub tool, or through authenticated `gh` where no tool owns the operation. This gives Codex, Claude, opencode, and a terminal session one write path with the same checks. It also avoids a connector mutation that predictably lacks repository authorization while the verified `gh` session already has it. A provider-specific instruction may explain how to reach the common path. It never replaces that path with its own mutation surface.
- **Never fabricate, guess, or reuse an identifier passed to a write.** Capture every identifier a state-changing call consumes from a live query in the **same** session. This includes node, numeric, thread, and comment ids. Pass the captured value directly. Do not hand-type an id, recall it from another session, or copy it from documentation or an example. Ids commonly resolve **globally**, so a wrong-but-valid id does not fail. It writes to the wrong target, in someone else's repository. Apply the same rule to an identifier embedded in outward-facing text. Read the complete URL from the live object. Never construct a plausible link from an unverified id. If a query returns no id or URL, stop rather than invent one to proceed.
- **A write is never a probe, and a write's output is never suppressed.** Never fire a state-changing call to see whether it works: decide it should happen, make it happen, and read the result. Never append output-discarding redirection or a force-success tail to a mutation (for example `>/dev/null`, `2>/dev/null`, `&>/dev/null`, `|| true`, `|| :`, `|| echo`), because the write's output is exactly what must be read. A write that appears to fail is **verified, not assumed harmless**, because the operation may have succeeded on the server while the client reported an error, so confirm the actual state before retrying or moving on. The ban targets hiding a *failure*. An ad-hoc call's response is the only signal you get, so `>/dev/null 2>&1`, `|| true`, and `|| echo`, which swallow the error stream or force success, are never acceptable on one. A committed script under `set -e` is a narrow exception: it may send a write's *stdout* to `/dev/null` to drop the success-response noise, because stderr stays visible and a failed write still aborts loudly (the hub's own `repo-config/configure.sh` does exactly this, and a repository reaches it there rather than carrying a copy). The exception is stdout-only suppression inside a reviewed, fail-loud script, never `2>&1` or a force-success tail, and never an ad-hoc command.
- **A refused write is reported, never re-shaped, and the maintainer's say-so does not lift a refusal by the harness.** These are two different permissions and only one of them is the maintainer's to give. When the agent harness refuses a write, the maintainer authorizing it in conversation does not change the outcome, and the identical call is refused again, so a second attempt is not worth making and reading the second refusal as a flake is how an agent starts hunting for another shape of the same request. **That hunt is the failure this rule exists to stop.** Re-expressing a refused `gh` command as a raw `gh api -X POST` reaches the same endpoint with the same identity and the same blast radius, having defeated the one control that stopped it, and it is the more dangerous version because the agent believes it has permission. So a refused write is never re-attempted through a different API surface, a different tool, or a rephrasing, and it is never routed around by the agent writing itself a permission rule, which is self-authorization whatever the maintainer said. Two routes remain, both of them the maintainer's: they add the permission rule themselves, or they run the command themselves. Raise it as a blocked decision naming those two (see "Communicating with the User"), and where the work needs the result rather than the call, say what the agent will verify once the maintainer has run it. **A refusal is also a fact about the contract, not just about the session**: where a required verification can only be performed by a write the agent is refused, the document requiring it says so and names who runs it, since a check that is mandatory and unperformable is quietly dropped and then reported as done.
- **Each task runs in its own checkout, in its own directory, on its own feature branch.** The unit is the task rather than the agent, since one agent moving between two repositories meets the same hazard as two agents sharing one tree, and a rule written per agent permits exactly the case that goes wrong. The commands that cross the boundary are the ordinary ones rather than the reckless ones, and each is correct in isolation: a blanket `git add -A` sweeps another task's uncommitted work into the commit, a `git reset --hard` deletes it, and a branch switch carries it into an unrelated change. The mechanical habit that holds the rule up is that a mutating command takes an absolute path, or a `cd` to one in the same invocation, rather than the working directory it inherited, because a read in the wrong directory is a wasted call and a write there is damage.
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Route GitHub Writes Through Portable Tooling by ptr727 · Pull Request #808 · ptr727/ProjectTemplate · GitHub
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
8 changes: 5 additions & 3 deletions .agents/skills/pr-review-conduct/SKILL.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -124,7 +124,8 @@ reviewer's own words to identify it), give one bold verdict per finding (`Fixed
`Disproven`, or `No change needed`), state the `(N)` count the block gave so answers can be
checked against findings, and link the review round. **Read every round, not only the head.** A
suppressed finding does not retire when a later push supersedes it, it just stops showing up in a
head-scoped query while still unanswered.
head-scoped query while still unanswered. Post the answer with `scripts/pr_review.py comment`
from a hub checkout. Do not use a provider connector or reconstruct the GitHub mutation.

## Escalate to the maintainer when

Expand All@@ -138,7 +139,8 @@ head-scoped query while still unanswered.

This skill is the provider-agnostic contract. Use `scripts/pr_review.py` from a hub checkout for
the GitHub-specific API operations. `status` reports coverage, threads, body-only findings, and
shapes in one call. `wait` requests and polls in-process. `reply` resolves a thread by matching
the finding's own words instead of a line number a fix push can move. The repository's
shapes in one call. `wait` requests and polls in-process. `comment` posts a PR-conversation
answer after it reads the PR node ID. `reply` resolves a thread by matching the finding's own
words instead of a line number a fix push can move. The repository's
`.github/copilot-instructions.md` bootstraps Copilot into the `code-review` skill and its stable
coverage marker. Do not reconstruct the API operations by hand.
2 changes: 1 addition & 1 deletion .claude-plugin/fleet-skills/.source-digest
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
ac73ed965d3dfe48
a4b2a2c434e2ab63
Original file line numberDiff line numberDiff line change
Expand Up@@ -124,7 +124,8 @@ reviewer's own words to identify it), give one bold verdict per finding (`Fixed
`Disproven`, or `No change needed`), state the `(N)` count the block gave so answers can be
checked against findings, and link the review round. **Read every round, not only the head.** A
suppressed finding does not retire when a later push supersedes it, it just stops showing up in a
head-scoped query while still unanswered.
head-scoped query while still unanswered. Post the answer with `scripts/pr_review.py comment`
from a hub checkout. Do not use a provider connector or reconstruct the GitHub mutation.

## Escalate to the maintainer when

Expand All@@ -138,7 +139,8 @@ head-scoped query while still unanswered.

This skill is the provider-agnostic contract. Use `scripts/pr_review.py` from a hub checkout for
the GitHub-specific API operations. `status` reports coverage, threads, body-only findings, and
shapes in one call. `wait` requests and polls in-process. `reply` resolves a thread by matching
the finding's own words instead of a line number a fix push can move. The repository's
shapes in one call. `wait` requests and polls in-process. `comment` posts a PR-conversation
answer after it reads the PR node ID. `reply` resolves a thread by matching the finding's own
words instead of a line number a fix push can move. The repository's
`.github/copilot-instructions.md` bootstraps Copilot into the `code-review` skill and its stable
coverage marker. Do not reconstruct the API operations by hand.
7 changes: 4 additions & 3 deletions .github/copilot-instructions.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -38,9 +38,10 @@ For every review:
5. End the review body with the exact machine-readable marker required by the `code-review` skill.

The review automation is `scripts/pr_review.py`, run from a hub checkout. Use its `status`, `wait`,
and `reply --resolve` commands instead of reconstructing GraphQL queries or copying review
identifiers by hand. Its status gate verifies the current head, diff coverage, output shape,
inline threads, body-only findings, and required checks.
`comment`, and `reply --resolve` commands instead of reconstructing GraphQL queries or copying
review identifiers by hand. Use `comment` for a suppressed-finding answer in the pull request
conversation. Its status gate verifies the current head, diff coverage, output shape, inline
threads, body-only findings, and required checks.

A formal review with no findings is complete only when it covers the current head and states full
diff coverage. A refusal, partial or absent coverage statement, unrecognized output shape,
Expand Down
8 changes: 5 additions & 3 deletions .github/skills/pr-review-conduct/SKILL.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -124,7 +124,8 @@ reviewer's own words to identify it), give one bold verdict per finding (`Fixed
`Disproven`, or `No change needed`), state the `(N)` count the block gave so answers can be
checked against findings, and link the review round. **Read every round, not only the head.** A
suppressed finding does not retire when a later push supersedes it, it just stops showing up in a
head-scoped query while still unanswered.
head-scoped query while still unanswered. Post the answer with `scripts/pr_review.py comment`
from a hub checkout. Do not use a provider connector or reconstruct the GitHub mutation.

## Escalate to the maintainer when

Expand All@@ -138,7 +139,8 @@ head-scoped query while still unanswered.

This skill is the provider-agnostic contract. Use `scripts/pr_review.py` from a hub checkout for
the GitHub-specific API operations. `status` reports coverage, threads, body-only findings, and
shapes in one call. `wait` requests and polls in-process. `reply` resolves a thread by matching
the finding's own words instead of a line number a fix push can move. The repository's
shapes in one call. `wait` requests and polls in-process. `comment` posts a PR-conversation
answer after it reads the PR node ID. `reply` resolves a thread by matching the finding's own
words instead of a line number a fix push can move. The repository's
`.github/copilot-instructions.md` bootstraps Copilot into the `code-review` skill and its stable
coverage marker. Do not reconstruct the API operations by hand.
3 changes: 2 additions & 1 deletion GOVERNANCE.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -25,7 +25,8 @@ This section keeps the full rules and is surfaced at its decision moment by the
A state-changing GitHub call is the highest-blast-radius thing an agent does here: it runs under the maintainer's identity, so one wrong target writes to another owner's repository as the maintainer, an outward-facing and hard-to-reverse act. These rules bound every write (a git push, an API mutation, a comment, a label, a merge) on any platform, and they bound a write to a checkout on disk as well, since a blanket add or a hard reset in a working tree another task is using destroys work without ever reaching GitHub. Reads are unrestricted, and how far a local read can be trusted is governed under "Verification Discipline" rather than here. The bounds below are on writes.

- **Write only within the owner of the current project's repository.** Every state-changing call targets this project's `origin` or another repository under the same owner, which is the fleet the maintainer already administers. A broad or logged-in identity is capability, not permission: a token that *can* reach another owner's repository does not authorize writing to it. Writing under a **different owner** needs explicit human permission naming that repository, granted deliberately rather than assumed from a token's reach, and a "harmless test" write is still a write, so there is no probe exception. That boundary is where the harm sits, since the incident this rule exists for was a stray comment on a stranger's repository, not work across the maintainer's own projects. Reads from anywhere are fine.
- **Never fabricate, guess, or reuse an identifier passed to a write.** Every id a state-changing call consumes (a node id, a numeric id, a thread or comment id) is captured from a live query in the **same** session into a variable and passed from there. Do not hand-type an id, guess it, recall it from memory or an earlier session, or copy it from documentation or an example. Ids commonly resolve **globally**, so a wrong-but-valid id does not fail. It writes to the wrong target, in someone else's repository. If a query returns no id, stop rather than invent one to proceed.
- **Provider connectors are read-only for fleet work.** Use a provider's GitHub connector for reads where it helps. Perform each GitHub mutation through the documented hub tool, or through authenticated `gh` where no tool owns the operation. This gives Codex, Claude, opencode, and a terminal session one write path with the same checks. It also avoids a connector mutation that predictably lacks repository authorization while the verified `gh` session already has it. A provider-specific instruction may explain how to reach the common path. It never replaces that path with its own mutation surface.
- **Never fabricate, guess, or reuse an identifier passed to a write.** Capture every identifier a state-changing call consumes from a live query in the **same** session. This includes node, numeric, thread, and comment ids. Pass the captured value directly. Do not hand-type an id, recall it from another session, or copy it from documentation or an example. Ids commonly resolve **globally**, so a wrong-but-valid id does not fail. It writes to the wrong target, in someone else's repository. Apply the same rule to an identifier embedded in outward-facing text. Read the complete URL from the live object. Never construct a plausible link from an unverified id. If a query returns no id or URL, stop rather than invent one to proceed.
- **A write is never a probe, and a write's output is never suppressed.** Never fire a state-changing call to see whether it works: decide it should happen, make it happen, and read the result. Never append output-discarding redirection or a force-success tail to a mutation (for example `>/dev/null`, `2>/dev/null`, `&>/dev/null`, `|| true`, `|| :`, `|| echo`), because the write's output is exactly what must be read. A write that appears to fail is **verified, not assumed harmless**, because the operation may have succeeded on the server while the client reported an error, so confirm the actual state before retrying or moving on. The ban targets hiding a *failure*. An ad-hoc call's response is the only signal you get, so `>/dev/null 2>&1`, `|| true`, and `|| echo`, which swallow the error stream or force success, are never acceptable on one. A committed script under `set -e` is a narrow exception: it may send a write's *stdout* to `/dev/null` to drop the success-response noise, because stderr stays visible and a failed write still aborts loudly (the hub's own `repo-config/configure.sh` does exactly this, and a repository reaches it there rather than carrying a copy). The exception is stdout-only suppression inside a reviewed, fail-loud script, never `2>&1` or a force-success tail, and never an ad-hoc command.
- **A refused write is reported, never re-shaped, and the maintainer's say-so does not lift a refusal by the harness.** These are two different permissions and only one of them is the maintainer's to give. When the agent harness refuses a write, the maintainer authorizing it in conversation does not change the outcome, and the identical call is refused again, so a second attempt is not worth making and reading the second refusal as a flake is how an agent starts hunting for another shape of the same request. **That hunt is the failure this rule exists to stop.** Re-expressing a refused `gh` command as a raw `gh api -X POST` reaches the same endpoint with the same identity and the same blast radius, having defeated the one control that stopped it, and it is the more dangerous version because the agent believes it has permission. So a refused write is never re-attempted through a different API surface, a different tool, or a rephrasing, and it is never routed around by the agent writing itself a permission rule, which is self-authorization whatever the maintainer said. Two routes remain, both of them the maintainer's: they add the permission rule themselves, or they run the command themselves. Raise it as a blocked decision naming those two (see "Communicating with the User"), and where the work needs the result rather than the call, say what the agent will verify once the maintainer has run it. **A refusal is also a fact about the contract, not just about the session**: where a required verification can only be performed by a write the agent is refused, the document requiring it says so and names who runs it, since a check that is mandatory and unperformable is quietly dropped and then reported as done.
- **Each task runs in its own checkout, in its own directory, on its own feature branch.** The unit is the task rather than the agent, since one agent moving between two repositories meets the same hazard as two agents sharing one tree, and a rule written per agent permits exactly the case that goes wrong. The commands that cross the boundary are the ordinary ones rather than the reckless ones, and each is correct in isolation: a blanket `git add -A` sweeps another task's uncommitted work into the commit, a `git reset --hard` deletes it, and a branch switch carries it into an unrelated change. The mechanical habit that holds the rule up is that a mutating command takes an absolute path, or a `cd` to one in the same invocation, rather than the working directory it inherited, because a read in the wrong directory is a wasted call and a write there is damage.
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); Route GitHub Writes Through Portable Tooling by ptr727 · Pull Request #808 · ptr727/ProjectTemplate · GitHub
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
8 changes: 5 additions & 3 deletions .agents/skills/pr-review-conduct/SKILL.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -124,7 +124,8 @@ reviewer's own words to identify it), give one bold verdict per finding (`Fixed
`Disproven`, or `No change needed`), state the `(N)` count the block gave so answers can be
checked against findings, and link the review round. **Read every round, not only the head.** A
suppressed finding does not retire when a later push supersedes it, it just stops showing up in a
head-scoped query while still unanswered.
head-scoped query while still unanswered. Post the answer with `scripts/pr_review.py comment`
from a hub checkout. Do not use a provider connector or reconstruct the GitHub mutation.

## Escalate to the maintainer when

Expand All@@ -138,7 +139,8 @@ head-scoped query while still unanswered.

This skill is the provider-agnostic contract. Use `scripts/pr_review.py` from a hub checkout for
the GitHub-specific API operations. `status` reports coverage, threads, body-only findings, and
shapes in one call. `wait` requests and polls in-process. `reply` resolves a thread by matching
the finding's own words instead of a line number a fix push can move. The repository's
shapes in one call. `wait` requests and polls in-process. `comment` posts a PR-conversation
answer after it reads the PR node ID. `reply` resolves a thread by matching the finding's own
words instead of a line number a fix push can move. The repository's
`.github/copilot-instructions.md` bootstraps Copilot into the `code-review` skill and its stable
coverage marker. Do not reconstruct the API operations by hand.
2 changes: 1 addition & 1 deletion .claude-plugin/fleet-skills/.source-digest
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
ac73ed965d3dfe48
a4b2a2c434e2ab63
Original file line numberDiff line numberDiff line change
Expand Up@@ -124,7 +124,8 @@ reviewer's own words to identify it), give one bold verdict per finding (`Fixed
`Disproven`, or `No change needed`), state the `(N)` count the block gave so answers can be
checked against findings, and link the review round. **Read every round, not only the head.** A
suppressed finding does not retire when a later push supersedes it, it just stops showing up in a
head-scoped query while still unanswered.
head-scoped query while still unanswered. Post the answer with `scripts/pr_review.py comment`
from a hub checkout. Do not use a provider connector or reconstruct the GitHub mutation.

## Escalate to the maintainer when

Expand All@@ -138,7 +139,8 @@ head-scoped query while still unanswered.

This skill is the provider-agnostic contract. Use `scripts/pr_review.py` from a hub checkout for
the GitHub-specific API operations. `status` reports coverage, threads, body-only findings, and
shapes in one call. `wait` requests and polls in-process. `reply` resolves a thread by matching
the finding's own words instead of a line number a fix push can move. The repository's
shapes in one call. `wait` requests and polls in-process. `comment` posts a PR-conversation
answer after it reads the PR node ID. `reply` resolves a thread by matching the finding's own
words instead of a line number a fix push can move. The repository's
`.github/copilot-instructions.md` bootstraps Copilot into the `code-review` skill and its stable
coverage marker. Do not reconstruct the API operations by hand.
7 changes: 4 additions & 3 deletions .github/copilot-instructions.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -38,9 +38,10 @@ For every review:
5. End the review body with the exact machine-readable marker required by the `code-review` skill.

The review automation is `scripts/pr_review.py`, run from a hub checkout. Use its `status`, `wait`,
and `reply --resolve` commands instead of reconstructing GraphQL queries or copying review
identifiers by hand. Its status gate verifies the current head, diff coverage, output shape,
inline threads, body-only findings, and required checks.
`comment`, and `reply --resolve` commands instead of reconstructing GraphQL queries or copying
review identifiers by hand. Use `comment` for a suppressed-finding answer in the pull request
conversation. Its status gate verifies the current head, diff coverage, output shape, inline
threads, body-only findings, and required checks.

A formal review with no findings is complete only when it covers the current head and states full
diff coverage. A refusal, partial or absent coverage statement, unrecognized output shape,
Expand Down
8 changes: 5 additions & 3 deletions .github/skills/pr-review-conduct/SKILL.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -124,7 +124,8 @@ reviewer's own words to identify it), give one bold verdict per finding (`Fixed
`Disproven`, or `No change needed`), state the `(N)` count the block gave so answers can be
checked against findings, and link the review round. **Read every round, not only the head.** A
suppressed finding does not retire when a later push supersedes it, it just stops showing up in a
head-scoped query while still unanswered.
head-scoped query while still unanswered. Post the answer with `scripts/pr_review.py comment`
from a hub checkout. Do not use a provider connector or reconstruct the GitHub mutation.

## Escalate to the maintainer when

Expand All@@ -138,7 +139,8 @@ head-scoped query while still unanswered.

This skill is the provider-agnostic contract. Use `scripts/pr_review.py` from a hub checkout for
the GitHub-specific API operations. `status` reports coverage, threads, body-only findings, and
shapes in one call. `wait` requests and polls in-process. `reply` resolves a thread by matching
the finding's own words instead of a line number a fix push can move. The repository's
shapes in one call. `wait` requests and polls in-process. `comment` posts a PR-conversation
answer after it reads the PR node ID. `reply` resolves a thread by matching the finding's own
words instead of a line number a fix push can move. The repository's
`.github/copilot-instructions.md` bootstraps Copilot into the `code-review` skill and its stable
coverage marker. Do not reconstruct the API operations by hand.
3 changes: 2 additions & 1 deletion GOVERNANCE.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -25,7 +25,8 @@ This section keeps the full rules and is surfaced at its decision moment by the
A state-changing GitHub call is the highest-blast-radius thing an agent does here: it runs under the maintainer's identity, so one wrong target writes to another owner's repository as the maintainer, an outward-facing and hard-to-reverse act. These rules bound every write (a git push, an API mutation, a comment, a label, a merge) on any platform, and they bound a write to a checkout on disk as well, since a blanket add or a hard reset in a working tree another task is using destroys work without ever reaching GitHub. Reads are unrestricted, and how far a local read can be trusted is governed under "Verification Discipline" rather than here. The bounds below are on writes.

- **Write only within the owner of the current project's repository.** Every state-changing call targets this project's `origin` or another repository under the same owner, which is the fleet the maintainer already administers. A broad or logged-in identity is capability, not permission: a token that *can* reach another owner's repository does not authorize writing to it. Writing under a **different owner** needs explicit human permission naming that repository, granted deliberately rather than assumed from a token's reach, and a "harmless test" write is still a write, so there is no probe exception. That boundary is where the harm sits, since the incident this rule exists for was a stray comment on a stranger's repository, not work across the maintainer's own projects. Reads from anywhere are fine.
- **Never fabricate, guess, or reuse an identifier passed to a write.** Every id a state-changing call consumes (a node id, a numeric id, a thread or comment id) is captured from a live query in the **same** session into a variable and passed from there. Do not hand-type an id, guess it, recall it from memory or an earlier session, or copy it from documentation or an example. Ids commonly resolve **globally**, so a wrong-but-valid id does not fail. It writes to the wrong target, in someone else's repository. If a query returns no id, stop rather than invent one to proceed.
- **Provider connectors are read-only for fleet work.** Use a provider's GitHub connector for reads where it helps. Perform each GitHub mutation through the documented hub tool, or through authenticated `gh` where no tool owns the operation. This gives Codex, Claude, opencode, and a terminal session one write path with the same checks. It also avoids a connector mutation that predictably lacks repository authorization while the verified `gh` session already has it. A provider-specific instruction may explain how to reach the common path. It never replaces that path with its own mutation surface.
- **Never fabricate, guess, or reuse an identifier passed to a write.** Capture every identifier a state-changing call consumes from a live query in the **same** session. This includes node, numeric, thread, and comment ids. Pass the captured value directly. Do not hand-type an id, recall it from another session, or copy it from documentation or an example. Ids commonly resolve **globally**, so a wrong-but-valid id does not fail. It writes to the wrong target, in someone else's repository. Apply the same rule to an identifier embedded in outward-facing text. Read the complete URL from the live object. Never construct a plausible link from an unverified id. If a query returns no id or URL, stop rather than invent one to proceed.
- **A write is never a probe, and a write's output is never suppressed.** Never fire a state-changing call to see whether it works: decide it should happen, make it happen, and read the result. Never append output-discarding redirection or a force-success tail to a mutation (for example `>/dev/null`, `2>/dev/null`, `&>/dev/null`, `|| true`, `|| :`, `|| echo`), because the write's output is exactly what must be read. A write that appears to fail is **verified, not assumed harmless**, because the operation may have succeeded on the server while the client reported an error, so confirm the actual state before retrying or moving on. The ban targets hiding a *failure*. An ad-hoc call's response is the only signal you get, so `>/dev/null 2>&1`, `|| true`, and `|| echo`, which swallow the error stream or force success, are never acceptable on one. A committed script under `set -e` is a narrow exception: it may send a write's *stdout* to `/dev/null` to drop the success-response noise, because stderr stays visible and a failed write still aborts loudly (the hub's own `repo-config/configure.sh` does exactly this, and a repository reaches it there rather than carrying a copy). The exception is stdout-only suppression inside a reviewed, fail-loud script, never `2>&1` or a force-success tail, and never an ad-hoc command.
- **A refused write is reported, never re-shaped, and the maintainer's say-so does not lift a refusal by the harness.** These are two different permissions and only one of them is the maintainer's to give. When the agent harness refuses a write, the maintainer authorizing it in conversation does not change the outcome, and the identical call is refused again, so a second attempt is not worth making and reading the second refusal as a flake is how an agent starts hunting for another shape of the same request. **That hunt is the failure this rule exists to stop.** Re-expressing a refused `gh` command as a raw `gh api -X POST` reaches the same endpoint with the same identity and the same blast radius, having defeated the one control that stopped it, and it is the more dangerous version because the agent believes it has permission. So a refused write is never re-attempted through a different API surface, a different tool, or a rephrasing, and it is never routed around by the agent writing itself a permission rule, which is self-authorization whatever the maintainer said. Two routes remain, both of them the maintainer's: they add the permission rule themselves, or they run the command themselves. Raise it as a blocked decision naming those two (see "Communicating with the User"), and where the work needs the result rather than the call, say what the agent will verify once the maintainer has run it. **A refusal is also a fact about the contract, not just about the session**: where a required verification can only be performed by a write the agent is refused, the document requiring it says so and names who runs it, since a check that is mandatory and unperformable is quietly dropped and then reported as done.
- **Each task runs in its own checkout, in its own directory, on its own feature branch.** The unit is the task rather than the agent, since one agent moving between two repositories meets the same hazard as two agents sharing one tree, and a rule written per agent permits exactly the case that goes wrong. The commands that cross the boundary are the ordinary ones rather than the reckless ones, and each is correct in isolation: a blanket `git add -A` sweeps another task's uncommitted work into the commit, a `git reset --hard` deletes it, and a branch switch carries it into an unrelated change. The mechanical habit that holds the rule up is that a mutating command takes an absolute path, or a `cd` to one in the same invocation, rather than the working directory it inherited, because a read in the wrong directory is a wasted call and a write there is damage.
Expand Down
Loading