-- 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 = {} 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, ~/foo, bare ~. Quoted / -- escaped paths are intentionally out of scope in Phase 0. ~ is included -- for symmetry with executor.maybe_chdir, which expands ~ on `cd ~/foo`. local function path_like(token) return token == "~" or token:sub(1, 1) == "/" or token:sub(1, 2) == "./" 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 return M