wyvrnpm 2.4.1 → 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.4.0** — Highlights since 2.1.0: `wyvrnpm add <name>` for dependency authoring, `HeaderOnlyLib` packages that survive cross-arch `find_package()`, `WYVRN_ARCH_SUFFIX` + the `WYVRN_APPLY_ARCH_SUFFIX(<target>)` helper so x64/x86 artefacts coexist on disk, `wyvrnpm build --generator / -G` to swap CMake generators from the CLI, package `options`, version ranges (`^`, `~`, explicit comparators), `--format=json` output, and `--upload-built` to warm the registry with source-built artefacts.
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
 
@@ -97,6 +97,37 @@ wyvrnpm configure profile set-default release
97
97
 
98
98
  Once a default profile is saved, `install` and `publish` use it automatically — no flags required.
99
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
+
100
131
  ---
101
132
 
102
133
  ## Commands
@@ -161,7 +192,7 @@ Resolves the full dependency graph and downloads all packages.
161
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.
162
193
  4. **v1 legacy path** — only when no v2 entry exists for the package at all.
163
194
 
164
- 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.
165
196
 
166
197
  ```bash
167
198
  # Use configured sources and the default build profile
@@ -180,6 +211,13 @@ wyvrnpm install \
180
211
 
181
212
  # Build any missing binary from source
182
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
183
221
  ```
184
222
 
185
223
  **Options**
@@ -193,6 +231,8 @@ wyvrnpm install --build=missing
193
231
  | `--timeout` | `300` | HTTP request timeout in seconds. |
194
232
  | `--manifest` | `./wyvrn.json` | Path to the manifest file. |
195
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). |
196
236
 
197
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.
198
238
 
@@ -215,6 +255,16 @@ After download, `install` also generates:
215
255
 
216
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.
217
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
+
218
268
  ```cmake
219
269
  project(String CXX)
220
270
  add_library(String STATIC ...)
@@ -303,7 +353,9 @@ wyvrnpm publish --source default
303
353
  # Explicit destination URL
304
354
  wyvrnpm publish --source s3://my-bucket/packages --aws-profile my-sso
305
355
 
306
- # 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
307
359
  wyvrnpm publish --source https://pkg.corp.com --token mytoken
308
360
 
309
361
  # Local directory or SMB share
@@ -325,6 +377,7 @@ wyvrnpm publish --force
325
377
  | `--profile` / `-p` | *(config / "default")* | Named build profile to publish under. |
326
378
  | `--aws-profile` | *(config)* | AWS SSO profile for S3 authentication. |
327
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. |
328
381
  | `--path` | `.` | Directory to zip and publish. |
329
382
  | `--manifest` | `./wyvrn.json` | Path to the manifest file. |
330
383
  | `--force` / `-f` | `false` | Overwrite an existing published version (same version + profile hash). |
@@ -370,6 +423,130 @@ wyvrnpm clean --build-cache
370
423
  | Option | Default | Description |
371
424
  |---|---|---|
372
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`.
373
550
 
374
551
  ---
375
552
 
@@ -523,7 +700,8 @@ wyvrnpm configure add-source \
523
700
  | `--name` | Yes | Unique name for this source. |
524
701
  | `--url` | Yes | Destination URL, URI, or file path. |
525
702
  | `--profile` | No | AWS SSO profile (S3 sources). |
526
- | `--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. |
527
705
 
528
706
  #### `wyvrnpm configure remove-source`
529
707
 
@@ -925,6 +1103,56 @@ Example:
925
1103
  }
926
1104
  ```
927
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
+
928
1156
  ### Package options (`options`)
929
1157
 
930
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.
@@ -1019,6 +1247,169 @@ On Windows with MSVC and a non-Visual-Studio generator (Ninja, Make), `vcvarsall
1019
1247
 
1020
1248
  ---
1021
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
+
1022
1413
  ## Compatibility Rules
1023
1414
 
1024
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.
@@ -1073,16 +1464,6 @@ When multiple published builds satisfy the consumer's profile, the winner is pic
1073
1464
 
1074
1465
  The chosen candidate is logged along with a per-field summary (e.g. `compiler.cppstd 20→23 (lte), compiler.runtime=dynamic`).
1075
1466
 
1076
- ### Legacy loose fallback
1077
-
1078
- 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:
1079
-
1080
- ```bash
1081
- WYVRNPM_ALLOW_LOOSE_COMPAT=1 wyvrnpm install
1082
- ```
1083
-
1084
- 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`.
1085
-
1086
1467
  ---
1087
1468
 
1088
1469
  ## Source Builds
@@ -1144,6 +1525,8 @@ Source builds are cached machine-wide, keyed by `(name, version, profileHash)`:
1144
1525
 
1145
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.
1146
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
+
1147
1530
  Clone behaviour is tri-tier:
1148
1531
 
1149
1532
  1. **Shallow-SHA fetch** (`git fetch origin <sha> --depth=1`) — fastest; works when the git server allows it.
@@ -1193,7 +1576,7 @@ wyvrnpm install --build=missing --upload-built --upload-source team-s3
1193
1576
 
1194
1577
  # Upload auth is resolved in order: CLI flag → named-source config → first configured publish source.
1195
1578
  wyvrnpm install --build=missing --upload-built --aws-profile my-sso
1196
- wyvrnpm install --build=missing --upload-built --token mytoken
1579
+ wyvrnpm install --build=missing --upload-built --token-env WYVRN_HTTP_TOKEN
1197
1580
  ```
1198
1581
 
1199
1582
  Semantics: