Packages
In-process autonomous dev agent for Elixir. Captures errors, creates tickets, picks up board work, and runs Codex to investigate and fix.
Current section
Files
Jump to
Current section
Files
lib/sentinel/tracker.ex
defmodule Sentinel.Tracker do
@moduledoc """
Behaviour for issue tracker adapters.
Covers both the write path (errors -> tickets) and the read path
(board -> work items). Implementations include `Sentinel.Tracker.Linear`
and `Sentinel.Tracker.GitHub`.
"""
alias Sentinel.ErrorBucket
@type issue_ref :: String.t()
@type issue :: %{
id: String.t(),
identifier: String.t(),
title: String.t(),
description: String.t() | nil,
state: String.t(),
labels: [String.t()],
url: String.t() | nil
}
@type filter_opts :: keyword()
# Write path: errors -> tickets
@callback create_issue(ErrorBucket.t()) :: {:ok, issue_ref()} | {:error, term()}
@callback update_issue(issue_ref(), map()) :: :ok | {:error, term()}
@callback find_existing(ErrorBucket.t()) :: {:ok, issue_ref()} | :not_found | {:error, term()}
# Read path: board -> work items
@callback fetch_available_issues(filter_opts()) :: {:ok, [issue()]} | {:error, term()}
@callback assign_issue(String.t(), String.t()) :: :ok | {:error, term()}
@callback transition_issue(String.t(), String.t()) :: :ok | {:error, term()}
@callback add_comment(String.t(), String.t()) :: :ok | {:error, term()}
@doc "Get the configured tracker adapter module and its options."
@spec adapter() :: {module(), keyword()} | nil
def adapter do
case Application.get_env(:sentinel, :tracker) do
{mod, opts} when is_atom(mod) -> {mod, opts}
_ -> nil
end
end
@doc "Call a tracker operation using the configured adapter."
@spec call(atom(), [term()]) :: term()
def call(operation, args) do
case adapter() do
{mod, _opts} -> apply(mod, operation, args)
nil -> {:error, :no_tracker_configured}
end
end
end