phase0 amendment: §3/§7/§10 close review-surfaced manifest gaps

Three additions to PHASE0.md, all surfaced by the Phase 5 review of
the Phase 0 implementation. No invariant changes; manifest now matches
implementation reality.

§3 — FFI loader fallback paragraph. ffi.load("name") needs the
unversioned `libname.so` symlink that comes with the -dev package.
Phase 0 loaders try unversioned first then versioned sonames so
runtime-only hosts (no -dev) work as-is. Documents the actual
behavior in ffi/readline.lua and ffi/curl.lua.

§7 — LuaJIT 2.1 popen-close caveat paragraph. The §7 sketch had been
showing Lua 5.2's three-return io.popen():close() shape; LuaJIT 2.1
follows the Lua 5.1 ABI and returns just `true`. Phase 0 recovers
the exit status with a sentinel echo (`echo __AISH_EXIT_<tag>__$?`).
Phase 1 PTY+waitpid replaces the hack and the sketch becomes
accurate. Sketch left as-is (it's the right shape conceptually);
caveat now explicit.

§10 — cwd-relative package.path note. Phase 0 prepends `./?.lua;
./vendor/?.lua`, so aish must run from the repo root. Cwd-independent
resolution is a later concern. Also clarifies that --config is strict
(no fallback if the path is unopenable) — matches main.lua post the
review-followup commit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-10 17:44:20 +00:00
parent abc993aa49
commit a76ff664b3
+27 -3
View File
@@ -43,6 +43,13 @@ Phase 0 is the minimal working skeleton. It establishes the REPL loop, input dis
| Config format | Lua table (plain `.lua` file sourced at startup) | No parser dependency; native types; easily extended |
| JSON encode/decode | dkjson 2.8 vendored under `vendor/dkjson.lua` | Pure Lua (preserves §3 "no compiled extensions" invariant); single-file vendor avoids `luarocks`; sourced from Debian's `lua-dkjson` package, originally from dkolf.de |
**FFI loader fallback.** `ffi.load("readline")` and `ffi.load("curl")`
look for the unversioned `lib<name>.so` symlink, which is only installed
by the `-dev` package. Phase 0 loaders try the unversioned name first
then fall back to versioned sonames (`readline.so.8`, `readline.so.7`,
`curl.so.4`, `curl-gnutls.so.4`) so a runtime-only host (Debian/ALARM
without `lib<name>-dev`) just works.
---
## 4. Target Directory Layout
@@ -153,7 +160,7 @@ The `CMD:` prefix convention is the extraction contract between the model and `e
## 7. Execution Model (Phase 0)
```lua
-- executor.lua Phase 0
-- executor.lua Phase 0 (illustrative — see note below)
local function exec(cmd)
local handle = io.popen(cmd .. " 2>&1", "r")
local output = handle:read("*a")
@@ -162,11 +169,21 @@ local function exec(cmd)
end
```
**LuaJIT 2.1 popen-close caveat.** The sketch above assumes Lua 5.2's
three-return `io.popen():close()` shape. LuaJIT 2.1 follows the Lua 5.1
ABI and returns just `true` — no exit status. The Phase 0 implementation
recovers the exit code by appending a sentinel echo to the wrapped
command (`(cmd) 2>&1; echo __AISH_EXIT_<tag>__$?`) and parsing it back
out. Phase 1's PTY work replaces this with `waitpid` via libc FFI; the
sketch becomes accurate at that point.
Output is captured and:
1. Printed to the terminal
2. Injected into `context.lua` as a `[exec output]` user turn
`cd` is intercepted before `popen` and handled via `posix.chdir` (libc FFI) so the working directory change persists across calls — `popen` forks a subprocess and `cd` inside it would otherwise be discarded.
`cd` is intercepted before `popen` and handled via `libc.chdir` (FFI) so
the working directory change persists across calls — `popen` forks a
subprocess and `cd` inside it would otherwise be discarded.
---
@@ -270,11 +287,18 @@ return {
```
Config path resolution order:
1. `--config <path>` CLI argument
1. `--config <path>` CLI argument (explicit; failure if not openable, no fallback)
2. `$AISH_CONFIG` environment variable
3. `~/.config/aish/config.lua`
4. `./config.lua` (development fallback)
**Cwd-relative module resolution.** Phase 0 prepends `./?.lua;./vendor/?.lua`
to `package.path`, so `luajit main.lua` must be invoked with the repo
root as cwd. Cwd-independent resolution (relative to the script's own
directory) lands later — likely Phase 1 alongside the install path
work, or whenever the first user reports trying `luajit ~/aish/main.lua`
from somewhere else.
---
## 11. Planned Phase Sequence