wyvrnpm 2.3.2 → 2.8.1

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/README.md CHANGED
@@ -4,7 +4,7 @@ A simple, private C++ package manager that works with any static file hosting pr
4
4
 
5
5
  There is no central registry. You control where packages are hosted.
6
6
 
7
- > **README version: 2.1.0** — Adds CMake toolchain generation, the `wyvrnpm build` command, declarative compatibility, and `--build=missing` source-build.
7
+ > **README version: 2.8.1** — Highlights since 2.4.0: non-ABI build-time `conf` block (CMake cache variables via recipe / team profile / `wyvrn.local.json` / CLI `--conf`), per-package `source` pin for multi-source defense, profile inheritance via `extends`, pattern-based `settingsOverrides` (`boost*: {...}`), `wyvrnpm cache list` / `cache prune` for selective source-build-cache cleanup, `install --dry-run` (JSON plan without side effects), `install --build=always` to force source-rebuild, `--token-env` for CI credentials, and a raft of security hardening (Zip Slip, MSVC arch allow-list). Full list in `claude/EVALUATION.md`.
8
8
 
9
9
  ---
10
10
 
@@ -37,16 +37,19 @@ wyvrnpm configure add-source --kind publish --name default --url s3://my-bucket
37
37
  # 3. Detect and save your build environment as the default profile
38
38
  wyvrnpm configure profile detect
39
39
 
40
- # 4. Install dependencies (resolved against your build profile)
40
+ # 4. Add dependencies (resolves latest from your configured sources)
41
+ wyvrnpm add zlib fmt@^10.0.0
42
+
43
+ # 5. Install dependencies (resolved against your build profile)
41
44
  wyvrnpm install
42
45
 
43
- # 5. Configure and build via the generated CMake preset
46
+ # 6. Configure and build via the generated CMake preset
44
47
  wyvrnpm build
45
48
 
46
- # 6. Publish your package (uses the active profile automatically)
49
+ # 7. Publish your package (uses the active profile automatically)
47
50
  wyvrnpm publish
48
51
 
49
- # 7. (Optional) Link local packages for development
52
+ # 8. (Optional) Link local packages for development
50
53
  cd ../my-local-lib && wyvrnpm link
51
54
  cd ../my-app && wyvrnpm link my-local-lib
52
55
  ```
@@ -94,6 +97,37 @@ wyvrnpm configure profile set-default release
94
97
 
95
98
  Once a default profile is saved, `install` and `publish` use it automatically — no flags required.
96
99
 
100
+ ### Profile inheritance — `extends` (2.8.0+)
101
+
102
+ Teams often end up with a small fleet of near-duplicate profiles (`windows-msvc-debug`, `windows-msvc-release`, `windows-msvc-x86`, …) that drift over time. A profile can inherit from another via `extends`:
103
+
104
+ ```json
105
+ {
106
+ "extends": "windows-msvc-base",
107
+ "compiler.cppstd": "23"
108
+ }
109
+ ```
110
+
111
+ Or from multiple parents (later entries override earlier):
112
+
113
+ ```json
114
+ {
115
+ "extends": ["windows-base", "team-defaults"],
116
+ "compiler.runtime": "static"
117
+ }
118
+ ```
119
+
120
+ Rules:
121
+
122
+ - `[settings]` fields merge per-key — child wins.
123
+ - The `conf` block (if present) merges per-leaf — child overrides single keys without dropping siblings.
124
+ - Overlay value `null` explicitly unsets a parent field (Conan's `None` semantics).
125
+ - Inheritance cycles are detected and fail with the full trail (`alpha → beta → alpha`).
126
+ - Missing parent names fail fast — no silent drop.
127
+ - `extends` itself is stripped from the resolved profile and is **not** folded into `profileHash`, so `{ "extends": "base" }` with no overrides hashes byte-identically to `base`.
128
+
129
+ `wyvrnpm configure profile show <name>` prints the raw `extends:` chain alongside the resolved `[settings]` so you can inspect inheritance without grepping JSON.
130
+
97
131
  ---
98
132
 
99
133
  ## Commands
@@ -109,6 +143,42 @@ wyvrnpm init --root ./my-project
109
143
 
110
144
  ---
111
145
 
146
+ ### `wyvrnpm add`
147
+
148
+ Adds one or more dependencies to `wyvrn.json` without downloading them. Useful when you know the package name but not the latest version.
149
+
150
+ - **Bare name** — resolves `latest.json` from your configured install sources and writes `"^<latest>"` (caret range, major-pinned).
151
+ - **`name@<version>`** — writes the given string verbatim. Accepts exact pins (`1.2.3.4`), caret/tilde ranges (`^1.2.3.4`, `~1.2.3.4`), and explicit comparators (`>=1.80.0 <2.0.0`).
152
+
153
+ If the dependency is already listed, it is replaced. When the prior entry was an object (`{ version, settings, options }`), the `settings` and `options` are preserved — only the version field is updated.
154
+
155
+ `add` does **not** run install. Chain `wyvrnpm install` afterwards to download.
156
+
157
+ ```bash
158
+ # Resolve latest, write "^<latest>"
159
+ wyvrnpm add zlib
160
+
161
+ # Pin exactly
162
+ wyvrnpm add zlib@1.3.0.0
163
+
164
+ # Use a caret / tilde / comparator range
165
+ wyvrnpm add fmt@^10.0.0
166
+ wyvrnpm add boost@">=1.80.0 <2.0.0"
167
+
168
+ # Multiple in one call
169
+ wyvrnpm add zlib fmt@10.2.0.0 spdlog
170
+ ```
171
+
172
+ **Options**
173
+
174
+ | Option | Default | Description |
175
+ |---|---|---|
176
+ | `--source` / `-s` | *(config)* | Base URL used for the `latest` lookup. Repeat for multiple. |
177
+ | `--platform` | `win_x64` | v1 legacy platform sub-path (used as a fallback during `latest` lookup). |
178
+ | `--manifest` | `./wyvrn.json` | Path to the manifest file. |
179
+
180
+ ---
181
+
112
182
  ### `wyvrnpm install`
113
183
 
114
184
  Resolves the full dependency graph and downloads all packages.
@@ -122,7 +192,7 @@ Resolves the full dependency graph and downloads all packages.
122
192
  3. **Source build** (with `--build=missing`) — clones the package's source at the published `gitSha`, resolves its own dependencies, invokes CMake through the shared toolchain generator, and caches the result under `%LOCALAPPDATA%\wyvrnpm\bc\`. On Windows/MSVC with a Ninja or Make generator, `vcvarsall.bat` is auto-activated.
123
193
  4. **v1 legacy path** — only when no v2 entry exists for the package at all.
124
194
 
125
- The pre-2.1 "same OS + arch, any compiler" loose fallback is removed from the default path. It remains reachable for one release cycle via `WYVRNPM_ALLOW_LOOSE_COMPAT=1` and prints a deprecation warning.
195
+ The pre-2.1 "same OS + arch, any compiler" loose fallback is no longer available. Publish a build for your profile, add a declarative `compatibility` block to the recipe, or rerun with `--build=missing` to source-build.
126
196
 
127
197
  ```bash
