commit 431020773848e131c59bd8424011bc33fb5a547c Author: Claude (noether) Date: Sat May 9 23:16:07 2026 +0000 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) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..da023c4 --- /dev/null +++ b/.gitignore @@ -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 diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..205f5f6 --- /dev/null +++ b/CLAUDE.md @@ -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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..c4d9e19 --- /dev/null +++ b/README.md @@ -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 ] +``` + +## 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. diff --git a/broker.lua b/broker.lua new file mode 100644 index 0000000..f91b0ca --- /dev/null +++ b/broker.lua @@ -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 diff --git a/config.lua b/config.lua new file mode 100644 index 0000000..70637b1 --- /dev/null +++ b/config.lua @@ -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", + }, +} diff --git a/context.lua b/context.lua new file mode 100644 index 0000000..82f2ca3 --- /dev/null +++ b/context.lua @@ -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 diff --git a/docs/PHASE0.md b/docs/PHASE0.md new file mode 100644 index 0000000..863bc3c --- /dev/null +++ b/docs/PHASE0.md @@ -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 ` | Switch active model (must exist in config) | +| `:models` | List configured models and active selection | +| `:history` | Print conversation turns for current session | +| `:exec ` | Force shell execution regardless of heuristic | +| `:ask ` | 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:///v1/chat/completions +Content-Type: application/json + +{ + "model": "", + "messages": [ ], + "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 ` 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* diff --git a/executor.lua b/executor.lua new file mode 100644 index 0000000..9d1036d --- /dev/null +++ b/executor.lua @@ -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 ` (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 diff --git a/ffi/curl.lua b/ffi/curl.lua new file mode 100644 index 0000000..aaa2a89 --- /dev/null +++ b/ffi/curl.lua @@ -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 diff --git a/ffi/libc.lua b/ffi/libc.lua new file mode 100644 index 0000000..e22c5c5 --- /dev/null +++ b/ffi/libc.lua @@ -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 diff --git a/ffi/pty.lua b/ffi/pty.lua new file mode 100644 index 0000000..0cebc8b --- /dev/null +++ b/ffi/pty.lua @@ -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 diff --git a/ffi/readline.lua b/ffi/readline.lua new file mode 100644 index 0000000..9669b4b --- /dev/null +++ b/ffi/readline.lua @@ -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 diff --git a/history.lua b/history.lua new file mode 100644 index 0000000..7578304 --- /dev/null +++ b/history.lua @@ -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 diff --git a/main.lua b/main.lua new file mode 100644 index 0000000..6d7a6af --- /dev/null +++ b/main.lua @@ -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 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() diff --git a/renderer.lua b/renderer.lua new file mode 100644 index 0000000..2456fd4 --- /dev/null +++ b/renderer.lua @@ -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 diff --git a/repl.lua b/repl.lua new file mode 100644 index 0000000..e81e369 --- /dev/null +++ b/repl.lua @@ -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 diff --git a/router.lua b/router.lua new file mode 100644 index 0000000..99b2b0c --- /dev/null +++ b/router.lua @@ -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 diff --git a/safety.lua b/safety.lua new file mode 100644 index 0000000..1cef296 --- /dev/null +++ b/safety.lua @@ -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