Skill/SKILL.md

611 lines
35 KiB
Markdown

---
name: library-manager
description: >-
Manage Vecmocon's component library. Extract parameters from a component datasheet PDF into the
per-typeid Excel template (125 type-IDs across 18 classes). Checks Gitea for a duplicate
MPN_make, classifies to a typeid, updates that typeid's template (versioning, changelog,
backfill), fills a per-part workbook, loops on 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 on a duplicate, and can WRITE the mandatory SOP parameters directly
into a .SchLib symbol (Value, Manufacturer, Operating Temperature, Tolerance, RoHS, Datasheet,
...) while stripping Ultra-Librarian defaults. Every Description follows 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", or "\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.)
## 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:
```
<MPN>_<make>_<typeid> 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/
<Class>/ e.g. Diode, IC, Transistor, Resistor, ...
<MPN>_<make>_<typeid>/ e.g. BAT46WJ_Nexperia_SCH
<MPN>_<make>_<typeid>.xlsx this part's own one-row parameter sheet
<MPN>_data.pdf the datasheet
<symbol>.SchLib user-provided
<footprint>.PcbLib user-provided
```
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":"<Name>","email":"<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 "<Name> <<email>>"` to every `push-part` / `push-skill` / `commit-push`, and
- pass `--by "<Name>"` 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 <Name>)`, 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 <MPN> --make <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 <typeid> \
--param "New Parameter(unit)" [--param "Another(unit)"] \
--desc "why these were added" --by "<operator name>"
```
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 — a type prefix, the defining parameters in a fixed order, package near the
end, optional AEC-Q last (e.g. `CHIP_RES_36kΩ_62.2mW_±0.1%_0402`, `CHIP_CAP_2.2uF_100v_±10%_1210_x7r`,
`SCH_100V_0.25A_SOD-323F`). `references/description_format.md` defines the format for **every**
type in the library: the four the Altium SOP spells out (Resistor, Capacitor, Zener, TVS) are
**strict**; the rest are the house extension on the same basis. 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":"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` → prefix `SCH`, format `SCH_Vr_Io_Package`, so a 100 V / 250 mA Schottky in
SOD-323F becomes `SCH_100V_0.25A_SOD-323F`.)
```bash
python scripts/fill_templates.py part.json \
--template assets/template/template.xlsx --dest <stage>/<tag>/
```
This writes `<tag>.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.
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.
### 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 <uploaded_symbol> <stage>/<tag>/<symbol_name>.SchLib
cp <uploaded_footprint> <stage>/<tag>/<footprint_name>.PcbLib
python scripts/altium_refs.py design \
--symbol <stage>/<tag>/<symbol_name>.SchLib \
--footprint <stage>/<tag>/<footprint_name>.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 <stage>/<tag>/ --design design.json
```
Once you have the symbol and its Library Ref, you can also produce the **mandatory symbol
parameters** the SOP requires on the `.SchLib` (Manufacturer, Manufacturer Part, Value,
Tolerance, Operating Temperature, RoHS, Datasheet, Process, Vecmocon Part Code, …) — see
*Mandatory symbol parameters* below. This is optional per run but is how the datasheet values
land in the Altium symbol's properties.
### 7. Assemble the part folder
The staging folder `<tag>/` should now hold the four files: the per-part `<tag>.xlsx`, the
datasheet (name it `<MPN>_data.<ext>`), the symbol, and the footprint.
### 8. Push to the library repo, under the part's Class
```bash
python scripts/gitea_components.py push-part --folder <stage>/<tag> --typeid <typeid> \
--author "<operator name> <<operator email>>"
```
This places the folder at `components/<Class>/<tag>/` — creating the Class folder if it
doesn't exist yet, or pushing into it if it does — and commits and pushes. Confirm to the
user where it landed.
## Mandatory symbol parameters (.SchLib)
The SOP (§5) requires every schematic symbol to carry a fixed parameter set in its Altium
properties — `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 `Manufacturer_Name` / `Manufacturer_Part_Number`
defaults that duplicate the SOP fields). Three kinds of value:
- **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`, `Datasheet`, and `Process` by inference from the package.
- **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.)
- **Ask the engineer** — only `Vecmocon Part Code` (internal, not derivable).
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`.
Write the **full parameter set** — the typeid template's engineering columns **plus** the SOP
params above (see `references/schlib_parameters.md`). Collect your filled values into a
`params.json` and pass `--typeid` so the writer guarantees every template column is present
(blank where the datasheet is silent):
```bash
python scripts/schlib_write.py --schlib <in>.SchLib --params params.json \
--out <stage>/<tag>/<sym>.SchLib --typeid <TYPEID>
```
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.
## Submitting to Altium 365 as Part Requests (web)
If the org's central library is a managed **Altium 365 Workspace** (not the Gitea repos), the
skill's end task can submit each finished component as a **Part Request** through the Workspace
web UI, using browser automation (Claude-in-Chrome) in the operator's own signed-in Chrome — no
API token, no admin rights. A librarian then approves each request into the library.
This runs over **every component processed in the run**: the skill writes a `part_requests.json`
manifest (one entry per component — manufacturer, MPN, Description, component type, the full
parameter set, and the local paths to its `.SchLib`/`.PcbLib`/datasheet), then the browser step
loops it, filling and submitting the form for each. On the **first** component, fill everything
and stop at Save for the operator to review; once confirmed, Save and loop the rest, logging each
Request Id. Full field mapping, prerequisites, and the exact browser steps are in
`references/part_request_web.md` — read it before driving the browser.
Because the files upload from local disk, commit each component's `.SchLib`/`.PcbLib`/datasheet
to the operator's machine (device bridge) first, and put those local paths in the manifest.
### The end-to-end workflow (this is the standard run)
When the central library is a managed Altium 365 Workspace, a full run goes:
1. **Datasheet + make** → extract parameters, classify to a typeid, fill the per-part Excel
workbook, human-verify (the existing flow).
2. **Operator provides the `.SchLib` + `.PcbLib`** → write the full parameter set into the
`.SchLib` with `schlib_write.py --typeid <ID>` (template columns + SOP params, plus the
ComponentDescription), exactly as before.
3. **Push to Gitea** (part folder + symbol/footprint, per the normal push steps).
4. **Prepare the Altium 365 Part Request and hand it to the local runner** — the automatic tail:
a. Build the manifest:
```bash
python scripts/build_part_request_manifest.py --params <part params.json> --typeid <ID> \
--file <inbox>/<MPN>.SchLib --file <inbox>/<MPN>.PcbLib --file <inbox>/<MPN>_datasheet.pdf \
--out <inbox>/part_requests.json
```
(`build_part_request_manifest.py` maps the typeid to the Workspace Component Type — e.g.
`CER → Capacitors` — pulls manufacturer/MPN/Description/parameters from the params, and lists
the local attachment paths.)
b. **Commit the `.SchLib`, `.PcbLib`, datasheet, and `part_requests.json` into the operator's
Altium Runner inbox** on their PC via the device bridge (the `inbox` folder in
`runner_config.json`, e.g. `C:/Altium Runner/inbox`). The manifest's `files` paths must be
those on-disk inbox paths.
5. **The local runner finishes it, hands-off.** `scripts/local_runner.py` (installed once on the
operator's machine as a Task Scheduler job at logon) watches the inbox, ensures Chrome is up
with the debug port on the signed-in profile, and runs `altium365_part_request.py`, which
**fills the Part Request completely — fields, parameters, and all three attachments — and
leaves it on screen without saving** (review mode). The operator just **reviews and clicks
Save**. Nothing is submitted without their click.
So the operator's only actions per component are giving the datasheet+make, then the
`.SchLib`+`.PcbLib`, then reviewing the filled Part Request and clicking Save. Everything between
is automatic, and the submission step costs zero Claude tokens. Standing requirements: the
operator's machine is on, the runner is installed, and its Chrome profile has been signed into the
Workspace once (the runner relaunches Chrome with the debug port and reuses the session).
## 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.
## 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 <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 <that_part>.json \
--template assets/template/template.xlsx --dest work/<Class>/<that_tag>/
```
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 <param> into <typeid>"
```
## 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 <MPN> --make <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 `<tag>.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/<Class>/<tag>/`.** 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
`<tag>.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/<Class>/<tag>/<symbol>.SchLib \
--footprint work/<Class>/<tag>/<footprint>.PcbLib > design.json
python scripts/fill_templates.py part.json \
--template assets/template/template.xlsx --dest work/<Class>/<tag>/ --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 — that's a correctness issue.
- **Datasheet** → drop the newer PDF in as `<MPN>_data.<ext>`, replacing the old one. If the
values should reflect it, also redo the values step above.
4. **Verify, then push.** Hand the rebuilt `<tag>.xlsx` back and run the same human
verification loop (step 5) — the engineer is still the ground truth. Once confirmed, commit
the checkout and push (attributed to the operator, with a message that says it's an update):
```bash
python scripts/gitea_components.py commit-push --root work/ \
--message "update <tag>: <what changed> (by <operator name>)" \
--author "<operator name> <<operator email>>"
```
The authored commit and message are the record of the revision (visible in `git log`, the
Gitea commit view and `git blame`); the changelog stays reserved for template/version
changes, not per-part data fixes. Tell the user what changed and where it landed.
## Pushing the skill repo
When skill files change (a new typeid template, a parameter add, a version/changelog bump),
push the skill's own files to the skill repo with `push-skill` **automatically** (no
confirmation):
```bash
python scripts/gitea_components.py push-skill --author "<operator name> <<operator email>>" \
--message "Sync skill files + changelog"
```
`push-skill` clones the skill repo, copies the skill files in with the **`GIT_TOKEN` blanked
out** (the real token never leaves the machine), and **merges** `CHANGELOG.xlsx` — appending
this run's new rows onto the changelog already in Gitea so earlier entries are preserved — then
writes the merged changelog back locally. (The older `push_to_gitea.sh` still exists for a
plain flat push, but it does not merge the changelog or blank the token, so prefer
`push-skill` for the skill repo.)
## Resources
- `assets/template/template.xlsx` — the master template: one sheet per **typeid** (125),
source of every sheet's headers, styling and order. Columns A/B/C are always
`MPN_make_type` / `Skill Version` / `Template Version`; `Library Ref/Path`,
`Footprint Ref/Path` and `Manufacturer` sit near the end.
- `assets/template/Type_ID.xlsx` + `references/taxonomy.md` — Class → Subclass → Type ID.
- `references/description_format.md` — Vecmocon's Altium **Description Format** (the `_`-joined
engineering string for each part's Description column). Defines a format for **every** type:
the four SOP-defined ones (RES/CAP/Zener/TVS) are strict, the rest are the house extension on
the same basis. Read it before filling any Description.
- `references/schlib_parameters.md` — the SOP **mandatory symbol parameters** (§5) for the
`.SchLib`: the parameter set, where each value comes from, and how the generated Altium script
stamps them onto the symbol.
- `references/part_request_web.md` — submitting finished components to a managed Altium 365
Workspace as **Part Requests** (no token/admin): prerequisites, per-component field mapping,
the `part_requests.json` manifest, the captured form selectors, and both routes (live
Claude-in-Chrome, or the standalone Selenium script).
- `scripts/altium365_part_request.py` — standalone **Selenium** submitter: attaches to the
operator's signed-in Chrome and loops the `part_requests.json` manifest, filling and saving each
Part Request. Token-free per component; the token-economical end task for a whole library.
- `scripts/build_part_request_manifest.py` — build/append `part_requests.json` from a component's
schlib `params.json` + local file paths; maps typeid → Altium Component Type. Run at the end of
a run, before committing the manifest+files to the operator's inbox.
- `scripts/local_runner.py` (+ `runner_config.example.json`) — the operator installs this **once**
on their machine; it watches the inbox for manifests the skill drops there, ensures Chrome's
debug session, and runs the Selenium submitter in **review mode** (fills everything, leaves the
form for the operator to review + Save) — the automatic, hands-off tail of every run.
- `assets/template/versions.json` — per-typeid `template_version` + `skill_version`.
- `assets/CHANGELOG.xlsx` — global version/parameter changelog (created on first add;
merged into the skill repo's copy in Gitea by `push-skill`).
- `scripts/common.py` — taxonomy loader (`load_taxonomy`, `class_folder`), version store
(`get_versions`, `version_labels`, `bump_versions`), and the tag helper (`part_tag`).
- `scripts/fill_templates.py` — build one per-part `<tag>.xlsx` (version-stamped); reused for
backfill.
- `scripts/append_parameter.py` — append parameter(s) to a typeid, bump its versions, write
the changelog.
- `scripts/altium_refs.py` — read Library/Footprint Ref from `.SchLib`/`.PcbLib`.
- `scripts/schlib_write.py` — write the SOP mandatory parameters **directly into a `.SchLib`**
(pure-Python OLE rebuild; removes the Ultra-Librarian `Manufacturer_Name` /
`Manufacturer_Part_Number` defaults). Primary path; see `references/schlib_parameters.md`.
- `scripts/altium_params.py` — fallback: generate an Altium DelphiScript that stamps the same
parameters onto a `.SchLib` from inside Altium (DXP → Run Script).
- `scripts/gitea_components.py``check-mpn`, `find-part` (locate an existing part to
update), `checkout`, `list-type`, `place-part`, `commit-push`, `push-part` (library repo),
and `push-skill` (skill repo: token-blanked push + append-only changelog merge).
- `scripts/push_to_gitea.sh` — push a folder's contents to a Gitea repo (used for the skill
repo).
- `config/gitea.env` — host, user, token, and the `SKILL_REPO` / `LIBRARY_REPO` names
(**secret** — do not push the token).