Packages
fnord
0.9.36
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/cmd/memory.ex
defmodule Cmd.Memory do
@min_match_threshold 0.3
alias Memory.Presentation
@behaviour Cmd
@impl Cmd
def requires_project?(), do: false
@impl Cmd
def spec() do
[
memory: [
name: "memory",
about: "List memories or perform a semantic search across memory scopes",
flags: [
global: [
long: "--global",
short: "-g",
help: "Use global (user) memories. If not set, project memories are used.",
required: false,
default: false
]
],
options: [
project: Cmd.project_arg(),
query: [
value_name: "QUERY",
long: "--query",
short: "-q",
help: "Semantic search query. If omitted, all memories are shown.",
required: false
]
]
]
]
end
@impl Cmd
def run(opts, _subcommands, unknown) do
if unknown != [] do
UI.warn("Ignoring unknown arguments: #{Enum.join(unknown, " ")}")
end
scopes = resolve_scopes!(opts)
markdown =
case Map.get(opts, :query) do
nil ->
render_list(scopes)
query ->
AI.Embeddings.Pool.ensure_started()
AI.Embeddings.Migration.maybe_migrate(:read_only)
render_search(scopes, query)
end
markdown
|> UI.format()
|> UI.puts()
end
# ----------------------------------------------------------------------------
# Listing
# ----------------------------------------------------------------------------
defp render_list(scopes) do
sections =
scopes
|> Enum.map(fn scope ->
case available?(scope) do
true ->
{:ok, memories} = list_memories(scope)
rendered_memories =
memories
|> Enum.sort_by(& &1.title)
|> Enum.map(fn memory ->
{memory, nil}
end)
render_scope_section(scope, rendered_memories, %{mode: :list})
false ->
render_unavailable_scope_section(scope)
end
end)
"""
# Memories
#{Enum.join(sections, "\n\n")}
"""
end
# ----------------------------------------------------------------------------
# Search
# ----------------------------------------------------------------------------
defp render_search(scopes, query) when is_binary(query) do
query = String.trim(query)
case Indexer.impl().get_embeddings(query) do
{:ok, needle} ->
needle_dim = length(needle)
sections =
scopes
|> Enum.map(fn scope ->
if available?(scope) do
{:ok, titles} = list_titles(scope)
{matches, stale_count} =
titles
|> Enum.reduce({[], 0}, fn title, {acc, stale} ->
case Memory.read(scope, title) do
{:ok, %Memory{embeddings: nil}} ->
{acc, stale + 1}
{:ok, %Memory{embeddings: embeddings} = mem}
when is_list(embeddings) and length(embeddings) == needle_dim ->
score = AI.Util.cosine_similarity(needle, embeddings)
if score > @min_match_threshold do
{[{mem, score} | acc], stale}
else
{acc, stale}
end
# Dimension mismatch - memory is stale under current model.
{:ok, %Memory{embeddings: embeddings}} when is_list(embeddings) ->
{acc, stale + 1}
{:error, _} ->
{acc, stale}
end
end)
matches = Enum.sort_by(matches, fn {_mem, score} -> score end, :desc)
render_scope_section(scope, matches, %{mode: :search, stale_count: stale_count})
else
render_unavailable_scope_section(scope)
end
end)
"""
# Memories
#{Enum.join(sections, "\n\n")}
"""
{:error, reason} ->
"""
# Memories
Failed to generate embeddings for query: #{inspect(reason)}
"""
end
end
# ----------------------------------------------------------------------------
# Markdown formatting
# ----------------------------------------------------------------------------
defp render_scope_section(scope, memories, %{mode: :list}) do
body =
case memories do
[] ->
"_No memories._"
_ ->
memories
|> Enum.map(&render_memory/1)
|> Enum.join("\n\n")
end
"""
## #{scope}
#{body}
"""
end
defp render_scope_section(scope, memories, %{mode: :search, stale_count: stale_count}) do
stale_note =
if stale_count > 0 do
"_Skipped #{stale_count} stale memorie(s) missing embeddings._"
else
""
end
body =
case memories do
[] ->
"_No matches._\n"
_ ->
memories
|> Enum.map(&render_memory/1)
|> Enum.join("\n\n")
end
"""
## #{scope}
#{stale_note}
#{body}
"""
end
defp render_unavailable_scope_section(scope) do
"""
## #{scope}
_Unavailable in the current context._
"""
end
defp render_memory({%Memory{} = mem, nil}) do
now = DateTime.utc_now()
age = Presentation.age_line(mem, now)
warning = Presentation.warning_line(mem, now)
"""
### [#{mem.scope}] #{mem.title}
_#{age}_
_#{warning}_
#{mem.content}
"""
end
defp render_memory({%Memory{} = mem, score}) when is_number(score) do
now = DateTime.utc_now()
age = Presentation.age_line(mem, now)
warning = Presentation.warning_line(mem, now)
"""
### #{mem.title}
_Score:_ #{Float.round(score, 4)}
_#{age}_
_#{warning}_
#{mem.content}
"""
end
# ----------------------------------------------------------------------------
# Scope resolution
# ----------------------------------------------------------------------------
defp resolve_scopes!(%{global: true}), do: [:global]
defp resolve_scopes!(opts) do
maybe_set_project(opts)
case Settings.get_selected_project() do
{:ok, _project} ->
[:project]
{:error, _reason} ->
UI.fatal(
"No project selected; use --project or run in a project directory, or pass --global."
)
exit({:shutdown, 1})
end
end
defp maybe_set_project(%{project: project}) when is_binary(project) and project != "" do
Settings.set_project(project)
:ok
end
defp maybe_set_project(_), do: :ok
defp available?(:global), do: true
defp available?(:project), do: Memory.Project.is_available?()
defp list_memories(:global), do: Memory.Global.list_memories()
defp list_memories(:project), do: Memory.Project.list_memories()
defp list_titles(:global), do: Memory.Global.list()
defp list_titles(:project), do: Memory.Project.list()
end