Packages
fnord
0.9.35
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/commit/search.ex
defmodule AI.Tools.Commit.Search do
@behaviour AI.Tools
@max_search_results 25
@impl AI.Tools
def async?, do: true
@impl AI.Tools
def is_available?(), do: AI.Tools.has_indexed_project()
@impl AI.Tools
def ui_note_on_request(args), do: {"Commit search", args["query"]}
@impl AI.Tools
def ui_note_on_result(%{"query" => query}, result) do
re = ~r/Commit search found (\d+) matching commits in (\d+) ms:/
case Regex.run(re, result) do
[_, count_str, took_ms] ->
count = String.to_integer(count_str)
{"Commit search", "#{query} -> #{count} commit matches in #{took_ms} ms"}
_ ->
{"Commit search", "#{query} -> #{result}"}
end
end
@impl AI.Tools
def tool_call_failure_message(_args, _reason), do: :default
@impl AI.Tools
def read_args(%{"query" => query} = args) do
limit = Map.get(args, "limit", @max_search_results)
{:ok,
%{
"query" => query,
"limit" => normalize_limit(limit)
}}
end
def read_args(_), do: AI.Tools.required_arg_error("query")
@impl AI.Tools
def spec() do
%{
type: "function",
name: "commit_search_tool",
description: "Semantic search over indexed git commits.",
parameters: %{
additionalProperties: false,
type: "object",
required: ["query"],
properties: %{
query: %{
type: "string",
description: "Natural language query to match against commit embeddings."
},
limit: %{
type: "integer",
description: "Maximum number of results to return (default: #{@max_search_results}).",
minimum: 1,
maximum: 100
}
}
}
}
end
@impl AI.Tools
def call(%{"query" => query, "limit" => limit}) do
start_time = DateTime.utc_now()
with {:ok, matches} <- do_search(query, limit),
took_ms = DateTime.diff(DateTime.utc_now(), start_time, :millisecond),
{:ok, index_state_msg} <- index_state() do
{:ok, build_response(matches, took_ms, index_state_msg)}
end
end
# --- internals ---
defp normalize_limit(limit) when is_integer(limit) and limit > 0 and limit <= 100, do: limit
defp normalize_limit(_), do: @max_search_results
defp build_response(matches, took_ms, index_state_msg) do
header = "[commit_search_tool]"
body =
matches
|> Enum.map(&format_entry/1)
|> Enum.join("\n-----\n")
|> then(fn body ->
"""
Commit search found #{length(matches)} matching commits in #{took_ms} ms:
-----
#{body}
"""
end)
[header, body, "-----", index_state_msg]
|> Enum.join("\n")
end
defp format_entry({sha, score, metadata}) do
subject = Map.get(metadata, "subject") || "(no subject)"
author = Map.get(metadata, "author") || "(unknown)"
time = Map.get(metadata, "committed_at") || ""
"""
# #{sha} (cosine similarity: #{score})
- subject: #{subject}
- author: #{author}
- committed_at: #{time}
"""
end
defp index_state() do
with {:ok, project} <- Store.get_project() do
%{new: new, stale: stale, deleted: deleted} =
Store.Project.CommitIndex.index_status(project)
msg = """
The results of this search may be affected by the state of the commit index.
The current commit index state is:
- New commits (not yet embedded): #{length(new)}
- Stale commits (outdated index): #{length(stale)}
- Deleted commits (indexed but missing from repo): #{length(deleted)}
If you are seeing unexpected search results, re-run `fnord index`.
"""
{:ok, msg}
end
end
defp do_search(query, limit) do
%{"query" => query, "limit" => limit}
|> Search.Commits.new()
|> Search.Commits.get_results()
end
end