From 90be51c17186b53833bbb14aa1c88b09c0ea9c16 Mon Sep 17 00:00:00 2001 From: "Claude (noether)" Date: Sat, 9 May 2026 23:20:07 +0000 Subject: [PATCH] docs: rewrite README + CLAUDE for handoff to a dedicated session MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit README is now self-contained for a human reader landing on the repo cold: project value-prop, status, quick-orientation reading order, directory layout, build/runtime deps, run + config invocation, and a pointer to CLAUDE.md for contribution norms. CLAUDE.md is rewritten as the substrate a fresh Claude session needs to pick up Phase 0→1 implementation without prior conversation context: - Reading order (PHASE0.md → README → config.lua) - Phase-loop discipline (8+1 with loopbacks) - Eight invariants from PHASE0.md called out as non-negotiable without manifest amendment - Bottom-up implementation order for Phase 0 (libc → readline → curl → context → executor → router → broker → renderer → repl → main) - Testing approach without a test framework - Open question on JSON library (dkjson recommended; needs §3 amendment) - Ambiguity handling pattern (ask vs log-in-§13 vs stop-and-ask) - Commit style + Co-Authored-By trailer template - Model-class caveat: small Q4 coder models have output variance, validate before exec, confirm_cmd defaults exist for this reason - Push credential note for sessions without ssh-keys-on-Gitea Co-Authored-By: Claude Opus 4.7 (1M context) --- CLAUDE.md | 224 ++++++++++++++++++++++++++++++++++++++++++++++++------ README.md | 115 ++++++++++++++++++++++++---- 2 files changed, 299 insertions(+), 40 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 205f5f6..7e87174 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,37 +1,213 @@ -# CLAUDE.md — aish project conventions +# CLAUDE.md — aish project handoff -This file is auto-loaded when any Claude session opens a clone of this repo. +You are continuing work on **aish**, an AI-augmented conversational shell +implemented in LuaJIT. This file is auto-loaded when a Claude session opens +a clone of this repo. Read it once at session start. -## 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. +## 1. Read these first, in order -## Phase loop +1. **`docs/PHASE0.md`** — locked substrate. Every architectural decision is + here. Do not contradict it without amending it. +2. **`README.md`** — human-facing project summary and orientation. +3. **`config.lua`** — the runtime model registry. Endpoints reflect a + specific LAN; the user may have already adapted yours. -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. +If a question seems open after reading those three, **ask the user a +single focused question** rather than guessing. Cost of asking is one +turn; cost of building on a wrong assumption is half a phase. -Loopbacks per the canon: 3→1, 7→4, any→0. +--- -## Module structure invariant +## 2. Where you are in the phase loop -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. +aish follows an **8(+1) phase loop**: -## No C extensions +``` +0 substrate → 1 formulate → 2 analyze → 3 baseline → 4 plan + → 5 review → 6 implement → 7 verify → 8 memory-update + (+1 reflect) +``` -LuaJIT FFI only. If you reach for a C extension to "make this easier", -stop. The FFI bindings under `ffi/` are extended in place. +Loopbacks: +- `3 → 1` if baseline data invalidates the formulation +- `7 → 4` if verification fails — fix the plan, not the substrate +- `any → 0` if a substrate fact turns out to be wrong -## Commit style +Don't skip phases. Phase 0 is done (the manifest is locked); your starting +position depends on what's already in the tree. -Short imperative subject, file-scoped. Example: `executor: add cd -interception via libc chdir`. Body explains the *why* if non-obvious. +Check the state by reading `docs/` and `git log --oneline`. If only +PHASE0.md exists in `docs/` and every module raises NotImplemented, **you +are at Phase 0 → Phase 1 transition**: the substrate is locked, no module +is implemented, your job is to write the Phase 0 implementation per the +manifest. -Co-Authored-By trailers on Claude commits per project canon. +If you find phase docs `docs/PHASE1.md`, `docs/PHASE2.md` etc., follow +their state. + +--- + +## 3. Source-of-truth invariants — do not violate without amending PHASE0.md + +These are not stylistic preferences; they are decisions that downstream +phases depend on: + +| Invariant | Where it's locked | +|---|---| +| LuaJIT 2.x only — no PUC-Rio Lua-only constructs | §3 | +| FFI only — no compiled C extensions, no `luarocks` packages | §3 | +| Module file names in §4 are stable across phases | §4 | +| `/v1/chat/completions` is the broker contract | §6 | +| `CMD:` (exact prefix, single space) is the command-extraction marker | §6 | +| Config is plain Lua loaded with `dofile`, not JSON/YAML | §3, §10 | +| `cd` is intercepted via libc `chdir`, not delegated to `popen` | §7 | +| Phase 0 has no disk I/O for history (memory only) | §8 | + +If a Phase N implementation needs to break one of these, **amend +PHASE0.md in the same commit and call out the change in the commit +message**. Don't silently diverge. + +--- + +## 4. Implementation order for Phase 0 + +Bottom-up beats top-down for this codebase. Suggested ordering: + +1. `ffi/libc.lua` — `chdir`, `strerror` work; verifiable in isolation. +2. `ffi/readline.lua` — `readline()`, `add_history()`, `free()` work; test + with a tiny REPL stub before wiring `repl.lua`. +3. `ffi/curl.lua` — easy interface, blocking POST with response capture + into a Lua string. Test against any local llama-server with a curl + one-liner side-by-side. +4. `context.lua` — pure data structure, trivial to unit-test. +5. `executor.lua` — `popen` wrapper, `cd` interception (uses libc.chdir), + `CMD:` line extraction. +6. `router.lua` — pure function; classify(line, config) → (kind, payload). +7. `broker.lua` — uses `ffi/curl.lua` + JSON encode/decode. **You will + need a JSON library** — see §6 below. +8. `renderer.lua` — output formatting; trivial. +9. `repl.lua` — wires everything via the dispatch table. +10. `main.lua` — already mostly there; finalize once `repl.run` exists. + +Don't write all ten in one commit. One commit per module, build the +chain by passing through; each commit should leave the tree in a state +where `luajit main.lua` either runs further than the previous commit or +fails with the next NotImplemented. + +--- + +## 5. Testing approach + +There is no test framework dependency by design. Testing is per-module +ad-hoc with `luajit -e 'local m = require("module"); ...'` from the repo +root, or a smoke `luajit main.lua` after each module lands. + +For broker testing without burning model time: any of the local +llama-servers in `config.lua` will respond to a hand-crafted POST. Use +`curl -sS http://dirac.fritz.box:8081/v1/chat/completions -d '{...}'` to +generate a known-good reference, then compare your FFI output. + +--- + +## 6. JSON encode/decode — undecided + +The manifest doesn't pick a JSON library. LuaJIT 2.x has no built-in JSON. +Options: + +- **`dkjson`** — pure Lua, single file, vendor it under `vendor/dkjson.lua`. + Slow but no dependency. +- **`cjson`** — fast, but it's a C extension. Violates §3 (no compiled + extensions). Skip unless you amend the manifest. +- **Hand-rolled minimal encoder** — viable since the broker payload shape is + small and well-defined; ~50 LOC. + +Recommend dkjson vendored. Decide and add a note to PHASE0.md §3 in the +same commit. + +--- + +## 7. When you hit ambiguity + +The user (mfritsche) prefers being asked over being assumed-about. The +phrase "Ask readily — prefer quick question over 3+ guessing attempts" +applies. Concrete pattern: + +- If the question affects only the current commit: ask. +- If it affects a future phase: log it in `docs/PHASE0.md` §13 (Open + Questions) with target phase, keep working. +- If it affects multiple existing modules: stop, ask before refactoring. + +Don't suggest pausing the session. Don't suggest "let's pick this up +tomorrow". Don't pre-emptively defer work the user hasn't asked you to +defer. The user controls pace. + +--- + +## 8. Commit style + +Imperative subject, file-scoped where possible. Examples: + +- `executor: implement io.popen wrapper with stderr merge` +- `ffi/curl: blocking POST with header list and response buffer` +- `repl: wire router → executor → broker dispatch` +- `phase0 amendment: vendor dkjson under vendor/` + +Body explains the *why* if non-obvious. Reference PHASE0.md sections by +number when relevant. + +Co-Authored-By trailer on Claude-authored commits: + +``` +Co-Authored-By: Claude Opus 4.7 (1M context) +``` + +(or whichever model you actually are; check before substituting.) + +--- + +## 9. What aish does NOT do — out of scope, all phases + +These are listed in PHASE0.md §12. Briefly: + +- Does not manage llama.cpp lifecycle (assumed externally running) +- Does not implement model inference +- Is not a multiplexer (no tmux semantics) +- Is not a sandbox (no namespaces, no seccomp) + +If you find yourself implementing any of those, stop — that's a different +project. + +--- + +## 10. The model serving aish (typically) + +aish targets local llama.cpp endpoints. The committed `config.lua` +references the user's home network (`dirac.fritz.box`, +`hossenfelder.fritz.box`). The user's other Claude sessions have +established that small Q4_K_M models (Qwen2.5-Coder-7B and similar) have +**variance issues on code generation tasks** — the same prompt can yield +both correct and broken code across rolls. Practical implications for +aish: + +- Default `temperature` to `0.2` or lower for code tasks. +- Don't assume the model output is correct — validate before exec. +- The `confirm_cmd = true` default in `config.shell` is there for this + reason; don't disable it without a deliberate UX change. + +This isn't paranoia, it's a measured property of the local model class +the user runs. + +--- + +## 11. Identity + +If this is a session running on a fleet host other than the user's +primary Claude window, your Gitea identity is `claude-`. For +aish (a non-PR-flow repo), commits as that identity are fine without a PR. +If you need to push and lack credentials, use a Gitea Personal Access +Token in the URL: `git push https://:@git.reauktion.de/marfrit/aish.git main`. + +The user has marfrit-level credentials available via a separate channel +if needed for repo-admin operations. diff --git a/README.md b/README.md index c4d9e19..d460cdb 100644 --- a/README.md +++ b/README.md @@ -2,35 +2,118 @@ **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. +A single REPL that interleaves shell command execution and language-model +conversation, backed by a llama.cpp HTTP broker. Implementation is LuaJIT +2.x with FFI bindings to libcurl, GNU readline, and libc — no C extensions, +no build step, one source tree. -Implementation: LuaJIT 2.x with FFI bindings (libcurl, GNU readline, libc), -no compiled extensions, no build step. +## Why + +Three flows that currently live in three windows fold into one: + +1. **"Run this command and show me the output"** — fast feedback loop, no + copy-paste between terminal and chat. +2. **"Explain or write code based on the output we just looked at"** — + exec output is automatically injected into the model's context. +3. **"Plan and execute a multi-step task with confirmation gates"** — + landing in Phase 2 as Chuck Norris autonomous mode. + +aish is not a wrapper around bash. It's a first-class interactive +environment where the shell is one of several execution channels. ## 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. +| Component | State | +|---|---| +| Repository skeleton | ✅ in this commit | +| Phase 0 manifest | ✅ [`docs/PHASE0.md`](docs/PHASE0.md) — locked | +| Phase 0 implementation | 🔜 next session | +| Phase 1+ | 📋 enumerated in PHASE0.md §11 | + +Every module file currently raises `not implemented (Phase 0 pending)` +when called. `luajit main.lua` fails loudly at the first un-implemented +function, never silently. + +## Quick orientation + +| Read this | If you want to know | +|---|---| +| `docs/PHASE0.md` §1–2 | What aish is and what Phase 0 ships | +| `docs/PHASE0.md` §3 | Technology decisions (LuaJIT, FFI, readline, libcurl, llama.cpp) | +| `docs/PHASE0.md` §4 | Directory layout — these file names are stable across all phases | +| `docs/PHASE0.md` §5 | How input is dispatched (meta / shell / AI) | +| `docs/PHASE0.md` §6 | Broker contract: `/v1/chat/completions`, `CMD:` extraction | +| `docs/PHASE0.md` §10 | Config schema and resolution order | +| `docs/PHASE0.md` §11 | Phase sequence (what lands when) | +| `docs/PHASE0.md` §13 | Open questions, tracked per phase | +| `CLAUDE.md` | Project conventions for AI-assisted contributors | ## Directory layout -See `docs/PHASE0.md` §4. File names are stable across phases — later phases -fill in module bodies, they do not rename files. +``` +aish/ +├── main.lua # entry point +├── repl.lua # readline loop, dispatch, prompt +├── broker.lua # llama.cpp HTTP client +├── router.lua # input classifier (meta/shell/AI) +├── executor.lua # command exec + CMD: extraction +├── context.lua # in-memory turn history +├── history.lua # disk persistence (Phase 1+) +├── safety.lua # destructive-op gate (Phase 2+) +├── renderer.lua # output formatting +├── config.lua # default model registry + preferences +├── ffi/ +│ ├── curl.lua # libcurl easy interface +│ ├── readline.lua # GNU readline +│ ├── pty.lua # forkpty (Phase 1+) +│ └── libc.lua # chdir, errno, strerror +└── docs/ + └── PHASE0.md # locked substrate +``` + +## Build / runtime dependencies + +System packages (Debian / ALARM / Arch names): + +- `luajit` (>= 2.0) +- `libcurl4` / `libcurl-openssl-3` runtime +- `libreadline8` runtime +- `libc6` runtime (always present) + +No compilation, no `luarocks`, no `make`. Just `luajit main.lua`. ## Running -Not runnable yet. Once Phase 0 lands: +Once Phase 0 ships: ```sh -luajit main.lua [--config ] +luajit main.lua # uses ~/.config/aish/config.lua +luajit main.lua --config ./config.lua # explicit config path +AISH_CONFIG=/path/to/config.lua luajit main.lua ``` +Config resolution order is documented in `docs/PHASE0.md` §10. + +## Configuration + +`config.lua` is a Lua file returning a single table. The committed +`config.lua` in this repo is both the canonical example and the +development-fallback config (lowest precedence). Copy it to +`~/.config/aish/config.lua` and edit endpoints to your local llama.cpp +servers, or point `AISH_CONFIG` at your own. + +The default endpoints assume mfritsche's home network: +- `fast` → `dirac.fritz.box:8081` (Qwen2.5-Coder-7B q4 8k ctx) +- `deep` → `dirac.fritz.box:8080` (Qwen2.5-Coder-7B q4 32k ctx) +- `cloud` → `hossenfelder.fritz.box:8082` (forwards to OpenRouter) + +Replace these with your own llama.cpp endpoints if you're not on that LAN. + +## License + +Not yet selected. Default-private until decided. + ## 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. +See [`CLAUDE.md`](CLAUDE.md) for contribution conventions, commit style, +and the phase-loop discipline this project follows.