ClaudeWrapper.Prompt (ClaudeWrapper v0.11.0)

Copy Markdown View Source

Composable prompt builder with deferred file / git expansion.

A Prompt is a pure value: the builders (new/1, prepend/2, append/2, attach/2, git_diff/2, git_log/2, git_status/1, vars/2) only record intent. All IO -- reading attached files, shelling out to git -- happens in render/1, which assembles the final string in a fixed order:

prepends -> base -> context (attach / git) -> appends

Each section is separated from the next by a blank line, and within the context section the attach/git blocks appear in the order their builder calls were made (interleaved exactly as you wrote them).

This split keeps composition cheap and total -- you can build a prompt without touching the filesystem -- and concentrates every fallible operation in render/1, which returns a tagged tuple.

Example

iex> ClaudeWrapper.Prompt.new("Review this change")
...> |> ClaudeWrapper.Prompt.prepend("You are a careful reviewer.")
...> |> ClaudeWrapper.Prompt.append("Focus on correctness.")
...> |> ClaudeWrapper.Prompt.render()
{:ok, "You are a careful reviewer.\n\nReview this change\n\nFocus on correctness."}

Context blocks

Attached files and git output are emitted as fenced blocks, each headed by a # <source> comment line:

  • attach/2 -- one # <path> block per matched text file. Globs are expanded with Path.wildcard/1 and sorted; a glob matching nothing is an error. Files that are not valid UTF-8, or exceed 256 KB, are skipped.
  • git_diff/2 -- # git diff (working tree) or # git diff <ref>, inside a ```diff fence.
  • git_log/2 -- # git log --oneline -n N (default 5).
  • git_status/1 -- # git status --short.

A git block whose output is empty (clean tree, no commits, no changes) contributes nothing rather than an empty fence.

Template variables

vars/2 records {{key}} substitutions applied to the authored text (base / prepends / appends) at render time; captured context blocks are left verbatim. Unknown {{...}} slots are left untouched.

iex> ClaudeWrapper.Prompt.new("Summarize {{file}} for a {{who}} reader")
...> |> ClaudeWrapper.Prompt.vars(file: "config.ex", who: "beginner")
...> |> ClaudeWrapper.Prompt.render()
{:ok, "Summarize config.ex for a beginner reader"}

Summary

Types

A recorded context item, expanded at render time in call order.

t()

Functions

Add a block after everything else.

Record a file path or glob to attach.

Record a git diff to include.

Record a git log --oneline -n N block (default N = 5).

Record a git status --short block.

Start a new prompt from its base text.

Add a block before the base text.

Render the prompt to its final string.

Like render/1 but returns the string directly, raising ClaudeWrapper.Error on failure.

Record {{key}} template substitutions.

Types

context_item()

@type context_item() ::
  {:attach, String.t()}
  | {:diff, String.t() | nil}
  | {:log, pos_integer()}
  | :status

A recorded context item, expanded at render time in call order.

  • {:attach, glob} -- a path or glob; each matched text file becomes a fenced, path-headed block.
  • {:diff, ref} -- a git diff; nil is the working tree, a binary is a ref/commit to diff against.
  • {:log, n} -- a git log --oneline -n n block.
  • :status -- a git status --short block.

t()

@type t() :: %ClaudeWrapper.Prompt{
  appends: [String.t()],
  base: String.t(),
  context: [context_item()],
  prepends: [String.t()],
  vars: %{optional(String.t()) => String.t()}
}

Functions

append(prompt, text)

@spec append(t(), String.t()) :: t()

Add a block after everything else.

Multiple appends render in the order they were added, after the context section.

attach(prompt, glob)

@spec attach(t(), String.t()) :: t()

Record a file path or glob to attach.

Nothing is read now; the glob is expanded at render/1 time with Path.wildcard/1, sorted, and each text file is emitted as a fenced, path-headed code block. Recorded in call order relative to the other context builders.

git_diff(prompt, ref)

@spec git_diff(t(), String.t() | nil) :: t()

Record a git diff to include.

ref is nil for the working tree, or a ref/commit string to diff against (git diff <ref>). Produced at render/1 time under a # git diff header inside a ```diff fence. Recorded in call order relative to the other context builders.

git_log(prompt, opts \\ [])

@spec git_log(
  t(),
  keyword()
) :: t()

Record a git log --oneline -n N block (default N = 5).

Options:

  • :n -- number of commits (default 5).

Produced at render/1 time under a # git log header. Recorded in call order relative to the other context builders.

git_status(prompt)

@spec git_status(t()) :: t()

Record a git status --short block.

Produced at render/1 time under a # git status header. A clean working tree contributes no block. Recorded in call order relative to the other context builders.

new(base)

@spec new(String.t()) :: t()

Start a new prompt from its base text.

iex> ClaudeWrapper.Prompt.new("hello").base
"hello"

prepend(prompt, text)

@spec prepend(t(), String.t()) :: t()

Add a block before the base text.

Multiple prepends render in the order they were added, ahead of the base.

render(prompt)

@spec render(t()) :: {:ok, String.t()} | {:error, ClaudeWrapper.Error.t()}

Render the prompt to its final string.

Performs all deferred IO -- reads attached files, runs git -- substitutes template vars in the authored text, and joins the sections (prepends, base, context, appends) with blank lines.

Returns {:error, %ClaudeWrapper.Error{}} if a glob matches no files (:not_found), or if a git command fails (:git_failed) or git is unavailable (:git_unavailable). The working directory for file globs and git is the current process cwd.

render!(prompt)

@spec render!(t()) :: String.t()

Like render/1 but returns the string directly, raising ClaudeWrapper.Error on failure.

vars(prompt, vars)

@spec vars(t(), map() | keyword()) :: t()

Record {{key}} template substitutions.

Applied at render/1 time to the authored text (base, prepends, appends) only -- captured context blocks (attach/git_*) are left verbatim. Accepts a map or keyword list; keys and values are stringified. Unknown {{...}} placeholders are left untouched. Repeatable (later calls merge, last wins).

iex> ClaudeWrapper.Prompt.new("hi {{name}}").vars
%{}