docs(02-driver-management): create phase plan

Two plans: INF parser TDD (wave 1), upload endpoint + UI (wave 2).
Covers DRV-01 through DRV-05.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-10 11:48:02 +02:00
co-authored by Claude Opus 4.6
parent 7930619404
commit 9f5ee24811
3 changed files with 528 additions and 5 deletions
@@ -0,0 +1,213 @@
---
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>