---
name: concur
description: Cross-source consensus check for chip libraries. Compares ds2sf-extracted symbol+footprint against chip-fetcher's KiCad/EAGLE/Altium libraries and emits a result.json with status (golden/divergent/unrecoverable), exit codes (0/2/3), and tagged hints[] so chip-fetcher can autonomously re-fetch a suspect library or escalate to a human. Trigger words — concur, golden dataset, cross-source check, kicad eagle altium agreement, lib agreement, library mismatch, ds2sf vs kicad, ul stub symbol, refetch library, chip lib consensus.
---

# concur — cross-source consensus check

Pipeline position: `chip-fetcher` → `ds2sf` (status: ok) → **concur** (status: golden) → safe for downstream.

When the four sources of truth for a chip's symbol+footprint (ds2sf-extracted from the datasheet, KiCad UL bundle, EAGLE/Fusion 360 .lbr, Altium binary) all agree, you have a golden dataset. When they diverge, somebody's wrong — concur tells the upstream caller which source is suspect and what to do.

## Outputs

`<chip-dir>/<MPN>-concur.result.json` — the audit + agent-to-agent contract:

```json
{
  "schema_version": "1",
  "status": "golden | divergent | unrecoverable",
  "exit_code": 0 | 2 | 3,
  "mpn": "ADS1115IDGSR",
  "summary": "ADS1115IDGSR — DIVERGENT. Sources: [ds2sf,kicad,eagle]. Axes: 5 agree, 2 disagree, 0 source-missing.",
  "sources_compared": ["ds2sf","kicad","eagle"],
  "sources_missing": ["altium"],
  "axes": [
    { "axis": "pad_count",      "verdict": "agree", … },
    { "axis": "pin_names",      "verdict": "disagree",
      "conflicts": ["kicad has stub-only pin names (UL bundle did not carry the part's real pin labels)"] }
  ],
  "hints": [
    { "action": "ul_symbol_has_stub_names", "source": "kicad", "pin_count": 10,
      "suggestion": "Use ds2sf-extracted symbol JSON or refetch the .kicad_sym from a manufacturer-direct source" }
  ]
}
```

## Commands

```
concur check <CHIP-DIR>     # run the cross-source check
concur inspect <CHIP-DIR>   # print summary of an existing concur.result.json
concur health               # readiness check
concur install              # drop SKILL.md
```

`check` flags: `--ds2sf-symbol`, `--ds2sf-footprint`, `--kicad-sym`, `--kicad-mod`, `--eagle`, `--mpn`, `--out-dir`, `--force`, `--json-only`.

## Status ladder (agent-to-agent contract)

| Exit | `status` | Meaning | Caller action |
|---|---|---|---|
| 0 | `golden` | All sources agree exactly. | Accept. |
| 0 | `golden_with_variants` | Disagreements exist but every one is explained by a `variants[]` entry (EP-only, module-extras, label-only, stub pin names, body-dim measurement artifact). | **Accept.** Downstream tools use the variant list to decide which source to prefer. |
| 2 | `divergent` | At least one disagreement is **not** covered by a variant. | Walk `hints[]`; refetch a library or escalate. |
| 3 | `unrecoverable` | Couldn't parse ≥2 sources. | Surface to human. |

## `variants[].kind` enum

Variants explain *why* a disagreement is benign, separate from `hints[]` which tell the caller *what to do*.

| Kind | When | Meaning |
|---|---|---|
| `exposed_pad_variant` | Pad count diff is exactly 1 | EP/thermal pad counted as pin N+1 by some sources, omitted by others. |
| `module_extra_pads` | Pad count diff > 1 | KiCad/EAGLE footprint includes mounting/shield/test pads beyond module datasheet pinout. |
| `label_only_mismatch` | Package family names differ but geometry agrees | TI's `SOT_` prefix on VSSOP footprints, `DGS0010A` JEDEC outline → VSSOP, IPC-7351 generic names like `SOP65P640X120-20N`, etc. Cosmetic. |
| `stub_symbol_pin_names` | One source's pin names are numeric ("1","2"…) | UL bundle stub case. Use ds2sf or EAGLE for real names. |
| `body_dim_measurement_artifact` | Body dim disagrees but pad geometry agrees | Different "body" measurement methods. Doesn't affect placement. |
| `naming_convention_mismatch` | Pin names differ but every disagreement is a benign convention swap | Channel-digit position (`OUT1` ↔ `1OUT`) for op-amps and multi-channel parts; power-pin aliases (`V+` ↔ `VCC`, `V-` ↔ `GND`) for chip-vendor-vs-library naming. |

## Comparison axes (what gets cross-checked)

- `pad_count` — number of pads in the footprint
- `pad_numbers` — set of pad numbers / labels
- `symbol_pin_count` — number of pins in the symbol
- `pin_names` — per-pin name across sources (slash-normalized, case-insensitive). Stub-only sources (UL bundles where pin "name" is the pin number) are flagged as `ul_symbol_has_stub_names` rather than failing the axis.
- `pin_to_pad_map` — does pin VDD map to the same pad number across sources?
- `package_family` — SOIC/QFN/SOT/VSSOP/etc., with JEDEC outline aliases (DGS0010A → VSSOP)
- `body_dims` — body x/y in mm, ±0.5 mm tolerance

