| name | verify |
| description | Witness BetterCmdTab product-source changes at the live macOS surface when a diff needs end-to-end runtime evidence. |
Verify BetterCmdTab
A witness is a captured observation from the freshly built app after real
user input crosses every changed seam. Build and test results are setup; the
witness decides the verdict.
1. Bind the claim
Fix the scope as an explicit BASE..TARGET range or as the working tree.
Inventory committed, staged, unstaged, and untracked files with the matching
git diff --name-status commands plus
git ls-files --others --exclude-standard. Map every changed product file to:
- the user action that reaches it; and
- the observable outcome that should change or remain unchanged.
Treat tests as specifications for what to drive. A diff with no runtime surface
(docs, tests, or compile-time metadata only) is SKIP.
Complete when: the range is stated and every inventoried product file
belongs to one runtime claim or has an explicit non-runtime reason.
2. Build and launch a witness process
Use the Debug lane by default. Use the Release lane for Liquid Glass/macOS 26,
release-configuration, or performance claims. Signing, notarization, and
updater installation are approval-gated release work outside this skill; return
BLOCKED and ask before invoking release tooling. An ordinary Xcode Release
build is not evidence for them.
VERIFY_ROOT="$PWD/build/verify"
SCHEME="BetterCmdTab Debug"
CONFIGURATION="Debug"
PRODUCT="BetterCmdTab Debug"
BUNDLE_ID="pro.bettercmdtab.BetterCmdTab.debug"
DERIVED_DATA="$VERIFY_ROOT/DerivedData-$CONFIGURATION"
mkdir -p "$VERIFY_ROOT"
if ! xcodebuild -project BetterCmdTab.xcodeproj \
-scheme "$SCHEME" -configuration "$CONFIGURATION" \
-derivedDataPath "$DERIVED_DATA" build \
>"$VERIFY_ROOT/build.log" 2>&1; then
tail -80 "$VERIFY_ROOT/build.log"
exit 1
fi
APP="$DERIVED_DATA/Build/Products/$CONFIGURATION/$PRODUCT.app"
EXEC="$APP/Contents/MacOS/$PRODUCT"
test -x "$EXEC"
Every BetterCmdTab process installs global handlers. Before launch, identify all
running BetterCmdTab processes. Gracefully clean up a recorded prior witness;
if an installed copy is running, ask before quitting it, record its executable
path and running state, and restore that exact copy afterward. A declined quit
makes runtime verification BLOCKED.
Launch the bundle executable directly with isolated preferences and config.
Keep its unique state for restarts and crash recovery. At the first launch of a
verification session, an existing state-path means the prior run needs
recovery. An intentional restart in the current session reuses
the recorded state and skips this initializer.
test ! -e "$VERIFY_ROOT/state-path" || {
echo "Recover the prior witness first: $VERIFY_ROOT/state-path" >&2
exit 1
}
STATE="$(mktemp -d "$VERIFY_ROOT/state.XXXXXX")"
printf '%s\n' "$STATE" >"$VERIFY_ROOT/state-path"
printf '%s\n' "$EXEC" >"$VERIFY_ROOT/executable-path"
printf '%s\n' "$BUNDLE_ID" >"$VERIFY_ROOT/bundle-id"
mkdir -p "$STATE/home" "$STATE/xdg"
CFFIXED_USER_HOME="$STATE/home" \
XDG_CONFIG_HOME="$STATE/xdg" \
"$EXEC" -GitHubUpdater.checkInterval manual \
>"$VERIFY_ROOT/app.stdout.log" 2>&1 &
echo $! >"$VERIFY_ROOT/app.pid"
Open Settings through the recorded process with:
osascript -e "tell application id \"$BUNDLE_ID\" to reopen"
The switcher becomes ready only after Accessibility trust. Panel appearance
after the real chord is the readiness check. If macOS prompts for Accessibility
or Automation, ask whether to grant it; the grant is persistent shared TCC
state and belongs in the report. Without approval, return BLOCKED; preserve
TCC as-is.
Complete when: the recorded PID resolves to EXEC and is the sole
BetterCmdTab process, the intended surface is drivable, verification state is
outside the user's defaults and XDG config, and any TCC change was approved.
3. Drive the changed seam
Reach the behavior through its real boundary: CGEvent/Carbon input, an AppKit
control, a watched file, or an Accessibility window action. Calling an internal
Swift function is a test, not a witness.
| Changed area | Drive | Witness |
|---|
App/, System/ | Fresh launch, reopen, menu action, or relevant permission transition | Window/menu/lifecycle state and filtered unified-log lines |
Input/, Switcher/ | Hold and send the actual configured chord with System Events; use a real password field for Secure Event Input and ask the user for a real trackpad gesture | Cropped panel capture plus the selected/cancelled app or window outcome |
Settings/, Preferences | Reopen Settings, operate the control, restart with the same isolated state, then exercise its downstream behavior | Restored control state and the changed live behavior |
Catalog/, Windows/ | Prepare named real apps/windows, open the switcher, then select or act on one | Visible rows and resulting frontmost app/window state |
ConfigFile, import/export | Seed $STATE/xdg/bettercmdtab/config.json or use the real import/export UI | File contents and the live setting/behavior after reload |
| updater/About | Use the manual UI action against a safe target | UI result and relevant unified-log lines |
| hot-path performance | Use separate clean base/target worktrees and SHA-specific Release build/state roots; drive identical seeded scenarios under Instruments with Log.reveal | Equal sample counts and median/p95 latency, CPU, or allocation numbers |
For each independent claim, drive the claimed path and one adjacent probe:
cancel, repeat, stale state, empty input, rapid input, or the neighboring error
case. Inspect visual captures for clipping, stale/blank frames, focus, and
contrast. A changed macOS 13 fallback requires a real macOS 13 host/VM;
otherwise that claim is BLOCKED.
Destructive actions use a temporary target or dry run. Publishing, notarizing,
installing an update, deleting user data, or changing shared system state
requires approval before the action.
Complete when: every runtime claim has an observed expected outcome and its
own adjacent probe has an observed outcome at the same surface.
4. Preserve evidence and restore the machine
Capture the smallest useful window/region with screencapture; read every image
before using it as evidence. Capture nonvisual outcomes from app state or
Unified Logging filtered to the witness process. Keep the relevant output
inline; a local path alone is not evidence for a remote reader.
Request a graceful AppKit quit for the exact recorded PID, then wait for it to
disappear:
VERIFY_ROOT="$PWD/build/verify"
ROOT_REAL="$(realpath "$VERIFY_ROOT")"
test "$ROOT_REAL" = "$(realpath "$PWD")/build/verify"
PID="$(cat "$VERIFY_ROOT/app.pid")"
EXEC="$(realpath "$(cat "$VERIFY_ROOT/executable-path")")"
case "$EXEC" in "$ROOT_REAL"/*) ;; *) exit 1 ;; esac
swift -e 'import AppKit
import Darwin
import Foundation
func normalized(_ url: URL) -> String {
url.resolvingSymlinksInPath().standardizedFileURL.path
}
let pid = pid_t(CommandLine.arguments[1])!
let executable = URL(fileURLWithPath: CommandLine.arguments[2])
let bundle = executable.deletingLastPathComponent()
.deletingLastPathComponent().deletingLastPathComponent()
guard let app = NSRunningApplication(processIdentifier: pid),
let actualBundle = app.bundleURL,
normalized(actualBundle) == normalized(bundle) else {
fatalError("Recorded PID no longer belongs to the witness")
}
guard app.terminate() else { fatalError("AppKit termination request failed") }
let deadline = Date().addingTimeInterval(5)
while !app.isTerminated && Date() < deadline {
RunLoop.current.run(until: Date().addingTimeInterval(0.1))
}
exit(app.isTerminated ? 0 : 1)
' "$PID" "$EXEC"
Signals bypass in-process symbolic-hotkey restoration. If AppKit quit fails or
the process vanished abnormally, follow RECOVERY.md before
removing state. After a clean quit, use the validated cleanup block in recovery
step 3, then restore the approved installed app path.
Complete when: each reported step has reviewed evidence, the witness PID is
gone after AppKit quit or recovery, state metadata is removed, native ⌘Tab
works, and the installed app has its original running/stopped state.
5. Report the verdict
## Verification: <one-line claim>
**Verdict:** PASS | FAIL | BLOCKED | SKIP
**Build:** <scheme/configuration, ref, executable path>
**Claim:** <expected user-visible behavior>
### Claim 1: <expected behavior>
1. ✅ <real action> → <observation and evidence>
2. 🔍 <adjacent probe> → <observation and evidence>
<Repeat per independent claim.>
**Witness:** <attached screenshots or inline state/log excerpts>
**Findings:** <runtime surprises, friction, or “none”>
PASS: every claim was witnessed and the probe held.
FAIL: the running app contradicted a claim, regressed adjacent behavior, or
produced ambiguous evidence.
BLOCKED: the surface could not be reached; include the exact build,
permission, automation, or environment failure.
SKIP: no runtime surface exists; give the reason in one line.
Complete when: the verdict follows these definitions, every claim from step
1 is accounted for, and the evidence reaches the reader.