12 unchanged lines
- Reproducing CLI/TUI bugs with deterministic input.
- Reproducing CLI/TUI bugs with deterministic input.
- Verifying keyboard flows, prompts, interrupts, resize behavior, and terminal layout.
- Verifying keyboard flows, prompts, interrupts, resize behavior, and terminal layout.
- Capturing before/after transcripts for bug fixes.
- Capturing before/after transcripts for bug fixes.
- Profiling startup time, slow operations, hangs, or memory growth.
- Profiling startup time, slow operations, hangs, or memory growth.
- Recording a short terminal demo when output is easier to show than explain.
- Recording a short terminal demo when output is easier to show than explain.
## Harness Loop
## Harness Loop
1. Identify the command under test and the smallest reproducible workspace.
1. Identify the command under test and the smallest reproducible workspace.
2. Discover existing local harnesses: package scripts, e2e tests, demo recorders, expect scripts, or PTY helpers.
2. Discover existing local harnesses: package scripts, e2e tests, demo recorders, expect scripts, or PTY helpers.
3. If no harness exists, launch the CLI in an isolated terminal session with deterministic env vars.
3. If no harness exists, launch the CLI in an isolated terminal session with deterministic env vars.
74 unchanged lines
- Repo-native harness: prefer checked-in scripts because they know the app's startup, env, and prompts.
- Repo-native harness: prefer checked-in scripts because they know the app's startup, env, and prompts.
- `tmux`: managed sessions, `capture-pane`, `send-keys`, attach/detach.
- `tmux`: managed sessions, `capture-pane`, `send-keys`, attach/detach.
- PTY probe: use a short Python, Node, or Expect script when tmux is unavailable.
- PTY probe: use a short Python, Node, or Expect script when tmux is unavailable.
- Runtime inspector: use Node or Bun inspector for CPU profiles, heap snapshots, and live evaluation.
- Runtime inspector: use Node or Bun inspector for CPU profiles, heap snapshots, and live evaluation.
- Terminal recorder: use repo-local demo tools or asciinema-compatible tools when the user asks for a demo.
- Terminal recorder: use repo-local demo tools or asciinema-compatible tools when the user asks for a demo.
## Minimal tmux Harness
## Minimal tmux Harness
SESSION="cli-harness-$(date +%s)"
SESSION="cli-harness-$(date +%s)"
tmux new-session -d -s "$SESSION" -- <command-under-test>
tmux new-session -d -s "$SESSION" -- <command-under-test>
tmux capture-pane -pt "$SESSION"
tmux capture-pane -pt "$SESSION"
tmux send-keys -t "$SESSION" "help" Enter
tmux send-keys -t "$SESSION" "help" Enter
tmux capture-pane -pt "$SESSION"
tmux capture-pane -pt "$SESSION"
tmux kill-session -t "$SESSION"
tmux kill-session -t "$SESSION"
For Node CLIs:
For Node CLIs:
NODE_OPTIONS="--inspect=127.0.0.1:0" tmux new-session -d -s "$SESSION" -- <node-cli-command>
NODE_OPTIONS="--inspect=127.0.0.1:0" tmux new-session -d -s "$SESSION" -- <node-cli-command>
Read the terminal output to find the inspector URL, then use Chrome DevTools-compatible tooling if profiling is needed.
Read the terminal output to find the inspector URL, then use Chrome DevTools-compatible tooling if profiling is needed.
## Minimal PTY Harness
## Minimal PTY Harness
Use a PTY script when you need deterministic waits in a repo that does not have tmux or a demo harness. Keep it temporary unless the user asks to add a reusable test.
Use a PTY script when you need deterministic waits in a repo that does not have tmux or a demo harness. Keep it temporary unless the user asks to add a reusable test.
import select
import select
import subprocess
import subprocess
master_fd, slave_fd = pty.openpty()
master_fd, slave_fd = pty.openpty()
proc = subprocess.Popen(
proc = subprocess.Popen(
["<command>", "<arg>"],
["<command>", "<arg>"],
stdin=slave_fd,
stdin=slave_fd,
stdout=slave_fd,
stdout=slave_fd,
stderr=slave_fd,
stderr=slave_fd,
close_fds=True,
close_fds=True,
os.close(slave_fd)
os.close(slave_fd)
deadline = time.time() + 30
deadline = time.time() + 30
while time.time() < deadline:
while time.time() < deadline:
ready, _, _ = select.select([master_fd], [], [], 0.25)
ready, _, _ = select.select([master_fd], [], [], 0.25)
if not ready:
if not ready:
chunk = os.read(master_fd, 4096)
chunk = os.read(master_fd, 4096)
buffer += chunk
buffer += chunk
if b"<ready text>" in buffer:
if b"<ready text>" in buffer:
os.write(master_fd, b"help\n")
os.write(master_fd, b"help\n")
print(buffer.decode(errors="replace"))
print(buffer.decode(errors="replace"))
proc.terminate()
proc.terminate()
os.close(master_fd)
os.close(master_fd)
If the CLI needs richer terminal control, use `pty.fork()` or an existing PTY library.
If the CLI needs richer terminal control, use `pty.fork()` or an existing PTY library.
## Profiling Recipes
## Profiling Recipes
- Startup regression: capture baseline and treatment startup timings under the same machine, env, and command.
- Startup regression: capture baseline and treatment startup timings under the same machine, env, and command.
- Slow operation: start a CPU profile, perform the operation, stop the profile, and compare top self-time functions.
- Slow operation: start a CPU profile, perform the operation, stop the profile, and compare top self-time functions.
- Memory leak: force GC if available, take a heap snapshot, perform the operation repeatedly, force GC again, and take another snapshot.
- Memory leak: force GC if available, take a heap snapshot, perform the operation repeatedly, force GC again, and take another snapshot.
- Hang: capture the screen, active handles/resources, and a stack/CPU sample before interrupting.
- Hang: capture the screen, active handles/resources, and a stack/CPU sample before interrupting.
## Guardrails
## Guardrails