# PROMPT AGENT: REFACTORING COMPLET - Alignement avec Enhanced Layer 1

> **ARCHIVED (February 2026):** This document describes forge-specific trace refactoring.
> - Forge is private tooling: `forge/forge.py trace` (was: `trace_cli.py`)
> - NeuroBrix package is separate: `pip install neurobrix` (src/neurobrix/)
> - All paths updated to reflect forge/ extraction and package restructure
> - See `docs/TRACE_SEPARATION.md` for trace system architecture

## SYSTEM CONTEXT (February 2026)

**This document describes forge-specific trace refactoring** (private tooling, separate from neurobrix package)

**Architecture Overview:**
```
PUBLIC (pip install neurobrix):
  src/neurobrix/          ← Package source
    ├── cli.py            ← neurobrix CLI (run, info, import, list, remove)
    ├── core/             ← Runtime engine (from neurobrix.core.*)
    ├── kernels/          ← Triton kernels (from neurobrix.kernels.*)
    └── nbx/              ← NBX format (from neurobrix.nbx.*)

PRIVATE (forge/):
  forge/                  ← Forge tooling (snap, build, trace, publish)
    ├── forge.py          ← Forge CLI (requires pip install -e .)
    ├── importer/         ← NBX builder (imports neurobrix.nbx.*)
    └── tracer/           ← Graph tracing (from tracer.*)
        ├── stimulus/     ← Input generation (THIS REFACTORING)
        └── worker.py     ← Trace worker subprocess
```

**Key points:**
- **forge/** is PRIVATE, separate from neurobrix package
- forge/ imports: `from tracer.*` (internal) and `from neurobrix.nbx.*` (public package)
- neurobrix package imports: `from neurobrix.core.*`
- ZERO coupling between forge/ and neurobrix.core/
- Communication via files only (.nbx, .cache/graphs/)

## RÔLE

Tu es ingénieur expert en **refactoring systémique** de code Python pour systèmes d'inférence deep learning.

Tu comprends:
- Les principes NeuroBrix (ZERO HARDCODE, VARIABLE EMBARQUÉE, ZERO FALLBACK, UNIVERSALITÉ)
- L'architecture Enhanced Layer 1 avec EnhancedInputInfo
- Le flow complet: Enhanced Layer 1 → generate_inputs → dry_run → blind_solver → kwargs_solver
- Python type hints, dataclasses, et passage de contexte
- **Forge/neurobrix separation** (forge = private tooling, neurobrix = pip package)

---

## PROBLÈME

**Objectif**: Aligner TOUT le flow de trace avec Enhanced Layer 1.

**État actuel**: Enhanced Layer 1 fonctionne parfaitement et génère EnhancedInputInfo avec metadata riche:
- shape (structural), dtype (type_hint), type_hint, description (docstring)
- required (signature), validations (assertions)
- activates_modules, required_if (conditions)

**MAIS**: Cette metadata riche est PERDUE en aval. Les layers suivants:
- blind_solver: Génère des shapes aléatoires au lieu d'utiliser enhanced_info.shape
- kwargs_solver: Pattern matching hardcodé au lieu d'utiliser enhanced_info.type_hint
- generator: Defaults hardcodés au lieu d'utiliser enhanced_info
- tracer: Pattern matching sur component_name au lieu d'utiliser enhanced_info

**Résultat**: PixArt/Flux échouent car blind_solver/kwargs_solver font du "poker" au lieu d'utiliser les VALEURS SÛRES de Enhanced Layer 1.

---

## FICHIERS À REFACTORER

**NOTE**: File paths updated 2026-02-08 to reflect forge/ extraction

**This is FORGE-SPECIFIC refactoring** (private tooling, separate from neurobrix package)

**Priorité 1** (Bloquants):
1. forge/tracer/stimulus/blind_solver.py (~40KB)
2. forge/tracer/stimulus/input_resolver.py (formerly kwargs_solver)
3. forge/tracer/stimulus/structure_inference.py (~53KB) - Section generate_inputs()

**Priorité 2** (Nettoyage):
4. forge/tracer/stimulus/generator.py (~13KB)
5. forge/tracer/worker.py (~1KB) - Trace worker subprocess

---

## PRINCIPE UNIVERSEL

Tous les refactorings suivent ce pattern:

AVANT (❌ Hardcode/Supposition):
```python
def solve(model, config):
    # ❌ Pattern matching hardcodé
    if arg_name == "timestep":
        shape = [batch_size]
        dtype = torch.long
    # ❌ Default hardcodé
    hidden_size = config.get("hidden_size", 768)
```

APRÈS (✅ Variable Embarquée):
```python
def solve(model, config, enhanced_info: Dict[str, EnhancedInputInfo]):
    info = enhanced_info[arg_name]
    # ✅ Valeurs depuis Enhanced Layer 1
    shape = info.shape      # Source sûre
    dtype = info.dtype      # Source sûre
    type_hint = info.type_hint  # Source sûre
```

---

## REFACTORING DÉTAILLÉ

### 1. forge/tracer/stimulus/blind_solver.py

#### Change 1.1: Signature de blind_solve_inputs()

AVANT:
```python
def blind_solve_inputs(
    model: nn.Module,
    config: Dict[str, Any],
    device: str = "cpu",
    batch_size: int = 1,
    max_iterations: int = 100,
    initial_inputs: Optional[Dict[str, torch.Tensor]] = None,
) -> Optional[Dict[str, torch.Tensor]]:
```

APRÈS:
```python
def blind_solve_inputs(
    model: nn.Module,
    config: Dict[str, Any],
    device: str = "cpu",
    batch_size: int = 1,
    max_iterations: int = 100,
    initial_inputs: Optional[Dict[str, torch.Tensor]] = None,
    enhanced_info: Optional[Dict[str, EnhancedInputInfo]] = None,  # ← AJOUTÉ
) -> Optional[Dict[str, torch.Tensor]]:
    """
    LAYER 2.5: Complete missing inputs.
    
    NEW: Uses enhanced_info from Layer 1 as source of truth.
    - Shapes from enhanced_info.shape (no poker)
    - Dtypes from enhanced_info.dtype (no supposition)
    - Dict detection from enhanced_info.type_hint
    """
```

#### Change 1.2: infer_initial_shape() → get_shape_from_enhanced_info()

AVANT:
```python
def infer_initial_shape(
    param_name: str,
    config: Dict[str, Any],
    int_pool: List[int],
    batch_size: int
) -> Tuple[List[int], torch.dtype]:
    # ❌ POKER
    small = int_pool[0] if len(int_pool) > 0 else 1
    medium = int_pool[len(int_pool)//2] if len(int_pool) > 1 else small
    return [batch_size, small, medium], torch.float32
```

APRÈS:
```python
def get_shape_from_enhanced_info(
    param_name: str,
    enhanced_info: Dict[str, EnhancedInputInfo],
    config: Dict[str, Any],
    int_pool: List[int],
    batch_size: int
) -> Tuple[List[int], torch.dtype]:
    """
    Get shape from Enhanced Layer 1 metadata.
    
    Priority:
    1. enhanced_info[param_name].shape ← PRIORITY (source sûre)
    2. int_pool (fallback si enhanced_info absent)
    
    ZERO HARDCODE: No more [batch, small, medium] poker.
    """
    if enhanced_info and param_name in enhanced_info:
        info = enhanced_info[param_name]
        # ✅ Source sûre depuis Enhanced Layer 1
        shape = info.shape if info.shape else [batch_size, 1]
        dtype = info.dtype if info.dtype else torch.float32
        return shape, dtype
    
    # Fallback to int_pool (si enhanced_info pas dispo)
    if not int_pool:
        logger.warning(
            f"No enhanced_info and no int_pool for '{param_name}'.\n"
            f"Using minimal shape [batch_size, 1]"
        )
        return [batch_size, 1], torch.float32
    
    small = int_pool[0]
    medium = int_pool[len(int_pool)//2] if len(int_pool) > 1 else small
    return [batch_size, small, medium], torch.float32
```

#### Change 1.3: Détecter Dict[str, Tensor] depuis type_hint

AVANT:
```python
# ❌ MANQUANT - Ne gère pas Dict
if arg not in inputs:
    shape, _ = infer_initial_shape(arg, config, int_pool, batch_size)
    inputs[arg] = torch.randn(shape, device=device)
```

APRÈS:
```python
if arg not in inputs:
    # ✅ Vérifier type_hint pour Dict
    info = enhanced_info.get(arg) if enhanced_info else None
    
    if info and info.type_hint and "Dict" in info.type_hint:
        # Build dict de tensors
        inputs[arg] = build_dict_tensor_from_enhanced_info(info, config, device)
        logger.debug(f"    Added dict: {arg}")
    else:
        # Tensor normal
        shape, dtype = get_shape_from_enhanced_info(
            arg, enhanced_info, config, int_pool, batch_size
        )
        if dtype == torch.long:
            inputs[arg] = torch.zeros(shape, dtype=dtype, device=device)
        else:
            inputs[arg] = torch.randn(shape, dtype=dtype, device=device)
        logger.debug(f"    Added tensor: {arg} = {shape}")
```

#### Change 1.4: Nouvelle fonction build_dict_tensor_from_enhanced_info()

NOUVEAU:
```python
def build_dict_tensor_from_enhanced_info(
    info: EnhancedInputInfo,
    config: Dict[str, Any],
    device: str,
) -> Dict[str, torch.Tensor]:
    """
    Build dict of tensors from Enhanced Layer 1 metadata.
    
    For params like: added_cond_kwargs: Dict[str, torch.Tensor]
    
    Strategy:
    1. Parse expected keys from description or config
    2. Infer shape for each key
    3. Build dict
    
    Example:
        added_cond_kwargs = {
            "resolution": torch.tensor([1024]),
            "aspect_ratio": torch.tensor([1.0]),
        }
    """
    # Strategy 1: Parse keys from description
    # "Dictionary with keys: 'resolution', 'aspect_ratio'"
    keys = []
    if info.description:
        # Pattern: 'key1', 'key2'
        import re
        matches = re.findall(r"'(\w+)'", info.description)
        keys.extend(matches)
    
    # Strategy 2: Look for related config keys
    if not keys:
        # Try to infer from param name
        # added_cond_kwargs → look for cond_*, added_*, etc.
        base_name = info.arg_name.replace("_kwargs", "").replace("added_", "")
        for key in config:
            if base_name in key or key in base_name:
                keys.append(key)
    
    # Strategy 3: Minimal fallback
    if not keys:
        logger.warning(
            f"Cannot determine dict keys for '{info.arg_name}'.\n"
            f"Description: {info.description}\n"
            f"Returning empty dict"
        )
        return {}
    
    # Build dict
    result = {}
    for key in keys:
        # Infer shape for this key
        # Most dict values are scalars or small vectors
        result[key] = torch.tensor([1.0], device=device)
    
    logger.debug(f"Built dict for '{info.arg_name}': keys={list(result.keys())}")
    return result
```

#### Change 1.5: Mettre à jour tous les appels

Remplacer toutes occurrences de:
```python
infer_initial_shape(param, config, int_pool, batch_size)
```

Par:
```python
get_shape_from_enhanced_info(param, enhanced_info, config, int_pool, batch_size)
```

---

### 2. forge/tracer/stimulus/input_resolver.py (formerly kwargs_solver.py)

#### Change 2.1: Signature de solve_missing_kwargs()

AVANT:
```python
def solve_missing_kwargs(
    model: nn.Module,
    inputs: Dict[str, Any],
    config: Dict[str, Any],
    device: torch.device,
    model_dtype: torch.dtype = torch.float32,
    max_retries: int = 3,
) -> Optional[Dict[str, Any]]:
```

APRÈS:
```python
def solve_missing_kwargs(
    model: nn.Module,
    inputs: Dict[str, Any],
    config: Dict[str, Any],
    device: torch.device,
    model_dtype: torch.dtype = torch.float32,
    max_retries: int = 3,
    enhanced_info: Optional[Dict[str, EnhancedInputInfo]] = None,  # ← AJOUTÉ
) -> Optional[Dict[str, Any]]:
    """
    Solve missing kwargs via structural introspection.
    
    NEW: Uses enhanced_info for:
    - Shape/dtype from enhanced_info (no pattern matching)
    - activates_modules to decide what to include
    - required_if to check config conditions
    """
```

#### Change 2.2: Remplacer pattern matching hardcodé

AVANT:
```python
# ❌ HARDCODE - Pattern matching
if 'timestep' in name_lower or 'time' in name_lower:
    solved_inputs[arg] = torch.zeros([1], dtype=torch.long, device=device)
elif 'encoder' in name_lower and 'hidden' in name_lower:
    cross_dim = resolve_cross_attention_dim(config, model)
    seq_len = resolve_seq_length(config, None, model)
    solved_inputs[arg] = torch.randn([1, seq_len, cross_dim], ...)
```

APRÈS:
```python
# ✅ Utilise enhanced_info
if enhanced_info and arg in enhanced_info:
    info = enhanced_info[arg]
    
    # Vérifier si requis selon conditions
    if info.required_if:
        condition_met = eval_config_condition(info.required_if, config)
        if not condition_met:
            logger.debug(f"Skipping {arg}: condition not met ({info.required_if})")
            continue
    
    # Créer tensor avec valeurs Enhanced Layer 1
    if "Dict" in info.type_hint:
        solved_inputs[arg] = build_dict_tensor_from_enhanced_info(info, config, device)
    else:
        solved_inputs[arg] = create_tensor_from_enhanced_info(info, device, model_dtype)
    
    logger.info(f"kwargs_solver: added {arg} (from enhanced_info)")
else:
    # Fallback to structural introspection (old method)
    logger.debug(f"No enhanced_info for {arg}, using structural introspection")
    # ... existing code ...
```

#### Change 2.3: Nouvelle fonction eval_config_condition()

NOUVEAU:
```python
def eval_config_condition(condition: str, config: Dict[str, Any]) -> bool:
    """
    Evaluate config condition from required_if.
    
    Example:
        condition = "config.guidance_embeds == true"
        config = {"guidance_embeds": True}
        → Returns True
    
    Supports:
    - config.key == value
    - config.key != value
    - config.key (exists check)
    """
    # Remove "config." prefix
    cond = condition.replace("config.", "")
    
    # Parse: key == value
    if "==" in cond:
        key, value = cond.split("==")
        key = key.strip()
        value = value.strip().lower()
        
        config_value = config.get(key)
        if config_value is None:
            return False
        
        # Compare (handle bool)
        if value == "true":
            return bool(config_value)
        elif value == "false":
            return not bool(config_value)
        else:
            return str(config_value).lower() == value
    
    # Parse: key != value
    elif "!=" in cond:
        key, value = cond.split("!=")
        key = key.strip()
        value = value.strip().lower()
        
        config_value = config.get(key)
        if config_value is None:
            return True  # Missing != value → True
        
        if value == "true":
            return not bool(config_value)
        elif value == "false":
            return bool(config_value)
        else:
            return str(config_value).lower() != value
    
    # Simple existence check
    else:
        key = cond.strip()
        return key in config and config[key] is not None
```

#### Change 2.4: Nouvelle fonction create_tensor_from_enhanced_info()

NOUVEAU:
```python
def create_tensor_from_enhanced_info(
    info: EnhancedInputInfo,
    device: torch.device,
    model_dtype: torch.dtype,
) -> torch.Tensor:
    """
    Create tensor from Enhanced Layer 1 metadata.
    
    Uses:
    - info.shape
    - info.dtype (with model_dtype fallback)
    """
    shape = info.shape if info.shape else [1, 1]
    dtype = info.dtype if info.dtype else model_dtype
    
    if dtype in (torch.long, torch.int, torch.int64):
        return torch.zeros(shape, dtype=dtype, device=device)
    else:
        return torch.randn(shape, dtype=dtype, device=device)
```

---

### 3. forge/tracer/stimulus/structure_inference.py

#### Change 3.1: generate_inputs() passe enhanced_info

AVANT:
```python
# Layer 1: Enhanced structural + code parsing
enhanced_info = enhanced_structural_inference(model, merged, batch_size)

# Layer 2.5: Blind solver
result = blind_solve_inputs(
    model, merged,
    device=str(device),
    batch_size=batch_size,
    initial_inputs=initial_inputs  # ← Seulement tensors
)
```

APRÈS:
```python
# Layer 1: Enhanced structural + code parsing
enhanced_info = enhanced_structural_inference(model, merged, batch_size)

# Layer 2.5: Blind solver
result = blind_solve_inputs(
    model, merged,
    device=str(device),
    batch_size=batch_size,
    initial_inputs=initial_inputs,
    enhanced_info=enhanced_info,  # ← AJOUTÉ - Source de vérité
)
```

#### Change 3.2: Passer enhanced_info à kwargs_solver

Dans le code qui appelle kwargs_solver:
```python
# Essayer kwargs_solver si blind_solve échoue
# NOTE: This is forge-specific code (tracer module)
from tracer.stimulus.input_resolver import solve_missing_kwargs

solved = solve_missing_kwargs(
    model, result,
    config=merged,
    device=device,
    model_dtype=model_dtype,
    enhanced_info=enhanced_info,  # ← AJOUTÉ
)
```

---

### 4. forge/tracer/stimulus/generator.py (Nettoyage)

#### Change 4.1: _infer_shape() prend enhanced_info

AVANT:
```python
def _infer_shape(self, arg_name: str, batch_size: int = 1) -> Tuple[List[int], torch.dtype]:
    # ❌ Pattern matching
    if arg_name in ("timestep", "t", "timesteps"):
        return [batch_size], torch.long
```

APRÈS:
```python
def _infer_shape(
    self,
    arg_name: str,
    batch_size: int = 1,
    enhanced_info: Optional[Dict[str, EnhancedInputInfo]] = None,
) -> Tuple[List[int], torch.dtype]:
    """
    Infer shape - UNIVERSAL with enhanced_info priority.
    
    Priority:
    1. enhanced_info[arg_name] ← PRIORITY
    2. Config-based inference (fallback)
    """
    # Priority 1: Enhanced Layer 1
    if enhanced_info and arg_name in enhanced_info:
        info = enhanced_info[arg_name]
        return info.shape, info.dtype
    
    # Priority 2: Config inference (old method)
    # ... existing code as fallback ...
```

#### Change 4.2: generate_from_introspection() utilise enhanced_structural_inference()

AVANT:
```python
def generate_from_introspection(self, batch_size: int = 1):
    sig = inspect.signature(self.model.forward)
    for name, param in sig.parameters.items():
        shape, dtype = self._infer_shape(name, batch_size)  # ❌
```

APRÈS:
```python
def generate_from_introspection(self, batch_size: int = 1):
    # ✅ Appeler Enhanced Layer 1
    # NOTE: This is forge-specific code (tracer module)
    from tracer.stimulus.structure_inference import enhanced_structural_inference
    
    enhanced_info = enhanced_structural_inference(
        self.model,
        self.combined_config,
        batch_size
    )
    
    inputs = {}
    for arg_name, info in enhanced_info.items():
        # ✅ Utiliser valeurs Enhanced Layer 1
        if info.dtype in (torch.long, torch.int):
            max_val = self.combined_config.get("num_train_timesteps", 1000)
            if arg_name == "input_ids":
                max_val = self.combined_config.get("vocab_size", 50000)
            inputs[arg_name] = torch.randint(0, max_val, info.shape, dtype=info.dtype)
        else:
            inputs[arg_name] = torch.randn(*info.shape, dtype=info.dtype)
    
    return inputs
```

#### Change 4.3: Supprimer defaults hardcodés

Remplacer:
```python
# ❌
seq_len = cfg.get("caption_projection_dim") or cfg.get("max_position_embeddings") or 120
hidden_size = config.get("hidden_size", config.get("d_model", 768))
```

Par:
```python
# ✅
seq_len = cfg.get("caption_projection_dim") or cfg.get("max_position_embeddings")
if seq_len is None:
    raise GeneratorError(
        f"Missing sequence length config.\n"
        f"Need 'caption_projection_dim' or 'max_position_embeddings'.\n"
        f"Available: {sorted(cfg.keys())}"
    )

hidden_size = config.get("hidden_size") or config.get("d_model")
if hidden_size is None:
    raise GeneratorError(
        f"Missing hidden_size config.\n"
        f"Available: {sorted(config.keys())}"
    )
```

---

### 5. forge/tracer/worker.py (Nettoyage)

#### Change 5.1: Supprimer defaults dans InputGenerator

AVANT:
```python
sample_size = 64  # ❌ Conservative default
in_channels = 4  # ❌ Latent channels
```

APRÈS:
```python
sample_size = getattr(config, 'sample_size', None)
if sample_size is None:
    raise TracerError(
        f"Missing 'sample_size' in config.\n"
        f"Available: {dir(config)}"
    )

in_channels = getattr(config, 'in_channels', None)
if in_channels is None:
    raise TracerError(
        f"Missing 'in_channels' in config.\n"
        f"Available: {dir(config)}"
    )
```

#### Change 5.2: _generate_component_inputs() utilise enhanced_structural_inference()

AVANT:
```python
def _generate_component_inputs(self, component_name: str, component: nn.Module):
    # ❌ Pattern matching sur component_name
    if component_name in ('text_encoder', 'text_encoder_2'):
        vocab_size = getattr(config, 'vocab_size', 49408)  # ❌ Default
```

APRÈS:
```python
def _generate_component_inputs(self, component_name: str, component: nn.Module):
    # ✅ Utiliser Enhanced Layer 1
    # NOTE: This is forge-specific code (tracer module)
    from tracer.stimulus.structure_inference import enhanced_structural_inference
    
    config_dict = component.config.to_dict() if hasattr(component, 'config') else {}
    
    enhanced_info = enhanced_structural_inference(
        component,
        config_dict,
        batch_size=1
    )
    
    inputs = {}
    for arg_name, info in enhanced_info.items():
        if info.dtype in (torch.long, torch.int):
            inputs[arg_name] = torch.zeros(info.shape, dtype=info.dtype, device=self.device)
        else:
            inputs[arg_name] = torch.randn(info.shape, dtype=info.dtype, device=self.device)
    
    return inputs
```

---

## TEST

**NOTE (February 2026):** Trace entry point moved to forge/

Après refactoring, tester sur les 3 cas:

### Test 1: T5 (Baseline - Doit rester PASS)
```bash
# Test via forge tracer (requires pip install -e . for neurobrix package)
ssh mlops@10.0.0.40 "cd NeuroBrix_System && python -m forge.tracer.stimulus.test_structure_inference"
```

Expected: PASS (aucune régression)

### Test 2: PixArt (Doit maintenant PASS)
```bash
# Trace via forge CLI (was: trace_cli.py)
ssh mlops@10.0.0.40 "cd NeuroBrix_System && python forge/forge.py trace --family image --model PixArt-alpha/PixArt-Sigma-XL-2-1024-MS --device cuda:3 --component transformer"
```

Expected:
- Layer 1: hidden_states [1, 4, 128, 128] ✅
- blind_solver: Utilise enhanced_info pour added_cond_kwargs (Dict) ✅
- Layer 2: PASS ✅

### Test 3: Flux (Doit maintenant PASS après fix find_entry_module)
```bash
# Trace via forge CLI
ssh mlops@10.0.0.40 "cd NeuroBrix_System && python forge/forge.py trace --family image --model black-forest-labs/FLUX.1-dev --device cuda:3 --component transformer"
```

Expected:
- Layer 1: hidden_states avec shape correcte ✅
- blind_solver: Utilise enhanced_info.shape ✅
- Layer 2: PASS ✅

---

## VÉRIFICATIONS CRITIQUES

### 1. Pas de Hardcode

```bash
# Ces patterns NE DOIVENT PLUS exister après refactoring:
# NOTE: Paths updated for forge/ structure
ssh mlops@10.0.0.40 "cd NeuroBrix_System && grep -rn 'if arg_name in (' forge/tracer/stimulus/blind_solver.py forge/tracer/stimulus/input_resolver.py"

# Expected: Aucun résultat
```

### 2. enhanced_info partout

```bash
# Vérifier que enhanced_info est passé:
ssh mlops@10.0.0.40 "cd NeuroBrix_System && grep -n 'enhanced_info:' forge/tracer/stimulus/blind_solver.py forge/tracer/stimulus/input_resolver.py"

# Expected: Plusieurs occurrences dans signatures
```

### 3. Pas de defaults hardcodés

```bash
# Ces patterns NE DOIVENT PLUS exister:
ssh mlops@10.0.0.40 "cd NeuroBrix_System && grep -n 'or 120\|or 768\|or 64\|= 64  #\|= 4  #' forge/tracer/"

# Expected: Aucun résultat
```

---

## NOTES CRITIQUES

### ⚠️ NE PAS FAIRE

1. **Ne pas casser l'API existante**
   - Ajouter enhanced_info comme param OPTIONNEL
   - Garder fallback sur ancienne méthode si enhanced_info=None

2. **Ne pas supposer enhanced_info toujours présent**
   ```python
   # ✅ CORRECT
   if enhanced_info and param_name in enhanced_info:
       info = enhanced_info[param_name]
   else:
       # Fallback
   ```

3. **Ne pas oublier les imports**
   ```python
   # Forge tracer imports (private tooling)
   from tracer.stimulus.structure_inference import EnhancedInputInfo
   ```

### ✅ FAIRE

1. **Documenter clairement**
   ```python
   """
   NEW: Uses enhanced_info from Enhanced Layer 1.
   - Shapes from enhanced_info.shape (no poker)
   - Dtypes from enhanced_info.dtype (no supposition)
   """
   ```

2. **Logger les sources**
   ```python
   logger.debug(f"Using enhanced_info for {param}: shape={info.shape} (source: Layer 1)")
   ```

3. **Erreurs claires si metadata manquante**
   ```python
   if not enhanced_info:
       logger.warning("No enhanced_info provided, using fallback inference")
   ```

---

## ORDRE D'EXÉCUTION

**NOTE (February 2026):** All paths updated for forge/ extraction

1. **forge/tracer/stimulus/blind_solver.py** (Change 1.1 → 1.5)
2. **forge/tracer/stimulus/input_resolver.py** (Change 2.1 → 2.4)
3. **forge/tracer/stimulus/structure_inference.py** (Change 3.1 → 3.2)
4. **Tests** (T5, PixArt, Flux) - via `forge/forge.py trace`
5. **forge/tracer/stimulus/generator.py** (Change 4.1 → 4.3) - Si temps restant
6. **forge/tracer/worker.py** (Change 5.1 → 5.2) - Si temps restant

**Total changes**: 15 modifications réparties sur 5 fichiers

**Important context (February 2026):**
- This refactoring is for **forge/** (private tooling), NOT the public neurobrix package
- forge/ requires `pip install -e .` to import neurobrix.nbx.*
- Forge tracer imports: `from tracer.*` (not `from neurobrix.core.*`)
- Public package is at `src/neurobrix/` with imports: `from neurobrix.core.*`

---

*Prompt créé le 12 Décembre 2025*
*Updated February 2026 for forge/ extraction and package restructure*
