--- name: library-manager description: >- Manage Vecmocon's component library. Extract parameters from a component datasheet PDF into the per-typeid Excel template. Checks Gitea for a duplicate MPN_make, classifies to a typeid, updates that typeid's template (versioning, changelog, backfill), fills a per-part workbook with human verification, reads the Altium symbol/footprint refs, assembles a part folder and pushes to Gitea. Can UPDATE an existing part instead of hard-stopping. ALWAYS WRITES the mandatory SOP parameters into the .SchLib symbol (stripping Ultra-Librarian defaults) as a required step of every run involving a symbol — never asks first. On the .SchLib task it also fills that component's Excel and pushes, no verification loop. Descriptions follow Vecmocon's strict Altium Description Format. Use whenever the user uploads a datasheet, builds/updates a library entry, adds a template parameter, fills/updates .SchLib parameters, or pushes a part to Gitea. Always trigger on "\datasheet", "\library", "\library-manager", "\schlib". --- # Library Manager Turn one component datasheet into a verified, versioned library entry in Gitea. The guiding idea is honesty and traceability: every value lands in the right column and unit, anything the datasheet doesn't state stays blank, and nothing reaches Gitea until a human has confirmed it. ## Ask, don't assume This is a deliberately **interactive** skill. At every decision point, **ask the user and wait for an answer — do not assume a default and proceed.** In particular, always confirm: the **make**; the **typeid/classification** you inferred; whether any **new parameters** should be added to the template; the extracted **values** (the verification loop); that the **symbol/footprint actually match this part**; and whether to **apply a new parameter to existing parts** (backfill). When something is ambiguous, ask a specific question rather than guessing. It is always better to ask one more question than to write the wrong thing into the library. One more thing to confirm when a part **already exists**: whether to **update it or stop** (don't silently overwrite), and if updating, **which fields change** (values, symbol/footprint, datasheet). An update still goes through the same verification loop before anything is pushed. **Pushing is the exception — push automatically.** Once a part is verified and assembled (or a template/version change has been made, or a verified update is ready), push to Gitea **on your own, without asking for confirmation**. Never add a "shall I push?" step — just push, then tell the user where it landed. (You still flag a genuine symbol/footprint mismatch, because that's a correctness issue, not a push confirmation.) **The `.SchLib` task is a second exception — it fills and pushes without a verification loop.** When the engineer hands over a symbol for the schlib flow, fill that component's Excel (Description + all parameters) and push the updated symbol automatically, without pausing to confirm the values. See *Mandatory symbol parameters* for why this path skips the loop. **Writing symbol parameters is a third exception — always do it, never ask.** Whenever a `.SchLib` is in play — the standalone schlib task *or* step 6b of a new-part run — filling its mandatory SOP parameters is a required action, not an offer. Never ask "should I fill the symbol parameters?" or leave it as a suggested next step; the answer is always yes, so just write them and report it. A symbol that still holds Ultra-Librarian defaults is an incomplete part. Every other flow keeps the normal interactive asks below. ## Keep a copy in the connected folder — always, after every part push So the operator ends up with the parts they worked on **on their own machine** — without having to go into the admin Gitea to fetch them — the skill drops a copy of each pushed part folder into the **connected session folder**. At the start of a Cowork session the operator connects a folder; that folder is the destination. If no folder was explicitly connected, use the session's output directory `/mnt/user-data/outputs/` (create it if missing) so the copy still lands somewhere the operator can retrieve. The rule is: **immediately after any successful part push to Gitea**, copy that part's `__/` folder — with its files intact (`.xlsx`, datasheet, `.SchLib`, `.PcbLib`, `.IntLib`) — into the connected folder. This is automatic and never a question, exactly like the push itself. Details and the exact command are in *Copy the pushed folder to the connected folder* below. Two boundaries: (1) copy **only after the push succeeds** — a failed push must not leave a misleading copy on the operator's machine; (2) this applies to **part** pushes only (`push-part`, and the `commit-push` used by updates, backfill, and the `.SchLib` fill-and-push). **Skill-repo** pushes (`push-skill`, template/changelog changes) involve no part folder and are excluded. ## Inputs - **A datasheet PDF whose filename is the MPN** (e.g. `BAT46WJ.pdf`). If it's a series datasheet, search that exact MPN inside to read the correct variant. - **The make** (manufacturer), given by the user. The `make` tag is the first word of the manufacturer, alphanumerics only (Texas Instruments → `Texas`, Nexperia → `Nexperia`). - **Later in the flow**, after verification: an Altium **symbol** (`.SchLib`) and **footprint** (`.PcbLib`) file, provided by the user. ## The identifier: `MPN_make_typeid` Every part folder, every per-part workbook, and column A of every sheet (`MPN_make_type`) use the same tag: ``` __ e.g. BAT46WJ_Nexperia_SCH ``` `typeid` is the part's type-ID code from the taxonomy (`references/taxonomy.md`, full source `assets/template/Type_ID.xlsx`) — Schottky → `SCH`, MOSFET → `MOS`, LDO → `LDO`. In the new template **each typeid is its own sheet** (125 of them). The broader **Class** (Diode, Transistor, IC …) is used only to organise the library repo into top-level folders. ## Gitea layout (two repos) ``` skill repo/ this skill's own files (updated versions land here too) library repo/ / e.g. Diode, IC, Transistor, Resistor, ... __/ e.g. BAT46WJ_Nexperia_SCH __.xlsx this part's own one-row parameter sheet _data.pdf the datasheet .SchLib user-provided .PcbLib user-provided __.IntLib compiled integrated library (SchLib+PcbLib in one) ``` There is **no single master workbook** — each part carries its own sheet inside its folder. Connection + repo names live in `config/gitea.env` (`SKILL_REPO`, `LIBRARY_REPO`), so runs need no per-session token. If the host is unreachable, the git steps fail clearly and write nothing. ## Who's running this (operator identity) The whole org shares one Claude account and one Gitea token, so the skill can't tell who's running it on its own. Instead it records the **operator's name** on everything it pushes, and asks **only once per person**. Establish the identity at the very start of a run: 1. Read `~/.library-manager-id` on the operator's machine (via the desktop bridge): `cat ~/.library-manager-id`. 2. **If it exists** (JSON like `{"name":"Priya Sharma","email":"priya@vecmocon.com"}`) → use it, **don't ask**. 3. **If it does NOT exist → this person isn't onboarded yet, so ask once** (do this even though the config carries a default, so a teammate is never silently logged as "admin"): > "First time using the library skill on this machine — what name should your library > changes be recorded under? (If you're the admin, just enter `admin`.) And your email, > if you have one." Then save it so it's remembered forever: `echo '{"name":"","email":""}' > ~/.library-manager-id`. Every later run finds it in step 1 and never asks again — the admin answers `admin` once, each teammate answers their own name once, and from then on everyone is attributed correctly with no prompt. 4. **Only if you genuinely can't ask** (an unattended / scheduled run, or the machine isn't reachable) fall back to the config `OPERATOR` default so the run isn't blocked — and say in your summary that attribution used the default rather than a confirmed person. Then carry that identity through the run: - pass `--author " <>"` to every `push-part` / `push-skill` / `commit-push`, and - pass `--by ""` to `append_parameter`. (Equivalent alternatives the scripts also read: `export LM_AUTHOR_NAME=... LM_AUTHOR_EMAIL=...` for the session, or drop the same `~/.library-manager-id` file in the container home.) **Identity precedence** (first one that's set wins): `--author` flag → `LM_AUTHOR_*` env → per-person `~/.library-manager-id` → the per-install **`OPERATOR`** default in `config/gitea.env`. So this admin install has `OPERATOR=admin`, meaning **every run/push here is recorded as "admin" automatically**, with no file or prompt needed. A member's own `~/.library-manager-id` (or an explicit `--author`) overrides that default with their real name. This stamps the operator onto three things: the **commit author** (shown in `git log`, the Gitea commit page, and `git blame`), the **commit message** (it ends with `(by )`, so the name shows right in Gitea's activity feed), and a **By** column in the changelog. Honest limit: the top-line "*X* pushed to main" in Gitea's activity still shows the **shared token owner** — only giving each person their own Gitea token changes that bottom layer. ## Workflow Run these in order. Each `python`/`bash` command is a helper in `scripts/`. ### 0. Sync the skill state from Gitea first — always The skill's state (the template, the per-typeid versions, and the changelog) lives in the **skill repo**, and it grows over time. A fresh install/session starts from the packaged v1 state, so if you don't sync first, a second template change wouldn't build on the first — the versions would restart at v1 and the changelog would look like it only holds the latest change. **So begin every run by pulling the current state:** ```bash python scripts/gitea_components.py pull-skill ``` This copies `template.xlsx`, `versions.json`, and `CHANGELOG.xlsx` from the skill repo into the local skill, so version bumps continue correctly (v2 → v3 → …) and the changelog stays **cumulative from the very first change**. ### 1. Duplicate check first — before any real work The part's presence is keyed on **MPN + make** (typeid not known yet). If it already exists, stop; re-doing an existing part would only risk overwriting good data. ```bash python scripts/gitea_components.py check-mpn --mpn --make ``` `ABSENT` (exit 0) → this is a **new part**; continue to step 2. `EXISTS …` (exit 3) → the part is already in Gitea. Don't silently overwrite it, but don't dead-end either — **ask the user whether they want to update the existing part or stop.** If they want to revise it (new/corrected values, a swapped symbol/footprint, a newer datasheet), go to *Updating an existing part*. If not, stop here. (If the user's request already said "update"/"revise"/"fix" this part, take that as the answer and go straight to the update flow.) ### 2. Classify → typeid (and its Class) Read the datasheet, identify the part, and match it to the closest subclass in `references/taxonomy.md`; record its **typeid** (= the template sheet name). The **Class** (for the library-repo folder) comes from the same taxonomy row — `scripts/common.py:class_folder(typeid)` returns it (e.g. `SCH` → `Diode`). ### 3. Confirm the typeid's template (and add parameters if asked) Check whether that typeid has a sheet in `assets/template/template.xlsx`. - **No sheet for this typeid** → ask the user to upload the template sheet for it. Add it to `assets/template/template.xlsx`, then push the updated skill files to the skill repo (see *Pushing the skill repo*). Then continue. - **Sheet exists** → print **all** of that sheet's parameters (its column headers) in the chat and ask the user whether any new parameters should be added. - **No** → go to step 4. - **Yes** → collect the new parameter name(s), then: ```bash python scripts/append_parameter.py --typeid \ --param "New Parameter(unit)" [--param "Another(unit)"] \ --desc "why these were added" --by "" ``` This appends the column(s) at the end of that typeid's sheet, **bumps that typeid's Template Version and Skill Version together** (v1→v2 — see *Per-typeid versioning*), and writes one row to the global changelog `assets/CHANGELOG.xlsx`. Then sync the updated skill files + changelog to the skill repo with `push-skill` **automatically** (see *Pushing the skill repo*) — that merges the new changelog row onto the one already in Gitea rather than overwriting it. Then ask: **should this change apply to the parts of this typeid already in Gitea?** - **No** → go to step 4 (only the current part gets the new column). - **Yes → backfill** (see *Backfilling existing parts*), then tell the user the previous sheets were updated, and go to step 4. ### 4. Extract and fill the per-part workbook Read every parameter the datasheet actually states into that typeid's columns, converting to each header's unit. **Leave blanks where the datasheet is silent — an honest blank beats a guess.** The **`Description`** column is special: it is **not** free prose but a strict `_`-joined engineering string. It **always opens with `Class_TYPEID`** — the part's library Class exactly as spelled in its repo folder, then its Type ID in upper case — followed by the defining parameters in a fixed order, package near the end, optional AEC-Q last. E.g. `Resistor_FIX_36kΩ_62.2mW_±0.1%_0402`, `Capacitor_CER_2.2uF_100v_±10%_1210_x7r`, `Diode_SCH_100V_0.25A_SOD-323F`, `Diode_TVS_5V_9.2V_18A_SOD-323`. This `Class_TYPEID` opening is **mandatory for every part, with no exceptions** — including Resistor, Capacitor, Zener and TVS, where it deliberately overrides the prefix given in SOP §6. The old technology prefixes (`CHIP_RES`, `CHIP_CAP`, `TANT_CAP`, `NMOS`, `ZEN`, …) are **retired** — never emit them. `references/description_format.md` defines the parameter order for **every** type in the library and is authoritative; read it before writing a Description. Look up the part's type there, build the Description to that format, and if a real datasheet doesn't fit the format cleanly, follow the pattern and **flag the mismatch to the engineer** rather than bending it silently. Collect the values into a small `part.json`: ```json {"mpn":"BAT46WJ","manufacturer":"Nexperia","typeid":"SCH", "values":{"Description":"Diode_SCH_100V_0.25A_SOD-323F","Forward Voltage(V)":"0.71", "Reverse Voltage(V)":"100","Forward Current(A)":"0.25","Package":"SOD-323F"}} ``` (typeid `SCH` sits in Class `Diode` → prefix `Diode_SCH`, format `Diode_SCH_Vr_Io_Package`, so a 100 V / 250 mA Schottky in SOD-323F becomes `Diode_SCH_100V_0.25A_SOD-323F`. The Class token is the repo folder name from `CLASS_FOLDER` in `scripts/common.py`, so the Description's first token always matches the folder the part is pushed to.) ```bash python scripts/fill_templates.py part.json \ --template assets/template/template.xlsx --dest // ``` This writes `.xlsx` with column A = the tag, **Skill Version (col B)** and **Template Version (col C)** stamped from this typeid's current versions, and the four design columns left blank for now. Every sheet also carries an **`MPN` column** (positioned just before `Manufacturer`) that `fill_templates` populates with the part's bare MPN — it's filled from `part["mpn"]` automatically, so no extra input is needed. The part workbook has up to **two sheets**: - **Sheet 1** — the typeid's parameter sheet (the one filled row). - **Sheet 2 — `Version History`** — added **only when this typeid has had a template/skill update**. It lists the **cumulative** change history for that typeid (Date, Skill Version, Template Version as `v1 → v2`, Description) — every change up to the version this file was built at. So a part built at v3 shows both `v1 → v2` and `v2 → v3`; a still-at-v1 typeid has no second sheet. The history is read from `assets/CHANGELOG.xlsx`, so make sure the local changelog is current (it's kept in sync by `push-skill`) before building parts. ### 5. Human verification loop Deliver the filled workbook to the user and ask them to verify it. If they report an error or say it isn't right, **go back to step 4, re-read the datasheet more carefully, re-fill, and hand it back.** Repeat until the user confirms it's verified. Nothing is pushed until this passes — the engineer is the ground truth for the numbers. (The standalone `.SchLib` task is the exception: it fills the Excel and pushes without this loop — see *Fill the component's Excel and push* under *Mandatory symbol parameters*.) ### 6. Symbol + footprint → the design columns Once verified, ask the user to upload the **symbol (`.SchLib`)** and **footprint (`.PcbLib`)** files. Copy them into the staging part folder **under their proper names** (so the Path columns match the files that actually get stored — strip any upload-staging prefix the environment may have added), then read all four design values in one shot: ```bash cp //.SchLib cp //.PcbLib python scripts/altium_refs.py design \ --symbol //.SchLib \ --footprint //.PcbLib > design.json ``` This produces (verified against real Ultra-Librarian exports): - **Library Ref** = the component name **inside** the `.SchLib` (e.g. `CGA3E3X7R1H474K080AE`) - **Library Path** = the `.SchLib` file name - **Footprint Ref** = the **base** pattern inside the `.PcbLib`; Altium ships IPC density variants (`-L` / `-M` / `-N`) alongside the base, and the base is the one used (e.g. `CAP_CGA3_TDK`, not `CAP_CGA3_TDK-L`) - **Footprint Path** = the `.PcbLib` file name These Ref names come from *inside* the files and can differ from the MPN or filename. If a Ref comes back `null` (or a footprint shows several unrelated candidates), ask the user to confirm the name from Altium's properties and edit `design.json`. Then re-fill so the columns land: ```bash python scripts/fill_templates.py part.json \ --template assets/template/template.xlsx --dest // --design design.json ``` ### 6b. Write the mandatory symbol parameters — always, without asking Once you have the symbol and its Library Ref, **write the mandatory symbol parameters into the `.SchLib`**. This is **not optional and not a question** — it is a required step of every new-part run. Do not ask "would you like me to fill the symbol parameters?", do not offer it as a suggestion, and do not defer it to a later turn. Just do it, then report that it's done. The SOP requires the fixed parameter set (Manufacturer, Manufacturer Part, Value, Tolerance, Operating Temperature, RoHS, Datasheet, Process, Vecmocon Part Code, …) on every symbol, so a part folder whose `.SchLib` has not been filled is **incomplete and must not be pushed**. Build the part's `params.json` from the values the engineer just verified in step 5 and run: ```bash python scripts/schlib_write.py \ --schlib //.SchLib \ --params params.json \ --out //.SchLib ``` `params.json` must carry **`description`** and **`comment`** alongside `parameters`. The `description` is the strict `Class_TYPEID_…` string you already built for the workbook — reuse the *exact same string* so the symbol's ComponentDescription and the Excel `Description` column never diverge. The `comment` is the MPN. If the script warns that `description` is missing, treat that as an error: fix `params.json` and re-run before pushing. See *Mandatory symbol parameters* below for the full parameter set, the value-shorthand rules, and the Ultra-Librarian default-stripping behaviour — read `references/schlib_parameters.md` before building the parameter set. The values are already verified at this point, so this step inherits no verification loop of its own. ### 7. Assemble the part folder — and compile the IntLib (fifth file) The staging folder `/` should now hold four files: the per-part `.xlsx`, the datasheet (name it `_data.`), the symbol (`.SchLib`), and the footprint (`.PcbLib`). **Before compiling or pushing, check that the symbol has actually been filled** (step 6b). The part folder is not complete until it has. If the `.SchLib` still carries only Ultra-Librarian defaults, go back and run `schlib_write.py` — don't push, and don't ask the user whether to; just fill it. This ordering matters: the IntLib embeds the **finished** symbol, so it must be built *after* the SchLib parameters are written, not before. **Compile the integrated library** as the fifth file, so the folder ships a single ready-to-use `.IntLib` alongside the source SchLib/PcbLib. Run `build_intlib.py` against the two library files already in the folder, writing the output named after the part tag: ```bash python scripts/build_intlib.py \ --schlib //.SchLib \ --pcblib //.PcbLib \ --out //.IntLib ``` This is a pure-Python compile (no Altium needed): it embeds both libraries into one OLE compound file, reusing the bundled `assets/templates/intlib_container.IntLib` as the container skeleton, and self-validates by round-tripping the embedded libs. The symbol→footprint link lives inside the embedded SchLib (its RECORD=45 model link), so the compiled IntLib resolves the footprint on its own. Requirements: the `olefile` package, and the `.SchLib` **must** have a footprint model link (it will, after step 6b / a proper Ultra-Librarian export) — the script exits clearly if it doesn't. If the compile fails, fix the cause (usually a missing footprint model link) rather than pushing a folder without its IntLib; the IntLib is now a standard part of the deliverable. After it succeeds, the folder holds **five files**. ### 8. Push to the library repo, under the part's Class ```bash python scripts/gitea_components.py push-part --folder / --typeid \ --author " <>" ``` This places the folder at `components///` — creating the Class folder if it doesn't exist yet, or pushing into it if it does — and commits and pushes all five files (xlsx, datasheet, SchLib, PcbLib, IntLib). Confirm to the user where it landed. **Then copy the folder to the connected folder** (next section). ### Copy the pushed folder to the connected folder — automatic, after every part push Right after the push above **succeeds**, copy the just-pushed `__/` folder into the connected session folder so the operator has a local copy without touching Gitea. Use the folder the operator connected at the start of the session; if none was connected, fall back to `/mnt/user-data/outputs/`. ```bash DEST="${CONNECTED_FOLDER:-/mnt/user-data/outputs}" # the folder connected this session mkdir -p "$DEST/" cp -r //. "$DEST//" # xlsx, datasheet, .SchLib, .PcbLib ``` Copy the folder as `/` (i.e. `__/`) — just the part folder, not the `/` parent — so the connected folder simply accumulates one folder per part the operator worked on. If that folder already exists there (a re-run or an update), refresh it so the local copy matches what was just pushed. Then tell the user the copy is in the connected folder. This copy step runs after **every** part push, including the `commit-push` used by *Updating an existing part*, *Backfilling existing parts*, and the `.SchLib` *fill-and-push* — run the same three lines against that part's folder once its push succeeds. It does **not** run for `push-skill` (skill-repo/template/changelog pushes carry no part folder). ## Mandatory symbol parameters (.SchLib) The SOP (§5) requires every schematic symbol to carry a fixed parameter set in its Altium properties — `Component Type`, `Manufacturer`, `Manufacturer Part`, `Value`, `Tolerance`, `Operating Temperature`, `ROHS`, `Datasheet`, `Process`, `Vecmocon Part Code`, and the two second-source fields — with the **Comment** set to the MPN. The skill can stamp these onto the symbol from the datasheet. This runs **as its own task too**, not only inside new-part creation: whenever the user hands over one or more `.SchLib` files and wants their parameters filled/updated (e.g. "\schlib", "add these parameters to this symbol", "update the schlib"), read the datasheet/specs for each part, build its `params.json`, and run `scripts/schlib_write.py` per file — same steps as below. The skill writes these **directly into the `.SchLib` in pure Python** with `scripts/schlib_write.py` (it rebuilds the OLE around the enlarged `Data` stream, preserving every other byte, and strips the Ultra-Librarian defaults that shouldn't ship: `Manufacturer_Name` / `Manufacturer_Part_Number` (they duplicate the SOP fields), the UL `Copyright` notice, and the UL `Component_Type` — replaced by Vecmocon's own spaced `Component Type`). Kinds of value: - **Derived** — `Component Type` = the part's **Class** (`Resistor`, `Capacitor`, `Diode`, `Transistor`, `IC`, …), from `scripts/common.py:class_folder(typeid)` for this part's typeid. This is the same Class that names its library-repo folder, so the symbol carries it too. - **Read from the datasheet** (don't just echo given text — open the PDF and fill the real, verified values): `Value` = the **value only** in shorthand (e.g. `1u`, `12p`, `10k` — no package), plus `Manufacturer`, `Manufacturer Part`, `Operating Temperature`, `Tolerance`, `ROHS`, and `Process` by inference from the package. - **Derived link** — `Datasheet` = the **Gitea URL to the pushed `_data.`**, so the symbol clicks straight through to the exact datasheet in the library. Build it from `GIT_HOST` + `LIBRARY_REPO` and the part's Class/tag (`https:////raw/branch/main///_data.`); the full rule is in `references/schlib_parameters.md`. - **Leave blank for now** — `Manufacturer 2` / `Manufacturer Part 2` (the second source). Don't populate these by default; they stay hidden in Altium until filled later. (An optional cross-reference search to find a second source is documented in `references/schlib_parameters.md` but is currently off — only do it if the engineer asks.) - **Fill without pausing** — the `.SchLib` task is non-interactive, so don't stop to ask. The only genuinely non-derivable field is `Vecmocon Part Code` (internal): use it if the engineer already supplied it in the request, otherwise leave it blank (the SOP hides blank parameters) and note the gap in your summary rather than blocking on a question. Leave any genuinely-unknown field blank — the SOP hides blank parameters, so a gap simply stays empty until filled. The full method for the second-source search is in `references/schlib_parameters.md`. Collect the values into a `params.json` and write them into the symbol: ```bash python scripts/schlib_write.py --schlib .SchLib --params params.json --out //.SchLib ``` Deliver the resulting `.SchLib`; the engineer opens it in Altium once to confirm it loads, then **Saves to Server** with a revision note. The full parameter set, each value's source, the `params.json` shape (incl. the `remove` list), and the mini-stream size caveat are in `references/schlib_parameters.md` — read it before building the parameter set. If a file doesn't round-trip, fall back to `scripts/altium_params.py` (emits an Altium DXP script to apply the same parameters from inside Altium). Always have the engineer confirm the file opens in Altium — the skill writes Altium's own binary format, so Altium is the final validator. ### Fill the component's Excel and push — automatically, no verification loop The datasheet values you just read for the symbol belong in that component's per-part Excel workbook too, so fill them in the **same pass and push the result without asking**. This is a deliberate exception to the skill's usual "verify before Gitea" rule: when the engineer hands over a symbol for the `.SchLib` task, they want the library entry brought fully up to date in one shot — the Description and every parameter written into the Excel, the updated symbol saved beside it, and the whole thing pushed — not a round of confirmation questions. The engineer is already reviewing the symbol in Altium, so a separate spreadsheet verification loop adds delay without adding safety. The one check that still stands is the symbol/footprint-vs-part match: if the symbol clearly isn't this component, stop and flag it rather than pushing the wrong part. 1. **Fill the Excel automatically.** Reuse the values you already read for the symbol to build `part.json` — with `Description` built to `references/description_format.md` and every parameter the datasheet states — and fill the per-part sheet **directly, skipping the human verification loop (step 5)**. Do this inside a checkout of the library repo so it can be pushed; for a part already in Gitea, locate it with `find-part` exactly as in *Updating an existing part*, and re-derive the design columns from the symbol/footprint already in the folder so they carry through unchanged: ```bash python scripts/gitea_components.py checkout --dest work/ python scripts/gitea_components.py find-part --mpn --make --root work/ --json python scripts/altium_refs.py design \ --symbol work///.SchLib \ --footprint work///.PcbLib > design.json python scripts/fill_templates.py part.json \ --template assets/template/template.xlsx --dest work/// --design design.json ``` (If `find-part` returns `NOT FOUND`, the part isn't in Gitea yet — assemble the folder as a new part per steps 4–7, still without the verification loop, then push with `push-part`.) 2. **Drop in the updated symbol and push — no confirmation.** Copy the `.SchLib` you wrote with `schlib_write.py` into the same part folder under its proper name, then push the updated part — the symbol and the freshly filled Excel together — automatically: ```bash cp //.SchLib work///.SchLib python scripts/gitea_components.py commit-push --root work/ \ --message "update : fill parameters + symbol (by )" \ --author " <>" ``` Pushing without asking is consistent with the skill's standing rule that pushing is automatic; what's new here is that the Excel fill is automatic too. Tell the user which parameters you filled, that the symbol was updated, and where in Gitea it landed. (A part-data fill isn't a template change, so nothing version-bumps and the changelog is untouched.) **Then copy the part folder to the connected folder** — see *Copy the pushed folder to the connected folder*. ## Per-typeid versioning Versioning is **per typeid**, not global. Each typeid carries its own `template_version` and `skill_version` in `assets/template/versions.json` (both start at 1). When a parameter is added to a typeid, that typeid gets a new template, so its `template_version` bumps — and on the back of that its `skill_version` bumps too (v1→v2). **Only that typeid moves**; every other typeid keeps its versions. Those two numbers are exactly what `fill_templates` stamps into that typeid's rows (cols B and C), so a row always records the template/skill version it was built against. `append_parameter.py` does the bump; `common.py` is the single source for reading and writing these numbers. **One deliberate exception already applied:** the `MPN` column (before `Manufacturer`) was added to **every** typeid sheet as a one-off structural change **without** bumping any `template_version`/`skill_version` and **without** a changelog row — it's a bookkeeping column, not a spec parameter, and versions were intentionally left where they were. Don't treat the existing sheets' MPN column as a version discrepancy to "correct." (This no-bump behaviour is specific to that one change; genuine parameter additions still go through `append_parameter.py` and bump normally.) ## The changelog `append_parameter.py` maintains one **global** changelog as an Excel workbook at `assets/CHANGELOG.xlsx` (sheet `Changelog`, styled green header). Every time a typeid's template/version changes, one row is appended with columns **Date | Typeid | Skill Version | Template Version | Description** — the version columns hold the new versions, and Description is your note (or the parameter(s) added if you gave none). The changelog lives in the **skill repo in Gitea** as well, and it is **cumulative from the first change onward**. Two things keep it that way: at the start of a run `pull-skill` (step 0) brings the current changelog down so a new change appends to the full history, and on push `push-skill` **merges** the new local rows onto the changelog already in Gitea — appended, never overwritten. So the Gitea copy is the growing, authoritative history across machines and sessions; the merged file is copied back locally so the two stay in sync. If you ever see the Gitea changelog with only the latest change, it means step 0 (`pull-skill`) was skipped. ## Backfilling existing parts When the user wants a newly-added parameter applied to parts of that typeid already in Gitea: ```bash python scripts/gitea_components.py checkout --dest work/ python scripts/gitea_components.py list-type --typeid --root work/ --json ``` `list-type` lists every existing part of that typeid with the files in its folder — including its datasheet, which is co-located. For each one: read that datasheet, re-extract the values (including the new parameter), and rebuild its per-part sheet in place: ```bash python scripts/fill_templates.py .json \ --template assets/template/template.xlsx --dest work/// ``` Because `fill_templates` uses the current template and current versions, each rebuilt sheet picks up the new column and the bumped version automatically. When all are done, push once and tell the user the previous sheets were updated: ```bash python scripts/gitea_components.py commit-push --root work/ --message "backfill into " ``` Then copy each rebuilt part folder into the connected folder (see *Copy the pushed folder to the connected folder*) so the operator's local copies reflect the backfill too. ## Updating an existing part When the duplicate check finds the part (or the user asks to revise one that's already in Gitea), you're **editing a part in place** rather than assembling a new one. The mechanics are the same edit-in-a-checkout pattern as backfill — the difference is you're changing one part's own data, not applying a template change across a whole typeid. The guiding rule doesn't change: nothing is overwritten until the engineer has verified the new version. 1. **Locate the part in a checkout you can commit.** Clone the library repo, then find the part's folder and typeid (recovered from its tag): ```bash python scripts/gitea_components.py checkout --dest work/ python scripts/gitea_components.py find-part --mpn --make --root work/ --json ``` `find-part` returns the part's Class, tag, typeid, the editable folder path under `work/`, and the files in it (its current `.xlsx`, datasheet, symbol, footprint). If it prints `NOT FOUND` (exit 4), the part isn't actually there — treat it as a **new** part and go back to the normal add flow from step 2. 2. **Decide with the user what's changing.** A part update can revise any of: the **parameter values** (re-read the datasheet, or a corrected/newer one), the **symbol/footprint**, or the **datasheet PDF** itself. Ask which, so you only touch what's meant to change and leave the rest of the folder intact. 3. **Apply the change in place, in `work///`.** A key thing to understand first: `fill_templates` **rewrites the whole data row**, so it fills the four design columns (`Library Ref/Path`, `Footprint Ref/Path`) from the `--design` map you give it — and leaves them **blank if you don't**. When you rebuild a sheet, always re-supply the design values, or you'll silently wipe the symbol/footprint refs that were already there. The symbol and footprint files live in the part folder, so re-deriving them is cheap: point `altium_refs` at whatever the folder will hold **after** your change. - **Values** (re-read the datasheet, or a corrected/newer one) → read the current `.xlsx` and the folder's datasheet so you start from what's there, re-extract into a fresh `part.json` (Description still built to `references/description_format.md`), re-derive the design columns from the folder's existing symbol/footprint, and rebuild the sheet in place **with `--design`**: ```bash python scripts/altium_refs.py design \ --symbol work///.SchLib \ --footprint work///.PcbLib > design.json python scripts/fill_templates.py part.json \ --template assets/template/template.xlsx --dest work/// --design design.json ``` Because `fill_templates` reads the **current** template and versions, the rebuilt sheet keeps this typeid's version stamp (a part-data fix isn't a template change, so nothing bumps) and picks up any columns the typeid has gained — while the `--design` map carries the existing symbol/footprint refs through unchanged. (If a part somehow has no symbol/footprint yet, there's nothing to preserve — rebuild without `--design`.) - **Symbol/footprint** → copy the new `.SchLib`/`.PcbLib` into the folder under their proper names (replacing the old ones), then re-derive against the **new** files and rebuild with `--design` exactly as above (this is step 6 of the add flow). Still flag a genuine symbol/footprint-vs-part mismatch to the user rather than silently accepting it. **Rebuild the IntLib whenever the symbol or footprint changed.** The `.IntLib` embeds copies of the SchLib and PcbLib, so any update that touches either one leaves the old IntLib stale. After the `.SchLib` is re-filled (and the footprint is in place), recompile it before pushing: ```bash python scripts/build_intlib.py \ --schlib work///.SchLib \ --pcblib work///.PcbLib \ --out work///.IntLib ``` A values-only or datasheet-only update (symbol and footprint unchanged) doesn't require an IntLib rebuild — the embedded libraries are still current — though rebuilding is harmless. Then `commit-push` as usual, and the connected-folder copy carries the refreshed IntLib too.