Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions AGENTS.tape
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
#!/usr/bin/env tape
# ══════════════════════════════════════════════════════════════════════
# .tape v1.2 — grammar primer (cold-read by any LLM / agent / human)
Expand Down Expand Up @@ -290,6 +290,54 @@
exception = "JSON keys (string literals) are fine"
authority = "~/core/hexa-lang/self/lexer.hexa"

@F f8 := "xterm-modifyOtherKeys-push" :: governance [required deny:write d=2026-05-18]
pattern <<~EOF
Pushing xterm modifyOtherKeys mode at TUI entry. Concretely:
term_write_str(chr(27) + "[>4;1m") // Lv1
term_write_str(chr(27) + "[>4;2m") // Lv2
term_write_str("\x1b[>4;Nm") // any N>0
Applies anywhere — harness-cli, future harnesses, plugins, hexa-lang stdlib.
EOF
remedy <<~EOF
Shift+Enter has two cleaner cover paths that don't need terminal mode state:
(1) ESC+CR — claude-code's terminalSetup convention. Decoded in
self/tui/input.hexa::_decode_one (ESC + 13/10 branch). User maps
shift+enter → `\x1b\r` in their terminal (Ghostty: `keybind=
shift+enter=text:\x1b\r`); iTerm2 / VS Code / Cursor / Windsurf
install this out of the box.
(3) `\` + Enter — portable in-buffer fallback. Backslash immediately
before cursor is stripped and replaced with `\n`. Works on every
terminal with zero config.
Both already implemented in plugins/harness-cli/main.hexa:602-608.
EOF
why <<~EOF
modifyOtherKeys is a terminal-side global mode that survives the
pushing process — once a (pre-fix) wilson exits without popping, the
parent shell sees `CSI 27;mod;code~` body leaks on every modified
keypress for the rest of the terminal session. The 2026-05-18 debug
session traced a cascade of bugs through this single push: Lv2 →
Hangul/CJK silently dropped (ASCII ceiling at L477); Lv1 → still
broken because `chr(cp)` byte-truncated codepoint→UTF-8 emit; on-exit
pop with `ESC[>4m` (Pn omitted) → stuck-mode in Ghostty leaking into
zsh's Shift+Enter. The whole class disappears when you don't push.
EOF
exception <<~EOF
The modifyOtherKeys *decoder* branches in self/tui/input.hexa stay —
they harmlessly no-op when no one pushes the mode, and remain useful
if a future feature opts back in deliberately (with proper push/pop/
flush hygiene and a passing chr-vs-from_char_code audit). The deny
is on the PUSH (`ESC[>4;Nm` with N>0), not the decoder.
The defensive pop (`ESC[>4;0m`, Pn=0) at term_modes_off is allowed —
it's a one-shot cleanup for sessions inheriting a stuck mode from
an *older* wilson binary, not a push partner.
EOF
authority <<~EOF
docs/sessions/2026-05-18-korean-input-modifyotherkeys-fix.md Decision 3
plugins/harness-cli/main.hexa::harness_cli_term_modes_on (history note)
Removal commit: this branch (docs/2026-05-18-korean-input-followup)
EOF
@> AGENTS.md

# ─── §6 External citations ───────────────────────────────────────────

@X x_anima_index_carry := "anima INDEX.md verdict ledger" :: spec [d=2026-05-15 active]
Expand Down
129 changes: 129 additions & 0 deletions docs/sessions/2026-05-18-korean-input-modifyotherkeys-fix.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,132 @@ raw-mode UTF-8 경로(L300)와 동일 폭. governance g7대로 inbox 노트 먼
`.tape`를 편집 가능하다고 규정 → 가드와 governance 불일치. 가드 임의 우회 대신
flag만 함. (line 474의 `[decoder] keys 11/11 PASS`도 동일.)
- 두 레포(wilson + hexa-lang) 모두 미커밋 — 사용자 커밋 지시 대기.

---

## 후속 — 가설 B 확정 (2026-05-18 second session)

새 세션 진입 시 사용자 보고: "최근 한글입력 수정 후 commit 했는데 안되 여전히"

진단:
- wilson commit 0cfe555 (Lv2 → Lv1 + 회귀 테스트)는 main에 정상 land.
- **그러나 fix ② (hexa-lang `self/tui/input.hexa` 디코더 127 천장 상향)는 사라짐.**
세션이 usage-limit으로 끊겼고, `wilson-checkpoint`가 wilson 레포만 stash
체크포인트하기 때문에 hexa-lang 레포의 working-tree 변경(인풋 디코더 수정 +
inbox 노트)은 통째로 유실. 결과 — wilson `main`의 회귀 테스트가 의존하는
upstream 변경 부재 → `wilson test` 22/23 (FAIL: modifyOtherKeys Hangul '안').
- 재빌드한 17:42 Darwin-arm64 바이너리로 사용자가 Ghostty에서 한글 시험 → 깨짐
(`\\ �` 표면화). **가설 B 확정**: Ghostty Lv1에서도 한글이 `CSI 27;...~`로
재포장되므로 fix ②가 필수.

### Decision 1 — option-B-g7-standard

**picked:** option-B-g7-standard (g7 표준 절차로 fix ② 복원)

**rationale:**
- 5/18 1차 세션의 의도된 절차를 이번엔 hexa-lang 커밋까지 완수 — 멀티 레포 누수 종결
- g7 invariant("모든 cross-repo 변경은 upstream inbox에서 discoverable")를 유지하면서 surgical upstream 수정 (memory `feedback_governance_g7_inbox_not_ban.md` — 직접 fix는 inbox 노트 land 후 OK)
- 단일 사이클로 두 문제(`wilson test` 22/23 빨강 + Ghostty 한글 입력 깨짐) 동시 해결
- 비용은 hexa.real 재빌드 2-3분 + 디코더 1줄 + inbox 노트 — A(직접 수정)의 audit 누락 부담을 부담할 가치 없음

### 실행

1. ✅ `~/core/hexa-lang/self/tui/input.hexa:477` — `_mok_code < 127` → `_mok_code < 1114112` (U+110000 상한, raw UTF-8 path와 동일 폭) + 8-line comment block 추가
2. ✅ `~/core/hexa-lang/inbox/patches/modifyotherkeys-non-ascii-decoder-gap.md` 생성 (CSI u sister note 형식 모방)
3. ✅ `~/core/hexa-lang/inbox/PATCHES.yaml` 엔트리 추가 (status: pending, downstream_consumer: wilson harness-cli)
4. ⏳ `hexa.real` 재빌드 — note n3 레시피 + `RESOURCE_LOCAL_HEXA=1` (memory `reference_hexa_real_rebuild.md`)
5. ⏳ wilson 재빌드 + `wilson test` 23/23 확인
6. ⏳ 사용자 Ghostty 한글 재시험

### 후속 — hexa-lang 커밋
- inbox 노트 + 디코더 수정 — 사용자 확인 후 커밋 (별도 PR 권장 / 직접 commit 둘 다 OK; 1차 세션에 직접 commit 의도였음)
- 커밋 후 wilson `AGENTS.tape` `@X p_*` 엔트리 추가 + `wilson` 커밋 SHA 갱신

---

## 후속 #2 — 진짜 root cause 확정 + Decision 2

가설 B 확정 후 추가 검증: 사용자가 "안 OK, 녕 시작하면 깨짐"이라 보고 →
modifyOtherKeys 가설 무너짐 (그게 원인이면 첫 음절도 깨졌어야).

tmux로 raw UTF-8 직접 주입 reproduce: `안` 입력 → wilson display `H`,
`녕` → `HU`. 첫 글자도 깨지고 있었음 (단지 단일 음절일 땐 "한글자 이상하다"
정도로 인식). 패턴:

- 안 = U+C548 = 50504 → 50504 & 0xFF = 0x48 = **'H'**
- 녕 = U+B155 = 45397 → 45397 & 0xFF = 0x55 = **'U'**

low-byte truncation. `self/runtime.c:8104`: `hexa_chr_byte()` literally
`buf[0] = (char)(code & 0xFF)` — 1-byte truncation. `self/codegen_c2.hexa:4302`
의 RFC `chr-byte-vs-codepoint-asymmetry` (2026-05-17) 가 의도적으로 chr 을
byte-truncation 으로 정의 (URL decode / PNG header / emoji prefix 보호).
codepoint→UTF-8 용 빌트인은 별도로 `from_char_code` 존재 — `tui/input.hexa`
decoder 가 잘못된 빌트인을 호출하고 있었음. 2026-05-17 RFC 전 작성된
decoder 가 split 을 모르고 chr 을 UTF-8 인코딩으로 가정.

### Decision 2 — option-B-canonical-upstream

**picked:** option-B-canonical-upstream (캐노니컬 hexa-lang upstream — chr→from_char_code 교체)

**rationale:**
- 진짜 root cause 에 직격 — 1-byte 출력의 원인이 chr 빌트인 자체였고, decoder 는 잘못된 도구를 쓰고 있었다 → 빌트인 의미를 유지하면서 호출자만 교체하는 게 정확한 fix
- RFC chr-byte-vs-codepoint-asymmetry (2026-05-17) 가 이미 두 빌트인을 명시적으로 갈라놨음 — `chr` (byte 1 바이트, byte-synthesis 보호) vs `from_char_code` (codepoint→UTF-8) — decoder 만 RFC 이전 가정으로 남아있던 것
- blast radius 최소: `self/tui/input.hexa` 2줄 (L300 raw UTF-8 path, L477 modifyOtherKeys), runtime / codegen 변경 없음, toolchain 재빌드 불필요
- 컴파일 기준 (사용자 지정 — 인터프리터는 폐기) → 인터프리터 호환성 고민 불필요

### 실행
1. ✅ `self/tui/input.hexa:300` — raw UTF-8 path: `chr(codepoint)` → `from_char_code(codepoint)` + 주석 갱신
2. ✅ `self/tui/input.hexa:477` — modifyOtherKeys path: `chr(_mok_code)` → `from_char_code(_mok_code)` + 주석 갱신 (Decision 1의 ceiling widening 도 그대로 유지 — Lv2 시나리오 belt-and-suspenders)
3. ✅ `inbox/patches/modifyotherkeys-non-ascii-decoder-gap.md` 재작성 — 진짜 root cause (chr-byte-truncation) 반영
4. ✅ `inbox/PATCHES.yaml` 엔트리 id 변경 (`modifyotherkeys-non-ascii-decoder-gap` → `input-decoder-chr-vs-from_char_code`) + 본문 갱신
5. ✅ wilson 재빌드 + tmux reproduce 통과 — `❯ 안녕 하세요 🌌` 정상 표시
6. ✅ wilson test 23/23 PASS
7. ✅ Ghostty 사용자 검증 — "잘된다"

### 학습 / 회고
- modifyOtherKeys 가설 (Decision 1) 은 contributing layer 였지만 root 가 아니었음. 가설 검증 단계에서 "단일 음절은 OK / 두 번째부터 깨짐" 사용자 보고가 결정적 단서였고, 이를 뒷받침할 reproducible test (tmux raw UTF-8 주입) 가 root 추적을 가능하게 함.
- wilson 의 `harness_cli_decoder_smoke` 가 `cp` 만 검사하고 `ch` 는 검사하지 않아 chr-byte-truncation 을 놓침 — `ch == "안"` 같은 byte-수준 assertion 으로 보강 필요 (후속).
- 2026-05-17 RFC chr-byte-fix 가 land 된 후 모든 chr(cp) 호출자가 의도(byte vs codepoint)를 명시적으로 결정해야 했는데, decoder 가 누락 → 다음 chr 호출 audit 가 필요할 수 있음 (다른 곳도 같은 함정에 빠졌을 수 있음).

---

## 후속 #3 — modifyOtherKeys 표면 자체 제거 + Decision 3

Decision 1 (Lv2→Lv1) + Decision 2 (chr→from_char_code) + 추가 exit-leak fix
(`>4;0m` + flush) 모두 land 후 사용자 질문: "modifyOtherKeys 없이 진행
가능한가". 가능 — Shift+Enter는 4가지 경로 중 하나만 살리면 충분하고,
modifyOtherKeys 의존을 빼면 오늘 디버깅한 클래스 전체가 소멸.

남는 경로:
- (1) ESC+CR — claude-code terminalSetup convention (iTerm2 / VS Code / Cursor / Windsurf / Ghostty의 `keybind=shift+enter=text:\x1b\r` out-of-box)
- (3) `\` + Enter — portable in-buffer fallback, 모든 터미널에서 zero-config 동작

### Decision 3 — option-A-remove-modifyotherkeys-surface

**picked:** option-A-remove-modifyotherkeys-surface

**rationale:**
- 오늘 디버깅한 3개 버그 전부 (modifyOtherKeys Lv2 한글, chr-byte truncation 직격, exit-leak stuck-mode)가 modifyOtherKeys 푸시에서 시작 — push 표면 자체를 제거하면 클래스 소멸 (Karpathy simplicity-first 원칙)
- 기능 손실 없음 — Shift+Enter는 경로 1(ESC+CR, claude-code 검증된 portable 표준) + 경로 3(`\`+Enter, universal) 두 가지로 cover
- 옵션 B(Kitty kbd protocol 활성화 + CSI u 디코더 갭 fix)는 별도 작업 필요 + Kitty kbd도 다른 leak class 우려가 있어 비활성된 상태 — A가 strictly 더 단순
- modifyOtherKeys *디코더* 분기는 hexa-lang에 그대로 둠 (push 없으면 harmlessly no-op, 미래에 의식적으로 다시 켤 때 재사용 가능). 제거 대상은 PUSH (`ESC[>4;Nm` with N>0) 뿐

### 실행
1. ✅ `plugins/harness-cli/main.hexa::harness_cli_term_modes_on` — `ESC[>4;1m` push 줄 주석 처리 + history block (왜 제거했는지 / 어떻게 대체되는지)
2. ✅ `harness_cli_term_modes_off` — `ESC[>4;0m` 줄은 *defensive* pop으로 유지 (push 없으니 정상 흐름엔 의미 없지만, *이전* wilson 빌드가 남긴 stuck-mode 상속한 세션을 1-shot 청소 — idempotent)
3. ✅ `AGENTS.tape` — `@F f8 xterm-modifyOtherKeys-push` 등록 (deny:write, remedy + why + exception 명시)
4. ✅ wilson 재빌드 + test 23/23 PASS
5. ⏳ Ghostty 사용자 검증 — exit 후 Shift+Enter 누수 없음 확인
6. ⏳ Ghostty 사용자 keybind 설정 (선택) — `keybind=shift+enter=text:\x1b\r` 추가하면 Shift+Enter 직접 입력 시에도 줄바꿈

### 정리
오늘 작업 총정리:
- hexa-lang `bf943479` (이전에 land) — modifyOtherKeys 디코더 ASCII 천장 widening
- hexa-lang `fix/input-decoder-chr-vs-from_char_code` 브랜치 `ac72668c` — chr→from_char_code (PR 대기)
- wilson `0cfe555` (이전에 land via PR #13) — Lv2→Lv1 + 회귀 테스트
- wilson `docs/2026-05-18-korean-input-followup` 브랜치:
- `94523bb` — session doc Decision 2
- `90fe78c` — modifyOtherKeys exit-leak fix (Pn=0 + flush)
- (다음 커밋) — push 제거 + AGENTS.tape f8 forbidden 등록 + Decision 3

마무리되면 wilson은 modifyOtherKeys "사용 금지" 상태로 governance 단단해지고, 이번 클래스의 버그는 영구히 안 나옴.
44 changes: 34 additions & 10 deletions plugins/harness-cli/main.hexa
Original file line number Diff line number Diff line change
Expand Up @@ -3083,15 +3083,26 @@ fn harness_cli_leave_tui() -> void {
// safely ignore a pop with no prior push).
fn harness_cli_term_modes_on() -> void {
let _bp = term_write_str(chr(27) + "[?2004h") // DECSET 2004 — bracketed paste
let _mok = term_write_str(chr(27) + "[>4;1m") // xterm modifyOtherKeys level 1
// Level 1 (not 2): level 2 reformats *every* printable keypress — including
// non-ASCII (Hangul / CJK / emoji) — as `CSI 27;<mod>;<codepoint>~`, but the
// upstream decoder's modifyOtherKeys branch (self/tui/input.hexa ~L477) only
// decodes codepoints 32..126; anything above returns an error event that gets
// silently dropped → Korean input arrives garbled / blank. Level 1 reformats
// only genuinely-ambiguous modified keys (Shift+Enter etc.) and leaves plain
// printable + IME-composed text as raw UTF-8, which the decoder's UTF-8 path
// handles correctly. See inbox/patches/modifyotherkeys-non-ascii-decoder-gap.md.
// xterm modifyOtherKeys (ESC[>4;Nm) was previously pushed here (Lv2 originally,
// then Lv1) to capture Shift+Enter as a structured `CSI 27;mod;code~` sequence.
// Removed 2026-05-18 — the mode caused a cascade of input bugs (non-ASCII
// Hangul/CJK silently dropped at the ASCII ceiling, chr-vs-from_char_code
// byte truncation, stuck-mode leak surviving wilson exit and corrupting the
// parent shell's Shift+Enter for the rest of the terminal session) and the
// feature it enabled — Shift+Enter — has two cleaner cover paths:
// 1) ESC+CR (alt=1) — claude-code's terminalSetup convention. iTerm2 / VS
// Code / Cursor / Windsurf / Ghostty map shift+enter → `\x1b\r` out of
// the box (Ghostty: `keybind=shift+enter=text:\x1b\r`). Decoded in
// self/tui/input.hexa::_decode_one (ESC + 13/10 branch). Zero per-
// terminal-runtime state — pure input encoding.
// 3) `\` immediately before cursor — backslash-then-Enter inserts a
// newline. Portable fallback; works on every terminal with zero config.
// (Path 2, Kitty kbd protocol `ESC[>1u`, is similarly disabled below pending
// the CSI u decoder gap — see inbox/patches/csi-u-modifier-keys-decoder-gap.md.)
// The modifyOtherKeys decoder branches in self/tui/input.hexa stay — they
// remain useful if a future feature ever opts back in, and they harmlessly
// no-op when no one pushes the mode.
// let _mok = term_write_str(chr(27) + "[>4;1m")
// Kitty kbd protocol push (`ESC[>1u`) is temporarily disabled because the
// upstream CSI u decoder in self/tui/input.hexa only handles Enter (cp=13);
// every other modifier+key combo — including Ctrl+C — returns an error
Expand All @@ -3118,8 +3129,21 @@ fn harness_cli_term_modes_off() -> void {
// when no entry exists in the kitty-kbd stack — leaving it ensures we
// restore terminal state even if our push got skipped.
let _kkb = term_write_str(chr(27) + "[<u")
let _mok = term_write_str(chr(27) + "[>4m") // modifyOtherKeys reset to default
// `ESC[>4;0m` — defensive modifyOtherKeys DISABLE. We removed our push
// (term_modes_on, 2026-05-18 history note), so under normal flow there's
// nothing to pop; this is left as a one-shot cleanup for sessions inheriting
// a stuck mode from an *older* wilson build (pre-2026-05-18 left
// modifyOtherKeys on across exit) — running today's wilson once resets the
// terminal even before the user reaches `/exit`. Idempotent on any terminal
// (Pn=0 is unambiguous "off"; no push, no negative effect).
let _mok = term_write_str(chr(27) + "[>4;0m")
let _bp = term_write_str(chr(27) + "[?2004l") // DECRST 2004 — bracketed paste off
// Best-effort fsync so the disable sequences actually leave the wilson
// process before exit. Without this, `eprintln("bye.") + return` can race
// the terminal — bye. lands, then wilson dies, and the pop bytes still
// sitting in stdio never reach the terminal, leaving modifyOtherKeys on
// in the parent shell. `render_flush` is the harness's own write-through.
let _ = render_flush()
}

// host_render → core routes "render" here when this harness owns the input loop.
Expand Down
Loading