From c44a4145491ea9848922839f4f76dcb7e31f8274 Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Sun, 12 Jul 2026 15:45:09 -0400 Subject: [PATCH 1/5] @ feat(work-items): republish v2 plugin from post-seam provider-neutral core Re-plumbs the work-items plugin skill onto the provider-neutral work-item-tracker seam (GitHub the bound adapter today), replacing the v1 inline-gh, label-based hold->verify->claim implementation. - Skill content synced from the post-seam core: coordination routes through `tools/work-item-tracker/work-item-tracker.sh ` (create, claim, renew/reclaim lease, links, sub-items, frontier); provider mechanics reference the bound adapter. Zero inline gh in the core. - Claiming is now assignee + lease comment, race-safe via lease-comment identity, with idempotent session-start reclaim. The status:considering / status:claimed hold protocol is retired for claiming. - Applied the standard publish de-couple transform: stripped medley path/name coupling (docs/adr, AGENTS.md, tools/shared/comment-hygiene, review/code-quality, recurring-issues.yml, .github/scripts, doc-drift-detector, .claude/rules/conversational-mechanics Primitives, DD design-decision tags, concrete medley label values) to the generic consumer-context forms established by v1; kept the consumer-convention seam paths and the optional .github/recurring-schedule.json. - version 0.1.0 -> 0.2.0; new CHANGELOG.md; provider-neutral plugin.json description/keywords, README, root catalog row, and marketplace tags. - agent-brief template relocated to reference/; research/ excluded. Refs melodic-software/medley#1340 @ --- .claude-plugin/marketplace.json | 2 +- README.md | 2 +- plugins/work-items/.claude-plugin/plugin.json | 6 +- plugins/work-items/CHANGELOG.md | 41 ++++ plugins/work-items/README.md | 70 ++++--- plugins/work-items/skills/work-items/SKILL.md | 118 ++++++------ .../skills/work-items/actions/add.md | 86 +++------ .../skills/work-items/actions/audit.md | 126 +++---------- .../skills/work-items/actions/decompose.md | 70 +++++-- .../skills/work-items/actions/done.md | 66 ++----- .../skills/work-items/actions/due.md | 45 ++--- .../skills/work-items/actions/list.md | 48 ++--- .../skills/work-items/actions/recheck.md | 32 +--- .../skills/work-items/actions/scan.md | 6 +- .../skills/work-items/actions/search.md | 50 ++--- .../skills/work-items/actions/start.md | 87 +++------ .../skills/work-items/actions/stats.md | 44 ++--- .../skills/work-items/actions/triage.md | 44 ++--- .../skills/work-items/actions/work.md | 175 +++++------------- .../skills/work-items/evals/evals.json | 55 +++--- .../work-items/reference/agent-brief.md | 6 +- .../work-items/reference/label-taxonomy.md | 10 +- .../skills/work-items/templates/checklist.md | 40 ++-- 23 files changed, 485 insertions(+), 744 deletions(-) create mode 100644 plugins/work-items/CHANGELOG.md diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index c2a58dfa5..fef38c957 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -143,7 +143,7 @@ "name": "work-items", "source": "./plugins/work-items", "category": "productivity", - "tags": ["work-items", "issues", "github", "tracker", "triage", "orchestration", "skill"] + "tags": ["work-items", "tracker", "seam", "provider-neutral", "github", "issues", "triage", "orchestration", "skill"] }, { "name": "discovery", diff --git a/README.md b/README.md index 6e74b6826..7f291c63a 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ Browse and manage with `/plugin`. To refresh after updates: `/plugin marketplace | [`fable-5-playbook`](plugins/fable-5-playbook) | Skill | Ships Claude Fable 5's operating doctrine as an on-demand knowledge skill — core standing instructions plus twelve trigger-routed chapters (calibration, reasoning moves, planning, debugging, orchestration, verification, recovery, trust boundaries, and more) and an Opus-adaptation chapter for non-Fable models. | | [`firecrawl`](plugins/firecrawl) | Skill | Web scraping, search, crawling, URL discovery, browser interaction, and local file parsing through the `firecrawl-cli` binary — results written to disk and read back selectively to keep large pages out of context, plus a gated maintainer update flow tracking the upstream CLI and skill source. | | [`claude-config-audit`](plugins/claude-config-audit) | Skills | Keeps a repo's Claude Code configuration healthy via three report-first audit skills: `settings-audit` (config files vs upstream truth, with live plugin-drift detection), `memory-health` (CLAUDE.md / rules / auto-memory against official-doc criteria), and `automation-deep-dive` (evidence-gated verdicts on automation gaps). | -| [`work-items`](plugins/work-items) | Skill | Manages GitHub Issues as a development work-item tracker: dashboard, taxonomy-labeled creation, a concurrent-safe hold→verify→claim protocol for multi-agent pickup, recurring-schedule checks, TODO scanning, stale-claim auditing, plan decomposition into vertical slices, and structured triage. | +| [`work-items`](plugins/work-items) | Skill | Manages development work items through a provider-neutral tracker seam (GitHub the bound adapter today): dashboard, taxonomy-labeled creation, a race-safe assignee-plus-lease claim protocol for multi-agent pickup, recurring-schedule checks, TODO scanning, stale-lease auditing, plan decomposition into vertical slices, and structured triage. | | [`discovery`](plugins/discovery) | Skills | Structured discovery before changes. Ships four skills: `/discovery:explore` and `/discovery:explore-deep` (six-dimension codebase exploration, inline or in a forked subagent) plus `/discovery:research` and `/discovery:research-deep` (three-phase multi-source external research with source tiers, falsification, and recency gates). | | [`playwright`](plugins/playwright) | Skill | Live E2E browser automation through Microsoft's `@playwright/cli` — named sessions, accessibility-ref snapshots (click/fill by ref), screenshots, console/network capture, mocking, tracing, video, and auth state, with artifacts written to disk so only paths enter context, plus Windows/orchestrator overlays and a gated maintainer update flow tracking the upstream npm skill. | | [`code-tidying`](plugins/code-tidying) | Skills | Structure-only codebase improvement per Beck's *Tidy First?*: `/code-tidying:tidy` proactively hunts a rotated, glob-scoped lane for named tidyings under a research-backed scope budget (consumer-defined lanes in `.claude/tidy-lanes/` override bundled ones) and ships one tight PR; `/code-tidying:batch-simplify` sweeps recently changed files through grouped, dependency-ordered simplification waves with a never-drop deferred-items contract. | diff --git a/plugins/work-items/.claude-plugin/plugin.json b/plugins/work-items/.claude-plugin/plugin.json index c6e406da1..c6f71eb46 100644 --- a/plugins/work-items/.claude-plugin/plugin.json +++ b/plugins/work-items/.claude-plugin/plugin.json @@ -1,12 +1,12 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "work-items", - "version": "0.1.0", - "description": "Manages GitHub Issues as a development work-item tracker: dashboard, create with a label taxonomy, concurrent-safe claim protocol, recurring-schedule checks, TODO scanning, stale-claim auditing, plan decomposition into vertical-slice issues, and structured triage.", + "version": "0.2.0", + "description": "Manages development work items through a provider-neutral tracker seam (GitHub the bound adapter today): 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 structured triage.", "author": { "name": "Melodic Software", "email": "info@melodicsoftware.com" }, "license": "MIT", - "keywords": ["work-items", "issues", "github", "tracker", "triage", "orchestration"] + "keywords": ["work-items", "tracker", "seam", "provider-neutral", "github", "issues", "triage", "orchestration"] } diff --git a/plugins/work-items/CHANGELOG.md b/plugins/work-items/CHANGELOG.md new file mode 100644 index 000000000..76406fbad --- /dev/null +++ b/plugins/work-items/CHANGELOG.md @@ -0,0 +1,41 @@ +# Changelog + +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.2.0] + +Re-plumbed onto the provider-neutral work-item-tracker seam. The skill is now backend-agnostic; GitHub +is the bound adapter today rather than a hardcoded dependency. + +### Changed (breaking) + +- **Provider-neutral over the tracker seam.** Every tracker operation routes through the + work-item-tracker seam — the skill calls `tools/work-item-tracker/work-item-tracker.sh ` and the + bound provider adapter executes it (contract: `tools/work-item-tracker/CONTRACT.md`). The skill core + inlines **no** provider commands: coordination (create, claim, renew/reclaim lease, dependency links, + sub-items, frontier selection, single-item fetch) uses seam verbs, and provider mechanics (filtered + listing, search, aggregation, close, label/comment edits) reference the bound adapter's operations + doc. Previously the skill called `gh` directly throughout. +- **Claim protocol is now assignee + lease, race-safe at the seam.** The label-based + hold → verify → claim dance (`status:considering` / `status:claimed`) is retired. + Claiming assigns the item and writes a lease comment; races are resolved by lease-comment identity, + and a session-start `reclaim` runs idempotently to recover crashed sessions' stale leases. The claim + identity is always the authenticated session user, never a shared bot. +- **New consumer requirement.** The consuming repo provides the seam at `tools/work-item-tracker/` and + binds its active provider in `.work-item-tracker.json`. The skill no longer shells out to `gh` on its + own; the GitHub adapter behind the seam does. + +### Changed + +- Backend-neutral vocabulary throughout — "work item" rather than "GitHub issue"; the description and + action docs read against any bound provider. +- Removed the skill's `gh`-scoped `allowed-tools` and the inline `gh`-based pre-computed dashboard + block; the dashboard now derives through the seam and adapter. +- The agent-brief template ships at `reference/agent-brief.md`. + +## [0.1.0] + +- Initial release: a GitHub-Issues work-item tracker skill — `stats`, `list`, `add`, `work`, `start`, + `done`, `due`, `recheck`, `search`, `scan`, `audit`, `decompose`, `triage` — with a `gh`-backed + hold → verify → claim multi-agent claim protocol. diff --git a/plugins/work-items/README.md b/plugins/work-items/README.md index a11b5d732..d4f8101b5 100644 --- a/plugins/work-items/README.md +++ b/plugins/work-items/README.md @@ -1,17 +1,18 @@ # work-items -A Claude Code plugin that manages **GitHub Issues as a development work-item -tracker** — a centralized, concurrent-safe alternative to file-based TODO -lists, designed for teams where humans and autonomous agents pick work from -the same queue. +A Claude Code plugin that manages **development work items through a +provider-neutral tracker seam** — a centralized, concurrent-safe alternative to +file-based TODO lists, designed for teams where humans and autonomous agents +pick work from the same queue. The skill core is backend-agnostic; GitHub is the +bound adapter today. Invoke it with `/work-items:work-items ` (or let Claude invoke it when -you ask about issues, tracked work, or what to do next): +you ask about work items, tracked work, or what to do next): ```text /work-items:work-items # stats dashboard (default) /work-items:work-items add "fix the flaky retry test" --type fix -/work-items:work-items work # auto-select + claim + execute one issue +/work-items:work-items work # auto-select + claim + execute one item /work-items:work-items triage 42 ``` @@ -20,28 +21,48 @@ you ask about issues, tracked work, or what to do next): | Action | What it does | |--------|--------------| | `stats` | Dashboard: open/claimed counts, overdue recurring items, category breakdown | -| `list` / `search` | Filtered listing / full-text search across open + closed issues | -| `add` | Create an issue with a label taxonomy, duplicate pre-flight, and an authorization gate against model-initiated filing | -| `work` | Auto-select one issue by priority tiers and execute it end-to-end | -| `start` / `done` | Claim an issue / close it with a completion comment and PR linkage | +| `list` / `search` | Filtered listing / full-text search across open + closed items | +| `add` | Create a work item with a label taxonomy, duplicate pre-flight, and an authorization gate against model-initiated filing | +| `work` | Auto-select one item by priority tiers and execute it end-to-end | +| `start` / `done` | Claim an item / close it with a completion comment and PR linkage | | `due` / `recheck` | Recurring-schedule checks and cadence advancement (optional consumer infrastructure) | | `scan` | Sweep the codebase for TODO/FIXME/HACK markers; resolve or file each | -| `audit` | Detect stale claims/holds, orphaned recurring entries, label hygiene issues | -| `decompose` | Break a plan/PRD/issue into vertical-slice issues with AFK/HITL classification and dependency ordering | -| `triage` | Structured evaluation of incoming issues, with an attention view | +| `audit` | Detect stale leases, orphaned recurring entries, label hygiene issues | +| `decompose` | Break a plan/PRD/item into vertical-slice items with AFK/HITL classification and dependency ordering | +| `triage` | Structured evaluation of incoming items, with an attention view | + +## The tracker seam + +Every tracker operation goes through the **work-item-tracker seam** — the skill +calls `tools/work-item-tracker/work-item-tracker.sh ` and the bound +provider adapter executes it (contract: +`tools/work-item-tracker/CONTRACT.md`). Coordination — create, claim +(assignee + lease), renew/reclaim lease, dependency links, sub-items, frontier +selection, single-item fetch — uses seam verbs directly. Operations without a +core verb (filtered listing, search, aggregation, close, label/comment edits) +are provider-specific and route through the bound adapter's operations reference +(GitHub: `tools/work-item-tracker/adapters/github/README.md`). The skill core +inlines no provider commands, so swapping the backend is swapping the bound +adapter, not editing the skill. ## Multi-agent claim protocol -`work` and `start` use a three-phase **hold → verify → claim** optimistic-lock -protocol built on GitHub comment-ID ordering, so multiple concurrent agents -never grab the same issue. Stale holds and claims are cleaned up by `audit`. -Claim assignments always run on the session's own identity — a shared bot -identity would defeat the collision check. +`work` and `start` claim an item by **assigning it and writing a lease +comment**, race-safe at the seam via lease-comment identity, so multiple +concurrent agents never grab the same item. A session-start `reclaim` runs +idempotently to recover the stale leases of crashed or abandoned sessions. +Claim assignments always run on the session's own authenticated identity — never +a shared bot — so the race check stays sound. ## Requirements -- **`gh` CLI**, authenticated against the repository's host. All tracker - operations go through `gh`; nothing else leaves the machine. +- **The work-item-tracker seam.** The consuming repo provides the seam at + `tools/work-item-tracker/` and binds its active provider in + `.work-item-tracker.json`. The seam's contract and per-adapter mechanics are + documented alongside it (`tools/work-item-tracker/CONTRACT.md`). +- **The bound provider's client.** For the GitHub adapter that is the **`gh` + CLI**, authenticated against the repository's host; the adapter is the only + thing that leaves the machine. - **Labels** (optional but recommended): the universal `type:` / `priority:` / `status:` / meta groups, plus any project-specific `area:` / `category:` / `ecosystem:` groups the repo defines. The taxonomy and discovery command are @@ -60,10 +81,11 @@ identity would defeat the collision check. ## Configuration No `userConfig`. Project-specific behavior routes through the consuming repo's -own surfaces: its labels (taxonomy discovery via `gh label list`), its optional -recurring schedule file, and its own `CLAUDE.md` / rules for write-identity -policy (e.g. routing tracker writes through a bot wrapper) and development -workflow. The skill degrades gracefully when any of these are absent. +own surfaces: the bound provider in `.work-item-tracker.json`, its labels +(taxonomy discovery through the adapter), its optional recurring schedule file, +and its own `CLAUDE.md` / rules for write-identity policy (e.g. routing tracker +writes through a bot wrapper) and development workflow. The skill degrades +gracefully when any of these are absent. ## License diff --git a/plugins/work-items/skills/work-items/SKILL.md b/plugins/work-items/skills/work-items/SKILL.md index 0cffbcc30..e855b585d 100644 --- a/plugins/work-items/skills/work-items/SKILL.md +++ b/plugins/work-items/skills/work-items/SKILL.md @@ -1,35 +1,24 @@ --- name: work-items -description: "Manage GitHub Issues as a development work-item tracker — dashboard stats, create with a label taxonomy, concurrent-safe claim protocol, recurring-schedule checks, codebase TODO scanning, stale-claim auditing, plan decomposition into vertical-slice issues, and structured triage. Use when: 'add an issue', 'pick work', 'close issue', 'list issues', 'what's due', 'issue stats', 'search issues', 'scan TODOs', 'audit claims', 'break plan into issues', 'decompose into tickets', 'triage issue', 'check overdue recurring items'." +description: "Manage development work items through the bound tracker (work-item-tracker seam). Actions: stats, list, add, work, start, done, due, recheck, search, scan, audit, decompose, triage. Use when: 'add a work item', 'add an issue', 'pick work', 'close a work item', 'list work items', 'what's due', 'work-item stats', 'search work items', 'scan TODOs', 'audit claims', 'break a plan into tickets', 'decompose into tickets', 'create issues from plan', 'triage', 'what needs triage', 'check overdue recurring items'. Covers codebase TODO/FIXME scanning, plan decomposition into vertical-slice tickets, stale-claim auditing, and recurring schedule checks. Not for new bug reports — use /bug-report:bug-report first (read-only report), then chain to /work-items add via --context if filing is needed." argument-hint: " [args] — actions: stats, list, add, work, start, done, due, recheck, search, scan, audit, decompose, triage (default: stats)" user-invocable: true disable-model-invocation: false -allowed-tools: - - "Bash(gh issue list*)" - - "Bash(gh api user*)" --- -## Pre-computed context - -Open issues: !`gh issue list --state open --limit 500 --json number --jq 'length' 2>/dev/null || echo "0"` -Claimed issues: !`gh issue list --label "status:claimed" --limit 500 --json number --jq 'length' 2>/dev/null || echo "0"` -Current user: !`gh api user --jq '.login' 2>/dev/null || echo "unknown"` - ## Variables Arguments: `$ARGUMENTS` ## Scope -This skill manages **development work items** via GitHub Issues — maintenance tasks, feature requests, bug reports, recurring audits, and housekeeping. It is a centralized, concurrent-safe alternative to file-based TODO lists. - -**Backend-agnostic design.** The skill uses "work items" language. The current backend is GitHub Issues; all `gh` CLI commands operate against the current repository. +This skill manages **development work items** — maintenance tasks, feature requests, bug reports, recurring audits, and housekeeping. It is the centralized, concurrent-safe work-item tracker. -**Write identity.** All commands below use bare `gh` (the session's own identity). If the consuming project routes tracker writes through a bot identity or wrapper script, follow that project's own rules (its `CLAUDE.md` / `.claude/rules`) for every write operation (create, comment, label edit, close) — with one exception: claim assignments (`--add-assignee "@me"`) must always run on the session identity, never a shared bot, or the multi-agent collision check in the claim protocol silently breaks (every claimant would resolve to the same account). +**Provider-neutral over the seam.** Every tracker operation goes through the work-item-tracker seam — the skill calls `tools/work-item-tracker/work-item-tracker.sh ` and the bound provider adapter executes it (contract: `tools/work-item-tracker/CONTRACT.md`). The repo's active provider is bound in `.work-item-tracker.json`. Coordination — create, claim (assignee + lease), lease renew/reclaim, dependency links, sub-items, frontier selection, single-item fetch — uses seam verbs directly. Operations without a core verb (listing with arbitrary filters, search, aggregation, close, label/comment edits) are provider-specific; for the bound GitHub adapter their mechanics live in `tools/work-item-tracker/adapters/github/README.md`. The skill core stays provider-portable and inlines no provider commands. -**Default = fix, not file.** Do NOT reflexively suggest `add` or `scan` for small/medium drift discovered while working. Boy Scout scope (cosmetic fixes, stale counts, broken links, single-line corrections) belongs in the current change, not the tracker. File NEW issues only when the work is genuinely orthogonal to the current session, large enough to need its own plan, or needs research the current session isn't positioned to do. When in doubt, fix in-place and surface what was fixed in the commit message / PR description. +**Default = fix, not file.** Do NOT reflexively suggest `/work-items add` or `/work-items scan` for small / medium drift discovered while working. Boy Scout scope (cosmetic, stale counts, broken links, single-line corrections, one-paragraph clarifications) belongs in the current change, not the tracker. File NEW items only when the work is genuinely orthogonal to the current session, large enough to need its own `/architect` plan, or needs research the current session isn't positioned to do. Auto-suggesting `add` for fixable scope is the failure mode this rule prevents. When in doubt, fix in-place and surface what was fixed in the commit message / PR description. -**Label taxonomy.** Issues use a label prefix structure — UNIVERSAL groups (work in any repo) plus PROJECT-SPECIFIC groups whose members each consuming repo defines. The full structure, the discovery command, and the extension pattern live in [`reference/label-taxonomy.md`](reference/label-taxonomy.md). +**Label taxonomy.** Work items use an 8-group label prefix structure — UNIVERSAL groups (work in any repo) plus REPO-SPECIFIC groups carrying this repo's concrete values. The full member list (including this repo's populated `area:` / `category:` / `ecosystem:` / `cadence:` values) lives in [`reference/label-taxonomy.md`](reference/label-taxonomy.md). | Group | Prefix | Scope | Examples | |-------|--------|-------|----------| @@ -37,16 +26,16 @@ This skill manages **development work items** via GitHub Issues — maintenance | Priority | `priority:` | universal | `priority:p0-critical` through `priority:p3-low` | | Status | `status:` | universal | `status:needs-triage`, `status:considering`, `status:claimed`, `status:blocked`, `status:needs-info` | | Meta | (none) | universal | `automated`, `recurring`, `agent-ready`, `good-first-issue`, `migrated`, `stale` | -| Cadence | `cadence:` | universal | `cadence:weekly`, `cadence:biweekly`, `cadence:monthly`, `cadence:quarterly`, `cadence:semi-annual`, `cadence:annual` | -| Area | `area:` | project-specific | the consuming repo's architecture surface — discover via `gh label list` | -| Category | `category:` | project-specific | the consuming repo's domain categorization — discover via `gh label list` | -| Ecosystem | `ecosystem:` | project-specific | the consuming repo's language/toolchain mix — discover via `gh label list` | +| Area | `area:` | repo-specific | the consuming repo's architecture surface — see `reference/label-taxonomy.md` | +| Category | `category:` | repo-specific | the consuming repo's domain categorization — see `reference/label-taxonomy.md` | +| Ecosystem | `ecosystem:` | repo-specific | the consuming repo's language/toolchain mix — see `reference/label-taxonomy.md` | +| Cadence | `cadence:` | repo-specific | e.g. `cadence:weekly`, `cadence:monthly` — full set in `reference/label-taxonomy.md` | -**Recurring schedule (optional consumer infrastructure).** When the consuming repo defines recurring items in `.github/recurring-schedule.json` (and optionally automates issue creation from it with its own scheduled workflow), the `due`, `recheck`, and `work` actions consume that schedule. When the file is absent, those recurring features degrade gracefully: `due` reports "no recurring schedule configured", and `work` skips the recurring tiers. Schedule item shape: see [`actions/add.md`](actions/add.md) step "If `--recurring`". +**Recurring schedule.** Recurring items are defined in `.github/recurring-schedule.json` and created as items by the consuming repo's recurring-issues automation when they come due. The `/work-items recheck` action updates this schedule after completing a periodic check. ## Emit checklist -For the `work` action (the most common multi-step path), copy the "Action: work" section of [`templates/checklist.md`](templates/checklist.md) into your session task list (or the consuming project's working-notes convention) and tick each step as completed. For `add`, `start`, `done`, and `recheck`, copy the matching action section. Single-action reads (`stats`, `list`, `search`, `scan`, `audit`) don't need a checklist. +For the `work` action (the most common multi-step path), instruct the agent to copy `templates/checklist.md` "Action: work" section into `.work//work-items-checklist.md`. Tick each step as completed. For other actions (`add`, `start`, `done`, `recheck`), copy the matching action section. Single-action reads (`stats`, `list`, `search`, `scan`, `audit`) don't need a checklist. ## Action Router @@ -55,47 +44,72 @@ Parse `$ARGUMENTS` to extract the action (first token) and remaining arguments. | Action | Description | Detail | |--------|-------------|--------| | `stats` | Dashboard: open/claimed counts, overdue recurring, category breakdown | [actions/stats.md](actions/stats.md) | -| `list` | List issues with label/state/assignee filtering | [actions/list.md](actions/list.md) | -| `add` | Create a new issue with labels from the taxonomy | [actions/add.md](actions/add.md) | -| `work` | Auto-select one issue and execute it via the project's development workflow | [actions/work.md](actions/work.md) | -| `start` | Claim an issue (assign + label `status:claimed`) | [actions/start.md](actions/start.md) | -| `done` | Close an issue with a completion comment | [actions/done.md](actions/done.md) | +| `list` | List work items with label/state/assignee filtering | [actions/list.md](actions/list.md) | +| `add` | Create a new work item with labels from the taxonomy | [actions/add.md](actions/add.md) | +| `work` | Auto-select one item and execute it via the project's development workflow | [actions/work.md](actions/work.md) | +| `start` | Claim an item (assignee + lease via the seam) | [actions/start.md](actions/start.md) | +| `done` | Close an item with a completion comment | [actions/done.md](actions/done.md) | | `due` | Show recurring items past their `next_due` date | [actions/due.md](actions/due.md) | -| `recheck` | Update `last_checked`/`next_due` in the recurring schedule after a periodic check | [actions/recheck.md](actions/recheck.md) | -| `search` | Full-text search across issues (open + closed) | [actions/search.md](actions/search.md) | -| `scan` | Scan codebase for TODO/FIXME/HACK comments, resolve or file issues from them | [actions/scan.md](actions/scan.md) | -| `audit` | Detect stale claims/holds, orphaned recurring entries, label hygiene | [actions/audit.md](actions/audit.md) | -| `decompose` | Break a plan/PRD/issue into vertical-slice issues with HITL/AFK classification and dependency ordering | [actions/decompose.md](actions/decompose.md) | -| `triage` | Evaluate incoming issue: gather → recommend → reproduce → interview → apply outcome. No args = attention view | [actions/triage.md](actions/triage.md) | +| `recheck` | Update `last_checked`/`next_due` in recurring schedule after a periodic check | [actions/recheck.md](actions/recheck.md) | +| `search` | Full-text search across items (open + closed) | [actions/search.md](actions/search.md) | +| `scan` | Scan codebase for TODO/FIXME/HACK comments, create items from them | [actions/scan.md](actions/scan.md) | +| `audit` | Detect stale claims, orphaned recurring entries, label hygiene | [actions/audit.md](actions/audit.md) | +| `decompose` | Break a plan/PRD/item into vertical-slice work items with HITL/AFK classification and dependency ordering | [actions/decompose.md](actions/decompose.md) | +| `triage` | Evaluate incoming item: gather → recommend → reproduce → interview → apply outcome. No args = attention view | [actions/triage.md](actions/triage.md) | | `help` | Show the action table above | *(inline)* | If `$ARGUMENTS` is empty, run `stats`. If the action is unknown, show the action table. --- -## Integration points +## Operation routing -- **Development workflow.** The `work` action executes a claimed issue through the consuming project's own development workflow when one is defined (a workflow skill, CLAUDE.md workflow section, or team convention). When none is defined, follow the generic sequence: explore → plan → implement → test → review → PR. -- **Bug intake.** For new bug reports, prefer a structured capture first (the `/bug-report:bug-report` skill when that plugin is installed), then chain to `add` with the structured report as `--context`. -- **PR linkage.** Branch name `/-` (proposed by `start` / `work`) carries the issue number forward so PR tooling can inject `Closes #N`. `done --pr ` is the belt-and-suspenders path for manual PR flows: it verifies keyword presence on the unmerged PR body or falls back to `gh issue close` when the PR already merged. -- **Autonomous agents.** Issues labeled `agent-ready` with no assignee are available for autonomous pickup. The `work` action's claim protocol prevents concurrent-agent collisions; the `audit` action detects stale claims from crashed/abandoned sessions. -- **End of session.** Check `due` to see if any recurring items need attention. +The skill core carries no provider commands. Every action routes its tracker operations one of two ways: -**JSON fields available** (for `--json`): `assignees`, `author`, `body`, `closed`, `closedAt`, `comments`, `createdAt`, `id`, `isPinned`, `labels`, `milestone`, `number`, `projectCards`, `projectItems`, `reactionGroups`, `state`, `stateReason`, `title`, `updatedAt`, `url` +| Kind | Where | +|------|-------| +| **Coordination** — create, claim (assignee + lease), renew/reclaim lease, dependency links, sub-items, frontier selection, single-item fetch | Seam verbs: `tools/work-item-tracker/work-item-tracker.sh ` — contract in `tools/work-item-tracker/CONTRACT.md` | +| **Provider mechanics** — list with filters, search, aggregate/count, close, label/assignee edits, comments | The bound adapter's operations reference (GitHub: `tools/work-item-tracker/adapters/github/README.md`) | + +Coordination claims are race-safe at the seam (assignee + lease comment; `tools/work-item-tracker/CONTRACT.md` "Lease protocol") — the retired hold→verify→claim label dance is gone. Reads are non-mutating; writes route through the adapter's identity policy. + +--- + +## Integration Points + +### With `/workflow` + +The project's development workflow — a `/workflow` skill, a CLAUDE.md workflow section, or team convention — applies to every item worked via `/work-items work`; the `work` action chains its full step sequence. + +### With `/retro` + +The retrospective skill's Phase 3 surfaces "Issue candidates" -- deferred research, discovered gaps, recurring recheck updates. Approved items use `/work-items add`. Mid-session learnings can be captured with `/retro codify`. + +### With `/pull-request` + +Branch name `/-` (proposed by `/work-items start` / `/work-items work`) carries the item number forward. `/pull-request create` parses the branch name and injects the closing keyword into the PR body; the pre-create gate verifies the keyword (or an opt-out marker) is present before creating the PR. Closing-keyword shape and PR body shape are owned by `/pull-request`. + +`/work-items done --pr ` is the belt-and-suspenders path for manual PR flows where `/pull-request create` was not used: it verifies keyword presence on the unmerged PR body or falls back to closing the item when the PR has already merged (mechanics: the GitHub adapter README "PR closing-keyword mechanics"). + +### With autonomous agents + +Items labeled `agent-ready` with no assignee are available for autonomous agent pickup. The `work` action's seam claim (assignee + lease) prevents concurrent agent collisions. The `audit` action detects stale claims from crashed/abandoned agent sessions. + +### End-of-session check + +At end of session, alongside `/retro`, check `/work-items due` to see if any recurring items need attention. --- ## Gotchas -Failure patterns and platform quirks. Add to this section when new gotchas are discovered. - -- **Windows `\r` in pipe output.** Git Bash on Windows adds `\r` to `gh` output piped through `jq` or `--jq`. Add `| tr -d '\r'` to the end of any pipeline parsing `gh` JSON output -- **`gh issue list --search` uses GitHub search syntax, not `gh` flags.** Label filtering in `--search` uses `label:name` (no `--label` flag). Multiple labels: `label:bug label:help-wanted` (AND) or `label:bug,help-wanted` (OR). Exclude: `-label:stale`. Sort: `sort:created-asc`, `sort:updated-desc` -- **`gh issue list` default limit is 30.** Always pass `--limit` explicitly when you need more results. Maximum single request: 100 (API page size). For >100, use multiple calls with `--search` date ranges -- **`gh issue edit` uses `--add-label`/`--remove-label`, not `--label`.** `--label` is for `gh issue create` only. Edit uses `--add-label` (additive) and `--remove-label` (subtractive). Same for `--add-assignee`/`--remove-assignee` -- **`gh issue close --reason` accepts only `completed` or `not planned`.** No other values. Omitting `--reason` defaults to `completed` -- **Rate limits: 5,000 req/hr (PAT), 80 content-generating/min.** The `add` and `work` actions create issues — respect the 80/min secondary limit. For bulk operations (migration), batch 30 items with 10s pauses -- **Recurring schedule lives in the consuming repo's `.github/`, not the plugin.** The conventional path is `.github/recurring-schedule.json`; it is version-controlled and shared by the whole team. Actions that read it degrade gracefully when it's absent -- **Concurrency protocol: hold→verify→claim.** `work` and `start` use a three-phase optimistic locking protocol to prevent collisions when multiple agents pick work concurrently. Phase 1 (hold): add `status:considering` label + hold comment with session metadata. Phase 2 (verify): check for concurrent holds using GitHub comment ID ordering (monotonically increasing, server-assigned — lowest ID wins ties). Phase 3 (claim): promote to `status:claimed` + assignee. Post-claim verification catches the remaining edge case of simultaneous promotions (multiple assignees → later claimant releases). Stale holds (`status:considering` >15min) are cleaned up by `audit`. The protocol adds ~3 API calls per selection (~1s total). At 50+ concurrent agents, consider an external queue — comment-based ordering is the best GitHub-native approach but degrades with extreme concurrency -- **Pre-computed context counts may be stale.** The open/claimed counts above reflect the state at skill invocation time. For current counts mid-session, run the `stats` action -- **Auto-labeling workflows don't fire on CLI creates.** If the consuming repo auto-labels issues via an Issue Forms workflow, `gh issue create` does NOT trigger it. Always apply labels explicitly via `--label` flags when creating issues programmatically +Skill-behavior failure patterns. Add to this section when new gotchas are discovered. Provider-mechanic gotchas (Windows `\r`, search-qualifier syntax, the `gh` 30-row default limit, `--add-label` vs `--label`, `--reason` values, rate limits, Issue-Forms auto-labeling) live in the bound adapter's operations reference — for GitHub, `tools/work-item-tracker/adapters/github/README.md` "Gotchas". + +- **Claim concurrency is the seam's job.** Claiming is race-safe at the seam (assignee + lease comment, same-identity aware) — `tools/work-item-tracker/CONTRACT.md` "Lease protocol". Reclaim runs idempotently at session start (`work` / `start`). Do not hand-roll a label-based hold protocol. +- **Recurring schedule is in `.github/`, not the skill directory.** The schedule file is `.github/recurring-schedule.json`. It's version-controlled and shared. The consuming repo's recurring-issues automation reads it; the `/work-items recheck` action updates it. + +## What this skill does NOT do + +- Inline provider (`gh`) commands — coordination goes through the seam, provider mechanics through the bound adapter reference. +- Own the label taxonomy content — that is `reference/label-taxonomy.md` (universal + repo-specific groups). +- Bind the provider — the active provider lives in `.work-item-tracker.json`, not here. diff --git a/plugins/work-items/skills/work-items/actions/add.md b/plugins/work-items/skills/work-items/actions/add.md index 20fd9805f..af4f47e86 100644 --- a/plugins/work-items/skills/work-items/actions/add.md +++ b/plugins/work-items/skills/work-items/actions/add.md @@ -4,73 +4,45 @@ Create a new work item with labels from the taxonomy. **Defaults applied by this action:** -- **Priority** — when the `--priority` flag is absent, apply `priority:p3-low` (include it in the labels array built below). -- **Body template** — when `--body` is not provided, fall back to the default skeleton: a `## Context` paragraph (what observation surfaced this issue, what's the cost of leaving it), a `## Proposed work` bullet list (concrete next actions), `## Acceptance criteria` (one verifiable assertion per bullet), and `## References` (cross-references to rules, files, prior PRs, or external docs). The concrete body the workflow builds is detailed in step "Build body" below. -- **Label taxonomy** — labels are validated against the group structure documented in [`../reference/label-taxonomy.md`](../reference/label-taxonomy.md). +- **Priority** — when the `--priority` flag is absent, apply `priority:p3-low`. +- **Body template** — when `--body` is not provided, fall back to the default skeleton: a `## Context` paragraph (what observation surfaced this item, what's the cost of leaving it), a `## Proposed work` bullet list (concrete next actions), `## Acceptance criteria` (one verifiable assertion per bullet), and `## References` (cross-references to rules, files, prior PRs, or external docs). The concrete body the workflow builds is detailed in step "Build body" below. +- **Label taxonomy** — labels are validated against the 8-group structure documented in [`../reference/label-taxonomy.md`](../reference/label-taxonomy.md). ## Usage ``` -add [--category ] [--type ] [--area ] [--ecosystem ] [--priority

] [--recurring --cadence ] [--context "summary"] "Item description" +/work-items add [--category ] [--type ] [--area ] [--ecosystem ] [--priority

] [--recurring --cadence ] [--context "summary"] "Item description" ``` ## Flags -- `--category ` -- Category label. Valid values are the consuming repo's `category:` labels ([`../reference/label-taxonomy.md`](../reference/label-taxonomy.md)). Default: `general` ONLY when the repo actually has a `category:general` label (check `gh label list`); otherwise omit the category label entirely +- `--category ` -- Category label. Valid values are the consuming repo's `category:` labels (see [`../reference/label-taxonomy.md`](../reference/label-taxonomy.md)); default `general` only when the repo actually defines a `category:general` label, otherwise omit the category label - `--type ` -- Type label (default: `chore`). Valid: `feat`, `fix`, `chore`, `docs`, `refactor`, `test`, `build`, `perf` -- `--area ` -- Area label (the consuming repo's `area:` labels) -- `--ecosystem ` -- Ecosystem label (the consuming repo's `ecosystem:` labels) +- `--area ` -- Area label — the consuming repo's `area:` labels (see [`../reference/label-taxonomy.md`](../reference/label-taxonomy.md)) +- `--ecosystem ` -- Ecosystem label — the consuming repo's `ecosystem:` labels (see [`../reference/label-taxonomy.md`](../reference/label-taxonomy.md)) - `--priority

