Phase 0 implementation per PHASE0.md §6, §7.
M.exec(cmd) -> (output, exit_code)
M.maybe_chdir(cmd) -> nil | true | false, errmsg
M.extract_cmd_lines(text)-> { "ls -la", "echo hi", ... }
Two non-obvious bits:
1. LuaJIT 2.1's io.popen():close() follows the Lua 5.1 ABI and returns
only `true` — no child exit status. The §7 manifest sketch assumes
Lua 5.2's three-return form, which doesn't apply here. Recover the
exit code by appending `; echo __AISH_EXIT_<tag>__$?` after the
command and parsing the sentinel-prefixed integer back out. Phase 1
replaces this with waitpid via libc FFI when PTY support lands.
2. `cd` interception is a §3 invariant: must not delegate to popen
(popen forks; a child cd evaporates). maybe_chdir parses the line,
~ expands, calls libc.chdir, returns success/failure separate from
"not a cd" (nil) so the caller can distinguish.
CMD: extraction is anchored at start-of-line per the §3 "exact prefix,
single space" invariant — leading whitespace before CMD: does not match.
Smoke covers: echo capture (code=0), failed ls (code!=0), `false`
(code=1), multi-line output preserved, all maybe_chdir branches
(non-cd / bare / explicit / ~ expansion / failure), CMD extraction
including the leading-whitespace-rejection case.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- README, .gitignore, CLAUDE.md (project conventions)
- docs/PHASE0.md — full Phase 0 manifest (locked substrate)
- 10 root .lua modules + 4 ffi/ bindings, all stubs raising NotImplemented
with module-scoped responsibilities matching the manifest
- config.lua wired to current dirac/hossenfelder endpoints (qwen-coder-7b
snappy/32k + cloud via OpenRouter through hossenfelder)
File names match docs/PHASE0.md §4 exactly. Module bodies fill in across
later phases; the tree shape is locked.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>