Skip to content
Merged
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
6 changes: 3 additions & 3 deletions corpus/skills/cat-mode/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -250,9 +250,9 @@ agent switch, or resubmit is a fix, and none comes before the repro.

**An interruption or stuck state gets instrument-level proof before a fix, and the fix goes to a subagent.** A poll loop not converging, a process not responding as expected, a restart that doesn't complete — treat this as its own investigation, not something to guess through inline. Gather real evidence first (the target's own logs, `ps -o stat,wchan`, a live query) before naming a cause, then delegate the actual fix to a subagent rather than hand-patching it in the main thread. A DO1 restart once looked hung on a stale PID; the owner's own log showed the real mechanism in two lines — `received SIGTERM, shutting down gracefully` followed 30s later by `process survived SIGTERM for 30000ms after worker stop; restarting worker` — a per-worker watchdog resurrecting mid-shutdown under real task load, not a hang.

**A factual or technical claim gets a real repro script, not a history search.**
Judging an old comment or a "probably confabulated" suspicion needs an actual attempt under the claimed conditions, not a `git log` sweep. No citation means
"never verified," not "false." A live repro proved a dismissed "yauzl hangs" comment was real on the pinned versions.
**UI testing must not disrupt the user's own session.** Prove a UI or surface change somewhere disposable — a test channel or workspace, a throwaway profile, a second display, a VM, a headless run. Driving the user's real keyboard, mouse, or screen is a last resort needing an explicit hands-off window first: state the acceptance test in one line, get the yes, `touch /tmp/.ui-input-window`, and remove it when the window closes; a PreToolUse hook (`engine/hooks/ui-input-guard/`) blocks synthetic input and screen recording while no window is open, the screen is locked, or the user is still typing. Stop at the first sign the session is theirs again (idle time drops, the frontmost app changes, the screen locks), and leave no residue: undo stray messages, pins, or reactions, or say what was left behind.

**A factual or technical claim gets a real repro script, not a history search.** Judging an old comment or a "probably confabulated" suspicion needs an actual attempt under the claimed conditions, not a `git log` sweep. No citation means "never verified," not "false."

**Unhedged root-cause or fix claims about live system behavior need
instrument-level proof in the same message, or `UNVERIFIED:`.** The gate is the claim type ("this is why it's slow," "this is the bug"), not a
Expand Down
1 change: 1 addition & 0 deletions engine/CLAUDE.core.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ These override brevity. If proof makes a message longer, the message gets longer
- Before inviting me to test: run one full end-to-end machine-verified rehearsal of the exact flow I will perform. Pieces verified separately don't count as ready. Never say "go" on assembly alone.
- Before the live test starts, restate the acceptance test in one sentence and get my yes ("the test is: you speak, and X happens"). I should never have to write it myself in caps.
- Once I'm testing: freeze the demo surface. No edits, relaunches, or cosmetic changes to the thing I'm looking at unless I asked or the test is failing. Same session: an unrequested layout edit during the test window corrupted the demo page. Mechanically: when the live window opens, write the demo-surface paths (one absolute path, `dir/` prefix, or glob per line) to `/tmp/.demo-freeze`, and delete the file when the window ends — a PreToolUse hook (`engine/hooks/demo-freeze/`) blocks edits to matching paths while it exists (auto-expires after 2h).
- Prove UI work somewhere disposable (test channel, throwaway profile, second display, VM, headless run) instead of my live session. Driving my real keyboard, mouse, or screen needs an explicit hands-off window: state the acceptance test in one line, get my yes, then `touch /tmp/.ui-input-window` and delete it when the window ends — a PreToolUse hook (`engine/hooks/ui-input-guard/`) blocks synthetic input and screen recording while no window is open, the screen is locked, or I am still typing. Undo stray messages or reactions the run created, or say what was left.
- Every message during a live window ends with exactly one action for me, or "nothing needed from you for ~N minutes". Never leave me waiting without a named next step.
- If the deliverable is a same-day demo, plan the demo path first — the smallest end-to-end visible slice. Product-grade extras (settings UIs, multi-platform parity, test suites) come only after the demo runs.

Expand Down
27 changes: 27 additions & 0 deletions tests/test_cat_mode.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,33 @@ def test_body_names_the_default_hook_and_flag(self):
self.assertEqual(parse_frontmatter(read_skill_text())["disable-model-invocation"], "true")


class TestUiTestingRule(unittest.TestCase):
"""The rule that keeps a UI proof run off the user's own session.

Prose cannot be executed, but the pieces an agent has to act on -- a
disposable surface, a granted window, the marker path the guard hook
reads, and cleanup -- must all still be named, and the marker path must
match the hook that enforces it.
"""

def test_names_a_disposable_surface_and_a_granted_window(self):
text = read_skill_text().lower()
self.assertIn("disposable", text)
self.assertIn("hands-off window", text)

def test_names_the_marker_path_the_guard_hook_reads(self):
self.assertIn("/tmp/.ui-input-window", read_skill_text())

def test_marker_path_matches_the_hook_default(self):
hook = os.path.join(REPO_ROOT, "engine", "hooks", "ui-input-guard", "detect.py")
with open(hook, encoding="utf-8") as handle:
self.assertIn('"/tmp/.ui-input-window"', handle.read())

def test_requires_cleanup_of_what_the_run_left_behind(self):
text = read_skill_text().lower()
self.assertTrue("residue" in text or "undo stray" in text, "cleanup rule missing")


class TestCatModeReferences(unittest.TestCase):
def test_every_referenced_skill_still_exists(self):
text = read_skill_text()
Expand Down
Loading