Merge pull request 'fix: close implements tracker gap and clarify closing keywords (#110)' (#116) from fix/issue-110-implements-tracker-gap into master

This commit was merged in pull request #116.
This commit is contained in:
2026-07-02 18:51:05 -05:00
7 changed files with 32 additions and 11 deletions
+1 -1
View File
@@ -227,7 +227,7 @@ tied to an issue number so the work is traceable end to end:
| Issue | `#123` (claimed with `status:in-progress`) | | Issue | `#123` (claimed with `status:in-progress`) |
| Branch | `(fix\|feat\|docs\|chore)/issue-123-<slug>` (review: `review/pr-456-<slug>`) | | Branch | `(fix\|feat\|docs\|chore)/issue-123-<slug>` (review: `review/pr-456-<slug>`) |
| Worktree | `branches/fix-issue-123-<slug>` (slashes → hyphens) | | Worktree | `branches/fix-issue-123-<slug>` (slashes → hyphens) |
| PR | body says `Closes #123` (closes) or `Refs #123` (related) | | PR | body says `Closes #123` or `Fixes #123` (closes issue); `Implements #123` or `Refs #123` (does NOT close) |
| Cleanup | remove remote+local branch + worktree folder; drop `status:in-progress` | | Cleanup | remove remote+local branch + worktree folder; drop `status:in-progress` |
`scripts/worktree-start` **rejects** implementation branches that are not `scripts/worktree-start` **rejects** implementation branches that are not
+1 -1
View File
@@ -53,7 +53,7 @@ mcp = FastMCP("gitea-tools", instructions=(
def extract_linked_issue_numbers(text: str | None, branch_name: str | None = None) -> list[int]: def extract_linked_issue_numbers(text: str | None, branch_name: str | None = None) -> list[int]:
issues = set() issues = set()
if text: if text:
pattern = re.compile(r'(?i)(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?|ref[s]?)\s+#(\d+)') pattern = re.compile(r'(?i)(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?|implement[s]?|implemented)\s+#(\d+)')
issues.update(int(m) for m in pattern.findall(text)) issues.update(int(m) for m in pattern.findall(text))
if branch_name: if branch_name:
pattern = re.compile(r'(?i)issue-(\d+)') pattern = re.compile(r'(?i)issue-(\d+)')
+2 -2
View File
@@ -90,8 +90,8 @@ is maintained by:
- the branch name (contains the issue number), - the branch name (contains the issue number),
- a claim comment on the issue, e.g. - a claim comment on the issue, e.g.
`Claimed. Branch: fix/issue-123-short-description. Worktree: branches/fix-issue-123-short-description.`, `Claimed. Branch: fix/issue-123-short-description. Worktree: branches/fix-issue-123-short-description.`,
- the PR body — `Closes #123` when the PR should close the issue, `Refs #123` - the PR body — `Closes #123` or `Fixes #123` when the PR should close the issue
when related but not closing, (do NOT use `Implements #123` or `Refs #123` to close, as Gitea will not auto-close),
- cleanup after merge — remove the remote branch, local branch, and the issue - cleanup after merge — remove the remote branch, local branch, and the issue
worktree folder, and drop `status:in-progress`. worktree folder, and drop `status:in-progress`.
@@ -19,7 +19,7 @@ Steps:
- Target task role: merger identity (must NOT be the PR author) - Target task role: merger identity (must NOT be the PR author)
*If the current identity does not match the required role (or is the PR author), STOP. Relaunch/switch to the correct profile first.* *If the current identity does not match the required role (or is the PR author), STOP. Relaunch/switch to the correct profile first.*
2. Verify authenticated identity + active profile. 2. Verify authenticated identity + active profile.
3. Confirm PR #<pr>: author (not you), state open, mergeable, review approved. 3. Confirm PR #<pr>: author (not you), state open, mergeable, review approved. Check if PR body uses `Closes #N` or `Fixes #N`; if it uses `Implements #N` or `Refs #N`, manual closing will be needed in step 29.
4. If any gate fails → STOP and report. 4. If any gate fails → STOP and report.
4. Merge with explicit confirmation (e.g. confirmation="MERGE PR <pr>"), 4. Merge with explicit confirmation (e.g. confirmation="MERGE PR <pr>"),
optionally pinning the reviewed head SHA / changed-file set. optionally pinning the reviewed head SHA / changed-file set.
@@ -24,7 +24,7 @@ Steps:
4. scripts/worktree-review <pr-head-branch> # detached, branches/review-* 4. scripts/worktree-review <pr-head-branch> # detached, branches/review-*
cd branches/review-<pr-head-branch-slug> cd branches/review-<pr-head-branch-slug>
5. Confirm the worktree is clean. Inspect the FULL diff; confirm scope matches 5. Confirm the worktree is clean. Inspect the FULL diff; confirm scope matches
issue #<n>; flag any unrelated files, secrets, or formatting churn. issue #<n>; flag any unrelated files, secrets, or formatting churn. Check that the PR body correctly uses Gitea-closing keywords (`Closes #N` or `Fixes #N`) instead of non-closing ones (`Implements #N`, `Refs #N`).
6. Run the test suite; note results. 6. Run the test suite; note results.
7. Post the review verdict: approve only if scope is clean and checks pass; 7. Post the review verdict: approve only if scope is clean and checks pass;
otherwise request changes with specifics. Never merge from this review step. otherwise request changes with specifics. Never merge from this review step.
@@ -22,12 +22,13 @@ Steps:
3. git fetch <remote> --prune; confirm local master == <remote>/master (0 0). 3. git fetch <remote> --prune; confirm local master == <remote>/master (0 0).
4. Create the issue "<title>" (problem, scope, acceptance) and claim it 4. Create the issue "<title>" (problem, scope, acceptance) and claim it
(status:in-progress + a "starting work" comment naming the branch). (status:in-progress + a "starting work" comment naming the branch).
4. scripts/worktree-start <type>/issue-<n>-<slug> # type = fix|feat|docs 5. scripts/worktree-start <type>/issue-<n>-<slug> # type = fix|feat|docs
cd branches/<type>-issue-<n>-<slug> cd branches/<type>-issue-<n>-<slug>
5. Implement the narrow scope only; add/update focused tests if behavior changes. 6. Implement the narrow scope only; add/update focused tests if behavior changes.
6. Checks: run the test suite, compile/lint changed files, git diff --check, 7. Checks: run the test suite, compile/lint changed files, git diff --check,
and scan the diff for secrets. and scan the diff for secrets.
7. Commit (issue-linked message), push the branch, open a PR to master. 8. Commit (issue-linked message), push the branch, open a PR to master.
*The PR body MUST use closing keywords like `Closes #N` or `Fixes #N` to close the issue; do NOT use `Implements #N` or `Refs #N` for closing, as Gitea will not auto-close it.*
Include an "LLM Handoff Metadata" block in the PR body (attribution only; Include an "LLM Handoff Metadata" block in the PR body (attribution only;
never an eligibility input — docs/llm-agent-sha.md): never an eligibility input — docs/llm-agent-sha.md):
@@ -39,7 +40,7 @@ Steps:
- Branch: <branch> - Branch: <branch>
- Worktree: <worktree path> - Worktree: <worktree path>
- Self-review allowed: no - Self-review allowed: no
8. Stop before review/merge — you are the author. 9. Stop before review/merge — you are the author.
Handoff: issue #, branch, worktree path, files changed, checks + results, PR URL — Handoff: issue #, branch, worktree path, files changed, checks + results, PR URL —
formatted as the compact Controller Handoff (SKILL.md §K; long form only on formatted as the compact Controller Handoff (SKILL.md §K; long form only on
+20
View File
@@ -1649,3 +1649,23 @@ class TestTrackerHygieneCleanup(unittest.TestCase):
res = gitea_close_issue(issue_number=1) res = gitea_close_issue(issue_number=1)
self.assertTrue(res["success"]) self.assertTrue(res["success"])
self.assertIn("error:", res["cleanup_status"].get(1)) self.assertIn("error:", res["cleanup_status"].get(1))
def test_extract_linked_issue_numbers_hygiene(self):
from mcp_server import extract_linked_issue_numbers
# Standard closing keywords
self.assertEqual(extract_linked_issue_numbers("Closes #123"), [123])
self.assertEqual(extract_linked_issue_numbers("Fixes #123"), [123])
self.assertEqual(extract_linked_issue_numbers("Resolves #123"), [123])
# New implements/implemented keywords
self.assertEqual(extract_linked_issue_numbers("Implements #123"), [123])
self.assertEqual(extract_linked_issue_numbers("implemented #123"), [123])
self.assertEqual(extract_linked_issue_numbers("implement #123"), [123])
# refs / ref should NOT match
self.assertEqual(extract_linked_issue_numbers("Refs #123"), [])
self.assertEqual(extract_linked_issue_numbers("ref #123"), [])
# branch name fallback
self.assertEqual(extract_linked_issue_numbers("", branch_name="issue-123"), [123])
self.assertEqual(extract_linked_issue_numbers("", branch_name="feat/issue-123-foo"), [123])