Skip to content

Meticulous-review platform — phases 2 through 4c (review workflow, cross-MCP, schematic analyzers)#125

Merged
RFingAdam merged 10 commits into
mainfrom
feat/meticulous-review-phases-2-through-4c
May 19, 2026
Merged

Meticulous-review platform — phases 2 through 4c (review workflow, cross-MCP, schematic analyzers)#125
RFingAdam merged 10 commits into
mainfrom
feat/meticulous-review-phases-2-through-4c

Conversation

@RFingAdam

Copy link
Copy Markdown
Owner

Summary

End-to-end build of the meticulous-professional-engineer review platform on top of the existing layout-centric MCP server. The PR collapses six commits worth of work into one reviewable diff and closes seven tracking issues.

Closes

#124 (mypy debt) intentionally not closed — typecheck is advisory until the cleanup lands.

What landed (six phases)

Commit Phase Adds
63135db 2 8-pass CLAUDE_REVIEW_PLAYBOOK.md, market packs, pcb_start_professional_review, instructions= on the Server constructor
8affb3a 3a ExternalAction intent queue, ReviewFinding schema upgrade (confidence/verified/source/finding_id/linked_actions), 4 orchestration tools
ef23829 3b limits_provider with live-regs cache, regulations/NEC2/drawio bridges, openEMS compare-results verification loop
833f6d6 4 Multi-market intake refactor, standards coverage map, preflight gate, 3 new tools
b2b5b91 4b Schematic ingestion auto-dispatch, 4 schematic analyzers (power_topology, protection_circuits, decoupling_per_ic, component_rating), 3-way cross-reference
f47df45 4c KiCad sexpdata parser, ORCAD/Pads netlist parser, signal_flow analyzer, Altium DNP/MPN parameter mapping, schematic fixture corpus, ruff pinning

Numbers

  • Tool surface: 116 → 131 MCP tools, registry ↔ dispatch parity verified by the all-tools smoke test.
  • Tests: 1383 → 1611 passing, 0 failing, 11 skipped. +228 new tests across 17 new test files.
  • Coverage: 78.65 % (CI gate at 50 %).
  • Lint: ruff clean against the pinned 0.9+ config.
  • Pre-existing failures fixed: 4 (_prefix_for letters-only/GEN fallback + Ethernet substring matching).

End-to-end review flow that's now real

pcb_start_professional_review(inputs, declared_market)
  → playbook + manifest + interview pack + standards shortlist
pcb_set_market / pcb_parse_layout(session_id=...) / pcb_parse_schematic / pcb_parse_bom
pcb_get_review_questions → pcb_answer_review_questions → pcb_set_review_context
pcb_validate_review_complete                    # preflight gate
pcb_run_design_review
  → next_actions[]: openEMS escalations, NEC2 antenna runs,
                   emc-regulations live limit lookups, drawio diagrams
pcb_suggest_next_actions / pcb_attach_external_result   (round-trip)
pcb_get_standards_coverage
pcb_generate_design_review_report               # gated, force=True stamps PRELIMINARY
pcb_finalize_review                             # confidence audit + self-critique

Honest readout

  • Layout-complete reviews: production-ready across all five markets (automotive / medical / wireless / commercial / industrial).
  • Schematic side: works on KiCad-native (sexpdata) and PDF (text layer) and ORCAD/Pads netlist; Altium parameter-record extraction lands here, deeper pin-net work needs licensed .SchDoc fixtures (tracked).
  • Mypy debt: ~66 pre-existing type errors across 13 files (current_profiler.py dominates); typecheck job advisory until Clean up pre-existing mypy errors (~66 across 13 files) #124 closes.

