> ## Documentation Index
> Fetch the complete documentation index at: https://docs.trytrellis.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Dynamic Spec Loading

> Load the project rules that govern a file at the moment an agent reads or changes it.

Trellis can attach specs to code paths and deliver the matching rules when an
agent touches a file. This keeps the prompt small while putting the relevant
rules close to the edit they govern.

## Declare which paths a spec governs

Add a `paths` list to the spec's YAML frontmatter. Paths are relative to the
repository root.

```md theme={null}
---
name: commands-workflow
description: Workflow command and resolver contracts
paths:
  - packages/cli/src/commands/workflow.ts
  - packages/cli/src/utils/workflow-resolver.ts
  - packages/cli/test/commands/workflow*.test.ts
---

# Workflow command rules

...
```

The matcher supports:

* `*` within one path segment
* `**` across path segments
* `?` for one character
* a trailing `/` as shorthand for everything below that directory

Specs without `paths` frontmatter keep their existing behavior. They are not
loaded automatically.

## What the agent receives

For each matching spec, Trellis chooses one of three deliveries:

| Situation                                | Delivery                                      |
| ---------------------------------------- | --------------------------------------------- |
| First matching touch in a session        | Full, budgeted spec body                      |
| Unchanged spec inside the refresh window | No repeated output                            |
| Unchanged spec after the refresh window  | Short ticket with the spec path and read hint |
| Spec content changed                     | Full body again                               |
| Session was cleared or compacted         | Full body again on the next matching touch    |

The refresh window is fixed. Silent touches do not extend it, so continuous
editing still receives a later reminder.

When several specs match one file, narrower path patterns are considered
before broad patterns. If the event budget cannot hold every full body,
remaining matches become an index of spec paths instead of disappearing.

## Platform behavior

| Platform        | Trigger                                                    | Behavior                                                                                                                                    |
| --------------- | ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| Claude Code     | `PostToolUse` for `Read`, `Edit`, `Write`, and `MultiEdit` | Delivers matching context after the file operation. Claude Code's read-before-write behavior usually loads the rules before the later edit. |
| Codex           | `PreToolUse` for native `apply_patch`                      | Parses every add, update, delete, and move header before the patch runs.                                                                    |
| Other platforms | Pull mode                                                  | Query matching spec paths explicitly with `get_context.py`.                                                                                 |

### Why Codex blocks the first patch once

Codex does not require a file read before an edit. When a patch first matches a
spec, the hook returns the full rules and denies that patch once:

```text theme={null}
patch requested
  → Trellis injects governing specs
  → patch is denied
  → Codex reads the specs and retries
  → retry proceeds
```

Only a newly delivered **full** spec blocks the patch. A short refresh ticket
does not block, and an unchanged spec inside the refresh window produces no
output. This is a context-delivery handshake, not a policy rejection.

## Configure the budget and refresh window

The defaults fit the host context limits and require no configuration:

```yaml theme={null}
spec_injection:
  enabled: true
  max_spec_chars: 9400
  max_total_chars: 9500
  refresh_window_seconds: 2700
  tools: [Read, Edit, Write, MultiEdit]
```

| Key                      | Meaning                                                                                                                                      |
| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------- |
| `enabled`                | Set to `false` to disable dynamic spec loading.                                                                                              |
| `max_spec_chars`         | Maximum characters from one spec. `0` removes this limit.                                                                                    |
| `max_total_chars`        | Maximum characters for one hook event. `0` removes this limit.                                                                               |
| `refresh_window_seconds` | Seconds before an unchanged spec gets a short ticket. `0` disables time-based refresh.                                                       |
| `tools`                  | Claude Code tool names that can trigger matching. Codex `apply_patch` uses the logical `Edit` trigger. An empty list disables every trigger. |

Truncated bodies include a notice with the full spec path so the agent can read
the source file directly.

## Inspect matches manually

Use pull mode to check which specs govern a path without loading their bodies:

```bash theme={null}
# macOS and Linux
python3 ./.trellis/scripts/get_context.py --mode spec \
  --file packages/cli/src/commands/workflow.ts

# Windows
python ./.trellis/scripts/get_context.py --mode spec \
  --file packages/cli/src/commands/workflow.ts
```

Add `--json` for structured output:

```bash theme={null}
python3 ./.trellis/scripts/get_context.py --mode spec \
  --file packages/cli/src/commands/workflow.ts \
  --json
```

An empty match is valid and returns no governing specs.

## State, reset, and failure behavior

Delivery state is stored outside the repository under
`~/.trellis/spec-inject/`. Parent and sub-agent histories are separate.
`SessionStart(source=clear|compact)` records a reset marker shared by that
session, so rules removed by compaction are delivered again.

The hook does not parse Claude Code or Codex transcript contents. Transcript
formats are host internals and are not part of this contract.

Matching and state failures are fail-open: malformed frontmatter, missing
paths, unreadable state, or an internal hook error must not break the host tool
call. The only intentional block is the first Codex patch that has just
received a full governing spec.

## Related pages

* [Dynamic Workflow Switching](/beta/advanced/dynamic-workflow-switching)
* [Custom Hooks](/beta/advanced/custom-hooks)
* [Architecture](/beta/advanced/architecture)