128
198
  # Use configured sources and the default build profile
@@ -141,6 +211,13 @@ wyvrnpm install \
141
211
 
142
212
  # Build any missing binary from source
143
213
  wyvrnpm install --build=missing
214
+
215
+ # Force every dep to be rebuilt from source (debugging / reproducibility check)
216
+ wyvrnpm install --build=always
217
+
218
+ # Print the resolved install plan WITHOUT downloading (CI-friendly with --format=json)
219
+ wyvrnpm install --dry-run
220
+ wyvrnpm install --dry-run --format=json
144
221
  ```
145
222
 
146
223
  **Options**
@@ -154,6 +231,8 @@ wyvrnpm install --build=missing
154
231
  | `--timeout` | `300` | HTTP request timeout in seconds. |
155
232
  | `--manifest` | `./wyvrn.json` | Path to the manifest file. |
156
233
  | `--root` | `./` | Project root (packages extracted to `{root}/wyvrn_internal/`). |
234
+ | `--dry-run` | `false` | Run the resolver + profile/options math, print the install plan, and exit. No download, no `wyvrn.lock`, no `wyvrn_internal/`, no toolchain. Combine with `--format=json` for CI ingestion (payload carries `command: "install-dry-run"`). |
235
+ | `--conf` | — | Non-ABI build-time override. Repeatable. Format: `<namespace.leaf>=<value>` — e.g. `--conf cmake.cache.CHROMA_ENABLE_TEST=ON`. Layered: CLI > `wyvrn.local.json` > named-profile `conf` > recipe. Does **not** fold into `profileHash`. See [Build-time configuration (`conf`)](#build-time-configuration-conf). |
157
236
 
158
237
  Packages are extracted to `wyvrn_internal/{name}/`. A `wyvrn.lock` file is written alongside the manifest recording the exact resolved versions, profile, and per-package SHA256. On subsequent installs the lock file pins all previously resolved versions — only newly added dependencies are resolved fresh.
159
238
 
@@ -164,6 +243,35 @@ After download, `install` also generates:
164
243
  - `wyvrn_internal/wyvrn_profile.json` — snapshot of the active build profile.
165
244
  - `CMakePresets.json` (or `CMakeUserPresets.json` if the project root file is user-owned) with a `wyvrn-<profile>` configure preset. Presets for other profiles are preserved so multiple profiles coexist.
166
245
 
246
+ **Toolchain variables exposed to your `CMakeLists.txt` (valid BEFORE `project()`)**:
247
+
248
+ | Variable | Value |
249
+ |---|---|
250
+ | `WYVRN_PROFILE_NAME` / `WYVRN_PROFILE_HASH` | Active profile name + 16-char hash |
251
+ | `WYVRN_PROFILE_OS` / `WYVRN_PROFILE_ARCH` | OS + architecture from the profile |
252
+ | `WYVRN_PROFILE_COMPILER` / `WYVRN_PROFILE_COMPILER_VERSION` | Compiler identity |
253
+ | `WYVRN_PROFILE_CPPSTD` / `WYVRN_PROFILE_RUNTIME` | C++ standard + MSVC runtime linkage |
254
+ | `WYVRN_ARCH_SUFFIX` | `"64"` for 64-bit arches, `"32"` for 32-bit, empty for unmapped. Derived at toolchain-generation time so it's usable before `project()` — unlike `CMAKE_SIZEOF_VOID_P`. |
255
+
256
+ The optional helper `WYVRN_APPLY_ARCH_SUFFIX(<target> [BASE_NAME <name>])` from the bundled `macros.cmake` applies the suffix to a target's `OUTPUT_NAME` so x64 and x86 builds of the same library coexist on disk without overwriting each other. Target linkage (`target_link_libraries`) is unaffected — only the produced filename changes.
257
+
258
+ The bundled `variables.cmake` also suffixes the consumer's own output directories with `WYVRN_ARCH_SUFFIX` so multi-arch builds from one source tree don't clobber each other:
259
+
260
+ | Profile arch | Archive/library dir | Runtime (DLL/EXE) dir |
261
+ |---|---|---|
262
+ | `x86_64` / `armv8` | `output/lib64/` | `output/bin64/<CONFIG>/` |
263
+ | `x86` / `armv7` | `output/lib/` | `output/bin/<CONFIG>/` |
264
+ | unmapped | `output/lib/` | `output/bin/<CONFIG>/` |
265
+
266
+ Prior to `2.5.0` the runtime directory was always `output/bin/<CONFIG>/` regardless of arch — a multi-arch consumer building x64 then x86 would silently overwrite the first arch's DLLs. Scripts that hard-code `output/bin/…` expecting x64 payloads need to switch to `output/bin64/…` after upgrading.
267
+
268
+ ```cmake
269
+ project(String CXX)
270
+ add_library(String STATIC ...)
271
+ WYVRN_APPLY_ARCH_SUFFIX(String) # → String64.lib / String32.lib
272
+ WYVRN_APPLY_ARCH_SUFFIX(String BASE_NAME my-str) # → my-str64.lib / my-str32.lib
273
+ ```
274
+
167
275
  ---
168
276
 
169
277
  ### `wyvrnpm build`
@@ -189,6 +297,10 @@ wyvrnpm build --target my-lib
189
297
  # Wipe the build directory first
190
298
  wyvrnpm build --clean
191
299
 
300
+ # Override the preset's generator (pair with --clean when switching generators)
301
+ wyvrnpm build --clean --generator "Visual Studio 17 2022"
302
+ wyvrnpm build --clean -G "Ninja Multi-Config"
303
+
192
304
  # Build, then install to the CMake install prefix
193
305
  wyvrnpm build --install
194
306
  wyvrnpm build --install --install-prefix C:\out\stage
@@ -210,6 +322,7 @@ wyvrnpm build -- -j 8
210
322
  | `--target` / `-t` | *(all)* | Specific CMake target to build. |
211
323
  | `--verbose` / `-v` | `false` | Pass `--verbose` to `cmake --build`. |
212
324
  | `--clean` | `false` | Remove the build directory before configuring. |
325
+ | `--generator` / `-G` | *(from preset)* | Override the CMake generator used at configure time (e.g. `"Visual Studio 17 2022"`, `"Ninja"`, `"Ninja Multi-Config"`). CMake bakes the generator into the cache, so pair with `--clean` when switching. When the effective generator is Visual Studio, `-A <platform>` is auto-derived from the profile's arch (`x86→Win32`, `x86_64→x64`, `armv8→ARM64`, `armv7→ARM`) so VS doesn't silently default to x64 on 32-bit profiles. |
213
326
  | `--install` | `false` | Run `cmake --install` after a successful build. |
214
327
  | `--install-prefix` | *(baked in)* | Override `CMAKE_INSTALL_PREFIX` for `--install` without reconfiguring. |
215
328
  | `--auto-install` | `true` | Auto-run `wyvrnpm install` when the toolchain is stale. Disable with `--no-auto-install`. |
@@ -240,7 +353,9 @@ wyvrnpm publish --source default
240
353
  # Explicit destination URL
241
354
  wyvrnpm publish --source s3://my-bucket/packages --aws-profile my-sso
242
355
 
243
- # HTTP server with token auth
356
+ # HTTP server with token auth (CI-preferred: --token-env avoids argv leakage)
357
+ wyvrnpm publish --source https://pkg.corp.com --token-env WYVRN_HTTP_TOKEN
358
+ # Equivalent for interactive use; --token lands in shell history / ps output
244
359
  wyvrnpm publish --source https://pkg.corp.com --token mytoken
245
360
 
246
361
  # Local directory or SMB share
@@ -262,6 +377,7 @@ wyvrnpm publish --force
262
377
  | `--profile` / `-p` | *(config / "default")* | Named build profile to publish under. |
263
378
  | `--aws-profile` | *(config)* | AWS SSO profile for S3 authentication. |
264
379
  | `--token` | *(config)* | Bearer token for HTTP server authentication. |
380
+ | `--token-env` | *(config)* | Name of an env var holding the bearer token. Preferred for CI — the literal value never lands in `argv` or shell history. |
265
381
  | `--path` | `.` | Directory to zip and publish. |
266
382
  | `--manifest` | `./wyvrn.json` | Path to the manifest file. |
267
383
  | `--force` / `-f` | `false` | Overwrite an existing published version (same version + profile hash). |
@@ -307,6 +423,130 @@ wyvrnpm clean --build-cache
307
423
  | Option | Default | Description |
308
424
  |---|---|---|
309
425
  | `--build-cache` | `false` | Also remove the source-build cache under `%LOCALAPPDATA%\wyvrnpm\bc\` (Unix: `~/.cache/wyvrnpm/bc/`). |
426
+ | `--uploaded-built` | `false` | Wipe only the local record of what this machine has uploaded via `--upload-built` (the `.uploaded-to.json` sidecars). Does **not** touch the registry, the artefacts, or `wyvrn_internal/`. |
427
+
428
+ For selective cache pruning (keep last N variants, drop entries older than N days), use `wyvrnpm cache prune` instead — see below.
429
+
430
+ ---
431
+
432
+ ### `wyvrnpm cache` (2.8.0+)
433
+
434
+ Inspect and selectively prune the machine-wide source-build cache at `%LOCALAPPDATA%\wyvrnpm\bc\` (Unix: `~/.cache/wyvrnpm/bc/`). Useful once `--upload-built` adoption balloons cache size on dev rigs — `clean --build-cache` is the blunt alternative that wipes everything.
435
+
436
+ ```bash
437
+ # List every cached entry as a table
438
+ wyvrnpm cache list
439
+
440
+ # Filter by package name (glob)
441
+ wyvrnpm cache list "boost*"
442
+
443
+ # Machine-readable output for CI
444
+ wyvrnpm cache list --format json
445
+
446
+ # Keep the 2 most-recently-touched variants per (name, version)
447
+ wyvrnpm cache prune --keep-last 2
448
+
449
+ # Drop entries older than 30 days
450
+ wyvrnpm cache prune --older-than 30d
451
+
452
+ # Combine: keep 1 recent per version AND drop anything stale
453
+ wyvrnpm cache prune --keep-last 1 --older-than 30d
454
+
455
+ # Preview — print what would be removed, delete nothing
456
+ wyvrnpm cache prune --keep-last 2 --dry-run
457
+ ```
458
+
459
+ **`cache list` options**
460
+
461
+ | Option | Default | Description |
462
+ |---|---|---|
463
+ | `[pattern]` / `--pattern` | *(none)* | Glob over package names (`*` for any chars, `?` for one). |
464
+ | `--format` | `text` | `text` (table) or `json` (structured payload for CI). |
465
+
466
+ **`cache prune` options**
467
+
468
+ | Option | Default | Description |
469
+ |---|---|---|
470
+ | `--keep-last <N>` | *(none)* | Retain the N most-recently-touched entries **per (name, version) group**. A four-profile rig keeping-last-2 still retains the two newest profile variants of each version. |
471
+ | `--older-than <dur>` | *(none)* | Remove entries whose mtime is older than the threshold. Format: `<number><unit>`, unit ∈ `s` / `m` / `h` / `d` (e.g. `30d`, `72h`). |
472
+ | `--pattern <glob>` | *(none)* | Restrict scope to package names matching the glob. |
473
+ | `--dry-run` | `false` | Print what would be removed without deleting. |
474
+
475
+ At least one of `--keep-last` / `--older-than` is required — calling `prune` bare errors out with a pointer to `clean --build-cache` (the nuke-everything path).
476
+
477
+ ---
478
+
479
+ ### `wyvrnpm show`
480
+
481
+ Read-only registry-metadata inspector. Given `<name>[@<version>[@<profileHash>]]`, queries the first configured install source that knows the package (override with `--source`) and prints what's published.
482
+
483
+ ```bash
484
+ # Index of every published version (one HTTP round trip per source)
485
+ wyvrnpm show zlib
486
+
487
+ # Narrow to one version — fans out a per-profile query so uploadedBy,
488
+ # compatibility, and declared options show up per build
489
+ wyvrnpm show zlib@1.3.0.0
490
+
491
+ # Narrow to one specific build
492
+ wyvrnpm show zlib@1.3.0.0@a1b2c3d4e5f60718
493
+
494
+ # Pick the source explicitly (skips install-source lookup order)
495
+ wyvrnpm show zlib@1.3.0.0 --source s3://team-pkgs
496
+
497
+ # Machine-readable for CI ingestion
498
+ wyvrnpm show zlib@1.3.0.0 --format json
499
+ ```
500
+
501
+ What gets surfaced per profile:
502
+
503
+ - **origin** — `author-publish` vs `consumer-upload` (derived from whether the manifest carries an `uploadedBy` block).
504
+ - **uploadedBy** — when set, the consumer-upload provenance (`kind`, `builtFromGit`, `builtFromSha`, `uploadedAt`, `wyvrnpmVersion`).
505
+ - **compatibility** — the recipe's declarative compat block, if any.
506
+ - **declaredOptions** — the author-declared `options` map from the recipe.
507
+ - **resolvedOptions** — the resolved option values that produced this profile's `profileHash`.
508
+ - **publishedConf** — recipe + CLI `--conf` the publisher captured (informational; doesn't affect resolution).
509
+ - **signature** — when PLAN-SIGNING phase 1 ships, signer identity; absent today.
510
+
511
+ Useful for: auditing consumer uploads (who pushed this artefact, from which commit), spotting profile drift between two consumers, verifying a package's declared options before adding it to a recipe.
512
+
513
+ **Options**
514
+
515
+ | Option | Default | Description |
516
+ |---|---|---|
517
+ | `pkg` *(positional)* | *(required)* | `<name>[@<version>[@<profileHash>]]` |
518
+ | `--source` / `-s` | *(first configured install source)* | URL or configured source name. Repeatable. |
519
+ | `--format` | `text` | `text` or `json` (see [Shape — `show`](#shape--show) below). |
520
+ | `--token` / `--token-env` / `--aws-profile` | | Auth forwarded to the provider the same way `install` does. |
521
+
522
+ ---
523
+
524
+ ### `wyvrnpm install-skill`
525
+
526
+ Installs the bundled wyvrnpm Agent Skill — the authoring guide Claude Code uses when it sees a `wyvrn.json` in the workspace. Ships as a `.skill` zip inside the npm package (`claude/skills/wyvrnpm.skill`) and is extracted into `~/.claude/skills/wyvrnpm/` on demand.
527
+
528
+ ```bash
529
+ # One-shot: install the bundled skill for Claude Code
530
+ wyvrnpm install-skill --claude
531
+
532
+ # Overwrite an existing installation (e.g. after upgrading wyvrnpm)
533
+ wyvrnpm install-skill --claude --force
534
+
535
+ # Preview without writing
536
+ wyvrnpm install-skill --claude --dry-run
537
+ ```
538
+
539
+ You only need this if you want Claude Code to pick up the skill. The CLI itself works without it.
540
+
541
+ **Options**
542
+
543
+ | Option | Default | Description |
544
+ |---|---|---|
545
+ | `--claude` | `false` | Install for Claude Code (extracts to `~/.claude/skills/wyvrnpm/` + writes the `.skill` zip alongside). Required — present so a future `--cursor` / `--vscode` target can slot in without CLI churn. |
546
+ | `--force` | `false` | Overwrite an existing skill install. Without it, the command refuses to clobber. |
547
+ | `--dry-run` | `false` | Print the destination paths and intended actions without writing. |
548
+
549
+ **CLAUDE.md §9 note for contributors:** the tested path for deploying a locally-edited skill is `node scripts/repack-skill.js && node bin/wyvrn.js install-skill --claude --force` — never `rm -rf` + `cp -r`.
310
550
 
311
551
  ---
312
552
 
@@ -460,7 +700,8 @@ wyvrnpm configure add-source \
460
700
  | `--name` | Yes | Unique name for this source. |
461
701
  | `--url` | Yes | Destination URL, URI, or file path. |
462
702
  | `--profile` | No | AWS SSO profile (S3 sources). |
463
- | `--token` | No | Bearer token (HTTP sources). |
703
+ | `--token` | No | Bearer token (HTTP sources). Persisted in plaintext in `config.json` — prefer `--token-env` for CI and shared setups. |
704
+ | `--token-env` | No | Name of an env var whose value is the bearer token. Only the **name** lands in `config.json`; the value is resolved fresh on every `wyvrnpm` call. |
464
705
 
465
706
  #### `wyvrnpm configure remove-source`
466
707
 
@@ -862,6 +1103,56 @@ Example:
862
1103
  }
863
1104
  ```
