Packages
fnord
0.9.24
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/store/project/commit_document.ex
defmodule Store.Project.CommitDocument do
@moduledoc """
Builds the bounded canonical text used to embed git commits for a project.
The document keeps commit semantics isolated from file and conversation
indexing while still producing a stable text form that can be hashed for
stale detection when the document format or embedding model changes.
"""
@type commit :: %{
sha: String.t(),
parent_shas: [String.t()],
subject: String.t(),
body: String.t(),
author: String.t(),
committed_at: String.t() | DateTime.t() | non_neg_integer(),
changed_files: [String.t()],
diffstat: String.t() | [map()]
}
@document_version 1
@spec version() :: non_neg_integer()
def version do
@document_version
end
@doc """
Builds the bounded canonical commit document used for embeddings.
The shape stays deliberately small so commit histories can be hashed and
reindexed deterministically without embedding unbounded patch text.
"""
@spec build(%{
sha: binary(),
parent_shas: [binary()],
subject: binary(),
body: binary(),
author: binary(),
committed_at: binary() | non_neg_integer() | DateTime.t(),
changed_files: [binary()],
diffstat: binary() | [map()]
}) :: {binary(), binary()}
def build(commit) do
document =
[
"commit: #{commit.sha}",
"parents: #{Enum.join(commit.parent_shas, ", ")}",
"author: #{commit.author}",
"committed_at: #{format_committed_at(commit.committed_at)}",
"subject: #{normalize_line(commit.subject)}",
"body:",
bounded_body(commit.body),
"changed_files:",
Enum.map_join(commit.changed_files, "\n", &format_changed_file/1),
"diffstat:",
format_diffstat(commit.diffstat),
"document_version: #{@document_version}"
]
|> Enum.join("\n")
{document, doc_hash(document)}
end
@doc """
Hashes the canonical commit document so callers can compare stable semantic
content rather than raw commit metadata.
"""
@spec doc_hash(binary()) :: binary()
def doc_hash(document) when is_binary(document) do
:crypto.hash(:sha256, document)
|> Base.encode16(case: :lower)
end
defp bounded_body(body) when is_binary(body) do
body
|> String.trim()
|> String.split("\n")
|> Enum.take(60)
|> Enum.join("\n")
end
defp bounded_body(_), do: ""
defp format_changed_file(path) when is_binary(path), do: "- #{path}"
defp format_changed_file(path), do: "- #{inspect(path)}"
defp format_diffstat(diffstat) when is_binary(diffstat), do: diffstat
defp format_diffstat(diffstat) when is_list(diffstat) do
Enum.map_join(diffstat, "\n", fn stat ->
file = Map.get(stat, :file) || Map.get(stat, "file") || ""
additions = Map.get(stat, :additions) || Map.get(stat, "additions") || 0
deletions = Map.get(stat, :deletions) || Map.get(stat, "deletions") || 0
"#{file} | +#{additions} -#{deletions}"
end)
end
defp format_diffstat(_), do: ""
defp format_committed_at(%DateTime{} = dt), do: DateTime.to_iso8601(dt)
defp format_committed_at(int) when is_integer(int), do: Integer.to_string(int)
defp format_committed_at(value) when is_binary(value), do: value
defp format_committed_at(_), do: ""
defp normalize_line(value) when is_binary(value), do: String.replace(value, "\n", " ")
defp normalize_line(value), do: inspect(value)
end