Skip to content

feat(cli): [experimental] flags + caret-versions sketch#520

Closed
Kamirus wants to merge 2 commits intomainfrom
feat/experimental-flags
Closed

feat(cli): [experimental] flags + caret-versions sketch#520
Kamirus wants to merge 2 commits intomainfrom
feat/experimental-flags

Conversation

@Kamirus
Copy link
Copy Markdown
Collaborator

@Kamirus Kamirus commented Apr 24, 2026

Why

We want a way to ship and iterate on new CLI features without breaking changes. Today any new resolution / config behavior risks breaking other users' projects (and the earlier feat/version-ranges attempt confirmed this — real range syntax is breaking for older CLIs and the registry canister rejects ranges in published manifests outright). Opt-in flags let us release new behavior continuously: users who want it turn it on; everyone else is unaffected.

What this adds

  1. [experimental] section in mops.toml — generic opt-in for unstable CLI behavior:

    [experimental]
    flags = ["caret-versions"]
  2. caret-versions flag — first flag, as a concrete shape for the mechanism. Bare versions in the root project's deps resolve as Cargo-style caret ranges:

    • core = "1.2.3" → highest published 1.x.y (>=1.2.3, <2.0.0)
    • core = "0.2.3" → highest published 0.2.x (>=0.2.3, <0.3.0)

    mops update and mops outdated honor the same bound. Resolved version is pinned in mops.lock. Aliased deps (core@1 = "...") and transitive deps unaffected. mops.toml syntax does not change.

  3. Lockfile — adds optional experimentalHash field to mops.lock v3, so toggling flags invalidates the fast path. Absent for projects not opting in, so existing lockfiles see zero churn. Older CLIs that don't recognize the flag still read the lockfile and use the resolved versions transparently.

Test plan

  • npm run check, npm run lint clean
  • Unit test for isExperimentEnabled
  • 4 integration tests in cli.test.ts against the live registry: install reproducibility, caret-bounded mops update, flag-toggle lock invalidation, no-flag major-cross control
  • Smoke on a real project with core = "1.0.0" + base = "0.14.5":
    • With flag: core stays 1.0.0, base upgrades to 0.14.14 (capped at 0.14.x); lockfile gets experimentalHash
    • Without flag: mops outdated suggests core 1.0.0 -> 2.5.0 and base 0.14.5 -> 0.16.0 (both cross the caret bound)

Sketch only — both the [experimental] section and the compatible-resolution
flag are explicitly unstable. Behavior may change or be removed.

- Recognize [experimental] flags = [...] in mops.toml; older CLIs ignore it.
- Hash flags into a separate optional experimentalHash in mops.lock so the
  fast-path stays untouched for projects that don't opt in, and toggling a
  flag invalidates the lockfile.
- compatible-resolution: bare versions in the root project's [dependencies]
  / [dev-dependencies] resolve as Cargo-style caret ranges via the existing
  getHighestSemverBatch backend call. Aliased / github / local deps and
  transitives are unchanged.

Made-with: Cursor
@Kamirus Kamirus changed the title feat(cli): [experimental] flags + compatible-resolution sketch feat(cli): [experimental] flags + caret-versions sketch Apr 24, 2026
- mops update / mops outdated honor the caret bound when the flag is on
- prefetch upgraded versions before collectDeps reads transitive mops.toml
- use semver.major() instead of manual parse
- experimentalHash invalidates the lock fast path on flag toggle
- 4 integration tests: install reproducibility, caret-bounded update,
  flag-toggle lock invalidation, no-flag major-cross control
- drop incorrect "older CLIs fail loudly" claim from docs/changelog

Also: replace deprecated @noble/hashes/sha256 with sha2.
Made-with: Cursor
@Kamirus
Copy link
Copy Markdown
Collaborator Author

Kamirus commented Apr 24, 2026

Closing — the [experimental] mechanism is over-engineered for a single use case, and a per-project toml opt-in would force a migration on every Caffeine.ai user's mops.toml just to get caret resolution.

Pivoting to a smaller change in a new PR: make mops update (and mops outdated) caret-bound by default, with --major as escape hatch. Brings mops in line with cargo update / npm update defaults — no toml changes, no migration, no new mechanism.

@Kamirus Kamirus closed this Apr 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant