# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Objet

Boîte à outils de parsing CSV. Chaque traitement est un **micro-script** exposé
par un hub interactif unique (`csvkit`). Les micro-scripts sont chaînables : la
sortie de l'un est proposée en entrée du suivant.

## Commandes

```bash
mise run hub          # menu interactif (équivaut à `cargo run --bin csvkit`)
mise run build        # binaire release
mise run test         # cargo test --workspace + pytest
mise run lint         # clippy -D warnings + ruff check
mise run fmt          # cargo fmt + ruff format

# Un seul test
cargo test -p csvkit-core splits_and_keeps_header    # Rust
uv run --project python pytest -k splittable          # Python
```

Mode scriptable (sans prompt) :

```bash
cargo run --bin csvkit -- list
cargo run --bin csvkit -- split --input sources/x.csv --column Location [--overwrite]
cargo run --bin csvkit -- split -i sources/x.csv -c Location --naming mangools [--project p] [--date 2026-07-16]
cargo run --bin csvkit -- run profile -- --input sources/x.csv [--all]
```

## Architecture

Deux runtimes derrière **un seul menu**. Le hub ne sait pas quel runtime
exécute quoi — il ne manipule que des `registry::Tool`.

- `crates/csvkit-core/` — primitives CSV (lecture, slug, split). **Ne prompte
  jamais et n'écrit pas sur le terminal** : appelable depuis un test comme
  depuis le hub. C'est là que vit la logique testable.
- `crates/csvkit/` — le binaire. `registry.rs` construit le catalogue,
  `tools/mod.rs::dispatch_interactive` aiguille, `ui.rs` porte les prompts
  partagés, `workspace.rs` résout la racine.
- `python/` — projet uv (`csvkit_py`). `cli.py` implémente le contrat hub →
  script ; `workspace.py` est le pendant Python de `workspace.rs`.
- `tools/<id>/tool.toml` — manifeste d'un micro-script Python.

### Contrat hub → micro-script Python

Le hub lance `uv run --project python python -m <module>`, exporte `CSVKIT_ROOT`
et **hérite du terminal** : c'est le script Python qui prompte pour ses propres
paramètres. Tout micro-script accepte `--input` / `--out-dir`, tous deux
optionnels — fournis, ils désactivent les prompts (mode scriptable).

### Ajouter un micro-script

- **Rust** : ajouter une entrée dans `registry::NATIVE` **et** un bras dans
  `tools::dispatch_interactive`. Les deux, sinon le menu propose un outil qui
  échoue à l'exécution. Ajouter aussi une sous-commande clap dans `main.rs` —
  `csvkit run <id>` refuse les natifs exprès, pour que clap parse les arguments.
- **Python** : créer `tools/<id>/tool.toml` + le module sous
  `python/src/csvkit_py/tools/`. Aucune inscription en Rust : la découverte est
  automatique au démarrage du hub.

## Conventions

- `sources/` = entrées (versionné), `out/` = fichiers produits (gitignoré,
  reproductible). Ne jamais écrire dans `sources/`.
- Un manifeste `tool.toml` illisible est une **erreur franche**, pas un silence :
  un outil qui disparaît du menu sans rien dire est pire qu'un crash.
- Le lecteur CSV est en mode `flexible` : les exports Mangools ont des lignes
  plus courtes que le header quand les derniers mois de volume sont vides.
- Les seuils heuristiques (`SPLITTABLE_MAX_DISTINCT`, `NUMERIC_SHARE_MAX`,
  `UNIQUE_SHARE_MAX`, `MAX_GROUPS`) sont des constantes nommées et commentées,
  jamais des littéraux dans le code.
- Dépendances : toujours via `cargo add` / `uv add` (préfixés `sfw`), jamais en
  écrivant une version à la main dans le manifeste.

## Données Mangools — à savoir

Les exports KWFinder ont ~149 colonnes, dont ~130 `Search Volume <mois>/<année>`.

**Il n'y a pas de colonne de langue.** Un export multi-marchés contient le même
jeu de keywords *anglais* répété par marché : `Germany`, `Japan` et `Brazil`
portent tous `jewelry manufacturer`. Une détection de langue sur le keyword
renverrait `en` partout. Le seul axe de découpage réel est **`Location`** (le
marché), d'où `DEFAULT_COLUMN = "Location"` dans `tools/split.rs`.

Si on demande un « split par langue », c'est `Location` qu'il faut — le
confirmer avant de coder une détection de langue.

### Nommage `--naming mangools`

`mangools-{date}-{projet}-{langue-pays}.csv`, ex.
`mangools-2026-07-16-advmfr-en-us.csv`.

Le segment de fin est une locale **`langue-pays`**, jamais une langue seule :
sept marchés du jeu sont anglophones (US, UK, Canada, Australie, NZ, Singapour)
et deux arabophones (Arabie Saoudite, Qatar). Un `en` nu ferait s'écraser sept
fichiers et détruirait le découpage par marché. `naming::LOCALES` est la table
de vérité ; le test `anglophone_markets_stay_distinct` verrouille cette
propriété.

La langue mappée est celle du **marché** (SERP), pas des keywords — ces
derniers sont anglais partout. Un marché absent de `LOCALES` n'échoue pas : il
retombe sur le slug de sa valeur et remonte dans `SplitReport::unmapped_markets`
pour que le hub avertisse. Ne jamais le rendre silencieux.

`naming.rs` ne lit pas l'horloge : la date vient de l'appelant (`jiff`, côté
binaire). C'est ce qui garde le nommage testable.
