Packages
fnord
0.9.19
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/indexer.ex
defmodule Indexer do
@moduledoc """
This behaviour wraps the AI-powered operations used by `Cmd.Index` to allow
overrides for testing. See `impl/0`.
"""
# -----------------------------------------------------------------------------
# Input Types
# -----------------------------------------------------------------------------
@type indexer :: module()
@type file_path :: String.t()
@type file_content :: String.t()
# -----------------------------------------------------------------------------
# Output Types
# -----------------------------------------------------------------------------
@type completion :: {:ok, String.t()}
@type embeddings :: {:ok, [float]}
@type error :: {:error, term}
# -----------------------------------------------------------------------------
# Behaviour Definition
# -----------------------------------------------------------------------------
@callback get_embeddings(file_content) :: embeddings | error
@callback get_summary(file_path, file_content) :: completion | error
@callback get_outline(file_path, file_content) :: completion | error
# -----------------------------------------------------------------------------
# Behaviour Implementation
# -----------------------------------------------------------------------------
@behaviour Indexer
@impl Indexer
def get_embeddings(content) do
AI.Embeddings.get(content)
end
@impl Indexer
def get_summary(file, content) do
AI.Agent.FileSummary
|> AI.Agent.new(named?: false)
|> AI.Agent.get_response(%{file: file, content: content})
end
@impl Indexer
def get_outline(file, content) do
AI.Agent.CodeMapper
|> AI.Agent.new(named?: false)
|> AI.Agent.get_response(%{file: file, content: content})
end
# -----------------------------------------------------------------------------
# API Functions
# -----------------------------------------------------------------------------
@doc """
Returns the current indexer module. This can be overridden by config for unit
testing. See `test/test_helper.exs`.
"""
def impl() do
Services.Globals.get_env(:fnord, :indexer) || __MODULE__
end
@doc """
Indexes a single entry: reads the source, generates summary and outline in
parallel, computes embeddings, and persists everything. Returns the entry
struct on success so callers can immediately read its data.
This is the canonical single-file indexing pipeline, used by `Cmd.Index`
for bulk indexing.
"""
@spec index_entry(Store.Project.Entry.t()) ::
{:ok, Store.Project.Entry.t()} | {:error, term}
def index_entry(entry) do
indexer = impl()
with {:ok, contents} <- Store.Project.Entry.read_source_file(entry),
{:ok, summary, outline} <- get_derivatives(indexer, entry.file, contents),
{:ok, embeddings} <- get_file_embeddings(indexer, entry.file, summary, outline, contents),
:ok <- Store.Project.Entry.save(entry, summary, outline, embeddings) do
{:ok, entry}
end
end
# Generate summary and outline concurrently using the configured indexer impl.
defp get_derivatives(indexer, file, contents) do
summary_task = Services.Globals.Spawn.async(fn -> indexer.get_summary(file, contents) end)
outline_task = Services.Globals.Spawn.async(fn -> indexer.get_outline(file, contents) end)
with {:ok, summary} <- Task.await(summary_task, :infinity),
{:ok, outline} <- Task.await(outline_task, :infinity) do
{:ok, summary, outline}
end
end
# Build the embedding input from the file's derivatives and content, matching
# the format used by Cmd.Index.get_embeddings/4.
defp get_file_embeddings(indexer, file, summary, outline, contents) do
to_embed = """
# File
`#{file}`
## Summary
#{summary}
## Outline
#{outline}
## Contents
```
#{contents}
```
"""
indexer.get_embeddings(to_embed)
end
end