Test plan

  • CI lint + test (3.10 / 3.11 / 3.12) green on this PR.
  • CI typecheck job runs and reports issues but does not block (per Clean up pre-existing mypy errors (~66 across 13 files) #124).
  • Smoke test confirms 131 tools / 131 dispatch branches.
  • Coverage stays ≥ 50 %.
  • Skim the docs/CLAUDE_REVIEW_PLAYBOOK.md, docs/MARKET_INTAKE_MATRIX.md, docs/SELF_CRITIQUE_CHECKLIST.md, docs/STANDARDS_COVERAGE_MATRIX.md for accuracy against the codebase.

🤖 Generated with Claude Code

RFingAdam and others added 9 commits May 14, 2026 21:49
Adds the binding 8-pass professional design review workflow plus the
single entry-point MCP tool that drives it.

* docs/CLAUDE_REVIEW_PLAYBOOK.md — P0 intake → P1 scoping interview →
  P2 parse + cross-reference → P3 domain analysis → P4 standards
  verification → P5 simulation escalation → P6 cross-domain correlation
  → P7 reporting → P8 self-critique. Includes per-pass MCP tool-call
  sequences and a worked CISPR-25 example.
* docs/MARKET_INTAKE_MATRIX.md — per-market question packs (automotive,
  medical, wireless, commercial, industrial) and the standards/analyzers
  each market activates.
* docs/SELF_CRITIQUE_CHECKLIST.md — explicit Pass-8 checklist (coverage,
  confidence audit, assumption ledger, counter-evidence, simulation gap,
  standards traceability, human-review flags, known blind spots).
* src/mcp_pcb_emcopilot/market_packs.py — data-only QUESTION_BANK,
  MARKET_STANDARDS, MARKET_ANALYZERS plus merge helpers.
* src/mcp_pcb_emcopilot/review_playbook.py — SERVER_INSTRUCTIONS string
  (surfaced to Claude on every MCP initialize), file classifier,
  manifest/gap builder, interview-pack assembler, standards/analyzer
  shortlist computers, and the start_professional_review payload builder.
* src/mcp_pcb_emcopilot/server.py — Server constructed with
  instructions=SERVER_INSTRUCTIONS; registers pcb_start_professional_review
  as the canonical entry point; pcb_parse_layout now accepts an optional
  session_id so the playbook session survives parsing.
* src/mcp_pcb_emcopilot/session.py — replace_session() for in-place
  session updates without poking private attrs.

Plus untracked tests bundled in: test_review_playbook (40 tests covering
file classification, manifest, gaps, interview pack, multi-market unions,
playbook markdown loader, start_professional_review payload) and
test_pcb_start_professional_review (6 dispatcher tests, including the
session-reuse path through pcb_parse_layout).

Tool surface now 117 (was 116); all-tools smoke green; full suite 1383
passing with the same 4 pre-existing failures unrelated to this work.

Also includes the v1 production-readiness plan reference doc, the test
suite (smoke + per-domain coverage) that had been sitting untracked, and
the .gitignore updates to exclude generated review HTML and sim_plots/.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wires the sibling-MCP coordination layer that lets Claude verify
high-severity / low-confidence findings against openEMS, NEC2, and
emc-regulations without breaking the request/response model FastMCP
enforces. The pcb-emcopilot Python process emits actions; Claude
executes them via mcp__<server>__<tool>; results flow back through a
new attach endpoint that re-correlates the linked findings.

* src/mcp_pcb_emcopilot/integrations/external_actions.py — new module
  with ExternalAction + ExternalResult dataclasses, priority constants
  (CRITICAL/HIGH/NORMAL/LOW/NICE_TO_HAVE), dedupe_actions,
  sort_by_priority, filter_pending, has_pending_critical helpers. Pure
  data — no I/O, hashable params signatures for safe dedupe across nested
  geometries.
* src/mcp_pcb_emcopilot/session.py — DesignSessionManager gains
  pending_actions queue and external_results map per session_id, with
  enqueue_actions / get_pending_actions / find_action / record_result
  helpers. close_session evicts the queues alongside the design data.
* src/mcp_pcb_emcopilot/orchestrator.py — ReviewFinding schema upgrade:
  confidence (default 0.8), verified (False), source ("analytical"),
  finding_id (auto-generated, uses the consolidated _prefix_for from
  report_builder), linked_actions list. Adds escalation policy
  constants (ESCALATE_SEVERITY_MAX, ESCALATE_CONFIDENCE_BELOW,
  SIM_TOLERANCE_PCT, NEC2_MIN_FREQ_MHZ, OPENEMS/NEC2 domain sets).
  New _emit_next_actions(design, review_result) walks findings that
  meet the predicate and emits openEMS-bound ExternalActions, pairing
  candidate geometries from RFSimulationExtractor with each finding.
* src/mcp_pcb_emcopilot/reports/report_builder.py — _prefix_for now
  strips non-letters and falls back to GEN when the domain string has
  no alpha characters at all. Fixes 3 pre-existing failing tests in
  test_more_coverage.py::TestFindingIdPrefix.
* Orchestrator ethernet skip predicates — switched from exact tuple
  membership to substring match so 1000BASE-T / 2.5GbE / 10GbE variants
  are caught. Fixes the 4th pre-existing failing test.
* src/mcp_pcb_emcopilot/server.py — Server(... instructions=...) now
  registers four new tools:
  - pcb_suggest_next_actions(session_id, domains?, max_actions?,
    include_completed?) — drains the queue, prioritised order.
  - pcb_attach_external_result(session_id, action_id, result, error?)
    — persists the sibling-MCP result; on success marks the linked
    finding verified=True, bumps confidence to 0.95, sets
    source=<mcp_server>. On error stamps source with "+sim_failed".
  - pcb_finalize_review(session_id, require_critical_verified?) —
    blocks while critical-priority actions are pending unless the
    caller opts out; returns confidence_distribution and a pointer to
    docs/SELF_CRITIQUE_CHECKLIST.md.
  - pcb_lookup_limit_live(standard, class_or_level, frequency_mhz,
    detector?, fallback?) — Phase 3a stub: emits a deferred envelope
    pointing at the matching mcp__emc-regulations__* tool. Phase 3b
    will add the regulations_bridge with cached + local-fallback
    values via analyzers/emc/limits_provider.py.
  pcb_run_design_review now invokes _emit_next_actions and enqueues
  the resulting actions; the response includes a next_actions field.
  pcb_generate_design_review_report gains a force flag and refuses to
  run (status=deferred) when critical-priority actions remain, unless
  force=True (then stamps the output preliminary=True).

Tool surface: 117 → 121 registrations, parity with dispatch branches.
Tests: +36 new (test_external_actions, test_review_finding_schema,
test_phase3_mcp_tools) AND 4 pre-existing failures now pass. Full
suite 1423 passing (was 1383 passed + 4 failed before this slice).

Phase 3b will follow with limits_provider.py + analyzer refactor to
read live limits, and the regulations / nec2 / drawio bridges that
emit their own ExternalActions through this same queue.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds the centralised regulatory-limit provider with live-cache override
plus three intent-emitting bridges (regulations, NEC2, drawio) and the
openEMS verification loop. The orchestrator now emits four flavours of
sibling-MCP intent per review — Claude drives them, results round-trip
through pcb_attach_external_result, and findings/limits update in place.

* src/mcp_pcb_emcopilot/analyzers/emc/limits_provider.py — new module.
  get_limit(standard, class_or_level, freq_mhz, detector?) → LimitPoint
  with source tracking ("local_fallback" | "live_regs"). Local tables
  cover CISPR-25 radiated+conducted (per class, with radiated/conducted
  disambiguation via standard alias or AVG detector), FCC Part 15 A/B
  radiated+conducted, CISPR 32 / EN 55032 A/B, ISO 11452-2 field +
  ISO 11452-4 BCI, IEC 60601-1-2 Ed 4.0/4.1 RF immunity. Runtime cache
  populated by cache_live_result() from the regs bridge.

* src/mcp_pcb_emcopilot/analyzers/emc/clock_emi_analyzer.py —
  _get_regulatory_limit now delegates to limits_provider.get_limit and
  falls back to its legacy tuple list only on provider miss. Behaviour
  identical for the historical call path; live regs overrides propagate
  automatically.

* src/mcp_pcb_emcopilot/reports/simulation_plots.py — cispr25_compliance
  plot's limit mask now reads through limits_provider, so a Claude-fed
  live regs result repaints the mask without code changes.

* src/mcp_pcb_emcopilot/integrations/regulations_bridge.py — new module.
  build_limit_lookup_intent / build_intents_for_standards emit
  ExternalAction objects targeting mcp__emc-regulations__* tools per
  standard. apply_limit_result parses sibling-MCP responses (supports
  several common payload shapes) and writes them into the provider's
  runtime cache. Standard→tool mapping covers CISPR/FCC/ISO/IEC families.

* src/mcp_pcb_emcopilot/integrations/nec2_bridge.py — new module.
  build_antenna_intent emits mcp__nec2-antenna__nec2_create_<type>
  actions for antenna findings ≥ 30 MHz with non-zero trace length;
  infer_antenna_type maps title/description text to dipole / monopole /
  yagi / loop / vertical / inverted-v primitives. build_simulate_followup
  pairs each create with the corresponding nec2_simulate action.
  is_antenna_finding gates the orchestrator scan.

* src/mcp_pcb_emcopilot/integrations/drawio_bridge.py — new module.
  build_diagram_intents composes up to four report-time intents
  (create_pcb_stackup, create_rf_block_diagram, create_emc_test_setup
  per market, markup_schematic when schematic parsed). Market →
  canonical standard mapping (automotive→CISPR-25, medical→IEC-60601,
  etc.).

* src/mcp_pcb_emcopilot/orchestrator.py — _emit_next_actions now
  produces all four intent flavours: openEMS escalations (passing
  analytical_value through for compare_results), NEC2 antenna runs for
  antenna findings ≥ 30 MHz, regulations live-lookup batches per
  standard in the shortlist, drawio diagrams at the end. Dedupe handles
  overlap between escalation paths.

* src/mcp_pcb_emcopilot/server.py — pcb_attach_external_result now
  performs bridge-specific post-processing:
  - emc-regulations results call apply_limit_result, populating the
    provider cache and returning live_limit_cached=True.
  - openems results call OpenEMSBridge.compare_results against the
    analytical_value carried in the action's params (passed in by
    orchestrator). Sim status drives the linked finding's confidence/
    severity per SIM_TOLERANCE_PCT: pass→0.95+verified, warning→0.6,
    fail→severity escalated to critical with an explanatory note
    appended to the description.

Tests: +52 new (test_limits_provider, test_regulations_bridge,
test_nec2_bridge, test_drawio_bridge, test_openems_attach_loop) covering
local fallback for every standard family, live-cache override and
clock-emi provider integration, intent emission per market, NEC2 type
inference + min-freq gating, drawio composite emission, and the full
openEMS pass/warning/fail roundtrip + emc-regulations cache write-back.
Full suite 1475 passing / 0 failing.

Tool surface stable at 121 (no new tools — all module-level work).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…gate

Wires the per-market question packs (already shipped in market_packs.py
during Phase 2) into review_context.py so pcb_get_review_questions now
returns the merged core + active-market pack. Adds a programmatic
standards-coverage map and the preflight gate the playbook references
in Pass 0/1, plus three new MCP tools that drive the gate end-to-end.

* src/mcp_pcb_emcopilot/review_context.py — get_review_questions merges
  CORE legacy pack with market_packs.get_pack(m) for every active market.
  New helpers: get_active_markets (explicit list > playbook declared >
  RF-net heuristic), get_target_standards_for (union across markets).
  Typed getters added for every market-specific answer (vehicle_class,
  bus_voltage, iso26262_asil, cispr25_class, iso7637_pulses, oem_spec,
  device_class, iec60601_edition, patient_contact, fcc_part,
  tx_power_dbm, antenna_gain_dbi, intentional_radiator, target_regions,
  cispr32_class, iec61000_4_immunity_level, hazloc_class,
  surge_target_kV). Tolerates list-or-csv-string forms.

