Packages
fnord
0.9.27
0.9.40
0.9.39
0.9.38
0.9.37
0.9.36
0.9.35
0.9.34
0.9.33
0.9.32
0.9.31
0.9.30
0.9.29
0.9.28
0.9.27
0.9.26
0.9.25
0.9.24
0.9.23
0.9.22
0.9.21
0.9.20
0.9.19
0.9.18
0.9.17
0.9.16
0.9.15
0.9.14
0.9.13
0.9.12
0.9.11
0.9.10
0.9.9
0.9.8
0.9.7
0.9.6
0.9.5
0.9.4
0.9.3
0.9.2
0.9.1
0.9.0
0.8.99
0.8.98
0.8.97
0.8.96
0.8.95
0.8.94
0.8.93
0.8.92
0.8.91
0.8.90
0.8.89
0.8.88
0.8.87
0.8.86
0.8.85
0.8.84
0.8.83
0.8.82
0.8.81
0.8.80
0.8.79
0.8.78
0.8.77
0.8.76
0.8.75
0.8.74
0.8.73
0.8.72
0.8.71
0.8.70
0.8.69
0.8.68
0.8.67
0.8.66
0.8.65
0.8.64
0.8.63
0.8.62
0.8.61
0.8.60
0.8.59
0.8.58
0.8.57
0.8.56
0.8.55
0.8.54
0.8.53
0.8.52
0.8.51
0.8.50
0.8.49
0.8.48
0.8.47
0.8.46
0.8.45
0.8.44
0.8.43
0.8.42
0.8.41
0.8.40
0.8.39
0.8.38
0.8.37
0.8.36
0.8.35
0.8.34
0.8.33
0.8.32
0.8.31
0.8.30
0.8.29
0.8.27
0.8.26
0.8.25
0.8.24
0.8.23
0.8.22
0.8.21
0.8.20
0.8.19
0.8.18
0.8.17
0.8.16
0.8.15
0.8.14
0.8.13
0.8.12
0.8.11
0.8.1
0.8.0
0.7.24
0.7.23
0.7.22
0.7.21
0.7.20
0.7.19
0.7.18
0.7.17
0.7.16
0.7.15
0.7.14
0.7.13
0.7.12
0.7.11
0.7.10
0.7.9
0.7.8
0.7.7
0.7.6
0.7.5
0.7.3
0.7.2
0.7.1
0.7.0
0.6.9
0.6.8
0.6.7
0.6.6
0.6.5
0.6.4
0.6.3
0.6.1
0.6.0
0.5.9
0.5.8
0.5.7
0.5.6
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.44
0.4.43
0.4.42
0.4.41
0.4.40
0.4.39
0.4.38
0.4.37
0.4.36
0.4.35
0.4.34
0.4.33
0.4.32
0.4.30
0.4.29
0.4.28
0.4.27
0.4.26
0.4.25
0.4.24
0.4.23
0.4.22
0.4.21
0.4.20
0.4.19
0.4.18
0.4.17
0.4.16
0.4.15
0.4.14
0.4.13
0.4.12
0.4.11
0.4.10
0.4.9
0.4.8
0.4.7
0.4.6
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.3.0
0.2.0
0.1.0
AI code archaeology
Current section
Files
Jump to
Current section
Files
lib/services/approvals/edit.ex
defmodule Services.Approvals.Edit do
@behaviour Services.Approvals.Workflow
@approve "Approve"
@session "Approve for this session"
@deny "Deny"
@deny_feedback "Deny with feedback"
@auto_approve "(auto-approve)"
@auto_deny "(auto-deny)"
@no_feedback "The user denied the request."
@not_edit_mode """
The application is not running in edit mode.
The user must pass --edit to enable edit mode.
"""
@no_tty """
The application is not running in an interactive terminal.
The user cannot respond to prompts, so they were unable to approve or deny the request.
"""
@impl Services.Approvals.Workflow
def confirm(state, {file, diff}) do
display = AI.Tools.display_path(file)
cond do
!edit?() ->
UI.warn("Edit #{display}", @not_edit_mode)
{:denied, @not_edit_mode, state}
auto?() ->
UI.interact(fn ->
render_diff_box(file, diff)
extra = max(Settings.get_yes_count() - 1, 0) |> min(3)
msg =
case extra do
0 -> "Auto-approved (either --yes passed or approved for session)"
1 -> "Auto-approved? You got it!"
2 -> "Auto-approved? Correct-a-mundo!"
3 -> "Auto-approved. Ayyyyy! Sit on it!"
# A future caller that forgets to clamp get_yes_count shouldn't
# crash the approval flow at pattern match. Fall back to the
# plain auto-approve message rather than raising.
_ -> "Auto-approved (either --yes passed or approved for session)"
end
UI.info("Edit #{display}", msg)
{:approved, state}
end)
!interactive?() ->
UI.error("Edit #{display}", @no_tty)
{:error, @no_tty, state}
true ->
UI.interact(fn ->
render_diff_box(file, diff)
prompt(state)
end)
end
end
def approved?(_, _), do: edit?() && auto?()
defp prompt(state) do
opts = [@approve, @session, @deny, @deny_feedback]
Settings.get_auto_policy()
|> case do
{:approve, ms} -> UI.choose("Approve this request?", opts, ms, @auto_approve)
{:deny, ms} -> UI.choose("Approve this request?", opts, ms, @auto_deny)
_ -> UI.choose("Approve this request?", opts)
end
|> case do
@approve ->
{:approved, state}
@auto_approve ->
{:approved, state}
@session ->
Settings.set_auto_approve(true)
{:approved, state}
@deny ->
{:denied, @no_feedback, state}
@auto_deny ->
{:denied, build_auto_deny_message(), state}
@deny_feedback ->
{:denied, get_feedback(), state}
end
end
defp edit?, do: Settings.get_edit_mode()
defp auto?, do: edit?() && Settings.get_auto_approve()
defp interactive?(), do: UI.is_tty?()
defp get_feedback() do
"Feedback:"
|> UI.prompt()
|> then(&"The user denied the request with the following feedback: #{&1}")
end
defp build_auto_deny_message() do
case Settings.get_auto_policy() do
{:deny, ms} when is_integer(ms) and ms > 0 ->
seconds = div(ms, 1000)
"""
The request was automatically denied after #{seconds} seconds due to an active auto-deny policy.
The user may not be monitoring their terminal. Only use pre-approved commands that will not require user confirmation.
"""
|> String.trim()
_ ->
@no_feedback
end
end
# Safely fetch terminal columns, fallback to 120
defp safe_columns() do
try do
case Owl.IO.columns() do
cols when is_integer(cols) -> cols
_ -> 140
end
rescue
_ -> 140
end
end
# Render the diff display box with safe width handling
defp render_diff_box(file, diff) do
# Build the box content
UI.box(diff,
title: " #{render_file_path(file)} ",
padding: 1,
horizontal_align: :left,
border_tag: [:red, :bright],
border_style: :solid_rounded,
min_width: max(safe_columns() - 8, 80),
truncate_lines: true
)
end
defp render_file_path(file) do
cols = safe_columns()
max_width = max(cols - 12, 0)
# Relativize against source root rather than cwd for worktree clarity
rel_path = AI.Tools.display_path(file)
branch = GitCli.current_branch()
file_display = if branch, do: "#{rel_path} (#{branch})", else: rel_path
if String.length(file_display) > max_width do
"..." <> String.slice(file_display, -max_width..-1)
else
file_display
end
end
end