From 637b983df7caaf5493e30979ab04a33d41b3b07e Mon Sep 17 00:00:00 2001 From: "melodic-automation-lanes[bot]" <337595936+melodic-automation-lanes[bot]@users.noreply.github.com> Date: Sun, 1 Nov 2026 12:11:13 +0000 Subject: [PATCH] Resolve issue #11873 Lane-Activity: work-resolve-issue/resolve-issue --- ...work-resolve-issue-issue-11873-9c3e5a71.md | 7 + plugins/work-items/README.md | 13 +- .../case.yaml | 6 +- .../samples/says-bundled.json | 4 + .../skills/onboard-adapter/SKILL.md | 15 +- .../skills/onboard-adapter/evals/evals.json | 10 +- .../onboard-adapter/reference/adapter-spec.md | 2 +- .../reference/live-exploration.md | 2 +- .../scripts/generate-adapter.test.sh | 5 +- plugins/work-items/skills/setup/SKILL.md | 27 +++- .../skills/setup/reference/check.md | 6 +- .../skills/setup/reference/providers.md | 12 +- .../tools/work-item-tracker/CONTRACT.md | 26 +++- .../conformance/bindings/jira.test.sh | 4 +- .../tools/work-item-tracker/seed-copy.sh | 146 ++++++++++++++++++ .../tools/work-item-tracker/seed-copy.test.sh | 124 +++++++++++++++ .../tools/work-item-tracker/seeds/README.md | 22 +++ .../{adapters => seeds}/gitea/README.md | 0 .../gitea/adapter-spec.json | 0 .../gitea/capabilities.json | 0 .../{adapters => seeds}/gitea/capabilities.sh | 0 .../gitea/capabilities.test.sh | 0 .../{adapters => seeds}/gitea/change-link.sh | 0 .../gitea/change-link.test.sh | 0 .../{adapters => seeds}/gitea/common.sh | 0 .../{adapters => seeds}/gitea/common.test.sh | 0 .../{adapters => seeds}/gitea/create-item.sh | 0 .../gitea/create-item.test.sh | 0 .../{adapters => seeds}/gitea/get-item.sh | 0 .../gitea/get-item.test.sh | 0 .../{adapters => seeds}/gitea/link-blocks.sh | 0 .../gitea/link-blocks.test.sh | 0 .../{adapters => seeds}/gitea/list-items.sh | 0 .../gitea/list-items.test.sh | 2 +- .../{adapters => seeds}/gitea/mock.sh | 0 .../{adapters => seeds}/jira/README.md | 0 .../jira/capabilities.json | 0 .../{adapters => seeds}/jira/capabilities.sh | 0 .../jira/capabilities.test.sh | 0 .../{adapters => seeds}/jira/change-link.sh | 0 .../jira/change-link.test.sh | 0 .../{adapters => seeds}/jira/common.sh | 33 ++-- .../{adapters => seeds}/jira/common.test.sh | 0 .../{adapters => seeds}/jira/get-item.sh | 0 .../{adapters => seeds}/jira/get-item.test.sh | 0 .../{adapters => seeds}/jira/list-items.sh | 0 .../jira/list-items.test.sh | 2 +- .../{adapters => seeds}/jira/mock.sh | 0 .../{adapters => seeds}/linear/README.md | 0 .../linear/adapter-spec.json | 0 .../linear/add-sub-item.sh | 0 .../linear/add-sub-item.test.sh | 0 .../linear/capabilities.json | 0 .../linear/capabilities.sh | 0 .../linear/capabilities.test.sh | 0 .../{adapters => seeds}/linear/change-link.sh | 0 .../linear/change-link.test.sh | 0 .../{adapters => seeds}/linear/claim.sh | 0 .../{adapters => seeds}/linear/claim.test.sh | 0 .../{adapters => seeds}/linear/common.sh | 0 .../{adapters => seeds}/linear/common.test.sh | 0 .../{adapters => seeds}/linear/create-item.sh | 0 .../linear/create-item.test.sh | 0 .../{adapters => seeds}/linear/get-item.sh | 0 .../linear/get-item.test.sh | 0 .../{adapters => seeds}/linear/link-blocks.sh | 0 .../linear/link-blocks.test.sh | 0 .../{adapters => seeds}/linear/list-items.sh | 0 .../linear/list-items.test.sh | 2 +- .../linear/list-sub-items.sh | 0 .../linear/list-sub-items.test.sh | 0 .../{adapters => seeds}/linear/mock.sh | 0 .../{adapters => seeds}/linear/reclaim.sh | 0 .../linear/reclaim.test.sh | 0 .../{adapters => seeds}/linear/renew-lease.sh | 0 .../linear/renew-lease.test.sh | 0 .../linear/schema-check/.gitignore | 0 .../linear/schema-check/README.md | 2 +- .../linear/schema-check/fetch-schema.sh | 0 .../linear/schema-check/fidelity.sh | 0 .../linear/schema-check/negative.mjs | 0 .../linear/schema-check/validate.mjs | 0 .../work-item-tracker/work-item-tracker.sh | 4 + .../work-item-tracker.test.sh | 6 +- scripts/affected-tests-no-suite.txt | 2 +- scripts/em-dash-purged-paths.txt | 4 +- scripts/suite-seconds.txt | 43 +++--- 87 files changed, 440 insertions(+), 91 deletions(-) create mode 100644 .changes/work-items/work-resolve-issue-issue-11873-9c3e5a71.md create mode 100755 plugins/work-items/tools/work-item-tracker/seed-copy.sh create mode 100755 plugins/work-items/tools/work-item-tracker/seed-copy.test.sh create mode 100644 plugins/work-items/tools/work-item-tracker/seeds/README.md rename plugins/work-items/tools/work-item-tracker/{adapters => seeds}/gitea/README.md (100%) rename plugins/work-items/tools/work-item-tracker/{adapters => seeds}/gitea/adapter-spec.json (100%) rename plugins/work-items/tools/work-item-tracker/{adapters => seeds}/gitea/capabilities.json (100%) rename plugins/work-items/tools/work-item-tracker/{adapters => seeds}/gitea/capabilities.sh (100%) rename plugins/work-items/tools/work-item-tracker/{adapters => seeds}/gitea/capabilities.test.sh (100%) rename plugins/work-items/tools/work-item-tracker/{adapters => seeds}/gitea/change-link.sh (100%) rename plugins/work-items/tools/work-item-tracker/{adapters => seeds}/gitea/change-link.test.sh (100%) rename plugins/work-items/tools/work-item-tracker/{adapters => seeds}/gitea/common.sh (100%) mode change 100644 => 100755 rename plugins/work-items/tools/work-item-tracker/{adapters => seeds}/gitea/common.test.sh (100%) rename plugins/work-items/tools/work-item-tracker/{adapters => seeds}/gitea/create-item.sh (100%) rename plugins/work-items/tools/work-item-tracker/{adapters => seeds}/gitea/create-item.test.sh (100%) rename plugins/work-items/tools/work-item-tracker/{adapters => seeds}/gitea/get-item.sh (100%) rename plugins/work-items/tools/work-item-tracker/{adapters => seeds}/gitea/get-item.test.sh (100%) rename plugins/work-items/tools/work-item-tracker/{adapters => seeds}/gitea/link-blocks.sh (100%) rename plugins/work-items/tools/work-item-tracker/{adapters => seeds}/gitea/link-blocks.test.sh (100%) rename plugins/work-items/tools/work-item-tracker/{adapters => seeds}/gitea/list-items.sh (100%) rename plugins/work-items/tools/work-item-tracker/{adapters => seeds}/gitea/list-items.test.sh (99%) rename plugins/work-items/tools/work-item-tracker/{adapters => seeds}/gitea/mock.sh (100%) mode change 100644 => 100755 rename plugins/work-items/tools/work-item-tracker/{adapters => seeds}/jira/README.md (100%) rename plugins/work-items/tools/work-item-tracker/{adapters => seeds}/jira/capabilities.json (100%) rename plugins/work-items/tools/work-item-tracker/{adapters => seeds}/jira/capabilities.sh (100%) rename plugins/work-items/tools/work-item-tracker/{adapters => seeds}/jira/capabilities.test.sh (100%) rename plugins/work-items/tools/work-item-tracker/{adapters => seeds}/jira/change-link.sh (100%) rename plugins/work-items/tools/work-item-tracker/{adapters => seeds}/jira/change-link.test.sh (100%) rename plugins/work-items/tools/work-item-tracker/{adapters => seeds}/jira/common.sh (95%) mode change 100644 => 100755 rename plugins/work-items/tools/work-item-tracker/{adapters => seeds}/jira/common.test.sh (100%) rename plugins/work-items/tools/work-item-tracker/{adapters => seeds}/jira/get-item.sh (100%) rename plugins/work-items/tools/work-item-tracker/{adapters => seeds}/jira/get-item.test.sh (100%) rename plugins/work-items/tools/work-item-tracker/{adapters => seeds}/jira/list-items.sh (100%) rename plugins/work-items/tools/work-item-tracker/{adapters => seeds}/jira/list-items.test.sh (99%) rename plugins/work-items/tools/work-item-tracker/{adapters => seeds}/jira/mock.sh (100%) mode change 100644 => 100755 rename plugins/work-items/tools/work-item-tracker/{adapters => seeds}/linear/README.md (100%) rename plugins/work-items/tools/work-item-tracker/{adapters => seeds}/linear/adapter-spec.json (100%) rename plugins/work-items/tools/work-item-tracker/{adapters => seeds}/linear/add-sub-item.sh (100%) rename plugins/work-items/tools/work-item-tracker/{adapters => seeds}/linear/add-sub-item.test.sh (100%) rename plugins/work-items/tools/work-item-tracker/{adapters => seeds}/linear/capabilities.json (100%) rename plugins/work-items/tools/work-item-tracker/{adapters => seeds}/linear/capabilities.sh (100%) rename plugins/work-items/tools/work-item-tracker/{adapters => seeds}/linear/capabilities.test.sh (100%) rename plugins/work-items/tools/work-item-tracker/{adapters => seeds}/linear/change-link.sh (100%) rename plugins/work-items/tools/work-item-tracker/{adapters => seeds}/linear/change-link.test.sh (100%) rename plugins/work-items/tools/work-item-tracker/{adapters => seeds}/linear/claim.sh (100%) rename plugins/work-items/tools/work-item-tracker/{adapters => seeds}/linear/claim.test.sh (100%) rename plugins/work-items/tools/work-item-tracker/{adapters => seeds}/linear/common.sh (100%) mode change 100644 => 100755 rename plugins/work-items/tools/work-item-tracker/{adapters => seeds}/linear/common.test.sh (100%) rename plugins/work-items/tools/work-item-tracker/{adapters => seeds}/linear/create-item.sh (100%) rename plugins/work-items/tools/work-item-tracker/{adapters => seeds}/linear/create-item.test.sh (100%) rename plugins/work-items/tools/work-item-tracker/{adapters => seeds}/linear/get-item.sh (100%) rename plugins/work-items/tools/work-item-tracker/{adapters => seeds}/linear/get-item.test.sh (100%) rename plugins/work-items/tools/work-item-tracker/{adapters => seeds}/linear/link-blocks.sh (100%) rename plugins/work-items/tools/work-item-tracker/{adapters => seeds}/linear/link-blocks.test.sh (100%) rename plugins/work-items/tools/work-item-tracker/{adapters => seeds}/linear/list-items.sh (100%) rename plugins/work-items/tools/work-item-tracker/{adapters => seeds}/linear/list-items.test.sh (99%) rename plugins/work-items/tools/work-item-tracker/{adapters => seeds}/linear/list-sub-items.sh (100%) rename plugins/work-items/tools/work-item-tracker/{adapters => seeds}/linear/list-sub-items.test.sh (100%) rename plugins/work-items/tools/work-item-tracker/{adapters => seeds}/linear/mock.sh (100%) mode change 100644 => 100755 rename plugins/work-items/tools/work-item-tracker/{adapters => seeds}/linear/reclaim.sh (100%) rename plugins/work-items/tools/work-item-tracker/{adapters => seeds}/linear/reclaim.test.sh (100%) rename plugins/work-items/tools/work-item-tracker/{adapters => seeds}/linear/renew-lease.sh (100%) rename plugins/work-items/tools/work-item-tracker/{adapters => seeds}/linear/renew-lease.test.sh (100%) rename plugins/work-items/tools/work-item-tracker/{adapters => seeds}/linear/schema-check/.gitignore (100%) rename plugins/work-items/tools/work-item-tracker/{adapters => seeds}/linear/schema-check/README.md (98%) rename plugins/work-items/tools/work-item-tracker/{adapters => seeds}/linear/schema-check/fetch-schema.sh (100%) rename plugins/work-items/tools/work-item-tracker/{adapters => seeds}/linear/schema-check/fidelity.sh (100%) rename plugins/work-items/tools/work-item-tracker/{adapters => seeds}/linear/schema-check/negative.mjs (100%) rename plugins/work-items/tools/work-item-tracker/{adapters => seeds}/linear/schema-check/validate.mjs (100%) diff --git a/.changes/work-items/work-resolve-issue-issue-11873-9c3e5a71.md b/.changes/work-items/work-resolve-issue-issue-11873-9c3e5a71.md new file mode 100644 index 00000000000..8cc35e2a284 --- /dev/null +++ b/.changes/work-items/work-resolve-issue-issue-11873-9c3e5a71.md @@ -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 `/tools/work-item-tracker/adapters//` 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). diff --git a/plugins/work-items/README.md b/plugins/work-items/README.md index f717685ed3e..af84527a4ac 100644 --- a/plugins/work-items/README.md +++ b/plugins/work-items/README.md @@ -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 @@ -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 @@ -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 diff --git a/plugins/work-items/evals/oa-bundled-provider-routes-to-setup/case.yaml b/plugins/work-items/evals/oa-bundled-provider-routes-to-setup/case.yaml index fab8ad1840a..a2c4aef745a 100644 --- a/plugins/work-items/evals/oa-bundled-provider-routes-to-setup/case.yaml +++ b/plugins/work-items/evals/oa-bundled-provider-routes-to-setup/case.yaml @@ -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: @@ -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 diff --git a/plugins/work-items/evals/oa-bundled-provider-routes-to-setup/samples/says-bundled.json b/plugins/work-items/evals/oa-bundled-provider-routes-to-setup/samples/says-bundled.json index 1c6b6e7d778..11550a7fb95 100644 --- a/plugins/work-items/evals/oa-bundled-provider-routes-to-setup/samples/says-bundled.json +++ b/plugins/work-items/evals/oa-bundled-provider-routes-to-setup/samples/says-bundled.json @@ -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": [ diff --git a/plugins/work-items/skills/onboard-adapter/SKILL.md b/plugins/work-items/skills/onboard-adapter/SKILL.md index 03ba0b9fda9..d1e4e5a0a47 100644 --- a/plugins/work-items/skills/onboard-adapter/SKILL.md +++ b/plugins/work-items/skills/onboard-adapter/SKILL.md @@ -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 @@ -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 @@ -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 @@ -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_

