From 91187d2302d840b4d92bb9a4df8e772f2061efeb Mon Sep 17 00:00:00 2001 From: Markus Fritsche Date: Sun, 10 May 2026 12:05:25 +0000 Subject: [PATCH] router: classify(line, config) -> (kind, payload) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 0 implementation per PHASE0.md §5. Pure function. Three kinds: "meta" — line starts with ":", payload is the rest "shell" — line starts with "$" (override, $ stripped), OR first word is in config.shell.known_commands, OR first word is path-like (`./`, `../`, `/`) "ai" — everything else (including empty / whitespace-only; the repl loop skips empty payloads before dispatching) Path-like detection is deliberately conservative in Phase 0: anchored prefixes only, no quoted-path or shell-glob handling. Q4 in §13 tracks multi-command CMD: blocks; this router doesn't see those (it only classifies user input lines, not assistant output). Smoke covers all branches plus a nil-config fallthrough. Co-Authored-By: Claude Opus 4.7 (1M context) --- router.lua | 61 ++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 55 insertions(+), 6 deletions(-) diff --git a/router.lua b/router.lua index 99b2b0c..9d44f5d 100644 --- a/router.lua +++ b/router.lua @@ -1,14 +1,63 @@ -- router.lua — task classifier: meta / shell / AI. -- See docs/PHASE0.md §5. +-- +-- Pure function. Takes (line, config). Returns (kind, payload). +-- kind : "meta" | "shell" | "ai" +-- payload: the (possibly stripped) line that the dispatcher should act on. +-- +-- Empty / whitespace-only lines are returned as ("ai", ""); the repl loop +-- skips them before dispatching. 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)") +local function trim(s) + return (s:gsub("^%s+", ""):gsub("%s+$", "")) +end + +local function first_word(s) + return s:match("^(%S+)") or "" +end + +local function known_commands_set(config) + local set = {} + local list = config and config.shell and config.shell.known_commands or {} + for _, c in ipairs(list) do set[c] = true end + return set +end + +-- §5.1 path-like: ./foo, ../foo, /usr/bin/foo. Quoted/escaped paths are +-- intentionally out of scope in Phase 0. +local function path_like(token) + return token:sub(1, 1) == "/" + or token:sub(1, 2) == "./" + or token:sub(1, 3) == "../" +end + +function M.classify(line, config) + line = trim(line or "") + if line == "" then return "ai", "" end + + -- meta: ":" prefix + if line:sub(1, 1) == ":" then + return "meta", line:sub(2) + end + + -- shell explicit override: "$" prefix + if line:sub(1, 1) == "$" then + return "shell", trim(line:sub(2)) + end + + local first = first_word(line) + local known = known_commands_set(config) + + -- known-command allowlist + if known[first] then return "shell", line end + + -- path-like first token + if path_like(first) then return "shell", line end + + -- everything else -> AI + return "ai", line end --- Default known-command allowlist seeds the heuristic in §5.1. --- Final list is config.shell.known_commands at runtime. return M