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
2 changes: 1 addition & 1 deletion plugins/work-items/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
"name": "work-items",
"version": "0.14.0",
"version": "0.14.1",
"description": "Manages development work items through a provider-neutral tracker seam that ships with the plugin (bundled dispatcher plus github and local-markdown adapters; seam plugin-dir canonical, adapters consumer-local-first): dashboard, taxonomy-labeled creation, a race-safe assignee-plus-lease claim protocol, recurring-schedule checks, TODO scanning, stale-lease auditing, plan decomposition into vertical-slice items, and raw-intake triage (issues and unsolicited PRs through raw, verified, briefed, autonomous-eligible states). The re-runnable setup skill binds the provider (.work-item-tracker.json), seeds the recurring-schedule seam (.github/recurring-schedule.json), and remaps canonical role labels.",
"author": {
"name": "Melodic Software",
Expand Down
15 changes: 15 additions & 0 deletions plugins/work-items/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,21 @@
All notable changes to the `work-items` plugin are documented here. Format follows
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning.

## [0.14.1]

### Fixed

- **Open-linked-PR filter no longer wrongly drops issues from fenced examples (`#654`).** The GitHub
adapter's "Open linked PRs" mechanic (`#463`) matched a closing-keyword `jq` regex over the raw
PR body, so a PR body carrying a fenced `Closes #<N>` example spuriously reported issue `#<N>` as
having an open closing PR and dropped the still-pickable issue from the `/work-items:work`
frontier. The mechanic now reads GitHub's own computed close-linkage via the GraphQL
`Issue.closedByPullRequestsReferences` connection (open-state nodes only), which excludes fenced
code blocks and HTML comments, needs no word/number-boundary guards, and honors the default-branch
requirement — retiring the raw-body regex and its partial `gsub` fence-stripper (which recognized
only exactly-three backtick/tilde fences). Behavior change: an issue whose only `Closes #<N>` is on
a non-default-base PR now stays pickable, matching GitHub's real auto-close semantics.

## [0.14.0]

Absorb bullets 1–4 of the v4 loop-prompt routing rules into `/work-items:triage` so the skill owns
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,33 +159,61 @@ Match GitHub's issue-closing keyword set (`close`/`closes`/`closed`/`fix`/`fixes

For `/work-items:work` selection — report whether item `<N>` already has an open PR targeting it
for closure, so a candidate whose work is in flight is dropped from the pickable frontier rather
than re-picked. `--search "<N> in:body"` is a coarse prefilter: it returns every open PR whose
body mentions the number — a superset, since a small `<N>` (e.g. `#5`) appears in many PR bodies
and GitHub sorts search by relevance, not recency. `--limit` MUST therefore exceed the repo's
open-PR count so the real closing PR is never truncated away before the precise filter runs
(`--limit 1000` covers any realistic repo; page with `--search` date ranges if a repo ever holds
more open PRs than that — see the `--limit` note under "List items"). The `jq` `test` over the
returned bodies is the authoritative match, keeping only PRs that carry a real closing keyword for
`#<N>` (bare read):
than re-picked. The authoritative signal is **GitHub's own computed close-linkage**, not a text
match over the PR body: the GraphQL `Issue.closedByPullRequestsReferences` connection returns
exactly the PRs GitHub links as closing this issue — the same linkage GitHub renders in the
issue sidebar and acts on for merge-time auto-close. Keep only the `OPEN`-state nodes: a `MERGED`
PR that closed the issue already dropped it from the open frontier, and a `CLOSED` (unmerged) PR
is not in flight (bare read):

```bash
gh pr list --state open --search "<N> in:body" --json number,body --limit 1000 | tr -d '\r' \
| jq --arg n "<N>" 'any(.[]; (.body | gsub("(?s)(\\x60{3}|~{3}).*?\\1"; "")) | test(
"(?i)(?<!\\w)(?:close|closes|closed|fix|fixes|fixed|resolve|resolves|resolved):?[ \t]+#\($n)(?![0-9])"))'
OWNER_REPO=$(gh repo view --json owner,name -q '.owner.login + " " + .name' | tr -d '\r')
gh api graphql \
-f query='query($owner:String!, $repo:String!, $n:Int!) {
repository(owner:$owner, name:$repo) {
issue(number:$n) {
closedByPullRequestsReferences(first:100, includeClosedPrs:false) { nodes { number state } }
}
}
}' \
-f owner="${OWNER_REPO% *}" -f repo="${OWNER_REPO#* }" -F n=<N> \
--jq '[.data.repository.issue.closedByPullRequestsReferences.nodes[] | select(.state=="OPEN")] | any' \
| tr -d '\r'
```

Emits `true` when at least one open PR closes `#<N>`, `false` otherwise. The **closing keyword is
the authoritative signal**, not the head-branch name: the `pr-issue-linkage` pre-create gate
guarantees a standard-flow PR carries a `Closes #<N>` keyword, so keyword-matching catches it,
while an intentional opt-out (`Refs #<num>` / `No related issue:`) correctly does NOT match and so
does not exclude its issue. This is the same keyword set the "PR closing-keyword mechanics" section
matches. Three guards keep the match precise: the leading `gsub` strips fenced code blocks
(triple-backtick or `~~~`) before matching, so a `Closes #<N>` shown only inside an example snippet
does not spuriously exclude the issue — GitHub treats fenced blocks as inert, so such a snippet
never auto-closes anything; the `(?<!\w)` lookbehind (Oniguruma, variable-width — valid in `jq`'s
regex engine) stops a keyword matching inside a longer word, so a body reading `prefixes #<N>` /
`suffix #<N>` does not spuriously exclude the issue; the trailing `(?![0-9])` number boundary keeps
`#463` from matching `#4630` / `#1463` (the `#` anchor already prevents a match inside `#1463`).
Emits `true` when at least one **open** PR closes `#<N>`, `false` otherwise. `-F n=<N>` passes the
number as a GraphQL `Int` (typed); `-f` passes the owner/repo strings; the trailing `| tr -d '\r'`
follows the Windows/Git Bash rule under "Gotchas" (the boolean can otherwise arrive as `true\r`).
The `select(.state=="OPEN")` filter is **load-bearing, not redundant with `includeClosedPrs:false`**:
that argument suppresses only `CLOSED` (unmerged) PRs, so a `MERGED` PR still appears in the
connection and must be dropped here — otherwise an issue whose only closing PR merged to a
non-default base (or that was reopened after a merge) would be wrongly reported as in-flight.
`first:100` requests the connection's maximum page (GitHub GraphQL caps `first`/`last` at 100).
Because the connection retains `MERGED` nodes, this bound counts every PR the issue has *ever*
linked as closing — not only the open ones — so a long merge/reopen history consumes page slots
ahead of the currently-open PR. An issue carrying more than 100 such linked closing PRs (not
realistic in practice) would need cursor pagination, the GraphQL analogue of the `--limit` note
under "List items". Why GitHub's computed
linkage instead of a body regex over `gh pr list --search`:

- **Fenced code blocks and HTML comments are inert for free.** GitHub does not link a closing
keyword that appears only inside a fenced code block or an HTML comment, so an example snippet
such as a fenced `Closes #<N>` never surfaces here and never spuriously excludes the still-open
issue. There is no fence-tracking heuristic to maintain — the retired approach hand-rolled a
`jq` `gsub` that recognized only exactly-three backticks or tildes and silently missed
four-or-more-backtick and indented fences. This closes the fence-blindness the prior regex
carried.
- **No word-boundary or number-boundary guards.** `#463` cannot collide with `#4630` / `#1463`
and a keyword cannot match inside a longer word, because the reference is GitHub's parsed issue
linkage, not a regex over raw text.
- **Base-branch correctness (behavior change).** GitHub forms the close-link only for a PR that
targets the repository's default branch — a closing keyword on any other base branch is ignored
and creates no linkage. This mechanic therefore does not exclude an issue whose only `Closes
#<N>` lives on a non-default-base PR, whereas the retired raw-body regex counted it. That issue
now stays pickable, matching GitHub's real merge-time auto-close semantics.
- **Opt-out is intrinsic.** An intentional `Refs #<num>` (reference without closing) never enters
the closing linkage, so it correctly does not exclude its issue — the same opt-out the
`pr-issue-linkage` gate honors, now with no keyword allow/deny list to keep in sync.

## Aggregate / count (dashboard + hygiene)

Expand Down
Loading