# CrystFlux

![Python Version](https://img.shields.io/badge/python-3.11%2B-blue)
![License](https://img.shields.io/badge/license-MIT-green)
![PyPI Version](https://img.shields.io/pypi/v/crystflux)
![Immutability](https://img.shields.io/badge/immutability-deep%20recursive-orange)
![JSON Access](https://img.shields.io/badge/JSON-type%20safe%20access-yellow)

CrystFluxは、JSONデータを不変オブジェクトへ変換し、メソッドチェーンによる型安全アクセサを提供するライブラリです。

## Related Documents

- [主要ファイルマップ](ESSENTIAL_FILES_MAP.md)
- [構造ガイド](src/crystflux/v1/STRUCTURE_ja.md)
- [コンセプト](docs/01-concepts/README.md)

## Table of Contents

- [Introduction](#introduction)
- [Installation](#installation)
- [Getting Started](#getting-started)
- [Usage Example](#usage-example)
  - [Filtering & Mapping](#filtering--mapping)
  - [Handling Missing Structures](#handling-missing-structures)
- [as / expected](#as--expected)
- [Latent / Strict](#latent--strict)
- [Dream Mode](#dream-mode)
  - [Hallucination Tolerance](#hallucination-tolerance)
  - [Observed Logging](#observed-logging)
- [License](#license)

## Introduction

CrystFluxは、AIコーディングアシスタントがJSON関連の実装で苦戦する様子を見て、より良い手法がないか模索する過程で生まれたライブラリです。

AIは動的かつ再帰的なデータ構造を、堅牢に処理するコード生成は苦手とする傾向があります。
その結果、キーの欠損や型不整合による実行時エラー（`KeyError`, `IndexError`, `AttributeError`など）を誘発するコードや、
過度な防御的記述（頻繁な `isinstance` 確認、 `None` チェック、`cast`）により本来のロジックが埋没したコードが生成されがちです。

AIの推論コストを最小化し、より安定した実装を導くため、CrystFluxは以下の特徴を持つJSONアクセサを提供します。

-   **再帰的不変性 (Deep Immutability):** JSONデータを再帰的にイミュータブルな構造へ変換し、論理フローからの状態変更や副作用を排除します。
-   **宣言的メソッドチェーン (Fluent Interface):** `map` や `filter` を使用可能なメソッドチェーンにより、複雑なネストや条件分岐を排除した、直線的で構造化されたデータ抽出を実現します。
-   **型指定アクセサ:** `as_str()` / `expected_str()` などにより型を指定して値を取得できます。型不一致時、`as_*` は `None` を返し、`expected_*` は `TypeError` を送出します。
-   **マルチモード安全性 (Multi-Mode Safety):** 要件に応じて挙動を切り替えられます。

| モード | 説明 |
|---|---|
| **Strict** | スキーマ違反時に即座に例外を送出する厳格モード |
| **Latent (Safe)** | `KeyError`, `IndexError`を `None` (Void) として扱い、チェーンを継続させるセーフモード |
| **Dream** | ハルシネーションによる存在しない関数呼び出しさえも許容してチェーンを継続させ、観測・記録する研究用モード（デフォルトでは無効） |

---

## Installation

### pip

```bash
# Using pip
pip install crystflux
# インポートとバージョン確認
python -c "import crystflux.v1; print(crystflux.__version__)"
```

### uv

```bash
# Using uv
uv pip install crystflux
# インポートとバージョン確認
uv run python -c "import crystflux.v1; print(crystflux.__version__)"
```

詳細なセットアップ手順は以下のファイルを参照してください。

- [INSTALL.md](INSTALL.md)
- [INSTALL_ja.md](INSTALL_ja.md)

---

## Getting Started

インストール直後に実行して動作確認できる、シンプルな使用例です。

```bash
python -c "
from crystflux.v1 import Crystallizer;
val = Crystallizer.latent({'name': 'Alice', 'age': 30});
print(val.get('name').as_str());
print(val.get('age').as_int());
print(val.get('missing').as_str());
"
```
**結果:**

```bash
Alice
30
None
```

---

## Usage Example

欠損や型不整合を含む半構造化データを、`Latent (Safe)` モードで扱う実装例です。

### Target Data

以降の例で使用するサンプルデータを用意します。

```python
data = {
    "users": [
        {"id": 1, "active": True,  "profile": {"name": "Alice", "age": 30}},
        {"id": 2, "active": True,  "profile": {"name": "Anna",  "age": 17}},
        {"id": 3, "active": 0,     "profile": None},
        {},
        {"id": 5, "active": False, "profile": {"name": "Bob"}},
        {"id": 6, "active": True,  "profile": {"name": "Carol"}}
    ]
}

from crystflux.v1 import Crystallizer, it

# データのラップ（Latentモード）
val = Crystallizer.latent(data)
```

### Basic Extraction

配列要素へアクセスし、ネストされた値を抽出します。
途中のキー欠損や型不一致は `None` (Void) として扱われます。

```python
names = (
    val.get("users")
       .map(lambda u: u.get("profile").get("name"))
       .as_str_array()
)
# Result: ('Alice', 'Anna', 'Bob', 'Carol')
# (None values are excluded by .as_str_array())
```

### Filtering & Mapping

lambdaの代わりに `it` (Projection) を使用し、値の型変換と条件判定を宣言的に記述することもできます。プロパティアクセス（`.`）と `get()` は同等に扱われます。
型変換（`as_bool`, `as_int`）は、対象の値が適合しない場合に安全に `None` (False扱い) を返します。

```python
active_adults = (
    val.get("users")
       .filter(it.active.as_bool().is_true())  # activeがTrue(bool)のものだけ
       .filter(it.profile.age.as_int() >= 20)  # ageが20以上のものだけ
       .map(it.profile.name)                   # 名前を抽出
       .as_str_array()
)
# Result: ('Alice',)
```

### Handling Missing Structures

構造的な欠損（Void状態）を明示的に検出することも可能です。

**キーが存在しない場合を検出:**
```python
missing_key = (
    val.get("users")
       .filter(lambda u: u.get("profile").is_void())
)
# Result: Wraps object for id=4 (empty dict - no profile key)
```

**値がNoneもしくはキーがない場合を検出:**
```python
profile_is_none = (
    val.get("users")
       .filter(lambda u: u.get("profile").as_json_value() is None)
)
# Result: Wraps objects for id=3 (profile is None) and id=4 (empty dict)
```

**注意:** Latentモードでは `profile: None` は有効な値として扱われます（`is_void()`はFalse）。キーが存在しない場合のみVoidと判定されます。
## as / expected

`as_` 系メソッドと `expected_` 系メソッドの違いは、型不一致や欠損が発生した際の挙動にあります。
`as_` は `None` を返し、`expected_` は例外（`TypeError`）を送出します。

### Same Result (Type Matches) 

型が一致している場合、両者は同じ値を返します。

```python
from crystflux.v1 import Crystallizer

data = {
    "age": 30,
    "name": "Alice",
}

val = Crystallizer.latent(data)

val.get("age").as_int()
# -> 30
val.get("age").expected_int()
# -> 30

val.get("name").as_str()
# -> "Alice"
val.get("name").expected_str()
# -> "Alice"
```

### Different Result (Type Mismatch or Missing) 

型が異なる場合やキーが存在しない場合、挙動が分岐します。

```python
# Case 1: Type Mismatch (String is not Int)
val.get("name").as_int()
# -> None

val.get("name").expected_int()
# -> raises TypeError: Expected an integer, but got str

# Case 2: Missing Key
val.get("missing").as_str()
# -> None

val.get("missing").expected_str()
# -> raises TypeError: VOID has no string value
```

### Primitive Type Accessors

主なアクセサの紹介です。これ以外にも多数のアクセサがあります。詳細は以下を参照してください。

- [`value_api.py`](src/crystflux/v1/core/value_api.py) 
- [`value_core.py`](src/crystflux/v1/core/value_core.py) 

#### As Type Accessors

値が存在しない場合、もしくは型が一致しない場合、Noneを返す

| メソッド | 返り値の型 |
|---------|----------|
| `as_str()` | `str \| None` |
| `as_int()` | `int \| None` |
| `as_float()` | `float \| None` |
| `as_bool()` | `bool \| None` |

#### Expected Type Accessors

期待される型での値が存在しない場合はエラー

| メソッド | 返り値の型 |
|---------|----------|
| `expected_str()` | `str` |
| `expected_int()` | `int` |
| `expected_float()` | `float` |
| `expected_bool()` | `bool` |

---

## Latent / Strict

両モードは同じAPI (`ValueAPI`) を共有しますが、スキーマ違反（キー欠損、インデックス範囲外）発生時のランタイム挙動が異なります。

参考: [`value_modes.py`](src/crystflux/v1/core/value_modes.py) 

**注意**: ラップ時のモードを変更しても`as_*` / `expected_*` の実装自体は変わりません。挙動が変わる原因はあくまでスキーマ違反（キー欠損、インデックス範囲外）と、それを扱う内部実装によるものです。

### Same Code, Different Behavior

キーが欠損しているデータに対して、同じコードを `Latent` モードと `Strict` モードで実行した場合を考えます。

```python
from crystflux.v1 import Crystallizer
from crystflux.v1.core.value_api import ValueAPI

data = {"id": 1}  # 'name' is missing

def extract_name(val: ValueAPI) -> str | None:
    return val.get("name").as_str()
```

### Latent Mode

`Latent` モードは構造上のエラーを吸収し、Void状態を伝播させることで、結果として `None` を返します。

```python
val = Crystallizer.latent(data)

print(extract_name(val))
# -> None
```

### Strict Mode

`Strict` モードはスキーマを即座に強制し、失敗した箇所で例外を送出します。

```python
val = Crystallizer.strict(data)

print(extract_name(val))
# -> raises KeyError: 'name'
```

### Summary

| 特徴 | Latent モード | Strict モード |
| :--- | :--- | :--- |
| **キー/インデックス欠損** | `Void` を返す (評価結果は `None`) | `KeyError` / `IndexError` を送出 |
| **チェーン実行** | 継続する (失敗を吸収) | 即座に停止する (Fail-fast) |

---

## Dream Mode

AIエージェントの長時間自律運用を見据えると、ハルシネーションを含む未検証挙動を無視せず観測・記録する手段は運用上の必須条件と考えられます。Dream Modeは、その必要性に応える試行的実装の一つです。

研究用モードであり、デフォルトでは無効になっています。


**特徴**

- AIコーダーによって生成された、存在しないメソッド呼び出しを含むチェーンでも、処理を停止せず挙動を観測・記録する
- 存在しないメソッドが一度でも呼ばれた場合、処理は `VOID` チェーンに遷移する
- VOID チェーンでは最終的な値取得は `None` となる

**[NOTE]**
- ここでは、ハルシネーションを単なる誤りとして捨てず、「AIが現行世界線よりも確からしいと推論した別の世界線」として観測対象化します。フィードバックループを適切に設計できれば、ハルシネーション駆動型開発への道が開けます。

**[WARNING]**
- 本ライブラリの有無に関わらず、自己参照的・自己改変的な構造（ウロボロス型）を持つAIエージェントは、観測が自身の振る舞いに影響を与える再帰的自己進化型ループを形成し、予期せぬ挙動を引き起こすリスクがあります。実験は必ず隔離された環境で行い、影響範囲の限定・監査ログの保全・フェイルセーフの設置等を行った上で、利用者自身の管理責任のもとで実施してください。本ライブラリはMITライセンスの下で提供され、いかなる責任も負いません。

### Opt-in Required

Dreamモードを使用するには、明示的に有効化する必要があります。

```python
from crystflux.v1 import Crystallizer
from crystflux.v1.adapters import enable_dream_mode

with enable_dream_mode():
    val = Crystallizer.dream({"name": "Alice"})
```

### Hallucination Tolerance

存在しないメソッドを呼び出しても例外が送出されず、 DreamValue void を返します。

参考:
- [`value_modes.py`](src/crystflux/v1/core/value_modes.py) 

```python
val.some_nonexistent_method()
# -> DreamValue void (no exception)

# チェーン継続
val.some_method().another_method()
# -> DreamValue void (チェーンは維持)
```

### Observed Logging

Dreamモードでは、ハルシネーションによる関数呼び出しと、構造的欠損の両方を観測・記録が可能です。

#### Undefined Method Calls (Desire Logger)

存在しないメソッド呼び出しは"デサイア・パス"として記録されます。

```python
from crystflux.v1.adapters import StdoutDesireLogger

logger = StdoutDesireLogger()
val = Crystallizer.dream({"name": "Alice"}, desire_logger=logger)

val.some_nonexistent_method()
# Output: [Desire] method=some_nonexistent_method target=mappingproxy repr=mappingproxy({'name': 'Alice'})
```

メソッドチェーンも観測されます。
```python
val.method_a().method_b()
# Output: 
# [Desire] method=method_a target=mappingproxy ...
# [Desire] method=method_b target=void ...
```

#### Structural Missing (Missing Logger)

存在しないキーやインデックスへのアクセスは、`MissingLogger` で観測できます。

```python
from crystflux.v1.adapters import StdoutMissingLogger

logger = StdoutMissingLogger()
val = Crystallizer.dream({"name": "Alice"}, missing_logger=logger)

val.get("profile").get("age")
# Output: [Missing] reason=VoidReason.MISSING_KEY key='profile' target=mappingproxy repr=mappingproxy({'name': 'Alice'})
```

`DesireLogger` プロトコルと `MissingLogger` プロトコルを実装することで、独自のロガーを作成できます。

**[IMPORTANT]**
- 現在の実装では、`val.profile.age`のようなプロパティチェーン記法は`AttributeError`になります

### Summary

| 特徴 | Dream モード |
| :--- | :--- |
| **ハルシネーション許容** | 存在しないメソッド呼び出しもvoidを返す |
| **チェーン実行** | 継続する |
| **オプトイン** | `enable_dream_mode()` で明示的有効化 |
| **観測・記録** | `DesireLogger` でデサイア・パスを追跡 |

---

## License

MIT License - 詳細は [LICENSE](LICENSE) を参照してください。
