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.13.0",
"version": "0.13.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
14 changes: 14 additions & 0 deletions plugins/work-items/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,20 @@
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.13.1]

### Fixed

- **`status: ready` issues with an open linked PR are no longer pickable (`#463`).** `/work-items:work`
selection now excludes a frontier candidate (tiers 2–3) that already has an open PR targeting it for
closure, closing the re-pick risk where an issue kept `status: ready` for its entire open-PR window and
a picker had to hand-cross-check `gh pr list` to avoid starting a duplicate branch. The check routes
through a new GitHub adapter *Open linked PRs* mechanic (closing-keyword linkage — the same `Closes #N`
signal `pr-issue-linkage` enforces — is authoritative; an intentional `Refs #N` opt-out does not
exclude), and fails open when the bound provider exposes no PR host (offline `local-markdown`). This
retires the interim in-flight heuristic that lived in the execute-step staleness pre-check. The durable
seam-level in-review state is deferred to the tracker-seam layer (`#416`/`#498`), not built here.

## [0.13.0]

Absorb the v4 loop-prompt execution rules into `/work-items:work` so the execute step owns them
Expand Down
3 changes: 2 additions & 1 deletion plugins/work-items/skills/work/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ For each tier, emit the corresponding query:

`--autonomous` additionally excludes items carrying the human-gated role label (`needs-human` by default — [`${CLAUDE_PLUGIN_ROOT}/reference/label-taxonomy.md`](${CLAUDE_PLUGIN_ROOT}/reference/label-taxonomy.md) "Canonical roles"). Tier 2 keeps only `area: guardrails` items that do not carry the resolved recurring-maintenance label; tier 3 keeps the rest of that non-recurring set. The normalized frontier model omits `createdAt`, so apply tier 3's **oldest-first** ordering by sorting the candidates on `createdAt` from the adapter "List items" projection (over the frontier numbers) before picking the top one — pass an explicit `--limit` covering the whole frontier on that projection so the default truncation can't hide an older candidate outside the first page and defeat the oldest-first pick (page per the adapter "List items" note if the frontier exceeds the max page size). Provider search syntax never leaves the adapter — the label filter runs over the labels `list-frontier` already returns.

**Exclude in-flight frontier candidates (open linked PR).** A frontier candidate (tiers 2–3) that already has an open PR targeting it for closure is work already in flight — drop it from the pickable set so it is not re-picked. For each surviving frontier number, query the bound adapter's *open linked PRs* operation (GitHub: [`${CLAUDE_PLUGIN_ROOT}/tools/work-item-tracker/adapters/github/README.md`](${CLAUDE_PLUGIN_ROOT}/tools/work-item-tracker/adapters/github/README.md) "Open linked PRs") and exclude the number when it reports an open PR closing that item. The **closing-keyword linkage** is the authoritative signal — the same `Closes #N` / native-closing-keyword linkage `pr-issue-linkage` enforces (owned by `/source-control:pull-request`), so an intentional `Refs #N` opt-out does not exclude its issue. Provider search syntax never leaves the adapter — this filter reads only the open-closing-PR boolean the adapter returns per number. **Fail open when the bound provider exposes no PR host:** the offline `local-markdown` binding is never a coordination surface and touches no network tool ([`${CLAUDE_PLUGIN_ROOT}/tools/work-item-tracker/CONTRACT.md`](${CLAUDE_PLUGIN_ROOT}/tools/work-item-tracker/CONTRACT.md) "local-markdown adapter"), so when no PR-host operation is available this filter has nothing to query — keep the candidate rather than blocking or reaching for a network tool.

Tiers flagged `last-resort: true` are skipped if any prior tier yielded a candidate.

### Step 2: Cross-reference with open items
Expand Down Expand Up @@ -122,7 +124,6 @@ Before claiming, verify the item is still actionable:
- If it references a file to modify: check the file exists and the item is still relevant.
- If it references a test to add: check whether similar tests already exist.
- If stale (work already done): close it with a comment (adapter: "Close item") and advance to the next candidate.
- **Already-in-flight (interim, retire on `#463`):** skip a frontier item that already has an open PR — advance to the next candidate rather than starting a duplicate branch. Detect the open PR through the PR host: match an open PR whose head branch follows this item's `<type>/<N>-` branch pattern (the *Suggest branch name* convention below) or whose body carries a GitHub issue-closing keyword for this item — any of `close`/`closes`/`closed`, `fix`/`fixes`/`fixed`, `resolve`/`resolves`/`resolved` (case-insensitive, optional colon) followed by `#<number>` with exact-number boundaries, the same nine forms `/source-control:pull-request` recognizes, not only the `Closes #<number>` the branch parse injects (a PR opened from an orphan or noncanonical branch can link via any of them). **Fail open when there is no PR host:** the offline `local-markdown` binding is never a coordination surface and touches no network tool, so when no PR host is configured or available this check has nothing to query — skip it and proceed to claim rather than blocking or reaching for a network tool. This is an interim heuristic; retire it once the durable in-progress marker (`#463`) lands and the frontier itself excludes in-flight items.

### Step 5: Claim and execute

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,38 @@ Match GitHub's issue-closing keyword set (`close`/`closes`/`closed`/`fix`/`fixes
`resolve`/`resolves`/`resolved`) followed by `#<num>`; the opt-out markers `Refs #<num>` /
`No related issue:` leave the body untouched.

## Open linked PRs

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):

```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])"))'
Comment thread
kyle-sexton marked this conversation as resolved.
Comment thread
kyle-sexton marked this conversation as resolved.
```

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`).

## Aggregate / count (dashboard + hygiene)

`gh issue list --json ... --jq` projections for `stats` and `audit` (bare `gh`).
Expand Down
Loading