_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

`, 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 @@ -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 diff --git a/plugins/work-items/skills/onboard-adapter/evals/evals.json b/plugins/work-items/skills/onboard-adapter/evals/evals.json index 4410166f289..bd59ae735c1 100644 --- a/plugins/work-items/skills/onboard-adapter/evals/evals.json +++ b/plugins/work-items/skills/onboard-adapter/evals/evals.json @@ -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" ] } ] diff --git a/plugins/work-items/skills/onboard-adapter/reference/adapter-spec.md b/plugins/work-items/skills/onboard-adapter/reference/adapter-spec.md index 59dab73bf4c..9cf9fd597df 100644 --- a/plugins/work-items/skills/onboard-adapter/reference/adapter-spec.md +++ b/plugins/work-items/skills/onboard-adapter/reference/adapter-spec.md @@ -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. diff --git a/plugins/work-items/skills/onboard-adapter/reference/live-exploration.md b/plugins/work-items/skills/onboard-adapter/reference/live-exploration.md index 107267d472d..4c49499f9fd 100644 --- a/plugins/work-items/skills/onboard-adapter/reference/live-exploration.md +++ b/plugins/work-items/skills/onboard-adapter/reference/live-exploration.md @@ -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 diff --git a/plugins/work-items/skills/onboard-adapter/scripts/generate-adapter.test.sh b/plugins/work-items/skills/onboard-adapter/scripts/generate-adapter.test.sh index b51490d990a..7e5c21da24e 100755 --- a/plugins/work-items/skills/onboard-adapter/scripts/generate-adapter.test.sh +++ b/plugins/work-items/skills/onboard-adapter/scripts/generate-adapter.test.sh @@ -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" \ diff --git a/plugins/work-items/skills/setup/SKILL.md b/plugins/work-items/skills/setup/SKILL.md index ffbaaf6796c..3a4d6eb9176 100644 --- a/plugins/work-items/skills/setup/SKILL.md +++ b/plugins/work-items/skills/setup/SKILL.md @@ -51,8 +51,10 @@ directory or any machine-local state. `check` binding probe validates it read-only. The tracker seam runs against exactly one provider per repo, declared in the tracked `.work-item-tracker.json` at the project root (resolved as `BINDING` above); every seam verb resolves the bound provider from it, and with no binding the seam hard-errors -(exit 3). The seam **ships with this plugin** and bundles the `github`, `local-markdown`, `jira`, `gitea`, and `linear` -adapters. Installing the plugin is enough; a repo only declares which one it uses. Binding shape, discovery, and +(exit 3). The seam **ships with this plugin** and bundles two adapters, `github` and `local-markdown`; +for those, installing the plugin is enough and a repo only declares which one it uses. `jira`, `gitea`, +and `linear` ship as **seeds**, which step 4 copies into the consuming repo, where the repo owns them. +Binding shape, discovery, and adapter resolution are the seam contract's [`${CLAUDE_PLUGIN_ROOT}/tools/work-item-tracker/CONTRACT.md`](${CLAUDE_PLUGIN_ROOT}/tools/work-item-tracker/CONTRACT.md) "Setup (binding file)" and "Adapter resolution". @@ -64,7 +66,8 @@ when this pass must stop instead of guessing. 1. **Read the current binding first.** If `.work-item-tracker.json` exists, load it and report the bound `provider` and `config`. RECOMMENDED: keep it. Re-bind only to switch providers or fix - config. If it is absent, say so and continue to the interview. + config. Keeping a binding to a seeded provider still runs step 4's seed copy, attended or not. + If it is absent, say so and continue to the interview. 2. **Choose the provider**, recommendation first. One line each below; the selection detail, how to verify a `github` bind, what each provider can and cannot do, and every config key, is `reference/providers.md`. Read it before recommending, and cite it when explaining a @@ -82,7 +85,7 @@ when this pass must stop instead of guessing. leases and no sub-items**, so `/work-items:work` cannot claim on it. - **another provider**. Put its adapter consumer-local under `/tools/work-item-tracker/adapters//`; the seam resolves those ahead of - the bundled set, so no fork is needed. `/work-items:onboard-adapter` (if installed) generates + the bundled pair, so no fork is needed. `/work-items:onboard-adapter` (if installed) generates one rather than starting from a blank file. 3. **Settle the config, all non-secret.** `lease_ttl_hours` (REQUIRED for every provider; RECOMMENDED `24`) plus that provider's own subtree, the per-provider key table is in @@ -97,6 +100,16 @@ when this pass must stop instead of guessing. other keys. Write `schema_version: "1.0"`, the chosen `provider`, the `config`, and, unless one already exists, the self-describing `docs` pointer (CONTRACT.md "Setup (binding file)"). Confirm tracked-not-ignored via the pair: `git check-ignore -v` no match AND `git ls-files --error-unmatch` exit 0 (non-zero after a fresh write = "written but untracked: commit it"). + **For a seeded provider** (`jira`, `gitea`, `linear`), whether this step wrote the binding or + step 1 kept it, copy its seed by running + `bash "${CLAUDE_PLUGIN_ROOT}/tools/work-item-tracker/seed-copy.sh" ` from the project + root; the seam exits 3 for that provider until the copy exists. Its first stdout line is the outcome + (the table is in `${CLAUDE_PLUGIN_ROOT}/tools/work-item-tracker/seeds/README.md`): + - `copied`, `up-to-date`, or `fast-forwarded` (exit 0): report it, and say the copy under + `tools/work-item-tracker/adapters//` is the consumer's to commit and own. + - `edited` (exit 1): someone changed the copy, so nothing was written. Show the diff that follows + and stop this step; never delete or overwrite the copy to force an update. With no interactive + user, report it as a finding and continue with the existing copy. 5. **Ensure the personal-overlay gitignore line.** The gitignored per-user overlay (`.work-item-tracker.local.json`, allowlisted keys only. CONTRACT.md "Setup (binding file)") sits at the repo root, outside the `.claude/**/*.local.*` convention line, so `apply` must confirm a @@ -419,9 +432,9 @@ token resolving in a supporting file or in the Bash tool. - Write the plugin cache, Claude Code user settings, or `pluginConfigs`. - Duplicate the per-item `add --recurring` path, that path stays for filing a single recurring item; setup is the bulk path that seeds or reshapes the whole schedule, opt-in on a first-time bind. -- Author or vendor a provider adapter, the seam ships the bundled adapters named under "Provider - binding" (`github`, `local-markdown`, `jira`, `gitea`, `linear`); a consumer-supplied adapter lives - in the consuming repo at +- Author a provider adapter. The seam bundles `github` and `local-markdown`, and setup only copies + the `jira`, `gitea`, or `linear` seed (step 4 of "Provider binding"); any other adapter lives in the + consuming repo at `${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel)}/tools/work-item-tracker/adapters//`, not written by setup. - Store secrets, the binding is tracked in git and carries non-secret config only (a provider token is referenced by name from inside its adapter, never written here). diff --git a/plugins/work-items/skills/setup/reference/check.md b/plugins/work-items/skills/setup/reference/check.md index d69fa42c18d..f1d57afea3b 100644 --- a/plugins/work-items/skills/setup/reference/check.md +++ b/plugins/work-items/skills/setup/reference/check.md @@ -16,8 +16,10 @@ check. bound, so every seam verb hard-errors (exit 3) until `apply` seeds it, and the role remap has nothing to configure; the remediation is `/work-items:setup apply`. Present → validate without mutating: it parses as JSON, carries `schema_version` and a `provider`, and that provider resolves to a bundled - adapter (`github`, `local-markdown`, `jira`, `gitea`, `linear`) or a consumer-local one at - `${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel)}/tools/work-item-tracker/adapters//`; `config.lease_ttl_hours` is + adapter (`github`, `local-markdown`) or a consumer-local one at + `${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel)}/tools/work-item-tracker/adapters//`. + A seeded provider (`jira`, `gitea`, `linear`) resolves only through that consumer-local copy: with + none there it is FAIL, remediation `/work-items:setup apply`, which copies the seed. `config.lease_ttl_hours` is present, `local-markdown` additionally carries `config.storage_dir`, and `jira` additionally carries `config.jira` (`site`, non-empty `project_keys[]`, `auth_email`, `auth_env`). A malformed shape, an unknown/unresolvable provider, or a missing required config key is FAIL, naming what is wrong. diff --git a/plugins/work-items/skills/setup/reference/providers.md b/plugins/work-items/skills/setup/reference/providers.md index 3ed3d8aedef..77eaa8599ea 100644 --- a/plugins/work-items/skills/setup/reference/providers.md +++ b/plugins/work-items/skills/setup/reference/providers.md @@ -1,10 +1,12 @@ # Provider comparison -The full bundled-provider detail behind step 2 of the provider-binding interview in `SKILL.md`. +The full provider detail behind step 2 of the provider-binding interview in `SKILL.md`. Read it when choosing or re-binding a provider; the skill's own list is the summary. The seam's contract for each is `/tools/work-item-tracker/CONTRACT.md`, and -each adapter carries its own README beside it. +each adapter carries its own README beside it. `github` and `local-markdown` are bundled under +`adapters/`; `jira`, `gitea`, and `linear` are seeds under `seeds/`, which step 4 copies into the +consuming repo. ## `github`: RECOMMENDED @@ -75,7 +77,7 @@ cannot claim on it, and it is not a multi-agent coordination surface. Requires `config.gitea` (`host`, non-empty `scopes[]` of `owner/repo`, `auth_env`) and `curl`; the token is referenced by env-var name only, never stored. Optional `page_size`, `host_suffix`, -`allow_custom_domain`. See `adapters/gitea/README.md` for its provider notes and recorded +`allow_custom_domain`. See `seeds/gitea/README.md` for its provider notes and recorded deferrals, **including that no live-instance conformance pass has been run**. ## `linear` @@ -96,7 +98,7 @@ Requires `config.linear` (`host`, non-empty `scopes[]` of `/ `allow_custom_domain`. All scopes must share one workspace, because an API key reaches exactly one. -See `adapters/linear/README.md` for its provider notes, its **documented deviation from +See `seeds/linear/README.md` for its provider notes, its **documented deviation from the lease protocol** (Linear's single assignee field forces comment-ordering arbitration), and its recorded deferrals, **including that no live-workspace conformance pass has been run**. @@ -105,7 +107,7 @@ pass has been run**. Supply the adapter consumer-local at `${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel)}/tools/work-item-tracker/adapters//`. -The seam resolves consumer-local adapters ahead of the bundled set, so a repo can add an unshipped +The seam resolves consumer-local adapters ahead of the bundled pair, so a repo can add an unshipped provider, or shadow a bundled one, without forking the plugin. Set `provider` to its name in the binding. diff --git a/plugins/work-items/tools/work-item-tracker/CONTRACT.md b/plugins/work-items/tools/work-item-tracker/CONTRACT.md index e2f8a0e2809..e695ccc8ec4 100644 --- a/plugins/work-items/tools/work-item-tracker/CONTRACT.md +++ b/plugins/work-items/tools/work-item-tracker/CONTRACT.md @@ -364,8 +364,8 @@ adapters//list-sub-items.sh [--state open|closed|all] `features.list_items_label_filter: true` and puts the label in the provider query, so the ceiling counts labeled items only; it MAY match more loosely than exact, since core filters exactly afterward ("Verbs"), and it MAY refuse with exit `2` a label its provider cannot - hold. An adapter without the feature never receives `--label`. Every bundled adapter - declares it: github (`gh issue list --label`, which gh serves from the search API; that API + hold. An adapter without the feature never receives `--label`. Every bundled adapter and + seed declares it: github (`gh issue list --label`, which gh serves from the search API; that API returns at most 1000 results, so a labeled listing that reaches 1000 reports `truncated: true`), gitea (the `labels` query parameter), jira (a `labels = ""` JQL clause), linear (a `labels.some.name.eq` issue filter), and local-markdown (filtered in its walk before the ceiling check). @@ -394,7 +394,8 @@ Two independent resolutions, deliberately opposite: that exists, else `${CLAUDE_PROJECT_DIR}/tools/work-item-tracker/`, so a consuming repo runs the plugin's engine by default and a vendored copy still works. - **Adapters** (`adapters//`): **consumer-local-first, plugin-bundled fallback; first match - wins.** For the bound ``, the dispatcher searches + wins.** The plugin bundles two adapters, `github` and `local-markdown`. For the bound + ``, the dispatcher searches `/tools/work-item-tracker/adapters//` first, then its own bundled `adapters//`. The repo root is the same anchor the binding read resolved, `${CLAUDE_PROJECT_DIR}` when set, else the git toplevel, which can differ inside a worktree @@ -402,6 +403,14 @@ Two independent resolutions, deliberately opposite: does not ship, or shadow a bundled adapter with a local copy it owns fully, without forking the plugin. `WIT_ADAPTERS_DIR` overrides the search with a single explicit adapter root (tests, conformance). +- **Seeds** (`seeds//`: `jira`, `gitea`, `linear`): **never dispatched from the plugin.** + `/work-items:setup apply` runs `seed-copy.sh `, which copies the seed into + `/tools/work-item-tracker/adapters//`, where the consumer owns it and the + consumer-local search above finds it. A binding to a seeded provider with no copy exits `3` + naming that setup step. The copy carries a `.seed-hash` record: on a re-run an untouched copy + fast-forwards to the current seed and an edited one prints its diff and is left as it is + (`seeds/README.md`). Versioning is the manifest's `schema_version` and the handshake below, + with no header in the copied files. The binding (`.work-item-tracker.json`) and any consumer-local adapters live in the consuming repo (the repo root above); the bundled engine and adapters live in the plugin (`${CLAUDE_PLUGIN_ROOT}`), @@ -414,7 +423,8 @@ that adapter's own `../../lib` points at a seam copy the consumer never vendored `${WIT_SEAM_LIB_DIR}` instead resolves the libs of the engine actually dispatching it, the same engine its manifest handshook against a step earlier. A consumer-local adapter therefore does NOT require vendoring the seam. Bundled adapters resolve relatively and ignore it; an adapter run -directly (outside the dispatcher) falls back to its own relative path. +directly (outside the dispatcher) falls back to its own relative path. Seeds source it too, since a +copied seed is a consumer-local adapter. ## JSON output contract @@ -446,7 +456,7 @@ Normalized item object: - `state` is normalized lowercase: `open` | `closed`. - `created_at` is when the provider created the item, in UTC to the second (`YYYY-MM-DDTHH:MM:SSZ`), so values from one adapter sort as text; `null` when the - provider gives none or one the adapter cannot parse. Every bundled adapter reads the + provider gives none or one the adapter cannot parse. Every bundled adapter and seed reads the provider's own creation time (github `createdAt`, gitea `created_at`, jira `created`, linear `createdAt`); local-markdown records one at `create-item`, and its items created before that read `null`. Additive field: an adapter written before it emits none, which a @@ -1071,7 +1081,7 @@ concurrent assignment is arbitrated cannot be settled without a live instance, a lease over last-write-wins loses races silently. Provider divergences that shaped it, each verified against the Gitea source rather than assumed -from GitHub's API, and all documented in `adapters/gitea/README.md`: +from GitHub's API, and all documented in `seeds/gitea/README.md`: - A pull request **is** an issue (`pull_request` populated); `list-items` drops them. - `create-item` takes label **IDs**, not names; the adapter resolves names first and refuses an @@ -1122,6 +1132,10 @@ touch no network tool. The jira read verbs are covered offline by the adapter's `*.test.sh` with a mocked curl; a live-Jira conformance pass is deferred to the work-laptop pass that settles the exact `statusCategory` "done" key and blocker link type. +The dispatcher never resolves a seed from the plugin ("Adapter resolution"), so a conformance +run against the `jira`, `gitea` or `linear` seed itself sets `WIT_ADAPTERS_DIR` to `seeds/`, as +`bindings/jira.test.sh` does; with it unset, the run covers the consuming repo's copy. + The `gitea` and `linear` bindings are **on-demand like GitHub's**, not offline like jira's: both manifests declare `create-item` true, so the suite seeds a real item and every case after it is a live call. Each requires its own `WIT_CONFORMANCE__HOST` and diff --git a/plugins/work-items/tools/work-item-tracker/conformance/bindings/jira.test.sh b/plugins/work-items/tools/work-item-tracker/conformance/bindings/jira.test.sh index 6ec0b4bb036..12698fe0397 100755 --- a/plugins/work-items/tools/work-item-tracker/conformance/bindings/jira.test.sh +++ b/plugins/work-items/tools/work-item-tracker/conformance/bindings/jira.test.sh @@ -2,7 +2,7 @@ # shellcheck disable=SC2154 # FAILED/CASE_NUM initialized by the sourced lib # RUNS the full abstract suite against the consume-only jira adapter, once normally and # once under a PATH shim that makes gh/curl fail: every exercised path is pre-network. -# test-scope: plugins/work-items/tools/work-item-tracker/adapters/jira/* +# test-scope: plugins/work-items/tools/work-item-tracker/seeds/jira/* # test-scope: plugins/work-items/tools/work-item-tracker/lib/id.sh set -uo pipefail @@ -10,6 +10,8 @@ TMP_ROOT="$(mktemp -d)" trap 'rm -rf "$TMP_ROOT"' EXIT SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# jira is a seed, which the dispatcher never resolves from the plugin; point it there. +export WIT_ADAPTERS_DIR="$SCRIPT_DIR/../../seeds" RUNNER="$SCRIPT_DIR/../run-conformance.sh" source "$SCRIPT_DIR/../../tests/lib.sh" diff --git a/plugins/work-items/tools/work-item-tracker/seed-copy.sh b/plugins/work-items/tools/work-item-tracker/seed-copy.sh new file mode 100755 index 00000000000..232d2b5dbe6 --- /dev/null +++ b/plugins/work-items/tools/work-item-tracker/seed-copy.sh @@ -0,0 +1,146 @@ +#!/usr/bin/env bash +# Copy an adapter seed (seeds//) into the consuming repo's adapter directory, +# the step /work-items:setup apply runs for a seeded provider. seeds/README.md has the +# re-run table: an untouched copy fast-forwards, an edited one prints the diff and stops. +# +# Usage: seed-copy.sh [--root ] +# stdout: one status word (copied | up-to-date | fast-forwarded | edited), then the diff +# when edited. Exit 0 = copy current, 1 = edited copy left as-is, 2 = usage or no seed. +set -uo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=lib/binding.sh +source "$SCRIPT_DIR/lib/binding.sh" + +# WIT_SEEDS_DIR overrides the seed root, as WIT_ADAPTERS_DIR does the adapter root (tests). +readonly SEEDS_DIR="${WIT_SEEDS_DIR:-$SCRIPT_DIR/seeds}" +readonly HASH_FILE=".seed-hash" + +usage() { + printf 'usage: seed-copy.sh [--root ]\n' >&2 + exit 2 +} + +# seed_files

