CLAUDE.md · git:20260809.2d06d5d · 2026-08-09 · sha256 d610e244c22698bf

CLAUDE.md git:20260809.2d06d5dA

Immutable. This exact content is served forever at /api/v1/blob/d610e244c22698bf.

# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## ⚠️ 硬性红线:严禁密集/并发 spawn 真实进程(会卡死/崩溃本机)

2026-07-13 事故:在一个会话里短时间内反复 spawn 大量真实进程(多个 grok/codex/
kiro-cli 全屏 TUI + 反复起关 daemon + mutation 来回 git checkout + verify_fx 每次
又开一堆 PTY 子进程),累积瞬时并发把用户 Windows 机器拖到卡死,用户被迫重启。
单个进程都清理干净了(零残留),元凶是**叠加的瞬时并发峰值**。

以后必须遵守:
1. **一次只驱动一个 PTY/TUI 会话。** 跑完立刻 `close` + 确认 `tui.py list` 零残留,
   再起下一个。绝不同时开多个 agent CLI(grok/codex/kiro-cli 每个都拉起
   node/rust 进程 + winpty,极吃资源)。
2. **重活(verify_fx、run_all、drive-probe 全套)先征得用户同意再跑**,且串行、
   分开跑,不在一个会话里堆几十个进程。verify_fx 每跑一次要 spawn 一大批 PTY 特效
   子进程——尤其重。
3. **mutation 验证要克制**:来回 git checkout + 重跑 PTY 探针代价高;能用代码级
   断言证明的就别反复起真实进程。
4. **看到 spawn 层错误**(`uv_spawn`、`EUNKNOWN`、exit 143/45、Git-bash spawn
   失败)= 系统在示警资源紧张,**立即停手**,别换 shell 硬上。
5. 需要用户自己跑重活时,建议用 `! python tests/verify_fx.py`(前缀 `!` 在会话里
   直接跑),而不是我又开一批子进程。

## Git commits

- 不加任何 AI 署名(遵循用户全局 CLAUDE.md)。
- 只有用户明确要求才 commit。

## Commands

Set `PYTHONIOENCODING=utf-8` before running anything (box-drawing/CJK glyphs crash
on legacy codepages; CI sets it too).

**Use an interpreter that has `pyte` installed.** Every test here imports
`smartcli_core`, so a bare `python`/`python3` from a version manager will die with
`ModuleNotFoundError: No module named 'pyte'` — which reads like a broken test.
`python -m venv .venv && .venv/bin/pip install -r requirements.txt -e .` gives you
one; `python -m smartcli_core` reports what a given interpreter can actually see.
And **take the exit code directly** — `python tests/x.py 2>&1 | tail` yields
`tail`'s status, so an import crash reads as a pass.

```bash
# Full self-test aggregator — exit 0 iff everything present passes, and a gate
# that is tracked by git but missing on disk now FAILS rather than skipping
# (renaming or deleting one used to be a green SKIP). Entries may still SKIP
# themselves internally when an external binary is absent — tmux, vim, less — so
# a green run on a host lacking those covers less than a green run here. SLOW: spawns real
# PTY sessions serially. Per the red line above, get user consent before running.
python tests/run_all.py

# One gate needs a test-only dependency, or it reports an internal SKIP (which
# run_all now surfaces even on a PASS). It is NOT in requirements-optional.txt —
# that file is runtime extras with guarded imports; this is test-only, and CI
# installs it inline before running the gate.
pip install hypothesis                    # test_readiness_properties

# Run a single test (each test file is a standalone script, exit 0 = pass):
python tests/test_fx_contract.py          # deterministic, pure-memory — safe
python tests/test_readiness.py
python tests/test_vendor_sync.py
python tests/_drive_probe1.py             # spawns a real PTY — run serially

# fx effect regression (spawns many PTY children — heavy, consent first):
python tests/verify_fx.py

# Coverage over the deterministic subset (no PTY spawn; --full drives PTYs):
python tools/coverage_run.py
python tools/coverage_run.py --xml        # also write coverage.xml for Codecov

# Lint / type-check. PARTLY BLOCKING in CI (lint.yml): the ruff correctness subset
# and mypy fail the build; the full ruff report and format check are advisory.
ruff check --select E9,F63,F7,F82 .       # the blocking subset
mypy                                      # config in pyproject.toml; checks smartcli_core only
ruff check .                              # advisory (full style/modernization backlog)
# mypy MUST see the real pyte. Without it, ignore_missing_imports degrades
# pyte.Screen to Any, which BOTH hides real errors in the subclass and reports a
# correct `type: ignore` as unused — that once failed CI on a clean tree while two
# genuine errors went unseen. Run it in an env where `requirements.txt` is installed.

# cmd-art effect engine (run from skills/cmd-art):
python -m fx list                         # live catalog (30 effects)
python -m fx play donut --seconds 5
python -m fx gallery

# tui-ui layout engine (run from skills/tui-ui):
python -m ui widgets                      # 17 widgets
python self_test.py

# drive-tui: drive a real interactive program (ONE session at a time).
# Use "python3 -i -q" (POSIX) / "py -i -q" (Windows) — don't assume a bare
# `python` exists on PATH (modern macOS has none):
python skills/drive-tui/scripts/tui.py start --cmd "python3 -i -q" --cols 100 --rows 30
python skills/drive-tui/scripts/tui.py wait-regex --id <SID> ">>> " --timeout-ms 15000
python skills/drive-tui/scripts/tui.py send-line --id <SID> "print(6*7)"
python skills/drive-tui/scripts/tui.py snapshot --id <SID>
python skills/drive-tui/scripts/tui.py close --id <SID>
python skills/drive-tui/scripts/tui.py list   # verify zero leaked sessions

# After ANY change to smartcli_core: re-sync the vendored copy and verify.
python tools/sync_vendor.py
python tests/test_vendor_sync.py
```

