# `JsonRemedy.Layer1.ContentCleaning`
[🔗](https://github.com/nshkrdotcom/json_remedy/blob/v0.2.1/lib/json_remedy/layer1/content_cleaning.ex#L1)

Layer 1: Content Cleaning - Removes non-JSON content and normalizes encoding.

This layer handles:
- Code fence removal (```json ... ```)
- Comment stripping (// and /* */)
- Wrapper text extraction (HTML, prose)
- Echoed key removal (Gemini API bug: `{"key": "key": "value"}`)
- Trailing dots truncation (Gemini max_output_tokens pattern)
- Encoding normalization

Uses direct string methods instead of regex for better performance and clearer code.

# `layer_result`
[🔗](https://github.com/nshkrdotcom/json_remedy/blob/v0.2.1/lib/json_remedy/layer1/content_cleaning.ex#L23)

```elixir
@type layer_result() :: JsonRemedy.LayerBehaviour.layer_result()
```

# `repair_action`
[🔗](https://github.com/nshkrdotcom/json_remedy/blob/v0.2.1/lib/json_remedy/layer1/content_cleaning.ex#L21)

```elixir
@type repair_action() :: JsonRemedy.LayerBehaviour.repair_action()
```

# `repair_context`
[🔗](https://github.com/nshkrdotcom/json_remedy/blob/v0.2.1/lib/json_remedy/layer1/content_cleaning.ex#L22)

```elixir
@type repair_context() :: JsonRemedy.LayerBehaviour.repair_context()
```

# `extract_json_content`
[🔗](https://github.com/nshkrdotcom/json_remedy/blob/v0.2.1/lib/json_remedy/layer1/content_cleaning.ex#L380)

```elixir
@spec extract_json_content(input :: String.t()) :: {String.t(), [repair_action()]}
```

Extract JSON from wrapper text (HTML, prose, etc.).
Public API version that takes string input directly.

# `extract_json_content_internal`
[🔗](https://github.com/nshkrdotcom/json_remedy/blob/v0.2.1/lib/json_remedy/layer1/content_cleaning.ex#L87)

```elixir
@spec extract_json_content_internal(input :: {String.t(), [repair_action()]}) ::
  {String.t(), [repair_action()]}
```

Extract JSON from wrapper text (HTML, prose, etc.).

# `name`
[🔗](https://github.com/nshkrdotcom/json_remedy/blob/v0.2.1/lib/json_remedy/layer1/content_cleaning.ex#L322)

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

Return a human-readable name for this layer.

# `normalize_encoding`
[🔗](https://github.com/nshkrdotcom/json_remedy/blob/v0.2.1/lib/json_remedy/layer1/content_cleaning.ex#L391)

```elixir
@spec normalize_encoding(input :: String.t()) :: {String.t(), [repair_action()]}
```

Normalize text encoding to UTF-8.
Public API version that takes string input directly.

# `normalize_encoding_internal`
[🔗](https://github.com/nshkrdotcom/json_remedy/blob/v0.2.1/lib/json_remedy/layer1/content_cleaning.ex#L106)

```elixir
@spec normalize_encoding_internal(input :: {String.t(), [repair_action()]}) ::
  {String.t(), [repair_action()]}
```

Normalize text encoding to UTF-8.

# `priority`
[🔗](https://github.com/nshkrdotcom/json_remedy/blob/v0.2.1/lib/json_remedy/layer1/content_cleaning.ex#L316)

```elixir
@spec priority() :: 1
```

Return the priority order for this layer.
Layer 1 (Content Cleaning) should run first in the pipeline.

# `process`
[🔗](https://github.com/nshkrdotcom/json_remedy/blob/v0.2.1/lib/json_remedy/layer1/content_cleaning.ex#L34)

```elixir
@spec process(input :: String.t(), context :: repair_context()) :: layer_result()
```

Process input string and apply Layer 1 content cleaning repairs.

Returns:
- `{:ok, processed_input, updated_context}` - Layer completed successfully
- `{:continue, input, context}` - Layer doesn't apply, pass to next layer
- `{:error, reason}` - Layer failed, stop pipeline

# `remove_code_fences`
[🔗](https://github.com/nshkrdotcom/json_remedy/blob/v0.2.1/lib/json_remedy/layer1/content_cleaning.ex#L60)

```elixir
@spec remove_code_fences(input :: String.t()) :: {String.t(), [repair_action()]}
```

Remove code fences from input while preserving fence content in strings.

# `remove_comments`
[🔗](https://github.com/nshkrdotcom/json_remedy/blob/v0.2.1/lib/json_remedy/layer1/content_cleaning.ex#L74)

```elixir
@spec remove_comments(input :: {String.t(), [repair_action()]}) ::
  {String.t(), [repair_action()]}
```

Strip comments while preserving comment-like content in strings.

# `strip_comments`
[🔗](https://github.com/nshkrdotcom/json_remedy/blob/v0.2.1/lib/json_remedy/layer1/content_cleaning.ex#L371)

```elixir
@spec strip_comments(input :: String.t()) :: {String.t(), [repair_action()]}
```

Strip comments while preserving comment-like content in strings.
Public API version that takes string input directly.

# `strip_echoed_keys`
[🔗](https://github.com/nshkrdotcom/json_remedy/blob/v0.2.1/lib/json_remedy/layer1/content_cleaning.ex#L191)

```elixir
@spec strip_echoed_keys(input :: String.t()) :: {String.t(), [repair_action()]}
```

Strip echoed/duplicated keys from input.

Public API version that takes string input directly.
Detects the Gemini pattern where a key appears twice: once as the key and once
echoed at the start of the value.

## Examples

    iex> ContentCleaning.strip_echoed_keys(~s|{"key": "key": "value"}|)
    {~s|{"key": "value"}|, [%{layer: :content_cleaning, action: "removed echoed key 'key'", ...}]}

    iex> ContentCleaning.strip_echoed_keys(~s|{"key": "value"}|)
    {~s|{"key": "value"}|, []}

# `strip_echoed_keys_internal`
[🔗](https://github.com/nshkrdotcom/json_remedy/blob/v0.2.1/lib/json_remedy/layer1/content_cleaning.ex#L164)

```elixir
@spec strip_echoed_keys_internal(input :: {String.t(), [repair_action()]}) ::
  {String.t(), [repair_action()]}
```

Strip echoed/duplicated keys from JSON content (Gemini API bug pattern).

When Gemini (particularly gemini-2.5-flash-lite) returns structured JSON,
it sometimes echoes the key name inside the value:

    {"coach_notes": "coach_notes": "You showed grace under pressure."}

This function detects and repairs this pattern to produce valid JSON:

    {"coach_notes": "You showed grace under pressure."}

# `strip_trailing_dots`
[🔗](https://github.com/nshkrdotcom/json_remedy/blob/v0.2.1/lib/json_remedy/layer1/content_cleaning.ex#L264)

```elixir
@spec strip_trailing_dots(input :: String.t()) :: {String.t(), [repair_action()]}
```

Strip trailing dots from input.

Public API version that takes string input directly.
Detects and removes trailing dots that indicate truncation (10+ consecutive dots).

# `strip_trailing_dots_internal`
[🔗](https://github.com/nshkrdotcom/json_remedy/blob/v0.2.1/lib/json_remedy/layer1/content_cleaning.ex#L139)

```elixir
@spec strip_trailing_dots_internal(input :: {String.t(), [repair_action()]}) ::
  {String.t(), [repair_action()]}
```

Strip trailing dots from truncated content (Gemini max_output_tokens pattern).

When LLMs like Gemini hit max_output_tokens, they sometimes fill remaining tokens
with dots instead of stopping cleanly. This results in truncated JSON followed
by thousands of trailing dots.

This function detects and strips these trailing dots while preserving:
- Dots inside string values
- Legitimate ellipsis (...) in content
- Valid JSON structure

# `supports?`
[🔗](https://github.com/nshkrdotcom/json_remedy/blob/v0.2.1/lib/json_remedy/layer1/content_cleaning.ex#L297)

```elixir
@spec supports?(input :: String.t()) :: boolean()
```

Check if this layer can handle the given input.
Layer 1 can handle any text input that may contain JSON with wrapping content.

# `validate_options`
[🔗](https://github.com/nshkrdotcom/json_remedy/blob/v0.2.1/lib/json_remedy/layer1/content_cleaning.ex#L329)

```elixir
@spec validate_options(options :: keyword()) :: :ok | {:error, String.t()}
```

Validate layer configuration and options.
Layer 1 accepts options for enabling/disabling specific cleaning features.

---

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