← back to Terminal Status
snapshot before child-session paint-guard fix
418fa0942294896ab7cfbbcb6b5f6660d8201eb9 · 2026-09-09 14:54:02 -0700 · Steve Abrams
Files touched
A configure_codex.pyA install.pyA integrations/current-dot.shA integrations/lightgreendot.shA integrations/tab-ticket-set.shA integrations/tab-ticket-title.shA pin_codex_headers.pyM terminal_status.pyM test_terminal_status.pyA ticket_binding.pyA verification/install/after/.agents/skills/color/scripts/color.shA verification/install/after/.agents/skills/color/scripts/repaint-dot.shA verification/install/after/.agents/skills/color/scripts/semantic-dot.shA verification/install/after/.claude/hooks/dot-floor.shA verification/install/after/.claude/skills/allcolordots/allcolordots.shA verification/install/after/.claude/skills/color/scripts/color.shA verification/install/after/.claude/skills/color/scripts/paint-tab.shA verification/install/after/.claude/skills/color/scripts/tab-state.shA verification/install/after/.claude/skills/color/scripts/working-state.shA verification/install/after/.claude/skills/colordots/paint-lib.shA verification/install/after/.claude/skills/dot/current.shA verification/install/after/.claude/skills/greendot/greendot.shA verification/install/after/.claude/skills/lightgreendot/lightgreendot.shA verification/install/after/.claude/skills/orangedot/orangedot.shA verification/install/after/.claude/skills/pinkdot/pinkdot.shA verification/install/after/.claude/skills/purpledot/purpledot.shA verification/install/after/.claude/skills/tab-ticket/scripts/tab-ticket-set.shA verification/install/after/.claude/skills/tab-ticket/scripts/tab-ticket-title.shA verification/install/after/.claude/skills/yellowdot/yellowdot.shA verification/install/before/.agents/skills/color/scripts/color.shA verification/install/before/.agents/skills/color/scripts/repaint-dot.shA verification/install/before/.agents/skills/color/scripts/semantic-dot.shA verification/install/before/.claude/hooks/dot-floor.shA verification/install/before/.claude/skills/allcolordots/allcolordots.shA verification/install/before/.claude/skills/color/scripts/color.shA verification/install/before/.claude/skills/color/scripts/paint-tab.shA verification/install/before/.claude/skills/color/scripts/tab-state.shA verification/install/before/.claude/skills/color/scripts/working-state.shA verification/install/before/.claude/skills/colordots/paint-lib.shA verification/install/before/.claude/skills/dot/current.shA verification/install/before/.claude/skills/greendot/greendot.shA verification/install/before/.claude/skills/lightgreendot/lightgreendot.shA verification/install/before/.claude/skills/orangedot/orangedot.shA verification/install/before/.claude/skills/pinkdot/pinkdot.shA verification/install/before/.claude/skills/purpledot/purpledot.shA verification/install/before/.claude/skills/tab-ticket/scripts/tab-ticket-set.shA verification/install/before/.claude/skills/tab-ticket/scripts/tab-ticket-title.shA verification/install/before/.claude/skills/yellowdot/yellowdot.shA verify_live.pyA verify_ui.py
Diff
commit 418fa0942294896ab7cfbbcb6b5f6660d8201eb9
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Wed Sep 9 14:54:02 2026 -0700
snapshot before child-session paint-guard fix
---
configure_codex.py | 62 +++++
install.py | 166 ++++++++++++++
integrations/current-dot.sh | 3 +
integrations/lightgreendot.sh | 9 +
integrations/tab-ticket-set.sh | 5 +
integrations/tab-ticket-title.sh | 4 +
pin_codex_headers.py | 60 +++++
terminal_status.py | 158 +++++++++++--
test_terminal_status.py | 88 ++++++++
ticket_binding.py | 79 +++++++
.../after/.agents/skills/color/scripts/color.sh | 170 ++++++++++++++
.../.agents/skills/color/scripts/repaint-dot.sh | 3 +
.../.agents/skills/color/scripts/semantic-dot.sh | 7 +
.../install/after/.claude/hooks/dot-floor.sh | 4 +
.../.claude/skills/allcolordots/allcolordots.sh | 114 ++++++++++
.../after/.claude/skills/color/scripts/color.sh | 249 +++++++++++++++++++++
.../.claude/skills/color/scripts/paint-tab.sh | 10 +
.../.claude/skills/color/scripts/tab-state.sh | 11 +
.../.claude/skills/color/scripts/working-state.sh | 10 +
.../after/.claude/skills/colordots/paint-lib.sh | 12 +
.../install/after/.claude/skills/dot/current.sh | 3 +
.../after/.claude/skills/greendot/greendot.sh | 3 +
.../.claude/skills/lightgreendot/lightgreendot.sh | 9 +
.../after/.claude/skills/orangedot/orangedot.sh | 3 +
.../after/.claude/skills/pinkdot/pinkdot.sh | 3 +
.../after/.claude/skills/purpledot/purpledot.sh | 3 +
.../skills/tab-ticket/scripts/tab-ticket-set.sh | 5 +
.../skills/tab-ticket/scripts/tab-ticket-title.sh | 4 +
.../after/.claude/skills/yellowdot/yellowdot.sh | 3 +
.../before/.agents/skills/color/scripts/color.sh | 167 ++++++++++++++
.../.agents/skills/color/scripts/repaint-dot.sh | 36 +++
.../.agents/skills/color/scripts/semantic-dot.sh | 70 ++++++
.../install/before/.claude/hooks/dot-floor.sh | 40 ++++
.../.claude/skills/allcolordots/allcolordots.sh | 196 ++++++++++++++++
.../before/.claude/skills/color/scripts/color.sh | 246 ++++++++++++++++++++
.../.claude/skills/color/scripts/paint-tab.sh | 94 ++++++++
.../.claude/skills/color/scripts/tab-state.sh | 170 ++++++++++++++
.../.claude/skills/color/scripts/working-state.sh | 219 ++++++++++++++++++
.../before/.claude/skills/colordots/paint-lib.sh | 75 +++++++
.../install/before/.claude/skills/dot/current.sh | 25 +++
.../before/.claude/skills/greendot/greendot.sh | 61 +++++
.../.claude/skills/lightgreendot/lightgreendot.sh | 72 ++++++
.../before/.claude/skills/orangedot/orangedot.sh | 98 ++++++++
.../before/.claude/skills/pinkdot/pinkdot.sh | 98 ++++++++
.../before/.claude/skills/purpledot/purpledot.sh | 98 ++++++++
.../skills/tab-ticket/scripts/tab-ticket-set.sh | 60 +++++
.../skills/tab-ticket/scripts/tab-ticket-title.sh | 115 ++++++++++
.../before/.claude/skills/yellowdot/yellowdot.sh | 94 ++++++++
verify_live.py | 77 +++++++
verify_ui.py | 28 +++
50 files changed, 3381 insertions(+), 18 deletions(-)
diff --git a/configure_codex.py b/configure_codex.py
new file mode 100644
index 0000000..74c4c39
--- /dev/null
+++ b/configure_codex.py
@@ -0,0 +1,62 @@
+#!/usr/bin/env python3
+"""One-time local Codex title/default instructions; private preimage backups."""
+import hashlib
+import json
+from pathlib import Path
+import re
+from terminal_status import atomic_write
+
+root = Path.home()
+backup = root / '.local/state/abrams-terminal-status/config-backups'
+config = root / '.codex/config.toml'
+instructions = root / '.codex/AGENTS.md'
+changes = {}
+text = config.read_text()
+if re.search(r'^\[tui\]\s*$', text, re.M):
+ match = re.search(r'^\[tui\]\s*\n(?P<body>.*?)(?=^\[|\Z)', text, re.M | re.S)
+ body = match['body']
+ if re.search(r'^terminal_title\s*=', body, re.M):
+ body = re.sub(r'^terminal_title\s*=.*$', 'terminal_title = []', body, flags=re.M)
+ else:
+ body = 'terminal_title = []\n' + body
+ text = text[:match.start('body')] + body + text[match.end('body'):]
+else:
+ text += '\n# Shared TK/dot renderer owns terminal titles.\n[tui]\nterminal_title = []\n'
+changes[config] = text
+text = instructions.read_text()
+marker = '## Every Codex build displays its ticket and semantic dot'
+if marker not in text:
+ text += '''
+
+## Every Codex build displays its ticket and semantic dot
+
+At the start of every non-trivial build, use the canonical tickets MCP
+(CLI fallback ~/Projects/ticket-system/tk) to find/create/take its ticket.
+Immediately bind it to this session:
+
+ python3 ~/Projects/terminal-status/terminal_status.py ticket TK-NUMBER
+
+Keep the ticket visible in the terminal header, badge, and desktop dots menu.
+Set status using the existing Codex dot skills, all backed by this shared engine:
+green = working; yellow = direction needed; purple = approval gated;
+orange = pasted input needed; pink = parked. Put the short reason in the label.
+Generic WORKING/repaint hooks preserve the ticket; never write a competing
+status file or start a title-paint loop. For monitoring with a scheduled check,
+use set green "MONITORING · next TIME" --monitoring on the same engine.
+Update the binding when the main task changes. An unbound build must be
+visibly TK REQUIRED until claimed; never invent or reuse a stale ticket.
+Subagents inherit the ticket and must never paint their parent's terminal.
+Implementation and rollback: ~/Projects/terminal-status/README.md.
+'''
+changes[instructions] = text
+report = []
+for path, desired in changes.items():
+ original = path.read_text()
+ saved = backup / (path.name + '.before')
+ if not saved.exists():
+ atomic_write(saved, original)
+ if original != desired:
+ atomic_write(path, desired)
+ report.append({'path': str(path), 'backup': str(saved),
+ 'after_sha256': hashlib.sha256(desired.encode()).hexdigest()})
+print(json.dumps(report, indent=2))
diff --git a/install.py b/install.py
new file mode 100644
index 0000000..401ce17
--- /dev/null
+++ b/install.py
@@ -0,0 +1,166 @@
+#!/usr/bin/env python3
+"""Prepare, inspect, apply, or roll back a fixed list of local script adapters."""
+import argparse
+import hashlib
+import json
+import os
+from pathlib import Path
+import stat
+import subprocess
+import sys
+
+from terminal_status import atomic_write
+
+PROJECT = Path(__file__).resolve().parent
+USER_ROOT = Path.home()
+PLAN = PROJECT / "verification/install-plan.json"
+BUNDLE = PROJECT / "verification/install"
+
+
+def digest(data):
+ return hashlib.sha256(data).hexdigest()
+
+
+def replacements():
+ files = {}
+ for color in ("green", "yellow", "purple", "orange", "pink"):
+ path = f".claude/skills/{color}dot/{color}dot.sh"
+ files[path] = (
+ '#!/usr/bin/env bash\n'
+ '# Shared semantic status; see Projects/terminal-status/README.md.\n'
+ f'exec bash "$HOME/Projects/terminal-status/integrations/dot.sh" claude {color} "$@"\n')
+ mapping = {
+ ".claude/skills/colordots/paint-lib.sh": "paint-lib.sh",
+ ".claude/hooks/dot-floor.sh": "dot-floor.sh",
+ ".claude/skills/color/scripts/working-state.sh": "working-state.sh",
+ ".claude/skills/color/scripts/paint-tab.sh": "paint-tab.sh",
+ ".claude/skills/color/scripts/tab-state.sh": "tab-state.sh",
+ ".agents/skills/color/scripts/semantic-dot.sh": "semantic-dot.sh",
+ ".agents/skills/color/scripts/repaint-dot.sh": "repaint-dot.sh",
+ ".claude/skills/tab-ticket/scripts/tab-ticket-set.sh": "tab-ticket-set.sh",
+ ".claude/skills/tab-ticket/scripts/tab-ticket-title.sh": "tab-ticket-title.sh",
+ ".claude/skills/dot/current.sh": "current-dot.sh",
+ ".claude/skills/lightgreendot/lightgreendot.sh": "lightgreendot.sh",
+ }
+ for target, source in mapping.items():
+ files[target] = (PROJECT / "integrations" / source).read_text()
+
+ scanner = ".claude/skills/allcolordots/allcolordots.sh"
+ baseline = BUNDLE / "before" / scanner
+ text = (baseline if baseline.exists() else USER_ROOT / scanner).read_text()
+ start = text.index("# Codex rewrites its title")
+ end = text.index("# ---- JUMP:")
+ text = text[:start] + text[end:]
+ start = text.index("# ---- collect normalized")
+ end = text.index("# ---- roster of ONE color")
+ text = text[:start] + (
+ '# ---- Shared state scanner: presentation never overrides canonical state ----\n'
+ 'collect_rows(){\n'
+ ' python3 "$HOME/Projects/terminal-status/terminal_status.py" scan --tsv\n'
+ '}\n\n') + text[end:]
+ start = text.index("# Prefer semantic LIVE")
+ end = text.index("# Usage:")
+ text = text[:start] + (
+ '# Status comes from the shared session-bound terminal-status engine.\n'
+ '# Labels and dot files are presentation; exact main agent ownership is required.\n'
+ '#\n') + text[end:]
+ text = text.replace('CODEX_DOTS="$HOME/.codex/tab-dots"\n', '')
+ text = text.replace('set -u\n', 'set -u\nset -o pipefail\n')
+ text = "\n".join(line + ' || return $?' if 'local want=' in line and 'collect_rows' in line else line for line in text.split('\n'))
+ text = text.replace('print_roster yellow; exit 0', 'print_roster yellow; exit $?')
+ text = text.replace('print_roster "${2:-yellow}"; exit 0', 'print_roster "${2:-yellow}"; exit $?')
+ text = text.replace(" exit 0 ;;", " exit $? ;;")
+ text = text.replace('rows="$(collect_rows)"', 'rows="$(collect_rows)" || exit $?')
+ files[scanner] = text
+
+ # Keep the existing SessionStart hook and initialize semantic status afterward.
+ for target in (".claude/skills/color/scripts/color.sh",
+ ".agents/skills/color/scripts/color.sh"):
+ text = (USER_ROOT / target).read_text()
+ marker = "# Shared terminal status follows the cosmetic session color."
+ if marker not in text:
+ text += (
+ '\n' + marker + '\n'
+ 'python3 "$HOME/Projects/terminal-status/terminal_status.py" start --quiet || true\n')
+ files[target] = text
+ return files
+
+
+def prepare(extend=False):
+ if PLAN.exists() and not extend:
+ raise SystemExit("Plan already exists; inspect it instead of replacing the baseline")
+ old = {item["relative"]: item for item in json.loads(PLAN.read_text())} if extend else {}
+ entries = []
+ for relative, content in replacements().items():
+ target = USER_ROOT / relative
+ if target.is_symlink():
+ raise SystemExit("Refusing to replace symlink: " + str(target))
+ original = target.read_bytes()
+ before = BUNDLE / "before" / relative
+ after = BUNDLE / "after" / relative
+ if relative in old:
+ if digest(original) not in (old[relative]["after_sha256"], old[relative]["before_sha256"], old[relative].get("upgrade_sha256")):
+ raise SystemExit("Concurrent adapter edit: " + relative)
+ else:
+ atomic_write(before, original.decode())
+ atomic_write(after, content)
+ subprocess.run(["bash", "-n", str(after)], check=True)
+ entries.append({
+ "relative": relative, "target": str(target),
+ "before": str(before), "after": str(after),
+ "before_sha256": digest(before.read_bytes()),
+ "after_sha256": digest(content.encode()),
+ "upgrade_sha256": digest(original),
+ "mode": stat.S_IMODE(target.stat().st_mode),
+ })
+ atomic_write(PLAN, json.dumps(entries, indent=2) + "\n")
+ print(f"Prepared {len(entries)} script changes: {PLAN}")
+
+
+def change(rollback=False):
+ entries = json.loads(PLAN.read_text())
+ expected = "after_sha256" if rollback else "before_sha256"
+ desired = "before_sha256" if rollback else "after_sha256"
+ source_key = "before" if rollback else "after"
+ # Check every preimage before the first mutation; preserve concurrent edits.
+ for item in entries:
+ target = Path(item["target"])
+ if target.is_symlink():
+ raise SystemExit("Refusing to replace symlink: " + str(target))
+ current = digest(target.read_bytes())
+ allowed = (item[expected], item[desired], item.get("upgrade_sha256")) if not rollback else (item[expected], item[desired])
+ if current not in allowed:
+ raise SystemExit("Concurrent edit; no changes applied: " + str(target))
+ if digest(Path(item[source_key]).read_bytes()) != item[desired]:
+ raise SystemExit("Changed install bundle; no changes applied")
+ for item in entries:
+ target = Path(item["target"])
+ if digest(target.read_bytes()) == item[desired]:
+ continue
+ atomic_write(target, Path(item[source_key]).read_text())
+ os.chmod(target, item["mode"])
+ print(("Restored" if rollback else "Installed") + f" {len(entries)} script adapters")
+
+
+def check():
+ failed = []
+ for item in json.loads(PLAN.read_text()):
+ if digest(Path(item["target"]).read_bytes()) != item["after_sha256"]:
+ failed.append(item["target"])
+ if failed:
+ print(json.dumps({"ok": False, "drift": failed}, indent=2))
+ return 1
+ print(json.dumps({"ok": True, "adapters": len(json.loads(PLAN.read_text()))}))
+ return 0
+
+
+if __name__ == "__main__":
+ parser = argparse.ArgumentParser(description=__doc__)
+ parser.add_argument("action", choices=["prepare", "extend", "apply", "rollback", "check"])
+ args = parser.parse_args()
+ if args.action in ("prepare", "extend"):
+ prepare(extend=args.action == "extend")
+ elif args.action == "check":
+ sys.exit(check())
+ else:
+ change(rollback=args.action == "rollback")
diff --git a/integrations/current-dot.sh b/integrations/current-dot.sh
new file mode 100644
index 0000000..5c0a81a
--- /dev/null
+++ b/integrations/current-dot.sh
@@ -0,0 +1,3 @@
+#!/usr/bin/env bash
+set -eu
+exec python3 "$HOME/Projects/terminal-status/terminal_status.py" status
diff --git a/integrations/lightgreendot.sh b/integrations/lightgreendot.sh
new file mode 100644
index 0000000..e8a9cf9
--- /dev/null
+++ b/integrations/lightgreendot.sh
@@ -0,0 +1,9 @@
+#!/usr/bin/env bash
+# Green monitoring variant with a scheduled check/deadline.
+set -eu
+engine="$HOME/Projects/terminal-status/terminal_status.py"
+case "${1:-}" in
+ --off) exec python3 "$engine" clear ;;
+ --all) exec python3 "$engine" set green MONITORING --monitoring --all claude ;;
+ *) exec python3 "$engine" set green "${1:-MONITORING}" --monitoring ;;
+esac
diff --git a/integrations/tab-ticket-set.sh b/integrations/tab-ticket-set.sh
new file mode 100644
index 0000000..660d549
--- /dev/null
+++ b/integrations/tab-ticket-set.sh
@@ -0,0 +1,5 @@
+#!/usr/bin/env bash
+# Ticket binding is tied to the actual main agent's PID, start time, and tty.
+set -eu
+[[ "${1:-}" == "--apply" ]] && shift
+exec python3 "$HOME/Projects/terminal-status/terminal_status.py" ticket "${1:?TK number required}"
diff --git a/integrations/tab-ticket-title.sh b/integrations/tab-ticket-title.sh
new file mode 100644
index 0000000..9c05047
--- /dev/null
+++ b/integrations/tab-ticket-title.sh
@@ -0,0 +1,4 @@
+#!/usr/bin/env bash
+# Existing launchd renderer; read-only, no independent state or background loop.
+set -eu
+exec python3 "$HOME/Projects/terminal-status/terminal_status.py" headers
diff --git a/pin_codex_headers.py b/pin_codex_headers.py
new file mode 100644
index 0000000..3b0f083
--- /dev/null
+++ b/pin_codex_headers.py
@@ -0,0 +1,60 @@
+#!/usr/bin/env python3
+"""One-time native bindings for currently live Codex windows; no timer installed."""
+import json
+from pathlib import Path
+import subprocess
+import terminal_status as ts
+
+live = ts.owners(ts.processes())
+codex = {tty for tty, owner in live.items() if owner.runtime == 'codex'}
+script = '''
+tell application "iTerm2"
+ set resultRows to ""
+ repeat with w in windows
+ repeat with ti from 1 to count of tabs of w
+ repeat with s in sessions of tab ti of w
+ set resultRows to resultRows & (id of w as text) & " " & ti & " " & (tty of s) & linefeed
+ end repeat
+ end repeat
+ end repeat
+ return resultRows
+end tell
+'''
+output = subprocess.run(['osascript', '-e', script], capture_output=True, text=True, check=True).stdout
+targets = set()
+for line in output.splitlines():
+ parts = line.split()
+ if len(parts) == 3 and parts[2].removeprefix('/dev/') in codex:
+ targets.add((int(parts[0]), int(parts[1])))
+evidence = []
+for window, tab in sorted(targets):
+ script = f'''
+tell application "iTerm2"
+ select window id {window}
+ select tab {tab} of window id {window}
+ activate
+end tell
+tell application "System Events" to tell process "iTerm2"
+ click menu item "Edit Tab Title" of menu 1 of menu item "Tab" of menu "Window" of menu bar 1
+ delay 0.2
+ if not (exists static text "Set Tab Title" of sheet 1 of window 1) then error "Unexpected tab dialog"
+ set oldTab to value of text field 1 of sheet 1 of window 1
+ set value of text field 1 of sheet 1 of window 1 to "\\\\(currentSession.user.abramsTitle)"
+ click button "OK" of sheet 1 of window 1
+ click menu item "Edit Window Title" of menu "Window" of menu bar 1
+ delay 0.2
+ if not (exists static text "Set Window Title" of window 1) then error "Unexpected window dialog"
+ set oldWindow to value of text field 1 of window 1
+ set value of text field 1 of window 1 to "\\\\(currentTab.currentSession.user.abramsTitle)"
+ click button "OK" of window 1
+end tell
+delay 0.2
+tell application "iTerm2"
+ return oldTab & linefeed & oldWindow & linefeed & (name of window id {window})
+end tell
+'''
+ result = subprocess.run(['osascript', '-e', script], capture_output=True, text=True, check=True)
+ evidence.append({'window': window, 'tab': tab, 'result': result.stdout.strip()})
+path = Path.home() / '.local/state/abrams-terminal-status/native-title-bindings.json'
+ts.atomic_write(path, json.dumps(evidence, ensure_ascii=False, indent=2) + '\n')
+print(json.dumps(evidence, ensure_ascii=False, indent=2))
diff --git a/terminal_status.py b/terminal_status.py
index e8d53a7..32282df 100644
--- a/terminal_status.py
+++ b/terminal_status.py
@@ -18,6 +18,7 @@ import sys
import tempfile
import time
import uuid
+import ticket_binding as tickets
COLORS = {
"green": ("🟢", (0, 200, 83), "WORKING"),
@@ -33,6 +34,34 @@ TTY = re.compile(r"ttys[0-9]+")
VERSION = 1
+def issued_clock():
+ # Python <3.10 on macOS made monotonic_ns() process-relative. Read the
+ # underlying system-wide raw clock so queued events compare across runtimes.
+ return time.clock_gettime_ns(getattr(time, "CLOCK_MONOTONIC_RAW", time.CLOCK_MONOTONIC))
+
+
+def status_title(color, label, ticket=""):
+ if color == "none":
+ return ""
+ return COLORS[color][0] + " " + " · ".join(p for p in (ticket, label) if p)
+
+
+def label_ticket(label):
+ label = label.strip(" ·")
+ match = re.match(r"^(TK-\d+)(?=$|[ ·:-])", label, re.I)
+ ticket = match[1].upper() if match else ""
+ if ticket:
+ label = label[len(ticket):].lstrip(" ·:-")
+ return label, ticket
+
+
+def title_payload(title):
+ encoded = base64.b64encode(title.encode()).decode()
+ return (f"\x1b]1;{title}\x07\x1b]2;{title}\x07"
+ f"\x1b]1337;SetBadgeFormat={encoded}\x07"
+ f"\x1b]1337;SetUserVar=abramsTitle={encoded}\x07").encode()
+
+
class StatusError(Exception):
pass
@@ -148,21 +177,18 @@ def osc_payload(record):
if record["state"] == "none":
return b"\x1b]6;1;bg;*;default\x07\x1b]1;\x07\x1b]2;\x07\x1b]1337;SetBadgeFormat=\x07"
title = record["title"]
- rgb = COLORS[record["state"]][1]
+ rgb = (0, 150, 136) if record.get("variant") == "monitoring" else COLORS[record["state"]][1]
text = "".join(
f"\x1b]6;1;bg;{channel};brightness;{value}\x07"
for channel, value in zip(("red", "green", "blue"), rgb))
- badge = base64.b64encode(title.encode()).decode()
- text += f"\x1b]1;{title}\x07\x1b]2;{title}\x07\x1b]1337;SetBadgeFormat={badge}\x07"
- return text.encode()
+ return text.encode() + title_payload(title)
-def paint(owner, record):
+def write_terminal(owner, payload):
fd = os.open("/dev/" + owner.tty, os.O_WRONLY | os.O_NOCTTY | os.O_NONBLOCK)
try:
if not stat.S_ISCHR(os.fstat(fd).st_mode) or not os.isatty(fd):
raise StatusError("Paint target is not a terminal")
- payload = osc_payload(record)
# One write, no background loop, no terminal input injection.
if os.write(fd, payload) != len(payload):
raise StatusError("Incomplete terminal paint")
@@ -170,6 +196,10 @@ def paint(owner, record):
os.close(fd)
+def paint(owner, record):
+ write_terminal(owner, osc_payload(record))
+
+
class Store:
def __init__(self, user_root=None, process_provider=processes, painter=paint):
self.user_root = Path(user_root) if user_root else Path.home()
@@ -178,6 +208,22 @@ class Store:
for runtime in ("claude", "codex")}
self.process_provider = process_provider
self.painter = painter
+ self.ticket_evidence = {}
+ self.known_tickets = None
+
+ def ticket_info(self, owner, record=None):
+ record = record or {}
+ saved = {"id": record.get("ticket", ""), "at": record.get("ticket_at", 0),
+ "source": record.get("ticket_source", "unbound")}
+ found = self.ticket_evidence.get(owner.tty, {})
+ if found.get("at", 0) > saved["at"]:
+ return found
+ # A migration record was already freshness/identity checked by read().
+ if not saved["at"] and not saved["id"] and "version" not in record:
+ _, legacy_ticket = label_ticket(record.get("label", ""))
+ if legacy_ticket and (self.known_tickets is None or legacy_ticket in self.known_tickets):
+ return {"id": legacy_ticket, "at": owner.epoch, "source": "fresh_legacy_label"}
+ return saved
def path(self, owner):
if not TTY.fullmatch(owner.tty):
@@ -222,7 +268,12 @@ class Store:
return None, "invalid"
if r["owner"] != dataclasses.asdict(owner):
return None, "owner_changed"
- expected = (COLORS[r["state"]][0] + " " + r["label"]).strip()
+ ticket = r.get("ticket", "")
+ if (ticket and not re.fullmatch(r"TK-\d+", ticket)) or not isinstance(r.get("ticket_at", 0), (int, float)):
+ return None, "invalid"
+ if r.get("variant", "") not in ("", "monitoring"):
+ return None, "invalid"
+ expected = status_title(r["state"], r["label"], ticket)
if r["title"] != expected or (r["state"] == "none" and r["label"]):
return None, "invalid"
return r, "canonical"
@@ -254,19 +305,32 @@ class Store:
# A clear tombstone, corruption, or reused tty can never fall back.
return r, reason
- def set(self, owner, color, label="", *, issued_ns=None):
- issued_ns = issued_ns if issued_ns is not None else time.monotonic_ns()
- if color not in COLORS or not valid_label(label):
+ def set(self, owner, color, label="", *, issued_ns=None, ticket_update=None,
+ variant=""):
+ issued_ns = issued_ns if issued_ns is not None else issued_clock()
+ if color not in COLORS or not valid_label(label) or variant not in ("", "monitoring"):
raise StatusError("Invalid status or label")
+ label, embedded_ticket = label_ticket(label)
+ if embedded_ticket:
+ ticket_update = embedded_ticket
+ if ticket_update is not None:
+ if ticket_update and (not re.fullmatch(r"TK-\d+", ticket_update) or
+ self.known_tickets is not None and ticket_update not in self.known_tickets):
+ raise StatusError("Ticket does not exist in the canonical ledger")
label = "" if color == "none" else (label or COLORS[color][2])
with self.lock(owner):
previous, _ = self.load(owner)
if previous and issued_ns <= previous["issued_ns"]:
raise StatusError("Obsolete status update; a newer event already won")
+ ticket = self.ticket_info(owner, previous)
+ if ticket_update is not None:
+ ticket = {"id": ticket_update, "at": time.time(), "source": "explicit"}
record = {
"version": VERSION, "owner": dataclasses.asdict(owner),
"state": color, "label": label,
- "title": (COLORS[color][0] + " " + label).strip(),
+ "title": status_title(color, label, ticket["id"]),
+ "ticket": ticket["id"], "ticket_at": ticket["at"],
+ "ticket_source": ticket["source"], "variant": variant if color == "green" else "",
"revision": previous["revision"] + 1 if previous else 1,
"event_id": uuid.uuid4().hex, "issued_ns": issued_ns,
"updated_at": dt.datetime.now(dt.timezone.utc).isoformat(),
@@ -295,7 +359,7 @@ class Store:
def repaint(self, owner):
with self.lock(owner):
- record, reason = self.read(owner)
+ record, reason = self.load(owner)
if record is None:
raise StatusError("Cannot repaint unknown status: " + reason)
self.assert_owner(owner)
@@ -305,8 +369,10 @@ class Store:
def row(self, owner, title=""):
r, reason = self.read(owner, title)
+ ticket = self.ticket_info(owner, r)
result = {"tty": owner.tty, "pid": str(owner.pid),
"runtime": owner.runtime, "live": True,
+ "ticket": ticket["id"], "ticket_source": ticket["source"],
"status_source": reason, "owner": dataclasses.asdict(owner)}
if r is None:
labels = {
@@ -314,9 +380,10 @@ class Store:
"invalid": "Invalid status record — refresh dot",
"owner_changed": "New session — status not set",
}
- result.update(color="none", label="⚪ " + labels.get(reason, "Status not set"))
+ result.update(color="none", label="⚪ " + (ticket["id"] or "TK REQUIRED") + " · " + labels.get(reason, "Status not set"))
return result
- result.update(color=r["state"], label=r["title"] or "⚪ Status cleared",
+ label, _ = label_ticket(r["label"])
+ result.update(color=r["state"], label=status_title(r["state"], label, ticket["id"] or "TK REQUIRED") or "⚪ " + (ticket["id"] or "TK REQUIRED") + " · Status cleared",
updated_at=r.get("updated_at"), revision=r.get("revision"))
warnings = []
live_color = color_of(title)
@@ -334,6 +401,15 @@ class Store:
result["warnings"] = warnings
return result
+ def header(self, owner, writer=write_terminal):
+ with self.lock(owner):
+ row = self.row(owner)
+ minutes = max(0, int((time.time() - owner.epoch) / 60))
+ title = row["label"] + f" · up {minutes // 60}h {minutes % 60}m"
+ self.assert_owner(owner)
+ writer(owner, title_payload(title))
+ return title
+
def iterm_sessions():
script = """
@@ -384,12 +460,15 @@ def scan(store, rows=None, sessions=None):
def main(argv=None):
parser = argparse.ArgumentParser(description=__doc__)
sub = parser.add_subparsers(dest="command", required=True)
- for name in ("set", "clear", "repaint", "current", "start"):
+ for name in ("set", "clear", "repaint", "current", "start", "ticket", "status", "headers"):
cmd = sub.add_parser(name)
if name == "set":
cmd.add_argument("color", choices=[c for c in COLORS if c != "none"])
cmd.add_argument("label", nargs="?", default="")
cmd.add_argument("--all", choices=["claude", "codex"])
+ cmd.add_argument("--monitoring", action="store_true")
+ if name == "ticket":
+ cmd.add_argument("ticket")
cmd.add_argument("--quiet", action="store_true")
legacy = sub.add_parser("paint-legacy")
legacy.add_argument("tty")
@@ -405,6 +484,8 @@ def main(argv=None):
args = parser.parse_args(argv)
store = Store()
rows = processes()
+ store.ticket_evidence, store.known_tickets = tickets.discover(
+ store.user_root, rows, owners(rows), ancestors)
if args.command in ("scan", "audit"):
data = scan(store, rows)
if args.tsv:
@@ -416,11 +497,42 @@ def main(argv=None):
for r in data:
notes = ", ".join(r.get("warnings", []))
print(f'{r["tty"]} {r["label"]} [{r["status_source"]}] {notes}')
+ if args.command == "audit" and any(
+ r.get("warnings") or r["status_source"] in
+ ("invalid", "owner_changed", "legacy_conflict") for r in data):
+ return 1
+ return 0
+ if args.command == "headers":
+ failures = []
+ for owner in owners(rows).values():
+ try:
+ title = store.header(owner)
+ if not args.quiet:
+ print(owner.tty + " " + title)
+ except (StatusError, OSError) as exc:
+ failures.append(owner.tty + ": " + str(exc))
+ if failures:
+ raise StatusError("; ".join(failures))
return 0
caller = current_owner(rows)
if args.command == "current":
print(json.dumps(dataclasses.asdict(caller)))
return 0
+ if args.command == "status":
+ print(json.dumps(store.row(caller), ensure_ascii=False))
+ return 0
+ if args.command == "ticket":
+ ticket = args.ticket.upper()
+ ticket = "" if ticket in ("CLEAR", "NONE") else ("TK-" + ticket if ticket.isdigit() else tickets.short(ticket))
+ if not ticket and args.ticket.upper() not in ("CLEAR", "NONE"):
+ raise StatusError("Expected a canonical TK number or clear")
+ previous, _ = store.read(caller)
+ record = store.set(caller, previous["state"] if previous else "none",
+ label_ticket(previous["label"])[0] if previous else "",
+ ticket_update=ticket, variant=previous.get("variant", "") if previous else "")
+ store.header(caller)
+ print(f'/terminal-ticket → {caller.tty} {record["ticket"] or "TK REQUIRED"}')
+ return 0
targets = [caller]
if args.command == "paint-legacy":
tty = args.tty.removeprefix("/dev/")
@@ -441,17 +553,27 @@ def main(argv=None):
color, label = "none", ""
for owner in targets:
if args.command == "start":
- record, reason = store.read(owner)
+ record, reason = store.load(owner)
if record is not None:
record = store.repaint(owner)
- elif reason in ("missing", "legacy_stale", "legacy_empty", "owner_changed"):
+ elif reason == "missing":
+ sessions, api = iterm_sessions()
+ legacy, legacy_reason = store.legacy_status(owner, sessions.get(owner.tty, ""))
+ if legacy is not None and api == "available":
+ record = store.set(owner, legacy["state"], legacy["label"])
+ elif legacy_reason in ("missing", "legacy_stale", "legacy_empty"):
+ record = store.set(owner, "green")
+ else:
+ raise StatusError("Ambiguous legacy state; set an explicit color")
+ elif reason == "owner_changed":
record = store.set(owner, "green")
else:
raise StatusError("Start found ambiguous status; set an explicit color")
elif args.command == "repaint":
record = store.repaint(owner)
else:
- record = store.set(owner, color, label)
+ record = store.set(owner, color, label,
+ variant="monitoring" if getattr(args, "monitoring", False) else "")
if not getattr(args, "quiet", False):
print(f'/terminal-status → /dev/{owner.tty} {record["title"] or "CLEARED"}')
return 0
diff --git a/test_terminal_status.py b/test_terminal_status.py
index fe31f4d..908a42a 100644
--- a/test_terminal_status.py
+++ b/test_terminal_status.py
@@ -1,7 +1,11 @@
import dataclasses
+import datetime as dt
+import json
import os
from pathlib import Path
import tempfile
+import subprocess
+import sys
import unittest
from unittest.mock import patch
@@ -29,6 +33,71 @@ class StatusTests(unittest.TestCase):
os.utime(path, (when, when))
return path
+ def test_ticket_survives_every_generic_color_and_clear(self):
+ self.store.set(self.owner, "green", "TK-11317 · initial")
+ for color in ts.COLORS:
+ record = self.store.set(self.owner, color)
+ self.assertEqual(record["ticket"], "TK-11317")
+ self.assertEqual(record["title"].count("TK-11317"), 0 if color == "none" else 1)
+
+ def test_event_clock_is_comparable_between_python_processes(self):
+ before = ts.issued_clock()
+ child = int(subprocess.check_output([sys.executable, '-c',
+ 'import terminal_status; print(terminal_status.issued_clock())'], text=True))
+ after = ts.issued_clock()
+ self.assertLessEqual(before, child)
+ self.assertLessEqual(child, after)
+
+ def test_ticket_header_is_read_only_and_does_not_resurrect_clear(self):
+ self.store.set(self.owner, "none", ticket_update="TK-11317")
+ before = self.store.path(self.owner).read_bytes()
+ outputs = []
+ title = self.store.header(self.owner, lambda owner, data: outputs.append(data))
+ self.assertIn("⚪ TK-11317 · Status cleared", title)
+ self.assertEqual(before, self.store.path(self.owner).read_bytes())
+ self.assertNotIn(b']6;', outputs[0])
+
+ def test_new_assignment_wins_and_generic_hook_preserves_binding(self):
+ self.store.ticket_evidence = {self.owner.tty: {"id": "TK-1", "at": 10, "source": "ticket_ledger"}}
+ self.store.set(self.owner, "green")
+ self.store.ticket_evidence[self.owner.tty].update(id="TK-2", at=20)
+ self.assertIn("TK-2", self.store.row(self.owner)["label"])
+ r = self.store.set(self.owner, "purple")
+ self.assertEqual(r["ticket"], "TK-2")
+ self.store.ticket_evidence[self.owner.tty].update(id="TK-1", at=10)
+ self.assertEqual(self.store.set(self.owner, "green")["ticket"], "TK-2")
+
+ def test_ticket_validation_and_tty_reuse(self):
+ self.store.known_tickets = {"TK-11317"}
+ with self.assertRaises(ts.StatusError):
+ self.store.set(self.owner, "green", ticket_update="TK-99999999")
+ self.store.set(self.owner, "green", ticket_update="TK-11317")
+ self.process.started = "Wed Sep 9 09:35:08 2026"
+ self.assertEqual(self.store.row(self.process.owner())["ticket"], "")
+
+ def test_monitoring_shade_remains_green_and_survives_repaint(self):
+ record = self.store.set(self.owner, "green", "Next 10:30", variant="monitoring")
+ self.assertIn(b"green;brightness;150", ts.osc_payload(record))
+ self.store.repaint(self.owner)
+ self.assertEqual(self.paints[-1]["variant"], "monitoring")
+ self.assertEqual(self.store.row(self.owner)["color"], "green")
+
+ def test_ledger_discovery_rejects_reused_pid_and_nested_agent(self):
+ self.rows[124] = ts.Process(124, 123, "??", self.process.started, "/bin/node")
+ self.rows[125] = ts.Process(125, 123, "??", self.process.started, "/bin/codex")
+ self.rows[126] = ts.Process(126, 125, "??", self.process.started, "/bin/node")
+ path = self.root / ".claude/tickets/events.jsonl"
+ path.parent.mkdir(parents=True)
+ def event(pid, seconds, ticket):
+ return {"id": ticket, "type": "create", "correlation_id": f"create-abc-{pid}-xyz",
+ "ts": dt.datetime.fromtimestamp(self.owner.epoch + seconds, dt.timezone.utc).isoformat()}
+ events = [event(124, -1, "TK-1-old"), event(124, 10, "TK-2-current"),
+ event(126, 20, "TK-3-child"), event(999, 30, "TK-4-unrelated")]
+ path.write_text("\n".join(json.dumps(e) for e in events))
+ found, known = ts.tickets.discover(self.root, self.rows, ts.owners(self.rows), ts.ancestors, argv={})
+ self.assertEqual(found["ttys010"]["id"], "TK-2")
+ self.assertEqual(len(known), 4)
+
def test_all_colors_write_canonical_and_identical_mirrors(self):
for color in ts.COLORS:
r = self.store.set(self.owner, color, "" if color == "none" else "Task")
@@ -111,6 +180,25 @@ class StatusTests(unittest.TestCase):
self.assertEqual(row["status_source"], "legacy_conflict")
self.assertFalse(self.store.path(self.owner).exists())
+ def test_repaint_never_adopts_unbound_legacy_state(self):
+ self.legacy("codex", "🟢 fresh but unbound")
+ with self.assertRaises(ts.StatusError):
+ self.store.repaint(self.owner)
+ self.assertFalse(self.paints)
+
+ def test_concurrent_writers_keep_the_newest_event_and_valid_json(self):
+ import concurrent.futures
+ def update(sequence):
+ try:
+ self.store.set(self.owner, "green", str(sequence), issued_ns=sequence)
+ except ts.StatusError as exc:
+ self.assertIn("Obsolete status update", str(exc))
+ with concurrent.futures.ThreadPoolExecutor(max_workers=6) as pool:
+ list(pool.map(update, [6, 2, 5, 1, 3, 4]))
+ record, reason = self.store.load(self.owner)
+ self.assertEqual(reason, "canonical")
+ self.assertEqual(record["label"], "6")
+
def test_scan_survives_terminal_api_outage_and_drops_dead_sessions(self):
self.store.set(self.owner, "purple", "Approval needed")
result = ts.scan(self.store, sessions={})
diff --git a/ticket_binding.py b/ticket_binding.py
new file mode 100644
index 0000000..e8fa5c2
--- /dev/null
+++ b/ticket_binding.py
@@ -0,0 +1,79 @@
+"""Read the existing ticket ledger; never create a second ticket database."""
+import datetime as dt
+import json
+import re
+import subprocess
+
+SHORT = re.compile(r"^TK-\d+(?=$|-)", re.I)
+CID = re.compile(r"^(?:assign|create|action)-[a-z0-9]+-(\d+)-[a-z0-9]+$")
+
+
+def short(value):
+ match = SHORT.match(str(value))
+ return match.group().upper() if match else ""
+
+
+def timestamp(value):
+ try:
+ return dt.datetime.fromisoformat(value.replace("Z", "+00:00")).timestamp()
+ except (ValueError, AttributeError):
+ return 0
+
+
+def discover(root, rows, live, chain, argv=None):
+ """Attribute events only to a currently live main agent's own MCP process."""
+ known, claims, actions = set(), {}, {}
+ descendants = {}
+ for pid, p in rows.items():
+ nearest = next((a for a in chain(pid, rows) if a.runtime), None)
+ if nearest and live.get(nearest.tty) == nearest.owner():
+ descendants[pid] = (nearest.owner(), timestamp_start(p.started))
+ try:
+ stream = (root / ".claude/tickets/events.jsonl").open()
+ except OSError:
+ return {}, known
+ with stream:
+ for line in stream:
+ try:
+ event = json.loads(line)
+ except ValueError:
+ continue
+ ticket = short(event.get("id", ""))
+ if not ticket:
+ continue
+ if event.get("type") == "create":
+ known.add(ticket)
+ match = CID.fullmatch(event.get("correlation_id", ""))
+ if not match or int(match[1]) not in descendants:
+ continue
+ owner, born = descendants[int(match[1])]
+ at = timestamp(event.get("ts"))
+ if at < max(owner.epoch, born):
+ continue
+ target = claims if event.get("type") in ("assign", "create") else actions
+ if at > target.get(owner.tty, {}).get("at", 0):
+ target[owner.tty] = {"id": ticket, "at": at,
+ "source": "ticket_ledger", "correlation_id": match[0]}
+ result = {tty: claims.get(tty, actions.get(tty)) for tty in live}
+ # A unique ticket in the main process launch command is a conservative fallback.
+ if argv is None and live:
+ output = subprocess.run(["ps", "-p", ",".join(str(o.pid) for o in live.values()),
+ "-o", "pid=,args="], capture_output=True, text=True,
+ timeout=8)
+ argv = {}
+ for line in output.stdout.splitlines():
+ parts = line.strip().split(None, 1)
+ if len(parts) == 2:
+ argv[int(parts[0])] = parts[1]
+ for tty, owner in live.items():
+ if result[tty]:
+ continue
+ ids = set(re.findall(r"\bTK-\d+\b", (argv or {}).get(owner.pid, ""), re.I))
+ if len(ids) == 1 and next(iter(ids)).upper() in known:
+ result[tty] = {"id": next(iter(ids)).upper(), "at": owner.epoch,
+ "source": "main_process_argument"}
+ return {tty: value for tty, value in result.items() if value}, known
+
+
+def timestamp_start(value):
+ return dt.datetime.strptime(value, "%a %b %d %H:%M:%S %Y").timestamp()
diff --git a/verification/install/after/.agents/skills/color/scripts/color.sh b/verification/install/after/.agents/skills/color/scripts/color.sh
new file mode 100644
index 0000000..dfe4040
--- /dev/null
+++ b/verification/install/after/.agents/skills/color/scripts/color.sh
@@ -0,0 +1,170 @@
+#!/usr/bin/env bash
+# /color — give this Claude Code session a fresh, visually-distinct color and
+# apply it to the terminal tab so parallel Claude sessions are tellable apart.
+#
+# Mechanism: Claude Code's Bash tool runs detached with NO controlling tty
+# (verified: `tty` => "not a tty", /dev/tty => "device not configured"), so
+# OSC escapes on stdout/stderr never reach the terminal. The working path is
+# to locate the `claude` process's own pty by walking process ancestry and
+# write the OSC sequence straight to /dev/ttysNNN. OSC color/title codes are
+# non-destructive (no cursor moves, no cell writes) so this is glitch-safe.
+#
+# Colors: golden-angle (137.508 deg) hue rotation => every session maximally
+# far from the last on the hue wheel, never repeating. iTerm2 gets a tab tint;
+# every terminal (incl. macOS Terminal.app) gets an emoji + name tab title.
+set -u
+
+STATE_DIR="${HOME}/.claude"
+STATE="${STATE_DIR}/color-session-index"
+mkdir -p "$STATE_DIR"
+
+idx=0
+[[ -f "$STATE" ]] && idx="$(cat "$STATE" 2>/dev/null)"
+[[ "$idx" =~ ^[0-9]+$ ]] || idx=0
+session=$(( idx + 1 ))
+printf '%s\n' "$session" > "$STATE"
+
+# golden-angle hue -> HSL(90% sat, 58% light) -> RGB, pure awk (no python/flock dep)
+read -r R G B HUE < <(awk -v idx="$idx" '
+ function absf(v){ return v<0 ? -v : v }
+ BEGIN{
+ hue = idx*137.508; hue = hue - int(hue/360)*360
+ s=0.90; l=0.58
+ c=(1-absf(2*l-1))*s
+ hp=hue/60.0
+ m2=hp - int(hp/2)*2
+ x=c*(1-absf(m2-1))
+ if(hp<1){r=c;g=x;b=0} else if(hp<2){r=x;g=c;b=0}
+ else if(hp<3){r=0;g=c;b=x} else if(hp<4){r=0;g=x;b=c}
+ else if(hp<5){r=x;g=0;b=c} else {r=c;g=0;b=x}
+ m=l-c/2
+ printf "%d %d %d %d", int((r+m)*255+0.5),int((g+m)*255+0.5),int((b+m)*255+0.5),int(hue)
+ }')
+
+# hue -> coarse name + nearest emoji square
+if (( HUE < 20 )); then NAME=Red; EMOJI=🟥
+elif (( HUE < 45 )); then NAME=Orange; EMOJI=🟧
+elif (( HUE < 70 )); then NAME=Amber; EMOJI=🟨
+elif (( HUE < 150 )); then NAME=Green; EMOJI=🟩
+elif (( HUE < 215 )); then NAME=Teal; EMOJI=🟦
+elif (( HUE < 255 )); then NAME=Blue; EMOJI=🟦
+elif (( HUE < 290 )); then NAME=Indigo; EMOJI=🟪
+elif (( HUE < 330 )); then NAME=Violet; EMOJI=🟪
+else NAME=Magenta; EMOJI=🟥
+fi
+
+# locate the claude process's controlling pty by walking the ancestry
+tty_path=""; p=$$
+for _ in 1 2 3 4 5 6 7 8 9 10; do
+ line="$(ps -o ppid=,tty= -p "$p" 2>/dev/null)" || break
+ set -- $line
+ pp="${1:-}"; tt="${2:-}"
+ [[ -z "$pp" ]] && break
+ if [[ "$tt" == ttys* ]]; then tty_path="/dev/$tt"; break; fi
+ p="$pp"
+done
+
+# Recall the session TOPIC (the user's first prompt) so re-coloring mid-session
+# does NOT clobber the topic with the color name — the color is the tint, the
+# text stays the topic. color-title.sh already wrote the topic onto the tmux
+# window name (with automatic-rename off), so read it straight back from there.
+# Ignore our own color-name placeholder ("<emoji> Indigo #359").
+topic=""
+if [[ -n "${TMUX_PANE:-}" ]] && command -v tmux >/dev/null 2>&1; then
+ cur="$(tmux display-message -p -t "$TMUX_PANE" '#{window_name}' 2>/dev/null)"
+ if [[ -n "$cur" && ! "$cur" =~ (Red|Orange|Amber|Green|Teal|Blue|Indigo|Violet|Magenta)\ \#[0-9]+$ ]]; then
+ topic="$cur"
+ fi
+fi
+
+# Tab/window TEXT: prefer the topic; until the first prompt sets one, show just
+# the emoji so the tab is visibly initialised without shouting the color name.
+if [[ -n "$topic" ]]; then label="$topic"; else label="${EMOJI}"; fi
+hex=$(printf '#%02X%02X%02X' "$R" "$G" "$B")
+
+# Detect tmux. Inside tmux, OSC escapes are swallowed and never reach
+# iTerm2's tab bar — so the user sees no color "at top". Surface the color
+# on tmux's own window/pane instead.
+inside_tmux=0
+[[ -n "${TMUX:-}" ]] && inside_tmux=1
+if (( ! inside_tmux )) && command -v tmux >/dev/null 2>&1; then
+ p2=$$
+ for _ in 1 2 3 4 5 6 7 8 9 10; do
+ line2="$(ps -o ppid=,comm= -p "$p2" 2>/dev/null)" || break
+ set -- $line2
+ pp2="${1:-}"; cm2="${2:-}"
+ [[ -z "$pp2" ]] && break
+ case "$cm2" in *tmux*) inside_tmux=1; break;; esac
+ p2="$pp2"
+ done
+fi
+
+# tmux swallows the DCS-wrapped iTerm2 tab-color OSC unless passthrough is on.
+# Default is OFF, so ensure it on every run — this is what makes the tab header
+# actually tint (otherwise the escape is dropped and the tab shows no color).
+if (( inside_tmux )) && command -v tmux >/dev/null 2>&1; then
+ tmux set -g allow-passthrough on 2>/dev/null || true
+fi
+
+applied=0
+
+if [[ -n "$tty_path" && -w "$tty_path" ]]; then
+ if (( inside_tmux )); then
+ # Inside tmux, raw OSC is consumed by tmux. With allow-passthrough on,
+ # DCS-wrap (ESC P tmux ; <inner-with-doubled-ESC> ESC \) lets the
+ # iTerm2 tab-tint + title OSC reach the outer terminal.
+ {
+ printf '\033Ptmux;\033\033]6;1;bg;red;brightness;%d\007\033\\' "$R"
+ printf '\033Ptmux;\033\033]6;1;bg;green;brightness;%d\007\033\\' "$G"
+ printf '\033Ptmux;\033\033]6;1;bg;blue;brightness;%d\007\033\\' "$B"
+ printf '\033Ptmux;\033\033]1;%s\007\033\\' "$label"
+ printf '\033Ptmux;\033\033]2;%s\007\033\\' "$label"
+ } > "$tty_path" 2>/dev/null
+ else
+ {
+ printf '\033]6;1;bg;red;brightness;%d\007' "$R"
+ printf '\033]6;1;bg;green;brightness;%d\007' "$G"
+ printf '\033]6;1;bg;blue;brightness;%d\007' "$B"
+ printf '\033]1;%s\007' "$label"
+ printf '\033]2;%s\007' "$label"
+ } > "$tty_path" 2>/dev/null
+ fi
+ applied=1
+fi
+
+# tmux indicator path — recolors the tmux window name + current pane border
+# so the color surfaces in tmux's status bar regardless of outer terminal.
+# Window TEXT = topic if captured, else the color label until first prompt.
+if [[ -n "$topic" ]]; then winname="$topic"; else winname="${EMOJI} ${NAME} #${session}"; fi
+if (( inside_tmux )) && command -v tmux >/dev/null 2>&1; then
+ # scope to THIS pane via $TMUX_PANE — without it, plain `rename-window`
+ # targets the active window of the session, which is almost never the
+ # window the new claude session is actually running in.
+ tgt="${TMUX_PANE:-}"
+ if [[ -n "$tgt" ]]; then
+ # keep the topic sticky against tmux automatic-rename
+ [[ -n "$topic" ]] && tmux set-window-option -q -t "$tgt" automatic-rename off 2>/dev/null || true
+ tmux rename-window -t "$tgt" "$winname" 2>/dev/null || true
+ tmux set-window-option -q -t "$tgt" window-status-current-style "bg=${hex},fg=#000000,bold" 2>/dev/null || true
+ tmux set-option -p -q -t "$tgt" pane-active-border-style "fg=${hex},bold" 2>/dev/null || true
+ tmux set-option -p -q -t "$tgt" pane-border-style "fg=${hex}" 2>/dev/null || true
+ else
+ [[ -n "$topic" ]] && tmux set-window-option -q automatic-rename off 2>/dev/null || true
+ tmux rename-window "$winname" 2>/dev/null || true
+ tmux set-window-option -q window-status-current-style "bg=${hex},fg=#000000,bold" 2>/dev/null || true
+ tmux set-option -p -q pane-active-border-style "fg=${hex},bold" 2>/dev/null || true
+ tmux set-option -p -q pane-border-style "fg=${hex}" 2>/dev/null || true
+ fi
+ applied=1
+fi
+
+if (( applied )); then
+ suffix=""
+ (( inside_tmux )) && suffix=" · tmux window+pane recolored"
+ echo "/color → ${NAME} ${EMOJI} rgb(${R},${G},${B}) ${hex} · session #${session}${suffix}"
+else
+ echo "/color → ${NAME} ${EMOJI} (session #${session}) — terminal pty not found, color not applied"
+fi
+
+# Shared terminal status follows the cosmetic session color.
+python3 "$HOME/Projects/terminal-status/terminal_status.py" start --quiet || true
diff --git a/verification/install/after/.agents/skills/color/scripts/repaint-dot.sh b/verification/install/after/.agents/skills/color/scripts/repaint-dot.sh
new file mode 100644
index 0000000..e9b2cfe
--- /dev/null
+++ b/verification/install/after/.agents/skills/color/scripts/repaint-dot.sh
@@ -0,0 +1,3 @@
+#!/usr/bin/env bash
+# Rendering only: cannot overwrite a gate, refresh its age, or undo clear.
+exec python3 "$HOME/Projects/terminal-status/terminal_status.py" repaint --quiet
diff --git a/verification/install/after/.agents/skills/color/scripts/semantic-dot.sh b/verification/install/after/.agents/skills/color/scripts/semantic-dot.sh
new file mode 100644
index 0000000..0229210
--- /dev/null
+++ b/verification/install/after/.agents/skills/color/scripts/semantic-dot.sh
@@ -0,0 +1,7 @@
+#!/usr/bin/env bash
+# Existing Codex signature: state R G B emoji default-label [label|flag].
+set -eu
+color="${1:?state required}"
+default_label="${6:?default label required}"
+shift 6
+exec bash "$HOME/Projects/terminal-status/integrations/dot.sh" codex "$color" "${1:-$default_label}"
diff --git a/verification/install/after/.claude/hooks/dot-floor.sh b/verification/install/after/.claude/hooks/dot-floor.sh
new file mode 100644
index 0000000..1de2cbf
--- /dev/null
+++ b/verification/install/after/.claude/hooks/dot-floor.sh
@@ -0,0 +1,4 @@
+#!/usr/bin/env bash
+# Stop hooks preserve deliberate status. Unknown is never guessed green.
+cat >/dev/null 2>&1 || true
+python3 "$HOME/Projects/terminal-status/terminal_status.py" repaint --quiet || true
diff --git a/verification/install/after/.claude/skills/allcolordots/allcolordots.sh b/verification/install/after/.claude/skills/allcolordots/allcolordots.sh
new file mode 100644
index 0000000..53698a3
--- /dev/null
+++ b/verification/install/after/.claude/skills/allcolordots/allcolordots.sh
@@ -0,0 +1,114 @@
+#!/usr/bin/env bash
+# /allcolordots — scan EVERY active terminal's color dot + label in one shot,
+# and (new) present a clickable single-window ROSTER per color that JUMPS focus
+# to any tab — the "gather my yellows in one place" navigator (path A).
+#
+# Status comes from the shared session-bound terminal-status engine.
+# Labels and dot files are presentation; exact main agent ownership is required.
+#
+# Usage:
+# allcolordots.sh # grouped human table (orange > purple > yellow > green > pink)
+# allcolordots.sh --json # JSON array: [{tty,color,label,live,pid}] (navbar app)
+# allcolordots.sh --live # table, but only ACTIVE (live) terminals
+# allcolordots.sh --yellow # numbered ROSTER of just the 🟡 needs-direction tabs
+# allcolordots.sh --only <color> # numbered roster of one color (yellow|orange|purple|green|pink)
+# allcolordots.sh jump <ttyNNN|N> # focus that tab (N = row# from the last roster)
+set -u
+set -o pipefail
+# Emoji glob-matching (case *🟡*) needs a UTF-8 ctype; some launch contexts pass
+# LC_CTYPE= empty which silently breaks the match -> every dot reads "none".
+export LANG="${LANG:-en_US.UTF-8}" LC_ALL="en_US.UTF-8"
+DOTS="$HOME/.claude/tab-dots"
+ROSTER_CACHE="$DOTS/.last-roster"
+
+color_of(){ case "$1" in *🟢*) echo green;; *🟡*) echo yellow;; *🟣*) echo purple;; *🟠*) echo orange;; *🩷*) echo pink;; *) echo none;; esac; }
+emoji_of(){ case "$1" in orange) echo 🟠;; purple) echo 🟣;; yellow) echo 🟡;; green) echo 🟢;; pink) echo 🩷;; *) echo ⚪;; esac; }
+
+# ---- JUMP: focus the tab whose tty matches (select + activate; NOT a reparent) ----
+jump_to(){
+ local arg="$1" tty=""
+ # index into the last roster?
+ if printf '%s' "$arg" | grep -qE '^[0-9]+$' && [ -f "$ROSTER_CACHE" ]; then
+ tty="$(awk -F'\t' -v n="$arg" 'NR==n{print $1}' "$ROSTER_CACHE")"
+ fi
+ [ -z "$tty" ] && tty="$arg"
+ case "$tty" in /dev/*) :;; ttys*) tty="/dev/$tty";; *) tty="/dev/$tty";; esac
+ osascript 2>&1 <<OSA
+tell application "iTerm2"
+ repeat with w in windows
+ repeat with t in tabs of w
+ repeat with s in sessions of t
+ if (tty of s) is "$tty" then
+ select w
+ select t
+ select s
+ activate
+ return "jumped -> $tty"
+ end if
+ end repeat
+ end repeat
+ end repeat
+ return "not found: $tty"
+end tell
+OSA
+}
+
+# ---- Shared state scanner: presentation never overrides canonical state ----
+collect_rows(){
+ python3 "$HOME/Projects/terminal-status/terminal_status.py" scan --tsv
+}
+
+# ---- roster of ONE color: numbered, jump-ready, caches order for `jump N` ----
+print_roster(){
+ local want="$1" rows; rows="$(collect_rows | awk -F'\t' -v c="$want" '$2==c')" || return $?
+ : > "$ROSTER_CACHE"
+ local e; e="$(emoji_of "$want")"
+ local n; n="$(printf '%s\n' "$rows" | grep -c .)"
+ echo "=== $e $want roster ($n) — jump with: allcolordots.sh jump <#|tty> ==="
+ [ "$n" = 0 ] && { echo " (none)"; return 0; }
+ local i=0
+ printf '%s\n' "$rows" | while IFS=$'\t' read -r tty color live pid label; do
+ i=$((i+1))
+ printf '%s\t%s\t%s\n' "$tty" "$label" "$live" >> "$ROSTER_CACHE"
+ local flag=""; [ "$live" = true ] && flag=" ·live"
+ printf ' %2d) %-9s %s%s\n' "$i" "$tty" "$label" "$flag"
+ done
+}
+
+# ---------------- dispatch ----------------
+case "${1:-}" in
+ jump) shift; jump_to "${1:-}"; exit $? ;;
+ --yellow) print_roster yellow; exit $? ;;
+ --only) print_roster "${2:-yellow}"; exit $? ;;
+ --json)
+ collect_rows | python3 -c '
+import sys,json
+out=[]
+for line in sys.stdin:
+ line=line.rstrip("\n")
+ if not line: continue
+ tty,color,live,pid,label=(line.split("\t")+["","","","",""])[:5]
+ out.append({"tty":tty,"color":color,"live":live=="true","pid":pid,"label":label})
+pri={"orange":0,"purple":1,"yellow":2,"green":3,"pink":4,"none":5}
+out.sort(key=lambda r:(pri.get(r["color"],9), r["tty"]))
+print(json.dumps(out))
+'
+ exit $? ;;
+esac
+
+# default / --live : grouped human table
+LIVEONLY=0; [ "${1:-}" = "--live" ] && LIVEONLY=1
+rows="$(collect_rows)" || exit $?
+[ "$LIVEONLY" = 1 ] && rows="$(printf '%s\n' "$rows" | awk -F'\t' '$3=="true"')"
+total="$(printf '%s\n' "$rows" | grep -c . )"
+echo "=== active-terminal dots ($total) ==="
+for c in orange purple yellow green pink none; do
+ block="$(printf '%s\n' "$rows" | awk -F'\t' -v c="$c" '$2==c')"
+ [ -z "$block" ] && continue
+ n="$(printf '%s\n' "$block" | grep -c .)"
+ echo "$(emoji_of "$c") ${c} ($n)"
+ printf '%s\n' "$block" | while IFS=$'\t' read -r tty color live pid label; do
+ flag=""; [ "$live" = true ] && flag=" ·live"
+ printf ' %-9s %s%s\n' "$tty" "$label" "$flag"
+ done
+done
diff --git a/verification/install/after/.claude/skills/color/scripts/color.sh b/verification/install/after/.claude/skills/color/scripts/color.sh
new file mode 100644
index 0000000..9412388
--- /dev/null
+++ b/verification/install/after/.claude/skills/color/scripts/color.sh
@@ -0,0 +1,249 @@
+#!/usr/bin/env bash
+# /color — give this Claude Code session a fresh, visually-distinct color and
+# apply it to the terminal tab so parallel Claude sessions are tellable apart.
+#
+# Mechanism: Claude Code's Bash tool runs detached with NO controlling tty
+# (verified: `tty` => "not a tty", /dev/tty => "device not configured"), so
+# OSC escapes on stdout/stderr never reach the terminal. The working path is
+# to locate the `claude` process's own pty by walking process ancestry and
+# write the OSC sequence straight to /dev/ttysNNN. OSC color/title codes are
+# non-destructive (no cursor moves, no cell writes) so this is glitch-safe.
+#
+# Colors: golden-angle (137.508 deg) hue rotation => every session maximally
+# far from the last on the hue wheel, never repeating. iTerm2 gets a tab tint;
+# every terminal (incl. macOS Terminal.app) gets an emoji + name tab title.
+set -u
+
+# HARD RAIL (2026-09-08): a subagent has no tab of its own — its ancestry resolves to the
+# PARENT session's pty, so painting would corrupt Steve's live pane (the input-field bug). No-op.
+if [ -n "${CLAUDE_CODE_CHILD_SESSION:-}" ]; then
+ echo "/color → subagent context — refusing to paint (would write to the parent session's live pane). no-op."
+ exit 0
+fi
+
+# Streamline / lite mode — skip the AUTOMATIC SessionStart color assignment
+# fleet-wide (the hook calls with no args). Explicit invocations like
+# `/color --all` pass an arg and still run. rm the file to restore auto mode.
+[ -f "$HOME/.claude/hooks-lite" ] && [ $# -eq 0 ] && exit 0
+
+STATE_DIR="${HOME}/.claude"
+STATE="${STATE_DIR}/color-session-index"
+mkdir -p "$STATE_DIR"
+
+idx=0
+[[ -f "$STATE" ]] && idx="$(cat "$STATE" 2>/dev/null)"
+[[ "$idx" =~ ^[0-9]+$ ]] || idx=0
+session=$(( idx + 1 ))
+printf '%s\n' "$session" > "$STATE"
+
+rgb_for_idx() { # idx -> "R G B HUE" via golden-angle HSL(90%,58%)
+ awk -v idx="$1" '
+ function absf(v){ return v<0 ? -v : v }
+ BEGIN{
+ hue = idx*137.508; hue = hue - int(hue/360)*360
+ s=0.90; l=0.58
+ c=(1-absf(2*l-1))*s
+ hp=hue/60.0
+ m2=hp - int(hp/2)*2
+ x=c*(1-absf(m2-1))
+ if(hp<1){r=c;g=x;b=0} else if(hp<2){r=x;g=c;b=0}
+ else if(hp<3){r=0;g=c;b=x} else if(hp<4){r=0;g=x;b=c}
+ else if(hp<5){r=x;g=0;b=c} else {r=c;g=0;b=x}
+ m=l-c/2
+ printf "%d %d %d %d", int((r+m)*255+0.5),int((g+m)*255+0.5),int((b+m)*255+0.5),int(hue)
+ }'
+}
+
+name_for_hue() { # hue -> "Name Emoji"
+ local h="$1"
+ if (( h < 20 )); then echo "Red 🟥"
+ elif (( h < 45 )); then echo "Orange 🟧"
+ elif (( h < 70 )); then echo "Amber 🟨"
+ elif (( h < 150 )); then echo "Green 🟩"
+ elif (( h < 215 )); then echo "Teal 🟦"
+ elif (( h < 255 )); then echo "Blue 🟦"
+ elif (( h < 290 )); then echo "Indigo 🟪"
+ elif (( h < 330 )); then echo "Violet 🟪"
+ else echo "Magenta 🟥"
+ fi
+}
+
+# --all: paint EVERY open tab running a live `claude` session, each with its
+# own distinct golden-angle color. Claude processes report comm=node, so match
+# on the full command line and take each unique controlling tty.
+if [[ "${1:-}" == "--all" ]]; then
+ ttys="$(ps -axo tty=,command= 2>/dev/null \
+ | awk '$1 ~ /^ttys/ && $0 ~ /claude/ && $0 !~ /grep/ {print $1}' \
+ | sort -u)"
+ if [[ -z "$ttys" ]]; then
+ echo "/color --all → no live claude ttys found"
+ exit 0
+ fi
+ n=0
+ for tt in $ttys; do
+ tp="/dev/$tt"
+ [[ -w "$tp" ]] || { echo " $tt → not writable, skipped"; continue; }
+ aidx=$(( session + n ))
+ read -r R G B HUE < <(rgb_for_idx "$aidx")
+ read -r NAME EMOJI < <(name_for_hue "$HUE")
+ # NOTE: do NOT send a leading '6;1;bg;*;default' reset — on iTerm2 it swallows
+ # the color set, leaving the tab default (bug found 2026-09-02, same as pinkdot).
+ # Set RGB directly and write BOTH title channels (]1 icon/tab, ]2 window).
+ {
+ printf '\033]6;1;bg;red;brightness;%d\007' "$R"
+ printf '\033]6;1;bg;green;brightness;%d\007' "$G"
+ printf '\033]6;1;bg;blue;brightness;%d\007' "$B"
+ printf '\033]1;%s\007' "$EMOJI"
+ printf '\033]2;%s\007' "$EMOJI"
+ } > "$tp" 2>/dev/null && echo " $tt → ${NAME} ${EMOJI} rgb(${R},${G},${B})"
+ n=$(( n + 1 ))
+ done
+ printf '%s\n' "$(( session + n ))" > "$STATE"
+ echo "/color --all → painted ${n} tab(s), counter now $(( session + n ))"
+ exit 0
+fi
+
+# golden-angle hue -> HSL(90% sat, 58% light) -> RGB, pure awk (no python/flock dep)
+read -r R G B HUE < <(awk -v idx="$idx" '
+ function absf(v){ return v<0 ? -v : v }
+ BEGIN{
+ hue = idx*137.508; hue = hue - int(hue/360)*360
+ s=0.90; l=0.58
+ c=(1-absf(2*l-1))*s
+ hp=hue/60.0
+ m2=hp - int(hp/2)*2
+ x=c*(1-absf(m2-1))
+ if(hp<1){r=c;g=x;b=0} else if(hp<2){r=x;g=c;b=0}
+ else if(hp<3){r=0;g=c;b=x} else if(hp<4){r=0;g=x;b=c}
+ else if(hp<5){r=x;g=0;b=c} else {r=c;g=0;b=x}
+ m=l-c/2
+ printf "%d %d %d %d", int((r+m)*255+0.5),int((g+m)*255+0.5),int((b+m)*255+0.5),int(hue)
+ }')
+
+# hue -> coarse name + nearest emoji square
+if (( HUE < 20 )); then NAME=Red; EMOJI=🟥
+elif (( HUE < 45 )); then NAME=Orange; EMOJI=🟧
+elif (( HUE < 70 )); then NAME=Amber; EMOJI=🟨
+elif (( HUE < 150 )); then NAME=Green; EMOJI=🟩
+elif (( HUE < 215 )); then NAME=Teal; EMOJI=🟦
+elif (( HUE < 255 )); then NAME=Blue; EMOJI=🟦
+elif (( HUE < 290 )); then NAME=Indigo; EMOJI=🟪
+elif (( HUE < 330 )); then NAME=Violet; EMOJI=🟪
+else NAME=Magenta; EMOJI=🟥
+fi
+
+# locate the claude process's controlling pty by walking the ancestry
+tty_path=""; p=$$
+for _ in 1 2 3 4 5 6 7 8 9 10; do
+ line="$(ps -o ppid=,tty= -p "$p" 2>/dev/null)" || break
+ set -- $line
+ pp="${1:-}"; tt="${2:-}"
+ [[ -z "$pp" ]] && break
+ if [[ "$tt" == ttys* ]]; then tty_path="/dev/$tt"; break; fi
+ p="$pp"
+done
+
+# Recall the session TOPIC (the user's first prompt) so re-coloring mid-session
+# does NOT clobber the topic with the color name — the color is the tint, the
+# text stays the topic. color-title.sh already wrote the topic onto the tmux
+# window name (with automatic-rename off), so read it straight back from there.
+# Ignore our own color-name placeholder ("<emoji> Indigo #359").
+topic=""
+if [[ -n "${TMUX_PANE:-}" ]] && command -v tmux >/dev/null 2>&1; then
+ cur="$(tmux display-message -p -t "$TMUX_PANE" '#{window_name}' 2>/dev/null)"
+ if [[ -n "$cur" && ! "$cur" =~ (Red|Orange|Amber|Green|Teal|Blue|Indigo|Violet|Magenta)\ \#[0-9]+$ ]]; then
+ topic="$cur"
+ fi
+fi
+
+# Tab/window TEXT: prefer the topic; until the first prompt sets one, show just
+# the emoji so the tab is visibly initialised without shouting the color name.
+if [[ -n "$topic" ]]; then label="$topic"; else label="${EMOJI}"; fi
+hex=$(printf '#%02X%02X%02X' "$R" "$G" "$B")
+
+# Detect tmux. Inside tmux, OSC escapes are swallowed and never reach
+# iTerm2's tab bar — so the user sees no color "at top". Surface the color
+# on tmux's own window/pane instead.
+inside_tmux=0
+[[ -n "${TMUX:-}" ]] && inside_tmux=1
+if (( ! inside_tmux )) && command -v tmux >/dev/null 2>&1; then
+ p2=$$
+ for _ in 1 2 3 4 5 6 7 8 9 10; do
+ line2="$(ps -o ppid=,comm= -p "$p2" 2>/dev/null)" || break
+ set -- $line2
+ pp2="${1:-}"; cm2="${2:-}"
+ [[ -z "$pp2" ]] && break
+ case "$cm2" in *tmux*) inside_tmux=1; break;; esac
+ p2="$pp2"
+ done
+fi
+
+# tmux swallows the DCS-wrapped iTerm2 tab-color OSC unless passthrough is on.
+# Default is OFF, so ensure it on every run — this is what makes the tab header
+# actually tint (otherwise the escape is dropped and the tab shows no color).
+if (( inside_tmux )) && command -v tmux >/dev/null 2>&1; then
+ tmux set -g allow-passthrough on 2>/dev/null || true
+fi
+
+applied=0
+
+if [[ -n "$tty_path" && -w "$tty_path" ]]; then
+ if (( inside_tmux )); then
+ # Inside tmux, raw OSC is consumed by tmux. With allow-passthrough on,
+ # DCS-wrap (ESC P tmux ; <inner-with-doubled-ESC> ESC \) lets the
+ # iTerm2 tab-tint + title OSC reach the outer terminal.
+ {
+ printf '\033Ptmux;\033\033]6;1;bg;red;brightness;%d\007\033\\' "$R"
+ printf '\033Ptmux;\033\033]6;1;bg;green;brightness;%d\007\033\\' "$G"
+ printf '\033Ptmux;\033\033]6;1;bg;blue;brightness;%d\007\033\\' "$B"
+ printf '\033Ptmux;\033\033]1;%s\007\033\\' "$label"
+ printf '\033Ptmux;\033\033]2;%s\007\033\\' "$label"
+ } > "$tty_path" 2>/dev/null
+ else
+ {
+ printf '\033]6;1;bg;red;brightness;%d\007' "$R"
+ printf '\033]6;1;bg;green;brightness;%d\007' "$G"
+ printf '\033]6;1;bg;blue;brightness;%d\007' "$B"
+ printf '\033]1;%s\007' "$label"
+ printf '\033]2;%s\007' "$label"
+ } > "$tty_path" 2>/dev/null
+ fi
+ applied=1
+fi
+
+# tmux indicator path — recolors the tmux window name + current pane border
+# so the color surfaces in tmux's status bar regardless of outer terminal.
+# Window TEXT = topic if captured, else the color label until first prompt.
+if [[ -n "$topic" ]]; then winname="$topic"; else winname="${EMOJI} ${NAME} #${session}"; fi
+if (( inside_tmux )) && command -v tmux >/dev/null 2>&1; then
+ # scope to THIS pane via $TMUX_PANE — without it, plain `rename-window`
+ # targets the active window of the session, which is almost never the
+ # window the new claude session is actually running in.
+ tgt="${TMUX_PANE:-}"
+ if [[ -n "$tgt" ]]; then
+ # keep the topic sticky against tmux automatic-rename
+ [[ -n "$topic" ]] && tmux set-window-option -q -t "$tgt" automatic-rename off 2>/dev/null || true
+ tmux rename-window -t "$tgt" "$winname" 2>/dev/null || true
+ tmux set-window-option -q -t "$tgt" window-status-current-style "bg=${hex},fg=#000000,bold" 2>/dev/null || true
+ tmux set-option -p -q -t "$tgt" pane-active-border-style "fg=${hex},bold" 2>/dev/null || true
+ tmux set-option -p -q -t "$tgt" pane-border-style "fg=${hex}" 2>/dev/null || true
+ else
+ [[ -n "$topic" ]] && tmux set-window-option -q automatic-rename off 2>/dev/null || true
+ tmux rename-window "$winname" 2>/dev/null || true
+ tmux set-window-option -q window-status-current-style "bg=${hex},fg=#000000,bold" 2>/dev/null || true
+ tmux set-option -p -q pane-active-border-style "fg=${hex},bold" 2>/dev/null || true
+ tmux set-option -p -q pane-border-style "fg=${hex}" 2>/dev/null || true
+ fi
+ applied=1
+fi
+
+if (( applied )); then
+ suffix=""
+ (( inside_tmux )) && suffix=" · tmux window+pane recolored"
+ echo "/color → ${NAME} ${EMOJI} rgb(${R},${G},${B}) ${hex} · session #${session}${suffix}"
+else
+ echo "/color → ${NAME} ${EMOJI} (session #${session}) — terminal pty not found, color not applied"
+fi
+
+# Shared terminal status follows the cosmetic session color.
+python3 "$HOME/Projects/terminal-status/terminal_status.py" start --quiet || true
diff --git a/verification/install/after/.claude/skills/color/scripts/paint-tab.sh b/verification/install/after/.claude/skills/color/scripts/paint-tab.sh
new file mode 100644
index 0000000..bcf0b11
--- /dev/null
+++ b/verification/install/after/.claude/skills/color/scripts/paint-tab.sh
@@ -0,0 +1,10 @@
+#!/usr/bin/env bash
+# Legacy one-shot painter routed through semantic status.
+set -eu
+engine="$HOME/Projects/terminal-status/terminal_status.py"
+case "${1:-waiting}" in
+ working|green) exec python3 "$engine" set green WORKING --quiet ;;
+ waiting|purple|solid-purple) exec python3 "$engine" set purple GATED --quiet ;;
+ stop) exec python3 "$engine" repaint --quiet ;;
+ *) echo "Unknown paint-tab state" >&2; exit 2 ;;
+esac
diff --git a/verification/install/after/.claude/skills/color/scripts/tab-state.sh b/verification/install/after/.claude/skills/color/scripts/tab-state.sh
new file mode 100644
index 0000000..7836290
--- /dev/null
+++ b/verification/install/after/.claude/skills/color/scripts/tab-state.sh
@@ -0,0 +1,11 @@
+#!/usr/bin/env bash
+# Legacy lifecycle entrypoint: one-shot status, no flashers.
+set -eu
+engine="$HOME/Projects/terminal-status/terminal_status.py"
+case "${1:-}" in
+ red) exec python3 "$engine" set pink "Closing session" --quiet ;;
+ flash-red) exec python3 "$engine" set green COMPACTING --quiet ;;
+ purple|flash-purple|waiting) exec python3 "$engine" set purple GATED --quiet ;;
+ green|flash-green|stop) exec python3 "$engine" repaint --quiet ;;
+ *) echo "Unknown tab-state mode" >&2; exit 2 ;;
+esac
diff --git a/verification/install/after/.claude/skills/color/scripts/working-state.sh b/verification/install/after/.claude/skills/color/scripts/working-state.sh
new file mode 100644
index 0000000..8369e45
--- /dev/null
+++ b/verification/install/after/.claude/skills/color/scripts/working-state.sh
@@ -0,0 +1,10 @@
+#!/usr/bin/env bash
+# One status update per user prompt; no heat colors or background title writers.
+set -eu
+engine="$HOME/Projects/terminal-status/terminal_status.py"
+case "${1:-}" in
+ on) exec python3 "$engine" set green WORKING --quiet ;;
+ off) exec python3 "$engine" repaint --quiet ;;
+ _watch|_idle_watch) exit 0 ;;
+ *) echo "usage: working-state.sh {on|off}" >&2; exit 2 ;;
+esac
diff --git a/verification/install/after/.claude/skills/colordots/paint-lib.sh b/verification/install/after/.claude/skills/colordots/paint-lib.sh
new file mode 100644
index 0000000..22b60a6
--- /dev/null
+++ b/verification/install/after/.claude/skills/colordots/paint-lib.sh
@@ -0,0 +1,12 @@
+#!/usr/bin/env bash
+# Compatibility API. Semantic state and terminal writes share one engine.
+TABDOTS_DIR="$HOME/.claude/tab-dots"
+tabdots_is_subagent() { [ -n "${CLAUDE_CODE_CHILD_SESSION:-}" ]; }
+tabdots_resolve_tty() {
+ python3 "$HOME/Projects/terminal-status/terminal_status.py" current |
+ python3 -c 'import json,sys; print("/dev/"+json.load(sys.stdin)["tty"])'
+}
+tabdots_stop_pulse() { :; } # Current painters never create pulse processes.
+tabdots_paint() {
+ python3 "$HOME/Projects/terminal-status/terminal_status.py" paint-legacy "$@"
+}
diff --git a/verification/install/after/.claude/skills/dot/current.sh b/verification/install/after/.claude/skills/dot/current.sh
new file mode 100644
index 0000000..5c0a81a
--- /dev/null
+++ b/verification/install/after/.claude/skills/dot/current.sh
@@ -0,0 +1,3 @@
+#!/usr/bin/env bash
+set -eu
+exec python3 "$HOME/Projects/terminal-status/terminal_status.py" status
diff --git a/verification/install/after/.claude/skills/greendot/greendot.sh b/verification/install/after/.claude/skills/greendot/greendot.sh
new file mode 100644
index 0000000..797b6eb
--- /dev/null
+++ b/verification/install/after/.claude/skills/greendot/greendot.sh
@@ -0,0 +1,3 @@
+#!/usr/bin/env bash
+# Shared semantic status; see Projects/terminal-status/README.md.
+exec bash "$HOME/Projects/terminal-status/integrations/dot.sh" claude green "$@"
diff --git a/verification/install/after/.claude/skills/lightgreendot/lightgreendot.sh b/verification/install/after/.claude/skills/lightgreendot/lightgreendot.sh
new file mode 100644
index 0000000..e8a9cf9
--- /dev/null
+++ b/verification/install/after/.claude/skills/lightgreendot/lightgreendot.sh
@@ -0,0 +1,9 @@
+#!/usr/bin/env bash
+# Green monitoring variant with a scheduled check/deadline.
+set -eu
+engine="$HOME/Projects/terminal-status/terminal_status.py"
+case "${1:-}" in
+ --off) exec python3 "$engine" clear ;;
+ --all) exec python3 "$engine" set green MONITORING --monitoring --all claude ;;
+ *) exec python3 "$engine" set green "${1:-MONITORING}" --monitoring ;;
+esac
diff --git a/verification/install/after/.claude/skills/orangedot/orangedot.sh b/verification/install/after/.claude/skills/orangedot/orangedot.sh
new file mode 100644
index 0000000..21f6ba6
--- /dev/null
+++ b/verification/install/after/.claude/skills/orangedot/orangedot.sh
@@ -0,0 +1,3 @@
+#!/usr/bin/env bash
+# Shared semantic status; see Projects/terminal-status/README.md.
+exec bash "$HOME/Projects/terminal-status/integrations/dot.sh" claude orange "$@"
diff --git a/verification/install/after/.claude/skills/pinkdot/pinkdot.sh b/verification/install/after/.claude/skills/pinkdot/pinkdot.sh
new file mode 100644
index 0000000..b81ecc5
--- /dev/null
+++ b/verification/install/after/.claude/skills/pinkdot/pinkdot.sh
@@ -0,0 +1,3 @@
+#!/usr/bin/env bash
+# Shared semantic status; see Projects/terminal-status/README.md.
+exec bash "$HOME/Projects/terminal-status/integrations/dot.sh" claude pink "$@"
diff --git a/verification/install/after/.claude/skills/purpledot/purpledot.sh b/verification/install/after/.claude/skills/purpledot/purpledot.sh
new file mode 100644
index 0000000..9f26d48
--- /dev/null
+++ b/verification/install/after/.claude/skills/purpledot/purpledot.sh
@@ -0,0 +1,3 @@
+#!/usr/bin/env bash
+# Shared semantic status; see Projects/terminal-status/README.md.
+exec bash "$HOME/Projects/terminal-status/integrations/dot.sh" claude purple "$@"
diff --git a/verification/install/after/.claude/skills/tab-ticket/scripts/tab-ticket-set.sh b/verification/install/after/.claude/skills/tab-ticket/scripts/tab-ticket-set.sh
new file mode 100644
index 0000000..660d549
--- /dev/null
+++ b/verification/install/after/.claude/skills/tab-ticket/scripts/tab-ticket-set.sh
@@ -0,0 +1,5 @@
+#!/usr/bin/env bash
+# Ticket binding is tied to the actual main agent's PID, start time, and tty.
+set -eu
+[[ "${1:-}" == "--apply" ]] && shift
+exec python3 "$HOME/Projects/terminal-status/terminal_status.py" ticket "${1:?TK number required}"
diff --git a/verification/install/after/.claude/skills/tab-ticket/scripts/tab-ticket-title.sh b/verification/install/after/.claude/skills/tab-ticket/scripts/tab-ticket-title.sh
new file mode 100644
index 0000000..9c05047
--- /dev/null
+++ b/verification/install/after/.claude/skills/tab-ticket/scripts/tab-ticket-title.sh
@@ -0,0 +1,4 @@
+#!/usr/bin/env bash
+# Existing launchd renderer; read-only, no independent state or background loop.
+set -eu
+exec python3 "$HOME/Projects/terminal-status/terminal_status.py" headers
diff --git a/verification/install/after/.claude/skills/yellowdot/yellowdot.sh b/verification/install/after/.claude/skills/yellowdot/yellowdot.sh
new file mode 100644
index 0000000..12340de
--- /dev/null
+++ b/verification/install/after/.claude/skills/yellowdot/yellowdot.sh
@@ -0,0 +1,3 @@
+#!/usr/bin/env bash
+# Shared semantic status; see Projects/terminal-status/README.md.
+exec bash "$HOME/Projects/terminal-status/integrations/dot.sh" claude yellow "$@"
diff --git a/verification/install/before/.agents/skills/color/scripts/color.sh b/verification/install/before/.agents/skills/color/scripts/color.sh
new file mode 100644
index 0000000..1cab0d7
--- /dev/null
+++ b/verification/install/before/.agents/skills/color/scripts/color.sh
@@ -0,0 +1,167 @@
+#!/usr/bin/env bash
+# /color — give this Claude Code session a fresh, visually-distinct color and
+# apply it to the terminal tab so parallel Claude sessions are tellable apart.
+#
+# Mechanism: Claude Code's Bash tool runs detached with NO controlling tty
+# (verified: `tty` => "not a tty", /dev/tty => "device not configured"), so
+# OSC escapes on stdout/stderr never reach the terminal. The working path is
+# to locate the `claude` process's own pty by walking process ancestry and
+# write the OSC sequence straight to /dev/ttysNNN. OSC color/title codes are
+# non-destructive (no cursor moves, no cell writes) so this is glitch-safe.
+#
+# Colors: golden-angle (137.508 deg) hue rotation => every session maximally
+# far from the last on the hue wheel, never repeating. iTerm2 gets a tab tint;
+# every terminal (incl. macOS Terminal.app) gets an emoji + name tab title.
+set -u
+
+STATE_DIR="${HOME}/.claude"
+STATE="${STATE_DIR}/color-session-index"
+mkdir -p "$STATE_DIR"
+
+idx=0
+[[ -f "$STATE" ]] && idx="$(cat "$STATE" 2>/dev/null)"
+[[ "$idx" =~ ^[0-9]+$ ]] || idx=0
+session=$(( idx + 1 ))
+printf '%s\n' "$session" > "$STATE"
+
+# golden-angle hue -> HSL(90% sat, 58% light) -> RGB, pure awk (no python/flock dep)
+read -r R G B HUE < <(awk -v idx="$idx" '
+ function absf(v){ return v<0 ? -v : v }
+ BEGIN{
+ hue = idx*137.508; hue = hue - int(hue/360)*360
+ s=0.90; l=0.58
+ c=(1-absf(2*l-1))*s
+ hp=hue/60.0
+ m2=hp - int(hp/2)*2
+ x=c*(1-absf(m2-1))
+ if(hp<1){r=c;g=x;b=0} else if(hp<2){r=x;g=c;b=0}
+ else if(hp<3){r=0;g=c;b=x} else if(hp<4){r=0;g=x;b=c}
+ else if(hp<5){r=x;g=0;b=c} else {r=c;g=0;b=x}
+ m=l-c/2
+ printf "%d %d %d %d", int((r+m)*255+0.5),int((g+m)*255+0.5),int((b+m)*255+0.5),int(hue)
+ }')
+
+# hue -> coarse name + nearest emoji square
+if (( HUE < 20 )); then NAME=Red; EMOJI=🟥
+elif (( HUE < 45 )); then NAME=Orange; EMOJI=🟧
+elif (( HUE < 70 )); then NAME=Amber; EMOJI=🟨
+elif (( HUE < 150 )); then NAME=Green; EMOJI=🟩
+elif (( HUE < 215 )); then NAME=Teal; EMOJI=🟦
+elif (( HUE < 255 )); then NAME=Blue; EMOJI=🟦
+elif (( HUE < 290 )); then NAME=Indigo; EMOJI=🟪
+elif (( HUE < 330 )); then NAME=Violet; EMOJI=🟪
+else NAME=Magenta; EMOJI=🟥
+fi
+
+# locate the claude process's controlling pty by walking the ancestry
+tty_path=""; p=$$
+for _ in 1 2 3 4 5 6 7 8 9 10; do
+ line="$(ps -o ppid=,tty= -p "$p" 2>/dev/null)" || break
+ set -- $line
+ pp="${1:-}"; tt="${2:-}"
+ [[ -z "$pp" ]] && break
+ if [[ "$tt" == ttys* ]]; then tty_path="/dev/$tt"; break; fi
+ p="$pp"
+done
+
+# Recall the session TOPIC (the user's first prompt) so re-coloring mid-session
+# does NOT clobber the topic with the color name — the color is the tint, the
+# text stays the topic. color-title.sh already wrote the topic onto the tmux
+# window name (with automatic-rename off), so read it straight back from there.
+# Ignore our own color-name placeholder ("<emoji> Indigo #359").
+topic=""
+if [[ -n "${TMUX_PANE:-}" ]] && command -v tmux >/dev/null 2>&1; then
+ cur="$(tmux display-message -p -t "$TMUX_PANE" '#{window_name}' 2>/dev/null)"
+ if [[ -n "$cur" && ! "$cur" =~ (Red|Orange|Amber|Green|Teal|Blue|Indigo|Violet|Magenta)\ \#[0-9]+$ ]]; then
+ topic="$cur"
+ fi
+fi
+
+# Tab/window TEXT: prefer the topic; until the first prompt sets one, show just
+# the emoji so the tab is visibly initialised without shouting the color name.
+if [[ -n "$topic" ]]; then label="$topic"; else label="${EMOJI}"; fi
+hex=$(printf '#%02X%02X%02X' "$R" "$G" "$B")
+
+# Detect tmux. Inside tmux, OSC escapes are swallowed and never reach
+# iTerm2's tab bar — so the user sees no color "at top". Surface the color
+# on tmux's own window/pane instead.
+inside_tmux=0
+[[ -n "${TMUX:-}" ]] && inside_tmux=1
+if (( ! inside_tmux )) && command -v tmux >/dev/null 2>&1; then
+ p2=$$
+ for _ in 1 2 3 4 5 6 7 8 9 10; do
+ line2="$(ps -o ppid=,comm= -p "$p2" 2>/dev/null)" || break
+ set -- $line2
+ pp2="${1:-}"; cm2="${2:-}"
+ [[ -z "$pp2" ]] && break
+ case "$cm2" in *tmux*) inside_tmux=1; break;; esac
+ p2="$pp2"
+ done
+fi
+
+# tmux swallows the DCS-wrapped iTerm2 tab-color OSC unless passthrough is on.
+# Default is OFF, so ensure it on every run — this is what makes the tab header
+# actually tint (otherwise the escape is dropped and the tab shows no color).
+if (( inside_tmux )) && command -v tmux >/dev/null 2>&1; then
+ tmux set -g allow-passthrough on 2>/dev/null || true
+fi
+
+applied=0
+
+if [[ -n "$tty_path" && -w "$tty_path" ]]; then
+ if (( inside_tmux )); then
+ # Inside tmux, raw OSC is consumed by tmux. With allow-passthrough on,
+ # DCS-wrap (ESC P tmux ; <inner-with-doubled-ESC> ESC \) lets the
+ # iTerm2 tab-tint + title OSC reach the outer terminal.
+ {
+ printf '\033Ptmux;\033\033]6;1;bg;red;brightness;%d\007\033\\' "$R"
+ printf '\033Ptmux;\033\033]6;1;bg;green;brightness;%d\007\033\\' "$G"
+ printf '\033Ptmux;\033\033]6;1;bg;blue;brightness;%d\007\033\\' "$B"
+ printf '\033Ptmux;\033\033]1;%s\007\033\\' "$label"
+ printf '\033Ptmux;\033\033]2;%s\007\033\\' "$label"
+ } > "$tty_path" 2>/dev/null
+ else
+ {
+ printf '\033]6;1;bg;red;brightness;%d\007' "$R"
+ printf '\033]6;1;bg;green;brightness;%d\007' "$G"
+ printf '\033]6;1;bg;blue;brightness;%d\007' "$B"
+ printf '\033]1;%s\007' "$label"
+ printf '\033]2;%s\007' "$label"
+ } > "$tty_path" 2>/dev/null
+ fi
+ applied=1
+fi
+
+# tmux indicator path — recolors the tmux window name + current pane border
+# so the color surfaces in tmux's status bar regardless of outer terminal.
+# Window TEXT = topic if captured, else the color label until first prompt.
+if [[ -n "$topic" ]]; then winname="$topic"; else winname="${EMOJI} ${NAME} #${session}"; fi
+if (( inside_tmux )) && command -v tmux >/dev/null 2>&1; then
+ # scope to THIS pane via $TMUX_PANE — without it, plain `rename-window`
+ # targets the active window of the session, which is almost never the
+ # window the new claude session is actually running in.
+ tgt="${TMUX_PANE:-}"
+ if [[ -n "$tgt" ]]; then
+ # keep the topic sticky against tmux automatic-rename
+ [[ -n "$topic" ]] && tmux set-window-option -q -t "$tgt" automatic-rename off 2>/dev/null || true
+ tmux rename-window -t "$tgt" "$winname" 2>/dev/null || true
+ tmux set-window-option -q -t "$tgt" window-status-current-style "bg=${hex},fg=#000000,bold" 2>/dev/null || true
+ tmux set-option -p -q -t "$tgt" pane-active-border-style "fg=${hex},bold" 2>/dev/null || true
+ tmux set-option -p -q -t "$tgt" pane-border-style "fg=${hex}" 2>/dev/null || true
+ else
+ [[ -n "$topic" ]] && tmux set-window-option -q automatic-rename off 2>/dev/null || true
+ tmux rename-window "$winname" 2>/dev/null || true
+ tmux set-window-option -q window-status-current-style "bg=${hex},fg=#000000,bold" 2>/dev/null || true
+ tmux set-option -p -q pane-active-border-style "fg=${hex},bold" 2>/dev/null || true
+ tmux set-option -p -q pane-border-style "fg=${hex}" 2>/dev/null || true
+ fi
+ applied=1
+fi
+
+if (( applied )); then
+ suffix=""
+ (( inside_tmux )) && suffix=" · tmux window+pane recolored"
+ echo "/color → ${NAME} ${EMOJI} rgb(${R},${G},${B}) ${hex} · session #${session}${suffix}"
+else
+ echo "/color → ${NAME} ${EMOJI} (session #${session}) — terminal pty not found, color not applied"
+fi
diff --git a/verification/install/before/.agents/skills/color/scripts/repaint-dot.sh b/verification/install/before/.agents/skills/color/scripts/repaint-dot.sh
new file mode 100644
index 0000000..c8596dd
--- /dev/null
+++ b/verification/install/before/.agents/skills/color/scripts/repaint-dot.sh
@@ -0,0 +1,36 @@
+#!/usr/bin/env bash
+set -u
+
+tty_path=""
+process_id=$$
+for _ in 1 2 3 4 5 6 7 8 9 10; do
+ line="$(ps -o ppid=,tty= -p "$process_id" 2>/dev/null)" || break
+ set -- $line
+ parent_id="${1:-}"
+ tty_name="${2:-}"
+ [[ -z "$parent_id" ]] && break
+ if [[ "$tty_name" == ttys* ]]; then tty_path="/dev/$tty_name"; break; fi
+ process_id="$parent_id"
+done
+[[ -n "$tty_path" ]] || exit 0
+
+state_file="${HOME}/.codex/tab-dots/$(basename "$tty_path").dot"
+[[ -f "$state_file" ]] || exit 0
+title="$(cat "$state_file")"
+case "$title" in
+ 🟢*) rgb="0 200 83" ;;
+ 🟡*) rgb="255 204 0" ;;
+ 🟣*) rgb="148 0 211" ;;
+ 🟠*) rgb="255 140 0" ;;
+ 🩷*) rgb="255 105 180" ;;
+ *) exit 0 ;;
+esac
+set -- $rgb
+{
+ printf '\033]6;1;bg;red;brightness;%d\007' "$1"
+ printf '\033]6;1;bg;green;brightness;%d\007' "$2"
+ printf '\033]6;1;bg;blue;brightness;%d\007' "$3"
+ printf '\033]1;%s\007' "$title"
+ printf '\033]2;%s\007' "$title"
+ printf '\033]1337;SetBadgeFormat=%s\a' "$(printf '%s' "$title" | base64 | tr -d '\n')"
+} > "$tty_path" 2>/dev/null
diff --git a/verification/install/before/.agents/skills/color/scripts/semantic-dot.sh b/verification/install/before/.agents/skills/color/scripts/semantic-dot.sh
new file mode 100644
index 0000000..5451693
--- /dev/null
+++ b/verification/install/before/.agents/skills/color/scripts/semantic-dot.sh
@@ -0,0 +1,70 @@
+#!/usr/bin/env bash
+set -u
+
+STATE="${1:?state required}"
+R="${2:?red required}"
+G="${3:?green required}"
+B="${4:?blue required}"
+DOT="${5:?dot required}"
+DEFAULT_LABEL="${6:?default label required}"
+shift 6
+
+paint() {
+ local tty_path="$1" label="${2:-$DEFAULT_LABEL}" off="${3:-0}"
+ [[ -w "$tty_path" ]] || return 1
+ local state_file="${HOME}/.codex/tab-dots/$(basename "$tty_path").dot"
+ if [[ "$off" == "1" ]]; then
+ { printf '\033]6;1;bg;*;default\007'; printf '\033]1337;SetBadgeFormat=\a'; } > "$tty_path" 2>/dev/null
+ rm -f "$state_file" 2>/dev/null
+ return 0
+ fi
+ local title="$DOT $label"
+ {
+ printf '\033]6;1;bg;red;brightness;%d\007' "$R"
+ printf '\033]6;1;bg;green;brightness;%d\007' "$G"
+ printf '\033]6;1;bg;blue;brightness;%d\007' "$B"
+ printf '\033]1;%s\007' "$title"
+ printf '\033]2;%s\007' "$title"
+ } > "$tty_path" 2>/dev/null
+ mkdir -p "${HOME}/.codex/tab-dots"
+ printf '%s' "$title" > "$state_file"
+ printf '\033]1337;SetBadgeFormat=%s\a' "$(printf '%s' "$title" | base64 | tr -d '\n')" > "$tty_path" 2>/dev/null
+}
+
+if [[ "${1:-}" == "--all" ]]; then
+ ttys="$(ps -axo tty=,comm=,command= 2>/dev/null | awk '$1 ~ /^ttys/ && $3 ~ /(^|\/)codex$/ {print $1}' | sort -u)"
+ [[ -z "$ttys" ]] && { echo "/${STATE}dot --all → no live Codex ttys"; exit 0; }
+ count=0
+ for tty_name in $ttys; do
+ paint "/dev/$tty_name" "$DEFAULT_LABEL" 0 && count=$((count + 1))
+ done
+ echo "/${STATE}dot --all → marked ${count} Codex tab(s) $DOT $DEFAULT_LABEL"
+ exit 0
+fi
+
+OFF=0
+LABEL="${1:-$DEFAULT_LABEL}"
+[[ "${1:-}" == "--off" ]] && OFF=1
+
+tty_path=""
+process_id=$$
+for _ in 1 2 3 4 5 6 7 8 9 10; do
+ line="$(ps -o ppid=,tty= -p "$process_id" 2>/dev/null)" || break
+ set -- $line
+ parent_id="${1:-}"
+ tty_name="${2:-}"
+ [[ -z "$parent_id" ]] && break
+ if [[ "$tty_name" == ttys* ]]; then tty_path="/dev/$tty_name"; break; fi
+ process_id="$parent_id"
+done
+
+if [[ -z "$tty_path" ]]; then
+ echo "/${STATE}dot → could not resolve this Codex session tty"
+ exit 1
+fi
+
+if [[ "$OFF" == "1" ]]; then
+ paint "$tty_path" "" 1 && echo "/${STATE}dot --off → cleared $tty_path"
+else
+ paint "$tty_path" "$LABEL" 0 && echo "/${STATE}dot → $tty_path $DOT $LABEL rgb(${R},${G},${B})"
+fi
diff --git a/verification/install/before/.claude/hooks/dot-floor.sh b/verification/install/before/.claude/hooks/dot-floor.sh
new file mode 100644
index 0000000..f53ec69
--- /dev/null
+++ b/verification/install/before/.claude/hooks/dot-floor.sh
@@ -0,0 +1,40 @@
+#!/usr/bin/env bash
+# dot-floor.sh — Stop-hook DOT FLOOR: guarantee this session's iTerm2 tab always shows a dot.
+#
+# Fires at the end of every turn (settings.json Stop hook). It is a SAFETY NET, not a judge:
+# • If this tab already has a dot set (any color) → do NOTHING. The model's deliberate
+# /dot choice (green/yellow/purple/orange/pink) is respected and never overridden here.
+# • If this tab has NO dot (blank) → paint GREEN ("WORKING"). This closes the "no dot" gap
+# (the exact bug greendot was born for) without trying to auto-guess the semantic color —
+# purple/yellow are persistent semantic states only the model can judge, so a per-turn
+# heuristic must NOT touch them.
+#
+# Zero model cost (no block / no extra turn). Always exits 0 — a Stop hook must never break the
+# session. Resolves the tab the same way the dot scripts do: walk process ancestry to the ttysNNN
+# (the hook runs as a child of the `claude` process, so the walk reaches the controlling pty).
+set -u
+
+# Drain stdin (Stop hook delivers JSON: session_id, transcript_path, stop_hook_active, cwd).
+# We don't need it for the floor — read-and-ignore so the pipe never blocks.
+cat >/dev/null 2>&1 || true
+
+# Resolve THIS session's controlling pty via ancestry (identical walk to greendot.sh / current.sh).
+tty_path=""; p=$$
+for _ in 1 2 3 4 5 6 7 8 9 10 11 12; do
+ line="$(ps -o ppid=,tty= -p "$p" 2>/dev/null)" || break
+ set -- $line
+ pp="${1:-}"; tt="${2:-}"
+ [ -z "$pp" ] && break
+ case "$tt" in ttys*) tty_path="/dev/$tt"; break;; esac
+ p="$pp"
+done
+[ -z "$tty_path" ] && exit 0 # no ttysNNN in ancestry (launchd/pipe context) → nothing to paint
+
+dotfile="${HOME}/.claude/tab-dots/$(basename "$tty_path").dot"
+
+# FLOOR: only act when the tab is blank (no dot file, or empty). Respect any existing dot.
+if [ ! -s "$dotfile" ]; then
+ bash "${HOME}/.claude/skills/greendot/greendot.sh" >/dev/null 2>&1 || true
+fi
+
+exit 0
diff --git a/verification/install/before/.claude/skills/allcolordots/allcolordots.sh b/verification/install/before/.claude/skills/allcolordots/allcolordots.sh
new file mode 100644
index 0000000..a599ab2
--- /dev/null
+++ b/verification/install/before/.claude/skills/allcolordots/allcolordots.sh
@@ -0,0 +1,196 @@
+#!/usr/bin/env bash
+# /allcolordots — scan EVERY active terminal's color dot + label in one shot,
+# and (new) present a clickable single-window ROSTER per color that JUMPS focus
+# to any tab — the "gather my yellows in one place" navigator (path A).
+#
+# Prefer semantic LIVE iTerm2 names. If Codex overwrites the dot, recover the
+# newest valid ~/.claude or ~/.codex/tab-dots record from this agent lifetime.
+# "live" = a `claude`/`codex` process is running on that tty.
+#
+# Usage:
+# allcolordots.sh # grouped human table (orange > purple > yellow > green > pink)
+# allcolordots.sh --json # JSON array: [{tty,color,label,live,pid}] (navbar app)
+# allcolordots.sh --live # table, but only ACTIVE (live) terminals
+# allcolordots.sh --yellow # numbered ROSTER of just the 🟡 needs-direction tabs
+# allcolordots.sh --only <color> # numbered roster of one color (yellow|orange|purple|green|pink)
+# allcolordots.sh jump <ttyNNN|N> # focus that tab (N = row# from the last roster)
+set -u
+# Emoji glob-matching (case *🟡*) needs a UTF-8 ctype; some launch contexts pass
+# LC_CTYPE= empty which silently breaks the match -> every dot reads "none".
+export LANG="${LANG:-en_US.UTF-8}" LC_ALL="en_US.UTF-8"
+DOTS="$HOME/.claude/tab-dots"
+CODEX_DOTS="$HOME/.codex/tab-dots"
+ROSTER_CACHE="$DOTS/.last-roster"
+
+color_of(){ case "$1" in *🟢*) echo green;; *🟡*) echo yellow;; *🟣*) echo purple;; *🟠*) echo orange;; *🩷*) echo pink;; *) echo none;; esac; }
+emoji_of(){ case "$1" in orange) echo 🟠;; purple) echo 🟣;; yellow) echo 🟡;; green) echo 🟢;; pink) echo 🩷;; *) echo ⚪;; esac; }
+
+# Codex rewrites its title while working. Recover its semantic state only from
+# this tty's files written during the current agent lifetime; old tty numbers
+# get reused. Read both painters' stores and choose by mtime, never by color.
+saved_label(){
+ [ -n "${2:-}" ] || return 0
+ python3 - "$1" "$2" "$DOTS" "$CODEX_DOTS" <<'PY'
+import datetime, pathlib, re, subprocess, sys
+tty, pid, *directories = sys.argv[1:]
+if not re.fullmatch(r"ttys[0-9]+", tty) or not pid.isdigit():
+ sys.exit(0)
+try:
+ started = subprocess.check_output(
+ ["ps", "-p", pid, "-o", "lstart="], text=True,
+ stderr=subprocess.DEVNULL).strip()
+ started = datetime.datetime.strptime(started, "%a %b %d %H:%M:%S %Y").timestamp()
+except (ValueError, OSError, subprocess.CalledProcessError):
+ sys.exit(0)
+eligible = []
+for directory in directories:
+ try:
+ path = pathlib.Path(directory) / (tty + ".dot")
+ stamp = path.stat().st_mtime
+ label = path.read_text(encoding="utf-8").strip()
+ except (OSError, UnicodeError):
+ continue
+ if (stamp >= started and label.startswith(("🟢", "🟡", "🟣", "🟠", "🩷"))
+ and not any(ord(c) < 32 or ord(c) == 127 for c in label)):
+ eligible.append((stamp, label))
+if eligible:
+ print(max(eligible, key=lambda item: item[0])[1])
+PY
+}
+
+# ---- LIVE session scan via iTerm2 (name carries the dot+label; freshest source) ----
+# Emits: tty<TAB>name (one per session). Falls back silently to .dot files if osascript fails.
+live_sessions(){
+ # NOTE: inside `tell application "iTerm2"`, the word `tab` resolves to iTerm2's
+ # own `tab` object class — NOT the tab character — so `& tab &` emits the literal
+ # string "tab". Use an explicit ASCII 9 for the field separator.
+ osascript 2>/dev/null <<'OSA'
+set SEP to (ASCII character 9)
+tell application "iTerm2"
+ set out to ""
+ repeat with w in windows
+ repeat with t in tabs of w
+ repeat with s in sessions of t
+ set out to out & (tty of s) & SEP & (name of s) & linefeed
+ end repeat
+ end repeat
+ end repeat
+ return out
+end tell
+OSA
+}
+
+# ---- JUMP: focus the tab whose tty matches (select + activate; NOT a reparent) ----
+jump_to(){
+ local arg="$1" tty=""
+ # index into the last roster?
+ if printf '%s' "$arg" | grep -qE '^[0-9]+$' && [ -f "$ROSTER_CACHE" ]; then
+ tty="$(awk -F'\t' -v n="$arg" 'NR==n{print $1}' "$ROSTER_CACHE")"
+ fi
+ [ -z "$tty" ] && tty="$arg"
+ case "$tty" in /dev/*) :;; ttys*) tty="/dev/$tty";; *) tty="/dev/$tty";; esac
+ osascript 2>&1 <<OSA
+tell application "iTerm2"
+ repeat with w in windows
+ repeat with t in tabs of w
+ repeat with s in sessions of t
+ if (tty of s) is "$tty" then
+ select w
+ select t
+ select s
+ activate
+ return "jumped -> $tty"
+ end if
+ end repeat
+ end repeat
+ end repeat
+ return "not found: $tty"
+end tell
+OSA
+}
+
+# ---- collect normalized rows: tty \t color \t live \t pid \t label ----
+collect_rows(){
+ local live_raw; live_raw="$(live_sessions)"
+ if [ -n "$live_raw" ]; then
+ # LIVE path: iTerm2 names are the label; a claude/codex proc on the tty = live
+ printf '%s\n' "$live_raw" | while IFS=$'\t' read -r tty name; do
+ [ -z "$tty" ] && continue
+ local short="${tty#/dev/}"
+ local color; color="$(color_of "$name")"
+ local pid; pid="$(ps -axo pid=,tty=,command= 2>/dev/null | awk -v t="$short" '$2==t && (/claude/||/codex/) && !/awk/ {print $1; exit}')"
+ local live=false; [ -n "$pid" ] && live=true
+ if [ "$color" = none ] && [ "$live" = true ]; then
+ local saved; saved="$(saved_label "$short" "$pid")"
+ if [ -n "$saved" ]; then name="$saved"; color="$(color_of "$saved")"; fi
+ fi
+ printf '%s\t%s\t%s\t%s\t%s\n' "$short" "$color" "$live" "${pid:-}" "$name"
+ done
+ else
+ # FALLBACK path: persisted .dot files
+ for f in "$DOTS"/*.dot; do
+ [ -f "$f" ] || continue
+ local tty; tty="$(basename "$f" .dot)"
+ local label; label="$(cat "$f" 2>/dev/null)"
+ local color; color="$(color_of "$label")"
+ local pid; pid="$(ps -axo pid=,tty=,command= 2>/dev/null | awk -v t="$tty" '$2==t && (/claude/||/codex/) && !/awk/ {print $1; exit}')"
+ local live=false; [ -n "$pid" ] && live=true
+ printf '%s\t%s\t%s\t%s\t%s\n' "$tty" "$color" "$live" "${pid:-}" "$label"
+ done
+ fi
+}
+
+# ---- roster of ONE color: numbered, jump-ready, caches order for `jump N` ----
+print_roster(){
+ local want="$1" rows; rows="$(collect_rows | awk -F'\t' -v c="$want" '$2==c')"
+ : > "$ROSTER_CACHE"
+ local e; e="$(emoji_of "$want")"
+ local n; n="$(printf '%s\n' "$rows" | grep -c .)"
+ echo "=== $e $want roster ($n) — jump with: allcolordots.sh jump <#|tty> ==="
+ [ "$n" = 0 ] && { echo " (none)"; return 0; }
+ local i=0
+ printf '%s\n' "$rows" | while IFS=$'\t' read -r tty color live pid label; do
+ i=$((i+1))
+ printf '%s\t%s\t%s\n' "$tty" "$label" "$live" >> "$ROSTER_CACHE"
+ local flag=""; [ "$live" = true ] && flag=" ·live"
+ printf ' %2d) %-9s %s%s\n' "$i" "$tty" "$label" "$flag"
+ done
+}
+
+# ---------------- dispatch ----------------
+case "${1:-}" in
+ jump) shift; jump_to "${1:-}"; exit $? ;;
+ --yellow) print_roster yellow; exit 0 ;;
+ --only) print_roster "${2:-yellow}"; exit 0 ;;
+ --json)
+ collect_rows | python3 -c '
+import sys,json
+out=[]
+for line in sys.stdin:
+ line=line.rstrip("\n")
+ if not line: continue
+ tty,color,live,pid,label=(line.split("\t")+["","","","",""])[:5]
+ out.append({"tty":tty,"color":color,"live":live=="true","pid":pid,"label":label})
+pri={"orange":0,"purple":1,"yellow":2,"green":3,"pink":4,"none":5}
+out.sort(key=lambda r:(pri.get(r["color"],9), r["tty"]))
+print(json.dumps(out))
+'
+ exit 0 ;;
+esac
+
+# default / --live : grouped human table
+LIVEONLY=0; [ "${1:-}" = "--live" ] && LIVEONLY=1
+rows="$(collect_rows)"
+[ "$LIVEONLY" = 1 ] && rows="$(printf '%s\n' "$rows" | awk -F'\t' '$3=="true"')"
+total="$(printf '%s\n' "$rows" | grep -c . )"
+echo "=== active-terminal dots ($total) ==="
+for c in orange purple yellow green pink none; do
+ block="$(printf '%s\n' "$rows" | awk -F'\t' -v c="$c" '$2==c')"
+ [ -z "$block" ] && continue
+ n="$(printf '%s\n' "$block" | grep -c .)"
+ echo "$(emoji_of "$c") ${c} ($n)"
+ printf '%s\n' "$block" | while IFS=$'\t' read -r tty color live pid label; do
+ flag=""; [ "$live" = true ] && flag=" ·live"
+ printf ' %-9s %s%s\n' "$tty" "$label" "$flag"
+ done
+done
diff --git a/verification/install/before/.claude/skills/color/scripts/color.sh b/verification/install/before/.claude/skills/color/scripts/color.sh
new file mode 100644
index 0000000..4c76b91
--- /dev/null
+++ b/verification/install/before/.claude/skills/color/scripts/color.sh
@@ -0,0 +1,246 @@
+#!/usr/bin/env bash
+# /color — give this Claude Code session a fresh, visually-distinct color and
+# apply it to the terminal tab so parallel Claude sessions are tellable apart.
+#
+# Mechanism: Claude Code's Bash tool runs detached with NO controlling tty
+# (verified: `tty` => "not a tty", /dev/tty => "device not configured"), so
+# OSC escapes on stdout/stderr never reach the terminal. The working path is
+# to locate the `claude` process's own pty by walking process ancestry and
+# write the OSC sequence straight to /dev/ttysNNN. OSC color/title codes are
+# non-destructive (no cursor moves, no cell writes) so this is glitch-safe.
+#
+# Colors: golden-angle (137.508 deg) hue rotation => every session maximally
+# far from the last on the hue wheel, never repeating. iTerm2 gets a tab tint;
+# every terminal (incl. macOS Terminal.app) gets an emoji + name tab title.
+set -u
+
+# HARD RAIL (2026-09-08): a subagent has no tab of its own — its ancestry resolves to the
+# PARENT session's pty, so painting would corrupt Steve's live pane (the input-field bug). No-op.
+if [ -n "${CLAUDE_CODE_CHILD_SESSION:-}" ]; then
+ echo "/color → subagent context — refusing to paint (would write to the parent session's live pane). no-op."
+ exit 0
+fi
+
+# Streamline / lite mode — skip the AUTOMATIC SessionStart color assignment
+# fleet-wide (the hook calls with no args). Explicit invocations like
+# `/color --all` pass an arg and still run. rm the file to restore auto mode.
+[ -f "$HOME/.claude/hooks-lite" ] && [ $# -eq 0 ] && exit 0
+
+STATE_DIR="${HOME}/.claude"
+STATE="${STATE_DIR}/color-session-index"
+mkdir -p "$STATE_DIR"
+
+idx=0
+[[ -f "$STATE" ]] && idx="$(cat "$STATE" 2>/dev/null)"
+[[ "$idx" =~ ^[0-9]+$ ]] || idx=0
+session=$(( idx + 1 ))
+printf '%s\n' "$session" > "$STATE"
+
+rgb_for_idx() { # idx -> "R G B HUE" via golden-angle HSL(90%,58%)
+ awk -v idx="$1" '
+ function absf(v){ return v<0 ? -v : v }
+ BEGIN{
+ hue = idx*137.508; hue = hue - int(hue/360)*360
+ s=0.90; l=0.58
+ c=(1-absf(2*l-1))*s
+ hp=hue/60.0
+ m2=hp - int(hp/2)*2
+ x=c*(1-absf(m2-1))
+ if(hp<1){r=c;g=x;b=0} else if(hp<2){r=x;g=c;b=0}
+ else if(hp<3){r=0;g=c;b=x} else if(hp<4){r=0;g=x;b=c}
+ else if(hp<5){r=x;g=0;b=c} else {r=c;g=0;b=x}
+ m=l-c/2
+ printf "%d %d %d %d", int((r+m)*255+0.5),int((g+m)*255+0.5),int((b+m)*255+0.5),int(hue)
+ }'
+}
+
+name_for_hue() { # hue -> "Name Emoji"
+ local h="$1"
+ if (( h < 20 )); then echo "Red 🟥"
+ elif (( h < 45 )); then echo "Orange 🟧"
+ elif (( h < 70 )); then echo "Amber 🟨"
+ elif (( h < 150 )); then echo "Green 🟩"
+ elif (( h < 215 )); then echo "Teal 🟦"
+ elif (( h < 255 )); then echo "Blue 🟦"
+ elif (( h < 290 )); then echo "Indigo 🟪"
+ elif (( h < 330 )); then echo "Violet 🟪"
+ else echo "Magenta 🟥"
+ fi
+}
+
+# --all: paint EVERY open tab running a live `claude` session, each with its
+# own distinct golden-angle color. Claude processes report comm=node, so match
+# on the full command line and take each unique controlling tty.
+if [[ "${1:-}" == "--all" ]]; then
+ ttys="$(ps -axo tty=,command= 2>/dev/null \
+ | awk '$1 ~ /^ttys/ && $0 ~ /claude/ && $0 !~ /grep/ {print $1}' \
+ | sort -u)"
+ if [[ -z "$ttys" ]]; then
+ echo "/color --all → no live claude ttys found"
+ exit 0
+ fi
+ n=0
+ for tt in $ttys; do
+ tp="/dev/$tt"
+ [[ -w "$tp" ]] || { echo " $tt → not writable, skipped"; continue; }
+ aidx=$(( session + n ))
+ read -r R G B HUE < <(rgb_for_idx "$aidx")
+ read -r NAME EMOJI < <(name_for_hue "$HUE")
+ # NOTE: do NOT send a leading '6;1;bg;*;default' reset — on iTerm2 it swallows
+ # the color set, leaving the tab default (bug found 2026-09-02, same as pinkdot).
+ # Set RGB directly and write BOTH title channels (]1 icon/tab, ]2 window).
+ {
+ printf '\033]6;1;bg;red;brightness;%d\007' "$R"
+ printf '\033]6;1;bg;green;brightness;%d\007' "$G"
+ printf '\033]6;1;bg;blue;brightness;%d\007' "$B"
+ printf '\033]1;%s\007' "$EMOJI"
+ printf '\033]2;%s\007' "$EMOJI"
+ } > "$tp" 2>/dev/null && echo " $tt → ${NAME} ${EMOJI} rgb(${R},${G},${B})"
+ n=$(( n + 1 ))
+ done
+ printf '%s\n' "$(( session + n ))" > "$STATE"
+ echo "/color --all → painted ${n} tab(s), counter now $(( session + n ))"
+ exit 0
+fi
+
+# golden-angle hue -> HSL(90% sat, 58% light) -> RGB, pure awk (no python/flock dep)
+read -r R G B HUE < <(awk -v idx="$idx" '
+ function absf(v){ return v<0 ? -v : v }
+ BEGIN{
+ hue = idx*137.508; hue = hue - int(hue/360)*360
+ s=0.90; l=0.58
+ c=(1-absf(2*l-1))*s
+ hp=hue/60.0
+ m2=hp - int(hp/2)*2
+ x=c*(1-absf(m2-1))
+ if(hp<1){r=c;g=x;b=0} else if(hp<2){r=x;g=c;b=0}
+ else if(hp<3){r=0;g=c;b=x} else if(hp<4){r=0;g=x;b=c}
+ else if(hp<5){r=x;g=0;b=c} else {r=c;g=0;b=x}
+ m=l-c/2
+ printf "%d %d %d %d", int((r+m)*255+0.5),int((g+m)*255+0.5),int((b+m)*255+0.5),int(hue)
+ }')
+
+# hue -> coarse name + nearest emoji square
+if (( HUE < 20 )); then NAME=Red; EMOJI=🟥
+elif (( HUE < 45 )); then NAME=Orange; EMOJI=🟧
+elif (( HUE < 70 )); then NAME=Amber; EMOJI=🟨
+elif (( HUE < 150 )); then NAME=Green; EMOJI=🟩
+elif (( HUE < 215 )); then NAME=Teal; EMOJI=🟦
+elif (( HUE < 255 )); then NAME=Blue; EMOJI=🟦
+elif (( HUE < 290 )); then NAME=Indigo; EMOJI=🟪
+elif (( HUE < 330 )); then NAME=Violet; EMOJI=🟪
+else NAME=Magenta; EMOJI=🟥
+fi
+
+# locate the claude process's controlling pty by walking the ancestry
+tty_path=""; p=$$
+for _ in 1 2 3 4 5 6 7 8 9 10; do
+ line="$(ps -o ppid=,tty= -p "$p" 2>/dev/null)" || break
+ set -- $line
+ pp="${1:-}"; tt="${2:-}"
+ [[ -z "$pp" ]] && break
+ if [[ "$tt" == ttys* ]]; then tty_path="/dev/$tt"; break; fi
+ p="$pp"
+done
+
+# Recall the session TOPIC (the user's first prompt) so re-coloring mid-session
+# does NOT clobber the topic with the color name — the color is the tint, the
+# text stays the topic. color-title.sh already wrote the topic onto the tmux
+# window name (with automatic-rename off), so read it straight back from there.
+# Ignore our own color-name placeholder ("<emoji> Indigo #359").
+topic=""
+if [[ -n "${TMUX_PANE:-}" ]] && command -v tmux >/dev/null 2>&1; then
+ cur="$(tmux display-message -p -t "$TMUX_PANE" '#{window_name}' 2>/dev/null)"
+ if [[ -n "$cur" && ! "$cur" =~ (Red|Orange|Amber|Green|Teal|Blue|Indigo|Violet|Magenta)\ \#[0-9]+$ ]]; then
+ topic="$cur"
+ fi
+fi
+
+# Tab/window TEXT: prefer the topic; until the first prompt sets one, show just
+# the emoji so the tab is visibly initialised without shouting the color name.
+if [[ -n "$topic" ]]; then label="$topic"; else label="${EMOJI}"; fi
+hex=$(printf '#%02X%02X%02X' "$R" "$G" "$B")
+
+# Detect tmux. Inside tmux, OSC escapes are swallowed and never reach
+# iTerm2's tab bar — so the user sees no color "at top". Surface the color
+# on tmux's own window/pane instead.
+inside_tmux=0
+[[ -n "${TMUX:-}" ]] && inside_tmux=1
+if (( ! inside_tmux )) && command -v tmux >/dev/null 2>&1; then
+ p2=$$
+ for _ in 1 2 3 4 5 6 7 8 9 10; do
+ line2="$(ps -o ppid=,comm= -p "$p2" 2>/dev/null)" || break
+ set -- $line2
+ pp2="${1:-}"; cm2="${2:-}"
+ [[ -z "$pp2" ]] && break
+ case "$cm2" in *tmux*) inside_tmux=1; break;; esac
+ p2="$pp2"
+ done
+fi
+
+# tmux swallows the DCS-wrapped iTerm2 tab-color OSC unless passthrough is on.
+# Default is OFF, so ensure it on every run — this is what makes the tab header
+# actually tint (otherwise the escape is dropped and the tab shows no color).
+if (( inside_tmux )) && command -v tmux >/dev/null 2>&1; then
+ tmux set -g allow-passthrough on 2>/dev/null || true
+fi
+
+applied=0
+
+if [[ -n "$tty_path" && -w "$tty_path" ]]; then
+ if (( inside_tmux )); then
+ # Inside tmux, raw OSC is consumed by tmux. With allow-passthrough on,
+ # DCS-wrap (ESC P tmux ; <inner-with-doubled-ESC> ESC \) lets the
+ # iTerm2 tab-tint + title OSC reach the outer terminal.
+ {
+ printf '\033Ptmux;\033\033]6;1;bg;red;brightness;%d\007\033\\' "$R"
+ printf '\033Ptmux;\033\033]6;1;bg;green;brightness;%d\007\033\\' "$G"
+ printf '\033Ptmux;\033\033]6;1;bg;blue;brightness;%d\007\033\\' "$B"
+ printf '\033Ptmux;\033\033]1;%s\007\033\\' "$label"
+ printf '\033Ptmux;\033\033]2;%s\007\033\\' "$label"
+ } > "$tty_path" 2>/dev/null
+ else
+ {
+ printf '\033]6;1;bg;red;brightness;%d\007' "$R"
+ printf '\033]6;1;bg;green;brightness;%d\007' "$G"
+ printf '\033]6;1;bg;blue;brightness;%d\007' "$B"
+ printf '\033]1;%s\007' "$label"
+ printf '\033]2;%s\007' "$label"
+ } > "$tty_path" 2>/dev/null
+ fi
+ applied=1
+fi
+
+# tmux indicator path — recolors the tmux window name + current pane border
+# so the color surfaces in tmux's status bar regardless of outer terminal.
+# Window TEXT = topic if captured, else the color label until first prompt.
+if [[ -n "$topic" ]]; then winname="$topic"; else winname="${EMOJI} ${NAME} #${session}"; fi
+if (( inside_tmux )) && command -v tmux >/dev/null 2>&1; then
+ # scope to THIS pane via $TMUX_PANE — without it, plain `rename-window`
+ # targets the active window of the session, which is almost never the
+ # window the new claude session is actually running in.
+ tgt="${TMUX_PANE:-}"
+ if [[ -n "$tgt" ]]; then
+ # keep the topic sticky against tmux automatic-rename
+ [[ -n "$topic" ]] && tmux set-window-option -q -t "$tgt" automatic-rename off 2>/dev/null || true
+ tmux rename-window -t "$tgt" "$winname" 2>/dev/null || true
+ tmux set-window-option -q -t "$tgt" window-status-current-style "bg=${hex},fg=#000000,bold" 2>/dev/null || true
+ tmux set-option -p -q -t "$tgt" pane-active-border-style "fg=${hex},bold" 2>/dev/null || true
+ tmux set-option -p -q -t "$tgt" pane-border-style "fg=${hex}" 2>/dev/null || true
+ else
+ [[ -n "$topic" ]] && tmux set-window-option -q automatic-rename off 2>/dev/null || true
+ tmux rename-window "$winname" 2>/dev/null || true
+ tmux set-window-option -q window-status-current-style "bg=${hex},fg=#000000,bold" 2>/dev/null || true
+ tmux set-option -p -q pane-active-border-style "fg=${hex},bold" 2>/dev/null || true
+ tmux set-option -p -q pane-border-style "fg=${hex}" 2>/dev/null || true
+ fi
+ applied=1
+fi
+
+if (( applied )); then
+ suffix=""
+ (( inside_tmux )) && suffix=" · tmux window+pane recolored"
+ echo "/color → ${NAME} ${EMOJI} rgb(${R},${G},${B}) ${hex} · session #${session}${suffix}"
+else
+ echo "/color → ${NAME} ${EMOJI} (session #${session}) — terminal pty not found, color not applied"
+fi
diff --git a/verification/install/before/.claude/skills/color/scripts/paint-tab.sh b/verification/install/before/.claude/skills/color/scripts/paint-tab.sh
new file mode 100644
index 0000000..e4e2f0e
--- /dev/null
+++ b/verification/install/before/.claude/skills/color/scripts/paint-tab.sh
@@ -0,0 +1,94 @@
+#!/usr/bin/env bash
+# paint-tab.sh <state> — tint THIS Claude session's iTerm tab by state.
+# working | green -> SOLID green (Claude is busy) + stop any blinker
+# waiting | purple -> BLINKING purple (turn handed back to Steve — answer
+# needed). A background loop alternates purple<->default
+# until the next `working` call kills it.
+# solid-purple -> solid purple, no blink (internal/manual use)
+# stop -> kill the blinker only
+#
+# Why slot-prefix matching: the color hook/bash subprocess runs detached (tty
+# "??"), so walking process ancestry to find the tab pty is unreliable. And the
+# inherited ITERM_SESSION_ID's UUID half goes STALE on an iTerm reattach. The
+# w#t#p# (window/tab/pane) PREFIX is stable, so we match THAT against each live
+# `claude` process's env to resolve the correct /dev/ttysNNN. Verified 2026-06-26.
+set -u
+
+GREEN=(114 244 52) # busy
+PURPLE=(168 85 247) # awaiting Steve — bright phase
+PURPLE_DIM=(84 42 124) # awaiting Steve — dim phase (PULSE, never blank)
+BLINK_SEC="${PAINT_BLINK_SEC:-0.7}" # cadence of the idle pulse
+
+state="${1:-waiting}"
+
+# --- resolve THIS tab's pty by stable w#t#p# slot prefix --------------------
+[[ -z "${ITERM_SESSION_ID:-}" ]] && exit 0
+prefix="${ITERM_SESSION_ID%%:*}"
+tty=""
+for pid in $(pgrep -x claude 2>/dev/null); do
+ sid="$(ps eww -p "$pid" 2>/dev/null | grep -o 'ITERM_SESSION_ID=[^ ]*' | head -1 | cut -d= -f2-)"
+ [[ "${sid%%:*}" == "$prefix" ]] && tty="/dev/$(ps -o tty= -p "$pid" 2>/dev/null | tr -d ' ')"
+done
+[[ -n "$tty" && -w "$tty" ]] || exit 0
+
+# Per-tab blinker pidfile, keyed by slot prefix so one tab never kills another's.
+RUN_DIR="${HOME}/.claude/working-state"
+mkdir -p "$RUN_DIR" 2>/dev/null || true
+pidfile="${RUN_DIR}/blink-${prefix//[^A-Za-z0-9]/_}.pid"
+
+write_rgb() { # write_rgb <tty> R G B
+ { printf '\033]6;1;bg;red;brightness;%d\007' "$2"
+ printf '\033]6;1;bg;green;brightness;%d\007' "$3"
+ printf '\033]6;1;bg;blue;brightness;%d\007' "$4"
+ } > "$1" 2>/dev/null
+}
+reset_tint() { printf '\033]6;1;bg;*;default\007' > "$1" 2>/dev/null; }
+
+stop_blink() {
+ [[ -f "$pidfile" ]] || return 0
+ kill "$(cat "$pidfile" 2>/dev/null)" 2>/dev/null || true
+ rm -f "$pidfile"
+}
+
+case "$state" in
+ green|working)
+ stop_blink
+ write_rgb "$tty" "${GREEN[@]}"
+ ;;
+ purple|waiting)
+ # blinking purple: stop any prior blinker, then start a fresh detached loop.
+ # tty is resolved HERE (parent) and handed to the loop — once backgrounded
+ # the loop may be reparented and lose the ability to re-resolve it.
+ stop_blink
+ ( trap 'exit 0' TERM INT
+ br="${PURPLE[0]}"; bg="${PURPLE[1]}"; bb="${PURPLE[2]}" # bright
+ dr="${PURPLE_DIM[0]}"; dg="${PURPLE_DIM[1]}"; db="${PURPLE_DIM[2]}" # dim
+ while :; do
+ # PULSE bright purple <-> dim purple — always a color, never blank.
+ { printf '\033]6;1;bg;red;brightness;%d\007' "$br"
+ printf '\033]6;1;bg;green;brightness;%d\007' "$bg"
+ printf '\033]6;1;bg;blue;brightness;%d\007' "$bb"
+ } > "$tty" 2>/dev/null
+ sleep "$BLINK_SEC"
+ { printf '\033]6;1;bg;red;brightness;%d\007' "$dr"
+ printf '\033]6;1;bg;green;brightness;%d\007' "$dg"
+ printf '\033]6;1;bg;blue;brightness;%d\007' "$db"
+ } > "$tty" 2>/dev/null
+ sleep "$BLINK_SEC"
+ done ) >/dev/null 2>&1 &
+ echo $! > "$pidfile"
+ disown 2>/dev/null || true
+ ;;
+ solid-purple)
+ stop_blink
+ write_rgb "$tty" "${PURPLE[@]}"
+ ;;
+ stop)
+ stop_blink
+ reset_tint "$tty"
+ ;;
+ *)
+ write_rgb "$tty" 128 128 128
+ ;;
+esac
+exit 0
diff --git a/verification/install/before/.claude/skills/color/scripts/tab-state.sh b/verification/install/before/.claude/skills/color/scripts/tab-state.sh
new file mode 100644
index 0000000..4007335
--- /dev/null
+++ b/verification/install/before/.claude/skills/color/scripts/tab-state.sh
@@ -0,0 +1,170 @@
+#!/usr/bin/env bash
+# tab-state.sh — drive the session-close visual state machine on the Claude
+# Code terminal tab. States:
+# red solid red — session closing (/cs)
+# flash-red blinking red — compaction in progress (PreCompact hook)
+# flash-orange blinking orange — STOPPED, waiting on a background result
+# (e.g. a background task / DTD panel / long job to land)
+# flash-purple blinking purple — STOPPED & WAITING ON STEVE (Stop hook): the
+# (alias: turn has been handed back and Claude is idle awaiting input.
+# waiting) Cleared by a `stop`/`green`/`red` (or the next UserPromptSubmit).
+# purple solid purple
+# flash-green green flash ×3 then solid green — compaction complete (SessionStart compact)
+# green solid green
+# stop kill any running flasher
+#
+# Reuses /color's mechanism: Claude Code's Bash tool has no controlling tty, so
+# OSC escapes on stdout never reach the terminal. Instead we locate the `claude`
+# process's own pty by walking process ancestry and write the OSC tab-tint code
+# straight to /dev/ttysNNN. OSC color codes are non-destructive (no cursor moves)
+# so writing them — even repeatedly from a background flasher — is glitch-safe.
+set -u
+
+MODE="${1:-}"
+
+# Streamline / lite mode — skip cosmetic tab-state painting fleet-wide, but
+# always allow 'stop' so a running flasher can still be cleaned up. rm to restore.
+case "$MODE" in stop) : ;; *) [ -f "$HOME/.claude/hooks-lite" ] && exit 0 ;; esac
+
+PIDFILE="${HOME}/.claude/tab-flash.pid"
+
+# Walk ancestry from $$ to find the controlling ttys* (same as color.sh).
+resolve_tty() {
+ local p=$$ line pp tt
+ for _ in 1 2 3 4 5 6 7 8 9 10 11 12; do
+ line="$(ps -o ppid=,tty= -p "$p" 2>/dev/null)" || break
+ # shellcheck disable=SC2086
+ set -- $line; pp="${1:-}"; tt="${2:-}"
+ [[ -z "$pp" ]] && break
+ if [[ "$tt" == ttys* ]]; then echo "/dev/$tt"; return 0; fi
+ p="$pp"
+ done
+ return 1
+}
+
+# write_color <tty> <R> <G> <B> — tint the iTerm2 tab (tmux-wrapped if needed)
+write_color() {
+ local tty="$1" R="$2" G="$3" B="$4"
+ [[ -w "$tty" ]] || return 1
+ if [[ -n "${TMUX:-}" ]]; then
+ { printf '\033Ptmux;\033\033]6;1;bg;red;brightness;%d\007\033\\' "$R"
+ printf '\033Ptmux;\033\033]6;1;bg;green;brightness;%d\007\033\\' "$G"
+ printf '\033Ptmux;\033\033]6;1;bg;blue;brightness;%d\007\033\\' "$B"
+ } > "$tty" 2>/dev/null
+ else
+ { printf '\033]6;1;bg;red;brightness;%d\007' "$R"
+ printf '\033]6;1;bg;green;brightness;%d\007' "$G"
+ printf '\033]6;1;bg;blue;brightness;%d\007' "$B"
+ } > "$tty" 2>/dev/null
+ fi
+}
+
+# reset_color <tty> — clear the tab tint back to iTerm2 default (the "off" half
+# of a blink)
+reset_color() {
+ local tty="$1"
+ [[ -w "$tty" ]] || return 1
+ if [[ -n "${TMUX:-}" ]]; then
+ printf '\033Ptmux;\033\033]6;1;bg;*;default\007\033\\' > "$tty" 2>/dev/null
+ else
+ printf '\033]6;1;bg;*;default\007' > "$tty" 2>/dev/null
+ fi
+}
+
+stop_flasher() {
+ if [[ -f "$PIDFILE" ]]; then
+ kill "$(cat "$PIDFILE" 2>/dev/null)" 2>/dev/null || true
+ rm -f "$PIDFILE"
+ fi
+}
+
+RED=(255 0 0)
+GREEN=(0 200 0)
+PURPLE=(148 0 211)
+
+case "$MODE" in
+ red)
+ stop_flasher
+ tty="$(resolve_tty)" || { echo "tab-state: no pty found"; exit 1; }
+ write_color "$tty" "${RED[@]}" && echo "tab → solid RED (closing)"
+ ;;
+ green)
+ stop_flasher
+ tty="$(resolve_tty)" || { echo "tab-state: no pty found"; exit 1; }
+ write_color "$tty" "${GREEN[@]}" && echo "tab → solid GREEN (done)"
+ ;;
+ purple)
+ stop_flasher
+ tty="$(resolve_tty)" || { echo "tab-state: no pty found"; exit 1; }
+ write_color "$tty" "${PURPLE[@]}" && echo "tab → solid PURPLE (stopped, waiting on Steve)"
+ ;;
+ flash-red)
+ tty="$(resolve_tty)" || { echo "tab-state: no pty found"; exit 1; }
+ stop_flasher
+ # Resolve tty in the parent, then hand it to the detached loop — once
+ # backgrounded the loop may be reparented and lose the ancestry walk.
+ ( trap 'exit 0' TERM INT
+ while :; do
+ printf '\033]6;1;bg;red;brightness;255\007\033]6;1;bg;green;brightness;0\007\033]6;1;bg;blue;brightness;0\007' > "$tty" 2>/dev/null
+ sleep 0.5
+ printf '\033]6;1;bg;*;default\007' > "$tty" 2>/dev/null
+ sleep 0.5
+ done ) >/dev/null 2>&1 &
+ echo $! > "$PIDFILE"
+ disown 2>/dev/null || true
+ echo "tab → FLASHING RED (compacting) pid $(cat "$PIDFILE")"
+ ;;
+ flash-orange)
+ tty="$(resolve_tty)" || { echo "tab-state: no pty found"; exit 1; }
+ stop_flasher
+ # STOPPED, waiting on a background result. Blink orange (255,140,0) until
+ # something kills the flasher (a `stop`/`green`/`red`, or work resumes).
+ ( trap 'exit 0' TERM INT
+ while :; do
+ printf '\033]6;1;bg;red;brightness;255\007\033]6;1;bg;green;brightness;140\007\033]6;1;bg;blue;brightness;0\007' > "$tty" 2>/dev/null
+ sleep 0.5
+ printf '\033]6;1;bg;*;default\007' > "$tty" 2>/dev/null
+ sleep 0.5
+ done ) >/dev/null 2>&1 &
+ echo $! > "$PIDFILE"
+ disown 2>/dev/null || true
+ echo "tab → FLASHING ORANGE (stopped, waiting on background result) pid $(cat "$PIDFILE")"
+ ;;
+ flash-purple|waiting)
+ tty="$(resolve_tty)" || { echo "tab-state: no pty found"; exit 1; }
+ stop_flasher
+ # STOPPED & WAITING ON STEVE — the turn was handed back and Claude is idle
+ # awaiting input. Blink purple (148,0,211) until something kills the flasher
+ # (a `stop`/`green`/`red`, or the next UserPromptSubmit when Steve replies).
+ # Resolve tty in the parent, then hand it to the detached loop — once
+ # backgrounded the loop may be reparented and lose the ancestry walk.
+ ( trap 'exit 0' TERM INT
+ while :; do
+ printf '\033]6;1;bg;red;brightness;148\007\033]6;1;bg;green;brightness;0\007\033]6;1;bg;blue;brightness;211\007' > "$tty" 2>/dev/null
+ sleep 0.5
+ printf '\033]6;1;bg;*;default\007' > "$tty" 2>/dev/null
+ sleep 0.5
+ done ) >/dev/null 2>&1 &
+ echo $! > "$PIDFILE"
+ disown 2>/dev/null || true
+ echo "tab → FLASHING PURPLE (stopped, waiting on Steve) pid $(cat "$PIDFILE")"
+ ;;
+ flash-green)
+ stop_flasher
+ tty="$(resolve_tty)" || { echo "tab-state: no pty found"; exit 1; }
+ for _ in 1 2 3; do
+ write_color "$tty" "${GREEN[@]}"; sleep 0.3
+ reset_color "$tty"; sleep 0.3
+ done
+ write_color "$tty" "${GREEN[@]}"
+ echo "tab → GREEN flash ×3 then solid (compaction complete)"
+ ;;
+ stop)
+ stop_flasher
+ echo "tab-state: flasher stopped"
+ ;;
+ *)
+ echo "usage: tab-state.sh {red|green|purple|flash-red|flash-orange|flash-purple|waiting|flash-green|stop}"
+ exit 2
+ ;;
+esac
diff --git a/verification/install/before/.claude/skills/color/scripts/working-state.sh b/verification/install/before/.claude/skills/color/scripts/working-state.sh
new file mode 100644
index 0000000..4de7bb0
--- /dev/null
+++ b/verification/install/before/.claude/skills/color/scripts/working-state.sh
@@ -0,0 +1,219 @@
+#!/usr/bin/env bash
+# working-state.sh — "Claude is working" indicator in the iTerm2 window HEADER.
+#
+# on (UserPromptSubmit hook) prepend a green-square bar to the header title
+# and start a watcher that GROWS the bar and
+# HEATS it (🟩 → 🟨 → 🟧 → 🟥) the longer the
+# turn runs.
+# off (Stop hook) stop the watcher, restore the plain topic title
+#
+# Why the header TITLE TEXT (not tab tint / OSC 9;4 progress): tested live on
+# iTerm2 3.6.10 — it ignores the OSC 9;4 progress bar AND won't background-tint
+# the title bar on single-tab windows. But the title TEXT always renders in the
+# header. So the indicator is a row of colored squares prepended to the title:
+# a growing, heating "progress bar + icon" that's visible no matter the layout.
+#
+# Bash tool has no controlling tty, but HOOKS run in the terminal's context, so
+# the ancestry walk (same as color-title.sh) resolves the pty here.
+set -u
+
+# --- tunables: elapsed seconds → square color + count -----------------------
+T_YELLOW=60 # after this many seconds the bar turns yellow
+T_ORANGE=120 # then orange
+T_RED=180 # then red
+RAMP_STEP=3 # re-paint cadence (s) while working
+IDLE_STEP="${IDLE_BLINK_SEC:-0.7}" # blink cadence (s) while idle/waiting
+
+MODE="${1:-}"
+
+# Streamline / lite mode — one flag makes the working-state indicator no-op
+# fleet-wide (heavy load, autonomous officer-yolo loops, headless jobs). This
+# skips the per-hook pty resolution (ps eww over every claude proc) + the
+# repaint watcher loop — the O(sessions^2) cost that bogs down under many
+# parallel sessions. Existing watchers self-terminate via their owner_pid check.
+# Reversible: rm ~/.claude/hooks-lite. Internal watcher modes are exempt so a
+# running loop can still be told to exit cleanly.
+case "$MODE" in _watch|_idle_watch) : ;; *) [ -f "$HOME/.claude/hooks-lite" ] && exit 0 ;; esac
+
+TITLE_DIR="${HOME}/.claude/color-session-titles"
+RUN_DIR="${HOME}/.claude/working-state"
+mkdir -p "$RUN_DIR"
+
+# bar string for a given elapsed time: heat + growing count
+bar_for() { # echoes the square bar
+ local e="$1" sq n i out=""
+ if (( e < T_YELLOW )); then sq="🟩"; n=$(( e/20 + 1 )); (( n>3 )) && n=3
+ elif (( e < T_ORANGE )); then sq="🟨"; n=4
+ elif (( e < T_RED )); then sq="🟧"; n=5
+ else sq="🟥"; n=6
+ fi
+ for ((i=0;i<n;i++)); do out+="$sq"; done
+ printf '%s' "$out"
+}
+
+# ---------------------------------------------------------------------------
+# internal watcher: _watch <tty> <inside_tmux> <session_id>
+# re-reads the topic file each tick so it picks up color-title.sh's title
+# ---------------------------------------------------------------------------
+if [[ "$MODE" == "_watch" ]]; then
+ tty_path="$2"; inside_tmux="$3"; sid="$4"; owner_pid="${5:-}"
+ set_title() {
+ if (( inside_tmux )); then
+ printf '\033Ptmux;\033\033]1;%s\007\033\\' "$1" > "$tty_path" 2>/dev/null
+ printf '\033Ptmux;\033\033]2;%s\007\033\\' "$1" > "$tty_path" 2>/dev/null
+ else
+ printf '\033]1;%s\007' "$1" > "$tty_path" 2>/dev/null
+ printf '\033]2;%s\007' "$1" > "$tty_path" 2>/dev/null
+ fi
+ }
+ trap 'exit 0' TERM INT
+ elapsed=0
+ while :; do
+ # stop the moment our session dies or the pty is gone — never paint a
+ # REUSED tty (the leaked-watcher prompt-corruption bug).
+ [[ -n "$owner_pid" ]] && ! kill -0 "$owner_pid" 2>/dev/null && exit 0
+ [[ -w "$tty_path" ]] || exit 0
+ topic=""; [[ -f "${TITLE_DIR}/${sid}" ]] && topic="$(cat "${TITLE_DIR}/${sid}" 2>/dev/null)"
+ bar="$(bar_for "$elapsed")"
+ if [[ -n "$topic" ]]; then set_title "${bar} ${topic}"; else set_title "${bar} working…"; fi
+ sleep "$RAMP_STEP"
+ elapsed=$(( elapsed + RAMP_STEP ))
+ done
+fi
+
+# ---------------------------------------------------------------------------
+# internal idle watcher: _idle_watch <tty> <inside_tmux> <session_id>
+# Blinks PURPLE squares in the title while the turn is handed back to Steve.
+# Pulses 🟪🟪🟪 <-> 🟪 so there is ALWAYS at least one purple dot — it blinks
+# but never goes blank. Re-reads the topic each tick like the working watcher.
+# ---------------------------------------------------------------------------
+if [[ "$MODE" == "_idle_watch" ]]; then
+ tty_path="$2"; inside_tmux="$3"; sid="$4"; owner_pid="${5:-}"
+ set_title() {
+ if (( inside_tmux )); then
+ printf '\033Ptmux;\033\033]1;%s\007\033\\' "$1" > "$tty_path" 2>/dev/null
+ printf '\033Ptmux;\033\033]2;%s\007\033\\' "$1" > "$tty_path" 2>/dev/null
+ else
+ printf '\033]1;%s\007' "$1" > "$tty_path" 2>/dev/null
+ printf '\033]2;%s\007' "$1" > "$tty_path" 2>/dev/null
+ fi
+ }
+ trap 'exit 0' TERM INT
+ phase=0
+ while :; do
+ # stop the moment our session dies or the pty is gone — never paint a
+ # REUSED tty (the leaked-watcher prompt-corruption bug).
+ [[ -n "$owner_pid" ]] && ! kill -0 "$owner_pid" 2>/dev/null && exit 0
+ [[ -w "$tty_path" ]] || exit 0
+ topic=""; [[ -f "${TITLE_DIR}/${sid}" ]] && topic="$(cat "${TITLE_DIR}/${sid}" 2>/dev/null)"
+ if (( phase == 0 )); then bar="🟪🟪🟪"; else bar="🟪"; fi
+ if [[ -n "$topic" ]]; then set_title "${bar} ${topic}"; else set_title "${bar} idle…"; fi
+ phase=$(( 1 - phase ))
+ sleep "$IDLE_STEP"
+ done
+fi
+
+# --- main: resolve session + pty -------------------------------------------
+input="$(cat 2>/dev/null || true)"
+session_id="$(printf '%s' "$input" | sed -n 's/.*"session_id"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -1)"
+[[ -z "$session_id" ]] && session_id="default"
+pidfile="${RUN_DIR}/${session_id}.watcher.pid"
+
+# Resolve THIS session's OWN tab pty.
+# PRIMARY: match the stable w#t#p# slot prefix of ITERM_SESSION_ID against each
+# live `claude` process's env. The hook inherits ITERM_SESSION_ID from its claude
+# parent, so this pins the correct tab even across many parallel sessions.
+# The old ancestry walk landed on a NEIGHBOR tab (every session painted someone
+# else's tab) — kept only as a fallback when there's no ITERM_SESSION_ID.
+tty_path=""
+owner_pid=""
+if [[ -n "${ITERM_SESSION_ID:-}" ]]; then
+ _slot="${ITERM_SESSION_ID%%:*}"
+ for _cpid in $(pgrep -x claude 2>/dev/null); do
+ _csid="$(ps eww -p "$_cpid" 2>/dev/null | grep -o 'ITERM_SESSION_ID=[^ ]*' | head -1 | cut -d= -f2-)"
+ if [[ "${_csid%%:*}" == "$_slot" ]]; then
+ _ctty="$(ps -o tty= -p "$_cpid" 2>/dev/null | tr -d ' ')"
+ if [[ "$_ctty" == ttys* ]]; then tty_path="/dev/$_ctty"; owner_pid="$_cpid"; fi
+ fi
+ done
+fi
+if [[ -z "$tty_path" ]]; then
+ p=$$
+ for _ in 1 2 3 4 5 6 7 8 9 10 11 12; do
+ line="$(ps -o ppid=,tty= -p "$p" 2>/dev/null)" || break
+ # shellcheck disable=SC2086
+ set -- $line; pp="${1:-}"; tt="${2:-}"
+ [[ -z "$pp" ]] && break
+ if [[ "$tt" == ttys* ]]; then tty_path="/dev/$tt"; break; fi
+ p="$pp"
+ done
+fi
+[[ -n "$tty_path" && -w "$tty_path" ]] || exit 0
+
+inside_tmux=0
+[[ -n "${TMUX:-}" ]] && inside_tmux=1
+
+set_title() {
+ if (( inside_tmux )); then
+ printf '\033Ptmux;\033\033]1;%s\007\033\\' "$1" > "$tty_path" 2>/dev/null
+ printf '\033Ptmux;\033\033]2;%s\007\033\\' "$1" > "$tty_path" 2>/dev/null
+ else
+ printf '\033]1;%s\007' "$1" > "$tty_path" 2>/dev/null
+ printf '\033]2;%s\007' "$1" > "$tty_path" 2>/dev/null
+ fi
+}
+
+stop_watcher() {
+ [[ -f "$pidfile" ]] || return 0
+ kill "$(cat "$pidfile" 2>/dev/null)" 2>/dev/null || true
+ rm -f "$pidfile" "${pidfile}.owner"
+}
+
+# Reap watchers left behind by DEAD sessions: any pidfile whose recorded OWNER
+# claude PID is gone gets its watcher killed + files removed. Clears leaked
+# painters that survive a crash/kill-9 (no clean Stop hook) and would otherwise
+# keep writing OSC title escapes to a REUSED pty. Only reaps when the owner is
+# recorded AND confirmed dead — never guesses at unknown-owner (fallback-path)
+# watchers, which self-terminate via their own per-tick tty-writable guard.
+reap_orphans() {
+ local pf owner wpid
+ for pf in "${RUN_DIR}"/*.watcher.pid; do
+ [[ -e "$pf" ]] || continue
+ [[ "$pf" == "$pidfile" ]] && continue
+ owner=""; [[ -f "${pf}.owner" ]] && owner="$(cat "${pf}.owner" 2>/dev/null)"
+ [[ -z "$owner" ]] && continue
+ kill -0 "$owner" 2>/dev/null && continue # owner alive → legit, leave it
+ wpid="$(cat "$pf" 2>/dev/null)"
+ [[ -n "$wpid" ]] && kill "$wpid" 2>/dev/null || true
+ rm -f "$pf" "${pf}.owner"
+ done
+}
+
+topic=""; [[ -f "${TITLE_DIR}/${session_id}" ]] && topic="$(cat "${TITLE_DIR}/${session_id}" 2>/dev/null)"
+
+case "$MODE" in
+ on)
+ stop_watcher
+ reap_orphans
+ set_title "🟩 ${topic:-working…}"
+ ( "$0" _watch "$tty_path" "$inside_tmux" "$session_id" "$owner_pid" ) >/dev/null 2>&1 &
+ echo $! > "$pidfile"
+ [[ -n "$owner_pid" ]] && echo "$owner_pid" > "${pidfile}.owner"
+ disown 2>/dev/null || true
+ ;;
+ off)
+ stop_watcher
+ reap_orphans
+ # idle/waiting: blink PURPLE dots in the title (never plain) until next turn
+ set_title "🟪🟪🟪 ${topic:-idle…}"
+ ( "$0" _idle_watch "$tty_path" "$inside_tmux" "$session_id" "$owner_pid" ) >/dev/null 2>&1 &
+ echo $! > "$pidfile"
+ [[ -n "$owner_pid" ]] && echo "$owner_pid" > "${pidfile}.owner"
+ disown 2>/dev/null || true
+ ;;
+ *)
+ echo "usage: working-state.sh {on|off}" >&2
+ exit 2
+ ;;
+esac
+exit 0
diff --git a/verification/install/before/.claude/skills/colordots/paint-lib.sh b/verification/install/before/.claude/skills/colordots/paint-lib.sh
new file mode 100644
index 0000000..0ed9d5e
--- /dev/null
+++ b/verification/install/before/.claude/skills/colordots/paint-lib.sh
@@ -0,0 +1,75 @@
+#!/usr/bin/env bash
+# paint-lib.sh — the ONE safe path for tinting an iTerm2 tab via an OSC write.
+#
+# Fixes the 2026-09-08 "dot payload lands in the Claude input field" bug. Two proven
+# root causes (reproduced in a throwaway iTerm window, not on a live pane):
+#
+# 1. SUBAGENT CROSS-WRITE. The Claude Bash tool runs with no controlling tty, so the
+# old ancestry walk climbed past the tty-less `zsh -c` shells to the OWNING `claude`
+# process's ttysNNN. From the top-level session that resolves to the right pane, but
+# from a SPAWNED SUBAGENT (Agent tool) it resolves to the PARENT session's pty — so a
+# subagent painting a dot wrote straight into Steve's live pane. A subagent has no tab
+# of its own; it must never paint. (CLAUDE_CODE_CHILD_SESSION=1 marks a subagent.)
+#
+# 2. BACKGROUND PULSE LOOP. The dot painters started a background loop that rewrote the
+# tab-color OSC every ~0.11s forever. Those rapid async writes interleave with the
+# Claude TUI's own pty output; a torn/desynced sequence drops its leading ESC]6;
+# framing and iTerm renders the tail ("1;bg;green;brightness;0") as literal text on the
+# Claude screen — exactly the reported symptom. A SINGLE atomic write can't be torn.
+#
+# A clean single OSC write was verified to set the tab color AND to never enter a
+# raw-mode reader's stdin — so painting once, atomically, is both correct and leak-free.
+
+TABDOTS_DIR="${HOME}/.claude/tab-dots"
+
+# True (0) iff running inside a spawned subagent — MUST NOT paint (no tab of its own;
+# any write lands in the parent session's live pane).
+tabdots_is_subagent() { [ -n "${CLAUDE_CODE_CHILD_SESSION:-}" ]; }
+
+# Resolve THIS session's controlling pty by climbing ancestry to the owning `claude`
+# process's ttys. Prints "/dev/ttysNNN" on success. Returns non-zero (and prints nothing)
+# when called from a subagent or when no ttys is found — callers must treat that as "do not paint".
+tabdots_resolve_tty() {
+ tabdots_is_subagent && return 1
+ local p=$$ line pp tt
+ for _ in $(seq 1 12); do
+ line="$(ps -o ppid=,tty= -p "$p" 2>/dev/null)" || break
+ # shellcheck disable=SC2086
+ set -- $line; pp="${1:-}"; tt="${2:-}"
+ [ -z "$pp" ] && break
+ case "$tt" in ttys*) printf '/dev/%s' "$tt"; return 0;; esac
+ p="$pp"
+ done
+ return 1
+}
+
+# Kill any legacy background pulse loop on a tty (the old corruption engine) and drop its pidfile.
+tabdots_stop_pulse() {
+ local tp="$1" pf
+ pf="${TABDOTS_DIR}/$(basename "$tp").pulse.pid"
+ [ -f "$pf" ] && { kill "$(cat "$pf" 2>/dev/null)" 2>/dev/null; rm -f "$pf"; }
+ return 0
+}
+
+# Paint a tab in ONE atomic write() so a concurrent writer can never tear it.
+# $1 tty_path $2 R $3 G $4 B $5 title $6 off(1=reset)
+# No background loop — solid color only. Refuses in a subagent context.
+tabdots_paint() {
+ local tp="$1" R="$2" G="$3" B="$4" title="$5" off="${6:-0}"
+ tabdots_is_subagent && return 2
+ [ -w "$tp" ] || return 1
+ tabdots_stop_pulse "$tp"
+ if [ "$off" = "1" ]; then
+ # Single write: reset tab color + clear badge.
+ printf '\033]6;1;bg;*;default\007\033]1337;SetBadgeFormat=\a' > "$tp" 2>/dev/null
+ rm -f "${TABDOTS_DIR}/$(basename "$tp").dot" 2>/dev/null
+ return 0
+ fi
+ local badge; badge="$(printf '%s' "$title" | base64 | tr -d '\n')"
+ # ONE write(): 3 tab-color OSCs + both title channels + the in-window badge, concatenated.
+ printf '\033]6;1;bg;red;brightness;%d\007\033]6;1;bg;green;brightness;%d\007\033]6;1;bg;blue;brightness;%d\007\033]1;%s\007\033]2;%s\007\033]1337;SetBadgeFormat=%s\a' \
+ "$R" "$G" "$B" "$title" "$title" "$badge" > "$tp" 2>/dev/null
+ mkdir -p "$TABDOTS_DIR"
+ printf '%s' "$title" > "${TABDOTS_DIR}/$(basename "$tp").dot"
+ return 0
+}
diff --git a/verification/install/before/.claude/skills/dot/current.sh b/verification/install/before/.claude/skills/dot/current.sh
new file mode 100644
index 0000000..b0085d5
--- /dev/null
+++ b/verification/install/before/.claude/skills/dot/current.sh
@@ -0,0 +1,25 @@
+#!/usr/bin/env bash
+# /dot helper — print THIS session's controlling tty + its current persisted dot label,
+# so the /dot review can report "was: <X>" and avoid a no-op re-apply.
+# Resolves the tty the same way the dot scripts do (walk process ancestry to the ttysNNN),
+# because the Claude Bash tool has no controlling tty of its own.
+set -u
+tty_path=""; p=$$
+for _ in 1 2 3 4 5 6 7 8 9 10; do
+ line="$(ps -o ppid=,tty= -p "$p" 2>/dev/null)" || break
+ set -- $line
+ pp="${1:-}"; tt="${2:-}"
+ [[ -z "$pp" ]] && break
+ if [[ "$tt" == ttys* ]]; then tty_path="/dev/$tt"; break; fi
+ p="$pp"
+done
+if [[ -z "$tty_path" ]]; then
+ echo "(none) — could not resolve this session's tty"; exit 0
+fi
+base="$(basename "$tty_path")"
+dotfile="${HOME}/.claude/tab-dots/${base}.dot"
+if [[ -f "$dotfile" ]]; then
+ printf '%s %s\n' "$base" "$(cat "$dotfile")"
+else
+ printf '%s (none)\n' "$base"
+fi
diff --git a/verification/install/before/.claude/skills/greendot/greendot.sh b/verification/install/before/.claude/skills/greendot/greendot.sh
new file mode 100644
index 0000000..289f34d
--- /dev/null
+++ b/verification/install/before/.claude/skills/greendot/greendot.sh
@@ -0,0 +1,61 @@
+#!/usr/bin/env bash
+# /greendot — mark a Claude session's iTerm2 tab GREEN = "WORKING — running or monitoring, nothing needed from Steve".
+# A fixed SEMANTIC color (not /color's rotating golden-angle hue): green always means
+# "this session is WORKING or monitoring — nothing is needed from Steve." The default resting state.
+# Steve, 2026-09-02: every window always shows exactly one dot; green closes the "no dot" gap.
+# Sibling of /yellowdot (needs direction), /purpledot (gated memo), /orangedot (paste in terminal),
+# /pinkdot (parked).
+#
+# Mechanism (shared with all dot painters via paint-lib.sh): the Claude Bash tool runs with NO
+# controlling tty, so OSC escapes on stdout never reach the terminal. We resolve the OWNING `claude`
+# process's pty by ancestry and write the OSC straight to /dev/ttysNNN — in a SINGLE atomic write so
+# it can never be torn mid-sequence. Two hard safety rails (see paint-lib.sh header for the proven
+# root cause): (1) refuse to paint from a subagent — a subagent's ancestry resolves to the PARENT
+# session's pty, so painting would corrupt Steve's live pane; (2) no background pulse loop — the old
+# rapid-fire loop is what leaked the OSC payload ("1;bg;green;brightness;0") into the Claude input.
+#
+# Usage:
+# greendot.sh # green-dot THIS session (title "🟢 WORKING")
+# greendot.sh "TK-11098 x3" # green tint + custom title text (🟢 prefixed) — put the TK + count
+# greendot.sh --all # green-dot EVERY live claude tab (rare)
+# greendot.sh --off # clear the tint on THIS session
+set -u
+
+source "${HOME}/.claude/skills/colordots/paint-lib.sh"
+
+# HARD RAIL: a subagent has no tab of its own; painting would write to the parent session's
+# live pane (the input-field-pollution bug). Refuse and no-op.
+if tabdots_is_subagent; then
+ echo "/greendot → subagent context (CLAUDE_CODE_CHILD_SESSION set) — refusing to paint (would write to the parent session's live pane). no-op."
+ exit 0
+fi
+
+# Semantic green — "working / monitoring, nothing needed".
+R=0; G=200; B=83
+DOT="🟢"
+
+# --all: paint every live claude tab green (one atomic write each; no pulse loop).
+if [ "${1:-}" = "--all" ]; then
+ ttys="$(ps -axo tty=,command= 2>/dev/null \
+ | awk '$1 ~ /^ttys/ && $0 ~ /claude/ && $0 !~ /grep/ {print $1}' | sort -u)"
+ [ -z "$ttys" ] && { echo "/greendot --all → no live claude ttys"; exit 0; }
+ n=0
+ for tt in $ttys; do tabdots_paint "/dev/$tt" "$R" "$G" "$B" "$DOT WORKING" 0 && { echo " $tt → 🟢 WORKING"; n=$((n+1)); }; done
+ echo "/greendot --all → marked ${n} tab(s) green (working)"
+ exit 0
+fi
+
+OFF=0; LABEL=""
+[ "${1:-}" = "--off" ] && OFF=1 || LABEL="${1:-}"
+
+tty_path="$(tabdots_resolve_tty)" || {
+ echo "/greendot → could not resolve this session's tty (no ttysNNN in ancestry)"; exit 1
+}
+
+if [ "$OFF" = "1" ]; then
+ tabdots_paint "$tty_path" "$R" "$G" "$B" "" 1 && echo "/greendot --off → cleared tint on $tty_path"
+else
+ title="$DOT ${LABEL:-WORKING}"
+ tabdots_paint "$tty_path" "$R" "$G" "$B" "$title" 0 \
+ && echo "/greendot → $tty_path 🟢 ${LABEL:-WORKING} rgb(${R},${G},${B})"
+fi
diff --git a/verification/install/before/.claude/skills/lightgreendot/lightgreendot.sh b/verification/install/before/.claude/skills/lightgreendot/lightgreendot.sh
new file mode 100644
index 0000000..def2005
--- /dev/null
+++ b/verification/install/before/.claude/skills/lightgreendot/lightgreendot.sh
@@ -0,0 +1,72 @@
+#!/usr/bin/env bash
+# /lightgreendot — mark a Claude session's iTerm2 tab LIGHT GREEN = "MONITORING with a SCHEDULED
+# next-check / DEADLINE". A shade-variant of /greendot (Steve, 2026-09-04): plain green = working/
+# monitoring with no specific wake time; LIGHT green = working/monitoring AND a wake is scheduled at
+# a known time (a ScheduleWakeup / next-monitor). So Steve can glance and see "this one has a deadline".
+# Pair with showing the next-check time 3x inline in the reply (memory: monitor-response-dot-and-3x-time).
+#
+# Mechanism identical to greendot.sh (resolve own pty by ancestry, write OSC straight to /dev/ttysNNN).
+# Usage:
+# lightgreendot.sh "TK-NNNNN · monitoring · next 10:15" # light-green tint + title (put the time!)
+# lightgreendot.sh --all
+# lightgreendot.sh --off
+set -u
+
+# HARD RAIL (2026-09-08): a subagent has no tab of its own — its ancestry resolves to the
+# PARENT session's pty, so painting would corrupt Steve's live pane (the input-field bug). No-op.
+if [ -n "${CLAUDE_CODE_CHILD_SESSION:-}" ]; then
+ echo "/lightgreendot → subagent context — refusing to paint (would write to the parent session's live pane). no-op."
+ exit 0
+fi
+
+# Teal green — "monitoring with a scheduled deadline". Distinct from plain green (0,200,83).
+R=0; G=150; B=136
+DOT="🟢"
+
+paint() { # $1 = tty_path, $2 = label(optional), $3 = off(1=reset)
+ local tp="$1" label="${2:-}" off="${3:-0}"
+ [[ -w "$tp" ]] || return 1
+ if [[ "$off" == "1" ]]; then
+ { printf '\033]6;1;bg;*;default\007'; printf '\033]1337;SetBadgeFormat=\a'; } > "$tp" 2>/dev/null
+ rm -f "${HOME}/.claude/tab-dots/$(basename "$tp").dot" 2>/dev/null
+ return 0
+ fi
+ local title="$DOT ${label:-MONITORING}"
+ {
+ printf '\033]6;1;bg;red;brightness;%d\007' "$R"
+ printf '\033]6;1;bg;green;brightness;%d\007' "$G"
+ printf '\033]6;1;bg;blue;brightness;%d\007' "$B"
+ printf '\033]1;%s\007' "$title"
+ printf '\033]2;%s\007' "$title"
+ } > "$tp" 2>/dev/null
+ mkdir -p "${HOME}/.claude/tab-dots"
+ printf '%s' "$title" > "${HOME}/.claude/tab-dots/$(basename "$tp").dot"
+ printf '\033]1337;SetBadgeFormat=%s\a' "$(printf '%s' "$title" | base64 | tr -d '\n')" > "$tp" 2>/dev/null
+}
+
+if [[ "${1:-}" == "--all" ]]; then
+ ttys="$(ps -axo tty=,command= 2>/dev/null | awk '$1 ~ /^ttys/ && $0 ~ /claude/ && $0 !~ /grep/ {print $1}' | sort -u)"
+ [[ -z "$ttys" ]] && { echo "/lightgreendot --all → no live claude ttys"; exit 0; }
+ n=0; for tt in $ttys; do paint "/dev/$tt" "MONITORING" 0 && { echo " $tt → 🟢 MONITORING"; n=$((n+1)); }; done
+ echo "/lightgreendot --all → marked ${n} tab(s) light green"; exit 0
+fi
+
+OFF=0; LABEL=""
+[[ "${1:-}" == "--off" ]] && OFF=1 || LABEL="${1:-}"
+
+tty_path=""; p=$$
+for _ in 1 2 3 4 5 6 7 8 9 10; do
+ line="$(ps -o ppid=,tty= -p "$p" 2>/dev/null)" || break
+ set -- $line
+ pp="${1:-}"; tt="${2:-}"
+ [[ -z "$pp" ]] && break
+ if [[ "$tt" == ttys* ]]; then tty_path="/dev/$tt"; break; fi
+ p="$pp"
+done
+if [[ -z "$tty_path" ]]; then echo "/lightgreendot → could not resolve this session's tty"; exit 1; fi
+
+if [[ "$OFF" == "1" ]]; then
+ paint "$tty_path" "" 1 && echo "/lightgreendot --off → cleared tint on $tty_path"
+else
+ paint "$tty_path" "$LABEL" 0 && echo "/lightgreendot → $tty_path 🟢(light) ${LABEL:-MONITORING} rgb(${R},${G},${B})"
+fi
diff --git a/verification/install/before/.claude/skills/orangedot/orangedot.sh b/verification/install/before/.claude/skills/orangedot/orangedot.sh
new file mode 100644
index 0000000..78e332a
--- /dev/null
+++ b/verification/install/before/.claude/skills/orangedot/orangedot.sh
@@ -0,0 +1,98 @@
+#!/usr/bin/env bash
+# /orangedot — mark a Claude session's iTerm2 tab ORANGE = "PASTE WAITING — Steve action required".
+# A fixed SEMANTIC color (not /color's rotating golden-angle hue): orange always means
+# "this session has handed Steve a `!` paste (or another one-step approval) and is waiting on it."
+# Steve, 2026-09-02: "when we see pastes or paste this: use one of the color dots to notify terminal".
+# Sibling of /pinkdot (pink = parked/idle) and /yellowdot (legacy idle). Lets Steve scan a wall of
+# tabs and see exactly which windows are blocked on HIM (orange) vs parked (pink) vs working (/color).
+#
+# Mechanism (identical to pinkdot.sh / color.sh): the Claude Bash tool runs with NO controlling
+# tty, so OSC escapes on stdout never reach the terminal. We resolve the `claude` process's own
+# pty by walking process ancestry and write the OSC straight to /dev/ttysNNN. OSC color/title
+# codes are non-destructive (no cursor moves) so this is glitch-safe.
+#
+# Usage:
+# orangedot.sh # orange-dot THIS session (title "🟠 PASTE waiting")
+# orangedot.sh "TK-11098 x3" # orange tint + custom title text (🟠 prefixed) — put the TK + count
+# orangedot.sh --all # orange-dot EVERY live claude tab (rare; fleet-wide "all blocked on Steve")
+# orangedot.sh --off # clear the tint on THIS session (paste was run → back to /color or /pinkdot)
+set -u
+
+# HARD RAIL (2026-09-08): a subagent has no tab of its own — its ancestry resolves to the
+# PARENT session's pty, so painting would corrupt Steve's live pane (the input-field bug). No-op.
+if [ -n "${CLAUDE_CODE_CHILD_SESSION:-}" ]; then
+ echo "/orangedot → subagent context — refusing to paint (would write to the parent session's live pane). no-op."
+ exit 0
+fi
+
+# Semantic orange — "action required from Steve". Distinct from pink (parked) and /color hues.
+R=255; G=140; B=0
+DOT="🟠"
+# Smooth pulsate helper — ACTIVE state, so we pulse this color (Steve, 2026-09-04:
+# "Pulsate the color whenever claude or codex is waiting for me or just running").
+# Best-effort: if it's missing or the tty isn't writable, painting the solid color still succeeds.
+PULSE="${HOME}/.claude/tab-dots/pulse.sh"
+
+paint() { # $1 = tty_path, $2 = label(optional), $3 = off(1=reset)
+ local tp="$1" label="${2:-}" off="${3:-0}"
+ [[ -w "$tp" ]] || return 1
+ if [[ "$off" == "1" ]]; then
+ # Stop any active pulse FIRST (before the reset) so the pulse loop can't repaint after we clear.
+ [[ -f "$PULSE" ]] && bash "$PULSE" --stop "$tp" >/dev/null 2>&1 || true
+ { printf '\033]6;1;bg;*;default\007'; printf '\033]1337;SetBadgeFormat=\a'; } > "$tp" 2>/dev/null
+ rm -f "${HOME}/.claude/tab-dots/$(basename "$tp").dot" 2>/dev/null
+ return 0
+ fi
+ local title="$DOT ${label:-PASTE waiting}"
+ # NOTE: no leading '*;default' reset — on iTerm2 it can swallow the color set (pinkdot bug,
+ # 2026-09-02). Set RGB directly and write BOTH title channels (]1; tab, ]2; window).
+ {
+ printf '\033]6;1;bg;red;brightness;%d\007' "$R"
+ printf '\033]6;1;bg;green;brightness;%d\007' "$G"
+ printf '\033]6;1;bg;blue;brightness;%d\007' "$B"
+ printf '\033]1;%s\007' "$title"
+ printf '\033]2;%s\007' "$title"
+ } > "$tp" 2>/dev/null
+ # Persist the dot so the 60s tab-ticket repainter PREFIXES it into the window title
+ # (visible even when the tab bar is hidden), and show an in-window iTerm2 BADGE.
+ mkdir -p "${HOME}/.claude/tab-dots"
+ printf '%s' "$title" > "${HOME}/.claude/tab-dots/$(basename "$tp").dot"
+ printf '\033]1337;SetBadgeFormat=%s\a' "$(printf '%s' "$title" | base64 | tr -d '\n')" > "$tp" 2>/dev/null
+ # ACTIVE state → start a smooth pulse of this same color (pulse.sh's per-tty pidfile replaces
+ # any existing pulse on this tty, so pulses never stack). Best-effort, never fatal.
+ [[ -f "$PULSE" ]] && bash "$PULSE" "$R" "$G" "$B" "$tp" >/dev/null 2>&1 || true
+}
+
+# --all: paint every live claude tab orange
+if [[ "${1:-}" == "--all" ]]; then
+ ttys="$(ps -axo tty=,command= 2>/dev/null \
+ | awk '$1 ~ /^ttys/ && $0 ~ /claude/ && $0 !~ /grep/ {print $1}' | sort -u)"
+ [[ -z "$ttys" ]] && { echo "/orangedot --all → no live claude ttys"; exit 0; }
+ n=0
+ for tt in $ttys; do paint "/dev/$tt" "PASTE waiting" 0 && { echo " $tt → 🟠 PASTE waiting"; n=$((n+1)); }; done
+ echo "/orangedot --all → marked ${n} tab(s) orange (paste waiting on Steve)"
+ exit 0
+fi
+
+OFF=0; LABEL=""
+[[ "${1:-}" == "--off" ]] && OFF=1 || LABEL="${1:-}"
+
+# Resolve THIS session's controlling pty by walking ancestry (same as color.sh).
+tty_path=""; p=$$
+for _ in 1 2 3 4 5 6 7 8 9 10; do
+ line="$(ps -o ppid=,tty= -p "$p" 2>/dev/null)" || break
+ set -- $line
+ pp="${1:-}"; tt="${2:-}"
+ [[ -z "$pp" ]] && break
+ if [[ "$tt" == ttys* ]]; then tty_path="/dev/$tt"; break; fi
+ p="$pp"
+done
+if [[ -z "$tty_path" ]]; then
+ echo "/orangedot → could not resolve this session's tty (no ttysNNN in ancestry)"; exit 1
+fi
+
+if [[ "$OFF" == "1" ]]; then
+ paint "$tty_path" "" 1 && echo "/orangedot --off → cleared tint on $tty_path"
+else
+ paint "$tty_path" "$LABEL" 0 && echo "/orangedot → $tty_path 🟠 ${LABEL:-PASTE waiting} rgb(${R},${G},${B})"
+fi
diff --git a/verification/install/before/.claude/skills/pinkdot/pinkdot.sh b/verification/install/before/.claude/skills/pinkdot/pinkdot.sh
new file mode 100644
index 0000000..f9bc796
--- /dev/null
+++ b/verification/install/before/.claude/skills/pinkdot/pinkdot.sh
@@ -0,0 +1,98 @@
+#!/usr/bin/env bash
+# /pinkdot — mark a Claude session's iTerm2 tab PINK = "idle / parked / nothing left to progress".
+# A fixed SEMANTIC color (not /color's rotating golden-angle hue): pink always means
+# "this session finished its work and is holding." Supersedes /yellowdot as the parked marker
+# (Steve, 2026-09-02: "make parked terminals show pink dot"). Lets Steve see at a glance, across
+# a wall of tabs, which sessions are done/parked (pink) vs actively working (their /color tint).
+#
+# Mechanism (identical to color.sh / yellowdot.sh): the Claude Bash tool runs with NO controlling
+# tty, so OSC escapes on stdout never reach the terminal. We resolve the `claude` process's own
+# pty by walking process ancestry and write the OSC straight to /dev/ttysNNN. OSC color/title
+# codes are non-destructive (no cursor moves) so this is glitch-safe.
+#
+# Usage:
+# pinkdot.sh # pink-dot THIS session (title "🩷 idle")
+# pinkdot.sh "label" # pink tint + custom title text (🩷 prefixed)
+# pinkdot.sh --all # pink-dot EVERY live claude tab (mark whole fleet parked)
+# pinkdot.sh --off # clear the tint back to default on THIS session
+set -u
+
+# HARD RAIL (2026-09-08): a subagent has no tab of its own — its ancestry resolves to the
+# PARENT session's pty, so painting would corrupt Steve's live pane (the input-field bug). No-op.
+if [ -n "${CLAUDE_CODE_CHILD_SESSION:-}" ]; then
+ echo "/pinkdot → subagent context — refusing to paint (would write to the parent session's live pane). no-op."
+ exit 0
+fi
+
+# Semantic pink (hot pink) — reads clearly as "idle/parked" and is distinct from /color hues.
+R=255; G=105; B=180
+DOT="🩷"
+# PARKED state stays SOLID — pinkdot never STARTS a pulse. But it must KILL any active-state
+# pulse (green/orange/yellow/purple) inherited on this tty so parking reads as a steady dot
+# (Steve, 2026-09-04). Best-effort: missing helper / unwritable tty never blocks the paint.
+PULSE="${HOME}/.claude/tab-dots/pulse.sh"
+
+paint() { # $1 = tty_path, $2 = label(optional), $3 = off(1=reset)
+ local tp="$1" label="${2:-}" off="${3:-0}"
+ [[ -w "$tp" ]] || return 1
+ if [[ "$off" == "1" ]]; then
+ # Stop any active pulse FIRST (before the reset) so the pulse loop can't repaint after we clear.
+ [[ -f "$PULSE" ]] && bash "$PULSE" --stop "$tp" >/dev/null 2>&1 || true
+ { printf '\033]6;1;bg;*;default\007'; printf '\033]1337;SetBadgeFormat=\a'; } > "$tp" 2>/dev/null
+ rm -f "${HOME}/.claude/tab-dots/$(basename "$tp").dot" 2>/dev/null
+ return 0
+ fi
+ # Parking → kill any active-state pulse on this tty BEFORE setting the solid color, so nothing
+ # repaints over the steady pink. pinkdot stays SOLID and never starts a pulse of its own.
+ [[ -f "$PULSE" ]] && bash "$PULSE" --stop "$tp" >/dev/null 2>&1 || true
+ local title="$DOT ${label:-idle}"
+ # NOTE: do NOT send a leading '*;default' reset here — on iTerm2 it can leave the tab
+ # at default and swallow the color set (bug found 2026-09-02). Set RGB directly and write
+ # BOTH title channels (]1; icon/tab, ]2; window) exactly like the proven /color path.
+ {
+ printf '\033]6;1;bg;red;brightness;%d\007' "$R"
+ printf '\033]6;1;bg;green;brightness;%d\007' "$G"
+ printf '\033]6;1;bg;blue;brightness;%d\007' "$B"
+ printf '\033]1;%s\007' "$title"
+ printf '\033]2;%s\007' "$title"
+ } > "$tp" 2>/dev/null
+ # Persist the dot so the 60s tab-ticket repainter PREFIXES it into the window title
+ # (visible even when the tab bar is hidden), and show an in-window iTerm2 BADGE.
+ mkdir -p "${HOME}/.claude/tab-dots"
+ printf '%s' "$title" > "${HOME}/.claude/tab-dots/$(basename "$tp").dot"
+ printf '\033]1337;SetBadgeFormat=%s\a' "$(printf '%s' "$title" | base64 | tr -d '\n')" > "$tp" 2>/dev/null
+}
+
+# --all: paint every live claude tab pink
+if [[ "${1:-}" == "--all" ]]; then
+ ttys="$(ps -axo tty=,command= 2>/dev/null \
+ | awk '$1 ~ /^ttys/ && $0 ~ /claude/ && $0 !~ /grep/ {print $1}' | sort -u)"
+ [[ -z "$ttys" ]] && { echo "/pinkdot --all → no live claude ttys"; exit 0; }
+ n=0
+ for tt in $ttys; do paint "/dev/$tt" "idle" 0 && { echo " $tt → 🩷 idle"; n=$((n+1)); }; done
+ echo "/pinkdot --all → marked ${n} tab(s) pink (parked)"
+ exit 0
+fi
+
+OFF=0; LABEL=""
+[[ "${1:-}" == "--off" ]] && OFF=1 || LABEL="${1:-}"
+
+# Resolve THIS session's controlling pty by walking ancestry (same as color.sh).
+tty_path=""; p=$$
+for _ in 1 2 3 4 5 6 7 8 9 10; do
+ line="$(ps -o ppid=,tty= -p "$p" 2>/dev/null)" || break
+ set -- $line
+ pp="${1:-}"; tt="${2:-}"
+ [[ -z "$pp" ]] && break
+ if [[ "$tt" == ttys* ]]; then tty_path="/dev/$tt"; break; fi
+ p="$pp"
+done
+if [[ -z "$tty_path" ]]; then
+ echo "/pinkdot → could not resolve this session's tty (no ttysNNN in ancestry)"; exit 1
+fi
+
+if [[ "$OFF" == "1" ]]; then
+ paint "$tty_path" "" 1 && echo "/pinkdot --off → cleared tint on $tty_path"
+else
+ paint "$tty_path" "$LABEL" 0 && echo "/pinkdot → $tty_path 🩷 ${LABEL:-idle} rgb(${R},${G},${B})"
+fi
diff --git a/verification/install/before/.claude/skills/purpledot/purpledot.sh b/verification/install/before/.claude/skills/purpledot/purpledot.sh
new file mode 100644
index 0000000..0267f89
--- /dev/null
+++ b/verification/install/before/.claude/skills/purpledot/purpledot.sh
@@ -0,0 +1,98 @@
+#!/usr/bin/env bash
+# /purpledot — mark a Claude session's iTerm2 tab PURPLE = "GATED — memo drafted to pending-approval, awaiting Steve's approve/reject in the viewer".
+# A fixed SEMANTIC color (not /color's rotating golden-angle hue): purple always means
+# "this session's remaining work is a gated action drafted to ~/.claude/yolo-queue/pending-approval/ and it waits on the viewer decision."
+# Steve, 2026-09-02: "and a color for gated" (companion to /orangedot = a direct ! paste in the terminal).
+# Sibling of /orangedot (orange = paste in terminal), /pinkdot (pink = parked). Lets Steve scan a wall of
+# tabs and see which windows wait on a VIEWER decision (purple) vs a terminal paste (orange) vs parked (pink) vs working (/color).
+#
+# Mechanism (identical to pinkdot.sh / color.sh): the Claude Bash tool runs with NO controlling
+# tty, so OSC escapes on stdout never reach the terminal. We resolve the `claude` process's own
+# pty by walking process ancestry and write the OSC straight to /dev/ttysNNN. OSC color/title
+# codes are non-destructive (no cursor moves) so this is glitch-safe.
+#
+# Usage:
+# purpledot.sh # purple-dot THIS session (title "🟣 GATED")
+# purpledot.sh "TK-11098 x3" # purple tint + custom title text (🟣 prefixed) — put the TK + count
+# purpledot.sh --all # purple-dot EVERY live claude tab (rare; fleet-wide "all gated")
+# purpledot.sh --off # clear the tint on THIS session (memo approved/rejected → back to /color or /pinkdot)
+set -u
+
+# HARD RAIL (2026-09-08): a subagent has no tab of its own — its ancestry resolves to the
+# PARENT session's pty, so painting would corrupt Steve's live pane (the input-field bug). No-op.
+if [ -n "${CLAUDE_CODE_CHILD_SESSION:-}" ]; then
+ echo "/purpledot → subagent context — refusing to paint (would write to the parent session's live pane). no-op."
+ exit 0
+fi
+
+# Semantic purple — "gated, awaiting viewer decision". Distinct from orange (paste), pink (parked), /color hues.
+R=148; G=0; B=211
+DOT="🟣"
+# Smooth pulsate helper — ACTIVE state, so we pulse this color (Steve, 2026-09-04:
+# "Pulsate the color whenever claude or codex is waiting for me or just running").
+# Best-effort: if it's missing or the tty isn't writable, painting the solid color still succeeds.
+PULSE="${HOME}/.claude/tab-dots/pulse.sh"
+
+paint() { # $1 = tty_path, $2 = label(optional), $3 = off(1=reset)
+ local tp="$1" label="${2:-}" off="${3:-0}"
+ [[ -w "$tp" ]] || return 1
+ if [[ "$off" == "1" ]]; then
+ # Stop any active pulse FIRST (before the reset) so the pulse loop can't repaint after we clear.
+ [[ -f "$PULSE" ]] && bash "$PULSE" --stop "$tp" >/dev/null 2>&1 || true
+ { printf '\033]6;1;bg;*;default\007'; printf '\033]1337;SetBadgeFormat=\a'; } > "$tp" 2>/dev/null
+ rm -f "${HOME}/.claude/tab-dots/$(basename "$tp").dot" 2>/dev/null
+ return 0
+ fi
+ local title="$DOT ${label:-GATED}"
+ # NOTE: no leading '*;default' reset — on iTerm2 it can swallow the color set (pinkdot bug,
+ # 2026-09-02). Set RGB directly and write BOTH title channels (]1; tab, ]2; window).
+ {
+ printf '\033]6;1;bg;red;brightness;%d\007' "$R"
+ printf '\033]6;1;bg;green;brightness;%d\007' "$G"
+ printf '\033]6;1;bg;blue;brightness;%d\007' "$B"
+ printf '\033]1;%s\007' "$title"
+ printf '\033]2;%s\007' "$title"
+ } > "$tp" 2>/dev/null
+ # Persist the dot so the 60s tab-ticket repainter PREFIXES it into the window title
+ # (visible even when the tab bar is hidden), and show an in-window iTerm2 BADGE.
+ mkdir -p "${HOME}/.claude/tab-dots"
+ printf '%s' "$title" > "${HOME}/.claude/tab-dots/$(basename "$tp").dot"
+ printf '\033]1337;SetBadgeFormat=%s\a' "$(printf '%s' "$title" | base64 | tr -d '\n')" > "$tp" 2>/dev/null
+ # ACTIVE state → start a smooth pulse of this same color (pulse.sh's per-tty pidfile replaces
+ # any existing pulse on this tty, so pulses never stack). Best-effort, never fatal.
+ [[ -f "$PULSE" ]] && bash "$PULSE" "$R" "$G" "$B" "$tp" >/dev/null 2>&1 || true
+}
+
+# --all: paint every live claude tab purple
+if [[ "${1:-}" == "--all" ]]; then
+ ttys="$(ps -axo tty=,command= 2>/dev/null \
+ | awk '$1 ~ /^ttys/ && $0 ~ /claude/ && $0 !~ /grep/ {print $1}' | sort -u)"
+ [[ -z "$ttys" ]] && { echo "/purpledot --all → no live claude ttys"; exit 0; }
+ n=0
+ for tt in $ttys; do paint "/dev/$tt" "GATED" 0 && { echo " $tt → 🟣 GATED"; n=$((n+1)); }; done
+ echo "/purpledot --all → marked ${n} tab(s) purple (gated, awaiting approval)"
+ exit 0
+fi
+
+OFF=0; LABEL=""
+[[ "${1:-}" == "--off" ]] && OFF=1 || LABEL="${1:-}"
+
+# Resolve THIS session's controlling pty by walking ancestry (same as color.sh).
+tty_path=""; p=$$
+for _ in 1 2 3 4 5 6 7 8 9 10; do
+ line="$(ps -o ppid=,tty= -p "$p" 2>/dev/null)" || break
+ set -- $line
+ pp="${1:-}"; tt="${2:-}"
+ [[ -z "$pp" ]] && break
+ if [[ "$tt" == ttys* ]]; then tty_path="/dev/$tt"; break; fi
+ p="$pp"
+done
+if [[ -z "$tty_path" ]]; then
+ echo "/purpledot → could not resolve this session's tty (no ttysNNN in ancestry)"; exit 1
+fi
+
+if [[ "$OFF" == "1" ]]; then
+ paint "$tty_path" "" 1 && echo "/purpledot --off → cleared tint on $tty_path"
+else
+ paint "$tty_path" "$LABEL" 0 && echo "/purpledot → $tty_path 🟣 ${LABEL:-GATED} rgb(${R},${G},${B})"
+fi
diff --git a/verification/install/before/.claude/skills/tab-ticket/scripts/tab-ticket-set.sh b/verification/install/before/.claude/skills/tab-ticket/scripts/tab-ticket-set.sh
new file mode 100644
index 0000000..fd518e4
--- /dev/null
+++ b/verification/install/before/.claude/skills/tab-ticket/scripts/tab-ticket-set.sh
@@ -0,0 +1,60 @@
+#!/usr/bin/env bash
+# tab-ticket-set.sh TK-#### — declare THIS window's ticket.
+# Resolves the caller's own controlling ttysNNN (walking process ancestry, same
+# as color.sh) and writes the TK id to ~/.claude/tab-tickets/<ttyname>.txt so
+# tab-ticket-title.sh will render "TK-#### · up Xh Ym" on this window's title.
+#
+# Usage: tab-ticket-set.sh TK-10921 # set this window's ticket
+# tab-ticket-set.sh clear|none # clear it (back to no-TK)
+# tab-ticket-set.sh --apply TK-1234 # set AND immediately repaint titles
+set -u
+
+TICKET_DIR="${HOME}/.claude/tab-tickets"
+mkdir -p "$TICKET_DIR"
+SELF_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+
+APPLY=0
+if [[ "${1:-}" == "--apply" ]]; then APPLY=1; shift; fi
+
+TK="${1:-}"
+if [[ -z "$TK" ]]; then
+ echo "usage: tab-ticket-set.sh [--apply] TK-#### | clear"
+ exit 2
+fi
+
+# resolve caller's own controlling ttys* by walking ancestry from $$
+tty_path=""; ttyname=""; p=$$
+for _ in 1 2 3 4 5 6 7 8 9 10 11 12; do
+ line="$(ps -o ppid=,tty= -p "$p" 2>/dev/null)" || break
+ # shellcheck disable=SC2086
+ set -- $line; pp="${1:-}"; tt="${2:-}"
+ [[ -z "$pp" ]] && break
+ if [[ "$tt" == ttys* ]]; then ttyname="$tt"; tty_path="/dev/$tt"; break; fi
+ p="$pp"
+done
+
+if [[ -z "$ttyname" ]]; then
+ echo "tab-ticket-set → could not resolve my own tty; ticket not set"
+ exit 1
+fi
+
+tkfile="$TICKET_DIR/${ttyname}.txt"
+
+case "$TK" in
+ clear|none|CLEAR|NONE)
+ rm -f "$tkfile"
+ echo "tab-ticket-set → cleared ticket for ${ttyname} (now no-TK)"
+ ;;
+ *)
+ # normalise: accept "10921" or "tk-10921" → "TK-10921"; else store verbatim
+ norm="$TK"
+ if [[ "$TK" =~ ^[0-9]+$ ]]; then norm="TK-$TK"; fi
+ norm="$(printf '%s' "$norm" | sed -E 's/^tk-/TK-/I')"
+ printf '%s\n' "$norm" > "$tkfile"
+ echo "tab-ticket-set → ${ttyname} now working ${norm}"
+ ;;
+esac
+
+if (( APPLY )); then
+ bash "$SELF_DIR/tab-ticket-title.sh" >/dev/null 2>&1 || true
+fi
diff --git a/verification/install/before/.claude/skills/tab-ticket/scripts/tab-ticket-title.sh b/verification/install/before/.claude/skills/tab-ticket/scripts/tab-ticket-title.sh
new file mode 100644
index 0000000..5912803
--- /dev/null
+++ b/verification/install/before/.claude/skills/tab-ticket/scripts/tab-ticket-title.sh
@@ -0,0 +1,115 @@
+#!/usr/bin/env bash
+# tab-ticket-title.sh — set EVERY open `claude` OR `codex` window/tab TITLE
+# (top of the window) to "TK-#### · up Xh Ym" so at a glance you know which
+# ticket each window is working AND how long it's been up. (2026-09-09: added
+# codex coverage — Steve runs claude+codex peer sessions side by side on the
+# same tickets, and codex ttys were silently falling back to iTerm's generic
+# "<dir> (codex)" auto-title with no ticket shown at all.)
+#
+# Mechanism (reuses /color's proven path): both tools' Bash/exec runs detached
+# with NO controlling tty, so OSC escapes on stdout never reach the terminal.
+# We enumerate every live `claude`/`codex` process's controlling ttysNNN (same
+# as color.sh --all) and write the OSC TITLE escape straight to /dev/ttysNNN.
+#
+# CRITICAL: the title OSC ( ESC ]0; / ]1; / ]2; ) is INDEPENDENT of the
+# color-tint OSC ( ESC ]6;1;bg;... ). Writing a title does NOT clear the tab
+# color — verified. So this can run every 60s without disturbing /color's tint.
+#
+# UPTIME — derived per-tty from the claude process's OWN start time via
+# `ps -o etime= -p <pid>`. No state file needed.
+# TICKET — read from ~/.claude/tab-tickets/<ttyname>.txt (just the TK id).
+# Declared by a session via tab-ticket-set.sh. Missing → "no-TK".
+set -u
+
+TICKET_DIR="${HOME}/.claude/tab-tickets"
+mkdir -p "$TICKET_DIR"
+
+# fmt_uptime <etime> — turn ps etime ([[DD-]HH:]MM:SS) into "Xd Yh" / "Xh Ym" / "Xm"
+fmt_uptime() {
+ awk -v e="$1" 'BEGIN{
+ days=0; rest=e
+ n=split(e, a, "-")
+ if (n==2){ days=a[1]+0; rest=a[2] } else { rest=a[1] }
+ m=split(rest, t, ":")
+ if (m==3){ h=t[1]+0; mi=t[2]+0 }
+ else if (m==2){ h=0; mi=t[1]+0 }
+ else { h=0; mi=0 }
+ # total hours including days
+ th = days*24 + h
+ if (days > 0) printf "up %dd %dh", days, h
+ else if (th > 0) printf "up %dh %dm", th, mi
+ else printf "up %dm", mi
+ }'
+}
+
+# Enumerate every live claude OR codex session's controlling tty. Both report
+# comm=node / their own binary, so match on the full command line (same as
+# color.sh --all). codex sessions never call tab-ticket-set.sh, so we also
+# pull a TK-#### straight out of the launch command line itself (it's right
+# there in argv for every ticket-driven run — "TK-10670-onboard-refresh-...")
+# as a fallback when no manually-declared ticket file exists. This covers
+# BOTH claude and codex ttys — claude tabs that never called tab-ticket-set.sh
+# now also pick up their ticket from argv instead of sitting on "no-TK".
+# Capture tty + etime + auto-detected-TK in one ps pass so we pick the RIGHT
+# process per tty. Written for bash 3.2 (macOS /bin/bash under launchd): NO
+# mapfile, NO associative arrays. One session may report multiple matching
+# rows on the same tty (helper procs); awk collapses to the FIRST row seen
+# per tty.
+ROWS="$(ps -axo tty=,pid=,etime=,command= 2>/dev/null \
+ | awk '$1 ~ /^ttys/ && $0 ~ /[c]laude|[c]odex/ && !seen[$1]++ {
+ tk = "";
+ if (match($0, /TK-[0-9]+/)) tk = substr($0, RSTART, RLENGTH);
+ print $1"\t"$3"\t"tk
+ }')"
+
+if [[ -z "$ROWS" ]]; then
+ echo "tab-ticket-title → no live claude/codex ttys found"
+ exit 0
+fi
+
+n=0
+while IFS=$'\t' read -r tt etime autotk; do
+ [[ -z "$tt" ]] && continue
+
+ tp="/dev/$tt"
+ [[ -w "$tp" ]] || continue
+
+ # ticket for this tty: manually-declared file wins, else fall back to
+ # whatever TK-#### was found in the process's own launch command.
+ tk="no-TK"
+ tkfile="$TICKET_DIR/${tt}.txt"
+ if [[ -f "$tkfile" ]]; then
+ v="$(tr -d ' \t\r\n' < "$tkfile" 2>/dev/null)"
+ [[ -n "$v" ]] && tk="$v"
+ fi
+ if [[ "$tk" == "no-TK" && -n "$autotk" ]]; then tk="$autotk"; fi
+
+ up="$(fmt_uptime "$etime")"
+ label="${tk} · ${up}"
+
+ # Semantic DOT prefix (2026-09-02): /orangedot /purpledot /yellowdot /pinkdot drop
+ # "<emoji> <label>" into ~/.claude/tab-dots/<tty>.dot; --off removes it. Prefixing
+ # it here means the dot SURVIVES this 60s repaint (before, the repaint erased it),
+ # and it shows in the WINDOW TITLE even when the tab bar is hidden (single-tab windows).
+ dotfile="${HOME}/.claude/tab-dots/${tt}.dot"
+ d=""
+ if [[ -f "$dotfile" ]]; then
+ d="$(head -c 80 "$dotfile" 2>/dev/null | tr -d '\r\n')"
+ fi
+ # DEFAULT DOT (2026-09-02): a live claude tab with NO explicit state file gets a
+ # neutral ⚪ so NO tab is ever dotless at the top (root fix for "where is my dot?" —
+ # a brand-new session had no ~/.claude/tab-dots/<tty>.dot yet). The semantic dot
+ # skills (/pinkdot /orangedot /yellowdot /purpledot) still override by writing the file.
+ [[ -z "$d" ]] && d="⚪"
+ label="${d} · ${label}"
+
+ # Title-only OSC — set icon-name(1), window-title(2), and generic title(0).
+ # These are INDEPENDENT of the ]6;bg color-tint OSC, so the tab COLOR survives.
+ {
+ printf '\033]1;%s\007' "$label"
+ printf '\033]2;%s\007' "$label"
+ printf '\033]0;%s\007' "$label"
+ } > "$tp" 2>/dev/null && n=$(( n + 1 ))
+done <<< "$ROWS"
+
+echo "tab-ticket-title → set title on ${n} claude window(s)"
diff --git a/verification/install/before/.claude/skills/yellowdot/yellowdot.sh b/verification/install/before/.claude/skills/yellowdot/yellowdot.sh
new file mode 100644
index 0000000..de8b57d
--- /dev/null
+++ b/verification/install/before/.claude/skills/yellowdot/yellowdot.sh
@@ -0,0 +1,94 @@
+#!/usr/bin/env bash
+# /yellowdot — mark a Claude session's iTerm2 tab YELLOW = "NEEDS DIRECTION — a question / decision fork is pending for Steve".
+# A fixed SEMANTIC color (not /color's rotating golden-angle hue): yellow always means
+# "this session finished its work and is holding." Lets Steve see at a glance, across a wall
+# of tabs, which sessions are waiting on his ANSWER (yellow) vs gated in the viewer (purple) vs a terminal paste (orange) vs parked (pink) vs working (/color).
+#
+# Mechanism (identical to color.sh): the Claude Bash tool runs with NO controlling tty, so
+# OSC escapes on stdout never reach the terminal. We resolve the `claude` process's own pty
+# by walking process ancestry and write the OSC straight to /dev/ttysNNN. OSC color/title
+# codes are non-destructive (no cursor moves) so this is glitch-safe.
+#
+# Usage:
+# yellowdot.sh # yellow-dot THIS session (title "🟡 DIRECTION?")
+# yellowdot.sh "label" # yellow tint + custom title text (🟡 prefixed)
+# yellowdot.sh --all # yellow-dot EVERY live claude tab (rare)
+# yellowdot.sh --off # clear the tint back to default on THIS session
+set -u
+
+# HARD RAIL (2026-09-08): a subagent has no tab of its own — its ancestry resolves to the
+# PARENT session's pty, so painting would corrupt Steve's live pane (the input-field bug). No-op.
+if [ -n "${CLAUDE_CODE_CHILD_SESSION:-}" ]; then
+ echo "/yellowdot → subagent context — refusing to paint (would write to the parent session's live pane). no-op."
+ exit 0
+fi
+
+# Semantic yellow (warm gold) — "needs direction". REPURPOSED 2026-09-02 (Steve: "where is color dot for direction"); idle/parked is now /pinkdot.
+R=255; G=204; B=0
+DOT="🟡"
+# Smooth pulsate helper — ACTIVE state, so we pulse this color (Steve, 2026-09-04:
+# "Pulsate the color whenever claude or codex is waiting for me or just running").
+# Best-effort: if it's missing or the tty isn't writable, painting the solid color still succeeds.
+PULSE="${HOME}/.claude/tab-dots/pulse.sh"
+
+paint() { # $1 = tty_path, $2 = label(optional), $3 = off(1=reset)
+ local tp="$1" label="${2:-}" off="${3:-0}"
+ [[ -w "$tp" ]] || return 1
+ if [[ "$off" == "1" ]]; then
+ # Stop any active pulse FIRST (before the reset) so the pulse loop can't repaint after we clear.
+ [[ -f "$PULSE" ]] && bash "$PULSE" --stop "$tp" >/dev/null 2>&1 || true
+ { printf '\033]6;1;bg;*;default\007'; printf '\033]1337;SetBadgeFormat=\a'; } > "$tp" 2>/dev/null
+ rm -f "${HOME}/.claude/tab-dots/$(basename "$tp").dot" 2>/dev/null
+ return 0
+ fi
+ local title="$DOT ${label:-DIRECTION?}"
+ {
+ printf '\033]6;1;bg;red;brightness;%d\007' "$R"
+ printf '\033]6;1;bg;green;brightness;%d\007' "$G"
+ printf '\033]6;1;bg;blue;brightness;%d\007' "$B"
+ printf '\033]1;%s\007' "$title"
+ printf '\033]2;%s\007' "$title"
+ } > "$tp" 2>/dev/null
+ # Persist the dot so the 60s tab-ticket repainter PREFIXES it into the window title
+ # (visible even when the tab bar is hidden), and show an in-window iTerm2 BADGE.
+ mkdir -p "${HOME}/.claude/tab-dots"
+ printf '%s' "$title" > "${HOME}/.claude/tab-dots/$(basename "$tp").dot"
+ printf '\033]1337;SetBadgeFormat=%s\a' "$(printf '%s' "$title" | base64 | tr -d '\n')" > "$tp" 2>/dev/null
+ # ACTIVE state → start a smooth pulse of this same color (pulse.sh's per-tty pidfile replaces
+ # any existing pulse on this tty, so pulses never stack). Best-effort, never fatal.
+ [[ -f "$PULSE" ]] && bash "$PULSE" "$R" "$G" "$B" "$tp" >/dev/null 2>&1 || true
+}
+
+# --all: paint every live claude tab yellow
+if [[ "${1:-}" == "--all" ]]; then
+ ttys="$(ps -axo tty=,command= 2>/dev/null \
+ | awk '$1 ~ /^ttys/ && $0 ~ /claude/ && $0 !~ /grep/ {print $1}' | sort -u)"
+ [[ -z "$ttys" ]] && { echo "/yellowdot --all → no live claude ttys"; exit 0; }
+ n=0
+ for tt in $ttys; do paint "/dev/$tt" "DIRECTION?" 0 && { echo " $tt → 🟡 DIRECTION?"; n=$((n+1)); }; done
+ echo "/yellowdot --all → marked ${n} tab(s) yellow (needs direction)"
+ exit 0
+fi
+
+OFF=0; LABEL=""
+[[ "${1:-}" == "--off" ]] && OFF=1 || LABEL="${1:-}"
+
+# Resolve THIS session's controlling pty by walking ancestry (same as color.sh).
+tty_path=""; p=$$
+for _ in 1 2 3 4 5 6 7 8 9 10; do
+ line="$(ps -o ppid=,tty= -p "$p" 2>/dev/null)" || break
+ set -- $line
+ pp="${1:-}"; tt="${2:-}"
+ [[ -z "$pp" ]] && break
+ if [[ "$tt" == ttys* ]]; then tty_path="/dev/$tt"; break; fi
+ p="$pp"
+done
+if [[ -z "$tty_path" ]]; then
+ echo "/yellowdot → could not resolve this session's tty (no ttysNNN in ancestry)"; exit 1
+fi
+
+if [[ "$OFF" == "1" ]]; then
+ paint "$tty_path" "" 1 && echo "/yellowdot --off → cleared tint on $tty_path"
+else
+ paint "$tty_path" "$LABEL" 0 && echo "/yellowdot → $tty_path 🟡 ${LABEL:-DIRECTION?} rgb(${R},${G},${B})"
+fi
diff --git a/verify_live.py b/verify_live.py
new file mode 100644
index 0000000..516770b
--- /dev/null
+++ b/verify_live.py
@@ -0,0 +1,77 @@
+#!/usr/bin/env python3
+"""Exercise the actual own-terminal Claude/Codex adapters and native header."""
+import datetime as dt
+import json
+from pathlib import Path
+import subprocess
+import sys
+import terminal_status as ts
+
+root = Path.home()
+project = Path(__file__).resolve().parent
+owner = ts.current_owner(ts.processes())
+assert owner.tty == 'ttys010', 'This recorded verification is scoped to TK-11317 only'
+store = ts.Store()
+checks = []
+
+
+def command(args):
+ return subprocess.run(args, text=True, capture_output=True, check=True, timeout=30).stdout
+
+
+def own_row():
+ data = json.loads(command(['bash', str(root / '.claude/skills/allcolordots/allcolordots.sh'), '--json']))
+ return next(r for r in data if r['tty'] == owner.tty)
+
+
+def native():
+ script = '''tell application "iTerm2"
+repeat with s in sessions of tab 1 of window id 152190
+ if tty of s is "/dev/ttys010" then
+ select s
+ exit repeat
+ end if
+end repeat
+delay 0.15
+return name of window id 152190
+end tell'''
+ return command(['osascript', '-e', script]).strip()
+
+
+try:
+ command([sys.executable, str(project / 'terminal_status.py'), 'ticket', 'TK-11317'])
+ for runtime in ('codex', 'claude'):
+ skills = root / ('.agents' if runtime == 'codex' else '.claude') / 'skills'
+ for color in ('green', 'orange', 'purple', 'yellow', 'pink'):
+ output = command(['bash', str(skills / f'{color}dot/{color}dot.sh'), 'E2E check'])
+ r, source = store.load(owner)
+ assert source == 'canonical' and r['state'] == color
+ assert r['ticket'] == 'TK-11317'
+ assert all((d / (owner.tty + '.dot')).read_text() == r['title'] for d in store.legacy.values())
+ row = own_row()
+ assert row['color'] == color and row['label'] == r['title']
+ title = native()
+ assert r['title'] in title, (r['title'], title)
+ checks.append({'check': f'{runtime}:{color}', 'verdict': 'PASS',
+ 'canonical': r['title'], 'topbar': row['label'], 'window': title})
+ command([sys.executable, str(project / 'terminal_status.py'), 'clear'])
+ before = store.path(owner).read_bytes()
+ command(['bash', str(root / '.agents/skills/color/scripts/repaint-dot.sh')])
+ assert before == store.path(owner).read_bytes()
+ assert own_row()['color'] == 'none'
+ command([sys.executable, str(project / 'terminal_status.py'), 'headers', '--quiet'])
+ assert 'TK-11317' in native() and 'Status cleared' in native()
+ checks.append({'check': 'clear + Stop repaint + native title refresh', 'verdict': 'PASS'})
+ command(['bash', str(root / '.claude/skills/color/scripts/working-state.sh'), 'on'])
+ assert own_row()['label'] == '🟢 TK-11317 · WORKING'
+ checks.append({'check': 'generic prompt hook preserves ticket', 'verdict': 'PASS'})
+ ts.atomic_write(store.legacy['claude'] / (owner.tty + '.dot'), '🟠 old stale orange')
+ assert own_row()['color'] == 'green'
+ checks.append({'check': 'topbar rejects stale orange mirror', 'verdict': 'PASS'})
+finally:
+ command(['bash', str(root / '.agents/skills/greendot/greendot.sh'), 'TK-11317 · WORKING'])
+ report = {'ticket': 'TK-11317', 'timestamp': dt.datetime.now(dt.timezone.utc).isoformat(),
+ 'owner': ts.dataclasses.asdict(owner), 'checks': checks,
+ 'cleanup': 'Own terminal restored green WORKING, bound to TK-11317; mirrors repaired.'}
+ ts.atomic_write(project / 'verification/live-journey.json', json.dumps(report, ensure_ascii=False, indent=2) + '\n')
+print(json.dumps(report, ensure_ascii=False, indent=2))
diff --git a/verify_ui.py b/verify_ui.py
new file mode 100644
index 0000000..4dec684
--- /dev/null
+++ b/verify_ui.py
@@ -0,0 +1,28 @@
+#!/usr/bin/env python3
+"""Capture only the native status menu, excluding terminal contents."""
+import json
+from pathlib import Path
+import subprocess
+import terminal_status as ts
+
+project = Path(__file__).resolve().parent
+script = '''tell application "System Events" to tell process "ticket-desktop-bar"
+ click button "◉" of window 2
+ delay 0.7
+ set m to menu 1 of button "◉" of window 2
+ set names to name of menu items of m
+ set AppleScript's text item delimiters to linefeed
+ set labels to names as text
+ set AppleScript's text item delimiters to ","
+ set rect to (position of m & size of m) as text
+ return rect & linefeed & labels
+end tell'''
+result = subprocess.run(['osascript', '-e', script], capture_output=True, text=True, check=True)
+rect, labels = result.stdout.split('\n', 1)
+coords = ','.join(str(int(x.strip())) for x in rect.split(','))
+assert '🟢 TK-11317 · WORKING' in labels
+image = project / 'verification/desktop-menu.png'
+subprocess.run(['screencapture', '-x', '-R', coords, str(image)], check=True)
+ts.atomic_write(project / 'verification/native-menu.json', json.dumps({
+ 'verdict': 'PASS', 'labels': labels.splitlines(), 'capture': str(image)}, ensure_ascii=False, indent=2) + '\n')
+print('PASS: actual desktop menu contains TK-11317 and six Codex ticket labels; ' + str(image))
← 03cc6f9 auto-data-snapshot: 2026-09-09T11:09:48 (1 data files) — ver
·
back to Terminal Status
·
fix: paint guard resolves ancestry before refusing on CLAUDE 914a88d →