---
id: "0107a"
title: "fn doctor modules — detectar drift uses_modules vs uses_functions y version skew"
status: pendiente
type: feature
domain:
  - meta
  - cpp-stack
  - tooling
scope: registry
priority: alta
depends: []
blocks:
  - "0107b"
related:
  - "0107"
created: 2026-05-17
updated: 2026-05-17
tags: [modules, fn-doctor, drift, audit, cpp]
---

# 0107a — `fn doctor modules`

Parte del issue principal [0107](0107-modules-standardization.md). Feature flag `modules-v2`.

## Objetivo

Subcomando `fn doctor modules` + funcion del registry `audit_modules_drift_go_infra` que detecta:

1. App declara `uses_modules: [X]` Y un miembro de X aparece en `uses_functions` → drift.
2. App declara `uses_modules: [X]` pero su `CMakeLists.txt` NO linkea `fn_module_X` → mismatch.
3. App linkea `fn_module_X` pero NO declara `uses_modules: [X]` → mismatch inverso.
4. App declara `uses_modules: [{name: X, min_version: "1.4"}]` y `module.md::version` < 1.4 → version skew (post 0107e).

## Tareas

- [ ] **1.1** `functions/infra/audit_modules_drift.go` con firma:
  ```go
  type ModuleDriftReport struct {
      ModuleID          string
      ModuleVersion     string
      ConsumersTotal    int
      ConsumersWithDrift int
      Violations        []DriftViolation
  }
  type DriftViolation struct {
      AppID            string
      Kind             string // "duplicate_members" | "uses_modules_no_link" | "link_no_uses_modules" | "version_skew"
      DuplicatedIDs    []string
      Message          string
  }
  func AuditModulesDrift(registryDB string, cppRoot string) ([]ModuleDriftReport, error)
  ```
- [ ] **1.2** `.md` correspondiente con frontmatter completo + ejemplo lanzable.
- [ ] **1.3** Subcomando en `cmd/fn/doctor.go`: `fn doctor modules` + `fn doctor modules --json`.
- [ ] **1.4** Exponer via MCP: `mcp__registry__fn_doctor subcommand="modules"`.
- [ ] **1.5** Test sintetico: fixture con 1 modulo + 3 apps (1 limpia, 1 con drift de duplicados, 1 con version skew).
- [ ] **1.6** Anadir entrada a `.claude/rules/fn_doctor.md` mapeando subcomando.

## Output esperado (texto)

```
fn doctor modules
=================
data_table_cpp v1.4.0 — 7 consumers
  services_monitor       DRIFT  12 duplicated members in uses_functions
  dag_engine_ui          DRIFT  12 duplicated members in uses_functions
  odr_console            DRIFT   5 duplicated members in uses_functions
  navegator_dashboard    DRIFT  12 duplicated members in uses_functions
  graph_explorer         DRIFT  12 duplicated members in uses_functions
  registry_dashboard     DRIFT  11 duplicated members in uses_functions
  app_gestion            DRIFT  12 duplicated members in uses_functions

framework_cpp v1.1.0 — 0 explicit consumers (transitive via add_imgui_app)

Summary: 2 modules, 7 apps with drift, 0 version skews.
Exit: 1 (drift detected)
```

## Riesgos

- Falso positivo si un app legitimamente necesita un miembro fuera del scope del modulo (ej. usar `lua_engine` standalone). Mitigacion: post-0107d (members generales fuera del modulo), este caso desaparece. Mientras tanto: flag `--ignore-known` con allowlist temporal.
