# @x12i/rendrix

> Inline variant template rendering with Handlebars

Latest version **4.3.0** (published 2026-05-06) · MIT license · 0 weekly downloads

## Install

```sh
npm install @x12i/rendrix
pnpm add @x12i/rendrix
yarn add @x12i/rendrix
bun add @x12i/rendrix
```

## Health

**Score 55/100 (C)** — status: active.

Positive: has types; no vulnerabilities; high quality score.

Warnings: low downloads; no esm support.

## Facts

| | |
|---|---|
| Version | 4.3.0 |
| Published | 2026-05-06 |
| First published | 2026-04-24 |
| Weekly downloads | 0 |
| License | MIT |
| TypeScript types | bundled |
| Module format | CommonJS |
| Dependencies | 5 |
| Unpacked size | 612.7 KB |
| Known vulnerabilities | 0 |
| Install scripts | no |
| Maintainers | x12i |
| Keywords | handlebars, template, rendering, variant, conditional |

## Links

- npm: https://www.npmjs.com/package/@x12i/rendrix
- Repository: https://github.com/x12i/rendrix
- Homepage: https://github.com/x12i/rendrix#readme
- Issues: https://github.com/x12i/rendrix/issues
- npm.io page: https://npm.io/package/@x12i/rendrix

## Dependencies (5)

