router: classify(line, config) -> (kind, payload)
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) <noreply@anthropic.com>
This commit is contained in:
+55
-6
@@ -1,14 +1,63 @@
|
|||||||
-- router.lua — task classifier: meta / shell / AI.
|
-- router.lua — task classifier: meta / shell / AI.
|
||||||
-- See docs/PHASE0.md §5.
|
-- 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 M = {}
|
||||||
|
|
||||||
-- Classify an input line.
|
local function trim(s)
|
||||||
-- Returns one of: "meta", "shell", "ai" plus the (possibly stripped) payload.
|
return (s:gsub("^%s+", ""):gsub("%s+$", ""))
|
||||||
function M.classify(line, config)
|
end
|
||||||
error("router.classify: not implemented (Phase 0 pending)")
|
|
||||||
|
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
|
end
|
||||||
|
|
||||||
-- Default known-command allowlist seeds the heuristic in §5.1.
|
|
||||||
-- Final list is config.shell.known_commands at runtime.
|
|
||||||
return M
|
return M
|
||||||
|
|||||||
Reference in New Issue
Block a user