Bump MDK versions
Raise version(s) across the monorepo and regenerate every lockfile that
cascades from the change, so the lockfile-sync gate stays green.
Target version: $ARGUMENTS (e.g. 0.4.0, optionally a scope). If empty, ask
what to bump to and confirm scope.
The gate this satisfies
CI runs, per package, from that package's directory:
npm install --package-lock-only --ignore-scripts --no-audit --no-fund
git diff --exit-code <that>/package-lock.json
If regenerating the lock produces any diff, the job fails with
package-lock.json is out of sync with package.json. So the rule is simple:
after editing any package.json, regenerate the locks and commit a tree where
that command yields zero diff.
⚠️ The gate does NOT cover examples/. The lockfile-consistency step runs
only for the ui, core and workers installs; the examples job warms the dependency
cache without checking anything. Example locks can therefore drift for entire
release cycles without turning CI red — one has been found carrying embedded ui
versions two releases stale. Always regenerate and inspect the examples/* locks
yourself; never infer they are clean from a green CI run.
Repo version topology (why one bump cascades)
Packages are wired together with file: links, so bumping one package's version
changes the embedded version recorded in the lockfile of every package that
links it. The drift is often NOT in a lock's own version field — it's in the
embedded file:-dependency entries.
ui/ — a single npm workspace (Turborepo) with one root
ui/package-lock.json. Members: packages/{ui-foundation,react-adapter,react-devkit,ui-agent,cli,fonts}
and apps/*. Dep flow: react-devkit → react-adapter → ui-foundation.
- ⚠️
packages/react-devkit/package.json pins internal deps with ranges
(@tetherto/mdk-react-adapter: ^X, @tetherto/mdk-ui-foundation: ^X). When
you bump those packages, bump these ^ ranges too, or npm install fails
with 404 … @tetherto/mdk-* @^X not found (it falls through to the registry
because nothing local satisfies the range). cli similarly pins
@tetherto/mdk-react-devkit: ^X.
- Regenerate the whole workspace with one command:
cd ui && npm install --package-lock-only ….
backend/core/* — independent packages, each with its own lock:
agent, client, plugins, gateway, mdk, kernel, mcp, mdk-worker.
- ⚠️
agent is NOT a repo-root workspace member — it installs standalone via
backend/core/install-packages.sh, which also symlinks it into the root
node_modules/@tetherto/ so the gateway plugin can resolve it by name. Do not
"fix" this by adding it to the root workspaces: npm then claims the tree,
prunes the standalone install and does not rebuild it — measured as "removed
407 packages, added 0", silently, with no error to explain it.
gateway file:-links client and plugins, so bumping client/plugins
forces a gateway lock regen.
backend/plugins/* — gateway plugins, own locks, versioned independently of
the release line (agent is at 0.1.0 while the repo is at 0.6.0), so a
release bump does not touch them. Their @tetherto/* deps are linked, never
fetched, so pin them at *: a version range there resolves against nothing and
rots unnoticed — the agent plugin sat at ^0.0.0 across several releases with
nothing to catch it.
backend/workers/* — independent worker packages, own locks.
examples/* — many independent packages, own locks. The UI examples
(examples/full-site/ui, examples/mvp-site/ui, examples/mdk-ui-shell-template)
file:-link the ui/packages/* packages, so a ui bump cascades into their locks.
Because the CI gate skips examples/, these are the locks most likely to be stale.
As of the 0.6.0 cycle versioning is uniform across the release line: every
tracked package sits at the same version, mdk-worker no longer lags, and
mdk-ui-shell-template has moved off its scaffold default onto the shared
version — treat it as a normal package in future bumps. Packages outside the
release line (backend/plugins/*) keep their own versions.
Still never blanket-set versions by find-and-replace. Confirm the current spread
first by parsing each manifest, not grepping it (a bare grep -m1 '"version"' can
latch onto a nested field):
for f in $(git ls-files '*package.json'); do
node -e 'const j=require("fs").readFileSync(process.argv[1],"utf8");const o=JSON.parse(j);console.log(`${o.version??"<none>"}\t${process.argv[1]}`)' "$f"
done | sort | grep -v '^<target>' # lists everything not already at the target
Procedure
Confirm target + scope. Which packages move to which version. If fixing a
red CI gate, first read the failing job log — it names the package and shows
the -/+ "version" diff telling you which value is intended.
Edit package.json version fields for the in-scope packages (use Edit
on the exact "version": "…" line). If bumping a ui package that others pin
by range, also bump those ^ ranges (react-devkit, cli — see topology).
Regenerate locks where things changed. Fast path — regenerate the ui
workspace once and every backend/examples package dir:
cd "$REPO/ui" && npm install --package-lock-only --ignore-scripts --no-audit --no-fund
cd "$REPO"
git ls-files 'backend/**/package-lock.json' 'examples/**/package-lock.json' \
| xargs -n1 dirname | sort -u \
| while IFS= read -r d; do (cd "$d" && npm install --package-lock-only --ignore-scripts --no-audit --no-fund >/dev/null 2>&1); done
Verify — the CI-equivalent drift check across ALL locks:
git ls-files '*package-lock.json' | xargs -n1 dirname | sort -u | while IFS= read -r d; do
( cd "$d" && npm install --package-lock-only --ignore-scripts --no-audit --no-fund >/dev/null 2>&1 )
git -C "$REPO" diff --quiet -- "$d/package-lock.json" || echo "STILL DRIFTS: $d"
done
git status --porcelain | grep package-lock.json # the full set that changed
Zero STILL DRIFTS lines = CI's per-package gate will pass.
Sanity the diffs. Every changed lock line should be a version/range sync
(0.3.0→0.4.0, ^0.3.0→^0.4.0) or benign format normalization (npm
populating an empty "packages": {}). Anything else (new deps, removed deps,
integrity churn) means something beyond a version bump moved — investigate.
Commit & push only with explicit permission. Stage package.json +
package-lock.json together so the tree is always self-consistent.
Gotchas seen in the wild
- A merge that takes one branch's
package.json but another's package-lock.json
yields pkg=0.3.0 / lock=0.4.0. Decide the intended value (ask / read the
release), fix package.json to it, then regen.
- Bumping a ui package but forgetting react-devkit's
^ pins → npm install
404s. Always bump the range pins in lockstep.
- Checking only a lock's root
version misses embedded file:-dep drift
(gateway, example UIs). Always run the full per-lock regen in step 4.
- Node/npm version differences can reshuffle lock formatting. Use the repo's
expected Node (CI uses
lts/*, engines want >=24).
1---2name: bump-mdk3description: Bump package version(s) across the MDK monorepo and resync every affected lockfile the way CI expects. Use when raising a release version, when the "package-lock.json is out of sync with package.json" gate is red, or after a merge that left package.json / package-lock.json versions mismatched.4---56# Bump MDK versions78Raise version(s) across the monorepo and regenerate **every** lockfile that9cascades from the change, so the lockfile-sync gate stays green.1011Target version: **$ARGUMENTS** (e.g. `0.4.0`, optionally a scope). If empty, ask12what to bump to and confirm scope.1314## The gate this satisfies1516CI runs, **per package**, from that package's directory:1718```bash19npm install --package-lock-only --ignore-scripts --no-audit --no-fund20git diff --exit-code <that>/package-lock.json21```2223If regenerating the lock produces *any* diff, the job fails with24`package-lock.json is out of sync with package.json`. So the rule is simple:25**after editing any `package.json`, regenerate the locks and commit a tree where26that command yields zero diff.**2728⚠️ **The gate does NOT cover `examples/`.** The lockfile-consistency step runs29only for the ui, core and workers installs; the examples job warms the dependency30cache without checking anything. Example locks can therefore drift for entire31release cycles without turning CI red — one has been found carrying embedded ui32versions two releases stale. Always regenerate and inspect the `examples/*` locks33yourself; never infer they are clean from a green CI run.3435## Repo version topology (why one bump cascades)3637Packages are wired together with `file:` links, so bumping one package's version38changes the *embedded* version recorded in the lockfile of every package that39links it. The drift is often NOT in a lock's own `version` field — it's in the40embedded `file:`-dependency entries.4142- **`ui/`** — a single npm workspace (Turborepo) with **one** root43 `ui/package-lock.json`. Members: `packages/{ui-foundation,react-adapter,react-devkit,ui-agent,cli,fonts}`44 and `apps/*`. Dep flow: `react-devkit → react-adapter → ui-foundation`.45 - ⚠️ **`packages/react-devkit/package.json` pins internal deps with ranges**46 (`@tetherto/mdk-react-adapter: ^X`, `@tetherto/mdk-ui-foundation: ^X`). When47 you bump those packages, bump these `^` ranges too, or `npm install` fails48 with `404 … @tetherto/mdk-* @^X not found` (it falls through to the registry49 because nothing local satisfies the range). `cli` similarly pins50 `@tetherto/mdk-react-devkit: ^X`.51 - Regenerate the whole workspace with **one** command: `cd ui && npm install --package-lock-only …`.52- **`backend/core/*`** — independent packages, each with its own lock:53 `agent, client, plugins, gateway, mdk, kernel, mcp, mdk-worker`.54 - ⚠️ **`agent` is NOT a repo-root workspace member** — it installs standalone via55 `backend/core/install-packages.sh`, which also symlinks it into the root56 `node_modules/@tetherto/` so the gateway plugin can resolve it by name. Do not57 "fix" this by adding it to the root `workspaces`: npm then claims the tree,58 prunes the standalone install and does not rebuild it — measured as "removed59 407 packages, added 0", silently, with no error to explain it.60 `gateway` `file:`-links `client` and `plugins`, so bumping client/plugins61 forces a `gateway` lock regen.62- **`backend/plugins/*`** — gateway plugins, own locks, versioned independently of63 the release line (`agent` is at `0.1.0` while the repo is at `0.6.0`), so a64 release bump does **not** touch them. Their `@tetherto/*` deps are linked, never65 fetched, so pin them at `*`: a version range there resolves against nothing and66 rots unnoticed — the agent plugin sat at `^0.0.0` across several releases with67 nothing to catch it.68- **`backend/workers/*`** — independent worker packages, own locks.69- **`examples/*`** — many independent packages, own locks. The UI examples70 (`examples/full-site/ui`, `examples/mvp-site/ui`, `examples/mdk-ui-shell-template`)71 `file:`-link the `ui/packages/*` packages, so a ui bump cascades into their locks.72 Because the CI gate skips `examples/`, these are the locks most likely to be stale.7374As of the 0.6.0 cycle versioning is **uniform across the release line**: every75tracked package sits at the same version, `mdk-worker` no longer lags, and76`mdk-ui-shell-template` has moved off its scaffold default onto the shared77version — treat it as a normal package in future bumps. Packages outside the78release line (`backend/plugins/*`) keep their own versions.7980Still **never blanket-set versions by find-and-replace.** Confirm the current spread81first by *parsing* each manifest, not grepping it (a bare `grep -m1 '"version"'` can82latch onto a nested field):8384```bash85for f in $(git ls-files '*package.json'); do86 node -e 'const j=require("fs").readFileSync(process.argv[1],"utf8");const o=JSON.parse(j);console.log(`${o.version??"<none>"}\t${process.argv[1]}`)' "$f"87done | sort | grep -v '^<target>' # lists everything not already at the target88```8990## Procedure91921. **Confirm target + scope.** Which packages move to which version. If fixing a93 red CI gate, first read the failing job log — it names the package and shows94 the `-/+ "version"` diff telling you which value is intended.95962. **Edit `package.json` version fields** for the in-scope packages (use `Edit`97 on the exact `"version": "…"` line). If bumping a ui package that others pin98 by range, also bump those `^` ranges (react-devkit, cli — see topology).991003. **Regenerate locks** where things changed. Fast path — regenerate the ui101 workspace once and every backend/examples package dir:102103 ```bash104 cd "$REPO/ui" && npm install --package-lock-only --ignore-scripts --no-audit --no-fund105 cd "$REPO"106 git ls-files 'backend/**/package-lock.json' 'examples/**/package-lock.json' \107 | xargs -n1 dirname | sort -u \108 | while IFS= read -r d; do (cd "$d" && npm install --package-lock-only --ignore-scripts --no-audit --no-fund >/dev/null 2>&1); done109 ```1101114. **Verify — the CI-equivalent drift check across ALL locks:**112113 ```bash114 git ls-files '*package-lock.json' | xargs -n1 dirname | sort -u | while IFS= read -r d; do115 ( cd "$d" && npm install --package-lock-only --ignore-scripts --no-audit --no-fund >/dev/null 2>&1 )116 git -C "$REPO" diff --quiet -- "$d/package-lock.json" || echo "STILL DRIFTS: $d"117 done118 git status --porcelain | grep package-lock.json # the full set that changed119 ```120 Zero `STILL DRIFTS` lines = CI's per-package gate will pass.1211225. **Sanity the diffs.** Every changed lock line should be a version/range sync123 (`0.3.0`→`0.4.0`, `^0.3.0`→`^0.4.0`) or benign format normalization (npm124 populating an empty `"packages": {}`). Anything else (new deps, removed deps,125 integrity churn) means something beyond a version bump moved — investigate.1261276. **Commit & push only with explicit permission.** Stage `package.json` +128 `package-lock.json` together so the tree is always self-consistent.129130## Gotchas seen in the wild131132- A merge that takes one branch's `package.json` but another's `package-lock.json`133 yields `pkg=0.3.0 / lock=0.4.0`. Decide the intended value (ask / read the134 release), fix `package.json` to it, then regen.135- Bumping a ui package but forgetting react-devkit's `^` pins → `npm install`136 404s. Always bump the range pins in lockstep.137- Checking only a lock's root `version` misses embedded `file:`-dep drift138 (gateway, example UIs). Always run the full per-lock regen in step 4.139- Node/npm version differences can reshuffle lock formatting. Use the repo's140 expected Node (CI uses `lts/*`, engines want `>=24`).