# `ClaudeWrapper.Prompt`
[🔗](https://github.com/genagent/claude_wrapper_ex/blob/main/lib/claude_wrapper/prompt.ex#L1)

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"}

# `context_item`

```elixir
@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`

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

# `append`

```elixir
@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`

```elixir
@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`

```elixir
@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`

```elixir
@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`

```elixir
@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`

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

Start a new prompt from its base text.

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

# `prepend`

```elixir
@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`

```elixir
@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!`

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

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

# `vars`

```elixir
@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
    %{}

---

*Consult [api-reference.md](api-reference.md) for complete listing*
