Phase 0: scaffold tree + manifest
- 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>
This commit is contained in:
+19
@@ -0,0 +1,19 @@
|
||||
# LuaJIT bytecode
|
||||
*.luac
|
||||
*.ljbc
|
||||
|
||||
# Local runtime state
|
||||
sessions/
|
||||
memory.jsonl
|
||||
*.log
|
||||
|
||||
# Editor scratch
|
||||
.*.sw?
|
||||
*~
|
||||
|
||||
# Local config overrides (committed config.lua is the example/default)
|
||||
config.local.lua
|
||||
|
||||
# OS noise
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
@@ -0,0 +1,37 @@
|
||||
# CLAUDE.md — aish project conventions
|
||||
|
||||
This file is auto-loaded when any Claude session opens a clone of this repo.
|
||||
|
||||
## Source of truth
|
||||
|
||||
[`docs/PHASE0.md`](docs/PHASE0.md) is the locked substrate. Read it before
|
||||
making non-trivial changes. If a change touches §3 (technology decisions),
|
||||
§4 (directory layout), or §6 (broker contract), the change needs to be
|
||||
reflected in PHASE0.md *and* called out in the commit message.
|
||||
|
||||
## Phase loop
|
||||
|
||||
This project follows the 8(+1) phase loop documented in mfritsche's home
|
||||
canon (`feedback_dev_process.md` in claude-noether memory). Each phase has
|
||||
its own document under `docs/`. Don't skip phases.
|
||||
|
||||
Loopbacks per the canon: 3→1, 7→4, any→0.
|
||||
|
||||
## Module structure invariant
|
||||
|
||||
The file names listed in `docs/PHASE0.md` §4 are stable across phases. Later
|
||||
phases fill in module bodies; they do not rename files or restructure the
|
||||
tree. If you find yourself wanting to rename or split a module, that's a
|
||||
PHASE0.md amendment first.
|
||||
|
||||
## No C extensions
|
||||
|
||||
LuaJIT FFI only. If you reach for a C extension to "make this easier",
|
||||
stop. The FFI bindings under `ffi/` are extended in place.
|
||||
|
||||
## Commit style
|
||||
|
||||
Short imperative subject, file-scoped. Example: `executor: add cd
|
||||
interception via libc chdir`. Body explains the *why* if non-obvious.
|
||||
|
||||
Co-Authored-By trailers on Claude commits per project canon.
|
||||
@@ -0,0 +1,36 @@
|
||||
# aish
|
||||
|
||||
**aish** — AI-augmented conversational shell.
|
||||
|
||||
A unified REPL backed by language models accessed through a llama.cpp broker.
|
||||
Shell execution, AI inference, context management, and session memory in one
|
||||
terminal interface.
|
||||
|
||||
Implementation: LuaJIT 2.x with FFI bindings (libcurl, GNU readline, libc),
|
||||
no compiled extensions, no build step.
|
||||
|
||||
## Status
|
||||
|
||||
**Pre-implementation.** Phase 0 manifest only — see
|
||||
[`docs/PHASE0.md`](docs/PHASE0.md) for the full substrate, scope, technology
|
||||
decisions, directory layout, dispatch model, broker contract, execution
|
||||
model, configuration, and planned phase sequence.
|
||||
|
||||
## Directory layout
|
||||
|
||||
See `docs/PHASE0.md` §4. File names are stable across phases — later phases
|
||||
fill in module bodies, they do not rename files.
|
||||
|
||||
## Running
|
||||
|
||||
Not runnable yet. Once Phase 0 lands:
|
||||
|
||||
```sh
|
||||
luajit main.lua [--config <path>]
|
||||
```
|
||||
|
||||
## Project conventions
|
||||
|
||||
This repo follows the 8(+1) phase loop documented in mfritsche's home-project
|
||||
canon (`feedback_dev_process.md` in claude-noether memory). Each phase has its
|
||||
own document under `docs/`. The Phase 0 manifest is the locked substrate.
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
-- broker.lua — llama.cpp HTTP client.
|
||||
-- Phase 0: blocking POST via libcurl FFI; SSE streaming wired in Phase 1.
|
||||
-- See docs/PHASE0.md §6.
|
||||
|
||||
local M = {}
|
||||
|
||||
-- Send a /v1/chat/completions request.
|
||||
-- model_cfg: entry from config.models (endpoint, model, temperature, [key_env])
|
||||
-- messages: list of { role = ..., content = ... } including system prompt
|
||||
-- Returns: assistant content string on success, (nil, errmsg) on failure.
|
||||
function M.chat(model_cfg, messages)
|
||||
error("broker.chat: not implemented (Phase 0 pending)")
|
||||
end
|
||||
|
||||
return M
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
-- config.lua — model registry, routing rules, user preferences.
|
||||
-- Loaded with dofile() at startup; returns a plain Lua table.
|
||||
-- See docs/PHASE0.md §10 for resolution order and full schema.
|
||||
|
||||
return {
|
||||
default_model = "fast",
|
||||
|
||||
models = {
|
||||
fast = {
|
||||
endpoint = "http://dirac.fritz.box:8081",
|
||||
model = "qwen-coder-7b-snappy-8k",
|
||||
temperature = 0.2,
|
||||
},
|
||||
deep = {
|
||||
endpoint = "http://dirac.fritz.box:8080",
|
||||
model = "qwen-coder-7b-32k",
|
||||
temperature = 0.1,
|
||||
},
|
||||
cloud = {
|
||||
endpoint = "https://hossenfelder.fritz.box:8082",
|
||||
model = "anthropic/claude-haiku-4.5",
|
||||
-- Hossenfelder forwards to OpenRouter using its own key from
|
||||
-- /etc/conf.d/llm-proxy on the LXC; no client-side key needed.
|
||||
temperature = 0.2,
|
||||
},
|
||||
},
|
||||
|
||||
shell = {
|
||||
known_commands = {
|
||||
"ls", "cat", "cd", "grep", "find", "cp", "mv", "rm",
|
||||
"mkdir", "rmdir", "git", "make", "cmake", "gcc", "clang",
|
||||
"python3", "luajit", "ssh", "scp", "curl", "wget",
|
||||
},
|
||||
capture_output = true, -- inject exec output into context
|
||||
confirm_cmd = true, -- prompt before executing CMD: suggestions
|
||||
},
|
||||
|
||||
context = {
|
||||
max_turns = 40,
|
||||
token_budget = 4096,
|
||||
},
|
||||
|
||||
history = {
|
||||
dir = (os.getenv("HOME") or ".") .. "/.local/share/aish",
|
||||
},
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
-- context.lua — in-memory conversation history + token budget.
|
||||
-- Phase 0: ordered turn list, sliding window eviction.
|
||||
-- Tokenization is char/4 heuristic in Phase 0; accurate count is Phase 2.
|
||||
-- See docs/PHASE0.md §8.
|
||||
|
||||
local M = {}
|
||||
|
||||
-- Construct a Context table from config.context.
|
||||
function M.new(opts)
|
||||
error("context.new: not implemented (Phase 0 pending)")
|
||||
end
|
||||
|
||||
-- Append a turn { role = ..., content = ... }.
|
||||
function M:append(turn)
|
||||
error("context:append: not implemented (Phase 0 pending)")
|
||||
end
|
||||
|
||||
-- Render messages array suitable for broker.chat (system prompt prepended).
|
||||
function M:to_messages()
|
||||
error("context:to_messages: not implemented (Phase 0 pending)")
|
||||
end
|
||||
|
||||
-- Apply max_turns eviction policy. Returns number of turns evicted.
|
||||
function M:enforce_budget()
|
||||
error("context:enforce_budget: not implemented (Phase 0 pending)")
|
||||
end
|
||||
|
||||
return M
|
||||
+316
@@ -0,0 +1,316 @@
|
||||
# aish — Phase 0 Manifest
|
||||
|
||||
**Project:** aish — AI-augmented conversational shell
|
||||
**Document:** Phase 0 Requirements, Architecture & Design Decisions
|
||||
**Status:** Pre-implementation
|
||||
**Date:** 2026-05-10
|
||||
|
||||
---
|
||||
|
||||
## 1. Project Vision
|
||||
|
||||
aish is a conversational shell that presents a unified REPL to the user, backed by one or more language models accessed through a llama.cpp broker. Its purpose is to support interactive command execution, code assistance, debugging, and re-engineering tasks from a single terminal interface. The model layer is transparent to the user but configurable and extensible.
|
||||
|
||||
aish is not a wrapper around an existing shell. It is a first-class interactive environment that composes shell execution, AI inference, context management, and session memory into a coherent workflow.
|
||||
|
||||
---
|
||||
|
||||
## 2. Scope of Phase 0
|
||||
|
||||
Phase 0 is the minimal working skeleton. It establishes the REPL loop, input dispatch, model communication, and basic meta-command handling. No streaming, no autonomous mode, no persistence, no PTY.
|
||||
|
||||
**Phase 0 is done when:**
|
||||
|
||||
- The user can type natural language and receive a model response
|
||||
- The user can type a shell command and have it executed with output captured and displayed
|
||||
- The user can type `:meta` commands to control aish itself
|
||||
- The conversation history is maintained in memory for the session
|
||||
- The codebase structure matches the target layout so later phases slot in without refactoring
|
||||
|
||||
---
|
||||
|
||||
## 3. Technology Decisions
|
||||
|
||||
| Decision | Choice | Rationale |
|
||||
|---|---|---|
|
||||
| Implementation language | LuaJIT 2.x | Compact, embeddable, FFI eliminates need for C shim layer |
|
||||
| FFI strategy | LuaJIT FFI only (no C extension modules) | Direct `ffi.cdef` access to libc, libcurl, readline; no build toolchain required |
|
||||
| HTTP client | libcurl via FFI | Supports SSE streaming (Phase 1+); available on all target platforms |
|
||||
| Terminal input | GNU readline via FFI | Full readline semantics, custom key bindings, history, without dependency on a Lua readline package |
|
||||
| Model backend | llama.cpp OpenAI-compatible server (`/v1/chat/completions`) | Externally managed; aish does not own the process lifecycle |
|
||||
| Shell execution | `io.popen` in Phase 0; `forkpty` via libc FFI from Phase 1 | `popen` sufficient for non-interactive commands; PTY required for vim, htop, etc. |
|
||||
| Session persistence | Deferred to Phase 1 | Phase 0 holds history in memory only |
|
||||
| Config format | Lua table (plain `.lua` file sourced at startup) | No parser dependency; native types; easily extended |
|
||||
|
||||
---
|
||||
|
||||
## 4. Target Directory Layout
|
||||
|
||||
```
|
||||
aish/
|
||||
├── main.lua # Entry point: arg parsing, config load, REPL start
|
||||
├── repl.lua # Readline loop, input dispatch, prompt rendering
|
||||
├── broker.lua # llama.cpp HTTP client; Phase 0: blocking POST
|
||||
├── router.lua # Task classifier: shell / AI / meta
|
||||
├── executor.lua # Command execution; Phase 0: io.popen
|
||||
├── context.lua # In-memory conversation history, token budget
|
||||
├── history.lua # Persistent session log + memory.jsonl (Phase 1)
|
||||
├── safety.lua # Destructive op heuristic, Chuck Norris gate (Phase 1)
|
||||
├── renderer.lua # Output formatting, ANSI sequences
|
||||
├── config.lua # Model registry, routing rules, user preferences
|
||||
└── ffi/
|
||||
├── curl.lua # libcurl easy interface binding
|
||||
├── readline.lua # GNU readline binding
|
||||
├── pty.lua # forkpty, openpty, waitpid (Phase 1)
|
||||
└── libc.lua # Shared: errno, signal, write, read, misc
|
||||
```
|
||||
|
||||
All modules are required explicitly from `main.lua`. No module autoloading. File names are stable across phases — later phases fill in bodies, not rename files.
|
||||
|
||||
---
|
||||
|
||||
## 5. Input Dispatch Model
|
||||
|
||||
Every line of user input is classified by `router.lua` before any action is taken.
|
||||
|
||||
```
|
||||
input
|
||||
├── :command [args] → meta handler (repl.lua)
|
||||
├── $ prefix or → shell executor (executor.lua)
|
||||
│ heuristic match
|
||||
└── everything else → AI broker (broker.lua)
|
||||
```
|
||||
|
||||
### 5.1 Shell heuristic
|
||||
|
||||
An input line is treated as a shell command if it:
|
||||
|
||||
- Begins with `$` (explicit override, prefix stripped before exec)
|
||||
- Matches a known command prefix from a configurable allowlist (e.g. `ls`, `cd`, `git`, `make`, `grep`, `cat`, `find`, `cp`, `mv`, `mkdir`)
|
||||
- Contains a bare path-like token as first word (`./foo`, `/usr/bin/bar`)
|
||||
|
||||
Everything else goes to the model. The user can always force routing with `$` or with `:exec`.
|
||||
|
||||
### 5.2 Meta commands (Phase 0 set)
|
||||
|
||||
| Command | Action |
|
||||
|---|---|
|
||||
| `:quit` / `:q` | Exit aish |
|
||||
| `:clear` | Clear screen and reset display; keep history |
|
||||
| `:reset` | Clear in-memory conversation history |
|
||||
| `:model <name>` | Switch active model (must exist in config) |
|
||||
| `:models` | List configured models and active selection |
|
||||
| `:history` | Print conversation turns for current session |
|
||||
| `:exec <cmd>` | Force shell execution regardless of heuristic |
|
||||
| `:ask <text>` | Force AI query regardless of heuristic |
|
||||
| `:help` | Print meta command reference |
|
||||
|
||||
---
|
||||
|
||||
## 6. Broker Interface (Phase 0)
|
||||
|
||||
Phase 0 uses a single blocking HTTP POST. libcurl FFI is wired but SSE streaming is not yet consumed — the response is read to completion and returned as a string.
|
||||
|
||||
### Request shape
|
||||
|
||||
```
|
||||
POST http://<endpoint>/v1/chat/completions
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"model": "<model_id>",
|
||||
"messages": [ <conversation history> ],
|
||||
"stream": false,
|
||||
"temperature": 0.2
|
||||
}
|
||||
```
|
||||
|
||||
### Conversation history format
|
||||
|
||||
Each turn is stored in `context.lua` as:
|
||||
|
||||
```lua
|
||||
{ role = "system" | "user" | "assistant", content = "..." }
|
||||
```
|
||||
|
||||
The system prompt is prepended on every request and is not stored as a history turn. Exec output injected into context uses role `"user"` with a prefix tag `[exec output]`.
|
||||
|
||||
### System prompt (Phase 0 default)
|
||||
|
||||
```
|
||||
You are aish, an AI-augmented shell assistant. You help the user execute shell
|
||||
commands, write and debug code, and re-engineer software. When suggesting shell
|
||||
commands, output them on a line beginning with exactly "CMD: " so aish can
|
||||
identify and optionally execute them. Be concise. Prefer concrete actions over
|
||||
explanations unless asked.
|
||||
```
|
||||
|
||||
The `CMD:` prefix convention is the extraction contract between the model and `executor.lua`. Phase 0 presents CMD lines with a confirmation prompt before execution.
|
||||
|
||||
---
|
||||
|
||||
## 7. Execution Model (Phase 0)
|
||||
|
||||
```lua
|
||||
-- executor.lua Phase 0
|
||||
local function exec(cmd)
|
||||
local handle = io.popen(cmd .. " 2>&1", "r")
|
||||
local output = handle:read("*a")
|
||||
local ok, _, code = handle:close()
|
||||
return output, code
|
||||
end
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
## 8. Context Management (Phase 0)
|
||||
|
||||
In-memory only. No disk I/O in Phase 0.
|
||||
|
||||
```lua
|
||||
-- context.lua Phase 0 shape
|
||||
Context = {
|
||||
system_prompt = "...",
|
||||
turns = {}, -- ordered list of {role, content}
|
||||
max_turns = 40, -- sliding window; oldest non-system turns evicted
|
||||
token_budget = 4096, -- soft limit; rough char/4 estimate
|
||||
}
|
||||
```
|
||||
|
||||
Token budget enforcement is approximate in Phase 0 (character count / 4). Accurate tokenization is a Phase 2 concern.
|
||||
|
||||
When `max_turns` is reached, the oldest two turns (one user + one assistant) are evicted silently. The user is notified with a status line: `[context] oldest 2 turns evicted`.
|
||||
|
||||
---
|
||||
|
||||
## 9. readline Integration (Phase 0)
|
||||
|
||||
Minimal FFI binding wrapping three calls:
|
||||
|
||||
```lua
|
||||
ffi.cdef[[
|
||||
char *readline(const char *prompt);
|
||||
void add_history(const char *line);
|
||||
void free(void *ptr);
|
||||
]]
|
||||
```
|
||||
|
||||
- `add_history` called for every non-empty input line
|
||||
- Arrow keys and `Ctrl-R` reverse search work automatically via readline
|
||||
- Custom key bindings (`Ctrl-N` for Norris mode etc.) deferred to Phase 1
|
||||
|
||||
Prompt format:
|
||||
|
||||
```
|
||||
[aish:fast]>
|
||||
```
|
||||
|
||||
Where `fast` is the active model name as defined in `config.lua`. In Norris mode (Phase 1) this becomes:
|
||||
|
||||
```
|
||||
[aish:fast ⚡]>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. Configuration (Phase 0)
|
||||
|
||||
`config.lua` is loaded at startup with `dofile`. It returns a plain Lua table.
|
||||
|
||||
```lua
|
||||
-- config.lua example
|
||||
return {
|
||||
default_model = "fast",
|
||||
|
||||
models = {
|
||||
fast = {
|
||||
endpoint = "http://localhost:8080",
|
||||
model = "qwen2.5-coder-1.5b-q8",
|
||||
temperature = 0.2,
|
||||
},
|
||||
deep = {
|
||||
endpoint = "http://localhost:8081",
|
||||
model = "qwen2.5-coder-32b-q4",
|
||||
temperature = 0.1,
|
||||
},
|
||||
cloud = {
|
||||
endpoint = "https://api.openai.com",
|
||||
model = "gpt-4o",
|
||||
key_env = "OPENAI_API_KEY", -- read from environment at startup
|
||||
temperature = 0.2,
|
||||
},
|
||||
},
|
||||
|
||||
shell = {
|
||||
known_commands = {
|
||||
"ls", "cat", "cd", "grep", "find", "cp", "mv", "rm",
|
||||
"mkdir", "rmdir", "git", "make", "cmake", "gcc", "clang",
|
||||
"python3", "luajit", "ssh", "scp", "curl", "wget",
|
||||
},
|
||||
capture_output = true, -- inject exec output into context
|
||||
confirm_cmd = true, -- prompt before executing CMD: suggestions
|
||||
},
|
||||
|
||||
context = {
|
||||
max_turns = 40,
|
||||
token_budget = 4096,
|
||||
},
|
||||
|
||||
history = {
|
||||
dir = os.getenv("HOME") .. "/.local/share/aish",
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Config path resolution order:
|
||||
1. `--config <path>` CLI argument
|
||||
2. `$AISH_CONFIG` environment variable
|
||||
3. `~/.config/aish/config.lua`
|
||||
4. `./config.lua` (development fallback)
|
||||
|
||||
---
|
||||
|
||||
## 11. Planned Phase Sequence
|
||||
|
||||
| Phase | Key additions |
|
||||
|---|---|
|
||||
| **0** | Blocking REPL, `io.popen` exec, single model, in-memory context, meta commands |
|
||||
| **1** | SSE streaming via libcurl FFI, PTY via `forkpty` FFI, session persistence (`sessions/*.jsonl`), readline custom bindings |
|
||||
| **2** | Chuck Norris autonomous mode, destructive op heuristic (static + model), HALT/confirm gate, planning loop |
|
||||
| **3** | `memory.jsonl` summarization, startup context injection from memory, `:history` management, pruning |
|
||||
| **4** | Multi-model routing by task type, cloud fallback, context summarization via fast model on eviction |
|
||||
| **5** | Tree-sitter syntax highlighting hooks, diff-aware code injection, project-level context (file tree summary) |
|
||||
|
||||
---
|
||||
|
||||
## 12. Out of Scope (All Phases)
|
||||
|
||||
- aish does not manage llama.cpp server lifecycle
|
||||
- aish does not implement its own model inference
|
||||
- aish is not a multiplexer (no tmux-style window management)
|
||||
- aish does not provide a GUI or web interface
|
||||
- aish does not sandbox executed commands at the OS level (no namespaces, no seccomp)
|
||||
|
||||
Security posture: aish trusts the local user. The destructive-op gate in Norris mode is a workflow safeguard, not a security boundary.
|
||||
|
||||
---
|
||||
|
||||
## 13. Open Questions (Tracked)
|
||||
|
||||
| # | Question | Impact | Target Phase |
|
||||
|---|---|---|---|
|
||||
| Q1 | Token counting: use model's `/tokenize` endpoint or keep char/4 heuristic? | Context eviction accuracy | Phase 2 |
|
||||
| Q2 | Norris mode: should the planner emit a numbered step list and track progress, or re-plan after each step? | Loop structure in safety.lua | Phase 2 |
|
||||
| Q3 | Summarization at session end: automatic on `:quit`, or explicit `:save`? | UX + history.lua API | Phase 3 |
|
||||
| Q4 | Should `CMD:` extraction support multi-command blocks (here-doc style)? | executor.lua parser | Phase 1 |
|
||||
| Q5 | Cloud model routing: explicit `:model cloud` only, or automatic fallback on local timeout? | router.lua policy | Phase 4 |
|
||||
|
||||
---
|
||||
|
||||
*End of Phase 0 Manifest — aish*
|
||||
@@ -0,0 +1,25 @@
|
||||
-- executor.lua — command execution.
|
||||
-- Phase 0: io.popen with stderr merge. PTY (forkpty) lands in Phase 1.
|
||||
-- `cd` is intercepted before popen and routed through libc chdir so the
|
||||
-- working directory persists across calls. See docs/PHASE0.md §7.
|
||||
|
||||
local M = {}
|
||||
|
||||
-- Execute a shell command.
|
||||
-- Returns: (output_string, exit_code).
|
||||
function M.exec(cmd)
|
||||
error("executor.exec: not implemented (Phase 0 pending)")
|
||||
end
|
||||
|
||||
-- Intercept and apply `cd <path>` (or bare `cd` -> $HOME) without forking.
|
||||
function M.maybe_chdir(cmd)
|
||||
error("executor.maybe_chdir: not implemented (Phase 0 pending)")
|
||||
end
|
||||
|
||||
-- Extract `CMD: ...` lines from an assistant response per the broker
|
||||
-- contract (PHASE0.md §6 system prompt).
|
||||
function M.extract_cmd_lines(text)
|
||||
error("executor.extract_cmd_lines: not implemented (Phase 0 pending)")
|
||||
end
|
||||
|
||||
return M
|
||||
@@ -0,0 +1,16 @@
|
||||
-- ffi/curl.lua — libcurl easy interface binding.
|
||||
-- Phase 0: blocking POST. Phase 1: SSE streaming via WRITEFUNCTION callback.
|
||||
|
||||
local ffi = require("ffi")
|
||||
|
||||
ffi.cdef[[
|
||||
typedef void CURL;
|
||||
CURL *curl_easy_init(void);
|
||||
void curl_easy_cleanup(CURL *handle);
|
||||
int curl_easy_setopt(CURL *handle, int option, ...);
|
||||
int curl_easy_perform(CURL *handle);
|
||||
]]
|
||||
|
||||
local M = {}
|
||||
-- Phase 0 stubs; full binding lands with broker.chat() implementation.
|
||||
return M
|
||||
@@ -0,0 +1,18 @@
|
||||
-- ffi/libc.lua — shared libc bindings: errno, signal, write, read, chdir.
|
||||
|
||||
local ffi = require("ffi")
|
||||
|
||||
ffi.cdef[[
|
||||
int chdir(const char *path);
|
||||
int errno;
|
||||
char *strerror(int errnum);
|
||||
]]
|
||||
|
||||
local M = {}
|
||||
|
||||
-- Apply chdir per PHASE0.md §7 (intercepts `cd` so wd persists across popen).
|
||||
function M.chdir(path)
|
||||
error("libc.chdir: not implemented (Phase 0 pending)")
|
||||
end
|
||||
|
||||
return M
|
||||
@@ -0,0 +1,5 @@
|
||||
-- ffi/pty.lua — forkpty, openpty, waitpid bindings.
|
||||
-- Phase 0: stub. Lands in Phase 1 to enable interactive programs (vim, htop).
|
||||
|
||||
local M = {}
|
||||
return M
|
||||
@@ -0,0 +1,15 @@
|
||||
-- ffi/readline.lua — GNU readline binding.
|
||||
-- Phase 0: readline + add_history + free. Phase 1: custom key bindings.
|
||||
-- See docs/PHASE0.md §9.
|
||||
|
||||
local ffi = require("ffi")
|
||||
|
||||
ffi.cdef[[
|
||||
char *readline(const char *prompt);
|
||||
void add_history(const char *line);
|
||||
void free(void *ptr);
|
||||
]]
|
||||
|
||||
local M = {}
|
||||
-- Phase 0 stubs; wired with the REPL implementation.
|
||||
return M
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
-- history.lua — persistent session log + memory.jsonl.
|
||||
-- Phase 0: NO disk I/O. This module is a stub placeholder so module names are
|
||||
-- stable when Phase 1 lands the persistence layer.
|
||||
-- See docs/PHASE0.md §11 (Phase 1).
|
||||
|
||||
local M = {}
|
||||
|
||||
function M.open_session(dir)
|
||||
error("history.open_session: not implemented (Phase 1)")
|
||||
end
|
||||
|
||||
function M.append_turn(session, turn)
|
||||
error("history.append_turn: not implemented (Phase 1)")
|
||||
end
|
||||
|
||||
function M.summarize_and_close(session, broker)
|
||||
error("history.summarize_and_close: not implemented (Phase 3)")
|
||||
end
|
||||
|
||||
return M
|
||||
@@ -0,0 +1,32 @@
|
||||
-- main.lua — entry point
|
||||
-- Phase 0: arg parsing, config load, REPL start.
|
||||
-- See docs/PHASE0.md §4, §10.
|
||||
|
||||
local function load_config()
|
||||
-- Resolution order per PHASE0.md §10:
|
||||
-- 1. --config <path> 2. $AISH_CONFIG
|
||||
-- 3. ~/.config/aish/config.lua 4. ./config.lua
|
||||
-- Phase 0 stub: pick the first existing path; no CLI parsing yet.
|
||||
local home = os.getenv("HOME") or ""
|
||||
local candidates = {
|
||||
os.getenv("AISH_CONFIG"),
|
||||
home .. "/.config/aish/config.lua",
|
||||
"./config.lua",
|
||||
}
|
||||
for _, path in ipairs(candidates) do
|
||||
if path then
|
||||
local f = io.open(path, "r")
|
||||
if f then f:close(); return dofile(path), path end
|
||||
end
|
||||
end
|
||||
error("aish: no config.lua found in any standard location")
|
||||
end
|
||||
|
||||
local function main()
|
||||
local config, config_path = load_config()
|
||||
io.stderr:write(("aish: loaded config from %s\n"):format(config_path))
|
||||
local repl = require("repl")
|
||||
repl.run(config)
|
||||
end
|
||||
|
||||
main()
|
||||
@@ -0,0 +1,19 @@
|
||||
-- renderer.lua — output formatting and ANSI sequences.
|
||||
-- Phase 0: minimal — assistant text plain-printed; CMD: lines highlighted;
|
||||
-- exec output framed. Syntax highlighting hooks land in Phase 5.
|
||||
|
||||
local M = {}
|
||||
|
||||
function M.assistant(text)
|
||||
error("renderer.assistant: not implemented (Phase 0 pending)")
|
||||
end
|
||||
|
||||
function M.exec_output(output, exit_code)
|
||||
error("renderer.exec_output: not implemented (Phase 0 pending)")
|
||||
end
|
||||
|
||||
function M.status(line)
|
||||
error("renderer.status: not implemented (Phase 0 pending)")
|
||||
end
|
||||
|
||||
return M
|
||||
@@ -0,0 +1,25 @@
|
||||
-- repl.lua — readline loop, input dispatch, prompt rendering.
|
||||
-- See docs/PHASE0.md §5, §9.
|
||||
|
||||
local M = {}
|
||||
|
||||
-- Phase 0 stub.
|
||||
function M.run(config)
|
||||
error("repl.run: not implemented (Phase 0 pending)")
|
||||
end
|
||||
|
||||
-- Meta command table per PHASE0.md §5.2.
|
||||
M.meta_commands = {
|
||||
quit = function(ctx) os.exit(0) end,
|
||||
q = function(ctx) os.exit(0) end,
|
||||
clear = nil, -- TODO Phase 0 impl
|
||||
reset = nil,
|
||||
model = nil,
|
||||
models = nil,
|
||||
history = nil,
|
||||
exec = nil,
|
||||
ask = nil,
|
||||
help = nil,
|
||||
}
|
||||
|
||||
return M
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
-- router.lua — task classifier: meta / shell / AI.
|
||||
-- See docs/PHASE0.md §5.
|
||||
|
||||
local M = {}
|
||||
|
||||
-- Classify an input line.
|
||||
-- Returns one of: "meta", "shell", "ai" plus the (possibly stripped) payload.
|
||||
function M.classify(line, config)
|
||||
error("router.classify: not implemented (Phase 0 pending)")
|
||||
end
|
||||
|
||||
-- Default known-command allowlist seeds the heuristic in §5.1.
|
||||
-- Final list is config.shell.known_commands at runtime.
|
||||
return M
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
-- safety.lua — destructive op heuristic + Chuck Norris autonomous gate.
|
||||
-- Phase 0: stub. Lands in Phase 2.
|
||||
-- See docs/PHASE0.md §11 (Phase 2), §12 (security posture is workflow-not-OS).
|
||||
|
||||
local M = {}
|
||||
|
||||
-- Returns true if cmd matches the destructive-op heuristic and should HALT
|
||||
-- in Norris mode pending user confirmation.
|
||||
function M.is_destructive(cmd)
|
||||
error("safety.is_destructive: not implemented (Phase 2)")
|
||||
end
|
||||
|
||||
-- Norris mode planning loop entry point.
|
||||
function M.norris_step(plan, broker, executor)
|
||||
error("safety.norris_step: not implemented (Phase 2)")
|
||||
end
|
||||
|
||||
return M
|
||||
Reference in New Issue
Block a user