` -- Priority label (e.g., `p0-critical`, `p1-high`, `p2-medium`, `p3-low`) - `--recurring` -- Mark as recurring. Requires `--cadence` - `--cadence ` -- One of: `weekly`, `biweekly`, `monthly`, `quarterly`, `semi-annual`, `annual` -- `--context "summary"` -- Add research context to the issue body -- `--agent-ready` -- Apply `agent-ready` meta label and use the agent-brief body template ([`../reference/agent-brief.md`](../reference/agent-brief.md)). Brief format: behavioral (not procedural), no file paths, complete acceptance criteria, explicit scope boundaries. Use for issues intended for AFK agent execution +- `--context "summary"` -- Add research context to the item body +- `--agent-ready` -- Apply `agent-ready` meta label and use agent-brief body template (see [`reference/agent-brief.md`](../reference/agent-brief.md)). Brief format: behavioral (not procedural), no file paths, complete acceptance criteria, explicit scope boundaries. Use for items intended for AFK agent execution - `--force` -- Skip duplicate check ## Workflow -> **Authorization gate (BEFORE any step below).** Never file an issue on inferred intent. A topic the user raised, "they'd want it tracked", or approval of a related *direction* is NOT authorization to create an outward-facing artifact. An explicit user `add ...` invocation IS the authorization; model-initiated filing is not. If you only *infer* an issue should exist: draft the title + body and ASK first, or keep a local working note instead. +> **Authorization gate (BEFORE any step below).** Never file a work item on inferred intent. A topic the user raised, "they'd want it tracked", or approval of a related *direction* is NOT authorization to create an outward-facing artifact — those need explicit authorization. An explicit user `/work-items add ...` invocation IS the authorization; model-initiated filing is not. If you only *infer* an item should exist: draft the title + body, ASK first, OR write a local `.work//` note instead. 1. Parse the item text and flags from arguments. -1. **Duplicate check** (skip if `--force`) — search before creating (read — bare `gh`): +1. **Duplicate check** (skip if `--force`) — the search-before-create pre-flight (adapter: "Search items", `--state all`, bare read). If a potential duplicate is found (similar title), present it: "Similar item found: **#N {title}** ({state}). Add anyway, merge, or skip?" -```bash -gh issue list --search "" --state all --json number,title,state --limit 10 | tr -d '\r' -``` - -If a potential duplicate is found (similar title), present it: "Similar issue found: **#N {title}** ({state}). Add anyway, merge, or skip?" - -1. **Build labels array.** A group default applies ONLY when no flag supplied a label for that group — `--type fix` replaces `type:chore`, `--priority p1-high` replaces `priority:p3-low`, `--category x` replaces `category:general` (one label per group). Every label must also exist in the repo — `gh issue create` fails on unknown labels — so filter the resolved set against the live label list. When a default label doesn't exist, either offer to create the universal set once (`gh label create`) or omit that label: +1. **Build labels list** (comma-separated for the seam) from the flags, e.g. `type:chore,category:general` plus any `--type`/`--category`/`--area`/`--ecosystem`/`--priority` values. -```bash -EXISTING=$(gh label list --limit 200 --json name --jq '.[].name' | tr -d '\r') -# Resolve one label per group first (flag value wins over the group default), -# then keep only labels that exist in the repo. -LABELS="" -for l in "type:${TYPE:-chore}" "priority:${PRIORITY:-p3-low}" "category:${CATEGORY:-general}" <other flag-specified...>; do - grep -qxF "$l" <<<"$EXISTING" && LABELS="$LABELS --label $l" -done -``` - -1. **Build body.** If `--agent-ready`, use the agent-brief template from [`../reference/agent-brief.md`](../reference/agent-brief.md) (Type, Summary, Current behavior, Desired behavior, Key interfaces, Acceptance criteria, Out of scope). Otherwise use the default template: +1. **Build body.** If `--agent-ready`, use the agent-brief template from [`reference/agent-brief.md`](../reference/agent-brief.md) (Category, Summary, Current behavior, Desired behavior, Key interfaces, Acceptance criteria, Out of scope). Otherwise use the default template: ```markdown -## Context - -{what observation surfaced this issue; the cost of leaving it — from the description and --context} +## Description -## Proposed work - -- {concrete next action derived from the description} - -## Acceptance criteria - -- [ ] {one verifiable assertion per bullet} - -## References - -- {cross-references to rules, files, prior PRs, or external docs — or "none"} +{description text} ## Metadata @@ -80,35 +52,31 @@ done | Area | {area or "unspecified"} | | Ecosystem | {ecosystem or "unspecified"} | +{if --context: ## Context\n\n{context summary}} + {if --recurring: ## Recurring\n\nCadence: {cadence}\nTriggers: {triggers or "none configured"}} ``` -1. **Create the issue** (write). If `--recurring`, prefix the title with `[Maintenance]` — the convention that enables dedup and `recheck` matching against the recurring schedule: - -Pass the generated body via `--body-file` (never inline `--body` — generated text can contain quotes, backticks, or `$()` that the shell would interpret): +1. **Create the item** via the seam (`create-item` routes the write through the adapter's identity policy). If `--recurring`, prefix the title with `[Maintenance]` to match the convention used by the recurring-issues automation (enables dedup and `recheck` matching): ```bash -BODY_FILE=$(mktemp) -# Write the composed body to $BODY_FILE with the Write tool (not shell interpolation) -gh issue create \ +tools/work-item-tracker/work-item-tracker.sh create-item \ --title "[Maintenance] {title}" \ - --body-file "$BODY_FILE" \ - $LABELS \ - | tr -d '\r' -rm -f "$BODY_FILE" + --body "{body}" \ + --labels "type:chore,category:general" ``` -For non-recurring issues, omit the `[Maintenance]` prefix. +For non-recurring items, omit the `[Maintenance]` prefix. The emitted item object carries the new `id` (fully-qualified) and `number`. -1. **If `--recurring`:** Also add the item to the consuming repo's `.github/recurring-schedule.json` (create the file with an `{"items": []}` skeleton if the repo has opted into recurring scheduling but the file doesn't exist yet — ask first if the repo has no recurring setup at all): +1. **If `--recurring`:** Also add the item to `.github/recurring-schedule.json`: ```json { "id": "kebab-case-id", "title": "Title text", "cadence": "quarterly", - "area": ["<area>"], - "category": "<category>", + "area": ["<your-area>"], + "category": "<your-category>", "triggers": [], "last_checked": "2026-04-08", "next_due": "2026-07-07", @@ -117,9 +85,7 @@ For non-recurring issues, omit the `[Maintenance]` prefix. } ``` -Read the current file, append the new item to the `items` array, write it back. Compute `next_due` from today + cadence duration. - -Also add the `recurring` and `cadence:{cadence}` labels to the issue — route them through the same existence filter as step "Build labels array" (create the missing label once via `gh label create`, or omit it; never pass a label the repo lacks). +Read the current file, append the new item to the `items` array, write it back. Compute `next_due` from today + cadence duration. Also add the `recurring` and `cadence:{cadence}` labels to the item. 1. Confirm: "Created **#{number}**: {title} (labels: {labels})" diff --git a/plugins/work-items/skills/work-items/actions/audit.md b/plugins/work-items/skills/work-items/actions/audit.md index d500a46b6..eb9a98ad4 100644 --- a/plugins/work-items/skills/work-items/actions/audit.md +++ b/plugins/work-items/skills/work-items/actions/audit.md @@ -5,144 +5,62 @@ Detect stale claims, orphaned recurring entries, and label hygiene issues. ## Usage ``` -audit +/work-items audit ``` ## Checks -### 1. Stale Holds +### 1. Stale claims -Issues with `status:considering` label that haven't been promoted to `status:claimed` within 15 minutes (abandoned evaluation or agent crash). Reads use bare `gh`: +A claim is a lease; the `reclaim` verb is the SSOT for staleness (activity-check + outcome semantics: `tools/work-item-tracker/CONTRACT.md` "Lease protocol"). Enumerate currently-assigned items (adapter: "List items", assigned filter — rows carry `number`), resolve each `number` to a fully-qualified id (adapter: "Resolve item ID"; `reclaim` rejects a bare number), and run `reclaim` on each id — idempotent, safe to run repeatedly: ```bash -gh issue list --label "status:considering" --state open --json number,title,updatedAt --limit 50 | tr -d '\r' +tools/work-item-tracker/work-item-tracker.sh reclaim "<id>" ``` -For each issue, list the hold comments and read each hold's age from its embedded epoch timestamp (`<!-- hold:<host>:<epoch> -->` — authoritative; the issue's `updatedAt` moves on ANY activity and would mask a stale hold): +Present each item the verb reports `reclaimed: true` (released — the `reason` field says why); `reclaimed: false` means still-held or lease-renewed, left in place. Legacy label-based holds from before the seam are migrated by the label-reconciliation pass, not here. -```bash -# List hold comments with their embedded timestamps -gh api --paginate "repos/{owner}/{repo}/issues/<N>/comments?per_page=100" --jq '[.[] | select(.body | startswith("<!-- hold:")) | {id, marker: (.body | split("-->")[0])}]' | tr -d '\r' -``` - -Release ONLY the individual hold comments older than 15 minutes via PATCH (preserves audit trail) — an issue can carry an abandoned hold AND a newer active one, and the active holder must keep its hold: - -```bash -gh api --method PATCH "repos/{owner}/{repo}/issues/comments/<STALE_COMMENT_ID>" -f body="⏸ **Released** — hold lifted (reason: stale)" -``` - -Remove the `status:considering` label and post the released-for-pickup comment ONLY when no unreleased hold comments remain (writes): - -```bash -gh issue edit <N> --remove-label "status:considering" -gh issue comment <N> --body "Released stale hold (no activity for >15min). Available for pickup." -``` - -### 2. Stale Claims - -Issues with `status:claimed` label that haven't been updated in >24 hours (stale activity suggests abandoned work): - -```bash -gh issue list --label "status:claimed" --state open --json number,title,assignees,updatedAt --limit 50 | tr -d '\r' -``` - -For stale claim detection, use GitHub search's date filtering to find issues not updated in >24 hours: - -```bash -gh issue list --label "status:claimed" --search "updated:<$(date -u -d '24 hours ago' +%Y-%m-%d 2>/dev/null || date -u -v-1d +%Y-%m-%d)" --state open --json number,title,assignees,updatedAt --limit 50 | tr -d '\r' -``` - -Note: the `date` command differs between GNU (`-d '24 hours ago'`) and macOS BSD (`-v-1d`). If both fail, fall back to fetching all claimed issues and comparing `updatedAt` timestamps against the current time. +### 2. Orphaned recurring entries -Before flagging, check each candidate for an active linked PR — a claim with an open PR under review is working, not stale (this is what the report's "no linked PR" column asserts): +Entries in `.github/recurring-schedule.json` with no corresponding open or recently-closed item: ```bash -gh pr list --search "<N> in:body" --state open --json number,title --limit 5 | tr -d '\r' -``` - -Exclude candidates with an open referencing PR. Present each remaining stale issue with assignee and last update time. Ask the user before releasing — the agent may still be working in another session. - -**Action:** For stale claims, suggest (writes; explicit-login `--remove-assignee` is identity-agnostic): - -```bash -gh issue edit <N> --remove-label "status:claimed" --remove-assignee <login> -gh issue comment <N> --body "Released stale claim (no activity for >24h). Available for pickup." -``` - -### 3. Orphaned Recurring Entries - -Skip when the consuming repo has no `.github/recurring-schedule.json`. Entries in the schedule that have no corresponding open or recently-closed issue: - -```bash -# Get all recurring titles from schedule cat .github/recurring-schedule.json | jq -r '.items[].title' - -# Get all recurring issues -gh issue list --label "recurring" --state all --json number,title --limit 100 | tr -d '\r' ``` -Cross-reference: schedule items without a matching issue are orphaned. Note: recurring automation titles issues as `[Maintenance] {title}`, so strip the prefix when comparing against schedule titles. - -### 4. Unlabeled Issues - -Open issues missing expected labels. Gate the `category:` check on the repo actually defining category labels — otherwise every issue would be flagged: +List open recurring items (adapter: "List items", `--label recurring`, `--state all`, bare read) and cross-reference: schedule items without a matching item are orphaned. The recurring workflow titles items `[Maintenance] {title}`, so strip the prefix when comparing. -```bash -HAS_CATEGORY=$(gh label list --limit 200 --json name --jq '[.[].name | select(startswith("category:"))] | length' | tr -d '\r') - -# Missing type: label (always checked) -gh issue list --state open --json number,title,labels --limit 100 --jq ' - [.[] | select(any(.labels[]; .name | startswith("type:")) | not) - | {number, title, labels: [.labels[].name]}] -' | tr -d '\r' - -# Missing category: label (only when HAS_CATEGORY > 0) -gh issue list --state open --json number,title,labels --limit 100 --jq ' - [.[] | select(any(.labels[]; .name | startswith("category:")) | not) - | {number, title, labels: [.labels[].name]}] -' | tr -d '\r' -``` +### 3. Unlabeled items + label conflicts -### 5. Duplicate Label Detection - -Issues with conflicting labels (e.g., both `priority:p0-critical` and `priority:p3-low`): - -```bash -gh issue list --state open --json number,title,labels --limit 100 --jq ' - [.[] | select( - ([.labels[].name | select(startswith("priority:"))] | length) > 1 - ) | {number, title, priorities: [.labels[] | .name | select(startswith("priority:"))]}] -' | tr -d '\r' -``` +Items missing expected labels (no `type:*`, no `category:*`) and items with conflicting labels (e.g. two `priority:*`) surface via the hygiene projections in the bound adapter's operations reference (GitHub: `tools/work-item-tracker/adapters/github/README.md` "Aggregate / count (dashboard + hygiene)" — bare reads). ## Output ```markdown -## Work-Item Audit +## Work Items Audit -### Stale Claims (>24h, no linked PR) -| # | Issue | Assigned | Last Update | Action | -|---|-------|----------|-------------|--------| -| 1 | #42 Fix analyzer | @agent1 | 36h ago | Release? | +### Stale Claims (reclaimed) +| # | Item | Assigned | Last Update | Result | +|---|------|----------|-------------|--------| +| 1 | #42 Fix analyzer | @agent1 | 36h ago | released (no activity) | ### Orphaned Recurring Entries | # | Schedule Item | Status | |---|--------------|--------| -| 1 | Review linter config | No matching issue | +| 1 | Review biome.json | No matching item | -### Unlabeled Issues -| # | Issue | Missing | -|---|-------|---------| +### Unlabeled Items +| # | Item | Missing | +|---|------|---------| | 1 | #55 Something | No type:* label | ### Label Conflicts (none found) -**Summary:** X stale claims, Y orphaned entries, Z unlabeled issues +**Summary:** X stale claims reclaimed, Y orphaned entries, Z unlabeled items ``` ## Notes -- The audit is read-only by default. It presents findings and suggests actions -- For each stale claim, ask the user before releasing (the agent may still be working in another session) -- Run periodically (weekly) or before the `work` action to keep the tracker clean +- The `reclaim` verb never releases a live lease — a session actively working an item is safe. +- Run periodically (weekly) or before `/work-items work` (which also reclaims at session start) to keep the tracker clean. diff --git a/plugins/work-items/skills/work-items/actions/decompose.md b/plugins/work-items/skills/work-items/actions/decompose.md index 886dea839..24caca44e 100644 --- a/plugins/work-items/skills/work-items/actions/decompose.md +++ b/plugins/work-items/skills/work-items/actions/decompose.md @@ -1,48 +1,70 @@ # Action: `decompose` -Break a plan, spec, or PRD into independently-grabbable issues using vertical-slice (tracer-bullet) decomposition. +Break a plan, spec, or PRD into independently-grabbable work items using vertical-slice (tracer-bullet) decomposition. ## Usage ``` -decompose [source] +/work-items decompose [source] ``` `source` can be: -- *(empty)* — reads the consuming project's current plan document (its planning convention's output, e.g. a PLAN.md in the active working notes) — ask which document when ambiguous -- A path — reads that plan/PRD document directly -- `#<issue-number>` — reads an existing issue's body +- *(empty)* — reads `.work/<slug>/PLAN.md` phases (default) +- `prd` — reads `.work/<slug>/PRD.md` user stories +- `#<item-number>` — reads an existing item's body - Conversation context — synthesizes from current discussion ## Process ### 1. Gather source material -Read the source document. If a plan, extract phases + verification criteria. If a PRD, extract user stories + goals. If an issue, fetch full body and comments. +Read the source document. If PLAN.md, extract phases + sanity checks. If PRD.md, extract user stories + goals. If an item, fetch full body and comments. Use the project's domain glossary vocabulary throughout (its ubiquitous-language / glossary files when present). Respect the project's architecture decision records in the area. ### 2. Draft vertical slices -Break into **tracer-bullet** issues. Each issue is a thin vertical slice cutting through ALL integration layers end-to-end — NOT a horizontal slice of one layer. +Break into **tracer-bullet** items. Each item is a thin vertical slice cutting through ALL integration layers end-to-end — NOT a horizontal slice of one layer. **Vertical-slice rules:** - Each slice delivers a narrow but COMPLETE path through every layer (domain, application, infrastructure, tests) - A completed slice is demoable or verifiable on its own - Prefer many thin slices over few thick ones -- Slices map to plan phases when source is a plan — but split phases that touch multiple independent concerns +- Slices map to PLAN.md phases when source is a plan — but split phases that touch multiple independent concerns **Classify each slice:** | Type | Meaning | Label | |------|---------|-------| | **AFK** | Implementable and mergeable without human interaction | `agent-ready` | -| **HITL** | Requires human decision, design review, or manual testing | No `agent-ready` label | +| **HITL** | Requires human decision, design review, or manual testing | `needs-human` | Prefer AFK. Mark HITL only when the slice genuinely needs judgment (architectural decision, UX review, external-system access, manual QA). +`needs-human` is the label that keeps a slice out of autonomous pickup — `list-frontier --autonomous` excludes it (`tools/work-item-tracker/CONTRACT.md` "Verbs (core public surface)"). Merely omitting `agent-ready` does NOT: the frontier filter keys on the `needs-human` label, not on the absence of `agent-ready`, so an unlabeled HITL slice would still be claimable by `/work-items work`. `agent-ready` is the positive autonomous-pickup eligibility marker; the two labels gate different filters and an HITL slice wants `needs-human` set AND `agent-ready` omitted. + +**Investigation tickets — decisions, not deliverables.** When the source still carries unresolved unknowns (open design questions, unvalidated approaches, fuzzy scope), emit **investigation tickets** alongside — or ahead of — build slices. An investigation ticket resolves ONE decision and records the resolution as a closing comment; it produces no production code. Type each by the skill that resolves it: + +| Investigation type | Resolves | Routes to | +|--------------------|----------|-----------| +| research | External unknown (best practice, library choice, API behavior) | `/research` | +| prototype | Feasibility or design-feel unknown | `/prototype` | +| interview | Scope/contract ambiguity only the user can settle | `/interview` | + +Build slices blocked on an unresolved decision list the investigation ticket in "Blocked by". Investigation tickets are HITL by default (their output is a decision a human confirms) — label them `needs-human`, never `agent-ready`. + +### 2b. Wide refactors — expand-contract exception + +Mechanical changes with codebase-wide blast radius (rename a persisted column, retype a shared symbol, swap a serialization format) cannot land green as one vertical slice — a single-ticket attempt breaks every consumer at once. Sequence them **expand → migrate → contract**: + +1. **Expand** — one ticket adds the new form beside the old; both work; lands green +2. **Migrate** — one ticket per consumer batch moves call sites to the new form; each batch lands green independently +3. **Contract** — one final ticket removes the old form once nothing references it + +Each step is its own ticket with blocking edges (contract blocked by every migrate batch; migrate batches blocked by expand). Caveat: shared integration points (a wire format, a persisted schema) may pin expand + contract to a coordinated window — say so in the ticket body. + ### 3. Present for approval Present the proposed breakdown as a numbered list. For each slice: @@ -62,21 +84,31 @@ Ask the user: Iterate one question at a time until the user approves — never publish an unapproved breakdown. -### 4. Publish issues +### 4. Publish items -For each approved slice, create an issue (the `add` action is the canonical creation path). **Publish in dependency order** — blockers first — so real issue numbers can be referenced in "Blocked by" fields. +For each approved slice, create a work item via the seam (`tools/work-item-tracker/work-item-tracker.sh create-item`; `/work-items add` is the canonical creation path). **Publish in dependency order** — blockers first — so real IDs can fill the `--blocked-by` edges of dependents (native dependency edges, not just body text): + +```bash +# AFK slices get agent-ready (autonomous-pickup eligibility); HITL + investigation slices +# get needs-human instead — the label list-frontier --autonomous actually honors to exclude +# an item. Omitting agent-ready alone does NOT keep an HITL slice off the frontier. +META_LABEL=$([ -n "$AFK" ] && echo "agent-ready" || echo "needs-human") +tools/work-item-tracker/work-item-tracker.sh create-item --title "<slice title>" --body "<body>" \ + --labels "type:<t>,area:<a>,$META_LABEL" \ + --blocked-by "<blocker-id>[,<blocker-id>]" +``` -Use the agent-brief body format ([`../reference/agent-brief.md`](../reference/agent-brief.md)) for AFK slices. Body structure: +Use agent-brief body format (see [`reference/agent-brief.md`](../reference/agent-brief.md)) for AFK slices. Body structure: ```markdown ## Parent -Refs #<parent-issue> (if source was an existing issue) -<!-- or: Source: <plan document> Phase N --> +Refs #<parent-item> (if source was an existing item) +<!-- or: Source: .work/<slug>/PLAN.md Phase N --> ## What to build -Concise description of this vertical slice. Describe end-to-end behavior, not layer-by-layer implementation. No file paths — they go stale. Exception: if a prototype produced a snippet encoding a design decision more precisely than prose (state machine, reducer, schema, type shape), inline it and note it came from a prototype. +Concise description of this vertical slice. Describe end-to-end behavior, not layer-by-layer implementation. No file paths — they go stale. Exception: if `/prototype:logic` produced a snippet encoding a design decision more precisely than prose (state machine, reducer, schema, type shape), inline it and note it came from a prototype. ## Acceptance criteria @@ -86,15 +118,15 @@ Concise description of this vertical slice. Describe end-to-end behavior, not la ## Blocked by -- #<blocker-issue-number> +- #<blocker-item-number> Or "None — can start immediately" if no blockers. ``` -Apply labels per taxonomy: `type:` from slice nature, `area:` from affected module (when the repo defines area labels), `agent-ready` for AFK slices. +Apply labels per taxonomy: `type:` from slice nature, `area:` from affected module, `agent-ready` for AFK slices, `needs-human` for HITL + investigation slices. The seam records `--blocked-by` as a native dependency edge; the human-readable "Blocked by" body section mirrors it for readers. -**Do NOT close or modify any parent issue** — decomposition creates children, doesn't replace the parent. +**Do NOT close or modify any parent item** — decomposition creates children, doesn't replace the parent. ### 5. Report -After publishing, present summary: N issues created, dependency graph, which are AFK vs HITL, suggested execution order. +After publishing, present summary: N items created, dependency graph, which are AFK vs HITL, suggested execution order. diff --git a/plugins/work-items/skills/work-items/actions/done.md b/plugins/work-items/skills/work-items/actions/done.md index e48e69431..30cf79093 100644 --- a/plugins/work-items/skills/work-items/actions/done.md +++ b/plugins/work-items/skills/work-items/actions/done.md @@ -1,34 +1,33 @@ # Action: `done` -Close an issue with a completion comment. +Close a work item with a completion comment. ## Usage ``` -done <number or text match> [--summary "completion summary"] [--pr <number>] [--not-planned] +/work-items done <number or text match> [--summary "completion summary"] [--pr <number>] [--not-planned] ``` ## Flags - `--summary "text"` -- Completion summary (required -- will prompt if missing) - `--pr <number>` -- Link the closing PR -- `--not-planned` -- Close as "not planned" instead of "completed" (for issues decided against or superseded) +- `--not-planned` -- Close as "not planned" instead of "completed" (for items decided against or superseded) ## Workflow -1. **Resolve the issue.** If a number is given, use it directly. If text, search open issues. +1. **Resolve the item.** If a number is given, use it directly. If text, search open items (adapter: "Search items"). -1. **Check if recurring** (skip when the consuming repo has no `.github/recurring-schedule.json`). Issues created by recurring automation have a `[Maintenance]` prefix, so strip it before comparing: +1. **Check if recurring.** Read `.github/recurring-schedule.json` and check if the item's title matches any recurring item. Items created by the recurring-issues automation have a `[Maintenance]` prefix, so strip it before comparing: ```bash -SCHEDULE="${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel)}/.github/recurring-schedule.json" -cat "$SCHEDULE" | jq --arg title "<issue title>" ' +cat .github/recurring-schedule.json | jq --arg title "<item title>" ' ($title | ltrimstr("[Maintenance] ")) as $stripped | [.items[] | select(.title == $stripped or .title == $title or .id == "<kebab-id>")] | length ' ``` -If it's a recurring item, warn: "This is a recurring item. Did you mean the `recheck` action instead?" Proceed only if the user confirms. +If it's a recurring item, warn: "This is a recurring item. Did you mean `/work-items recheck` instead?" Proceed only if the user confirms. 1. **Build the closing comment:** @@ -38,55 +37,18 @@ If it's a recurring item, warn: "This is a recurring item. Did you mean the `rec {if --pr: Fixed in #{pr_number}} ``` -1. **Close the issue — unless an unmerged PR will auto-close it.** When `--pr` names an UNMERGED PR, do NOT close manually: run step 6 first to ensure the closing keyword is on the PR body, report "will auto-close when #{pr} merges", and post the completion summary as a plain comment instead. Closing now would mark the issue done before the work has actually landed. Manual close (write) applies when there is no PR, or the PR already merged without a keyword. Use `--reason "not planned"` when `--not-planned` was passed: +1. **Close the item** (adapter: "Close item" — WRITE via the adapter's identity policy), passing the closing comment and `--reason completed` (or `not planned` for `--not-planned`). -```bash -gh issue close <N> --comment "Done ($(date +%Y-%m-%d)): {summary}" --reason completed -# with --not-planned: -gh issue close <N> --comment "Closing ($(date +%Y-%m-%d)): {summary}" --reason "not planned" -``` - -1. **Clean up claim labels** (write): + The seam claim is a lease (assignee + lease comment), not a label — closing removes the item from the frontier, so no `status:*` label cleanup is part of this flow (the retired `status:claimed` label is handled by the label-reconciliation migration, not here). -```bash -gh issue edit <N> --remove-label "status:claimed" -``` - -1. **Belt-and-suspenders: verify PR body keyword presence.** This step fires when `done` is invoked for a manual PR flow (no PR tooling injected a closing keyword). Only runs when `--pr` is provided: - -```bash -PR_BODY=$(mktemp) -gh pr view <PR> --json body,mergedAt --jq '.body' | tr -d '\r' > "$PR_BODY" -# Both regexes must target THIS issue — a `Closes #7` / `Refs #7` for a -# different issue says nothing about #<N>. The (\b) boundary keeps #4 from -# matching #42. -KEYWORD_REGEX='^(close|closes|closed|fix|fixes|fixed|resolve|resolves|resolved):? #<N>\b' -OPTOUT_REGEX='^(Refs #<N>\b|No related issue:)' - -if grep -iE "$KEYWORD_REGEX" "$PR_BODY" >/dev/null; then - : # keyword present — auto-close will fire on merge -elif grep -E "$OPTOUT_REGEX" "$PR_BODY" >/dev/null; then - : # explicit opt-out — leave PR body alone -else - # PR body lacks keyword AND lacks opt-out. Behavior depends on merge state. - MERGED_AT=$(gh pr view <PR> --json mergedAt --jq '.mergedAt') - if [[ -z "$MERGED_AT" || "$MERGED_AT" == "null" ]]; then - # Unmerged — read-modify-write the PR body to prepend `Closes #<N>` - # (`--body-file` REPLACES, never appends). Write op. - printf '%s\n\n%s\n' "Closes #<N>" "$(cat "$PR_BODY")" | gh pr edit <PR> --body-file - - fi - # Merged — keyword can no longer auto-fire. Step 4's `gh issue close <N>` - # is the only remaining path. -fi -rm -f "$PR_BODY" -``` +1. **Belt-and-suspenders: verify PR body keyword presence.** Primary path is the `/pull-request create` §2.4.2 pre-create gate (covers all 9 closing keywords + opt-out markers). This step fires when `/work-items done` is invoked WITHOUT having gone through `/pull-request create` (rare — manual close path). Only runs when `--pr` is provided. -If keyword present → GitHub auto-closes the issue on merge (the structural path). If absent on an unmerged PR → read-modify-write injects `Closes #<N>` at top of body. If absent on a merged PR → step 4's manual `gh issue close` is the only remaining path; keyword can no longer auto-fire. + Apply the read-modify-write keyword check + prepend from the adapter "PR closing-keyword mechanics" section: if the (unmerged) PR body carries neither a closing keyword nor an opt-out marker, prepend `Closes #<N>`; if merged, the keyword can no longer auto-fire and Step 4's close is the only path. 1. **Confirm:** "Closed **#N**: {title}. Summary: {summary}" ## Notes -- Always require a completion summary. Summaries are institutional memory of what was decided/learned -- If no `--summary` provided, ask for one before closing -- The `--reason` flag accepts `completed` (default) or `not planned`. Use `not planned` for issues that were decided against, superseded, or no longer relevant +- Always require a completion summary. Summaries are institutional memory of what was decided/learned. +- If no `--summary` provided, ask for one before closing. +- The `done` action closes with `completed` (default), or `not planned` when `--not-planned` is given — for items decided against, superseded, or no longer relevant. diff --git a/plugins/work-items/skills/work-items/actions/due.md b/plugins/work-items/skills/work-items/actions/due.md index 68161d5cf..116b32c44 100644 --- a/plugins/work-items/skills/work-items/actions/due.md +++ b/plugins/work-items/skills/work-items/actions/due.md @@ -5,11 +5,9 @@ Show recurring items that are past their `next_due` date. ## Usage ``` -due +/work-items due ``` -Requires the consuming repo's `.github/recurring-schedule.json`. When the file is absent, report "no recurring schedule configured" and stop — see the SKILL body "Recurring schedule (optional consumer infrastructure)". - ## Workflow 1. **Read the recurring schedule:** @@ -17,30 +15,21 @@ Requires the consuming repo's `.github/recurring-schedule.json`. When the file i Read `.github/recurring-schedule.json` and filter items where `next_due <= today`. Use jq for the initial filter: ```bash -# Root the path at the project root — a relative path breaks when invoked from a subdirectory. -SCHEDULE="${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel)}/.github/recurring-schedule.json" -cat "$SCHEDULE" | jq --arg today "$(date +%Y-%m-%d)" ' +cat .github/recurring-schedule.json | jq --arg today "$(date +%Y-%m-%d)" ' [.items[] | select(.next_due != null and .next_due <= $today)] | sort_by(.next_due) | map({id, title, cadence, last_checked, next_due}) ' ``` -For days-overdue computation, calculate `(today - next_due)` in days. jq lacks date arithmetic, so compute this when presenting the table (e.g., parse the ISO dates and subtract). - -1. **Cross-reference with open issues.** For each due recurring item, check if an issue already exists (read — bare `gh`): +For days-overdue computation, calculate `(today - next_due)` in days. jq lacks date arithmetic, so compute this when presenting the table (parse the ISO dates and subtract). -```bash -gh issue list --label "recurring" --state open --json number,title --limit 50 | tr -d '\r' -``` +1. **Cross-reference with open items.** For each due recurring item, check if one already exists (adapter: "List items", `--label recurring`, bare read). Match by title prefix `[Maintenance]` (the format used by the recurring-issues automation). -Match each schedule row against the FULL expected title `[Maintenance] {schedule item title}` — never by the bare `[Maintenance]` prefix alone, or any recurring issue would satisfy every due row. - -1. **Check for orphaned entries.** Only DUE entries (`next_due <= today`) without a corresponding open issue are orphan-suspect — future items legitimately have no issue yet (recurring automation creates issues only when an item becomes due): +1. **Check for orphaned entries.** Entries in `recurring-schedule.json` with no corresponding item file or open item: ```bash -# Due item IDs only — future rows are healthy without an issue -cat "$SCHEDULE" | jq -r --arg today "$(date +%Y-%m-%d)" '.items[] | select(.next_due != null and .next_due <= $today) | .id' +cat .github/recurring-schedule.json | jq -r '.items[].id' ``` 1. **Present:** @@ -48,19 +37,23 @@ cat "$SCHEDULE" | jq -r --arg today "$(date +%Y-%m-%d)" '.items[] | select(.next ```markdown ## Recurring Items Due -| # | Item | Cadence | Last Checked | Days Overdue | Open Issue | -|---|------|---------|-------------|--------------|------------| -| 1 | Review dependency manifest | quarterly | 2026-03-23 | 15 | #52 | -| 2 | Review linter config | quarterly | 2026-03-23 | 15 | -- | +| # | Item | Cadence | Last Checked | Days Overdue | Open Item | +|---|------|---------|-------------|--------------|-----------| +| 1 | Review Directory.Packages.props | quarterly | 2026-03-23 | 15 | #52 | +| 2 | Review biome.json | quarterly | 2026-03-23 | 15 | -- | -**Orphaned entries** (in schedule but no matching issue): -- {id}: {title} -- create issue or remove entry +**Orphaned entries** (in schedule but no matching item): +- {id}: {title} -- create item or remove entry ``` If nothing is due: "All recurring items are current. Next due: **{item}** on **{date}**." ## Notes -- Cadence is a minimum interval, not a lock. On-demand rechecks are always allowed via the `recheck` action -- The `triggers` field in each schedule item lists external events that warrant early recheck regardless of cadence -- When the user mentions a trigger event (e.g., a new framework release shipped), proactively suggest relevant rechecks even if they aren't technically due yet +- Cadence is a minimum interval, not a lock. On-demand rechecks are always allowed via `/work-items recheck`. +- The `triggers` field in each schedule item lists external events that warrant early recheck regardless of cadence. +- When the user mentions a trigger event (e.g., ".NET 10.1 shipped"), proactively suggest relevant rechecks even if they aren't technically due yet. + +## Documentation freshness (optional) + +After presenting due items, when the user asks for a doc audit or maintenance is the focus, hand off to the consuming repo's documentation-audit tooling if it provides one (e.g. a doc-drift subagent or skill scoped to the repo's docs and rules) and surface the summary alongside the due table. Degrade gracefully — skip when no such tooling is present. diff --git a/plugins/work-items/skills/work-items/actions/list.md b/plugins/work-items/skills/work-items/actions/list.md index 768d9fa09..93c883d9d 100644 --- a/plugins/work-items/skills/work-items/actions/list.md +++ b/plugins/work-items/skills/work-items/actions/list.md @@ -1,59 +1,37 @@ # Action: `list` -List issues with optional filtering. +List work items with optional filtering. ## Usage ``` -list [--category <name>] [--label <name>] [--state <open|closed|all>] [--assignee <login>] [--limit <n>] [--search <query>] +/work-items list [--category <name>] [--label <name>] [--state <open|closed|all>] [--assignee <login>] [--limit <n>] [--search <query>] ``` ## Flags -- `--category <name>` -- Filter by category label (adds `--label "category:<name>"`; values are the consuming repo's `category:` labels — see [`../reference/label-taxonomy.md`](../reference/label-taxonomy.md)) +- `--category <name>` -- Filter by category label (adds `category:<name>`; the consuming repo's `category:` values — see [`../reference/label-taxonomy.md`](../reference/label-taxonomy.md)) - `--label <name>` -- Filter by any label (repeatable, AND logic) - `--state <s>` -- `open` (default), `closed`, `all` - `--assignee <login>` -- Filter by assignee (`@me` for self) - `--limit <n>` -- Max results (default: 30, max per request: 100) -- `--search <query>` -- Pass-through to `gh issue list --search` (GitHub search syntax) +- `--search <query>` -- Free search using the bound provider's search syntax ## Workflow -1. Build the `gh issue list` command from parsed flags (read — bare `gh`): +1. List items filtered by the parsed flags (adapter: "List items" — bare read; category/label/state/assignee/limit map to the adapter's filter args; `--search` uses the adapter's "Search items" path). The adapter returns normalized item objects. -```bash -gh issue list \ - --state open \ - --label "category:<name>" \ - --json number,title,state,labels,assignees,updatedAt \ - --limit 30 \ - | tr -d '\r' -``` - -1. Parse JSON and present as a condensed table: +1. Parse the result and present as a condensed table: ```markdown -| # | Issue | Labels | Assignee | Updated | -|---|-------|--------|----------|---------| -| 1 | #42 Fix <thing> | type:fix, area:<name> | @user | 2d ago | -| 2 | #38 Review <config> | type:chore, category:<name> | -- | 5d ago | +| # | Item | Labels | Assignee | Updated | +|---|------|--------|----------|---------| +| 1 | #42 Fix <thing> | type:fix, area:<your-area> | @user | 2d ago | +| 2 | #38 Review <config> | type:chore, category:<your-category> | -- | 5d ago | ``` -The `#` column is a sequential index for this listing. When the user references an item by `#`, match it to the issue in the most recent listing. - -## Search Syntax Reference - -When using `--search`, the query uses GitHub's search syntax: +The `#` column is a sequential index for this listing. When the user references an item by `#`, match it to the item in the most recent listing. -| Qualifier | Example | Meaning | -|-----------|---------|---------| -| `label:name` | `label:type:chore` | Has label | -| `-label:name` | `-label:stale` | Excludes label | -| `no:assignee` | `no:assignee` | Unassigned | -| `assignee:login` | `assignee:@me` | Assigned to user | -| `sort:field-dir` | `sort:created-asc` | Sort (created, updated, comments) | -| `created:>date` | `created:>2026-01-01` | Created after date | -| `updated:>date` | `updated:>2026-03-01` | Updated after date | -| text | `"fix authentication"` | Body/title text search | +## Search syntax -Multiple qualifiers are AND-combined: `label:type:chore label:recurring no:assignee sort:created-asc` +Provider search qualifiers (label/exclude/assignee/sort/date) and the pass-through behavior of `--search` are documented in the bound adapter's operations reference — GitHub: `tools/work-item-tracker/adapters/github/README.md` "Search items". diff --git a/plugins/work-items/skills/work-items/actions/recheck.md b/plugins/work-items/skills/work-items/actions/recheck.md index 82ee84764..df6cad205 100644 --- a/plugins/work-items/skills/work-items/actions/recheck.md +++ b/plugins/work-items/skills/work-items/actions/recheck.md @@ -5,25 +5,22 @@ Update a recurring item's `last_checked` and `next_due` dates after completing a ## Usage ``` -recheck <text match or schedule ID> +/work-items recheck <text match or schedule ID> ``` -Requires the consuming repo's `.github/recurring-schedule.json`. When the file is absent, report "no recurring schedule configured" and stop. - ## Workflow 1. **Find the item in the recurring schedule:** ```bash -SCHEDULE="${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel)}/.github/recurring-schedule.json" -cat "$SCHEDULE" | jq --arg q "<query>" ' +cat .github/recurring-schedule.json | jq --arg q "<query>" ' .items[] | select(.id == $q or (.title | ascii_downcase | contains($q | ascii_downcase))) ' ``` If multiple matches, present them and ask the user to clarify. -1. **Update dates.** Always set `last_checked` to today. Only advance `next_due` if it's in the past or today — if it's already in the future, the consuming repo's recurring automation has already advanced it and re-advancing would skip a cycle. +1. **Update dates.** Always set `last_checked` to today. Only advance `next_due` if it's in the past or today — if it's already in the future, the recurring-issues automation has already advanced it and re-advancing would skip a cycle. | Cadence | Days | |---------|------| @@ -40,26 +37,15 @@ Read the current file, find the matched item, then: - Set `last_checked` to today's date (always) - If `next_due <= today`: set `next_due` to today + cadence days -- If `next_due > today`: leave `next_due` unchanged (already advanced by the recurring automation) - -1. **Close the associated issue** (if one exists). Search for open issues with the `recurring` label matching the item's title (read — bare `gh`): - -```bash -gh issue list --search "\"[Maintenance] <title>\" label:recurring" --state open --json number,title --limit 5 | tr -d '\r' -``` - -If found, close it with a recheck comment and clean up any claim label (writes): +- If `next_due > today`: leave `next_due` unchanged (already advanced by the recurring workflow) -```bash -gh issue close <N> --comment "Rechecked $(date +%Y-%m-%d). Next due: <next_due>." --reason completed -gh issue edit <N> --remove-label "status:claimed" -``` +1. **Close the associated item** (if one exists). Search for open items with the `recurring` label matching the item's title (adapter: "Search items", `label:recurring` + the `[Maintenance]` title, bare read). If found, close it with a recheck comment (adapter: "Close item"), reason `completed`, comment "Rechecked YYYY-MM-DD. Next due: <next_due>.". 1. **Confirm:** "Rechecked: **{title}**. Next due: **{next_due}**" ## Notes -- Cadence is a minimum interval. On-demand rechecks are always valid -- If the consuming repo automates recurring-issue creation, a new issue will appear when `next_due` arrives -- If the schedule file was recently updated by automation, pull latest first -- The schedule file edit is a working-tree change — it gets committed and pushed with the PR for the work that triggered the recheck. If rechecking without other changes, commit from a feature branch and open a PR per the consuming repo's branching rules (never commit directly to a protected default branch) +- Cadence is a minimum interval. On-demand rechecks are always valid. +- The recurring-issues automation will create a new item when `next_due` arrives. +- If the schedule file was recently updated by the workflow's PR, pull latest first. +- The schedule file edit is a working-tree change — it gets committed and pushed with the PR for the work that triggered the recheck. If rechecking without other changes, commit from your feature branch and open a PR: `git add .github/recurring-schedule.json && git commit -m "chore: advance recurring schedule for <item>"` (never commit directly to main). diff --git a/plugins/work-items/skills/work-items/actions/scan.md b/plugins/work-items/skills/work-items/actions/scan.md index a30897baf..dc8bc86d5 100644 --- a/plugins/work-items/skills/work-items/actions/scan.md +++ b/plugins/work-items/skills/work-items/actions/scan.md @@ -23,7 +23,7 @@ If the consuming repo has its own comment-hygiene tooling (a shared pattern libr git -C "${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel)}" grep -nE '\b(TODO|FIXME|HACK|XXX)\b' -- ':!*.min.*' ':!*node_modules*' | tr -d '\r' ``` -- **Actionable (in scope):** bare `TODO`/`FIXME`/`HACK`/`XXX` markers describing work to do; internal tracker provenance comments (e.g. `issue #N` breadcrumbs left in code) +- **Actionable (in scope):** bare `TODO`/`FIXME`/`HACK`/`XXX` markers describing work to do; internal tracker provenance comments (e.g. `item #N` breadcrumbs left in code) - **Not actionable (skip):** external upstream citations (`org/repo#issue`), structured task-list grammar in working-notes files (e.g. `[TODO]` phase tags), test fixtures that assert on the literal marker text, and the consuming repo's documented exclusion paths ## Workflow @@ -36,10 +36,10 @@ git -C "${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel)}" grep -nE '\b(TO 1. **User selects a group** (by `#`) or use `--work` to auto-select the smallest. -1. **For each violation**, read context (10 lines before/after), then classify: +1. **For each marker**, read context (10 lines before/after), then classify: - **Resolve now** — small fix; do the work, remove the marker -- **File issue + remove marker** — significant work; create via the `add` action, remove the inline marker (do not leave `TODO` as a stand-in for the issue) +- **File a work item + remove marker** — significant work; create via the `add` action, remove the inline marker (do not leave `TODO` as a stand-in for the item) - **Remove (already done)** — work completed; delete the comment - **False positive** — structured grammar or external upstream citation misclassified; fix the exclusion if systemic, otherwise note it in the PR diff --git a/plugins/work-items/skills/work-items/actions/search.md b/plugins/work-items/skills/work-items/actions/search.md index aedf76e55..15fb626e4 100644 --- a/plugins/work-items/skills/work-items/actions/search.md +++ b/plugins/work-items/skills/work-items/actions/search.md @@ -5,28 +5,17 @@ Full-text search across work items (open and closed). ## Usage ``` -search "<query>" +/work-items search "<query>" ``` ## Workflow -1. **Search open issues** (read — bare `gh`; `--search` is the GitHub-search variant): +1. **Search open + closed items** using the adapter's search path (adapter: "Search items" — bare reads; run once for `--state open` and once for `--state closed` to show whether work was already done). -```bash -gh issue list --search "<query>" --state open --json number,title,state,labels,assignees --limit 20 | tr -d '\r' -``` - -1. **Search closed issues** (shows whether work was already done): - -```bash -gh issue list --search "<query>" --state closed --json number,title,state,labels,closedAt --limit 10 | tr -d '\r' -``` - -1. **Search the recurring schedule** (skip when the consuming repo has no `.github/recurring-schedule.json`): +1. **Search recurring schedule:** ```bash -SCHEDULE="${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel)}/.github/recurring-schedule.json" -cat "$SCHEDULE" | jq --arg q "<query>" ' +cat .github/recurring-schedule.json | jq --arg q "<query>" ' [.items[] | select( (.id | ascii_downcase | contains($q | ascii_downcase)) or (.title | ascii_downcase | contains($q | ascii_downcase)) or @@ -41,34 +30,27 @@ cat "$SCHEDULE" | jq --arg q "<query>" ' ```markdown ## Search: "{query}" -### Open Issues (X matches) +### Open Items (X matches) -| # | Issue | Labels | Assignee | -|---|-------|--------|----------| -| 1 | #42 Fix analyzer false positive | type:fix | @user | +| # | Item | Labels | Assignee | +|---|------|--------|----------| +| 1 | #42 Fix analyzer false positive | type:fix | @kyle | -### Closed Issues (X matches) +### Closed Items (X matches) -| # | Issue | Closed | -|---|-------|--------| -| 1 | #15 Review linter config | 2026-03-23 | +| # | Item | Closed | +|---|------|--------| +| 1 | #15 Review .editorconfig | 2026-03-23 | ### Recurring Schedule (X matches) | # | Item | Cadence | Next Due | |---|------|---------|----------| -| 1 | Review linter config | quarterly | 2026-06-21 | +| 1 | Review biome.json | quarterly | 2026-06-21 | ``` -If no matches anywhere: "No issues matching '{query}' in open, closed, or recurring." - -## Search Syntax +If no matches anywhere: "No items matching '{query}' in open, closed, or recurring." -The `--search` flag passes through to GitHub's search. Advanced syntax: +## Search syntax -- `label:type:chore` -- filter by label -- `-label:stale` -- exclude label -- `no:assignee` -- unassigned only -- `sort:updated-desc` -- sort by last update -- `created:>2026-01-01` -- date filtering -- `"exact phrase"` -- exact match in title/body +The provider's search qualifiers (label / exclude / assignee / sort / date / exact-phrase) are documented in the bound adapter's operations reference — GitHub: `tools/work-item-tracker/adapters/github/README.md` "Search items". diff --git a/plugins/work-items/skills/work-items/actions/start.md b/plugins/work-items/skills/work-items/actions/start.md index 08ac53ac4..efa95a981 100644 --- a/plugins/work-items/skills/work-items/actions/start.md +++ b/plugins/work-items/skills/work-items/actions/start.md @@ -1,74 +1,44 @@ # Action: `start` -Claim an issue by assigning yourself and adding the `status:claimed` label. +Claim a work item through the seam (assignee + lease record). ## Usage ``` -start <number or text match> +/work-items start <number or text match> ``` ## Workflow -1. **Resolve the issue.** If a number is given, use it directly. If text is given, search (read — bare `gh`): +1. **Resolve the item.** If a number is given, build its fully-qualified ID (adapter: "Resolve item ID"). If text is given, search for it (adapter: "Search items", bare read) — the search emits raw `gh` fields, so take the matched item's `number` and build its fully-qualified ID via "Resolve item ID" (the seam rejects a bare number). If multiple matches, present them and ask the user to clarify; if exactly one, proceed. -```bash -gh issue list --search "<text>" --state open --json number,title,assignees,labels --limit 10 | tr -d '\r' -``` - -If multiple matches, present them and ask the user to clarify. If exactly one match, proceed. - -1. **Pre-check.** Verify the issue isn't already claimed or held (read — bare `gh`): - -```bash -gh issue view <N> --json assignees,labels --jq '{assignees: [.assignees[].login], claimed: [.labels[].name] | any(. == "status:claimed"), considering: [.labels[].name] | any(. == "status:considering")}' | tr -d '\r' -``` - -If assignees are non-empty OR has `status:claimed`/`status:considering` label, warn: "Issue #N is already claimed/held by {assignee}. Proceed anyway? (yes / pick different)" +1. **Pre-check + reclaim.** Fetch current state, then clear any stale lease so a crashed session's claim is recoverable — `reclaim` is idempotent, so a live lease is left untouched (matches `work` Step 0): -1. **Hold.** Place a temporary hold before claiming. First run in a repo: ensure `status:considering` / `status:claimed` labels exist (see `work.md` step 3 — the hold write fails on an unknown label). Then (writes): - -```bash -gh issue edit <N> --add-label "status:considering" -gh issue comment <N> --body "<!-- hold:$(hostname):$(date +%s) --> -⏳ **Considering** — held by agent session -- **Host:** $(hostname) -- **Worktree:** $(git rev-parse --show-toplevel 2>/dev/null | xargs basename) -- **Branch:** $(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo 'unknown') -- **Time:** $(date -u +%Y-%m-%dT%H:%M:%SZ)" -``` - -1. **Verify hold.** Check for concurrent holds (see `work.md` step 4 for the full conflict resolution protocol using comment ID ordering). If another agent holds with a lower comment ID, release your hold and warn the user. + ```bash + tools/work-item-tracker/work-item-tracker.sh get-item "<id>" + tools/work-item-tracker/work-item-tracker.sh reclaim "<id>" + ``` -1. **Promote to claim.** The `--add-assignee "@me"` edit MUST run on the session identity (never a shared bot identity) so the post-claim collision check keeps working: + If the item is still assigned to another user after reclaim, its lease is live — warn: "Item `<id>` held by {assignee} (live lease). Proceed anyway? (yes / pick different)". Without the reclaim, `claim` would back off (exit 7) on the stale assignee before evaluating lease expiry. -```bash -# Replace considering with claimed, add assignee, release hold comment via PATCH (preserves audit trail) -gh issue edit <N> --remove-label "status:considering" --add-label "status:claimed" --add-assignee "@me" -gh api --method PATCH "repos/{owner}/{repo}/issues/comments/<HOLD_COMMENT_ID>" -f body="⏸ **Released** — hold lifted (reason: claim-promotion)" -gh issue comment <N> --body "🔒 **Claimed** by agent session -- **Host:** $(hostname) -- **Worktree:** $(git rev-parse --show-toplevel 2>/dev/null | xargs basename) -- **Branch:** $(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo 'unknown') -- **User:** $(gh api user --jq .login 2>/dev/null || echo 'unknown') -- **Time:** $(date -u +%Y-%m-%dT%H:%M:%SZ)" -``` +1. **Claim via the seam.** The `claim` verb runs the full race-safe, same-identity-aware protocol (assign `@me` → re-read → post lease comment → re-read leases → back off on a foreign earlier lease) and emits the claim object, or exits `7` on a lost race: -1. **Post-claim verification** — relies on distinct claimant identities, hence the session-identity `--add-assignee "@me"` above: + ```bash + tools/work-item-tracker/work-item-tracker.sh claim "<id>" + ``` -```bash -gh issue view <N> --json assignees --jq '[.assignees[].login]' | tr -d '\r' -``` + - Exit `0` — claim held; the emitted object carries `holder`, `lease_comment_id`, `acquired_at`, `ttl_hours`. Record `lease_comment_id` if you may renew later (`tools/work-item-tracker/work-item-tracker.sh renew-lease "<id>" --lease-comment-id <n>`). + - Exit `7` — another session won; report it and pick a different item (do NOT retry the same one). -If multiple assignees detected, order the collision by the 🔒 claim comments' server-assigned IDs (the assignee set is unordered): the higher-ID claimant is the later one and releases — remove ONLY your own assignee, never the issue-wide `status:claimed` label the winner still holds — and picks next. + Claim identity is the authenticated session user, never the bot (seam identity routing: `tools/work-item-tracker/CONTRACT.md` "Identity routing (GitHub adapter)"). -1. **Confirm:** "Claimed **#N**: {title}. Ready to work — follow the project's development workflow." +1. **Confirm:** "Claimed **`<id>`**: {title}. Ready to work — follow the project's development workflow." -1. **Suggest branch name.** Propose a branch that carries the issue number so PR tooling can auto-inject `Closes #N` from the branch parse. Emit the command for the user; never switch branches without explicit authorization. +1. **Suggest branch name.** Signal the closing-keyword link upstream so `/pull-request create` can auto-inject `Closes #N` from the branch parse. The agent NEVER runs `git checkout` itself; it emits the command for the user. - **Derive `<type>`** from issue labels by Conventional Commits priority — `feat > fix > refactor > docs > chore > test > build > perf`. First match wins; strip `type:` prefix. Default to `chore` if no `type:*` label present. + **Derive `<type>`** from item labels by Conventional Commits priority — `feat > fix > refactor > docs > chore > test > build > perf`. First match wins; strip `type:` prefix. Default to `chore` if no `type:*` label present. - **Derive `<slug>`** from issue title: lowercase, replace non-alphanumeric runs with `-`, trim leading/trailing `-`, cap 40 chars. + **Derive `<slug>`** from the item title: lowercase, replace non-alphanumeric runs with `-`, trim leading/trailing `-`, cap 40 chars. **Existing-branch check first** (skip prompt if branch already correct): @@ -82,17 +52,16 @@ If multiple assignees detected, order the collision by the 🔒 claim comments' - **`CURRENT_N` == claimed `<N>`** → acknowledge: "Already on `<current-branch>` — branch matches claimed #N. No rename needed." Skip prompt. Done. - **`CURRENT_N` is a different number** → multi-claim 3-option (below). - - **`CURRENT_N` empty** (no number on current branch) → present bare suggestion: "Suggest branch `<type>/<N>-<slug>`. Switch? (yes / no)". On `yes`, emit `git checkout -b <type>/<N>-<slug> origin/<default-branch>` for the user. On `no`, continue on the current branch — PR tooling can prompt for the closing keyword at PR time instead. + - **`CURRENT_N` empty** (no number on current branch) → present bare suggestion: "Suggest branch `<type>/<N>-<slug>`. Switch? (yes / no — orphan-PR path)". On `yes`, emit `git checkout -b <type>/<N>-<slug> origin/main` for the user. On `no`, continue on current branch — `/pull-request create` falls through to its interactive Closes-keyword prompt. - **Multi-claim 3-option** — when on `<other-type>/<OTHER>-<other-slug>` and just claimed #N (different issue): + **Multi-claim 3-option** — when on `<other-type>/<OTHER>-<other-slug>` and just claimed #N (different item): - 1. **Switch to `<type>/<N>-<slug>`** — WARN: uncommitted work on the current branch must be committed or stashed first (never stash a shared branch's work without confirming). Emit `git checkout -b <type>/<N>-<slug> origin/<default-branch>` for the user. - 2. **Stay on current branch and cover both in one PR** — inject `Closes #<OTHER>` + `Closes #<N>` into the PR body at PR time. - 3. **Skip** — decide later; continue on the current branch without rename. + 1. **Switch to `<type>/<N>-<slug>`** — WARN: uncommitted work on the current branch must be committed or stashed first; the agent never runs `git stash` on a shared branch without confirming. Emit `git checkout -b <type>/<N>-<slug> origin/main` for the user. + 1. **Stay on current branch and cover both in one PR** — `/pull-request create` will inject `Closes #<OTHER>` + `Closes #<N>` at PR-time via its multi-issue prompt. + 1. **Skip** — decide later; continue on current branch without rename. ## Notes -- In GitHub Actions context, replace `@me` with `$GITHUB_ACTOR` -- The hold→verify→claim protocol uses optimistic locking via GitHub comment IDs (monotonically increasing, server-assigned). Lowest comment ID wins ties -- If collision detected post-claim (two assignees), the second agent releases and picks next -- Stale holds (`status:considering` >15min) are detected by the `audit` action +- In GitHub Actions context, `@me` cannot resolve to a human — pass `--session-id "$GITHUB_ACTOR"` to `claim` for diagnostic attribution; the assignee is still the authenticated token identity. +- The seam claim replaces the retired `status:considering` / `status:claimed` label hold protocol — coordination is assignee + lease, race-safe via lease-comment identity (`tools/work-item-tracker/CONTRACT.md` "Lease protocol"). +- Stale claims (expired lease, no activity) are cleared by the `reclaim` verb at session start (`/work-items audit`, `/work-items work`). diff --git a/plugins/work-items/skills/work-items/actions/stats.md b/plugins/work-items/skills/work-items/actions/stats.md index 75c7599dd..7878e7ebf 100644 --- a/plugins/work-items/skills/work-items/actions/stats.md +++ b/plugins/work-items/skills/work-items/actions/stats.md @@ -1,33 +1,15 @@ # Action: `stats` -Present a dashboard summarizing the current state of the work-item tracker. +Present a dashboard summarizing the current state of work items. ## Workflow -1. **Fetch category counts** (read — bare `gh`): +1. **Fetch category counts** and **status/assignee counts** using the aggregation projections in the bound adapter's operations reference (GitHub: `tools/work-item-tracker/adapters/github/README.md` "Aggregate / count (dashboard + hygiene)" — bare reads). -```bash -gh issue list --state open --json labels --limit 500 --jq ' - [.[].labels[].name] | map(select(startswith("category:"))) | group_by(.) | map({key: .[0], count: length}) | sort_by(.key) -' | tr -d '\r' -``` - -1. **Fetch status counts:** - -```bash -gh issue list --state open --json labels,assignees --limit 500 --jq ' - { total: length, - considering: [.[] | select(any(.labels[]; .name == "status:considering"))] | length, - claimed: [.[] | select(any(.labels[]; .name == "status:claimed"))] | length, - unassigned: [.[] | select(.assignees | length == 0)] | length } -' | tr -d '\r' -``` - -1. **Check recurring due items** by reading `.github/recurring-schedule.json` (skip this step with a "no recurring schedule configured" note when the file is absent): +1. **Check recurring due items** by reading `.github/recurring-schedule.json`: ```bash -SCHEDULE="${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel)}/.github/recurring-schedule.json" -cat "$SCHEDULE" | jq --arg today "$(date +%Y-%m-%d)" ' +cat .github/recurring-schedule.json | jq --arg today "$(date +%Y-%m-%d)" ' [.items[] | select(.next_due != null and .next_due <= $today)] | length ' ``` @@ -35,21 +17,21 @@ cat "$SCHEDULE" | jq --arg today "$(date +%Y-%m-%d)" ' 1. **Present:** ```markdown -## Work-Item Dashboard +## Work Items Dashboard | Category | Open | |----------|------| -| category:<name> | X | -| (one row per category label in the repo) | | +| category:<your-category-1> | X | +| category:<your-category-2> | X | +| (one row per `category:` label the repo defines) | | | **Total** | **X** | -**Considering:** X issues (held by an agent via `status:considering`) -**Claimed:** X issues (assigned + `status:claimed`) -**Unassigned:** X issues (no assignee, available for pickup) -**Recurring due:** X items past their `next_due` date (use the `due` action to see them) +**Claimed:** X items (assigned — a seam claim is an assignee + lease) +**Unassigned:** X items (no assignee, available for pickup) +**Recurring due:** X items past their `next_due` date (use `/work-items due` to see them) ``` ## Notes -- If the repo has >100 open issues, the `--limit 500` cap means counts are approximate. Add a note: "Counts approximate. Use the `list` action with filters for the full set." -- For the category breakdown, issues with no `category:*` label are counted as "uncategorized." +- If the repo has >100 open items, the `--limit 500` aggregation cap means counts are approximate. Add a note: "Showing top 100. Use `/work-items list` with filters for the full set." +- For the category breakdown, items with no `category:*` label are counted as "uncategorized." diff --git a/plugins/work-items/skills/work-items/actions/triage.md b/plugins/work-items/skills/work-items/actions/triage.md index 2c77bc301..1b1c78b46 100644 --- a/plugins/work-items/skills/work-items/actions/triage.md +++ b/plugins/work-items/skills/work-items/actions/triage.md @@ -1,43 +1,29 @@ -# Action: `triage` +# Action: triage -Evaluate an incoming issue through a structured state-machine workflow: gather context → recommend category/state → reproduce (bugs) → interview (if needed) → apply outcome. +Evaluate an incoming work item through a structured state-machine workflow: gather context → recommend category/state → reproduce (bugs) → interview (if needed) → apply outcome. ## Usage ```text -triage <number> -triage # shows attention view (untriaged issues) +/work-items triage <number> +/work-items triage # shows attention view (untriaged items) ``` ## Attention view (no number) When invoked without a number, show three buckets (oldest first, one-line summaries): -1. **Unlabeled** — issues with zero labels +1. **Unlabeled** — items with zero labels 2. **`status:needs-triage`** — explicitly tagged for evaluation 3. **`status:needs-info` with reporter activity** — reporter replied since last triage note; ready for re-evaluation -```bash -gh issue list --state open --json number,title,labels,updatedAt --limit 100 | tr -d '\r' -``` - -Filter buckets 1-2 from that listing. Bucket 3 needs comment-level data (`updatedAt` moves on ANY activity, not just reporter replies) — for each `status:needs-info` candidate, fetch its comments and include it only when the LAST comment author is the reporter (issue author), not the triager: - -```bash -gh issue view <n> --json author,comments --jq '{reporter: .author.login, last: .comments[-1].author.login}' | tr -d '\r' -``` - -Present as compact table. (Reads — bare `gh`.) +List open items and filter into buckets programmatically (adapter: "List items", bare read). Present as a compact table. ## Triage workflow (with number) ### 1. Gather context -Read the issue body, comments, and any linked PRs (read — bare `gh`): - -```bash -gh issue view <n> --json number,title,body,labels,assignees,comments | tr -d '\r' -``` +Read the item body, comments, and any linked PRs (adapter: "View item", bare read). ### 2. Recommend category + state @@ -45,11 +31,11 @@ Based on content, recommend: - **Type label** (`type:feat`, `type:fix`, `type:chore`, etc.) - **Priority label** (`priority:p0-critical` through `priority:p3-low`) -- **State label** — initial recommendation from: `status:needs-info` or agent-ready (`agent-ready` meta label). Never apply `status:considering` during triage — that label is reserved for the hold→verify→claim protocol (an active hold with a session comment); using it as a triage state would make the issue look held and hide it from `work`/`start` selection +- **State label** — initial recommendation from: `status:needs-info`, `status:considering`, or agent-ready (`agent-ready` meta label) ### 3. Reproduce (bugs only) -For `type:fix` issues, attempt reproduction before interviewing: +For `type:fix` items, attempt reproduction before interviewing: - Run the described steps - Confirm the failure mode matches the report @@ -57,27 +43,29 @@ For `type:fix` issues, attempt reproduction before interviewing: ### 4. Interview (if needed) -When the issue description is vague or missing acceptance criteria, ask focused questions one at a time — resolve the most load-bearing ambiguity first. Post questions as issue comments. Mark `status:needs-info` until the reporter responds. +When the description is vague or missing acceptance criteria, ask focused questions one at a time — resolve the most load-bearing ambiguity first. Post questions as item comments. Mark `status:needs-info` until reporter responds. ### 5. Apply outcome | Outcome | Action | |---------|--------| -| Ready for agent work | Apply labels + write agent brief ([`../reference/agent-brief.md`](../reference/agent-brief.md)) + add `agent-ready` label | +| Ready for agent work | Apply labels + write agent brief (per [`reference/agent-brief.md`](../reference/agent-brief.md)) + add `agent-ready` label | | Ready for human work | Apply labels + brief summary comment | | Needs more info | `status:needs-info` + needs-info template comment | | Won't fix (enhancement) | Close with rationale comment | | Duplicate | Close with link to original | +Label edits, comments, and closes route through the adapter's write mechanics (adapter: "Edit labels / assignees", "Comment on item / edit a comment", "Close item"); the gather + attention-view reads are bare. + ## State transitions Valid state label transitions: ```text (unlabeled) → status:needs-triage -status:needs-triage → status:needs-info | agent-ready | ready (labels applied, no state label) | wontfix (close) +status:needs-triage → status:needs-info | status:considering | agent-ready | wontfix (close) status:needs-info → status:needs-triage (on reporter reply) -status:considering → status:claimed (hold protocol only — never set by triage) +status:considering → status:claimed | agent-ready | wontfix (close) status:claimed → (close on completion) ``` @@ -98,6 +86,6 @@ Preserves partial-triage work so reporter re-engagement does not restart from ze ## AI disclaimer -When creating comments or issues during autonomous/agent triage sessions, prefix with: +When creating comments or items during autonomous/agent triage sessions, prefix with: > *This was generated by AI during triage.* diff --git a/plugins/work-items/skills/work-items/actions/work.md b/plugins/work-items/skills/work-items/actions/work.md index a3e1bbc64..c03573bea 100644 --- a/plugins/work-items/skills/work-items/actions/work.md +++ b/plugins/work-items/skills/work-items/actions/work.md @@ -1,184 +1,103 @@ # Action: `work` -Auto-select one issue and execute it, following the consuming project's development workflow. +Auto-select one work item and execute it, following the project's development workflow. ## Usage ``` -work +/work-items work +``` + +## Step 0: Session-start reclaim (idempotent) + +Before selecting, clear stale claims left by crashed or abandoned sessions (an idempotent entry step). Enumerate currently-assigned items (adapter: "List items", assigned filter — the rows carry `number`), resolve each `number` to a fully-qualified id (adapter: "Resolve item ID"; `reclaim` rejects a bare number), and run the seam `reclaim` verb on each id — idempotent; outcome + activity-check semantics per `tools/work-item-tracker/CONTRACT.md` "Lease protocol". + +```bash +tools/work-item-tracker/work-item-tracker.sh reclaim "<id>" ``` ## Selection Priority -`work` evaluates these tiers top-down, only falling through to the next tier when the current one yields no candidates. Tiers flagged last-resort are skipped if any prior tier already yielded a candidate. Recurring tiers (1 and 4) apply only when the consuming repo has a `.github/recurring-schedule.json`. +`/work-items work` evaluates these tiers top-down, only falling through to the next tier when the current one yields no candidates. Tiers flagged last-resort are skipped if any prior tier already yielded a candidate. -1. **Due recurring items** — schedule items where `next_due <= today`, sorted by `next_due`. Schedule commitments take precedence over category flags; picking a recurring item early shifts its subsequent cadence and undermines the recurrence guarantee. +1. **Due recurring items** — `recurring-schedule`, where `next_due <= today`, sorted by `next_due`. Schedule commitments take precedence over category flags; picking a recurring item early shifts its subsequent cadence and undermines the recurrence guarantee. -2. **Non-recurring guardrails items** — issues labeled `category:guardrails` (when the repo uses that category), search `no:assignee -label:status:claimed -label:status:considering -label:recurring`. Force multipliers — each one completed makes ALL future autonomous work more reliable. Within this tier, prefer: enforcement mechanisms (CI/CD gates, architecture tests, hooks) > tool validation > research/planning. +2. **Non-recurring guardrails items** — the frontier (open ∧ unblocked ∧ unassigned) filtered to `category:guardrails` (when the repo uses that category), non-recurring. Force multipliers — each one completed makes ALL future autonomous work more reliable. Within this tier, prefer: enforcement mechanisms (CI/CD gates, architecture tests, hooks) > tool validation > research/planning. -3. **Highest-impact non-recurring unassigned items** — search `no:assignee -label:status:claimed -label:status:considering -label:recurring sort:created-asc`. Scan remaining open issues WITHOUT the recurring label. Select based on: items that unblock others, items in smaller categories, shorter well-scoped items over sprawling research epics. +3. **Highest-impact non-recurring unassigned items** — the remaining frontier, non-recurring, oldest-first. Select based on: items that unblock others, items in smaller categories, shorter well-scoped items over sprawling research epics. -4. **Recurring items not yet due** (last-resort) — schedule items where `next_due > today`, sorted by `next_due`. LAST RESORT only, when tiers 1–3 are empty. Picking a recurring item before its `next_due` shifts the cadence forward — avoid unless nothing else is available. Prefer items closest to `next_due` (least cadence disruption). +4. **Recurring items not yet due** (last-resort) — `recurring-schedule`, where `next_due > today`, sorted by `next_due`. LAST RESORT only, when tiers 1–3 are empty. Picking a recurring item before its `next_due` shifts the cadence forward — avoid unless nothing else is available. Prefer items closest to `next_due` (least cadence disruption). ## Workflow ### Step 1: Find candidates -For each tier in the Selection Priority list above, emit the corresponding query. Translation: +For each tier, emit the corresponding query: -- Recurring-schedule tiers → +- **Recurring tiers (1, 4):** filter the schedule locally: ```bash - # Root the path at the project root — a relative path breaks when invoked from a subdirectory. - SCHEDULE="${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel)}/.github/recurring-schedule.json" - cat "$SCHEDULE" | jq --arg today "$(date +%Y-%m-%d)" \ - '[.items[] | select(.next_due != null and .next_due <where_expr> $today)] | sort_by(.next_due)' + cat .github/recurring-schedule.json | jq --arg today "$(date +%Y-%m-%d)" \ + '[.items[] | select(.next_due != null and .next_due <where_expr> $today)] | sort_by(.<sort-by>)' ``` - where `<where_expr>` is `<=` (current/overdue) or `>` (not-yet-due) per the tier. + where `<where_expr>` is `<=` (current/overdue) or `>` (not-yet-due) per the tier's `where` field. -- Issue-list tiers (read — bare `gh`): +- **Frontier tiers (2, 3):** the seam derives the frontier (open ∧ `blocked_by_count == 0` ∧ unassigned); filter its output by the tier's category/recurring criteria core-side: ```bash - gh issue list \ - ${LABEL:+--label "$LABEL"} \ - --search "$SEARCH" \ - --state open \ - --json number,title,labels,assignees \ - --limit 20 \ - | tr -d '\r' + tools/work-item-tracker/work-item-tracker.sh list-frontier --autonomous ``` - where `LABEL` and `SEARCH` come from the tier's label filter and search expression. + `--autonomous` additionally excludes `needs-human` items. Tier 2 keeps only `category:guardrails` non-recurring; tier 3 keeps the rest. 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. -Tiers flagged last-resort are skipped if any prior tier yielded a candidate. +Tiers flagged `last-resort: true` are skipped if any prior tier yielded a candidate. -### Step 2: Cross-reference with open issues +### Step 2: Cross-reference with open items -For tier 1 and tier 4 (recurring candidates), cross-reference against open issues — the consuming repo's recurring-issues automation may have already created an issue (read — bare `gh`): +For tier 1 and tier 4 (recurring candidates), cross-reference against open items — the recurring-issues automation may have already created one (adapter: "List items", `--label recurring`). Pass an explicit `--limit` covering every open recurring issue: the default truncation would silently drop rows, so a bare read can miss an existing `[Maintenance]` issue and wrongly fall through to the create path below, duplicating it — page per the adapter "List items" note if they exceed the max page size. Match by title prefix `[Maintenance] {schedule item title}`. -```bash -gh issue list --label "recurring" --state open --json number,title --limit 100 | tr -d '\r' -``` - -Match by title prefix `[Maintenance] {schedule item title}`. - -**Due-recurring tier (`next_due <= today`):** if no open issue exists, create one before claiming — issue only, using the `[Maintenance] {title}` prefix plus the `recurring` and `cadence:{cadence}` labels (filtered through the live label list per `add.md` "Build labels array" — create the missing label or omit it). Do NOT route through `add --recurring`: the item already exists in the schedule, and that flow would append a duplicate schedule entry. These items are actionable now — dead-ending without an issue to hold would strand work. +**Due-recurring tiers (`where: 'next_due <= today'`):** if no open item exists, create one via the `add` action pattern before claiming. These items are actionable now — dead-ending without an item to hold would strand work. -**Last-resort recurring tier (`next_due > today`):** recurring automation typically only creates issues when `next_due <= today`, so there is usually no open issue to hold. Since picking early shifts the cadence and undermines the recurrence guarantee, **skip last-resort candidates that have no open issue and advance to the next candidate**. Only hold/claim a last-resort item when an open issue already exists (e.g., created manually ahead of cadence). If every last-resort candidate is skipped for lack of an issue, report "no actionable work" to the user rather than forcing one into existence. +**Last-resort recurring tiers (`last-resort: true` AND `where: 'next_due > today'`):** by design the consuming repo's recurring-issues automation typically creates items only when `next_due <= today`, so there is usually no open item to hold. Since picking early shifts the cadence, **skip last-resort candidates that have no open item and advance to the next candidate**. Only hold/claim a last-resort item when an open one already exists. If every last-resort candidate is skipped for lack of an item, report "no actionable work" rather than forcing one into existence. -### Step 3: Hold (immediate — before presenting) +### Step 3: Present and confirm -Place a hold on the candidate to prevent concurrent agents from selecting it. This MUST happen before presenting to the user. First run in a repo: ensure the protocol's status labels exist — the hold write fails on an unknown label: +Because the frontier is already unassigned + unblocked, present the top candidate directly — no pre-hold is needed (the seam `claim` in Step 4 is the atomic acquisition point): -```bash -EXISTING=$(gh label list --limit 200 --json name --jq '.[].name' | tr -d '\r') -grep -qxF "status:considering" <<<"$EXISTING" || gh label create "status:considering" --description "Held by an agent evaluating the item" -grep -qxF "status:claimed" <<<"$EXISTING" || gh label create "status:claimed" --description "Claimed by a work session" ``` - -Then hold (writes): - -```bash -# Add considering label -gh issue edit <N> --add-label "status:considering" - -# Add hold comment with session metadata for identification and ordering -gh issue comment <N> --body "<!-- hold:$(hostname):$(date +%s) --> -⏳ **Considering** — held by agent session -- **Host:** $(hostname) -- **Worktree:** $(git rev-parse --show-toplevel 2>/dev/null | xargs basename) -- **Branch:** $(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo 'unknown') -- **Time:** $(date -u +%Y-%m-%dT%H:%M:%SZ)" -``` - -### Step 4: Verify hold (conflict detection) - -Check whether another agent also placed a hold concurrently. GitHub comment IDs are monotonically increasing — lowest ID wins ties (read — bare `gh`): - -```bash -# List all hold comments on this issue -gh api --paginate "repos/{owner}/{repo}/issues/<N>/comments?per_page=100" --jq '[.[] | select(.body | startswith("<!-- hold:")) | {id, user: .user.login, created_at}] | sort_by(.id)' | tr -d '\r' -``` - -**Conflict resolution:** - -- **One hold comment (yours):** proceed to step 5 -- **Multiple hold comments:** compare comment IDs. If yours is the **lowest ID**, you win — proceed. If not: - 1. Release your hold comment via PATCH (preserves audit trail): `gh api --method PATCH "repos/{owner}/{repo}/issues/comments/<YOUR_COMMENT_ID>" -f body="⏸ **Released** — hold lifted (reason: conflict)"` - 2. Remove the label (only if no other holds remain): `gh issue edit <N> --remove-label "status:considering"` - 3. Pick the next candidate from step 1 (skip this issue) - -Also verify no concurrent claim happened during your hold (read — bare `gh`): - -```bash -gh issue view <N> --json assignees,labels --jq '{assignees: [.assignees[].login], claimed: [.labels[].name] | any(. == "status:claimed")}' | tr -d '\r' -``` - -If already claimed or assigned, release your hold and pick next. - -### Step 5: Staleness pre-check - -Before presenting the item, verify it's still actionable: - -- If the issue references a file to modify: check if the file exists and the issue is still relevant -- If the issue references a test to add: check if similar tests already exist -- If stale (work already done): release the hold (PATCH the hold comment to released state with reason: `stale`, remove label), close the issue with a comment, and pick the next one - -### Step 6: Present and confirm - -``` -**Auto-selected (<tier name>):** #42 Fix <thing> -Labels: type:fix, category:<name>, area:<name> +**Auto-selected (<tier-name>):** #42 Fix <thing> +Labels: type:fix, category:<your-category>, area:<your-area> Proceed with this item? (yes / pick different / skip) ``` -**On "pick different" or "skip":** release the hold before proceeding: +### Step 4: Staleness pre-check -```bash -# Release hold comment via PATCH (preserves audit trail) -gh api --method PATCH "repos/{owner}/{repo}/issues/comments/<HOLD_COMMENT_ID>" -f body="⏸ **Released** — hold lifted (reason: user-skip)" -# Remove considering label — ONLY if no other unreleased hold comments remain -# (re-run the step-4 hold listing first; another agent may have held meanwhile) -gh issue edit <N> --remove-label "status:considering" -``` +Before claiming, verify the item is still actionable: -### Step 7: Claim and execute - -On user confirmation ("yes"): +- 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. -1. **Promote hold to claim.** The `--add-assignee "@me"` edit MUST run on the session identity (never a shared bot identity) — the post-claim collision check below needs distinct claimant identities; routing every claim through one bot account would silently defeat it: +### Step 5: Claim and execute -```bash -# Replace considering with claimed, add assignee -gh issue edit <N> --remove-label "status:considering" --add-label "status:claimed" --add-assignee "@me" - -# Release the hold comment via PATCH (preserves audit trail) and add a claim comment with full session metadata -gh api --method PATCH "repos/{owner}/{repo}/issues/comments/<HOLD_COMMENT_ID>" -f body="⏸ **Released** — hold lifted (reason: claim-promotion)" -gh issue comment <N> --body "🔒 **Claimed** by agent session -- **Host:** $(hostname) -- **Worktree:** $(git rev-parse --show-toplevel 2>/dev/null | xargs basename) -- **Branch:** $(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo 'unknown') -- **User:** $(gh api user --jq .login 2>/dev/null || echo 'unknown') -- **Time:** $(date -u +%Y-%m-%dT%H:%M:%SZ)" -``` +On user confirmation ("yes"): -1. **Post-claim verification** (defense in depth) — relies on distinct claimant identities in the assignee set: +1. **Claim via the seam** — the atomic, race-safe acquisition (assign `@me` → lease → back off on a foreign earlier lease): -```bash -# Verify you're the sole assignee -gh issue view <N> --json assignees --jq '[.assignees[].login]' | tr -d '\r' -``` + ```bash + tools/work-item-tracker/work-item-tracker.sh claim "<id>" + ``` -If multiple assignees detected, order the collision by the claim COMMENTS (same server-assigned comment-ID ordering as holds — the assignee set itself is unordered): the claimant whose 🔒 claim comment has the higher ID is the later one and releases — remove ONLY your own assignee (`--remove-assignee`), never the issue-wide `status:claimed` label, which the winning claimant still holds — then pick next. + `<id>` MUST be fully-qualified (`claim` rejects a bare number): frontier candidates (tiers 2/3) already carry it from `list-frontier`; a recurring candidate matched to an open item by `number` (Step 2) is first qualified via adapter "Resolve item ID". Exit `0` → claim held. Exit `7` → another session won: advance to the next candidate (do NOT retry the same item). Claim identity is the authenticated session user, never the bot. -1. **Suggest branch name.** Propose `<type>/<N>-<slug>` so PR tooling can auto-inject `Closes #N` from the branch parse. Same protocol as `start.md` "Workflow" final step (Suggest branch name) — type derivation by Conventional Commits priority, slug from title (kebab-case, 40-char cap), existing-branch detection, multi-claim 3-option (switch / stay+cover-both / skip). See `start.md` for the full logic. Emit `git checkout -b ...` for the user unless the session has explicit branching authorization. +1. **Suggest branch name.** Propose `<type>/<N>-<slug>` so `/pull-request create` can auto-inject `Closes #N` from the branch parse. Same protocol as `start.md` "Workflow" final step — type derivation by Conventional Commits priority, slug from title (kebab-case, 40-char cap), existing-branch detection, multi-claim 3-option (switch / stay+cover-both / skip). Agent emits `git checkout -b ...` for the user; never executes itself. -1. **Execute the project's development workflow.** When the consuming project defines a workflow (a workflow skill, a CLAUDE.md workflow section, or team convention), follow every step of it — no shortcuts, no skipping research, no surface-level execution; read the project's rules for the item's domain first. When no workflow is defined, follow the generic sequence: explore → plan → implement → test → review → PR. +1. **Execute the project's development workflow:** the agent MUST follow every step — no shortcuts, no skipping research, no surface-level execution. When the consuming project defines a workflow (a workflow skill, a CLAUDE.md workflow section, or team convention), follow every step of it and read the project's rules for the item's domain first; otherwise follow the generic sequence: explore → plan → implement → test → review → PR. 1. **On completion:** run the `done` action (one-off items) or `recheck` action (recurring items). ## Bug Investigation Rule -Reproduce the reported failure FIRST. Never close a bug issue without either reproducing and fixing it, or proving via git history why the reporter saw the failure and why it no longer applies. +Reproduce the reported failure FIRST. Never close a bug item without either reproducing and fixing it, or proving via git history why the reporter saw the failure and why it no longer applies. diff --git a/plugins/work-items/skills/work-items/evals/evals.json b/plugins/work-items/skills/work-items/evals/evals.json index edb385b75..056459f6d 100644 --- a/plugins/work-items/skills/work-items/evals/evals.json +++ b/plugins/work-items/skills/work-items/evals/evals.json @@ -4,57 +4,44 @@ { "id": 1, "name": "empty-args-routes-to-stats-dashboard", - "prompt": "/work-items:work-items", - "expected_output": "Empty args route to the stats action (not a missing-action error). Emits the work-item dashboard: open and claimed issue counts, overdue recurring items (or a no-schedule note), and a category breakdown. Read-only — creates or modifies nothing.", + "prompt": "/work-items", + "expected_output": "Empty args route to the stats action (not a missing-action error). Emits the work-item dashboard: open and claimed counts, overdue recurring items, and a category/area breakdown. Read-only — creates or modifies nothing.", "files": [], "expectations": [ "Routes empty `$ARGUMENTS` to the `stats` action — does NOT error on a missing/unknown action", - "Reports both open-issue and claimed-issue counts", - "Surfaces recurring items past their `next_due` date, or notes that no recurring schedule is configured when `.github/recurring-schedule.json` is absent", - "Includes a category breakdown of open work", - "Performs no write — no issue created, closed, labeled, or assigned" + "Reports both open-item and claimed-item counts", + "Surfaces recurring items past their `next_due` date (overdue)", + "Includes a category/area breakdown of open work", + "Performs no write — no item created, closed, labeled, or assigned" ] }, { "id": 2, - "name": "work-auto-selects-one-issue-via-claim-protocol", - "prompt": "/work-items:work-items work", - "expected_output": "Auto-selects exactly ONE issue and acquires it through the three-phase hold->verify->claim optimistic-lock protocol before executing it via the consuming project's development workflow. The claim assignment runs on the session identity (`gh ... --add-assignee \"@me\"`), never a shared bot identity.", + "name": "work-auto-selects-one-item-via-seam-claim", + "prompt": "/work-items work", + "expected_output": "Reclaims stale leases at session start (idempotent), auto-selects exactly ONE item from the frontier (open, unblocked, unassigned) via the seam list-frontier, then acquires it through the seam claim verb (assignee + lease record, race-safe) before executing it via the full 8-step workflow. The claim identity is the authenticated session user, never the bot.", "files": [], "expectations": [ - "Selects exactly ONE issue to work — never claims multiple at once", - "Runs the three phases in order: hold (`status:considering` + hold comment) -> verify (lowest server-assigned comment ID wins ties) -> claim (`status:claimed` + assignee)", - "Claim assignment uses `gh ... --add-assignee \"@me\"` on the session identity — never a shared bot identity, which would defeat the collision check", - "Post-claim verification handles simultaneous-promotion collisions (later claimant releases)", - "Executes the selected issue through the consuming project's development workflow when one is defined, else the generic explore -> plan -> implement -> test -> review -> PR sequence" + "Runs an idempotent session-start reclaim (`work-item-tracker.sh reclaim`) before selecting — never releases a live lease", + "Selects exactly ONE item to work — never claims multiple at once", + "Selects from the frontier via `work-item-tracker.sh list-frontier` (open, `blocked_by_count == 0`, unassigned); `--autonomous` additionally excludes `needs-human`", + "Acquires via the seam `work-item-tracker.sh claim` (assignee + lease comment, same-identity aware) — NOT a `status:considering`/`status:claimed` label hold protocol", + "On a lost race (`claim` exit 7) advances to the next candidate rather than retrying the same item", + "Executes the selected item through the 8-step workflow (/explore -> /research -> /architect -> /implement -> /test-plan -> /quality-gate -> /verify-changes outcome -> /retro)" ] }, { "id": 3, "name": "decompose-vertical-slices-hitl-afk-dependency-order", - "prompt": "/work-items:work-items decompose", - "expected_output": "Reads the source plan/PRD, drafts thin tracer-bullet vertical slices cutting through all layers, classifies each AFK (labeled agent-ready) vs HITL (no agent-ready label), orders by dependency, gets user approval, then publishes blockers-first without touching the parent issue.", + "prompt": "/work-items decompose", + "expected_output": "Reads the source (default .work/<slug>/PLAN.md phases), drafts thin tracer-bullet vertical slices cutting through all layers, classifies each AFK (labeled agent-ready) vs HITL (labeled needs-human), orders by dependency, gets user approval, then publishes blockers-first via the seam create-item with native --blocked-by edges without touching the parent item.", "files": [], "expectations": [ "Drafts VERTICAL tracer-bullet slices (each a complete end-to-end path through all layers), NOT horizontal single-layer slices", - "Classifies each slice AFK (`agent-ready` label) vs HITL (no `agent-ready` label); prefers AFK, marks HITL only for genuine human-judgment / design-review / manual-QA needs", - "Presents the breakdown for approval with per-slice Type + Blocked-by + scope, iterating with the user before creating anything", - "Publishes issues in dependency order (blockers first) so real numbers fill the Blocked-by fields", - "Does NOT close or modify the parent issue" - ] - }, - { - "id": 4, - "name": "add-gates-runs-preflight-and-applies-defaults", - "prompt": "/work-items:work-items add \"retry logic drops the final attempt when the backoff cap is hit\" --type fix", - "expected_output": "Treats the explicit add invocation as authorization, runs the duplicate pre-flight search before creating, and creates ONE issue with the type label plus the default priority:p3-low, applying category:general only if that label exists in the repo.", - "files": [], - "expectations": [ - "Treats the explicit user `add` invocation as the authorization to create (no extra confirmation demanded, no silent skip)", - "Runs the duplicate pre-flight (`gh issue list --search ... --state all`) BEFORE creating, and pauses to present any similar match", - "Creates exactly ONE issue with `--label type:fix` and the default `--label priority:p3-low` (priority flag absent)", - "Applies `category:general` only when the repo defines that label — never passes a label the repo lacks", - "Body follows the default skeleton (Context / Proposed work / Acceptance criteria / References) since `--body` was not provided" + "Classifies each slice AFK (`agent-ready` label) vs HITL (`needs-human` label — the label `list-frontier --autonomous` honors to exclude an item from autonomous pickup; omitting `agent-ready` alone does NOT); prefers AFK, marks HITL only for genuine human-judgment / design-review / manual-QA needs", + "Presents the breakdown for approval with per-slice Type + Blocked-by + scope, iterating one question at a time with the user before creating anything", + "Publishes items in dependency order (blockers first) via `work-item-tracker.sh create-item`, recording dependencies as native `--blocked-by` edges", + "Does NOT close or modify the parent item" ] } ] diff --git a/plugins/work-items/skills/work-items/reference/agent-brief.md b/plugins/work-items/skills/work-items/reference/agent-brief.md index beedc5d74..03ba0beab 100644 --- a/plugins/work-items/skills/work-items/reference/agent-brief.md +++ b/plugins/work-items/skills/work-items/reference/agent-brief.md @@ -27,7 +27,7 @@ Describe **what** the system should do, not **how** to implement it. The agent e The agent needs to know when it's done. Every criterion should be independently verifiable. -- **Good:** "Running the test suite passes with the new validator active" +- **Good:** "Running `dotnet test` passes with the new validator active" - **Bad:** "Feature should work correctly" ### Explicit scope boundaries @@ -69,8 +69,8 @@ Be specific about edge cases and error conditions. Apply this template when: -- Issue receives the `agent-ready` meta label -- Issue is intended for AFK agent execution (scheduled agents, autonomous loops) +- Issue receives `agent-ready` meta label +- Issue is intended for AFK agent execution (`/schedule`, `/loop`, Codex) - Issue body is vague and needs structuring for autonomous execution The brief can be the issue body itself or posted as a comment (prefixed with `## Agent Brief` heading so agents can locate it). diff --git a/plugins/work-items/skills/work-items/reference/label-taxonomy.md b/plugins/work-items/skills/work-items/reference/label-taxonomy.md index cd83a1577..80d8273c7 100644 --- a/plugins/work-items/skills/work-items/reference/label-taxonomy.md +++ b/plugins/work-items/skills/work-items/reference/label-taxonomy.md @@ -1,6 +1,6 @@ # Label taxonomy -The label prefix structure consumed by every action that creates, queries, or filters issues. UNIVERSAL groups work in any repo; PROJECT-SPECIFIC groups carry the consuming repo's concrete values. +The label prefix structure consumed by every action that creates, queries, or filters work items. UNIVERSAL groups work in any repo; PROJECT-SPECIFIC groups carry the consuming repo's concrete values. When no taxonomy enforcement is desired, actions accept any label without a prefix check. By default, actions validate labels against the groups below. @@ -18,11 +18,7 @@ These groups work in any repo and don't change per team. ## Project-specific groups -The consuming repo defines the members of these groups to match its own architecture surface, domain categorization, and language/toolchain mix. Discover the live set: - -```bash -gh label list --limit 200 --json name --jq '[.[].name | select(startswith("area:") or startswith("category:") or startswith("ecosystem:"))]' | tr -d '\r' -``` +The consuming repo defines the members of these groups to match its own architecture surface, domain categorization, and language/toolchain mix. Discover the live set from the bound adapter's label listing (for the GitHub adapter, `tools/work-item-tracker/adapters/github/README.md` — e.g. `gh label list`). | Group | Prefix | What it encodes | |-------|--------|-----------------| @@ -30,4 +26,4 @@ gh label list --limit 200 --json name --jq '[.[].name | select(startswith("area: | Category | `category:` | Domain categorization of the work (e.g. guardrails, testing, general) | | Ecosystem | `ecosystem:` | Language/toolchain (e.g. dotnet, python, typescript, bash) | -When a project-specific group has no labels in the consuming repo, actions simply omit that group — no validation error. To adopt a group, create its labels once (`gh label create "area:<name>"`) and list the members in the consuming project's own rules if agents should prefer specific values. +When a project-specific group has no labels in the consuming repo, actions simply omit that group — no validation error. To adopt a group, create its labels once through the bound adapter and list the members in the consuming project's own rules if agents should prefer specific values. diff --git a/plugins/work-items/skills/work-items/templates/checklist.md b/plugins/work-items/skills/work-items/templates/checklist.md index b5960dcd0..3b28e9310 100644 --- a/plugins/work-items/skills/work-items/templates/checklist.md +++ b/plugins/work-items/skills/work-items/templates/checklist.md @@ -1,32 +1,34 @@ -# Work-items Checklist +# /work-items Checklist -Copy the section matching the action you're running into your session task list (or the consuming project's working-notes convention). Per-action checklists below. +Copy into `.work/<slug>/work-items-checklist.md`. Per-action checklists below — copy only the section matching the action you're running. ## Action: work (most common — full workflow per item) -- [ ] Claim — hold→verify→claim protocol, ending in `gh issue edit <N> --add-assignee @me` -- [ ] Branch — `git checkout -b <type>/<N>-<short-slug>` from the default branch -- [ ] Execute the project's development workflow (or explore → plan → implement → test → review → PR) -- [ ] Close — `gh issue close <N> --comment '<closing rationale>'` after PR merges (or via PR body `Closes #N` auto-close) +- [ ] Session-start reclaim — `tools/work-item-tracker/work-item-tracker.sh reclaim "<id>"` over assigned items (idempotent) +- [ ] Claim — `tools/work-item-tracker/work-item-tracker.sh claim "<id>"` (exit 7 = lost race, pick next) +- [ ] Branch — `git checkout -b <type>/<N>-<short-slug>` from origin/main +- [ ] Run `/workflow` chain — copy the checklist `/workflow` emits into this slice's PLAN.md +- [ ] Close — `/work-items done <N>` after PR merges (or via PR body `Closes #N` auto-close) ## Action: add -- [ ] Pre-flight: `gh issue list --state all --search '<key-term> in:title'` — pivot if open/closed match exists +- [ ] Pre-flight: search-before-create (adapter: "Search items", `--state all`) — pivot if open/closed match exists - [ ] Compose title (`<type>: <description>`); body; labels -- [ ] `gh issue create --title '...' --body '...' --label '...'` -- [ ] Capture issue number for cross-reference +- [ ] `tools/work-item-tracker/work-item-tracker.sh create-item --title '...' --body '...' --labels '...'` +- [ ] Capture item ID/number for cross-reference ## Action: start -- [ ] Pick item — `list --label '<label>'` OR `due` -- [ ] Claim via hold→verify→claim, ending in `gh issue edit <N> --add-assignee @me` +- [ ] Pick item — `/work-items list --label '<label>'` OR `/work-items due` +- [ ] Pre-check + reclaim — `tools/work-item-tracker/work-item-tracker.sh reclaim "<id>"` (idempotent; recovers a crashed session's stale lease so `claim` doesn't back off on the stale assignee) +- [ ] Claim via `tools/work-item-tracker/work-item-tracker.sh claim "<id>"` - [ ] Chain to `work` action ## Action: done -- [ ] Confirm the work is complete (PR merged, or no PR applies) — for recurring items use `recheck` instead -- [ ] `gh issue close <N> --comment '<summary>'` (or rely on PR `Closes #N` auto-close); `--reason "not planned"` when closing as superseded/rejected -- [ ] Remove `status:claimed`; comment with merge SHA + learnings pointer if applicable +- [ ] Verify PR merged +- [ ] `/work-items done <N>` (or rely on PR `Closes #N` auto-close) +- [ ] Comment with merge SHA + retro pointer if applicable ## Action: stats / list / search / scan / audit @@ -34,10 +36,14 @@ Copy the section matching the action you're running into your session task list ## Action: recheck -- [ ] Find the schedule item; complete the periodic check itself -- [ ] Update `last_checked` (always) and `next_due` (only if past due) -- [ ] Close the associated open issue with a recheck comment +- [ ] Search items with `recheck:` body lines older than threshold +- [ ] For each, evaluate trigger; close-with-comment OR re-open per outcome +- [ ] Update item body with re-check timestamp ## Skip criteria - `add` pre-flight SKIPPED only in tightly-scoped automation where duplicate-risk explicitly assessed; ad-hoc invocations always pre-flight + +## How to use + +Same shape as the `/workflow` checklist template ("How to use"). Copy only the per-action section relevant to your invocation. From 77008926b569bae87013953c9ede6fd2a2ce626e Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Sun, 12 Jul 2026 15:51:35 -0400 Subject: [PATCH 2/5] @ chore(work-items): genericize example usernames in skill docs De-couple prose-level examples (@kyle -> @agent1) to match the generic placeholders used elsewhere in the skill. Refs melodic-software/medley#1340 @ --- plugins/work-items/skills/work-items/actions/search.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/work-items/skills/work-items/actions/search.md b/plugins/work-items/skills/work-items/actions/search.md index 15fb626e4..ee63d8a62 100644 --- a/plugins/work-items/skills/work-items/actions/search.md +++ b/plugins/work-items/skills/work-items/actions/search.md @@ -34,7 +34,7 @@ cat .github/recurring-schedule.json | jq --arg q "<query>" ' | # | Item | Labels | Assignee | |---|------|--------|----------| -| 1 | #42 Fix analyzer false positive | type:fix | @kyle | +| 1 | #42 Fix analyzer false positive | type:fix | @agent1 | ### Closed Items (X matches) From 22b27a28efff018001ac440358b3ab5f7efe5e17 Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Sun, 12 Jul 2026 16:50:20 -0400 Subject: [PATCH 3/5] fix(work-items): address Codex publish-review findings - Shell-safe body on create-item: write the composed body to a temp file via the Write tool and pass it argv-safe (the file content is one literal argument, never re-parsed) instead of inlining generated text that could contain quotes, backticks, or command substitution. Restores v1's body-file safety since the seam create-item takes no body-file flag. - Optional recurring schedule now degrades gracefully: guard every .github/recurring-schedule.json read with an existence check and root it at the project directory across stats/done/search/audit/work/due/recheck, so the default stats dashboard (and other actions) no longer fail in repos without recurring setup or when invoked from a subdirectory. - Qualify command references to the installed plugin form /work-items:work-items <action> (the bare form does not route in plugin form; matches the plugin README). Refs melodic-software/medley#1340 --- plugins/work-items/skills/work-items/SKILL.md | 18 +++++++------- .../skills/work-items/actions/add.md | 12 ++++++---- .../skills/work-items/actions/audit.md | 9 +++---- .../skills/work-items/actions/decompose.md | 6 ++--- .../skills/work-items/actions/done.md | 21 +++++++++------- .../skills/work-items/actions/due.md | 24 ++++++++++++------- .../skills/work-items/actions/list.md | 2 +- .../skills/work-items/actions/recheck.md | 7 +++--- .../skills/work-items/actions/search.md | 9 +++---- .../skills/work-items/actions/start.md | 4 ++-- .../skills/work-items/actions/stats.md | 16 ++++++++----- .../skills/work-items/actions/triage.md | 4 ++-- .../skills/work-items/actions/work.md | 9 +++---- .../skills/work-items/evals/evals.json | 4 ++-- .../skills/work-items/templates/checklist.md | 8 +++---- 15 files changed, 88 insertions(+), 65 deletions(-) diff --git a/plugins/work-items/skills/work-items/SKILL.md b/plugins/work-items/skills/work-items/SKILL.md index e855b585d..d7d061c49 100644 --- a/plugins/work-items/skills/work-items/SKILL.md +++ b/plugins/work-items/skills/work-items/SKILL.md @@ -1,6 +1,6 @@ --- name: work-items -description: "Manage development work items through the bound tracker (work-item-tracker seam). Actions: stats, list, add, work, start, done, due, recheck, search, scan, audit, decompose, triage. Use when: 'add a work item', 'add an issue', 'pick work', 'close a work item', 'list work items', 'what's due', 'work-item stats', 'search work items', 'scan TODOs', 'audit claims', 'break a plan into tickets', 'decompose into tickets', 'create issues from plan', 'triage', 'what needs triage', 'check overdue recurring items'. Covers codebase TODO/FIXME scanning, plan decomposition into vertical-slice tickets, stale-claim auditing, and recurring schedule checks. Not for new bug reports — use /bug-report:bug-report first (read-only report), then chain to /work-items add via --context if filing is needed." +description: "Manage development work items through the bound tracker (work-item-tracker seam). Actions: stats, list, add, work, start, done, due, recheck, search, scan, audit, decompose, triage. Use when: 'add a work item', 'add an issue', 'pick work', 'close a work item', 'list work items', 'what's due', 'work-item stats', 'search work items', 'scan TODOs', 'audit claims', 'break a plan into tickets', 'decompose into tickets', 'create issues from plan', 'triage', 'what needs triage', 'check overdue recurring items'. Covers codebase TODO/FIXME scanning, plan decomposition into vertical-slice tickets, stale-claim auditing, and recurring schedule checks. Not for new bug reports — use /bug-report:bug-report first (read-only report), then chain to /work-items:work-items add via --context if filing is needed." argument-hint: "<action> [args] — actions: stats, list, add, work, start, done, due, recheck, search, scan, audit, decompose, triage (default: stats)" user-invocable: true disable-model-invocation: false @@ -16,7 +16,7 @@ This skill manages **development work items** — maintenance tasks, feature req **Provider-neutral over the seam.** Every tracker operation goes through the work-item-tracker seam — the skill calls `tools/work-item-tracker/work-item-tracker.sh <verb>` and the bound provider adapter executes it (contract: `tools/work-item-tracker/CONTRACT.md`). The repo's active provider is bound in `.work-item-tracker.json`. Coordination — create, claim (assignee + lease), lease renew/reclaim, dependency links, sub-items, frontier selection, single-item fetch — uses seam verbs directly. Operations without a core verb (listing with arbitrary filters, search, aggregation, close, label/comment edits) are provider-specific; for the bound GitHub adapter their mechanics live in `tools/work-item-tracker/adapters/github/README.md`. The skill core stays provider-portable and inlines no provider commands. -**Default = fix, not file.** Do NOT reflexively suggest `/work-items add` or `/work-items scan` for small / medium drift discovered while working. Boy Scout scope (cosmetic, stale counts, broken links, single-line corrections, one-paragraph clarifications) belongs in the current change, not the tracker. File NEW items only when the work is genuinely orthogonal to the current session, large enough to need its own `/architect` plan, or needs research the current session isn't positioned to do. Auto-suggesting `add` for fixable scope is the failure mode this rule prevents. When in doubt, fix in-place and surface what was fixed in the commit message / PR description. +**Default = fix, not file.** Do NOT reflexively suggest `/work-items:work-items add` or `/work-items:work-items scan` for small / medium drift discovered while working. Boy Scout scope (cosmetic, stale counts, broken links, single-line corrections, one-paragraph clarifications) belongs in the current change, not the tracker. File NEW items only when the work is genuinely orthogonal to the current session, large enough to need its own `/architect` plan, or needs research the current session isn't positioned to do. Auto-suggesting `add` for fixable scope is the failure mode this rule prevents. When in doubt, fix in-place and surface what was fixed in the commit message / PR description. **Label taxonomy.** Work items use an 8-group label prefix structure — UNIVERSAL groups (work in any repo) plus REPO-SPECIFIC groups carrying this repo's concrete values. The full member list (including this repo's populated `area:` / `category:` / `ecosystem:` / `cadence:` values) lives in [`reference/label-taxonomy.md`](reference/label-taxonomy.md). @@ -31,7 +31,7 @@ This skill manages **development work items** — maintenance tasks, feature req | Ecosystem | `ecosystem:` | repo-specific | the consuming repo's language/toolchain mix — see `reference/label-taxonomy.md` | | Cadence | `cadence:` | repo-specific | e.g. `cadence:weekly`, `cadence:monthly` — full set in `reference/label-taxonomy.md` | -**Recurring schedule.** Recurring items are defined in `.github/recurring-schedule.json` and created as items by the consuming repo's recurring-issues automation when they come due. The `/work-items recheck` action updates this schedule after completing a periodic check. +**Recurring schedule.** Recurring items are defined in `.github/recurring-schedule.json` and created as items by the consuming repo's recurring-issues automation when they come due. The `/work-items:work-items recheck` action updates this schedule after completing a periodic check. ## Emit checklist @@ -79,17 +79,17 @@ Coordination claims are race-safe at the seam (assignee + lease comment; `tools/ ### With `/workflow` -The project's development workflow — a `/workflow` skill, a CLAUDE.md workflow section, or team convention — applies to every item worked via `/work-items work`; the `work` action chains its full step sequence. +The project's development workflow — a `/workflow` skill, a CLAUDE.md workflow section, or team convention — applies to every item worked via `/work-items:work-items work`; the `work` action chains its full step sequence. ### With `/retro` -The retrospective skill's Phase 3 surfaces "Issue candidates" -- deferred research, discovered gaps, recurring recheck updates. Approved items use `/work-items add`. Mid-session learnings can be captured with `/retro codify`. +The retrospective skill's Phase 3 surfaces "Issue candidates" -- deferred research, discovered gaps, recurring recheck updates. Approved items use `/work-items:work-items add`. Mid-session learnings can be captured with `/retro codify`. ### With `/pull-request` -Branch name `<type>/<N>-<short-slug>` (proposed by `/work-items start` / `/work-items work`) carries the item number forward. `/pull-request create` parses the branch name and injects the closing keyword into the PR body; the pre-create gate verifies the keyword (or an opt-out marker) is present before creating the PR. Closing-keyword shape and PR body shape are owned by `/pull-request`. +Branch name `<type>/<N>-<short-slug>` (proposed by `/work-items:work-items start` / `/work-items:work-items work`) carries the item number forward. `/pull-request create` parses the branch name and injects the closing keyword into the PR body; the pre-create gate verifies the keyword (or an opt-out marker) is present before creating the PR. Closing-keyword shape and PR body shape are owned by `/pull-request`. -`/work-items done --pr <N>` is the belt-and-suspenders path for manual PR flows where `/pull-request create` was not used: it verifies keyword presence on the unmerged PR body or falls back to closing the item when the PR has already merged (mechanics: the GitHub adapter README "PR closing-keyword mechanics"). +`/work-items:work-items done --pr <N>` is the belt-and-suspenders path for manual PR flows where `/pull-request create` was not used: it verifies keyword presence on the unmerged PR body or falls back to closing the item when the PR has already merged (mechanics: the GitHub adapter README "PR closing-keyword mechanics"). ### With autonomous agents @@ -97,7 +97,7 @@ Items labeled `agent-ready` with no assignee are available for autonomous agent ### End-of-session check -At end of session, alongside `/retro`, check `/work-items due` to see if any recurring items need attention. +At end of session, alongside `/retro`, check `/work-items:work-items due` to see if any recurring items need attention. --- @@ -106,7 +106,7 @@ At end of session, alongside `/retro`, check `/work-items due` to see if any rec Skill-behavior failure patterns. Add to this section when new gotchas are discovered. Provider-mechanic gotchas (Windows `\r`, search-qualifier syntax, the `gh` 30-row default limit, `--add-label` vs `--label`, `--reason` values, rate limits, Issue-Forms auto-labeling) live in the bound adapter's operations reference — for GitHub, `tools/work-item-tracker/adapters/github/README.md` "Gotchas". - **Claim concurrency is the seam's job.** Claiming is race-safe at the seam (assignee + lease comment, same-identity aware) — `tools/work-item-tracker/CONTRACT.md` "Lease protocol". Reclaim runs idempotently at session start (`work` / `start`). Do not hand-roll a label-based hold protocol. -- **Recurring schedule is in `.github/`, not the skill directory.** The schedule file is `.github/recurring-schedule.json`. It's version-controlled and shared. The consuming repo's recurring-issues automation reads it; the `/work-items recheck` action updates it. +- **Recurring schedule is in `.github/`, not the skill directory.** The schedule file is `.github/recurring-schedule.json`. It's version-controlled and shared. The consuming repo's recurring-issues automation reads it; the `/work-items:work-items recheck` action updates it. ## What this skill does NOT do diff --git a/plugins/work-items/skills/work-items/actions/add.md b/plugins/work-items/skills/work-items/actions/add.md index af4f47e86..42fc2bd5e 100644 --- a/plugins/work-items/skills/work-items/actions/add.md +++ b/plugins/work-items/skills/work-items/actions/add.md @@ -11,7 +11,7 @@ Create a new work item with labels from the taxonomy. ## Usage ``` -/work-items add [--category <name>] [--type <type>] [--area <area>] [--ecosystem <eco>] [--priority <p>] [--recurring --cadence <cadence>] [--context "summary"] "Item description" +/work-items:work-items add [--category <name>] [--type <type>] [--area <area>] [--ecosystem <eco>] [--priority <p>] [--recurring --cadence <cadence>] [--context "summary"] "Item description" ``` ## Flags @@ -29,7 +29,7 @@ Create a new work item with labels from the taxonomy. ## Workflow -> **Authorization gate (BEFORE any step below).** Never file a work item on inferred intent. A topic the user raised, "they'd want it tracked", or approval of a related *direction* is NOT authorization to create an outward-facing artifact — those need explicit authorization. An explicit user `/work-items add ...` invocation IS the authorization; model-initiated filing is not. If you only *infer* an item should exist: draft the title + body, ASK first, OR write a local `.work/<slug>/` note instead. +> **Authorization gate (BEFORE any step below).** Never file a work item on inferred intent. A topic the user raised, "they'd want it tracked", or approval of a related *direction* is NOT authorization to create an outward-facing artifact — those need explicit authorization. An explicit user `/work-items:work-items add ...` invocation IS the authorization; model-initiated filing is not. If you only *infer* an item should exist: draft the title + body, ASK first, OR write a local `.work/<slug>/` note instead. 1. Parse the item text and flags from arguments. @@ -57,13 +57,17 @@ Create a new work item with labels from the taxonomy. {if --recurring: ## Recurring\n\nCadence: {cadence}\nTriggers: {triggers or "none configured"}} ``` -1. **Create the item** via the seam (`create-item` routes the write through the adapter's identity policy). If `--recurring`, prefix the title with `[Maintenance]` to match the convention used by the recurring-issues automation (enables dedup and `recheck` matching): +1. **Create the item** via the seam (`create-item` routes the write through the adapter's identity policy). If `--recurring`, prefix the title with `[Maintenance]` to match the convention used by the recurring-issues automation (enables dedup and `recheck` matching). Write the composed body to a temp file with the Write tool and pass it argv-safe — **never** inline the generated body, which can contain quotes, backticks, or `$()` the shell would interpret before the seam sees it: ```bash +# Write the composed body to $BODY_FILE with the Write tool (not shell interpolation). +# "$(cat "$BODY_FILE")" passes the file content as one literal argument — its contents are never re-parsed. +BODY_FILE=$(mktemp) tools/work-item-tracker/work-item-tracker.sh create-item \ --title "[Maintenance] {title}" \ - --body "{body}" \ + --body "$(cat "$BODY_FILE")" \ --labels "type:chore,category:general" +rm -f "$BODY_FILE" ``` For non-recurring items, omit the `[Maintenance]` prefix. The emitted item object carries the new `id` (fully-qualified) and `number`. diff --git a/plugins/work-items/skills/work-items/actions/audit.md b/plugins/work-items/skills/work-items/actions/audit.md index eb9a98ad4..88d282a9a 100644 --- a/plugins/work-items/skills/work-items/actions/audit.md +++ b/plugins/work-items/skills/work-items/actions/audit.md @@ -5,7 +5,7 @@ Detect stale claims, orphaned recurring entries, and label hygiene issues. ## Usage ``` -/work-items audit +/work-items:work-items audit ``` ## Checks @@ -22,10 +22,11 @@ Present each item the verb reports `reclaimed: true` (released — the `reason` ### 2. Orphaned recurring entries -Entries in `.github/recurring-schedule.json` with no corresponding open or recently-closed item: +Entries in `.github/recurring-schedule.json` with no corresponding open or recently-closed item (skip when the repo has no recurring schedule): ```bash -cat .github/recurring-schedule.json | jq -r '.items[].title' +SCHEDULE="${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel)}/.github/recurring-schedule.json" +[[ -f "$SCHEDULE" ]] && jq -r '.items[].title' "$SCHEDULE" ``` List open recurring items (adapter: "List items", `--label recurring`, `--state all`, bare read) and cross-reference: schedule items without a matching item are orphaned. The recurring workflow titles items `[Maintenance] {title}`, so strip the prefix when comparing. @@ -63,4 +64,4 @@ Items missing expected labels (no `type:*`, no `category:*`) and items with conf ## Notes - The `reclaim` verb never releases a live lease — a session actively working an item is safe. -- Run periodically (weekly) or before `/work-items work` (which also reclaims at session start) to keep the tracker clean. +- Run periodically (weekly) or before `/work-items:work-items work` (which also reclaims at session start) to keep the tracker clean. diff --git a/plugins/work-items/skills/work-items/actions/decompose.md b/plugins/work-items/skills/work-items/actions/decompose.md index 24caca44e..3109f2b4a 100644 --- a/plugins/work-items/skills/work-items/actions/decompose.md +++ b/plugins/work-items/skills/work-items/actions/decompose.md @@ -5,7 +5,7 @@ Break a plan, spec, or PRD into independently-grabbable work items using vertica ## Usage ``` -/work-items decompose [source] +/work-items:work-items decompose [source] ``` `source` can be: @@ -43,7 +43,7 @@ Break into **tracer-bullet** items. Each item is a thin vertical slice cutting t Prefer AFK. Mark HITL only when the slice genuinely needs judgment (architectural decision, UX review, external-system access, manual QA). -`needs-human` is the label that keeps a slice out of autonomous pickup — `list-frontier --autonomous` excludes it (`tools/work-item-tracker/CONTRACT.md` "Verbs (core public surface)"). Merely omitting `agent-ready` does NOT: the frontier filter keys on the `needs-human` label, not on the absence of `agent-ready`, so an unlabeled HITL slice would still be claimable by `/work-items work`. `agent-ready` is the positive autonomous-pickup eligibility marker; the two labels gate different filters and an HITL slice wants `needs-human` set AND `agent-ready` omitted. +`needs-human` is the label that keeps a slice out of autonomous pickup — `list-frontier --autonomous` excludes it (`tools/work-item-tracker/CONTRACT.md` "Verbs (core public surface)"). Merely omitting `agent-ready` does NOT: the frontier filter keys on the `needs-human` label, not on the absence of `agent-ready`, so an unlabeled HITL slice would still be claimable by `/work-items:work-items work`. `agent-ready` is the positive autonomous-pickup eligibility marker; the two labels gate different filters and an HITL slice wants `needs-human` set AND `agent-ready` omitted. **Investigation tickets — decisions, not deliverables.** When the source still carries unresolved unknowns (open design questions, unvalidated approaches, fuzzy scope), emit **investigation tickets** alongside — or ahead of — build slices. An investigation ticket resolves ONE decision and records the resolution as a closing comment; it produces no production code. Type each by the skill that resolves it: @@ -86,7 +86,7 @@ Iterate one question at a time until the user approves — never publish an unap ### 4. Publish items -For each approved slice, create a work item via the seam (`tools/work-item-tracker/work-item-tracker.sh create-item`; `/work-items add` is the canonical creation path). **Publish in dependency order** — blockers first — so real IDs can fill the `--blocked-by` edges of dependents (native dependency edges, not just body text): +For each approved slice, create a work item via the seam (`tools/work-item-tracker/work-item-tracker.sh create-item`; `/work-items:work-items add` is the canonical creation path). **Publish in dependency order** — blockers first — so real IDs can fill the `--blocked-by` edges of dependents (native dependency edges, not just body text): ```bash # AFK slices get agent-ready (autonomous-pickup eligibility); HITL + investigation slices diff --git a/plugins/work-items/skills/work-items/actions/done.md b/plugins/work-items/skills/work-items/actions/done.md index 30cf79093..0d2b1c44b 100644 --- a/plugins/work-items/skills/work-items/actions/done.md +++ b/plugins/work-items/skills/work-items/actions/done.md @@ -5,7 +5,7 @@ Close a work item with a completion comment. ## Usage ``` -/work-items done <number or text match> [--summary "completion summary"] [--pr <number>] [--not-planned] +/work-items:work-items done <number or text match> [--summary "completion summary"] [--pr <number>] [--not-planned] ``` ## Flags @@ -18,16 +18,21 @@ Close a work item with a completion comment. 1. **Resolve the item.** If a number is given, use it directly. If text, search open items (adapter: "Search items"). -1. **Check if recurring.** Read `.github/recurring-schedule.json` and check if the item's title matches any recurring item. Items created by the recurring-issues automation have a `[Maintenance]` prefix, so strip it before comparing: +1. **Check if recurring.** Read `.github/recurring-schedule.json` and check if the item's title matches any recurring item. Items created by the recurring-issues automation have a `[Maintenance]` prefix, so strip it before comparing. Skip gracefully when the repo has no recurring schedule: ```bash -cat .github/recurring-schedule.json | jq --arg title "<item title>" ' - ($title | ltrimstr("[Maintenance] ")) as $stripped | - [.items[] | select(.title == $stripped or .title == $title or .id == "<kebab-id>")] | length -' +SCHEDULE="${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel)}/.github/recurring-schedule.json" +if [[ -f "$SCHEDULE" ]]; then + jq --arg title "<item title>" ' + ($title | ltrimstr("[Maintenance] ")) as $stripped | + [.items[] | select(.title == $stripped or .title == $title or .id == "<kebab-id>")] | length + ' "$SCHEDULE" +else + echo 0 # no recurring schedule configured +fi ``` -If it's a recurring item, warn: "This is a recurring item. Did you mean `/work-items recheck` instead?" Proceed only if the user confirms. +If it's a recurring item, warn: "This is a recurring item. Did you mean `/work-items:work-items recheck` instead?" Proceed only if the user confirms. 1. **Build the closing comment:** @@ -41,7 +46,7 @@ If it's a recurring item, warn: "This is a recurring item. Did you mean `/work-i The seam claim is a lease (assignee + lease comment), not a label — closing removes the item from the frontier, so no `status:*` label cleanup is part of this flow (the retired `status:claimed` label is handled by the label-reconciliation migration, not here). -1. **Belt-and-suspenders: verify PR body keyword presence.** Primary path is the `/pull-request create` §2.4.2 pre-create gate (covers all 9 closing keywords + opt-out markers). This step fires when `/work-items done` is invoked WITHOUT having gone through `/pull-request create` (rare — manual close path). Only runs when `--pr` is provided. +1. **Belt-and-suspenders: verify PR body keyword presence.** Primary path is the `/pull-request create` §2.4.2 pre-create gate (covers all 9 closing keywords + opt-out markers). This step fires when `/work-items:work-items done` is invoked WITHOUT having gone through `/pull-request create` (rare — manual close path). Only runs when `--pr` is provided. Apply the read-modify-write keyword check + prepend from the adapter "PR closing-keyword mechanics" section: if the (unmerged) PR body carries neither a closing keyword nor an opt-out marker, prepend `Closes #<N>`; if merged, the keyword can no longer auto-fire and Step 4's close is the only path. diff --git a/plugins/work-items/skills/work-items/actions/due.md b/plugins/work-items/skills/work-items/actions/due.md index 116b32c44..e1b4807e1 100644 --- a/plugins/work-items/skills/work-items/actions/due.md +++ b/plugins/work-items/skills/work-items/actions/due.md @@ -5,21 +5,26 @@ Show recurring items that are past their `next_due` date. ## Usage ``` -/work-items due +/work-items:work-items due ``` ## Workflow 1. **Read the recurring schedule:** -Read `.github/recurring-schedule.json` and filter items where `next_due <= today`. Use jq for the initial filter: +Read `.github/recurring-schedule.json` and filter items where `next_due <= today`. When the file is absent, report "no recurring schedule configured" and stop. Use jq for the initial filter: ```bash -cat .github/recurring-schedule.json | jq --arg today "$(date +%Y-%m-%d)" ' - [.items[] | select(.next_due != null and .next_due <= $today)] - | sort_by(.next_due) - | map({id, title, cadence, last_checked, next_due}) -' +SCHEDULE="${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel)}/.github/recurring-schedule.json" +if [[ -f "$SCHEDULE" ]]; then + jq --arg today "$(date +%Y-%m-%d)" ' + [.items[] | select(.next_due != null and .next_due <= $today)] + | sort_by(.next_due) + | map({id, title, cadence, last_checked, next_due}) + ' "$SCHEDULE" +else + echo "no recurring schedule configured" +fi ``` For days-overdue computation, calculate `(today - next_due)` in days. jq lacks date arithmetic, so compute this when presenting the table (parse the ISO dates and subtract). @@ -29,7 +34,8 @@ For days-overdue computation, calculate `(today - next_due)` in days. jq lacks d 1. **Check for orphaned entries.** Entries in `recurring-schedule.json` with no corresponding item file or open item: ```bash -cat .github/recurring-schedule.json | jq -r '.items[].id' +SCHEDULE="${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel)}/.github/recurring-schedule.json" +[[ -f "$SCHEDULE" ]] && jq -r '.items[].id' "$SCHEDULE" ``` 1. **Present:** @@ -50,7 +56,7 @@ If nothing is due: "All recurring items are current. Next due: **{item}** on **{ ## Notes -- Cadence is a minimum interval, not a lock. On-demand rechecks are always allowed via `/work-items recheck`. +- Cadence is a minimum interval, not a lock. On-demand rechecks are always allowed via `/work-items:work-items recheck`. - The `triggers` field in each schedule item lists external events that warrant early recheck regardless of cadence. - When the user mentions a trigger event (e.g., ".NET 10.1 shipped"), proactively suggest relevant rechecks even if they aren't technically due yet. diff --git a/plugins/work-items/skills/work-items/actions/list.md b/plugins/work-items/skills/work-items/actions/list.md index 93c883d9d..65c63c0ad 100644 --- a/plugins/work-items/skills/work-items/actions/list.md +++ b/plugins/work-items/skills/work-items/actions/list.md @@ -5,7 +5,7 @@ List work items with optional filtering. ## Usage ``` -/work-items list [--category <name>] [--label <name>] [--state <open|closed|all>] [--assignee <login>] [--limit <n>] [--search <query>] +/work-items:work-items list [--category <name>] [--label <name>] [--state <open|closed|all>] [--assignee <login>] [--limit <n>] [--search <query>] ``` ## Flags diff --git a/plugins/work-items/skills/work-items/actions/recheck.md b/plugins/work-items/skills/work-items/actions/recheck.md index df6cad205..75d5f9fa5 100644 --- a/plugins/work-items/skills/work-items/actions/recheck.md +++ b/plugins/work-items/skills/work-items/actions/recheck.md @@ -5,7 +5,7 @@ Update a recurring item's `last_checked` and `next_due` dates after completing a ## Usage ``` -/work-items recheck <text match or schedule ID> +/work-items:work-items recheck <text match or schedule ID> ``` ## Workflow @@ -13,9 +13,10 @@ Update a recurring item's `last_checked` and `next_due` dates after completing a 1. **Find the item in the recurring schedule:** ```bash -cat .github/recurring-schedule.json | jq --arg q "<query>" ' +SCHEDULE="${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel)}/.github/recurring-schedule.json" +[[ -f "$SCHEDULE" ]] && jq --arg q "<query>" ' .items[] | select(.id == $q or (.title | ascii_downcase | contains($q | ascii_downcase))) -' +' "$SCHEDULE" ``` If multiple matches, present them and ask the user to clarify. diff --git a/plugins/work-items/skills/work-items/actions/search.md b/plugins/work-items/skills/work-items/actions/search.md index ee63d8a62..287a58432 100644 --- a/plugins/work-items/skills/work-items/actions/search.md +++ b/plugins/work-items/skills/work-items/actions/search.md @@ -5,24 +5,25 @@ Full-text search across work items (open and closed). ## Usage ``` -/work-items search "<query>" +/work-items:work-items search "<query>" ``` ## Workflow 1. **Search open + closed items** using the adapter's search path (adapter: "Search items" — bare reads; run once for `--state open` and once for `--state closed` to show whether work was already done). -1. **Search recurring schedule:** +1. **Search recurring schedule** (skip gracefully when the repo has no recurring schedule): ```bash -cat .github/recurring-schedule.json | jq --arg q "<query>" ' +SCHEDULE="${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel)}/.github/recurring-schedule.json" +[[ -f "$SCHEDULE" ]] && jq --arg q "<query>" ' [.items[] | select( (.id | ascii_downcase | contains($q | ascii_downcase)) or (.title | ascii_downcase | contains($q | ascii_downcase)) or (.notes // "" | ascii_downcase | contains($q | ascii_downcase)) or ([.triggers[]? // empty | ascii_downcase | contains($q | ascii_downcase)] | any) )] -' +' "$SCHEDULE" ``` 1. **Present results grouped by source:** diff --git a/plugins/work-items/skills/work-items/actions/start.md b/plugins/work-items/skills/work-items/actions/start.md index efa95a981..7f545dc44 100644 --- a/plugins/work-items/skills/work-items/actions/start.md +++ b/plugins/work-items/skills/work-items/actions/start.md @@ -5,7 +5,7 @@ Claim a work item through the seam (assignee + lease record). ## Usage ``` -/work-items start <number or text match> +/work-items:work-items start <number or text match> ``` ## Workflow @@ -64,4 +64,4 @@ Claim a work item through the seam (assignee + lease record). - In GitHub Actions context, `@me` cannot resolve to a human — pass `--session-id "$GITHUB_ACTOR"` to `claim` for diagnostic attribution; the assignee is still the authenticated token identity. - The seam claim replaces the retired `status:considering` / `status:claimed` label hold protocol — coordination is assignee + lease, race-safe via lease-comment identity (`tools/work-item-tracker/CONTRACT.md` "Lease protocol"). -- Stale claims (expired lease, no activity) are cleared by the `reclaim` verb at session start (`/work-items audit`, `/work-items work`). +- Stale claims (expired lease, no activity) are cleared by the `reclaim` verb at session start (`/work-items:work-items audit`, `/work-items:work-items work`). diff --git a/plugins/work-items/skills/work-items/actions/stats.md b/plugins/work-items/skills/work-items/actions/stats.md index 7878e7ebf..2c81833e0 100644 --- a/plugins/work-items/skills/work-items/actions/stats.md +++ b/plugins/work-items/skills/work-items/actions/stats.md @@ -6,12 +6,16 @@ Present a dashboard summarizing the current state of work items. 1. **Fetch category counts** and **status/assignee counts** using the aggregation projections in the bound adapter's operations reference (GitHub: `tools/work-item-tracker/adapters/github/README.md` "Aggregate / count (dashboard + hygiene)" — bare reads). -1. **Check recurring due items** by reading `.github/recurring-schedule.json`: +1. **Check recurring due items** (optional — degrade gracefully when the consuming repo has no recurring schedule): ```bash -cat .github/recurring-schedule.json | jq --arg today "$(date +%Y-%m-%d)" ' - [.items[] | select(.next_due != null and .next_due <= $today)] | length -' +SCHEDULE="${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel)}/.github/recurring-schedule.json" +if [[ -f "$SCHEDULE" ]]; then + jq --arg today "$(date +%Y-%m-%d)" \ + '[.items[] | select(.next_due != null and .next_due <= $today)] | length' "$SCHEDULE" +else + echo 0 # no recurring schedule configured +fi ``` 1. **Present:** @@ -28,10 +32,10 @@ cat .github/recurring-schedule.json | jq --arg today "$(date +%Y-%m-%d)" ' **Claimed:** X items (assigned — a seam claim is an assignee + lease) **Unassigned:** X items (no assignee, available for pickup) -**Recurring due:** X items past their `next_due` date (use `/work-items due` to see them) +**Recurring due:** X items past their `next_due` date (use `/work-items:work-items due` to see them) ``` ## Notes -- If the repo has >100 open items, the `--limit 500` aggregation cap means counts are approximate. Add a note: "Showing top 100. Use `/work-items list` with filters for the full set." +- If the repo has >100 open items, the `--limit 500` aggregation cap means counts are approximate. Add a note: "Showing top 100. Use `/work-items:work-items list` with filters for the full set." - For the category breakdown, items with no `category:*` label are counted as "uncategorized." diff --git a/plugins/work-items/skills/work-items/actions/triage.md b/plugins/work-items/skills/work-items/actions/triage.md index 1b1c78b46..b1cd8a6f9 100644 --- a/plugins/work-items/skills/work-items/actions/triage.md +++ b/plugins/work-items/skills/work-items/actions/triage.md @@ -5,8 +5,8 @@ Evaluate an incoming work item through a structured state-machine workflow: gath ## Usage ```text -/work-items triage <number> -/work-items triage # shows attention view (untriaged items) +/work-items:work-items triage <number> +/work-items:work-items triage # shows attention view (untriaged items) ``` ## Attention view (no number) diff --git a/plugins/work-items/skills/work-items/actions/work.md b/plugins/work-items/skills/work-items/actions/work.md index c03573bea..6fcde243b 100644 --- a/plugins/work-items/skills/work-items/actions/work.md +++ b/plugins/work-items/skills/work-items/actions/work.md @@ -5,7 +5,7 @@ Auto-select one work item and execute it, following the project's development wo ## Usage ``` -/work-items work +/work-items:work-items work ``` ## Step 0: Session-start reclaim (idempotent) @@ -18,7 +18,7 @@ tools/work-item-tracker/work-item-tracker.sh reclaim "<id>" ## Selection Priority -`/work-items work` evaluates these tiers top-down, only falling through to the next tier when the current one yields no candidates. Tiers flagged last-resort are skipped if any prior tier already yielded a candidate. +`/work-items:work-items work` evaluates these tiers top-down, only falling through to the next tier when the current one yields no candidates. Tiers flagged last-resort are skipped if any prior tier already yielded a candidate. 1. **Due recurring items** — `recurring-schedule`, where `next_due <= today`, sorted by `next_due`. Schedule commitments take precedence over category flags; picking a recurring item early shifts its subsequent cadence and undermines the recurrence guarantee. @@ -37,8 +37,9 @@ For each tier, emit the corresponding query: - **Recurring tiers (1, 4):** filter the schedule locally: ```bash - cat .github/recurring-schedule.json | jq --arg today "$(date +%Y-%m-%d)" \ - '[.items[] | select(.next_due != null and .next_due <where_expr> $today)] | sort_by(.<sort-by>)' + SCHEDULE="${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel)}/.github/recurring-schedule.json" + jq --arg today "$(date +%Y-%m-%d)" \ + '[.items[] | select(.next_due != null and .next_due <where_expr> $today)] | sort_by(.<sort-by>)' "$SCHEDULE" ``` where `<where_expr>` is `<=` (current/overdue) or `>` (not-yet-due) per the tier's `where` field. diff --git a/plugins/work-items/skills/work-items/evals/evals.json b/plugins/work-items/skills/work-items/evals/evals.json index 056459f6d..a207a85bf 100644 --- a/plugins/work-items/skills/work-items/evals/evals.json +++ b/plugins/work-items/skills/work-items/evals/evals.json @@ -18,7 +18,7 @@ { "id": 2, "name": "work-auto-selects-one-item-via-seam-claim", - "prompt": "/work-items work", + "prompt": "/work-items:work-items work", "expected_output": "Reclaims stale leases at session start (idempotent), auto-selects exactly ONE item from the frontier (open, unblocked, unassigned) via the seam list-frontier, then acquires it through the seam claim verb (assignee + lease record, race-safe) before executing it via the full 8-step workflow. The claim identity is the authenticated session user, never the bot.", "files": [], "expectations": [ @@ -33,7 +33,7 @@ { "id": 3, "name": "decompose-vertical-slices-hitl-afk-dependency-order", - "prompt": "/work-items decompose", + "prompt": "/work-items:work-items decompose", "expected_output": "Reads the source (default .work/<slug>/PLAN.md phases), drafts thin tracer-bullet vertical slices cutting through all layers, classifies each AFK (labeled agent-ready) vs HITL (labeled needs-human), orders by dependency, gets user approval, then publishes blockers-first via the seam create-item with native --blocked-by edges without touching the parent item.", "files": [], "expectations": [ diff --git a/plugins/work-items/skills/work-items/templates/checklist.md b/plugins/work-items/skills/work-items/templates/checklist.md index 3b28e9310..b32d3c932 100644 --- a/plugins/work-items/skills/work-items/templates/checklist.md +++ b/plugins/work-items/skills/work-items/templates/checklist.md @@ -1,4 +1,4 @@ -# /work-items Checklist +# /work-items:work-items Checklist Copy into `.work/<slug>/work-items-checklist.md`. Per-action checklists below — copy only the section matching the action you're running. @@ -8,7 +8,7 @@ Copy into `.work/<slug>/work-items-checklist.md`. Per-action checklists below - [ ] Claim — `tools/work-item-tracker/work-item-tracker.sh claim "<id>"` (exit 7 = lost race, pick next) - [ ] Branch — `git checkout -b <type>/<N>-<short-slug>` from origin/main - [ ] Run `/workflow` chain — copy the checklist `/workflow` emits into this slice's PLAN.md -- [ ] Close — `/work-items done <N>` after PR merges (or via PR body `Closes #N` auto-close) +- [ ] Close — `/work-items:work-items done <N>` after PR merges (or via PR body `Closes #N` auto-close) ## Action: add @@ -19,7 +19,7 @@ Copy into `.work/<slug>/work-items-checklist.md`. Per-action checklists below ## Action: start -- [ ] Pick item — `/work-items list --label '<label>'` OR `/work-items due` +- [ ] Pick item — `/work-items:work-items list --label '<label>'` OR `/work-items:work-items due` - [ ] Pre-check + reclaim — `tools/work-item-tracker/work-item-tracker.sh reclaim "<id>"` (idempotent; recovers a crashed session's stale lease so `claim` doesn't back off on the stale assignee) - [ ] Claim via `tools/work-item-tracker/work-item-tracker.sh claim "<id>"` - [ ] Chain to `work` action @@ -27,7 +27,7 @@ Copy into `.work/<slug>/work-items-checklist.md`. Per-action checklists below ## Action: done - [ ] Verify PR merged -- [ ] `/work-items done <N>` (or rely on PR `Closes #N` auto-close) +- [ ] `/work-items:work-items done <N>` (or rely on PR `Closes #N` auto-close) - [ ] Comment with merge SHA + retro pointer if applicable ## Action: stats / list / search / scan / audit From 59d05a74cd2ef5b8101509a5b8f0dec9ebd2fec6 Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Sun, 12 Jul 2026 17:09:19 -0400 Subject: [PATCH 4/5] fix(work-items): address second Codex review round - decompose: route the generated slice body through the same temp-file / argv-safe path as add, so plan/PRD text containing backticks or command substitution cannot be shell-expanded when publishing slices. - work: guard the recurring-tier jq with an existence check so ordinary non-recurring pickup does not fail in repos without a recurring schedule (rooting alone still ran jq against a missing file). - add: the create example now passes the resolved label list built in the Build-labels step (including the default priority) instead of a hardcoded type:chore,category:general, so flagged/default taxonomy labels are applied. - due: the orphan pass now filters to due entries (next_due <= today) before flagging missing items, so a healthy future entry is not reported orphaned. - checklist: align the add reminder with the argv-safe body-file pattern. Refs melodic-software/medley#1340 --- plugins/work-items/skills/work-items/actions/add.md | 4 ++-- plugins/work-items/skills/work-items/actions/decompose.md | 7 ++++++- plugins/work-items/skills/work-items/actions/due.md | 5 +++-- plugins/work-items/skills/work-items/actions/work.md | 2 +- .../work-items/skills/work-items/templates/checklist.md | 4 ++-- 5 files changed, 14 insertions(+), 8 deletions(-) diff --git a/plugins/work-items/skills/work-items/actions/add.md b/plugins/work-items/skills/work-items/actions/add.md index 42fc2bd5e..fd944eb14 100644 --- a/plugins/work-items/skills/work-items/actions/add.md +++ b/plugins/work-items/skills/work-items/actions/add.md @@ -35,7 +35,7 @@ Create a new work item with labels from the taxonomy. 1. **Duplicate check** (skip if `--force`) — the search-before-create pre-flight (adapter: "Search items", `--state all`, bare read). If a potential duplicate is found (similar title), present it: "Similar item found: **#N {title}** ({state}). Add anyway, merge, or skip?" -1. **Build labels list** (comma-separated for the seam) from the flags, e.g. `type:chore,category:general` plus any `--type`/`--category`/`--area`/`--ecosystem`/`--priority` values. +1. **Build labels list** `{labels}` (comma-separated for the seam) from the flags. Start from the group defaults `type:chore,priority:p3-low,category:general` and replace each group's default with any supplied `--type`/`--priority`/`--category` value (one label per group); append `--area`/`--ecosystem` labels when provided. A default that the consuming repo doesn't define is omitted rather than passed. 1. **Build body.** If `--agent-ready`, use the agent-brief template from [`reference/agent-brief.md`](../reference/agent-brief.md) (Category, Summary, Current behavior, Desired behavior, Key interfaces, Acceptance criteria, Out of scope). Otherwise use the default template: @@ -66,7 +66,7 @@ BODY_FILE=$(mktemp) tools/work-item-tracker/work-item-tracker.sh create-item \ --title "[Maintenance] {title}" \ --body "$(cat "$BODY_FILE")" \ - --labels "type:chore,category:general" + --labels "{labels}" rm -f "$BODY_FILE" ``` diff --git a/plugins/work-items/skills/work-items/actions/decompose.md b/plugins/work-items/skills/work-items/actions/decompose.md index 3109f2b4a..ff46c25f6 100644 --- a/plugins/work-items/skills/work-items/actions/decompose.md +++ b/plugins/work-items/skills/work-items/actions/decompose.md @@ -93,9 +93,14 @@ For each approved slice, create a work item via the seam (`tools/work-item-track # get needs-human instead — the label list-frontier --autonomous actually honors to exclude # an item. Omitting agent-ready alone does NOT keep an HITL slice off the frontier. META_LABEL=$([ -n "$AFK" ] && echo "agent-ready" || echo "needs-human") -tools/work-item-tracker/work-item-tracker.sh create-item --title "<slice title>" --body "<body>" \ +# Write the composed slice body to $BODY_FILE with the Write tool (not shell interpolation) — plan/PRD +# text can contain backticks or $() the shell would interpret. "$(cat "$BODY_FILE")" passes it as one +# literal argument, never re-parsed. +BODY_FILE=$(mktemp) +tools/work-item-tracker/work-item-tracker.sh create-item --title "<slice title>" --body "$(cat "$BODY_FILE")" \ --labels "type:<t>,area:<a>,$META_LABEL" \ --blocked-by "<blocker-id>[,<blocker-id>]" +rm -f "$BODY_FILE" ``` Use agent-brief body format (see [`reference/agent-brief.md`](../reference/agent-brief.md)) for AFK slices. Body structure: diff --git a/plugins/work-items/skills/work-items/actions/due.md b/plugins/work-items/skills/work-items/actions/due.md index e1b4807e1..a6570ce7d 100644 --- a/plugins/work-items/skills/work-items/actions/due.md +++ b/plugins/work-items/skills/work-items/actions/due.md @@ -31,11 +31,12 @@ For days-overdue computation, calculate `(today - next_due)` in days. jq lacks d 1. **Cross-reference with open items.** For each due recurring item, check if one already exists (adapter: "List items", `--label recurring`, bare read). Match by title prefix `[Maintenance]` (the format used by the recurring-issues automation). -1. **Check for orphaned entries.** Entries in `recurring-schedule.json` with no corresponding item file or open item: +1. **Check for orphaned entries.** Only **due** entries can be orphaned — the recurring automation creates a tracker item only once an entry reaches `next_due <= today`, so a healthy future entry (`next_due > today`) legitimately has no open item and is NOT orphaned. Filter to due entries before flagging missing items: ```bash SCHEDULE="${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel)}/.github/recurring-schedule.json" -[[ -f "$SCHEDULE" ]] && jq -r '.items[].id' "$SCHEDULE" +[[ -f "$SCHEDULE" ]] && jq -r --arg today "$(date +%Y-%m-%d)" \ + '.items[] | select(.next_due != null and .next_due <= $today) | .id' "$SCHEDULE" ``` 1. **Present:** diff --git a/plugins/work-items/skills/work-items/actions/work.md b/plugins/work-items/skills/work-items/actions/work.md index 6fcde243b..ba7a06613 100644 --- a/plugins/work-items/skills/work-items/actions/work.md +++ b/plugins/work-items/skills/work-items/actions/work.md @@ -38,7 +38,7 @@ For each tier, emit the corresponding query: ```bash SCHEDULE="${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel)}/.github/recurring-schedule.json" - jq --arg today "$(date +%Y-%m-%d)" \ + [[ -f "$SCHEDULE" ]] && jq --arg today "$(date +%Y-%m-%d)" \ '[.items[] | select(.next_due != null and .next_due <where_expr> $today)] | sort_by(.<sort-by>)' "$SCHEDULE" ``` diff --git a/plugins/work-items/skills/work-items/templates/checklist.md b/plugins/work-items/skills/work-items/templates/checklist.md index b32d3c932..c2c786c3c 100644 --- a/plugins/work-items/skills/work-items/templates/checklist.md +++ b/plugins/work-items/skills/work-items/templates/checklist.md @@ -13,8 +13,8 @@ Copy into `.work/<slug>/work-items-checklist.md`. Per-action checklists below ## Action: add - [ ] Pre-flight: search-before-create (adapter: "Search items", `--state all`) — pivot if open/closed match exists -- [ ] Compose title (`<type>: <description>`); body; labels -- [ ] `tools/work-item-tracker/work-item-tracker.sh create-item --title '...' --body '...' --labels '...'` +- [ ] Compose title (`<type>: <description>`); write the body to a temp file with the Write tool (argv-safe — never inline generated text); build labels +- [ ] `tools/work-item-tracker/work-item-tracker.sh create-item --title '...' --body "$(cat "$BODY_FILE")" --labels '...'` - [ ] Capture item ID/number for cross-reference ## Action: start From 21737377abd5cd9265b6a5a5bada484acdba85c4 Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Sun, 12 Jul 2026 17:18:57 -0400 Subject: [PATCH 5/5] fix(work-items): filter audit orphan check to due recurring entries audit only flags a recurring entry as orphaned once next_due <= today, matching the due action, so healthy future entries are not false-flagged. Refs melodic-software/medley#1340 --- plugins/work-items/skills/work-items/actions/audit.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/work-items/skills/work-items/actions/audit.md b/plugins/work-items/skills/work-items/actions/audit.md index 88d282a9a..12c46f2e6 100644 --- a/plugins/work-items/skills/work-items/actions/audit.md +++ b/plugins/work-items/skills/work-items/actions/audit.md @@ -22,11 +22,12 @@ Present each item the verb reports `reclaimed: true` (released — the `reason` ### 2. Orphaned recurring entries -Entries in `.github/recurring-schedule.json` with no corresponding open or recently-closed item (skip when the repo has no recurring schedule): +Entries in `.github/recurring-schedule.json` with no corresponding open or recently-closed item (skip when the repo has no recurring schedule). Only **due** entries can be orphaned — the automation creates an item only once `next_due <= today`, so a healthy future entry legitimately has no open item and is NOT orphaned: ```bash SCHEDULE="${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel)}/.github/recurring-schedule.json" -[[ -f "$SCHEDULE" ]] && jq -r '.items[].title' "$SCHEDULE" +[[ -f "$SCHEDULE" ]] && jq -r --arg today "$(date +%Y-%m-%d)" \ + '.items[] | select(.next_due != null and .next_due <= $today) | .title' "$SCHEDULE" ``` List open recurring items (adapter: "List items", `--label recurring`, `--state all`, bare read) and cross-reference: schedule items without a matching item are orphaned. The recurring workflow titles items `[Maintenance] {title}`, so strip the prefix when comparing.