# `Sagents.FileSystem.FileEntry`

Represents a file or directory in the virtual filesystem.

## Field Semantics

- `:entry_type` - Whether this is a file or directory
  - `:file` - A regular file with content
  - `:directory` - A directory entry (no content, never dirty)

- `:title` - Human-readable display name (e.g. "Hero Character Sheet")
  Promoted to a first-class field so both application code and LLM tools
  can reference entries by their user-visible name.

- `:persistence` - Storage strategy
  - `:memory` - Ephemeral, exists only in ETS (e.g., /scratch files)
  - `:persisted` - Durable, backed by storage (e.g., /Memories files)

- `:loaded` - Content availability
  - `true` - Content is in ETS, ready to use
  - `false` - Content exists in storage but not loaded (lazy load on read)

- `:dirty_content` - Content sync status (only meaningful when persistence: :persisted)
  - `false` - In-memory content matches storage
  - `true` - In-memory content differs from storage (needs persist)

- `:dirty_non_content` - Non-content change tracking
  - `false` - Entry-level fields and metadata match storage
  - `true` - Only non-content fields changed (enables metadata-only persist optimization)

## State Examples

Memory file (always loaded, never dirty):
  %FileEntry{entry_type: :file, persistence: :memory, loaded: true, dirty_content: false, content: "data"}

Persisted file, clean, loaded:
  %FileEntry{entry_type: :file, persistence: :persisted, loaded: true, dirty_content: false, content: "data"}

Persisted file, not yet loaded (lazy):
  %FileEntry{entry_type: :file, persistence: :persisted, loaded: false, dirty_content: false, content: nil}

Persisted file, modified since last save:
  %FileEntry{entry_type: :file, persistence: :persisted, loaded: true, dirty_content: true, content: "new data"}

Directory entry:
  %FileEntry{entry_type: :directory, persistence: :persisted, loaded: true, dirty_content: false, content: nil}

# `t`

```elixir
@type t() :: %Sagents.FileSystem.FileEntry{
  content: String.t() | nil,
  dirty_content: boolean(),
  dirty_non_content: boolean(),
  entry_type: :file | :directory,
  file_type: String.t() | nil,
  id: String.t() | nil,
  loaded: boolean(),
  metadata: Sagents.FileSystem.FileMetadata.t() | nil,
  path: String.t(),
  persistence: :memory | :persisted,
  title: String.t() | nil
}
```

# `changeset`

Creates a changeset for a file entry.

# `directory?`

```elixir
@spec directory?(t()) :: boolean()
```

Returns true if the entry is a directory.

# `mark_clean`

Marks a persisted file as clean (synced with storage).

# `mark_loaded`

Marks a file as loaded with the given content.

Preserves existing metadata if present, updating only content-related fields.

# `new_directory`

Creates a new directory entry.

Directories have no content, are always "loaded", and are never dirty on creation.

## Options

- `:title` - Human-readable name for the directory
- `:custom` - Custom metadata map
- `:persistence` - `:memory` or `:persisted` (default: `:persisted`)

# `new_indexed_file`

Creates a file entry for an indexed persisted file (not yet loaded).

Accepts optional keyword opts to set `:title`, `:entry_type`, and `:metadata`.

# `new_memory_file`

Creates a new file entry for memory storage.

# `new_persisted_file`

Creates a new file entry for persisted storage. Intended for situations when
the LLM instructs a new file to be created that will need to be persisted to
storage.

# `update_content`

Updates file content and marks as dirty if persisted.

Preserves existing metadata (custom, created_at, mime_type, etc.) and only
updates content-related fields (size, modified_at, checksum).

Returns `{:error, :directory_has_no_content}` if called on a directory entry.

# `update_entry_changeset`

Changeset for updating entry-level fields (not content or internal state).

Only allows `:title`, `:id`, and `:file_type` to be changed.

# `valid_name?`

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

Validates that a name (title or path segment) is safe for use in paths.

The library enforces minimal restrictions — only characters that would break
path resolution are disallowed:
- `/` (path separator)
- null bytes
- leading/trailing whitespace
- empty strings

---

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