Packages
fnord
0.9.40
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/ai/tools/reviewer.ex
defmodule AI.Tools.Reviewer do
@moduledoc """
Tool entry point for the review agent pipeline. Delegates to
`AI.Agent.Review.Decomposer`, which triages changes by complexity, partitions
large diffs into focused review units, and fans out scoped Reviewers - each
running five specialists (pedantic, acceptance, state flow, no-slop,
breadcrumbs) - before synthesizing a deduplicated final report.
Always available - reviews are read-only operations.
"""
@behaviour AI.Tools
@impl AI.Tools
def async?, do: true
@impl AI.Tools
def is_available?, do: true
@impl AI.Tools
def read_args(args), do: {:ok, args}
@impl AI.Tools
def ui_note_on_request(%{"scope" => scope}) do
{"Starting code review", scope}
end
@impl AI.Tools
def ui_note_on_result(_args, result) do
{"Review complete", result}
end
@impl AI.Tools
def tool_call_failure_message(_args, reason), do: reason
@impl AI.Tools
def spec do
%{
type: "function",
name: "reviewer_tool",
description: """
AI-powered, multi-agent code review.
Triages change complexity, decomposes large diffs into focused review
units, and fans out scoped multi-specialist reviewers in parallel.
Produces a unified, deduplicated, severity-grouped report.
The reviewer reads the target via git directly - it does NOT need the
target checked out in your working tree. Always name an explicit
target via `branch`, `pr`, or `range`; the reviewer will fetch refs
as needed.
Use for:
- Post-implementation review of a branch or commit range
- Pre-merge quality checks of a PR
- Comprehensive audit of changes spanning multiple files
NOT for quick, single-file checks - just read the file yourself.
Not safe to run concurrently. Only one active review per fnord
process at a time.
""",
parameters: %{
type: "object",
required: ["scope"],
additionalProperties: false,
properties: %{
scope: %{
type: "string",
description: """
Design context and specific concerns for the review. Free text.
Describe intent, risky areas, what "done" looks like. Do NOT
use this field to specify the target branch or PR - use the
dedicated `branch`, `pr`, or `range` parameters for that.
"""
},
branch: %{
type: "string",
description: """
Branch to review (e.g. "feature-x"). The reviewer fetches the
branch from origin if it is not locally reachable, then reviews
the range `merge-base(branch, base)..branch`. Mutually
exclusive with `pr` and `range` - **omit the other two
entirely**; do not pass them as empty strings, 0, or null.
"""
},
pr: %{
type: "integer",
description: """
GitHub pull request number. Requires the `gh` CLI to be
installed and authenticated. The reviewer resolves the PR's
head and base via `gh pr view`, fetches both refs, and
reviews the range `merge-base(head, base)..head`. Mutually
exclusive with `branch` and `range` - **omit the other two
entirely**; do not pass them as empty strings, 0, or null.
"""
},
range: %{
type: "string",
description: """
Explicit git range in `A..B` or `A...B` form (e.g.
`HEAD~3..HEAD`, `abc123..def456`). Use for commit-scoped
reviews that are not tied to a branch or PR. The reviewer
fetches endpoints from origin if they are not locally
reachable. Mutually exclusive with `branch` and `pr` -
**omit the other two entirely**; do not pass them as empty
strings, 0, or null.
"""
},
base: %{
type: "string",
description: """
Override the base branch used to compute merge-base. Default
is the repo's default branch (`main` or `master`). Useful for
stacked branches - set to the parent branch, not `main`.
Ignored when `range` is used.
"""
}
}
}
}
end
@impl AI.Tools
def call(args) do
with {:ok, scope} <- AI.Tools.get_arg(args, "scope"),
{:ok, target_args} <- normalize_target_args(args) do
AI.Agent.Review.Decomposer
|> AI.Agent.new()
|> AI.Agent.get_response(Map.put(target_args, :scope, scope))
end
end
# Extract and validate the target-selection params. The LLM may pass any
# subset (or none) of branch / pr / range; at most one is allowed. `base`
# is free-standing and applies only when resolving branch: or falling back
# to the current checkout.
#
# `present?/1` rather than Elixir truthiness: LLMs often emit type-default
# placeholders (`""` for omitted strings, `0` for omitted integers) instead
# of leaving the key out of the tool call. Those are semantically "not
# provided" - treating them as set produces a mutex-failure-loop where every
# retry passes the same shape and gets rejected the same way.
defp normalize_target_args(args) do
branch = Map.get(args, "branch")
pr = Map.get(args, "pr")
range = Map.get(args, "range")
base = Map.get(args, "base")
given =
[{:branch, branch}, {:pr, pr}, {:range, range}]
|> Enum.filter(fn {_, v} -> present?(v) end)
case given do
[] ->
{:ok, %{branch: nil, pr: nil, range: nil, base: present_or_nil(base)}}
[{:branch, _}] ->
{:ok, %{branch: branch, pr: nil, range: nil, base: present_or_nil(base)}}
[{:pr, _}] ->
{:ok, %{branch: nil, pr: pr, range: nil, base: present_or_nil(base)}}
[{:range, _}] ->
{:ok, %{branch: nil, pr: nil, range: range, base: present_or_nil(base)}}
multiple ->
details =
multiple
|> Enum.map_join(", ", fn {k, v} -> "#{k}=#{inspect(v)}" end)
{:error,
"reviewer_tool: only one of branch, pr, range may be set; got: #{details}. " <>
"Pass ONLY the target you want and OMIT the others entirely - do not " <>
"send them as empty strings, 0, or null. Use `range` for explicit commit " <>
"ranges, `pr` for a GitHub PR number, `branch` for a branch name."}
end
end
# Treat type-default placeholders ("", 0, []) as semantically absent, matching
# how OpenAPI / JSON Schema callers typically intend "field not provided."
defp present?(nil), do: false
defp present?(""), do: false
defp present?(0), do: false
defp present?([]), do: false
defp present?(_), do: true
defp present_or_nil(value), do: if(present?(value), do: value, else: nil)
end