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
3 changes: 2 additions & 1 deletion .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<!-- Use the actual accepted scope, not just an agent-authored plan.
For human-owned goal Issues awaiting acceptance, use plain `Refs #N`, not
Closes/Fixes/Resolves or other automatic closing relationships.
Closes/Fixes/Resolves or other automatic closing relationships. A negated
sentence still closes the Issue on merge; GitHub ignores the negation.
Conventions: docs/agent-collaboration.md -->

## Governing Issue and delivery slice
Expand Down
15 changes: 15 additions & 0 deletions .github/workflows/ci-ready.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,21 @@ jobs:
- name: Tests (smoke + unit)
run: pytest

pr-references:
# Reports closing syntax in PR body text at each ready-lane event; not a branch-protection gate.
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
contents: read
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Reject closing syntax inside PR body text
env:
PR_BODY: ${{ github.event.pull_request.body }}
run: python3 tools/check_pr_references.py

kv-task-qualification:
# Bounded software-control qualification; not a branch-protection or efficacy gate.
if: github.event.pull_request.draft == false
Expand Down
2 changes: 1 addition & 1 deletion docs/agent-collaboration.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ sequenceDiagram
3. **Evidence:** link checks to actual checked content and relevant environment/test definitions. Distinguish passed, failed, cancelled, skipped, timed-out and not-run. H/B/T—reviewed head, assessed base, tested content—may come from existing records, not mandatory worksheets.
4. **Refresh:** reassess affected evidence after relevant head/base/content/environment/check changes. Refresh checks or explain continued applicability. Neither blanket reuse nor full hardware reruns is the default; changed hashes alone prove neither content change nor equivalence.
5. **Handoff:** only for an actual transfer or accepted decision change, record the delta, recipient and next action. Keep acceptance status/link, communication status and application status separate. "Applied" needs artifact/evidence; otherwise retain pending or unknown. No full conversation traces or new ledger.
6. **Completion:** merged PR ≠ accepted goal ≠ campaign `PASS` ≠ release. Use non-closing `Refs #N` until closure is authorized. Subtasks cannot implicitly close a parent goal. Knowledge proposals to [#17](https://github.com/InferMatrixLab/InferRSI/issues/17) are optional; no automatic promotion. Before an authorized closure, publish a closure summary card ([closure summaries](#closure-summaries)).
6. **Completion:** merged PR ≠ accepted goal ≠ campaign `PASS` ≠ release. Use non-closing `Refs #N` until closure is authorized. GitHub treats a closing keyword followed by an Issue reference as closing syntax even inside a negated sentence, so never write one in PR text; for an authorized closure, put `Closes #N` alone on its own line. The ready lane's `pr-references` job reports any other use. Subtasks cannot implicitly close a parent goal. Knowledge proposals to [#17](https://github.com/InferMatrixLab/InferRSI/issues/17) are optional; no automatic promotion. Before an authorized closure, publish a closure summary card ([closure summaries](#closure-summaries)).

## Closure summaries

Expand Down
5 changes: 5 additions & 0 deletions docs/contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,11 @@ helpers and CLI reads without installing any inference engine.
The ready test and installed-package jobs cover the declared minimum Python
3.10 alongside Python 3.11. This matrix does not certify later Python releases
or accelerator runtime compatibility.
The `pr-references` job fails when GitHub closing syntax appears inside PR body
text, including negated sentences (working rule 6 in the
[collaboration guide](agent-collaboration.md#working-rules)). It reads the body at
the triggering event, so a body edit is checked at the next push. It is not a
required check.

The merge lane runs the same small smoke selection as draft on every main push,
including direct pushes. It is a post-push safety check, not a branch-protection
Expand Down
74 changes: 74 additions & 0 deletions tests/test_pr_references.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
from pathlib import Path

import pytest

from tools import check_pr_references as check

ROOT = Path(__file__).resolve().parents[1]

# Negated sentences from merged PR bodies that GitHub still treated as closing syntax.
HISTORICAL = [
"Acceptance stays with the maintainer; this PR does not close #43.",
"Issue acceptance/closure remains pending; this PR does not close #40 automatically.",
"This PR does not automatically close #29 and does not claim hardware validation.",
"**Closure delegation:** @owner (issue author); this PR does not close #49.",
"This PR does not close #50/#140 automatically.",
"**Do not promote the candidate or close #255.**",
"It remains valid evidence; neither this software PR nor green CI closes #253.",
"Therefore this does not establish accuracy improvement or close #255.",
]


@pytest.mark.parametrize("line", HISTORICAL)
def test_negated_closing_sentences_fail(line):
assert check.violations(f"Refs #1\n\n{line}\n") == [(3, line)]


@pytest.mark.parametrize("line", [
"This fixes InferMatrixLab/InferRSI#12 partially.",
"It also resolves https://github.com/InferMatrixLab/InferRSI/issues/12 here.",
"- Closes #12",
"Closes #12 and #13",
"FIXED: #7 in the parser",
])
def test_closing_syntax_inside_other_text_fails(line):
assert check.violations(line) == [(1, line)]


@pytest.mark.parametrize("body", [
"Refs #273",
"Closes #12",
" fixes: #12 ",
"Resolves InferMatrixLab/InferRSI#12",
"#255 stays open for the owner's acceptance.",
"This does not close the campaign.",
"Disclosed #5 during review; an unfixed #6 remains.",
"",
])
def test_references_without_inline_closing_syntax_pass(body):
assert check.violations(body) == []


def test_crlf_body_reports_the_original_line_number():
assert check.violations("Refs #1\r\nthis PR does not close #2\r\n") == [
(2, "this PR does not close #2")
]


def test_pull_request_template_passes():
template = (ROOT / ".github/PULL_REQUEST_TEMPLATE.md").read_text(encoding="utf-8")
assert check.violations(template) == []


def test_main_reports_each_line_and_fails(monkeypatch, capsys):
monkeypatch.setenv("PR_BODY", "Refs #1\nThis PR does not close #2.\nCloses #3\n")
assert check.main() == 1
out = capsys.readouterr().out.splitlines()
assert out[0] == "line 2: closing syntax inside text: This PR does not close #2."
assert "Refs #N" in out[1] and len(out) == 2


def test_main_passes_silently(monkeypatch, capsys):
monkeypatch.setenv("PR_BODY", "Refs #1\nCloses #3\n")
assert check.main() == 0
assert capsys.readouterr().out == ""
41 changes: 41 additions & 0 deletions tools/check_pr_references.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
from __future__ import annotations

import os
import re
import sys

# GitHub links an Issue for closure on merge when one of these keywords precedes the
# reference, including inside a negated sentence such as "this PR does not close #12".
KEYWORD = r"(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)"
REFERENCE = r"(?:[\w.-]+/[\w.-]+)?#\d+|https://github\.com/[\w.-]+/[\w.-]+/issues/\d+"
CLOSING = re.compile(rf"\b{KEYWORD}:?[ \t]+(?:{REFERENCE})\b", re.IGNORECASE)
# A line holding only a keyword and one reference is a deliberate, visible closure.
STANDALONE = re.compile(rf"[ \t]*{KEYWORD}:?[ \t]+(?:{REFERENCE})[ \t]*", re.IGNORECASE)

ADVICE = (
"GitHub closes the referenced Issue on merge even when the sentence negates it. "
"Write `Refs #N` instead. For an authorized closure, put `Closes #N` alone on its "
"own line. See working rule 6 in docs/agent-collaboration.md."
)


def violations(body: str) -> list[tuple[int, str]]:
return [
(number, line.strip())
for number, line in enumerate(body.splitlines(), 1)
if CLOSING.search(line) and not STANDALONE.fullmatch(line)
]


def main() -> int:
found = violations(os.environ["PR_BODY"])
# Each printed line starts with "line" so PR text cannot form a workflow command.
for number, line in found:
print(f"line {number}: closing syntax inside text: {line}")
if found:
print(ADVICE)
return 1 if found else 0


if __name__ == "__main__":
sys.exit(main())
Loading