864
1105
 
1106
+ ### Per-dependency source pin (2.8.0+)
1107
+
1108
+ When you have more than one configured install source (e.g. a primary S3 bucket plus a mirror), you can pin a security-sensitive dep to its canonical source by name:
1109
+
1110
+ ```json
1111
+ {
1112
+ "dependencies": {
1113
+ "OpenSSL": {
1114
+ "version": "3.0.0.0",
1115
+ "source": "primary-s3"
1116
+ }
1117
+ }
1118
+ }
1119
+ ```
1120
+
1121
+ `"primary-s3"` must match a `name` from `wyvrnpm configure list`; an unknown name fails install with exit code 1 rather than silently fanning out. The pin closes two common multi-source holes:
1122
+
1123
+ - **Mirror shadowing.** A package accidentally published to the mirror can't substitute for the primary's copy.
1124
+ - **Dependency confusion.** A squatter on a secondary source can't poison the build.
1125
+
1126
+ Pins are direct-only — they don't cascade to the pinned package's own transitive deps. CLI `--source` overrides config entirely, so on a run with `--source`, any manifest pin is ignored with a warning rather than an error. The lockfile records `source: "<name>"` on pinned entries so reviewers can spot at a glance that the artefact came from the declared source; absent on non-pinned entries so pre-2.8.0 locks remain byte-identical.
1127
+
1128
+ ### Pattern-based `settingsOverrides` (2.8.0+)
1129
+
1130
+ When many deps need the same settings override — every `boost-*` family member needing `compiler.cppstd: "17"`, or every `*-debug` variant needing `compiler.runtime: "static"` — declare the pattern at the top level of the manifest instead of copy-pasting per dep:
1131
+
1132
+ ```json
1133
+ {
1134
+ "dependencies": {
1135
+ "boost-filesystem": "1.80.0.0",
1136
+ "boost-system": "1.80.0.0",
1137
+ "boost-thread": "1.80.0.0",
1138
+ "zlib": "1.3.0.0"
1139
+ },
1140
+ "settingsOverrides": {
1141
+ "boost*": { "compiler.cppstd": "17" },
1142
+ "*-debug": { "compiler.runtime": "static" }
1143
+ }
1144
+ }
1145
+ ```
1146
+
1147
+ Rules:
1148
+
1149
+ - Keys are globs — `*` matches any chars, `?` matches exactly one, literal names work too. Both ends are anchored (no substring match unless you add `*` on either side).
1150
+ - Values accept the same keys as profile overrides: `os`, `arch`, `compiler`, `compiler.version`, `compiler.cppstd`, `compiler.runtime`. Unknown keys fail install with a clear error.
1151
+ - More-specific patterns win: literal `"boost-filesystem"` beats `"boost*"`, which beats `"*"`.
1152
+ - A literal per-dep `settings` block inside `dependencies` still wins over any pattern. Use that for the one dep that deviates from the family rule.
1153
+
1154
+ Each matching pattern shifts that dep's `profileHash`, so different settings produce distinct registry artefacts — identical semantics to literal per-dep `settings`.
1155
+
865
1156
  ### Package options (`options`)
