readline: bind() via rl_bind_keyseq; repl reserves \C-n no-op

Phase 1 readline binding wiring per PHASE1.md §7.

ffi/readline:
  M.bind(seq, lua_fn) -> bool
    Wraps lua_fn as a C callback (signature `int (int, int)` per
    readline's rl_command_func_t) and registers it via
    rl_bind_keyseq(seq, cb). Returns true on success (rl returns 0).
    Trampolines are pinned in module-local state so they outlive the
    bind call — readline retains the function pointer for the process
    lifetime. Rebinding the same seq frees the previous trampoline.
    Bound handlers are pcall-wrapped so a Lua error doesn't crash
    readline's input loop.

repl:
  Binds \C-n to a no-op that emits
    "[aish] Norris mode not yet implemented (Phase 3)"
  Verifies the mechanism end-to-end; Phase 3 (Norris autonomous mode)
  replaces the body with the actual toggle.

Smoke covers bind / rebind-same-seq (exercises the :free path) /
bind-different-seq with no errors. Live keyboard verification waits
on user-test.

Phase 1's 8(+1) inner loop is now functionally through `implement`;
next inner phase is `verify` (review pass) followed by memory-update.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-10 19:26:58 +00:00
parent 9d586870e8
commit a75118b2ae
2 changed files with 35 additions and 2 deletions
+7
View File
@@ -67,6 +67,13 @@ function M.run(config)
return ("[aish:%s]> "):format(active_name)
end
-- Phase 1 reserved-key wiring (PHASE1.md §7). The mechanism is real; the
-- handlers are placeholders that emit a status. Phase 3 (Norris) is the
-- first consumer that replaces the body with real work.
rl.bind("\\C-n", function()
renderer.status("Norris mode not yet implemented (Phase 3)")
end)
local function status_evictions(n)
if n and n > 0 then
renderer.status(("oldest %d turns evicted"):format(n))