---
phase: 02-driver-management
plan: "01"
type: tdd
wave: 1
depends_on: []
files_modified:
  - imptune/services/__init__.py
  - imptune/services/inf_parser.py
  - tests/test_inf_parser.py
  - tests/fixtures/sample.inf
  - tests/fixtures/sample_utf16.inf
  - tests/fixtures/sample_multi_model.inf
autonomous: true
requirements: [DRV-02, DRV-05]

must_haves:
  truths:
    - "parse_inf extracts DriverDesc values from a simple INF with literal names"
    - "parse_inf resolves %TOKEN% references via the [Strings] section"
    - "parse_inf handles UTF-16 LE BOM, UTF-8 BOM, and ANSI (cp1252) encoded INF files"
    - "parse_inf deduplicates driver names from multi-model INFs (NTamd64 + undecorated)"
    - "parse_inf returns a list of unused files not referenced in the INF text"
    - "parse_inf detects architecture from section decorations (x64, x86, arm64)"
    - "parse_inf detects presence of .cat file in ZIP member list"
  artifacts:
    - path: "imptune/services/inf_parser.py"
      provides: "ParsedInf dataclass and parse_inf() + _detect_encoding() functions"
      exports: ["ParsedInf", "parse_inf", "_detect_encoding"]
    - path: "tests/test_inf_parser.py"
      provides: "Unit tests covering all DRV-02 and DRV-05 behaviors"
      min_lines: 80
    - path: "tests/fixtures/sample.inf"
      provides: "Minimal valid INF with %TOKEN% values and [Strings] section"
    - path: "tests/fixtures/sample_utf16.inf"
      provides: "UTF-16 LE encoded INF for encoding detection test"
    - path: "tests/fixtures/sample_multi_model.inf"
      provides: "INF with NTamd64 and undecorated Models sections"
  key_links:
    - from: "imptune/services/inf_parser.py"
      to: "configparser.RawConfigParser"
      via: "stdlib import"
      pattern: "RawConfigParser.*strict=False"
    - from: "imptune/services/inf_parser.py"
      to: "[Strings] section"
      via: "_resolve_tokens regex expansion"
      pattern: "re\\.sub.*%([^%]+)%"
---

<objective>
Create the INF parser service that extracts driver names (DriverDesc) from Windows INF files, with encoding auto-detection, %TOKEN% resolution, multi-model support, and unused-file detection.

Purpose: This is the core novel logic of Phase 2. The INF parser is a pure function with defined I/O — ideal for TDD. All other Phase 2 work (upload endpoint, UI) consumes this parser's output.
Output: `imptune/services/inf_parser.py` with `ParsedInf` dataclass and `parse_inf()` function, plus comprehensive unit tests and INF fixture files.
</objective>

<execution_context>
@C:/Users/SebastienQUEROL/.claude/get-shit-done/workflows/execute-plan.md
@C:/Users/SebastienQUEROL/.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/02-driver-management/02-RESEARCH.md

<interfaces>
<!-- Existing codebase interfaces this plan needs -->

From imptune/config.py:
```python
DATA_DIR = os.environ.get("DATA_DIR", "/data")
DRIVERS_DIR = str(Path(DATA_DIR) / "drivers")
```

From imptune/storage/driver_store.py:
```python
class DriverStore:
    def save(self, data: bytes) -> str: ...  # returns SHA256 hex digest
    def get_path(self, sha256: str) -> Path: ...
    def exists(self, sha256: str) -> bool: ...
```

From imptune/db/models.py:
```python
class Driver(BaseModel):
    sha256 = CharField(unique=True, index=True)
    original_filename = CharField()
    size_bytes = IntegerField()
    uploaded_at = DateTimeField(default=datetime.utcnow)
    driver_desc = CharField(null=True)    # Store json.dumps(list) for multi-model
    inf_filename = CharField(null=True)
    architecture = CharField(null=True)   # 'x64', 'x86', 'arm64', or None
    has_cat_file = BooleanField(default=False)
```
</interfaces>
</context>

<tasks>

<task type="auto" tdd="true">
  <name>Task 1: INF parser with TDD (RED then GREEN)</name>
  <files>imptune/services/__init__.py, imptune/services/inf_parser.py, tests/test_inf_parser.py, tests/fixtures/sample.inf, tests/fixtures/sample_utf16.inf, tests/fixtures/sample_multi_model.inf</files>
  <behavior>
    - test_detect_encoding_utf16le: _detect_encoding(b'\xff\xfe...') returns 'utf-16'
    - test_detect_encoding_utf8bom: _detect_encoding(b'\xef\xbb\xbf...') returns 'utf-8-sig'
    - test_detect_encoding_ansi: _detect_encoding(b'[Version]...') returns 'cp1252'
    - test_simple_driver_desc: parse_inf with literal DriverDesc in [Models] section returns those names in driver_names
    - test_token_resolution: parse_inf with %HP_DRIVER% in [Models] and HP_DRIVER="HP LaserJet" in [Strings] resolves to "HP LaserJet"
    - test_utf16_encoding: Reading a UTF-16 LE BOM fixture, decoding with _detect_encoding, and passing to parse_inf produces correct driver_names
    - test_multi_model_inf: INF with both [Mfg.NTamd64] and [Mfg] sections returns deduplicated driver_names; architecture='x64' when NTamd64 is present alone
    - test_architecture_detection: NTamd64 -> 'x64', NTarm64 -> 'arm64', undecorated only -> 'x86', mixed -> None
    - test_cat_file_detection: zip_names containing 'driver.cat' -> has_cat_file=True; without -> False
    - test_unused_files: ZIP members ['driver.inf', 'driver.dll', 'readme.txt'] where INF text mentions 'driver.inf' and 'driver.dll' but not 'readme.txt' -> unused_files=['readme.txt']
    - test_empty_models_section: INF with [Manufacturer] but empty Models section returns empty driver_names list
  </behavior>
  <action>
