Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
bump: major
---

### Changed

- **Breaking:** the work-item tracker bundles only the `github` and `local-markdown` adapters. `jira`, `gitea` and `linear` now ship as seeds under `tools/work-item-tracker/seeds/`, and the consuming repo owns its copy. `/work-items:setup apply` runs the new `seed-copy.sh`, which copies the bound provider's seed into `<repo root>/tools/work-item-tracker/adapters/<provider>/` with a `.seed-hash` record. On a re-run it fast-forwards a copy nobody edited, and for an edited one it prints the diff and writes nothing. A repo already bound to one of the three gets exit `3` from the seam, naming the setup step, until it runs `/work-items:setup apply` once (#11873).
13 changes: 8 additions & 5 deletions plugins/work-items/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ about work items, tickets, issues, tracked work, or what to do next):
/work-items:ship # macro map over a spec container: status, shape, next step
/work-items:ship run #300 # run the container's ready items under its shape (attended)
/work-items:scan-todos # sweep TODO/FIXME/HACK markers
/work-items:onboard-adapter gitea # generate an adapter for an unbundled tracker
/work-items:onboard-adapter youtrack # generate an adapter for a tracker with no adapter or seed
```

## Skills
Expand All @@ -34,7 +34,7 @@ about work items, tickets, issues, tracked work, or what to do next):
| `/work-items:decompose` | Break a plan/PRD/item into vertical-slice items with AFK/HITL classification and dependency ordering. When a spec container is published and the design behind the plan produced typed artifacts (frontmatter carrying `scope` and `dialect`), each is inlined into the container body as a fenced block plus a one-line provenance note naming the scope and dialect, with no file path; an `integration` design labels two, and both are inlined. Slice bodies are unchanged. |
| `/work-items:ship` | Macro-journey router over one spec container: rollup + scoped frontier, the container's recorded execution shape (per-item PRs vs integration branch → single PR) with that mode's discipline, and the routed next step. `run` works the container's ready items in parallel through `/implementation:implement-dispatch` and lands them one at a time, attended only. Mechanics stay with their owners. |
| `/work-items:scan-todos` | Sweep the codebase for TODO/FIXME/HACK markers; resolve or file each. |
| `/work-items:onboard-adapter` | Onboard a tracker this plugin does not bundle: interview to lock the provider's shape, explore the consumer's real instance for the per-instance facts only it can settle, generate a consumer-owned adapter (hardened security skeleton, honest capability manifest, contract-fixed verb scaffolds, conformance binding) into the consuming repo, then verify. The tail half of the hybrid adapter model. Bundled adapters cover the majors. |
| `/work-items:onboard-adapter` | Onboard a tracker this plugin does not bundle: interview to lock the provider's shape, explore the consumer's real instance for the per-instance facts only it can settle, generate a consumer-owned adapter (hardened security skeleton, honest capability manifest, contract-fixed verb scaffolds, conformance binding) into the consuming repo, then verify. The tail half of the hybrid adapter model. Bundled adapters and seeds cover the majors. |
| `/work-items:setup` | `check` inspects the tracked `.github/recurring-schedule.json`, the jq/tracker-seam entry gates, and the recurring-maintenance role label read-only; `apply` binds the provider, writes the empty schedule skeleton, and offers the canonical-role → label remap in the tracker binding (re-runnable). Seeding actual rows, inferring candidate items from the repo and interviewing per item, is opt-in via `apply --seed-schedule` or an offer that recommends skipping; a schedule that already carries items is offered updates as before. |

## Naming
Expand Down Expand Up @@ -121,9 +121,12 @@ enough that one skill no longer predicts its contents.
correctness: when it is missing, stop and surface that remediation instead
of improvising a parse.
- **The work-item-tracker seam.** The plugin **ships** the seam under
`${CLAUDE_PLUGIN_ROOT}/tools/work-item-tracker/`: the dispatcher, `lib/`, and the `github`,
`local-markdown`, `jira`, `gitea`, and `linear` adapters. The consuming repo declares only its
active provider, in `.work-item-tracker.json` at the repo root. Run `/work-items:setup` to write
`${CLAUDE_PLUGIN_ROOT}/tools/work-item-tracker/`: the dispatcher, `lib/`, and two bundled
adapters, `github` and `local-markdown`. The `jira`, `gitea`, and `linear` adapters ship as
seeds under `seeds/`: `/work-items:setup apply` copies the bound one into the consuming repo,
which owns the copy from then on. On a re-run, setup fast-forwards a copy nobody edited and
stops with the diff on one somebody did. The consuming repo declares its
active provider in `.work-item-tracker.json` at the repo root. Run `/work-items:setup` to write
that file. A gitignored `.work-item-tracker.local.json` overlay beside it may carry the per-user
lease TTL and the per-provider auth identity: `auth_email` and `auth_env` for jira, `auth_env`
for linear, and `auth_env` for gitea. A repo
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
schema_version: "1.1"
name: oa-bundled-provider-routes-to-setup
description: 'Bundled-provider route: asked to generate an adapter for jira, which the plugin already bundles, the run says jira is bundled and routes to /work-items:setup instead of generating anything: no generator run, no adapter files written through Bash, and no Write or Edit call. A run needs --scaffold and --allow-tools Bash,Write,Edit.'
description: 'Shipped-provider route: asked to generate an adapter for jira, which the plugin already ships as a seed, the run says jira ships with the plugin and routes to /work-items:setup instead of generating anything: no generator run, no adapter files written through Bash, and no Write or Edit call. A run needs --scaffold and --allow-tools Bash,Write,Edit.'
tags: [onboard-adapter, routing, must-not]
plugins: ["../.."]
expected_outcome: 'The answer says jira is one of the providers the plugin bundles, so a consumer-owned adapter is not the path, and routes to /work-items:setup to bind it. It does not run generate-adapter.sh, does not write a spec or adapter files, and makes no Write or Edit call.'
expected_outcome: 'The answer says jira is one of the providers the plugin ships, so generating a consumer-owned adapter is not the path, and routes to /work-items:setup to bind it and copy its seed. It does not run generate-adapter.sh, does not write a spec or adapter files, and makes no Write or Edit call.'
context:
scaffold_script: scaffold.sh
execution:
Expand All @@ -19,7 +19,7 @@ graders:
target: last_message
- name: says-bundled
type: regex
pattern: '^(?=[^\n]*\bjira\b)(?![^\n]*\b(?:not|isn[''’]t|no longer|never)\s+(?:\w+\s+){0,2}?(?:bundled|supported|built[- ]in|included|shipped)\b)[^\n]*\b(?:bundled|bundles|built[- ]in|ships (?:with|an?)\b|already (?:supported|ships?|includes?|included|has|bundles?|provides?)|supported providers?|first-party|out of the box)'
pattern: '^(?=[^\n]*\bjira\b)(?![^\n]*\b(?:not|isn[''’]t|no longer|never)\s+(?:\w+\s+){0,2}?(?:bundled|supported|built[- ]in|included|shipped|seeded)\b)[^\n]*\b(?:bundled|bundles|built[- ]in|ships (?:with|an?|as)\b|already (?:supported|ships?|includes?|included|has|bundles?|provides?)|supported providers?|first-party|out of the box|(?:a|the|its) (?:jira )?seed\b)'
flags: 'mi'
target: last_message
- name: no-generate
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
{
"answer": "Jira is already supported out of the box.",
"why": "already supported"
},
{
"answer": "Jira ships as a seed that /work-items:setup copies into this repo.",
"why": "ships as a seed"
}
],
"fail": [
Expand Down
15 changes: 8 additions & 7 deletions plugins/work-items/skills/onboard-adapter/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
description: "Generate a consumer-owned adapter for a tracker this plugin does not bundle: interview for the provider's shape, probe the real instance, write the adapter and conformance binding into the consumer repo, then verify. Use when: 'add support for TRACKER', 'onboard a tracker', 'write a work-item adapter', 'generate a tracker adapter', 'my tracker is not supported', 'bring my own tracker'. Bundled providers (github, local-markdown, jira, gitea, linear) and provider switches: '/work-items:setup'."
description: "Generate a consumer-owned adapter for a tracker this plugin does not ship: interview for the provider's shape, probe the real instance, write the adapter and conformance binding into the consumer repo, then verify. Use when: 'add support for TRACKER', 'onboard a tracker', 'write a work-item adapter', 'generate a tracker adapter', 'my tracker is not supported', 'bring my own tracker'. Shipped providers (github, local-markdown, jira, gitea, linear) and provider switches: '/work-items:setup'."
argument-hint: "[provider-name]"
user-invocable: true
# Model-invoked (fleet default); no exception class applies. Generation is gated by the
Expand All @@ -19,9 +19,9 @@ Walk a **consumer** from "my tracker is not supported" to an adapter that lives
Adapters resolve consumer-local-first (`CONTRACT.md` "Adapter resolution"), so it needs no fork, no vendored
engine, and no upstream PR.

**Not for**: a provider already bundled (`github`, `local-markdown`, `jira`, `gitea`,
`linear`). Bind those with `/work-items:setup`, which also re-points a repo at a
different provider. Not for fixing an existing adapter (ordinary implementation work).
**Not for**: a provider the plugin already ships, bundled (`github`, `local-markdown`) or as a
seed (`jira`, `gitea`, `linear`). Bind those with `/work-items:setup`, which copies a seed into
the repo and also re-points a repo at a different provider. Not for fixing an existing adapter (ordinary implementation work).

## The split

Expand Down Expand Up @@ -71,7 +71,7 @@ carries a worked example.

## Step 2. Explore the live instance

Some facts live only in the user's actual instance (the bundled `jira` adapter's done-state
Some facts live only in the user's actual instance (the `jira` seed's done-state
category key and blocker link-type name vary per instance, so both are config keys with
defaults). For each field the normalizer will read, have the **user** run a read-only probe
against their instance and paste the response shape. Typical probes: fetch one item and
Expand Down Expand Up @@ -121,7 +121,8 @@ In this order, because each step's failure means something different:
2. **Fill the mappings, verb by verb.** Each generated verb script carries one
`PROVIDER MAPPING` block and exits `1` until it is written. Write a mocked-transport
test beside each (`WIT_<P>_CURL` is the injection point. Copy the shape from the
bundled `jira` adapter's `*.test.sh`) so the verb is covered offline.
`jira` seed's `*.test.sh` under the plugin's `tools/work-item-tracker/seeds/jira/`) so
the verb is covered offline.
3. **Conformance.** `run-conformance.sh --binding <p>`, which drives the same abstract
suite over the adapter through the core CLI only. It needs a **throwaway** target;
the generated binding refuses to run without one named explicitly. Never point it at
Expand All @@ -146,7 +147,7 @@ that does nothing.
## Security posture

The skeleton is **template-driven, not re-derived per provider**. It is generated already
carrying the same guards the bundled `jira` adapter carries, and the generated
carrying the same guards the `jira` seed carries, and the generated
`common.test.sh` proves them:

- Credential read from the env var *named by* the binding, never stored in the tracked
Expand Down
10 changes: 5 additions & 5 deletions plugins/work-items/skills/onboard-adapter/evals/evals.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,12 @@
"id": 5,
"name": "routes-a-bundled-provider-to-setup",
"prompt": "/work-items:onboard-adapter\n\nI want to use Jira with work-items.",
"expected_output": "Jira is already bundled, so this is a binding job, not a generation job. Routes to /work-items:setup to bind the provider in .work-item-tracker.json rather than generating a second, competing jira adapter into the consumer's repo.",
"expected_output": "Jira already ships with the plugin as a seed, so this is a binding job, not a generation job. Routes to /work-items:setup, which binds the provider in .work-item-tracker.json and copies the jira seed into the consumer's repo, rather than generating a second, competing jira adapter.",
"expectations": [
"Recognizes jira as an already-bundled adapter (alongside github and local-markdown)",
"Routes to /work-items:setup for the binding instead of generating",
"Does not generate a shadowing consumer-local adapter for a bundled provider without an explicit reason to shadow it",
"Explains the difference between binding a provider and onboarding an unbundled one"
"Recognizes jira as a provider the plugin already ships (a seed beside the bundled github and local-markdown)",
"Routes to /work-items:setup for the binding and seed copy instead of generating",
"Does not generate a consumer-local adapter for a shipped provider without an explicit reason to replace it",
"Explains the difference between binding a shipped provider and onboarding one the plugin does not ship"
]
}
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ The manifest is a promise the core routes on, so these are refusals, not warning
- `verbs["list-items"]` and `limits.list_items_max` must agree.
- `link-blocks: true` needs a non-zero `dependencies_per_type`.

One note, not a refusal: `list-items: false` is coherent, since the bundled `jira` adapter is
One note, not a refusal: `list-items: false` is coherent, since the `jira` seed is
consume-only, but `list-frontier` can then never succeed, so no work-selection flow will
find anything. The generator says so on stderr.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ wrong, because they are per-instance rather than per-product.
## Why this step exists

Some facts are per-instance rather than per-product, so the API documentation cannot settle them.
The bundled `jira` adapter carries two: the `statusCategory` key that means "done" (it defaults
The `jira` seed carries two: the `statusCategory` key that means "done" (it defaults
to both known keys and takes a config override) and the blocker link-type name
(instance-configurable). Both are config keys with documented defaults. That is the pattern to
reach for whenever a probe cannot be run: make the adapter *independent* of the fact rather than
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -552,9 +552,10 @@ DISPATCH_VERB_LIST="$({
assert_eq "the generator's verb set is non-empty" "true" "$([[ -n "$ADAPTER_VERB_LIST" ]] && echo true || echo false)"
assert_eq "ADAPTER_VERBS equals the dispatcher's verbs plus list-items" "$DISPATCH_VERB_LIST" "$ADAPTER_VERB_LIST"

for f in "$SEAM"/adapters/*/capabilities.json "$SEAM"/adapters/*/adapter-spec.json; do
for f in "$SEAM"/adapters/*/capabilities.json "$SEAM"/adapters/*/adapter-spec.json \
"$SEAM"/seeds/*/capabilities.json "$SEAM"/seeds/*/adapter-spec.json; do
[[ -f "$f" ]] || continue
label="${f#"$SEAM"/adapters/}"
label="${f#"$SEAM"/}"
assert_eq "$label carries exactly the adapter verbs" "$ADAPTER_VERB_LIST" \
"$(jq -r '.verbs | keys[]' "$f" | tr -d '\r' | sort)"
assert_eq "$label declares every verb as a boolean" "boolean" \
Expand Down
Loading
Loading