⚠️ Core Workflow (Must Be Strictly Followed)
Whenever you generate format data,you must immediately call the validation script, and only after validation passes can you return the result.
Mandatory Workflow:
1. 查询资料 → 2. 检查关联图元 → 3. 生成格式 → 4. 验证格式 → 5. 存档
↓ 失败
修复并重试(最多3次)
Checklist That Must Be Executed After Each Generation:
- Have queried types-index.md to find the type name
- Have entered the documentation layer to understand the domain
- Have entered the primitive layer to check field definitions and constraints
- (Optional) Have checked the example layer for reference real data
- (Optional) Have checked the JSON Schema to understand constraints
- Have checked whether associated primitives need to be generated
- Have generated the format data (including associated primitives, if needed)
- The coordinates and parameters of associated primitives have been automatically inferred (without asking the user)
- Have called
node validate.jsto validate all generated primitives - Validation result
valid: true - Have saved the format to a file
- Return the complete format + validation result
Row Data Format
All data is in row units, and each row consists of outer data and inner data:
{type, id, ticket}||{实际数据}
Row Terminator: Each row ends with | plus a newline (LF,not CRLF).|| is the shell/payload separator, and the | + newline at the end of the row is the actual record end; the payload/shell must not contain "a literal | immediately followed by a literal newline" (JSON serialization already guarantees this will not happen).
Outer Data(eventual consistency framework):
type: Primitive type name (such as LINE, PAD, VIA)id: Unique identifier. Ordinary primitives use a 16-digit hexadecimal random string; singleton atoms (META / CANVAS / UNIVERSAL, etc.) directly use the type name; FONT uses the font key, and BLOB uses the content hashticket: Logical clock (incrementing integer).DOCHEAD rows do not haveid, and the outer layer only hastypeandticket——ticketare decided by the writer; the eprj3 save path will write (ProConsistencyManager'sJSON.stringify({ type: DOCHEAD, ticket }), and the parsing side'sProOuterParserwill also be read back)
Inner Data: The attribute values of the specific primitive; each primitive has a corresponding field definition.
Eventual Consistency (When the same data appears in multiple rows, which row counts)
Storage isappend-only: additions, deletions, and modifications all write another row to the log, so the same data will have multiple rows. The eventual consistency framework determines the winner, and the rules are:
- The arbitration key is "document + normalized id" ——
typedoes not participate incomparison; even if the same id changes type, it still competes along the same chain. ticketdifferent → keepticketthe row with the larger value。ticketthe same → then comparethe document header'sclient,keepclientthe row with the lexicographically larger value。
⚠️ Rule 3'sdirection is "the larger one wins". The old 2.0 document says "
clientif the identifier is smaller, it is kept",which is opposite to the implementation(the implementation is inlc-protocol's3.0/consistency/utils/list.ts'scompareFnand the accompanying unit tests),do not write according to the old document。
There is also a **pending (pend) / revoke** mechanism that temporarily overrides the above conclusions (for the same (client, ticket) when a hit occurs, only the pend flag is flipped, without overwriting with the new value;revoke can make a resolved node with a smaller ticket take effect again). That is internal state for collaborative editing and is not needed when generating static format data.
How deletions are expressed
- Deleting an atomic data item: write another line, and setthe inner data to an empty string ——
{"type":"…","id":"…","ticket":N}||(||afterward is empty).It is noterasing this line from the log. - Deleting a document: write a line
{"type":"DELETE_DOC","ticket":N}||{"isDelete":true}. It onlyadds a deletion marker, and the original document data remains in the log, soit can be undone(by overwriting it again with{"isDelete":false}). - ⚠️ The payloads of the two kinds of deletion are not interchangeable, and must be provided separately by type:
DELETE_DOCsending an empty string ⇒ it will be judged as "not deleted", reviving the already-deleted documentrevive; what it wants is{"isDelete":true}/{"isDelete":false}。METAmust neversend an empty string — the consumer will directly takedataObj.title, the empty string parses intonulland then throws an exception, aborting the entire batch replay; deleting META viaDELETE_DOC。INSTANCE_ATTRwith an empty string iscorrectpayload (the semantics are exactly "delete this property").
⚠️ Type names and document type prefixes (required reading for validation)
Graphic elements with the same name have different schemas under different document types:LINE on a schematic page it is a "wire" (with lineGroup/strokeColor…), while on a PCB it is a "trace" (with netName/layerId/width…). When validating, the validator must know which kind of document it is, otherwise it will take the wrong schema — manifesting as "a bunch of fields you don't need are missing" or "errors that should be reported are not reported".
Method 1 (recommended): when validating, use the <文档类型>_<图元名> prefix name:
node validate.js PCB_LINE '{"netName":"+5V",...}'
node validate.js PANEL_POLY '{...}'
node validate.js BOARD_META '{"title":"Board1",...}'
Method 2: use --doc to explicitly specify the document type (clearer when using bare names):
node validate.js LINE '{"netName":"+5V",...}' --doc PCB
Method 3: directly use the bare name — it will be resolved according to the priority of SCH_PAGE > SCH > SYMBOL > PCB > 其他 , andacross document types the resolution result is not what you want, so it is only reliable under schematic-type documents.
Common comparison (for the full list see validate.js in TYPE_MAP):
| Document type | Trace / wire | Polygon | Component | Arc | Metadata | Canvas |
|---|---|---|---|---|---|---|
| SCH_PAGE / SYMBOL | SCH_PAGE_LINE | SCH_PAGE_POLY | SCH_PAGE_COMPONENT | SCH_PAGE_ARC | SCH_PAGE_META | SCH_PAGE_CANVAS |
| PCB / FOOTPRINT | PCB_LINE | PCB_POLY | PCB_COMPONENT | PCB_ARC | PCB_META | PCB_CANVAS |
| PANEL / PANEL_LIB | PANEL_AUXLINE | PANEL_POLY | — | — | PANEL_META | PANEL_CANVAS |
| SIMULATION | SIMULATION_LINE | SIMULATION_POLY | SIMULATION_COMPONENT | SIMULATION_ARC | SIMULATION_META | SIMULATION_CANVAS |
| BOARD / CONFIG / DEVICE / SCH / SIMULATION_SCH | — | — | — | — | BOARD_META / CONFIG_META / SCH_META / SIMULATION_SCH_META … | — |
Required formats: DOCHEAD and CANVAS
Important: when generating any graphic element format,you mustfirst include DOCHEAD; the order is DOCHEAD → CANVAS → other graphic elements.CANVAS exists only for document types that contain a canvas(SCH_PAGE / SYMBOL / SIMULATION / PCB / FOOTPRINT / PANEL / PANEL_LIB, see the corresponding table below); documents such as FONT / BLOB / CONFIG / BOARD / DEVICE / SCH / SIMULATION_SCH do not have CANVAS, so do not add it.
DOCHEAD (document header)
DOCHEAD is the first line of every document, defining the document's type and metadata.
Format:
{"type":"DOCHEAD","ticket":1}||{docType, client, uuid, updateTime, version}
Field descriptions:
| Field | Type | Required | Description |
|---|---|---|---|
| docType | string | ✓ | Document type, such as SCH_PAGE、PCB、SYMBOL、FOOTPRINT |
| client | string | ✓ | Client ID - 16-digit lowercase hexadecimal(such as 1f0f511a4641034c), user-specifiable, otherwise AI-inferred |
| uuid | string | ✓ | Document unique identifier - 16-digit lowercase hexadecimal, user-specifiable, otherwise AI-inferred |
| updateTime | number | - | Update timestamp (milliseconds) - user-specifiable, otherwise AI-inferred (current time) |
| version | string | - | Version number - user-specifiable, otherwise AI-inferred (usually the same as updateTime) |
User field specification rules:
- If the user provides
client、uuid、updateTime、versionin the request, use the user-specified value (client/uuidmust still satisfy 16-digit lowercase hexadecimal) - If the user does not provide it, the AI infers a reasonable default value:
client: generate a 16-digit lowercase hexadecimal stringuuid: generate a 16-digit lowercase hexadecimal stringupdateTime: use the current timestamp (milliseconds)version: same asupdateTime: same as
Example (SCH_PAGE):
{"type":"DOCHEAD","ticket":1}||{"docType":"SCH_PAGE","client":"1f0f511a4641034c","uuid":"81ace96648894616","updateTime":1777537222142,"version":"1777537222142"}|
Example (user-specified client, which must also be 16-digit lowercase hexadecimal):
{"type":"DOCHEAD","ticket":1}||{"docType":"SCH_PAGE","client":"a1b2c3d4e5f60718","uuid":"81ace96648894616","updateTime":1777537222142,"version":"1777537222142"}|
Example (PCB):
{"type":"DOCHEAD","ticket":1}||{"docType":"PCB","client":"1f0f511a4641034c","uuid":"81ace96648894616","updateTime":1777537222142,"version":"1777537222142"}|
CANVAS (canvas configuration)
CANVAS is the second line of each document, defining the canvas origin and configuration information. Depending on the document type, the CANVAS format differs.
SCH_PAGE / SYMBOL canvas (simplified version)
Format:
{"type":"CANVAS","ticket":1,"id":"CANVAS"}||{originX, originY}
Field description:
| Field | Type | Required | Description |
|---|---|---|---|
| originX | number | ✓ | Canvas origin X |
| originY | number | ✓ | Canvas origin Y |
Example:
{"type":"CANVAS","ticket":1,"id":"CANVAS"}||{"originX":0,"originY":0}
PCB / FOOTPRINT canvas (full version)
Format:
{"type":"CANVAS","ticket":1,"id":"CANVAS"}||{originX, originY, unit, gridXSize, gridYSize, ...}
Field description:
| Field | Type | Required | Description |
|---|---|---|---|
| originX | number | ✓ | Canvas origin X |
| originY | number | ✓ | Canvas origin Y |
| unit | string | ✓ | Display unit (such as "mm", "mil") |
| gridXSize | number | ✓ | Grid size X |
| gridYSize | number | ✓ | Grid size Y |
| snapXSize | number | ✓ | Raster size X |
| snapYSize | number | ✓ | Raster size Y |
| altSnapXSize | number | ✓ | Alt raster size X |
| altSnapYSize | number | ✓ | Alt raster size Y |
| gridType | EGridType | ✓ | Grid type (such as "GRID") |
| multiGridType | EGridType | ✓ | Bold grid type |
| multiGridRatio | number | ✓ | Bold grid multiple |
| highlightValue | number | ✓ | Highlight brightness value |
| layerBrightness | ELayerBrightness | ✓ | Layer brightness (such as "NORMAL") |
Example (PCB / FOOTPRINT):
{"type":"CANVAS","ticket":1,"id":"CANVAS"}||{"originX":0,"originY":0,"unit":"mm","gridXSize":10,"gridYSize":10,"snapXSize":1,"snapYSize":1,"altSnapXSize":0.1,"altSnapYSize":0.1,"gridType":"NONE","multiGridType":"NONE","multiGridRatio":10,"highlightValue":0.5,"layerBrightness":"NORMAL"}
Example (PANEL / PANEL_LIB, fields are completely different from the grid canvas above):
{"type":"CANVAS","ticket":1,"id":"CANVAS"}||{"material":"acrylic","thickness":"0.8mm","print":"Bottom Side","craft":"Transparent","desc":"","coverColor":"white","width":"393mm","height":"579mm","originX":0,"originY":0,"orderWidth":"393mm","orderHeight":"579mm","backgroundColor":""}
Generation order and rules
Generation order:
- DOCHEAD (line 1) - fixed type is "DOCHEAD"
- CANVAS (line 2,only present for the document types listed below) - fixed ticket is 1, id is "CANVAS"
- Other primitives (starting from line 3) - ticket increments starting from 2
Document type and CANVAS type correspondence table:
| Document type | CANVAS type | CANVAS format | Type name for validation |
|---|---|---|---|
| SCH_PAGE | TSchCanvas | Simplified version (only originX, originY) | SCH_PAGE_CANVAS |
| SYMBOL | TSchCanvas | Simplified version (only originX, originY) | SYMBOL_CANVAS |
| SIMULATION | TSchCanvas | Simplified version (only originX, originY) | SIMULATION_CANVAS |
| PCB | TCanvas | Full version | PCB_CANVAS |
| FOOTPRINT | TCanvas | Full version | FOOTPRINT_CANVAS |
| PANEL | TPanelCanvas | Panel canvas (material/thickness/printing/color…) | PANEL_CANVAS |
| PANEL_LIB | TPanelCanvas | Panel canvas (material/thickness/printing/color…) | PANEL_LIB_CANVAS |
Document types without CANVAS: FONT, BLOB, CONFIG, BOARD, DEVICE, SCH, SIMULATION_SCH — these documents only have DOCHEAD + primitives, do not insert CANVAS lines.
LLM Workflow
Generation task reception
When receiving a format generation request (such as "generate the format for a via", "create a resistor component", "draw a wire"):
- Understand the request: Identify the primitive type the user wants to generate (such as via/component/wire) and the specific parameter requirements
- Find the type(first step, must be performed):
- Search in types-index.md based on the user's Chinese description (such as "via", "component", "wire")
- Find the corresponding type name (such as TPcbVia, TSchComponent, TSchLine) and document layer path
- Progressive loading of materials(load on demand, avoid reading too many files at once):
- Enter the document layer: read the document for the corresponding domain (such as documents/PCB.md) → The document layer contains an index of all primitives in that domain
- Enter the primitive layer: select a specific primitive from the index and read its definition (such as primitives/PCB/via.md) → The primitive layer contains field definitions, JSON Schema constraints, and related primitive descriptions
- (Optional) Enter the example layer: if real data is needed for reference, read it via the link provided by the primitive layer (such as examples/PCB/t-pcb-via.md)
- (Optional) View the Schema: the primitive layer provides a JSON Schema link, read it on demand
- Check related primitives:
- View the "related primitives" section of the primitive layer (parent-child primitives, container members, embedded structures, reference associations)
- If there are related primitive descriptions, related primitives must be generated
- The values of related fields must correctly point to the main primitive
- ⚠️ Do not ask the user: automatically infer related primitives based on the main primitive's coordinates and attributes
- Generate the format:
- Prefer field values provided by the user
- If the user did not provide them but the fields have default values, use the default values
- If there are no default values, infer reasonable values based on the document constraints
- At the same time, validate the attribute values provided by the user (adjust if there are conflicts)
- If needed, also generate related primitives (one primitive format per line)
- ⚠️ The coordinates and parameters of related primitives are automatically inferred by the AI, do not ask the user
- Validate the format:
- Call the validation script to validate each generated primitive
- The main primitive and all related primitives must pass validation
- Archive:
- After validation passes, save all formats to files
- Save each line of format as one file, or save multiple formats as one file
⚠️ Important:
- Do not skip the step of querying materials; you must first collect enough information before generating the format!
- When there are associated primitives, they must be generated together; you cannot generate only the main primitive!
- The coordinates and parameters of associated primitives are automatically generated by AI; do not ask the user!
Example of associated primitives (DOCHEAD + CANVAS + TBus + LINE + ATTR)
Below is a complete SCH_PAGE file example (each line inside the code block can be copied directly):
Includes the file header, canvas, BUS primitive, and its associated primitives (4 LINEs and 1 ATTR):
{"type":"DOCHEAD","ticket":1}||{"docType":"SCH_PAGE","client":"1f0f511a4641034c","uuid":"81ace96648894616","updateTime":1777537222142,"version":"1777537222142"}|
{"type":"CANVAS","ticket":1,"id":"CANVAS"}||{"originX":0,"originY":0}|
{"type":"BUS","ticket":35,"id":"917cf8401f113481"}||{"busEntry":{},"zIndex":6,"groupId":"","locked":false}|
{"type":"LINE","ticket":36,"id":"899f254f57c290ca"}||{"fillColor":null,"fillStyle":null,"strokeColor":null,"strokeStyle":null,"strokeWidth":null,"startX":600,"startY":-570,"endX":680,"endY":-570,"lineGroup":"917cf8401f113481"}|
{"type":"LINE","ticket":37,"id":"0cbf6fa91e809769"}||{"fillColor":null,"fillStyle":null,"strokeColor":null,"strokeStyle":null,"strokeWidth":null,"startX":680,"startY":-570,"endX":680,"endY":-500,"lineGroup":"917cf8401f113481"}|
{"type":"LINE","ticket":38,"id":"133a0ef467701da4"}||{"fillColor":null,"fillStyle":null,"strokeColor":null,"strokeStyle":null,"strokeWidth":null,"startX":680,"startY":-500,"endX":505,"endY":-500,"lineGroup":"917cf8401f113481"}|
{"type":"LINE","ticket":39,"id":"e7c73f80b9a41723"}||{"fillColor":null,"fillStyle":null,"strokeColor":null,"strokeStyle":null,"strokeWidth":null,"startX":505,"startY":-500,"endX":505,"endY":-565,"lineGroup":"917cf8401f113481"}|
{"type":"ATTR","ticket":40,"id":"fd36cf31312bbfcd"}||{"x":592.5,"y":-500,"rotation":0,"color":null,"fontFamily":null,"fontSize":null,"fontWeight":null,"italic":null,"underline":null,"align":"LEFT_BOTTOM","value":"BUS[0:5]","keyVisible":false,"valueVisible":true,"key":"NET","fillColor":null,"parentId":"917cf8401f113481","zIndex":4,"groupId":"","locked":false,"strikeout":null}
In the code block above, the
|at the end of each line is the line separator: every line except the last one must have it, and the last line must not.
Key points:
- DOCHEAD defines the document type and metadata
- CANVAS defines the canvas origin coordinates
- Primitives in the same group reference each other by id: the LINE's
lineGroup, the ATTR'sparentIdboth point to the BUS's id - Primitives generated later have larger tickets
- The end of each line format requires a
|separator (except for the last line)
🔍 Verification steps (core process)
After generating the formatverification must be executed immediately:
Step 1: Call the verification script
# 用「文档类型_图元名」前缀名(推荐,跨文档类型的同名图元必须这样写)
node ./validate.js PCB_LINE '{"netName":"+5V",...}'
# 或用 --doc 指定文档类型
node ./validate.js LINE '{"netName":"+5V",...}' --doc PCB
Step 2: Check the verification result
{
"valid": true,
"errors": []
}
- If
valid: trueanderrors: []→ verification passed, save the format to a file - If
valid: false→ verification failed, needs to be fixed
Step 3: Save the format to a file (must be executed after verification passes)
# 保存到 format 目录
# 格式:{外层JSON}||{内层JSON}
# 示例:{"type":"LINE","id":"abc123","ticket":0}||{"startX":100,"startY":200}
Saving rules:
- Save directory:
./format(same directory as SKILL.md) - File name format:
{type}_{timestamp}.txt - File content: separate multiple primitive formats with newlines, with a
|at the end of each line format (except for the last line) - Note: The file format is text format
Important format rules:
- The end of each primitive format string requires adding a
|separator (except for the last primitive) - For example: the format string wrapped in backticks requires adding a | separator at the end
- The end of the last primitive format stringdoes not needto add
|
Step 4: If verification fails
- Parse
errorsthe error information in the array - According to
fieldandmessagefix the corresponding fields - Call the verification script again
- Retry at most 3 times
⚠️ Important: Do not skip the verification and saving steps, and do not assume the generated result is correct!
Verification command example:
node ./validate.js FONT '{"width":50,"height":40,"path":[[2,5,"L",2,35,48,35,48,5,2,5]]}'
JSON Schemas
- schemas/ - Verify the schemas directory