## Architecture

**One shared core, three skills on top.** SmartCLI drives, perceives, and renders
terminals through a pluggable PTY + `pyte` cell-grid screen model (not a byte
pipe), so it knows which menu row is highlighted rather than pattern-matching a
stream.

### `smartcli_core/` — the shared typed core (the only mypy-checked package)

- `pty_backend.py` — pluggable PTY: `WinptyBackend` (ConPTY via pywinpty, the
  primary dev target) and `PosixPtyBackend` (stdlib `pty`, Linux/macOS).
  `get_default_backend()` picks per-platform.
- `screen_model.py` — `ScreenModel` wraps a `pyte` screen: cell grid, cursor,
  alt-screen, DECCKM app-cursor state (arrow keys adapt SS3 vs CSI from it).
- `snapshot.py` — semantic `Snapshot`/`Span`: styled runs, selected-row
  detection, content hashes.
- `readiness.py` — the wait primitives: `wait_until_stable`, `wait_for_regex`,
  `wait_ready` (races marker vs stability), `wait_any` (pexpect-style
  multi-marker). Never `sleep` — always these.
- `session.py` — `PtySession` ties it together; `KEY_MAP` translates key tokens
  to escape sequences.

Gotcha baked into the API docs: pyte right-pads lines with spaces, so
end-anchored markers like `r">>> $"` never match — use unanchored markers.

### `skills/` — three self-contained skills run in place from the checkout

- `skills/drive-tui/` — drives interactive TUIs via a perceive → decide → act →
  wait → confirm loop. `scripts/tui.py` is the CLI: a detached daemon owns one
  live program; commands connect over a localhost-only, token-authenticated
  socket so state survives across shell calls. `scripts/mcp_server.py` exposes
  the same surface as a stdio MCP server. `_vendor/smartcli_core/` is a
  byte-identical vendored copy of the core (enforced by `test_vendor_sync`);
  `smartcli_bootstrap.locate_core()` resolves the real core first
  (`$SMARTCLI_ROOT` → parent walk → `_vendor/` → pip install). The control plane is
  security-relevant: per-session token, session-id validation, a per-user `0700`
  registry, a deny-list on `--env` (compared uppercased, since Windows upcases env
  keys), and `close` refuses to remove the entry of a daemon whose pid is still alive.
  Its serial accept loop means one unauthenticated connection can stall the others —
  bounded, not fixed; see `SECURITY.md` and A0-DAEMON-CONCURRENCY.
- `skills/cmd-art/` — the `fx` effect engine: `Effect` ABC + `@register` +
  pkgutil auto-discovery. Effects are **pure frame producers** (return one full
  frame; never print/sleep/touch ANSI modes — the play loop owns the terminal).
  30 effects, 8 themes.
- `skills/tui-ui/` — web-like layout engine emitting **tmux-safe ANSI frames**
  (SGR runs + newlines only; no cursor moves, no alt-screen). CSS box model,
  `VStack/HStack/Grid` with `Fr` units, 17 widgets, plus engine modules:
  `field.py` (shaders), `raster.py` (sub-cell braille/quad pixels),
  `box_junction.py` (auto-connecting borders), `color_model.py` (truecolor→mono
  degrade). All sizing is display-cell accurate via `ui.core.width()` — never
  `len()`. It produces frames; something else owns the terminal (contrast with
  drive-tui).

### Packaging duality

