repl: :diff meta + _git_clean_cmd helper (Phase 6 commit #3)
User-driven git diff injection. The model sees the diff on the next
ask_ai turn through the existing exec_output channel.
Changes:
- _git_clean_cmd(subcmd_and_args) helper near _scan_project_tree.
B1: every git invocation that flows into context MUST use
`--no-pager -c color.ui=never`. Forkpty makes git think stdout
is a TTY, enabling both color and the pager's keypad/line-clear
escapes — these would pollute the captured context block. The
helper is the single chokepoint; commit #4's @<r1>..<r2> retry
will reuse it.
- :diff [<args>] meta:
- Reads cwd at meta invocation (R6: differs from :tree's
scan-time cwd capture; documented in §5).
- Runs `_git_clean_cmd("diff " .. args)` via executor.exec.
- Empty output -> "(no diff): <label>" status, no context append.
- Non-zero exit -> "diff failed (exit N): <label>" status,
no context append. git's stderr already streamed to the
user via executor.exec's live multiplex, so the failure
reason is visible.
- Success -> appends "[diff <label>]\n<output>" via
ctx:append_exec_output. Label is "(working tree)" for empty
args, else verbatim args.
- Status confirms injection size: "diff injected: <label> (N bytes)".
- HELP gains :diff line with three example arg shapes; N3-resolved
(no `staged` alias — the meta is thin pass-through to git's grammar).
Smoke verified across four scenarios in an ephemeral test repo:
- Working-tree dirty -> 110-byte diff injected, no ANSI escapes
- --cached -> 118-byte staged diff injected, clean
- garbage..nonexistent -> exit 128, status + skip
- Clean working tree -> "(no diff)", status + skip
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:
@@ -151,6 +151,8 @@ Meta commands:
|
|||||||
:tree [<depth>] scan cwd file-tree, inject as [project] block in system prompt
|
:tree [<depth>] scan cwd file-tree, inject as [project] block in system prompt
|
||||||
:tree refresh re-scan with last opts (or config defaults)
|
:tree refresh re-scan with last opts (or config defaults)
|
||||||
:tree off clear the [project] block
|
:tree off clear the [project] block
|
||||||
|
:diff [<git-args>] git diff <args> -> inject as [diff ...] exec_output
|
||||||
|
examples: :diff :diff --cached :diff main..feature
|
||||||
:delegate <p> <prompt> one-shot sub-broker call to preset <p>; prints reply
|
:delegate <p> <prompt> one-shot sub-broker call to preset <p>; prints reply
|
||||||
:help this message
|
:help this message
|
||||||
]]
|
]]
|
||||||
@@ -684,6 +686,15 @@ function M.run(config)
|
|||||||
end
|
end
|
||||||
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
|
||||||
|
|
||||||
-- Phase 6 (§6 + N4): project file-tree scanner. Prefers
|
-- Phase 6 (§6 + N4): project file-tree scanner. Prefers
|
||||||
-- `git -C <dir> ls-files --cached --others --exclude-standard`
|
-- `git -C <dir> ls-files --cached --others --exclude-standard`
|
||||||
-- when <dir> is inside a git repo (free .gitignore honor);
|
-- when <dir> is inside a git repo (free .gitignore honor);
|
||||||
@@ -1763,6 +1774,29 @@ function M.run(config)
|
|||||||
-- :tree <N> scan with depth=N; cached as _project_opts
|
-- :tree <N> scan with depth=N; cached as _project_opts
|
||||||
-- :tree refresh re-scan with cached opts; else config defaults
|
-- :tree refresh re-scan with cached opts; else config defaults
|
||||||
-- :tree off clear ctx.project AND ctx._project_opts
|
-- :tree off clear ctx.project AND ctx._project_opts
|
||||||
|
-- Phase 6: :diff meta — `git diff <args>` (B1-clean), appends as
|
||||||
|
-- [diff <args>]\n<output> exec_output. Reads cwd at invocation
|
||||||
|
-- time (R6: differs from :tree's scan-time cwd capture). Empty
|
||||||
|
-- diff or git failure emits status and skips — never pollutes
|
||||||
|
-- context with empty or error noise.
|
||||||
|
meta.diff = function(args)
|
||||||
|
args = (args or ""):gsub("^%s+", ""):gsub("%s+$", "")
|
||||||
|
local cmd = _git_clean_cmd("diff " .. args)
|
||||||
|
local out, code = executor.exec(cmd)
|
||||||
|
if code ~= 0 then
|
||||||
|
renderer.status(("diff failed (exit %d): %s")
|
||||||
|
:format(code, args == "" and "(working tree)" or args))
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if not out or out:gsub("%s", "") == "" then
|
||||||
|
renderer.status(("(no diff): %s"):format(
|
||||||
|
args == "" and "(working tree)" or args))
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local label = args == "" and "(working tree)" or args
|
||||||
|
ctx:append_exec_output(("[diff %s]\n%s"):format(label, out))
|
||||||
|
renderer.status(("diff injected: %s (%d bytes)"):format(label, #out))
|
||||||
|
end
|
||||||
meta.tree = function(args)
|
meta.tree = function(args)
|
||||||
local sub = (args or ""):match("^%s*(%S*)") or ""
|
local sub = (args or ""):match("^%s*(%S*)") or ""
|
||||||
if sub == "off" then
|
if sub == "off" then
|
||||||
|
|||||||
Reference in New Issue
Block a user