feat(capabilities): add isV013OrLater convenience predicate

Surfaces a v0.12 → v0.13 boundary check that doesn't proxy through any
specific feature flag. Used by WS-8 (redaction default-state hint copy,
"v0.13 features active" Settings badge in iOS WS-9) where the call site
isn't actually about a specific feature — it's about whether the host is
on the v0.13 line.

Equivalent to any individual v0.13 flag (e.g. `hasGoals`); both resolve
to the same `>= 0.13.0` threshold. Convenience exists to keep call sites
honest: `caps.isV013OrLater` reads better than `caps.hasGoals` when the
context isn't goal-related.

Tests: 4 new fixtures covering v0.13 host (true), v0.12 host (false),
empty/undetected (false), and v0.14 host (true). 19 total tests in the
suite, all passing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Alan Wizemann
2026-05-09 18:08:14 +02:00
parent 52c802676f
commit 963d0e1a5c
2 changed files with 32 additions and 0 deletions
@@ -210,6 +210,16 @@ public struct HermesCapabilities: Sendable, Equatable {
/// PluginsView surfaces it as a documented hook in plugin metadata. /// PluginsView surfaces it as a documented hook in plugin metadata.
public var hasTransformLLMOutputHook: Bool { atLeastSemver(0, 13, 0) } public var hasTransformLLMOutputHook: Bool { atLeastSemver(0, 13, 0) }
// MARK: Convenience predicates
/// Whether the connected host is on the v0.13 line or newer. Convenience
/// for UI copy that needs to switch on the v0.12 v0.13 boundary without
/// proxying through a feature-specific flag (e.g. "v0.13 features active"
/// badges, redaction default-state hints). Equivalent to any individual
/// v0.13 flag; prefer this when the call site isn't actually about a
/// specific feature.
public var isV013OrLater: Bool { atLeastSemver(0, 13, 0) }
private func atLeastSemver(_ major: Int, _ minor: Int, _ patch: Int) -> Bool { private func atLeastSemver(_ major: Int, _ minor: Int, _ patch: Int) -> Bool {
guard let s = semver else { return false } guard let s = semver else { return false }
return s >= SemVer(major: major, minor: minor, patch: patch) return s >= SemVer(major: major, minor: minor, patch: patch)
@@ -202,4 +202,26 @@ import Foundation
#expect(caps.hasKanbanDiagnostics) #expect(caps.hasKanbanDiagnostics)
#expect(caps.hasGoogleChatPlatform) #expect(caps.hasGoogleChatPlatform)
} }
// MARK: - isV013OrLater convenience predicate
@Test func isV013OrLater_v013HostTrue() {
let caps = HermesCapabilities.parseLine("Hermes Agent v0.13.0 (2026.5.7)")
#expect(caps.isV013OrLater)
}
@Test func isV013OrLater_v012HostFalse() {
let caps = HermesCapabilities.parseLine("Hermes Agent v0.12.0 (2026.4.30)")
#expect(!caps.isV013OrLater)
}
@Test func isV013OrLater_emptyFalse() {
let caps = HermesCapabilities.empty
#expect(!caps.isV013OrLater)
}
@Test func isV013OrLater_v014HostTrue() {
let caps = HermesCapabilities.parseLine("Hermes Agent v0.14.0 (2026.7.1)")
#expect(caps.isV013OrLater)
}
} }