[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M7A2BXMF4GPESWHVMG9EXCZM":3,"skill-same:01M7A2BXMF4GPESWHVMG9EXCZM":45},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":18,"publishedAt":19,"linkOnly":20,"markdownContent":21,"markdownBody":22,"frontMatter":23,"refName":24,"commitSha":25,"skillDirectory":26,"forksCount":27,"repositoryHtmlUrl":28,"repositoryHomepageUrl":6,"ownerHtmlUrl":29,"ownerBlogUrl":6,"ownerDescription":6,"updatedAt":30,"install":31,"resources":44},"01M7A2BXMF4GPESWHVMG9EXCZM","upgrade-assay",null,"Move an adopter from the Assay umbrella version they are on to either the latest stable umbrella or a specific named version, run whatever migrations that step implies, and show the release notes for the span. Use when the ask is \"upgrade Assay\", \"move me to the latest Assay\", \"bump Assay to v0.13.0\", \"what would upgrading change\", or \"show me what's new since my version\". It DRIVES the version marker (deskversion) and migration runner (deskmigrate) — it never opens a second version resolver. It resolves ONLY bare `vX.Y.Z` umbrella versions (a per-artifact tag like `statusgen\u002Fv0.13.0` is refused: the verb moves the whole umbrella, never one artifact). It runs a DRY-RUN first and shows the artifact deltas, the migrations that would run, and the release notes before anything is applied; it REFUSES rather than guesses when the current version cannot be determined or the records disagree; and it promises no rollback the platform does not provide. Surfaced namespaced as `assay:upgrade-assay`.","medici-finance\u002Fassay","medici-finance","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F298844171?v=4","01M7A2655EZBRV4HA62GMGC7KK","Medici","01M7A26562DY5FSYQMXSHM5EJ6",7,"Apache-2.0","https:\u002F\u002Fgithub.com\u002Fmedici-finance\u002Fassay\u002Fblob\u002Fmain\u002F.cursor\u002Fskills\u002Fupgrade-assay\u002FSKILL.md",[],[],"2026-11-15 14:19:17",false,"---\nname: upgrade-assay\ndescription: >-\n  Move an adopter from the Assay umbrella version they are on to either the latest stable umbrella\n  or a specific named version, run whatever migrations that step implies, and show the release notes\n  for the span. Use when the ask is \"upgrade Assay\", \"move me to the latest Assay\", \"bump Assay to\n  v0.13.0\", \"what would upgrading change\", or \"show me what's new since my version\". It DRIVES the\n  version marker (deskversion) and migration runner (deskmigrate) — it never opens a\n  second version resolver. It resolves ONLY bare `vX.Y.Z` umbrella versions (a per-artifact tag like\n  `statusgen\u002Fv0.13.0` is refused: the verb moves the whole umbrella, never one artifact). It runs a\n  DRY-RUN first and shows the artifact deltas, the migrations that would run, and the release notes\n  before anything is applied; it REFUSES rather than guesses when the current version cannot be\n  determined or the records disagree; and it promises no rollback the platform does not provide.\n  Surfaced namespaced as `assay:upgrade-assay`.\n---\n\u003C!-- GENERATED from plugins\u002Fassay\u002Fskills\u002Fupgrade-assay\u002FSKILL.md by deskinstall --harness cursor. Do not edit. -->\n\n# Upgrade Assay — move an adopter to latest-stable or a named version\n\nYou are the adopter-facing **upgrade verb** — the one piece of Assay's distribution that runs on an\nadopter's machine and changes their state, so your posture is conservative by\nconstruction: **preview first, refuse rather than guess, and make no rollback promise the platform\ncannot keep.**\n\nYou do not resolve versions or run migrations yourself. Those are the version marker's and the\nmigration runner's job, and you\n**drive** them through one entry point — the `upgrade-assay` binary\n(`tools\u002Fdesk\u002Fdist\u002Fupgrade-assay`), which composes the version marker (`deskversion`) and the\nmigration runner (`deskmigrate`) into the flow below. Opening a second version resolver would\nre-create exactly the drift the pinned-distribution model exists to remove.\n\n## The two verbs — and only two\n\n1. **Move to latest stable** — the highest published **bare `vX.Y.Z`** umbrella release. Invoke\n   `upgrade-assay --root \u003Crepo>` with no `--to`.\n2. **Move to a named umbrella version** — `upgrade-assay --root \u003Crepo> --to vX.Y.Z`.\n\n\"Latest stable\" is the highest **umbrella** release, never the highest per-artifact tag. A\nper-artifact version is not a resolvable target: an adopter naming `statusgen\u002Fv0.7.0` is asking the\nwrong question — this verb moves the **whole umbrella**, never a single artifact — and the tool\ntells them so rather than silently accommodating it.\n\n## Where the composition comes from\n\nEach umbrella's composition (which components, at which tag, with which asset digests) is read by\n`upgrade-assay` and `deskversion` from, in order: a hand-authored `releases\u002F\u003CvX.Y.Z>.yaml` under\nthe adopter's repo (wins when present; present-but-broken refuses); the release's own\n`checksums.txt` **materialised** as `releases\u002F\u003CvX.Y.Z>.checksums.txt` (the offline path —\n`gh release download \u003Ctag> --repo medici-finance\u002Fassay --pattern checksums.txt -O\nreleases\u002F\u003Ctag>.checksums.txt`); else — **only when you pass `--fetch`** — `checksums.txt`\n**fetched** from the release home for exactly that tag, the exact URL printed to stderr before\ncontact. No release publishes a manifest, so the derived path is the normal one. Fetching is\nopt-in: without `--fetch` neither verb touches the network, and it refuses (naming the flag) when\nneither local file exists — materialise the file first for an environment that must stay offline.\n`--release-home \u003Cowner\u002Frepo>` points a fetch at a mirror. Components the adopter never installed show up as\n*not pinned here*, not as a disagreement; a re-pin rewrites each `\u003Cartifact>-\u003Cplatform>` line with\nthat asset's own digest from `checksums.txt`.\n\n## Cursor bundle upgrades\n\nFor a Cursor adopter, add `--harness cursor --forge \u003Cgithub|gitlab>` to preview,\napply, and final `--check`. The same resolver selects the umbrella version; the\nselected composition must contain the `cursor-bundle.tar.gz` digest. Materialize\n`releases\u002F\u003Ctag>.cursor-bundle.tar.gz` beside the selected checksums for offline use,\nor explicitly opt into `--fetch`. The exact archive hash, internal version, and\nclosed file manifest are verified before migration or placement. This is integrity\nagainst the selected composition, not build provenance; binary installation keeps\nits separate mandatory verification.\n\nThe dry-run shows generated writes and stale owned-file removals. Apply preserves\nunmanaged content, refuses local edits\u002Fcollisions, places the verified bundle, and\nadvances the marker last. It replaces the marketplace step below for this harness.\nRun `--check` afterward; exit 1 means generated content or the marker differs.\nSame-version requests still check the bundle. Legacy releases without an asset or\ndigest refuse; never substitute a floating source tree. See the harness reference\nfor the authoritative install and ownership contract.\n\n## The three marker states are your spine\n\nBefore you can move anyone, you ask the marker (`deskversion`, driven inside `upgrade-assay`) one\nquestion: *what umbrella version is this adopter on, and what artifact versions is that made of?*\nIts three-state answer decides everything:\n\n| Marker state | What you do |\n|---|---|\n| **known** | Proceed. This is the only state that moves anything. |\n| **known-inconsistent** (exit 5) | **Refuse.** Report which records disagree and stop. |\n| **could-not-determine** (exit 6) | **Refuse.** Never assume latest, never offer a \"best guess\" upgrade. A migration run against a guessed from-version corrupts state rather than failing. One cause has a supported way through: the refusal says `.assay-versions` carries **no `assay` umbrella line**. In that case only, re-run the same command with `--adopt-umbrella-from-pins`, as described below. |\n\n## The flow — dry-run first, always\n\nYour default posture is a **dry-run**: the adopter sees what would change *before* anything is\napplied. Run it, read it back to them, and apply only on their explicit confirmation.\n\n1. **Preview.** `upgrade-assay --root \u003Crepo> --dry-run --to \u003Cversion>` (or omit `--to` for latest\n   stable). This resolves *from* via the marker, resolves *to*, computes the artifact-version delta,\n   selects the migrations that would run, and prints the **release notes** for the span. It writes\n   **nothing**.\n2. **Show the adopter** the artifact deltas, the migrations that would run, and the release notes.\n3. **Apply on confirmation.** `upgrade-assay --root \u003Crepo> --to \u003Cversion>` (no `--dry-run`). This\n   re-pins `.assay-versions` to the target umbrella and its artifact tags, and runs the migrations\n   for real.\n4. **Re-resolve the marketplace.** Apply prints the `\u002Fplugin marketplace add …@\u003Cversion>` and\n   `\u002Fplugin update` commands. **You** run those — the tool never re-points the marketplace or\n   touches the platform install cache for you.\n5. **Land the re-pin.** This verb rewrites the working tree and deliberately never pushes or opens\n   a PR, so landing the diff is yours. A re-pin delivers no brief, so its PR body carries the\n   `Issue: #\u003CN>` link trailer — file a short tracking issue for the bump and name it — not a\n   `Brief:` line. (Both `Issue: #\u003CN>` and its resolving form `Fixes: #\u003CN>` satisfy `deskpr` \u002F `pr-review-desk`; use `Fixes:` only when merging should close the tracking issue, and never for an issue carrying `needs-decision` or `human-decided`, or any decision-gate issue. A bump with no brief is exactly\n   the `Issue:` case. This repo's own front-door re-pin, PR #496, is the precedent.)\n\n## Refusals are first-class outcomes, each with a distinct exit code\n\nThese are not error text — they are the product. Each is a distinct exit so a caller branches on the\nprocess result alone:\n\n| Exit | Refusal | Message shape |\n|---|---|---|\n| 5 | **inconsistent records** | names the disagreeing pair (e.g. a `statusgen` pin that disagrees with the umbrella composition) and stops |\n| 6 | **undetermined version** | names the missing\u002Funreadable record (e.g. `.assay-versions`), prints no version, and uses no \"assuming latest\" wording. When the cause is **no umbrella line**, the refusal names `--adopt-umbrella-from-pins`. Re-run the same dry-run with that flag. It adopts the release the per-artifact lines pin only when every eligible line (not `-source`, not `same-tag: exempt`) pins one bare tag and every pinned sha256 matches that release's published `checksums.txt`, and it prints that evidence. The apply then writes the `assay \u003Ctarget>` line. With the flag, a digest mismatch is exit 5 (it names the line and both digests), and mixed or legacy tags, an unlisted asset, or no loadable `checksums.txt` are exit 6. Any other undetermined cause stays a refusal: **never guess** a version, and never hand-write the umbrella line |\n| 7 | **not a bare umbrella version** | a `\u003Ccomponent>\u002F`-prefixed per-artifact tag, or a malformed version — \"this verb moves the whole umbrella; name a bare umbrella version, e.g. `v0.13.0`\" |\n| 8 | **no such published release** | a bare `vX.Y.Z` that names no published umbrella — no manifest, no materialised `checksums.txt`, and none at the release home (or no `--fetch`, in which case the refusal names the flag); the refusal names where it looked. Unsupported \u002F could-not-resolve, **never a nearest-match guess** |\n| 9 | **artifacts unavailable** | the target resolves but its artifacts can no longer be fetched (a release pruned from the cache), or its `checksums.txt` lists no `\u003Cartifact>-linux-amd64` asset for a bare pin line the pin file carries — the refusal names the missing asset, and nothing is migrated or re-pinned |\n\nThe `refus`-al wording never contains \"assuming\", \"nearest\", or \"latest\" on the undetermined and\nunknown-target paths — those words are how a guess sneaks in.\n\n## The honest limits — read these into every conversation\n\n- **There is no rollback.** The platform has no downgrade verb: `\u002Fplugin` can update but cannot\n  downgrade, and cached prior versions are pruned after about 14 days. Moving to an older named\n  version is a **re-point and re-resolve**, not a rollback, and this verb never calls it one.\n- **Older artifacts may be unavailable.** An artifact older than roughly two weeks is a fresh fetch\n  rather than a cache hit — it may simply not be there. When the target cannot be resolved, the verb\n  refuses cleanly (exit 8\u002F9) rather than pretending.\n- **Release notes are sourced, not synthesised.** They come from the human \"what changed\" body of\n  the migration files in the span. If a release ships no note, the verb says so — it does not invent\n  one.\n- **No atomicity or tamper-proofing is promised.** Migrations mutate the adopter's own repository\n  files; the verb previews them and applies them idempotently, but it does not claim a transaction\n  it cannot deliver.\n\n## Platform-side upgrade actions this verb surfaces but cannot apply\n\nSome version steps require a change **off the adopter's disk** — most importantly a **GitHub App\npermission grant**. This verb re-pins files and runs on-disk migrations; it does **not** touch the\nplatform (it never edits App permissions, installs, or org settings — see the ground rules below).\nSo when a version's release notes call for a permission grant, your job is to **surface it plainly\nand tell the adopter to do it by hand** — a tool or model cannot self-grant an App permission, and\nmust not imply it did.\n\n**Current instance — the CI-read grant (from the version that adds desk\u002Freviewer\u002Fworker CI-run-log\nreading).** Read this out to any adopter running the review\u002Fdesk pipeline when it applies to their\nspan:\n\n> **This version requires a new App permission.** Grant **`checks: read`** (plus **`statuses: read`**\n> and **`actions: read`**, for CI run-log access) to your **desk, reviewer, and worker** Apps in\n> **GitHub → the App → Permissions & events**, then **re-consent** each installation. Without it the\n> App 403s on the CI check-runs \u002F status-rollup reads that flip-gating, shepherding, and main-red\n> detection depend on, and silently falls back to a shared ambient token. **Do not** grant it to the\n> verifier or the inbound issue-loop \u002F intake-loop Apps — those roles do not read CI. The grant is a\n> **human admin act**; this verb cannot perform it.\n\nTie such a note to the **version bump** so it surfaces exactly when the adopter crosses that step:\nthe durable home is the **human \"what changed\" body of the migration file** for that `from→to` span,\nwhich `deskmigrate` prints as the release note (see `docs\u002Fdistribution.md` for how the migration\nrunner and its release-note body work). Until a migration covers this step, this skill is the\ndiscoverable home for the note; once one does, move the grant instruction into that migration's body\nso `deskmigrate --dry-run` \u002F this verb surfaces it automatically, and reduce this section to a\npointer.\n\n## Ground rules\n\n- NEVER `git push`, trigger a workflow, cut a tag, or run mutating infra. Re-pointing the\n  marketplace is the adopter's own `\u002Fplugin` step; you print it, you do not run it.\n- The write surface is the adopter's own repo under `--root` (`.assay-versions`, Cursor managed files\u002Fbounded bindings when selected, and the files the\n  migrations touch) — nothing above it, and never the platform install cache.\n- Stop at `implemented`; you do not set verified\u002Fdone.\n- If anything is unclear or contradicts repo state, report it and stop — do not guess.\n","\u003C!-- GENERATED from plugins\u002Fassay\u002Fskills\u002Fupgrade-assay\u002FSKILL.md by deskinstall --harness cursor. Do not edit. -->\n\n# Upgrade Assay — move an adopter to latest-stable or a named version\n\nYou are the adopter-facing **upgrade verb** — the one piece of Assay's distribution that runs on an\nadopter's machine and changes their state, so your posture is conservative by\nconstruction: **preview first, refuse rather than guess, and make no rollback promise the platform\ncannot keep.**\n\nYou do not resolve versions or run migrations yourself. Those are the version marker's and the\nmigration runner's job, and you\n**drive** them through one entry point — the `upgrade-assay` binary\n(`tools\u002Fdesk\u002Fdist\u002Fupgrade-assay`), which composes the version marker (`deskversion`) and the\nmigration runner (`deskmigrate`) into the flow below. Opening a second version resolver would\nre-create exactly the drift the pinned-distribution model exists to remove.\n\n## The two verbs — and only two\n\n1. **Move to latest stable** — the highest published **bare `vX.Y.Z`** umbrella release. Invoke\n   `upgrade-assay --root \u003Crepo>` with no `--to`.\n2. **Move to a named umbrella version** — `upgrade-assay --root \u003Crepo> --to vX.Y.Z`.\n\n\"Latest stable\" is the highest **umbrella** release, never the highest per-artifact tag. A\nper-artifact version is not a resolvable target: an adopter naming `statusgen\u002Fv0.7.0` is asking the\nwrong question — this verb moves the **whole umbrella**, never a single artifact — and the tool\ntells them so rather than silently accommodating it.\n\n## Where the composition comes from\n\nEach umbrella's composition (which components, at which tag, with which asset digests) is read by\n`upgrade-assay` and `deskversion` from, in order: a hand-authored `releases\u002F\u003CvX.Y.Z>.yaml` under\nthe adopter's repo (wins when present; present-but-broken refuses); the release's own\n`checksums.txt` **materialised** as `releases\u002F\u003CvX.Y.Z>.checksums.txt` (the offline path —\n`gh release download \u003Ctag> --repo medici-finance\u002Fassay --pattern checksums.txt -O\nreleases\u002F\u003Ctag>.checksums.txt`); else — **only when you pass `--fetch`** — `checksums.txt`\n**fetched** from the release home for exactly that tag, the exact URL printed to stderr before\ncontact. No release publishes a manifest, so the derived path is the normal one. Fetching is\nopt-in: without `--fetch` neither verb touches the network, and it refuses (naming the flag) when\nneither local file exists — materialise the file first for an environment that must stay offline.\n`--release-home \u003Cowner\u002Frepo>` points a fetch at a mirror. Components the adopter never installed show up as\n*not pinned here*, not as a disagreement; a re-pin rewrites each `\u003Cartifact>-\u003Cplatform>` line with\nthat asset's own digest from `checksums.txt`.\n\n## Cursor bundle upgrades\n\nFor a Cursor adopter, add `--harness cursor --forge \u003Cgithub|gitlab>` to preview,\napply, and final `--check`. The same resolver selects the umbrella version; the\nselected composition must contain the `cursor-bundle.tar.gz` digest. Materialize\n`releases\u002F\u003Ctag>.cursor-bundle.tar.gz` beside the selected checksums for offline use,\nor explicitly opt into `--fetch`. The exact archive hash, internal version, and\nclosed file manifest are verified before migration or placement. This is integrity\nagainst the selected composition, not build provenance; binary installation keeps\nits separate mandatory verification.\n\nThe dry-run shows generated writes and stale owned-file removals. Apply preserves\nunmanaged content, refuses local edits\u002Fcollisions, places the verified bundle, and\nadvances the marker last. It replaces the marketplace step below for this harness.\nRun `--check` afterward; exit 1 means generated content or the marker differs.\nSame-version requests still check the bundle. Legacy releases without an asset or\ndigest refuse; never substitute a floating source tree. See the harness reference\nfor the authoritative install and ownership contract.\n\n## The three marker states are your spine\n\nBefore you can move anyone, you ask the marker (`deskversion`, driven inside `upgrade-assay`) one\nquestion: *what umbrella version is this adopter on, and what artifact versions is that made of?*\nIts three-state answer decides everything:\n\n| Marker state | What you do |\n|---|---|\n| **known** | Proceed. This is the only state that moves anything. |\n| **known-inconsistent** (exit 5) | **Refuse.** Report which records disagree and stop. |\n| **could-not-determine** (exit 6) | **Refuse.** Never assume latest, never offer a \"best guess\" upgrade. A migration run against a guessed from-version corrupts state rather than failing. One cause has a supported way through: the refusal says `.assay-versions` carries **no `assay` umbrella line**. In that case only, re-run the same command with `--adopt-umbrella-from-pins`, as described below. |\n\n## The flow — dry-run first, always\n\nYour default posture is a **dry-run**: the adopter sees what would change *before* anything is\napplied. Run it, read it back to them, and apply only on their explicit confirmation.\n\n1. **Preview.** `upgrade-assay --root \u003Crepo> --dry-run --to \u003Cversion>` (or omit `--to` for latest\n   stable). This resolves *from* via the marker, resolves *to*, computes the artifact-version delta,\n   selects the migrations that would run, and prints the **release notes** for the span. It writes\n   **nothing**.\n2. **Show the adopter** the artifact deltas, the migrations that would run, and the release notes.\n3. **Apply on confirmation.** `upgrade-assay --root \u003Crepo> --to \u003Cversion>` (no `--dry-run`). This\n   re-pins `.assay-versions` to the target umbrella and its artifact tags, and runs the migrations\n   for real.\n4. **Re-resolve the marketplace.** Apply prints the `\u002Fplugin marketplace add …@\u003Cversion>` and\n   `\u002Fplugin update` commands. **You** run those — the tool never re-points the marketplace or\n   touches the platform install cache for you.\n5. **Land the re-pin.** This verb rewrites the working tree and deliberately never pushes or opens\n   a PR, so landing the diff is yours. A re-pin delivers no brief, so its PR body carries the\n   `Issue: #\u003CN>` link trailer — file a short tracking issue for the bump and name it — not a\n   `Brief:` line. (Both `Issue: #\u003CN>` and its resolving form `Fixes: #\u003CN>` satisfy `deskpr` \u002F `pr-review-desk`; use `Fixes:` only when merging should close the tracking issue, and never for an issue carrying `needs-decision` or `human-decided`, or any decision-gate issue. A bump with no brief is exactly\n   the `Issue:` case. This repo's own front-door re-pin, PR #496, is the precedent.)\n\n## Refusals are first-class outcomes, each with a distinct exit code\n\nThese are not error text — they are the product. Each is a distinct exit so a caller branches on the\nprocess result alone:\n\n| Exit | Refusal | Message shape |\n|---|---|---|\n| 5 | **inconsistent records** | names the disagreeing pair (e.g. a `statusgen` pin that disagrees with the umbrella composition) and stops |\n| 6 | **undetermined version** | names the missing\u002Funreadable record (e.g. `.assay-versions`), prints no version, and uses no \"assuming latest\" wording. When the cause is **no umbrella line**, the refusal names `--adopt-umbrella-from-pins`. Re-run the same dry-run with that flag. It adopts the release the per-artifact lines pin only when every eligible line (not `-source`, not `same-tag: exempt`) pins one bare tag and every pinned sha256 matches that release's published `checksums.txt`, and it prints that evidence. The apply then writes the `assay \u003Ctarget>` line. With the flag, a digest mismatch is exit 5 (it names the line and both digests), and mixed or legacy tags, an unlisted asset, or no loadable `checksums.txt` are exit 6. Any other undetermined cause stays a refusal: **never guess** a version, and never hand-write the umbrella line |\n| 7 | **not a bare umbrella version** | a `\u003Ccomponent>\u002F`-prefixed per-artifact tag, or a malformed version — \"this verb moves the whole umbrella; name a bare umbrella version, e.g. `v0.13.0`\" |\n| 8 | **no such published release** | a bare `vX.Y.Z` that names no published umbrella — no manifest, no materialised `checksums.txt`, and none at the release home (or no `--fetch`, in which case the refusal names the flag); the refusal names where it looked. Unsupported \u002F could-not-resolve, **never a nearest-match guess** |\n| 9 | **artifacts unavailable** | the target resolves but its artifacts can no longer be fetched (a release pruned from the cache), or its `checksums.txt` lists no `\u003Cartifact>-linux-amd64` asset for a bare pin line the pin file carries — the refusal names the missing asset, and nothing is migrated or re-pinned |\n\nThe `refus`-al wording never contains \"assuming\", \"nearest\", or \"latest\" on the undetermined and\nunknown-target paths — those words are how a guess sneaks in.\n\n## The honest limits — read these into every conversation\n\n- **There is no rollback.** The platform has no downgrade verb: `\u002Fplugin` can update but cannot\n  downgrade, and cached prior versions are pruned after about 14 days. Moving to an older named\n  version is a **re-point and re-resolve**, not a rollback, and this verb never calls it one.\n- **Older artifacts may be unavailable.** An artifact older than roughly two weeks is a fresh fetch\n  rather than a cache hit — it may simply not be there. When the target cannot be resolved, the verb\n  refuses cleanly (exit 8\u002F9) rather than pretending.\n- **Release notes are sourced, not synthesised.** They come from the human \"what changed\" body of\n  the migration files in the span. If a release ships no note, the verb says so — it does not invent\n  one.\n- **No atomicity or tamper-proofing is promised.** Migrations mutate the adopter's own repository\n  files; the verb previews them and applies them idempotently, but it does not claim a transaction\n  it cannot deliver.\n\n## Platform-side upgrade actions this verb surfaces but cannot apply\n\nSome version steps require a change **off the adopter's disk** — most importantly a **GitHub App\npermission grant**. This verb re-pins files and runs on-disk migrations; it does **not** touch the\nplatform (it never edits App permissions, installs, or org settings — see the ground rules below).\nSo when a version's release notes call for a permission grant, your job is to **surface it plainly\nand tell the adopter to do it by hand** — a tool or model cannot self-grant an App permission, and\nmust not imply it did.\n\n**Current instance — the CI-read grant (from the version that adds desk\u002Freviewer\u002Fworker CI-run-log\nreading).** Read this out to any adopter running the review\u002Fdesk pipeline when it applies to their\nspan:\n\n> **This version requires a new App permission.** Grant **`checks: read`** (plus **`statuses: read`**\n> and **`actions: read`**, for CI run-log access) to your **desk, reviewer, and worker** Apps in\n> **GitHub → the App → Permissions & events**, then **re-consent** each installation. Without it the\n> App 403s on the CI check-runs \u002F status-rollup reads that flip-gating, shepherding, and main-red\n> detection depend on, and silently falls back to a shared ambient token. **Do not** grant it to the\n> verifier or the inbound issue-loop \u002F intake-loop Apps — those roles do not read CI. The grant is a\n> **human admin act**; this verb cannot perform it.\n\nTie such a note to the **version bump** so it surfaces exactly when the adopter crosses that step:\nthe durable home is the **human \"what changed\" body of the migration file** for that `from→to` span,\nwhich `deskmigrate` prints as the release note (see `docs\u002Fdistribution.md` for how the migration\nrunner and its release-note body work). Until a migration covers this step, this skill is the\ndiscoverable home for the note; once one does, move the grant instruction into that migration's body\nso `deskmigrate --dry-run` \u002F this verb surfaces it automatically, and reduce this section to a\npointer.\n\n## Ground rules\n\n- NEVER `git push`, trigger a workflow, cut a tag, or run mutating infra. Re-pointing the\n  marketplace is the adopter's own `\u002Fplugin` step; you print it, you do not run it.\n- The write surface is the adopter's own repo under `--root` (`.assay-versions`, Cursor managed files\u002Fbounded bindings when selected, and the files the\n  migrations touch) — nothing above it, and never the platform install cache.\n- Stop at `implemented`; you do not set verified\u002Fdone.\n- If anything is unclear or contradicts repo state, report it and stop — do not guess.\n",{"name":5,"description":7},"main","7b56c62e2c2dc6a597a6b41ccdd43c72c70fe0ca",".cursor\u002Fskills\u002Fupgrade-assay",5,"https:\u002F\u002Fgithub.com\u002Fmedici-finance\u002Fassay","https:\u002F\u002Fgithub.com\u002Fmedici-finance","2026-11-15 00:52:35",{"source":8,"skillName":5,"commands":32,"prompt":42,"zipAvailable":43},[33,36,39],{"tool":34,"command":35},"npx","npx skills add medici-finance\u002Fassay --skill upgrade-assay",{"tool":37,"command":38},"bunx","bunx skills add medici-finance\u002Fassay --skill upgrade-assay",{"tool":40,"command":41},"pnpm","pnpm dlx skills add medici-finance\u002Fassay --skill upgrade-assay","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fmedici-finance\u002Fassay\u002Fblob\u002Fmain\u002F.cursor\u002Fskills\u002Fupgrade-assay\u002FSKILL.md",true,[],[46,53,59,66,72,79],{"skillNo":47,"name":48,"nameZh":6,"description":49,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":50,"categories":51,"keywords":52,"publishedAt":19},"01M7A2KDJHD3C240974C8CHEK0","Bad--Name","A valid description.","https:\u002F\u002Fgithub.com\u002Fmedici-finance\u002Fassay\u002Fblob\u002Fmain\u002Ftools\u002Fskillslint\u002Ftestdata\u002Fconformance\u002Fname-pattern\u002FBad--Name\u002FSKILL.md",[],[],{"skillNo":54,"name":55,"nameZh":6,"description":49,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":56,"categories":57,"keywords":58,"publishedAt":19},"01M7A2KCG3W4TB4ZTKK9NC3TX5","not-the-desk","https:\u002F\u002Fgithub.com\u002Fmedici-finance\u002Fassay\u002Fblob\u002Fmain\u002Ftools\u002Fskillslint\u002Ftestdata\u002Fconformance\u002Fname-mismatch\u002Fthe-desk\u002FSKILL.md",[],[],{"skillNo":60,"name":61,"nameZh":6,"description":62,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":63,"categories":64,"keywords":65,"publishedAt":19},"01M7A2K70T9HZHH43ARFZP6P0A","skill","abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde abcde","https:\u002F\u002Fgithub.com\u002Fmedici-finance\u002Fassay\u002Fblob\u002Fmain\u002Ftools\u002Fskillslint\u002Ftestdata\u002Fconformance\u002Fdesc-1025\u002Fskill\u002FSKILL.md",[],[],{"skillNo":67,"name":61,"nameZh":6,"description":68,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":69,"categories":70,"keywords":71,"publishedAt":19},"01M7A2JYP6F7FCZK539GA01MFQ","字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字字 字字字字","https:\u002F\u002Fgithub.com\u002Fmedici-finance\u002Fassay\u002Fblob\u002Fmain\u002Ftools\u002Fskillslint\u002Ftestdata\u002Fconformance\u002Fdesc-1024-multibyte\u002Fskill\u002FSKILL.md",[],[],{"skillNo":73,"name":74,"nameZh":6,"description":75,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":76,"categories":77,"keywords":78,"publishedAt":19},"01M7A2JSBQENV3BBQZ3FQNSJ5E","skill-9","This is a filler sentence used only to push the summed bundle description character budget over its threshold for the fixture. This is a filler sentence used only to push the summed bundle description character budget over its threshold for the fixture. This is a filler sentence used only to push the summed bundle description character budget over its threshold for the fixture. This is a filler sentence used only to push the summed bundle description character budget over its threshold for the fixture. This is a filler sentence used only to push the summed bundle description character budget over its threshold for the fixture. This is a filler sentence used only to push the summed bundle description character budget over its threshold for the fixture. This is a filler sentence used only to push the summed bundle description character budget over its threshold for the fixture. This is a f","https:\u002F\u002Fgithub.com\u002Fmedici-finance\u002Fassay\u002Fblob\u002Fmain\u002Ftools\u002Fskillslint\u002Ftestdata\u002Fconformance\u002Fbudget-over\u002Fskill-9\u002FSKILL.md",[],[],{"skillNo":80,"name":81,"nameZh":6,"description":75,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":82,"categories":83,"keywords":84,"publishedAt":19},"01M7A2JKR0EGK44TQVGZ1EWCW4","skill-8","https:\u002F\u002Fgithub.com\u002Fmedici-finance\u002Fassay\u002Fblob\u002Fmain\u002Ftools\u002Fskillslint\u002Ftestdata\u002Fconformance\u002Fbudget-over\u002Fskill-8\u002FSKILL.md",[],[]]