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: 4 additions & 4 deletions 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.35.7",
"version": "0.35.8",
"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, raw-intake triage (issues and unsolicited PRs through raw, verified, briefed, autonomous-eligible states), plus the two work-items loop lanes of the loop-lane convention: a self-paced autonomous work-loop drain (work-class admission gate, adaptive item cap, PR-only) and an attended attend-queue escalation lane. 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 All @@ -22,12 +22,12 @@
"lane_instance": {
"type": "string",
"title": "Lane instance id",
"description": "Writer identity for this machine's loop-lane telemetry, per the loop-lane convention's lane-instance identity rule. It becomes the suffix of the lane's telemetry sentinel marker (`work-items:work-loop@<id>`), so each concurrently running lane instance owns its own comment and none can overwrite another's durable state including first_drain_complete, whose loss would end one machine's earn-trust ratification gate because a different machine finished a drain. Must match ^[a-z0-9][a-z0-9-]{0,31}$, be stable across restarts, and be distinct across concurrent instances; two lanes on one machine each need an explicit value. Absent: the sanitized lowercased hostname. The value appears verbatim in tracker comments set an opaque id if a machine name should not be published in a public tracker."
"description": "Writer identity for this machine's loop-lane telemetry, per the loop-lane convention's lane-instance identity rule. It becomes the suffix of the lane's telemetry sentinel marker (`work-items:work-loop@<id>`), so each concurrently running lane instance owns its own comment and none can overwrite another's durable state \u2014 including first_drain_complete, whose loss would end one machine's earn-trust ratification gate because a different machine finished a drain. Must match ^[a-z0-9][a-z0-9-]{0,31}$, be stable across restarts, and be distinct across concurrent instances; two lanes on one machine each need an explicit value. Absent: the sanitized lowercased hostname. The value appears verbatim in tracker comments \u2014 set an opaque id if a machine name should not be published in a public tracker."
},
"work_dispatch_concurrency_cap": {
"type": "number",
"title": "Autonomous dispatch concurrency cap",
"description": "Maximum concurrent dispatch waves /work-items:work's autonomous execute step allows per invocation (it runs exactly one item per invocation). Give a whole number of waves; a fractional value is floored to whole waves since a wave is discrete. When set, /work-items:work threads it into /implementation:implement-dispatch as that skill's --wave-cap ceiling. Leave unset to let implement-dispatch apply its own internal 3-5 wave default this key declares no default, so an unset value stays distinguishable from a configured one (which a declared default would collapse into a hard cap).",
"description": "Maximum concurrent dispatch waves /work-items:work's autonomous execute step allows per invocation (it runs exactly one item per invocation). Give a whole number of waves; a fractional value is floored to whole waves since a wave is discrete. When set, /work-items:work threads it into /implementation:implement-dispatch as that skill's --wave-cap ceiling. Leave unset to let implement-dispatch apply its own internal 3-5 wave default \u2014 this key declares no default, so an unset value stays distinguishable from a configured one (which a declared default would collapse into a hard cap).",
"min": 1
},
"work_loop_item_cap_start": {
Expand All @@ -54,7 +54,7 @@
"work_loop_frontier_item_cap_ceiling": {
"type": "number",
"title": "Work-loop frontier-tier item cap ceiling",
"description": "Quota guard for frontier-capability-tier items in the work-loop lane: such items run at concurrency 1 and their adaptive cap is bounded by this ceiling instead of the general one. Keep it at or below work_loop_item_cap_ceiling. The frontier tier is read from the item body, which any item author can write, so a frontier ceiling above the general one would let a body claim buy higher throughput; the lane detects that inversion and ignores this ceiling, bounding the item by the general one instead. The manifest cannot enforce the ordering userConfig min/max are static bounds with no cross-key validation.",
"description": "Quota guard for frontier-capability-tier items in the work-loop lane: such items run at concurrency 1 and their adaptive cap is bounded by this ceiling instead of the general one. Keep it at or below work_loop_item_cap_ceiling. The frontier tier is read from the item body, which any item author can write, so a frontier ceiling above the general one would let a body claim buy higher throughput; the lane detects that inversion and ignores this ceiling, bounding the item by the general one instead. The manifest cannot enforce the ordering \u2014 userConfig min/max are static bounds with no cross-key validation.",
"default": 2,
"min": 1
},
Expand Down
6 changes: 6 additions & 0 deletions plugins/work-items/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@
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.35.8]

### Fixed

- Harden triage intake against priority-axis stacking and blocked-by relabel races.

## [0.35.7]

### Added
Expand Down
5 changes: 5 additions & 0 deletions plugins/work-items/reference/issue-conventions.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ dialects are accepted:
Umbrella / epic items use an `Epic:` prefix or a trailing `(umbrella)`. A child-of relationship is
recorded as a native sub-issue edge, **never** as a title suffix.

