Semantic versioning toolbox for DeepSeek Harness (dsh) — parse, compare, range-check, increment, diff, sort, find a range's lowest version and check whether two ranges overlap. Zero runtime dependencies, pure logic, fully deterministic.
Agents are bad at version arithmetic. "Is 2.1.0-rc.1 newer than 2.1.0-beta.11?", "does 1.2.4-beta.1 satisfy ^1.2.3?" and "what does 1.2.4-beta.0 + prerelease become?" are exactly the questions LLMs get wrong — prerelease ordering, tuple-lock, caret/tilde upper bounds and prerelease bump rules are easy to misremember. This plugin turns those questions into deterministic tool calls.
| Tool | Description |
|---|---|
semver_parse |
Parse a semver 2.0.0 string into major / minor / patch / prerelease / build, with a precise rejection reason for malformed input (1.2, 1.2.3.4, leading zeros, 1.2.3-01, …) |
semver_compare |
Compare two versions → lt / eq / gt plus a human verdict (2.1.0 > 2.0.3). Build metadata is ignored for precedence (1.0.0+build.1 == 1.0.0) |
semver_satisfies |
Check a version against an npm-style range, with the matched range normalized in the explanation |
semver_inc |
Increment a version following npm semver.inc rules: major / minor / patch / premajor / preminor / prepatch / prerelease, with an optional prerelease identifier |
semver_diff |
Release-type difference between two versions (npm semver.diff): major / minor / patch / premajor / preminor / prepatch / prerelease |
semver_sort |
Sort a list of versions by precedence, ascending or descending, with build metadata as tie-break (npm semver.sort / rsort) |
semver_min_version |
Lowest version a range accepts (npm semver.minVersion): ~1.2.3 → 1.2.3, >1.2 → 1.3.0, <=1.2.3 → 0.0.0; unsatisfiable combinations report satisfiable: false instead of a plausible-looking bound |
semver_intersects |
Whether two ranges can be satisfied by one version (npm semver.intersects) — plus a witness version that satisfies both when they overlap |
Two questions that come up whenever an agent has to negotiate versions:
semver_min_version("^1.2.3") → 1.2.3
semver_min_version(">1.2.3") → 1.2.4 (> bound with no prerelease bumps patch)
semver_min_version(">1.2") → 1.3.0 (partial versions follow npm's x-range rule)
semver_min_version("~1.2.3") → 1.2.3
semver_min_version("<=1.2.3") → 0.0.0 (no lower bound → the floor)
semver_min_version(">2.0.0 <2.0.1") → satisfiable: false (never a guessed bound)
semver_intersects("^1.2.3", "^1.3.0") → true, witness 1.3.0
semver_intersects("^1.0.0", "^2.0.0") → false
semver_intersects("^1.0.0 || ^2.0.0", "^2.5.0") → true
satisfiable: falseis a result, not an error. Unsatisfiable combinations (^0.1.4 ~3.0.2,>=1.0.0 <1.0.0,>2.0.0 <2.0.1) report that no version fits, rather than returning a boundary that looks reasonable but matches nothing.- The witness is verified, never guessed. When two ranges overlap,
semver_intersectsprobes the ranges' boundary versions and checks each candidate against both ranges before reporting it — so a reported witness always satisfies both, though it is not claimed to be the lowest one. - npm parity is enforced against npm itself.
npm semverrenders~1.2.3as>=1.2.3 <1.3.0; a hand-written expectation would have missed that1.2.0used to be accepted. See Testing below.
- Plain bumps:
1.2.3+major→2.0.0, +minor→1.3.0, +patch→1.2.4. - Pre bumps with identifier:
1.2.3+preminor(rc) →1.3.0-rc.0; without identifier →1.3.0-0. - Prerelease counter:
1.2.4-beta.0+prerelease→1.2.4-beta.1;1.2.4-beta+prerelease→1.2.4-beta.0; switching identifier restarts the counter (prereleasewithrc→1.2.4-rc.0). - npm quirks preserved: a pre-major/pre-minor/pre-patch version releases without incrementing (
1.0.0-5+major→1.0.0);1.2.0-5+prerelease→1.2.0-6(no patch bump); build metadata is dropped; dotted identifiers are allowed (premajorwithbeta.1→2.0.0-beta.1.0); identifiers with leading zeros are rejected.
1.2.3 → 2.0.0 = major; 1.2.3 → 2.0.0-beta.1 = premajor; 1.2.4-beta.1 → 1.2.4-beta.2 = prerelease. npm special cases preserved: 1.2.3 vs 1.2.3-beta.1 → patch, 1.0.0-1 vs 1.0.0 → major. Equal precedence (build metadata ignored) → equal: true with no difference key.
- Exact:
1.2.3,=1.2.3 - Comparisons:
>1.2.0,>=1.2.0,<2.0.0,<=2.0.0— partial versions follow npm's x-range rewrite (>1.2→>=1.3.0,<=1.2→<1.3.0,<1.2→<1.2.0,>=1.2→>=1.2.0; npm compiles>*/<*to a range nothing satisfies) - Caret:
^1.2.3→>=1.2.3 <2.0.0, with the 0.x rules (^0.2.3→>=0.2.3 <0.3.0,^0.0.3→>=0.0.3 <0.0.4) - Tilde:
~1.2.3→>=1.2.3 <1.3.0,~1.2→>=1.2.0 <1.3.0,~1→>=1.0.0 <2.0.0(fixed in v0.3.0 — the patch component used to be dropped from the lower bound, so1.2.0wrongly matched~1.2.3) - Wildcards:
1.2.x,1.x,*(bare partials1.2/1behave the same) - Hyphen ranges:
1.2.3 - 2.3.4(inclusive endpoints; partial upper endpoints like1.2.3 - 2.3→<2.4.0) - AND / OR:
>=1.2.0 <2.0.0,^1.2.3 || >=3.0.0
- A plain range never matches prerelease versions:
1.2.4-beta.1does not satisfy^1.2.3. - A range with a prerelease lower bound only matches prereleases of the same
[major, minor, patch]tuple:1.2.3-beta.5satisfies^1.2.3-beta.2, but1.2.4-beta.1does not (releases like1.2.3still match). - Set
includePrerelease: true(tool parameter or plugin config) to relax both rules.
# into the web profile (dsh plugin has no default profile — always pass --profile)
dsh plugin --profile web add github:TYEclipse/dsh-semverVerify the layer mounted:
dsh --profile web --dump-config | grep '== dsh-semver'semver_parse("2.1.0-rc.1+build.42")
→ major 2, minor 1, patch 0, prerelease [rc, 1], build [build, 42]
semver_compare("2.1.0", "2.0.3")
→ "2.1.0 > 2.0.3" (gt)
semver_satisfies("1.2.4-beta.1", "^1.2.3")
→ false — prerelease does not match a plain range
semver_inc("1.2.4-beta.0", "prerelease", "beta")
→ 1.2.4-beta.1
semver_inc("1.2.3", "preminor", "rc")
→ 1.3.0-rc.0
semver_diff("1.2.3", "2.0.0-beta.1")
→ premajor
semver_sort(["1.2.3", "1.2.10", "1.0.0"], "desc")
→ ["1.2.10", "1.2.3", "1.0.0"]
semver_min_version(">=1.2.0 <2.0.0")
→ 1.2.0
semver_intersects("^1.2.3", ">=1.3.0 <1.5.0")
→ true — e.g. 1.3.0 satisfies both
All eight tools are pure and offline: no network, no filesystem access, no shell, no dynamic evaluation. Inputs are validated with strict regexes and numeric checks; malformed versions, ranges and identifiers return explicit reasons instead of throwing.
pnpm test # vitest — 132 testsTool semantics are anchored to npm's own semver package as an independent oracle, never to hand-written expectations:
test/oracle/range-algebra-oracle.mjs # regenerates the anchors from npm semver
test/anchors/range-algebra.json # 54 ranges · 400 range pairs · 864 matcher cases
With npm i semver@7 available, node test/oracle/range-algebra-oracle.mjs rewrites the anchors; the test suite sweeps the plugin's matcher, semver_min_version and semver_intersects against every one of them. The ~1.2.3 lower-bound bug fixed in v0.3.0 was found exactly this way — its v0.2.x expectation had been chosen by hand and sat outside the wrong bound.
pnpm install # dev dependencies only (esbuild allowBuilds is pre-configured)
pnpm build # tsc → dist/ (committed; git installs do not run build scripts)
pnpm test # vitest — 132 tests; anchors generated by npm semver 7.8.5 (independent oracle)
pnpm lint # oxlint (src + test only)MIT
dsh-semver 是 DeepSeek Harness 的语义化版本号工具箱:解析(semver_parse)、比较(semver_compare,遵循 semver 2.0.0 优先级规则,build 元数据不影响比较)、范围匹配(semver_satisfies,支持 ^/~/>=/<=/通配符/连字符区间/||,含 npm 的 prerelease tuple-lock 语义),以及 v0.2.0 新增的版本号递增(semver_inc,npm semver.inc 全规则:major/minor/patch/premajor/preminor/prepatch/prerelease + 可选预发布标识符)、版本差异判定(semver_diff,npm semver.diff 语义,含 prerelease 特殊分支)与版本列表排序(semver_sort,升/降序,build 元数据作次序裁决)。零运行时依赖、纯逻辑、确定性输出——LLM 经常算错的版本号问题(如 1.0.0-beta.11 > 1.0.0-beta.2、1.2.4-beta.0 递增后是 beta.1 而非 beta.01、1.2.4-beta.1 不满足 ^1.2.3)交给工具一次性算对。