**Phase: RED**

1. Create `tests/fixtures/` directory if it does not exist.

2. Create `tests/fixtures/sample.inf` — minimal valid INF with %TOKEN% values:
```ini
[Version]
Signature="$Windows NT$"
Class=Printer
Provider=%MFG%

[Manufacturer]
%MFG%=Models,NTamd64

[Models.NTamd64]
%DRIVER_NAME%=Install,{GUID}

[Strings]
MFG="Test Manufacturer"
DRIVER_NAME="Test LaserJet Pro"
```

3. Create `tests/fixtures/sample_utf16.inf` — same content as sample.inf but encoded as UTF-16 LE with BOM. Write using Python: `content.encode('utf-16-le')` prepended with `b'\xff\xfe'`. Actually, create this fixture programmatically within the test (or as a conftest fixture) since writing binary fixtures from plan text is fragile.

4. Create `tests/fixtures/sample_multi_model.inf` — INF with both decorated and undecorated sections:
```ini
[Version]
Signature="$Windows NT$"
Class=Printer

[Manufacturer]
%MFG%=Models,Models.NTamd64

[Models]
%DRIVER_A%=InstallA,{GUID1}

[Models.NTamd64]
%DRIVER_A%=InstallA,{GUID1}
%DRIVER_B%=InstallB,{GUID2}

[Strings]
MFG="Multi Corp"
DRIVER_A="Multi Printer 1000"
DRIVER_B="Multi Printer 2000"
```

5. Create `imptune/services/__init__.py` — empty package marker.

6. Create `tests/test_inf_parser.py` with all 11 test functions listed in behavior. Tests import from `imptune.services.inf_parser` and call `parse_inf()` / `_detect_encoding()`. Each test asserts specific expected outputs. For the UTF-16 test, generate the fixture bytes inline: `sample_text.encode('utf-16')`.

7. Run `pytest tests/test_inf_parser.py -x` — all tests MUST FAIL (ImportError or assertion errors). Commit: `test(02-01): add failing tests for INF parser`

**Phase: GREEN**

8. Create `imptune/services/inf_parser.py` implementing:
   - `ParsedInf` dataclass with fields: `driver_names: list[str]`, `inf_filename: str`, `architecture: str | None`, `has_cat_file: bool`, `unused_files: list[str]`
   - `_detect_encoding(raw: bytes) -> str` — BOM sniffing (UTF-16 BOM -> 'utf-16', UTF-8 BOM -> 'utf-8-sig', else -> 'cp1252')
   - `_resolve_tokens(value: str, strings: dict[str, str]) -> str` — regex `%TOKEN%` expansion from strings dict
   - `parse_inf(inf_text: str, inf_filename: str, zip_names: list[str]) -> ParsedInf` — uses `configparser.RawConfigParser(strict=False, comment_prefixes=(';', '#'), delimiters=('=',))`, reads [Manufacturer] to find Models section names, iterates all matching sections (decorated: .NTamd64, .NTarm64, .NTx86; undecorated), extracts left-hand keys as device-descriptions, resolves tokens, deduplicates with set(), detects architecture from section suffix, detects .cat in zip_names, computes unused files by checking if each zip member's basename appears in inf_text (case-insensitive)

   Follow the exact code patterns from 02-RESEARCH.md "Pattern 2: INF DriverDesc Extraction". Key points:
   - Use `RawConfigParser` (NOT `ConfigParser` — avoids %(interpolation)s interference)
   - `strict=False` to handle duplicate keys in real INFs
   - Strings dict keys must be lowercased (configparser lowercases keys by default)
   - Strip surrounding double-quotes from [Strings] values
   - Architecture: if exactly one arch hint in set -> return it; multiple -> None
   - `sorted(driver_names)` for deterministic dropdown order

9. Run `pytest tests/test_inf_parser.py -x` — all tests MUST PASS. Commit: `feat(02-01): implement INF parser with encoding detection and token resolution`
  </action>
  <verify>
    <automated>pytest tests/test_inf_parser.py -v</automated>
  </verify>
  <done>All 11 tests pass. ParsedInf dataclass and parse_inf() function correctly extract driver names from simple, tokenized, UTF-16, and multi-model INF files. Unused files detected. Architecture and .cat presence detected.</done>
</task>

</tasks>

<verification>
```bash
pytest tests/test_inf_parser.py -v
pytest tests/ -x -q  # no regressions in existing tests
```
</verification>

<success_criteria>
- parse_inf() extracts DriverDesc from all 3 fixture types (simple, UTF-16, multi-model)
- %TOKEN% references resolved to human-readable names
- Unused files correctly identified
- All 11+ unit tests green, zero regressions in existing suite
</success_criteria>

<output>
After completion, create `.planning/phases/02-driver-management/02-01-SUMMARY.md`
</output>
