Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ let package = Package(
var products: [Product] = [
.library(name: "CodexBarCore", targets: ["CodexBarCore"]),
.executable(name: "CodexBarCLI", targets: ["CodexBarCLI"]),
// Offline adaptive-refresh replay harness.
.library(name: "AdaptiveReplayKit", targets: ["AdaptiveReplayKit"]),
.executable(name: "AdaptiveReplayCLI", targets: ["AdaptiveReplayCLI"]),
]

#if os(macOS)
Expand Down Expand Up @@ -82,6 +85,23 @@ let package = Package(
.enableUpcomingFeature("StrictConcurrency"),
],
linkerSettings: sqlite3LinkerSettings),
// Offline adaptive-refresh replay harness: pure Foundation,
// no CodexBar/CodexBarCore dependency, so it builds anywhere CodexBarCore does.
.target(
name: "AdaptiveReplayKit",
dependencies: [],
path: "Sources/AdaptiveReplayKit",
exclude: ["README.md"],
swiftSettings: [
.enableUpcomingFeature("StrictConcurrency"),
]),
.executableTarget(
name: "AdaptiveReplayCLI",
dependencies: ["AdaptiveReplayKit"],
path: "Sources/AdaptiveReplayCLI",
swiftSettings: [
.enableUpcomingFeature("StrictConcurrency"),
]),
.testTarget(
name: "CodexBarLinuxTests",
dependencies: [
Expand Down Expand Up @@ -112,6 +132,8 @@ let package = Package(
.product(name: "KeyboardShortcuts", package: "KeyboardShortcuts"),
.product(name: "Vortex", package: "Vortex"),
"CodexBarCore",
// Opt-in adaptive-refresh trace recording.
"AdaptiveReplayKit",
],
path: "Sources/CodexBar",
resources: [
Expand Down Expand Up @@ -140,7 +162,7 @@ let package = Package(

targets.append(.testTarget(
name: "CodexBarTests",
dependencies: ["CodexBar", "CodexBarCore", "CodexBarCLI", "CodexBarWidget"],
dependencies: ["CodexBar", "CodexBarCore", "CodexBarCLI", "CodexBarWidget", "AdaptiveReplayKit"],
path: "Tests",
resources: [
.copy("CodexBarTests/Fixtures"),
Expand Down
311 changes: 311 additions & 0 deletions Sources/AdaptiveReplayCLI/main.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,311 @@
import AdaptiveReplayKit
import Foundation

/// Thin CLI shell over `AdaptiveReplayKit`: parses a trace path and a policy name, runs the
/// replay, and prints the resulting `ReplayMetrics`. All parsing/replay/metrics logic lives in
/// the library — this file only routes arguments to it and formats the result.
enum AdaptiveReplayCLI {
static func main() {
let arguments = CLIArguments.parse(Array(CommandLine.arguments.dropFirst()))

switch arguments {
case let .help(exitCode):
print(Self.helpText)
exit(exitCode)
case let .invalid(message):
FileHandle.standardError.write(Data("error: \(message)\n\n\(Self.helpText)\n".utf8))
exit(EXIT_FAILURE)
case let .run(tracePath, policyNames, jsonOutput, gapGraceSeconds):
Self.run(
tracePath: tracePath,
policyNames: policyNames,
jsonOutput: jsonOutput,
gapGraceSeconds: gapGraceSeconds)
}
}

private static func run(
tracePath: String,
policyNames: [String],
jsonOutput: Bool,
gapGraceSeconds: TimeInterval?)
{
let records: [AdaptiveRefreshTraceRecord]
do {
records = try AdaptiveRefreshTraceParser.parse(contentsOf: URL(fileURLWithPath: tracePath))
} catch {
FileHandle.standardError.write(Data("error: failed to parse trace: \(error)\n".utf8))
exit(EXIT_FAILURE)
}

let policies: [any ReplayPolicy]
do {
policies = try policyNames.map { try Self.policy(named: $0) }
} catch {
FileHandle.standardError.write(Data("error: \(error)\n".utf8))
exit(EXIT_FAILURE)
}

let results = policies.map { policy in
gapGraceSeconds.map {
ReplayEngine.runSegmented(trace: records, policy: policy, graceSeconds: $0)
} ?? ReplayEngine.run(trace: records, policy: policy)
}
let activityCoverage = ActivityCoverageStats.compute(from: records)
let recordedScheduleAudit = RecordedScheduleAuditor.audit(records)

if jsonOutput {
print(Self.renderJSON(
results,
activityCoverage: activityCoverage,
recordedScheduleAudit: recordedScheduleAudit,
gapGraceSeconds: gapGraceSeconds))
} else {
print(Self.renderTable(results))
print(Self.renderActivityCoverage(activityCoverage))
print(Self.renderRecordedScheduleAudit(recordedScheduleAudit))
if let gapGraceSeconds, let first = results.first {
print(String(
format: "segmentation: %d segments, %.2fh excluded (legacy heuristic, %.0fs grace)",
first.segmentCount,
first.excludedGapSeconds / 3600,
gapGraceSeconds))
} else {
print("segmentation: disabled (raw wall clock)")
}
}
}

private static func renderRecordedScheduleAudit(_ audit: RecordedScheduleAudit) -> String {
"recorded schedule: \(audit.recordedAdvanceCount) advances, "
+ "\(audit.acceptedEvaluationCount)/\(audit.evaluatedCount) evaluations accepted, "
+ "payload=\(audit.payloadMismatchCount) decision=\(audit.decisionMismatchCount) "
+ "menu-link=\(audit.menuLinkMismatchCount) mismatches, "
+ "ambiguous=\(audit.ambiguousComparisonCount)"
}

/// Shadow-mode telemetry line (never fed into any production policy): how much of the trace
/// carries the `CodingActivityProbe` signal, and how much of that looked like active coding.
private static func renderActivityCoverage(_ stats: ActivityCoverageStats) -> String {
guard stats.decisionCount > 0 else {
return "activity telemetry: no decision events in trace"
}
let sampledSummary = String(
format: "%d/%d decisions sampled (%.0f%%)",
stats.sampledCount,
stats.decisionCount,
stats.sampledFraction * 100)
let activeSummary = String(
format: "%d/%d active coding at decision time (%.0f%%)",
stats.activeCount,
stats.sampledCount,
stats.activeFraction * 100)
return "activity telemetry: \(sampledSummary), \(activeSummary)"
}

private static func policy(named name: String) throws -> any ReplayPolicy {
if name == "adaptive" {
return MirroredAdaptivePolicy()
}
if name == "adaptive-activity" {
return CodingActivityAdaptivePolicy()
}
if name == "manual" {
return ManualPolicy()
}
if name.hasPrefix("fixed-"), name.hasSuffix("m"),
let minutes = Int(name.dropFirst("fixed-".count).dropLast())
{
return FixedIntervalPolicy(minutes: minutes)
}
throw CLIError.unknownPolicy(name)
}

private static func renderTable(_ results: [ReplayMetrics]) -> String {
var lines: [String] = []
let header = [
"policy", "refreshes", "per24h", "sim advances", "active >5m", "staleness p50",
"staleness p95", "constrained ok",
]
lines.append(header.joined(separator: "\t"))
for metrics in results {
let staleness = metrics.stalenessAtMenuOpen
lines.append([
metrics.policyName,
String(metrics.totalRefreshCount),
String(format: "%.2f", metrics.refreshCountPer24h),
String(metrics.interactionAdvanceCount),
"\(metrics.codingActiveDelayViolationCount)/\(metrics.codingActiveDecisionCount)",
staleness.map { String(format: "%.0fs", $0.median) } ?? "n/a",
staleness.map { String(format: "%.0fs", $0.p95) } ?? "n/a",
metrics.constrainedCompliance
.isCompliant ? "yes" : "NO (\(metrics.constrainedCompliance.violationCount))",
].joined(separator: "\t"))
}
return lines.joined(separator: "\n")
}

private static func renderJSON(
_ results: [ReplayMetrics],
activityCoverage: ActivityCoverageStats,
recordedScheduleAudit: RecordedScheduleAudit,
gapGraceSeconds: TimeInterval?) -> String
{
let policies = results.map { metrics -> [String: Any] in
var dict: [String: Any] = [
"policy": metrics.policyName,
"simulatedSpanSeconds": metrics.simulatedSpanSeconds,
"totalRefreshCount": metrics.totalRefreshCount,
"refreshCountPer24h": metrics.refreshCountPer24h,
"interactionAdvanceCount": metrics.interactionAdvanceCount,
"codingActiveDecisionCount": metrics.codingActiveDecisionCount,
"codingActiveDelayViolationCount": metrics.codingActiveDelayViolationCount,
"segmentCount": metrics.segmentCount,
"excludedGapSeconds": metrics.excludedGapSeconds,
"boundaryCensoredMenuOpenCount": metrics.boundaryCensoredMenuOpenCount,
"constrainedDecisionCount": metrics.constrainedCompliance.constrainedDecisionCount,
"constrainedViolationCount": metrics.constrainedCompliance.violationCount,
"constrainedCompliant": metrics.constrainedCompliance.isCompliant,
]
if let staleness = metrics.stalenessAtMenuOpen {
dict["stalenessMeanSeconds"] = staleness.mean
dict["stalenessMedianSeconds"] = staleness.median
dict["stalenessP95Seconds"] = staleness.p95
dict["stalenessSampleCount"] = staleness.sampleCount
}
return dict
}
let segmentation: [String: Any] = [
"mode": gapGraceSeconds == nil ? "rawWallClock" : "legacyGapHeuristic",
"gapGraceSeconds": gapGraceSeconds.map { $0 as Any } ?? NSNull(),
]
let payload: [String: Any] = [
"policies": policies,
"activityCoverage": [
"decisionCount": activityCoverage.decisionCount,
"sampledCount": activityCoverage.sampledCount,
"activeCount": activityCoverage.activeCount,
"sampledFraction": activityCoverage.sampledFraction,
"activeFraction": activityCoverage.activeFraction,
],
"recordedScheduleAudit": [
"recordedAdvanceCount": recordedScheduleAudit.recordedAdvanceCount,
"evaluatedCount": recordedScheduleAudit.evaluatedCount,
"acceptedEvaluationCount": recordedScheduleAudit.acceptedEvaluationCount,
"rejectedEvaluationCount": recordedScheduleAudit.rejectedEvaluationCount,
"payloadMismatchCount": recordedScheduleAudit.payloadMismatchCount,
"decisionMismatchCount": recordedScheduleAudit.decisionMismatchCount,
"menuLinkMismatchCount": recordedScheduleAudit.menuLinkMismatchCount,
"ambiguousComparisonCount": recordedScheduleAudit.ambiguousComparisonCount,
"isValid": recordedScheduleAudit.isValid,
],
"segmentation": segmentation,
]
guard let data = try? JSONSerialization.data(
withJSONObject: payload,
options: [.prettyPrinted, .sortedKeys]),
let text = String(data: data, encoding: .utf8)
else {
return "{}"
}
return text
}

private static let helpText = """
Usage: adaptive-replay-cli <trace.jsonl> [--policy <name>]... [--gap-grace <seconds>] [--raw-wall-clock] [--json]

Replays a JSONL adaptive-refresh trace against one or more refresh-timing policies and prints
per-policy metrics over automatically segmented observed time. Simulated advances are
counterfactual policy events; recorded live schedule evaluations are audited separately.

Policies:
adaptive The current adaptive table, mirrored from AdaptiveRefreshPolicy. Advances on
menu-open interactions, same as UsageStore.noteMenuOpened(at:).
adaptive-activity Experimental adaptive table capped at 5m after observed coding activity.
fixed-2m Fixed 2 minute cadence. Unaffected by menu-open interactions.
fixed-5m Fixed 5 minute cadence.
fixed-15m Fixed 15 minute cadence.
fixed-30m Fixed 30 minute cadence.
manual Never refreshes (degenerate floor).

Defaults to comparing all seven policies when --policy is omitted.

Options:
--policy <name> Restrict to one policy; repeat to compare a specific subset.
--gap-grace <s> Split legacy gaps this many seconds after the last timer deadline (default 300).
--raw-wall-clock Disable gap segmentation; useful only for auditing the old behavior.
--json Print a machine-readable report including replay, activity, and audit data.
-h, --help Print this help text.
"""
}

private enum CLIError: Error, CustomStringConvertible {
case unknownPolicy(String)

var description: String {
switch self {
case let .unknownPolicy(name):
"unknown policy '\(name)' (expected: adaptive, adaptive-activity, manual, fixed-<N>m)"
}
}
}

private enum CLIArguments {
case run(tracePath: String, policyNames: [String], jsonOutput: Bool, gapGraceSeconds: TimeInterval?)
case help(exitCode: Int32)
case invalid(message: String)

static let allPolicyNames = [
"adaptive", "adaptive-activity", "fixed-2m", "fixed-5m", "fixed-15m", "fixed-30m", "manual",
]

static func parse(_ arguments: [String]) -> Self {
if arguments.contains("-h") || arguments.contains("--help") {
return .help(exitCode: EXIT_SUCCESS)
}

var tracePath: String?
var policyNames: [String] = []
var jsonOutput = false
var gapGraceSeconds: TimeInterval? = ReplayTraceSegmenter.defaultGraceSeconds
var index = 0
while index < arguments.count {
let argument = arguments[index]
switch argument {
case "--json":
jsonOutput = true
case "--raw-wall-clock":
gapGraceSeconds = nil
case "--gap-grace":
index += 1
guard index < arguments.count,
let seconds = TimeInterval(arguments[index]),
seconds >= 0,
seconds.isFinite
else { return .invalid(message: "--gap-grace requires non-negative finite seconds") }
gapGraceSeconds = seconds
case "--policy":
index += 1
guard index < arguments.count else { return .invalid(message: "--policy requires a value") }
policyNames.append(arguments[index])
default:
guard tracePath == nil else {
return .invalid(message: "unexpected argument '\(argument)'")
}
tracePath = argument
}
index += 1
}

guard let tracePath else {
return .help(exitCode: EXIT_FAILURE)
}
return .run(
tracePath: tracePath,
policyNames: policyNames.isEmpty ? self.allPolicyNames : policyNames,
jsonOutput: jsonOutput,
gapGraceSeconds: gapGraceSeconds)
}
}

AdaptiveReplayCLI.main()
Loading