repl: expand_mentions tiered @<r1>..<r2> diff retry (Phase 6 commit #4)
Per A6 (tiered resolution): @<token> tries file lookup first; if the
file doesn't exist AND the token contains "..", retry as a git
ref-range and substitute with a fenced `diff` block. Preserves the
existing peel-on-trailing-punct logic (e.g., `@HEAD~1..HEAD,` peels
the comma, resolves the ref, restores the comma after the closing
fence).
Resolution order for @<token>:
1. io.open(token, "rb") -- file lookup, with trailing-punct peel
2. if (1) fails and token contains "..":
git --no-pager -c color.ui=never diff <r1>..<r2>
on exit 0 + non-empty body: substitute as ```diff fenced block
3. else: leave literal `@token` + emit "[aish] @X: not found" status
Examples:
@README.md -> file (path branch)
@../sibling.txt -> file (path branch; `..` only triggers retry
when path lookup FAILS, so existing
paths with `..` segments are unaffected)
@HEAD~1..HEAD -> diff (path fails, ref succeeds)
@origin/main..feature -> diff (path fails — no such literal file;
ref succeeds; `/` in ref is fine because
we don't use the path's `/`-absence as
a discriminator)
@nonsense..gibberish -> literal preserved (both fail)
Required restructuring:
- _shq and _git_clean_cmd lifted from M.run closure scope to module
scope (above expand_mentions). Single source of truth for the
B1 prefix shared with commit #3's :diff. The in-M.run duplicates
are removed.
- expand_mentions now references `executor` (already required at
module scope on line 7) for the diff retry.
Status messages updated:
- File expansion: "@<path> expanded (N bytes, truncated)" (existing)
- Diff expansion: "@<path> expanded (N bytes, diff)" (new)
Tested with the 7 existing #7 cases + 7 new diff-retry cases (14/14):
ref-range expansion shape, body contains `diff --git`, trailing
prose preserved, @../path stays as file (not diff), neither-path-
nor-ref preserves literal, trailing-comma peel composes with ref
retry.
Regression: test_safety 87/87, test_router_model 31/31, repl loads.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -45,6 +45,16 @@ local function _read_truncated(path)
|
||||
.. ("\n... [%d bytes elided] ...\n"):format(#content - MENTION_HEAD - MENTION_TAIL)
|
||||
.. tail, true
|
||||
end
|
||||
|
||||
-- ---------------------------------------------------------------- shared shell helpers
|
||||
-- Lifted from M.run closure scope so expand_mentions (module-scope) can also
|
||||
-- use them for the @<r1>..<r2> diff-retry path. Same single source of truth
|
||||
-- for the B1 git invocation prefix; commits #3 and #4 both call _git_clean_cmd.
|
||||
local function _shq(s) return "'" .. (s or ""):gsub("'", [['\'']]) .. "'" end
|
||||
local function _git_clean_cmd(subcmd_and_args)
|
||||
return "git --no-pager -c color.ui=never " .. subcmd_and_args
|
||||
end
|
||||
|
||||
local function expand_mentions(line, on_status)
|
||||
-- Walk the line; for each "@<path>" preceded by SOL or whitespace,
|
||||
-- attempt to read and substitute. Missing files leave the literal
|
||||
@@ -72,13 +82,35 @@ local function expand_mentions(line, on_status)
|
||||
end
|
||||
if path ~= "" then
|
||||
local content, truncated = _read_truncated(path)
|
||||
local lang_override = nil
|
||||
-- Phase 6 / A6: tiered resolution — if path lookup
|
||||
-- failed AND token contains "..", try as a git diff
|
||||
-- ref-range. `@HEAD~1..HEAD` and `@origin/main..feature`
|
||||
-- both fall through to this branch when no such file
|
||||
-- exists. `@../sibling.txt` resolves as path first
|
||||
-- and never reaches this retry.
|
||||
if not content and path:find("..", 1, true) then
|
||||
local r1, r2 = path:match("^(.-)%.%.(.+)$")
|
||||
if r1 and r2 and r1 ~= "" and r2 ~= "" then
|
||||
local out_diff, code = executor.exec(
|
||||
_git_clean_cmd(("diff %s..%s 2>/dev/null")
|
||||
:format(_shq(r1), _shq(r2))))
|
||||
if code == 0 and out_diff and out_diff:match("%S") then
|
||||
content = out_diff
|
||||
lang_override = "diff"
|
||||
end
|
||||
end
|
||||
end
|
||||
if content then
|
||||
local lang = lang_override or _lang_of(path)
|
||||
if on_status then
|
||||
on_status(("@%s expanded (%d bytes%s)"):format(
|
||||
path, #content, truncated and ", truncated" or ""))
|
||||
path, #content,
|
||||
truncated and ", truncated"
|
||||
or (lang_override == "diff" and ", diff" or "")))
|
||||
end
|
||||
out[#out + 1] = ("```%s path=%s\n%s\n```%s"):format(
|
||||
_lang_of(path), path, content, trail)
|
||||
lang, path, content, trail)
|
||||
i = path_end
|
||||
else
|
||||
if on_status then
|
||||
@@ -669,7 +701,8 @@ function M.run(config)
|
||||
-- receives the command on stdin and AISH_CMD/AISH_TURN/AISH_CWD as
|
||||
-- env vars. Non-zero exit on pre_cmd aborts. post_cmd exit is
|
||||
-- ignored; its stdout is logged via renderer.status.
|
||||
local function _shq(s) return "'" .. (s or ""):gsub("'", [['\'']]) .. "'" end
|
||||
-- _shq lifted to module scope (above expand_mentions) so the
|
||||
-- @-mention diff retry can share the same quoter.
|
||||
local function _run_hook(script, cmd, want_output)
|
||||
local cwd = (require("ffi.libc").getcwd()) or os.getenv("PWD") or "?"
|
||||
local pipeline = string.format(
|
||||
@@ -686,14 +719,10 @@ function M.run(config)
|
||||
end
|
||||
end
|
||||
|
||||
-- Phase 6 (B1): every git invocation that flows back into context
|
||||
-- MUST suppress git's interactive pager + color output. Forkpty
|
||||
-- makes git think stdout is a TTY, which enables both. Helper
|
||||
-- prepends `git --no-pager -c color.ui=never <subcmd_and_args>`;
|
||||
-- used by :diff and the @<r1>..<r2> @-mention path in commit #4.
|
||||
local function _git_clean_cmd(subcmd_and_args)
|
||||
return "git --no-pager -c color.ui=never " .. subcmd_and_args
|
||||
end
|
||||
-- _git_clean_cmd lifted to module scope (above expand_mentions);
|
||||
-- shared with the @<r1>..<r2> @-mention diff retry. Same B1
|
||||
-- invariant: every git invocation that flows back into context
|
||||
-- runs with `--no-pager -c color.ui=never`.
|
||||
|
||||
-- Phase 6 (§6 + N4): project file-tree scanner. Prefers
|
||||
-- `git -C <dir> ls-files --cached --others --exclude-standard`
|
||||
|
||||
Reference in New Issue
Block a user