Packages
fnord
0.5.9
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/strategy.ex
defmodule Store.Strategy do
@moduledoc """
The `ask` subcommand saves useful prompts to the store. Saved prompts are
accompanied by a title and a list of example questions for which the prompt
is appropriate.
When a research strategy prompt is saved, an embedding is generated from it
to make the prompt (and the questions to which it applies) searchable.
`<$STORE>/prompts/<$PROMPT_ID>./`
File structure:
```
$HOME/
-> .fnord/
-> prompts/
-> <$PROMPT_ID>/
-> title.md
-> prompt.md
-> questions.md
-> embeddings.json
```
There are a number of initial prompts that are installed the first time the
prompt store is searched. These prompts are defined in
`data/strategies.yaml`. When a new version of fnord is installed, the next
time the prompt store is searched, the prompts will be updated to the latest
versions.
Note that `data/strategies.yaml` is read at *compile time* and is not itself
a part of the release binary.
"""
defstruct [
:id,
:store_path
]
@store_dir "strategies"
# ----------------------------------------------------------------------------
# These are installed the first time the prompt store is searched.
# ----------------------------------------------------------------------------
@strategies_file "data/strategies.yaml"
@external_resource @strategies_file
@initial_strategies YamlElixir.read_from_file!(@strategies_file)
@doc """
Create a new prompt with a random UUID.
"""
def new(), do: new(UUID.uuid4())
@doc """
Create a new prompt with the given ID. This is used to access an existing
prompt. If `id` is `nil`, acts as an alias for `new/0`.
"""
def new(nil), do: new(UUID.uuid4())
def new(id), do: %__MODULE__{id: id, store_path: build_store_dir(id)}
@doc """
Returns true if the prompt has been written to the store.
"""
def exists?(prompt) do
File.exists?(prompt.store_path)
end
@doc """
Saves the prompt to the store. If the prompt already exists but thew `title`,
`prompt_text`, or `questions` have changed, it will be replaced by the new
version. If they have not changed, an error will be returned:
`{:error, {:strategy_exists, id}}`
"""
def write(prompt, title, prompt_text, questions) do
qstr = format_questions(questions)
Store.list_strategies()
|> Enum.find(fn p ->
with {:ok, old_title} when old_title == title <- read_title(p),
{:ok, old_prompt} when old_prompt == prompt_text <- read_prompt(p),
{:ok, old_questions} when old_questions == qstr <- read_questions(p) do
true
else
_ -> false
end
end)
|> case do
nil -> do_write(prompt, title, prompt_text, questions)
p -> {:error, {:strategy_exists, p.id}}
end
end
defp do_write(prompt, title, prompt_text, questions) do
# --------------------------------------------------------------------------
# We only reach this point if we want to *overwrite* the prompt, so we
# remove the existing prompt files.
# --------------------------------------------------------------------------
File.rm_rf!(prompt.store_path)
# --------------------------------------------------------------------------
# Create the prompt's store path if it does not yet exist.
# --------------------------------------------------------------------------
File.mkdir_p!(prompt.store_path)
# --------------------------------------------------------------------------
# Write the title to the prompt's store path.
# --------------------------------------------------------------------------
prompt.store_path
|> Path.join("title.md")
|> File.write!(title)
# --------------------------------------------------------------------------
# Write the prompt's text to the prompt's store path.
# --------------------------------------------------------------------------
prompt.store_path
|> Path.join("prompt.md")
|> File.write!(prompt_text)
# --------------------------------------------------------------------------
# Write the prompt's questions to the prompt's store path.
# --------------------------------------------------------------------------
questions = format_questions(questions)
prompt.store_path
|> Path.join("questions.md")
|> File.write!(questions)
# --------------------------------------------------------------------------
# Generate and save embeddings for the prompt.
# --------------------------------------------------------------------------
embeddings_text = [title, prompt_text, questions] |> Enum.join("\n")
with {:ok, embeddings} <- get_embeddings(embeddings_text),
{:ok, json} <- Jason.encode(embeddings),
:ok <- prompt.store_path |> Path.join("embeddings.json") |> File.write(json) do
{:ok, prompt}
end
end
@doc """
Reads the prompt from the store. If the prompt does not exist, an error will
be returned (`{:error, :not_found}`). Returns an `:ok` tuple with the prompt's
title, prompt text, questions, and embeddings:
```
{:ok,
%{
title: title,
prompt: prompt_text,
questions: questions,
embeddings: embeddings,
}}
```
"""
def read(prompt) do
with {:ok, title} <- read_title(prompt),
{:ok, prompt_text} <- read_prompt(prompt),
{:ok, questions} <- read_questions(prompt),
{:ok, embeddings} <- read_embeddings(prompt) do
{:ok,
%{
title: title,
prompt: prompt_text,
questions: questions,
embeddings: embeddings
}}
end
end
def read_title(prompt) do
prompt.store_path
|> Path.join("title.md")
|> File.read()
end
def read_prompt(prompt) do
prompt.store_path
|> Path.join("prompt.md")
|> File.read()
end
def read_questions(prompt) do
prompt.store_path
|> Path.join("questions.md")
|> File.read()
end
def read_embeddings(prompt) do
prompt.store_path
|> Path.join("embeddings.json")
|> File.read()
|> case do
{:ok, json} -> Jason.decode(json)
error -> error
end
end
# ----------------------------------------------------------------------------
# Private functions
# ----------------------------------------------------------------------------
defp get_embeddings(input) do
Indexer.impl().new()
|> Indexer.impl().get_embeddings(input)
end
defp format_questions(questions) do
questions
|> Enum.map(&"- #{&1}")
|> Enum.join("\n")
end
defp build_store_dir(id) do
Store.store_home()
|> Path.join(@store_dir)
|> Path.join(id)
end
# ----------------------------------------------------------------------------
# Initial strategies
# ----------------------------------------------------------------------------
def install_initial_strategies() do
delete_old_prompts_dir()
@initial_strategies
|> Enum.each(fn %{
"id" => id,
"title" => title,
"prompt" => prompt_str,
"questions" => questions
} ->
prompt = new(id)
if differs?(prompt, prompt_str, questions) do
UI.info("Saving new or updated research prompt: #{title}")
write(prompt, title, prompt_str, questions)
end
end)
end
defp differs?(prompt, prompt_str, questions) do
if exists?(prompt) do
with {:ok, old_questions} <- read_questions(prompt),
{:ok, old_prompt} <- read_prompt(prompt) do
cond do
old_questions != format_questions(questions) -> true
old_prompt != prompt_str -> true
true -> false
end
else
_ -> true
end
else
true
end
end
# ----------------------------------------------------------------------------
# We used to store versioned strategies that could be updated, but we no
# longer do so because they were not very valuable. So we check for the
# existence of the old style of directory and delete all of those versioned
# entries. They can be identified because they had a different store dir
# (prompts, rather than strategies).
# ----------------------------------------------------------------------------
defp delete_old_prompts_dir do
old_prompts_dir =
Store.store_home()
|> Path.join("prompts")
if File.exists?(old_prompts_dir) do
File.rm_rf!(old_prompts_dir)
end
end
end