When folding a member into an umbrella, treat the sequence as **one atomic action**: post the
Comment thread
kyle-sexton marked this conversation as resolved.
membership comment on the item, post the matching membership comment on the umbrella issue, record
the `blocked-by` edge, then strip the raw marker — never stop after the item-side comment alone
Comment thread
kyle-sexton marked this conversation as resolved.
(#633).
Comment thread
kyle-sexton marked this conversation as resolved.

Recurring maintenance items keep the `[Maintenance] {title}` shape owned by `track add --recurring`
(the due / work / recheck flows exact-match that prefix); they are exempt from the two prefix dialects.

Expand Down
4 changes: 2 additions & 2 deletions plugins/work-items/skills/triage/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ Show three buckets (oldest first, one-line summaries):
2. **Raw marker** — `status:needs-triage` / `priority:needs-triage`, whichever axis the repo files it under — explicitly tagged for evaluation
3. **`status:needs-info` with reporter activity** — reporter replied since last triage note; ready for re-evaluation

List open items and filter into buckets programmatically (adapter: "List items", bare read). Apply the lane-infrastructure exclusion ("Scope: raw intake only") to that listing **before** bucketing, so a telemetry issue carrying the raw marker is filtered out rather than bucketed under it. When the repo treats external PRs as a request surface, include them and tag each line `[PR]` or `[issue]` — but surface only *external* PRs (a collaborator's in-flight PR is not triage work; this filter is discovery-only, and an explicitly named PR is always triaged regardless of author). Present as a compact table.
List open items and filter into buckets programmatically (adapter: "List items", bare read). Apply the lane-infrastructure exclusion ("Scope: raw intake only") to that listing **before** bucketing, so a telemetry issue carrying the raw marker is filtered out rather than bucketed under it. **Defensive skip:** drop any item that already carries a native `blocked-by` edge *and* a prior triage comment (machine disclaimer or structured needs-info template) — a stray re-label from another lane must not cost a full re-investigation (#646). When the repo treats external PRs as a request surface, include them and tag each line `[PR]` or `[issue]` — but surface only *external* PRs (a collaborator's in-flight PR is not triage work; this filter is discovery-only, and an explicitly named PR is always triaged regardless of author). Present as a compact table.

## Triage workflow (with number)

Expand All @@ -105,7 +105,7 @@ Read the item body, comments, and any linked PRs; for a PR, the diff too (adapte
Classify **bug vs enhancement** first — it steers the rest of the flow (bugs get reproduced; rejected enhancements get ledgered). Then recommend:

- **Type** — bug → `Bug`; enhancement → `Feature` (or `Task` for tracked non-feature work). Native GitHub Issue Type on org repos, set through the seam; `type:` label on personal / non-org repos. Item title and prefix conventions: [`${CLAUDE_PLUGIN_ROOT}/reference/issue-conventions.md`](${CLAUDE_PLUGIN_ROOT}/reference/issue-conventions.md)
- **Priority label** — resolve the live `priority:` label set from the bound adapter at action entry (for the GitHub adapter, `gh label list --search 'priority:'`; members are never snapshotted here — [`${CLAUDE_PLUGIN_ROOT}/reference/label-taxonomy.md`](${CLAUDE_PLUGIN_ROOT}/reference/label-taxonomy.md) "Universal axes"). Default to the resolved set's **assessed-default** tier — the mid-urgency member when the set follows the conventional critical/high/medium/low ordering (e.g. `priority: medium`, if present) — when no directive, category rule, or severity signal sets one. Reserve the next tier up (e.g. `priority: high`) for items that block other work or carry an imminent external deadline; the top tier (e.g. `priority: critical`) keeps its existing critical semantics. A live set that doesn't follow that ordering has no default to infer by convention — ask, or omit the label the way a repo-undefined default is omitted elsewhere ([`${CLAUDE_PLUGIN_ROOT}/skills/track/actions/add.md`](${CLAUDE_PLUGIN_ROOT}/skills/track/actions/add.md) "Priority"). When a directive or category rule sets this label **above** the finding's self-labeled severity, record the original severity in the triage comment (e.g. `priority set to <resolved label> by <rule>; reporter severity: <sev>`) so implementers can sub-sort within a priority band. No new labels. This triage-assessed default is deliberately distinct from the `/work-items:track add` filing default (the resolved set's lowest-urgency tier, an untriaged-signal floor rather than a priority assessment)
- **Priority label** — resolve the live `priority:` label set from the bound adapter at action entry (for the GitHub adapter, `gh label list --search 'priority:'`; members are never snapshotted here — [`${CLAUDE_PLUGIN_ROOT}/reference/label-taxonomy.md`](${CLAUDE_PLUGIN_ROOT}/reference/label-taxonomy.md) "Universal axes"). The `priority:` axis is **single-valued**: before applying the assessed priority in step 5, remove every other `priority:` label already on the item (same replace-not-stack rule used when clearing the raw marker). Default to the resolved set's **assessed-default** tier — the mid-urgency member when the set follows the conventional critical/high/medium/low ordering (e.g. `priority: medium`, if present) — when no directive, category rule, or severity signal sets one. Reserve the next tier up (e.g. `priority: high`) for items that block other work or carry an imminent external deadline; the top tier (e.g. `priority: critical`) keeps its existing critical semantics. A live set that doesn't follow that ordering has no default to infer by convention — ask, or omit the label the way a repo-undefined default is omitted elsewhere ([`${CLAUDE_PLUGIN_ROOT}/skills/track/actions/add.md`](${CLAUDE_PLUGIN_ROOT}/skills/track/actions/add.md) "Priority"). When a directive or category rule sets this label **above** the finding's self-labeled severity, record the original severity in the triage comment (e.g. `priority set to <resolved label> by <rule>; reporter severity: <sev>`) so implementers can sub-sort within a priority band. No new labels. This triage-assessed default is deliberately distinct from the `/work-items:track add` filing default (the resolved set's lowest-urgency tier, an untriaged-signal floor rather than a priority assessment)
- **Target state** — from the state machine above: needs-info, or one of the three briefed exits (delegable, decision-defaulted, human-gated). For a briefed item that carries a decision, apply the **routing test**: is the alternative reversible/maintainer-vetoable (→ **decision-defaulted**: autonomous-eligible role + `status:ready`, recorded with a `Decision defaulted: X — veto before merge` comment) or genuinely open — open design space, product intent, or cross-repo policy (→ **human-gated**)?

**Direction gate.** Recommending is read-only; the gate governs *mutation* — labels, comments, closes, item creation — and which side of it you are on is fixed by how triage was invoked:
Expand Down
Loading