Sync upstream improvements through d6823d5 - #1
Merged
Conversation
Adds part_search and part_fetch over ten providers, queried equally with no default and no fallback ordering. Merged hits are sorted by provider then part, which is not a relevance ranking, and each hit carries a ref of the form provider:part_id so a fetch names its source without the caller pairing an id with a provider by hand. Sources come in two kinds and the distinction is reported per hit. Library providers yield geometry that can be placed: the Altium and KiCad libraries already installed, an open registry, and the openly published KiCad libraries on GitHub. Catalogue providers yield part identity and a datasheet and no geometry at all: Digi-Key, Mouser, Nexar, element14 and TME. Learning that a distributor hit has no symbol after choosing the part is expensive, so kind travels with the result. Every catalogue endpoint was probed before being written down. Three further candidates answered 404 on the recalled URL and were dropped rather than shipped as plausible guesses. What probing could not establish without a paid credential is the response shape, so each catalogue publishes verified_live false and the parsers degrade a single hit on a renamed field instead of losing the whole search. Mouser answers an invalid API key with HTTP 200 and an error body. A client judging success by status code alone would report a rejected credential as a search that ran and matched nothing, so payload level error detection lives in the shared base rather than in five copies. No credential ships with the project. An unconfigured or rejected source raises ProviderUnavailable naming the environment variable it wants, because "the endpoint is gone" and "no such part exists" are different answers and only one is a reason to stop looking. GitHub is read through its documented API with a User-Agent naming this project, one recursive request per repository, and a week long disk cache. GitLab is left alone: its robots.txt disallows /api/v*, which is where KiCad's symbol repository lives, so symbols stay with the local reader.
Gen_GetErcViolations returned only a description string, so a violation arrived as a category and a sheet name. That is not actionable: the only safe response to "floating input pin, somewhere on this sheet" is to do nothing, because a NoERC marker placed by guesswork silently suppresses a real disconnection and is worse than the warning it clears. IViolation.DM_RelatedObjects carries the offending objects and was being discarded. Each violation now reports them with their kind, their document, and a cross probe string, which is what Altium itself uses to jump to an object and therefore identifies the specific pin or net. related_object_count is reported separately so a violation that exposes no objects stays distinguishable from one whose objects could not be read. Everything read from a related object is declared on IDMObject, the base interface every one of them implements, so no call here can hit the undeclared identifier fault that a narrower interface would risk. Verified on a live project: six object kinds across 27 violations, all with at least one related object. Also in this build: Lib_AddSymbolText places body text in a symbol, so imported text reaches Altium instead of being dropped. Selection is qualified by part id, so a multi part component keeps its parts addressable. PCB_ApplyDnpPasteExclusion removes paste from components marked do not populate. Restoring is refused rather than guessed at, because the applied state is not detectable after the fact. IEEE symbol name conversion moves to Utils with a self test covering the converters, so DelphiScript disagreement with Free Pascal shows up in Altium's own engine rather than at the point of use.
Independent implementations from the published format specs, so no third party converter is involved. Symbol, footprint, pads, drills, silkscreen, 3D model and metadata all convert. The Altium target returns an ordered plan of this server's own library tools rather than synthesizing Altium's binary formats, so parts are authored through the API. The two importers share one neutral geometry model and one Altium emitter, so they cannot drift apart. The KiCad reader handles what the standard library actually contains rather than the simple cases: derived symbols via extends, which are over half of it and would otherwise convert to a part with no pins; multi part components, which become one Altium symbol with per pin owner part ids in a single call instead of a flat merge; hidden pins, kept and kept hidden; DeMorgan body styles, one taken so pins are not duplicated; arcs recovered from start, mid and end form; active low and clock pin markers; and the closing edge of a filled outline, which fp_poly stores without repeating the final vertex and which leaves a notch in thousands of shipped footprints if consecutive pairs alone are walked. The s-expression reader is written here rather than depended on, so its escaping rules are verified rather than trusted. Geometry with no faithful equivalent is reported in warnings rather than quietly approximated. That includes the two cases that look correct after conversion and are not: a slotted drill emitted round, and an unplated hole emitted plated.
EDA_AGENT_TOOLSET=minimal, or --toolset minimal, advertises only tool_catalog and tool_invoke. Every other tool stays registered and reachable through them, so nothing is lost, but the advertised surface drops from 396 to 2. tool_catalog returns a filtered, classified index: by category, by maturity, by interaction, or by substring. tool_invoke then runs a tool by name. Filtering is the point, since an unfiltered index is larger than the surface it replaces. with_schema is available on the catalog and matters here: under the minimal toolset no tool schema is loaded up front, so calling tool_invoke without first asking for the schema means guessing argument names, and several tools take names that look obvious but are not. Both toolsets serve the same backend registration, so a tool cannot exist in one and be missing from the other.
A repair port is a label placed where a connection is missing. Drawing a real wire stub instead reads correctly on the sheet, but only when there is room: the stub must clear component bodies, and its glyph must not land on existing text. Both are checked before the upgrade, and a stub that cannot be placed safely stays a port rather than being drawn over something. Footprint policy and the design audit report what they could not check rather than passing silently, so a skipped check is visible in the result instead of looking like a clean pass. Adds tests for sheet loading, sheet listing, net verification, and the symbol limits the benchmark relies on.
The batch grammar separates fields with semicolons and records with a pair of tildes, so a value containing either silently splits into the wrong shape. payload_safe escapes those, and the importers scan for characters above the byte boundary the transport can carry, reporting them as a warning rather than writing text that arrives mangled. The scan lives in the shared plan builder rather than in one importer, so both the EasyEDA and KiCad paths get it and cannot drift apart. obj_batch_delete now enforces the same confirm_delete_all guard that obj_delete does. Deleting every object of a kind was reachable through the batch entry point without the confirmation the single object path requires. Recovery guidance names the steps to take rather than describing the fault, so a stalled polling loop can be cleared without reading the source. A dropped request is distinguishable from a slow one: the trace records whether Altium ever saw the request file at all.
Several tools ran a sequence and returned ok even when part of it did not take. A caller reading only the status had no way to tell a complete run from a partial one. The affected paths now report what succeeded and what did not, so a partial result is visible as a partial result. Fixes found while going through the tool surface: The net delete list was split on a comma, so a net whose name contains one was cut into pieces and the wrong nets were deleted. The variant filter accepted words that were never in its vocabulary and silently matched nothing, so a filter that looked applied did nothing. Moving library components in dry run mode reported the move as done. Printing all variants left the active variant changed rather than restoring it. The health check could not tell a stale deploy from a healthy one when the version was unreadable, so it reported a warning with a message that did not say what was wrong. Symbol export reported the selection it had changed rather than the one it was asked for.
The corpus test runs the reader over KiCad's own libraries rather than over hand written samples, which is what surfaced the cases that matter: derived symbols, multi part components, pin markers, and the unrepeated closing vertex in filled outlines. test_kicad_accepts_our_output goes the other way and feeds generated files back to KiCad, so the export is checked by the tool that has to read it rather than by this project's own parser agreeing with itself.
A fact stated in two places with nothing enforcing agreement drifts. These guards pin the pairs that had already drifted at least once. Published tool maturity was derived rather than measured, so 121 tools were advertised as simulator tested when the simulator rejects the commands they send. It is now measured by probing an absent action, and a catch all handler can no longer fake coverage. The README named tools that do not exist. Tool docstrings recommended identifiers that are not valid. Pipeline stage names in prose disagreed with the stage list in code. The mils to millimetres factor was written out in several places. Enumerated words crossing the bridge were checked against no vocabulary at all. Integration tests are gated at collection time on the environment variable rather than skipped inside a fixture, because a fixture that contacts Altium before deciding to skip has already contacted Altium. Each guard was checked by reintroducing the defect it exists to catch. Three of them passed against the broken code on the first attempt and were rewritten until they failed.
0.4.0 was already tagged and shipped, so the declared version had to move before anything here could be released. CI now fetches tags. The default shallow checkout brings none, and the check that compares the declared version against the tags had nothing to compare, so it passed without testing anything in the one place it runs unattended. Documents the ten part providers and the two kinds they come in, the library importers, the minimal toolset, and which sources answer without a credential. The tool reference is regenerated from the code rather than edited by hand, and a test fails if the committed copy falls behind.
Both asserted against KiCad's installed libraries, so they passed on a machine with KiCad and failed in CI, which has none. That tests the machine rather than the code, and it only shows up after a push. The no-network test now asserts the part that must hold anywhere: the fan-out completes and every source that failed says why, since a provider going quiet is indistinguishable from one reporting no match. The local provider answering is checked only where it is installed. The download behaviour it covered is now also tested against a fake with no download method, so it stays covered where KiCad is absent instead of skipping in the one place regressions go unnoticed.
The availability check called search with an empty string, and kicad_local returns early on an empty query before it ever looks for a library directory. So the probe reported success on a machine with no KiCad at all, the guard it fed was always true, and the two tests it was meant to protect failed in CI exactly as before. A non-empty query reaches the index and raises when no library directory exists, which is the condition the guard is asking about. Checked against both states this time, by forcing the symbol directory away rather than by trusting an environment variable: the probe returns false with no KiCad and true with it, and the two tests skip or pass accordingly. The previous fix verified the branches and never verified the predicate that selects them.
A plated via's copper sits between the finished hole and the drill wall, because the drill removes material and copper is deposited on the wall of the hole it leaves. The barrel area was computed for the annulus on the far side of that wall, r_drill to r_drill + t, which is copper in space the drill had already cleared. For the common 0.3 mm drill with 25 um plating this overstated the conducting cross-section by 18 percent, so thermal resistance came out low by the same factor: 163 K/W per via where the geometry gives 192. Every count derived from it was low too. A 2 W part with a 20 C budget was told 17 vias when it needs 20, and an array that looked adequate was not. The docstring already described the correct annulus, so only the arithmetic changed. Plating thicker than the radius closes the hole and the barrel becomes a solid cylinder; a copper-filled via is copper across the drilled hole, not beyond it. Test expectations are derived from the geometry rather than read back from the implementation.
A mechanical layer's KIND is what resolves it by purpose: courtyard checking, assembly drawings, 3D body placement and the IPC-4761 via treatments all read it, and a layer named "Courtyard Top" carrying no kind is skipped by every one of them. Nothing here could write it. Kinds come in two enums, which is why the first attempt failed. A single kind such as Fab Notes belongs to the layer. A paired kind is held by the layer PAIR under a shorter name with the side dropped, so "Component Outline Top" is stored as the pair kind "Component Outline" across two layers. Writing the layer property for a paired kind reads back unchanged and leaves the mapping stream empty. Three things follow from that and each cost a live measurement: AddPair appends without checking, so calling it on a pair that already exists leaves a duplicate. Repeated sweeps left one library with fourteen pairs where four were wanted, all visible in the Layer Stack Manager. Every join now drains existing pairs first, which also yields the index, since AddPair is the only call that reports one. A pair kind is exclusive the way a layer kind is, so the first attempt is refused while another layer still holds it. Releasing that layer and stopping there left the library with fewer kinds than it started with. The assignment is retried once the old holder is clear, and the released kinds are put back if it still will not take. Joining a pair renames both layers to Altium's own Top and Bottom keywords, so any name asked for in the same call is reapplied afterwards, for the partner as well as the target. Layers above 16 are reached through LayerUtils.MechanicalLayer, and tidy_pairs removes pairs that no kind justifies, bounded at layer 64 and reported rather than silent. lib_run_across runs one library command against many libraries inside Altium, failing per library so one that will not open does not abandon the rest. pcb_set_mech_layer_kind takes partner_layer, since it acts on one layer per call and could not otherwise pair; pcb_get_layer_display and pcb_set_layer_color expose the view settings, which follow the installation rather than the file.
Everything here was found by connecting to a running EasyEDA Pro and
watching what it actually does. None of it was visible offline,
because the fixtures had been written from the same assumptions as
the code and so agreed with it.
A schematic wire is a list of flat [x1, y1, x2, y2] SEGMENTS, not a
list of [x, y] points. Three call sites sent points, which is a list
of malformed segments, so create returned null and drew nothing. The
single wire, the bulk wires and the bus had none of them ever drawn
anything. The editor applies no transformation, so what a caller
sends is what the sheet gets, and wire geometry counts y downward
while sch_PrimitiveComponent counts it upward.
A refusal is not an empty result. The adapter read reply["result"] or
{} and ignored reply["error"], so design.snapshot refusing on a
schematic became a snapshot of nothing and review_design reported a
clean board with zero findings. It read violations past ran:false in
the same way, so an ERC whose reply said "This is NOT a clean result"
was reported as a clean ERC.
Tool registration and backend resolution had separate sources of
truth: registration took the backend as an argument while the
resolver re-read the environment. Anything embedding the registry
diverged, and review_design returned a complete, plausible review of
a different EDA's open document. An unrecognised backend name is now
refused rather than silently becoming Altium.
A JSON string where a list was declared hung the editor. list() over
a string yields its characters, so eight arguments reached a method
that never answers and the connection was dead for the whole timeout.
Search declared query optional and then always failed with the
default. Only lib_Symbol.search hangs on an empty query; the other
three answer with a default page. The second search argument turns
out to be a library uuid, and the ten-result cap is now reported
rather than left to look like a complete answer.
lib_Device.get omits the 3D model that search reports for the same
uuid, so a device read straight looked unmodelled. pcb_Document has
no autoRouting method at all: the API exposes importing routing
produced elsewhere, not running a router.
Re-importing the extension leaves the previous instance running with
its socket open. One editor held seven connections across three
builds, and which one answered a command was a coin toss, so a fix
verified against one build measured as absent minutes later. The
bridge now records each socket's build and retires superseded ones.
The extension could also wedge and never reconnect, because a health
probe with no timeout held the in-flight flag forever.
The EasyEDA test corpus is deleted rather than repaired. It was
written alongside the extension from shared assumptions and passed
green through every defect above. What replaces it is grounded in two
captures from a live editor: the API surface, 92 classes and 675
methods, and the shapes of 37 replies. Shapes only, never values.
Four files so far, each guard checked by reintroducing the bug it
exists to catch. The first found a call to a method the runtime does
not have.
The README had grown to 1031 lines and 18000 words, of which GitHub renders only the first part, so roughly half of it was invisible on the repository front page. Three sections carried more than half the file. The tool catalog is deleted rather than moved. It was a hand-curated copy of docs/TOOL_REFERENCE.md, which is generated from the code, so it was a second source of truth for a fact the code already states. It had drifted: the headers read 65 Library and 105 PCB while the code registered 67 and 108. The README now links the generated file, and the guard that compared the two is replaced by one that fails if per-section counts reappear. Part sourcing and the backend descriptions move to docs/PART_SOURCING.md and docs/BACKENDS.md, each with a short summary left behind. What remains answers what this is, whether it runs here, how to install it and what it cannot do, in 458 lines. Six guards read the README. They now read the documentation set, named once in tests/documentation_set.py, because a tool named in BACKENDS is the same promise to a reader as one named in the README and a guard that stops looking at it stops protecting it. Three of those guards were only found by running the suite. They build the path inline rather than naming a constant, so searching for the filename did not reveal them. Two of them were also scoped to nothing without saying so: both matched only the hand-written "### Name (N tools)" heading, while the generated reference writes "## library (67)". Their own vacuity checks caught it. A third compared against the altium and kicad surfaces only, so every easyeda tool read as undocumented once the set covered three backends. The list of canonical circuit blocks moved to the feature list rather than being dropped. It was only in the deleted table, and the generated reference truncates that docstring before the block names, so it would have left the documentation entirely.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
salitronic/eda-agentrepository throughd6823d5.Why
The fork and the original repository had diverged from common base
15c814a: this repository carried eight Altium-focused commits while upstream carried sixteen broader backend, import, provider, documentation, and reliability commits. This merge retains both histories and integrates their overlapping behavior.During integration, the combined suite exposed several cross-branch defects: Windows drive-list parsing under WSL, duplicate UNC path unescaping, stale backend-specific discipline anchors, inaccurate tool metadata, and an async bridge polling path that could strand executor completions under WSL. These were corrected as part of the merge.
Impact
The project moves to package version
0.5.0and gains the upstream backends and sourcing/import workflows without dropping the fork's Altium-specific improvements. The merge changes 255 files with 60,915 insertions and 1,793 deletions.Validation
4050 passed, 107 skippedin the full pytest suite