: the seed's copied files, relative and sorted. The offline tests and +# the linear schema check need this plugin's test helpers and conformance bindings, so +# they stay in the plugin. +seed_files() { + (cd "$1" && find . -type f \ + ! -name '*.test.sh' ! -name 'mock.sh' ! -name "$HASH_FILE" \ + ! -path './schema-check/*' -print | LC_ALL=C sort) +} + +# copy_files : every file in a copy except its hash record, relative and sorted. +copy_files() { + (cd "$1" && find . -type f ! -path "./$HASH_FILE" -print | LC_ALL=C sort) +} + +sha256_stdin() { + if command -v sha256sum >/dev/null 2>&1; then + sha256sum | cut -d' ' -f1 + else + shasum -a 256 | cut -d' ' -f1 + fi +} + +# tree_hash : one hash over each path read from stdin (relative to ) and its content. +tree_hash() { + local dir="$1" rel + while IFS= read -r rel; do + printf '%s %s\n' "$rel" "$(sha256_stdin <"$dir/$rel")" + done | sha256_stdin +} + +# stage_seed : the copied file set, modes kept, plus its hash record. +stage_seed() { + local seed="$1" stage="$2" rel + while IFS= read -r rel; do + mkdir -p "$stage/$(dirname "$rel")" && cp -p "$seed/$rel" "$stage/$rel" || return 1 + done < <(seed_files "$seed") + seed_files "$seed" | tree_hash "$seed" >"$stage/$HASH_FILE" +} + +provider="" +root="" +while [[ $# -gt 0 ]]; do + case "$1" in + --root) + [[ $# -ge 2 && -n "$2" ]] || usage + root="$2" + shift 2 + ;; + -h | --help) usage ;; + -*) usage ;; + *) + [[ -z "$provider" ]] || usage + provider="$1" + shift + ;; + esac +done +[[ -n "$provider" ]] || usage +# The name becomes a path segment: constrain it before any path is built from it. +[[ "$provider" =~ ^[a-z][a-z0-9-]*$ ]] || usage + +seed="$SEEDS_DIR/$provider" +if [[ ! -d "$seed" ]]; then + known="" + for d in "$SEEDS_DIR"/*/; do + known+="$(basename "$d") " + done + printf "seed-copy: no seed for provider '%s'; seeds: %s\n" "$provider" "$known" >&2 + exit 2 +fi +if [[ -z "$root" ]]; then + root="$(wit_project_root)" || { + printf 'seed-copy: no repo root (set CLAUDE_PROJECT_DIR, run inside a git checkout, or pass --root)\n' >&2 + exit 2 + } +fi + +fail() { + printf 'seed-copy: %s\n' "$1" >&2 + exit 2 +} + +dest="$root/tools/work-item-tracker/adapters/$provider" +mkdir -p "$(dirname "$dest")" || fail "cannot create $(dirname "$dest")" +stage="$(mktemp -d "$(dirname "$dest")/.seed-$provider.XXXXXX")" && [[ -d "$stage" ]] || + fail "cannot create a staging directory beside $dest" +trap 'rm -rf "$stage"' EXIT +# mktemp -d makes the directory owner-only; the adapter directory it becomes is not. +chmod 755 "$stage" +stage_seed "$seed" "$stage" || fail "cannot stage the $provider seed" +seed_hash="$(<"$stage/$HASH_FILE")" + +if [[ ! -d "$dest" ]]; then + mv "$stage" "$dest" || fail "cannot move the staged seed to $dest" + printf 'copied\n' + exit 0 +fi + +recorded="" +[[ -f "$dest/$HASH_FILE" ]] && recorded="$(<"$dest/$HASH_FILE")" +if [[ -z "$recorded" || "$(copy_files "$dest" | tree_hash "$dest")" != "$recorded" ]]; then + # No record means the directory was not copied from a seed; either way it is the + # consumer's, so show how it differs from the current seed and write nothing. + printf 'edited\n' + diff -ru -x "$HASH_FILE" "$stage" "$dest" + exit 1 +fi + +if [[ "$recorded" == "$seed_hash" ]]; then + printf 'up-to-date\n' + exit 0 +fi + +# Swap through a backup so a failed move leaves the old copy in place. +backup="$stage.old" +mv "$dest" "$backup" || fail "cannot move $dest aside" +if ! mv "$stage" "$dest"; then + mv "$backup" "$dest" + fail "cannot move the staged seed to $dest; the previous copy is back in place" +fi +rm -rf "$backup" +printf 'fast-forwarded\n' +exit 0 diff --git a/plugins/work-items/tools/work-item-tracker/seed-copy.test.sh b/plugins/work-items/tools/work-item-tracker/seed-copy.test.sh new file mode 100755 index 00000000000..b4022a8461c --- /dev/null +++ b/plugins/work-items/tools/work-item-tracker/seed-copy.test.sh @@ -0,0 +1,124 @@ +#!/usr/bin/env bash +# shellcheck disable=SC2154 # FAILED/CASE_NUM initialized by the sourced lib +# seed-copy.sh: a first run copies a seed, an untouched copy fast-forwards when the seed +# changes, and an edited copy stops with a diff and is left as it was. +# test-scope: plugins/work-items/tools/work-item-tracker/seeds/* +set -uo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$SCRIPT_DIR/tests/lib.sh" +COPY="$SCRIPT_DIR/seed-copy.sh" +TRACKER="$SCRIPT_DIR/work-item-tracker.sh" + +TMP_ROOT="$(mktemp -d)" +trap 'rm -rf "$TMP_ROOT"' EXIT + +SEEDS="$TMP_ROOT/seeds" +ROOT="$TMP_ROOT/repo" +DEST="$ROOT/tools/work-item-tracker/adapters/acme" +mkdir -p "$SEEDS/acme/schema-check" "$ROOT" +printf 'v1\n' >"$SEEDS/acme/get-item.sh" +printf '{}\n' >"$SEEDS/acme/capabilities.json" +printf 'test\n' >"$SEEDS/acme/get-item.test.sh" +printf 'mock\n' >"$SEEDS/acme/mock.sh" +printf 'check\n' >"$SEEDS/acme/schema-check/fidelity.sh" +chmod +x "$SEEDS/acme/get-item.sh" + +# run_copy : OUT holds stdout, RC the exit code. +run_copy() { + OUT="$(WIT_SEEDS_DIR="$SEEDS" bash "$COPY" "$@" 2>"$TMP_ROOT/err")" + RC=$? +} + +# --- usage ------------------------------------------------------------------- +run_copy +assert_eq "no provider → exit 2" "2" "$RC" +run_copy ../acme --root "$ROOT" +assert_eq "a path-like provider → exit 2" "2" "$RC" +run_copy nope --root "$ROOT" +assert_eq "an unknown seed → exit 2" "2" "$RC" +assert_contains "…naming the seeds there are" "$(<"$TMP_ROOT/err")" "acme" + +# --- first copy -------------------------------------------------------------- +run_copy acme --root "$ROOT" +assert_eq "first run → exit 0" "0" "$RC" +assert_eq "first run → copied" "copied" "$OUT" +assert_eq "the verb script is copied" "v1" "$(<"$DEST/get-item.sh")" +assert_succeeds "…keeping its executable bit" "executable" "not executable" test -x "$DEST/get-item.sh" +assert_fails "the seed's tests stay in the plugin" "absent" "present" test -e "$DEST/get-item.test.sh" +assert_fails "the seed's mock stays in the plugin" "absent" "present" test -e "$DEST/mock.sh" +assert_fails "the schema check stays in the plugin" "absent" "present" test -e "$DEST/schema-check" +assert_succeeds "the copy records its hash" "present" "absent" test -s "$DEST/.seed-hash" +assert_eq "no staging directory is left behind" "acme" \ + "$(cd "$ROOT/tools/work-item-tracker/adapters" && ls -A)" + +run_copy acme --root "$ROOT" +assert_eq "unchanged seed → exit 0" "0" "$RC" +assert_eq "unchanged seed → up-to-date" "up-to-date" "$OUT" + +# --- an untouched copy fast-forwards ----------------------------------------- +printf 'v2\n' >"$SEEDS/acme/get-item.sh" +printf 'new\n' >"$SEEDS/acme/list-items.sh" +HASH_BEFORE="$(<"$DEST/.seed-hash")" +run_copy acme --root "$ROOT" +assert_eq "changed seed, untouched copy → exit 0" "0" "$RC" +assert_eq "changed seed, untouched copy → fast-forwarded" "fast-forwarded" "$OUT" +assert_eq "the changed file is updated" "v2" "$(<"$DEST/get-item.sh")" +assert_eq "the added file arrives" "new" "$(<"$DEST/list-items.sh")" +if [[ "$(<"$DEST/.seed-hash")" != "$HASH_BEFORE" ]]; then + pass "the hash record moves to the new seed" +else + fail "the hash record moves to the new seed" "a new hash" "$HASH_BEFORE" +fi +run_copy acme --root "$ROOT" +assert_eq "after the fast-forward → up-to-date" "up-to-date" "$OUT" + +# --- an edited copy stops with a diff ---------------------------------------- +printf 'v2\nlocal fix\n' >"$DEST/get-item.sh" +printf 'v3\n' >"$SEEDS/acme/get-item.sh" +run_copy acme --root "$ROOT" +assert_eq "edited copy → exit 1" "1" "$RC" +assert_eq "edited copy → the status line says so" "edited" "$(head -n 1 <<<"$OUT")" +assert_contains "…and the diff shows the local edit" "$OUT" "+local fix" +assert_contains "…against the current seed" "$OUT" "-v3" +assert_eq "the edited file is left as it was" "$(printf 'v2\nlocal fix')" "$(<"$DEST/get-item.sh")" + +# A file the consumer added is an edit too: fast-forwarding would delete it. +printf 'v2\n' >"$DEST/get-item.sh" +printf 'v2\n' >"$SEEDS/acme/get-item.sh" +printf 'mine\n' >"$DEST/extra.sh" +run_copy acme --root "$ROOT" +assert_eq "an added file → exit 1" "1" "$RC" +assert_eq "the added file is kept" "mine" "$(<"$DEST/extra.sh")" +rm -f "$DEST/extra.sh" + +# A directory with no hash record was not copied from a seed, so it is never replaced. +rm -f "$DEST/.seed-hash" +run_copy acme --root "$ROOT" +assert_eq "no hash record → exit 1" "1" "$RC" +assert_eq "no hash record → edited" "edited" "$(head -n 1 <<<"$OUT")" +assert_fails "…and no record is written" "absent" "present" test -e "$DEST/.seed-hash" + +# --- a real seed runs from its copy ------------------------------------------ +# The copied jira seed has no ../../lib beside it; the dispatcher's WIT_SEAM_LIB_DIR +# must carry it, through the consumer-local adapter resolution. +JROOT="$TMP_ROOT/jira-repo" +mkdir -p "$JROOT" +OUT="$(bash "$COPY" jira --root "$JROOT" 2>/dev/null)" +assert_eq "the jira seed copies" "copied" "$OUT" +jq -cn '{schema_version:"1.0", provider:"jira", + config:{lease_ttl_hours:24, jira:{site:"a.atlassian.net", project_keys:["AB"], + auth_email:"a@example.invalid", auth_env:"WIT_SEED_COPY_TEST_UNUSED"}}}' >"$JROOT/.work-item-tracker.json" +CAPS="$(CLAUDE_PROJECT_DIR="$JROOT" WORK_ITEM_TRACKER_BINDING="$JROOT/.work-item-tracker.json" \ + bash "$TRACKER" capabilities 2>"$TMP_ROOT/err")" +assert_eq "the dispatcher runs the copied jira adapter" "0" "$?" +assert_eq "…which names its provider" "jira" "$(jq -r '.provider' <<<"$CAPS" 2>/dev/null)" + +# With no copy the dispatcher points at setup instead of a bare not-found. +rm -rf "$JROOT/tools" +CLAUDE_PROJECT_DIR="$JROOT" WORK_ITEM_TRACKER_BINDING="$JROOT/.work-item-tracker.json" \ + bash "$TRACKER" capabilities >/dev/null 2>"$TMP_ROOT/err" +assert_eq "an uncopied seed → exit 3" "3" "$?" +assert_contains "…naming the setup step that copies it" "$(<"$TMP_ROOT/err")" "/work-items:setup apply" + +[[ $FAILED -eq 0 ]] || exit 1 diff --git a/plugins/work-items/tools/work-item-tracker/seeds/README.md b/plugins/work-items/tools/work-item-tracker/seeds/README.md new file mode 100644 index 00000000000..86cda832799 --- /dev/null +++ b/plugins/work-items/tools/work-item-tracker/seeds/README.md @@ -0,0 +1,22 @@ +# Adapter seeds + +The seam bundles two adapters, `github` and `local-markdown`, under `../adapters/`. The adapters +here (`jira`, `gitea`, `linear`) are seeds: the dispatcher never resolves them from this +directory. `/work-items:setup apply` copies the bound provider's seed into the consuming repo at +`/tools/work-item-tracker/adapters//`, and from then on the consumer owns +that copy. + +`../seed-copy.sh ` does the copy. It leaves out the seed's offline tests (`*.test.sh` +and `mock.sh`) and linear's `schema-check/`, which need this plugin's test helpers and +conformance bindings, and writes `.seed-hash`, the content hash of what it copied. On a re-run: + +| Copy state | Result | Exit | +|---|---|---| +| absent | copied | 0 | +| hash matches, seed unchanged | `up-to-date` | 0 | +| hash matches, seed changed | `fast-forwarded` to the current seed | 0 | +| edited since the copy, or no `.seed-hash` | `edited`, the diff printed, nothing written | 1 | + +Versions need no header in the copied files: each seed's `capabilities.json` carries the +`schema_version` the dispatcher's contract-version handshake checks (`../CONTRACT.md` +"Contract-version handshake"), so a copy the engine has outgrown is refused at dispatch. diff --git a/plugins/work-items/tools/work-item-tracker/adapters/gitea/README.md b/plugins/work-items/tools/work-item-tracker/seeds/gitea/README.md similarity index 100% rename from plugins/work-items/tools/work-item-tracker/adapters/gitea/README.md rename to plugins/work-items/tools/work-item-tracker/seeds/gitea/README.md diff --git a/plugins/work-items/tools/work-item-tracker/adapters/gitea/adapter-spec.json b/plugins/work-items/tools/work-item-tracker/seeds/gitea/adapter-spec.json similarity index 100% rename from plugins/work-items/tools/work-item-tracker/adapters/gitea/adapter-spec.json rename to plugins/work-items/tools/work-item-tracker/seeds/gitea/adapter-spec.json diff --git a/plugins/work-items/tools/work-item-tracker/adapters/gitea/capabilities.json b/plugins/work-items/tools/work-item-tracker/seeds/gitea/capabilities.json similarity index 100% rename from plugins/work-items/tools/work-item-tracker/adapters/gitea/capabilities.json rename to plugins/work-items/tools/work-item-tracker/seeds/gitea/capabilities.json diff --git a/plugins/work-items/tools/work-item-tracker/adapters/gitea/capabilities.sh b/plugins/work-items/tools/work-item-tracker/seeds/gitea/capabilities.sh similarity index 100% rename from plugins/work-items/tools/work-item-tracker/adapters/gitea/capabilities.sh rename to plugins/work-items/tools/work-item-tracker/seeds/gitea/capabilities.sh diff --git a/plugins/work-items/tools/work-item-tracker/adapters/gitea/capabilities.test.sh b/plugins/work-items/tools/work-item-tracker/seeds/gitea/capabilities.test.sh similarity index 100% rename from plugins/work-items/tools/work-item-tracker/adapters/gitea/capabilities.test.sh rename to plugins/work-items/tools/work-item-tracker/seeds/gitea/capabilities.test.sh diff --git a/plugins/work-items/tools/work-item-tracker/adapters/gitea/change-link.sh b/plugins/work-items/tools/work-item-tracker/seeds/gitea/change-link.sh similarity index 100% rename from plugins/work-items/tools/work-item-tracker/adapters/gitea/change-link.sh rename to plugins/work-items/tools/work-item-tracker/seeds/gitea/change-link.sh diff --git a/plugins/work-items/tools/work-item-tracker/adapters/gitea/change-link.test.sh b/plugins/work-items/tools/work-item-tracker/seeds/gitea/change-link.test.sh similarity index 100% rename from plugins/work-items/tools/work-item-tracker/adapters/gitea/change-link.test.sh rename to plugins/work-items/tools/work-item-tracker/seeds/gitea/change-link.test.sh diff --git a/plugins/work-items/tools/work-item-tracker/adapters/gitea/common.sh b/plugins/work-items/tools/work-item-tracker/seeds/gitea/common.sh old mode 100644 new mode 100755 similarity index 100% rename from plugins/work-items/tools/work-item-tracker/adapters/gitea/common.sh rename to plugins/work-items/tools/work-item-tracker/seeds/gitea/common.sh diff --git a/plugins/work-items/tools/work-item-tracker/adapters/gitea/common.test.sh b/plugins/work-items/tools/work-item-tracker/seeds/gitea/common.test.sh similarity index 100% rename from plugins/work-items/tools/work-item-tracker/adapters/gitea/common.test.sh rename to plugins/work-items/tools/work-item-tracker/seeds/gitea/common.test.sh diff --git a/plugins/work-items/tools/work-item-tracker/adapters/gitea/create-item.sh b/plugins/work-items/tools/work-item-tracker/seeds/gitea/create-item.sh similarity index 100% rename from plugins/work-items/tools/work-item-tracker/adapters/gitea/create-item.sh rename to plugins/work-items/tools/work-item-tracker/seeds/gitea/create-item.sh diff --git a/plugins/work-items/tools/work-item-tracker/adapters/gitea/create-item.test.sh b/plugins/work-items/tools/work-item-tracker/seeds/gitea/create-item.test.sh similarity index 100% rename from plugins/work-items/tools/work-item-tracker/adapters/gitea/create-item.test.sh rename to plugins/work-items/tools/work-item-tracker/seeds/gitea/create-item.test.sh diff --git a/plugins/work-items/tools/work-item-tracker/adapters/gitea/get-item.sh b/plugins/work-items/tools/work-item-tracker/seeds/gitea/get-item.sh similarity index 100% rename from plugins/work-items/tools/work-item-tracker/adapters/gitea/get-item.sh rename to plugins/work-items/tools/work-item-tracker/seeds/gitea/get-item.sh diff --git a/plugins/work-items/tools/work-item-tracker/adapters/gitea/get-item.test.sh b/plugins/work-items/tools/work-item-tracker/seeds/gitea/get-item.test.sh similarity index 100% rename from plugins/work-items/tools/work-item-tracker/adapters/gitea/get-item.test.sh rename to plugins/work-items/tools/work-item-tracker/seeds/gitea/get-item.test.sh diff --git a/plugins/work-items/tools/work-item-tracker/adapters/gitea/link-blocks.sh b/plugins/work-items/tools/work-item-tracker/seeds/gitea/link-blocks.sh similarity index 100% rename from plugins/work-items/tools/work-item-tracker/adapters/gitea/link-blocks.sh rename to plugins/work-items/tools/work-item-tracker/seeds/gitea/link-blocks.sh diff --git a/plugins/work-items/tools/work-item-tracker/adapters/gitea/link-blocks.test.sh b/plugins/work-items/tools/work-item-tracker/seeds/gitea/link-blocks.test.sh similarity index 100% rename from plugins/work-items/tools/work-item-tracker/adapters/gitea/link-blocks.test.sh rename to plugins/work-items/tools/work-item-tracker/seeds/gitea/link-blocks.test.sh diff --git a/plugins/work-items/tools/work-item-tracker/adapters/gitea/list-items.sh b/plugins/work-items/tools/work-item-tracker/seeds/gitea/list-items.sh similarity index 100% rename from plugins/work-items/tools/work-item-tracker/adapters/gitea/list-items.sh rename to plugins/work-items/tools/work-item-tracker/seeds/gitea/list-items.sh diff --git a/plugins/work-items/tools/work-item-tracker/adapters/gitea/list-items.test.sh b/plugins/work-items/tools/work-item-tracker/seeds/gitea/list-items.test.sh similarity index 99% rename from plugins/work-items/tools/work-item-tracker/adapters/gitea/list-items.test.sh rename to plugins/work-items/tools/work-item-tracker/seeds/gitea/list-items.test.sh index adce9cf1850..8fe123b9222 100755 --- a/plugins/work-items/tools/work-item-tracker/adapters/gitea/list-items.test.sh +++ b/plugins/work-items/tools/work-item-tracker/seeds/gitea/list-items.test.sh @@ -2,7 +2,7 @@ # shellcheck disable=SC2154 # FAILED/CASE_NUM initialized by the sourced helper # list-items: offline contract tests. Pagination, PR exclusion, and the per-item # blocker count are all driven through a mocked curl — no live Gitea call. -# test-scope: plugins/work-items/tools/work-item-tracker/adapters/gitea/* +# test-scope: plugins/work-items/tools/work-item-tracker/seeds/gitea/* set -uo pipefail S="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/list-items.sh" D="$(dirname "$S")" diff --git a/plugins/work-items/tools/work-item-tracker/adapters/gitea/mock.sh b/plugins/work-items/tools/work-item-tracker/seeds/gitea/mock.sh old mode 100644 new mode 100755 similarity index 100% rename from plugins/work-items/tools/work-item-tracker/adapters/gitea/mock.sh rename to plugins/work-items/tools/work-item-tracker/seeds/gitea/mock.sh diff --git a/plugins/work-items/tools/work-item-tracker/adapters/jira/README.md b/plugins/work-items/tools/work-item-tracker/seeds/jira/README.md similarity index 100% rename from plugins/work-items/tools/work-item-tracker/adapters/jira/README.md rename to plugins/work-items/tools/work-item-tracker/seeds/jira/README.md diff --git a/plugins/work-items/tools/work-item-tracker/adapters/jira/capabilities.json b/plugins/work-items/tools/work-item-tracker/seeds/jira/capabilities.json similarity index 100% rename from plugins/work-items/tools/work-item-tracker/adapters/jira/capabilities.json rename to plugins/work-items/tools/work-item-tracker/seeds/jira/capabilities.json diff --git a/plugins/work-items/tools/work-item-tracker/adapters/jira/capabilities.sh b/plugins/work-items/tools/work-item-tracker/seeds/jira/capabilities.sh similarity index 100% rename from plugins/work-items/tools/work-item-tracker/adapters/jira/capabilities.sh rename to plugins/work-items/tools/work-item-tracker/seeds/jira/capabilities.sh diff --git a/plugins/work-items/tools/work-item-tracker/adapters/jira/capabilities.test.sh b/plugins/work-items/tools/work-item-tracker/seeds/jira/capabilities.test.sh similarity index 100% rename from plugins/work-items/tools/work-item-tracker/adapters/jira/capabilities.test.sh rename to plugins/work-items/tools/work-item-tracker/seeds/jira/capabilities.test.sh diff --git a/plugins/work-items/tools/work-item-tracker/adapters/jira/change-link.sh b/plugins/work-items/tools/work-item-tracker/seeds/jira/change-link.sh similarity index 100% rename from plugins/work-items/tools/work-item-tracker/adapters/jira/change-link.sh rename to plugins/work-items/tools/work-item-tracker/seeds/jira/change-link.sh diff --git a/plugins/work-items/tools/work-item-tracker/adapters/jira/change-link.test.sh b/plugins/work-items/tools/work-item-tracker/seeds/jira/change-link.test.sh similarity index 100% rename from plugins/work-items/tools/work-item-tracker/adapters/jira/change-link.test.sh rename to plugins/work-items/tools/work-item-tracker/seeds/jira/change-link.test.sh diff --git a/plugins/work-items/tools/work-item-tracker/adapters/jira/common.sh b/plugins/work-items/tools/work-item-tracker/seeds/jira/common.sh old mode 100644 new mode 100755 similarity index 95% rename from plugins/work-items/tools/work-item-tracker/adapters/jira/common.sh rename to plugins/work-items/tools/work-item-tracker/seeds/jira/common.sh index e833ea53093..3435e2dc876 --- a/plugins/work-items/tools/work-item-tracker/adapters/jira/common.sh +++ b/plugins/work-items/tools/work-item-tracker/seeds/jira/common.sh @@ -17,32 +17,33 @@ readonly _WIT_JIRA_COMMON_LOADED=1 WIT_JIRA_ADAPTER_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" readonly WIT_JIRA_ADAPTER_DIR -# A consumer may shadow this adapter with a local copy (CONTRACT.md "Adapter -# resolution") while still running the bundled dispatcher; the shared seam libs -# below then resolve relative to that copy and can be absent. A missing required -# lib must hard-fail with a clear diagnostic (exit 3 — setup/config error), never -# let sourcing fail silently and leave a verb emitting a malformed record. Runs -# before the EX_* constants below exist, so it uses the literal. +# This seed runs as a copy in the consuming repo (seeds/README.md), where its own +# ../../lib does not exist; the dispatcher exports WIT_SEAM_LIB_DIR (CONTRACT.md +# "Adapter resolution") for that case, and ../../lib serves a direct run from the +# plugin. A missing required lib must hard-fail with a clear diagnostic (exit 3 — +# setup/config error), never let sourcing fail silently and leave a verb emitting a +# malformed record. Runs before the EX_* constants below exist, so it uses the literal. +WIT_JIRA_SEAM_LIB="${WIT_SEAM_LIB_DIR:-$WIT_JIRA_ADAPTER_DIR/../../lib}" wit_jira_require_seam_lib() { [[ -f "$1" ]] && return 0 printf 'work-item-tracker jira adapter: required seam library not found: %s\n' "$1" >&2 - printf ' a shadowed consumer-local adapter must still resolve the bundled seam lib/ directory\n' >&2 + printf ' set WIT_SEAM_LIB_DIR to the work-item-tracker lib/ directory, or run the adapter through the dispatcher\n' >&2 exit 3 } -wit_jira_require_seam_lib "$WIT_JIRA_ADAPTER_DIR/../../lib/id.sh" -# shellcheck source=../../lib/id.sh -source "$WIT_JIRA_ADAPTER_DIR/../../lib/id.sh" -wit_jira_require_seam_lib "$WIT_JIRA_ADAPTER_DIR/../../lib/json.sh" -# shellcheck source=../../lib/json.sh -source "$WIT_JIRA_ADAPTER_DIR/../../lib/json.sh" +wit_jira_require_seam_lib "$WIT_JIRA_SEAM_LIB/id.sh" +# shellcheck source=../../lib/id.sh disable=SC1091 +source "$WIT_JIRA_SEAM_LIB/id.sh" +wit_jira_require_seam_lib "$WIT_JIRA_SEAM_LIB/json.sh" +# shellcheck source=../../lib/json.sh disable=SC1091 +source "$WIT_JIRA_SEAM_LIB/json.sh" # binding.sh is sourced (not inherited): the dispatcher runs each verb as a fresh # `bash .sh` subprocess, so only exported env vars cross — not wit_find_binding. # The adapter re-locates the binding itself to read its jira-specific config subtree, # rather than growing the shared binding.sh with jira keys. -wit_jira_require_seam_lib "$WIT_JIRA_ADAPTER_DIR/../../lib/binding.sh" -# shellcheck source=../../lib/binding.sh -source "$WIT_JIRA_ADAPTER_DIR/../../lib/binding.sh" +wit_jira_require_seam_lib "$WIT_JIRA_SEAM_LIB/binding.sh" +# shellcheck source=../../lib/binding.sh disable=SC1091 +source "$WIT_JIRA_SEAM_LIB/binding.sh" readonly EX_INTERNAL=1 readonly EX_USAGE=2 diff --git a/plugins/work-items/tools/work-item-tracker/adapters/jira/common.test.sh b/plugins/work-items/tools/work-item-tracker/seeds/jira/common.test.sh similarity index 100% rename from plugins/work-items/tools/work-item-tracker/adapters/jira/common.test.sh rename to plugins/work-items/tools/work-item-tracker/seeds/jira/common.test.sh diff --git a/plugins/work-items/tools/work-item-tracker/adapters/jira/get-item.sh b/plugins/work-items/tools/work-item-tracker/seeds/jira/get-item.sh similarity index 100% rename from plugins/work-items/tools/work-item-tracker/adapters/jira/get-item.sh rename to plugins/work-items/tools/work-item-tracker/seeds/jira/get-item.sh diff --git a/plugins/work-items/tools/work-item-tracker/adapters/jira/get-item.test.sh b/plugins/work-items/tools/work-item-tracker/seeds/jira/get-item.test.sh similarity index 100% rename from plugins/work-items/tools/work-item-tracker/adapters/jira/get-item.test.sh rename to plugins/work-items/tools/work-item-tracker/seeds/jira/get-item.test.sh diff --git a/plugins/work-items/tools/work-item-tracker/adapters/jira/list-items.sh b/plugins/work-items/tools/work-item-tracker/seeds/jira/list-items.sh similarity index 100% rename from plugins/work-items/tools/work-item-tracker/adapters/jira/list-items.sh rename to plugins/work-items/tools/work-item-tracker/seeds/jira/list-items.sh diff --git a/plugins/work-items/tools/work-item-tracker/adapters/jira/list-items.test.sh b/plugins/work-items/tools/work-item-tracker/seeds/jira/list-items.test.sh similarity index 99% rename from plugins/work-items/tools/work-item-tracker/adapters/jira/list-items.test.sh rename to plugins/work-items/tools/work-item-tracker/seeds/jira/list-items.test.sh index 70e451958d5..edbc1f2f607 100755 --- a/plugins/work-items/tools/work-item-tracker/adapters/jira/list-items.test.sh +++ b/plugins/work-items/tools/work-item-tracker/seeds/jira/list-items.test.sh @@ -3,7 +3,7 @@ # list-items: offline contract tests. The happy path drives the real normalizer + # nextPageToken pagination with a mocked curl (WIT_JIRA_CURL) returning canned # /search/jql pages — no live Jira call. -# test-scope: plugins/work-items/tools/work-item-tracker/adapters/jira/* plugins/work-items/tools/work-item-tracker/lib/* +# test-scope: plugins/work-items/tools/work-item-tracker/seeds/jira/* plugins/work-items/tools/work-item-tracker/lib/* set -uo pipefail S="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/list-items.sh" source "$(dirname "$S")/../../lib/verb-test-helpers.sh" diff --git a/plugins/work-items/tools/work-item-tracker/adapters/jira/mock.sh b/plugins/work-items/tools/work-item-tracker/seeds/jira/mock.sh old mode 100644 new mode 100755 similarity index 100% rename from plugins/work-items/tools/work-item-tracker/adapters/jira/mock.sh rename to plugins/work-items/tools/work-item-tracker/seeds/jira/mock.sh diff --git a/plugins/work-items/tools/work-item-tracker/adapters/linear/README.md b/plugins/work-items/tools/work-item-tracker/seeds/linear/README.md similarity index 100% rename from plugins/work-items/tools/work-item-tracker/adapters/linear/README.md rename to plugins/work-items/tools/work-item-tracker/seeds/linear/README.md diff --git a/plugins/work-items/tools/work-item-tracker/adapters/linear/adapter-spec.json b/plugins/work-items/tools/work-item-tracker/seeds/linear/adapter-spec.json similarity index 100% rename from plugins/work-items/tools/work-item-tracker/adapters/linear/adapter-spec.json rename to plugins/work-items/tools/work-item-tracker/seeds/linear/adapter-spec.json diff --git a/plugins/work-items/tools/work-item-tracker/adapters/linear/add-sub-item.sh b/plugins/work-items/tools/work-item-tracker/seeds/linear/add-sub-item.sh similarity index 100% rename from plugins/work-items/tools/work-item-tracker/adapters/linear/add-sub-item.sh rename to plugins/work-items/tools/work-item-tracker/seeds/linear/add-sub-item.sh diff --git a/plugins/work-items/tools/work-item-tracker/adapters/linear/add-sub-item.test.sh b/plugins/work-items/tools/work-item-tracker/seeds/linear/add-sub-item.test.sh similarity index 100% rename from plugins/work-items/tools/work-item-tracker/adapters/linear/add-sub-item.test.sh rename to plugins/work-items/tools/work-item-tracker/seeds/linear/add-sub-item.test.sh diff --git a/plugins/work-items/tools/work-item-tracker/adapters/linear/capabilities.json b/plugins/work-items/tools/work-item-tracker/seeds/linear/capabilities.json similarity index 100% rename from plugins/work-items/tools/work-item-tracker/adapters/linear/capabilities.json rename to plugins/work-items/tools/work-item-tracker/seeds/linear/capabilities.json diff --git a/plugins/work-items/tools/work-item-tracker/adapters/linear/capabilities.sh b/plugins/work-items/tools/work-item-tracker/seeds/linear/capabilities.sh similarity index 100% rename from plugins/work-items/tools/work-item-tracker/adapters/linear/capabilities.sh rename to plugins/work-items/tools/work-item-tracker/seeds/linear/capabilities.sh diff --git a/plugins/work-items/tools/work-item-tracker/adapters/linear/capabilities.test.sh b/plugins/work-items/tools/work-item-tracker/seeds/linear/capabilities.test.sh similarity index 100% rename from plugins/work-items/tools/work-item-tracker/adapters/linear/capabilities.test.sh rename to plugins/work-items/tools/work-item-tracker/seeds/linear/capabilities.test.sh diff --git a/plugins/work-items/tools/work-item-tracker/adapters/linear/change-link.sh b/plugins/work-items/tools/work-item-tracker/seeds/linear/change-link.sh similarity index 100% rename from plugins/work-items/tools/work-item-tracker/adapters/linear/change-link.sh rename to plugins/work-items/tools/work-item-tracker/seeds/linear/change-link.sh diff --git a/plugins/work-items/tools/work-item-tracker/adapters/linear/change-link.test.sh b/plugins/work-items/tools/work-item-tracker/seeds/linear/change-link.test.sh similarity index 100% rename from plugins/work-items/tools/work-item-tracker/adapters/linear/change-link.test.sh rename to plugins/work-items/tools/work-item-tracker/seeds/linear/change-link.test.sh diff --git a/plugins/work-items/tools/work-item-tracker/adapters/linear/claim.sh b/plugins/work-items/tools/work-item-tracker/seeds/linear/claim.sh similarity index 100% rename from plugins/work-items/tools/work-item-tracker/adapters/linear/claim.sh rename to plugins/work-items/tools/work-item-tracker/seeds/linear/claim.sh diff --git a/plugins/work-items/tools/work-item-tracker/adapters/linear/claim.test.sh b/plugins/work-items/tools/work-item-tracker/seeds/linear/claim.test.sh similarity index 100% rename from plugins/work-items/tools/work-item-tracker/adapters/linear/claim.test.sh rename to plugins/work-items/tools/work-item-tracker/seeds/linear/claim.test.sh diff --git a/plugins/work-items/tools/work-item-tracker/adapters/linear/common.sh b/plugins/work-items/tools/work-item-tracker/seeds/linear/common.sh old mode 100644 new mode 100755 similarity index 100% rename from plugins/work-items/tools/work-item-tracker/adapters/linear/common.sh rename to plugins/work-items/tools/work-item-tracker/seeds/linear/common.sh diff --git a/plugins/work-items/tools/work-item-tracker/adapters/linear/common.test.sh b/plugins/work-items/tools/work-item-tracker/seeds/linear/common.test.sh similarity index 100% rename from plugins/work-items/tools/work-item-tracker/adapters/linear/common.test.sh rename to plugins/work-items/tools/work-item-tracker/seeds/linear/common.test.sh diff --git a/plugins/work-items/tools/work-item-tracker/adapters/linear/create-item.sh b/plugins/work-items/tools/work-item-tracker/seeds/linear/create-item.sh similarity index 100% rename from plugins/work-items/tools/work-item-tracker/adapters/linear/create-item.sh rename to plugins/work-items/tools/work-item-tracker/seeds/linear/create-item.sh diff --git a/plugins/work-items/tools/work-item-tracker/adapters/linear/create-item.test.sh b/plugins/work-items/tools/work-item-tracker/seeds/linear/create-item.test.sh similarity index 100% rename from plugins/work-items/tools/work-item-tracker/adapters/linear/create-item.test.sh rename to plugins/work-items/tools/work-item-tracker/seeds/linear/create-item.test.sh diff --git a/plugins/work-items/tools/work-item-tracker/adapters/linear/get-item.sh b/plugins/work-items/tools/work-item-tracker/seeds/linear/get-item.sh similarity index 100% rename from plugins/work-items/tools/work-item-tracker/adapters/linear/get-item.sh rename to plugins/work-items/tools/work-item-tracker/seeds/linear/get-item.sh diff --git a/plugins/work-items/tools/work-item-tracker/adapters/linear/get-item.test.sh b/plugins/work-items/tools/work-item-tracker/seeds/linear/get-item.test.sh similarity index 100% rename from plugins/work-items/tools/work-item-tracker/adapters/linear/get-item.test.sh rename to plugins/work-items/tools/work-item-tracker/seeds/linear/get-item.test.sh diff --git a/plugins/work-items/tools/work-item-tracker/adapters/linear/link-blocks.sh b/plugins/work-items/tools/work-item-tracker/seeds/linear/link-blocks.sh similarity index 100% rename from plugins/work-items/tools/work-item-tracker/adapters/linear/link-blocks.sh rename to plugins/work-items/tools/work-item-tracker/seeds/linear/link-blocks.sh diff --git a/plugins/work-items/tools/work-item-tracker/adapters/linear/link-blocks.test.sh b/plugins/work-items/tools/work-item-tracker/seeds/linear/link-blocks.test.sh similarity index 100% rename from plugins/work-items/tools/work-item-tracker/adapters/linear/link-blocks.test.sh rename to plugins/work-items/tools/work-item-tracker/seeds/linear/link-blocks.test.sh diff --git a/plugins/work-items/tools/work-item-tracker/adapters/linear/list-items.sh b/plugins/work-items/tools/work-item-tracker/seeds/linear/list-items.sh similarity index 100% rename from plugins/work-items/tools/work-item-tracker/adapters/linear/list-items.sh rename to plugins/work-items/tools/work-item-tracker/seeds/linear/list-items.sh diff --git a/plugins/work-items/tools/work-item-tracker/adapters/linear/list-items.test.sh b/plugins/work-items/tools/work-item-tracker/seeds/linear/list-items.test.sh similarity index 99% rename from plugins/work-items/tools/work-item-tracker/adapters/linear/list-items.test.sh rename to plugins/work-items/tools/work-item-tracker/seeds/linear/list-items.test.sh index e5a83f233e3..e7c51e3e169 100755 --- a/plugins/work-items/tools/work-item-tracker/adapters/linear/list-items.test.sh +++ b/plugins/work-items/tools/work-item-tracker/seeds/linear/list-items.test.sh @@ -2,7 +2,7 @@ # shellcheck disable=SC2154 # FAILED/CASE_NUM initialized by the sourced helper # list-items: offline contract tests. Pagination, state filtering, and the scope # boundary are driven through a mocked curl — no live Linear call. -# test-scope: plugins/work-items/tools/work-item-tracker/adapters/linear/*.sh +# test-scope: plugins/work-items/tools/work-item-tracker/seeds/linear/*.sh set -uo pipefail S="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/list-items.sh" D="$(dirname "$S")" diff --git a/plugins/work-items/tools/work-item-tracker/adapters/linear/list-sub-items.sh b/plugins/work-items/tools/work-item-tracker/seeds/linear/list-sub-items.sh similarity index 100% rename from plugins/work-items/tools/work-item-tracker/adapters/linear/list-sub-items.sh rename to plugins/work-items/tools/work-item-tracker/seeds/linear/list-sub-items.sh diff --git a/plugins/work-items/tools/work-item-tracker/adapters/linear/list-sub-items.test.sh b/plugins/work-items/tools/work-item-tracker/seeds/linear/list-sub-items.test.sh similarity index 100% rename from plugins/work-items/tools/work-item-tracker/adapters/linear/list-sub-items.test.sh rename to plugins/work-items/tools/work-item-tracker/seeds/linear/list-sub-items.test.sh diff --git a/plugins/work-items/tools/work-item-tracker/adapters/linear/mock.sh b/plugins/work-items/tools/work-item-tracker/seeds/linear/mock.sh old mode 100644 new mode 100755 similarity index 100% rename from plugins/work-items/tools/work-item-tracker/adapters/linear/mock.sh rename to plugins/work-items/tools/work-item-tracker/seeds/linear/mock.sh diff --git a/plugins/work-items/tools/work-item-tracker/adapters/linear/reclaim.sh b/plugins/work-items/tools/work-item-tracker/seeds/linear/reclaim.sh similarity index 100% rename from plugins/work-items/tools/work-item-tracker/adapters/linear/reclaim.sh rename to plugins/work-items/tools/work-item-tracker/seeds/linear/reclaim.sh diff --git a/plugins/work-items/tools/work-item-tracker/adapters/linear/reclaim.test.sh b/plugins/work-items/tools/work-item-tracker/seeds/linear/reclaim.test.sh similarity index 100% rename from plugins/work-items/tools/work-item-tracker/adapters/linear/reclaim.test.sh rename to plugins/work-items/tools/work-item-tracker/seeds/linear/reclaim.test.sh diff --git a/plugins/work-items/tools/work-item-tracker/adapters/linear/renew-lease.sh b/plugins/work-items/tools/work-item-tracker/seeds/linear/renew-lease.sh similarity index 100% rename from plugins/work-items/tools/work-item-tracker/adapters/linear/renew-lease.sh rename to plugins/work-items/tools/work-item-tracker/seeds/linear/renew-lease.sh diff --git a/plugins/work-items/tools/work-item-tracker/adapters/linear/renew-lease.test.sh b/plugins/work-items/tools/work-item-tracker/seeds/linear/renew-lease.test.sh similarity index 100% rename from plugins/work-items/tools/work-item-tracker/adapters/linear/renew-lease.test.sh rename to plugins/work-items/tools/work-item-tracker/seeds/linear/renew-lease.test.sh diff --git a/plugins/work-items/tools/work-item-tracker/adapters/linear/schema-check/.gitignore b/plugins/work-items/tools/work-item-tracker/seeds/linear/schema-check/.gitignore similarity index 100% rename from plugins/work-items/tools/work-item-tracker/adapters/linear/schema-check/.gitignore rename to plugins/work-items/tools/work-item-tracker/seeds/linear/schema-check/.gitignore diff --git a/plugins/work-items/tools/work-item-tracker/adapters/linear/schema-check/README.md b/plugins/work-items/tools/work-item-tracker/seeds/linear/schema-check/README.md similarity index 98% rename from plugins/work-items/tools/work-item-tracker/adapters/linear/schema-check/README.md rename to plugins/work-items/tools/work-item-tracker/seeds/linear/schema-check/README.md index 1066c011e65..9600fdc16f3 100644 --- a/plugins/work-items/tools/work-item-tracker/adapters/linear/schema-check/README.md +++ b/plugins/work-items/tools/work-item-tracker/seeds/linear/schema-check/README.md @@ -20,7 +20,7 @@ comment ordering. ## Running it ```bash -cd "$(git rev-parse --show-toplevel)/plugins/work-items/tools/work-item-tracker/adapters/linear/schema-check" +cd "$(git rev-parse --show-toplevel)/plugins/work-items/tools/work-item-tracker/seeds/linear/schema-check" ./fetch-schema.sh # ~1.2 MB SDL, fetched not vendored npm install graphql # or have graphql >= 17 resolvable node validate.mjs # every operation must PASS diff --git a/plugins/work-items/tools/work-item-tracker/adapters/linear/schema-check/fetch-schema.sh b/plugins/work-items/tools/work-item-tracker/seeds/linear/schema-check/fetch-schema.sh similarity index 100% rename from plugins/work-items/tools/work-item-tracker/adapters/linear/schema-check/fetch-schema.sh rename to plugins/work-items/tools/work-item-tracker/seeds/linear/schema-check/fetch-schema.sh diff --git a/plugins/work-items/tools/work-item-tracker/adapters/linear/schema-check/fidelity.sh b/plugins/work-items/tools/work-item-tracker/seeds/linear/schema-check/fidelity.sh similarity index 100% rename from plugins/work-items/tools/work-item-tracker/adapters/linear/schema-check/fidelity.sh rename to plugins/work-items/tools/work-item-tracker/seeds/linear/schema-check/fidelity.sh diff --git a/plugins/work-items/tools/work-item-tracker/adapters/linear/schema-check/negative.mjs b/plugins/work-items/tools/work-item-tracker/seeds/linear/schema-check/negative.mjs similarity index 100% rename from plugins/work-items/tools/work-item-tracker/adapters/linear/schema-check/negative.mjs rename to plugins/work-items/tools/work-item-tracker/seeds/linear/schema-check/negative.mjs diff --git a/plugins/work-items/tools/work-item-tracker/adapters/linear/schema-check/validate.mjs b/plugins/work-items/tools/work-item-tracker/seeds/linear/schema-check/validate.mjs similarity index 100% rename from plugins/work-items/tools/work-item-tracker/adapters/linear/schema-check/validate.mjs rename to plugins/work-items/tools/work-item-tracker/seeds/linear/schema-check/validate.mjs diff --git a/plugins/work-items/tools/work-item-tracker/work-item-tracker.sh b/plugins/work-items/tools/work-item-tracker/work-item-tracker.sh index 702dd253c92..a05d6bd9e5a 100755 --- a/plugins/work-items/tools/work-item-tracker/work-item-tracker.sh +++ b/plugins/work-items/tools/work-item-tracker/work-item-tracker.sh @@ -123,6 +123,10 @@ main() { local adapter_dir manifest adapter_dir="$(wit_resolve_adapter_dir "$WIT_PROVIDER")" + if [[ ! -d "$adapter_dir" && -d "$SCRIPT_DIR/seeds/$WIT_PROVIDER" ]]; then + # A seed is never dispatched from the plugin (CONTRACT.md "Adapter resolution"). + fail_config "provider '$WIT_PROVIDER' ships as a seed the consuming repo owns; run /work-items:setup apply to copy it into tools/work-item-tracker/adapters/$WIT_PROVIDER/" + fi [[ -d "$adapter_dir" ]] || fail_config "no adapter for provider '$WIT_PROVIDER' (searched consumer-local then plugin-bundled) — last tried $adapter_dir" manifest="$adapter_dir/capabilities.json" diff --git a/plugins/work-items/tools/work-item-tracker/work-item-tracker.test.sh b/plugins/work-items/tools/work-item-tracker/work-item-tracker.test.sh index bfb418bcc07..2fb656e1adb 100755 --- a/plugins/work-items/tools/work-item-tracker/work-item-tracker.test.sh +++ b/plugins/work-items/tools/work-item-tracker/work-item-tracker.test.sh @@ -363,9 +363,9 @@ assert_eq "empty --body-file → exit 2" "2" "$?" run_skew bodyfake create-item --title t --body-file >/dev/null 2>&1 assert_eq "--body-file without a value → exit 2" "2" "$?" -# Every bundled branch grammar needs a delimiter after the ref, so `ENG-12oops` never -# names ENG-12, and its last capture group is the ref. -for m in "$SCRIPT_DIR"/adapters/*/capabilities.json; do +# Every bundled and seed branch grammar needs a delimiter after the ref, so `ENG-12oops` +# never names ENG-12, and its last capture group is the ref. +for m in "$SCRIPT_DIR"/adapters/*/capabilities.json "$SCRIPT_DIR"/seeds/*/capabilities.json; do p="$(jq -r '.change_link.branch_pattern' "$m")" a="$(basename "$(dirname "$m")")" case "$a" in jira | linear) ref="ENG-12" ;; *) ref="12" ;; esac diff --git a/scripts/affected-tests-no-suite.txt b/scripts/affected-tests-no-suite.txt index 1cfcbab1840..68d7fb96ae2 100644 --- a/scripts/affected-tests-no-suite.txt +++ b/scripts/affected-tests-no-suite.txt @@ -91,7 +91,7 @@ scripts/.gitattributes # freshly fetched ~1.2 MB schema and an installed graphql package, neither of # which the offline suites carry. The lane covering this class is that manual # invocation, recorded in the schema-check README's "Running it" section. -plugins/work-items/tools/work-item-tracker/adapters/linear/schema-check/* +plugins/work-items/tools/work-item-tracker/seeds/linear/schema-check/* # Docs-sync generator executed directly by the plugin-options-docs-gate CI # lane: `python3 scripts/sync-plugin-options-docs.py --check` runs this exact diff --git a/scripts/em-dash-purged-paths.txt b/scripts/em-dash-purged-paths.txt index b06acccff34..8928c7393e6 100644 --- a/scripts/em-dash-purged-paths.txt +++ b/scripts/em-dash-purged-paths.txt @@ -508,7 +508,9 @@ plugins/work-items/skills/work-loop/reference/*.md plugins/work-items/templates/*.md plugins/work-items/tools/work-item-tracker/*.md plugins/work-items/tools/work-item-tracker/adapters/*/*.md -plugins/work-items/tools/work-item-tracker/adapters/linear/schema-check/*.md +plugins/work-items/tools/work-item-tracker/seeds/*.md +plugins/work-items/tools/work-item-tracker/seeds/*/*.md +plugins/work-items/tools/work-item-tracker/seeds/linear/schema-check/*.md # The writing plugin was authored purged rather than purged later, so its # doctrine and source records are declared here alongside the README and the # skill body. The reference glob stops short of evals/, whose fixtures carry diff --git a/scripts/suite-seconds.txt b/scripts/suite-seconds.txt index d38a886646a..77fbf79794a 100644 --- a/scripts/suite-seconds.txt +++ b/scripts/suite-seconds.txt @@ -667,12 +667,6 @@ plugins/work-items/skills/work/scripts/preflight.test.sh 4 plugins/work-items/tests/floor-class-stays-routable.test.sh 1 plugins/work-items/tests/no-hardcoded-priority-scheme.test.sh 1 plugins/work-items/tests/triage-board.test.sh 1 -plugins/work-items/tools/work-item-tracker/adapters/gitea/capabilities.test.sh 1 -plugins/work-items/tools/work-item-tracker/adapters/gitea/common.test.sh 2 -plugins/work-items/tools/work-item-tracker/adapters/gitea/create-item.test.sh 2 -plugins/work-items/tools/work-item-tracker/adapters/gitea/get-item.test.sh 1 -plugins/work-items/tools/work-item-tracker/adapters/gitea/link-blocks.test.sh 1 -plugins/work-items/tools/work-item-tracker/adapters/gitea/list-items.test.sh 4 plugins/work-items/tools/work-item-tracker/adapters/github/add-sub-item.test.sh 1 plugins/work-items/tools/work-item-tracker/adapters/github/capabilities.test.sh 1 plugins/work-items/tools/work-item-tracker/adapters/github/claim.test.sh 1 @@ -686,21 +680,6 @@ plugins/work-items/tools/work-item-tracker/adapters/github/list-sub-items.test.s plugins/work-items/tools/work-item-tracker/adapters/github/reclaim.test.sh 1 plugins/work-items/tools/work-item-tracker/adapters/github/release.test.sh 1 plugins/work-items/tools/work-item-tracker/adapters/github/renew-lease.test.sh 1 -plugins/work-items/tools/work-item-tracker/adapters/jira/capabilities.test.sh 1 -plugins/work-items/tools/work-item-tracker/adapters/jira/common.test.sh 1 -plugins/work-items/tools/work-item-tracker/adapters/jira/get-item.test.sh 1 -plugins/work-items/tools/work-item-tracker/adapters/jira/list-items.test.sh 2 -plugins/work-items/tools/work-item-tracker/adapters/linear/add-sub-item.test.sh 1 -plugins/work-items/tools/work-item-tracker/adapters/linear/capabilities.test.sh 1 -plugins/work-items/tools/work-item-tracker/adapters/linear/claim.test.sh 8 -plugins/work-items/tools/work-item-tracker/adapters/linear/common.test.sh 3 -plugins/work-items/tools/work-item-tracker/adapters/linear/create-item.test.sh 4 -plugins/work-items/tools/work-item-tracker/adapters/linear/get-item.test.sh 3 -plugins/work-items/tools/work-item-tracker/adapters/linear/link-blocks.test.sh 1 -plugins/work-items/tools/work-item-tracker/adapters/linear/list-items.test.sh 2 -plugins/work-items/tools/work-item-tracker/adapters/linear/list-sub-items.test.sh 1 -plugins/work-items/tools/work-item-tracker/adapters/linear/reclaim.test.sh 5 -plugins/work-items/tools/work-item-tracker/adapters/linear/renew-lease.test.sh 2 plugins/work-items/tools/work-item-tracker/adapters/local-markdown/add-sub-item.test.sh 1 plugins/work-items/tools/work-item-tracker/adapters/local-markdown/capabilities.test.sh 1 plugins/work-items/tools/work-item-tracker/adapters/local-markdown/claim-integrity.test.sh 1 @@ -725,6 +704,28 @@ plugins/work-items/tools/work-item-tracker/lib/gh-version.test.sh 1 plugins/work-items/tools/work-item-tracker/lib/id.test.sh 1 plugins/work-items/tools/work-item-tracker/lib/json.test.sh 1 plugins/work-items/tools/work-item-tracker/lib/lease.test.sh 1 +plugins/work-items/tools/work-item-tracker/seed-copy.test.sh 1 +plugins/work-items/tools/work-item-tracker/seeds/gitea/capabilities.test.sh 1 +plugins/work-items/tools/work-item-tracker/seeds/gitea/common.test.sh 2 +plugins/work-items/tools/work-item-tracker/seeds/gitea/create-item.test.sh 2 +plugins/work-items/tools/work-item-tracker/seeds/gitea/get-item.test.sh 1 +plugins/work-items/tools/work-item-tracker/seeds/gitea/link-blocks.test.sh 1 +plugins/work-items/tools/work-item-tracker/seeds/gitea/list-items.test.sh 4 +plugins/work-items/tools/work-item-tracker/seeds/jira/capabilities.test.sh 1 +plugins/work-items/tools/work-item-tracker/seeds/jira/common.test.sh 1 +plugins/work-items/tools/work-item-tracker/seeds/jira/get-item.test.sh 1 +plugins/work-items/tools/work-item-tracker/seeds/jira/list-items.test.sh 2 +plugins/work-items/tools/work-item-tracker/seeds/linear/add-sub-item.test.sh 1 +plugins/work-items/tools/work-item-tracker/seeds/linear/capabilities.test.sh 1 +plugins/work-items/tools/work-item-tracker/seeds/linear/claim.test.sh 8 +plugins/work-items/tools/work-item-tracker/seeds/linear/common.test.sh 3 +plugins/work-items/tools/work-item-tracker/seeds/linear/create-item.test.sh 4 +plugins/work-items/tools/work-item-tracker/seeds/linear/get-item.test.sh 3 +plugins/work-items/tools/work-item-tracker/seeds/linear/link-blocks.test.sh 1 +plugins/work-items/tools/work-item-tracker/seeds/linear/list-items.test.sh 2 +plugins/work-items/tools/work-item-tracker/seeds/linear/list-sub-items.test.sh 1 +plugins/work-items/tools/work-item-tracker/seeds/linear/reclaim.test.sh 5 +plugins/work-items/tools/work-item-tracker/seeds/linear/renew-lease.test.sh 2 plugins/work-items/tools/work-item-tracker/work-item-tracker.test.sh 8 scripts/affected-tests.test.sh 9 scripts/ai-slop-report.test.sh 1