The PyPI dist is `smartcli-toolkit`; the import package is `smartcli_core`.
`pyproject.toml` maps `smartcli_drive` → `skills/drive-tui/scripts`, so one
implementation serves source checkouts, skill installs, the `smartcli-tui` /
`smartcli-mcp` / `smartcli-toolkit` console scripts, and MCP Registry clients
(`io.github.dwgx/smartcli`). cmd-art and tui-ui are intentionally not packaged —
they run in place via `python -m fx` / `python -m ui`.

### Version bump = TEN sites move together

`pyproject.toml`, `smartcli_core/__init__.py`, `skills/cmd-art/fx/__init__.py`,
all 3 `skills/*/SKILL.md` `version:` fields, `.claude-plugin/marketplace.json`,
`.claude-plugin/plugin.json`, the vendored `_vendor/smartcli_core/__init__.py`,
and `server.json` (two fields: top-level + `packages[0].version`). After
bumping: `python tools/sync_vendor.py`, then `python tests/test_vendor_sync.py`
and `python tests/test_version_sync.py` (anti-drift gate over all ten sites).

### Tests

`tests/run_all.py` aggregates every self-test as a subprocess and reports one
pass/fail. Tests are standalone scripts, not pytest. Two tiers:

- **Deterministic gates** (pure/in-memory, no PTY): `test_fx_contract`,
  `test_readiness`, `test_wait_any`, `test_visual_change`,
  `test_drive_security`, `test_terminal_fidelity`, `test_perf_contract`,
  `test_sixel`, `test_doc_counts` (anti-drift: doc counts must match code),
  `test_version_sync` (ten version sites), `test_dependency_sync` (one dependency
  fact, one value — `requirements.txt` is what the Docker image installs, so it
  must not drift from pyproject), `test_harbor_agent`, `test_vendor_sync`,
  `_sandbox_fuzz_core`. The authoritative list is
  `build_suite()` in run_all.py. These run in CI on a 3-OS matrix
  (Windows/Ubuntu/macOS × py3.10/3.14).
- **Real-process probes** — spawn real ConPTY/pty/tmux; slow, serial-only, one
  at a time, consent required: `_drive_probe*`, `_tui_cli_probe`, `_mcp_probe`,
  `verify_fx`, `probe_pty_fx`, `_sandbox_posix_backend`,
  `_sandbox_daemon_robustness`, plus the two real-tmux probes
  (`_diff_tmux_pyte`, `_tmux_launcher_probe`), which SKIP themselves when tmux
  is absent.

Docs and counts are contract-tested: changing the number of effects/widgets/recipes
requires updating README/SKILL.md counts or `test_doc_counts` fails (it also
bans hard-coded dev-box paths from portable docs). To quote a deliberately WRONG
number — explaining a past drift, citing another project's catalog — put
`doc-counts:ignore` on that line. The gate used to infer that from nearby words and
thereby exempted HANDOFF's own authoritative counts line, so the marker is explicit
by design.

**A green check is only evidence if it can turn red.** Reviewing for that specifically
found nine defects on 2026-08-07, six inside the gates themselves (HANDOFF §10k): a
contract gated on a predicate that evaluated the condition it asserts, a skipped check
counted as a pass, a regex family with a dead branch, a pip-shaped pattern run over a
Ruby formula, an assertion pinned to values argparse had just parsed, a verification
grep that could not match its own text, and a missing-file case that skipped instead of
failing. When adding a gate, name the change that would make it fail — and for a regex
family, require every branch to have a live hit on disk.

**Differential testing is the strongest evidence available here.**
`tests/_diff_tmux_pyte.py` diffs our grid against a real tmux pane cell by
cell — it is how the ZWJ/VS16 text-loss bug was found, and how HARD RULE 7 was
confirmed. When it disagrees, suspect the rig first (tty `ONLCR`, capture-pane's
literal TAB, NFC vs NFD were all harness artifacts, not emulation gaps).

### Project docs

`docs/DISTRIBUTION-CHANNELS.md` maps where this project can actually get
discovered, with each channel's real acceptance rules — **including the ones whose
rules forbid automated/AI submissions**. Read it before filing anything anywhere;
one channel baits agents with a fake "fast-track" marker.

`HANDOFF.md` is the authoritative current-state record a fresh session reads
first (§0, then the newest §10 subsection); `NEXT-STEPS.md` is the prioritized task
queue. Both must be kept reconciled with reality after significant work — one
document, appended to, rather than a new takeover/summary file per session. A second
file claiming to hold the current state just splits the truth in two, and the older
one keeps being read.

`skills/drive-tui/references/LIMITATIONS.md` is the living log for driving defects:
read it FIRST when a program misbehaves, and append there when you fix one. Known
issues that affect a user's decisions belong in it and in `SECURITY.md`, not only in
a commit message.