- [ajv](https://npm.io/package/ajv.md) ^8.17.1
- [flex-md](https://npm.io/package/flex-md.md) ^4.7.1
- [handlebars](https://npm.io/package/handlebars.md) ^4.7.8
- [nx-helpers](https://npm.io/package/nx-helpers.md) ^1.5.1
- [jsonc-parser](https://npm.io/package/jsonc-parser.md) ^3.3.1

## Recent versions

- 4.3.0 (latest) — 2026-05-06
- 4.2.0 — 2026-04-24

## README

# Rendrix - Inline Variant Template Rendering

A powerful TypeScript library for rendering Handlebars templates with multiple memory contexts and priority-based token resolution. This system allows you to manage complex template rendering scenarios with separate memory objects that automatically resolve tokens based on priority.

## Features

- 🎯 **Multi-Memory System**: Four distinct memory types with priority-based resolution
- 🔧 **Custom Handlebars Helpers**: Built-in helpers for common operations
- ✅ **Template Verification**: Detect unresolved template variables
- 📊 **Template Resolution Reporting**: See which tokens were parsed vs unparsed (template+memory) and detect leftover `{{}}` in output
- 📝 **TypeScript Support**: Full type definitions included
- 🚀 **JSON Template Enrichment**: Automatically enrich JSON templates based on configuration
- 🔀 **Priority-Based Resolution**: Tokens resolve automatically regardless of which memory object they're in
- 🧩 **Partial Rendering (v3.5)**: Staged template rendering with incremental state accumulation
- 💬 **Chat Message Support (v3.5)**: Structured chat message rendering using `{{#message}}` helper
- 📚 **Few-Shot Examples (v3.5)**: Flexible management and formatting of prompt examples
- 🔗 **Pipeline Composition (v3.5)**: Modular prompt building using template sections
- 🏗️ **Structured Output (v3.5)**: JSON schema-based format instructions and response parsing
- 📁 **File content inlining**: `{{file:<filename>}}` replaced by provided file contents (`filesContent`)
- ⚠️ **Template protocol (v4)**: MUST vs optional `{{path | fallback}}`, structured `TemplateResolutionError`, opt-in `subPathSearch` with JSON default root order — same rules for `render` and `renderChat`
- 🧠 **Semantic core directives**: `{{core:analysis}}`, `{{core:decision}}`, `{{core:question}}` are directive tokens (not memory paths)

## Installation

```bash
npm install @x12i/rendrix
```

## Quick Start

```typescript
import { render } from '@x12i/rendrix';

const template = `
# Project: {{organization.name}}

{{#if (contains flags "debug")}}
⚠️ Debug mode enabled
{{/if}}

Job Objective: {{job.objective}}
Current Task: {{task.objective}}

{{#if productInfo}}
# Product Information

{{productInfo}}
{{/if}}
`;

const workingMemory = {
  flags: ['debug', 'verbose'],
  organization: {
    name: 'My Company',
    industry: 'Technology'
  },
  job: {
    objective: 'Build amazing software'
  },
  task: {
    objective: 'Implement feature X'
  },
  productInfo: {
    name: 'My Product',
    features: ['Feature 1', 'Feature 2'],
    price: '$99'
  }
};

const output = render(template, workingMemory);
console.log(output);
```

## Core Concepts

### Memory system and effective root (merged context)

Rendering uses **one merged context object**: the four memory arguments are **layers** combined in a fixed order. Templates should **not** be thought of as “reading from one of four separate roots”; they read **paths on the merged result** (e.g. `{{input.question}}`, `{{execution.answeredQuestions}}`) wherever those keys exist after the overlay.

**Who merges what**

- **ai-gateway** (or your caller) builds `workingMemory`, `experienceMemory`, `knowledgeMemory`, and `shortTermMemory`, and should merge **per-request `templateTokens` into short-term** before calling `render` / `renderChat`.
- **This library** merges the four objects into the effective root inside **`buildTemplateRootContext`** (same order as `render`).

**Strict precedence on top-level key clash** (rank **1 = wins**). The parser exposes four memory arguments; ranks **1–2** both live in **`shortTermMemory`** after the gateway assembles it — merge gateway **`templateTokens` into short-term last** so per-request keys override other short-term fields.

| Rank | Source | Role |
|------|--------|------|
| **1** | Gateway **`templateTokens`** (merged into **`shortTermMemory`**) | Per-request overrides; strongest |
| **2** | Rest of **`shortTermMemory`** | Session / turn data |
| **3** | **`workingMemory`** | Primary job / task / user payload |
| **4** | **`experienceMemory`** | Enriched / learned context |
| **5** | **`knowledgeMemory`** | Reference / RAG-style context |

Implementation detail: the parser merge applies **knowledge → experience → working → short-term** (each spread overwrites duplicate **top-level** keys from the previous). So **`shortTermMemory`** wins over **`workingMemory`**, and within STM the gateway controls which keys win via its own merge order.

The **`MEMORY_PRIORITY`** export still uses **higher number = stronger** (`shortTermMemory: 4` … `knowledgeMemory: 1`) for any code that keys off that map.

### Layer contents (reference)

1. **ShortTermMemory** — Flexible key-value storage; session- or turn-specific data; **gateway should fold `templateTokens` here** before render.
2. **WorkingMemory** — Runtime state: `job`, `task`, `entity`, `organization`, execution-shaped data, etc.
3. **ExperienceMemory** — Learned context: knowHows, narratives, experiences, insights, …
4. **KnowledgeMemory** — General knowledge base; weakest overlay.

### Priority-Based Token Resolution

**Top-level keys** from any layer appear on the merged root; if the same key exists in multiple layers, **precedence follows the table above**. You do not need to nest everything under `workingMemory.*` in templates unless you want the namespaced copy (see below).

```typescript
// Same top-level key in multiple memories → strongest layer wins
const shortTermMemory = { name: 'Short Term' };
const workingMemory = { job: {}, task: {}, name: 'Working' };

// Template: {{name}}
// Result: "Short Term"
```

**Namespaced copies:** The merged root also exposes **`workingMemory`**, **`shortTermMemory`**, **`experienceMemory`**, and **`knowledgeMemory`** as nested objects for templates that need them (e.g. `{{workingMemory.job.objective}}`).

### Automatic Markdown Conversion

**Objects and arrays are automatically converted to markdown by default!** This means you can use `{{variableName}}` directly and get beautifully formatted markdown output with title-case keys.

**Example:**
```typescript
const workingMemory = {
  productInfo: {
    name: 'CloudSync Pro',
    features: ['Storage', 'Collaboration'],
    pricing: '$15/month'
  }
};

// Template: {{productInfo}}
// Output: 
// # Name
// CloudSync Pro
//
// # Features
// - Storage
// - Collaboration
//
// # Pricing
// $15/month
```

If you need JSON output instead, use the `{{json variableName}}` helper:

```handlebars
{{json productInfo}}
```

This will output:
```json
{
  "name": "CloudSync Pro",
  "features": ["Storage", "Collaboration"],
  "pricing": "$15/month"
}
```

## API Reference

### Template protocol (v4)

**Applies to `render`, `renderChat`, and (for path resolution) `analyzeTemplateResolution` when you pass options.**

- **Simple variable mustaches** matching `{{identifier}}` or `{{a.b.c}}` (and optional `| fallback`) are handled by the protocol. Helpers, blocks, `{{file:...}}`, and `{{json ...}}` are unchanged.
- **Semantic directives — `{{core:value}}`:** `core` is treated as a directive namespace, not object traversal. These tokens render as empty string by default and never trigger missing path errors.
- **Directive validation:** `{{core:}}` is invalid and throws. Whitespace is trimmed (for example `{{core: analysis}}` is parsed as value `analysis`).
- **MUST — `{{path}}`:** If the value is missing (`undefined` after lookup, including [sub-path search](#sub-path-search-subpathsearch)), rendering throws **`TemplateResolutionError`** with `type`, `token`, `attemptedPath`, `availableKeys`, and optional `template` (from `templateId`). Values **`null`**, **`""`**, **`0`**, **`false`** count as present (no throw).
- **OPTIONAL — `{{path |}}` or `{{path | fallback text}}`:** If missing, output is empty or the fallback text; never throws for that token.
- **Legacy behavior:** Pass **`renderOptions.silentMissingMustTokens: true`** to treat missing MUST tokens as empty strings (Handlebars-style), with no throw.
- **Default root order** when `subPathSearch` is enabled and you omit `roots`: see **`src/config/default-template-protocol.json`** (copied to **`dist/config/`** on build). Edit that JSON to change priority; it does **not** enable sub-path search by itself.

#### Sub-path search (`subPathSearch`)

**Default:** `subPathSearch.enabled` is **off** unless you set it. With search off, resolution is **full path only** (strict): missing MUST paths throw **`TemplateResolutionError`** after the merge.

Opt-in on **`TemplateRenderOptions`** / **`ChatRenderOptions`**:

```typescript
subPathSearch?: {
  enabled?: boolean;  // default false — must be true to search
  roots?: string[];  // omit → use parser package defaults (do not duplicate in gateway)
};
```

When `enabled: true` and **`roots` is omitted**, defaults come only from **`DEFAULT_TEMPLATE_PROTOCOL_CONFIG`** / **`src/config/default-template-protocol.json`** (copied to **`dist/config/`** on build). **Recommended default roots** (skills / graphs; **order matters**): **`execution`**, then **`input`**, then **`inputs`** — so execution-shaped data is tried before generic input buckets.

When **`roots` is provided**, that **ordered list** is used; **first successful match wins** along the algorithm in **`resolveTokenPath`** (`template-resolution.ts`): exact path first, then leading-root swap, then prepend each root.

### `render(template, workingMemory, shortTermMemory?, experienceMemory?, knowledgeMemory?, functionsMap?, choiceOptions?, filesContent?, renderOptions?)`

Renders a Handlebars template with all memory contexts. Uses a fresh **`Handlebars.create()`** instance per call (isolated helpers). Tokens are resolved with priority-based lookup. Objects and arrays are automatically converted to markdown format. Any `{{file:<filename>}}` token is replaced by the corresponding entry in `filesContent` when `entry.file` matches `<filename>`.

**Parameters:**
- `template: string` - The Handlebars template string
- `workingMemory: WorkingMemory` - Runtime state object (required)
- `shortTermMemory?: ShortTermMemory` - Optional short-term memory (highest priority)
- `experienceMemory?: ExperienceMemory` - Optional experience memory
- `knowledgeMemory?: KnowledgeMemory` - Optional knowledge memory (lowest priority)
- `functionsMap?: FunctionMap` - Optional function map for function calls in templates
- `choiceOptions?: ChoiceOptions` - Optional choice options for options helper
- `filesContent?: FilesContent` - Optional list of `{ file, content }`; `{{file:<filename>}}` is replaced by `content` when `file` matches
- `renderOptions?: TemplateRenderOptions` - Optional **`templateId`**, **`subPathSearch`**, **`silentMissingMustTokens`** (see [Template protocol (v4)](#template-protocol-v4))

**Returns:** `string` - Rendered template

## Smart Input token

Rendrix supports a special **case-insensitive** insertion token:

- `{{smartInput}}` (also `{{ smartinput }}`, `{{SMARTINPUT}}`, etc.)

When this exact token appears in a template, Rendrix replaces the **whole token** (before the normal Handlebars render) with markdown generated from runtime configuration supplied via `renderOptions.smartInput` / `ChatRenderOptions.smartInput`.

### Important boundaries

- Only the exact token `{{smartInput}}` (case-insensitive, whitespace allowed) is special.
- Rendrix does **not** treat these as the Smart Input macro:
  - `{{smartInput.paths}}`
  - `{{#if smartInput}} ... {{/if}}`
  - `{{json smartInput}}`

### Behavior

- If the template contains `{{smartInput}}` and `renderOptions.smartInput` is **omitted**, it is replaced with **empty string**.
- If `smartInput.paths` is empty, `{{smartInput}}` renders as **empty string**.
- Paths are resolved against the same merged render root that normal tokens use (`buildTemplateRootContext` output).
- After generation, the markdown is also injected into the render root as `smartInputMarkdown`, so templates can use `{{smartInputMarkdown}}` as a normal variable path token (case-sensitive).

### Example

```typescript
import { render } from '@x12i/rendrix';

const out = render(
  "Context:\\n{{smartInput}}\\nQuestion: {{input.question}}",
  {
    job: {},
    task: {},
    input: { question: "What exposure exists?" },
    executionMemory: {
      graphOutputs: {
        candidateIngressPaths: [{ pathId: "p1" }]
      }
    },
    xynthesized: {
      job: {
        topologyReachabilitySummary: { mainIngress: "shared-alb" }
      }
    }
  } as any,
  undefined,
  undefined,
  undefined,
  undefined,
  undefined,
  undefined,
  {
    smartInput: {
      paths: [
        {
          title: "Topology summary",
          path: "xynthesized.job.topologyReachabilitySummary"
        },
        {
          title: "Candidate ingress paths",
          path: "executionMemory.graphOutputs.candidateIngressPaths",
          required: true
        }
      ]
    }
  }
);
```

### enrichJsonTemplate scope (phase 1)

The Smart Input token is supported in `render` / `renderChat` first. It is **not** supported in `enrichJsonTemplate` in phase 1.

**Example:**
```typescript
import { render, FunctionMap } from '@x12i/rendrix';

// Without function map
const output = render(
  'Hello {{name}}!',
  { job: {}, task: {} },  // workingMemory (required)
  { name: 'World' },      // shortTermMemory (optional)
  {},                     // experienceMemory (optional)
  {}                      // knowledgeMemory (optional)
);

// With function map
type MyFunctions = {
  format: (value: any) => string;
};

const functionsMap: MyFunctions = {
  format: (value) => JSON.stringify(value, null, 2)
};

const output2 = render(
  'Formatted: {{function "format" data}}',
  { job: {}, task: {}, data: { key: 'value' } },
  undefined,
  undefined,
  undefined,
  functionsMap
);

// With filesContent: {{file:<filename>}} is replaced by the matching entry's content
const output3 = render(
  'Instructions:\n{{file:readme}}\n---\nContext: {{name}}',
  { job: {}, task: {} },
  { name: 'Alice' },
  undefined,
  undefined,
  undefined,
  undefined,
  [{ file: 'readme', content: 'Follow these steps.' }]
);
// "Instructions:\nFollow these steps.\n---\nContext: Alice"

// Optional: legacy silent missing MUST tokens, or template id for errors
render('Hello {{maybe}}!', { job: {}, task: {} }, undefined, undefined, undefined, undefined, undefined, undefined, {
  silentMissingMustTokens: true,
});
render('{{input.x}}', wm, undefined, undefined, undefined, undefined, undefined, undefined, {
  subPathSearch: { enabled: true },
  templateId: 'my-prompt',
});
```

**See also:** [File Content Inlining](functionality/files-content.md) for `{{file:<filename>}}`, `filesContent`, and usage in `render`, `partial`, `pipeline`, `enrichJsonTemplate`, `renderChat`, and `analyzeTemplateResolution`.

### `enrichJsonTemplate(jsonTemplate, workingMemory?, shortTermMemory?, experienceMemory?, knowledgeMemory?, opts?)`

Enriches a JSON template string by rendering Handlebars conditionals. Supports optional JSON validation and JSONC (JSON with comments) parsing.

**Parameters:**
- `jsonTemplate: string` - JSON template string with Handlebars syntax
- `workingMemory?: WorkingMemory` - Optional working memory context
- `shortTermMemory?: ShortTermMemory` - Optional short-term memory
- `experienceMemory?: ExperienceMemory` - Optional experience memory
- `knowledgeMemory?: KnowledgeMemory` - Optional knowledge memory
- `opts?: EnrichJsonTemplateOptions` - Optional validation and parsing options; may include **`templateRenderOptions`** (same shape as `TemplateRenderOptions` for `render`) to pass **`templateId`**, **`subPathSearch`**, or **`silentMissingMustTokens`**

**Returns:** `string` - Enriched JSON string with conditionals resolved

**Example (Basic):**
```typescript
import { enrichJsonTemplate } from '@x12i/rendrix';

const jsonTemplate = `{
  "confidence": 0.85{{#if includeCitations}},
  "citations": ["Citation 1"]
  {{/if}}{{#if includeReasoning}},
  "reasoning": "Explanation"
  {{/if}}
}`;

const workingMemory = {
  job: {},
  task: {},
  includeCitations: true,
  includeReasoning: true
};

const enriched = enrichJsonTemplate(jsonTemplate, workingMemory);
// Returns JSON with citations and reasoning included
```

**Example (With Validation):**
```typescript
import { enrichJsonTemplate } from '@x12i/rendrix';
import type { JSONSchemaType } from 'ajv';

const schema: JSONSchemaType<{ confidence: number; citations?: string[] }> = {
  type: 'object',
  properties: {
    confidence: { type: 'number' },
    citations: { type: 'array', items: { type: 'string' } }
  },
  required: ['confidence'],
  additionalProperties: false
};

const enriched = enrichJsonTemplate(
  jsonTemplate,
  workingMemory,
  undefined,
  undefined,
  undefined,
  {
    validate: true,
    schema,
    parseMode: 'strict', // or 'jsonc' for JSON with comments
    failOnUnresolvedTokens: true
  }
);
```

**See also:** [JSON Enrichment Improvements](functionality/json-enrichment-improvements.md) for detailed documentation on validation and JSONC support.

### `verifyTemplate(output)`

Checks the **rendered output** for any `{{...}}` still present. Useful to catch leftover mustaches. **Note:** Simple MUST variables that are missing either **throw** during `render` (default) or render as empty if you set **`silentMissingMustTokens: true`** — in the latter case, `verifyTemplate` will not see them. Non-rewritten helpers or typos may still leave `{{` in the output. Use **`analyzeTemplateResolution`** to find which paths are missing in memory before rendering.

**Parameters:**
- `output: string` - The rendered template output

**Returns:** `{ valid: boolean, unresolvedTokens: string[] }`

### `analyzeTemplateResolution(template, workingMemory, shortTermMemory?, experienceMemory?, knowledgeMemory?, filesContent?, resolutionOptions?)`

Analyzes **template + memory** and reports which variable paths were **parsed** (resolved, value ≠ `undefined`) and which were **unparsed** (missing in the merged context). Uses the same merge order and path resolution as `render()` (including **`subPathSearch`** when passed in **`resolutionOptions`**). Optional tokens `{{path | fallback}}` count as parsed with the fallback value when the path is missing. Supports `filesContent` so `{{file:<filename>}}` is resolved when provided.

**Parameters:** Same memory/`filesContent` order as `render()` (without `functionsMap`, `choiceOptions`, or the ninth render argument). Optional last argument **`resolutionOptions?: TemplateRenderOptions`** (typically only **`subPathSearch`** is used here).

**Returns:** `{ parsed: Array<{ path: string; value: unknown }>; unparsed: string[] }`

**Example:**
```typescript
import { analyzeTemplateResolution } from '@x12i/rendrix';

const r = analyzeTemplateResolution(
  'Hello {{name}}! Project: {{project}}.',
  { job: {}, task: {} },
  { name: 'Alice' }
);
// r.parsed  = [ { path: 'name', value: 'Alice' } ]
// r.unparsed = [ 'project' ]
```

**See also:** [Template Resolution Reporting](functionality/template-resolution-report.md) for `analyzeTemplateResolution`, `verifyTemplate`, and when to use each.

### `listTokens(template, options?)`

Returns token references from template source **without rendering**. This is useful for contract discovery (for example, required semantic paths) before runtime value resolution.

**Compatibility:** additive API; does not change `render()` or `renderChat()` behavior.

**Type shape:**

```typescript
type TemplateTokenKind = 'path' | 'helper' | 'block' | 'core' | 'unknown';

interface TemplateTokenRef {
  raw: string;  // token content without outer {{ }}
  path?: string;
  directive?: string;
  value?: string;
  kind: TemplateTokenKind;
  start: number;
  end: number;
}
```

**Options:**
- `includeDuplicates?: boolean` (default `false`) — include repeated references of the same token expression.

**Example:**

```typescript
import { listTokens } from '@x12i/rendrix';

const refs = listTokens('{{core:analysis}} {{#if x}}{{/if}} {{json payload}} {{metadata.intent}}');
// refs includes:
// - { kind: 'core', directive: 'core', value: 'analysis', ... }
// - { kind: 'block', raw: '#if x', ... }
// - { kind: 'helper', raw: 'json payload', ... }
// - { kind: 'path', path: 'metadata.intent', ... }
```

### `resolveTokenValues({ template, context, selectors, options? })`

Resolves only selected **path tokens** from a template and context, without full template rendering. Helpers/blocks/directive tokens are ignored for value resolution.

**Compatibility:** additive API; does not change existing template rendering behavior.

**Selectors:**
- exact path: `question`, `metadata.intent`
- segment wildcard: `metadata.*` (matches one segment, e.g. `metadata.intent`)

**Missing handling:**
- default (`strict: false`): returns entries with `found: false`
- strict (`strict: true`): throws if any selected token is missing or a selector matches no path token

**Example:**

```typescript
import { resolveTokenValues } from '@x12i/rendrix';

const values = resolveTokenValues({
  template: '{{core:analysis}} {{metadata.intent}} {{question}}',
  context: {
    metadata: { intent: 'summarize' },
  },
  selectors: ['metadata.intent', 'question'],
});
// [
//   { token: 'metadata.intent', selector: 'metadata.intent', value: 'summarize', found: true },
//   { token: 'question', selector: 'question', value: undefined, found: false }
// ]
```

### `listDirectiveValues(template, directive)`

Returns directive values in source order (including duplicates).

**Example:**

```typescript
import { listDirectiveValues } from '@x12i/rendrix';

const values = listDirectiveValues(
  'System:\n- {{core:analysis}}\n- {{core: decision}}\n- {{core:analysis}}',
  'core'
);
// ['analysis', 'decision', 'analysis']
```

### `renderChat(template, workingMemory, shortTermMemory?, experienceMemory?, knowledgeMemory?, options?)`

Returns an array of **`ChatMessage`** objects from a template that uses **`{{#message}}`**. Same template protocol as **`render`** (MUST / optional `|`, **`subPathSearch`**, **`TemplateResolutionError`**, **`silentMissingMustTokens`**). There is **no** `functionsMap` or `choiceOptions` on this entry point; pass **`filesContent`** inside **`options`** when needed.

**`options?: ChatRenderOptions`** extends **`TemplateRenderOptions`** with **`stripEmpty`**, **`trimContent`**, **`mergeConsecutive`**, and **`filesContent`**.

### `registerHelpers(handlebars?)`

Registers custom Handlebars helpers on the given instance (default: the global Handlebars). **`render()`** and **`renderChat()`** use **`Handlebars.create()`** and register helpers on that instance automatically; you usually do not need to call this unless you compile templates yourself. When you pass **`functionsMap`** to **`render`**, function helpers are registered on the same per-call instance.

### Exports (protocol-related)

- **`TemplateResolutionError`** — thrown when a MUST token cannot be resolved
- **`DEFAULT_TEMPLATE_PROTOCOL_CONFIG`** — parsed default JSON (including `subPathSearch.roots`)
- **`buildTemplateRootContext`**, **`wrapForTemplate`** — same context shape as `render` (e.g. for advanced use)
- **`registerChatHelpersOn(handlebars)`** — register `{{#message}}` on a specific Handlebars instance

## Extended Features (v3.5+)

Rendrix v3.5 introduced staged rendering, chat, and pipeline features; **v4** aligns **`renderChat`** with the same template protocol as **`render`** (MUST / optional `|`, `subPathSearch`, errors).

### Partial Rendering

Partial rendering allows you to accumulate state incrementally before the final render.

```typescript
import { partial } from '@x12i/rendrix';

const p = partial('Hello {{name}}! Welcome to {{place}}.', { name: 'Alice' });
const p2 = p.partial(undefined, { place: 'Wonderland' });
const result = p2.render(); // "Hello Alice! Welcome to Wonderland."
```

### Chat Message Rendering

Render structured chat messages using the `{{#message}}` helper. **`renderChat`** uses the **same** preprocessing and variable rules as **`render`** (including **`TemplateResolutionError`** for missing MUST paths, **`{{path | fallback}}`**, **`subPathSearch`**, and **`silentMissingMustTokens`** on **`ChatRenderOptions`**).

```handlebars
{{#message role="system"}}
You are a {{role}} assistant.
{{/message}}

{{#message role="user"}}
My name is {{userName}}.
{{/message}}
```

```typescript
import { renderChat } from '@x12i/rendrix';

const messages = renderChat(template, { job: {}, task: {}, role: 'helpful' }, { userName: 'Bob' });
// Returns: [ { role: 'system', content: '...' }, { role: 'user', content: '...' } ]

// Same optional protocol flags as render (on the last options argument):
// renderChat(template, workingMemory, stm, em, km, { silentMissingMustTokens: true, subPathSearch: { enabled: true } })
```

### Few-Shot Examples

Manage prompt examples with flexible selection strategies.

```typescript
import { fewShot } from '@x12i/rendrix';

const fs = fewShot({
    examples: [
        { input: 'Hi', output: 'Hello' },
        { input: 'Bye', output: 'Goodbye' }
    ],
    maxExamples: 1,
    selector: 'random'
});

const prompt = fs.format({}, (ex) => `Input: ${ex.input}\nOutput: ${ex.output}`);
```

### Pipeline Composition

Build modular prompts from reusable template sections.

```typescript
import { pipeline } from '@x12i/rendrix';

const pipe = pipeline([
    { name: 'header', template: '# {{title}}' },
    { name: 'body', template: 'Content: {{content}}' },
    { name: 'footer', template: 'Footer: {{footer}}', condition: 'showFooter' }
]);

const output = pipe.render({ title: 'My Doc' }, { content: '...', showFooter: true });
```

### Structured Output Parsers

Generate format instructions and parse JSON responses.

```typescript
import { fromSchemas } from '@x12i/rendrix';

const parser = fromSchemas([
    { name: 'answer', description: 'The final answer', type: 'string' }
]);

console.log(parser.getFormatInstructions());
const data = parser.parse(llmResponse);
```

## Custom Helpers

The library provides several custom Handlebars helpers:

| Helper | Usage | Description |
|--------|-------|-------------|
| `eq` | `{{#if (eq value "test")}}` | Equality check |
| `neq` | `{{#if (neq status "ready")}}` | Inequality check |
| `gt` | `{{#if (gt value 10)}}` | Greater than |
| `lt` | `{{#if (lt value 5)}}` | Less than |
| `contains` | `{{#if (contains flags "debug")}}` | Array contains |
| `and` | `{{#if (and flag1 flag2)}}` | Logical AND |
| `or` | `{{#if (or flag1 flag2)}}` | Logical OR |
| `json` | `{{json variableName}}` | Convert object/array to JSON format (default is markdown) |
| `function` | `{{function "name" expression}}` or `{{function "name"}}` | Call a function from functionsMap (optional). Can be used in conditionals: `{{#if (function "check" value)}}` |
| `limit` | `{{limit <number> <value>}}` | Limit the number of words in text/string |
| `limitProperties` | `{{limitProperties <number> <value>}}` | Limit words in each property of an object (use namespaced access for objects) |
| `limitArray` | `{{limitArray <number> <value>}}` | Limit the number of items in an array (use namespaced access for arrays) |
| `smartLimit` | `{{smartLimit "<items>/<words>" <value>}}` | Apply all limit strategies: limit items and words (use namespaced access for objects/arrays) |
| `section` | `{{section "title"}}` | Create a formatted section header with clear separator |
| `ifSection` | `{{#ifSection <condition> "title"}} ... {{/ifSection}}` | Conditional section that combines if logic with section formatting |
| `options` | `{{options}}` | Render choice options list (requires choiceOptions parameter) |
| `getOption` | `{{getOption <selection>}}` | Get selected option value (requires choiceOptions parameter) |

## Function Map

The Function Map feature allows you to register custom functions that can be called from templates. This is **optional** - you only need to provide a `functionsMap` if you want to use function calls in your templates.

### What is a Function Map?

A **Function Map** is a typed object that maps **string keys** to **functions**, where each key is bound to an exact function signature.

In TypeScript, it is the safest way to register and call functions **by name** without losing type safety.

### Why use a Function Map?

A Function Map gives you:

* ✅ Compile-time validation of function names
* ✅ Compile-time validation of arguments and return types
* ✅ No mismatch between a function's name and its implementation
* ✅ Simple runtime behavior (just plain JavaScript)

This pattern is ideal for **tool registries, skill systems, routers, and dispatchers**.

### Basic Example

```typescript
import { render, FunctionMap } from '@x12i/rendrix';

// Define your function map type
type MyFunctionMap = {
  sum: (a: number, b: number) => number;
  greet: (name: string) => string;
  format: (value: any) => string;
  getTimestamp: () => string;
  hasData: (obj: any) => boolean;
  isValid: (value: any) => boolean;
};

// Register functions
const functionsMap: MyFunctionMap = {
  sum: (a, b) => a + b,
  greet: (name) => `Hello ${name}`,
  format: (value) => JSON.stringify(value, null, 2),
  getTimestamp: () => new Date().toISOString(),
  hasData: (obj) => obj && Object.keys(obj).length > 0,
  isValid: (value) => value !== null && value !== undefined
};

const template = `
Sum: {{function "sum" 5 3}}
Greeting: {{function "greet" userName}}
Formatted: {{function "format" workingMemory.data}}
Timestamp: {{function "getTimestamp"}}

{{#if (function "hasData" data)}}
  Data is available: {{data}}
{{/if}}

{{#if (function "isValid" user)}}
  User is valid
{{/if}}
`;

const workingMemory = {
  job: {},
  task: {},
  userName: 'World',
  data: { key: 'value' }
};

const output = render(template, workingMemory, undefined, undefined, undefined, functionsMap);
```

### Function Call Syntax

Use the `function` helper to call functions from your functionsMap:

**Syntax**: `{{function "functionName" expression}}` or `{{function "functionName"}}`

- First argument is the function name as a string (required)
- Second argument (optional) is the expression value to pass to the function
- If no expression is provided, the function is called with no arguments
- **Values are passed AS IS**: If the expression is an object, it stays an object (not converted to markdown)

**Examples:**

```handlebars
<!-- With expression - value is passed AS IS (object stays object, string stays string) -->
{{function "format" productInfo}}
{{function "format" workingMemory.data}}  <!-- Use namespaced access for original objects -->

<!-- Without expression - function called with no arguments -->
{{function "getTimestamp"}}

<!-- In conditionals - function should return boolean -->
{{#if (function "hasData" data)}}
  Data is available
{{/if}}

{{#if (function "isValid" workingMemory.user)}}
  User is valid
{{else}}
  User is invalid
{{/if}}

{{#if (function "checkFlag")}}
  Flag is enabled
{{/if}}
```

### Important Notes

- **Values are passed AS IS**: When you use `{{function "name" expression}}`, the value from the expression is passed directly to the function. If it's an object, it remains an object (not converted to markdown).
- **Function Map is optional**: You only need to provide `functionsMap` if you're using function calls in your templates.
- **Type safety**: TypeScript will validate function names and signatures at compile time.
- **Error handling**: If a function is not found or throws an error, a descriptive error message is thrown.
- **Use in conditionals**: Functions can be used in `{{#if}}` conditionals using subexpressions. The function should return a boolean value:
  ```handlebars
  {{#if (function "checkCondition" variable)}}
    Content here
  {{/if}}
  ```

### When to use Function Maps

Use a Function Map when you need:

* Custom formatting or transformation functions
* Dynamic data processing in templates
* Reusable utility functions across templates
* AI tool / skill registries
* Plugin systems

### Key Takeaway

> A Function Map turns string-based function calls into **fully typed, compile-time-safe operations**, while staying simple and runtime-light.

## Variable Access

### Direct Access (Recommended)

Variables can be accessed directly without prefixes, thanks to priority-based resolution. **Objects and arrays are automatically converted to markdown format by default:**

```handlebars
{{#if execution}}
  # Execution
  
  {{execution}}
{{/if}}

{{name}}
{{job.objective}}
{{task.context}}
{{productInfo}}  <!-- Automatically converted to markdown -->
```

**Note:** For conditionals like `{{#if execution}}`, use the original object from namespaced access (e.g., `{{#if workingMemory.execution}}`) or the object will be truthy as a string. Direct access in conditionals works because the original objects are preserved in namespaced contexts.

### JSON Output

If you need JSON format instead of markdown, use the `json` helper with **namespaced access** to get the original object:

```handlebars
{{json workingMemory.productInfo}}  <!-- Outputs JSON instead of markdown -->
```

**Note:** Use namespaced access (e.g., `workingMemory.productInfo`) with the `json` helper to access the original object. Direct access like `{{json productInfo}}` will return the markdown string since objects are automatically converted to markdown in the merged context.

### Namespaced Access

You can also access variables with explicit namespaces:

```handlebars
{{#if workingMemory.flags}}
  {{workingMemory.job.objective}}
{{/if}}

{{shortTermMemory.userName}}
{{experienceMemory.knowHows}}
{{knowledgeMemory.facts}}
```

## Limit Helpers

The library provides several helpers to limit the size of output content, useful for controlling token usage and output length.

### `limit` - Word Limit for Text

Limits the number of words in a text string:

```handlebars
{{limit 10 longText}}  <!-- Limits to 10 words -->
```

**Example:**
```typescript
const workingMemory = {
  job: {},
  task: {},
  longText: 'This is a very long text that has many words in it'
};

// Template: {{limit 5 longText}}
// Output: "This is a very long..."
```

### `limitProperties` - Word Limit for Object Properties

Limits the number of words in each property of an object. **Use namespaced access to get the original object:**

```handlebars
{{limitProperties 5 workingMemory.object}}  <!-- Limits each property to 5 words -->
```

**Example:**
```typescript
const workingMemory = {
  job: {},
  task: {},
  productInfo: {
    name: 'Product Name',
    description: 'This is a very long description with many words',
    price: 'Expensive'
  }
};

// Template: {{limitProperties 3 workingMemory.productInfo}}
// Output: Markdown with each property limited to 3 words
```

### `limitArray` - Item Limit for Arrays

Limits the number of items in an array. **Use namespaced access to get the original array:**

```handlebars
{{limitArray 3 workingMemory.array}}  <!-- Limits to 3 items -->
```

**Example:**
```typescript
const workingMemory = {
  job: {},
  task: {},
  features: ['Feature 1', 'Feature 2', 'Feature 3', 'Feature 4', 'Feature 5']
};

// Template: {{limitArray 2 workingMemory.features}}
// Output: Markdown list with only first 2 items
```

### `smartLimit` - Combined Limiting Strategy

Applies multiple limit strategies at once. Takes a spec in the format `"<items>/<words>"` where:
- `items` - Maximum number of items/properties
- `words` - Maximum number of words per item/property

```handlebars
{{smartLimit "5/100" workingMemory.data}}  <!-- Limit to 5 items, 100 words each -->
```

**Example:**
```typescript
const workingMemory = {
  job: {},
  task: {},
  data: {
    prop1: 'Long text with many words',
    prop2: 'Another long text',
    prop3: 'Third property',
    prop4: 'Fourth property'
  }
};

// Template: {{smartLimit "2/3" workingMemory.data}}
// Output: Only first 2 properties, each limited to 3 words
```

**How smartLimit works:**
- **For arrays**: Limits to `items` number of array elements, then limits words in each element to `words`
- **For objects**: Limits to `items` number of properties, then limits words in each property value to `words`
- **For strings**: Limits words to `words` (items limit is ignored)

**Important Notes:**
- For `limitProperties`, `limitArray`, and `smartLimit` with objects/arrays, use **namespaced access** (e.g., `workingMemory.data`) to get the original object/array
- Direct access will work with strings, but for objects/arrays you need namespaced access to avoid the markdown conversion
- All limit helpers add `...` when content is truncated

## Section Helpers

The library provides helpers for creating clearly formatted sections in your templates.

### `section` - Section Header

Creates a formatted section header with a clear separator frame:

```handlebars
{{section "Section Title"}}
```

**Example:**
```typescript
const workingMemory = {
  job: {},
  task: {},
  productInfo: {
    name: 'Product Name',
    price: '$99'
  }
};

// Template:
// {{section "Product Information"}}
// {{productInfo}}

// Output:
// ══════════════════════════════════════════════════
//   Product Information
// ══════════════════════════════════════════════════
//
// # Name
// Product Name
//
// # Price
// $99
```

### `ifSection` - Conditional Section

Combines conditional logic with section formatting. Only displays the section header and content if the condition is true:

```handlebars
{{#ifSection <condition> "Section Title"}}
  Content here
{{/ifSection}}
```

You can also use an `{{else}}` block:

```handlebars
{{#ifSection hasData "Data Section"}}
  {{data}}
{{else}}
  No data available
{{/ifSection}}
```

**Example:**
```typescript
const workingMemory = {
  job: {},
  task: {},
  execution: {
    step1: 'Completed',
    step2: 'In progress'
  },
  hasData: true
};

// Template:
// {{#ifSection hasData "Execution Status"}}
// {{execution}}
// {{/ifSection}}

// Output (when hasData is true):
// ══════════════════════════════════════════════════
//   Execution Status
// ══════════════════════════════════════════════════
//
// # Step1
// Completed
//
// # Step2
// In progress
```

**Example with Arrays:**
```typescript
const workingMemory = {
  job: {},
  task: {},
  execution: {
    answeredQuestions: [
      { question: 'Q1', shortAnswer: 'A1', longAnswer: 'L1' },
      { question: 'Q2', shortAnswer: 'A2', longAnswer: 'L2' }
    ]
  }
};

// Template (use namespaced access for arrays):
// {{#ifSection workingMemory.execution.answeredQuestions "Previously Answered Questions"}}
// {{#each workingMemory.execution.answeredQuestions}}
// Q: {{question}}
// Short: {{shortAnswer}}
// Long: {{longAnswer}}
//
// {{/each}}
// {{/ifSection}}

// Output (when array has items):
// ═══════════════════════════════════════════════════════════════
//   Previously Answered Questions
// ═══════════════════════════════════════════════════════════════
//
// Q: Q1
// Short: A1
// Long: L1
//
// Q: Q2
// Short: A2
// Long: L2
//
```

**Note:** When the array is empty, the entire section (header and content) is not rendered.

**Use Cases:**
- Organize long templates into clear sections
- Conditionally show entire sections based on data availability
- Create visually distinct sections in output documents
- Combine conditional logic with section formatting in one helper

**Important Notes:**
- **Array handling**: Empty arrays (`[]`) are treated as falsy - the section won't render
- **Object handling**: Empty objects (`{}`) are treated as falsy - the section won't render
- **String handling**: Empty strings are treated as falsy
- **Use namespaced access** for arrays/objects in conditions: `{{#ifSection workingMemory.execution.answeredQuestions "Title"}}` to access the original array/object instead of the markdown string
- **Direct access** works for simple values, but for arrays/objects, use namespaced access to ensure proper truthiness checking

## Options Helpers

The library provides helpers for rendering and selecting from choice options. These helpers require a `choiceOptions` parameter to be passed to the `render` function (similar to `functionsMap`).

### `options` - Render Choice Options

Renders a formatted list of options from the `choiceOptions` parameter:

```handlebars
{{options}}
```

**Input Format:**
```typescript
type ChoiceOptions = Array<{
  title: string;
  type: 'index' | 'enums';
  options: any[];
}>;
```

**Example:**
```typescript
const choiceOptions = [{
  title: 'Choose Product',
  type: 'index',
  options: ['Product A', 'Product B', 'Product C']
}];

// Template: {{options}}
// Output:
// Choose Product:
// 1: Product A
// 2: Product B
// 3: Product C
```

**Indexed Options:**
- Uses 1-based numbering (1, 2, 3, ...)
- Simple format: `1: Option Value`

**Enum Options:**
- Uses the enum key as the identifier
- Format: `enumKey: Option Value`
- For objects: `{ 'key': 'value' }` → `key: value`

**Nested Options:**
- Supports parent-child relationships
- Format: `[parent, [child1, child2, ...]]`
- Nested items shown with indentation: `     - childKey: childValue`

### `getOption` - Get Selected Option

Returns the selected option value from `choiceOptions`:

```handlebars
{{getOption 1}}              <!-- Index-based selection (1-based) -->
{{getOption "premium"}}       <!-- Enum-based selection -->
{{getOption "1,2"}}           <!-- Nested selection (parent,child) -->
{{getOption 0}}               <!-- Special: None -->
{{getOption "other"}}         <!-- Special: Other -->
```

**Supported Selection Types:**
- **Number (1-based)**: For indexed options, e.g., `1`, `2`, `3`
- **String (enum key)**: For enum options, e.g., `"premium"`, `"basic"`
- **Nested**: `"parentKey,childKey"` or `"1,2"` for nested selections
- **Special values**: `0`, `-1`, `"none"`, `"other"` return `"None"` or `"Other"`

**Example:**
```typescript
const choiceOptions = [{
  title: 'Choose Service',
  type: 'enums',
  options: [
    { 'basic': 'Basic Service - $10/month' },
    { 'premium': 'Premium Service - $25/month' }
  ]
}];

// Template: {{getOption "premium"}}
// Output: "Premium Service - $25/month"
```

**Important Notes:**
- `choiceOptions` must be passed as the 7th parameter to `render()`
- Index count starts at 1 (not 0)
- For enum options with objects, returns the value (not the object)
- Special values (`0`, `-1`, `"none"`, `"other"`) return string labels

## Type Definitions

### WorkingMemory

```typescript
interface WorkingMemory {
  flags?: string[];
  
  entity?: {
    name?: string;
    type?: string;
    description?: string;
  };
  
  organization?: {
    name?: string;
    industry?: string;
    size?: string;
    description?: string;
  };
  
  job: {
    objective?: string | object | object[] | string[];
    context?: string | object | object[] | string[];
    data?: object | object[] | string[] | string;
    narrative?: object | object[] | string[] | string;
    bigPicture?: object | object[] | string[] | string;
  };
  
  task: {
    objective?: string | object | object[] | string[];
    context?: string | object | object[] | string[];
    data?: object | object[] | string[] | string;
    narrative?: object | object[] | string[] | string;
    governance?: object | object[] | string[] | string;
  };
}
```

**Note:** `job` and `task` are **required** fields (cannot be undefined).

### ShortTermMemory

```typescript
interface ShortTermMemory {
  [key: string]: string | number | boolean | object | object[];
}
```

Flexible key-value storage for temporary data. Highest priority (4).

### ExperienceMemory

```typescript
interface ExperienceMemory {
  knowHows?: object | object[] | string[] | string;
  narratives?: object | object[] | string[] | string;
  experiences?: object | object[] | string[] | string;
  insights?: object | object[] | string[] | string;
  learnings?: object | object[] | string[] | string;
  lessons?: object | object[] | string[] | string;
  frameworks?: object | object[] | string[] | string;
  tools?: object | object[] | string[] | string;
}
```

Structured experience data. Priority 2.

### KnowledgeMemory

```typescript
interface KnowledgeMemory {
  [key: string]: string | number | boolean | object | object[];
}
```

Flexible key-value storage for general knowledge. Lowest priority (1).

## Memory Priority Configuration

The priority system is configurable via `MEMORY_PRIORITY`:

```typescript
import { MEMORY_PRIORITY } from '@x12i/rendrix';

console.log(MEMORY_PRIORITY);
// {
//   shortTermMemory: 4,    // Strongest - highest priority
//   workingMemory: 3,
//   experienceMemory: 2,
//   knowledgeMemory: 1     // Weakest - lowest priority
// }
```

**Priority Order (Strongest to Weakest):**
1. `shortTermMemory` (4) - Takes precedence over all others
2. `workingMemory` (3)
3. `experienceMemory` (2)
4. `knowledgeMemory` (1) - Gets overwritten by others

## Common Patterns

### 1. Direct Variable Access

```handlebars
{{#if requireCitations}}
- Include citations
{{/if}}

{{#if includeReasoning}}
- Provide reasoning
{{/if}}
```

### 2. Priority-Based Resolution

```typescript
// Template: {{userName}}
const shortTermMemory = { userName: 'Alice' };  // Wins (Priority 4)
const workingMemory = { userName: 'Bob' };     // Ignored
const knowledgeMemory = { userName: 'Charlie' }; // Ignored

// Result: "Alice"
```

### 3. JSON Template Enrichment

```handlebars
{
  "confidence": 0.85{{#if requireCitations}},
  "citations": ["Citation 1", "Citation 2"]
  {{/if}}{{#if includeReasoning}},
  "reasoning": "Detailed explanation..."
  {{/if}}
}
```

### 4. Structured Data Access

```handlebars
Organization: {{organization.name}}
Entity: {{entity.name}}
Job: {{job.objective}}
Task: {{task.objective}}
```

## Examples

### Basic Rendering

```typescript
import { render } from '@x12i/rendrix';

const template = 'Hello {{name}}!';
const workingMemory = {
  job: {},
  task: {}
};
const shortTermMemory = { name: 'World' };

const output = render(template, workingMemory, shortTermMemory);
// Output: "Hello World!"
```

### JSON Enrichment

```typescript
import { enrichJsonTemplate } from '@x12i/rendrix';

const jsonTemplate = `{
  "question": "{{question}}",
  "confidence": 0.85{{#if requireCitations}},
  "citations": ["Citation 1"]
  {{/if}}
}`;

const workingMemory = {
  job: {},
  task: {},
  requireCitations: true
};

const shortTermMemory = {
  question: 'What is AI?'
};

const enriched = enrichJsonTemplate(
  jsonTemplate,
  workingMemory,
  shortTermMemory
);
```

### Template Verification and Resolution Reporting

```typescript
import { render, verifyTemplate, analyzeTemplateResolution } from '@x12i/rendrix';

const template = 'Hello {{name}}!';
const workingMemory = { job: {}, task: {} };
const shortTermMemory = { name: 'World' };

// Which tokens are missing in memory? (template + memory)
const resolution = analyzeTemplateResolution(template, workingMemory, shortTermMemory);
if (resolution.unparsed.length > 0) {
  console.warn('Unparsed paths:', resolution.unparsed);
}

const output = render(template, workingMemory, shortTermMemory);

// Any {{...}} left in the output?
const verification = verifyTemplate(output);
if (!verification.valid) {
  console.error('Unresolved tokens:', verification.unresolvedTokens);
}
```

## Best Practices

1. **Use direct paths on the merged root**: Same logical key from **`shortTermMemory`** overrides **`workingMemory`** and lower layers; put per-request overrides (e.g. gateway **`templateTokens`**) in short-term, merged last on the gateway side when needed.
2. **Structured Data**: Use `WorkingMemory` for structured job/task/entity data
3. **Temporary Data**: Use `ShortTermMemory` for session-specific or temporary variables
4. **Experience Data**: Use `ExperienceMemory` for learned knowledge, frameworks, tools
5. **General Knowledge**: Use `KnowledgeMemory` for general knowledge base
6. **Type Safety**: Always provide required fields (`job` and `task` in `WorkingMemory`)
7. **Verify Templates**: Check for unresolved tokens in production

## Contributing

1. Fork the repository
2. Create a feature branch
3. Add tests for new functionality
4. Ensure all tests pass
5. Submit a pull request

## License

MIT License - see LICENSE file for details.

## Integrator documentation (`.docs`)

- **[Gateway ↔ parser alignment](.docs/gateway-parser-alignment.md)** — merged template root, strict memory priority (including `templateTokens` via short-term), `subPathSearch` defaults (`execution` / `input` / `inputs`), and responsibility split with **ai-gateway**.

## Functionality Documentation

For detailed information about how the parser handles object manipulation, context merging, and memory resolution, see:

- **[Object Manipulation](functionality/object-manipulation.md)** - Comprehensive guide to how the parser manipulates and merges memory objects before rendering
- **[Template Rendering](functionality/template-rendering.md)** - Complete documentation of the `render()` function, including all parameters (required and optional), processing flow, and usage examples
- **[Template Resolution Reporting](functionality/template-resolution-report.md)** - `analyzeTemplateResolution` (parsed vs unparsed from template+memory) and `verifyTemplate` (leftover `{{}}` in output); when to use each
- **[File Content Inlining](functionality/files-content.md)** - `{{file:<filename>}}` tokens and the `filesContent` option in `render`, `partial`, `pipeline`, `enrichJsonTemplate`, `renderChat`, and `analyzeTemplateResolution`
- **[JSON Enrichment Improvements](functionality/json-enrichment-improvements.md)** - Enhanced JSON validation, JSONC support, and error handling for `enrichJsonTemplate()`

## Support

For questions, issues, or contributions, please open an issue on the repository.

---
_Source: https://npm.io/package/@x12i/rendrix · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