* src/mcp_pcb_emcopilot/standards/coverage.py (new package). 30+ standards
  mapped to required_analyzers, limit_source (local_fallback or
  emc-regulations), coverage_level (full / partial / stub / unimplemented).
  StandardCoverage dataclass + get_coverage(standards, ran_analyzers) +
  summarise_coverage() rollup.

* src/mcp_pcb_emcopilot/standards/preflight.py — ValidationGate dataclass
  with ready/missing_required_questions/missing_standard_selection/
  incomplete_standards/notes. validate_review_complete(design, ran)
  enforces core required questions (operating_environment,
  fab_stackup_spec) plus per-market required sets (automotive:
  vehicle_class+cispr25_class+bus_voltage; medical: device_class+
  iec60601_edition; wireless: intentional_radiator+fcc_part; commercial:
  cispr32_class+target_regions; industrial: en61326_immunity+
  surge_target_kV). Surfaces stub/unimplemented standards as
  incomplete_standards but does not block on partial coverage.

* docs/STANDARDS_COVERAGE_MATRIX.md — human-readable mirror.

* src/mcp_pcb_emcopilot/server.py — three new tools:
  - pcb_set_market(session_id, market_id, replace?, sub_options?) —
    activates a market preset, updates the playbook's standards +
    analyzer shortlists, optionally pre-fills sub_options into
    interactive_answers. Multi-market via repeated calls.
  - pcb_get_standards_coverage(session_id) — returns the coverage
    summary derived from review_results' domain_results.
  - pcb_validate_review_complete(session_id) — wraps the preflight gate.
  pcb_generate_design_review_report now runs the preflight gate FIRST
  (before the cross-MCP critical-action gate) and returns
  status=deferred with the gate breakdown on failure. force=True
  bypasses both gates and stamps the output preliminary=True. The
  generated response includes preflight + standards_coverage payloads.

* tests/test_phase3_mcp_tools.py — Phase 3a report-gate tests now
  satisfy preflight first via a helper, then exercise the cross-MCP
  gate as before.

Tool surface 121 → 124 (parity with dispatch branches intact).
Tests: +50 new (test_review_context_multi_market 19, test_standards_coverage
9, test_preflight 7, test_phase4_mcp_tools 15). Full suite 1521 passing
/ 0 failing / 10 skipped.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Layers the schematic side of the meticulous review onto the layout-centric
work shipped in phases 2-4. Adds an auto-dispatch schematic parser, four
new schematic-aware analyzers, and the long-promised 3-way (schematic ↔
BOM ↔ layout) cross-reference. All run automatically when schematic data
is attached to the session.

* src/mcp_pcb_emcopilot/parsers/schematic_dispatch.py — new module.
  detect_format(path) maps extension + magic bytes to
  Literal["kicad","altium","pdf","netlist","unknown"].
  parse_schematic_auto(path) routes to the existing PDF / KiCad /
  Altium parsers and returns a uniform {source_format, components,
  nets, title, warnings, raw} dict. Refuses image-only PDFs with a
  clear remediation message via pdfplumber peek. Includes a minimal
  ORCAD/Pads netlist stub so .net files yield at least refdes + net
  lists.