## `hints[].action` enum

| Action | Carries | When |
|---|---|---|
| `refetch_library_source` | `{format, suspect, notes}` | A specific lib looks wrong; chip-fetcher should try a different upstream. |
| `ul_symbol_has_stub_names` | `{source, pin_count, suggestion}` | KiCad UL stub case (pin "names" are pin numbers). |
| `accept_with_caveats` | `{caveats}` | Soft diffs within tolerance. |
| `human_review` | `{reason, notes}` | No automated repair safe (e.g. Altium parser not yet implemented). |
| `source_parse_failed` | `{format, path, error}` | A file couldn't be parsed at all. |

## `hints[].action` enum

Every hint tells the caller exactly what to do. v0.3 enriches the previously-skinny refetch hints with `suggested_sources` (priority-ordered list of where to refetch from) and `expected_resolution` (how the caller verifies the refetch worked).

| Action | Carries | What chip-fetcher should do |
|---|---|---|
| `refetch_library_source` | `{format, suspect, notes, suggested_sources, expected_resolution}` | Walk `suggested_sources` in order. After each refetch, re-run `concur check`. The `expected_resolution` field tells you the specific axis that should change verdict on a successful refetch. Stop when status flips to `golden` / `golden_with_variants`. |
| `ul_symbol_has_stub_names` | `{source, pin_count, suggestion, chip_fetcher_action}` | **Don't refetch the .kicad_sym** — UL stub-naming is a UL property, not a chip-fetcher choice. Instead mark the chip dir's symbol source as `ds2sf-preferred` so downstream `sym_create` authors a fresh symbol with real labels from ds2sf's JSON. |
| `rerun_ds2sf_with_stronger_model` | `{reason, suspect_field, suggested_model}` | Re-run `ds2sf extract --force --model claude-opus-4-7`. ds2sf likely misread a small dimension or pin field with Haiku. Verify by re-running concur. |
| `accept_with_caveats` | `{caveats}` | Soft diffs within tolerance — accept the dataset, just remember the caveats for UI display. |
| `human_review` | `{reason, notes}` | Surface to a human; no automated repair safe. |
| `source_parse_failed` | `{format, path, error}` | A source file couldn't be parsed. If `format == altium` and concur version < 1.0, this is expected (Altium binary parser isn't implemented yet). |

## Agent-to-agent calling recipe

```rust
// (Pseudo-Rust for chip-fetcher's caller side.)

// Step 1: ds2sf
let ds2sf = run_ds2sf(chip_dir);
if ds2sf.status != "ok" {
    // Handle ds2sf needs_better_pdf path — see ds2sf SKILL.md.
}

// Step 2: concur
let mut tried_refetches: HashSet<String> = HashSet::new();
loop {
    let r = run_concur(chip_dir);
    match r.status.as_str() {
        "golden" | "golden_with_variants" => {
            // Apply ul_symbol_has_stub_names by marking the chip dir's
            // symbol source as ds2sf-preferred for downstream sym_create.
            for h in &r.hints {
                if let Hint::UlSymbolHasStubNames { .. } = h {
                    mark_chip_dir_symbol_source(chip_dir, "ds2sf-preferred");
                }
            }
            mark_chip_dir_clean(chip_dir, &r.variants);
            break;
        }
        "divergent" => {
            // Walk hints in order. First action that's not yet been tried wins.
            let mut acted = false;
            for h in &r.hints {
                match h {
                    Hint::RerunDs2sfWithStrongerModel { suggested_model, .. } => {
                        run_ds2sf(chip_dir, &["--force", "--model", suggested_model]);
                        acted = true; break;
                    }
                    Hint::RefetchLibrarySource { format, suggested_sources, .. } => {
                        for src in suggested_sources {
                            let key = format!("{format}|{src}");
                            if tried_refetches.insert(key) {
                                refetch_library(chip_dir, format, src);
                                acted = true; break;
                            }
                        }
                        if acted { break; }
                    }
                    _ => {}
                }
            }
            if !acted {
                // Out of automated options.
                log_for_human(&r);
                break;
            }
        }
        "unrecoverable" => { log_for_human(&r); break; }
        _ => unreachable!(),
    }
}
```

## Out of scope for v0.1

- Altium binary parsing (.SchLib / .PcbLib are OLE-compound). Surfaces as `human_review` with reason `altium_parser_not_implemented`.
- Body-thickness comparison (z-dim). Sources don't reliably report this.
- Automatic pin-name slash-normalization beyond `/` → `_` (e.g. fully-qualified mux paths).

## Related

- `ds2sf` (skill at `~/.claude/skills/ds2sf/SKILL.md`) — upstream: extracts symbol+footprint+provenance from the datasheet PDF.
- `chip-fetcher` (skill at `~/.claude/skills/chip-fetcher/SKILL.md`) — primary consumer; pulls the libraries that concur cross-checks.