866
1157
 
867
1158
  A library author can declare **options** — compile-time feature toggles that change the produced binary's ABI. Examples: `shared` (shared vs static library), `minizip` (zlib's minizip support), `fips` (OpenSSL's FIPS mode). Each option produces a distinct `profileHash`, so every option combination gets its own artefact on the registry.
@@ -956,6 +1247,169 @@ On Windows with MSVC and a non-Visual-Studio generator (Ninja, Make), `vcvarsall
956
1247
 
957
1248
  ---
958
1249
 
1250
+ ## Build-time configuration (`conf`)
1251
+
1252
+ `conf` is wyvrnpm's channel for **non-ABI** build-time knobs — CMake cache variables, feature toggles that change `add_subdirectory()` but not the public binary, and (in future phases) other build-tool settings. It is a deliberate sibling of `options`, with the opposite hashing property:
1253
+
1254
+ | | `options` (F1) | `conf` (this section) |
1255
+ |---|---|---|
1256
+ | Folds into `profileHash`? | Yes — every combination is a distinct registry artefact. | **No.** Informational only; two builds with different `conf` still share the same `profileHash`. |
1257
+ | Typical use | `shared`/`minizip`/`fips` — things that genuinely change the binary. | `CHROMA_ENABLE_TEST`, `BUILD_DOCS`, `CMAKE_EXPORT_COMPILE_COMMANDS` — things that don't. |
1258
+ | Consumer can override? | Yes (per-dep `options` in `wyvrn.json` or `-o pkg:name=val` on CLI). | Yes (recipe → named-profile `conf` → `wyvrn.local.json` → `--conf`, in that precedence). |
1259
+
1260
+ Phase 1 (2.6.0+) supports a single namespace: `cmake.cache.<VAR>`. It flows into the generated `CMakePresets.json`'s `cacheVariables`.
1261
+
1262
+ ### The four layers
1263
+
1264
+ ```
1265
+ highest CLI --conf cmake.cache.X=ON
1266
+ │ ─────
1267
+ │ local wyvrn.local.json (per-developer, gitignored)
1268
+ │ ─────
1269
+ │ profile named-profile JSON.conf (team-shared, checked-in)
1270
+ │ ─────
1271
+ │ recipe wyvrn.json.conf (author defaults)
1272
+ ▼ ─────
1273
+ lowest (nothing — key absent)
1274
+ ```
1275
+
1276
+ **Per-leaf override, not per-branch replace.** A CLI `--conf cmake.cache.FOO=ON` on top of a recipe setting `BAR=OFF` yields both keys — it does not discard `BAR`. Any omitted layer is equivalent to an empty map at that position — a project with no `wyvrn.local.json` and no profile `conf` merges recipe + CLI exactly as phase 1.
1277
+
1278
+ ### Recipe-level defaults
1279
+
1280
+ Authors can declare defaults on their package:
1281
+
1282
+ ```json
1283
+ {
1284
+ "name": "ChromaSDK",
1285
+ "version": "2.5.0.0",
1286
+ "conf": {
1287
+ "cmake": {
1288
+ "cache": {
1289
+ "CHROMA_ENABLE_TEST": "OFF",
1290
+ "CHROMA_ENABLE_EXAMPLES": "OFF",
1291
+ "CHROMA_LOG_LEVEL": "WARNING"
1292
+ }
1293
+ }
1294
+ }
1295
+ }
1296
+ ```
1297
+
1298
+ `true`/`false` in the JSON are normalised to CMake `"ON"`/`"OFF"`; numbers are stringified; strings pass through.
1299
+
1300
+ ### Per-developer overlay — `wyvrn.local.json`
1301
+
1302
+ Drop a `wyvrn.local.json` next to `wyvrn.json` for per-machine overrides. `wyvrnpm init` scaffolds a `.gitignore` entry for new projects; existing projects get a one-line nudge on `wyvrnpm install` when the file is present but not ignored.
1303
+
1304
+ ```json
1305
+ {
1306
+ "conf": {
1307
+ "cmake": {
1308
+ "cache": {
1309
+ "CHROMA_ENABLE_TEST": "ON",
1310
+ "CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
1311
+ }
1312
+ }
1313
+ }
1314
+ }
1315
+ ```
1316
+
1317
+ Phase 1 accepts **only** the top-level key `conf` in this file — `dependencies` / `options` / anything else is a hard error, forecloseing the silent-inert footgun where a user writes `{ "dependencies": {...} }` expecting local dep overrides. Phase 2 will widen the allow-list.
1318
+
1319
+ ### Team-shared overlay — named-profile `conf` (phase 2, 2.7.0+)
1320
+
1321
+ When every developer on a team would end up with the same `wyvrn.local.json`, check that shared policy into the named profile instead. A saved profile (e.g. `%LOCALAPPDATA%\wyvrnpm\profiles\team-default.json`) may carry an optional top-level `conf` block alongside its `[settings]`:
1322
+
1323
+ ```json
1324
+ {
1325
+ "os": "Windows",
1326
+ "arch": "x86_64",
1327
+ "compiler": "msvc",
1328
+ "compiler.version": "193",
1329
+ "compiler.cppstd": "20",
1330
+ "compiler.runtime": "dynamic",
1331
+ "conf": {
1332
+ "cmake": {
1333
+ "cache": {
1334
+ "CHROMA_ENABLE_SANITIZERS": "OFF",
1335
+ "CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
1336
+ }
1337
+ }
1338
+ }
1339
+ }
1340
+ ```
1341
+
1342
+ Edit via the `configure profile set` convenience (repeatable, same `KEY=VAL` / bare-`KEY` / `KEY=` syntax as the other CLI sinks):
1343
+
1344
+ ```bash
1345
+ wyvrnpm configure profile set --name team-default --conf cmake.cache.CMAKE_EXPORT_COMPILE_COMMANDS=ON
1346
+ wyvrnpm configure profile set --name team-default --conf cmake.cache.LEGACY_FLAG= # clears it
1347
+ ```
1348
+
1349
+ Profile `conf` layers **between recipe and local**: a project's `wyvrn.local.json` (the individual override) still wins over team defaults, and CLI `--conf` still wins over everything. The distinction Conan makes between the `[conf]` block in a profile and the `-c` CLI flag maps directly onto this precedence.
1350
+
1351
+ Profile `conf` is **non-ABI**: adding or changing keys inside it does NOT shift `profileHash`, so switching a team policy between `ENABLE_SANITIZERS=ON` and `=OFF` does not invalidate already-downloaded artefacts. The invariant is locked in by `tests/profile-hash.test.js`.
1352
+
1353
+ Auto-detected profiles (when no on-disk profile file exists) never inject a `conf` layer — detection is about `[settings]` only.
1354
+
1355
+ ### CLI — ad-hoc, one invocation
1356
+
1357
+ ```bash
1358
+ # Single toggle
1359
+ wyvrnpm build --conf cmake.cache.CHROMA_ENABLE_TEST=ON
1360
+
1361
+ # Repeatable
1362
+ wyvrnpm build \
1363
+ --conf cmake.cache.CHROMA_ENABLE_TEST=ON \
1364
+ --conf cmake.cache.BUILD_DOCS=OFF
1365
+
1366
+ # Bare KEY is sugar for KEY=ON (CMake -D convention)
1367
+ wyvrnpm build --conf cmake.cache.CHROMA_ENABLE_TEST
1368
+
1369
+ # KEY= (empty RHS) clears a value inherited from a lower layer
1370
+ wyvrnpm build --conf cmake.cache.CHROMA_ENABLE_TEST=
1371
+
1372
+ # Also honoured on install and publish (publish records it as
1373
+ # `publishedConf` on the uploaded manifest — informational metadata
1374
+ # that `wyvrnpm show` surfaces).
1375
+ wyvrnpm install --conf cmake.cache.CHROMA_ENABLE_TEST=ON
1376
+ wyvrnpm publish --conf cmake.cache.BUILD_DOCS=OFF
1377
+ ```
1378
+
1379
+ `--conf` on `wyvrnpm build` passes through to `cmake --preset` as `-D` / `-U` overrides **for that invocation only** — it does not regenerate `CMakePresets.json`. Persistent per-machine overrides belong in `wyvrn.local.json`; `wyvrnpm install` regenerates the preset deterministically with the overlay applied.
1380
+
1381
+ ### Lockfile semantics
1382
+
1383
+ `wyvrn.lock` gains an informational `effectiveConf` block after each install:
1384
+
1385
+ ```json
1386
+ {
1387
+ "wyvrnVersion": 2,
1388
+ "profileHash": "a1b2c3d4e5f60718",
1389
+ "effectiveConf": {
1390
+ "cmake": { "cache": { "CHROMA_ENABLE_TEST": "ON" } }
1391
+ },
1392
+ "packages": { ... }
1393
+ }
1394
+ ```
1395
+
1396
+ This is diagnostic only — changing `conf` between installs shows up as a lockfile diff but never causes a relock or version change. Conf drift is a legitimate review signal, not an error.
1397
+
1398
+ ### Source-build boundary
1399
+
1400
+ When `--build=missing` or `--build=always` source-builds a dependency, **only that dep's own recipe `conf`** flows into its CMake invocation. The root project's merged conf (including `wyvrn.local.json`, named-profile `conf`, and CLI `--conf`) never reaches a dep's source-build. This preserves cross-consumer reproducibility — two developers source-building the same `(name, version, profileHash)` produce the same binary regardless of their local overlays or team profile, which is load-bearing for `--upload-built`.
1401
+
1402
+ ### Why this isn't `build.configure`
1403
+
1404
+ Existing authors using `build.configure: ["-DFOO=ON", "-DBAR=OFF"]` keep working unchanged. But `build.configure` has two weaknesses `conf` fixes:
1405
+
1406
+ 1. **All-or-nothing.** A consumer can't disable one flag without overriding the whole array.
1407
+ 2. **Opaque strings.** `"-DFOO=ON"` is parsed at configure time, not at manifest load.
1408
+
1409
+ Recipe authors writing new packages should prefer `conf.cmake.cache.FOO=ON` for cache variables and keep `build.configure` for non-cache args like `-G`, `-T`, or `-A`. Setting the same KEY in both `build.configure` (`-DKEY=...`) AND `conf.cmake.cache.KEY` is a recipe-normalise error — pick one route.
1410
+
1411
+ ---
1412
+
959
1413
  ## Compatibility Rules
960
1414
 
961
1415
  Publishers declare an optional `compatibility` block in a package's `wyvrn.json`. It tells wyvrnpm which profile fields must match the consumer's profile exactly, and which may legitimately differ without breaking ABI. When no exact `profileHash` match exists on the registry, `install` evaluates this block against every published build for the requested `(name, version)` and picks the best compatible candidate.
@@ -1010,16 +1464,6 @@ When multiple published builds satisfy the consumer's profile, the winner is pic
1010
1464
 
1011
1465
  The chosen candidate is logged along with a per-field summary (e.g. `compiler.cppstd 20→23 (lte), compiler.runtime=dynamic`).
1012
1466
 
1013
- ### Legacy loose fallback
1014
-
1015
- The pre-2.1 "same OS + arch, any compiler" fallback is removed from the default resolution path. For one release cycle it remains reachable via an env var:
1016
-
1017
- ```bash
1018
- WYVRNPM_ALLOW_LOOSE_COMPAT=1 wyvrnpm install
1019
- ```
1020
-
1021
- This prints a deprecation warning every time it fires and will be removed in the next minor release. Prefer adding a real `compatibility` block to affected packages, or rebuilding with `--build=missing`.
1022
-
1023
1467
  ---
1024
1468
 
1025
1469
  ## Source Builds
@@ -1081,6 +1525,8 @@ Source builds are cached machine-wide, keyed by `(name, version, profileHash)`:
1081
1525
 
1082
1526
  Re-running `install --build=missing` with the same inputs short-circuits to the cached artefact. The cache root is deliberately shallow (`bc/`, not `build-cache/`) to keep nested CMake paths under Windows' 260-char limit.
1083
1527
 
1528
+ `--build=always` (2.5.0+) ignores every registry-binary path — exact-match, compat-match, and any already-extracted `wyvrn_internal/{name}/` — and forces a source-build for every dep. Useful for debugging whether a registry binary differs from what the current source tree would produce, or for reproducibility verification. The build-cache short-circuit still applies: re-running `--build=always` with unchanged inputs completes in seconds.
1529
+
1084
1530
  Clone behaviour is tri-tier:
1085
1531
 
1086
1532
  1. **Shallow-SHA fetch** (`git fetch origin <sha> --depth=1`) — fastest; works when the git server allows it.
@@ -1130,7 +1576,7 @@ wyvrnpm install --build=missing --upload-built --upload-source team-s3
1130
1576
 
1131
1577
  # Upload auth is resolved in order: CLI flag → named-source config → first configured publish source.
1132
1578
  wyvrnpm install --build=missing --upload-built --aws-profile my-sso
1133
- wyvrnpm install --build=missing --upload-built --token mytoken
1579
+ wyvrnpm install --build=missing --upload-built --token-env WYVRN_HTTP_TOKEN
1134
1580
  ```
1135
1581
 
1136
1582
  Semantics: