@klhapp/skillmux 0.2.1 → 0.4.3
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.
- package/CHANGELOG.md +76 -0
- package/README.md +72 -3
- package/config.example.toml +5 -0
- package/docs/configuration.md +58 -4
- package/docs/releasing.md +60 -21
- package/package.json +1 -1
- package/src/adapters.ts +381 -0
- package/src/calibrate.ts +797 -0
- package/src/cli.ts +866 -385
- package/src/clients.ts +3 -1
- package/src/completions.ts +84 -0
- package/src/config-service.ts +408 -0
- package/src/config-watcher.ts +179 -0
- package/src/config.ts +7 -0
- package/src/context.ts +183 -0
- package/src/dataset-generator.ts +140 -0
- package/src/doctor.ts +43 -0
- package/src/eval.ts +22 -4
- package/src/init.ts +1 -1
- package/src/manifest.ts +124 -10
- package/src/output.ts +151 -0
- package/src/router-core.ts +60 -12
- package/src/server.ts +119 -0
- package/src/snapshot.ts +135 -0
- package/src/sync.ts +29 -8
- package/src/types.ts +7 -0
- package/src/vault.ts +59 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,82 @@ All notable changes to this project are documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.4.3](https://github.com/klhq/skillmux/compare/v0.4.2...v0.4.3) (2026-07-23)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
* **release:** prevent slim from overwriting latest ([#53](https://github.com/klhq/skillmux/issues/53)) ([8b5da28](https://github.com/klhq/skillmux/commit/8b5da285b5cb2c9b3231e94ed1ec29331cf8cb9a))
|
|
14
|
+
* **release:** safe backfill and isolate publishing ([#51](https://github.com/klhq/skillmux/issues/51)) ([872333a](https://github.com/klhq/skillmux/commit/872333a70f32bf5be68cc3599c43bd4943a91051))
|
|
15
|
+
|
|
16
|
+
## [0.4.2](https://github.com/klhq/skillmux/compare/v0.4.1...v0.4.2) (2026-07-23)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
* **release:** validate explicit release tag ([#46](https://github.com/klhq/skillmux/issues/46)) ([ec11057](https://github.com/klhq/skillmux/commit/ec11057d18162ace7944b1ee07bd0fb1f282efcc))
|
|
22
|
+
|
|
23
|
+
## [0.4.1](https://github.com/klhq/skillmux/compare/v0.4.0...v0.4.1) (2026-07-22)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
|
|
28
|
+
* **release:** publish immediately from release please ([#44](https://github.com/klhq/skillmux/issues/44)) ([4569cf3](https://github.com/klhq/skillmux/commit/4569cf3eed7ded5d4cb032f622a6a92e3eb28b21))
|
|
29
|
+
|
|
30
|
+
## [0.4.0](https://github.com/klhq/skillmux/compare/v0.3.0...v0.4.0) (2026-07-22)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
### Added
|
|
34
|
+
|
|
35
|
+
* **cli:** add 'skillmux local-vault init <path>' to write the local_vault marker ([7648145](https://github.com/klhq/skillmux/commit/76481452994f98d6ef01fb1302344fc76a547492))
|
|
36
|
+
* **cli:** add 'skillmux which <skill_id>' to show which root resolves a skill ([0dfd0ed](https://github.com/klhq/skillmux/commit/0dfd0ed9e067f12dfce6cd9a3bc5fe61eeedf4d8))
|
|
37
|
+
* **cli:** add skill visibility command ([a00d248](https://github.com/klhq/skillmux/commit/a00d24811b3b91e947a529fe87f742acf6cb4ce7))
|
|
38
|
+
* **cli:** filter [project.*] groups by each target's project_groups ([e039920](https://github.com/klhq/skillmux/commit/e039920349769e65d01555a5681a4b1724fddf88))
|
|
39
|
+
* **completions:** list 'which' in bash and zsh command completions ([fc8151f](https://github.com/klhq/skillmux/commit/fc8151fc83a0e57b2eb57decdf78d716cdf7a8fa))
|
|
40
|
+
* **config:** add local_vault_paths alongside the unchanged vault_path ([4d8c95e](https://github.com/klhq/skillmux/commit/4d8c95e600a6607e8e83b9fb5a6344677003d3c3))
|
|
41
|
+
* **doctor:** report local_vault_paths marker presence and vault_path drift ([eb8e8a3](https://github.com/klhq/skillmux/commit/eb8e8a304847c366f2ba057e1d9b9741516e75c2))
|
|
42
|
+
* **doctor:** report shadowed skills across vault_path and local_vault_paths ([eac168d](https://github.com/klhq/skillmux/commit/eac168d3c729d7a610632c73fa573ad4947964d9))
|
|
43
|
+
* **manifest:** add pin/unpin --core to skillmux manifest CLI ([59a4ceb](https://github.com/klhq/skillmux/commit/59a4ceb8a25867c4bc73932294aae6a8a1f9edd8))
|
|
44
|
+
* **manifest:** add pin/unpin --project <group> --repo to skillmux manifest CLI ([3bf98e9](https://github.com/klhq/skillmux/commit/3bf98e983bd05b35d15b25f4effd1fb33cf03b8f))
|
|
45
|
+
* **manifest:** add pin/unpin and local-vault markers ([f1329ae](https://github.com/klhq/skillmux/commit/f1329ae48b1c7603201f039b80ac2b8489101cf8))
|
|
46
|
+
* **manifest:** add the AC6 portability guard to validateManifest ([fb83299](https://github.com/klhq/skillmux/commit/fb8329969228029d7d9561884a1f0f0df3aa7a77))
|
|
47
|
+
* **manifest:** project_groups + local_vault_paths overlay support ([#40](https://github.com/klhq/skillmux/issues/40)) ([411ecfb](https://github.com/klhq/skillmux/commit/411ecfb243695465aea44dc4a9192190ecde6086))
|
|
48
|
+
* **manifest:** replace targets.*.project boolean with project_groups ([ccd246e](https://github.com/klhq/skillmux/commit/ccd246e8238c00eebac6abfde4d4250f87d770db))
|
|
49
|
+
* **sync:** add role field to SkillmuxMarker with read-compat for absent role ([79af610](https://github.com/klhq/skillmux/commit/79af610c4fd598e458a947eb01f7c01358f38402))
|
|
50
|
+
* **vault:** add findShadowedSkills to surface cross-root skill_id collisions ([7d1a328](https://github.com/klhq/skillmux/commit/7d1a3287ea47044e39371d38a360c3acc3d2f2c0))
|
|
51
|
+
* **vault:** resolve skills through local_vault_paths before vault_path ([0c1f603](https://github.com/klhq/skillmux/commit/0c1f60328b30681dd739224a5a53cdb178413a18))
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
### Fixed
|
|
55
|
+
|
|
56
|
+
* **manifest:** reject invalid [project.*] group names in pinProject before writing ([153d0ac](https://github.com/klhq/skillmux/commit/153d0ac39a548ca47aafb0ddd309cbb4da63fda8))
|
|
57
|
+
* **router-core:** tolerate broken content when a root wins on existence alone ([6cb4d0d](https://github.com/klhq/skillmux/commit/6cb4d0d64bd0dd98d7fd44ac382ea2e9e3f2e125))
|
|
58
|
+
|
|
59
|
+
## [0.3.0](https://github.com/klhq/skillmux/compare/v0.2.1...v0.3.0) (2026-07-21)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
### Added
|
|
63
|
+
|
|
64
|
+
* **adapters:** add unified local and remote target adapters (AC3, AC7, AC10) ([552b7d8](https://github.com/klhq/skillmux/commit/552b7d8f26ece4ab4a98f14326e508e35aeb57d5))
|
|
65
|
+
* **admin:** add authenticated /admin/v1 HTTP control plane (AC7, AC8, AC9, AC10) ([7b7958f](https://github.com/klhq/skillmux/commit/7b7958fb0e248e2c33a0ca1e566286617a8a0d84))
|
|
66
|
+
* **calibrate:** Automated calibration-tuning pipeline, evidence store, and live reload ([#35](https://github.com/klhq/skillmux/issues/35)) ([7ccd79f](https://github.com/klhq/skillmux/commit/7ccd79f9d25d901aa3d7aec43953b79896ebfa24))
|
|
67
|
+
* **cli:** add output formatting, exit code mapping, and shell completions (AC11, AC12) ([0099395](https://github.com/klhq/skillmux/commit/009939501192f81dc8fbe2e15c33bce22beb1456))
|
|
68
|
+
* **cli:** CLI polish, target resolution, config/calibrate parity, and admin control plane ([a4fb67d](https://github.com/klhq/skillmux/commit/a4fb67d3e8a09ae6ef8151ad4892ec3d94d261e0))
|
|
69
|
+
* **cli:** connect CLI dispatcher with context, config, calibrate, and completions (cli-polish) ([bd3fd14](https://github.com/klhq/skillmux/commit/bd3fd140443e4523fcaf163b2fe5e57d5c51ac9c))
|
|
70
|
+
* **config:** add source-aware config service and status tracking (AC4, AC5, AC6) ([325342a](https://github.com/klhq/skillmux/commit/325342a55b5bac4a7b3aee7d7740e2fc22f7aee7))
|
|
71
|
+
* **context:** add context management and target resolution service (AC1, AC2) ([34435c9](https://github.com/klhq/skillmux/commit/34435c934aaa5ad69af90b7b42e761c674d6ac85))
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
### Fixed
|
|
75
|
+
|
|
76
|
+
* **clients:** handle trailing /v1 in embedding base_url ([26d8158](https://github.com/klhq/skillmux/commit/26d8158138b390ffb3e5c3f54777273673db0a73))
|
|
77
|
+
* **types:** resolve TypeScript compilation errors in CLI, adapters, and server ([e4ad7d2](https://github.com/klhq/skillmux/commit/e4ad7d2c059157e52a9f584f15eeaed6a8091fec))
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
### Changed
|
|
81
|
+
|
|
82
|
+
* **config:** remove deprecated environment variable shims ([#34](https://github.com/klhq/skillmux/issues/34)) ([e046f45](https://github.com/klhq/skillmux/commit/e046f45c3133b2e91b4920513975107b025b19f5))
|
|
83
|
+
|
|
8
84
|
## [0.2.1](https://github.com/klhq/skillmux/compare/skillmux-v0.2.0...skillmux-v0.2.1) (2026-07-21)
|
|
9
85
|
|
|
10
86
|
## [0.2.0](https://github.com/klhq/skillmux/compare/skillmux-v0.1.1...skillmux-v0.2.0) (2026-07-21)
|
package/README.md
CHANGED
|
@@ -13,11 +13,18 @@ Built for agents that lack native skill triggering (Goose recipe workers, openco
|
|
|
13
13
|
- [Pinning skills across surfaces](#pinning-skills-across-surfaces) — optional: statically load a curated set across multiple agents
|
|
14
14
|
- [Docker Usage](#docker-usage)
|
|
15
15
|
- [Configuration](#configuration) — inference modes, security scanning, installing skills, env vars
|
|
16
|
+
- [CLI & Automation](docs/cli.md) — context management, remote target resolution, policy calibration, JSON envelopes
|
|
17
|
+
- [Benchmarks & Evaluation](#benchmarks--evaluation)
|
|
18
|
+
- [FAQ & Troubleshooting](#faq--troubleshooting)
|
|
16
19
|
- [Guarantees](#guarantees)
|
|
17
20
|
- [Development](#development)
|
|
18
21
|
|
|
19
22
|
## How it works
|
|
20
23
|
|
|
24
|
+
<p align="center">
|
|
25
|
+
<img src="docs/assets/architecture.svg" alt="skillmux Architecture & Hybrid Routing Flow" width="100%">
|
|
26
|
+
</p>
|
|
27
|
+
|
|
21
28
|
```
|
|
22
29
|
resolve_skill("convert this spreadsheet to markdown")
|
|
23
30
|
│
|
|
@@ -195,7 +202,15 @@ This writes `skillmux.toml` at the vault root and marks `~/.claude/skills` as sk
|
|
|
195
202
|
|
|
196
203
|
### 2. Pick which skills are pinned
|
|
197
204
|
|
|
198
|
-
`init` always starts `[core]` empty — there's no heuristic yet for which skills belong there.
|
|
205
|
+
`init` always starts `[core]` empty — there's no heuristic yet for which skills belong there. Pin with the CLI instead of hand-editing `skillmux.toml`:
|
|
206
|
+
|
|
207
|
+
```sh
|
|
208
|
+
skillmux manifest pin csv-formatter --core
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Unpin the same way: `skillmux manifest unpin csv-formatter --core`. `--project <group>` pins into a `[project.<group>]` tier instead (add `--repo <path>` the first time, to create the group). Every pin is validated before writing — the skill must actually resolve from the vault, and `[core]` stays under its 25-skill cap.
|
|
212
|
+
|
|
213
|
+
Hand-editing `skillmux.toml` still works if you prefer it:
|
|
199
214
|
|
|
200
215
|
```toml
|
|
201
216
|
[core]
|
|
@@ -203,10 +218,10 @@ skills = ["csv-formatter"]
|
|
|
203
218
|
|
|
204
219
|
[targets.claude]
|
|
205
220
|
dir = "/Users/you/.claude/skills"
|
|
206
|
-
|
|
221
|
+
project_groups = []
|
|
207
222
|
```
|
|
208
223
|
|
|
209
|
-
|
|
224
|
+
Full manifest schema, including `[project.<group>]` pins scoped to one repo and machine-local overlay vaults via `local_vault_paths`, is in [`docs/configuration.md`](docs/configuration.md#tiers-and-the-manifest).
|
|
210
225
|
|
|
211
226
|
### 3. Materialize
|
|
212
227
|
|
|
@@ -387,6 +402,10 @@ All core settings can be overridden via environment variables (handy for Docker)
|
|
|
387
402
|
|
|
388
403
|
Remote API keys are read from the environment variables named by `inference.embedding.api_key_env` and `inference.reranker.api_key_env`; no secret ever lives in the config file.
|
|
389
404
|
|
|
405
|
+
## Benchmarks & Evaluation
|
|
406
|
+
|
|
407
|
+
Skillmux includes a built-in evaluation framework to benchmark retrieval accuracy (lexical vs. hybrid vector search) against labeled intent datasets.
|
|
408
|
+
|
|
390
409
|
Evaluate lexical and local hybrid retrieval against the checked-in labeled queries:
|
|
391
410
|
|
|
392
411
|
```sh
|
|
@@ -396,6 +415,56 @@ bun run src/cli.ts eval
|
|
|
396
415
|
# hybrid recall@5: 1.000
|
|
397
416
|
```
|
|
398
417
|
|
|
418
|
+
Custom policy calibration can also be performed against domain-specific query logs using `skillmux calibrate` (see [`docs/cli.md`](docs/cli.md#policy-calibration-skillmux-calibrate)).
|
|
419
|
+
|
|
420
|
+
## FAQ & Troubleshooting
|
|
421
|
+
|
|
422
|
+
<details>
|
|
423
|
+
<summary><b>Why did my query return <code>ambiguous</code> instead of <code>matched</code>?</b></summary>
|
|
424
|
+
|
|
425
|
+
<br>
|
|
426
|
+
|
|
427
|
+
The router returns `"outcome": "ambiguous"` when multiple candidate skills meet retrieval confidence thresholds, or when no single candidate dominates by a sufficient score margin. In this state, up to 10 candidate skill summaries (`skill_id`, `title`, `description`) are returned so the calling LLM can choose the exact skill and invoke `fetch_skill`.
|
|
428
|
+
|
|
429
|
+
</details>
|
|
430
|
+
|
|
431
|
+
<details>
|
|
432
|
+
<summary><b>Does skillmux require an active internet connection?</b></summary>
|
|
433
|
+
|
|
434
|
+
<br>
|
|
435
|
+
|
|
436
|
+
No. In default local inference mode (`inference.mode = "local"`), skillmux operates 100% offline. The default GTE-small embedding model is quantized to q8 and bundled within the binary/Docker image.
|
|
437
|
+
|
|
438
|
+
</details>
|
|
439
|
+
|
|
440
|
+
<details>
|
|
441
|
+
<summary><b>What happens when remote embedding or reranking endpoints fail?</b></summary>
|
|
442
|
+
|
|
443
|
+
<br>
|
|
444
|
+
|
|
445
|
+
Skillmux features automatic fallback degradation. If remote embedding APIs or rerankers become unreachable, the router gracefully falls back to SQLite FTS5 lexical search rather than throwing an error to the calling agent.
|
|
446
|
+
|
|
447
|
+
</details>
|
|
448
|
+
|
|
449
|
+
<details>
|
|
450
|
+
<summary><b>When should I use routed skills vs. pinned skills?</b></summary>
|
|
451
|
+
|
|
452
|
+
<br>
|
|
453
|
+
|
|
454
|
+
- **Routed (Default)**: Best for expanding vaults with dozens or hundreds of skills. Skills are loaded dynamically on demand via `resolve_skill`, keeping the agent's initial context window clean.
|
|
455
|
+
- **Pinned (`skillmux sync`)**: Best when running multiple agent surfaces (e.g. Claude Code, opencode) that all require the same core set of 2–5 skills loaded statically at agent startup.
|
|
456
|
+
|
|
457
|
+
</details>
|
|
458
|
+
|
|
459
|
+
<details>
|
|
460
|
+
<summary><b>How do I verify server readiness and routing health?</b></summary>
|
|
461
|
+
|
|
462
|
+
<br>
|
|
463
|
+
|
|
464
|
+
Run `skillmux doctor` locally or hit the HTTP readiness endpoint (`GET /health/ready`). `doctor` checks vault accessibility, state directory permissions, ONNX runtime binding status, and active retrieval lane status.
|
|
465
|
+
|
|
466
|
+
</details>
|
|
467
|
+
|
|
399
468
|
## Guarantees
|
|
400
469
|
|
|
401
470
|
- **Read-only vault** — no code path writes under the vault; all state is confined to `state_dir`. Covered by tests.
|
package/config.example.toml
CHANGED
|
@@ -3,3 +3,8 @@
|
|
|
3
3
|
# vault_path is a neutral path, never a harness's static-loading scan
|
|
4
4
|
# surface (e.g. ~/.agents/skills) — see docs/sdd/skr-cli/think.md.
|
|
5
5
|
vault_path = "~/skills"
|
|
6
|
+
|
|
7
|
+
# Optional, default []: machine-local override-only vaults, checked before
|
|
8
|
+
# vault_path for a given skill_id. Never holds skillmux.toml or the sync git
|
|
9
|
+
# hook — those always stay in vault_path. See docs/configuration.md#local-vault-overlays.
|
|
10
|
+
# local_vault_paths = ["~/skills-local"]
|
package/docs/configuration.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Skillmux defaults to FTS5 plus local GTE-small semantic retrieval. Most users need no config file.
|
|
4
4
|
|
|
5
|
+
For detailed CLI command reference, target resolution, policy calibration, and automation envelopes, see [`docs/cli.md`](cli.md).
|
|
6
|
+
|
|
5
7
|
## Local mode
|
|
6
8
|
|
|
7
9
|
```toml
|
|
@@ -88,13 +90,65 @@ skills = ["pdf-extractor"] # must not overlap [core]
|
|
|
88
90
|
|
|
89
91
|
[targets.claude]
|
|
90
92
|
dir = "/Users/you/.claude/skills"
|
|
91
|
-
|
|
93
|
+
project_groups = ["repo1"] # which [project.*] groups materialize into this target — [] means none
|
|
92
94
|
```
|
|
93
95
|
|
|
94
96
|
- `[core].skills` — symlinked into every `[targets.*]` dir on `sync`. Capped at 25 skills; `sync` fails if a listed skill id isn't actually in the vault.
|
|
95
|
-
- `[project.<group>].skills` — symlinked only into `<repo>/<relative path from $HOME to the target dir>`, for each `repos` entry, and only for targets
|
|
96
|
-
- `[targets.<name>]` — one entry per adopted surface. `skillmux init --target <name> --yes` writes these; hand-editing is fine as long as `sync` is still allowed to own the directory (see below).
|
|
97
|
+
- `[project.<group>].skills` — symlinked only into `<repo>/<relative path from $HOME to the target dir>`, for each `repos` entry, and only for targets whose `project_groups` names that group. `repos` entries must resolve under `$HOME` (that's how the pin path is derived). A skill can't appear in both `[core]` and the same `[project.*]` group.
|
|
98
|
+
- `[targets.<name>]` — one entry per adopted surface. `skillmux init --target <name> --yes` writes these; hand-editing is fine as long as `sync` is still allowed to own the directory (see below). `project_groups` is an explicit list, not a boolean — a target only receives the specific groups it names, never every group in the manifest.
|
|
99
|
+
|
|
100
|
+
**Pin/unpin without hand-editing.** `skillmux manifest pin`/`unpin` mutate `[core]`/`[project.*]` for you, validating with the same rules `sync`/`doctor` enforce (skill must resolve from `vault_path`, no duplicate pins, `[core]` stays under the 25-skill cap) before writing anything:
|
|
101
|
+
|
|
102
|
+
```sh
|
|
103
|
+
skillmux manifest pin csv-formatter --core # add to [core]
|
|
104
|
+
skillmux manifest pin pdf-extractor --project repo1 # add to an existing group
|
|
105
|
+
skillmux manifest pin pdf-extractor --project repo2 --repo ~/code/repo2 # create a new group
|
|
106
|
+
skillmux manifest unpin csv-formatter --core # remove from [core]
|
|
107
|
+
skillmux manifest unpin pdf-extractor --project repo1 # remove from a group (group stays, even if empty)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
`--repo` is only accepted when `--project <group>` names a group that doesn't exist yet — passing it for an existing group is rejected, since changing an existing group's `repos` isn't this command's job. Hand-editing `skillmux.toml` directly is still fully supported; these commands are a convenience layer over the same file, not a replacement for it.
|
|
111
|
+
|
|
112
|
+
> **Breaking change:** `[targets.<name>].project` (a boolean) has been replaced by `project_groups` (an array of `[project.*]` names). A manifest still using the old field fails to parse with an error pointing at the new one. To migrate, replace `project = true` with `project_groups = [...]` listing every group that target previously received (previously *all* groups, unconditionally); replace `project = false` with `project_groups = []`.
|
|
113
|
+
|
|
114
|
+
Every `[core]`/`[project.*]` skill_id must resolve from the canonical `vault_path` — pinning a skill that only exists in a `local_vault_paths` entry (see below) fails `sync`/`doctor` with a distinct error, since the manifest is meant to be portable across machines and a machine-local override wouldn't exist elsewhere.
|
|
97
115
|
|
|
98
116
|
### Ownership marker
|
|
99
117
|
|
|
100
|
-
Every directory `sync` manages gets a `.skillmux` marker file (`{"managed_by": "skillmux", "target": "<name>", "created_at": ...}`). `sync` refuses to touch a directory that exists but has no marker — run `skillmux init --target <name> --yes` first, which either creates the directory fresh or adopts an existing one in place (contents untouched). This is also why `sync --restore-monolith` (which deletes the marker and replaces the whole directory with one symlink straight to the vault) requires re-running `init` before that target can be `sync`'d again.
|
|
118
|
+
Every directory `sync` manages gets a `.skillmux` marker file (`{"managed_by": "skillmux", "role": "target", "target": "<name>", "created_at": ...}`). `sync` refuses to touch a directory that exists but has no marker — run `skillmux init --target <name> --yes` first, which either creates the directory fresh or adopts an existing one in place (contents untouched). This is also why `sync --restore-monolith` (which deletes the marker and replaces the whole directory with one symlink straight to the vault) requires re-running `init` before that target can be `sync`'d again.
|
|
119
|
+
|
|
120
|
+
The same `.skillmux` marker filename and JSON shape is also used for `local_vault_paths` entries (see below), distinguished by `role: "local_vault"` with a `vault_path` field instead of `target`. Markers written before this `role` field existed have no `role` key at all — they're read back as `role: "target"`, so nothing already on disk needs migrating.
|
|
121
|
+
|
|
122
|
+
### Local vault overlays
|
|
123
|
+
|
|
124
|
+
`local_vault_paths` (in `config.toml`, alongside `vault_path`) lets one machine layer override-only skills on top of the shared vault — a skill being authored locally, a machine-specific script, or a patched copy of an upstream skill — without touching `vault_path` itself:
|
|
125
|
+
|
|
126
|
+
```toml
|
|
127
|
+
vault_path = "~/skills" # unchanged: canonical, owns skillmux.toml and the sync git hook
|
|
128
|
+
local_vault_paths = ["~/skills-local"] # optional, default []: override-only, checked first
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
- **Resolution order**: for any given `skill_id`, `local_vault_paths` entries are checked first, in array order; `vault_path` is the fallback. This applies everywhere a skill's on-disk location matters — indexing, `resolve_skill`/`fetch_skill` delivery, and `sync`'s symlink target.
|
|
132
|
+
- **`vault_path` keeps its exact existing meaning.** `skillmux.toml` and the `sync --install-hook` git hook only ever live in `vault_path`; `skillmux doctor` warns if it finds a stray manifest inside a `local_vault_paths` entry instead.
|
|
133
|
+
- **`[core]`/`[project.*]` pins must resolve from `vault_path`.** Since the manifest is meant to be portable, `sync`/`doctor` reject a pin backed only by a `local_vault_paths` entry — see the manifest section above.
|
|
134
|
+
- **Not yet covered**: `startVaultWatcher`'s live filesystem watch still only watches `vault_path`; a change inside a `local_vault_paths` entry is picked up lazily (on the next `resolve_skill`/`fetch_skill`/`sync` call, via the same mtime staleness check `vault_path` already uses), not instantly.
|
|
135
|
+
|
|
136
|
+
**Visibility.** A `skill_id` present in more than one root is silently resolved via the precedence above with no output during normal use — two commands make that resolution visible on demand:
|
|
137
|
+
|
|
138
|
+
- `skillmux which <skill_id>` — prints which root actually serves that skill, and names every root it shadows:
|
|
139
|
+
```
|
|
140
|
+
$ skillmux which my-skill
|
|
141
|
+
my-skill: serving from /home/user/skills-local
|
|
142
|
+
shadows: /home/user/skills
|
|
143
|
+
```
|
|
144
|
+
Exits non-zero with `<skill_id>: not found in vault_path or local_vault_paths` if no root has it.
|
|
145
|
+
- `skillmux doctor` reports every shadowed skill_id as an informational check (`shadowed:<skill_id>`, always `ok`) alongside its existing vault/manifest/embedding checks — so a scan of `doctor` output surfaces every override in one place, not just the one you thought to ask about.
|
|
146
|
+
|
|
147
|
+
**Discoverability.** A `local_vault_paths` entry is otherwise just a bare directory — nothing on disk says it belongs to skillmux or which `vault_path` it overlays. `skillmux local-vault init <path>` writes a `.skillmux` marker recording that relationship:
|
|
148
|
+
|
|
149
|
+
```sh
|
|
150
|
+
skillmux local-vault init ~/skills-local
|
|
151
|
+
# wrote /home/user/skills-local/.skillmux (role: local_vault, vault_path: /home/user/skills)
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
`<path>` must already be one of the configured `local_vault_paths` entries and must exist on disk — the command only ever writes the marker, it never adds the path to `config.toml` for you. `skillmux doctor` reports each entry's marker status (`local_vault_marker:<path>`): `ok: false` if no marker exists yet (with the exact `local-vault init` command to fix it), or if the marker's recorded `vault_path` no longer matches the one currently configured (drift — e.g. after copying the directory to a machine with a different `vault_path`).
|
package/docs/releasing.md
CHANGED
|
@@ -1,39 +1,78 @@
|
|
|
1
1
|
# Releasing
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Release Please creates releases after `main` is green. When its release PR is
|
|
4
|
+
merged, the same workflow creates the matching version tag and runs the
|
|
5
|
+
publishing jobs directly. The pipeline does not rely on a second workflow being
|
|
6
|
+
triggered by a tag created with `GITHUB_TOKEN`, and production environment
|
|
7
|
+
secrets do not cross a reusable-workflow boundary.
|
|
4
8
|
|
|
5
9
|
## Prepare
|
|
6
10
|
|
|
7
|
-
1.
|
|
8
|
-
2.
|
|
9
|
-
3.
|
|
11
|
+
1. Merge changes to `main` using Conventional Commit titles.
|
|
12
|
+
2. Review the Release Please PR, including its version and changelog.
|
|
13
|
+
3. Confirm CI passes, then merge the Release Please PR.
|
|
10
14
|
|
|
11
|
-
## Publish
|
|
15
|
+
## Publish automatically
|
|
12
16
|
|
|
13
|
-
|
|
17
|
+
After the Release Please PR merges, the same workflow run creates the tag and
|
|
18
|
+
GitHub Release, then publishes with the exact tag and commit SHA returned by
|
|
19
|
+
Release Please. The package version, tag, and commit must all match before
|
|
20
|
+
publishing begins.
|
|
14
21
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
22
|
+
There is intentionally no tag-push entry point. GitHub does not start a second
|
|
23
|
+
workflow for a tag created with the default `GITHUB_TOKEN`; publishing in the
|
|
24
|
+
Release Please workflow avoids that event-suppression edge case without adding
|
|
25
|
+
a PAT or GitHub App token.
|
|
26
|
+
|
|
27
|
+
## Exceptional recovery
|
|
21
28
|
|
|
22
|
-
|
|
29
|
+
Use the manual `workflow_dispatch` entry point only to backfill an existing
|
|
30
|
+
Release Please tag. The workflow verifies that the tag is clean SemVer,
|
|
31
|
+
resolves to the checked-out commit, is reachable from `main`, and still matches
|
|
32
|
+
`package.json`. It also requires a matching existing GitHub Release. Manual
|
|
33
|
+
backfills never publish npm, so a container or binary recovery cannot
|
|
34
|
+
accidentally republish an existing npm version.
|
|
23
35
|
|
|
24
36
|
The release workflow publishes:
|
|
25
37
|
|
|
26
38
|
- `skillmux-linux-amd64`
|
|
27
39
|
- `skillmux-linux-arm64`
|
|
28
40
|
- GitHub build provenance attestations when the repository is public
|
|
29
|
-
- Full image: `:<version>`, `:<major>.<minor>`,
|
|
30
|
-
|
|
31
|
-
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
41
|
+
- Full image to GHCR and Docker Hub: `:<version>`, `:<major>.<minor>`,
|
|
42
|
+
and `:latest`
|
|
43
|
+
- Slim image to GHCR and Docker Hub: `:<version>-slim`,
|
|
44
|
+
`:<major>.<minor>-slim`, and `:latest-slim`
|
|
45
|
+
- Multi-architecture `linux/amd64` and `linux/arm64` images with SBOM and
|
|
46
|
+
provenance
|
|
47
|
+
|
|
48
|
+
GHCR is the canonical container build. After both GHCR variants are published,
|
|
49
|
+
Docker Hub copies those exact multi-architecture images under its matching
|
|
50
|
+
tags. A Docker Hub authentication or availability failure therefore cannot
|
|
51
|
+
prevent GHCR from publishing.
|
|
52
|
+
|
|
53
|
+
Each Docker tag is a multi-architecture manifest. Users run the same tag on
|
|
54
|
+
AMD64 and ARM64; Docker automatically pulls the matching image.
|
|
55
|
+
|
|
56
|
+
The two variants are separate multi-architecture manifests rather than
|
|
57
|
+
architecture-suffixed public tags. Docker selects the correct AMD64 or ARM64
|
|
58
|
+
image for the host automatically. Pin `:<version>` or `:<version>-slim` for
|
|
59
|
+
reproducible deployments; use the floating `latest` tags only when automatic
|
|
60
|
+
upgrades are intentional.
|
|
61
|
+
|
|
62
|
+
Container images are published to:
|
|
63
|
+
|
|
64
|
+
- `ghcr.io/klhq/skillmux`
|
|
65
|
+
- `${DOCKERHUB_USERNAME}/skillmux` on Docker Hub
|
|
66
|
+
|
|
67
|
+
The `production-release` GitHub environment provides the
|
|
68
|
+
`DOCKERHUB_USERNAME` variable and `DOCKERHUB_TOKEN` secret. The npm job also
|
|
69
|
+
uses this environment as its Trusted Publisher identity; configure npm with
|
|
70
|
+
workflow `release-please.yml` and environment `production-release`. No
|
|
71
|
+
long-lived npm token is required. GitHub Packages uses the workflow's scoped
|
|
72
|
+
`GITHUB_TOKEN`.
|
|
73
|
+
Private repositories still publish BuildKit SBOM/provenance with container
|
|
74
|
+
images, but GitHub artifact attestations are skipped because GitHub does not
|
|
75
|
+
support them for user-owned private repositories.
|
|
37
76
|
|
|
38
77
|
Verify downloaded binaries with build provenance attestation:
|
|
39
78
|
|
package/package.json
CHANGED