@kontextmind/kxm 0.7.174 → 0.7.176

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -11,7 +11,7 @@
11
11
  "name": "kxm",
12
12
  "source": "./plugins/kxm",
13
13
  "description": "Durable workflows, peer agents, and kxm tui",
14
- "version": "0.7.174",
14
+ "version": "0.7.176",
15
15
  "category": "development",
16
16
  "tags": ["kxm", "multi-agent", "workflows", "mcp"]
17
17
  }
package/CHANGELOG.md CHANGED
@@ -181,6 +181,19 @@ All notable user-facing changes are documented here. The project follows [Semant
181
181
 
182
182
  ### Changed
183
183
 
184
+ - **`kxm update --kxm` defaults to npm.** Missing or source-less `update.yaml` now
185
+ uses the public `@kontextmind/kxm` package instead of the GitHub release tarball.
186
+ `source: github` still selects the digest-checked tarball. When the working
187
+ directory is this repository, `--kxm` installs the local files and does not fetch.
188
+ - **`kxm update` is a full update by default.** Bare `kxm update` and `--all` run
189
+ detected harness `--self` first (default harness leading), then the kxm package,
190
+ then extensions and models. `--kxm` remains kxm-only. Detected harness updater
191
+ failures on `--all` degrade the receipt and do not fail the command; an unknown
192
+ harness id still exits 2.
193
+ - **`kxm land` runs `npm` on Windows.** The verify stage launches `npm.cmd` through `cmd.exe` when `npm` is not an `.exe`.
194
+ - **npm 12 `pack --json`.** The install smoke and pack tests accept the package-keyed object as well as the older one-element array.
195
+ - **Roadmap links on Windows.** `kxm land` keeps the git symlink text when this process cannot create the `docs/roadmap` links.
196
+
184
197
  - **Tool policy is enforced at load and before dispatch.**
185
198
  `coordinator` and `read-only` select the read-only one-shot profile.
186
199
  `workspace-writer` and `tests-writer` select the edit profile. Dispatch
@@ -2,7 +2,7 @@
2
2
  title: "Decisions"
3
3
  description: "Decisions of medium significance or higher made by automated KXM work in this repository. One entry per decision: the options evaluated with their weights, and why the selected option won. Recorded the turn the decision is taken."
4
4
  audience: "agents and maintainers"
5
- updated: "2026-09-28"
5
+ updated: "2026-10-02"
6
6
  ---
7
7
 
8
8
  # Decisions
@@ -151,3 +151,24 @@ Evidence: Evidence: test/core/oneshot-producer.test.ts (real reviewer shapes), r
151
151
  - Selected: port through the engine lane `vision-assert-port`; the PR is
152
152
  closed by its replacement landing, not by hand.
153
153
  Evidence: Evidence: merge-base 77beda4 predates the CLI restructure; merge-tree showed cli/vnext.ts deleted on main; vision-gate.ts already on main.
154
+
155
+ ## 2026-10-02 — Porting the stale update-default branch onto main (landing)
156
+
157
+ - Context: `feat/update-npm-default-and-all` was based on 2026-09-24 main
158
+ and still uncommitted. `origin/main` had moved about 66 commits and
159
+ already contained the claude-bridge `apiKey` registration fix and the
160
+ `gh`/`curl` release downloader. `kxm land`'s rebase stage only resolves
161
+ union conflicts, which cannot merge the `planKxmPackageUpdate` signature
162
+ clash (`downloader` vs `localRoot`).
163
+ - Options:
164
+ - Rebase the 2026-09-24 branch through `kxm land`'s rebase stage — weight
165
+ 0.2. Its union-only resolver cannot handle the `planKxmPackageUpdate`
166
+ signature clash.
167
+ - Stash-apply onto a fresh lane, take upstream for claude-bridge, `dist`,
168
+ and `package.json`, and merge the rest — weight 0.9. Keeps the reviewed
169
+ update-default work and the upstream fixes that must not be duplicated.
170
+ - Discard and re-dispatch to the writer — weight 0.3. Re-authors reviewed
171
+ work that already had a specified merge.
172
+ - Selected: stash-apply onto a fresh lane off `origin/main`, take upstream
173
+ for claude-bridge, `dist`, and `package.json`, and merge the rest.
174
+ Evidence: Evidence: `git diff HEAD origin/main` on the stale branch showed upstream's claude-bridge fix and curl downloader already landed; the lane port keeps both those and the npm/`--all` behavior.
@@ -145,7 +145,7 @@ The context suites in detail:
145
145
  | Permission-diff trust: authority lattice, prose neutrality, Git base shadowing, CLI diff and check | `permission.test.ts`, `cli.test.ts`, `contracts.test.ts`, `package-install.test.ts` |
146
146
  | KXM schemas, restricted YAML fixtures, cross-resource semantics and sync-safe rejection | `contracts.test.ts`, `restricted-yaml.test.ts` |
147
147
  | Harness detection, auth and dispatch for the built-in catalog, including Windows launch rules | `harness.test.ts` |
148
- | `kxm update`: `update.yaml` validation, GitHub or npm version checks, install-kind detection, `kxm-<v>.tgz` asset selection | `kxm-update.test.ts`, `kxm-update-cli.test.ts`, `kxm-install-kind.test.ts` |
148
+ | `kxm update`: `update.yaml` validation, npm default / GitHub override, local checkout install, install-kind detection, `kxm-<v>.tgz` asset selection | `kxm-update.test.ts`, `kxm-update-cli.test.ts`, `kxm-install-kind.test.ts` |
149
149
 
150
150
  The round trip seeds its Runtime stores in a project-local `.kxm/runtime/`
151
151
  layout that the Runtime never writes. Separate tests seed two projects' event
@@ -6,7 +6,7 @@ Move the `kxm` CLI, the hub, the Runtime, and the Claude Code or Pi integrations
6
6
 
7
7
  - Protected storage for a full backup; see [Back up and restore KXM](backup-and-restore.md).
8
8
  - A window in which the hub, the Runtime supervisor and long-lived workers may stop.
9
- - The GitHub CLI (`gh`, signed in) if you apply updates with `kxm update --kxm` from the default `github` source. When `gh` is not installed, that command downloads the same `kxm-<version>.tgz` with curl and still checks the release sha256 before installing. Without a working `kxm` either, use the curl commands in [Update the CLI without gh](#update-the-cli-without-gh).
9
+ - The GitHub CLI (`gh`, signed in), only when `update.yaml` sets `source: github`. When `gh` is not installed, that command downloads the same `kxm-<version>.tgz` with curl and still checks the release sha256 before installing. Without a working `kxm` either, use the curl commands in [Update the CLI without gh](#update-the-cli-without-gh).
10
10
 
11
11
  ## Check for an update
12
12
 
@@ -17,7 +17,7 @@ kxm update --check
17
17
  Expected output when a newer release exists:
18
18
 
19
19
  ```text
20
- kxm 0.7.1 → 0.7.2 available · kxm update --kxm
20
+ kxm 0.7.1 → 0.7.2 available · kxm update
21
21
  ```
22
22
 
23
23
  From a source checkout, the check makes no network call and reports the checkout instead:
@@ -26,7 +26,7 @@ From a source checkout, the check makes no network call and reports the checkout
26
26
  kxm 0.7.1 (running from source at /work/kxm)
27
27
  ```
28
28
 
29
- The check reads `update.yaml` under the user state root. Its `source` is `github` (release tarballs) by default; `npm` is also accepted. See [Updater settings](../reference/config-reference.md#updater-settings-kxmupdatev1). Add `--json` to see the install kind KXM detected.
29
+ The check reads `update.yaml` under the user state root. Its `source` is `npm` by default; `github` (release tarballs) is also accepted. When the working directory is this repository, `kxm update --kxm` installs the local files instead of fetching. See [Updater settings](../reference/config-reference.md#updater-settings-kxmupdatev1). Add `--json` to see the install kind KXM detected.
30
30
 
31
31
  ## Stop the services and back up
32
32
 
@@ -48,7 +48,7 @@ Use the path that matches how KXM was installed. `kxm update --kxm` applies an u
48
48
 
49
49
  | Install kind | Upgrade with |
50
50
  |---|---|
51
- | Global npm install | `kxm update --kxm` (release tarball, or npm with `source: npm`), or `npm install --global --omit=peer @kontextmind/kxm@latest` |
51
+ | Global npm install | `kxm update --kxm` (npm registry, or GitHub tarball with `source: github`), or `npm install --global --omit=peer @kontextmind/kxm@latest` |
52
52
  | Pi package | `pi update` |
53
53
  | Claude Code marketplace plugin | Reinstall the plugin; see the plugin note below |
54
54
  | Project dependency | `npm install @kontextmind/kxm@latest` in that project |
@@ -77,7 +77,7 @@ Refuse a tarball whose sha256 does not match the release asset digest.
77
77
 
78
78
  ### Update harnesses and plugins
79
79
 
80
- Without `--check` or a lone `--kxm`, `kxm update` also runs the native updaters of detected harnesses. Narrow it with a harness id and one scope:
80
+ Bare `kxm update` (same as `--all`) runs detected harness `--self` updaters first, then the kxm package, then extensions and models; `--kxm` alone updates only kxm. Narrow it with a harness id and one scope:
81
81
 
82
82
  ```bash
83
83
  kxm update --dry-run # plan every detected harness
@@ -971,7 +971,7 @@ Not run: stores a credential.
971
971
  ## `kxm update`
972
972
 
973
973
  ```text
974
- kxm update [harness] [--check | --kxm] [--self | --extensions | --models]
974
+ kxm update [harness] [--check | --kxm] [--all | --self | --extensions | --models]
975
975
  ```
976
976
 
977
977
  Checks for or applies a KXM operator package update, and runs the native updaters of detected harnesses.
@@ -979,14 +979,15 @@ Checks for or applies a KXM operator package update, and runs the native updater
979
979
  | Option | Argument | Default | Description |
980
980
  |---|---|---|---|
981
981
  | `--check` | none | off | Check for a kxm package update without applying |
982
- | `--kxm` | none | off | Apply the kxm operator package update (GitHub release tarball or npm) |
983
- | `--self` | none | off | Update only the harness CLI |
982
+ | `--all` | none | off | Update harness CLIs first (`--self`), then kxm, extensions, and models (default for a bare `kxm update`) |
983
+ | `--kxm` | none | off | Apply only the kxm operator package update (npm by default, GitHub tarball when configured, or local files in this repo) |
984
+ | `--self` | none | off | Update only harness CLIs |
984
985
  | `--extensions` | none | off | Update only extensions/plugins (Pi packages, Claude kxm) |
985
986
  | `--models` | none | off | Refresh model catalogs where the harness supports it |
986
987
 
987
988
  - Arguments: `[harness]`, Harness id (default: every detected harness).
988
- - `--check` cannot be combined with any other update flag or a harness (exit 2, `scope_conflict`), and at most one of `--self`, `--extensions`, `--models` is allowed (exit 2).
989
- - From a source checkout, `--check` reports the running version without network access, and `--kxm` is refused (exit 2, `install_kind_source`) with an instruction to `git pull`. Only npm-global installs can apply `--kxm`; other install kinds exit 2 with `install_kind_<kind>`. A GitHub release must publish a sha256 digest for `kxm-<version>.tgz` or the install fails closed (`release_digest_missing`, `release_digest_mismatch`). When `gh` is missing, the github source downloads that same asset with `curl --fail --silent --show-error --location` and still verifies the digest before `npm install -g`.
989
+ - `--check` cannot be combined with any other update flag or a harness (exit 2, `scope_conflict`), and at most one of `--all`, `--self`, `--extensions`, `--models` is allowed (exit 2).
990
+ - Default `source` is npm. Set `update.yaml` `source: github` for the release tarball. When the working directory is this repository (`@kontextmind/kxm` plus `.git`), `--check` reports `source: local` without a network fetch and `--kxm` runs `npm install --global --omit=peer <cwd>`. From a source-built binary used outside this repo, `--check` reports the running version without network access, and `--kxm` is refused (exit 2, `install_kind_source`) with an instruction to `git pull`. Only npm-global installs can apply a registry or GitHub `--kxm`; other install kinds exit 2 with `install_kind_<kind>`. A GitHub release must publish a sha256 digest for `kxm-<version>.tgz` or the install fails closed (`release_digest_missing`, `release_digest_mismatch`). When `gh` is missing, the github source downloads that same asset with `curl --fail --silent --show-error --location` and still verifies the digest before `npm install -g`.
990
991
 
991
992
  On a host with neither `gh` nor a working `kxm`, download and check it yourself:
992
993
 
@@ -1001,7 +1002,7 @@ npm install --global --omit=peer "./kxm-${version}.tgz"
1001
1002
  ```
1002
1003
 
1003
1004
  - Settings come only from `update.yaml` under the user state root (`auto: true` enables auto-apply); a project `.kxm/update.yaml` is ignored with a warning.
1004
- - Without `--check` or a lone `--kxm`, KXM probes harnesses (as `harness list` does) and runs each updater for the selected scope. An unknown harness id exits 2 (`unknown_harness` step); a failed step exits 1.
1005
+ - Without `--check` or a lone `--kxm`, KXM probes harnesses (as `harness list` does) and runs each updater for the selected scope. An unknown harness id exits 2 (`unknown_harness` step); a failed step exits 1, except under `--all` (the default), where a failed harness updater sets `degraded: true` and exits 0.
1005
1006
  - Honors `--dry-run`: steps are planned, not run, and the cached update notice is not refreshed.
1006
1007
  - JSON keys: `--check` gives `current`, `available`, `auto`, `source`, `latest`, `message`, `installKind`, `root`; otherwise `dryRun`, `scope`, `notice`, `kxm` (when applying), `steps` (`harness`, `scope`, `command`, `args`, `outcome`, `detail`), `installKind`, `root`.
1007
1008
 
@@ -1010,7 +1011,7 @@ kxm update --check --dry-run --json
1010
1011
  ```
1011
1012
 
1012
1013
  ```text
1013
- {"schema":"kxm.cli-result.v1","ok":true,"command":"update check","current":"0.7.1","available":false,"auto":false,"source":"github","installKind":"source","root":"/work/kxm","message":"kxm 0.7.1 (running from source at /work/kxm)"}
1014
+ {"schema":"kxm.cli-result.v1","ok":true,"command":"update check","current":"0.7.1","available":false,"auto":false,"source":"npm","installKind":"source","root":"/work/kxm","message":"kxm 0.7.1 (running from source at /work/kxm)"}
1014
1015
  ```
1015
1016
 
1016
1017
  ```bash
@@ -1656,14 +1656,14 @@ never mints a token or writes a file, and always exits 0. See the
1656
1656
  |---|---|---|
1657
1657
  | `schema` | `kxm.update.v1` | Required |
1658
1658
  | `auto` | Boolean | Required |
1659
- | `source` | `github` or `npm` | Optional, `github` |
1659
+ | `source` | `github` or `npm` | Optional, `npm` |
1660
1660
 
1661
1661
  Unknown fields are refused (`update.yaml unknown field <name>`).
1662
1662
 
1663
1663
  ```yaml
1664
1664
  schema: kxm.update.v1
1665
1665
  auto: false # required boolean
1666
- source: github # github (default) | npm
1666
+ source: npm # npm (default) | github
1667
1667
  ```
1668
1668
 
1669
1669
  ## Workspace layout: tracked, ignored, and state
@@ -8,7 +8,7 @@ KXM has three parts you can install: the `kxm` command-line tool, the Claude Cod
8
8
  - Git.
9
9
  - Claude Code, if you want the plugin.
10
10
  - Pi, if you want Pi agents. Install it with `npm install --global @earendil-works/pi-coding-agent` and sign in to a model provider as the [Pi documentation](https://pi.dev/docs/latest) describes.
11
- - The GitHub CLI (`gh`), only if you plan to update with `kxm update --kxm` from the GitHub source. When `gh` is missing, that command falls back to curl and still checks the release checksum. The manual curl commands are in [Upgrade KXM](../operations/upgrade.md#update-the-cli-without-gh).
11
+ - The GitHub CLI (`gh`), only if `update.yaml` sets `source: github` so `kxm update --kxm` downloads the release tarball. When `gh` is missing, that command falls back to curl and still checks the release checksum. The manual curl commands are in [Upgrade KXM](../operations/upgrade.md#update-the-cli-without-gh).
12
12
 
13
13
  ## Choose what to install
14
14
 
@@ -107,13 +107,13 @@ npm ci
107
107
  node scripts/kxm.mjs --version
108
108
  ```
109
109
 
110
- Use `node scripts/kxm.mjs` wherever the docs show `kxm`. It runs the CLI bundle committed in `plugins/kxm/dist/`. Update a checkout with `git pull` and `npm ci`; `kxm update --kxm` refuses to run there. [Develop KXM](../contributing/development.md) covers building and testing.
110
+ Use `node scripts/kxm.mjs` wherever the docs show `kxm`. It runs the CLI bundle committed in `plugins/kxm/dist/`. Update a checkout with `git pull` and `npm ci`. From this repository, `kxm update --kxm` installs those local files globally. [Develop KXM](../contributing/development.md) covers building and testing.
111
111
 
112
112
  ## Update
113
113
 
114
114
  | Part | Command |
115
115
  |---|---|
116
- | `kxm` CLI | `npm install --global --omit=peer @kontextmind/kxm@latest` (check first with `kxm update --check`) |
116
+ | `kxm` CLI | `kxm update --kxm` or `npm install --global --omit=peer @kontextmind/kxm@latest` (check first with `kxm update --check`) |
117
117
  | Claude Code plugin | `claude plugin marketplace update kxm`, then uninstall and reinstall the plugin; `claude plugin update` never refreshes it |
118
118
  | Pi package | `pi update --extensions` |
119
119
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kontextmind/kxm",
3
- "version": "0.7.174",
3
+ "version": "0.7.176",
4
4
  "description": "KXM local-first multi-agent orchestration and operator dashboard",
5
5
  "type": "module",
6
6
  "author": "KontextMind",
@@ -37,7 +37,7 @@
37
37
  "test:core": "npm run build && node scripts/run-bounded.mjs 1200000 node --disable-warning=ExperimentalWarning --experimental-strip-types --test --test-force-exit --test-concurrency=4 test/core/*.test.ts",
38
38
  "test:simulations": "npm run build && node scripts/run-bounded.mjs 1200000 node --disable-warning=ExperimentalWarning --experimental-strip-types --test --test-force-exit --test-concurrency=4 test/simulations/*.test.ts",
39
39
  "test:complete": "npm run build && node scripts/run-bounded.mjs 2400000 node --disable-warning=ExperimentalWarning --experimental-strip-types --test --test-force-exit --test-concurrency=4 test/core/*.test.ts test/simulations/*.test.ts",
40
- "test": "npm run build && node scripts/run-bounded.mjs 1200000 node --disable-warning=ExperimentalWarning --experimental-strip-types --test --test-force-exit --test-concurrency=4 test/core/*.test.ts packages/core/*/tests/unit/*.test.ts",
40
+ "test": "npm run build && node scripts/run-bounded.mjs 1800000 node --disable-warning=ExperimentalWarning --experimental-strip-types --test --test-force-exit --test-concurrency=4 test/core/*.test.ts packages/core/*/tests/unit/*.test.ts",
41
41
  "test:ci-shard": "node scripts/ci-unit-shard.mjs",
42
42
  "e2e": "node scripts/obscura.mjs --ensure && playwright test",
43
43
  "test:coverage:core": "npm run build && node scripts/run-bounded.mjs 2400000 node --disable-warning=ExperimentalWarning --experimental-strip-types --test --test-force-exit --test-concurrency=4 --experimental-test-coverage --test-coverage-lines=91 --test-coverage-branches=80 --test-coverage-functions=92 --test-coverage-include=plugins/kxm/src/**/*.ts --test-coverage-exclude=plugins/kxm/src/server.ts --test-coverage-exclude=plugins/kxm/src/mcp-server.ts --test-coverage-exclude=plugins/kxm/src/runtime-supervisor.ts --test-coverage-include=packages/core/*/src/**/*.ts test/core/*.test.ts packages/core/*/tests/unit/*.test.ts",
@@ -77,9 +77,9 @@
77
77
  ]
78
78
  },
79
79
  "dependencies": {
80
- "@anthropic-ai/claude-agent-sdk": "0.3.283",
80
+ "@anthropic-ai/claude-agent-sdk": "0.3.289",
81
81
  "@earendil-works/pi-tui": "0.87.1",
82
- "@modelcontextprotocol/sdk": "1.30.1",
82
+ "@modelcontextprotocol/sdk": "1.32.0",
83
83
  "yaml": "^2.9.0"
84
84
  },
85
85
  "peerDependencies": {
@@ -96,9 +96,9 @@
96
96
  },
97
97
  "devDependencies": {
98
98
  "@earendil-works/pi-coding-agent": "0.87.1",
99
- "@modelcontextprotocol/sdk": "1.30.1",
99
+ "@modelcontextprotocol/sdk": "1.32.0",
100
100
  "@playwright/test": "1.63.0",
101
- "@types/node": "26.6.3",
101
+ "@types/node": "26.6.4",
102
102
  "ajv": "8.20.0",
103
103
  "commander": "15.0.0",
104
104
  "esbuild": "0.28.2",
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
3
3
  "name": "kxm",
4
4
  "displayName": "KXM",
5
- "version": "0.7.174",
5
+ "version": "0.7.176",
6
6
  "description": "Headless multi-agent orchestration, durable workflows, and a live operator dashboard for Pi and Claude Code",
7
7
  "author": {
8
8
  "name": "KontextMind",
@@ -19780,8 +19780,17 @@ function argsFor(entry, scope) {
19780
19780
  if (scope === "extensions") return entry.update.extensions;
19781
19781
  return entry.update.models;
19782
19782
  }
19783
+ function detectedHarnesses(inventory, requestedHarness) {
19784
+ if (requestedHarness) return inventory.harnesses.filter((entry) => entry.id === requestedHarness);
19785
+ const detected = inventory.harnesses.filter((entry) => entry.detected);
19786
+ const preferred = inventory.defaultHarness;
19787
+ return [
19788
+ ...detected.filter((entry) => entry.id === preferred),
19789
+ ...detected.filter((entry) => entry.id !== preferred)
19790
+ ];
19791
+ }
19783
19792
  function planHarnessUpdate(inventory, requested) {
19784
- const selected = requested.harness ? inventory.harnesses.filter((entry) => entry.id === requested.harness) : inventory.harnesses.filter((entry) => entry.detected);
19793
+ const selected = detectedHarnesses(inventory, requested.harness);
19785
19794
  if (requested.harness && selected.length === 0) {
19786
19795
  return [{
19787
19796
  harness: requested.harness,
@@ -19792,36 +19801,44 @@ function planHarnessUpdate(inventory, requested) {
19792
19801
  detail: isKnownHarnessId(requested.harness) ? "not_detected" : "unknown_harness"
19793
19802
  }];
19794
19803
  }
19804
+ const waves = requested.scope === "all" ? ["self", "extensions", "models"] : [requested.scope];
19795
19805
  const steps = [];
19796
- for (const status of selected) {
19797
- const entry = BUILTIN_HARNESSES.find((candidate) => candidate.id === status.id);
19798
- if (!entry) continue;
19799
- const scopes = scopesFor(requested.scope, entry);
19800
- if (scopes.length === 0) {
19801
- steps.push({
19802
- harness: status.id,
19803
- scope: requested.scope === "all" ? "self" : requested.scope,
19804
- command: status.command ?? entry.commands[0],
19805
- args: [],
19806
- outcome: "skipped",
19807
- detail: "no_updater"
19808
- });
19809
- continue;
19810
- }
19811
- if (!status.detected || !status.command) {
19806
+ for (const scope of waves) {
19807
+ for (const status of selected) {
19808
+ const entry = BUILTIN_HARNESSES.find((candidate) => candidate.id === status.id);
19809
+ if (!entry) continue;
19810
+ const supported = scopesFor(requested.scope === "all" ? "all" : scope, entry);
19811
+ if (!supported.includes(scope)) {
19812
+ if (requested.scope !== "all") {
19813
+ steps.push({
19814
+ harness: status.id,
19815
+ scope,
19816
+ command: status.command ?? entry.commands[0],
19817
+ args: [],
19818
+ outcome: "skipped",
19819
+ detail: "no_updater"
19820
+ });
19821
+ }
19822
+ continue;
19823
+ }
19824
+ if (!status.detected || !status.command) {
19825
+ steps.push({
19826
+ harness: status.id,
19827
+ scope,
19828
+ command: entry.commands[0],
19829
+ args: argsFor(entry, scope) ?? [],
19830
+ outcome: "skipped",
19831
+ detail: "not_detected"
19832
+ });
19833
+ continue;
19834
+ }
19812
19835
  steps.push({
19813
19836
  harness: status.id,
19814
- scope: scopes[0],
19815
- command: entry.commands[0],
19816
- args: argsFor(entry, scopes[0]) ?? [],
19817
- outcome: "skipped",
19818
- detail: "not_detected"
19837
+ scope,
19838
+ command: status.command,
19839
+ args: argsFor(entry, scope) ?? [],
19840
+ outcome: "would"
19819
19841
  });
19820
- continue;
19821
- }
19822
- for (const scope of scopes) {
19823
- const args = argsFor(entry, scope) ?? [];
19824
- steps.push({ harness: status.id, scope, command: status.command, args, outcome: "would" });
19825
19842
  }
19826
19843
  }
19827
19844
  return steps;
@@ -35526,7 +35543,7 @@ function compareSemver(left, right) {
35526
35543
  }
35527
35544
  function formatKxmUpdateNotice(notice) {
35528
35545
  if (!notice.available) return notice.message;
35529
- const apply = notice.auto ? "kxm update --kxm (auto)" : "kxm update --kxm";
35546
+ const apply = notice.auto ? "kxm update (auto)" : "kxm update";
35530
35547
  return `kxm ${notice.current} \u2192 ${notice.latest} available \xB7 ${apply}`;
35531
35548
  }
35532
35549
  function withAsset(notice, asset) {
@@ -35644,7 +35661,11 @@ function verifyReleaseAssetDigest(path4, sha256) {
35644
35661
  return false;
35645
35662
  }
35646
35663
  }
35647
- function planKxmPackageUpdate(source, latest, releaseDir, asset, downloader = "gh") {
35664
+ function planKxmPackageUpdate(source, latest, releaseDir, asset, downloader = "gh", localRoot) {
35665
+ if (source === "local") {
35666
+ if (!localRoot) return [];
35667
+ return [{ kind: "install", command: "npm", args: ["install", "--global", "--omit=peer", localRoot] }];
35668
+ }
35648
35669
  if (source === "npm") {
35649
35670
  return [{ kind: "install", command: "npm", args: ["install", "--global", "--omit=peer", `@kontextmind/kxm@${latest}`] }];
35650
35671
  }
@@ -51787,6 +51808,11 @@ init_hub_binding();
51787
51808
  init_bindings();
51788
51809
  import { existsSync as existsSync38, readFileSync as readFileSync34 } from "node:fs";
51789
51810
  import { dirname as dirname21, join as join48 } from "node:path";
51811
+ function kxmSourceCheckoutRoot(dir) {
51812
+ if (!existsSync38(join48(dir, ".git"))) return void 0;
51813
+ if (!packageIsKxm(dir)) return void 0;
51814
+ return dir;
51815
+ }
51790
51816
  function packageIsKxm(root) {
51791
51817
  try {
51792
51818
  const pkg = JSON.parse(readFileSync34(join48(root, "package.json"), "utf8"));
@@ -51872,7 +51898,7 @@ import { join as join49 } from "node:path";
51872
51898
  init_bindings();
51873
51899
  function loadKxmUpdateConfig(env = process.env) {
51874
51900
  const path4 = join49(kxmUserStateRoot({ env }), "update.yaml");
51875
- if (!existsSync39(path4)) return { schema: KXM_UPDATE_SCHEMA, auto: false, source: "github" };
51901
+ if (!existsSync39(path4)) return { schema: KXM_UPDATE_SCHEMA, auto: false, source: "npm" };
51876
51902
  let parsed;
51877
51903
  try {
51878
51904
  parsed = (0, import_yaml19.parse)(readFileSync35(path4, "utf8"));
@@ -51893,7 +51919,7 @@ function loadKxmUpdateConfig(env = process.env) {
51893
51919
  if (typeof row.auto !== "boolean") {
51894
51920
  throw new KxmUpdateConfigError("update.yaml auto must be a boolean");
51895
51921
  }
51896
- const source = row.source === void 0 ? "github" : row.source;
51922
+ const source = row.source === void 0 ? "npm" : row.source;
51897
51923
  if (source !== "npm" && source !== "github") {
51898
51924
  throw new KxmUpdateConfigError("update.yaml source must be npm or github");
51899
51925
  }
@@ -55090,7 +55116,7 @@ function applyKxmPackageUpdate(runtime, notice) {
55090
55116
  const releaseDir = mkdtempSync3(join57(tmpdir3(), "kxm-pkg-update-"));
55091
55117
  try {
55092
55118
  const downloader = notice.source === "github" && !ghReleaseDownloadAvailable(runtime.env) ? "curl" : "gh";
55093
- const planned = planKxmPackageUpdate(notice.source, notice.latest, releaseDir, notice.asset, downloader);
55119
+ const planned = planKxmPackageUpdate(notice.source, notice.latest, releaseDir, notice.asset, downloader, notice.localRoot);
55094
55120
  if (runtime.dryRun) {
55095
55121
  return { ok: true, detail: planned.map(formatPackageUpdateStep).join(" && ") };
55096
55122
  }
@@ -55216,22 +55242,34 @@ async function cmdSshClose(runtime, host) {
55216
55242
  return 0;
55217
55243
  }
55218
55244
  async function cmdUpdate(runtime, harness, options) {
55219
- if (options.check && (options.kxm || options.self || options.extensions || options.models || harness)) {
55245
+ if (options.check && (options.kxm || options.self || options.extensions || options.models || options.all || harness)) {
55220
55246
  print(runtime.io, runtime.json, { ok: false, command: "update", error: "scope_conflict" }, "--check cannot be combined with other update flags");
55221
55247
  return 2;
55222
55248
  }
55223
- const selected = [options.self, options.extensions, options.models].filter(Boolean).length;
55249
+ const selected = [options.self, options.extensions, options.models, options.all].filter(Boolean).length;
55224
55250
  if (selected > 1) {
55225
- print(runtime.io, runtime.json, { ok: false, command: "update", error: "scope_conflict" }, "specify at most one of --self, --extensions, or --models");
55251
+ print(runtime.io, runtime.json, { ok: false, command: "update", error: "scope_conflict" }, "specify at most one of --all, --self, --extensions, or --models");
55226
55252
  return 2;
55227
55253
  }
55228
55254
  warnIgnoredProjectUpdateYaml(runtime);
55229
55255
  const probe = installProbeFrom(runtime);
55230
55256
  const classified = classifyInstallRoot(probe);
55231
55257
  const current = installedKxmVersion(classified.root) ?? installedKxmVersion(findKxmRepoRoot(import.meta.url));
55258
+ const localRoot = kxmSourceCheckoutRoot(runtime.dirs.workdir) ?? kxmSourceCheckoutRoot(runtime.cwd);
55232
55259
  let notice;
55233
55260
  let kindReport = classified;
55234
- if (classified.kind === "source") {
55261
+ if (localRoot) {
55262
+ const localVersion = installedKxmVersion(localRoot) ?? current ?? "unknown";
55263
+ notice = {
55264
+ current: current ?? localVersion,
55265
+ latest: localVersion,
55266
+ available: true,
55267
+ auto: false,
55268
+ source: "local",
55269
+ message: `kxm ${current ?? localVersion} (local ${localRoot})`,
55270
+ localRoot
55271
+ };
55272
+ } else if (classified.kind === "source") {
55235
55273
  if (options.check) {
55236
55274
  const message = `kxm ${current ?? "unknown"} (running from source at ${classified.root})`;
55237
55275
  print(runtime.io, runtime.json, {
@@ -55240,7 +55278,7 @@ async function cmdUpdate(runtime, harness, options) {
55240
55278
  current: current ?? "unknown",
55241
55279
  available: false,
55242
55280
  auto: false,
55243
- source: "github",
55281
+ source: "npm",
55244
55282
  installKind: "source",
55245
55283
  root: classified.root,
55246
55284
  message
@@ -55262,7 +55300,7 @@ async function cmdUpdate(runtime, harness, options) {
55262
55300
  current: current ?? "unknown",
55263
55301
  available: false,
55264
55302
  auto: false,
55265
- source: "github",
55303
+ source: "npm",
55266
55304
  message: `kxm ${current ?? "unknown"} (running from source)`
55267
55305
  };
55268
55306
  } else {
@@ -55285,13 +55323,30 @@ async function cmdUpdate(runtime, harness, options) {
55285
55323
  }, notice.message);
55286
55324
  return 0;
55287
55325
  }
55288
- const applyKxm = Boolean(options.kxm || notice.auto);
55326
+ const fullUpdate = Boolean(options.all) || selected === 0 && !options.kxm && !harness;
55327
+ const applyKxm = Boolean(options.kxm || fullUpdate || notice.auto);
55289
55328
  let kxmApply;
55290
- if (applyKxm) {
55329
+ const applyKxmPackage = (requireSuccess) => {
55330
+ if (!applyKxm) return void 0;
55331
+ if (notice.source === "local" && notice.localRoot) {
55332
+ kxmApply = applyKxmPackageUpdate(runtime, notice);
55333
+ if (!kxmApply.ok && requireSuccess) {
55334
+ print(runtime.io, runtime.json, {
55335
+ ok: false,
55336
+ command: "update",
55337
+ error: kxmApply.error,
55338
+ kxm: kxmApply,
55339
+ notice,
55340
+ ...installKindPayload(classified)
55341
+ }, kxmApply.detail);
55342
+ return 1;
55343
+ }
55344
+ return void 0;
55345
+ }
55291
55346
  const resolved = resolveInstallKind(probe, npmGlobalRootFn(runtime));
55292
55347
  kindReport = resolved;
55293
55348
  if (resolved.kind !== "npm-global") {
55294
- if (options.kxm) {
55349
+ if (requireSuccess) {
55295
55350
  print(runtime.io, runtime.json, {
55296
55351
  ok: false,
55297
55352
  command: "update",
@@ -55305,9 +55360,11 @@ async function cmdUpdate(runtime, harness, options) {
55305
55360
  }
55306
55361
  if (notice.available) runtime.io.stderr(`kxm: ${resolved.instruction}
55307
55362
  `);
55308
- } else if (notice.available) {
55363
+ return void 0;
55364
+ }
55365
+ if (notice.available) {
55309
55366
  kxmApply = applyKxmPackageUpdate(runtime, notice);
55310
- if (!kxmApply.ok && options.kxm) {
55367
+ if (!kxmApply.ok && requireSuccess) {
55311
55368
  print(runtime.io, runtime.json, {
55312
55369
  ok: false,
55313
55370
  command: "update",
@@ -55319,9 +55376,12 @@ async function cmdUpdate(runtime, harness, options) {
55319
55376
  return 1;
55320
55377
  }
55321
55378
  }
55322
- }
55323
- const skipHarness = Boolean(options.kxm && selected === 0 && !harness && !notice.auto);
55379
+ return void 0;
55380
+ };
55381
+ const skipHarness = Boolean(options.kxm && !fullUpdate && selected === 0 && !harness && !notice.auto);
55324
55382
  if (skipHarness) {
55383
+ const aborted = applyKxmPackage(true);
55384
+ if (aborted !== void 0) return aborted;
55325
55385
  print(
55326
55386
  runtime.io,
55327
55387
  runtime.json,
@@ -55332,23 +55392,39 @@ async function cmdUpdate(runtime, harness, options) {
55332
55392
  }
55333
55393
  const scope = options.self ? "self" : options.extensions ? "extensions" : options.models ? "models" : "all";
55334
55394
  const inventory = await probeHarnessesAsync({ env: runtime.env });
55335
- const planned = planHarnessUpdate(inventory, { ...harness ? { harness } : {}, scope });
55336
- const steps = runHarnessUpdate(planned, { env: runtime.env, dryRun: runtime.dryRun });
55337
- const failed2 = steps.some((step) => step.outcome === "failed") || kxmApply?.ok === false;
55395
+ const planned = planHarnessUpdate(inventory, { ...harness ? { harness } : {}, scope: fullUpdate ? "all" : scope });
55396
+ const runOpts = { env: runtime.env, dryRun: runtime.dryRun };
55397
+ let steps;
55398
+ if (fullUpdate) {
55399
+ const selfSteps = runHarnessUpdate(planned.filter((step) => step.scope === "self"), runOpts);
55400
+ applyKxmPackage(false);
55401
+ const restSteps = runHarnessUpdate(planned.filter((step) => step.scope !== "self"), runOpts);
55402
+ steps = [...selfSteps, ...restSteps];
55403
+ } else {
55404
+ const aborted = applyKxmPackage(Boolean(options.kxm));
55405
+ if (aborted !== void 0) return aborted;
55406
+ steps = runHarnessUpdate(planned, runOpts);
55407
+ }
55408
+ const kxmFailed = kxmApply?.ok === false;
55338
55409
  const skippedUnknown = steps.some((step) => step.detail === "unknown_harness");
55410
+ const harnessFailed = steps.some((step) => step.outcome === "failed");
55339
55411
  const text = [notice.available ? notice.message : void 0, kxmApply?.detail, formatHarnessUpdate(steps)].filter(Boolean).join("\n");
55340
55412
  print(runtime.io, runtime.json, {
55341
- ok: !failed2 && !skippedUnknown,
55413
+ ok: !kxmFailed && !skippedUnknown,
55342
55414
  command: "update",
55343
55415
  dryRun: runtime.dryRun,
55344
- scope,
55416
+ scope: fullUpdate ? "all" : scope,
55417
+ defaultHarness: inventory.defaultHarness,
55345
55418
  notice,
55346
55419
  ...kxmApply ? { kxm: kxmApply } : {},
55347
55420
  steps,
55421
+ ...harnessFailed ? { degraded: true } : {},
55348
55422
  ...installKindPayload(kindReport)
55349
55423
  }, text);
55350
55424
  if (skippedUnknown) return 2;
55351
- return failed2 ? 1 : 0;
55425
+ if (kxmFailed) return 1;
55426
+ if (harnessFailed && !fullUpdate) return 1;
55427
+ return 0;
55352
55428
  }
55353
55429
  async function cmdValidate(runtime, fileFlag) {
55354
55430
  const worker = gateOf(runtime, "validate");
@@ -56420,7 +56496,7 @@ function createProgram(ctx, result, argv) {
56420
56496
  addGlobalOptions(authCmd.command("token").description("Inspect, issue, or clear local disk session tokens")).option("--status", "Check status of the active session token").option("--clear", "Clear persisted disk session token").option("--issue", "Force issuing a fresh session token").action(async function authTokenAction(options) {
56421
56497
  result.code = await cmdAuthToken(runtimeFrom(ctx, this), options);
56422
56498
  });
56423
- addGlobalOptions(program2.command("update").description("Update kxm, harness CLIs, extensions, plugins, and model catalogs").argument("[harness]", "Harness id (default: every detected harness)").option("--check", "Check for a kxm package update without applying").option("--kxm", "Apply the kxm operator package update (GitHub release tarball or npm)").option("--self", "Update only the harness CLI").option("--extensions", "Update only extensions/plugins (Pi packages, Claude kxm)").option("--models", "Refresh model catalogs where the harness supports it")).action(async function updateAction(harness, options) {
56499
+ addGlobalOptions(program2.command("update").description("Update kxm, harness CLIs, extensions, plugins, and model catalogs").argument("[harness]", "Harness id (default: every detected harness)").option("--check", "Check for a kxm package update without applying").option("--all", "Update harness CLIs first (--self), then kxm, extensions, and models (default)").option("--kxm", "Apply only the kxm operator package update").option("--self", "Update only harness CLIs").option("--extensions", "Update only extensions/plugins (Pi packages, Claude kxm)").option("--models", "Refresh model catalogs where the harness supports it")).action(async function updateAction(harness, options) {
56424
56500
  result.code = await cmdUpdate(runtimeFrom(ctx, this), harness, options);
56425
56501
  });
56426
56502
  const runtimeCmd = addGlobalOptions(program2.command("runtime").description("Manage the KXM Runtime supervisor"));