@huaqiu/dsh-hqsch 0.4.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +43 -0
  3. package/cordis.patch.yml +13 -0
  4. package/lib/index.d.mts +98 -0
  5. package/lib/index.mjs +159 -0
  6. package/package.json +40 -0
  7. package/skills/hqsch-scenario-b/AGENTS.md +209 -0
  8. package/skills/hqsch-scenario-b/CLAUDE.md +209 -0
  9. package/skills/hqsch-scenario-b/INSTALL.md +140 -0
  10. package/skills/hqsch-scenario-b/SKILL.md +107 -0
  11. package/skills/hqsch-scenario-b/SYSTEM-PROMPT.md +209 -0
  12. package/skills/hqsch-scenario-b/docs/circuit-pattern-layout.md +304 -0
  13. package/skills/hqsch-scenario-b/docs/editing-a-circuit.md +167 -0
  14. package/skills/hqsch-scenario-b/docs/part-search.md +244 -0
  15. package/skills/hqsch-scenario-b/docs/placement-conventions.md +714 -0
  16. package/skills/hqsch-scenario-b/docs/property-conventions.md +225 -0
  17. package/skills/hqsch-scenario-b/docs/quickstart.md +99 -0
  18. package/skills/hqsch-scenario-b/docs/reading-a-circuit.md +317 -0
  19. package/skills/hqsch-scenario-b/docs/rpc/canvas-auto-connect-objects-by-id.md +98 -0
  20. package/skills/hqsch-scenario-b/docs/rpc/canvas-delete-objects-by-ids.md +94 -0
  21. package/skills/hqsch-scenario-b/docs/rpc/canvas-find-object-by-property.md +101 -0
  22. package/skills/hqsch-scenario-b/docs/rpc/canvas-list-wire-segments.md +94 -0
  23. package/skills/hqsch-scenario-b/docs/rpc/canvas-mirror-objects-by-ids.md +96 -0
  24. package/skills/hqsch-scenario-b/docs/rpc/canvas-rotate-objects-by-ids.md +94 -0
  25. package/skills/hqsch-scenario-b/docs/rpc/canvas-set-object-property.md +99 -0
  26. package/skills/hqsch-scenario-b/docs/rpc/canvas-zoom-all.md +92 -0
  27. package/skills/hqsch-scenario-b/docs/rpc/find-find-object.md +96 -0
  28. package/skills/hqsch-scenario-b/docs/rpc/kernel-get-snapshot.md +104 -0
  29. package/skills/hqsch-scenario-b/docs/rpc/netlist-get-active-page-net-list.md +87 -0
  30. package/skills/hqsch-scenario-b/docs/rpc/netlist-get-port-pairing.md +34 -0
  31. package/skills/hqsch-scenario-b/docs/rpc/obj-place-place-pin-stub-wire-and-net-alias.md +114 -0
  32. package/skills/hqsch-scenario-b/docs/rpc/pattern-layout-apply-circuit-pattern.md +125 -0
  33. package/skills/hqsch-scenario-b/docs/rpc/pattern-layout-get-page-occupancy.md +100 -0
  34. package/skills/hqsch-scenario-b/docs/rpc/pattern-layout-list-circuit-patterns.md +97 -0
  35. package/skills/hqsch-scenario-b/docs/rpc/placement-list-part-libraries.md +94 -0
  36. package/skills/hqsch-scenario-b/docs/rpc/placement-list-symbol-libraries.md +94 -0
  37. package/skills/hqsch-scenario-b/docs/rpc/placement-place-kicad-symbol.md +105 -0
  38. package/skills/hqsch-scenario-b/docs/rpc/placement-place-part-from-library.md +106 -0
  39. package/skills/hqsch-scenario-b/docs/rpc/placement-place-symbol-from-library.md +108 -0
  40. package/skills/hqsch-scenario-b/docs/rpc/project-get-active-project.md +96 -0
  41. package/skills/hqsch-scenario-b/docs/rpc/project-list-open-projects.md +99 -0
  42. package/skills/hqsch-scenario-b/docs/rpc-availability.md +97 -0
  43. package/skills/hqsch-scenario-b/docs/rpc-index/canvas.md +81 -0
  44. package/skills/hqsch-scenario-b/docs/rpc-index/find.md +36 -0
  45. package/skills/hqsch-scenario-b/docs/rpc-index/kernel.md +36 -0
  46. package/skills/hqsch-scenario-b/docs/rpc-index/netlist.md +38 -0
  47. package/skills/hqsch-scenario-b/docs/rpc-index/obj-place.md +76 -0
  48. package/skills/hqsch-scenario-b/docs/rpc-index/pattern-layout.md +38 -0
  49. package/skills/hqsch-scenario-b/docs/rpc-index/placement.md +48 -0
  50. package/skills/hqsch-scenario-b/docs/rpc-index/project.md +47 -0
  51. package/skills/hqsch-scenario-b/docs/serialization.md +60 -0
  52. package/skills/hqsch-scenario-b/docs/skill-dependencies.md +93 -0
  53. package/skills/hqsch-scenario-b/skill.json +13 -0
  54. package/skills/hqsch-scenario-b/template/package.json +16 -0
  55. package/skills/hqsch-scenario-b/template/scripts/edit-property-by-ref.ts +44 -0
  56. package/skills/hqsch-scenario-b/template/scripts/fix-designators.ts +52 -0
  57. package/skills/hqsch-scenario-b/template/scripts/hello.ts +46 -0
  58. package/skills/hqsch-scenario-b/template/scripts/read-circuit.ts +357 -0
  59. package/skills/hqsch-scenario-b/template/scripts/replace-part-by-ref.ts +44 -0
  60. package/skills/hqsch-scenario-b/template/scripts/rewire-pin-by-ref.ts +42 -0
  61. package/skills/hqsch-scenario-b/template/tsconfig.json +11 -0
  62. package/src/config.ts +25 -0
  63. package/src/index.ts +186 -0
  64. package/src/paths.ts +48 -0