* src/mcp_pcb_emcopilot/analyzers/schematic/ — new package, four
  analyzers + a shared _normalise helper that handles both dict-form
  (from PDF) and dataclass-form (from KiCad/Altium) inputs.
  - power_topology — per-rail caps, bulk presence, regulator/input
    source. Degrades cleanly when pin-net mapping is missing.
  - protection_circuits — TVS coverage on external nets (USB / ETH /
    ANT / GPIO_EXT / CAN / VIN / VBUS / VBAT), common-mode choke on
    diff pairs, fuse on power input. Per-net flagging when pin-net data
    is present; aggregate fallback otherwise.
  - decoupling_per_ic — counts caps on each IC's Vdd-class pins;
    falls back to global ratio when pin-net data is absent.
  - component_rating — joins BOM voltage ratings against inferred rail
    voltages; flags 80% derating violations.

* src/mcp_pcb_emcopilot/analyzers/validation/three_way_xref.py —
  promoted from bom_cross_reference. Severity ladder per the plan:
  CRITICAL = component missing from layout / footprint mismatch sch ↔
  layout / value mismatch sch ↔ BOM. HIGH = DNP flag differs / MPN
  differs. MEDIUM = component absent from BOM or schematic. LOW =
  manufacturer differs only. Value normalisation handles unit symbol
  variants (10 kΩ ≡ 10K). Missing-from-X checks fire only when source
  X actually has data, so an empty BOM doesn't generate false
  "missing from BOM" for every component.

* src/mcp_pcb_emcopilot/server.py — six new tools (130 total, 124 → 130):
  - pcb_parse_schematic — auto-dispatch over .kicad_sch, .SchDoc,
    .pdf, .net. Reuses an existing session via optional session_id.
  - pcb_parse_bom — wraps BOMParser; attaches bom_items to session.
  - pcb_analyze_power_topology / _protection_circuits /
    _decoupling_per_ic — individual schematic analyzers.
  - pcb_three_way_cross_reference — runs the new 3-way xref.

* src/mcp_pcb_emcopilot/orchestrator.py — _select_analyzers appends
  the five new schematic analyzers (power_topology, protection,
  decoupling_per_ic, component_rating, three_way_xref) when
  design.schematic_components is non-empty. New _run_schematic_callable
  + _run_three_way_xref helpers dispatch the function-style analyzers.
  Ethernet skip predicate (Phase 3a fix) and finding-id auto-generation
  remain unchanged.

Tests: +43 new (test_schematic_dispatch 13, test_schematic_analyzers
24, test_phase4b_mcp_tools 7). Full suite 1564 passing / 0 failing /
11 skipped (+1 skip is the optional pdf-fixture path).

Tool surface 124 → 130, parity with dispatch branches intact.

Honest readout: deeper parser rewrites (KiCad sexpdata, Altium OLE
extension, full ORCAD/Pads netlist) remain deferred — they need real
fixture files. The auto-dispatch + degraded-mode analyzers cover the
typical-case meticulous review today.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ed CI

Closes the long-tail items from the meticulous-review buildout: a proper
S-expression KiCad parser, real ORCAD/Pads netlist support, the deferred
signal-flow analyzer, schematic test fixtures, Altium DNP/MPN parameter
mapping, plus the ruff/CI pinning that lets local + GitHub Actions agree
on lint output.

Issues closed by this commit: #117 #118 #119 #120 #121 #122 #123.
Follow-up #124 tracks the (pre-existing) mypy debt — typecheck job is
advisory until that lands.

Pinning + CI
------------
* pyproject.toml — ruff >= 0.9.0,<1.0 (knows UP045); sexpdata >= 1.0
  added to [dev] + [all] extras. New [sexpdata] extra for installs that
  only need the parser dependency.
* .github/workflows/ci.yml — lint job installs the pinned ruff via
  `pip install -e ".[dev]"` (was bare `pip install ruff`). Typecheck
  job marked `continue-on-error: true` until #124 closes.