@@ -0,0 +1,225 @@
1
+ # Object Property Key Conventions (HQSCH)
2
+
3
+ ## Objective
4
+
5
+ Map common schematic editing terms (designator, value, footprint) to the
6
+ `propKey` strings required by `CanvasOpsService` property RPCs. The protobuf
7
+ schema only declares `propKey` as a generic `string`; HQSCH uses
8
+ vendor-specific key names that differ between **find** and **read/write**
9
+ operations.
10
+
11
+ ## Property Key Reference
12
+
13
+ | Business term | Read / write (`GetObjectProperty`, `SetObjectProperty`) | Find (`FindObjectByProperty`) | Notes |
14
+ | --- | --- | --- | --- |
15
+ | Designator (位号) | `Part Reference` | `Reference` | **Always use `Part Reference` when setting the designator.** `Reference` and `Designator` do not work for get/set. |
16
+ | Net label (电源/全局符号) | `Name` | `Name` | Power symbols (VCC, GND, VCC_5V) store the net label in `Name`. **Part Reference is often empty** — do not use `FindObjectByProperty("Reference", "VCC_5V")`. |
17
+ | Value (值) | `Value` | — | Component value (e.g. `10k`, `NCE30P50G`). |
18
+ | Footprint (封装) | `Footprint` or `pcbFootprint` | — | Prefer reading back with `GetObjectProperty` to confirm the active key on a given object. |
19
+ | Description | `Description` | — | Free-text description when present. |
20
+ | MPN | `MPN` | — | Manufacturer part number in symbol properties. |
21
+
22
+ ## Find vs Set Asymmetry
23
+
24
+ HQSCH maps designator lookup and designator mutation to **different keys**:
25
+
26
+ 1. **Locate** a part by designator → `FindObjectByProperty` with
27
+ `propKey = "Reference"`, `propValue = "Q31"`.
28
+ 2. **Read or change** the designator → `GetObjectProperty` /
29
+ `SetObjectProperty` with `propKey = "Part Reference"`.
30
+
31
+ Do not assume the key used for find is valid for set.
32
+
33
+ ## Response Shape Notes
34
+
35
+ | API | Field | Correct usage |
36
+ | --- | --- | --- |
37
+ | `FindObjectByProperty` | `objectIds` | Repeated field — use `objectIds[0]`, not `objectId`. |
38
+ | `SetObjectProperty` | `op` | Use `OBJ_PROP_SET` (value `1`) for replacement. |
39
+ | `SetObjectProperty` | `commitUndo` | Set `true` to record the edit on the editor undo stack. |
40
+
41
+ ## Rename Designator Workflow
42
+
43
+ 1. `project-get-active-project` → build `ProjectContext`.
44
+ 2. `canvas-find-object-by-property` — `propKey: "Reference"`, `propValue: "<old>"`.
45
+ 3. Take `objectIds[0]` from the response.
46
+ 4. Optional: `canvas-get-object-property` — `propKey: "Part Reference"` to confirm current value.
47
+ 5. `canvas-set-object-property` — `propKey: "Part Reference"`, `propValue: "<new>"`, `op: OBJ_PROP_SET`, `commitUndo: true`.
48
+ 6. Verify with another `GetObjectProperty` or `FindObjectByProperty`.
49
+
50
+ ## Pin Coordinates and Wiring
51
+
52
+ After placement, **always save `response.object_id`** from
53
+ `PlacePartFromLibrary` / `PlaceSymbolFromLibrary` / `PlaceKicadSymbol`.
54
+ Use that id for wiring — not designator strings alone.
55
+
56
+ ### Wiring priority
57
+
58
+ | Priority | Skill | When to use |
59
+ | --- | --- | --- |
60
+ | **1 (preferred)** | `canvas-auto-connect-objects-by-id` | You have both placement `object_id`s (works for C1 ↔ VCC/GND power symbols). |
61
+ | **2** | `obj-place-place-wire` | After `kernel-get-snapshot` pin lookup via `canvas_object_id`. |
62
+ | **3** | `canvas-auto-connect-by-part-reference` | Both endpoints are regular parts with stable `Reference` designators (R/C/U). |
63
+ | **Avoid** | Estimated coords (placement Y ± offset) | Never guess pin positions — wires will miss pins. |
64
+
65
+ For nets with **three or more nodes**, also read
66
+ [`placement-conventions.md` — Schematic wire routing (avoid overlap)](placement-conventions.md#schematic-wire-routing-avoid-overlap):
67
+ separate branch lanes at placement and chain-connect (do not hub from one anchor).
68
+
69
+ ### Option 1 — AutoConnect by object id (recommended)
70
+
71
+ Uses the same `object_id` returned by placement RPCs directly — no snapshot
72
+ lookup required:
73
+
74
+ ```typescript
75
+ // After placing C1, VCC_5V, GND — save each object_id
76
+ await getSkill("canvas-auto-connect-objects-by-id")!.execute(ctx, {
77
+ context: projectCtx,
78
+ objectId1: c1ObjectId,
79
+ pinNum1: [1],
80
+ objectId2: vccObjectId,
81
+ pinNum2: [0], // power / GND / port / off-page symbol → always 0
82
+ });
83
+
84
+ await getSkill("canvas-auto-connect-objects-by-id")!.execute(ctx, {
85
+ context: projectCtx,
86
+ objectId1: c1ObjectId,
87
+ pinNum1: [2],
88
+ objectId2: gndObjectId,
89
+ pinNum2: [0], // power / GND / port / off-page symbol → always 0
90
+ });
91
+ ```
92
+
93
+ Proto fields: `object_id_1`, `pin_num_1` (repeated int32), `object_id_2`,
94
+ `pin_num_2`.
95
+
96
+ **Symbol pin numbers:** when either endpoint is a **power / GND / hierarchical
97
+ port / off-page** symbol (`PlaceSymbolFromLibrary`), pass **`pinNum: [0]`** for
98
+ that side. Do not use `1` — the engine treats symbol connection points as pin `0`.
99
+ Regular parts use their printed pin numbers.
100
+
101
+ ### Option 2 — PlaceWire after snapshot pin lookup
102
+
103
+ Pin positions come from **`kernel-get-snapshot`**, not property RPCs.
104
+
105
+ **Primary lookup:** `canvas_object_id` — same value as placement
106
+ `object_id`. Do not rely on `designator` or `Name` alone (power symbols have
107
+ empty Part Reference; duplicate names may exist on one page).
108
+
109
+ ```typescript
110
+ const PAGE_TOP = 1400; // PAGE_SIZE_START_Y — page top canvas anchor
111
+
112
+ function canvasPinToExt(x: bigint, y: bigint) {
113
+ return { x: Number(x), y: PAGE_TOP - Number(y) };
114
+ }
115
+
116
+ function getPinExtByObjectId(
117
+ snap: KernelSnapshot,
118
+ objectId: bigint,
119
+ pinNum: string,
120
+ ): { x: number; y: number } | null {
121
+ const oid = objectId.toString();
122
+ const si = snap.symbolInstances?.find(
123
+ (s) => String(s.canvasObjectId) === oid,
124
+ );
125
+ if (!si) return null;
126
+ const defId = si.definitionId;
127
+ const symDef = snap.symbolDefinitions?.find((sd) => sd.id === defId);
128
+ const pinInst = snap.pinInstances?.find((p) => {
129
+ if (String(p.canvasObjectId) !== oid) return false;
130
+ const def = symDef?.pins?.find((pd) => pd.id === p.pinDefinitionId);
131
+ return def?.number === pinNum;
132
+ });
133
+ if (!pinInst?.position) return null;
134
+ return canvasPinToExt(pinInst.position.x!, pinInst.position.y!);
135
+ }
136
+ ```
137
+
138
+ Workflow:
139
+
140
+ 1. `PlacePartFromLibrary` / `PlaceSymbolFromLibrary` → save `response.object_id`.
141
+ 2. `kernel-get-snapshot`.
142
+ 3. `symbolInstances.find(s => s.canvasObjectId === object_id)`.
143
+ 4. `pinInstances` filtered by `canvasObjectId === object_id` and pin number via
144
+ `symbolDefinitions[].pins[].number`.
145
+ 5. Read `pinInstances[].position` (canvas coordinates, Y **up**), convert, then
146
+ `obj-place-place-wire`.
147
+
148
+ **Fallback (human-readable only):** match `symbolInstances[].designator` (e.g.
149
+ `Q31`) when `canvas_object_id` is unavailable — not reliable for power symbols.
150
+
151
+ ```text
152
+ extY = PAGE_SIZE_START_Y - canvasY // PAGE_SIZE_START_Y ≈ 1400 (page top anchor)
153
+ ```
154
+
155
+ `obj-place-place-wire` (`PlaceWire`) uses the **same external display
156
+ coordinates** as placement RPCs — page top-left origin, Y **down**. The server
157
+ converts internally (`canvasY = PAGE_SIZE_START_Y - extY`).
158
+
159
+ ```typescript
160
+ // geometry.start / geometry.end — external display coords
161
+ await client.objPlace.placeWire({
162
+ context: projectCtx,
163
+ geometry: {
164
+ start: { x: BigInt(extA.x), y: BigInt(extA.y) },
165
+ end: { x: BigInt(extB.x), y: BigInt(extB.y) },
166
+ },
167
+ snapToGrid: true,
168
+ });
169
+ ```
170
+
171
+ Use orthogonal segments (horizontal then vertical) when pins are not aligned.
172
+
173
+ **Do not use** `canvas-auto-connect-by-part-reference-single-pin` for power
174
+ symbols — their `Part Reference` is empty so `partRef2: "VCC_5V"` will not
175
+ resolve. Use `canvas-auto-connect-objects-by-id` instead.
176
+
177
+ **Common mistake:** passing raw `pinInstances[].position` (canvas, Y-up) directly
178
+ to `PlaceWire` — wire placement will fail or land off-grid. Always convert first.
179
+
180
+ **Common mistake:** locating power-symbol pins by `FindObjectByProperty("Reference",
181
+ "VCC_5V")` or by snapshot `designator` — use placement `object_id` →
182
+ `canvas_object_id` instead.
183
+
184
+ **Common mistake:** using placement center coordinates ± offset as pin endpoints.
185
+
186
+ ## Kernel Snapshot Lag
187
+
188
+ `GetSnapshot` may lag the canvas immediately after placement — newly placed
189
+ objects may be missing from `symbolInstances` / `pinInstances` / `labels`, or
190
+ `canvas_object_id` may not yet be populated. Mitigations:
191
+
192
+ 1. **Prefer `canvas-auto-connect-objects-by-id`** — does not require snapshot.
193
+ 2. If using snapshot pin lookup, **retry** `kernel-get-snapshot` (e.g. 3–5 times,
194
+ 200–500 ms apart) before falling back.
195
+ 3. For designator state after `SetObjectProperty`, use `GetObjectProperty` /
196
+ `FindObjectByProperty` — not snapshot `designator`.
197
+
198
+ **Enumerating unknown port / NetAlias names:** use `GetSnapshot` —
199
+ `labels[].text` for net aliases, and `symbolInstances[].designator` for
200
+ hierarchical ports / off-page / power. Each also carries owning page via
201
+ `schematic_name` / `page_name` (top-level after SDK regen, or
202
+ `metadata.properties` keys `schematic_name` / `page_name` today).
203
+ **Do not** guess candidate names and poll `FindObjectByProperty(Name=…)`.
204
+
205
+ ## Required Capabilities
206
+
207
+ | Capability | Purpose |
208
+ | --- | --- |
209
+ | `project-get-active-project` | Resolve active project / `ProjectContext`. |
210
+ | `canvas-find-object-by-property` | Locate objects by designator (`Reference`) or net label (`Name`). |
211
+ | `canvas-get-object-property` | Read property values (`Part Reference`, `Value`, …). |
212
+ | `canvas-set-object-property` | Write property values (`Part Reference`, `Value`, …). |
213
+ | `canvas-auto-connect-objects-by-id` | Wire two placed objects by `object_id` + pin numbers. |
214
+ | `kernel-get-snapshot` | Pin positions via `canvas_object_id`, net graph. |
215
+ | `obj-place-place-wire` | Place wire segments between converted pin coordinates. |
216
+
217
+ ## Critical Guardrails
218
+
219
+ - Never use `Reference` as `propKey` in `SetObjectProperty` for designator changes.
220
+ - Never use `JSON.stringify()` on protobuf responses — use `toJsonString()` from `@huaqiu/hqeda`.
221
+ - When `FindObjectByProperty` returns multiple `objectIds`, disambiguate with
222
+ `objTypeFilter` or additional property constraints before mutating.
223
+ - Report designators exactly as shown on the schematic (e.g. `Q31`, `U1`).
224
+ - Never invent candidate port/NetAlias names to poll `FindObjectByProperty` —
225
+ enumerate via `GetSnapshot` (`labels` / `symbolInstances`) first.
@@ -0,0 +1,99 @@
1
+ # HQ EDA Skill Quickstart
2
+
3
+ End-to-end chain for running a generated skill against a live HQ EDA editor.
4
+
5
+ ## Prerequisites
6
+
7
+ - HQ EDA desktop runtime running locally
8
+ - Node.js 18+
9
+ - Packages: `@huaqiu/hqeda` and `@huaqiu/huaqiu-client`
10
+
11
+ ```bash
12
+ npm install @huaqiu/hqeda @huaqiu/huaqiu-client
13
+ ```
14
+
15
+ ## Step 1 — Discover running editors
16
+
17
+ ```typescript
18
+ import { listEditors } from "@huaqiu/huaqiu-client";
19
+
20
+ const editors = await listEditors();
21
+ console.log(editors);
22
+ // [{ instanceId: "...", pid: 12345, ... }, ...]
23
+ ```
24
+
25
+ When multiple editors are running, pass `instanceId` explicitly to `connect()`.
26
+
27
+ ## Step 2 — Connect
28
+
29
+ ```typescript
30
+ import { connect } from "@huaqiu/huaqiu-client";
31
+
32
+ const client = await connect();
33
+ // Or target a specific instance:
34
+ // const client = await connect({ instanceId: editors[0].instanceId });
35
+ ```
36
+
37
+ `connect()` returns an `EditorClient` with service accessors (`client.project`, `client.edaBom`, `client.canvasOps`, …).
38
+
39
+ ## Step 3 — Build context (when required)
40
+
41
+ Many RPCs require a `context` field. Build it from the connected client:
42
+
43
+ ```typescript
44
+ // Editor-level context (project listing, active project discovery)
45
+ const editorContext = client.createEditorContext();
46
+
47
+ // Project-scoped context (canvas, selection, BOM for one project)
48
+ const projectContext = client.createProjectContext("my-project");
49
+ ```
50
+
51
+ Obtain `projectId` via [`project-get-active-project`](../project/get-active-project/SKILL.md) or [`project-list-open-projects`](../project/list-open-projects/SKILL.md).
52
+
53
+ ## Step 4 — Execute a skill
54
+
55
+ ```typescript
56
+ import { getSkill, toJsonString } from "@huaqiu/hqeda";
57
+
58
+ const skill = getSkill("bom-get-bom");
59
+ if (!skill) throw new Error("Skill not found: bom-get-bom");
60
+
61
+ const result = await skill.execute(
62
+ { client },
63
+ { projectId: "my-project" },
64
+ );
65
+
66
+ console.log(toJsonString(result, { prettySpaces: 2 }));
67
+ ```
68
+
69
+ ## Step 5 — Serialize responses
70
+
71
+ Always use `toJsonString()` or `toJson()` from `@huaqiu/hqeda` — never `JSON.stringify()` directly on protobuf messages. See [serialization.md](serialization.md).
72
+
73
+ ## Minimal complete script
74
+
75
+ ```typescript
76
+ import { connect } from "@huaqiu/huaqiu-client";
77
+ import { getSkill, toJsonString } from "@huaqiu/hqeda";
78
+
79
+ async function main() {
80
+ const client = await connect();
81
+ const active = await client.project.getActiveProject({
82
+ context: client.createEditorContext(),
83
+ });
84
+ const projectId = active.project?.projectId;
85
+ if (!projectId) throw new Error("No active project");
86
+
87
+ const skill = getSkill("bom-get-bom");
88
+ const bom = await skill!.execute({ client }, { projectId });
89
+ console.log(toJsonString(bom, { prettySpaces: 2 }));
90
+ }
91
+
92
+ main().catch(console.error);
93
+ ```
94
+
95
+ ## Install a single skill (Agent)
96
+
97
+ ```bash
98
+ npx skills add Huaqiu-Electronics/skills --path eda/bom/get-bom
99
+ ```
@@ -0,0 +1,317 @@
1
+ # Reading a circuit (understanding an existing design)
2
+
3
+ Use this when the task is **read-only understanding**, not drawing: "what does
4
+ VR201 do?", "how is this powered?", "why is this net named X?".
5
+
6
+ The goal is to reconstruct the **electrical topology plus device semantics**
7
+ without hand-parsing wire geometry or guessing.
8
+
9
+ ## The one call that does most of the work
10
+
11
+ `kernel.GetSnapshot({ context })` returns a **whole-project** snapshot in a
12
+ single unary RPC (~1.6 s for 160 parts / 602 wires / 181 nets). It is the
13
+ primary data source for circuit understanding.
14
+
15
+ Measured scope: snapshot nets (181) == `netList.GetProjectNetList` nets (181),
16
+ while the current page held only 132 nets. So **snapshot is project-wide, not
17
+ page-scoped** — you do not need to walk pages.
18
+
19
+ | Entity | Count here | What it gives you |
20
+ | --- | --- | --- |
21
+ | `symbolInstances` | 160 | designator, definitionId, position, rotation, mirrored, **metadata.properties** — includes parts, power globals, **hierarchical ports**, and **off-page connectors** (`designator` = Name) |
22
+ | `pinInstances` | 612 | `symbolInstanceId`, `pinDefinitionId`, **absolute position** |
23
+ | `nets` | 181 | `name`, `netClass`, `pinInstanceIds[]` |
24
+ | `symbolDefinitions` | 58 | `name`, **`pins[]` with `number` + `name`** (pin-number → pin-name table) |
25
+ | `wireSegments` | 602 | geometry (rarely needed for semantics) |
26
+ | `labels` | (when used) | NetAlias instances: `text` (= name), `position`, `netId`, `metadata.id` (= canvas dbId when present) |
27
+ | `junctions` | (when used) | wire junctions |
28
+ | `noConnects` | (when used) | no-connect markers |
29
+
30
+ ### Enumerating ports / net labels (do not guess names)
31
+
32
+ **Never** invent candidate names and poll `FindObjectByProperty(Name=…)`. Use one snapshot:
33
+
34
+ ```typescript
35
+ const snap = await client.kernel.getSnapshot({ context: ctx });
36
+ const s = snap.snapshot!;
37
+
38
+ // Net aliases — text + owning schematic/page
39
+ const labels = (s.labels ?? []).map((l) => ({
40
+ name: l.text,
41
+ schematic: l.schematicName
42
+ ?? l.metadata?.properties?.find((p) => p.key === "schematic_name")?.stringValue
43
+ ?? "",
44
+ page: l.pageName
45
+ ?? l.metadata?.properties?.find((p) => p.key === "page_name")?.stringValue
46
+ ?? "",
47
+ }));
48
+
49
+ // Ports / off-page / power — designator + schematic/page (same property keys)
50
+ const ports = (s.symbolInstances ?? []).map((x) => ({
51
+ name: x.designator,
52
+ schematic: x.schematicName
53
+ ?? x.metadata?.properties?.find((p) => p.key === "schematic_name")?.stringValue
54
+ ?? "",
55
+ page: x.pageName
56
+ ?? x.metadata?.properties?.find((p) => p.key === "page_name")?.stringValue
57
+ ?? "",
58
+ }));
59
+ ```
60
+
61
+ `Label` / `SymbolInstance` proto fields: `schematic_name`, `page_name`.
62
+ Until the published SDK regenerates those top-level fields, they are also present as
63
+ `metadata.properties` with keys `schematic_name` / `page_name`.
64
+
65
+ `FindObjectByProperty` is only for **known** names after this enumeration.
66
+
67
+ ### Cross-page / cross-hierarchy port pairing (do not guess)
68
+
69
+ Different page ports can share one design net **even when names differ**
70
+ (e.g. `/TYPE-C` `USB_D+` ↔ `/ESP32-C3FH4` `GPIO18`). That comes from hierarchical
71
+ block-pin ↔ port merge on the parent sheet — **not** from GUID matching.
72
+
73
+ **Never** invent same-name rules for this case. Call:
74
+
75
+ ```typescript
76
+ const pairing = await client.netList.getPortPairing({});
77
+ for (const p of pairing.pairings ?? []) {
78
+ console.log(
79
+ `${p.portA?.occPath} ${p.portA?.pageName}/${p.portA?.name}` +
80
+ ` ↔ ${p.portB?.occPath} ${p.portB?.pageName}/${p.portB?.name}` +
81
+ ` on ${p.netName} rule=${p.rule}`
82
+ );
83
+ }
84
+ ```
85
+
86
+ | `rule` | Meaning |
87
+ | --- | --- |
88
+ | `BY_NAME` | Same-name Off-page / Hierarchical Port within a schematic folder |
89
+ | `BY_HIERARCHY` | Parent hierarchical block pin joins child ports (may be **different names**) |
90
+ | `BY_GLOBAL` | Design-wide Power / Global name merge |
91
+
92
+ Recommended flow: `GetSnapshot` (enumerate ports) → `GetPortPairing` (cross-page edges)
93
+ → `GetProjectNetList` only if you need pin membership of the merged net.
94
+
95
+ ### Reconstructing pin → net (verified recipe)
96
+
97
+ Three joins give the full topology. This was verified end-to-end against VR201:
98
+
99
+ ```typescript
100
+ const snap = await client.kernel.getSnapshot({ context: ctx });
101
+ const s = snap.snapshot!;
102
+
103
+ // 1. designator → instance
104
+ const sym = s.symbolInstances.find((x) => x.designator === "VR201");
105
+ const symId = sym.metadata!.id; // e.g. "/-74618591"
106
+
107
+ // 2. pin-number → pin-name (from the symbol definition)
108
+ const def = s.symbolDefinitions.find((d) => d.name === sym.definitionId);
109
+ const pinName = new Map(def!.pins.map((p) => [p.number, p.name]));
110
+ // → 1=OUTPUT 2=SENSE 3=GND 4=SHDN# 5=VIN
111
+
112
+ // 3. pin instance → net
113
+ const netOf = new Map<string, string>();
114
+ for (const net of s.nets)
115
+ for (const pid of net.pinInstanceIds) netOf.set(pid, net.name);
116
+
117
+ // 4. join
118
+ for (const p of s.pinInstances.filter((x) => x.symbolInstanceId === symId)) {
119
+ const pn = p.metadata!.id; // "/-74618591-1"
120
+ const num = p.pinDefinitionId; // "1"
121
+ console.log(`pin${num} ${pinName.get(num)} → ${netOf.get(pn) ?? "(unconnected)"} @${p.position}`);
122
+ }
123
+ ```
124
+
125
+ Output (matches the netlist exactly, and **additionally** shows the
126
+ unconnected pin and the physical coordinates):
127
+
128
+ ```
129
+ pin1 OUTPUT → +3.3V @(1890,670)
130
+ pin2 SENSE → +3.3V @(1890,660)
131
+ pin3 GND → GND @(1890,650)
132
+ pin4 SHDN# → (unconnected) @(1770,670)
133
+ pin5 VIN → N0261718 @(1770,660)
134
+ ```
135
+
136
+ Key advantages over `netList`:
137
+
138
+ - **Unconnected pins are visible.** `pin4 SHDN#` shows up here; the netlist
139
+ only lists pins that belong to a net, so floating pins disappear.
140
+ - **Absolute pin coordinates**, in kernel canvas coordinates (Y up).
141
+ - **`rotation` / `mirrored`** for each instance.
142
+
143
+ > Beware BigInt: `position.x/y` are `bigint`. `JSON.stringify` throws
144
+ > "Do not know how to serialize a BigInt" — pass a replacer:
145
+ > `JSON.stringify(v, (_k, x) => typeof x === "bigint" ? String(x) : x)`.
146
+
147
+ ## Device semantics are already in the snapshot — do not fetch datasheets
148
+
149
+ `symbolInstance.metadata.properties` carries a rich electrical parameter set
150
+ harvested from the part database. **Read it before going online.**
151
+
152
+ Real dump for `VR201`:
153
+
154
+ ```
155
+ Datasheet = http://file.elecfans.com/.../pYYBAGDuZ16ARrigAALCJORq5X4437.pdf
156
+ Description = Micropower Low Dropout Regulator with Shutdown,
157
+ 4.15 to 30 V Vin, 5 V Vout, 5-pin FM (T-5), 0 to 125 degC
158
+ MPN = LT1129CT-5#06PBF
159
+ Manufacturer = Analog Devices
160
+ dropout_voltage = 450 mV
161
+ input_voltage = 30 V, 4.15 V
162
+ output_voltage = 5 V
163
+ output_current = 700 mA
164
+ supply_current = 50 μA
165
+ power_supply_rejection_ratio = 64 dB
166
+ topology = 正,固定式
167
+ features = Enable
168
+ operating_temperature = 0 °C, 125 °C
169
+ package = TO-220-5
170
+ huaqiu_pn = G7258042
171
+ ```
172
+
173
+ For `U202` (MAX202ESE+T) the same mechanism yields `interface=RS-232`,
174
+ `data_rate=120 kb/s`, `driver_receiver=2/2`, `supply_voltage=5.5 V`, etc.
175
+
176
+ **Consequence:** an "is this part appropriate for its net?" check is fully
177
+ local. Comparing `output_voltage = 5 V` against the net name `+3.3V` surfaces
178
+ the mismatch with zero network access.
179
+
180
+ Property keys vary by part family (a regulator exposes `topology` /
181
+ `dropout_voltage`; a transceiver exposes `interface` / `data_rate`). Treat the
182
+ set as open — read the keys, do not assume a fixed schema.
183
+
184
+ ## Which comprehension interfaces actually work
185
+
186
+ **Canonical map (works / ban / hollow):** [rpc-availability.md](./rpc-availability.md).
187
+
188
+ Summary: **Snapshot + `netList` is the whole toolbox for comprehension.** Do not
189
+ build on `GetConnectivity`, selection, ERC, export, runtime, or `FindNet`
190
+ (silent `success:false`). Advanced helpers (`GetObjectsJsonByIds`, `FindObject`,
191
+ `GetSelectionNetList`) are optional — not required for Flow B.
192
+
193
+ Timing baseline: `GetSnapshot` ~1.6–3.2 s for a mid-size project; do not treat
194
+ that as a hang.
195
+
196
+ ## Project identification — do not assume the project
197
+
198
+ `getActiveProject` returns whichever schematic is focused. The project can
199
+ change between calls (the user may switch tabs, or the editor may restart).
200
+ Always read `projectId` from the response and print it, rather than reusing a
201
+ cached one.
202
+
203
+ Note the active project is identified by **file path** as the id, e.g.
204
+ `C:/Users/Administrator/Desktop/kit-dev-coldfire.HQSch`.
205
+
206
+ The editor instance can also restart (new `instanceId`, new pid), which clears
207
+ the live canvas. After a restart the canvas is empty while the saved project
208
+ file still holds data — that is expected, not a bug. Re-check
209
+ `GetPageOccupancy` before assuming a prior placement persists.
210
+
211
+ ## Practical workflow
212
+
213
+ 1. `project.GetActiveProject` → print `projectId`. Do not cache across tasks.
214
+ 2. `kernel.GetSnapshot` once — it answers most questions.
215
+ 3. Build the three maps (designator→instance, pinDef→name, pinInstance→net).
216
+ 4. For semantics, read `metadata.properties` of the instance.
217
+ 5. **Neighborhood expansion** — for each pin of the target REF, list other
218
+ members on the same net (grouped by pin). See below.
219
+ 6. **Optional series traversal** — `TRACE=1` on `read-circuit.ts` to follow
220
+ C/R/L/FB two-terminal bridges to the far-side net. No extra RPC.
221
+ 7. **Canvas edit ids** — print `metadata.id`, `canvasObjectId`, and
222
+ `FindObjectByProperty(Reference)` for Flow C. See [editing-a-circuit.md](./editing-a-circuit.md).
223
+ 8. Only if a project is large enough to make the 1.6 s snapshot painful,
224
+ fall back to `netList.GetProjectNetList` (~27 ms) for pure net queries.
225
+
226
+ ## Standard answer template (agent natural language)
227
+
228
+ When answering "what does REF do?", structure the reply as:
229
+
230
+ 1. **Identity** — `Description` / `MPN` from `metadata.properties`; if empty,
231
+ state definition name only — **do not invent datasheet values**.
232
+ 2. **Pin → net** — table from snapshot join.
233
+ 3. **Neighborhood** — per pin, who else shares each net (see next section).
234
+ 4. **Series links (optional)** — if user asks about coupling/series path, enable
235
+ TRACE or describe C/R/L/FB bridges manually from snapshot.
236
+ 5. **Consistency flags** — e.g. `output_voltage` vs rail net name.
237
+ 6. **Role summary** — one paragraph: what this part does **in this local context**.
238
+
239
+ Template script: `template/scripts/read-circuit.ts` (`REF=VR201`).
240
+
241
+ ## Neighborhood expansion (same-net, 1 hop)
242
+
243
+ After pin→net for target `REF`:
244
+
245
+ 1. Collect connected nets (exclude `(unconnected)`).
246
+ 2. For each net, reverse-map `nets[].pinInstanceIds` → designator + pin number/name.
247
+ 3. **Group by pin** (not flat per net) so "what's on VIN?" is obvious.
248
+ 4. Remove the target REF's own pins from neighbor lists.
249
+ 5. **Large net summary** (GND, +5V, …): if >8 pin instances, show netClass,
250
+ total part/pin counts, prefix stats (`C×4, R×2`), and **first 5** representative neighbors.
251
+ 6. **Role hints** (rule-based, not online): `C*` → bypass/filter cap;
252
+ `R*` → pull/divider; `U*`/`VR*` → active IC; power symbols → rail entry.
253
+ 7. Attach `Value=` from neighbor `metadata.properties` when present.
254
+
255
+ Default depth: **1 hop** (same net only). Do not recursively expand every
256
+ neighbor's other nets unless the user asks for a subsystem view.
257
+
258
+ ## Series traversal (C / R / L / ferrite bead)
259
+
260
+ **No new engine RPC.** Build a client-side graph from one snapshot:
261
+
262
+ | Edge | Rule |
263
+ | --- | --- |
264
+ | Same net | All pins on net N are mutually reachable |
265
+ | Series passive | `C/R/L/FB*` with exactly **2 connected pins on 2 different nets** |
266
+
267
+ **Do not bridge:**
268
+
269
+ - Multi-pin ICs (`U*`, `VR*`, …) — stop at net boundary.
270
+ - **Shunt decoupling cap** — one leg on GND/VCC/POWER netClass, other on signal:
271
+ list as same-net neighbor only; **do not** traverse into the rail through the cap.
272
+ - Optional: stop penetration into huge GND/POWER nets after listing summary.
273
+
274
+ **BFS** from each pin of target REF (default `maxDepth=2`):
275
+
276
+ ```
277
+ +3.3V —[C205 100nF]— NET_MCU_AVDD → U301(14 VDD)
278
+ ```
279
+
280
+ Enable in template:
281
+
282
+ ```bash
283
+ REF=VR201 TRACE=1 npx tsx scripts/read-circuit.ts
284
+ REF=VR201 TRACE=1 TRACE_DEPTH=2 npx tsx scripts/read-circuit.ts
285
+ ```
286
+
287
+ Engine-side `GetConnectivity` with `edgeKind=series` would be more authoritative
288
+ (P2); skill uses this BFS until then — see [rpc-availability.md](./rpc-availability.md).
289
+
290
+ ## Designator lookup and canvas id mapping
291
+
292
+ | Step | API / field |
293
+ | --- | --- |
294
+ | Exact REF | `symbolInstances.find(designator === REF)` |
295
+ | Fuzzy | prefix match / substring; print candidates — never guess silently |
296
+ | Snapshot join id | `symbolInstance.metadata.id` |
297
+ | Canvas mutate id | `canvasObjectId` **or** `FindObjectByProperty("Reference", REF).objectIds[0]` |
298
+ | Set designator | `SetObjectProperty` + **`Part Reference`** (not `Reference`) |
299
+
300
+ After Flow B, carry ids into Flow C — [editing-a-circuit.md](./editing-a-circuit.md).
301
+
302
+ ## When properties are empty
303
+
304
+ Hand-drawn or non-CIS parts may have no `metadata.properties`:
305
+
306
+ - Use `symbolDefinitions` pin names + neighborhood topology.
307
+ - State uncertainty explicitly.
308
+ - **Forbidden:** fabricating voltage/current ratings or MPN from memory.
309
+
310
+ ## Multi-part / subsystem questions
311
+
312
+ "How is this power block fed?" — expand by **net cluster**, not one REF:
313
+
314
+ 1. Start from regulator VIN/EN nets (Flow B on REF).
315
+ 2. Run `NET=<name>` mode on upstream nets.
316
+ 3. Use `TRACE=1` for series elements between rails and loads.
317
+ 4. Summarize chain: source → filter → regulator → load caps → loads.