Schematic fixture corpus (#118)
-------------------------------
* tests/fixtures/sample_2sheet.kicad_sch — hand-written hierarchical
  KiCad schematic, 4 components (R/C/C/U), MPN/Manufacturer/Datasheet
  properties, in_bom/on_board flags (one DNP cap), wires/labels/junctions.
* tests/fixtures/generate_schematic_pdf.py + sample_schematic.pdf — tiny
  hand-rolled PDF (985 bytes, no PyMuPDF dependency to generate) with an
  extractable text layer naming 7 components + 5 net labels.
* tests/fixtures/sample_netlist.NET — synthetic PSTXNET netlist, 7 parts
  + 5 nets, MPN/Manufacturer per part, per-net pin lists.
* tests/fixtures/sample.SchDoc — minimal OLE2 stub (1.5 kB) the
  schematic_dispatch.detect_format() path recognises as Altium.
* tests/conftest.py — pytest fixtures for each of the above.

KiCadSchematicParser rewrite (#119)
-----------------------------------
* parsers/schematic_parser.py — full sexpdata tree walker replaces the
  regex parser. Surfaces:
    - refdes, value, footprint, MPN, Manufacturer, Datasheet
    - in_bom / on_board → properties['dnp'], dnp_in_bom, dnp_on_board
    - per-component pins with x/y absolute coords
    - pin → net resolution via label-coordinate proximity to wires
    - hierarchical / global label nets
    - sub-sheet references in properties['lib_id']
* Falls back to the legacy regex parser when sexpdata isn't installed
  or the file is malformed — keeps the package usable without the
  optional dependency.

ORCAD/Pads netlist parser (#122)
--------------------------------
* parsers/netlist_parser.py — new module. Section-aware *PART* / *NET*
  reader, refdes + value + MPN + manufacturer per component, per-net
  pin lists with REFDES.PIN parsing.
* detect_netlist_dialect() distinguishes PSTXNET vs Pads via header
  sniff; both share the same section structure so one walker handles
  both.
* parsers/schematic_dispatch.parse_schematic_auto() now routes .net
  files through this proper parser, falling back to the regex stub
  only when the proper parser yields zero output.

Altium parameter records (#121, partial)
----------------------------------------
* parsers/altium_parser.py — AltiumComponent gains a `properties` dict.
  _parse_records now maps Parameter rows: DNP / DONOTPLACE / NOSTUFF →
  properties['dnp'] = True; COMPONENTCLASS / CLASS → properties
  ['component_class']; COMMENT/NOTE → properties['comment']; unknown
  parameter names stashed lower-cased so downstream analyzers can
  inspect Tolerance, Voltage, etc.
* Deeper sheet-symbol traversal + pin-net resolution remain deferred
  (need real .SchDoc fixtures — out of scope without licensed test
  data). #121 stays open with that scope clarified.

Signal-flow analyzer (#123)
---------------------------
* analyzers/schematic/signal_flow.py — three checks in one analyzer:
    1. Clock distribution: Y/X crystals + oscillator-class ICs +
       frequency-pattern values are sources; flag clock fan-out > 4
       unbuffered loads (HIGH); flag designs with ICs but no clock
       source (MEDIUM).
    2. Reset distribution: detect *RESET*/*RST*/*nRST* nets; flag
       no-supervisor-IC (MEDIUM); flag multi-IC-driver contention (HIGH).
    3. JTAG/SWD accessibility: detect TCK/TDI/TDO/TMS/SWCLK/SWDIO/SWO
       nets; flag missing debug connector / ICs without JTAG nets.
* analyzers/schematic/__init__.py exports the new function.
* New pcb_analyze_signal_flow MCP tool (130 → 131).
* Orchestrator wires it under sch_signal_flow; _select_analyzers
  appends it when schematic_components are attached.

Two mypy fixes from earlier phases
----------------------------------
* analyzers/schematic/_normalise.py — guard asdict() against the
  Type | Instance ambiguity is_dataclass() returns.
* standards/preflight.py — coerce markets to list[str] before iterating
  so the "Optional list" branch is type-safe.

Coverage / surface
------------------
* MCP tools: 130 → 131 (parity with dispatch branches confirmed).
* Tests: +51 new (test_kicad_schematic_parser 12, test_netlist_parser 12,
  test_signal_flow 11, test_altium_parameter_records 11, + parser
  smoke). Suite 1564 → 1611 passing / 0 failing / 11 skipped.
* Coverage 78.65 % (CI gate at 50 %).
* Ruff clean against the pinned 0.9+ config.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CI failed because the test imports were of the form
`from src.mcp_pcb_emcopilot...` which works locally (pytest's rootdir
walk picks up src/ as a package root) but breaks under `pip install -e .`
where the package is at the top level. Strip the src. prefix from all
imports in this file so the test collects everywhere.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous fix caught only top-of-file `from src.mcp_pcb_emcopilot...`
imports. Several tests build module paths as strings passed to
`importlib.import_module("src.mcp_pcb_emcopilot...")` — same root cause,
same fix: drop the `src.` prefix so installed-package layouts (CI) work.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Job-level `continue-on-error: true` lets the workflow finish but still
reports the job as FAILURE, leaving the PR in an UNSTABLE state. Moving
the flag onto the mypy step converts a non-zero exit into a successful
step, so the job — and the PR check — go green. mypy errors still
appear in the job log for #124 cleanup tracking.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes #124. mypy goes from 66 errors across 13 files to zero. The CI
typecheck job no longer needs `continue-on-error` — it's now a hard
gate alongside lint and tests.

By file
-------
* current_profiler.py (-41) — `profile.get("X", 0)` calls return `Any`
  through `Dict[str, Any]`; wrap each return / sleep_ua / quiescent_ua
  in `float(... or 0)`. Tighten `_COMPILED_PATTERNS` element type to
  `tuple["re.Pattern[str]", Dict[str, Any]]` so the matcher returns
  the proper profile dict.
* server.py (-8) — rename clashing locals (`detector` was shadowing
  `InterfaceDetector` import; `parser` was shadowing `STEPParser`;
  `summary` was shadowing the earlier handler's dict; `ran` /
  `candidates` were redefined across branches). Convert
  `ParsedBOMItem` instances to dicts via `asdict` when storing on
  `data.bom_items` (typed `list[dict]`). Annotate the simulation-
  candidate cache narrowing explicitly. Use `setattr` for the
  dynamic `_accepted_findings` attribute.
* review_context.py (-5) — typed getters returning `str`/`Optional[str]`
  were returning `Any` from `_answers.get`. Wrap with `str(val)` to
  narrow the type.
* orchestrator.py (-2) — annotate `cand` / `ant_cand` as
  `SimulationCandidate | None` explicitly so mypy doesn't infer
  `SimulationCandidate` from the outer-loop `cand` binding.
* impedance_validator.py (-2), rf_simulation_extractor.py (-2) — wrap
  layer-derived dielectric values in `float()` for explicit narrowing.
* gnss_analyzer.py (-2) — guard the `s["distance_mm"] < 8.0` check
  with an `isinstance(... (int, float))` to satisfy the `dict[str, Any]`
  union path.
* smps_loop_analyzer.py (-1) — defensive `if nearest_cap is None: continue`
  on the both-`None` branch so the `getattr(...).reference` access is
  type-safe.
* copper_pour_checker.py (-1) — `(net_name or "").upper()` to handle
  the `None` net case.
* schematic_dispatch.py (-1) — narrow `is_dataclass(obj)` to instance
  before calling `asdict()` (same fix as `_normalise.coerce`).

CI
--
* .github/workflows/ci.yml — drop `continue-on-error: true` from the
  mypy step. typecheck now blocks merges on type regressions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@RFingAdam RFingAdam merged commit 0c0fac7 into main May 19, 2026
5 checks passed
@RFingAdam RFingAdam deleted the feat/meticulous-review-phases-2-through-4c branch May 19, 2026 16:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant